from oslo_concurrency import processutils
import six
+from cinder import context
from cinder import exception
from cinder import test
from cinder.tests.unit import fake_snapshot
+from cinder.tests.unit import fake_volume
from cinder.tests.unit import utils
from cinder.volume import configuration as conf
from cinder.volume.drivers.emc import emc_cli_fc
from cinder.volume.drivers.emc import emc_vnx_cli
from cinder.zonemanager import fc_san_lookup_service as fc_service
+from mock import patch
+
SUCCEED = ("", 0)
FAKE_ERROR_RETURN = ("FAKE ERROR", 255)
class EMCVNXCLIDriverTestData(object):
test_volume = {
+ 'status': 'creating',
'name': 'vol1',
'size': 1,
'volume_name': 'vol1',
mock_utils.assert_has_calls(expected)
-class EMCVNXCLIDMultiPoolsTestCase(DriverTestCaseBase):
+class EMCVNXCLIBackupTestCase(DriverTestCaseBase):
+ """Provides cli-level and client-level mock test."""
+
+ def driverSetup(self):
+ self.context = context.get_admin_context()
+ self.driver = self.generate_driver(self.configuration)
+ self.driver.cli._client = mock.Mock()
+ self.snapshot = fake_snapshot.fake_snapshot_obj(
+ self.context, **self.testData.test_snapshot)
+ volume = fake_volume.fake_volume_obj(self.context)
+ self.snapshot.volume = volume
+ return self.driver.cli._client
+
+ def generate_driver(self, conf):
+ driver = emc_cli_iscsi.EMCCLIISCSIDriver(configuration=conf)
+ return driver
+
+ @patch.object(emc_vnx_cli.EMCVnxCliBase, 'terminate_connection')
+ def test_terminate_connection_snapshot(self, terminate_connection):
+ fake_client = self.driverSetup()
+ connector = self.testData.connector
+ smp_name = 'tmp-smp-' + self.snapshot['id']
+ volume = {'name': smp_name}
+ self.driver.terminate_connection_snapshot(
+ self.snapshot, connector)
+ terminate_connection.assert_called_once_with(
+ volume, connector)
+ fake_client.detach_mount_point.assert_called_once_with(
+ smp_name)
+
+ @patch.object(emc_vnx_cli.EMCVnxCliBase, 'initialize_connection')
+ def test_initialize_connection_snapshot(self, initialize_connection):
+ fake_client = self.driverSetup()
+ connector = self.testData.connector
+ smp_name = 'tmp-smp-' + self.snapshot['id']
+ self.driver.initialize_connection_snapshot(
+ self.snapshot, connector)
+ fake_client.attach_mount_point.assert_called_once_with(
+ smp_name, self.snapshot['name'])
+ volume = {'name': smp_name, 'id': self.snapshot['id']}
+ initialize_connection.assert_called_once_with(
+ volume, connector)
+
+ def test_create_export_snapshot(self):
+ fake_client = self.driverSetup()
+ connector = self.testData.connector
+ smp_name = 'tmp-smp-' + self.snapshot['id']
+ self.driver.create_export_snapshot(
+ None, self.snapshot, connector)
+ fake_client.create_mount_point.assert_called_once_with(
+ self.snapshot['volume_name'], smp_name)
+
+ @patch.object(emc_vnx_cli.EMCVnxCliBase, 'delete_volume')
+ def test_remove_export_snapshot(self, delete_volume):
+ self.driverSetup()
+ smp_name = 'tmp-smp-' + self.snapshot['id']
+ self.driver.remove_export_snapshot(None, self.snapshot)
+ volume = {'name': smp_name, 'provider_location': None}
+ delete_volume.assert_called_once_with(volume, True)
+
+
+class EMCVNXCLIMultiPoolsTestCase(DriverTestCaseBase):
def generate_driver(self, conf):
driver = emc_cli_iscsi.EMCCLIISCSIDriver(configuration=conf)
Manage/unmanage volume revise
White list target ports support
Snap copy support
+ Support efficient non-disruptive backup
"""
def __init__(self, *args, **kwargs):
def update_migrated_volume(self, context, volume, new_volume):
"""Returns model update for migrated volume."""
return self.cli.update_migrated_volume(context, volume, new_volume)
+
+ def create_export_snapshot(self, context, snapshot, connector):
+ """Creates a snapshot mount point for snapshot."""
+ return self.cli.create_export_snapshot(context, snapshot, connector)
+
+ def remove_export_snapshot(self, context, snapshot):
+ """Removes snapshot mount point for snapshot."""
+ return self.cli.remove_export_snapshot(context, snapshot)
+
+ def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
+ """Allows connection to snapshot."""
+ return self.cli.initialize_connection_snapshot(snapshot,
+ connector,
+ **kwargs)
+
+ def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
+ """Disallows connection to snapshot."""
+ return self.cli.terminate_connection_snapshot(snapshot,
+ connector,
+ **kwargs)
+
+ def backup_use_temp_snapshot(self):
+ return True
Manage/unmanage volume revise
White list target ports support
Snap copy support
+ Support efficient non-disruptive backup
"""
def __init__(self, *args, **kwargs):
def update_migrated_volume(self, context, volume, new_volume):
"""Returns model update for migrated volume."""
return self.cli.update_migrated_volume(context, volume, new_volume)
+
+ def create_export_snapshot(self, context, snapshot, connector):
+ """Creates a snapshot mount point for snapshot."""
+ return self.cli.create_export_snapshot(context, snapshot, connector)
+
+ def remove_export_snapshot(self, context, snapshot):
+ """Removes snapshot mount point for snapshot."""
+ return self.cli.remove_export_snapshot(context, snapshot)
+
+ def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
+ """Allows connection to snapshot."""
+ return self.cli.initialize_connection_snapshot(snapshot,
+ connector,
+ **kwargs)
+
+ def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
+ """Disallows connection to snapshot."""
+ return self.cli.terminate_connection_snapshot(snapshot,
+ connector,
+ **kwargs)
+
+ def backup_use_temp_snapshot(self):
+ return True
return lun
def delete_lun(self, name):
-
+ """Deletes a LUN or mount point."""
command_delete_lun = ['lun', '-destroy',
'-name', name,
'-forceDetach',
tmp_snap_prefix = 'tmp-snap-'
snap_as_vol_prefix = 'snap-as-vol-'
tmp_cgsnap_prefix = 'tmp-cgsnapshot-'
+ tmp_smp_for_backup_prefix = 'tmp-smp-'
def __init__(self, prtcl, configuration=None):
self.protocol = prtcl
def _construct_tmp_snap_name(self, volume):
return self.tmp_snap_prefix + volume['id']
+ def _construct_tmp_smp_name(self, snapshot):
+ return self.tmp_smp_for_backup_prefix + snapshot.id
+
def create_volume(self, volume):
"""Creates a EMC volume."""
volume_size = volume['size']
provisioning, tiering, volume['consistencygroup_id'],
ignore_thresholds=self.ignore_pool_full_threshold,
poll=False)
- pl = self._build_provider_location_for_lun(data['lun_id'])
+ pl = self._build_provider_location(data['lun_id'])
volume_metadata['lun_type'] = 'lun'
model_update = {'provider_location': pl,
'metadata': volume_metadata}
raise exception.VolumeBackendAPIException(data=msg)
return
- def delete_volume(self, volume):
+ def delete_volume(self, volume, force_delete=False):
"""Deletes an EMC volume."""
try:
self._client.delete_lun(volume['name'])
except exception.EMCVnxCLICmdError as ex:
orig_out = "\n".join(ex.kwargs["out"])
- if (self.force_delete_lun_in_sg and
+ if ((force_delete or self.force_delete_lun_in_sg) and
VNXError.has_error(orig_out, VNXError.LUN_IN_SG)):
LOG.warning(_LW('LUN corresponding to %s is still '
'in some Storage Groups.'
self._client.delete_snapshot(
self._construct_snap_as_vol_name(volume))
- pl = self._build_provider_location_for_lun(src_id, 'lun')
+ pl = self._build_provider_location(src_id, 'lun')
volume_metadata = self._get_volume_metadata(volume)
volume_metadata['lun_type'] = 'lun'
model_update = {'provider_location': pl,
store=store_spec)
flow_engine.run()
new_lun_id = flow_engine.storage.fetch('new_lun_id')
- pl = self._build_provider_location_for_lun(new_lun_id, 'lun')
+ pl = self._build_provider_location(new_lun_id, 'lun')
volume_metadata['lun_type'] = 'lun'
else:
work_flow.add(CopySnapshotTask(),
store=store_spec)
flow_engine.run()
new_lun_id = flow_engine.storage.fetch('new_smp_id')
- pl = self._build_provider_location_for_lun(new_lun_id, 'smp')
+ pl = self._build_provider_location(new_lun_id, 'smp')
volume_metadata['lun_type'] = 'smp'
model_update = {'provider_location': pl,
'metadata': volume_metadata}
self._client.delete_cgsnapshot(snapshot['id'])
else:
self.delete_snapshot(snapshot)
- pl = self._build_provider_location_for_lun(new_lun_id, 'lun')
+ pl = self._build_provider_location(new_lun_id, 'lun')
volume_metadata['lun_type'] = 'lun'
else:
work_flow.add(CreateSnapshotTask(),
store=store_spec)
flow_engine.run()
new_lun_id = flow_engine.storage.fetch('new_smp_id')
- pl = self._build_provider_location_for_lun(new_lun_id, 'smp')
+ pl = self._build_provider_location(new_lun_id, 'smp')
volume_metadata['lun_type'] = 'smp'
model_update = {'provider_location': pl,
def dumps_provider_location(self, pl_dict):
return '|'.join([k + '^' + pl_dict[k] for k in pl_dict])
- def _build_provider_location_for_lun(self, lun_id, type='lun'):
+ def _build_provider_location(self, lun_id, type='lun'):
+ """Builds provider_location for volume or snapshot."""
pl_dict = {'system': self.get_array_serial(),
'type': type,
'id': six.text_type(lun_id),
return conn_info
return do_terminate_connection()
+ def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
+ """Initializes connection for mount point."""
+ smp_name = self._construct_tmp_smp_name(snapshot)
+ self._client.attach_mount_point(smp_name, snapshot.name)
+ volume = {'name': smp_name, 'id': snapshot.id}
+ return self.initialize_connection(volume, connector)
+
+ def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
+ """Disallows connection for mount point."""
+ smp_name = self._construct_tmp_smp_name(snapshot)
+ volume = {'name': smp_name}
+ conn_info = self.terminate_connection(volume, connector)
+ self._client.detach_mount_point(smp_name)
+ return conn_info
+
+ def create_export_snapshot(self, context, snapshot, connector):
+ """Creates mount point for a snapshot."""
+ smp_name = self._construct_tmp_smp_name(snapshot)
+ primary_lun_name = snapshot.volume_name
+ self._client.create_mount_point(primary_lun_name, smp_name)
+ return None
+
+ def remove_export_snapshot(self, context, snapshot):
+ """Removes mount point for a snapshot."""
+ smp_name = self._construct_tmp_smp_name(snapshot)
+ volume = {'name': smp_name, 'provider_location': None}
+ self.delete_volume(volume, True)
+
def manage_existing_get_size(self, volume, existing_ref):
"""Returns size of volume to be managed by manage_existing."""
if 'source-id' in existing_ref:
existing_ref=manage_existing_ref, reason=reason)
self._client.rename_lun(lun_id, volume['name'])
model_update = {'provider_location':
- self._build_provider_location_for_lun(lun_id)}
+ self._build_provider_location(lun_id)}
return model_update
def get_login_ports(self, connector, io_ports=None):
for i, update in enumerate(volume_model_updates):
new_lun_id = flow_engine.storage.fetch(lun_id_key_template % i)
update['provider_location'] = (
- self._build_provider_location_for_lun(new_lun_id))
+ self._build_provider_location(new_lun_id))
return None, volume_model_updates