Activate image and reboot host

When activating a host image, gives the option to:
  a) Activate image without booting
  b) Activate image and boot host. If server is
     in 'off' status the option is to power on.
     If the server is in 'unavailable', 'on', or
     'error' status the option is to reboot
Also updated polling during boot and reboot to use
/subscribe API.

Resolves openbmc/openbmc#3323

Tested: The GUI displays the options and boots /
   reboots appropriately based on server status.

Change-Id: I62c19e094ecc7112263c26844fcd30b2bf3c2b84
Signed-off-by: beccabroek <rebecca.shaw@ibm.com>
diff --git a/app/common/services/api-utils.js b/app/common/services/api-utils.js
index a9fb99a..25e4aef 100644
--- a/app/common/services/api-utils.js
+++ b/app/common/services/api-utils.js
@@ -9,8 +9,8 @@
 window.angular && (function(angular) {
   'use strict';
   angular.module('app.common.services').factory('APIUtils', [
-    '$http', 'Constants', '$q', 'dataService',
-    function($http, Constants, $q, DataService) {
+    '$http', 'Constants', '$q', 'dataService', '$interval',
+    function($http, Constants, $q, DataService, $interval) {
       var getScaledValue = function(value, scale) {
         scale = scale + '';
         scale = parseInt(scale, 10);
@@ -88,6 +88,112 @@
                 return response.data;
               });
         },
+        pollHostStatusTillOn: function() {
+          var deferred = $q.defer();
+          var hostOnTimeout = setTimeout(function() {
+            ws.close();
+            deferred.reject(new Error(Constants.MESSAGES.POLL.HOST_ON_TIMEOUT));
+          }, Constants.TIMEOUT.HOST_ON);
+
+          var ws =
+              new WebSocket('wss://' + DataService.server_id + '/subscribe');
+          var data = JSON.stringify({
+            'paths': ['/xyz/openbmc_project/state/host0'],
+            'interfaces': ['xyz.openbmc_project.State.Host']
+          });
+          ws.onopen = function() {
+            ws.send(data);
+          };
+          ws.onmessage = function(evt) {
+            var content = JSON.parse(evt.data);
+            var hostState = content.properties.CurrentHostState;
+            if (hostState === Constants.HOST_STATE_TEXT.on_code) {
+              clearTimeout(hostOnTimeout);
+              ws.close();
+              deferred.resolve();
+            } else if (hostState === Constants.HOST_STATE_TEXT.error_code) {
+              clearTimeout(hostOnTimeout);
+              ws.close();
+              deferred.reject(new Error(Constants.MESSAGES.POLL.HOST_QUIESCED));
+            }
+          };
+        },
+
+        pollHostStatusTilReboot: function() {
+          var deferred = $q.defer();
+          var onState = Constants.HOST_STATE_TEXT.on_code;
+          var offState = Constants.HOST_STATE_TEXT.on_code;
+          var hostTimeout;
+          var setHostTimeout = function(message, timeout) {
+            hostTimeout = setTimeout(function() {
+              ws.close();
+              deferred.reject(new Error(message));
+            }, timeout);
+          };
+          var ws =
+              new WebSocket('wss://' + DataService.server_id + '/subscribe');
+          var data = JSON.stringify({
+            'paths': ['/xyz/openbmc_project/state/host0'],
+            'interfaces': ['xyz.openbmc_project.State.Host']
+          });
+          ws.onopen = function() {
+            ws.send(data);
+          };
+          setHostTimeout(
+              Constants.MESSAGES.POLL.HOST_OFF_TIMEOUT,
+              Constants.TIMEOUT.HOST_OFF);
+          var pollState = offState;
+          ws.onmessage = function(evt) {
+            var content = JSON.parse(evt.data);
+            var hostState = content.properties.CurrentHostState;
+            if (hostState === pollState) {
+              if (pollState === offState) {
+                clearTimeout(hostTimeout);
+                pollState = onState;
+                setHostTimeout(
+                    Constants.MESSAGES.POLL.HOST_ON_TIMEOUT,
+                    Constants.TIMEOUT.HOST_ON);
+              }
+              if (pollState === onState) {
+                clearTimeout(hostTimeout);
+                ws.close();
+                deferred.resolve();
+              }
+            } else if (hostState === Constants.HOST_STATE_TEXT.error_code) {
+              clearTimeout(hostTimeout);
+              ws.close();
+              deferred.reject(new Error(Constants.MESSAGES.POLL.HOST_QUIESCED));
+            }
+          };
+        },
+
+        pollHostStatusTillOff: function() {
+          var deferred = $q.defer();
+          var hostOffTimeout = setTimeout(function() {
+            ws.close();
+            deferred.reject(
+                new Error(Constants.MESSAGES.POLL.HOST_OFF_TIMEOUT));
+          }, Constants.TIMEOUT.HOST_OFF);
+
+          var ws =
+              new WebSocket('wss://' + DataService.server_id + '/subscribe');
+          var data = JSON.stringify({
+            'paths': ['/xyz/openbmc_project/state/host0'],
+            'interfaces': ['xyz.openbmc_project.State.Host']
+          });
+          ws.onopen = function() {
+            ws.send(data);
+          };
+          ws.onmessage = function(evt) {
+            var content = JSON.parse(evt.data);
+            var hostState = content.properties.CurrentHostState;
+            if (hostState === Constants.HOST_STATE_TEXT.off_code) {
+              clearTimeout(hostOffTimeout);
+              ws.close();
+              deferred.resolve();
+            }
+          };
+        },
         getNetworkInfo: function() {
           var deferred = $q.defer();
           $http({
diff --git a/app/configuration/controllers/firmware-controller.html b/app/configuration/controllers/firmware-controller.html
index 4d3e2a3..e79913b 100644
--- a/app/configuration/controllers/firmware-controller.html
+++ b/app/configuration/controllers/firmware-controller.html
@@ -120,7 +120,7 @@
 		<div class="modal__content">
 			<div ng-switch on="activate_image_type">
 				<p ng-switch-when="BMC">When you activate the BMC firmware file, {{activate_image_version}}, the BMC must be rebooted before it will operate with the new firmware code. Note that when you reboot the BMC, the BMC will be unavailable for several minutes and you must log in again.</p>
-				<p ng-switch-when="Host">When you activate server firmware file, {{activate_image_version}}, the new firmware will not operate until the next time the server boots.</p>
+				<p ng-switch-when="Host">When you activate the server firmware file, {{activate_image_version}}, the new firmware will not operate until the next time the server boots.</p>
 			</div>
 			<form ng-if="activate_image_type == 'BMC'">
 				<fieldset>
@@ -138,6 +138,22 @@
 					</div>
 				</fieldset>
 			</form>
+			<form ng-if="activate_image_type == 'Host'">
+				<fieldset>
+					<div class="row column">
+						<label class="control-radio bold" for="activate-host-without-reboot">Activate firmware file without {{isServerOff() ? "booting" : "rebooting"}} server
+							<input type="radio" name="activate-host-without-reboot" id="activate-host-without-reboot" ng-model="activate.reboot" ng-value="false"/>
+							<span class="control__indicator control__indicator-on"></span>
+						</label>
+					</div>
+					<div class="row column">
+						<label class="control-radio bold" for="activate-host-with-reboot">Activate firmware file and automatically {{isServerOff() ? "boot" : "reboot"}} server
+							<input type="radio" name="activate-host-with-reboot" id="activate-host-with-reboot" ng-model="activate.reboot" ng-value="true"/>
+							<span class="control__indicator control__indicator-on"></span>
+						</label>
+					</div>
+				</fieldset>
+			</form>
 		</div>
 		<div class="modal__button-wrapper">
 			<button class="inline btn-secondary" ng-click="activate_confirm=false;">Cancel</button>
diff --git a/app/configuration/controllers/firmware-controller.js b/app/configuration/controllers/firmware-controller.js
index 80f3806..04a7c78 100644
--- a/app/configuration/controllers/firmware-controller.js
+++ b/app/configuration/controllers/firmware-controller.js
@@ -11,10 +11,10 @@
 
   angular.module('app.configuration').controller('firmwareController', [
     '$scope', '$window', 'APIUtils', 'dataService', '$location',
-    '$anchorScroll', 'Constants', '$interval', '$q', '$timeout',
+    '$anchorScroll', 'Constants', '$interval', '$q', '$timeout', '$interpolate',
     function(
         $scope, $window, APIUtils, dataService, $location, $anchorScroll,
-        Constants, $interval, $q, $timeout) {
+        Constants, $interval, $q, $timeout, $interpolate) {
       $scope.dataService = dataService;
 
       // Scroll to target anchor
@@ -125,7 +125,6 @@
                         });
                       })
                   .then(function(state) {
-                    // Only look at reboot if it's a BMC image
                     if ($scope.activate.reboot &&
                         ($scope.activate_image_type == 'BMC')) {
                       // Despite the new image being active, issue,
@@ -151,10 +150,65 @@
                             });
                       }, 10000);
                     }
+                    if ($scope.activate.reboot &&
+                        ($scope.activate_image_type == 'Host')) {
+                      // If image type being activated is a host image, the
+                      // current power status of the server determines if the
+                      // server should power on or reboot.
+                      if ($scope.isServerOff()) {
+                        powerOn();
+                      } else {
+                        warmReboot();
+                      }
+                    }
                   });
             });
         $scope.activate_confirm = false;
       };
+      function powerOn() {
+        dataService.setUnreachableState();
+        APIUtils.hostPowerOn()
+            .then(function(response) {
+              return response;
+            })
+            .then(function(lastStatus) {
+              return APIUtils.pollHostStatusTillOn();
+            })
+            .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
+              });
+            });
+      };
+      function warmReboot() {
+        $scope.uploading = true;
+        dataService.setUnreachableState();
+        APIUtils.hostReboot()
+            .then(function(response) {
+              return response;
+            })
+            .then(function(lastStatus) {
+              return APIUtils.pollHostStatusTilReboot();
+            })
+            .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.isServerOff = function() {
+        return dataService.server_state === Constants.HOST_STATE_TEXT.off;
+      };
 
       $scope.upload = function() {
         if ($scope.file) {
diff --git a/app/server-control/controllers/power-operations-controller.js b/app/server-control/controllers/power-operations-controller.js
index e36b61d..6c6f68c 100644
--- a/app/server-control/controllers/power-operations-controller.js
+++ b/app/server-control/controllers/power-operations-controller.js
@@ -25,7 +25,6 @@
       $scope.loading = true;
 
       var pollChassisStatusTimer = undefined;
-      var pollHostStatusTimer = undefined;
       var pollStartTime = null;
 
       //@TODO: call api and get proper state
@@ -59,8 +58,7 @@
               return response;
             })
             .then(function(lastStatus) {
-              pollStartTime = new Date();
-              return pollHostStatusTillOn();
+              return APIUtils.pollHostStatusTillOn();
             })
             .then(function(hostState) {
               $scope.loading = false;
@@ -113,68 +111,6 @@
 
         return deferred.promise;
       }
-
-      function pollHostStatusTillOn() {
-        var deferred = $q.defer();
-        pollHostStatusTimer = $interval(function() {
-          var now = new Date();
-          if ((now.getTime() - pollStartTime.getTime()) >=
-              Constants.TIMEOUT.HOST_ON) {
-            $interval.cancel(pollHostStatusTimer);
-            pollHostStatusTimer = undefined;
-            deferred.reject(new Error(Constants.MESSAGES.POLL.HOST_ON_TIMEOUT));
-          }
-          APIUtils.getHostState()
-              .then(function(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) {
-                if (state === Constants.HOST_STATE_TEXT.off_code) {
-                  $interval.cancel(pollHostStatusTimer);
-                  pollHostStatusTimer = undefined;
-                  deferred.resolve(state);
-                }
-              })
-              .catch(function(error) {
-                $interval.cancel(pollHostStatusTimer);
-                pollHostStatusTimer = undefined;
-                deferred.reject(error);
-              });
-        }, Constants.POLL_INTERVALS.POWER_OP);
-
-        return deferred.promise;
-      }
       $scope.warmReboot = function() {
         $scope.loading = true;
         dataService.setUnreachableState();
@@ -183,12 +119,7 @@
               return response;
             })
             .then(function(lastStatus) {
-              pollStartTime = new Date();
-              return pollHostStatusTillOff();
-            })
-            .then(function(hostState) {
-              pollStartTime = new Date();
-              return pollHostStatusTillOn();
+              return APIUtils.pollHostStatusTilReboot();
             })
             .then(function(hostState) {
               $scope.loading = false;
@@ -238,8 +169,7 @@
               });
             })
             .then(function(hostState) {
-              pollStartTime = new Date();
-              return pollHostStatusTillOn();
+              return APIUtils.pollHostStatusTillOn();
             })
             .then(function(state) {
               $scope.loading = false;
@@ -272,8 +202,7 @@
               return response;
             })
             .then(function(lastStatus) {
-              pollStartTime = new Date();
-              return pollHostStatusTillOff();
+              return APIUtils.pollHostStatusTillOff();
             })
             .then(function(hostState) {
               pollStartTime = new Date();