summaryrefslogtreecommitdiffstats
path: root/nova
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 /nova
parent67d4e16a8c18989e73456f79220b97faa7374d92 (diff)
Share my updates to the Rackspace API.
Diffstat (limited to 'nova')
-rw-r--r--nova/endpoint/rackspace.py149
1 files changed, 109 insertions, 40 deletions
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"]
+