Merge "XtremIO volume driver consistency group support"
This commit is contained in:
commit
86baaff849
@ -13,11 +13,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import mock
|
||||
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
from cinder.tests.unit import fake_snapshot
|
||||
from cinder.volume.drivers.emc import xtremio
|
||||
|
||||
|
||||
@ -25,55 +25,67 @@ typ2id = {'volumes': 'vol-id',
|
||||
'snapshots': 'vol-id',
|
||||
'initiators': 'initiator-id',
|
||||
'initiator-groups': 'ig-id',
|
||||
'lun-maps': 'mapping-id'}
|
||||
'lun-maps': 'mapping-id',
|
||||
'consistency-groups': 'cg-id',
|
||||
'consistency-group-volumes': 'cg-vol-id',
|
||||
}
|
||||
|
||||
xms_data = {'xms': {1: {'version': '4.0.0'}},
|
||||
'clusters': {'cluster1':
|
||||
{'name': 'cluster1',
|
||||
'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73",
|
||||
'ud-ssd-space': '8146708710',
|
||||
'ud-ssd-space-in-use': '708710',
|
||||
'vol-size': '29884416',
|
||||
'chap-authentication-mode': 'disabled',
|
||||
'chap-discovery-mode': 'disabled',
|
||||
"index": 1},
|
||||
1: {'name': 'cluster1',
|
||||
'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73",
|
||||
'clusters': {1: {'name': 'brick1',
|
||||
'sys-sw-version': "4.0.0-devel_ba23ee5381eeab73",
|
||||
'ud-ssd-space': '8146708710',
|
||||
'ud-ssd-space-in-use': '708710',
|
||||
'vol-size': '29884416',
|
||||
'chap-authentication-mode': 'disabled',
|
||||
'chap-discovery-mode': 'disabled',
|
||||
"index": 1}},
|
||||
'target-groups': {'Default': {"index": 1, }},
|
||||
"index": 1,
|
||||
},
|
||||
},
|
||||
'target-groups': {'Default': {"index": 1, },
|
||||
},
|
||||
'iscsi-portals': {'10.205.68.5/16':
|
||||
{"port-address":
|
||||
"iqn.2008-05.com.xtremio:001e67939c34",
|
||||
"ip-port": 3260,
|
||||
"ip-addr": "10.205.68.5/16",
|
||||
"name": "10.205.68.5/16",
|
||||
"index": 1}},
|
||||
"index": 1,
|
||||
},
|
||||
},
|
||||
'targets': {'X1-SC2-fc1': {'index': 1, "name": "X1-SC2-fc1",
|
||||
"port-address":
|
||||
"21:00:00:24:ff:57:b2:36",
|
||||
'port-state': 'up'},
|
||||
'port-state': 'up',
|
||||
},
|
||||
'X1-SC2-fc2': {'index': 2, "name": "X1-SC2-fc2",
|
||||
"port-address":
|
||||
"21:00:00:24:ff:57:b2:55",
|
||||
'port-state': 'up'}
|
||||
'port-state': 'up',
|
||||
}
|
||||
},
|
||||
'volumes': {},
|
||||
'initiator-groups': {},
|
||||
'initiators': {},
|
||||
'lun-maps': {},
|
||||
'consistency-groups': {},
|
||||
'consistency-group-volumes': {},
|
||||
}
|
||||
|
||||
|
||||
def get_xms_obj_by_name(typ, name):
|
||||
for item in xms_data[typ].values():
|
||||
if 'name' in item and item['name'] == name:
|
||||
return item
|
||||
raise exception.NotFound()
|
||||
|
||||
|
||||
def clean_xms_data():
|
||||
xms_data['volumes'] = {}
|
||||
xms_data['initiator-groups'] = {}
|
||||
xms_data['initiators'] = {}
|
||||
xms_data['lun-maps'] = {}
|
||||
xms_data['consistency-group-volumes'] = {}
|
||||
xms_data['consistency-groups'] = {}
|
||||
|
||||
|
||||
def fix_data(data, object_type):
|
||||
@ -88,7 +100,7 @@ def fix_data(data, object_type):
|
||||
|
||||
d[typ2id[object_type]] = ["a91e8c81c2d14ae4865187ce4f866f8a",
|
||||
d.get('name'),
|
||||
len(xms_data[object_type]) + 1]
|
||||
len(xms_data.get(object_type, [])) + 1]
|
||||
d['index'] = len(xms_data[object_type]) + 1
|
||||
return d
|
||||
|
||||
@ -99,24 +111,30 @@ def get_xms_obj_key(data):
|
||||
return key
|
||||
|
||||
|
||||
def get_obj(typ, name, idx):
|
||||
if name:
|
||||
return {"content": get_xms_obj_by_name(typ, name)}
|
||||
elif idx:
|
||||
if idx not in xms_data.get(typ, {}):
|
||||
raise exception.NotFound()
|
||||
return {"content": xms_data[typ][idx]}
|
||||
|
||||
|
||||
def xms_request(object_type='volumes', request_typ='GET', data=None,
|
||||
name=None, idx=None):
|
||||
name=None, idx=None, ver='v1'):
|
||||
if object_type == 'snapshots':
|
||||
object_type = 'volumes'
|
||||
|
||||
obj_key = name if name else idx
|
||||
try:
|
||||
res = xms_data[object_type]
|
||||
except KeyError:
|
||||
raise exception.VolumeDriverException
|
||||
if request_typ == 'GET':
|
||||
try:
|
||||
res = xms_data[object_type]
|
||||
except KeyError:
|
||||
raise exception.VolumeDriverException
|
||||
if name or idx:
|
||||
if obj_key not in res:
|
||||
raise exception.NotFound()
|
||||
return {"content": res[obj_key]}
|
||||
return get_obj(object_type, name, idx)
|
||||
else:
|
||||
if data and data.get('full') == 1:
|
||||
return {object_type: res.values()}
|
||||
return {object_type: list(res.values())}
|
||||
else:
|
||||
return {object_type: [{"href": "/%s/%d" % (object_type,
|
||||
obj['index']),
|
||||
@ -124,41 +142,45 @@ def xms_request(object_type='volumes', request_typ='GET', data=None,
|
||||
for obj in res.values()]}
|
||||
elif request_typ == 'POST':
|
||||
data = fix_data(data, object_type)
|
||||
data['index'] = len(xms_data[object_type]) + 1
|
||||
xms_data[object_type][data['index']] = data
|
||||
# find the name key
|
||||
name_key = get_xms_obj_key(data)
|
||||
if object_type == 'lun-maps':
|
||||
data['ig-name'] = data['ig-id']
|
||||
if name_key:
|
||||
if data[name_key] in xms_data[object_type]:
|
||||
try:
|
||||
if name_key and get_xms_obj_by_name(object_type, data[name_key]):
|
||||
raise (exception
|
||||
.VolumeBackendAPIException
|
||||
('Volume by this name already exists'))
|
||||
xms_data[object_type][data[name_key]] = data
|
||||
except exception.NotFound:
|
||||
pass
|
||||
data['index'] = len(xms_data[object_type]) + 1
|
||||
xms_data[object_type][data['index']] = data
|
||||
# find the name key
|
||||
if name_key:
|
||||
data['name'] = data[name_key]
|
||||
if object_type == 'lun-maps':
|
||||
data['ig-name'] = data['ig-id']
|
||||
|
||||
return {"links": [{"href": "/%s/%d" %
|
||||
(object_type, data[typ2id[object_type]][2])}]}
|
||||
elif request_typ == 'DELETE':
|
||||
if obj_key in xms_data[object_type]:
|
||||
data = xms_data[object_type][obj_key]
|
||||
if object_type == 'consistency-group-volumes':
|
||||
data = [cgv for cgv in
|
||||
xms_data['consistency-group-volumes'].values()
|
||||
if cgv['vol-id'] == data['vol-id']
|
||||
and cgv['cg-id'] == data['cg-id']][0]
|
||||
else:
|
||||
data = get_obj(object_type, name, idx)['content']
|
||||
if data:
|
||||
del xms_data[object_type][data['index']]
|
||||
del xms_data[object_type][data[typ2id[object_type]][1]]
|
||||
else:
|
||||
raise exception.NotFound()
|
||||
elif request_typ == 'PUT':
|
||||
if obj_key in xms_data[object_type]:
|
||||
obj = xms_data[object_type][obj_key]
|
||||
obj.update(data)
|
||||
key = get_xms_obj_key(data)
|
||||
if key:
|
||||
xms_data[object_type][data[key]] = obj
|
||||
else:
|
||||
raise exception.NotFound()
|
||||
obj = get_obj(object_type, name, idx)['content']
|
||||
data = fix_data(data, object_type)
|
||||
del data['index']
|
||||
obj.update(data)
|
||||
|
||||
|
||||
def xms_bad_request(object_type='volumes', request_typ='GET', data=None,
|
||||
name=None, idx=None):
|
||||
name=None, idx=None, ver='v1'):
|
||||
if request_typ == 'GET':
|
||||
raise exception.NotFound()
|
||||
elif request_typ == 'POST':
|
||||
@ -167,7 +189,7 @@ def xms_bad_request(object_type='volumes', request_typ='GET', data=None,
|
||||
|
||||
def xms_failed_rename_snapshot_request(object_type='volumes',
|
||||
request_typ='GET', data=None,
|
||||
name=None, idx=None):
|
||||
name=None, idx=None, ver='v1'):
|
||||
if request_typ == 'POST':
|
||||
xms_data['volumes'][27] = {}
|
||||
return {
|
||||
@ -176,7 +198,7 @@ def xms_failed_rename_snapshot_request(object_type='volumes',
|
||||
"href": "https://host/api/json/v2/types/snapshots/27",
|
||||
"rel": "self"}]}
|
||||
elif request_typ == 'PUT':
|
||||
raise exception.VolumeBackendAPIException(msg='Failed to delete')
|
||||
raise exception.VolumeBackendAPIException(data='Failed to delete')
|
||||
elif request_typ == 'DELETE':
|
||||
del xms_data['volumes'][27]
|
||||
|
||||
@ -192,7 +214,8 @@ class CommonData(object):
|
||||
'initiator': 'iqn.1993-08.org.debian:01:222',
|
||||
'wwpns': ["123456789012345", "123456789054321"],
|
||||
'wwnns': ["223456789012345", "223456789054321"],
|
||||
'host': 'fakehost'}
|
||||
'host': 'fakehost',
|
||||
}
|
||||
|
||||
test_volume = {'name': 'vol1',
|
||||
'size': 1,
|
||||
@ -202,14 +225,20 @@ class CommonData(object):
|
||||
'project_id': 'project',
|
||||
'display_name': 'vol1',
|
||||
'display_description': 'test volume',
|
||||
'volume_type_id': None}
|
||||
'volume_type_id': None,
|
||||
'consistencygroup_id':
|
||||
'192eb39b-6c2f-420c-bae3-3cfd117f0345',
|
||||
}
|
||||
test_snapshot = D()
|
||||
test_snapshot.update({'name': 'snapshot1',
|
||||
'size': 1,
|
||||
'id': '192eb39b-6c2f-420c-bae3-3cfd117f0002',
|
||||
'volume_name': 'vol-vol1',
|
||||
'volume_id': '192eb39b-6c2f-420c-bae3-3cfd117f0001',
|
||||
'project_id': 'project'})
|
||||
'project_id': 'project',
|
||||
'consistencygroup_id':
|
||||
'192eb39b-6c2f-420c-bae3-3cfd117f0345',
|
||||
})
|
||||
test_snapshot.__dict__.update(test_snapshot)
|
||||
test_volume2 = {'name': 'vol2',
|
||||
'size': 1,
|
||||
@ -219,7 +248,10 @@ class CommonData(object):
|
||||
'project_id': 'project',
|
||||
'display_name': 'vol2',
|
||||
'display_description': 'test volume 2',
|
||||
'volume_type_id': None}
|
||||
'volume_type_id': None,
|
||||
'consistencygroup_id':
|
||||
'192eb39b-6c2f-420c-bae3-3cfd117f0345',
|
||||
}
|
||||
test_clone = {'name': 'clone1',
|
||||
'size': 1,
|
||||
'volume_name': 'vol3',
|
||||
@ -228,30 +260,49 @@ class CommonData(object):
|
||||
'project_id': 'project',
|
||||
'display_name': 'clone1',
|
||||
'display_description': 'volume created from snapshot',
|
||||
'volume_type_id': None}
|
||||
'volume_type_id': None,
|
||||
'consistencygroup_id':
|
||||
'192eb39b-6c2f-420c-bae3-3cfd117f0345',
|
||||
}
|
||||
unmanaged1 = {'id': 'unmanaged1',
|
||||
'name': 'unmanaged1',
|
||||
'size': 3}
|
||||
'size': 3,
|
||||
}
|
||||
context = {'user': 'admin', }
|
||||
group = {'id': '192eb39b-6c2f-420c-bae3-3cfd117f0345',
|
||||
'name': 'cg1',
|
||||
'status': 'OK',
|
||||
}
|
||||
cgsnapshot = mock.Mock(id='192eb39b-6c2f-420c-bae3-3cfd117f9876',
|
||||
consistencygroup_id=group['id'])
|
||||
|
||||
def cgsnap_getitem(self, val):
|
||||
return self.__dict__[val]
|
||||
|
||||
cgsnapshot.__getitem__ = cgsnap_getitem
|
||||
|
||||
|
||||
@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req')
|
||||
class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(EMCXIODriverISCSITestCase, self).setUp()
|
||||
clean_xms_data()
|
||||
|
||||
configuration = mock.Mock()
|
||||
configuration.san_login = ''
|
||||
configuration.san_password = ''
|
||||
configuration.san_ip = ''
|
||||
configuration.xtremio_cluster_name = ''
|
||||
configuration.xtremio_provisioning_factor = 20.0
|
||||
config = mock.Mock()
|
||||
config.san_login = ''
|
||||
config.san_password = ''
|
||||
config.san_ip = ''
|
||||
config.xtremio_cluster_name = 'brick1'
|
||||
config.xtremio_provisioning_factor = 20.0
|
||||
|
||||
def safe_get(key):
|
||||
getattr(configuration, key)
|
||||
|
||||
configuration.safe_get = safe_get
|
||||
self.driver = xtremio.XtremIOISCSIDriver(configuration=configuration)
|
||||
getattr(config, key)
|
||||
|
||||
config.safe_get = safe_get
|
||||
self.driver = xtremio.XtremIOISCSIDriver(configuration=config)
|
||||
self.driver.client = xtremio.XtremIOClient4(config,
|
||||
config
|
||||
.xtremio_cluster_name)
|
||||
self.data = CommonData()
|
||||
|
||||
def test_check_for_setup_error(self, req):
|
||||
@ -265,30 +316,28 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
|
||||
def test_create_extend_delete_volume(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
self.driver.create_volume(self.data.test_volume)
|
||||
self.driver.extend_volume(self.data.test_volume, 5)
|
||||
self.driver.delete_volume(self.data.test_volume)
|
||||
|
||||
def test_create_delete_snapshot(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
self.driver.create_volume(self.data.test_volume)
|
||||
self.driver.create_snapshot(self.data.test_snapshot)
|
||||
self.assertEqual(self.data.test_snapshot['id'],
|
||||
xms_data['volumes'][3]['name'])
|
||||
xms_data['volumes'][2]['name'])
|
||||
self.driver.delete_snapshot(self.data.test_snapshot)
|
||||
self.driver.delete_volume(self.data.test_volume)
|
||||
|
||||
def test_failed_rename_snapshot(self, req):
|
||||
req.side_effect = xms_failed_rename_snapshot_request
|
||||
self.driver.create_snapshot(self.data.test_snapshot)
|
||||
self.assertIn(27, xms_data['volumes'])
|
||||
clean_xms_data()
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.create_snapshot,
|
||||
self.data.test_snapshot)
|
||||
self.assertEqual(0, len(xms_data['volumes']))
|
||||
|
||||
def test_volume_from_snapshot(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
xms_data['volumes'] = {}
|
||||
self.driver.create_volume(self.data.test_volume)
|
||||
self.driver.create_snapshot(self.data.test_snapshot)
|
||||
@ -300,7 +349,6 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
|
||||
def test_clone_volume(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
self.driver.create_volume(self.data.test_volume)
|
||||
self.driver.create_cloned_volume(self.data.test_clone,
|
||||
self.data.test_volume)
|
||||
@ -309,7 +357,6 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
|
||||
def test_duplicate_volume(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
self.driver.create_volume(self.data.test_volume)
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.create_volume, self.data.test_volume)
|
||||
@ -317,7 +364,6 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
|
||||
def test_no_portals_configured(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
portals = xms_data['iscsi-portals'].copy()
|
||||
xms_data['iscsi-portals'].clear()
|
||||
lunmap = {'lun': 4}
|
||||
@ -327,7 +373,6 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
|
||||
def test_initialize_terminate_connection(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
self.driver.create_volume(self.data.test_volume)
|
||||
map_data = self.driver.initialize_connection(self.data.test_volume,
|
||||
self.data.connector)
|
||||
@ -337,7 +382,6 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
|
||||
def test_initialize_connection_bad_ig(self, req):
|
||||
req.side_effect = xms_bad_request
|
||||
clean_xms_data()
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.initialize_connection,
|
||||
self.data.test_volume,
|
||||
@ -346,17 +390,17 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
|
||||
def test_get_stats(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
stats = self.driver.get_volume_stats(True)
|
||||
self.assertEqual(stats['volume_backend_name'],
|
||||
self.driver.backend_name)
|
||||
|
||||
def test_manage_unmanage(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
xms_data['volumes'] = {'unmanaged1': {'vol-name': 'unmanaged1',
|
||||
'index': 'unmanaged1',
|
||||
'vol-size': '3'}}
|
||||
xms_data['volumes'] = {1: {'name': 'unmanaged1',
|
||||
'index': 1,
|
||||
'vol-size': '3',
|
||||
},
|
||||
}
|
||||
ref_vol = {"source-name": "unmanaged1"}
|
||||
invalid_ref = {"source-name": "invalid"}
|
||||
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||
@ -371,26 +415,60 @@ class EMCXIODriverISCSITestCase(test.TestCase):
|
||||
self.data.test_volume2)
|
||||
self.driver.unmanage(self.data.test_volume)
|
||||
|
||||
@mock.patch('cinder.objects.snapshot.SnapshotList.get_all_for_cgsnapshot')
|
||||
def test_cg_operations(self, get_all_for_cgsnapshot, req):
|
||||
req.side_effect = xms_request
|
||||
d = self.data
|
||||
snapshot_obj = fake_snapshot.fake_snapshot_obj(d.context)
|
||||
snapshot_obj.consistencygroup_id = d.group['id']
|
||||
get_all_for_cgsnapshot.return_value = [snapshot_obj]
|
||||
|
||||
self.driver.create_consistencygroup(d.context, d.group)
|
||||
self.assertEqual(1, len(xms_data['consistency-groups']))
|
||||
self.driver.update_consistencygroup(d.context, d.group,
|
||||
add_volumes=[d.test_volume,
|
||||
d.test_volume2])
|
||||
self.assertEqual(2, len(xms_data['consistency-group-volumes']))
|
||||
self.driver.update_consistencygroup(d.context, d.group,
|
||||
remove_volumes=[d.test_volume2])
|
||||
self.assertEqual(1, len(xms_data['consistency-group-volumes']))
|
||||
self.driver.db = mock.Mock()
|
||||
(self.driver.db.
|
||||
volume_get_all_by_group.return_value) = [mock.MagicMock()]
|
||||
self.driver.create_cgsnapshot(d.context, d.cgsnapshot)
|
||||
snaps_name = self.driver._get_cgsnap_name(d.cgsnapshot)
|
||||
snaps = xms_data['volumes'][1]
|
||||
snaps['index'] = 1
|
||||
xms_data['snapshot-sets'] = {snaps_name: snaps, 1: snaps}
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
self.driver.create_consistencygroup_from_src,
|
||||
d.context, d.group, [])
|
||||
self.driver.delete_cgsnapshot(d.context, d.cgsnapshot)
|
||||
self.driver.delete_consistencygroup(d.context, d.group)
|
||||
|
||||
|
||||
@mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req')
|
||||
class EMCXIODriverFibreChannelTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(EMCXIODriverFibreChannelTestCase, self).setUp()
|
||||
clean_xms_data()
|
||||
|
||||
configuration = mock.Mock()
|
||||
configuration.san_login = ''
|
||||
configuration.san_password = ''
|
||||
configuration.san_ip = ''
|
||||
configuration.xtremio_cluster_name = ''
|
||||
configuration.xtremio_provisioning_factor = 20.0
|
||||
config = mock.Mock()
|
||||
config.san_login = ''
|
||||
config.san_password = ''
|
||||
config.san_ip = ''
|
||||
config.xtremio_cluster_name = ''
|
||||
config.xtremio_provisioning_factor = 20.0
|
||||
self.driver = xtremio.XtremIOFibreChannelDriver(
|
||||
configuration=configuration)
|
||||
configuration=config)
|
||||
self.driver.client = xtremio.XtremIOClient4(config,
|
||||
config.
|
||||
xtremio_cluster_name)
|
||||
|
||||
self.data = CommonData()
|
||||
|
||||
def test_initialize_terminate_connection(self, req):
|
||||
req.side_effect = xms_request
|
||||
clean_xms_data()
|
||||
self.driver.create_volume(self.data.test_volume)
|
||||
map_data = self.driver.initialize_connection(self.data.test_volume,
|
||||
self.data.connector)
|
||||
|
@ -22,7 +22,8 @@ supported XtremIO version 2.4 and up
|
||||
1.0.3 - update logging level, add translation
|
||||
1.0.4 - support for FC zones
|
||||
1.0.5 - add support for XtremIO 4.0
|
||||
1.0.6 - add support for iSCSI and CA validation
|
||||
1.0.6 - add support for iSCSI multipath, CA validation, consistency groups,
|
||||
R/O snapshots
|
||||
"""
|
||||
|
||||
import json
|
||||
@ -38,6 +39,7 @@ import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE, _LI, _LW
|
||||
from cinder import objects
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.san import san
|
||||
from cinder.zonemanager import utils as fczm_utils
|
||||
@ -163,6 +165,9 @@ class XtremIOClient(object):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_extra_capabilities(self):
|
||||
return {}
|
||||
|
||||
|
||||
class XtremIOClient3(XtremIOClient):
|
||||
def __init__(self, configuration, cluster_id):
|
||||
@ -217,6 +222,9 @@ class XtremIOClient4(XtremIOClient):
|
||||
super(XtremIOClient4, self).__init__(configuration, cluster_id)
|
||||
self._cluster_name = None
|
||||
|
||||
def get_extra_capabilities(self):
|
||||
return {'consistencygroup_support': True}
|
||||
|
||||
def find_lunmap(self, ig_name, vol_name):
|
||||
try:
|
||||
return (self.req('lun-maps',
|
||||
@ -269,6 +277,10 @@ class XtremIOClient4(XtremIOClient):
|
||||
self.req(typ, 'DELETE', idx=int(idx))
|
||||
raise
|
||||
|
||||
def add_vol_to_cg(self, vol_id, cg_id):
|
||||
add_data = {'vol-id': vol_id, 'cg-id': cg_id}
|
||||
self.req('consistency-group-volumes', 'POST', add_data, ver='v2')
|
||||
|
||||
|
||||
class XtremIOVolumeDriver(san.SanDriver):
|
||||
"""Executes commands relating to Volumes."""
|
||||
@ -322,17 +334,28 @@ class XtremIOVolumeDriver(san.SanDriver):
|
||||
data = {'vol-name': volume['id'],
|
||||
'vol-size': str(volume['size']) + 'g'
|
||||
}
|
||||
|
||||
self.client.req('volumes', 'POST', data)
|
||||
|
||||
if volume.get('consistencygroup_id'):
|
||||
self.client.add_vol_to_cg(volume['id'],
|
||||
volume['consistencygroup_id'])
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
"""Creates a volume from a snapshot."""
|
||||
self.client.create_snapshot(snapshot.id, volume['id'])
|
||||
|
||||
if snapshot.get('consistencygroup_id'):
|
||||
self.client.add_vol_to_cg(volume['id'],
|
||||
snapshot['consistencygroup_id'])
|
||||
|
||||
def create_cloned_volume(self, volume, src_vref):
|
||||
"""Creates a clone of the specified volume."""
|
||||
self.client.create_snapshot(src_vref['id'], volume['id'])
|
||||
|
||||
if volume.get('consistencygroup_id'):
|
||||
self.client.add_vol_to_cg(volume['id'],
|
||||
volume['consistencygroup_id'])
|
||||
|
||||
def delete_volume(self, volume):
|
||||
"""Deletes a volume."""
|
||||
try:
|
||||
@ -370,7 +393,9 @@ class XtremIOVolumeDriver(san.SanDriver):
|
||||
'thick_provisioning_support': False,
|
||||
'reserved_percentage':
|
||||
self.configuration.reserved_percentage,
|
||||
'QoS_support': False}
|
||||
'QoS_support': False,
|
||||
}
|
||||
self._stats.update(self.client.get_extra_capabilities())
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats.
|
||||
@ -435,7 +460,7 @@ class XtremIOVolumeDriver(san.SanDriver):
|
||||
self.client.req('volumes', 'PUT', data, name=volume['id'])
|
||||
except exception.NotFound:
|
||||
msg = _("can't find the volume to extend")
|
||||
raise (exception.VolumeDriverException(message=msg))
|
||||
raise exception.VolumeDriverException(message=msg)
|
||||
|
||||
def check_for_export(self, context, volume_id):
|
||||
"""Make sure volume is exported."""
|
||||
@ -479,6 +504,117 @@ class XtremIOVolumeDriver(san.SanDriver):
|
||||
def _get_ig(self, connector):
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_consistencygroup(self, context, group):
|
||||
"""Creates a consistency group.
|
||||
|
||||
:param context: the context
|
||||
:param group: the group object to be created
|
||||
:returns: dict -- modelUpdate = {'status': 'available'}
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
create_data = {'consistency-group-name': group['id']}
|
||||
self.client.req('consistency-groups', 'POST', data=create_data,
|
||||
ver='v2')
|
||||
return {'status': 'available'}
|
||||
|
||||
def delete_consistencygroup(self, context, group):
|
||||
"""Deletes a consistency group."""
|
||||
self.client.req('consistency-groups', 'DELETE', name=group['id'],
|
||||
ver='v2')
|
||||
|
||||
volumes = self.db.volume_get_all_by_group(context, group['id'])
|
||||
|
||||
for volume in volumes:
|
||||
self.delete_volume(volume)
|
||||
volume.status = 'deleted'
|
||||
|
||||
model_update = {'status': group['status']}
|
||||
|
||||
return model_update, volumes
|
||||
|
||||
def create_consistencygroup_from_src(self, context, group, volumes,
|
||||
cgsnapshot=None, snapshots=None):
|
||||
"""Creates a consistencygroup from source.
|
||||
|
||||
:param context: the context of the caller.
|
||||
:param group: the dictionary of the consistency group to be created.
|
||||
:param volumes: a list of volume dictionaries in the group.
|
||||
:param cgsnapshot: the dictionary of the cgsnapshot as source.
|
||||
:param snapshots: a list of snapshot dictionaries in the cgsnapshot.
|
||||
:return model_update, volumes_model_update
|
||||
"""
|
||||
if cgsnapshot and snapshots:
|
||||
for volume, snapshot in zip(volumes, snapshots):
|
||||
self.create_volume_from_snapshot(volume, snapshot)
|
||||
create_data = {'consistency-group-name': group['id'],
|
||||
'vol-list': [v['id'] for v in volumes]}
|
||||
self.client.req('consistency-groups', 'POST', data=create_data,
|
||||
ver='v2')
|
||||
else:
|
||||
msg = _("create_consistencygroup_from_src only supports a"
|
||||
" cgsnapshot source, other sources cannot be used.")
|
||||
raise exception.InvalidInput(msg)
|
||||
|
||||
return None, None
|
||||
|
||||
def update_consistencygroup(self, context, group,
|
||||
add_volumes=None, remove_volumes=None):
|
||||
"""Updates a consistency group.
|
||||
|
||||
:param context: the context of the caller.
|
||||
:param group: the dictionary of the consistency group to be updated.
|
||||
:param add_volumes: a list of volume dictionaries to be added.
|
||||
:param remove_volumes: a list of volume dictionaries to be removed.
|
||||
:return model_update, add_volumes_update, remove_volumes_update
|
||||
"""
|
||||
add_volumes = add_volumes if add_volumes else []
|
||||
remove_volumes = remove_volumes if remove_volumes else []
|
||||
for vol in add_volumes:
|
||||
add_data = {'vol-id': vol['id'], 'cg-id': group['id']}
|
||||
self.client.req('consistency-group-volumes', 'POST', add_data,
|
||||
ver='v2')
|
||||
for vol in remove_volumes:
|
||||
remove_data = {'vol-id': vol['id'], 'cg-id': group['id']}
|
||||
self.client.req('consistency-group-volumes', 'DELETE', remove_data,
|
||||
name=group['id'], ver='v2')
|
||||
return None, None, None
|
||||
|
||||
def _get_cgsnap_name(self, cgsnapshot):
|
||||
return '%(cg)s%(snap)s' % {'cg': cgsnapshot['consistencygroup_id']
|
||||
.replace('-', ''),
|
||||
'snap': cgsnapshot['id'].replace('-', '')}
|
||||
|
||||
def create_cgsnapshot(self, context, cgsnapshot):
|
||||
"""Creates a cgsnapshot."""
|
||||
data = {'consistency-group-id': cgsnapshot['consistencygroup_id'],
|
||||
'snapshot-set-name': self._get_cgsnap_name(cgsnapshot)}
|
||||
self.client.req('snapshots', 'POST', data, ver='v2')
|
||||
|
||||
snapshots = objects.SnapshotList().get_all_for_cgsnapshot(
|
||||
context, cgsnapshot['id'])
|
||||
|
||||
for snapshot in snapshots:
|
||||
snapshot.status = 'available'
|
||||
|
||||
model_update = {'status': 'available'}
|
||||
|
||||
return model_update, snapshots
|
||||
|
||||
def delete_cgsnapshot(self, context, cgsnapshot):
|
||||
"""Deletes a cgsnapshot."""
|
||||
self.client.req('snapshot-sets', 'DELETE',
|
||||
name=self._get_cgsnap_name(cgsnapshot), ver='v2')
|
||||
|
||||
snapshots = objects.SnapshotList().get_all_for_cgsnapshot(
|
||||
context, cgsnapshot['id'])
|
||||
|
||||
for snapshot in snapshots:
|
||||
snapshot.status = 'deleted'
|
||||
|
||||
model_update = {'status': cgsnapshot.status}
|
||||
|
||||
return model_update, snapshots
|
||||
|
||||
|
||||
class XtremIOISCSIDriver(XtremIOVolumeDriver, driver.ISCSIDriver):
|
||||
"""Executes commands relating to ISCSI volumes.
|
||||
|
Loading…
Reference in New Issue
Block a user