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/common/directives/app-header.js b/app/common/directives/app-header.js
index 2a34381..93d8fa8 100644
--- a/app/common/directives/app-header.js
+++ b/app/common/directives/app-header.js
@@ -30,7 +30,7 @@
}else if(status == 'xyz.openbmc_project.State.Host.HostState.Running'){
dataService.setPowerOnState();
}else{
- dataService.setBootingState();
+ dataService.setErrorState();
}
}, function(error){
dataService.activateErrorModal();
diff --git a/app/common/services/constants.js b/app/common/services/constants.js
index 57c5eb1..3afd9ae 100644
--- a/app/common/services/constants.js
+++ b/app/common/services/constants.js
@@ -36,13 +36,14 @@
on_code: 'xyz.openbmc_project.State.Host.HostState.Running',
off: 'Off',
off_code: 'xyz.openbmc_project.State.Host.HostState.Off',
- booting: 'Quiesced',
+ error: 'Quiesced',
+ error_code: 'xyz.openbmc_project.State.Host.HostState.Quiesced',
unreachable: 'Unreachable'
},
HOST_STATE: {
on: 1,
off: -1,
- booting: 0,
+ error: 0,
unreachable: -2
},
LED_STATE: {
@@ -122,10 +123,21 @@
ACTIVATION: 1000 * 60 * 10, // 10 mins
CHASSIS_OFF: 1000 * 60 * 5, // 5 mins
HOST_ON: 1000 * 60 * 5, // 5 mins
+ HOST_OFF: 1000 * 60 * 5, // 5 mins
},
MESSAGES: {
POLL: {
- TIMEOUT: 'Time out. Did not reach power state in allotted time.',
+ 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: 'Time out. System did not reach Off state in allotted time.',
+ HOST_QUIESCED: 'System is in Error state.',
+ },
+ POWER_OP: {
+ POWER_ON_FAILED: 'Power On Failed',
+ WARM_REBOOT_FAILED: 'Warm Reboot Failed',
+ COLD_REBOOT_FAILED: 'Cold Reboot Failed',
+ ORDERLY_SHUTDOWN_FAILED: 'Orderly Shutdown Failed',
+ IMMEDIATE_SHUTDOWN_FAILED: 'Immediate Shutdown Failed',
},
SENSOR: {
NO_SENSOR_DATA: 'There are no sensors found.',
@@ -136,7 +148,8 @@
ERROR_MODAL: {
TITLE: 'Unexpected error',
DESCRIPTION: 'Oops! An unexpected error occurred. Record specific details of the issue, then contact your company support services.'
- }
+ },
+ ERROR_MESSAGE_DESC_TEMPLATE: '{{status}} - {{description}}',
},
POWER_CAP_TEXT: {
unit: 'W',
diff --git a/app/common/services/dataService.js b/app/common/services/dataService.js
index d84821a..6cf62ee 100644
--- a/app/common/services/dataService.js
+++ b/app/common/services/dataService.js
@@ -86,9 +86,9 @@
this.server_status = Constants.HOST_STATE.off;
}
- this.setBootingState = function(){
- this.server_state = Constants.HOST_STATE_TEXT.booting;
- this.server_status = Constants.HOST_STATE.booting;
+ this.setErrorState = function(){
+ this.server_state = Constants.HOST_STATE_TEXT.error;
+ this.server_status = Constants.HOST_STATE.error;
}
this.setUnreachableState = function(){
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(){