From: keystone Date: Mon, 11 Nov 2013 17:21:30 +0000 (-0800) Subject: Add volume migration code to Nexenta iSCSI volume driver X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=a0cbbf8e1afe6e9d6bbe29f977b2726146615447;p=openstack-build%2Fcinder-build.git Add volume migration code to Nexenta iSCSI volume driver Utilize ZFS-specific optimizations for direct host-to-host volume migration DocImpact Implements: blueprint nexenta-iscsi-volume-migrate Change-Id: Ib9f2fa7e1ccb93dbdbc951ddc3a0eda55cb001d5 --- diff --git a/cinder/tests/test_nexenta.py b/cinder/tests/test_nexenta.py index 359eb7916..efb35336c 100644 --- a/cinder/tests/test_nexenta.py +++ b/cinder/tests/test_nexenta.py @@ -43,12 +43,14 @@ class TestNexentaISCSIDriver(test.TestCase): TEST_VOLUME_REF = { 'name': TEST_VOLUME_NAME, 'size': 1, - 'id': '1' + 'id': '1', + 'status': 'available' } TEST_VOLUME_REF2 = { 'name': TEST_VOLUME_NAME2, 'size': 1, - 'id': '2' + 'id': '2', + 'status': 'in-use' } TEST_SNAPSHOT_REF = { 'name': TEST_SNAPSHOT_NAME, @@ -72,6 +74,9 @@ class TestNexentaISCSIDriver(test.TestCase): self.configuration.nexenta_target_group_prefix = 'cinder/' self.configuration.nexenta_blocksize = '8K' self.configuration.nexenta_sparse = True + self.configuration.nexenta_rrmgr_compression = 1 + self.configuration.nexenta_rrmgr_tcp_buf_size = 1024 + self.configuration.nexenta_rrmgr_connections = 2 self.nms_mock = self.mox.CreateMockAnything() for mod in ['volume', 'zvol', 'iscsitarget', 'appliance', 'stmf', 'scsidisk', 'snapshot']: @@ -150,6 +155,43 @@ class TestNexentaISCSIDriver(test.TestCase): self.mox.ReplayAll() self.drv.create_cloned_volume(vol, src_vref) + def test_migrate_volume(self): + volume = self.TEST_VOLUME_REF + host = { + 'capabilities': { + 'vendor_name': 'Nexenta', + 'location_info': 'NexentaISCSIDriver:1.1.1.1:cinder', + 'free_capacity_gb': 1 + } + } + snapshot = { + 'volume_name': volume['name'], + 'name': 'cinder-migrate-snapshot-%s' % volume['id'], + } + self.nms_mock.appliance.ssh_list_bindings().AndReturn([]) + self.nms_mock.zvol.create_snapshot('cinder/%s' % volume['name'], + snapshot['name'], '') + + src = '%(volume)s/%(zvol)s@%(snapshot)s' % { + 'volume': 'cinder', + 'zvol': volume['name'], + 'snapshot': snapshot['name']} + dst = '1.1.1.1:cinder' + cmd = ' '.join(['rrmgr -s zfs -c 1 -q -e -w 1024 -n 2', src, dst]) + + self.nms_mock.appliance.execute(cmd) + + self.nms_mock.snapshot.destroy('cinder/%(volume)s@%(snapshot)s' % { + 'volume': volume['name'], + 'snapshot': snapshot['name']}, '') + volume_name = 'cinder/%s' % volume['name'] + self.nms_mock.zvol.get_child_props(volume_name, + 'origin').AndReturn(None) + self.nms_mock.zvol.destroy(volume_name, '') + + self.mox.ReplayAll() + self.drv.migrate_volume(None, volume, host) + def test_create_snapshot(self): self.nms_mock.zvol.create_snapshot('cinder/volume1', 'snapshot1', '') self.mox.ReplayAll() diff --git a/cinder/volume/drivers/nexenta/iscsi.py b/cinder/volume/drivers/nexenta/iscsi.py index 47eddddf3..a909e8a36 100644 --- a/cinder/volume/drivers/nexenta/iscsi.py +++ b/cinder/volume/drivers/nexenta/iscsi.py @@ -69,12 +69,17 @@ class NexentaISCSIDriver(driver.ISCSIDriver): # pylint: disable=R0921 options.NEXENTA_ISCSI_OPTIONS) self.configuration.append_config_values( options.NEXENTA_VOLUME_OPTIONS) + self.configuration.append_config_values( + options.NEXENTA_RRMGR_OPTIONS) self.nms_protocol = self.configuration.nexenta_rest_protocol self.nms_host = self.configuration.nexenta_host self.nms_port = self.configuration.nexenta_rest_port self.nms_user = self.configuration.nexenta_user self.nms_password = self.configuration.nexenta_password self.volume = self.configuration.nexenta_volume + self.rrmgr_compression = self.configuration.nexenta_rrmgr_compression + self.rrmgr_tcp_buf_size = self.configuration.nexenta_rrmgr_tcp_buf_size + self.rrmgr_connections = self.configuration.nexenta_rrmgr_connections @property def backend_name(self): @@ -127,6 +132,11 @@ class NexentaISCSIDriver(driver.ISCSIDriver): # pylint: disable=R0921 name = snapshot.split('@')[-1] return name.startswith('cinder-clone-snapshot-') + @staticmethod + def _get_migrate_snapshot_name(volume): + """Return name for snapshot that will be used to migrate the volume.""" + return 'cinder-migrate-snapshot-%(id)s' % volume + def create_volume(self, volume): """Create a zvol on appliance. @@ -205,6 +215,84 @@ class NexentaISCSIDriver(driver.ISCSIDriver): # pylint: disable=R0921 '%(volume_name)s@%(name)s'), snapshot) raise + def _get_zfs_send_recv_cmd(self, src, dst): + """Returns rrmgr command for source and destination.""" + return utils.get_rrmgr_cmd(src, dst, + compression=self.rrmgr_compression, + tcp_buf_size=self.rrmgr_tcp_buf_size, + connections=self.rrmgr_connections) + + def migrate_volume(self, ctxt, volume, host): + """Migrate if volume and host are managed by Nexenta appliance. + + :param ctxt: context + :param volume: a dictionary describing the volume to migrate + :param host: a dictionary describing the host to migrate to + """ + LOG.debug(_('Enter: migrate_volume: id=%(id)s, host=%(host)s') % + {'id': volume['id'], 'host': host}) + + false_ret = (False, None) + + if volume['status'] != 'available': + return false_ret + + if 'location_info' not in host['capabilities']: + return false_ret + + dst_parts = host['capabilities']['location_info'].split(':') + + if host['capabilities']['vendor_name'] != 'Nexenta' or \ + dst_parts[0] != self.__class__.__name__ or \ + host['capabilities']['free_capacity_gb'] < volume['size']: + return false_ret + + dst_host, dst_volume = dst_parts[1:] + + ssh_bound = False + ssh_bindings = self.nms.appliance.ssh_list_bindings() + for bind in ssh_bindings: + if bind.index(dst_host) != -1: + ssh_bound = True + break + if not(ssh_bound): + LOG.warning(_("Remote NexentaStor appliance at %s should be " + "SSH-bound."), dst_host) + + # Create temporary snapshot of volume on NexentaStor Appliance. + snapshot = {'volume_name': volume['name'], + 'name': self._get_migrate_snapshot_name(volume)} + self.create_snapshot(snapshot) + + src = '%(volume)s/%(zvol)s@%(snapshot)s' % { + 'volume': self.volume, + 'zvol': volume['name'], + 'snapshot': snapshot['name']} + dst = ':'.join([dst_host, dst_volume]) + + try: + self.nms.appliance.execute(self._get_zfs_send_recv_cmd(src, dst)) + except nexenta.NexentaException as exc: + LOG.warning(_("Cannot send source snapshot %(src)s to " + "destination %(dst)s. Reason: %(exc)s"), + {'src': src, 'dst': dst, 'exc': exc}) + return false_ret + finally: + try: + self.delete_snapshot(snapshot) + except nexenta.NexentaException as exc: + LOG.warning(_("Cannot delete temporary source snapshot " + "%(src)s on NexentaStor Appliance: %(exc)s"), + {'src': src, 'exc': exc}) + try: + self.delete_volume(volume) + except nexenta.NexentaException as exc: + LOG.warning(_("Cannot delete source volume %(volume)s on " + "NexentaStor Appliance: %(exc)s"), + {'volume': volume['name'], 'exc': exc}) + + return (True, None) + def create_snapshot(self, snapshot): """Create snapshot of existing zvol on appliance. diff --git a/cinder/volume/drivers/nexenta/options.py b/cinder/volume/drivers/nexenta/options.py index b71e3d2d5..f56340b16 100644 --- a/cinder/volume/drivers/nexenta/options.py +++ b/cinder/volume/drivers/nexenta/options.py @@ -103,3 +103,16 @@ NEXENTA_VOLUME_OPTIONS = [ default=False, help='flag to create sparse volumes'), ] + +NEXENTA_RRMGR_OPTIONS = [ + cfg.IntOpt('nexenta_rrmgr_compression', + default=0, + help=('Enable stream compression, level 1..9. 1 - gives best ' + 'speed; 9 - gives best compression.')), + cfg.IntOpt('nexenta_rrmgr_tcp_buf_size', + default=4096, + help='TCP Buffer size in KiloBytes.'), + cfg.IntOpt('nexenta_rrmgr_connections', + default=2, + help='Number of TCP connections.'), +] diff --git a/cinder/volume/drivers/nexenta/utils.py b/cinder/volume/drivers/nexenta/utils.py index 0bf75aa55..4fa9f22fb 100644 --- a/cinder/volume/drivers/nexenta/utils.py +++ b/cinder/volume/drivers/nexenta/utils.py @@ -20,7 +20,7 @@ .. automodule:: nexenta.utils .. moduleauthor:: Victor Rodionov -.. moduleauthor:: Mikhail Khodos +.. moduleauthor:: Mikhail Khodos """ import re @@ -63,6 +63,22 @@ def str2gib_size(s): return size_in_bytes / units.GiB +def get_rrmgr_cmd(src, dst, compression=None, tcp_buf_size=None, + connections=None): + """Returns rrmgr command for source and destination.""" + cmd = ['rrmgr', '-s', 'zfs'] + if compression: + cmd.extend(['-c', '%s' % str(compression)]) + cmd.append('-q') + cmd.append('-e') + if tcp_buf_size: + cmd.extend(['-w', str(tcp_buf_size)]) + if connections: + cmd.extend(['-n', str(connections)]) + cmd.extend([src, dst]) + return ' '.join(cmd) + + def parse_nms_url(url): """Parse NMS url into normalized parts like scheme, user, host and others. diff --git a/etc/cinder/cinder.conf.sample b/etc/cinder/cinder.conf.sample index 2b67d8456..078bed710 100644 --- a/etc/cinder/cinder.conf.sample +++ b/etc/cinder/cinder.conf.sample @@ -1377,6 +1377,16 @@ # value. (boolean value) #nexenta_nms_cache_volroot=true +# Enable stream compression, level 1..9. 1 - gives best speed; +# 9 - gives best compression. (integer value) +#nexenta_rrmgr_compression=0 + +# TCP Buffer size in KiloBytes. (integer value) +#nexenta_rrmgr_tcp_buf_size=4096 + +# Number of TCP connections. (integer value) +#nexenta_rrmgr_connections=2 + # block size for volumes (blank=default,8KB) (string value) #nexenta_blocksize=