Merge "Add cinder v2 experimental job"
diff --git a/.zuul.yaml b/.zuul.yaml
index 75507d7..3fee6ff 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -101,6 +101,19 @@
Base integration test with Neutron networking and py36.
voting: false
+# TODO(gmann): needs to migrate this to zuulv3
+- job:
+ name: tempest-scenario-multinode-lvm-multibackend
+ parent: legacy-dsvm-base-multinode
+ run: playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
+ post-run: playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
+ timeout: 10800
+ required-projects:
+ - openstack-infra/devstack-gate
+ - openstack/neutron
+ - openstack/tempest
+ nodeset: ubuntu-xenial-2-node
+
- job:
name: tempest-full-queens
parent: tempest-full
@@ -272,6 +285,17 @@
- ^tempest/hacking/.*$
- ^tempest/tests/.*$
- tempest-tox-plugin-sanity-check
+ - tempest-scenario-multinode-lvm-multibackend:
+ voting: false
+ irrelevant-files:
+ - ^(test-|)requirements.txt$
+ - ^.*\.rst$
+ - ^doc/.*$
+ - ^etc/.*$
+ - ^releasenotes/.*$
+ - ^setup.cfg$
+ - ^tempest/hacking/.*$
+ - ^tempest/tests/.*$
gate:
jobs:
- nova-multiattach
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 3bc1d0c..fa76770 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -354,6 +354,10 @@
.. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+ * `2.49`_
+
+ .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
+
* `2.54`_
.. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id4
diff --git a/playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml b/playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
new file mode 100644
index 0000000..e07f551
--- /dev/null
+++ b/playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
@@ -0,0 +1,15 @@
+- hosts: primary
+ tasks:
+
+ - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
+ synchronize:
+ src: '{{ ansible_user_dir }}/workspace/'
+ dest: '{{ zuul.executor.log_root }}'
+ mode: pull
+ copy_links: true
+ verify_host: true
+ rsync_opts:
+ - --include=/logs/**
+ - --include=*/
+ - --exclude=*
+ - --prune-empty-dirs
diff --git a/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml b/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
new file mode 100644
index 0000000..03f64f9
--- /dev/null
+++ b/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
@@ -0,0 +1,65 @@
+- hosts: primary
+ name: Autoconverted job tempest-scenario-multinode-lvm-multibackend
+ from old job gate-tempest-dsvm-neutron-scenario-multinode-lvm-multibackend-ubuntu-xenial-nv
+ tasks:
+
+ - name: Ensure legacy workspace directory
+ file:
+ path: '{{ ansible_user_dir }}/workspace'
+ state: directory
+
+ - shell:
+ cmd: |
+ set -e
+ set -x
+ cat > clonemap.yaml << EOF
+ clonemap:
+ - name: openstack-infra/devstack-gate
+ dest: devstack-gate
+ EOF
+ /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
+ git://git.openstack.org \
+ openstack-infra/devstack-gate
+ executable: /bin/bash
+ chdir: '{{ ansible_user_dir }}/workspace'
+ environment: '{{ zuul | zuul_legacy_vars }}'
+
+ - shell:
+ cmd: |
+ set -e
+ set -x
+ cat << 'EOF' >>"/tmp/dg-local.conf"
+ [[local|localrc]]
+ ENABLE_IDENTITY_V2=False
+ TEMPEST_USE_TEST_ACCOUNTS=True
+ # Enable lvm multiple backends to run multi backend slow scenario tests.
+ # Note: multi backend experimental job exclude the slow scenario tests.
+ CINDER_ENABLED_BACKENDS=lvm:lvmdriver-1,lvm:lvmdriver-2
+
+ EOF
+ executable: /bin/bash
+ chdir: '{{ ansible_user_dir }}/workspace'
+ environment: '{{ zuul | zuul_legacy_vars }}'
+
+ - shell:
+ cmd: |
+ set -e
+ set -x
+ export PYTHONUNBUFFERED=true
+ export DEVSTACK_GATE_TEMPEST=1
+ # Run scenario and nova migration tests with concurrency 2
+ export DEVSTACK_GATE_TEMPEST_REGEX='(^tempest\.(scenario|api\.compute\.admin\.test_(live_|)migration))'
+ export TEMPEST_CONCURRENCY=2
+ export DEVSTACK_GATE_NEUTRON=1
+ export DEVSTACK_GATE_TLSPROXY=1
+ export BRANCH_OVERRIDE=default
+ if [ "$BRANCH_OVERRIDE" != "default" ] ; then
+ export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
+ fi
+ export DEVSTACK_GATE_TOPOLOGY="multinode"
+
+ cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
+ ./safe-devstack-vm-gate-wrap.sh
+ executable: /bin/bash
+ chdir: '{{ ansible_user_dir }}/workspace'
+ environment: '{{ zuul | zuul_legacy_vars }}'
diff --git a/releasenotes/notes/volume-v3-service-clients-a863a6336af56cca.yaml b/releasenotes/notes/volume-v3-service-clients-a863a6336af56cca.yaml
new file mode 100644
index 0000000..b572a34
--- /dev/null
+++ b/releasenotes/notes/volume-v3-service-clients-a863a6336af56cca.yaml
@@ -0,0 +1,12 @@
+---
+features:
+ - |
+ Adds volume service clients for v3 APIs. As v3 base API should be
+ identical to v2 APIs, we just copy all existing v2 service client
+ for v3 API.
+deprecations:
+ - |
+ Deprecates the volume service clients for v2 APIs. Volume v2 APIs
+ are deprecated in all supported stable branches, so it's time
+ to deprecate the tempest service clients for v2 APIs and remove in future
+ release.
diff --git a/tempest/README.rst b/tempest/README.rst
index 62821de..a5f4a92 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -12,12 +12,12 @@
and guidelines. Below is the overview of the Tempest respository structure
to make this clear.
- .. code-block:: console
+.. code-block:: console
- tempest/
- api/ - API tests
- scenario/ - complex scenario tests
- tests/ - unit tests for Tempest internals
+ tempest/
+ api/ - API tests
+ scenario/ - complex scenario tests
+ tests/ - unit tests for Tempest internals
Each of these directories contains different types of tests. What
belongs in each directory, the rules and examples for good tests, are
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index c2bdf7e..df534bc 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -46,13 +46,19 @@
# tenant most of them should be skipped if we can't do that
cls.demo_tenant_id = cls.quotas_client.tenant_id
- cls.default_quota_set = set(('injected_file_content_bytes',
- 'metadata_items', 'injected_files',
- 'ram', 'floating_ips',
- 'fixed_ips', 'key_pairs',
- 'injected_file_path_bytes',
- 'instances', 'security_group_rules',
- 'cores', 'security_groups'))
+ cls.default_quota_set = set(('metadata_items', 'ram', 'key_pairs',
+ 'instances', 'cores',
+ 'server_group_members', 'server_groups'))
+ if cls.is_requested_microversion_compatible('2.35'):
+ cls.default_quota_set = \
+ cls.default_quota_set | set(['fixed_ips', 'floating_ips',
+ 'security_group_rules',
+ 'security_groups'])
+ if cls.is_requested_microversion_compatible('2.56'):
+ cls.default_quota_set = \
+ cls.default_quota_set | set(['injected_file_content_bytes',
+ 'injected_file_path_bytes',
+ 'injected_files'])
@decorators.idempotent_id('3b0a7c8f-cf58-46b8-a60c-715a32a8ba7d')
def test_get_default_quotas(self):
@@ -69,13 +75,19 @@
# Admin can update all the resource quota limits for a tenant
default_quota_set = self.adm_client.show_default_quota_set(
self.demo_tenant_id)['quota_set']
- new_quota_set = {'injected_file_content_bytes': 20480,
- 'metadata_items': 256, 'injected_files': 10,
- 'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10,
- 'key_pairs': 200, 'injected_file_path_bytes': 512,
- 'instances': 20, 'security_group_rules': 20,
- 'cores': 2, 'security_groups': 20,
- 'server_groups': 20, 'server_group_members': 20}
+ new_quota_set = {'metadata_items': 256, 'ram': 10240,
+ 'key_pairs': 200, 'instances': 20,
+ 'server_groups': 20,
+ 'server_group_members': 20, 'cores': 2}
+ if self.is_requested_microversion_compatible('2.35'):
+ new_quota_set.update({'fixed_ips': 10, 'floating_ips': 20,
+ 'security_group_rules': 20,
+ 'security_groups': 20})
+ if self.is_requested_microversion_compatible('2.56'):
+ new_quota_set.update({'injected_file_content_bytes': 20480,
+ 'injected_file_path_bytes': 512,
+ 'injected_files': 10})
+
# Update limits for all quota resources
quota_set = self.adm_client.update_quota_set(
self.demo_tenant_id,
diff --git a/tempest/api/compute/admin/test_quotas_negative.py b/tempest/api/compute/admin/test_quotas_negative.py
index 5ef7ee4..f90ff92 100644
--- a/tempest/api/compute/admin/test_quotas_negative.py
+++ b/tempest/api/compute/admin/test_quotas_negative.py
@@ -22,12 +22,12 @@
CONF = config.CONF
-class QuotasAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class QuotasAdminNegativeTestBase(base.BaseV2ComputeAdminTest):
force_tenant_isolation = True
@classmethod
def setup_clients(cls):
- super(QuotasAdminNegativeTestJSON, cls).setup_clients()
+ super(QuotasAdminNegativeTestBase, cls).setup_clients()
cls.client = cls.os_primary.quotas_client
cls.adm_client = cls.os_admin.quotas_client
cls.sg_client = cls.security_groups_client
@@ -35,7 +35,7 @@
@classmethod
def resource_setup(cls):
- super(QuotasAdminNegativeTestJSON, cls).resource_setup()
+ super(QuotasAdminNegativeTestBase, cls).resource_setup()
# NOTE(afazekas): these test cases should always create and use a new
# tenant most of them should be skipped if we can't do that
cls.demo_tenant_id = cls.client.tenant_id
@@ -51,6 +51,9 @@
self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
**{quota_item: default_quota_value})
+
+class QuotasAdminNegativeTest(QuotasAdminNegativeTestBase):
+
@decorators.attr(type=['negative'])
@decorators.idempotent_id('733abfe8-166e-47bb-8363-23dbd7ff3476')
def test_update_quota_normal_user(self):
@@ -85,6 +88,10 @@
self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
self.create_test_server)
+
+class QuotasSecurityGroupAdminNegativeTest(QuotasAdminNegativeTestBase):
+ max_microversion = '2.35'
+
@decorators.skip_because(bug="1186354",
condition=CONF.service_available.neutron)
@decorators.attr(type=['negative'])
diff --git a/tempest/api/compute/admin/test_security_group_default_rules.py b/tempest/api/compute/admin/test_security_group_default_rules.py
index f2f3b57..bca6a22 100644
--- a/tempest/api/compute/admin/test_security_group_default_rules.py
+++ b/tempest/api/compute/admin/test_security_group_default_rules.py
@@ -23,6 +23,7 @@
class SecurityGroupDefaultRulesTest(base.BaseV2ComputeAdminTest):
+ max_microversion = '2.35'
@classmethod
# TODO(GMann): Once Bug# 1311500 is fixed, these test can run
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index ff9caa3..f0178aa 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -20,6 +20,7 @@
class SecurityGroupsTestAdminJSON(base.BaseV2ComputeAdminTest):
+ max_microversion = '2.35'
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 975728c..b377c0c 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -501,7 +501,7 @@
# is already detached.
pass
- def attach_volume(self, server, volume, device=None):
+ def attach_volume(self, server, volume, device=None, tag=None):
"""Attaches volume to server and waits for 'in-use' volume status.
The volume will be detached when the test tears down.
@@ -510,10 +510,14 @@
:param volume: The volume to attach.
:param device: Optional mountpoint for the attached volume. Note that
this is not guaranteed for all hypervisors and is not recommended.
+ :param tag: Optional device role tag to apply to the volume.
"""
attach_kwargs = dict(volumeId=volume['id'])
if device:
attach_kwargs['device'] = device
+ if tag:
+ attach_kwargs['tag'] = tag
+
attachment = self.servers_client.attach_volume(
server['id'], **attach_kwargs)['volumeAttachment']
# On teardown detach the volume and wait for it to be available. This
diff --git a/tempest/api/compute/security_groups/base.py b/tempest/api/compute/security_groups/base.py
index 54a6da8..49125d1 100644
--- a/tempest/api/compute/security_groups/base.py
+++ b/tempest/api/compute/security_groups/base.py
@@ -22,6 +22,7 @@
class BaseSecurityGroupsTest(base.BaseV2ComputeTest):
+ max_microversion = '2.35'
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index b0d527c..3a85a86 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -13,12 +13,14 @@
# under the License.
import json
+import time
from oslo_log import log as logging
from tempest.api.compute import base
from tempest.common import utils
from tempest.common.utils.linux import remote_client
+from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
@@ -31,18 +33,11 @@
LOG = logging.getLogger(__name__)
-class DeviceTaggingTest(base.BaseV2ComputeTest):
-
- min_microversion = '2.32'
- # NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
- # bug in the 2.32 microversion, tags on block devices only worked with the
- # 2.32 microversion specifically. And tags on networks only worked between
- # 2.32 and 2.36 inclusive; the 2.37 microversion broke tags for networks.
- max_microversion = '2.32'
+class DeviceTaggingBase(base.BaseV2ComputeTest):
@classmethod
def skip_checks(cls):
- super(DeviceTaggingTest, cls).skip_checks()
+ super(DeviceTaggingBase, cls).skip_checks()
if not CONF.service_available.neutron:
raise cls.skipException('Neutron is required')
if not CONF.validation.run_validation:
@@ -54,7 +49,7 @@
@classmethod
def setup_clients(cls):
- super(DeviceTaggingTest, cls).setup_clients()
+ super(DeviceTaggingBase, cls).setup_clients()
cls.networks_client = cls.os_primary.networks_client
cls.ports_client = cls.os_primary.ports_client
cls.subnets_client = cls.os_primary.subnets_client
@@ -64,7 +59,55 @@
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True,
dhcp=True)
- super(DeviceTaggingTest, cls).setup_credentials()
+ super(DeviceTaggingBase, cls).setup_credentials()
+
+ def verify_metadata_from_api(self, server, ssh_client, verify_method):
+ md_url = 'http://169.254.169.254/openstack/latest/meta_data.json'
+ LOG.info('Attempting to verify tagged devices in server %s via '
+ 'the metadata service: %s', server['id'], md_url)
+
+ def get_and_verify_metadata():
+ try:
+ ssh_client.exec_command('curl -V')
+ except exceptions.SSHExecCommandFailed:
+ if not CONF.compute_feature_enabled.config_drive:
+ raise self.skipException('curl not found in guest '
+ 'and config drive is '
+ 'disabled')
+ LOG.warning('curl was not found in the guest, device '
+ 'tagging metadata was not checked in the '
+ 'metadata API')
+ return True
+ cmd = 'curl %s' % md_url
+ md_json = ssh_client.exec_command(cmd)
+ verify_method(md_json)
+ return True
+
+ if not test_utils.call_until_true(get_and_verify_metadata,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
+ raise exceptions.TimeoutException('Timeout while verifying '
+ 'metadata on server.')
+
+ def verify_metadata_on_config_drive(self, server, ssh_client,
+ verify_method):
+ LOG.info('Attempting to verify tagged devices in server %s via '
+ 'the config drive.', server['id'])
+ ssh_client.mount_config_drive()
+ cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
+ md_json = ssh_client.exec_command(cmd_md)
+ verify_method(md_json)
+ ssh_client.unmount_config_drive()
+
+
+class TaggedBootDevicesTest(DeviceTaggingBase):
+
+ min_microversion = '2.32'
+ # NOTE(mriedem): max_version looks odd but it's actually correct. Due to a
+ # bug in the 2.32 microversion, tags on block devices only worked with the
+ # 2.32 microversion specifically. And tags on networks only worked between
+ # 2.32 and 2.36 inclusive; the 2.37 microversion broke tags for networks.
+ max_microversion = '2.32'
def verify_device_metadata(self, md_json):
md_dict = json.loads(md_json)
@@ -92,7 +135,7 @@
@decorators.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
@utils.services('network', 'volume', 'image')
- def test_device_tagging(self):
+ def test_tagged_boot_devices(self):
# Create volumes
# The create_volume methods waits for the volumes to be available and
# the base class will clean them up on tearDown.
@@ -134,7 +177,6 @@
self.addCleanup(self.ports_client.delete_port, self.port2['id'])
# Create server
- admin_pass = data_utils.rand_password()
config_drive_enabled = CONF.compute_feature_enabled.config_drive
validation_resources = self.get_test_validation_resources(
self.os_primary)
@@ -144,7 +186,6 @@
wait_until='ACTIVE',
validation_resources=validation_resources,
config_drive=config_drive_enabled,
- adminPass=admin_pass,
name=data_utils.rand_name('device-tagging-server'),
networks=[
# Validation network for ssh
@@ -209,11 +250,10 @@
self.addCleanup(self.delete_server, server['id'])
server = self.servers_client.show_server(server['id'])['server']
- self.ssh_client = remote_client.RemoteClient(
+ ssh_client = remote_client.RemoteClient(
self.get_server_ip(server, validation_resources),
CONF.validation.image_ssh_user,
- admin_pass,
- validation_resources['keypair']['private_key'],
+ pkey=validation_resources['keypair']['private_key'],
server=server,
servers_client=self.servers_client)
@@ -233,46 +273,104 @@
self.assertTrue(self.net_2_100_mac)
self.assertTrue(self.net_2_200_mac)
- # Verify metadata from metadata service
+ # Verify metadata from metadata API
if CONF.compute_feature_enabled.metadata_service:
- md_url = 'http://169.254.169.254/openstack/latest/meta_data.json'
- LOG.info('Attempting to verify tagged devices in server %s via '
- 'the metadata service: %s', server['id'], md_url)
-
- def get_and_verify_metadata():
- try:
- self.ssh_client.exec_command('curl -V')
- except exceptions.SSHExecCommandFailed:
- if not CONF.compute_feature_enabled.config_drive:
- raise self.skipException('curl not found in guest '
- 'and config drive is '
- 'disabled')
- LOG.warning('curl was not found in the guest, device '
- 'tagging metadata was not checked in the '
- 'metadata API')
- return True
- cmd = 'curl %s' % md_url
- md_json = self.ssh_client.exec_command(cmd)
- self.verify_device_metadata(md_json)
- return True
-
- if not test_utils.call_until_true(get_and_verify_metadata,
- CONF.compute.build_timeout,
- CONF.compute.build_interval):
- raise exceptions.TimeoutException('Timeout while verifying '
- 'metadata on server.')
+ self.verify_metadata_from_api(server, ssh_client,
+ self.verify_device_metadata)
# Verify metadata on config drive
if CONF.compute_feature_enabled.config_drive:
- LOG.info('Attempting to verify tagged devices in server %s via '
- 'the config drive.', server['id'])
- self.ssh_client.mount_config_drive()
- cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
- md_json = self.ssh_client.exec_command(cmd_md)
- self.verify_device_metadata(md_json)
- self.ssh_client.unmount_config_drive()
+ self.verify_metadata_on_config_drive(server, ssh_client,
+ self.verify_device_metadata)
-class DeviceTaggingTestV2_42(DeviceTaggingTest):
+class TaggedBootDevicesTest_v242(TaggedBootDevicesTest):
min_microversion = '2.42'
max_microversion = 'latest'
+
+
+class TaggedAttachmentsTest(DeviceTaggingBase):
+
+ min_microversion = '2.49'
+ max_microversion = 'latest'
+
+ @classmethod
+ def skip_checks(cls):
+ super(TaggedAttachmentsTest, cls).skip_checks()
+ if not CONF.compute_feature_enabled.metadata_service:
+ raise cls.skipException('Metadata API must be enabled')
+
+ def verify_device_metadata(self, md_json):
+ md_dict = json.loads(md_json)
+ found_devices = [d['tags'][0] for d in md_dict['devices']]
+ self.assertItemsEqual(found_devices, ['nic-tag', 'volume-tag'])
+
+ def verify_empty_devices(self, md_json):
+ md_dict = json.loads(md_json)
+ self.assertEmpty(md_dict['devices'])
+
+ @decorators.idempotent_id('3e41c782-2a89-4922-a9d2-9a188c4e7c7c')
+ @utils.services('network', 'volume', 'image')
+ def test_tagged_attachment(self):
+ # Create network
+ net = self.networks_client.create_network(
+ name=data_utils.rand_name(
+ 'tagged-attachments-test-net'))['network']
+ self.addCleanup(self.networks_client.delete_network, net['id'])
+
+ # Create subnet
+ subnet = self.subnets_client.create_subnet(
+ network_id=net['id'],
+ cidr='10.10.10.0/24',
+ ip_version=4)['subnet']
+ self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
+
+ # Create volume
+ volume = self.create_volume()
+
+ # Boot test server
+ config_drive_enabled = CONF.compute_feature_enabled.config_drive
+ validation_resources = self.get_test_validation_resources(
+ self.os_primary)
+
+ server = self.create_test_server(
+ validatable=True,
+ validation_resources=validation_resources,
+ config_drive=config_drive_enabled,
+ name=data_utils.rand_name('device-tagging-server'),
+ networks=[{'uuid': self.get_tenant_network()['id']}])
+ self.addCleanup(self.delete_server, server['id'])
+
+ # Attach tagged nic and volume
+ interface = self.interfaces_client.create_interface(
+ server['id'], net_id=net['id'],
+ tag='nic-tag')['interfaceAttachment']
+ self.attach_volume(server, volume, tag='volume-tag')
+
+ ssh_client = remote_client.RemoteClient(
+ self.get_server_ip(server, validation_resources),
+ CONF.validation.image_ssh_user,
+ pkey=validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
+
+ # NOTE(artom) The newly attached tagged nic won't appear in the
+ # metadata until the cache is refreshed. We wait 16 seconds since the
+ # default cache expiry is 15 seconds.
+ time.sleep(16)
+ self.verify_metadata_from_api(server, ssh_client,
+ self.verify_device_metadata)
+
+ # Detach tagged nic and volume
+ self.servers_client.detach_volume(server['id'], volume['id'])
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume['id'], 'available')
+ self.interfaces_client.delete_interface(server['id'],
+ interface['port_id'])
+ waiters.wait_for_interface_detach(self.interfaces_client,
+ server['id'],
+ interface['port_id'])
+ # NOTE(artom) More waiting until metadata cache is refreshed.
+ time.sleep(16)
+ self.verify_metadata_from_api(server, ssh_client,
+ self.verify_empty_devices)
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index 7cf90ae..a62492d 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -43,14 +43,19 @@
super(QuotasTestJSON, cls).resource_setup()
cls.tenant_id = cls.client.tenant_id
cls.user_id = cls.client.user_id
- cls.default_quota_set = set(('injected_file_content_bytes',
- 'metadata_items', 'injected_files',
- 'ram', 'floating_ips',
- 'fixed_ips', 'key_pairs',
- 'injected_file_path_bytes',
- 'instances', 'security_group_rules',
- 'cores', 'security_groups',
+ cls.default_quota_set = set(('metadata_items', 'ram', 'key_pairs',
+ 'instances', 'cores',
'server_group_members', 'server_groups'))
+ if cls.is_requested_microversion_compatible('2.35'):
+ cls.default_quota_set = \
+ cls.default_quota_set | set(['fixed_ips', 'floating_ips',
+ 'security_group_rules',
+ 'security_groups'])
+ if cls.is_requested_microversion_compatible('2.56'):
+ cls.default_quota_set = \
+ cls.default_quota_set | set(['injected_file_content_bytes',
+ 'injected_file_path_bytes',
+ 'injected_files'])
@decorators.idempotent_id('f1ef0a97-dbbb-4cca-adc5-c9fbc4f76107')
def test_get_quotas(self):
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index 7a74869..8618148 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -56,3 +56,16 @@
self.assertRaises(lib_exc.BadRequest,
self.attach_volume, server, volume)
+
+ @decorators.attr(type=['negative'])
+ @decorators.idempotent_id('ee37a796-2afb-11e7-bc0f-fa163e65f5ce')
+ def test_attach_attached_volume_to_different_server(self):
+ server1 = self.create_test_server(wait_until='ACTIVE')
+ volume = self.create_volume()
+
+ self.attach_volume(server1, volume)
+
+ # Create server2 and attach in-use volume
+ server2 = self.create_test_server(wait_until='ACTIVE')
+ self.assertRaises(lib_exc.BadRequest,
+ self.attach_volume, server2, volume)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 08e2a12..0e86f05 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -287,3 +287,24 @@
raise lib_exc.TimeoutException(message)
return body
+
+
+def wait_for_interface_detach(client, server_id, port_id):
+ """Waits for an interface to be detached from a server."""
+ body = client.list_interfaces(server_id)['interfaceAttachments']
+ ports = [iface['port_id'] for iface in body]
+ start = int(time.time())
+
+ while port_id in ports:
+ time.sleep(client.build_interval)
+ body = client.list_interfaces(server_id)['interfaceAttachments']
+ ports = [iface['port_id'] for iface in body]
+ if port_id not in ports:
+ return body
+
+ timed_out = int(time.time()) - start >= client.build_timeout
+ if timed_out:
+ message = ('Interface %s failed to detach from server %s within '
+ 'the required time (%s s)' % (port_id, server_id,
+ client.build_timeout))
+ raise lib_exc.TimeoutException(message)
diff --git a/tempest/lib/services/volume/v2/availability_zone_client.py b/tempest/lib/services/volume/v2/availability_zone_client.py
index 147e4c6..bdb2304 100644
--- a/tempest/lib/services/volume/v2/availability_zone_client.py
+++ b/tempest/lib/services/volume/v2/availability_zone_client.py
@@ -13,15 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_serialization import jsonutils as json
+from debtcollector import moves
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import availability_zone_client
-class AvailabilityZoneClient(rest_client.RestClient):
-
- def list_availability_zones(self):
- resp, body = self.get('os-availability-zone')
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+AvailabilityZoneClient = moves.moved_class(
+ availability_zone_client.AvailabilityZoneClient, 'AvailabilityZoneClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index 3dbb30c..80b3631 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -12,107 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.volume import base_client
+from tempest.lib.services.volume.v3 import backups_client
-class BackupsClient(base_client.BaseClient):
- """Volume Backups client"""
-
- def create_backup(self, **kwargs):
- """Creates a backup of volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/index.html#create-backup
- """
- post_body = json.dumps({'backup': kwargs})
- resp, body = self.post('backups', post_body)
- body = json.loads(body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def restore_backup(self, backup_id, **kwargs):
- """Restore volume from backup.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/index.html#restore-backup
- """
- post_body = json.dumps({'restore': kwargs})
- resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
- body = json.loads(body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_backup(self, backup_id):
- """Delete a backup of volume."""
- resp, body = self.delete('backups/%s' % backup_id)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_backup(self, backup_id):
- """Returns the details of a single backup."""
- url = "backups/%s" % backup_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_backups(self, detail=False, **params):
- """List all the tenant's backups.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#list-backups
- http://developer.openstack.org/api-ref/block-storage/v2/#list-backups-with-details
- """
- url = "backups"
- if detail:
- url += "/detail"
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def export_backup(self, backup_id):
- """Export backup metadata record."""
- url = "backups/%s/export_record" % backup_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def import_backup(self, **kwargs):
- """Import backup metadata record."""
- post_body = json.dumps({'backup-record': kwargs})
- resp, body = self.post("backups/import_record", post_body)
- body = json.loads(body)
- self.expected_success(201, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def reset_backup_status(self, backup_id, status):
- """Reset the specified backup's status."""
- post_body = json.dumps({'os-reset_status': {"status": status}})
- resp, body = self.post('backups/%s/action' % backup_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def is_resource_deleted(self, id):
- try:
- self.show_backup(id)
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'backup'
+BackupsClient = moves.moved_class(
+ backups_client.BackupsClient, 'BackupsClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/capabilities_client.py b/tempest/lib/services/volume/v2/capabilities_client.py
index 7ebcd69..d8cf806 100644
--- a/tempest/lib/services/volume/v2/capabilities_client.py
+++ b/tempest/lib/services/volume/v2/capabilities_client.py
@@ -13,22 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_serialization import jsonutils as json
+from debtcollector import moves
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import capabilities_client
-class CapabilitiesClient(rest_client.RestClient):
-
- def show_backend_capabilities(self, host):
- """Shows capabilities for a storage back end.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-back-end-capabilities
- """
- url = 'capabilities/%s' % host
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+CapabilitiesClient = moves.moved_class(
+ capabilities_client.CapabilitiesClient, 'CapabilitiesClient',
+ __name__, version="Queens", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index 5a7ea12..875e59e 100644
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -13,78 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_serialization import jsonutils as json
+from debtcollector import moves
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import encryption_types_client
-class EncryptionTypesClient(rest_client.RestClient):
-
- def is_resource_deleted(self, id):
- try:
- body = self.show_encryption_type(id)
- if not body:
- return True
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'encryption-type'
-
- def show_encryption_type(self, volume_type_id):
- """Get the volume encryption type for the specified volume type.
-
- volume_type_id: Id of volume_type.
- """
- url = "/types/%s/encryption" % volume_type_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_encryption_specs_item(self, volume_type_id, key):
- """Get the encryption specs item for the specified volume type."""
- url = "/types/%s/encryption/%s" % (volume_type_id, key)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_encryption_type(self, volume_type_id, **kwargs):
- """Create encryption type.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#create-an-encryption-type-for-v2
- """
- url = "/types/%s/encryption" % volume_type_id
- post_body = json.dumps({'encryption': kwargs})
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_encryption_type(self, volume_type_id):
- """Delete the encryption type for the specified volume-type."""
- resp, body = self.delete(
- "/types/%s/encryption/provider" % volume_type_id)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_encryption_type(self, volume_type_id, **kwargs):
- """Update an encryption type for an existing volume type.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#update-an-encryption-type-for-v2
- """
- url = "/types/%s/encryption/provider" % volume_type_id
- put_body = json.dumps({'encryption': kwargs})
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+EncryptionTypesClient = moves.moved_class(
+ encryption_types_client.EncryptionTypesClient, 'EncryptionTypesClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/extensions_client.py b/tempest/lib/services/volume/v2/extensions_client.py
index 45b7a56..6316ef5 100644
--- a/tempest/lib/services/volume/v2/extensions_client.py
+++ b/tempest/lib/services/volume/v2/extensions_client.py
@@ -12,18 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import extensions_client
-class ExtensionsClient(rest_client.RestClient):
- """Volume extensions client."""
-
- def list_extensions(self):
- url = 'extensions'
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+ExtensionsClient = moves.moved_class(
+ extensions_client.ExtensionsClient, 'ExtensionsClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
index c395325..38f1b38 100644
--- a/tempest/lib/services/volume/v2/hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -12,36 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import hosts_client
-class HostsClient(rest_client.RestClient):
- """Client class to send CRUD Volume API requests"""
-
- def list_hosts(self, **params):
- """Lists all hosts.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#list-all-hosts
- """
- url = 'os-hosts'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_host(self, host_name):
- """Show host details."""
- url = 'os-hosts/%s' % host_name
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
+HostsClient = moves.moved_class(
+ hosts_client.HostsClient, 'HostsClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/limits_client.py b/tempest/lib/services/volume/v2/limits_client.py
index 9500254..a6b8c5a 100644
--- a/tempest/lib/services/volume/v2/limits_client.py
+++ b/tempest/lib/services/volume/v2/limits_client.py
@@ -12,19 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import limits_client
-class LimitsClient(rest_client.RestClient):
- """Volume limits client."""
-
- def show_limits(self):
- """Returns the details of a volume absolute limits."""
- url = "limits"
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+LimitsClient = moves.moved_class(
+ limits_client.LimitsClient, 'LimitsClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/qos_client.py b/tempest/lib/services/volume/v2/qos_client.py
index f8b8c3c..b81384e 100644
--- a/tempest/lib/services/volume/v2/qos_client.py
+++ b/tempest/lib/services/volume/v2/qos_client.py
@@ -11,121 +11,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import qos_client
-class QosSpecsClient(rest_client.RestClient):
- """Volume QoS client.
-
- Client class to send CRUD QoS API requests
- """
-
- def is_resource_deleted(self, qos_id):
- try:
- self.show_qos(qos_id)
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'qos'
-
- def create_qos(self, **kwargs):
- """Create a QoS Specification.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#create-qos-specification
- """
- post_body = json.dumps({'qos_specs': kwargs})
- resp, body = self.post('qos-specs', post_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_qos(self, qos_id, force=False):
- """Delete the specified QoS specification."""
- resp, body = self.delete(
- "qos-specs/%s?force=%s" % (qos_id, force))
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_qos(self):
- """List all the QoS specifications created."""
- url = 'qos-specs'
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_qos(self, qos_id):
- """Get the specified QoS specification."""
- url = "qos-specs/%s" % qos_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def set_qos_key(self, qos_id, **kwargs):
- """Set the specified keys/values of QoS specification.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#set-keys-in-qos-specification
- """
- put_body = json.dumps({"qos_specs": kwargs})
- resp, body = self.put('qos-specs/%s' % qos_id, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def unset_qos_key(self, qos_id, keys):
- """Unset the specified keys of QoS specification.
-
- :param keys: keys to delete from the QoS specification.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#unset-keys-in-qos-specification
- """
- put_body = json.dumps({'keys': keys})
- resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def associate_qos(self, qos_id, vol_type_id):
- """Associate the specified QoS with specified volume-type."""
- url = "qos-specs/%s/associate" % qos_id
- url += "?vol_type_id=%s" % vol_type_id
- resp, body = self.get(url)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_association_qos(self, qos_id):
- """Get the association of the specified QoS specification."""
- url = "qos-specs/%s/associations" % qos_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def disassociate_qos(self, qos_id, vol_type_id):
- """Disassociate the specified QoS with specified volume-type."""
- url = "qos-specs/%s/disassociate" % qos_id
- url += "?vol_type_id=%s" % vol_type_id
- resp, body = self.get(url)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def disassociate_all_qos(self, qos_id):
- """Disassociate the specified QoS with all associations."""
- url = "qos-specs/%s/disassociate_all" % qos_id
- resp, body = self.get(url)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
+QosSpecsClient = moves.moved_class(
+ qos_client.QosSpecsClient, 'QosSpecsClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/quota_classes_client.py b/tempest/lib/services/volume/v2/quota_classes_client.py
index eeeb268..24aab89 100644
--- a/tempest/lib/services/volume/v2/quota_classes_client.py
+++ b/tempest/lib/services/volume/v2/quota_classes_client.py
@@ -12,38 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import quota_classes_client
-class QuotaClassesClient(rest_client.RestClient):
- """Volume quota class client."""
-
- def show_quota_class_set(self, quota_class_id):
- """List quotas for a quota class.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-quota-classes
- """
- url = 'os-quota-class-sets/%s' % quota_class_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_quota_class_set(self, quota_class_id, **kwargs):
- """Update quotas for a quota class.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/index.html#update-quota-classes
- """
- url = 'os-quota-class-sets/%s' % quota_class_id
- put_body = json.dumps({'quota_class_set': kwargs})
- resp, body = self.put(url, put_body)
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
+QuotaClassesClient = moves.moved_class(
+ quota_classes_client.QuotaClassesClient, 'QuotaClassesClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/quotas_client.py b/tempest/lib/services/volume/v2/quotas_client.py
index 8906294..6f9f61c 100644
--- a/tempest/lib/services/volume/v2/quotas_client.py
+++ b/tempest/lib/services/volume/v2/quotas_client.py
@@ -12,52 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import quotas_client
-class QuotasClient(rest_client.RestClient):
- """Client class to send CRUD Volume Quotas API requests"""
-
- def show_default_quota_set(self, tenant_id):
- """List the default volume quota set for a tenant."""
-
- url = 'os-quota-sets/%s/defaults' % tenant_id
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = jsonutils.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def show_quota_set(self, tenant_id, params=None):
- """List the quota set for a tenant."""
-
- url = 'os-quota-sets/%s' % tenant_id
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- self.expected_success(200, resp.status)
- body = jsonutils.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def update_quota_set(self, tenant_id, **kwargs):
- """Updates quota set
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/index.html#update-quotas
- """
- put_body = jsonutils.dumps({'quota_set': kwargs})
- resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
- self.expected_success(200, resp.status)
- body = jsonutils.loads(body)
- return rest_client.ResponseBody(resp, body)
-
- def delete_quota_set(self, tenant_id):
- """Delete the tenant's quota set."""
- resp, body = self.delete('os-quota-sets/%s' % tenant_id)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+QuotasClient = moves.moved_class(
+ quotas_client.QuotasClient, 'QuotasClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/scheduler_stats_client.py b/tempest/lib/services/volume/v2/scheduler_stats_client.py
index bd5fa6d..a5adb34 100644
--- a/tempest/lib/services/volume/v2/scheduler_stats_client.py
+++ b/tempest/lib/services/volume/v2/scheduler_stats_client.py
@@ -12,25 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import scheduler_stats_client
-class SchedulerStatsClient(rest_client.RestClient):
-
- def list_pools(self, detail=False):
- """List all the volumes pools (hosts).
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/index.html#list-back-end-storage-pools
- """
- url = 'scheduler-stats/get_pools'
- if detail:
- url += '?detail=True'
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+SchedulerStatsClient = moves.moved_class(
+ scheduler_stats_client.SchedulerStatsClient, 'SchedulerStatsClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/services_client.py b/tempest/lib/services/volume/v2/services_client.py
index 09036a4..a4491d3 100644
--- a/tempest/lib/services/volume/v2/services_client.py
+++ b/tempest/lib/services/volume/v2/services_client.py
@@ -12,22 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import services_client
-class ServicesClient(rest_client.RestClient):
- """Client class to send CRUD Volume API requests"""
-
- def list_services(self, **params):
- url = 'os-services'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+ServicesClient = moves.moved_class(
+ services_client.ServicesClient, 'ServicesClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/snapshot_manage_client.py b/tempest/lib/services/volume/v2/snapshot_manage_client.py
index 43fd328..132209f 100644
--- a/tempest/lib/services/volume/v2/snapshot_manage_client.py
+++ b/tempest/lib/services/volume/v2/snapshot_manage_client.py
@@ -12,20 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import snapshot_manage_client
-class SnapshotManageClient(rest_client.RestClient):
- """Snapshot manage client."""
-
- def manage_snapshot(self, **kwargs):
- """Manage a snapshot."""
- post_body = json.dumps({'snapshot': kwargs})
- url = 'os-snapshot-manage'
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
+SnapshotManageClient = moves.moved_class(
+ snapshot_manage_client.SnapshotManageClient, 'SnapshotManageClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 1f05180..3a72cc1 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -9,199 +9,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import snapshots_client
-class SnapshotsClient(rest_client.RestClient):
- """Client class to send CRUD Volume API requests."""
- create_resp = 202
-
- def list_snapshots(self, detail=False, **params):
- """List all the snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-with-details
- http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots
- """
- url = 'snapshots'
- if detail:
- url += '/detail'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_snapshot(self, snapshot_id):
- """Returns the details of a single snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-details
- """
- url = "snapshots/%s" % snapshot_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_snapshot(self, **kwargs):
- """Creates a new snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot
- """
- post_body = json.dumps({'snapshot': kwargs})
- resp, body = self.post('snapshots', post_body)
- body = json.loads(body)
- self.expected_success(self.create_resp, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_snapshot(self, snapshot_id, **kwargs):
- """Updates a snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot
- """
- put_body = json.dumps({'snapshot': kwargs})
- resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_snapshot(self, snapshot_id):
- """Delete Snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot
- """
- resp, body = self.delete("snapshots/%s" % snapshot_id)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def is_resource_deleted(self, id):
- try:
- self.show_snapshot(id)
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'volume-snapshot'
-
- def reset_snapshot_status(self, snapshot_id, status):
- """Reset the specified snapshot's status."""
- post_body = json.dumps({'os-reset_status': {"status": status}})
- resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_snapshot_status(self, snapshot_id, **kwargs):
- """Update the specified snapshot's status."""
- # TODO(gmann): api-site doesn't contain doc ref
- # for this API. After fixing the api-site, we need to
- # add the link here.
- # Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645
-
- post_body = json.dumps({'os-update_snapshot_status': kwargs})
- url = 'snapshots/%s/action' % snapshot_id
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_snapshot_metadata(self, snapshot_id, metadata):
- """Create metadata for the snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot-metadata
- """
- put_body = json.dumps({'metadata': metadata})
- url = "snapshots/%s/metadata" % snapshot_id
- resp, body = self.post(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_snapshot_metadata(self, snapshot_id):
- """Get metadata of the snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata
- """
- url = "snapshots/%s/metadata" % snapshot_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_snapshot_metadata(self, snapshot_id, **kwargs):
- """Update metadata for the snapshot.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata
- """
- put_body = json.dumps(kwargs)
- url = "snapshots/%s/metadata" % snapshot_id
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_snapshot_metadata_item(self, snapshot_id, id):
- """Show metadata item for the snapshot."""
- url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
- """Update metadata item for the snapshot."""
- # TODO(piyush): Current api-site doesn't contain this API description.
- # After fixing the api-site, we need to fix here also for putting the
- # link to api-site.
- # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1529064
- put_body = json.dumps(kwargs)
- url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_snapshot_metadata_item(self, snapshot_id, id):
- """Delete metadata item for the snapshot."""
- url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
- resp, body = self.delete(url)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def force_delete_snapshot(self, snapshot_id):
- """Force Delete Snapshot."""
- post_body = json.dumps({'os-force_delete': {}})
- resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def unmanage_snapshot(self, snapshot_id):
- """Unmanage a snapshot."""
- post_body = json.dumps({'os-unmanage': {}})
- url = 'snapshots/%s/action' % (snapshot_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
+SnapshotsClient = moves.moved_class(
+ snapshots_client.SnapshotsClient, 'SnapshotsClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/transfers_client.py b/tempest/lib/services/volume/v2/transfers_client.py
index 291aadf..701d0ae 100644
--- a/tempest/lib/services/volume/v2/transfers_client.py
+++ b/tempest/lib/services/volume/v2/transfers_client.py
@@ -12,71 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import transfers_client
-class TransfersClient(rest_client.RestClient):
- """Client class to send CRUD Volume Transfer API requests"""
-
- def create_volume_transfer(self, **kwargs):
- """Create a volume transfer.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-transfer
- """
- post_body = json.dumps({'transfer': kwargs})
- resp, body = self.post('os-volume-transfer', post_body)
- body = json.loads(body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_transfer(self, transfer_id):
- """Returns the details of a volume transfer."""
- url = "os-volume-transfer/%s" % transfer_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_volume_transfers(self, detail=False, **params):
- """List all the volume transfers created.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers
- https://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers-with-details
- """
- url = 'os-volume-transfer'
- if detail:
- url += '/detail'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_transfer(self, transfer_id):
- """Delete a volume transfer."""
- resp, body = self.delete("os-volume-transfer/%s" % transfer_id)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def accept_volume_transfer(self, transfer_id, **kwargs):
- """Accept a volume transfer.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#accept-volume-transfer
- """
- url = 'os-volume-transfer/%s/accept' % transfer_id
- post_body = json.dumps({'accept': kwargs})
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
+TransfersClient = moves.moved_class(
+ transfers_client.TransfersClient, 'TransfersClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/types_client.py b/tempest/lib/services/volume/v2/types_client.py
index ef5e434..8457f91 100644
--- a/tempest/lib/services/volume/v2/types_client.py
+++ b/tempest/lib/services/volume/v2/types_client.py
@@ -12,193 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume.v3 import types_client
-class TypesClient(rest_client.RestClient):
- """Client class to send CRUD Volume API requests"""
-
- def is_resource_deleted(self, id):
- try:
- self.show_volume_type(id)
- except lib_exc.NotFound:
- return True
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'volume-type'
-
- def list_volume_types(self, **params):
- """List all the volume_types created.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2
- """
- url = 'types'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_type(self, volume_type_id):
- """Returns the details of a single volume_type.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2
- """
- url = "types/%s" % volume_type_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_volume_type(self, **kwargs):
- """Create volume type.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2
- """
- post_body = json.dumps({'volume_type': kwargs})
- resp, body = self.post('types', post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_type(self, volume_type_id):
- """Deletes the Specified Volume_type.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type
- """
- resp, body = self.delete("types/%s" % volume_type_id)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_volume_types_extra_specs(self, volume_type_id, **params):
- """List all the volume_types extra specs created.
-
- TODO: Current api-site doesn't contain this API description.
- After fixing the api-site, we need to fix here also for putting
- the link to api-site.
- """
- url = 'types/%s/extra_specs' % volume_type_id
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
- """Returns the details of a single volume_type extra spec."""
- url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
- """Creates a new Volume_type extra spec.
-
- volume_type_id: Id of volume_type.
- extra_specs: A dictionary of values to be used as extra_specs.
- """
- url = "types/%s/extra_specs" % volume_type_id
- post_body = json.dumps({'extra_specs': extra_specs})
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
- """Deletes the Specified Volume_type extra spec."""
- resp, body = self.delete("types/%s/extra_specs/%s" % (
- volume_type_id, extra_spec_name))
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume_type(self, volume_type_id, **kwargs):
- """Updates volume type name, description, and/or is_public.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type
- """
- put_body = json.dumps({'volume_type': kwargs})
- resp, body = self.put('types/%s' % volume_type_id, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name,
- extra_specs):
- """Update a volume_type extra spec.
-
- volume_type_id: Id of volume_type.
- extra_spec_name: Name of the extra spec to be updated.
- extra_spec: A dictionary of with key as extra_spec_name and the
- updated value.
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#update-extra-specs-for-a-volume-type
- """
- url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
- put_body = json.dumps(extra_specs)
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def add_type_access(self, volume_type_id, **kwargs):
- """Adds volume type access for the given project.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#add-private-volume-type-access
- """
- post_body = json.dumps({'addProjectAccess': kwargs})
- url = 'types/%s/action' % volume_type_id
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def remove_type_access(self, volume_type_id, **kwargs):
- """Removes volume type access for the given project.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#remove-private-volume-type-access
- """
- post_body = json.dumps({'removeProjectAccess': kwargs})
- url = 'types/%s/action' % volume_type_id
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def list_type_access(self, volume_type_id):
- """Print access information about the given volume type.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#list-private-volume-type-access-details
- """
- url = 'types/%s/os-volume-type-access' % volume_type_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
+TypesClient = moves.moved_class(
+ types_client.TypesClient, 'TypesClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/volume_manage_client.py b/tempest/lib/services/volume/v2/volume_manage_client.py
index ddae127..0669326 100644
--- a/tempest/lib/services/volume/v2/volume_manage_client.py
+++ b/tempest/lib/services/volume/v2/volume_manage_client.py
@@ -12,24 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
+from tempest.lib.services.volume.v3 import volume_manage_client
-class VolumeManageClient(rest_client.RestClient):
- """Volume manage client."""
-
- def manage_volume(self, **kwargs):
- """Manage existing volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#manage-existing-volume
- """
- post_body = json.dumps({'volume': kwargs})
- resp, body = self.post('os-volume-manage', post_body)
- self.expected_success(202, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
+VolumeManageClient = moves.moved_class(
+ volume_manage_client.VolumeManageClient, 'VolumeManageClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index dd019f6..f5f9e6e 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -12,340 +12,11 @@
# 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 debtcollector import moves
-from oslo_serialization import jsonutils as json
-import six
-from six.moves.urllib import parse as urllib
-
-from tempest.lib.common import rest_client
-from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.volume import base_client
+from tempest.lib.services.volume.v3 import volumes_client
-class VolumesClient(base_client.BaseClient):
- """Client class to send CRUD Volume API requests"""
-
- def _prepare_params(self, params):
- """Prepares params for use in get or _ext_get methods.
-
- If params is a string it will be left as it is, but if it's not it will
- be urlencoded.
- """
- if isinstance(params, six.string_types):
- return params
- return urllib.urlencode(params)
-
- def list_volumes(self, detail=False, params=None):
- """List all the volumes created.
-
- Params can be a string (must be urlencoded) or a dictionary.
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes-with-details
- http://developer.openstack.org/api-ref/block-storage/v2/#list-volumes
- """
- url = 'volumes'
- if detail:
- url += '/detail'
- if params:
- url += '?%s' % self._prepare_params(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume(self, volume_id):
- """Returns the details of a single volume."""
- url = "volumes/%s" % volume_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_volume(self, **kwargs):
- """Creates a new Volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#create-volume
- """
- post_body = json.dumps({'volume': kwargs})
- resp, body = self.post('volumes', post_body)
- body = json.loads(body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume(self, volume_id, **kwargs):
- """Updates the Specified Volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#update-volume
- """
- put_body = json.dumps({'volume': kwargs})
- resp, body = self.put('volumes/%s' % volume_id, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume(self, volume_id, **params):
- """Deletes the Specified Volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#delete-volume
- """
- url = 'volumes/%s' % volume_id
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.delete(url)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def upload_volume(self, volume_id, **kwargs):
- """Uploads a volume in Glance."""
- post_body = json.dumps({'os-volume_upload_image': kwargs})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def attach_volume(self, volume_id, **kwargs):
- """Attaches a volume to a given instance on a given mountpoint.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-to-server
- """
- post_body = json.dumps({'os-attach': kwargs})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def set_bootable_volume(self, volume_id, **kwargs):
- """Set a bootable flag for a volume - true or false.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-bootable-status
- """
- post_body = json.dumps({'os-set_bootable': kwargs})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def detach_volume(self, volume_id):
- """Detaches a volume from an instance."""
- post_body = json.dumps({'os-detach': {}})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def reserve_volume(self, volume_id):
- """Reserves a volume."""
- post_body = json.dumps({'os-reserve': {}})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def unreserve_volume(self, volume_id):
- """Restore a reserved volume ."""
- post_body = json.dumps({'os-unreserve': {}})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def is_resource_deleted(self, id):
- """Check the specified resource is deleted or not.
-
- :param id: A checked resource id
- :raises lib_exc.DeleteErrorException: If the specified resource is on
- the status the delete was failed.
- """
- try:
- volume = self.show_volume(id)
- except lib_exc.NotFound:
- return True
- if volume["volume"]["status"] == "error_deleting":
- raise lib_exc.DeleteErrorException(resource_id=id)
- return False
-
- @property
- def resource_type(self):
- """Returns the primary type of resource this client works with."""
- return 'volume'
-
- def extend_volume(self, volume_id, **kwargs):
- """Extend a volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-size
- """
- post_body = json.dumps({'os-extend': kwargs})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def reset_volume_status(self, volume_id, **kwargs):
- """Reset the Specified Volume's Status.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-statuses
- """
- post_body = json.dumps({'os-reset_status': kwargs})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume_readonly(self, volume_id, **kwargs):
- """Update the Specified Volume readonly."""
- post_body = json.dumps({'os-update_readonly_flag': kwargs})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def force_delete_volume(self, volume_id):
- """Force Delete Volume."""
- post_body = json.dumps({'os-force_delete': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def create_volume_metadata(self, volume_id, metadata):
- """Create metadata for the volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-metadata
- """
- put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % volume_id
- resp, body = self.post(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_metadata(self, volume_id):
- """Get metadata of the volume."""
- url = "volumes/%s/metadata" % volume_id
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume_metadata(self, volume_id, metadata):
- """Update metadata for the volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-metadata
- """
- put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % volume_id
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_metadata_item(self, volume_id, id):
- """Show metadata item for the volume."""
- url = "volumes/%s/metadata/%s" % (volume_id, id)
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume_metadata_item(self, volume_id, id, meta_item):
- """Update metadata item for the volume."""
- put_body = json.dumps({'meta': meta_item})
- url = "volumes/%s/metadata/%s" % (volume_id, id)
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_metadata_item(self, volume_id, id):
- """Delete metadata item for the volume."""
- url = "volumes/%s/metadata/%s" % (volume_id, id)
- resp, body = self.delete(url)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def retype_volume(self, volume_id, **kwargs):
- """Updates volume with new volume type.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#retype-volume
- """
- post_body = json.dumps({'os-retype': kwargs})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def force_detach_volume(self, volume_id, **kwargs):
- """Force detach a volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#force-detach-volume
- """
- post_body = json.dumps({'os-force_detach': kwargs})
- url = 'volumes/%s/action' % volume_id
- resp, body = self.post(url, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def update_volume_image_metadata(self, volume_id, **kwargs):
- """Update image metadata for the volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- http://developer.openstack.org/api-ref/block-storage/v2/#set-image-metadata-for-volume
- """
- post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
- url = "volumes/%s/action" % (volume_id)
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def delete_volume_image_metadata(self, volume_id, key_name):
- """Delete image metadata item for the volume."""
- post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
- url = "volumes/%s/action" % (volume_id)
- resp, body = self.post(url, post_body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def show_volume_image_metadata(self, volume_id):
- """Show image metadata for the volume."""
- post_body = json.dumps({'os-show_image_metadata': {}})
- url = "volumes/%s/action" % volume_id
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return rest_client.ResponseBody(resp, body)
-
- def unmanage_volume(self, volume_id):
- """Unmanage volume.
-
- For a full list of available parameters, please refer to the official
- API reference:
- https://developer.openstack.org/api-ref/block-storage/v2/#unmanage-volume
- """
- post_body = json.dumps({'os-unmanage': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- self.expected_success(202, resp.status)
- return rest_client.ResponseBody(resp, body)
+VolumesClient = moves.moved_class(
+ volumes_client.VolumesClient, 'VolumesClient',
+ __name__, version="Rocky", removal_version='?')
diff --git a/tempest/lib/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
index 2d85553..a1b7de3 100644
--- a/tempest/lib/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -11,19 +11,44 @@
# 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.services.volume.v3.availability_zone_client \
+ import AvailabilityZoneClient
from tempest.lib.services.volume.v3.backups_client import BackupsClient
from tempest.lib.services.volume.v3.base_client import BaseClient
+from tempest.lib.services.volume.v3.capabilities_client import \
+ CapabilitiesClient
+from tempest.lib.services.volume.v3.encryption_types_client import \
+ EncryptionTypesClient
+from tempest.lib.services.volume.v3.extensions_client import ExtensionsClient
from tempest.lib.services.volume.v3.group_snapshots_client import \
GroupSnapshotsClient
from tempest.lib.services.volume.v3.group_types_client import GroupTypesClient
from tempest.lib.services.volume.v3.groups_client import GroupsClient
+from tempest.lib.services.volume.v3.hosts_client import HostsClient
+from tempest.lib.services.volume.v3.limits_client import LimitsClient
from tempest.lib.services.volume.v3.messages_client import MessagesClient
+from tempest.lib.services.volume.v3.qos_client import QosSpecsClient
+from tempest.lib.services.volume.v3.quota_classes_client import \
+ QuotaClassesClient
+from tempest.lib.services.volume.v3.quotas_client import QuotasClient
+from tempest.lib.services.volume.v3.scheduler_stats_client import \
+ SchedulerStatsClient
+from tempest.lib.services.volume.v3.services_client import ServicesClient
+from tempest.lib.services.volume.v3.snapshot_manage_client import \
+ SnapshotManageClient
from tempest.lib.services.volume.v3.snapshots_client import SnapshotsClient
+from tempest.lib.services.volume.v3.transfers_client import TransfersClient
+from tempest.lib.services.volume.v3.types_client import TypesClient
from tempest.lib.services.volume.v3.versions_client import VersionsClient
+from tempest.lib.services.volume.v3.volume_manage_client import \
+ VolumeManageClient
from tempest.lib.services.volume.v3.volumes_client import VolumesClient
-__all__ = ['BackupsClient', 'BaseClient', 'GroupsClient',
- 'GroupSnapshotsClient', 'GroupTypesClient',
- 'MessagesClient', 'SnapshotsClient', 'VersionsClient',
- 'VolumesClient']
+__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'BaseClient',
+ 'CapabilitiesClient', 'EncryptionTypesClient', 'ExtensionsClient',
+ 'GroupSnapshotsClient', 'GroupTypesClient', 'GroupsClient',
+ 'HostsClient', 'LimitsClient', 'MessagesClient', 'QosSpecsClient',
+ 'QuotaClassesClient', 'QuotasClient', 'SchedulerStatsClient',
+ 'ServicesClient', 'SnapshotManageClient', 'SnapshotsClient',
+ 'TransfersClient', 'TypesClient', 'VersionsClient',
+ 'VolumeManageClient', 'VolumesClient']
diff --git a/tempest/lib/services/volume/v3/availability_zone_client.py b/tempest/lib/services/volume/v3/availability_zone_client.py
new file mode 100644
index 0000000..147e4c6
--- /dev/null
+++ b/tempest/lib/services/volume/v3/availability_zone_client.py
@@ -0,0 +1,27 @@
+# Copyright 2014 IBM Corp.
+# 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_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class AvailabilityZoneClient(rest_client.RestClient):
+
+ def list_availability_zones(self):
+ resp, body = self.get('os-availability-zone')
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index e742e39..10538b0 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -14,15 +14,30 @@
# under the License.
from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
-from tempest.lib.services.volume.v2 import backups_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
-class BackupsClient(backups_client.BackupsClient):
+class BackupsClient(base_client.BaseClient):
"""Volume V3 Backups client"""
api_version = "v3"
+ def create_backup(self, **kwargs):
+ """Creates a backup of volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-backup
+ """
+ post_body = json.dumps({'backup': kwargs})
+ resp, body = self.post('backups', post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def update_backup(self, backup_id, **kwargs):
"""Updates the specified volume backup.
@@ -35,3 +50,83 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def restore_backup(self, backup_id, **kwargs):
+ """Restore volume from backup.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#restore-a-backup
+ """
+ post_body = json.dumps({'restore': kwargs})
+ resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_backup(self, backup_id):
+ """Delete a backup of volume."""
+ resp, body = self.delete('backups/%s' % backup_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_backup(self, backup_id):
+ """Returns the details of a single backup."""
+ url = "backups/%s" % backup_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_backups(self, detail=False, **params):
+ """List all the tenant's backups.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-backups-for-project
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-backups-with-detail
+ """
+ url = "backups"
+ if detail:
+ url += "/detail"
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def export_backup(self, backup_id):
+ """Export backup metadata record."""
+ url = "backups/%s/export_record" % backup_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def import_backup(self, **kwargs):
+ """Import backup metadata record."""
+ post_body = json.dumps({'backup-record': kwargs})
+ resp, body = self.post("backups/import_record", post_body)
+ body = json.loads(body)
+ self.expected_success(201, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def reset_backup_status(self, backup_id, status):
+ """Reset the specified backup's status."""
+ post_body = json.dumps({'os-reset_status': {"status": status}})
+ resp, body = self.post('backups/%s/action' % backup_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_backup(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'backup'
diff --git a/tempest/lib/services/volume/v3/capabilities_client.py b/tempest/lib/services/volume/v3/capabilities_client.py
new file mode 100644
index 0000000..7ebcd69
--- /dev/null
+++ b/tempest/lib/services/volume/v3/capabilities_client.py
@@ -0,0 +1,34 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class CapabilitiesClient(rest_client.RestClient):
+
+ def show_backend_capabilities(self, host):
+ """Shows capabilities for a storage back end.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/index.html#show-back-end-capabilities
+ """
+ url = 'capabilities/%s' % host
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/encryption_types_client.py b/tempest/lib/services/volume/v3/encryption_types_client.py
new file mode 100644
index 0000000..7443a87
--- /dev/null
+++ b/tempest/lib/services/volume/v3/encryption_types_client.py
@@ -0,0 +1,90 @@
+# Copyright 2012 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class EncryptionTypesClient(rest_client.RestClient):
+
+ def is_resource_deleted(self, id):
+ try:
+ body = self.show_encryption_type(id)
+ if not body:
+ return True
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'encryption-type'
+
+ def show_encryption_type(self, volume_type_id):
+ """Get the volume encryption type for the specified volume type.
+
+ volume_type_id: Id of volume_type.
+ """
+ url = "/types/%s/encryption" % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_encryption_specs_item(self, volume_type_id, key):
+ """Get the encryption specs item for the specified volume type."""
+ url = "/types/%s/encryption/%s" % (volume_type_id, key)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_encryption_type(self, volume_type_id, **kwargs):
+ """Create encryption type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-an-encryption-type
+ """
+ url = "/types/%s/encryption" % volume_type_id
+ post_body = json.dumps({'encryption': kwargs})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_encryption_type(self, volume_type_id):
+ """Delete the encryption type for the specified volume-type."""
+ resp, body = self.delete(
+ "/types/%s/encryption/provider" % volume_type_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_encryption_type(self, volume_type_id, **kwargs):
+ """Update an encryption type for an existing volume type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-an-encryption-type
+ """
+ url = "/types/%s/encryption/provider" % volume_type_id
+ put_body = json.dumps({'encryption': kwargs})
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/extensions_client.py b/tempest/lib/services/volume/v3/extensions_client.py
new file mode 100644
index 0000000..45b7a56
--- /dev/null
+++ b/tempest/lib/services/volume/v3/extensions_client.py
@@ -0,0 +1,29 @@
+# Copyright 2014 IBM Corp.
+# 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_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class ExtensionsClient(rest_client.RestClient):
+ """Volume extensions client."""
+
+ def list_extensions(self):
+ url = 'extensions'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/hosts_client.py b/tempest/lib/services/volume/v3/hosts_client.py
new file mode 100644
index 0000000..8b65805
--- /dev/null
+++ b/tempest/lib/services/volume/v3/hosts_client.py
@@ -0,0 +1,47 @@
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class HostsClient(rest_client.RestClient):
+ """Client class to send CRUD Volume API requests"""
+
+ def list_hosts(self, **params):
+ """Lists all hosts.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-all-hosts-for-a-project
+ """
+ url = 'os-hosts'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_host(self, host_name):
+ """Show host details."""
+ url = 'os-hosts/%s' % host_name
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/limits_client.py b/tempest/lib/services/volume/v3/limits_client.py
new file mode 100644
index 0000000..9500254
--- /dev/null
+++ b/tempest/lib/services/volume/v3/limits_client.py
@@ -0,0 +1,30 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class LimitsClient(rest_client.RestClient):
+ """Volume limits client."""
+
+ def show_limits(self):
+ """Returns the details of a volume absolute limits."""
+ url = "limits"
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/qos_client.py b/tempest/lib/services/volume/v3/qos_client.py
new file mode 100644
index 0000000..8f4d37f
--- /dev/null
+++ b/tempest/lib/services/volume/v3/qos_client.py
@@ -0,0 +1,131 @@
+# 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_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class QosSpecsClient(rest_client.RestClient):
+ """Volume QoS client.
+
+ Client class to send CRUD QoS API requests
+ """
+
+ def is_resource_deleted(self, qos_id):
+ try:
+ self.show_qos(qos_id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'qos'
+
+ def create_qos(self, **kwargs):
+ """Create a QoS Specification.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-qos-specification
+ """
+ post_body = json.dumps({'qos_specs': kwargs})
+ resp, body = self.post('qos-specs', post_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_qos(self, qos_id, force=False):
+ """Delete the specified QoS specification."""
+ resp, body = self.delete(
+ "qos-specs/%s?force=%s" % (qos_id, force))
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_qos(self):
+ """List all the QoS specifications created."""
+ url = 'qos-specs'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_qos(self, qos_id):
+ """Get the specified QoS specification."""
+ url = "qos-specs/%s" % qos_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_qos_key(self, qos_id, **kwargs):
+ """Set the specified keys/values of QoS specification.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#set-keys-in-a-qos-specification
+ """
+ put_body = json.dumps({"qos_specs": kwargs})
+ resp, body = self.put('qos-specs/%s' % qos_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def unset_qos_key(self, qos_id, keys):
+ """Unset the specified keys of QoS specification.
+
+ :param keys: keys to delete from the QoS specification.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#unset-keys-in-a-qos-specification
+ """
+ put_body = json.dumps({'keys': keys})
+ resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def associate_qos(self, qos_id, vol_type_id):
+ """Associate the specified QoS with specified volume-type."""
+ url = "qos-specs/%s/associate" % qos_id
+ url += "?vol_type_id=%s" % vol_type_id
+ resp, body = self.get(url)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_association_qos(self, qos_id):
+ """Get the association of the specified QoS specification."""
+ url = "qos-specs/%s/associations" % qos_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def disassociate_qos(self, qos_id, vol_type_id):
+ """Disassociate the specified QoS with specified volume-type."""
+ url = "qos-specs/%s/disassociate" % qos_id
+ url += "?vol_type_id=%s" % vol_type_id
+ resp, body = self.get(url)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def disassociate_all_qos(self, qos_id):
+ """Disassociate the specified QoS with all associations."""
+ url = "qos-specs/%s/disassociate_all" % qos_id
+ resp, body = self.get(url)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/quota_classes_client.py b/tempest/lib/services/volume/v3/quota_classes_client.py
new file mode 100644
index 0000000..a8eb536
--- /dev/null
+++ b/tempest/lib/services/volume/v3/quota_classes_client.py
@@ -0,0 +1,49 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class QuotaClassesClient(rest_client.RestClient):
+ """Volume quota class client."""
+
+ def show_quota_class_set(self, quota_class_id):
+ """List quotas for a quota class.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-quota-classes-for-a-project
+ """
+ url = 'os-quota-class-sets/%s' % quota_class_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """Update quotas for a quota class.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-quota-classes-for-a-project
+ """
+ url = 'os-quota-class-sets/%s' % quota_class_id
+ put_body = json.dumps({'quota_class_set': kwargs})
+ resp, body = self.put(url, put_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/quotas_client.py b/tempest/lib/services/volume/v3/quotas_client.py
new file mode 100644
index 0000000..538a915
--- /dev/null
+++ b/tempest/lib/services/volume/v3/quotas_client.py
@@ -0,0 +1,63 @@
+# 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 oslo_serialization import jsonutils
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class QuotasClient(rest_client.RestClient):
+ """Client class to send CRUD Volume Quotas API requests"""
+
+ def show_default_quota_set(self, tenant_id):
+ """List the default volume quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % tenant_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = jsonutils.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_quota_set(self, tenant_id, params=None):
+ """List the quota set for a tenant."""
+
+ url = 'os-quota-sets/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = jsonutils.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_quota_set(self, tenant_id, **kwargs):
+ """Updates quota set
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-quotas-for-a-project
+ """
+ put_body = jsonutils.dumps({'quota_set': kwargs})
+ resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
+ self.expected_success(200, resp.status)
+ body = jsonutils.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ resp, body = self.delete('os-quota-sets/%s' % tenant_id)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/scheduler_stats_client.py b/tempest/lib/services/volume/v3/scheduler_stats_client.py
new file mode 100644
index 0000000..9b80851
--- /dev/null
+++ b/tempest/lib/services/volume/v3/scheduler_stats_client.py
@@ -0,0 +1,36 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class SchedulerStatsClient(rest_client.RestClient):
+
+ def list_pools(self, detail=False):
+ """List all the volumes pools (hosts).
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-all-back-end-storage-pools
+ """
+ url = 'scheduler-stats/get_pools'
+ if detail:
+ url += '?detail=True'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/services_client.py b/tempest/lib/services/volume/v3/services_client.py
new file mode 100644
index 0000000..09036a4
--- /dev/null
+++ b/tempest/lib/services/volume/v3/services_client.py
@@ -0,0 +1,33 @@
+# 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class ServicesClient(rest_client.RestClient):
+ """Client class to send CRUD Volume API requests"""
+
+ def list_services(self, **params):
+ url = 'os-services'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/snapshot_manage_client.py b/tempest/lib/services/volume/v3/snapshot_manage_client.py
new file mode 100644
index 0000000..43fd328
--- /dev/null
+++ b/tempest/lib/services/volume/v3/snapshot_manage_client.py
@@ -0,0 +1,31 @@
+# Copyright 2016 Red Hat, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class SnapshotManageClient(rest_client.RestClient):
+ """Snapshot manage client."""
+
+ def manage_snapshot(self, **kwargs):
+ """Manage a snapshot."""
+ post_body = json.dumps({'snapshot': kwargs})
+ url = 'os-snapshot-manage'
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index 88c094f..298925a 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -13,9 +13,199 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.lib.services.volume.v2 import snapshots_client
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
-class SnapshotsClient(snapshots_client.SnapshotsClient):
+class SnapshotsClient(rest_client.RestClient):
"""Client class to send CRUD Volume Snapshot V3 API requests."""
api_version = "v3"
+ create_resp = 202
+
+ def list_snapshots(self, detail=False, **params):
+ """List all the snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-snapshots
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-snapshots-and-details
+ """
+ url = 'snapshots'
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_snapshot(self, snapshot_id):
+ """Returns the details of a single snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-a-snapshot-s-details
+ """
+ url = "snapshots/%s" % snapshot_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_snapshot(self, **kwargs):
+ """Creates a new snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-snapshot
+ """
+ post_body = json.dumps({'snapshot': kwargs})
+ resp, body = self.post('snapshots', post_body)
+ body = json.loads(body)
+ self.expected_success(self.create_resp, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_snapshot(self, snapshot_id, **kwargs):
+ """Updates a snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-snapshot
+ """
+ put_body = json.dumps({'snapshot': kwargs})
+ resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_snapshot(self, snapshot_id):
+ """Delete Snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#delete-a-snapshot
+ """
+ resp, body = self.delete("snapshots/%s" % snapshot_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_snapshot(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'volume-snapshot'
+
+ def reset_snapshot_status(self, snapshot_id, status):
+ """Reset the specified snapshot's status."""
+ post_body = json.dumps({'os-reset_status': {"status": status}})
+ resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_snapshot_status(self, snapshot_id, **kwargs):
+ """Update the specified snapshot's status."""
+ # TODO(gmann): api-site doesn't contain doc ref
+ # for this API. After fixing the api-site, we need to
+ # add the link here.
+ # Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645
+
+ post_body = json.dumps({'os-update_snapshot_status': kwargs})
+ url = 'snapshots/%s/action' % snapshot_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_snapshot_metadata(self, snapshot_id, metadata):
+ """Create metadata for the snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-snapshot-s-metadata
+ """
+ put_body = json.dumps({'metadata': metadata})
+ url = "snapshots/%s/metadata" % snapshot_id
+ resp, body = self.post(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_snapshot_metadata(self, snapshot_id):
+ """Get metadata of the snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-a-snapshot-s-metadata
+ """
+ url = "snapshots/%s/metadata" % snapshot_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_snapshot_metadata(self, snapshot_id, **kwargs):
+ """Update metadata for the snapshot.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-snapshot-s-metadata
+ """
+ put_body = json.dumps(kwargs)
+ url = "snapshots/%s/metadata" % snapshot_id
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_snapshot_metadata_item(self, snapshot_id, id):
+ """Show metadata item for the snapshot."""
+ url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_snapshot_metadata_item(self, snapshot_id, id, **kwargs):
+ """Update metadata item for the snapshot."""
+ # TODO(piyush): Current api-site doesn't contain this API description.
+ # After fixing the api-site, we need to fix here also for putting the
+ # link to api-site.
+ # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1529064
+ put_body = json.dumps(kwargs)
+ url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_snapshot_metadata_item(self, snapshot_id, id):
+ """Delete metadata item for the snapshot."""
+ url = "snapshots/%s/metadata/%s" % (snapshot_id, id)
+ resp, body = self.delete(url)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def force_delete_snapshot(self, snapshot_id):
+ """Force Delete Snapshot."""
+ post_body = json.dumps({'os-force_delete': {}})
+ resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def unmanage_snapshot(self, snapshot_id):
+ """Unmanage a snapshot."""
+ post_body = json.dumps({'os-unmanage': {}})
+ url = 'snapshots/%s/action' % (snapshot_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/transfers_client.py b/tempest/lib/services/volume/v3/transfers_client.py
new file mode 100644
index 0000000..97c5597
--- /dev/null
+++ b/tempest/lib/services/volume/v3/transfers_client.py
@@ -0,0 +1,82 @@
+# Copyright 2012 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class TransfersClient(rest_client.RestClient):
+ """Client class to send CRUD Volume Transfer API requests"""
+
+ def create_volume_transfer(self, **kwargs):
+ """Create a volume transfer.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume-transfer
+ """
+ post_body = json.dumps({'transfer': kwargs})
+ resp, body = self.post('os-volume-transfer', post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_transfer(self, transfer_id):
+ """Returns the details of a volume transfer."""
+ url = "os-volume-transfer/%s" % transfer_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_volume_transfers(self, detail=False, **params):
+ """List all the volume transfers created.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-for-a-project
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-volume-transfers-and-details
+ """
+ url = 'os-volume-transfer'
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_transfer(self, transfer_id):
+ """Delete a volume transfer."""
+ resp, body = self.delete("os-volume-transfer/%s" % transfer_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def accept_volume_transfer(self, transfer_id, **kwargs):
+ """Accept a volume transfer.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#accept-a-volume-transfer
+ """
+ url = 'os-volume-transfer/%s/accept' % transfer_id
+ post_body = json.dumps({'accept': kwargs})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/types_client.py b/tempest/lib/services/volume/v3/types_client.py
new file mode 100644
index 0000000..6d9d03a
--- /dev/null
+++ b/tempest/lib/services/volume/v3/types_client.py
@@ -0,0 +1,204 @@
+# Copyright 2012 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 oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+from tempest.lib import exceptions as lib_exc
+
+
+class TypesClient(rest_client.RestClient):
+ """Client class to send CRUD Volume API requests"""
+
+ def is_resource_deleted(self, id):
+ try:
+ self.show_volume_type(id)
+ except lib_exc.NotFound:
+ return True
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'volume-type'
+
+ def list_volume_types(self, **params):
+ """List all the volume_types created.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-all-volume-types
+ """
+ url = 'types'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_type(self, volume_type_id):
+ """Returns the details of a single volume_type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#show-volume-type-detail
+ """
+ url = "types/%s" % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume_type(self, **kwargs):
+ """Create volume type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume-type
+ """
+ post_body = json.dumps({'volume_type': kwargs})
+ resp, body = self.post('types', post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_type(self, volume_type_id):
+ """Deletes the Specified Volume_type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#delete-a-volume-type
+ """
+ resp, body = self.delete("types/%s" % volume_type_id)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_volume_types_extra_specs(self, volume_type_id, **params):
+ """List all the volume_types extra specs created.
+
+ TODO: Current api-site doesn't contain this API description.
+ After fixing the api-site, we need to fix here also for putting
+ the link to api-site.
+ """
+ url = 'types/%s/extra_specs' % volume_type_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_type_extra_specs(self, volume_type_id, extra_specs_name):
+ """Returns the details of a single volume_type extra spec."""
+ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_specs_name)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume_type_extra_specs(self, volume_type_id, extra_specs):
+ """Creates a new Volume_type extra spec.
+
+ volume_type_id: Id of volume_type.
+ extra_specs: A dictionary of values to be used as extra_specs.
+ """
+ url = "types/%s/extra_specs" % volume_type_id
+ post_body = json.dumps({'extra_specs': extra_specs})
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_type_extra_specs(self, volume_type_id, extra_spec_name):
+ """Deletes the Specified Volume_type extra spec."""
+ resp, body = self.delete("types/%s/extra_specs/%s" % (
+ volume_type_id, extra_spec_name))
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_type(self, volume_type_id, **kwargs):
+ """Updates volume type name, description, and/or is_public.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-type
+ """
+ put_body = json.dumps({'volume_type': kwargs})
+ resp, body = self.put('types/%s' % volume_type_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_type_extra_specs(self, volume_type_id, extra_spec_name,
+ extra_specs):
+ """Update a volume_type extra spec.
+
+ volume_type_id: Id of volume_type.
+ extra_spec_name: Name of the extra spec to be updated.
+ extra_spec: A dictionary of with key as extra_spec_name and the
+ updated value.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-extra-specification-for-volume-type
+ """
+ url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
+ put_body = json.dumps(extra_specs)
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_type_access(self, volume_type_id, **kwargs):
+ """Adds volume type access for the given project.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#add-private-volume-type-access-to-project
+ """
+ post_body = json.dumps({'addProjectAccess': kwargs})
+ url = 'types/%s/action' % volume_type_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def remove_type_access(self, volume_type_id, **kwargs):
+ """Removes volume type access for the given project.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#remove-private-volume-type-access-from-project
+ """
+ post_body = json.dumps({'removeProjectAccess': kwargs})
+ url = 'types/%s/action' % volume_type_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_type_access(self, volume_type_id):
+ """Print access information about the given volume type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-private-volume-type-access-detail
+ """
+ url = 'types/%s/os-volume-type-access' % volume_type_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/volume_manage_client.py b/tempest/lib/services/volume/v3/volume_manage_client.py
new file mode 100644
index 0000000..349e11d
--- /dev/null
+++ b/tempest/lib/services/volume/v3/volume_manage_client.py
@@ -0,0 +1,35 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class VolumeManageClient(rest_client.RestClient):
+ """Volume manage client."""
+
+ def manage_volume(self, **kwargs):
+ """Manage existing volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#manage-an-existing-volume
+ """
+ post_body = json.dumps({'volume': kwargs})
+ resp, body = self.post('os-volume-manage', post_body)
+ self.expected_success(202, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index 5f4b278..a1185c4 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -14,16 +14,96 @@
# under the License.
from oslo_serialization import jsonutils as json
+import six
from six.moves.urllib import parse as urllib
from tempest.lib.common import rest_client
-from tempest.lib.services.volume.v2 import volumes_client
+from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.volume import base_client
-class VolumesClient(volumes_client.VolumesClient):
+class VolumesClient(base_client.BaseClient):
"""Client class to send CRUD Volume V3 API requests"""
api_version = "v3"
+ def _prepare_params(self, params):
+ """Prepares params for use in get or _ext_get methods.
+
+ If params is a string it will be left as it is, but if it's not it will
+ be urlencoded.
+ """
+ if isinstance(params, six.string_types):
+ return params
+ return urllib.urlencode(params)
+
+ def list_volumes(self, detail=False, params=None):
+ """List all the volumes created.
+
+ Params can be a string (must be urlencoded) or a dictionary.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes-with-details
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#list-accessible-volumes
+ """
+ url = 'volumes'
+ if detail:
+ url += '/detail'
+ if params:
+ url += '?%s' % self._prepare_params(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume(self, volume_id):
+ """Returns the details of a single volume."""
+ url = "volumes/%s" % volume_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume(self, **kwargs):
+ """Creates a new Volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume
+ """
+ post_body = json.dumps({'volume': kwargs})
+ resp, body = self.post('volumes', post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume(self, volume_id, **kwargs):
+ """Updates the Specified Volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume
+ """
+ put_body = json.dumps({'volume': kwargs})
+ resp, body = self.put('volumes/%s' % volume_id, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume(self, volume_id, **params):
+ """Deletes the Specified Volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#delete-a-volume
+ """
+ url = 'volumes/%s' % volume_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.delete(url)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def show_volume_summary(self, **params):
"""Get volumes summary.
@@ -38,3 +118,250 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def upload_volume(self, volume_id, **kwargs):
+ """Uploads a volume in Glance."""
+ post_body = json.dumps({'os-volume_upload_image': kwargs})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def attach_volume(self, volume_id, **kwargs):
+ """Attaches a volume to a given instance on a given mountpoint.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#attach-volume-to-a-server
+ """
+ post_body = json.dumps({'os-attach': kwargs})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def set_bootable_volume(self, volume_id, **kwargs):
+ """Set a bootable flag for a volume - true or false.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-s-bootable-status
+ """
+ post_body = json.dumps({'os-set_bootable': kwargs})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def detach_volume(self, volume_id):
+ """Detaches a volume from an instance."""
+ post_body = json.dumps({'os-detach': {}})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def reserve_volume(self, volume_id):
+ """Reserves a volume."""
+ post_body = json.dumps({'os-reserve': {}})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def unreserve_volume(self, volume_id):
+ """Restore a reserved volume ."""
+ post_body = json.dumps({'os-unreserve': {}})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def is_resource_deleted(self, id):
+ """Check the specified resource is deleted or not.
+
+ :param id: A checked resource id
+ :raises lib_exc.DeleteErrorException: If the specified resource is on
+ the status the delete was failed.
+ """
+ try:
+ volume = self.show_volume(id)
+ except lib_exc.NotFound:
+ return True
+ if volume["volume"]["status"] == "error_deleting":
+ raise lib_exc.DeleteErrorException(resource_id=id)
+ return False
+
+ @property
+ def resource_type(self):
+ """Returns the primary type of resource this client works with."""
+ return 'volume'
+
+ def extend_volume(self, volume_id, **kwargs):
+ """Extend a volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#extend-a-volume-size
+ """
+ post_body = json.dumps({'os-extend': kwargs})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def reset_volume_status(self, volume_id, **kwargs):
+ """Reset the Specified Volume's Status.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#reset-a-volume-s-statuses
+ """
+ post_body = json.dumps({'os-reset_status': kwargs})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_readonly(self, volume_id, **kwargs):
+ """Update the Specified Volume readonly."""
+ post_body = json.dumps({'os-update_readonly_flag': kwargs})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def force_delete_volume(self, volume_id):
+ """Force Delete Volume."""
+ post_body = json.dumps({'os-force_delete': {}})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_volume_metadata(self, volume_id, metadata):
+ """Create metadata for the volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#create-metadata-for-volume
+ """
+ put_body = json.dumps({'metadata': metadata})
+ url = "volumes/%s/metadata" % volume_id
+ resp, body = self.post(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_metadata(self, volume_id):
+ """Get metadata of the volume."""
+ url = "volumes/%s/metadata" % volume_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_metadata(self, volume_id, metadata):
+ """Update metadata for the volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#update-a-volume-s-metadata
+ """
+ put_body = json.dumps({'metadata': metadata})
+ url = "volumes/%s/metadata" % volume_id
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_metadata_item(self, volume_id, id):
+ """Show metadata item for the volume."""
+ url = "volumes/%s/metadata/%s" % (volume_id, id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_metadata_item(self, volume_id, id, meta_item):
+ """Update metadata item for the volume."""
+ put_body = json.dumps({'meta': meta_item})
+ url = "volumes/%s/metadata/%s" % (volume_id, id)
+ resp, body = self.put(url, put_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_metadata_item(self, volume_id, id):
+ """Delete metadata item for the volume."""
+ url = "volumes/%s/metadata/%s" % (volume_id, id)
+ resp, body = self.delete(url)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def retype_volume(self, volume_id, **kwargs):
+ """Updates volume with new volume type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#retype-a-volume
+ """
+ post_body = json.dumps({'os-retype': kwargs})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def force_detach_volume(self, volume_id, **kwargs):
+ """Force detach a volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#force-delete-a-volume
+ """
+ post_body = json.dumps({'os-force_detach': kwargs})
+ url = 'volumes/%s/action' % volume_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_volume_image_metadata(self, volume_id, **kwargs):
+ """Update image metadata for the volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#set-image-metadata-for-a-volume
+ """
+ post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
+ url = "volumes/%s/action" % (volume_id)
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_volume_image_metadata(self, volume_id, key_name):
+ """Delete image metadata item for the volume."""
+ post_body = json.dumps({'os-unset_image_metadata': {'key': key_name}})
+ url = "volumes/%s/action" % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_volume_image_metadata(self, volume_id):
+ """Show image metadata for the volume."""
+ post_body = json.dumps({'os-show_image_metadata': {}})
+ url = "volumes/%s/action" % volume_id
+ resp, body = self.post(url, post_body)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def unmanage_volume(self, volume_id):
+ """Unmanage volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/index.html#unmanage-a-volume
+ """
+ post_body = json.dumps({'os-unmanage': {}})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 1fc57e7..2d024e9 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -236,16 +236,11 @@
# Delete the second server which should also delete the second volume
# created from the volume snapshot.
- # TODO(mriedem): Currently, the compute service fails to delete the
- # volume it created because the volume still has the snapshot
- # associated with it, and the cleanups for the volume snapshot which
- # were added in create_server_snapshot above, don't run until after
- # this is called. So we need to delete the volume snapshot and wait for
- # it to be gone here first before deleting the server, and then we can
- # also assert that the underlying volume is deleted when the server is
- # deleted.
self._delete_server(instance)
+ # Assert that the underlying volume is gone.
+ self.volumes_client.wait_for_resource_deletion(created_volume['id'])
+
@decorators.idempotent_id('cb78919a-e553-4bab-b73b-10cf4d2eb125')
@testtools.skipUnless(CONF.compute_feature_enabled.attach_encrypted_volume,
'Encrypted volume attach is not supported')
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index bc197b5..eb1e2b6 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -72,3 +72,64 @@
mock_show.assert_has_calls([mock.call(volume_id),
mock.call(volume_id)])
mock_sleep.assert_called_once_with(1)
+
+
+class TestInterfaceWaiters(base.TestCase):
+ def setUp(self):
+ super(TestInterfaceWaiters, self).setUp()
+ self.client = mock.MagicMock()
+ self.client.build_timeout = 1
+ self.client.build_interval = 1
+
+ def _port_down(self):
+ return {'interfaceAttachment': {'port_state': 'DOWN'}}
+
+ def _port_active(self):
+ return {'interfaceAttachment': {'port_state': 'ACTIVE'}}
+
+ def test_wait_for_interface_status(self):
+ self.client.show_interface.side_effect = [self._port_down(),
+ self._port_active()]
+ with mock.patch.object(time, 'sleep') as sleep_mock:
+ start_time = int(time.time())
+ waiters.wait_for_interface_status(self.client, 'server_id',
+ 'port_id', 'ACTIVE')
+ end_time = int(time.time())
+ self.assertLess(end_time, (start_time + self.client.build_timeout))
+ sleep_mock.assert_called_once_with(self.client.build_interval)
+
+ def test_wait_for_interface_status_timeout(self):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ self.client.show_interface.return_value = self._port_down()
+ self.assertRaises(lib_exc.TimeoutException,
+ waiters.wait_for_interface_status,
+ self.client, 'server_id', 'port_id', 'ACTIVE')
+
+ def _one_interface(self):
+ return {'interfaceAttachments': [{'port_id': 'port_one'}]}
+
+ def _two_interfaces(self):
+ return {'interfaceAttachments': [{'port_id': 'port_one'},
+ {'port_id': 'port_two'}]}
+
+ def test_wait_for_interface_detach(self):
+ self.client.list_interfaces.side_effect = [self._two_interfaces(),
+ self._one_interface()]
+ with mock.patch.object(time, 'sleep') as sleep_mock:
+ start_time = int(time.time())
+ waiters.wait_for_interface_detach(self.client, 'server_id',
+ 'port_two')
+ end_time = int(time.time())
+ self.assertLess(end_time, (start_time + self.client.build_timeout))
+ sleep_mock.assert_called_once_with(self.client.build_interval)
+
+ def test_wait_for_interface_detach_timeout(self):
+ time_mock = self.patch('time.time')
+ time_mock.side_effect = utils.generate_timeout_series(1)
+
+ self.client.list_interfaces.return_value = self._one_interface()
+ self.assertRaises(lib_exc.TimeoutException,
+ waiters.wait_for_interface_detach,
+ self.client, 'server_id', 'port_one')
diff --git a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py b/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
index 3fe8970..e03a8eb 100644
--- a/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_snapshot_manage_client.py
@@ -18,6 +18,8 @@
from oslo_serialization import jsonutils as json
from tempest.lib.services.volume.v2 import snapshot_manage_client
+from tempest.lib.services.volume.v3 import snapshot_manage_client \
+ as snapshot_manage_clientv3
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -63,7 +65,7 @@
# NOTE: Use sort_keys for json.dumps so that the expected and actual
# payloads are guaranteed to be identical for mock_args assert check.
- with mock.patch.object(snapshot_manage_client.json,
+ with mock.patch.object(snapshot_manage_clientv3.json,
'dumps') as mock_dumps:
mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_transfers_client.py b/tempest/tests/lib/services/volume/v2/test_transfers_client.py
index 84f4992..8e7c6f4 100644
--- a/tempest/tests/lib/services/volume/v2/test_transfers_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_transfers_client.py
@@ -19,6 +19,8 @@
from oslo_serialization import jsonutils as json
from tempest.lib.services.volume.v2 import transfers_client
+from tempest.lib.services.volume.v3 import transfers_client \
+ as transfers_clientv3
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -63,7 +65,7 @@
# NOTE: Use sort_keys for json.dumps so that the expected and actual
# payloads are guaranteed to be identical for mock_args assert check.
- with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
+ with mock.patch.object(transfers_clientv3.json, 'dumps') as mock_dumps:
mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
self.check_service_client_function(
@@ -84,7 +86,7 @@
# NOTE: Use sort_keys for json.dumps so that the expected and actual
# payloads are guaranteed to be identical for mock_args assert check.
- with mock.patch.object(transfers_client.json, 'dumps') as mock_dumps:
+ with mock.patch.object(transfers_clientv3.json, 'dumps') as mock_dumps:
mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)
self.check_service_client_function(
diff --git a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py b/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
index ea4a9f9..0fb66bb 100644
--- a/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
+++ b/tempest/tests/lib/services/volume/v2/test_volume_manage_client.py
@@ -18,6 +18,8 @@
from oslo_serialization import jsonutils as json
from tempest.lib.services.volume.v2 import volume_manage_client
+from tempest.lib.services.volume.v3 import volume_manage_client \
+ as volume_manage_clientv3
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base
@@ -91,7 +93,7 @@
# NOTE: Use sort_keys for json.dumps so that the expected and actual
# payloads are guaranteed to be identical for mock_args assert check.
- with mock.patch.object(volume_manage_client.json,
+ with mock.patch.object(volume_manage_clientv3.json,
'dumps') as mock_dumps:
mock_dumps.side_effect = lambda d: json_dumps(d, sort_keys=True)