[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
This commit is contained in:
parent
d7abcbfeec
commit
3a59bec6a7
@ -1,18 +1,19 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015, Rackspace, US, Inc.
|
* 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.
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
You may obtain a copy of the License at
|
* 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
|
*
|
||||||
|
* 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,
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
See the License for the specific language governing permissions and
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
limitations under the License.
|
* See the License for the specific language governing permissions and
|
||||||
*/
|
* limitations under the License.
|
||||||
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
@ -61,10 +62,252 @@ limitations under the License.
|
|||||||
horizon.alert('error', gettext('Unable to retrieve admin configuration.'));
|
horizon.alert('error', gettext('Unable to retrieve admin configuration.'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register it with the API module so that anybody using the
|
// Register it with the API module so that anybody using the
|
||||||
// API module will have access to the Config APIs.
|
// API module will have access to the Config APIs.
|
||||||
angular.module('hz.api')
|
angular.module('hz.api')
|
||||||
.service('configAPI', ['apiService', ConfigAPI]);
|
.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]);
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
253
horizon/static/horizon/js/angular/services/hz.api.config.spec.js
Normal file
253
horizon/static/horizon/js/angular/services/hz.api.config.spec.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
@ -22,6 +22,7 @@ class ServicesTests(test.JasmineTests):
|
|||||||
'horizon/js/angular/services/horizon.utils.js',
|
'horizon/js/angular/services/horizon.utils.js',
|
||||||
'horizon/js/angular/hz.api.module.js',
|
'horizon/js/angular/hz.api.module.js',
|
||||||
'horizon/js/angular/services/hz.api.service.js',
|
'horizon/js/angular/services/hz.api.service.js',
|
||||||
|
'horizon/js/angular/services/hz.api.config.js',
|
||||||
'angular/widget.module.js',
|
'angular/widget.module.js',
|
||||||
'angular/action-list/action-list.js',
|
'angular/action-list/action-list.js',
|
||||||
'angular/action-list/button-tooltip.js',
|
'angular/action-list/button-tooltip.js',
|
||||||
@ -47,6 +48,7 @@ class ServicesTests(test.JasmineTests):
|
|||||||
]
|
]
|
||||||
specs = [
|
specs = [
|
||||||
'horizon/js/angular/services/hz.api.service.spec.js',
|
'horizon/js/angular/services/hz.api.service.spec.js',
|
||||||
|
'horizon/js/angular/services/hz.api.config.spec.js',
|
||||||
'horizon/tests/jasmine/utils.spec.js',
|
'horizon/tests/jasmine/utils.spec.js',
|
||||||
'angular/action-list/action-list.spec.js',
|
'angular/action-list/action-list.spec.js',
|
||||||
'angular/bind-scope/bind-scope.spec.js',
|
'angular/bind-scope/bind-scope.spec.js',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Copyright 2015 IBM Corp.
|
# Copyright 2015 IBM Corp.
|
||||||
|
# Copyright 2015, Hewlett-Packard Development Company, L.P.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -12,6 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from horizon import conf
|
from horizon import conf
|
||||||
@ -25,6 +27,14 @@ admin_configs = ['ajax_queue_limit', 'ajax_poll_interval',
|
|||||||
'user_home', 'help_url',
|
'user_home', 'help_url',
|
||||||
'password_autocomplete', 'disable_password_reveal']
|
'password_autocomplete', 'disable_password_reveal']
|
||||||
|
|
||||||
|
# settings that we allow to be retrieved via REST API
|
||||||
|
# these settings are available to the client and are not secured.
|
||||||
|
# *** THEY SHOULD BE TREATED WITH EXTREME CAUTION ***
|
||||||
|
settings_required = getattr(settings, 'REST_API_REQUIRED_SETTINGS', [])
|
||||||
|
settings_additional = getattr(settings, 'REST_API_ADDITIONAL_SETTINGS', [])
|
||||||
|
|
||||||
|
settings_allowed = settings_required + settings_additional
|
||||||
|
|
||||||
# properties we know are user config
|
# properties we know are user config
|
||||||
# this is a white list of keys under HORIZON_CONFIG in settings.pys
|
# this is a white list of keys under HORIZON_CONFIG in settings.pys
|
||||||
# that we want to pass onto client
|
# that we want to pass onto client
|
||||||
@ -70,3 +80,18 @@ class AdminConfigs(generic.View):
|
|||||||
for key in admin_configs:
|
for key in admin_configs:
|
||||||
config[key] = conf.HORIZON_CONFIG.get(key, None)
|
config[key] = conf.HORIZON_CONFIG.get(key, None)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@urls.register
|
||||||
|
class Settings(generic.View):
|
||||||
|
"""API for retrieving settings.
|
||||||
|
|
||||||
|
This API returns read-only settings values.
|
||||||
|
This configuration object can be fetched as needed.
|
||||||
|
Examples of settings: OPENSTACK_HYPERVISOR_FEATURES
|
||||||
|
"""
|
||||||
|
url_regex = r'settings/$'
|
||||||
|
|
||||||
|
@rest_utils.ajax()
|
||||||
|
def get(self, request):
|
||||||
|
return {k: getattr(settings, k, None) for k in settings_allowed}
|
||||||
|
@ -614,3 +614,24 @@ SECURITY_GROUP_RULES = {
|
|||||||
# auth_token middleware are using. Allowed values are the
|
# auth_token middleware are using. Allowed values are the
|
||||||
# algorithms supported by Python's hashlib library.
|
# algorithms supported by Python's hashlib library.
|
||||||
#OPENSTACK_TOKEN_HASH_ALGORITHM = 'md5'
|
#OPENSTACK_TOKEN_HASH_ALGORITHM = 'md5'
|
||||||
|
|
||||||
|
# AngularJS requires some settings to be made available to
|
||||||
|
# the client side. Some settings are required by in-tree / built-in horizon
|
||||||
|
# features. These settings must be added to REST_API_REQUIRED_SETTINGS in the
|
||||||
|
# form of ['SETTING_1','SETTING_2'], etc.
|
||||||
|
#
|
||||||
|
# You may remove settings from this list for security purposes, but do so at
|
||||||
|
# the risk of breaking a built-in horizon feature. These settings are required
|
||||||
|
# for horizon to function properly. Only remove them if you know what you
|
||||||
|
# are doing. These settings may in the future be moved to be defined within
|
||||||
|
# the enabled panel configuration.
|
||||||
|
# You should not add settings to this list for out of tree extensions.
|
||||||
|
# See: https://wiki.openstack.org/wiki/Horizon/RESTAPI
|
||||||
|
REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES']
|
||||||
|
|
||||||
|
# Additional settings can be made available to the client side for
|
||||||
|
# extensibility by specifying them in REST_API_ADDITIONAL_SETTINGS
|
||||||
|
# !! Please use extreme caution as the settings are transferred via HTTP/S
|
||||||
|
# and are not encrypted on the browser. This is an experimental API and
|
||||||
|
# may be deprecated in the future without notice.
|
||||||
|
#REST_API_ADDITIONAL_SETTINGS = []
|
||||||
|
@ -51,6 +51,14 @@ class ConfigRestTestCase(test.TestCase):
|
|||||||
self.assertStatusCode(response, 200)
|
self.assertStatusCode(response, 200)
|
||||||
self.assertContains(response.content, content)
|
self.assertContains(response.content, content)
|
||||||
|
|
||||||
|
def test_settings_config_get(self):
|
||||||
|
request = self.mock_rest_request()
|
||||||
|
response = config.Settings().get(request)
|
||||||
|
self.assertStatusCode(response, 200)
|
||||||
|
self.assertContains(response.content, "REST_API_SETTING_1")
|
||||||
|
self.assertContains(response.content, "REST_API_SETTING_2")
|
||||||
|
self.assertNotContains(response.content, "REST_API_SECURITY")
|
||||||
|
|
||||||
def test_ignore_list(self):
|
def test_ignore_list(self):
|
||||||
ignore_config = {"password_validator": "someobject"}
|
ignore_config = {"password_validator": "someobject"}
|
||||||
content = '"password_validator": "someobject"'
|
content = '"password_validator": "someobject"'
|
||||||
|
@ -205,3 +205,9 @@ POLICY_FILES = {
|
|||||||
|
|
||||||
# The openstack_auth.user.Token object isn't JSON-serializable ATM
|
# The openstack_auth.user.Token object isn't JSON-serializable ATM
|
||||||
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||||
|
|
||||||
|
REST_API_SETTING_1 = 'foo'
|
||||||
|
REST_API_SETTING_2 = 'bar'
|
||||||
|
REST_API_SECURITY = 'SECURITY'
|
||||||
|
REST_API_REQUIRED_SETTINGS = ['REST_API_SETTING_1']
|
||||||
|
REST_API_ADDITIONAL_SETTINGS = ['REST_API_SETTING_2']
|
||||||
|
Loading…
x
Reference in New Issue
Block a user