Exemple #1
0
    def _connect(self, volume, connector, initiator_data):
        """Connect the host and volume; return dict describing connection."""
        iqn = connector["initiator"]

        if self.configuration.use_chap_auth:
            (chap_username, chap_password, initiator_update) = \
                self._get_chap_credentials(connector['host'], initiator_data)

        vol_name = self._get_vol_name(volume)
        host = self._get_host(connector)

        if host:
            host_name = host["name"]
            LOG.info(_LI("Re-using existing purity host %(host_name)r"),
                     {"host_name": host_name})
            if self.configuration.use_chap_auth:
                if not GENERATED_NAME.match(host_name):
                    LOG.error(_LE("Purity host %(host_name)s is not managed "
                                  "by Cinder and can't have CHAP credentials "
                                  "modified. Remove IQN %(iqn)s from the host "
                                  "to resolve this issue."),
                              {"host_name": host_name,
                               "iqn": connector["initiator"]})
                    raise exception.PureDriverException(
                        reason=_("Unable to re-use a host that is not "
                                 "managed by Cinder with use_chap_auth=True,"))
                elif chap_username is None or chap_password is None:
                    LOG.error(_LE("Purity host %(host_name)s is managed by "
                                  "Cinder but CHAP credentials could not be "
                                  "retrieved from the Cinder database."),
                              {"host_name": host_name})
                    raise exception.PureDriverException(
                        reason=_("Unable to re-use host with unknown CHAP "
                                 "credentials configured."))
        else:
            host_name = self._generate_purity_host_name(connector["host"])
            LOG.info(_LI("Creating host object %(host_name)r with IQN:"
                         " %(iqn)s."), {"host_name": host_name, "iqn": iqn})
            self._array.create_host(host_name, iqnlist=[iqn])

            if self.configuration.use_chap_auth:
                self._array.set_host(host_name,
                                     host_user=chap_username,
                                     host_password=chap_password)

        connection = self._connect_host_to_vol(host_name, vol_name)

        if self.configuration.use_chap_auth:
            connection["auth_username"] = chap_username
            connection["auth_password"] = chap_password

            if initiator_update:
                connection["initiator_update"] = initiator_update

        return connection
Exemple #2
0
    def _connect_host_to_vol(self, host_name, vol_name):
        connection = None
        try:
            connection = self._array.connect_host(host_name, vol_name)
        except purestorage.PureHTTPError as err:
            with excutils.save_and_reraise_exception() as ctxt:
                if (err.code == 400
                        and "Connection already exists" in err.text):
                    # Happens if the volume is already connected to the host.
                    # Treat this as a success.
                    ctxt.reraise = False
                    LOG.debug(
                        "Volume connection already exists for Purity "
                        "host with message: %s", err.text)

                    # Get the info for the existing connection
                    connected_hosts = \
                        self._array.list_volume_private_connections(vol_name)
                    for host_info in connected_hosts:
                        if host_info["host"] == host_name:
                            connection = host_info
                            break
        if not connection:
            raise exception.PureDriverException(
                reason=_("Unable to connect or find connection to host"))

        return connection
Exemple #3
0
 def _choose_target_iscsi_port(self):
     """Find a reachable iSCSI-enabled port on target array."""
     ports = self._array.list_ports()
     iscsi_ports = [port for port in ports if port["iqn"]]
     for port in iscsi_ports:
         try:
             self._run_iscsiadm_bare([
                 "-m", "discovery", "-t", "sendtargets", "-p",
                 port["portal"]
             ])
         except processutils.ProcessExecutionError as err:
             LOG.debug(
                 ("iSCSI discovery of port %(port_name)s at "
                  "%(port_portal)s failed with error: %(err_msg)s"), {
                      "port_name": port["name"],
                      "port_portal": port["portal"],
                      "err_msg": err.stderr
                  })
         else:
             LOG.info(
                 _LI("Using port %(name)s on the array at %(portal)s "
                     "for iSCSI connectivity."), {
                         "name": port["name"],
                         "portal": port["portal"]
                     })
             return port
     raise exception.PureDriverException(
         reason=_("No reachable iSCSI-enabled ports on target array."))
Exemple #4
0
 def _get_target_iscsi_ports(self):
     """Return list of iSCSI-enabled port descriptions."""
     ports = self._array.list_ports()
     iscsi_ports = [port for port in ports if port["iqn"]]
     if not iscsi_ports:
         raise exception.PureDriverException(
             reason=_("No iSCSI-enabled ports on target array."))
     return iscsi_ports
Exemple #5
0
 def _get_host_name(self, connector):
     """Return dictionary describing the Purity host with initiator IQN."""
     hosts = self._array.list_hosts()
     for host in hosts:
         if connector["initiator"] in host["iqn"]:
             return host["name"]
     raise exception.PureDriverException(
         reason=(_("No host object on target array with IQN: ") +
                 connector["initiator"]))
Exemple #6
0
 def _verify_manage_snap_api_requirements(self):
     api_version = self._array.get_rest_version()
     if api_version not in MANAGE_SNAP_REQUIRED_API_VERSIONS:
         msg = _('Unable to do manage snapshot operations with Purity REST '
                 'API version %(api_version)s, requires '
                 '%(required_versions)s.') % {
                     'api_version': api_version,
                     'required_versions': MANAGE_SNAP_REQUIRED_API_VERSIONS
                 }
         raise exception.PureDriverException(reason=msg)
Exemple #7
0
 def _choose_rest_version(self):
     """Return a REST API version."""
     self._root_url = "https://{0}/api/".format(self._target)
     data = self._http_request("GET", "api_version")
     available_versions = data["version"]
     available_versions.sort(reverse=True)
     for version in available_versions:
         if version in FlashArray.SUPPORTED_REST_API_VERSIONS:
             return version
     raise exception.PureDriverException(
         reason=_("All REST API versions supported by this version of the "
                  "Pure Storage iSCSI driver are unavailable on array."))
Exemple #8
0
    def do_setup(self, context):
        """Performs driver initialization steps that could raise exceptions."""
        if purestorage is None:
            msg = _("Missing 'purestorage' python module, ensure the library"
                    " is installed and available.")
            raise exception.PureDriverException(msg)

        # Raises PureDriverException if unable to connect and PureHTTPError
        # if unable to authenticate.
        purestorage.FlashArray.supported_rest_versions = \
            self.SUPPORTED_REST_API_VERSIONS
        self._array = purestorage.FlashArray(
            self.configuration.san_ip,
            api_token=self.configuration.pure_api_token)
Exemple #9
0
    def assert_error_propagates(self, mocks, func, *args, **kwargs):
        """Assert that errors from mocks propagate to func.

        Fail if exceptions raised by mocks are not seen when calling
        func(*args, **kwargs). Ensure that we are really seeing exceptions
        from the mocks by failing if just running func(*args, **kargs) raises
        an exception itself.
        """
        func(*args, **kwargs)
        for mock_func in mocks:
            mock_func.side_effect = exception.PureDriverException(
                reason="reason")
            self.assertRaises(exception.PureDriverException, func, *args,
                              **kwargs)
            mock_func.side_effect = None
Exemple #10
0
 def test_terminate_connection(self, mock_host):
     vol_name = VOLUME["name"] + "-cinder"
     mock_host.return_value = HOST_NAME
     self.driver.terminate_connection(VOLUME, CONNECTOR)
     self.array.disconnect_host.assert_called_with(HOST_NAME, vol_name)
     self.array.disconnect_host.side_effect = exception.PureAPIException(
         code=400, reason="reason")
     self.driver.terminate_connection(VOLUME, CONNECTOR)
     self.array.disconnect_host.assert_called_with(HOST_NAME, vol_name)
     self.array.disconnect_host.side_effect = None
     self.array.disconnect_host.reset_mock()
     mock_host.side_effect = exception.PureDriverException(reason="reason")
     self.assertFalse(self.array.disconnect_host.called)
     mock_host.side_effect = None
     self.assert_error_propagates([self.array.disconnect_host],
                                  self.driver.terminate_connection, VOLUME,
                                  CONNECTOR)
Exemple #11
0
 def _choose_target_iscsi_port(self):
     """Find a reachable iSCSI-enabled port on target array."""
     ports = self._array.list_ports()
     iscsi_ports = [port for port in ports if port["iqn"]]
     for port in iscsi_ports:
         try:
             self._run_iscsiadm_bare(["-m", "discovery",
                                      "-t", "sendtargets",
                                      "-p", port["portal"]])
         except processutils.ProcessExecutionError as err:
             LOG.debug(("iSCSI discovery of port {0[name]} at {0[portal]} "
                        "failed with error: {1}").format(port, err.stderr))
         else:
             LOG.info(_("Using port {0[name]} on the array at {0[portal]} "
                        "for iSCSI connectivity.").format(port))
             return port
     raise exception.PureDriverException(
         reason=_("No reachable iSCSI-enabled ports on target array."))
Exemple #12
0
 def _http_request(self, method, path, data=None, reestablish_session=True):
     """Perform HTTP request for REST API."""
     req = urllib2.Request(self._root_url + path,
                           headers={"Content-Type": "application/json"})
     req.get_method = lambda: method
     body = json.dumps(data)
     try:
         # Raises urllib2.HTTPError if response code != 200
         response = self._opener.open(req, body)
     except urllib2.HTTPError as err:
         if (reestablish_session and err.code == 401):
             self._start_session()
             return self._http_request(method,
                                       path,
                                       data,
                                       reestablish_session=False)
         elif err.code == 450:
             # Purity REST API version is bad
             new_version = self._choose_rest_version()
             if new_version == self._rest_version:
                 raise exception.PureAPIException(
                     code=err.code,
                     reason=(_LE("Unable to find usable REST API version. "
                                 "Response from Pure Storage REST API: ") +
                             err.read()))
             self._rest_version = new_version
             self._root_url = "https://%s/api/%s/" % (self._target,
                                                      self._rest_version)
             return self._http_request(method, path, data)
         else:
             raise exception.PureAPIException(code=err.code,
                                              reason=err.read())
     except urllib2.URLError as err:
         # Error outside scope of HTTP status codes,
         # e.g., unable to resolve domain name
         raise exception.PureDriverException(
             reason=_LE("Unable to connect to %r. Check san_ip.") %
             self._target)
     else:
         content = response.read()
         if "application/json" in response.info().get('Content-Type'):
             return json.loads(content)
         raise exception.PureAPIException(
             reason=(_LE("Response not in JSON: ") + content))
Exemple #13
0
    def _connect(self, volume, connector):
        """Connect the host and volume; return dict describing connection."""
        connection = None
        vol_name = _get_vol_name(volume)
        host = self._get_host(connector)
        if host:
            host_name = host["name"]
            LOG.info(_LI("Re-using existing purity host %(host_name)r"),
                     {"host_name": host_name})
        else:
            host_name = _generate_purity_host_name(connector["host"])
            iqn = connector["initiator"]
            LOG.info(
                _LI("Creating host object %(host_name)r with IQN:"
                    " %(iqn)s."), {
                        "host_name": host_name,
                        "iqn": iqn
                    })
            self._array.create_host(host_name, iqnlist=[iqn])

        try:
            connection = self._array.connect_host(host_name, vol_name)
        except purestorage.PureHTTPError as err:
            with excutils.save_and_reraise_exception() as ctxt:
                if (err.code == 400
                        and "Connection already exists" in err.text):
                    # Happens if the volume is already connected to the host.
                    ctxt.reraise = False
                    LOG.warn(
                        _LW("Volume connection already exists with "
                            "message: %s"), err.text)
                    # Get the info for the existing connection
                    connected_hosts = \
                        self._array.list_volume_private_connections(vol_name)
                    for host_info in connected_hosts:
                        if host_info["host"] == host_name:
                            connection = host_info
                            break
        if not connection:
            raise exception.PureDriverException(
                reason=_("Unable to connect or find connection to host"))
        return connection
Exemple #14
0
    def create_volume_from_snapshot(self, volume, snapshot):
        """Creates a volume from a snapshot."""
        vol_name = self._get_vol_name(volume)
        if snapshot['cgsnapshot_id']:
            snap_name = self._get_pgroup_snap_name_from_snapshot(snapshot)
        else:
            snap_name = self._get_snap_name(snapshot)

        if not snap_name:
            msg = _('Unable to determine snapshot name in Purity for snapshot '
                    '%(id)s.') % {
                        'id': snapshot['id']
                    }
            raise exception.PureDriverException(reason=msg)

        self._array.copy_volume(snap_name, vol_name)
        self._extend_if_needed(vol_name, snapshot["volume_size"],
                               volume["size"])
        if volume['consistencygroup_id']:
            self._add_volume_to_consistency_group(
                volume['consistencygroup_id'], vol_name)
Exemple #15
0
    def _connect(self, volume, connector, initiator_data):
        """Connect the host and volume; return dict describing connection."""
        connection = None
        iqn = connector["initiator"]

        if self.configuration.use_chap_auth:
            (chap_username, chap_password, initiator_update) = \
                self._get_chap_credentials(connector['host'], initiator_data)

        vol_name = _get_vol_name(volume)
        host = self._get_host(connector)

        if host:
            host_name = host["name"]
            LOG.info(_LI("Re-using existing purity host %(host_name)r"),
                     {"host_name": host_name})
            if self.configuration.use_chap_auth:
                if not GENERATED_NAME.match(host_name):
                    LOG.error(
                        _LE("Purity host %(host_name)s is not managed "
                            "by Cinder and can't have CHAP credentials "
                            "modified. Remove IQN %(iqn)s from the host "
                            "to resolve this issue."), {
                                "host_name": host_name,
                                "iqn": connector["initiator"]
                            })
                    raise exception.PureDriverException(
                        reason=_("Unable to re-use a host that is not "
                                 "managed by Cinder with use_chap_auth=True,"))
                elif chap_username is None or chap_password is None:
                    LOG.error(
                        _LE("Purity host %(host_name)s is managed by "
                            "Cinder but CHAP credentials could not be "
                            "retrieved from the Cinder database."),
                        {"host_name": host_name})
                    raise exception.PureDriverException(
                        reason=_("Unable to re-use host with unknown CHAP "
                                 "credentials configured."))
        else:
            host_name = _generate_purity_host_name(connector["host"])
            LOG.info(
                _LI("Creating host object %(host_name)r with IQN:"
                    " %(iqn)s."), {
                        "host_name": host_name,
                        "iqn": iqn
                    })
            self._array.create_host(host_name, iqnlist=[iqn])

            if self.configuration.use_chap_auth:
                self._array.set_host(host_name,
                                     host_user=chap_username,
                                     host_password=chap_password)

        try:
            connection = self._array.connect_host(host_name, vol_name)
        except purestorage.PureHTTPError as err:
            with excutils.save_and_reraise_exception() as ctxt:
                if (err.code == 400
                        and "Connection already exists" in err.text):
                    # Happens if the volume is already connected to the host.
                    ctxt.reraise = False
                    LOG.warn(
                        _LW("Volume connection already exists with "
                            "message: %s"), err.text)
                    # Get the info for the existing connection
                    connected_hosts = \
                        self._array.list_volume_private_connections(vol_name)
                    for host_info in connected_hosts:
                        if host_info["host"] == host_name:
                            connection = host_info
                            break
        if not connection:
            raise exception.PureDriverException(
                reason=_("Unable to connect or find connection to host"))

        if self.configuration.use_chap_auth:
            connection["auth_username"] = chap_username
            connection["auth_password"] = chap_password

            if initiator_update:
                connection["initiator_update"] = initiator_update

        return connection