Merged Todd and Michael's changes.
This commit is contained in:
@@ -35,23 +35,12 @@ FLAGS = flags.FLAGS
|
||||
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
|
||||
|
||||
def main(_argv):
|
||||
user_manager = manager.AuthManager()
|
||||
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()
|
||||
7
nova/endpoint/eventletserver.py
Normal file
7
nova/endpoint/eventletserver.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import eventlet
|
||||
import eventlet.wsgi
|
||||
eventlet.patcher.monkey_patch(all=False, socket=True)
|
||||
|
||||
def serve(app, port):
|
||||
sock = eventlet.listen(('0.0.0.0', port))
|
||||
eventlet.wsgi.server(sock, app)
|
||||
136
nova/endpoint/new_wsgi.py
Normal file
136
nova/endpoint/new_wsgi.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import eventletserver
|
||||
import carrot.connection
|
||||
import carrot.messaging
|
||||
import itertools
|
||||
import routes
|
||||
|
||||
|
||||
# See http://pythonpaste.org/webob/ for usage
|
||||
from webob.dec import wsgify
|
||||
from webob import exc, Request, Response
|
||||
|
||||
class WSGILayer(object):
|
||||
def __init__(self, application=None):
|
||||
self.application = application
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
# Subclasses will probably want to implement __call__ like this:
|
||||
#
|
||||
# @wsgify
|
||||
# def __call__(self, req):
|
||||
# # Any of the following objects work as responses:
|
||||
#
|
||||
# # Option 1: simple string
|
||||
# resp = 'message\n'
|
||||
#
|
||||
# # Option 2: a nicely formatted HTTP exception page
|
||||
# resp = exc.HTTPForbidden(detail='Nice try')
|
||||
#
|
||||
# # Option 3: a webob Response object (in case you need to play with
|
||||
# # headers, or you want to be treated like an iterable, or or or)
|
||||
# resp = Response(); resp.app_iter = open('somefile')
|
||||
#
|
||||
# # Option 4: any wsgi app to be run next
|
||||
# resp = self.application
|
||||
#
|
||||
# # Option 5: you can get a Response object for a wsgi app, too, to
|
||||
# # play with headers etc
|
||||
# resp = req.get_response(self.application)
|
||||
#
|
||||
#
|
||||
# # You can then just return your response...
|
||||
# return resp # option 1
|
||||
# # ... or set req.response and return None.
|
||||
# req.response = resp # option 2
|
||||
#
|
||||
# See the end of http://pythonpaste.org/webob/modules/dec.html
|
||||
# for more info.
|
||||
raise NotImplementedError("You must implement __call__")
|
||||
|
||||
|
||||
class WsgiStack(WSGILayer):
|
||||
def __init__(self, wsgi_layers):
|
||||
bottom_up = list(reversed(wsgi_layers))
|
||||
app, remaining = bottom_up[0], bottom_up[1:]
|
||||
for layer in remaining:
|
||||
layer.application = app
|
||||
app = layer
|
||||
super(WsgiStack, self).__init__(app)
|
||||
|
||||
@wsgify
|
||||
def __call__(self, req):
|
||||
return self.application
|
||||
|
||||
class Debug(WSGILayer):
|
||||
@wsgify
|
||||
def __call__(self, req):
|
||||
for k, v in req.environ.items():
|
||||
print k, "=", v
|
||||
return self.application
|
||||
|
||||
class Auth(WSGILayer):
|
||||
@wsgify
|
||||
def __call__(self, req):
|
||||
if not 'openstack.auth.token' in req.environ:
|
||||
# Check auth params here
|
||||
if True:
|
||||
req.environ['openstack.auth.token'] = '12345'
|
||||
else:
|
||||
return exc.HTTPForbidden(detail="Go away")
|
||||
|
||||
response = req.get_response(self.application)
|
||||
response.headers['X-Openstack-Auth'] = 'Success'
|
||||
return response
|
||||
|
||||
class Router(WSGILayer):
|
||||
def __init__(self, application=None):
|
||||
super(Router, self).__init__(application)
|
||||
self.map = routes.Mapper()
|
||||
self._connect()
|
||||
|
||||
@wsgify
|
||||
def __call__(self, req):
|
||||
match = self.map.match(req.path_info)
|
||||
if match is None:
|
||||
return self.application
|
||||
req.environ['openstack.match'] = match
|
||||
return match['controller']
|
||||
|
||||
def _connect(self):
|
||||
raise NotImplementedError("You must implement _connect")
|
||||
|
||||
class FileRouter(Router):
|
||||
def _connect(self):
|
||||
self.map.connect(None, '/files/{file}', controller=File())
|
||||
self.map.connect(None, '/rfiles/{file}', controller=Reverse(File()))
|
||||
|
||||
class Message(WSGILayer):
|
||||
@wsgify
|
||||
def __call__(self, req):
|
||||
return 'message\n'
|
||||
|
||||
class Reverse(WSGILayer):
|
||||
@wsgify
|
||||
def __call__(self, req):
|
||||
inner_resp = req.get_response(self.application)
|
||||
resp = Response()
|
||||
resp.app_iter = itertools.imap(lambda x: x[::-1], inner_resp.app_iter)
|
||||
return resp
|
||||
|
||||
class File(WSGILayer):
|
||||
@wsgify
|
||||
def __call__(self, req):
|
||||
try:
|
||||
myfile = open(req.environ['openstack.match']['file'])
|
||||
except IOError, e:
|
||||
raise exc.HTTPNotFound()
|
||||
req.response = Response()
|
||||
req.response.app_iter = myfile
|
||||
|
||||
wsgi_layers = [
|
||||
Auth(),
|
||||
Debug(),
|
||||
FileRouter(),
|
||||
Message(),
|
||||
]
|
||||
eventletserver.serve(app=WsgiStack(wsgi_layers), port=12345)
|
||||
@@ -26,8 +26,6 @@ import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import time
|
||||
import tornado.web
|
||||
from twisted.internet import defer
|
||||
|
||||
from nova import datastore
|
||||
from nova import exception
|
||||
@@ -45,34 +43,54 @@ 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 = {}
|
||||
@@ -89,38 +107,68 @@ class Api(object):
|
||||
|
||||
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):
|
||||
class RackspaceAuthenticationApi(object):
|
||||
|
||||
def __init__(self):
|
||||
self.process_without_authentication = True
|
||||
def process(self, path, env):
|
||||
return self.index(path, env)
|
||||
|
||||
# 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']),
|
||||
@@ -138,20 +186,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)
|
||||
|
||||
##
|
||||
@@ -224,3 +277,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