Fix share server info in CGs created from CGs
Currently when a POST request is made to /consistency-groups with a source cg-snapshot, the API does not register the share network information (share_server_id and share_network_id) in the database row newly created for the CG being created. This information is essential to any shares that are being created along with the consistency group. - Disallow providing a share_network_id when using a source cg_snapshot_id - Copy share network information from the parent CG - Fix the share_server_id that was incorrect in the API response APIImpact Closes-Bug: #1571594 Closes-Bug: #1572742 Change-Id: I1c3581c81e0b845f46eef3cd0acddb55850447a5
This commit is contained in:
parent
669bcd1557
commit
118d440a90
@ -192,6 +192,12 @@ class CGController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
raise exc.HTTPBadRequest(explanation=msg)
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
kwargs['share_type_ids'] = _share_types
|
kwargs['share_type_ids'] = _share_types
|
||||||
|
|
||||||
|
if 'share_network_id' in cg and 'source_cgsnapshot_id' in cg:
|
||||||
|
msg = _("Cannot supply both 'share_network_id' and "
|
||||||
|
"'source_cgsnapshot_id' attributes as the share network "
|
||||||
|
"is inherited from the source.")
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
if 'source_cgsnapshot_id' in cg:
|
if 'source_cgsnapshot_id' in cg:
|
||||||
source_cgsnapshot_id = cg.get('source_cgsnapshot_id')
|
source_cgsnapshot_id = cg.get('source_cgsnapshot_id')
|
||||||
if not uuidutils.is_uuid_like(source_cgsnapshot_id):
|
if not uuidutils.is_uuid_like(source_cgsnapshot_id):
|
||||||
@ -199,7 +205,7 @@ class CGController(wsgi.Controller, wsgi.AdminActionsMixin):
|
|||||||
raise exc.HTTPBadRequest(explanation=six.text_type(msg))
|
raise exc.HTTPBadRequest(explanation=six.text_type(msg))
|
||||||
kwargs['source_cgsnapshot_id'] = source_cgsnapshot_id
|
kwargs['source_cgsnapshot_id'] = source_cgsnapshot_id
|
||||||
|
|
||||||
if 'share_network_id' in cg:
|
elif 'share_network_id' in cg:
|
||||||
share_network_id = cg.get('share_network_id')
|
share_network_id = cg.get('share_network_id')
|
||||||
if not uuidutils.is_uuid_like(share_network_id):
|
if not uuidutils.is_uuid_like(share_network_id):
|
||||||
msg = _("The 'share_network_id' attribute must be a uuid.")
|
msg = _("The 'share_network_id' attribute must be a uuid.")
|
||||||
|
@ -59,7 +59,7 @@ class CGViewBuilder(common.ViewBuilder):
|
|||||||
'links': self._get_links(request, cg['id']),
|
'links': self._get_links(request, cg['id']),
|
||||||
}
|
}
|
||||||
if context.is_admin:
|
if context.is_admin:
|
||||||
cg_dict['share_server_id'] = cg_dict.get('share_server_id')
|
cg_dict['share_server_id'] = cg.get('share_server_id')
|
||||||
return {'consistency_group': cg_dict}
|
return {'consistency_group': cg_dict}
|
||||||
|
|
||||||
def _list_view(self, func, request, shares):
|
def _list_view(self, func, request, shares):
|
||||||
|
@ -54,6 +54,9 @@ class API(base.Base):
|
|||||||
|
|
||||||
cgsnapshot = None
|
cgsnapshot = None
|
||||||
original_cg = None
|
original_cg = None
|
||||||
|
# NOTE(gouthamr): share_server_id is inherited from the parent CG if a
|
||||||
|
# CG snapshot is specified, else, it will be set in the share manager.
|
||||||
|
share_server_id = None
|
||||||
if source_cgsnapshot_id:
|
if source_cgsnapshot_id:
|
||||||
cgsnapshot = self.db.cgsnapshot_get(context, source_cgsnapshot_id)
|
cgsnapshot = self.db.cgsnapshot_get(context, source_cgsnapshot_id)
|
||||||
if cgsnapshot['status'] != constants.STATUS_AVAILABLE:
|
if cgsnapshot['status'] != constants.STATUS_AVAILABLE:
|
||||||
@ -65,6 +68,8 @@ class API(base.Base):
|
|||||||
'consistency_group_id'])
|
'consistency_group_id'])
|
||||||
share_type_ids = [s['share_type_id'] for s in original_cg[
|
share_type_ids = [s['share_type_id'] for s in original_cg[
|
||||||
'share_types']]
|
'share_types']]
|
||||||
|
share_network_id = original_cg['share_network_id']
|
||||||
|
share_server_id = original_cg['share_server_id']
|
||||||
|
|
||||||
# Get share_type_objects
|
# Get share_type_objects
|
||||||
share_type_objects = []
|
share_type_objects = []
|
||||||
@ -116,6 +121,7 @@ class API(base.Base):
|
|||||||
options = {
|
options = {
|
||||||
'source_cgsnapshot_id': source_cgsnapshot_id,
|
'source_cgsnapshot_id': source_cgsnapshot_id,
|
||||||
'share_network_id': share_network_id,
|
'share_network_id': share_network_id,
|
||||||
|
'share_server_id': share_server_id,
|
||||||
'name': name,
|
'name': name,
|
||||||
'description': description,
|
'description': description,
|
||||||
'user_id': context.user_id,
|
'user_id': context.user_id,
|
||||||
@ -135,7 +141,7 @@ class API(base.Base):
|
|||||||
for member in members:
|
for member in members:
|
||||||
share_type = share_types.get_share_type(
|
share_type = share_types.get_share_type(
|
||||||
context, member['share_type_id'])
|
context, member['share_type_id'])
|
||||||
member['share'] = self.db.share_instance_get(
|
member['share_instance'] = self.db.share_instance_get(
|
||||||
context, member['share_instance_id'],
|
context, member['share_instance_id'],
|
||||||
with_share_data=True)
|
with_share_data=True)
|
||||||
self.share_api.create(context, member['share_proto'],
|
self.share_api.create(context, member['share_proto'],
|
||||||
|
@ -277,10 +277,16 @@ class API(base.Base):
|
|||||||
share_network_id=share_network_id))
|
share_network_id=share_network_id))
|
||||||
|
|
||||||
if cgsnapshot_member:
|
if cgsnapshot_member:
|
||||||
host = cgsnapshot_member['share']['host']
|
# Inherit properties from the cgsnapshot_member
|
||||||
|
member_share_instance = cgsnapshot_member['share_instance']
|
||||||
|
updates = {
|
||||||
|
'host': member_share_instance['host'],
|
||||||
|
'share_network_id': member_share_instance['share_network_id'],
|
||||||
|
'share_server_id': member_share_instance['share_server_id'],
|
||||||
|
}
|
||||||
share = self.db.share_instance_update(context,
|
share = self.db.share_instance_update(context,
|
||||||
share_instance['id'],
|
share_instance['id'],
|
||||||
{'host': host})
|
updates)
|
||||||
# NOTE(ameade): Do not cast to driver if creating from cgsnapshot
|
# NOTE(ameade): Do not cast to driver if creating from cgsnapshot
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -75,7 +75,11 @@ class CGApiTest(test.TestCase):
|
|||||||
|
|
||||||
return cg, req
|
return cg, req
|
||||||
|
|
||||||
def _get_fake_cg(self, **values):
|
def _get_fake_cg(self, ctxt=None, **values):
|
||||||
|
|
||||||
|
if ctxt is None:
|
||||||
|
ctxt = self.context
|
||||||
|
|
||||||
cg = {
|
cg = {
|
||||||
'id': 'fake_id',
|
'id': 'fake_id',
|
||||||
'user_id': 'fakeuser',
|
'user_id': 'fakeuser',
|
||||||
@ -85,8 +89,8 @@ class CGApiTest(test.TestCase):
|
|||||||
'description': None,
|
'description': None,
|
||||||
'host': None,
|
'host': None,
|
||||||
'source_cgsnapshot_id': None,
|
'source_cgsnapshot_id': None,
|
||||||
'share_network_id': None,
|
'share_network_id': uuid.uuid4(),
|
||||||
'share_server_id': None,
|
'share_server_id': uuid.uuid4(),
|
||||||
'share_types': [],
|
'share_types': [],
|
||||||
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||||
}
|
}
|
||||||
@ -95,7 +99,8 @@ class CGApiTest(test.TestCase):
|
|||||||
|
|
||||||
expected_cg = copy.deepcopy(cg)
|
expected_cg = copy.deepcopy(cg)
|
||||||
del expected_cg['user_id']
|
del expected_cg['user_id']
|
||||||
del expected_cg['share_server_id']
|
if not ctxt.is_admin:
|
||||||
|
del expected_cg['share_server_id']
|
||||||
expected_cg['links'] = mock.ANY
|
expected_cg['links'] = mock.ANY
|
||||||
expected_cg['share_types'] = [st['share_type_id']
|
expected_cg['share_types'] = [st['share_type_id']
|
||||||
for st in cg.get('share_types')]
|
for st in cg.get('share_types')]
|
||||||
@ -120,11 +125,10 @@ class CGApiTest(test.TestCase):
|
|||||||
mock.Mock(return_value=fake_cg))
|
mock.Mock(return_value=fake_cg))
|
||||||
|
|
||||||
body = {"consistency_group": {}}
|
body = {"consistency_group": {}}
|
||||||
context = self.request.environ['manila.context']
|
|
||||||
res_dict = self.controller.create(self.request, body)
|
res_dict = self.controller.create(self.request, body)
|
||||||
|
|
||||||
self.controller.cg_api.create.assert_called_once_with(
|
self.controller.cg_api.create.assert_called_once_with(
|
||||||
context, share_type_ids=[self.fake_share_type['id']])
|
self.context, share_type_ids=[self.fake_share_type['id']])
|
||||||
self.assertEqual(expected_cg, res_dict['consistency_group'])
|
self.assertEqual(expected_cg, res_dict['consistency_group'])
|
||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.context, self.resource_name, 'create')
|
self.context, self.resource_name, 'create')
|
||||||
@ -209,6 +213,27 @@ class CGApiTest(test.TestCase):
|
|||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
self.context, self.resource_name, 'create')
|
self.context, self.resource_name, 'create')
|
||||||
|
|
||||||
|
def test_cg_create_with_source_cgsnapshot_id_and_share_network(self):
|
||||||
|
fake_snap_id = six.text_type(uuid.uuid4())
|
||||||
|
fake_net_id = six.text_type(uuid.uuid4())
|
||||||
|
self.mock_object(share_types, 'get_default_share_type',
|
||||||
|
mock.Mock(return_value=self.fake_share_type))
|
||||||
|
mock_api_call = self.mock_object(self.controller.cg_api, 'create')
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"consistency_group": {
|
||||||
|
"source_cgsnapshot_id": fake_snap_id,
|
||||||
|
"share_network_id": fake_net_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.create,
|
||||||
|
self.request, body)
|
||||||
|
self.assertFalse(mock_api_call.called)
|
||||||
|
self.mock_policy_check.assert_called_once_with(
|
||||||
|
self.context, self.resource_name, 'create')
|
||||||
|
|
||||||
def test_cg_create_with_source_cgsnapshot_id(self):
|
def test_cg_create_with_source_cgsnapshot_id(self):
|
||||||
fake_snap_id = six.text_type(uuid.uuid4())
|
fake_snap_id = six.text_type(uuid.uuid4())
|
||||||
fake_cg, expected_cg = self._get_fake_cg(
|
fake_cg, expected_cg = self._get_fake_cg(
|
||||||
@ -513,14 +538,15 @@ class CGApiTest(test.TestCase):
|
|||||||
self.context, self.resource_name, 'get_all')
|
self.context, self.resource_name, 'get_all')
|
||||||
|
|
||||||
def test_cg_list_detail_with_limit(self):
|
def test_cg_list_detail_with_limit(self):
|
||||||
fake_cg, expected_cg = self._get_fake_cg()
|
|
||||||
fake_cg2, expected_cg2 = self._get_fake_cg(id="fake_id2")
|
|
||||||
self.mock_object(cg_api.API, 'get_all',
|
|
||||||
mock.Mock(return_value=[fake_cg, fake_cg2]))
|
|
||||||
req = fakes.HTTPRequest.blank('/consistency_groups?limit=1',
|
req = fakes.HTTPRequest.blank('/consistency_groups?limit=1',
|
||||||
version=self.api_version,
|
version=self.api_version,
|
||||||
experimental=True)
|
experimental=True)
|
||||||
req_context = req.environ['manila.context']
|
req_context = req.environ['manila.context']
|
||||||
|
fake_cg, expected_cg = self._get_fake_cg(ctxt=req_context)
|
||||||
|
fake_cg2, expected_cg2 = self._get_fake_cg(ctxt=req_context,
|
||||||
|
id="fake_id2")
|
||||||
|
self.mock_object(cg_api.API, 'get_all',
|
||||||
|
mock.Mock(return_value=[fake_cg, fake_cg2]))
|
||||||
|
|
||||||
res_dict = self.controller.detail(req)
|
res_dict = self.controller.detail(req)
|
||||||
|
|
||||||
@ -530,14 +556,15 @@ class CGApiTest(test.TestCase):
|
|||||||
req_context, self.resource_name, 'get_all')
|
req_context, self.resource_name, 'get_all')
|
||||||
|
|
||||||
def test_cg_list_detail_with_limit_and_offset(self):
|
def test_cg_list_detail_with_limit_and_offset(self):
|
||||||
fake_cg, expected_cg = self._get_fake_cg()
|
|
||||||
fake_cg2, expected_cg2 = self._get_fake_cg(id="fake_id2")
|
|
||||||
self.mock_object(cg_api.API, 'get_all',
|
|
||||||
mock.Mock(return_value=[fake_cg, fake_cg2]))
|
|
||||||
req = fakes.HTTPRequest.blank('/consistency_groups?limit=1&offset=1',
|
req = fakes.HTTPRequest.blank('/consistency_groups?limit=1&offset=1',
|
||||||
version=self.api_version,
|
version=self.api_version,
|
||||||
experimental=True)
|
experimental=True)
|
||||||
req_context = req.environ['manila.context']
|
req_context = req.environ['manila.context']
|
||||||
|
fake_cg, expected_cg = self._get_fake_cg(ctxt=req_context)
|
||||||
|
fake_cg2, expected_cg2 = self._get_fake_cg(
|
||||||
|
id="fake_id2", ctxt=req_context)
|
||||||
|
self.mock_object(cg_api.API, 'get_all',
|
||||||
|
mock.Mock(return_value=[fake_cg, fake_cg2]))
|
||||||
|
|
||||||
res_dict = self.controller.detail(req)
|
res_dict = self.controller.detail(req)
|
||||||
|
|
||||||
@ -596,30 +623,32 @@ class CGApiTest(test.TestCase):
|
|||||||
req_context, self.resource_name, 'get')
|
req_context, self.resource_name, 'get')
|
||||||
|
|
||||||
def test_cg_show_as_admin(self):
|
def test_cg_show_as_admin(self):
|
||||||
fake_cg, expected_cg = self._get_fake_cg()
|
|
||||||
expected_cg['share_server_id'] = None
|
|
||||||
self.mock_object(cg_api.API, 'get',
|
|
||||||
mock.Mock(return_value=fake_cg))
|
|
||||||
req = fakes.HTTPRequest.blank(
|
req = fakes.HTTPRequest.blank(
|
||||||
'/consistency_groups/%s' % fake_cg['id'],
|
'/consistency_groups/my_cg_id',
|
||||||
version=self.api_version, experimental=True)
|
version=self.api_version, experimental=True)
|
||||||
admin_context = req.environ['manila.context'].elevated()
|
admin_context = req.environ['manila.context'].elevated()
|
||||||
req.environ['manila.context'] = admin_context
|
req.environ['manila.context'] = admin_context
|
||||||
|
fake_cg, expected_cg = self._get_fake_cg(
|
||||||
|
ctxt=admin_context, id='my_cg_id')
|
||||||
|
self.mock_object(cg_api.API, 'get',
|
||||||
|
mock.Mock(return_value=fake_cg))
|
||||||
|
|
||||||
res_dict = self.controller.show(req, fake_cg['id'])
|
res_dict = self.controller.show(req, fake_cg['id'])
|
||||||
|
|
||||||
self.assertEqual(expected_cg, res_dict['consistency_group'])
|
self.assertEqual(expected_cg, res_dict['consistency_group'])
|
||||||
|
self.assertIsNotNone(res_dict['consistency_group']['share_server_id'])
|
||||||
self.mock_policy_check.assert_called_once_with(
|
self.mock_policy_check.assert_called_once_with(
|
||||||
admin_context, self.resource_name, 'get')
|
admin_context, self.resource_name, 'get')
|
||||||
|
|
||||||
def test_cg_show_cg_not_found(self):
|
def test_cg_show_cg_not_found(self):
|
||||||
fake_cg, expected_cg = self._get_fake_cg()
|
|
||||||
self.mock_object(cg_api.API, 'get',
|
|
||||||
mock.Mock(side_effect=exception.NotFound))
|
|
||||||
req = fakes.HTTPRequest.blank(
|
req = fakes.HTTPRequest.blank(
|
||||||
'/consistency_groups/%s' % fake_cg['id'],
|
'/consistency_groups/myfakecg',
|
||||||
version=self.api_version, experimental=True)
|
version=self.api_version, experimental=True)
|
||||||
req_context = req.environ['manila.context']
|
req_context = req.environ['manila.context']
|
||||||
|
fake_cg, expected_cg = self._get_fake_cg(
|
||||||
|
ctxt=req_context, id='myfakecg')
|
||||||
|
self.mock_object(cg_api.API, 'get',
|
||||||
|
mock.Mock(side_effect=exception.NotFound))
|
||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
|
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
|
||||||
req, fake_cg['id'])
|
req, fake_cg['id'])
|
||||||
|
@ -45,9 +45,15 @@ def fake_cg(id, **kwargs):
|
|||||||
'host': None,
|
'host': None,
|
||||||
'source_cgsnapshot_id': None,
|
'source_cgsnapshot_id': None,
|
||||||
'share_network_id': None,
|
'share_network_id': None,
|
||||||
|
'share_server_id': None,
|
||||||
'share_types': None,
|
'share_types': None,
|
||||||
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
'created_at': datetime.datetime(1, 1, 1, 1, 1, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if 'source_cgsnapshot_id' in kwargs:
|
||||||
|
cg['share_network_id'] = 'fake_share_network_id'
|
||||||
|
cg['share_server_id'] = 'fake_share_server_id'
|
||||||
|
|
||||||
cg.update(kwargs)
|
cg.update(kwargs)
|
||||||
return cg
|
return cg
|
||||||
|
|
||||||
@ -89,6 +95,7 @@ class CGAPITestCase(test.TestCase):
|
|||||||
user_id=self.context.user_id,
|
user_id=self.context.user_id,
|
||||||
project_id=self.context.project_id,
|
project_id=self.context.project_id,
|
||||||
status=constants.STATUS_CREATING)
|
status=constants.STATUS_CREATING)
|
||||||
|
|
||||||
expected_values = cg.copy()
|
expected_values = cg.copy()
|
||||||
for name in ('id', 'host', 'created_at'):
|
for name in ('id', 'host', 'created_at'):
|
||||||
expected_values.pop(name, None)
|
expected_values.pop(name, None)
|
||||||
@ -283,19 +290,26 @@ class CGAPITestCase(test.TestCase):
|
|||||||
project_id=self.context.project_id,
|
project_id=self.context.project_id,
|
||||||
share_types=[fake_share_type_mapping],
|
share_types=[fake_share_type_mapping],
|
||||||
status=constants.STATUS_AVAILABLE,
|
status=constants.STATUS_AVAILABLE,
|
||||||
host='fake_original_host')
|
host='fake_original_host',
|
||||||
|
share_network_id='fake_network_id',
|
||||||
|
share_server_id='fake_server_id')
|
||||||
|
|
||||||
cg = fake_cg('fakeid',
|
cg = fake_cg('fakeid',
|
||||||
user_id=self.context.user_id,
|
user_id=self.context.user_id,
|
||||||
project_id=self.context.project_id,
|
project_id=self.context.project_id,
|
||||||
share_types=[fake_share_type_mapping],
|
share_types=[fake_share_type_mapping],
|
||||||
status=constants.STATUS_CREATING,
|
status=constants.STATUS_CREATING,
|
||||||
host='fake_original_host')
|
host='fake_original_host',
|
||||||
|
share_network_id='fake_network_id',
|
||||||
|
share_server_id='fake_server_id')
|
||||||
expected_values = cg.copy()
|
expected_values = cg.copy()
|
||||||
for name in ('id', 'created_at'):
|
for name in ('id', 'created_at', 'share_network_id',
|
||||||
|
'share_server_id'):
|
||||||
expected_values.pop(name, None)
|
expected_values.pop(name, None)
|
||||||
expected_values['source_cgsnapshot_id'] = snap['id']
|
expected_values['source_cgsnapshot_id'] = snap['id']
|
||||||
expected_values['share_types'] = ["fake_share_type_id"]
|
expected_values['share_types'] = ["fake_share_type_id"]
|
||||||
|
expected_values['share_network_id'] = 'fake_network_id'
|
||||||
|
expected_values['share_server_id'] = 'fake_server_id'
|
||||||
|
|
||||||
self.mock_object(db_driver, 'cgsnapshot_get',
|
self.mock_object(db_driver, 'cgsnapshot_get',
|
||||||
mock.Mock(return_value=snap))
|
mock.Mock(return_value=snap))
|
||||||
@ -327,18 +341,25 @@ class CGAPITestCase(test.TestCase):
|
|||||||
user_id=self.context.user_id,
|
user_id=self.context.user_id,
|
||||||
project_id=self.context.project_id,
|
project_id=self.context.project_id,
|
||||||
share_types=[fake_share_type_mapping],
|
share_types=[fake_share_type_mapping],
|
||||||
status=constants.STATUS_AVAILABLE)
|
status=constants.STATUS_AVAILABLE,
|
||||||
|
share_network_id='fake_network_id',
|
||||||
|
share_server_id='fake_server_id')
|
||||||
|
|
||||||
cg = fake_cg('fakeid',
|
cg = fake_cg('fakeid',
|
||||||
user_id=self.context.user_id,
|
user_id=self.context.user_id,
|
||||||
project_id=self.context.project_id,
|
project_id=self.context.project_id,
|
||||||
share_types=[fake_share_type_mapping],
|
share_types=[fake_share_type_mapping],
|
||||||
status=constants.STATUS_CREATING)
|
status=constants.STATUS_CREATING,
|
||||||
|
share_network_id='fake_network_id',
|
||||||
|
share_server_id='fake_server_id')
|
||||||
expected_values = cg.copy()
|
expected_values = cg.copy()
|
||||||
for name in ('id', 'created_at'):
|
for name in ('id', 'created_at', 'fake_network_id',
|
||||||
|
'fake_share_server_id'):
|
||||||
expected_values.pop(name, None)
|
expected_values.pop(name, None)
|
||||||
expected_values['source_cgsnapshot_id'] = snap['id']
|
expected_values['source_cgsnapshot_id'] = snap['id']
|
||||||
expected_values['share_types'] = ["fake_share_type_id"]
|
expected_values['share_types'] = ["fake_share_type_id"]
|
||||||
|
expected_values['share_network_id'] = 'fake_network_id'
|
||||||
|
expected_values['share_server_id'] = 'fake_server_id'
|
||||||
|
|
||||||
self.mock_object(db_driver, 'cgsnapshot_get',
|
self.mock_object(db_driver, 'cgsnapshot_get',
|
||||||
mock.Mock(return_value=snap))
|
mock.Mock(return_value=snap))
|
||||||
@ -375,18 +396,25 @@ class CGAPITestCase(test.TestCase):
|
|||||||
user_id=self.context.user_id,
|
user_id=self.context.user_id,
|
||||||
project_id=self.context.project_id,
|
project_id=self.context.project_id,
|
||||||
share_types=[fake_share_type_mapping],
|
share_types=[fake_share_type_mapping],
|
||||||
status=constants.STATUS_AVAILABLE)
|
status=constants.STATUS_AVAILABLE,
|
||||||
|
share_network_id='fake_network_id',
|
||||||
|
share_server_id='fake_server_id')
|
||||||
|
|
||||||
cg = fake_cg('fakeid',
|
cg = fake_cg('fakeid',
|
||||||
user_id=self.context.user_id,
|
user_id=self.context.user_id,
|
||||||
project_id=self.context.project_id,
|
project_id=self.context.project_id,
|
||||||
share_types=[fake_share_type_mapping],
|
share_types=[fake_share_type_mapping],
|
||||||
status=constants.STATUS_CREATING)
|
status=constants.STATUS_CREATING,
|
||||||
|
share_network_id='fake_network_id',
|
||||||
|
share_server_id='fake_server_id')
|
||||||
expected_values = cg.copy()
|
expected_values = cg.copy()
|
||||||
for name in ('id', 'created_at'):
|
for name in ('id', 'created_at', 'share_network_id',
|
||||||
|
'share_server_id'):
|
||||||
expected_values.pop(name, None)
|
expected_values.pop(name, None)
|
||||||
expected_values['source_cgsnapshot_id'] = snap['id']
|
expected_values['source_cgsnapshot_id'] = snap['id']
|
||||||
expected_values['share_types'] = ["fake_share_type_id"]
|
expected_values['share_types'] = ["fake_share_type_id"]
|
||||||
|
expected_values['share_network_id'] = 'fake_network_id'
|
||||||
|
expected_values['share_server_id'] = 'fake_server_id'
|
||||||
|
|
||||||
self.mock_object(db_driver, 'cgsnapshot_get',
|
self.mock_object(db_driver, 'cgsnapshot_get',
|
||||||
mock.Mock(return_value=snap))
|
mock.Mock(return_value=snap))
|
||||||
|
@ -54,11 +54,16 @@ def fake_share_instance(base_share=None, **kwargs):
|
|||||||
'share_id': share['id'],
|
'share_id': share['id'],
|
||||||
'id': "fakeinstanceid",
|
'id': "fakeinstanceid",
|
||||||
'status': "active",
|
'status': "active",
|
||||||
|
'host': 'fakehost',
|
||||||
|
'share_network_id': 'fakesharenetworkid',
|
||||||
|
'share_server_id': 'fakeshareserverid',
|
||||||
}
|
}
|
||||||
|
|
||||||
for attr in models.ShareInstance._proxified_properties:
|
for attr in models.ShareInstance._proxified_properties:
|
||||||
share_instance[attr] = getattr(share, attr, None)
|
share_instance[attr] = getattr(share, attr, None)
|
||||||
|
|
||||||
|
share_instance.update(kwargs)
|
||||||
|
|
||||||
return db_fakes.FakeModel(share_instance)
|
return db_fakes.FakeModel(share_instance)
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ from manila.data import rpcapi as data_rpc
|
|||||||
from manila import db as db_api
|
from manila import db as db_api
|
||||||
from manila.db.sqlalchemy import models
|
from manila.db.sqlalchemy import models
|
||||||
from manila import exception
|
from manila import exception
|
||||||
|
from manila import policy
|
||||||
from manila import quota
|
from manila import quota
|
||||||
from manila import share
|
from manila import share
|
||||||
from manila.share import api as share_api
|
from manila.share import api as share_api
|
||||||
@ -728,6 +729,42 @@ class ShareAPITestCase(test.TestCase):
|
|||||||
self.context, request_spec=mock.ANY, filter_properties={})
|
self.context, request_spec=mock.ANY, filter_properties={})
|
||||||
self.assertFalse(self.api.share_rpcapi.create_share_instance.called)
|
self.assertFalse(self.api.share_rpcapi.create_share_instance.called)
|
||||||
|
|
||||||
|
def test_create_instance_cgsnapshot_member(self):
|
||||||
|
fake_req_spec = {
|
||||||
|
'share_properties': 'fake_share_properties',
|
||||||
|
'share_instance_properties': 'fake_share_instance_properties',
|
||||||
|
}
|
||||||
|
share = fakes.fake_share()
|
||||||
|
member_info = {
|
||||||
|
'host': 'host',
|
||||||
|
'share_network_id': 'share_network_id',
|
||||||
|
'share_server_id': 'share_server_id',
|
||||||
|
}
|
||||||
|
|
||||||
|
fake_instance = fakes.fake_share_instance(
|
||||||
|
share_id=share['id'], **member_info)
|
||||||
|
cgsnapmember = {'share_instance': fake_instance}
|
||||||
|
self.mock_policy_check = self.mock_object(
|
||||||
|
policy, 'check_policy', mock.Mock(return_value=True))
|
||||||
|
mock_share_rpcapi_call = self.mock_object(self.share_rpcapi,
|
||||||
|
'create_share_instance')
|
||||||
|
mock_scheduler_rpcapi_call = self.mock_object(self.scheduler_rpcapi,
|
||||||
|
'create_share_instance')
|
||||||
|
mock_db_share_instance_update = self.mock_object(
|
||||||
|
db_api, 'share_instance_update')
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API, '_create_share_instance_and_get_request_spec',
|
||||||
|
mock.Mock(return_value=(fake_req_spec, fake_instance)))
|
||||||
|
|
||||||
|
retval = self.api.create_instance(self.context, fake_share,
|
||||||
|
cgsnapshot_member=cgsnapmember)
|
||||||
|
|
||||||
|
self.assertIsNone(retval)
|
||||||
|
mock_db_share_instance_update.assert_called_once_with(
|
||||||
|
self.context, fake_instance['id'], member_info)
|
||||||
|
self.assertFalse(mock_scheduler_rpcapi_call.called)
|
||||||
|
self.assertFalse(mock_share_rpcapi_call.called)
|
||||||
|
|
||||||
@ddt.data('dr', 'readable', None)
|
@ddt.data('dr', 'readable', None)
|
||||||
def test_manage_new(self, replication_type):
|
def test_manage_new(self, replication_type):
|
||||||
share_data = {
|
share_data = {
|
||||||
|
@ -43,6 +43,8 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
|
|||||||
# Create a consistency group
|
# Create a consistency group
|
||||||
cls.consistency_group = cls.create_consistency_group(
|
cls.consistency_group = cls.create_consistency_group(
|
||||||
share_type_ids=[cls.share_type['id'], cls.share_type2['id']])
|
share_type_ids=[cls.share_type['id'], cls.share_type2['id']])
|
||||||
|
cls.consistency_group = cls.shares_v2_client.get_consistency_group(
|
||||||
|
cls.consistency_group['id'])
|
||||||
|
|
||||||
@test.attr(type=["gate", ])
|
@test.attr(type=["gate", ])
|
||||||
def test_create_cg_from_cgsnapshot_with_multiple_share_types_v2_4(self):
|
def test_create_cg_from_cgsnapshot_with_multiple_share_types_v2_4(self):
|
||||||
@ -103,9 +105,13 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
|
|||||||
version='2.4',
|
version='2.4',
|
||||||
)
|
)
|
||||||
|
|
||||||
self.create_consistency_group(cleanup_in_class=False,
|
new_cg = self.create_consistency_group(
|
||||||
source_cgsnapshot_id=cgsnapshot['id'],
|
cleanup_in_class=False, source_cgsnapshot_id=cgsnapshot['id'],
|
||||||
version='2.4')
|
version='2.4')
|
||||||
|
new_cg_shares = self.shares_v2_client.list_shares(
|
||||||
|
detailed=True,
|
||||||
|
params={'consistency_group_id': new_cg['id']},
|
||||||
|
version='2.4')
|
||||||
|
|
||||||
# TODO(akerr): Skip until bug 1483886 is resolved
|
# TODO(akerr): Skip until bug 1483886 is resolved
|
||||||
# Verify that the new shares correspond to correct share types
|
# Verify that the new shares correspond to correct share types
|
||||||
@ -117,3 +123,29 @@ class ConsistencyGroupActionsTest(base.BaseSharesAdminTest):
|
|||||||
# 'Expected shares of types %s, got %s.' % (
|
# 'Expected shares of types %s, got %s.' % (
|
||||||
# sorted(expected_share_types),
|
# sorted(expected_share_types),
|
||||||
# sorted(actual_share_types)))
|
# sorted(actual_share_types)))
|
||||||
|
|
||||||
|
# Ensure that share_server information of the child CG and associated
|
||||||
|
# shares match with that of the parent CG
|
||||||
|
self.assertEqual(self.consistency_group['share_network_id'],
|
||||||
|
new_cg['share_network_id'])
|
||||||
|
self.assertEqual(self.consistency_group['share_server_id'],
|
||||||
|
new_cg['share_server_id'])
|
||||||
|
|
||||||
|
for share in new_cg_shares:
|
||||||
|
msg = ('Share %(share)s has %(attr)s=%(value)s and it does not '
|
||||||
|
'match that of the parent CG where %(attr)s=%(orig)s.')
|
||||||
|
payload = {
|
||||||
|
'share': share['id'],
|
||||||
|
'attr': 'share_network_id',
|
||||||
|
'value': share['share_network_id'],
|
||||||
|
'orig': self.consistency_group['share_network_id'],
|
||||||
|
}
|
||||||
|
self.assertEqual(self.consistency_group['share_network_id'],
|
||||||
|
share['share_network_id'], msg % payload)
|
||||||
|
|
||||||
|
payload.update({'attr': 'share_server_id',
|
||||||
|
'value': share['share_server_id'],
|
||||||
|
'orig': self.consistency_group['share_server_id'],
|
||||||
|
})
|
||||||
|
self.assertEqual(self.consistency_group['share_server_id'],
|
||||||
|
share['share_server_id'], msg % payload)
|
||||||
|
@ -66,3 +66,33 @@ class ConsistencyGroupsTest(base.BaseSharesAdminTest):
|
|||||||
'%s. Expected %s, got %s' % (consistency_group['id'],
|
'%s. Expected %s, got %s' % (consistency_group['id'],
|
||||||
expected_share_types,
|
expected_share_types,
|
||||||
actual_share_types))
|
actual_share_types))
|
||||||
|
|
||||||
|
@testtools.skipIf(
|
||||||
|
not CONF.share.multitenancy_enabled, "Only for multitenancy.")
|
||||||
|
def test_create_cg_from_cgsnapshot_verify_share_server_information(self):
|
||||||
|
# Create a consistency group
|
||||||
|
orig_consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False,
|
||||||
|
share_type_ids=[self.share_type['id']],
|
||||||
|
version='2.4')
|
||||||
|
|
||||||
|
# Get latest CG information
|
||||||
|
orig_consistency_group = self.shares_v2_client.get_consistency_group(
|
||||||
|
orig_consistency_group['id'], version='2.4')
|
||||||
|
|
||||||
|
# Assert share server information
|
||||||
|
self.assertIsNotNone(orig_consistency_group['share_network_id'])
|
||||||
|
self.assertIsNotNone(orig_consistency_group['share_server_id'])
|
||||||
|
|
||||||
|
cg_snapshot = self.create_cgsnapshot_wait_for_active(
|
||||||
|
orig_consistency_group['id'], cleanup_in_class=False,
|
||||||
|
version='2.4')
|
||||||
|
new_consistency_group = self.create_consistency_group(
|
||||||
|
cleanup_in_class=False, version='2.4',
|
||||||
|
source_cgsnapshot_id=cg_snapshot['id'])
|
||||||
|
|
||||||
|
# Assert share server information
|
||||||
|
self.assertEqual(orig_consistency_group['share_network_id'],
|
||||||
|
new_consistency_group['share_network_id'])
|
||||||
|
self.assertEqual(orig_consistency_group['share_server_id'],
|
||||||
|
new_consistency_group['share_server_id'])
|
||||||
|
@ -431,8 +431,9 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
def create_consistency_group(cls, client=None, cleanup_in_class=True,
|
def create_consistency_group(cls, client=None, cleanup_in_class=True,
|
||||||
share_network_id=None, **kwargs):
|
share_network_id=None, **kwargs):
|
||||||
client = client or cls.shares_v2_client
|
client = client or cls.shares_v2_client
|
||||||
kwargs['share_network_id'] = (share_network_id or
|
if kwargs.get('source_cgsnapshot_id') is None:
|
||||||
client.share_network_id or None)
|
kwargs['share_network_id'] = (share_network_id or
|
||||||
|
client.share_network_id or None)
|
||||||
consistency_group = client.create_consistency_group(**kwargs)
|
consistency_group = client.create_consistency_group(**kwargs)
|
||||||
resource = {
|
resource = {
|
||||||
"type": "consistency_group",
|
"type": "consistency_group",
|
||||||
|
@ -263,6 +263,13 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
|
|||||||
version='2.4'
|
version='2.4'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
new_consistency_group = self.shares_v2_client.get_consistency_group(
|
||||||
|
new_consistency_group['id'], version='2.4')
|
||||||
|
|
||||||
|
# Verify that share_network information matches source CG
|
||||||
|
self.assertEqual(self.cg['share_network_id'],
|
||||||
|
new_consistency_group['share_network_id'])
|
||||||
|
|
||||||
new_shares = self.shares_v2_client.list_shares(
|
new_shares = self.shares_v2_client.list_shares(
|
||||||
params={'consistency_group_id': new_consistency_group['id']},
|
params={'consistency_group_id': new_consistency_group['id']},
|
||||||
detailed=True,
|
detailed=True,
|
||||||
@ -292,6 +299,8 @@ class ConsistencyGroupActionsTest(base.BaseSharesTest):
|
|||||||
# TODO(akerr): Add back assert when bug 1483886 is fixed
|
# TODO(akerr): Add back assert when bug 1483886 is fixed
|
||||||
# self.assertEqual(member['share_type_id'],
|
# self.assertEqual(member['share_type_id'],
|
||||||
# share['share_type'])
|
# share['share_type'])
|
||||||
|
self.assertEqual(self.cg['share_network_id'],
|
||||||
|
share['share_network_id'])
|
||||||
|
|
||||||
|
|
||||||
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
@testtools.skipUnless(CONF.share.run_consistency_group_tests,
|
||||||
|
@ -125,7 +125,7 @@ class ConsistencyGroupsTest(base.BaseSharesTest):
|
|||||||
self.assertEmpty(new_shares,
|
self.assertEmpty(new_shares,
|
||||||
'Expected 0 new shares, got %s' % len(new_shares))
|
'Expected 0 new shares, got %s' % len(new_shares))
|
||||||
|
|
||||||
msg = 'Expected cgsnapshot_id %s as source of share %s' % (
|
msg = 'Expected cgsnapshot_id %s as source of consistency group %s' % (
|
||||||
cgsnapshot['id'], new_consistency_group['source_cgsnapshot_id'])
|
cgsnapshot['id'], new_consistency_group['source_cgsnapshot_id'])
|
||||||
self.assertEqual(new_consistency_group['source_cgsnapshot_id'],
|
self.assertEqual(new_consistency_group['source_cgsnapshot_id'],
|
||||||
cgsnapshot['id'], msg)
|
cgsnapshot['id'], msg)
|
||||||
@ -135,3 +135,11 @@ class ConsistencyGroupsTest(base.BaseSharesTest):
|
|||||||
new_consistency_group['share_types']))
|
new_consistency_group['share_types']))
|
||||||
self.assertEqual(sorted(consistency_group['share_types']),
|
self.assertEqual(sorted(consistency_group['share_types']),
|
||||||
sorted(new_consistency_group['share_types']), msg)
|
sorted(new_consistency_group['share_types']), msg)
|
||||||
|
|
||||||
|
# Assert the share_network information is the same
|
||||||
|
msg = 'Expected share_network %s as share_network of cg %s' % (
|
||||||
|
consistency_group['share_network_id'],
|
||||||
|
new_consistency_group['share_network_id'])
|
||||||
|
self.assertEqual(consistency_group['share_network_id'],
|
||||||
|
new_consistency_group['share_network_id'],
|
||||||
|
msg)
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- Consistency Group APIs return share_server_id information correctly to
|
||||||
|
administrators.
|
||||||
|
- When using a consistency group snapshot to create another consistency
|
||||||
|
group, share server and network information is persisted from the source
|
||||||
|
consistency group to the new consistency group.
|
Loading…
Reference in New Issue
Block a user