diff --git a/setup.cfg b/setup.cfg index 80ace47e7..812e8309e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ classifier = [files] packages = watcher + watcher_tempest_plugin data_files = etc/ = etc/* @@ -38,6 +39,9 @@ console_scripts = watcher-decision-engine = watcher.cmd.decisionengine:main watcher-applier = watcher.cmd.applier:main +tempest.test_plugins = + watcher_tests = watcher_tempest_plugin.plugin:WatcherTempestPlugin + watcher.database.migration_backend = sqlalchemy = watcher.db.sqlalchemy.migration diff --git a/watcher/contrib/tempest/tempest/api/infra_optim/README.rst b/watcher/contrib/tempest/tempest/api/infra_optim/README.rst deleted file mode 100644 index 98db05f09..000000000 --- a/watcher/contrib/tempest/tempest/api/infra_optim/README.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. - Except where otherwise noted, this document is licensed under Creative - Commons Attribution 3.0 License. You can view the license at: - - https://creativecommons.org/licenses/by/3.0/ - -Tempest Field Guide to Infrastructure Optimization API tests -============================================================ - - -What are these tests? ---------------------- - -These tests stress the OpenStack Infrastructure Optimization API provided by -Watcher. - - -Why are these tests in tempest? ------------------------------- - -The purpose of these tests is to exercise the various APIs provided by Watcher -for optimizing the infrastructure. - - -Scope of these tests --------------------- - -The Infrastructure Optimization API test perform basic CRUD operations on the Watcher node -inventory. They do not actually perform placement or migration of virtual resources. It is important -to note that all Watcher API actions are admin operations meant to be used -either by cloud operators. diff --git a/watcher/contrib/tempest/tempest/api/infra_optim/admin/base.py b/watcher/contrib/tempest/tempest/api/infra_optim/admin/base.py deleted file mode 100644 index e0669af64..000000000 --- a/watcher/contrib/tempest/tempest/api/infra_optim/admin/base.py +++ /dev/null @@ -1,130 +0,0 @@ -# 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 functools - -from tempest_lib.common.utils import data_utils -from tempest_lib import exceptions as lib_exc - -from tempest import clients_infra_optim as clients -from tempest.common import credentials -from tempest import config -from tempest import test - -CONF = config.CONF - - -# Resources must be deleted in a specific order, this list -# defines the resource types to clean up, and the correct order. -RESOURCE_TYPES = ['audit_template'] -# RESOURCE_TYPES = ['action', 'action_plan', 'audit', 'audit_template'] - - -def creates(resource): - """Decorator that adds resources to the appropriate cleanup list.""" - - def decorator(f): - @functools.wraps(f) - def wrapper(cls, *args, **kwargs): - resp, body = f(cls, *args, **kwargs) - - if 'uuid' in body: - cls.created_objects[resource].add(body['uuid']) - - return resp, body - return wrapper - return decorator - - -class BaseInfraOptimTest(test.BaseTestCase): - """Base class for Infrastructure Optimization API tests.""" - - @classmethod - # def skip_checks(cls): - # super(BaseInfraOptimTest, cls).skip_checks() - # if not CONF.service_available.watcher: - # skip_msg = \ - # ('%s skipped as Watcher is not available' % cls.__name__) - # raise cls.skipException(skip_msg) - @classmethod - def setup_credentials(cls): - super(BaseInfraOptimTest, cls).setup_credentials() - if (not hasattr(cls, 'isolated_creds') or - not cls.isolated_creds.name == cls.__name__): - cls.isolated_creds = credentials.get_isolated_credentials( - name=cls.__name__, network_resources=cls.network_resources) - cls.mgr = clients.Manager(cls.isolated_creds.get_admin_creds()) - - @classmethod - def setup_clients(cls): - super(BaseInfraOptimTest, cls).setup_clients() - cls.client = cls.mgr.io_client - - @classmethod - def resource_setup(cls): - super(BaseInfraOptimTest, cls).resource_setup() - - cls.created_objects = {} - for resource in RESOURCE_TYPES: - cls.created_objects[resource] = set() - - @classmethod - def resource_cleanup(cls): - """Ensure that all created objects get destroyed.""" - - try: - for resource in RESOURCE_TYPES: - uuids = cls.created_objects[resource] - delete_method = getattr(cls.client, 'delete_%s' % resource) - for u in uuids: - delete_method(u, ignore_errors=lib_exc.NotFound) - finally: - super(BaseInfraOptimTest, cls).resource_cleanup() - - @classmethod - @creates('audit_template') - def create_audit_template(cls, description=None, expect_errors=False): - """Wrapper utility for creating test audit_template. - - :param description: A description of the audit template. - if not supplied, a random value will be generated. - :return: Created audit template. - """ - - description = description or data_utils.rand_name( - 'test-audit_template') - resp, body = cls.client.create_audit_template(description=description) - return resp, body - - @classmethod - def delete_audit_template(cls, audit_template_id): - """Deletes a audit_template having the specified UUID. - - :param uuid: The unique identifier of the audit_template. - :return: Server response. - """ - - resp, body = cls.client.delete_audit_template(audit_template_id) - - if audit_template_id in cls.created_objects['audit_template']: - cls.created_objects['audit_template'].remove(audit_template_id) - - return resp - - def validate_self_link(self, resource, uuid, link): - """Check whether the given self link formatted correctly.""" - expected_link = "{base}/{pref}/{res}/{uuid}".format( - base=self.client.base_url, - pref=self.client.uri_prefix, - res=resource, - uuid=uuid) - self.assertEqual(expected_link, link) diff --git a/watcher/contrib/tempest/tempest/cli/README.rst b/watcher/contrib/tempest/tempest/cli/README.rst deleted file mode 100644 index 25f78c90e..000000000 --- a/watcher/contrib/tempest/tempest/cli/README.rst +++ /dev/null @@ -1,56 +0,0 @@ -.. - Except where otherwise noted, this document is licensed under Creative - Commons Attribution 3.0 License. You can view the license at: - - https://creativecommons.org/licenses/by/3.0/ - -.. _cli_field_guide: - -Tempest Field Guide to CLI tests -================================ - - -What are these tests? ---------------------- -The cli tests test the various OpenStack command line interface tools -to ensure that they minimally function. The current scope is read only -operations on a cloud that are hard to test via unit tests. - - -Why are these tests in tempest? -------------------------------- -These tests exist here because it is extremely difficult to build a -functional enough environment in the python-\*client unit tests to -provide this kind of testing. Because we already put up a cloud in the -gate with devstack + tempest it was decided it was better to have -these as a side tree in tempest instead of another QA effort which -would split review time. - - -Scope of these tests --------------------- -This should stay limited to the scope of testing the cli. Functional -testing of the cloud should be elsewhere, this is about exercising the -cli code. - - -Example of a good test ----------------------- -Tests should be isolated to a single command in one of the python -clients. - -Tests should not modify the cloud. - -If a test is validating the cli for bad data, it should do it with -assertRaises. - -A reasonable example of an existing test is as follows:: - - def test_admin_list(self): - self.nova('list') - self.nova('list', params='--all-tenants 1') - self.nova('list', params='--all-tenants 0') - self.assertRaises(subprocess.CalledProcessError, - self.nova, - 'list', - params='--all-tenants bad') diff --git a/watcher/contrib/tempest/tempest/cli/__init__.py b/watcher/contrib/tempest/tempest/cli/__init__.py deleted file mode 100644 index 673320473..000000000 --- a/watcher/contrib/tempest/tempest/cli/__init__.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# 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 functools - -from tempest_lib.cli import base -from tempest_lib.cli import output_parser -import testtools - -from tempest.common import credentials -from tempest import config -from tempest import exceptions -from tempest.openstack.common import versionutils -from tempest import test - - -CONF = config.CONF - - -def check_client_version(client, version): - """Checks if the client's version is compatible with the given version - - @param client: The client to check. - @param version: The version to compare against. - @return: True if the client version is compatible with the given version - parameter, False otherwise. - """ - current_version = base.execute(client, '', params='--version', - merge_stderr=True, cli_dir=CONF.cli.cli_dir) - - if not current_version.strip(): - raise exceptions.TempestException('"%s --version" output was empty' % - client) - - return versionutils.is_compatible(version, current_version, - same_major=False) - - -def min_client_version(*args, **kwargs): - """A decorator to skip tests if the client used isn't of the right version. - - @param client: The client command to run. For python-novaclient, this is - 'nova', for python-cinderclient this is 'cinder', etc. - @param version: The minimum version required to run the CLI test. - """ - def decorator(func): - @functools.wraps(func) - def wrapper(*func_args, **func_kwargs): - if not check_client_version(kwargs['client'], kwargs['version']): - msg = "requires %s client version >= %s" % (kwargs['client'], - kwargs['version']) - raise testtools.TestCase.skipException(msg) - return func(*func_args, **func_kwargs) - return wrapper - return decorator - - -class ClientTestBase(test.BaseTestCase): - - @classmethod - def skip_checks(cls): - super(ClientTestBase, cls).skip_checks() - if not CONF.identity_feature_enabled.api_v2: - raise cls.skipException("CLI clients rely on identity v2 API, " - "which is configured as not available") - - @classmethod - def resource_setup(cls): - if not CONF.cli.enabled: - msg = "cli testing disabled" - raise cls.skipException(msg) - super(ClientTestBase, cls).resource_setup() - cls.isolated_creds = credentials.get_isolated_credentials(cls.__name__) - cls.creds = cls.isolated_creds.get_admin_creds() - - def _get_clients(self): - clients = base.CLIClient(self.creds.username, - self.creds.password, - self.creds.tenant_name, - CONF.identity.uri, CONF.cli.cli_dir) - return clients - - # TODO(mtreinish): The following code is basically copied from tempest-lib. - # The base cli test class in tempest-lib 0.0.1 doesn't work as a mixin like - # is needed here. The code below should be removed when tempest-lib - # provides a way to provide this functionality - def setUp(self): - super(ClientTestBase, self).setUp() - self.clients = self._get_clients() - self.parser = output_parser - - def assertTableStruct(self, items, field_names): - """Verify that all items has keys listed in field_names. - - :param items: items to assert are field names in the output table - :type items: list - :param field_names: field names from the output table of the cmd - :type field_names: list - """ - for item in items: - for field in field_names: - self.assertIn(field, item) - - def assertFirstLineStartsWith(self, lines, beginning): - """Verify that the first line starts with a string - - :param lines: strings for each line of output - :type lines: list - :param beginning: verify this is at the beginning of the first line - :type beginning: string - """ - self.assertTrue(lines[0].startswith(beginning), - msg=('Beginning of first line has invalid content: %s' - % lines[:3])) diff --git a/watcher/contrib/tempest/tempest/cli/simple_read_only/README.txt b/watcher/contrib/tempest/tempest/cli/simple_read_only/README.txt deleted file mode 100644 index ca5fa2f6f..000000000 --- a/watcher/contrib/tempest/tempest/cli/simple_read_only/README.txt +++ /dev/null @@ -1 +0,0 @@ -This directory consists of simple read only python client tests. diff --git a/watcher/contrib/tempest/tempest/cli/simple_read_only/infra-optim/test_watcher.py b/watcher/contrib/tempest/tempest/cli/simple_read_only/infra-optim/test_watcher.py deleted file mode 100644 index f316e1047..000000000 --- a/watcher/contrib/tempest/tempest/cli/simple_read_only/infra-optim/test_watcher.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# 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 logging -import re - -from tempest_lib import exceptions -import testtools - -from tempest import cli -from tempest import clients -from tempest import config -from tempest import test - - -CONF = config.CONF -LOG = logging.getLogger(__name__) - - -class SimpleReadOnlyCinderClientTest(cli.ClientTestBase): - """Basic, read-only tests for Cinder CLI client. - - Checks return values and output of read-only commands. - These tests do not presume any content, nor do they create - their own. They only verify the structure of output if present. - """ - - @classmethod - def resource_setup(cls): - # if not CONF.service_available.cinder: - # msg = ("%s skipped as Cinder is not available" % cls.__name__) - # raise cls.skipException(msg) - super(SimpleReadOnlyCinderClientTest, cls).resource_setup() - id_cl = clients.AdminManager().identity_client - tenant = id_cl.get_tenant_by_name(CONF.identity.admin_tenant_name) - cls.admin_tenant_id = tenant['id'] - - def cinder(self, *args, **kwargs): - return self.clients.cinder(*args, - endpoint_type=CONF.volume.endpoint_type, - **kwargs) - - @test.idempotent_id('229bc6dc-d804-4668-b753-b590caf63061') - def test_cinder_fake_action(self): - self.assertRaises(exceptions.CommandFailed, - self.cinder, - 'this-does-not-exist') - - @test.idempotent_id('77140216-14db-4fc5-a246-e2a587e9e99b') - def test_cinder_absolute_limit_list(self): - roles = self.parser.listing(self.cinder('absolute-limits')) - self.assertTableStruct(roles, ['Name', 'Value']) - - @test.idempotent_id('2206b9ce-1a36-4a0a-a129-e5afc7cee1dd') - def test_cinder_backup_list(self): - backup_list = self.parser.listing(self.cinder('backup-list')) - self.assertTableStruct(backup_list, ['ID', 'Volume ID', 'Status', - 'Name', 'Size', 'Object Count', - 'Container']) - - @test.idempotent_id('c7f50346-cd99-4e0b-953f-796ff5f47295') - def test_cinder_extra_specs_list(self): - extra_specs_list = self.parser.listing(self.cinder('extra-specs-list')) - self.assertTableStruct(extra_specs_list, ['ID', 'Name', 'extra_specs']) - - @test.idempotent_id('9de694cb-b40b-442c-a30c-5f9873e144f7') - def test_cinder_volumes_list(self): - list = self.parser.listing(self.cinder('list')) - self.assertTableStruct(list, ['ID', 'Status', 'Name', 'Size', - 'Volume Type', 'Bootable', - 'Attached to']) - self.cinder('list', params='--all-tenants 1') - self.cinder('list', params='--all-tenants 0') - self.assertRaises(exceptions.CommandFailed, - self.cinder, - 'list', - params='--all-tenants bad') - - @test.idempotent_id('56f7c15c-ee82-4f23-bbe8-ce99b66da493') - def test_cinder_quota_class_show(self): - """This CLI can accept and string as param.""" - roles = self.parser.listing(self.cinder('quota-class-show', - params='abc')) - self.assertTableStruct(roles, ['Property', 'Value']) - - @test.idempotent_id('a919a811-b7f0-47a7-b4e5-f3eb674dd200') - def test_cinder_quota_defaults(self): - """This CLI can accept and string as param.""" - roles = self.parser.listing(self.cinder('quota-defaults', - params=self.admin_tenant_id)) - self.assertTableStruct(roles, ['Property', 'Value']) - - @test.idempotent_id('18166673-ffa8-4df3-b60c-6375532288bc') - def test_cinder_quota_show(self): - """This CLI can accept and string as param.""" - roles = self.parser.listing(self.cinder('quota-show', - params=self.admin_tenant_id)) - self.assertTableStruct(roles, ['Property', 'Value']) - - @test.idempotent_id('b2c66ed9-ca96-4dc4-94cc-8083e664e516') - def test_cinder_rate_limits(self): - rate_limits = self.parser.listing(self.cinder('rate-limits')) - self.assertTableStruct(rate_limits, ['Verb', 'URI', 'Value', 'Remain', - 'Unit', 'Next_Available']) - - @test.idempotent_id('7a19955b-807c-481a-a2ee-9d76733eac28') - @testtools.skipUnless(CONF.volume_feature_enabled.snapshot, - 'Volume snapshot not available.') - def test_cinder_snapshot_list(self): - snapshot_list = self.parser.listing(self.cinder('snapshot-list')) - self.assertTableStruct(snapshot_list, ['ID', 'Volume ID', 'Status', - 'Name', 'Size']) - - @test.idempotent_id('6e54ecd9-7ba9-490d-8e3b-294b67139e73') - def test_cinder_type_list(self): - type_list = self.parser.listing(self.cinder('type-list')) - self.assertTableStruct(type_list, ['ID', 'Name']) - - @test.idempotent_id('2c363583-24a0-4980-b9cb-b50c0d241e82') - def test_cinder_list_extensions(self): - roles = self.parser.listing(self.cinder('list-extensions')) - self.assertTableStruct(roles, ['Name', 'Summary', 'Alias', 'Updated']) - - @test.idempotent_id('691bd6df-30ad-4be7-927b-a02d62aaa38a') - def test_cinder_credentials(self): - credentials = self.parser.listing(self.cinder('credentials')) - self.assertTableStruct(credentials, ['User Credentials', 'Value']) - - @test.idempotent_id('5c6d71a3-4904-4a3a-aec9-7fd4aa830e95') - def test_cinder_availability_zone_list(self): - zone_list = self.parser.listing(self.cinder('availability-zone-list')) - self.assertTableStruct(zone_list, ['Name', 'Status']) - - @test.idempotent_id('9b0fd5a6-f955-42b9-a42f-6f542a80b9a3') - def test_cinder_endpoints(self): - out = self.cinder('endpoints') - tables = self.parser.tables(out) - for table in tables: - headers = table['headers'] - self.assertTrue(2 >= len(headers)) - self.assertEqual('Value', headers[1]) - - @test.idempotent_id('301b5ae1-9591-4e9f-999c-d525a9bdf822') - def test_cinder_service_list(self): - service_list = self.parser.listing(self.cinder('service-list')) - self.assertTableStruct(service_list, ['Binary', 'Host', 'Zone', - 'Status', 'State', 'Updated_at']) - - @test.idempotent_id('7260ae52-b462-461e-9048-36d0bccf92c6') - def test_cinder_transfer_list(self): - transfer_list = self.parser.listing(self.cinder('transfer-list')) - self.assertTableStruct(transfer_list, ['ID', 'Volume ID', 'Name']) - - @test.idempotent_id('0976dea8-14f3-45a9-8495-3617fc4fbb13') - def test_cinder_bash_completion(self): - self.cinder('bash-completion') - - @test.idempotent_id('b7c00361-be80-4512-8735-5f98fc54f2a9') - def test_cinder_qos_list(self): - qos_list = self.parser.listing(self.cinder('qos-list')) - self.assertTableStruct(qos_list, ['ID', 'Name', 'Consumer', 'specs']) - - @test.idempotent_id('2e92dc6e-22b5-4d94-abfc-b543b0c50a89') - def test_cinder_encryption_type_list(self): - encrypt_list = self.parser.listing(self.cinder('encryption-type-list')) - self.assertTableStruct(encrypt_list, ['Volume Type ID', 'Provider', - 'Cipher', 'Key Size', - 'Control Location']) - - @test.idempotent_id('0ee6cb4c-8de6-4811-a7be-7f4bb75b80cc') - def test_admin_help(self): - help_text = self.cinder('help') - lines = help_text.split('\n') - self.assertFirstLineStartsWith(lines, 'usage: cinder') - - commands = [] - cmds_start = lines.index('Positional arguments:') - cmds_end = lines.index('Optional arguments:') - command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') - for line in lines[cmds_start:cmds_end]: - match = command_pattern.match(line) - if match: - commands.append(match.group(1)) - commands = set(commands) - wanted_commands = set(('absolute-limits', 'list', 'help', - 'quota-show', 'type-list', 'snapshot-list')) - self.assertFalse(wanted_commands - commands) - - # Optional arguments: - - @test.idempotent_id('2fd6f530-183c-4bda-8918-1e59e36c26b9') - def test_cinder_version(self): - self.cinder('', flags='--version') - - @test.idempotent_id('306bac51-c443-4426-a6cf-583a953fcd68') - def test_cinder_debug_list(self): - self.cinder('list', flags='--debug') - - @test.idempotent_id('6d97fcd2-5dd1-429d-af70-030c949d86cd') - def test_cinder_retries_list(self): - self.cinder('list', flags='--retries 3') - - @test.idempotent_id('95a2850c-35b4-4159-bb93-51647a5ad232') - def test_cinder_region_list(self): - region = CONF.volume.region - if not region: - region = CONF.identity.region - self.cinder('list', flags='--os-region-name ' + region) diff --git a/watcher/contrib/tempest/tempest/clients_infra_optim.py b/watcher/contrib/tempest/tempest/clients_infra_optim.py deleted file mode 100644 index f71de1cab..000000000 --- a/watcher/contrib/tempest/tempest/clients_infra_optim.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2014 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 import clients -from tempest.common import cred_provider -from tempest import config -from tempest.services.infra_optim.v1.json import infra_optim_client as ioc - -CONF = config.CONF - - -class Manager(clients.Manager): - def __init__(self, credentials=None, service=None): - super(Manager, self).__init__(credentials, service) - self.io_client = ioc.InfraOptimClientJSON(self.auth_provider, - 'infra-optim', - CONF.identity.region) - - -class AltManager(Manager): - def __init__(self, service=None): - super(AltManager, self).__init__( - cred_provider.get_configured_credentials('alt_user'), service) - - -class AdminManager(Manager): - def __init__(self, service=None): - super(AdminManager, self).__init__( - cred_provider.get_configured_credentials('identity_admin'), - service) diff --git a/watcher/contrib/tempest/tempest/config_infra_optim.py b/watcher/contrib/tempest/tempest/config_infra_optim.py deleted file mode 100644 index ac5787e48..000000000 --- a/watcher/contrib/tempest/tempest/config_infra_optim.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2014 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 __future__ import print_function - -from oslo_config import cfg - -from tempest import config # noqa - -service_available_group = cfg.OptGroup(name="service_available", - title="Available OpenStack Services") - -ServiceAvailableGroup = [ - cfg.BoolOpt("watcher", - default=True, - help="Whether or not watcher is expected to be available"), -] - - -class TempestConfigProxyWatcher(object): - """Wrapper over standard Tempest config that sets Watcher opts.""" - - def __init__(self): - self._config = config.CONF - config.register_opt_group( - cfg.CONF, service_available_group, ServiceAvailableGroup) - self._config.share = cfg.CONF.share - - def __getattr__(self, attr): - return getattr(self._config, attr) - - -CONF = TempestConfigProxyWatcher() diff --git a/watcher/contrib/tempest/tempest/services/infra_optim/v1/json/infra_optim_client.py b/watcher/contrib/tempest/tempest/services/infra_optim/v1/json/infra_optim_client.py deleted file mode 100644 index aacf23c17..000000000 --- a/watcher/contrib/tempest/tempest/services/infra_optim/v1/json/infra_optim_client.py +++ /dev/null @@ -1,142 +0,0 @@ -# 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.services.infra_optim import base - - -class InfraOptimClientJSON(base.InfraOptimClient): - """Base Tempest REST client for Watcher API v1.""" - version = '1' - uri_prefix = 'v1' - - # Audit Template - - @base.handle_errors - def list_audit_templates(self, **kwargs): - """List all existing audit templates.""" - return self._list_request('audit_templates', **kwargs) - - @base.handle_errors - def list_audit_template_audits(self, audit_template_uuid): - """Lists all audits associated with a audit template.""" - return self._list_request( - '/audit_templates/%s/audits' % audit_template_uuid) - - @base.handle_errors - def list_audit_templates_detail(self, **kwargs): - """Lists details of all existing audit templates.""" - return self._list_request('/audit_templates/detail', **kwargs) - - @base.handle_errors - def show_audit_template(self, uuid): - """Gets a specific audit template. - - :param uuid: Unique identifier of the audit template in UUID format. - :return: Serialized audit template as a dictionary. - """ - - return self._show_request('audit_templates', uuid) - - @base.handle_errors - def show_audit_template_by_host_agregate(self, host_agregate_id): - """Gets an audit template associated with given host agregate ID. - - :param uuid: Unique identifier of the audit_template in UUID format. - :return: Serialized audit_template as a dictionary. - """ - - uri = '/audit_templates/detail?host_agregate=%s' % host_agregate_id - - return self._show_request('audit_templates', uuid=None, uri=uri) - - @base.handle_errors - def show_audit_template_by_goal(self, goal): - """Gets an audit template associated with given goal. - - :param uuid: Unique identifier of the audit_template in UUID format. - :return: Serialized audit_template as a dictionary. - """ - - uri = '/audit_templates/detail?goal=%s' % goal - - return self._show_request('audit_templates', uuid=None, uri=uri) - - @base.handle_errors - def create_audit_template(self, **kwargs): - """Creates an audit template with the specified parameters. - - :param name: The name of the audit template. Default: My Audit Template - :param description: The description of the audit template. - Default: AT Description - :param goal: The goal associated within the audit template. - Default: SERVERS_CONSOLIDATION - :param host_aggregate: ID of the host aggregate targeted by - this audit template. Default: 1 - :param extra: IMetadata associated to this audit template. - Default: {} - :return: A tuple with the server response and the created audit - template. - """ - - audit_template = { - 'name': kwargs.get('name', 'My Audit Template'), - 'description': kwargs.get('description', 'AT Description'), - 'goal': kwargs.get('goal', 'SERVERS_CONSOLIDATION'), - 'host_aggregate': kwargs.get('host_aggregate', 1), - 'extra': kwargs.get('extra', {}), - } - - return self._create_request('audit_templates', audit_template) - - # @base.handle_errors - # def create_audit(self, audit_template_id=None, **kwargs): - # """ - # Create a infra_optim audit with the specified parameters. - - # :param cpu_arch: CPU architecture of the audit. Default: x86_64. - # :param cpus: Number of CPUs. Default: 8. - # :param local_gb: Disk size. Default: 1024. - # :param memory_mb: Available RAM. Default: 4096. - # :param driver: Driver name. Default: "fake" - # :return: A tuple with the server response and the created audit. - - # """ - # audit = {'audit_template_uuid': audit_template_id, - # 'properties': {'cpu_arch': kwargs.get('cpu_arch', 'x86_64'), - # 'cpus': kwargs.get('cpus', 8), - # 'local_gb': kwargs.get('local_gb', 1024), - # 'memory_mb': kwargs.get('memory_mb', 4096)}, - # 'driver': kwargs.get('driver', 'fake')} - - # return self._create_request('audits', audit) - - @base.handle_errors - def delete_audit_template(self, uuid): - """Deletes an audit template having the specified UUID. - - :param uuid: The unique identifier of the audit template. - :return: A tuple with the server response and the response body. - """ - - return self._delete_request('audit_templates', uuid) - - @base.handle_errors - def update_audit_template(self, uuid, patch): - """Update the specified audit template. - - :param uuid: The unique identifier of the audit template. - :param patch: List of dicts representing json patches. - :return: A tuple with the server response and the updated audit - template. - """ - - return self._patch_request('audit_templates', uuid, patch) diff --git a/watcher_tempest_plugin/README.rst b/watcher_tempest_plugin/README.rst new file mode 100644 index 000000000..7f91645f3 --- /dev/null +++ b/watcher_tempest_plugin/README.rst @@ -0,0 +1,133 @@ +.. + Except where otherwise noted, this document is licensed under Creative + Commons Attribution 3.0 License. You can view the license at: + + https://creativecommons.org/licenses/by/3.0/ + +.. _tempest_integration: + +============= +Tempest tests +============= + +This directory contains Tempest tests to cover Watcher project. + +The following procedure gets you started with Tempest testing but you can also +refer to the `Tempest documentation`_ for more details. + +.. _Tempest documentation: http://docs.openstack.org/developer/tempest/ + + +Tempest installation +==================== + +To install Tempest you can issue the following commands:: + + $ git clone https://github.com/openstack/tempest/ + $ cd tempest/ + $ pip install . + +The folder you are into now will be called ```` from now onwards. + +Please note that although it is fully working outside a virtual environment, it +is recommended to install within a venv. + + +Watcher Tempest testing setup +============================= + +You can now install Watcher alongside it in development mode by issuing the +following command:: + + $ pip install -e + +Then setup a local working environment (here ``watcher-cloud``) for running +Tempest for Watcher which shall contain the configuration for your OpenStack +intergration platform. + +In a virtual environment, you can do so by issuing the following command:: + + $ cd + $ tempest init watcher-cloud + +Otherwise, if you are not using a virtualenv:: + + $ cd + $ tempest init --config-dir ./etc watcher-cloud + +By default the configuration file is empty so before starting, you need to issue the following commands:: + + $ cd /watcher-cloud/etc + $ cp tempest.conf.sample tempest.conf + +At this point you need to edit the ``watcher-cloud/etc/tempest.conf`` +file as described in the `Tempest configuration guide`_. +Shown below is a minimal configuration you need to set within your +``tempest.conf`` configuration file which can get you started. + +For Keystone V3:: + + uri_v3 = http://:/v3 + admin_tenant_name = + admin_username = + admin_password = + admin_domain_name = + api_v2 = false + api_v3 = true + auth_version = v3 + +For Keystone V2:: + + uri = http://:/v2.0 + admin_tenant_name = + admin_username = + admin_password = + auth_version = v2 + + +For more information, please refer to: + +- Keystone connection: http://docs.openstack.org/developer/tempest/configuration.html#keystone-connection-info +- Dynamic Keystone Credentials: http://docs.openstack.org/developer/tempest/configuration.html#dynamic-credentials + +.. _virtual environment: http://docs.python-guide.org/en/latest/dev/virtualenvs/ +.. _Tempest configuration guide: http://docs.openstack.org/developer/tempest/configuration.html + + +Watcher Tempest tests execution +=============================== + +To list all Watcher Tempest cases, you can issue the following commands:: + + $ cd + $ testr list-tests watcher + +To run only these tests in Tempest, you can then issue these commands:: + + $ ./run_tempest.sh --config watcher-cloud/etc/tempest.conf -N -- watcher + +Or alternatively the following commands if you are:: + + $ cd /watcher-cloud + $ ../run_tempest.sh -N -- watcher + +To run a single test case, go to Tempest directory, then run with test case +name, e.g.:: + + $ cd + $ ./run_tempest.sh --config watcher-cloud/etc/tempest.conf -N \ + -- watcher_tempest_plugin.tests.api.admin.test_audit_template.TestCreateDeleteAuditTemplate.test_create_audit_template + +Alternatively, you can also run the Watcher Tempest plugin tests using tox. But +before you can do so, you need to follow the Tempest explanation on running +`tox with plugins`_. Then, run:: + + $ export TEMPEST_CONFIG_DIR=/watcher-cloud/etc/ + $ tox -eall-plugin watcher + +.. _tox with plugins: http://docs.openstack.org/developer/tempest/plugin.html#notes-for-using-plugins-with-virtualenvs + +And, to run a specific test:: + + $ export TEMPEST_CONFIG_DIR=/watcher-cloud/etc/ + $ tox -eall-plugin watcher_tempest_plugin.tests.api.admin.test_audit_template.TestCreateDeleteAuditTemplate.test_create_audit_template diff --git a/watcher/contrib/tempest/tempest/__init__.py b/watcher_tempest_plugin/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/__init__.py rename to watcher_tempest_plugin/__init__.py diff --git a/watcher_tempest_plugin/config.py b/watcher_tempest_plugin/config.py new file mode 100644 index 000000000..5d8bca1d4 --- /dev/null +++ b/watcher_tempest_plugin/config.py @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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("watcher", + default=True, + help="Whether or not watcher is expected to be available"), +] diff --git a/watcher_tempest_plugin/infra_optim_clients.py b/watcher_tempest_plugin/infra_optim_clients.py new file mode 100644 index 000000000..7f32a3702 --- /dev/null +++ b/watcher_tempest_plugin/infra_optim_clients.py @@ -0,0 +1,45 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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 abc + +import six +from tempest import clients +from tempest.common import credentials_factory as creds_factory +from tempest import config + +from watcher_tempest_plugin.services.infra_optim.v1.json import client as ioc + +CONF = config.CONF + + +@six.add_metaclass(abc.ABCMeta) +class BaseManager(clients.Manager): + + def __init__(self, credentials, service=None, api_microversions=None): + super(BaseManager, self).__init__( + credentials, service, api_microversions) + self.io_client = ioc.InfraOptimClientJSON( + self.auth_provider, 'infra-optim', CONF.identity.region) + + +class AdminManager(BaseManager): + def __init__(self, service=None, api_microversions=None): + super(AdminManager, self).__init__( + creds_factory.get_configured_credentials('identity_admin'), + service, + api_microversions + ) diff --git a/watcher_tempest_plugin/plugin.py b/watcher_tempest_plugin/plugin.py new file mode 100644 index 000000000..f2c3f8597 --- /dev/null +++ b/watcher_tempest_plugin/plugin.py @@ -0,0 +1,36 @@ +# 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 import config +from tempest.test_discover import plugins + +from watcher_tempest_plugin import config as watcher_config + + +class WatcherTempestPlugin(plugins.TempestPlugin): + def load_tests(self): + base_path = os.path.split(os.path.dirname( + os.path.abspath(__file__)))[0] + test_dir = "watcher_tempest_plugin/tests" + full_test_dir = os.path.join(base_path, test_dir) + return full_test_dir, base_path + + def register_opts(self, conf): + config.register_opt_group( + conf, watcher_config.service_available_group, + watcher_config.ServiceAvailableGroup) + + def get_opt_lists(self): + return [] diff --git a/watcher/contrib/tempest/tempest/api/infra_optim/__init__.py b/watcher_tempest_plugin/services/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/api/infra_optim/__init__.py rename to watcher_tempest_plugin/services/__init__.py diff --git a/watcher/contrib/tempest/tempest/api/infra_optim/admin/__init__.py b/watcher_tempest_plugin/services/infra_optim/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/api/infra_optim/admin/__init__.py rename to watcher_tempest_plugin/services/infra_optim/__init__.py diff --git a/watcher/contrib/tempest/tempest/services/infra_optim/base.py b/watcher_tempest_plugin/services/infra_optim/base.py similarity index 85% rename from watcher/contrib/tempest/tempest/services/infra_optim/base.py rename to watcher_tempest_plugin/services/infra_optim/base.py index b8f161135..1b67eaa2d 100644 --- a/watcher/contrib/tempest/tempest/services/infra_optim/base.py +++ b/watcher_tempest_plugin/services/infra_optim/base.py @@ -1,18 +1,23 @@ -# 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 +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com # -# http://www.apache.org/licenses/LICENSE-2.0 +# 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 # -# 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. +# 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 abc import functools -import json +import six import six.moves.urllib.parse as urlparse from tempest.common import service_client @@ -38,20 +43,21 @@ def handle_errors(f): return wrapper -class InfraOptimClient(service_client.ServiceClient): +@six.add_metaclass(abc.ABCMeta) +class BaseInfraOptimClient(service_client.ServiceClient): """Base Tempest REST client for Watcher API.""" - uri_prefix = '' + URI_PREFIX = '' + @abc.abstractmethod def serialize(self, object_dict): """Serialize an Watcher object.""" + raise NotImplementedError() - return json.dumps(object_dict) - + @abc.abstractmethod def deserialize(self, object_str): """Deserialize an Watcher object.""" - - return json.loads(object_str) + raise NotImplementedError() def _get_uri(self, resource_name, uuid=None, permanent=False): """Get URI for a specific resource or object. @@ -61,7 +67,7 @@ class InfraOptimClient(service_client.ServiceClient): :return: Relative URI for the resource or object. """ - prefix = self.uri_prefix if not permanent else '' + prefix = self.URI_PREFIX if not permanent else '' return '{pref}/{res}{uuid}'.format(pref=prefix, res=resource_name, @@ -172,7 +178,7 @@ class InfraOptimClient(service_client.ServiceClient): """ uri = self._get_uri(resource, uuid) - patch_body = json.dumps(patch_object) + patch_body = self.serialize(patch_object) resp, body = self.patch(uri, body=patch_body) self.expected_success(200, resp['status']) @@ -198,7 +204,7 @@ class InfraOptimClient(service_client.ServiceClient): """Update specified object with JSON-patch.""" uri = self._get_uri(resource) - put_body = json.dumps(put_object) + put_body = self.serialize(put_object) resp, body = self.put(uri, body=put_body) self.expected_success(202, resp['status']) diff --git a/watcher/contrib/tempest/tempest/cli/simple_read_only/__init__.py b/watcher_tempest_plugin/services/infra_optim/v1/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/cli/simple_read_only/__init__.py rename to watcher_tempest_plugin/services/infra_optim/v1/__init__.py diff --git a/watcher/contrib/tempest/tempest/cli/simple_read_only/infra-optim/__init__.py b/watcher_tempest_plugin/services/infra_optim/v1/json/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/cli/simple_read_only/infra-optim/__init__.py rename to watcher_tempest_plugin/services/infra_optim/v1/json/__init__.py diff --git a/watcher_tempest_plugin/services/infra_optim/v1/json/client.py b/watcher_tempest_plugin/services/infra_optim/v1/json/client.py new file mode 100644 index 000000000..ebc6e222c --- /dev/null +++ b/watcher_tempest_plugin/services/infra_optim/v1/json/client.py @@ -0,0 +1,146 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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 +import uuid + +from watcher_tempest_plugin.services.infra_optim import base + + +class InfraOptimClientJSON(base.BaseInfraOptimClient): + """Base Tempest REST client for Watcher API v1.""" + + URI_PREFIX = 'v1' + + def serialize(self, object_dict): + """Serialize an Watcher object.""" + return json.dumps(object_dict) + + def deserialize(self, object_str): + """Deserialize an Watcher object.""" + return json.loads(object_str.decode('utf-8')) + + # ### AUDIT TEMPLATES ### # + + @base.handle_errors + def list_audit_templates(self, **kwargs): + """List all existing audit templates.""" + return self._list_request('audit_templates', **kwargs) + + @base.handle_errors + def list_audit_template_audits(self, audit_template_uuid): + """Lists all audits associated with a audit template.""" + return self._list_request( + '/audit_templates/%s/audits' % audit_template_uuid) + + @base.handle_errors + def list_audit_templates_detail(self, **kwargs): + """Lists details of all existing audit templates.""" + return self._list_request('/audit_templates/detail', **kwargs) + + @base.handle_errors + def show_audit_template(self, audit_template_uuid): + """Gets a specific audit template. + + :param audit_template_uuid: Unique identifier of the audit template + :return: Serialized audit template as a dictionary. + """ + return self._show_request('audit_templates', audit_template_uuid) + + @base.handle_errors + def filter_audit_template_by_host_aggregate(self, host_aggregate): + """Gets an audit template associated with given host agregate ID. + + :param host_aggregate: Unique identifier of the host aggregate + :return: Serialized audit template as a dictionary. + """ + return self._list_request('/audit_templates', + host_aggregate=host_aggregate) + + @base.handle_errors + def filter_audit_template_by_goal(self, goal): + """Gets an audit template associated with given goal. + + :param goal: goal identifier + :return: Serialized audit template as a dictionary. + """ + return self._list_request('/audit_templates', goal=goal) + + @base.handle_errors + def create_audit_template(self, **kwargs): + """Creates an audit template with the specified parameters. + + :param name: The name of the audit template. Default: My Audit Template + :param description: The description of the audit template. + Default: AT Description + :param goal: The goal associated within the audit template. + Default: DUMMY + :param host_aggregate: ID of the host aggregate targeted by + this audit template. Default: 1 + :param extra: IMetadata associated to this audit template. + Default: {} + :return: A tuple with the server response and the created audit + template. + """ + + parameters = {k: v for k, v in kwargs.items() if v is not None} + # This name is unique to avoid the DB unique constraint on names + unique_name = 'Tempest Audit Template %s' % uuid.uuid4() + + audit_template = { + 'name': parameters.get('name', unique_name), + 'description': parameters.get('description', ''), + 'goal': parameters.get('goal', 'DUMMY'), + 'host_aggregate': parameters.get('host_aggregate', 1), + 'extra': parameters.get('extra', {}), + } + + return self._create_request('audit_templates', audit_template) + + @base.handle_errors + def create_audit(self, audit_template_uuid, **kwargs): + """Create an audit with the specified parameters. + + :param audit_template_uuid: Audit template ID used by the audit + :return: A tuple with the server response and the created audit. + """ + audit = {'audit_template_uuid': audit_template_uuid} + audit.update(kwargs) + + return self._create_request('audits', audit) + + @base.handle_errors + def delete_audit_template(self, audit_template_uuid): + """Deletes an audit template having the specified UUID. + + :param audit_template_uuid: The unique identifier of the audit template + :return: A tuple with the server response and the response body. + """ + + return self._delete_request('audit_templates', audit_template_uuid) + + @base.handle_errors + def update_audit_template(self, audit_template_uuid, patch): + """Update the specified audit template. + + :param audit_template_uuid: The unique identifier of the audit template + :param patch: List of dicts representing json patches. + :return: A tuple with the server response and the updated audit + template. + """ + + return self._patch_request('audit_templates', + audit_template_uuid, patch) diff --git a/watcher/contrib/tempest/tempest/services/infra_optim/__init__.py b/watcher_tempest_plugin/tests/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/services/infra_optim/__init__.py rename to watcher_tempest_plugin/tests/__init__.py diff --git a/watcher/contrib/tempest/tempest/services/infra_optim/v1/__init__.py b/watcher_tempest_plugin/tests/api/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/services/infra_optim/v1/__init__.py rename to watcher_tempest_plugin/tests/api/__init__.py diff --git a/watcher/contrib/tempest/tempest/services/infra_optim/v1/json/__init__.py b/watcher_tempest_plugin/tests/api/admin/__init__.py similarity index 100% rename from watcher/contrib/tempest/tempest/services/infra_optim/v1/json/__init__.py rename to watcher_tempest_plugin/tests/api/admin/__init__.py diff --git a/watcher_tempest_plugin/tests/api/admin/base.py b/watcher_tempest_plugin/tests/api/admin/base.py new file mode 100644 index 000000000..c40714766 --- /dev/null +++ b/watcher_tempest_plugin/tests/api/admin/base.py @@ -0,0 +1,139 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# 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 functools + +from tempest import test +from tempest_lib.common.utils import data_utils +from tempest_lib import exceptions as lib_exc + +from watcher_tempest_plugin import infra_optim_clients as clients + +# Resources must be deleted in a specific order, this list +# defines the resource types to clean up, and the correct order. +RESOURCE_TYPES = ['audit_template'] +# RESOURCE_TYPES = ['action', 'action_plan', 'audit', 'audit_template'] + + +def creates(resource): + """Decorator that adds resources to the appropriate cleanup list.""" + + def decorator(f): + @functools.wraps(f) + def wrapper(cls, *args, **kwargs): + resp, body = f(cls, *args, **kwargs) + + if 'uuid' in body: + cls.created_objects[resource].add(body['uuid']) + + return resp, body + return wrapper + return decorator + + +class BaseInfraOptimTest(test.BaseTestCase): + """Base class for Infrastructure Optimization API tests.""" + + @classmethod + def setup_credentials(cls): + super(BaseInfraOptimTest, cls).setup_credentials() + cls.mgr = clients.AdminManager() + + @classmethod + def setup_clients(cls): + super(BaseInfraOptimTest, cls).setup_clients() + cls.client = cls.mgr.io_client + + @classmethod + def resource_setup(cls): + super(BaseInfraOptimTest, cls).resource_setup() + + cls.created_objects = {} + for resource in RESOURCE_TYPES: + cls.created_objects[resource] = set() + + @classmethod + def resource_cleanup(cls): + """Ensure that all created objects get destroyed.""" + + try: + for resource in RESOURCE_TYPES: + uuids = cls.created_objects[resource] + delete_method = getattr(cls.client, 'delete_%s' % resource) + for u in uuids: + delete_method(u, ignore_errors=lib_exc.NotFound) + finally: + super(BaseInfraOptimTest, cls).resource_cleanup() + + def validate_self_link(self, resource, uuid, link): + """Check whether the given self link formatted correctly.""" + expected_link = "{base}/{pref}/{res}/{uuid}".format( + base=self.client.base_url, + pref=self.client.URI_PREFIX, + res=resource, + uuid=uuid + ) + self.assertEqual(expected_link, link) + + def assert_expected(self, expected, actual, + keys=('created_at', 'updated_at', 'deleted_at')): + # Check if not expected keys/values exists in actual response body + for key, value in expected.items(): + if key not in keys: + self.assertIn(key, actual) + self.assertEqual(value, actual[key]) + + # ### AUDIT TEMPLATES ### # + + @classmethod + @creates('audit_template') + def create_audit_template(cls, name=None, description=None, goal=None, + host_aggregate=None, extra=None): + """Wrapper utility for creating a test audit template + + :param name: The name of the audit template. Default: My Audit Template + :param description: The description of the audit template. + Default: AT Description + :param goal: The goal associated within the audit template. + Default: DUMMY + :param host_aggregate: ID of the host aggregate targeted by + this audit template. Default: 1 + :param extra: IMetadata associated to this audit template. + Default: {} + :return: A tuple with The HTTP response and its body + """ + + description = description or data_utils.rand_name( + 'test-audit_template') + resp, body = cls.client.create_audit_template( + name=name, description=description, goal=goal, + host_aggregate=host_aggregate, extra=extra) + return resp, body + + @classmethod + def delete_audit_template(cls, uuid): + """Deletes a audit_template having the specified UUID + + :param uuid: The unique identifier of the audit template + :return: Server response + """ + + resp, body = cls.client.delete_audit_template(uuid) + + if uuid in cls.created_objects['audit_template']: + cls.created_objects['audit_template'].remove(uuid) + + return resp diff --git a/watcher/contrib/tempest/tempest/api/infra_optim/admin/test_api_discovery.py b/watcher_tempest_plugin/tests/api/admin/test_api_discovery.py similarity index 60% rename from watcher/contrib/tempest/tempest/api/infra_optim/admin/test_api_discovery.py rename to watcher_tempest_plugin/tests/api/admin/test_api_discovery.py index 106e06eec..fceacda9c 100644 --- a/watcher/contrib/tempest/tempest/api/infra_optim/admin/test_api_discovery.py +++ b/watcher_tempest_plugin/tests/api/admin/test_api_discovery.py @@ -1,18 +1,23 @@ -# 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 +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com # -# http://www.apache.org/licenses/LICENSE-2.0 +# 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 # -# 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. +# 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.api.infra_optim.admin import base from tempest import test +from watcher_tempest_plugin.tests.api.admin import base + class TestApiDiscovery(base.BaseInfraOptimTest): """Tests for API discovery features.""" diff --git a/watcher/contrib/tempest/tempest/api/infra_optim/admin/test_audit_template.py b/watcher_tempest_plugin/tests/api/admin/test_audit_template.py similarity index 73% rename from watcher/contrib/tempest/tempest/api/infra_optim/admin/test_audit_template.py rename to watcher_tempest_plugin/tests/api/admin/test_audit_template.py index c74e03c17..8cd344432 100644 --- a/watcher/contrib/tempest/tempest/api/infra_optim/admin/test_audit_template.py +++ b/watcher_tempest_plugin/tests/api/admin/test_audit_template.py @@ -1,21 +1,73 @@ -# -*- coding: utf-8 -*- -# 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 +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com # -# http://www.apache.org/licenses/LICENSE-2.0 +# 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 # -# 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. +# 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 __future__ import unicode_literals +import uuid + +from tempest import test +from tempest_lib import decorators from tempest_lib import exceptions as lib_exc -from tempest.api.infra_optim.admin import base -from tempest import test +from watcher_tempest_plugin.tests.api.admin import base + + +class TestCreateDeleteAuditTemplate(base.BaseInfraOptimTest): + """Tests on audit templates""" + + @test.attr(type='smoke') + def test_create_audit_template(self): + params = {'name': 'my at name %s' % uuid.uuid4(), + 'description': 'my at description', + 'host_aggregate': 12, + 'goal': 'DUMMY', + 'extra': {'str': 'value', 'int': 123, 'float': 0.123, + 'bool': True, 'list': [1, 2, 3], + 'dict': {'foo': 'bar'}}} + + _, body = self.create_audit_template(**params) + self.assert_expected(params, body) + + _, audit_template = self.client.show_audit_template(body['uuid']) + self.assert_expected(audit_template, body) + + @test.attr(type='smoke') + def test_create_audit_template_unicode_description(self): + # Use a unicode string for testing: + params = {'name': 'my at name %s' % uuid.uuid4(), + 'description': 'my àt déscrïptïôn', + 'host_aggregate': 12, + 'goal': 'DUMMY', + 'extra': {'foo': 'bar'}} + + _, body = self.create_audit_template(**params) + self.assert_expected(params, body) + + _, audit_template = self.client.show_audit_template(body['uuid']) + self.assert_expected(audit_template, body) + + @test.attr(type='smoke') + def test_delete_audit_template(self): + _, body = self.create_audit_template() + audit_uuid = body['uuid'] + + self.delete_audit_template(audit_uuid) + + self.assertRaises(lib_exc.NotFound, self.client.show_audit_template, + audit_uuid) class TestAuditTemplate(base.BaseInfraOptimTest): @@ -26,63 +78,30 @@ class TestAuditTemplate(base.BaseInfraOptimTest): super(TestAuditTemplate, cls).resource_setup() _, cls.audit_template = cls.create_audit_template() - def _assertExpected(self, expected, actual): - # Check if not expected keys/values exists in actual response body - for key, value in expected.items(): - if key not in ('created_at', 'updated_at', 'deleted_at'): - self.assertIn(key, actual) - self.assertEqual(value, actual[key]) - - @test.attr(type='smoke') - def test_create_audit_template(self): - params = {'name': 'my at name', - 'description': 'my at description', - 'host_aggregate': 12, - 'goal': 'A GOAL', - 'extra': {'str': 'value', 'int': 123, 'float': 0.123, - 'bool': True, 'list': [1, 2, 3], - 'dict': {'foo': 'bar'}}} - - _, body = self.create_audit_template(**params) - self._assertExpected(params, body['properties']) - - _, audit_template = self.client.show_audit_template(body['uuid']) - self._assertExpected(audit_template, body) - - @test.attr(type='smoke') - def test_create_audit_template_unicode_description(self): - # Use a unicode string for testing: - params = {'name': 'my at name', - 'description': 'my àt déscrïptïôn', - 'host_aggregate': 12, - 'goal': 'A GOAL', - 'extra': {'foo': 'bar'}} - - _, body = self.create_audit_template(**params) - self._assertExpected(params, body['properties']) - - _, audit_template = self.client.show_audit_template(body['uuid']) - self._assertExpected(audit_template, body) - @test.attr(type='smoke') def test_show_audit_template(self): _, audit_template = self.client.show_audit_template( self.audit_template['uuid']) - self._assertExpected(self.audit_template, audit_template) + self.assert_expected(self.audit_template, audit_template) + + @decorators.skip_because(bug="1510189") @test.attr(type='smoke') - def test_show_audit_template_by_goal(self): + def test_filter_audit_template_by_goal(self): _, audit_template = self.client.\ - show_audit_template_by_goal(self.audit_template['goal']) - self._assertExpected(self.audit_template, + filter_audit_template_by_goal(self.audit_template['goal']) + + self.assert_expected(self.audit_template, audit_template['audit_templates'][0]) + @decorators.skip_because(bug="1510189") @test.attr(type='smoke') - def test_show_audit_template_by_host_aggregate(self): + def test_filter_audit_template_by_host_aggregate(self): _, audit_template = self.client.\ - show_audit_template_by_host_aggregate( + filter_audit_template_by_host_aggregate( self.audit_template['host_aggregate']) - self._assertExpected(self.audit_template, + + self.assert_expected(self.audit_template, audit_template['audit_templates'][0]) @test.attr(type='smoke') @@ -106,31 +125,27 @@ class TestAuditTemplate(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_list_with_limit(self): + # We create 3 extra audit templates to exceed the limit we fix + for _ in range(3): + self.create_audit_template() + _, body = self.client.list_audit_templates(limit=3) next_marker = body['audit_templates'][-1]['uuid'] + self.assertEqual(len(body['audit_templates']), 3) self.assertIn(next_marker, body['next']) - @test.attr(type='smoke') - def test_delete_audit_template(self): - _, body = self.create_audit_template() - uuid = body['uuid'] - - self.delete_audit_template(uuid) - self.assertRaises(lib_exc.NotFound, self.client.show_audit_template, - uuid) - @test.attr(type='smoke') def test_update_audit_template_replace(self): - params = {'name': 'my at name', + params = {'name': 'my at name %s' % uuid.uuid4(), 'description': 'my at description', 'host_aggregate': 12, - 'goal': 'A GOAL', + 'goal': 'DUMMY', 'extra': {'key1': 'value1', 'key2': 'value2'}} _, body = self.create_audit_template(**params) - new_name = 'my at new name' + new_name = 'my at new name %s' % uuid.uuid4() new_description = 'my new at description' new_host_aggregate = 10 new_goal = 'A NEW GOAL' @@ -168,8 +183,8 @@ class TestAuditTemplate(base.BaseInfraOptimTest): def test_update_audit_template_remove(self): extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} description = 'my at description' - goal = 'A GOAL' - name = 'my at name' + goal = 'DUMMY' + name = 'my at name %s' % uuid.uuid4() params = {'name': name, 'description': description, 'host_aggregate': 12, @@ -199,7 +214,7 @@ class TestAuditTemplate(base.BaseInfraOptimTest): audit_template['uuid'], [{'path': '/host_aggregate', 'op': 'remove'}]) _, body = self.client.show_audit_template(audit_template['uuid']) - self.assertEqual('', body['extra']) + self.assertEqual({}, body['extra']) # Assert nothing else was changed self.assertEqual(name, body['name']) @@ -208,10 +223,10 @@ class TestAuditTemplate(base.BaseInfraOptimTest): @test.attr(type='smoke') def test_update_audit_template_add(self): - params = {'name': 'my at name', + params = {'name': 'my at name %s' % uuid.uuid4(), 'description': 'my at description', 'host_aggregate': 12, - 'goal': 'A GOAL'} + 'goal': 'DUMMY'} _, body = self.create_audit_template(**params) @@ -228,10 +243,3 @@ class TestAuditTemplate(base.BaseInfraOptimTest): _, body = self.client.show_audit_template(body['uuid']) self.assertEqual(extra, body['extra']) - - @test.attr(type='smoke') - def test_audit_template_audit_list(self): - _, audit = self.create_audit(self.audit_template['uuid']) - _, body = self.client.list_audit_template_audits( - self.audit_template['uuid']) - self.assertIn(audit['uuid'], [n['uuid'] for n in body['audits']])