diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 104c9a2a..66052b12 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -285,7 +285,8 @@ function configure_murano_tempest_plugin() { sudo chown -R tempest:stack $MURANO_DIR/murano_tempest_tests fi if is_service_enabled murano-cfapi; then - # Enable Service Broker tests if cfapi enabled + # Enable Service Broker tests if cfapi enabled and set murano-cfapi service availability flag + iniset $TEMPEST_CONFIG service_available murano_cfapi "True" iniset $TEMPEST_CONFIG service_broker run_service_broker_tests "True" fi fi diff --git a/murano_tempest_tests/clients.py b/murano_tempest_tests/clients.py index 6639f68b..9e97ab41 100644 --- a/murano_tempest_tests/clients.py +++ b/murano_tempest_tests/clients.py @@ -22,7 +22,10 @@ from murano_tempest_tests.services.service_broker import service_broker_client class Manager(clients.Manager): - def __init__(self, credentials=None, service=None): + def __init__(self, + credentials=common_creds.get_configured_credentials( + 'identity_admin'), + service=None): super(Manager, self).__init__(credentials, service) self.service_broker_client = service_broker_client.ServiceBrokerClient( self.auth_provider) @@ -35,10 +38,3 @@ class AltManager(Manager): def __init__(self, service=None): super(AltManager, self).__init__( common_creds.get_configured_credentials('alt_user'), service) - - -class AdminManager(Manager): - def __init__(self, service=None): - super(AdminManager, self).__init__( - common_creds.get_configured_credentials('identity_admin'), - service) diff --git a/murano_tempest_tests/config.py b/murano_tempest_tests/config.py index e3845198..c4f86a13 100644 --- a/murano_tempest_tests/config.py +++ b/murano_tempest_tests/config.py @@ -22,6 +22,10 @@ ServiceAvailableGroup = [ cfg.BoolOpt("murano", default=True, help="Whether or not murano is expected to be available"), + cfg.BoolOpt("murano_cfapi", + default=False, + help="Whether or not murano-cfapi is expected to be " + "unavailable by default") ] application_catalog_group = cfg.OptGroup(name="application_catalog", diff --git a/murano_tempest_tests/extras/MockApp/UI/ui.yaml b/murano_tempest_tests/extras/MockApp/UI/ui.yaml new file mode 100644 index 00000000..e7a77577 --- /dev/null +++ b/murano_tempest_tests/extras/MockApp/UI/ui.yaml @@ -0,0 +1,22 @@ +# 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. + +Version: 2.2 + +Forms: + - appConfiguration: + fields: + - name: license + type: string + description: Apache License, Version 2.0 + hidden: false + required: false \ No newline at end of file diff --git a/murano_tempest_tests/extras/MockApp/logo.png b/murano_tempest_tests/extras/MockApp/logo.png new file mode 100644 index 00000000..ab7ca2c4 Binary files /dev/null and b/murano_tempest_tests/extras/MockApp/logo.png differ diff --git a/murano_tempest_tests/services/application_catalog/application_catalog_client.py b/murano_tempest_tests/services/application_catalog/application_catalog_client.py index eafa3bd4..ac14d449 100644 --- a/murano_tempest_tests/services/application_catalog/application_catalog_client.py +++ b/murano_tempest_tests/services/application_catalog/application_catalog_client.py @@ -32,9 +32,10 @@ class ApplicationCatalogClient(rest_client.RestClient): CONF.application_catalog.catalog_type, CONF.identity.region, endpoint_type=CONF.application_catalog.endpoint_type) - self.build_interval = CONF.service_broker.build_interval - self.build_timeout = CONF.service_broker.build_timeout + self.build_interval = CONF.application_catalog.build_interval + self.build_timeout = CONF.application_catalog.build_timeout +# -----------------------------Packages methods-------------------------------- def upload_package(self, package_name, package_path, top_dir, body): """Upload a Murano package into Murano repository @@ -45,7 +46,6 @@ class ApplicationCatalogClient(rest_client.RestClient): :return: """ headers = {'X-Auth-Token': self.auth_provider.get_token()} - files = open(os.path.join(top_dir, package_path), 'rb') uri = "/v1/catalog/packages" post_body = {'JsonString': json.dumps(body)} @@ -62,9 +62,7 @@ class ApplicationCatalogClient(rest_client.RestClient): } uri = 'v1/catalog/packages/{0}'.format(package_id) - resp, body = self.patch(uri, json.dumps(post_body), headers=headers) - self.expected_success(200, resp.status) return self._parse_resp(body) @@ -97,3 +95,21 @@ class ApplicationCatalogClient(rest_client.RestClient): resp, body = self.get(uri, headers=headers) self.expected_success(200, resp.status) return self._parse_resp(body) + + def get_ui_definition(self, package_id): + headers = { + 'content-type': 'application/octet-stream' + } + uri = 'v1/catalog/packages/{0}/ui'.format(package_id) + resp, body = self.get(uri, headers=headers) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_logo(self, package_id): + headers = { + 'content-type': 'application/octet-stream' + } + uri = 'v1/catalog/packages/{0}/ui'.format(package_id) + resp, body = self.get(uri, headers=headers) + self.expected_success(200, resp.status) + return self._parse_resp(body) diff --git a/murano_tempest_tests/tests/api/application_catalog/__init__.py b/murano_tempest_tests/tests/api/application_catalog/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/murano_tempest_tests/tests/api/application_catalog/base.py b/murano_tempest_tests/tests/api/application_catalog/base.py new file mode 100644 index 00000000..1ead4930 --- /dev/null +++ b/murano_tempest_tests/tests/api/application_catalog/base.py @@ -0,0 +1,110 @@ +# Copyright (c) 2016 Mirantis, Inc. +# All Rights Reserved. +# +# 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 tempest.common import credentials_factory as common_creds +from tempest.common import dynamic_creds +from tempest import config +from tempest import test + +from murano_tempest_tests import clients + +CONF = config.CONF + + +class BaseApplicationCatalogTest(test.BaseTestCase): + """Base test class for Murano Service Broker API tests.""" + + @classmethod + def get_client_with_isolated_creds(cls, type_of_creds="admin"): + + creds = cls.get_configured_isolated_creds(type_of_creds=type_of_creds) + + os = clients.Manager(credentials=creds) + client = os.application_catalog_client + + return client + + @classmethod + def get_configured_isolated_creds(cls, type_of_creds='admin'): + + identity_version = cls.get_identity_version() + if identity_version == 'v3': + cls.admin_role = CONF.identity.admin_role + else: + cls.admin_role = 'admin' + cls.dynamic_cred = dynamic_creds.DynamicCredentialProvider( + identity_version=CONF.application_catalog.identity_version, + name=cls.__name__, admin_role=cls.admin_role, + admin_creds=common_creds.get_configured_credentials( + 'identity_admin')) + if type_of_creds == 'primary': + creds = cls.dynamic_cred.get_primary_creds() + elif type_of_creds == 'admin': + creds = cls.dynamic_cred.get_admin_creds() + elif type_of_creds == 'alt': + creds = cls.dynamic_cred.get_alt_creds() + else: + creds = cls.dynamic_cred.get_credentials(type_of_creds) + cls.dynamic_cred.type_of_creds = type_of_creds + + return creds + + @classmethod + def verify_nonempty(cls, *args): + if not all(args): + msg = "Missing API credentials in configuration." + raise cls.skipException(msg) + + @classmethod + def resource_setup(cls): + if not CONF.service_available.murano: + skip_msg = "Murano is disabled" + raise cls.skipException(skip_msg) + super(BaseApplicationCatalogTest, cls).resource_setup() + if not hasattr(cls, "os"): + creds = cls.get_configured_isolated_creds(type_of_creds='primary') + cls.os = clients.Manager(credentials=creds) + cls.application_catalog_client = cls.os.application_catalog_client + + def setUp(self): + super(BaseApplicationCatalogTest, self).setUp() + self.addCleanup(self.clear_isolated_creds) + + @classmethod + def resource_cleanup(cls): + super(BaseApplicationCatalogTest, cls).resource_cleanup() + cls.clear_isolated_creds() + + @classmethod + def clear_isolated_creds(cls): + if hasattr(cls, "dynamic_cred"): + cls.dynamic_cred.clear_creds() + + +class BaseApplicationCatalogAdminTest(BaseApplicationCatalogTest): + + @classmethod + def resource_setup(cls): + cls.os = clients.Manager() + super(BaseApplicationCatalogAdminTest, cls).resource_setup() + + +class BaseApplicationCatalogIsolatedAdminTest(BaseApplicationCatalogTest): + + @classmethod + def resource_setup(cls): + creds = cls.get_configured_isolated_creds(type_of_creds='admin') + cls.os = clients.Manager(credentials=creds) + super(BaseApplicationCatalogIsolatedAdminTest, cls).resource_setup() diff --git a/murano_tempest_tests/tests/api/application_catalog/test_repository.py b/murano_tempest_tests/tests/api/application_catalog/test_repository.py new file mode 100644 index 00000000..07e163bf --- /dev/null +++ b/murano_tempest_tests/tests/api/application_catalog/test_repository.py @@ -0,0 +1,171 @@ +# 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. + +import os + +from tempest.test import attr + +from murano_tempest_tests.tests.api.application_catalog import base +from murano_tempest_tests import utils + + +class TestRepositorySanity(base.BaseApplicationCatalogIsolatedAdminTest): + + @attr(type='smoke') + def test_get_list_packages(self): + package_list = self.application_catalog_client.get_list_packages() + self.assertIsInstance(package_list, list) + + @attr(type='smoke') + def test_upload_and_delete_package(self): + application_name = utils.generate_name('package_test_upload') + abs_archive_path, dir_with_archive, archive_name = \ + utils.prepare_package(application_name) + self.addCleanup(os.remove, abs_archive_path) + packages_list = self.application_catalog_client.get_list_packages() + package = self.application_catalog_client.upload_package( + application_name, archive_name, dir_with_archive, + {"categories": [], "tags": [], 'is_public': False}) + updated_packages_list = self.application_catalog_client.\ + get_list_packages() + self.assertEqual(len(packages_list) + 1, len(updated_packages_list)) + self.application_catalog_client.delete_package(package['id']) + updated_packages_list = self.application_catalog_client.\ + get_list_packages() + self.assertEqual(len(packages_list), len(updated_packages_list)) + + +class TestRepository(base.BaseApplicationCatalogAdminTest): + + @classmethod + def resource_setup(cls): + super(TestRepository, cls).resource_setup() + + application_name = utils.generate_name('test_repository_class') + cls.abs_archive_path, dir_with_archive, archive_name = \ + utils.prepare_package(application_name) + cls.package = cls.application_catalog_client.upload_package( + application_name, archive_name, dir_with_archive, + {"categories": [], "tags": [], 'is_public': False}) + + @classmethod + def resource_cleanup(cls): + super(TestRepository, cls).resource_cleanup() + os.remove(cls.abs_archive_path) + cls.application_catalog_client.delete_package(cls.package['id']) + + @attr(type='smoke') + def test_get_package(self): + package = self.application_catalog_client.get_package( + self.package['id']) + self.assertEqual(self.package['tags'], package['tags']) + + @attr(type='smoke') + def test_update_package(self): + post_body = [ + { + "op": "add", + "path": "/tags", + "value": ["im a test"] + } + ] + + result = self.application_catalog_client.update_package( + self.package['id'], post_body) + self.assertIn("im a test", result['tags']) + + post_body = [ + { + "op": "replace", + "path": "/tags", + "value": ["im bad:D"] + } + ] + + result = self.application_catalog_client.update_package( + self.package['id'], post_body) + self.assertNotIn("im a test", result['tags']) + self.assertIn("im bad:D", result['tags']) + + post_body = [ + { + "op": "remove", + "path": "/tags", + "value": ["im bad:D"] + } + ] + + result = self.application_catalog_client.update_package( + self.package['id'], post_body) + self.assertNotIn("im bad:D", result['tags']) + + post_body = [ + { + "op": "replace", + "path": "/is_public", + "value": True + } + ] + + result = self.application_catalog_client.update_package( + self.package['id'], post_body) + self.assertTrue(result['is_public']) + + post_body = [ + { + "op": "replace", + "path": "/enabled", + "value": True + } + ] + + result = self.application_catalog_client.update_package( + self.package['id'], post_body) + self.assertTrue(result['enabled']) + + post_body = [ + { + "op": "replace", + "path": "/description", + "value": "New description" + } + ] + + result = self.application_catalog_client.update_package( + self.package['id'], post_body) + self.assertEqual("New description", result['description']) + + post_body = [ + { + "op": "replace", + "path": "/name", + "value": "New name" + } + ] + + result = self.application_catalog_client.update_package( + self.package['id'], post_body) + self.assertEqual("New name", result['name']) + + @attr(type='smoke') + def test_download_package(self): + self.application_catalog_client.download_package(self.package['id']) + + @attr(type='smoke') + def test_get_ui_definitions(self): + self.application_catalog_client.get_ui_definition(self.package['id']) + + @attr(type='smoke') + def test_get_logo(self): + self.application_catalog_client.get_logo(self.package['id']) diff --git a/murano_tempest_tests/tests/api/application_catalog/test_repository_negative.py b/murano_tempest_tests/tests/api/application_catalog/test_repository_negative.py new file mode 100644 index 00000000..1a21d66d --- /dev/null +++ b/murano_tempest_tests/tests/api/application_catalog/test_repository_negative.py @@ -0,0 +1,138 @@ +# 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. + +import os + +from tempest.test import attr +from tempest_lib import exceptions + +from murano_tempest_tests.tests.api.application_catalog import base +from murano_tempest_tests import utils + + +class TestRepositoryNegativeNotFound(base.BaseApplicationCatalogAdminTest): + + @attr(type='negative') + def test_update_package_with_incorrect_id(self): + + post_body = [ + { + "op": "add", + "path": "/tags", + "value": ["im a test"] + } + ] + + self.assertRaises(exceptions.NotFound, + self.application_catalog_client.update_package, + utils.generate_uuid(), post_body) + + @attr(type='negative') + def test_get_package_with_incorrect_id(self): + self.assertRaises(exceptions.NotFound, + self.application_catalog_client.get_package, + utils.generate_uuid()) + + @attr(type='negative') + def test_delete_package_with_incorrect_id(self): + self.assertRaises(exceptions.NotFound, + self.application_catalog_client.delete_package, + utils.generate_uuid()) + + @attr(type='negative') + def test_download_package_with_incorrect_id(self): + self.assertRaises(exceptions.NotFound, + self.application_catalog_client.download_package, + utils.generate_uuid()) + + @attr(type='negative') + def test_get_ui_definition_with_incorrect_id(self): + self.assertRaises(exceptions.NotFound, + self.application_catalog_client.get_ui_definition, + utils.generate_uuid()) + + @attr(type='negative') + def test_get_logo_with_incorrect_id(self): + self.assertRaises(exceptions.NotFound, + self.application_catalog_client.get_logo, + utils.generate_uuid()) + + +class TestRepositoryNegativeForbidden(base.BaseApplicationCatalogAdminTest): + + # TODO(freerunner): I hope, that we can setup and cleanup resources + # TODO(freerunner): dramatically better. + @classmethod + def resource_setup(cls): + super(TestRepositoryNegativeForbidden, cls).resource_setup() + + application_name = utils.generate_name('package_test_upload') + cls.abs_archive_path, dir_with_archive, archive_name = \ + utils.prepare_package(application_name) + cls.package = cls.application_catalog_client.upload_package( + application_name, archive_name, dir_with_archive, + {"categories": [], "tags": [], 'is_public': False}) + cls.alt_client = cls.get_client_with_isolated_creds( + type_of_creds='alt') + + @classmethod + def resource_cleanup(cls): + super(TestRepositoryNegativeForbidden, cls).resource_cleanup() + os.remove(cls.abs_archive_path) + cls.application_catalog_client.delete_package(cls.package['id']) + + @attr(type='negative') + def test_update_package_from_another_tenant(self): + post_body = [ + { + "op": "add", + "path": "/tags", + "value": ["im a test"] + } + ] + + self.assertRaises(exceptions.Forbidden, + self.alt_client.update_package, + self.package['id'], + post_body) + + @attr(type='negative') + def test_get_package_from_another_tenant(self): + self.assertRaises(exceptions.Forbidden, + self.alt_client.get_package, + self.package['id']) + + @attr(type='negative') + def test_delete_package_from_another_tenant(self): + self.assertRaises(exceptions.Forbidden, + self.alt_client.delete_package, + self.package['id']) + + @attr(type='negative') + def test_download_package_from_another_tenant(self): + self.assertRaises(exceptions.Forbidden, + self.alt_client.download_package, + self.package['id']) + + @attr(type='negative') + def test_get_ui_definition_from_another_tenant(self): + self.assertRaises(exceptions.Forbidden, + self.alt_client.get_ui_definition, + self.package['id']) + + @attr(type='negative') + def test_get_logo_from_another_tenant(self): + self.assertRaises(exceptions.Forbidden, + self.alt_client.get_logo, + self.package['id']) diff --git a/murano_tempest_tests/tests/api/service_broker/base.py b/murano_tempest_tests/tests/api/service_broker/base.py index 61eb8ce3..cbc2c50d 100644 --- a/murano_tempest_tests/tests/api/service_broker/base.py +++ b/murano_tempest_tests/tests/api/service_broker/base.py @@ -59,7 +59,10 @@ class BaseServiceBrokerTest(test.BaseTestCase): def resource_setup(cls): if not CONF.service_broker.run_service_broker_tests: skip_msg = "Service Broker API tests are disabled" - cls.skipException(skip_msg) + raise cls.skipException(skip_msg) + if not CONF.service_available.murano_cfapi: + skip_msg = "Service Broker API is disabled" + raise cls.skipException(skip_msg) if not CONF.service_available.murano: skip_msg = "Murano is disabled" raise cls.skipException(skip_msg) @@ -127,5 +130,5 @@ class BaseServiceBrokerAdminTest(BaseServiceBrokerTest): cls.password = CONF.auth.admin_password cls.tenant_name = CONF.auth.admin_tenant_name cls.verify_nonempty(cls.username, cls.password, cls.tenant_name) - cls.os = clients.AdminManager() + cls.os = clients.Manager() super(BaseServiceBrokerAdminTest, cls).resource_setup()