Adding serialization/deserilization for network resources.

Adding fake plugin
This commit is contained in:
Salvatore Orlando 2011-05-27 17:52:06 +01:00
parent 55599d7184
commit e8c29b8b96
6 changed files with 249 additions and 162 deletions

View File

@ -22,9 +22,7 @@
import gettext import gettext
import optparse import optparse
import os import os
import re
import sys import sys
import time
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), 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) gettext.install('quantum', unicode=1)
from quantum import service from quantum import service
from quantum.common import wsgi
from quantum.common import config from quantum.common import config
def create_options(parser): def create_options(parser):
@ -55,19 +52,10 @@ if __name__ == '__main__':
(options, args) = config.parse_options(oparser) (options, args) = config.parse_options(oparser)
try: try:
print "HERE-1"
print sys.path
service = service.serve_wsgi(service.QuantumApiService, service = service.serve_wsgi(service.QuantumApiService,
options=options, options=options,
args=args) args=args)
#version_conf, version_app = config.load_paste_app('quantumversion', options, args)
print "HERE-2"
service.wait() 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: except RuntimeError, e:
sys.exit("ERROR: %s" % e) sys.exit("ERROR: %s" % e)

View File

@ -13,22 +13,18 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import base64 import httplib
import logging import logging
import traceback
from webob import exc from webob import exc
from xml.dom import minidom from xml.dom import minidom
from quantum import manager from quantum import manager
from quantum import quantum_plugin_base
from quantum.common import exceptions as exception from quantum.common import exceptions as exception
from quantum.common import flags from quantum.common import flags
from quantum.common import wsgi from quantum.common import wsgi
from quantum import utils
from quantum.api import api_common as common
from quantum.api import faults from quantum.api import faults
import quantum.api from quantum.api.views import networks as networks_view
LOG = logging.getLogger('quantum.api.networks') LOG = logging.getLogger('quantum.api.networks')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@ -37,168 +33,114 @@ FLAGS = flags.FLAGS
class Controller(wsgi.Controller): class Controller(wsgi.Controller):
""" Network API controller for Quantum API """ """ 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 = { _serialization_metadata = {
"application/xml": { "application/xml": {
"attributes": { "attributes": {
"server": ["id", "imageId", "name", "flavorId", "hostId", "network": ["id","name"],
"status", "progress", "adminPass", "flavorRef",
"imageRef"],
"link": ["rel", "type", "href"], "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() self._setup_network_manager()
super(Controller, self).__init__() 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): def _setup_network_manager(self):
self.network_manager=manager.QuantumManager().get_manager() self.network_manager=manager.QuantumManager().get_manager()
def index(self, req, tenant_id): def index(self, req, tenant_id):
""" Returns a list of network names and ids """ """ Returns a list of network names and ids """
#TODO: this should be for a given tenant!!! #TODO: this should be for a given tenant!!!
LOG.debug("HERE - Controller.index")
return self._items(req, tenant_id, is_detail=False) return self._items(req, tenant_id, is_detail=False)
def _items(self, req, tenant_id, is_detail): def _items(self, req, tenant_id, is_detail):
""" Returns a list of networks. """ """ Returns a list of networks. """
test = self.network_manager.get_all_networks(tenant_id) networks = self.network_manager.get_all_networks(tenant_id)
#builder = self._get_view_builder(req) builder = networks_view.get_view_builder(req)
#servers = [builder.build(inst, is_detail)['server'] result = [builder.build(network, is_detail)['network']
# for inst in limited_list] for network in networks]
#return dict(servers=servers) return dict(networks=result)
return test
def show(self, req, tenant_id, id): def show(self, req, tenant_id, id):
""" Returns network details by network id """ """ Returns network details by network id """
try: 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: except exception.NotFound:
return faults.Fault(exc.HTTPNotFound()) 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 """ """ Destroys the network with the given id """
try: try:
return "TEST NETWORK DELETE" network_name = req.headers['x-network-name']
except exception.NotFound: except KeyError as e:
return faults.Fault(exc.HTTPNotFound()) 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() 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 ""

View File

@ -71,13 +71,9 @@ def bool_from_string(subject):
def import_class(import_str): def import_class(import_str):
"""Returns a class from a string including module and class""" """Returns a class from a string including module and class"""
mod_str, _sep, class_str = import_str.rpartition('.') mod_str, _sep, class_str = import_str.rpartition('.')
print "MOD_STR:%s SEP:%s CLASS_STR:%s" %(mod_str, _sep, class_str)
try: try:
#mod_str = os.path.join(FLAGS.state_path, mod_str) #mod_str = os.path.join(FLAGS.state_path, mod_str)
print "MODULE PATH:%s" %mod_str __import__(mod_str)
print "CUR DIR:%s" %os.getcwd()
__import__(mod_str, level=2)
print "IO SONO QUI"
return getattr(sys.modules[mod_str], class_str) return getattr(sys.modules[mod_str], class_str)
except (ImportError, ValueError, AttributeError) as e: except (ImportError, ValueError, AttributeError) as e:
print e print e
@ -193,8 +189,6 @@ def parse_isotime(timestr):
return datetime.datetime.strptime(timestr, TIME_FORMAT) return datetime.datetime.strptime(timestr, TIME_FORMAT)
def getPluginFromConfig(file="config.ini"): def getPluginFromConfig(file="config.ini"):
print "FILE:%s" %os.path.join(FLAGS.state_path, file)
print "Globals:%s" %globals()
Config = ConfigParser.ConfigParser() Config = ConfigParser.ConfigParser()
Config.read(os.path.join(FLAGS.state_path, file)) Config.read(os.path.join(FLAGS.state_path, file))
return Config.get("PLUGIN", "provider") return Config.get("PLUGIN", "provider")

View File

@ -126,7 +126,7 @@ class Request(webob.Request):
""" """
parts = self.path.rsplit('.', 1) parts = self.path.rsplit('.', 1)
LOG.debug("Request parts:%s",parts)
if len(parts) > 1: if len(parts) > 1:
format = parts[1] format = parts[1]
if format in ['json', 'xml']: if format in ['json', 'xml']:
@ -134,7 +134,7 @@ class Request(webob.Request):
ctypes = ['application/json', 'application/xml'] ctypes = ['application/json', 'application/xml']
bm = self.accept.best_match(ctypes) bm = self.accept.best_match(ctypes)
LOG.debug("BM:%s",bm)
return bm or 'application/json' return bm or 'application/json'
def get_content_type(self): def get_content_type(self):
@ -281,7 +281,7 @@ class Router(object):
mapper.connect(None, "/svrlist", controller=sc, action="list") mapper.connect(None, "/svrlist", controller=sc, action="list")
# Actions are all implicitly defined # 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 # Pointing to an arbitrary WSGI app. You can specify the
# {path_info:.*} parameter so the target app can be handed just that # {path_info:.*} parameter so the target app can be handed just that
@ -349,6 +349,8 @@ class Controller(object):
if type(result) is dict: if type(result) is dict:
content_type = req.best_match_content_type() 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) default_xmlns = self.get_default_xmlns(req)
body = self._serialize(result, content_type, default_xmlns) body = self._serialize(result, content_type, default_xmlns)
@ -495,8 +497,9 @@ class Serializer(object):
xmlns = metadata.get('xmlns', None) xmlns = metadata.get('xmlns', None)
if xmlns: if xmlns:
result.setAttribute('xmlns', xmlns) result.setAttribute('xmlns', xmlns)
LOG.debug("DATA:%s",data)
if type(data) is list: if type(data) is list:
LOG.debug("TYPE IS LIST")
collections = metadata.get('list_collections', {}) collections = metadata.get('list_collections', {})
if nodename in collections: if nodename in collections:
metadata = collections[nodename] metadata = collections[nodename]
@ -515,6 +518,7 @@ class Serializer(object):
node = self._to_xml_node(doc, metadata, singular, item) node = self._to_xml_node(doc, metadata, singular, item)
result.appendChild(node) result.appendChild(node)
elif type(data) is dict: elif type(data) is dict:
LOG.debug("TYPE IS DICT")
collections = metadata.get('dict_collections', {}) collections = metadata.get('dict_collections', {})
if nodename in collections: if nodename in collections:
metadata = collections[nodename] metadata = collections[nodename]
@ -534,6 +538,7 @@ class Serializer(object):
result.appendChild(node) result.appendChild(node)
else: else:
# Type is atom # Type is atom
LOG.debug("TYPE IS ATOM:%s",data)
node = doc.createTextNode(str(data)) node = doc.createTextNode(str(data))
result.appendChild(node) result.appendChild(node)
return result return result

View File

@ -1,3 +1,3 @@
[PLUGIN] [PLUGIN]
# Quantum plugin provider module # Quantum plugin provider module
provider = quantum.plugins.SamplePlugin.DummyDataPlugin provider = quantum.plugins.SamplePlugin.FakePlugin

View File

@ -259,4 +259,162 @@ class DummyDataPlugin(object):
# returns a list of all attached remote interfaces # returns a list of all attached remote interfaces
vifs_on_net = ["/tenant1/networks/net_id/portid/vif2.0", "/tenant1/networks/10/121/vif1.1"] vifs_on_net = ["/tenant1/networks/net_id/portid/vif2.0", "/tenant1/networks/10/121/vif1.1"]
return vifs_on_net 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
<network_uuid, network_name> 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