From f9d7427c685553a1ccbdf9105d4948c752deef73 Mon Sep 17 00:00:00 2001 From: Jeegn Chen Date: Sun, 14 Dec 2014 17:17:41 +0800 Subject: [PATCH] EMC VNX Cinder Driver Update VNX Direct Driver was contributed in Icehouse and updated in Juno. This commit is to continuously improve the driver with the following enhancements in Kilo: * Performance improvement, especially the synchronized operations initiatlize_connetion and terminate_connection. * LUN Number Threshold Support * Initiator Auto Deregistration * Force Deleting LUN in Storage Groups * Code refactor to enhance the robustness Change-Id: Id263a5d0405ba942582ce06beed09b436b80ff3c Implements: blueprint emc-vnx-direct-driver-kilo-update --- cinder/exception.py | 19 +- cinder/tests/test_emc_vnxdirect.py | 1425 +++++++++++----- cinder/volume/drivers/emc/emc_cli_fc.py | 15 +- cinder/volume/drivers/emc/emc_cli_iscsi.py | 8 +- cinder/volume/drivers/emc/emc_vnx_cli.py | 1796 ++++++++++++-------- 5 files changed, 2042 insertions(+), 1221 deletions(-) diff --git a/cinder/exception.py b/cinder/exception.py index b5458de25..7559c8e9b 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -844,23 +844,8 @@ class NetAppDriverException(VolumeDriverException): class EMCVnxCLICmdError(VolumeBackendAPIException): - def __init__(self, cmd=None, rc=None, out='', - log_as_error=True, **kwargs): - self.cmd = cmd - self.rc = rc - self.out = out - msg = _("EMCVnxCLICmdError : %(cmd)s " - "(Return Code: %(rc)s) " - "(Output: %(out)s) ") % \ - {'cmd': cmd, - 'rc': rc, - 'out': out.split('\n')} - kwargs["data"] = msg - super(EMCVnxCLICmdError, self).__init__(**kwargs) - if log_as_error: - LOG.error(msg) - else: - LOG.warn(msg) + message = _("EMC VNX Cinder Driver CLI exception: %(cmd)s " + "(Return Code: %(rc)s) (Output: %(out)s).") # ConsistencyGroup diff --git a/cinder/tests/test_emc_vnxdirect.py b/cinder/tests/test_emc_vnxdirect.py index 4848b7738..6a5c873d1 100644 --- a/cinder/tests/test_emc_vnxdirect.py +++ b/cinder/tests/test_emc_vnxdirect.py @@ -12,7 +12,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - import os import re @@ -28,7 +27,6 @@ from cinder.volume.drivers.emc.emc_cli_iscsi import EMCCLIISCSIDriver import cinder.volume.drivers.emc.emc_vnx_cli as emc_vnx_cli from cinder.volume.drivers.emc.emc_vnx_cli import CommandLineHelper from cinder.volume.drivers.emc.emc_vnx_cli import EMCVnxCLICmdError -from cinder.volume import volume_types from cinder.zonemanager.fc_san_lookup_service import FCSanLookupService SUCCEED = ("", 0) @@ -44,6 +42,7 @@ class EMCVNXCLIDriverTestData(): 'id': '1', 'provider_auth': None, 'project_id': 'project', + 'provider_location': 'system^FNM11111|type^lun|lun_id^1', 'display_name': 'vol1', 'display_description': 'test volume', 'volume_type_id': None, @@ -141,11 +140,37 @@ class EMCVNXCLIDriverTestData(): 'consistencygroup_id': None, 'display_description': 'test failed volume', 'volume_type_id': None} + + test_volume1_in_sg = { + 'name': 'vol1_in_sg', + 'size': 1, + 'volume_name': 'vol1_in_sg', + 'id': '4', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'failed_vol', + 'display_description': 'Volume 1 in SG', + 'volume_type_id': None, + 'provider_location': 'system^fakesn|type^lun|id^4'} + + test_volume2_in_sg = { + 'name': 'vol2_in_sg', + 'size': 1, + 'volume_name': 'vol2_in_sg', + 'id': '5', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'failed_vol', + 'display_description': 'Volume 2 in SG', + 'volume_type_id': None, + 'provider_location': 'system^fakesn|type^lun|id^3'} + test_snapshot = { 'name': 'snapshot1', 'size': 1, 'id': '4444', 'volume_name': 'vol1', + 'volume': test_volume, 'volume_size': 1, 'consistencygroup_id': None, 'cgsnapshot_id': None, @@ -155,6 +180,7 @@ class EMCVNXCLIDriverTestData(): 'size': 1, 'id': '5555', 'volume_name': 'vol-vol1', + 'volume': test_volume, 'volume_size': 1, 'project_id': 'project'} test_clone = { @@ -200,6 +226,7 @@ class EMCVNXCLIDriverTestData(): 'attach_status': 'detached', 'volume_type': [], 'attached_host': None, + 'provider_location': 'system^FNM11111|type^lun|lun_id^1', '_name_id': None, 'volume_metadata': []} test_new_type = {'name': 'voltype0', 'qos_specs_id': None, @@ -322,14 +349,15 @@ class EMCVNXCLIDriverTestData(): '-allowReadWrite', 'yes', '-allowAutoDelete', 'no') + def SNAP_LIST_CMD(self, res_id=1, poll=True): + cmd = ('snap', '-list', '-res', res_id) + if not poll: + cmd = ('-np',) + cmd + return cmd + def LUN_DELETE_CMD(self, name): return ('lun', '-destroy', '-name', name, '-forceDetach', '-o') - def LUN_CREATE_CMD(self, name, isthin=False): - return ('lun', '-create', '-type', 'Thin' if isthin else 'NonThin', - '-capacity', 1, '-sq', 'gb', '-poolName', - 'unit_test_pool', '-name', name) - def LUN_EXTEND_CMD(self, name, newsize): return ('lun', '-expand', '-name', name, '-capacity', newsize, '-sq', 'gb', '-o', '-ignoreThresholds') @@ -340,8 +368,9 @@ class EMCVNXCLIDriverTestData(): '-attachedSnapshot') def MIGRATION_CMD(self, src_id=1, dest_id=1): - return ("migrate", "-start", "-source", src_id, "-dest", dest_id, - "-rate", "high", "-o") + cmd = ("migrate", "-start", "-source", src_id, "-dest", dest_id, + "-rate", "high", "-o") + return cmd def MIGRATION_VERIFY_CMD(self, src_id): return ("migrate", "-list", "-source", src_id) @@ -351,7 +380,7 @@ class EMCVNXCLIDriverTestData(): def PINGNODE_CMD(self, sp, portid, vportid, ip): return ("connection", "-pingnode", "-sp", sp, '-portid', portid, - "-vportid", vportid, "-address", ip) + "-vportid", vportid, "-address", ip, '-count', '1') def GETFCPORT_CMD(self): return ('port', '-list', '-sp') @@ -364,6 +393,16 @@ class EMCVNXCLIDriverTestData(): return ('compression', '-on', '-l', lun_id, '-ignoreThresholds', '-o') + def STORAGEGROUP_LIST_CMD(self, gname=None): + if gname: + return ('storagegroup', '-list', '-gname', gname) + else: + return ('storagegroup', '-list') + + def STORAGEGROUP_REMOVEHLU_CMD(self, gname, hlu): + return ('storagegroup', '-removehlu', + '-hlu', hlu, '-gname', gname, '-o') + provisioning_values = { 'thin': ['-type', 'Thin'], 'thick': ['-type', 'NonThin'], @@ -386,12 +425,15 @@ class EMCVNXCLIDriverTestData(): '-initialTier', 'optimizePool', '-tieringPolicy', 'noMovement']} - def LUN_CREATION_CMD(self, name, size, pool, provisioning, tiering): + def LUN_CREATION_CMD(self, name, size, pool, provisioning, tiering, + poll=True): initial = ['lun', '-create', '-capacity', size, '-sq', 'gb', '-poolName', pool, '-name', name] + if not poll: + initial = ['-np'] + initial if provisioning: initial.extend(self.provisioning_values[provisioning]) else: @@ -401,7 +443,7 @@ class EMCVNXCLIDriverTestData(): return tuple(initial) def CHECK_FASTCACHE_CMD(self, storage_pool): - return ('-np', 'storagepool', '-list', '-name', + return ('storagepool', '-list', '-name', storage_pool, '-fastcache') def CREATE_CONSISTENCYGROUP_CMD(self, cg_name): @@ -454,11 +496,12 @@ State: Ready POOL_PROPERTY = ("""\ Pool Name: unit_test_pool Pool ID: 1 -User Capacity (Blocks): 5769501696 -User Capacity (GBs): 10000.5 -Available Capacity (Blocks): 5676521472 -Available Capacity (GBs): 1000.6 - """, 0) +User Capacity (Blocks): 6881061888 +User Capacity (GBs): 3281.146 +Available Capacity (Blocks): 6832207872 +Available Capacity (GBs): 3257.851 + +""", 0) ALL_PORTS = ("SP: A\n" + "Port ID: 4\n" + @@ -477,7 +520,7 @@ Available Capacity (GBs): 1000.6 'target_discovered': True, 'target_iqn': 'iqn.1992-04.com.emc:cx.fnm00124000215.a4', - 'target_lun': 1, + 'target_lun': 2, 'target_portal': '10.244.214.118:3260'}, 'driver_volume_type': 'iscsi'} @@ -486,7 +529,7 @@ Available Capacity (GBs): 1000.6 'target_discovered': True, 'target_iqn': 'iqn.1992-04.com.emc:cx.fnm00124000215.a4', - 'target_lun': 1, + 'target_lun': 2, 'target_portal': '10.244.214.118:3260'}, 'driver_volume_type': 'iscsi'} @@ -631,6 +674,89 @@ Available Capacity (GBs): 1000.6 1 1 Shareable: YES""" % sgname, 0) + def STORAGE_GROUP_HAS_MAP_2(self, sgname): + + return ("""\ + Storage Group Name: %s + Storage Group UID: 54:46:57:0F:15:A2:E3:11:9A:8D:FF:E5:3A:03:FD:6D + HBA/SP Pairs: + + HBA UID SP Name SPPort + ------- ------- ------ + iqn.1993-08.org.debian:01:222 SP A 4 + + HLU/ALU Pairs: + + HLU Number ALU Number + ---------- ---------- + 1 1 + 2 3 + Shareable: YES""" % sgname, 0) + + def POOL_FEATURE_INFO_POOL_LUNS_CMD(self): + cmd = ('storagepool', '-feature', '-info', + '-maxPoolLUNs', '-numPoolLUNs') + return cmd + + def POOL_FEATURE_INFO_POOL_LUNS(self, max, total): + return (('Max. Pool LUNs: %s\n' % max) + + ('Total Number of Pool LUNs: %s\n' % total), 0) + + def STORAGE_GROUPS_HAS_MAP(self, sgname1, sgname2): + + return (""" + + Storage Group Name: irrelative + Storage Group UID: 9C:86:4F:30:07:76:E4:11:AC:83:C8:C0:8E:9C:D6:1F + HBA/SP Pairs: + + HBA UID SP Name SPPort + ------- ------- ------ + iqn.1993-08.org.debian:01:5741c6307e60 SP A 6 + + Storage Group Name: %(sgname1)s + Storage Group UID: 54:46:57:0F:15:A2:E3:11:9A:8D:FF:E5:3A:03:FD:6D + HBA/SP Pairs: + + HBA UID SP Name SPPort + ------- ------- ------ + iqn.1993-08.org.debian:01:222 SP A 4 + + HLU/ALU Pairs: + + HLU Number ALU Number + ---------- ---------- + 31 3 + 41 4 + Shareable: YES + + Storage Group Name: %(sgname2)s + Storage Group UID: 9C:86:4F:30:07:76:E4:11:AC:83:C8:C0:8E:9C:D6:1F + HBA/SP Pairs: + + HBA UID SP Name SPPort + ------- ------- ------ + iqn.1993-08.org.debian:01:5741c6307e60 SP A 6 + + HLU/ALU Pairs: + + HLU Number ALU Number + ---------- ---------- + 32 3 + 42 4 + Shareable: YES""" % {'sgname1': sgname1, + 'sgname2': sgname2}, 0) + + def LUN_DELETE_IN_SG_ERROR(self, up_to_date=True): + if up_to_date: + return ("Cannot unbind LUN " + "because it's contained in a Storage Group", + 156) + else: + return ("SP B: Request failed. " + "Host LUN/LUN mapping still exists.", + 0) + class EMCVNXCLIDriverISCSITestCase(test.TestCase): @@ -638,7 +764,7 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): super(EMCVNXCLIDriverISCSITestCase, self).setUp() self.stubs.Set(CommandLineHelper, 'command_execute', - self.succeed_fake_command_execute) + self.fake_setup_command_execute) self.stubs.Set(CommandLineHelper, 'get_array_serial', mock.Mock(return_value={'array_serial': 'fakeSerial'})) @@ -658,15 +784,13 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): #set the timeout to 0.012s = 0.0002 * 60 = 1.2ms self.configuration.default_timeout = 0.0002 self.configuration.initiator_auto_registration = True + self.configuration.check_max_pool_luns_threshold = False self.stubs.Set(self.configuration, 'safe_get', self.fake_safe_get) self.testData = EMCVNXCLIDriverTestData() self.navisecclicmd = '/opt/Navisphere/bin/naviseccli ' + \ '-address 10.0.0.1 -user sysadmin -password sysadmin -scope 0 ' self.configuration.iscsi_initiators = '{"fakehost": ["10.0.0.2"]}' - def tearDown(self): - super(EMCVNXCLIDriverISCSITestCase, self).tearDown() - def driverSetup(self, commands=tuple(), results=tuple()): self.driver = EMCCLIISCSIDriver(configuration=self.configuration) fake_command_execute = self.get_command_execute_simulator( @@ -677,7 +801,6 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): def get_command_execute_simulator(self, commands=tuple(), results=tuple()): - assert(len(commands) == len(results)) def fake_command_execute(*args, **kwargv): @@ -728,8 +851,9 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): mock.call(*self.testData.LUN_CREATION_CMD( 'vol1', 1, 'unit_test_pool', - 'thick', None)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), + 'thick', None, False)), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False), mock.call(*self.testData.LUN_DELETE_CMD('vol1'))] fake_cli.assert_has_calls(expect_cmd) @@ -737,14 +861,16 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): @mock.patch( "eventlet.event.Event.wait", mock.Mock(return_value=None)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'compressed'})) def test_create_volume_compressed(self): - extra_specs = {'storagetype:provisioning': 'compressed'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), + self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD] results = [self.testData.LUN_PROPERTY('vol_with_type', True), + self.testData.LUN_PROPERTY('vol_with_type', True), self.testData.NDU_LIST_RESULT] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', @@ -758,11 +884,11 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): mock.call(*self.testData.LUN_CREATION_CMD( 'vol_with_type', 1, 'unit_test_pool', - 'compressed', None)), + 'compressed', None, False)), mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( - 'vol_with_type')), + 'vol_with_type'), poll=False), mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( - 'vol_with_type')), + 'vol_with_type'), poll=True), mock.call(*self.testData.ENABLE_COMPRESSION_CMD( 1))] fake_cli.assert_has_calls(expect_cmd) @@ -770,15 +896,17 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): @mock.patch( "eventlet.event.Event.wait", mock.Mock(return_value=None)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'compressed', + 'storagetype:tiering': 'HighestAvailable'})) def test_create_volume_compressed_tiering_highestavailable(self): - extra_specs = {'storagetype:provisioning': 'compressed', - 'storagetype:tiering': 'HighestAvailable'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), + self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD] results = [self.testData.LUN_PROPERTY('vol_with_type', True), + self.testData.LUN_PROPERTY('vol_with_type', True), self.testData.NDU_LIST_RESULT] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', @@ -793,11 +921,11 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): mock.call(*self.testData.LUN_CREATION_CMD( 'vol_with_type', 1, 'unit_test_pool', - 'compressed', 'highestavailable')), + 'compressed', 'highestavailable', False)), mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( - 'vol_with_type')), + 'vol_with_type'), poll=False), mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( - 'vol_with_type')), + 'vol_with_type'), poll=True), mock.call(*self.testData.ENABLE_COMPRESSION_CMD( 1))] fake_cli.assert_has_calls(expect_cmd) @@ -805,14 +933,16 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): @mock.patch( "eventlet.event.Event.wait", mock.Mock(return_value=None)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'deduplicated'})) def test_create_volume_deduplicated(self): - extra_specs = {'storagetype:provisioning': 'deduplicated'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), + self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD] results = [self.testData.LUN_PROPERTY('vol_with_type', True), + self.testData.LUN_PROPERTY('vol_with_type', True), self.testData.NDU_LIST_RESULT] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', @@ -827,20 +957,22 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): mock.call(*self.testData.LUN_CREATION_CMD( 'vol_with_type', 1, 'unit_test_pool', - 'deduplicated', None))] + 'deduplicated', None, False))] fake_cli.assert_has_calls(expect_cmd) @mock.patch( "eventlet.event.Event.wait", mock.Mock(return_value=None)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:tiering': 'Auto'})) def test_create_volume_tiering_auto(self): - extra_specs = {'storagetype:tiering': 'Auto'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), + self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD] results = [self.testData.LUN_PROPERTY('vol_with_type', True), + self.testData.LUN_PROPERTY('vol_with_type', True), self.testData.NDU_LIST_RESULT] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', @@ -855,15 +987,15 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): mock.call(*self.testData.LUN_CREATION_CMD( 'vol_with_type', 1, 'unit_test_pool', - None, 'auto'))] + None, 'auto', False))] fake_cli.assert_has_calls(expect_cmd) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:tiering': 'Auto', + 'storagetype:provisioning': 'Deduplicated'})) def test_create_volume_deduplicated_tiering_auto(self): - extra_specs = {'storagetype:tiering': 'Auto', - 'storagetype:provisioning': 'Deduplicated'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD] results = [self.testData.LUN_PROPERTY('vol_with_type', True), @@ -877,11 +1009,11 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): re.match(r".*deduplicated and auto tiering can't be both enabled", ex.msg)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'Compressed'})) def test_create_volume_compressed_no_enabler(self): - extra_specs = {'storagetype:provisioning': 'Compressed'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD] results = [self.testData.LUN_PROPERTY('vol_with_type', True), @@ -898,6 +1030,11 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): @mock.patch( "eventlet.event.Event.wait", mock.Mock(return_value=None)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'Compressed', + 'storagetype:pool': 'unit_test_pool'})) def test_create_compression_volume_on_array_backend(self): """Unit test for create a compression volume on array backend. @@ -917,14 +1054,11 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): self.driver = EMCCLIISCSIDriver(configuration=config) assert isinstance(self.driver.cli, emc_vnx_cli.EMCVnxCliArray) - extra_specs = {'storagetype:provisioning': 'Compressed', - 'storagetype:pool': 'unit_test_pool'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), + self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD] results = [self.testData.LUN_PROPERTY('vol_with_type', True), + self.testData.LUN_PROPERTY('vol_with_type', True), self.testData.NDU_LIST_RESULT] fake_command_execute = self.get_command_execute_simulator( commands, results) @@ -943,32 +1077,31 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): mock.call(*self.testData.LUN_CREATION_CMD( 'vol_with_type', 1, 'unit_test_pool', - 'compressed', None)), + 'compressed', None, False)), mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( - 'vol_with_type')), + 'vol_with_type'), poll=False), mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( - 'vol_with_type')), + 'vol_with_type'), poll=True), mock.call(*self.testData.ENABLE_COMPRESSION_CMD( 1))] fake_cli.assert_has_calls(expect_cmd) def test_get_volume_stats(self): - #expect_result = [POOL_PROPERTY] self.driverSetup() stats = self.driver.get_volume_stats(True) self.assertTrue(stats['driver_version'] is not None, - "dirver_version is not returned") + "driver_version is not returned") self.assertTrue( - stats['free_capacity_gb'] == 1000.6, + stats['free_capacity_gb'] == 3257.851, "free_capacity_gb is not correct") self.assertTrue( - stats['reserved_percentage'] == 0, + stats['reserved_percentage'] == 3, "reserved_percentage is not correct") self.assertTrue( stats['storage_protocol'] == 'iSCSI', "storage_protocol is not correct") self.assertTrue( - stats['total_capacity_gb'] == 10000.5, + stats['total_capacity_gb'] == 3281.146, "total_capacity_gb is not correct") self.assertTrue( stats['vendor_name'] == "EMC", @@ -978,12 +1111,39 @@ class EMCVNXCLIDriverISCSITestCase(test.TestCase): "volume backend name is not correct") self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial") self.assertTrue( - stats['driver_version'] == "04.01.00", + stats['driver_version'] == "05.00.00", "driver version is incorrect.") + def test_get_volume_stats_too_many_luns(self): + commands = [self.testData.POOL_FEATURE_INFO_POOL_LUNS_CMD()] + results = [self.testData.POOL_FEATURE_INFO_POOL_LUNS(1000, 1000)] + fake_cli = self.driverSetup(commands, results) + + self.driver.cli.check_max_pool_luns_threshold = True + stats = self.driver.get_volume_stats(True) + self.assertTrue( + stats['free_capacity_gb'] == 0, + "free_capacity_gb is not correct") + expect_cmd = [ + mock.call(*self.testData.POOL_FEATURE_INFO_POOL_LUNS_CMD(), + poll=False)] + fake_cli.assert_has_calls(expect_cmd) + expect_cmd = [ + mock.call(*self.testData.POOL_FEATURE_INFO_POOL_LUNS_CMD(), + poll=False)] + fake_cli.assert_has_calls(expect_cmd) + + self.driver.cli.check_max_pool_luns_threshold = False + stats = self.driver.get_volume_stats(True) + self.assertTrue(stats['driver_version'] is not None, + "driver_version is not returned") + self.assertTrue( + stats['free_capacity_gb'] == 3257.851, + "free_capacity_gb is not correct") + @mock.patch("cinder.volume.drivers.emc.emc_vnx_cli." "CommandLineHelper.create_lun_by_cmd", - mock.Mock(return_value=True)) + mock.Mock(return_value={'lun_id': 1})) @mock.patch( "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase.get_lun_id", mock.Mock( @@ -1012,10 +1172,10 @@ Percent Complete: 100 Time Remaining: 0 second(s) """ results = [(FAKE_ERROR_MSG, 255), - [SUCCEED, - (FAKE_MIGRATE_PROPERTY, 0), - ('The specified source LUN is not currently migrating', - 23)]] + [(FAKE_MIGRATE_PROPERTY, 0), + (FAKE_MIGRATE_PROPERTY, 0), + ('The specified source LUN is not currently migrating', + 23)]] fake_cli = self.driverSetup(commands, results) fakehost = {'capabilities': {'location_info': "unit_test_pool2|fakeSerial", @@ -1025,15 +1185,20 @@ Time Remaining: 0 second(s) self.assertTrue(ret) #verification expect_cmd = [mock.call(*self.testData.MIGRATION_CMD(1, 1), - retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1))] + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=False)] fake_cli.assert_has_calls(expect_cmd) @mock.patch("cinder.volume.drivers.emc.emc_vnx_cli." "CommandLineHelper.create_lun_by_cmd", mock.Mock( - return_value=True)) + return_value={'lun_id': 1})) @mock.patch( "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase.get_lun_id", mock.Mock( @@ -1056,32 +1221,39 @@ Current State: MIGRATED Percent Complete: 100 Time Remaining: 0 second(s) """ - results = [SUCCEED, [(FAKE_MIGRATE_PROPERTY, 0), - ('The specified source LUN is not ' - 'currently migrating', - 23)]] + results = [SUCCEED, + [(FAKE_MIGRATE_PROPERTY, 0), + ('The specified source LUN is not ' + 'currently migrating', 23)]] fake_cli = self.driverSetup(commands, results) - fakehost = {'capabilities': {'location_info': - "unit_test_pool2|fakeSerial", - 'storage_protocol': 'iSCSI'}} + fake_host = {'capabilities': {'location_info': + "unit_test_pool2|fakeSerial", + 'storage_protocol': 'iSCSI'}} ret = self.driver.migrate_volume(None, self.testData.test_volume, - fakehost)[0] + fake_host)[0] self.assertTrue(ret) #verification expect_cmd = [mock.call(*self.testData.MIGRATION_CMD(), - retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1))] + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=False)] fake_cli.assert_has_calls(expect_cmd) @mock.patch("cinder.volume.drivers.emc.emc_vnx_cli." "CommandLineHelper.create_lun_by_cmd", mock.Mock( - return_value=True)) + return_value={'lun_id': 5})) @mock.patch( "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase." "get_lun_id_by_name", mock.Mock(return_value=5)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:tiering': 'Auto'})) def test_volume_migration_02(self): commands = [self.testData.MIGRATION_CMD(5, 5), @@ -1096,10 +1268,10 @@ Current State: MIGRATED Percent Complete: 100 Time Remaining: 0 second(s) """ - results = [SUCCEED, [(FAKE_MIGRATE_PROPERTY, 0), - ('The specified source LUN is not ' - 'currently migrating', - 23)]] + results = [SUCCEED, + [(FAKE_MIGRATE_PROPERTY, 0), + ('The specified source LUN is not currently migrating', + 23)]] fake_cli = self.driverSetup(commands, results) fakehost = {'capabilities': {'location_info': "unit_test_pool2|fakeSerial", @@ -1109,15 +1281,18 @@ Time Remaining: 0 second(s) self.assertTrue(ret) #verification expect_cmd = [mock.call(*self.testData.MIGRATION_CMD(5, 5), - retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(5)), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(5))] + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(5), + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(5), + poll=False)] fake_cli.assert_has_calls(expect_cmd) @mock.patch("cinder.volume.drivers.emc.emc_vnx_cli." "CommandLineHelper.create_lun_by_cmd", mock.Mock( - return_value=True)) + return_value={'lun_id': 1})) @mock.patch( "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase.get_lun_id", mock.Mock( @@ -1138,7 +1313,8 @@ Time Remaining: 0 second(s) self.assertFalse(ret) #verification expect_cmd = [mock.call(*self.testData.MIGRATION_CMD(), - retry_disable=True)] + retry_disable=True, + poll=True)] fake_cli.assert_has_calls(expect_cmd) def test_create_destroy_volume_snapshot(self): @@ -1149,9 +1325,10 @@ Time Remaining: 0 second(s) self.driver.delete_snapshot(self.testData.test_snapshot) #verification - expect_cmd = [mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call(*self.testData.SNAP_CREATE_CMD('snapshot1')), - mock.call(*self.testData.SNAP_DELETE_CMD('snapshot1'))] + expect_cmd = [mock.call(*self.testData.SNAP_CREATE_CMD('snapshot1'), + poll=False), + mock.call(*self.testData.SNAP_DELETE_CMD('snapshot1'), + poll=True)] fake_cli.assert_has_calls(expect_cmd) @@ -1160,21 +1337,19 @@ Time Remaining: 0 second(s) mock.Mock( return_value=( "fakeportal iqn.1992-04.fake.com:fake.apm00123907237.a8", 0))) - @mock.patch("random.shuffle", mock.Mock()) + @mock.patch('random.randint', + mock.Mock(return_value=0)) def test_initialize_connection(self): # Test for auto registration self.configuration.initiator_auto_registration = True commands = [('storagegroup', '-list', '-gname', 'fakehost'), - self.testData.GETPORT_CMD(), self.testData.PINGNODE_CMD('A', 4, 0, '10.0.0.2')] results = [[("No group", 83), - self.testData.STORAGE_GROUP_NO_MAP('fakehost'), - self.testData.STORAGE_GROUP_HAS_MAP('fakehost'), self.testData.STORAGE_GROUP_HAS_MAP('fakehost')], - self.testData.ALL_PORTS, self.testData.PING_OK] fake_cli = self.driverSetup(commands, results) + connection_info = self.driver.initialize_connection( self.testData.test_volume, self.testData.connector) @@ -1182,22 +1357,20 @@ Time Remaining: 0 second(s) self.assertEqual(connection_info, self.testData.iscsi_connection_info_ro) - expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost'), + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), mock.call('storagegroup', '-create', '-gname', 'fakehost'), - mock.call('storagegroup', '-list'), - mock.call(*self.testData.GETPORT_CMD()), mock.call('storagegroup', '-gname', 'fakehost', '-setpath', '-hbauid', 'iqn.1993-08.org.debian:01:222', '-sp', 'A', '-spport', 4, '-spvport', 0, '-ip', '10.0.0.2', '-host', 'fakehost', '-o'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call('storagegroup', '-addhlu', '-hlu', 1, '-alu', 1, - '-gname', 'fakehost'), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call(*self.testData.GETPORT_CMD()), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, + '-gname', 'fakehost', + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False), mock.call(*self.testData.PINGNODE_CMD('A', 4, 0, '10.0.0.2'))] fake_cli.assert_has_calls(expected) @@ -1207,35 +1380,168 @@ Time Remaining: 0 second(s) commands = [('storagegroup', '-list', '-gname', 'fakehost'), self.testData.CONNECTHOST_CMD('fakehost', 'fakehost'), - self.testData.GETPORT_CMD(), self.testData.PINGNODE_CMD('A', 4, 0, '10.0.0.2')] - results = [[("No group", 83), - self.testData.STORAGE_GROUP_NO_MAP('fakehost'), - self.testData.STORAGE_GROUP_HAS_MAP('fakehost'), - self.testData.STORAGE_GROUP_HAS_MAP('fakehost')], - ('', 0), - self.testData.ALL_PORTS, - self.testData.PING_OK] + results = [ + [("No group", 83), + self.testData.STORAGE_GROUP_HAS_MAP('fakehost')], + ('', 0), + self.testData.PING_OK + ] fake_cli = self.driverSetup(commands, results) + test_volume_rw = self.testData.test_volume_rw.copy() + test_volume_rw['provider_location'] = 'system^fakesn|type^lun|id^1' connection_info = self.driver.initialize_connection( - self.testData.test_volume_rw, + test_volume_rw, self.testData.connector) self.assertEqual(connection_info, self.testData.iscsi_connection_info_rw) - expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost'), + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), mock.call('storagegroup', '-create', '-gname', 'fakehost'), mock.call('storagegroup', '-connecthost', '-host', 'fakehost', '-gname', 'fakehost', '-o'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call('storagegroup', '-addhlu', '-hlu', 1, '-alu', 1, - '-gname', 'fakehost'), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call('connection', '-getport', '-address', '-vlanid')] + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, + '-gname', 'fakehost', poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False), + mock.call(*self.testData.PINGNODE_CMD('A', 4, 0, + '10.0.0.2'))] + fake_cli.assert_has_calls(expected) + + @mock.patch( + "oslo_concurrency.processutils.execute", + mock.Mock( + return_value=( + "fakeportal iqn.1992-04.fake.com:fake.apm00123907237.a8", 0))) + @mock.patch( + "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase.get_lun_id", + mock.Mock( + return_value=3)) + @mock.patch('random.randint', + mock.Mock(return_value=0)) + def test_initialize_connection_exist(self): + """A LUN is added to the SG right before the attach, + it may not exists in the first SG query + """ + # Test for auto registration + self.configuration.initiator_auto_registration = True + self.configuration.max_luns_per_storage_group = 2 + commands = [('storagegroup', '-list', '-gname', 'fakehost'), + ('storagegroup', '-addhlu', '-hlu', 2, '-alu', 3, + '-gname', 'fakehost'), + self.testData.PINGNODE_CMD('A', 4, 0, '10.0.0.2')] + results = [[self.testData.STORAGE_GROUP_HAS_MAP('fakehost'), + self.testData.STORAGE_GROUP_HAS_MAP_2('fakehost')], + ("fakeerror", 23), + self.testData.PING_OK] + + fake_cli = self.driverSetup(commands, results) + + iscsi_data = self.driver.initialize_connection( + self.testData.test_volume, + self.testData.connector + ) + self.assertTrue(iscsi_data['data']['target_lun'] == 2, + "iSCSI initialize connection returned wrong HLU") + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 3, + '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False), + mock.call(*self.testData.PINGNODE_CMD('A', 4, 0, + '10.0.0.2'))] + fake_cli.assert_has_calls(expected) + + @mock.patch( + "oslo_concurrency.processutils.execute", + mock.Mock( + return_value=( + "fakeportal iqn.1992-04.fake.com:fake.apm00123907237.a8", 0))) + @mock.patch( + "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase.get_lun_id", + mock.Mock( + return_value=4)) + @mock.patch('random.randint', + mock.Mock(return_value=0)) + def test_initialize_connection_no_hlu_left_1(self): + """There is no hlu per the first SG query + But there are hlu left after the full poll + """ + # Test for auto registration + self.configuration.initiator_auto_registration = True + self.configuration.max_luns_per_storage_group = 2 + commands = [('storagegroup', '-list', '-gname', 'fakehost'), + ('storagegroup', '-addhlu', '-hlu', 2, '-alu', 4, + '-gname', 'fakehost'), + self.testData.PINGNODE_CMD('A', 4, 0, '10.0.0.2')] + results = [[self.testData.STORAGE_GROUP_HAS_MAP_2('fakehost'), + self.testData.STORAGE_GROUP_HAS_MAP('fakehost')], + ("", 0), + self.testData.PING_OK] + + fake_cli = self.driverSetup(commands, results) + + iscsi_data = self.driver.initialize_connection( + self.testData.test_volume, + self.testData.connector) + self.assertTrue(iscsi_data['data']['target_lun'] == 2, + "iSCSI initialize connection returned wrong HLU") + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 4, + '-gname', 'fakehost', + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False), + mock.call(*self.testData.PINGNODE_CMD('A', 4, 0, + u'10.0.0.2'))] + fake_cli.assert_has_calls(expected) + + @mock.patch( + "oslo_concurrency.processutils.execute", + mock.Mock( + return_value=( + "fakeportal iqn.1992-04.fake.com:fake.apm00123907237.a8", 0))) + @mock.patch( + "cinder.volume.drivers.emc.emc_vnx_cli.EMCVnxCliBase.get_lun_id", + mock.Mock( + return_value=4)) + @mock.patch('random.randint', + mock.Mock(return_value=0)) + def test_initialize_connection_no_hlu_left_2(self): + """There is no usable hlu for the SG + """ + # Test for auto registration + self.configuration.initiator_auto_registration = True + self.configuration.max_luns_per_storage_group = 2 + commands = [('storagegroup', '-list', '-gname', 'fakehost')] + results = [ + [self.testData.STORAGE_GROUP_HAS_MAP_2('fakehost'), + self.testData.STORAGE_GROUP_HAS_MAP_2('fakehost')] + ] + + fake_cli = self.driverSetup(commands, results) + + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.initialize_connection, + self.testData.test_volume, + self.testData.connector) + expected = [ + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + ] fake_cli.assert_has_calls(expected) def test_terminate_connection(self): @@ -1269,7 +1575,7 @@ Time Remaining: 0 second(s) def test_create_volume_cli_failed(self): commands = [self.testData.LUN_CREATION_CMD( - 'failed_vol1', 1, 'unit_test_pool', None, None)] + 'failed_vol1', 1, 'unit_test_pool', None, None, False)] results = [FAKE_ERROR_RETURN] fake_cli = self.driverSetup(commands, results) @@ -1277,7 +1583,7 @@ Time Remaining: 0 second(s) self.driver.create_volume, self.testData.test_failed_volume) expect_cmd = [mock.call(*self.testData.LUN_CREATION_CMD( - 'failed_vol1', 1, 'unit_test_pool', None, None))] + 'failed_vol1', 1, 'unit_test_pool', None, None, False))] fake_cli.assert_has_calls(expect_cmd) def test_create_volume_snapshot_failed(self): @@ -1293,29 +1599,24 @@ Time Remaining: 0 second(s) #verification expect_cmd = [ mock.call( - *self.testData.LUN_PROPERTY_ALL_CMD( - 'vol-vol1')), - mock.call( - *self.testData.SNAP_CREATE_CMD( - 'failed_snapshot'))] + *self.testData.SNAP_CREATE_CMD('failed_snapshot'), + poll=False)] fake_cli.assert_has_calls(expect_cmd) def test_create_volume_from_snapshot(self): #set up - cmd_smp = ('lun', '-list', '-name', 'vol2', '-attachedSnapshot') - output_smp = ("""LOGICAL UNIT NUMBER 1 - Name: vol2 - Attached Snapshot: N/A""", 0) cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest") + cmd_dest_np = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest") output_dest = self.testData.LUN_PROPERTY("vol2_dest") cmd_migrate = self.testData.MIGRATION_CMD(1, 1) output_migrate = ("", 0) cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) output_migrate_verify = (r'The specified source LUN ' 'is not currently migrating', 23) - commands = [cmd_smp, cmd_dest, cmd_migrate, cmd_migrate_verify] - results = [output_smp, output_dest, output_migrate, + commands = [cmd_dest, cmd_dest_np, cmd_migrate, + cmd_migrate_verify] + results = [output_dest, output_dest, output_migrate, output_migrate_verify] fake_cli = self.driverSetup(commands, results) @@ -1324,41 +1625,81 @@ Time Remaining: 0 second(s) expect_cmd = [ mock.call( *self.testData.SNAP_MP_CREATE_CMD( - name='vol2', source='vol1')), + name='vol2', source='vol1'), + poll=False), mock.call( *self.testData.SNAP_ATTACH_CMD( name='vol2', snapName='snapshot1')), mock.call(*self.testData.LUN_CREATION_CMD( 'vol2_dest', 1, 'unit_test_pool', None, None)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2'), + poll=True), mock.call(*self.testData.MIGRATION_CMD(1, 1), - retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)), - - mock.call('lun', '-list', '-name', 'vol2', '-attachedSnapshot')] + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True)] fake_cli.assert_has_calls(expect_cmd) @mock.patch('cinder.openstack.common.loopingcall.FixedIntervalLoopingCall', new=ZeroIntervalLoopingCall) def test_create_volume_from_snapshot_sync_failed(self): - output_smp = ("""LOGICAL UNIT NUMBER 1 - Name: vol1 - Attached Snapshot: fakesnap""", 0) - cmd_smp = ('lun', '-list', '-name', 'vol2', '-attachedSnapshot') cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest") + cmd_dest_np = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest") output_dest = self.testData.LUN_PROPERTY("vol2_dest") cmd_migrate = self.testData.MIGRATION_CMD(1, 1) + cmd_detach_lun = ('lun', '-detach', '-name', 'vol2') output_migrate = ("", 0) cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) - output_migrate_verify = (r'The specified source LUN ' - 'is not currently migrating', 23) - commands = [cmd_smp, cmd_dest, cmd_migrate, cmd_migrate_verify] - results = [output_smp, output_dest, output_migrate, - output_migrate_verify] + + commands = [cmd_dest, cmd_dest_np, cmd_migrate, + cmd_migrate_verify] + results = [output_dest, output_dest, output_migrate, + FAKE_ERROR_RETURN] + fake_cli = self.driverSetup(commands, results) + + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.create_volume_from_snapshot, + self.testData.test_volume2, + self.testData.test_snapshot) + expect_cmd = [ + mock.call( + *self.testData.SNAP_MP_CREATE_CMD( + name='vol2', source='vol1'), + poll=False), + mock.call( + *self.testData.SNAP_ATTACH_CMD( + name='vol2', snapName='snapshot1')), + mock.call(*self.testData.LUN_CREATION_CMD( + 'vol2_dest', 1, 'unit_test_pool', None, None)), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2'), + poll=True), + mock.call(*self.testData.MIGRATION_CMD(1, 1), + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True), + mock.call(*self.testData.LUN_DELETE_CMD('vol2_dest')), + mock.call(*cmd_detach_lun), + mock.call(*self.testData.LUN_DELETE_CMD('vol2'))] + fake_cli.assert_has_calls(expect_cmd) + + def test_create_vol_from_snap_failed_in_migrate_lun(self): + cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest") + output_dest = self.testData.LUN_PROPERTY("vol2_dest") + cmd_migrate = self.testData.MIGRATION_CMD(1, 1) + cmd_detach_lun = ('lun', '-detach', '-name', 'vol2') + commands = [cmd_dest, cmd_migrate] + results = [output_dest, FAKE_ERROR_RETURN] fake_cli = self.driverSetup(commands, results) self.assertRaises(exception.VolumeBackendAPIException, @@ -1368,36 +1709,38 @@ Time Remaining: 0 second(s) expect_cmd = [ mock.call( *self.testData.SNAP_MP_CREATE_CMD( - name='vol2', source='vol1')), + name='vol2', source='vol1'), poll=False), mock.call( *self.testData.SNAP_ATTACH_CMD( name='vol2', snapName='snapshot1')), mock.call(*self.testData.LUN_CREATION_CMD( 'vol2_dest', 1, 'unit_test_pool', None, None)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2'), poll=True), mock.call(*self.testData.MIGRATION_CMD(1, 1), + poll=True, retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1))] + mock.call(*self.testData.LUN_DELETE_CMD('vol2_dest')), + mock.call(*cmd_detach_lun), + mock.call(*self.testData.LUN_DELETE_CMD('vol2'))] fake_cli.assert_has_calls(expect_cmd) def test_create_cloned_volume(self): - cmd_smp = ('lun', '-list', '-name', 'vol1', '-attachedSnapshot') - output_smp = ("""LOGICAL UNIT NUMBER 1 - Name: vol1 - Attached Snapshot: N/A""", 0) cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol1_dest") + cmd_dest_p = self.testData.LUN_PROPERTY_ALL_CMD("vol1_dest") output_dest = self.testData.LUN_PROPERTY("vol1_dest") cmd_migrate = self.testData.MIGRATION_CMD(1, 1) output_migrate = ("", 0) cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) output_migrate_verify = (r'The specified source LUN ' 'is not currently migrating', 23) - commands = [cmd_smp, cmd_dest, cmd_migrate, + commands = [cmd_dest, cmd_dest_p, cmd_migrate, cmd_migrate_verify, self.testData.NDU_LIST_CMD] - results = [output_smp, output_dest, output_migrate, + results = [output_dest, output_dest, output_migrate, output_migrate_verify, self.testData.NDU_LIST_RESULT] fake_cli = self.driverSetup(commands, results) @@ -1406,26 +1749,31 @@ Time Remaining: 0 second(s) self.testData.test_snapshot) tmp_snap = 'tmp-snap-' + self.testData.test_volume['id'] expect_cmd = [ - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('snapshot1')), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('snapshot1'), + poll=True), mock.call( - *self.testData.SNAP_CREATE_CMD(tmp_snap)), - mock.call(*self.testData.SNAP_MP_CREATE_CMD(name='vol1', - source='snapshot1')), + *self.testData.SNAP_CREATE_CMD(tmp_snap), poll=False), + mock.call(*self.testData.SNAP_MP_CREATE_CMD( + name='vol1', + source='snapshot1'), poll=False), mock.call( *self.testData.SNAP_ATTACH_CMD( name='vol1', snapName=tmp_snap)), mock.call(*self.testData.LUN_CREATION_CMD( 'vol1_dest', 1, 'unit_test_pool', None, None)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=True), mock.call(*self.testData.MIGRATION_CMD(1, 1), + poll=True, retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)), - mock.call('lun', '-list', '-name', 'vol1', '-attachedSnapshot'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call(*self.testData.SNAP_DELETE_CMD(tmp_snap))] + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True), + mock.call(*self.testData.SNAP_DELETE_CMD(tmp_snap), + poll=True)] fake_cli.assert_has_calls(expect_cmd) def test_delete_volume_failed(self): @@ -1439,6 +1787,63 @@ Time Remaining: 0 second(s) expected = [mock.call(*self.testData.LUN_DELETE_CMD('failed_vol1'))] fake_cli.assert_has_calls(expected) + def test_delete_volume_in_sg_failed(self): + commands = [self.testData.LUN_DELETE_CMD('vol1_in_sg'), + self.testData.LUN_DELETE_CMD('vol2_in_sg')] + results = [self.testData.LUN_DELETE_IN_SG_ERROR(), + self.testData.LUN_DELETE_IN_SG_ERROR(False)] + self.driverSetup(commands, results) + self.assertRaises(EMCVnxCLICmdError, + self.driver.delete_volume, + self.testData.test_volume1_in_sg) + self.assertRaises(EMCVnxCLICmdError, + self.driver.delete_volume, + self.testData.test_volume2_in_sg) + + def test_delete_volume_in_sg_force(self): + commands = [self.testData.LUN_DELETE_CMD('vol1_in_sg'), + self.testData.STORAGEGROUP_LIST_CMD(), + self.testData.STORAGEGROUP_REMOVEHLU_CMD('fakehost1', + '41'), + self.testData.STORAGEGROUP_REMOVEHLU_CMD('fakehost1', + '42'), + self.testData.LUN_DELETE_CMD('vol2_in_sg'), + self.testData.STORAGEGROUP_REMOVEHLU_CMD('fakehost2', + '31'), + self.testData.STORAGEGROUP_REMOVEHLU_CMD('fakehost2', + '32')] + results = [[self.testData.LUN_DELETE_IN_SG_ERROR(), + SUCCEED], + self.testData.STORAGE_GROUPS_HAS_MAP('fakehost1', + 'fakehost2'), + SUCCEED, + SUCCEED, + [self.testData.LUN_DELETE_IN_SG_ERROR(False), + SUCCEED], + SUCCEED, + SUCCEED] + fake_cli = self.driverSetup(commands, results) + self.driver.cli.force_delete_lun_in_sg = True + self.driver.delete_volume(self.testData.test_volume1_in_sg) + self.driver.delete_volume(self.testData.test_volume2_in_sg) + expected = [mock.call(*self.testData.LUN_DELETE_CMD('vol1_in_sg')), + mock.call(*self.testData.STORAGEGROUP_LIST_CMD(), + poll=True), + mock.call(*self.testData.STORAGEGROUP_REMOVEHLU_CMD( + 'fakehost1', '41'), poll=False), + mock.call(*self.testData.STORAGEGROUP_REMOVEHLU_CMD( + 'fakehost2', '42'), poll=False), + mock.call(*self.testData.LUN_DELETE_CMD('vol1_in_sg')), + mock.call(*self.testData.LUN_DELETE_CMD('vol2_in_sg')), + mock.call(*self.testData.STORAGEGROUP_LIST_CMD(), + poll=True), + mock.call(*self.testData.STORAGEGROUP_REMOVEHLU_CMD( + 'fakehost1', '31'), poll=False), + mock.call(*self.testData.STORAGEGROUP_REMOVEHLU_CMD( + 'fakehost2', '32'), poll=False), + mock.call(*self.testData.LUN_DELETE_CMD('vol2_in_sg'))] + fake_cli.assert_has_calls(expected) + def test_extend_volume(self): commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol1')] results = [self.testData.LUN_PROPERTY('vol1', size=2)] @@ -1446,9 +1851,10 @@ Time Remaining: 0 second(s) # case self.driver.extend_volume(self.testData.test_volume, 2) - expected = [mock.call(*self.testData.LUN_EXTEND_CMD('vol1', 2)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD( - 'vol1'))] + expected = [mock.call(*self.testData.LUN_EXTEND_CMD('vol1', 2), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False)] fake_cli.assert_has_calls(expected) def test_extend_volume_has_snapshot(self): @@ -1460,7 +1866,8 @@ Time Remaining: 0 second(s) self.driver.extend_volume, self.testData.test_failed_volume, 2) - expected = [mock.call(*self.testData.LUN_EXTEND_CMD('failed_vol1', 2))] + expected = [mock.call(*self.testData.LUN_EXTEND_CMD('failed_vol1', 2), + poll=False)] fake_cli.assert_has_calls(expected) @mock.patch('cinder.openstack.common.loopingcall.FixedIntervalLoopingCall', @@ -1476,31 +1883,19 @@ Time Remaining: 0 second(s) 3) expected = [ mock.call( - *self.testData.LUN_EXTEND_CMD('failed_vol1', 3)), + *self.testData.LUN_EXTEND_CMD('failed_vol1', 3), + poll=False), mock.call( - *self.testData.LUN_PROPERTY_ALL_CMD('failed_vol1'))] - fake_cli.assert_has_calls(expected) - - def test_create_remove_export(self): - fake_cli = self.driverSetup() - - self.driver.create_export(None, self.testData.test_volume) - self.driver.remove_export(None, self.testData.test_volume) - expected = [mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'))] + *self.testData.LUN_PROPERTY_ALL_CMD('failed_vol1'), + poll=False)] fake_cli.assert_has_calls(expected) def test_manage_existing(self): - """Unit test for the manage_existing function - of driver - """ - get_lun_cmd = ('lun', '-list', '-l', self.testData.test_lun_id, - '-state', '-userCap', '-owner', - '-attachedSnapshot', '-poolName') lun_rename_cmd = ('lun', '-modify', '-l', self.testData.test_lun_id, '-newName', 'vol_with_type', '-o') - commands = [get_lun_cmd, lun_rename_cmd] + commands = [lun_rename_cmd] - results = [self.testData.LUN_PROPERTY('lun_name'), SUCCEED] + results = [SUCCEED] self.configuration.storage_vnx_pool_name = \ self.testData.test_pool_name self.driver = EMCCLIISCSIDriver(configuration=self.configuration) @@ -1513,15 +1908,10 @@ Time Remaining: 0 second(s) self.driver.manage_existing( self.testData.test_volume_with_type, self.testData.test_existing_ref) - expected = [mock.call(*get_lun_cmd), - mock.call(*lun_rename_cmd)] + expected = [mock.call(*lun_rename_cmd, poll=False)] fake_cli.assert_has_calls(expected) def test_manage_existing_lun_in_another_pool(self): - """Unit test for the manage_existing function - of driver with a invalid pool backend. - An exception would occur in this case - """ get_lun_cmd = ('lun', '-list', '-l', self.testData.test_lun_id, '-state', '-userCap', '-owner', '-attachedSnapshot', '-poolName') @@ -1539,22 +1929,19 @@ Time Remaining: 0 second(s) self.driver.cli._client.command_execute = fake_cli ex = self.assertRaises( exception.ManageExistingInvalidReference, - self.driver.manage_existing, + self.driver.manage_existing_get_size, self.testData.test_volume_with_type, self.testData.test_existing_ref) self.assertTrue( re.match(r'.*not in a manageable pool backend by cinder', ex.msg)) - expected = [mock.call(*get_lun_cmd)] + expected = [mock.call(*get_lun_cmd, poll=True)] fake_cli.assert_has_calls(expected) - def test_manage_existing_get_size(self): - """Unit test for the manage_existing_get_size - function of driver. - """ + def test_manage_existing_get_size_pool_backend(self): get_lun_cmd = ('lun', '-list', '-l', self.testData.test_lun_id, - '-state', '-status', '-opDetails', '-userCap', '-owner', - '-attachedSnapshot') + '-state', '-userCap', '-owner', + '-attachedSnapshot', '-poolName') test_size = 2 commands = [get_lun_cmd] results = [self.testData.LUN_PROPERTY('lun_name', size=test_size)] @@ -1573,7 +1960,7 @@ Time Remaining: 0 second(s) get_size = self.driver.manage_existing_get_size( self.testData.test_volume_with_type, self.testData.test_existing_ref) - expected = [mock.call(*get_lun_cmd)] + expected = [mock.call(*get_lun_cmd, poll=True)] assert get_size == test_size fake_cli.assert_has_calls(expected) #Test the function with invalid reference. @@ -1583,6 +1970,32 @@ Time Remaining: 0 second(s) self.testData.test_volume_with_type, invaild_ref) + def test_manage_existing_get_size_array_backend(self): + get_lun_cmd = ('lun', '-list', '-l', self.testData.test_lun_id, + '-state', '-status', '-opDetails', '-userCap', '-owner', + '-attachedSnapshot',) + test_size = 2 + commands = [get_lun_cmd] + results = [self.testData.LUN_PROPERTY('lun_name', size=test_size)] + + self.configuration.safe_get = mock.Mock(return_value=None) + self.driver = EMCCLIISCSIDriver(configuration=self.configuration) + assert isinstance(self.driver.cli, emc_vnx_cli.EMCVnxCliArray) + + # Mock the command executor + fake_command_execute = self.get_command_execute_simulator( + commands, results) + fake_cli = mock.MagicMock(side_effect=fake_command_execute) + self.driver.cli._client.command_execute = fake_cli + + get_size = self.driver.manage_existing_get_size( + self.testData.test_volume_with_type, + self.testData.test_existing_ref) + expected = [mock.call(*get_lun_cmd, poll=True)] + assert get_size == test_size + fake_cli.assert_has_calls(expected) + self.configuration.safe_get = self.fake_safe_get + def test_manage_existing_with_array_backend(self): """Unit test for the manage_existing with the array backend which is not support the manage @@ -1615,7 +2028,7 @@ Time Remaining: 0 second(s) self.driver.manage_existing( self.testData.test_volume_with_type, self.testData.test_existing_ref) - expected = [mock.call(*lun_rename_cmd)] + expected = [mock.call(*lun_rename_cmd, poll=False)] fake_cli.assert_has_calls(expected) @mock.patch( @@ -1631,6 +2044,10 @@ Time Remaining: 0 second(s) @mock.patch( "time.time", mock.Mock(return_value=123456)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'compressed'})) def test_retype_compressed_to_deduplicated(self): """Unit test for retype compressed to deduplicated.""" diff_data = {'encryption': {}, 'qos_specs': {}, @@ -1650,10 +2067,15 @@ Time Remaining: 0 second(s) 'volume_backend_name': 'pool_backend_1', 'storage_protocol': 'iSCSI'}} + cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) + output_migrate_verify = (r'The specified source LUN ' + 'is not currently migrating', 23) commands = [self.testData.NDU_LIST_CMD, - ('snap', '-list', '-res', 1)] + self.testData.SNAP_LIST_CMD(), + cmd_migrate_verify] results = [self.testData.NDU_LIST_RESULT, - ('No snap', 1023)] + ('No snap', 1023), + output_migrate_verify] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', '-Deduplication', @@ -1662,19 +2084,19 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'compressed'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, host_test_data) expect_cmd = [ - mock.call('snap', '-list', '-res', 1), + mock.call(*self.testData.SNAP_LIST_CMD(), poll=False), mock.call(*self.testData.LUN_CREATION_CMD( 'vol3-123456', 2, 'unit_test_pool', 'deduplicated', None)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol3-123456')), - mock.call(*self.testData.MIGRATION_CMD(), retry_disable=True)] + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol3-123456'), + poll=False), + mock.call(*self.testData.MIGRATION_CMD(1, None), + retry_disable=True, + poll=True)] fake_cli.assert_has_calls(expect_cmd) @mock.patch( @@ -1690,6 +2112,10 @@ Time Remaining: 0 second(s) @mock.patch( "time.time", mock.Mock(return_value=123456)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'thin'})) def test_retype_thin_to_compressed_auto(self): """Unit test for retype thin to compressed and auto tiering.""" diff_data = {'encryption': {}, 'qos_specs': {}, @@ -1710,11 +2136,15 @@ Time Remaining: 0 second(s) {'location_info': 'unit_test_pool|FNM00124500890', 'volume_backend_name': 'pool_backend_1', 'storage_protocol': 'iSCSI'}} - + cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) + output_migrate_verify = (r'The specified source LUN ' + 'is not currently migrating', 23) commands = [self.testData.NDU_LIST_CMD, - ('snap', '-list', '-res', 1)] + self.testData.SNAP_LIST_CMD(), + cmd_migrate_verify] results = [self.testData.NDU_LIST_RESULT, - ('No snap', 1023)] + ('No snap', 1023), + output_migrate_verify] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', '-Deduplication', @@ -1723,19 +2153,18 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'thin'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, host_test_data) expect_cmd = [ - mock.call('snap', '-list', '-res', 1), + mock.call(*self.testData.SNAP_LIST_CMD(), poll=False), mock.call(*self.testData.LUN_CREATION_CMD( 'vol3-123456', 2, 'unit_test_pool', 'compressed', 'auto')), mock.call(*self.testData.ENABLE_COMPRESSION_CMD(1)), - mock.call(*self.testData.MIGRATION_CMD(), retry_disable=True)] + mock.call(*self.testData.MIGRATION_CMD(), + retry_disable=True, + poll=True)] fake_cli.assert_has_calls(expect_cmd) @mock.patch( @@ -1751,6 +2180,11 @@ Time Remaining: 0 second(s) @mock.patch( "time.time", mock.Mock(return_value=123456)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'deduplicated', + 'storagetype:pool': 'unit_test_pool'})) def test_retype_pool_changed_dedup_to_compressed_auto(self): """Unit test for retype dedup to compressed and auto tiering and pool changed @@ -1779,9 +2213,11 @@ Time Remaining: 0 second(s) 'storage_protocol': 'iSCSI'}} commands = [self.testData.NDU_LIST_CMD, - ('snap', '-list', '-res', 1)] + self.testData.SNAP_LIST_CMD(), + self.testData.MIGRATION_VERIFY_CMD(1)] results = [self.testData.NDU_LIST_RESULT, - ('No snap', 1023)] + ('No snap', 1023), + ('The specified source LUN is not currently migrating', 23)] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', '-Deduplication', @@ -1790,20 +2226,20 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'deduplicated', - 'storagetype:pool': 'unit_test_pool'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, host_test_data) expect_cmd = [ - mock.call('snap', '-list', '-res', 1), + mock.call(*self.testData.SNAP_LIST_CMD(), poll=False), mock.call(*self.testData.LUN_CREATION_CMD( 'vol3-123456', 2, 'unit_test_pool2', 'compressed', 'auto')), mock.call(*self.testData.ENABLE_COMPRESSION_CMD(1)), - mock.call(*self.testData.MIGRATION_CMD(), retry_disable=True)] + mock.call(*self.testData.MIGRATION_CMD(), + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True)] fake_cli.assert_has_calls(expect_cmd) @mock.patch( @@ -1813,6 +2249,12 @@ Time Remaining: 0 second(s) "cinder.volume.drivers.emc.emc_vnx_cli.CommandLineHelper." + "get_lun_by_name", mock.Mock(return_value={'lun_id': 1})) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'compressed', + 'storagetype:pool': 'unit_test_pool', + 'storagetype:tiering': 'auto'})) def test_retype_compressed_auto_to_compressed_nomovement(self): """Unit test for retype only tiering changed.""" diff_data = {'encryption': {}, 'qos_specs': {}, @@ -1835,7 +2277,7 @@ Time Remaining: 0 second(s) 'storage_protocol': 'iSCSI'}} commands = [self.testData.NDU_LIST_CMD, - ('snap', '-list', '-res', 1)] + self.testData.SNAP_LIST_CMD(poll=False)] results = [self.testData.NDU_LIST_RESULT, ('No snap', 1023)] fake_cli = self.driverSetup(commands, results) @@ -1846,11 +2288,6 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'compressed', - 'storagetype:pool': 'unit_test_pool', - 'storagetype:tiering': 'auto'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, @@ -1867,6 +2304,11 @@ Time Remaining: 0 second(s) "cinder.volume.drivers.emc.emc_vnx_cli.CommandLineHelper." + "get_lun_by_name", mock.Mock(return_value={'lun_id': 1})) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'thin', + 'storagetype:pool': 'unit_test_pool'})) def test_retype_compressed_to_thin_cross_array(self): """Unit test for retype cross array.""" diff_data = {'encryption': {}, 'qos_specs': {}, @@ -1887,7 +2329,7 @@ Time Remaining: 0 second(s) 'storage_protocol': 'iSCSI'}} commands = [self.testData.NDU_LIST_CMD, - ('snap', '-list', '-res', 1)] + self.testData.SNAP_LIST_CMD(poll=False)] results = [self.testData.NDU_LIST_RESULT, ('No snap', 1023)] self.driverSetup(commands, results) @@ -1898,10 +2340,6 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'thin', - 'storagetype:pool': 'unit_test_pool'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) retyped = self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, host_test_data) @@ -1922,6 +2360,12 @@ Time Remaining: 0 second(s) @mock.patch( "time.time", mock.Mock(return_value=123456)) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'thin', + 'storagetype:tiering': 'auto', + 'storagetype:pool': 'unit_test_pool'})) def test_retype_thin_auto_to_dedup_diff_procotol(self): """Unit test for retype different procotol.""" diff_data = {'encryption': {}, 'qos_specs': {}, @@ -1944,9 +2388,11 @@ Time Remaining: 0 second(s) 'storage_protocol': 'FC'}} commands = [self.testData.NDU_LIST_CMD, - ('snap', '-list', '-res', 1)] + self.testData.SNAP_LIST_CMD(), + self.testData.MIGRATION_VERIFY_CMD(1)] results = [self.testData.NDU_LIST_RESULT, - ('No snap', 1023)] + ('No snap', 1023), + ('The specified source LUN is not currently migrating', 23)] fake_cli = self.driverSetup(commands, results) self.driver.cli.enablers = ['-Compression', '-Deduplication', @@ -1955,21 +2401,19 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'thin', - 'storagetype:tiering': 'auto', - 'storagetype:pool': 'unit_test_pool'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, host_test_data) expect_cmd = [ - mock.call('snap', '-list', '-res', 1), + mock.call(*self.testData.SNAP_LIST_CMD(), poll=False), mock.call(*self.testData.LUN_CREATION_CMD( 'vol3-123456', 2, 'unit_test_pool', 'deduplicated', None)), - mock.call(*self.testData.MIGRATION_CMD(), retry_disable=True)] + mock.call(*self.testData.MIGRATION_CMD(), + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True)] fake_cli.assert_has_calls(expect_cmd) @mock.patch( @@ -1979,6 +2423,12 @@ Time Remaining: 0 second(s) "cinder.volume.drivers.emc.emc_vnx_cli.CommandLineHelper." + "get_lun_by_name", mock.Mock(return_value={'lun_id': 1})) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'thin', + 'storagetype:tiering': 'auto', + 'storagetype:pool': 'unit_test_pool'})) def test_retype_thin_auto_has_snap_to_thick_highestavailable(self): """Unit test for retype volume has snap when need migration.""" diff_data = {'encryption': {}, 'qos_specs': {}, @@ -2001,7 +2451,7 @@ Time Remaining: 0 second(s) 'storage_protocol': 'iSCSI'}} commands = [self.testData.NDU_LIST_CMD, - ('snap', '-list', '-res', 1)] + self.testData.SNAP_LIST_CMD(poll=False)] results = [self.testData.NDU_LIST_RESULT, ('Has snap', 0)] self.driverSetup(commands, results) @@ -2012,12 +2462,6 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'thin', - 'storagetype:tiering': 'auto', - 'storagetype:pool': 'unit_test_pool'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - retyped = self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, @@ -2033,6 +2477,12 @@ Time Remaining: 0 second(s) "cinder.volume.drivers.emc.emc_vnx_cli.CommandLineHelper." + "get_lun_by_name", mock.Mock(return_value={'lun_id': 1})) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'storagetype:provisioning': 'thin', + 'storagetype:tiering': 'auto', + 'storagetype:pool': 'unit_test_pool'})) def test_retype_thin_auto_to_thin_auto(self): """Unit test for retype volume which has no change.""" diff_data = {'encryption': {}, 'qos_specs': {}, @@ -2062,27 +2512,24 @@ Time Remaining: 0 second(s) CommandLineHelper.get_array_serial = mock.Mock( return_value={'array_serial': "FNM00124500890"}) - extra_specs = {'storagetype:provisioning': 'thin', - 'storagetype:tiering': 'auto', - 'storagetype:pool': 'unit_test_pool'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) self.driver.retype(None, self.testData.test_volume3, new_type_data, diff_data, host_test_data) + @mock.patch( + "cinder.volume.volume_types." + "get_volume_type_extra_specs", + mock.Mock(return_value={'fast_cache_enabled': 'True'})) def test_create_volume_with_fastcache(self): '''enable fastcache when creating volume.''' - extra_specs = {'fast_cache_enabled': 'True'} - volume_types.get_volume_type_extra_specs = \ - mock.Mock(return_value=extra_specs) - commands = [self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), + self.testData.LUN_PROPERTY_ALL_CMD('vol_with_type'), self.testData.NDU_LIST_CMD, self.testData.CHECK_FASTCACHE_CMD( self.testData.test_pool_name)] results = [self.testData.LUN_PROPERTY('vol_with_type', True), + self.testData.LUN_PROPERTY('vol_with_type', True), SUCCEED, ('FAST Cache: Enabled', 0)] fake_cli = self.driverSetup(commands, results) @@ -2107,15 +2554,18 @@ Time Remaining: 0 second(s) cli_helper.command_execute = fake_cli cli_helper.get_lun_by_name = mock.Mock(return_value=lun_info) cli_helper.get_enablers_on_array = mock.Mock(return_value="-FASTCache") + cli_helper.get_pool = mock.Mock(return_value={'lun_nums': 1000, + 'total_capacity_gb': 10, + 'free_capacity_gb': 5}) self.driver.update_volume_stats() self.driver.create_volume(self.testData.test_volume_with_type) self.assertEqual(self.driver.cli.stats['fast_cache_enabled'], 'True') expect_cmd = [ + mock.call('connection', '-getport', '-address', '-vlanid', + poll=False), mock.call('storagepool', '-list', '-name', - 'Pool_02_SASFLASH', '-userCap', '-availableCap'), - mock.call('-np', 'storagepool', '-list', '-name', - 'Pool_02_SASFLASH', '-fastcache'), - mock.call('lun', '-create', '-capacity', + 'Pool_02_SASFLASH', '-fastcache', poll=False), + mock.call('-np', 'lun', '-create', '-capacity', 1, '-sq', 'gb', '-poolName', 'Pool_02_SASFLASH', '-name', 'vol_with_type', '-type', 'NonThin') ] @@ -2189,10 +2639,8 @@ Time Remaining: 0 second(s) mock.call( *self.testData.DELETE_CONSISTENCYGROUP_CMD( cg_name)), - mock.call( - *self.testData.LUN_DELETE_CMD('vol1')), - mock.call( - *self.testData.LUN_DELETE_CMD('vol1'))] + mock.call(*self.testData.LUN_DELETE_CMD('vol1')), + mock.call(*self.testData.LUN_DELETE_CMD('vol1'))] fake_cli.assert_has_calls(expect_cmd) def test_create_cgsnapshot(self): @@ -2246,20 +2694,16 @@ Time Remaining: 0 second(s) mock.call(*self.testData.LUN_CREATION_CMD( 'vol1', 1, 'unit_test_pool', - None, None)), - mock.call('lun', '-list', '-name', 'vol1', - '-state', '-status', '-opDetails', - '-userCap', '-owner', '-attachedSnapshot'), + None, None, False)), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), + poll=False), mock.call(*self.testData.ADD_LUN_TO_CG_CMD( 'cg_id', 1))] fake_cli.assert_has_calls(expect_cmd) def test_create_cloned_volume_from_consistnecy_group(self): - cmd_smp = ('lun', '-list', '-name', 'vol1', '-attachedSnapshot') - output_smp = ("""LOGICAL UNIT NUMBER 1 - Name: vol1 - Attached Snapshot: N/A""", 0) cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol1_dest") + cmd_dest_p = self.testData.LUN_PROPERTY_ALL_CMD("vol1_dest") output_dest = self.testData.LUN_PROPERTY("vol1_dest") cmd_migrate = self.testData.MIGRATION_CMD(1, 1) output_migrate = ("", 0) @@ -2268,9 +2712,9 @@ Time Remaining: 0 second(s) 'is not currently migrating', 23) cg_name = self.testData.test_cgsnapshot['consistencygroup_id'] - commands = [cmd_smp, cmd_dest, cmd_migrate, + commands = [cmd_dest, cmd_dest_p, cmd_migrate, cmd_migrate_verify] - results = [output_smp, output_dest, output_migrate, + results = [output_dest, output_dest, output_migrate, output_migrate_verify] fake_cli = self.driverSetup(commands, results) @@ -2281,38 +2725,38 @@ Time Remaining: 0 second(s) mock.call( *self.testData.CREATE_CG_SNAPSHOT(cg_name, tmp_cgsnapshot)), mock.call(*self.testData.SNAP_MP_CREATE_CMD(name='vol1', - source='clone1')), + source='clone1'), + poll=False), mock.call( *self.testData.SNAP_ATTACH_CMD( name='vol1', snapName=tmp_cgsnapshot)), mock.call(*self.testData.LUN_CREATION_CMD( 'vol1_dest', 1, 'unit_test_pool', None, None)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest')), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1'), poll=True), mock.call(*self.testData.MIGRATION_CMD(1, 1), - retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)), - mock.call('lun', '-list', '-name', 'vol1', '-attachedSnapshot'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True), mock.call(*self.testData.DELETE_CG_SNAPSHOT(tmp_cgsnapshot))] fake_cli.assert_has_calls(expect_cmd) def test_create_volume_from_cgsnapshot(self): - cmd_smp = ('lun', '-list', '-name', 'vol2', '-attachedSnapshot') - output_smp = ("""LOGICAL UNIT NUMBER 1 - Name: vol2 - Attached Snapshot: N/A""", 0) cmd_dest = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest") + cmd_dest_np = self.testData.LUN_PROPERTY_ALL_CMD("vol2_dest") output_dest = self.testData.LUN_PROPERTY("vol2_dest") cmd_migrate = self.testData.MIGRATION_CMD(1, 1) output_migrate = ("", 0) cmd_migrate_verify = self.testData.MIGRATION_VERIFY_CMD(1) output_migrate_verify = (r'The specified source LUN ' 'is not currently migrating', 23) - commands = [cmd_smp, cmd_dest, cmd_migrate, cmd_migrate_verify] - results = [output_smp, output_dest, output_migrate, + commands = [cmd_dest, cmd_dest_np, cmd_migrate, + cmd_migrate_verify] + results = [output_dest, output_dest, output_migrate, output_migrate_verify] fake_cli = self.driverSetup(commands, results) @@ -2321,26 +2765,62 @@ Time Remaining: 0 second(s) expect_cmd = [ mock.call( *self.testData.SNAP_MP_CREATE_CMD( - name='vol2', source='vol1')), + name='vol2', source='vol1'), + poll=False), mock.call( *self.testData.SNAP_ATTACH_CMD( name='vol2', snapName='cgsnapshot_id')), mock.call(*self.testData.LUN_CREATION_CMD( 'vol2_dest', 1, 'unit_test_pool', None, None)), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2')), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest')), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2_dest'), + poll=False), + mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2'), + poll=True), mock.call(*self.testData.MIGRATION_CMD(1, 1), - retry_disable=True), - mock.call(*self.testData.MIGRATION_VERIFY_CMD(1)), - mock.call('lun', '-list', '-name', 'vol2', '-attachedSnapshot'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol2'))] + retry_disable=True, + poll=True), + mock.call(*self.testData.MIGRATION_VERIFY_CMD(1), + poll=True)] + fake_cli.assert_has_calls(expect_cmd) + + def test_deregister_initiator(self): + fake_cli = self.driverSetup() + self.driver.cli.destroy_empty_sg = True + self.driver.cli.itor_auto_dereg = True + cli_helper = self.driver.cli._client + data = {'storage_group_name': "fakehost", + 'storage_group_uid': "2F:D4:00:00:00:00:00:" + "00:00:00:FF:E5:3A:03:FD:6D", + 'lunmap': {1: 16}} + cli_helper.get_storage_group = mock.Mock( + return_value=data) + lun_info = {'lun_name': "unit_test_lun", + 'lun_id': 1, + 'pool': "unit_test_pool", + 'attached_snapshot': "N/A", + 'owner': "A", + 'total_capacity_gb': 1.0, + 'state': "Ready"} + cli_helper.get_lun_by_name = mock.Mock(return_value=lun_info) + cli_helper.remove_hlu_from_storagegroup = mock.Mock() + cli_helper.disconnect_host_from_storage_group = mock.Mock() + cli_helper.delete_storage_group = mock.Mock() + self.driver.terminate_connection(self.testData.test_volume, + self.testData.connector) + expect_cmd = [ + mock.call('port', '-removeHBA', '-hbauid', + self.testData.connector['initiator'], + '-o')] fake_cli.assert_has_calls(expect_cmd) def succeed_fake_command_execute(self, *command, **kwargv): return SUCCEED + def fake_setup_command_execute(self, *command, **kwargv): + return self.testData.ALL_PORTS + def fake_get_pool_properties(self, filter_option, properties=None): pool_info = {'pool_name': "unit_test_pool0", 'total_capacity_gb': 1000.0, @@ -2373,7 +2853,7 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): super(EMCVNXCLIDriverFCTestCase, self).setUp() self.stubs.Set(CommandLineHelper, 'command_execute', - self.succeed_fake_command_execute) + self.fake_setup_command_execute) self.stubs.Set(CommandLineHelper, 'get_array_serial', mock.Mock(return_value={'array_serial': "fakeSerial"})) @@ -2393,15 +2873,14 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): #set the timeout to 0.012s = 0.0002 * 60 = 1.2ms self.configuration.default_timeout = 0.0002 self.configuration.initiator_auto_registration = True + self.configuration.check_max_pool_luns_threshold = False self.configuration.zoning_mode = None + self.configuration.max_luns_per_storage_pool = 4000 self.stubs.Set(self.configuration, 'safe_get', self.fake_safe_get) self.testData = EMCVNXCLIDriverTestData() self.navisecclicmd = '/opt/Navisphere/bin/naviseccli ' + \ '-address 10.0.0.1 -user sysadmin -password sysadmin -scope 0 ' - def tearDown(self): - super(EMCVNXCLIDriverFCTestCase, self).tearDown() - def driverSetup(self, commands=tuple(), results=tuple()): self.driver = EMCCLIFCDriver(configuration=self.configuration) fake_command_execute = self.get_command_execute_simulator( @@ -2450,8 +2929,8 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): return standard_default - def succeed_fake_command_execute(self, *command, **kwargv): - return SUCCEED + def fake_setup_command_execute(self, *command, **kwargv): + return self.testData.ALL_PORTS def fake_get_pool_properties(self, filter_option, properties=None): pool_info = {'pool_name': "unit_test_pool0", @@ -2483,109 +2962,99 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): mock.Mock( return_value=( "fakeportal iqn.1992-04.fake.com:fake.apm00123907237.a8", 0))) - @mock.patch("random.shuffle", mock.Mock()) + @mock.patch('random.randint', + mock.Mock(return_value=0)) def test_initialize_connection_fc_auto_reg(self): # Test for auto registration + test_volume = self.testData.test_volume.copy() + test_volume['provider_location'] = 'system^fakesn|type^lun|id^1' self.configuration.initiator_auto_registration = True commands = [('storagegroup', '-list', '-gname', 'fakehost'), - ('storagegroup', '-list'), self.testData.GETFCPORT_CMD(), ('port', '-list', '-gname', 'fakehost')] results = [[("No group", 83), - self.testData.STORAGE_GROUP_NO_MAP('fakehost'), self.testData.STORAGE_GROUP_HAS_MAP('fakehost')], - self.testData.STORAGE_GROUP_HAS_MAP('fakehost'), self.testData.FC_PORTS, self.testData.FAKEHOST_PORTS] fake_cli = self.driverSetup(commands, results) - data = self.driver.initialize_connection( - self.testData.test_volume, + self.driver.initialize_connection( + test_volume, self.testData.connector) - self.assertEqual(data['data']['access_mode'], 'ro') - - expected = [ - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call('storagegroup', '-create', '-gname', 'fakehost'), - mock.call('storagegroup', '-list'), - mock.call('port', '-list', '-sp'), - mock.call('storagegroup', '-gname', 'fakehost', - '-setpath', '-hbauid', - '22:34:56:78:90:12:34:56:12:34:56:78:90:12:34:56', - '-sp', 'A', '-spport', '0', '-ip', '10.0.0.2', - '-host', 'fakehost', '-o'), - mock.call('port', '-list', '-sp'), - mock.call('storagegroup', '-gname', 'fakehost', - '-setpath', '-hbauid', - '22:34:56:78:90:54:32:16:12:34:56:78:90:54:32:16', - '-sp', 'A', '-spport', '0', '-ip', '10.0.0.2', - '-host', 'fakehost', '-o'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call('storagegroup', '-addhlu', '-hlu', 1, '-alu', 1, - '-gname', 'fakehost'), - mock.call('port', '-list', '-gname', 'fakehost'), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call('port', '-list', '-sp')] + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-create', '-gname', 'fakehost'), + mock.call('port', '-list', '-sp'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:12:34:56:12:34:56:78:' + '90:12:34:56', + '-sp', 'A', '-spport', '0', '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-gname', 'fakehost', + '-setpath', '-hbauid', + '22:34:56:78:90:54:32:16:12:34:56:78:' + '90:54:32:16', + '-sp', 'A', '-spport', '0', '-ip', '10.0.0.2', + '-host', 'fakehost', '-o'), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), + mock.call('storagegroup', '-addhlu', '-hlu', 2, '-alu', 1, + '-gname', 'fakehost', + poll=False), + mock.call('port', '-list', '-gname', 'fakehost') + ] fake_cli.assert_has_calls(expected) # Test for manaul registration self.configuration.initiator_auto_registration = False commands = [('storagegroup', '-list', '-gname', 'fakehost'), - ('storagegroup', '-list'), self.testData.CONNECTHOST_CMD('fakehost', 'fakehost'), self.testData.GETFCPORT_CMD(), ('port', '-list', '-gname', 'fakehost')] results = [[("No group", 83), - self.testData.STORAGE_GROUP_NO_MAP('fakehost'), - self.testData.STORAGE_GROUP_HAS_MAP('fakehost')], - self.testData.STORAGE_GROUP_HAS_MAP('fakehost'), + self.testData.STORAGE_GROUP_NO_MAP('fakehost')], ('', 0), self.testData.FC_PORTS, self.testData.FAKEHOST_PORTS] fake_cli = self.driverSetup(commands, results) - data = self.driver.initialize_connection( - self.testData.test_volume_rw, + self.driver.initialize_connection( + test_volume, self.testData.connector) - self.assertEqual(data['data']['access_mode'], 'rw') - - expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost'), + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), mock.call('storagegroup', '-create', '-gname', 'fakehost'), mock.call('storagegroup', '-connecthost', '-host', 'fakehost', '-gname', 'fakehost', '-o'), - mock.call(*self.testData.LUN_PROPERTY_ALL_CMD('vol1')), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), mock.call('storagegroup', '-addhlu', '-hlu', 1, '-alu', 1, - '-gname', 'fakehost'), - mock.call('port', '-list', '-gname', 'fakehost'), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), - mock.call('port', '-list', '-sp')] + '-gname', 'fakehost', poll=False), + mock.call('port', '-list', '-gname', 'fakehost') + ] fake_cli.assert_has_calls(expected) @mock.patch( "cinder.zonemanager.fc_san_lookup_service.FCSanLookupService." + "get_device_mapping_from_network", mock.Mock(return_value=EMCVNXCLIDriverTestData.device_map)) - @mock.patch("random.shuffle", mock.Mock()) + @mock.patch('random.randint', + mock.Mock(return_value=0)) def test_initialize_connection_fc_auto_zoning(self): # Test for auto zoning self.configuration.zoning_mode = 'fabric' self.configuration.initiator_auto_registration = False commands = [('storagegroup', '-list', '-gname', 'fakehost'), - ('storagegroup', '-list'), self.testData.CONNECTHOST_CMD('fakehost', 'fakehost'), - self.testData.GETFCPORT_CMD(), - ('port', '-list', '-gname', 'fakehost')] + self.testData.GETFCPORT_CMD()] results = [[("No group", 83), self.testData.STORAGE_GROUP_NO_MAP('fakehost'), self.testData.STORAGE_GROUP_HAS_MAP('fakehost')], - self.testData.STORAGE_GROUP_HAS_MAP('fakehost'), ('', 0), - self.testData.FC_PORTS, - self.testData.FAKEHOST_PORTS] + self.testData.FC_PORTS] fake_cli = self.driverSetup(commands, results) self.driver.cli.zonemanager_lookup_service = FCSanLookupService( configuration=self.configuration) @@ -2598,18 +3067,18 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): EMCVNXCLIDriverTestData.i_t_map) self.assertEqual(conn_info['data']['target_wwn'], ['1122334455667777']) - expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost'), + expected = [mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=False), mock.call('storagegroup', '-create', '-gname', 'fakehost'), mock.call('storagegroup', '-connecthost', '-host', 'fakehost', '-gname', 'fakehost', '-o'), - mock.call('lun', '-list', '-name', 'vol1', - '-state', '-status', '-opDetails', - '-userCap', '-owner', '-attachedSnapshot'), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), mock.call('storagegroup', '-addhlu', '-hlu', 1, '-alu', 1, - '-gname', 'fakehost'), - mock.call('port', '-list', '-gname', 'fakehost'), - mock.call('storagegroup', '-list', '-gname', 'fakehost'), + '-gname', 'fakehost', + poll=False), + mock.call('storagegroup', '-list', '-gname', 'fakehost', + poll=True), mock.call('port', '-list', '-sp')] fake_cli.assert_has_calls(expected) @@ -2626,22 +3095,14 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): 'lunmap': {1: 16, 2: 88, 3: 47}} cli_helper.get_storage_group = mock.Mock( return_value=data) - lun_info = {'lun_name': "unit_test_lun", - 'lun_id': 1, - 'pool': "unit_test_pool", - 'attached_snapshot': "N/A", - 'owner': "A", - 'total_capacity_gb': 1.0, - 'state': "Ready"} - cli_helper.get_lun_by_name = mock.Mock(return_value=lun_info) cli_helper.remove_hlu_from_storagegroup = mock.Mock() self.driver.cli.zonemanager_lookup_service = FCSanLookupService( configuration=self.configuration) connection_info = self.driver.terminate_connection( self.testData.test_volume, self.testData.connector) - self.assertFalse('initiator_target_map' in connection_info['data'], - 'initiator_target_map should not appear.') + self.assertFalse(connection_info['data'], + 'connection_info data should not be None.') cli_helper.remove_hlu_from_storagegroup.assert_called_once_with( 16, self.testData.connector["host"]) @@ -2659,14 +3120,6 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): 'lunmap': {}} cli_helper.get_storage_group = mock.Mock( return_value=data) - lun_info = {'lun_name': "unit_test_lun", - 'lun_id': 1, - 'pool': "unit_test_pool", - 'attached_snapshot': "N/A", - 'owner': "A", - 'total_capacity_gb': 1.0, - 'state': "Ready"} - cli_helper.get_lun_by_name = mock.Mock(return_value=lun_info) cli_helper.remove_hlu_from_storagegroup = mock.Mock() self.driver.cli.zonemanager_lookup_service = FCSanLookupService( configuration=self.configuration) @@ -2679,22 +3132,21 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): EMCVNXCLIDriverTestData.i_t_map) def test_get_volume_stats(self): - #expect_result = [POOL_PROPERTY] self.driverSetup() stats = self.driver.get_volume_stats(True) self.assertTrue(stats['driver_version'] is not None, - "dirver_version is not returned") + "driver_version is not returned") self.assertTrue( - stats['free_capacity_gb'] == 1000.6, + stats['free_capacity_gb'] == 3257.851, "free_capacity_gb is not correct") self.assertTrue( - stats['reserved_percentage'] == 0, + stats['reserved_percentage'] == 3, "reserved_percentage is not correct") self.assertTrue( stats['storage_protocol'] == 'FC', "storage_protocol is not correct") self.assertTrue( - stats['total_capacity_gb'] == 10000.5, + stats['total_capacity_gb'] == 3281.146, "total_capacity_gb is not correct") self.assertTrue( stats['vendor_name'] == "EMC", @@ -2704,9 +3156,67 @@ class EMCVNXCLIDriverFCTestCase(test.TestCase): "volume backend name is not correct") self.assertTrue(stats['location_info'] == "unit_test_pool|fakeSerial") self.assertTrue( - stats['driver_version'] == "04.01.00", + stats['driver_version'] == "05.00.00", "driver version is incorrect.") + def test_get_volume_stats_too_many_luns(self): + commands = [self.testData.POOL_FEATURE_INFO_POOL_LUNS_CMD()] + results = [self.testData.POOL_FEATURE_INFO_POOL_LUNS(1000, 1000)] + fake_cli = self.driverSetup(commands, results) + + self.driver.cli.check_max_pool_luns_threshold = True + stats = self.driver.get_volume_stats(True) + self.assertTrue( + stats['free_capacity_gb'] == 0, + "free_capacity_gb is not correct") + expect_cmd = [ + mock.call(*self.testData.POOL_FEATURE_INFO_POOL_LUNS_CMD(), + poll=False)] + fake_cli.assert_has_calls(expect_cmd) + expect_cmd = [ + mock.call(*self.testData.POOL_FEATURE_INFO_POOL_LUNS_CMD(), + poll=False)] + fake_cli.assert_has_calls(expect_cmd) + + self.driver.cli.check_max_pool_luns_threshold = False + stats = self.driver.get_volume_stats(True) + self.assertTrue(stats['driver_version'] is not None, + "driver_version is not returned") + self.assertTrue( + stats['free_capacity_gb'] == 3257.851, + "free_capacity_gb is not correct") + + def test_deregister_initiator(self): + fake_cli = self.driverSetup() + self.driver.cli.destroy_empty_sg = True + self.driver.cli.itor_auto_dereg = True + cli_helper = self.driver.cli._client + data = {'storage_group_name': "fakehost", + 'storage_group_uid': "2F:D4:00:00:00:00:00:" + "00:00:00:FF:E5:3A:03:FD:6D", + 'lunmap': {1: 16}} + cli_helper.get_storage_group = mock.Mock( + return_value=data) + lun_info = {'lun_name': "unit_test_lun", + 'lun_id': 1, + 'pool': "unit_test_pool", + 'attached_snapshot': "N/A", + 'owner': "A", + 'total_capacity_gb': 1.0, + 'state': "Ready"} + cli_helper.get_lun_by_name = mock.Mock(return_value=lun_info) + cli_helper.remove_hlu_from_storagegroup = mock.Mock() + cli_helper.disconnect_host_from_storage_group = mock.Mock() + cli_helper.delete_storage_group = mock.Mock() + self.driver.terminate_connection(self.testData.test_volume, + self.testData.connector) + fc_itor_1 = '22:34:56:78:90:12:34:56:12:34:56:78:90:12:34:56' + fc_itor_2 = '22:34:56:78:90:54:32:16:12:34:56:78:90:54:32:16' + expect_cmd = [ + mock.call('port', '-removeHBA', '-hbauid', fc_itor_1, '-o'), + mock.call('port', '-removeHBA', '-hbauid', fc_itor_2, '-o')] + fake_cli.assert_has_calls(expect_cmd) + class EMCVNXCLIToggleSPTestData(): def FAKE_COMMAND_PREFIX(self, sp_address): @@ -2737,25 +3247,19 @@ class EMCVNXCLIToggleSPTestCase(test.TestCase): configuration=self.configuration) self.test_data = EMCVNXCLIToggleSPTestData() - def tearDown(self): - super(EMCVNXCLIToggleSPTestCase, self).tearDown() - def test_no_sp_toggle(self): self.cli_client.active_storage_ip = '10.10.10.10' FAKE_SUCCESS_RETURN = ('success', 0) FAKE_COMMAND = ('list', 'pool') - SIDE_EFFECTS = [FAKE_SUCCESS_RETURN, FAKE_SUCCESS_RETURN] + SIDE_EFFECTS = [FAKE_SUCCESS_RETURN] with mock.patch('cinder.utils.execute') as mock_utils: mock_utils.side_effect = SIDE_EFFECTS self.cli_client.command_execute(*FAKE_COMMAND) self.assertEqual(self.cli_client.active_storage_ip, "10.10.10.10") - expected = [mock.call(*('ping', '-c', 1, '10.10.10.10'), - check_exit_code=True), - mock.call( - *(self.test_data.FAKE_COMMAND_PREFIX('10.10.10.10') - + FAKE_COMMAND), - check_exit_code=True)] + expected = [ + mock.call(*(self.test_data.FAKE_COMMAND_PREFIX('10.10.10.10') + + FAKE_COMMAND), check_exit_code=True)] mock_utils.assert_has_calls(expected) def test_toggle_sp_with_server_unavailabe(self): @@ -2765,10 +3269,9 @@ Error occurred during HTTP request/response from the target: '10.244.213.142'. Message : HTTP/1.1 503 Service Unavailable""" FAKE_SUCCESS_RETURN = ('success', 0) FAKE_COMMAND = ('list', 'pool') - SIDE_EFFECTS = [FAKE_SUCCESS_RETURN, - processutils.ProcessExecutionError( - exit_code=255, stdout=FAKE_ERROR_MSG), - FAKE_SUCCESS_RETURN] + SIDE_EFFECTS = [processutils.ProcessExecutionError( + exit_code=255, stdout=FAKE_ERROR_MSG), + FAKE_SUCCESS_RETURN] with mock.patch('cinder.utils.execute') as mock_utils: mock_utils.side_effect = SIDE_EFFECTS @@ -2792,10 +3295,9 @@ Error occurred during HTTP request/response from the target: '10.244.213.142'. Message : End of data stream""" FAKE_SUCCESS_RETURN = ('success', 0) FAKE_COMMAND = ('list', 'pool') - SIDE_EFFECTS = [FAKE_SUCCESS_RETURN, - processutils.ProcessExecutionError( - exit_code=255, stdout=FAKE_ERROR_MSG), - FAKE_SUCCESS_RETURN] + SIDE_EFFECTS = [processutils.ProcessExecutionError( + exit_code=255, stdout=FAKE_ERROR_MSG), + FAKE_SUCCESS_RETURN] with mock.patch('cinder.utils.execute') as mock_utils: mock_utils.side_effect = SIDE_EFFECTS @@ -2821,10 +3323,35 @@ Unable to establish a secure connection to the Management Server. """ FAKE_SUCCESS_RETURN = ('success', 0) FAKE_COMMAND = ('list', 'pool') - SIDE_EFFECTS = [FAKE_SUCCESS_RETURN, - processutils.ProcessExecutionError( - exit_code=255, stdout=FAKE_ERROR_MSG), - FAKE_SUCCESS_RETURN] + SIDE_EFFECTS = [processutils.ProcessExecutionError( + exit_code=255, stdout=FAKE_ERROR_MSG), + FAKE_SUCCESS_RETURN] + + with mock.patch('cinder.utils.execute') as mock_utils: + mock_utils.side_effect = SIDE_EFFECTS + self.cli_client.command_execute(*FAKE_COMMAND) + self.assertEqual(self.cli_client.active_storage_ip, "10.10.10.11") + expected = [ + mock.call( + *(self.test_data.FAKE_COMMAND_PREFIX('10.10.10.10') + + FAKE_COMMAND), + check_exit_code=True), + mock.call( + *(self.test_data.FAKE_COMMAND_PREFIX('10.10.10.11') + + FAKE_COMMAND), + check_exit_code=True)] + mock_utils.assert_has_calls(expected) + + def test_toggle_sp_with_connection_error(self): + self.cli_client.active_storage_ip = '10.10.10.10' + FAKE_ERROR_MSG = """\ +A network error occurred while trying to connect: '192.168.1.56'. +Message : Error occurred because of time out""" + FAKE_SUCCESS_RETURN = ('success', 0) + FAKE_COMMAND = ('list', 'pool') + SIDE_EFFECTS = [processutils.ProcessExecutionError( + exit_code=255, stdout=FAKE_ERROR_MSG), + FAKE_SUCCESS_RETURN] with mock.patch('cinder.utils.execute') as mock_utils: mock_utils.side_effect = SIDE_EFFECTS diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index d4c66f407..40580492b 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -47,6 +47,10 @@ class EMCCLIFCDriver(driver.FibreChannelDriver): External Volume Management, Read-only Volume, FC Auto Zoning 4.1.0 - Consistency group support + 5.0.0 - Performance enhancement, LUN Number Threshold Support, + Initiator Auto Deregistration, + Force Deleting LUN in Storage Groups, + robust enhancement """ def __init__(self, *args, **kwargs): @@ -158,25 +162,18 @@ class EMCCLIFCDriver(driver.FibreChannelDriver): """ conn_info = self.cli.initialize_connection(volume, connector) - conn_info = self.cli.adjust_fc_conn_info(conn_info, connector) LOG.debug("Exit initialize_connection" " - Returning FC connection info: %(conn_info)s." % {'conn_info': conn_info}) - return conn_info @RemoveFCZone def terminate_connection(self, volume, connector, **kwargs): """Disallow connection from connector.""" - remove_zone = self.cli.terminate_connection(volume, connector) - conn_info = {'driver_volume_type': 'fibre_channel', - 'data': {}} - conn_info = self.cli.adjust_fc_conn_info(conn_info, connector, - remove_zone) + conn_info = self.cli.terminate_connection(volume, connector) LOG.debug("Exit terminate_connection" " - Returning FC connection info: %(conn_info)s." % {'conn_info': conn_info}) - return conn_info def get_volume_stats(self, refresh=False): @@ -235,4 +232,4 @@ class EMCCLIFCDriver(driver.FibreChannelDriver): def delete_cgsnapshot(self, context, cgsnapshot): """Deletes a cgsnapshot.""" - return self.cli.delete_cgsnapshot(self, context, cgsnapshot) \ No newline at end of file + return self.cli.delete_cgsnapshot(self, context, cgsnapshot) diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index 6c69c768c..43b0819f5 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -44,6 +44,10 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver): External Volume Management, Read-only Volume, FC Auto Zoning 4.1.0 - Consistency group support + 5.0.0 - Performance enhancement, LUN Number Threshold Support, + Initiator Auto Deregistration, + Force Deleting LUN in Storage Groups, + robust enhancement """ def __init__(self, *args, **kwargs): @@ -98,7 +102,7 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver): def create_export(self, context, volume): """Driver entry point to get the export info for a new volume.""" - self.cli.create_export(context, volume) + pass def remove_export(self, context, volume): """Driver entry point to remove an export for a volume.""" @@ -192,4 +196,4 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver): def delete_cgsnapshot(self, context, cgsnapshot): """Deletes a cgsnapshot.""" - return self.cli.delete_cgsnapshot(self, context, cgsnapshot) \ No newline at end of file + return self.cli.delete_cgsnapshot(self, context, cgsnapshot) diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 5389b99f7..41c0acd39 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -15,12 +15,14 @@ """ VNX CLI """ - +import math import os import random import re import time +import types +import eventlet from oslo_concurrency import lockutils from oslo_concurrency import processutils from oslo_config import cfg @@ -28,6 +30,10 @@ from oslo_serialization import jsonutils as json from oslo_utils import excutils from oslo_utils import timeutils import six +import taskflow.engines +from taskflow.patterns import linear_flow +from taskflow import task +from taskflow.types import failure from cinder import exception from cinder.exception import EMCVnxCLICmdError @@ -44,10 +50,11 @@ CONF = cfg.CONF LOG = logging.getLogger(__name__) INTERVAL_5_SEC = 5 +INTERVAL_20_SEC = 20 INTERVAL_30_SEC = 30 INTERVAL_60_SEC = 60 -NO_POLL = True +ENABLE_TRACE = False loc_opts = [ cfg.StrOpt('storage_vnx_authentication_type', @@ -92,14 +99,50 @@ loc_opts = [ default=False, help='Automatically register initiators. ' 'By default, the value is False.'), + cfg.BoolOpt('initiator_auto_deregistration', + default=False, + help='Automatically deregister initiators after the related ' + 'storage group is destroyed. ' + 'By default, the value is False.'), + cfg.BoolOpt('check_max_pool_luns_threshold', + default=False, + help='Report free_capacity_gb as 0 when the limit to ' + 'maximum number of pool LUNs is reached. ' + 'By default, the value is False.'), + cfg.BoolOpt('force_delete_lun_in_storagegroup', + default=False, + help='Delete a LUN even if it is in Storage Groups.') ] CONF.register_opts(loc_opts) +def decorate_all_methods(method_decorator): + """Applies decorator on the methods of a class. + + This is a class decorator, which will apply method decorator referred + by method_decorator to all the public methods (without underscore as + the prefix) in a class. + """ + if not ENABLE_TRACE: + return lambda cls: cls + + def _decorate_all_methods(cls): + for attr_name, attr_val in cls.__dict__.items(): + if (isinstance(attr_val, types.FunctionType) and + not attr_name.startswith("_")): + setattr(cls, attr_name, method_decorator(attr_val)) + return cls + + return _decorate_all_methods + + def log_enter_exit(func): + if not CONF.debug: + return func + def inner(self, *args, **kwargs): - LOG.debug("Entering %(cls)s.%(method)s" % + LOG.debug("Entering %(cls)s.%(method)s", {'cls': self.__class__.__name__, 'method': func.__name__}) start = timeutils.utcnow() @@ -107,7 +150,7 @@ def log_enter_exit(func): end = timeutils.utcnow() LOG.debug("Exiting %(cls)s.%(method)s. " "Spent %(duration)s sec. " - "Return %(return)s" % + "Return %(return)s", {'cls': self.__class__.__name__, 'duration': timeutils.delta_seconds(start, end), 'method': func.__name__, @@ -124,6 +167,7 @@ class PropertyDescriptor(object): self.converter = converter +@decorate_all_methods(log_enter_exit) class CommandLineHelper(object): LUN_STATE = PropertyDescriptor( @@ -188,8 +232,27 @@ class CommandLineHelper(object): POOL_ALL = [POOL_TOTAL_CAPACITY, POOL_FREE_CAPACITY] + MAX_POOL_LUNS = PropertyDescriptor( + '-maxPoolLUNs', + 'Max. Pool LUNs:\s*(.*)\s*', + 'max_pool_luns', + int) + TOTAL_POOL_LUNS = PropertyDescriptor( + '-numPoolLUNs', + 'Total Number of Pool LUNs:\s*(.*)\s*', + 'total_pool_luns', + int) + + POOL_FEATURE_DEFAULT = (MAX_POOL_LUNS, TOTAL_POOL_LUNS) + CLI_RESP_PATTERN_CG_NOT_FOUND = 'Cannot find' CLI_RESP_PATTERN_SNAP_NOT_FOUND = 'The specified snapshot does not exist' + CLI_RESP_PATTERN_LUN_NOT_EXIST = 'The (pool lun) may not exist' + CLI_RESP_PATTERN_SMP_NOT_ATTACHED = ('The specified Snapshot mount point ' + 'is not currently attached.') + CLI_RESP_PATTERN_SG_NAME_IN_USE = "Storage Group name already in use" + CLI_RESP_PATTERN_LUN_IN_SG_1 = "contained in a Storage Group" + CLI_RESP_PATTERN_LUN_IN_SG_2 = "Host LUN/LUN mapping still exists" def __init__(self, configuration): configuration.append_config_values(san.san_opts) @@ -210,8 +273,8 @@ class CommandLineHelper(object): self.primary_storage_ip = self.active_storage_ip self.secondary_storage_ip = configuration.san_secondary_ip if self.secondary_storage_ip == self.primary_storage_ip: - LOG.warn(_LE("san_secondary_ip is configured as " - "the same value as san_ip.")) + LOG.warning(_LE("san_secondary_ip is configured as " + "the same value as san_ip.")) self.secondary_storage_ip = None if not configuration.san_ip: err_msg = _('san_ip: Mandatory field configuration. ' @@ -235,7 +298,7 @@ class CommandLineHelper(object): # if there is security file path provided, use this security file if storage_vnx_security_file: self.credentials = ('-secfilepath', storage_vnx_security_file) - LOG.info(_LI("Using security file in %s for authentication") % + LOG.info(_LI("Using security file in %s for authentication"), storage_vnx_security_file) # if there is a username/password provided, use those in the cmd line elif storage_username is not None and len(storage_username) > 0 and\ @@ -283,15 +346,23 @@ class CommandLineHelper(object): '-initialTier', 'optimizePool', '-tieringPolicy', 'noMovement']} - @log_enter_exit + def _raise_cli_error(self, cmd=None, rc=None, out='', **kwargs): + raise EMCVnxCLICmdError(cmd=cmd, + rc=rc, + out=out.split('\n'), + **kwargs) + def create_lun_with_advance_feature(self, pool, name, size, provisioning, tiering, - consistencygroup_id=None): + consistencygroup_id=None, + poll=True): command_create_lun = ['lun', '-create', '-capacity', size, '-sq', 'gb', '-poolName', pool, '-name', name] + if not poll: + command_create_lun = ['-np'] + command_create_lun # provisioning if provisioning: command_create_lun.extend(self.provisioning_values[provisioning]) @@ -310,8 +381,8 @@ class CommandLineHelper(object): except EMCVnxCLICmdError as ex: with excutils.save_and_reraise_exception(): self.delete_lun(name) - LOG.error(_LE("Error on enable compression on lun %s.") - % six.text_type(ex)) + LOG.error(_LE("Error on enable compression on lun %s."), + six.text_type(ex)) # handle consistency group try: @@ -322,32 +393,40 @@ class CommandLineHelper(object): with excutils.save_and_reraise_exception(): self.delete_lun(name) LOG.error(_LE("Error on adding lun to consistency" - " group. %s") % six.text_type(ex)) + " group. %s"), six.text_type(ex)) return data - @log_enter_exit def create_lun_by_cmd(self, cmd, name): out, rc = self.command_execute(*cmd) if rc != 0: # Ignore the error that due to retry if rc == 4 and out.find('(0x712d8d04)') >= 0: - LOG.warn(_LW('LUN already exists, LUN name %(name)s. ' - 'Message: %(msg)s') % - {'name': name, 'msg': out}) + LOG.warning(_LW('LUN already exists, LUN name %(name)s. ' + 'Message: %(msg)s'), + {'name': name, 'msg': out}) else: - raise EMCVnxCLICmdError(cmd, rc, out) + self._raise_cli_error(cmd, rc, out) def lun_is_ready(): - data = self.get_lun_by_name(name) - return data[self.LUN_STATE.key] == 'Ready' and \ - data[self.LUN_STATUS.key] == 'OK(0x0)' and \ - data[self.LUN_OPERATION.key] == 'None' + try: + data = self.get_lun_by_name(name, self.LUN_ALL, False) + return (data[self.LUN_STATE.key] == 'Ready' and + data[self.LUN_STATUS.key] == 'OK(0x0)' and + data[self.LUN_OPERATION.key] == 'None') + except EMCVnxCLICmdError as ex: + orig_out = "\n".join(ex.kwargs["out"]) + if orig_out.find( + self.CLI_RESP_PATTERN_LUN_NOT_EXIST) >= 0: + return False + else: + raise ex - self._wait_for_a_condition(lun_is_ready) - lun = self.get_lun_by_name(name) + self._wait_for_a_condition(lun_is_ready, + None, + INTERVAL_5_SEC) + lun = self.get_lun_by_name(name, self.LUN_ALL, False) return lun - @log_enter_exit def delete_lun(self, name): command_delete_lun = ['lun', '-destroy', @@ -356,14 +435,37 @@ class CommandLineHelper(object): '-o'] # executing cli command to delete volume out, rc = self.command_execute(*command_delete_lun) - if rc != 0: + if rc != 0 or out.strip(): # Ignore the error that due to retry - if rc == 9 and out.find("not exist") >= 0: - LOG.warn(_LW("LUN is already deleted, LUN name %(name)s. " - "Message: %(msg)s") % - {'name': name, 'msg': out}) + if rc == 9 and self.CLI_RESP_PATTERN_LUN_NOT_EXIST in out: + LOG.warning(_LW("LUN is already deleted, LUN name %(name)s. " + "Message: %(msg)s"), + {'name': name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_delete_lun, rc, out) + self._raise_cli_error(command_delete_lun, rc, out) + + def get_hlus(self, lun_id, poll=True): + hlus = list() + command_storage_group_list = ('storagegroup', '-list') + out, rc = self.command_execute(*command_storage_group_list, + poll=poll) + if rc != 0: + self._raise_cli_error(command_storage_group_list, rc, out) + sg_name_p = re.compile(r'^\s*(?P[^\n\r]+)') + hlu_alu_p = re.compile(r'HLU/ALU Pairs:' + r'\s*HLU Number\s*ALU Number' + r'\s*[-\s]*' + r'(\d|\s)*' + r'\s+(?P\d+)( |\t)+%s' % lun_id) + for sg_info in out.split('Storage Group Name:'): + hlu_alu_m = hlu_alu_p.search(sg_info) + if hlu_alu_m is None: + continue + sg_name_m = sg_name_p.search(sg_info) + if sg_name_m: + hlus.append((hlu_alu_m.group('hlu'), + sg_name_m.group('sg_name'))) + return hlus def _wait_for_a_condition(self, testmethod, timeout=None, interval=INTERVAL_5_SEC): @@ -378,9 +480,9 @@ class CommandLineHelper(object): testValue = False LOG.debug('CommandLineHelper.' '_wait_for_condition: %(method_name)s ' - 'execution failed for %(exception)s' - % {'method_name': testmethod.__name__, - 'exception': ex.message}) + 'execution failed for %(exception)s', + {'method_name': testmethod.__name__, + 'exception': six.text_type(ex)}) if testValue: raise loopingcall.LoopingCallDone() @@ -393,8 +495,7 @@ class CommandLineHelper(object): timer = loopingcall.FixedIntervalLoopingCall(_inner) timer.start(interval=interval).wait() - @log_enter_exit - def expand_lun(self, name, new_size): + def expand_lun(self, name, new_size, poll=True): command_expand_lun = ('lun', '-expand', '-name', name, @@ -402,28 +503,27 @@ class CommandLineHelper(object): '-sq', 'gb', '-o', '-ignoreThresholds') - out, rc = self.command_execute(*command_expand_lun) + out, rc = self.command_execute(*command_expand_lun, + poll=poll) if rc != 0: # Ignore the error that due to retry if rc == 4 and out.find("(0x712d8e04)") >= 0: - LOG.warn(_LW("LUN %(name)s is already expanded. " - "Message: %(msg)s") % - {'name': name, 'msg': out}) + LOG.warning(_LW("LUN %(name)s is already expanded. " + "Message: %(msg)s"), + {'name': name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_expand_lun, rc, out) + self._raise_cli_error(command_expand_lun, rc, out) - @log_enter_exit def expand_lun_and_wait(self, name, new_size): - self.expand_lun(name, new_size) + self.expand_lun(name, new_size, poll=False) def lun_is_extented(): - data = self.get_lun_by_name(name) + data = self.get_lun_by_name(name, poll=False) return new_size == data[self.LUN_CAPACITY.key] self._wait_for_a_condition(lun_is_extented) - @log_enter_exit - def lun_rename(self, lun_id, new_name): + def lun_rename(self, lun_id, new_name, poll=False): """This function used to rename a lun to match the expected name for the volume. """ @@ -432,11 +532,11 @@ class CommandLineHelper(object): '-newName', new_name, '-o') - out, rc = self.command_execute(*command_lun_rename) + out, rc = self.command_execute(*command_lun_rename, + poll=poll) if rc != 0: - raise EMCVnxCLICmdError(command_lun_rename, rc, out) + self._raise_cli_error(command_lun_rename, rc, out) - @log_enter_exit def modify_lun_tiering(self, name, tiering): """This function used to modify a lun's tiering policy.""" command_modify_lun = ['lun', '-modify', @@ -447,9 +547,8 @@ class CommandLineHelper(object): out, rc = self.command_execute(*command_modify_lun) if rc != 0: - raise EMCVnxCLICmdError(command_modify_lun, rc, out) + self._raise_cli_error(command_modify_lun, rc, out) - @log_enter_exit def create_consistencygroup(self, context, group): """create the consistency group.""" cg_name = group['id'] @@ -463,13 +562,12 @@ class CommandLineHelper(object): # Ignore the error if consistency group already exists if (rc == 33 and out.find("(0x716d8021)") >= 0): - LOG.warn(_LW('Consistency group %(name)s already ' - 'exists. Message: %(msg)s') % - {'name': cg_name, 'msg': out}) + LOG.warning(_LW('Consistency group %(name)s already ' + 'exists. Message: %(msg)s'), + {'name': cg_name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_create_cg, rc, out) + self._raise_cli_error(command_create_cg, rc, out) - @log_enter_exit def get_consistency_group_by_name(self, cg_name): cmd = ('snap', '-group', '-list', '-id', cg_name) data = { @@ -490,11 +588,10 @@ class CommandLineHelper(object): luns_of_cg = m.groups()[3].split(',') if luns_of_cg: data['Luns'] = [lun.strip() for lun in luns_of_cg] - LOG.debug("Found consistent group %s." % data['Name']) + LOG.debug("Found consistent group %s.", data['Name']) return data - @log_enter_exit def add_lun_to_consistency_group(self, cg_name, lun_id): add_lun_to_cg_cmd = ('-np', 'snap', '-group', '-addmember', '-id', @@ -506,25 +603,24 @@ class CommandLineHelper(object): "group %(cg_name)s.") % {'lun': lun_id, 'cg_name': cg_name}) LOG.error(msg) - raise EMCVnxCLICmdError(add_lun_to_cg_cmd, rc, out) + 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.") % + "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.") % + "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) - @log_enter_exit def delete_consistencygroup(self, cg_name): delete_cg_cmd = ('-np', 'snap', '-group', '-destroy', '-id', cg_name) @@ -532,20 +628,19 @@ class CommandLineHelper(object): if rc != 0: # Ignore the error if CG doesn't exist if rc == 13 and out.find(self.CLI_RESP_PATTERN_CG_NOT_FOUND) >= 0: - LOG.warn(_LW("CG %(cg_name)s does not exist. " - "Message: %(msg)s") % - {'cg_name': cg_name, 'msg': out}) + LOG.warning(_LW("CG %(cg_name)s does not exist. " + "Message: %(msg)s"), + {'cg_name': cg_name, 'msg': out}) elif rc == 1 and out.find("0x712d8801") >= 0: - LOG.warn(_LW("CG %(cg_name)s is deleting. " - "Message: %(msg)s") % - {'cg_name': cg_name, 'msg': out}) + LOG.warning(_LW("CG %(cg_name)s is deleting. " + "Message: %(msg)s"), + {'cg_name': cg_name, 'msg': out}) else: - raise EMCVnxCLICmdError(delete_cg_cmd, rc, out) + self._raise_cli_error(delete_cg_cmd, rc, out) else: LOG.info(_LI('Consistency group %s was deleted ' - 'successfully.') % cg_name) + 'successfully.'), cg_name) - @log_enter_exit def create_cgsnapshot(self, cgsnapshot): """Create a cgsnapshot (snap group).""" cg_name = cgsnapshot['consistencygroup_id'] @@ -562,13 +657,12 @@ class CommandLineHelper(object): # Ignore the error if cgsnapshot already exists if (rc == 5 and out.find("(0x716d8005)") >= 0): - LOG.warn(_LW('Cgsnapshot name %(name)s already ' - 'exists. Message: %(msg)s') % - {'name': snap_name, 'msg': out}) + LOG.warning(_LW('Cgsnapshot name %(name)s already ' + 'exists. Message: %(msg)s'), + {'name': snap_name, 'msg': out}) else: - raise EMCVnxCLICmdError(create_cg_snap_cmd, rc, out) + self._raise_cli_error(create_cg_snap_cmd, rc, out) - @log_enter_exit def delete_cgsnapshot(self, cgsnapshot): """Delete a cgsnapshot (snap group).""" snap_name = cgsnapshot['id'] @@ -580,62 +674,61 @@ class CommandLineHelper(object): # Ignore the error if cgsnapshot does not exist. if (rc == 5 and out.find(self.CLI_RESP_PATTERN_SNAP_NOT_FOUND) >= 0): - LOG.warn(_LW('Snapshot %(name)s for consistency group ' - 'does not exist. Message: %(msg)s') % - {'name': snap_name, 'msg': out}) + LOG.warning(_LW('Snapshot %(name)s for consistency group ' + 'does not exist. Message: %(msg)s'), + {'name': snap_name, 'msg': out}) else: - raise EMCVnxCLICmdError(delete_cg_snap_cmd, rc, out) + self._raise_cli_error(delete_cg_snap_cmd, rc, out) - @log_enter_exit - def create_snapshot(self, volume_name, name): - data = self.get_lun_by_name(volume_name) - if data[self.LUN_ID.key] is not None: + def create_snapshot(self, lun_id, name): + if lun_id is not None: command_create_snapshot = ('snap', '-create', - '-res', data[self.LUN_ID.key], + '-res', lun_id, '-name', name, '-allowReadWrite', 'yes', '-allowAutoDelete', 'no') - out, rc = self.command_execute(*command_create_snapshot) + out, rc = self.command_execute(*command_create_snapshot, + poll=False) if rc != 0: # Ignore the error that due to retry if (rc == 5 and out.find("(0x716d8005)") >= 0): - LOG.warn(_LW('Snapshot %(name)s already exists. ' - 'Message: %(msg)s') % - {'name': name, 'msg': out}) + LOG.warning(_LW('Snapshot %(name)s already exists. ' + 'Message: %(msg)s'), + {'name': name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_create_snapshot, rc, out) + self._raise_cli_error(command_create_snapshot, rc, out) else: - msg = _('Failed to get LUN ID for volume %s.') % volume_name + msg = _('Failed to create snapshot as no LUN ID is specified') raise exception.VolumeBackendAPIException(data=msg) - @log_enter_exit def delete_snapshot(self, name): def delete_snapshot_success(): command_delete_snapshot = ('snap', '-destroy', '-id', name, '-o') - out, rc = self.command_execute(*command_delete_snapshot) + out, rc = self.command_execute(*command_delete_snapshot, + poll=True) if rc != 0: # Ignore the error that due to retry if rc == 5 and out.find("not exist") >= 0: - LOG.warn(_LW("Snapshot %(name)s may deleted already. " - "Message: %(msg)s") % - {'name': name, 'msg': out}) + LOG.warning(_LW("Snapshot %(name)s may deleted already. " + "Message: %(msg)s"), + {'name': name, 'msg': out}) return True # The snapshot cannot be destroyed because it is # attached to a snapshot mount point. Wait elif rc == 3 and out.find("(0x716d8003)") >= 0: - LOG.warn(_LW("Snapshot %(name)s is in use, retry. " - "Message: %(msg)s") % - {'name': name, 'msg': out}) + LOG.warning(_LW("Snapshot %(name)s is in use, retry. " + "Message: %(msg)s"), + {'name': name, 'msg': out}) return False else: - raise EMCVnxCLICmdError(command_delete_snapshot, rc, out) + self._raise_cli_error(command_delete_snapshot, rc, out) else: - LOG.info(_LI('Snapshot %s was deleted successfully.') % + LOG.info(_LI('Snapshot %s was deleted successfully.'), name) return True @@ -643,7 +736,6 @@ class CommandLineHelper(object): interval=INTERVAL_30_SEC, timeout=INTERVAL_30_SEC * 3) - @log_enter_exit def create_mount_point(self, primary_lun_name, name): command_create_mount_point = ('lun', '-create', @@ -651,19 +743,19 @@ class CommandLineHelper(object): '-primaryLunName', primary_lun_name, '-name', name) - out, rc = self.command_execute(*command_create_mount_point) + out, rc = self.command_execute(*command_create_mount_point, + poll=False) if rc != 0: # Ignore the error that due to retry if rc == 4 and out.find("(0x712d8d04)") >= 0: - LOG.warn(_LW("Mount point %(name)s already exists. " - "Message: %(msg)s") % - {'name': name, 'msg': out}) + LOG.warning(_LW("Mount point %(name)s already exists. " + "Message: %(msg)s"), + {'name': name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_create_mount_point, rc, out) + self._raise_cli_error(command_create_mount_point, rc, out) return rc - @log_enter_exit def attach_mount_point(self, name, snapshot_name): command_attach_mount_point = ('lun', '-attach', @@ -674,36 +766,35 @@ class CommandLineHelper(object): if rc != 0: # Ignore the error that due to retry if rc == 85 and out.find('(0x716d8055)') >= 0: - LOG.warn(_LW("Snapshot %(snapname)s is attached to snapshot " - "mount point %(mpname)s already. " - "Message: %(msg)s") % - {'snapname': snapshot_name, - 'mpname': name, - 'msg': out}) + LOG.warning(_LW("Snapshot %(snapname)s is attached to " + "snapshot mount point %(mpname)s already. " + "Message: %(msg)s"), + {'snapname': snapshot_name, + 'mpname': name, + 'msg': out}) else: - raise EMCVnxCLICmdError(command_attach_mount_point, rc, out) + self._raise_cli_error(command_attach_mount_point, rc, out) return rc - @log_enter_exit - def check_smp_not_attached(self, smp_name): - """Ensure a snap mount point with snap become a LUN.""" + def detach_mount_point(self, smp_name): - def _wait_for_sync_status(): - lun_list = ('lun', '-list', '-name', smp_name, - '-attachedSnapshot') - out, rc = self.command_execute(*lun_list) - if rc == 0: - vol_details = out.split('\n') - snap_name = vol_details[2].split(':')[1].strip() - if (snap_name == 'N/A'): - return True - return False + command_detach_mount_point = ('lun', '-detach', + '-name', smp_name) + + out, rc = self.command_execute(*command_detach_mount_point) + if rc != 0: + # Ignore the error that due to retry + if (rc == 162 and + out.find(self.CLI_RESP_PATTERN_SMP_NOT_ATTACHED) >= 0): + LOG.warning(_LW("The specified Snapshot mount point %s is not " + "currently attached."), smp_name) + else: + self._raise_cli_error(command_detach_mount_point, rc, out) - self._wait_for_a_condition(_wait_for_sync_status) + return rc - @log_enter_exit - def migrate_lun(self, src_id, dst_id, log_failure_as_error=True): + def migrate_lun(self, src_id, dst_id): command_migrate_lun = ('migrate', '-start', '-source', src_id, '-dest', dst_id, @@ -711,49 +802,50 @@ class CommandLineHelper(object): '-o') # SP HA is not supported by LUN migration out, rc = self.command_execute(*command_migrate_lun, - retry_disable=True) + retry_disable=True, + poll=True) if 0 != rc: - raise EMCVnxCLICmdError(command_migrate_lun, rc, out, - log_failure_as_error) + self._raise_cli_error(command_migrate_lun, rc, out) return rc - @log_enter_exit def migrate_lun_with_verification(self, src_id, dst_id=None, dst_name=None): try: - self.migrate_lun(src_id, dst_id, log_failure_as_error=False) + self.migrate_lun(src_id, dst_id) except EMCVnxCLICmdError as ex: migration_succeed = False - if self._is_sp_unavailable_error(ex.out): - LOG.warn(_LW("Migration command may get network timeout. " - "Double check whether migration in fact " - "started successfully. Message: %(msg)s") % - {'msg': ex.out}) + orig_out = "\n".join(ex.kwargs["out"]) + if self._is_sp_unavailable_error(orig_out): + LOG.warning(_LW("Migration command may get network timeout. " + "Double check whether migration in fact " + "started successfully. Message: %(msg)s"), + {'msg': ex.kwargs["out"]}) command_migrate_list = ('migrate', '-list', '-source', src_id) - rc = self.command_execute(*command_migrate_list)[1] + rc = self.command_execute(*command_migrate_list, + poll=True)[1] if rc == 0: migration_succeed = True if not migration_succeed: - LOG.warn(_LW("Start migration failed. Message: %s") % - ex.out) - LOG.debug("Delete temp LUN after migration " - "start failed. LUN: %s" % dst_name) - if(dst_name is not None): + LOG.warning(_LW("Start migration failed. Message: %s"), + ex.kwargs["out"]) + if dst_name is not None: + LOG.warning(_LW("Delete temp LUN after migration " + "start failed. LUN: %s"), dst_name) self.delete_lun(dst_name) return False # Set the proper interval to verify the migration status - def migration_is_ready(): + def migration_is_ready(poll=False): mig_ready = False - command_migrate_list = ('migrate', '-list', - '-source', src_id) - out, rc = self.command_execute(*command_migrate_list) - LOG.debug("Migration output: %s" % out) + cmd_migrate_list = ('migrate', '-list', '-source', src_id) + out, rc = self.command_execute(*cmd_migrate_list, + poll=poll) + LOG.debug("Migration output: %s", out) if rc == 0: # parse the percentage out = re.split(r'\n', out) @@ -762,7 +854,7 @@ class CommandLineHelper(object): else: if re.search(r'The specified source LUN ' 'is not currently migrating', out): - LOG.debug("Migration of LUN %s is finished." % src_id) + LOG.debug("Migration of LUN %s is finished.", src_id) mig_ready = True else: reason = _("Querying migrating status error.") @@ -772,28 +864,33 @@ class CommandLineHelper(object): {'reason': reason, 'output': out}) return mig_ready + eventlet.sleep(INTERVAL_30_SEC) + if migration_is_ready(True): + return True self._wait_for_a_condition(migration_is_ready, interval=INTERVAL_30_SEC) return True - @log_enter_exit - def get_storage_group(self, name): + def get_storage_group(self, name, poll=True): # ALU/HLU as key/value map lun_map = {} data = {'storage_group_name': name, 'storage_group_uid': None, - 'lunmap': lun_map} + 'lunmap': lun_map, + 'raw_output': ''} command_get_storage_group = ('storagegroup', '-list', '-gname', name) - out, rc = self.command_execute(*command_get_storage_group) + out, rc = self.command_execute(*command_get_storage_group, + poll=poll) if rc != 0: - raise EMCVnxCLICmdError(command_get_storage_group, rc, out) + self._raise_cli_error(command_get_storage_group, rc, out) + data['raw_output'] = out re_stroage_group_id = 'Storage Group UID:\s*(.*)\s*' m = re.search(re_stroage_group_id, out) if m is not None: @@ -812,7 +909,6 @@ class CommandLineHelper(object): return data - @log_enter_exit def create_storage_group(self, name): command_create_storage_group = ('storagegroup', '-create', @@ -821,14 +917,13 @@ class CommandLineHelper(object): out, rc = self.command_execute(*command_create_storage_group) if rc != 0: # Ignore the error that due to retry - if rc == 66 and out.find("name already in use") >= 0: - LOG.warn(_LW('Storage group %(name)s already exists. ' - 'Message: %(msg)s') % - {'name': name, 'msg': out}) + if rc == 66 and self.CLI_RESP_PATTERN_SG_NAME_IN_USE in out >= 0: + LOG.warning(_LW('Storage group %(name)s already exists. ' + 'Message: %(msg)s'), + {'name': name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_create_storage_group, rc, out) + self._raise_cli_error(command_create_storage_group, rc, out) - @log_enter_exit def delete_storage_group(self, name): command_delete_storage_group = ('storagegroup', '-destroy', @@ -839,14 +934,13 @@ class CommandLineHelper(object): # Ignore the error that due to retry if rc == 83 and out.find("group name or UID does not " "match any storage groups") >= 0: - LOG.warn(_LW("Storage group %(name)s doesn't exist, " - "may have already been deleted. " - "Message: %(msg)s") % - {'name': name, 'msg': out}) + LOG.warning(_LW("Storage group %(name)s doesn't exist, " + "may have already been deleted. " + "Message: %(msg)s"), + {'name': name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_delete_storage_group, rc, out) + self._raise_cli_error(command_delete_storage_group, rc, out) - @log_enter_exit def connect_host_to_storage_group(self, hostname, sg_name): command_host_connect = ('storagegroup', '-connecthost', @@ -856,9 +950,8 @@ class CommandLineHelper(object): out, rc = self.command_execute(*command_host_connect) if rc != 0: - raise EMCVnxCLICmdError(command_host_connect, rc, out) + self._raise_cli_error(command_host_connect, rc, out) - @log_enter_exit def disconnect_host_from_storage_group(self, hostname, sg_name): command_host_disconnect = ('storagegroup', '-disconnecthost', '-host', hostname, @@ -871,53 +964,49 @@ class CommandLineHelper(object): if rc == 116 and \ re.search("host is not.*connected to.*storage group", out) is not None: - LOG.warn(_LW("Host %(host)s has already disconnected from " - "storage group %(sgname)s. Message: %(msg)s") % - {'host': hostname, 'sgname': sg_name, 'msg': out}) + LOG.warning(_LW("Host %(host)s has already disconnected from " + "storage group %(sgname)s. Message: %(msg)s"), + {'host': hostname, 'sgname': sg_name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_host_disconnect, rc, out) + self._raise_cli_error(command_host_disconnect, rc, out) - @log_enter_exit def add_hlu_to_storage_group(self, hlu, alu, sg_name): + """Adds a lun into storage group as specified hlu number. + + Return True if the hlu is as specified, otherwise False. + """ command_add_hlu = ('storagegroup', '-addhlu', '-hlu', hlu, '-alu', alu, '-gname', sg_name) - out, rc = self.command_execute(*command_add_hlu) + out, rc = self.command_execute(*command_add_hlu, poll=False) if rc != 0: - # Ignore the error that due to retry - if rc == 66 and \ - re.search("LUN.*already.*added to.*Storage Group", - out) is not None: - LOG.warn(_LW("LUN %(lun)s has already added to " - "Storage Group %(sgname)s. " - "Message: %(msg)s") % - {'lun': alu, 'sgname': sg_name, 'msg': out}) - else: - raise EMCVnxCLICmdError(command_add_hlu, rc, out) + # Do not need to consider the retry for add hlu + # Retry is handled in the caller + self._raise_cli_error(command_add_hlu, rc, out) - @log_enter_exit - def remove_hlu_from_storagegroup(self, hlu, sg_name): + return True + + def remove_hlu_from_storagegroup(self, hlu, sg_name, poll=False): command_remove_hlu = ('storagegroup', '-removehlu', '-hlu', hlu, '-gname', sg_name, '-o') - out, rc = self.command_execute(*command_remove_hlu) + out, rc = self.command_execute(*command_remove_hlu, poll=poll) if rc != 0: # Ignore the error that due to retry if rc == 66 and\ out.find("No such Host LUN in this Storage Group") >= 0: - LOG.warn(_LW("HLU %(hlu)s has already been removed from " - "%(sgname)s. Message: %(msg)s") % - {'hlu': hlu, 'sgname': sg_name, 'msg': out}) + LOG.warning(_LW("HLU %(hlu)s has already been removed from " + "%(sgname)s. Message: %(msg)s"), + {'hlu': hlu, 'sgname': sg_name, 'msg': out}) else: - raise EMCVnxCLICmdError(command_remove_hlu, rc, out) + self._raise_cli_error(command_remove_hlu, rc, out) - @log_enter_exit def get_iscsi_protocol_endpoints(self, device_sp): command_get_port = ('connection', '-getport', @@ -925,64 +1014,77 @@ class CommandLineHelper(object): out, rc = self.command_execute(*command_get_port) if rc != 0: - raise EMCVnxCLICmdError(command_get_port, rc, out) + self._raise_cli_error(command_get_port, rc, out) re_port_wwn = 'Port WWN:\s*(.*)\s*' initiator_address = re.findall(re_port_wwn, out) return initiator_address - @log_enter_exit - def get_pool_name_of_lun(self, lun_name): + def get_pool_name_of_lun(self, lun_name, poll=True): data = self.get_lun_properties( - ('-name', lun_name), self.LUN_WITH_POOL) + ('-name', lun_name), self.LUN_WITH_POOL, poll=poll) return data.get('pool', '') - @log_enter_exit - def get_lun_by_name(self, name, properties=LUN_ALL): - data = self.get_lun_properties(('-name', name), properties) + def get_lun_by_name(self, name, properties=LUN_ALL, poll=True): + data = self.get_lun_properties(('-name', name), + properties, + poll=poll) return data - @log_enter_exit - def get_lun_by_id(self, lunid, properties=LUN_ALL): - data = self.get_lun_properties(('-l', lunid), properties) + def get_lun_by_id(self, lunid, properties=LUN_ALL, poll=True): + data = self.get_lun_properties(('-l', lunid), + properties, poll=poll) return data - @log_enter_exit - def get_pool(self, name): - data = self.get_pool_properties(('-name', name)) + def get_pool(self, name, poll=True): + data = self.get_pool_properties(('-name', name), + poll=poll) return data - @log_enter_exit - def get_pool_properties(self, filter_option, properties=POOL_ALL): + def get_pool_properties(self, filter_option, properties=POOL_ALL, + poll=True): module_list = ('storagepool', '-list') - data = self._get_lun_or_pool_properties( + data = self._get_obj_properties( module_list, filter_option, base_properties=[self.POOL_NAME], - adv_properties=properties) + adv_properties=properties, + poll=poll) return data - @log_enter_exit - def get_lun_properties(self, filter_option, properties=LUN_ALL): + def get_lun_properties(self, filter_option, properties=LUN_ALL, + poll=True): module_list = ('lun', '-list') - data = self._get_lun_or_pool_properties( + data = self._get_obj_properties( module_list, filter_option, base_properties=[self.LUN_NAME, self.LUN_ID], - adv_properties=properties) + adv_properties=properties, + poll=poll) + return data + + def get_pool_feature_properties(self, properties=POOL_FEATURE_DEFAULT, + poll=True): + module_list = ("storagepool", '-feature', '-info') + data = self._get_obj_properties( + module_list, tuple(), + base_properties=[], + adv_properties=properties, + poll=poll) return data - def _get_lun_or_pool_properties(self, module_list, - filter_option, - base_properties=tuple(), - adv_properties=tuple()): + def _get_obj_properties(self, module_list, + filter_option, + base_properties=tuple(), + adv_properties=tuple(), + poll=True): # to do instance check - command_get_lun = module_list + filter_option + command_get = module_list + filter_option for prop in adv_properties: - command_get_lun += (prop.option, ) - out, rc = self.command_execute(*command_get_lun) + command_get += (prop.option, ) + out, rc = self.command_execute(*command_get, poll=poll) if rc != 0: - raise EMCVnxCLICmdError(command_get_lun, rc, out) + self._raise_cli_error(command_get, rc, out) data = {} for baseprop in base_properties: @@ -991,7 +1093,7 @@ class CommandLineHelper(object): for prop in adv_properties: data[prop.key] = self._get_property_value(out, prop) - LOG.debug('Return LUN or Pool properties. Data: %s' % data) + LOG.debug('Return Object properties. Data: %s', data) return data def _get_property_value(self, out, propertyDescriptor): @@ -1003,37 +1105,32 @@ class CommandLineHelper(object): return propertyDescriptor.converter(m.group(1)) except ValueError: LOG.error(_LE("Invalid value for %(key)s, " - "value is %(value)s.") % + "value is %(value)s."), {'key': propertyDescriptor.key, 'value': m.group(1)}) return None else: return m.group(1) else: - LOG.debug('%s value is not found in the output.' - % propertyDescriptor.label) + LOG.debug('%s value is not found in the output.', + propertyDescriptor.label) return None - @log_enter_exit def check_lun_has_snap(self, lun_id): cmd = ('snap', '-list', '-res', lun_id) - rc = self.command_execute(*cmd)[1] + rc = self.command_execute(*cmd, poll=False)[1] if rc == 0: - LOG.debug("Find snapshots for %s." % lun_id) + LOG.debug("Find snapshots for %s.", lun_id) return True else: return False - # Return a pool list - @log_enter_exit - def get_pool_list(self, no_poll=False): + def get_pool_list(self, poll=True): temp_cache = [] - cmd = ('-np', 'storagepool', '-list', '-availableCap', '-state') \ - if no_poll \ - else ('storagepool', '-list', '-availableCap', '-state') - out, rc = self.command_execute(*cmd) + cmd = ('storagepool', '-list', '-availableCap', '-state') + out, rc = self.command_execute(*cmd, poll=poll) if rc != 0: - raise EMCVnxCLICmdError(cmd, rc, out) + self._raise_cli_error(cmd, rc, out) try: for pool in out.split('\n\n'): @@ -1045,39 +1142,37 @@ class CommandLineHelper(object): pool, self.POOL_FREE_CAPACITY) temp_cache.append(obj) except Exception as ex: - LOG.error(_LE("Error happened during storage pool querying, %s.") - % ex) + LOG.error(_LE("Error happened during storage pool querying, %s."), + ex) # NOTE: Do not want to continue raise the exception # as the pools may temporarly unavailable pass return temp_cache - @log_enter_exit - def get_array_serial(self, no_poll=False): + def get_array_serial(self, poll=False): """return array Serial No for pool backend.""" data = {'array_serial': 'unknown'} - command_get_array_serial = ('-np', 'getagent', '-serial') \ - if no_poll else ('getagent', '-serial') + command_get_array_serial = ('getagent', '-serial') # Set the property timeout to get array serial - out, rc = self.command_execute(*command_get_array_serial) + out, rc = self.command_execute(*command_get_array_serial, + poll=poll) if 0 == rc: m = re.search(r'Serial No:\s+(\w+)', out) if m: data['array_serial'] = m.group(1) else: - LOG.warn(_LW("No array serial number returned, " - "set as unknown.")) + LOG.warning(_LW("No array serial number returned, " + "set as unknown.")) else: - raise EMCVnxCLICmdError(command_get_array_serial, rc, out) + self._raise_cli_error(command_get_array_serial, rc, out) return data - @log_enter_exit - def get_status_up_ports(self, storage_group_name): + def get_status_up_ports(self, storage_group_name, poll=True): """Function to get ports whose status are up.""" cmd_get_hba = ('storagegroup', '-list', '-gname', storage_group_name) - out, rc = self.command_execute(*cmd_get_hba) + out, rc = self.command_execute(*cmd_get_hba, poll=poll) wwns = [] if 0 == rc: _re_hba_sp_pair = re.compile('((\w\w:){15}(\w\w)\s*' + @@ -1088,19 +1183,21 @@ class CommandLineHelper(object): cmd_get_port = ('port', '-list', '-sp') out, rc = self.command_execute(*cmd_get_port) if 0 != rc: - raise EMCVnxCLICmdError(cmd_get_port, rc, out) + self._raise_cli_error(cmd_get_port, rc, out) for i, sp in enumerate(sps): wwn = self.get_port_wwn(sp, portid[i], out) if (wwn is not None) and (wwn not in wwns): - LOG.debug('Add wwn:%(wwn)s for sg:%(sg)s.' - % {'wwn': wwn, - 'sg': storage_group_name}) + LOG.debug('Add wwn:%(wwn)s for sg:%(sg)s.', + {'wwn': wwn, + 'sg': storage_group_name}) wwns.append(wwn) + elif 83 == rc: + LOG.warning(_LW("Storage Group %s is not found."), + storage_group_name) else: - raise EMCVnxCLICmdError(cmd_get_hba, rc, out) + self._raise_cli_error(cmd_get_hba, rc, out) return wwns - @log_enter_exit def get_login_ports(self, storage_group_name, connector_wwpns): cmd_list_hba = ('port', '-list', '-gname', storage_group_name) @@ -1131,17 +1228,16 @@ class CommandLineHelper(object): if wwn: wwns.append(wwn) else: - raise EMCVnxCLICmdError(cmd_list_hba, rc, out) + self._raise_cli_error(cmd_list_hba, rc, out) return wwns - @log_enter_exit def get_port_wwn(self, sp, port_id, allports=None): wwn = None if allports is None: cmd_get_port = ('port', '-list', '-sp') out, rc = self.command_execute(*cmd_get_port) if 0 != rc: - raise EMCVnxCLICmdError(cmd_get_port, rc, out) + self._raise_cli_error(cmd_get_port, rc, out) else: allports = out _re_port_wwn = re.compile('SP Name:\s*' + sp + @@ -1154,12 +1250,11 @@ class CommandLineHelper(object): wwn = _obj_search.group(1).replace(':', '')[16:] return wwn - @log_enter_exit def get_fc_targets(self): fc_getport = ('port', '-list', '-sp') out, rc = self.command_execute(*fc_getport) if rc != 0: - raise EMCVnxCLICmdError(fc_getport, rc, out) + self._raise_cli_error(fc_getport, rc, out) fc_target_dict = {'A': [], 'B': []} @@ -1176,12 +1271,11 @@ class CommandLineHelper(object): 'Port ID': sp_port_id}) return fc_target_dict - @log_enter_exit - def get_iscsi_targets(self): + def get_iscsi_targets(self, poll=True): cmd_getport = ('connection', '-getport', '-address', '-vlanid') - out, rc = self.command_execute(*cmd_getport) + out, rc = self.command_execute(*cmd_getport, poll=poll) if rc != 0: - raise EMCVnxCLICmdError(cmd_getport, rc, out) + self._raise_cli_error(cmd_getport, rc, out) iscsi_target_dict = {'A': [], 'B': []} iscsi_spport_pat = r'(A|B)\s*' + \ @@ -1213,31 +1307,25 @@ class CommandLineHelper(object): return iscsi_target_dict - @log_enter_exit - def get_registered_spport_set(self, initiator_iqn, sgname): - sg_list = ('storagegroup', '-list', '-gname', sgname) - out, rc = self.command_execute(*sg_list) + def get_registered_spport_set(self, initiator_iqn, sgname, sg_raw_out): spport_set = set() - if rc == 0: - for m_spport in re.finditer(r'\n\s+%s\s+SP\s(A|B)\s+(\d+)' % - initiator_iqn, - out, - flags=re.IGNORECASE): - spport_set.add((m_spport.group(1), int(m_spport.group(2)))) - LOG.debug('See path %(path)s in %(sg)s' - % ({'path': m_spport.group(0), - 'sg': sgname})) - else: - raise EMCVnxCLICmdError(sg_list, rc, out) + for m_spport in re.finditer(r'\n\s+%s\s+SP\s(A|B)\s+(\d+)' % + initiator_iqn, + sg_raw_out, + flags=re.IGNORECASE): + spport_set.add((m_spport.group(1), int(m_spport.group(2)))) + LOG.debug('See path %(path)s in %(sg)s', + {'path': m_spport.group(0), + 'sg': sgname}) return spport_set - @log_enter_exit def ping_node(self, target_portal, initiator_ip): connection_pingnode = ('connection', '-pingnode', '-sp', target_portal['SP'], '-portid', target_portal['Port ID'], '-vportid', target_portal['Virtual Port ID'], - '-address', initiator_ip) + '-address', initiator_ip, + '-count', '1') out, rc = self.command_execute(*connection_pingnode) if rc == 0: ping_ok = re.compile(r'Reply from %s' % initiator_ip) @@ -1245,13 +1333,14 @@ class CommandLineHelper(object): LOG.debug("See available iSCSI target: %s", connection_pingnode) return True - LOG.warn(_LW("See unavailable iSCSI target: %s"), connection_pingnode) + LOG.warning(_LW("See unavailable iSCSI target: %s"), + connection_pingnode) return False - @log_enter_exit def find_avaialable_iscsi_target_one(self, hostname, preferred_sp, - registered_spport_set): + registered_spport_set, + all_iscsi_targets): if self.iscsi_initiator_map and hostname in self.iscsi_initiator_map: iscsi_initiator_ips = list(self.iscsi_initiator_map[hostname]) random.shuffle(iscsi_initiator_ips) @@ -1263,17 +1352,16 @@ class CommandLineHelper(object): else: target_sps = ('B', 'A') - iscsi_targets = self.get_iscsi_targets() for target_sp in target_sps: - target_portals = list(iscsi_targets[target_sp]) + target_portals = list(all_iscsi_targets[target_sp]) random.shuffle(target_portals) for target_portal in target_portals: spport = (target_portal['SP'], target_portal['Port ID']) if spport not in registered_spport_set: LOG.debug("Skip SP Port %(port)s since " - "no path from %(host)s is through it" - % {'port': spport, - 'host': hostname}) + "no path from %(host)s is through it", + {'port': spport, + 'host': hostname}) continue if iscsi_initiator_ips is not None: for initiator_ip in iscsi_initiator_ips: @@ -1281,9 +1369,9 @@ class CommandLineHelper(object): return target_portal else: LOG.debug("No iSCSI IP address of %(hostname)s is known. " - "Return a random iSCSI target portal %(portal)s." - % - {'hostname': hostname, 'portal': target_portal}) + "Return a random target portal %(portal)s.", + {'hostname': hostname, + 'portal': target_portal}) return target_portal return None @@ -1291,38 +1379,33 @@ class CommandLineHelper(object): def _is_sp_unavailable_error(self, out): error_pattern = '(^Error.*Message.*End of data stream.*)|'\ '(.*Message.*connection refused.*)|'\ - '(^Error.*Message.*Service Unavailable.*)' + '(^Error.*Message.*Service Unavailable.*)|'\ + '(^A network error occurred while trying to'\ + ' connect.* )|'\ + '(^Exception: Error occurred because of time out\s*)' pattern = re.compile(error_pattern) return pattern.match(out) - @log_enter_exit def command_execute(self, *command, **kwargv): + """Executes command against the VNX array. + + When there is named parameter poll=False, the command will be sent + alone with option -np. + """ # NOTE: retry_disable need to be removed from kwargv # before it pass to utils.execute, otherwise exception will thrown retry_disable = kwargv.pop('retry_disable', False) - if self._is_sp_alive(self.active_storage_ip): - out, rc = self._command_execute_on_active_ip(*command, **kwargv) - if not retry_disable and self._is_sp_unavailable_error(out): - # When active sp is unavailble, swith to another sp - # and set it to active - if self._toggle_sp(): - LOG.debug('EMC: Command Exception: %(rc) %(result)s. ' - 'Retry on another SP.' % {'rc': rc, - 'result': out}) - out, rc = self._command_execute_on_active_ip(*command, - **kwargv) - elif self._toggle_sp() and not retry_disable: - # If active ip is not accessible, toggled to another sp - out, rc = self._command_execute_on_active_ip(*command, **kwargv) - else: - # Active IP is inaccessible, and cannot toggle to another SP, - # return Error - out, rc = "Server Unavailable", 255 - - LOG.debug('EMC: Command: %(command)s.' - % {'command': self.command + command}) - LOG.debug('EMC: Command Result: %(result)s.' % - {'result': out.replace('\n', '\\n')}) + out, rc = self._command_execute_on_active_ip(*command, **kwargv) + if not retry_disable and self._is_sp_unavailable_error(out): + # When active sp is unavailble, swith to another sp + # and set it to active and force a poll + if self._toggle_sp(): + LOG.debug('EMC: Command Exception: %(rc) %(result)s. ' + 'Retry on another SP.', {'rc': rc, + 'result': out}) + kwargv['poll'] = True + out, rc = self._command_execute_on_active_ip(*command, + **kwargv) return out, rc @@ -1331,6 +1414,10 @@ class CommandLineHelper(object): kwargv["check_exit_code"] = True rc = 0 out = "" + need_poll = kwargv.pop('poll', True) + if "-np" not in command and not need_poll: + command = ("-np",) + command + try: active_ip = (self.active_storage_ip,) out, err = utils.execute( @@ -1343,6 +1430,11 @@ class CommandLineHelper(object): rc = pe.exit_code out = pe.stdout out = out.replace('\n', '\\n') + + LOG.debug('EMC: Command: %(command)s. Result: %(result)s.', + {'command': self.command + active_ip + command, + 'result': out.replace('\n', '\\n')}) + return out, rc def _is_sp_alive(self, ipaddr): @@ -1354,9 +1446,9 @@ class CommandLineHelper(object): out = pe.stdout rc = pe.exit_code if rc != 0: - LOG.debug('%s is unavaialbe' % ipaddr) + LOG.debug('%s is unavaialbe', ipaddr) return False - LOG.debug('Ping SP %(spip)s Command Result: %(result)s.' % + LOG.debug('Ping SP %(spip)s Command Result: %(result)s.', {'spip': self.active_storage_ip, 'result': out}) return True @@ -1373,32 +1465,29 @@ class CommandLineHelper(object): self.primary_storage_ip LOG.info(_LI('Toggle storage_vnx_ip_address from %(old)s to ' - '%(new)s.') % + '%(new)s.'), {'old': old_ip, - 'new': self.primary_storage_ip}) + 'new': self.active_storage_ip}) return True - @log_enter_exit - def get_enablers_on_array(self, no_poll=False): + def get_enablers_on_array(self, poll=False): """The function would get all the enabler installed on array. """ enablers = [] - cmd_list = ('-np', 'ndu', '-list') \ - if no_poll else ('ndu', '-list') - out, rc = self.command_execute(*cmd_list) + cmd_list = ('ndu', '-list') + out, rc = self.command_execute(*cmd_list, poll=poll) if rc != 0: - raise EMCVnxCLICmdError(cmd_list, rc, out) + self._raise_cli_error(cmd_list, rc, out) else: enabler_pat = r'Name of the software package:\s*(\S+)\s*' for m in re.finditer(enabler_pat, out): enablers.append(m.groups()[0]) - LOG.debug('Enablers on array %s.' % enablers) + LOG.debug('Enablers on array %s.', enablers) return enablers - @log_enter_exit def enable_or_disable_compression_on_lun(self, volumename, compression): """The function will enable or disable the compression on lun @@ -1412,14 +1501,39 @@ class CommandLineHelper(object): out, rc = self.command_execute(*command_compression_cmd) if 0 != rc: - raise EMCVnxCLICmdError(command_compression_cmd, rc, out) + self._raise_cli_error(command_compression_cmd, rc, out) return rc, out + def deregister_initiator(self, initiator_uid): + """This function tries to deregister initiators on VNX.""" + command_deregister = ('port', '-removeHBA', + '-hbauid', initiator_uid, + '-o') + out, rc = self.command_execute(*command_deregister) + return rc, out + def is_pool_fastcache_enabled(self, storage_pool, poll=False): + command_check_fastcache = ('storagepool', '-list', '-name', + storage_pool, '-fastcache') + out, rc = self.command_execute(*command_check_fastcache, poll=poll) + + if 0 != rc: + self._raise_cli_error(command_check_fastcache, rc, out) + else: + re_fastcache = 'FAST Cache:\s*(.*)\s*' + m = re.search(re_fastcache, out) + if m is not None: + result = True if 'Enabled' == m.group(1) else False + else: + LOG.error(_LE("Error parsing output for FastCache Command.")) + return result + + +@decorate_all_methods(log_enter_exit) class EMCVnxCliBase(object): """This class defines the functions to use the native CLI functionality.""" - VERSION = '04.01.00' + VERSION = '05.00.00' stats = {'driver_version': VERSION, 'free_capacity_gb': 'unknown', 'reserved_percentage': 0, @@ -1436,10 +1550,12 @@ class EMCVnxCliBase(object): def __init__(self, prtcl, configuration=None): self.protocol = prtcl self.configuration = configuration - self.timeout = self.configuration.default_timeout * 60 self.max_luns_per_sg = self.configuration.max_luns_per_storage_group self.destroy_empty_sg = self.configuration.destroy_empty_storage_group self.itor_auto_reg = self.configuration.initiator_auto_registration + self.itor_auto_dereg = self.configuration.initiator_auto_deregistration + self.check_max_pool_luns_threshold = ( + self.configuration.check_max_pool_luns_threshold) # if zoning_mode is fabric, use lookup service to build itor_tgt_map self.zonemanager_lookup_service = None zm_conf = Configuration(manager.volume_manager_opts) @@ -1451,9 +1567,9 @@ class EMCVnxCliBase(object): FCSanLookupService(configuration=configuration) self.max_retries = 5 if self.destroy_empty_sg: - LOG.warn(_LW("destroy_empty_storage_group: True. " - "Empty storage group will be deleted " - "after volume is detached.")) + LOG.warning(_LW("destroy_empty_storage_group: True. " + "Empty storage group will be deleted " + "after volume is detached.")) if not self.itor_auto_reg: LOG.info(_LI("initiator_auto_registration: False. " "Initiator auto registration is not enabled. " @@ -1461,6 +1577,13 @@ class EMCVnxCliBase(object): self.hlu_set = set(xrange(1, self.max_luns_per_sg + 1)) self._client = CommandLineHelper(self.configuration) self.array_serial = None + if self.protocol == 'iSCSI': + self.iscsi_targets = self._client.get_iscsi_targets(poll=True) + self.hlu_cache = {} + self.force_delete_lun_in_sg = ( + self.configuration.force_delete_lun_in_storagegroup) + if self.force_delete_lun_in_sg: + LOG.warning(_LW("force_delete_lun_in_storagegroup=True")) def get_target_storagepool(self, volume, source_volume_name=None): raise NotImplementedError @@ -1473,17 +1596,42 @@ class EMCVnxCliBase(object): self.array_serial = self._client.get_array_serial() return self.array_serial['array_serial'] - @log_enter_exit + def _construct_store_spec(self, volume, snapshot): + if snapshot['cgsnapshot_id']: + snapshot_name = snapshot['cgsnapshot_id'] + else: + snapshot_name = snapshot['name'] + source_volume_name = snapshot['volume_name'] + volume_name = volume['name'] + volume_size = snapshot['volume_size'] + dest_volume_name = volume_name + '_dest' + + pool_name = self.get_target_storagepool(volume, source_volume_name) + specs = self.get_volumetype_extraspecs(volume) + provisioning, tiering = self._get_extra_spec_value(specs) + store_spec = { + 'source_vol_name': source_volume_name, + 'volume': volume, + 'snap_name': snapshot_name, + 'dest_vol_name': dest_volume_name, + 'pool_name': pool_name, + 'provisioning': provisioning, + 'tiering': tiering, + 'volume_size': volume_size, + 'client': self._client + } + return store_spec + def create_volume(self, volume): """Creates a EMC volume.""" - volumesize = volume['size'] - volumename = volume['name'] + volume_size = volume['size'] + volume_name = volume['name'] self._volume_creation_check(volume) # defining CLI command specs = self.get_volumetype_extraspecs(volume) pool = self.get_target_storagepool(volume) - provisioning, tiering = self.get_extra_spec_value(specs) + provisioning, tiering = self._get_extra_spec_value(specs) if not provisioning: provisioning = 'thick' @@ -1491,16 +1639,16 @@ class EMCVnxCliBase(object): LOG.info(_LI('Create Volume: %(volume)s Size: %(size)s ' 'pool: %(pool)s ' 'provisioning: %(provisioning)s ' - 'tiering: %(tiering)s.') - % {'volume': volumename, - 'size': volumesize, - 'pool': pool, - 'provisioning': provisioning, - 'tiering': tiering}) + 'tiering: %(tiering)s.'), + {'volume': volume_name, + 'size': volume_size, + 'pool': pool, + 'provisioning': provisioning, + 'tiering': tiering}) data = self._client.create_lun_with_advance_feature( - pool, volumename, volumesize, - provisioning, tiering, volume['consistencygroup_id']) + pool, volume_name, volume_size, + provisioning, tiering, volume['consistencygroup_id'], False) pl_dict = {'system': self.get_array_serial(), 'type': 'lun', 'id': str(data['lun_id'])} @@ -1517,23 +1665,23 @@ class EMCVnxCliBase(object): """ specs = self.get_volumetype_extraspecs(volume) - provisioning, tiering = self.get_extra_spec_value(specs) + provisioning, tiering = self._get_extra_spec_value(specs) # step 1: check extra spec value if provisioning: - self.check_extra_spec_value( + self._check_extra_spec_value( provisioning, self._client.provisioning_values.keys()) if tiering: - self.check_extra_spec_value( + self._check_extra_spec_value( tiering, self._client.tiering_values.keys()) # step 2: check extra spec combination - self.check_extra_spec_combination(specs) + self._check_extra_spec_combination(specs) - def check_extra_spec_value(self, extra_spec, valid_values): - """check whether an extra spec's value is valid.""" + def _check_extra_spec_value(self, extra_spec, valid_values): + """Checks whether an extra spec's value is valid.""" if not extra_spec or not valid_values: LOG.error(_LE('The given extra_spec or valid_values is None.')) @@ -1543,8 +1691,8 @@ class EMCVnxCliBase(object): raise exception.VolumeBackendAPIException(data=msg) return - def get_extra_spec_value(self, extra_specs): - """get EMC extra spec values.""" + def _get_extra_spec_value(self, extra_specs): + """Gets EMC extra spec values.""" provisioning = 'thick' tiering = None @@ -1555,10 +1703,10 @@ class EMCVnxCliBase(object): return provisioning, tiering - def check_extra_spec_combination(self, extra_specs): - """check whether extra spec combination is valid.""" + def _check_extra_spec_combination(self, extra_specs): + """Checks whether extra spec combination is valid.""" - provisioning, tiering = self.get_extra_spec_value(extra_specs) + provisioning, tiering = self._get_extra_spec_value(extra_specs) enablers = self.enablers # check provisioning and tiering @@ -1591,12 +1739,29 @@ class EMCVnxCliBase(object): raise exception.VolumeBackendAPIException(data=msg) return - @log_enter_exit def delete_volume(self, volume): """Deletes an EMC volume.""" - self._client.delete_lun(volume['name']) + try: + self._client.delete_lun(volume['name']) + except EMCVnxCLICmdError as ex: + orig_out = "\n".join(ex.kwargs["out"]) + if (self.force_delete_lun_in_sg and + (self._client.CLI_RESP_PATTERN_LUN_IN_SG_1 in orig_out or + self._client.CLI_RESP_PATTERN_LUN_IN_SG_2 in orig_out)): + LOG.warning(_LW('LUN corresponding to %s is still ' + 'in some Storage Groups.' + 'Try to bring the LUN out of Storage Groups ' + 'and retry the deletion.'), + volume['name']) + lun_id = self.get_lun_id(volume) + for hlu, sg in self._client.get_hlus(lun_id): + self._client.remove_hlu_from_storagegroup(hlu, sg) + self._client.delete_lun(volume['name']) + else: + with excutils.save_and_reraise_exception(): + # Reraise the original exceiption + pass - @log_enter_exit def extend_volume(self, volume, new_size): """Extends an EMC volume.""" self._client.expand_lun_and_wait(volume['name'], new_size) @@ -1614,21 +1779,21 @@ class EMCVnxCliBase(object): false_ret = (False, None) if 'location_info' not in host['capabilities']: - LOG.warn(_LW("Failed to get target_pool_name and " - "target_array_serial. 'location_info' " - "is not in host['capabilities'].")) + LOG.warning(_LW("Failed to get target_pool_name and " + "target_array_serial. 'location_info' " + "is not in host['capabilities'].")) return false_ret # mandatory info should be ok info = host['capabilities']['location_info'] - LOG.debug("Host for migration is %s." % info) + LOG.debug("Host for migration is %s.", info) try: info_detail = info.split('|') target_pool_name = info_detail[0] target_array_serial = info_detail[1] except AttributeError: - LOG.warn(_LW("Error on parsing target_pool_name/" - "target_array_serial.")) + LOG.warning(_LW("Error on parsing target_pool_name/" + "target_array_serial.")) return false_ret if len(target_pool_name) == 0: @@ -1649,8 +1814,8 @@ class EMCVnxCliBase(object): "it doesn't support array backend .") return false_ret # source and destination should be on same array - array_serial = self._client.get_array_serial() - if target_array_serial != array_serial['array_serial']: + array_serial = self.get_array_serial() + if target_array_serial != array_serial: LOG.debug('Skip storage-assisted migration because ' 'target and source backend are not managing' 'the same array.') @@ -1660,12 +1825,11 @@ class EMCVnxCliBase(object): and self._get_original_status(volume) == 'in-use': LOG.debug('Skip storage-assisted migration because ' 'in-use volume can not be ' - 'migrate between diff protocol.') + 'migrate between different protocols.') return false_ret return (True, target_pool_name) - @log_enter_exit def migrate_volume(self, ctxt, volume, host, new_type=None): """Leverage the VNX on-array migration functionality. @@ -1691,38 +1855,37 @@ class EMCVnxCliBase(object): provisioning = 'thick' tiering = None if new_type: - provisioning, tiering = self.get_extra_spec_value( + provisioning, tiering = self._get_extra_spec_value( new_type['extra_specs']) else: - provisioning, tiering = self.get_extra_spec_value( + provisioning, tiering = self._get_extra_spec_value( self.get_volumetype_extraspecs(volume)) - self._client.create_lun_with_advance_feature( + data = self._client.create_lun_with_advance_feature( target_pool_name, new_volume_name, volume['size'], provisioning, tiering) - dst_id = self.get_lun_id_by_name(new_volume_name) + dst_id = data['lun_id'] moved = self._client.migrate_lun_with_verification( src_id, dst_id, new_volume_name) return moved, {} - @log_enter_exit def retype(self, ctxt, volume, new_type, diff, host): new_specs = new_type['extra_specs'] - new_provisioning, new_tiering = self.get_extra_spec_value( + new_provisioning, new_tiering = self._get_extra_spec_value( new_specs) # validate new_type if new_provisioning: - self.check_extra_spec_value( + self._check_extra_spec_value( new_provisioning, self._client.provisioning_values.keys()) if new_tiering: - self.check_extra_spec_value( + self._check_extra_spec_value( new_tiering, self._client.tiering_values.keys()) - self.check_extra_spec_combination(new_specs) + self._check_extra_spec_combination(new_specs) # check what changes are needed migration, tiering_change = self.determine_changes_when_retype( @@ -1745,14 +1908,14 @@ class EMCVnxCliBase(object): volume, target_pool_name, new_type)[0]: return True else: - LOG.warn(_LW('Storage-assisted migration failed during ' - 'retype.')) + LOG.warning(_LW('Storage-assisted migration failed during ' + 'retype.')) return False else: # migration is invalid LOG.debug('Driver is not able to do retype due to ' 'storage-assisted migration is not valid ' - 'in this stuation.') + 'in this situation.') return False elif not migration and tiering_change: # modify lun to change tiering policy @@ -1766,14 +1929,14 @@ class EMCVnxCliBase(object): tiering_change = False old_specs = self.get_volumetype_extraspecs(volume) - old_provisioning, old_tiering = self.get_extra_spec_value( + old_provisioning, old_tiering = self._get_extra_spec_value( old_specs) old_pool = self.get_specific_extra_spec( old_specs, self._client.pool_spec) new_specs = new_type['extra_specs'] - new_provisioning, new_tiering = self.get_extra_spec_value( + new_provisioning, new_tiering = self._get_extra_spec_value( new_specs) new_pool = self.get_specific_extra_spec( new_specs, @@ -1803,13 +1966,12 @@ class EMCVnxCliBase(object): return False return True - @log_enter_exit def update_volume_stats(self): """Update the common status share with pool and array backend. """ if not self.determine_all_enablers_exist(self.enablers): - self.enablers = self._client.get_enablers_on_array(NO_POLL) + self.enablers = self._client.get_enablers_on_array() if '-Compression' in self.enablers: self.stats['compression_support'] = 'True' else: @@ -1835,103 +1997,70 @@ class EMCVnxCliBase(object): else: self.stats['consistencygroup_support'] = 'False' - return self.stats - - @log_enter_exit - def create_export(self, context, volume): - """Driver entry point to get the export info for a new volume.""" - volumename = volume['name'] - - data = self._client.get_lun_by_name(volumename) - - device_id = data['lun_id'] - - LOG.debug('Exiting EMCVnxCliBase.create_export: Volume: %(volume)s ' - 'Device ID: %(device_id)s' - % {'volume': volumename, - 'device_id': device_id}) + if self.protocol == 'iSCSI': + self.iscsi_targets = self._client.get_iscsi_targets(poll=False) - return {'provider_location': device_id} + return self.stats - @log_enter_exit def create_snapshot(self, snapshot): """Creates a snapshot.""" - snapshotname = snapshot['name'] - volumename = snapshot['volume_name'] - - LOG.info(_LI('Create snapshot: %(snapshot)s: volume: %(volume)s') - % {'snapshot': snapshotname, - 'volume': volumename}) - - self._client.create_snapshot(volumename, snapshotname) + snapshot_name = snapshot['name'] + volume_name = snapshot['volume_name'] + volume = snapshot['volume'] + LOG.info(_LI('Create snapshot: %(snapshot)s: volume: %(volume)s'), + {'snapshot': snapshot_name, + 'volume': volume_name}) + lun_id = self.get_lun_id(volume) + self._client.create_snapshot(lun_id, snapshot_name) - @log_enter_exit def delete_snapshot(self, snapshot): """Deletes a snapshot.""" - snapshotname = snapshot['name'] + snapshot_name = snapshot['name'] - LOG.info(_LI('Delete Snapshot: %(snapshot)s') - % {'snapshot': snapshotname}) + LOG.info(_LI('Delete Snapshot: %(snapshot)s'), + {'snapshot': snapshot_name}) - self._client.delete_snapshot(snapshotname) + self._client.delete_snapshot(snapshot_name) - @log_enter_exit def create_volume_from_snapshot(self, volume, snapshot): - """Creates a volume from a snapshot.""" - if snapshot['cgsnapshot_id']: - snapshot_name = snapshot['cgsnapshot_id'] - else: - snapshot_name = snapshot['name'] - source_volume_name = snapshot['volume_name'] - volume_name = volume['name'] - volume_size = snapshot['volume_size'] - - # defining CLI command - self._client.create_mount_point(source_volume_name, volume_name) - - # defining CLI command - self._client.attach_mount_point(volume_name, snapshot_name) - - dest_volume_name = volume_name + '_dest' - - LOG.debug('Creating Temporary Volume: %s ' % dest_volume_name) - pool_name = self.get_target_storagepool(volume, source_volume_name) - try: - self._volume_creation_check(volume) - specs = self.get_volumetype_extraspecs(volume) - provisioning, tiering = self.get_extra_spec_value(specs) - self._client.create_lun_with_advance_feature( - pool_name, dest_volume_name, volume_size, - provisioning, tiering) - except exception.VolumeBackendAPIException as ex: - msg = (_('Command to create the temporary Volume %s failed') - % dest_volume_name) - LOG.error(msg) - raise ex + """Constructs a work flow to create a volume from snapshot. - source_vol_lun_id = self.get_lun_id(volume) - temp_vol_lun_id = self.get_lun_id_by_name(dest_volume_name) + This flow will do the following: - LOG.debug('Migrating Mount Point Volume: %s ' % volume_name) - self._client.migrate_lun_with_verification(source_vol_lun_id, - temp_vol_lun_id, - dest_volume_name) - self._client.check_smp_not_attached(volume_name) - data = self._client.get_lun_by_name(volume_name) - pl_dict = {'system': self.get_array_serial(), + 1. Create a snap mount point (SMP) for the snapshot. + 2. Attach the snapshot to the SMP created in the first step. + 3. Create a temporary lun prepare for migration. + 4. Start a migration between the SMP and the temp lun. + """ + self._volume_creation_check(volume) + array_serial = self.get_array_serial() + flow_name = 'create_volume_from_snapshot' + work_flow = linear_flow.Flow(flow_name) + store_spec = self._construct_store_spec(volume, snapshot) + work_flow.add(CreateSMPTask(), + AttachSnapTask(), + CreateDestLunTask(), + MigrateLunTask()) + flow_engine = taskflow.engines.load(work_flow, + store=store_spec) + flow_engine.run() + new_lun_id = flow_engine.storage.fetch('new_lun_id') + pl_dict = {'system': array_serial, 'type': 'lun', - 'id': str(data['lun_id'])} + 'id': str(new_lun_id)} model_update = {'provider_location': self.dumps_provider_location(pl_dict)} volume['provider_location'] = model_update['provider_location'] return model_update - @log_enter_exit def create_cloned_volume(self, volume, src_vref): """Creates a clone of the specified volume.""" + self._volume_creation_check(volume) + array_serial = self.get_array_serial() source_volume_name = src_vref['name'] + source_lun_id = self.get_lun_id(src_vref) volume_size = src_vref['size'] consistencygroup_id = src_vref['consistencygroup_id'] snapshot_name = 'tmp-snap-%s' % volume['id'] @@ -1943,30 +2072,42 @@ class EMCVnxCliBase(object): 'name': snapshot_name, 'volume_name': source_volume_name, 'volume_size': volume_size, + 'volume': src_vref, 'cgsnapshot_id': tmp_cgsnapshot_name, 'consistencygroup_id': consistencygroup_id, 'id': tmp_cgsnapshot_name } - # Create temp Snapshot - if consistencygroup_id: - self._client.create_cgsnapshot(snapshot) - else: - self.create_snapshot(snapshot) - - # Create volume - model_update = self.create_volume_from_snapshot(volume, snapshot) + store_spec = self._construct_store_spec(volume, snapshot) + flow_name = 'create_cloned_volume' + work_flow = linear_flow.Flow(flow_name) + store_spec.update({'snapshot': snapshot}) + store_spec.update({'source_lun_id': source_lun_id}) + work_flow.add(CreateSnapshotTask(), + CreateSMPTask(), + AttachSnapTask(), + CreateDestLunTask(), + MigrateLunTask()) + flow_engine = taskflow.engines.load(work_flow, + store=store_spec) + flow_engine.run() + new_lun_id = flow_engine.storage.fetch('new_lun_id') # Delete temp Snapshot if consistencygroup_id: self._client.delete_cgsnapshot(snapshot) else: self.delete_snapshot(snapshot) + + pl_dict = {'system': array_serial, + 'type': 'lun', + 'id': str(new_lun_id)} + model_update = {'provider_location': + self.dumps_provider_location(pl_dict)} return model_update - @log_enter_exit def create_consistencygroup(self, context, group): - """Create a consistency group.""" + """Creates a consistency group.""" LOG.info(_LI('Start to create consistency group: %(group_name)s ' - 'id: %(id)s') % + 'id: %(id)s'), {'group_name': group['name'], 'id': group['id']}) model_update = {'status': 'available'} @@ -1974,22 +2115,20 @@ class EMCVnxCliBase(object): self._client.create_consistencygroup(context, group) except Exception: with excutils.save_and_reraise_exception(): - msg = (_('Create consistency group %s failed.') - % group['id']) - LOG.error(msg) + LOG.error(_LE('Create consistency group %s failed.'), + group['id']) return model_update - @log_enter_exit def delete_consistencygroup(self, driver, context, group): - """Delete a consistency group.""" + """Deletes a consistency group.""" cg_name = group['id'] volumes = driver.db.volume_get_all_by_group(context, group['id']) model_update = {} model_update['status'] = group['status'] - LOG.info(_LI('Start to delete consistency group: %(cg_name)s') - % {'cg_name': cg_name}) + LOG.info(_LI('Start to delete consistency group: %(cg_name)s'), + {'cg_name': cg_name}) try: self._client.delete_consistencygroup(cg_name) except Exception: @@ -2008,16 +2147,15 @@ class EMCVnxCliBase(object): return model_update, volumes - @log_enter_exit def create_cgsnapshot(self, driver, context, cgsnapshot): - """Create a cgsnapshot (snap group).""" + """Creates a cgsnapshot (snap group).""" cgsnapshot_id = cgsnapshot['id'] snapshots = driver.db.snapshot_get_all_for_cgsnapshot( context, cgsnapshot_id) model_update = {} LOG.info(_LI('Start to create cgsnapshot for consistency group' - ': %(group_name)s') % + ': %(group_name)s'), {'group_name': cgsnapshot['consistencygroup_id']}) try: @@ -2026,17 +2164,15 @@ class EMCVnxCliBase(object): snapshot['status'] = 'available' except Exception: with excutils.save_and_reraise_exception(): - msg = (_('Create cg snapshot %s failed.') - % cgsnapshot_id) - LOG.error(msg) + LOG.error(_LE('Create cg snapshot %s failed.'), + cgsnapshot_id) model_update['status'] = 'available' return model_update, snapshots - @log_enter_exit def delete_cgsnapshot(self, driver, context, cgsnapshot): - """delete a cgsnapshot (snap group).""" + """Deletes a cgsnapshot (snap group).""" cgsnapshot_id = cgsnapshot['id'] snapshots = driver.db.snapshot_get_all_for_cgsnapshot( context, cgsnapshot_id) @@ -2044,7 +2180,7 @@ class EMCVnxCliBase(object): model_update = {} model_update['status'] = cgsnapshot['status'] LOG.info(_LI('Delete cgsnapshot %(snap_name)s for consistency group: ' - '%(group_name)s') % {'snap_name': cgsnapshot['id'], + '%(group_name)s'), {'snap_name': cgsnapshot['id'], 'group_name': cgsnapshot['consistencygroup_id']}) try: @@ -2053,9 +2189,8 @@ class EMCVnxCliBase(object): snapshot['status'] = 'deleted' except Exception: with excutils.save_and_reraise_exception(): - msg = (_('Delete cgsnapshot %s failed.') - % cgsnapshot_id) - LOG.error(msg) + LOG.error(_LE('Delete cgsnapshot %s failed.'), + cgsnapshot_id) return model_update, snapshots @@ -2074,9 +2209,9 @@ class EMCVnxCliBase(object): 'query it.') lun_id = self._client.get_lun_by_name(volume['name'])['lun_id'] except Exception as ex: - LOG.debug('Exception when getting lun id: %s.' % (ex)) + LOG.debug('Exception when getting lun id: %s.', six.text_type(ex)) lun_id = self._client.get_lun_by_name(volume['name'])['lun_id'] - LOG.debug('Get lun_id: %s.' % (lun_id)) + LOG.debug('Get lun_id: %s.', lun_id) return lun_id def get_lun_map(self, storage_group): @@ -2088,22 +2223,18 @@ class EMCVnxCliBase(object): return data['storage_group_uid'] def assure_storage_group(self, storage_group): - try: - self._client.create_storage_group(storage_group) - except EMCVnxCLICmdError as ex: - if ex.out.find("Storage Group name already in use") == -1: - raise ex + self._client.create_storage_group(storage_group) def assure_host_in_storage_group(self, hostname, storage_group): try: self._client.connect_host_to_storage_group(hostname, storage_group) except EMCVnxCLICmdError as ex: - if ex.rc == 83: + if ex.kwargs["rc"] == 83: # SG was not created or was destroyed by another concurrent # operation before connected. # Create SG and try to connect again - LOG.warn(_LW('Storage Group %s is not found. Create it.'), - storage_group) + LOG.warning(_LW('Storage Group %s is not found. Create it.'), + storage_group) self.assure_storage_group(storage_group) self._client.connect_host_to_storage_group( hostname, storage_group) @@ -2111,31 +2242,13 @@ class EMCVnxCliBase(object): raise ex return hostname - def find_device_details(self, volume, storage_group): - """Returns the Host Device number for the volume.""" - - host_lun_id = -1 - - data = self._client.get_storage_group(storage_group) - lun_map = data['lunmap'] - data = self._client.get_lun_by_name(volume['name']) - allocated_lun_id = data['lun_id'] + def get_lun_owner(self, volume): + """Returns SP owner of the volume.""" + data = self._client.get_lun_by_name(volume['name'], + poll=False) owner_sp = data['owner'] - - for lun in lun_map.iterkeys(): - if lun == int(allocated_lun_id): - host_lun_id = lun_map[lun] - LOG.debug('Host Lun Id : %s' % (host_lun_id)) - break - - LOG.debug('Owner SP : %s' % (owner_sp)) - - device = { - 'hostlunid': host_lun_id, - 'ownersp': owner_sp, - 'lunmap': lun_map, - } - return device + LOG.debug('Owner SP : %s', owner_sp) + return owner_sp def filter_available_hlu_set(self, used_hlus): used_hlu_set = set(used_hlus) @@ -2178,24 +2291,27 @@ class EMCVnxCliBase(object): '-spport', port_id, '-spvport', vport_id, '-ip', ip, '-host', host, '-o') out, rc = self._client.command_execute(*cmd_iscsi_setpath) - if rc != 0: - raise EMCVnxCLICmdError(cmd_iscsi_setpath, rc, out) else: cmd_fc_setpath = ('storagegroup', '-gname', gname, '-setpath', '-hbauid', initiator_uid, '-sp', sp, '-spport', port_id, '-ip', ip, '-host', host, '-o') out, rc = self._client.command_execute(*cmd_fc_setpath) - if rc != 0: - raise EMCVnxCLICmdError(cmd_fc_setpath, rc, out) + if rc != 0: + LOG.warning(_LW("Failed to register %(itor)s to SP%(sp)s " + "port %(portid)s because: %(msg)s."), + {'itor': initiator_uid, + 'sp': sp, + 'portid': port_id, + 'msg': out}) def _register_iscsi_initiator(self, ip, host, initiator_uids): + iscsi_targets = self.iscsi_targets for initiator_uid in initiator_uids: - iscsi_targets = self._client.get_iscsi_targets() LOG.info(_LI('Get ISCSI targets %(tg)s to register ' - 'initiator %(in)s.') - % ({'tg': iscsi_targets, - 'in': initiator_uid})) + 'initiator %(in)s.'), + {'tg': iscsi_targets, + 'in': initiator_uid}) target_portals_SPA = list(iscsi_targets['A']) target_portals_SPB = list(iscsi_targets['B']) @@ -2215,11 +2331,12 @@ class EMCVnxCliBase(object): ip, host, vport_id) def _register_fc_initiator(self, ip, host, initiator_uids): + fc_targets = self._client.get_fc_targets() for initiator_uid in initiator_uids: - fc_targets = self._client.get_fc_targets() - LOG.info(_LI('Get FC targets %(tg)s to register initiator %(in)s.') - % ({'tg': fc_targets, - 'in': initiator_uid})) + LOG.info(_LI('Get FC targets %(tg)s to register ' + 'initiator %(in)s.'), + {'tg': fc_targets, + 'in': initiator_uid}) target_portals_SPA = list(fc_targets['A']) target_portals_SPB = list(fc_targets['B']) @@ -2236,16 +2353,32 @@ class EMCVnxCliBase(object): self._exec_command_setpath(initiator_uid, sp, port_id, ip, host) - def _filter_unregistered_initiators(self, initiator_uids=tuple()): + def _deregister_initiators(self, connector): + initiator_uids = [] + try: + if self.protocol == 'iSCSI': + initiator_uids = self._extract_iscsi_uids(connector) + elif self.protocol == 'FC': + initiator_uids = self._extract_fc_uids(connector) + except exception.VolumeBackendAPIException: + LOG.warning(_LW("Failed to extract initiators of %s, so ignore " + "deregistration operation."), + connector['host']) + if initiator_uids: + for initiator_uid in initiator_uids: + rc, out = self._client.deregister_initiator(initiator_uid) + if rc != 0: + LOG.warning(_LW("Failed to deregister %(itor)s " + "because: %(msg)s."), + {'itor': initiator_uid, + 'msg': out}) + + def _filter_unregistered_initiators(self, initiator_uids, sgdata): unregistered_initiators = [] if not initiator_uids: return unregistered_initiators - command_get_storage_group = ('storagegroup', '-list') - out, rc = self._client.command_execute(*command_get_storage_group) - - if rc != 0: - raise EMCVnxCLICmdError(command_get_storage_group, rc, out) + out = sgdata['raw_output'] for initiator_uid in initiator_uids: m = re.search(initiator_uid, out) @@ -2253,103 +2386,144 @@ class EMCVnxCliBase(object): unregistered_initiators.append(initiator_uid) return unregistered_initiators - def auto_register_initiator(self, connector): - """Automatically register available initiators.""" + def auto_register_initiator(self, connector, sgdata): + """Automatically registers available initiators. + + Returns True if has registered initiator otherwise returns False. + """ initiator_uids = [] ip = connector['ip'] host = connector['host'] if self.protocol == 'iSCSI': initiator_uids = self._extract_iscsi_uids(connector) - itors_toReg = self._filter_unregistered_initiators(initiator_uids) - LOG.debug('iSCSI Initiators %(in)s of %(ins)s need registration.' - % ({'in': itors_toReg, - 'ins': initiator_uids})) - if not itors_toReg: - LOG.debug('Initiators %s are already registered' - % initiator_uids) - return + if sgdata is not None: + itors_toReg = self._filter_unregistered_initiators( + initiator_uids, + sgdata) + else: + itors_toReg = initiator_uids + + if len(itors_toReg) == 0: + return False + + LOG.info(_LI('iSCSI Initiators %(in)s of %(ins)s ' + 'need registration.'), + {'in': itors_toReg, + 'ins': initiator_uids}) self._register_iscsi_initiator(ip, host, itors_toReg) + return True elif self.protocol == 'FC': initiator_uids = self._extract_fc_uids(connector) - itors_toReg = self._filter_unregistered_initiators(initiator_uids) - LOG.debug('FC Initiators %(in)s of %(ins)s need registration.' - % ({'in': itors_toReg, - 'ins': initiator_uids})) - if not itors_toReg: - LOG.debug('Initiators %s are already registered.' - % initiator_uids) - return + if sgdata is not None: + itors_toReg = self._filter_unregistered_initiators( + initiator_uids, + sgdata) + else: + itors_toReg = initiator_uids + + if len(itors_toReg) == 0: + return False + + LOG.info(_LI('FC Initiators %(in)s of %(ins)s need registration'), + {'in': itors_toReg, + 'ins': initiator_uids}) self._register_fc_initiator(ip, host, itors_toReg) + return True - def assure_host_access(self, volumename, connector): + def assure_host_access(self, volume, connector): hostname = connector['host'] + volumename = volume['name'] auto_registration_done = False try: - self.get_storage_group_uid(hostname) + sgdata = self._client.get_storage_group(hostname, + poll=False) except EMCVnxCLICmdError as ex: - if ex.rc != 83: + if ex.kwargs["rc"] != 83: raise ex # Storage Group has not existed yet self.assure_storage_group(hostname) if self.itor_auto_reg: - self.auto_register_initiator(connector) + self.auto_register_initiator(connector, None) auto_registration_done = True else: self._client.connect_host_to_storage_group(hostname, hostname) + sgdata = self._client.get_storage_group(hostname, + poll=True) + if self.itor_auto_reg and not auto_registration_done: - self.auto_register_initiator(connector) - auto_registration_done = True - - lun_id = self.get_lun_id_by_name(volumename) - lun_map = self.get_lun_map(hostname) - if lun_id in lun_map: - return lun_map[lun_id] - used_hlus = lun_map.values() - if len(used_hlus) >= self.max_luns_per_sg: - msg = (_('Reach limitation set by configuration ' - 'option max_luns_per_storage_group. ' - 'Operation to add %(vol)s into ' - 'Storage Group %(sg)s is rejected.') - % {'vol': volumename, 'sg': hostname}) - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) + new_registerred = self.auto_register_initiator(connector, sgdata) + if new_registerred: + sgdata = self._client.get_storage_group(hostname, + poll=True) + + lun_id = self.get_lun_id(volume) + tried = 0 + while tried < self.max_retries: + tried += 1 + lun_map = sgdata['lunmap'] + used_hlus = lun_map.values() + candidate_hlus = self.filter_available_hlu_set(used_hlus) + candidate_hlus = list(candidate_hlus) + + if len(candidate_hlus) != 0: + hlu = candidate_hlus[random.randint(0, + len(candidate_hlus) - 1)] + try: + self._client.add_hlu_to_storage_group( + hlu, + lun_id, + hostname) + + if hostname not in self.hlu_cache: + self.hlu_cache[hostname] = {} + self.hlu_cache[hostname][lun_id] = hlu + return hlu, sgdata + except EMCVnxCLICmdError as ex: + LOG.debug("Add HLU to storagegroup failed, retry %s", + tried) + elif tried == 1: + # The first try didn't get the in time data, + # so we need a retry + LOG.debug("Did not find candidate HLUs, retry %s", + tried) + else: + msg = (_('Reach limitation set by configuration ' + 'option max_luns_per_storage_group. ' + 'Operation to add %(vol)s into ' + 'Storage Group %(sg)s is rejected.') + % {'vol': volumename, 'sg': hostname}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) - candidate_hlus = self.filter_available_hlu_set(used_hlus) - candidate_hlus = list(candidate_hlus) - random.shuffle(candidate_hlus) - for i, hlu in enumerate(candidate_hlus): - if i >= self.max_retries: - break - try: - self._client.add_hlu_to_storage_group( - hlu, - lun_id, - hostname) - return hlu - except EMCVnxCLICmdError as ex: - # Retry - continue + # Need a full poll to get the real in time data + # Query storage group with poll for retry + sgdata = self._client.get_storage_group(hostname, poll=True) + self.hlu_cache[hostname] = sgdata['lunmap'] + if lun_id in sgdata['lunmap']: + hlu = sgdata['lunmap'][lun_id] + return hlu, sgdata msg = _("Failed to add %(vol)s into %(sg)s " "after %(retries)s tries.") % \ {'vol': volumename, 'sg': hostname, - 'retries': min(self.max_retries, len(candidate_hlus))} + 'retries': tried} LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) - def vnx_get_iscsi_properties(self, volume, connector): + def vnx_get_iscsi_properties(self, volume, connector, hlu, sg_raw_output): storage_group = connector['host'] - device_info = self.find_device_details(volume, storage_group) - owner_sp = device_info['ownersp'] + owner_sp = self.get_lun_owner(volume) registered_spports = self._client.get_registered_spport_set( connector['initiator'], - storage_group) + storage_group, + sg_raw_output) target = self._client.find_avaialable_iscsi_target_one( storage_group, owner_sp, - registered_spports) + registered_spports, + self.iscsi_targets) properties = {'target_discovered': True, 'target_iqn': 'unknown', 'target_portal': 'unknown', @@ -2359,7 +2533,7 @@ class EMCVnxCliBase(object): properties = {'target_discovered': True, 'target_iqn': target['Port WWN'], 'target_portal': "%s:3260" % target['IP Address'], - 'target_lun': device_info['hostlunid']} + 'target_lun': hlu} LOG.debug("iSCSI Properties: %s", properties) auth = volume['provider_auth'] if auth: @@ -2368,19 +2542,25 @@ class EMCVnxCliBase(object): properties['auth_username'] = auth_username properties['auth_password'] = auth_secret else: - LOG.error(_LE('Failed to find an available ' - 'iSCSI targets for %s.'), + LOG.error(_LE('Failed to find an available iSCSI targets for %s.'), storage_group) return properties def vnx_get_fc_properties(self, connector, device_number): - ports = self.get_login_ports(connector) - return {'target_lun': device_number, - 'target_discovered': True, - 'target_wwn': ports} + fc_properties = {'target_lun': device_number, + 'target_dicovered': True, + 'target_wwn': None} + if self.zonemanager_lookup_service is None: + fc_properties['target_wwn'] = self.get_login_ports(connector) + else: + target_wwns, itor_tgt_map = self.get_initiator_target_map( + connector['wwpns'], + self.get_status_up_ports(connector)) + fc_properties['target_wwn'] = target_wwns + fc_properties['initiator_target_map'] = itor_tgt_map + return fc_properties - @log_enter_exit def initialize_connection(self, volume, connector): volume_metadata = {} for metadata in volume['volume_admin_metadata']: @@ -2390,27 +2570,30 @@ class EMCVnxCliBase(object): access_mode = ('ro' if volume_metadata.get('readonly') == 'True' else 'rw') - LOG.debug('Volume %(vol)s Access mode is: %(access)s.' - % {'vol': volume['name'], - 'access': access_mode}) + LOG.debug('Volume %(vol)s Access mode is: %(access)s.', + {'vol': volume['name'], + 'access': access_mode}) """Initializes the connection and returns connection info.""" @lockutils.synchronized('emc-connection-' + connector['host'], "emc-connection-", True) def do_initialize_connection(): - device_number = self.assure_host_access( - volume['name'], connector) - return device_number + return self.assure_host_access( + volume, connector) if self.protocol == 'iSCSI': - do_initialize_connection() - iscsi_properties = self.vnx_get_iscsi_properties(volume, - connector) + (device_number, sg_data) = do_initialize_connection() + iscsi_properties = self.vnx_get_iscsi_properties( + volume, + connector, + device_number, + sg_data['raw_output'] + ) iscsi_properties['access_mode'] = access_mode data = {'driver_volume_type': 'iscsi', 'data': iscsi_properties} elif self.protocol == 'FC': - device_number = do_initialize_connection() + (device_number, sg_data) = do_initialize_connection() fc_properties = self.vnx_get_fc_properties(connector, device_number) fc_properties['volume_id'] = volume['id'] @@ -2420,77 +2603,76 @@ class EMCVnxCliBase(object): return data - @log_enter_exit def terminate_connection(self, volume, connector): """Disallow connection from connector.""" - @lockutils.synchronized('emc-connection-' + connector['host'], "emc-connection-", True) def do_terminate_connection(): hostname = connector['host'] volume_name = volume['name'] - try: - lun_map = self.get_lun_map(hostname) - except EMCVnxCLICmdError as ex: - if ex.rc == 83: - LOG.warn(_LW("Storage Group %s is not found. " - "terminate_connection() is unnecessary."), - hostname) - return True - try: - lun_id = self.get_lun_id(volume) - except EMCVnxCLICmdError as ex: - if ex.rc == 9: - LOG.warn(_LW("Volume %s is not found. " - "It has probably been removed in VNX.") - % volume_name) - - if lun_id in lun_map: - self._client.remove_hlu_from_storagegroup( - lun_map[lun_id], hostname) + lun_id = self.get_lun_id(volume) + lun_map = None + conn_info = None + if (hostname in self.hlu_cache and + lun_id in self.hlu_cache[hostname] and + not self.destroy_empty_sg and + not self.zonemanager_lookup_service): + hlu = self.hlu_cache[hostname][lun_id] + self._client.remove_hlu_from_storagegroup(hlu, hostname, + poll=True) + self.hlu_cache[hostname].pop(lun_id) else: - LOG.warn(_LW("Volume %(vol)s was not in Storage Group %(sg)s.") - % {'vol': volume_name, 'sg': hostname}) - if self.destroy_empty_sg or self.zonemanager_lookup_service: try: lun_map = self.get_lun_map(hostname) - if not lun_map: - LOG.debug("Storage Group %s was empty.", hostname) - if self.destroy_empty_sg: - LOG.info(_LI("Storage Group %s was empty, " - "destroy it."), hostname) - self._client.disconnect_host_from_storage_group( - hostname, hostname) - self._client.delete_storage_group(hostname) - return True - else: - LOG.debug("Storage Group %s not empty,", hostname) - return False + self.hlu_cache[hostname] = lun_map + except EMCVnxCLICmdError as ex: + if ex.kwargs["rc"] == 83: + LOG.warning(_LW("Storage Group %s is not found. " + "terminate_connection() is " + "unnecessary."), + hostname) + if lun_id in lun_map: + self._client.remove_hlu_from_storagegroup( + lun_map[lun_id], hostname) + lun_map.pop(lun_id) + else: + LOG.warning(_LW("Volume %(vol)s was not in Storage Group" + " %(sg)s."), + {'vol': volume_name, 'sg': hostname}) + + if self.protocol == 'FC': + conn_info = {'driver_volume_type': 'fibre_channel', + 'data': {}} + if self.zonemanager_lookup_service and not lun_map: + target_wwns, itor_tgt_map = self.get_initiator_target_map( + connector['wwpns'], + self.get_status_up_ports(connector)) + conn_info['data']['initiator_target_map'] = itor_tgt_map + + if self.destroy_empty_sg and not lun_map: + try: + LOG.info(_LI("Storage Group %s was empty."), hostname) + self._client.disconnect_host_from_storage_group( + hostname, hostname) + self._client.delete_storage_group(hostname) + if self.itor_auto_dereg: + self._deregister_initiators(connector) except Exception: - LOG.warn(_LW("Failed to destroy Storage Group %s."), - hostname) - else: - return False + LOG.warning(_LW("Failed to destroy Storage Group %s."), + hostname) + try: + self._client.connect_host_to_storage_group( + hostname, hostname) + except Exception: + LOG.warning(_LW("Fail to connect host %(host)s " + "back to storage group %(sg)s."), + {'host': hostname, 'sg': hostname}) + return conn_info return do_terminate_connection() - @log_enter_exit - def adjust_fc_conn_info(self, conn_info, connector, remove_zone=None): - target_wwns, itor_tgt_map = self.get_initiator_target_map( - connector['wwpns'], - self.get_status_up_ports(connector)) - if target_wwns: - conn_info['data']['target_wwn'] = target_wwns - if remove_zone is None or remove_zone: - # Return initiator_target_map for initialize_connection (None) - # Return initiator_target_map for terminate_connection when (True) - # no volumes are in the storagegroup for host to use - conn_info['data']['initiator_target_map'] = itor_tgt_map - return conn_info - - @log_enter_exit def manage_existing_get_size(self, volume, ref): - """Return size of volume to be managed by manage_existing. - """ + """Return size of volume to be managed by manage_existing.""" + # Check that the reference is valid if 'id' not in ref: reason = _('Reference must contain lun_id element.') @@ -2506,9 +2688,19 @@ class EMCVnxCliBase(object): reason=reason) return data['total_capacity_gb'] - @log_enter_exit def manage_existing(self, volume, ref): - raise NotImplementedError + """Imports the existing backend storage object as a volume. + + Renames the backend storage object so that it matches the, + volume['name'] which is how drivers traditionally map between a + cinder volume and the associated backend storage object. + + existing_ref:{ + 'id':lun_id + } + """ + + self._client.lun_rename(ref['id'], volume['name']) def find_iscsi_protocol_endpoints(self, device_sp): """Returns the iSCSI initiators for a SP.""" @@ -2546,6 +2738,7 @@ class EMCVnxCliBase(object): return specs +@decorate_all_methods(log_enter_exit) class EMCVnxCliPool(EMCVnxCliBase): def __init__(self, prtcl, configuration): @@ -2568,71 +2761,68 @@ class EMCVnxCliPool(EMCVnxCliBase): raise exception.VolumeBackendAPIException(data=msg) return self.storage_pool - def is_pool_fastcache_enabled(self, storage_pool, no_poll=False): - command_check_fastcache = None - if no_poll: - command_check_fastcache = ('-np', 'storagepool', '-list', '-name', - storage_pool, '-fastcache') - else: - command_check_fastcache = ('storagepool', '-list', '-name', - storage_pool, '-fastcache') - out, rc = self._client.command_execute(*command_check_fastcache) - - if 0 != rc: - raise EMCVnxCLICmdError(command_check_fastcache, rc, out) - else: - re_fastcache = 'FAST Cache:\s*(.*)\s*' - m = re.search(re_fastcache, out) - if m is not None: - result = True if 'Enabled' == m.group(1) else False - else: - LOG.error(_LE("Error parsing output for FastCache Command.")) - return result - - @log_enter_exit def update_volume_stats(self): - """Retrieve stats info.""" + """Retrieves stats info.""" self.stats = super(EMCVnxCliPool, self).update_volume_stats() - data = self._client.get_pool(self.get_target_storagepool()) - self.stats['total_capacity_gb'] = data['total_capacity_gb'] - self.stats['free_capacity_gb'] = data['free_capacity_gb'] - - array_serial = self._client.get_array_serial(NO_POLL) + pool = self._client.get_pool(self.get_target_storagepool(), + poll=False) + self.stats['total_capacity_gb'] = pool['total_capacity_gb'] + self.stats['free_capacity_gb'] = pool['free_capacity_gb'] + # Some extra capacity will be used by meta data of pool LUNs. + # The overhead is about LUN_Capacity * 0.02 + 3 GB + # reserved_percentage will be used to make sure the scheduler + # takes the overhead into consideration + # Assume that all the remaining capacity is to be used to create + # a thick LUN, reserved_percentage is estimated as follows: + reserved = (((0.02 * pool['free_capacity_gb'] + 3) / + (1.02 * pool['total_capacity_gb'])) * 100) + self.stats['reserved_percentage'] = int(math.ceil(min(reserved, 100))) + if self.check_max_pool_luns_threshold: + pool_feature = self._client.get_pool_feature_properties(poll=False) + if (pool_feature['max_pool_luns'] + <= pool_feature['total_pool_luns']): + LOG.warning(_LW("Maximum number of Pool LUNs, %s, " + "have been created. " + "No more LUN creation can be done."), + pool_feature['max_pool_luns']) + self.stats['free_capacity_gb'] = 0 + array_serial = self._client.get_array_serial() self.stats['location_info'] = ('%(pool_name)s|%(array_serial)s' % {'pool_name': self.storage_pool, 'array_serial': array_serial['array_serial']}) # check if this pool's fast_cache is really enabled if self.stats['fast_cache_enabled'] == 'True' and \ - not self.is_pool_fastcache_enabled(self.storage_pool, NO_POLL): + not self._client.is_pool_fastcache_enabled(self.storage_pool): self.stats['fast_cache_enabled'] = 'False' return self.stats - @log_enter_exit - def manage_existing(self, volume, ref): - """Manage an existing lun in the array. - - The lun should be in a manageable pool backend, otherwise - error would return. - Rename the backend storage object so that it matches the, - volume['name'] which is how drivers traditionally map between a - cinder volume and the associated backend storage object. - - existing_ref:{ - 'id':lun_id - } - """ + def manage_existing_get_size(self, volume, ref): + """Returns size of volume to be managed by manage_existing.""" + # Check that the reference is valid + if 'id' not in ref: + reason = _('Reference must contain lun_id element.') + raise exception.ManageExistingInvalidReference( + existing_ref=ref, + reason=reason) + # Check for existence of the lun data = self._client.get_lun_by_id( - ref['id'], self._client.LUN_WITH_POOL) - if self.storage_pool != data['pool']: + ref['id'], + properties=self._client.LUN_WITH_POOL) + if data is None: + reason = _('Cannot find the lun with LUN id %s.') % ref['id'] + raise exception.ManageExistingInvalidReference(existing_ref=ref, + reason=reason) + if data['pool'] != self.storage_pool: reason = _('The input lun is not in a manageable pool backend ' 'by cinder') raise exception.ManageExistingInvalidReference(existing_ref=ref, reason=reason) - self._client.lun_rename(ref['id'], volume['name']) + return data['total_capacity_gb'] +@decorate_all_methods(log_enter_exit) class EMCVnxCliArray(EMCVnxCliBase): def __init__(self, prtcl, configuration): @@ -2642,7 +2832,7 @@ class EMCVnxCliArray(EMCVnxCliBase): def _update_pool_cache(self): LOG.debug("Updating Pool Cache") - self.pool_cache = self._client.get_pool_list(NO_POLL) + self.pool_cache = self._client.get_pool_list(poll=False) def get_target_storagepool(self, volume, source_volume_name=None): """Find the storage pool for given volume.""" @@ -2671,14 +2861,13 @@ class EMCVnxCliArray(EMCVnxCliBase): LOG.error(msg) raise exception.VolumeBackendAPIException(data=msg) - @log_enter_exit def update_volume_stats(self): """Retrieve stats info.""" self.stats = super(EMCVnxCliArray, self).update_volume_stats() self._update_pool_cache() self.stats['total_capacity_gb'] = 'unknown' self.stats['free_capacity_gb'] = 'unknown' - array_serial = self._client.get_array_serial(NO_POLL) + array_serial = self._client.get_array_serial() self.stats['location_info'] = ('%(pool_name)s|%(array_serial)s' % {'pool_name': '', 'array_serial': @@ -2686,19 +2875,6 @@ class EMCVnxCliArray(EMCVnxCliBase): self.stats['fast_cache_enabled'] = 'unknown' return self.stats - @log_enter_exit - def manage_existing(self, volume, ref): - """Rename the backend storage object so that it matches the, - volume['name'] which is how drivers traditionally map between a - cinder volume and the associated backend storage object. - - existing_ref:{ - 'id':lun_id - } - """ - - self._client.lun_rename(ref['id'], volume['name']) - def getEMCVnxCli(prtcl, configuration=None): configuration.append_config_values(loc_opts) @@ -2708,3 +2884,135 @@ def getEMCVnxCli(prtcl, configuration=None): return EMCVnxCliArray(prtcl, configuration=configuration) else: return EMCVnxCliPool(prtcl, configuration=configuration) + + +class CreateSMPTask(task.Task): + """Creates a snap mount point (SMP) for the source snapshot. + + Reversion strategy: Delete the SMP. + """ + def execute(self, client, volume, source_vol_name, *args, **kwargs): + LOG.debug('CreateSMPTask.execute') + client.create_mount_point(source_vol_name, volume['name']) + + def revert(self, result, client, volume, *args, **kwargs): + LOG.debug('CreateSMPTask.revert') + if isinstance(result, failure.Failure): + return + else: + LOG.warning(_LW('CreateSMPTask.revert: delete mount point %s'), + volume['name']) + client.delete_lun(volume['name']) + + +class AttachSnapTask(task.Task): + """Attaches the snapshot to the SMP created before. + + Reversion strategy: Detach the SMP. + """ + def execute(self, client, volume, snap_name, *args, **kwargs): + LOG.debug('AttachSnapTask.execute') + client.attach_mount_point(volume['name'], snap_name) + + def revert(self, result, client, volume, *args, **kwargs): + LOG.debug('AttachSnapTask.revert') + if isinstance(result, failure.Failure): + return + else: + LOG.warning(_LW('AttachSnapTask.revert: detach mount point %s'), + volume['name']) + client.detach_mount_point(volume['name']) + + +class CreateDestLunTask(task.Task): + """Creates a destination lun for migration. + + Reversion strategy: Detach the temp lun. + """ + def __init__(self): + super(CreateDestLunTask, self).__init__(provides='lun_data') + + def execute(self, client, pool_name, dest_vol_name, volume_size, + provisioning, tiering, *args, **kwargs): + LOG.debug('CreateDestLunTask.execute') + data = client.create_lun_with_advance_feature( + pool_name, dest_vol_name, volume_size, + provisioning, tiering) + return data + + def revert(self, result, client, dest_vol_name, *args, **kwargs): + LOG.debug('CreateDestLunTask.revert') + if isinstance(result, failure.Failure): + return + else: + LOG.warning(_LW('CreateDestLunTask.revert: delete temp lun %s'), + dest_vol_name) + client.delete_lun(dest_vol_name) + + +class MigrateLunTask(task.Task): + """Starts a migration between the SMP and the temp lun. + + Reversion strategy: None + """ + def __init__(self): + super(MigrateLunTask, self).__init__(provides='new_lun_id') + + def execute(self, client, dest_vol_name, volume, lun_data, + *args, **kwargs): + LOG.debug('MigrateLunTask.execute') + new_vol_name = volume['name'] + new_vol_lun_id = client.get_lun_by_name(new_vol_name)['lun_id'] + dest_vol_lun_id = lun_data['lun_id'] + + LOG.info(_LI('Migrating Mount Point Volume: %s'), new_vol_name) + + migrated = client.migrate_lun_with_verification(new_vol_lun_id, + dest_vol_lun_id, + None) + if not migrated: + msg = (_LE("Migrate volume failed between source vol %(src)s" + " and dest vol %(dst)s."), + {'src': new_vol_name, 'dst': dest_vol_name}) + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + return new_vol_lun_id + + def revert(self, *args, **kwargs): + pass + + +class CreateSnapshotTask(task.Task): + """Creates a snapshot/cgsnapshot of a volume. + + Reversion Strategy: Delete the created snapshot/cgsnapshot. + """ + def execute(self, client, snapshot, source_lun_id, *args, **kwargs): + LOG.debug('CreateSnapshotTask.execute') + # Create temp Snapshot + if snapshot['consistencygroup_id']: + client.create_cgsnapshot(snapshot) + else: + snapshot_name = snapshot['name'] + volume_name = snapshot['volume_name'] + LOG.info(_LI('Create snapshot: %(snapshot)s: volume: %(volume)s'), + {'snapshot': snapshot_name, + 'volume': volume_name}) + client.create_snapshot(source_lun_id, snapshot_name) + + def revert(self, result, client, snapshot, *args, **kwargs): + LOG.debug('CreateSnapshotTask.revert') + if isinstance(result, failure.Failure): + return + else: + if snapshot['consistencygroup_id']: + LOG.warning(_LW('CreateSnapshotTask.revert: ' + 'delete temp cgsnapshot %s'), + snapshot['consistencygroup_id']) + client.delete_cgsnapshot(snapshot) + else: + LOG.warning(_LW('CreateSnapshotTask.revert: ' + 'delete temp snapshot %s'), + snapshot['name']) + client.delete_snapshot(snapshot['name']) -- 2.45.2