You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by sr...@apache.org on 2014/01/10 22:36:14 UTC

git commit: AMBARI-4268. Implement rolling restart dialog (srimanth)

Updated Branches:
  refs/heads/trunk 7e73eba61 -> 93338ed9f


AMBARI-4268. Implement rolling restart dialog (srimanth)


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

Branch: refs/heads/trunk
Commit: 93338ed9fd80bc996f8251abba4b429d2e0be213
Parents: 7e73eba
Author: Srimanth Gunturi <sg...@hortonworks.com>
Authored: Fri Jan 10 13:36:02 2014 -0800
Committer: Srimanth Gunturi <sg...@hortonworks.com>
Committed: Fri Jan 10 13:36:02 2014 -0800

----------------------------------------------------------------------
 ambari-web/app/controllers/main/service/item.js |   8 +
 ambari-web/app/messages.js                      |  21 +++
 ambari-web/app/styles/application.less          |  27 +++
 .../templates/common/rolling_restart_view.hbs   |  62 +++++++
 ambari-web/app/utils/ajax.js                    |  53 ++++++
 .../app/utils/batch_scheduled_requests.js       | 167 +++++++++++++++++++
 ambari-web/app/views.js                         |   1 +
 .../app/views/common/rolling_restart_view.js    | 125 ++++++++++++++
 ambari-web/app/views/main/service/item.js       |  10 +-
 9 files changed, 473 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/controllers/main/service/item.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/service/item.js b/ambari-web/app/controllers/main/service/item.js
index 325e738..72cec23 100644
--- a/ambari-web/app/controllers/main/service/item.js
+++ b/ambari-web/app/controllers/main/service/item.js
@@ -18,6 +18,7 @@
 
 var App = require('app');
 var service_components = require('data/service_components');
+var batchUtils = require('utils/batch_scheduled_requests');
 
 App.MainServiceItemController = Em.Controller.extend({
   name: 'mainServiceItemController',
@@ -186,6 +187,13 @@ App.MainServiceItemController = Em.Controller.extend({
     });
   },
 
+  restartAllHostComponents: function(event) {
+  },
+
+  rollingRestart: function(hostComponentName) {
+    batchUtils.launchHostComponentRollingRestart(hostComponentName, false);
+  },
+
   runSmokeTestPrimary: function() {
     App.ajax.send({
       'name': 'service.item.smoke',

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 9b1d6bf..ea1500d 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -1759,6 +1759,27 @@ Em.I18n.translations = {
   'mirroring.dateOrder.error': 'End Date must be after Start Date',
   'mirroring.required.invalidNumberError' : 'Enter valid number',
 
+  'rollingrestart.dialog.title': 'Rolling Restart {0}s',
+  'rollingrestart.dialog.primary': 'Trigger Restart',
+  'rollingrestart.notsupported.hostComponent': 'Rolling restart not supported for {0} components',
+  'rollingrestart.dialog.msg.restart': 'This will restart {0} {1} in batches to keep your cluster operational and minimize downtime.',
+  'rollingrestart.dialog.msg.restart.plural': 'This will restart {0} {1}s in batches to keep your cluster operational and minimize downtime.',
+  'rollingrestart.dialog.msg.noRestartHosts': 'There are no {0}s to do rolling restarts',
+  'rollingrestart.dialog.msg.maintainance': 'Note: {0} {1} in Maintainance Mode will not be restarted',
+  'rollingrestart.dialog.msg.maintainance.plural': 'Note: {0} {1}s in Maintainance Mode will not be restarted',
+  'rollingrestart.dialog.msg.componentsAtATime': '{0}s at a time',
+  'rollingrestart.dialog.msg.timegap.prefix': 'Wait ',
+  'rollingrestart.dialog.msg.timegap.suffix': 'seconds between batches ',
+  'rollingrestart.dialog.msg.toleration.prefix': 'Tolerate up to ',
+  'rollingrestart.dialog.msg.toleration.suffix': 'failures',
+  'rollingrestart.dialog.err.empty.batchsize': 'Restart batch size cannot be empty',
+  'rollingrestart.dialog.err.empty.waittime': 'Wait interval cannot be empty',
+  'rollingrestart.dialog.err.empty.tolerate': 'Failure toleration size cannot be empty',
+  'rollingrestart.dialog.err.invalid.batchsize': 'Restart batch size should be between 1 and {0}',
+  'rollingrestart.dialog.err.invalid.waitTime': 'Wait time cannot be negative',
+  'rollingrestart.dialog.err.invalid.toleratesize': 'Failure toleration cannot be negative',
+  'rollingrestart.dialog.msg.staleConfigsOnly': 'Only restart {0}s with stale configs',
+  'rollingrestart.rest.context': 'Rolling Restart of {0}s - batch {1} of {2}',
 
   'menu.item.dashboard':'Dashboard',
   'menu.item.heatmaps':'Heatmaps',

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index ab97c5e..e919a43 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -5172,3 +5172,30 @@ i.icon-asterisks {
 .margin-bottom-5 {
   margin-bottom: 5px;
 }
+
+.rolling-restart-view {
+  table {
+    td:first-of-type {
+      width: 30%;
+      text-align: right;
+    }
+    td:nth-of-type(2) {
+      input {
+        margin-left: 7px;
+        margin-right: 5px;
+      }
+    }
+    td:last-of-type {
+      width: 60%;
+      text-align: left;
+    }
+    tr:last-of-type {
+      td {
+        text-align: center;
+        input {
+          vertical-align: top;
+        }
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/templates/common/rolling_restart_view.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/rolling_restart_view.hbs b/ambari-web/app/templates/common/rolling_restart_view.hbs
new file mode 100644
index 0000000..8b38044
--- /dev/null
+++ b/ambari-web/app/templates/common/rolling_restart_view.hbs
@@ -0,0 +1,62 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div class="rolling-restart-view">
+  <div class="alert alert-info">
+    <p>
+      {{view.restartMessage}}
+    </p>
+    {{#if view.maintainanceMessage}}
+      <p>
+        {{view.maintainanceMessage}}
+      </p>
+    {{/if}}
+  </div>
+  <table>
+    <tr>
+      <td>{{t common.restart}}</td>
+      <td>{{view Ember.TextField valueBinding="view.batchSize" class="span1"}}</td>
+      <td>{{view.batchSizeMessage}}</td>
+    </tr>
+    <tr>
+      <td>{{t rollingrestart.dialog.msg.timegap.prefix}}</td>
+      <td>{{view Ember.TextField valueBinding="view.interBatchWaitTimeSeconds" class="span1"}}</td>
+      <td>{{t rollingrestart.dialog.msg.timegap.suffix}}</td>
+    </tr>
+    <tr>
+      <td>{{t rollingrestart.dialog.msg.toleration.prefix}}</td>
+      <td>{{view Ember.TextField valueBinding="view.tolerateSize" class="span1"}}</td>
+      <td>{{t rollingrestart.dialog.msg.toleration.suffix}}</td>
+    </tr>
+    <tr>
+      <td colspan="3">
+        {{view Ember.Checkbox checkedBinding="view.staleConfigsOnly"}}
+        {{view.staleConfigsOnlyMessage}}
+      </td>
+    </tr>
+  </table>
+  {{#if view.errors}}
+    <div class="alert alert-warn">
+      <ul>
+      {{#each error in view.errors}}
+        <li>{{error}}</li>
+      {{/each}}
+      </ul>
+    </div>
+  {{/if}}
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/utils/ajax.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/ajax.js b/ambari-web/app/utils/ajax.js
index 992f12d..e690b67 100644
--- a/ambari-web/app/utils/ajax.js
+++ b/ambari-web/app/utils/ajax.js
@@ -1269,6 +1269,59 @@ var urls = {
         data: JSON.stringify(data.data)
       }
     }
+  },
+  'rolling_restart.post': {
+    'real': '/clusters/{clusterName}/request_schedules',
+    'mock': '',
+    'format' : function(data) {
+      var hostIndex = 0;
+      var batchSize = data.batchSize;
+      var hostComponents = data.restartHostComponents;
+      var length = hostComponents.length;
+      var batches = [];
+      var batchCount = Math.ceil(length / batchSize);
+      var sampleHostComponent = hostComponents.objectAt(0);
+      var componentName = sampleHostComponent.get('componentName');
+      var componentDisplayName = App.format.role(componentName);
+      var serviceName = sampleHostComponent.get('service.serviceName');
+      for ( var count = 0; count < batchCount; count++) {
+        var hostNames = [];
+        for ( var hc = 0; hc < batchSize && hostIndex < length; hc++) {
+          hostNames.push(hostComponents.objectAt(hostIndex++).get('host.hostName'));
+        }
+        if (hostNames.length > 0) {
+          batches.push({
+            "order_id" : count + 1,
+            "type" : "POST",
+            "uri" : "/api/v1/clusters/" + data.clusterName + "/requests",
+            "RequestBodyInfo" : {
+              "RequestInfo" : {
+                "context" : Em.I18n.t('rollingrestart.rest.context').format(componentDisplayName, (count + 1), batchCount),
+                "command" : "RESTART",
+                "service_name" : serviceName,
+                "component_name" : componentName,
+                "hosts" : hostNames.join(",")
+              }
+            }
+          });
+        }
+      }
+      return {
+        type : 'POST',
+        data : JSON.stringify([ {
+          "RequestSchedule" : {
+            "batch" : [ {
+              "requests" : batches
+            }, {
+              "batch_settings" : {
+                "batch_separation_in_seconds" : data.intervalTimeSeconds,
+                "task_failure_tolerance" : data.tolerateSize
+              }
+            } ]
+          }
+        } ])
+      }
+    }
   }
 };
 /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/utils/batch_scheduled_requests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/batch_scheduled_requests.js b/ambari-web/app/utils/batch_scheduled_requests.js
new file mode 100644
index 0000000..2f8ae78
--- /dev/null
+++ b/ambari-web/app/utils/batch_scheduled_requests.js
@@ -0,0 +1,167 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * Contains helpful utilities for handling batch and scheduled requests.
+ */
+module.exports = {
+
+  /**
+   * Some services have components which have a need for rolling restarts. This
+   * method returns the name of the host-component which supports rolling
+   * restarts for a service.
+   */
+  getRollingRestartComponentName : function(serviceName) {
+    var rollingRestartComponent = null;
+    switch (serviceName) {
+    case 'HDFS':
+      rollingRestartComponent = 'DATANODE';
+      break;
+    case 'YARN':
+      rollingRestartComponent = 'NODEMANAGER';
+      break;
+    case 'MAPREDUCE':
+      rollingRestartComponent = 'TASKTRACKER';
+      break;
+    case 'HBASE':
+      rollingRestartComponent = 'HBASE_REGIONSERVER';
+      break;
+    case 'STORM':
+      rollingRestartComponent = 'SUPERVISOR';
+      break;
+    default:
+      break;
+    }
+    return rollingRestartComponent;
+  },
+
+  /**
+   * Makes a REST call to the server requesting the rolling restart of the
+   * provided host components.
+   */
+  doPostBatchRollingRestartRequest : function(restartHostComponents, batchSize, intervalTimeSeconds, tolerateSize, successCallback, errorCallback) {
+    var clusterName = App.get('clusterName');
+    var data = {
+      restartHostComponents : restartHostComponents,
+      batchSize : batchSize,
+      intervalTimeSeconds : intervalTimeSeconds,
+      tolerateSize : tolerateSize,
+      clusterName : clusterName
+    }
+    var sender = {
+      successFunction : function() {
+        successCallback();
+      },
+      errorFunction : function(xhr, textStatus, error, opt) {
+        errorCallback(xhr, textStatus, error, opt);
+      }
+    }
+    App.ajax.send({
+      name : 'rolling_restart.post',
+      sender : sender,
+      data : data,
+      success : 'successFunction',
+      error : 'errorFunction'
+    });
+  },
+
+  /**
+   * Launches dialog to handle rolling restarts of host components.
+   *
+   * Rolling restart is supported for the following host components only
+   * <ul>
+   * <li>Data Nodes (HDFS)
+   * <li>Task Trackers (MapReduce)
+   * <li>Node Managers (YARN)
+   * <li>Region Servers (HBase)
+   * <li>Supervisors (Storm)
+   * </ul>
+   *
+   * @param {String}
+   *          hostComponentName Type of host-component to restart across cluster
+   *          (ex: DATANODE)
+   * @param {Boolean}
+   *          staleConfigsOnly Pre-select host-components which have stale
+   *          configurations
+   */
+  launchHostComponentRollingRestart : function(hostComponentName, staleConfigsOnly) {
+    var componentDisplayName = App.format.role(hostComponentName);
+    if (!componentDisplayName) {
+      componentDisplayName = hostComponentName;
+    }
+    var self = this;
+    var title = Em.I18n.t('rollingrestart.dialog.title').format(componentDisplayName)
+    if (hostComponentName == "DATANODE" || hostComponentName == "TASKTRACKER" || hostComponentName == "NODEMANAGER"
+        || hostComponentName == "HBASE_REGIONSERVER" || hostComponentName == "SUPERVISOR") {
+      App.ModalPopup.show({
+        header : title,
+        hostComponentName : hostComponentName,
+        staleConfigsOnly : staleConfigsOnly,
+        innerView : null,
+        bodyClass : App.RollingRestartView.extend({
+          hostComponentName : hostComponentName,
+          staleConfigsOnly : staleConfigsOnly,
+          didInsertElement : function() {
+            this.set('parentView.innerView', this);
+            this.initialize();
+          }
+        }),
+        classNames : [ 'rolling-restart-popup' ],
+        primary : Em.I18n.t('rollingrestart.dialog.primary'),
+        secondary : Em.I18n.t('common.cancel'),
+        onPrimary : function() {
+          var dialog = this;
+          if (!dialog.get('enablePrimary')) {
+            return false;
+          }
+          var restartComponents = this.get('innerView.restartHostComponents');
+          var batchSize = this.get('innerView.batchSize');
+          var waitTime = this.get('innerView.interBatchWaitTimeSeconds');
+          var tolerateSize = this.get('innerView.tolerateSize');
+          self.doPostBatchRollingRestartRequest(restartComponents, batchSize, waitTime, tolerateSize, function() {
+            dialog.hide();
+          }, function(xhr, textStatus, error, opt) {
+            App.ajax.defaultErrorHandler(xhr, opt.url, 'POST', xhr.status);
+          });
+          return;
+        },
+        onSecondary : function() {
+          this.hide();
+        },
+        onClose : function() {
+          this.hide();
+        },
+        updateButtons : function() {
+          var errors = this.get('innerView.errors');
+          this.set('enablePrimary', !(errors != null && errors.length > 0))
+        }.observes('innerView.errors'),
+      });
+    } else {
+      var msg = Em.I18n.t('rollingrestart.notsupported.hostComponent').format(componentDisplayName);
+      console.log(msg);
+      App.ModalPopup.show({
+        header : title,
+        secondary : false,
+        msg : msg,
+        bodyClass : Ember.View.extend({
+          template : Ember.Handlebars.compile('<div class="alert alert-warning">{{msg}}</div>'),
+          msg : msg
+        })
+      });
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index 9a9b36a..ed21192 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -25,6 +25,7 @@ require('views/common/chart/pie');
 require('views/common/chart/linear');
 require('views/common/chart/linear_time');
 require('views/common/modal_popup');
+require('views/common/rolling_restart_view');
 require('views/common/metric');
 require('views/common/time_range');
 require('views/common/form/field');

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/views/common/rolling_restart_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/rolling_restart_view.js b/ambari-web/app/views/common/rolling_restart_view.js
new file mode 100644
index 0000000..2671ddd
--- /dev/null
+++ b/ambari-web/app/views/common/rolling_restart_view.js
@@ -0,0 +1,125 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+var App = require('app');
+
+/**
+ * View content of the rolling restart dialog.
+ *
+ * Callers provide the context in which this dialog is invoked.
+ */
+App.RollingRestartView = Em.View.extend({
+  templateName : require('templates/common/rolling_restart_view'),
+  hostComponentName : null,
+  staleConfigsOnly : false,
+  batchSize : -1,
+  interBatchWaitTimeSeconds : -1,
+  tolerateSize : -1,
+  errors : null,
+  initialize : function() {
+    if (this.get('batchSize') == -1 && this.get('interBatchWaitTimeSeconds') == -1 && this.get('tolerateSize') == -1) {
+      var restartCount = this.get('restartHostComponents');
+      var batchSize = 1;
+      if (restartCount > 10) {
+        batchSize = Math.ceil(restartCount / 10);
+      }
+      var tolerateCount = batchSize;
+      this.set('batchSize', batchSize);
+      this.set('tolerateSize', tolerateCount);
+      this.set('interBatchWaitTimeSeconds', 120);
+    }
+  },
+  validate : function() {
+    var displayName = this.get('hostComponentDisplayName');
+    var totalCount = this.get('restartHostComponents.length');
+    var bs = this.get('batchSize');
+    var ts = this.get('tolerateSize');
+    var wait = this.get('interBatchWaitTimeSeconds');
+    var errors = [];
+    if (totalCount < 1) {
+      errors.push(Em.I18n.t('rollingrestart.dialog.msg.noRestartHosts').format(displayName));
+    } else {
+      if (!bs) {
+        errors.push(Em.I18n.t('rollingrestart.dialog.err.empty.batchsize'));
+      } else if (bs > totalCount || bs < 0) {
+        errors.push(Em.I18n.t('rollingrestart.dialog.err.invalid.batchsize').format(totalCount));
+      }
+      if (!ts) {
+        errors.push(Em.I18n.t('rollingrestart.dialog.err.empty.waittime'));
+      } else if (ts < 0) {
+        errors.push(Em.I18n.t('rollingrestart.dialog.err.invalid.toleratesize'));
+      }
+    }
+    if (!wait) {
+      errors.push(Em.I18n.t('rollingrestart.dialog.err.empty.tolerate'));
+    } else if (wait < 0) {
+      errors.push(Em.I18n.t('rollingrestart.dialog.err.invalid.waitTime'));
+    }
+    if (errors.length < 1) {
+      errors = null;
+    }
+    this.set('errors', errors);
+  }.observes('batchSize', 'interBatchWaitTimeSeconds', 'tolerateSize', 'restartHostComponents', 'hostComponentDisplayName'),
+  hostComponentDisplayName : function() {
+    return App.format.role(this.get('hostComponentName'));
+  }.property('hostComponentName'),
+  allHostComponents : function() {
+    return App.HostComponent.find().filterProperty('componentName', this.get('hostComponentName'));
+  }.property('hostComponentName'),
+  nonMaintainanceHostComponents : function() {
+    var hostComponents = this.get('allHostComponents');
+    hostComponents = hostComponents.filter(function(item) {
+      if (item.get('workStatus') !== App.HostComponentStatus.maintenance) {
+        return true;
+      }
+    });
+    return hostComponents;
+  }.property('allHostComponents', 'allHostComponents.@each.workStatus'),
+  restartHostComponents : function() {
+    var hostComponents = this.get('nonMaintainanceHostComponents');
+    if (this.get('staleConfigsOnly')) {
+      hostComponents = hostComponents.filterProperty('staleConfigs', true);
+    }
+    return hostComponents;
+  }.property('nonMaintainanceHostComponents', 'staleConfigsOnly'),
+  restartMessage : function() {
+    var rhc = this.get('restartHostComponents.length');
+    if (rhc > 1) {
+      return Em.I18n.t('rollingrestart.dialog.msg.restart.plural').format(rhc, this.get('hostComponentDisplayName'))
+    }
+    return Em.I18n.t('rollingrestart.dialog.msg.restart').format(rhc, this.get('hostComponentDisplayName'))
+  }.property('restartHostComponents', 'hostComponentDisplayName'),
+  maintainanceMessage : function() {
+    var allCount = this.get('allHostComponents.length');
+    var nonMaintainCount = this.get('nonMaintainanceHostComponents.length');
+    var count = allCount - nonMaintainCount;
+    if (count > 0) {
+      var name = this.get('hostComponentDisplayName');
+      if (count > 1) {
+        return Em.I18n.t('rollingrestart.dialog.msg.maintainance.plural').format(count, name)
+      }
+      return Em.I18n.t('rollingrestart.dialog.msg.maintainance').format(count, name)
+    }
+    return null;
+  }.property('allHostComponents', 'nonMaintainanceHostComponents', 'hostComponentDisplayName'),
+  batchSizeMessage : function() {
+    return Em.I18n.t('rollingrestart.dialog.msg.componentsAtATime').format(this.get('hostComponentDisplayName'));
+  }.property('hostComponentDisplayName'),
+  staleConfigsOnlyMessage : function() {
+    return Em.I18n.t('rollingrestart.dialog.msg.staleConfigsOnly').format(this.get('hostComponentDisplayName'));
+  }.property('hostComponentDisplayName')
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/93338ed9/ambari-web/app/views/main/service/item.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/item.js b/ambari-web/app/views/main/service/item.js
index fc1adcf..e67b303 100644
--- a/ambari-web/app/views/main/service/item.js
+++ b/ambari-web/app/views/main/service/item.js
@@ -17,6 +17,7 @@
  */
 
 var App = require('app');
+var batchUtils = require('utils/batch_scheduled_requests');
 
 App.MainServiceItemView = Em.View.extend({
   templateName: require('templates/main/service/item'),
@@ -26,7 +27,8 @@ App.MainServiceItemView = Em.View.extend({
     var hosts = App.Host.find().content.length;
     var allMasters = this.get('controller.content.hostComponents').filterProperty('isMaster').mapProperty('componentName').uniq();
     var disabled = this.get('controller.isStopDisabled');
-    switch (service.get('serviceName')) {
+    var serviceName = service.get('serviceName');
+    switch (serviceName) {
       case 'GANGLIA':
       case 'NAGIOS':
         break;
@@ -44,6 +46,12 @@ App.MainServiceItemView = Em.View.extend({
       default:
         options.push({action: 'runSmokeTest', 'label': Em.I18n.t('services.service.actions.run.smoke').format(service.get('serviceName')), disabled:disabled});
     }
+
+    var rrComponentName = batchUtils.getRollingRestartComponentName(serviceName);
+    if (rrComponentName) {
+      var label = Em.I18n.t('rollingrestart.dialog.title').format(App.format.role(rrComponentName));
+      options.push({action:'rollingRestart', context: rrComponentName, 'label': label, disabled: false});
+    }
     return options;
   }.property('controller.content', 'controller.isStopDisabled'),
   isMaintenanceActive: function() {