25 import socket |
25 import socket |
26 import subprocess |
26 import subprocess |
27 import time |
27 import time |
28 |
28 |
29 from oslo.config import cfg |
29 from oslo.config import cfg |
|
30 import paramiko |
30 |
31 |
31 from cinder import exception |
32 from cinder import exception |
32 from cinder.i18n import _ |
33 from cinder.i18n import _ |
33 from cinder.image import image_utils |
34 from cinder.image import image_utils |
34 from cinder.openstack.common import log as logging |
35 from cinder.openstack.common import log as logging |
111 "from that of the source volume, '%s'.") |
112 "from that of the source volume, '%s'.") |
112 % (volume['name'], volume['size'], |
113 % (volume['name'], volume['size'], |
113 src_vref['size'])) |
114 src_vref['size'])) |
114 raise exception.VolumeBackendAPIException(data=exception_message) |
115 raise exception.VolumeBackendAPIException(data=exception_message) |
115 |
116 |
116 src_volume_name = src_vref['name'] |
117 self._zfs_send_recv(src_vref, |
117 volume_name = volume['name'] |
118 self._get_zfs_volume_name(volume['name'])) |
118 tmp_snapshot = {'volume_name': src_volume_name, |
|
119 'name': 'tmp-snapshot-%s' % volume['id']} |
|
120 self.create_snapshot(tmp_snapshot) |
|
121 |
|
122 # Create a ZFS clone |
|
123 zfs_snapshot = self._get_zfs_snap_name(tmp_snapshot) |
|
124 zfs_volume = self._get_zfs_volume_name(volume['name']) |
|
125 cmd = ['/usr/sbin/zfs', 'clone', zfs_snapshot, zfs_volume] |
|
126 self._execute(*cmd) |
|
127 |
119 |
128 LOG.debug(_("Created cloned volume '%s' from source volume '%s'") |
120 LOG.debug(_("Created cloned volume '%s' from source volume '%s'") |
129 % (volume_name, src_volume_name)) |
121 % (volume['name'], src_vref['name'])) |
130 |
122 |
131 def delete_volume(self, volume): |
123 def delete_volume(self, volume): |
132 """Delete a volume. |
124 """Delete a volume. |
133 |
125 |
134 Firstly, the volume should be checked if it is a cloned one. If yes, |
126 Firstly, the volume should be checked if it is a cloned one. If yes, |
249 def _get_zfs_volume_name(self, volume_name): |
241 def _get_zfs_volume_name(self, volume_name): |
250 """Add the pool name to get the ZFS volume.""" |
242 """Add the pool name to get the ZFS volume.""" |
251 return "%s/%s" % (self.configuration.zfs_volume_base, |
243 return "%s/%s" % (self.configuration.zfs_volume_base, |
252 volume_name) |
244 volume_name) |
253 |
245 |
|
246 def _remote_piped_execute(self, cmd1, cmd2, ip, username, password): |
|
247 """Piped execute on a remote host.""" |
|
248 LOG.debug(_("Piping cmd1='%s' into cmd='%s' on host '%s'") % |
|
249 (' '.join(cmd1), ' '.join(cmd2), ip)) |
|
250 |
|
251 client = paramiko.SSHClient() |
|
252 client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
|
253 client.connect(ip, username=username, password=password) |
|
254 |
|
255 cmd = ' '.join(cmd1) + '|' + ' '.join(cmd2) |
|
256 stdin, stdout, stderr = client.exec_command(cmd) |
|
257 channel = stdout.channel |
|
258 exit_status = channel.recv_exit_status() |
|
259 |
|
260 if exit_status != 0: |
|
261 LOG.error(_("_remote_piped_execute: failed to host '%s' with " |
|
262 "stdout '%s' and stderr '%s'") |
|
263 % (ip, stdout.read(), stderr.read())) |
|
264 msg = (_("Remote piped execution failed to host '%s'.") % ip) |
|
265 raise exception.VolumeBackendAPIException(data=msg) |
|
266 |
254 def _piped_execute(self, cmd1, cmd2): |
267 def _piped_execute(self, cmd1, cmd2): |
255 """Pipe output of cmd1 into cmd2.""" |
268 """Pipe output of cmd1 into cmd2.""" |
256 LOG.debug(_("Piping cmd1='%s' into cmd2='%s'") % |
269 LOG.debug(_("Piping cmd1='%s' into cmd2='%s'") % |
257 (' '.join(cmd1), ' '.join(cmd2))) |
270 (' '.join(cmd1), ' '.join(cmd2))) |
258 |
271 |
276 if p2.returncode: |
289 if p2.returncode: |
277 msg = (_("_piped_execute failed with the info '%s' and '%s'.") % |
290 msg = (_("_piped_execute failed with the info '%s' and '%s'.") % |
278 (stdout, stderr)) |
291 (stdout, stderr)) |
279 raise exception.VolumeBackendAPIException(data=msg) |
292 raise exception.VolumeBackendAPIException(data=msg) |
280 |
293 |
281 def _zfs_send_recv(self, src, dst, remote=False): |
294 def _zfs_send_recv(self, src, dst): |
282 """Replicate the ZFS dataset by calling zfs send/recv cmd""" |
295 """Replicate the ZFS dataset by calling zfs send/recv cmd""" |
283 src_snapshot = {'volume_name': src['name'], |
296 src_snapshot = {'volume_name': src['name'], |
284 'name': 'tmp-snapshot-%s' % src['id']} |
297 'name': 'tmp-snapshot-%s' % src['id']} |
285 src_snapshot_name = self._get_zfs_snap_name(src_snapshot) |
298 src_snapshot_name = self._get_zfs_snap_name(src_snapshot) |
286 prop_type = self._get_zfs_property('type', src_snapshot_name) |
299 prop_type = self._get_zfs_property('type', src_snapshot_name) |
291 self.create_snapshot(src_snapshot) |
304 self.create_snapshot(src_snapshot) |
292 src_snapshot_name = self._get_zfs_snap_name(src_snapshot) |
305 src_snapshot_name = self._get_zfs_snap_name(src_snapshot) |
293 |
306 |
294 cmd1 = ['/usr/sbin/zfs', 'send', src_snapshot_name] |
307 cmd1 = ['/usr/sbin/zfs', 'send', src_snapshot_name] |
295 cmd2 = ['/usr/sbin/zfs', 'receive', dst] |
308 cmd2 = ['/usr/sbin/zfs', 'receive', dst] |
296 self._piped_execute(cmd1, cmd2) |
309 # Due to pipe injection protection in the ssh utils method, |
|
310 # cinder.utils.check_ssh_injection(), the piped commands must be passed |
|
311 # through via paramiko. These commands take no user defined input |
|
312 # other than the names of the zfs datasets which are already protected |
|
313 # against the special characters of concern. |
|
314 if not self.run_local: |
|
315 ip = self.configuration.san_ip |
|
316 username = self.configuration.san_login |
|
317 password = self.configuration.san_password |
|
318 self._remote_piped_execute(cmd1, cmd2, ip, username, password) |
|
319 else: |
|
320 self._piped_execute(cmd1, cmd2) |
297 |
321 |
298 # Delete the temporary src snapshot and dst snapshot |
322 # Delete the temporary src snapshot and dst snapshot |
299 self.delete_snapshot(src_snapshot) |
323 self.delete_snapshot(src_snapshot) |
300 dst_snapshot_name = "%[email protected]%s" % (dst, src['id']) |
324 dst_snapshot_name = "%[email protected]%s" % (dst, src['id']) |
301 cmd = ['/usr/sbin/zfs', 'destroy', dst_snapshot_name] |
325 cmd = ['/usr/sbin/zfs', 'destroy', dst_snapshot_name] |
551 |
575 |
552 def create_export(self, context, volume): |
576 def create_export(self, context, volume): |
553 """Export the volume.""" |
577 """Export the volume.""" |
554 # If the volume is already exported there is nothing to do, as we |
578 # If the volume is already exported there is nothing to do, as we |
555 # simply export volumes and they are universally available. |
579 # simply export volumes and they are universally available. |
556 if self._get_luid(volume): |
580 luid = self._get_luid(volume) |
557 return |
581 if luid: |
|
582 view_lun = self._get_view_and_lun(luid) |
|
583 if view_lun['view'] is not None: |
|
584 return |
|
585 else: |
|
586 msg = (_("Failed to create logical unit for volume '%s' due " |
|
587 "to an existing LU id but no view.") % volume['name']) |
|
588 raise exception.VolumeBackendAPIException(data=msg) |
558 |
589 |
559 zvol = self._get_zvol_path(volume) |
590 zvol = self._get_zvol_path(volume) |
560 |
591 |
561 # Create a Logical Unit (LU) |
592 # Create a Logical Unit (LU) |
562 self._stmf_execute('/usr/sbin/stmfadm', 'create-lu', zvol) |
593 self._stmf_execute('/usr/sbin/stmfadm', 'create-lu', zvol) |
772 LOG.debug(_("The target '%s' is not in any target group.") % target) |
803 LOG.debug(_("The target '%s' is not in any target group.") % target) |
773 return False |
804 return False |
774 |
805 |
775 def create_export(self, context, volume): |
806 def create_export(self, context, volume): |
776 """Export the volume.""" |
807 """Export the volume.""" |
|
808 # If the volume is already exported there is nothing to do, as we |
|
809 # simply export volumes and they are universally available. |
|
810 luid = self._get_luid(volume) |
|
811 if luid: |
|
812 view_lun = self._get_view_and_lun(luid) |
|
813 if view_lun['view'] is not None: |
|
814 return |
|
815 else: |
|
816 msg = (_("Failed to create logical unit for volume '%s' due " |
|
817 "to an existing LU id but no view.") % volume['name']) |
|
818 raise exception.VolumeBackendAPIException(data=msg) |
|
819 |
777 zvol = self._get_zvol_path(volume) |
820 zvol = self._get_zvol_path(volume) |
778 |
821 |
779 # Create a Logical Unit (LU) |
822 # Create a Logical Unit (LU) |
780 self._stmf_execute('/usr/sbin/stmfadm', 'create-lu', zvol) |
823 self._stmf_execute('/usr/sbin/stmfadm', 'create-lu', zvol) |
781 luid = self._get_luid(volume) |
824 luid = self._get_luid(volume) |