INFINIDAT: add support for generic volume groups

This supports following functionality:
* Create generic volume group
* Delete generic volume group
* Update (add/remove) volume from a generic volume group
* Create group from a source, where source can be another
  group or a snapshot of a group
* Create consistent group snapshot
* Delete consistent group snapshot

Change-Id: I713fdbb83eb008b9e93fc4a743d37cb05d6d77d4
Implements: blueprint add-infinidat-generic-volume-groups
This commit is contained in:
Arnon Yaari 2017-03-12 11:24:18 +02:00
parent 747d4464c7
commit f308007862
3 changed files with 263 additions and 4 deletions

View File

@ -27,8 +27,10 @@ TEST_WWN_1 = '00:11:22:33:44:55:66:77'
TEST_WWN_2 = '11:11:22:33:44:55:66:77' TEST_WWN_2 = '11:11:22:33:44:55:66:77'
test_volume = mock.Mock(id=1, size=1) test_volume = mock.Mock(id=1, size=1)
test_snapshot = mock.Mock(id=2, volume=test_volume) test_snapshot = mock.Mock(id=2, volume=test_volume, volume_id='1')
test_clone = mock.Mock(id=3, size=1) test_clone = mock.Mock(id=3, size=1)
test_group = mock.Mock(id=4)
test_snapgroup = mock.Mock(id=5, group=test_group)
test_connector = dict(wwpns=[TEST_WWN_1], test_connector = dict(wwpns=[TEST_WWN_1],
initiator='iqn.2012-07.org.fake:01') initiator='iqn.2012-07.org.fake:01')
@ -86,10 +88,13 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
self._mock_pool.get_physical_capacity.return_value = units.Gi self._mock_pool.get_physical_capacity.return_value = units.Gi
self._mock_ns = mock.Mock() self._mock_ns = mock.Mock()
self._mock_ns.get_ips.return_value = [mock.Mock(ip_address='1.1.1.1')] self._mock_ns.get_ips.return_value = [mock.Mock(ip_address='1.1.1.1')]
self._mock_group = mock.Mock()
result.volumes.safe_get.return_value = self._mock_volume result.volumes.safe_get.return_value = self._mock_volume
result.volumes.create.return_value = self._mock_volume result.volumes.create.return_value = self._mock_volume
result.pools.safe_get.return_value = self._mock_pool result.pools.safe_get.return_value = self._mock_pool
result.hosts.safe_get.return_value = self._mock_host result.hosts.safe_get.return_value = self._mock_host
result.cons_groups.safe_get.return_value = self._mock_group
result.cons_groups.create.return_value = self._mock_group
result.hosts.create.return_value = self._mock_host result.hosts.create.return_value = self._mock_host
result.network_spaces.safe_get.return_value = self._mock_ns result.network_spaces.safe_get.return_value = self._mock_ns
result.components.nodes.get_all.return_value = [] result.components.nodes.get_all.return_value = []
@ -302,6 +307,132 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
self.driver.create_cloned_volume, self.driver.create_cloned_volume,
test_clone, test_volume) test_clone, test_volume)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_create_group(self, *mocks):
self.driver.create_group(None, test_group)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_create_group_twice(self, *mocks):
self.driver.create_group(None, test_group)
self.driver.create_group(None, test_group)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_create_group_api_fail(self, *mocks):
self._system.cons_groups.create.side_effect = self._raise_infinisdk
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_group,
None, test_group)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_delete_group(self, *mocks):
self.driver.delete_group(None, test_group, [test_volume])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_delete_group_doesnt_exist(self, *mocks):
self._system.cons_groups.safe_get.return_value = None
self.driver.delete_group(None, test_group, [test_volume])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_delete_group_api_fail(self, *mocks):
self._mock_group.safe_delete.side_effect = self._raise_infinisdk
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_group,
None, test_group, [test_volume])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_update_group_add_and_remove(self, *mocks):
self.driver.update_group(None, test_group,
[test_volume], [test_volume])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_update_group_api_fail(self, *mocks):
self._mock_group.add_member.side_effect = self._raise_infinisdk
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.update_group,
None, test_group,
[test_volume], [test_volume])
@mock.patch("cinder.volume.utils.copy_volume")
@mock.patch("cinder.utils.brick_get_connector")
@mock.patch("cinder.utils.brick_get_connector_properties",
return_value=test_connector)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_create_group_from_src_snaps(self, *mocks):
self.driver.create_group_from_src(None, test_group, [test_volume],
test_snapgroup, [test_snapshot],
None, None)
@mock.patch("cinder.volume.utils.copy_volume")
@mock.patch("cinder.utils.brick_get_connector")
@mock.patch("cinder.utils.brick_get_connector_properties",
return_value=test_connector)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_create_group_from_src_vols(self, *mocks):
self.driver.create_group_from_src(None, test_group, [test_volume],
None, None,
test_group, [test_volume])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_create_group_snap(self, *mocks):
mock_snapgroup = mock.Mock()
mock_snapgroup.get_members.return_value = [self._mock_volume]
self._mock_volume.get_parent.return_value = self._mock_volume
self._mock_volume.get_name.return_value = ''
self._mock_group.create_snapshot.return_value = mock_snapgroup
self.driver.create_group_snapshot(None,
test_snapgroup,
[test_snapshot])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_create_group_snap_api_fail(self, *mocks):
self._mock_group.create_snapshot.side_effect = self._raise_infinisdk
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_group_snapshot, None,
test_snapgroup, [test_snapshot])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_delete_group_snap(self, *mocks):
self.driver.delete_group_snapshot(None,
test_snapgroup,
[test_snapshot])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_delete_group_snap_does_not_exist(self, *mocks):
self._system.cons_groups.safe_get.return_value = None
self.driver.delete_group_snapshot(None,
test_snapgroup,
[test_snapshot])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_delete_group_snap_invalid_group(self, *mocks):
self._mock_group.is_snapgroup.return_value = False
self.assertRaises(exception.InvalidGroupSnapshot,
self.driver.delete_group_snapshot,
None, test_snapgroup, [test_snapshot])
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
def test_delete_group_snap_api_fail(self, *mocks):
self._mock_group.safe_delete.side_effect = self._raise_infinisdk
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.delete_group_snapshot,
None, test_snapgroup, [test_snapshot])
class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase): class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase):
def test_initialize_connection_multiple_wwpns(self): def test_initialize_connection_multiple_wwpns(self):

View File

@ -28,6 +28,7 @@ from cinder import coordination
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import interface from cinder import interface
from cinder.objects import fields
from cinder import utils from cinder import utils
from cinder.volume.drivers.san import san from cinder.volume.drivers.san import san
from cinder.volume import utils as vol_utils from cinder.volume import utils as vol_utils
@ -88,7 +89,7 @@ def infinisdk_to_cinder_exceptions(func):
@interface.volumedriver @interface.volumedriver
class InfiniboxVolumeDriver(san.SanISCSIDriver): class InfiniboxVolumeDriver(san.SanISCSIDriver):
VERSION = '1.2' VERSION = '1.3'
# ThirdPartySystems wiki page # ThirdPartySystems wiki page
CI_WIKI_NAME = "INFINIDAT_Cinder_CI" CI_WIKI_NAME = "INFINIDAT_Cinder_CI"
@ -130,6 +131,12 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
def _make_host_name(self, port): def _make_host_name(self, port):
return 'openstack-host-%s' % str(port).replace(":", ".") return 'openstack-host-%s' % str(port).replace(":", ".")
def _make_cg_name(self, cinder_group):
return 'openstack-cg-%s' % cinder_group.id
def _make_group_snapshot_name(self, cinder_group_snap):
return 'openstack-group-snap-%s' % cinder_group_snap.id
def _get_infinidat_volume_by_name(self, name): def _get_infinidat_volume_by_name(self, name):
volume = self._system.volumes.safe_get(name=name) volume = self._system.volumes.safe_get(name=name)
if volume is None: if volume is None:
@ -163,6 +170,15 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
raise exception.VolumeDriverException(message=msg) raise exception.VolumeDriverException(message=msg)
return pool return pool
def _get_infinidat_cg(self, cinder_group):
group_name = self._make_cg_name(cinder_group)
infinidat_cg = self._system.cons_groups.safe_get(name=group_name)
if infinidat_cg is None:
msg = _('Consistency group "%s" not found') % group_name
LOG.error(msg)
raise exception.InvalidGroup(message=msg)
return infinidat_cg
def _get_or_create_host(self, port): def _get_or_create_host(self, port):
host_name = self._make_host_name(port) host_name = self._make_host_name(port)
infinidat_host = self._system.hosts.safe_get(name=host_name) infinidat_host = self._system.hosts.safe_get(name=host_name)
@ -334,9 +350,10 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
vendor_name=VENDOR_NAME, vendor_name=VENDOR_NAME,
driver_version=self.VERSION, driver_version=self.VERSION,
storage_protocol=self._protocol, storage_protocol=self._protocol,
consistencygroup_support='False', consistencygroup_support=False,
total_capacity_gb=total_capacity_gb, total_capacity_gb=total_capacity_gb,
free_capacity_gb=free_capacity_gb) free_capacity_gb=free_capacity_gb,
consistent_group_snapshot_enabled=True)
return self._volume_stats return self._volume_stats
def _create_volume(self, volume): def _create_volume(self, volume):
@ -538,3 +555,111 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
init_targ_map[initiator] = target_wwns init_targ_map[initiator] = target_wwns
return target_wwns, init_targ_map return target_wwns, init_targ_map
@infinisdk_to_cinder_exceptions
def create_group(self, context, group):
"""Creates a group."""
# let generic volume group support handle non-cgsnapshots
if not vol_utils.is_group_a_cg_snapshot_type(group):
raise NotImplementedError()
self._system.cons_groups.create(name=self._make_cg_name(group),
pool=self._get_infinidat_pool())
return {'status': fields.GroupStatus.AVAILABLE}
@infinisdk_to_cinder_exceptions
def delete_group(self, context, group, volumes):
"""Deletes a group."""
# let generic volume group support handle non-cgsnapshots
if not vol_utils.is_group_a_cg_snapshot_type(group):
raise NotImplementedError()
try:
infinidat_cg = self._get_infinidat_cg(group)
except exception.InvalidGroup:
pass # group not found
else:
infinidat_cg.safe_delete()
for volume in volumes:
self.delete_volume(volume)
return None, None
@infinisdk_to_cinder_exceptions
def update_group(self, context, group,
add_volumes=None, remove_volumes=None):
"""Updates a group."""
# let generic volume group support handle non-cgsnapshots
if not vol_utils.is_group_a_cg_snapshot_type(group):
raise NotImplementedError()
add_volumes = add_volumes if add_volumes else []
remove_volumes = remove_volumes if remove_volumes else []
infinidat_cg = self._get_infinidat_cg(group)
for vol in add_volumes:
infinidat_volume = self._get_infinidat_volume(vol)
infinidat_cg.add_member(infinidat_volume)
for vol in remove_volumes:
infinidat_volume = self._get_infinidat_volume(vol)
infinidat_cg.remove_member(infinidat_volume)
return None, None, None
@infinisdk_to_cinder_exceptions
def create_group_from_src(self, context, group, volumes,
group_snapshot=None, snapshots=None,
source_group=None, source_vols=None):
"""Creates a group from source."""
# The source is either group_snapshot+snapshots or
# source_group+source_vols. The target is group+voluems
# we assume the source (source_vols / snapshots) are in the same
# order as the target (volumes)
# let generic volume group support handle non-cgsnapshots
if not vol_utils.is_group_a_cg_snapshot_type(group):
raise NotImplementedError()
self.create_group(context, group)
new_infinidat_group = self._get_infinidat_cg(group)
if group_snapshot is not None and snapshots is not None:
for volume, snapshot in zip(volumes, snapshots):
self.create_volume_from_snapshot(volume, snapshot)
new_infinidat_volume = self._get_infinidat_volume(volume)
new_infinidat_group.add_member(new_infinidat_volume)
elif source_group is not None and source_vols is not None:
for volume, src_vol in zip(volumes, source_vols):
self.create_cloned_volume(volume, src_vol)
new_infinidat_volume = self._get_infinidat_volume(volume)
new_infinidat_group.add_member(new_infinidat_volume)
return None, None
@infinisdk_to_cinder_exceptions
def create_group_snapshot(self, context, group_snapshot, snapshots):
"""Creates a group_snapshot."""
# let generic volume group support handle non-cgsnapshots
if not vol_utils.is_group_a_cg_snapshot_type(group_snapshot):
raise NotImplementedError()
infinidat_cg = self._get_infinidat_cg(group_snapshot.group)
group_snap_name = self._make_group_snapshot_name(group_snapshot)
new_group = infinidat_cg.create_snapshot(name=group_snap_name)
# update the names of the individual snapshots in the new snapgroup
# to match the names we use for cinder snapshots
for infinidat_snapshot in new_group.get_members():
parent_name = infinidat_snapshot.get_parent().get_name()
for cinder_snapshot in snapshots:
if cinder_snapshot.volume_id in parent_name:
snapshot_name = self._make_snapshot_name(cinder_snapshot)
infinidat_snapshot.update_name(snapshot_name)
return None, None
@infinisdk_to_cinder_exceptions
def delete_group_snapshot(self, context, group_snapshot, snapshots):
"""Deletes a group_snapshot."""
# let generic volume group support handle non-cgsnapshots
if not vol_utils.is_group_a_cg_snapshot_type(group_snapshot):
raise NotImplementedError()
cgsnap_name = self._make_group_snapshot_name(group_snapshot)
infinidat_cgsnap = self._system.cons_groups.safe_get(name=cgsnap_name)
if infinidat_cgsnap is not None:
if not infinidat_cgsnap.is_snapgroup():
msg = _('Group "%s" is not a snapshot group') % cgsnap_name
LOG.error(msg)
raise exception.InvalidGroupSnapshot(message=msg)
infinidat_cgsnap.safe_delete()
for snapshot in snapshots:
self.delete_snapshot(snapshot)
return None, None

View File

@ -0,0 +1,3 @@
---
features:
- Add CG capability to generic volume groups in INFINIDAT driver.