summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTodd Willey <todd@ansolabs.com>2010-07-26 23:19:51 -0400
committerTodd Willey <todd@ansolabs.com>2010-07-26 23:19:51 -0400
commit4044051266d97ffe05fbe75b642759d2e604da4d (patch)
treed29038afb528a064fed131d2cb349ca212c6905d
parent67d4e16a8c18989e73456f79220b97faa7374d92 (diff)
downloadnova-4044051266d97ffe05fbe75b642759d2e604da4d.tar.gz
nova-4044051266d97ffe05fbe75b642759d2e604da4d.tar.xz
nova-4044051266d97ffe05fbe75b642759d2e604da4d.zip
Share my updates to the Rackspace API.
-rwxr-xr-xbin/nova-rsapi13
-rw-r--r--exercise_rsapi.py51
-rw-r--r--nova/endpoint/rackspace.py149
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"]
+