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    function(
16        $scope, $window, APIUtils, dataService, $location, $anchorScroll,
17        Constants, $interval, $q, $timeout, $interpolate) {
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                    if ($scope.activate.reboot &&
129                        ($scope.activate_image_type == 'BMC')) {
130                      // Despite the new image being active, issue,
131                      // https://github.com/openbmc/openbmc/issues/2764, can
132                      // cause a system to brick, if the system reboots before
133                      // the service to set the U-Boot variables is complete.
134                      // Wait 10 seconds before rebooting to ensure this service
135                      // is complete. This issue is fixed in newer images, but
136                      // the user may be updating from an older image that does
137                      // not that have this fix.
138                      // TODO: remove this timeout after sufficient time has
139                      // passed.
140                      $timeout(function() {
141                        APIUtils.bmcReboot(
142                            function(response) {},
143                            function(error) {
144                              $scope.displayError({
145                                modal_title: 'Error during BMC reboot',
146                                title: 'Error during BMC reboot',
147                                desc: JSON.stringify(error.data),
148                                type: 'Error'
149                              });
150                            });
151                      }, 10000);
152                    }
153                    if ($scope.activate.reboot &&
154                        ($scope.activate_image_type == 'Host')) {
155                      // If image type being activated is a host image, the
156                      // current power status of the server determines if the
157                      // server should power on or reboot.
158                      if ($scope.isServerOff()) {
159                        powerOn();
160                      } else {
161                        warmReboot();
162                      }
163                    }
164                  });
165            });
166        $scope.activate_confirm = false;
167      };
168      function powerOn() {
169        dataService.setUnreachableState();
170        APIUtils.hostPowerOn()
171            .then(function(response) {
172              return response;
173            })
174            .then(function(lastStatus) {
175              return APIUtils.pollHostStatusTillOn();
176            })
177            .catch(function(error) {
178              dataService.activateErrorModal({
179                title: Constants.MESSAGES.POWER_OP.POWER_ON_FAILED,
180                description: error.statusText ?
181                    $interpolate(
182                        Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)(
183                        {status: error.status, description: error.statusText}) :
184                    error
185              });
186            });
187      };
188      function warmReboot() {
189        $scope.uploading = true;
190        dataService.setUnreachableState();
191        APIUtils.hostReboot()
192            .then(function(response) {
193              return response;
194            })
195            .then(function(lastStatus) {
196              return APIUtils.pollHostStatusTilReboot();
197            })
198            .catch(function(error) {
199              dataService.activateErrorModal({
200                title: Constants.MESSAGES.POWER_OP.WARM_REBOOT_FAILED,
201                description: error.statusText ?
202                    $interpolate(
203                        Constants.MESSAGES.ERROR_MESSAGE_DESC_TEMPLATE)(
204                        {status: error.status, description: error.statusText}) :
205                    error
206              });
207            });
208      };
209      $scope.isServerOff = function() {
210        return dataService.server_state === Constants.HOST_STATE_TEXT.off;
211      };
212
213      $scope.upload = function() {
214        if ($scope.file) {
215          $scope.uploading = true;
216          $scope.upload_success = false;
217          APIUtils.uploadImage($scope.file)
218              .then(
219                  function(response) {
220                    $scope.file = '';
221                    $scope.uploading = false;
222                    $scope.upload_success = true;
223                    $scope.loadFirmwares();
224                  },
225                  function(error) {
226                    $scope.uploading = false;
227                    console.log(error);
228                    $scope.displayError({
229                      modal_title: 'Error during image upload',
230                      title: 'Error during image upload',
231                      desc: error,
232                      type: 'Error'
233                    });
234                  });
235        }
236      };
237
238      // TODO: openbmc/openbmc#1691 Add support to return
239      // the id of the newly created image, downloaded via
240      // tftp. Polling the number of software objects is a
241      // near term solution.
242      function waitForDownload() {
243        var deferred = $q.defer();
244        var startTime = new Date();
245        pollDownloadTimer = $interval(function() {
246          var now = new Date();
247          if ((now.getTime() - startTime.getTime()) >=
248              Constants.TIMEOUT.DOWNLOAD_IMAGE) {
249            $interval.cancel(pollDownloadTimer);
250            pollDownloadTimer = undefined;
251            deferred.reject(
252                new Error(Constants.MESSAGES.POLL.DOWNLOAD_IMAGE_TIMEOUT));
253          }
254
255          APIUtils.getFirmwares().then(
256              function(response) {
257                if (response.data.length === $scope.firmwares.length + 1) {
258                  $interval.cancel(pollDownloadTimer);
259                  pollDownloadTimer = undefined;
260                  deferred.resolve(response.data);
261                }
262              },
263              function(error) {
264                $interval.cancel(pollDownloadTimer);
265                pollDownloadTimer = undefined;
266                deferred.reject(error);
267              });
268        }, Constants.POLL_INTERVALS.DOWNLOAD_IMAGE);
269
270        return deferred.promise;
271      }
272
273      $scope.download = function() {
274        $scope.download_success = false;
275        $scope.download_error_msg = '';
276        if (!$scope.download_host || !$scope.download_filename) {
277          $scope.download_error_msg = 'Field is required!';
278          return false;
279        }
280
281        $scope.downloading = true;
282        APIUtils.getFirmwares()
283            .then(function(response) {
284              $scope.firmwares = response.data;
285            })
286            .then(function() {
287              return APIUtils
288                  .downloadImage($scope.download_host, $scope.download_filename)
289                  .then(function(downloadStatus) {
290                    return downloadStatus;
291                  });
292            })
293            .then(function(downloadStatus) {
294              return waitForDownload();
295            })
296            .then(
297                function(newFirmwareList) {
298                  $scope.download_host = '';
299                  $scope.download_filename = '';
300                  $scope.downloading = false;
301                  $scope.download_success = true;
302                  $scope.loadFirmwares();
303                },
304                function(error) {
305                  console.log(error);
306                  $scope.displayError({
307                    modal_title: 'Error during downloading Image',
308                    title: 'Error during downloading Image',
309                    desc: error,
310                    type: 'Error'
311                  });
312                  $scope.downloading = false;
313                });
314      };
315
316      $scope.changePriority = function(imageId, imageVersion, from, to) {
317        $scope.priority_image_id = imageId;
318        $scope.priority_image_version = imageVersion;
319        $scope.priority_from = from;
320        $scope.priority_to = to;
321        $scope.confirm_priority = true;
322      };
323
324      $scope.confirmChangePriority = function() {
325        $scope.loading = true;
326        APIUtils.changePriority($scope.priority_image_id, $scope.priority_to)
327            .then(function(response) {
328              $scope.loading = false;
329              if (response.status == 'error') {
330                $scope.displayError({
331                  modal_title: response.data.description,
332                  title: response.data.description,
333                  desc: response.data.exception,
334                  type: 'Error'
335                });
336              } else {
337                $scope.loadFirmwares();
338              }
339            });
340        $scope.confirm_priority = false;
341      };
342      $scope.deleteImage = function(imageId, imageVersion) {
343        $scope.delete_image_id = imageId;
344        $scope.delete_image_version = imageVersion;
345        $scope.confirm_delete = true;
346      };
347      $scope.confirmDeleteImage = function() {
348        $scope.loading = true;
349        APIUtils.deleteImage($scope.delete_image_id).then(function(response) {
350          $scope.loading = false;
351          if (response.status == 'error') {
352            $scope.displayError({
353              modal_title: response.data.description,
354              title: response.data.description,
355              desc: response.data.exception,
356              type: 'Error'
357            });
358          } else {
359            $scope.loadFirmwares();
360          }
361        });
362        $scope.confirm_delete = false;
363      };
364      $scope.fileNameChanged = function() {
365        $scope.file_empty = false;
366      };
367
368      $scope.filters = {bmc: {imageType: 'BMC'}, host: {imageType: 'Host'}};
369
370      $scope.loadFirmwares = function() {
371        APIUtils.getFirmwares().then(function(result) {
372          $scope.firmwares = result.data;
373          $scope.bmcActiveVersion = result.bmcActiveVersion;
374          $scope.hostActiveVersion = result.hostActiveVersion;
375        });
376      };
377
378      $scope.loadFirmwares();
379    }
380  ]);
381})(angular);
382