Add fixes for power operations

Fixes applies to 'Power on', 'Warm reboot', 'Orderly shutdown',
and 'Immediate shutdown' power operations. Fixes include:

- Verifying that the chassis and host transition through the
  expected states for each power operation. During which time
  a spinner will be displayed. A timeout of 5 minutes for each
  state transition has been added.
- Displaying the current state of the host in the 'Server power'
  and 'Current status' bar.  The state displayed will change as
  the host transition through different states even as the
  spinner continues to spin.
- Displaying an error pop-up with reason for failure when the
  power operation fails.

Resolves openbmc/openbmc#2758
Resolves openbmc/openbmc#2787
Resolves openbmc/openbmc#2803

Change-Id: I514e030f9df7bd4d8b53634408f0449cd9abada9
Signed-off-by: CamVan Nguyen <ctnguyen@us.ibm.com>
diff --git a/app/server-control/controllers/bmc-reboot-controller.js b/app/server-control/controllers/bmc-reboot-controller.js
index f5e0923..3106823 100644
--- a/app/server-control/controllers/bmc-reboot-controller.js
+++ b/app/server-control/controllers/bmc-reboot-controller.js
@@ -27,7 +27,7 @@
                     $scope.confirm = true;
                 };
                 $scope.reboot = function(){
-                    dataService.setBootingState();
+                    dataService.setUnreachableState();
                     APIUtils.bmcReboot(function(response){
                         //@NOTE: using common event to reload server status, may be a better event listener name?
                         $scope.$emit('user-logged-in',{});
diff --git a/app/server-control/controllers/power-operations-controller.html b/app/server-control/controllers/power-operations-controller.html
index 876ce24..d20778d 100644
--- a/app/server-control/controllers/power-operations-controller.html
+++ b/app/server-control/controllers/power-operations-controller.html
@@ -24,7 +24,7 @@
         <span class="inactive-message" ng-show="dataService.server_state == 'Unreachable'">There are no power operations to display while a power operation is in progress. When complete, any new power operations will be displayed here.</span>
         <!-- Power on displays only when server is shutdown -->
         <div class="row column power-option" ng-hide="dataService.server_state == 'Running' || dataService.server_state == 'Quiesced' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !power_confirm) || dataService.loading, transitionAll: confirm && power_confirm}">
-            <button id="power__power-on" class="btn-secondary inline" ng-click="togglePower()" role="button" ng-disabled="dataService.server_unreachable"><img src="../../assets/images/icon-power.svg" alt="power on" aria-hidden="true">Power on</button>
+            <button id="power__power-on" class="btn-secondary inline" ng-click="powerOn()" role="button" ng-disabled="dataService.server_unreachable"><img src="../../assets/images/icon-power.svg" alt="power on" aria-hidden="true">Power on</button>
             <p class="inline">Attempts to power on the server</p>
         </div>
         <!-- Power reboot/shutdown options : when server is off all of these are hidden. When one option is selected, the others are disabled. -->
diff --git a/app/server-control/controllers/power-operations-controller.js b/app/server-control/controllers/power-operations-controller.js
index 461c1e7..989ca1b 100644
--- a/app/server-control/controllers/power-operations-controller.js
+++ b/app/server-control/controllers/power-operations-controller.js
@@ -19,8 +19,10 @@
             'Constants',
             '$timeout',
             '$interval',
+            '$interpolate',
             '$q',
-            function($scope, APIUtils, dataService, Constants, $timeout, $interval, $q){
+            function($scope, APIUtils, dataService, Constants, $timeout,
+                     $interval, $interpolate, $q){
                 $scope.dataService = dataService;
                 $scope.confirm = false;
                 $scope.power_confirm = false;
@@ -39,22 +41,24 @@
                     dataService.server_state = (dataService.server_state == 'Running') ? 'Off': 'Running';
                 }
 
-                $scope.togglePower = function(){
-                    var method = (dataService.server_state == 'Running') ? 'hostPowerOff' : 'hostPowerOn';
-                    //@TODO: show progress or set class orange
-                    APIUtils[method]().then(function(response){
-                        //update state based on response
-                        //error case?
-                        if(response == null){
-                            console.log("Failed request.");
-                        }else{
-                            //@TODO::need to get the server status
-                            if(dataService.server_state == 'Running'){
-                                dataService.setPowerOffState();
-                            }else{
-                                dataService.setPowerOnState();
-                            }
-                        }
+                $scope.powerOn = function(){
+                    $scope.loading = true;
+                    dataService.setUnreachableState();
+                    APIUtils.hostPowerOn().then(function(response){
+                        return response;
+                    }).then(function(lastStatus) {
+                        pollStartTime = new Date();
+                        return pollHostStatusTillOn();
+                    }).then(function(hostState) {
+                        $scope.loading = false;
+                    }).catch(function(error){
+                        dataService.activateErrorModal(
+                          {title: Constants.MESSAGES.POWER_OP.POWER_ON_FAILED,
+                           description: error.statusText ?
+                               $interpolate(Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)(
+                                   {status: error.status, description: error.statusText}) :
+                               error });
+                        $scope.loading = false;
                     });
                 }
                 $scope.powerOnConfirm = function(){
@@ -65,6 +69,16 @@
                     $scope.power_confirm = true;
                 };
 
+                function setHostState(state){
+                    if(state == Constants.HOST_STATE_TEXT.off_code){
+                        dataService.setPowerOffState();
+                    }else if(state == Constants.HOST_STATE_TEXT.on_code){
+                        dataService.setPowerOnState();
+                    }else{
+                        dataService.setErrorState();
+                    }
+                }
+
                 function pollChassisStatusTillOff(){
                     var deferred = $q.defer();
                     pollChassisStatusTimer = $interval(function(){
@@ -72,7 +86,7 @@
                         if((now.getTime() - pollStartTime.getTime()) >= Constants.TIMEOUT.CHASSIS_OFF){
                             $interval.cancel(pollChassisStatusTimer);
                             pollChassisStatusTimer = undefined;
-                            deferred.reject(new Error(Constants.MESSAGES.POLL.TIMEOUT));
+                            deferred.reject(new Error(Constants.MESSAGES.POLL.CHASSIS_OFF_TIMEOUT));
                         }
                         APIUtils.getChassisState().then(function(state){
                             if(state === Constants.CHASSIS_POWER_STATE.off_code){
@@ -96,13 +110,43 @@
                         if((now.getTime() - pollStartTime.getTime()) >= Constants.TIMEOUT.HOST_ON){
                             $interval.cancel(pollHostStatusTimer);
                             pollHostStatusTimer = undefined;
-                            deferred.reject(new Error(Constants.MESSAGES.POLL.TIMEOUT));
+                            deferred.reject(new Error(Constants.MESSAGES.POLL.HOST_ON_TIMEOUT));
                         }
                         APIUtils.getHostState().then(function(state){
+                            setHostState(state);
                             if(state === Constants.HOST_STATE_TEXT.on_code){
                                 $interval.cancel(pollHostStatusTimer);
                                 pollHostStatusTimer = undefined;
                                 deferred.resolve(state);
+                            }else if(state === Constants.HOST_STATE_TEXT.error_code){
+                                $interval.cancel(pollHostStatusTimer);
+                                pollHostStatusTimer = undefined;
+                                deferred.reject(new Error(Constants.MESSAGES.POLL.HOST_QUIESCED));
+                            }
+                        }).catch(function(error){
+                            $interval.cancel(pollHostStatusTimer);
+                            pollHostStatusTimer = undefined;
+                            deferred.reject(error);
+                        });
+                    }, Constants.POLL_INTERVALS.POWER_OP);
+
+                    return deferred.promise;
+                }
+                function pollHostStatusTillOff(){
+                    var deferred = $q.defer();
+                    pollHostStatusTimer = $interval(function(){
+                        var now = new Date();
+                        if((now.getTime() - pollStartTime.getTime()) >= Constants.TIMEOUT.HOST_OFF){
+                            $interval.cancel(pollHostStatusTimer);
+                            pollHostStatusTimer = undefined;
+                            deferred.reject(new Error(Constants.MESSAGES.POLL.HOST_OFF_TIMEOUT));
+                        }
+                        APIUtils.getHostState().then(function(state){
+                            setHostState(state);
+                            if(state === Constants.HOST_STATE_TEXT.off_code){
+                                $interval.cancel(pollHostStatusTimer);
+                                pollHostStatusTimer = undefined;
+                                deferred.resolve(state);
                             }
                         }).catch(function(error){
                             $interval.cancel(pollHostStatusTimer);
@@ -114,14 +158,26 @@
                     return deferred.promise;
                 }
                 $scope.warmReboot = function(){
-                    //@TODO:show progress
-                    dataService.setBootingState();
+                    $scope.loading = true;
+                    dataService.setUnreachableState();
                     APIUtils.hostReboot().then(function(response){
-                        if(response){
-                            dataService.setPowerOnState();
-                        }else{
-                            //@TODO:hide progress & show error message
-                        }
+                        return response;
+                    }).then(function(lastStatus) {
+                        pollStartTime = new Date();
+                        return pollHostStatusTillOff();
+                    }).then(function(hostState) {
+                        pollStartTime = new Date();
+                        return pollHostStatusTillOn();
+                    }).then(function(hostState) {
+                        $scope.loading = false;
+                    }).catch(function(error){
+                        dataService.activateErrorModal(
+                          {title: Constants.MESSAGES.POWER_OP.WARM_REBOOT_FAILED,
+                           description: error.statusText ?
+                               $interpolate(Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)(
+                                   {status: error.status, description: error.statusText}) :
+                               error });
+                        $scope.loading = false;
                     });
                 };
                 $scope.testState = function(){
@@ -142,23 +198,28 @@
 
                 $scope.coldReboot = function(){
                     $scope.loading = true;
-                    dataService.setBootingState();
+                    dataService.setUnreachableState();
                     APIUtils.chassisPowerOff().then(function(state){
                         return state;
                     }).then(function(lastState) {
                         pollStartTime = new Date();
                         return pollChassisStatusTillOff();
                     }).then(function(chassisState) {
-                        APIUtils.hostPowerOn().then(function(hostState){
+                        return APIUtils.hostPowerOn().then(function(hostState){
                             return hostState;
                         })
                     }).then(function(hostState) {
                         pollStartTime = new Date();
                         return pollHostStatusTillOn();
                     }).then(function(state) {
-                        dataService.setPowerOnState();
                         $scope.loading = false;
                     }).catch(function(error){
+                        dataService.activateErrorModal(
+                          {title: Constants.MESSAGES.POWER_OP.COLD_REBOOT_FAILED,
+                           description: error.statusText ?
+                               $interpolate(Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)(
+                                   {status: error.status, description: error.statusText}) :
+                               error });
                         $scope.loading = false;
                     });
                 };
@@ -171,13 +232,26 @@
                 };
 
                 $scope.orderlyShutdown = function(){
-                    //@TODO:show progress
+                    $scope.loading = true;
+                    dataService.setUnreachableState();
                     APIUtils.hostPowerOff().then(function(response){
-                        if(response){
-                            dataService.setPowerOffState();
-                        }else{
-                            //@TODO:hide progress & show error message
-                        }
+                        return response;
+                    }).then(function(lastStatus) {
+                        pollStartTime = new Date();
+                        return pollHostStatusTillOff()
+                    }).then(function(hostState) {
+                        pollStartTime = new Date();
+                        return pollChassisStatusTillOff();
+                    }).then(function(chassisState) {
+                        $scope.loading = false;
+                    }).catch(function(error){
+                        dataService.activateErrorModal(
+                          {title: Constants.MESSAGES.POWER_OP.ORDERLY_SHUTDOWN_FAILED,
+                           description: error.statusText ?
+                               $interpolate(Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)(
+                                   {status: error.status, description: error.statusText}) :
+                               error });
+                        $scope.loading = false;
                     });
                 };
                 $scope.orderlyShutdownConfirm = function(){
@@ -189,13 +263,24 @@
                 };
 
                 $scope.immediateShutdown = function(){
-                    //@TODO:show progress
-                    APIUtils.chassisPowerOff(function(response){
-                        if(response){
-                            dataService.setPowerOffState();
-                        }else{
-                            //@TODO:hide progress & show error message
-                        }
+                    $scope.loading = true;
+                    dataService.setUnreachableState();
+                    APIUtils.chassisPowerOff().then(function(response){
+                        return response;
+                    }).then(function(lastStatus) {
+                        pollStartTime = new Date();
+                        return pollChassisStatusTillOff();
+                    }).then(function(chassisState) {
+                        dataService.setPowerOffState();
+                        $scope.loading = false;
+                    }).catch(function(error){
+                        dataService.activateErrorModal(
+                          {title: Constants.MESSAGES.POWER_OP.IMMEDIATE_SHUTDOWN_FAILED,
+                           description: error.statusText ?
+                               $interpolate(Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)(
+                                   {status: error.status, description: error.statusText}) :
+                               error });
+                        $scope.loading = false;
                     });
                 };
                 $scope.immediateShutdownConfirm = function(){