You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2016/03/07 11:13:29 UTC

ambari git commit: AMBARI-15302 Service deletion should recommend and change configs before deleting service.(ababiichuk)

Repository: ambari
Updated Branches:
  refs/heads/trunk 65f0ff68f -> b88ede8df


AMBARI-15302 Service deletion should recommend and change configs before deleting service.(ababiichuk)


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

Branch: refs/heads/trunk
Commit: b88ede8dfef4fb6595ac7fe122a09eee7dba5d17
Parents: 65f0ff6
Author: ababiichuk <ab...@hortonworks.com>
Authored: Fri Mar 4 17:49:04 2016 +0200
Committer: ababiichuk <ab...@hortonworks.com>
Committed: Mon Mar 7 11:49:20 2016 +0200

----------------------------------------------------------------------
 ambari-web/app/controllers/main/service/item.js | 214 ++++++++++++++++---
 .../app/controllers/wizard/step7_controller.js  |   1 +
 ambari-web/app/messages.js                      |   2 +
 .../app/mixins/common/configs/configs_saver.js  |  12 +-
 ambari-web/app/utils/blueprint.js               |  22 ++
 .../configs/widgets/plain_config_text_field.js  |   6 +-
 .../widgets/textfield_config_widget_view.js     |   3 +-
 .../test/controllers/main/service/item_test.js  |  37 +++-
 8 files changed, 242 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/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 92d4d79..eda0485 100644
--- a/ambari-web/app/controllers/main/service/item.js
+++ b/ambari-web/app/controllers/main/service/item.js
@@ -18,8 +18,9 @@
 
 var App = require('app');
 var batchUtils = require('utils/batch_scheduled_requests');
+var blueprintUtils = require('utils/blueprint');
 
-App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDownload, App.InstallComponent, {
+App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDownload, App.InstallComponent, App.ConfigsSaverMixin, App.EnhancedConfigsMixin, {
   name: 'mainServiceItemController',
 
   /**
@@ -106,20 +107,85 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
   }.property('content.serviceName'),
 
   /**
-   * Load all config tags for loading configs
+   * Returns interdependent services
+   *
+   * @returns {string[]}
    */
-  loadConfigs: function(){
-    if (this.get('serviceConfigsMap')[this.get('content.serviceName')]) {
-      this.set('isServiceConfigsLoaded', false);
-      App.ajax.send({
-        name: 'config.tags',
-        sender: this,
-        success: 'onLoadConfigsTags',
-        error: 'onTaskError'
+  interDependentServices: function() {
+    var serviceName = this.get('content.serviceName'), interDependentServices = [];
+    App.StackService.find(serviceName).get('requiredServices').forEach(function(requiredService) {
+      if (App.StackService.find(requiredService).get('requiredServices').contains(serviceName)) {
+        interDependentServices.push(requiredService);
+      }
+    });
+    return interDependentServices;
+  }.property('content.serviceName'),
+
+  /**
+   * collection of serviceConfigs
+   *
+   * @type {Object[]}
+   */
+  stepConfigs: [],
+
+  /**
+   * List of service names that have configs dependent on current service configs
+   *
+   * @type {String[]}
+   */
+  dependentServiceNames: function() {
+    return App.StackService.find(this.get('content.serviceName')).get('dependentServiceNames');
+  }.property('content.serviceName'),
+
+  /**
+   * List of service names that could be deleted
+   * Common case when there is only current service should be removed
+   * But for some services there is <code>interDependentServices<code> services
+   * Like 'YARN' depends on 'MAPREDUCE2' and 'MAPREDUCE2' depends on 'YARN'
+   * So these services can be removed only together
+   *
+   * @type {String[]}
+   */
+  serviceNamesToDelete: function() {
+    return [this.get('content.serviceName')].concat(this.get('interDependentServices'));
+  }.property('content.serviceName'),
+
+  /**
+   * List of config types that should be loaded
+   * Includes
+   * 1. Dependent services config-types
+   * 2. Some special cases from <code>serviceConfigsMap<code>
+   * 3. 'cluster-env'
+   *
+   * @type {String[]}
+   */
+  sitesToLoad: function() {
+    var services = this.get('dependentServiceNames'), configTypeList = [];
+    if (services.length) {
+      var configTypeList = App.StackService.find().filter(function(s) {
+        return services.contains(s.get('serviceName'));
+      }).mapProperty('configTypeList').reduce(function(p, v) {
+        return p.concat(v);
       });
-    } else {
-      this.set('isServiceConfigsLoaded', true);
     }
+    if (this.get('serviceConfigsMap')[this.get('content.serviceName')]) {
+      configTypeList = configTypeList.concat(this.get('serviceConfigsMap')[this.get('content.serviceName')]);
+    }
+    configTypeList.push('cluster-env');
+    return configTypeList.uniq();
+  }.property('content.serviceName'),
+
+  /**
+   * Load all config tags for loading configs
+   */
+  loadConfigs: function(){
+    this.set('isServiceConfigsLoaded', false);
+    App.ajax.send({
+      name: 'config.tags',
+      sender: this,
+      success: 'onLoadConfigsTags',
+      error: 'onTaskError'
+    });
   },
 
   /**
@@ -128,7 +194,7 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
    */
   onLoadConfigsTags: function (data) {
     var self = this;
-    var sitesToLoad = this.get('serviceConfigsMap')[this.get('content.serviceName')];
+    var sitesToLoad = this.get('sitesToLoad'), allConfigs = [];
     var loadedSites = data.Clusters.desired_configs;
     var siteTagsToLoad = [];
     for (var site in loadedSites) {
@@ -142,7 +208,17 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
     App.router.get('configurationController').getConfigsByTags(siteTagsToLoad).done(function (configs) {
       configs.forEach(function (site) {
         self.get('configs')[site.type] = site.properties;
+        allConfigs = allConfigs.concat(App.config.getConfigsFromJSON(site, true));
+      });
+
+      self.get('dependentServiceNames').forEach(function(serviceName) {
+        var configTypes = App.StackService.find(serviceName).get('configTypeList');
+        var configsByService = allConfigs.filter(function (c) {
+          return configTypes.contains(App.config.getConfigTagFromFileName(c.get('filename')));
+        });
+        self.get('stepConfigs').pushObject(App.config.createServiceConfig(serviceName, [], configsByService));
       });
+
       self.set('isServiceConfigsLoaded', true);
     });
   },
@@ -971,22 +1047,6 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
   },
 
   /**
-   * Returns interdependent services
-   *
-   * @param serviceName
-   * @returns {string[]}
-   */
-  interDependentServices: function(serviceName) {
-    var interDependentServices = [];
-    App.StackService.find(serviceName).get('requiredServices').forEach(function(requiredService) {
-      if (App.StackService.find(requiredService).get('requiredServices').contains(serviceName)) {
-        interDependentServices.push(requiredService);
-      }
-    });
-    return interDependentServices;
-  },
-
-  /**
    * find dependent services
    * @param {string[]} serviceNamesToDelete
    * @returns {Array}
@@ -1035,8 +1095,8 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
    */
   deleteService: function(serviceName) {
     var self = this,
-      interDependentServices = this.interDependentServices(serviceName),
-      serviceNamesToDelete = interDependentServices.concat(serviceName),
+      interDependentServices = this.get('interDependentServices'),
+      serviceNamesToDelete = this.get('serviceNamesToDelete'),
       dependentServices = this.findDependentServices(serviceNamesToDelete),
       displayName = App.format.role(serviceName),
       popupHeader = Em.I18n.t('services.service.delete.popup.header'),
@@ -1168,6 +1228,96 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
   },
 
   /**
+   * All host names
+   * This property required for request for recommendations
+   *
+   * @type {String[]}
+   * @override
+   */
+  hostNames: Em.computed.alias('App.allHostNames'),
+
+  /**
+   * Recommendation object
+   * This property required for request for recommendations
+   *
+   * @type {Object}
+   * @override
+   */
+  hostGroups: function() {
+    var hostGroup = blueprintUtils.generateHostGroups(App.get('allHostNames'));
+    return blueprintUtils.removeDeletedComponents(hostGroup, [this.get('serviceNamesToDelete')]);
+  }.property('serviceNamesToDelete', 'App.allHostNames'),
+
+  /**
+   * List of services without removed
+   * This property required for request for recommendations
+   *
+   * @type {String[]}
+   * @override
+   */
+  serviceNames: function() {
+    return App.Service.find().filter(function(s) {
+      return !this.get('serviceNamesToDelete').contains(s.get('serviceName'));
+    }, this).mapProperty('serviceName');
+  }.property('serviceNamesToDelete'),
+
+  /**
+   * This property required for request for recommendations
+   *
+   * @return {Boolean}
+   * @override
+   */
+  isConfigHasInitialState: function() { return false; },
+
+  /**
+   * Describes condition when recommendation should be applied
+   * For removing services it's always true
+   * This property required for request for recommendations
+   *
+   * @return {Boolean}
+   * @override
+   */
+  allowUpdateProperty: function() { return true; },
+
+  /**
+   * Just config version note
+   *
+   * @type {String}
+   */
+  serviceConfigVersionNote: function() {
+    var services = this.get('serviceNamesToDelete').join(',');
+    if (this.get('serviceNamesToDelete.length') === 1) {
+      return Em.I18n.t('services.service.delete.configVersionNote').format(services);
+    }
+    return Em.I18n.t('services.service.delete.configVersionNote.plural').format(services);
+  }.property('serviceNamesToDelete'),
+
+  /**
+   * Method ot save configs after service have been removed
+   * @override
+   */
+  saveConfigs: function() {
+    var data = [];
+    this.get('stepConfigs').forEach(function(stepConfig) {
+      var serviceConfig = this.getServiceConfigToSave(stepConfig.get('serviceName'), stepConfig.get('configs'));
+
+      if (serviceConfig)  {
+        data.push(serviceConfig);
+      }
+    }, this);
+
+    if (Em.isArray(data) && data.length) {
+      this.putChangedConfigurations(data, 'onSaveConfigs');
+    } else {
+      this.onSaveConfigs();
+    }
+  },
+
+  onSaveConfigs: function() {
+    window.location.reload();
+  },
+
+  /**
    * Ajax call to delete service
    * @param {string[]} serviceNames
    * @returns {$.ajax}
@@ -1193,7 +1343,7 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
     if (params.servicesToDeleteNext) {
       this.deleteServiceCall(params.servicesToDeleteNext);
     } else {
-      window.location.reload();
+      this.loadConfigRecommendations(null, this.saveConfigs.bind(this));
     }
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/ambari-web/app/controllers/wizard/step7_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/wizard/step7_controller.js b/ambari-web/app/controllers/wizard/step7_controller.js
index 5557d69..06488b1 100644
--- a/ambari-web/app/controllers/wizard/step7_controller.js
+++ b/ambari-web/app/controllers/wizard/step7_controller.js
@@ -1327,6 +1327,7 @@ App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, App.E
    * @override
    */
   allowUpdateProperty: function(parentProperties, name, fileName) {
+    if (name.contains('proxyuser')) return true;
     if (['installerController'].contains(this.get('wizardController.name')) || !!(parentProperties && parentProperties.length)) {
       return true;
     } else if (['addServiceController'].contains(this.get('wizardController.name'))) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 7aa38e4..a3a4d1d 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -1714,6 +1714,8 @@ Em.I18n.translations = {
   'services.service.actions.serviceActions':'Service Actions',
 
   'services.service.delete.popup.header': 'Delete Service',
+  'services.service.delete.configVersionNote': 'Update configs after {0} has been removed',
+  'services.service.delete.configVersionNote.plural': 'Update configs after {0} have been removed',
   'services.service.delete.lastService.popup.body': 'The <b>{0}</b> service can\'t be deleted, at least one service must be installed.',
   'services.service.delete.popup.dependentServices': 'Prior to deleting <b>{0}</b>, you must delete the following dependent services:',
   'services.service.delete.popup.mustBeStopped': 'Prior to deleting <b>{0}</b>, you must stop the service.',

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/ambari-web/app/mixins/common/configs/configs_saver.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/configs/configs_saver.js b/ambari-web/app/mixins/common/configs/configs_saver.js
index 23f69c2..4f1952c 100644
--- a/ambari-web/app/mixins/common/configs/configs_saver.js
+++ b/ambari-web/app/mixins/common/configs/configs_saver.js
@@ -135,7 +135,7 @@ App.ConfigsSaverMixin = Em.Mixin.create({
       }, this);
 
       if (Em.isArray(data) && data.length) {
-        this.putChangedConfigurations(data, true);
+        this.putChangedConfigurations(data, 'doPUTClusterConfigurationSiteSuccessCallback');
       } else {
         this.onDoPUTClusterConfigurations();
       }
@@ -151,7 +151,7 @@ App.ConfigsSaverMixin = Em.Mixin.create({
             var configsToSave = this.getServiceConfigToSave(serviceName, configs);
 
             if (configsToSave) {
-              this.putChangedConfigurations([configsToSave], false);
+              this.putChangedConfigurations([configsToSave]);
             }
 
           } else {
@@ -573,11 +573,11 @@ App.ConfigsSaverMixin = Em.Mixin.create({
    * Saves configuration of set of sites. The provided data
    * contains the site name and tag to be used.
    * @param {Object[]} services
-   * @param {boolean} showPopup
+   * @param {String} [successCallback]
    * @return {$.ajax}
    * @method putChangedConfigurations
    */
-  putChangedConfigurations: function (services, showPopup) {
+  putChangedConfigurations: function (services, successCallback) {
     var ajaxData = {
       name: 'common.across.services.configurations',
       sender: this,
@@ -586,8 +586,8 @@ App.ConfigsSaverMixin = Em.Mixin.create({
       },
       error: 'doPUTClusterConfigurationSiteErrorCallback'
     };
-    if (showPopup) {
-      ajaxData.success = 'doPUTClusterConfigurationSiteSuccessCallback'
+    if (successCallback) {
+      ajaxData.success = successCallback;
     }
     return App.ajax.send(ajaxData);
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/ambari-web/app/utils/blueprint.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/blueprint.js b/ambari-web/app/utils/blueprint.js
index dd9555a..30e69af 100644
--- a/ambari-web/app/utils/blueprint.js
+++ b/ambari-web/app/utils/blueprint.js
@@ -396,6 +396,28 @@ module.exports = {
   },
 
   /**
+   * Clean up host groups from components that should be removed
+   *
+   * @param hostGroups
+   * @param serviceNames
+   */
+  removeDeletedComponents: function(hostGroups, serviceNames) {
+    var components = [];
+    App.StackService.find().forEach(function(s) {
+      if (serviceNames.contains(s.get('serviceName'))) {
+        components = components.concat(s.get('serviceComponents').mapProperty('componentName'));
+      }
+    });
+
+    hostGroups.blueprint.host_groups.forEach(function(hg) {
+      hg.components = hg.components.filter(function(c) {
+        return !components.contains(c.name);
+      })
+    });
+    return hostGroups;
+  },
+
+  /**
    * collect all component names that are present on hosts
    * @returns {object}
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/ambari-web/app/views/common/configs/widgets/plain_config_text_field.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/configs/widgets/plain_config_text_field.js b/ambari-web/app/views/common/configs/widgets/plain_config_text_field.js
index 25b9994..1403acc 100644
--- a/ambari-web/app/views/common/configs/widgets/plain_config_text_field.js
+++ b/ambari-web/app/views/common/configs/widgets/plain_config_text_field.js
@@ -24,7 +24,7 @@
 var App = require('app');
 require('views/common/controls_view');
 
-App.PlainConfigTextField = Ember.View.extend(App.SupportsDependentConfigs, App.WidgetPopoverSupport, {
+App.PlainConfigTextField = Ember.View.extend(App.SupportsDependentConfigs, App.WidgetPopoverSupport, App.ValueObserver, {
   templateName: require('templates/common/configs/widgets/plain_config_text_field'),
   valueBinding: 'config.value',
   classNames: ['widget-config-plain-text-field'],
@@ -52,10 +52,6 @@ App.PlainConfigTextField = Ember.View.extend(App.SupportsDependentConfigs, App.W
     return unit;
   }.property('unit'),
 
-  focusOut: function () {
-    this.sendRequestRorDependentConfigs(this.get('config'));
-  },
-
   insertNewline: function() {
     this.get('parentView').trigger('toggleWidgetView');
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/ambari-web/app/views/common/configs/widgets/textfield_config_widget_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/configs/widgets/textfield_config_widget_view.js b/ambari-web/app/views/common/configs/widgets/textfield_config_widget_view.js
index 48a1321..bffaaf8 100644
--- a/ambari-web/app/views/common/configs/widgets/textfield_config_widget_view.js
+++ b/ambari-web/app/views/common/configs/widgets/textfield_config_widget_view.js
@@ -34,8 +34,7 @@ App.TextFieldConfigWidgetView = App.ConfigWidgetView.extend({
   configView: App.ServiceConfigTextField.extend({
     isPopoverEnabled: 'false',
     textFieldClassName: 'span12',
-    serviceConfigBinding: 'parentView.config',
-    focusIn: function() {}
+    serviceConfigBinding: 'parentView.config'
   }),
 
   didInsertElement: function() {

http://git-wip-us.apache.org/repos/asf/ambari/blob/b88ede8d/ambari-web/test/controllers/main/service/item_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/service/item_test.js b/ambari-web/test/controllers/main/service/item_test.js
index 8fb772a..f2fcd2d 100644
--- a/ambari-web/test/controllers/main/service/item_test.js
+++ b/ambari-web/test/controllers/main/service/item_test.js
@@ -1262,16 +1262,18 @@ describe('App.MainServiceItemController', function () {
       sinon.stub(mainServiceItemController, 'servicesDisplayNames', function(servicesDisplayNames) {
         return servicesDisplayNames;
       });
-      sinon.stub(mainServiceItemController, 'interDependentServices').returns([]);
       this.allowUninstallServices = sinon.stub(mainServiceItemController, 'allowUninstallServices');
       this.mockService = sinon.stub(App.Service, 'find');
       sinon.stub(App, 'showConfirmationPopup');
       sinon.stub(App.ModalPopup, 'show');
       sinon.stub(App.format, 'role', function(name) {return name});
+
+      mainServiceItemController.reopen({
+        interDependentServices: []
+      })
     });
     afterEach(function() {
       mainServiceItemController.allowUninstallServices.restore();
-      mainServiceItemController.interDependentServices.restore();
       mainServiceItemController.servicesDisplayNames.restore();
       this.mockDependentServices.restore();
       this.mockService.restore();
@@ -1372,16 +1374,27 @@ describe('App.MainServiceItemController', function () {
       sinon.stub(App.StackService, 'find', function (serviceName) {
         return stackSerivceModel[serviceName];
       });
-      mainServiceItemController = App.MainServiceItemController.create({});
+      mainServiceItemController = App.MainServiceItemController.create({
+        content: {}
+      });
     });
 
     afterEach(function() {
       App.StackService.find.restore();
     });
 
-    it('get interdependent services', function() {
-      expect(mainServiceItemController.interDependentServices('YARN')).to.eql(['MAPREDUCE2']);
-      expect(mainServiceItemController.interDependentServices('MAPREDUCE2')).to.eql(['YARN']);
+    it('get interdependent services for YARN', function() {
+      mainServiceItemController.set('content', Em.Object.create({
+        serviceName: 'YARN'
+      }));
+      expect(mainServiceItemController.get('interDependentServices')).to.eql(['MAPREDUCE2']);
+    });
+
+    it('get interdependent services for MAPREDUCE2', function() {
+      mainServiceItemController.set('content', Em.Object.create({
+        serviceName: 'MAPREDUCE2'
+      }));
+      expect(mainServiceItemController.get('interDependentServices')).to.eql(['YARN']);
     });
   });
 
@@ -1409,23 +1422,27 @@ describe('App.MainServiceItemController', function () {
 
     beforeEach(function() {
       mainServiceItemController = App.MainServiceItemController.create({});
-      sinon.stub(window.location, 'reload');
+      sinon.spy(mainServiceItemController, 'loadConfigRecommendations');
       sinon.spy(mainServiceItemController, 'deleteServiceCall');
+      mainServiceItemController.reopen({
+        interDependentServices: []
+      })
     });
     afterEach(function() {
-      window.location.reload.restore();
+      mainServiceItemController.loadConfigRecommendations.restore();
+      mainServiceItemController.deleteServiceCall.restore();
     });
 
     it("window.location.reload should be called", function() {
       mainServiceItemController.deleteServiceCallSuccessCallback([], null, {});
       expect(mainServiceItemController.deleteServiceCall.called).to.be.false;
-      expect(window.location.reload.calledOnce).to.be.true;
+      expect(mainServiceItemController.loadConfigRecommendations.calledOnce).to.be.true;
     });
 
     it("deleteServiceCall should be called", function() {
       mainServiceItemController.deleteServiceCallSuccessCallback([], null, {servicesToDeleteNext: true});
       expect(mainServiceItemController.deleteServiceCall.calledOnce).to.be.true;
-      expect(window.location.reload.called).to.be.false;
+      expect(mainServiceItemController.loadConfigRecommendations.called).to.be.false;
     });
   });