From 0abc93b859d25416ed8734566269f178141c83b3 Mon Sep 17 00:00:00 2001 From: Marc Koderer Date: Wed, 15 Jul 2015 09:18:35 +0200 Subject: [PATCH] Use Tempest plugin interface Make use of the Tempest plugin interface instead of copying Manila files into Tempest. This will remove the burden to port Manila tests onto Tempest master recurrently. This ports all existing Manila Tempest test to the new structure. It uses manila_tempest_tests as new top folder for all Tempest tests. It follow the model of Heat (see [1]). [1]: https://github.com/openstack/heat/tree/master/heat_integrationtests Change-Id: Ie5ed64a6777ed1acf8dd56522c26705ae897596d Partly-implements: bp tempest-plugin-interface Depends-On: I26dd32b1de8cceeaa6dc674092efec683df71889 --- manila_tempest_tests/README.rst | 6 + manila_tempest_tests/__init__.py | 0 manila_tempest_tests/clients_share.py | 38 + manila_tempest_tests/config.py | 154 ++++ manila_tempest_tests/plugin.py | 41 + manila_tempest_tests/services/__init__.py | 0 .../services/share/__init__.py | 0 .../services/share/json/__init__.py | 0 .../services/share/json/shares_client.py | 821 ++++++++++++++++++ manila_tempest_tests/share_exceptions.py | 52 ++ manila_tempest_tests/tests/__init__.py | 0 manila_tempest_tests/tests/api/__init__.py | 0 .../tests/api/admin/__init__.py | 0 .../tests/api/admin/test_admin_actions.py | 117 +++ .../api/admin/test_admin_actions_negative.py | 167 ++++ .../tests/api/admin/test_multi_backend.py | 101 +++ .../tests/api/admin/test_quotas.py | 351 ++++++++ .../tests/api/admin/test_quotas_negative.py | 178 ++++ .../tests/api/admin/test_scheduler_stats.py | 140 +++ .../tests/api/admin/test_security_services.py | 64 ++ .../tests/api/admin/test_services.py | 96 ++ .../tests/api/admin/test_services_negative.py | 78 ++ .../tests/api/admin/test_share_manage.py | 163 ++++ .../tests/api/admin/test_share_networks.py | 97 +++ .../tests/api/admin/test_share_servers.py | 276 ++++++ .../api/admin/test_share_servers_negative.py | 108 +++ .../tests/api/admin/test_share_types.py | 160 ++++ .../api/admin/test_share_types_extra_specs.py | 111 +++ .../test_share_types_extra_specs_negative.py | 269 ++++++ .../api/admin/test_share_types_negative.py | 70 ++ .../tests/api/admin/test_shares_actions.py | 373 ++++++++ manila_tempest_tests/tests/api/base.py | 618 +++++++++++++ .../tests/api/test_extensions.py | 31 + manila_tempest_tests/tests/api/test_limits.py | 60 ++ .../tests/api/test_metadata.py | 163 ++++ .../tests/api/test_metadata_negative.py | 91 ++ manila_tempest_tests/tests/api/test_quotas.py | 55 ++ .../tests/api/test_quotas_negative.py | 41 + manila_tempest_tests/tests/api/test_rules.py | 285 ++++++ .../tests/api/test_rules_negative.py | 299 +++++++ .../api/test_scheduler_stats_negative.py | 33 + .../tests/api/test_security_services.py | 202 +++++ .../api/test_security_services_mapping.py | 70 ++ ...test_security_services_mapping_negative.py | 157 ++++ .../api/test_security_services_negative.py | 128 +++ .../tests/api/test_share_networks.py | 216 +++++ .../tests/api/test_share_networks_negative.py | 131 +++ .../tests/api/test_share_types_negative.py | 64 ++ manila_tempest_tests/tests/api/test_shares.py | 150 ++++ .../tests/api/test_shares_actions.py | 507 +++++++++++ .../tests/api/test_shares_actions_negative.py | 136 +++ .../tests/api/test_shares_negative.py | 259 ++++++ .../tests/scenario/__init__.py | 0 .../tests/scenario/manager_share.py | 184 ++++ .../tests/scenario/test_share_basic_ops.py | 218 +++++ 55 files changed, 8129 insertions(+) create mode 100644 manila_tempest_tests/README.rst create mode 100644 manila_tempest_tests/__init__.py create mode 100644 manila_tempest_tests/clients_share.py create mode 100644 manila_tempest_tests/config.py create mode 100644 manila_tempest_tests/plugin.py create mode 100644 manila_tempest_tests/services/__init__.py create mode 100644 manila_tempest_tests/services/share/__init__.py create mode 100644 manila_tempest_tests/services/share/json/__init__.py create mode 100644 manila_tempest_tests/services/share/json/shares_client.py create mode 100644 manila_tempest_tests/share_exceptions.py create mode 100644 manila_tempest_tests/tests/__init__.py create mode 100644 manila_tempest_tests/tests/api/__init__.py create mode 100644 manila_tempest_tests/tests/api/admin/__init__.py create mode 100644 manila_tempest_tests/tests/api/admin/test_admin_actions.py create mode 100644 manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_multi_backend.py create mode 100644 manila_tempest_tests/tests/api/admin/test_quotas.py create mode 100644 manila_tempest_tests/tests/api/admin/test_quotas_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_scheduler_stats.py create mode 100644 manila_tempest_tests/tests/api/admin/test_security_services.py create mode 100644 manila_tempest_tests/tests/api/admin/test_services.py create mode 100644 manila_tempest_tests/tests/api/admin/test_services_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_manage.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_networks.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_servers.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_servers_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_types.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_types_negative.py create mode 100644 manila_tempest_tests/tests/api/admin/test_shares_actions.py create mode 100644 manila_tempest_tests/tests/api/base.py create mode 100644 manila_tempest_tests/tests/api/test_extensions.py create mode 100644 manila_tempest_tests/tests/api/test_limits.py create mode 100644 manila_tempest_tests/tests/api/test_metadata.py create mode 100644 manila_tempest_tests/tests/api/test_metadata_negative.py create mode 100644 manila_tempest_tests/tests/api/test_quotas.py create mode 100644 manila_tempest_tests/tests/api/test_quotas_negative.py create mode 100644 manila_tempest_tests/tests/api/test_rules.py create mode 100644 manila_tempest_tests/tests/api/test_rules_negative.py create mode 100644 manila_tempest_tests/tests/api/test_scheduler_stats_negative.py create mode 100644 manila_tempest_tests/tests/api/test_security_services.py create mode 100644 manila_tempest_tests/tests/api/test_security_services_mapping.py create mode 100644 manila_tempest_tests/tests/api/test_security_services_mapping_negative.py create mode 100644 manila_tempest_tests/tests/api/test_security_services_negative.py create mode 100644 manila_tempest_tests/tests/api/test_share_networks.py create mode 100644 manila_tempest_tests/tests/api/test_share_networks_negative.py create mode 100644 manila_tempest_tests/tests/api/test_share_types_negative.py create mode 100644 manila_tempest_tests/tests/api/test_shares.py create mode 100644 manila_tempest_tests/tests/api/test_shares_actions.py create mode 100644 manila_tempest_tests/tests/api/test_shares_actions_negative.py create mode 100644 manila_tempest_tests/tests/api/test_shares_negative.py create mode 100644 manila_tempest_tests/tests/scenario/__init__.py create mode 100644 manila_tempest_tests/tests/scenario/manager_share.py create mode 100644 manila_tempest_tests/tests/scenario/test_share_basic_ops.py diff --git a/manila_tempest_tests/README.rst b/manila_tempest_tests/README.rst new file mode 100644 index 00000000..7569d961 --- /dev/null +++ b/manila_tempest_tests/README.rst @@ -0,0 +1,6 @@ +==================== +Tempest Integration +==================== + +This directory contains Tempest tests to cover Manila project. + diff --git a/manila_tempest_tests/__init__.py b/manila_tempest_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/clients_share.py b/manila_tempest_tests/clients_share.py new file mode 100644 index 00000000..1411286a --- /dev/null +++ b/manila_tempest_tests/clients_share.py @@ -0,0 +1,38 @@ +# 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 manila_tempest_tests.services.share.json import shares_client + + +class Manager(clients.Manager): + def __init__(self, credentials=None, service=None): + super(Manager, self).__init__(credentials, service) + self.shares_client = shares_client.SharesClient(self.auth_provider) + + +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/manila_tempest_tests/config.py b/manila_tempest_tests/config.py new file mode 100644 index 00000000..f6962ea4 --- /dev/null +++ b/manila_tempest_tests/config.py @@ -0,0 +1,154 @@ +# 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("manila", + default=True, + help="Whether or not manila is expected to be available"), +] + +share_group = cfg.OptGroup(name="share", title="Share Service Options") + +ShareGroup = [ + cfg.StrOpt("min_api_microversion", + default="1.0", + help="The minimum api microversion is configured to be the " + "value of the minimum microversion supported by Manila."), + cfg.StrOpt("max_api_microversion", + default="1.4", + help="The maximum api microversion is configured to be the " + "value of the latest microversion supported by Manila."), + cfg.StrOpt("region", + default="", + help="The share region name to use. If empty, the value " + "of identity.region is used instead. If no such region " + "is found in the service catalog, the first found one is " + "used."), + cfg.StrOpt("catalog_type", + default="share", + help="Catalog type of the Share service."), + cfg.StrOpt('endpoint_type', + default='publicURL', + choices=['public', 'admin', 'internal', + 'publicURL', 'adminURL', 'internalURL'], + help="The endpoint type to use for the share service."), + cfg.BoolOpt("multitenancy_enabled", + default=True, + help="This option used to determine backend driver type, " + "multitenant driver uses share-networks, but " + "single-tenant doesn't."), + cfg.ListOpt("enable_protocols", + default=["nfs", "cifs"], + help="First value of list is protocol by default, " + "items of list show enabled protocols at all."), + cfg.ListOpt("enable_ip_rules_for_protocols", + default=["nfs", "cifs", ], + help="Selection of protocols, that should " + "be covered with ip rule tests"), + cfg.ListOpt("enable_user_rules_for_protocols", + default=[], + help="Selection of protocols, that should " + "be covered with user rule tests"), + cfg.ListOpt("enable_cert_rules_for_protocols", + default=["glusterfs", ], + help="Protocols that should be covered with cert rule tests."), + cfg.StrOpt("username_for_user_rules", + default="Administrator", + help="Username, that will be used in user tests."), + cfg.ListOpt("enable_ro_access_level_for_protocols", + default=["nfs", ], + help="List of protocols to run tests with ro access level."), + cfg.StrOpt("storage_protocol", + default="NFS_CIFS", + help="Backend protocol to target when creating volume types."), + cfg.StrOpt("share_network_id", + default="", + help="Some backend drivers requires share network " + "for share creation. Share network id, that will be " + "used for shares. If not set, it won't be used."), + cfg.StrOpt("alt_share_network_id", + default="", + help="Share network id, that will be used for shares" + " in alt tenant. If not set, it won't be used"), + cfg.StrOpt("admin_share_network_id", + default="", + help="Share network id, that will be used for shares" + " in admin tenant. If not set, it won't be used"), + cfg.BoolOpt("multi_backend", + default=False, + help="Runs Manila multi-backend tests."), + cfg.ListOpt("backend_names", + default=[], + help="Names of share backends, that will be used with " + "multibackend tests. Tempest will use first two values."), + cfg.IntOpt("share_creation_retry_number", + default=0, + help="Defines number of retries for share creation. " + "It is useful to avoid failures caused by unstable " + "environment."), + cfg.IntOpt("build_interval", + default=3, + help="Time in seconds between share availability checks."), + cfg.IntOpt("build_timeout", + default=500, + help="Timeout in seconds to wait for a share to become" + "available."), + cfg.BoolOpt("suppress_errors_in_cleanup", + default=False, + help="Whether to suppress errors with clean up operation " + "or not. There are cases when we may want to skip " + "such errors and catch only test errors."), + cfg.BoolOpt("run_manage_unmanage_tests", + default=False, + help="Defines whether to run manage/unmanage tests or not. " + "These test may leave orphaned resources, so be careful " + "enabling this opt."), + cfg.BoolOpt("run_extend_tests", + default=True, + help="Defines whether to run share extend tests or not. " + "Disable this feature if used driver doesn't " + "support it."), + cfg.BoolOpt("run_shrink_tests", + default=True, + help="Defines whether to run share shrink tests or not. " + "Disable this feature if used driver doesn't " + "support it."), + cfg.BoolOpt("run_snapshot_tests", + default=True, + help="Defines whether to run tests that use share snapshots " + "or not. Disable this feature if used driver doesn't " + "support it."), + cfg.StrOpt("image_with_share_tools", + default="manila-service-image", + help="Image name for vm booting with nfs/smb clients tool."), + cfg.StrOpt("image_username", + default="manila", + help="Image username."), + cfg.StrOpt("image_password", + help="Image password. Should be used for " + "'image_with_share_tools' without Nova Metadata support."), + cfg.StrOpt("client_vm_flavor_ref", + default="100", + help="Flavor used for client vm in scenario tests."), +] diff --git a/manila_tempest_tests/plugin.py b/manila_tempest_tests/plugin.py new file mode 100644 index 00000000..3a8550b6 --- /dev/null +++ b/manila_tempest_tests/plugin.py @@ -0,0 +1,41 @@ +# Copyright 2015 Deutsche Telekom AG +# 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 tempest import config +from tempest.test_discover import plugins + +from manila_tempest_tests import config as config_share + + +class ManilaTempestPlugin(plugins.TempestPlugin): + def load_tests(self): + base_path = os.path.split(os.path.dirname( + os.path.abspath(__file__)))[0] + test_dir = "manila_tempest_tests/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, config_share.service_available_group, + config_share.ServiceAvailableGroup) + config.register_opt_group(conf, config_share.share_group, + config_share.ShareGroup) + + def get_opt_lists(self): + return [(config_share.share_group.name, config_share.ShareGroup)] diff --git a/manila_tempest_tests/services/__init__.py b/manila_tempest_tests/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/services/share/__init__.py b/manila_tempest_tests/services/share/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/services/share/json/__init__.py b/manila_tempest_tests/services/share/json/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/services/share/json/shares_client.py b/manila_tempest_tests/services/share/json/shares_client.py new file mode 100644 index 00000000..fcfd5eaf --- /dev/null +++ b/manila_tempest_tests/services/share/json/shares_client.py @@ -0,0 +1,821 @@ +# 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. + +import json +import time +import urllib + +import six +from tempest import config # noqa +from tempest_lib.common import rest_client +from tempest_lib.common.utils import data_utils +from tempest_lib import exceptions + +from manila_tempest_tests import share_exceptions + +CONF = config.CONF + + +class SharesClient(rest_client.RestClient): + """Tempest REST client for Manila. + + It handles shares and access to it in OpenStack. + """ + + def __init__(self, auth_provider): + super(SharesClient, self).__init__( + auth_provider, + CONF.share.catalog_type, + CONF.share.region or CONF.identity.region, + endpoint_type=CONF.share.endpoint_type) + self.share_protocol = None + if CONF.share.enable_protocols: + self.share_protocol = CONF.share.enable_protocols[0] + self.share_network_id = CONF.share.share_network_id + self.build_interval = CONF.share.build_interval + self.build_timeout = CONF.share.build_timeout + self.API_MICROVERSIONS_HEADER = 'x-openstack-manila-api-version' + + def _get_version_dict(self, version): + return {self.API_MICROVERSIONS_HEADER: version} + + def send_microversion_request(self, version=None): + """Prepare and send the HTTP GET Request to the base URL. + + Extracts the base URL from the shares_client endpoint and makes a GET + request with the microversions request header. + """ + + headers = self.get_headers() + url, headers, body = self.auth_provider.auth_request( + 'GET', 'shares', headers, None, self.filters) + url = '/'.join(url.split('/')[:3]) + '/' + if version: + headers[self.API_MICROVERSIONS_HEADER] = version + resp, resp_body = self.raw_request(url, 'GET', headers=headers) + self.response_checker('GET', resp, resp_body) + resp_body = json.loads(resp_body) + return resp, resp_body + + def create_share(self, share_protocol=None, size=1, + name=None, snapshot_id=None, description=None, + metadata=None, share_network_id=None, + share_type_id=None, is_public=False): + metadata = metadata or {} + if name is None: + name = data_utils.rand_name("tempest-created-share") + if description is None: + description = data_utils.rand_name("tempest-created-share-desc") + if share_protocol is None: + share_protocol = self.share_protocol + if share_protocol is None: + raise share_exceptions.ShareProtocolNotSpecified() + post_body = { + "share": { + "share_proto": share_protocol, + "description": description, + "snapshot_id": snapshot_id, + "name": name, + "size": size, + "metadata": metadata, + "is_public": is_public, + } + } + if share_network_id: + post_body["share"]["share_network_id"] = share_network_id + if share_type_id: + post_body["share"]["share_type"] = share_type_id + body = json.dumps(post_body) + resp, body = self.post("shares", body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_share(self, share_id): + resp, body = self.delete("shares/%s" % share_id) + self.expected_success(202, resp.status) + return body + + def manage_share(self, service_host, protocol, export_path, + share_type_id, name=None, description=None): + post_body = { + "share": { + "export_path": export_path, + "service_host": service_host, + "protocol": protocol, + "share_type": share_type_id, + "name": name, + "description": description, + } + } + body = json.dumps(post_body) + resp, body = self.post("os-share-manage", body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def unmanage_share(self, share_id): + resp, body = self.post( + "os-share-unmanage/%s/unmanage" % share_id, None) + self.expected_success(202, resp.status) + return body + + def list_shares(self, detailed=False, params=None): + """Get list of shares w/o filters.""" + uri = 'shares/detail' if detailed else 'shares' + uri += '?%s' % urllib.urlencode(params) if params else '' + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_shares_with_detail(self, params=None): + """Get detailed list of shares w/o filters.""" + return self.list_shares(detailed=True, params=params) + + def get_share(self, share_id): + resp, body = self.get("shares/%s" % share_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_instances_of_share(self, share_id): + resp, body = self.get("shares/%s/instances" % share_id, + headers=self._get_version_dict('1.4'), + extra_headers=True) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_share_instances(self): + resp, body = self.get("share_instances", + headers=self._get_version_dict('1.4'), + extra_headers=True) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_share_instance(self, instance_id): + resp, body = self.get("share_instances/%s" % instance_id, + headers=self._get_version_dict('1.4'), + extra_headers=True) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def create_access_rule(self, share_id, access_type="ip", + access_to="0.0.0.0", access_level=None): + post_body = { + "os-allow_access": { + "access_type": access_type, + "access_to": access_to, + "access_level": access_level, + } + } + body = json.dumps(post_body) + resp, body = self.post("shares/%s/action" % share_id, body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_access_rules(self, share_id): + body = {"os-access_list": None} + resp, body = self.post("shares/%s/action" % share_id, json.dumps(body)) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_access_rule(self, share_id, rule_id): + post_body = { + "os-deny_access": { + "access_id": rule_id, + } + } + body = json.dumps(post_body) + resp, body = self.post("shares/%s/action" % share_id, body) + self.expected_success(202, resp.status) + return body + + def extend_share(self, share_id, new_size): + post_body = { + "os-extend": { + "new_size": new_size, + } + } + body = json.dumps(post_body) + resp, body = self.post("shares/%s/action" % share_id, body) + self.expected_success(202, resp.status) + return body + + def shrink_share(self, share_id, new_size): + post_body = { + "os-shrink": { + "new_size": new_size, + } + } + body = json.dumps(post_body) + resp, body = self.post("shares/%s/action" % share_id, body) + self.expected_success(202, resp.status) + return body + + def create_snapshot(self, share_id, name=None, description=None, + force=False): + if name is None: + name = data_utils.rand_name("tempest-created-share-snap") + if description is None: + description = data_utils.rand_name( + "tempest-created-share-snap-desc") + post_body = { + "snapshot": { + "name": name, + "force": force, + "description": description, + "share_id": share_id, + } + } + body = json.dumps(post_body) + resp, body = self.post("snapshots", body) + self.expected_success(202, resp.status) + return self._parse_resp(body) + + def get_snapshot(self, snapshot_id): + resp, body = self.get("snapshots/%s" % snapshot_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_snapshots(self, detailed=False, params=None): + """Get list of share snapshots w/o filters.""" + uri = 'snapshots/detail' if detailed else 'snapshots' + uri += '?%s' % urllib.urlencode(params) if params else '' + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_snapshots_with_detail(self, params=None): + """Get detailed list of share snapshots w/o filters.""" + return self.list_snapshots(detailed=True, params=params) + + def delete_snapshot(self, snap_id): + resp, body = self.delete("snapshots/%s" % snap_id) + self.expected_success(202, resp.status) + return body + + def wait_for_share_status(self, share_id, status): + """Waits for a share to reach a given status.""" + body = self.get_share(share_id) + share_name = body['name'] + share_status = body['status'] + start = int(time.time()) + + while share_status != status: + time.sleep(self.build_interval) + body = self.get_share(share_id) + share_status = body['status'] + if share_status == status: + return + elif 'error' in share_status.lower(): + raise share_exceptions.\ + ShareBuildErrorException(share_id=share_id) + + if int(time.time()) - start >= self.build_timeout: + message = ('Share %s failed to reach %s status within ' + 'the required time (%s s).' % + (share_name, status, self.build_timeout)) + raise exceptions.TimeoutException(message) + + def wait_for_share_instance_status(self, instance_id, status): + """Waits for a share to reach a given status.""" + body = self.get_share_instance(instance_id) + instance_status = body['status'] + start = int(time.time()) + + while instance_status != status: + time.sleep(self.build_interval) + body = self.get_share(instance_id) + instance_status = body['status'] + if instance_status == status: + return + elif 'error' in instance_status.lower(): + raise share_exceptions.\ + ShareInstanceBuildErrorException(id=instance_id) + + if int(time.time()) - start >= self.build_timeout: + message = ('Share instance %s failed to reach %s status within' + ' the required time (%s s).' % + (instance_id, status, self.build_timeout)) + raise exceptions.TimeoutException(message) + + def wait_for_snapshot_status(self, snapshot_id, status): + """Waits for a snapshot to reach a given status.""" + body = self.get_snapshot(snapshot_id) + snapshot_name = body['name'] + snapshot_status = body['status'] + start = int(time.time()) + + while snapshot_status != status: + time.sleep(self.build_interval) + body = self.get_snapshot(snapshot_id) + snapshot_status = body['status'] + if 'error' in snapshot_status: + raise exceptions.\ + SnapshotBuildErrorException(snapshot_id=snapshot_id) + + if int(time.time()) - start >= self.build_timeout: + message = ('Share Snapshot %s failed to reach %s status ' + 'within the required time (%s s).' % + (snapshot_name, status, self.build_timeout)) + raise exceptions.TimeoutException(message) + + def wait_for_access_rule_status(self, share_id, rule_id, status): + """Waits for an access rule to reach a given status.""" + rule_status = "new" + start = int(time.time()) + while rule_status != status: + time.sleep(self.build_interval) + rules = self.list_access_rules(share_id) + for rule in rules: + if rule["id"] in rule_id: + rule_status = rule['state'] + break + if 'error' in rule_status: + raise share_exceptions.\ + AccessRuleBuildErrorException(rule_id=rule_id) + + if int(time.time()) - start >= self.build_timeout: + message = ('Share Access Rule %s failed to reach %s status ' + 'within the required time (%s s).' % + (rule_id, status, self.build_timeout)) + raise exceptions.TimeoutException(message) + + def default_quotas(self, tenant_id): + resp, body = self.get("os-quota-sets/%s/defaults" % tenant_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def show_quotas(self, tenant_id, user_id=None): + uri = "os-quota-sets/%s" % tenant_id + if user_id is not None: + uri += "?user_id=%s" % user_id + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def reset_quotas(self, tenant_id, user_id=None): + uri = "os-quota-sets/%s" % tenant_id + if user_id is not None: + uri += "?user_id=%s" % user_id + resp, body = self.delete(uri) + self.expected_success(202, resp.status) + return body + + def update_quotas(self, tenant_id, user_id=None, shares=None, + snapshots=None, gigabytes=None, snapshot_gigabytes=None, + share_networks=None, force=True): + uri = "os-quota-sets/%s" % tenant_id + if user_id is not None: + uri += "?user_id=%s" % user_id + + put_body = {"tenant_id": tenant_id} + if force: + put_body["force"] = "true" + if shares is not None: + put_body["shares"] = shares + if snapshots is not None: + put_body["snapshots"] = snapshots + if gigabytes is not None: + put_body["gigabytes"] = gigabytes + if snapshot_gigabytes is not None: + put_body["snapshot_gigabytes"] = snapshot_gigabytes + if share_networks is not None: + put_body["share_networks"] = share_networks + put_body = json.dumps({"quota_set": put_body}) + + resp, body = self.put(uri, put_body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_limits(self): + resp, body = self.get("limits") + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def is_resource_deleted(self, *args, **kwargs): + """Verifies whether provided resource deleted or not. + + :param kwargs: dict with expected keys 'share_id', 'snapshot_id', + :param kwargs: 'sn_id', 'ss_id', 'vt_id' and 'server_id' + :raises share_exceptions.InvalidResource + """ + if "share_id" in kwargs: + if "rule_id" in kwargs: + rule_id = kwargs.get("rule_id") + share_id = kwargs.get("share_id") + rules = self.list_access_rules(share_id) + for rule in rules: + if rule["id"] == rule_id: + return False + return True + else: + return self._is_resource_deleted( + self.get_share, kwargs.get("share_id")) + elif "share_instance_id" in kwargs: + return self._is_resource_deleted( + self.get_share_instance, kwargs.get("share_instance_id")) + elif "snapshot_id" in kwargs: + return self._is_resource_deleted( + self.get_snapshot, kwargs.get("snapshot_id")) + elif "sn_id" in kwargs: + return self._is_resource_deleted( + self.get_share_network, kwargs.get("sn_id")) + elif "ss_id" in kwargs: + return self._is_resource_deleted( + self.get_security_service, kwargs.get("ss_id")) + elif "vt_id" in kwargs: + return self._is_resource_deleted( + self.get_volume_type, kwargs.get("vt_id")) + elif "st_id" in kwargs: + return self._is_resource_deleted( + self.get_share_type, kwargs.get("st_id")) + elif "server_id" in kwargs: + return self._is_resource_deleted( + self.show_share_server, kwargs.get("server_id")) + else: + raise share_exceptions.InvalidResource( + message=six.text_type(kwargs)) + + def _is_resource_deleted(self, func, res_id): + try: + res = func(res_id) + except exceptions.NotFound: + return True + if res.get('status') == 'error_deleting': + # Resource has "error_deleting" status and can not be deleted. + resource_type = func.__name__.split('_', 1)[-1] + raise share_exceptions.ResourceReleaseFailed( + res_type=resource_type, res_id=res_id) + return False + + def wait_for_resource_deletion(self, *args, **kwargs): + """Waits for a resource to be deleted.""" + start_time = int(time.time()) + while True: + if self.is_resource_deleted(*args, **kwargs): + return + if int(time.time()) - start_time >= self.build_timeout: + raise exceptions.TimeoutException + time.sleep(self.build_interval) + + def list_extensions(self): + resp, extensions = self.get("extensions") + self.expected_success(200, resp.status) + return self._parse_resp(extensions) + + def update_share(self, share_id, name=None, desc=None, is_public=None): + body = {"share": {}} + if name is not None: + body["share"].update({"display_name": name}) + if desc is not None: + body["share"].update({"display_description": desc}) + if is_public is not None: + body["share"].update({"is_public": is_public}) + body = json.dumps(body) + resp, body = self.put("shares/%s" % share_id, body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def rename_snapshot(self, snapshot_id, name, desc=None): + body = {"snapshot": {"display_name": name}} + if desc is not None: + body["snapshot"].update({"display_description": desc}) + body = json.dumps(body) + resp, body = self.put("snapshots/%s" % snapshot_id, body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def reset_state(self, s_id, status="error", s_type="shares"): + """Resets the state of a share or a snapshot. + + status: available, error, creating, deleting, error_deleting + s_type: shares, snapshots + """ + body = {"os-reset_status": {"status": status}} + body = json.dumps(body) + resp, body = self.post("%s/%s/action" % (s_type, s_id), body) + self.expected_success(202, resp.status) + return body + + def force_delete(self, s_id, s_type="shares"): + """Force delete share or snapshot. + + s_type: shares, snapshots + """ + body = {"os-force_delete": None} + body = json.dumps(body) + resp, body = self.post("%s/%s/action" % (s_type, s_id), body) + self.expected_success(202, resp.status) + return body + +############### + + def list_services(self, params=None): + """List services.""" + uri = 'os-services' + if params: + uri += '?%s' % urllib.urlencode(params) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + +############### + + def _update_metadata(self, share_id, metadata=None, method="post"): + uri = "shares/%s/metadata" % share_id + if metadata is None: + metadata = {} + post_body = {"metadata": metadata} + body = json.dumps(post_body) + if method is "post": + resp, metadata = self.post(uri, body) + if method is "put": + resp, metadata = self.put(uri, body) + self.expected_success(200, resp.status) + return self._parse_resp(metadata) + + def set_metadata(self, share_id, metadata=None): + return self._update_metadata(share_id, metadata) + + def update_all_metadata(self, share_id, metadata=None): + return self._update_metadata(share_id, metadata, method="put") + + def delete_metadata(self, share_id, key): + resp, body = self.delete("shares/%s/metadata/%s" % (share_id, key)) + self.expected_success(200, resp.status) + return body + + def get_metadata(self, share_id): + resp, body = self.get("shares/%s/metadata" % share_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + +############### + + def create_security_service(self, ss_type="ldap", **kwargs): + # ss_type: ldap, kerberos, active_directory + # kwargs: name, description, dns_ip, server, domain, user, password + post_body = {"type": ss_type} + post_body.update(kwargs) + body = json.dumps({"security_service": post_body}) + resp, body = self.post("security-services", body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def update_security_service(self, ss_id, **kwargs): + # ss_id - id of security-service entity + # kwargs: dns_ip, server, domain, user, password, name, description + # for 'active' status can be changed + # only 'name' and 'description' fields + body = json.dumps({"security_service": kwargs}) + resp, body = self.put("security-services/%s" % ss_id, body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_security_service(self, ss_id): + resp, body = self.get("security-services/%s" % ss_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_security_services(self, detailed=False, params=None): + uri = "security-services" + if detailed: + uri += '/detail' + if params: + uri += "?%s" % urllib.urlencode(params) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_security_service(self, ss_id): + resp, body = self.delete("security-services/%s" % ss_id) + self.expected_success(202, resp.status) + return body + +############### + + def create_share_network(self, **kwargs): + # kwargs: name, description + # + for neutron: neutron_net_id, neutron_subnet_id + body = json.dumps({"share_network": kwargs}) + resp, body = self.post("share-networks", body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def update_share_network(self, sn_id, **kwargs): + # kwargs: name, description + # + for neutron: neutron_net_id, neutron_subnet_id + body = json.dumps({"share_network": kwargs}) + resp, body = self.put("share-networks/%s" % sn_id, body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_share_network(self, sn_id): + resp, body = self.get("share-networks/%s" % sn_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_share_networks(self): + resp, body = self.get("share-networks") + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_share_networks_with_detail(self, params=None): + """List the details of all shares.""" + uri = "share-networks/detail" + if params: + uri += "?%s" % urllib.urlencode(params) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_share_network(self, sn_id): + resp, body = self.delete("share-networks/%s" % sn_id) + self.expected_success(202, resp.status) + return body + +############### + + def _map_security_service_and_share_network(self, sn_id, ss_id, + action="add"): + # sn_id: id of share_network_entity + # ss_id: id of security service entity + # action: add, remove + data = { + "%s_security_service" % action: { + "security_service_id": ss_id, + } + } + body = json.dumps(data) + resp, body = self.post("share-networks/%s/action" % sn_id, body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def add_sec_service_to_share_network(self, sn_id, ss_id): + body = self._map_security_service_and_share_network(sn_id, ss_id) + return body + + def remove_sec_service_from_share_network(self, sn_id, ss_id): + body = self._map_security_service_and_share_network( + sn_id, ss_id, "remove") + return body + + def list_sec_services_for_share_network(self, sn_id): + resp, body = self.get("security-services?share_network_id=%s" % sn_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + +############### + + def list_share_types(self, params=None): + uri = 'types' + if params is not None: + uri += '?%s' % urllib.urlencode(params) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def create_share_type(self, name, is_public=True, **kwargs): + post_body = { + 'name': name, + 'extra_specs': kwargs.get('extra_specs'), + 'os-share-type-access:is_public': is_public, + } + post_body = json.dumps({'share_type': post_body}) + resp, body = self.post('types', post_body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_share_type(self, share_type_id): + resp, body = self.delete("types/%s" % share_type_id) + self.expected_success(202, resp.status) + return body + + def get_share_type(self, share_type_id): + resp, body = self.get("types/%s" % share_type_id) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def add_access_to_share_type(self, share_type_id, project_id): + uri = 'types/%s/action' % share_type_id + post_body = {'project': project_id} + post_body = json.dumps({'addProjectAccess': post_body}) + resp, body = self.post(uri, post_body) + self.expected_success(202, resp.status) + return body + + def remove_access_from_share_type(self, share_type_id, project_id): + uri = 'types/%s/action' % share_type_id + post_body = {'project': project_id} + post_body = json.dumps({'removeProjectAccess': post_body}) + resp, body = self.post(uri, post_body) + self.expected_success(202, resp.status) + return body + + def list_access_to_share_type(self, share_type_id): + uri = 'types/%s/os-share-type-access' % share_type_id + resp, body = self.get(uri) + # [{"share_type_id": "%st_id%", "project_id": "%project_id%"}, ] + self.expected_success(200, resp.status) + return self._parse_resp(body) + +############### + + def create_share_type_extra_specs(self, share_type_id, extra_specs): + url = "types/%s/extra_specs" % share_type_id + post_body = json.dumps({'extra_specs': extra_specs}) + resp, body = self.post(url, post_body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_share_type_extra_spec(self, share_type_id, extra_spec_name): + uri = "types/%s/extra_specs/%s" % (share_type_id, extra_spec_name) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_share_type_extra_specs(self, share_type_id, params=None): + uri = "types/%s/extra_specs" % share_type_id + if params is not None: + uri += '?%s' % urllib.urlencode(params) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def update_share_type_extra_spec(self, share_type_id, spec_name, + spec_value): + uri = "types/%s/extra_specs/%s" % (share_type_id, spec_name) + extra_spec = {spec_name: spec_value} + post_body = json.dumps(extra_spec) + resp, body = self.put(uri, post_body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def update_share_type_extra_specs(self, share_type_id, extra_specs): + uri = "types/%s/extra_specs" % share_type_id + extra_specs = {"extra_specs": extra_specs} + post_body = json.dumps(extra_specs) + resp, body = self.post(uri, post_body) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_share_type_extra_spec(self, share_type_id, extra_spec_name): + uri = "types/%s/extra_specs/%s" % (share_type_id, extra_spec_name) + resp, body = self.delete(uri) + self.expected_success(202, resp.status) + return body + +############### + + def list_share_servers(self, search_opts=None): + """Get list of share servers.""" + uri = "share-servers" + if search_opts: + uri += "?%s" % urllib.urlencode(search_opts) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def delete_share_server(self, share_server_id): + """Delete share server by its ID.""" + uri = "share-servers/%s" % share_server_id + resp, body = self.delete(uri) + self.expected_success(202, resp.status) + return body + + def show_share_server(self, share_server_id): + """Get share server info.""" + uri = "share-servers/%s" % share_server_id + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def show_share_server_details(self, share_server_id): + """Get share server details only.""" + uri = "share-servers/%s/details" % share_server_id + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return self._parse_resp(body) + +############### + + def list_pools(self, detail=False, search_opts=None): + """Get list of scheduler pools.""" + uri = 'scheduler-stats/pools' + if detail: + uri += '/detail' + if search_opts: + uri += "?%s" % urllib.urlencode(search_opts) + resp, body = self.get(uri) + self.expected_success(200, resp.status) + return json.loads(body) diff --git a/manila_tempest_tests/share_exceptions.py b/manila_tempest_tests/share_exceptions.py new file mode 100644 index 00000000..0f3de859 --- /dev/null +++ b/manila_tempest_tests/share_exceptions.py @@ -0,0 +1,52 @@ +# 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_lib import exceptions + + +class ShareBuildErrorException(exceptions.TempestException): + message = "Share %(share_id)s failed to build and is in ERROR status" + + +class ShareInstanceBuildErrorException(exceptions.TempestException): + message = "Share instance %(id)s failed to build and is in ERROR status" + + +class AccessRuleBuildErrorException(exceptions.TempestException): + message = "Share's rule with id %(rule_id)s is in ERROR status" + + +class SnapshotBuildErrorException(exceptions.TempestException): + message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status" + + +class ShareProtocolNotSpecified(exceptions.TempestException): + message = "Share can not be created, share protocol is not specified" + + +class ShareNetworkNotSpecified(exceptions.TempestException): + message = "Share can not be created, share network not specified" + + +class NoAvailableNetwork(exceptions.TempestException): + message = "No available network for service VM" + + +class InvalidResource(exceptions.TempestException): + message = "Provided invalid resource: %(message)s" + + +class ResourceReleaseFailed(exceptions.TempestException): + message = "Failed to release resource '%(res_type)s' with id '%(res_id)s'." diff --git a/manila_tempest_tests/tests/__init__.py b/manila_tempest_tests/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/tests/api/__init__.py b/manila_tempest_tests/tests/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/tests/api/admin/__init__.py b/manila_tempest_tests/tests/api/admin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/tests/api/admin/test_admin_actions.py b/manila_tempest_tests/tests/api/admin/test_admin_actions.py new file mode 100644 index 00000000..6acec11f --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_admin_actions.py @@ -0,0 +1,117 @@ +# 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 config # noqa +from tempest import test # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + + +CONF = config.CONF + + +class AdminActionsTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(AdminActionsTest, cls).resource_setup() + cls.states = ["error", "available"] + cls.bad_status = "error_deleting" + cls.sh = cls.create_share() + cls.sh_instance = ( + cls.shares_client.get_instances_of_share(cls.sh["id"])[0] + ) + if CONF.share.run_snapshot_tests: + cls.sn = cls.create_snapshot_wait_for_active(cls.sh["id"]) + + @test.attr(type=["gate", ]) + def test_reset_share_state(self): + for status in self.states: + self.shares_client.reset_state(self.sh["id"], status=status) + self.shares_client.wait_for_share_status(self.sh["id"], status) + + @test.attr(type=["gate", ]) + def test_reset_share_instance_state(self): + id = self.sh_instance["id"] + for status in self.states: + self.shares_client.reset_state( + id, s_type="share_instances", status=status) + self.shares_client.wait_for_share_instance_status(id, status) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_reset_snapshot_state_to_error(self): + for status in self.states: + self.shares_client.reset_state( + self.sn["id"], s_type="snapshots", status=status) + self.shares_client.wait_for_snapshot_status(self.sn["id"], status) + + @test.attr(type=["gate", ]) + def test_force_delete_share(self): + share = self.create_share() + + # Change status from 'available' to 'error_deleting' + self.shares_client.reset_state(share["id"], status=self.bad_status) + + # Check that status was changed + check_status = self.shares_client.get_share(share["id"]) + self.assertEqual(check_status["status"], self.bad_status) + + # Share with status 'error_deleting' should be deleted + self.shares_client.force_delete(share["id"]) + self.shares_client.wait_for_resource_deletion(share_id=share["id"]) + + @test.attr(type=["gate", ]) + def test_force_delete_share_instance(self): + share = self.create_share(cleanup_in_class=False) + instances = self.shares_client.get_instances_of_share(share["id"]) + # Check that instance was created + self.assertEqual(1, len(instances)) + + instance = instances[0] + + # Change status from 'available' to 'error_deleting' + self.shares_client.reset_state( + instance["id"], s_type="share_instances", status=self.bad_status) + + # Check that status was changed + check_status = self.shares_client.get_share_instance(instance["id"]) + self.assertEqual(self.bad_status, check_status["status"]) + + # Share with status 'error_deleting' should be deleted + self.shares_client.force_delete( + instance["id"], s_type="share_instances") + self.shares_client.wait_for_resource_deletion( + share_instance_id=instance["id"]) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_force_delete_snapshot(self): + sn = self.create_snapshot_wait_for_active(self.sh["id"]) + + # Change status from 'available' to 'error_deleting' + self.shares_client.reset_state( + sn["id"], s_type="snapshots", status=self.bad_status) + + # Check that status was changed + check_status = self.shares_client.get_snapshot(sn["id"]) + self.assertEqual(check_status["status"], self.bad_status) + + # Snapshot with status 'error_deleting' should be deleted + self.shares_client.force_delete(sn["id"], s_type="snapshots") + self.shares_client.wait_for_resource_deletion(snapshot_id=sn["id"]) diff --git a/manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py b/manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py new file mode 100644 index 00000000..7df3b30e --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py @@ -0,0 +1,167 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class AdminActionsNegativeTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(AdminActionsNegativeTest, cls).resource_setup() + cls.sh = cls.create_share() + cls.sh_instance = ( + cls.shares_client.get_instances_of_share(cls.sh["id"])[0] + ) + if CONF.share.run_snapshot_tests: + cls.sn = cls.create_snapshot_wait_for_active(cls.sh["id"]) + cls.member_shares_client = clients.Manager().shares_client + + @test.attr(type=["gate", "negative", ]) + def test_reset_nonexistent_share_state(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.reset_state, "fake") + + @test.attr(type=["gate", "negative", ]) + def test_reset_nonexistent_share_instance_state(self): + self.assertRaises(lib_exc.NotFound, self.shares_client.reset_state, + "fake", s_type="share_instances") + + @test.attr(type=["gate", "negative", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_reset_nonexistent_snapshot_state(self): + self.assertRaises(lib_exc.NotFound, self.shares_client.reset_state, + "fake", s_type="snapshots") + + @test.attr(type=["gate", "negative", ]) + def test_reset_share_state_to_unacceptable_state(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.reset_state, + self.sh["id"], status="fake") + + @test.attr(type=["gate", "negative", ]) + def test_reset_share_instance_state_to_unacceptable_state(self): + self.assertRaises( + lib_exc.BadRequest, + self.shares_client.reset_state, + self.sh_instance["id"], + s_type="share_instances", + status="fake" + ) + + @test.attr(type=["gate", "negative", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_reset_snapshot_state_to_unacceptable_state(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.reset_state, + self.sn["id"], s_type="snapshots", status="fake") + + @test.attr(type=["gate", "negative", ]) + def test_try_reset_share_state_with_member(self): + # Even if member from another tenant, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.reset_state, + self.sh["id"]) + + @test.attr(type=["gate", "negative", ]) + def test_try_reset_share_instance_state_with_member(self): + # Even if member from another tenant, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.reset_state, + self.sh_instance["id"], s_type="share_instances") + + @test.attr(type=["gate", "negative", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_try_reset_snapshot_state_with_member(self): + # Even if member from another tenant, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.reset_state, + self.sn["id"], s_type="snapshots") + + @test.attr(type=["gate", "negative", ]) + def test_force_delete_nonexistent_share(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.force_delete, "fake") + + @test.attr(type=["gate", "negative", ]) + def test_force_delete_nonexistent_share_instance(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.force_delete, + "fake", + s_type="share_instances") + + @test.attr(type=["gate", "negative", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_force_delete_nonexistent_snapshot(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.force_delete, + "fake", + s_type="snapshots") + + @test.attr(type=["gate", "negative", ]) + def test_try_force_delete_share_with_member(self): + # If a non-admin tries to do force_delete, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.force_delete, + self.sh["id"]) + + @test.attr(type=["gate", "negative", ]) + def test_try_force_delete_share_instance_with_member(self): + # If a non-admin tries to do force_delete, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.force_delete, + self.sh_instance["id"], s_type="share_instances") + + @test.attr(type=["gate", "negative", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_try_force_delete_snapshot_with_member(self): + # If a non-admin tries to do force_delete, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.force_delete, + self.sn["id"], s_type="snapshots") + + @test.attr(type=["gate", "negative", ]) + def test_try_get_share_instance_with_member(self): + # If a non-admin tries to get instance, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.get_share_instance, + self.sh_instance["id"]) + + @test.attr(type=["gate", "negative", ]) + def test_try_list_share_instance_with_member(self): + # If a non-admin tries to list instances, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.list_share_instances) + + @test.attr(type=["gate", "negative", ]) + def test_try_get_instances_of_share_with_member(self): + # If a non-admin tries to list instances of given share, it should be + # unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.get_instances_of_share, + self.sh['id']) diff --git a/manila_tempest_tests/tests/api/admin/test_multi_backend.py b/manila_tempest_tests/tests/api/admin/test_multi_backend.py new file mode 100644 index 00000000..ab57d7fa --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_multi_backend.py @@ -0,0 +1,101 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa + +from manila_tempest_tests.tests.api import base + + +CONF = config.CONF + + +class ShareMultiBackendTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(ShareMultiBackendTest, cls).resource_setup() + if not CONF.share.multi_backend: + raise cls.skipException("Manila multi-backend tests are disabled.") + elif len(CONF.share.backend_names) < 2: + raise cls.skipException("For running multi-backend tests required" + " two names in config. Skipping.") + elif any(not name for name in CONF.share.backend_names): + raise cls.skipException("Share backend names can not be empty. " + "Skipping.") + cls.sts = [] + cls.shares = [] + share_data_list = [] + + # Create share types + for i in [0, 1]: + st_name = data_utils.rand_name("share-type-%s" % str(i)) + extra_specs = { + "share_backend_name": CONF.share.backend_names[i], + } + st = cls.create_share_type( + name=st_name, + extra_specs=cls.add_required_extra_specs_to_dict(extra_specs)) + cls.sts.append(st["share_type"]) + st_id = st["share_type"]["id"] + share_data_list.append({"kwargs": {"share_type_id": st_id}}) + + # Create shares using precreated share types + cls.shares = cls.create_shares(share_data_list) + + @test.attr(type=["gate", "smoke", ]) + def test_share_backend_name_reporting(self): + # Share's 'host' should be like "hostname@backend_name" + for share in self.shares: + get = self.shares_client.get_share(share['id']) + self.assertTrue(len(get["host"].split("@")) == 2) + + @test.attr(type=["gate", "smoke", ]) + def test_share_share_type(self): + # Share type should be the same as provided with share creation + for i in [0, 1]: + get = self.shares_client.get_share(self.shares[i]['id']) + self.assertEqual(get["share_type"], self.sts[i]["name"]) + + @test.attr(type=["gate", ]) + def test_share_export_locations(self): + # Different backends have different IPs on interfaces + # and export locations should be different too. + if CONF.share.backend_names[0] == CONF.share.backend_names[1]: + raise self.skipException("Share backends " + "configured with same name. Skipping.") + ips = [] + for share in self.shares: + get = self.shares_client.get_share(share['id']) + if get["share_proto"].lower() == "nfs": + # %ip%:/%share_path% + ip = get["export_location"].split(":")[0] + ips.append(ip) + elif get["share_proto"].lower() == "cifs": + # //%ip%/%share_path% + ip = get["export_location"][2:].split("/")[0] + ips.append(ip) + self.assertNotEqual(ips[0], ips[1]) + + @test.attr(type=["gate", ]) + def test_share_backend_name_distinction(self): + # Different share backends should have different host records + if CONF.share.backend_names[0] == CONF.share.backend_names[1]: + raise self.skipException("Share backends " + "configured with same name. Skipping.") + get1 = self.shares_client.get_share(self.shares[0]['id']) + get2 = self.shares_client.get_share(self.shares[1]['id']) + self.assertNotEqual(get1["host"], get2["host"]) diff --git a/manila_tempest_tests/tests/api/admin/test_quotas.py b/manila_tempest_tests/tests/api/admin/test_quotas.py new file mode 100644 index 00000000..dd7164eb --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_quotas.py @@ -0,0 +1,351 @@ +# 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 config # noqa +from tempest import test # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SharesAdminQuotasTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + cls.os = clients.AdminManager() + super(SharesAdminQuotasTest, cls).resource_setup() + cls.user_id = cls.shares_client.user_id + cls.tenant_id = cls.shares_client.tenant_id + + @test.attr(type=["gate", "smoke", ]) + def test_default_quotas(self): + quotas = self.shares_client.default_quotas(self.tenant_id) + self.assertGreater(int(quotas["gigabytes"]), -2) + self.assertGreater(int(quotas["snapshot_gigabytes"]), -2) + self.assertGreater(int(quotas["shares"]), -2) + self.assertGreater(int(quotas["snapshots"]), -2) + self.assertGreater(int(quotas["share_networks"]), -2) + + @test.attr(type=["gate", "smoke", ]) + def test_show_quotas(self): + quotas = self.shares_client.show_quotas(self.tenant_id) + self.assertGreater(int(quotas["gigabytes"]), -2) + self.assertGreater(int(quotas["snapshot_gigabytes"]), -2) + self.assertGreater(int(quotas["shares"]), -2) + self.assertGreater(int(quotas["snapshots"]), -2) + self.assertGreater(int(quotas["share_networks"]), -2) + + @test.attr(type=["gate", "smoke", ]) + def test_show_quotas_for_user(self): + quotas = self.shares_client.show_quotas(self.tenant_id, self.user_id) + self.assertGreater(int(quotas["gigabytes"]), -2) + self.assertGreater(int(quotas["snapshot_gigabytes"]), -2) + self.assertGreater(int(quotas["shares"]), -2) + self.assertGreater(int(quotas["snapshots"]), -2) + self.assertGreater(int(quotas["share_networks"]), -2) + + +class SharesAdminQuotasUpdateTest(base.BaseSharesAdminTest): + + force_tenant_isolation = True + + @test.attr(type=["gate", "smoke", ]) + def test_update_tenant_quota_shares(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + quotas = client.show_quotas(client.tenant_id) + new_quota = int(quotas["shares"]) + 2 + + # set new quota for shares + updated = client.update_quotas(client.tenant_id, shares=new_quota) + self.assertEqual(int(updated["shares"]), new_quota) + + @test.attr(type=["gate", "smoke", ]) + def test_update_user_quota_shares(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + quotas = client.show_quotas(client.tenant_id, client.user_id) + new_quota = int(quotas["shares"]) - 1 + + # set new quota for shares + updated = client.update_quotas( + client.tenant_id, client.user_id, shares=new_quota) + self.assertEqual(int(updated["shares"]), new_quota) + + @test.attr(type=["gate", "smoke", ]) + def test_update_tenant_quota_snapshots(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + quotas = client.show_quotas(client.tenant_id) + new_quota = int(quotas["snapshots"]) + 2 + + # set new quota for snapshots + updated = client.update_quotas(client.tenant_id, snapshots=new_quota) + self.assertEqual(int(updated["snapshots"]), new_quota) + + @test.attr(type=["gate", "smoke", ]) + def test_update_user_quota_snapshots(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + quotas = client.show_quotas(client.tenant_id, client.user_id) + new_quota = int(quotas["snapshots"]) - 1 + + # set new quota for snapshots + updated = client.update_quotas( + client.tenant_id, client.user_id, snapshots=new_quota) + self.assertEqual(int(updated["snapshots"]), new_quota) + + @test.attr(type=["gate", "smoke", ]) + def test_update_tenant_quota_gigabytes(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + custom = client.show_quotas(client.tenant_id) + + # make quotas for update + gigabytes = int(custom["gigabytes"]) + 2 + + # set new quota for shares + updated = client.update_quotas( + client.tenant_id, gigabytes=gigabytes) + self.assertEqual(int(updated["gigabytes"]), gigabytes) + + @test.attr(type=["gate", "smoke", ]) + def test_update_tenant_quota_snapshot_gigabytes(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + custom = client.show_quotas(client.tenant_id) + + # make quotas for update + snapshot_gigabytes = int(custom["snapshot_gigabytes"]) + 2 + + # set new quota for shares + updated = client.update_quotas( + client.tenant_id, + snapshot_gigabytes=snapshot_gigabytes) + self.assertEqual( + int(updated["snapshot_gigabytes"]), snapshot_gigabytes) + + @test.attr(type=["gate", "smoke", ]) + def test_update_user_quota_gigabytes(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + custom = client.show_quotas(client.tenant_id, client.user_id) + + # make quotas for update + gigabytes = int(custom["gigabytes"]) - 1 + + # set new quota for shares + updated = client.update_quotas( + client.tenant_id, client.user_id, + gigabytes=gigabytes) + self.assertEqual(int(updated["gigabytes"]), gigabytes) + + @test.attr(type=["gate", "smoke", ]) + def test_update_user_quota_snapshot_gigabytes(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + custom = client.show_quotas(client.tenant_id, client.user_id) + + # make quotas for update + snapshot_gigabytes = int(custom["snapshot_gigabytes"]) - 1 + + # set new quota for shares + updated = client.update_quotas( + client.tenant_id, client.user_id, + snapshot_gigabytes=snapshot_gigabytes) + self.assertEqual( + int(updated["snapshot_gigabytes"]), snapshot_gigabytes) + + @test.attr(type=["gate", "smoke", ]) + def test_update_tenant_quota_share_networks(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + quotas = client.show_quotas(client.tenant_id) + new_quota = int(quotas["share_networks"]) + 2 + + # set new quota for share-networks + updated = client.update_quotas( + client.tenant_id, share_networks=new_quota) + self.assertEqual(int(updated["share_networks"]), new_quota) + + @test.attr(type=["gate", "smoke", ]) + def test_update_user_quota_share_networks(self): + client = self.get_client_with_isolated_creds() + + # get current quotas + quotas = client.show_quotas( + client.tenant_id, client.user_id) + new_quota = int(quotas["share_networks"]) - 1 + + # set new quota for share-networks + updated = client.update_quotas( + client.tenant_id, client.user_id, + share_networks=new_quota) + self.assertEqual(int(updated["share_networks"]), new_quota) + + @test.attr(type=["gate", "smoke", ]) + def test_reset_tenant_quotas(self): + client = self.get_client_with_isolated_creds() + + # get default_quotas + default = client.default_quotas(client.tenant_id) + + # get current quotas + custom = client.show_quotas(client.tenant_id) + + # make quotas for update + shares = int(custom["shares"]) + 2 + snapshots = int(custom["snapshots"]) + 2 + gigabytes = int(custom["gigabytes"]) + 2 + snapshot_gigabytes = int(custom["snapshot_gigabytes"]) + 2 + share_networks = int(custom["share_networks"]) + 2 + + # set new quota + updated = client.update_quotas( + client.tenant_id, + shares=shares, + snapshots=snapshots, + gigabytes=gigabytes, + snapshot_gigabytes=snapshot_gigabytes, + share_networks=share_networks) + self.assertEqual(int(updated["shares"]), shares) + self.assertEqual(int(updated["snapshots"]), snapshots) + self.assertEqual(int(updated["gigabytes"]), gigabytes) + self.assertEqual( + int(updated["snapshot_gigabytes"]), snapshot_gigabytes) + self.assertEqual(int(updated["share_networks"]), share_networks) + + # reset customized quotas + client.reset_quotas(client.tenant_id) + + # verify quotas + reseted = client.show_quotas(client.tenant_id) + self.assertEqual(int(reseted["shares"]), int(default["shares"])) + self.assertEqual(int(reseted["snapshots"]), int(default["snapshots"])) + self.assertEqual(int(reseted["gigabytes"]), int(default["gigabytes"])) + self.assertEqual(int(reseted["share_networks"]), + int(default["share_networks"])) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_quota_for_shares(self): + client = self.get_client_with_isolated_creds() + client.update_quotas(client.tenant_id, shares=-1) + + quotas = client.show_quotas(client.tenant_id) + + self.assertEqual(-1, quotas.get('shares')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_user_quota_for_shares(self): + client = self.get_client_with_isolated_creds() + client.update_quotas( + client.tenant_id, client.user_id, + shares=-1) + + quotas = client.show_quotas(client.tenant_id, client.user_id) + + self.assertEqual(-1, quotas.get('shares')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_quota_for_snapshots(self): + client = self.get_client_with_isolated_creds() + client.update_quotas(client.tenant_id, snapshots=-1) + + quotas = client.show_quotas(client.tenant_id) + + self.assertEqual(-1, quotas.get('snapshots')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_user_quota_for_snapshots(self): + client = self.get_client_with_isolated_creds() + client.update_quotas( + client.tenant_id, client.user_id, + snapshots=-1) + + quotas = client.show_quotas(client.tenant_id, client.user_id) + + self.assertEqual(-1, quotas.get('snapshots')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_quota_for_gigabytes(self): + client = self.get_client_with_isolated_creds() + client.update_quotas(client.tenant_id, gigabytes=-1) + + quotas = client.show_quotas(client.tenant_id) + + self.assertEqual(-1, quotas.get('gigabytes')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_quota_for_snapshot_gigabytes(self): + client = self.get_client_with_isolated_creds() + client.update_quotas( + client.tenant_id, snapshot_gigabytes=-1) + + quotas = client.show_quotas(client.tenant_id) + + self.assertEqual(-1, quotas.get('snapshot_gigabytes')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_user_quota_for_gigabytes(self): + client = self.get_client_with_isolated_creds() + client.update_quotas( + client.tenant_id, client.user_id, + gigabytes=-1) + + quotas = client.show_quotas(client.tenant_id, client.user_id) + + self.assertEqual(-1, quotas.get('gigabytes')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_user_quota_for_snapshot_gigabytes(self): + client = self.get_client_with_isolated_creds() + client.update_quotas( + client.tenant_id, client.user_id, + snapshot_gigabytes=-1) + + quotas = client.show_quotas(client.tenant_id, client.user_id) + + self.assertEqual(-1, quotas.get('snapshot_gigabytes')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_quota_for_share_networks(self): + client = self.get_client_with_isolated_creds() + client.update_quotas(client.tenant_id, share_networks=-1) + + quotas = client.show_quotas(client.tenant_id) + + self.assertEqual(-1, quotas.get('share_networks')) + + @test.attr(type=["gate", "smoke", ]) + def test_unlimited_user_quota_for_share_networks(self): + client = self.get_client_with_isolated_creds() + client.update_quotas( + client.tenant_id, client.user_id, + share_networks=-1) + + quotas = client.show_quotas(client.tenant_id, client.user_id) + + self.assertEqual(-1, quotas.get('share_networks')) diff --git a/manila_tempest_tests/tests/api/admin/test_quotas_negative.py b/manila_tempest_tests/tests/api/admin/test_quotas_negative.py new file mode 100644 index 00000000..7850a2d0 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_quotas_negative.py @@ -0,0 +1,178 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SharesAdminQuotasNegativeTest(base.BaseSharesAdminTest): + + force_tenant_isolation = True + + @classmethod + def resource_setup(cls): + cls.os = clients.AdminManager() + super(SharesAdminQuotasNegativeTest, cls).resource_setup() + cls.user_id = cls.shares_client.user_id + cls.tenant_id = cls.shares_client.tenant_id + + @test.attr(type=["gate", "smoke", "negative"]) + def test_get_quotas_with_empty_tenant_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.show_quotas, "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_reset_quotas_with_empty_tenant_id(self): + client = self.get_client_with_isolated_creds() + self.assertRaises(lib_exc.NotFound, + client.reset_quotas, "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_update_shares_quota_with_wrong_data(self): + # -1 is acceptable value as unlimited + client = self.get_client_with_isolated_creds() + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + shares=-2) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_update_snapshots_quota_with_wrong_data(self): + # -1 is acceptable value as unlimited + client = self.get_client_with_isolated_creds() + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + snapshots=-2) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_update_gigabytes_quota_with_wrong_data(self): + # -1 is acceptable value as unlimited + client = self.get_client_with_isolated_creds() + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + gigabytes=-2) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_update_snapshot_gigabytes_quota_with_wrong_data(self): + # -1 is acceptable value as unlimited + client = self.get_client_with_isolated_creds() + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + snapshot_gigabytes=-2) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_update_share_networks_quota_with_wrong_data(self): + # -1 is acceptable value as unlimited + client = self.get_client_with_isolated_creds() + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + share_networks=-2) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_create_share_with_size_bigger_than_quota(self): + quotas = self.shares_client.show_quotas( + self.shares_client.tenant_id) + overquota = int(quotas['gigabytes']) + 2 + + # try schedule share with size, bigger than gigabytes quota + self.assertRaises(lib_exc.OverLimit, + self.shares_client.create_share, + size=overquota) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_set_user_quota_shares_bigger_than_tenant_quota(self): + client = self.get_client_with_isolated_creds() + + # get current quotas for tenant + tenant_quotas = client.show_quotas(client.tenant_id) + + # try set user quota for shares bigger than tenant quota + bigger_value = int(tenant_quotas["shares"]) + 2 + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + client.user_id, + shares=bigger_value) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_set_user_quota_snaps_bigger_than_tenant_quota(self): + client = self.get_client_with_isolated_creds() + + # get current quotas for tenant + tenant_quotas = client.show_quotas(client.tenant_id) + + # try set user quota for snapshots bigger than tenant quota + bigger_value = int(tenant_quotas["snapshots"]) + 2 + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + client.user_id, + snapshots=bigger_value) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_set_user_quota_gigabytes_bigger_than_tenant_quota(self): + client = self.get_client_with_isolated_creds() + + # get current quotas for tenant + tenant_quotas = client.show_quotas(client.tenant_id) + + # try set user quota for gigabytes bigger than tenant quota + bigger_value = int(tenant_quotas["gigabytes"]) + 2 + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + client.user_id, + gigabytes=bigger_value) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_set_user_quota_snap_gigabytes_bigger_than_tenant_quota(self): + client = self.get_client_with_isolated_creds() + + # get current quotas for tenant + tenant_quotas = client.show_quotas(client.tenant_id) + + # try set user quota for snapshot gigabytes bigger than tenant quota + bigger_value = int(tenant_quotas["snapshot_gigabytes"]) + 2 + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + client.user_id, + snapshot_gigabytes=bigger_value) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_set_user_quota_share_networks_bigger_than_tenant_quota(self): + client = self.get_client_with_isolated_creds() + + # get current quotas for tenant + tenant_quotas = client.show_quotas(client.tenant_id) + + # try set user quota for share_networks bigger than tenant quota + bigger_value = int(tenant_quotas["share_networks"]) + 2 + self.assertRaises(lib_exc.BadRequest, + client.update_quotas, + client.tenant_id, + client.user_id, + share_networks=bigger_value) diff --git a/manila_tempest_tests/tests/api/admin/test_scheduler_stats.py b/manila_tempest_tests/tests/api/admin/test_scheduler_stats.py new file mode 100644 index 00000000..939c3a94 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_scheduler_stats.py @@ -0,0 +1,140 @@ +# Copyright (c) 2015 Clinton Knight. 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SchedulerStatsAdminTest(base.BaseSharesAdminTest): + + @test.attr(type=["gate", "smoke", ]) + def test_pool_list(self): + + # List pools + pool_response = self.shares_client.list_pools() + pool_list = pool_response.get('pools') + self.assertIsNotNone(pool_list, 'No pools returned from pools API') + self.assertNotEmpty(pool_list) + pool = pool_list[0] + required_keys = {'name', 'host', 'backend', 'pool'} + actual_keys = set(pool.keys()) + self.assertTrue(actual_keys.issuperset(required_keys)) + + @test.attr(type=["gate", "smoke", ]) + def test_pool_list_with_filters(self): + + # List pools + pool_response = self.shares_client.list_pools() + pool_list = pool_response.get('pools') + + # Ensure we got at least one pool + self.assertIsNotNone(pool_list, 'No pools returned from pools API') + self.assertNotEmpty(pool_list) + pool = pool_list[0] + + # Build search opts from data and get pools again with filter + search_opts = { + 'host': pool.get('host'), + 'backend': pool.get('backend'), + 'pool': pool.get('pool'), + } + pool_response = self.shares_client.list_pools( + search_opts=search_opts) + filtered_pool_list = pool_response.get('pools') + + # Ensure we got exactly one pool matching the first one from above + self.assertEqual(1, len(filtered_pool_list)) + + # Match the key values, not the timestamp. + for k, v in search_opts.items(): + self.assertEqual(v, filtered_pool_list[0][k]) + + @test.attr(type=["gate", "smoke", ]) + def test_pool_list_with_filters_negative(self): + + # Build search opts for a non-existent pool + search_opts = { + 'host': 'foo', + 'backend': 'bar', + 'pool': 'shark', + } + pool_response = self.shares_client.list_pools( + search_opts=search_opts) + pool_list = pool_response.get('pools') + + # Ensure we got no pools + self.assertEmpty(pool_list) + + @test.attr(type=["gate", "smoke", ]) + def test_pool_list_detail(self): + + # List pools + pool_response = self.shares_client.list_pools(detail=True) + pool_list = pool_response.get('pools') + self.assertIsNotNone(pool_list, 'No pools returned from pools API') + self.assertNotEmpty(pool_list) + pool = pool_list[0] + required_keys = {'name', 'host', 'backend', 'pool', 'capabilities'} + actual_keys = set(pool.keys()) + self.assertTrue(actual_keys.issuperset(required_keys)) + + @test.attr(type=["gate", "smoke", ]) + def test_pool_list_detail_with_filters(self): + + # List pools + pool_response = self.shares_client.list_pools(detail=True) + pool_list = pool_response.get('pools') + + # Ensure we got at least one pool + self.assertIsNotNone(pool_list, 'No pools returned from pools API') + self.assertNotEmpty(pool_list) + pool = pool_list[0] + + # Build search opts from data and get pools again with filter + search_opts = { + 'host': pool.get('host'), + 'backend': pool.get('backend'), + 'pool': pool.get('pool'), + } + pool_response = self.shares_client.list_pools( + detail=True, search_opts=search_opts) + filtered_pool_list = pool_response.get('pools') + + # Ensure we got exactly one pool matching the first one from above + self.assertEqual(1, len(filtered_pool_list)) + + # Match the key values, not the timestamp. + for k, v in search_opts.items(): + self.assertEqual(v, filtered_pool_list[0][k]) + + @test.attr(type=["gate", "smoke", ]) + def test_pool_list_detail_with_filters_negative(self): + + # Build search opts for a non-existent pool + search_opts = { + 'host': 'foo', + 'backend': 'bar', + 'pool': 'shark', + } + pool_response = self.shares_client.list_pools( + detail=True, search_opts=search_opts) + pool_list = pool_response.get('pools') + + # Ensure we got no pools + self.assertEmpty(pool_list) diff --git a/manila_tempest_tests/tests/api/admin/test_security_services.py b/manila_tempest_tests/tests/api/admin/test_security_services.py new file mode 100644 index 00000000..562aa3d3 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_security_services.py @@ -0,0 +1,64 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base +from manila_tempest_tests.tests.api import test_security_services + + +class SecurityServiceAdminTest( + base.BaseSharesAdminTest, + test_security_services.SecurityServiceListMixin): + + def setUp(self): + super(SecurityServiceAdminTest, self).setUp() + ss_ldap_data = { + 'name': 'ss_ldap', + 'dns_ip': '1.1.1.1', + 'server': 'fake_server_1', + 'domain': 'fake_domain_1', + 'user': 'fake_user', + 'password': 'pass', + } + ss_kerberos_data = { + 'name': 'ss_kerberos', + 'dns_ip': '2.2.2.2', + 'server': 'fake_server_2', + 'domain': 'fake_domain_2', + 'user': 'test_user', + 'password': 'word', + } + self.ss_ldap = self.create_security_service('ldap', **ss_ldap_data) + self.ss_kerberos = self.create_security_service( + 'kerberos', + **ss_kerberos_data) + + @test.attr(type=["gate", "smoke", ]) + def test_list_security_services_all_tenants(self): + listed = self.shares_client.list_security_services( + params={'all_tenants': 1}) + self.assertTrue(any(self.ss_ldap['id'] == ss['id'] for ss in listed)) + self.assertTrue(any(self.ss_kerberos['id'] == ss['id'] + for ss in listed)) + + keys = ["name", "id", "status", "type", ] + [self.assertIn(key, s_s.keys()) for s_s in listed for key in keys] + + @test.attr(type=["gate", "smoke", ]) + def test_list_security_services_invalid_filters(self): + listed = self.shares_client.list_security_services( + params={'fake_opt': 'some_value'}) + self.assertEqual(0, len(listed)) diff --git a/manila_tempest_tests/tests/api/admin/test_services.py b/manila_tempest_tests/tests/api/admin/test_services.py new file mode 100644 index 00000000..4b4085ca --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_services.py @@ -0,0 +1,96 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base + + +class ServicesAdminTest(base.BaseSharesAdminTest): + + def setUp(self): + super(ServicesAdminTest, self).setUp() + self.services = self.shares_client.list_services() + + @test.attr(type=["gate", "smoke", ]) + def test_list_services(self): + services = self.shares_client.list_services() + self.assertNotEqual(0, len(services)) + + for service in services: + self.assertIsNotNone(service['id']) + + @test.attr(type=["gate", "smoke", ]) + def test_get_services_by_host_name(self): + host = self.services[0]["host"] + params = {"host": host} + services = self.shares_client.list_services(params) + self.assertNotEqual(0, len(services)) + for service in services: + self.assertEqual(host, service["host"]) + + @test.attr(type=["gate", "smoke", ]) + def test_get_services_by_binary_name(self): + binary = self.services[0]["binary"] + params = {"binary": binary, } + services = self.shares_client.list_services(params) + self.assertNotEqual(0, len(services)) + for service in services: + self.assertEqual(binary, service["binary"]) + + @test.attr(type=["gate", "smoke", ]) + def test_get_services_by_availability_zone(self): + zone = self.services[0]["zone"] + params = {"zone": zone, } + services = self.shares_client.list_services(params) + self.assertNotEqual(0, len(services)) + for service in services: + self.assertEqual(zone, service["zone"]) + + @test.attr(type=["gate", "smoke", ]) + def test_get_services_by_status(self): + status = self.services[0]["status"] + params = {"status": status, } + services = self.shares_client.list_services(params) + self.assertNotEqual(0, len(services)) + for service in services: + self.assertEqual(status, service["status"]) + + @test.attr(type=["gate", "smoke", ]) + def test_get_services_by_state(self): + state = self.services[0]["state"] + params = {"state": state, } + services = self.shares_client.list_services(params) + self.assertNotEqual(0, len(services)) + for service in services: + self.assertEqual(state, service["state"]) + + @test.attr(type=["gate", "smoke", ]) + def test_get_services_by_all_filters(self): + params = { + "host": self.services[0]["host"], + "binary": self.services[0]["binary"], + "zone": self.services[0]["zone"], + "status": self.services[0]["status"], + "state": self.services[0]["state"], + } + services = self.shares_client.list_services(params) + self.assertNotEqual(0, len(services)) + for service in services: + self.assertEqual(params["host"], service["host"]) + self.assertEqual(params["binary"], service["binary"]) + self.assertEqual(params["zone"], service["zone"]) + self.assertEqual(params["status"], service["status"]) + self.assertEqual(params["state"], service["state"]) diff --git a/manila_tempest_tests/tests/api/admin/test_services_negative.py b/manila_tempest_tests/tests/api/admin/test_services_negative.py new file mode 100644 index 00000000..6ed0c053 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_services_negative.py @@ -0,0 +1,78 @@ +# 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 test # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + + +class ServicesAdminNegativeTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(ServicesAdminNegativeTest, cls).resource_setup() + user_clients = clients.Manager() + cls.user_shares_client = user_clients.shares_client + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_services_with_non_admin_user(self): + self.assertRaises(lib_exc.Forbidden, + self.user_shares_client.list_services) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_get_service_by_invalid_params(self): + # All services are expected if send the request with invalid parameter + services = self.shares_client.list_services() + params = {'fake_param': 'fake_param_value'} + services_fake = self.shares_client.list_services(params) + self.assertEqual(len(services), len(services_fake)) + + # "update_at" field could be updated before second request, + # so do not take it in account. + for service in services + services_fake: + service["updated_at"] = "removed_possible_difference" + self.assertEqual(services, services_fake) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_get_service_by_invalid_host(self): + params = {'host': 'fake_host'} + services_fake = self.shares_client.list_services(params) + self.assertEqual(0, len(services_fake)) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_get_service_by_invalid_binary(self): + params = {'binary': 'fake_binary'} + services_fake = self.shares_client.list_services(params) + self.assertEqual(0, len(services_fake)) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_get_service_by_invalid_zone(self): + params = {'zone': 'fake_zone'} + services_fake = self.shares_client.list_services(params) + self.assertEqual(0, len(services_fake)) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_get_service_by_invalid_status(self): + params = {'status': 'fake_status'} + services_fake = self.shares_client.list_services(params) + self.assertEqual(0, len(services_fake)) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_get_service_by_invalid_state(self): + params = {'state': 'fake_state'} + services_fake = self.shares_client.list_services(params) + self.assertEqual(0, len(services_fake)) diff --git a/manila_tempest_tests/tests/api/admin/test_share_manage.py b/manila_tempest_tests/tests/api/admin/test_share_manage.py new file mode 100644 index 00000000..df3896ec --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_manage.py @@ -0,0 +1,163 @@ +# Copyright 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 import config # noqa +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class ManageNFSShareTest(base.BaseSharesAdminTest): + protocol = 'nfs' + + # NOTE(vponomaryov): be careful running these tests using generic driver + # because cinder volumes will stay attached to service Nova VM and + # won't be deleted. + + @classmethod + @testtools.skipIf( + CONF.share.multitenancy_enabled, + "Only for driver_handles_share_servers = False driver mode.") + @testtools.skipUnless( + CONF.share.run_manage_unmanage_tests, + "Manage/unmanage tests are disabled.") + def resource_setup(cls): + super(ManageNFSShareTest, cls).resource_setup() + if cls.protocol not in CONF.share.enable_protocols: + message = "%s tests are disabled" % cls.protocol + raise cls.skipException(message) + + # Create share types + cls.st_name = data_utils.rand_name("manage-st-name") + cls.st_name_invalid = data_utils.rand_name("manage-st-name-invalid") + cls.extra_specs = { + 'storage_protocol': CONF.share.storage_protocol, + 'driver_handles_share_servers': False + } + cls.extra_specs_invalid = { + 'storage_protocol': CONF.share.storage_protocol, + 'driver_handles_share_servers': True + } + + cls.st = cls.create_share_type( + name=cls.st_name, + cleanup_in_class=True, + extra_specs=cls.extra_specs) + + cls.st_invalid = cls.create_share_type( + name=cls.st_name_invalid, + cleanup_in_class=True, + extra_specs=cls.extra_specs_invalid) + + creation_data = {'kwargs': { + 'share_type_id': cls.st['share_type']['id'], + 'share_protocol': cls.protocol, + }} + # Create two shares in parallel + cls.shares = cls.create_shares([creation_data, creation_data]) + + # Load all share data (host, etc.) + cls.share1 = cls.shares_client.get_share(cls.shares[0]['id']) + cls.share2 = cls.shares_client.get_share(cls.shares[1]['id']) + + # Unmanage shares from manila + cls.shares_client.unmanage_share(cls.share1['id']) + cls.shares_client.wait_for_resource_deletion(share_id=cls.share1['id']) + cls.shares_client.unmanage_share(cls.share2['id']) + cls.shares_client.wait_for_resource_deletion(share_id=cls.share2['id']) + + @test.attr(type=["gate", "smoke"]) + def test_manage(self): + name = "Name for 'managed' share that had ID %s" % self.share1["id"] + description = "Description for 'managed' share" + + # Manage share + share = self.shares_client.manage_share( + service_host=self.share1['host'], + export_path=self.share1['export_locations'][0], + protocol=self.share1['share_proto'], + share_type_id=self.st['share_type']['id'], + name=name, + description=description, + ) + + # Add managed share to cleanup queue + self.method_resources.insert( + 0, {'type': 'share_type', 'id': share['id'], + 'client': self.shares_client}) + + # Wait for success + self.shares_client.wait_for_share_status(share['id'], 'available') + + # Verify data of managed share + get = self.shares_client.get_share(share['id']) + self.assertEqual(name, get['name']) + self.assertEqual(description, get['description']) + self.assertEqual(self.share1['host'], get['host']) + self.assertEqual(self.share1['share_proto'], get['share_proto']) + self.assertEqual(self.st['share_type']['name'], get['share_type']) + + # Delete share + self.shares_client.delete_share(share['id']) + self.shares_client.wait_for_resource_deletion(share_id=share['id']) + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share, + share['id']) + + @test.attr(type=["gate", "smoke"]) + def test_manage_retry(self): + # Manage share with invalid parameters + share = None + parameters = [(self.st_invalid['share_type']['id'], 'manage_error'), + (self.st['share_type']['id'], 'available')] + + for share_type_id, status in parameters: + share = self.shares_client.manage_share( + service_host=self.share2['host'], + export_path=self.share2['export_locations'][0], + protocol=self.share2['share_proto'], + share_type_id=share_type_id) + + # Add managed share to cleanup queue + self.method_resources.insert( + 0, {'type': 'share_type', 'id': share['id'], + 'client': self.shares_client}) + + # Wait for success + self.shares_client.wait_for_share_status(share['id'], status) + + # Delete share + self.shares_client.delete_share(share['id']) + self.shares_client.wait_for_resource_deletion(share_id=share['id']) + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share, + share['id']) + + +class ManageCIFSShareTest(ManageNFSShareTest): + protocol = 'cifs' + + +class ManageGLUSTERFSShareTest(ManageNFSShareTest): + protocol = 'glusterfs' + + +class ManageHDFSShareTest(ManageNFSShareTest): + protocol = 'hdfs' diff --git a/manila_tempest_tests/tests/api/admin/test_share_networks.py b/manila_tempest_tests/tests/api/admin/test_share_networks.py new file mode 100644 index 00000000..c54ab97a --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_networks.py @@ -0,0 +1,97 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base +from manila_tempest_tests.tests.api import test_share_networks + + +class ShareNetworkAdminTest( + base.BaseSharesAdminTest, + test_share_networks.ShareNetworkListMixin): + + @classmethod + def resource_setup(cls): + super(ShareNetworkAdminTest, cls).resource_setup() + ss_data = cls.generate_security_service_data() + cls.ss_ldap = cls.create_security_service(**ss_data) + + cls.data_sn_with_ldap_ss = { + 'name': 'sn_with_ldap_ss', + 'neutron_net_id': '1111', + 'neutron_subnet_id': '2222', + 'created_at': '2002-02-02', + 'updated_at': None, + 'network_type': 'vlan', + 'segmentation_id': 1000, + 'cidr': '10.0.0.0/24', + 'ip_version': 4, + 'description': 'fake description', + } + cls.sn_with_ldap_ss = cls.create_share_network( + cleanup_in_class=True, + **cls.data_sn_with_ldap_ss) + + cls.shares_client.add_sec_service_to_share_network( + cls.sn_with_ldap_ss["id"], + cls.ss_ldap["id"]) + + cls.isolated_client = cls.get_client_with_isolated_creds( + type_of_creds='alt') + cls.data_sn_with_kerberos_ss = { + 'name': 'sn_with_kerberos_ss', + 'neutron_net_id': '3333', + 'neutron_subnet_id': '4444', + 'created_at': '2003-03-03', + 'updated_at': None, + 'neutron_net_id': 'test net id', + 'neutron_subnet_id': 'test subnet id', + 'network_type': 'local', + 'segmentation_id': 2000, + 'cidr': '10.0.0.0/13', + 'ip_version': 6, + 'description': 'fake description', + } + + cls.ss_kerberos = cls.isolated_client.create_security_service( + ss_type='kerberos', + **cls.data_sn_with_ldap_ss) + + cls.sn_with_kerberos_ss = cls.isolated_client.create_share_network( + cleanup_in_class=True, + **cls.data_sn_with_kerberos_ss) + + cls.isolated_client.add_sec_service_to_share_network( + cls.sn_with_kerberos_ss["id"], + cls.ss_kerberos["id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_networks_all_tenants(self): + listed = self.shares_client.list_share_networks_with_detail( + {'all_tenants': 1}) + self.assertTrue(any(self.sn_with_ldap_ss['id'] == sn['id'] + for sn in listed)) + self.assertTrue(any(self.sn_with_kerberos_ss['id'] == sn['id'] + for sn in listed)) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_networks_filter_by_project_id(self): + listed = self.shares_client.list_share_networks_with_detail( + {'project_id': self.sn_with_kerberos_ss['project_id']}) + self.assertTrue(any(self.sn_with_kerberos_ss['id'] == sn['id'] + for sn in listed)) + self.assertTrue(all(self.sn_with_kerberos_ss['project_id'] == + sn['project_id'] for sn in listed)) diff --git a/manila_tempest_tests/tests/api/admin/test_share_servers.py b/manila_tempest_tests/tests/api/admin/test_share_servers.py new file mode 100644 index 00000000..7cd67f82 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_servers.py @@ -0,0 +1,276 @@ +# Copyright 2014 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 re + +import six # noqa +from tempest import config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class ShareServersAdminTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(ShareServersAdminTest, cls).resource_setup() + if not CONF.share.multitenancy_enabled: + msg = ("Share servers can be tested only with multitenant drivers." + " Skipping.") + raise cls.skipException(msg) + cls.share = cls.create_share() + cls.share_network = cls.shares_client.get_share_network( + cls.shares_client.share_network_id) + if not cls.share_network["name"]: + sn_id = cls.share_network["id"] + cls.share_network = cls.shares_client.update_share_network( + sn_id, name="sn_%s" % sn_id) + cls.sn_name_and_id = [ + cls.share_network["name"], + cls.share_network["id"], + ] + + # Date should be like '2014-13-12T11:10:09.000000' + cls.date_re = re.compile("^([0-9]{4}-[0-9]{2}-[0-9]{2}[A-Z]{1}" + "[0-9]{2}:[0-9]{2}:[0-9]{2}).*$") + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_servers_without_filters(self): + servers = self.shares_client.list_share_servers() + self.assertTrue(len(servers) > 0) + keys = [ + "id", + "host", + "status", + "share_network_name", + "updated_at", + "project_id", + ] + for server in servers: + # All expected keys are present + for key in keys: + self.assertIn(key, server.keys()) + # 'Updated at' is valid date if set + if server["updated_at"]: + self.assertTrue(self.date_re.match(server["updated_at"])) + # Host is not empty + self.assertTrue(len(server["host"]) > 0) + # Id is not empty + self.assertTrue(len(server["id"]) > 0) + # Project id is not empty + self.assertTrue(len(server["project_id"]) > 0) + + # Do not verify statuses because we get all share servers from whole + # cluster and here can be servers with any state. + # Server we used is present. + any(s["share_network_name"] in self.sn_name_and_id for s in servers) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_servers_with_host_filter(self): + # Get list of share servers and remember 'host' name + servers = self.shares_client.list_share_servers() + # Remember name of server that was used by this test suite + # to be sure it will be still existing. + host = "" + for server in servers: + if server["share_network_name"] in self.sn_name_and_id: + if not server["host"]: + msg = ("Server '%s' has wrong value for host - " + "'%s'.") % (server["id"], server["host"]) + raise lib_exc.InvalidContentType(message=msg) + host = server["host"] + break + if not host: + msg = ("Appropriate server was not found. Its share_network_data" + ": '%s'. List of servers: '%s'.") % (self.sn_name_and_id, + str(servers)) + raise lib_exc.NotFound(message=msg) + search_opts = {"host": host} + servers = self.shares_client.list_share_servers(search_opts) + self.assertTrue(len(servers) > 0) + for server in servers: + self.assertEqual(server["host"], host) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_servers_with_status_filter(self): + # Get list of share servers + servers = self.shares_client.list_share_servers() + # Remember status of server that was used by this test suite + # to be sure it will be still existing. + status = "" + for server in servers: + if server["share_network_name"] in self.sn_name_and_id: + if not server["status"]: + msg = ("Server '%s' has wrong value for status - " + "'%s'.") % (server["id"], server["host"]) + raise lib_exc.InvalidContentType(message=msg) + status = server["status"] + break + if not status: + msg = ("Appropriate server was not found. Its share_network_data" + ": '%s'. List of servers: '%s'.") % (self.sn_name_and_id, + str(servers)) + raise lib_exc.NotFound(message=msg) + search_opts = {"status": status} + servers = self.shares_client.list_share_servers(search_opts) + self.assertTrue(len(servers) > 0) + for server in servers: + self.assertEqual(server["status"], status) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_servers_with_project_id_filter(self): + search_opts = {"project_id": self.share_network["project_id"]} + servers = self.shares_client.list_share_servers(search_opts) + # Should exist, at least, one share server, used by this test suite. + self.assertTrue(len(servers) > 0) + for server in servers: + self.assertEqual(server["project_id"], + self.share_network["project_id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_servers_with_share_network_name_filter(self): + search_opts = {"share_network": self.share_network["name"]} + servers = self.shares_client.list_share_servers(search_opts) + # Should exist, at least, one share server, used by this test suite. + self.assertTrue(len(servers) > 0) + for server in servers: + self.assertEqual(server["share_network_name"], + self.share_network["name"]) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_servers_with_share_network_id_filter(self): + search_opts = {"share_network": self.share_network["id"]} + servers = self.shares_client.list_share_servers(search_opts) + # Should exist, at least, one share server, used by this test suite. + self.assertTrue(len(servers) > 0) + for server in servers: + self.assertIn(server["share_network_name"], + self.sn_name_and_id) + + @test.attr(type=["gate", "smoke", ]) + def test_show_share_server(self): + servers = self.shares_client.list_share_servers() + server = self.shares_client.show_share_server(servers[0]["id"]) + keys = [ + "id", + "host", + "project_id", + "status", + "share_network_name", + "created_at", + "updated_at", + "backend_details", + ] + # all expected keys are present + for key in keys: + self.assertIn(key, server.keys()) + # 'created_at' is valid date + self.assertTrue(self.date_re.match(server["created_at"])) + # 'updated_at' is valid date if set + if server["updated_at"]: + self.assertTrue(self.date_re.match(server["updated_at"])) + # Host is not empty + self.assertTrue(len(server["host"]) > 0) + # Id is not empty + self.assertTrue(len(server["id"]) > 0) + # Project id is not empty + self.assertTrue(len(server["project_id"]) > 0) + # Status is not empty + self.assertTrue(len(server["status"]) > 0) + # share_network_name is not empty + self.assertTrue(len(server["share_network_name"]) > 0) + # backend_details should be a dict + self.assertTrue(isinstance(server["backend_details"], dict)) + + @test.attr(type=["gate", "smoke", ]) + def test_show_share_server_details(self): + servers = self.shares_client.list_share_servers() + details = self.shares_client.show_share_server_details( + servers[0]["id"]) + # If details are present they and their values should be only strings + for k, v in details.iteritems(): + self.assertTrue(isinstance(k, six.string_types)) + self.assertTrue(isinstance(v, six.string_types)) + + @test.attr(type=["gate", "smoke", ]) + def _delete_share_server(self, delete_share_network): + # Get network and subnet from existing share_network and reuse it + # to be able to delete share_server after test ends. + # TODO(vponomaryov): attach security-services too. If any exist from + # donor share-network. + new_sn = self.create_share_network( + neutron_net_id=self.share_network['neutron_net_id'], + neutron_subnet_id=self.share_network['neutron_subnet_id']) + + # Create server with share + share = self.create_share(share_network_id=new_sn['id']) + + # List share servers, filtered by share_network_id + search_opts = {"share_network": new_sn["id"]} + servers = self.shares_client.list_share_servers(search_opts) + + # There can be more than one share server for share network when retry + # was used and share was created successfully not from first time. + # So, iterate all share-servers, release all created resources. It will + # allow share network to be deleted in cleanup. + for serv in servers: + # Verify that filtering worked as expected. + self.assertEqual(new_sn["id"], serv["share_network_id"]) + + # List shares by share server id + params = {"share_server_id": serv["id"]} + shares = self.shares_client.list_shares_with_detail(params) + for s in shares: + self.assertEqual(new_sn["id"], s["share_network_id"]) + self.assertTrue(any(share["id"] == s["id"] for s in shares)) + + # Delete shares, so we will have share server without shares + for s in shares: + self.shares_client.delete_share(s["id"]) + + # Wait for shares deletion + for s in shares: + self.shares_client.wait_for_resource_deletion(share_id=s["id"]) + + # List shares by share server id, we expect empty list + params = {"share_server_id": serv["id"]} + empty = self.shares_client.list_shares_with_detail(params) + self.assertEqual(len(empty), 0) + + if delete_share_network: + # Delete share network, it should trigger share server deletion + self.shares_client.delete_share_network(new_sn["id"]) + else: + # Delete share server + self.shares_client.delete_share_server(serv["id"]) + + # Wait for share server deletion + self.shares_client.wait_for_resource_deletion(server_id=serv["id"]) + + if delete_share_network: + self.shares_client.wait_for_resource_deletion( + sn_id=new_sn["id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_delete_share_server(self): + self._delete_share_server(False) + + @test.attr(type=["gate", "smoke", ]) + def test_delete_share_server_by_deletion_of_share_network(self): + self._delete_share_server(True) diff --git a/manila_tempest_tests/tests/api/admin/test_share_servers_negative.py b/manila_tempest_tests/tests/api/admin/test_share_servers_negative.py new file mode 100644 index 00000000..b664de5f --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_servers_negative.py @@ -0,0 +1,108 @@ +# Copyright 2014 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. + +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + + +class ShareServersNegativeAdminTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(ShareServersNegativeAdminTest, cls).resource_setup() + cls.member_shares_client = clients.Manager().shares_client + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_try_list_share_servers_with_member(self): + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.list_share_servers) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_try_show_share_server_with_member(self): + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.show_share_server, + 'fake_id') + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_try_show_share_server_details_with_member(self): + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.show_share_server_details, + 'fake_id') + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_show_share_server_with_inexistent_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.show_share_server, + 'fake_id') + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_show_share_server_details_with_inexistent_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.show_share_server_details, + 'fake_id') + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_share_servers_with_wrong_filter_key(self): + search_opts = {'fake_filter_key': 'ACTIVE'} + servers = self.shares_client.list_share_servers(search_opts) + self.assertEqual(len(servers), 0) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_share_servers_with_wrong_filter_value(self): + search_opts = {'host': 123} + servers = self.shares_client.list_share_servers(search_opts) + self.assertEqual(len(servers), 0) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_share_servers_with_fake_status(self): + search_opts = {"status": data_utils.rand_name("fake_status")} + servers = self.shares_client.list_share_servers(search_opts) + self.assertEqual(len(servers), 0) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_share_servers_with_fake_host(self): + search_opts = {"host": data_utils.rand_name("fake_host")} + servers = self.shares_client.list_share_servers(search_opts) + self.assertEqual(len(servers), 0) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_share_servers_with_fake_project(self): + search_opts = {"project_id": data_utils.rand_name("fake_project_id")} + servers = self.shares_client.list_share_servers(search_opts) + self.assertEqual(len(servers), 0) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_share_servers_with_fake_share_network(self): + search_opts = { + "share_network": data_utils.rand_name("fake_share_network"), + } + servers = self.shares_client.list_share_servers(search_opts) + self.assertEqual(len(servers), 0) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_delete_share_server_with_nonexistent_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share_server, + "fake_nonexistent_share_server_id") + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_delete_share_server_with_member(self): + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.delete_share_server, + "fake_nonexistent_share_server_id") diff --git a/manila_tempest_tests/tests/api/admin/test_share_types.py b/manila_tempest_tests/tests/api/admin/test_share_types.py new file mode 100644 index 00000000..465d8d5a --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_types.py @@ -0,0 +1,160 @@ +# Copyright 2014 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. + +from tempest import config # noqa +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests.tests.api import base + + +CONF = config.CONF + + +class ShareTypesAdminTest(base.BaseSharesAdminTest): + + @test.attr(type=["gate", "smoke", ]) + def test_share_type_create_delete(self): + name = data_utils.rand_name("tempest-manila") + extra_specs = self.add_required_extra_specs_to_dict() + + # Create share type + st_create = self.shares_client.create_share_type( + name, extra_specs=extra_specs) + self.assertEqual(name, st_create['share_type']['name']) + st_id = st_create['share_type']['id'] + + # Delete share type + self.shares_client.delete_share_type(st_id) + + # Verify deletion of share type + self.shares_client.wait_for_resource_deletion(st_id=st_id) + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type, + st_id) + + @test.attr(type=["gate", "smoke", ]) + def test_share_type_create_get(self): + name = data_utils.rand_name("tempest-manila") + extra_specs = self.add_required_extra_specs_to_dict({"key": "value", }) + + # Create share type + st_create = self.create_share_type(name, extra_specs=extra_specs) + self.assertEqual(name, st_create['share_type']['name']) + st_id = st_create["share_type"]["id"] + + # Get share type + get = self.shares_client.get_share_type(st_id) + self.assertEqual(name, get["share_type"]["name"]) + self.assertEqual(st_id, get["share_type"]["id"]) + self.assertEqual(extra_specs, get["share_type"]["extra_specs"]) + + # Check that backwards compatibility didn't break + self.assertDictMatch(get["volume_type"], get["share_type"]) + + @test.attr(type=["gate", "smoke", ]) + def test_share_type_create_list(self): + name = data_utils.rand_name("tempest-manila") + extra_specs = self.add_required_extra_specs_to_dict() + + # Create share type + st_create = self.create_share_type(name, extra_specs=extra_specs) + st_id = st_create["share_type"]["id"] + + # list share types + st_list = self.shares_client.list_share_types() + sts = st_list["share_types"] + self.assertTrue(len(sts) >= 1) + self.assertTrue(any(st_id in st["id"] for st in sts)) + + # Check that backwards compatibility didn't break + vts = st_list["volume_types"] + self.assertEqual(len(sts), len(vts)) + for i in xrange(len(sts)): + self.assertDictMatch(sts[i], vts[i]) + + @test.attr(type=["gate", "smoke", ]) + def test_get_share_with_share_type(self): + + # Data + share_name = data_utils.rand_name("share") + shr_type_name = data_utils.rand_name("share-type") + extra_specs = self.add_required_extra_specs_to_dict({ + "storage_protocol": CONF.share.storage_protocol, + }) + + # Create share type + st_create = self.create_share_type( + shr_type_name, extra_specs=extra_specs) + + # Create share with share type + share = self.create_share( + name=share_name, share_type_id=st_create["share_type"]["id"]) + self.assertEqual(share["name"], share_name) + self.shares_client.wait_for_share_status(share["id"], "available") + + # Verify share info + get = self.shares_client.get_share(share["id"]) + self.assertEqual(share_name, get["name"]) + self.assertEqual(share["id"], get["id"]) + self.assertEqual(shr_type_name, get["share_type"]) + + def test_private_share_type_access(self): + name = data_utils.rand_name("tempest-manila") + extra_specs = self.add_required_extra_specs_to_dict({"key": "value", }) + project_id = self.shares_client.tenant_id + + # Create private share type + st_create = self.create_share_type( + name, False, extra_specs=extra_specs) + self.assertEqual(name, st_create['share_type']['name']) + st_id = st_create["share_type"]["id"] + + # It should not be listed without access + st_list = self.shares_client.list_share_types() + sts = st_list["share_types"] + self.assertFalse(any(st_id in st["id"] for st in sts)) + + # List projects that have access for share type - none expected + access = self.shares_client.list_access_to_share_type(st_id) + self.assertEqual([], access) + + # Add project access to share type + access = self.shares_client.add_access_to_share_type( + st_id, project_id) + + # Now it should be listed + st_list = self.shares_client.list_share_types() + sts = st_list["share_types"] + self.assertTrue(any(st_id in st["id"] for st in sts)) + + # List projects that have access for share type - one expected + access = self.shares_client.list_access_to_share_type(st_id) + expected = [{'share_type_id': st_id, 'project_id': project_id}, ] + self.assertEqual(expected, access) + + # Remove project access from share type + access = self.shares_client.remove_access_from_share_type( + st_id, project_id) + + # It should not be listed without access + st_list = self.shares_client.list_share_types() + sts = st_list["share_types"] + self.assertFalse(any(st_id in st["id"] for st in sts)) + + # List projects that have access for share type - none expected + access = self.shares_client.list_access_to_share_type(st_id) + self.assertEqual([], access) diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py new file mode 100644 index 00000000..c4be5a84 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py @@ -0,0 +1,111 @@ +# Copyright 2014 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 copy + +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa + +from manila_tempest_tests.tests.api import base + + +class ExtraSpecsReadAdminTest(base.BaseSharesAdminTest): + + @classmethod + def resource_setup(cls): + super(ExtraSpecsReadAdminTest, cls).resource_setup() + cls.share_type_name = data_utils.rand_name("share-type") + cls.required_extra_specs = cls.add_required_extra_specs_to_dict() + + cls.share_type = cls.create_share_type( + cls.share_type_name, extra_specs=cls.required_extra_specs) + + cls.st_id = cls.share_type["share_type"]["id"] + cls.custom_extra_specs = {"key1": "value1", "key2": "value2"} + cls.expected_extra_specs = copy.copy(cls.custom_extra_specs) + cls.expected_extra_specs.update(cls.required_extra_specs) + + cls.shares_client.create_share_type_extra_specs( + cls.st_id, cls.custom_extra_specs) + + @test.attr(type=["gate", "smoke", ]) + def test_get_one_share_type_extra_spec(self): + es_get_one = self.shares_client.get_share_type_extra_spec( + self.st_id, "key1") + + self.assertEqual({"key1": self.custom_extra_specs["key1"]}, es_get_one) + + @test.attr(type=["gate", "smoke", ]) + def test_get_all_share_type_extra_specs(self): + es_get_all = self.shares_client.get_share_type_extra_specs(self.st_id) + + self.assertEqual(self.expected_extra_specs, es_get_all) + + +class ExtraSpecsWriteAdminTest(base.BaseSharesAdminTest): + + def setUp(self): + super(ExtraSpecsWriteAdminTest, self).setUp() + self.required_extra_specs = self.add_required_extra_specs_to_dict() + self.custom_extra_specs = {"key1": "value1", "key2": "value2"} + self.share_type_name = data_utils.rand_name("share-type") + + # Create share type + self.share_type = self.create_share_type( + self.share_type_name, extra_specs=self.required_extra_specs) + + self.st_id = self.share_type['share_type']['id'] + + # Create extra specs for share type + self.shares_client.create_share_type_extra_specs( + self.st_id, self.custom_extra_specs) + + @test.attr(type=["gate", "smoke", ]) + def test_update_one_share_type_extra_spec(self): + self.custom_extra_specs["key1"] = "fake_value1_updated" + + # Update extra specs of share type + update_one = self.shares_client.update_share_type_extra_spec( + self.st_id, "key1", self.custom_extra_specs["key1"]) + self.assertEqual({"key1": self.custom_extra_specs["key1"]}, update_one) + + get = self.shares_client.get_share_type_extra_specs(self.st_id) + expected_extra_specs = self.custom_extra_specs + expected_extra_specs.update(self.required_extra_specs) + self.assertEqual(self.custom_extra_specs, get) + + @test.attr(type=["gate", "smoke", ]) + def test_update_all_share_type_extra_specs(self): + self.custom_extra_specs["key2"] = "value2_updated" + + # Update extra specs of share type + update_all = self.shares_client.update_share_type_extra_specs( + self.st_id, self.custom_extra_specs) + self.assertEqual(self.custom_extra_specs, update_all) + + get = self.shares_client.get_share_type_extra_specs(self.st_id) + expected_extra_specs = self.custom_extra_specs + expected_extra_specs.update(self.required_extra_specs) + self.assertEqual(self.custom_extra_specs, get) + + @test.attr(type=["gate", "smoke", ]) + def test_delete_one_share_type_extra_spec(self): + # Delete one extra spec for share type + self.shares_client.delete_share_type_extra_spec(self.st_id, "key1") + + # Get metadata + get = self.shares_client.get_share_type_extra_specs(self.st_id) + + self.assertNotIn('key1', get) diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py new file mode 100644 index 00000000..aaad426e --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py @@ -0,0 +1,269 @@ +# Copyright 2014 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. + +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + + +class ExtraSpecsAdminNegativeTest(base.BaseSharesAdminTest): + + def _create_share_type(self): + name = data_utils.rand_name("unique_st_name") + extra_specs = self.add_required_extra_specs_to_dict({"key": "value"}) + return self.create_share_type(name, extra_specs=extra_specs) + + @classmethod + def resource_setup(cls): + super(ExtraSpecsAdminNegativeTest, cls).resource_setup() + cls.member_shares_client = clients.Manager().shares_client + + @test.attr(type=["gate", "smoke", ]) + def test_try_create_extra_specs_with_user(self): + st = self._create_share_type() + self.assertRaises( + lib_exc.Forbidden, + self.member_shares_client.create_share_type_extra_specs, + st["share_type"]["id"], + self.add_required_extra_specs_to_dict({"key": "new_value"})) + + @test.attr(type=["gate", "smoke", ]) + def test_try_list_extra_specs_with_user(self): + st = self._create_share_type() + self.assertRaises( + lib_exc.Forbidden, + self.member_shares_client.get_share_type_extra_specs, + st["share_type"]["id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_try_get_extra_spec_with_user(self): + st = self._create_share_type() + self.assertRaises( + lib_exc.Forbidden, + self.member_shares_client.get_share_type_extra_spec, + st["share_type"]["id"], "key") + + @test.attr(type=["gate", "smoke", ]) + def test_try_get_extra_specs_with_user(self): + st = self._create_share_type() + self.assertRaises( + lib_exc.Forbidden, + self.member_shares_client.get_share_type_extra_specs, + st["share_type"]["id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_try_update_extra_spec_with_user(self): + st = self._create_share_type() + self.assertRaises( + lib_exc.Forbidden, + self.member_shares_client.update_share_type_extra_spec, + st["share_type"]["id"], "key", "new_value") + + @test.attr(type=["gate", "smoke", ]) + def test_try_update_extra_specs_with_user(self): + st = self._create_share_type() + self.assertRaises( + lib_exc.Forbidden, + self.member_shares_client.update_share_type_extra_specs, + st["share_type"]["id"], {"key": "new_value"}) + + @test.attr(type=["gate", "smoke", ]) + def test_try_delete_extra_specs_with_user(self): + st = self._create_share_type() + self.assertRaises( + lib_exc.Forbidden, + self.member_shares_client.delete_share_type_extra_spec, + st["share_type"]["id"], "key") + + @test.attr(type=["gate", "smoke", ]) + def test_try_set_too_long_key(self): + too_big_key = "k" * 256 + st = self._create_share_type() + self.assertRaises( + lib_exc.BadRequest, + self.shares_client.create_share_type_extra_specs, + st["share_type"]["id"], + self.add_required_extra_specs_to_dict({too_big_key: "value"})) + + @test.attr(type=["gate", "smoke", ]) + def test_try_set_too_long_value_with_creation(self): + too_big_value = "v" * 256 + st = self._create_share_type() + self.assertRaises( + lib_exc.BadRequest, + self.shares_client.create_share_type_extra_specs, + st["share_type"]["id"], + self.add_required_extra_specs_to_dict({"key": too_big_value})) + + @test.attr(type=["gate", "smoke", ]) + def test_try_set_too_long_value_with_update(self): + too_big_value = "v" * 256 + st = self._create_share_type() + self.shares_client.create_share_type_extra_specs( + st["share_type"]["id"], + self.add_required_extra_specs_to_dict({"key": "value"})) + self.assertRaises( + lib_exc.BadRequest, + self.shares_client.update_share_type_extra_specs, + st["share_type"]["id"], + self.add_required_extra_specs_to_dict({"key": too_big_value})) + + @test.attr(type=["gate", "smoke", ]) + def test_try_set_too_long_value_with_update_of_one_key(self): + too_big_value = "v" * 256 + st = self._create_share_type() + self.shares_client.create_share_type_extra_specs( + st["share_type"]["id"], + self.add_required_extra_specs_to_dict({"key": "value"})) + self.assertRaises(lib_exc.BadRequest, + self.shares_client.update_share_type_extra_spec, + st["share_type"]["id"], "key", too_big_value) + + @test.attr(type=["gate", "smoke", ]) + def test_try_list_es_with_empty_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type_extra_specs, "") + + @test.attr(type=["gate", "smoke", ]) + def test_try_list_es_with_invalid_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type_extra_specs, + data_utils.rand_name("fake")) + + @test.attr(type=["gate", "smoke", ]) + def test_try_create_es_with_empty_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_share_type_extra_specs, + "", {"key1": "value1", }) + + @test.attr(type=["gate", "smoke", ]) + def test_try_create_es_with_invalid_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_share_type_extra_specs, + data_utils.rand_name("fake"), {"key1": "value1", }) + + @test.attr(type=["gate", "smoke", ]) + def test_try_create_es_with_empty_specs(self): + st = self._create_share_type() + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_share_type_extra_specs, + st["share_type"]["id"], "") + + @test.attr(type=["gate", "smoke", ]) + def test_try_create_es_with_invalid_specs(self): + st = self._create_share_type() + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_share_type_extra_specs, + st["share_type"]["id"], {"": "value_with_empty_key"}) + + @test.attr(type=["gate", "smoke", ]) + def test_try_get_extra_spec_with_empty_key(self): + st = self._create_share_type() + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type_extra_spec, + st["share_type"]["id"], "") + + @test.attr(type=["gate", "smoke", ]) + def test_try_get_extra_spec_with_invalid_key(self): + st = self._create_share_type() + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type_extra_spec, + st["share_type"]["id"], data_utils.rand_name("fake")) + + @test.attr(type=["gate", "smoke", ]) + def test_try_get_extra_specs_with_empty_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type_extra_specs, + "") + + @test.attr(type=["gate", "smoke", ]) + def test_try_get_extra_specs_with_invalid_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type_extra_specs, + data_utils.rand_name("fake")) + + @test.attr(type=["gate", "smoke", ]) + def test_try_delete_es_key_with_empty_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share_type_extra_spec, + "", "key", ) + + @test.attr(type=["gate", "smoke", ]) + def test_try_delete_es_key_with_invalid_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share_type_extra_spec, + data_utils.rand_name("fake"), "key", ) + + @test.attr(type=["gate", "smoke", ]) + def test_try_delete_with_invalid_key(self): + st = self._create_share_type() + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share_type_extra_spec, + st["share_type"]["id"], data_utils.rand_name("fake")) + + @test.attr(type=["gate", "smoke", ]) + def test_try_update_spec_with_empty_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_share_type_extra_spec, + "", "key", "new_value") + + @test.attr(type=["gate", "smoke", ]) + def test_try_update_spec_with_invalid_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_share_type_extra_spec, + data_utils.rand_name("fake"), "key", "new_value") + + @test.attr(type=["gate", "smoke", ]) + def test_try_update_spec_with_empty_key(self): + st = self._create_share_type() + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_share_type_extra_spec, + st["share_type"]["id"], "", "new_value") + + @test.attr(type=["gate", "smoke", ]) + def test_try_update_with_invalid_shr_type_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_share_type_extra_specs, + data_utils.rand_name("fake"), {"key": "new_value"}) + + @test.attr(type=["gate", "smoke", ]) + def test_try_update_with_invalid_specs(self): + st = self._create_share_type() + self.assertRaises(lib_exc.BadRequest, + self.shares_client.update_share_type_extra_specs, + st["share_type"]["id"], {"": "new_value"}) + + @test.attr(type=["gate", "smoke", ]) + def test_try_delete_spec_driver_handles_share_servers(self): + st = self._create_share_type() + + # Try delete extra spec 'driver_handles_share_servers' + self.assertRaises(lib_exc.Forbidden, + self.shares_client.delete_share_type_extra_spec, + st["share_type"]["id"], + "driver_handles_share_servers") + + @test.attr(type=["gate", "smoke", ]) + def test_try_delete_spec_snapshot_support(self): + st = self._create_share_type() + + # Try delete extra spec 'snapshot_support' + self.assertRaises(lib_exc.Forbidden, + self.shares_client.delete_share_type_extra_spec, + st["share_type"]["id"], + "snapshot_support") diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_negative.py b/manila_tempest_tests/tests/api/admin/test_share_types_negative.py new file mode 100644 index 00000000..62e4523a --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_types_negative.py @@ -0,0 +1,70 @@ +# Copyright 2014 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. + +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + + +class ShareTypesAdminNegativeTest(base.BaseSharesAdminTest): + + def _create_share_type(self): + name = data_utils.rand_name("unique_st_name") + extra_specs = self.add_required_extra_specs_to_dict({"key": "value"}) + return self.create_share_type(name, extra_specs=extra_specs) + + @classmethod + def resource_setup(cls): + super(ShareTypesAdminNegativeTest, cls).resource_setup() + cls.member_shares_client = clients.Manager().shares_client + + @test.attr(type=["gate", "smoke", ]) + def test_create_share_with_nonexistent_share_type(self): + self.assertRaises(lib_exc.NotFound, + self.create_share, + share_type_id=data_utils.rand_name("fake")) + + @test.attr(type=["gate", "smoke", ]) + def test_create_share_type_with_empty_name(self): + self.assertRaises(lib_exc.BadRequest, self.create_share_type, '') + + @test.attr(type=["gate", "smoke", ]) + def test_create_share_type_with_too_big_name(self): + self.assertRaises(lib_exc.BadRequest, + self.create_share_type, + "x" * 256) + + @test.attr(type=["gate", "smoke", ]) + def test_get_share_type_by_nonexistent_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_type, + data_utils.rand_name("fake")) + + @test.attr(type=["gate", "smoke", ]) + def test_try_delete_share_type_by_nonexistent_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share_type, + data_utils.rand_name("fake")) + + @test.attr(type=["gate", "smoke", ]) + def test_try_create_duplicate_of_share_type(self): + st = self._create_share_type() + self.assertRaises(lib_exc.Conflict, + self.create_share_type, + st["share_type"]["name"], + extra_specs=self.add_required_extra_specs_to_dict()) diff --git a/manila_tempest_tests/tests/api/admin/test_shares_actions.py b/manila_tempest_tests/tests/api/admin/test_shares_actions.py new file mode 100644 index 00000000..b2904c49 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_shares_actions.py @@ -0,0 +1,373 @@ +# Copyright 2014 Mirantis Inc. All Rights Reserved. +# Copyright (c) 2015 Yogesh Kshirsagar. 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 config # noqa +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SharesActionsAdminTest(base.BaseSharesAdminTest): + """Covers share functionality, that doesn't related to share type.""" + + @classmethod + def resource_setup(cls): + super(SharesActionsAdminTest, cls).resource_setup() + + cls.shares = [] + + # create share type for share filtering purposes + cls.st_name = data_utils.rand_name("tempest-st-name") + cls.extra_specs = cls.add_required_extra_specs_to_dict( + {'storage_protocol': CONF.share.storage_protocol}) + cls.st = cls.create_share_type( + name=cls.st_name, + cleanup_in_class=True, + extra_specs=cls.extra_specs, + ) + + # create share + cls.share_name = data_utils.rand_name("tempest-share-name") + cls.share_desc = data_utils.rand_name("tempest-share-description") + cls.metadata = { + 'foo_key_share_1': 'foo_value_share_1', + 'bar_key_share_1': 'foo_value_share_1', + } + cls.share_size = 1 + cls.shares.append(cls.create_share( + name=cls.share_name, + description=cls.share_desc, + size=cls.share_size, + metadata=cls.metadata, + share_type_id=cls.st['share_type']['id'], + )) + + if CONF.share.run_snapshot_tests: + # create snapshot + cls.snap_name = data_utils.rand_name("tempest-snapshot-name") + cls.snap_desc = data_utils.rand_name( + "tempest-snapshot-description") + cls.snap = cls.create_snapshot_wait_for_active( + cls.shares[0]["id"], cls.snap_name, cls.snap_desc) + + # create second share from snapshot for purposes of sorting and + # snapshot filtering + cls.share_name2 = data_utils.rand_name("tempest-share-name") + cls.share_desc2 = data_utils.rand_name("tempest-share-description") + cls.metadata2 = { + 'foo_key_share_2': 'foo_value_share_2', + 'bar_key_share_2': 'foo_value_share_2', + } + cls.shares.append(cls.create_share( + name=cls.share_name2, + description=cls.share_desc2, + size=cls.share_size, + metadata=cls.metadata2, + snapshot_id=cls.snap['id'], + )) + + @test.attr(type=["gate", ]) + def test_get_share(self): + + # get share + share = self.shares_client.get_share(self.shares[0]['id']) + + # verify keys + expected_keys = ["status", "description", "links", "availability_zone", + "created_at", "export_location", "share_proto", + "name", "snapshot_id", "id", "size"] + actual_keys = share.keys() + [self.assertIn(key, actual_keys) for key in expected_keys] + + # verify values + msg = "Expected name: '%s', actual name: '%s'" % (self.share_name, + share["name"]) + self.assertEqual(self.share_name, str(share["name"]), msg) + + msg = "Expected description: '%s', "\ + "actual description: '%s'" % (self.share_desc, + share["description"]) + self.assertEqual(self.share_desc, str(share["description"]), msg) + + msg = "Expected size: '%s', actual size: '%s'" % (self.share_size, + share["size"]) + self.assertEqual(self.share_size, int(share["size"]), msg) + + @test.attr(type=["gate", ]) + def test_list_shares(self): + + # list shares + shares = self.shares_client.list_shares() + + # verify keys + keys = ["name", "id", "links"] + [self.assertIn(key, sh.keys()) for sh in shares for key in keys] + + # our share id in list and have no duplicates + for share in self.shares: + gen = [sid["id"] for sid in shares if sid["id"] in share["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail(self): + + # list shares + shares = self.shares_client.list_shares_with_detail() + + # verify keys + keys = [ + "status", "description", "links", "availability_zone", + "created_at", "export_location", "share_proto", "host", + "name", "snapshot_id", "id", "size", "project_id", + ] + [self.assertIn(key, sh.keys()) for sh in shares for key in keys] + + # our shares in list and have no duplicates + for share in self.shares: + gen = [sid["id"] for sid in shares if sid["id"] in share["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_metadata(self): + filters = {'metadata': self.metadata} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertDictContainsSubset( + filters['metadata'], share['metadata']) + if CONF.share.run_snapshot_tests: + self.assertFalse(self.shares[1]['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_extra_specs(self): + filters = { + "extra_specs": {'storage_protocol': CONF.share.storage_protocol} + } + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + shares_ids = [s["id"] for s in shares] + for share in self.shares: + self.assertTrue(share["id"] in shares_ids) + for share in shares: + st_list = self.shares_client.list_share_types() + # find its name or id, get id + sts = st_list["share_types"] + st_id = None + for st in sts: + if share["share_type"] in [st["id"], st["name"]]: + st_id = st["id"] + break + if st_id is None: + raise ValueError( + "Share '%(s_id)s' listed with extra_specs filter has " + "nonexistent share type '%(st)s'." % { + "s_id": share["id"], "st": share["share_type"]} + ) + extra_specs = self.shares_client.get_share_type_extra_specs(st_id) + self.assertDictContainsSubset(filters["extra_specs"], extra_specs) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_share_type_id(self): + filters = {'share_type_id': self.st['share_type']['id']} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + st_list = self.shares_client.list_share_types() + # find its name or id, get id + sts = st_list["share_types"] + st_id = None + for st in sts: + if share["share_type"] in [st["id"], st["name"]]: + st_id = st["id"] + break + if st_id is None: + raise ValueError( + "Share '%(s_id)s' listed with share_type_id filter has " + "nonexistent share type '%(st)s'." % { + "s_id": share["id"], "st": share["share_type"]} + ) + self.assertEqual( + filters['share_type_id'], st_id) + share_ids = [share['id'] for share in shares] + for share in self.shares: + self.assertTrue(share['id'] in share_ids) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_host(self): + base_share = self.shares_client.get_share(self.shares[0]['id']) + filters = {'host': base_share['host']} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['host'], share['host']) + + @test.attr(type=["gate", ]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_list_shares_with_detail_filter_by_share_network_id(self): + base_share = self.shares_client.get_share(self.shares[0]['id']) + filters = {'share_network_id': base_share['share_network_id']} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual( + filters['share_network_id'], share['share_network_id']) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_shares_with_detail_filter_by_snapshot_id(self): + filters = {'snapshot_id': self.snap['id']} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['snapshot_id'], share['snapshot_id']) + self.assertFalse(self.shares[0]['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_with_asc_sorting(self): + filters = {'sort_key': 'created_at', 'sort_dir': 'asc'} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + sorted_list = [share['created_at'] for share in shares] + self.assertEqual(sorted_list, sorted(sorted_list)) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_existed_name(self): + # list shares by name, at least one share is expected + params = {"name": self.share_name} + shares = self.shares_client.list_shares_with_detail(params) + self.assertEqual(shares[0]["name"], self.share_name) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_fake_name(self): + # list shares by fake name, no shares are expected + params = {"name": data_utils.rand_name("fake-nonexistent-name")} + shares = self.shares_client.list_shares_with_detail(params) + self.assertEqual(0, len(shares)) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_active_status(self): + # list shares by active status, at least one share is expected + params = {"status": "available"} + shares = self.shares_client.list_shares_with_detail(params) + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(share["status"], params["status"]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_fake_status(self): + # list shares by fake status, no shares are expected + params = {"status": 'fake'} + shares = self.shares_client.list_shares_with_detail(params) + self.assertEqual(0, len(shares)) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_get_snapshot(self): + + # get snapshot + get = self.shares_client.get_snapshot(self.snap["id"]) + + # verify keys + expected_keys = ["status", "links", "share_id", "name", + "share_proto", "created_at", + "description", "id", "share_size"] + actual_keys = get.keys() + [self.assertIn(key, actual_keys) for key in expected_keys] + + # verify data + msg = "Expected name: '%s', actual name: '%s'" % (self.snap_name, + get["name"]) + self.assertEqual(self.snap_name, get["name"], msg) + + msg = "Expected description: '%s', "\ + "actual description: '%s'" % (self.snap_desc, get["description"]) + self.assertEqual(self.snap_desc, get["description"], msg) + + msg = "Expected share_id: '%s', "\ + "actual share_id: '%s'" % (self.shares[0]["id"], get["share_id"]) + self.assertEqual(self.shares[0]["id"], get["share_id"], msg) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_snapshots(self): + + # list share snapshots + snaps = self.shares_client.list_snapshots() + + # verify keys + keys = ["id", "name", "links"] + [self.assertIn(key, sn.keys()) for sn in snaps for key in keys] + + # our share id in list and have no duplicates + gen = [sid["id"] for sid in snaps if sid["id"] in self.snap["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_snapshots_with_detail(self): + + # list share snapshots + snaps = self.shares_client.list_snapshots_with_detail() + + # verify keys + keys = ["status", "links", "share_id", "name", + "share_proto", "created_at", + "description", "id", "share_size"] + [self.assertIn(key, sn.keys()) for sn in snaps for key in keys] + + # our share id in list and have no duplicates + gen = [sid["id"] for sid in snaps if sid["id"] in self.snap["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py new file mode 100644 index 00000000..554ba35c --- /dev/null +++ b/manila_tempest_tests/tests/api/base.py @@ -0,0 +1,618 @@ +# 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. + +import copy +import inspect +import traceback + +from oslo_concurrency import lockutils +from oslo_log import log +import six +from tempest.common import isolated_creds # noqa +from tempest import config # noqa +from tempest import test # noqa +from tempest_lib.common.utils import data_utils +from tempest_lib import exceptions + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests import share_exceptions + +CONF = config.CONF +LOG = log.getLogger(__name__) + + +class handle_cleanup_exceptions(object): + """Handle exceptions raised with cleanup operations. + + Always suppress errors when exceptions.NotFound or exceptions.Forbidden + are raised. + Suppress all other exceptions only in case config opt + 'suppress_errors_in_cleanup' in config group 'share' is True. + """ + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + if not (isinstance(exc_value, + (exceptions.NotFound, exceptions.Forbidden)) or + CONF.share.suppress_errors_in_cleanup): + return False # Do not suppress error if any + if exc_traceback: + LOG.error("Suppressed cleanup error in Manila: " + "\n%s" % traceback.format_exc()) + return True # Suppress error if any + + +def network_synchronized(f): + + def wrapped_func(self, *args, **kwargs): + with_isolated_creds = True if len(args) > 2 else False + no_lock_required = kwargs.get( + "isolated_creds_client", with_isolated_creds) + if no_lock_required: + # Usage of not reusable network. No need in lock. + return f(self, *args, **kwargs) + + # Use lock assuming reusage of common network. + @lockutils.synchronized("manila_network_lock", external=True) + def source_func(self, *args, **kwargs): + return f(self, *args, **kwargs) + + return source_func(self, *args, **kwargs) + + return wrapped_func + + +class BaseSharesTest(test.BaseTestCase): + """Base test case class for all Manila API tests.""" + + force_tenant_isolation = False + protocols = ["nfs", "cifs", "glusterfs", "hdfs"] + + # Will be cleaned up in resource_cleanup + class_resources = [] + + # Will be cleaned up in tearDown method + method_resources = [] + + # Will be cleaned up in resource_cleanup + class_isolated_creds = [] + + # Will be cleaned up in tearDown method + method_isolated_creds = [] + + @classmethod + def get_client_with_isolated_creds(cls, + name=None, + type_of_creds="admin", + cleanup_in_class=False): + """Creates isolated creds. + + :param name: name, will be used for naming ic and related stuff + :param type_of_creds: admin, alt or primary + :param cleanup_in_class: defines place where to delete + :returns: SharesClient -- shares client with isolated creds. + :returns: To client added dict attr 'creds' with + :returns: key elements 'tenant' and 'user'. + """ + if name is None: + # Get name of test method + name = inspect.stack()[1][3] + if len(name) > 32: + name = name[0:32] + + # Choose type of isolated creds + ic = isolated_creds.IsolatedCreds(name=name) + if "admin" in type_of_creds: + creds = ic.get_admin_creds() + elif "alt" in type_of_creds: + creds = ic.get_alt_creds() + else: + creds = ic.self.get_credentials(type_of_creds) + ic.type_of_creds = type_of_creds + + # create client with isolated creds + os = clients.Manager(credentials=creds) + client = os.shares_client + + # Set place where will be deleted isolated creds + ic_res = { + "method": ic.clear_isolated_creds, + "deleted": False, + } + if cleanup_in_class: + cls.class_isolated_creds.insert(0, ic_res) + else: + cls.method_isolated_creds.insert(0, ic_res) + + # Provide share network + if CONF.share.multitenancy_enabled: + if not CONF.service_available.neutron: + raise cls.skipException("Neutron support is required") + nc = os.network_client + share_network_id = cls.provide_share_network(client, nc, ic) + client.share_network_id = share_network_id + resource = { + "type": "share_network", + "id": client.share_network_id, + "client": client, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + return client + + @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 (any(p in CONF.share.enable_protocols + for p in cls.protocols) and + CONF.service_available.manila): + skip_msg = "Manila is disabled" + raise cls.skipException(skip_msg) + super(BaseSharesTest, cls).resource_setup() + if not hasattr(cls, "os"): + cls.username = CONF.identity.username + cls.password = CONF.identity.password + cls.tenant_name = CONF.identity.tenant_name + cls.verify_nonempty(cls.username, cls.password, cls.tenant_name) + cls.os = clients.Manager() + if CONF.share.multitenancy_enabled: + if not CONF.service_available.neutron: + raise cls.skipException("Neutron support is required") + sc = cls.os.shares_client + nc = cls.os.network_client + share_network_id = cls.provide_share_network(sc, nc) + cls.os.shares_client.share_network_id = share_network_id + cls.shares_client = cls.os.shares_client + + def setUp(self): + super(BaseSharesTest, self).setUp() + self.addCleanup(self.clear_resources) + self.addCleanup(self.clear_isolated_creds) + + @classmethod + def resource_cleanup(cls): + super(BaseSharesTest, cls).resource_cleanup() + cls.clear_resources(cls.class_resources) + cls.clear_isolated_creds(cls.class_isolated_creds) + + @classmethod + @network_synchronized + def provide_share_network(cls, shares_client, network_client, + isolated_creds_client=None): + """Used for finding/creating share network for multitenant driver. + + This method creates/gets entity share-network for one tenant. This + share-network will be used for creation of service vm. + + :param shares_client: shares client, which requires share-network + :param network_client: network client from same tenant as shares + :param isolated_creds_client: IsolatedCreds instance + If provided, then its networking will be used if needed. + If not provided, then common network will be used if needed. + :returns: str -- share network id for shares_client tenant + :returns: None -- if single-tenant driver used + """ + + sc = shares_client + + if not CONF.share.multitenancy_enabled: + # Assumed usage of a single-tenant driver + share_network_id = None + elif sc.share_network_id: + # Share-network already exists, use it + share_network_id = sc.share_network_id + else: + net_id = subnet_id = share_network_id = None + + if not isolated_creds_client: + # Search for networks, created in previous runs + search_word = "reusable" + sn_name = "autogenerated_by_tempest_%s" % search_word + service_net_name = "share-service" + networks = network_client.list_networks() + if "networks" in networks.keys(): + networks = networks["networks"] + for network in networks: + if (service_net_name in network["name"] and + sc.tenant_id == network['tenant_id']): + net_id = network["id"] + if len(network["subnets"]) > 0: + subnet_id = network["subnets"][0] + break + + # Create suitable network + if (net_id is None or subnet_id is None): + ic = isolated_creds.IsolatedCreds(name=service_net_name) + net_data = ic._create_network_resources(sc.tenant_id) + network, subnet, router = net_data + net_id = network["id"] + subnet_id = subnet["id"] + + # Try get suitable share-network + share_networks = sc.list_share_networks_with_detail() + for sn in share_networks: + if (net_id == sn["neutron_net_id"] and + subnet_id == sn["neutron_subnet_id"] and + sn["name"] and search_word in sn["name"]): + share_network_id = sn["id"] + break + else: + sn_name = "autogenerated_by_tempest_for_isolated_creds" + # Use precreated network and subnet from isolated creds + net_id = isolated_creds_client.get_credentials( + isolated_creds_client.type_of_creds).network['id'] + subnet_id = isolated_creds_client.get_credentials( + isolated_creds_client.type_of_creds).subnet['id'] + + # Create suitable share-network + if share_network_id is None: + sn_desc = "This share-network was created by tempest" + sn = sc.create_share_network(name=sn_name, + description=sn_desc, + neutron_net_id=net_id, + neutron_subnet_id=subnet_id) + share_network_id = sn["id"] + + return share_network_id + + @classmethod + def _create_share(cls, share_protocol=None, size=1, name=None, + snapshot_id=None, description=None, metadata=None, + share_network_id=None, share_type_id=None, + client=None, cleanup_in_class=True, is_public=False): + client = client or cls.shares_client + description = description or "Tempest's share" + share_network_id = share_network_id or client.share_network_id or None + metadata = metadata or {} + kwargs = { + 'share_protocol': share_protocol, + 'size': size, + 'name': name, + 'snapshot_id': snapshot_id, + 'description': description, + 'metadata': metadata, + 'share_network_id': share_network_id, + 'share_type_id': share_type_id, + 'is_public': is_public, + } + share = client.create_share(**kwargs) + resource = {"type": "share", "id": share["id"], "client": client} + cleanup_list = (cls.class_resources if cleanup_in_class else + cls.method_resources) + cleanup_list.insert(0, resource) + return share + + @classmethod + def create_share(cls, *args, **kwargs): + """Create one share and wait for available state. Retry if allowed.""" + result = cls.create_shares([{"args": args, "kwargs": kwargs}]) + return result[0] + + @classmethod + def create_shares(cls, share_data_list): + """Creates several shares in parallel with retries. + + Use this method when you want to create more than one share at same + time. Especially if config option 'share.share_creation_retry_number' + has value more than zero (0). + All shares will be expected to have 'available' status with or without + recreation else error will be raised. + + :param share_data_list: list -- list of dictionaries with 'args' and + 'kwargs' for '_create_share' method of this base class. + example of data: + share_data_list=[{'args': ['quuz'], 'kwargs': {'foo': 'bar'}}}] + :returns: list -- list of shares created using provided data. + """ + + data = [copy.deepcopy(d) for d in share_data_list] + for d in data: + if not isinstance(d, dict): + raise exceptions.TempestException( + "Expected 'dict', got '%s'" % type(d)) + if "args" not in d: + d["args"] = [] + if "kwargs" not in d: + d["kwargs"] = {} + if len(d) > 2: + raise exceptions.TempestException( + "Expected only 'args' and 'kwargs' keys. " + "Provided %s" % list(d)) + d["kwargs"]["client"] = d["kwargs"].get( + "client", cls.shares_client) + d["share"] = cls._create_share(*d["args"], **d["kwargs"]) + d["cnt"] = 0 + d["available"] = False + + while not all(d["available"] for d in data): + for d in data: + if d["available"]: + continue + try: + d["kwargs"]["client"].wait_for_share_status( + d["share"]["id"], "available") + d["available"] = True + except (share_exceptions.ShareBuildErrorException, + exceptions.TimeoutException) as e: + if CONF.share.share_creation_retry_number > d["cnt"]: + d["cnt"] += 1 + msg = ("Share '%s' failed to be built. " + "Trying create another." % d["share"]["id"]) + LOG.error(msg) + LOG.error(e) + d["share"] = cls._create_share( + *d["args"], **d["kwargs"]) + else: + raise e + + return [d["share"] for d in data] + + @classmethod + def create_snapshot_wait_for_active(cls, share_id, name=None, + description=None, force=False, + client=None, cleanup_in_class=True): + if client is None: + client = cls.shares_client + if description is None: + description = "Tempest's snapshot" + snapshot = client.create_snapshot(share_id, name, description, force) + resource = { + "type": "snapshot", + "id": snapshot["id"], + "client": client, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + client.wait_for_snapshot_status(snapshot["id"], "available") + return snapshot + + @classmethod + def create_share_network(cls, client=None, + cleanup_in_class=False, **kwargs): + if client is None: + client = cls.shares_client + share_network = client.create_share_network(**kwargs) + resource = { + "type": "share_network", + "id": share_network["id"], + "client": client, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + return share_network + + @classmethod + def create_security_service(cls, ss_type="ldap", client=None, + cleanup_in_class=False, **kwargs): + if client is None: + client = cls.shares_client + security_service = client.create_security_service(ss_type, **kwargs) + resource = { + "type": "security_service", + "id": security_service["id"], + "client": client, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + return security_service + + @classmethod + def create_share_type(cls, name, is_public=True, client=None, + cleanup_in_class=True, **kwargs): + if client is None: + client = cls.shares_client + share_type = client.create_share_type(name, is_public, **kwargs) + resource = { + "type": "share_type", + "id": share_type["share_type"]["id"], + "client": client, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + return share_type + + @staticmethod + def add_required_extra_specs_to_dict(extra_specs=None): + value = six.text_type(CONF.share.multitenancy_enabled) + required = { + "driver_handles_share_servers": value, + "snapshot_support": 'True', + } + if extra_specs: + required.update(extra_specs) + return required + + @classmethod + def clear_isolated_creds(cls, creds=None): + if creds is None: + creds = cls.method_isolated_creds + for ic in creds: + if "deleted" not in ic.keys(): + ic["deleted"] = False + if not ic["deleted"]: + with handle_cleanup_exceptions(): + ic["method"]() + ic["deleted"] = True + + @classmethod + def clear_resources(cls, resources=None): + """Deletes resources, that were created in test suites. + + This method tries to remove resources from resource list, + if it is not found, assumed it was deleted in test itself. + It is expected, that all resources were added as LIFO + due to restriction of deletion resources, that is in the chain. + + :param resources: dict with keys 'type','id','client' and 'deleted' + """ + + if resources is None: + resources = cls.method_resources + for res in resources: + if "deleted" not in res.keys(): + res["deleted"] = False + if "client" not in res.keys(): + res["client"] = cls.shares_client + if not(res["deleted"]): + res_id = res['id'] + client = res["client"] + with handle_cleanup_exceptions(): + if res["type"] is "share": + client.delete_share(res_id) + client.wait_for_resource_deletion(share_id=res_id) + elif res["type"] is "snapshot": + client.delete_snapshot(res_id) + client.wait_for_resource_deletion(snapshot_id=res_id) + elif res["type"] is "share_network": + client.delete_share_network(res_id) + client.wait_for_resource_deletion(sn_id=res_id) + elif res["type"] is "security_service": + client.delete_security_service(res_id) + client.wait_for_resource_deletion(ss_id=res_id) + elif res["type"] is "share_type": + client.delete_share_type(res_id) + client.wait_for_resource_deletion(st_id=res_id) + else: + LOG.warn("Provided unsupported resource type for " + "cleanup '%s'. Skipping." % res["type"]) + res["deleted"] = True + + @classmethod + def generate_share_network_data(self): + data = { + "name": data_utils.rand_name("sn-name"), + "description": data_utils.rand_name("sn-desc"), + "neutron_net_id": data_utils.rand_name("net-id"), + "neutron_subnet_id": data_utils.rand_name("subnet-id"), + } + return data + + @classmethod + def generate_security_service_data(self): + data = { + "name": data_utils.rand_name("ss-name"), + "description": data_utils.rand_name("ss-desc"), + "dns_ip": data_utils.rand_name("ss-dns_ip"), + "server": data_utils.rand_name("ss-server"), + "domain": data_utils.rand_name("ss-domain"), + "user": data_utils.rand_name("ss-user"), + "password": data_utils.rand_name("ss-password"), + } + return data + + # Useful assertions + def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001): + """Assert two dicts are equivalent. + + This is a 'deep' match in the sense that it handles nested + dictionaries appropriately. + + NOTE: + + If you don't care (or don't know) a given value, you can specify + the string DONTCARE as the value. This will cause that dict-item + to be skipped. + + """ + def raise_assertion(msg): + d1str = str(d1) + d2str = str(d2) + base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s ' + 'd2: %(d2str)s' % + {"msg": msg, "d1str": d1str, "d2str": d2str}) + raise AssertionError(base_msg) + + d1keys = set(d1.keys()) + d2keys = set(d2.keys()) + if d1keys != d2keys: + d1only = d1keys - d2keys + d2only = d2keys - d1keys + raise_assertion('Keys in d1 and not d2: %(d1only)s. ' + 'Keys in d2 and not d1: %(d2only)s' % + {"d1only": d1only, "d2only": d2only}) + + for key in d1keys: + d1value = d1[key] + d2value = d2[key] + try: + error = abs(float(d1value) - float(d2value)) + within_tolerance = error <= tolerance + except (ValueError, TypeError): + # If both values aren't convertable to float, just ignore + # ValueError if arg is a str, TypeError if it's something else + # (like None) + within_tolerance = False + + if hasattr(d1value, 'keys') and hasattr(d2value, 'keys'): + self.assertDictMatch(d1value, d2value) + elif 'DONTCARE' in (d1value, d2value): + continue + elif approx_equal and within_tolerance: + continue + elif d1value != d2value: + raise_assertion("d1['%(key)s']=%(d1value)s != " + "d2['%(key)s']=%(d2value)s" % + { + "key": key, + "d1value": d1value, + "d2value": d2value + }) + + +class BaseSharesAltTest(BaseSharesTest): + """Base test case class for all Shares Alt API tests.""" + + @classmethod + def resource_setup(cls): + cls.username = CONF.identity.alt_username + cls.password = CONF.identity.alt_password + cls.tenant_name = CONF.identity.alt_tenant_name + cls.verify_nonempty(cls.username, cls.password, cls.tenant_name) + cls.os = clients.AltManager() + alt_share_network_id = CONF.share.alt_share_network_id + cls.os.shares_client.share_network_id = alt_share_network_id + super(BaseSharesAltTest, cls).resource_setup() + + +class BaseSharesAdminTest(BaseSharesTest): + """Base test case class for all Shares Admin API tests.""" + + @classmethod + def resource_setup(cls): + cls.username = CONF.identity.admin_username + cls.password = CONF.identity.admin_password + cls.tenant_name = CONF.identity.admin_tenant_name + cls.verify_nonempty(cls.username, cls.password, cls.tenant_name) + cls.os = clients.AdminManager() + admin_share_network_id = CONF.share.admin_share_network_id + cls.os.shares_client.share_network_id = admin_share_network_id + super(BaseSharesAdminTest, cls).resource_setup() diff --git a/manila_tempest_tests/tests/api/test_extensions.py b/manila_tempest_tests/tests/api/test_extensions.py new file mode 100644 index 00000000..1d71d110 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_extensions.py @@ -0,0 +1,31 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base + + +class ExtensionsTest(base.BaseSharesTest): + + @test.attr(type=["smoke", "gate"]) + def test_extensions(self): + + # get extensions + extensions = self.shares_client.list_extensions() + + # verify response + keys = ["alias", "updated", "name", "description"] + [self.assertIn(key, ext.keys()) for ext in extensions for key in keys] diff --git a/manila_tempest_tests/tests/api/test_limits.py b/manila_tempest_tests/tests/api/test_limits.py new file mode 100644 index 00000000..044d6010 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_limits.py @@ -0,0 +1,60 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base + + +class ShareLimitsTest(base.BaseSharesTest): + + @test.attr(type=["gate", "smoke", ]) + def test_limits_keys(self): + + # list limits + limits = self.shares_client.get_limits() + + # verify response + keys = ["rate", "absolute"] + [self.assertIn(key, limits.keys()) for key in keys] + + abs_keys = [ + "maxTotalShareGigabytes", + "maxTotalShares", + "maxTotalShareSnapshots", + "maxTotalShareNetworks", + "totalSharesUsed", + "totalShareSnapshotsUsed", + "totalShareNetworksUsed", + "totalShareGigabytesUsed", + ] + [self.assertIn(key, limits["absolute"].keys()) for key in abs_keys] + + @test.attr(type=["gate", "smoke", ]) + def test_limits_values(self): + + # list limits + limits = self.shares_client.get_limits() + + # verify integer values for absolute limits + abs_l = limits["absolute"] + self.assertGreater(int(abs_l["maxTotalShareGigabytes"]), -2) + self.assertGreater(int(abs_l["maxTotalShares"]), -2) + self.assertGreater(int(abs_l["maxTotalShareSnapshots"]), -2) + self.assertGreater(int(abs_l["maxTotalShareNetworks"]), -2) + self.assertGreater(int(abs_l["totalSharesUsed"]), -2) + self.assertGreater(int(abs_l["totalShareSnapshotsUsed"]), -2) + self.assertGreater(int(abs_l["totalShareNetworksUsed"]), -2) + self.assertGreater(int(abs_l["totalShareGigabytesUsed"]), -2) diff --git a/manila_tempest_tests/tests/api/test_metadata.py b/manila_tempest_tests/tests/api/test_metadata.py new file mode 100644 index 00000000..47d31f97 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_metadata.py @@ -0,0 +1,163 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base + + +class SharesMetadataTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(SharesMetadataTest, cls).resource_setup() + cls.share = cls.create_share() + + @test.attr(type=["gate", ]) + def test_set_metadata_in_share_creation(self): + + md = {u"key1": u"value1", u"key2": u"value2", } + + # create share with metadata + share = self.create_share(metadata=md, cleanup_in_class=False) + + # get metadata of share + metadata = self.shares_client.get_metadata(share["id"]) + + # verify metadata + self.assertEqual(md, metadata) + + @test.attr(type=["gate", ]) + def test_set_get_delete_metadata(self): + + md = {u"key3": u"value3", u"key4": u"value4", } + + # create share + share = self.create_share(cleanup_in_class=False) + + # set metadata + self.shares_client.set_metadata(share["id"], md) + + # read metadata + get_md = self.shares_client.get_metadata(share["id"]) + + # verify metadata + self.assertEqual(md, get_md) + + # delete metadata + for key in md.keys(): + self.shares_client.delete_metadata(share["id"], key) + + # verify deletion of metadata + get_metadata = self.shares_client.get_metadata(share["id"]) + self.assertEqual({}, get_metadata) + + @test.attr(type=["gate", ]) + def test_set_and_update_metadata_by_key(self): + + md1 = {u"key5": u"value5", u"key6": u"value6", } + md2 = {u"key7": u"value7", u"key8": u"value8", } + + # create share + share = self.create_share(cleanup_in_class=False) + + # set metadata + self.shares_client.set_metadata(share["id"], md1) + + # update metadata + self.shares_client.update_all_metadata(share["id"], md2) + + # get metadata + get_md = self.shares_client.get_metadata(share["id"]) + + # verify metadata + self.assertEqual(md2, get_md) + + @test.attr(type=["gate", ]) + def test_set_metadata_min_size_key(self): + data = {"k": "value"} + + self.shares_client.set_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertEqual(data['k'], body_get.get('k')) + + @test.attr(type=["gate", ]) + def test_set_metadata_max_size_key(self): + max_key = "k" * 255 + data = {max_key: "value"} + + self.shares_client.set_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertIn(max_key, body_get) + self.assertEqual(data[max_key], body_get.get(max_key)) + + @test.attr(type=["gate", ]) + def test_set_metadata_min_size_value(self): + data = {"key": "v"} + + self.shares_client.set_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertEqual(data['key'], body_get['key']) + + @test.attr(type=["gate", ]) + def test_set_metadata_max_size_value(self): + max_value = "v" * 1023 + data = {"key": max_value} + + self.shares_client.set_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertEqual(data['key'], body_get['key']) + + @test.attr(type=["gate", ]) + def test_upd_metadata_min_size_key(self): + data = {"k": "value"} + + self.shares_client.update_all_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertEqual(data, body_get) + + @test.attr(type=["gate", ]) + def test_upd_metadata_max_size_key(self): + max_key = "k" * 255 + data = {max_key: "value"} + + self.shares_client.update_all_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertEqual(data, body_get) + + @test.attr(type=["gate", ]) + def test_upd_metadata_min_size_value(self): + data = {"key": "v"} + + self.shares_client.update_all_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertEqual(data, body_get) + + @test.attr(type=["gate", ]) + def test_upd_metadata_max_size_value(self): + max_value = "v" * 1023 + data = {"key": max_value} + + self.shares_client.update_all_metadata(self.share["id"], data) + + body_get = self.shares_client.get_metadata(self.share["id"]) + self.assertEqual(data, body_get) diff --git a/manila_tempest_tests/tests/api/test_metadata_negative.py b/manila_tempest_tests/tests/api/test_metadata_negative.py new file mode 100644 index 00000000..7401a41b --- /dev/null +++ b/manila_tempest_tests/tests/api/test_metadata_negative.py @@ -0,0 +1,91 @@ +# 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 test # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests.tests.api import base + + +class SharesMetadataNegativeTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(SharesMetadataNegativeTest, cls).resource_setup() + cls.share = cls.create_share() + + @test.attr(type=["gate", "negative", ]) + def test_try_set_metadata_to_unexisting_share(self): + md = {u"key1": u"value1", u"key2": u"value2", } + self.assertRaises(lib_exc.NotFound, + self.shares_client.set_metadata, + "wrong_share_id", md) + + @test.attr(type=["gate", "negative", ]) + def test_try_update_all_metadata_for_unexisting_share(self): + md = {u"key1": u"value1", u"key2": u"value2", } + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_all_metadata, + "wrong_share_id", md) + + @test.attr(type=["gate", "negative", ]) + def test_try_set_metadata_with_empty_key(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.set_metadata, + self.share["id"], {"": "value"}) + + @test.attr(type=["gate", "negative", ]) + def test_try_upd_metadata_with_empty_key(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.update_all_metadata, + self.share["id"], {"": "value"}) + + @test.attr(type=["gate", "negative", ]) + def test_try_set_metadata_with_too_big_key(self): + too_big_key = "x" * 256 + md = {too_big_key: "value"} + self.assertRaises(lib_exc.BadRequest, + self.shares_client.set_metadata, + self.share["id"], md) + + @test.attr(type=["gate", "negative", ]) + def test_try_upd_metadata_with_too_big_key(self): + too_big_key = "x" * 256 + md = {too_big_key: "value"} + self.assertRaises(lib_exc.BadRequest, + self.shares_client.update_all_metadata, + self.share["id"], md) + + @test.attr(type=["gate", "negative", ]) + def test_try_set_metadata_with_too_big_value(self): + too_big_value = "x" * 1024 + md = {"key": too_big_value} + self.assertRaises(lib_exc.BadRequest, + self.shares_client.set_metadata, + self.share["id"], md) + + @test.attr(type=["gate", "negative", ]) + def test_try_upd_metadata_with_too_big_value(self): + too_big_value = "x" * 1024 + md = {"key": too_big_value} + self.assertRaises(lib_exc.BadRequest, + self.shares_client.update_all_metadata, + self.share["id"], md) + + @test.attr(type=["gate", "negative", ]) + def test_try_delete_unexisting_metadata(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_metadata, + self.share["id"], "wrong_key") diff --git a/manila_tempest_tests/tests/api/test_quotas.py b/manila_tempest_tests/tests/api/test_quotas.py new file mode 100644 index 00000000..af5885af --- /dev/null +++ b/manila_tempest_tests/tests/api/test_quotas.py @@ -0,0 +1,55 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base + + +class SharesQuotasTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(SharesQuotasTest, cls).resource_setup() + cls.user_id = cls.shares_client.user_id + cls.tenant_id = cls.shares_client.tenant_id + + @test.attr(type=["gate", "smoke", ]) + def test_default_quotas(self): + quotas = self.shares_client.default_quotas(self.tenant_id) + self.assertGreater(int(quotas["gigabytes"]), -2) + self.assertGreater(int(quotas["snapshot_gigabytes"]), -2) + self.assertGreater(int(quotas["shares"]), -2) + self.assertGreater(int(quotas["snapshots"]), -2) + self.assertGreater(int(quotas["share_networks"]), -2) + + @test.attr(type=["gate", "smoke", ]) + def test_show_quotas(self): + quotas = self.shares_client.show_quotas(self.tenant_id) + self.assertGreater(int(quotas["gigabytes"]), -2) + self.assertGreater(int(quotas["snapshot_gigabytes"]), -2) + self.assertGreater(int(quotas["shares"]), -2) + self.assertGreater(int(quotas["snapshots"]), -2) + self.assertGreater(int(quotas["share_networks"]), -2) + + @test.attr(type=["gate", "smoke", ]) + def test_show_quotas_for_user(self): + quotas = self.shares_client.show_quotas( + self.tenant_id, self.user_id) + self.assertGreater(int(quotas["gigabytes"]), -2) + self.assertGreater(int(quotas["snapshot_gigabytes"]), -2) + self.assertGreater(int(quotas["shares"]), -2) + self.assertGreater(int(quotas["snapshots"]), -2) + self.assertGreater(int(quotas["share_networks"]), -2) diff --git a/manila_tempest_tests/tests/api/test_quotas_negative.py b/manila_tempest_tests/tests/api/test_quotas_negative.py new file mode 100644 index 00000000..0736b98a --- /dev/null +++ b/manila_tempest_tests/tests/api/test_quotas_negative.py @@ -0,0 +1,41 @@ +# 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 test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + + +class SharesQuotasNegativeTest(base.BaseSharesTest): + + @test.attr(type=["gate", "smoke", "negative"]) + def test_get_quotas_with_empty_tenant_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.show_quotas, "") + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_try_reset_quotas_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.reset_quotas, + self.shares_client.tenant_id) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_try_update_quotas_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.update_quotas, + self.shares_client.tenant_id, + shares=9) diff --git a/manila_tempest_tests/tests/api/test_rules.py b/manila_tempest_tests/tests/api/test_rules.py new file mode 100644 index 00000000..7198c907 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_rules.py @@ -0,0 +1,285 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +def _create_delete_ro_access_rule(self): + """Common test case for usage in test suites with different decorators. + + :param self: instance of test class + """ + rule = self.shares_client.create_access_rule( + self.share["id"], self.access_type, self.access_to, 'ro') + self.assertEqual('ro', rule['access_level']) + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + self.shares_client.delete_access_rule(self.share["id"], rule["id"]) + self.shares_client.wait_for_resource_deletion( + rule_id=rule["id"], share_id=self.share['id']) + + +class ShareIpRulesForNFSTest(base.BaseSharesTest): + protocol = "nfs" + + @classmethod + def resource_setup(cls): + super(ShareIpRulesForNFSTest, cls).resource_setup() + if (cls.protocol not in CONF.share.enable_protocols or + cls.protocol not in CONF.share.enable_ip_rules_for_protocols): + msg = "IP rule tests for %s protocol are disabled" % cls.protocol + raise cls.skipException(msg) + cls.share = cls.create_share(cls.protocol) + cls.access_type = "ip" + cls.access_to = "2.2.2.2" + + @test.attr(type=["gate", ]) + def test_create_delete_access_rules_with_one_ip(self): + + # test data + access_to = "1.1.1.1" + + # create rule + rule = self.shares_client.create_access_rule( + self.share["id"], self.access_type, access_to) + self.assertEqual('rw', rule['access_level']) + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + + # delete rule and wait for deletion + self.shares_client.delete_access_rule(self.share["id"], rule["id"]) + self.shares_client.wait_for_resource_deletion( + rule_id=rule["id"], share_id=self.share['id']) + + @test.attr(type=["gate", ]) + def test_create_delete_access_rule_with_cidr(self): + + # test data + access_to = "1.2.3.4/32" + + # create rule + rule = self.shares_client.create_access_rule( + self.share["id"], self.access_type, access_to) + self.assertEqual('rw', rule['access_level']) + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + + # delete rule and wait for deletion + self.shares_client.delete_access_rule(self.share["id"], rule["id"]) + self.shares_client.wait_for_resource_deletion( + rule_id=rule["id"], share_id=self.share['id']) + + @test.attr(type=["gate", ]) + @testtools.skipIf( + "nfs" not in CONF.share.enable_ro_access_level_for_protocols, + "RO access rule tests are disabled for NFS protocol.") + def test_create_delete_ro_access_rule(self): + _create_delete_ro_access_rule(self) + + +class ShareIpRulesForCIFSTest(ShareIpRulesForNFSTest): + protocol = "cifs" + + @test.attr(type=["gate", ]) + @testtools.skipIf( + "cifs" not in CONF.share.enable_ro_access_level_for_protocols, + "RO access rule tests are disabled for CIFS protocol.") + def test_create_delete_ro_access_rule(self): + _create_delete_ro_access_rule(self) + + +class ShareUserRulesForNFSTest(base.BaseSharesTest): + protocol = "nfs" + + @classmethod + def resource_setup(cls): + super(ShareUserRulesForNFSTest, cls).resource_setup() + if (cls.protocol not in CONF.share.enable_protocols or + cls.protocol not in + CONF.share.enable_user_rules_for_protocols): + msg = "USER rule tests for %s protocol are disabled" % cls.protocol + raise cls.skipException(msg) + cls.share = cls.create_share(cls.protocol) + cls.access_type = "user" + cls.access_to = CONF.share.username_for_user_rules + + @test.attr(type=["gate", ]) + def test_create_delete_user_rule(self): + + # create rule + rule = self.shares_client.create_access_rule( + self.share["id"], self.access_type, self.access_to) + self.assertEqual('rw', rule['access_level']) + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + + # delete rule and wait for deletion + self.shares_client.delete_access_rule(self.share["id"], rule["id"]) + self.shares_client.wait_for_resource_deletion( + rule_id=rule["id"], share_id=self.share['id']) + + @test.attr(type=["gate", ]) + @testtools.skipIf( + "nfs" not in CONF.share.enable_ro_access_level_for_protocols, + "RO access rule tests are disabled for NFS protocol.") + def test_create_delete_ro_access_rule(self): + _create_delete_ro_access_rule(self) + + +class ShareUserRulesForCIFSTest(ShareUserRulesForNFSTest): + protocol = "cifs" + + @test.attr(type=["gate", ]) + @testtools.skipIf( + "cifs" not in CONF.share.enable_ro_access_level_for_protocols, + "RO access rule tests are disabled for CIFS protocol.") + def test_create_delete_ro_access_rule(self): + _create_delete_ro_access_rule(self) + + +class ShareCertRulesForGLUSTERFSTest(base.BaseSharesTest): + protocol = "glusterfs" + + @classmethod + def resource_setup(cls): + super(ShareCertRulesForGLUSTERFSTest, cls).resource_setup() + if (cls.protocol not in CONF.share.enable_protocols or + cls.protocol not in + CONF.share.enable_cert_rules_for_protocols): + msg = "Cert rule tests for %s protocol are disabled" % cls.protocol + raise cls.skipException(msg) + cls.share = cls.create_share(cls.protocol) + cls.access_type = "cert" + # Provide access to a client identified by a common name (CN) of the + # certificate that it possesses. + cls.access_to = "client1.com" + + @test.attr(type=["gate", ]) + def test_create_delete_cert_rule(self): + + # create rule + rule = self.shares_client.create_access_rule( + self.share["id"], self.access_type, self.access_to) + self.assertEqual('rw', rule['access_level']) + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + + # delete rule + self.shares_client.delete_access_rule(self.share["id"], rule["id"]) + + @test.attr(type=["gate", ]) + @testtools.skipIf( + "glusterfs" not in CONF.share.enable_ro_access_level_for_protocols, + "RO access rule tests are disabled for GLUSTERFS protocol.") + def test_create_delete_cert_ro_access_rule(self): + rule = self.shares_client.create_access_rule( + self.share["id"], 'cert', 'client2.com', 'ro') + self.assertEqual('ro', rule['access_level']) + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + self.shares_client.delete_access_rule(self.share["id"], rule["id"]) + + +class ShareRulesTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(ShareRulesTest, cls).resource_setup() + if not (any(p in CONF.share.enable_ip_rules_for_protocols + for p in cls.protocols) or + any(p in CONF.share.enable_user_rules_for_protocols + for p in cls.protocols) or + any(p in CONF.share.enable_cert_rules_for_protocols + for p in cls.protocols)): + cls.message = "Rule tests are disabled" + raise cls.skipException(cls.message) + cls.share = cls.create_share() + + def setUp(self): + # Here we choose protocol and rule type for + # testing common rules functionality, + # that isn't dependent on protocol or rule type. + super(ShareRulesTest, self).setUp() + if CONF.share.enable_ip_rules_for_protocols: + self.access_type = "ip" + self.access_to = "8.8.8.8" + protocol = CONF.share.enable_ip_rules_for_protocols[0] + elif CONF.share.enable_user_rules_for_protocols: + self.access_type = "user" + self.access_to = CONF.share.username_for_user_rules + protocol = CONF.share.enable_user_rules_for_protocols[0] + elif CONF.share.enable_cert_rules_for_protocols: + self.access_type = "cert" + self.access_to = "client3.com" + protocol = CONF.share.enable_cert_rules_for_protocols[0] + else: + raise self.skipException(self.message) + self.shares_client.protocol = protocol + + @test.attr(type=["gate", ]) + def test_list_access_rules(self): + + # create rule + rule = self.shares_client.create_access_rule( + self.share["id"], self.access_type, self.access_to) + + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + + # list rules + rules = self.shares_client.list_access_rules(self.share["id"]) + + # verify keys + keys = ["state", "id", "access_type", "access_to", "access_level"] + [self.assertIn(key, r.keys()) for r in rules for key in keys] + + # verify values + self.assertEqual("active", rules[0]["state"]) + self.assertEqual(self.access_type, rules[0]["access_type"]) + self.assertEqual(self.access_to, rules[0]["access_to"]) + self.assertEqual('rw', rules[0]["access_level"]) + + # our share id in list and have no duplicates + gen = [r["id"] for r in rules if r["id"] in rule["id"]] + msg = "expected id lists %s times in rule list" % (len(gen)) + self.assertEqual(len(gen), 1, msg) + + @test.attr(type=["gate", ]) + def test_access_rules_deleted_if_share_deleted(self): + + # create share + share = self.create_share() + + # create rule + rule = self.shares_client.create_access_rule( + share["id"], self.access_type, self.access_to) + self.shares_client.wait_for_access_rule_status( + share["id"], rule["id"], "active") + + # delete share + self.shares_client.delete_share(share['id']) + self.shares_client.wait_for_resource_deletion(share_id=share['id']) + + # verify absence of rules for nonexistent share id + self.assertRaises(lib_exc.NotFound, + self.shares_client.list_access_rules, + share['id']) diff --git a/manila_tempest_tests/tests/api/test_rules_negative.py b/manila_tempest_tests/tests/api/test_rules_negative.py new file mode 100644 index 00000000..6ad36ba2 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_rules_negative.py @@ -0,0 +1,299 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class ShareIpRulesForNFSNegativeTest(base.BaseSharesTest): + protocol = "nfs" + + @classmethod + def resource_setup(cls): + super(ShareIpRulesForNFSNegativeTest, cls).resource_setup() + if not (cls.protocol in CONF.share.enable_protocols and + cls.protocol in CONF.share.enable_ip_rules_for_protocols): + msg = "IP rule tests for %s protocol are disabled" % cls.protocol + raise cls.skipException(msg) + # create share + cls.share = cls.create_share(cls.protocol) + if CONF.share.run_snapshot_tests: + # create snapshot + cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"]) + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_1(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.2.3.256") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_2(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.1.1.-") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_3(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.2.3.4/33") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_4(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.2.3.*") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_5(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.2.3.*/23") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_6(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.2.3.1|23") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_7(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.2.3.1/-1") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_target_8(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "ip", "1.2.3.1/") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_with_wrong_level(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], + 'ip', + '2.2.2.2', + 'su') + + @test.attr(type=["negative", "gate", ]) + def test_create_duplicate_of_ip_rule(self): + # test data + access_type = "ip" + access_to = "1.2.3.4" + + # create rule + rule = self.shares_client.create_access_rule( + self.share["id"], access_type, access_to) + self.shares_client.wait_for_access_rule_status( + self.share["id"], rule["id"], "active") + + # try create duplicate of rule + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], access_type, access_to) + + +class ShareIpRulesForCIFSNegativeTest(ShareIpRulesForNFSNegativeTest): + protocol = "cifs" + + +class ShareUserRulesForNFSNegativeTest(base.BaseSharesTest): + protocol = "nfs" + + @classmethod + def resource_setup(cls): + super(ShareUserRulesForNFSNegativeTest, cls).resource_setup() + if not (cls.protocol in CONF.share.enable_protocols and + cls.protocol in CONF.share.enable_user_rules_for_protocols): + msg = "USER rule tests for %s protocol are disabled" % cls.protocol + raise cls.skipException(msg) + # create share + cls.share = cls.create_share(cls.protocol) + if CONF.share.run_snapshot_tests: + # create snapshot + cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"]) + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_user_with_wrong_input_2(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "user", + "try+") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_user_with_empty_key(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "user", "") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_user_with_too_little_key(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "user", "abc") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_user_with_too_big_key(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "user", "a" * 33) + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_user_with_wrong_input_1(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "user", + "try+") + + @test.attr(type=["negative", "gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_access_rule_user_to_snapshot(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_access_rule, + self.snap["id"], + access_type="user", + access_to="fakeuser") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_user_with_wrong_share_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_access_rule, + "wrong_share_id", + access_type="user", + access_to="fakeuser") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_with_wrong_level(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], + 'user', + CONF.share.username_for_user_rules, + 'su') + + +class ShareUserRulesForCIFSNegativeTest(ShareUserRulesForNFSNegativeTest): + protocol = "cifs" + + +class ShareCertRulesForGLUSTERFSNegativeTest(base.BaseSharesTest): + protocol = "glusterfs" + + @classmethod + def resource_setup(cls): + super(ShareCertRulesForGLUSTERFSNegativeTest, cls).resource_setup() + if not (cls.protocol in CONF.share.enable_protocols and + cls.protocol in CONF.share.enable_cert_rules_for_protocols): + msg = "CERT rule tests for %s protocol are disabled" % cls.protocol + raise cls.skipException(msg) + # create share + cls.share = cls.create_share(cls.protocol) + if CONF.share.run_snapshot_tests: + # create snapshot + cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"]) + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_cert_with_empty_common_name(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "cert", "") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_cert_with_whitespace_common_name(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "cert", " ") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_cert_with_too_big_common_name(self): + # common name cannot be more than 64 characters long + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "cert", "a" * 65) + + @test.attr(type=["negative", "gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_access_rule_cert_to_snapshot(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_access_rule, + self.snap["id"], + access_type="cert", + access_to="fakeclient1.com") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_cert_with_wrong_share_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_access_rule, + "wrong_share_id", + access_type="cert", + access_to="fakeclient2.com") + + +class ShareRulesNegativeTest(base.BaseSharesTest): + # Tests independent from rule type and share protocol + + @classmethod + def resource_setup(cls): + super(ShareRulesNegativeTest, cls).resource_setup() + if not (any(p in CONF.share.enable_ip_rules_for_protocols + for p in cls.protocols) or + any(p in CONF.share.enable_user_rules_for_protocols + for p in cls.protocols) or + any(p in CONF.share.enable_cert_rules_for_protocols + for p in cls.protocols)): + cls.message = "Rule tests are disabled" + raise cls.skipException(cls.message) + # create share + cls.share = cls.create_share() + if CONF.share.run_snapshot_tests: + # create snapshot + cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"]) + + @test.attr(type=["negative", "gate", ]) + def test_delete_access_rule_with_wrong_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_access_rule, + self.share["id"], "wrong_rule_id") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_type(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_access_rule, + self.share["id"], "wrong_type", "1.2.3.4") + + @test.attr(type=["negative", "gate", ]) + def test_create_access_rule_ip_with_wrong_share_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_access_rule, + "wrong_share_id") + + @test.attr(type=["negative", "gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_access_rule_ip_to_snapshot(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_access_rule, + self.snap["id"]) diff --git a/manila_tempest_tests/tests/api/test_scheduler_stats_negative.py b/manila_tempest_tests/tests/api/test_scheduler_stats_negative.py new file mode 100644 index 00000000..9be8f642 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_scheduler_stats_negative.py @@ -0,0 +1,33 @@ +# Copyright 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 import test # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests.tests.api import base + + +class SchedulerStatsNegativeTest(base.BaseSharesTest): + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_try_list_pools_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.list_pools) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_try_list_pools_detailed_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.list_pools, + detail=True) diff --git a/manila_tempest_tests/tests/api/test_security_services.py b/manila_tempest_tests/tests/api/test_security_services.py new file mode 100644 index 00000000..c0d67ec1 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_security_services.py @@ -0,0 +1,202 @@ +# 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 oslo_log import log # noqa +import six # noqa +from tempest import config # noqa +from tempest import test # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF +LOG = log.getLogger(__name__) + + +class SecurityServiceListMixin(object): + + @test.attr(type=["gate", "smoke"]) + def test_list_security_services(self): + listed = self.shares_client.list_security_services() + self.assertTrue(any(self.ss_ldap['id'] == ss['id'] for ss in listed)) + self.assertTrue(any(self.ss_kerberos['id'] == ss['id'] + for ss in listed)) + + # verify keys + keys = ["name", "id", "status", "type", ] + [self.assertIn(key, s_s.keys()) for s_s in listed for key in keys] + + @test.attr(type=["gate", "smoke"]) + def test_list_security_services_with_detail(self): + listed = self.shares_client.list_security_services(detailed=True) + self.assertTrue(any(self.ss_ldap['id'] == ss['id'] for ss in listed)) + self.assertTrue(any(self.ss_kerberos['id'] == ss['id'] + for ss in listed)) + + # verify keys + keys = [ + "name", "id", "status", "description", + "domain", "server", "dns_ip", "user", "password", "type", + "created_at", "updated_at", "project_id", + ] + [self.assertIn(key, s_s.keys()) for s_s in listed for key in keys] + + @test.attr(type=["gate", "smoke"]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_list_security_services_filter_by_share_network(self): + sn = self.shares_client.get_share_network( + self.os.shares_client.share_network_id) + fresh_sn = [] + for i in range(2): + sn = self.create_share_network( + neutron_net_id=sn["neutron_net_id"], + neutron_subnet_id=sn["neutron_subnet_id"]) + fresh_sn.append(sn) + + self.shares_client.add_sec_service_to_share_network( + fresh_sn[0]["id"], self.ss_ldap["id"]) + self.shares_client.add_sec_service_to_share_network( + fresh_sn[1]["id"], self.ss_kerberos["id"]) + + listed = self.shares_client.list_security_services( + params={'share_network_id': fresh_sn[0]['id']}) + self.assertEqual(1, len(listed)) + self.assertEqual(self.ss_ldap['id'], listed[0]['id']) + + keys = ["name", "id", "status", "type", ] + [self.assertIn(key, s_s.keys()) for s_s in listed for key in keys] + + @test.attr(type=["gate", "smoke"]) + def test_list_security_services_detailed_filter_by_ss_attributes(self): + search_opts = { + 'name': 'ss_ldap', + 'type': 'ldap', + 'user': 'fake_user', + 'server': 'fake_server_1', + 'dns_ip': '1.1.1.1', + 'domain': 'fake_domain_1', + } + listed = self.shares_client.list_security_services( + detailed=True, + params=search_opts) + self.assertTrue(any(self.ss_ldap['id'] == ss['id'] for ss in listed)) + for ss in listed: + self.assertTrue(all(ss[key] == value for key, value + in six.iteritems(search_opts))) + + +class SecurityServicesTest(base.BaseSharesTest, + SecurityServiceListMixin): + def setUp(self): + super(SecurityServicesTest, self).setUp() + ss_ldap_data = { + 'name': 'ss_ldap', + 'dns_ip': '1.1.1.1', + 'server': 'fake_server_1', + 'domain': 'fake_domain_1', + 'user': 'fake_user', + 'password': 'pass', + } + ss_kerberos_data = { + 'name': 'ss_kerberos', + 'dns_ip': '2.2.2.2', + 'server': 'fake_server_2', + 'domain': 'fake_domain_2', + 'user': 'test_user', + 'password': 'word', + } + self.ss_ldap = self.create_security_service('ldap', **ss_ldap_data) + self.ss_kerberos = self.create_security_service( + 'kerberos', **ss_kerberos_data) + + @test.attr(type=["gate", "smoke"]) + def test_create_delete_security_service(self): + data = self.generate_security_service_data() + self.service_names = ["ldap", "kerberos", "active_directory"] + for ss_name in self.service_names: + ss = self.create_security_service(ss_name, **data) + self.assertDictContainsSubset(data, ss) + self.assertEqual(ss_name, ss["type"]) + self.shares_client.delete_security_service(ss["id"]) + + @test.attr(type=["gate", "smoke"]) + def test_get_security_service(self): + data = self.generate_security_service_data() + ss = self.create_security_service(**data) + self.assertDictContainsSubset(data, ss) + + get = self.shares_client.get_security_service(ss["id"]) + self.assertDictContainsSubset(data, get) + + @test.attr(type=["gate", "smoke"]) + def test_update_security_service(self): + data = self.generate_security_service_data() + ss = self.create_security_service(**data) + self.assertDictContainsSubset(data, ss) + + upd_data = self.generate_security_service_data() + updated = self.shares_client.update_security_service( + ss["id"], **upd_data) + + get = self.shares_client.get_security_service(ss["id"]) + self.assertDictContainsSubset(upd_data, updated) + self.assertDictContainsSubset(upd_data, get) + + @test.attr(type=["gate", "smoke"]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_try_update_valid_keys_sh_server_exists(self): + ss_data = self.generate_security_service_data() + ss = self.create_security_service(**ss_data) + + sn = self.shares_client.get_share_network( + self.os.shares_client.share_network_id) + fresh_sn = self.create_share_network( + neutron_net_id=sn["neutron_net_id"], + neutron_subnet_id=sn["neutron_subnet_id"]) + + self.shares_client.add_sec_service_to_share_network( + fresh_sn["id"], ss["id"]) + + # Security service with fake data is used, so if we use backend driver + # that fails on wrong data, we expect error here. + # We require any share that uses our share-network. + try: + self.create_share( + share_network_id=fresh_sn["id"], cleanup_in_class=False) + except Exception as e: + # we do wait for either 'error' or 'available' status because + # it is the only available statuses for proper deletion. + LOG.warning("Caught exception. It is expected in case backend " + "fails having security-service with improper data " + "that leads to share-server creation error. " + "%s" % six.text_type(e)) + + update_data = { + "name": "name", + "description": "new_description", + } + updated = self.shares_client.update_security_service( + ss["id"], **update_data) + self.assertDictContainsSubset(update_data, updated) + + @test.attr(type=["gate", "smoke"]) + def test_list_security_services_filter_by_invalid_opt(self): + listed = self.shares_client.list_security_services( + params={'fake_opt': 'some_value'}) + self.assertTrue(any(self.ss_ldap['id'] == ss['id'] for ss in listed)) + self.assertTrue(any(self.ss_kerberos['id'] == ss['id'] + for ss in listed)) diff --git a/manila_tempest_tests/tests/api/test_security_services_mapping.py b/manila_tempest_tests/tests/api/test_security_services_mapping.py new file mode 100644 index 00000000..7173e22f --- /dev/null +++ b/manila_tempest_tests/tests/api/test_security_services_mapping.py @@ -0,0 +1,70 @@ +# 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 test # noqa + +from manila_tempest_tests.tests.api import base + + +class SecurityServicesMappingTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(SecurityServicesMappingTest, cls).resource_setup() + cls.cl = cls.shares_client + + def setUp(self): + super(SecurityServicesMappingTest, self).setUp() + + # create share network + data = self.generate_share_network_data() + + self.sn = self.create_share_network(client=self.cl, **data) + self.assertDictContainsSubset(data, self.sn) + + # create security service + data = self.generate_security_service_data() + + self.ss = self.create_security_service(client=self.cl, **data) + self.assertDictContainsSubset(data, self.ss) + + # Add security service to share network + self.cl.add_sec_service_to_share_network(self.sn["id"], self.ss["id"]) + + @test.attr(type=["gate", "smoke"]) + def test_map_ss_to_sn_and_list(self): + + # List security services for share network + ls = self.cl.list_sec_services_for_share_network(self.sn["id"]) + self.assertEqual(1, len(ls)) + for key in ["status", "id", "name"]: + self.assertIn(self.ss[key], ls[0][key]) + + @test.attr(type=["gate", "smoke"]) + def test_map_ss_to_sn_and_delete(self): + + # Remove security service from share network + self.cl.remove_sec_service_from_share_network( + self.sn["id"], self.ss["id"]) + + @test.attr(type=["gate", "smoke"]) + def test_remap_ss_to_sn(self): + + # Remove security service from share network + self.cl.remove_sec_service_from_share_network( + self.sn["id"], self.ss["id"]) + + # Add security service to share network again + self.cl.add_sec_service_to_share_network(self.sn["id"], self.ss["id"]) diff --git a/manila_tempest_tests/tests/api/test_security_services_mapping_negative.py b/manila_tempest_tests/tests/api/test_security_services_mapping_negative.py new file mode 100644 index 00000000..de0064cb --- /dev/null +++ b/manila_tempest_tests/tests/api/test_security_services_mapping_negative.py @@ -0,0 +1,157 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SecServicesMappingNegativeTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(SecServicesMappingNegativeTest, cls).resource_setup() + cls.sn = cls.create_share_network(cleanup_in_class=True) + cls.ss = cls.create_security_service(cleanup_in_class=True) + cls.cl = cls.shares_client + + @test.attr(type=["gate", "smoke", "negative"]) + def test_add_sec_service_twice_to_share_network(self): + self.cl.add_sec_service_to_share_network(self.sn["id"], self.ss["id"]) + self.assertRaises(lib_exc.Conflict, + self.cl.add_sec_service_to_share_network, + self.sn["id"], self.ss["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_add_nonexistant_sec_service_to_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.cl.add_sec_service_to_share_network, + self.sn["id"], "wrong_ss_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_add_empty_sec_service_id_to_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.cl.add_sec_service_to_share_network, + self.sn["id"], "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_add_sec_service_to_nonexistant_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.cl.add_sec_service_to_share_network, + "wrong_sn_id", self.ss["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_add_sec_service_to_share_network_with_empty_id(self): + self.assertRaises(lib_exc.NotFound, + self.cl.add_sec_service_to_share_network, + "", self.ss["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_list_sec_services_for_nonexistant_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.cl.list_sec_services_for_share_network, + "wrong_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_delete_nonexistant_sec_service_from_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.cl.remove_sec_service_from_share_network, + self.sn["id"], "wrong_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_delete_sec_service_from_nonexistant_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.cl.remove_sec_service_from_share_network, + "wrong_id", self.ss["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_delete_nonexistant_ss_from_nonexistant_sn(self): + self.assertRaises(lib_exc.NotFound, + self.cl.remove_sec_service_from_share_network, + "wrong_id", "wrong_id") + + @test.attr(type=["gate", "smoke", "negative"]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_delete_ss_from_sn_used_by_share_server(self): + sn = self.shares_client.get_share_network( + self.os.shares_client.share_network_id) + fresh_sn = self.create_share_network( + neutron_net_id=sn["neutron_net_id"], + neutron_subnet_id=sn["neutron_subnet_id"]) + + self.shares_client.add_sec_service_to_share_network( + fresh_sn["id"], self.ss["id"]) + self.create_share( + share_network_id=fresh_sn["id"], cleanup_in_class=False) + self.assertRaises(lib_exc.Forbidden, + self.cl.remove_sec_service_from_share_network, + fresh_sn["id"], + self.ss["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_map_two_ss_with_same_type_to_sn(self): + # create share network + data = self.generate_share_network_data() + + sn = self.create_share_network(client=self.cl, **data) + self.assertDictContainsSubset(data, sn) + + # create security services with same type + security_services = [] + for i in range(2): + data = self.generate_security_service_data() + ss = self.create_security_service(client=self.cl, **data) + self.assertDictContainsSubset(data, ss) + security_services.insert(i, ss) + + # Add security service to share network + self.cl.add_sec_service_to_share_network( + sn["id"], security_services[0]["id"]) + + # Try to add security service with same type + self.assertRaises(lib_exc.Conflict, + self.cl.add_sec_service_to_share_network, + sn["id"], security_services[1]["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_delete_ss_that_assigned_to_sn(self): + # create share network + data = self.generate_share_network_data() + + sn = self.create_share_network(client=self.cl, **data) + self.assertDictContainsSubset(data, sn) + + # create security service + data = self.generate_security_service_data() + + ss = self.create_security_service(client=self.cl, **data) + self.assertDictContainsSubset(data, ss) + + # Add security service to share network + self.cl.add_sec_service_to_share_network(sn["id"], ss["id"]) + + # Try delete ss, that has been assigned to some sn + self.assertRaises(lib_exc.Forbidden, + self.cl.delete_security_service, + ss["id"], ) + + # remove seurity service from share-network + self.cl.remove_sec_service_from_share_network(sn["id"], ss["id"]) diff --git a/manila_tempest_tests/tests/api/test_security_services_negative.py b/manila_tempest_tests/tests/api/test_security_services_negative.py new file mode 100644 index 00000000..5908f818 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_security_services_negative.py @@ -0,0 +1,128 @@ +# 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 oslo_log import log # noqa +import six # noqa +from tempest import config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF +LOG = log.getLogger(__name__) + + +class SecurityServicesNegativeTest(base.BaseSharesTest): + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_create_security_service_with_empty_type(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_security_service, "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_create_security_service_with_wrong_type(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_security_service, + "wrong_type") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_get_security_service_without_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_security_service, "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_get_security_service_with_wrong_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_security_service, + "wrong_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_delete_security_service_without_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_security_service, "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_delete_security_service_with_wrong_type(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_security_service, + "wrong_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_update_nonexistant_security_service(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_security_service, + "wrong_id", name="name") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_update_security_service_with_empty_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_security_service, + "", name="name") + + @test.attr(type=["gate", "smoke", "negative"]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_try_update_invalid_keys_sh_server_exists(self): + ss_data = self.generate_security_service_data() + ss = self.create_security_service(**ss_data) + + sn = self.shares_client.get_share_network( + self.os.shares_client.share_network_id) + fresh_sn = self.create_share_network( + neutron_net_id=sn["neutron_net_id"], + neutron_subnet_id=sn["neutron_subnet_id"]) + + self.shares_client.add_sec_service_to_share_network( + fresh_sn["id"], ss["id"]) + + # Security service with fake data is used, so if we use backend driver + # that fails on wrong data, we expect error here. + # We require any share that uses our share-network. + try: + self.create_share( + share_network_id=fresh_sn["id"], cleanup_in_class=False) + except Exception as e: + # we do wait for either 'error' or 'available' status because + # it is the only available statuses for proper deletion. + LOG.warning("Caught exception. It is expected in case backend " + "fails having security-service with improper data " + "that leads to share-server creation error. " + "%s" % six.text_type(e)) + + self.assertRaises(lib_exc.Forbidden, + self.shares_client.update_security_service, + ss["id"], + user="new_user") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_get_deleted_security_service(self): + data = self.generate_security_service_data() + ss = self.create_security_service(**data) + self.assertDictContainsSubset(data, ss) + + self.shares_client.delete_security_service(ss["id"]) + + # try get deleted security service entity + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_security_service, + ss["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_list_security_services_all_tenants(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.list_security_services, + params={'all_tenants': 1}) diff --git a/manila_tempest_tests/tests/api/test_share_networks.py b/manila_tempest_tests/tests/api/test_share_networks.py new file mode 100644 index 00000000..4dfef311 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_networks.py @@ -0,0 +1,216 @@ +# 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. + +import six # noqa +from tempest import config # noqa +from tempest import test # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class ShareNetworkListMixin(object): + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_networks(self): + listed = self.shares_client.list_share_networks() + any(self.sn_with_ldap_ss["id"] in sn["id"] for sn in listed) + + # verify keys + keys = ["name", "id"] + [self.assertIn(key, sn.keys()) for sn in listed for key in keys] + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_networks_with_detail(self): + listed = self.shares_client.list_share_networks_with_detail() + any(self.sn_with_ldap_ss["id"] in sn["id"] for sn in listed) + + # verify keys + keys = [ + "name", "id", "description", "network_type", + "project_id", "cidr", "ip_version", + "neutron_net_id", "neutron_subnet_id", + "created_at", "updated_at", "segmentation_id", + ] + [self.assertIn(key, sn.keys()) for sn in listed for key in keys] + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_networks_filter_by_ss(self): + listed = self.shares_client.list_share_networks_with_detail( + {'security_service_id': self.ss_ldap['id']}) + self.assertTrue(any(self.sn_with_ldap_ss['id'] == sn['id'] + for sn in listed)) + for sn in listed: + ss_list = self.shares_client.list_sec_services_for_share_network( + sn['id']) + self.assertTrue(any(ss['id'] == self.ss_ldap['id'] + for ss in ss_list)) + + @test.attr(type=["gate", "smoke", ]) + def test_list_share_networks_all_filter_opts(self): + valid_filter_opts = { + 'created_before': '2002-10-10', + 'created_since': '2001-01-01', + 'neutron_net_id': '1111', + 'neutron_subnet_id': '2222', + 'network_type': 'vlan', + 'segmentation_id': 1000, + 'cidr': '10.0.0.0/24', + 'ip_version': 4, + 'name': 'sn_with_ldap_ss' + } + + listed = self.shares_client.list_share_networks_with_detail( + valid_filter_opts) + self.assertTrue(any(self.sn_with_ldap_ss['id'] == sn['id'] + for sn in listed)) + created_before = valid_filter_opts.pop('created_before') + created_since = valid_filter_opts.pop('created_since') + for sn in listed: + self.assertTrue(all(sn[key] == value for key, value in + six.iteritems(valid_filter_opts))) + self.assertTrue(sn['created_at'] <= created_before) + self.assertTrue(sn['created_at'] >= created_since) + + +class ShareNetworksTest(base.BaseSharesTest, ShareNetworkListMixin): + + @classmethod + def resource_setup(cls): + super(ShareNetworksTest, cls).resource_setup() + ss_data = cls.generate_security_service_data() + cls.ss_ldap = cls.create_security_service(**ss_data) + + cls.data_sn_with_ldap_ss = { + 'name': 'sn_with_ldap_ss', + 'neutron_net_id': '1111', + 'neutron_subnet_id': '2222', + 'created_at': '2002-02-02', + 'updated_at': None, + 'network_type': 'vlan', + 'segmentation_id': 1000, + 'cidr': '10.0.0.0/24', + 'ip_version': 4, + 'description': 'fake description', + } + cls.sn_with_ldap_ss = cls.create_share_network( + cleanup_in_class=True, + **cls.data_sn_with_ldap_ss) + + cls.shares_client.add_sec_service_to_share_network( + cls.sn_with_ldap_ss["id"], + cls.ss_ldap["id"]) + + cls.data_sn_with_kerberos_ss = { + 'name': 'sn_with_kerberos_ss', + 'neutron_net_id': '3333', + 'neutron_subnet_id': '4444', + 'created_at': '2003-03-03', + 'updated_at': None, + 'neutron_net_id': 'test net id', + 'neutron_subnet_id': 'test subnet id', + 'network_type': 'local', + 'segmentation_id': 2000, + 'cidr': '10.0.0.0/13', + 'ip_version': 6, + 'description': 'fake description', + } + + cls.ss_kerberos = cls.create_security_service( + ss_type='kerberos', + **cls.data_sn_with_ldap_ss) + + cls.sn_with_kerberos_ss = cls.create_share_network( + cleanup_in_class=True, + **cls.data_sn_with_kerberos_ss) + + cls.shares_client.add_sec_service_to_share_network( + cls.sn_with_kerberos_ss["id"], + cls.ss_kerberos["id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_create_delete_share_network(self): + # generate data for share network + data = self.generate_share_network_data() + + # create share network + created = self.shares_client.create_share_network(**data) + self.assertDictContainsSubset(data, created) + + # Delete share_network + self.shares_client.delete_share_network(created["id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_get_share_network(self): + get = self.shares_client.get_share_network(self.sn_with_ldap_ss["id"]) + self.assertEqual('2002-02-02T00:00:00.000000', get['created_at']) + data = self.data_sn_with_ldap_ss.copy() + del data['created_at'] + self.assertDictContainsSubset(data, get) + + @test.attr(type=["gate", "smoke", ]) + def test_update_share_network(self): + update_data = self.generate_share_network_data() + updated = self.shares_client.update_share_network( + self.sn_with_ldap_ss["id"], + **update_data) + self.assertDictContainsSubset(update_data, updated) + + @test.attr(type=["gate", "smoke"]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_update_valid_keys_sh_server_exists(self): + self.create_share(cleanup_in_class=False) + update_dict = { + "name": "new_name", + "description": "new_description", + } + updated = self.shares_client.update_share_network( + self.shares_client.share_network_id, **update_dict) + self.assertDictContainsSubset(update_dict, updated) + + @test.attr(type=["gate", "smoke", ]) + def test_recreate_share_network(self): + # generate data for share network + data = self.generate_share_network_data() + + # create share network + sn1 = self.shares_client.create_share_network(**data) + self.assertDictContainsSubset(data, sn1) + + # Delete first share network + self.shares_client.delete_share_network(sn1["id"]) + + # create second share network with same data + sn2 = self.shares_client.create_share_network(**data) + self.assertDictContainsSubset(data, sn2) + + # Delete second share network + self.shares_client.delete_share_network(sn2["id"]) + + @test.attr(type=["gate", "smoke", ]) + def test_create_two_share_networks_with_same_net_and_subnet(self): + # generate data for share network + data = self.generate_share_network_data() + + # create first share network + sn1 = self.create_share_network(**data) + self.assertDictContainsSubset(data, sn1) + + # create second share network + sn2 = self.create_share_network(**data) + self.assertDictContainsSubset(data, sn2) diff --git a/manila_tempest_tests/tests/api/test_share_networks_negative.py b/manila_tempest_tests/tests/api/test_share_networks_negative.py new file mode 100644 index 00000000..d0036c3d --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_networks_negative.py @@ -0,0 +1,131 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class ShareNetworksNegativeTest(base.BaseSharesTest): + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_get_share_network_without_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_network, "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_get_share_network_with_wrong_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share_network, "wrong_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_delete_share_network_without_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share_network, "") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_delete_share_network_with_wrong_type(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share_network, "wrong_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_update_nonexistant_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_share_network, + "wrong_id", name="name") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_update_share_network_with_empty_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.update_share_network, + "", name="name") + + @test.attr(type=["gate", "smoke", "negative"]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_try_update_invalid_keys_sh_server_exists(self): + self.create_share(cleanup_in_class=False) + + self.assertRaises(lib_exc.Forbidden, + self.shares_client.update_share_network, + self.shares_client.share_network_id, + neutron_net_id="new_net_id") + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_get_deleted_share_network(self): + data = self.generate_share_network_data() + sn = self.create_share_network(**data) + self.assertDictContainsSubset(data, sn) + + self.shares_client.delete_share_network(sn["id"]) + + # try get deleted share network entity + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_security_service, + sn["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_list_share_networks_all_tenants(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.list_share_networks_with_detail, + params={'all_tenants': 1}) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_list_share_networks_project_id(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.list_share_networks_with_detail, + params={'project_id': 'some_project'}) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_list_share_networks_wrong_created_since_value(self): + self.assertRaises( + lib_exc.BadRequest, + self.shares_client.list_share_networks_with_detail, + params={'created_since': '2014-10-23T08:31:58.000000'}) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_list_share_networks_wrong_created_before_value(self): + self.assertRaises( + lib_exc.BadRequest, + self.shares_client.list_share_networks_with_detail, + params={'created_before': '2014-10-23T08:31:58.000000'}) + + @test.attr(type=["gate", "smoke", "negative"]) + @testtools.skipIf(not CONF.share.multitenancy_enabled, + 'Can run only with drivers that do handle share servers ' + 'creation. Skipping.') + def test_try_delete_share_network_with_existing_shares(self): + # Get valid network data for successful share creation + share_network = self.shares_client.get_share_network( + self.shares_client.share_network_id) + new_sn = self.create_share_network( + neutron_net_id=share_network['neutron_net_id'], + neutron_subnet_id=share_network['neutron_subnet_id'], + nova_net_id=share_network['nova_net_id'], + cleanup_in_class=False) + + # Create share with share network + self.create_share( + share_network_id=new_sn['id'], cleanup_in_class=False) + + # Try delete share network + self.assertRaises( + lib_exc.Conflict, + self.shares_client.delete_share_network, new_sn['id']) diff --git a/manila_tempest_tests/tests/api/test_share_types_negative.py b/manila_tempest_tests/tests/api/test_share_types_negative.py new file mode 100644 index 00000000..4d2c1895 --- /dev/null +++ b/manila_tempest_tests/tests/api/test_share_types_negative.py @@ -0,0 +1,64 @@ +# Copyright 2014 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. + +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +from tempest_lib import exceptions as lib_exc # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + + +class ShareTypesNegativeTest(base.BaseSharesTest): + + @classmethod + def _create_share_type(cls): + name = data_utils.rand_name("unique_st_name") + extra_specs = cls.add_required_extra_specs_to_dict() + return cls.create_share_type( + name, extra_specs=extra_specs, + client=clients.AdminManager().shares_client) + + @classmethod + def resource_setup(cls): + super(ShareTypesNegativeTest, cls).resource_setup() + cls.st = cls._create_share_type() + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_create_share_type_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.create_share_type, + data_utils.rand_name("used_user_creds"), + client=self.shares_client) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_delete_share_type_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.delete_share_type, + self.st["share_type"]["id"]) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_add_access_to_share_type_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.add_access_to_share_type, + self.st['share_type']['id'], + self.shares_client.tenant_id) + + @test.attr(type=["gate", "smoke", "negative"]) + def test_try_remove_access_from_share_type_with_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.remove_access_from_share_type, + self.st['share_type']['id'], + self.shares_client.tenant_id) diff --git a/manila_tempest_tests/tests/api/test_shares.py b/manila_tempest_tests/tests/api/test_shares.py new file mode 100644 index 00000000..b5c7e01b --- /dev/null +++ b/manila_tempest_tests/tests/api/test_shares.py @@ -0,0 +1,150 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SharesNFSTest(base.BaseSharesTest): + """Covers share functionality, that is related to NFS share type.""" + protocol = "nfs" + + @classmethod + def resource_setup(cls): + super(SharesNFSTest, cls).resource_setup() + if cls.protocol not in CONF.share.enable_protocols: + message = "%s tests are disabled" % cls.protocol + raise cls.skipException(message) + cls.share = cls.create_share(cls.protocol) + + @test.attr(type=["gate", ]) + def test_create_delete_share(self): + + # create share + share = self.create_share(self.protocol) + detailed_elements = {'name', 'id', 'availability_zone', + 'description', 'export_location', 'project_id', + 'host', 'created_at', 'share_proto', 'metadata', + 'size', 'snapshot_id', 'share_network_id', + 'status', 'share_type', 'volume_type', 'links', + 'is_public'} + self.assertTrue(detailed_elements.issubset(share.keys()), + 'At least one expected element missing from share ' + 'response. Expected %(expected)s, got %(actual)s.' % { + "expected": detailed_elements, + "actual": share.keys()}) + self.assertFalse(share['is_public']) + + # delete share + self.shares_client.delete_share(share['id']) + self.shares_client.wait_for_resource_deletion(share_id=share['id']) + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share, + share['id']) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_delete_snapshot(self): + + # create snapshot + snap = self.create_snapshot_wait_for_active(self.share["id"]) + detailed_elements = {'name', 'id', 'description', + 'created_at', 'share_proto', 'size', 'share_size', + 'share_id', 'status', 'links'} + self.assertTrue(detailed_elements.issubset(snap.keys()), + 'At least one expected element missing from snapshot ' + 'response. Expected %(expected)s, got %(actual)s.' % { + "expected": detailed_elements, + "actual": snap.keys()}) + + # delete snapshot + self.shares_client.delete_snapshot(snap["id"]) + self.shares_client.wait_for_resource_deletion(snapshot_id=snap["id"]) + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_snapshot, snap['id']) + + @test.attr(type=["gate", "smoke", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_share_from_snapshot(self): + # If multitenant driver used, share_network will be provided by default + + # create snapshot + snap = self.create_snapshot_wait_for_active( + self.share["id"], cleanup_in_class=False) + + # create share from snapshot + s2 = self.create_share( + self.protocol, snapshot_id=snap["id"], cleanup_in_class=False) + + # verify share, created from snapshot + get = self.shares_client.get_share(s2["id"]) + msg = "Expected snapshot_id %s as "\ + "source of share %s" % (snap["id"], get["snapshot_id"]) + self.assertEqual(get["snapshot_id"], snap["id"], msg) + + @test.attr(type=["gate", "smoke", ]) + @testtools.skipIf(not CONF.share.multitenancy_enabled, + "Only for multitenancy.") + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_share_from_snapshot_share_network_not_provided(self): + # We expect usage of share network from parent's share + # when creating share from snapshot using multitenant driver. + + # get parent share + parent = self.shares_client.get_share(self.share["id"]) + + # create snapshot + snap = self.create_snapshot_wait_for_active( + self.share["id"], cleanup_in_class=False) + + # create share from snapshot + child = self.create_share( + self.protocol, snapshot_id=snap["id"], cleanup_in_class=False) + + # verify share, created from snapshot + get = self.shares_client.get_share(child["id"]) + keys = { + "share": self.share["id"], + "actual_sn": get["share_network_id"], + "expected_sn": parent["share_network_id"], + } + msg = ("Expected share_network_id %(expected_sn)s for" + "share %(share)s, but %(actual_sn)s found." % keys) + self.assertEqual( + get["share_network_id"], parent["share_network_id"], msg) + + +class SharesCIFSTest(SharesNFSTest): + """Covers share functionality, that is related to CIFS share type.""" + protocol = "cifs" + + +class SharesGLUSTERFSTest(SharesNFSTest): + """Covers share functionality that is related to GLUSTERFS share type.""" + protocol = "glusterfs" + + +class SharesHDFSTest(SharesNFSTest): + """Covers share functionality that is related to HDFS share type.""" + protocol = "hdfs" diff --git a/manila_tempest_tests/tests/api/test_shares_actions.py b/manila_tempest_tests/tests/api/test_shares_actions.py new file mode 100644 index 00000000..94754a8e --- /dev/null +++ b/manila_tempest_tests/tests/api/test_shares_actions.py @@ -0,0 +1,507 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib.common.utils import data_utils # noqa +import testtools # noqa + +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SharesActionsTest(base.BaseSharesTest): + """Covers share functionality, that doesn't related to share type.""" + + @classmethod + def resource_setup(cls): + super(SharesActionsTest, cls).resource_setup() + + cls.shares = [] + + # create share + cls.share_name = data_utils.rand_name("tempest-share-name") + cls.share_desc = data_utils.rand_name("tempest-share-description") + cls.metadata = { + 'foo_key_share_1': 'foo_value_share_1', + 'bar_key_share_1': 'foo_value_share_1', + } + cls.share_size = 1 + cls.shares.append(cls.create_share( + name=cls.share_name, + description=cls.share_desc, + size=cls.share_size, + metadata=cls.metadata, + )) + + if CONF.share.run_snapshot_tests: + # create snapshot + cls.snap_name = data_utils.rand_name("tempest-snapshot-name") + cls.snap_desc = data_utils.rand_name( + "tempest-snapshot-description") + cls.snap = cls.create_snapshot_wait_for_active( + cls.shares[0]["id"], cls.snap_name, cls.snap_desc) + + # create second share from snapshot for purposes of sorting and + # snapshot filtering + cls.share_name2 = data_utils.rand_name("tempest-share-name") + cls.share_desc2 = data_utils.rand_name("tempest-share-description") + cls.metadata2 = { + 'foo_key_share_2': 'foo_value_share_2', + 'bar_key_share_2': 'foo_value_share_2', + } + cls.shares.append(cls.create_share( + name=cls.share_name2, + description=cls.share_desc2, + size=cls.share_size, + metadata=cls.metadata2, + snapshot_id=cls.snap['id'], + )) + + @test.attr(type=["gate", ]) + def test_get_share(self): + + # get share + share = self.shares_client.get_share(self.shares[0]['id']) + + # verify keys + expected_keys = ["status", "description", "links", "availability_zone", + "created_at", "export_location", "share_proto", + "name", "snapshot_id", "id", "size"] + actual_keys = share.keys() + [self.assertIn(key, actual_keys) for key in expected_keys] + + # verify values + msg = "Expected name: '%s', actual name: '%s'" % (self.share_name, + share["name"]) + self.assertEqual(self.share_name, str(share["name"]), msg) + + msg = "Expected description: '%s', "\ + "actual description: '%s'" % (self.share_desc, + share["description"]) + self.assertEqual(self.share_desc, str(share["description"]), msg) + + msg = "Expected size: '%s', actual size: '%s'" % (self.share_size, + share["size"]) + self.assertEqual(self.share_size, int(share["size"]), msg) + + @test.attr(type=["gate", ]) + def test_list_shares(self): + + # list shares + shares = self.shares_client.list_shares() + + # verify keys + keys = ["name", "id", "links"] + [self.assertIn(key, sh.keys()) for sh in shares for key in keys] + + # our share id in list and have no duplicates + for share in self.shares: + gen = [sid["id"] for sid in shares if sid["id"] in share["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail(self): + + # list shares + shares = self.shares_client.list_shares_with_detail() + + # verify keys + keys = [ + "status", "description", "links", "availability_zone", + "created_at", "export_location", "share_proto", "host", + "name", "snapshot_id", "id", "size", "project_id", + ] + [self.assertIn(key, sh.keys()) for sh in shares for key in keys] + + # our shares in list and have no duplicates + for share in self.shares: + gen = [sid["id"] for sid in shares if sid["id"] in share["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_metadata(self): + filters = {'metadata': self.metadata} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertDictContainsSubset( + filters['metadata'], share['metadata']) + if CONF.share.run_snapshot_tests: + self.assertFalse(self.shares[1]['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_host(self): + base_share = self.shares_client.get_share(self.shares[0]['id']) + filters = {'host': base_share['host']} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['host'], share['host']) + + @test.attr(type=["gate", ]) + @testtools.skipIf( + not CONF.share.multitenancy_enabled, "Only for multitenancy.") + def test_list_shares_with_detail_filter_by_share_network_id(self): + base_share = self.shares_client.get_share(self.shares[0]['id']) + filters = {'share_network_id': base_share['share_network_id']} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual( + filters['share_network_id'], share['share_network_id']) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_shares_with_detail_filter_by_snapshot_id(self): + filters = {'snapshot_id': self.snap['id']} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['snapshot_id'], share['snapshot_id']) + self.assertFalse(self.shares[0]['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_with_asc_sorting(self): + filters = {'sort_key': 'created_at', 'sort_dir': 'asc'} + + # list shares + shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + sorted_list = [share['created_at'] for share in shares] + self.assertEqual(sorted_list, sorted(sorted_list)) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_existed_name(self): + # list shares by name, at least one share is expected + params = {"name": self.share_name} + shares = self.shares_client.list_shares_with_detail(params) + self.assertEqual(shares[0]["name"], self.share_name) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_fake_name(self): + # list shares by fake name, no shares are expected + params = {"name": data_utils.rand_name("fake-nonexistent-name")} + shares = self.shares_client.list_shares_with_detail(params) + self.assertEqual(len(shares), 0) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_active_status(self): + # list shares by active status, at least one share is expected + params = {"status": "available"} + shares = self.shares_client.list_shares_with_detail(params) + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(share["status"], params["status"]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_fake_status(self): + # list shares by fake status, no shares are expected + params = {"status": 'fake'} + shares = self.shares_client.list_shares_with_detail(params) + self.assertEqual(len(shares), 0) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_all_tenants(self): + # non-admin user can get shares only from his project + params = {"all_tenants": 1} + shares = self.shares_client.list_shares_with_detail(params) + self.assertTrue(len(shares) > 0) + + # get share with detailed info, we need its 'project_id' + share = self.shares_client.get_share(self.shares[0]["id"]) + project_id = share["project_id"] + for share in shares: + self.assertEqual(share["project_id"], project_id) + + @test.attr(type=["gate", ]) + def test_list_shares_public_with_detail(self): + public_share = self.create_share( + name='public_share', + description='public_share_desc', + size=1, + is_public=True, + cleanup_in_class=False + ) + private_share = self.create_share( + name='private_share', + description='private_share_desc', + size=1, + is_public=False, + cleanup_in_class=False + ) + + params = {"is_public": True} + isolated_client = self.get_client_with_isolated_creds( + type_of_creds='alt') + shares = isolated_client.list_shares_with_detail(params) + + keys = [ + "status", "description", "links", "availability_zone", + "created_at", "export_location", "share_proto", "host", + "name", "snapshot_id", "id", "size", "project_id", "is_public", + ] + [self.assertIn(key, sh.keys()) for sh in shares for key in keys] + + gen = [sid["id"] for sid in shares if sid["id"] == public_share["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + self.assertFalse(any([s["id"] == private_share["id"] for s in shares])) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_get_snapshot(self): + + # get snapshot + get = self.shares_client.get_snapshot(self.snap["id"]) + + # verify keys + expected_keys = ["status", "links", "share_id", "name", + "share_proto", "created_at", + "description", "id", "share_size"] + actual_keys = get.keys() + [self.assertIn(key, actual_keys) for key in expected_keys] + + # verify data + msg = "Expected name: '%s', actual name: '%s'" % (self.snap_name, + get["name"]) + self.assertEqual(self.snap_name, get["name"], msg) + + msg = "Expected description: '%s', "\ + "actual description: '%s'" % (self.snap_desc, get["description"]) + self.assertEqual(self.snap_desc, get["description"], msg) + + msg = "Expected share_id: '%s', "\ + "actual share_id: '%s'" % (self.shares[0]["id"], get["share_id"]) + self.assertEqual(self.shares[0]["id"], get["share_id"], msg) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_snapshots(self): + + # list share snapshots + snaps = self.shares_client.list_snapshots() + + # verify keys + keys = ["id", "name", "links"] + [self.assertIn(key, sn.keys()) for sn in snaps for key in keys] + + # our share id in list and have no duplicates + gen = [sid["id"] for sid in snaps if sid["id"] in self.snap["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_snapshots_with_detail(self): + + # list share snapshots + snaps = self.shares_client.list_snapshots_with_detail() + + # verify keys + keys = ["status", "links", "share_id", "name", + "share_proto", "created_at", + "description", "id", "share_size"] + [self.assertIn(key, sn.keys()) for sn in snaps for key in keys] + + # our share id in list and have no duplicates + gen = [sid["id"] for sid in snaps if sid["id"] in self.snap["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(len(gen), 1, msg) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_snapshots_with_detail_use_limit(self): + for l, o in [('1', '1'), ('0', '1')]: + filters = { + 'limit': l, + 'offset': o, + 'share_id': self.shares[0]['id'], + } + + # list snapshots + snaps = self.shares_client.list_snapshots_with_detail( + params=filters) + + # Our snapshot should not be listed + self.assertEqual(0, len(snaps)) + + # Only our one snapshot should be listed + snaps = self.shares_client.list_snapshots_with_detail( + params={'limit': '1', 'offset': '0', + 'share_id': self.shares[0]['id']}) + + self.assertEqual(1, len(snaps['snapshots'])) + self.assertEqual(self.snap['id'], snaps['snapshots'][0]['id']) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_snapshots_with_detail_filter_by_status_and_name(self): + filters = {'status': 'available', 'name': self.snap_name} + + # list snapshots + snaps = self.shares_client.list_snapshots_with_detail( + params=filters) + + # verify response + self.assertTrue(len(snaps) > 0) + for snap in snaps: + self.assertEqual(filters['status'], snap['status']) + self.assertEqual(filters['name'], snap['name']) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_list_snapshots_with_detail_and_asc_sorting(self): + filters = {'sort_key': 'share_id', 'sort_dir': 'asc'} + + # list snapshots + snaps = self.shares_client.list_snapshots_with_detail( + params=filters) + + # verify response + self.assertTrue(len(snaps) > 0) + sorted_list = [snap['share_id'] for snap in snaps] + self.assertEqual(sorted_list, sorted(sorted_list)) + + @test.attr(type=["gate", ]) + @testtools.skipUnless( + CONF.share.run_extend_tests, + "Share extend tests are disabled.") + def test_extend_share(self): + share = self.create_share(size=1, cleanup_in_class=False) + new_size = 2 + + # extend share and wait for active status + self.shares_client.extend_share(share['id'], new_size) + self.shares_client.wait_for_share_status(share['id'], 'available') + + # check state and new size + share = self.shares_client.get_share(share['id']) + self.assertEqual(new_size, share['size']) + + @test.attr(type=["gate", ]) + @testtools.skipUnless( + CONF.share.run_shrink_tests, + "Share shrink tests are disabled.") + def test_shrink_share(self): + share = self.create_share(size=2, cleanup_in_class=False) + new_size = 1 + + # shrink share and wait for active status + self.shares_client.shrink_share(share['id'], new_size) + self.shares_client.wait_for_share_status(share['id'], 'available') + + # check state and new size + share = self.shares_client.get_share(share['id']) + self.assertEqual(new_size, share['size']) + + +class SharesRenameTest(base.BaseSharesTest): + + @classmethod + def resource_setup(cls): + super(SharesRenameTest, cls).resource_setup() + + # create share + cls.share_name = data_utils.rand_name("tempest-share-name") + cls.share_desc = data_utils.rand_name("tempest-share-description") + cls.share_size = 1 + cls.share = cls.create_share( + name=cls.share_name, description=cls.share_desc, + size=cls.share_size) + + if CONF.share.run_snapshot_tests: + # create snapshot + cls.snap_name = data_utils.rand_name("tempest-snapshot-name") + cls.snap_desc = data_utils.rand_name( + "tempest-snapshot-description") + cls.snap = cls.create_snapshot_wait_for_active( + cls.share["id"], cls.snap_name, cls.snap_desc) + + @test.attr(type=["gate", ]) + def test_update_share(self): + + # get share + share = self.shares_client.get_share(self.share['id']) + self.assertEqual(self.share_name, share["name"]) + self.assertEqual(self.share_desc, share["description"]) + self.assertFalse(share["is_public"]) + + # update share + new_name = data_utils.rand_name("tempest-new-name") + new_desc = data_utils.rand_name("tempest-new-description") + updated = self.shares_client.update_share( + share["id"], new_name, new_desc, is_public=True) + self.assertEqual(new_name, updated["name"]) + self.assertEqual(new_desc, updated["description"]) + self.assertTrue(updated["is_public"]) + + # get share + share = self.shares_client.get_share(self.share['id']) + self.assertEqual(new_name, share["name"]) + self.assertEqual(new_desc, share["description"]) + self.assertTrue(share["is_public"]) + + @test.attr(type=["gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_rename_snapshot(self): + + # get snapshot + get = self.shares_client.get_snapshot(self.snap["id"]) + self.assertEqual(self.snap_name, get["name"]) + self.assertEqual(self.snap_desc, get["description"]) + + # rename snapshot + new_name = data_utils.rand_name("tempest-new-name-for-snapshot") + new_desc = data_utils.rand_name("tempest-new-description-for-snapshot") + renamed = self.shares_client.rename_snapshot( + self.snap["id"], new_name, new_desc) + self.assertEqual(new_name, renamed["name"]) + self.assertEqual(new_desc, renamed["description"]) + + # get snapshot + get = self.shares_client.get_snapshot(self.snap["id"]) + self.assertEqual(new_name, get["name"]) + self.assertEqual(new_desc, get["description"]) diff --git a/manila_tempest_tests/tests/api/test_shares_actions_negative.py b/manila_tempest_tests/tests/api/test_shares_actions_negative.py new file mode 100644 index 00000000..624bf31e --- /dev/null +++ b/manila_tempest_tests/tests/api/test_shares_actions_negative.py @@ -0,0 +1,136 @@ +# Copyright 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 import config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests import clients_share as clients +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SharesActionsNegativeTest(base.BaseSharesTest): + @classmethod + def resource_setup(cls): + super(SharesActionsNegativeTest, cls).resource_setup() + cls.share = cls.create_share( + size=1, + ) + + @test.attr(type=["negative", ]) + @testtools.skipUnless( + CONF.share.run_extend_tests, + "Share extend tests are disabled.") + def test_share_extend_over_quota(self): + tenant_quotas = self.shares_client.show_quotas( + self.shares_client.tenant_id) + new_size = int(tenant_quotas["gigabytes"]) + 1 + + # extend share with over quota and check result + self.assertRaises(lib_exc.Forbidden, + self.shares_client.extend_share, + self.share['id'], + new_size) + + @test.attr(type=["negative", ]) + @testtools.skipUnless( + CONF.share.run_extend_tests, + "Share extend tests are disabled.") + def test_share_extend_with_less_size(self): + new_size = int(self.share['size']) - 1 + + # extend share with invalid size and check result + self.assertRaises(lib_exc.BadRequest, + self.shares_client.extend_share, + self.share['id'], + new_size) + + @test.attr(type=["negative", ]) + @testtools.skipUnless( + CONF.share.run_extend_tests, + "Share extend tests are disabled.") + def test_share_extend_with_same_size(self): + new_size = int(self.share['size']) + + # extend share with invalid size and check result + self.assertRaises(lib_exc.BadRequest, + self.shares_client.extend_share, + self.share['id'], + new_size) + + @test.attr(type=["negative", ]) + @testtools.skipUnless( + CONF.share.run_extend_tests, + "Share extend tests are disabled.") + def test_share_extend_with_invalid_share_state(self): + share = self.create_share(size=1, cleanup_in_class=False) + new_size = int(share['size']) + 1 + + # set "error" state + admin_client = clients.AdminManager().shares_client + admin_client.reset_state(share['id']) + + # run extend operation on same share and check result + self.assertRaises(lib_exc.BadRequest, + self.shares_client.extend_share, + share['id'], + new_size) + + @test.attr(type=["negative", ]) + @testtools.skipUnless( + CONF.share.run_shrink_tests, + "Share shrink tests are disabled.") + def test_share_shrink_with_greater_size(self): + new_size = int(self.share['size']) + 1 + + # shrink share with invalid size and check result + self.assertRaises(lib_exc.BadRequest, + self.shares_client.shrink_share, + self.share['id'], + new_size) + + @test.attr(type=["negative", ]) + @testtools.skipUnless( + CONF.share.run_shrink_tests, + "Share shrink tests are disabled.") + def test_share_shrink_with_same_size(self): + new_size = int(self.share['size']) + + # shrink share with invalid size and check result + self.assertRaises(lib_exc.BadRequest, + self.shares_client.shrink_share, + self.share['id'], + new_size) + + @test.attr(type=["negative", ]) + @testtools.skipUnless( + CONF.share.run_shrink_tests, + "Share shrink tests are disabled.") + def test_share_shrink_with_invalid_share_state(self): + share = self.create_share(size=2, cleanup_in_class=False) + new_size = int(share['size']) - 1 + + # set "error" state + admin_client = clients.AdminManager().shares_client + admin_client.reset_state(share['id']) + + # run shrink operation on same share and check result + self.assertRaises(lib_exc.BadRequest, + self.shares_client.shrink_share, + share['id'], + new_size) diff --git a/manila_tempest_tests/tests/api/test_shares_negative.py b/manila_tempest_tests/tests/api/test_shares_negative.py new file mode 100644 index 00000000..ae57a5fe --- /dev/null +++ b/manila_tempest_tests/tests/api/test_shares_negative.py @@ -0,0 +1,259 @@ +# 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 config # noqa +from tempest import test # noqa +from tempest_lib import exceptions as lib_exc # noqa +import testtools # noqa + +from manila_tempest_tests import share_exceptions +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +class SharesNegativeTest(base.BaseSharesTest): + @classmethod + def resource_setup(cls): + super(SharesNegativeTest, cls).resource_setup() + cls.share = cls.create_share( + name='public_share', + description='public_share_desc', + size=1, + is_public=True, + metadata={'key': 'value'} + ) + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_create_share_with_invalid_protocol(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_share, + share_protocol="nonexistent_protocol") + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_create_share_with_wrong_public_value(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_share, is_public='truebar') + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_update_share_with_wrong_public_value(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.update_share, self.share["id"], + is_public="truebar") + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_get_share_with_wrong_id(self): + self.assertRaises(lib_exc.NotFound, self.shares_client.get_share, + "wrong_share_id") + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_get_share_without_passing_share_id(self): + # Should not be able to get share when empty ID is passed + self.assertRaises(lib_exc.NotFound, + self.shares_client.get_share, '') + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_list_shares_nonadmin_with_nonexistent_share_server_filter(self): + # filtering by share server allowed only for admins by default + self.assertRaises(lib_exc.Forbidden, + self.shares_client.list_shares_with_detail, + {'share_server_id': 'fake_share_server_id'}) + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_delete_share_with_wrong_id(self): + self.assertRaises(lib_exc.NotFound, self.shares_client.delete_share, + "wrong_share_id") + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_delete_share_without_passing_share_id(self): + # Should not be able to delete share when empty ID is passed + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_share, '') + + @test.attr(type=["negative", "smoke", "gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_snapshot_with_wrong_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_snapshot, + "wrong_share_id") + + @test.attr(type=["negative", "smoke", "gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_delete_snapshot_with_wrong_id(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.delete_snapshot, + "wrong_share_id") + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_create_share_with_invalid_size(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_share, size="#$%") + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_create_share_with_out_passing_size(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_share, size="") + + @test.attr(type=["negative", "smoke", "gate", ]) + def test_create_share_with_zero_size(self): + self.assertRaises(lib_exc.BadRequest, + self.shares_client.create_share, size=0) + + @test.attr(type=["negative", "gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_try_delete_share_with_existing_snapshot(self): + # share can not be deleted while snapshot exists + + # create share + share = self.create_share() + + # create snapshot + self.create_snapshot_wait_for_active(share["id"]) + + # try delete share + self.assertRaises(lib_exc.Forbidden, + self.shares_client.delete_share, share["id"]) + + @test.attr(type=["negative", "gate", ]) + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_share_from_snap_with_less_size(self): + # requires minimum 5Gb available space + + skip_msg = "Check disc space for this test" + + try: # create share + share = self.create_share(size=2, cleanup_in_class=False) + except share_exceptions.ShareBuildErrorException: + self.skip(skip_msg) + + try: # create snapshot + snap = self.create_snapshot_wait_for_active( + share["id"], cleanup_in_class=False) + except share_exceptions.SnapshotBuildErrorException: + self.skip(skip_msg) + + # try create share from snapshot with less size + self.assertRaises(lib_exc.BadRequest, + self.create_share, + size=1, snapshot_id=snap["id"], + cleanup_in_class=False) + + @test.attr(type=["negative", "smoke", "gate", ]) + @testtools.skipIf(not CONF.share.multitenancy_enabled, + "Only for multitenancy.") + def test_create_share_with_nonexistant_share_network(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.create_share, + share_network_id="wrong_sn_id") + + @test.attr(type=["negative", "smoke", "gate", ]) + @testtools.skipIf(not CONF.share.multitenancy_enabled, + "Only for multitenancy.") + @testtools.skipUnless(CONF.share.run_snapshot_tests, + "Snapshot tests are disabled.") + def test_create_share_from_snap_with_different_share_network(self): + # create share + share = self.create_share(cleanup_in_class=False) + + # get parent's share network + parent_share = self.shares_client.get_share(share["id"]) + parent_sn = self.shares_client.get_share_network( + parent_share["share_network_id"]) + + # create new share-network - net duplicate of parent's share + new_duplicated_sn = self.create_share_network( + cleanup_in_class=False, + neutron_net_id=parent_sn["neutron_net_id"], + neutron_subnet_id=parent_sn["neutron_subnet_id"], + ) + + # create snapshot of parent share + snap = self.create_snapshot_wait_for_active( + share["id"], cleanup_in_class=False) + + # try create share with snapshot using another share-network + # 400 bad request is expected + self.assertRaises( + lib_exc.BadRequest, + self.create_share, + cleanup_in_class=False, + share_network_id=new_duplicated_sn["id"], + snapshot_id=snap["id"], + ) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_update_other_tenants_public_share(self): + isolated_client = self.get_client_with_isolated_creds( + type_of_creds='alt') + self.assertRaises(lib_exc.Forbidden, isolated_client.update_share, + self.share["id"], name="new_name") + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_delete_other_tenants_public_share(self): + isolated_client = self.get_client_with_isolated_creds( + type_of_creds='alt') + self.assertRaises(lib_exc.Forbidden, + isolated_client.delete_share, + self.share['id']) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_set_metadata_of_other_tenants_public_share(self): + isolated_client = self.get_client_with_isolated_creds( + type_of_creds='alt') + self.assertRaises(lib_exc.Forbidden, + isolated_client.set_metadata, + self.share['id'], + {'key': 'value'}) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_update_metadata_of_other_tenants_public_share(self): + isolated_client = self.get_client_with_isolated_creds( + type_of_creds='alt') + self.assertRaises(lib_exc.Forbidden, + isolated_client.update_all_metadata, + self.share['id'], + {'key': 'value'}) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_delete_metadata_of_other_tenants_public_share(self): + isolated_client = self.get_client_with_isolated_creds( + type_of_creds='alt') + self.assertRaises(lib_exc.Forbidden, + isolated_client.delete_metadata, + self.share['id'], + 'key') + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_list_by_share_server_by_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.list_shares, + params={'share_server_id': 12345}) + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_manage_share_by_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.manage_share, + 'fake-host', 'nfs', '/export/path', + 'fake-type') + + @test.attr(type=["gate", "smoke", "negative", ]) + def test_unmanage_share_by_user(self): + self.assertRaises(lib_exc.Forbidden, + self.shares_client.unmanage_share, + 'fake-id') diff --git a/manila_tempest_tests/tests/scenario/__init__.py b/manila_tempest_tests/tests/scenario/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py new file mode 100644 index 00000000..a8133f99 --- /dev/null +++ b/manila_tempest_tests/tests/scenario/manager_share.py @@ -0,0 +1,184 @@ +# Copyright 2015 Deutsche Telekom AG +# 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_log import log +import six + +from tempest.common.utils.linux import remote_client # noqa +from tempest import config # noqa +from tempest.scenario import manager # noqa +from tempest_lib.common.utils import data_utils + +from manila_tempest_tests import clients_share + +CONF = config.CONF + +LOG = log.getLogger(__name__) + + +class ShareScenarioTest(manager.NetworkScenarioTest): + """Provide harness to do Manila scenario tests.""" + + @classmethod + def resource_setup(cls): + cls.set_network_resources() + super(ShareScenarioTest, cls).resource_setup() + + # Manila clients + cls.shares_client = clients_share.Manager().shares_client + cls.shares_admin_client = clients_share.AdminManager().shares_client + + def _create_share(self, share_protocol=None, size=1, name=None, + snapshot_id=None, description=None, metadata=None, + share_network_id=None, share_type_id=None, + client=None, cleanup_in_class=True): + """Create a share + + :param share_protocol: NFS or CIFS + :param size: size in GB + :param name: name of the share (otherwise random) + :param snapshot_id: snapshot as basis for the share + :param description: description of the share + :param metadata: adds additional metadata + :param share_network_id: id of network to be used + :param share_type_id: type of the share to be created + :param client: client object + :param cleanup_in_class: default: True + :returns: a created share + """ + client = client or self.shares_client + description = description or "Tempest's share" + if not name: + name = data_utils.rand_name("manila-scenario") + share_network_id = share_network_id or client.share_network_id or None + metadata = metadata or {} + kwargs = { + 'share_protocol': share_protocol, + 'size': size, + 'name': name, + 'snapshot_id': snapshot_id, + 'description': description, + 'metadata': metadata, + 'share_network_id': share_network_id, + 'share_type_id': share_type_id, + } + share = self.shares_client.create_share(**kwargs) + + self.addCleanup(client.wait_for_resource_deletion, + share_id=share['id']) + self.addCleanup(client.delete_share, + share['id']) + + client.wait_for_share_status(share['id'], 'available') + return share + + def _wait_for_share_server_deletion(self, sn_id, client=None): + """Wait for a share server to be deleted + + :param sn_id: shared network id + :param client: client object + """ + client = client or self.shares_admin_client + servers = client.list_share_servers( + search_opts={"share_network": sn_id}) + for server in servers: + client.delete_share_server(server['id']) + client.wait_for_resource_deletion(server_id=server['id']) + + def _create_share_network(self, client=None, **kwargs): + """Create a share network + + :param client: client object + :returns: a created share network + """ + + client = client or self.shares_client + sn = client.create_share_network(**kwargs) + + self.addCleanup(client.wait_for_resource_deletion, + sn_id=sn['id']) + self.addCleanup(client.delete_share_network, + sn['id']) + self.addCleanup(self._wait_for_share_server_deletion, + sn['id']) + return sn + + def _allow_access(self, share_id, client=None, + access_type="ip", access_to="0.0.0.0"): + """Allow share access + + :param share_id: id of the share + :param client: client object + :param access_type: "ip", "user" or "cert" + :param access_to + :returns: access object + """ + client = client or self.shares_client + access = client.create_access_rule(share_id, access_type, access_to) + client.wait_for_access_rule_status(share_id, access['id'], "active") + self.addCleanup(client.delete_access_rule, + share_id, access['id']) + return access + + def _create_router_interface(self, subnet_id, client=None, + tenant_id=None, router_id=None): + """Create a router interface + + :param subnet_id: id of the subnet + :param client: client object + :param tenant_id + """ + if not client: + client = self.network_client + if not tenant_id: + tenant_id = client.tenant_id + if not router_id: + router_id = self._get_router()['id'] + client.add_router_interface_with_subnet_id(router_id, + subnet_id) + self.addCleanup(client.remove_router_interface_with_subnet_id, + router_id, subnet_id) + + def get_remote_client(self, *args, **kwargs): + if not CONF.share.image_with_share_tools: + return super(ShareScenarioTest, + self).get_remote_client(*args, **kwargs) + # NOTE(u_glide): We need custom implementation of this method until + # original implementation depends on CONF.compute.ssh_auth_method + # option. + server_or_ip = kwargs['server_or_ip'] + if isinstance(server_or_ip, six.string_types): + ip = server_or_ip + else: + addr = server_or_ip['addresses'][CONF.compute.network_for_ssh][0] + ip = addr['addr'] + + # NOTE(u_glide): Both options (pkey and password) are required here to + # support service images without Nova metadata support + client_params = { + 'username': kwargs['username'], + 'password': CONF.share.image_password, + 'pkey': kwargs.get('private_key'), + } + + linux_client = remote_client.RemoteClient(ip, **client_params) + try: + linux_client.validate_authentication() + except Exception: + LOG.exception('Initializing SSH connection to %s failed' % ip) + self._log_console_output() + raise + + return linux_client diff --git a/manila_tempest_tests/tests/scenario/test_share_basic_ops.py b/manila_tempest_tests/tests/scenario/test_share_basic_ops.py new file mode 100644 index 00000000..2864b00d --- /dev/null +++ b/manila_tempest_tests/tests/scenario/test_share_basic_ops.py @@ -0,0 +1,218 @@ +# Copyright 2015 Deutsche Telekom AG +# 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_log import log as logging +from tempest import config # noqa +from tempest import test # noqa +from tempest_lib.common.utils import data_utils +from tempest_lib import exceptions + +from manila_tempest_tests.tests.scenario import manager_share as manager + +CONF = config.CONF + +LOG = logging.getLogger(__name__) + + +class ShareBasicOpsBase(manager.ShareScenarioTest): + + """This smoke test case follows this basic set of operations: + + * Create share network + * Create share + * Launch an instance + * Allow access + * Perform ssh to instance + * Mount share + * Terminate the instance + """ + protocol = None + + def setUp(self): + super(ShareBasicOpsBase, self).setUp() + # Setup image and flavor the test instance + # Support both configured and injected values + if not hasattr(self, 'flavor_ref'): + self.flavor_ref = CONF.share.client_vm_flavor_ref + if CONF.share.image_with_share_tools: + images = self.images_client.list_images()["images"] + for img in images: + if img["name"] == CONF.share.image_with_share_tools: + self.image_ref = img['id'] + break + if not self.image_ref: + msg = ("Image %s not found" % + CONF.share.image_with_share_tools) + raise exceptions.InvalidConfiguration(message=msg) + self.ssh_user = CONF.share.image_username + LOG.debug('Starting test for i:{image}, f:{flavor}. ' + 'user: {ssh_user}'.format( + image=self.image_ref, flavor=self.flavor_ref, + ssh_user=self.ssh_user)) + + def boot_instance(self, network): + self.keypair = self.create_keypair() + security_groups = [{'name': self.security_group['name']}] + create_kwargs = { + 'networks': [ + {'uuid': network['id']}, + ], + 'key_name': self.keypair['name'], + 'security_groups': security_groups, + } + instance = self.create_server(image=self.image_ref, + create_kwargs=create_kwargs, + flavor=self.flavor_ref) + return instance + + def init_ssh(self, instance, do_ping=False): + # Obtain a floating IP + floating_ip = (self.floating_ips_client.create_floating_ip() + ['floating_ip']) + self.addCleanup(self.delete_wrapper, + self.floating_ips_client.delete_floating_ip, + floating_ip['id']) + # Attach a floating IP + self.floating_ips_client.associate_floating_ip_to_server( + floating_ip['ip'], instance['id']) + # Check ssh + ssh_client = self.get_remote_client( + server_or_ip=floating_ip['ip'], + username=self.ssh_user, + private_key=self.keypair['private_key']) + + # NOTE(u_glide): Workaround for bug #1465682 + ssh_client = ssh_client.ssh_client + + self.share = self.shares_client.get_share(self.share['id']) + if do_ping: + server_ip = self.share['export_location'].split(":")[0] + ssh_client.exec_command("ping -c 1 %s" % server_ip) + return ssh_client + + def mount_share(self, location, ssh_client): + raise NotImplementedError + + def umount_share(self, ssh_client): + ssh_client.exec_command("sudo umount /mnt") + + def write_data(self, data, ssh_client): + ssh_client.exec_command("echo \"%s\" | sudo tee /mnt/t1 && sudo sync" % + data) + + def read_data(self, ssh_client): + data = ssh_client.exec_command("sudo cat /mnt/t1") + return data.rstrip() + + def create_share_network(self): + self.net = self._create_network(namestart="manila-share") + self.subnet = self._create_subnet(network=self.net, + namestart="manila-share-sub") + router = self._get_router() + self._create_router_interface(subnet_id=self.subnet['id'], + router_id=router['id']) + self.share_net = self._create_share_network( + neutron_net_id=self.net['id'], + neutron_subnet_id=self.subnet['id'], + name=data_utils.rand_name("sn-name")) + + def create_share(self, share_net_id): + self.share = self._create_share(share_protocol=self.protocol, + share_network_id=share_net_id) + + def allow_access_ip(self, share_id, ip=None, instance=None): + if instance and not ip: + try: + net_addresses = instance['addresses'] + first_address = net_addresses.values()[0][0] + ip = first_address['addr'] + except Exception: + # In case on an error ip will be still none + LOG.exception("Instance does not have a valid IP address." + "Falling back to default") + if not ip: + ip = '0.0.0.0/0' + self._allow_access(share_id, access_type='ip', access_to=ip) + + @test.services('compute', 'network') + def test_mount_share_one_vm(self): + self.security_group = self._create_security_group() + self.create_share_network() + self.create_share(self.share_net['id']) + instance = self.boot_instance(self.net) + self.allow_access_ip(self.share['id'], instance=instance) + ssh_client = self.init_ssh(instance) + for location in self.share['export_locations']: + self.mount_share(location, ssh_client) + self.umount_share(ssh_client) + self.servers_client.delete_server(instance['id']) + + @test.services('compute', 'network') + def test_read_write_two_vms(self): + """Boots two vms and writes/reads data on it.""" + test_data = "Some test data to write" + self.security_group = self._create_security_group() + self.create_share_network() + self.create_share(self.share_net['id']) + + # boot first VM and write data + instance1 = self.boot_instance(self.net) + self.allow_access_ip(self.share['id'], instance=instance1) + ssh_client_inst1 = self.init_ssh(instance1) + first_location = self.share['export_locations'][0] + self.mount_share(first_location, ssh_client_inst1) + self.addCleanup(self.umount_share, + ssh_client_inst1) + self.write_data(test_data, ssh_client_inst1) + + # boot second VM and read + instance2 = self.boot_instance(self.net) + self.allow_access_ip(self.share['id'], instance=instance2) + ssh_client_inst2 = self.init_ssh(instance2) + self.mount_share(first_location, ssh_client_inst2) + self.addCleanup(self.umount_share, + ssh_client_inst2) + data = self.read_data(ssh_client_inst2) + self.assertEqual(test_data, data) + + +class TestShareBasicOpsNFS(ShareBasicOpsBase): + protocol = "NFS" + + def mount_share(self, location, ssh_client): + ssh_client.exec_command("sudo mount \"%s\" /mnt" % location) + + +class TestShareBasicOpsCIFS(ShareBasicOpsBase): + protocol = "CIFS" + + def mount_share(self, location, ssh_client): + location = location.replace("\\", "/") + ssh_client.exec_command( + "sudo mount.cifs \"%s\" /mnt -o guest" % location + ) + + +# NOTE(u_glide): this function is required to exclude ShareBasicOpsBase from +# executed test cases. +# See: https://docs.python.org/2/library/unittest.html#load-tests-protocol +# for details. +def load_tests(loader, tests, _): + result = [] + for test_case in tests: + if type(test_case._tests[0]) is ShareBasicOpsBase: + continue + result.append(test_case) + return loader.suiteClass(result)