From 9843bc8baae066643d556c1bc5600202338011a6 Mon Sep 17 00:00:00 2001 From: Tomas Sedovic Date: Wed, 2 May 2012 13:51:08 +0200 Subject: [PATCH] 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 --- heat/metadata/api/v1/__init__.py | 22 ++++++++++- heat/metadata/api/v1/metadata.py | 63 ++++++++++++++++++++++++++++++- heat/metadata/db.py | 64 ++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 heat/metadata/db.py diff --git a/heat/metadata/api/v1/__init__.py b/heat/metadata/api/v1/__init__.py index 7c1631d4e7..c0957f966e 100644 --- a/heat/metadata/api/v1/__init__.py +++ b/heat/metadata/api/v1/__init__.py @@ -30,7 +30,27 @@ class API(wsgi.Router): mapper = routes.Mapper() 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'])) + 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) diff --git a/heat/metadata/api/v1/metadata.py b/heat/metadata/api/v1/metadata.py index 25b4728424..8c114a153c 100644 --- a/heat/metadata/api/v1/metadata.py +++ b/heat/metadata/api/v1/metadata.py @@ -14,17 +14,76 @@ # under the License. import logging +import json + +from webob.exc import Response 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: def __init__(self, options): self.options = options - def index(self, req): - return [] + def entry_point(self, req): + 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): """ diff --git a/heat/metadata/db.py b/heat/metadata/db.py new file mode 100644 index 0000000000..732422ca47 --- /dev/null +++ b/heat/metadata/db.py @@ -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