Merge "Remove remaining usage of cinder v1 API call from Tempest"
diff --git a/releasenotes/notes/add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml b/releasenotes/notes/add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
new file mode 100644
index 0000000..c95e77c
--- /dev/null
+++ b/releasenotes/notes/add-update-encryption-type-to-encryption-types-client-f3093532a0bcf9a1.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add update encryption type API to the v2 encryption_types_client library.
+ This feature enables the possibility to update an encryption type for an
+ existing volume type.
diff --git a/releasenotes/notes/add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml b/releasenotes/notes/add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
new file mode 100644
index 0000000..3453050
--- /dev/null
+++ b/releasenotes/notes/add-volume-manage-client-as-library-78ab198a1dc1bd41.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ Add the unmanage volume API service method in v2 volumes_client library.
+ Define v2 volume_manage_client client for the volume service as library
+ interfaces, allowing other projects to use this module as stable libraries
+ without maintenance changes.
+
+ * volume_manage_client(v2)
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 73c7614..054877e 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -65,6 +65,20 @@
# Stop and Start a server with an attached volume, ensuring that
# the volume remains attached.
server = self._create_server()
+
+ # NOTE(andreaf) Create one remote client used throughout the test.
+ if CONF.validation.run_validation:
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server),
+ self.image_ssh_user,
+ self.image_ssh_password,
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
+ # NOTE(andreaf) We need to ensure the ssh key has been
+ # injected in the guest before we power cycle
+ linux_client.validate_authentication()
+
volume = self.create_volume()
attachment = self.attach_volume(server, volume,
device=('/dev/%s' % self.device))
@@ -78,14 +92,6 @@
'ACTIVE')
if CONF.validation.run_validation:
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(server),
- self.image_ssh_user,
- self.image_ssh_password,
- self.validation_resources['keypair']['private_key'],
- server=server,
- servers_client=self.servers_client)
-
disks = linux_client.get_disks()
device_name_to_match = '\n' + self.device + ' '
self.assertIn(device_name_to_match, disks)
@@ -103,14 +109,6 @@
'ACTIVE')
if CONF.validation.run_validation:
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(server),
- self.image_ssh_user,
- self.image_ssh_password,
- self.validation_resources['keypair']['private_key'],
- server=server,
- servers_client=self.servers_client)
-
disks = linux_client.get_disks()
self.assertNotIn(device_name_to_match, disks)
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index aa5145d..2088a44 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -71,6 +71,20 @@
# on order.
self.assertEqual(sorted(s1), sorted(s2))
+ @decorators.idempotent_id('67ec6902-f91d-4dec-91fa-338523208bbc')
+ def test_get_service_by_volume_host_name(self):
+ volume_id = self.create_volume()['id']
+ volume = self.admin_volume_client.show_volume(volume_id)['volume']
+ hostname = _get_host(volume['os-vol-host-attr:host'])
+
+ services = (self.admin_volume_services_client.list_services(
+ host=hostname, binary='cinder-volume')['services'])
+
+ self.assertNotEqual(0, len(services),
+ 'cinder-volume not found on host %s' % hostname)
+ self.assertEqual(hostname, _get_host(services[0]['host']))
+ self.assertEqual('cinder-volume', services[0]['binary'])
+
@decorators.idempotent_id('ffa6167c-4497-4944-a464-226bbdb53908')
def test_get_service_by_service_and_host_name(self):
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index fd333eb..ae57540 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -122,43 +122,55 @@
fetched_volume_type['os-volume-type-access:is_public'])
@decorators.idempotent_id('7830abd0-ff99-4793-a265-405684a54d46')
- def test_volume_type_encryption_create_get_delete(self):
- # Create/get/delete encryption type.
- provider = "LuksEncryptor"
- control_location = "front-end"
- body = self.create_volume_type()
+ def test_volume_type_encryption_create_get_update_delete(self):
+ # Create/get/update/delete encryption type.
+ create_kwargs = {'provider': 'LuksEncryptor',
+ 'control_location': 'front-end'}
+ volume_type_id = self.create_volume_type()['id']
+
# Create encryption type
encryption_type = \
self.admin_encryption_types_client.create_encryption_type(
- body['id'], provider=provider,
- control_location=control_location)['encryption']
+ volume_type_id, **create_kwargs)['encryption']
self.assertIn('volume_type_id', encryption_type)
- self.assertEqual(provider, encryption_type['provider'],
- "The created encryption_type provider is not equal "
- "to the requested provider")
- self.assertEqual(control_location, encryption_type['control_location'],
- "The created encryption_type control_location is not "
- "equal to the requested control_location")
+ for key in create_kwargs:
+ self.assertEqual(create_kwargs[key], encryption_type[key],
+ 'The created encryption_type %s is different '
+ 'from the requested encryption_type' % key)
# Get encryption type
+ encrypt_type_id = encryption_type['volume_type_id']
fetched_encryption_type = (
self.admin_encryption_types_client.show_encryption_type(
- encryption_type['volume_type_id']))
- self.assertEqual(provider,
- fetched_encryption_type['provider'],
- 'The fetched encryption_type provider is different '
- 'from the created encryption_type')
- self.assertEqual(control_location,
- fetched_encryption_type['control_location'],
- 'The fetched encryption_type control_location is '
- 'different from the created encryption_type')
+ encrypt_type_id))
+ for key in create_kwargs:
+ self.assertEqual(create_kwargs[key], fetched_encryption_type[key],
+ 'The fetched encryption_type %s is different '
+ 'from the created encryption_type' % key)
+
+ # Update encryption type
+ update_kwargs = {'key_size': 128,
+ 'provider': 'SomeProvider',
+ 'cipher': 'aes-xts-plain64',
+ 'control_location': 'back-end'}
+ self.admin_encryption_types_client.update_encryption_type(
+ encrypt_type_id, **update_kwargs)
+ updated_encryption_type = (
+ self.admin_encryption_types_client.show_encryption_type(
+ encrypt_type_id))
+ for key in update_kwargs:
+ self.assertEqual(update_kwargs[key], updated_encryption_type[key],
+ 'The fetched encryption_type %s is different '
+ 'from the updated encryption_type' % key)
# Delete encryption type
- type_id = encryption_type['volume_type_id']
- self.admin_encryption_types_client.delete_encryption_type(type_id)
- self.admin_encryption_types_client.wait_for_resource_deletion(type_id)
+ self.admin_encryption_types_client.delete_encryption_type(
+ encrypt_type_id)
+ self.admin_encryption_types_client.wait_for_resource_deletion(
+ encrypt_type_id)
deleted_encryption_type = (
- self.admin_encryption_types_client.show_encryption_type(type_id))
+ self.admin_encryption_types_client.show_encryption_type(
+ encrypt_type_id))
self.assertEmpty(deleted_encryption_type)
@decorators.idempotent_id('cf9f07c6-db9e-4462-a243-5933ad65e9c8')
diff --git a/tempest/api/volume/admin/v2/test_volume_manage.py b/tempest/api/volume/admin/v2/test_volume_manage.py
new file mode 100644
index 0000000..f983490
--- /dev/null
+++ b/tempest/api/volume/admin/v2/test_volume_manage.py
@@ -0,0 +1,81 @@
+# 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 tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class VolumeManageAdminV2Test(base.BaseVolumeAdminTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumeManageAdminV2Test, cls).skip_checks()
+
+ if not CONF.volume_feature_enabled.manage_volume:
+ raise cls.skipException("Manage volume tests are disabled")
+
+ if len(CONF.volume.manage_volume_ref) != 2:
+ raise cls.skipException("Manage volume ref is not correctly "
+ "configured")
+
+ @decorators.idempotent_id('70076c71-0ce1-4208-a8ff-36a66e65cc1e')
+ def test_unmanage_manage_volume(self):
+ # Create original volume
+ org_vol_id = self.create_volume()['id']
+ org_vol_info = self.admin_volume_client.show_volume(
+ org_vol_id)['volume']
+
+ # Unmanage the original volume
+ self.admin_volume_client.unmanage_volume(org_vol_id)
+ self.admin_volume_client.wait_for_resource_deletion(org_vol_id)
+
+ # Verify the original volume does not exist in volume list
+ params = {'all_tenants': 1}
+ all_tenants_volumes = self.admin_volume_client.list_volumes(
+ detail=True, params=params)['volumes']
+ self.assertNotIn(org_vol_id, [v['id'] for v in all_tenants_volumes])
+
+ # Manage volume
+ new_vol_name = data_utils.rand_name(
+ self.__class__.__name__ + '-volume')
+ new_vol_ref = {
+ 'name': new_vol_name,
+ 'host': org_vol_info['os-vol-host-attr:host'],
+ 'ref': {CONF.volume.manage_volume_ref[0]:
+ CONF.volume.manage_volume_ref[1] % org_vol_id},
+ 'volume_type': org_vol_info['volume_type'],
+ 'availability_zone': org_vol_info['availability_zone']}
+ new_vol_id = self.admin_volume_manage_client.manage_volume(
+ **new_vol_ref)['volume']['id']
+ self.addCleanup(self.delete_volume,
+ self.admin_volume_client, new_vol_id)
+ waiters.wait_for_volume_resource_status(self.admin_volume_client,
+ new_vol_id, 'available')
+
+ # Compare the managed volume with the original
+ new_vol_info = self.admin_volume_client.show_volume(
+ new_vol_id)['volume']
+ self.assertNotIn(new_vol_id, [org_vol_id])
+ self.assertEqual(new_vol_info['name'], new_vol_name)
+ for key in ['size',
+ 'volume_type',
+ 'availability_zone',
+ 'os-vol-host-attr:host']:
+ self.assertEqual(new_vol_info[key], org_vol_info[key])
diff --git a/tempest/api/volume/admin/v2/test_volume_pools.py b/tempest/api/volume/admin/v2/test_volume_pools.py
index 91d092d..5041095 100644
--- a/tempest/api/volume/admin/v2/test_volume_pools.py
+++ b/tempest/api/volume/admin/v2/test_volume_pools.py
@@ -14,29 +14,26 @@
# under the License.
from tempest.api.volume import base
+from tempest import config
from tempest.lib import decorators
+CONF = config.CONF
+
class VolumePoolsAdminV2TestsJSON(base.BaseVolumeAdminTest):
-
- @classmethod
- def resource_setup(cls):
- super(VolumePoolsAdminV2TestsJSON, cls).resource_setup()
- # Create a test shared volume for tests
- cls.volume = cls.create_volume()
-
- def _assert_host_volume_in_pools(self, with_detail=False):
- volume_info = self.admin_volume_client.show_volume(
- self.volume['id'])['volume']
+ def _assert_pools(self, with_detail=False):
cinder_pools = self.admin_volume_client.show_pools(
detail=with_detail)['pools']
- self.assertIn(volume_info['os-vol-host-attr:host'],
- [pool['name'] for pool in cinder_pools])
+ self.assertIn('name', cinder_pools[0])
+ if with_detail:
+ self.assertIn(CONF.volume.vendor_name,
+ [pool['capabilities']['vendor_name']
+ for pool in cinder_pools])
@decorators.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7')
def test_get_pools_without_details(self):
- self._assert_host_volume_in_pools()
+ self._assert_pools()
@decorators.idempotent_id('d4bb61f7-762d-4437-b8a4-5785759a0ced')
def test_get_pools_with_details(self):
- self._assert_host_volume_in_pools(with_detail=True)
+ self._assert_pools(with_detail=True)
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 5e4fada..2f719c8 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -225,6 +225,7 @@
cls.admin_volume_services_client = \
cls.os_adm.volume_services_v2_client
cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
+ cls.admin_volume_manage_client = cls.os_adm.volume_manage_v2_client
cls.admin_volume_client = cls.os_adm.volumes_v2_client
cls.admin_hosts_client = cls.os_adm.volume_hosts_v2_client
cls.admin_snapshot_manage_client = \
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index a852cea..df98720 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -78,11 +78,7 @@
params=params)['volumes']
# Validating params of fetched volumes
- # In v2, only list detail view includes items in params.
- # In v1, list view and list detail view are same. So the
- # following check should be run when 'with_detail' is True
- # or v1 tests.
- if with_detail or self._api_version == 1:
+ if with_detail:
for volume in fetched_vol_list:
for key in params:
msg = "Failed to list volumes %s by %s" % \
diff --git a/tempest/api/volume/test_volumes_snapshots_list.py b/tempest/api/volume/test_volumes_snapshots_list.py
index 0ea8ec7..a1b514b 100644
--- a/tempest/api/volume/test_volumes_snapshots_list.py
+++ b/tempest/api/volume/test_volumes_snapshots_list.py
@@ -97,7 +97,6 @@
self._list_snapshots_by_param_limit(limit=100000,
expected_elements=len(snap_list))
- @decorators.skip_because(bug='1540893')
@decorators.idempotent_id('e3b44b7f-ae87-45b5-8a8c-66110eb24d0a')
def test_snapshot_list_param_limit_equals_zero(self):
# List returns zero elements
diff --git a/tempest/clients.py b/tempest/clients.py
index 0654110..49a046a 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -262,6 +262,7 @@
self.snapshot_manage_v2_client = self.volume_v2.SnapshotManageClient()
self.snapshots_client = self.volume_v1.SnapshotsClient()
self.snapshots_v2_client = self.volume_v2.SnapshotsClient()
+ self.volume_manage_v2_client = self.volume_v2.VolumeManageClient()
self.volumes_client = self.volume_v1.VolumesClient()
self.volumes_v2_client = self.volume_v2.VolumesClient()
self.volume_v3_messages_client = self.volume_v3.MessagesClient()
diff --git a/tempest/config.py b/tempest/config.py
index a8d0355..c7669b7 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -291,7 +291,9 @@
cfg.StrOpt('volume_device_name',
default='vdb',
help="Expected device name when a volume is attached to "
- "an instance"),
+ "an instance. Not all hypervisors guarantee that they "
+ "will respect the user defined device name, tests may "
+ "fail if inappropriate device name is set."),
cfg.IntOpt('shelved_offload_time',
default=0,
help='Time in seconds before a shelved instance is eligible '
@@ -773,6 +775,12 @@
cfg.IntOpt('volume_size',
default=1,
help='Default size in GB for volumes created by volumes tests'),
+ cfg.ListOpt('manage_volume_ref',
+ default=['source-name', 'volume-%s'],
+ help="A reference to existing volume for volume manage. "
+ "It contains two elements, the first is ref type "
+ "(like 'source-name', 'source-id', etc), the second is "
+ "volume name template used in storage backend"),
cfg.StrOpt('min_microversion',
default=None,
help="Lower version of the test target microversion range. "
@@ -812,6 +820,9 @@
cfg.BoolOpt('manage_snapshot',
default=False,
help='Runs Cinder manage snapshot tests'),
+ cfg.BoolOpt('manage_volume',
+ default=False,
+ help='Runs Cinder manage volume tests'),
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled volume extensions with a special '
diff --git a/tempest/lib/services/volume/v2/__init__.py b/tempest/lib/services/volume/v2/__init__.py
index 8acad0f..b4eb771 100644
--- a/tempest/lib/services/volume/v2/__init__.py
+++ b/tempest/lib/services/volume/v2/__init__.py
@@ -31,10 +31,12 @@
SnapshotManageClient
from tempest.lib.services.volume.v2.snapshots_client import SnapshotsClient
from tempest.lib.services.volume.v2.types_client import TypesClient
+from tempest.lib.services.volume.v2.volume_manage_client import \
+ VolumeManageClient
from tempest.lib.services.volume.v2.volumes_client import VolumesClient
__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient',
'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient',
'LimitsClient', 'CapabilitiesClient', 'SchedulerStatsClient',
- 'SnapshotManageClient']
+ 'SnapshotManageClient', 'VolumeManageClient']
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index 8b01f11..eeff537 100755
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -67,3 +67,17 @@
"/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.
+
+ 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/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/v2/volume_manage_client.py b/tempest/lib/services/volume/v2/volume_manage_client.py
new file mode 100644
index 0000000..12f4240
--- /dev/null
+++ b/tempest/lib/services/volume/v2/volume_manage_client.py
@@ -0,0 +1,37 @@
+# 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 V2 client."""
+
+ api_version = "v2"
+
+ 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)
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index 8b8e249..2c109ea 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -351,3 +351,15 @@
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)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index c69010d..c1270c7 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -878,16 +878,16 @@
show_floatingip(floatingip_id)['floatingip'])
return status == result['status']
- test_utils.call_until_true(refresh,
- CONF.network.build_timeout,
- CONF.network.build_interval)
- floating_ip = self.floating_ips_client.show_floatingip(
- floatingip_id)['floatingip']
- self.assertEqual(status, floating_ip['status'],
- message="FloatingIP: {fp} is at status: {cst}. "
- "failed to reach status: {st}"
- .format(fp=floating_ip, cst=floating_ip['status'],
- st=status))
+ if not test_utils.call_until_true(refresh,
+ CONF.network.build_timeout,
+ CONF.network.build_interval):
+ floating_ip = self.floating_ips_client.show_floatingip(
+ floatingip_id)['floatingip']
+ self.assertEqual(status, floating_ip['status'],
+ message="FloatingIP: {fp} is at status: {cst}. "
+ "failed to reach status: {st}"
+ .format(fp=floating_ip, cst=floating_ip['status'],
+ st=status))
LOG.info("FloatingIP: {fp} is at status: {st}"
.format(fp=floating_ip, st=status))