Initial metadata server API

This implements the basic capabilities we need from the metadata server. Each
API call returns proper HTTP responses (404, 409, etc.).

The server is currently not connected to a real database. Rather, it uses a
simple mock. This allows for quick initial API changes before things stabilize.

The immediate next steps are to integrate the server with the cfn tools
(cfn-metadata being the prime candidate) to see what may be wrong/missing.

And then to connect the server to a real database.

Signed-off-by: Tomas Sedovic <tomas@sedovic.cz>
This commit is contained in:
Tomas Sedovic 2012-05-02 13:51:08 +02:00
parent c061dc0029
commit 9843bc8baa
3 changed files with 146 additions and 3 deletions

View File

@ -30,7 +30,27 @@ class API(wsgi.Router):
mapper = routes.Mapper() mapper = routes.Mapper()
metadata_controller = metadata.create_resource(conf) metadata_controller = metadata.create_resource(conf)
mapper.connect('/', controller=metadata_controller, action='index', mapper.connect('/',
controller=metadata_controller, action='entry_point',
conditions=dict(method=['GET'])) conditions=dict(method=['GET']))
mapper.connect('/stacks/',
controller=metadata_controller, action='list_stacks',
conditions=dict(method=['GET']))
mapper.connect('/stacks/:stack_name/resources/',
controller=metadata_controller, action='list_resources',
conditions=dict(method=['GET']))
mapper.connect('/stacks/:stack_name/resources/:resource_id',
controller=metadata_controller, action='get_resource',
conditions=dict(method=['GET']))
mapper.connect('/stacks/:stack_name',
controller=metadata_controller, action='create_stack',
conditions=dict(method=['PUT']))
mapper.connect('/stacks/:stack_name/resources/:resource_id',
controller=metadata_controller, action='update_metadata',
conditions=dict(method=['PUT']))
# TODO(shadower): make sure all responses are JSON-encoded
# currently, calling an unknown route uses the default handler which
# produces a HTML response.
super(API, self).__init__(mapper) super(API, self).__init__(mapper)

View File

@ -14,17 +14,76 @@
# under the License. # under the License.
import logging import logging
import json
from webob.exc import Response
from heat.common import wsgi from heat.common import wsgi
from heat.metadata import db as db_api
from heat.metadata.db import (ConflictError, StackNotFoundError,
ResourceNotFoundError)
def json_response(http_status, data):
"""Create a JSON response with a specific HTTP code."""
response = Response(json.dumps(data))
response.status = http_status
response.content_type = 'application/json'
return response
def json_error(http_status, message):
"""Create a JSON error response."""
body = {'error': message}
return json_response(http_status, body)
class MetadataController: class MetadataController:
def __init__(self, options): def __init__(self, options):
self.options = options self.options = options
def index(self, req): def entry_point(self, req):
return [] return {
'name': 'Heat Metadata Server API',
'version': '1',
}
def list_stacks(self, req):
return db_api.list_stacks()
def list_resources(self, req, stack_name):
try:
resources = db_api.list_resources(stack_name)
except StackNotFoundError:
return json_error(404, 'The stack "%s" does not exist.' % stack_name)
return resources
def get_resource(self, req, stack_name, resource_id):
try:
resource = db_api.get_resource(stack_name, resource_id)
except StackNotFoundError:
return json_error(404, 'The stack "%s" does not exist.' % stack_name)
except ResourceNotFoundError:
return json_error(404, 'The resource "%s" does not exist.' % resource_id)
return resource
def create_stack(self, req, body, stack_name):
try:
stack = db_api.create_stack(stack_name, body)
except ConflictError:
return json_error(409, 'The stack "%s" already exists.' % stack_name)
return json_response(201, stack)
def update_metadata(self, req, body, stack_name, resource_id):
try:
db_api.update_resource_metadata(stack_name, resource_id, body)
except StackNotFoundError:
return json_error(409, 'The stack "%s" does not exist.' % stack_name)
except ResourceNotFoundError:
# The resource doesn't exit yet, create it.
db_api.create_resource_metadata(stack_name, resource_id, body)
return json_response(201, {
'resource': resource_id,
'metadata': body,
})
def create_resource(options): def create_resource(options):
""" """

64
heat/metadata/db.py Normal file
View File

@ -0,0 +1,64 @@
DB = {}
class ConflictError(Exception):
pass
class StackNotFoundError(Exception):
pass
class ResourceNotFoundError(Exception):
pass
def list_stacks():
return DB.keys()
def create_stack(name, stack):
global DB
if name in DB:
raise ConflictError(name)
data = {}
# TODO(shadower): validate the stack input format
data['name'] = name
data['heat_id'] = stack['id']
data['resources'] = {}
DB[name] = data
return data
def list_resources(stack_name):
if not stack_name in DB:
raise StackNotFoundError(stack_name)
stack = DB[stack_name]
try:
resources = stack['resources'].keys()
except:
resources = []
return resources
def get_resource(stack_name, resource_id):
if not stack_name in DB:
raise StackNotFoundError(stack_name)
stack = DB[stack_name]
if not resource_id in stack['resources']:
raise ResourceNotFoundError(resource_id)
return stack['resources'][resource_id]
def create_resource_metadata(stack_name, resource_id, metadata):
if not stack_name in DB:
raise StackNotFoundError(stack_name)
stack = DB[stack_name]
if resource_id in stack['resources']:
raise ConflictError(resource_id)
stack['resources'][resource_id] = metadata
def update_resource_metadata(stack_name, resource_id, metadata):
if not stack_name in DB:
raise StackNotFoundError(stack_name)
stack = DB[stack_name]
if not resource_id in stack['resources']:
raise ResourceNotFoundError(resource_id)
stack['resources'][resource_id] = metadata