Add tempest tests for Consistency Groups

These tempest tests add coverage for all the API's related to
consistency groups in Cinder. They were originally proposed
for upstream tempest in https://review.openstack.org/#/c/252213/
but were not a good fit since they aren't supported by the
reference driver. They have been modified to work as part
of the in-tree tempest plugins for Cinder now.

The tests are behind a new config option for tempest, which
in turn is part of a new config group called 'cinder'. This
was added to avoid any collisions with the 'volume-features-enabled'
or 'volume' groups already in the upstream tempest tests.

To enable them set the following in tempest.conf

[cinder]
consistency_group = True

Then make sure to run tempest with the 'all-plugin' tox environment.

Don't forget to update policy.json to allow for CG API's to be called..

Change-Id: I772ea13ca156e71620d722eee476f222a8653831
Co-Authored-By: Xing Yang <xing.yang@emc.com>
This commit is contained in:
Patrick East 2016-08-12 17:23:19 -07:00
parent 91cdf7a4aa
commit 9b434d1117
5 changed files with 534 additions and 1 deletions

View File

@ -0,0 +1,283 @@
# Copyright (C) 2015 EMC Corporation.
# Copyright (C) 2016 Pure Storage, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
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 import test
from cinder.tests.tempest import cinder_clients
CONF = config.CONF
LOG = logging.getLogger(__name__)
class ConsistencyGroupsV2Test(base.BaseVolumeAdminTest):
@classmethod
def setup_clients(cls):
cls._api_version = 2
super(ConsistencyGroupsV2Test, cls).setup_clients()
manager = cinder_clients.Manager(cls.os_adm)
cls.consistencygroups_adm_client = manager.consistencygroups_adm_client
@classmethod
def skip_checks(cls):
super(ConsistencyGroupsV2Test, cls).skip_checks()
if not CONF.cinder.consistency_group:
raise cls.skipException("Cinder consistency group "
"feature disabled")
def _delete_consistencygroup(self, cg_id):
self.consistencygroups_adm_client.delete_consistencygroup(cg_id)
vols = self.admin_volume_client.list_volumes(detail=True)['volumes']
for vol in vols:
if vol['consistencygroup_id'] == cg_id:
self.admin_volume_client.wait_for_resource_deletion(vol['id'])
self.consistencygroups_adm_client.wait_for_consistencygroup_deletion(
cg_id)
def _delete_cgsnapshot(self, cgsnapshot_id, cg_id):
self.consistencygroups_adm_client.delete_cgsnapshot(cgsnapshot_id)
vols = self.admin_volume_client.list_volumes(detail=True)['volumes']
snapshots = self.admin_snapshots_client.list_snapshots(
detail=True)['snapshots']
for vol in vols:
for snap in snapshots:
if (vol['consistencygroup_id'] == cg_id and
vol['id'] == snap['volume_id']):
self.snapshots_client.wait_for_resource_deletion(
snap['id'])
self.consistencygroups_adm_client.wait_for_cgsnapshot_deletion(
cgsnapshot_id)
@test.idempotent_id('3fe776ba-ec1f-4e6c-8d78-4b14c3a7fc44')
def test_consistencygroup_create_delete(self):
# Create volume type
name = data_utils.rand_name("volume-type")
volume_type = self.admin_volume_types_client.create_volume_type(
name=name)['volume_type']
# Create CG
cg_name = data_utils.rand_name('CG')
create_consistencygroup = (
self.consistencygroups_adm_client.create_consistencygroup)
cg = create_consistencygroup(volume_type['id'],
name=cg_name)['consistencygroup']
vol_name = data_utils.rand_name("volume")
self.name_field = self.special_fields['name_field']
params = {self.name_field: vol_name,
'volume_type': volume_type['id'],
'consistencygroup_id': cg['id']}
# Create volume
volume = self.admin_volume_client.create_volume(**params)['volume']
waiters.wait_for_volume_status(self.admin_volume_client,
volume['id'], 'available')
self.consistencygroups_adm_client.wait_for_consistencygroup_status(
cg['id'], 'available')
self.assertEqual(cg_name, cg['name'])
# Get a given CG
cg = self.consistencygroups_adm_client.show_consistencygroup(
cg['id'])['consistencygroup']
self.assertEqual(cg_name, cg['name'])
# Get all CGs with detail
cgs = self.consistencygroups_adm_client.list_consistencygroups(
detail=True)['consistencygroups']
self.assertIn((cg['name'], cg['id']),
[(m['name'], m['id']) for m in cgs])
# Clean up
self._delete_consistencygroup(cg['id'])
self.admin_volume_types_client.delete_volume_type(volume_type['id'])
@test.idempotent_id('2134dd52-f333-4456-bb05-6cb0f009a44f')
def test_consistencygroup_cgsnapshot_create_delete(self):
# Create volume type
name = data_utils.rand_name("volume-type")
volume_type = self.admin_volume_types_client.create_volume_type(
name=name)['volume_type']
# Create CG
cg_name = data_utils.rand_name('CG')
create_consistencygroup = (
self.consistencygroups_adm_client.create_consistencygroup)
cg = create_consistencygroup(volume_type['id'],
name=cg_name)['consistencygroup']
vol_name = data_utils.rand_name("volume")
self.name_field = self.special_fields['name_field']
params = {self.name_field: vol_name,
'volume_type': volume_type['id'],
'consistencygroup_id': cg['id']}
# Create volume
volume = self.admin_volume_client.create_volume(**params)['volume']
waiters.wait_for_volume_status(self.admin_volume_client,
volume['id'], 'available')
self.consistencygroups_adm_client.wait_for_consistencygroup_status(
cg['id'], 'available')
self.assertEqual(cg_name, cg['name'])
# Create cgsnapshot
cgsnapshot_name = data_utils.rand_name('cgsnapshot')
create_cgsnapshot = (
self.consistencygroups_adm_client.create_cgsnapshot)
cgsnapshot = create_cgsnapshot(cg['id'],
name=cgsnapshot_name)['cgsnapshot']
snapshots = self.admin_snapshots_client.list_snapshots(
detail=True)['snapshots']
for snap in snapshots:
if volume['id'] == snap['volume_id']:
waiters.wait_for_snapshot_status(self.admin_snapshots_client,
snap['id'], 'available')
self.consistencygroups_adm_client.wait_for_cgsnapshot_status(
cgsnapshot['id'], 'available')
self.assertEqual(cgsnapshot_name, cgsnapshot['name'])
# Get a given CG snapshot
cgsnapshot = self.consistencygroups_adm_client.show_cgsnapshot(
cgsnapshot['id'])['cgsnapshot']
self.assertEqual(cgsnapshot_name, cgsnapshot['name'])
# Get all CG snapshots with detail
cgsnapshots = self.consistencygroups_adm_client.list_cgsnapshots(
detail=True)['cgsnapshots']
self.assertIn((cgsnapshot['name'], cgsnapshot['id']),
[(m['name'], m['id']) for m in cgsnapshots])
# Clean up
self._delete_cgsnapshot(cgsnapshot['id'], cg['id'])
self._delete_consistencygroup(cg['id'])
self.admin_volume_types_client.delete_volume_type(volume_type['id'])
@test.idempotent_id('3a6a5525-25ca-4a6c-aac4-cac6fa8f5b43')
def test_create_consistencygroup_from_cgsnapshot(self):
# Create volume type
name = data_utils.rand_name("volume-type")
volume_type = self.admin_volume_types_client.create_volume_type(
name=name)['volume_type']
# Create CG
cg_name = data_utils.rand_name('CG')
create_consistencygroup = (
self.consistencygroups_adm_client.create_consistencygroup)
cg = create_consistencygroup(volume_type['id'],
name=cg_name)['consistencygroup']
vol_name = data_utils.rand_name("volume")
self.name_field = self.special_fields['name_field']
params = {self.name_field: vol_name,
'volume_type': volume_type['id'],
'consistencygroup_id': cg['id']}
# Create volume
volume = self.admin_volume_client.create_volume(**params)['volume']
waiters.wait_for_volume_status(self.admin_volume_client,
volume['id'], 'available')
self.consistencygroups_adm_client.wait_for_consistencygroup_status(
cg['id'], 'available')
self.assertEqual(cg_name, cg['name'])
# Create cgsnapshot
cgsnapshot_name = data_utils.rand_name('cgsnapshot')
create_cgsnapshot = (
self.consistencygroups_adm_client.create_cgsnapshot)
cgsnapshot = create_cgsnapshot(cg['id'],
name=cgsnapshot_name)['cgsnapshot']
snapshots = self.snapshots_client.list_snapshots(
detail=True)['snapshots']
for snap in snapshots:
if volume['id'] == snap['volume_id']:
waiters.wait_for_snapshot_status(self.admin_snapshots_client,
snap['id'], 'available')
self.consistencygroups_adm_client.wait_for_cgsnapshot_status(
cgsnapshot['id'], 'available')
self.assertEqual(cgsnapshot_name, cgsnapshot['name'])
# Create CG from CG snapshot
cg_name2 = data_utils.rand_name('CG_from_snap')
create_consistencygroup2 = (
self.consistencygroups_adm_client.create_consistencygroup_from_src)
cg2 = create_consistencygroup2(cgsnapshot_id=cgsnapshot['id'],
name=cg_name2)['consistencygroup']
vols = self.admin_volume_client.list_volumes(
detail=True)['volumes']
for vol in vols:
if vol['consistencygroup_id'] == cg2['id']:
waiters.wait_for_volume_status(self.admin_volume_client,
vol['id'], 'available')
self.consistencygroups_adm_client.wait_for_consistencygroup_status(
cg2['id'], 'available')
self.assertEqual(cg_name2, cg2['name'])
# Clean up
self._delete_consistencygroup(cg2['id'])
self._delete_cgsnapshot(cgsnapshot['id'], cg['id'])
self._delete_consistencygroup(cg['id'])
self.admin_volume_types_client.delete_volume_type(volume_type['id'])
@test.idempotent_id('556121ae-de9c-4342-9897-e54260447a19')
def test_create_consistencygroup_from_consistencygroup(self):
# Create volume type
name = data_utils.rand_name("volume-type")
volume_type = self.admin_volume_types_client.create_volume_type(
name=name)['volume_type']
# Create CG
cg_name = data_utils.rand_name('CG')
create_consistencygroup = (
self.consistencygroups_adm_client.create_consistencygroup)
cg = create_consistencygroup(volume_type['id'],
name=cg_name)['consistencygroup']
vol_name = data_utils.rand_name("volume")
self.name_field = self.special_fields['name_field']
params = {self.name_field: vol_name,
'volume_type': volume_type['id'],
'consistencygroup_id': cg['id']}
# Create volume
volume = self.admin_volume_client.create_volume(**params)['volume']
waiters.wait_for_volume_status(self.admin_volume_client,
volume['id'], 'available')
self.consistencygroups_adm_client.wait_for_consistencygroup_status(
cg['id'], 'available')
self.assertEqual(cg_name, cg['name'])
# Create CG from CG
cg_name2 = data_utils.rand_name('CG_from_cg')
create_consistencygroup2 = (
self.consistencygroups_adm_client.create_consistencygroup_from_src)
cg2 = create_consistencygroup2(source_cgid=cg['id'],
name=cg_name2)['consistencygroup']
vols = self.admin_volume_client.list_volumes(
detail=True)['volumes']
for vol in vols:
if vol['consistencygroup_id'] == cg2['id']:
waiters.wait_for_volume_status(self.admin_volume_client,
vol['id'], 'available')
self.consistencygroups_adm_client.wait_for_consistencygroup_status(
cg2['id'], 'available')
self.assertEqual(cg_name2, cg2['name'])
# Clean up
self._delete_consistencygroup(cg2['id'])
self._delete_consistencygroup(cg['id'])
self.admin_volume_types_client.delete_volume_type(volume_type['id'])

View File

@ -0,0 +1,37 @@
# Copyright (c) 2016 Pure Storage, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tempest import config
from cinder.tests.tempest.services import consistencygroups_client
CONF = config.CONF
class Manager(object):
def __init__(self, base_manager):
params = {
'service': CONF.volume.catalog_type,
'region': CONF.volume.region or CONF.identity.region,
'endpoint_type': CONF.volume.endpoint_type,
'build_interval': CONF.volume.build_interval,
'build_timeout': CONF.volume.build_timeout
}
params.update(base_manager.default_params)
auth_provider = base_manager.auth_provider
self.consistencygroups_adm_client = (
consistencygroups_client.ConsistencyGroupsClient(auth_provider,
**params))

View File

@ -24,3 +24,14 @@ ServiceAvailableGroup = [
default=True,
help="Whether or not cinder is expected to be available"),
]
# Use a new config group specific to the cinder in-tree tests to avoid
# any naming confusion with the upstream tempest config options.
cinder_group = cfg.OptGroup(name='cinder',
title='Cinder Tempest Config Options')
CinderGroup = [
cfg.BoolOpt('consistency_group',
default=False,
help='Enable to run Cinder volume consistency group tests'),
]

View File

@ -17,6 +17,7 @@ import cinder
import os
from cinder.tests.tempest import config as project_config
from tempest import config
from tempest.test_discover import plugins
@ -33,6 +34,15 @@ class CinderTempestPlugin(plugins.TempestPlugin):
config.register_opt_group(
conf, project_config.service_available_group,
project_config.ServiceAvailableGroup)
config.register_opt_group(
conf, project_config.cinder_group,
project_config.CinderGroup
)
def get_opt_lists(self):
pass
return [
(project_config.service_available_group.name,
project_config.ServiceAvailableGroup),
(project_config.cinder_group.name,
project_config.CinderGroup),
]

View File

@ -0,0 +1,192 @@
# Copyright (C) 2015 EMC Corporation.
# Copyright (C) 2016 Pure Storage, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
from oslo_serialization import jsonutils as json
from tempest import exceptions
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
class ConsistencyGroupsClient(rest_client.RestClient):
"""Client class to send CRUD Volume ConsistencyGroup API requests"""
def __init__(self, auth_provider, service, region, **kwargs):
super(ConsistencyGroupsClient, self).__init__(
auth_provider, service, region, **kwargs)
def create_consistencygroup(self, volume_types, **kwargs):
"""Creates a consistency group."""
post_body = {'volume_types': volume_types}
if kwargs.get('availability_zone'):
post_body['availability_zone'] = kwargs.get('availability_zone')
if kwargs.get('name'):
post_body['name'] = kwargs.get('name')
if kwargs.get('description'):
post_body['description'] = kwargs.get('description')
post_body = json.dumps({'consistencygroup': post_body})
resp, body = self.post('consistencygroups', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def create_consistencygroup_from_src(self, **kwargs):
"""Creates a consistency group from source."""
post_body = {}
if kwargs.get('cgsnapshot_id'):
post_body['cgsnapshot_id'] = kwargs.get('cgsnapshot_id')
if kwargs.get('source_cgid'):
post_body['source_cgid'] = kwargs.get('source_cgid')
if kwargs.get('name'):
post_body['name'] = kwargs.get('name')
if kwargs.get('description'):
post_body['description'] = kwargs.get('description')
post_body = json.dumps({'consistencygroup-from-src': post_body})
resp, body = self.post('consistencygroups/create_from_src', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def delete_consistencygroup(self, cg_id):
"""Delete a consistency group."""
post_body = {'force': True}
post_body = json.dumps({'consistencygroup': post_body})
resp, body = self.post('consistencygroups/%s/delete' % cg_id,
post_body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def show_consistencygroup(self, cg_id):
"""Returns the details of a single consistency group."""
url = "consistencygroups/%s" % str(cg_id)
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
def list_consistencygroups(self, detail=False):
"""Information for all the tenant's consistency groups."""
url = "consistencygroups"
if detail:
url += "/detail"
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
def create_cgsnapshot(self, consistencygroup_id, **kwargs):
"""Creates a consistency group snapshot."""
post_body = {'consistencygroup_id': consistencygroup_id}
if kwargs.get('name'):
post_body['name'] = kwargs.get('name')
if kwargs.get('description'):
post_body['description'] = kwargs.get('description')
post_body = json.dumps({'cgsnapshot': post_body})
resp, body = self.post('cgsnapshots', post_body)
body = json.loads(body)
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def delete_cgsnapshot(self, cgsnapshot_id):
"""Delete a consistency group snapshot."""
resp, body = self.delete('cgsnapshots/%s' % (str(cgsnapshot_id)))
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
def show_cgsnapshot(self, cgsnapshot_id):
"""Returns the details of a single consistency group snapshot."""
url = "cgsnapshots/%s" % str(cgsnapshot_id)
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
def list_cgsnapshots(self, detail=False):
"""Information for all the tenant's consistency group snapshotss."""
url = "cgsnapshots"
if detail:
url += "/detail"
resp, body = self.get(url)
body = json.loads(body)
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
def wait_for_consistencygroup_status(self, cg_id, status):
"""Waits for a consistency group to reach a given status."""
body = self.show_consistencygroup(cg_id)['consistencygroup']
cg_status = body['status']
start = int(time.time())
while cg_status != status:
time.sleep(self.build_interval)
body = self.show_consistencygroup(cg_id)['consistencygroup']
cg_status = body['status']
if cg_status == 'error':
raise exceptions.ConsistencyGroupException(cg_id=cg_id)
if int(time.time()) - start >= self.build_timeout:
message = ('Consistency group %s failed to reach %s status '
'(current %s) within the required time (%s s).' %
(cg_id, status, cg_status,
self.build_timeout))
raise exceptions.TimeoutException(message)
def wait_for_consistencygroup_deletion(self, cg_id):
"""Waits for consistency group deletion"""
start_time = int(time.time())
while True:
try:
self.show_consistencygroup(cg_id)
except lib_exc.NotFound:
return
if int(time.time()) - start_time >= self.build_timeout:
raise exceptions.TimeoutException
time.sleep(self.build_interval)
def wait_for_cgsnapshot_status(self, cgsnapshot_id, status):
"""Waits for a consistency group snapshot to reach a given status."""
body = self.show_cgsnapshot(cgsnapshot_id)['cgsnapshot']
cgsnapshot_status = body['status']
start = int(time.time())
while cgsnapshot_status != status:
time.sleep(self.build_interval)
body = self.show_cgsnapshot(cgsnapshot_id)['cgsnapshot']
cgsnapshot_status = body['status']
if cgsnapshot_status == 'error':
raise exceptions.ConsistencyGroupSnapshotException(
cgsnapshot_id=cgsnapshot_id)
if int(time.time()) - start >= self.build_timeout:
message = ('Consistency group snapshot %s failed to reach '
'%s status (current %s) within the required time '
'(%s s).' %
(cgsnapshot_id, status, cgsnapshot_status,
self.build_timeout))
raise exceptions.TimeoutException(message)
def wait_for_cgsnapshot_deletion(self, cgsnapshot_id):
"""Waits for consistency group snapshot deletion"""
start_time = int(time.time())
while True:
try:
self.show_cgsnapshot(cgsnapshot_id)
except lib_exc.NotFound:
return
if int(time.time()) - start_time >= self.build_timeout:
raise exceptions.TimeoutException
time.sleep(self.build_interval)