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:
parent
b3dcf8c5ca
commit
6d40d87987
|
@ -11,4 +11,5 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
# import REST API modules here
|
# import REST API modules here
|
||||||
|
from . import environments # noqa
|
||||||
from . import packages # noqa
|
from . import packages # noqa
|
||||||
|
|
|
@ -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)
|
|
@ -117,6 +117,27 @@ class Session(object):
|
||||||
"for the environment {0}".format(environment_id))
|
"for the environment {0}".format(environment_id))
|
||||||
return 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
|
@staticmethod
|
||||||
def get(request, environment_id):
|
def get(request, environment_id):
|
||||||
"""Get an open session id
|
"""Get an open session id
|
||||||
|
|
|
@ -433,6 +433,36 @@ class UpdateName(tables.UpdateAction):
|
||||||
return True
|
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):
|
class EnvironmentsTable(tables.DataTable):
|
||||||
name = md_utils.Column(
|
name = md_utils.Column(
|
||||||
'name',
|
'name',
|
||||||
|
@ -472,7 +502,8 @@ class EnvironmentsTable(tables.DataTable):
|
||||||
table_actions = (CreateEnvironment, DeployEnvironment,
|
table_actions = (CreateEnvironment, DeployEnvironment,
|
||||||
DeleteEnvironment, AbandonEnvironment)
|
DeleteEnvironment, AbandonEnvironment)
|
||||||
row_actions = (ShowEnvironmentServices, DeployEnvironment,
|
row_actions = (ShowEnvironmentServices, DeployEnvironment,
|
||||||
DeleteEnvironment, AbandonEnvironment)
|
DeleteEnvironment, AbandonEnvironment,
|
||||||
|
UpdateEnvMetadata)
|
||||||
|
|
||||||
|
|
||||||
def get_service_details_link(service):
|
def get_service_details_link(service):
|
||||||
|
@ -484,6 +515,40 @@ def get_service_type(datum):
|
||||||
return datum['?'].get(consts.DASHBOARD_ATTRS_KEY, {}).get('name')
|
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):
|
class ServicesTable(tables.DataTable):
|
||||||
name = md_utils.Column(
|
name = md_utils.Column(
|
||||||
'name',
|
'name',
|
||||||
|
@ -572,7 +637,7 @@ class ServicesTable(tables.DataTable):
|
||||||
status_columns = ['status']
|
status_columns = ['status']
|
||||||
row_class = UpdateServiceRow
|
row_class = UpdateServiceRow
|
||||||
table_actions = (AddApplication, DeployThisEnvironment)
|
table_actions = (AddApplication, DeployThisEnvironment)
|
||||||
row_actions = (DeleteService,)
|
row_actions = (DeleteService, UpdateMetadata)
|
||||||
multi_select = False
|
multi_select = False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,14 @@ ADD_EXCEPTIONS = {
|
||||||
'unauthorized': exceptions.UNAUTHORIZED,
|
'unauthorized': exceptions.UNAUTHORIZED,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ADD_ANGULAR_MODULES = ['horizon.app.murano']
|
||||||
|
|
||||||
ADD_JS_FILES = [
|
ADD_JS_FILES = [
|
||||||
'muranodashboard/js/upload_form.js',
|
'muranodashboard/js/upload_form.js',
|
||||||
'muranodashboard/js/import_bundle_form.js',
|
'muranodashboard/js/import_bundle_form.js',
|
||||||
'muranodashboard/js/more-less.js',
|
'muranodashboard/js/more-less.js',
|
||||||
|
|
||||||
'app/murano/murano.service.js',
|
'app/murano/murano.service.js',
|
||||||
|
'app/murano/murano.module.js'
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
|
@ -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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
|
@ -31,7 +31,11 @@
|
||||||
|
|
||||||
function muranoAPI(apiService, toastService) {
|
function muranoAPI(apiService, toastService) {
|
||||||
var service = {
|
var service = {
|
||||||
getPackages: getPackages
|
getPackages: getPackages,
|
||||||
|
getComponentMeta: getComponentMeta,
|
||||||
|
editComponentMeta: editComponentMeta,
|
||||||
|
getEnvironmentMeta: getEnvironmentMeta,
|
||||||
|
editEnvironmentMeta: editEnvironmentMeta
|
||||||
};
|
};
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
@ -70,5 +74,130 @@
|
||||||
toastService.add('error', gettext('Unable to retrieve the packages.'));
|
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.'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
Loading…
Reference in New Issue