From 897782eea966bd2258686b494eb87e2027ff8b30 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Mon, 21 Jun 2010 12:44:39 -0400 Subject: [PATCH] More rackspace API. --- bin/nova-compute | 2 +- exercise_rsapi.py | 33 ++++++++ nova/endpoint/rackspace.py | 157 ++++++++++++++++++++++++++++++++----- 3 files changed, 170 insertions(+), 22 deletions(-) create mode 100644 exercise_rsapi.py diff --git a/bin/nova-compute b/bin/nova-compute index bd3648d2..aa90f2c3 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -55,7 +55,7 @@ logging.getLogger().setLevel(logging.DEBUG) def main(): logging.warn('Starting compute node') - n = node.NetworkNode() + n = node.Node() d = n.adopt_instances() d.addCallback(lambda x: logging.info('Adopted %d instances', x)) diff --git a/exercise_rsapi.py b/exercise_rsapi.py new file mode 100644 index 00000000..4b9e65fc --- /dev/null +++ b/exercise_rsapi.py @@ -0,0 +1,33 @@ +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 7438e3bd..ac32d20a 100644 --- a/nova/endpoint/rackspace.py +++ b/nova/endpoint/rackspace.py @@ -40,18 +40,12 @@ from nova.endpoint import wsgi from nova.endpoint import images from nova.volume import storage -FLAGS = flags.FLAGS +FLAGS = flags.FLAGS flags.DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on') -def _gen_key(user_id, key_name): - """ Tuck this into UserManager """ - try: - manager = users.UserManager.instance() - private_key, fingerprint = manager.generate_key_pair(user_id, key_name) - except Exception as ex: - return {'exception': ex} - return {'private_key': private_key, 'fingerprint': fingerprint} + +# TODO(todd): subclass Exception so we can bubble meaningful errors class Api(object): @@ -59,14 +53,18 @@ class Api(object): def __init__(self, rpc_mechanism): self.controllers = { "v1.0": RackspaceAuthenticationApi(), - "server": RackspaceCloudServerApi() + "servers": RackspaceCloudServerApi() } self.rpc_mechanism = rpc_mechanism def handler(self, environ, responder): - logging.error("*** %s" % environ) - controller, path = wsgi.Util.route(environ['PATH_INFO'], self.controllers) + environ['nova.context'] = self.build_context(environ) + controller, path = wsgi.Util.route( + environ['PATH_INFO'], + self.controllers + ) if not controller: + # TODO(todd): Exception (404) raise Exception("Missing Controller") rv = controller.process(path, environ) if type(rv) is tuple: @@ -76,8 +74,25 @@ class Api(object): responder("200 OK", []) return rv + def build_context(self, env): + rv = {} + if env.has_key("HTTP_X_AUTH_TOKEN"): + rv['user'] = users.UserManager.instance().get_user_from_access_key( + env['HTTP_X_AUTH_TOKEN'] + ) + if rv['user']: + rv['project'] = users.UserManager.instance().get_project( + rv['user'].name + ) + return rv + + class RackspaceApiEndpoint(object): def process(self, path, env): + if not self.check_authentication(env): + # TODO(todd): Exception (Unauthorized) + raise Exception("Unable to authenticate") + if len(path) == 0: return self.index(env) @@ -86,26 +101,126 @@ class RackspaceApiEndpoint(object): method = getattr(self, action) return method(path, env) else: + # TODO(todd): Exception (404) raise Exception("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 + + # TODO(todd): make a actual session with a unique token + # just pass the auth key back through for now 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'), + ('X-Server-Management-Url', 'http://%s' % env['HTTP_HOST']), + ('X-Storage-Url', 'http://%s' % env['HTTP_HOST']), + ('X-CDN-Managment-Url', 'http://%s' % env['HTTP_HOST']), + ('X-Auth-Token', env['HTTP_X_AUTH_KEY']) ] body = "" return (response, headers, body) - -class RackspaceCloudServerApi(object): - def index(self): - return "IDX" +class RackspaceCloudServerApi(RackspaceApiEndpoint): - def list(self, args): - return "%s" % args + def __init__(self): + self.instdir = model.InstanceDirectory() + self.network = network.PublicNetworkController() + + def index(self, env): + if env['REQUEST_METHOD'] == 'GET': + return self.detail(env) + elif env['REQUEST_METHOD'] == 'POST': + return self.launch_server(env) + + def detail(self, args, env): + value = { + "servers": + [] + } + for inst in self.instdir.all: + value["servers"].append(self.instance_details(inst)) + + return json.dumps(value) + + ## + ## + + def launch_server(self, env): + data = json.loads(env['wsgi.input'].read(int(env['CONTENT_LENGTH']))) + inst = self.build_server_instance(data, env['nova.context']) + self.schedule_launch_of_instance(inst) + return json.dumps({"server": self.instance_details(inst)}) + + def instance_details(self, inst): + return { + "id": inst.get("instance_id", None), + "imageId": inst.get("image_id", None), + "flavorId": inst.get("instacne_type", None), + "hostId": inst.get("node_name", None), + "status": inst.get("state", "pending"), + "addresses": { + "public": [self.network.get_public_ip_for_instance( + inst.get("instance_id", None) + )], + "private": [inst.get("private_dns_name", None)] + }, + + # implemented only by Rackspace, not AWS + "name": inst.get("name", "Not-Specified"), + + # not supported + "progress": "Not-Supported", + "metadata": { + "Server Label": "Not-Supported", + "Image Version": "Not-Supported" + } + } + + def build_server_instance(self, env, context): + reservation = utils.generate_uid('r') + ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) + inst = self.instdir.new() + inst['name'] = env['server']['name'] + inst['image_id'] = env['server']['imageId'] + inst['instance_type'] = env['server']['flavorId'] + inst['user_id'] = context['user'].id + inst['project_id'] = context['project'].id + inst['reservation_id'] = reservation + inst['launch_time'] = ltime + inst['mac_address'] = utils.generate_mac() + address = network.allocate_ip( + inst['user_id'], + inst['project_id'], + mac=inst['mac_address'] + ) + inst['private_dns_name'] = str(address) + inst['bridge_name'] = network.BridgedNetwork.get_network_for_project( + inst['user_id'], + inst['project_id'], + 'default' # security group + )['bridge_name'] + # key_data, key_name, ami_launch_index + # TODO(todd): key data or root password + inst.save() + return inst + + def schedule_launch_of_instance(self, inst): + rpc.cast( + FLAGS.compute_topic, + { + "method": "run_instance", + "args": {"instance_id": inst.instance_id} + } + )