1/** 2 * Controller for firmware 3 * 4 * @module app/configuration 5 * @exports firmwareController 6 * @name firmwareController 7 */ 8 9window.angular && (function(angular) { 10 'use strict'; 11 12 angular.module('app.configuration').controller('firmwareController', [ 13 '$scope', '$window', 'APIUtils', 'dataService', '$location', 14 '$anchorScroll', 'Constants', '$interval', '$q', '$timeout', '$interpolate', 15 function( 16 $scope, $window, APIUtils, dataService, $location, $anchorScroll, 17 Constants, $interval, $q, $timeout, $interpolate) { 18 $scope.dataService = dataService; 19 20 // Scroll to target anchor 21 $scope.gotoAnchor = function() { 22 $location.hash('upload'); 23 $anchorScroll(); 24 }; 25 26 $scope.firmwares = []; 27 $scope.bmcActiveVersion = ''; 28 $scope.hostActiveVersion = ''; 29 $scope.display_error = false; 30 $scope.activate_confirm = false; 31 $scope.delete_image_id = ''; 32 $scope.delete_image_version = ''; 33 $scope.activate_image_id = ''; 34 $scope.activate_image_version = ''; 35 $scope.activate_image_type = ''; 36 $scope.priority_image_id = ''; 37 $scope.priority_image_version = ''; 38 $scope.priority_from = -1; 39 $scope.priority_to = -1; 40 $scope.confirm_priority = false; 41 $scope.file_empty = true; 42 $scope.uploading = false; 43 $scope.upload_success = false; 44 $scope.activate = {reboot: true}; 45 $scope.download_error_msg = ''; 46 $scope.download_success = false; 47 48 var pollActivationTimer = undefined; 49 var pollDownloadTimer = undefined; 50 51 $scope.error = {modal_title: '', title: '', desc: '', type: 'warning'}; 52 53 $scope.activateImage = function(imageId, imageVersion, imageType) { 54 $scope.activate_image_id = imageId; 55 $scope.activate_image_version = imageVersion; 56 $scope.activate_image_type = imageType; 57 $scope.activate_confirm = true; 58 }; 59 60 $scope.displayError = function(data) { 61 $scope.error = data; 62 $scope.display_error = true; 63 }; 64 65 function waitForActive(imageId) { 66 var deferred = $q.defer(); 67 var startTime = new Date(); 68 pollActivationTimer = $interval(function() { 69 APIUtils.getActivation(imageId).then( 70 function(state) { 71 //@TODO: display an error message if image "Failed" 72 if (((/\.Active$/).test(state.data)) || 73 ((/\.Failed$/).test(state.data))) { 74 $interval.cancel(pollActivationTimer); 75 pollActivationTimer = undefined; 76 deferred.resolve(state); 77 } 78 }, 79 function(error) { 80 $interval.cancel(pollActivationTimer); 81 pollActivationTimer = undefined; 82 console.log(error); 83 deferred.reject(error); 84 }); 85 var now = new Date(); 86 if ((now.getTime() - startTime.getTime()) >= 87 Constants.TIMEOUT.ACTIVATION) { 88 $interval.cancel(pollActivationTimer); 89 pollActivationTimer = undefined; 90 console.log('Time out activating image, ' + imageId); 91 deferred.reject( 92 'Time out. Image did not activate in allotted time.'); 93 } 94 }, Constants.POLL_INTERVALS.ACTIVATION); 95 return deferred.promise; 96 } 97 98 $scope.activateConfirmed = function() { 99 APIUtils.activateImage($scope.activate_image_id) 100 .then( 101 function(state) { 102 $scope.loadFirmwares(); 103 return state; 104 }, 105 function(error) { 106 $scope.displayError({ 107 modal_title: 'Error during activation call', 108 title: 'Error during activation call', 109 desc: JSON.stringify(error.data), 110 type: 'Error' 111 }); 112 }) 113 .then(function(activationState) { 114 waitForActive($scope.activate_image_id) 115 .then( 116 function(state) { 117 $scope.loadFirmwares(); 118 }, 119 function(error) { 120 $scope.displayError({ 121 modal_title: 'Error during image activation', 122 title: 'Error during image activation', 123 desc: JSON.stringify(error.data), 124 type: 'Error' 125 }); 126 }) 127 .then(function(state) { 128 if ($scope.activate.reboot && 129 ($scope.activate_image_type == 'BMC')) { 130 // Despite the new image being active, issue, 131 // https://github.com/openbmc/openbmc/issues/2764, can 132 // cause a system to brick, if the system reboots before 133 // the service to set the U-Boot variables is complete. 134 // Wait 10 seconds before rebooting to ensure this service 135 // is complete. This issue is fixed in newer images, but 136 // the user may be updating from an older image that does 137 // not that have this fix. 138 // TODO: remove this timeout after sufficient time has 139 // passed. 140 $timeout(function() { 141 APIUtils.bmcReboot( 142 function(response) {}, 143 function(error) { 144 $scope.displayError({ 145 modal_title: 'Error during BMC reboot', 146 title: 'Error during BMC reboot', 147 desc: JSON.stringify(error.data), 148 type: 'Error' 149 }); 150 }); 151 }, 10000); 152 } 153 if ($scope.activate.reboot && 154 ($scope.activate_image_type == 'Host')) { 155 // If image type being activated is a host image, the 156 // current power status of the server determines if the 157 // server should power on or reboot. 158 if ($scope.isServerOff()) { 159 powerOn(); 160 } else { 161 warmReboot(); 162 } 163 } 164 }); 165 }); 166 $scope.activate_confirm = false; 167 }; 168 function powerOn() { 169 dataService.setUnreachableState(); 170 APIUtils.hostPowerOn() 171 .then(function(response) { 172 return response; 173 }) 174 .then(function(lastStatus) { 175 return APIUtils.pollHostStatusTillOn(); 176 }) 177 .catch(function(error) { 178 dataService.activateErrorModal({ 179 title: Constants.MESSAGES.POWER_OP.POWER_ON_FAILED, 180 description: error.statusText ? 181 $interpolate( 182 Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)( 183 {status: error.status, description: error.statusText}) : 184 error 185 }); 186 }); 187 }; 188 function warmReboot() { 189 $scope.uploading = true; 190 dataService.setUnreachableState(); 191 APIUtils.hostReboot() 192 .then(function(response) { 193 return response; 194 }) 195 .then(function(lastStatus) { 196 return APIUtils.pollHostStatusTilReboot(); 197 }) 198 .catch(function(error) { 199 dataService.activateErrorModal({ 200 title: Constants.MESSAGES.POWER_OP.WARM_REBOOT_FAILED, 201 description: error.statusText ? 202 $interpolate( 203 Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)( 204 {status: error.status, description: error.statusText}) : 205 error 206 }); 207 }); 208 }; 209 $scope.isServerOff = function() { 210 return dataService.server_state === Constants.HOST_STATE_TEXT.off; 211 }; 212 213 $scope.upload = function() { 214 if ($scope.file) { 215 $scope.uploading = true; 216 $scope.upload_success = false; 217 APIUtils.uploadImage($scope.file) 218 .then( 219 function(response) { 220 $scope.file = ''; 221 $scope.uploading = false; 222 $scope.upload_success = true; 223 $scope.loadFirmwares(); 224 }, 225 function(error) { 226 $scope.uploading = false; 227 console.log(error); 228 $scope.displayError({ 229 modal_title: 'Error during image upload', 230 title: 'Error during image upload', 231 desc: error, 232 type: 'Error' 233 }); 234 }); 235 } 236 }; 237 238 // TODO: openbmc/openbmc#1691 Add support to return 239 // the id of the newly created image, downloaded via 240 // tftp. Polling the number of software objects is a 241 // near term solution. 242 function waitForDownload() { 243 var deferred = $q.defer(); 244 var startTime = new Date(); 245 pollDownloadTimer = $interval(function() { 246 var now = new Date(); 247 if ((now.getTime() - startTime.getTime()) >= 248 Constants.TIMEOUT.DOWNLOAD_IMAGE) { 249 $interval.cancel(pollDownloadTimer); 250 pollDownloadTimer = undefined; 251 deferred.reject( 252 new Error(Constants.MESSAGES.POLL.DOWNLOAD_IMAGE_TIMEOUT)); 253 } 254 255 APIUtils.getFirmwares().then( 256 function(response) { 257 if (response.data.length === $scope.firmwares.length + 1) { 258 $interval.cancel(pollDownloadTimer); 259 pollDownloadTimer = undefined; 260 deferred.resolve(response.data); 261 } 262 }, 263 function(error) { 264 $interval.cancel(pollDownloadTimer); 265 pollDownloadTimer = undefined; 266 deferred.reject(error); 267 }); 268 }, Constants.POLL_INTERVALS.DOWNLOAD_IMAGE); 269 270 return deferred.promise; 271 } 272 273 $scope.download = function() { 274 $scope.download_success = false; 275 $scope.download_error_msg = ''; 276 if (!$scope.download_host || !$scope.download_filename) { 277 $scope.download_error_msg = 'Field is required!'; 278 return false; 279 } 280 281 $scope.downloading = true; 282 APIUtils.getFirmwares() 283 .then(function(response) { 284 $scope.firmwares = response.data; 285 }) 286 .then(function() { 287 return APIUtils 288 .downloadImage($scope.download_host, $scope.download_filename) 289 .then(function(downloadStatus) { 290 return downloadStatus; 291 }); 292 }) 293 .then(function(downloadStatus) { 294 return waitForDownload(); 295 }) 296 .then( 297 function(newFirmwareList) { 298 $scope.download_host = ''; 299 $scope.download_filename = ''; 300 $scope.downloading = false; 301 $scope.download_success = true; 302 $scope.loadFirmwares(); 303 }, 304 function(error) { 305 console.log(error); 306 $scope.displayError({ 307 modal_title: 'Error during downloading Image', 308 title: 'Error during downloading Image', 309 desc: error, 310 type: 'Error' 311 }); 312 $scope.downloading = false; 313 }); 314 }; 315 316 $scope.changePriority = function(imageId, imageVersion, from, to) { 317 $scope.priority_image_id = imageId; 318 $scope.priority_image_version = imageVersion; 319 $scope.priority_from = from; 320 $scope.priority_to = to; 321 $scope.confirm_priority = true; 322 }; 323 324 $scope.confirmChangePriority = function() { 325 $scope.loading = true; 326 APIUtils.changePriority($scope.priority_image_id, $scope.priority_to) 327 .then(function(response) { 328 $scope.loading = false; 329 if (response.status == 'error') { 330 $scope.displayError({ 331 modal_title: response.data.description, 332 title: response.data.description, 333 desc: response.data.exception, 334 type: 'Error' 335 }); 336 } else { 337 $scope.loadFirmwares(); 338 } 339 }); 340 $scope.confirm_priority = false; 341 }; 342 $scope.deleteImage = function(imageId, imageVersion) { 343 $scope.delete_image_id = imageId; 344 $scope.delete_image_version = imageVersion; 345 $scope.confirm_delete = true; 346 }; 347 $scope.confirmDeleteImage = function() { 348 $scope.loading = true; 349 APIUtils.deleteImage($scope.delete_image_id).then(function(response) { 350 $scope.loading = false; 351 if (response.status == 'error') { 352 $scope.displayError({ 353 modal_title: response.data.description, 354 title: response.data.description, 355 desc: response.data.exception, 356 type: 'Error' 357 }); 358 } else { 359 $scope.loadFirmwares(); 360 } 361 }); 362 $scope.confirm_delete = false; 363 }; 364 $scope.fileNameChanged = function() { 365 $scope.file_empty = false; 366 }; 367 368 $scope.filters = {bmc: {imageType: 'BMC'}, host: {imageType: 'Host'}}; 369 370 $scope.loadFirmwares = function() { 371 APIUtils.getFirmwares().then(function(result) { 372 $scope.firmwares = result.data; 373 $scope.bmcActiveVersion = result.bmcActiveVersion; 374 $scope.hostActiveVersion = result.hostActiveVersion; 375 }); 376 }; 377 378 $scope.loadFirmwares(); 379 } 380 ]); 381})(angular); 382