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