Merging lp:~salvatore-orlando/quantum/quantum-api-alignment

Bug #813433: Align API implementation with specifcation (Critical)
Bug #821628: Getting network details is failing 	(Undecided)
Bug #823841: Remove constraint on network names 	(Medium)
Blueprint: Quantum API v1.0 Implementation (Essential)
This commit is contained in:
Salvatore Orlando 2011-08-25 10:43:36 +01:00
commit d5086c8b79
21 changed files with 493 additions and 251 deletions

View File

@ -17,7 +17,7 @@ api_extensions_path = extensions
[composite:quantum]
use = egg:Paste#urlmap
/: quantumversions
/v0.1: quantumapi
/v1.0: quantumapi
[pipeline:quantumapi]
pipeline = extensions quantumapiapp

View File

@ -26,6 +26,7 @@ import webob.exc
from quantum import manager
from quantum.api import faults
from quantum.api import attachments
from quantum.api import networks
from quantum.api import ports
from quantum.common import flags
@ -58,24 +59,29 @@ class APIRouterV01(wsgi.Router):
path_prefix=uri_prefix)
mapper.resource('port', 'ports',
controller=ports.Controller(plugin),
collection={'detail': 'GET'},
member={'detail': 'GET'},
parent_resource=dict(member_name='network',
collection_name=uri_prefix +\
'networks'))
attachments_ctrl = attachments.Controller(plugin)
mapper.connect("get_resource",
uri_prefix + 'networks/{network_id}/' \
'ports/{id}/attachment{.format}',
controller=ports.Controller(plugin),
controller=attachments_ctrl,
action="get_resource",
conditions=dict(method=['GET']))
mapper.connect("attach_resource",
uri_prefix + 'networks/{network_id}/' \
'ports/{id}/attachment{.format}',
controller=ports.Controller(plugin),
controller=attachments_ctrl,
action="attach_resource",
conditions=dict(method=['PUT']))
mapper.connect("detach_resource",
uri_prefix + 'networks/{network_id}/' \
'ports/{id}/attachment{.format}',
controller=ports.Controller(plugin),
controller=attachments_ctrl,
action="detach_resource",
conditions=dict(method=['DELETE']))

View File

@ -38,7 +38,7 @@ class QuantumController(wsgi.Controller):
for param in params:
param_name = param['param-name']
param_value = None
# 1- parse request body
# Parameters are expected to be in request body only
if req.body:
des_body = self._deserialize(req.body,
req.best_match_content_type())
@ -50,22 +50,13 @@ class QuantumController(wsgi.Controller):
LOG.error(line)
raise exc.HTTPBadRequest(msg)
param_value = data.get(param_name, None)
if not param_value:
# 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:
try:
param_value = req.str_GET[param_name]
except KeyError:
#param not found
pass
if not param_value and param['required']:
msg = ("Failed to parse request. " +
"Parameter: " + param_name + " not specified")
for line in msg.split('\n'):
LOG.error(line)
raise exc.HTTPBadRequest(msg)
# If the parameter wasn't found and it was required, return 400
if not param_value and param['required']:
msg = ("Failed to parse request. " +
"Parameter: " + param_name + " not specified")
for line in msg.split('\n'):
LOG.error(line)
raise exc.HTTPBadRequest(msg)
results[param_name] = param_value or param.get('default-value')
return results

View File

@ -0,0 +1,86 @@
# 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 logging
from webob import exc
from quantum.api import api_common as common
from quantum.api import faults
from quantum.api.views import attachments as attachments_view
from quantum.common import exceptions as exception
LOG = logging.getLogger('quantum.api.ports')
class Controller(common.QuantumController):
""" Port API controller for Quantum API """
_attachment_ops_param_list = [{
'param-name': 'id',
'required': True}, ]
_serialization_metadata = {
"application/xml": {
"attributes": {
"attachment": ["id"], }
},
}
def __init__(self, plugin):
self._resource_name = 'attachment'
super(Controller, self).__init__(plugin)
def get_resource(self, request, tenant_id, network_id, id):
try:
att_data = self._plugin.get_port_details(
tenant_id, network_id, id)
builder = attachments_view.get_view_builder(request)
result = builder.build(att_data)['attachment']
return dict(attachment=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
def attach_resource(self, request, tenant_id, network_id, id):
try:
request_params = \
self._parse_request_params(request,
self._attachment_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
self._plugin.plug_interface(tenant_id, network_id, id,
request_params['id'])
return exc.HTTPNoContent()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
except exception.PortInUse as e:
return faults.Fault(faults.PortInUse(e))
except exception.AlreadyAttached as e:
return faults.Fault(faults.AlreadyAttached(e))
def detach_resource(self, request, tenant_id, network_id, id):
try:
self._plugin.unplug_interface(tenant_id,
network_id, id)
return exc.HTTPNoContent()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))

View File

@ -31,7 +31,6 @@ class Fault(webob.exc.HTTPException):
401: "unauthorized",
420: "networkNotFound",
421: "networkInUse",
422: "networkNameExists",
430: "portNotFound",
431: "requestedStateInvalid",
432: "portInUse",
@ -92,22 +91,6 @@ class NetworkInUse(webob.exc.HTTPClientError):
explanation = ('Unable to remove the network: attachments still plugged.')
class NetworkNameExists(webob.exc.HTTPClientError):
"""
subclass of :class:`~HTTPClientError`
This indicates that the server could not set the network name to the
specified value because another network for the same tenant already has
that name.
code: 422, title: Network Name Exists
"""
code = 422
title = 'Network Name Exists'
explanation = ('Unable to set network name: tenant already has network' \
' with same name.')
class PortNotFound(webob.exc.HTTPClientError):
"""
subclass of :class:`~HTTPClientError`

View File

@ -29,7 +29,7 @@ class Controller(common.QuantumController):
""" Network API controller for Quantum API """
_network_ops_param_list = [{
'param-name': 'net-name',
'param-name': 'name',
'required': True}, ]
_serialization_metadata = {
@ -37,38 +37,43 @@ class Controller(common.QuantumController):
"attributes": {
"network": ["id", "name"],
"port": ["id", "state"],
},
"plurals": {"networks": "network"}
},
"attachment": ["id"]},
"plurals": {"networks": "network",
"ports": "port"}},
}
def __init__(self, plugin):
self._resource_name = 'network'
super(Controller, self).__init__(plugin)
def index(self, request, tenant_id):
""" Returns a list of network ids """
#TODO: this should be for a given tenant!!!
return self._items(request, tenant_id)
def _item(self, req, tenant_id, network_id,
net_details=True, port_details=False):
# We expect get_network_details to return information
# concerning logical ports as well.
network = self._plugin.get_network_details(
tenant_id, network_id)
port_list = self._plugin.get_all_ports(
tenant_id, network_id)
ports_data = [self._plugin.get_port_details(
tenant_id, network_id, port['port-id'])
for port in port_list]
builder = networks_view.get_view_builder(req)
result = builder.build(network, net_details, port_details)['network']
result = builder.build(network, net_details,
ports_data, port_details)['network']
return dict(network=result)
def _items(self, req, tenant_id, net_details=False, port_details=False):
def _items(self, req, tenant_id, net_details=False):
""" Returns a list of networks. """
networks = self._plugin.get_all_networks(tenant_id)
builder = networks_view.get_view_builder(req)
result = [builder.build(network, net_details, port_details)['network']
result = [builder.build(network, net_details)['network']
for network in networks]
return dict(networks=result)
def index(self, request, tenant_id):
""" Returns a list of network ids """
return self._items(request, tenant_id)
def show(self, request, tenant_id, id):
""" Returns network details for the given network id """
try:
@ -80,23 +85,13 @@ class Controller(common.QuantumController):
def detail(self, request, **kwargs):
tenant_id = kwargs.get('tenant_id')
network_id = kwargs.get('id')
try:
if network_id:
# show details for a given network
return self._item(request, tenant_id, network_id,
net_details=True, port_details=True)
else:
# show details for all networks
return self._items(request, tenant_id,
net_details=True, port_details=False)
network = self._plugin.get_network_details(
tenant_id, id)
builder = networks_view.get_view_builder(request)
#build response with details
result = builder.build(network, True)
return dict(networks=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
if network_id:
# show details for a given network
return self._item(request, tenant_id, network_id,
net_details=True, port_details=True)
else:
# show details for all networks
return self._items(request, tenant_id, net_details=True)
def create(self, request, tenant_id):
""" Creates a new network for a given tenant """
@ -107,15 +102,13 @@ class Controller(common.QuantumController):
self._network_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
network = self._plugin.\
create_network(tenant_id,
request_params['net-name'])
builder = networks_view.get_view_builder(request)
result = builder.build(network)
return dict(networks=result)
except exception.NetworkNameExists as e:
return faults.Fault(faults.NetworkNameExists(e))
network = self._plugin.\
create_network(tenant_id,
request_params['name'])
builder = networks_view.get_view_builder(request)
result = builder.build(network)['network']
#MUST RETURN 202???
return dict(network=result)
def update(self, request, tenant_id, id):
""" Updates the name for the network with the given id """
@ -127,18 +120,16 @@ class Controller(common.QuantumController):
return faults.Fault(e)
try:
self._plugin.rename_network(tenant_id, id,
request_params['net-name'])
return exc.HTTPAccepted()
request_params['name'])
return exc.HTTPNoContent()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.NetworkNameExists as e:
return faults.Fault(faults.NetworkNameExists(e))
def delete(self, request, tenant_id, id):
""" Destroys the network with the given id """
try:
self._plugin.delete_network(tenant_id, id)
return exc.HTTPAccepted()
return exc.HTTPNoContent()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.NetworkInUse as e:

View File

@ -29,55 +29,79 @@ class Controller(common.QuantumController):
""" Port API controller for Quantum API """
_port_ops_param_list = [{
'param-name': 'port-state',
'param-name': 'state',
'default-value': 'DOWN',
'required': False}, ]
_attachment_ops_param_list = [{
'param-name': 'attachment-id',
'required': True}, ]
_serialization_metadata = {
"application/xml": {
"attributes": {
"port": ["id", "state"], },
"plurals": {"ports": "port"}
},
"port": ["id", "state"],
"attachment": ["id"]},
"plurals": {"ports": "port"}},
}
def __init__(self, plugin):
self._resource_name = 'port'
super(Controller, self).__init__(plugin)
def index(self, request, tenant_id, network_id):
""" Returns a list of port ids for a given network """
return self._items(request, tenant_id, network_id, is_detail=False)
def _items(self, request, tenant_id, network_id, is_detail):
""" Returns a list of networks. """
def _items(self, request, tenant_id, network_id,
port_details=False):
""" Returns a list of ports. """
try:
ports = self._plugin.get_all_ports(tenant_id, network_id)
port_list = self._plugin.get_all_ports(tenant_id, network_id)
builder = ports_view.get_view_builder(request)
result = [builder.build(port, is_detail)['port']
for port in ports]
# Load extra data for ports if required.
if port_details:
port_list_detail = \
[self._plugin.get_port_details(
tenant_id, network_id, port['port-id'])
for port in port_list]
port_list = port_list_detail
result = [builder.build(port, port_details)['port']
for port in port_list]
return dict(ports=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
def _item(self, request, tenant_id, network_id, port_id,
att_details=False):
""" Returns a specific port. """
port = self._plugin.get_port_details(
tenant_id, network_id, port_id)
builder = ports_view.get_view_builder(request)
result = builder.build(port, port_details=True,
att_details=att_details)['port']
return dict(port=result)
def index(self, request, tenant_id, network_id):
""" Returns a list of port ids for a given network """
return self._items(request, tenant_id, network_id, port_details=False)
def show(self, request, tenant_id, network_id, id):
""" Returns port details for given port and network """
try:
port = self._plugin.get_port_details(
tenant_id, network_id, id)
builder = ports_view.get_view_builder(request)
#build response with details
result = builder.build(port, True)['port']
return dict(port=result)
return self._item(request, tenant_id, network_id, id)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
def detail(self, request, **kwargs):
tenant_id = kwargs.get('tenant_id')
network_id = kwargs.get('network_id')
port_id = kwargs.get('id')
if port_id:
# show details for a given network
return self._item(request, tenant_id,
network_id, port_id, att_details=True)
else:
# show details for all port
return self._items(request, tenant_id,
network_id, port_details=True)
def create(self, request, tenant_id, network_id):
""" Creates a new port for a given network """
#look for port state in request
@ -89,10 +113,10 @@ class Controller(common.QuantumController):
try:
port = self._plugin.create_port(tenant_id,
network_id,
request_params['port-state'])
request_params['state'])
builder = ports_view.get_view_builder(request)
result = builder.build(port)
return dict(ports=result)
result = builder.build(port)['port']
return dict(port=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.StateInvalid as e:
@ -107,11 +131,9 @@ class Controller(common.QuantumController):
except exc.HTTPError as e:
return faults.Fault(e)
try:
port = self._plugin.update_port(tenant_id, network_id, id,
request_params['port-state'])
builder = ports_view.get_view_builder(request)
result = builder.build(port, True)
return dict(ports=result)
self._plugin.update_port(tenant_id, network_id, id,
request_params['state'])
return exc.HTTPNoContent()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
@ -124,53 +146,10 @@ class Controller(common.QuantumController):
#look for port state in request
try:
self._plugin.delete_port(tenant_id, network_id, id)
return exc.HTTPAccepted()
# TODO(salvatore-orlando): Handle portInUse error
return exc.HTTPNoContent()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
except exception.PortInUse as e:
return faults.Fault(faults.PortInUse(e))
def get_resource(self, request, tenant_id, network_id, id):
try:
result = self._plugin.get_port_details(
tenant_id, network_id, id).get('attachment-id',
None)
return dict(attachment=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
def attach_resource(self, request, tenant_id, network_id, id):
try:
request_params = \
self._parse_request_params(request,
self._attachment_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
self._plugin.plug_interface(tenant_id,
network_id, id,
request_params['attachment-id'])
return exc.HTTPAccepted()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
except exception.PortInUse as e:
return faults.Fault(faults.PortInUse(e))
except exception.AlreadyAttached as e:
return faults.Fault(faults.AlreadyAttached(e))
def detach_resource(self, request, tenant_id, network_id, id):
try:
self._plugin.unplug_interface(tenant_id,
network_id, id)
return exc.HTTPAccepted()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))

View File

@ -31,11 +31,11 @@ class Versions(wsgi.Application):
"""Respond to a request for all Quantum API versions."""
version_objs = [
{
"id": "v0.1",
"id": "v1.0",
"status": "CURRENT",
},
{
"id": "v1.0",
"id": "v1.1",
"status": "FUTURE",
},
]

View File

@ -0,0 +1,37 @@
# 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.
def get_view_builder(req):
base_url = req.application_url
return ViewBuilder(base_url)
class ViewBuilder(object):
def __init__(self, base_url):
"""
:param base_url: url of the root wsgi application
"""
self.base_url = base_url
def build(self, attachment_data):
"""Generic method used to generate an attachment entity."""
if attachment_data['attachment']:
return dict(attachment=dict(id=attachment_data['attachment']))
else:
return dict(attachment={})

View File

@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
from quantum.api.views import ports as ports_view
def get_view_builder(req):
base_url = req.application_url
@ -31,17 +29,16 @@ class ViewBuilder(object):
"""
self.base_url = base_url
def build(self, network_data, net_detail=False, port_detail=False):
def build(self, network_data, net_detail=False,
ports_data=None, port_detail=False):
"""Generic method used to generate a network entity."""
if net_detail:
network = self._build_detail(network_data)
else:
network = self._build_simple(network_data)
if port_detail:
builder = ports_view.ViewBuilder(self.base_url)
ports = [builder.build(port_data, port_detail)['port']
for port_data in network_data['net-ports'].values()]
network['ports'] = ports
ports = [self._build_port(port_data) for port_data in ports_data]
network['network']['ports'] = ports
return network
def _build_simple(self, network_data):
@ -55,6 +52,8 @@ class ViewBuilder(object):
def _build_port(self, port_data):
"""Return details about a specific logical port."""
return dict(port=dict(id=port_data['port-id'],
state=port_data['port-state'],
attachment=port_data['attachment']))
port_dict = dict(id=port_data['port-id'],
state=port_data['port-state'])
if port_data['attachment']:
port_dict['attachment'] = dict(id=port_data['attachment'])
return port_dict

View File

@ -29,19 +29,11 @@ class ViewBuilder(object):
"""
self.base_url = base_url
def build(self, port_data, is_detail=False):
def build(self, port_data, port_details=False, att_details=False):
"""Generic method used to generate a port entity."""
if is_detail:
port = self._build_detail(port_data)
else:
port = self._build_simple(port_data)
port = dict(port=dict(id=port_data['port-id']))
if port_details:
port['port']['state'] = port_data['port-state']
if att_details and port_data['attachment']:
port['port']['attachment'] = dict(id=port_data['attachment'])
return port
def _build_simple(self, port_data):
"""Return a simple model of a port."""
return dict(port=dict(id=port_data['port-id']))
def _build_detail(self, port_data):
"""Return a simple model of a port (with its state)."""
return dict(port=dict(id=port_data['port-id'],
state=port_data['port-state']))

View File

@ -69,7 +69,7 @@ def api_create_net(client, *args):
LOG.debug(res)
nid = None
try:
nid = res["networks"]["network"]["id"]
nid = res["network"]["id"]
except Exception, e:
print "Failed to create network"
# TODO(bgh): grab error details from ws request result
@ -104,7 +104,7 @@ def detail_net(manager, *args):
def api_detail_net(client, *args):
tid, nid = args
try:
res = client.show_network_details(nid)["networks"]["network"]
res = client.show_network_details(nid)["network"]
except Exception, e:
LOG.error("Failed to get network details: %s" % e)
return
@ -121,7 +121,7 @@ def api_detail_net(client, *args):
pid = port["id"]
res = client.show_port_attachment(nid, pid)
LOG.debug(res)
remote_iface = res["attachment"]
remote_iface = res["attachment"]["id"]
print "\tRemote interface:%s" % remote_iface
@ -133,7 +133,7 @@ def rename_net(manager, *args):
def api_rename_net(client, *args):
tid, nid, name = args
data = {'network': {'net-name': '%s' % name}}
data = {'network': {'name': '%s' % name}}
try:
res = client.update_network(nid, data)
except Exception, e:
@ -179,7 +179,7 @@ def api_create_port(client, *args):
except Exception, e:
LOG.error("Failed to create port: %s" % e)
return
new_port = res["ports"]["port"]["id"]
new_port = res["port"]["id"]
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port, nid)
@ -214,16 +214,17 @@ def detail_port(manager, *args):
def api_detail_port(client, *args):
tid, nid, pid = args
try:
port = client.show_port_details(nid, pid)["ports"]["port"]
port = client.show_port_details(nid, pid)["port"]
att = client.show_port_attachment(nid, pid)
except Exception, e:
LOG.error("Failed to get port details: %s" % e)
return
id = port["id"]
attachment = port["attachment"]
id = port['id']
interface_id = att['id']
LOG.debug(port)
print "Virtual Port:%s on Virtual Network:%s " \
"contains remote interface:%s" % (pid, nid, attachment)
"contains remote interface:%s" % (pid, nid, interface_id)
def plug_iface(manager, *args):
@ -236,7 +237,7 @@ def plug_iface(manager, *args):
def api_plug_iface(client, *args):
tid, nid, pid, vid = args
try:
data = {'port': {'attachment-id': '%s' % vid}}
data = {'attachment': {'id': '%s' % vid}}
res = client.attach_resource(nid, pid, data)
except Exception, e:
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid,

View File

@ -27,13 +27,10 @@ EXCEPTIONS = {
401: exceptions.NotAuthorized,
420: exceptions.NetworkNotFound,
421: exceptions.NetworkInUse,
422: exceptions.NetworkNameExists,
430: exceptions.PortNotFound,
431: exceptions.StateInvalid,
432: exceptions.PortInUse,
440: exceptions.AlreadyAttached,
441: exceptions.AttachmentNotReady,
}
440: exceptions.AlreadyAttached}
class ApiCall(object):
@ -72,7 +69,7 @@ class Client(object):
def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
format="xml", testingStub=None, key_file=None, cert_file=None,
logger=None, action_prefix="/v0.1/tenants/{tenant_id}"):
logger=None, action_prefix="/v1.0/tenants/{tenant_id}"):
"""
Creates a new client to some service.

View File

@ -111,16 +111,6 @@ class AlreadyAttached(QuantumException):
"already plugged into port %(att_port_id)s")
class AttachmentNotReady(QuantumException):
message = _("The attachment %(att_id)s is not ready")
class NetworkNameExists(QuantumException):
message = _("Unable to set network name to %(net_name). " \
"Network with id %(net_id) already has this name for " \
"tenant %(tenant_id)")
class Duplicate(Error):
pass

View File

@ -122,6 +122,7 @@ class Request(webob.Request):
Based on the query extension then the Accept header.
"""
# First lookup http request
parts = self.path.rsplit('.', 1)
LOG.debug("Request parts:%s", parts)
if len(parts) > 1:
@ -129,21 +130,26 @@ class Request(webob.Request):
if format in ['json', 'xml']:
return 'application/{0}'.format(parts[1])
#Then look up content header
type_from_header = self.get_content_type()
if type_from_header:
return type_from_header
ctypes = ['application/json', 'application/xml']
#Finally search in Accept-* headers
bm = self.accept.best_match(ctypes)
return bm or 'application/json'
def get_content_type(self):
allowed_types = ("application/xml", "application/json")
if not "Content-Type" in self.headers:
msg = _("Missing Content-Type")
LOG.debug(msg)
raise webob.exc.HTTPBadRequest(msg)
LOG.debug(_("Missing Content-Type"))
return None
type = self.content_type
if type in allowed_types:
return type
LOG.debug(_("Wrong Content-Type: %s") % type)
raise webob.exc.HTTPBadRequest("Invalid content type")
return None
class Application(object):

View File

@ -17,6 +17,8 @@
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
import logging
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, exc
@ -27,6 +29,7 @@ from quantum.db import models
_ENGINE = None
_MAKER = None
BASE = models.BASE
LOG = logging.getLogger('quantum.db.api')
def configure_db(options):
@ -78,6 +81,11 @@ def unregister_models():
def _check_duplicate_net_name(tenant_id, net_name):
"""Checks whether a network with the same name
already exists for the tenant.
"""
#TODO(salvatore-orlando): Not used anymore - candidate for removal
session = get_session()
try:
net = session.query(models.Network).\
@ -94,7 +102,6 @@ def _check_duplicate_net_name(tenant_id, net_name):
def network_create(tenant_id, name):
session = get_session()
_check_duplicate_net_name(tenant_id, name)
with session.begin():
net = models.Network(tenant_id, name)
session.add(net)

View File

@ -70,13 +70,14 @@ class Port(BASE, QuantumBase):
uuid = Column(String(255), primary_key=True)
network_id = Column(String(255), ForeignKey("networks.uuid"),
nullable=False)
interface_id = Column(String(255))
interface_id = Column(String(255), nullable=True)
# Port state - Hardcoding string value at the moment
state = Column(String(8))
def __init__(self, network_id):
self.uuid = str(uuid.uuid4())
self.network_id = network_id
self.interface_id = None
self.state = "DOWN"
def __repr__(self):

View File

@ -352,7 +352,7 @@ class FakePlugin(object):
LOG.debug("FakePlugin.get_port_details() called")
port = self._get_port(tenant_id, net_id, port_id)
return {'port-id': str(port.uuid),
'attachment-id': port.interface_id,
'attachment': port.interface_id,
'port-state': port.state}
def create_port(self, tenant_id, net_id, port_state=None):
@ -407,10 +407,10 @@ class FakePlugin(object):
specified Virtual Network.
"""
LOG.debug("FakePlugin.plug_interface() called")
port = self._get_port(tenant_id, net_id, port_id)
# Validate attachment
self._validate_attachment(tenant_id, net_id, port_id,
remote_interface_id)
port = self._get_port(tenant_id, net_id, port_id)
if port['interface_id']:
raise exc.PortInUse(net_id=net_id, port_id=port_id,
att_id=port['interface_id'])

View File

@ -48,7 +48,7 @@ class APITest(unittest.TestCase):
if expected_res_status == 200:
network_data = Serializer().deserialize(network_res.body,
content_type)
return network_data['networks']['network']['id']
return network_data['network']['id']
def _create_port(self, network_id, port_state, format,
custom_req_body=None, expected_res_status=200):
@ -61,7 +61,7 @@ class APITest(unittest.TestCase):
self.assertEqual(port_res.status_int, expected_res_status)
if expected_res_status == 200:
port_data = Serializer().deserialize(port_res.body, content_type)
return port_data['ports']['port']['id']
return port_data['port']['id']
def _test_create_network(self, format):
LOG.debug("_test_create_network - format:%s - START", format)
@ -74,8 +74,7 @@ class APITest(unittest.TestCase):
self.assertEqual(show_network_res.status_int, 200)
network_data = Serializer().deserialize(show_network_res.body,
content_type)
self.assertEqual(network_id,
network_data['network']['id'])
self.assertEqual(network_id, network_data['network']['id'])
LOG.debug("_test_create_network - format:%s - END", format)
def _test_create_network_badrequest(self, format):
@ -102,6 +101,25 @@ class APITest(unittest.TestCase):
self.assertEqual(len(network_data['networks']), 2)
LOG.debug("_test_list_networks - format:%s - END", format)
def _test_list_networks_detail(self, format):
LOG.debug("_test_list_networks_detail - format:%s - START", format)
content_type = "application/%s" % format
self._create_network(format, "net_1")
self._create_network(format, "net_2")
list_network_req = testlib.network_list_detail_request(self.tenant_id,
format)
list_network_res = list_network_req.get_response(self.api)
self.assertEqual(list_network_res.status_int, 200)
network_data = self._net_serializer.deserialize(
list_network_res.body, content_type)
# Check network count: should return 2
self.assertEqual(len(network_data['networks']), 2)
# Check contents - id & name for each network
for network in network_data['networks']:
self.assertTrue('id' in network and 'name' in network)
self.assertTrue(network['id'] and network['name'])
LOG.debug("_test_list_networks_detail - format:%s - END", format)
def _test_show_network(self, format):
LOG.debug("_test_show_network - format:%s - START", format)
content_type = "application/%s" % format
@ -118,6 +136,25 @@ class APITest(unittest.TestCase):
network_data['network'])
LOG.debug("_test_show_network - format:%s - END", format)
def _test_show_network_detail(self, format):
LOG.debug("_test_show_network_detail - format:%s - START", format)
content_type = "application/%s" % format
# Create a network and a port
network_id = self._create_network(format)
port_id = self._create_port(network_id, "ACTIVE", format)
show_network_req = testlib.show_network_detail_request(
self.tenant_id, network_id, format)
show_network_res = show_network_req.get_response(self.api)
self.assertEqual(show_network_res.status_int, 200)
network_data = self._net_serializer.deserialize(
show_network_res.body, content_type)
self.assertEqual({'id': network_id,
'name': self.network_name,
'ports': [{'id': port_id,
'state': 'ACTIVE'}]},
network_data['network'])
LOG.debug("_test_show_network_detail - format:%s - END", format)
def _test_show_network_not_found(self, format):
LOG.debug("_test_show_network_not_found - format:%s - START", format)
show_network_req = testlib.show_network_request(self.tenant_id,
@ -137,7 +174,7 @@ class APITest(unittest.TestCase):
new_name,
format)
update_network_res = update_network_req.get_response(self.api)
self.assertEqual(update_network_res.status_int, 202)
self.assertEqual(update_network_res.status_int, 204)
show_network_req = testlib.show_network_request(self.tenant_id,
network_id,
format)
@ -187,7 +224,7 @@ class APITest(unittest.TestCase):
network_id,
format)
delete_network_res = delete_network_req.get_response(self.api)
self.assertEqual(delete_network_res.status_int, 202)
self.assertEqual(delete_network_res.status_int, 204)
list_network_req = testlib.network_list_request(self.tenant_id,
format)
list_network_res = list_network_req.get_response(self.api)
@ -213,7 +250,7 @@ class APITest(unittest.TestCase):
port_id,
attachment_id)
attachment_res = attachment_req.get_response(self.api)
self.assertEquals(attachment_res.status_int, 202)
self.assertEquals(attachment_res.status_int, 204)
LOG.debug("Deleting network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
@ -241,6 +278,27 @@ class APITest(unittest.TestCase):
self.assertEqual(len(port_data['ports']), 2)
LOG.debug("_test_list_ports - format:%s - END", format)
def _test_list_ports_detail(self, format):
LOG.debug("_test_list_ports_detail - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
self._create_port(network_id, port_state, format)
self._create_port(network_id, port_state, format)
list_port_req = testlib.port_list_detail_request(self.tenant_id,
network_id, format)
list_port_res = list_port_req.get_response(self.api)
self.assertEqual(list_port_res.status_int, 200)
port_data = self._port_serializer.deserialize(
list_port_res.body, content_type)
# Check port count: should return 2
self.assertEqual(len(port_data['ports']), 2)
# Check contents - id & name for each network
for port in port_data['ports']:
self.assertTrue('id' in port and 'state' in port)
self.assertTrue(port['id'] and port['state'])
LOG.debug("_test_list_ports_detail - format:%s - END", format)
def _test_show_port(self, format):
LOG.debug("_test_show_port - format:%s - START", format)
content_type = "application/%s" % format
@ -258,6 +316,44 @@ class APITest(unittest.TestCase):
port_data['port'])
LOG.debug("_test_show_port - format:%s - END", format)
def _test_show_port_detail(self, format):
LOG.debug("_test_show_port - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
# Part 1 - no attachment
show_port_req = testlib.show_port_detail_request(self.tenant_id,
network_id, port_id, format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 200)
port_data = self._port_serializer.deserialize(
show_port_res.body, content_type)
self.assertEqual({'id': port_id, 'state': port_state},
port_data['port'])
# Part 2 - plug attachment into port
interface_id = "test_interface"
put_attachment_req = testlib.put_attachment_request(self.tenant_id,
network_id,
port_id,
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 204)
show_port_req = testlib.show_port_detail_request(self.tenant_id,
network_id, port_id, format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 200)
port_data = self._port_serializer.deserialize(
show_port_res.body, content_type)
self.assertEqual({'id': port_id, 'state': port_state,
'attachment': {'id': interface_id}},
port_data['port'])
LOG.debug("_test_show_port_detail - format:%s - END", format)
def _test_show_port_networknotfound(self, format):
LOG.debug("_test_show_port_networknotfound - format:%s - START",
format)
@ -343,7 +439,7 @@ class APITest(unittest.TestCase):
network_id, port_id,
format)
delete_port_res = delete_port_req.get_response(self.api)
self.assertEqual(delete_port_res.status_int, 202)
self.assertEqual(delete_port_res.status_int, 204)
list_port_req = testlib.port_list_request(self.tenant_id, network_id,
format)
list_port_res = list_port_req.get_response(self.api)
@ -367,7 +463,7 @@ class APITest(unittest.TestCase):
port_id,
attachment_id)
attachment_res = attachment_req.get_response(self.api)
self.assertEquals(attachment_res.status_int, 202)
self.assertEquals(attachment_res.status_int, 204)
LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
delete_port_req = testlib.port_delete_request(self.tenant_id,
@ -417,7 +513,7 @@ class APITest(unittest.TestCase):
new_port_state,
format)
update_port_res = update_port_req.get_response(self.api)
self.assertEqual(update_port_res.status_int, 200)
self.assertEqual(update_port_res.status_int, 204)
show_port_req = testlib.show_port_request(self.tenant_id,
network_id, port_id,
format)
@ -427,6 +523,22 @@ class APITest(unittest.TestCase):
show_port_res.body, content_type)
self.assertEqual({'id': port_id, 'state': new_port_state},
port_data['port'])
# now set it back to the original value
update_port_req = testlib.update_port_request(self.tenant_id,
network_id, port_id,
port_state,
format)
update_port_res = update_port_req.get_response(self.api)
self.assertEqual(update_port_res.status_int, 204)
show_port_req = testlib.show_port_request(self.tenant_id,
network_id, port_id,
format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 200)
port_data = self._port_serializer.deserialize(
show_port_res.body, content_type)
self.assertEqual({'id': port_id, 'state': port_state},
port_data['port'])
LOG.debug("_test_set_port_state - format:%s - END", format)
def _test_set_port_state_networknotfound(self, format):
@ -491,7 +603,7 @@ class APITest(unittest.TestCase):
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 202)
self.assertEqual(put_attachment_res.status_int, 204)
get_attachment_req = testlib.get_attachment_request(self.tenant_id,
network_id,
port_id,
@ -499,7 +611,7 @@ class APITest(unittest.TestCase):
get_attachment_res = get_attachment_req.get_response(self.api)
attachment_data = Serializer().deserialize(get_attachment_res.body,
content_type)
self.assertEqual(attachment_data['attachment'], interface_id)
self.assertEqual(attachment_data['attachment']['id'], interface_id)
LOG.debug("_test_show_attachment - format:%s - END", format)
def _test_show_attachment_networknotfound(self, format):
@ -544,7 +656,7 @@ class APITest(unittest.TestCase):
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 202)
self.assertEqual(put_attachment_res.status_int, 204)
LOG.debug("_test_put_attachment - format:%s - END", format)
def _test_put_attachment_networknotfound(self, format):
@ -593,13 +705,13 @@ class APITest(unittest.TestCase):
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 202)
self.assertEqual(put_attachment_res.status_int, 204)
del_attachment_req = testlib.delete_attachment_request(self.tenant_id,
network_id,
port_id,
format)
del_attachment_res = del_attachment_req.get_response(self.api)
self.assertEqual(del_attachment_res.status_int, 202)
self.assertEqual(del_attachment_res.status_int, 204)
LOG.debug("_test_delete_attachment - format:%s - END", format)
def _test_delete_attachment_networknotfound(self, format):
@ -670,6 +782,12 @@ class APITest(unittest.TestCase):
def test_list_networks_xml(self):
self._test_list_networks('xml')
def test_list_networks_detail_json(self):
self._test_list_networks_detail('json')
def test_list_networks_detail_xml(self):
self._test_list_networks_detail('xml')
def test_create_network_json(self):
self._test_create_network('json')
@ -694,6 +812,12 @@ class APITest(unittest.TestCase):
def test_show_network_xml(self):
self._test_show_network('xml')
def test_show_network_detail_json(self):
self._test_show_network_detail('json')
def test_show_network_detail_xml(self):
self._test_show_network_detail('xml')
def test_delete_network_json(self):
self._test_delete_network('json')
@ -730,12 +854,24 @@ class APITest(unittest.TestCase):
def test_list_ports_xml(self):
self._test_list_ports('xml')
def test_list_ports_detail_json(self):
self._test_list_ports_detail('json')
def test_list_ports_detail_xml(self):
self._test_list_ports_detail('xml')
def test_show_port_json(self):
self._test_show_port('json')
def test_show_port_xml(self):
self._test_show_port('xml')
def test_show_port_detail_json(self):
self._test_show_port_detail('json')
def test_show_port_detail_xml(self):
self._test_show_port_detail('xml')
def test_show_port_networknotfound_json(self):
self._test_show_port_networknotfound('json')

View File

@ -43,7 +43,7 @@ class ServerStub():
return self.content
def status(self):
return status
return self.status
# To test error codes, set the host to 10.0.0.1, and the port to the code
def __init__(self, host, port=9696, key_file="", cert_file=""):

View File

@ -12,26 +12,45 @@ def create_request(path, body, content_type, method='GET'):
return req
def network_list_request(tenant_id, format='xml'):
def _network_list_request(tenant_id, format='xml', detail=False):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
detail_str = detail and '/detail' or ''
path = "/tenants/%(tenant_id)s/networks" \
"%(detail_str)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def network_list_request(tenant_id, format='xml'):
return _network_list_request(tenant_id, format)
def network_list_detail_request(tenant_id, format='xml'):
return _network_list_request(tenant_id, format, detail=True)
def _show_network_request(tenant_id, network_id, format='xml', detail=False):
method = 'GET'
detail_str = detail and '/detail' or ''
path = "/tenants/%(tenant_id)s/networks" \
"/%(network_id)s%(detail_str)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def show_network_request(tenant_id, network_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks" \
"/%(network_id)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
return _show_network_request(tenant_id, network_id, format)
def show_network_detail_request(tenant_id, network_id, format='xml'):
return _show_network_request(tenant_id, network_id, format, detail=True)
def new_network_request(tenant_id, network_name='new_name',
format='xml', custom_req_body=None):
method = 'POST'
path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
data = custom_req_body or {'network': {'net-name': '%s' % network_name}}
data = custom_req_body or {'network': {'name': '%s' % network_name}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
@ -42,7 +61,7 @@ def update_network_request(tenant_id, network_id, network_name, format='xml',
method = 'PUT'
path = "/tenants/%(tenant_id)s/networks" \
"/%(network_id)s.%(format)s" % locals()
data = custom_req_body or {'network': {'net-name': '%s' % network_name}}
data = custom_req_body or {'network': {'name': '%s' % network_name}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
@ -56,20 +75,41 @@ def network_delete_request(tenant_id, network_id, format='xml'):
return create_request(path, None, content_type, method)
def port_list_request(tenant_id, network_id, format='xml'):
def _port_list_request(tenant_id, network_id, format='xml', detail=False):
method = 'GET'
detail_str = detail and '/detail' or ''
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports.%(format)s" % locals()
"%(network_id)s/ports%(detail_str)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def port_list_request(tenant_id, network_id, format='xml'):
return _port_list_request(tenant_id, network_id, format)
def port_list_detail_request(tenant_id, network_id, format='xml'):
return _port_list_request(tenant_id, network_id,
format, detail=True)
def _show_port_request(tenant_id, network_id, port_id,
format='xml', detail=False):
method = 'GET'
detail_str = detail and '/detail' or ''
path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \
"/ports/%(port_id)s%(detail_str)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def show_port_request(tenant_id, network_id, port_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \
"/ports/%(port_id)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
return _show_port_request(tenant_id, network_id, port_id, format)
def show_port_detail_request(tenant_id, network_id, port_id, format='xml'):
return _show_port_request(tenant_id, network_id, port_id,
format, detail=True)
def new_port_request(tenant_id, network_id, port_state,
@ -78,7 +118,7 @@ def new_port_request(tenant_id, network_id, port_state,
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports.%(format)s" % locals()
data = custom_req_body or port_state and \
{'port': {'port-state': '%s' % port_state}}
{'port': {'state': '%s' % port_state}}
content_type = "application/%s" % format
body = data and Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
@ -97,7 +137,7 @@ def update_port_request(tenant_id, network_id, port_id, port_state,
method = 'PUT'
path = "/tenants/%(tenant_id)s/networks" \
"/%(network_id)s/ports/%(port_id)s.%(format)s" % locals()
data = custom_req_body or {'port': {'port-state': '%s' % port_state}}
data = custom_req_body or {'port': {'state': '%s' % port_state}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
@ -116,7 +156,7 @@ def put_attachment_request(tenant_id, network_id, port_id,
method = 'PUT'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals()
data = {'port': {'attachment-id': attachment_id}}
data = {'attachment': {'id': attachment_id}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)