def load_paste_app(app_name): """Builds and returns a WSGI app from a paste config file. :param app_name: Name of the application to load :raises ConfigFilesNotFoundError when config file cannot be located :raises RuntimeError when application cannot be loaded from config file """ config_path = cfg.CONF.find_file(cfg.CONF.api_paste_config) if not config_path: raise cfg.ConfigFilesNotFoundError( config_files=[cfg.CONF.api_paste_config]) config_path = os.path.abspath(config_path) LOG.info(_("Config paste file: %s"), config_path) try: app = deploy.loadapp("config:%s" % config_path, name=app_name) except (LookupError, ImportError): msg = (_("Unable to load %(app_name)s from " "configuration file %(config_path)s.") % { 'app_name': app_name, 'config_path': config_path }) LOG.exception(msg) raise RuntimeError(msg) return app
def _load_all_extensions_from_path(self, path): # Sorting the extension list makes the order in which they # are loaded predictable across a cluster of load-balanced # bigbang Servers for f in sorted(os.listdir(path)): try: LOG.debug(_('Loading extension file: %s'), f) mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) ext_path = os.path.join(path, f) if file_ext.lower() == '.py' and not mod_name.startswith('_'): mod = imp.load_source(mod_name, ext_path) ext_name = mod_name[0].upper() + mod_name[1:] new_ext_class = getattr(mod, ext_name, None) if not new_ext_class: LOG.warning(_('Did not find expected name ' '"%(ext_name)s" in %(file)s'), {'ext_name': ext_name, 'file': ext_path}) continue new_ext = new_ext_class() self.add_extension(new_ext) except Exception as exception: LOG.warning(_("Extension file %(f)s wasn't loaded due to " "%(exception)s"), {'f': f, 'exception': exception})
def __init__(self, application, ext_mgr=None): self.ext_mgr = (ext_mgr or ExtensionManager(get_extensions_path())) mapper = routes.Mapper() # extended resources for resource in self.ext_mgr.get_resources(): path_prefix = resource.path_prefix if resource.parent: path_prefix = (resource.path_prefix + "/%s/{%s_id}" % (resource.parent["collection_name"], resource.parent["member_name"])) LOG.debug(_('Extended resource: %s'), resource.collection) for action, method in (resource.collection_actions).items(): conditions = dict(method=[method]) path = "/%s/%s" % (resource.collection, action) with mapper.submapper(controller=resource.controller, action=action, path_prefix=path_prefix, conditions=conditions) as submap: submap.connect(path) submap.connect("%s.:(format)" % path) mapper.resource(resource.collection, resource.collection, controller=resource.controller, member=resource.member_actions, parent_resource=resource.parent, path_prefix=path_prefix) # extended actions action_controllers = self._action_ext_controllers(application, self.ext_mgr, mapper) for action in self.ext_mgr.get_actions(): LOG.debug(_('Extended action: %s'), action.action_name) controller = action_controllers[action.collection] controller.add_action(action.action_name, action.handler) # extended requests req_controllers = self._request_ext_controllers(application, self.ext_mgr, mapper) for request_ext in self.ext_mgr.get_request_extensions(): LOG.debug(_('Extended request: %s'), request_ext.key) controller = req_controllers[request_ext.key] controller.add_handler(request_ext.handler) self._router = routes.middleware.RoutesMiddleware(self._dispatch, mapper) super(ExtensionMiddleware, self).__init__(application)
def extend_resources(self, version, attr_map): """Extend resources with additional resources or attributes. :param: attr_map, the existing mapping from resource name to attrs definition. After this function, we will extend the attr_map if an extension wants to extend this map. """ update_exts = [] processed_exts = set() exts_to_process = self.extensions.copy() # Iterate until there are unprocessed extensions or if no progress # is made in a whole iteration while exts_to_process: processed_ext_count = len(processed_exts) for ext_name, ext in exts_to_process.items(): if not hasattr(ext, 'get_extended_resources'): del exts_to_process[ext_name] continue if hasattr(ext, 'update_attributes_map'): update_exts.append(ext) if hasattr(ext, 'get_required_extensions'): # Process extension only if all required extensions # have been processed already required_exts_set = set(ext.get_required_extensions()) if required_exts_set - processed_exts: continue try: extended_attrs = ext.get_extended_resources(version) for resource, resource_attrs in extended_attrs.items(): if attr_map.get(resource): attr_map[resource].update(resource_attrs) else: attr_map[resource] = resource_attrs except AttributeError: LOG.exception(_("Error fetching extended attributes for " "extension '%s'"), ext.get_name()) processed_exts.add(ext_name) del exts_to_process[ext_name] if len(processed_exts) == processed_ext_count: # Exit loop as no progress was made break if exts_to_process: # NOTE(salv-orlando): Consider whether this error should be fatal LOG.error(_("It was impossible to process the following " "extensions: %s because of missing requirements."), ','.join(exts_to_process.keys())) # Extending extensions' attributes map. for ext in update_exts: ext.update_attributes_map(attr_map)
def __call__(self, req): """Call the method specified in req.environ by RoutesMiddleware.""" arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] method = getattr(self, action) del arg_dict['controller'] del arg_dict['action'] if 'format' in arg_dict: del arg_dict['format'] arg_dict['request'] = req result = method(**arg_dict) if isinstance(result, dict) or result is None: if result is None: status = 204 content_type = '' body = None else: status = 200 content_type = req.best_match_content_type() body = self._serialize(result, content_type) response = webob.Response(status=status, content_type=content_type, body=body) msg_dict = dict(url=req.url, status=response.status_int) msg = _("%(url)s returned with HTTP %(status)d") % msg_dict LOG.debug(msg) return response else: return result
def _get_custom_cache_region(expiration_time=WEEK, backend=None, url=None): """Create instance of oslo_cache client. For backends you can pass specific parameters by kwargs. For 'dogpile.cache.memcached' backend 'url' parameter must be specified. :param backend: backend name :param expiration_time: interval in seconds to indicate maximum time-to-live value for each key :param url: memcached url(s) """ region = cache.create_region() region_params = {} if expiration_time != 0: region_params['expiration_time'] = expiration_time if backend == 'oslo_cache.dict': region_params['arguments'] = {'expiration_time': expiration_time} elif backend == 'dogpile.cache.memcached': region_params['arguments'] = {'url': url} else: raise RuntimeError( _('old style configuration can use ' 'only dictionary or memcached backends')) region.configure(backend, **region_params) return region
def __call__(self, environ, start_response): r"""Subclasses will probably want to implement __call__ like this: @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): # Any of the following objects work as responses: # Option 1: simple string res = 'message\n' # Option 2: a nicely formatted HTTP exception page res = exc.HTTPForbidden(explanation='Nice try') # Option 3: a webob Response object (in case you need to play with # headers, or you want to be treated like an iterable, or or or) res = Response(); res.app_iter = open('somefile') # Option 4: any wsgi app to be run next res = self.application # Option 5: you can get a Response object for a wsgi app, too, to # play with headers etc res = req.get_response(self.application) # You can then just return your response... return res # ... or set req.response and return None. req.response = res See the end of http://pythonpaste.org/webob/modules/dec.html for more info. """ raise NotImplementedError(_('You must implement __call__'))
def show(self, request, id): # NOTE(dprince): the extensions alias is used as the 'id' for show ext = self.extension_manager.extensions.get(id) if not ext: raise webob.exc.HTTPNotFound( _("Extension with alias %s does not exist") % id) return dict(extension=self._translate(ext))
def _run_wsgi(app_name): app = config.load_paste_app(app_name) if not app: LOG.error(_('No known API applications configured.')) return server = wsgi.Server("bigbang") server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host, workers=cfg.CONF.api_workers) # Dump all option values here after all options are parsed cfg.CONF.log_opt_values(LOG, std_logging.DEBUG) LOG.info(_("bigbang service started, listening on %(host)s:%(port)s"), { 'host': cfg.CONF.bind_host, 'port': cfg.CONF.bind_port }) return server
def setup_logging(conf): """Sets up the logging options for a log with supplied name. :param conf: a cfg.ConfOpts object """ product_name = "bigbang" logging.setup(conf, product_name) LOG.info(_("Logging enabled!"))
def get_content_type(self): allowed_types = ("application/json") if "Content-Type" not in self.headers: LOG.debug(_("Missing Content-Type")) return None _type = self.content_type if _type in allowed_types: return _type return None
def validate(cls, value, pattern=None): if value is None: return value super(NameType, cls).validate(value, min_length=2, max_length=255) match = pattern.match(value) if match: return value else: message = _('%s does not match [a-zA-Z0-9][a-zA-Z0-9_.-]') % value raise exception.InvalidValue(message)
def main(): # the configuration will be read into the cfg.CONF global data structure config.init(sys.argv[1:]) if not cfg.CONF.config_file: sys.exit( _("ERROR: Unable to find configuration file via the default" " search paths (~/.bigbang/, ~/, /etc/bigbang/, /etc/) and" " the '--config-file' option!")) try: api_route = service.serve_wsgi(service.BigbangApiService) launcher = common_service.launch(cfg.CONF, api_route, workers=cfg.CONF.api_workers or None) launcher.wait() except KeyboardInterrupt: pass except RuntimeError as e: sys.exit(_("ERROR: %s") % e)
def serve_wsgi(cls): try: service = cls.create() except Exception: with excutils.save_and_reraise_exception(): LOG.exception( _('Unrecoverable error: please check log ' 'for details.')) return service
def __call__(self, request): """WSGI method that controls (de)serialization and method dispatch.""" LOG.info(_("%(method)s %(url)s"), { "method": request.method, "url": request.url }) try: action, args, accept = self.deserializer.deserialize(request) except exception.InvalidContentType: msg = _("Unsupported Content-Type") LOG.exception(_("InvalidContentType: %s"), msg) return Fault(webob.exc.HTTPBadRequest(explanation=msg)) except exception.MalformedRequestBody: msg = _("Malformed request body") LOG.exception(_("MalformedRequestBody: %s"), msg) return Fault(webob.exc.HTTPBadRequest(explanation=msg)) try: action_result = self.dispatch(request, action, args) except webob.exc.HTTPException as ex: LOG.info(_("HTTP exception thrown: %s"), six.text_type(ex)) action_result = Fault(ex, self._fault_body_function) except Exception: LOG.exception(_("Internal error")) # Do not include the traceback to avoid returning it to clients. action_result = Fault(webob.exc.HTTPServerError(), self._fault_body_function) if isinstance(action_result, dict) or action_result is None: response = self.serializer.serialize(action_result, accept, action=action) else: response = action_result try: msg_dict = dict(url=request.url, status=response.status_int) msg = _("%(url)s returned with HTTP %(status)d") % msg_dict except AttributeError as e: msg_dict = dict(url=request.url, exception=e) msg = _("%(url)s returned a fault: %(exception)s") % msg_dict LOG.info(msg) return response
def validate(cls, value, name=None, values=None): if value is None: return None if value.lower() not in set(values): message = _( "%(name)s should be one of: %(values)s") % { 'name': name, 'values': ', '.join(map(six.text_type, values))} raise exception.InvalidValue(message) else: return value.lower()
def add_extension(self, ext): # Do nothing if the extension doesn't check out if not self._check_extension(ext): return alias = ext.get_alias() LOG.info(_('Loaded extension: %s'), alias) if alias in self.extensions: raise exceptions.DuplicatedExtension(alias=alias) self.extensions[alias] = ext
def deserialize(self, datastring, content_type): """Deserialize a string to a dictionary. The string must be in the format of a supported MIME type. """ try: return self.get_deserialize_handler(content_type).deserialize( datastring) except Exception: raise webob.exc.HTTPBadRequest(_("Could not deserialize data"))
def validate(cls, value, minimum=None, maximum=None): if value is None: return None if not isinstance(value, six.integer_types): try: value = int(value) except Exception: LOG.exception(_LE('Failed to convert value to int')) raise exception.InvalidValue(value=value, type=cls.type_name) if minimum is not None and value < minimum: message = _("Integer '%(value)s' is smaller than " "'%(min)d'.") % {'value': value, 'min': minimum} raise exception.InvalidValue(message=message) if maximum is not None and value > maximum: message = _("Integer '%(value)s' is large than " "'%(max)d'.") % {'value': value, 'max': maximum} raise exception.InvalidValue(message=message) return value
class BigbangException(Exception): """Base bigbang Exception To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd with the keyword arguments provided to the constructor. """ message = _("An unknown exception occurred.") code = 500 def __init__(self, message=None, **kwargs): self.kwargs = kwargs if 'code' not in self.kwargs and hasattr(self, 'code'): self.kwargs['code'] = self.code if message: self.message = message try: self.message = self.message % kwargs except KeyError: # kwargs doesn't match a variable in the message # log the issue and the kwargs LOG.exception( _LE('Exception in string format operation, ' 'kwargs: %s') % kwargs) try: ferr = CONF.fatal_exception_format_errors except cfg.NoSuchOptError: ferr = CONF.oslo_versionedobjects.fatal_exception_format_errors if ferr: raise super(BigbangException, self).__init__(self.message) def __str__(self): if six.PY3: return self.message return self.message.encode('utf-8') def __unicode__(self): return self.message def format_message(self): if self.__class__.__name__.endswith('_Remote'): return self.args[0] else: return six.text_type(self)
def _dispatch(req): """Dispatch a Request. Called by self._router after matching the incoming request to a route and putting the information into req.environ. Either returns 404 or the routed WSGI app's response. """ match = req.environ['wsgiorg.routing_args'][1] if not match: language = req.best_match_language() msg = _('The resource could not be found.') msg = i18n.translate(msg, language) return webob.exc.HTTPNotFound(explanation=msg) app = match['controller'] return app
def _serialize(self, data, content_type): """Serialize the given dict to the provided content_type. Uses self._serialization_metadata if it exists, which is a dict mapping MIME types to information needed to serialize to that type. """ _metadata = getattr(type(self), '_serialization_metadata', {}) serializer = Serializer(_metadata) try: return serializer.serialize(data, content_type) except exception.InvalidContentType: msg = _('The requested content type %s is invalid.') % content_type raise webob.exc.HTTPNotAcceptable(msg)
def deserialize_body(self, request, action): try: content_type = request.best_match_content_type() except exception.InvalidContentType: LOG.debug(_("Unrecognized Content-Type provided in request")) return {} if content_type is None: LOG.debug(_("No Content-Type provided in request")) return {} if not len(request.body) > 0: LOG.debug(_("Empty body provided in request")) return {} try: deserializer = self.get_body_deserializer(content_type) except exception.InvalidContentType: with excutils.save_and_reraise_exception(): LOG.debug( _("Unable to deserialize body as provided " "Content-Type")) return deserializer.deserialize(request.body, action)
def __init__(self, app, conf, public_api_routes=None): if public_api_routes is None: public_api_routes = [] route_pattern_tpl = '%s(\.json)?$' try: self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl) for route_tpl in public_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 _load_all_extensions(self): """Load extensions from the configured path. The extension name is constructed from the module_name. If your extension module is named widgets.py, the extension class within that module should be 'Widgets'. See tests/unit/extensions/foxinsocks.py for an example extension implementation. """ for path in self.path.split(':'): if os.path.exists(path): self._load_all_extensions_from_path(path) else: LOG.error(_("Extension path '%s' doesn't exist!"), path)
def validate(cls, value, default=None): if value is None: return elif value.isdigit(): return value elif (value.isalnum() and value[:-1].isdigit() and value[-1] in VALID_UNITS.keys()): return int(value[:-1]) * VALID_UNITS[value[-1]] else: LOG.exception(_LE('Failed to validate image size')) message = _(""" size must be either integer or string of below format. <integer><memory_unit> memory_unit must be 'k','b','m','g' in both cases""") raise exception.InvalidValue(message=message, value=value, type=cls.type_name)
def _check_extension(self, extension): """Checks for required methods in extension objects.""" try: LOG.debug(_('Ext name: %s'), extension.get_name()) LOG.debug(_('Ext alias: %s'), extension.get_alias()) LOG.debug(_('Ext description: %s'), extension.get_description()) LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) LOG.debug(_('Ext updated: %s'), extension.get_updated()) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), six.text_type(ex)) return False return True
def get_id(source_uuid): """Derive a short (12 character) id from a random UUID. The supplied UUID must be a version 4 UUID object. """ if isinstance(source_uuid, six.string_types): source_uuid = uuid.UUID(source_uuid) if source_uuid.version != 4: raise ValueError(_('Invalid UUID version (%d)') % source_uuid.version) # The "time" field of a v4 UUID contains 60 random bits # (see RFC4122, Section 4.4) random_bytes = _to_byte_string(source_uuid.time, 60) # The first 12 bytes (= 60 bits) of base32-encoded output is our data encoded = base64.b32encode(six.b(random_bytes))[:12] if six.PY3: return encoded.lower().decode('utf-8') else: return encoded.lower()
def __init__(self, name, use_ssl=False): """Initialize, but do not start the WSGI server. :param name: The name of the WSGI server given to the loader. :param use_ssl: Wraps the socket in an SSL context if True. :returns: None """ self.name = name self.app = app.load_app() self.workers = (CONF.api.workers or processutils.get_worker_count()) if self.workers and self.workers < 1: raise exception.ConfigInvalid( _("api_workers value of %d is invalid, " "must be greater than 0.") % self.workers) self.server = wsgi.Server(CONF, name, self.app, host=CONF.api.host, port=CONF.api.port, use_ssl=use_ssl)
from oslo_config import cfg from oslo_db import options as db_options from oslo_log import log as logging import oslo_messaging from paste import deploy from bigbang import version from bigbang.common.i18n import _ LOG = logging.getLogger(__name__) core_opts = [ cfg.StrOpt('bind_host', default='0.0.0.0', help=_("The host IP to bind to")), cfg.IntOpt('bind_port', default=9890, help=_("The port to bind to")), cfg.StrOpt('api_paste_config', default="api-paste.ini", help=_("The API paste config file to use")), cfg.StrOpt('api_extensions_path', default="", help=_("The path for API extensions")), cfg.ListOpt('service_plugins', default=['nfvo', 'vnfm'], help=_("The service plugins Tacker will use")), cfg.StrOpt('policy_file', default="policy.json", help=_("The policy file to use")), cfg.StrOpt('auth_strategy', default='keystone',