diff --git a/bin/quantum b/bin/quantum index 0c4a5dd6c..0913c31c0 100755 --- a/bin/quantum +++ b/bin/quantum @@ -22,9 +22,7 @@ import gettext import optparse import os -import re import sys -import time possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), @@ -36,7 +34,6 @@ if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')): gettext.install('quantum', unicode=1) from quantum import service -from quantum.common import wsgi from quantum.common import config def create_options(parser): @@ -55,19 +52,10 @@ if __name__ == '__main__': (options, args) = config.parse_options(oparser) try: - print "HERE-1" - print sys.path 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) diff --git a/quantum/api/networks.py b/quantum/api/networks.py index 5700a1996..88d56fcb8 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -13,22 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. -import base64 +import httplib 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 +from quantum.api.views import networks as networks_view LOG = logging.getLogger('quantum.api.networks') FLAGS = flags.FLAGS @@ -37,168 +33,114 @@ FLAGS = flags.FLAGS class Controller(wsgi.Controller): """ Network API controller for Quantum API """ - #TODO (salvatore-orlando): adjust metadata for quantum + _network_ops_param_list = [{ + 'param-name': 'network-name', + 'required': True},] + _serialization_metadata = { "application/xml": { "attributes": { - "server": ["id", "imageId", "name", "flavorId", "hostId", - "status", "progress", "adminPass", "flavorRef", - "imageRef"], + "network": ["id","name"], "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 __init__(self): + def __init__(self, plugin_conf_file=None): self._setup_network_manager() super(Controller, self).__init__() + def _parse_request_params(self, req, params): + results = {} + for param in params: + param_name = param['param-name'] + # 1- parse request body + # 2- parse request headers + # prepend param name with a 'x-' prefix + param_value = req.headers.get("x-" + param_name, None) + # 3- parse request query parameters + if not param_value: + param_value = req.str_GET[param_name] + if not param_value and param['required']: + msg = ("Failed to parse request. " + + "Parameter: %(param)s not specified" % locals()) + for line in msg.split('\n'): + LOG.error(line) + raise exc.HTTPBadRequest(msg) + results[param_name]=param_value + return results + def _setup_network_manager(self): self.network_manager=manager.QuantumManager().get_manager() def index(self, req, tenant_id): """ Returns a list of network names and ids """ #TODO: this should be for a given tenant!!! - LOG.debug("HERE - Controller.index") return self._items(req, tenant_id, is_detail=False) def _items(self, req, tenant_id, is_detail): """ Returns a list of networks. """ - test = self.network_manager.get_all_networks(tenant_id) - #builder = self._get_view_builder(req) - #servers = [builder.build(inst, is_detail)['server'] - # for inst in limited_list] - #return dict(servers=servers) - return test + networks = self.network_manager.get_all_networks(tenant_id) + builder = networks_view.get_view_builder(req) + result = [builder.build(network, is_detail)['network'] + for network in networks] + return dict(networks=result) def show(self, req, tenant_id, id): """ Returns network details by network id """ try: - return "SHOW NETWORK %s FOR TENANT %s" %(id,tenant_id) + network = self.network_manager.get_network_details( + tenant_id,id) + builder = networks_view.get_view_builder(req) + #build response with details + result = builder.build(network, True) + return dict(networks=result) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - def delete(self, req, id): + def create(self, req, tenant_id): + """ Creates a new network for a given tenant """ + #look for network name in request + req_params = \ + self._parse_request_params(req, self._network_ops_param_list) + network = self.network_manager.create_network(tenant_id, req_params['network-name']) + builder = networks_view.get_view_builder(req) + result = builder.build(network) + return dict(networks=result) + + def update(self, req, tenant_id, id): + """ Updates the name for the network with the given id """ + try: + network_name = req.headers['x-network-name'] + except KeyError as e: + msg = ("Failed to create network. Got error: %(e)s" % locals()) + for line in msg.split('\n'): + LOG.error(line) + raise exc.HTTPBadRequest(msg) + + network = self.network_manager.rename_network(tenant_id, + id,network_name) + if not network: + raise exc.HTTPNotFound("Network %(id)s could not be found" % locals()) + builder = networks_view.get_view_builder(req) + result = builder.build(network, True) + return dict(networks=result) + + + def delete(self, req, tenant_id, id): """ Destroys the network with the given id """ try: - return "TEST NETWORK DELETE" - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + network_name = req.headers['x-network-name'] + except KeyError as e: + msg = ("Failed to create network. Got error: %(e)s" % locals()) + for line in msg.split('\n'): + LOG.error(line) + raise exc.HTTPBadRequest(msg) + + network = self.network_manager.delete_network(tenant_id, id) + if not network: + raise exc.HTTPNotFound("Network %(id)s could not be found" % locals()) + 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 "" diff --git a/quantum/common/utils.py b/quantum/common/utils.py index 1317bc157..c80ac9ccc 100644 --- a/quantum/common/utils.py +++ b/quantum/common/utils.py @@ -71,13 +71,9 @@ def bool_from_string(subject): def import_class(import_str): """Returns a class from a string including module and class""" mod_str, _sep, class_str = import_str.rpartition('.') - print "MOD_STR:%s SEP:%s CLASS_STR:%s" %(mod_str, _sep, class_str) try: #mod_str = os.path.join(FLAGS.state_path, mod_str) - print "MODULE PATH:%s" %mod_str - print "CUR DIR:%s" %os.getcwd() - __import__(mod_str, level=2) - print "IO SONO QUI" + __import__(mod_str) return getattr(sys.modules[mod_str], class_str) except (ImportError, ValueError, AttributeError) as e: print e @@ -193,8 +189,6 @@ def parse_isotime(timestr): return datetime.datetime.strptime(timestr, TIME_FORMAT) def getPluginFromConfig(file="config.ini"): - print "FILE:%s" %os.path.join(FLAGS.state_path, file) - print "Globals:%s" %globals() Config = ConfigParser.ConfigParser() Config.read(os.path.join(FLAGS.state_path, file)) return Config.get("PLUGIN", "provider") diff --git a/quantum/common/wsgi.py b/quantum/common/wsgi.py index 277b482d6..4caeab9ab 100644 --- a/quantum/common/wsgi.py +++ b/quantum/common/wsgi.py @@ -126,7 +126,7 @@ class Request(webob.Request): """ parts = self.path.rsplit('.', 1) - + LOG.debug("Request parts:%s",parts) if len(parts) > 1: format = parts[1] if format in ['json', 'xml']: @@ -134,7 +134,7 @@ class Request(webob.Request): ctypes = ['application/json', 'application/xml'] bm = self.accept.best_match(ctypes) - + LOG.debug("BM:%s",bm) return bm or 'application/json' def get_content_type(self): @@ -281,7 +281,7 @@ class Router(object): mapper.connect(None, "/svrlist", controller=sc, action="list") # Actions are all implicitly defined - mapper.resource("server", "servers", controller=sc) + mapper.resource("network", "networks", controller=nc) # Pointing to an arbitrary WSGI app. You can specify the # {path_info:.*} parameter so the target app can be handed just that @@ -349,6 +349,8 @@ class Controller(object): if type(result) is dict: content_type = req.best_match_content_type() + LOG.debug("Content type:%s",content_type) + LOG.debug("Result:%s",result) default_xmlns = self.get_default_xmlns(req) body = self._serialize(result, content_type, default_xmlns) @@ -495,8 +497,9 @@ class Serializer(object): xmlns = metadata.get('xmlns', None) if xmlns: result.setAttribute('xmlns', xmlns) - + LOG.debug("DATA:%s",data) if type(data) is list: + LOG.debug("TYPE IS LIST") collections = metadata.get('list_collections', {}) if nodename in collections: metadata = collections[nodename] @@ -515,6 +518,7 @@ class Serializer(object): node = self._to_xml_node(doc, metadata, singular, item) result.appendChild(node) elif type(data) is dict: + LOG.debug("TYPE IS DICT") collections = metadata.get('dict_collections', {}) if nodename in collections: metadata = collections[nodename] @@ -534,6 +538,7 @@ class Serializer(object): result.appendChild(node) else: # Type is atom + LOG.debug("TYPE IS ATOM:%s",data) node = doc.createTextNode(str(data)) result.appendChild(node) return result diff --git a/quantum/plugins.ini b/quantum/plugins.ini index e6dc080d4..307d2b48d 100644 --- a/quantum/plugins.ini +++ b/quantum/plugins.ini @@ -1,3 +1,3 @@ [PLUGIN] # Quantum plugin provider module -provider = quantum.plugins.SamplePlugin.DummyDataPlugin +provider = quantum.plugins.SamplePlugin.FakePlugin diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 5088b71fa..431f5fbc7 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -259,4 +259,162 @@ class DummyDataPlugin(object): # returns a list of all attached remote interfaces vifs_on_net = ["/tenant1/networks/net_id/portid/vif2.0", "/tenant1/networks/10/121/vif1.1"] return vifs_on_net + + +class FakePlugin(object): + """ + FakePlugin is a demo plugin that provides + in-memory data structures to aid in quantum + client/cli/api development + """ + def __init__(self): + #add a first sample network on init + self._networks={'001': + { + 'net-id':'001', + 'net-name':'pippotest' + }, + '002': + { + 'net-id':'002', + 'net-name':'cicciotest' + }} + self._net_counter=len(self._networks) + + def get_all_networks(self, tenant_id): + """ + Returns a dictionary containing all + for + the specified tenant. + """ + print("get_all_networks() called\n") + return self._networks.values() + + def get_network_details(self, tenant_id, net_id): + """ + retrieved a list of all the remote vifs that + are attached to the network + """ + print("get_network_details() called\n") + return self._networks.get(net_id) + + + def create_network(self, tenant_id, net_name): + """ + Creates a new Virtual Network, and assigns it + a symbolic name. + """ + print("create_network() called\n") + self._net_counter += 1 + new_net_id=("0" * (3 - len(str(self._net_counter)))) + \ + str(self._net_counter) + print new_net_id + new_net_dict={'net-id':new_net_id, + 'net-name':net_name} + self._networks[new_net_id]=new_net_dict + # return network_id of the created network + return new_net_dict + + + def delete_network(self, tenant_id, net_id): + """ + Deletes the network with the specified network identifier + belonging to the specified tenant. + """ + print("delete_network() called\n") + net = self._networks.get(net_id) + if net: + self._networks.pop(net_id) + return net + return None + + + def rename_network(self, tenant_id, net_id, new_name): + """ + Updates the symbolic name belonging to a particular + Virtual Network. + """ + print("rename_network() called\n") + net = self._networks.get(net_id, None) + if net: + net['net-name']=new_name + return net + return None + + + #TODO - neeed to update methods from this point onwards + def get_all_ports(self, tenant_id, net_id): + """ + Retrieves all port identifiers belonging to the + specified Virtual Network. + """ + print("get_all_ports() called\n") + port_ids_on_net = ["2", "3", "4"] + return port_ids_on_net + + + def create_port(self, tenant_id, net_id): + """ + Creates a port on the specified Virtual Network. + """ + print("create_port() called\n") + #return the port id + return 201 + + + def delete_port(self, tenant_id, net_id, port_id): + """ + Deletes a port on a specified Virtual Network, + if the port contains a remote interface attachment, + the remote interface is first un-plugged and then the port + is deleted. + """ + print("delete_port() called\n") + + + def get_port_details(self, tenant_id, net_id, port_id): + """ + This method allows the user to retrieve a remote interface + that is attached to this particular port. + """ + print("get_port_details() called\n") + #returns the remote interface UUID + return "/tenant1/networks/net_id/portid/vif2.1" + + + def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id): + """ + Attaches a remote interface to the specified port on the + specified Virtual Network. + """ + print("plug_interface() called\n") + + + def unplug_interface(self, tenant_id, net_id, port_id): + """ + Detaches a remote interface from the specified port on the + specified Virtual Network. + """ + print("unplug_interface() called\n") + + + def get_interface_details(self, tenant_id, net_id, port_id): + """ + Retrieves the remote interface that is attached at this + particular port. + """ + print("get_interface_details() called\n") + #returns the remote interface UUID + return "/tenant1/networks/net_id/portid/vif2.0" + + + def get_all_attached_interfaces(self, tenant_id, net_id): + """ + Retrieves all remote interfaces that are attached to + a particular Virtual Network. + """ + print("get_all_attached_interfaces() called\n") + # returns a list of all attached remote interfaces + vifs_on_net = ["/tenant1/networks/net_id/portid/vif2.0", "/tenant1/networks/10/121/vif1.1"] + return vifs_on_net \ No newline at end of file