[composite:osapi_compute] use = call:nova.api.openstack.urlmap:urlmap_factory /: oscomputeversions # version API /v2: oscomputeversion_legacy_v2 # v2 API /v2.1: oscomputeversion_v2 # v2.1 API # v21 is an exactly feature match for v2, except it has more stringent # input validation on the wsgi surface (prevents fuzzing early on the # API). It also provides new features via API microversions which are # opt into for clients. Unaware clients will receive the same frozen # v2 API feature set, but with some relaxed validation /v2/+: openstack_compute_api_v21_legacy_v2_compatible /v2.1/+: openstack_compute_api_v21
[composite:openstack_compute_api_v21] use = call:nova.api.auth:pipeline_factory_v21 # 加载中间件 keystone = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler bees_profiler authtoken keystonecontext osapi_compute_app_v21 # DEPRECATED: The [api]auth_strategy conf option is deprecated and will be # removed in a subsequent release, whereupon this pipeline will be unreachable. noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler bees_profiler noauth2 osapi_compute_app_v21
# Router 类对 WSGI routes 模块进行了简单的封装 classAPIRouterV21(base_wsgi.Router): """Routes requests on the OpenStack API to the appropriate controller and method. The URL mapping based on the plain list `ROUTE_LIST` is built at here. """ def__init__(self, custom_routes=None): """:param custom_routes: the additional routes can be added by this parameter. This parameter is used to test on some fake routes primarily. """ super(APIRouterV21, self).__init__(nova.api.openstack.ProjectMapper())
if custom_routes isNone: custom_routes = tuple()
for path, methods in ROUTE_LIST + custom_routes: # NOTE(alex_xu): The variable 'methods' is a dict in normal, since # the dict includes all the methods supported in the path. But # if the variable 'method' is a string, it means a redirection. # For example, the request to the '' will be redirect to the '/' in # the Nova API. To indicate that, using the target path instead of # a dict. The route entry just writes as "('', '/)". ifisinstance(methods, six.string_types): self.map.redirect(path, methods) continue
for method, controller_info in methods.items(): # TODO(alex_xu): In the end, I want to create single controller # instance instead of create controller instance for each # route. controller = controller_info[0]() action = controller_info[1] self.map.create_route(path, method, controller, action)
# 路由 classRouter(object): """WSGI middleware that maps incoming requests to WSGI apps."""
def__init__(self, mapper): """Create a router for the given routes.Mapper. Each route in `mapper` must specify a 'controller', which is a WSGI app to call. You'll probably want to specify an 'action' as well and have your controller be an object that can route the request to the action-specific method. Examples: mapper = routes.Mapper() sc = ServerController() # Explicit mapping of one route to a controller+action mapper.connect(None, '/svrlist', controller=sc, action='list') # Actions are all implicitly defined mapper.resource('server', 'servers', controller=sc) # Pointing to an arbitrary WSGI app. You can specify the # {path_info:.*} parameter so the target app can be handed just that # section of the URL. mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp()) """ self.map = mapper # 使用 routes 模块关联 mapper 和 _dispatch # routes.middleware.RoutesMiddleware 设置 environ 信息 self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map)
@webob.dec.wsgify(RequestClass=Request) def__call__(self, req): """Route the incoming request to a controller based on self.map. If no match, return a 404. """ # 根据 mapper 将请求路由到 WSGI 应用(资源) # 每个资源会在 __call__ 方法中根据 HTTP 请求的 URL 路由到对应 Controller 上的方法(Action) return self._router
@staticmethod @webob.dec.wsgify(RequestClass=Request) def_dispatch(req): """Dispatch the request to the appropriate controller. 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. """ # 根据 HTTP 请求的 environ 信息找到 URL 对应的 Controller match = req.environ['wsgiorg.routing_args'][1] ifnotmatch: return webob.exc.HTTPNotFound() app = match['controller'] return app
API 实现
nova/api/openstack/compute/ 目录包含每个 API 对应的 Controller 实现,Resource 对象将请求的 API 映射到相应的 Controller 方法上。
defmain(): config.parse_args(sys.argv) # 解析参数 logging.setup(CONF, "nova") # 设置日志 objects.register_all() # 注册 nova object gmr_opts.set_defaults(CONF) # 设置 oslo_reports if'osapi_compute'in CONF.enabled_apis: # NOTE(mriedem): This is needed for caching the nova-compute service # version. objects.Service.enable_min_version_cache() log = logging.getLogger(__name__)
# 生成报告的机制 Guru Meditation Report (GMR) gmr.TextGuruMeditation.setup_autorun(version, conf=CONF)
# oslo_service.ProcessLauncher launcher = service.process_launcher() started = 0 # 根据 paste-ini 文件创建 WSGI 应用 for api in CONF.enabled_apis: should_use_ssl = api in CONF.enabled_ssl_apis try: # nova.service.WSGIService 初始化 WSGI 程序 server = service.WSGIService(api, use_ssl=should_use_ssl) # oslo_service.ProcessLauncher 创建子进程启动服务 launcher.launch_service(server, workers=server.workers or1) started += 1 except exception.PasteAppNotFound as ex: log.warning("%s. ``enabled_apis`` includes bad values. " "Fix to remove this warning.", ex)
if started == 0: log.error('No APIs were started. ' 'Check the enabled_apis config option.') sys.exit(1)
classServer(service.ServiceBase): """Server class to manage a WSGI server, serving a WSGI application.""" ...
defstart(self): """Start serving a WSGI application. :returns: None """ # The server socket object will be closed after server exits, # but the underlying file descriptor will remain open, and will # give bad file descriptor error. So duplicating the socket object, # to keep file descriptor usable.
dup_socket = self._socket.dup() dup_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # sockets can hang around forever without keepalive dup_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# NOTE(danms): This event and watchdog thread are used to send # call-monitoring heartbeats for this message while the call # is executing if it runs for some time. The thread will wait # for the event to be signaled, which we do explicitly below # after dispatching the method call. completion_event = eventletutils.Event() watchdog_thread = threading.Thread(target=self._watchdog, args=(completion_event, incoming)) if incoming.client_timeout: # NOTE(danms): The client provided a timeout, so we start # the watchdog thread. If the client is old or didn't send # a timeout, we just never start the watchdog thread. watchdog_thread.start()
found_compatible = False for endpoint in self.endpoints: target = getattr(endpoint, 'target', None) ifnot target: target = self._default_target
ifnot (self._is_namespace(target, namespace) and self._is_compatible(target, version)): continue
ifhasattr(endpoint, method): if self.access_policy.is_allowed(endpoint, method): try: # 分派,调用函数 return self._do_dispatch(endpoint, method, ctxt, args) finally: completion_event.set() if incoming.client_timeout: watchdog_thread.join()
found_compatible = True
if found_compatible: raise NoSuchMethod(method) else: raise UnsupportedVersion(version, method=method)
defcache_images(self, ctxt, aggregate, image_ids): version = '1.21' ifnot self.client.can_send_version(version): raise exception.NovaException('Conductor RPC version pin does not ' 'allow cache_images() to be called') cctxt = self.client.prepare(version=version) cctxt.cast(ctxt, 'cache_images', aggregate=aggregate, image_ids=image_ids)
defcall(self, ctxt, method, **kwargs): """Invoke a method and wait for a reply. See RPCClient.call().""" if self.target.fanout: raise exceptions.InvalidTarget('A call cannot be used with fanout', self.target)