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;
});
});