diff options
| author | Todd Willey <todd@ansolabs.com> | 2010-07-26 23:19:51 -0400 |
|---|---|---|
| committer | Todd Willey <todd@ansolabs.com> | 2010-07-26 23:19:51 -0400 |
| commit | 4044051266d97ffe05fbe75b642759d2e604da4d (patch) | |
| tree | d29038afb528a064fed131d2cb349ca212c6905d | |
| parent | 67d4e16a8c18989e73456f79220b97faa7374d92 (diff) | |
| download | nova-4044051266d97ffe05fbe75b642759d2e604da4d.tar.gz nova-4044051266d97ffe05fbe75b642759d2e604da4d.tar.xz nova-4044051266d97ffe05fbe75b642759d2e604da4d.zip | |
Share my updates to the Rackspace API.
| -rwxr-xr-x | bin/nova-rsapi | 13 | ||||
| -rw-r--r-- | exercise_rsapi.py | 51 | ||||
| -rw-r--r-- | nova/endpoint/rackspace.py | 149 |
3 files changed, 110 insertions, 103 deletions
diff --git a/bin/nova-rsapi b/bin/nova-rsapi index c2f2c9d70..a529fc669 100755 --- a/bin/nova-rsapi +++ b/bin/nova-rsapi @@ -37,23 +37,12 @@ FLAGS = flags.FLAGS flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') def main(_argv): - user_manager = users.UserManager() - api_instance = rackspace.Api(user_manager) - conn = rpc.Connection.instance() - rpc_consumer = rpc.AdapterConsumer(connection=conn, - topic=FLAGS.cloud_topic, - proxy=api_instance) - -# TODO: fire rpc response listener (without attach to tornado) -# io_inst = ioloop.IOLoop.instance() -# _injected = consumer.attach_to_tornado(io_inst) - + api_instance = rackspace.Api() 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/exercise_rsapi.py b/exercise_rsapi.py deleted file mode 100644 index 20589b9cb..000000000 --- a/exercise_rsapi.py +++ /dev/null @@ -1,51 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import cloudservers - -class IdFake: - def __init__(self, id): - self.id = id - -# to get your access key: -# from nova.auth import users -# users.UserManger.instance().get_users()[0].access -rscloud = cloudservers.CloudServers( - 'admin', - '6cca875e-5ab3-4c60-9852-abf5c5c60cc6' - ) -rscloud.client.AUTH_URL = 'http://localhost:8773/v1.0' - - -rv = rscloud.servers.list() -print "SERVERS: %s" % rv - -if len(rv) == 0: - server = rscloud.servers.create( - "test-server", - IdFake("ami-tiny"), - IdFake("m1.tiny") - ) - print "LAUNCH: %s" % server -else: - server = rv[0] - print "Server to kill: %s" % server - -raw_input("press enter key to kill the server") - -server.delete() diff --git a/nova/endpoint/rackspace.py b/nova/endpoint/rackspace.py index 29a077b24..b561212f5 100644 --- a/nova/endpoint/rackspace.py +++ b/nova/endpoint/rackspace.py @@ -48,82 +48,128 @@ FLAGS = flags.FLAGS flags.DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on') -# TODO(todd): subclass Exception so we can bubble meaningful errors +class Unauthorized(Exception): + pass + +class NotFound(Exception): + pass class Api(object): - def __init__(self, rpc_mechanism): + def __init__(self): + """build endpoints here""" self.controllers = { "v1.0": RackspaceAuthenticationApi(), "servers": RackspaceCloudServerApi() } - self.rpc_mechanism = rpc_mechanism def handler(self, environ, responder): + """ + This is the entrypoint from wsgi. Read PEP 333 and wsgi.org for + more intormation. The key points are responder is a callback that + needs to run before you return, and takes two arguments, response + code string ("200 OK") and headers (["X-How-Cool-Am-I: Ultra-Suede"]) + and the return value is the body of the response. + """ environ['nova.context'] = self.build_context(environ) controller, path = wsgi.Util.route( environ['PATH_INFO'], self.controllers ) + logging.debug("Route %s to %s", str(path), str(controller)) if not controller: - # TODO(todd): Exception (404) - 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 + responder("404 Not Found", []) + return "" + try: + rv = controller.process(path, environ) + if type(rv) is tuple: + responder(rv[0], rv[1]) + rv = rv[2] + else: + responder("200 OK", []) + return rv + except Unauthorized: + responder("401 Unauthorized", []) + return "" + except NotFound: + responder("404 Not Found", []) + return "" + def build_context(self, env): rv = {} if env.has_key("HTTP_X_AUTH_TOKEN"): + # TODO(todd): once we make an actual unique token, this will change rv['user'] = users.UserManager.instance().get_user_from_access_key( - env['HTTP_X_AUTH_TOKEN'] - ) + env['HTTP_X_AUTH_TOKEN']) if rv['user']: rv['project'] = users.UserManager.instance().get_project( - rv['user'].name - ) + rv['user'].name) return rv class RackspaceApiEndpoint(object): def process(self, path, env): + """ + Main entrypoint for all controllers (what gets run by the wsgi handler). + Check authentication based on key, raise Unauthorized if invalid. + + Select the most appropriate action based on request type GET, POST, etc, + then pass it through to the implementing controller. Defalut to GET if + the implementing child doesn't respond to a particular type. + """ if not self.check_authentication(env): - # TODO(todd): Exception (Unauthorized) - raise Exception("Unable to authenticate") - - if len(path) == 0: + raise Unauthorized("Unable to authenticate") + + method = env['REQUEST_METHOD'].lower() + callback = getattr(self, method, None) + if not callback: + callback = getattr(self, "get") + logging.debug("%s processing %s with %s", self, method, callback) + return callback(path, env) + + def get(self, path, env): + """ + The default GET will look at the path and call an appropriate + action within this controller based on the the structure of the path. + + Given the following path lengths (with the first part stripped of by + router, as it is the controller name): + = 0 -> index + = 1 -> first component (/servers/details -> details) + >= 2 -> second path component (/servers/ID/ips/* -> ips) + + This should return + A String if 200 OK and no additional headers + (CODE, HEADERS, BODY) for custom response code and headers + """ + if len(path) == 0 and hasattr(self, "index"): + logging.debug("%s running index", self) return self.index(env) + if len(path) >= 2: + action = path[1] + else: + action = path.pop(0) - action = path.pop(0) + logging.debug("%s running action %s", self, action) if hasattr(self, action): method = getattr(self, action) return method(path, env) else: - # TODO(todd): Exception (404) - raise Exception("Missing method %s" % path[0]) + raise NotFound("Missing method %s" % path[0]) def check_authentication(self, env): - if hasattr(self, "process_without_authentication") \ - and getattr(self, "process_without_authentication"): - return True if not env['nova.context']['user']: return False return True -class RackspaceAuthenticationApi(RackspaceApiEndpoint): - - def __init__(self): - self.process_without_authentication = True +class RackspaceAuthenticationApi(object): # TODO(todd): make a actual session with a unique token # just pass the auth key back through for now - def index(self, env): + def index(self, _path, env): response = '204 No Content' headers = [ ('X-Server-Management-Url', 'http://%s' % env['HTTP_HOST']), @@ -141,20 +187,25 @@ class RackspaceCloudServerApi(RackspaceApiEndpoint): self.instdir = model.InstanceDirectory() self.network = network.PublicNetworkController() + def post(self, path, env): + if len(path) == 0: + return self.launch_server(env) + + def delete(self, path_parts, env): + if self.delete_server(path_parts[0]): + return ("202 Accepted", [], "") + else: + return ("404 Not Found", [], + "Did not find image, or it was not in a running state") + + def index(self, env): - if env['REQUEST_METHOD'] == 'GET': - return self.detail(env) - elif env['REQUEST_METHOD'] == 'POST': - return self.launch_server(env) + return self.detail(env) def detail(self, args, env): - value = { - "servers": - [] - } + value = {"servers": []} for inst in self.instdir.all: value["servers"].append(self.instance_details(inst)) - return json.dumps(value) ## @@ -227,3 +278,21 @@ class RackspaceCloudServerApi(RackspaceApiEndpoint): "args": {"instance_id": inst.instance_id} } ) + + def delete_server(self, instance_id): + owner_hostname = self.host_for_instance(instance_id) + # it isn't launched? + if not owner_hostname: + return None + rpc_transport = "%s:%s" % (FLAGS.compute_topic, owner_hostname) + rpc.cast(rpc_transport, + {"method": "reboot_instance", + "args": {"instance_id": instance_id}}) + return True + + def host_for_instance(self, instance_id): + instance = model.Instance.lookup(instance_id) + if not instance: + return None + return instance["node_name"] + |
