Add API endpoint to query build information
This creates the '/<tenant_id>/build_info' endpoint and returns the build revision as defined in the 'heat_revision' configuration option. This provides both the revision of the engine and the api: { "api": { "revision": <api revision> }, "engine": { "revision": <engine revision> } } Implements: blueprint heat-build-info Change-Id: Ibb2f5afda7c5d7c314e5e062d618405d9f4eb318
This commit is contained in:
parent
155d94ef78
commit
f3d0899949
@ -983,3 +983,16 @@
|
|||||||
#insecure=false
|
#insecure=false
|
||||||
|
|
||||||
|
|
||||||
|
[revision]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Options defined in heat.common.config
|
||||||
|
#
|
||||||
|
|
||||||
|
# Heat build revision. If you would prefer to manage your
|
||||||
|
# build revision separately you can move this section to a
|
||||||
|
# different file and add it as another config option (string
|
||||||
|
# value)
|
||||||
|
#heat_revision=unknown
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ from heat.api.openstack.v1 import stacks
|
|||||||
from heat.api.openstack.v1 import resources
|
from heat.api.openstack.v1 import resources
|
||||||
from heat.api.openstack.v1 import events
|
from heat.api.openstack.v1 import events
|
||||||
from heat.api.openstack.v1 import actions
|
from heat.api.openstack.v1 import actions
|
||||||
|
from heat.api.openstack.v1 import build_info
|
||||||
from heat.common import wsgi
|
from heat.common import wsgi
|
||||||
|
|
||||||
from heat.openstack.common import log as logging
|
from heat.openstack.common import log as logging
|
||||||
@ -36,8 +37,8 @@ class API(wsgi.Router):
|
|||||||
self.conf = conf
|
self.conf = conf
|
||||||
mapper = routes.Mapper()
|
mapper = routes.Mapper()
|
||||||
|
|
||||||
|
# Stacks
|
||||||
stacks_resource = stacks.create_resource(conf)
|
stacks_resource = stacks.create_resource(conf)
|
||||||
|
|
||||||
with mapper.submapper(controller=stacks_resource,
|
with mapper.submapper(controller=stacks_resource,
|
||||||
path_prefix="/{tenant_id}") as stack_mapper:
|
path_prefix="/{tenant_id}") as stack_mapper:
|
||||||
# Template handling
|
# Template handling
|
||||||
@ -164,4 +165,14 @@ class API(wsgi.Router):
|
|||||||
action="action",
|
action="action",
|
||||||
conditions={'method': 'POST'})
|
conditions={'method': 'POST'})
|
||||||
|
|
||||||
|
# Info
|
||||||
|
info_resource = build_info.create_resource(conf)
|
||||||
|
with mapper.submapper(controller=info_resource,
|
||||||
|
path_prefix="/{tenant_id}") as info_mapper:
|
||||||
|
|
||||||
|
info_mapper.connect('build_info',
|
||||||
|
'/build_info',
|
||||||
|
action='build_info',
|
||||||
|
conditions={'method': 'GET'})
|
||||||
|
|
||||||
super(API, self).__init__(mapper)
|
super(API, self).__init__(mapper)
|
||||||
|
51
heat/api/openstack/v1/build_info.py
Normal file
51
heat/api/openstack/v1/build_info.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from heat.api.openstack.v1 import util
|
||||||
|
from heat.common import wsgi
|
||||||
|
from heat.rpc import client as rpc_client
|
||||||
|
|
||||||
|
|
||||||
|
class BuildInfoController(object):
|
||||||
|
"""
|
||||||
|
WSGI controller for BuildInfo in Heat v1 API
|
||||||
|
Returns build information for current app
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, options):
|
||||||
|
self.options = options
|
||||||
|
self.engine = rpc_client.EngineClient()
|
||||||
|
|
||||||
|
@util.tenant_local
|
||||||
|
def build_info(self, req):
|
||||||
|
engine_revision = self.engine.get_revision(req.context)
|
||||||
|
build_info = {
|
||||||
|
'api': {'revision': cfg.CONF.revision['heat_revision']},
|
||||||
|
'engine': {'revision': engine_revision}
|
||||||
|
}
|
||||||
|
|
||||||
|
return build_info
|
||||||
|
|
||||||
|
|
||||||
|
def create_resource(options):
|
||||||
|
"""
|
||||||
|
BuildInfo factory method.
|
||||||
|
"""
|
||||||
|
deserializer = wsgi.JSONRequestDeserializer()
|
||||||
|
serializer = wsgi.JSONResponseSerializer()
|
||||||
|
return wsgi.Resource(BuildInfoController(options), deserializer,
|
||||||
|
serializer)
|
@ -150,6 +150,15 @@ def register_clients_opts():
|
|||||||
cfg.CONF.register_opts(opts_copy, group=client_specific_group)
|
cfg.CONF.register_opts(opts_copy, group=client_specific_group)
|
||||||
|
|
||||||
|
|
||||||
|
revision_group = cfg.OptGroup('revision')
|
||||||
|
revision_opts = [
|
||||||
|
cfg.StrOpt('heat_revision',
|
||||||
|
default='unknown',
|
||||||
|
help=_('Heat build revision. '
|
||||||
|
'If you would prefer to manage your build revision '
|
||||||
|
'separately you can move this section to a different '
|
||||||
|
'file and add it as another config option'))]
|
||||||
|
|
||||||
cfg.CONF.register_opts(engine_opts)
|
cfg.CONF.register_opts(engine_opts)
|
||||||
cfg.CONF.register_opts(service_opts)
|
cfg.CONF.register_opts(service_opts)
|
||||||
cfg.CONF.register_opts(rpc_opts)
|
cfg.CONF.register_opts(rpc_opts)
|
||||||
@ -157,6 +166,8 @@ cfg.CONF.register_group(paste_deploy_group)
|
|||||||
cfg.CONF.register_opts(paste_deploy_opts, group=paste_deploy_group)
|
cfg.CONF.register_opts(paste_deploy_opts, group=paste_deploy_group)
|
||||||
cfg.CONF.register_group(auth_password_group)
|
cfg.CONF.register_group(auth_password_group)
|
||||||
cfg.CONF.register_opts(auth_password_opts, group=auth_password_group)
|
cfg.CONF.register_opts(auth_password_opts, group=auth_password_group)
|
||||||
|
cfg.CONF.register_group(revision_group)
|
||||||
|
cfg.CONF.register_opts(revision_opts, group=revision_group)
|
||||||
register_clients_opts()
|
register_clients_opts()
|
||||||
|
|
||||||
|
|
||||||
|
@ -196,6 +196,9 @@ class EngineService(service.Service):
|
|||||||
|
|
||||||
return [format_stack_detail(s) for s in stacks]
|
return [format_stack_detail(s) for s in stacks]
|
||||||
|
|
||||||
|
def get_revision(self, cnxt):
|
||||||
|
return cfg.CONF.revision['heat_revision']
|
||||||
|
|
||||||
@request_context
|
@request_context
|
||||||
def list_stacks(self, cnxt, limit=None, marker=None, sort_keys=None,
|
def list_stacks(self, cnxt, limit=None, marker=None, sort_keys=None,
|
||||||
sort_dir=None, filters=None):
|
sort_dir=None, filters=None):
|
||||||
|
@ -322,3 +322,6 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy):
|
|||||||
return self.call(ctxt, self.make_msg('set_watch_state',
|
return self.call(ctxt, self.make_msg('set_watch_state',
|
||||||
watch_name=watch_name,
|
watch_name=watch_name,
|
||||||
state=state))
|
state=state))
|
||||||
|
|
||||||
|
def get_revision(self, ctxt):
|
||||||
|
return self.call(ctxt, self.make_msg('get_revision'))
|
||||||
|
@ -33,6 +33,7 @@ import heat.api.openstack.v1.stacks as stacks
|
|||||||
import heat.api.openstack.v1.resources as resources
|
import heat.api.openstack.v1.resources as resources
|
||||||
import heat.api.openstack.v1.events as events
|
import heat.api.openstack.v1.events as events
|
||||||
import heat.api.openstack.v1.actions as actions
|
import heat.api.openstack.v1.actions as actions
|
||||||
|
import heat.api.openstack.v1.build_info as build_info
|
||||||
from heat.tests import utils
|
from heat.tests import utils
|
||||||
|
|
||||||
import heat.api.middleware.fault as fault
|
import heat.api.middleware.fault as fault
|
||||||
@ -2432,6 +2433,16 @@ class RoutesTest(HeatTestCase):
|
|||||||
'event_id': 'dddd'
|
'event_id': 'dddd'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_build_info(self):
|
||||||
|
self.assertRoute(
|
||||||
|
self.m,
|
||||||
|
'/fake_tenant/build_info',
|
||||||
|
'GET',
|
||||||
|
'build_info',
|
||||||
|
'BuildInfoController',
|
||||||
|
{'tenant_id': 'fake_tenant'}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ActionControllerTest(ControllerTest, HeatTestCase):
|
class ActionControllerTest(ControllerTest, HeatTestCase):
|
||||||
'''
|
'''
|
||||||
@ -2595,3 +2606,39 @@ class ActionControllerTest(ControllerTest, HeatTestCase):
|
|||||||
stack_id=stack_identity.stack_id,
|
stack_id=stack_identity.stack_id,
|
||||||
body=body)
|
body=body)
|
||||||
self.m.VerifyAll()
|
self.m.VerifyAll()
|
||||||
|
|
||||||
|
|
||||||
|
class BuildInfoControllerTest(HeatTestCase):
|
||||||
|
def test_theres_a_default_api_build_revision(self):
|
||||||
|
req = mock.Mock()
|
||||||
|
controller = build_info.BuildInfoController({})
|
||||||
|
controller.engine = mock.Mock()
|
||||||
|
|
||||||
|
response = controller.build_info(req, tenant_id='tenant_id')
|
||||||
|
self.assertIn('api', response)
|
||||||
|
self.assertIn('revision', response['api'])
|
||||||
|
self.assertEqual('unknown', response['api']['revision'])
|
||||||
|
|
||||||
|
@mock.patch.object(build_info.cfg, 'CONF')
|
||||||
|
def test_response_api_build_revision_from_config_file(self, mock_conf):
|
||||||
|
req = mock.Mock()
|
||||||
|
controller = build_info.BuildInfoController({})
|
||||||
|
mock_engine = mock.Mock()
|
||||||
|
mock_engine.get_revision.return_value = 'engine_revision'
|
||||||
|
controller.engine = mock_engine
|
||||||
|
mock_conf.revision = {'heat_revision': 'test'}
|
||||||
|
|
||||||
|
response = controller.build_info(req, tenant_id='tenant_id')
|
||||||
|
self.assertEqual('test', response['api']['revision'])
|
||||||
|
|
||||||
|
def test_retrieves_build_revision_from_the_engine(self):
|
||||||
|
req = mock.Mock()
|
||||||
|
controller = build_info.BuildInfoController({})
|
||||||
|
mock_engine = mock.Mock()
|
||||||
|
mock_engine.get_revision.return_value = 'engine_revision'
|
||||||
|
controller.engine = mock_engine
|
||||||
|
|
||||||
|
response = controller.build_info(req, tenant_id='tenant_id')
|
||||||
|
self.assertIn('engine', response)
|
||||||
|
self.assertIn('revision', response['engine'])
|
||||||
|
self.assertEqual('engine_revision', response['engine']['revision'])
|
||||||
|
Loading…
Reference in New Issue
Block a user