From 299c09a4cd367ebbdd47c779f17ed81d67000936 Mon Sep 17 00:00:00 2001 From: IlyaMenkov Date: Tue, 11 Oct 2016 13:34:07 +0300 Subject: [PATCH] Add tempest plugin Added tempest plugin with ability to run new tempest tests for glare Change-Id: I7577ed3aa8f2bd79f57afeb6916851da9ea47f62 --- glare_tempest_plugin/__init__.py | 0 glare_tempest_plugin/clients.py | 57 +++++++++ glare_tempest_plugin/config.py | 38 ++++++ glare_tempest_plugin/plugin.py | 56 ++++++++ glare_tempest_plugin/services/__init__.py | 0 .../services/artifacts/__init__.py | 0 .../services/artifacts/artifacts_client.py | 121 ++++++++++++++++++ glare_tempest_plugin/tests/__init__.py | 0 glare_tempest_plugin/tests/api/__init__.py | 0 glare_tempest_plugin/tests/api/base.py | 81 ++++++++++++ .../tests/api/test_list_artifact.py | 40 ++++++ setup.cfg | 5 +- 12 files changed, 397 insertions(+), 1 deletion(-) create mode 100644 glare_tempest_plugin/__init__.py create mode 100644 glare_tempest_plugin/clients.py create mode 100644 glare_tempest_plugin/config.py create mode 100644 glare_tempest_plugin/plugin.py create mode 100644 glare_tempest_plugin/services/__init__.py create mode 100644 glare_tempest_plugin/services/artifacts/__init__.py create mode 100644 glare_tempest_plugin/services/artifacts/artifacts_client.py create mode 100644 glare_tempest_plugin/tests/__init__.py create mode 100644 glare_tempest_plugin/tests/api/__init__.py create mode 100644 glare_tempest_plugin/tests/api/base.py create mode 100644 glare_tempest_plugin/tests/api/test_list_artifact.py diff --git a/glare_tempest_plugin/__init__.py b/glare_tempest_plugin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/glare_tempest_plugin/clients.py b/glare_tempest_plugin/clients.py new file mode 100644 index 0000000..4949cdd --- /dev/null +++ b/glare_tempest_plugin/clients.py @@ -0,0 +1,57 @@ +# Copyright (c) 2015 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 import config +from tempest.lib import auth + + +from glare_tempest_plugin.services.artifacts import artifacts_client + +CONF = config.CONF + + +class Manager(object): + + def __init__(self, + credentials=common_creds.get_configured_admin_credentials( + 'identity_admin')): + self.auth_provider = get_auth_provider(credentials) + + self.artifacts_client = artifacts_client.ArtifactsClient( + self.auth_provider) + + +def get_auth_provider(credentials, scope='project'): + default_params = { + 'disable_ssl_certificate_validation': + CONF.identity.disable_ssl_certificate_validation, + 'ca_certs': CONF.identity.ca_certificates_file, + 'trace_requests': CONF.debug.trace_requests + } + + if isinstance(credentials, auth.KeystoneV3Credentials): + auth_provider_class, auth_url = \ + auth.KeystoneV3AuthProvider, CONF.identity.uri_v3 + else: + auth_provider_class, auth_url = \ + auth.KeystoneV2AuthProvider, CONF.identity.uri + + _auth_provider = auth_provider_class(credentials, auth_url, + scope=scope, + **default_params) + _auth_provider.set_auth() + return _auth_provider diff --git a/glare_tempest_plugin/config.py b/glare_tempest_plugin/config.py new file mode 100644 index 0000000..6732ba5 --- /dev/null +++ b/glare_tempest_plugin/config.py @@ -0,0 +1,38 @@ +# Copyright 2016 Red Hat, 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 oslo_config import cfg + +service_available_group = cfg.OptGroup(name='service_available', + title='Available OpenStack Services') + +ServiceAvailableGroup = [ + cfg.BoolOpt("glare", + default=True, + help="Whether or not glare is expected to be available") +] + +artifacts_group = cfg.OptGroup(name="artifacts", + title='Glare Options') + +ArtifactGroup = [ + cfg.StrOpt("catalog_type", + default="artifact"), + cfg.StrOpt("endpoint_type", + default="publicURL", + choices=["publicURL", "adminURL", "internalURL"], + help="The endpoint type for artifacts service") +] diff --git a/glare_tempest_plugin/plugin.py b/glare_tempest_plugin/plugin.py new file mode 100644 index 0000000..c552bf1 --- /dev/null +++ b/glare_tempest_plugin/plugin.py @@ -0,0 +1,56 @@ +# Copyright 2016 Red Hat, 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. + + +import os + + +from oslo_config import cfg +from tempest import config +from tempest.test_discover import plugins + + +from glare_tempest_plugin import config as glare_config + + +class GlareTempestPlugin(plugins.TempestPlugin): + def load_tests(self): + base_path = os.path.split(os.path.dirname( + os.path.abspath(__file__)))[0] + test_dir = "glare_tempest_plugin/tests" + full_test_dir = os.path.join(base_path, test_dir) + return full_test_dir, base_path + + def register_opts(self, conf): + try: + config.register_opt_group( + conf, glare_config.service_available_group, + glare_config.ServiceAvailableGroup + ) + except cfg.DuplicateOptError: + pass + try: + config.register_opt_group(conf, glare_config.artifacts_group, + glare_config.ArtifactGroup) + except cfg.DuplicateOptError: + pass + + def get_opt_lists(self): + return [ + (glare_config.service_available_group.name, + glare_config.ServiceAvailableGroup), + (glare_config.artifacts_group.name, + glare_config.ArtifactGroup) + ] diff --git a/glare_tempest_plugin/services/__init__.py b/glare_tempest_plugin/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/glare_tempest_plugin/services/artifacts/__init__.py b/glare_tempest_plugin/services/artifacts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/glare_tempest_plugin/services/artifacts/artifacts_client.py b/glare_tempest_plugin/services/artifacts/artifacts_client.py new file mode 100644 index 0000000..aaee70d --- /dev/null +++ b/glare_tempest_plugin/services/artifacts/artifacts_client.py @@ -0,0 +1,121 @@ +# 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. + + +import json + +from tempest import config +from tempest.lib.common import rest_client + + +CONF = config.CONF + + +class ArtifactsClient(rest_client.RestClient): + + def __init__(self, auth_provider): + super(ArtifactsClient, self).__init__( + auth_provider, + CONF.artifacts.catalog_type, + CONF.identity.region, + endpoint_type=CONF.artifacts.endpoint_type) + + def create_artifact(self, type_name, name, version='0.0.0', **kwargs): + kwargs.update({'name': name, 'version': version}) + uri = '/artifacts/{type_name}'.format(type_name=type_name) + resp, body = self.post(uri, body=json.dumps(kwargs)) + self.expected_success(201, resp.status) + parsed = self._parse_resp(body) + return parsed + + def get_artifact(self, type_name, art_id): + uri = '/artifacts/{type_name}/{id}'.format( + type_name=type_name, + id=art_id) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + parsed = self._parse_resp(body) + return parsed + + def update_artifact(self, type_name, art_id, remove_props=None, **kwargs): + headers = {'Content-Type': 'application/json-patch+json'} + uri = '/artifacts/{type_name}/{id}'.format(type_name=type_name, + id=art_id) + changes = [] + + if remove_props: + for prop_name in remove_props: + if prop_name not in kwargs: + if '/' in prop_name: + changes.append({'op': 'remove', + 'path': '/%s' % prop_name}) + else: + changes.append({'op': 'replace', + 'path': '/%s' % prop_name, + 'value': None}) + for prop_name in kwargs: + changes.append({'op': 'add', + 'path': '/%s' % prop_name, + 'value': kwargs[prop_name]}) + resp, body = self.patch(uri, json.dumps(changes), headers=headers) + self.expected_success(200, resp.status) + parsed = self._parse_resp(body) + return parsed + + def activate_artifact(self, type_name, art_id): + return self.update_artifact(type_name, art_id, status='active') + + def deactivate_artifact(self, type_name, art_id): + return self.update_artifact(type_name, art_id, status='deactivated') + + def reactivate_artifact(self, type_name, art_id): + return self.update_artifact(type_name, art_id, status='active') + + def publish_artifact(self, type_name, art_id): + return self.update_artifact(type_name, art_id, visibility='public') + + def upload_blob(self, type_name, art_id, blob_property, data): + headers = {'Content-Type': 'application/octet-stream'} + uri = '/artifacts/{type_name}/{id}/{blob_prop}'.format( + type_name=type_name, + id=art_id, + blob_prop=blob_property) + resp, body = self.put(uri, data, headers=headers) + self.expected_success(200, resp.status) + parsed = self._parse_resp(body) + return parsed + + def download_blob(self, type_name, art_id, blob_property): + uri = '/artifacts/{type_name}/{id}/{blob_prop}'.format( + type_name=type_name, + id=art_id, + blob_prop=blob_property) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + parsed = self._parse_resp(body) + return parsed + + def delete_artifact(self, type_name, art_id): + uri = '/artifacts/{type_name}/{id}'.format( + type_name=type_name, + id=art_id) + self.delete(uri) + + def list_artifacts(self, type_name): + uri = '/artifacts/{}'.format(type_name) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + parsed = self._parse_resp(body) + return parsed diff --git a/glare_tempest_plugin/tests/__init__.py b/glare_tempest_plugin/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/glare_tempest_plugin/tests/api/__init__.py b/glare_tempest_plugin/tests/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/glare_tempest_plugin/tests/api/base.py b/glare_tempest_plugin/tests/api/base.py new file mode 100644 index 0000000..f6123b3 --- /dev/null +++ b/glare_tempest_plugin/tests/api/base.py @@ -0,0 +1,81 @@ +# Copyright 2016 Red Hat, 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 glare_tempest_plugin import clients +from tempest.common import credentials_factory as common_creds +from tempest.common import dynamic_creds +from tempest import config +from tempest.lib import base + + +CONF = config.CONF + + +class BaseArtifactTest(base.BaseTestCase): + + @classmethod + def setUpClass(cls): + super(BaseArtifactTest, cls).setUpClass() + cls.resource_setup() + pass + + @classmethod + def tearDownClass(cls): + pass + + @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.artifact_client + return client + + @classmethod + def resource_setup(cls): + if not CONF.service_available.glare: + skip_msg = "Glare is disabled" + raise cls.skipException(skip_msg) + if not hasattr(cls, "os"): + creds = cls.get_configured_isolated_creds( + type_of_creds='primary') + cls.os = clients.Manager(credentials=creds) + cls.artifacts_client = cls.os.artifacts_client + + @classmethod + def get_configured_isolated_creds(cls, type_of_creds='admin'): + identity_version = CONF.identity.auth_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.identity.auth_version, + name=cls.__name__, admin_role=cls.admin_role, + admin_creds=common_creds.get_configured_admin_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.credentials diff --git a/glare_tempest_plugin/tests/api/test_list_artifact.py b/glare_tempest_plugin/tests/api/test_list_artifact.py new file mode 100644 index 0000000..4b516df --- /dev/null +++ b/glare_tempest_plugin/tests/api/test_list_artifact.py @@ -0,0 +1,40 @@ +# Copyright 2016 Red Hat, 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. + + +import testtools + + +from glare_tempest_plugin.tests.api import base +from tempest import config + + +CONF = config.CONF + + +class TestListSanity(base.BaseArtifactTest): + + @testtools.testcase.attr('smoke') + def test_list_artifacts(self): + art = self.artifacts_client.create_artifact('images', 'tempest_test') + self.artifacts_client.list_artifacts('images') + self.artifacts_client.get_artifact('images', art['id']) + self.artifacts_client.update_artifact(type_name='images', + art_id=art['id'], + name='newnewname') + data = 'dataaaa' + self.artifacts_client.upload_blob('images', art['id'], 'image', data) + self.artifacts_client.download_blob('images', art['id'], 'image') + self.artifacts_client.delete_artifact('images', art['id']) diff --git a/setup.cfg b/setup.cfg index 0074ee4..7e01c60 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,8 @@ classifier = [files] packages = glare + glare_tempest_plugin + data_files = etc/glare = etc/glare-paste.ini @@ -34,9 +36,10 @@ oslo.config.opts = glare = glare.opts:list_artifacts_opts oslo.policy.enforcer = glare = glare.common.policy:_get_enforcer - oslo.policy.policies = glare = glare.common.policy:list_rules +tempest.test_plugins = + glare_tempest_tests = glare_tempest_plugin.plugin:GlareTempestPlugin [build_sphinx] all_files = 1