diff -r b6b088be89d5 -r a515b642a711 components/openstack/cinder/files/solaris/zfs.py --- a/components/openstack/cinder/files/solaris/zfs.py Tue Feb 09 16:26:27 2016 -0800 +++ b/components/openstack/cinder/files/solaris/zfs.py Tue Feb 09 16:26:27 2016 -0800 @@ -27,6 +27,7 @@ import time from oslo.config import cfg +import paramiko from cinder import exception from cinder.i18n import _ @@ -113,20 +114,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. @@ -251,6 +243,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'") % @@ -278,7 +291,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']} @@ -293,7 +306,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) @@ -553,8 +577,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) @@ -774,6 +805,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)