]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
CHAP support for IBM Storwize/SVC driver.
authorAvishay Traeger <avishay@il.ibm.com>
Thu, 29 Nov 2012 10:22:00 +0000 (12:22 +0200)
committerAvishay Traeger <avishay@il.ibm.com>
Sun, 16 Dec 2012 09:39:28 +0000 (11:39 +0200)
This implements support for CHAP authentication of hosts by the IBM
Storwize/SVC controller. A host entity was created on the controller for
each server that logs in with iSCSI. Now a randomly-generated CHAP
secret is added to the host entity on the controller (passed via the
controller CLI over SSH).

Implements blueprint add-chap-support-to-storwize-svc-driver

Change-Id: I60b5ddd0ea2e1ede3070779e5293820480aa0401

cinder/tests/test_storwize_svc.py
cinder/volume/drivers/storwize_svc.py

index 86184426093d2f7d29df8b9b080dc1af5a5fe4bc..a2102ce8039f3c0022f112b34f5ff9d5e41d8c51 100644 (file)
@@ -134,6 +134,7 @@ class StorwizeSVCManagementSimulator:
             "nohdr",
         ]
         one_param_args = [
+            "chapsecret",
             "cleanrate",
             "delim",
             "filtervalue",
@@ -551,6 +552,22 @@ class StorwizeSVCManagementSimulator:
         return ("Host, id [%s], successfully created" %
                 (host_info["id"]), "")
 
+    # Change host properties
+    def _cmd_chhost(self, **kwargs):
+        if "chapsecret" not in kwargs:
+            return self._errors["CMMVC5707E"]
+        secret = kwargs["obj"].strip('\'\"')
+
+        if "obj" not in kwargs:
+            return self._errors["CMMVC5701E"]
+        host_name = kwargs["obj"].strip('\'\"')
+
+        if host_name not in self._hosts_list:
+            return self._errors["CMMVC5753E"]
+
+        self._hosts_list[host_name]["chapsecret"] = secret
+        return ("", "")
+
     # Remove a host
     def _cmd_rmhost(self, **kwargs):
         if "obj" not in kwargs:
@@ -611,6 +628,22 @@ class StorwizeSVCManagementSimulator:
 
             return ("%s" % "\n".join(rows), "")
 
+    # List iSCSI authorization information about hosts
+    def _cmd_lsiscsiauth(self, **kwargs):
+        rows = []
+        rows.append(["type", "id", "name", "iscsi_auth_method",
+                     "iscsi_chap_secret"])
+
+        for k, host in self._hosts_list.iteritems():
+            method = "none"
+            secret = ""
+            if "chapsecret" in host:
+                method = "chap"
+                secret = host["chapsecret"]
+            rows.append(["host", host["id"], host["host_name"], method,
+                         secret])
+        return self._print_info_cmd(rows=rows, **kwargs)
+
     # Create a vdisk-host mapping
     def _cmd_mkvdiskhostmap(self, **kwargs):
         mapping_info = {}
@@ -850,10 +883,14 @@ class StorwizeSVCManagementSimulator:
             out, err = self._cmd_lsvdisk(**kwargs)
         elif command == "mkhost":
             out, err = self._cmd_mkhost(**kwargs)
+        elif command == "chhost":
+            out, err = self._cmd_chhost(**kwargs)
         elif command == "rmhost":
             out, err = self._cmd_rmhost(**kwargs)
         elif command == "lshost":
             out, err = self._cmd_lshost(**kwargs)
+        elif command == "lsiscsiauth":
+            out, err = self._cmd_lsiscsiauth(**kwargs)
         elif command == "mkvdiskhostmap":
             out, err = self._cmd_mkvdiskhostmap(**kwargs)
         elif command == "rmvdiskhostmap":
index b838fa66af09c639439bfd1bad5219548d794688..7afcb5d2e9af0785697aa8c5b2be26834e8eff8b 100644 (file)
@@ -49,6 +49,7 @@ from cinder import flags
 from cinder.openstack.common import cfg
 from cinder.openstack.common import excutils
 from cinder.openstack.common import log as logging
+from cinder import utils
 from cinder.volume.drivers.san import san
 
 LOG = logging.getLogger(__name__)
@@ -536,6 +537,10 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
                 host_name is not None,
                 _('_create_new_host failed to return the host name.'))
 
+        chap_secret = self._get_chap_secret_for_host(host_name)
+        if chap_secret is None:
+            chap_secret = self._add_chapsecret_to_host(host_name)
+
         lun_id = self._map_vol_to_host(volume_name, host_name)
 
         # Get preferred path
@@ -573,6 +578,9 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
         properties['target_iqn'] = preferred_node_entry['iscsi_name']
         properties['target_lun'] = lun_id
         properties['volume_id'] = volume['id']
+        properties['auth_method'] = 'CHAP'
+        properties['auth_username'] = initiator_name
+        properties['auth_password'] = chap_secret
 
         LOG.debug(_('leave: initialize_connection:\n volume: %(vol)s\n '
                     'connector %(conn)s\n properties: %(prop)s')
@@ -986,6 +994,23 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
 
         return hostname
 
+    def _add_chapsecret_to_host(self, host_name):
+        """Generate and store a randomly-generated CHAP secret for the host."""
+
+        chap_secret = utils.generate_password()
+        out, err = self._run_ssh('chhost -chapsecret "%s" %s'
+                                 % (chap_secret, host_name))
+        # No output should be returned from chhost
+        self._driver_assert(
+            len(out.strip()) == 0,
+            _('change host %(name)s - non empty output from CLI.\n '
+              'stdout: %(out)s\n stderr: %(err)s')
+            % {'name': host_name,
+               'out': str(out),
+               'err': str(err)})
+
+        return chap_secret
+
     def _create_new_host(self, host_name, initiator_name):
         """Create a new host on the storage system.
 
@@ -1041,6 +1066,14 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
         if is_defined:
             # Delete host
             out, err = self._run_ssh('rmhost %s ' % host_name)
+            # No output should be returned from rmhost
+            self._driver_assert(
+                len(out.strip()) == 0,
+                _('delete host %(name)s - non empty output from CLI.\n '
+                  'stdout: %(out)s\n stderr: %(err)s')
+                % {'name': host_name,
+                   'out': str(out),
+                   'err': str(err)})
         else:
             LOG.info(_('warning: tried to delete host %(name)s but '
                        'it does not exist.') % {'name': host_name})
@@ -1268,3 +1301,50 @@ class StorwizeSVCDriver(san.SanISCSIDriver):
                      'attr': str(attributes)})
 
         return attributes
+
+    def _get_chap_secret_for_host(self, host_name):
+        """Return the CHAP secret for the given host."""
+
+        LOG.debug(_('enter: _get_chap_secret_for_host: host name %s')
+                  % host_name)
+
+        ssh_cmd = 'lsiscsiauth -delim !'
+        out, err = self._run_ssh(ssh_cmd)
+
+        if (len(out.strip()) == 0):
+            return None
+
+        err_msg = _('_get_chap_secret_for_host: '
+                    'failed with unexpected CLI output.\n'
+                    ' command: %(cmd)s\n stdout: %(out)s\n '
+                    'stderr: %(err)s') % {'cmd': ssh_cmd,
+                                          'out': str(out),
+                                          'err': str(err)}
+
+        host_lines = out.strip().split('\n')
+        self._driver_assert(len(host_lines) > 0, err_msg)
+
+        header = host_lines.pop(0).split('!')
+        self._driver_assert('name' in header, err_msg)
+        self._driver_assert('iscsi_auth_method' in header, err_msg)
+        self._driver_assert('iscsi_chap_secret' in header, err_msg)
+        name_index = header.index('name')
+        method_index = header.index('iscsi_auth_method')
+        secret_index = header.index('iscsi_chap_secret')
+
+        chap_secret = None
+        host_found = False
+        for line in host_lines:
+            info = line.split('!')
+            if info[name_index] == host_name:
+                host_found = True
+                if info[method_index] == 'chap':
+                    chap_secret = info[secret_index]
+
+        self._driver_assert(host_found is not False, err_msg)
+
+        LOG.debug(_('leave: _get_chap_secret_for_host: host name %(host)s '
+                    'with secret %(secret)s')
+                  % {'host': host_name, 'secret': chap_secret})
+
+        return chap_secret