'provider_auth': None,
'host': "host@backendsec#unit_test_pool",
'project_id': 'project',
- 'provider_location': 'system^FNM11111|type^lun|id^1|version^05.02.00',
+ 'provider_location': 'system^FNM11111|type^lun|id^1|version^05.03.00',
'display_name': 'vol1',
'display_description': 'test volume',
'volume_type_id': None,
'consistencygroup_id': None,
'volume_admin_metadata': [{'key': 'attached_mode', 'value': 'rw'},
{'key': 'readonly', 'value': 'False'}],
- 'provider_location': 'system^FNM11111|type^lun|id^1|version^05.02.00',
+ 'provider_location': 'system^FNM11111|type^lun|id^1|version^05.03.00',
}
test_volume2 = {
'provider_auth': None,
'host': "host@backendsec#unit_test_pool",
'project_id': 'project',
- 'display_name': 'vol2',
- 'consistencygroup_id': None,
+ 'display_name': 'vol1_in_cg',
+ 'provider_location': 'system^FNM11111|type^lun|id^1',
+ 'consistencygroup_id': 'consistencygroup_id',
+ 'display_description': 'test volume',
+ 'volume_type_id': None}
+
+ volume2_in_cg = {
+ 'name': 'vol2',
+ 'size': 1,
+ 'volume_name': 'vol2',
+ 'id': '3',
+ 'provider_auth': None,
+ 'project_id': 'project',
+ 'display_name': 'vol2_in_cg',
+ 'provider_location': 'system^FNM11111|type^lun|id^3',
+ 'consistencygroup_id': 'consistencygroup_id',
'display_description': 'test volume',
'volume_type_id': None}
'display_name': 'failed_vol',
'display_description': 'Volume 1 in SG',
'volume_type_id': None,
- 'provider_location': 'system^fakesn|type^lun|id^4|version^05.02.00'}
+ 'provider_location': 'system^fakesn|type^lun|id^4|version^05.03.00'}
test_volume2_in_sg = {
'name': 'vol2_in_sg',
'display_name': 'failed_vol',
'display_description': 'Volume 2 in SG',
'volume_type_id': None,
- 'provider_location': 'system^fakesn|type^lun|id^3|version^05.02.00'}
+ 'provider_location': 'system^fakesn|type^lun|id^3|version^05.03.00'}
test_snapshot = {
'name': 'snapshot1',
'volume_type': [],
'attached_host': None,
'provider_location':
- 'system^FNM11111|type^lun|id^1|version^05.02.00',
+ 'system^FNM11111|type^lun|id^1|version^05.03.00',
'_name_id': None, 'volume_metadata': []}
test_new_type = {'name': 'voltype0', 'qos_specs_id': None,
'volume_backend_name': 'pool_backend_1',
'storage_protocol': 'iSCSI'}}
+ test_volume4 = {'migration_status': None, 'availability_zone': 'nova',
+ 'id': '1181d1b2-cea3-4f55-8fa8-3360d026ce24',
+ 'name': 'vol4',
+ 'size': 2L,
+ 'volume_admin_metadata': [],
+ 'status': 'available',
+ 'volume_type_id':
+ '19fdd0dd-03b3-4d7c-b541-f4df46f308c8',
+ 'deleted': False, 'provider_location':
+ 'system^FNM11111|type^lun|id^4',
+ 'host': 'ubuntu-server12@array_backend_1',
+ 'source_volid': None, 'provider_auth': None,
+ 'display_name': 'vol-test02', 'instance_uuid': None,
+ 'attach_status': 'detached',
+ 'volume_type': [],
+ '_name_id': None, 'volume_metadata': []}
+
test_volume5 = {'migration_status': None, 'availability_zone': 'nova',
'id': '1181d1b2-cea3-4f55-8fa8-3360d026ce25',
'name_id': '1181d1b2-cea3-4f55-8fa8-3360d026ce25',
return ('-np', 'snap', '-group', '-destroy',
'-id', cg_name)
- def GET_CONSISTENCYGROUP_BY_NAME(self, cg_name):
- return ('snap', '-group', '-list', '-id', cg_name)
-
def ADD_LUN_TO_CG_CMD(self, cg_name, lun_id):
- return ('-np', 'snap', '-group',
+ return ('snap', '-group',
'-addmember', '-id', cg_name, '-res', lun_id)
def CREATE_CG_SNAPSHOT(self, cg_name, snap_name):
def GET_CG_BY_NAME_CMD(self, cg_name):
return ('snap', '-group', '-list', '-id', cg_name)
+ def REMOVE_LUNS_FROM_CG_CMD(self, cg_name, remove_ids):
+ return ('snap', '-group', '-rmmember', '-id', cg_name, '-res',
+ ','.join(remove_ids))
+
+ def REPLACE_LUNS_IN_CG_CMD(self, cg_name, new_ids):
+ return ('snap', '-group', '-replmember', '-id', cg_name, '-res',
+ ','.join(new_ids))
+
def CONSISTENCY_GROUP_VOLUMES(self):
volumes = []
volumes.append(self.test_volume)
snaps.append(self.test_snapshot)
return snaps
+ def VOLUMES_NOT_IN_CG(self):
+ add_volumes = []
+ add_volumes.append(self.test_volume4)
+ add_volumes.append(self.test_volume5)
+ return add_volumes
+
+ def VOLUMES_IN_CG(self):
+ remove_volumes = []
+ remove_volumes.append(self.volume_in_cg)
+ remove_volumes.append(self.volume2_in_cg)
+ return remove_volumes
+
def CG_PROPERTY(self, cg_name):
return """
Name: %(cg_name)s
Allow auto delete: No
Member LUN ID(s): 1, 3
State: Ready
-""" % {'cg_name': cg_name}
+""" % {'cg_name': cg_name}, 0
+
+ def CG_REPL_ERROR(self):
+ return """
+ The specified LUN is already a member
+ of another consistency group. (0x716d8045)
+ """, 71
POOL_PROPERTY = ("""\
Pool Name: unit_test_pool
mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'),
poll=False),
mock.call(*self.testData.ADD_LUN_TO_CG_CMD(
- 'cg_id', 1))]
+ 'cg_id', 1), poll=False)]
fake_cli.assert_has_calls(expect_cmd)
def test_create_cloned_volume_from_consistnecy_group(self):
poll=True)]
fake_cli.assert_has_calls(expect_cmd)
+ def test_update_consistencygroup(self):
+ cg_name = self.testData.test_cg['id']
+ commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name)]
+ results = [self.testData.CG_PROPERTY(cg_name)]
+ fake_cli = self.driverSetup(commands, results)
+
+ (model_update, add_vols, remove_vols) = (
+ self.driver.update_consistencygroup(None, self.testData.test_cg,
+ self.testData.
+ VOLUMES_NOT_IN_CG(),
+ self.testData.VOLUMES_IN_CG()))
+ expect_cmd = [
+ mock.call(*self.testData.REPLACE_LUNS_IN_CG_CMD(
+ cg_name, ['4', '5']), poll=False)]
+ fake_cli.assert_has_calls(expect_cmd)
+ self.assertEqual('available', model_update['status'])
+
+ def test_update_consistencygroup_remove_all(self):
+ cg_name = self.testData.test_cg['id']
+ commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name)]
+ results = [self.testData.CG_PROPERTY(cg_name)]
+ fake_cli = self.driverSetup(commands, results)
+
+ (model_update, add_vols, remove_vols) = (
+ self.driver.update_consistencygroup(None, self.testData.test_cg,
+ None,
+ self.testData.VOLUMES_IN_CG()))
+ expect_cmd = [
+ mock.call(*self.testData.REMOVE_LUNS_FROM_CG_CMD(
+ cg_name, ['1', '3']), poll=False)]
+ fake_cli.assert_has_calls(expect_cmd)
+ self.assertEqual('available', model_update['status'])
+
+ def test_update_consistencygroup_remove_not_in_cg(self):
+ cg_name = self.testData.test_cg['id']
+ commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name)]
+ results = [self.testData.CG_PROPERTY(cg_name)]
+ fake_cli = self.driverSetup(commands, results)
+
+ (model_update, add_vols, remove_vols) = (
+ self.driver.update_consistencygroup(None, self.testData.test_cg,
+ None,
+ self.testData.
+ VOLUMES_NOT_IN_CG()))
+ expect_cmd = [
+ mock.call(*self.testData.REPLACE_LUNS_IN_CG_CMD(
+ cg_name, ['1', '3']), poll=False)]
+ fake_cli.assert_has_calls(expect_cmd)
+ self.assertEqual('available', model_update['status'])
+
+ def test_update_consistencygroup_error(self):
+ cg_name = self.testData.test_cg['id']
+ commands = [self.testData.GET_CG_BY_NAME_CMD(cg_name),
+ self.testData.REPLACE_LUNS_IN_CG_CMD(
+ cg_name, ['1', '3'])]
+ results = [self.testData.CG_PROPERTY(cg_name),
+ self.testData.CG_REPL_ERROR()]
+ fake_cli = self.driverSetup(commands, results)
+ self.assertRaises(exception.EMCVnxCLICmdError,
+ self.driver.update_consistencygroup,
+ None,
+ self.testData.test_cg,
+ [],
+ self.testData.VOLUMES_NOT_IN_CG())
+ expect_cmd = [
+ mock.call(*self.testData.REPLACE_LUNS_IN_CG_CMD(
+ cg_name, ['1', '3']), poll=False)]
+ fake_cli.assert_has_calls(expect_cmd)
+
def test_deregister_initiator(self):
fake_cli = self.driverSetup()
self.driver.cli.destroy_empty_sg = True
for m in re.finditer(cg_pat, out):
data['Name'] = m.groups()[0].strip()
data['State'] = m.groups()[4].strip()
- luns_of_cg = m.groups()[3].split(',')
- if luns_of_cg:
- data['Luns'] = [lun.strip() for lun in luns_of_cg]
+ # Handle case when no lun in cg Member LUN ID(s): None
+ luns_of_cg = m.groups()[3].replace('None', '').strip()
+ data['Luns'] = ([lun.strip() for lun in luns_of_cg.split(',')]
+ if luns_of_cg else [])
LOG.debug("Found consistent group %s.", data['Name'])
return data
- def add_lun_to_consistency_group(self, cg_name, lun_id):
- add_lun_to_cg_cmd = ('-np', 'snap', '-group',
+ def add_lun_to_consistency_group(self, cg_name, lun_id, poll=False):
+ add_lun_to_cg_cmd = ('snap', '-group',
'-addmember', '-id',
cg_name, '-res', lun_id)
- out, rc = self.command_execute(*add_lun_to_cg_cmd)
+ out, rc = self.command_execute(*add_lun_to_cg_cmd, poll=poll)
if rc != 0:
- msg = (_("Can not add the lun %(lun)s to consistency "
- "group %(cg_name)s.") % {'lun': lun_id,
- 'cg_name': cg_name})
- LOG.error(msg)
+ LOG.error(_LE("Can not add the lun %(lun)s to consistency "
+ "group %(cg_name)s."), {'lun': lun_id,
+ 'cg_name': cg_name})
self._raise_cli_error(add_lun_to_cg_cmd, rc, out)
def add_lun_to_consistency_success():
data = self.get_consistency_group_by_name(cg_name)
if str(lun_id) in data['Luns']:
- LOG.debug(("Add lun %(lun)s to consistency "
- "group %(cg_name)s successfully."),
+ LOG.debug("Add lun %(lun)s to consistency "
+ "group %(cg_name)s successfully.",
{'lun': lun_id, 'cg_name': cg_name})
return True
else:
- LOG.debug(("Adding lun %(lun)s to consistency "
- "group %(cg_name)s."),
+ LOG.debug("Adding lun %(lun)s to consistency "
+ "group %(cg_name)s.",
{'lun': lun_id, 'cg_name': cg_name})
return False
self._wait_for_a_condition(add_lun_to_consistency_success,
interval=INTERVAL_30_SEC)
+ def remove_luns_from_consistencygroup(self, cg_name, remove_ids,
+ poll=False):
+ """Removes LUN(s) from cg"""
+ remove_luns_cmd = ('snap', '-group', '-rmmember',
+ '-id', cg_name,
+ '-res', ','.join(remove_ids))
+ out, rc = self.command_execute(*remove_luns_cmd, poll=poll)
+ if rc != 0:
+ LOG.error(_LE("Can not remove LUNs %(luns)s in consistency "
+ "group %(cg_name)s."), {'luns': remove_ids,
+ 'cg_name': cg_name})
+ self._raise_cli_error(remove_luns_cmd, rc, out)
+
+ def replace_luns_in_consistencygroup(self, cg_name, new_ids,
+ poll=False):
+ """Replaces LUN(s) with new_ids for cg"""
+ replace_luns_cmd = ('snap', '-group', '-replmember',
+ '-id', cg_name,
+ '-res', ','.join(new_ids))
+ out, rc = self.command_execute(*replace_luns_cmd, poll=poll)
+ if rc != 0:
+ LOG.error(_LE("Can not place new LUNs %(luns)s in consistency "
+ "group %(cg_name)s."), {'luns': new_ids,
+ 'cg_name': cg_name})
+ self._raise_cli_error(replace_luns_cmd, rc, out)
+
def delete_consistencygroup(self, cg_name):
delete_cg_cmd = ('-np', 'snap', '-group',
'-destroy', '-id', cg_name)
class EMCVnxCliBase(object):
"""This class defines the functions to use the native CLI functionality."""
- VERSION = '05.02.01'
+ VERSION = '05.03.00'
stats = {'driver_version': VERSION,
'storage_protocol': None,
'vendor_name': 'EMC',
return model_update, volumes
+ def update_consistencygroup(self, context,
+ group,
+ add_volumes,
+ remove_volumes):
+ """Adds or removes LUN(s) to/from an existing consistency group"""
+ model_update = {'status': 'available'}
+ cg_name = group['id']
+ add_ids = [six.text_type(self.get_lun_id(vol))
+ for vol in add_volumes] if add_volumes else []
+ remove_ids = [six.text_type(self.get_lun_id(vol))
+ for vol in remove_volumes] if remove_volumes else []
+
+ data = self._client.get_consistency_group_by_name(cg_name)
+ ids_curr = data['Luns']
+ ids_later = []
+
+ if ids_curr:
+ ids_later.extend(ids_curr)
+ ids_later.extend(add_ids)
+ for remove_id in remove_ids:
+ if remove_id in ids_later:
+ ids_later.remove(remove_id)
+ else:
+ LOG.warning(_LW("LUN with id %(remove_id)s is not present "
+ "in cg %(cg_name)s, skip it."),
+ {'remove_id': remove_id, 'cg_name': cg_name})
+ # Remove all from cg
+ if not ids_later:
+ self._client.remove_luns_from_consistencygroup(cg_name,
+ ids_curr)
+ else:
+ self._client.replace_luns_in_consistencygroup(cg_name,
+ ids_later)
+ return model_update, None, None
+
def create_cgsnapshot(self, driver, context, cgsnapshot):
"""Creates a cgsnapshot (snap group)."""
cgsnapshot_id = cgsnapshot['id']