def create(self, conditions, actions, uuid=None, description=None): """Create a new introspection rule. :param conditions: list of rule conditions :param actions: list of rule actions :param uuid: rule UUID, will be generated if not specified :param description: optional rule description :returns: rule representation :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported """ if uuid is not None and not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) for name, arg in [('conditions', conditions), ('actions', actions)]: if not isinstance(arg, list) or not all(isinstance(x, dict) for x in arg): raise TypeError(_("Expected list of dicts for %(arg)s " "argument, got %(real)r"), {'arg': name, 'real': arg}) body = {'uuid': uuid, 'conditions': conditions, 'actions': actions, 'description': description} return self.from_json(body)
def introspect(self, uuid, new_ipmi_password=None, new_ipmi_username=None): """Start introspection for a node. :param uuid: node UUID or name :param new_ipmi_password: if set, *Ironic Inspector* will update IPMI password to this value. DEPRECATED. :param new_ipmi_username: if new_ipmi_password is set, this values sets new IPMI user name. Defaults to one in driver_info. DEPRECATED. :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) if new_ipmi_username and not new_ipmi_password: raise ValueError( _("Setting IPMI user name requires a new password")) if new_ipmi_password: LOG.warning( _LW('Setting IPMI credentials via ironic-inspector ' 'is deprecated, this feature will be removed ' 'in the Pike release')) params = { 'new_ipmi_username': new_ipmi_username, 'new_ipmi_password': new_ipmi_password } self.request('post', '/introspection/%s' % uuid, params=params)
def create(self, conditions, actions, uuid=None, description=None): """Create a new introspection rule. :param conditions: list of rule conditions :param actions: list of rule actions :param uuid: rule UUID, will be generated if not specified :param description: optional rule description :returns: rule representation :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported """ if uuid is not None and not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) for name, arg in [('conditions', conditions), ('actions', actions)]: if not isinstance(arg, list) or not all( isinstance(x, dict) for x in arg): raise TypeError( _("Expected list of dicts for %(arg)s " "argument, got %(real)r"), { 'arg': name, 'real': arg }) body = { 'uuid': uuid, 'conditions': conditions, 'actions': actions, 'description': description } return self.from_json(body)
def take_action(self, parsed_args): if parsed_args.check_errors and not parsed_args.wait: raise RuntimeError( _("--check-errors can only be used with --wait")) client = self.app.client_manager.baremetal_introspection for uuid in parsed_args.node: client.introspect(uuid) if parsed_args.wait: print('Waiting for introspection to finish...', file=sys.stderr) result = client.wait_for_finish(parsed_args.node) result = [(uuid, s.get('error')) for uuid, s in result.items()] if parsed_args.check_errors: uuids_errors = "\n".join("%s (%s)" % node_info for node_info in result if node_info[1] is not None) if uuids_errors: raise Exception( _("Introspection failed for some nodes: %s") % uuids_errors) else: result = [] return self.COLUMNS, result
def _parse_version(api_version): try: return tuple(int(x) for x in api_version.split('.')) except (ValueError, TypeError): raise ValueError( _("Malformed API version: expect tuple, string " "in form of X.Y or integer"))
def wait_for_finish(self, node_ids=None, retry_interval=DEFAULT_RETRY_INTERVAL, max_retries=DEFAULT_MAX_RETRIES, sleep_function=time.sleep, uuids=None): """Wait for introspection finishing for given nodes. :param uuids: collection of node UUIDs or names, deprecated :param node_ids: collection of node node_ids or names :param retry_interval: sleep interval between retries. :param max_retries: maximum number of retries. :param sleep_function: function used for sleeping between retries. :raises: :py:class:`ironic_inspector_client.WaitTimeoutError` on timeout :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: dictionary UUID -> status (the same as in get_status). """ result = {} node_ids = node_ids or uuids if uuids: warnings.warn( "Parameter uuid is deprecated and will be " "removed in future releases, please use " "node_id instead.", DeprecationWarning) elif not node_ids: raise TypeError("The node_ids argument is required") # Number of attempts = number of retries + first attempt for attempt in range(max_retries + 1): new_active_node_ids = [] for node_id in node_ids: status = self.get_status(node_id) if status.get('finished'): result[node_id] = status else: new_active_node_ids.append(node_id) if new_active_node_ids: if attempt != max_retries: node_ids = new_active_node_ids LOG.debug( 'Still waiting for introspection results for ' '%(count)d nodes, attempt %(attempt)d of ' '%(total)d', { 'count': len(new_active_node_ids), 'attempt': attempt + 1, 'total': max_retries + 1 }) sleep_function(retry_interval) else: return result raise WaitTimeoutError( _("Timeout while waiting for introspection " "of nodes %s") % new_active_node_ids)
def __init__(self, expected, supported): msg = (_('Version %(expected)s is not supported by the server, ' 'supported range is %(supported)s') % {'expected': expected, 'supported': ' to '.join(str(x) for x in supported)}) self.expected_version = expected self.supported_versions = supported super(Exception, self).__init__(msg)
def __init__(self, expected, supported): msg = (_('Version %(expected)s is not supported by the server, ' 'supported range is %(supported)s') % { 'expected': expected, 'supported': ' to '.join(str(x) for x in supported) }) self.expected_version = expected self.supported_versions = supported super(Exception, self).__init__(msg)
def _check_api_version(self, api_version): if isinstance(api_version, int): api_version = (api_version, 0) if isinstance(api_version, six.string_types): api_version = _parse_version(api_version) api_version = tuple(api_version) if not all(isinstance(x, int) for x in api_version): raise TypeError(_("All API version components should be integers")) if len(api_version) == 1: api_version += (0,) elif len(api_version) > 2: raise ValueError(_("API version should be of length 1 or 2")) minv, maxv = self.server_api_versions() if api_version < minv or api_version > maxv: raise VersionNotSupported(api_version, (minv, maxv)) return api_version
def delete(self, uuid): """Delete an introspection rule. :param uuid: rule UUID """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) self._request('delete', '/rules/%s' % uuid)
def _check_api_version(self, api_version): if isinstance(api_version, int): api_version = (api_version, 0) if isinstance(api_version, six.string_types): api_version = _parse_version(api_version) api_version = tuple(api_version) if not all(isinstance(x, int) for x in api_version): raise TypeError(_("All API version components should be integers")) if len(api_version) == 1: api_version += (0, ) elif len(api_version) > 2: raise ValueError(_("API version should be of length 1 or 2")) minv, maxv = self.server_api_versions() if api_version < minv or api_version > maxv: raise VersionNotSupported(api_version, (minv, maxv)) return api_version
def get(self, uuid): """Get detailed information about an introspection rule. :param uuid: rule UUID :returns: rule representation """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) return self._request('get', '/rules/%s' % uuid).json()
def delete(self, uuid): """Delete an introspection rule. :param uuid: rule UUID :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) self._request('delete', '/rules/%s' % uuid)
def get_status(self, uuid): """Get introspection status for a node. :param uuid: node uuid. :raises: ClientError on error reported from a server :raises: VersionNotSupported if requested api_version is not supported :raises: *requests* library exception on connection problems. """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) return self.request('get', '/introspection/%s' % uuid).json()
def get(self, uuid): """Get detailed information about an introspection rule. :param uuid: rule UUID :returns: rule representation :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) return self._request('get', '/rules/%s' % uuid).json()
def delete(self, uuid): """Delete an introspection rule. :param uuid: rule UUID :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) self._request('delete', '/rules/%s' % uuid)
def create(self, conditions, actions, uuid=None, description=None): """Create a new introspection rule. :conditions: list of rule conditions :actions: list of rule actions :uuid: rule UUID, will be generated if not specified :description: optional rule description :returns: rule representation """ if uuid is not None and not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) for name, arg in [('conditions', conditions), ('actions', actions)]: if not isinstance(arg, list) or not all(isinstance(x, dict) for x in arg): raise TypeError(_("Expected list of dicts for %(arg)s " "argument, got %(real)r"), {'arg': name, 'real': arg}) body = {'uuid': uuid, 'conditions': conditions, 'actions': actions, 'description': description} return self.from_json(body)
def wait_for_finish(self, node_ids, retry_interval=DEFAULT_RETRY_INTERVAL, max_retries=DEFAULT_MAX_RETRIES, sleep_function=time.sleep, uuids=None): """Wait for introspection finishing for given nodes. :param uuids: collection of node UUIDs or names, deprecated :param node_ids: collection of node node_ids or names :param retry_interval: sleep interval between retries. :param max_retries: maximum number of retries. :param sleep_function: function used for sleeping between retries. :raises: :py:class:`ironic_inspector_client.WaitTimeoutError` on timeout :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: dictionary UUID -> status (the same as in get_status). """ result = {} node_ids = node_ids or uuids if uuids: warnings.warn("Parameter uuid is deprecated and will be " "removed in future releases, please use " "node_id instead.", DeprecationWarning) # Number of attempts = number of retries + first attempt for attempt in range(max_retries + 1): new_active_node_ids = [] for node_id in node_ids: status = self.get_status(node_id) if status.get('finished'): result[node_id] = status else: new_active_node_ids.append(node_id) if new_active_node_ids: if attempt != max_retries: node_ids = new_active_node_ids LOG.debug('Still waiting for introspection results for ' '%(count)d nodes, attempt %(attempt)d of ' '%(total)d', {'count': len(new_active_node_ids), 'attempt': attempt + 1, 'total': max_retries + 1}) sleep_function(retry_interval) else: return result raise WaitTimeoutError(_("Timeout while waiting for introspection " "of nodes %s") % new_active_node_ids)
def list_statuses(self, marker=None, limit=None): """List introspection statuses. Supports pagination via the marker and limit params. The items are sorted by the server according to the `started_at` attribute, newer items first. :param marker: pagination maker, UUID or None :param limit: pagination limit, int or None :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: a list of status dictionaries with the keys: * `error` an error string or None, * `finished` whether introspection was finished, * `finished_at` an ISO8601 timestamp or None, * `links` with a self-link URL, * `started_at` an ISO8601 timestamp, * `uuid` the node UUID """ if not (marker is None or isinstance(marker, six.string_types)): raise TypeError( _('Expected a string value of the marker, got ' '%s instead') % marker) if not (limit is None or isinstance(limit, int)): raise TypeError( _('Expected an integer value of the limit, got ' '%s instead') % limit) params = { 'marker': marker, 'limit': limit, } response = self.request('get', '/introspection', params=params) return response.json()['introspection']
def introspect(self, uuid): """Start introspection for a node. :param uuid: node UUID or name :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) self.request('post', '/introspection/%s' % uuid)
def get(self, uuid): """Get detailed information about an introspection rule. :param uuid: rule UUID :returns: rule representation :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) return self._request('get', '/rules/%s' % uuid).json()
def introspect(self, uuid, new_ipmi_password=None, new_ipmi_username=None): """Start introspection for a node. :param uuid: node uuid :param new_ipmi_password: if set, *Ironic Inspector* will update IPMI password to this value. :param new_ipmi_username: if new_ipmi_password is set, this values sets new IPMI user name. Defaults to one in driver_info. :raises: ClientError on error reported from a server :raises: VersionNotSupported if requested api_version is not supported :raises: *requests* library exception on connection problems. """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) if new_ipmi_username and not new_ipmi_password: raise ValueError( _("Setting IPMI user name requires a new password")) params = {'new_ipmi_username': new_ipmi_username, 'new_ipmi_password': new_ipmi_password} self.request('post', '/introspection/%s' % uuid, params=params)
def list_statuses(self, marker=None, limit=None): """List introspection statuses. Supports pagination via the marker and limit params. The items are sorted by the server according to the `started_at` attribute, newer items first. :param marker: pagination maker, UUID or None :param limit: pagination limit, int or None :raises: :py:class:`ironic_inspector_client.ClientError` on error reported from a server :raises: :py:class:`ironic_inspector_client.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: a list of status dictionaries with the keys: * `error` an error string or None, * `finished` whether introspection was finished, * `finished_at` an ISO8601 timestamp or None, * `links` with a self-link URL, * `started_at` an ISO8601 timestamp, * `uuid` the node UUID """ if not (marker is None or isinstance(marker, six.string_types)): raise TypeError(_('Expected a string value of the marker, got ' '%s instead') % marker) if not (limit is None or isinstance(limit, int)): raise TypeError(_('Expected an integer value of the limit, got ' '%s instead') % limit) params = { 'marker': marker, 'limit': limit, } response = self.request('get', '/introspection', params=params) return response.json()['introspection']
def wait_for_finish(self, uuids, retry_interval=DEFAULT_RETRY_INTERVAL, max_retries=DEFAULT_MAX_RETRIES, sleep_function=time.sleep): """Wait for introspection finishing for given nodes. :param uuids: collection of node UUIDs or names. :param retry_interval: sleep interval between retries. :param max_retries: maximum number of retries. :param sleep_function: function used for sleeping between retries. :raises: :py:class:`.WaitTimeoutError` on timeout :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: dictionary UUID -> status (the same as in get_status). """ result = {} # Number of attempts = number of retries + first attempt for attempt in range(max_retries + 1): new_active_uuids = [] for uuid in uuids: status = self.get_status(uuid) if status.get('finished'): result[uuid] = status else: new_active_uuids.append(uuid) if new_active_uuids: if attempt != max_retries: uuids = new_active_uuids LOG.debug( 'Still waiting for introspection results for ' '%(count)d nodes, attempt %(attempt)d of ' '%(total)d', { 'count': len(new_active_uuids), 'attempt': attempt + 1, 'total': max_retries + 1 }) sleep_function(retry_interval) else: return result raise WaitTimeoutError( _("Timeout while waiting for introspection " "of nodes %s") % new_active_uuids)
def _check_parameters(self, node_id, uuid): """Deprecate uuid parameters. Check the parameters and return a deprecation warning if the uuid parameter is present. """ node_id = node_id or uuid if not isinstance(node_id, six.string_types): raise TypeError( _("Expected string for node_id argument, got %r") % node_id) if uuid: warnings.warn("Parameter uuid is deprecated and will be " "removed in future releases, please use " "node_id instead.", DeprecationWarning) return node_id
def reprocess(self, uuid): """Reprocess stored introspection data. :param uuid: node UUID or name. :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :raises: TypeError if uuid is not a string. """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got" " %r instead") % uuid) return self.request('post', '/introspection/%s/data/unprocessed' % uuid)
def _check_parameters(self, node_id, uuid): """Deprecate uuid parameters. Check the parameters and return a deprecation warning if the uuid parameter is present. """ node_id = node_id or uuid if not isinstance(node_id, six.string_types): raise TypeError( _("Expected string for node_id argument, got %r") % node_id) if uuid: warnings.warn( "Parameter uuid is deprecated and will be " "removed in future releases, please use " "node_id instead.", DeprecationWarning) return node_id
def get_interface_data(self, node_ident, interface, field_sel): """Get interface data for the input node and interface To get LLDP data, collection must be enabled by the kernel parameter ``ipa-collect-lldp=1``, and the inspector plugin ``basic_lldp`` must be enabled. :param node_ident: node UUID or name :param interface: interface name :param field_sel: list of all fields for which to get data :returns: interface data in OrderedDict :raises: ValueError if interface is not found. """ # Use OrderedDict to maintain order of user-entered fields iface_data = collections.OrderedDict() data = self.get_data(node_ident) all_interfaces = data.get('all_interfaces', []) # Make sure interface name is valid if interface not in all_interfaces: raise ValueError( _("Interface %s was not found on this node") % interface) # If lldp data not available this will still return interface, # mac, node_ident etc. lldp_proc = all_interfaces[interface].get('lldp_processed', {}) for f in field_sel: if f == 'node_ident': iface_data[f] = node_ident elif f == 'interface': iface_data[f] = interface elif f == 'mac': iface_data[f] = all_interfaces[interface].get(f) elif f == 'switch_port_vlan_ids': iface_data[f] = [ item['id'] for item in lldp_proc.get('switch_port_vlans', []) ] else: iface_data[f] = lldp_proc.get(f) return iface_data
def get_interface_data(self, node_ident, interface, field_sel): """Get interface data for the input node and interface To get LLDP data, collection must be enabled by the kernel parameter ``ipa-collect-lldp=1``, and the inspector plugin ``basic_lldp`` must be enabled. :param node_ident: node UUID or name :param interface: interface name :param field_sel: list of all fields for which to get data :returns: interface data in OrderedDict :raises: ValueError if interface is not found. """ # Use OrderedDict to maintain order of user-entered fields iface_data = collections.OrderedDict() data = self.get_data(node_ident) all_interfaces = data.get('all_interfaces', []) # Make sure interface name is valid if interface not in all_interfaces: raise ValueError( _("Interface %s was not found on this node") % interface) # If lldp data not available this will still return interface, # mac, node_ident etc. lldp_proc = all_interfaces[interface].get('lldp_processed', {}) for f in field_sel: if f == 'node_ident': iface_data[f] = node_ident elif f == 'interface': iface_data[f] = interface elif f == 'mac': iface_data[f] = all_interfaces[interface].get(f) elif f == 'switch_port_vlan_ids': iface_data[f] = [item['id'] for item in lldp_proc.get('switch_port_vlans', [])] else: iface_data[f] = lldp_proc.get(f) return iface_data
def get_data(self, uuid, raw=False): """Get introspection data from the last introspection of a node. :param uuid: node UUID. :param raw: whether to return raw binary data or parsed JSON data :returns: bytes or a dict depending on the 'raw' argument :raises: ClientError on error reported from a server :raises: VersionNotSupported if requested api_version is not supported :raises: *requests* library exception on connection problems. :raises: TypeError if uuid is not a string """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) resp = self.request('get', '/introspection/%s/data' % uuid) if raw: return resp.content else: return resp.json()
def get_status(self, uuid): """Get introspection status for a node. :param uuid: node UUID or name. :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :return: dictionary with the keys: `error` an error string or None, `finished` True/False, `finished_at` an ISO8601 timestamp or None, `links` with a self-link URL, `started_at` an ISO8601 timestamp, `uuid` the node UUID """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) return self.request('get', '/introspection/%s' % uuid).json()
def get_data(self, uuid, raw=False): """Get introspection data from the last introspection of a node. :param uuid: node UUID or name. :param raw: whether to return raw binary data or parsed JSON data :returns: bytes or a dict depending on the 'raw' argument :raises: :py:class:`.ClientError` on error reported from a server :raises: :py:class:`.VersionNotSupported` if requested api_version is not supported :raises: *requests* library exception on connection problems. :raises: TypeError if uuid is not a string """ if not isinstance(uuid, six.string_types): raise TypeError( _("Expected string for uuid argument, got %r") % uuid) resp = self.request('get', '/introspection/%s/data' % uuid) if raw: return resp.content else: return resp.json()
def __init__(self, response): # inspector returns error message in body msg = response.content.decode(_ERROR_ENCODING) try: msg = json.loads(msg) except ValueError: LOG.debug('Old style error response returned, assuming ' 'ironic-discoverd') except TypeError: LOG.exception('Bad error response from Ironic Inspector') else: try: msg = msg['error']['message'] except KeyError as exc: LOG.error( 'Invalid error response from Ironic Inspector: ' '%(msg)s (missing key %(key)s)', { 'msg': msg, 'key': exc }) # It's surprisingly common to try accessing ironic URL with # ironic-inspector-client, handle this case try: msg = msg['error_message'] except KeyError: pass else: msg = _('Received Ironic-style response %s. Are you ' 'trying to access Ironic URL instead of Ironic ' 'Inspector?') % msg except TypeError: LOG.exception('Bad error response from Ironic Inspector') LOG.debug('Inspector returned error "%(msg)s" (HTTP %(code)s)', { 'msg': msg, 'code': response.status_code }) super(ClientError, self).__init__(msg, response=response)
def __init__(self, service_type): self.service_type = service_type msg = _('Endpoint of type %s was not found in the service catalog ' 'and was not provided explicitly') % service_type super(Exception, self).__init__(msg)
def _parse_version(api_version): try: return tuple(int(x) for x in api_version.split('.')) except (ValueError, TypeError): raise ValueError(_("Malformed API version: expect tuple, string " "in form of X.Y or integer"))