from quantum.api import faults
from quantum.api import networks
+from quantum.api import ports
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):
"""
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("/tenants/{tenant_id}/network", "/tenants/{tenant_id}/networks", controller=networks.Controller())
+ uri_prefix = '/tenants/{tenant_id}/'
+ mapper.resource('network',
+ 'networks',
+ controller=networks.Controller(),
+ path_prefix=uri_prefix)
+ mapper.resource("port", "ports", controller=ports.Controller(),
+ parent_resource=dict(member_name='network',
+ collection_name= uri_prefix + 'networks'))
+
+ mapper.connect("get_resource",
+ uri_prefix + 'networks/{network_id}/ports/{id}/attachment{.format}',
+ controller=ports.Controller(),
+ action="get_resource",
+ conditions=dict(method=['GET']))
+ mapper.connect("attach_resource",
+ uri_prefix + 'networks/{network_id}/ports/{id}/attachment{.format}',
+ controller=ports.Controller(),
+ action="attach_resource",
+ conditions=dict(method=['PUT']))
+ mapper.connect("detach_resource",
+ uri_prefix + 'networks/{network_id}/ports/{id}/attachment{.format}',
+ controller=ports.Controller(),
+ action="detach_resource",
+ conditions=dict(method=['DELETE']))
+
print "AFTER MAPPING"
print mapper
for route in mapper.matchlist:
print "Found route:%s %s" %(route.defaults,route.conditions)
- #mapper.resource("port", "ports", controller=ports.Controller(),
- # collection=dict(public='GET', private='GET'),
- # parent_resource=dict(member_name='network',
- # collection_name='networks'))
-
# License for the specific language governing permissions and limitations
# under the License.
+import logging
+
+from webob import exc
+
+from quantum import manager
+from quantum.common import wsgi
XML_NS_V01 = 'http://netstack.org/quantum/api/v0.1'
XML_NS_V10 = 'http://netstack.org/quantum/api/v1.0'
+LOG = logging.getLogger('quantum.api.api_common')
+
+class QuantumController(wsgi.Controller):
+ """ Base controller class for Quantum API """
+
+ def __init__(self, plugin_conf_file=None):
+ self._setup_network_manager()
+ super(QuantumController, self).__init__()
+
+ def _parse_request_params(self, req, params):
+ results = {}
+ for param in params:
+ param_name = param['param-name']
+ param_value = None
+ # 1- parse request body
+ if req.body:
+ des_body = self._deserialize(req.body, req.best_match_content_type())
+ data = des_body and des_body.get(self._resource_name, None)
+ param_value = data and 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)s not specified" % locals())
+ 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
+
+ def _setup_network_manager(self):
+ self.network_manager=manager.QuantumManager().get_manager()
_fault_names = {
400: "malformedRequest",
401: "unauthorized",
- 402: "networkNotFound",
- 403: "requestedStateInvalid",
- 460: "networkInUse",
- 461: "alreadyAttached",
- 462: "portInUse",
+ 420: "networkNotFound",
+ 421: "networkInUse",
+ 430: "portNotFound",
+ 431: "requestedStateInvalid",
+ 432: "portInUse",
+ 440: "alreadyAttached",
470: "serviceUnavailable",
471: "pluginFault"
}
fault_data = {
fault_name: {
'code': code,
- 'message': self.wrapped_exc.explanation}}
- #TODO (salvatore-orlando): place over-limit stuff here
+ 'message': self.wrapped_exc.explanation,
+ 'detail': self.wrapped_exc.detail}}
# 'code' is an attribute on the fault tag itself
metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
default_xmlns = common.XML_NS_V10
self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
self.wrapped_exc.content_type = content_type
return self.wrapped_exc
+
+class NetworkNotFound(webob.exc.HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server did not find the network specified
+ in the HTTP request
+
+ code: 420, title: Network not Found
+ """
+ code = 420
+ title = 'Network not Found'
+ explanation = ('Unable to find a network with the specified identifier.')
+
+
+class NetworkInUse(webob.exc.HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server could not delete the network as there is
+ at least an attachment plugged into its ports
+
+ code: 421, title: Network In Use
+ """
+ code = 421
+ title = 'Network in Use'
+ explanation = ('Unable to remove the network: attachments still plugged.')
+
+
+class PortNotFound(webob.exc.HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server did not find the port specified
+ in the HTTP request for a given network
+
+ code: 430, title: Port not Found
+ """
+ code = 430
+ title = 'Port not Found'
+ explanation = ('Unable to find a port with the specified identifier.')
+
+
+class RequestedStateInvalid(webob.exc.HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server could not update the port state to
+ to the request value
+
+ code: 431, title: Requested State Invalid
+ """
+ code = 431
+ title = 'Requested State Invalid'
+ explanation = ('Unable to update port state with specified value.')
+
+
+class PortInUse(webob.exc.HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server could not remove o port or attach
+ a resource to it because there is an attachment plugged into the port
+
+ code: 432, title: PortInUse
+ """
+ code = 432
+ title = 'Port in Use'
+ explanation = ('A resource is currently attached to the logical port')
+
+class AlreadyAttached(webob.exc.HTTPClientError):
+ """
+ subclass of :class:`~HTTPClientError`
+
+ This indicates that the server refused an attempt to re-attach a resource
+ already attached to the network
+
+ code: 440, title: AlreadyAttached
+ """
+ code = 440
+ title = 'Already Attached'
+ explanation = ('The resource is already attached to another port')
# License for the specific language governing permissions and limitations
# under the License.
-import httplib
import logging
from webob import exc
-from xml.dom import minidom
-from quantum import manager
-from quantum.common import exceptions as exception
-from quantum.common import flags
-from quantum.common import wsgi
+from quantum.api import api_common as common
from quantum.api import faults
from quantum.api.views import networks as networks_view
+from quantum.common import exceptions as exception
LOG = logging.getLogger('quantum.api.networks')
-FLAGS = flags.FLAGS
-class Controller(wsgi.Controller):
+class Controller(common.QuantumController):
""" Network API controller for Quantum API """
_network_ops_param_list = [{
"application/xml": {
"attributes": {
"network": ["id","name"],
- "link": ["rel", "type", "href"],
},
},
}
def __init__(self, plugin_conf_file=None):
- self._setup_network_manager()
+ self._resource_name = 'network'
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 """
+ """ Returns a list of network ids """
#TODO: this should be for a given tenant!!!
return self._items(req, tenant_id, is_detail=False)
return dict(networks=result)
def show(self, req, tenant_id, id):
- """ Returns network details by network id """
+ """ Returns network details for the given network id """
try:
network = self.network_manager.get_network_details(
tenant_id,id)
#build response with details
result = builder.build(network, True)
return dict(networks=result)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ except exception.NetworkNotFound as e:
+ return faults.Fault(faults.NetworkNotFound(e))
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)
+ try:
+ req_params = \
+ self._parse_request_params(req, self._network_ops_param_list)
+ except exc.HTTPError as e:
+ return faults.Fault(e)
network = self.network_manager.create_network(tenant_id, req_params['network-name'])
builder = networks_view.get_view_builder(req)
result = builder.build(network)
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)
+ req_params = \
+ self._parse_request_params(req, self._network_ops_param_list)
+ except exc.HTTPError as e:
+ return faults.Fault(e)
+ try:
+ network = self.network_manager.rename_network(tenant_id,
+ id,req_params['network-name'])
+ builder = networks_view.get_view_builder(req)
+ result = builder.build(network, True)
+ return dict(networks=result)
+ except exception.NetworkNotFound as e:
+ return faults.Fault(faults.NetworkNotFound(e))
def delete(self, req, tenant_id, id):
""" Destroys 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.delete_network(tenant_id, id)
- if not network:
- raise exc.HTTPNotFound("Network %(id)s could not be found" % locals())
-
- return exc.HTTPAccepted()
-
-
+ self.network_manager.delete_network(tenant_id, id)
+ return exc.HTTPAccepted()
+ except exception.NetworkNotFound as e:
+ return faults.Fault(faults.NetworkNotFound(e))
+ except exception.NetworkInUse as e:
+ return faults.Fault(faults.NetworkInUse(e))
--- /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.
+
+
+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, port_data, is_detail=False):
+ """Generic method used to generate a port entity."""
+ print "PORT-DATA:%s" %port_data
+ if is_detail:
+ port = self._build_detail(port_data)
+ else:
+ port = self._build_simple(port_data)
+ return port
+
+ def _build_simple(self, port_data):
+ """Return a simple model of a server."""
+ return dict(port=dict(id=port_data['port-id']))
+
+ def _build_detail(self, port_data):
+ """Return a simple model of a server."""
+ return dict(port=dict(id=port_data['port-id'],
+ state=port_data['port-state']))
"""
import logging
-import sys
-import traceback
+class QuantumException(Exception):
+ """Base Quantum Exception
+
+ Taken from nova.exception.NovaException
+ To correctly use this class, inherit from it and define
+ a 'message' property. That message will get printf'd
+ with the keyword arguments provided to the constructor.
+
+ """
+ message = _("An unknown exception occurred.")
+
+ def __init__(self, **kwargs):
+ try:
+ self._error_string = self.message % kwargs
+
+ except Exception:
+ # at least get the core message out if something happened
+ self._error_string = self.message
+
+ def __str__(self):
+ return self._error_string
+
class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
super(ApiError, self).__init__('%s: %s' % (code, message))
-class NotFound(Error):
+class NotFound(QuantumException):
pass
message = _("Class %(class_name)s could not be found")
+class NetworkNotFound(NotFound):
+ message = _("Network %(net_id)s could not be found")
+
+
+class PortNotFound(NotFound):
+ message = _("Port %(port_id)s could not be found " \
+ "on network %(net_id)s")
+
+
+class StateInvalid(QuantumException):
+ message = _("Unsupported port state: %(port_state)s")
+
+
+class NetworkInUse(QuantumException):
+ message = _("Unable to complete operation on network %(net_id)s. " \
+ "There is one or more attachments plugged into its ports.")
+
+
+class PortInUse(QuantumException):
+ message = _("Unable to complete operation on port %(port_id)s " \
+ "for network %(net_id)s. The attachment '%(att_id)s" \
+ "is plugged into the logical port.")
+
+class AlreadyAttached(QuantumException):
+ message = _("Unable to plug the attachment %(att_id)s into port " \
+ "%(port_id)s for network %(net_id)s. The attachment is " \
+ "already plugged into port %(att_port_id)s")
+
class Duplicate(Error):
pass
# under the License.
# @author: Somik Behera, Nicira Networks, Inc.
+from quantum.common import exceptions as exc
+
class QuantumEchoPlugin(object):
"""
in-memory data structures to aid in quantum
client/cli/api development
"""
+
+ #static data for networks and ports
+ _port_dict_1 = {
+ 1 : {'port-id': 1,
+ 'port-state': 'DOWN',
+ 'attachment': None},
+ 2 : {'port-id': 2,
+ 'port-state':'UP',
+ 'attachment': None}
+ }
+ _port_dict_2 = {
+ 1 : {'port-id': 1,
+ 'port-state': 'UP',
+ 'attachment': 'SomeFormOfVIFID'},
+ 2 : {'port-id': 2,
+ 'port-state':'DOWN',
+ 'attachment': None}
+ }
+ _networks={'001':
+ {
+ 'net-id':'001',
+ 'net-name':'pippotest',
+ 'net-ports': _port_dict_1
+ },
+ '002':
+ {
+ 'net-id':'002',
+ 'net-name':'cicciotest',
+ 'net-ports': _port_dict_2
+ }}
+
+
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)
+ FakePlugin._net_counter=len(FakePlugin._networks)
+ def _get_network(self, tenant_id, network_id):
+ network = FakePlugin._networks.get(network_id)
+ if not network:
+ raise exc.NetworkNotFound(net_id=network_id)
+ return network
+
+
+ def _get_port(self, tenant_id, network_id, port_id):
+ net = self._get_network(tenant_id, network_id)
+ port = net['net-ports'].get(int(port_id))
+ if not port:
+ raise exc.PortNotFound(net_id=network_id, port_id=port_id)
+ return port
+
+ def _validate_port_state(self, port_state):
+ if port_state.upper() not in ('UP','DOWN'):
+ raise exc.StateInvalid(port_state=port_state)
+ return True
+
+ def _validate_attachment(self, tenant_id, network_id, port_id,
+ remote_interface_id):
+ network = self._get_network(tenant_id, network_id)
+ for port in network['net-ports'].values():
+ if port['attachment'] == remote_interface_id:
+ raise exc.AlreadyAttached(net_id = network_id,
+ port_id = port_id,
+ att_id = port['attachment'],
+ att_port_id = port['port-id'])
+
def get_all_networks(self, tenant_id):
"""
Returns a dictionary containing all
the specified tenant.
"""
print("get_all_networks() called\n")
- return self._networks.values()
+ return FakePlugin._networks.values()
def get_network_details(self, tenant_id, net_id):
"""
are attached to the network
"""
print("get_network_details() called\n")
- return self._networks.get(net_id)
+ return self._get_network(tenant_id, 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)
+ FakePlugin._net_counter += 1
+ new_net_id=("0" * (3 - len(str(FakePlugin._net_counter)))) + \
+ str(FakePlugin._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
+ 'net-name':net_name,
+ 'net-ports': {}}
+ FakePlugin._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)
+ net = FakePlugin._networks.get(net_id)
+ # Verify that no attachments are plugged into the network
if net:
- self._networks.pop(net_id)
+ if net['net-ports']:
+ for port in net['net-ports'].values():
+ if port['attachment']:
+ raise exc.NetworkInUse(net_id=net_id)
+ FakePlugin._networks.pop(net_id)
return net
- return None
-
+ # Network not found
+ raise exc.NetworkNotFound(net_id=net_id)
def rename_network(self, tenant_id, net_id, new_name):
"""
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
-
+ net = self._get_network(tenant_id, net_id)
+ net['net-name']=new_name
+ return net
- #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):
+ network = self._get_network(tenant_id, net_id)
+ ports_on_net = network['net-ports'].values()
+ return ports_on_net
+
+ 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")
+ return self._get_port(tenant_id, net_id, port_id)
+
+ def create_port(self, tenant_id, net_id, port_state=None):
"""
Creates a port on the specified Virtual Network.
"""
print("create_port() called\n")
- #return the port id
- return 201
-
-
+ net = self._get_network(tenant_id, net_id)
+ # check port state
+ # TODO(salvatore-orlando): Validate port state in API?
+ self._validate_port_state(port_state)
+ ports = net['net-ports']
+ new_port_id = max(ports.keys())+1
+ new_port_dict = {'port-id':new_port_id,
+ 'port-state': port_state,
+ 'attachment': None}
+ ports[new_port_id] = new_port_dict
+ return new_port_dict
+
+ def update_port(self, tenant_id, net_id, port_id, port_state):
+ """
+ Updates the state of a port on the specified Virtual Network.
+ """
+ print("create_port() called\n")
+ port = self._get_port(tenant_id, net_id, port_id)
+ self._validate_port_state(port_state)
+ port['port-state'] = port_state
+ return port
+
def delete_port(self, tenant_id, net_id, port_id):
"""
Deletes a port on a specified Virtual Network,
is deleted.
"""
print("delete_port() called\n")
-
-
- def get_port_details(self, tenant_id, net_id, port_id):
+ net = self._get_network(tenant_id, net_id)
+ port = self._get_port(tenant_id, net_id, port_id)
+ if port['attachment']:
+ raise exc.PortInUse(net_id=net_id,port_id=port_id,
+ att_id=port['attachment'])
+ try:
+ net['net-ports'].pop(int(port_id))
+ except KeyError:
+ raise exc.PortNotFound(net_id=net_id, port_id=port_id)
+
+ def get_interface_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.
+ Retrieves the remote interface that is attached at this
+ particular port.
"""
- print("get_port_details() called\n")
- #returns the remote interface UUID
- return "/tenant1/networks/net_id/portid/vif2.1"
-
-
+ print("get_interface_details() called\n")
+ port = self._get_port(tenant_id, net_id, port_id)
+ return port['attachment']
+
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")
-
+ # 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['attachment']:
+ raise exc.PortInUse(net_id=net_id,port_id=port_id,
+ att_id=port['attachment'])
+ port['attachment'] = remote_interface_id
def unplug_interface(self, tenant_id, net_id, port_id):
"""
specified Virtual Network.
"""
print("unplug_interface() called\n")
+ port = self._get_port(tenant_id, net_id, port_id)
+ # TODO(salvatore-orlando):
+ # Should unplug on port without attachment raise an Error?
+ port['attachment'] = None
-
- 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"
-
-
+ #TODO - neeed to update methods from this point onwards
def get_all_attached_interfaces(self, tenant_id, net_id):
"""
Retrieves all remote interfaces that are attached to
pass
@abstractmethod
- def create_port(self, tenant_id, net_id):
+ def create_port(self, tenant_id, net_id, port_state=None):
"""
Creates a port on the specified Virtual Network.
"""
pass
+ @abstractmethod
+ def update_port(self, tenant_id, net_id, port_id, port_state):
+ """
+ Updates the state of a specific port on the
+ specified Virtual Network
+ """
+ pass
+
@abstractmethod
def delete_port(self, tenant_id, net_id, port_id):
"""
--- /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 httplib
+import socket
+import urllib
+
+class MiniClient(object):
+
+ """A base client class - derived from Glance.BaseClient"""
+
+ action_prefix = '/v0.1/tenants/{tenant_id}'
+
+ def __init__(self, host, port, use_ssl):
+ """
+ Creates a new client to some service.
+
+ :param host: The host where service resides
+ :param port: The port where service resides
+ :param use_ssl: Should we use HTTPS?
+ """
+ self.host = host
+ self.port = port
+ self.use_ssl = use_ssl
+ self.connection = None
+
+ def get_connection_type(self):
+ """
+ Returns the proper connection type
+ """
+ if self.use_ssl:
+ return httplib.HTTPSConnection
+ else:
+ return httplib.HTTPConnection
+
+ def do_request(self, tenant, method, action, body=None,
+ headers=None, params=None):
+ """
+ Connects to the server and issues a request.
+ Returns the result data, or raises an appropriate exception if
+ HTTP status code is not 2xx
+
+ :param method: HTTP method ("GET", "POST", "PUT", etc...)
+ :param body: string of data to send, or None (default)
+ :param headers: mapping of key/value pairs to add as headers
+ :param params: dictionary of key/value pairs to add to append
+ to action
+
+ """
+ action = MiniClient.action_prefix + action
+ action = action.replace('{tenant_id}',tenant)
+ if type(params) is dict:
+ action += '?' + urllib.urlencode(params)
+
+ try:
+ connection_type = self.get_connection_type()
+ headers = headers or {}
+
+ # Open connection and send request
+ c = connection_type(self.host, self.port)
+ c.request(method, action, body, headers)
+ res = c.getresponse()
+ status_code = self.get_status_code(res)
+ if status_code in (httplib.OK,
+ httplib.CREATED,
+ httplib.ACCEPTED,
+ httplib.NO_CONTENT):
+ return res
+ else:
+ raise Exception("Server returned error: %s" % res.read())
+
+ except (socket.error, IOError), e:
+ raise Exception("Unable to connect to "
+ "server. Got error: %s" % e)
+
+ def get_status_code(self, response):
+ """
+ Returns the integer status code from the response, which
+ can be either a Webob.Response (used in testing) or httplib.Response
+ """
+ if hasattr(response, 'status_int'):
+ return response.status_int
+ else:
+ return response.status
\ No newline at end of file
--- /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 gettext
+
+gettext.install('quantum', unicode=1)
+
+from miniclient import MiniClient
+from quantum.common.wsgi import Serializer
+
+HOST = '127.0.0.1'
+PORT = 9696
+USE_SSL = False
+TENANT_ID = 'totore'
+
+test_network_data = \
+ {'network': {'network-name': 'test' }}
+
+def print_response(res):
+ content = res.read()
+ print "Status: %s" %res.status
+ print "Content: %s" %content
+ return content
+
+def test_list_networks_and_ports(format = 'xml'):
+ client = MiniClient(HOST, PORT, USE_SSL)
+ print "TEST LIST NETWORKS AND PORTS -- FORMAT:%s" %format
+ print "----------------------------"
+ print "--> Step 1 - List All Networks"
+ res = client.do_request(TENANT_ID,'GET', "/networks." + format)
+ print_response(res)
+ print "--> Step 2 - Details for Network 001"
+ res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
+ print_response(res)
+ print "--> Step 3 - Ports for Network 001"
+ res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
+ print_response(res)
+ print "--> Step 4 - Details for Port 1"
+ res = client.do_request(TENANT_ID,'GET', "/networks/001/ports/1." + format)
+ print_response(res)
+ print "COMPLETED"
+ print "----------------------------"
+
+def test_create_network(format = 'xml'):
+ client = MiniClient(HOST, PORT, USE_SSL)
+ print "TEST CREATE NETWORK -- FORMAT:%s" %format
+ print "----------------------------"
+ print "--> Step 1 - Create Network"
+ content_type = "application/" + format
+ body = Serializer().serialize(test_network_data, content_type)
+ res = client.do_request(TENANT_ID,'POST', "/networks." + format, body=body)
+ print_response(res)
+ print "--> Step 2 - List All Networks"
+ res = client.do_request(TENANT_ID,'GET', "/networks." + format)
+ print_response(res)
+ print "COMPLETED"
+ print "----------------------------"
+
+def test_rename_network(format = 'xml'):
+ client = MiniClient(HOST, PORT, USE_SSL)
+ content_type = "application/" + format
+ print "TEST RENAME NETWORK -- FORMAT:%s" %format
+ print "----------------------------"
+ print "--> Step 1 - Retrieve network"
+ res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
+ print_response(res)
+ print "--> Step 2 - Rename network to 'test_renamed'"
+ test_network_data['network']['network-name'] = 'test_renamed'
+ body = Serializer().serialize(test_network_data, content_type)
+ res = client.do_request(TENANT_ID,'PUT', "/networks/001." + format, body=body)
+ print_response(res)
+ print "--> Step 2 - Retrieve network (again)"
+ res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
+ print_response(res)
+ print "COMPLETED"
+ print "----------------------------"
+
+def test_delete_network(format = 'xml'):
+ client = MiniClient(HOST, PORT, USE_SSL)
+ content_type = "application/" + format
+ print "TEST DELETE NETWORK -- FORMAT:%s" %format
+ print "----------------------------"
+ print "--> Step 1 - List All Networks"
+ res = client.do_request(TENANT_ID,'GET', "/networks." + format)
+ content = print_response(res)
+ network_data = Serializer().deserialize(content, content_type)
+ print network_data
+ net_id = network_data['networks'][0]['id']
+ print "--> Step 2 - Delete network %s" %net_id
+ res = client.do_request(TENANT_ID,'DELETE',
+ "/networks/" + net_id + "." + format)
+ print_response(res)
+ print "--> Step 3 - List All Networks (Again)"
+ res = client.do_request(TENANT_ID,'GET', "/networks." + format)
+ print_response(res)
+ print "COMPLETED"
+ print "----------------------------"
+
+
+def test_create_port(format = 'xml'):
+ client = MiniClient(HOST, PORT, USE_SSL)
+ print "TEST CREATE PORT -- FORMAT:%s" %format
+ print "----------------------------"
+ print "--> Step 1 - List Ports for network 001"
+ res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
+ print_response(res)
+ print "--> Step 2 - Create Port for network 001"
+ res = client.do_request(TENANT_ID,'POST', "/networks/001/ports." + format)
+ print_response(res)
+ print "--> Step 3 - List Ports for network 001 (again)"
+ res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
+ print_response(res)
+ print "COMPLETED"
+ print "----------------------------"
+
+
+def main():
+ test_list_networks_and_ports('xml')
+ test_list_networks_and_ports('json')
+ test_create_network('xml')
+ test_create_network('json')
+ test_rename_network('xml')
+ test_rename_network('json')
+ # NOTE: XML deserializer does not work properly
+ # disabling XML test - this is NOT a server-side issue
+ #test_delete_network('xml')
+ test_delete_network('json')
+ test_create_port('xml')
+ test_create_port('json')
+
+ pass
+
+
+# Standard boilerplate to call the main() function.
+if __name__ == '__main__':
+ main()
\ No newline at end of file