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                      APIUtils.bmcReboot().then(
120                          function(response) {
121                            toastService.success('BMC is rebooting.')
122                          },
123                          function(error) {
124                            console.log(JSON.stringify(error));
125                            toastService.error('Unable to reboot BMC.');
126                          });
127                    }
128                    if ($scope.activate.reboot &&
129                        ($scope.activate_image_type == 'Host')) {
130                      // If image type being activated is a host image, the
131                      // current power status of the server determines if the
132                      // server should power on or reboot.
133                      if ($scope.isServerOff()) {
134                        powerOn();
135                      } else {
136                        warmReboot();
137                      }
138                    }
139                  });
140            });
141        $scope.activate_confirm = false;
142      };
143      function powerOn() {
144        dataService.setUnreachableState();
145        APIUtils.hostPowerOn()
146            .then(function(response) {
147              return response;
148            })
149            .then(function(lastStatus) {
150              return APIUtils.pollHostStatusTillOn();
151            })
152            .catch(function(error) {
153              console.log(JSON.stringify(error));
154              toastService.error(Constants.MESSAGES.POWER_OP.POWER_ON_FAILED);
155            });
156      };
157      function warmReboot() {
158        $scope.uploading = true;
159        dataService.setUnreachableState();
160        APIUtils.hostReboot()
161            .then(function(response) {
162              return response;
163            })
164            .then(function(lastStatus) {
165              return APIUtils.pollHostStatusTilReboot();
166            })
167            .catch(function(error) {
168              console.log(JSON.stringify(error));
169              toastService.error(
170                  Constants.MESSAGES.POWER_OP.WARM_REBOOT_FAILED);
171            });
172      };
173      $scope.isServerOff = function() {
174        return dataService.server_state === Constants.HOST_STATE_TEXT.off;
175      };
176
177      $scope.upload = function() {
178        if ($scope.file) {
179          $scope.uploading = true;
180          APIUtils.uploadImage($scope.file)
181              .then(
182                  function(response) {
183                    $scope.uploading = false;
184                    toastService.success(
185                        'Image file "' + $scope.file.name +
186                        '" has been uploaded');
187                    $scope.file = '';
188                    $scope.loadFirmwares();
189                  },
190                  function(error) {
191                    $scope.uploading = false;
192                    console.log(error);
193                    toastService.error('Unable to upload image file');
194                  });
195        }
196      };
197
198      // TODO: openbmc/openbmc#1691 Add support to return
199      // the id of the newly created image, downloaded via
200      // tftp. Polling the number of software objects is a
201      // near term solution.
202      function waitForDownload() {
203        var deferred = $q.defer();
204        var startTime = new Date();
205        pollDownloadTimer = $interval(function() {
206          var now = new Date();
207          if ((now.getTime() - startTime.getTime()) >=
208              Constants.TIMEOUT.DOWNLOAD_IMAGE) {
209            $interval.cancel(pollDownloadTimer);
210            pollDownloadTimer = undefined;
211            deferred.reject(
212                new Error(Constants.MESSAGES.POLL.DOWNLOAD_IMAGE_TIMEOUT));
213          }
214
215          APIUtils.getFirmwares().then(
216              function(response) {
217                if (response.data.length === $scope.firmwares.length + 1) {
218                  $interval.cancel(pollDownloadTimer);
219                  pollDownloadTimer = undefined;
220                  deferred.resolve(response.data);
221                }
222              },
223              function(error) {
224                $interval.cancel(pollDownloadTimer);
225                pollDownloadTimer = undefined;
226                deferred.reject(error);
227              });
228        }, Constants.POLL_INTERVALS.DOWNLOAD_IMAGE);
229
230        return deferred.promise;
231      }
232
233      $scope.download = function() {
234        if (!$scope.download_host || !$scope.download_filename) {
235          toastService.error(
236              'TFTP server IP address and file name are required!');
237          return false;
238        }
239
240        $scope.downloading = true;
241        APIUtils.getFirmwares()
242            .then(function(response) {
243              $scope.firmwares = response.data;
244            })
245            .then(function() {
246              return APIUtils
247                  .downloadImage($scope.download_host, $scope.download_filename)
248                  .then(function(downloadStatus) {
249                    return downloadStatus;
250                  });
251            })
252            .then(function(downloadStatus) {
253              return waitForDownload();
254            })
255            .then(
256                function(newFirmwareList) {
257                  $scope.download_host = '';
258                  $scope.download_filename = '';
259                  $scope.downloading = false;
260                  toastService.success('Download complete');
261                  $scope.loadFirmwares();
262                },
263                function(error) {
264                  console.log(error);
265                  toastService.error(
266                      'Image file from TFTP server "' + $scope.download_host +
267                      '" could not be downloaded');
268                  $scope.downloading = false;
269                });
270      };
271
272      $scope.changePriority = function(imageId, imageVersion, from, to) {
273        $scope.priority_image_id = imageId;
274        $scope.priority_image_version = imageVersion;
275        $scope.priority_from = from;
276        $scope.priority_to = to;
277        $scope.confirm_priority = true;
278      };
279
280      $scope.confirmChangePriority = function() {
281        $scope.loading = true;
282        APIUtils.changePriority($scope.priority_image_id, $scope.priority_to)
283            .then(function(response) {
284              $scope.loading = false;
285              if (response.status == 'error') {
286                toastService.error('Unable to update boot priority');
287              } else {
288                $scope.loadFirmwares();
289              }
290            });
291        $scope.confirm_priority = false;
292      };
293      $scope.deleteImage = function(imageId, imageVersion) {
294        $scope.delete_image_id = imageId;
295        $scope.delete_image_version = imageVersion;
296        $scope.confirm_delete = true;
297      };
298      $scope.confirmDeleteImage = function() {
299        $scope.loading = true;
300        APIUtils.deleteImage($scope.delete_image_id).then(function(response) {
301          $scope.loading = false;
302          if (response.status == 'error') {
303            toastService.error('Unable to delete image');
304          } else {
305            $scope.loadFirmwares();
306          }
307        });
308        $scope.confirm_delete = false;
309      };
310
311      $scope.filters = {bmc: {imageType: 'BMC'}, host: {imageType: 'Host'}};
312
313      $scope.loadFirmwares = function() {
314        APIUtils.getFirmwares().then(function(result) {
315          $scope.firmwares = result.data;
316          $scope.bmcActiveVersion = result.bmcActiveVersion;
317          $scope.hostActiveVersion = result.hostActiveVersion;
318        });
319      };
320
321      $scope.loadFirmwares();
322    }
323  ]);
324})(angular);
325