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/02/05 11:41:13 UTC
[2/2] ambari git commit: AMBARI-14934 Can't remove YARN or
MapReduce2.(ababiichuk)
AMBARI-14934 Can't remove YARN or MapReduce2.(ababiichuk)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/315f502b
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/315f502b
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/315f502b
Branch: refs/heads/trunk
Commit: 315f502b0e7fed166f7d91387fa41f85e8dc891a
Parents: 94932c9
Author: ababiichuk <ab...@hortonworks.com>
Authored: Fri Feb 5 12:37:25 2016 +0200
Committer: ababiichuk <ab...@hortonworks.com>
Committed: Fri Feb 5 12:37:25 2016 +0200
----------------------------------------------------------------------
ambari-web/app/controllers/main/service/item.js | 107 ++++++++++++----
ambari-web/app/messages.js | 4 +
ambari-web/app/models/stack_service.js | 2 +-
ambari-web/app/utils/ajax/ajax.js | 5 -
.../test/controllers/main/service/item_test.js | 122 +++++++++++++++----
5 files changed, 189 insertions(+), 51 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/315f502b/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 a26c820..1aae7cd 100644
--- a/ambari-web/app/controllers/main/service/item.js
+++ b/ambari-web/app/controllers/main/service/item.js
@@ -966,31 +966,76 @@ 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} serviceName
+ * @param {string[]} serviceNamesToDelete
* @returns {Array}
*/
- findDependentServices: function(serviceName) {
+ findDependentServices: function (serviceNamesToDelete) {
var dependentServices = [];
- App.StackService.find().forEach(function(stackService) {
- if (App.Service.find(stackService.get('serviceName')).get('isLoaded')
- && stackService.get('requiredServices').contains(serviceName)) {
- dependentServices.push(stackService.get('serviceName'));
+ App.Service.find().forEach(function (service) {
+ if (!serviceNamesToDelete.contains(service.get('serviceName'))) {
+ var requiredServices = App.StackService.find(service.get('serviceName')).get('requiredServices');
+ serviceNamesToDelete.forEach(function (dependsOnService) {
+ if (requiredServices.contains(dependsOnService)) {
+ dependentServices.push(service.get('serviceName'));
+ }
+ });
}
}, this);
return dependentServices;
},
/**
+ * @param serviceNames
+ * @returns {string}
+ */
+ servicesDisplayNames: function(serviceNames) {
+ return serviceNames.map(function(serviceName) {
+ return App.format.role(serviceName);
+ }).join(',');
+ },
+
+ /**
+ * Is services can be removed based on work status
+ * @param serviceNames
+ */
+ allowUninstallServices: function(serviceNames) {
+ return !App.Service.find().filter(function (service) {
+ return serviceNames.contains(service.get('serviceName'));
+ }).mapProperty('workStatus').some(function (workStatus) {
+ return !App.Service.allowUninstallStates.contains(workStatus);
+ });
+ },
+
+ /**
* delete service action
* @param {string} serviceName
*/
deleteService: function(serviceName) {
- var dependentServices = this.findDependentServices(serviceName),
- self = this,
- displayName = App.format.role(serviceName),
- popupHeader = Em.I18n.t('services.service.delete.popup.header');
+ var self = this,
+ interDependentServices = this.interDependentServices(serviceName),
+ serviceNamesToDelete = interDependentServices.concat(serviceName),
+ dependentServices = this.findDependentServices(serviceNamesToDelete),
+ displayName = App.format.role(serviceName),
+ popupHeader = Em.I18n.t('services.service.delete.popup.header'),
+ dependentServicesToDeleteFmt = this.servicesDisplayNames(interDependentServices);
if (App.Service.find().get('length') === 1) {
//at least one service should be installed
@@ -1002,21 +1047,26 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
});
} else if (dependentServices.length > 0) {
this.dependentServicesWarning(serviceName, dependentServices);
- } else if (App.Service.allowUninstallStates.contains(App.Service.find(serviceName).get('workStatus'))) {
+ } else if (this.allowUninstallServices(serviceNamesToDelete)) {
App.showConfirmationPopup(
- function() {self.confirmDeleteService(serviceName)},
- Em.I18n.t('services.service.delete.popup.warning').format(displayName),
+ function() {self.confirmDeleteService(serviceName, interDependentServices, dependentServicesToDeleteFmt)},
+ Em.I18n.t('services.service.delete.popup.warning').format(displayName) +
+ (interDependentServices.length ? Em.I18n.t('services.service.delete.popup.warning.dependent').format(dependentServicesToDeleteFmt) : ''),
null,
popupHeader,
Em.I18n.t('common.delete'),
true
);
} else {
+ var body = Em.I18n.t('services.service.delete.popup.mustBeStopped').format(displayName);
+ if (interDependentServices.length) {
+ body += Em.I18n.t('services.service.delete.popup.mustBeStopped.dependent').format(dependentServicesToDeleteFmt)
+ }
App.ModalPopup.show({
secondary: null,
header: popupHeader,
encodeBody: false,
- body: Em.I18n.t('services.service.delete.popup.mustBeStopped').format(displayName)
+ body: body
});
}
},
@@ -1048,9 +1098,13 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
/**
* Confirmation popup of service deletion
* @param {string} serviceName
+ * @param {string[]} [dependentServiceNames]
+ * @param {string} [servicesToDeleteFmt]
*/
- confirmDeleteService: function (serviceName) {
- var message = Em.I18n.t('services.service.confirmDelete.popup.body').format(App.format.role(serviceName)),
+ confirmDeleteService: function (serviceName, dependentServiceNames, servicesToDeleteFmt) {
+ var message = dependentServiceNames && dependentServiceNames.length
+ ? Em.I18n.t('services.service.confirmDelete.popup.body.dependent').format(App.format.role(serviceName), servicesToDeleteFmt)
+ : Em.I18n.t('services.service.confirmDelete.popup.body').format(App.format.role(serviceName)),
confirmKey = 'yes',
self = this;
@@ -1060,7 +1114,7 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
* @function onPrimary
*/
onPrimary: function() {
- self.deleteServiceCall(serviceName);
+ self.deleteServiceCall([serviceName].concat(dependentServiceNames));
this._super();
},
@@ -1105,22 +1159,31 @@ App.MainServiceItemController = Em.Controller.extend(App.SupportClientConfigsDow
/**
* Ajax call to delete service
- * @param {string} serviceName
+ * @param {string[]} serviceNames
* @returns {$.ajax}
*/
- deleteServiceCall: function(serviceName) {
+ deleteServiceCall: function(serviceNames) {
+ var serviceToDeleteNow = serviceNames[0];
+ if (serviceNames.length > 1) {
+ var servicesToDeleteNext = serviceNames.slice(1);
+ }
return App.ajax.send({
- name : 'service.item.delete',
+ name : 'common.delete.service',
sender: this,
data : {
- serviceName : serviceName
+ serviceName : serviceToDeleteNow,
+ servicesToDeleteNext: servicesToDeleteNext
},
success : 'deleteServiceCallSuccessCallback'
});
},
deleteServiceCallSuccessCallback: function(data, ajaxOptions, params) {
- window.location.reload();
+ if (params.servicesToDeleteNext) {
+ this.deleteServiceCall(params.servicesToDeleteNext);
+ } else {
+ window.location.reload();
+ }
}
});
http://git-wip-us.apache.org/repos/asf/ambari/blob/315f502b/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index e341de4..f24876b 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -1693,12 +1693,16 @@ Em.I18n.translations = {
'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.',
+ 'services.service.delete.popup.mustBeStopped.dependent': ' Along with dependent service <b>{0}</b>.',
'services.service.delete.popup.warning': 'The <b>{0} service will be removed from Ambari and all configurations' +
' and configuration history will be lost.</b>',
+ 'services.service.delete.popup.warning.dependent': '<b>Note! {0} will be deleted too.</b>',
'services.service.confirmDelete.popup.header': 'Confirm Delete',
'services.service.confirmDelete.popup.body': 'You must confirm delete of <b>{0}</b> by typing "yes"' +
' in the confirmation box. <b>This operation is not reversible and all configuration history will be lost.</b>',
+ 'services.service.confirmDelete.popup.body.dependent': 'You must confirm delete of <b>{0}</b> and <b>{1}</b> by typing "yes"' +
+ ' in the confirmation box. <b>This operation is not reversible and all configuration history will be lost.</b>',
'services.service.summary.unknown':'unknown',
'services.service.summary.notRunning':'Not Running',
'services.service.summary.notAvailable':'n/a',
http://git-wip-us.apache.org/repos/asf/ambari/blob/315f502b/ambari-web/app/models/stack_service.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/stack_service.js b/ambari-web/app/models/stack_service.js
index a7f5f4e..5814386 100644
--- a/ambari-web/app/models/stack_service.js
+++ b/ambari-web/app/models/stack_service.js
@@ -42,7 +42,7 @@ App.StackService = DS.Model.extend({
stack: DS.belongsTo('App.Stack'),
serviceComponents: DS.hasMany('App.StackServiceComponent'),
configs: DS.attr('array'),
- requiredServices: DS.attr('array'),
+ requiredServices: DS.attr('array', {defaultValue: []}),
/**
* @type {String[]}
http://git-wip-us.apache.org/repos/asf/ambari/blob/315f502b/ambari-web/app/utils/ajax/ajax.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/ajax/ajax.js b/ambari-web/app/utils/ajax/ajax.js
index 423941f..8b89365 100644
--- a/ambari-web/app/utils/ajax/ajax.js
+++ b/ambari-web/app/utils/ajax/ajax.js
@@ -498,11 +498,6 @@ var urls = {
};
}
},
- 'service.item.delete': {
- 'real': '/clusters/{clusterName}/services/{serviceName}',
- 'mock': '',
- 'type': 'DELETE'
- },
'service.item.smoke': {
'real': '/clusters/{clusterName}/requests',
'mock': '/data/wizard/deploy/poll_1.json',
http://git-wip-us.apache.org/repos/asf/ambari/blob/315f502b/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 90a1d90..8fb772a 100644
--- a/ambari-web/test/controllers/main/service/item_test.js
+++ b/ambari-web/test/controllers/main/service/item_test.js
@@ -28,6 +28,28 @@ require('controllers/main/service/reassign_controller');
require('controllers/main/service/item');
var batchUtils = require('utils/batch_scheduled_requests');
var testHelpers = require('test/helpers');
+var stackSerivceModel = {
+ 'HDFS': Em.Object.create({
+ serviceName: 'HDFS',
+ requiredServices: ['ZOOKEEPER']
+ }),
+ 'YARN': Em.Object.create({
+ serviceName: 'YARN',
+ requiredServices: ['MAPREDUCE2', 'HDFS']
+ }),
+ 'MAPREDUCE2': Em.Object.create({
+ serviceName: 'MAPREDUCE2',
+ requiredServices: ['YARN']
+ }),
+ 'TEZ': Em.Object.create({
+ serviceName: 'TEZ',
+ requiredServices: ['YARN']
+ }),
+ 'HIVE': Em.Object.create({
+ serviceName: 'HIVE',
+ requiredServices: ['YARN', 'TEZ']
+ })
+};
describe('App.MainServiceItemController', function () {
@@ -1182,35 +1204,50 @@ describe('App.MainServiceItemController', function () {
beforeEach(function() {
mainServiceItemController = App.MainServiceItemController.create({});
- this.mockStackService = sinon.stub(App.StackService, 'find');
+ sinon.stub(App.StackService, 'find', function (serviceName) {
+ return stackSerivceModel[serviceName];
+ });
this.mockService = sinon.stub(App.Service, 'find');
});
afterEach(function() {
- this.mockStackService.restore();
+ App.StackService.find.restore();
this.mockService.restore();
});
- it("no stack services", function() {
- this.mockStackService.returns([]);
- expect(mainServiceItemController.findDependentServices('S1')).to.be.empty;
+ it("no services", function() {
+ this.mockService.returns([]);
+ expect(mainServiceItemController.findDependentServices(['S1'])).to.be.empty;
});
- it("dependent service not installed", function() {
- this.mockStackService.returns([Em.Object.create({serviceName: 'S2'})]);
- this.mockService.withArgs('S2').returns(Em.Object.create({isLoaded: false}));
- expect(mainServiceItemController.findDependentServices('S1')).to.be.empty;
+ it("service has dependencies", function() {
+ this.mockService.returns([
+ Em.Object.create({ serviceName: 'HDFS' }),
+ Em.Object.create({ serviceName: 'YARN' }),
+ Em.Object.create({ serviceName: 'MAPREDUCE2' }),
+ Em.Object.create({ serviceName: 'TEZ' }),
+ Em.Object.create({ serviceName: 'HIVE' })
+ ]);
+ expect(mainServiceItemController.findDependentServices(['YARN', 'MAPREDUCE2'])).to.eql(['TEZ', 'HIVE']);
});
it("service has no dependencies", function() {
- this.mockStackService.returns([Em.Object.create({serviceName: 'S2', requiredServices: []})]);
- this.mockService.withArgs('S2').returns(Em.Object.create({isLoaded: true}));
- expect(mainServiceItemController.findDependentServices('S1')).to.be.empty;
+ this.mockService.returns([
+ Em.Object.create({ serviceName: 'HDFS' }),
+ Em.Object.create({ serviceName: 'YARN' }),
+ Em.Object.create({ serviceName: 'MAPREDUCE2' }),
+ Em.Object.create({ serviceName: 'TEZ' }),
+ Em.Object.create({ serviceName: 'HIVE' })
+ ]);
+ expect(mainServiceItemController.findDependentServices(['HIVE'])).to.be.empty;
});
- it("service has dependencies", function() {
- this.mockStackService.returns([Em.Object.create({serviceName: 'S2', requiredServices: ['S1']})]);
- this.mockService.withArgs('S2').returns(Em.Object.create({isLoaded: true}));
- expect(mainServiceItemController.findDependentServices('S1')).to.eql(['S2']);
+ it("service has no dependencies (except interdependent)", function() {
+ this.mockService.returns([
+ Em.Object.create({ serviceName: 'HDFS' }),
+ Em.Object.create({ serviceName: 'YARN' }),
+ Em.Object.create({ serviceName: 'MAPREDUCE2' })
+ ]);
+ expect(mainServiceItemController.findDependentServices(['YARN', 'MAPREDUCE2'])).to.be.empty;
});
});
@@ -1222,12 +1259,20 @@ describe('App.MainServiceItemController', function () {
mainServiceItemController = App.MainServiceItemController.create({});
this.mockDependentServices = sinon.stub(mainServiceItemController, 'findDependentServices');
sinon.stub(mainServiceItemController, 'dependentServicesWarning');
+ 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});
});
afterEach(function() {
+ mainServiceItemController.allowUninstallServices.restore();
+ mainServiceItemController.interDependentServices.restore();
+ mainServiceItemController.servicesDisplayNames.restore();
this.mockDependentServices.restore();
this.mockService.restore();
mainServiceItemController.dependentServicesWarning.restore();
@@ -1250,21 +1295,23 @@ describe('App.MainServiceItemController', function () {
it("service has installed dependent services", function() {
this.mockDependentServices.returns(['S2']);
- this.mockService.returns(Em.Object.create({workStatus: App.Service.statesMap.stopped}));
+ this.mockService.returns([Em.Object.create({workStatus: App.Service.statesMap.stopped}), Em.Object.create({workStatus: App.Service.statesMap.stopped})]);
mainServiceItemController.deleteService('S1');
expect(mainServiceItemController.dependentServicesWarning.calledWith('S1', ['S2'])).to.be.true;
});
it("service has not dependent services, and stopped", function() {
this.mockDependentServices.returns([]);
- this.mockService.returns(Em.Object.create({workStatus: App.Service.statesMap.stopped}));
+ this.allowUninstallServices.returns(true);
+ this.mockService.returns([Em.Object.create({workStatus: App.Service.statesMap.stopped}), Em.Object.create({workStatus: App.Service.statesMap.stopped})]);
mainServiceItemController.deleteService('S1');
expect(App.showConfirmationPopup.calledOnce).to.be.true;
});
it("service has not dependent services, and install failed", function() {
this.mockDependentServices.returns([]);
- this.mockService.returns(Em.Object.create({workStatus: App.Service.statesMap.install_failed}));
+ this.allowUninstallServices.returns(true);
+ this.mockService.returns([Em.Object.create({workStatus: App.Service.statesMap.install_failed}), Em.Object.create({workStatus: App.Service.statesMap.install_failed})]);
mainServiceItemController.deleteService('S1');
expect(App.showConfirmationPopup.calledOnce).to.be.true;
});
@@ -1318,6 +1365,26 @@ describe('App.MainServiceItemController', function () {
});
});
+ describe('#interDependentServices', function() {
+ var mainServiceItemController;
+
+ beforeEach(function() {
+ sinon.stub(App.StackService, 'find', function (serviceName) {
+ return stackSerivceModel[serviceName];
+ });
+ mainServiceItemController = App.MainServiceItemController.create({});
+ });
+
+ 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']);
+ });
+ });
+
describe("#deleteServiceCall()", function() {
var mainServiceItemController;
@@ -1326,12 +1393,13 @@ describe('App.MainServiceItemController', function () {
});
it("App.ajax.send should be called", function() {
- mainServiceItemController.deleteServiceCall('S1');
- var args = testHelpers.findAjaxRequest('name', 'service.item.delete');
+ mainServiceItemController.deleteServiceCall(['S1', 'S2']);
+ var args = testHelpers.findAjaxRequest('name', 'common.delete.service');
expect(args[0]).exists;
expect(args[0].sender).to.be.eql(mainServiceItemController);
expect(args[0].data).to.be.eql({
- serviceName : 'S1'
+ serviceName : 'S1',
+ servicesToDeleteNext: ['S2']
});
});
});
@@ -1342,15 +1410,23 @@ describe('App.MainServiceItemController', function () {
beforeEach(function() {
mainServiceItemController = App.MainServiceItemController.create({});
sinon.stub(window.location, 'reload');
+ sinon.spy(mainServiceItemController, 'deleteServiceCall');
});
afterEach(function() {
window.location.reload.restore();
});
it("window.location.reload should be called", function() {
- mainServiceItemController.deleteServiceCallSuccessCallback();
+ mainServiceItemController.deleteServiceCallSuccessCallback([], null, {});
+ expect(mainServiceItemController.deleteServiceCall.called).to.be.false;
expect(window.location.reload.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;
+ });
});
});