Launch Instance - MultiRegion Support / Networks
This makes it so that networks are only provided if the network service is enabled. This bugfix also adds multi-region support. Details on how to setup a multi-region test to verify it in horizon are in the bug. Closes-Bug: #1432401 Co-Authored-By: Shaoquan Chen <sean.chen2@hp.com> Co-Authored-By: Brian Tully <brian.tully@hp.com> Co-Authored-By: Richard Jones <r1chardj0n3s@gmail.com> Change-Id: I4e98c18b579eb5fc9e1b76ddf57d22142a7ddeab
This commit is contained in:
parent
c38d3029a0
commit
2d548313cc
|
@ -9,6 +9,7 @@
|
|||
'hz.widget.modal',
|
||||
'hz.widget.modal-wait-spinner',
|
||||
'hz.framework.bind-scope',
|
||||
'hz.framework.workflow',
|
||||
'hz.widget.transfer-table',
|
||||
'hz.widget.charts',
|
||||
'hz.widget.action-list',
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
<button class="btn nav-item"
|
||||
ng-class="{'current': currentIndex===$index}"
|
||||
ng-click="switchTo($index)"
|
||||
ng-if="step.ready"
|
||||
ng-repeat="step in steps"
|
||||
ng-show="ready && step.ready">
|
||||
ng-show="ready">
|
||||
<span ng-bind="::step.title"></span>
|
||||
<span class="status-indicator fa fa-lg fa-warning"
|
||||
ng-show="wizardForm[steps[$index].formName].$invalid"></span>
|
||||
|
@ -14,6 +15,7 @@
|
|||
</div>
|
||||
|
||||
<div class="step"
|
||||
ng-if="step.ready"
|
||||
ng-repeat="step in steps"
|
||||
ng-show="currentIndex===$index">
|
||||
<ng-include
|
||||
|
@ -61,6 +63,7 @@
|
|||
|
||||
<help-panel>
|
||||
<ng-include src="step.helpUrl"
|
||||
ng-if="step.ready"
|
||||
ng-repeat="step in steps"
|
||||
ng-show="currentIndex===$index"></ng-include>
|
||||
</help-panel>
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name hz.framework.workflow
|
||||
* @description
|
||||
*
|
||||
* # hz.framework.workflow
|
||||
*
|
||||
* This module provides utility function service `workflow` to allow
|
||||
* decorating a workflow object. A user (developer) friendly workflow
|
||||
* specification object can be shaped into a format that is friendly to
|
||||
* {@link hz.widget.wizard `wizard`} by utilizing this service.
|
||||
*
|
||||
* The service provides a mechanism of decoupling general wizard UI component
|
||||
* from business components.
|
||||
*
|
||||
* | Factories |
|
||||
* |--------------------------------------------------------------------------|
|
||||
* | {@link hz.framework.workflow.factory:workflow `workflow`} |
|
||||
*
|
||||
*/
|
||||
|
||||
angular.module('hz.framework.workflow', [])
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name hz.framework.workflow.factory:workflow
|
||||
* @module hz.framework.workflow
|
||||
* @kind function
|
||||
* @description
|
||||
*
|
||||
* Decorate the workflow specification object with specified decorators.
|
||||
*
|
||||
* @param {Object} The input workflow specification object
|
||||
* @param {Array<function>} decorators A list a decorator functions.
|
||||
*
|
||||
* @returns {Object} The decorated workflow specification object, the same
|
||||
* reference to the input spec object.
|
||||
*
|
||||
```js
|
||||
|
||||
angular.module('MyModule', [])
|
||||
|
||||
.factory('myService', ['$q', 'workflow', function ($q, workflow) {
|
||||
|
||||
// a workflow specification object:
|
||||
var spec = {
|
||||
steps: [
|
||||
{ requireSomeServices: true },
|
||||
{ },
|
||||
{ requireSomeServices: true }
|
||||
]
|
||||
};
|
||||
|
||||
// define some decorators
|
||||
var decorators = [
|
||||
// a decorator
|
||||
function (spec) {
|
||||
var steps = spec.steps;
|
||||
|
||||
angular.forEach(steps, function (step) {
|
||||
if (step.requireSomeServices) {
|
||||
step.checkReadiness = function () {
|
||||
var d = $q.defer();
|
||||
|
||||
// checking if the service is available asynchronously .
|
||||
setTimeout(function () {
|
||||
d.resolve();
|
||||
}, 500);
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// another decorator
|
||||
function (spec) {
|
||||
//...
|
||||
}
|
||||
];
|
||||
|
||||
return workflow(spec, decorators);
|
||||
}]);
|
||||
```
|
||||
*/
|
||||
|
||||
.factory('workflow', [
|
||||
function () {
|
||||
return function (spec, decorators) {
|
||||
angular.forEach(decorators, function (decorator) {
|
||||
decorator(spec);
|
||||
});
|
||||
return spec;
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
|
@ -0,0 +1,59 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('hz.framework.workflow module', function () {
|
||||
it('should have been defined', function () {
|
||||
expect(angular.module('hz.framework.workflow')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('workflow factory', function () {
|
||||
|
||||
var workflow,
|
||||
spec,
|
||||
decorators = [
|
||||
function (spec) {
|
||||
angular.forEach(spec.steps, function (step) {
|
||||
if (step.requireSomeServices) {
|
||||
step.checkReadiness = function () {};
|
||||
}
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(module('hz.framework.workflow'));
|
||||
|
||||
beforeEach(inject(function ($injector) {
|
||||
workflow = $injector.get('workflow');
|
||||
spec = {
|
||||
steps: [
|
||||
{ requireSomeServices: true },
|
||||
{ },
|
||||
{ requireSomeServices: true }
|
||||
]
|
||||
};
|
||||
}));
|
||||
|
||||
it('workflow is defined', function () {
|
||||
expect(workflow).toBeDefined();
|
||||
});
|
||||
|
||||
it('workflow is a function', function () {
|
||||
expect(angular.isFunction(workflow)).toBe(true);
|
||||
});
|
||||
|
||||
it('can be decorated', function () {
|
||||
workflow(spec, decorators);
|
||||
var steps = spec.steps;
|
||||
|
||||
expect(steps[0].checkReadiness).toBeDefined();
|
||||
expect(angular.isFunction(steps[0].checkReadiness)).toBe(true);
|
||||
|
||||
expect(steps[1].checkReadiness).not.toBeDefined();
|
||||
|
||||
expect(steps[2].checkReadiness).toBeDefined();
|
||||
expect(angular.isFunction(steps[2].checkReadiness)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
|
@ -39,6 +39,41 @@ limitations under the License.
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @name hz.api.keystoneApi.getCurrentUserSession
|
||||
* @description
|
||||
* Gets the current User Session Information
|
||||
* @example
|
||||
* {
|
||||
* "available_services_regions": [
|
||||
* "RegionOne"
|
||||
* ],
|
||||
* "domain_id": null,
|
||||
* "domain_name": null,
|
||||
* "enabled": true,
|
||||
* "id": "2138efda19264c64b69551c6b08054c9",
|
||||
* "is_superuser": true,
|
||||
* "project_id": "53fafe441399439a852d3bd81c22caf6",
|
||||
* "project_name": "demo",
|
||||
* "roles": [
|
||||
* {
|
||||
* "name": "admin"
|
||||
* }
|
||||
* ],
|
||||
* "services_region": "RegionOne",
|
||||
* "user_domain_id": "default",
|
||||
* "user_domain_name": "Default",
|
||||
* "username": "admin"
|
||||
* }
|
||||
*/
|
||||
this.getCurrentUserSession = function(config) {
|
||||
return apiService.get('/api/keystone/user-session/', config)
|
||||
.error(function () {
|
||||
horizon.alert('error',
|
||||
gettext('Unable to retrieve the current user session.'));
|
||||
});
|
||||
};
|
||||
|
||||
this.getUser = function(user_id) {
|
||||
return apiService.get('/api/keystone/users/' + user_id)
|
||||
.error(function () {
|
||||
|
@ -220,6 +255,46 @@ limitations under the License.
|
|||
angular.module('hz.api')
|
||||
.service('keystoneAPI', ['apiService', KeystoneAPI]);
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name hz.api.userSession
|
||||
* @description
|
||||
* Provides cached access to the user session. The cache may be reset
|
||||
* at any time by accessing the cache and calling removeAll, which means
|
||||
* that the next call to any function in this service will retrieve fresh
|
||||
* results after the cache is cleared. This allows programmatic refresh of
|
||||
* the cache.
|
||||
*
|
||||
* 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 userSession($cacheFactory, keystoneAPI) {
|
||||
|
||||
var service = {};
|
||||
|
||||
service.cache = $cacheFactory('hz.api.userSession', {capacity: 1});
|
||||
|
||||
service.get = function () {
|
||||
return keystoneAPI.getCurrentUserSession({cache: service.cache})
|
||||
.then(function (response) {
|
||||
return response.data;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
angular.module('hz.api')
|
||||
.factory('userSession', ['$cacheFactory', 'keystoneAPI', userSession]);
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
|
@ -230,49 +305,133 @@ limitations under the License.
|
|||
* by accessing the cache and calling removeAll. The next call to any
|
||||
* function will retrieve fresh results.
|
||||
*
|
||||
* The enabled extensions do not change often, so using cached data will
|
||||
* speed up results. Even on a local devstack in informal testing,
|
||||
* this saved between 30 - 100 ms per request.
|
||||
* 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 ServiceCatalog($cacheFactory, $q, keystoneAPI) {
|
||||
function serviceCatalog($cacheFactory, $q, keystoneAPI, userSession) {
|
||||
|
||||
var service = {};
|
||||
service.cache = $cacheFactory('hz.api.serviceCatalog', {capacity: 1});
|
||||
|
||||
/**
|
||||
* @name hz.api.serviceCatalog.get
|
||||
* @description
|
||||
* Returns the service catalog. This is cached.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
```js
|
||||
serviceCatalog.get()
|
||||
.then(doSomething, doSomethingElse);
|
||||
```
|
||||
*/
|
||||
service.get = function() {
|
||||
return keystoneAPI.serviceCatalog({cache: service.cache})
|
||||
.then(function(data){
|
||||
return data.data;
|
||||
.then(function(response){
|
||||
return response.data;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
service.ifTypeEnabled = function(desired, doThis) {
|
||||
return service.get().then(function(result){
|
||||
if (enabled(result, 'type', desired)){
|
||||
return $q.when(doThis());
|
||||
}
|
||||
/**
|
||||
* @name hz.api.serviceCatalog.ifTypeEnabled
|
||||
* @description
|
||||
* Checks if the desired service is enabled. If it is enabled, use the
|
||||
* promise returned to execute the desired function. If it is not enabled,
|
||||
* The promise will be rejected.
|
||||
*
|
||||
* @param {string} desiredType The type of service desired.
|
||||
*
|
||||
* @example
|
||||
* Assume if the network service is enabled, you want to get networks,
|
||||
* if it isn't, then you will do something else.
|
||||
* Assume getNetworks is a function that hits Neutron.
|
||||
* Assume doSomethingElse is a function that does something else if
|
||||
* the network service is not enabled (optional)
|
||||
*
|
||||
```js
|
||||
serviceCatalog.ifTypeEnabled('network')
|
||||
.then(getNetworks, doSomethingElse);
|
||||
```
|
||||
*/
|
||||
service.ifTypeEnabled = function (desiredType) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
$q.all(
|
||||
{
|
||||
session: userSession.get(),
|
||||
catalog: service.get()
|
||||
}
|
||||
).then(
|
||||
onDataLoaded,
|
||||
onDataFailure
|
||||
);
|
||||
|
||||
function onDataLoaded(d) {
|
||||
if (typeHasEndpointsInRegion(d.catalog,
|
||||
desiredType,
|
||||
d.session.services_region)) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject(interpolate(
|
||||
gettext('Service type is not enabled: %(desiredType)s'),
|
||||
{desiredType: desiredType},
|
||||
true));
|
||||
}
|
||||
}
|
||||
|
||||
function onDataFailure() {
|
||||
deferred.reject(gettext('Cannot get service catalog from keystone.'));
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
function enabled(resources, key, desired) {
|
||||
if(resources) {
|
||||
return resources.some(function (resource) {
|
||||
return resource[key] === desired;
|
||||
});
|
||||
function typeHasEndpointsInRegion(catalog, desiredType, desiredRegion) {
|
||||
var matchingSvcs = catalog.filter(function (svc) {
|
||||
return svc.type === desiredType;
|
||||
});
|
||||
|
||||
// Ignore region for identity. Identity service endpoint
|
||||
// should not change for different regions.
|
||||
if (desiredType === 'identity' && matchingSvcs.length > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
return matchingSvcs.some(function (svc) {
|
||||
return svc.endpoints.some(function (endpoint) {
|
||||
return getEndpointRegion(endpoint) === desiredRegion;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return service;
|
||||
/*
|
||||
* In Keystone V3, region has been deprecated in favor of
|
||||
* region_id.
|
||||
*
|
||||
* This method provides a way to get region that works for
|
||||
* both Keystone V2 and V3.
|
||||
*/
|
||||
function getEndpointRegion(endpoint) {
|
||||
return endpoint.region_id || endpoint.region;
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
angular.module('hz.api')
|
||||
.factory('serviceCatalog', ['$cacheFactory',
|
||||
'$q',
|
||||
'keystoneAPI',
|
||||
ServiceCatalog]);
|
||||
'userSession',
|
||||
serviceCatalog]);
|
||||
|
||||
}());
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
<script src='{{ STATIC_URL }}angular/metadata-display/metadata-display.js'></script>
|
||||
<script src='{{ STATIC_URL }}angular/magic-search/magic-search.js'></script>
|
||||
<script src='{{ STATIC_URL }}angular/validators/validators.js'></script>
|
||||
<script src='{{ STATIC_URL }}angular/workflow/workflow.js'></script>
|
||||
|
||||
<script src='{{ STATIC_URL }}horizon/lib/jquery/jquery.quicksearch.js'></script>
|
||||
<script src="{{ STATIC_URL }}horizon/lib/jquery/jquery.tablesorter.js"></script>
|
||||
|
|
|
@ -41,6 +41,7 @@ class ServicesTests(test.JasmineTests):
|
|||
'angular/transfer-table/transfer-table.js',
|
||||
'angular/validators/validators.js',
|
||||
'angular/wizard/wizard.js',
|
||||
'angular/workflow/workflow.js',
|
||||
'angular/metadata-display/metadata-display.js',
|
||||
'horizon/js/angular/filters/filters.js',
|
||||
]
|
||||
|
@ -59,6 +60,7 @@ class ServicesTests(test.JasmineTests):
|
|||
'angular/transfer-table/transfer-table.spec.js',
|
||||
'angular/wizard/wizard.spec.js',
|
||||
'angular/validators/validators.spec.js',
|
||||
'angular/workflow/workflow.spec.js',
|
||||
'angular/metadata-tree/metadata-tree.spec.js',
|
||||
'angular/metadata-display/metadata-display.spec.js',
|
||||
'horizon/js/angular/filters/filters.spec.js',
|
||||
|
|
|
@ -529,3 +529,31 @@ class ServiceCatalog(generic.View):
|
|||
user.
|
||||
"""
|
||||
return request.user.service_catalog
|
||||
|
||||
|
||||
@urls.register
|
||||
class UserSession(generic.View):
|
||||
"""API for a single keystone user.
|
||||
"""
|
||||
url_regex = r'keystone/user-session/$'
|
||||
allowed_fields = {
|
||||
'available_services_regions',
|
||||
'domain_id',
|
||||
'domain_name',
|
||||
'enabled',
|
||||
'id',
|
||||
'is_superuser',
|
||||
'project_id',
|
||||
'project_name',
|
||||
'roles',
|
||||
'services_region',
|
||||
'user_domain_id',
|
||||
'user_domain_name',
|
||||
'username'
|
||||
}
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get the current user session.
|
||||
"""
|
||||
return {k: getattr(request.user, k, None) for k in self.allowed_fields}
|
||||
|
|
|
@ -27,6 +27,7 @@ LAUNCH_INST = 'dashboard/launch-instance/'
|
|||
|
||||
ADD_JS_FILES = [
|
||||
'dashboard/dashboard.module.js',
|
||||
'dashboard/workflow/workflow.js',
|
||||
LAUNCH_INST + 'launch-instance.js',
|
||||
LAUNCH_INST + 'launch-instance.model.js',
|
||||
LAUNCH_INST + 'source/source.js',
|
||||
|
@ -40,6 +41,7 @@ ADD_JS_FILES = [
|
|||
|
||||
ADD_JS_SPEC_FILES = [
|
||||
'dashboard/dashboard.module.spec.js',
|
||||
'dashboard/workflow/workflow.spec.js',
|
||||
LAUNCH_INST + 'launch-instance.spec.js',
|
||||
LAUNCH_INST + 'launch-instance.model.spec.js',
|
||||
LAUNCH_INST + 'source/source.spec.js',
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
'use strict';
|
||||
|
||||
var module = angular.module('hz.dashboard', [
|
||||
'hz.dashboard.launch-instance'
|
||||
'hz.dashboard.launch-instance',
|
||||
'hz.dashboard.workflow'
|
||||
]);
|
||||
|
||||
module.constant('dashboardBasePath', '/static/dashboard/');
|
||||
|
|
|
@ -1,61 +1,67 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('hz.dashboard.launch-instance', [ 'ngSanitize']);
|
||||
var module = angular.module('hz.dashboard.launch-instance', [ 'ngSanitize' ]);
|
||||
|
||||
module.factory('launchInstanceWorkflow', ['dashboardBasePath', function (path) {
|
||||
module.factory('launchInstanceWorkflow', [
|
||||
'dashboardBasePath',
|
||||
'dashboardWorkflow',
|
||||
|
||||
return {
|
||||
title: gettext('Launch Instance'),
|
||||
function (path, dashboardWorkflow) {
|
||||
|
||||
steps: [
|
||||
{
|
||||
title: gettext('Select Source'),
|
||||
templateUrl: path + 'launch-instance/source/source.html',
|
||||
helpUrl: path + 'launch-instance/source/source.help.html',
|
||||
formName: 'launchInstanceSourceForm'
|
||||
return dashboardWorkflow({
|
||||
title: gettext('Launch Instance'),
|
||||
|
||||
steps: [
|
||||
{
|
||||
title: gettext('Select Source'),
|
||||
templateUrl: path + 'launch-instance/source/source.html',
|
||||
helpUrl: path + 'launch-instance/source/source.help.html',
|
||||
formName: 'launchInstanceSourceForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Flavor'),
|
||||
templateUrl: path + 'launch-instance/flavor/flavor.html',
|
||||
helpUrl: path + 'launch-instance/flavor/flavor.help.html',
|
||||
formName: 'launchInstanceFlavorForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Networks'),
|
||||
templateUrl: path + 'launch-instance/network/network.html',
|
||||
helpUrl: path + 'launch-instance/network/network.help.html',
|
||||
formName: 'launchInstanceNetworkForm',
|
||||
requiredServiceTypes: ['network']
|
||||
},
|
||||
{
|
||||
title: gettext('Security Groups'),
|
||||
templateUrl: path + 'launch-instance/security-groups/security-groups.html',
|
||||
helpUrl: path + 'launch-instance/security-groups/security-groups.help.html',
|
||||
formName: 'launchInstanceAccessAndSecurityForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Key Pair'),
|
||||
templateUrl: path + 'launch-instance/keypair/keypair.html',
|
||||
helpUrl: path + 'launch-instance/keypair/keypair.help.html',
|
||||
formName: 'launchInstanceKeypairForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Configuration'),
|
||||
templateUrl: path + 'launch-instance/configuration/configuration.html',
|
||||
helpUrl: path + 'launch-instance/configuration/configuration.help.html',
|
||||
formName: 'launchInstanceConfigurationForm'
|
||||
}
|
||||
],
|
||||
|
||||
btnText: {
|
||||
finish: gettext('Launch Instance')
|
||||
},
|
||||
{
|
||||
title: gettext('Flavor'),
|
||||
templateUrl: path + 'launch-instance/flavor/flavor.html',
|
||||
helpUrl: path + 'launch-instance/flavor/flavor.help.html',
|
||||
formName: 'launchInstanceFlavorForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Network'),
|
||||
templateUrl: path + 'launch-instance/network/network.html',
|
||||
helpUrl: path + 'launch-instance/network/network.help.html',
|
||||
formName: 'launchInstanceNetworkForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Security Groups'),
|
||||
templateUrl: path + 'launch-instance/security-groups/security-groups.html',
|
||||
helpUrl: path + 'launch-instance/security-groups/security-groups.help.html',
|
||||
formName: 'launchInstanceAccessAndSecurityForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Key Pair'),
|
||||
templateUrl: path + 'launch-instance/keypair/keypair.html',
|
||||
helpUrl: path + 'launch-instance/keypair/keypair.help.html',
|
||||
formName: 'launchInstanceKeypairForm'
|
||||
},
|
||||
{
|
||||
title: gettext('Configuration'),
|
||||
templateUrl: path + 'launch-instance/configuration/configuration.html',
|
||||
helpUrl: path + 'launch-instance/configuration/configuration.help.html',
|
||||
formName: 'launchInstanceConfigurationForm'
|
||||
|
||||
btnIcon: {
|
||||
finish: 'fa fa-cloud-download'
|
||||
}
|
||||
],
|
||||
|
||||
btnText: {
|
||||
finish: gettext('Launch Instance')
|
||||
},
|
||||
|
||||
btnIcon: {
|
||||
finish: 'fa fa-cloud-download'
|
||||
}
|
||||
};
|
||||
}]);
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
||||
// Using bootstrap-ui modal widget
|
||||
module.constant('launchInstanceWizardModalSpec', {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var push = Array.prototype.push;
|
||||
var push = Array.prototype.push,
|
||||
noop = angular.noop;
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
|
@ -183,22 +184,27 @@
|
|||
|
||||
model.allowedBootSources.length = 0;
|
||||
|
||||
promise = $q.all(
|
||||
promise = $q.all([
|
||||
getImages(),
|
||||
neutronAPI.getNetworks().then(onGetNetworks),
|
||||
novaAPI.getAvailabilityZones().then(onGetAvailabilityZones),
|
||||
novaAPI.getFlavors().then(onGetFlavors),
|
||||
novaAPI.getKeypairs().then(onGetKeypairs),
|
||||
novaAPI.getLimits().then(onGetNovaLimits),
|
||||
securityGroup.query().then(onGetSecurityGroups),
|
||||
serviceCatalog.ifTypeEnabled('volume', onLoadVolumes)
|
||||
);
|
||||
novaAPI.getAvailabilityZones().then(onGetAvailabilityZones, noop),
|
||||
novaAPI.getFlavors().then(onGetFlavors, noop),
|
||||
novaAPI.getKeypairs().then(onGetKeypairs, noop),
|
||||
novaAPI.getLimits().then(onGetNovaLimits, noop),
|
||||
securityGroup.query().then(onGetSecurityGroups, noop),
|
||||
serviceCatalog.ifTypeEnabled('network').then(getNetworks, noop),
|
||||
serviceCatalog.ifTypeEnabled('volume').then(getVolumes, noop)
|
||||
]);
|
||||
|
||||
promise.then(function() {
|
||||
model.initializing = false;
|
||||
model.initialized = true;
|
||||
initPromise = null;
|
||||
});
|
||||
promise.then(
|
||||
function() {
|
||||
model.initializing = false;
|
||||
model.initialized = true;
|
||||
},
|
||||
function () {
|
||||
model.initializing = false;
|
||||
model.initialized = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return promise;
|
||||
|
@ -322,6 +328,10 @@
|
|||
|
||||
// Networks
|
||||
|
||||
function getNetworks() {
|
||||
return neutronAPI.getNetworks().then(onGetNetworks, noop);
|
||||
}
|
||||
|
||||
function onGetNetworks(data) {
|
||||
model.networks.length = 0;
|
||||
push.apply(model.networks, data.data.items);
|
||||
|
@ -369,21 +379,21 @@
|
|||
addAllowedBootSource(model.imageSnapshots, SOURCE_TYPE_SNAPSHOT, gettext('Instance Snapshot'));
|
||||
}
|
||||
|
||||
function onLoadVolumes(){
|
||||
var volumeLoadPromises = [];
|
||||
function getVolumes(){
|
||||
var volumePromises = [];
|
||||
// Need to check if Volume service is enabled before getting volumes
|
||||
model.volumeBootable = true;
|
||||
addAllowedBootSource(model.volumes, SOURCE_TYPE_VOLUME, 'Volume');
|
||||
addAllowedBootSource(model.volumes, SOURCE_TYPE_VOLUME, gettext('Volume'));
|
||||
addAllowedBootSource(model.volumeSnapshots, SOURCE_TYPE_VOLUME_SNAPSHOT, gettext('Volume Snapshot'));
|
||||
volumeLoadPromises.push(cinderAPI.getVolumes({ status: 'available', bootable: 1 }).then(onGetVolumes));
|
||||
volumeLoadPromises.push(cinderAPI.getVolumeSnapshots({ status: 'available' }).then(onGetVolumeSnapshots));
|
||||
volumePromises.push(cinderAPI.getVolumes({ status: 'available', bootable: 1 }).then(onGetVolumes));
|
||||
volumePromises.push(cinderAPI.getVolumeSnapshots({ status: 'available' }).then(onGetVolumeSnapshots));
|
||||
|
||||
// Can only boot image to volume if the Nova extension is enabled.
|
||||
novaExtensions.ifNameEnabled('BlockDeviceMappingV2Boot', function(){
|
||||
model.allowCreateVolumeFromImage = true;
|
||||
});
|
||||
|
||||
return $q.all(volumeLoadPromises);
|
||||
return $q.all(volumePromises);
|
||||
}
|
||||
|
||||
function onGetVolumes(data) {
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
function LaunchInstanceNetworkCtrl($scope) {
|
||||
|
||||
$scope.label = {
|
||||
title: gettext('Network'),
|
||||
title: gettext('Networks'),
|
||||
subtitle: gettext('Networks provide the communication channels for instances in the cloud.'),
|
||||
network: gettext('Network'),
|
||||
subnet_associated: gettext('Subnets Associated'),
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
var forEach = angular.forEach;
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @name hz.dashboard.workflow
|
||||
* @description
|
||||
*
|
||||
* # hz.dashboard.workflow
|
||||
*
|
||||
* This module provides utility function factory `dashboardWorkflow` and
|
||||
* `dashboardWorkflowDecorator`.
|
||||
*
|
||||
* | Factories |
|
||||
* |------------------------------------------------------------------------------------------------|
|
||||
* | {@link hz.dashboard.workflow.factory:dashboardWorkflowDecorator `dashboardWorkflowDecorator`} |
|
||||
*
|
||||
*/
|
||||
angular.module('hz.dashboard.workflow', [])
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name hz.dashboard.workflow.factory:dashboardWorkflowDecorator
|
||||
* @module hz.dashboard.workflow
|
||||
* @kind function
|
||||
* @description
|
||||
*
|
||||
* A workflow decorator function that adds checkReadiness method to step in
|
||||
* the work flow. checkReadiness function will check is a bunch of certain
|
||||
* types of OpenStack services is enabled in the cloud for that step to show
|
||||
* on the user interface.
|
||||
*
|
||||
* Injected dependencies:
|
||||
* - $q
|
||||
* - serviceCatalog hz.api.serviceCatalog
|
||||
*
|
||||
* @param {Object} spec The input workflow specification object.
|
||||
* @returns {Object} The decorated workflow specification object, the same
|
||||
* reference to the input spec object.
|
||||
*
|
||||
* | Factories |
|
||||
* |------------------------------------------------------------------------------------------------|
|
||||
* | {@link hz.dashboard.workflow.factory:dashboardWorkflowDecorator `dashboardWorkflowDecorator`} |
|
||||
*
|
||||
*/
|
||||
|
||||
.factory('dashboardWorkflowDecorator', ['$q', 'serviceCatalog',
|
||||
|
||||
function ($q, serviceCatalog) {
|
||||
|
||||
function decorate(spec) {
|
||||
forEach(spec.steps, function (step) {
|
||||
var types = step.requiredServiceTypes;
|
||||
if (types && types.length > 0) {
|
||||
step.checkReadiness = function () {
|
||||
return $q.all(types.map(function (type) {
|
||||
return serviceCatalog.ifTypeEnabled(type);
|
||||
}));
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return function (spec) {
|
||||
decorate(spec);
|
||||
return spec;
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
/**
|
||||
* @ngdoc factory
|
||||
* @name hz.dashboard.workflow.factory:dashboardWorkflow
|
||||
* @module hz.dashboard.workflow
|
||||
* @kind function
|
||||
* @description
|
||||
*
|
||||
* Injected dependencies:
|
||||
* - workflow {@link hz.framework.workflow.factory:workflow `workflow`}
|
||||
* - dashboardWorkflowDecorator {@link hz.dashboard.workflow.factory
|
||||
* :dashboardWorkflowDecorator `dashboardWorkflowDecorator`}
|
||||
*
|
||||
* @param {Object} The input workflow specification object
|
||||
*
|
||||
* @returns {Object} The decorated workflow specification object, the same
|
||||
* reference to the input spec object.
|
||||
*
|
||||
* | Factories |
|
||||
* |------------------------------------------------------------------------------|
|
||||
* | {@link hz.dashboard.workflow.factory:dashboardWorkflow `dashboardWorkflow`} |
|
||||
*
|
||||
*/
|
||||
|
||||
.factory('dashboardWorkflow', [
|
||||
'workflow',
|
||||
'dashboardWorkflowDecorator',
|
||||
function (workflow, dashboardWorkflowDecorator) {
|
||||
var decorators = [dashboardWorkflowDecorator];
|
||||
return function (spec) {
|
||||
return workflow(spec, decorators);
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
|
@ -0,0 +1,10 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
describe('hz.dashboard.workflow module', function () {
|
||||
it('should have been defined', function () {
|
||||
expect(angular.module('hz.dashboard.workflow')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
|
@ -581,3 +581,21 @@ class KeystoneRestTestCase(test.TestCase):
|
|||
content = jsonutils.dumps(request.user.service_catalog,
|
||||
sort_keys=settings.DEBUG)
|
||||
self.assertEqual(content, response.content)
|
||||
|
||||
#
|
||||
# User Session
|
||||
#
|
||||
@mock.patch.object(keystone.api, 'keystone')
|
||||
def test_user_session_get(self, kc):
|
||||
request = self.mock_rest_request()
|
||||
request.user = mock.Mock(
|
||||
services_region='some region',
|
||||
super_secret_thing='not here',
|
||||
is_authenticated=lambda: True,
|
||||
spec=['services_region', 'super_secret_thing']
|
||||
)
|
||||
response = keystone.UserSession().get(request)
|
||||
self.assertStatusCode(response, 200)
|
||||
content = jsonutils.loads(response.content)
|
||||
self.assertEqual(content['services_region'], 'some region')
|
||||
self.assertNotIn('super_secret_thing', content)
|
||||
|
|
Loading…
Reference in New Issue