diff --git a/bin/nova-rsapi b/bin/nova-rsapi index cd2f285c..24b469f7 100755 --- a/bin/nova-rsapi +++ b/bin/nova-rsapi @@ -19,10 +19,9 @@ """ import logging +from wsgiref import simple_server from nova import vendor -from tornado import httpserver -import tornado.web from tornado import ioloop from nova import flags @@ -31,34 +30,28 @@ from nova import server from nova import utils from nova.auth import users from nova.endpoint import rackspace -from nova.endpoint import wsgi_wrapper -FLAGS = flags.FLAGS -flags.DEFINE_integer('cc_port', 8773, 'api server port') -def application(_args): - """ Wrap WSGI application as tornado """ - wsgi_wrapper.Wrap(rackspace.Api) +FLAGS = flags.FLAGS +flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') def main(_argv): user_manager = users.UserManager() - controllers = { - 'Servers': rackspace.ServersController() - } - _app = rackspace.RackspaceAPIServerApplication(user_manager, controllers) - + api_instance = rackspace.Api(user_manager) conn = rpc.Connection.instance() - consumer = rpc.AdapterConsumer(connection=conn, + rpc_consumer = rpc.AdapterConsumer(connection=conn, topic=FLAGS.cloud_topic, - proxy=controllers['Servers']) + proxy=api_instance) - io_inst = ioloop.IOLoop.instance() - _injected = consumer.attach_to_tornado(io_inst) - - http_server = httpserver.HTTPServer(_app) - http_server.listen(FLAGS.cc_port) - logging.debug('Started HTTP server on %s', FLAGS.cc_port) - io_inst.start() +# TODO: fire rpc response listener (without attach to tornado) +# io_inst = ioloop.IOLoop.instance() +# _injected = consumer.attach_to_tornado(io_inst) + http_server = simple_server.WSGIServer(('0.0.0.0', FLAGS.cc_port), simple_server.WSGIRequestHandler) + http_server.set_app(api_instance.handler) + logging.debug('Started HTTP server on port %i' % FLAGS.cc_port) + while True: + http_server.handle_request() +# io_inst.start() if __name__ == '__main__': utils.default_flagfile() diff --git a/nova/endpoint/rackspace.py b/nova/endpoint/rackspace.py index c4828565..7438e3bd 100644 --- a/nova/endpoint/rackspace.py +++ b/nova/endpoint/rackspace.py @@ -36,6 +36,7 @@ from nova import exception from nova.auth import users from nova.compute import model from nova.compute import network +from nova.endpoint import wsgi from nova.endpoint import images from nova.volume import storage @@ -53,216 +54,58 @@ def _gen_key(user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} -class ServersController(object): - """ ServersController provides the critical dispatch between - inbound API calls through the endpoint and messages - sent to the other nodes. -""" - def __init__(self): - self.instdir = model.InstanceDirectory() - self.network = network.PublicNetworkController() - self.setup() +class Api(object): - @property - def instances(self): - """ All instances in the system, as dicts """ - for instance in self.instdir.all: - yield {instance['instance_id']: instance} - - @property - def volumes(self): - """ returns a list of all volumes """ - for volume_id in datastore.Redis.instance().smembers("volumes"): - volume = storage.Volume(volume_id=volume_id) - yield volume - - def __str__(self): - return 'ServersController' - - def setup(self): - """ Ensure the keychains and folders exist. """ - # Create keys folder, if it doesn't exist - if not os.path.exists(FLAGS.keys_path): - os.makedirs(os.path.abspath(FLAGS.keys_path)) - # Gen root CA, if we don't have one - root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file) - if not os.path.exists(root_ca_path): - start = os.getcwd() - os.chdir(FLAGS.ca_path) - utils.runthis("Generating root CA: %s", "sh genrootca.sh") - os.chdir(start) - # TODO: Do this with M2Crypto instead - - def get_instance_by_ip(self, ip): - return self.instdir.by_ip(ip) - - def get_metadata(self, ip): - i = self.get_instance_by_ip(ip) - if i is None: - return None - if i['key_name']: - keys = { - '0': { - '_name': i['key_name'], - 'openssh-key': i['key_data'] - } - } - else: - keys = '' - data = { - 'user-data': base64.b64decode(i['user_data']), - 'meta-data': { - 'ami-id': i['image_id'], - 'ami-launch-index': i['ami_launch_index'], - 'ami-manifest-path': 'FIXME', # image property - 'block-device-mapping': { # TODO: replace with real data - 'ami': 'sda1', - 'ephemeral0': 'sda2', - 'root': '/dev/sda1', - 'swap': 'sda3' - }, - 'hostname': i['private_dns_name'], # is this public sometimes? - 'instance-action': 'none', - 'instance-id': i['instance_id'], - 'instance-type': i.get('instance_type', ''), - 'local-hostname': i['private_dns_name'], - 'local-ipv4': i['private_dns_name'], # TODO: switch to IP - 'kernel-id': i.get('kernel_id', ''), - 'placement': { - 'availaibility-zone': i.get('availability_zone', 'nova'), - }, - 'public-hostname': i.get('dns_name', ''), - 'public-ipv4': i.get('dns_name', ''), # TODO: switch to IP - 'public-keys' : keys, - 'ramdisk-id': i.get('ramdisk_id', ''), - 'reservation-id': i['reservation_id'], - 'security-groups': i.get('groups', '') - } + def __init__(self, rpc_mechanism): + self.controllers = { + "v1.0": RackspaceAuthenticationApi(), + "server": RackspaceCloudServerApi() } - if False: # TODO: store ancestor ids - data['ancestor-ami-ids'] = [] - if i.get('product_codes', None): - data['product-codes'] = i['product_codes'] - return data + self.rpc_mechanism = rpc_mechanism - def update_state(self, topic, value): - """ accepts status reports from the queue and consolidates them """ - # TODO(jmc): if an instance has disappeared from - # the node, call instance_death - if topic == "instances": - return defer.succeed(True) - aggregate_state = getattr(self, topic) - node_name = value.keys()[0] - items = value[node_name] - logging.debug("Updating %s state for %s" % (topic, node_name)) - for item_id in items.keys(): - if (aggregate_state.has_key('pending') and - aggregate_state['pending'].has_key(item_id)): - del aggregate_state['pending'][item_id] - aggregate_state[node_name] = items - return defer.succeed(True) + def handler(self, environ, responder): + logging.error("*** %s" % environ) + controller, path = wsgi.Util.route(environ['PATH_INFO'], self.controllers) + if not controller: + raise Exception("Missing Controller") + rv = controller.process(path, environ) + if type(rv) is tuple: + responder(rv[0], rv[1]) + rv = rv[2] + else: + responder("200 OK", []) + return rv -class RackspaceAPIServerApplication(tornado.web.Application): - def __init__(self, user_manager, controllers): - tornado.web.Application.__init__(self, [ - (r'/servers/?(.*)', RackspaceServerRequestHandler), - ], pool=multiprocessing.Pool(4)) - self.user_manager = user_manager - self.controllers = controllers +class RackspaceApiEndpoint(object): + def process(self, path, env): + if len(path) == 0: + return self.index(env) -class RackspaceServerRequestHandler(tornado.web.RequestHandler): + action = path.pop(0) + if hasattr(self, action): + method = getattr(self, action) + return method(path, env) + else: + raise Exception("Missing method %s" % path[0]) - def get(self, controller_name): - self.execute(controller_name) - @tornado.web.asynchronous - def execute(self, controller_name): - controller = self.application.controllers['Servers'] +class RackspaceAuthenticationApi(RackspaceApiEndpoint): - # Obtain the appropriate controller for this request. -# try: -# controller = self.application.controllers[controller_name] -# except KeyError: -# self._error('unhandled', 'no controller named %s' % controller_name) -# return -# - args = self.request.arguments - logging.error("ARGS: %s" % args) + def index(self, env): + response = '204 No Content' + headers = [ + ('X-Server-Management-Url', 'http://localhost:8773/server'), + ('X-Storage-Url', 'http://localhost:8773/server'), + ('X-CDN-Managment-Url', 'http://localhost:8773/server'), + ] + body = "" + return (response, headers, body) + - # Read request signature. - try: - signature = args.pop('Signature')[0] - except: - raise tornado.web.HTTPError(400) +class RackspaceCloudServerApi(object): - # Make a copy of args for authentication and signature verification. - auth_params = {} - for key, value in args.items(): - auth_params[key] = value[0] - - # Get requested action and remove authentication args for final request. - try: - action = args.pop('Action')[0] - access = args.pop('AWSAccessKeyId')[0] - args.pop('SignatureMethod') - args.pop('SignatureVersion') - args.pop('Version') - args.pop('Timestamp') - except: - raise tornado.web.HTTPError(400) - - # Authenticate the request. - try: - (user, project) = users.UserManager.instance().authenticate( - access, - signature, - auth_params, - self.request.method, - self.request.host, - self.request.path - ) - - except exception.Error, ex: - logging.debug("Authentication Failure: %s" % ex) - raise tornado.web.HTTPError(403) - - _log.debug('action: %s' % action) - - for key, value in args.items(): - _log.debug('arg: %s\t\tval: %s' % (key, value)) - - request = APIRequest(controller, action) - context = APIRequestContext(self, user, project) - d = request.send(context, **args) - # d.addCallback(utils.debug) - - # TODO: Wrap response in AWS XML format - d.addCallbacks(self._write_callback, self._error_callback) - - def _write_callback(self, data): - self.set_header('Content-Type', 'text/xml') - self.write(data) - self.finish() - - def _error_callback(self, failure): - try: - failure.raiseException() - except exception.ApiError as ex: - self._error(type(ex).__name__ + "." + ex.code, ex.message) - # TODO(vish): do something more useful with unknown exceptions - except Exception as ex: - self._error(type(ex).__name__, str(ex)) - raise - - def post(self, controller_name): - self.execute(controller_name) - - def _error(self, code, message): - self._status_code = 400 - self.set_header('Content-Type', 'text/xml') - self.write('\n') - self.write('%s' - '%s' - '?' % (code, message)) - self.finish() + def index(self): + return "IDX" + def list(self, args): + return "%s" % args diff --git a/nova/endpoint/wsgi.py b/nova/endpoint/wsgi.py new file mode 100644 index 00000000..e0817fd0 --- /dev/null +++ b/nova/endpoint/wsgi.py @@ -0,0 +1,23 @@ + +''' +Utility methods for working with WSGI servers +''' + +class Util(object): + + @staticmethod + def route(reqstr, controllers): + if len(reqstr) == 0: + return Util.select_root_controller(controllers), [] + parts = [x for x in reqstr.split("/") if len(x) > 0] + if len(parts) == 0: + return Util.select_root_controller(controllers), [] + return controllers[parts[0]], parts[1:] + + @staticmethod + def select_root_controller(controllers): + if '' in controllers: + return controllers[''] + else: + return None +