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:
Richard Lee 2013-11-12 16:20:33 -06:00
parent 155d94ef78
commit f3d0899949
7 changed files with 140 additions and 1 deletions

View File

@ -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

View File

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

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

View File

@ -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()

View File

@ -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):

View File

@ -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'))

View File

@ -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'])