Share my updates to the Rackspace API.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
raise Unauthorized("Unable to authenticate")
|
||||
|
||||
if len(path) == 0:
|
||||
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"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user