21480249 create --source-volid should clone the volume w/o blocking the delete of source
authorSean Wilcox <sean.wilcox@oracle.com>
Tue, 20 Oct 2015 05:38:14 -0700
changeset 4983 db2589571faa
parent 4982 df1be607c345
child 4984 7145b15b7f0d
21480249 create --source-volid should clone the volume w/o blocking the delete of source
components/openstack/cinder/files/solaris/zfs.py
--- a/components/openstack/cinder/files/solaris/zfs.py	Tue Oct 20 05:38:12 2015 -0700
+++ b/components/openstack/cinder/files/solaris/zfs.py	Tue Oct 20 05:38:14 2015 -0700
@@ -27,6 +27,7 @@
 import time
 
 from oslo.config import cfg
+import paramiko
 
 from cinder import exception
 from cinder.i18n import _
@@ -110,20 +111,11 @@
                                     src_vref['size']))
             raise exception.VolumeBackendAPIException(data=exception_message)
 
-        src_volume_name = src_vref['name']
-        volume_name = volume['name']
-        tmp_snapshot = {'volume_name': src_volume_name,
-                        'name': 'tmp-snapshot-%s' % volume['id']}
-        self.create_snapshot(tmp_snapshot)
-
-        # Create a ZFS clone
-        zfs_snapshot = self._get_zfs_snap_name(tmp_snapshot)
-        zfs_volume = self._get_zfs_volume_name(volume['name'])
-        cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume]
-        self._execute(*cmd)
+        self._zfs_send_recv(src_vref,
+                            self._get_zfs_volume_name(volume['name']))
 
         LOG.debug(_("Created cloned volume '%s' from source volume '%s'")
-                  % (volume_name, src_volume_name))
+                  % (volume['name'], src_vref['name']))
 
     def delete_volume(self, volume):
         """Delete a volume.
@@ -248,6 +240,27 @@
         return "%s/%s" % (self.configuration.zfs_volume_base,
                           volume_name)
 
+    def _remote_piped_execute(self, cmd1, cmd2, ip, username, password):
+        """Piped execute on a remote host."""
+        LOG.debug(_("Piping cmd1='%s' into cmd='%s' on host '%s'") %
+                  (' '.join(cmd1), ' '.join(cmd2), ip))
+
+        client = paramiko.SSHClient()
+        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        client.connect(ip, username=username, password=password)
+
+        cmd = ' '.join(cmd1) + '|' + ' '.join(cmd2)
+        stdin, stdout, stderr = client.exec_command(cmd)
+        channel = stdout.channel
+        exit_status = channel.recv_exit_status()
+
+        if exit_status != 0:
+            LOG.error(_("_remote_piped_execute: failed to host '%s' with "
+                        "stdout '%s' and stderr '%s'")
+                      % (ip, stdout.read(), stderr.read()))
+            msg = (_("Remote piped execution failed to host '%s'.") % ip)
+            raise exception.VolumeBackendAPIException(data=msg)
+
     def _piped_execute(self, cmd1, cmd2):
         """Pipe output of cmd1 into cmd2."""
         LOG.debug(_("Piping cmd1='%s' into cmd2='%s'") %
@@ -275,7 +288,7 @@
                    (stdout, stderr))
             raise exception.VolumeBackendAPIException(data=msg)
 
-    def _zfs_send_recv(self, src, dst, remote=False):
+    def _zfs_send_recv(self, src, dst):
         """Replicate the ZFS dataset by calling zfs send/recv cmd"""
         src_snapshot = {'volume_name': src['name'],
                         'name': 'tmp-snapshot-%s' % src['id']}
@@ -290,7 +303,18 @@
 
         cmd1 = ['/usr/sbin/zfs', 'send', src_snapshot_name]
         cmd2 = ['/usr/sbin/zfs', 'receive', dst]
-        self._piped_execute(cmd1, cmd2)
+        # Due to pipe injection protection in the ssh utils method,
+        # cinder.utils.check_ssh_injection(), the piped commands must be passed
+        # through via paramiko.  These commands take no user defined input
+        # other than the names of the zfs datasets which are already protected
+        # against the special characters of concern.
+        if not self.run_local:
+            ip = self.configuration.san_ip
+            username = self.configuration.san_login
+            password = self.configuration.san_password
+            self._remote_piped_execute(cmd1, cmd2, ip, username, password)
+        else:
+            self._piped_execute(cmd1, cmd2)
 
         # Delete the temporary src snapshot and dst snapshot
         self.delete_snapshot(src_snapshot)
@@ -523,8 +547,15 @@
         """Export the volume."""
         # If the volume is already exported there is nothing to do, as we
         # simply export volumes and they are universally available.
-        if self._get_luid(volume):
-            return
+        luid = self._get_luid(volume)
+        if luid:
+            view_lun = self._get_view_and_lun(luid)
+            if view_lun['view'] is not None:
+                return
+            else:
+                msg = (_("Failed to create logical unit for volume '%s' due "
+                         "to an existing LU id but no view.") % volume['name'])
+                raise exception.VolumeBackendAPIException(data=msg)
 
         zvol = self._get_zvol_path(volume)
 
@@ -758,6 +789,18 @@
 
     def create_export(self, context, volume):
         """Export the volume."""
+        # If the volume is already exported there is nothing to do, as we
+        # simply export volumes and they are universally available.
+        luid = self._get_luid(volume)
+        if luid:
+            view_lun = self._get_view_and_lun(luid)
+            if view_lun['view'] is not None:
+                return
+            else:
+                msg = (_("Failed to create logical unit for volume '%s' due "
+                         "to an existing LU id but no view.") % volume['name'])
+                raise exception.VolumeBackendAPIException(data=msg)
+
         zvol = self._get_zvol_path(volume)
 
         # Create a Logical Unit (LU)