NFVオーケストレーションを実現する手段として、一般的に、OpenStack heatが活用されることが多いと思います。
そこで、今回、heat-api内部構造を調査することにより、その動作メカニズムを理解していきたいと思います。
◼️ そもそも、OpenStack heatって、どんなもの?
Qiita記事「Mitaka版 OpenStack Heat環境をセットアップしてみた」が参考になると思います。
◼️ OpenStack heatでの、大まかな処理の流れ
OpenStack heatは、リクエストに応じて、適切なクラウドオペレーション(novaインスタンス作成など)を行います。
- heat-apiが、ユーザからのリクエストを受け付ける。
- heat-api内部で、必要なコンテキスト処理(token情報の入手等)を実施する。
- heat-api内部で、OpenStack固有の権限設定
policy.json
の記述に従って、実行可否を判定する。 - RabbitMQ等のメッセージング機構を介して、heat-engine側で適切なオーケストレーション処理を実施します。
なお、メッセージング処理機構は、Qiita記事「OpenStackメッセージング機構を探ってみる」を参照ください。
(1) heat-api起動の流れは、どうなっているのか?
こちらのスライド"OpenStack API's and WSGI"からの引用です。
今回、heat-api動作の流れを調査する上で、これらのキーワードを意識しておくことが大切になりそうです。
(2) heat向けのリクエストを、どうやって処理しているのか?
heat-api内部にて、受け付けたリクエストメッセージを適切に処理する場合、WSGI アプリケーションの機構が関与しているようです。そこで、まずは、WSGI アプリケーションの仕組みを理解する必要があります。
過去のQiita記事「OpenStackを活用して、WSGI アプリケーションの仕組みを理解するを参照ください。
◼️ OpenStack heat環境として、/etc/heat/api_paste.ini
ファイルを事前確認しておく
/etc/heat/api_paste.ini
が配備されているので、事前に内容を確認しておく
# heat-api pipeline
[pipeline:heat-api]
pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authtoken context osprofiler apiv1app
... (snip)
[app:apiv1app]
paste.app_factory = heat.common.wsgi:app_factory
heat.app_factory = heat.api.openstack.v1:API
... (snip)
[filter:versionnegotiation]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.openstack:version_negotiation_filter
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = heat
[filter:faultwrap]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.openstack:faultwrap_filter
... (snip)
[filter:context]
paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory
... (snip)
[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware:HTTPProxyToWSGI.factory
# Middleware to set auth_url header appropriately
[filter:authurl]
paste.filter_factory = heat.common.auth_url:filter_factory
# Auth middleware that validates token against keystone
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
... (snip)
# Middleware to set x-openstack-request-id in http response header
[filter:request_id]
paste.filter_factory = oslo_middleware.request_id:RequestId.factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
◼️ heat-api起動処理の動作メカニズムを調査してみる
まず、"heat/cmd/api.py"のPythonスクリプト動作の流れを、確認していきます。
(1) load_paste_appメソッドが起動される
load_paste_app
メソッドは、こんな感じ...
def load_paste_app(app_name=None):
"""Builds and returns a WSGI app from a paste config file.
We assume the last config file specified in the supplied ConfigOpts
object is the paste config file.
:param app_name: name of the application to load
:raises RuntimeError: when config file cannot be located or application
cannot be loaded from config file
"""
if app_name is None:
app_name = cfg.CONF.prog
# append the deployment flavor to the application name,
# in order to identify the appropriate paste pipeline
app_name += _get_deployment_flavor()
conf_file = _get_deployment_config_file()
if conf_file is None:
raise RuntimeError(_("Unable to locate config file [%s]") %
cfg.CONF.paste_deploy['api_paste_config'])
try:
app = wsgi.paste_deploy_app(conf_file, app_name, cfg.CONF)
# Log the options used when starting if we're in debug mode...
if cfg.CONF.debug:
cfg.CONF.log_opt_values(logging.getLogger(app_name),
logging.DEBUG)
return app
except (LookupError, ImportError) as e:
raise RuntimeError(_("Unable to load %(app_name)s from "
"configuration file %(conf_file)s."
"\nGot: %(e)r") % {'app_name': app_name,
'conf_file': conf_file,
'e': e})
そして、app_name
には、初期値として"heat-api"が設定される
(1-1) _get_deployment_flavorメソッドが起動される
/etc/heat/heat.conf
ファイルに、flavorパラメータを設定することが可能だが、"Heat Configuration Sample"によると、デフォルトでは、設定されていないので、特に、何もしない。
[paste_deploy]
#
# From heat.common.config
#
# The flavor to use. (string value)
#flavor = <None>
# The API paste config file to use. (string value)
#api_paste_config = api-paste.ini
(1-2) _get_deployment_config_fileメソッドが起動される
conf_file
には、"/etc/heat/api-paste.ini"ファイルの保管先としてパス情報が設定される。
(2) paste_deploy_appメソッドが起動される
paste_deploy_app
メソッドは、こんな感じ...
def paste_deploy_app(paste_config_file, app_name, conf):
"""Load a WSGI app from a PasteDeploy configuration.
Use deploy.loadapp() to load the app from the PasteDeploy configuration,
ensuring that the supplied ConfigOpts object is passed to the app and
filter constructors.
:param paste_config_file: a PasteDeploy config file
:param app_name: the name of the app/pipeline to load from the file
:param conf: a ConfigOpts object to supply to the app and its filters
:returns: the WSGI app
"""
setup_paste_factories(conf)
try:
return loadwsgi.loadapp("config:%s" % paste_config_file, name=app_name)
finally:
teardown_paste_factories()
(2-1) setup_paste_factoriesメソッドが起動される
setup_paste_factories
メソッドは、こんな感じ...
def setup_paste_factories(conf):
"""Set up the generic paste app and filter factories.
Set things up so that:
paste.app_factory = heat.common.wsgi:app_factory
and
paste.filter_factory = heat.common.wsgi:filter_factory
work correctly while loading PasteDeploy configuration.
The app factories are constructed at runtime to allow us to pass a
ConfigOpts object to the WSGI classes.
:param conf: a ConfigOpts object
"""
global app_factory, filter_factory
app_factory = AppFactory(conf)
filter_factory = FilterFactory(conf)
ここでは、AppFactory
, FilterFactory
クラスのアクティベート処理が行われる
(2-1-1) AppFactoryクラスが呼び出される
AppFactory
クラスは、こんな感じ...
class AppFactory(BasePasteFactory):
"""A Generic paste.deploy app factory.
This requires heat.app_factory to be set to a callable which returns a
WSGI app when invoked. The format of the name is <module>:<callable> e.g.
[app:apiv1app]
paste.app_factory = heat.common.wsgi:app_factory
heat.app_factory = heat.api.cfn.v1:API
The WSGI app constructor must accept a ConfigOpts object and a local config
dict as its two arguments.
"""
KEY = 'heat.app_factory'
def __call__(self, global_conf, **local_conf):
"""The actual paste.app_factory protocol method."""
factory = self._import_factory(local_conf)
return factory(self.conf, **local_conf)
グローバル変数app_factory
に保存される。
この段階で、[app:apiv1app]
セッションでの、paste.app_factory = heat.common.wsgi:app_factory
に基づく、app_factory
メソッドの起動準備が完了することになる。
... (snip)
[app:apiv1app]
paste.app_factory = heat.common.wsgi:app_factory
heat.app_factory = heat.api.openstack.v1:API
この後のdeploy.loadapp
処理の開始に伴い、AppFactory
インスタンスの__call__
メソッドが起動された場合には、_import_factoryメソッドが呼ばれる
なお、local_conf
には、{'heat.app_factory': 'heat.api.openstack.v1:API'}
という情報が設定されている
(2-1-2) FilterFactoryクラスが呼び出される
FilterFactory
クラスは、こんな感じ...
class FilterFactory(AppFactory):
"""A Generic paste.deploy filter factory.
This requires heat.filter_factory to be set to a callable which returns a
WSGI filter when invoked. The format is <module>:<callable> e.g.
[filter:cache]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.middleware.cache:CacheFilter
The WSGI filter constructor must accept a WSGI app, a ConfigOpts object and
a local config dict as its three arguments.
"""
KEY = 'heat.filter_factory'
def __call__(self, global_conf, **local_conf):
"""The actual paste.filter_factory protocol method."""
factory = self._import_factory(local_conf)
def filter(app):
return factory(app, self.conf, **local_conf)
return filter
グローバル変数filter_factory
に保存される。
この段階で、[filter:versionnegotiation]
セッション, [filter:faultwrap]
セッションでの、paste.filter_factory = heat.common.wsgi:filter_factory
に基づく、filter_factory
メソッドの起動準備が完了することになる。
# heat-api pipeline
[pipeline:heat-api]
pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authtoken context osprofiler apiv1app
... (snip)
[filter:versionnegotiation]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.openstack:version_negotiation_filter
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = heat
[filter:faultwrap]
paste.filter_factory = heat.common.wsgi:filter_factory
heat.filter_factory = heat.api.openstack:faultwrap_filter
... (snip)
[filter:context]
paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory
... (snip)
[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware:HTTPProxyToWSGI.factory
# Middleware to set auth_url header appropriately
[filter:authurl]
paste.filter_factory = heat.common.auth_url:filter_factory
# Auth middleware that validates token against keystone
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
... (snip)
# Middleware to set x-openstack-request-id in http response header
[filter:request_id]
paste.filter_factory = oslo_middleware.request_id:RequestId.factory
[filter:osprofiler]
paste.filter_factory = osprofiler.web:WsgiMiddleware.factory
この後のdeploy.loadapp
処理の開始に伴い、FilterFactory
インスタンスの__call__
メソッドが起動された場合には、_import_factoryメソッドが呼ばれる
-
version_negotiation_filterの場合、
local_conf
には、{'heat.filter_factory': 'heat.api.openstack:version_negotiation_filter'}
という情報が設定されている -
faultwrap_filterの場合、
local_conf
には、{'heat.filter_factory': 'heat.api.openstack:faultwrap_filter'}
という情報が設定されている
(2-2) loadappメソッドが起動される
deplory.loadappメソッド起動により、api_paste.ini
ファイルの[pipeline:heat-api]
セッションに記述された内容に従い、PasteDeployment処理が開始される。
# heat-api pipeline
[pipeline:heat-api]
pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authtoken context osprofiler apiv1app
... (snip)
すなわち、 apiv1app
-> osprofiler
-> context
-> authtoken
-> authurl
-> versionnegotiation
-> http_proxy_to_wsgi
-> faultwrap
-> request_id
-> cors
という順番で、PasteDeployment処理が実施されることになる。
(2-2-1) [app:apiv1app]
セクションのheat.common.wsgi:app_factory
メソッドが起動される
heat.common.wsgi:app_factory
メソッド起動により、AppFactoryクラスの__call__
メソッドが起動された場合には ...
-
_import_factory
メソッドが呼ばれる -
local_conf
には、{'heat.app_factory': 'heat.api.openstack.v1:API'}
という情報が設定されている - heat.api.openstack.v1.APIクラスが、インスタンス化されて、ルーティングのマッピング情報が設定される
- 上位クラスのRouterのコンストラクタが起動されて、
self.map
には、ルーティングのマッピング情報が設定される -
heat.api.openstack.v1.APIクラスの
__call__
メソッド起動準備が完了したことになる
class API(wsgi.Router):
"""WSGI router for Heat v1 REST API requests."""
def __init__(self, conf, **local_conf):
self.conf = conf
mapper = routes.Mapper()
default_resource = wsgi.Resource(wsgi.DefaultMethodController(),
wsgi.JSONRequestDeserializer())
def connect(controller, path_prefix, routes):
"""Connects list of routes to given controller with path_prefix.
This function connects the list of routes to the given
controller, prepending the given path_prefix. Then for each URL it
finds which request methods aren't handled and configures those
to return a 405 error. Finally, it adds a handler for the
OPTIONS method to all URLs that returns the list of allowed
methods with 204 status code.
"""
# register the routes with the mapper, while keeping track of which
# methods are defined for each URL
urls = {}
for r in routes:
url = path_prefix + r['url']
methods = r['method']
if isinstance(methods, six.string_types):
methods = [methods]
methods_str = ','.join(methods)
mapper.connect(r['name'], url, controller=controller,
action=r['action'],
conditions={'method': methods_str})
if url not in urls:
urls[url] = methods
else:
urls[url] += methods
# now register the missing methods to return 405s, and register
# a handler for OPTIONS that returns the list of allowed methods
for url, methods in urls.items():
all_methods = ['HEAD', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE']
missing_methods = [m for m in all_methods if m not in methods]
allowed_methods_str = ','.join(methods)
mapper.connect(url,
controller=default_resource,
action='reject',
allowed_methods=allowed_methods_str,
conditions={'method': missing_methods})
if 'OPTIONS' not in methods:
mapper.connect(url,
controller=default_resource,
action='options',
allowed_methods=allowed_methods_str,
conditions={'method': 'OPTIONS'})
# Stacks
stacks_resource = stacks.create_resource(conf)
connect(controller=stacks_resource,
path_prefix='/{tenant_id}',
routes=[
# Template handling
{
'name': 'template_validate',
'url': '/validate',
'action': 'validate_template',
'method': 'POST'
},
{
'name': 'resource_types',
'url': '/resource_types',
'action': 'list_resource_types',
'method': 'GET'
},
{
'name': 'resource_schema',
'url': '/resource_types/{type_name}',
'action': 'resource_schema',
'method': 'GET'
},
{
'name': 'generate_template',
'url': '/resource_types/{type_name}/template',
'action': 'generate_template',
'method': 'GET'
},
{
'name': 'template_versions',
'url': '/template_versions',
'action': 'list_template_versions',
'method': 'GET'
},
{
'name': 'template_functions',
'url': '/template_versions/{template_version}'
'/functions',
'action': 'list_template_functions',
'method': 'GET'
},
# Stack collection
{
'name': 'stack_index',
'url': '/stacks',
'action': 'index',
'method': 'GET'
},
{
'name': 'stack_create',
'url': '/stacks',
'action': 'create',
'method': 'POST'
},
{
'name': 'stack_preview',
'url': '/stacks/preview',
'action': 'preview',
'method': 'POST'
},
{
'name': 'stack_detail',
'url': '/stacks/detail',
'action': 'detail',
'method': 'GET'
},
... snip
])
... snip
super(API, self).__init__(mapper)
(2-2-2) [filter:osprofiler]
セクションのosprofiler.web:WsgiMiddleware.factory
メソッドが起動される
- osprofiler.web.WsgiMiddleware.factoryメソッドが起動される
- その場合、クロージャ
filter_
への引数app
には、heat.api.openstack.v1.API
インスタンスのオブジェクト値が渡される -
WsgiMiddlewareクラスがインスタンス化されて、
self.application
には、heat.api.openstack.v1.API
インスタンスのオブジェクト値が設定される -
WsgiMiddlewareクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-3) [filter:context]
セクションのheat.common.context:ContextMiddleware_filter_factory
メソッドが起動される
- heat.common.context.ContextMiddleware_filter_factoryメソッドが起動される
- その場合、クロージャ
filter
への引数app
には、osprofiler.web.WsgiMiddleware
インスタンスのオブジェクト値が渡される - ContextMiddlewareクラスがインスタンス化される
- 上位クラスのMiddlewareクラスのコンストラクタが起動されて、
self.application
には、osprofiler.web.WsgiMiddleware
インスタンスのオブジェクト値が設定される -
ContextMiddlewareクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-4) [filter:authtoken]
セクションのkeystonemiddleware.auth_token:filter_factory
メソッドが起動される
- keystonemiddleware.auth_token.filter_factoryメソッドが起動される
- その場合、クロージャ
filter
への引数app
には、heat.common.context.ContextMiddleware
インスタンスのオブジェクト値が渡される -
AuthProtocolクラスがインスタンス化されて、
self._app
には、heat.common.context.ContextMiddleware
インスタンスのオブジェクト値が設定される -
AuthProtocolクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-5) [filter:authurl]
セクションのheat.common.auth_url:filter_factory
メソッドが起動される
- heat.common.auth_url.filter_factoryメソッドが起動される
- その場合、クロージャ
filter
への引数app
には、keystonemiddleware.auth_token.AuthProtocol
インスタンスのオブジェクト値が渡される - AuthUrlFilterクラスがインスタンス化される
- Pythonモジュール
keystonemiddleware.auth_token
が、インポートされる -
/etc/heat/heat.conf
ファイルの[keystone_authtoken]
セクションに記述されたauth_uri
パラメータ値が取得される - 上位クラスのMiddlewareのコンストラクタが起動されて、
self.application
には、keystonemiddleware.auth_token.AuthProtocol
インスタンスのオブジェクト値が設定される -
AuthUrlFilterクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-6) [filter:versionnegotiation]
セクションのheat.common.wsgi:filter_factory
メソッドが起動される
-
heat.common.wsgi.filter_factory
メソッド起動により、FilterFactory クラスの__call__
メソッドが起動される -
_import_factory
メソッドが呼ばれる -
local_conf
には、{'heat.filter_factory': 'heat.api.openstack:version_negotiation_filter'}
という情報が設定されている - その場合、クロージャ
filter
への引数app
には、heat.common.auth_url.AuthUrlFilter
インスタンスのオブジェクト値が渡される - version_negotiation_filterメソッドが起動される
- VersionNegotiationFilterクラスがインスタンス化される
- 上位クラスのMiddlewareのコンストラクタが起動されて、
self.application
には、heat.common.auth_url.AuthUrlFilter
インスタンスのオブジェクト値が設定される -
VersionNegotiationFilterクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-7) [filter:http_proxy_to_wsgi]
セクションのoslo_middleware:HTTPProxyToWSGI.factory
メソッドが起動される
- oslo_middleware.HTTPProxyToWSGI.factorメソッドが起動される
- その場合、クロージャ
middleware_filter
への引数app
には、heat.api.middleware.version_negotiation.VersionNegotiationFilter
インスタンスのオブジェクト値が渡される -
HTTPProxyToWSGIクラスがインスタンス化されて、
self.application
には、heat.api.middleware.version_negotiation.VersionNegotiationFilter
インスタンスのオブジェクト値が設定される -
HTTPProxyToWSGIクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-8) [filter:faultwrap]
セクションのheat.common.wsgi:filter_factory
メソッドが起動される
-
heat.common.wsgi.filter_factory
メソッド起動により、FilterFactoryクラスの__call__
メソッドが起動される -
_import_factory
メソッドが呼ばれる -
local_conf
には、{'heat.filter_factory': 'heat.api.openstack:faultwrap_filter'}
という情報が設定されている - その場合、クロージャ
filter
への引数app
には、oslo_middleware.HTTPProxyToWSGI
インスタンスのオブジェクト値が渡される - faultwrap_filterメソッドが起動される
- FaultWrapperクラスがインスタンス化される
- 上位クラスのMiddlewareのコンストラクタが起動されて、
self.application
には、oslo_middleware.HTTPProxyToWSGI
インスタンスのオブジェクト値が設定される -
FaultWrapperクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-9) [filter:request_id]
セクションのoslo_middleware.request_id:RequestId.factory
メソッドが起動される
- oslo_middleware.request_id.RequestId.factoryメソッドが起動される
- その場合、クロージャ
middleware_filter
への引数app
には、heat.api.middleware.fault.FaultWrapper
インスタンスのオブジェクト値が渡される -
RequestIdクラスがインスタンス化されて、
self.application
には、heat.api.middleware.fault.FaultWrapper
インスタンスのオブジェクト値が設定される -
RequestIdクラスの
__call__
メソッド起動準備が完了したことになる
(2-2-10) [filter:cors]
セクションのoslo_middleware.cors:filter_factory
メソッドが起動される
- oslo_middleware.cors.filter_factoryメソッドが起動される
- その場合、クロージャ
middleware_filter
への引数app
には、oslo_middleware.request_id.RequestId
インスタンスのオブジェクト値が渡される -
CORSクラスがインスタンス化されて、
self.application
には、oslo_middleware.request_id.RequestId
インスタンスのオブジェクト値が設定される -
CORSクラスの
__call__
メソッド起動準備が完了したことになる
(2-3) paste_deploy_app
メソッド処理は、ここで完了となる。
(3) wsgiサーバが起動される
wsgi.Server
クラスが、インスタンス化された後、wsgi.Server.startメソッドが起動される
def launch_api(setup_logging=True):
if setup_logging:
logging.register_options(cfg.CONF)
cfg.CONF(project='heat', prog='heat-api',
version=version.version_info.version_string())
if setup_logging:
logging.setup(cfg.CONF, 'heat-api')
config.set_config_defaults()
messaging.setup()
app = config.load_paste_app()
port = cfg.CONF.heat_api.bind_port
host = cfg.CONF.heat_api.bind_host
LOG.info('Starting Heat REST API on %(host)s:%(port)s',
{'host': host, 'port': port})
profiler.setup('heat-api', host)
gmr.TextGuruMeditation.setup_autorun(version)
server = wsgi.Server('heat-api', cfg.CONF.heat_api)
server.start(app, default_port=port)
return server
def main():
try:
server = launch_api()
systemd.notify_once()
server.wait()
except RuntimeError as e:
msg = six.text_type(e)
sys.exit("ERROR: %s" % msg)
/etc/heat/heat.conf
ファイルに、workersパラメータを設定することが可能だが、"Heat Configuration Sample"によると、デフォルトでは、設定されていないので、デフォルト"0"として扱われる。
[heat_api]
...
# Number of workers for Heat service. Default value 0 means, that service will
# start number of workers equal number of cores on server. (integer value)
# Minimum value: 0
#workers = 0
そして、workers = 0
の場合には、run_serverメソッドが起動される
def run_server(self):
"""Run a WSGI server."""
eventlet.wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
eventlet.hubs.use_hub('poll')
eventlet.patcher.monkey_patch(all=False, socket=True)
self.pool = eventlet.GreenPool(size=self.threads)
socket_timeout = cfg.CONF.eventlet_opts.client_socket_timeout or None
# Close write to ensure only parent has it open
os.close(self.writepipe)
# Create greenthread to watch for parent to close pipe
eventlet.spawn_n(self._pipe_watcher)
try:
eventlet.wsgi.server(
self.sock,
self.application,
custom_pool=self.pool,
url_length_limit=URL_LENGTH_LIMIT,
log=self._logger,
debug=cfg.CONF.debug,
keepalive=cfg.CONF.eventlet_opts.wsgi_keep_alive,
socket_timeout=socket_timeout)
except socket.error as err:
if err[0] != errno.EINVAL:
raise
self.pool.waitall()
◼️ heat-apiのリクエスト受け付け処理の動作メカニズムを調査してみる
つづいて、heat-apiでのリクエスト受け付け処理の流れを、確認していきます。
(1) oslo_middleware.cors.CORSクラスの__call__
メソッドが起動される
-
CORSクラス
の__call__
メソッドが起動される -
self.application
には、oslo_middleware.request_id.RequestId
インスタンスのオブジェクト値が設定されるため、RequestIdクラスの__call__
メソッド`を呼び出すことになる
(2) oslo_middleware.request_id.RequestIdクラスの__call__
メソッドが起動される
-
RequestIdクラスの
__call__
メソッドが起動される -
req_id
が、新たに生成される -
req.environ['openstack.request_id']
に、新たに生成されたreq_id
が設定される -
self.application
には、heat.api.middleware.fault.FaultWrapper
インスタンスのオブジェクト値が設定されるため、FaultWrapperクラスの__call__
メソッド`を呼び出すことになる - レスポンスメッセージのヘッダ情報に、
x-openstack-request-id
パラメータとして追加される
(3) heat.api.middleware.fault.FaultWrapperクラスの__call__
メソッドが起動される
-
FaultWrapperクラス
の__call__
メソッドが起動される -
FaultWrapperクラス
のprocess_request
メソッドが起動される -
self.application
には、oslo_middleware.HTTPProxyToWSGI
インスタンスのオブジェクト値が設定されるため、HTTPProxyToWSGIクラスの__call__
メソッド`を呼び出すことになる
(4) oslo_middleware.HTTPProxyToWSGIクラスの__call__
メソッドが起動される
-
HTTPProxyToWSGIクラス
の__call__
メソッドが起動される -
HTTPProxyToWSGIクラス
のprocess_request
メソッドが起動される -
self.application
には、heat.api.middleware.version_negotiation.VersionNegotiationFilter
インスタンスのオブジェクト値が設定されるため、VersionNegotiationFilterクラスの__call__
メソッド`を呼び出すことになる
(5) heat.api.middleware.version_negotiation.VersionNegotiationFilterクラスの__call__
メソッドが起動される
-
VersionNegotiationFilterクラス
の__call__
メソッドが起動される -
VersionNegotiationFilterクラス
のprocess_request
メソッドが起動される -
req.path_info_peekメソッドを用いて、
req.environ['PATH_INFO']
に、バージョン番号が含まれていなければ、リクエストメッセージに対するバージョンチェックが行われる - バージョンチェックで問題なければ、デバックログとして、"Matched versioned URI. Version: 1.0"と出力される
-
self.application
には、heat.common.auth_url.AuthUrlFilter
インスタンスのオブジェクト値が設定されるため、AuthUrlFilterクラスの__call__
メソッド`を呼び出すことになる
(6) heat.common.auth_url.AuthUrlFilterクラスの__call__
メソッドが起動される
-
AuthUrlFilterクラス
の__call__
メソッドが起動される -
AuthUrlFilterクラス
のprocess_request
メソッドが起動される -
req.headers['X-Auth-Url']
に、self.auth_url
が設定される(すなわち、リクエスト受け付け毎に、/etc/heat/heat.conf
ファイルの[keystone_authtoken]
セクションのauth_uri
パラメータ値が引用される)
[keystone_authtoken]
auth_uri = http://<keystone-server>:5000
-
self.application
には、keystonemiddleware.auth_token.AuthProtocol
インスタンスのオブジェクト値が設定されるため、AuthProtocolクラスの__call__
メソッド`を呼び出すことになる
(7) keystonemiddleware.auth_token.AuthProtocolクラスの__call__
メソッドが起動される
-
AuthProtocolクラス
の__call__
メソッドが起動される -
AuthProtocolクラス
のprocess_request
メソッドが起動される - auth_token処理が開始される
-
self._app
には、heat.common.context.ContextMiddleware
インスタンスのオブジェクト値が設定されるため、ContextMiddlewareクラスの__call__
メソッド`を呼び出すことになる
(8) heat.common.context.ContextMiddlewareクラスの__call__
メソッドが起動される
-
ContextMiddlewareクラス
の__call__
メソッドが起動される -
ContextMiddlewareクラス
のprocess_request
メソッドが起動される - context情報が生成される
-
self.application
には、osprofiler.web.WsgiMiddleware
インスタンスのオブジェクト値が設定されるため、WsgiMiddlewareクラスの__call__
メソッド`を呼び出すことになる
(9) osprofiler.web.WsgiMiddlewareクラスの__call__
メソッドが起動される
-
WsgiMiddlewareクラス
の__call__
メソッドが起動される -
self.application
には、heat.api.openstack.v1.API
インスタンスのオブジェクト値が設定されるため、heat.api.openstack.v1.APIクラスの__call__
メソッド`を呼び出すことになる
(10) heat.api.openstack.v1.APIクラスの__call__
メソッドが起動される
-
heat.api.openstack.v1.APIクラス
の__call__
メソッドが起動される - リクエストメッセージ受け付け処理として、ルーティングのマッピング情報との比較により、その後の継続処理が決定される
- そして、適切なpolicy処理が施される
- StackControllerクラスで定義されたメソッドが起動される
(11) heat-engineへの依頼すべき、メッセージ生成が完了した段階で、RabbitMQを経由して、heat-engineに情報伝達される。
なお、メッセージング処理機構は、Qiita記事「OpenStackメッセージング機構を探ってみる」を参照ください。
◼️ おわりに
heat-api内部構造を説明するにあたり、結構、複雑な文章になってしまった。
ただし、大まかなheat-api処理は、Qiita記事「OpenStackを活用して、WSGI アプリケーションの仕組みを理解する」で記述したサンプルアプリと、やっている事は、概ね同じです。
根気を持って確認してもらえれば、理解は難しくないと思います。
◼️ 参考文献
(1) Paste Deployment
- Docs: Paste Deployment
- PEP333: The WSGI Specification
- A WSGI Developers’ Toolkit: Paste
- OpenStack API's and WSGI
- How to OpenStack API and WSGI api-paste.ini Work
- Getting Started with Python WSGI and Paste Deployment
- PasteDeploy で WSGI アプリケーションを設定する