20 """ |
20 """ |
21 |
21 |
22 import abc |
22 import abc |
23 import fcntl |
23 import fcntl |
24 import os |
24 import os |
25 import socket |
|
26 import subprocess |
25 import subprocess |
27 import time |
26 import time |
28 |
27 |
29 from oslo.config import cfg |
28 from oslo_concurrency import processutils |
|
29 from oslo_config import cfg |
|
30 from oslo_log import log as logging |
30 import paramiko |
31 import paramiko |
31 |
32 |
32 from cinder import exception |
33 from cinder import exception |
33 from cinder.i18n import _ |
34 from cinder.i18n import _, _LE, _LI |
34 from cinder.image import image_utils |
35 from cinder.image import image_utils |
35 from cinder.openstack.common import log as logging |
|
36 from cinder.openstack.common import processutils |
|
37 from cinder.volume import driver |
36 from cinder.volume import driver |
38 from cinder.volume.drivers.san.san import SanDriver |
37 from cinder.volume.drivers.san.san import SanDriver |
|
38 |
|
39 from eventlet.green import socket |
|
40 from eventlet.green.OpenSSL import SSL |
|
41 |
|
42 import rad.client as radc |
|
43 import rad.connect as radcon |
|
44 import rad.bindings.com.oracle.solaris.rad.zfsmgr_1 as zfsmgr |
|
45 import rad.auth as rada |
39 |
46 |
40 from solaris_install.target.size import Size |
47 from solaris_install.target.size import Size |
41 |
48 |
42 FLAGS = cfg.CONF |
49 FLAGS = cfg.CONF |
43 LOG = logging.getLogger(__name__) |
50 LOG = logging.getLogger(__name__) |
51 help='iSCSI target group name.'), ] |
58 help='iSCSI target group name.'), ] |
52 |
59 |
53 FLAGS.register_opts(solaris_zfs_opts) |
60 FLAGS.register_opts(solaris_zfs_opts) |
54 |
61 |
55 |
62 |
|
63 def connect_tls(host, port=12302, locale=None, ca_certs=None): |
|
64 """Connect to a RAD instance over TLS. |
|
65 |
|
66 Arguments: |
|
67 host string, target host |
|
68 port int, target port (RAD_PORT_TLS = 12302) |
|
69 locale string, locale |
|
70 ca_certs string, path to file containing CA certificates |
|
71 |
|
72 Returns: |
|
73 RadConnection: a connection to RAD |
|
74 """ |
|
75 # We don't want SSL 2.0, SSL 3.0 nor TLS 1.0 in RAD |
|
76 context = SSL.Context(SSL.SSLv23_METHOD) |
|
77 context.set_options(SSL.OP_NO_SSLv2) |
|
78 context.set_options(SSL.OP_NO_SSLv3) |
|
79 context.set_options(SSL.OP_NO_TLSv1) |
|
80 |
|
81 if ca_certs is not None: |
|
82 context.set_verify(SSL.VERIFY_PEER, _tls_verify_cb) |
|
83 context.load_verify_locations(ca_certs) |
|
84 |
|
85 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
86 sock = SSL.Connection(context, sock) |
|
87 sock.connect((host, port)) |
|
88 sock.do_handshake() |
|
89 |
|
90 return radcon.RadConnection(sock, locale=locale) |
|
91 |
|
92 |
56 class ZFSVolumeDriver(SanDriver): |
93 class ZFSVolumeDriver(SanDriver): |
57 """Local ZFS volume operations.""" |
94 """OpenStack Cinder ZFS volume driver for generic ZFS volumes. |
|
95 |
|
96 Version history: |
|
97 1.0.0 - Initial driver with basic functionalities in Havana |
|
98 1.1.0 - Support SAN for the remote storage nodes access in Juno |
|
99 1.1.1 - Add support for the volume backup |
|
100 1.1.2 - Add support for the volume migration |
|
101 1.2.0 - Add support for the volume management in Kilo |
|
102 1.2.1 - Enable the connect_tls by importing eventlet.green.socket |
|
103 1.2.2 - Introduce the ZFS RAD for volume migration enhancement |
|
104 1.2.3 - Replace volume-specific targets with one shared target in |
|
105 the ZFSISCSIDriver |
|
106 |
|
107 """ |
|
108 |
|
109 version = "1.2.3" |
58 protocol = 'local' |
110 protocol = 'local' |
59 |
111 |
60 def __init__(self, *args, **kwargs): |
112 def __init__(self, *args, **kwargs): |
61 super(ZFSVolumeDriver, self).__init__(execute=self.solaris_execute, |
113 super(ZFSVolumeDriver, self).__init__(execute=self.solaris_execute, |
62 *args, **kwargs) |
114 *args, **kwargs) |
200 def attach_volume(self, context, volume, instance_uuid, host_name, |
252 def attach_volume(self, context, volume, instance_uuid, host_name, |
201 mountpoint): |
253 mountpoint): |
202 """Callback for volume attached to instance or host.""" |
254 """Callback for volume attached to instance or host.""" |
203 pass |
255 pass |
204 |
256 |
205 def detach_volume(self, context, volume): |
257 def detach_volume(self, context, volume, attachment): |
206 """ Callback for volume detached.""" |
258 """ Callback for volume detached.""" |
207 pass |
259 pass |
208 |
260 |
209 def get_volume_stats(self, refresh=False): |
261 def get_volume_stats(self, refresh=False): |
210 """Get volume status.""" |
262 """Get volume status.""" |
211 |
263 |
212 if refresh: |
264 if refresh: |
213 self._update_volume_stats() |
265 self._update_volume_stats() |
214 |
266 |
215 return self._stats |
267 return self._stats |
216 |
|
217 def copy_image_to_volume(self, context, volume, image_service, image_id): |
|
218 """Fetch the image from image_service and write it to the volume.""" |
|
219 raise NotImplementedError() |
|
220 |
|
221 def copy_volume_to_image(self, context, volume, image_service, image_meta): |
|
222 """Copy the volume to the specified image.""" |
|
223 raise NotImplementedError() |
|
224 |
268 |
225 def _get_zfs_property(self, prop, dataset): |
269 def _get_zfs_property(self, prop, dataset): |
226 """Get the value of property for the dataset.""" |
270 """Get the value of property for the dataset.""" |
227 try: |
271 try: |
228 (out, _err) = self._execute('/usr/sbin/zfs', 'get', '-H', '-o', |
272 (out, _err) = self._execute('/usr/sbin/zfs', 'get', '-H', '-o', |
229 'value', prop, dataset) |
273 'value', prop, dataset) |
230 return out.rstrip() |
274 return out.rstrip() |
231 except processutils.ProcessExecutionError: |
275 except processutils.ProcessExecutionError: |
232 LOG.info(_("Failed to get the property '%s' of the dataset '%s'") % |
276 LOG.info(_LI("Failed to get the property '%s' of the dataset '%s'") |
233 (prop, dataset)) |
277 % (prop, dataset)) |
234 return None |
278 return None |
235 |
279 |
236 def _get_zfs_snap_name(self, snapshot): |
280 def _get_zfs_snap_name(self, snapshot): |
237 """Get the snapshot path.""" |
281 """Get the snapshot path.""" |
238 return "%s/%s@%s" % (self.configuration.zfs_volume_base, |
282 return "%s/%s@%s" % (self.configuration.zfs_volume_base, |
306 |
350 |
307 cmd1 = ['/usr/sbin/zfs', 'send', src_snapshot_name] |
351 cmd1 = ['/usr/sbin/zfs', 'send', src_snapshot_name] |
308 cmd2 = ['/usr/sbin/zfs', 'receive', dst] |
352 cmd2 = ['/usr/sbin/zfs', 'receive', dst] |
309 # Due to pipe injection protection in the ssh utils method, |
353 # Due to pipe injection protection in the ssh utils method, |
310 # cinder.utils.check_ssh_injection(), the piped commands must be passed |
354 # cinder.utils.check_ssh_injection(), the piped commands must be passed |
311 # through via paramiko. These commands take no user defined input |
355 # through via paramiko. These commands take no user defined input |
312 # other than the names of the zfs datasets which are already protected |
356 # other than the names of the zfs datasets which are already protected |
313 # against the special characters of concern. |
357 # against the special characters of concern. |
314 if not self.run_local: |
358 if not self.run_local: |
315 ip = self.configuration.san_ip |
359 ip = self.configuration.san_ip |
316 username = self.configuration.san_login |
360 username = self.configuration.san_login |
323 self.delete_snapshot(src_snapshot) |
367 self.delete_snapshot(src_snapshot) |
324 dst_snapshot_name = "%s@tmp-snapshot-%s" % (dst, src['id']) |
368 dst_snapshot_name = "%s@tmp-snapshot-%s" % (dst, src['id']) |
325 cmd = ['/usr/sbin/zfs', 'destroy', dst_snapshot_name] |
369 cmd = ['/usr/sbin/zfs', 'destroy', dst_snapshot_name] |
326 self._execute(*cmd) |
370 self._execute(*cmd) |
327 |
371 |
|
372 def _get_rc_connect(self, san_info=None): |
|
373 """Connect the RAD server.""" |
|
374 if san_info is not None: |
|
375 san_ip = san_info.split(';')[0] |
|
376 san_login = san_info.split(';')[1] |
|
377 san_password = san_info.split(';')[2] |
|
378 else: |
|
379 san_ip = self.configuration.san_ip |
|
380 san_login = self.configuration.san_login |
|
381 san_password = self.configuration.san_password |
|
382 |
|
383 rc = connect_tls(san_ip) |
|
384 auth = rada.RadAuth(rc) |
|
385 auth.pam_login(san_login, san_password) |
|
386 |
|
387 return rc |
|
388 |
|
389 def _rad_zfs_send_recv(self, src, dst, dst_san_info=None): |
|
390 """Replicate the ZFS dataset stream.""" |
|
391 src_snapshot = {'volume_name': src['name'], |
|
392 'name': 'tmp-send-snapshot-%s' % src['id']} |
|
393 src_snapshot_name = self._get_zfs_snap_name(src_snapshot) |
|
394 prop_type = self._get_zfs_property('type', src_snapshot_name) |
|
395 # Delete the temporary snapshot if it already exists |
|
396 if prop_type == 'snapshot': |
|
397 self.delete_snapshot(src_snapshot) |
|
398 # Create the temporary snapshot of src volume |
|
399 self.create_snapshot(src_snapshot) |
|
400 |
|
401 src_rc = self._get_rc_connect() |
|
402 dst_rc = self._get_rc_connect(dst_san_info) |
|
403 |
|
404 src_pat = self._get_zfs_volume_name(src['name']) |
|
405 src_vol_obj = src_rc.get_object(zfsmgr.ZfsDataset(), |
|
406 radc.ADRGlobPattern({"name": src_pat})) |
|
407 dst_pat = dst.rsplit('/', 1)[0] |
|
408 dst_vol_obj = dst_rc.get_object(zfsmgr.ZfsDataset(), |
|
409 radc.ADRGlobPattern({"name": dst_pat})) |
|
410 |
|
411 send_sock_info = src_vol_obj.get_send_socket( |
|
412 name=src_snapshot_name, socket_type=zfsmgr.SocketType.AF_INET) |
|
413 send_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
414 send_sock.connect((self.hostname, int(send_sock_info.socket))) |
|
415 |
|
416 dst_san_ip = dst_san_info.split(';')[0] |
|
417 remote_host, alias, addresslist = socket.gethostbyaddr(dst_san_ip) |
|
418 |
|
419 recv_sock_info = dst_vol_obj.get_receive_socket( |
|
420 name=dst, socket_type=zfsmgr.SocketType.AF_INET, |
|
421 name_options=zfsmgr.ZfsRecvNameOptions.use_provided_name) |
|
422 recv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
423 recv_sock.connect((remote_host, int(recv_sock_info.socket))) |
|
424 |
|
425 # Set 4mb buffer size |
|
426 buf_size = 4194304 |
|
427 while True: |
|
428 # Read the data from the send stream |
|
429 buf = send_sock.recv(buf_size) |
|
430 if not buf: |
|
431 break |
|
432 # Write the data to the receive steam |
|
433 recv_sock.send(buf) |
|
434 |
|
435 recv_sock.close() |
|
436 send_sock.close() |
|
437 time.sleep(1) |
|
438 |
|
439 # Delete the temporary dst snapshot |
|
440 pat = radc.ADRGlobPattern({"name": dst}) |
|
441 dst_zvol_obj = dst_rc.get_object(zfsmgr.ZfsDataset(), pat) |
|
442 snapshot_list = dst_zvol_obj.get_snapshots() |
|
443 for snap in snapshot_list: |
|
444 if 'tmp-send-snapshot'in snap: |
|
445 dst_zvol_obj.destroy_snapshot(snap) |
|
446 break |
|
447 |
|
448 # Delete the temporary src snapshot |
|
449 self.delete_snapshot(src_snapshot) |
|
450 LOG.debug(("Transfered src stream'%s' to dst'%s' on the host'%s'") % |
|
451 (src_snapshot_name, dst, self.hostname)) |
|
452 |
|
453 src_rc.close() |
|
454 dst_rc.close() |
|
455 |
328 def _get_zvol_path(self, volume): |
456 def _get_zvol_path(self, volume): |
329 """Get the ZFS volume path.""" |
457 """Get the ZFS volume path.""" |
330 return "/dev/zvol/rdsk/%s" % self._get_zfs_volume_name(volume['name']) |
458 return "/dev/zvol/rdsk/%s" % self._get_zfs_volume_name(volume['name']) |
331 |
459 |
332 def _update_volume_stats(self): |
460 def _update_volume_stats(self): |
335 LOG.debug(_("Updating volume status")) |
463 LOG.debug(_("Updating volume status")) |
336 stats = {} |
464 stats = {} |
337 backend_name = self.configuration.safe_get('volume_backend_name') |
465 backend_name = self.configuration.safe_get('volume_backend_name') |
338 stats["volume_backend_name"] = backend_name or self.__class__.__name__ |
466 stats["volume_backend_name"] = backend_name or self.__class__.__name__ |
339 stats["storage_protocol"] = self.protocol |
467 stats["storage_protocol"] = self.protocol |
340 stats["driver_version"] = '1.0' |
468 stats["driver_version"] = self.version |
341 stats["vendor_name"] = 'Oracle' |
469 stats["vendor_name"] = 'Oracle' |
342 stats['QoS_support'] = False |
470 stats['QoS_support'] = False |
343 |
471 |
344 dataset = self.configuration.zfs_volume_base |
472 dataset = self.configuration.zfs_volume_base |
345 used_size = self._get_zfs_property('used', dataset) |
473 used_size = self._get_zfs_property('used', dataset) |
346 avail_size = self._get_zfs_property('avail', dataset) |
474 avail_size = self._get_zfs_property('avail', dataset) |
347 stats['total_capacity_gb'] = \ |
475 stats['total_capacity_gb'] = \ |
348 (Size(used_size) + Size(avail_size)).get(Size.gb_units) |
476 (Size(used_size) + Size(avail_size)).get(Size.gb_units) |
349 stats['free_capacity_gb'] = Size(avail_size).get(Size.gb_units) |
477 stats['free_capacity_gb'] = Size(avail_size).get(Size.gb_units) |
350 stats['reserved_percentage'] = self.configuration.reserved_percentage |
478 stats['reserved_percentage'] = self.configuration.reserved_percentage |
|
479 |
351 stats['location_info'] =\ |
480 stats['location_info'] =\ |
352 ('ZFSVolumeDriver:%(hostname)s:%(zfs_volume_base)s' % |
481 ('ZFSVolumeDriver:%(hostname)s:%(zfs_volume_base)s:local' % |
353 {'hostname': self.hostname, |
482 {'hostname': self.hostname, |
354 'zfs_volume_base': self.configuration.zfs_volume_base}) |
483 'zfs_volume_base': self.configuration.zfs_volume_base}) |
355 |
484 |
356 self._stats = stats |
485 self._stats = stats |
357 |
486 |
371 cmd = ['/usr/sbin/zfs', 'rename', src, dst] |
500 cmd = ['/usr/sbin/zfs', 'rename', src, dst] |
372 self._execute(*cmd) |
501 self._execute(*cmd) |
373 |
502 |
374 LOG.debug(_("Rename the volume '%s' to '%s'") % (src, dst)) |
503 LOG.debug(_("Rename the volume '%s' to '%s'") % (src, dst)) |
375 |
504 |
|
505 def _get_existing_volume_ref_name(self, existing_ref): |
|
506 """Returns the volume name of an existing reference. |
|
507 And Check if an existing volume reference has a source-name |
|
508 """ |
|
509 if 'source-name' in existing_ref: |
|
510 vol_name = existing_ref['source-name'] |
|
511 return vol_name |
|
512 else: |
|
513 reason = _("Reference must contain source-name.") |
|
514 raise exception.ManageExistingInvalidReference( |
|
515 existing_ref=existing_ref, |
|
516 reason=reason) |
|
517 |
|
518 def manage_existing_get_size(self, volume, existing_ref): |
|
519 """Return size of volume to be managed by manage_existing. |
|
520 existing_ref is a dictionary of the form: |
|
521 {'source-name': <name of the volume>} |
|
522 """ |
|
523 target_vol_name = self._get_existing_volume_ref_name(existing_ref) |
|
524 volsize = self._get_zfs_property('volsize', target_vol_name) |
|
525 |
|
526 return Size(volsize).get(Size.gb_units) |
|
527 |
|
528 def manage_existing(self, volume, existing_ref): |
|
529 """Brings an existing zfs volume object under Cinder management. |
|
530 |
|
531 :param volume: Cinder volume to manage |
|
532 :param existing_ref: Driver-specific information used to identify a |
|
533 volume |
|
534 """ |
|
535 # Check the existence of the ZFS volume |
|
536 target_vol_name = self._get_existing_volume_ref_name(existing_ref) |
|
537 prop_type = self._get_zfs_property('type', target_vol_name) |
|
538 if prop_type != 'volume': |
|
539 msg = (_("Failed to identify the volume '%s'.") |
|
540 % target_vol_name) |
|
541 raise exception.InvalidInput(reason=msg) |
|
542 |
|
543 if volume['name']: |
|
544 volume_name = volume['name'] |
|
545 else: |
|
546 volume_name = 'new_zvol' |
|
547 |
|
548 # rename the volume |
|
549 dst_volume = "%s/%s" % (self.configuration.zfs_volume_base, |
|
550 volume_name) |
|
551 self.rename_volume(target_vol_name, dst_volume) |
|
552 |
|
553 def unmanage(self, volume): |
|
554 """Removes the specified volume from Cinder management.""" |
|
555 # Rename the volume's name to cinder-unm-* format. |
|
556 volume_name = self._get_zfs_volume_name(volume['name']) |
|
557 tmp_volume_name = "cinder-unm-%s" % volume['name'] |
|
558 new_volume_name = "%s/%s" % (self.configuration.zfs_volume_base, |
|
559 tmp_volume_name) |
|
560 self.rename_volume(volume_name, new_volume_name) |
|
561 |
376 def migrate_volume(self, context, volume, host): |
562 def migrate_volume(self, context, volume, host): |
377 """Migrate the volume among different backends on the same server. |
563 """Migrate the volume from one backend to another one. |
378 |
564 The backends should be in the same volume type. |
379 The volume migration can only run locally by calling zfs send/recv |
565 |
380 cmds and the specified host needs to be on the same server with the |
|
381 host. But, one exception is when the src and dst volume are located |
|
382 under the same zpool locally or remotely, the migration will be done |
|
383 by just renaming the volume. |
|
384 :param context: context |
566 :param context: context |
385 :param volume: a dictionary describing the volume to migrate |
567 :param volume: a dictionary describing the volume to migrate |
386 :param host: a dictionary describing the host to migrate to |
568 :param host: a dictionary describing the host to migrate to |
387 """ |
569 """ |
388 false_ret = (False, None) |
570 false_ret = (False, None) |
389 if volume['status'] != 'available': |
571 if volume['status'] != 'available': |
390 LOG.debug(_("Status of volume '%s' is '%s', not 'available'.") % |
572 LOG.debug(_("Status of volume '%s' is '%s', not 'available'.") % |
391 (volume['name'], volume['status'])) |
573 (volume['name'], volume['status'])) |
392 return false_ret |
574 return false_ret |
393 |
575 |
394 if 'capabilities' not in host or \ |
576 if 'capabilities' not in host: |
395 'location_info' not in host['capabilities']: |
577 LOG.debug(("No 'capabilities' is reported in the host'%s'") % |
396 LOG.debug(_("No location_info or capabilities are in host info")) |
578 host['host']) |
397 return false_ret |
579 return false_ret |
398 |
580 |
|
581 if 'location_info' not in host['capabilities']: |
|
582 LOG.debug(("No 'location_info' is reported in the host'%s'") % |
|
583 host['host']) |
|
584 return false_ret |
|
585 |
399 info = host['capabilities']['location_info'] |
586 info = host['capabilities']['location_info'] |
400 if (self.hostname != info.split(':')[1]): |
587 dst_volume = "%s/%s" % (info.split(':')[2], volume['name']) |
401 LOG.debug(_("Migration between two different servers '%s' and " |
|
402 "'%s' is not supported yet.") % |
|
403 (self.hostname, info.split(':')[1])) |
|
404 return false_ret |
|
405 |
|
406 dst_volume = "%s/%s" % (info.split(':')[-1], volume['name']) |
|
407 src_volume = self._get_zfs_volume_name(volume['name']) |
588 src_volume = self._get_zfs_volume_name(volume['name']) |
|
589 |
408 # check if the src and dst volume are under the same zpool |
590 # check if the src and dst volume are under the same zpool |
409 if (src_volume.split('/')[0] == dst_volume.split('/')[0]): |
591 dst_san_info = info.split(':')[3] |
410 self.rename_volume(src_volume, dst_volume) |
592 if dst_san_info == 'local': |
411 else: |
|
412 self._zfs_send_recv(volume, dst_volume) |
593 self._zfs_send_recv(volume, dst_volume) |
413 # delete the source volume |
594 else: |
414 self.delete_volume(volume) |
595 self._rad_zfs_send_recv(volume, dst_volume, dst_san_info) |
|
596 # delete the source volume |
|
597 self.delete_volume(volume) |
415 |
598 |
416 provider_location = {} |
599 provider_location = {} |
417 return (True, provider_location) |
600 return (True, provider_location) |
418 |
601 |
419 |
602 |
542 """ZFS volume operations in iSCSI mode.""" |
725 """ZFS volume operations in iSCSI mode.""" |
543 protocol = 'iSCSI' |
726 protocol = 'iSCSI' |
544 |
727 |
545 def __init__(self, *args, **kwargs): |
728 def __init__(self, *args, **kwargs): |
546 super(ZFSISCSIDriver, self).__init__(*args, **kwargs) |
729 super(ZFSISCSIDriver, self).__init__(*args, **kwargs) |
|
730 if not self.configuration.san_is_local: |
|
731 self.hostname, alias, addresslist = \ |
|
732 socket.gethostbyaddr(self.configuration.san_ip) |
|
733 |
|
734 def get_volume_stats(self, refresh=False): |
|
735 """Get volume status.""" |
|
736 status = super(ZFSISCSIDriver, self).get_volume_stats(refresh) |
|
737 status["storage_protocol"] = self.protocol |
|
738 backend_name = self.configuration.safe_get('volume_backend_name') |
|
739 status["volume_backend_name"] = backend_name or self.__class__.__name__ |
|
740 |
|
741 if not self.configuration.san_is_local: |
|
742 san_info = "%s;%s;%s" % (self.configuration.san_ip, |
|
743 self.configuration.san_login, |
|
744 self.configuration.san_password) |
|
745 status['location_info'] = \ |
|
746 ('ZFSISCSIDriver:%(hostname)s:%(zfs_volume_base)s:' |
|
747 '%(san_info)s' % |
|
748 {'hostname': self.hostname, |
|
749 'zfs_volume_base': self.configuration.zfs_volume_base, |
|
750 'san_info': san_info}) |
|
751 |
|
752 return status |
547 |
753 |
548 def do_setup(self, context): |
754 def do_setup(self, context): |
549 """Setup the target and target group.""" |
755 """Setup the target and target group.""" |
550 target_group = self.configuration.zfs_target_group |
756 target_group = self.configuration.zfs_target_group |
551 target_name = '%s%s-%s-target' % \ |
757 target_name = '%s%s-%s-target' % \ |
733 """ZFS volume operations in FC mode.""" |
939 """ZFS volume operations in FC mode.""" |
734 protocol = 'FC' |
940 protocol = 'FC' |
735 |
941 |
736 def __init__(self, *args, **kwargs): |
942 def __init__(self, *args, **kwargs): |
737 super(ZFSFCDriver, self).__init__(*args, **kwargs) |
943 super(ZFSFCDriver, self).__init__(*args, **kwargs) |
|
944 if not self.configuration.san_is_local: |
|
945 self.hostname, alias, addresslist = \ |
|
946 socket.gethostbyaddr(self.configuration.san_ip) |
|
947 |
|
948 def get_volume_stats(self, refresh=False): |
|
949 """Get volume status.""" |
|
950 status = super(ZFSFCDriver, self).get_volume_stats(refresh) |
|
951 status["storage_protocol"] = self.protocol |
|
952 backend_name = self.configuration.safe_get('volume_backend_name') |
|
953 status["volume_backend_name"] = backend_name or self.__class__.__name__ |
|
954 |
|
955 if not self.configuration.san_is_local: |
|
956 san_info = "%s;%s;%s" % (self.configuration.san_ip, |
|
957 self.configuration.san_login, |
|
958 self.configuration.san_password) |
|
959 status['location_info'] = \ |
|
960 ('ZFSFCDriver:%(hostname)s:%(zfs_volume_base)s:' |
|
961 '%(san_info)s' % |
|
962 {'hostname': self.hostname, |
|
963 'zfs_volume_base': self.configuration.zfs_volume_base, |
|
964 'san_info': san_info}) |
|
965 |
|
966 return status |
738 |
967 |
739 def check_for_setup_error(self): |
968 def check_for_setup_error(self): |
740 """Check the setup error.""" |
969 """Check the setup error.""" |
741 wwns = self._get_wwns() |
970 wwns = self._get_wwns() |
742 if not wwns: |
971 if not wwns: |
845 # Enable the target and add it to the 'tg-wwn-xxx' group |
1074 # Enable the target and add it to the 'tg-wwn-xxx' group |
846 self._stmf_execute('/usr/sbin/stmfadm', 'offline-target', |
1075 self._stmf_execute('/usr/sbin/stmfadm', 'offline-target', |
847 'wwn.%s' % wwn) |
1076 'wwn.%s' % wwn) |
848 self._stmf_execute('/usr/sbin/stmfadm', 'add-tg-member', '-g', |
1077 self._stmf_execute('/usr/sbin/stmfadm', 'add-tg-member', '-g', |
849 target_group, 'wwn.%s' % wwn) |
1078 target_group, 'wwn.%s' % wwn) |
850 self._stmf_execute('/usr/sbin/stmfadm', 'online-target', |
|
851 'wwn.%s' % wwn) |
|
852 assert self._target_in_tg(wwn, target_group) |
|
853 |
1079 |
854 # Add a logical unit view entry |
1080 # Add a logical unit view entry |
855 # TODO(Strony): replace the auto assigned LUN with '-n' option |
1081 # TODO(Strony): replace the auto assigned LUN with '-n' option |
856 if luid is not None: |
1082 if luid is not None: |
857 self._stmf_execute('/usr/sbin/stmfadm', 'add-view', '-t', |
1083 self._stmf_execute('/usr/sbin/stmfadm', 'add-view', '-t', |
858 target_group, luid) |
1084 target_group, luid) |
|
1085 self._stmf_execute('/usr/sbin/stmfadm', 'online-target', |
|
1086 'wwn.%s' % wwn) |
|
1087 assert self._target_in_tg(wwn, target_group) |
859 |
1088 |
860 def remove_export(self, context, volume): |
1089 def remove_export(self, context, volume): |
861 """Remove an export for a volume.""" |
1090 """Remove an export for a volume.""" |
862 luid = self._get_luid(volume) |
1091 luid = self._get_luid(volume) |
863 |
1092 |