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:
parent
c061dc0029
commit
9843bc8baa
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue