diff --git a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py index 1e787b0b7..b85e78fa7 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py @@ -98,7 +98,7 @@ class MockClient(object): @staticmethod def get_snap(name=None): - snap = test_client.MockResource(name=name) + snap = test_client.MockResource(name=name, _id=name) if name is not None: ret = snap else: @@ -192,6 +192,18 @@ def get_lun_pl(name): return 'id^%s|system^CLIENT_SERIAL|type^lun|version^None' % name +def get_snap_pl(name): + return 'id^%s|system^CLIENT_SERIAL|type^snapshot|version^None' % name + + +def get_connector_uids(adapter, connector): + return [] + + +def get_connection_info(adapter, hlu, host, connector): + return {} + + def patch_for_unity_adapter(func): @functools.wraps(func) @mock.patch('cinder.volume.drivers.dell_emc.unity.utils.' @@ -206,6 +218,28 @@ def patch_for_unity_adapter(func): return func_wrapper +def patch_for_concrete_adapter(clz_str): + def inner_decorator(func): + @functools.wraps(func) + @mock.patch('%s.get_connector_uids' % clz_str, + new=get_connector_uids) + @mock.patch('%s.get_connection_info' % clz_str, + new=get_connection_info) + def func_wrapper(*args, **kwargs): + return func(*args, **kwargs) + return func_wrapper + + return inner_decorator + + +patch_for_iscsi_adapter = patch_for_concrete_adapter( + 'cinder.volume.drivers.dell_emc.unity.adapter.ISCSIAdapter') + + +patch_for_fc_adapter = patch_for_concrete_adapter( + 'cinder.volume.drivers.dell_emc.unity.adapter.FCAdapter') + + ######################## # # Start of Tests @@ -233,8 +267,8 @@ class CommonAdapterTest(unittest.TestCase): snap = mock.Mock(volume=volume) snap.name = 'abc-def_snap' result = self.adapter.create_snapshot(snap) - self.assertEqual('abc-def_snap', result.name) - self.assertEqual('lun_43', result.get_id()) + self.assertEqual(get_snap_pl('lun_43'), result['provider_location']) + self.assertEqual('lun_43', result['provider_id']) def test_delete_snap(self): def f(): @@ -311,22 +345,7 @@ class CommonAdapterTest(unittest.TestCase): self.assertEqual(self.adapter.array_ca_cert_path, self.adapter.verify_cert) - def test_initialize_connection_common(self): - volume = mock.Mock(provider_location='id^lun_43', id='id_43') - connector = {'host': 'host1'} - data = self.adapter.initialize_connection(volume, connector)['data'] - self.assertTrue(data['target_discovered']) - self.assertEqual('id_43', data['volume_id']) - - def test_initialize_connection_for_resource(self): - snap = test_client.MockResource(_id='snap_1') - connector = {'host': 'host1'} - data = self.adapter._initialize_connection( - snap, connector, 'snap_1')['data'] - self.assertTrue(data['target_discovered']) - self.assertEqual('snap_1', data['volume_id']) - - def test_terminate_connection_common(self): + def test_terminate_connection_volume(self): def f(): volume = mock.Mock(provider_location='id^lun_43', id='id_43') connector = {'host': 'host1'} @@ -334,11 +353,12 @@ class CommonAdapterTest(unittest.TestCase): self.assertRaises(ex.DetachIsCalled, f) - def test_terminate_connection_snap(self): + def test_terminate_connection_snapshot(self): def f(): connector = {'host': 'host1'} - snap = test_client.MockResource(_id='snap_0') - self.adapter._terminate_connection(snap, connector) + snap = mock.Mock(id='snap_0', name='snap_0') + snap.name = 'snap_0' + self.adapter.terminate_connection_snapshot(snap, connector) self.assertRaises(ex.DetachIsCalled, f) @@ -475,6 +495,25 @@ class FCAdapterTest(unittest.TestCase): wwns = ['8899AABBCCDDEEFF', '8899AABBCCDDFFEE'] self.assertListEqual(wwns, ret['target_wwn']) + @patch_for_fc_adapter + def test_initialize_connection_volume(self): + volume = mock.Mock(provider_location='id^lun_43', id='id_43') + connector = {'host': 'host1'} + conn_info = self.adapter.initialize_connection(volume, connector) + self.assertEqual('fibre_channel', conn_info['driver_volume_type']) + self.assertTrue(conn_info['data']['target_discovered']) + self.assertEqual('id_43', conn_info['data']['volume_id']) + + @patch_for_fc_adapter + def test_initialize_connection_snapshot(self): + snap = mock.Mock(id='snap_1', name='snap_1') + connector = {'host': 'host1'} + conn_info = self.adapter.initialize_connection_snapshot( + snap, connector) + self.assertEqual('fibre_channel', conn_info['driver_volume_type']) + self.assertTrue(conn_info['data']['target_discovered']) + self.assertEqual('snap_1', conn_info['data']['volume_id']) + def test_terminate_connection_auto_zone_enabled(self): connector = {'host': 'host1', 'wwpns': 'abcdefg'} volume = mock.Mock(provider_location='id^lun_41', id='id_41') @@ -514,3 +553,22 @@ class ISCSIAdapterTest(unittest.TestCase): self.assertEqual(hlu, info['target_lun']) self.assertTrue(info['target_portal'] in target_portals) self.assertTrue(info['target_iqn'] in target_iqns) + + @patch_for_iscsi_adapter + def test_initialize_connection_volume(self): + volume = mock.Mock(provider_location='id^lun_43', id='id_43') + connector = {'host': 'host1'} + conn_info = self.adapter.initialize_connection(volume, connector) + self.assertEqual('iscsi', conn_info['driver_volume_type']) + self.assertTrue(conn_info['data']['target_discovered']) + self.assertEqual('id_43', conn_info['data']['volume_id']) + + @patch_for_iscsi_adapter + def test_initialize_connection_snapshot(self): + snap = mock.Mock(id='snap_1', name='snap_1') + connector = {'host': 'host1'} + conn_info = self.adapter.initialize_connection_snapshot( + snap, connector) + self.assertEqual('iscsi', conn_info['driver_volume_type']) + self.assertTrue(conn_info['data']['target_discovered']) + self.assertEqual('snap_1', conn_info['data']['volume_id']) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py index 0f7cb261d..b1ce7721b 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py @@ -56,6 +56,7 @@ class MockAdapter(object): @staticmethod def create_snapshot(snapshot): snapshot.exists = True + return snapshot @staticmethod def delete_snapshot(snapshot): @@ -88,6 +89,14 @@ class MockAdapter(object): def get_pool_name(volume): return 'pool_0' + @staticmethod + def initialize_connection_snapshot(snapshot, connector): + return {'snapshot': snapshot, 'connector': connector} + + @staticmethod + def terminate_connection_snapshot(snapshot, connector): + return {'snapshot': snapshot, 'connector': connector} + ######################## # @@ -232,3 +241,29 @@ class UnityDriverTest(unittest.TestCase): def test_unmanage(self): ret = self.driver.unmanage(None) self.assertIsNone(ret) + + def test_backup_use_temp_snapshot(self): + self.assertTrue(self.driver.backup_use_temp_snapshot()) + + def test_create_export_snapshot(self): + snapshot = self.driver.create_export_snapshot(self.get_context(), + self.get_snapshot(), + self.get_connector()) + self.assertTrue(snapshot.exists) + + def test_remove_export_snapshot(self): + snapshot = self.get_snapshot() + self.driver.remove_export_snapshot(self.get_context(), snapshot) + self.assertFalse(snapshot.exists) + + def test_initialize_connection_snapshot(self): + snapshot = self.get_snapshot() + conn_info = self.driver.initialize_connection_snapshot( + snapshot, self.get_connector()) + self.assertEqual(snapshot, conn_info['snapshot']) + + def test_terminate_connection_snapshot(self): + snapshot = self.get_snapshot() + conn_info = self.driver.terminate_connection_snapshot( + snapshot, self.get_connector()) + self.assertEqual(snapshot, conn_info['snapshot']) diff --git a/cinder/volume/drivers/dell_emc/unity/adapter.py b/cinder/volume/drivers/dell_emc/unity/adapter.py index 580658dd0..b0e106f73 100644 --- a/cinder/volume/drivers/dell_emc/unity/adapter.py +++ b/cinder/volume/drivers/dell_emc/unity/adapter.py @@ -129,8 +129,8 @@ class CommonAdapter(object): location = self._build_provider_location( lun_type='lun', lun_id=lun.get_id()) - model_update = {'provider_location': location} - return model_update + return {'provider_location': location, + 'provider_id': lun.get_id()} def delete_volume(self, volume): lun_id = self.get_lun_id(volume) @@ -141,6 +141,7 @@ class CommonAdapter(object): else: self.client.delete_lun(lun_id) + @cinder_utils.trace def _initialize_connection(self, lun_or_snap, connector, vol_id): host = self.client.create_host(connector['host'], self.get_connector_uids(connector)) @@ -156,17 +157,20 @@ class CommonAdapter(object): LOG.debug('Initialized connection info: %s', conn_info) return conn_info + @cinder_utils.trace def initialize_connection(self, volume, connector): lun = self.client.get_lun(lun_id=self.get_lun_id(volume)) return self._initialize_connection(lun, connector, volume.id) + @cinder_utils.trace def _terminate_connection(self, lun_or_snap, connector): host = self.client.get_host(connector['host']) self.client.detach(host, lun_or_snap) + @cinder_utils.trace def terminate_connection(self, volume, connector): lun = self.client.get_lun(lun_id=self.get_lun_id(volume)) - self._terminate_connection(lun, connector) + return self._terminate_connection(lun, connector) def get_connector_uids(self, connector): return None @@ -247,7 +251,11 @@ class CommonAdapter(object): :param snapshot: snapshot information. """ src_lun_id = self.get_lun_id(snapshot.volume) - return self.client.create_snap(src_lun_id, snapshot.name) + snap = self.client.create_snap(src_lun_id, snapshot.name) + location = self._build_provider_location(lun_type='snapshot', + lun_id=snap.get_id()) + return {'provider_location': location, + 'provider_id': snap.get_id()} def delete_snapshot(self, snapshot): """Deletes a snapshot. @@ -300,7 +308,8 @@ class CommonAdapter(object): lun.modify(name=volume.name) return {'provider_location': self._build_provider_location(lun_id=lun.get_id(), - lun_type='lun')} + lun_type='lun'), + 'provider_id': lun.get_id()} def manage_existing_get_size(self, volume, existing_ref): """Returns size of volume to be managed by `manage_existing`. @@ -364,7 +373,8 @@ class CommonAdapter(object): data from the Unity snapshot to the `volume`. """ model_update = self.create_volume(volume) - volume.provider_location = model_update['provider_location'] + # Update `provider_location` and `provider_id` of `volume` explicitly. + volume.update(model_update) src_id = snap.get_id() dest_lun = self.client.get_lun(lun_id=self.get_lun_id(volume)) try: @@ -430,6 +440,16 @@ class CommonAdapter(object): def get_pool_name(self, volume): return self.client.get_pool_name(volume.name) + @cinder_utils.trace + def initialize_connection_snapshot(self, snapshot, connector): + snap = self.client.get_snap(snapshot.name) + return self._initialize_connection(snap, connector, snapshot.id) + + @cinder_utils.trace + def terminate_connection_snapshot(self, snapshot, connector): + snap = self.client.get_snap(snapshot.name) + return self._terminate_connection(snap, connector) + class ISCSIAdapter(CommonAdapter): protocol = PROTOCOL_ISCSI @@ -496,8 +516,11 @@ class FCAdapter(CommonAdapter): data['target_lun'] = hlu return data - def terminate_connection(self, volume, connector): - super(FCAdapter, self).terminate_connection(volume, connector) + @cinder_utils.trace + def _terminate_connection(self, lun_or_snap, connector): + # For FC, terminate_connection needs to return data to zone manager + # which would clean the zone based on the data. + super(FCAdapter, self)._terminate_connection(lun_or_snap, connector) ret = None if self.auto_zone_enabled: @@ -510,7 +533,6 @@ class FCAdapter(CommonAdapter): targets = self.client.get_fc_target_info(logged_in_only=True) ret['data'] = self._get_fc_zone_info(connector['wwpns'], targets) - return ret def _get_fc_zone_info(self, initiator_wwns, target_wwns): diff --git a/cinder/volume/drivers/dell_emc/unity/driver.py b/cinder/volume/drivers/dell_emc/unity/driver.py index 7c8861ba2..686218aa6 100644 --- a/cinder/volume/drivers/dell_emc/unity/driver.py +++ b/cinder/volume/drivers/dell_emc/unity/driver.py @@ -152,27 +152,12 @@ class UnityDriver(driver.ManageableVD, } } """ - LOG.debug("Entering initialize_connection" - " - connector: %(connector)s.", - {'connector': connector}) - conn_info = self.adapter.initialize_connection(volume, - connector) - LOG.debug("Exit initialize_connection" - " - Returning connection info: %(conn_info)s.", - {'conn_info': conn_info}) - return conn_info + return self.adapter.initialize_connection(volume, connector) @zm_utils.remove_fc_zone def terminate_connection(self, volume, connector, **kwargs): """Disallow connection from connector.""" - LOG.debug("Entering terminate_connection" - " - connector: %(connector)s.", - {'connector': connector}) - conn_info = self.adapter.terminate_connection(volume, connector) - LOG.debug("Exit terminate_connection" - " - Returning connection info: %(conn_info)s.", - {'conn_info': conn_info}) - return conn_info + return self.adapter.terminate_connection(volume, connector) def get_volume_stats(self, refresh=False): """Get volume stats. @@ -211,3 +196,20 @@ class UnityDriver(driver.ManageableVD, def unmanage(self, volume): """Unmanages a volume.""" pass + + def backup_use_temp_snapshot(self): + return True + + def create_export_snapshot(self, context, snapshot, connector): + """Creates the snapshot for backup.""" + return self.adapter.create_snapshot(snapshot) + + def remove_export_snapshot(self, context, snapshot): + """Deletes the snapshot for backup.""" + self.adapter.delete_snapshot(snapshot) + + def initialize_connection_snapshot(self, snapshot, connector, **kwargs): + return self.adapter.initialize_connection_snapshot(snapshot, connector) + + def terminate_connection_snapshot(self, snapshot, connector, **kwargs): + return self.adapter.terminate_connection_snapshot(snapshot, connector) diff --git a/releasenotes/notes/unity-backup-via-snapshot-81a2d5a118c97042.yaml b/releasenotes/notes/unity-backup-via-snapshot-81a2d5a118c97042.yaml new file mode 100644 index 000000000..0a03b17b6 --- /dev/null +++ b/releasenotes/notes/unity-backup-via-snapshot-81a2d5a118c97042.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add support to backup volume using snapshot in the Unity driver.