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