summaryrefslogtreecommitdiff
path: root/horizon
diff options
context:
space:
mode:
authorTravis Tripp <travis.tripp@hp.com>2015-04-02 23:49:29 -0600
committerTravis Tripp <travis.tripp@hp.com>2015-04-09 21:00:47 -0600
commit3a59bec6a71aca166a9f23c714529fd9836d9e23 (patch)
tree50c9ebd275a97635d2ed54a0cd7e3f883fe26d61 /horizon
parentd7abcbfeec3c66a88801c6c73a57ef769eb5af5d (diff)
downloadhorizon-3a59bec6a71aca166a9f23c714529fd9836d9e23.tar.gz
[Launch Instance Fix] Settings for volume name
This patch provides the rest API needed to get settings that allow conditionally displaying the volume device name and admin password. Examples: settingsService.ifEnabled('setting').then(doThis); settingsService.ifEnabled('setting', 'expected', 'default').then(doThis, elseThis); Change-Id: I8d16f4b974786157c5aa562e2675e6e60aabc412 Partial-Bug: #1439906 Partial-Bug: #1439905
Diffstat (limited to 'horizon')
-rw-r--r--horizon/static/horizon/js/angular/services/hz.api.config.js271
-rw-r--r--horizon/static/horizon/js/angular/services/hz.api.config.spec.js253
-rw-r--r--horizon/test/jasmine/jasmine_tests.py2
3 files changed, 512 insertions, 14 deletions
diff --git a/horizon/static/horizon/js/angular/services/hz.api.config.js b/horizon/static/horizon/js/angular/services/hz.api.config.js
index 09a710669..e7315ef7a 100644
--- a/horizon/static/horizon/js/angular/services/hz.api.config.js
+++ b/horizon/static/horizon/js/angular/services/hz.api.config.js
@@ -1,18 +1,19 @@
/*
-Copyright 2015, Rackspace, US, Inc.
-
-Licensed 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.
-*/
+ * Copyright 2015 IBM Corp
+ * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed 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.
+ */
(function () {
'use strict';
@@ -61,10 +62,252 @@ limitations under the License.
horizon.alert('error', gettext('Unable to retrieve admin configuration.'));
});
};
+
}
// Register it with the API module so that anybody using the
// API module will have access to the Config APIs.
angular.module('hz.api')
.service('configAPI', ['apiService', ConfigAPI]);
+
+ /**
+ * @ngdoc service
+ * @name hz.api.settingsService
+ * @description
+ * Provides utilities to the cached settings data. This helps
+ * with asynchronous data loading.
+ *
+ * The cache in current horizon (Kilo non-single page app) only has a
+ * lifetime of the current page. The cache is reloaded every time you change
+ * panels. It also happens when you change the region selector at the top
+ * of the page, and when you log back in.
+ *
+ * So, at least for now, this seems to be a reliable way that will
+ * make only a single request to get user information for a
+ * particular page or modal. Making this a service allows it to be injected
+ * and used transparently where needed without making every single use of it
+ * pass it through as an argument.
+ */
+ function settingsService($q, apiService) {
+
+ var service = {};
+
+ /**
+ * @name hz.api.configAPI.getSettings
+ * @description
+ * Gets all the allowed settings
+ *
+ * Returns an object with settings.
+ */
+ service.getSettings = function (suppressError) {
+
+ function onError() {
+ var message = gettext('Unable to retrieve settings.');
+ if (!suppressError && horizon.alert) {
+ horizon.alert('error', message);
+ }
+
+ return message;
+ }
+
+ // The below ensures that errors are handled like other
+ // service errors (for better or worse), but when successful
+ // unwraps the success result data for direct consumption.
+ return apiService.get('/api/settings/', {cache: true})
+ .error(onError)
+ .then(function (response) {
+ return response.data;
+ });
+ };
+
+ /**
+ * @name hz.api.settingsService.getSetting
+ * @description
+ * This retrieves a specific setting.
+ *
+ * If the setting isn't found, it will return undefined unless a default
+ * is specified. In that case, the default will be returned.
+ *
+ * @param {string} setting The path to the setting to get.
+ *
+ * local_settings.py allows you to create settings such as:
+ *
+ * OPENSTACK_HYPERVISOR_FEATURES = {
+ * 'can_set_mount_point': True,
+ * 'can_set_password': False,
+ * }
+ *
+ * To access a specific setting, use a simplified path where a . (dot)
+ * separates elements in the path. So in the above example, the paths
+ * would be:
+ *
+ * OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point
+ * OPENSTACK_HYPERVISOR_FEATURES.can_set_password
+ *
+ * @param {object=} defaultSetting If the requested setting does not exist,
+ * the defaultSetting will be returned. This is optional.
+ *
+ * @example
+ *
+ * Using the OPENSTACK_HYPERVISOR_FEATURES mentioned above, the following
+ * would call doSomething and pass the setting value into doSomething.
+ *
+ ```js
+ settingsService.getSetting('OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point')
+ .then(doSomething);
+ ```
+ */
+ service.getSetting = function (path, defaultSetting) {
+ var deferred = $q.defer(),
+ pathElements = path.split("."),
+ settingAtRequestedPath;
+
+ function onSettingsLoaded(settings) {
+ // This recursively traverses the object hierarchy until either all the
+ // path elements are traversed or until the next element in the path
+ // does not have the requested child object.
+ settingAtRequestedPath = pathElements.reduce(
+ function (setting, nextPathElement) {
+ return setting ? setting[nextPathElement] : undefined;
+ }, settings);
+
+ if (typeof settingAtRequestedPath === "undefined" &&
+ (typeof defaultSetting !== "undefined")) {
+ settingAtRequestedPath = defaultSetting;
+ }
+
+ deferred.resolve(settingAtRequestedPath);
+ }
+
+ function onSettingsFailure(message) {
+ deferred.reject(message);
+ }
+
+ service.getSettings()
+ .then(onSettingsLoaded, onSettingsFailure);
+
+ return deferred.promise;
+ };
+
+ /**
+ * @name hz.api.settingsService.ifEnabled
+ * @description
+ * Checks if the desired setting is enabled. This returns a promise.
+ * If the setting is enabled, the promise will be resolved.
+ * If it is not enabled, the promise will be rejected. Use it like you
+ * would normal promises.
+ *
+ * @param {string} setting The path to the setting to check.
+ * local_settings.py allows you to create settings such as:
+ *
+ * OPENSTACK_HYPERVISOR_FEATURES = {
+ * 'can_set_mount_point': True,
+ * 'can_set_password': False,
+ * }
+ *
+ * To access a specific setting, use a simplified path where a . (dot)
+ * separates elements in the path. So in the above example, the paths
+ * would be:
+ *
+ * OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point
+ * OPENSTACK_HYPERVISOR_FEATURES.can_set_password
+ *
+ * @param (object=} expected Used to determine if the setting is
+ * enabled. The actual setting will be evaluated against the expected
+ * value using angular.equals(). If they are equal, then it will be
+ * considered enabled. This is optional and defaults to True.
+ *
+ * @param {object=} defaultSetting If the requested setting does not exist,
+ * the defaultSetting will be used for evaluation. This is optional. If
+ * not specified and the setting is not specified, then the setting will
+ * not be considered to be enabled.
+ *
+ * @example
+ * Simple true / false example:
+ *
+ * Using the OPENSTACK_HYPERVISOR_FEATURES mentioned above, the following
+ * would call the "setMountPoint" function only if
+ * OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point is set to true.
+ *
+ ```js
+ settingsService.ifEnabled('OPENSTACK_HYPERVISOR_FEATURES.can_set_mount_point')
+ .then(setMountPoint);
+ ```
+ *
+ * Evaluating other types of settings:
+ *
+ * local_settings.py allows you optionally set the enabled OpenStack
+ * Service API versions with the following setting:
+ *
+ * OPENSTACK_API_VERSIONS = {
+ * "data-processing": 1.1,
+ * "identity": 3,
+ * "volume": 2,
+ * }
+ *
+ * The above is a nested object structure. The simplified path to the
+ * volume service version is OPENSTACK_API_VERSIONS.volume
+ *
+ * It is not uncommon for different OpenStack deployments to have
+ * different versions of the service enabled for various reasons.
+ *
+ * So, now, assume that if version 2 of the volume service (Cinder) is
+ * enabled that you want to do something. If it isn't, then you will do
+ * something else.
+ *
+ * Assume doSomethingIfVersion2 is a function you want to call if version 2
+ * is enabled.
+ *
+ * Assume doSomethingElse is a function that does something else if
+ * version 2 is not enabled (optional)
+ *
+ ```js
+ settingsService.ifEnabled('OPENSTACK_API_VERSIONS.volume', 2)
+ .then(doSomethingIfVersion2, doSomethingElse);
+ ```
+ *
+ * Now assume that if nothing is set in local_settings, that you want to
+ * treat the result as if version 1 is enabled (default when nothing set).
+ *
+ ```js
+ settingsService.ifEnabled('OPENSTACK_API_VERSIONS.volume', 2, 1)
+ .then(doSomethingIfVersion2, doSomethingElse);
+ ```
+ */
+ service.ifEnabled = function (setting, expected, defaultSetting) {
+ var deferred = $q.defer();
+
+ // If expected is not defined, we default to expecting the setting
+ // to be 'true' in order for it to be considered enabled.
+ expected = (typeof expected === "undefined") ? true : expected;
+
+ function onSettingLoaded(setting) {
+ if (angular.equals(expected, setting)) {
+ deferred.resolve();
+ } else {
+ deferred.reject(interpolate(
+ gettext('Setting is not enabled: %(setting)s'),
+ {setting: setting},
+ true));
+ }
+
+ deferred.resolve(setting);
+ }
+
+ function onSettingFailure(message) {
+ deferred.reject(message);
+ }
+
+ service.getSetting(setting, defaultSetting)
+ .then(onSettingLoaded, onSettingFailure);
+
+ return deferred.promise;
+ };
+
+ return service;
+ }
+
+ angular.module('hz.api')
+ .factory('settingsService', ['$q', 'apiService', settingsService]);
+
}());
diff --git a/horizon/static/horizon/js/angular/services/hz.api.config.spec.js b/horizon/static/horizon/js/angular/services/hz.api.config.spec.js
new file mode 100644
index 000000000..7a4397668
--- /dev/null
+++ b/horizon/static/horizon/js/angular/services/hz.api.config.spec.js
@@ -0,0 +1,253 @@
+/*
+ * (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
+ *
+ * Licensed 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.
+ */
+/*global angular,describe,it,expect,inject,module,beforeEach,afterEach*/
+(function () {
+ 'use strict';
+
+ describe('settingsService', function () {
+ var settingsService,
+ $httpBackend,
+ responseMockOpts = {succeed: true},
+ testData = {
+ isTrue: true,
+ isFalse: false,
+ versions: {one: 1, two: 2},
+ deep: {nest: { foo: 'bar' } },
+ isNull: null
+ };
+
+ beforeEach(module('hz.api'));
+ beforeEach(inject(function (_$httpBackend_, _settingsService_) {
+ responseMockOpts.succeed = true;
+ settingsService = _settingsService_;
+ $httpBackend = _$httpBackend_;
+ $httpBackend.whenGET('/api/settings/').respond(
+ function () {
+ return responseMockOpts.succeed ?
+ [200, testData, {}] : [500, 'Fail', {}];
+ });
+ $httpBackend.expectGET('/api/settings/');
+ }));
+
+ afterEach(function () {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ describe('getSettings', function () {
+
+ it('should return all settings', function () {
+ settingsService.getSettings().then(
+ function (actual) {
+ expect(actual).toEqual(testData);
+ }
+ );
+ $httpBackend.flush();
+ });
+
+ it('should fail when error response', function () {
+ responseMockOpts.succeed = false;
+ settingsService.getSettings().then(
+ function (actual) {
+ fail('Should not have succeeded: ' + angular.toJson(actual));
+ },
+ function (actual) {
+ expect(actual).toBeDefined();
+ }
+ );
+ $httpBackend.flush();
+ });
+
+ });
+
+ describe('getSetting', function () {
+
+ it('nested deep object is found', function () {
+ settingsService.getSetting('deep.nest.foo')
+ .then(function (actual) {
+ expect(actual).toEqual('bar');
+ });
+ $httpBackend.flush();
+ });
+
+ it("is undefined when doesn't exist", function () {
+ settingsService.getSetting('will.not.exist')
+ .then(function (actual) {
+ expect(actual).toBeUndefined();
+ });
+ $httpBackend.flush();
+ });
+
+ it("default is returned when doesn't exist", function () {
+ var actual;
+ settingsService.getSetting('will.not.exist', 'hello')
+ .then(function (actual) {
+ expect(actual).toEqual('hello');
+ });
+ $httpBackend.flush();
+ });
+
+ it('should return true', function () {
+ settingsService.getSetting('isTrue')
+ .then(function (actual) {
+ expect(actual).toEqual(true);
+ });
+ $httpBackend.flush();
+ });
+
+ it('should fail when error response', function () {
+ responseMockOpts.succeed = false;
+ settingsService.getSetting('isTrue').then(
+ function (actual) {
+ fail('Should not have succeeded: ' + angular.toJson(actual));
+ },
+ function (actual) {
+ expect(actual).toBeDefined();
+ }
+ );
+ $httpBackend.flush();
+ });
+
+ });
+
+ describe('ifEnabled', function () {
+
+ var expectedResult = {};
+
+ var enabled = function () {
+ expectedResult.enabled = true;
+ };
+
+ var notEnabled = function () {
+ expectedResult.enabled = false;
+ };
+
+ beforeEach(inject(function () {
+ expectedResult = {enabled: null};
+ }));
+
+ function meetsExpectations(expected) {
+ $httpBackend.flush();
+ expect(expectedResult.enabled).toBe(expected);
+ }
+
+ it('should fail when error response', function () {
+ responseMockOpts.succeed = false;
+ settingsService.ifEnabled('isTrue').then(
+ function (actual) {
+ fail('Should not have succeeded: ' + angular.toJson(actual));
+ },
+ function (actual) {
+ expect(actual).toBeDefined();
+ }
+ );
+ $httpBackend.flush();
+ });
+
+ it('boolean is enabled when true', function () {
+ settingsService.ifEnabled('isTrue').then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('boolean is enabled when true expected', function () {
+ settingsService.ifEnabled('isTrue', true).then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('boolean is not enabled when false expected', function () {
+ settingsService.ifEnabled('isTrue', false).then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('boolean is not enabled when false', function () {
+ settingsService.ifEnabled('isFalse').then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('boolean is enabled when false expected', function () {
+ settingsService.ifEnabled('isFalse', false).then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('nested object is enabled when expected', function () {
+ settingsService.ifEnabled('versions.one', 1).then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('nested object is not enabled', function () {
+ settingsService.ifEnabled('versions.two', 1).then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('nested object is not enabled when not found', function () {
+ settingsService.ifEnabled('no-exist.two', 1).then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('nested deep object is enabled when expected', function () {
+ settingsService.ifEnabled('deep.nest.foo', 'bar').then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('nested deep object is not enabled when not expected', function () {
+ settingsService.ifEnabled('deep.nest.foo', 'wrong').then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('null is not enabled', function () {
+ settingsService.ifEnabled('isNull').then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('null is enabled when expected', function () {
+ settingsService.ifEnabled('isNull', null).then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('true is enabled when not found and true default', function () {
+ settingsService.ifEnabled('nonExistent', true, true).then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('true is not enabled when not found and false default', function () {
+ settingsService.ifEnabled('nonExistent', true, false).then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('true is not enabled when not found and no default', function () {
+ settingsService.ifEnabled('nonExistent', true).then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+
+ it('false is enabled when not found and expected w/ default', function () {
+ settingsService.ifEnabled('nonExistent', false, false).then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('bar is enabled when expected not found and bar default', function () {
+ settingsService.ifEnabled('nonExistent', 'bar', 'bar').then(enabled, notEnabled);
+ meetsExpectations(true);
+ });
+
+ it('bar is not enabled when expected not found and not default', function () {
+ settingsService.ifEnabled('nonExistent', 'bar', 'foo').then(enabled, notEnabled);
+ meetsExpectations(false);
+ });
+ });
+
+ });
+
+})();
diff --git a/horizon/test/jasmine/jasmine_tests.py b/horizon/test/jasmine/jasmine_tests.py
index 01f41a2ce..fd6511564 100644
--- a/horizon/test/jasmine/jasmine_tests.py
+++ b/horizon/test/jasmine/jasmine_tests.py
@@ -22,6 +22,7 @@ class ServicesTests(test.JasmineTests):
'horizon/js/angular/services/horizon.utils.js',
'horizon/js/angular/hz.api.module.js',
'horizon/js/angular/services/hz.api.service.js',
+ 'horizon/js/angular/services/hz.api.config.js',
'angular/widget.module.js',
'angular/action-list/action-list.js',
'angular/action-list/button-tooltip.js',
@@ -47,6 +48,7 @@ class ServicesTests(test.JasmineTests):
]
specs = [
'horizon/js/angular/services/hz.api.service.spec.js',
+ 'horizon/js/angular/services/hz.api.config.spec.js',
'horizon/tests/jasmine/utils.spec.js',
'angular/action-list/action-list.spec.js',
'angular/bind-scope/bind-scope.spec.js',