From: Avishay Traeger Date: Thu, 29 Nov 2012 10:22:00 +0000 (+0200) Subject: CHAP support for IBM Storwize/SVC driver. X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=be3d8ea90a9238b7afc5558db14c080e15001685;p=openstack-build%2Fcinder-build.git CHAP support for IBM Storwize/SVC driver. 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 --- diff --git a/cinder/tests/test_storwize_svc.py b/cinder/tests/test_storwize_svc.py index 861844260..a2102ce80 100644 --- a/cinder/tests/test_storwize_svc.py +++ b/cinder/tests/test_storwize_svc.py @@ -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": diff --git a/cinder/volume/drivers/storwize_svc.py b/cinder/volume/drivers/storwize_svc.py index b838fa66a..7afcb5d2e 100644 --- a/cinder/volume/drivers/storwize_svc.py +++ b/cinder/volume/drivers/storwize_svc.py @@ -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