Ability to assign metadata to Murano Applications and Environments

Applications deployed in Murano Environments can now be assigned with
metadata attributes which are passed to their object model as a
"metadata" block in their ?-header. A standard metadata assignment
modal dialog is utilized for this purpose.

Same action and dialog is available for Murano Environments as well.

Implementation adds a horizon-hosted rest API which handles requests
from Angular-based scripts and forwards them to murano-api.

To properly support the new api in metadata modal dialog a Horizon
core metadata service is decorated during the configuration of murano
module (explicitly added in enabled/_50_murano.py). This allows us to
reuse the latest version of upstream metadata service and put
murano-related calls on top of it.

Co-Authored-By: Timur Sufiev <tsufiev@mirantis.com>
Change-Id: I348629aedc3e9731616a53d731c33fb442ee12ec
Targets-blueprint: metadata-assignment-and-propagation
This commit is contained in:
Alexander Tivelkov 2016-09-28 18:30:23 +03:00 committed by Serg Melikyan
parent b3dcf8c5ca
commit 6d40d87987
10 changed files with 864 additions and 3 deletions

View File

@ -11,4 +11,5 @@
# limitations under the License.
# import REST API modules here
from . import environments # noqa
from . import packages # noqa

View File

@ -0,0 +1,158 @@
# Copyright (c) 2016 Mirantis, 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.
from django.views import generic
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
from muranodashboard import api
from muranodashboard.environments import api as env_api
@urls.register
class ComponentsMetadata(generic.View):
"""API for Murano components Metadata"""
url_regex = r'app-catalog/environments/(?P<environment>[^/]+)' \
r'/components/(?P<component>[^/]+)/metadata/$'
@rest_utils.ajax()
def get(self, request, environment, component):
"""Get a metadata object for a component in a given environment
Example GET:
http://localhost/api/app-catalog/environments/123/components/456/metadata
The following get parameters may be passed in the GET
request:
:param environment: identifier of the environment
:param component: identifier of the component
Any additionally a "session" parameter should be passed through the API
as a keyword.
"""
filters, keywords = rest_utils.parse_filters_kwargs(request,
['session'])
session = keywords.get('session')
if not session:
session = env_api.Session.get_or_create_or_delete(request,
environment)
component = api.muranoclient(request).services.get(
environment, '/' + component, session)
if component:
return component.to_dict()['?'].get('metadata', {})
return {}
@rest_utils.ajax(data_required=True)
def post(self, request, environment, component):
"""Set a metadata object for a component in a given environment
Example POST:
http://localhost/api/app-catalog/environments/123/components/456/metadata
The following get parameters may be passed in the GET
request:
:param environment: identifier of the environment
:param component: identifier of the component
Any additionally a "session" parameter should be passed through the API
as a keyword. Request body should contain 'updated' keyword, contain
all the updated metadata attributes. If it is empty, the metadata is
considered to be deleted.
"""
client = api.muranoclient(request)
filters, keywords = rest_utils.parse_filters_kwargs(request,
['session'])
session = keywords.get('session')
if not session:
session = env_api.Session.get_or_create_or_delete(request,
environment)
updated = request.DATA.get('updated', {})
path = '/{0}/%3F/metadata'.format(component)
if updated:
client.services.put(environment, path, updated, session)
else:
client.services.delete(environment, path, session)
@urls.register
class EnvironmentsMetadata(generic.View):
"""API for Murano components Metadata"""
url_regex = r'app-catalog/environments/(?P<environment>[^/]+)/metadata/$'
@rest_utils.ajax()
def get(self, request, environment):
"""Get a metadata object for an environment
Example GET:
http://localhost/api/app-catalog/environments/123/metadata
The following get parameters may be passed in the GET
request:
:param environment: identifier of the environment
Any additionally a "session" parameter should be passed through the API
as a keyword.
"""
filters, keywords = rest_utils.parse_filters_kwargs(request,
['session'])
session = keywords.get('session')
if not session:
session = env_api.Session.get_or_create_or_delete(request,
environment)
env = api.muranoclient(request).environments.get_model(
environment, '/', session)
if env:
return env['?'].get('metadata', {})
return {}
@rest_utils.ajax(data_required=True)
def post(self, request, environment):
"""Set a metadata object for a given environment
Example POST:
http://localhost/api/app-catalog/environments/123/metadata
The following get parameters may be passed in the GET
request:
:param environment: identifier of the environment
Any additionally a "session" parameter should be passed through the API
as a keyword. Request body should contain 'updated' keyword, contain
all the updated metadata attributes. If it is empty, the metadata is
considered to be deleted.
"""
client = api.muranoclient(request)
filters, keywords = rest_utils.parse_filters_kwargs(request,
['session'])
session = keywords.get('session')
if not session:
session = env_api.Session.get_or_create_or_delete(request,
environment)
updated = request.DATA.get('updated', {})
patch = {
"op": "replace",
"path": "/?/metadata",
"value": updated
}
client.environments.update_model(environment, [patch], session)

View File

@ -117,6 +117,27 @@ class Session(object):
"for the environment {0}".format(environment_id))
return id
@staticmethod
def get_if_available(request, environment_id):
"""Get an id of open session if it exists and is not in deployed state.
Returns None otherwise
"""
sessions = request.session.get('sessions', {})
client = api.muranoclient(request)
if environment_id in sessions:
id = sessions[environment_id]
try:
session_data = client.sessions.get(environment_id, id)
except exc.HTTPForbidden:
return None
else:
if session_data.state in [consts.STATUS_ID_DEPLOY_FAILURE,
consts.STATUS_ID_READY]:
return None
return id
@staticmethod
def get(request, environment_id):
"""Get an open session id

View File

@ -433,6 +433,36 @@ class UpdateName(tables.UpdateAction):
return True
class UpdateEnvMetadata(tables.LinkAction):
name = "update_env_metadata"
verbose_name = _("Update Metadata")
ajax = False
icon = "pencil"
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
def __init__(self, attrs=None, **kwargs):
kwargs['preempt'] = True
self.session_id = None
super(UpdateEnvMetadata, self).__init__(attrs, **kwargs)
def get_link_url(self, environment):
target = {
'environment': str(environment.id),
'session': str(self.session_id),
}
self.attrs['ng-click'] = (
"modal.openMetadataModal('muranoenv', %s, true)" % target)
return "javascript:void(0);"
def allowed(self, request, environment=None):
return environment.status not in (consts.STATUS_ID_DEPLOYING,
consts.STATUS_ID_DELETING)
def update(self, request, datum):
env_id = self.table.kwargs.get('environment_id')
self.session_id = api.Session.get_if_available(request, env_id)
class EnvironmentsTable(tables.DataTable):
name = md_utils.Column(
'name',
@ -472,7 +502,8 @@ class EnvironmentsTable(tables.DataTable):
table_actions = (CreateEnvironment, DeployEnvironment,
DeleteEnvironment, AbandonEnvironment)
row_actions = (ShowEnvironmentServices, DeployEnvironment,
DeleteEnvironment, AbandonEnvironment)
DeleteEnvironment, AbandonEnvironment,
UpdateEnvMetadata)
def get_service_details_link(service):
@ -484,6 +515,40 @@ def get_service_type(datum):
return datum['?'].get(consts.DASHBOARD_ATTRS_KEY, {}).get('name')
class UpdateMetadata(tables.LinkAction):
name = "update_metadata"
verbose_name = _("Update Metadata")
ajax = False
icon = "pencil"
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
def __init__(self, attrs=None, **kwargs):
kwargs['preempt'] = True
self.session_id = None
super(UpdateMetadata, self).__init__(attrs, **kwargs)
def get_link_url(self, service):
env_id = self.table.kwargs.get('environment_id')
comp_id = service['?']['id']
target = {
'environment': str(env_id),
'component': str(comp_id),
'session': str(self.session_id),
}
self.attrs['ng-click'] = (
"modal.openMetadataModal('muranoapp', %s, true)" % target)
return "javascript:void(0);"
def allowed(self, request, service=None):
status, version = _get_environment_status_and_version(request,
self.table)
return status != consts.STATUS_ID_DEPLOYING
def update(self, request, datum):
env_id = self.table.kwargs.get('environment_id')
self.session_id = api.Session.get_if_available(request, env_id)
class ServicesTable(tables.DataTable):
name = md_utils.Column(
'name',
@ -572,7 +637,7 @@ class ServicesTable(tables.DataTable):
status_columns = ['status']
row_class = UpdateServiceRow
table_actions = (AddApplication, DeployThisEnvironment)
row_actions = (DeleteService,)
row_actions = (DeleteService, UpdateMetadata)
multi_select = False

View File

@ -28,11 +28,14 @@ ADD_EXCEPTIONS = {
'unauthorized': exceptions.UNAUTHORIZED,
}
ADD_ANGULAR_MODULES = ['horizon.app.murano']
ADD_JS_FILES = [
'muranodashboard/js/upload_form.js',
'muranodashboard/js/import_bundle_form.js',
'muranodashboard/js/more-less.js',
'app/murano/murano.service.js',
'app/murano/murano.module.js'
]

View File

@ -0,0 +1,163 @@
/*
* Copyright 2015, ThoughtWorks 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.
*/
(function () {
'use strict';
describe('metadata.service', function () {
/**
* That's almost a copy-paste of original metadata.service.spec.js file,
* with the exception that a murano module is added to modules registry to
* check that murano decorator for metadata service doesn't break core
* OpenStack services metadata.
*/
beforeEach(module('horizon.app.core'));
beforeEach(module('horizon.app.murano'));
var nova = {getAggregateExtraSpecs: function() {},
getFlavorExtraSpecs: function() {},
editAggregateExtraSpecs: function() {},
editFlavorExtraSpecs: function() {},
getInstanceMetadata: function() {},
editInstanceMetadata: function() {} };
var glance = {getImageProps: function() {},
editImageProps: function() {},
getNamespaces: function() {}};
var cinder = {getVolumeMetadata:function() {},
getVolumeSnapshotMetadata:function() {},
getVolumeTypeMetadata:function() {},
editVolumeMetadata: function() {},
editVolumeSnapshotMetadata: function() {}};
beforeEach(function() {
module(function($provide) {
$provide.value('horizon.app.core.openstack-service-api.nova', nova);
$provide.value('horizon.app.core.openstack-service-api.glance', glance);
$provide.value('horizon.app.core.openstack-service-api.cinder', cinder);
});
});
var metadataService;
beforeEach(inject(function($injector) {
metadataService = $injector.get('horizon.app.core.metadata.service');
}));
it('should get aggregate metadata', function() {
var expected = 'aggregate metadata';
spyOn(nova, 'getAggregateExtraSpecs').and.returnValue(expected);
var actual = metadataService.getMetadata('aggregate', '1');
expect(actual).toBe(expected);
});
it('should edit aggregate metadata', function() {
spyOn(nova, 'editAggregateExtraSpecs');
metadataService.editMetadata('aggregate', '1', 'updated', ['removed']);
expect(nova.editAggregateExtraSpecs).toHaveBeenCalledWith('1', 'updated', ['removed']);
});
it('should get aggregate namespace', function() {
spyOn(glance, 'getNamespaces');
metadataService.getNamespaces('aggregate');
expect(glance.getNamespaces)
.toHaveBeenCalledWith({ resource_type: 'OS::Nova::Aggregate' }, false);
});
it('should get flavor metadata', function() {
var expected = 'flavor metadata';
spyOn(nova, 'getFlavorExtraSpecs').and.returnValue(expected);
var actual = metadataService.getMetadata('flavor', '1');
expect(actual).toBe(expected);
});
it('should edit flavor metadata', function() {
spyOn(nova, 'editFlavorExtraSpecs');
metadataService.editMetadata('flavor', '1', 'updated', ['removed']);
expect(nova.editFlavorExtraSpecs).toHaveBeenCalledWith('1', 'updated', ['removed']);
});
it('should get flavor namespace', function() {
spyOn(glance, 'getNamespaces');
metadataService.getNamespaces('flavor');
expect(glance.getNamespaces)
.toHaveBeenCalledWith({ resource_type: 'OS::Nova::Flavor' }, false);
});
it('should get image metadata', function() {
var expected = 'image metadata';
spyOn(glance, 'getImageProps').and.returnValue(expected);
var actual = metadataService.getMetadata('image', '1');
expect(actual).toBe(expected);
});
it('should edit image metadata', function() {
spyOn(glance, 'editImageProps');
metadataService.editMetadata('image', '1', 'updated', ['removed']);
expect(glance.editImageProps).toHaveBeenCalledWith('1', 'updated', ['removed']);
});
it('should edit volume metadata', function() {
spyOn(cinder, 'editVolumeMetadata');
metadataService.editMetadata('volume', '1', 'updated', ['removed']);
expect(cinder.editVolumeMetadata).toHaveBeenCalledWith('1', 'updated', ['removed']);
});
it('should edit volume snapshot metadata', function() {
spyOn(cinder, 'editVolumeSnapshotMetadata');
metadataService.editMetadata('volume_snapshot', '1', 'updated', ['removed']);
expect(cinder.editVolumeSnapshotMetadata).toHaveBeenCalledWith('1', 'updated', ['removed']);
});
it('should get image namespace', function() {
spyOn(glance, 'getNamespaces');
metadataService.getNamespaces('image');
expect(glance.getNamespaces)
.toHaveBeenCalledWith({ resource_type: 'OS::Glance::Image' }, false);
});
it('should get instance metadata', function() {
var expected = 'instance metadata';
spyOn(nova, 'getInstanceMetadata').and.returnValue(expected);
var actual = metadataService.getMetadata('instance', '1');
expect(actual).toBe(expected);
});
it('should get volume metadata', function() {
var expected = 'volume metadata';
spyOn(cinder, 'getVolumeMetadata').and.returnValue(expected);
var actual = metadataService.getMetadata('volume', '1');
expect(actual).toBe(expected);
});
it('should edit instance metadata', function() {
spyOn(nova, 'editInstanceMetadata');
metadataService.editMetadata('instance', '1', 'updated', ['removed']);
expect(nova.editInstanceMetadata).toHaveBeenCalledWith('1', 'updated', ['removed']);
});
it('should get instance namespace', function() {
spyOn(glance, 'getNamespaces');
metadataService.getNamespaces('instance', 'metadata');
expect(glance.getNamespaces)
.toHaveBeenCalledWith({ resource_type: 'OS::Nova::Server',
properties_target: 'metadata' }, false);
});
});
})();

View File

@ -0,0 +1,100 @@
/**
* Copyright 2016, Mirantis, 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.
*/
(function() {
'use strict';
/**
* @ngdoc horizon.app.murano
* @ng-module
* @description
* Provides all of the services and widgets required
* to support and display the Murano Packages panel.
*/
angular
.module('horizon.app.murano', [])
.config(config);
config.$inject = [
'$injector',
'$provide'
];
function config($injector, $provide) {
if ($injector.has('horizon.app.core.metadata.service')) {
$provide.decorator('horizon.app.core.metadata.service', patchMetadata);
}
patchMetadata.$inject = [
'$delegate',
'horizon.app.core.openstack-service-api.murano',
'horizon.app.core.openstack-service-api.glance'
];
function patchMetadata($delegate, murano, glance) {
var origEditMetadata = $delegate.editMetadata;
var origGetMetadata = $delegate.getMetadata;
var origGetNamespaces = $delegate.getNamespaces;
$delegate.editMetadata = editMetadata;
$delegate.getMetadata = getMetadata;
$delegate.getNamespaces = getNamespaces;
return $delegate;
function getMetadata(resource, id) {
if (resource === 'muranoapp') {
return murano.getComponentMeta(id);
}
if (resource === 'muranoenv') {
return murano.getEnvironmentMeta(id);
}
return origGetMetadata(resource, id);
}
function editMetadata(resource, id, updated, removed) {
if (resource === 'muranoapp') {
return murano.editComponentMeta(id, updated, removed);
}
if (resource === 'muranoenv') {
return murano.editEnvironmentMeta(id, updated, removed);
}
return origEditMetadata(resource, id, updated, removed);
}
function getNamespaces(resource, propertiesTarget) {
var params;
if (resource === 'muranoapp') {
params = {resource_type: 'OS::Murano::Application'};
if (propertiesTarget) {
params.properties_target = propertiesTarget;
}
return glance.getNamespaces(params, false);
}
if (resource === 'muranoenv') {
params = {resource_type: 'OS::Murano::Environment'};
if (propertiesTarget) {
params.properties_target = propertiesTarget;
}
return glance.getNamespaces(params, false);
}
return origGetNamespaces(resource, propertiesTarget);
}
}
}
})();

View File

@ -0,0 +1,113 @@
/**
* Copyright 2016, Mirantis, 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.
*/
(function () {
'use strict';
describe('horizon.app.murano', function () {
it('should be defined', function () {
expect(angular.module('horizon.app.murano')).toBeDefined();
});
});
describe('murano metadata patcher', function () {
var metadataService, glance, murano;
var lastFakeCallArgs = [];
var fakeResult = 'fakeMeta';
beforeEach(function () {
murano = {
getComponentMeta: fakeMeta,
editComponentMeta: fakeMeta,
getEnvironmentMeta: fakeMeta,
editEnvironmentMeta: fakeMeta
};
module('horizon.framework');
module('horizon.app.core');
module('horizon.app.murano');
module(function($provide) {
$provide.value('horizon.app.core.openstack-service-api.murano', murano);
});
function fakeMeta() {
var i;
for (i = 0, lastFakeCallArgs = []; i < arguments.length; i++) {
lastFakeCallArgs.push(arguments[i]);
}
return fakeResult;
}
});
beforeEach(inject(function ($injector) {
metadataService = $injector.get('horizon.app.core.metadata.service');
glance = $injector.get('horizon.app.core.openstack-service-api.glance');
}));
it('should get component metadata', function () {
var actual = metadataService.getMetadata('muranoapp', 'compId');
expect(actual).toBe(fakeResult);
expect(lastFakeCallArgs).toEqual(['compId']);
});
it('should get environment metadata', function () {
var actual = metadataService.getMetadata('muranoenv', 'envId');
expect(actual).toBe(fakeResult);
expect(lastFakeCallArgs).toEqual(['envId']);
});
it('should edit component metadata', function () {
metadataService.editMetadata('muranoapp', 'compId',
{'key1': 10}, ['key2']);
expect(lastFakeCallArgs).toEqual(['compId', {'key1': 10}, ['key2']]);
});
it('should edit environment metadata', function () {
metadataService.editMetadata('muranoenv', 'envId',
{'key1': 10}, ['key2']);
expect(lastFakeCallArgs).toEqual(['envId', {'key1': 10}, ['key2']]);
});
it('should get component namespace', function () {
var params, flag;
spyOn(glance, 'getNamespaces').and.callFake(function(_params, _flag) {
params = _params;
flag = _flag;
});
metadataService.getNamespaces('muranoapp', 'something');
expect(params).toEqual({
resource_type: 'OS::Murano::Application',
properties_target: 'something'
});
expect(flag).toBe(false);
});
it('should get environment namespace', function () {
var params, flag;
spyOn(glance, 'getNamespaces').and.callFake(function(_params, _flag) {
params = _params;
flag = _flag;
});
metadataService.getNamespaces('muranoenv', 'something');
expect(params).toEqual({
resource_type: 'OS::Murano::Environment',
properties_target: 'something'
});
expect(flag).toBe(false);
});
});
})();

View File

@ -31,7 +31,11 @@
function muranoAPI(apiService, toastService) {
var service = {
getPackages: getPackages
getPackages: getPackages,
getComponentMeta: getComponentMeta,
editComponentMeta: editComponentMeta,
getEnvironmentMeta: getEnvironmentMeta,
editEnvironmentMeta: editEnvironmentMeta
};
return service;
@ -70,5 +74,130 @@
toastService.add('error', gettext('Unable to retrieve the packages.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.murano.getComponentMeta
* @description
* Get metadata attributes associated with a given component
*
* @param {Object} target
* The object identifying the target component
*
* @param {string} target.environment
* The identifier of the environment the component belongs to
*
* @param {string} target.component
* The identifier of the component within the environment
*
* @param {string} target.session
* The identifier of the configuration session for which the data should be
* fetched
*
* @returns {Object} The metadata object
*/
function getComponentMeta(target) {
var params = { params: { session: target.session} };
var url = '/api/app-catalog/environments/' + target.environment +
'/components/' + target.component + '/metadata/';
return apiService.get(url, params)
.error(function () {
toastService.add('error', gettext('Unable to retrieve component metadata.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.murano.editComponentMetadata
* @description
* Update metadata attributes associated with a given component
*
* @param {Object} target
* The object identifying the target component
*
* @param {string} target.environment
* The identifier of the environment the component belongs to
*
* @param {string} target.component
* The identifier of the component within the environment
*
* @param {string} target.session
* The identifier of the configuration session for which the data should be
* updated
*
* @param {object} updated New metadata definitions.
*
* @param {[]} removed Names of removed metadata definitions.
*
* @returns {Object} The result of the API call
*/
function editComponentMeta(target, updated, removed) {
var params = { params: { session: target.session} };
var url = '/api/app-catalog/environments/' + target.environment +
'/components/' + target.component + '/metadata/';
return apiService.post(
url, { updated: updated, removed: removed}, params)
.error(function () {
toastService.add('error', gettext('Unable to edit component metadata.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.murano.getEnvironmentMeta
* @description
* Get metadata attributes associated with a given environment
*
* @param {Object} target
* The object identifying the target environment
*
* @param {string} target.environment
* The identifier of the target environment
*
* @param {string} target.session
* The identifier of the configuration session for which the data should be
* fetched
*
* @returns {Object} The metadata object
*/
function getEnvironmentMeta(target) {
var params = { params: { session: target.session} };
var url = '/api/app-catalog/environments/' + target.environment +
'/metadata/';
return apiService.get(url, params)
.error(function () {
toastService.add('error', gettext('Unable to retrieve environment metadata.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.murano.editEnvironmentMeta
* @description
* Update metadata attributes associated with a given environment
*
* @param {Object} target
* The object identifying the target environment
*
* @param {string} target.environment
* The identifier of the environment the component belongs to
*
* @param {string} target.session
* The identifier of the configuration session for which the data should be
* updated
*
* @param {object} updated New metadata definitions.
*
* @param {[]} removed Names of removed metadata definitions.
*
* @returns {Object} The result of the API call
*/
function editEnvironmentMeta(target, updated, removed) {
var params = { params: { session: target.session} };
var url = '/api/app-catalog/environments/' + target.environment +
'/metadata/';
return apiService.post(
url, { updated: updated, removed: removed}, params)
.error(function () {
toastService.add('error', gettext('Unable to edit environment metadata.'));
});
}
}
})();

View File

@ -0,0 +1,108 @@
/**
* Copyright 2016, Mirantis, 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.
*/
(function () {
'use strict';
describe('Murano API', function () {
var testCall, service;
var apiService = {};
var toastService = {};
beforeEach(
module('horizon.mock.openstack-service-api',
function($provide, initServices) {
testCall = initServices($provide, apiService, toastService);
})
);
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject([
'horizon.app.core.openstack-service-api.murano',
function(muranoAPI) {
service = muranoAPI;
}]));
it('defines the service', function () {
expect(service).toBeDefined();
});
var tests = [
{
func: 'getPackages',
method: 'get',
path: '/api/app-catalog/packages/',
data: {params: 'config'},
error: 'Unable to retrieve the packages.',
testInput: [ 'config' ]
}, {
func: 'getComponentMeta',
method: 'get',
path: '/api/app-catalog/environments/1/components/2/metadata/',
data: {params: {session: 'sessionId'}},
error: 'Unable to retrieve component metadata.',
testInput: [{session: 'sessionId', environment: '1', component: '2'}]
}, {
func: 'editComponentMeta',
method: 'post',
path: '/api/app-catalog/environments/1/components/2/metadata/',
call_args: [
'/api/app-catalog/environments/1/components/2/metadata/',
{updated: {'key1': 10}, removed: ['key2']},
{params: {session: 'sessionId'}}
],
error: 'Unable to edit component metadata.',
testInput: [
{session: 'sessionId', environment: '1', component: '2'},
{'key1': 10},
['key2']
]
}, {
func: 'getEnvironmentMeta',
method: 'get',
path: '/api/app-catalog/environments/1/metadata/',
data: {params: {session: 'sessionId'}},
error: 'Unable to retrieve environment metadata.',
testInput: [{session: 'sessionId', environment: '1'}]
}, {
func: 'editEnvironmentMeta',
method: 'post',
path: '/api/app-catalog/environments/1/metadata/',
call_args: [
'/api/app-catalog/environments/1/metadata/',
{updated: {'key1': 10}, removed: ['key2']},
{params: {session: 'sessionId'}}
],
error: 'Unable to edit environment metadata.',
testInput: [
{session: 'sessionId', environment: '1'},
{'key1': 10},
['key2']
]
}
];
// Iterate through the defined tests and apply as Jasmine specs.
angular.forEach(tests, function(params) {
it('defines the ' + params.func + ' call properly', function () {
var callParams = [apiService, service, toastService, params];
testCall.apply(this, callParams);
});
});
});
})();