Merge "Add functional tests for groups"

This commit is contained in:
Jenkins 2016-09-21 19:00:43 +00:00 committed by Gerrit Code Review
commit 4e2e29d1e5
7 changed files with 521 additions and 11 deletions

View File

@ -74,6 +74,9 @@ class FakeLoggingVolumeDriver(lvm.LVMVolumeDriver):
def create_cloned_volume(self, volume, src_vol): def create_cloned_volume(self, volume, src_vol):
pass pass
def create_volume_from_snapshot(self, volume, snapshot):
pass
def initialize_connection(self, volume, connector): def initialize_connection(self, volume, connector):
# NOTE(thangp): There are several places in the core cinder code where # NOTE(thangp): There are several places in the core cinder code where
# the volume passed through is a dict and not an oslo_versionedobject. # the volume passed through is a dict and not an oslo_versionedobject.

View File

@ -63,7 +63,7 @@ class TestOpenStackClient(object):
""" """
def __init__(self, auth_user, auth_key, auth_uri): def __init__(self, auth_user, auth_key, auth_uri, api_version=None):
super(TestOpenStackClient, self).__init__() super(TestOpenStackClient, self).__init__()
self.auth_result = None self.auth_result = None
self.auth_user = auth_user self.auth_user = auth_user
@ -71,6 +71,7 @@ class TestOpenStackClient(object):
self.auth_uri = auth_uri self.auth_uri = auth_uri
# default project_id # default project_id
self.project_id = fake.PROJECT_ID self.project_id = fake.PROJECT_ID
self.api_version = api_version
def request(self, url, method='GET', body=None, headers=None, def request(self, url, method='GET', body=None, headers=None,
ssl_verify=True, stream=False): ssl_verify=True, stream=False):
@ -133,6 +134,9 @@ class TestOpenStackClient(object):
headers = kwargs.setdefault('headers', {}) headers = kwargs.setdefault('headers', {})
headers['X-Auth-Token'] = auth_result['x-auth-token'] headers['X-Auth-Token'] = auth_result['x-auth-token']
if self.api_version:
headers['OpenStack-API-Version'] = 'volume ' + self.api_version
response = self.request(full_uri, **kwargs) response = self.request(full_uri, **kwargs)
http_status = response.status_code http_status = response.status_code
@ -219,3 +223,50 @@ class TestOpenStackClient(object):
type['extra_specs'] = extra_specs type['extra_specs'] = extra_specs
return self.api_post('/types', type)['volume_type'] return self.api_post('/types', type)['volume_type']
def delete_type(self, type_id):
return self.api_delete('/types/%s' % type_id)
def create_group_type(self, type_name, grp_specs=None):
grp_type = {"group_type": {"name": type_name}}
if grp_specs:
grp_type['group_specs'] = grp_specs
return self.api_post('/group_types', grp_type)['group_type']
def delete_group_type(self, group_type_id):
return self.api_delete('/group_types/%s' % group_type_id)
def get_group(self, group_id):
return self.api_get('/groups/%s' % group_id)['group']
def get_groups(self, detail=True):
rel_url = '/groups/detail' if detail else '/groups'
return self.api_get(rel_url)['groups']
def post_group(self, group):
return self.api_post('/groups', group)['group']
def post_group_from_src(self, group):
return self.api_post('/groups/action', group)['group']
def delete_group(self, group_id, params):
return self.api_post('/groups/%s/action' % group_id, params)
def put_group(self, group_id, group):
return self.api_put('/groups/%s' % group_id, group)['group']
def get_group_snapshot(self, group_snapshot_id):
return self.api_get('/group_snapshots/%s' % group_snapshot_id)[
'group_snapshot']
def get_group_snapshots(self, detail=True):
rel_url = '/group_snapshots/detail' if detail else '/group_snapshots'
return self.api_get(rel_url)['group_snapshots']
def post_group_snapshot(self, group_snapshot):
return self.api_post('/group_snapshots', group_snapshot)[
'group_snapshot']
def delete_group_snapshot(self, group_snapshot_id):
return self.api_delete('/group_snapshots/%s' % group_snapshot_id)

View File

@ -59,6 +59,9 @@ def generate_new_element(items, prefix, numeric=False):
class _FunctionalTestBase(test.TestCase): class _FunctionalTestBase(test.TestCase):
osapi_version_major = '2'
osapi_version_minor = '0'
def setUp(self): def setUp(self):
super(_FunctionalTestBase, self).setUp() super(_FunctionalTestBase, self).setUp()
@ -78,8 +81,10 @@ class _FunctionalTestBase(test.TestCase):
self._start_api_service() self._start_api_service()
self.addCleanup(self.osapi.stop) self.addCleanup(self.osapi.stop)
api_version = self.osapi_version_major + '.' + self.osapi_version_minor
self.api = client.TestOpenStackClient(fake.USER_ID, self.api = client.TestOpenStackClient(fake.USER_ID,
fake.PROJECT_ID, self.auth_url) fake.PROJECT_ID, self.auth_url,
api_version)
def _update_project(self, new_project_id): def _update_project(self, new_project_id):
self.api.update_project(new_project_id) self.api.update_project(new_project_id)
@ -93,7 +98,8 @@ class _FunctionalTestBase(test.TestCase):
self.osapi.start() self.osapi.start()
# FIXME(ja): this is not the auth url - this is the service url # FIXME(ja): this is not the auth url - this is the service url
# FIXME(ja): this needs fixed in nova as well # FIXME(ja): this needs fixed in nova as well
self.auth_url = 'http://%s:%s/v2' % (self.osapi.host, self.osapi.port) self.auth_url = 'http://%s:%s/v' % (self.osapi.host, self.osapi.port)
self.auth_url += self.osapi_version_major
def _get_flags(self): def _get_flags(self):
"""An opportunity to setup flags, before the services are started.""" """An opportunity to setup flags, before the services are started."""
@ -167,3 +173,63 @@ class _FunctionalTestBase(test.TestCase):
time.sleep(1) time.sleep(1)
retries += 1 retries += 1
def _poll_group_while(self, group_id, continue_states,
expected_end_status=None, max_retries=30):
"""Poll (briefly) while the state is in continue_states.
Continues until the state changes from continue_states or max_retries
are hit. If expected_end_status is specified, we assert that the end
status of the group is expected_end_status.
"""
retries = 0
while retries <= max_retries:
try:
found_grp = self.api.get_group(group_id)
except client.OpenStackApiException404:
return None
except client.OpenStackApiException:
# NOTE(xyang): Got OpenStackApiException(
# u'Unexpected status code',) sometimes, but
# it works if continue.
continue
self.assertEqual(group_id, found_grp['id'])
grp_status = found_grp['status']
if grp_status not in continue_states:
if expected_end_status:
self.assertEqual(expected_end_status, grp_status)
return found_grp
time.sleep(1)
retries += 1
def _poll_group_snapshot_while(self, group_snapshot_id, continue_states,
expected_end_status=None, max_retries=30):
"""Poll (briefly) while the state is in continue_states.
Continues until the state changes from continue_states or max_retries
are hit. If expected_end_status is specified, we assert that the end
status of the group_snapshot is expected_end_status.
"""
retries = 0
while retries <= max_retries:
try:
found_grp_snap = self.api.get_group_snapshot(group_snapshot_id)
except client.OpenStackApiException404:
return None
except client.OpenStackApiException:
# NOTE(xyang): Got OpenStackApiException(
# u'Unexpected status code',) sometimes, but
# it works if continue.
continue
self.assertEqual(group_snapshot_id, found_grp_snap['id'])
grp_snap_status = found_grp_snap['status']
if grp_snap_status not in continue_states:
if expected_end_status:
self.assertEqual(expected_end_status, grp_snap_status)
return found_grp_snap
time.sleep(1)
retries += 1

View File

@ -0,0 +1,297 @@
# Copyright 2016 EMC Corporation
# 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 cinder.tests.functional import functional_helpers
class GroupSnapshotsTest(functional_helpers._FunctionalTestBase):
_vol_type_name = 'functional_test_type'
_grp_type_name = 'functional_grp_test_type'
osapi_version_major = '3'
osapi_version_minor = '14'
def setUp(self):
super(GroupSnapshotsTest, self).setUp()
self.volume_type = self.api.create_type(self._vol_type_name)
self.group_type = self.api.create_group_type(self._grp_type_name)
def _get_flags(self):
f = super(GroupSnapshotsTest, self)._get_flags()
f['volume_driver'] = (
'cinder.tests.fake_driver.FakeLoggingVolumeDriver')
f['default_volume_type'] = self._vol_type_name
f['default_group_type'] = self._grp_type_name
return f
def test_get_group_snapshots_summary(self):
"""Simple check that listing group snapshots works."""
grp_snaps = self.api.get_group_snapshots(False)
self.assertIsNotNone(grp_snaps)
def test_get_group_snapshots(self):
"""Simple check that listing group snapshots works."""
grp_snaps = self.api.get_group_snapshots()
self.assertIsNotNone(grp_snaps)
def test_create_and_delete_group_snapshot(self):
"""Creates and deletes a group snapshot."""
# Create group
created_group = self.api.post_group(
{'group': {'group_type': self.group_type['id'],
'volume_types': [self.volume_type['id']]}})
self.assertTrue(created_group['id'])
created_group_id = created_group['id']
# Check it's there
found_group = self._poll_group_while(created_group_id,
['creating'])
self.assertEqual(created_group_id, found_group['id'])
self.assertEqual(self.group_type['id'], found_group['group_type'])
self.assertEqual('available', found_group['status'])
# Create volume
created_volume = self.api.post_volume(
{'volume': {'size': 1,
'group_id': created_group_id,
'volume_type': self.volume_type['id']}})
self.assertTrue(created_volume['id'])
created_volume_id = created_volume['id']
# Check it's there
found_volume = self.api.get_volume(created_volume_id)
self.assertEqual(created_volume_id, found_volume['id'])
self.assertEqual(self._vol_type_name, found_volume['volume_type'])
self.assertEqual(created_group_id, found_volume['group_id'])
# Wait (briefly) for creation. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['creating'])
# It should be available...
self.assertEqual('available', found_volume['status'])
# Create group snapshot
created_group_snapshot = self.api.post_group_snapshot(
{'group_snapshot': {'group_id': created_group_id}})
self.assertTrue(created_group_snapshot['id'])
created_group_snapshot_id = created_group_snapshot['id']
# Check it's there
found_group_snapshot = self._poll_group_snapshot_while(
created_group_snapshot_id, ['creating'])
self.assertEqual(created_group_snapshot_id, found_group_snapshot['id'])
self.assertEqual(created_group_id,
found_group_snapshot['group_id'])
self.assertEqual('available', found_group_snapshot['status'])
# Delete the group snapshot
self.api.delete_group_snapshot(created_group_snapshot_id)
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_group_snapshot = self._poll_group_snapshot_while(
created_group_snapshot_id, ['deleting'])
# Delete the original group
self.api.delete_group(created_group_id,
{'delete': {'delete-volumes': True}})
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['deleting'])
found_group = self._poll_group_while(created_group_id, ['deleting'])
# Should be gone
self.assertFalse(found_group_snapshot)
self.assertFalse(found_volume)
self.assertFalse(found_group)
def test_create_group_from_group_snapshot(self):
"""Creates a group from a group snapshot."""
# Create group
created_group = self.api.post_group(
{'group': {'group_type': self.group_type['id'],
'volume_types': [self.volume_type['id']]}})
self.assertTrue(created_group['id'])
created_group_id = created_group['id']
# Check it's there
found_group = self._poll_group_while(created_group_id,
['creating'])
self.assertEqual(created_group_id, found_group['id'])
self.assertEqual(self.group_type['id'], found_group['group_type'])
self.assertEqual('available', found_group['status'])
# Create volume
created_volume = self.api.post_volume(
{'volume': {'size': 1,
'group_id': created_group_id,
'volume_type': self.volume_type['id']}})
self.assertTrue(created_volume['id'])
created_volume_id = created_volume['id']
# Check it's there
found_volume = self.api.get_volume(created_volume_id)
self.assertEqual(created_volume_id, found_volume['id'])
self.assertEqual(self._vol_type_name, found_volume['volume_type'])
self.assertEqual(created_group_id, found_volume['group_id'])
# Wait (briefly) for creation. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['creating'])
# It should be available...
self.assertEqual('available', found_volume['status'])
# Create group snapshot
created_group_snapshot = self.api.post_group_snapshot(
{'group_snapshot': {'group_id': created_group_id}})
self.assertTrue(created_group_snapshot['id'])
created_group_snapshot_id = created_group_snapshot['id']
# Check it's there
found_group_snapshot = self._poll_group_snapshot_while(
created_group_snapshot_id, ['creating'])
self.assertEqual(created_group_snapshot_id, found_group_snapshot['id'])
self.assertEqual(created_group_id,
found_group_snapshot['group_id'])
self.assertEqual('available', found_group_snapshot['status'])
# Create group from group snapshot
created_group_from_snap = self.api.post_group_from_src(
{'create-from-src': {
'group_snapshot_id': created_group_snapshot_id}})
self.assertTrue(created_group_from_snap['id'])
created_group_from_snap_id = created_group_from_snap['id']
# Check it's there
found_volumes = self.api.get_volumes()
self._poll_volume_while(found_volumes[0], ['creating'])
self._poll_volume_while(found_volumes[1], ['creating'])
found_group_from_snap = self._poll_group_while(
created_group_from_snap_id, ['creating'])
self.assertEqual(created_group_from_snap_id,
found_group_from_snap['id'])
self.assertEqual(created_group_snapshot_id,
found_group_from_snap['group_snapshot_id'])
self.assertEqual(self.group_type['id'],
found_group_from_snap['group_type'])
self.assertEqual('available', found_group_from_snap['status'])
# Delete the group from snap
self.api.delete_group(created_group_from_snap_id,
{'delete': {'delete-volumes': True}})
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_group_from_snap = self._poll_group_while(
created_group_from_snap_id, ['deleting'])
# Delete the group snapshot
self.api.delete_group_snapshot(created_group_snapshot_id)
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_group_snapshot = self._poll_group_snapshot_while(
created_group_snapshot_id, ['deleting'])
# Delete the original group
self.api.delete_group(created_group_id,
{'delete': {'delete-volumes': True}})
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['deleting'])
found_group = self._poll_group_while(created_group_id, ['deleting'])
# Should be gone
self.assertFalse(found_group_from_snap)
self.assertFalse(found_group_snapshot)
self.assertFalse(found_volume)
self.assertFalse(found_group)
def test_create_group_from_source_group(self):
"""Creates a group from a source group."""
# Create group
created_group = self.api.post_group(
{'group': {'group_type': self.group_type['id'],
'volume_types': [self.volume_type['id']]}})
self.assertTrue(created_group['id'])
created_group_id = created_group['id']
# Check it's there
found_group = self._poll_group_while(created_group_id,
['creating'])
self.assertEqual(created_group_id, found_group['id'])
self.assertEqual(self.group_type['id'], found_group['group_type'])
self.assertEqual('available', found_group['status'])
# Create volume
created_volume = self.api.post_volume(
{'volume': {'size': 1,
'group_id': created_group_id,
'volume_type': self.volume_type['id']}})
self.assertTrue(created_volume['id'])
created_volume_id = created_volume['id']
# Check it's there
found_volume = self.api.get_volume(created_volume_id)
self.assertEqual(created_volume_id, found_volume['id'])
self.assertEqual(self._vol_type_name, found_volume['volume_type'])
self.assertEqual(created_group_id, found_volume['group_id'])
# Wait (briefly) for creation. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['creating'])
# It should be available...
self.assertEqual('available', found_volume['status'])
# Test create group from source group
created_group_from_group = self.api.post_group_from_src(
{'create-from-src': {
'source_group_id': created_group_id}})
self.assertTrue(created_group_from_group['id'])
created_group_from_group_id = created_group_from_group['id']
# Check it's there
found_volumes = self.api.get_volumes()
self._poll_volume_while(found_volumes[0], ['creating'])
self._poll_volume_while(found_volumes[1], ['creating'])
found_group_from_group = self._poll_group_while(
created_group_from_group_id, ['creating'])
self.assertEqual(created_group_from_group_id,
found_group_from_group['id'])
self.assertEqual(created_group_id,
found_group_from_group['source_group_id'])
self.assertEqual(self.group_type['id'],
found_group_from_group['group_type'])
self.assertEqual('available', found_group_from_group['status'])
# Delete the group from group
self.api.delete_group(created_group_from_group_id,
{'delete': {'delete-volumes': True}})
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_group_from_group = self._poll_group_while(
created_group_from_group_id, ['deleting'])
# Delete the original group
self.api.delete_group(created_group_id,
{'delete': {'delete-volumes': True}})
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['deleting'])
found_group = self._poll_group_while(created_group_id, ['deleting'])
# Should be gone
self.assertFalse(found_group_from_group)
self.assertFalse(found_volume)
self.assertFalse(found_group)

View File

@ -0,0 +1,95 @@
# Copyright 2016 EMC Corporation
# 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 cinder.tests.functional import functional_helpers
class GroupsTest(functional_helpers._FunctionalTestBase):
_vol_type_name = 'functional_test_type'
_grp_type_name = 'functional_grp_test_type'
osapi_version_major = '3'
osapi_version_minor = '13'
def setUp(self):
super(GroupsTest, self).setUp()
self.volume_type = self.api.create_type(self._vol_type_name)
self.group_type = self.api.create_group_type(self._grp_type_name)
def _get_flags(self):
f = super(GroupsTest, self)._get_flags()
f['volume_driver'] = (
'cinder.tests.fake_driver.FakeLoggingVolumeDriver')
f['default_volume_type'] = self._vol_type_name
f['default_group_type'] = self._grp_type_name
return f
def test_get_groups_summary(self):
"""Simple check that listing groups works."""
grps = self.api.get_groups(False)
self.assertIsNotNone(grps)
def test_get_groups(self):
"""Simple check that listing groups works."""
grps = self.api.get_groups()
self.assertIsNotNone(grps)
def test_create_and_delete_group(self):
"""Creates and deletes a group."""
# Create group
created_group = self.api.post_group(
{'group': {'group_type': self.group_type['id'],
'volume_types': [self.volume_type['id']]}})
self.assertTrue(created_group['id'])
created_group_id = created_group['id']
# Check it's there
found_group = self._poll_group_while(created_group_id,
['creating'])
self.assertEqual(created_group_id, found_group['id'])
self.assertEqual(self.group_type['id'], found_group['group_type'])
self.assertEqual('available', found_group['status'])
# Create volume
created_volume = self.api.post_volume(
{'volume': {'size': 1,
'group_id': created_group_id,
'volume_type': self.volume_type['id']}})
self.assertTrue(created_volume['id'])
created_volume_id = created_volume['id']
# Check it's there
found_volume = self.api.get_volume(created_volume_id)
self.assertEqual(created_volume_id, found_volume['id'])
self.assertEqual(self._vol_type_name, found_volume['volume_type'])
self.assertEqual(created_group_id, found_volume['group_id'])
# Wait (briefly) for creation. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['creating'])
# It should be available...
self.assertEqual('available', found_volume['status'])
# Delete the original group
self.api.delete_group(created_group_id,
{'delete': {'delete-volumes': True}})
# Wait (briefly) for deletion. Delay is due to the 'message queue'
found_volume = self._poll_volume_while(created_volume_id, ['deleting'])
found_group = self._poll_group_while(created_group_id, ['deleting'])
# Should be gone
self.assertFalse(found_volume)
self.assertFalse(found_group)

View File

@ -29,7 +29,7 @@ from cinder.tests.unit import conf_fixture
from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_snapshot from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import utils as tests_utils from cinder.tests.unit import utils as tests_utils
import cinder.volume from cinder.volume import api as volume_api
from cinder.volume import configuration as conf from cinder.volume import configuration as conf
from cinder.volume import driver from cinder.volume import driver
from cinder.volume import utils as volutils from cinder.volume import utils as volutils
@ -51,16 +51,15 @@ class GroupManagerTestCase(test.TestCase):
self.volume.driver.set_initialized() self.volume.driver.set_initialized()
self.volume.stats = {'allocated_capacity_gb': 0, self.volume.stats = {'allocated_capacity_gb': 0,
'pools': {}} 'pools': {}}
self.volume_api = cinder.volume.api.API() self.volume_api = volume_api.API()
def test_delete_volume_in_group(self): def test_delete_volume_in_group(self):
"""Test deleting a volume that's tied to a group fails.""" """Test deleting a volume that's tied to a group fails."""
volume_api = cinder.volume.api.API()
volume_params = {'status': 'available', volume_params = {'status': 'available',
'group_id': fake.GROUP_ID} 'group_id': fake.GROUP_ID}
volume = tests_utils.create_volume(self.context, **volume_params) volume = tests_utils.create_volume(self.context, **volume_params)
self.assertRaises(exception.InvalidVolume, self.assertRaises(exception.InvalidVolume,
volume_api.delete, self.context, volume) self.volume_api.delete, self.context, volume)
@mock.patch.object(GROUP_QUOTAS, "reserve", @mock.patch.object(GROUP_QUOTAS, "reserve",
return_value=["RESERVATION"]) return_value=["RESERVATION"])
@ -675,18 +674,17 @@ class GroupManagerTestCase(test.TestCase):
'id': '9999', 'id': '9999',
'name': 'fake', 'name': 'fake',
} }
vol_api = cinder.volume.api.API()
# Volume type must be provided when creating a volume in a # Volume type must be provided when creating a volume in a
# group. # group.
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
vol_api.create, self.volume_api.create,
self.context, 1, 'vol1', 'volume 1', self.context, 1, 'vol1', 'volume 1',
group=grp) group=grp)
# Volume type must be valid. # Volume type must be valid.
self.assertRaises(exception.InvalidInput, self.assertRaises(exception.InvalidInput,
vol_api.create, self.volume_api.create,
self.context, 1, 'vol1', 'volume 1', self.context, 1, 'vol1', 'volume 1',
volume_type=fake_type, volume_type=fake_type,
group=grp) group=grp)

View File

@ -1603,7 +1603,7 @@ class VolumeTestCase(BaseVolumeTestCase):
pass pass
@mock.patch.object(coordination.Coordinator, 'get_lock') @mock.patch.object(coordination.Coordinator, 'get_lock')
@mock.patch.object(cinder.volume.drivers.lvm.LVMVolumeDriver, @mock.patch.object(cinder.tests.fake_driver.FakeLoggingVolumeDriver,
'create_volume_from_snapshot') 'create_volume_from_snapshot')
def test_create_volume_from_snapshot_check_locks( def test_create_volume_from_snapshot_check_locks(
self, mock_lvm_create, mock_lock): self, mock_lvm_create, mock_lock):