Volume types need to be specified when creating CG

When creating a consistency group, the scheduler will find a backend that
supports all input volume types. If volume types are not provided, the
default_volume_type in cinder.conf will be used, however, this could cause
inconsistent behavior in a user environment where the default_volume_type
is defined in some places but not in others. This fix removed the use of
default_volume_type for CG creation, added a check to verify that volume
types are provided when creating a CG.

When creating a volume and adding it to a CG, we need to make sure a
volume type is provided as well.

Change-Id: I078d4fdd8d92529e853be16272ad74d1e130f712
Closes-Bug: #1366371
This commit is contained in:
Xing Yang 2014-09-06 13:32:02 -04:00
parent 52df6822f6
commit 9082273305
7 changed files with 240 additions and 136 deletions

View File

@ -125,11 +125,10 @@ class ConsistencyGroupsController(wsgi.Controller):
group = self.consistencygroup_api.get(context, id)
self.consistencygroup_api.delete(context, group, force)
except exception.ConsistencyGroupNotFound:
msg = _("Consistency group could not be found")
msg = _("Consistency group %s could not be found.") % id
raise exc.HTTPNotFound(explanation=msg)
except exception.InvalidConsistencyGroup:
msg = _("Invalid consistency group")
raise exc.HTTPBadRequest(explanation=msg)
except exception.InvalidConsistencyGroup as error:
raise exc.HTTPBadRequest(explanation=error.msg)
return webob.Response(status_int=202)
@ -176,6 +175,10 @@ class ConsistencyGroupsController(wsgi.Controller):
name = consistencygroup.get('name', None)
description = consistencygroup.get('description', None)
volume_types = consistencygroup.get('volume_types', None)
if not volume_types:
msg = _("volume_types must be provided to create "
"consistency group %(name)s.") % {'name': name}
raise exc.HTTPBadRequest(explanation=msg)
availability_zone = consistencygroup.get('availability_zone', None)
LOG.info(_("Creating consistency group %(name)s."),
@ -184,7 +187,7 @@ class ConsistencyGroupsController(wsgi.Controller):
try:
new_consistencygroup = self.consistencygroup_api.create(
context, name, description, cg_volume_types=volume_types,
context, name, description, volume_types,
availability_zone=availability_zone)
except exception.InvalidConsistencyGroup as error:
raise exc.HTTPBadRequest(explanation=error.msg)

View File

@ -104,27 +104,20 @@ class API(base.Base):
return availability_zone
def create(self, context, name, description,
cg_volume_types=None, availability_zone=None):
cg_volume_types, availability_zone=None):
check_policy(context, 'create')
volume_type_list = None
if cg_volume_types:
volume_type_list = cg_volume_types.split(',')
volume_type_list = cg_volume_types.split(',')
req_volume_types = []
if volume_type_list:
req_volume_types = (self.db.volume_types_get_by_name_or_id(
context, volume_type_list))
if not req_volume_types:
volume_type = volume_types.get_default_volume_type()
req_volume_types.append(volume_type)
req_volume_types = (self.db.volume_types_get_by_name_or_id(
context, volume_type_list))
req_volume_type_ids = ""
for voltype in req_volume_types:
if voltype:
req_volume_type_ids = (
req_volume_type_ids + voltype.get('id') + ",")
req_volume_type_ids = (
req_volume_type_ids + voltype.get('id') + ",")
if len(req_volume_type_ids) == 0:
req_volume_type_ids = None

View File

@ -96,18 +96,18 @@ class SchedulerManager(manager.Manager):
context, group_id,
request_spec_list,
filter_properties_list)
except exception.NoValidHost as ex:
except exception.NoValidHost:
msg = (_("Could not find a host for consistency group "
"%(group_id)s.") %
{'group_id': group_id})
LOG.error(msg)
db.consistencygroup_update(context, group_id,
{'status': 'error'})
except Exception as ex:
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_("Failed to create consistency group "
"%(group_id)s."))
LOG.exception(ex)
LOG.exception(_("Failed to create consistency group "
"%(group_id)s."),
{'group_id': group_id})
db.consistencygroup_update(context, group_id,
{'status': 'error'})

View File

@ -26,6 +26,7 @@ import webob
import cinder.consistencygroup
from cinder import context
from cinder import db
from cinder.i18n import _
from cinder import test
from cinder.tests.api import fakes
@ -73,14 +74,15 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['consistencygroup']['availability_zone'],
'az1')
self.assertEqual(res_dict['consistencygroup']['description'],
'this is a test consistency group')
self.assertEqual(res_dict['consistencygroup']['name'],
'test_consistencygroup')
self.assertEqual(res_dict['consistencygroup']['status'], 'creating')
self.assertEqual(200, res.status_int)
self.assertEqual('az1',
res_dict['consistencygroup']['availability_zone'])
self.assertEqual('this is a test consistency group',
res_dict['consistencygroup']['description'])
self.assertEqual('test_consistencygroup',
res_dict['consistencygroup']['name'])
self.assertEqual('creating',
res_dict['consistencygroup']['status'])
db.consistencygroup_destroy(context.get_admin_context(),
consistencygroup_id)
@ -93,11 +95,11 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
req.headers['Content-Type'] = 'application/xml'
req.headers['Accept'] = 'application/xml'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
self.assertEqual(200, res.status_int)
dom = minidom.parseString(res.body)
consistencygroup = dom.getElementsByTagName('consistencygroup')
name = consistencygroup.item(0).getAttribute('name')
self.assertEqual(name.strip(), "test_consistencygroup")
self.assertEqual("test_consistencygroup", name.strip())
db.consistencygroup_destroy(
context.get_admin_context(),
consistencygroup_id)
@ -109,10 +111,10 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 404)
self.assertEqual(res_dict['itemNotFound']['code'], 404)
self.assertEqual(res_dict['itemNotFound']['message'],
'ConsistencyGroup 9999 could not be found.')
self.assertEqual(404, res.status_int)
self.assertEqual(404, res_dict['itemNotFound']['code'])
self.assertEqual('ConsistencyGroup 9999 could not be found.',
res_dict['itemNotFound']['message'])
def test_list_consistencygroups_json(self):
consistencygroup_id1 = self._create_consistencygroup()
@ -125,19 +127,19 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['consistencygroups'][0]['id'],
consistencygroup_id1)
self.assertEqual(res_dict['consistencygroups'][0]['name'],
'test_consistencygroup')
self.assertEqual(res_dict['consistencygroups'][1]['id'],
consistencygroup_id2)
self.assertEqual(res_dict['consistencygroups'][1]['name'],
'test_consistencygroup')
self.assertEqual(res_dict['consistencygroups'][2]['id'],
consistencygroup_id3)
self.assertEqual(res_dict['consistencygroups'][2]['name'],
'test_consistencygroup')
self.assertEqual(200, res.status_int)
self.assertEqual(consistencygroup_id1,
res_dict['consistencygroups'][0]['id'])
self.assertEqual('test_consistencygroup',
res_dict['consistencygroups'][0]['name'])
self.assertEqual(consistencygroup_id2,
res_dict['consistencygroups'][1]['id'])
self.assertEqual('test_consistencygroup',
res_dict['consistencygroups'][1]['name'])
self.assertEqual(consistencygroup_id3,
res_dict['consistencygroups'][2]['id'])
self.assertEqual('test_consistencygroup',
res_dict['consistencygroups'][2]['name'])
db.consistencygroup_destroy(context.get_admin_context(),
consistencygroup_id3)
@ -157,16 +159,16 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
req.headers['Accept'] = 'application/xml'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
self.assertEqual(200, res.status_int)
dom = minidom.parseString(res.body)
consistencygroup_list = dom.getElementsByTagName('consistencygroup')
self.assertEqual(consistencygroup_list.item(0).getAttribute('id'),
consistencygroup_id1)
self.assertEqual(consistencygroup_list.item(1).getAttribute('id'),
consistencygroup_id2)
self.assertEqual(consistencygroup_list.item(2).getAttribute('id'),
consistencygroup_id3)
self.assertEqual(consistencygroup_id1,
consistencygroup_list.item(0).getAttribute('id'))
self.assertEqual(consistencygroup_id2,
consistencygroup_list.item(1).getAttribute('id'))
self.assertEqual(consistencygroup_id3,
consistencygroup_list.item(2).getAttribute('id'))
db.consistencygroup_destroy(context.get_admin_context(),
consistencygroup_id3)
@ -187,39 +189,39 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['consistencygroups'][0]['availability_zone'],
'az1')
self.assertEqual(res_dict['consistencygroups'][0]['description'],
'this is a test consistency group')
self.assertEqual(res_dict['consistencygroups'][0]['name'],
'test_consistencygroup')
self.assertEqual(res_dict['consistencygroups'][0]['id'],
consistencygroup_id1)
self.assertEqual(res_dict['consistencygroups'][0]['status'],
'creating')
self.assertEqual(200, res.status_int)
self.assertEqual('az1',
res_dict['consistencygroups'][0]['availability_zone'])
self.assertEqual('this is a test consistency group',
res_dict['consistencygroups'][0]['description'])
self.assertEqual('test_consistencygroup',
res_dict['consistencygroups'][0]['name'])
self.assertEqual(consistencygroup_id1,
res_dict['consistencygroups'][0]['id'])
self.assertEqual('creating',
res_dict['consistencygroups'][0]['status'])
self.assertEqual(res_dict['consistencygroups'][1]['availability_zone'],
'az1')
self.assertEqual(res_dict['consistencygroups'][1]['description'],
'this is a test consistency group')
self.assertEqual(res_dict['consistencygroups'][1]['name'],
'test_consistencygroup')
self.assertEqual(res_dict['consistencygroups'][1]['id'],
consistencygroup_id2)
self.assertEqual(res_dict['consistencygroups'][1]['status'],
'creating')
self.assertEqual('az1',
res_dict['consistencygroups'][1]['availability_zone'])
self.assertEqual('this is a test consistency group',
res_dict['consistencygroups'][1]['description'])
self.assertEqual('test_consistencygroup',
res_dict['consistencygroups'][1]['name'])
self.assertEqual(consistencygroup_id2,
res_dict['consistencygroups'][1]['id'])
self.assertEqual('creating',
res_dict['consistencygroups'][1]['status'])
self.assertEqual(res_dict['consistencygroups'][2]['availability_zone'],
'az1')
self.assertEqual(res_dict['consistencygroups'][2]['description'],
'this is a test consistency group')
self.assertEqual(res_dict['consistencygroups'][2]['name'],
'test_consistencygroup')
self.assertEqual(res_dict['consistencygroups'][2]['id'],
consistencygroup_id3)
self.assertEqual(res_dict['consistencygroups'][2]['status'],
'creating')
self.assertEqual('az1',
res_dict['consistencygroups'][2]['availability_zone'])
self.assertEqual('this is a test consistency group',
res_dict['consistencygroups'][2]['description'])
self.assertEqual('test_consistencygroup',
res_dict['consistencygroups'][2]['name'])
self.assertEqual(consistencygroup_id3,
res_dict['consistencygroups'][2]['id'])
self.assertEqual('creating',
res_dict['consistencygroups'][2]['status'])
db.consistencygroup_destroy(context.get_admin_context(),
consistencygroup_id3)
@ -239,54 +241,57 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
req.headers['Accept'] = 'application/xml'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200)
self.assertEqual(200, res.status_int)
dom = minidom.parseString(res.body)
consistencygroup_detail = dom.getElementsByTagName('consistencygroup')
self.assertEqual(
consistencygroup_detail.item(0).getAttribute('availability_zone'),
'az1')
'az1',
consistencygroup_detail.item(0).getAttribute('availability_zone'))
self.assertEqual(
consistencygroup_detail.item(0).getAttribute('description'),
'this is a test consistency group')
'this is a test consistency group',
consistencygroup_detail.item(0).getAttribute('description'))
self.assertEqual(
consistencygroup_detail.item(0).getAttribute('name'),
'test_consistencygroup')
'test_consistencygroup',
consistencygroup_detail.item(0).getAttribute('name'))
self.assertEqual(
consistencygroup_detail.item(0).getAttribute('id'),
consistencygroup_id1)
consistencygroup_id1,
consistencygroup_detail.item(0).getAttribute('id'))
self.assertEqual(
consistencygroup_detail.item(0).getAttribute('status'), 'creating')
'creating',
consistencygroup_detail.item(0).getAttribute('status'))
self.assertEqual(
consistencygroup_detail.item(1).getAttribute('availability_zone'),
'az1')
'az1',
consistencygroup_detail.item(1).getAttribute('availability_zone'))
self.assertEqual(
consistencygroup_detail.item(1).getAttribute('description'),
'this is a test consistency group')
'this is a test consistency group',
consistencygroup_detail.item(1).getAttribute('description'))
self.assertEqual(
consistencygroup_detail.item(1).getAttribute('name'),
'test_consistencygroup')
'test_consistencygroup',
consistencygroup_detail.item(1).getAttribute('name'))
self.assertEqual(
consistencygroup_detail.item(1).getAttribute('id'),
consistencygroup_id2)
consistencygroup_id2,
consistencygroup_detail.item(1).getAttribute('id'))
self.assertEqual(
consistencygroup_detail.item(1).getAttribute('status'), 'creating')
'creating',
consistencygroup_detail.item(1).getAttribute('status'))
self.assertEqual(
consistencygroup_detail.item(2).getAttribute('availability_zone'),
'az1')
'az1',
consistencygroup_detail.item(2).getAttribute('availability_zone'))
self.assertEqual(
consistencygroup_detail.item(2).getAttribute('description'),
'this is a test consistency group')
'this is a test consistency group',
consistencygroup_detail.item(2).getAttribute('description'))
self.assertEqual(
consistencygroup_detail.item(2).getAttribute('name'),
'test_consistencygroup')
'test_consistencygroup',
consistencygroup_detail.item(2).getAttribute('name'))
self.assertEqual(
consistencygroup_detail.item(2).getAttribute('id'),
consistencygroup_id3)
consistencygroup_id3,
consistencygroup_detail.item(2).getAttribute('id'))
self.assertEqual(
consistencygroup_detail.item(2).getAttribute('status'), 'creating')
'creating',
consistencygroup_detail.item(2).getAttribute('status'))
db.consistencygroup_destroy(context.get_admin_context(),
consistencygroup_id3)
@ -297,7 +302,14 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
def test_create_consistencygroup_json(self):
group_id = "1"
# Create volume type
vol_type = 'test'
db.volume_type_create(context.get_admin_context(),
{'name': vol_type, 'extra_specs': {}})
body = {"consistencygroup": {"name": "cg1",
"volume_types": vol_type,
"description":
"Consistency Group 1", }}
req = webob.Request.blank('/v2/fake/consistencygroups')
@ -307,7 +319,7 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 202)
self.assertEqual(202, res.status_int)
self.assertIn('id', res_dict['consistencygroup'])
db.consistencygroup_destroy(context.get_admin_context(), group_id)
@ -322,11 +334,11 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 400)
self.assertEqual(res_dict['badRequest']['code'], 400)
self.assertEqual(res_dict['badRequest']['message'],
'The server could not comply with the request since'
' it is either malformed or otherwise incorrect.')
self.assertEqual(400, res.status_int)
self.assertEqual(400, res_dict['badRequest']['code'])
self.assertEqual('The server could not comply with the request since'
' it is either malformed or otherwise incorrect.',
res_dict['badRequest']['message'])
def test_delete_consistencygroup_available(self):
consistencygroup_id = self._create_consistencygroup(status='available')
@ -338,10 +350,10 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
self.assertEqual(self._get_consistencygroup_attrib(consistencygroup_id,
'status'),
'deleting')
self.assertEqual(202, res.status_int)
self.assertEqual('deleting',
self._get_consistencygroup_attrib(consistencygroup_id,
'status'))
db.consistencygroup_destroy(context.get_admin_context(),
consistencygroup_id)
@ -354,10 +366,10 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 404)
self.assertEqual(res_dict['itemNotFound']['code'], 404)
self.assertEqual(res_dict['itemNotFound']['message'],
'Consistency group could not be found')
self.assertEqual(404, res.status_int)
self.assertEqual(404, res_dict['itemNotFound']['code'])
self.assertEqual('Consistency group 9999 could not be found.',
res_dict['itemNotFound']['message'])
def test_delete_consistencygroup_with_Invalidconsistencygroup(self):
consistencygroup_id = self._create_consistencygroup(status='invalid')
@ -370,10 +382,11 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 400)
self.assertEqual(res_dict['badRequest']['code'], 400)
self.assertEqual(res_dict['badRequest']['message'],
'Invalid consistency group')
self.assertEqual(400, res.status_int)
self.assertEqual(400, res_dict['badRequest']['code'])
msg = (_('Invalid ConsistencyGroup: Consistency group status must be '
'available or error, but current status is: invalid'))
self.assertEqual(msg, res_dict['badRequest']['message'])
db.consistencygroup_destroy(context.get_admin_context(),
consistencygroup_id)
@ -425,3 +438,21 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
context.get_admin_context(read_deleted='yes'),
cg['id'])
self.assertEqual(cg['status'], 'deleted')
def test_create_consistencygroup_failed_no_volume_type(self):
name = 'cg1'
body = {"consistencygroup": {"name": name,
"description":
"Consistency Group 1", }}
req = webob.Request.blank('/v2/fake/consistencygroups')
req.method = 'POST'
req.headers['Content-Type'] = 'application/json'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(400, res.status_int)
self.assertEqual(400, res_dict['badRequest']['code'])
msg = (_('volume_types must be provided to create '
'consistency group %s.') % name)
self.assertEqual(msg, res_dict['badRequest']['message'])

View File

@ -115,6 +115,36 @@ class VolumeApiTest(test.TestCase):
'encrypted': False}}
self.assertEqual(res_dict, ex)
def test_volume_create_with_consistencygroup_invalid_type(self):
ctxt = context.RequestContext('fake', 'fake', auth_token=True)
vol_type = db.volume_type_create(
context.get_admin_context(),
dict(name=CONF.default_volume_type, extra_specs={})
)
db_vol_type = db.volume_type_get(context.get_admin_context(),
vol_type.id)
cg = {
'id': '1',
'name': 'cg1',
'volume_type_id': db_vol_type['id'],
}
fake_type = {
'id': '9999',
'name': 'fake',
}
vol_api = volume_api.API()
self.assertRaises(exception.InvalidInput,
vol_api.create,
ctxt, 1, 'vol1', 'volume 1',
consistencygroup=cg)
self.assertRaises(exception.InvalidInput,
vol_api.create,
ctxt, 1, 'vol1', 'volume 1',
volume_type=fake_type,
consistencygroup=cg)
def test_volume_create_with_type(self):
vol_type = db.volume_type_create(
context.get_admin_context(),

View File

@ -22,7 +22,10 @@ import mock
from oslo.config import cfg
from cinder import context
from cinder import db
from cinder import exception
from cinder.i18n import _
from cinder.openstack.common import log as logging
from cinder.scheduler import driver
from cinder.scheduler import filter_scheduler
from cinder.scheduler import manager
@ -188,6 +191,46 @@ class SchedulerManagerTestCase(test.TestCase):
{'status': 'in-use'})
self.manager.driver.find_retype_host = orig_retype
def test_create_consistencygroup_exceptions(self):
with mock.patch.object(filter_scheduler.FilterScheduler,
'schedule_create_consistencygroup') as mock_cg:
original_driver = self.manager.driver
self.manager.driver = filter_scheduler.FilterScheduler
LOG = logging.getLogger('cinder.scheduler.manager')
self.stubs.Set(LOG, 'error', mock.Mock())
self.stubs.Set(LOG, 'exception', mock.Mock())
self.stubs.Set(db, 'consistencygroup_update', mock.Mock())
ex = exception.CinderException('test')
mock_cg.side_effect = ex
group_id = '1'
self.assertRaises(exception.CinderException,
self.manager.create_consistencygroup,
self.context,
'volume',
group_id)
LOG.exception.assert_called_once_with(_(
"Failed to create consistency group "
"%(group_id)s."), {'group_id': group_id})
db.consistencygroup_update.assert_called_once_with(
self.context, group_id, {'status': 'error'})
mock_cg.reset_mock()
LOG.exception.reset_mock()
db.consistencygroup_update.reset_mock()
mock_cg.side_effect = exception.NoValidHost(
reason="No weighed hosts available")
self.manager.create_consistencygroup(
self.context, 'volume', group_id)
LOG.error.assert_called_once_with(_(
"Could not find a host for consistency group "
"%(group_id)s.") % {'group_id': group_id})
db.consistencygroup_update.assert_called_once_with(
self.context, group_id, {'status': 'error'})
self.manager.driver = original_driver
class SchedulerTestCase(test.TestCase):
"""Test case for base scheduler driver class."""

View File

@ -155,11 +155,15 @@ class API(base.Base):
scheduler_hints=None, backup_source_volume=None,
source_replica=None, consistencygroup=None):
if volume_type and consistencygroup:
if consistencygroup:
if not volume_type:
msg = _("volume_type must be provided when creating "
"a volume in a consistency group.")
raise exception.InvalidInput(reason=msg)
cg_voltypeids = consistencygroup.get('volume_type_id')
if volume_type.get('id') not in cg_voltypeids:
msg = _("Invalid volume_type provided (requested type "
"must be supported by this consistency group.")
"must be supported by this consistency group).")
raise exception.InvalidInput(reason=msg)
if source_volume and volume_type: