From 9d69b204023eed196d4dd113d3378a0704d428dd Mon Sep 17 00:00:00 2001 From: Tomas Sedovic Date: Fri, 4 May 2012 15:16:11 +0200 Subject: [PATCH] Connect metadata server to the engine via RPC Similarly to the way heat-api works, the engine does all the heavy lifting (db access, etc.) while the metadata server provides the API layer that communicates with the engine. Signed-off-by: Tomas Sedovic --- bin/heat-metadata | 2 ++ heat/common/config.py | 4 ++- heat/db/sqlalchemy/models.py | 10 ++++++ heat/engine/manager.py | 59 ++++++++++++++++++++++++++++++ heat/metadata/api/v1/metadata.py | 61 ++++++++++++++++++-------------- 5 files changed, 109 insertions(+), 27 deletions(-) diff --git a/bin/heat-metadata b/bin/heat-metadata index f95334e2..f58dcad2 100755 --- a/bin/heat-metadata +++ b/bin/heat-metadata @@ -34,6 +34,7 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')): gettext.install('heat', unicode=1) import logging +from heat import rpc from heat.common import config from heat.common import wsgi from paste import httpserver @@ -45,6 +46,7 @@ if __name__ == '__main__': conf = config.HeatMetadataConfigOpts() conf() config.FLAGS = conf + rpc.configure(conf) config.setup_logging(conf) app = config.load_paste_app(conf) diff --git a/heat/common/config.py b/heat/common/config.py index 30217361..be14f9d7 100644 --- a/heat/common/config.py +++ b/heat/common/config.py @@ -148,7 +148,9 @@ class HeatMetadataConfigOpts(cfg.CommonConfigOpts): version='%%prog %s' % version.version_string(), default_config_files=default_config_files, **kwargs) - self.register_cli_opts([cfg.IntOpt('bind_port', default=8000)]) + opts = [cfg.IntOpt('bind_port', default=8000)] + opts.extend(rpc_opts) + self.register_cli_opts(opts) class HeatEngineConfigOpts(cfg.CommonConfigOpts): diff --git a/heat/db/sqlalchemy/models.py b/heat/db/sqlalchemy/models.py index bd55fcbf..e7730aac 100644 --- a/heat/db/sqlalchemy/models.py +++ b/heat/db/sqlalchemy/models.py @@ -89,6 +89,16 @@ class HeatBase(object): for k, v in values.iteritems(): setattr(self, k, v) + def update_and_save(self, values, session=None): + if not session: + session = Session.object_session(self) + if not session: + session = get_session() + session.begin() + for k, v in values.iteritems(): + setattr(self, k, v) + session.commit() + def iteritems(self): """Make the model object behave like a dict. diff --git a/heat/engine/manager.py b/heat/engine/manager.py index 262356d2..ece2c59c 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -15,6 +15,7 @@ import contextlib +from copy import deepcopy import functools import os import socket @@ -214,3 +215,61 @@ class EngineManager(manager.Manager): 'ResourceStatus': e.name} return {'events': [parse_event(e) for e in events]} + + def metadata_list_stacks(self, context): + """ + Return the names of the stacks registered with Heat. + """ + stacks = db_api.stack_get_all(None) + return [s.name for s in stacks] + + def metadata_list_resources(self, context, stack_name): + """ + Return the resource IDs of the given stack. + """ + stack = db_api.stack_get(None, stack_name) + if stack: + return [r.name for r in stack.resources] + else: + return None + + def metadata_get_resource(self, context, stack_name, resource_id): + """ + Get the metadata for the given resource. + """ + s = db_api.stack_get(None, stack_name) + if not s: + return ['stack', None] + + raw_template = db_api.raw_template_get(None, s.raw_template_id) + template = raw_template.template + + if not resource_id in template.get('Resources', {}): + return ['resource', None] + + metadata = template['Resources'][resource_id].get('Metadata', {}) + return [None, metadata] + + def metadata_update(self, context, stack_name, resource_id, metadata): + """ + Update the metadata for the given resource. + """ + s = db_api.stack_get(None, stack_name) + if not s: + return ['stack', None] + + raw_template = db_api.raw_template_get(None, s.raw_template_id) + + if not resource_id in raw_template.template.get('Resources', {}): + return ['resource', None] + + # TODO(shadower) deep copy of the template is required here. Without it, + # we directly modify raw_template.template by assigning the new + # metadata. When we then call raw_template.update_and_save, the session + # will detect no changes and thus not update the database. + # Just updating the values and calling save didn't seem to work either. + # There's probably an idiomatic way I'm missing right now. + t = deepcopy(raw_template.template) + t['Resources'][resource_id]['Metadata'] = metadata + raw_template.update_and_save({'template': t}) + return [None, metadata] diff --git a/heat/metadata/api/v1/metadata.py b/heat/metadata/api/v1/metadata.py index 8c114a15..77fc7ad5 100644 --- a/heat/metadata/api/v1/metadata.py +++ b/heat/metadata/api/v1/metadata.py @@ -19,9 +19,11 @@ import json from webob.exc import Response from heat.common import wsgi +from heat import context from heat.metadata import db as db_api from heat.metadata.db import (ConflictError, StackNotFoundError, ResourceNotFoundError) +from heat import rpc def json_response(http_status, data): @@ -47,39 +49,46 @@ class MetadataController: } def list_stacks(self, req): - return db_api.list_stacks() + con = context.get_admin_context() + resp = rpc.call(con, 'engine', + {'method': 'metadata_list_stacks'}) + return resp def list_resources(self, req, stack_name): - try: - resources = db_api.list_resources(stack_name) - except StackNotFoundError: + con = context.get_admin_context() + resources = rpc.call(con, 'engine', + {'method': 'metadata_list_resources', + 'args': {'stack_name': stack_name}}) + if resources: + return resources + else: 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) + con = context.get_admin_context() + [error, metadata] = rpc.call(con, 'engine', + {'method': 'metadata_get_resource', + 'args': {'stack_name': stack_name, + 'resource_id': resource_id}}) + if error: + if error == 'stack': + return json_error(404, 'The stack "%s" does not exist.' % stack_name) + else: + return json_error(404, 'The resource "%s" does not exist.' % resource_id) + return metadata 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) + con = context.get_admin_context() + [error, metadata] = rpc.call(con, 'engine', + {'method': 'metadata_update', + 'args': {'stack_name': stack_name, + 'resource_id': resource_id, + 'metadata': body}}) + if error: + if error == 'stack': + return json_error(404, 'The stack "%s" does not exist.' % stack_name) + else: + return json_error(404, 'The resource "%s" does not exist.' % resource_id) return json_response(201, { 'resource': resource_id, 'metadata': body, -- 2.45.2