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 'ngToast', 16 function( 17 $scope, $window, APIUtils, dataService, $location, $anchorScroll, 18 Constants, $interval, $q, $timeout, $interpolate, ngToast) { 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 ngToast.danger('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 ngToast.danger('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 ngToast.danger('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 ngToast.danger(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 ngToast.danger(Constants.MESSAGES.POWER_OP.WARM_REBOOT_FAILED); 175 }); 176 }; 177 $scope.isServerOff = function() { 178 return dataService.server_state === Constants.HOST_STATE_TEXT.off; 179 }; 180 181 $scope.upload = function() { 182 if ($scope.file) { 183 $scope.uploading = true; 184 APIUtils.uploadImage($scope.file) 185 .then( 186 function(response) { 187 $scope.uploading = false; 188 ngToast.success( 189 'Image file "' + $scope.file.name + 190 '" has been uploaded'); 191 $scope.file = ''; 192 $scope.loadFirmwares(); 193 }, 194 function(error) { 195 $scope.uploading = false; 196 console.log(error); 197 ngToast.danger('Unable to upload image file'); 198 }); 199 } 200 }; 201 202 // TODO: openbmc/openbmc#1691 Add support to return 203 // the id of the newly created image, downloaded via 204 // tftp. Polling the number of software objects is a 205 // near term solution. 206 function waitForDownload() { 207 var deferred = $q.defer(); 208 var startTime = new Date(); 209 pollDownloadTimer = $interval(function() { 210 var now = new Date(); 211 if ((now.getTime() - startTime.getTime()) >= 212 Constants.TIMEOUT.DOWNLOAD_IMAGE) { 213 $interval.cancel(pollDownloadTimer); 214 pollDownloadTimer = undefined; 215 deferred.reject( 216 new Error(Constants.MESSAGES.POLL.DOWNLOAD_IMAGE_TIMEOUT)); 217 } 218 219 APIUtils.getFirmwares().then( 220 function(response) { 221 if (response.data.length === $scope.firmwares.length + 1) { 222 $interval.cancel(pollDownloadTimer); 223 pollDownloadTimer = undefined; 224 deferred.resolve(response.data); 225 } 226 }, 227 function(error) { 228 $interval.cancel(pollDownloadTimer); 229 pollDownloadTimer = undefined; 230 deferred.reject(error); 231 }); 232 }, Constants.POLL_INTERVALS.DOWNLOAD_IMAGE); 233 234 return deferred.promise; 235 } 236 237 $scope.download = function() { 238 if (!$scope.download_host || !$scope.download_filename) { 239 ngToast.danger('TFTP server IP address and file name are required!'); 240 return false; 241 } 242 243 $scope.downloading = true; 244 APIUtils.getFirmwares() 245 .then(function(response) { 246 $scope.firmwares = response.data; 247 }) 248 .then(function() { 249 return APIUtils 250 .downloadImage($scope.download_host, $scope.download_filename) 251 .then(function(downloadStatus) { 252 return downloadStatus; 253 }); 254 }) 255 .then(function(downloadStatus) { 256 return waitForDownload(); 257 }) 258 .then( 259 function(newFirmwareList) { 260 $scope.download_host = ''; 261 $scope.download_filename = ''; 262 $scope.downloading = false; 263 ngToast.success('Download complete'); 264 $scope.loadFirmwares(); 265 }, 266 function(error) { 267 console.log(error); 268 ngToast.danger( 269 'Image file from TFTP server "' + $scope.download_host + 270 '" could not be downloaded'); 271 $scope.downloading = false; 272 }); 273 }; 274 275 $scope.changePriority = function(imageId, imageVersion, from, to) { 276 $scope.priority_image_id = imageId; 277 $scope.priority_image_version = imageVersion; 278 $scope.priority_from = from; 279 $scope.priority_to = to; 280 $scope.confirm_priority = true; 281 }; 282 283 $scope.confirmChangePriority = function() { 284 $scope.loading = true; 285 APIUtils.changePriority($scope.priority_image_id, $scope.priority_to) 286 .then(function(response) { 287 $scope.loading = false; 288 if (response.status == 'error') { 289 ngToast.danger('Unable to update boot priority'); 290 } else { 291 $scope.loadFirmwares(); 292 } 293 }); 294 $scope.confirm_priority = false; 295 }; 296 $scope.deleteImage = function(imageId, imageVersion) { 297 $scope.delete_image_id = imageId; 298 $scope.delete_image_version = imageVersion; 299 $scope.confirm_delete = true; 300 }; 301 $scope.confirmDeleteImage = function() { 302 $scope.loading = true; 303 APIUtils.deleteImage($scope.delete_image_id).then(function(response) { 304 $scope.loading = false; 305 if (response.status == 'error') { 306 ngToast.danger('Unable to delete image'); 307 } else { 308 $scope.loadFirmwares(); 309 } 310 }); 311 $scope.confirm_delete = false; 312 }; 313 $scope.fileNameChanged = function() { 314 $scope.file_empty = 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