diff --git a/doc/source/specification/murano-env-temp.rst b/doc/source/specification/murano-env-temp.rst index c3fe49591..0277e6e50 100644 --- a/doc/source/specification/murano-env-temp.rst +++ b/doc/source/specification/murano-env-temp.rst @@ -65,6 +65,13 @@ List Environments Templates | | | environment templates | +----------+----------------------------------+----------------------------------+ +*Parameters:* + +* `is_public` - boolean, indicates whether public environment templates are listed or not. + *True* public environments templates from all tenants are listed. + *False* private environments templates from current tenant are listed + *empty* all tenant templates plus public templates from all tenants are listed + *Response* This call returns a list of environment templates. Only the basic properties are @@ -81,6 +88,7 @@ returned. "created": "2014-05-14T13:02:46", "tenant_id": "726ed856965f43cc8e565bc991fa76c3", "version": 0, + "is_public": false, "id": "2fa5ab704749444bbeafe7991b412c33" }, { @@ -88,8 +96,9 @@ returned. "networking": {}, "name": "test2", "created": "2014-05-14T13:02:51", - "tenant_id": "726ed856965f43cc8e565bc991fa76c3", + "tenant_id": "123452452345346345634563456345346", "version": 0, + "is_public": true, "id": "744e44812da84e858946f5d817de4f72" } ] @@ -414,3 +423,58 @@ Create an environment from an environment template +----------------+-----------------------------------------------------------+ | 409 | The environment already exists | +----------------+-----------------------------------------------------------+ + + +**POST /templates/{env-temp-id}/clone** + +*Request* + ++----------+--------------------------------+-------------------------------------------------+ +| Method | URI | Description | ++==========+================================+=================================================+ +| POST | /templates/{env-temp-id}/clone | It clones a public template from one tenant | +| | | to another | ++----------+--------------------------------+-------------------------------------------------+ + +*Parameters:* + +* `env-temp-id` - environment template ID, required + +*Example Payload* +:: + + { + 'name': 'cloned_env_template_name' + } + +*Content-Type* + application/json + +*Response* + +:: + + { + "updated": "2015-01-26T09:12:51", + "name": "cloned_env_template_name", + "created": "2015-01-26T09:12:51", + "tenant_id": "00000000000000000000000000000001", + "version": 0, + "is_public": False, + "id": "aa9033ca7ce245fca10e38e1c8c4bbf7", + } + ++----------------+-----------------------------------------------------------+ +| Code | Description | ++================+===========================================================+ +| 200 | OK. Environment Template cloned successfully | ++----------------+-----------------------------------------------------------+ +| 401 | User is not authorized to access this session | ++----------------+-----------------------------------------------------------+ +| 403 | User has no access to these resources | ++----------------+-----------------------------------------------------------+ +| 404 | The environment template does not exist | ++----------------+-----------------------------------------------------------+ +| 409 | Conflict. The environment template name already exists | ++----------------+-----------------------------------------------------------+ + diff --git a/murano/api/v1/router.py b/murano/api/v1/router.py index 5479b6fd7..cce2315d5 100644 --- a/murano/api/v1/router.py +++ b/murano/api/v1/router.py @@ -121,6 +121,10 @@ class API(wsgi.Router): controller=templates_resource, action='create_environment', conditions={'method': ['POST']}) + mapper.connect('/templates/{env_template_id}/clone', + controller=templates_resource, + action='clone', + conditions={'method': ['POST']}) applications_resource = template_applications.create_resource() mapper.connect('/templates/{env_template_id}/services', diff --git a/murano/api/v1/templates.py b/murano/api/v1/templates.py index 99087c3e0..4fc1c7a17 100644 --- a/murano/api/v1/templates.py +++ b/murano/api/v1/templates.py @@ -17,10 +17,11 @@ from oslo_log import log as logging from webob import exc from murano.api.v1 import request_statistics -from murano.common.i18n import _ +from murano.common.i18n import _, _LE from murano.common import policy from murano.common import utils from murano.common import wsgi +from murano.db.models import EnvironmentTemplate from murano.db.services import core_services from murano.db.services import environment_templates as env_temps from murano.db.services import environments as envs @@ -40,12 +41,27 @@ class Controller(object): """ LOG.debug('EnvTemplates:List') policy.check('list_env_templates', request.context) + tenant_id = request.context.tenant + filters = {} + if request.GET.get('is_public'): + is_public = request.GET.get('is_public', 'false').lower() == 'true' + if not is_public: + + filters['is_public'] = False + filters = {'tenant_id': tenant_id} + elif is_public: + filters['is_public'] = True + + list_templates = env_temps.EnvTemplateServices.\ + get_env_templates_by(filters) + + else: + filters = (EnvironmentTemplate.is_public is True, + EnvironmentTemplate.tenant_id == tenant_id) + list_templates = env_temps.EnvTemplateServices.\ + get_env_templates_or_by(filters) - filters = {'tenant_id': request.context.tenant} - list_templates = env_temps.EnvTemplateServices.\ - get_env_templates_by(filters) list_templates = [temp.to_dict() for temp in list_templates] - return {"templates": list_templates} @request_statistics.stats_count(API_NAME, 'Create') @@ -59,24 +75,11 @@ class Controller(object): """ LOG.debug('EnvTemplates:Create '.format(body=body)) policy.check('create_env_template', request.context) + + self._validate_body_name(body) try: - LOG.debug('ENV TEMP NAME: {templ_name}>'.format( - templ_name=body['name'])) - if not str(body['name']).strip(): - msg = _('Environment Template must contain at least one ' - 'non-white space symbol') - LOG.error(msg) - raise exc.HTTPBadRequest(msg) - except Exception: - msg = _('Env template body is incorrect') - LOG.exception(msg) - raise exc.HTTPClientError(msg) - if len(body['name']) > 255: - msg = _('Environment Template name should be 255 characters ' - 'maximum') - LOG.exception(msg) - raise exc.HTTPBadRequest(explanation=msg) - try: + LOG.debug('ENV TEMP NAME: {templ_name}>'. + format(templ_name=body['name'])) template = env_temps.EnvTemplateServices.create( body.copy(), request.context.tenant) return template.to_dict() @@ -172,23 +175,15 @@ class Controller(object): :param body: the environment name :return: session_id and environment_id """ - LOG.debug('Templates:Create environment '. - format(templ_id=env_template_id)) target = {"env_template_id": env_template_id} policy.check('create_environment', request.context, target) self._validate_request(request, env_template_id) + LOG.debug('Templates:Create environment '. + format(templ_id=env_template_id)) template = env_temps.EnvTemplateServices.\ get_env_template(env_template_id) - - if ('name' not in body or not str(body['name']).strip()): - msg = _('Environment Template must contain at least one ' - 'non-white space symbol') - LOG.error(msg) - raise exc.HTTPBadRequest(explanation=msg) - LOG.debug('ENVIRONMENT NAME: {env_name}>'.format( - env_name=body['name'])) - + self._validate_body_name(body) try: environment = envs.EnvironmentServices.create( body.copy(), request.context) @@ -214,19 +209,75 @@ class Controller(object): ) return {"session_id": session.id, "environment_id": environment.id} + @request_statistics.stats_count(API_NAME, 'Clone') + def clone(self, request, env_template_id, body): + """It clones the env template from another env template + from other tenant. + :param request: the operation request. + :param env_template_id: the env template ID. + :param body: the request body. + :return: the description of the created template. + """ + + LOG.debug('EnvTemplates:Clone '. + format(body, env_template_id)) + policy.check('clone_env_template', request.context) + + old_env_template = self._validate_exists(env_template_id) + + if not old_env_template.get('is_public'): + msg = _LE('User has no access to these resources.') + LOG.error(msg) + raise exc.HTTPForbidden(explanation=msg) + self._validate_body_name(body) + LOG.debug('ENV TEMP NAME: {0}'.format(body['name'])) + + try: + is_public = body.get('is_public', False) + template = env_temps.EnvTemplateServices.clone( + env_template_id, request.context.tenant, body['name'], + is_public) + except db_exc.DBDuplicateEntry: + msg = _('Environment with specified name already exists') + LOG.error(msg) + raise exc.HTTPConflict(explanation=msg) + + return template.to_dict() + def _validate_request(self, request, env_template_id): + self._validate_exists(env_template_id) + get_env_template = env_temps.EnvTemplateServices.get_env_template + env_template = get_env_template(env_template_id) + if env_template.is_public or request.context.is_admin: + return + if env_template.tenant_id != request.context.tenant: + msg = _LE('User has no access to these resources.') + LOG.error(msg) + raise exc.HTTPForbidden(explanation=msg) + + def _validate_exists(self, env_template_id): env_template_exists = env_temps.EnvTemplateServices.env_template_exist if not env_template_exists(env_template_id): msg = _('EnvTemplate is not found').format( temp_id=env_template_id) - LOG.exception(msg) + LOG.error(msg) raise exc.HTTPNotFound(explanation=msg) get_env_template = env_temps.EnvTemplateServices.get_env_template - env_template = get_env_template(env_template_id) - if env_template.tenant_id != request.context.tenant: - msg = _('User is not authorized to access this tenant resources') - LOG.error(msg) - raise exc.HTTPForbidden(explanation=msg) + return get_env_template(env_template_id) + + def _validate_body_name(self, body): + + if not('name' in body and body['name'].strip()): + msg = _('Please, specify a name of the environment template.') + LOG.exception(msg) + raise exc.HTTPBadRequest(explanation=msg) + + name = unicode(body['name']) + if len(name) > 255: + msg = _('Environment template name should be 255 characters ' + 'maximum') + LOG.exception(msg) + raise exc.HTTPBadRequest(explanation=msg) def create_resource(): diff --git a/murano/db/migration/alembic_migrations/versions/011_add_is_public_to_template.py b/murano/db/migration/alembic_migrations/versions/011_add_is_public_to_template.py new file mode 100644 index 000000000..d473c6e61 --- /dev/null +++ b/murano/db/migration/alembic_migrations/versions/011_add_is_public_to_template.py @@ -0,0 +1,44 @@ +# 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. + +""" +Add the is_public column to the environment-template for public +environment template functionality. + +Revision ID: 011 +Revises: table template + +""" + +# revision identifiers, used by Alembic. +revision = '011' +down_revision = '010' + +from alembic import op +import sqlalchemy as sa + + +MYSQL_ENGINE = 'InnoDB' +MYSQL_CHARSET = 'utf8' + + +def upgrade(): + op.add_column('environment-template', + sa.Column('is_public', sa.Boolean(), + default=False, nullable=True)) + # end Alembic commands # + + +def downgrade(): + op.drop_column('environment-template', 'is_public') + # end Alembic commands # diff --git a/murano/db/models.py b/murano/db/models.py index efbdda738..7013e421b 100644 --- a/murano/db/models.py +++ b/murano/db/models.py @@ -97,6 +97,7 @@ class EnvironmentTemplate(Base, TimestampMixin): tenant_id = sa.Column(sa.String(36), nullable=False) version = sa.Column(sa.BigInteger, nullable=False, default=0) description = sa.Column(st.JsonBlob(), nullable=False, default={}) + is_public = sa.Column(sa.Boolean, default=False) def to_dict(self): dictionary = super(EnvironmentTemplate, self).to_dict() diff --git a/murano/db/services/environment_templates.py b/murano/db/services/environment_templates.py index 799282835..f6fc27324 100644 --- a/murano/db/services/environment_templates.py +++ b/murano/db/services/environment_templates.py @@ -19,6 +19,7 @@ from murano.db import session as db_session from oslo_db import exception as db_exc from oslo_log import log as logging +from sqlalchemy.sql import or_ LOG = logging.getLogger(__name__) @@ -38,6 +39,19 @@ class EnvTemplateServices(object): return templates + @staticmethod + def get_env_templates_or_by(filters): + """Returns list of environment-templates. + + :param filters: property filters + :return: Returns list of environment-templates + """ + unit = db_session.get_session() + templates = unit.query(models.EnvironmentTemplate). \ + filter(or_(*filters)).all() + + return templates + @staticmethod def create(env_template_params, tenant_id): """Creates environment-template with specified params, in particular - name. @@ -166,3 +180,32 @@ class EnvTemplateServices(object): """ session = db_session.get_session() return session.query(models.EnvironmentTemplate).get(env_template_id) + + @staticmethod + def clone(env_template_id, tenant_id, env_template_name, is_public): + """Clones environment-template with specified params, in particular - name. + + :param env_template_params: Dict, e.g. {'name': 'temp-name'} + :param tenant_id: Tenant Id + :return: Created Template + """ + + template = EnvTemplateServices.get_env_template(env_template_id) + env_template_params = template.to_dict() + env_template_params['id'] = uuidutils.generate_uuid() + env_template_params['tenant_id'] = tenant_id + env_template_params['name'] = env_template_name + env_template_params['is_public'] = is_public + env_temp_desc = EnvTemplateServices.get_description(env_template_id) + if "services" in env_temp_desc: + env_template_params['services'] = env_temp_desc['services'] + + env_template = models.EnvironmentTemplate() + env_template.update(env_template_params) + + unit = db_session.get_session() + with unit.begin(): + unit.add(env_template) + env_template.update({'description': env_template_params}) + env_template.save(unit) + return env_template diff --git a/murano/tests/functional/api/base.py b/murano/tests/functional/api/base.py index 5a4211d7c..461bf2d5a 100644 --- a/murano/tests/functional/api/base.py +++ b/murano/tests/functional/api/base.py @@ -245,15 +245,40 @@ class MuranoClient(rest_client.RestClient): """Check the environment templates deployed by the user.""" resp, body = self.get('v1/templates') + return resp, json.loads(body)['templates'] + + def get_public_env_templates_list(self): + """Check the public environment templates deployed by the user.""" + resp, body = self.get('v1/templates?is_public=true') + return resp, json.loads(body) + + def get_private_env_templates_list(self): + """Check the public environment templates deployed by the user.""" + resp, body = self.get('v1/templates?is_public=false') return resp, json.loads(body) def create_env_template(self, env_template_name): """Check the creation of an environment template.""" - body = {'name': env_template_name} + body = {'name': env_template_name, "is_public": False} resp, body = self.post('v1/templates', json.dumps(body)) return resp, json.loads(body) + def create_clone_env_template(self, env_template_id, + cloned_env_template_name): + """Clone an environment template.""" + body = {'name': cloned_env_template_name} + resp, body = self.post('v1/templates/{0}/clone'. + format(env_template_id), json.dumps(body)) + + return resp, json.loads(body) + + def create_public_env_template(self, env_template_name): + """Check the creation of an environment template.""" + body = {'name': env_template_name, "is_public": True} + resp, body = self.post('v1/templates', json.dumps(body)) + return resp, json.loads(body) + def create_env_template_with_apps(self, env_template_name): """Check the creation of an environment template.""" body = {'name': env_template_name} @@ -427,10 +452,34 @@ class TestCase(TestAuth): return environment def create_env_template(self, name): - env_template = self.client.create_env_template(name)[1] + resp, env_template = self.client.create_env_template(name) self.env_templates.append(env_template) + return resp, env_template - return env_template + def create_public_env_template(self, name): + resp, env_template = self.client.create_public_env_template(name) + self.env_templates.append(env_template) + return resp, env_template + + def create_env_template_with_apps(self, name): + resp, env_template = self.client.create_env_template_with_apps(name) + self.env_templates.append(env_template) + return resp, env_template + + def clone_env_template(self, env_template_id, cloned_env_template_name): + create_clone_env_temp = self.client.create_clone_env_template + resp, env_template = create_clone_env_temp(env_template_id, + cloned_env_template_name) + self.env_templates.append(env_template) + return resp, env_template + + def create_env_from_template(self, env_template_id, env_name): + resp, env_id = self.client.create_env_from_template(env_template_id, + env_name) + resp, env = self.client.get_environment(env_id['environment_id']) + + self.environments.append(env) + return resp, env def create_demo_service(self, environment_id, session_id, client=None): if not client: diff --git a/murano/tests/functional/api/v1/test_env_templates.py b/murano/tests/functional/api/v1/test_env_templates.py index 76497ec64..5f66eaad6 100644 --- a/murano/tests/functional/api/v1/test_env_templates.py +++ b/murano/tests/functional/api/v1/test_env_templates.py @@ -23,11 +23,11 @@ class TestEnvTemplate(base.TestCase): @tag('all', 'coverage') @attr(type='smoke') - def test_list_env_templates(self): + def test_list_empty_env_templates(self): """Check getting the list of environment templates.""" resp, body = self.client.get_env_templates_list() - self.assertIn('templates', body) + self.assertEqual(0, len(body)) self.assertEqual(resp.status, 200) @tag('all', 'coverage') @@ -36,23 +36,24 @@ class TestEnvTemplate(base.TestCase): """It checks the creation and deletion of an enviroment template.""" env_templates_list_start = self.client.get_env_templates_list()[1] - resp, env_template = self.client.create_env_template('test_env_temp') + resp, env_template = self.create_env_template('test_env_temp') self.env_templates.append(env_template) self.assertEqual(resp.status, 200) + self.assertFalse(env_template['is_public']) self.assertEqual('test_env_temp', env_template['name']) env_templates_list = self.client.get_env_templates_list()[1] - self.assertEqual(len(env_templates_list_start['templates']) + 1, - len(env_templates_list['templates'])) + self.assertEqual(len(env_templates_list_start) + 1, + len(env_templates_list)) self.client.delete_env_template(env_template['id']) env_templates_list = self.client.get_env_templates_list()[1] - self.assertEqual(len(env_templates_list_start['templates']), - len(env_templates_list['templates'])) + self.assertEqual(len(env_templates_list_start), + len(env_templates_list)) self.env_templates.pop(self.env_templates.index(env_template)) @@ -60,78 +61,151 @@ class TestEnvTemplate(base.TestCase): @attr(type='smoke') def test_get_env_template(self): """Check getting information about an environment template.""" - resp, env_template = self.client.create_env_template('test_env_temp') + resp, env_template = self.create_env_template('test_env_temp') resp, env_obtained_template =\ self.client.get_env_template(env_template['id']) self.assertEqual(resp.status, 200) self.assertEqual(env_obtained_template['name'], 'test_env_temp') - self.client.delete_env_template(env_template['id']) @tag('all', 'coverage') @attr(type='smoke') def test_create_env_template_with_apps(self): """Check the creation of an environment template with applications.""" resp, env_template = \ - self.client.create_env_template_with_apps('test_env_temp') + self.create_env_template_with_apps('test_env_temp') self.assertEqual(resp.status, 200) resp, apps_template = \ self.client.get_apps_in_env_template(env_template['id']) self.assertEqual(resp.status, 200) self.assertEqual(len(apps_template), 1) - self.client.delete_env_template(env_template['id']) @tag('all', 'coverage') @attr(type='smoke') def test_create_app_in_env_template(self): """Check the creationg of applications in an environment template.""" - resp, env_template = self.client.create_env_template('test_env_temp') - resp, apps = self.client.get_apps_in_env_template(env_template['id']) + resp, env_temp = self.create_env_template('test_env_temp') + self.assertEqual(resp.status, 200) + + resp, apps = self.client.get_apps_in_env_template(env_temp['id']) self.assertEqual(resp.status, 200) self.assertEqual(len(apps), 0) - resp, apps = self.client.create_app_in_env_template(env_template['id']) + resp, apps = self.client.create_app_in_env_template(env_temp['id']) self.assertEqual(resp.status, 200) - resp, apps = self.client.get_apps_in_env_template(env_template['id']) + resp, apps = self.client.get_apps_in_env_template(env_temp['id']) self.assertEqual(resp.status, 200) self.assertEqual(len(apps), 1) - self.client.delete_env_template(env_template['id']) - @tag('all', 'coverage') @attr(type='smoke') def test_delete_app_in_env_template(self): """Check the deletion of applications in an environmente template.""" - resp, env_template = self.client.create_env_template('test_env_temp') - - resp, apps = self.client.create_app_in_env_template(env_template['id']) + resp, env_temp = self.create_env_template_with_apps('test_env_temp') self.assertEqual(resp.status, 200) - resp, apps = self.client.get_apps_in_env_template(env_template['id']) + resp, apps = self.client.get_apps_in_env_template(env_temp['id']) self.assertEqual(resp.status, 200) self.assertEqual(len(apps), 1) - resp = self.client.delete_app_in_env_template(env_template['id']) + resp = self.client.delete_app_in_env_template(env_temp['id']) self.assertEqual(resp.status, 200) - resp, apps = self.client.get_apps_in_env_template(env_template['id']) + resp, apps = self.client.get_apps_in_env_template(env_temp['id']) self.assertEqual(resp.status, 200) self.assertEqual(len(apps), 0) - self.client.delete_env_template(env_template['id']) + @tag('all', 'coverage') + @attr(type='smoke') + def test_create_public_env_template(self): + """Check the creation of a public environment template.""" + resp, env_temp = self.create_public_env_template('test_env_temp') + self.assertEqual(resp.status, 200) + resp, env = self.client.get_env_template(env_temp['id']) + self.assertEqual(resp.status, 200) + self.assertTrue(env['is_public'], 200) + + @tag('all', 'coverage') + @attr(type='smoke') + def test_clone_env_template(self): + """Check the creation of a public environment template.""" + resp, env_template = self.\ + create_public_env_template('test_env_temp') + self.assertEqual(resp.status, 200) + resp, cloned_templ = self.clone_env_template(env_template['id'], + 'cloned_template') + self.assertEqual(resp.status, 200) + self.assertTrue(cloned_templ['name'], 'cloned_template') + + @tag('all', 'coverage') + @attr(type='smoke') + def test_clone_env_template_private(self): + """Check the creation of a public environment template.""" + resp, env_template = self.\ + create_env_template('test_env_temp') + self.assertEqual(resp.status, 200) + self.assertRaises(exceptions.Forbidden, + self.clone_env_template, + env_template['id'], 'cloned_template') + + @tag('all', 'coverage') + @attr(type='smoke') + def test_get_public_env_templates(self): + """Check the deletion of applications in an environmente template.""" + resp, public_env_template = \ + self.create_public_env_template('public_test_env_temp') + self.assertEqual(resp.status, 200) + self.assertEqual(public_env_template['is_public'], True) + resp, private_env_template = \ + self.create_env_template('private_test_env_temp') + self.assertEqual(resp.status, 200) + self.assertEqual(private_env_template['is_public'], False) + resp, public_envs = self.client.get_public_env_templates_list() + self.assertEqual(resp.status, 200) + self.assertEqual(len(public_envs), 1) + + @tag('all', 'coverage') + @attr(type='smoke') + def test_get_private_env_templates(self): + """Check the deletion of applications in an environmente template.""" + resp, public_env_template = \ + self.create_public_env_template('public_test_env_temp') + self.assertEqual(resp.status, 200) + self.assertEqual(public_env_template['is_public'], True) + resp, private_env_template = \ + self.create_env_template('private_test_env_temp') + self.assertEqual(resp.status, 200) + self.assertEqual(private_env_template['is_public'], False) + resp, private_envs = self.client.get_private_env_templates_list() + self.assertEqual(resp.status, 200) + self.assertEqual(len(private_envs), 1) + + @tag('all', 'coverage') + @attr(type='smoke') + def test_get_env_templates(self): + """Check the deletion of applications in an environmente template.""" + resp, public_env_template = \ + self.create_public_env_template('public_test_env_temp') + self.assertEqual(resp.status, 200) + self.assertEqual(public_env_template['is_public'], True) + resp, private_env_template = \ + self.create_env_template('private_test_env_temp') + self.assertEqual(resp.status, 200) + self.assertEqual(private_env_template['is_public'], False) + resp, envs_templates = self.client.get_env_templates_list() + self.assertEqual(resp.status, 200) + self.assertEqual(len(envs_templates), 2) @tag('all', 'coverage') @attr(type='smoke') def test_create_env_from_template(self): """Check the creation of an environment from a template.""" resp, env_template = \ - self.client.create_env_template_with_apps('test_env_temp') + self.create_env_template_with_apps('test_env_temp') self.assertEqual(resp.status, 200) - resp, env = self.client.create_env_from_template(env_template['id'], - "env") + resp, env = self.create_env_from_template(env_template['id'], + "env") self.assertEqual(resp.status, 200) - - self.client.delete_env_template(env_template['id']) - self.client.delete_environment(env['environment_id']) + self.assertIsNotNone(env) @tag('all', 'coverage') @attr(type='negative') @@ -153,7 +227,7 @@ class TestEnvTemplate(base.TestCase): @attr(type='negative') def test_double_delete_env_template(self): """Check the deletion of an wrong environment template request.""" - _, env_template = self.client.create_env_template('test_env_temp') + _, env_template = self.create_env_template('test_env_temp') self.client.delete_env_template(env_template['id']) @@ -165,7 +239,7 @@ class TestEnvTemplate(base.TestCase): @attr(type='negative') def test_get_deleted_env_template(self): """Check the deletion of an wrong environment template request.""" - _, env_template = self.client.create_env_template('test_env_temp') + _, env_template = self.create_env_template('test_env_temp') self.client.delete_env_template(env_template['id']) @@ -182,22 +256,19 @@ class TestEnvTemplatesTenantIsolation(base.NegativeTestCase): """It tests getting information from an environment template from another user. """ - env_template = self.create_env_template('test_env_temp') + _, env_template = self.create_env_template('test_env_temp') self.assertRaises(exceptions.Forbidden, self.alt_client.get_env_template, env_template['id']) - self.client.delete_env_template(env_template['id']) - @tag('all', 'coverage') @attr(type='negative') def test_delete_env_template_from_another_tenant(self): """It tests deleting information from an environment template from another user. """ - env_template = self.create_env_template('test_env_temp') + _, env_template = self.create_env_template('test_env_temp') self.assertRaises(exceptions.Forbidden, self.alt_client.delete_env_template, env_template['id']) - self.client.delete_env_template(env_template['id']) diff --git a/murano/tests/unit/api/v1/test_env_templates.py b/murano/tests/unit/api/v1/test_env_templates.py index 115fec7b3..9e5c187ed 100644 --- a/murano/tests/unit/api/v1/test_env_templates.py +++ b/murano/tests/unit/api/v1/test_env_templates.py @@ -59,6 +59,7 @@ class TestEnvTemplateApi(tb.ControllerTest, tb.MuranoApiTestCase): expected = {'tenant_id': self.tenant, 'id': 'env_template_id', + 'is_public': False, 'name': 'mytemp', 'version': 0, 'created': timeutils.isotime(fake_now)[:-1], @@ -86,6 +87,138 @@ class TestEnvTemplateApi(tb.ControllerTest, tb.MuranoApiTestCase): result = req.get_response(self.api) self.assertEqual(expected, json.loads(result.body)) + def test_list_public_env_templates(self): + """Create an template, test templates.public().""" + self._set_policy_rules( + {'create_env_template': '@', + 'list_env_templates': '@'} + ) + + self.expect_policy_check('create_env_template') + + body = {'name': 'mytemp2', 'is_public': True} + req = self._post('/templates', json.dumps(body)) + result = req.get_response(self.api) + self.assertTrue(json.loads(result.body)['is_public']) + + self.expect_policy_check('list_env_templates') + req = self._get('/templates', {'is_public': True}) + + result = req.get_response(self.api) + self.assertEqual(1, len(json.loads(result.body))) + self.assertTrue(json.loads(result.body)['templates'][0]['is_public']) + + def test_clone_env_templates(self): + """Create an template, test templates.public().""" + self._set_policy_rules( + {'create_env_template': '@', + 'clone_env_template': '@'} + ) + + self.expect_policy_check('create_env_template') + body = {'name': 'mytemp2', 'is_public': True} + req = self._post('/templates', json.dumps(body)) + result = req.get_response(self.api) + env_template_id = json.loads(result.body)['id'] + self.assertTrue(json.loads(result.body)['is_public']) + + self.expect_policy_check('clone_env_template') + body = {'name': 'clone', 'is_public': False} + req = self._post('/templates/%s/clone' % env_template_id, + json.dumps(body)) + result = req.get_response(self.api) + self.assertFalse(json.loads(result.body)['is_public']) + self.assertEqual('clone', json.loads(result.body)['name']) + + def test_clone_env_templates_private(self): + """Create an template, test templates.public().""" + self._set_policy_rules( + {'create_env_template': '@', + 'clone_env_template': '@'} + ) + + self.expect_policy_check('create_env_template') + body = {'name': 'mytemp2', 'is_public': False} + req = self._post('/templates', json.dumps(body)) + result = req.get_response(self.api) + env_template_id = json.loads(result.body)['id'] + self.assertFalse(json.loads(result.body)['is_public']) + + self.expect_policy_check('clone_env_template') + body = {'name': 'clone', 'is_public': False} + req = self._post('/templates/%s/clone' % env_template_id, + json.dumps(body)) + result = req.get_response(self.api) + self.assertEqual(result.status_code, 403) + + def test_list_public_env_templates_default(self): + """Create an template, test list public with no + public templates. + """ + self._set_policy_rules( + {'create_env_template': '@', + 'list_env_templates': '@'} + ) + + self.expect_policy_check('create_env_template') + body = {'name': 'mytemp'} + req = self._post('/templates', json.dumps(body)) + result = req.get_response(self.api) + self.assertFalse(json.loads(result.body)['is_public']) + + self.expect_policy_check('list_env_templates') + req = self._get('/templates', {'is_public': True}) + result = req.get_response(self.api) + + self.assertFalse(0, len(json.loads(result.body))) + + def test_list_private_env_templates(self): + """Create an template, test list public with no + public templates. + """ + self._set_policy_rules( + {'create_env_template': '@', + 'list_env_templates': '@'} + ) + + self.expect_policy_check('create_env_template') + body = {'name': 'mytemp', 'is_public': False} + req = self._post('/templates', json.dumps(body)) + result = req.get_response(self.api) + self.assertFalse(json.loads(result.body)['is_public']) + + self.expect_policy_check('list_env_templates') + req = self._get('/templates', {'is_public': False}) + result = req.get_response(self.api) + self.assertEqual(1, len(json.loads(result.body)['templates'])) + + def test_list_env_templates(self): + """Create an template, test list public with no + public templates. + """ + self._set_policy_rules( + {'create_env_template': '@', + 'list_env_templates': '@'} + ) + + self.expect_policy_check('create_env_template') + body = {'name': 'mytemp', 'is_public': False} + req = self._post('/templates', json.dumps(body)) + result = req.get_response(self.api) + self.assertFalse(json.loads(result.body)['is_public']) + + self.expect_policy_check('create_env_template') + body = {'name': 'mytemp1', 'is_public': True} + req = self._post('/templates', json.dumps(body)) + result = req.get_response(self.api) + self.assertTrue(json.loads(result.body)['is_public']) + + self.expect_policy_check('list_env_templates') + req = self._get('/templates') + result = req.get_response(self.api) + + self.assertEqual(2, len(json.loads(result.body)['templates'])) + def test_illegal_template_name_create(self): """Check that an illegal temp name results in an HTTPClientError.""" self._set_policy_rules( @@ -114,7 +247,7 @@ class TestEnvTemplateApi(tb.ControllerTest, tb.MuranoApiTestCase): result = req.get_response(self.api) self.assertEqual(400, result.status_code) result_msg = result.text.replace('\n', '') - self.assertIn('Environment Template name should be 255 characters ' + self.assertIn('Environment template name should be 255 characters ' 'maximum', result_msg) @@ -158,6 +291,7 @@ class TestEnvTemplateApi(tb.ControllerTest, tb.MuranoApiTestCase): expected = dict( id='12345', + is_public=False, name='my-temp', version=0, created=fake_now, @@ -242,6 +376,7 @@ class TestEnvTemplateApi(tb.ControllerTest, tb.MuranoApiTestCase): timeutils.utcnow.override_time = fake_now expected = {'tenant_id': self.tenant, 'id': self.uuids[0], + 'is_public': False, 'name': 'env_template_name', 'version': 0, 'created': timeutils.isotime(fake_now)[:-1], diff --git a/murano/tests/unit/db/services/test_templates_service.py b/murano/tests/unit/db/services/test_templates_service.py index 4cae83d2b..6861d9304 100644 --- a/murano/tests/unit/db/services/test_templates_service.py +++ b/murano/tests/unit/db/services/test_templates_service.py @@ -27,7 +27,7 @@ class TestTemplateServices(base.MuranoWithDBTestCase, def setUp(self): super(TestTemplateServices, self).setUp() self.template_services = env_temp.EnvTemplateServices - self.uuids = ['template_id'] + self.uuids = ['template_id', 'template_id2'] self.mock_uuid = self._stub_uuid(self.uuids) self.addCleanup(mock.patch.stopall) @@ -42,6 +42,18 @@ class TestTemplateServices(base.MuranoWithDBTestCase, self.assertEqual(fixture.environment_template_desc, template_des.description) + def test_clone_template(self): + """Check the clonation of a template.""" + body = { + "name": "my_template" + } + template = self.template_services.create(body, 'tenant_id') + cloned_template = self.template_services.clone(template['id'], + 'id2', + "my_template2", False) + self.assertEqual(cloned_template.description['name'], 'my_template2') + self.assertEqual(cloned_template.description['tenant_id'], 'id2') + def test_get_empty_template(self): """Check obtaining information about a template without services.""" fixture = self.useFixture(et.EmptyEnvironmentTemplateFixture()) diff --git a/releasenotes/notes/public-template-a8853ac02dcf9396.yaml b/releasenotes/notes/public-template-a8853ac02dcf9396.yaml new file mode 100644 index 000000000..b8d83e58a --- /dev/null +++ b/releasenotes/notes/public-template-a8853ac02dcf9396.yaml @@ -0,0 +1,6 @@ +--- +features: + - Added public field to environment templates. GET method + for api now displays public templates from other projects(tenants). + - Added public filter to environment templates api. + - Added clone action to environment templates.