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