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
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
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."))
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
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"]))
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)
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."))
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)
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
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)
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."))
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))
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
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)
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