components/openstack/cinder/files/solaris/zfs.py
changeset 6849 f9a2279efa0d
parent 6070 87daa7413b2d
--- a/components/openstack/cinder/files/solaris/zfs.py	Wed Sep 07 14:48:41 2016 -0700
+++ b/components/openstack/cinder/files/solaris/zfs.py	Wed Sep 07 14:48:41 2016 -0700
@@ -25,6 +25,8 @@
 import subprocess
 import time
 
+from eventlet.green.OpenSSL import SSL
+from eventlet.green import socket
 from oslo_concurrency import processutils
 from oslo_config import cfg
 from oslo_log import log as logging
@@ -36,17 +38,13 @@
 from cinder.volume import driver
 from cinder.volume.drivers.san.san import SanDriver
 
-from eventlet.green import socket
-from eventlet.green.OpenSSL import SSL
-
+import rad.auth as rada
+import rad.bindings.com.oracle.solaris.rad.zfsmgr_1 as zfsmgr
 import rad.client as radc
 import rad.connect as radcon
-import rad.bindings.com.oracle.solaris.rad.zfsmgr_1 as zfsmgr
-import rad.auth as rada
-
 from solaris_install.target.size import Size
 
-FLAGS = cfg.CONF
+CONF = cfg.CONF
 LOG = logging.getLogger(__name__)
 
 solaris_zfs_opts = [
@@ -57,7 +55,7 @@
                default='tgt-grp',
                help='iSCSI target group name.'), ]
 
-FLAGS.register_opts(solaris_zfs_opts)
+CONF.register_opts(solaris_zfs_opts)
 
 
 def connect_tls(host, port=12302, locale=None, ca_certs=None):
@@ -103,10 +101,12 @@
         1.2.2 - Introduce the ZFS RAD for volume migration enhancement
         1.2.3 - Replace volume-specific targets with one shared target in
                 the ZFSISCSIDriver
+        1.3.0 - Support the option iscsi_secondary_ip_addresses and then
+                return target_iqns, target_portals, target_luns in Mitaka
 
     """
 
-    version = "1.2.3"
+    version = "1.3.0"
     protocol = 'local'
 
     def __init__(self, *args, **kwargs):
@@ -224,7 +224,7 @@
         """Synchronously recreate an export for a logical volume."""
         pass
 
-    def create_export(self, context, volume):
+    def create_export(self, context, volume, connector):
         """Export the volume."""
         pass
 
@@ -398,60 +398,64 @@
         # Create the temporary snapshot of src volume
         self.create_snapshot(src_snapshot)
 
-        src_rc = self._get_rc_connect()
-        dst_rc = self._get_rc_connect(dst_san_info)
+        try:
+            src_rc = self._get_rc_connect()
+            dst_rc = self._get_rc_connect(dst_san_info)
 
-        src_pat = self._get_zfs_volume_name(src['name'])
-        src_vol_obj = src_rc.get_object(zfsmgr.ZfsDataset(),
-                                        radc.ADRGlobPattern({"name": src_pat}))
-        dst_pat = dst.rsplit('/', 1)[0]
-        dst_vol_obj = dst_rc.get_object(zfsmgr.ZfsDataset(),
-                                        radc.ADRGlobPattern({"name": dst_pat}))
+            src_pat = self._get_zfs_volume_name(src['name'])
+            src_vol_obj = src_rc.get_object(zfsmgr.ZfsDataset(),
+                                            radc.ADRGlobPattern({"name":
+                                                                src_pat}))
+            dst_pat = dst.rsplit('/', 1)[0]
+            dst_vol_obj = dst_rc.get_object(zfsmgr.ZfsDataset(),
+                                            radc.ADRGlobPattern({"name":
+                                                                dst_pat}))
 
-        send_sock_info = src_vol_obj.get_send_socket(
-            name=src_snapshot_name, socket_type=zfsmgr.SocketType.AF_INET)
-        send_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        send_sock.connect((self.hostname, int(send_sock_info.socket)))
+            send_sock_info = src_vol_obj.get_send_socket(
+                name=src_snapshot_name, socket_type=zfsmgr.SocketType.AF_INET)
+            send_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            send_sock.connect((self.hostname, int(send_sock_info.socket)))
 
-        dst_san_ip = dst_san_info.split(';')[0]
-        remote_host, alias, addresslist = socket.gethostbyaddr(dst_san_ip)
+            dst_san_ip = dst_san_info.split(';')[0]
+            remote_host, alias, addresslist = socket.gethostbyaddr(dst_san_ip)
 
-        recv_sock_info = dst_vol_obj.get_receive_socket(
-            name=dst, socket_type=zfsmgr.SocketType.AF_INET,
-            name_options=zfsmgr.ZfsRecvNameOptions.use_provided_name)
-        recv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        recv_sock.connect((remote_host, int(recv_sock_info.socket)))
+            recv_sock_info = dst_vol_obj.get_receive_socket(
+                name=dst, socket_type=zfsmgr.SocketType.AF_INET,
+                name_options=zfsmgr.ZfsRecvNameOptions.use_provided_name)
+            recv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            recv_sock.connect((remote_host, int(recv_sock_info.socket)))
 
-        # Set 4mb buffer size
-        buf_size = 4194304
-        while True:
-            # Read the data from the send stream
-            buf = send_sock.recv(buf_size)
-            if not buf:
-                break
-            # Write the data to the receive steam
-            recv_sock.send(buf)
+            # Set 4mb buffer size
+            buf_size = 4194304
+            while True:
+                # Read the data from the send stream
+                buf = send_sock.recv(buf_size)
+                if not buf:
+                    break
+                # Write the data to the receive steam
+                recv_sock.send(buf)
 
-        recv_sock.close()
-        send_sock.close()
-        time.sleep(1)
+            recv_sock.close()
+            send_sock.close()
+            time.sleep(1)
 
-        # Delete the temporary dst snapshot
-        pat = radc.ADRGlobPattern({"name": dst})
-        dst_zvol_obj = dst_rc.get_object(zfsmgr.ZfsDataset(), pat)
-        snapshot_list = dst_zvol_obj.get_snapshots()
-        for snap in snapshot_list:
-            if 'tmp-send-snapshot'in snap:
-                dst_zvol_obj.destroy_snapshot(snap)
-                break
+            # Delete the temporary dst snapshot
+            pat = radc.ADRGlobPattern({"name": dst})
+            dst_zvol_obj = dst_rc.get_object(zfsmgr.ZfsDataset(), pat)
+            snapshot_list = dst_zvol_obj.get_snapshots()
+            for snap in snapshot_list:
+                if 'tmp-send-snapshot'in snap:
+                    dst_zvol_obj.destroy_snapshot(snap)
+                    break
 
-        # Delete the temporary src snapshot
-        self.delete_snapshot(src_snapshot)
-        LOG.debug(("Transfered src stream'%s' to dst'%s' on the host'%s'") %
-                  (src_snapshot_name, dst, self.hostname))
+        finally:
+            # Delete the temporary src snapshot
+            self.delete_snapshot(src_snapshot)
+            LOG.debug(("Transfered src'%s' to dst'%s' on the host'%s'") %
+                      (src_snapshot_name, dst, self.hostname))
 
-        src_rc.close()
-        dst_rc.close()
+            src_rc.close()
+            dst_rc.close()
 
     def _get_zvol_path(self, volume):
         """Get the ZFS volume path."""
@@ -651,6 +655,12 @@
                            % (target, error.stderr))
         raise exception.VolumeBackendAPIException(data=err_msg)
 
+    def _online_target(self, target, protocol):
+        """Online the target in the offline state."""
+        self._execute('/usr/sbin/stmfadm', 'online-target',
+                      target)
+        assert self._check_target(target, protocol) == 'Online'
+
     def _check_tg(self, tg):
         """Check if the target group exists."""
         try:
@@ -751,35 +761,117 @@
 
         return status
 
+    def _add_tg_member(self, target, tg, tpg):
+        """Create the target and then add it to the target group."""
+        self._stmf_execute('/usr/sbin/itadm', 'create-target', '-n',
+                           target, '-t', tpg)
+        self._stmf_execute('/usr/sbin/stmfadm', 'offline-target',
+                           target)
+        self._stmf_execute('/usr/sbin/stmfadm', 'add-tg-member', '-g',
+                           tg, target)
+
+    def _get_target_portal(self, target):
+        """Get the current target IP address."""
+        (out, _err) = self._execute('/usr/sbin/itadm', 'list-target',
+                                    '-v', target)
+        portal = None
+        for line in [l.strip() for l in out.splitlines()]:
+            if line.startswith("Target Address:"):
+                portal = line.split()[-1]
+                break
+        return portal
+
+    def _check_tpg(self, tpg):
+        """Verify the tpg."""
+        try:
+            (out, _err) = self._execute('/usr/sbin/itadm', 'list-tpg',
+                                        '-v', tpg)
+            return True
+        except processutils.ProcessExecutionError as error:
+            if 'not found' in error.stderr:
+                return False
+            else:
+                err_msg = (_("Failed to list the tpg '%s': '%s'")
+                           % (tpg, error.stderr))
+            raise exception.VolumeBackendAPIException(data=err_msg)
+
+    def _create_tpg(self, tpg, ip):
+        """Create the TPG for the IP address."""
+        try:
+            self._execute('/usr/sbin/itadm', 'create-tpg', tpg, ip)
+        except processutils.ProcessExecutionError as error:
+            err_msg = (_("Failed to create the tpg '%s': '%s'") %
+                        (tpg, error.stderr))
+            raise exception.VolumeBackendAPIException(data=err_msg)
+
+    def _setup_targets(self, target_ips, target_group):
+        """Setup targets for the IP addresses."""
+        for ip in target_ips:
+            tpg_name = "tpg-%s" % ip
+            if not self._check_tpg(tpg_name):
+                self._create_tpg(tpg_name, ip)
+            target_name = '%s%s-%s-%s-target' % \
+                          (self.configuration.iscsi_target_prefix,
+                           self.hostname,
+                           tpg_name,
+                           target_group)
+            target_status = self._check_target(target_name, 'iSCSI')
+            if target_status == 'Online':
+                continue
+            if target_status is None:
+                self._add_tg_member(target_name, target_group, tpg_name)
+            self._online_target(target_name, 'iSCSI')
+
     def do_setup(self, context):
         """Setup the target and target group."""
         target_group = self.configuration.zfs_target_group
+
+        if not self._check_tg(target_group):
+            self._stmf_execute('/usr/sbin/stmfadm', 'create-tg', target_group)
         target_name = '%s%s-%s-target' % \
                       (self.configuration.iscsi_target_prefix,
                        self.hostname,
                        target_group)
-
-        if not self._check_tg(target_group):
-            self._stmf_execute('/usr/sbin/stmfadm', 'create-tg', target_group)
         target_status = self._check_target(target_name, 'iSCSI')
-        if target_status == 'Online':
+        secondary_interfaces = self.configuration.iscsi_secondary_ip_addresses
+        if target_status == 'Online' and not secondary_interfaces:
             return
-
         if target_status is None:
-            # Create and add the target into the target group
-            self._stmf_execute('/usr/sbin/itadm', 'create-target', '-n',
-                               target_name)
-            self._stmf_execute('/usr/sbin/stmfadm', 'offline-target',
-                               target_name)
-            self._stmf_execute('/usr/sbin/stmfadm', 'add-tg-member', '-g',
-                               target_group, target_name)
+            # Create the primary target
+            if self.configuration.san_is_local:
+                primary_ip = self.configuration.iscsi_ip_address
+            else:
+                primary_ip = self.configuration.san_ip
+            tpg_name = "tpg-%s" % primary_ip
+            if not self._check_tpg(tpg_name):
+                self._create_tpg(tpg_name, primary_ip)
+            self._add_tg_member(target_name, target_group, tpg_name)
 
         # Online the target from the 'Offline' status
-        self._stmf_execute('/usr/sbin/stmfadm', 'online-target',
-                           target_name)
-        assert self._check_target(target_name, 'iSCSI') == 'Online'
+        self._online_target(target_name, 'iSCSI')
+
+        if secondary_interfaces:
+            secondary_ips = [ip for ip in secondary_interfaces if ip.strip()]
+            self._setup_targets(secondary_ips, target_group)
 
-    def create_export(self, context, volume):
+    def _get_tg_secondary_members(self, primary_target):
+        """Get target members of the target group."""
+        tg = self.configuration.zfs_target_group
+        (out, _err) = self._execute('/usr/sbin/stmfadm', 'list-tg', '-v', tg)
+        targets = []
+        target_portals = []
+        for line in [l.strip() for l in out.splitlines()]:
+            if line.startswith("Member:"):
+                target = line.split()[-1]
+                if target == primary_target:
+                    continue
+                targets.append(target)
+                target_portal = self._get_target_portal(target)
+                if target_portal:
+                    target_portals.append(target_portal)
+        return targets, target_portals
+
+    def create_export(self, context, volume, conncetor):
         """Export the volume."""
         # If the volume is already exported there is nothing to do, as we
         # simply export volumes and they are universally available.
@@ -850,12 +942,18 @@
             the authentication details. Right now, either auth_method is not
             present meaning no authentication, or auth_method == `CHAP`
             meaning use CHAP with the specified credentials.
+
+        If multiple IP addresses are configured, the returns will include
+        :target_iqns, :target_portals, :target_luns, which contain lists of
+        multiple values. The main portal information is also returned in
+        :target_iqn, :target_portal, :target_lun for backward compatibility.
         """
         luid = self._get_luid(volume)
         if not luid:
             msg = (_("Failed to get LU for volume '%s'") % volume['name'])
             raise exception.VolumeBackendAPIException(data=msg)
 
+        old_target_name = True
         target_name = '%s%s' % (self.configuration.iscsi_target_prefix,
                                 volume['name'])
         if self._check_target(target_name, 'iSCSI') is None:
@@ -863,6 +961,7 @@
                           (self.configuration.iscsi_target_prefix,
                            self.hostname,
                            self.configuration.zfs_target_group)
+            old_target_name = False
 
         properties = {}
 
@@ -883,6 +982,21 @@
             properties['target_lun'] = view_lun['lun']
         properties['volume_id'] = volume['id']
 
+        secondary_ifs = self.configuration.iscsi_secondary_ip_addresses
+        # The multipathing doesn't apply to the old volume-specific target
+        if not old_target_name and secondary_ifs:
+            target_portals = []
+            target_iqns = []
+            target_luns = []
+            target_portals.append(properties['target_portal'])
+            target_iqns.append(properties['target_iqn'])
+            target_luns.append(properties['target_lun'])
+            secondary_iqns, secondary_portals = self._get_tg_secondary_members(
+                                                target_name)
+            properties['target_portals'] = target_portals + secondary_portals
+            properties['target_iqns'] = target_iqns + secondary_iqns
+            properties['target_luns'] = (len(secondary_iqns) + 1) * target_luns
+
         auth = volume['provider_auth']
         if auth:
             (auth_method, auth_username, auth_secret) = auth.split()
@@ -1016,10 +1130,7 @@
                                        target_wwn)
                     self._stmf_execute('/usr/sbin/stmfadm', 'add-tg-member',
                                        '-g', tg, target_wwn)
-                    self._stmf_execute('/usr/sbin/stmfadm', 'online-target',
-                                       target_wwn)
-                    assert self._check_target(wwn, 'Channel') == 'Online'
-
+                    self._online_target(target_wwn, 'Channel')
                 except:
                     LOG.error(_LE("Failed to add and online the target '%s'.")
                               % (target_wwn))
@@ -1068,7 +1179,7 @@
         for target_wwn in target_wwns:
             self._stmf_execute('/usr/sbin/fcadm', 'force-lip', target_wwn)
 
-    def create_export(self, context, volume):
+    def create_export(self, context, volume, connector):
         """Export the volume."""
         # If the volume is already exported there is nothing to do, as we
         # simply export volumes and they are universally available.