* Merged changes from Salvatore's branch - quantum-api-workinprogress
* Removed spurious methods from quantum_base_plugin class. * Updated the sample plugins to be compliant with the new QuantumBase class.
This commit is contained in:
commit
48f4e9aaa0
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?>
|
||||
|
||||
<pydev_project>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
</pydev_project>
|
21
bin/quantum
Normal file → Executable file
21
bin/quantum
Normal file → Executable file
@ -19,11 +19,10 @@
|
||||
# If ../quantum/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||
|
||||
import gettext
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
@ -32,14 +31,15 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
gettext.install('quantum', unicode=1)
|
||||
|
||||
from quantum.common import wsgi
|
||||
from quantum import service
|
||||
from quantum.common import config
|
||||
|
||||
def create_options(parser):
|
||||
"""
|
||||
"""
|
||||
Sets up the CLI and config-file options that may be
|
||||
parsed and program commands.
|
||||
parsed and program commands.
|
||||
:param parser: The option parser
|
||||
"""
|
||||
config.add_common_options(parser)
|
||||
@ -52,11 +52,10 @@ if __name__ == '__main__':
|
||||
(options, args) = config.parse_options(oparser)
|
||||
|
||||
try:
|
||||
conf, app = config.load_paste_app('quantum', options, args)
|
||||
|
||||
server = wsgi.Server()
|
||||
server.start(app, int(conf['bind_port']), conf['bind_host'])
|
||||
server.wait()
|
||||
service = service.serve_wsgi(service.QuantumApiService,
|
||||
options=options,
|
||||
args=args)
|
||||
service.wait()
|
||||
except RuntimeError, e:
|
||||
sys.exit("ERROR: %s" % e)
|
||||
sys.exit("ERROR: %s" % e)
|
||||
|
||||
|
25
etc/quantum.conf
Normal file
25
etc/quantum.conf
Normal file
@ -0,0 +1,25 @@
|
||||
[DEFAULT]
|
||||
# Show more verbose log output (sets INFO log level output)
|
||||
verbose = True
|
||||
|
||||
# Show debugging output in logs (sets DEBUG log level output)
|
||||
debug = True
|
||||
|
||||
# Address to bind the API server
|
||||
bind_host = 0.0.0.0
|
||||
|
||||
# Port the bind the API server to
|
||||
bind_port = 9696
|
||||
|
||||
[composite:quantum]
|
||||
use = egg:Paste#urlmap
|
||||
/: quantumversions
|
||||
/v0.1: quantumapi
|
||||
|
||||
[app:quantumversions]
|
||||
paste.app_factory = quantum.api.versions:Versions.factory
|
||||
|
||||
[app:quantumapi]
|
||||
paste.app_factory = quantum.api:APIRouterV01.factory
|
||||
|
||||
|
29
etc/quantum/api-paste.ini
Normal file
29
etc/quantum/api-paste.ini
Normal file
@ -0,0 +1,29 @@
|
||||
#############
|
||||
# Quantum #
|
||||
#############
|
||||
|
||||
[composite:quantumapi]
|
||||
use = egg:Paste#urlmap
|
||||
/: quantumversions
|
||||
/v1.0: quantumapi10
|
||||
|
||||
[pipeline:quantumapi10]
|
||||
pipeline = faultwrap auth ratelimit quantumapiapp10
|
||||
|
||||
[filter:faultwrap]
|
||||
paste.filter_factory = quantum.api:FaultWrapper.factory
|
||||
|
||||
[filter:auth]
|
||||
paste.filter_factory = quantum.api.auth:AuthMiddleware.factory
|
||||
|
||||
[filter:ratelimit]
|
||||
paste.filter_factory = quantum.api.limits:RateLimitingMiddleware.factory
|
||||
|
||||
[app:quantumapiapp10]
|
||||
paste.app_factory = nova.api.quantum:APIRouterV10.factory
|
||||
|
||||
[pipeline:quantumversions]
|
||||
pipeline = faultwrap quantumversionapp
|
||||
|
||||
[app:quantumversionapp]
|
||||
paste.app_factory = quantum.api.versions:Versions.factory
|
77
quantum/api/__init__.py
Normal file
77
quantum/api/__init__.py
Normal file
@ -0,0 +1,77 @@
|
||||
# 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.
|
||||
# @author: Salvatore Orlando, Citrix Systems
|
||||
|
||||
"""
|
||||
Quantum API controllers.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import routes
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from quantum.api import faults
|
||||
from quantum.api import networks
|
||||
from quantum.api import ports
|
||||
from quantum.common import flags
|
||||
from quantum.common import wsgi
|
||||
|
||||
|
||||
LOG = logging.getLogger('quantum.api')
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class APIRouterV01(wsgi.Router):
|
||||
"""
|
||||
Routes requests on the Quantum API to the appropriate controller
|
||||
"""
|
||||
|
||||
def __init__(self, ext_mgr=None):
|
||||
mapper = routes.Mapper()
|
||||
self._setup_routes(mapper)
|
||||
super(APIRouterV01, self).__init__(mapper)
|
||||
|
||||
def _setup_routes(self, mapper):
|
||||
|
||||
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']))
|
70
quantum/api/api_common.py
Normal file
70
quantum/api/api_common.py
Normal file
@ -0,0 +1,70 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Citrix System.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
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()
|
148
quantum/api/faults.py
Normal file
148
quantum/api/faults.py
Normal file
@ -0,0 +1,148 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Citrix Systems.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from quantum.api import api_common as common
|
||||
from quantum.common import wsgi
|
||||
|
||||
|
||||
class Fault(webob.exc.HTTPException):
|
||||
"""Error codes for API faults"""
|
||||
|
||||
_fault_names = {
|
||||
400: "malformedRequest",
|
||||
401: "unauthorized",
|
||||
420: "networkNotFound",
|
||||
421: "networkInUse",
|
||||
430: "portNotFound",
|
||||
431: "requestedStateInvalid",
|
||||
432: "portInUse",
|
||||
440: "alreadyAttached",
|
||||
470: "serviceUnavailable",
|
||||
471: "pluginFault"
|
||||
}
|
||||
|
||||
def __init__(self, exception):
|
||||
"""Create a Fault for the given webob.exc.exception."""
|
||||
self.wrapped_exc = exception
|
||||
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def __call__(self, req):
|
||||
"""Generate a WSGI response based on the exception passed to ctor."""
|
||||
# Replace the body with fault details.
|
||||
code = self.wrapped_exc.status_int
|
||||
fault_name = self._fault_names.get(code, "quantumServiceFault")
|
||||
fault_data = {
|
||||
fault_name: {
|
||||
'code': code,
|
||||
'message': self.wrapped_exc.explanation,
|
||||
'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
|
||||
serializer = wsgi.Serializer(metadata, default_xmlns)
|
||||
content_type = req.best_match_content_type()
|
||||
self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
|
||||
self.wrapped_exc.content_type = content_type
|
||||
return self.wrapped_exc
|
||||
|
||||
|
||||
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')
|
111
quantum/api/networks.py
Normal file
111
quantum/api/networks.py
Normal file
@ -0,0 +1,111 @@
|
||||
# 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 networks as networks_view
|
||||
from quantum.common import exceptions as exception
|
||||
|
||||
LOG = logging.getLogger('quantum.api.networks')
|
||||
|
||||
|
||||
class Controller(common.QuantumController):
|
||||
""" Network API controller for Quantum API """
|
||||
|
||||
_network_ops_param_list = [{
|
||||
'param-name': 'network-name',
|
||||
'required': True}, ]
|
||||
|
||||
_serialization_metadata = {
|
||||
"application/xml": {
|
||||
"attributes": {
|
||||
"network": ["id", "name"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, plugin_conf_file=None):
|
||||
self._resource_name = 'network'
|
||||
super(Controller, self).__init__()
|
||||
|
||||
def index(self, req, tenant_id):
|
||||
""" Returns a list of network ids """
|
||||
#TODO: this should be for a given tenant!!!
|
||||
return self._items(req, tenant_id, is_detail=False)
|
||||
|
||||
def _items(self, req, tenant_id, is_detail):
|
||||
""" Returns a list of networks. """
|
||||
networks = self.network_manager.get_all_networks(tenant_id)
|
||||
builder = networks_view.get_view_builder(req)
|
||||
result = [builder.build(network, is_detail)['network']
|
||||
for network in networks]
|
||||
return dict(networks=result)
|
||||
|
||||
def show(self, req, tenant_id, id):
|
||||
""" Returns network details for the given network id """
|
||||
try:
|
||||
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.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
|
||||
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)
|
||||
return dict(networks=result)
|
||||
|
||||
def update(self, req, tenant_id, id):
|
||||
""" Updates the name for the network with the given id """
|
||||
try:
|
||||
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:
|
||||
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))
|
183
quantum/api/ports.py
Normal file
183
quantum/api/ports.py
Normal file
@ -0,0 +1,183 @@
|
||||
# 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 ports as ports_view
|
||||
from quantum.common import exceptions as exception
|
||||
|
||||
LOG = logging.getLogger('quantum.api.ports')
|
||||
|
||||
class Controller(common.QuantumController):
|
||||
""" Port API controller for Quantum API """
|
||||
|
||||
_port_ops_param_list = [{
|
||||
'param-name': 'port-state',
|
||||
'default-value': 'DOWN',
|
||||
'required': False},]
|
||||
|
||||
|
||||
_attachment_ops_param_list = [{
|
||||
'param-name': 'attachment-id',
|
||||
'required': True},]
|
||||
|
||||
|
||||
_serialization_metadata = {
|
||||
"application/xml": {
|
||||
"attributes": {
|
||||
"port": ["id","state"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, plugin_conf_file=None):
|
||||
self._resource_name = 'port'
|
||||
super(Controller, self).__init__()
|
||||
|
||||
def index(self, req, tenant_id, network_id):
|
||||
""" Returns a list of port ids for a given network """
|
||||
return self._items(req, tenant_id, network_id, is_detail=False)
|
||||
|
||||
def _items(self, req, tenant_id, network_id, is_detail):
|
||||
""" Returns a list of networks. """
|
||||
try :
|
||||
ports = self.network_manager.get_all_ports(tenant_id, network_id)
|
||||
builder = ports_view.get_view_builder(req)
|
||||
result = [builder.build(port, is_detail)['port']
|
||||
for port in ports]
|
||||
return dict(ports=result)
|
||||
except exception.NetworkNotFound as e:
|
||||
return faults.Fault(faults.NetworkNotFound(e))
|
||||
|
||||
def show(self, req, tenant_id, network_id, id):
|
||||
""" Returns port details for given port and network """
|
||||
try:
|
||||
port = self.network_manager.get_port_details(
|
||||
tenant_id, network_id, id)
|
||||
builder = ports_view.get_view_builder(req)
|
||||
#build response with details
|
||||
result = builder.build(port, True)
|
||||
return dict(ports=result)
|
||||
except exception.NetworkNotFound as e:
|
||||
return faults.Fault(faults.NetworkNotFound(e))
|
||||
except exception.PortNotFound as e:
|
||||
return faults.Fault(faults.PortNotFound(e))
|
||||
|
||||
def create(self, req, tenant_id, network_id):
|
||||
""" Creates a new port for a given network """
|
||||
#look for port state in request
|
||||
try:
|
||||
req_params = \
|
||||
self._parse_request_params(req, self._port_ops_param_list)
|
||||
except exc.HTTPError as e:
|
||||
return faults.Fault(e)
|
||||
try:
|
||||
port = self.network_manager.create_port(tenant_id,
|
||||
network_id,
|
||||
req_params['port-state'])
|
||||
builder = ports_view.get_view_builder(req)
|
||||
result = builder.build(port)
|
||||
return dict(ports=result)
|
||||
except exception.NetworkNotFound as e:
|
||||
return faults.Fault(faults.NetworkNotFound(e))
|
||||
except exception.StateInvalid as e:
|
||||
return faults.Fault(faults.RequestedStateInvalid(e))
|
||||
|
||||
def update(self, req, tenant_id, network_id, id):
|
||||
""" Updates the state of a port for a given network """
|
||||
#look for port state in request
|
||||
try:
|
||||
req_params = \
|
||||
self._parse_request_params(req, self._port_ops_param_list)
|
||||
except exc.HTTPError as e:
|
||||
return faults.Fault(e)
|
||||
try:
|
||||
port = self.network_manager.update_port(tenant_id,network_id, id,
|
||||
req_params['port-state'])
|
||||
builder = ports_view.get_view_builder(req)
|
||||
result = builder.build(port, True)
|
||||
return dict(ports=result)
|
||||
except exception.NetworkNotFound as e:
|
||||
return faults.Fault(faults.NetworkNotFound(e))
|
||||
except exception.PortNotFound as e:
|
||||
return faults.Fault(faults.PortNotFound(e))
|
||||
except exception.StateInvalid as e:
|
||||
return faults.Fault(faults.RequestedStateInvalid(e))
|
||||
|
||||
|
||||
def delete(self, req, tenant_id, network_id, id):
|
||||
""" Destroys the port with the given id """
|
||||
#look for port state in request
|
||||
try:
|
||||
self.network_manager.delete_port(tenant_id, network_id, id)
|
||||
return exc.HTTPAccepted()
|
||||
#TODO(salvatore-orlando): Handle portInUse error
|
||||
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,req,tenant_id, network_id, id):
|
||||
try:
|
||||
result = self.network_manager.get_interface_details(
|
||||
tenant_id, network_id, id)
|
||||
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))
|
||||
|
||||
#TODO - Complete implementation of these APIs
|
||||
def attach_resource(self,req,tenant_id, network_id, id):
|
||||
content_type = req.best_match_content_type()
|
||||
print "Content type:%s" %content_type
|
||||
try:
|
||||
req_params = \
|
||||
self._parse_request_params(req,
|
||||
self._attachment_ops_param_list)
|
||||
except exc.HTTPError as e:
|
||||
return faults.Fault(e)
|
||||
try:
|
||||
self.network_manager.plug_interface(tenant_id,
|
||||
network_id,id,
|
||||
req_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))
|
||||
|
||||
|
||||
#TODO - Complete implementation of these APIs
|
||||
def detach_resource(self,req,tenant_id, network_id, id):
|
||||
try:
|
||||
self.network_manager.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))
|
63
quantum/api/versions.py
Normal file
63
quantum/api/versions.py
Normal file
@ -0,0 +1,63 @@
|
||||
# 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 logging
|
||||
import webob.dec
|
||||
|
||||
from quantum.common import wsgi
|
||||
from quantum.api.views import versions as versions_view
|
||||
|
||||
LOG = logging.getLogger('quantum.api.versions')
|
||||
|
||||
|
||||
class Versions(wsgi.Application):
|
||||
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def __call__(self, req):
|
||||
"""Respond to a request for all Quantum API versions."""
|
||||
version_objs = [
|
||||
{
|
||||
"id": "v0.1",
|
||||
"status": "CURRENT",
|
||||
},
|
||||
{
|
||||
"id": "v1.0",
|
||||
"status": "FUTURE",
|
||||
},
|
||||
]
|
||||
|
||||
builder = versions_view.get_view_builder(req)
|
||||
versions = [builder.build(version) for version in version_objs]
|
||||
response = dict(versions=versions)
|
||||
metadata = {
|
||||
"application/xml": {
|
||||
"attributes": {
|
||||
"version": ["status", "id"],
|
||||
"link": ["rel", "href"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content_type = req.best_match_content_type()
|
||||
body = wsgi.Serializer(metadata=metadata). \
|
||||
serialize(response, content_type)
|
||||
|
||||
response = webob.Response()
|
||||
response.content_type = content_type
|
||||
response.body = body
|
||||
|
||||
return response
|
16
quantum/api/views/__init__.py
Normal file
16
quantum/api/views/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
# Copyright 2011 Citrix Systems, Inc.
|
||||
# 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.
|
||||
# @author: Somik Behera, Nicira Networks, Inc.
|
50
quantum/api/views/networks.py
Normal file
50
quantum/api/views/networks.py
Normal file
@ -0,0 +1,50 @@
|
||||
# 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 os
|
||||
|
||||
|
||||
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, network_data, is_detail=False):
|
||||
"""Generic method used to generate a network entity."""
|
||||
print "NETWORK-DATA:%s" %network_data
|
||||
if is_detail:
|
||||
network = self._build_detail(network_data)
|
||||
else:
|
||||
network = self._build_simple(network_data)
|
||||
return network
|
||||
|
||||
def _build_simple(self, network_data):
|
||||
"""Return a simple model of a server."""
|
||||
return dict(network=dict(id=network_data['net-id']))
|
||||
|
||||
def _build_detail(self, network_data):
|
||||
"""Return a simple model of a server."""
|
||||
return dict(network=dict(id=network_data['net-id'],
|
||||
name=network_data['net-name']))
|
48
quantum/api/views/ports.py
Normal file
48
quantum/api/views/ports.py
Normal file
@ -0,0 +1,48 @@
|
||||
# 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']))
|
59
quantum/api/views/versions.py
Normal file
59
quantum/api/views/versions.py
Normal file
@ -0,0 +1,59 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010-2011 OpenStack LLC.
|
||||
# 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 os
|
||||
|
||||
|
||||
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, version_data):
|
||||
"""Generic method used to generate a version entity."""
|
||||
version = {
|
||||
"id": version_data["id"],
|
||||
"status": version_data["status"],
|
||||
"links": self._build_links(version_data),
|
||||
}
|
||||
|
||||
return version
|
||||
|
||||
def _build_links(self, version_data):
|
||||
"""Generate a container of links that refer to the provided version."""
|
||||
href = self.generate_href(version_data["id"])
|
||||
|
||||
links = [
|
||||
{
|
||||
"rel": "self",
|
||||
"href": href,
|
||||
},
|
||||
]
|
||||
|
||||
return links
|
||||
|
||||
def generate_href(self, version_number):
|
||||
"""Create an url that refers to a specific version_number."""
|
||||
return os.path.join(self.base_url, version_number)
|
@ -20,37 +20,37 @@ import sys
|
||||
from manager import QuantumManager
|
||||
|
||||
|
||||
def usage():
|
||||
print "\nUsage:"
|
||||
print "list_nets <tenant-id>"
|
||||
def usage():
|
||||
print "\nUsage:"
|
||||
print "list_nets <tenant-id>"
|
||||
print "create_net <tenant-id> <net-name>"
|
||||
print "delete_net <tenant-id> <net-id>"
|
||||
print "delete_net <tenant-id> <net-id>"
|
||||
print "detail_net <tenant-id> <net-id>"
|
||||
print "rename_net <tenant-id> <net-id> <new name>"
|
||||
print "list_ports <tenant-id> <net-id>"
|
||||
print "create_port <tenant-id> <net-id>"
|
||||
print "delete_port <tenant-id> <net-id> <port-id>"
|
||||
print "detail_port <tenant-id> <net-id> <port-id>"
|
||||
print "detail_port <tenant-id> <net-id> <port-id>"
|
||||
print "plug_iface <tenant-id> <net-id> <port-id> <iface-id>"
|
||||
print "unplug_iface <tenant-id> <net-id> <port-id>"
|
||||
print "detail_iface <tenant-id> <net-id> <port-id>"
|
||||
print "list_iface <tenant-id> <net-id>\n"
|
||||
|
||||
if len(sys.argv) < 2 or len(sys.argv) > 6:
|
||||
usage()
|
||||
exit(1)
|
||||
|
||||
usage()
|
||||
exit(1)
|
||||
|
||||
quantum = QuantumManager()
|
||||
manager = quantum.get_manager()
|
||||
|
||||
if sys.argv[1] == "list_nets" and len(sys.argv) == 3:
|
||||
if sys.argv[1] == "list_nets" and len(sys.argv) == 3:
|
||||
network_on_tenant = manager.get_all_networks(sys.argv[2])
|
||||
print "Virtual Networks on Tenant:%s\n" % sys.argv[2]
|
||||
for k, v in network_on_tenant.iteritems():
|
||||
print"\tNetwork ID:%s \n\tNetwork Name:%s \n" % (k, v)
|
||||
elif sys.argv[1] == "create_net" and len(sys.argv) == 4:
|
||||
new_net_id = manager.create_network(sys.argv[2], sys.argv[3])
|
||||
print "Created a new Virtual Network with ID:%s\n" % new_net_id
|
||||
print "Created a new Virtual Network with ID:%s\n" % new_net_id
|
||||
elif sys.argv[1] == "delete_net" and len(sys.argv) == 4:
|
||||
manager.delete_network(sys.argv[2], sys.argv[3])
|
||||
print "Deleted Virtual Network with ID:%s" % sys.argv[3]
|
||||
@ -58,7 +58,7 @@ elif sys.argv[1] == "detail_net" and len(sys.argv) == 4:
|
||||
vif_list = manager.get_network_details(sys.argv[2], sys.argv[3])
|
||||
print "Remote Interfaces on Virtual Network:%s\n" % sys.argv[3]
|
||||
for iface in vif_list:
|
||||
print "\tRemote interface :%s" % iface
|
||||
print "\tRemote interface :%s" % iface
|
||||
elif sys.argv[1] == "rename_net" and len(sys.argv) == 5:
|
||||
manager.rename_network(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
print "Renamed Virtual Network with ID:%s" % sys.argv[3]
|
||||
@ -66,33 +66,45 @@ elif sys.argv[1] == "list_ports" and len(sys.argv) == 4:
|
||||
ports = manager.get_all_ports(sys.argv[2], sys.argv[3])
|
||||
print " Virtual Ports on Virtual Network:%s\n" % sys.argv[3]
|
||||
for port in ports:
|
||||
print "\tVirtual Port:%s" % port
|
||||
print "\tVirtual Port:%s" % port
|
||||
elif sys.argv[1] == "create_port" and len(sys.argv) == 4:
|
||||
new_port = manager.create_port(sys.argv[2], sys.argv[3])
|
||||
print "Created Virtual Port:%s on Virtual Network:%s" % (new_port, sys.argv[3])
|
||||
print "Created Virtual Port:%s " \
|
||||
"on Virtual Network:%s" % (new_port, sys.argv[3])
|
||||
elif sys.argv[1] == "delete_port" and len(sys.argv) == 5:
|
||||
manager.delete_port(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
print "Deleted Virtual Port:%s on Virtual Network:%s" % (sys.argv[3], sys.argv[4])
|
||||
print "Deleted Virtual Port:%s " \
|
||||
"on Virtual Network:%s" % (sys.argv[3], sys.argv[4])
|
||||
elif sys.argv[1] == "detail_port" and len(sys.argv) == 5:
|
||||
port_detail = manager.get_port_details(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
print "Virtual Port:%s on Virtual Network:%s contains remote interface:%s" % (sys.argv[3], sys.argv[4], port_detail)
|
||||
port_detail = manager.get_port_details(sys.argv[2],
|
||||
sys.argv[3], sys.argv[4])
|
||||
print "Virtual Port:%s on Virtual Network:%s " \
|
||||
"contains remote interface:%s" % (sys.argv[3],
|
||||
sys.argv[4],
|
||||
port_detail)
|
||||
elif sys.argv[1] == "plug_iface" and len(sys.argv) == 6:
|
||||
manager.plug_interface(sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])
|
||||
print "Plugged remote interface:%s into Virtual Network:%s" % (sys.argv[5], sys.argv[3])
|
||||
print "Plugged remote interface:%s " \
|
||||
"into Virtual Network:%s" % (sys.argv[5], sys.argv[3])
|
||||
elif sys.argv[1] == "unplug_iface" and len(sys.argv) == 5:
|
||||
manager.unplug_interface(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
print "UnPlugged remote interface from Virtual Port:%s Virtual Network:%s" % (sys.argv[4], sys.argv[3])
|
||||
print "UnPlugged remote interface " \
|
||||
"from Virtual Port:%s Virtual Network:%s" % (sys.argv[4],
|
||||
sys.argv[3])
|
||||
elif sys.argv[1] == "detail_iface" and len(sys.argv) == 5:
|
||||
remote_iface = manager.get_interface_details(sys.argv[2], sys.argv[3], sys.argv[4])
|
||||
print "Remote interface on Virtual Port:%s Virtual Network:%s is %s" % (sys.argv[4], sys.argv[3], remote_iface)
|
||||
remote_iface = manager.get_interface_details(sys.argv[2],
|
||||
sys.argv[3], sys.argv[4])
|
||||
print "Remote interface on Virtual Port:%s " \
|
||||
"Virtual Network:%s is %s" % (sys.argv[4],
|
||||
sys.argv[3], remote_iface)
|
||||
elif sys.argv[1] == "list_iface" and len(sys.argv) == 4:
|
||||
iface_list = manager.get_all_attached_interfaces(sys.argv[2], sys.argv[3])
|
||||
print "Remote Interfaces on Virtual Network:%s\n" % sys.argv[3]
|
||||
for iface in iface_list:
|
||||
print "\tRemote interface :%s" % iface
|
||||
print "\tRemote interface :%s" % iface
|
||||
elif sys.argv[1] == "all" and len(sys.argv) == 2:
|
||||
print "Not Implemented"
|
||||
else:
|
||||
print "invalid arguments: %s" % str(sys.argv)
|
||||
else:
|
||||
print "invalid arguments: %s" % str(sys.argv)
|
||||
usage()
|
||||
|
||||
|
@ -31,11 +31,15 @@ import sys
|
||||
|
||||
from paste import deploy
|
||||
|
||||
import quantum.common.exception as exception
|
||||
from quantum.common import flags
|
||||
from quantum.common import exceptions as exception
|
||||
|
||||
DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
|
||||
DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
LOG = logging.getLogger('quantum.common.wsgi')
|
||||
|
||||
|
||||
def parse_options(parser, cli_args=None):
|
||||
"""
|
||||
@ -186,8 +190,8 @@ def find_config_file(options, args):
|
||||
* .
|
||||
* ~.quantum/
|
||||
* ~
|
||||
* /etc/quantum
|
||||
* /etc
|
||||
* $FLAGS.state_path/etc/quantum
|
||||
* $FLAGS.state_path/etc
|
||||
|
||||
:retval Full path to config file, or None if no config file found
|
||||
"""
|
||||
@ -204,9 +208,10 @@ def find_config_file(options, args):
|
||||
config_file_dirs = [fix_path(os.getcwd()),
|
||||
fix_path(os.path.join('~', '.quantum')),
|
||||
fix_path('~'),
|
||||
os.path.join(FLAGS.state_path, 'etc'),
|
||||
os.path.join(FLAGS.state_path, 'etc','quantum'),
|
||||
'/etc/quantum/',
|
||||
'/etc']
|
||||
|
||||
for cfg_dir in config_file_dirs:
|
||||
cfg_file = os.path.join(cfg_dir, 'quantum.conf')
|
||||
if os.path.exists(cfg_file):
|
||||
@ -239,10 +244,12 @@ def load_paste_config(app_name, options, args):
|
||||
problem loading the configuration file.
|
||||
"""
|
||||
conf_file = find_config_file(options, args)
|
||||
print "Conf_file:%s" %conf_file
|
||||
if not conf_file:
|
||||
raise RuntimeError("Unable to locate any configuration file. "
|
||||
"Cannot load application %s" % app_name)
|
||||
try:
|
||||
print "App_name:%s" %app_name
|
||||
conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
|
||||
return conf_file, conf
|
||||
except Exception, e:
|
||||
@ -250,7 +257,7 @@ def load_paste_config(app_name, options, args):
|
||||
% (conf_file, e))
|
||||
|
||||
|
||||
def load_paste_app(app_name, options, args):
|
||||
def load_paste_app(conf_file, app_name):
|
||||
"""
|
||||
Builds and returns a WSGI app from a paste config file.
|
||||
|
||||
@ -271,40 +278,16 @@ def load_paste_app(app_name, options, args):
|
||||
:raises RuntimeError when config file cannot be located or application
|
||||
cannot be loaded from config file
|
||||
"""
|
||||
conf_file, conf = load_paste_config(app_name, options, args)
|
||||
#conf_file, conf = load_paste_config(app_name, options, args)
|
||||
|
||||
try:
|
||||
# Setup logging early, supplying both the CLI options and the
|
||||
# configuration mapping from the config file
|
||||
setup_logging(options, conf)
|
||||
|
||||
# We only update the conf dict for the verbose and debug
|
||||
# flags. Everything else must be set up in the conf file...
|
||||
debug = options.get('debug') or \
|
||||
get_option(conf, 'debug', type='bool', default=False)
|
||||
verbose = options.get('verbose') or \
|
||||
get_option(conf, 'verbose', type='bool', default=False)
|
||||
conf['debug'] = debug
|
||||
conf['verbose'] = verbose
|
||||
|
||||
# Log the options used when starting if we're in debug mode...
|
||||
if debug:
|
||||
logger = logging.getLogger(app_name)
|
||||
logger.debug("*" * 80)
|
||||
logger.debug("Configuration options gathered from config file:")
|
||||
logger.debug(conf_file)
|
||||
logger.debug("================================================")
|
||||
items = dict([(k, v) for k, v in conf.items()
|
||||
if k not in ('__file__', 'here')])
|
||||
for key, value in sorted(items.items()):
|
||||
logger.debug("%(key)-30s %(value)s" % locals())
|
||||
logger.debug("*" * 80)
|
||||
conf_file = os.path.abspath(conf_file)
|
||||
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
|
||||
except (LookupError, ImportError), e:
|
||||
raise RuntimeError("Unable to load %(app_name)s from "
|
||||
"configuration file %(conf_file)s."
|
||||
"\nGot: %(e)r" % locals())
|
||||
return conf, app
|
||||
return app
|
||||
|
||||
|
||||
def get_option(options, option, **kwargs):
|
||||
|
@ -21,10 +21,30 @@ Quantum-type exceptions. SHOULD include dedicated exception logging.
|
||||
"""
|
||||
|
||||
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):
|
||||
@ -49,10 +69,42 @@ class ApiError(Error):
|
||||
super(ApiError, self).__init__('%s: %s' % (code, message))
|
||||
|
||||
|
||||
class NotFound(Error):
|
||||
class NotFound(QuantumException):
|
||||
pass
|
||||
|
||||
|
||||
class ClassNotFound(NotFound):
|
||||
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
|
||||
|
||||
@ -69,6 +121,10 @@ class Invalid(Error):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidContentType(Invalid):
|
||||
message = _("Invalid content type %(content_type)s.")
|
||||
|
||||
|
||||
class BadInputError(Exception):
|
||||
"""Error resulting from a client sending bad input to a server"""
|
||||
pass
|
||||
|
252
quantum/common/flags.py
Normal file
252
quantum/common/flags.py
Normal file
@ -0,0 +1,252 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Citrix Systems, Inc.
|
||||
# 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.
|
||||
|
||||
"""Command-line flag library.
|
||||
|
||||
Wraps gflags.
|
||||
Global flags should be defined here, the rest are defined where they're used.
|
||||
|
||||
"""
|
||||
|
||||
import getopt
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
|
||||
import gflags
|
||||
|
||||
|
||||
class FlagValues(gflags.FlagValues):
|
||||
"""Extension of gflags.FlagValues that allows undefined and runtime flags.
|
||||
|
||||
Unknown flags will be ignored when parsing the command line, but the
|
||||
command line will be kept so that it can be replayed if new flags are
|
||||
defined after the initial parsing.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, extra_context=None):
|
||||
gflags.FlagValues.__init__(self)
|
||||
self.__dict__['__dirty'] = []
|
||||
self.__dict__['__was_already_parsed'] = False
|
||||
self.__dict__['__stored_argv'] = []
|
||||
self.__dict__['__extra_context'] = extra_context
|
||||
|
||||
def __call__(self, argv):
|
||||
# We're doing some hacky stuff here so that we don't have to copy
|
||||
# out all the code of the original verbatim and then tweak a few lines.
|
||||
# We're hijacking the output of getopt so we can still return the
|
||||
# leftover args at the end
|
||||
sneaky_unparsed_args = {"value": None}
|
||||
original_argv = list(argv)
|
||||
|
||||
if self.IsGnuGetOpt():
|
||||
orig_getopt = getattr(getopt, 'gnu_getopt')
|
||||
orig_name = 'gnu_getopt'
|
||||
else:
|
||||
orig_getopt = getattr(getopt, 'getopt')
|
||||
orig_name = 'getopt'
|
||||
|
||||
def _sneaky(*args, **kw):
|
||||
optlist, unparsed_args = orig_getopt(*args, **kw)
|
||||
sneaky_unparsed_args['value'] = unparsed_args
|
||||
return optlist, unparsed_args
|
||||
|
||||
try:
|
||||
setattr(getopt, orig_name, _sneaky)
|
||||
args = gflags.FlagValues.__call__(self, argv)
|
||||
except gflags.UnrecognizedFlagError:
|
||||
# Undefined args were found, for now we don't care so just
|
||||
# act like everything went well
|
||||
# (these three lines are copied pretty much verbatim from the end
|
||||
# of the __call__ function we are wrapping)
|
||||
unparsed_args = sneaky_unparsed_args['value']
|
||||
if unparsed_args:
|
||||
if self.IsGnuGetOpt():
|
||||
args = argv[:1] + unparsed_args
|
||||
else:
|
||||
args = argv[:1] + original_argv[-len(unparsed_args):]
|
||||
else:
|
||||
args = argv[:1]
|
||||
finally:
|
||||
setattr(getopt, orig_name, orig_getopt)
|
||||
|
||||
# Store the arguments for later, we'll need them for new flags
|
||||
# added at runtime
|
||||
self.__dict__['__stored_argv'] = original_argv
|
||||
self.__dict__['__was_already_parsed'] = True
|
||||
self.ClearDirty()
|
||||
return args
|
||||
|
||||
def Reset(self):
|
||||
gflags.FlagValues.Reset(self)
|
||||
self.__dict__['__dirty'] = []
|
||||
self.__dict__['__was_already_parsed'] = False
|
||||
self.__dict__['__stored_argv'] = []
|
||||
|
||||
def SetDirty(self, name):
|
||||
"""Mark a flag as dirty so that accessing it will case a reparse."""
|
||||
self.__dict__['__dirty'].append(name)
|
||||
|
||||
def IsDirty(self, name):
|
||||
return name in self.__dict__['__dirty']
|
||||
|
||||
def ClearDirty(self):
|
||||
self.__dict__['__is_dirty'] = []
|
||||
|
||||
def WasAlreadyParsed(self):
|
||||
return self.__dict__['__was_already_parsed']
|
||||
|
||||
def ParseNewFlags(self):
|
||||
if '__stored_argv' not in self.__dict__:
|
||||
return
|
||||
new_flags = FlagValues(self)
|
||||
for k in self.__dict__['__dirty']:
|
||||
new_flags[k] = gflags.FlagValues.__getitem__(self, k)
|
||||
|
||||
new_flags(self.__dict__['__stored_argv'])
|
||||
for k in self.__dict__['__dirty']:
|
||||
setattr(self, k, getattr(new_flags, k))
|
||||
self.ClearDirty()
|
||||
|
||||
def __setitem__(self, name, flag):
|
||||
gflags.FlagValues.__setitem__(self, name, flag)
|
||||
if self.WasAlreadyParsed():
|
||||
self.SetDirty(name)
|
||||
|
||||
def __getitem__(self, name):
|
||||
if self.IsDirty(name):
|
||||
self.ParseNewFlags()
|
||||
return gflags.FlagValues.__getitem__(self, name)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.IsDirty(name):
|
||||
self.ParseNewFlags()
|
||||
val = gflags.FlagValues.__getattr__(self, name)
|
||||
if type(val) is str:
|
||||
tmpl = string.Template(val)
|
||||
context = [self, self.__dict__['__extra_context']]
|
||||
return tmpl.substitute(StrWrapper(context))
|
||||
return val
|
||||
|
||||
|
||||
class StrWrapper(object):
|
||||
"""Wrapper around FlagValues objects.
|
||||
|
||||
Wraps FlagValues objects for string.Template so that we're
|
||||
sure to return strings.
|
||||
|
||||
"""
|
||||
def __init__(self, context_objs):
|
||||
self.context_objs = context_objs
|
||||
|
||||
def __getitem__(self, name):
|
||||
for context in self.context_objs:
|
||||
val = getattr(context, name, False)
|
||||
if val:
|
||||
return str(val)
|
||||
raise KeyError(name)
|
||||
|
||||
|
||||
# Copied from gflags with small mods to get the naming correct.
|
||||
# Originally gflags checks for the first module that is not gflags that is
|
||||
# in the call chain, we want to check for the first module that is not gflags
|
||||
# and not this module.
|
||||
def _GetCallingModule():
|
||||
"""Returns the name of the module that's calling into this module.
|
||||
|
||||
We generally use this function to get the name of the module calling a
|
||||
DEFINE_foo... function.
|
||||
|
||||
"""
|
||||
# Walk down the stack to find the first globals dict that's not ours.
|
||||
for depth in range(1, sys.getrecursionlimit()):
|
||||
if not sys._getframe(depth).f_globals is globals():
|
||||
module_name = __GetModuleName(sys._getframe(depth).f_globals)
|
||||
if module_name == 'gflags':
|
||||
continue
|
||||
if module_name is not None:
|
||||
return module_name
|
||||
raise AssertionError("No module was found")
|
||||
|
||||
|
||||
# Copied from gflags because it is a private function
|
||||
def __GetModuleName(globals_dict):
|
||||
"""Given a globals dict, returns the name of the module that defines it.
|
||||
|
||||
Args:
|
||||
globals_dict: A dictionary that should correspond to an environment
|
||||
providing the values of the globals.
|
||||
|
||||
Returns:
|
||||
A string (the name of the module) or None (if the module could not
|
||||
be identified.
|
||||
|
||||
"""
|
||||
for name, module in sys.modules.iteritems():
|
||||
if getattr(module, '__dict__', None) is globals_dict:
|
||||
if name == '__main__':
|
||||
return sys.argv[0]
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def _wrapper(func):
|
||||
def _wrapped(*args, **kw):
|
||||
kw.setdefault('flag_values', FLAGS)
|
||||
func(*args, **kw)
|
||||
_wrapped.func_name = func.func_name
|
||||
return _wrapped
|
||||
|
||||
|
||||
FLAGS = FlagValues()
|
||||
gflags.FLAGS = FLAGS
|
||||
gflags._GetCallingModule = _GetCallingModule
|
||||
|
||||
|
||||
DEFINE = _wrapper(gflags.DEFINE)
|
||||
DEFINE_string = _wrapper(gflags.DEFINE_string)
|
||||
DEFINE_integer = _wrapper(gflags.DEFINE_integer)
|
||||
DEFINE_bool = _wrapper(gflags.DEFINE_bool)
|
||||
DEFINE_boolean = _wrapper(gflags.DEFINE_boolean)
|
||||
DEFINE_float = _wrapper(gflags.DEFINE_float)
|
||||
DEFINE_enum = _wrapper(gflags.DEFINE_enum)
|
||||
DEFINE_list = _wrapper(gflags.DEFINE_list)
|
||||
DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
|
||||
DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
|
||||
DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int)
|
||||
DEFINE_flag = _wrapper(gflags.DEFINE_flag)
|
||||
HelpFlag = gflags.HelpFlag
|
||||
HelpshortFlag = gflags.HelpshortFlag
|
||||
HelpXMLFlag = gflags.HelpXMLFlag
|
||||
|
||||
|
||||
def DECLARE(name, module_string, flag_values=FLAGS):
|
||||
if module_string not in sys.modules:
|
||||
__import__(module_string, globals(), locals())
|
||||
if name not in flag_values:
|
||||
raise gflags.UnrecognizedFlag(
|
||||
"%s not defined by %s" % (name, module_string))
|
||||
|
||||
|
||||
# __GLOBAL FLAGS ONLY__
|
||||
# Define any app-specific flags in their own files, docs at:
|
||||
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#a9
|
||||
|
||||
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../../'),
|
||||
"Top-level directory for maintaining quantum's state")
|
||||
|
@ -29,12 +29,13 @@ import socket
|
||||
import sys
|
||||
import ConfigParser
|
||||
|
||||
from common import exceptions
|
||||
import exceptions as exception
|
||||
import flags
|
||||
from exceptions import ProcessExecutionError
|
||||
|
||||
|
||||
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
def int_from_bool_as_string(subject):
|
||||
"""
|
||||
@ -71,9 +72,11 @@ def import_class(import_str):
|
||||
"""Returns a class from a string including module and class"""
|
||||
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||
try:
|
||||
#mod_str = os.path.join(FLAGS.state_path, mod_str)
|
||||
__import__(mod_str)
|
||||
return getattr(sys.modules[mod_str], class_str)
|
||||
except (ImportError, ValueError, AttributeError):
|
||||
except (ImportError, ValueError, AttributeError) as e:
|
||||
print e
|
||||
raise exception.NotFound('Class %s cannot be found' % class_str)
|
||||
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
#
|
||||
# Copyright 2011, Nicira Networks, Inc.
|
||||
@ -24,6 +25,8 @@ import logging
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
from xml.dom import minidom
|
||||
|
||||
import eventlet
|
||||
import eventlet.wsgi
|
||||
eventlet.patcher.monkey_patch(all=False, socket=True)
|
||||
@ -32,6 +35,10 @@ import routes.middleware
|
||||
import webob.dec
|
||||
import webob.exc
|
||||
|
||||
from quantum import utils
|
||||
from quantum.common import exceptions as exception
|
||||
|
||||
LOG = logging.getLogger('quantum.common.wsgi')
|
||||
|
||||
class WritableLogger(object):
|
||||
"""A thin wrapper that responds to `write` and logs."""
|
||||
@ -110,6 +117,104 @@ class Middleware(object):
|
||||
return self.process_response(response)
|
||||
|
||||
|
||||
class Request(webob.Request):
|
||||
|
||||
def best_match_content_type(self):
|
||||
"""Determine the most acceptable content-type.
|
||||
|
||||
Based on the query extension then the Accept header.
|
||||
|
||||
"""
|
||||
parts = self.path.rsplit('.', 1)
|
||||
LOG.debug("Request parts:%s",parts)
|
||||
if len(parts) > 1:
|
||||
format = parts[1]
|
||||
if format in ['json', 'xml']:
|
||||
return 'application/{0}'.format(parts[1])
|
||||
|
||||
ctypes = ['application/json', 'application/xml']
|
||||
bm = self.accept.best_match(ctypes)
|
||||
LOG.debug("BM:%s",bm)
|
||||
return bm or 'application/json'
|
||||
|
||||
def get_content_type(self):
|
||||
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)
|
||||
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")
|
||||
|
||||
|
||||
class Application(object):
|
||||
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
"""Used for paste app factories in paste.deploy config files.
|
||||
|
||||
Any local configuration (that is, values under the [app:APPNAME]
|
||||
section of the paste config) will be passed into the `__init__` method
|
||||
as kwargs.
|
||||
|
||||
A hypothetical configuration would look like:
|
||||
|
||||
[app:wadl]
|
||||
latest_version = 1.3
|
||||
paste.app_factory = nova.api.fancy_api:Wadl.factory
|
||||
|
||||
which would result in a call to the `Wadl` class as
|
||||
|
||||
import quantum.api.fancy_api
|
||||
fancy_api.Wadl(latest_version='1.3')
|
||||
|
||||
You could of course re-implement the `factory` method in subclasses,
|
||||
but using the kwarg passing it shouldn't be necessary.
|
||||
|
||||
"""
|
||||
return cls(**local_config)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
r"""Subclasses will probably want to implement __call__ like this:
|
||||
|
||||
@webob.dec.wsgify(RequestClass=Request)
|
||||
def __call__(self, req):
|
||||
# Any of the following objects work as responses:
|
||||
|
||||
# Option 1: simple string
|
||||
res = 'message\n'
|
||||
|
||||
# Option 2: a nicely formatted HTTP exception page
|
||||
res = exc.HTTPForbidden(detail='Nice try')
|
||||
|
||||
# Option 3: a webob Response object (in case you need to play with
|
||||
# headers, or you want to be treated like an iterable, or or or)
|
||||
res = Response();
|
||||
res.app_iter = open('somefile')
|
||||
|
||||
# Option 4: any wsgi app to be run next
|
||||
res = self.application
|
||||
|
||||
# Option 5: you can get a Response object for a wsgi app, too, to
|
||||
# play with headers etc
|
||||
res = req.get_response(self.application)
|
||||
|
||||
# You can then just return your response...
|
||||
return res
|
||||
# ... or set req.response and return None.
|
||||
req.response = res
|
||||
|
||||
See the end of http://pythonpaste.org/webob/modules/dec.html
|
||||
for more info.
|
||||
|
||||
"""
|
||||
raise NotImplementedError(_('You must implement __call__'))
|
||||
|
||||
|
||||
class Debug(Middleware):
|
||||
"""
|
||||
Helper class that can be inserted into any WSGI application chain
|
||||
@ -152,6 +257,13 @@ class Router(object):
|
||||
WSGI middleware that maps incoming requests to WSGI apps.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
"""
|
||||
Returns an instance of the WSGI Router class
|
||||
"""
|
||||
return cls()
|
||||
|
||||
def __init__(self, mapper):
|
||||
"""
|
||||
Create a router for the given routes.Mapper.
|
||||
@ -169,7 +281,7 @@ class Router(object):
|
||||
mapper.connect(None, "/svrlist", controller=sc, action="list")
|
||||
|
||||
# Actions are all implicitly defined
|
||||
mapper.resource("server", "servers", controller=sc)
|
||||
mapper.resource("network", "networks", controller=nc)
|
||||
|
||||
# Pointing to an arbitrary WSGI app. You can specify the
|
||||
# {path_info:.*} parameter so the target app can be handed just that
|
||||
@ -186,6 +298,7 @@ class Router(object):
|
||||
Route the incoming request to a controller based on self.map.
|
||||
If no match, return a 404.
|
||||
"""
|
||||
LOG.debug("HERE - wsgi.Router.__call__")
|
||||
return self._router
|
||||
|
||||
@staticmethod
|
||||
@ -204,92 +317,197 @@ class Router(object):
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""
|
||||
"""WSGI app that dispatched to methods.
|
||||
|
||||
WSGI app that reads routing information supplied by RoutesMiddleware
|
||||
and calls the requested action method upon itself. All action methods
|
||||
must, in addition to their normal parameters, accept a 'req' argument
|
||||
which is the incoming webob.Request. They raise a webob.exc exception,
|
||||
which is the incoming wsgi.Request. They raise a webob.exc exception,
|
||||
or return a dict which will be serialized by requested content type.
|
||||
|
||||
"""
|
||||
|
||||
@webob.dec.wsgify
|
||||
@webob.dec.wsgify(RequestClass=Request)
|
||||
def __call__(self, req):
|
||||
"""
|
||||
Call the method specified in req.environ by RoutesMiddleware.
|
||||
"""
|
||||
LOG.debug("HERE - wsgi.Controller.__call__")
|
||||
arg_dict = req.environ['wsgiorg.routing_args'][1]
|
||||
action = arg_dict['action']
|
||||
method = getattr(self, action)
|
||||
LOG.debug("ARG_DICT:%s",arg_dict)
|
||||
LOG.debug("Action:%s",action)
|
||||
LOG.debug("Method:%s",method)
|
||||
LOG.debug("%s %s" % (req.method, req.url))
|
||||
del arg_dict['controller']
|
||||
del arg_dict['action']
|
||||
arg_dict['request'] = req
|
||||
if 'format' in arg_dict:
|
||||
del arg_dict['format']
|
||||
arg_dict['req'] = req
|
||||
result = method(**arg_dict)
|
||||
|
||||
if type(result) is dict:
|
||||
return self._serialize(result, req)
|
||||
content_type = req.best_match_content_type()
|
||||
LOG.debug("Content type:%s",content_type)
|
||||
LOG.debug("Result:%s",result)
|
||||
default_xmlns = self.get_default_xmlns(req)
|
||||
body = self._serialize(result, content_type, default_xmlns)
|
||||
|
||||
response = webob.Response()
|
||||
response.headers['Content-Type'] = content_type
|
||||
response.body = body
|
||||
msg_dict = dict(url=req.url, status=response.status_int)
|
||||
msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
|
||||
LOG.debug(msg)
|
||||
return response
|
||||
else:
|
||||
return result
|
||||
|
||||
def _serialize(self, data, request):
|
||||
"""
|
||||
Serialize the given dict to the response type requested in request.
|
||||
def _serialize(self, data, content_type, default_xmlns):
|
||||
"""Serialize the given dict to the provided content_type.
|
||||
|
||||
Uses self._serialization_metadata if it exists, which is a dict mapping
|
||||
MIME types to information needed to serialize to that type.
|
||||
|
||||
"""
|
||||
_metadata = getattr(type(self), "_serialization_metadata", {})
|
||||
serializer = Serializer(request.environ, _metadata)
|
||||
return serializer.to_content_type(data)
|
||||
_metadata = getattr(type(self), '_serialization_metadata', {})
|
||||
|
||||
serializer = Serializer(_metadata, default_xmlns)
|
||||
try:
|
||||
return serializer.serialize(data, content_type)
|
||||
except exception.InvalidContentType:
|
||||
raise webob.exc.HTTPNotAcceptable()
|
||||
|
||||
def _deserialize(self, data, content_type):
|
||||
"""Deserialize the request body to the specefied content type.
|
||||
|
||||
Uses self._serialization_metadata if it exists, which is a dict mapping
|
||||
MIME types to information needed to serialize to that type.
|
||||
|
||||
"""
|
||||
_metadata = getattr(type(self), '_serialization_metadata', {})
|
||||
serializer = Serializer(_metadata)
|
||||
return serializer.deserialize(data, content_type)
|
||||
|
||||
def get_default_xmlns(self, req):
|
||||
"""Provide the XML namespace to use if none is otherwise specified."""
|
||||
return None
|
||||
|
||||
|
||||
class Serializer(object):
|
||||
"""
|
||||
Serializes a dictionary to a Content Type specified by a WSGI environment.
|
||||
"""
|
||||
"""Serializes and deserializes dictionaries to certain MIME types."""
|
||||
|
||||
def __init__(self, metadata=None, default_xmlns=None):
|
||||
"""Create a serializer based on the given WSGI environment.
|
||||
|
||||
def __init__(self, environ, metadata=None):
|
||||
"""
|
||||
Create a serializer based on the given WSGI environment.
|
||||
'metadata' is an optional dict mapping MIME types to information
|
||||
needed to serialize a dictionary to that type.
|
||||
"""
|
||||
self.environ = environ
|
||||
self.metadata = metadata or {}
|
||||
self._methods = {
|
||||
'application/json': self._to_json,
|
||||
'application/xml': self._to_xml}
|
||||
|
||||
def to_content_type(self, data):
|
||||
"""
|
||||
Serialize a dictionary into a string. The format of the string
|
||||
will be decided based on the Content Type requested in self.environ:
|
||||
by Accept: header, or by URL suffix.
|
||||
self.metadata = metadata or {}
|
||||
self.default_xmlns = default_xmlns
|
||||
|
||||
def _get_serialize_handler(self, content_type):
|
||||
handlers = {
|
||||
'application/json': self._to_json,
|
||||
'application/xml': self._to_xml,
|
||||
}
|
||||
|
||||
try:
|
||||
return handlers[content_type]
|
||||
except Exception:
|
||||
raise exception.InvalidContentType(content_type=content_type)
|
||||
|
||||
def serialize(self, data, content_type):
|
||||
"""Serialize a dictionary into the specified content type."""
|
||||
return self._get_serialize_handler(content_type)(data)
|
||||
|
||||
def deserialize(self, datastring, content_type):
|
||||
"""Deserialize a string to a dictionary.
|
||||
|
||||
The string must be in the format of a supported MIME type.
|
||||
|
||||
"""
|
||||
# FIXME(sirp): for now, supporting json only
|
||||
#mimetype = 'application/xml'
|
||||
mimetype = 'application/json'
|
||||
# TODO(gundlach): determine mimetype from request
|
||||
return self._methods.get(mimetype, repr)(data)
|
||||
return self.get_deserialize_handler(content_type)(datastring)
|
||||
|
||||
def get_deserialize_handler(self, content_type):
|
||||
handlers = {
|
||||
'application/json': self._from_json,
|
||||
'application/xml': self._from_xml,
|
||||
}
|
||||
|
||||
try:
|
||||
return handlers[content_type]
|
||||
except Exception:
|
||||
raise exception.InvalidContentType(content_type=content_type)
|
||||
|
||||
def _from_json(self, datastring):
|
||||
return utils.loads(datastring)
|
||||
|
||||
def _from_xml(self, datastring):
|
||||
xmldata = self.metadata.get('application/xml', {})
|
||||
plurals = set(xmldata.get('plurals', {}))
|
||||
node = minidom.parseString(datastring).childNodes[0]
|
||||
return {node.nodeName: self._from_xml_node(node, plurals)}
|
||||
|
||||
def _from_xml_node(self, node, listnames):
|
||||
"""Convert a minidom node to a simple Python type.
|
||||
|
||||
listnames is a collection of names of XML nodes whose subnodes should
|
||||
be considered list items.
|
||||
|
||||
"""
|
||||
if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
|
||||
return node.childNodes[0].nodeValue
|
||||
elif node.nodeName in listnames:
|
||||
return [self._from_xml_node(n, listnames) for n in node.childNodes]
|
||||
else:
|
||||
result = dict()
|
||||
for attr in node.attributes.keys():
|
||||
result[attr] = node.attributes[attr].nodeValue
|
||||
for child in node.childNodes:
|
||||
if child.nodeType != node.TEXT_NODE:
|
||||
result[child.nodeName] = self._from_xml_node(child,
|
||||
listnames)
|
||||
return result
|
||||
|
||||
def _to_json(self, data):
|
||||
def sanitizer(obj):
|
||||
if isinstance(obj, datetime.datetime):
|
||||
return obj.isoformat()
|
||||
return obj
|
||||
|
||||
return json.dumps(data, default=sanitizer)
|
||||
return utils.dumps(data)
|
||||
|
||||
def _to_xml(self, data):
|
||||
metadata = self.metadata.get('application/xml', {})
|
||||
# We expect data to contain a single key which is the XML root.
|
||||
root_key = data.keys()[0]
|
||||
from xml.dom import minidom
|
||||
doc = minidom.Document()
|
||||
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
|
||||
|
||||
xmlns = node.getAttribute('xmlns')
|
||||
if not xmlns and self.default_xmlns:
|
||||
node.setAttribute('xmlns', self.default_xmlns)
|
||||
|
||||
return node.toprettyxml(indent=' ')
|
||||
|
||||
def _to_xml_node(self, doc, metadata, nodename, data):
|
||||
"""Recursive method to convert data members to XML nodes."""
|
||||
result = doc.createElement(nodename)
|
||||
|
||||
# Set the xml namespace if one is specified
|
||||
# TODO(justinsb): We could also use prefixes on the keys
|
||||
xmlns = metadata.get('xmlns', None)
|
||||
if xmlns:
|
||||
result.setAttribute('xmlns', xmlns)
|
||||
LOG.debug("DATA:%s",data)
|
||||
if type(data) is list:
|
||||
LOG.debug("TYPE IS LIST")
|
||||
collections = metadata.get('list_collections', {})
|
||||
if nodename in collections:
|
||||
metadata = collections[nodename]
|
||||
for item in data:
|
||||
node = doc.createElement(metadata['item_name'])
|
||||
node.setAttribute(metadata['item_key'], str(item))
|
||||
result.appendChild(node)
|
||||
return result
|
||||
singular = metadata.get('plurals', {}).get(nodename, None)
|
||||
if singular is None:
|
||||
if nodename.endswith('s'):
|
||||
@ -300,6 +518,17 @@ class Serializer(object):
|
||||
node = self._to_xml_node(doc, metadata, singular, item)
|
||||
result.appendChild(node)
|
||||
elif type(data) is dict:
|
||||
LOG.debug("TYPE IS DICT")
|
||||
collections = metadata.get('dict_collections', {})
|
||||
if nodename in collections:
|
||||
metadata = collections[nodename]
|
||||
for k, v in data.items():
|
||||
node = doc.createElement(metadata['item_name'])
|
||||
node.setAttribute(metadata['item_key'], str(k))
|
||||
text = doc.createTextNode(str(v))
|
||||
node.appendChild(text)
|
||||
result.appendChild(node)
|
||||
return result
|
||||
attrs = metadata.get('attributes', {}).get(nodename, {})
|
||||
for k, v in data.items():
|
||||
if k in attrs:
|
||||
@ -307,7 +536,10 @@ class Serializer(object):
|
||||
else:
|
||||
node = self._to_xml_node(doc, metadata, k, v)
|
||||
result.appendChild(node)
|
||||
else: # atom
|
||||
else:
|
||||
# Type is atom
|
||||
LOG.debug("TYPE IS ATOM:%s",data)
|
||||
node = doc.createTextNode(str(data))
|
||||
result.appendChild(node)
|
||||
return result
|
||||
|
||||
|
@ -23,26 +23,33 @@ plugin that concretely implement quantum_plugin_base class
|
||||
|
||||
The caller should make sure that QuantumManager is a singleton.
|
||||
"""
|
||||
import gettext
|
||||
gettext.install('quantum', unicode=1)
|
||||
|
||||
from common import utils
|
||||
from quantum_plugin_base import QuantumPluginBase
|
||||
|
||||
CONFIG_FILE = "plugins.ini"
|
||||
|
||||
|
||||
class QuantumManager(object):
|
||||
|
||||
def __init__(self,config=CONFIG_FILE):
|
||||
|
||||
def __init__(self,config=CONFIG_FILE):
|
||||
self.configuration_file = CONFIG_FILE
|
||||
plugin_location = utils.getPluginFromConfig(CONFIG_FILE)
|
||||
print "PLUGIN LOCATION:%s" % plugin_location
|
||||
plugin_klass = utils.import_class(plugin_location)
|
||||
if not issubclass(plugin_klass, QuantumPluginBase):
|
||||
raise Exception("Configured Quantum plug-in didn't pass compatibility test")
|
||||
raise Exception("Configured Quantum plug-in " \
|
||||
"didn't pass compatibility test")
|
||||
else:
|
||||
print("Successfully imported Quantum plug-in. All compatibility tests passed\n")
|
||||
print("Successfully imported Quantum plug-in." \
|
||||
"All compatibility tests passed\n")
|
||||
self.plugin = plugin_klass()
|
||||
|
||||
def get_manager(self):
|
||||
return self.plugin
|
||||
|
||||
def get_manager(self):
|
||||
return self.plugin
|
||||
|
||||
|
||||
# TODO(somik): rmove the main class
|
||||
# Added for temporary testing purposes
|
||||
@ -55,4 +62,3 @@ def main():
|
||||
# Standard boilerplate to call the main() function.
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
[PLUGIN]
|
||||
# Quantum plugin provider module
|
||||
provider = plugins.SamplePlugin.DummyDataPlugin
|
||||
provider = quantum.plugins.SamplePlugin.FakePlugin
|
||||
|
@ -15,6 +15,8 @@
|
||||
# under the License.
|
||||
# @author: Somik Behera, Nicira Networks, Inc.
|
||||
|
||||
from quantum.common import exceptions as exc
|
||||
|
||||
class QuantumEchoPlugin(object):
|
||||
|
||||
"""
|
||||
@ -89,6 +91,12 @@ class QuantumEchoPlugin(object):
|
||||
is deleted.
|
||||
"""
|
||||
print("delete_port() called\n")
|
||||
|
||||
def update_port(self, tenant_id, net_id, port_id, port_state):
|
||||
"""
|
||||
Updates the state of a port on the specified Virtual Network.
|
||||
"""
|
||||
print("update_port() called\n")
|
||||
|
||||
|
||||
def get_port_details(self, tenant_id, net_id, port_id):
|
||||
@ -113,22 +121,7 @@ class QuantumEchoPlugin(object):
|
||||
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")
|
||||
|
||||
|
||||
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")
|
||||
|
||||
|
||||
class DummyDataPlugin(object):
|
||||
|
||||
@ -202,6 +195,12 @@ class DummyDataPlugin(object):
|
||||
print("create_port() called\n")
|
||||
#return the port id
|
||||
return 201
|
||||
|
||||
def update_port(self, tenant_id, net_id, port_id, port_state):
|
||||
"""
|
||||
Updates the state of a port on the specified Virtual Network.
|
||||
"""
|
||||
print("update_port() called\n")
|
||||
|
||||
|
||||
def delete_port(self, tenant_id, net_id, port_id):
|
||||
@ -240,23 +239,226 @@ class DummyDataPlugin(object):
|
||||
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"
|
||||
class FakePlugin(object):
|
||||
"""
|
||||
FakePlugin is a demo plugin that provides
|
||||
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 get_all_attached_interfaces(self, tenant_id, net_id):
|
||||
def __init__(self):
|
||||
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):
|
||||
"""
|
||||
Retrieves all remote interfaces that are attached to
|
||||
a particular Virtual Network.
|
||||
Returns a dictionary containing all
|
||||
<network_uuid, network_name> for
|
||||
the specified tenant.
|
||||
"""
|
||||
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
|
||||
print("get_all_networks() called\n")
|
||||
return FakePlugin._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._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")
|
||||
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,
|
||||
'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 = FakePlugin._networks.get(net_id)
|
||||
# Verify that no attachments are plugged into the network
|
||||
if net:
|
||||
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
|
||||
# Network not found
|
||||
raise exc.NetworkNotFound(net_id=net_id)
|
||||
|
||||
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._get_network(tenant_id, net_id)
|
||||
net['net-name']=new_name
|
||||
return net
|
||||
|
||||
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")
|
||||
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")
|
||||
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,
|
||||
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")
|
||||
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 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):
|
||||
"""
|
||||
Detaches a remote interface from the specified port on the
|
||||
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
|
||||
|
||||
|
@ -79,12 +79,20 @@ class QuantumPluginBase(object):
|
||||
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):
|
||||
"""
|
||||
@ -119,28 +127,13 @@ class QuantumPluginBase(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_interface_details(self, tenant_id, net_id, port_id):
|
||||
"""
|
||||
Retrieves the remote interface that is attached at this
|
||||
particular port.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_all_attached_interfaces(self, tenant_id, net_id):
|
||||
"""
|
||||
Retrieves all remote interfaces that are attached to
|
||||
a particular Virtual Network.
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def __subclasshook__(cls, klass):
|
||||
"""
|
||||
The __subclasshook__ method is a class method
|
||||
that will be called everytime a class is tested
|
||||
using issubclass(klass, Plugin).
|
||||
using issubclass(klass, Plugin).
|
||||
In that case, it will check that every method
|
||||
marked with the abstractmethod decorator is
|
||||
provided by the plugin class.
|
||||
@ -152,5 +145,3 @@ class QuantumPluginBase(object):
|
||||
return NotImplemented
|
||||
return True
|
||||
return NotImplemented
|
||||
|
||||
|
||||
|
@ -15,30 +15,99 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import routes
|
||||
from common import wsgi
|
||||
from webob import Response
|
||||
import logging
|
||||
from quantum.common import config
|
||||
from quantum.common import wsgi
|
||||
from quantum.common import exceptions as exception
|
||||
|
||||
|
||||
class NetworkController(wsgi.Controller):
|
||||
|
||||
def version(self, request):
|
||||
return "Quantum version 0.1"
|
||||
LOG = logging.getLogger('quantum.service')
|
||||
|
||||
|
||||
class API(wsgi.Router):
|
||||
def __init__(self, options):
|
||||
self.options = options
|
||||
mapper = routes.Mapper()
|
||||
network_controller = NetworkController()
|
||||
mapper.resource("net_controller", "/network",
|
||||
controller=network_controller)
|
||||
mapper.connect("/", controller=network_controller, action="version")
|
||||
super(API, self).__init__(mapper)
|
||||
class WsgiService(object):
|
||||
"""Base class for WSGI based services.
|
||||
|
||||
For each api you define, you must also define these flags:
|
||||
:<api>_listen: The address on which to listen
|
||||
:<api>_listen_port: The port on which to listen
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, app_name, conf_file, conf):
|
||||
self.app_name = app_name
|
||||
self.conf_file = conf_file
|
||||
self.conf = conf
|
||||
self.wsgi_app = None
|
||||
|
||||
def start(self):
|
||||
self.wsgi_app = _run_wsgi(self.app_name, self.conf, self.conf_file)
|
||||
|
||||
def wait(self):
|
||||
self.wsgi_app.wait()
|
||||
|
||||
|
||||
def app_factory(global_conf, **local_conf):
|
||||
conf = global_conf.copy()
|
||||
conf.update(local_conf)
|
||||
return API(conf)
|
||||
class QuantumApiService(WsgiService):
|
||||
"""Class for quantum-api service."""
|
||||
|
||||
@classmethod
|
||||
def create(cls, conf=None, options=None, args=None):
|
||||
app_name = "quantum"
|
||||
if not conf:
|
||||
conf_file, conf = config.load_paste_config(
|
||||
app_name, options, args)
|
||||
if not conf:
|
||||
message = (_('No paste configuration found for: %s'),
|
||||
app_name)
|
||||
raise exception.Error(message)
|
||||
|
||||
# Setup logging early, supplying both the CLI options and the
|
||||
# configuration mapping from the config file
|
||||
# We only update the conf dict for the verbose and debug
|
||||
# flags. Everything else must be set up in the conf file...
|
||||
# Log the options used when starting if we're in debug mode...
|
||||
|
||||
config.setup_logging(options, conf)
|
||||
debug = options.get('debug') or \
|
||||
config.get_option(conf, 'debug',
|
||||
type='bool', default=False)
|
||||
verbose = options.get('verbose') or \
|
||||
config.get_option(conf, 'verbose',
|
||||
type='bool', default=False)
|
||||
conf['debug'] = debug
|
||||
conf['verbose'] = verbose
|
||||
LOG.debug("*" * 80)
|
||||
LOG.debug("Configuration options gathered from config file:")
|
||||
LOG.debug(conf_file)
|
||||
LOG.debug("================================================")
|
||||
items = dict([(k, v) for k, v in conf.items()
|
||||
if k not in ('__file__', 'here')])
|
||||
for key, value in sorted(items.items()):
|
||||
LOG.debug("%(key)-30s %(value)s" % locals())
|
||||
LOG.debug("*" * 80)
|
||||
service = cls(app_name, conf_file, conf)
|
||||
return service
|
||||
|
||||
|
||||
def serve_wsgi(cls, conf=None, options = None, args=None):
|
||||
try:
|
||||
service = cls.create(conf, options, args)
|
||||
except Exception:
|
||||
logging.exception('in WsgiService.create()')
|
||||
raise
|
||||
|
||||
service.start()
|
||||
|
||||
return service
|
||||
|
||||
|
||||
def _run_wsgi(app_name, paste_conf, paste_config_file):
|
||||
LOG.info(_('Using paste.deploy config at: %s'), paste_config_file)
|
||||
app = config.load_paste_app(paste_config_file, app_name)
|
||||
if not app:
|
||||
LOG.error(_('No known API applications configured in %s.'),
|
||||
paste_config_file)
|
||||
return
|
||||
server = wsgi.Server()
|
||||
server.start(app,
|
||||
int(paste_conf['bind_port']), paste_conf['bind_host'])
|
||||
return server
|
||||
|
@ -35,6 +35,8 @@ import sys
|
||||
import time
|
||||
import types
|
||||
|
||||
from common import exceptions as exception
|
||||
|
||||
|
||||
def import_class(import_str):
|
||||
"""Returns a class from a string including module and class."""
|
||||
@ -55,3 +57,36 @@ def import_object(import_str):
|
||||
except ImportError:
|
||||
cls = import_class(import_str)
|
||||
return cls()
|
||||
|
||||
|
||||
def to_primitive(value):
|
||||
if type(value) is type([]) or type(value) is type((None,)):
|
||||
o = []
|
||||
for v in value:
|
||||
o.append(to_primitive(v))
|
||||
return o
|
||||
elif type(value) is type({}):
|
||||
o = {}
|
||||
for k, v in value.iteritems():
|
||||
o[k] = to_primitive(v)
|
||||
return o
|
||||
elif isinstance(value, datetime.datetime):
|
||||
return str(value)
|
||||
elif hasattr(value, 'iteritems'):
|
||||
return to_primitive(dict(value.iteritems()))
|
||||
elif hasattr(value, '__iter__'):
|
||||
return to_primitive(list(value))
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def dumps(value):
|
||||
try:
|
||||
return json.dumps(value)
|
||||
except TypeError:
|
||||
pass
|
||||
return json.dumps(to_primitive(value))
|
||||
|
||||
|
||||
def loads(s):
|
||||
return json.loads(s)
|
||||
|
0
smoketests/__init__.py
Normal file
0
smoketests/__init__.py
Normal file
98
smoketests/miniclient.py
Normal file
98
smoketests/miniclient.py
Normal file
@ -0,0 +1,98 @@
|
||||
# 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
|
133
smoketests/tests.py
Normal file
133
smoketests/tests.py
Normal file
@ -0,0 +1,133 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Citrix Systems
|
||||
# Copyright 2011 Nicira Networks
|
||||
# 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
|
||||
import simplejson
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
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'
|
||||
FORMAT = "json"
|
||||
|
||||
test_network1_data = \
|
||||
{'network': {'network-name': 'test1' }}
|
||||
test_network2_data = \
|
||||
{'network': {'network-name': 'test2' }}
|
||||
|
||||
def print_response(res):
|
||||
content = res.read()
|
||||
print "Status: %s" %res.status
|
||||
print "Content: %s" %content
|
||||
return content
|
||||
|
||||
class QuantumTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = MiniClient(HOST, PORT, USE_SSL)
|
||||
|
||||
def create_network(self, data):
|
||||
content_type = "application/" + FORMAT
|
||||
body = Serializer().serialize(data, content_type)
|
||||
res = self.client.do_request(TENANT_ID, 'POST', "/networks." + FORMAT,
|
||||
body=body)
|
||||
self.assertEqual(res.status, 200, "bad response: %s" % res.read())
|
||||
|
||||
def test_listNetworks(self):
|
||||
self.create_network(test_network1_data)
|
||||
self.create_network(test_network2_data)
|
||||
res = self.client.do_request(TENANT_ID,'GET', "/networks." + FORMAT)
|
||||
self.assertEqual(res.status, 200, "bad response: %s" % res.read())
|
||||
|
||||
def test_createNetwork(self):
|
||||
self.create_network(test_network1_data)
|
||||
|
||||
def test_createPort(self):
|
||||
self.create_network(test_network1_data)
|
||||
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
|
||||
resdict = simplejson.loads(res.read())
|
||||
for n in resdict["networks"]:
|
||||
net_id = n["id"]
|
||||
|
||||
# Step 1 - List Ports for network (should not find any)
|
||||
res = self.client.do_request(TENANT_ID, 'GET',
|
||||
"/networks/%s/ports.%s" % (net_id, FORMAT))
|
||||
self.assertEqual(res.status, 200, "Bad response: %s" % res.read())
|
||||
output = res.read()
|
||||
self.assertTrue(len(output) == 0,
|
||||
"Found unexpected ports: %s" % output)
|
||||
|
||||
# Step 2 - Create Port for network
|
||||
res = self.client.do_request(TENANT_ID, 'POST',
|
||||
"/networks/%s/ports.%s" % (net_id, FORMAT))
|
||||
self.assertEqual(res.status, 200, "Bad response: %s" % output)
|
||||
|
||||
# Step 3 - List Ports for network (again); should find one
|
||||
res = self.client.do_request(TENANT_ID, 'GET',
|
||||
"/networks/%s/ports.%s" % (net_id, FORMAT))
|
||||
output = res.read()
|
||||
self.assertEqual(res.status, 200, "Bad response: %s" % output)
|
||||
resdict = simplejson.loads(output)
|
||||
ids = []
|
||||
for p in resdict["ports"]:
|
||||
ids.append(p["id"])
|
||||
self.assertTrue(len(ids) == 1,
|
||||
"Didn't find expected # of ports (1): %s" % ids)
|
||||
|
||||
def test_renameNetwork(self):
|
||||
self.create_network(test_network1_data)
|
||||
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
|
||||
resdict = simplejson.loads(res.read())
|
||||
net_id = resdict["networks"][0]["id"]
|
||||
|
||||
data = test_network1_data.copy()
|
||||
data['network']['network-name'] = 'test_renamed'
|
||||
content_type = "application/" + FORMAT
|
||||
body = Serializer().serialize(data, content_type)
|
||||
res = self.client.do_request(TENANT_ID, 'PUT',
|
||||
"/networks/%s.%s" % (net_id, FORMAT), body=body)
|
||||
resdict = simplejson.loads(res.read())
|
||||
self.assertTrue(resdict["networks"]["network"]["id"] == net_id,
|
||||
"Network_rename: renamed network has a different uuid")
|
||||
self.assertTrue(resdict["networks"]["network"]["name"] == "test_renamed",
|
||||
"Network rename didn't take effect")
|
||||
|
||||
def delete_networks(self):
|
||||
# Remove all the networks created on the tenant
|
||||
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
|
||||
resdict = simplejson.loads(res.read())
|
||||
for n in resdict["networks"]:
|
||||
net_id = n["id"]
|
||||
res = self.client.do_request(TENANT_ID, 'DELETE',
|
||||
"/networks/" + net_id + "." + FORMAT)
|
||||
self.assertEqual(res.status, 202)
|
||||
|
||||
def tearDown(self):
|
||||
self.delete_networks()
|
||||
|
||||
# Standard boilerplate to call the main() function.
|
||||
if __name__ == '__main__':
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(QuantumTest)
|
||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
0
test_scripts/__init__.py
Normal file
0
test_scripts/__init__.py
Normal file
98
test_scripts/miniclient.py
Normal file
98
test_scripts/miniclient.py
Normal file
@ -0,0 +1,98 @@
|
||||
# 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
|
150
test_scripts/tests.py
Normal file
150
test_scripts/tests.py
Normal file
@ -0,0 +1,150 @@
|
||||
# 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()
|
Loading…
x
Reference in New Issue
Block a user