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