170 """Initialize the connection and returns connection info.""" |
170 """Initialize the connection and returns connection info.""" |
171 volume_path = '%s/volume-%s' % (self.configuration.zfs_volume_base, |
171 volume_path = '%s/volume-%s' % (self.configuration.zfs_volume_base, |
172 volume['id']) |
172 volume['id']) |
173 return { |
173 return { |
174 'driver_volume_type': 'local', |
174 'driver_volume_type': 'local', |
175 'volume_path': volume_path |
175 'volume_path': volume_path, |
|
176 'data': {} |
176 } |
177 } |
177 |
178 |
178 def terminate_connection(self, volume, connector, **kwargs): |
179 def terminate_connection(self, volume, connector, **kwargs): |
179 """Disconnection from the connector.""" |
180 """Disconnection from the connector.""" |
180 pass |
181 pass |
181 |
182 |
182 def attach_volume(self, context, volume_id, instance_uuid, mountpoint): |
183 def attach_volume(self, context, volume, instance_uuid, host_name, |
183 """ Callback for volume attached to instance.""" |
184 mountpoint): |
|
185 """Callback for volume attached to instance or host.""" |
184 pass |
186 pass |
185 |
187 |
186 def detach_volume(self, context, volume_id): |
188 def detach_volume(self, context, volume): |
187 """ Callback for volume detached.""" |
189 """ Callback for volume detached.""" |
188 pass |
190 pass |
189 |
191 |
190 def get_volume_stats(self, refresh=False): |
192 def get_volume_stats(self, refresh=False): |
191 """Get volume status.""" |
193 """Get volume status.""" |
325 return luid |
338 return luid |
326 |
339 |
327 def _get_view_and_lun(self, lu): |
340 def _get_view_and_lun(self, lu): |
328 """Check the view entry of the LU and then get the lun and view.""" |
341 """Check the view entry of the LU and then get the lun and view.""" |
329 view_and_lun = {} |
342 view_and_lun = {} |
330 view_and_lun['valid_value'] = False |
343 view_and_lun['view'] = view_and_lun['lun'] = None |
331 try: |
344 try: |
332 (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-view', |
345 (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-view', |
333 '-l', lu) |
346 '-l', lu, '-v') |
334 except exception.ProcessExecutionError as error: |
347 except processutils.ProcessExecutionError as error: |
335 if 'no views found' in error.stderr: |
348 if 'no views found' in error.stderr: |
336 LOG.debug(_("No view is found for LU '%s'") % lu) |
349 LOG.debug(_("No view is found for LU '%s'") % lu) |
337 return view_and_lun |
350 return view_and_lun |
338 else: |
351 else: |
339 raise |
352 raise |
340 |
353 |
341 for line in [l.strip() for l in out.splitlines()]: |
354 for line in [l.strip() for l in out.splitlines()]: |
342 if line.startswith("View Entry:"): |
355 if line.startswith("View Entry:"): |
343 view_and_lun['view'] = line.split()[-1] |
356 view_and_lun['view'] = line.split()[-1] |
344 view_and_lun['valid_value'] = True |
357 if line.startswith("LUN") and 'Auto' not in line.split()[-1]: |
345 if line.startswith("LUN"): |
358 view_and_lun['lun'] = int(line.split()[-1]) |
346 view_and_lun['lun'] = line.split()[-1] |
359 break |
347 |
360 if line.startswith("Lun"): |
348 if view_and_lun['lun'] == 'Auto': |
361 view_and_lun['lun'] = int(line.split()[2]) |
349 view_and_lun['lun'] = 0 |
362 |
350 |
363 if view_and_lun['view'] is None or view_and_lun['lun'] is None: |
351 LOG.debug(_("The view_entry and LUN of LU '%s' are '%s' and '%s'.") |
364 LOG.error(_("Failed to get the view_entry or LUN of the LU '%s'.") |
352 % (lu, view_and_lun['view'], view_and_lun['lun'])) |
365 % lu) |
|
366 raise |
|
367 else: |
|
368 LOG.debug(_("The view_entry and LUN of LU '%s' are '%s' and '%d'.") |
|
369 % (lu, view_and_lun['view'], view_and_lun['lun'])) |
353 |
370 |
354 return view_and_lun |
371 return view_and_lun |
355 |
372 |
356 |
373 |
357 class ZFSISCSIDriver(STMFDriver, driver.ISCSIDriver): |
374 class ZFSISCSIDriver(STMFDriver, driver.ISCSIDriver): |
506 |
523 |
507 LOG.debug(_('Disconnecting the initiator %(initiator_name)s ' |
524 LOG.debug(_('Disconnecting the initiator %(initiator_name)s ' |
508 'for volume %(volume_name)s') |
525 'for volume %(volume_name)s') |
509 % {'initiator_name': initiator_name, |
526 % {'initiator_name': initiator_name, |
510 'volume_name': volume_name}) |
527 'volume_name': volume_name}) |
|
528 |
|
529 |
|
530 class ZFSFCDriver(STMFDriver, driver.FibreChannelDriver): |
|
531 """ZFS volume operations in FC mode.""" |
|
532 protocol = 'FC' |
|
533 |
|
534 def __init__(self, *args, **kwargs): |
|
535 super(ZFSFCDriver, self).__init__(*args, **kwargs) |
|
536 |
|
537 def check_for_setup_error(self): |
|
538 """Check the setup error.""" |
|
539 wwns = self._get_wwns() |
|
540 if not wwns: |
|
541 msg = (_("Could not determine fibre channel world wide " |
|
542 "node names.")) |
|
543 raise exception.VolumeBackendAPIException(data=msg) |
|
544 |
|
545 def _get_wwns(self): |
|
546 """Get the FC port WWNs of the host.""" |
|
547 (out, _err) = self._execute('/usr/sbin/fcinfo', 'hba-port', '-t') |
|
548 |
|
549 wwns = [] |
|
550 for line in [l.strip() for l in out.splitlines()]: |
|
551 if line.startswith("HBA Port WWN:"): |
|
552 wwn = line.split()[-1] |
|
553 LOG.debug(_("Got the FC port WWN '%s'") % wwn) |
|
554 wwns.append(wwn) |
|
555 |
|
556 return wwns |
|
557 |
|
558 def _check_wwn_tg(self, wwn): |
|
559 """Check if the target group 'tg-wwn-xxx' exists.""" |
|
560 (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg') |
|
561 |
|
562 for line in [l.strip() for l in out.splitlines()]: |
|
563 if line.startswith("Target Group:") and wwn in line: |
|
564 tg = line.split()[-1] |
|
565 break |
|
566 else: |
|
567 LOG.debug(_("The target group 'tg-wwn-%s' doesn't exist.") % wwn) |
|
568 tg = None |
|
569 |
|
570 return tg |
|
571 |
|
572 def _only_lu(self, lu): |
|
573 """Check if the LU is the only one.""" |
|
574 (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-lu', '-v') |
|
575 linecount = 0 |
|
576 |
|
577 for line in [l.strip() for l in out.splitlines()]: |
|
578 if line.startswith("LU Name:"): |
|
579 luid = line.split()[-1] |
|
580 linecount += 1 |
|
581 |
|
582 if linecount == 1 and luid == lu: |
|
583 LOG.debug(_("The LU '%s' is the only one.") % lu) |
|
584 return True |
|
585 else: |
|
586 return False |
|
587 |
|
588 def _target_in_tg(self, wwn, tg): |
|
589 """Check if the target has been added into a target group.""" |
|
590 target = 'wwn.%s' % wwn.upper() |
|
591 |
|
592 if tg is not None: |
|
593 (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg', |
|
594 '-v', tg) |
|
595 else: |
|
596 (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg', '-v') |
|
597 |
|
598 for line in [l.strip() for l in out.splitlines()]: |
|
599 if line.startswith("Member:") and target in line: |
|
600 return True |
|
601 LOG.debug(_("The target '%s' is not in any target group.") % target) |
|
602 return False |
|
603 |
|
604 def create_export(self, context, volume): |
|
605 """Export the volume.""" |
|
606 zvol = self._get_zvol_path(volume) |
|
607 |
|
608 # Create a Logical Unit (LU) |
|
609 self._execute('/usr/sbin/stmfadm', 'create-lu', zvol) |
|
610 luid = self._get_luid(volume) |
|
611 if not luid: |
|
612 msg = (_("Failed to create logic unit for volume '%s'") |
|
613 % volume['name']) |
|
614 raise exception.VolumeBackendAPIException(data=msg) |
|
615 |
|
616 wwns = self._get_wwns() |
|
617 wwn = wwns[0] |
|
618 target_group = self._check_wwn_tg(wwn) |
|
619 if target_group is None: |
|
620 target_group = 'tg-wwn-%s' % wwn |
|
621 if self._target_in_tg(wwn, None): |
|
622 msg = (_("Target WWN '%s' has been found in another" |
|
623 "target group, so it will not be added " |
|
624 "into the expected target group '%s'.") % |
|
625 (wwn, target_group)) |
|
626 raise exception.VolumeBackendAPIException(data=msg) |
|
627 |
|
628 # Create a target group for the wwn |
|
629 self._execute('/usr/sbin/stmfadm', 'create-tg', target_group) |
|
630 |
|
631 # Enable the target and add it to the 'tg-wwn-xxx' group |
|
632 self._execute('/usr/sbin/stmfadm', 'offline-target', |
|
633 'wwn.%s' % wwn) |
|
634 self._execute('/usr/sbin/stmfadm', 'add-tg-member', '-g', |
|
635 target_group, 'wwn.%s' % wwn) |
|
636 self._execute('/usr/sbin/stmfadm', 'online-target', 'wwn.%s' % wwn) |
|
637 assert self._target_in_tg(wwn, target_group) |
|
638 |
|
639 # Add a logical unit view entry |
|
640 # TODO(Strony): replace the auto assigned LUN with '-n' option |
|
641 if luid is not None: |
|
642 self._execute('/usr/sbin/stmfadm', 'add-view', '-t', |
|
643 target_group, luid) |
|
644 |
|
645 def remove_export(self, context, volume): |
|
646 """Remove an export for a volume.""" |
|
647 luid = self._get_luid(volume) |
|
648 |
|
649 if luid is not None: |
|
650 wwns = self._get_wwns() |
|
651 wwn = wwns[0] |
|
652 target_wwn = 'wwn.%s' % wwn |
|
653 target_group = 'tg-wwn-%s' % wwn |
|
654 view_lun = self._get_view_and_lun(luid) |
|
655 if view_lun['view']: |
|
656 self._execute('/usr/sbin/stmfadm', 'remove-view', '-l', |
|
657 luid, view_lun['view']) |
|
658 |
|
659 # Remove the target group when only one LU exists. |
|
660 if self._only_lu(luid): |
|
661 if self._check_target(target_wwn, 'Channel'): |
|
662 self._execute('/usr/sbin/stmfadm', 'offline-target', |
|
663 target_wwn) |
|
664 if self._check_tg(target_group): |
|
665 self._execute('/usr/sbin/stmfadm', 'delete-tg', |
|
666 target_group) |
|
667 |
|
668 # Remove the LU |
|
669 self._execute('/usr/sbin/stmfadm', 'delete-lu', luid) |
|
670 |
|
671 def _get_fc_properties(self, volume): |
|
672 """Get Fibre Channel configuration. |
|
673 |
|
674 :target_discovered: boolean indicating whether discovery was used |
|
675 :target_wwn: the world wide name of the FC port target |
|
676 :target_lun: the lun assigned to the LU for the view entry |
|
677 |
|
678 """ |
|
679 wwns = self._get_wwns() |
|
680 if not wwns: |
|
681 msg = (_("Could not determine fibre channel world wide " |
|
682 "node names.")) |
|
683 raise exception.VolumeBackendAPIException(data=msg) |
|
684 |
|
685 luid = self._get_luid(volume) |
|
686 if not luid: |
|
687 msg = (_("Failed to get logic unit for volume '%s'") |
|
688 % volume['name']) |
|
689 raise exception.VolumeBackendAPIException(data=msg) |
|
690 |
|
691 properties = {} |
|
692 |
|
693 properties['target_discovered'] = True |
|
694 properties['target_wwn'] = wwns |
|
695 view_lun = self._get_view_and_lun(luid) |
|
696 if view_lun['lun']: |
|
697 properties['target_lun'] = view_lun['lun'] |
|
698 return properties |
|
699 |
|
700 def initialize_connection(self, volume, connector): |
|
701 """Initializes the connection and returns connection info. |
|
702 |
|
703 The driver returns a driver_volume_type of 'fibre_channel'. |
|
704 The target_wwn can be a single entry or a list of wwns that |
|
705 correspond to the list of remote wwn(s) that will export the volume. |
|
706 Example return values: |
|
707 |
|
708 { |
|
709 'driver_volume_type': 'fibre_channel' |
|
710 'data': { |
|
711 'target_discovered': True, |
|
712 'target_lun': 1, |
|
713 'target_wwn': '1234567890123', |
|
714 } |
|
715 } |
|
716 |
|
717 or |
|
718 |
|
719 { |
|
720 'driver_volume_type': 'fibre_channel' |
|
721 'data': { |
|
722 'target_discovered': True, |
|
723 'target_lun': 1, |
|
724 'target_wwn': ['1234567890123', '0987654321321'], |
|
725 } |
|
726 } |
|
727 |
|
728 """ |
|
729 fc_properties = self._get_fc_properties(volume) |
|
730 |
|
731 return { |
|
732 'driver_volume_type': 'fibre_channel', |
|
733 'data': fc_properties |
|
734 } |