xref: /openbmc/phosphor-webui/app/configuration/controllers/firmware-controller.js (revision 4733a11b42fca6013e3957bf0e345d0cea086d96)
1 /**
2  * Controller for firmware
3  *
4  * @module app/configuration
5  * @exports firmwareController
6  * @name firmwareController
7  */
8 
9 window.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 
316       $scope.filters = {bmc: {imageType: 'BMC'}, host: {imageType: 'Host'}};
317 
318       $scope.loadFirmwares = function() {
319         APIUtils.getFirmwares().then(function(result) {
320           $scope.firmwares = result.data;
321           $scope.bmcActiveVersion = result.bmcActiveVersion;
322           $scope.hostActiveVersion = result.hostActiveVersion;
323         });
324       };
325 
326       $scope.loadFirmwares();
327     }
328   ]);
329 })(angular);
330