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