gettext.install('quantum', unicode=1)
+from quantum import service
from quantum.common import wsgi
from quantum.common import config
(options, args) = config.parse_options(oparser)
try:
- conf, app = config.load_paste_app('quantumversionapp', options, args)
- server = wsgi.Server()
- server.start(app, int(conf['bind_port']), conf['bind_host'])
- server.wait()
+ print "HERE-1"
+ service = service.serve_wsgi(service.QuantumApiService,
+ options=options,
+ args=args)
+ #version_conf, version_app = config.load_paste_app('quantumversion', options, args)
+ print "HERE-2"
+ service.wait()
+ #api_conf, api_app = config.load_paste_app('quantum', options, args)
+ #server = wsgi.Server()
+ #server.start(version_app, int(version_conf['bind_port']), version_conf['bind_host'])
+ #server.start(api_app, int(api_conf['bind_port']), api_conf['bind_host'])
+ #server.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)
# Port the bind the API server to
bind_port = 9696
-#[app:quantum]
-#paste.app_factory = quantum.service:app_factory
+[composite:quantum]
+use = egg:Paste#urlmap
+/: quantumversions
+/v0.1: quantumapi
-[app:quantumversionapp]
+[app:quantumversions]
paste.app_factory = quantum.api.versions:Versions.factory
+[app:quantumapi]
+paste.app_factory = quantum.api:APIRouterV01.factory
+
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-# @author: Somik Behera, Nicira Networks, Inc.
\ No newline at end of file
+# @author: Salvatore Orlando, Citrix Systems
+
+"""
+Quantum API controllers.
+"""
+
+import logging
+import routes
+import webob.dec
+import webob.exc
+
+from quantum.api import faults
+from quantum.api import networks
+from quantum.common import flags
+from quantum.common import wsgi
+
+
+LOG = logging.getLogger('quantum.api')
+FLAGS = flags.FLAGS
+
+class FaultWrapper(wsgi.Middleware):
+ """Calls down the middleware stack, making exceptions into faults."""
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ try:
+ return req.get_response(self.application)
+ except Exception as ex:
+ LOG.exception(_("Caught error: %s"), unicode(ex))
+ exc = webob.exc.HTTPInternalServerError(explanation=unicode(ex))
+ return faults.Fault(exc)
+
+
+class APIRouterV01(wsgi.Router):
+ """
+ Routes requests on the Quantum API to the appropriate controller
+ """
+
+ def __init__(self, ext_mgr=None):
+ mapper = routes.Mapper()
+ self._setup_routes(mapper)
+ super(APIRouterV01, self).__init__(mapper)
+
+ def _setup_routes(self, mapper):
+ #server_members = self.server_members
+ #server_members['action'] = 'POST'
+
+ #server_members['pause'] = 'POST'
+ #server_members['unpause'] = 'POST'
+ #server_members['diagnostics'] = 'GET'
+ #server_members['actions'] = 'GET'
+ #server_members['suspend'] = 'POST'
+ #server_members['resume'] = 'POST'
+ #server_members['rescue'] = 'POST'
+ #server_members['unrescue'] = 'POST'
+ #server_members['reset_network'] = 'POST'
+ #server_members['inject_network_info'] = 'POST'
+
+ mapper.resource("network", "networks", controller=networks.Controller(),
+ collection={'detail': 'GET'})
+ print mapper
+ #mapper.resource("port", "ports", controller=ports.Controller(),
+ # collection=dict(public='GET', private='GET'),
+ # parent_resource=dict(member_name='network',
+ # collection_name='networks'))
+
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Citrix System.
+# 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.
+
+
+XML_NS_V01 = 'http://netstack.org/quantum/api/v0.1'
+XML_NS_V10 = 'http://netstack.org/quantum/api/v1.0'
+
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Citrix Systems.
+# 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 webob.dec
+import webob.exc
+
+from quantum.api import api_common as common
+from quantum.common import wsgi
+
+class Fault(webob.exc.HTTPException):
+ """Error codes for API faults"""
+
+ _fault_names = {
+ 400: "malformedRequest",
+ 401: "unauthorized",
+ 402: "networkNotFound",
+ 403: "requestedStateInvalid",
+ 460: "networkInUse",
+ 461: "alreadyAttached",
+ 462: "portInUse",
+ 470: "serviceUnavailable",
+ 471: "pluginFault"
+ }
+
+ def __init__(self, exception):
+ """Create a Fault for the given webob.exc.exception."""
+ self.wrapped_exc = exception
+
+ @webob.dec.wsgify(RequestClass=wsgi.Request)
+ def __call__(self, req):
+ """Generate a WSGI response based on the exception passed to ctor."""
+ # Replace the body with fault details.
+ code = self.wrapped_exc.status_int
+ fault_name = self._fault_names.get(code, "quantumServiceFault")
+ fault_data = {
+ fault_name: {
+ 'code': code,
+ 'message': self.wrapped_exc.explanation}}
+ #TODO (salvatore-orlando): place over-limit stuff here
+ # 'code' is an attribute on the fault tag itself
+ metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
+ default_xmlns = common.XML_NS_V10
+ serializer = wsgi.Serializer(metadata, default_xmlns)
+ content_type = req.best_match_content_type()
+ self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
+ self.wrapped_exc.content_type = content_type
+ return self.wrapped_exc
--- /dev/null
+# Copyright 2011 Citrix Systems.
+# 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 base64
+import logging
+import traceback
+
+from webob import exc
+from xml.dom import minidom
+
+from quantum import manager
+from quantum import quantum_plugin_base
+from quantum.common import exceptions as exception
+from quantum.common import flags
+from quantum.common import wsgi
+from quantum import utils
+from quantum.api import api_common as common
+from quantum.api import faults
+import quantum.api
+
+LOG = logging.getLogger('quantum.api.networks')
+FLAGS = flags.FLAGS
+
+
+class Controller(wsgi.Controller):
+ """ Network API controller for Quantum API """
+
+ #TODO (salvatore-orlando): adjust metadata for quantum
+ _serialization_metadata = {
+ "application/xml": {
+ "attributes": {
+ "server": ["id", "imageId", "name", "flavorId", "hostId",
+ "status", "progress", "adminPass", "flavorRef",
+ "imageRef"],
+ "link": ["rel", "type", "href"],
+ },
+ "dict_collections": {
+ "metadata": {"item_name": "meta", "item_key": "key"},
+ },
+ "list_collections": {
+ "public": {"item_name": "ip", "item_key": "addr"},
+ "private": {"item_name": "ip", "item_key": "addr"},
+ },
+ },
+ }
+
+ def index(self, request):
+ """ Returns a list of network names and ids """
+ #TODO: this should be for a given tenant!!!
+ print "PIPPO"
+ LOG.debug("HERE - index")
+ return self._items(request, is_detail=False)
+
+ def _items(self, req, is_detail):
+ """ Returns a list of networks. """
+ #TODO: we should return networks for a given tenant only
+ #TODO: network controller should be retrieved here!!!
+ test = { 'ciao':'bello','porco':'mondo' }
+ #builder = self._get_view_builder(req)
+ #servers = [builder.build(inst, is_detail)['server']
+ # for inst in limited_list]
+ #return dict(servers=servers)
+ return test
+
+ def show(self, req, id):
+ """ Returns network details by network id """
+ try:
+ return "TEST NETWORK DETAILS"
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+
+ def delete(self, req, id):
+ """ Destroys the network with the given id """
+ try:
+ return "TEST NETWORK DELETE"
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return exc.HTTPAccepted()
+
+ def create(self, req):
+ """ Creates a new network for a given tenant """
+ #env = self._deserialize_create(req)
+ #if not env:
+ # return faults.Fault(exc.HTTPUnprocessableEntity())
+ return "TEST NETWORK CREATE"
+
+ def _deserialize_create(self, request):
+ """
+ Deserialize a create request
+ Overrides normal behavior in the case of xml content
+ """
+ #if request.content_type == "application/xml":
+ # deserializer = ServerCreateRequestXMLDeserializer()
+ # return deserializer.deserialize(request.body)
+ #else:
+ # return self._deserialize(request.body, request.get_content_type())
+ pass
+
+ def update(self, req, id):
+ """ Updates the name for the network wit the given id """
+ if len(req.body) == 0:
+ raise exc.HTTPUnprocessableEntity()
+
+ inst_dict = self._deserialize(req.body, req.get_content_type())
+ if not inst_dict:
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+
+ try:
+ return "TEST NETWORK UPDATE"
+ except exception.NotFound:
+ return faults.Fault(exc.HTTPNotFound())
+ return exc.HTTPNoContent()
+
+
+class NetworkCreateRequestXMLDeserializer(object):
+ """
+ Deserializer to handle xml-formatted server create requests.
+
+ Handles standard server attributes as well as optional metadata
+ and personality attributes
+ """
+
+ def deserialize(self, string):
+ """Deserialize an xml-formatted server create request"""
+ dom = minidom.parseString(string)
+ server = self._extract_server(dom)
+ return {'server': server}
+
+ def _extract_server(self, node):
+ """Marshal the server attribute of a parsed request"""
+ server = {}
+ server_node = self._find_first_child_named(node, 'server')
+ for attr in ["name", "imageId", "flavorId"]:
+ server[attr] = server_node.getAttribute(attr)
+ metadata = self._extract_metadata(server_node)
+ if metadata is not None:
+ server["metadata"] = metadata
+ personality = self._extract_personality(server_node)
+ if personality is not None:
+ server["personality"] = personality
+ return server
+
+ def _extract_metadata(self, server_node):
+ """Marshal the metadata attribute of a parsed request"""
+ metadata_node = self._find_first_child_named(server_node, "metadata")
+ if metadata_node is None:
+ return None
+ metadata = {}
+ for meta_node in self._find_children_named(metadata_node, "meta"):
+ key = meta_node.getAttribute("key")
+ metadata[key] = self._extract_text(meta_node)
+ return metadata
+
+ def _extract_personality(self, server_node):
+ """Marshal the personality attribute of a parsed request"""
+ personality_node = \
+ self._find_first_child_named(server_node, "personality")
+ if personality_node is None:
+ return None
+ personality = []
+ for file_node in self._find_children_named(personality_node, "file"):
+ item = {}
+ if file_node.hasAttribute("path"):
+ item["path"] = file_node.getAttribute("path")
+ item["contents"] = self._extract_text(file_node)
+ personality.append(item)
+ return personality
+
+ def _find_first_child_named(self, parent, name):
+ """Search a nodes children for the first child with a given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ return node
+ return None
+
+ def _find_children_named(self, parent, name):
+ """Return all of a nodes children who have the given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ yield node
+
+ def _extract_text(self, node):
+ """Get the text field contained by the given node"""
+ if len(node.childNodes) == 1:
+ child = node.childNodes[0]
+ if child.nodeType == child.TEXT_NODE:
+ return child.nodeValue
+ return ""
problem loading the configuration file.
"""
conf_file = find_config_file(options, args)
+ print "Conf_file:%s" %conf_file
if not conf_file:
raise RuntimeError("Unable to locate any configuration file. "
"Cannot load application %s" % app_name)
try:
+ print "App_name:%s" %app_name
conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
return conf_file, conf
except Exception, e:
% (conf_file, e))
-def load_paste_app(app_name, options, args):
+def load_paste_app(conf_file, app_name):
"""
Builds and returns a WSGI app from a paste config file.
:raises RuntimeError when config file cannot be located or application
cannot be loaded from config file
"""
- conf_file, conf = load_paste_config(app_name, options, args)
+ #conf_file, conf = load_paste_config(app_name, options, args)
try:
- # Setup logging early, supplying both the CLI options and the
- # configuration mapping from the config file
- print "OPTIONS:%s" %options
- print "CONF:%s" %conf
- setup_logging(options, conf)
-
- # We only update the conf dict for the verbose and debug
- # flags. Everything else must be set up in the conf file...
- debug = options.get('debug') or \
- get_option(conf, 'debug', type='bool', default=False)
- verbose = options.get('verbose') or \
- get_option(conf, 'verbose', type='bool', default=False)
- conf['debug'] = debug
- conf['verbose'] = verbose
-
- # Log the options used when starting if we're in debug mode...
- LOG.debug("*" * 80)
- LOG.debug("Configuration options gathered from config file:")
- LOG.debug(conf_file)
- LOG.debug("================================================")
- items = dict([(k, v) for k, v in conf.items()
- if k not in ('__file__', 'here')])
- for key, value in sorted(items.items()):
- LOG.debug("%(key)-30s %(value)s" % locals())
- LOG.debug("*" * 80)
+ conf_file = os.path.abspath(conf_file)
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
except (LookupError, ImportError), e:
raise RuntimeError("Unable to load %(app_name)s from "
"configuration file %(conf_file)s."
"\nGot: %(e)r" % locals())
- return conf, app
+ return app
def get_option(options, option, **kwargs):
import sys
import ConfigParser
-from common import exceptions
+from quantum.common import exceptions
from exceptions import ProcessExecutionError
+
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2011, Nicira Networks, Inc.
WSGI middleware that maps incoming requests to WSGI apps.
"""
+ @classmethod
+ def factory(cls, global_config, **local_config):
+ """
+ Returns an instance of the WSGI Router class
+ """
+ return cls()
+
def __init__(self, mapper):
"""
Create a router for the given routes.Mapper.
MIME types to information needed to serialize to that type.
"""
_metadata = getattr(type(self), "_serialization_metadata", {})
- serializer = Serializer(request.environ, _metadata)
+ serializer = Serializer(_metadata)
return serializer.to_content_type(data)
# License for the specific language governing permissions and limitations
# under the License.
+import logging
import json
import routes
-from common import wsgi
+from quantum.common import config
+from quantum.common import wsgi
+from quantum.common import exceptions as exception
from webob import Response
+LOG = logging.getLogger('quantum.service')
-class NetworkController(wsgi.Controller):
+class WsgiService(object):
+ """Base class for WSGI based services.
- def version(self, request):
- return "Quantum version 0.1"
+ For each api you define, you must also define these flags:
+ :<api>_listen: The address on which to listen
+ :<api>_listen_port: The port on which to listen
+ """
-class API(wsgi.Router):
- def __init__(self, options):
- self.options = options
- mapper = routes.Mapper()
- network_controller = NetworkController()
- mapper.resource("net_controller", "/network",
- controller=network_controller)
- mapper.connect("/", controller=network_controller, action="version")
- super(API, self).__init__(mapper)
+ def __init__(self, app_name, conf_file, conf):
+ self.app_name = app_name
+ self.conf_file = conf_file
+ self.conf = conf
+ self.wsgi_app = None
+ def start(self):
+ self.wsgi_app = _run_wsgi(self.app_name, self.conf, self.conf_file)
-def app_factory(global_conf, **local_conf):
- conf = global_conf.copy()
- conf.update(local_conf)
- return API(conf)
+ def wait(self):
+ self.wsgi_app.wait()
+
+
+class QuantumApiService(WsgiService):
+ """Class for quantum-api service."""
+
+ @classmethod
+ def create(cls, conf=None, options=None, args=None):
+ app_name = "quantum"
+ if not conf:
+ conf_file, conf = config.load_paste_config(
+ app_name, options, args)
+ if not conf:
+ message = (_('No paste configuration found for: %s'),
+ app_name)
+ raise exception.Error(message)
+ print "OPTIONS:%s" %options
+ print "CONF:%s" %conf
+
+ # Setup logging early, supplying both the CLI options and the
+ # configuration mapping from the config file
+ # We only update the conf dict for the verbose and debug
+ # flags. Everything else must be set up in the conf file...
+ # Log the options used when starting if we're in debug mode...
+
+ config.setup_logging(options, conf)
+ debug = options.get('debug') or \
+ config.get_option(conf, 'debug',
+ type='bool', default=False)
+ verbose = options.get('verbose') or \
+ config.get_option(conf, 'verbose',
+ type='bool', default=False)
+ conf['debug'] = debug
+ conf['verbose'] = verbose
+ LOG.debug("*" * 80)
+ LOG.debug("Configuration options gathered from config file:")
+ LOG.debug(conf_file)
+ LOG.debug("================================================")
+ items = dict([(k, v) for k, v in conf.items()
+ if k not in ('__file__', 'here')])
+ for key, value in sorted(items.items()):
+ LOG.debug("%(key)-30s %(value)s" % locals())
+ LOG.debug("*" * 80)
+ service = cls(app_name, conf_file, conf)
+ return service
+
+
+def serve_wsgi(cls, conf=None, options = None, args = None):
+ try:
+ service = cls.create(conf, options, args)
+ except Exception:
+ logging.exception('in WsgiService.create()')
+ raise
+
+ service.start()
+
+ return service
+
+
+def _run_wsgi(app_name, paste_conf, paste_config_file):
+ print "CICCIO"
+ LOG.info(_('Using paste.deploy config at: %s'), paste_config_file)
+ app = config.load_paste_app(paste_config_file, app_name)
+ if not app:
+ LOG.error(_('No known API applications configured in %s.'),
+ paste_config_file)
+ return
+ server = wsgi.Server()
+ server.start(app,
+ int(paste_conf['bind_port']),paste_conf['bind_host'])
+ return server
+