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