You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2015/10/07 17:07:52 UTC
ambari git commit: AMBARI-13340. Kerberos: Enhance UI to set KDC
admin credentials
Repository: ambari
Updated Branches:
refs/heads/trunk 15ac28364 -> 78e49db0a
AMBARI-13340. Kerberos: Enhance UI to set KDC admin credentials
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/78e49db0
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/78e49db0
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/78e49db0
Branch: refs/heads/trunk
Commit: 78e49db0a1ba569a8e580c4ef182246475e0693f
Parents: 15ac283
Author: Alex Antonenko <hi...@gmail.com>
Authored: Wed Oct 7 18:03:46 2015 +0300
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Wed Oct 7 18:03:46 2015 +0300
----------------------------------------------------------------------
ambari-web/app/assets/test/tests.js | 1 +
ambari-web/app/config.js | 3 +-
.../main/admin/kerberos/step2_controller.js | 14 +-
.../main/admin/kerberos/wizard_controller.js | 16 +-
ambari-web/app/messages.js | 3 +
ambari-web/app/mixins.js | 1 +
.../common/kdc_credentials_controller_mixin.js | 150 +++++++++++++
.../configs/objects/service_config_property.js | 15 ++
ambari-web/app/styles/application.less | 10 +
ambari-web/app/styles/common.less | 10 +
.../common/configs/service_config_category.hbs | 28 ++-
ambari-web/app/utils/ajax/ajax.js | 47 ++++
ambari-web/app/utils/credentials.js | 225 +++++++++++++++++++
ambari-web/app/views/common/controls_view.js | 18 +-
.../kdc_credentials_controller_mixin_test.js | 195 ++++++++++++++++
15 files changed, 721 insertions(+), 15 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index b3b0e2c..06f1d2e 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -143,6 +143,7 @@ var files = [
'test/mixins/common/widgets/export_metrics_mixin_test',
'test/mixins/common/widgets/time_range_mixin_test',
'test/mixins/common/widgets/widget_section_test',
+ 'test/mixins/common/kdc_credentials_controller_mixin_test',
'test/mixins/common/localStorage_test',
'test/mixins/common/reload_popup_test',
'test/mixins/common/serverValidator_test',
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/config.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/config.js b/ambari-web/app/config.js
index d33239a..0a53fdf 100644
--- a/ambari-web/app/config.js
+++ b/ambari-web/app/config.js
@@ -75,7 +75,8 @@ App.supports = {
customizedWidgetLayout: false,
enhancedConfigs: true,
showPageLoadTime: false,
- skipComponentStartAfterInstall: false
+ skipComponentStartAfterInstall: false,
+ storeKDCCredentials: false
};
if (App.enableExperimental) {
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/controllers/main/admin/kerberos/step2_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/kerberos/step2_controller.js b/ambari-web/app/controllers/main/admin/kerberos/step2_controller.js
index f87fb77..77a27e5 100644
--- a/ambari-web/app/controllers/main/admin/kerberos/step2_controller.js
+++ b/ambari-web/app/controllers/main/admin/kerberos/step2_controller.js
@@ -19,7 +19,7 @@
var App = require('app');
require('controllers/wizard/step7_controller');
-App.KerberosWizardStep2Controller = App.WizardStep7Controller.extend({
+App.KerberosWizardStep2Controller = App.WizardStep7Controller.extend(App.KDCCredentialsControllerMixin, {
name: "kerberosWizardStep2Controller",
isKerberosWizard: true,
@@ -36,6 +36,10 @@ App.KerberosWizardStep2Controller = App.WizardStep7Controller.extend({
addMiscTabToPage: false,
+ isStorePersisted: function() {
+ return this.get('wizardController.content.secureStoragePersisted');
+ }.property('wizardController.content.secureStoragePersisted'),
+
/**
* @type {boolean} true if test connection to hosts is in progress
*/
@@ -93,6 +97,9 @@ App.KerberosWizardStep2Controller = App.WizardStep7Controller.extend({
App.config.setPreDefinedServiceConfigs(this.get('addMiscTabToPage'));
this.filterConfigs(this.get('configs'));
+ if (App.get('supports.storeKDCCredentials') && !this.get('wizardController.skipClientInstall')) {
+ this.initilizeKDCStoreProperties(this.get('configs'));
+ }
this.applyServicesConfigs(this.get('configs'), storedConfigs);
},
@@ -136,8 +143,11 @@ App.KerberosWizardStep2Controller = App.WizardStep7Controller.extend({
if (this.get('isSubmitDisabled')) return false;
this.set('isSubmitDisabled', true);
var self = this;
- this.deleteKerberosService().always(function (data) {
+ this.deleteKerberosService().always(function () {
self.configureKerberos();
+ if (App.get('supports.storeKDCCredentials') && !self.get('wizardController.skipClientInstall')) {
+ self.createKDCCredentials(self.get('stepConfigs.0.configs'));
+ }
});
},
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js b/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
index 6fb0f8b..6209f29 100644
--- a/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
+++ b/ambari-web/app/controllers/main/admin/kerberos/wizard_controller.js
@@ -18,6 +18,7 @@
var App = require('app');
+var credentialsUtils = require('utils/credentials');
App.KerberosWizardController = App.WizardController.extend(App.InstallComponent, {
@@ -63,7 +64,8 @@ App.KerberosWizardController = App.WizardController.extend(App.InstallComponent,
services: [],
advancedServiceConfig: null,
serviceConfigProperties: [],
- failedTask: null
+ failedTask: null,
+ secureStoragePersisted: null
}),
/**
@@ -255,6 +257,18 @@ App.KerberosWizardController = App.WizardController.extend(App.InstallComponent,
}, this);
}
}
+ },
+ {
+ type: 'async',
+ callback: function() {
+ var self = this;
+ var dfd = $.Deferred();
+ credentialsUtils.isStorePersisted(App.get('clusterName')).then(function(isPersisted) {
+ self.set('content.secureStoragePersisted', isPersisted);
+ dfd.resolve();
+ });
+ return dfd.promise();
+ }
}
],
'3': [
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index ab9a874..80902ce 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -1021,6 +1021,9 @@ Em.I18n.translations = {
'admin.authentication.form.test.success':'The configuration passes the test',
'admin.authentication.form.test.fail':'The configuration fails the test',
+
+ 'admin.kerberos.credentials.store.hint.supported': 'When checked, Ambari will store the KDC Admin credentials so they are not required to be re-entered during future changes of services, hosts, and components.',
+ 'admin.kerberos.credentials.store.hint.not.supported': 'Ambari is not configured for storing credentials',
'admin.kerberos.wizard.configuration.note': 'This is the initial configuration created by Enable Kerberos wizard.',
'admin.kerberos.wizard.header':'Enable Kerberos Wizard',
'admin.kerberos.button.enable': 'Enable Kerberos',
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/mixins.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js
index 4c9da09..276e777 100644
--- a/ambari-web/app/mixins.js
+++ b/ambari-web/app/mixins.js
@@ -20,6 +20,7 @@
// load all mixins here
require('mixins/common/blueprint');
+require('mixins/common/kdc_credentials_controller_mixin');
require('mixins/common/localStorage');
require('mixins/common/userPref');
require('mixins/common/reload_popup');
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js b/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js
new file mode 100644
index 0000000..7be3056
--- /dev/null
+++ b/ambari-web/app/mixins/common/kdc_credentials_controller_mixin.js
@@ -0,0 +1,150 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+var credentialsUtils = require('utils/credentials');
+
+App.KDCCredentialsControllerMixin = Em.Mixin.create({
+
+ /**
+ * Alias name used to store KDC credentials
+ *
+ * @type {string}
+ */
+ credentialAlias: 'kdc.admin.credential',
+
+ /**
+ * Returns <code>true</code> if persisted secure storage available.
+ * Should be implemented as computed property.
+ *
+ * @type {boolean}
+ */
+ isStorePersisted: function() {
+ Em.assert("Should be implemented", false);
+ }.property(),
+
+ /**
+ * List of required UI-only properties needed for storing KDC credentials
+ *
+ * @type {object[]}
+ */
+ credentialsStoreConfigs: [
+ {
+ name: 'persist_credentials',
+ displayType: 'checkbox',
+ value: 'false',
+ recommendedValue: 'false',
+ supportsFinal: false,
+ recommendedIsFinal: false,
+ displayName: 'Save Admin credentials',
+ category: 'Kadmin',
+ isRequired: false,
+ isRequiredByAgent: false,
+ hintMessage: false,
+ rightSideLabel: true,
+ isEditable: true,
+ index: 3
+ }
+ ],
+
+ /**
+ * @param {object} resource resource info to set e.g.
+ * <code>
+ * {
+ * principal: "USERNAME",
+ * key: "SecretKey",
+ * type: "persisted"
+ * }
+ * </code>
+ *
+ * Where:
+ * <ul>
+ * <li>principal: the principal (or username) part of the credential to store</li>
+ * <li>key: the secret key part of the credential to store</li>
+ * <li>type: declares the storage facility type: "persisted" or "temporary"</li>
+ * </ul>
+ * @returns {$.Deferred} promise object
+ */
+ createKDCCredentials: function(configs) {
+ var self = this;
+ var resource = {
+ type: this._getStorageTypeValue(configs),
+ key: configs.findProperty('name', 'admin_password').get('value'),
+ principal: configs.findProperty('name', 'admin_principal').get('value')
+ };
+ return credentialsUtils.createCredentials(App.get('clusterName'), this.get('credentialAlias'), resource).fail(function() {
+ return self.updateKDCCredentials(resource);
+ });
+ },
+
+ /**
+ * Remove KDC credentials
+ *
+ * @returns {$.Deferred} promise object
+ */
+ removeKDCCredentials: function() {
+ return credentialsUtils.removeCredentials(App.get('clusterName'), this.get('credentialAlias'));
+ },
+
+ /**
+ * @see createKDCCredentials
+ * @param {object} resource
+ * @returns {$.Deferred} promise object
+ */
+ updateKDCCredentials: function(resource) {
+ return credentialsUtils.updateCredentials(App.get('clusterName'), this.get('credentialAlias'), resource);
+ },
+
+ /**
+ * Generate additional properties regarding KDC credential storage
+ *
+ * @param {App.ServiceConfigProperty[]} configs list of configs
+ */
+ initilizeKDCStoreProperties: function(configs) {
+ var self = this;
+ this.get('credentialsStoreConfigs').forEach(function(item) {
+ var configObject = App.config.createDefaultConfig(item.name, 'KERBEROS', 'krb5-conf.xml', false, false);
+ $.extend(configObject, item);
+ if (item.name === 'persist_credentials') {
+ if (self.get('isStorePersisted')) {
+ configObject.hintMessage = Em.I18n.t('admin.kerberos.credentials.store.hint.supported');
+ } else {
+ configObject.hintMessage = Em.I18n.t('admin.kerberos.credentials.store.hint.not.supported');
+ configObject.isEditable = false;
+ }
+ }
+ configs.pushObject(configObject);
+ });
+ },
+
+ /**
+ * Return storage type e.g. <b>temporary</b>, <b>persisted</b>
+ *
+ * @param {App.ServiceConfigProperty[]} configs configs array from step configs
+ * @returns {string} storage type value
+ */
+ _getStorageTypeValue: function(configs) {
+ if (this.get('isStorePersisted')) {
+ return configs.findProperty('name', 'persist_credentials').get('value') === "true" ?
+ credentialsUtils.STORE_TYPES.PERSISTENT :
+ credentialsUtils.STORE_TYPES.TEMPORARY;
+ }
+ return credentialsUtils.STORE_TYPES.TEMPORARY;
+ }
+
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/models/configs/objects/service_config_property.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/objects/service_config_property.js b/ambari-web/app/models/configs/objects/service_config_property.js
index 01b4eb2..100de5d 100644
--- a/ambari-web/app/models/configs/objects/service_config_property.js
+++ b/ambari-web/app/models/configs/objects/service_config_property.js
@@ -77,6 +77,21 @@ App.ServiceConfigProperty = Em.Object.extend({
*/
supportsFinal: false,
+ /**
+ * Hint message to display in tooltip. Tooltip will be wrapped on question mark icon.
+ * If value is <code>false</code> no tooltip and question mark icon.
+ *
+ * @type {boolean|string}
+ */
+ hintMessage: false,
+
+ /**
+ * Display label on the right side from input. In general used for checkbox only.
+ *
+ * @type {boolean}
+ */
+ rightSideLabel: false,
+
retypedPassword: '',
description: '',
displayType: 'string', // string, digits, number, directories, custom
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index fcc4863..d08ca34 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -6036,3 +6036,13 @@ input[type="radio"].align-checkbox, input[type="checkbox"].align-checkbox {
margin-bottom: 0;
}
}
+
+[class^="icon-"],
+[class*="icon-"] {
+ &.icon-blue {
+ color: @blue;
+ }
+ &:hover {
+ text-decoration: none;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/styles/common.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/common.less b/ambari-web/app/styles/common.less
index 6559c06..b4cb61d 100644
--- a/ambari-web/app/styles/common.less
+++ b/ambari-web/app/styles/common.less
@@ -355,4 +355,14 @@
min-width: 60px;
font-size: 14px;
cursor: default;
+}
+
+.bootstrap-checkbox {
+ &>button.btn {
+ &:focus {
+ border-color: none;
+ box-shadow: 0;
+ outline: 0 none;
+ }
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/templates/common/configs/service_config_category.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/configs/service_config_category.hbs b/ambari-web/app/templates/common/configs/service_config_category.hbs
index 8cb65ae..6cd1203 100644
--- a/ambari-web/app/templates/common/configs/service_config_category.hbs
+++ b/ambari-web/app/templates/common/configs/service_config_category.hbs
@@ -33,20 +33,30 @@
{{#unless widget}} {{! configs with widgets should be shown only on the EnhancedConfigs tabs }}
<div {{bindAttr class=":entry-row isHiddenByFilter:hide isOverridden:overridden-property hasCompareDiffs:overridden-property"}}>
{{#if showLabel}}
- <span {{bindAttr class="errorMessage:error: :control-group :control-label-span"}}>
- <label class="control-label">
- {{formatWordBreak displayName}}
- {{#if isSecureConfig}}
- <a href="javascript:void(null);"><i class="icon-lock" rel="tooltip" data-toggle="tooltip"
- title="security knob"></i></a>
- {{/if}}
- </label>
- </span>
+ {{#unless rightSideLabel}}
+ <span {{bindAttr class="errorMessage:error: :control-group :control-label-span"}}>
+ <label class="control-label">
+ {{formatWordBreak displayName}}
+ {{#if isSecureConfig}}
+ <a href="javascript:void(null);"><i class="icon-lock" rel="tooltip" data-toggle="tooltip"
+ title="security knob"></i></a>
+ {{/if}}
+ </label>
+ </span>
+ {{else}}
+ <span class="control-group control-label-span"> </span>
+ {{/unless}}
{{/if}}
<div {{bindAttr class="showLabel:controls"}}>
{{! Here serviceConfigBinding should ideally be serviceConfigPropertyBinding }}
<div {{bindAttr class="errorMessage:error: warnMessage:warning: :control-group"}}>
{{view viewClass serviceConfigBinding="this" categoryConfigsAllBinding="view.categoryConfigsAll" }}
+ {{#if rightSideLabel}}
+ <span {{bindAttr class="isEditable::muted"}}>{{formatWordBreak displayName}}</span>
+ {{/if}}
+ {{#if hintMessage}}
+ <a class="icon-question-sign icon-blue" href="#" data-toggle="tooltip" {{bindAttr data-original-title="hintMessage"}}><a/>
+ {{/if}}
{{#if this.isComparison}}
{{#if controller.selectedConfigGroup.isDefault}}
<span
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/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 f10eff1..9e5e8d2 100644
--- a/ambari-web/app/utils/ajax/ajax.js
+++ b/ambari-web/app/utils/ajax/ajax.js
@@ -791,6 +791,53 @@ var urls = {
'mock': '/data/configuration/cluster_env_site.json'
},
+ 'credentials.store.info': {
+ 'real': '/clusters/{clusterName}?fields=Clusters/credential_store_properties',
+ 'mock': ''
+ },
+
+ 'credentials.list': {
+ 'real': '/clusters/{clusterName}/credentials',
+ 'mock': ''
+ },
+
+ 'credentials.get': {
+ 'real': '/clusters/{clusterName}/credentials/{alias}',
+ 'mock': ''
+ },
+
+ 'credentials.create': {
+ 'real': '/clusters/{clusterName}/credentials/{alias}',
+ 'mock': '',
+ type: 'POST',
+ 'format': function(data) {
+ return {
+ data: JSON.stringify({
+ Credential: data.resource
+ })
+ };
+ }
+ },
+
+ 'credentials.update': {
+ 'real': '/clusters/{clusterName}/credentials/{alias}',
+ 'mock': '',
+ 'type': 'PUT',
+ 'format': function(data) {
+ return {
+ data: JSON.stringify({
+ Credential: data.resource
+ })
+ };
+ }
+ },
+
+ 'credentials.delete': {
+ 'real': '/clusters/{clusterName}/credentials/{alias}',
+ 'mock': '',
+ 'type':'DELETE'
+ },
+
'host.host_component.add_new_component': {
'real': '/clusters/{clusterName}/hosts?Hosts/host_name={hostName}',
'mock': '/data/wizard/deploy/poll_1.json',
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/utils/credentials.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/credentials.js b/ambari-web/app/utils/credentials.js
new file mode 100644
index 0000000..7a567e9
--- /dev/null
+++ b/ambari-web/app/utils/credentials.js
@@ -0,0 +1,225 @@
+/**
+ * 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.
+ */
+
+var App = require('app');
+
+/** @module utils.credentials **/
+module.exports = {
+
+ STORE_TYPES: {
+ TEMPORARY: 'temporary',
+ PERSISTENT: 'persisted',
+ PERSISTENT_KEY: 'persistent',
+ TEMPORARY_KEY: 'temporary',
+ PERSISTENT_PATH: 'storage.persistent',
+ TEMPORARY_PATH: 'storage.temporary'
+ },
+
+ /**
+ * Store credentials to server
+ *
+ * @member utils.credentials
+ * @param {string} clusterName cluster name
+ * @param {string} alias credential alias name e.g. "kdc.admin.credentials"
+ * @param {object} resource resource info to set e.g.
+ * <code>
+ * {
+ * principal: "USERNAME",
+ * key: "SecretKey",
+ * type: "persisted"
+ * }
+ * </code>
+ *
+ * Where:
+ * <ul>
+ * <li>principal: the principal (or username) part of the credential to store</li>
+ * <li>key: the secret key part of the credential to store</li>
+ * <li>type: declares the storage facility type: "persisted" or "temporary"</li>
+ * </ul>
+ * @returns {$.Deferred} promise object
+ */
+ createCredentials: function(clusterName, alias, resource) {
+ return App.ajax.send({
+ sender: this,
+ name: 'credentials.create',
+ data: {
+ clusterName: clusterName,
+ resource: resource,
+ alias: alias
+ },
+ error: 'createCredentialsErrorCallback'
+ });
+ },
+
+ createCredentialsErrorCallback: function(req, ajaxOpts, error) {
+ console.error('createCredentials ERROR:', error);
+ },
+
+ /**
+ * Retrieve single credential from cluster by specified alias name
+ *
+ * @member utils.credentials
+ * @param {string} clusterName cluster name
+ * @param {string} alias credential alias name e.g. "kdc.admin.credentials"
+ * @returns {$.Deferred} promise object
+ */
+ getCredential: function(clusterName, alias, callback) {
+ return App.ajax.send({
+ sender: this,
+ name: 'credentials.get',
+ data: {
+ clusterName: clusterName,
+ alias: alias,
+ callback: callback
+ },
+ success: 'getCredentialSuccessCallback'
+ });
+ },
+
+ getCredentialSuccessCallback: function(data, opt, params) {
+ params.callback(Em.getWithDefault(data, 'Credential', null));
+ },
+
+ /**
+ * Update credential by alias and cluster name
+ *
+ * @see createCredentials
+ * @param {string} clusterName
+ * @param {string} alias
+ * @param {object} resource
+ * @returns {$.Deferred} promise object
+ */
+ updateCredentials: function(clusterName, alias, resource) {
+ return App.ajax.send({
+ sender: this,
+ name: 'credentials.update',
+ data: {
+ clusterName: clusterName,
+ alias: alias,
+ resource: resource
+ }
+ });
+ },
+
+ /**
+ * Get credenial list from server by specified cluster name
+ *
+ * @param {string} clusterName cluster name
+ * @param {function} callback
+ * @returns {$.Deferred} promise object
+ */
+ credentials: function(clusterName, callback) {
+ return App.ajax.send({
+ sender: this,
+ name: 'credentials.list',
+ data: {
+ clusterName: clusterName
+ },
+ success: 'credentialsSuccessCallback'
+ });
+ },
+
+ credentialsSuccessCallback: function(data, opt, params) {
+ params.callback(data.items.length ? data.items.mapProperty('Credential') : []);
+ },
+
+ /**
+ * Remove credential from server by specified cluster name and alias
+ *
+ * @param {string} clusterName cluster name
+ * @param {string} alias credential alias name e.g. "kdc.admin.credentials"
+ */
+ removeCredentials: function(clusterName, alias) {
+ return App.ajax.send({
+ sender: this,
+ name: 'credentials.delete',
+ data: {
+ clusterName: clusterName,
+ alias: alias
+ }
+ });
+ },
+
+ /**
+ * Get info regarding credential storage type like <code>persistent</code> and <code>temporary</code>
+ *
+ * @param {string} clusterName cluster name
+ * @param {function} callback
+ * @returns {$.Deferred} promise object
+ */
+ storageInfo: function(clusterName, callback) {
+ return App.ajax.send({
+ sender: this,
+ name: 'credentials.store.info',
+ data: {
+ clusterName: clusterName,
+ callback: callback
+ },
+ success: 'storageInfoSuccessCallback'
+ });
+ },
+
+ storageInfoSuccessCallback: function(json, opt, params, request) {
+ if (json.Clusters) {
+ var storage = Em.getWithDefault(json, 'Clusters.credential_store_properties', {});
+ var storeTypesObject = {};
+
+ storeTypesObject[this.STORE_TYPES.PERSISTENT_KEY] = storage[this.STORE_TYPES.PERSISTENT_PATH] === "true";
+ storeTypesObject[this.STORE_TYPES.TEMPORARY_KEY] = storage[this.STORE_TYPES.TEMPORARY_PATH] === "true";
+ params.callback(storeTypesObject);
+ } else {
+ params.callback(null);
+ }
+ },
+
+ /**
+ * Resolves promise with <code>true</code> value if secure store is persistent
+ *
+ * @param {string} clusterName
+ * @returns {$.Deferred} promise object
+ */
+ isStorePersisted: function(clusterName) {
+ return this.storeTypeStatus(clusterName, this.STORE_TYPES.PERSISTENT_KEY);
+ },
+
+ /**
+ * Resolves promise with <code>true</code> value if secure store is temporary
+ *
+ * @param {string} clusterName
+ * @returns {$.Deferred} promise object
+ */
+ isStoreTemporary: function(clusterName) {
+ return this.storeTypeStatus(clusterName, this.STORE_TYPES.TEMPORARY_KEY);
+ },
+
+ /**
+ * Get store type value for specified cluster and store type e.g. <b>persistent</b> or <b>temporary</b>
+ *
+ * @param {string} clusterName
+ * @param {string} type store type e.g. <b>persistent</b> or <b>temporary</b>
+ * @returns {$.Deferred} promise object
+ */
+ storeTypeStatus: function(clusterName, type) {
+ var dfd = $.Deferred();
+ this.storageInfo(clusterName, function(storage) {
+ dfd.resolve(Em.get(storage, type));
+ }).fail(function(error) {
+ dfd.reject(error);
+ });
+ return dfd.promise();
+ }
+};
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/app/views/common/controls_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/controls_view.js b/ambari-web/app/views/common/controls_view.js
index 085ae11..578e975 100644
--- a/ambari-web/app/views/common/controls_view.js
+++ b/ambari-web/app/views/common/controls_view.js
@@ -328,6 +328,7 @@ App.ServiceConfigCheckbox = Ember.Checkbox.extend(App.ServiceConfigPopoverSuppor
* and what value is negative (unchecked) proeprty
*/
didInsertElement: function() {
+ var self = this;
this._super();
this.addObserver('serviceConfig.value', this, 'toggleChecker');
Object.keys(this.get('allowedPairs')).forEach(function(key) {
@@ -336,7 +337,17 @@ App.ServiceConfigCheckbox = Ember.Checkbox.extend(App.ServiceConfigPopoverSuppor
this.set('falseValue', this.get('allowedPairs')[key][1]);
}
}, this);
- this.set('checked', this.get('serviceConfig.value') === this.get('trueValue'))
+ this.set('checked', this.get('serviceConfig.value') === this.get('trueValue'));
+ this.propertyDidChange('checked');
+ Em.run.next(function () {
+ if (self.$())
+ self.$().checkbox({
+ defaultState: self.get('serviceConfig.value'),
+ buttonStyle: 'btn-link btn-large',
+ checkedClass: 'icon-check',
+ uncheckedClass: 'icon-check-empty'
+ });
+ });
},
willDestroyElement: function() {
@@ -366,8 +377,11 @@ App.ServiceConfigCheckbox = Ember.Checkbox.extend(App.ServiceConfigPopoverSuppor
* change checkbox value if click on undo
*/
toggleChecker: function() {
- if (this.isNotAppropriateValue())
+ if (this.isNotAppropriateValue()) {
this.set('checked', !this.get('checked'));
+ // change bootstrap-checkbox state
+ this.$().change();
+ }
},
disabled: function () {
http://git-wip-us.apache.org/repos/asf/ambari/blob/78e49db0/ambari-web/test/mixins/common/kdc_credentials_controller_mixin_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/kdc_credentials_controller_mixin_test.js b/ambari-web/test/mixins/common/kdc_credentials_controller_mixin_test.js
new file mode 100644
index 0000000..a584979
--- /dev/null
+++ b/ambari-web/test/mixins/common/kdc_credentials_controller_mixin_test.js
@@ -0,0 +1,195 @@
+/**
+ * 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.
+ */
+
+require('mixins/common/kdc_credentials_controller_mixin');
+
+var App = require('app');
+var credentialsUtils = require('utils/credentials');
+
+var mixedObject;
+
+describe('App.KDCCredentialsControllerMixin', function() {
+
+ beforeEach(function() {
+ mixedObject = Em.Object.create(App.KDCCredentialsControllerMixin);
+ });
+
+ afterEach(function() {
+ mixedObject.destroy();
+ });
+
+ describe('#isStorePersisted', function() {
+ it('should throw error if not overrided in mixed object', function() {
+ var errorThrown = false;
+ try {
+ mixedObject.get('isStorePersisted');
+ } catch (e) {
+ errorThrown = true;
+ } finally {
+ expect(errorThrown).to.be.true;
+ }
+ });
+ it('should not throw error if overrided in mixed object', function() {
+ var errorThrown = false;
+ mixedObject.reopen({
+ isStorePersisted: function() {
+ return true;
+ }.property()
+ });
+ try {
+ mixedObject.get('isStorePersisted');
+ } catch (e) {
+ errorThrown = true;
+ } finally {
+ expect(errorThrown).to.be.false;
+ }
+ });
+ });
+
+ describe('#initilizeKDCStoreProperties', function() {
+ [
+ {
+ isStorePersisted: true,
+ e: {
+ isEditable: true,
+ hintMessage: Em.I18n.t('admin.kerberos.credentials.store.hint.supported')
+ },
+ message: 'Persistent store available, config should be editable, and appropriate hint shown'
+ },
+ {
+ isStorePersisted: false,
+ e: {
+ isEditable: false,
+ hintMessage: Em.I18n.t('admin.kerberos.credentials.store.hint.not.supported')
+ },
+ message: 'Only temporary store available, config should be disabled, and appropriate hint shown'
+ }
+ ].forEach(function(test) {
+ it(test.message, function() {
+ var configs = [],
+ config;
+ mixedObject.reopen({
+ isStorePersisted: function() {
+ return test.isStorePersisted;
+ }.property()
+ });
+ mixedObject.initilizeKDCStoreProperties(configs);
+ config = configs.findProperty('name', 'persist_credentials');
+ Em.keys(test.e).forEach(function(key) {
+ assert.equal(Em.get(config, key), test.e[key], 'validate attribute: ' + key);
+ });
+ });
+ });
+ });
+
+ describe('#createKDCCredentials', function() {
+ var createConfig = function(name, value) {
+ return App.ServiceConfigProperty.create({
+ name: name,
+ value: value
+ });
+ };
+ [
+ {
+ configs: [
+ createConfig('admin_password', 'admin'),
+ createConfig('admin_principal', 'admin/admin'),
+ createConfig('persist_credentials', 'true')
+ ],
+ e: [
+ 'testName',
+ 'kdc.admin.credential',
+ {
+ type: 'persisted',
+ key: 'admin',
+ principal: 'admin/admin'
+ }
+ ],
+ message: 'Save Admin credentials checkbox checked, credentials should be saved as `persisted`'
+ },
+ {
+ configs: [
+ createConfig('admin_password', 'admin'),
+ createConfig('admin_principal', 'admin/admin'),
+ createConfig('persist_credentials', 'false')
+ ],
+ e: [
+ 'testName',
+ 'kdc.admin.credential',
+ {
+ type: 'temporary',
+ key: 'admin',
+ principal: 'admin/admin'
+ }
+ ],
+ message: 'Save Admin credentials checkbox un-checked, credentials should be saved as `temporary`'
+ },
+ {
+ configs: [
+ createConfig('admin_password', 'admin'),
+ createConfig('admin_principal', 'admin/admin'),
+ createConfig('persist_credentials', 'false')
+ ],
+ e: [
+ 'testName',
+ 'kdc.admin.credential',
+ {
+ type: 'temporary',
+ key: 'admin',
+ principal: 'admin/admin'
+ }
+ ],
+ credentialWasSaved: true,
+ message: 'Save Admin credentials checkbox checked, credential was saved, credentials should be saved as `temporary`, #updateKDCCredentials should be called'
+ }
+ ].forEach(function(test) {
+ it(test.message, function() {
+ sinon.stub(App, 'get').withArgs('clusterName').returns('testName');
+ sinon.stub(credentialsUtils, 'createCredentials', function() {
+ if (test.credentialWasSaved) {
+ return $.Deferred().reject().promise();
+ } else {
+ return $.Deferred().resolve().promise();
+ }
+ });
+ if (test.credentialWasSaved) {
+ sinon.stub(credentialsUtils, 'updateCredentials', function() {
+ return $.Deferred().resolve().promise();
+ });
+ }
+
+ mixedObject.reopen({
+ isStorePersisted: function() {
+ return true;
+ }.property()
+ });
+ mixedObject.createKDCCredentials(test.configs);
+ assert.isTrue(credentialsUtils.createCredentials.calledOnce, 'credentialsUtils#createCredentials called');
+ assert.deepEqual(credentialsUtils.createCredentials.args[0], test.e, 'credentialsUtils#createCredentials called with correct arguments');
+ credentialsUtils.createCredentials.restore();
+ if (test.credentialWasSaved) {
+ assert.isTrue(credentialsUtils.updateCredentials.calledOnce, 'credentialUtils#updateCredentials called');
+ assert.deepEqual(credentialsUtils.updateCredentials.args[0], test.e, 'credentialUtils#updateCredentials called with correct arguments');
+ credentialsUtils.updateCredentials.restore();
+ }
+ App.get.restore();
+ });
+ });
+ });
+
+});