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_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_group = mock.Mock(id=4)
test_snapgroup = mock.Mock(id=5, group=test_group)
test_connector = dict(wwpns=[TEST_WWN_1],
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_ns = mock.Mock()
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.create.return_value = self._mock_volume
result.pools.safe_get.return_value = self._mock_pool
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.network_spaces.safe_get.return_value = self._mock_ns
result.components.nodes.get_all.return_value = []
@ -302,6 +307,132 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
self.driver.create_cloned_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):
def test_initialize_connection_multiple_wwpns(self):

View File

@ -28,6 +28,7 @@ from cinder import coordination
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder.objects import fields
from cinder import utils
from cinder.volume.drivers.san import san
from cinder.volume import utils as vol_utils
@ -88,7 +89,7 @@ def infinisdk_to_cinder_exceptions(func):
@interface.volumedriver
class InfiniboxVolumeDriver(san.SanISCSIDriver):
VERSION = '1.2'
VERSION = '1.3'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "INFINIDAT_Cinder_CI"
@ -130,6 +131,12 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
def _make_host_name(self, port):
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):
volume = self._system.volumes.safe_get(name=name)
if volume is None:
@ -163,6 +170,15 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
raise exception.VolumeDriverException(message=msg)
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):
host_name = self._make_host_name(port)
infinidat_host = self._system.hosts.safe_get(name=host_name)
@ -334,9 +350,10 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
vendor_name=VENDOR_NAME,
driver_version=self.VERSION,
storage_protocol=self._protocol,
consistencygroup_support='False',
consistencygroup_support=False,
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
def _create_volume(self, volume):
@ -538,3 +555,111 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
init_targ_map[initiator] = target_wwns
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.