Преглед изворни кода

AMBARI-8621. Kerberos Wizard: Implement and Integrate "Configure Identities" page.

Jaimin Jetly пре 10 година
родитељ
комит
a665a3cc13

+ 189 - 0
ambari-web/app/assets/data/wizard/kerberos/stack_descriptors.json

@@ -0,0 +1,189 @@
+{
+  "properties": {
+    "realm": "${cluster-env/kerberos_domain}",
+    "keytab_dir": "/etc/security/keytabs"
+  },
+  "identities": [
+    {
+      "name": "spnego",
+      "principal": {
+        "value": "HTTP/_HOST@${realm}"
+      },
+      "keytab": {
+        "file": "${keytab_dir}/spnego.service.keytab",
+        "owner": {
+          "name": "root",
+          "access": "r"
+        },
+        "group": {
+          "name": "${cluster-env/user_group}",
+          "access": "r"
+        }
+      }
+    }
+  ],
+  "configurations": [
+    {
+      "core-site": {
+        "hadoop.security.authentication": "kerberos",
+        "hadoop.rpc.protection": "authentication; integrity; privacy",
+        "hadoop.security.authorization": "true"
+      }
+    }
+  ],
+  "services": [
+    {
+      "name": "HDFS",
+      "components": [
+        {
+          "name": "NAMENODE",
+          "identities": [
+            {
+              "name": "namenode_nn",
+              "principal": {
+                "value": "nn/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.kerberos.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/nn.service.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.keytab.file"
+              }
+            },
+            {
+              "name": "namenode_host",
+              "principal": {
+                "value": "host/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.kerberos.https.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/host.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.keytab.file"
+              }
+            },
+            {
+              "name": "/spnego",
+              "principal": {
+                "configuration": "hdfs-site/dfs.web.authentication.kerberos.principal"
+              },
+              "keytab": {
+                "configuration": "hdfs/dfs.web.authentication.kerberos.keytab"
+              }
+            }
+          ]
+        },
+        {
+          "name": "DATANODE",
+          "identities": [
+            {
+              "name": "datanode_dn",
+              "principal": {
+                "value": "dn/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.kerberos.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/dn.service.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.keytab.file"
+              }
+            },
+            {
+              "name": "datanode_host",
+              "principal": {
+                "value": "host/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.datanode.kerberos.https.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/host.keytab.file",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.secondary.keytab.file"
+              }
+            }
+          ]
+        },
+        {
+          "name": "SECONDARY_NAMENODE",
+          "identities": [
+            {
+              "name": "secondary_namenode_nn",
+              "principal": {
+                "value": "nn/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.secondary.kerberos.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/snn.service.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.secondary.keytab.file"
+              }
+            },
+            {
+              "name": "secondary_namenode_host",
+              "principal": {
+                "value": "host/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.secondary.kerberos.https.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/host.keytab.file",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.secondary.keytab.file"
+              }
+            },
+            {
+              "name": "/spnego",
+              "principal": {
+                "configuration": "hdfs-site/dfs.web.authentication.kerberos.principal"
+              },
+              "keytab": {
+                "configuration": "hdfs/dfs.web.authentication.kerberos.keytab"
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
+

+ 1 - 0
ambari-web/app/assets/test/tests.js

@@ -128,6 +128,7 @@ var files = ['test/init_model_test',
   'test/mixins/common/localStorage_test',
   'test/mixins/common/table_server_view_mixin_test',
   'test/mixins/main/host/details/host_components/decommissionable_test',
+  'test/mixins/wizard/addSeccurityConfigs_test',
   'test/mixins/wizard/wizard_menu_view_test',
   'test/mixins/wizard/wizardProgressPageController_test',
   'test/utils/ajax/ajax_test',

+ 94 - 3
ambari-web/app/controllers/main/admin/kerberos/step4_controller.js

@@ -16,7 +16,98 @@
  * limitations under the License.
  */
 
-App.KerberosWizardStep4Controller = Em.Controller.extend({
-  name: 'kerberosWizardStep4Controller'
-});
+App.KerberosWizardStep4Controller = Em.Controller.extend(App.AddSecurityConfigs, {
+  name: 'kerberosWizardStep4Controller',
+  stepConfigs: [],
+  selectedService: null,
+  isRecommendedLoaded: false,
+  
+  clearStep: function() {
+    this.set('isRecommendedLoaded', false);
+    this.set('selectedService', null);
+    this.set('stepConfigs', []);
+  },
+  
+  loadStep: function() {
+    var self = this;
+    this.clearStep();
+    this.getStackDescriptorConfigs().then(function(properties) {
+      self.setStepConfigs(properties);
+      self.set('isRecommendedLoaded', true);
+    });
+  },
+
+  /**
+   * Create service config object for Kerberos service.
+   *
+   * @param {Em.Object[]} configCategories
+   * @param {App.ServiceConfigProperty[]} configs
+   * @returns {Em.Object} 
+   */
+  createServiceConfig: function(configCategories, configs) {
+    return Em.Object.create({
+      displayName: 'Kerberos Descriptor',
+      name: 'KERBEROS',
+      serviceName: 'KERBEROS',
+      configCategories: configCategories,
+      configs: configs,
+      showConfig: true,
+      selected: true
+    });
+  },
+
+  /**
+   * Prepare step configs using stack descriptor properties.
+   * 
+   * @param {App.ServiceConfigProperty[]} configs
+   */
+  setStepConfigs: function(configs) {
+    var selectedService = App.StackService.find().findProperty('serviceName', 'KERBEROS');
+    var configCategories = selectedService.get('configCategories');
+    this.prepareConfigProperties(configs);
+    this.get('stepConfigs').pushObject(this.createServiceConfig(configCategories, configs));
+    this.set('selectedService', this.get('stepConfigs')[0]);
+  },
 
+  /**
+   * 
+   * @param {} configs
+   */
+  prepareConfigProperties: function(configs) {
+    var self = this;
+    var realmValue = this.get('wizardController').getDBProperty('serviceConfigProperties').findProperty('name', 'realm').value;
+    configs.findProperty('name', 'realm').set('value', realmValue);
+    configs.findProperty('name', 'realm').set('defaultValue', realmValue);
+    
+    configs.setEach('isSecureConfig', false);
+    configs.forEach(function(property, item, allConfigs) {
+      if (['spnego_keytab', 'spnego_principal'].contains(property.get('name'))) {
+        property.addObserver('value', self, 'spnegoPropertiesObserver');
+      }
+      if (property.get('observesValueFrom')) {
+        var observedValue = allConfigs.findProperty('name', property.get('observesValueFrom')).get('value');
+        property.set('value', observedValue);
+        property.set('defaultValue', observedValue);
+      }
+      if (property.get('serviceName') == 'Cluster') property.set('category', 'General');
+      else property.set('category', 'Advanced');
+    });
+  },
+
+  spnegoPropertiesObserver: function(configProperty) {
+    var self = this;
+    this.get('stepConfigs')[0].get('configs').forEach(function(config) {
+      if (config.get('observesValueFrom') == configProperty.get('name')) {
+        Em.run.once(self, function() {
+          config.set('value', configProperty.get('value'));
+          config.set('defaultValue', configProperty.get('value'));
+        });
+      }
+    });
+  },
+
+  submit: function() {
+    App.router.send('next');
+  }
+  
+});

+ 134 - 1
ambari-web/app/mixins/wizard/addSecurityConfigs.js

@@ -105,7 +105,7 @@ App.AddSecurityConfigs = Em.Mixin.create({
       configs.push({
         name: 'nimbus_principal_name',
         serviceName: 'STORM'
-      })
+      });
     }
     return configs;
   }.property('App.isHadoop22Stack'),
@@ -381,5 +381,138 @@ App.AddSecurityConfigs = Em.Mixin.create({
       return true;
     }
     return false;
+  },
+
+  /**
+   * Generate stack descriptor configs.
+   *
+   * @returns {$.Deferred} 
+   */
+  getStackDescriptorConfigs: function() {
+    return this.loadStackDescriptorConfigs().pipe(this.createServicesStackDescriptorConfigs.bind(this));
+  },
+
+  /**
+   * 
+   * @param {object[]} items - stack descriptor json response
+   * @returns {App.ServiceConfigProperty[]} 
+   */
+  createServicesStackDescriptorConfigs: function(items) {
+    var self = this;
+    var configs = [];
+    var clusterConfigs = [];
+    
+    // generate configs for root level properties object, currently realm, keytab_dir
+    clusterConfigs = clusterConfigs.concat(this.expandKerberosStackDescriptorProps(items.properties));
+    // generate configs for root level identities object, currently spnego property
+    clusterConfigs = clusterConfigs.concat(this.createConfigsByIdentities(items.identities, 'Cluster'));
+    clusterConfigs.setEach('serviceName', 'Cluster');
+    // generate properties for services object
+    items.services.forEach(function(service) {
+      var serviceName = service.name;
+      service.components.forEach(function(component) {
+        var componentName = component.name;
+        var identityConfigs = self.createConfigsByIdentities(component.identities, componentName);
+        identityConfigs.setEach('serviceName', serviceName);
+        configs = configs.concat(identityConfigs);
+      });
+    });    
+    // unite cluster and service configs
+    configs = configs.concat(clusterConfigs);
+    // return configs with uniq names
+    return configs.reduce(function(p,c) {
+      if (!p.findProperty('name', c.get('name'))) p.push(c);
+      return p;
+    }, []);
+  },
+
+  /**
+   * Create service properties based on component identity
+   *
+   * @param {object[]} identities
+   * @param {string} componentName
+   * @returns {App.ServiceConfigProperty[]} 
+   */
+  createConfigsByIdentities: function(identities, componentName) {
+    var self = this;
+    var configs = [];
+    
+    identities.forEach(function(identity) {
+      var defaultObject = {
+        isOverridable: false,
+        isVisible: true,
+        isSecureConfig: true,
+        componentName: componentName,
+        name: identity.name
+      };
+      if (identity.name == '/spnego') {
+        defaultObject.isEditable = false;
+      }
+      self.parseIdentityObject(identity).forEach(function(item) {
+        configs.push(App.ServiceConfigProperty.create($.extend({}, defaultObject, item)));
+      });
+    });
+
+    return configs;
+  },
+
+  /**
+   * Bootstrap base object according to identity info. Generate objects will be converted to
+   * App.ServiceConfigProperty model class instances.
+   *
+   * @param {object} identity
+   * @returns {object[]} 
+   */
+  parseIdentityObject: function(identity) {
+    var result = [];
+    var keys = Em.keys(identity);
+    var name = identity[keys.shift()];
+    keys.forEach(function(item) {
+      var configObject = {};
+      var prop = identity[item];
+      if (name == '/spnego') configObject.observesValueFrom = 'spnego_' + item;
+      configObject.defaultValue = configObject.value = item == 'principal' ? prop.value : prop.file;
+      configObject.filename = prop.configuration ? prop.configuration.split('/')[0] : 'cluster-env';
+      configObject.name = configObject.displayName = prop.configuration ? prop.configuration.split('/')[1] : name + '_' + item;
+      result.push(configObject);
+    });
+    return result;
+  },
+
+  /**
+   * Wrap kerberos properties to App.ServiceConfigProperty model class instances.
+   * 
+   * @param {object} kerberosProperties
+   * @returns {App.ServiceConfigProperty[]} 
+   */
+  expandKerberosStackDescriptorProps: function(kerberosProperties) {
+    var configs = [];
+    
+    for (var propertyName in kerberosProperties) {
+      var propertyObject = {
+        name: propertyName,
+        value: kerberosProperties[propertyName],
+        defaultValue: kerberosProperties[propertyName],
+        serviceName: 'Cluster',
+        displayName: propertyName,
+        isOverridable: false,
+        isEditable: propertyName != 'realm',
+        isSecureConfig: true
+      };
+      configs.push(App.ServiceConfigProperty.create(propertyObject));
+    }
+
+    return configs;
+  },
+  /**
+   * Make request for stack descriptor configs.
+   *
+   * @returns {$.ajax} 
+   */
+  loadStackDescriptorConfigs: function() {
+    return App.ajax.send({
+      sender: this,
+      name: 'admin.kerberize.stack_descriptor'
+    });
   }
 });

+ 2 - 1
ambari-web/app/models/stack_service.js

@@ -337,7 +337,8 @@ App.StackService.configCategories = function () {
       break;
     case 'KERBEROS':
       serviceConfigCategories.pushObjects([
-        App.ServiceConfigCategory.create({ name: 'KDC', displayName: 'KDC and Kadmin'})
+        App.ServiceConfigCategory.create({ name: 'KDC', displayName: 'KDC and Kadmin'}),
+        App.ServiceConfigCategory.create({ name: 'General', displayName: 'General'})
       ]);
       break;
     case 'PIG':

+ 10 - 7
ambari-web/app/routes/add_kerberos_routes.js

@@ -92,7 +92,7 @@ module.exports = App.WizardRoute.extend({
       controller.dataLoading().done(function () {
         controller.loadAllPriorSteps();
         controller.connectOutlet('kerberosWizardStep1', controller.get('content'));
-      })
+      });
     },
 
     unroutePath: function () {
@@ -123,7 +123,7 @@ module.exports = App.WizardRoute.extend({
         var kerberosWizardStep2Controller = router.get('kerberosWizardStep2Controller');
         kerberosWizardStep2Controller.set('wizardController', controller);
         controller.connectOutlet('kerberosWizardStep2', controller.get('content'));
-      })
+      });
     },
     unroutePath: function () {
       return false;
@@ -149,7 +149,7 @@ module.exports = App.WizardRoute.extend({
       controller.dataLoading().done(function () {
         controller.loadAllPriorSteps();
         controller.connectOutlet('kerberosWizardStep3', controller.get('content'));
-      })
+      });
     },
     unroutePath: function () {
       return false;
@@ -170,10 +170,13 @@ module.exports = App.WizardRoute.extend({
     connectOutlets: function (router) {
       console.log('in kerberosWizardController.step4:connectOutlets');
       var controller = router.get('kerberosWizardController');
+      var step4Controller = router.get('kerberosWizardStep4Controller');
       controller.dataLoading().done(function () {
         controller.loadAllPriorSteps();
+        controller.setCurrentStep(4);
+        step4Controller.set('wizardController', controller);
         controller.connectOutlet('kerberosWizardStep4', controller.get('content'));
-      })
+      });
     },
     unroutePath: function () {
       return false;
@@ -196,7 +199,7 @@ module.exports = App.WizardRoute.extend({
       controller.dataLoading().done(function () {
         controller.loadAllPriorSteps();
         controller.connectOutlet('kerberosWizardStep5', controller.get('content'));
-      })
+      });
     },
     unroutePath: function () {
       return false;
@@ -220,7 +223,7 @@ module.exports = App.WizardRoute.extend({
       controller.dataLoading().done(function () {
         controller.loadAllPriorSteps();
         controller.connectOutlet('kerberosWizardStep6', controller.get('content'));
-      })
+      });
     },
     unroutePath: function () {
       return false;
@@ -240,4 +243,4 @@ module.exports = App.WizardRoute.extend({
     }
   })
 
-});
+});

+ 13 - 4
ambari-web/app/templates/main/admin/kerberos/step4.hbs

@@ -15,7 +15,16 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 }}
-<h2>{{t admin.kerberos.wizard.step4.header}}</h2>
-<p class="alert alert-info">
-  {{t admin.kerberos.wizard.step4.info.body}}
-</p>
+<div id="serviceConfig">
+  <h2>{{t admin.kerberos.wizard.step4.header}}</h2>
+  <p class="alert alert-info">
+    {{t admin.kerberos.wizard.step4.info.body}}
+  </p>
+
+  {{view App.ServicesConfigView}}
+
+  <div class="btn-area">
+    <a id="submit-kerberos-step4" class="btn btn-success pull-right" {{bindAttr disabled="isSubmitDisabled"}}
+       {{action submit target="controller"}}>{{t common.next}} &rarr;</a>
+  </div>
+</div>

+ 4 - 0
ambari-web/app/utils/ajax/ajax.js

@@ -1297,6 +1297,10 @@ var urls = {
       }
     }
   },
+  'admin.kerberize.stack_descriptor': {
+    'real': '',
+    'mock': '/data/wizard/kerberos/stack_descriptors.json'
+  },
   'admin.poll.kerberize.cluster.request': {
     'real': '/clusters/{clusterName}/requests/{requestId}?fields=stages/Stage/context,stages/Stage/status,stages/Stage/progress_percent,stages/tasks/*,Requests/*',
     'mock': '/data/wizard/kerberos/kerberize_cluster.json'

+ 169 - 15
ambari-web/test/mixins/wizard/addSeccurityConfigs_test.js

@@ -17,15 +17,16 @@
  */
 
 var App = require('app');
+var stackDescriptorData = require('test/mock_data_setup/stack_descriptors');
 
 require('mixins/wizard/addSecurityConfigs');
 
 describe('App.AddSecurityConfigs', function () {
 
-  var controller = App.AddSecurityConfigs.create({
+  var controller = Em.Object.extend(App.AddSecurityConfigs,{}).create({
     content: {},
     enableSubmit: function () {
-      this._super()
+      this._super();
     },
     secureMapping: [],
     secureProperties: []
@@ -37,7 +38,7 @@ describe('App.AddSecurityConfigs', function () {
       expect(controller.get('secureServices')).to.eql([{}]);
       controller.reopen({
         secureServices: []
-      })
+      });
     });
   });
 
@@ -49,11 +50,13 @@ describe('App.AddSecurityConfigs', function () {
       });
       sinon.stub(controller, 'setConfigValue', Em.K);
       sinon.stub(controller, 'formatConfigName', Em.K);
+      sinon.stub(App.Service, 'find').returns([{serviceName: 'SOME_SERVICE'}]);
     });
     afterEach(function(){
       controller.checkServiceForConfigValue.restore();
       controller.setConfigValue.restore();
       controller.formatConfigName.restore();
+      App.Service.find.restore();
     });
 
     it('secureMapping is empty', function() {
@@ -66,6 +69,7 @@ describe('App.AddSecurityConfigs', function () {
         name: 'config1',
         value: 'value1',
         filename: 'file1',
+        serviceName: 'SOME_SERVICE',
         foreignKey: null
       }]);
 
@@ -82,7 +86,8 @@ describe('App.AddSecurityConfigs', function () {
         value: 'value1',
         filename: 'file1',
         foreignKey: null,
-        dependedServiceName: 'service'
+        serviceName: 'SOME_SERVICE',
+        dependedServiceName: 'SOME_SERVICE'
       }]);
 
       expect(controller.loadUiSideSecureConfigs()).to.eql([{
@@ -98,14 +103,10 @@ describe('App.AddSecurityConfigs', function () {
         value: 'value1',
         filename: 'file1',
         foreignKey: true,
-        serviceName: 'service'
+        serviceName: 'NO_SERVICE'
       }]);
-      sinon.stub(App.Service, 'find', function(){
-        return [];
-      });
 
       expect(controller.loadUiSideSecureConfigs()).to.be.empty;
-      App.Service.find.restore();
     });
     it('Config has correct serviceName', function() {
       controller.set('secureMapping', [{
@@ -113,11 +114,8 @@ describe('App.AddSecurityConfigs', function () {
         value: 'value1',
         filename: 'file1',
         foreignKey: true,
-        serviceName: 'HDFS'
+        serviceName: 'SOME_SERVICE'
       }]);
-      sinon.stub(App.Service, 'find', function(){
-        return [{serviceName: 'HDFS'}];
-      });
 
       expect(controller.loadUiSideSecureConfigs()).to.eql([{
         "id": "site property",
@@ -127,7 +125,6 @@ describe('App.AddSecurityConfigs', function () {
       }]);
       expect(controller.setConfigValue.calledOnce).to.be.true;
       expect(controller.formatConfigName.calledOnce).to.be.true;
-      App.Service.find.restore();
     });
   });
 
@@ -216,6 +213,7 @@ describe('App.AddSecurityConfigs', function () {
         templateName: ['config1']
       };
       controller.set('globalProperties', []);
+      controller.set('configs', []);
 
       expect(controller.setConfigValue(config)).to.be.true;
       expect(config.value).to.be.null;
@@ -332,7 +330,163 @@ describe('App.AddSecurityConfigs', function () {
     });
   });
 
-});
+  describe('#createServicesStackDescriptorConfigs', function() {
+    var result = controller.createServicesStackDescriptorConfigs(stackDescriptorData);
+    var propertyValidationTests = [
+      {
+        property: 'spnego_keytab',
+        e: [
+          { key: 'value', value: '${keytab_dir}/spnego.service.keytab' },
+          { key: 'serviceName', value: 'Cluster' },
+        ]
+      }
+    ];
+
+    it('resulted array should have unique properties by name', function() {
+      expect(result.mapProperty('name').length).to.be.eql(result.mapProperty('name').uniq().length);
+    });
+    
+    propertyValidationTests.forEach(function(test) {
+      it('property {0} should be created'.format(test.property), function() {
+        expect(result.findProperty('name', test.property)).to.be.ok;
+      });
+      test.e.forEach(function(expected) {
+        it('property `{0}` should have `{1}` with value `{2}`'.format(test.property, expected.key, expected.value), function() {
+          expect(result.findProperty('name', test.property)).to.have.deep.property(expected.key, expected.value);
+        });
+      });
+    });
+  });
 
+  describe('#expandKerberosStackDescriptorProps', function() {
+    var result = controller.expandKerberosStackDescriptorProps(stackDescriptorData.properties);
+    var testCases = [
+      {
+        property: 'realm',
+        e: [
+          { key: 'isEditable', value: false },
+          { key: 'serviceName', value: 'Cluster' },
+        ]
+      },
+      {
+        property: 'keytab_dir',
+        e: [
+          { key: 'isEditable', value: true },
+          { key: 'serviceName', value: 'Cluster' },
+        ]
+      }
+    ];
+    testCases.forEach(function(test) {
+      it('property {0} should be created'.format(test.property), function() {
+        expect(result.findProperty('name', test.property)).to.be.ok;
+      });
+      test.e.forEach(function(expected) {
+        it('property `{0}` should have `{1}` with value `{2}`'.format(test.property, expected.key, expected.value), function() {
+          expect(result.findProperty('name', test.property)).to.have.deep.property(expected.key, expected.value);
+        });
+      });
+    });
+  });
 
+  describe('#createConfigsByIdentity', function() {
+    var identitiesData = stackDescriptorData.services[0].components[0].identities;
+    var tests = [
+      {
+        property: 'dfs.namenode.kerberos.principal',
+        e: [
+          { key: 'value', value: 'nn/_HOST@${realm}' },
+        ]
+      },
+      {
+        property: 'dfs.web.authentication.kerberos.principal',
+        e: [
+          { key: 'observesValueFrom', value: 'spnego_principal' },
+          { key: 'isEditable', value: false }
+        ]
+      }     
+    ];
+    var properties = controller.createConfigsByIdentities(identitiesData, 'NAMENODE');
+    tests.forEach(function(test) {
+      it('property {0} should be created'.format(test.property), function() {
+        expect(properties.findProperty('name', test.property)).to.be.ok;
+      });
+      test.e.forEach(function(expected) {
+        it('property `{0}` should have `{1}` with value `{2}`'.format(test.property, expected.key, expected.value), function() {
+          expect(properties.findProperty('name', test.property)).to.have.deep.property(expected.key, expected.value);
+        });
+      });
+    });
+  });
 
+  describe('#parseIdentityObject', function() {
+    var testCases = [
+      {
+        identity: stackDescriptorData.services[0].components[0].identities[0],
+        tests: [
+          {
+            property: 'dfs.namenode.kerberos.principal',
+            e: [
+              { key: 'filename', value: 'hdfs-site' },
+            ]
+          },
+          {
+            property: 'dfs.namenode.keytab.file',
+            e: [
+              { key: 'value', value: '${keytab_dir}/nn.service.keytab' }
+            ]
+          }
+        ],
+      },
+      {
+        identity: stackDescriptorData.services[0].components[0].identities[1],
+        tests: [
+          {
+            property: 'dfs.namenode.kerberos.https.principal',
+            e: [
+              { key: 'filename', value: 'hdfs-site' }
+            ]
+          }
+        ],
+      },
+      {
+        identity: stackDescriptorData.identities[0],
+        tests: [
+          {
+            property: 'spnego_principal',
+            e: [
+              { key: 'displayName', value: 'spnego_principal' },
+              { key: 'filename', value: 'cluster-env' },
+            ]
+          }
+        ]
+      },
+      {
+        identity: stackDescriptorData.identities[0],
+        tests: [
+          {
+            property: 'spnego_keytab',
+            e: [
+              { key: 'displayName', value: 'spnego_keytab' },
+              { key: 'filename', value: 'cluster-env' },
+            ]
+          }
+        ]
+      }
+    ];
+    
+    testCases.forEach(function(testCase) {
+      testCase.tests.forEach(function(test) {
+        var result = controller.parseIdentityObject(testCase.identity);
+        it('property `{0}` should be present'.format(test.property), function() {
+          expect(result.findProperty('name', test.property)).to.be.ok;
+        });
+        test.e.forEach(function(expected) {
+          it('property `{0}` should have `{1}` with value `{2}`'.format(test.property, expected.key, expected.value), function() {
+            expect(result.findProperty('name', test.property)).to.have.deep.property(expected.key, expected.value);
+          });
+        });
+      });
+    });
+  });
+  
+});

+ 206 - 0
ambari-web/test/mock_data_setup/stack_descriptors.js

@@ -0,0 +1,206 @@
+/**
+ * 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.
+ */
+
+module.exports = {
+  "properties": {
+    "realm": "${cluster-env/kerberos_domain}",
+    "keytab_dir": "/etc/security/keytabs"
+  },
+  "identities": [
+    {
+      "name": "spnego",
+      "principal": {
+        "value": "HTTP/_HOST@${realm}"
+      },
+      "keytab": {
+        "file": "${keytab_dir}/spnego.service.keytab",
+        "owner": {
+          "name": "root",
+          "access": "r"
+        },
+        "group": {
+          "name": "${cluster-env/user_group}",
+          "access": "r"
+        }
+      }
+    }
+  ],
+  "configurations": [
+    {
+      "core-site": {
+        "hadoop.security.authentication": "kerberos",
+        "hadoop.rpc.protection": "authentication; integrity; privacy",
+        "hadoop.security.authorization": "true"
+      }
+    }
+  ],
+  "services": [
+    {
+      "name": "HDFS",
+      "components": [
+        {
+          "name": "NAMENODE",
+          "identities": [
+            {
+              "name": "namenode_nn",
+              "principal": {
+                "value": "nn/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.kerberos.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/nn.service.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.keytab.file"
+              }
+            },
+            {
+              "name": "namenode_host",
+              "principal": {
+                "value": "host/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.kerberos.https.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/host.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.keytab.file"
+              }
+            },
+            {
+              "name": "/spnego",
+              "principal": {
+                "configuration": "hdfs-site/dfs.web.authentication.kerberos.principal"
+              },
+              "keytab": {
+                "configuration": "hdfs/dfs.web.authentication.kerberos.keytab"
+              }
+            }
+          ]
+        },
+        {
+          "name": "DATANODE",
+          "identities": [
+            {
+              "name": "datanode_dn",
+              "principal": {
+                "value": "dn/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.kerberos.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/dn.service.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.keytab.file"
+              }
+            },
+            {
+              "name": "datanode_host",
+              "principal": {
+                "value": "host/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.datanode.kerberos.https.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/host.keytab.file",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.secondary.keytab.file"
+              }
+            }
+          ]
+        },
+        {
+          "name": "SECONDARY_NAMENODE",
+          "identities": [
+            {
+              "name": "secondary_namenode_nn",
+              "principal": {
+                "value": "nn/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.secondary.kerberos.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/snn.service.keytab",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.secondary.keytab.file"
+              }
+            },
+            {
+              "name": "secondary_namenode_host",
+              "principal": {
+                "value": "host/_HOST@${realm}",
+                "configuration": "hdfs-site/dfs.namenode.secondary.kerberos.https.principal"
+              },
+              "keytab": {
+                "file": "${keytab_dir}/host.keytab.file",
+                "owner": {
+                  "name": "${hadoop-env/hdfs_user}",
+                  "access": "r"
+                },
+                "group": {
+                  "name": "${cluster-env/user_group}",
+                  "access": ""
+                },
+                "configuration": "hdfs-site/dfs.namenode.secondary.keytab.file"
+              }
+            },
+            {
+              "name": "/spnego",
+              "principal": {
+                "configuration": "hdfs-site/dfs.web.authentication.kerberos.principal"
+              },
+              "keytab": {
+                "configuration": "hdfs/dfs.web.authentication.kerberos.keytab"
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+};