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().then( 126 function(response) { 127 toastService.success('BMC is rebooting.') 128 }, 129 function(error) { 130 console.log(JSON.stringify(error)); 131 toastService.error('Unable to reboot BMC.'); 132 }); 133 }, 10000); 134 } 135 if ($scope.activate.reboot && 136 ($scope.activate_image_type == 'Host')) { 137 // If image type being activated is a host image, the 138 // current power status of the server determines if the 139 // server should power on or reboot. 140 if ($scope.isServerOff()) { 141 powerOn(); 142 } else { 143 warmReboot(); 144 } 145 } 146 }); 147 }); 148 $scope.activate_confirm = false; 149 }; 150 function powerOn() { 151 dataService.setUnreachableState(); 152 APIUtils.hostPowerOn() 153 .then(function(response) { 154 return response; 155 }) 156 .then(function(lastStatus) { 157 return APIUtils.pollHostStatusTillOn(); 158 }) 159 .catch(function(error) { 160 console.log(JSON.stringify(error)); 161 toastService.error(Constants.MESSAGES.POWER_OP.POWER_ON_FAILED); 162 }); 163 }; 164 function warmReboot() { 165 $scope.uploading = true; 166 dataService.setUnreachableState(); 167 APIUtils.hostReboot() 168 .then(function(response) { 169 return response; 170 }) 171 .then(function(lastStatus) { 172 return APIUtils.pollHostStatusTilReboot(); 173 }) 174 .catch(function(error) { 175 console.log(JSON.stringify(error)); 176 toastService.error( 177 Constants.MESSAGES.POWER_OP.WARM_REBOOT_FAILED); 178 }); 179 }; 180 $scope.isServerOff = function() { 181 return dataService.server_state === Constants.HOST_STATE_TEXT.off; 182 }; 183 184 $scope.upload = function() { 185 if ($scope.file) { 186 $scope.uploading = true; 187 APIUtils.uploadImage($scope.file) 188 .then( 189 function(response) { 190 $scope.uploading = false; 191 toastService.success( 192 'Image file "' + $scope.file.name + 193 '" has been uploaded'); 194 $scope.file = ''; 195 $scope.loadFirmwares(); 196 }, 197 function(error) { 198 $scope.uploading = false; 199 console.log(error); 200 toastService.error('Unable to upload image file'); 201 }); 202 } 203 }; 204 205 // TODO: openbmc/openbmc#1691 Add support to return 206 // the id of the newly created image, downloaded via 207 // tftp. Polling the number of software objects is a 208 // near term solution. 209 function waitForDownload() { 210 var deferred = $q.defer(); 211 var startTime = new Date(); 212 pollDownloadTimer = $interval(function() { 213 var now = new Date(); 214 if ((now.getTime() - startTime.getTime()) >= 215 Constants.TIMEOUT.DOWNLOAD_IMAGE) { 216 $interval.cancel(pollDownloadTimer); 217 pollDownloadTimer = undefined; 218 deferred.reject( 219 new Error(Constants.MESSAGES.POLL.DOWNLOAD_IMAGE_TIMEOUT)); 220 } 221 222 APIUtils.getFirmwares().then( 223 function(response) { 224 if (response.data.length === $scope.firmwares.length + 1) { 225 $interval.cancel(pollDownloadTimer); 226 pollDownloadTimer = undefined; 227 deferred.resolve(response.data); 228 } 229 }, 230 function(error) { 231 $interval.cancel(pollDownloadTimer); 232 pollDownloadTimer = undefined; 233 deferred.reject(error); 234 }); 235 }, Constants.POLL_INTERVALS.DOWNLOAD_IMAGE); 236 237 return deferred.promise; 238 } 239 240 $scope.download = function() { 241 if (!$scope.download_host || !$scope.download_filename) { 242 toastService.error( 243 'TFTP server IP address and file name are required!'); 244 return false; 245 } 246 247 $scope.downloading = true; 248 APIUtils.getFirmwares() 249 .then(function(response) { 250 $scope.firmwares = response.data; 251 }) 252 .then(function() { 253 return APIUtils 254 .downloadImage($scope.download_host, $scope.download_filename) 255 .then(function(downloadStatus) { 256 return downloadStatus; 257 }); 258 }) 259 .then(function(downloadStatus) { 260 return waitForDownload(); 261 }) 262 .then( 263 function(newFirmwareList) { 264 $scope.download_host = ''; 265 $scope.download_filename = ''; 266 $scope.downloading = false; 267 toastService.success('Download complete'); 268 $scope.loadFirmwares(); 269 }, 270 function(error) { 271 console.log(error); 272 toastService.error( 273 'Image file from TFTP server "' + $scope.download_host + 274 '" could not be downloaded'); 275 $scope.downloading = false; 276 }); 277 }; 278 279 $scope.changePriority = function(imageId, imageVersion, from, to) { 280 $scope.priority_image_id = imageId; 281 $scope.priority_image_version = imageVersion; 282 $scope.priority_from = from; 283 $scope.priority_to = to; 284 $scope.confirm_priority = true; 285 }; 286 287 $scope.confirmChangePriority = function() { 288 $scope.loading = true; 289 APIUtils.changePriority($scope.priority_image_id, $scope.priority_to) 290 .then(function(response) { 291 $scope.loading = false; 292 if (response.status == 'error') { 293 toastService.error('Unable to update boot priority'); 294 } else { 295 $scope.loadFirmwares(); 296 } 297 }); 298 $scope.confirm_priority = false; 299 }; 300 $scope.deleteImage = function(imageId, imageVersion) { 301 $scope.delete_image_id = imageId; 302 $scope.delete_image_version = imageVersion; 303 $scope.confirm_delete = true; 304 }; 305 $scope.confirmDeleteImage = function() { 306 $scope.loading = true; 307 APIUtils.deleteImage($scope.delete_image_id).then(function(response) { 308 $scope.loading = false; 309 if (response.status == 'error') { 310 toastService.error('Unable to delete image'); 311 } else { 312 $scope.loadFirmwares(); 313 } 314 }); 315 $scope.confirm_delete = false; 316 }; 317 318 $scope.filters = {bmc: {imageType: 'BMC'}, host: {imageType: 'Host'}}; 319 320 $scope.loadFirmwares = function() { 321 APIUtils.getFirmwares().then(function(result) { 322 $scope.firmwares = result.data; 323 $scope.bmcActiveVersion = result.bmcActiveVersion; 324 $scope.hostActiveVersion = result.hostActiveVersion; 325 }); 326 }; 327 328 $scope.loadFirmwares(); 329 } 330 ]); 331})(angular); 332