Refactor power operations

Refactored power operations to use checkHostStatus function
that checks host_status property provided by dataService,
instead of polling for host/chassis status.
Added property to check when a power operation is in progress.
This property will show/hide the in progress message and make
other operations unavailable until the operation completes.
We were previously checking whether the server_state property
was set to 'Unreachable', which enables the Power on button in
the middle of a reboot, since the server_state changes to 'Off'
during reboot.

- Removed unused $timeout service

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I234749a9a875c7370b52bd23ed74d6e9617cf5e2
diff --git a/app/common/services/api-utils.js b/app/common/services/api-utils.js
index 5a28294..0b1aa46 100644
--- a/app/common/services/api-utils.js
+++ b/app/common/services/api-utils.js
@@ -26,31 +26,10 @@
       var SERVICE = {
         API_CREDENTIALS: Constants.API_CREDENTIALS,
         API_RESPONSE: Constants.API_RESPONSE,
-        CHASSIS_POWER_STATE: Constants.CHASSIS_POWER_STATE,
         HOST_STATE_TEXT: Constants.HOST_STATE,
         LED_STATE: Constants.LED_STATE,
         LED_STATE_TEXT: Constants.LED_STATE_TEXT,
         HOST_SESSION_STORAGE_KEY: Constants.API_CREDENTIALS.host_storage_key,
-        getChassisState: function() {
-          var deferred = $q.defer();
-          $http({
-            method: 'GET',
-            url: DataService.getHost() +
-                '/xyz/openbmc_project/state/chassis0/attr/CurrentPowerState',
-            withCredentials: true
-          })
-              .then(
-                  function(response) {
-                    var json = JSON.stringify(response.data);
-                    var content = JSON.parse(json);
-                    deferred.resolve(content.data);
-                  },
-                  function(error) {
-                    console.log(error);
-                    deferred.reject(error);
-                  });
-          return deferred.promise;
-        },
         validIPV4IP: function(ip) {
           // Checks for [0-255].[0-255].[0-255].[0-255]
           return ip.match(
diff --git a/app/common/services/constants.js b/app/common/services/constants.js
index dabbc77..ae82e76 100644
--- a/app/common/services/constants.js
+++ b/app/common/services/constants.js
@@ -31,12 +31,6 @@
           'location': '/redfish/v1/AccountService/LDAP/Certificates'
         }
       ],
-      CHASSIS_POWER_STATE: {
-        on: 'On',
-        on_code: 'xyz.openbmc_project.State.Chassis.PowerState.On',
-        off: 'Off',
-        off_code: 'xyz.openbmc_project.State.Chassis.PowerState.Off'
-      },
       HOST_STATE_TEXT: {
         on: 'Running',
         on_code: 'xyz.openbmc_project.State.Host.HostState.Running',
@@ -104,15 +98,12 @@
       TIMEOUT: {
         ACTIVATION: 1000 * 60 * 10,        // 10 mins
         DOWNLOAD_IMAGE: 1000 * 60 * 2,     // 2 mins
-        CHASSIS_OFF: 1000 * 60 * 5,        // 5 mins
         HOST_ON: 1000 * 60 * 5,            // 5 mins
         HOST_OFF: 1000 * 60 * 5,           // 5 mins
         HOST_OFF_IMMEDIATE: 1000 * 60 * 2  // 2 mins
       },
       MESSAGES: {
         POLL: {
-          CHASSIS_OFF_TIMEOUT:
-              'Time out. Chassis did not reach power off state in allotted time.',
           HOST_ON_TIMEOUT:
               'Time out. System did not reach Running state in allotted time.',
           HOST_OFF_TIMEOUT:
diff --git a/app/server-control/controllers/power-operations-controller.html b/app/server-control/controllers/power-operations-controller.html
index 2fcb95b..ddf8bda 100644
--- a/app/server-control/controllers/power-operations-controller.html
+++ b/app/server-control/controllers/power-operations-controller.html
@@ -17,38 +17,42 @@
         <div class="row column">
             <h3 class="subhead">Select a power operation</h3>
         </div>
-        <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 || dataService.loading, transitionAll: confirm}">
-            <button id="power__power-on" class="btn  btn-secondary" ng-click="powerOn()" role="button" ng-disabled="dataService.server_unreachable">
-                <icon file="icon-power.svg"></icon>Power on
-            </button>
-            <p class="inline">Attempts to power on the server</p>
+        <div ng-if="operationPending">
+            <span class="inactive-message">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>
         </div>
-        <!-- Power reboot/shutdown options : when server is off all of these are hidden. When one option is selected, the others are disabled. -->
-        <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmWarmReboot) || dataService.loading, transitionAll: confirm && confirmWarmReboot}">
-            <button id="power__warm-boot" class="btn btn-secondary" ng-click="warmRebootConfirm()" role="button" ng-disabled="dataService.server_unreachable">
-                <icon file="icon-restart.svg"></icon>Warm reboot</button>
-            <p class="inline">Attempts to perform an orderly shutdown before restarting the server</p>
-            <confirm title="warm reboot" confirm="confirmWarmReboot" ng-show="confirmWarmReboot" callback="warmReboot"></confirm>
-        </div>
-        <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmColdReboot) || dataService.loading, transitionAll: confirm && confirmColdReboot}">
-            <button id="power__cold-boot" class="btn  btn-secondary" ng-click="coldRebootConfirm()" role="button" ng-disabled="dataService.server_unreachable">
-                <icon file="icon-restart.svg"></icon>Cold reboot</button>
-            <p class="inline">Shuts down the server immediately, then restarts it</p>
-            <confirm title="cold reboot" confirm="confirmColdReboot" ng-show="confirmColdReboot" cancel="coldbootCancel" callback="coldReboot"></confirm>
-        </div>
-        <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmOrderlyShutdown) || dataService.loading, transitionAll: confirm && confirmOrderlyShutdown}">
-            <button id="power__soft-shutdown" class="btn  btn-secondary" ng-click="orderlyShutdownConfirm()" role="button" ng-disabled="dataService.server_unreachable">
-                <icon file="icon-power.svg"></icon>Orderly shutdown</button>
-            <p class="inline">Attempts to stop all software on the server before removing power</p>
-            <confirm title="orderly shutdown" confirm="confirmOrderlyShutdown" ng-show="confirmOrderlyShutdown" cancel="orderlyShutdownCancel" callback="orderlyShutdown"></confirm>
-        </div>
-        <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmImmediateShutdown) || dataService.loading, transitionAll: confirm && confirmImmediateShutdown}">
-            <button id="power__hard-shutdown" class="btn btn-secondary" ng-click="immediateShutdownConfirm()" role="button" ng-disabled="dataService.server_unreachable">
-                <icon file="icon-power.svg"></icon>Immediate shutdown</button>
-            <p class="inline">Removes power from the server without waiting for software to stop</p>
-            <confirm title="immediate shutdown" confirm="confirmImmediateShutdown" ng-show="confirmImmediateShutdown" cancel="immediatelyShutdownCancel" callback="immediateShutdown"></confirm>
+        <div ng-if="!operationPending">
+            <!-- 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 || dataService.loading, transitionAll: confirm}">
+                <button id="power__power-on" class="btn  btn-secondary" ng-click="powerOn()" role="button" ng-disabled="dataService.server_unreachable">
+                    <icon file="icon-power.svg"></icon>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. -->
+            <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmWarmReboot) || dataService.loading, transitionAll: confirm && confirmWarmReboot}">
+                <button id="power__warm-boot" class="btn btn-secondary" ng-click="warmRebootConfirm()" role="button" ng-disabled="dataService.server_unreachable">
+                    <icon file="icon-restart.svg"></icon>Warm reboot</button>
+                <p class="inline">Attempts to perform an orderly shutdown before restarting the server</p>
+                <confirm title="warm reboot" confirm="confirmWarmReboot" ng-show="confirmWarmReboot" callback="warmReboot"></confirm>
+            </div>
+            <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmColdReboot) || dataService.loading, transitionAll: confirm && confirmColdReboot}">
+                <button id="power__cold-boot" class="btn  btn-secondary" ng-click="coldRebootConfirm()" role="button" ng-disabled="dataService.server_unreachable">
+                    <icon file="icon-restart.svg"></icon>Cold reboot</button>
+                <p class="inline">Shuts down the server immediately, then restarts it</p>
+                <confirm title="cold reboot" confirm="confirmColdReboot" ng-show="confirmColdReboot" cancel="coldbootCancel" callback="coldReboot"></confirm>
+            </div>
+            <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmOrderlyShutdown) || dataService.loading, transitionAll: confirm && confirmOrderlyShutdown}">
+                <button id="power__soft-shutdown" class="btn  btn-secondary" ng-click="orderlyShutdownConfirm()" role="button" ng-disabled="dataService.server_unreachable">
+                    <icon file="icon-power.svg"></icon>Orderly shutdown</button>
+                <p class="inline">Attempts to stop all software on the server before removing power</p>
+                <confirm title="orderly shutdown" confirm="confirmOrderlyShutdown" ng-show="confirmOrderlyShutdown" cancel="orderlyShutdownCancel" callback="orderlyShutdown"></confirm>
+            </div>
+            <div class="column power-option" ng-hide="dataService.server_state == 'Off' || dataService.server_state == 'Unreachable'" ng-class="{disabled: dataService.server_unreachable || (confirm && !confirmImmediateShutdown) || dataService.loading, transitionAll: confirm && confirmImmediateShutdown}">
+                <button id="power__hard-shutdown" class="btn btn-secondary" ng-click="immediateShutdownConfirm()" role="button" ng-disabled="dataService.server_unreachable">
+                    <icon file="icon-power.svg"></icon>Immediate shutdown</button>
+                <p class="inline">Removes power from the server without waiting for software to stop</p>
+                <confirm title="immediate shutdown" confirm="confirmImmediateShutdown" ng-show="confirmImmediateShutdown" cancel="immediatelyShutdownCancel" callback="immediateShutdown"></confirm>
+            </div>
         </div>
     </div>
 </div>
diff --git a/app/server-control/controllers/power-operations-controller.js b/app/server-control/controllers/power-operations-controller.js
index 9713d21..986ac3b 100644
--- a/app/server-control/controllers/power-operations-controller.js
+++ b/app/server-control/controllers/power-operations-controller.js
@@ -10,11 +10,10 @@
   'use strict';
 
   angular.module('app.serverControl').controller('powerOperationsController', [
-    '$scope', 'APIUtils', 'dataService', 'Constants', '$timeout', '$interval',
-    '$q', 'toastService',
+    '$scope', 'APIUtils', 'dataService', 'Constants', '$interval', '$q',
+    'toastService',
     function(
-        $scope, APIUtils, dataService, Constants, $timeout, $interval, $q,
-        toastService) {
+        $scope, APIUtils, dataService, Constants, $interval, $q, toastService) {
       $scope.dataService = dataService;
       // Is a || of the other 4 "confirm" variables to ensure only
       // one confirm is shown at a time.
@@ -25,35 +24,39 @@
       $scope.confirmImmediateShutdown = false;
       $scope.loading = true;
 
-      var pollChassisStatusTimer = undefined;
-      var pollStartTime = null;
+      // When a power operation is in progress, set to true,
+      // when a power operation completes (success/fail) set to false.
+      // This property is used to show/hide the 'in progress' message
+      // in markup.
+      $scope.operationPending = false;
 
       /**
        * Checks the host status provided by the dataService using an
        * interval timer
        * @param {string} statusType : host status type to check for
-       * @param {number} timeout : timeout limit
-       * @param {string} error : error message
+       * @param {number} timeout : timeout limit, defaults to 5 minutes
+       * @param {string} error : error message, defaults to 'Time out'
        * @returns {Promise} : returns a deferred promise that will be fulfilled
        * if the status is met or be rejected if the timeout is reached
        */
-      var checkHostStatus = (statusType, timeout, error = 'Time out.') => {
-        const deferred = $q.defer();
-        const start = new Date();
-        const checkHostStatusInverval = $interval(() => {
-          let now = new Date();
-          let timePassed = now.getTime() - start.getTime();
-          if (timePassed > timeout) {
-            deferred.reject(error);
-            $interval.cancel(checkHostStatusInverval);
-          }
-          if (dataService.server_state === statusType) {
-            deferred.resolve();
-            $interval.cancel(checkHostStatusInverval);
-          }
-        }, Constants.POLL_INTERVALS.POWER_OP);
-        return deferred.promise;
-      };
+      var checkHostStatus =
+          (statusType, timeout = 300000, error = 'Time out.') => {
+            const deferred = $q.defer();
+            const start = new Date();
+            const checkHostStatusInverval = $interval(() => {
+              let now = new Date();
+              let timePassed = now.getTime() - start.getTime();
+              if (timePassed > timeout) {
+                deferred.reject(error);
+                $interval.cancel(checkHostStatusInverval);
+              }
+              if (dataService.server_state === statusType) {
+                deferred.resolve();
+                $interval.cancel(checkHostStatusInverval);
+              }
+            }, Constants.POLL_INTERVALS.POWER_OP);
+            return deferred.promise;
+          };
 
       APIUtils.getLastPowerTime()
           .then(
@@ -76,71 +79,56 @@
             (dataService.server_state == 'Running') ? 'Off' : 'Running';
       };
 
-      $scope.powerOn = function() {
-        $scope.loading = true;
+      /**
+       * Initiate Power on
+       */
+      $scope.powerOn = () => {
+        $scope.operationPending = true;
         dataService.setUnreachableState();
         APIUtils.hostPowerOn()
-            .then(function(response) {
-              return response;
+            .then(() => {
+              // Check for on state
+              return checkHostStatus(
+                  Constants.HOST_STATE_TEXT.on, Constants.TIMEOUT.HOST_ON,
+                  Constants.MESSAGES.POLL.HOST_ON_TIMEOUT);
             })
-            .then(function(lastStatus) {
-              return APIUtils.pollHostStatusTillOn();
-            })
-            .then(function(hostState) {
-              $scope.loading = false;
-            })
-            .catch(function(error) {
+            .catch((error) => {
+              console.log(error);
               toastService.error(Constants.MESSAGES.POWER_OP.POWER_ON_FAILED);
-              $scope.loading = false;
-            });
+            })
+            .finally(() => {
+              $scope.operationPending = false;
+            })
       };
 
-      function pollChassisStatusTillOff() {
-        var deferred = $q.defer();
-        pollChassisStatusTimer = $interval(function() {
-          var now = new Date();
-          if ((now.getTime() - pollStartTime.getTime()) >=
-              Constants.TIMEOUT.CHASSIS_OFF) {
-            $interval.cancel(pollChassisStatusTimer);
-            pollChassisStatusTimer = undefined;
-            deferred.reject(
-                new Error(Constants.MESSAGES.POLL.CHASSIS_OFF_TIMEOUT));
-          }
-          APIUtils.getChassisState()
-              .then(function(state) {
-                if (state === Constants.CHASSIS_POWER_STATE.off_code) {
-                  $interval.cancel(pollChassisStatusTimer);
-                  pollChassisStatusTimer = undefined;
-                  deferred.resolve(state);
-                }
-              })
-              .catch(function(error) {
-                $interval.cancel(pollChassisStatusTimer);
-                pollChassisStatusTimer = undefined;
-                deferred.reject(error);
-              });
-        }, Constants.POLL_INTERVALS.POWER_OP);
-
-        return deferred.promise;
-      }
-      $scope.warmReboot = function() {
-        $scope.loading = true;
+      /**
+       * Initiate Orderly reboot
+       * Attempts to stop all software
+       */
+      $scope.warmReboot = () => {
+        $scope.operationPending = true;
         dataService.setUnreachableState();
         APIUtils.hostReboot()
-            .then(function(response) {
-              return response;
+            .then(() => {
+              // Check for off state
+              return checkHostStatus(
+                  Constants.HOST_STATE_TEXT.off, Constants.TIMEOUT.HOST_OFF,
+                  Constants.MESSAGES.POLL.HOST_OFF_TIMEOUT);
             })
-            .then(function(lastStatus) {
-              return APIUtils.pollHostStatusTilReboot();
+            .then(() => {
+              // Check for on state
+              return checkHostStatus(
+                  Constants.HOST_STATE_TEXT.on, Constants.TIMEOUT.HOST_ON,
+                  Constants.MESSAGES.POLL.HOST_ON_TIMEOUT);
             })
-            .then(function(hostState) {
-              $scope.loading = false;
-            })
-            .catch(function(error) {
+            .catch((error) => {
+              console.log(error);
               toastService.error(
                   Constants.MESSAGES.POWER_OP.WARM_REBOOT_FAILED);
-              $scope.loading = false;
-            });
+            })
+            .finally(() => {
+              $scope.operationPending = false;
+            })
       };
 
       $scope.warmRebootConfirm = function() {
@@ -152,33 +140,40 @@
         $scope.confirmWarmReboot = true;
       };
 
-      $scope.coldReboot = function() {
-        $scope.loading = true;
+      /**
+       * Initiate Immediate reboot
+       * Does not attempt to stop all software
+       */
+      $scope.coldReboot = () => {
+        $scope.operationPending = true;
         dataService.setUnreachableState();
         APIUtils.chassisPowerOff()
-            .then(function() {
+            .then(() => {
+              // Check for off state
               return checkHostStatus(
                   Constants.HOST_STATE_TEXT.off,
                   Constants.TIMEOUT.HOST_OFF_IMMEDIATE,
                   Constants.MESSAGES.POLL.HOST_OFF_TIMEOUT);
             })
-            .then(function() {
+            .then(() => {
               return APIUtils.hostPowerOn();
             })
-            .then(function() {
+            .then(() => {
+              // Check for on state
               return checkHostStatus(
                   Constants.HOST_STATE_TEXT.on, Constants.TIMEOUT.HOST_ON,
                   Constants.MESSAGES.POLL.HOST_ON_TIMEOUT);
             })
-            .catch(function(error) {
+            .catch((error) => {
               console.log(error);
               toastService.error(
                   Constants.MESSAGES.POWER_OP.COLD_REBOOT_FAILED);
             })
-            .finally(function() {
-              $scope.loading = false;
+            .finally(() => {
+              $scope.operationPending = false;
             })
       };
+
       $scope.coldRebootConfirm = function() {
         if ($scope.confirm) {
           return;
@@ -187,29 +182,30 @@
         $scope.confirmColdReboot = true;
       };
 
-      $scope.orderlyShutdown = function() {
-        $scope.loading = true;
+      /**
+       * Initiate Orderly shutdown
+       * Attempts to stop all software
+       */
+      $scope.orderlyShutdown = () => {
+        $scope.operationPending = true;
         dataService.setUnreachableState();
         APIUtils.hostPowerOff()
-            .then(function(response) {
-              return response;
+            .then(() => {
+              // Check for off state
+              return checkHostStatus(
+                  Constants.HOST_STATE_TEXT.off, Constants.TIMEOUT.HOST_OFF,
+                  Constants.MESSAGES.POLL.HOST_OFF_TIMEOUT);
             })
-            .then(function(lastStatus) {
-              return APIUtils.pollHostStatusTillOff();
-            })
-            .then(function(hostState) {
-              pollStartTime = new Date();
-              return pollChassisStatusTillOff();
-            })
-            .then(function(chassisState) {
-              $scope.loading = false;
-            })
-            .catch(function(error) {
+            .catch((error) => {
+              console.log(error);
               toastService.error(
                   Constants.MESSAGES.POWER_OP.ORDERLY_SHUTDOWN_FAILED);
-              $scope.loading = false;
-            });
+            })
+            .finally(() => {
+              $scope.operationPending = false;
+            })
       };
+
       $scope.orderlyShutdownConfirm = function() {
         if ($scope.confirm) {
           return;
@@ -218,27 +214,34 @@
         $scope.confirmOrderlyShutdown = true;
       };
 
-      $scope.immediateShutdown = function() {
-        $scope.loading = true;
+      /**
+       * Initiate Immediate shutdown
+       * Does not attempt to stop all software
+       */
+      $scope.immediateShutdown = () => {
+        $scope.operationPending = true;
         dataService.setUnreachableState();
         APIUtils.chassisPowerOff()
-            .then(function(response) {
-              return response;
+            .then(() => {
+              // Check for off state
+              return checkHostStatus(
+                  Constants.HOST_STATE_TEXT.off,
+                  Constants.TIMEOUT.HOST_OFF_IMMEDIATE,
+                  Constants.MESSAGES.POLL.HOST_OFF_TIMEOUT);
             })
-            .then(function(lastStatus) {
-              pollStartTime = new Date();
-              return pollChassisStatusTillOff();
-            })
-            .then(function(chassisState) {
+            .then(() => {
               dataService.setPowerOffState();
-              $scope.loading = false;
             })
-            .catch(function(error) {
+            .catch((error) => {
+              console.log(error);
               toastService.error(
                   Constants.MESSAGES.POWER_OP.IMMEDIATE_SHUTDOWN_FAILED);
-              $scope.loading = false;
-            });
+            })
+            .finally(() => {
+              $scope.operationPending = false;
+            })
       };
+
       $scope.immediateShutdownConfirm = function() {
         if ($scope.confirm) {
           return;