def _get_ksclient(token=None): auth_url = CONF.keystone_authtoken.www_authenticate_uri if not auth_url: raise exception.KeystoneFailure(_('Keystone API endpoint is missing')) auth_version = CONF.keystone_authtoken.auth_version api_v3 = _is_apiv3(auth_url, auth_version) if api_v3: from keystoneclient.v3 import client else: from keystoneclient.v2_0 import client auth_url = get_keystone_url(auth_url, auth_version) try: if token: return client.Client(token=token, auth_url=auth_url) else: return client.Client( username=CONF.keystone_authtoken.admin_user, password=CONF.keystone_authtoken.admin_password, tenant_name=CONF.keystone_authtoken.admin_tenant_name, region_name=CONF.keystone.region_name, auth_url=auth_url) except ksexception.Unauthorized: raise exception.KeystoneUnauthorized() except ksexception.AuthorizationFailure as err: raise exception.KeystoneFailure(_('Could not authorize in Keystone:' ' %s') % err)
def update_port_address(port_id, address): """Update a port's mac address. :param port_id: Neutron port id. :param address: new MAC address. :raises: FailedToUpdateMacOnPort """ client = get_client() port_req_body = {'port': {'mac_address': address}} try: msg = (_("Failed to get the current binding on Neutron " "port %s.") % port_id) port = client.show_port(port_id).get('port', {}) binding_host_id = port.get('binding:host_id') binding_profile = port.get('binding:profile') if binding_host_id: # Unbind port before we update it's mac address, because you can't # change a bound port's mac address. msg = (_("Failed to remove the current binding from " "Neutron port %s, while updating its MAC " "address.") % port_id) unbind_neutron_port(port_id, client=client) port_req_body['port']['binding:host_id'] = binding_host_id port_req_body['port']['binding:profile'] = binding_profile msg = (_("Failed to update MAC address on Neutron port %s.") % port_id) client.update_port(port_id, port_req_body) except (neutron_exceptions.NeutronClientException, exception.NetworkError): LOG.exception(msg) raise exception.FailedToUpdateMacOnPort(port_id=port_id)
def _verify_security_groups(security_groups, client): """Verify that the security groups exist. :param security_groups: a list of security group UUIDs; may be None or empty :param client: Neutron client :raises: NetworkError """ if not security_groups: return try: neutron_sec_groups = ( client.list_security_groups().get('security_groups', [])) except neutron_exceptions.NeutronClientException as e: msg = (_("Could not retrieve security groups from neutron: %(exc)s") % {'exc': e}) LOG.exception(msg) raise exception.NetworkError(msg) existing_sec_groups = [sec_group['id'] for sec_group in neutron_sec_groups] missing_sec_groups = set(security_groups) - set(existing_sec_groups) if missing_sec_groups: msg = (_('Could not find these security groups ' '(specified via iotronic ' 'config) in neutron: %(ir-sg)s') % {'ir-sg': list(missing_sec_groups)}) LOG.error(msg) raise exception.NetworkError(msg)
def parse_headers(headers, default_version, latest_version): """Determine the API version requested based on the headers supplied. :param headers: webob headers :param default_version: version to use if not specified in headers :param latest_version: version to use if latest is requested :returns: a tuple of (major, minor) version numbers :raises: webob.HTTPNotAcceptable """ version_str = headers.get(Version.string, default_version) if version_str.lower() == 'latest': parse_str = latest_version else: parse_str = version_str try: version = tuple(int(i) for i in parse_str.split('.')) except ValueError: version = () if len(version) != 2: raise exc.HTTPNotAcceptable(_( "Invalid value for %s header") % Version.string) return version
def save(self, context): """Save the changed fields back to the store. This is optional for subclasses, but is presented here in the base class for consistency among those that do. """ raise NotImplementedError(_("Cannot save anything in the base class"))
def unbind_neutron_port(port_id, client=None): """Unbind a neutron port Remove a neutron port's binding profile and host ID so that it returns to an unbound state. :param port_id: Neutron port ID. :param client: Optional a Neutron client object. :raises: NetworkError """ if not client: client = get_client() body = {'port': {'binding:host_id': '', 'binding:profile': {}}} try: client.update_port(port_id, body) # NOTE(vsaienko): Ignore if port was deleted before calling vif detach. except neutron_exceptions.PortNotFoundClient: LOG.info('Port %s was not found while unbinding.', port_id) except neutron_exceptions.NeutronClientException as e: msg = (_('Unable to clear binding profile for ' 'neutron port %(port_id)s. Error: ' '%(err)s') % {'port_id': port_id, 'err': e}) LOG.exception(msg) raise exception.NetworkError(msg)
def get_service_url(service_type='iot', endpoint_type='internal'): """Wrapper for get service url from keystone service catalog. Given a service_type and an endpoint_type, this method queries keystone service catalog and provides the url for the desired endpoint. :param service_type: the keystone service for which url is required. :param endpoint_type: the type of endpoint for the service. :returns: an http/https url for the desired endpoint. """ ksclient = _get_ksclient() if not ksclient.has_service_catalog(): raise exception.KeystoneFailure(_('No Keystone service catalog ' 'loaded')) try: endpoint = ksclient.service_catalog.url_for( service_type=service_type, endpoint_type=endpoint_type, region_name=CONF.keystone.region_name) except ksexception.EndpointNotFound: raise exception.CatalogNotFound(service_type=service_type, endpoint_type=endpoint_type) return endpoint
def validate_limit(limit): if limit is None: return CONF.api.max_limit if limit <= 0: raise wsme.exc.ClientSideError(_("Limit must be positive")) return min(CONF.api.max_limit, limit)
def apply_jsonpatch(doc, patch): for p in patch: if p['op'] == 'add' and p['path'].count('/') == 1: if p['path'].lstrip('/') not in doc: msg = _('Adding a new attribute (%s) to the root of ' ' the resource is not allowed') raise wsme.exc.ClientSideError(msg % p['path']) return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
def obj_load_attr(self, attrname): """Load an additional attribute from the real object. This should use self._conductor, and cache any data that might be useful for future load operations. """ raise NotImplementedError( _("Cannot load '%(attrname)s' in the base class") % {'attrname': attrname})
def obj_attr_is_set(self, attrname): """Test object to see if attrname is present. Returns True if the named attribute has a value set, or False if not. Raises AttributeError if attrname is not a valid attribute for this object. """ if attrname not in self.obj_fields: raise AttributeError( _("%(objname)s object has no attribute '%(attrname)s'") % {'objname': self.obj_name(), 'attrname': attrname}) return hasattr(self, get_attrname(attrname))
def get(self, key, value=NotSpecifiedSentinel): """For backwards-compatibility with dict-based objects. NOTE(danms): May be removed in the future. """ if key not in self.obj_fields: raise AttributeError( _("'%(objclass)s' object has no attribute '%(attrname)s'") % {'objclass': self.__class__, 'attrname': key}) if value != NotSpecifiedSentinel and not self.obj_attr_is_set(key): return value else: return self[key]
def datetime_or_none(dt): """Validate a datetime or None value.""" if dt is None: return None elif isinstance(dt, datetime.datetime): if dt.utcoffset() is None: # NOTE(danms): Legacy objects from sqlalchemy are stored in UTC, # but are returned without a timezone attached. # As a transitional aid, assume a tz-naive object is in UTC. return dt.replace(tzinfo=iso8601.UTC) else: return dt raise ValueError(_("A datetime.datetime is required here"))
def _check_version(self, version, headers=None): if headers is None: headers = {} # ensure that major version in the URL matches the header if version.major != BASE_VERSION: raise exc.HTTPNotAcceptable(_( "Mutually exclusive versions requested. Version %(ver)s " "requested but not supported by this service. The supported " "version range is: [%(min)s, %(max)s].") % { 'ver': version, 'min': versions.MIN_VERSION_STRING, 'max': versions.MAX_VERSION_STRING }, headers=headers) # ensure the minor version is within the supported range if version < MIN_VER or version > MAX_VER: raise exc.HTTPNotAcceptable(_( "Version %(ver)s was requested but the minor version is not " "supported by this service. The supported version range is: " "[%(min)s, %(max)s].") % { 'ver': version, 'min': versions.MIN_VERSION_STRING, 'max': versions.MAX_VERSION_STRING }, headers=headers)
def check_for_invalid_fields(fields, object_fields): """Check for requested non-existent fields. Check if the user requested non-existent fields. :param fields: A list of fields requested by the user :object_fields: A list of fields supported by the object. :raises: InvalidParameterValue if invalid fields were requested. """ invalid_fields = set(fields) - set(object_fields) if invalid_fields: raise exception.InvalidParameterValue( _('Field(s) "%s" are not valid') % ', '.join(invalid_fields))
def update_service(self, service_id, values): # NOTE(dtantsur): this can lead to very strange errors if 'uuid' in values: msg = _("Cannot overwrite UUID for an existing Service.") raise exception.InvalidParameterValue(err=msg) try: return self._do_update_service(service_id, values) except db_exc.DBDuplicateEntry as e: if 'name' in e.columns: raise exception.DuplicateName(name=values['name']) elif 'uuid' in e.columns: raise exception.ServiceAlreadyExists(uuid=values['uuid']) else: raise e
def update_injection_plugin(self, plugin_injection_id, values): if 'uuid' in values: msg = _("Cannot overwrite UUID for an existing Plugin.") raise exception.InvalidParameterValue(err=msg) try: return self._do_update_injection_plugin( plugin_injection_id, values) except db_exc.DBDuplicateEntry as e: if 'name' in e.columns: raise exception.DuplicateName(name=values['name']) elif 'uuid' in e.columns: raise exception.PluginAlreadyExists(uuid=values['uuid']) else: raise e
def check_object_version(server, client): try: client_major, _client_minor = client.split('.') server_major, _server_minor = server.split('.') client_minor = int(_client_minor) server_minor = int(_server_minor) except ValueError: raise exception.IncompatibleObjectVersion( _('Invalid version string')) if client_major != server_major: raise exception.IncompatibleObjectVersion( dict(client=client_major, server=server_major)) if client_minor > server_minor: raise exception.IncompatibleObjectVersion( dict(client=client_minor, server=server_minor))
def _paginate_query(model, limit=None, marker=None, sort_key=None, sort_dir=None, query=None): if not query: query = model_query(model) sort_keys = ['id'] if sort_key and sort_key not in sort_keys: sort_keys.insert(0, sort_key) try: query = db_utils.paginate_query(query, model, limit, sort_keys, marker=marker, sort_dir=sort_dir) except db_exc.InvalidSortKey: raise exception.InvalidParameterValue( _('The sort_key value "%(key)s" is an invalid field for sorting') % {'key': sort_key}) return query.all()
def __init__(self, app, conf, public_api_routes=None): api_routes = [] if public_api_routes is None else public_api_routes self._iotronic_app = app # TODO(mrda): Remove .xml and ensure that doesn't result in a # 401 Authentication Required instead of 404 Not Found route_pattern_tpl = '%s(\.json|\.xml)?$' try: self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl) for route_tpl in api_routes] except re.error as e: msg = _('Cannot compile public API routes: %s') % e LOG.error(msg) raise exception.ConfigInvalid(error_msg=msg) super(AuthTokenMiddleware, self).__init__(app, conf)
def replacement_start_response(status, headers, exc_info=None): """Overrides the default response to make errors parsable.""" try: status_code = int(status.split(' ')[0]) state['status_code'] = status_code except (ValueError, TypeError): # pragma: nocover raise Exception(_( 'ErrorDocumentMiddleware received an invalid ' 'status %s') % status) else: if (state['status_code'] // 100) not in (2, 3): # Remove some headers so we can replace them later # when we have the full error message and can # compute the length. headers = [(h, v) for (h, v) in headers if h not in ('Content-Length', 'Content-Type') ] # Save the headers in case we need to modify them. state['headers'] = headers return start_response(status, headers, exc_info)
def ssh_connect(connection): """Method to connect to a remote system using ssh protocol. :param connection: a dict of connection parameters. :returns: paramiko.SSHClient -- an active ssh connection. :raises: SSHConnectFailed """ try: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) key_contents = connection.get('key_contents') if key_contents: data = six.moves.StringIO(key_contents) if "BEGIN RSA PRIVATE" in key_contents: pkey = paramiko.RSAKey.from_private_key(data) elif "BEGIN DSA PRIVATE" in key_contents: pkey = paramiko.DSSKey.from_private_key(data) else: # Can't include the key contents - secure material. raise ValueError(_("Invalid private key")) else: pkey = None ssh.connect(connection.get('host'), username=connection.get('username'), password=connection.get('password'), port=connection.get('port', 22), pkey=pkey, key_filename=connection.get('key_filename'), timeout=connection.get('timeout', 10)) # send TCP keepalive packets every 20 seconds ssh.get_transport().set_keepalive(20) except Exception as e: LOG.debug("SSH connect failed: %s" % e) raise exception.SSHConnectFailed(host=connection.get('host')) return ssh
class FailedToParseSensorData(IotronicException): message = _("Failed to parse sensor data for node %(node)s. " "Error: %(error)s")
class InsufficientDiskSpace(IotronicException): message = _("Disk volume where '%(path)s' is located doesn't have " "enough disk space. Required %(required)d MiB, " "only %(actual)d MiB available space present.")
def save(self, context): """Save is not supported by Conductor objects.""" raise NotImplementedError( _('Cannot update a conductor record directly.'))
class NodeNotLocked(Invalid): message = _("Node %(node)s found not to be locked on release")
class HardwareInspectionFailure(IotronicException): message = _("Failed to inspect hardware. Reason: %(error)s")
class PathNotFound(IotronicException): message = _("Path %(dir)s does not exist.")
class SNMPFailure(IotronicException): message = _("SNMP operation '%(operation)s' failed: %(error)s")
class IRMCOperationError(IotronicException): message = _('iRMC %(operation)s failed. Reason: %(error)s')
def validator(val, objclass=objclass): if val is None or isinstance(val, objclass): return val raise ValueError(_("An object of class %s is required here") % objclass)
class ImageCreationFailed(IotronicException): message = _('Creating %(image_type)s image failed: %(error)s')
def validator(val, objclass=objclass): if val is None or isinstance(val, objclass): return val raise ValueError( _("An object of class %s is required here") % objclass)
from iotronic.objects import base as objects_base from iotronic.openstack.common import service service_opts = [ cfg.IntOpt('periodic_interval', default=60, help='Seconds between running periodic tasks.'), cfg.StrOpt('host', default=socket.getfqdn(), help='Name of this board. This can be an opaque identifier. ' 'It is not necessarily a hostname, FQDN, or IP address. ' 'However, the board name must be valid within ' 'an AMQP key, and if using ZeroMQ, a valid ' 'hostname, FQDN, or IP address.'), cfg.StrOpt('notification_level', choices=[('debug', _('"debug" level')), ('info', _('"info" level')), ('warning', _('"warning" level')), ('error', _('"error" level')), ('critical', _('"critical" level'))], help=_('Specifies the minimum level for which to send ' 'notifications. If not set, no notifications will ' 'be sent. The default is for this option to be unset.')), ] cfg.CONF.register_opts(service_opts) LOG = log.getLogger(__name__) class RPCService(service.Service):
def validate_sort_dir(sort_dir): if sort_dir not in ['asc', 'desc']: raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. " "Acceptable values are " "'asc' or 'desc'") % sort_dir) return sort_dir
class ConfigInvalid(IotronicException): message = _("Invalid configuration file. %(error_msg)s")
class NoFreeConductorWorker(TemporaryFailure): message = _('Requested action cannot be performed due to lack of free ' 'conductor workers.') code = 503 # Service Unavailable (temporary).
class SwiftOperationError(IotronicException): message = _("Swift operation '%(operation)s' failed: %(error)s")
class IloOperationNotSupported(IotronicException): message = _("%(operation)s not supported. error: %(error)s")
class FileSystemNotSupported(IotronicException): message = _("Failed to create a file system. " "File system %(fs)s is not supported.")
class DracClientError(DracRequestFailed): message = _('DRAC client failed. ' 'Last error (cURL error code): %(last_error)s, ' 'fault string: "%(fault_string)s" ' 'response_code: %(response_code)s')
class VirtualBoxOperationFailed(IotronicException): message = _("VirtualBox operation '%(operation)s' failed. " "Error: %(error)s")
class DracOperationFailed(DracRequestFailed): message = _('DRAC operation failed. Message: %(message)s')
class NodeCleaningFailure(IotronicException): message = _("Failed to clean node %(node)s: %(reason)s")
class DracUnexpectedReturnValue(DracRequestFailed): message = _('DRAC operation yielded return value %(actual_return_value)s ' 'that is neither error nor expected %(expected_return_value)s')
class DirectoryNotWritable(IotronicException): message = _("Directory %(dir)s is not writable.")
class DracPendingConfigJobExists(IotronicException): message = _('Another job with ID %(job_id)s is already created ' 'to configure %(target)s. Wait until existing job ' 'is completed or is canceled')
class DriverLoadError(IotronicException): message = _("Driver %(driver)s could not be loaded. Reason: %(reason)s.")
class DracInvalidFilterDialect(IotronicException): message = _('Invalid filter dialect \'%(invalid_filter)s\'. ' 'Supported options are %(supported)s')
class NoConsolePid(ConsoleError): message = _("Could not find pid in pid file %(pid_path)s")
class ConsoleSubprocessFailed(ConsoleError): message = _("Console subprocess failed to start. %(error)s")
class PasswordFileFailedToCreate(IotronicException): message = _("Failed to create the password file. %(error)s")
class NotAuthorized(IotronicException): message = _("Not authorized.") code = 403
class IloOperationError(IotronicException): message = _("%(operation)s failed, error: %(error)s")
import importlib from threading import Thread import ssl import os import signal from autobahn.asyncio.component import Component LOG = logging.getLogger(__name__) service_opts = [ cfg.StrOpt('notification_level', choices=[('debug', _('"debug" level')), ('info', _('"info" level')), ('warning', _('"warning" level')), ('error', _('"error" level')), ('critical', _('"critical" level'))], help=_('Specifies the minimum level for which to send ' 'notifications. If not set, no notifications will ' 'be sent. The default is for this option to be unset.')), ] wamp_opts = [ cfg.StrOpt('wamp_transport_url', default='ws://localhost:8181/', help=('URL of wamp broker')), cfg.StrOpt('wamp_realm', default='s4t',
def save(self, context): """Save is not supported by WampAgent objects.""" raise NotImplementedError( _('Cannot update a wampagent record directly.'))