Posted at

(Stein版) OpenStack heat内部構造を探ってみる[heat-api編]

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メッセージング機構を探ってみる」を参照ください。

Heat Architecture.002.png


(1) heat-api起動の流れは、どうなっているのか?

こちらのスライド"OpenStack API's and WSGI"からの引用です。

名称未設定.001.png

今回、heat-api動作の流れを調査する上で、これらのキーワードを意識しておくことが大切になりそうです。


(2) heat向けのリクエストを、どうやって処理しているのか?

heat-api内部にて、受け付けたリクエストメッセージを適切に処理する場合、WSGI アプリケーションの機構が関与しているようです。そこで、まずは、WSGI アプリケーションの仕組みを理解する必要があります。

過去のQiita記事「OpenStackを活用して、WSGI アプリケーションの仕組みを理解するを参照ください。


◼️ OpenStack heat環境として、/etc/heat/api_paste.iniファイルを事前確認しておく

/etc/heat/api_paste.ini が配備されているので、事前に内容を確認しておく


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メソッドは、こんな感じ...


heat/common/config.py

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メソッドは、こんな感じ...


heat/common/wsgi.py

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メソッドは、こんな感じ...


heat/common/wsgi.py

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クラスは、こんな感じ...


heat/common/wsgi.py

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メソッドの起動準備が完了することになる。


/etc/heat/api_paste.ini


... (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クラスは、こんな感じ...


heat/common/wsgi.py

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メソッドの起動準備が完了することになる。


/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)

[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処理が開始される。


/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)


すなわち、 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__メソッド起動準備が完了したことになる


heat/api/openstack/v1/__init__.py

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メソッドが起動される


(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メソッドが起動される


heat/cmd/api.py

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メソッドが起動される


heat/heat/common/wsgi.py

    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パラメータ値が引用される)


/etc/heat/heat.conf

[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


(2) WebOb


(3) その他