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