You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by at...@apache.org on 2017/06/07 16:23:27 UTC

ambari git commit: AMBARI-21194 Integrate background operations with websocket events. (atkach)

Repository: ambari
Updated Branches:
  refs/heads/branch-3.0-perf c2ab4a3ce -> b1d357ad1


AMBARI-21194 Integrate background operations with websocket events. (atkach)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/b1d357ad
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/b1d357ad
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/b1d357ad

Branch: refs/heads/branch-3.0-perf
Commit: b1d357ad11c7e4ac23aa61e7a446bd94030b8f44
Parents: c2ab4a3
Author: Andrii Tkach <at...@apache.org>
Authored: Wed Jun 7 19:03:50 2017 +0300
Committer: Andrii Tkach <at...@apache.org>
Committed: Wed Jun 7 19:03:50 2017 +0300

----------------------------------------------------------------------
 .../global/background_operations_controller.js  | 196 ++++++--
 .../app/controllers/main/admin/kerberos.js      |   4 +-
 ambari-web/app/controllers/main/service.js      |   6 +-
 ambari-web/app/templates/application.hbs        |   4 +-
 ambari-web/app/utils/host_progress_popup.js     |   2 +-
 .../global/background_operations_test.js        | 485 +++++++++++--------
 .../test/controllers/main/service_test.js       |   2 +-
 7 files changed, 437 insertions(+), 262 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/b1d357ad/ambari-web/app/controllers/global/background_operations_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/global/background_operations_controller.js b/ambari-web/app/controllers/global/background_operations_controller.js
index d833661..8be7b87 100644
--- a/ambari-web/app/controllers/global/background_operations_controller.js
+++ b/ambari-web/app/controllers/global/background_operations_controller.js
@@ -26,18 +26,21 @@ App.BackgroundOperationsController = Em.Controller.extend({
    */
   isWorking : false,
 
-  allOperationsCount : 0,
+  runningOperationsCount : function() {
+    return this.get('services').filterProperty('isRunning').length;
+  }.property('services.@each.isRunning'),
 
   /**
-   * For host component popup
+   * List of requests
    */
-  services:[],
+  services: Em.A([]),
   serviceTimestamp: null,
 
   /**
    * Number of operation to load
    */
   operationsCount: 10,
+
   /**
    * Possible levels:
    * REQUESTS_LIST
@@ -51,15 +54,125 @@ App.BackgroundOperationsController = Em.Controller.extend({
     taskId: null
   }),
 
+  handleRequestsUpdates: function () {
+    if (this.get('isWorking')) {
+      this.requestMostRecent(() => {
+        App.StompClient.subscribe('/events/requests', this.updateRequests.bind(this));
+      });
+    } else {
+      App.StompClient.unsubscribe('/events/requests');
+    }
+  }.observes('isWorking'),
+
+  updateRequests: function(event) {
+    const request = this.get('services').findProperty('id', event.requestId);
+    const context = this.parseRequestContext(event.requestContext);
+    const visibleOperationsCount = this.get('operationsCount');
+    const map = this.generateTasksMapOfRequest(event, request);
+    const updatedState = {
+      progress: Math.floor(event.progressPercent),
+      status: event.requestStatus,
+      isRunning: this.isRunning(event.requestStatus),
+      startTime: App.dateTimeWithTimeZone(event.startTime),
+      endTime: event.endTime > 0 ? App.dateTimeWithTimeZone(event.endTime) : event.endTime,
+      previousTaskStatusMap: map.currentTaskStatusMap,
+      hostsMap: map.hostsMap
+    };
+
+    if (request) {
+      request.setProperties(updatedState);
+    } else {
+      this.get('services').unshift(Em.Object.create(updatedState, {
+        id: event.requestId,
+        name: context.requestContext,
+        displayName: context.requestContext,
+        tasks: event.Tasks
+      }));
+      if (this.get('services').length >= visibleOperationsCount) {
+        this.set('isShowMoreAvailable', true);
+        this.get('services').pop();
+      }
+    }
+    this.set('serviceTimestamp', App.dateTime());
+    this.propertyDidChange('services');
+  },
+
   /**
-   * Start polling, when <code>isWorking</code> become true
+   *
+   * @param {object} event
+   * @param {Em.Object} request
+   * @returns {{}}
    */
-  startPolling: function(){
-    if(this.get('isWorking')){
-      this.requestMostRecent();
-      App.updater.run(this, 'requestMostRecent', 'isWorking', App.bgOperationsUpdateInterval);
+  generateTasksMapOfRequest: function(event, request) {
+    const hostsMap = request ? request.get('hostsMap') : {};
+    const previousTaskStatusMap = request ? request.get('previousTaskStatusMap') : {};
+    const currentTaskStatusMap = {};
+    event.Tasks.forEach((task) => {
+      const host = hostsMap[task.hostName];
+      if (host) {
+        const existedTask = host.logTasks.findProperty('Tasks.id', task.id);
+        if (existedTask) {
+          existedTask.Tasks.status = task.status;
+        } else {
+          host.logTasks.push(this.convertTaskFromEventToApi(task));
+        }
+        host.isModified = (host.isModified) ? true : previousTaskStatusMap[task.id] !== task.status;
+      } else {
+        hostsMap[task.hostName] = {
+          name: task.hostName,
+          publicName: task.hostName,
+          logTasks: [this.convertTaskFromEventToApi(task)],
+          isModified: previousTaskStatusMap[task.id] !== task.status
+        };
+      }
+      currentTaskStatusMap[task.id] = task.status;
+    }, this);
+    return {
+      currentTaskStatusMap,
+      hostsMap
     }
-  }.observes('isWorking'),
+  },
+
+  convertTaskFromEventToApi: function(task) {
+    return {
+      Tasks: {
+        status: task.status,
+        host_name: task.hostName,
+        id: task.id,
+        request_id: task.requestId
+      }
+    }
+  },
+
+  handleTaskUpdates: function() {
+    const levelInfo = this.get('levelInfo');
+    if (!levelInfo.get('requestId') || !levelInfo.get('taskId')) {
+      return;
+    }
+    const request = this.get('services').findProperty('id', levelInfo.get('requestId'));
+    const taskStatus = request.get('previousTaskStatusMap')[levelInfo.get('taskId')];
+    if (levelInfo.get('name') === 'TASK_DETAILS' && !this.isFinished(taskStatus)) {
+      App.StompClient.subscribe(`/events/tasks/${levelInfo.get('taskId')}`, (updatedTask) => {
+        this.updateTask(updatedTask);
+        if (this.isFinished(updatedTask.status)) {
+          App.StompClient.unsubscribe(`/events/tasks/${updatedTask.id}`);
+        }
+      });
+    }
+  }.observes('levelInfo.name'),
+
+  updateTask: function(updatedTask) {
+    const request = this.get('services').findProperty('id', updatedTask.requestId);
+    const host = request.get('hostsMap')[updatedTask.hostName];
+    const task = host.logTasks.findProperty('Tasks.id', updatedTask.id);
+    task.Tasks.status = updatedTask.status;
+    task.Tasks.stdout = updatedTask.stdout;
+    task.Tasks.stderr = updatedTask.stderr;
+    task.Tasks.structured_out = updatedTask.structured_out;
+    task.Tasks.output_log = updatedTask.outLog;
+    task.Tasks.error_log = updatedTask.errorLog;
+    this.set('serviceTimestamp', App.dateTime());
+  },
 
   /**
    * Get requests data from server
@@ -103,7 +216,7 @@ App.BackgroundOperationsController = Em.Controller.extend({
         'operationsCount': count
       }
     };
-    if (levelInfo.get('name') === 'TASK_DETAILS' && !App.get('testMode')) {
+    if (levelInfo.get('name') === 'TASK_DETAILS') {
       result.name = 'background_operations.get_by_task';
       result.successCallback = 'callBackFilteredByTask';
       result.data = {
@@ -123,10 +236,8 @@ App.BackgroundOperationsController = Em.Controller.extend({
   /**
    * Push hosts and their tasks to request
    * @param data
-   * @param ajaxQuery
-   * @param params
    */
-  callBackFilteredByRequest: function (data, ajaxQuery, params) {
+  callBackFilteredByRequest: function (data) {
     var requestId = data.Requests.id;
     var requestInputs = data.Requests.inputs;
     var request = this.get('services').findProperty('id', requestId);
@@ -150,16 +261,6 @@ App.BackgroundOperationsController = Em.Controller.extend({
       }
       currentTaskStatusMap[task.Tasks.id] = task.Tasks.status;
     }, this);
-    /**
-     * sync up request progress with up to date progress of hosts on Host's list,
-     * to avoid discrepancies while waiting for response with latest progress of request
-     * after switching to operation's list
-     */
-    if (request.get('isRunning')) {
-      request.set('progress', App.HostPopup.getProgress(data.tasks));
-      request.set('status', App.HostPopup.getStatus(data.tasks)[0]);
-      request.set('isRunning', request.get('progress') !== 100);
-    }
     request.set('previousTaskStatusMap', currentTaskStatusMap);
     request.set('hostsMap', hostsMap);
     this.set('serviceTimestamp', App.dateTime());
@@ -203,7 +304,6 @@ App.BackgroundOperationsController = Em.Controller.extend({
    * @param data
    */
   callBackForMostRecent: function (data) {
-    var runningServices = 0;
     var currentRequestIds = [];
     var countIssued = this.get('operationsCount');
     var countGot = data.itemTotal;
@@ -211,37 +311,33 @@ App.BackgroundOperationsController = Em.Controller.extend({
 
     data.items.forEach(function (request) {
       if (this.isUpgradeRequest(request)) {
-        if (!App.get('upgradeIsRunning') && !App.get('testMode')) {
+        if (!App.get('upgradeIsRunning')) {
           restoreUpgradeState = true;
         }
         return;
       }
       var rq = this.get("services").findProperty('id', request.Requests.id);
-      var isRunning = this.isRequestRunning(request);
+      var isRunning = this.isRunning(request.Requests.request_status);
       var requestParams = this.parseRequestContext(request.Requests.request_context);
+      const requestState = {
+        progress: Math.floor(request.Requests.progress_percent),
+        status: request.Requests.request_status,
+        isRunning: isRunning,
+        startTime: App.dateTimeWithTimeZone(request.Requests.start_time),
+        endTime: request.Requests.end_time > 0 ? App.dateTimeWithTimeZone(request.Requests.end_time) : request.Requests.end_time
+      };
       this.assignScheduleId(request, requestParams);
       currentRequestIds.push(request.Requests.id);
 
       if (rq) {
-        rq.setProperties({
-          progress: Math.floor(request.Requests.progress_percent),
-          status: request.Requests.request_status,
-          isRunning: isRunning,
-          startTime: App.dateTimeWithTimeZone(request.Requests.start_time),
-          endTime: request.Requests.end_time > 0 ? App.dateTimeWithTimeZone(request.Requests.end_time) : request.Requests.end_time
-        });
+        rq.setProperties(requestState);
       } else {
-        rq = Em.Object.create({
+        rq = Em.Object.create(requestState, {
           id: request.Requests.id,
           name: requestParams.requestContext,
           displayName: requestParams.requestContext,
-          progress: Math.floor(request.Requests.progress_percent),
-          status: request.Requests.request_status,
-          isRunning: isRunning,
           hostsMap: {},
           tasks: [],
-          startTime: App.dateTimeWithTimeZone(request.Requests.start_time),
-          endTime: request.Requests.end_time > 0 ? App.dateTimeWithTimeZone(request.Requests.end_time) : request.Requests.end_time,
           dependentService: requestParams.dependentService,
           sourceRequestScheduleId: request.Requests.request_schedule && request.Requests.request_schedule.schedule_id,
           previousTaskStatusMap: {},
@@ -251,13 +347,11 @@ App.BackgroundOperationsController = Em.Controller.extend({
         //To sort DESC by request id
         this.set("services", this.get("services").sortProperty('id').reverse());
       }
-      runningServices += ~~isRunning;
     }, this);
     if (restoreUpgradeState) {
       App.router.get('clusterController').restoreUpgradeState();
     }
     this.removeOldRequests(currentRequestIds);
-    this.set("allOperationsCount", runningServices);
     this.set('isShowMoreAvailable', countGot >= countIssued);
     this.set('serviceTimestamp', App.dateTimeWithTimeZone());
   },
@@ -282,15 +376,23 @@ App.BackgroundOperationsController = Em.Controller.extend({
   },
 
   /**
-   * identify whether request is running by task counters
-   * @param request
+   * identify whether request or task is running by status
+   * @param status
    * @return {Boolean}
    */
-  isRequestRunning: function (request) {
-    return (request.Requests.task_count -
-      (request.Requests.aborted_task_count + request.Requests.completed_task_count + request.Requests.failed_task_count
-        + request.Requests.timed_out_task_count - request.Requests.queued_task_count)) > 0;
+  isRunning: function (status) {
+    return ['IN_PROGRESS', 'QUEUED', 'PENDING'].contains(status);
   },
+
+  /**
+   * identify whether request or task is finished by status
+   * @param status
+   * @return {Boolean}
+   */
+  isFinished: function (status) {
+    return ['FAILED', 'ABORTED', 'COMPLETED'].contains(status);
+  },
+
   /**
    * identify whether there is only one host in request
    * @param inputs

http://git-wip-us.apache.org/repos/asf/ambari/blob/b1d357ad/ambari-web/app/controllers/main/admin/kerberos.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/kerberos.js b/ambari-web/app/controllers/main/admin/kerberos.js
index 564fb35..1b94758 100644
--- a/ambari-web/app/controllers/main/admin/kerberos.js
+++ b/ambari-web/app/controllers/main/admin/kerberos.js
@@ -215,13 +215,13 @@ App.MainAdminKerberosController = App.KerberosWizardStep4Controller.extend({
    * @return {$.ajax}
    */
   restartAllServices: function () {
-    if (!App.router.get('backgroundOperationsController.allOperationsCount')) {
+    if (!App.router.get('backgroundOperationsController.runningOperationsCount')) {
       if (this.get('needsRestartAfterRegenerate')) {
         this.set('needsRestartAfterRegenerate', false);
         App.router.get('mainServiceController').restartAllServices();
       }
     }
-  }.observes('controllers.backgroundOperationsController.allOperationsCount'),
+  }.observes('controllers.backgroundOperationsController.runningOperationsCount'),
 
   /**
    * performs cluster check before kerbefos security

http://git-wip-us.apache.org/repos/asf/ambari/blob/b1d357ad/ambari-web/app/controllers/main/service.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/service.js b/ambari-web/app/controllers/main/service.js
index eb9df0d..983e8b4 100644
--- a/ambari-web/app/controllers/main/service.js
+++ b/ambari-web/app/controllers/main/service.js
@@ -93,7 +93,7 @@ App.MainServiceController = Em.ArrayController.extend(App.SupportClientConfigsDo
   /**
    * @type {bool}
    */
-  isStartStopAllClicked: Em.computed.notEqual('App.router.backgroundOperationsController.allOperationsCount', 0),
+  isStartStopAllClicked: Em.computed.notEqual('App.router.backgroundOperationsController.runningOperationsCount', 0),
 
   /**
    * Callback for <code>start all service</code> button
@@ -234,7 +234,7 @@ App.MainServiceController = Em.ArrayController.extend(App.SupportClientConfigsDo
    */
   silentStartAllServices: function () {
     if (
-      !App.router.get('backgroundOperationsController').get('allOperationsCount')
+      !App.router.get('backgroundOperationsController').get('runningOperationsCount')
       && this.get('shouldStart')
       && !this.isStopAllServicesFailed()
     ) {
@@ -252,7 +252,7 @@ App.MainServiceController = Em.ArrayController.extend(App.SupportClientConfigsDo
         showLoadingPopup: true
       });
     }
-  }.observes('shouldStart', 'controllers.backgroundOperationsController.allOperationsCount'),
+  }.observes('shouldStart', 'controllers.backgroundOperationsController.runningOperationsCount'),
 
   /**
    * Success callback for silent start

http://git-wip-us.apache.org/repos/asf/ambari/blob/b1d357ad/ambari-web/app/templates/application.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/application.hbs b/ambari-web/app/templates/application.hbs
index 03c47db..e56232c 100644
--- a/ambari-web/app/templates/application.hbs
+++ b/ambari-web/app/templates/application.hbs
@@ -127,8 +127,8 @@
             <a href="#" class="bg-label" {{action "showPopup" target="App.router.backgroundOperationsController"}}>
               {{#with App.router.backgroundOperationsController}}
                 <span class="glyphicon glyphicon-cog"></span>
-                <span id="span-bg-operation-count" {{bindAttr class="allOperationsCount:operations-count :numberCircle"}}>
-                  {{allOperationsCount}}
+                <span id="span-bg-operation-count" {{bindAttr class="runningOperationsCount:operations-count :numberCircle"}}>
+                  {{runningOperationsCount}}
                 </span>
               {{/with}}
             </a>

http://git-wip-us.apache.org/repos/asf/ambari/blob/b1d357ad/ambari-web/app/utils/host_progress_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/host_progress_popup.js b/ambari-web/app/utils/host_progress_popup.js
index c615cae..b5715db 100644
--- a/ambari-web/app/utils/host_progress_popup.js
+++ b/ambari-web/app/utils/host_progress_popup.js
@@ -425,7 +425,7 @@ App.HostPopup = Em.Object.create({
    */
   setBackgroundOperationHeader: function (isServiceListHidden) {
     if (this.get('isBackgroundOperations') && !isServiceListHidden) {
-      var numRunning = App.router.get('backgroundOperationsController.allOperationsCount');
+      var numRunning = App.router.get('backgroundOperationsController.runningOperationsCount');
       this.set("popupHeaderName", numRunning + Em.I18n.t('hostPopup.header.postFix').format(numRunning === 1 ? "" : "s"));
     }
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/b1d357ad/ambari-web/test/controllers/global/background_operations_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/global/background_operations_test.js b/ambari-web/test/controllers/global/background_operations_test.js
index 8d982bb..8f0c79e 100644
--- a/ambari-web/test/controllers/global/background_operations_test.js
+++ b/ambari-web/test/controllers/global/background_operations_test.js
@@ -112,6 +112,10 @@ describe('App.BackgroundOperationsController', function () {
     tests.forEach(function (test) {
       it(test.m, function () {
         controller.set('levelInfo', test.levelInfo);
+        controller.set('services', [Em.Object.create({
+          id: 1,
+          previousTaskStatusMap: {}
+        })]);
         var r = controller.getQueryParams();
         expect(r.name).to.equal(test.e.name);
         expect(r.successCallback).to.equal(test.e.successCallback);
@@ -120,27 +124,6 @@ describe('App.BackgroundOperationsController', function () {
     });
   });
 
-  describe('#startPolling()', function () {
-
-    beforeEach(function () {
-      sinon.spy(controller, 'requestMostRecent');
-    });
-    afterEach(function () {
-      controller.requestMostRecent.restore();
-    });
-
-    it('isWorking = false', function () {
-      controller.set('isWorking', false);
-      expect(App.updater.run.calledOnce).to.equal(false);
-      expect(controller.requestMostRecent.calledOnce).to.equal(false);
-    });
-    it('isWorking = true', function () {
-      controller.set('isWorking', true);
-      expect(App.updater.run.calledOnce).to.equal(true);
-      expect(controller.requestMostRecent.calledOnce).to.equal(true);
-    });
-  });
-
   describe('#isUpgradeRequest', function() {
 
     it('defines if request is upgrade task (true)', function() {
@@ -179,7 +162,7 @@ describe('App.BackgroundOperationsController', function () {
         items: []
       };
       controller.callBackForMostRecent(data);
-      expect(controller.get("allOperationsCount")).to.equal(0);
+      expect(controller.get("runningOperationsCount")).to.equal(0);
       expect(controller.get("services.length")).to.equal(0);
     });
     it('One non-running request', function () {
@@ -200,7 +183,7 @@ describe('App.BackgroundOperationsController', function () {
         ]
       };
       controller.callBackForMostRecent(data);
-      expect(controller.get("allOperationsCount")).to.equal(0);
+      expect(controller.get("runningOperationsCount")).to.equal(0);
       expect(controller.get("services").mapProperty('id')).to.eql([1]);
     });
 
@@ -216,7 +199,7 @@ describe('App.BackgroundOperationsController', function () {
         ]
       };
       controller.callBackForMostRecent(data);
-      expect(controller.get("allOperationsCount")).to.equal(0);
+      expect(controller.get("runningOperationsCount")).to.equal(0);
       expect(controller.get("services").mapProperty('id')).to.eql([]);
     });
 
@@ -227,18 +210,13 @@ describe('App.BackgroundOperationsController', function () {
             Requests: {
               id: 1,
               request_context: '',
-              task_count: 1,
-              aborted_task_count: 0,
-              completed_task_count: 0,
-              failed_task_count: 0,
-              timed_out_task_count: 0,
-              queued_task_count: 0
+              request_status: 'IN_PROGRESS'
             }
           }
         ]
       };
       controller.callBackForMostRecent(data);
-      expect(controller.get("allOperationsCount")).to.equal(1);
+      expect(controller.get("runningOperationsCount")).to.equal(1);
       expect(controller.get("services").mapProperty('id')).to.eql([1]);
     });
     it('Two requests in order', function () {
@@ -259,7 +237,7 @@ describe('App.BackgroundOperationsController', function () {
         ]
       };
       controller.callBackForMostRecent(data);
-      expect(controller.get("allOperationsCount")).to.equal(0);
+      expect(controller.get("runningOperationsCount")).to.equal(0);
       expect(controller.get("services").mapProperty('id')).to.eql([2, 1]);
     });
   });
@@ -331,179 +309,7 @@ describe('App.BackgroundOperationsController', function () {
       it(test.title, function () {
         controller.set('services', test.content.services);
         controller.removeOldRequests(test.content.currentRequestIds);
-        expect(controller.get('services')).to.eql(test.result);
-      });
-    });
-  });
-
-  describe('#isRequestRunning()', function () {
-    var testCases = [
-      {
-        title: 'Counters are missing',
-        request: {
-          Requests: {}
-        },
-        result: false
-      },
-      {
-        title: 'Request has zero tasks',
-        request: {
-          Requests: {
-            task_count: 0,
-            aborted_task_count: 0,
-            completed_task_count: 0,
-            failed_task_count: 0,
-            timed_out_task_count: 0,
-            queued_task_count: 0
-          }
-        },
-        result: false
-      },
-      {
-        title: 'One task in running status',
-        request: {
-          Requests: {
-            task_count: 1,
-            aborted_task_count: 0,
-            completed_task_count: 0,
-            failed_task_count: 0,
-            timed_out_task_count: 0,
-            queued_task_count: 0
-          }
-        },
-        result: true
-      },
-      {
-        title: 'One task in queued status',
-        request: {
-          Requests: {
-            task_count: 1,
-            aborted_task_count: 0,
-            completed_task_count: 0,
-            failed_task_count: 0,
-            timed_out_task_count: 0,
-            queued_task_count: 1
-          }
-        },
-        result: true
-      },
-      {
-        title: 'One task in aborted status',
-        request: {
-          Requests: {
-            task_count: 1,
-            aborted_task_count: 1,
-            completed_task_count: 0,
-            failed_task_count: 0,
-            timed_out_task_count: 0,
-            queued_task_count: 0
-          }
-        },
-        result: false
-      },
-      {
-        title: 'One task in completed status',
-        request: {
-          Requests: {
-            task_count: 1,
-            aborted_task_count: 0,
-            completed_task_count: 1,
-            failed_task_count: 0,
-            timed_out_task_count: 0,
-            queued_task_count: 0
-          }
-        },
-        result: false
-      },
-      {
-        title: 'One task in failed status',
-        request: {
-          Requests: {
-            task_count: 1,
-            aborted_task_count: 0,
-            completed_task_count: 0,
-            failed_task_count: 1,
-            timed_out_task_count: 0,
-            queued_task_count: 0
-          }
-        },
-        result: false
-      },
-      {
-        title: 'One task in timed out status',
-        request: {
-          Requests: {
-            task_count: 1,
-            aborted_task_count: 0,
-            completed_task_count: 0,
-            failed_task_count: 0,
-            timed_out_task_count: 1,
-            queued_task_count: 0
-          }
-        },
-        result: false
-      },
-      {
-        title: 'One task in timed out status and the second one in running',
-        request: {
-          Requests: {
-            task_count: 2,
-            aborted_task_count: 0,
-            completed_task_count: 0,
-            failed_task_count: 0,
-            timed_out_task_count: 1,
-            queued_task_count: 0
-          }
-        },
-        result: true
-      },
-      {
-        title: 'One task in each status',
-        request: {
-          Requests: {
-            task_count: 5,
-            aborted_task_count: 1,
-            completed_task_count: 1,
-            failed_task_count: 1,
-            timed_out_task_count: 1,
-            queued_task_count: 1
-          }
-        },
-        result: true
-      },
-      {
-        title: 'One task in each status except queued',
-        request: {
-          Requests: {
-            task_count: 5,
-            aborted_task_count: 1,
-            completed_task_count: 1,
-            failed_task_count: 1,
-            timed_out_task_count: 1,
-            queued_task_count: 0
-          }
-        },
-        result: true
-      },
-      {
-        title: 'No tasks in running status',
-        request: {
-          Requests: {
-            task_count: 4,
-            aborted_task_count: 1,
-            completed_task_count: 1,
-            failed_task_count: 1,
-            timed_out_task_count: 1,
-            queued_task_count: 0
-          }
-        },
-        result: false
-      }
-    ];
-
-    testCases.forEach(function (test) {
-      it(test.title, function () {
-        expect(controller.isRequestRunning(test.request)).to.eql(test.result);
+        expect(JSON.stringify(controller.get('services'))).to.equal(JSON.stringify(test.result));
       });
     });
   });
@@ -674,7 +480,6 @@ describe('App.BackgroundOperationsController', function () {
       controller.callBackFilteredByRequest(data);
       expect(request.get('previousTaskStatusMap')).to.eql({"1": "COMPLETED"});
       expect(request.get('hostsMap.host1.logTasks.length')).to.equal(1);
-      expect(request.get('isRunning')).to.equal(false);
     });
 
     it('request has one completed task and one running task', function () {
@@ -939,4 +744,272 @@ describe('App.BackgroundOperationsController', function () {
       expect(controller.get('operationsCount')).to.be.equal(10);
     });
   });
+
+  describe('#handleRequestsUpdates', function() {
+    beforeEach(function() {
+      sinon.stub(controller, 'requestMostRecent', Em.clb);
+      sinon.stub(App.StompClient, 'subscribe');
+      sinon.stub(App.StompClient, 'unsubscribe');
+    });
+    afterEach(function() {
+      controller.requestMostRecent.restore();
+      App.StompClient.subscribe.restore();
+      App.StompClient.unsubscribe.restore();
+    });
+
+    it('App.StompClient.subscribe should be called', function() {
+      controller.set('isWorking', true);
+      expect(App.StompClient.subscribe.calledWith('/events/requests')).to.be.true;
+    });
+
+    it('App.StompClient.unsubscribe should be called', function() {
+      controller.set('isWorking', false);
+      expect(App.StompClient.unsubscribe.calledWith('/events/requests')).to.be.true;
+    });
+  });
+
+  describe('#updateRequests', function() {
+    beforeEach(function() {
+      sinon.stub(controller, 'parseRequestContext').returns({
+        requestContext: 'r1'
+      });
+      sinon.stub(controller, 'generateTasksMapOfRequest').returns({
+        currentTaskStatusMap: {},
+        hostsMap: {}
+      });
+      sinon.stub(controller, 'propertyDidChange');
+    });
+    afterEach(function() {
+      controller.parseRequestContext.restore();
+      controller.generateTasksMapOfRequest.restore();
+      controller.propertyDidChange.restore();
+    });
+
+    it('should add request to list', function() {
+      controller.set('services', []);
+      controller.updateRequests({
+        progressPercent: 0,
+        requestStatus: 'PENDING',
+        startTime: 1,
+        endTime: -1,
+        requestId: 1,
+        requestContext: '',
+        Tasks: []
+      });
+      expect(controller.get('services').objectAt(0)).to.be.eql(Em.Object.create({
+        id: 1,
+        name: 'r1',
+        displayName: 'r1',
+        tasks: [],
+        progress: 0,
+        status: 'PENDING',
+        isRunning: true,
+        startTime: 1,
+        endTime: -1,
+        previousTaskStatusMap: {},
+        hostsMap: {}
+      }));
+      expect(controller.propertyDidChange.calledWith('services')).to.be.true;
+    });
+
+    it('should update request status', function() {
+      controller.set('services', [Em.Object.create({
+        id: 1,
+        name: 'r1',
+        displayName: 'r1',
+        tasks: [],
+        progress: 0,
+        status: 'PENDING',
+        isRunning: true,
+        startTime: 1,
+        endTime: -1,
+        previousTaskStatusMap: {},
+        hostsMap: {}
+      })]);
+      controller.updateRequests({
+        progressPercent: 100,
+        requestStatus: 'COMPLETED',
+        startTime: 1,
+        endTime: 1,
+        requestId: 1,
+        requestContext: '',
+        Tasks: []
+      });
+      expect(controller.get('services').objectAt(0)).to.be.eql(Em.Object.create({
+        id: 1,
+        name: 'r1',
+        displayName: 'r1',
+        tasks: [],
+        progress: 100,
+        status: 'COMPLETED',
+        isRunning: false,
+        startTime: 1,
+        endTime: 1,
+        previousTaskStatusMap: {},
+        hostsMap: {}
+      }))
+    });
+  });
+
+  describe('#generateTasksMapOfRequest', function() {
+
+    it('should return tasks map', function() {
+      var event = {
+        Tasks: [
+          {
+            hostName: 'host1',
+            id: 1,
+            status: 'PENDING',
+            requestId: 1
+          },
+          {
+            hostName: 'host1',
+            id: 2,
+            status: 'PENDING',
+            requestId: 1
+          },
+          {
+            hostName: 'host2',
+            id: 3,
+            status: 'COMPLETED',
+            requestId: 1
+          }
+        ]
+      };
+      expect(JSON.stringify(controller.generateTasksMapOfRequest(event, null).hostsMap)).to.be.equal(JSON.stringify({
+        "host1": {
+          "name": "host1",
+          "publicName": "host1",
+          "logTasks": [
+            {
+              "Tasks": {
+                "status": "PENDING",
+                "host_name": "host1",
+                "id": 1,
+                "request_id": 1
+              }
+            },
+            {
+              "Tasks": {
+                "status": "PENDING",
+                "host_name": "host1",
+                "id": 2,
+                "request_id": 1
+              }
+            }
+          ],
+          "isModified": true
+        },
+        "host2": {
+          "name": "host2",
+          "publicName": "host2",
+          "logTasks": [{"Tasks": {"status": "COMPLETED", "host_name": "host2", "id": 3, "request_id": 1}}],
+          "isModified": true
+        }
+      }));
+      expect(controller.generateTasksMapOfRequest(event, null).currentTaskStatusMap).to.be.eql({
+        "1": "PENDING",
+        "2": "PENDING",
+        "3": "COMPLETED"
+      });
+    });
+  });
+
+  describe('#convertTaskFromEventToApi', function() {
+
+    it('should return converted task object', function() {
+      expect(controller.convertTaskFromEventToApi({
+        status: 'PENDING',
+        hostName: 'host1',
+        id: 1,
+        requestId: 1
+      })).to.be.eql({
+          Tasks: {
+            status: 'PENDING',
+            host_name: 'host1',
+            id: 1,
+            request_id: 1
+          }
+        });
+    });
+  });
+
+  describe('#handleTaskUpdates', function() {
+    beforeEach(function() {
+      sinon.stub(controller, 'updateTask');
+      sinon.stub(App.StompClient, 'subscribe', function(arg1, clb) {
+        clb({status: 'COMPLETED', id: 1});
+      });
+      sinon.stub(App.StompClient, 'unsubscribe');
+    });
+    afterEach(function() {
+      controller.updateTask.restore();
+      App.StompClient.subscribe.restore();
+      App.StompClient.unsubscribe.restore();
+    });
+
+    it('should subscribe and when task is finished unsubscribe', function() {
+      controller.set('services', [Em.Object.create({
+        id: 1,
+        previousTaskStatusMap: {
+          1: 'PENDING'
+        }
+      })]);
+      controller.set('levelInfo', Em.Object.create({
+        name: 'TASK_DETAILS',
+        requestId: 1,
+        taskId: 1
+      }));
+      expect(App.StompClient.subscribe.calledWith('/events/tasks/1')).to.be.true;
+      expect(controller.updateTask.calledOnce).to.be.true;
+      expect(App.StompClient.unsubscribe.calledWith('/events/tasks/1')).to.be.true;
+    });
+  });
+
+  describe('#updateTask', function() {
+
+    it('should update task properties', function() {
+      var event = {
+        requestId: 1,
+        id: 1,
+        hostName: 'host1',
+        status: 'COMPLETED',
+        stdout: 'stdout',
+        stderr: 'stderr',
+        structured_out: 'structured_out',
+        outLog: 'outLog',
+        errorLog: 'errorLog'
+      };
+      controller.set('services', [Em.Object.create({
+        id: 1,
+        hostsMap: {
+          host1: {
+            logTasks: [
+              {
+                Tasks: {
+                  id: 1,
+                  requestId: 1,
+                  hostName: 'host1'
+                }
+              }
+            ]
+          }
+        }
+      })]);
+      controller.updateTask(event);
+      expect(controller.get('services').objectAt(0).get('hostsMap')['host1'].logTasks[0]).to.be.eql({
+        Tasks: {
+          requestId: 1,
+          id: 1,
+          hostName: 'host1',
+          status: 'COMPLETED',
+          stdout: 'stdout',
+          stderr: 'stderr',
+          structured_out: 'structured_out',
+          output_log: 'outLog',
+          error_log: 'errorLog'
+        }
+      });
+    });
+  });
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/b1d357ad/ambari-web/test/controllers/main/service_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/service_test.js b/ambari-web/test/controllers/main/service_test.js
index 7ed7641..869b06f 100644
--- a/ambari-web/test/controllers/main/service_test.js
+++ b/ambari-web/test/controllers/main/service_test.js
@@ -87,7 +87,7 @@ describe('App.MainServiceController', function () {
     mainServiceController.destroy();
   });
 
-  App.TestAliases.testAsComputedNotEqual(getController(), 'isStartStopAllClicked', 'App.router.backgroundOperationsController.allOperationsCount', 0);
+  App.TestAliases.testAsComputedNotEqual(getController(), 'isStartStopAllClicked', 'App.router.backgroundOperationsController.runningOperationsCount', 0);
 
   describe('#isStartAllDisabled', function () {
     tests.forEach(function (test) {