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