Pārlūkot izejas kodu

AMBARI-7815. UI - prevent user from deleting host with not "DECOMMISSIONED" datanode/nodemanager. (akovalenko)

Aleksandr Kovalenko 10 gadi atpakaļ
vecāks
revīzija
290a813bb0

+ 4 - 3
ambari-web/app/controllers/global/update_controller.js

@@ -153,8 +153,8 @@ App.UpdateController = Em.Controller.extend({
       hostDetailsFilter = '';
     var realUrl = '/hosts?<parameters>fields=Hosts/host_name,Hosts/maintenance_state,Hosts/public_host_name,Hosts/cpu_count,Hosts/ph_cpu_count,' +
       'Hosts/host_status,Hosts/last_heartbeat_time,Hosts/ip,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,' +
-      'host_components/HostRoles/stale_configs,host_components/HostRoles/service_name,metrics/disk,metrics/load/load_one,Hosts/total_mem,' +
-      'legacy_alerts/summary<hostAuxiliaryInfo>&minimal_response=true';
+      'host_components/HostRoles/stale_configs,host_components/HostRoles/service_name,host_components/HostRoles/desired_admin_state,' +
+        'metrics/disk,metrics/load/load_one,Hosts/total_mem,legacy_alerts/summary<hostAuxiliaryInfo>&minimal_response=true';
     var hostAuxiliaryInfo = ',Hosts/os_arch,Hosts/os_type,metrics/cpu/cpu_system,metrics/cpu/cpu_user,metrics/memory/mem_total,metrics/memory/mem_free';
 
     if (App.router.get('currentState.name') == 'index' && App.router.get('currentState.parentState.name') == 'hosts') {
@@ -359,6 +359,7 @@ App.UpdateController = Em.Controller.extend({
         'host_components/HostRoles/maintenance_state,' +
         'host_components/HostRoles/stale_configs,' +
         'host_components/HostRoles/ha_state,' +
+        'host_components/HostRoles/desired_admin_state,' +
         'host_components/metrics/jvm/memHeapUsedM,' +
         'host_components/metrics/jvm/HeapMemoryMax,' +
         'host_components/metrics/jvm/HeapMemoryUsed,' +
@@ -430,7 +431,7 @@ App.UpdateController = Em.Controller.extend({
   },
   updateComponentConfig: function (callback) {
     var testUrl = '/data/services/host_component_stale_configs.json';
-    var componentConfigUrl = this.getUrl(testUrl, '/components?ServiceComponentInfo/category.in(SLAVE,CLIENT)&host_components/HostRoles/stale_configs=true&fields=host_components/HostRoles/service_name,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,host_components/HostRoles/host_name,host_components/HostRoles/stale_configs&minimal_response=true');
+    var componentConfigUrl = this.getUrl(testUrl, '/components?ServiceComponentInfo/category.in(SLAVE,CLIENT)&host_components/HostRoles/stale_configs=true&fields=host_components/HostRoles/service_name,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,host_components/HostRoles/host_name,host_components/HostRoles/stale_configs,host_components/HostRoles/desired_admin_state&minimal_response=true');
     App.HttpClient.get(componentConfigUrl, App.componentConfigMapper, {
       complete: callback
     });

+ 8 - 3
ambari-web/app/controllers/main/host/details.js

@@ -1456,6 +1456,7 @@ App.MainHostDetailsController = Em.Controller.extend({
       lastComponents: [],
       masterComponents: [],
       runningComponents: [],
+      notDecommissionedComponents: [],
       nonDeletableComponents: [],
       unknownComponents: []
     };
@@ -1481,6 +1482,9 @@ App.MainHostDetailsController = Em.Controller.extend({
         if (workStatus === App.HostComponentStatus.unknown) {
           container.unknownComponents.push(cInstance.get('displayName'));
         }
+        if (cInstance.get('adminState') === 'INSERVICE') {
+          container.notDecommissionedComponents.push(cInstance.get('displayName'));
+        }
       });
     }
     return container;
@@ -1502,7 +1506,7 @@ App.MainHostDetailsController = Em.Controller.extend({
     } else if (container.nonDeletableComponents.length > 0) {
       this.raiseDeleteComponentsError(container.nonDeletableComponents, 'nonDeletableList');
       return;
-    } else if (container.runningComponents.length > 0) {
+    } else if (container.runningComponents.length + container.notDecommissionedComponents.length > 0) {
       this.raiseDeleteComponentsError(container.runningComponents, 'runningList');
       return;
     }
@@ -1518,7 +1522,7 @@ App.MainHostDetailsController = Em.Controller.extend({
 
   /**
    * Show popup with info about reasons why host can't be deleted
-   * @param {string[]} components
+   * @param {Array} components
    * @param {string} type
    * @method raiseDeleteComponentsError
    */
@@ -1534,7 +1538,8 @@ App.MainHostDetailsController = Em.Controller.extend({
         return this.get('components').join(", ");
       }.property(),
       componentsBody: function () {
-        return Em.I18n.t('hosts.cant.do.popup.' + type + '.body').format(this.get('components').length);
+        var componentsLength = this.get('components.length');
+        return componentsLength ? Em.I18n.t('hosts.cant.do.popup.' + type + '.body').format(this.get('components').length) : '';
       }.property(),
       componentsBodyEnd: function () {
         if (this.get('showBodyEnd')) {

+ 2 - 1
ambari-web/app/mappers/component_config_mapper.js

@@ -29,7 +29,8 @@ App.componentConfigMapper = App.QuickDataMapper.create({
     $display_name_advanced: '',
     stale_configs: 'HostRoles.stale_configs',
     host_id: 'HostRoles.host_name',
-    service_id: 'HostRoles.service_name'
+    service_id: 'HostRoles.service_name',
+    admin_state: 'HostRoles.desired_admin_state'
   },
   map: function (json) {
     console.time('App.componentConfigMapper execution time');

+ 2 - 1
ambari-web/app/mappers/hosts_mapper.js

@@ -61,7 +61,8 @@ App.hostsMapper = App.QuickDataMapper.create({
     passive_state: 'HostRoles.maintenance_state',
     work_status: 'HostRoles.state',
     stale_configs: 'HostRoles.stale_configs',
-    host_name: 'host_name'
+    host_name: 'host_name',
+    admin_state: 'HostRoles.desired_admin_state'
   },
   map: function (json, returnMapped) {
     returnMapped = !!returnMapped;

+ 2 - 1
ambari-web/app/mappers/service_metrics_mapper.js

@@ -173,7 +173,8 @@ App.serviceMetricsMapper = App.QuickDataMapper.create({
     stale_configs: 'HostRoles.stale_configs',
     ha_status: 'HostRoles.ha_state',
     display_name_advanced: 'display_name_advanced',
-    $service_id: 'none' /* will be set outside of parse function */
+    $service_id: 'none', /* will be set outside of parse function */
+    admin_state: 'HostRoles.desired_admin_state'
   },
 
   map: function (json) {

+ 1 - 2
ambari-web/app/messages.js

@@ -1778,8 +1778,7 @@ Em.I18n.translations = {
   'hosts.cant.do.popup.masterList.body.end':'To delete this host, you must first move all the master components listed above to another host.',
   'hosts.cant.do.popup.nonDeletableList.body':'Deletion of the following {0} components is not supported. ',
   'hosts.cant.do.popup.runningList.body':'This host cannot be deleted since the following components are running:',
-  'hosts.cant.do.popup.runningList.body.end':'To delete this host, you must first stop all the running components listed above. ' +
-    'If this host has a DataNode, it should be decommissioned first to prevent data loss.',
+  'hosts.cant.do.popup.runningList.body.end': 'If this host has a DataNode or NodeManager, it should be decommissioned first to prevent data loss.',
   'hosts.add.header':'Add Host Wizard',
   'hosts.add.exit.header':'Exit',
   'hosts.add.exit.body':'Do you really want to exit Add Host Wizard?',

+ 1 - 0
ambari-web/app/models/host_component.js

@@ -28,6 +28,7 @@ App.HostComponent = DS.Model.extend({
   host: DS.belongsTo('App.Host'),
   hostName: DS.attr('string'),
   service: DS.belongsTo('App.Service'),
+  adminState: DS.attr('string'),
   /**
    * Determine if component is client
    * @returns {bool}

+ 6 - 4
ambari-web/app/templates/main/host/details/raiseDeleteComponentErrorPopup.hbs

@@ -20,11 +20,13 @@
   <div class="warning">
   <i class="icon-warning-sign"></i> <strong>{{componentsBody}}</strong>
   </div>
-  <div class="row-fluid">
-    <div class="tinyoffset span10 warning-list">
-    {{componentsStr}}
+  {{#if components.length}}
+    <div class="row-fluid">
+      <div class="tinyoffset span10 warning-list">
+        {{componentsStr}}
+      </div>
     </div>
-  </div>
+  {{/if}}
 
 {{#if showBodyEnd}}
   <div class="warning-details">{{{componentsBodyEnd}}}</div>

+ 37 - 1
ambari-web/test/controllers/main/host/details_test.js

@@ -1133,6 +1133,7 @@ describe('App.MainHostDetailsController', function () {
       lastComponents: [],
       masterComponents: [],
       runningComponents: [],
+      notDecommissionedComponents: [],
       nonDeletableComponents: [],
       unknownComponents: []
     };
@@ -1204,6 +1205,38 @@ describe('App.MainHostDetailsController', function () {
       expect(controller.getHostComponentsInfo().runningComponents).to.eql(['ZK1']);
       App.HostComponent.find.restore();
     });
+    it('content.hostComponents has notDecommissioned running component', function () {
+      sinon.stub(App.HostComponent, 'find', function() {
+        return [{
+          id: 'DATANODE_host1',
+          componentName: 'DATANODE'
+        }];
+      });
+      controller.set('content', {hostComponents: [Em.Object.create({
+        componentName: 'DATANODE',
+        workStatus: 'STARTED',
+        displayName: 'DataNode',
+        adminState: 'INSERVICE'
+      })]});
+      expect(controller.getHostComponentsInfo().notDecommissionedComponents).to.eql(['DataNode']);
+      App.HostComponent.find.restore();
+    });
+    it('content.hostComponents has notDecommissioned running component', function () {
+      sinon.stub(App.HostComponent, 'find', function() {
+        return [{
+          id: 'DATANODE_host1',
+          componentName: 'DATANODE'
+        }];
+      });
+      controller.set('content', {hostComponents: [Em.Object.create({
+        componentName: 'DATANODE',
+        workStatus: 'INSTALLED',
+        displayName: 'DataNode',
+        adminState: 'INSERVICE'
+      })]});
+      expect(controller.getHostComponentsInfo().notDecommissionedComponents).to.eql(['DataNode']);
+      App.HostComponent.find.restore();
+    });
     it('content.hostComponents has non-deletable component', function () {
       sinon.stub(App.HostComponent, 'find', function() {
         return [{
@@ -1277,7 +1310,8 @@ describe('App.MainHostDetailsController', function () {
       controller.set('mockHostComponentsInfo', {
         masterComponents: [],
         nonDeletableComponents: [],
-        runningComponents: [{}]
+        runningComponents: [{}],
+        notDecommissionedComponents: []
       });
       controller.validateAndDeleteHost();
       expect(controller.raiseDeleteComponentsError.calledWith([{}], 'runningList')).to.be.true;
@@ -1287,6 +1321,7 @@ describe('App.MainHostDetailsController', function () {
         masterComponents: [],
         nonDeletableComponents: [],
         runningComponents: [],
+        notDecommissionedComponents: [],
         unknownComponents: [],
         lastComponents: [],
         zkServerInstalled: true
@@ -1301,6 +1336,7 @@ describe('App.MainHostDetailsController', function () {
         masterComponents: [],
         nonDeletableComponents: [],
         runningComponents: [],
+        notDecommissionedComponents: [],
         unknownComponents: [],
         lastComponents: [],
         zkServerInstalled: false