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