Remove v1 code from quantum-server

bp remove-v1-related-code

Removes API, DB, and test code that is used only for Quantum v1 API,
which is no longer supported.

Includes removal of v1 code for sample, ovs, linuxbridge and ryu plugin.

Nicira and Cisco plugins will be handled outside of this patchset.

Change-Id: Id34dc7229bb7b399b5cfd4602dbc8d5ee4e8de61
This commit is contained in:
Dan Wendlandt 2012-08-10 10:37:26 -07:00
parent 2a2d7f2e3a
commit 77573d7338
50 changed files with 87 additions and 5956 deletions

View File

@ -1,20 +1,8 @@
[composite:quantum]
use = egg:Paste#urlmap
/: quantumversions
/v1.0: quantumapi_v1_0
/v1.1: quantumapi_v1_1
/v2.0: quantumapi_v2_0
[composite:quantumapi_v1_0]
use = call:quantum.auth:pipeline_factory
noauth = extensions quantumapiapp_v1_0
keystone = authtoken keystonecontext extensions quantumapiapp_v1_0
[composite:quantumapi_v1_1]
use = call:quantum.auth:pipeline_factory
noauth = extensions quantumapiapp_v1_1
keystone = authtoken keystonecontext extensions quantumapiapp_v1_1
[composite:quantumapi_v2_0]
use = call:quantum.auth:pipeline_factory
noauth = extensions quantumapiapp_v2_0
@ -38,11 +26,5 @@ paste.filter_factory = quantum.extensions.extensions:plugin_aware_extension_midd
[app:quantumversions]
paste.app_factory = quantum.api.versions:Versions.factory
[app:quantumapiapp_v1_0]
paste.app_factory = quantum.api:APIRouterV10.factory
[app:quantumapiapp_v1_1]
paste.app_factory = quantum.api:APIRouterV11.factory
[app:quantumapiapp_v2_0]
paste.app_factory = quantum.api.v2.router:APIRouter.factory

View File

@ -1,108 +0,0 @@
# 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 attachments
from quantum.api import networks
from quantum.api import ports
from quantum.common import flags
from quantum import manager
from quantum import wsgi
LOG = logging.getLogger('quantum.api')
FLAGS = flags.FLAGS
class APIRouter(wsgi.Router):
"""
Base class for Quantum API routes.
"""
_version = None
def __init__(self):
mapper = self._mapper()
self._setup_routes(mapper)
super(APIRouter, self).__init__(mapper)
def _mapper(self):
return routes.Mapper()
def _setup_routes(self, mapper):
self._setup_base_routes(mapper, self._version)
def _setup_base_routes(self, mapper, version):
"""Routes common to all versions."""
# Loads the quantum plugin
# Note(salvatore-orlando): Should the plugin be versioned
# I don't think so
plugin = manager.QuantumManager.get_plugin()
uri_prefix = '/tenants/{tenant_id}/'
attachment_path = (
'%snetworks/{network_id}/ports/{id}/attachment{.format}' %
uri_prefix)
mapper.resource('network', 'networks',
controller=networks.create_resource(plugin, version),
collection={'detail': 'GET'},
member={'detail': 'GET'},
path_prefix=uri_prefix)
mapper.resource('port', 'ports',
controller=ports.create_resource(plugin, version),
collection={'detail': 'GET'},
member={'detail': 'GET'},
parent_resource=dict(
member_name='network',
collection_name='%snetworks' % uri_prefix))
attachments_ctrl = attachments.create_resource(plugin, version)
mapper.connect("get_resource",
attachment_path,
controller=attachments_ctrl,
action="get_resource",
conditions=dict(method=['GET']))
mapper.connect("attach_resource",
attachment_path,
controller=attachments_ctrl,
action="attach_resource",
conditions=dict(method=['PUT']))
mapper.connect("detach_resource",
attachment_path,
controller=attachments_ctrl,
action="detach_resource",
conditions=dict(method=['DELETE']))
class APIRouterV10(APIRouter):
"""
API routes mappings for Quantum API v1.0
"""
_version = '1.0'
class APIRouterV11(APIRouter):
"""
API routes mappings for Quantum API v1.1
"""
_version = '1.1'

View File

@ -26,143 +26,6 @@ from quantum import wsgi
LOG = logging.getLogger(__name__)
XML_NS_V10 = 'http://openstack.org/quantum/api/v1.0'
XML_NS_V11 = 'http://openstack.org/quantum/api/v1.1'
class OperationalStatus:
""" Enumeration for operational status
UP : the resource is available (operationall up)
DOWN : the resource is not operational; this might indicate
a failure in the underlying switching fabric.
PROVISIONING: the plugin is creating or updating the resource
in the underlying switching fabric
UNKNOWN: the plugin does not support the operational status concept.
"""
UP = "UP"
DOWN = "DOWN"
PROVISIONING = "PROVISIONING"
UNKNOWN = "UNKNOWN"
def create_resource(version, controller_dict):
"""
Generic function for creating a wsgi resource
The function takes as input:
- desired version
- controller and metadata dictionary
e.g.: {'1.0': [ctrl_v10, meta_v10, xml_ns],
'1.1': [ctrl_v11, meta_v11, xml_ns]}
"""
# the first element of the iterable is expected to be the controller
controller = controller_dict[version][0]
# the second element should be the metadata
metadata = controller_dict[version][1]
# and the third element the xml namespace
xmlns = controller_dict[version][2]
# and also the function for building the fault body
fault_body_function = faults.fault_body_function(version)
headers_serializers = {
'1.0': HeaderSerializer10(),
'1.1': HeaderSerializer11()
}
xml_serializer = wsgi.XMLDictSerializer(metadata, xmlns)
json_serializer = wsgi.JSONDictSerializer()
xml_deserializer = wsgi.XMLDeserializer(metadata)
json_deserializer = wsgi.JSONDeserializer()
body_serializers = {
'application/xml': xml_serializer,
'application/json': json_serializer,
}
body_deserializers = {
'application/xml': xml_deserializer,
'application/json': json_deserializer,
}
serializer = wsgi.ResponseSerializer(body_serializers,
headers_serializers[version])
deserializer = wsgi.RequestDeserializer(body_deserializers)
return wsgi.Resource(controller,
fault_body_function,
deserializer,
serializer)
def APIFaultWrapper(errors=None):
quantum_error_dict = {
'1.0': faults.Quantum10HTTPError,
'1.1': faults.Quantum11HTTPError
}
def wrapper(func, **kwargs):
def the_func(*args, **kwargs):
try:
# Grab API version from type of controller
controller = args[0]
version = controller.version
return func(*args, **kwargs)
except Exception as e:
if errors is not None and type(e) in errors:
# Version-specific behaviour
quantum_error_class = quantum_error_dict[version]
raise quantum_error_class(e)
# otherwise just re-raise
raise
the_func.__name__ = func.__name__
return the_func
return wrapper
class HeaderSerializer10(wsgi.ResponseHeaderSerializer):
"""
Defines default respone status codes for Quantum API 1.0 operations
create - 200 OK
update - 204 NOCONTENT
delete - 204 NOCONTENT
others - 200 OK (defined in base class)
"""
def create(self, response, data):
response.status_int = 200
def delete(self, response, data):
response.status_int = 204
def update(self, response, data):
response.status_int = 204
def attach_resource(self, response, data):
response.status_int = 204
def detach_resource(self, response, data):
response.status_int = 204
class HeaderSerializer11(HeaderSerializer10):
"""
Defines default respone status codes for Quantum API 1.0 operations
create - 202 ACCEPTED
update - 204 NOCONTENT
delete - 204 NOCONTENT
others - 200 OK (defined in base class)
"""
def create(self, response, data):
response.status_int = 202
class QuantumController(object):
""" Base controller class for Quantum API """
# _resource_name will be redefined in sub concrete controller

View File

@ -1,89 +0,0 @@
# 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 quantum.api import api_common as common
from quantum.api.views import attachments as attachments_view
from quantum.common import exceptions as exception
LOG = logging.getLogger(__name__)
def create_resource(plugin, version):
controller_dict = {
'1.0': [ControllerV10(plugin),
ControllerV10._serialization_metadata,
common.XML_NS_V10],
'1.1': [ControllerV11(plugin),
ControllerV11._serialization_metadata,
common.XML_NS_V11],
}
return common.create_resource(version, controller_dict)
class Controller(common.QuantumController):
""" Port API controller for Quantum API """
_resource_name = 'attachment'
# version will be redefined by in child class
version = None
_attachment_ops_param_list = [
{
'param-name': 'id',
'required': True,
},
]
_serialization_metadata = {
"application/xml": {
"attributes": {
"attachment": ["id"],
},
},
}
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.PortNotFound])
def get_resource(self, request, tenant_id, network_id, id):
att_data = self._plugin.get_port_details(tenant_id, network_id, id)
builder = attachments_view.get_view_builder(request)
result = builder.build(att_data)['attachment']
return dict(attachment=result)
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.PortNotFound,
exception.PortInUse,
exception.AlreadyAttached])
def attach_resource(self, request, tenant_id, network_id, id, body):
body = self._prepare_request_body(body,
self._attachment_ops_param_list)
self._plugin.plug_interface(tenant_id, network_id, id,
body['attachment']['id'])
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.PortNotFound])
def detach_resource(self, request, tenant_id, network_id, id):
self._plugin.unplug_interface(tenant_id, network_id, id)
class ControllerV10(Controller):
"""Attachment resources controller for Quantum v1.0 API"""
version = "1.0"
class ControllerV11(Controller):
"""Attachment resources controller for Quantum v1.1 API"""
version = "1.1"

View File

@ -1,187 +0,0 @@
# 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.exc
from quantum.common import exceptions
_NETNOTFOUND_EXPL = 'Unable to find a network with the specified identifier.'
_NETINUSE_EXPL = 'Unable to remove the network: attachments still plugged.'
_PORTNOTFOUND_EXPL = 'Unable to find a port with the specified identifier.'
_STATEINVALID_EXPL = 'Unable to update port state with specified value.'
_PORTINUSE_EXPL = 'A resource is currently attached to the logical port'
_ALREADYATTACHED_EXPL = 'The resource is already attached to another port'
_NOTIMPLEMENTED_EXPL = 'Not implemented'
def fault_body_function_v10(wrapped_exc):
""" This function creates the contents of the body for a fault
response for Quantum API v1.0.
:param wrapped_exc: Exception thrown by the Quantum service
:type wrapped_exc: quantum.common.exceptions.QuantumException
:returns: response body contents and serialization metadata
:rtype: tuple
"""
code = wrapped_exc.status_int
fault_name = (hasattr(wrapped_exc, 'title') and
wrapped_exc.title or "quantumServiceFault")
fault_data = {
fault_name: {
'code': code,
'message': wrapped_exc.explanation,
'detail': str(wrapped_exc.detail),
},
}
metadata = {'attributes': {fault_name: ['code']}}
return fault_data, metadata
def fault_body_function_v11(wrapped_exc):
""" This function creates the contents of the body for a fault
response for Quantum API v1.1.
:param wrapped_exc: Exception thrown by the Quantum service
:type wrapped_exc: quantum.common.exceptions.QuantumException
:returns: response body contents and serialization metadata
:rtype: tuple
"""
fault_name = (hasattr(wrapped_exc, 'type') and
wrapped_exc.type or "QuantumServiceFault")
# Ensure first letter is capital
fault_name = fault_name[0].upper() + fault_name[1:]
fault_data = {
'QuantumError': {
'type': fault_name,
'message': wrapped_exc.explanation,
'detail': str(wrapped_exc.detail),
},
}
# Metadata not required for v11
return fault_data, None
def fault_body_function(version):
# dict mapping API version to functions for building the
# fault response body
fault_body_function_dict = {
'1.0': fault_body_function_v10,
'1.1': fault_body_function_v11
}
return fault_body_function_dict.get(version, None)
class Quantum10HTTPError(webob.exc.HTTPClientError):
_fault_dict = {
exceptions.NetworkNotFound: {
'code': 420,
'title': 'networkNotFound',
'explanation': _NETNOTFOUND_EXPL
},
exceptions.NetworkInUse: {
'code': 421,
'title': 'networkInUse',
'explanation': _NETINUSE_EXPL
},
exceptions.PortNotFound: {
'code': 430,
'title': 'portNotFound',
'explanation': _PORTNOTFOUND_EXPL
},
exceptions.StateInvalid: {
'code': 431,
'title': 'requestedStateInvalid',
'explanation': _STATEINVALID_EXPL
},
exceptions.PortInUse: {
'code': 432,
'title': 'portInUse',
'explanation': _PORTINUSE_EXPL
},
exceptions.AlreadyAttached: {
'code': 440,
'title': 'alreadyAttached',
'explanation': _ALREADYATTACHED_EXPL
},
exceptions.NotImplementedError: {
'code': 501,
'title': 'notImplemented',
'explanation': _NOTIMPLEMENTED_EXPL
}
}
def __init__(self, inner_exc):
_fault_data = self._fault_dict.get(type(inner_exc), None)
if _fault_data:
self.code = _fault_data['code']
self.title = _fault_data['title']
self.explanation = _fault_data['explanation']
super(webob.exc.HTTPClientError, self).__init__(inner_exc)
class Quantum11HTTPError(webob.exc.HTTPClientError):
_fault_dict = {
exceptions.NetworkNotFound: {
'code': webob.exc.HTTPNotFound.code,
'title': webob.exc.HTTPNotFound.title,
'type': 'NetworkNotFound',
'explanation': _NETNOTFOUND_EXPL
},
exceptions.NetworkInUse: {
'code': webob.exc.HTTPConflict.code,
'title': webob.exc.HTTPConflict.title,
'type': 'NetworkInUse',
'explanation': _NETINUSE_EXPL
},
exceptions.PortNotFound: {
'code': webob.exc.HTTPNotFound.code,
'title': webob.exc.HTTPNotFound.title,
'type': 'PortNotFound',
'explanation': _PORTNOTFOUND_EXPL
},
exceptions.StateInvalid: {
'code': webob.exc.HTTPBadRequest.code,
'title': webob.exc.HTTPBadRequest.title,
'type': 'RequestedStateInvalid',
'explanation': _STATEINVALID_EXPL
},
exceptions.PortInUse: {
'code': webob.exc.HTTPConflict.code,
'title': webob.exc.HTTPConflict.title,
'type': 'PortInUse',
'explanation': _PORTINUSE_EXPL
},
exceptions.AlreadyAttached: {
'code': webob.exc.HTTPConflict.code,
'title': webob.exc.HTTPConflict.title,
'type': 'AlreadyAttached',
'explanation': _ALREADYATTACHED_EXPL
}
}
def __init__(self, inner_exc):
_fault_data = self._fault_dict.get(type(inner_exc), None)
if _fault_data:
self.code = _fault_data['code']
self.title = _fault_data['title']
self.explanation = _fault_data['explanation']
self.type = _fault_data['type']
super(webob.exc.HTTPClientError, self).__init__(inner_exc)

View File

@ -1,188 +0,0 @@
# 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 quantum.api import api_common as common
from quantum.api.views import filters
from quantum.api.views import networks as networks_view
from quantum.common import exceptions as exception
LOG = logging.getLogger(__name__)
def create_resource(plugin, version):
controller_dict = {
'1.0': [ControllerV10(plugin),
ControllerV10._serialization_metadata,
common.XML_NS_V10],
'1.1': [ControllerV11(plugin),
ControllerV11._serialization_metadata,
common.XML_NS_V11],
}
return common.create_resource(version, controller_dict)
class Controller(common.QuantumController):
""" Network API controller for Quantum API """
_resource_name = 'network'
# version will be redefined in child class
version = None
_network_ops_param_list = [
{'param-name': 'name', 'required': True},
]
def _item(self, request, tenant_id, network_id,
net_details=True, port_details=False):
# We expect get_network_details to return information
# concerning logical ports as well.
network = self._plugin.get_network_details(tenant_id, network_id)
# Doing this in the API is inefficient
# TODO(salvatore-orlando): This should be fixed with Bug #834012
# Don't pass filter options
ports_data = None
if port_details:
port_list = self._plugin.get_all_ports(tenant_id, network_id)
ports_data = [
self._plugin.get_port_details(tenant_id, network_id,
port['port-id'])
for port in port_list]
builder = networks_view.get_view_builder(request, self.version)
result = builder.build(network, net_details,
ports_data, port_details)['network']
return dict(network=result)
def _items(self, request, tenant_id, net_details=False):
""" Returns a list of networks.
Ideally, the plugin would perform filtering,
returning only the items matching filters specified
on the request query string.
However, plugins are not required to support filtering.
In this case, this function will filter the complete list
of networks returned by the plugin
"""
filter_opts = {}
filter_opts.update(request.GET)
networks = self._plugin.get_all_networks(tenant_id,
filter_opts=filter_opts)
# Inefficient, API-layer filtering
# will be performed only for the filters not implemented by the plugin
# NOTE(salvatore-orlando): the plugin is supposed to leave only filters
# it does not implement in filter_opts
networks = filters.filter_networks(networks,
self._plugin,
tenant_id,
filter_opts)
builder = networks_view.get_view_builder(request, self.version)
result = [builder.build(network, net_details)['network']
for network in networks]
return dict(networks=result)
@common.APIFaultWrapper()
def index(self, request, tenant_id):
""" Returns a list of network ids """
return self._items(request, tenant_id)
@common.APIFaultWrapper([exception.NetworkNotFound])
def show(self, request, tenant_id, id):
""" Returns network details for the given network id """
return self._item(request, tenant_id, id,
net_details=True, port_details=False)
@common.APIFaultWrapper([exception.NetworkNotFound])
def detail(self, request, **kwargs):
tenant_id = kwargs.get('tenant_id')
network_id = kwargs.get('id')
if network_id:
# show details for a given network
return self._item(request, tenant_id, network_id,
net_details=True, port_details=True)
else:
# show details for all networks
return self._items(request, tenant_id, net_details=True)
@common.APIFaultWrapper()
def create(self, request, tenant_id, body):
""" Creates a new network for a given tenant """
# NOTE(bgh): We're currently passing both request_params['name'] and
# the entire request_params dict because their may be pieces of
# information (data extensions) inside the request params that the
# actual plugin will want to parse. We could just pass only
# request_params but that would mean all the plugins would need to
# change.
body = self._prepare_request_body(body, self._network_ops_param_list)
network = self._plugin.create_network(tenant_id,
body['network']['name'],
**body)
builder = networks_view.get_view_builder(request, self.version)
result = builder.build(network)['network']
return dict(network=result)
@common.APIFaultWrapper([exception.NetworkNotFound])
def update(self, request, tenant_id, id, body):
""" Updates the name for the network with the given id """
body = self._prepare_request_body(body, self._network_ops_param_list)
self._plugin.update_network(tenant_id, id, **body['network'])
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.NetworkInUse])
def delete(self, request, tenant_id, id):
""" Destroys the network with the given id """
self._plugin.delete_network(tenant_id, id)
class ControllerV10(Controller):
"""Network resources controller for Quantum v1.0 API"""
_serialization_metadata = {
"attributes": {
"network": ["id", "name"],
"port": ["id", "state"],
"attachment": ["id"],
},
"plurals": {
"networks": "network",
"ports": "port",
},
}
version = "1.0"
class ControllerV11(Controller):
"""Network resources controller for Quantum v1.1 API
Note: at this state this class only adds serialization
metadata for the operational status concept.
API filters, pagination, and atom links will be handled by
this class as well.
"""
_serialization_metadata = {
"attributes": {
"network": ["id", "name", "op-status"],
"port": ["id", "state", "op-status"],
"attachment": ["id"],
},
"plurals": {
"networks": "network",
"ports": "port",
},
}
version = "1.1"

View File

@ -1,185 +0,0 @@
# 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 quantum.api import api_common as common
from quantum.api.views import filters
from quantum.api.views import ports as ports_view
from quantum.common import exceptions as exception
LOG = logging.getLogger(__name__)
def create_resource(plugin, version):
controller_dict = {
'1.0': [ControllerV10(plugin),
ControllerV10._serialization_metadata,
common.XML_NS_V10],
'1.1': [ControllerV11(plugin),
ControllerV11._serialization_metadata,
common.XML_NS_V11],
}
return common.create_resource(version, controller_dict)
class Controller(common.QuantumController):
""" Port API controller for Quantum API """
_resource_name = 'port'
# version will be redefined in child class
version = None
_port_ops_param_list = [
{'param-name': 'state', 'default-value': 'DOWN', 'required': False},
]
def _items(self, request, tenant_id, network_id,
port_details=False):
""" Returns a list of ports.
Ideally, the plugin would perform filtering,
returning only the items matching filters specified
on the request query string.
However, plugins are not required to support filtering.
In this case, this function will filter the complete list
of ports returned by the plugin
"""
filter_opts = {}
filter_opts.update(request.GET)
port_list = self._plugin.get_all_ports(tenant_id,
network_id,
filter_opts=filter_opts)
builder = ports_view.get_view_builder(request, self.version)
# Load extra data for ports if required.
# This can be inefficient.
# TODO(salvatore-orlando): the fix for bug #834012 should deal with it
if port_details:
port_list_detail = [
self._plugin.get_port_details(tenant_id, network_id,
port['port-id'])
for port in port_list]
port_list = port_list_detail
# Perform manual filtering if not supported by plugin
# Inefficient, API-layer filtering
# will be performed only if the plugin does
# not support filtering
# NOTE(salvatore-orlando): the plugin is supposed to leave only filters
# it does not implement in filter_opts
port_list = filters.filter_ports(port_list, self._plugin,
tenant_id, network_id,
filter_opts)
result = [builder.build(port, port_details)['port']
for port in port_list]
return dict(ports=result)
def _item(self, request, tenant_id, network_id, port_id,
att_details=False):
""" Returns a specific port. """
port = self._plugin.get_port_details(tenant_id, network_id, port_id)
builder = ports_view.get_view_builder(request, self.version)
result = builder.build(port, port_details=True,
att_details=att_details)['port']
return dict(port=result)
@common.APIFaultWrapper([exception.NetworkNotFound])
def index(self, request, tenant_id, network_id):
""" Returns a list of port ids for a given network """
return self._items(request, tenant_id, network_id, port_details=False)
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.PortNotFound])
def show(self, request, tenant_id, network_id, id):
""" Returns port details for given port and network """
return self._item(request, tenant_id, network_id, id)
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.PortNotFound])
def detail(self, request, **kwargs):
tenant_id = kwargs.get('tenant_id')
network_id = kwargs.get('network_id')
port_id = kwargs.get('id')
if port_id:
# show details for a given network
return self._item(request, tenant_id,
network_id, port_id, att_details=True)
else:
# show details for all port
return self._items(request, tenant_id,
network_id, port_details=True)
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.StateInvalid])
def create(self, request, tenant_id, network_id, body=None):
""" Creates a new port for a given network
The request body is optional for a port object.
"""
body = self._prepare_request_body(body, self._port_ops_param_list)
port = self._plugin.create_port(tenant_id,
network_id, body['port']['state'],
**body)
builder = ports_view.get_view_builder(request, self.version)
result = builder.build(port)['port']
return dict(port=result)
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.PortNotFound,
exception.StateInvalid])
def update(self, request, tenant_id, network_id, id, body):
""" Updates the state of a port for a given network """
body = self._prepare_request_body(body, self._port_ops_param_list)
self._plugin.update_port(tenant_id, network_id, id, **body['port'])
@common.APIFaultWrapper([exception.NetworkNotFound,
exception.PortNotFound,
exception.PortInUse])
def delete(self, request, tenant_id, network_id, id):
""" Destroys the port with the given id """
self._plugin.delete_port(tenant_id, network_id, id)
class ControllerV10(Controller):
"""Port resources controller for Quantum v1.0 API"""
_serialization_metadata = {
"attributes": {
"port": ["id", "state"],
"attachment": ["id"],
},
"plurals": {
"ports": "port",
},
}
version = "1.0"
class ControllerV11(Controller):
"""Port resources controller for Quantum v1.1 API"""
_serialization_metadata = {
"attributes": {
"port": ["id", "state", "op-status"],
"attachment": ["id"],
},
"plurals": {
"ports": "port",
},
}
version = "1.1"

View File

@ -1,37 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Citrix Systems
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
def get_view_builder(req):
base_url = req.application_url
return ViewBuilder(base_url)
class ViewBuilder(object):
def __init__(self, base_url):
"""
:param base_url: url of the root wsgi application
"""
self.base_url = base_url
def build(self, attachment_data):
"""Generic method used to generate an attachment entity."""
if attachment_data['attachment']:
return dict(attachment=dict(id=attachment_data['attachment']))
else:
return dict(attachment={})

View File

@ -1,160 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 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
LOG = logging.getLogger(__name__)
def _load_network_ports_details(network, **kwargs):
plugin = kwargs.get('plugin', None)
tenant_id = kwargs.get('tenant_id', None)
#load network details only if required
if not 'net-ports' in network:
# Don't pass filter options, don't care about unused filters
port_list = plugin.get_all_ports(tenant_id, network['net-id'])
ports_data = [plugin.get_port_details(
tenant_id, network['net-id'],
port['port-id'])
for port in port_list]
network['net-ports'] = ports_data
def _filter_network_by_name(network, name, **kwargs):
return network.get('net-name', None) == name
def _filter_network_with_operational_port(network, port_op_status,
**kwargs):
_load_network_ports_details(network, **kwargs)
return any([port['port-op-status'] == port_op_status
for port in network['net-ports']])
def _filter_network_with_active_port(network, port_state, **kwargs):
_load_network_ports_details(network, **kwargs)
return any([port['port-state'] == port_state
for port in network['net-ports']])
def _filter_network_has_interface(network, has_interface, **kwargs):
_load_network_ports_details(network, **kwargs)
# convert to bool
match_has_interface = has_interface.lower() == 'true'
really_has_interface = any([port['attachment'] is not None
for port in network['net-ports']])
return match_has_interface == really_has_interface
def _filter_network_by_port(network, port_id, **kwargs):
_load_network_ports_details(network, **kwargs)
return any([port['port-id'] == port_id
for port in network['net-ports']])
def _filter_network_by_interface(network, interface_id, **kwargs):
_load_network_ports_details(network, **kwargs)
return any([port.get('attachment', None) == interface_id
for port in network['net-ports']])
def _filter_port_by_state(port, state, **kwargs):
return port.get('port-state', None) == state
def _filter_network_by_op_status(network, op_status, **kwargs):
return network.get('net-op-status', None) == op_status
def _filter_port_by_op_status(port, op_status, **kwargs):
return port.get('port-op-status', None) == op_status
def _filter_port_by_interface(port, interface_id, **kwargs):
return port.get('attachment', None) == interface_id
def _filter_port_has_interface(port, has_interface, **kwargs):
# convert to bool
match_has_interface = has_interface.lower() == 'true'
really_has_interface = ('attachment' in port and
port['attachment'] is not None)
return match_has_interface == really_has_interface
def _do_filtering(items, filters, filter_opts, plugin,
tenant_id, network_id=None):
filtered_items = []
for item in items:
is_filter_match = False
for flt in filters:
if flt in filter_opts:
is_filter_match = filters[flt](item,
filter_opts[flt],
plugin=plugin,
tenant_id=tenant_id,
network_id=network_id)
if not is_filter_match:
break
if is_filter_match:
filtered_items.append(item)
return filtered_items
def filter_networks(networks, plugin, tenant_id, filter_opts):
# Do filtering only if the plugin supports it
# and if filtering options have been specific
if len(filter_opts) == 0:
return networks
# load filter functions
filters = {
'name': _filter_network_by_name,
'op-status': _filter_network_by_op_status,
'port-op-status': _filter_network_with_operational_port,
'port-state': _filter_network_with_active_port,
'has-attachment': _filter_network_has_interface,
'attachment': _filter_network_by_interface,
'port': _filter_network_by_port}
# filter networks
return _do_filtering(networks, filters, filter_opts, plugin, tenant_id)
def filter_ports(ports, plugin, tenant_id, network_id, filter_opts):
# Do filtering only if the plugin supports it
# and if filtering options have been specific
if len(filter_opts) == 0:
return ports
# load filter functions
filters = {
'state': _filter_port_by_state,
'op-status': _filter_port_by_op_status,
'has-attachment': _filter_port_has_interface,
'attachment': _filter_port_by_interface}
# port details are need for filtering
ports = [plugin.get_port_details(tenant_id, network_id,
port['port-id']) for port in ports]
# filter ports
return _do_filtering(ports,
filters,
filter_opts,
plugin,
tenant_id,
network_id)

View File

@ -1,91 +0,0 @@
# 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.
from quantum.api.api_common import OperationalStatus
def get_view_builder(req, version):
base_url = req.application_url
view_builder = {
'1.0': ViewBuilder10,
'1.1': ViewBuilder11,
}[version](base_url)
return view_builder
class ViewBuilder10(object):
def __init__(self, base_url=None):
"""
:param base_url: url of the root wsgi application
"""
self.base_url = base_url
def build(self, network_data, net_detail=False,
ports_data=None, port_detail=False):
"""Generic method used to generate a network entity."""
if net_detail:
network = self._build_detail(network_data)
else:
network = self._build_simple(network_data)
if port_detail:
ports = [self._build_port(port_data) for port_data in ports_data]
network['network']['ports'] = ports
return network
def _build_simple(self, network_data):
"""Return a simple model of a network."""
return dict(network=dict(id=network_data['net-id']))
def _build_detail(self, network_data):
"""Return a detailed model of a network."""
return dict(network=dict(id=network_data['net-id'],
name=network_data['net-name']))
def _build_port(self, port_data):
"""Return details about a specific logical port."""
port_dict = dict(id=port_data['port-id'],
state=port_data['port-state'])
if port_data['attachment']:
port_dict['attachment'] = dict(id=port_data['attachment'])
return port_dict
class ViewBuilder11(ViewBuilder10):
def _build_simple(self, network_data):
"""Return a simple model of a network."""
return dict(network=dict(id=network_data['net-id']))
def _build_detail(self, network_data):
"""Return a detailed model of a network. """
op_status = network_data.get('net-op-status',
OperationalStatus.UNKNOWN)
return dict(network={'id': network_data['net-id'],
'name': network_data['net-name'],
'op-status': op_status})
def _build_port(self, port_data):
"""Return details about a specific logical port."""
op_status = port_data.get('port-op-status',
OperationalStatus.UNKNOWN)
port_dict = {'id': port_data['port-id'],
'state': port_data['port-state'],
'op-status': op_status}
if port_data['attachment']:
port_dict['attachment'] = dict(id=port_data['attachment'])
return port_dict

View File

@ -1,60 +0,0 @@
# 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.
from quantum.api.api_common import OperationalStatus
def get_view_builder(req, version):
base_url = req.application_url
view_builder = {
'1.0': ViewBuilder10,
'1.1': ViewBuilder11,
}[version](base_url)
return view_builder
class ViewBuilder10(object):
def __init__(self, base_url=None):
"""
:param base_url: url of the root wsgi application
"""
self.base_url = base_url
def build(self, port_data, port_details=False, att_details=False):
"""Generic method used to generate a port entity."""
port = dict(port=dict(id=port_data['port-id']))
if port_details:
port['port']['state'] = port_data['port-state']
if att_details and port_data['attachment']:
port['port']['attachment'] = dict(id=port_data['attachment'])
return port
class ViewBuilder11(ViewBuilder10):
def build(self, port_data, port_details=False, att_details=False):
"""Generates a port entity with operation status info"""
port = dict(port=dict(id=port_data['port-id']))
if port_details:
port['port']['state'] = port_data['port-state']
port['port']['op-status'] = port_data.get('port-op-status',
OperationalStatus.
UNKNOWN)
if att_details and port_data['attachment']:
port['port']['attachment'] = dict(id=port_data['attachment'])
return port

View File

@ -25,10 +25,8 @@ from sqlalchemy import create_engine
from sqlalchemy.exc import DisconnectionError
from sqlalchemy.orm import sessionmaker, exc
from quantum.api.api_common import OperationalStatus
from quantum.common import exceptions as q_exc
from quantum.db import model_base, models
from quantum.db import model_base
LOG = logging.getLogger(__name__)
@ -138,197 +136,3 @@ def unregister_models(base=BASE):
global _ENGINE
assert _ENGINE
base.metadata.drop_all(_ENGINE)
def network_create(tenant_id, name, op_status=OperationalStatus.UNKNOWN):
session = get_session()
with session.begin():
net = models.Network(tenant_id, name, op_status)
session.add(net)
session.flush()
return net
def network_all_tenant_list():
session = get_session()
return session.query(models.Network).all()
def network_list(tenant_id):
session = get_session()
return (session.query(models.Network).
filter_by(tenant_id=tenant_id).
all())
def network_get(net_id):
session = get_session()
try:
return (session.query(models.Network).
filter_by(uuid=net_id).
one())
except exc.NoResultFound:
raise q_exc.NetworkNotFound(net_id=net_id)
def network_update(net_id, tenant_id, **kwargs):
session = get_session()
net = network_get(net_id)
for key in kwargs.keys():
net[key] = kwargs[key]
session.merge(net)
session.flush()
return net
def network_destroy(net_id):
session = get_session()
try:
net = (session.query(models.Network).
filter_by(uuid=net_id).
one())
ports = (session.query(models.Port).
filter_by(network_id=net_id).
all())
for p in ports:
session.delete(p)
session.delete(net)
session.flush()
return net
except exc.NoResultFound:
raise q_exc.NetworkNotFound(net_id=net_id)
def validate_network_ownership(tenant_id, net_id):
session = get_session()
try:
return (session.query(models.Network).
filter_by(uuid=net_id).
filter_by(tenant_id=tenant_id).
one())
except exc.NoResultFound:
raise q_exc.NetworkNotFound(net_id=net_id)
def port_create(net_id, state=None, op_status=OperationalStatus.UNKNOWN):
# confirm network exists
network_get(net_id)
session = get_session()
with session.begin():
port = models.Port(net_id, op_status)
if state is None:
state = 'DOWN'
elif state not in ('ACTIVE', 'DOWN'):
raise q_exc.StateInvalid(port_state=state)
port['state'] = state
session.add(port)
session.flush()
return port
def port_list(net_id):
# confirm network exists
network_get(net_id)
session = get_session()
return (session.query(models.Port).
filter_by(network_id=net_id).
all())
def port_get(port_id, net_id, session=None):
# confirm network exists
network_get(net_id)
if not session:
session = get_session()
try:
return (session.query(models.Port).
filter_by(uuid=port_id).
filter_by(network_id=net_id).
one())
except exc.NoResultFound:
raise q_exc.PortNotFound(net_id=net_id, port_id=port_id)
def port_update(port_id, net_id, **kwargs):
# confirm network exists
network_get(net_id)
port = port_get(port_id, net_id)
session = get_session()
for key in kwargs:
if key == "state":
if kwargs[key] not in ('ACTIVE', 'DOWN'):
raise q_exc.StateInvalid(port_state=kwargs[key])
port[key] = kwargs[key]
session.merge(port)
session.flush()
return port
def port_set_attachment(port_id, net_id, new_interface_id):
# confirm network exists
network_get(net_id)
session = get_session()
port = port_get(port_id, net_id)
if new_interface_id != "":
# We are setting, not clearing, the attachment-id
if port['interface_id']:
raise q_exc.PortInUse(net_id=net_id, port_id=port_id,
att_id=port['interface_id'])
try:
port = (session.query(models.Port).
filter_by(interface_id=new_interface_id).
one())
raise q_exc.AlreadyAttached(net_id=net_id,
port_id=port_id,
att_id=new_interface_id,
att_port_id=port['uuid'])
except exc.NoResultFound:
# this is what should happen
pass
port.interface_id = new_interface_id
session.merge(port)
session.flush()
return port
def port_unset_attachment(port_id, net_id):
# confirm network exists
network_get(net_id)
session = get_session()
port = port_get(port_id, net_id, session)
port.interface_id = None
session.add(port)
session.flush()
def port_destroy(port_id, net_id):
# confirm network exists
network_get(net_id)
session = get_session()
try:
port = (session.query(models.Port).
filter_by(uuid=port_id).
filter_by(network_id=net_id).
one())
if port['interface_id']:
raise q_exc.PortInUse(net_id=net_id, port_id=port_id,
att_id=port['interface_id'])
session.delete(port)
session.flush()
return port
except exc.NoResultFound:
raise q_exc.PortNotFound(port_id=port_id)
def validate_port_ownership(tenant_id, net_id, port_id, session=None):
validate_network_ownership(tenant_id, net_id)
port_get(port_id, net_id)

View File

@ -1,77 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
# @author: Salvatore Orlando, Citrix Systems
import uuid
from sqlalchemy import Column, String, ForeignKey
from sqlalchemy.orm import relation
from quantum.api import api_common as common
from quantum.db import model_base
BASE = model_base.BASE
class Port(model_base.BASE):
"""Represents a port on a quantum network"""
__tablename__ = 'ports'
uuid = Column(String(255), primary_key=True)
network_id = Column(String(255), ForeignKey("networks.uuid"),
nullable=False)
interface_id = Column(String(255), nullable=True)
# Port state - Hardcoding string value at the moment
state = Column(String(8))
op_status = Column(String(16))
def __init__(self, network_id, op_status=common.OperationalStatus.UNKNOWN):
self.uuid = str(uuid.uuid4())
self.network_id = network_id
self.interface_id = None
self.state = "DOWN"
self.op_status = op_status
def __repr__(self):
return "<Port(%s,%s,%s,%s,%s)>" % (self.uuid, self.network_id,
self.state, self.op_status,
self.interface_id)
class Network(model_base.BASE):
"""Represents a quantum network"""
__tablename__ = 'networks'
uuid = Column(String(255), primary_key=True)
tenant_id = Column(String(255), nullable=False)
name = Column(String(255))
ports = relation(Port, order_by=Port.uuid, backref="network")
op_status = Column(String(16))
def __init__(self, tenant_id, name,
op_status=common.OperationalStatus.UNKNOWN):
self.uuid = str(uuid.uuid4())
self.tenant_id = tenant_id
self.name = name
self.op_status = op_status
def __repr__(self):
return "<Network(%s,%s,%s,%s)>" % (self.uuid, self.name,
self.op_status, self.tenant_id)

View File

@ -1,49 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2011 Nicira Networks, 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: Brad Hall, Nicira Networks, Inc
#
def get_view_builder(req):
"""get view builder"""
base_url = req.application_url
return ViewBuilder(base_url)
class ViewBuilder(object):
"""
ViewBuilder for Port statistics.
Port stats coming back from the plugin will look like this:
{
"rx_packets": 0,
"rx_bytes": 0,
"tx_errors": 0,
"rx_errors": 0,
"tx_bytes": 0,
"tx_packets": 0
}
"""
def __init__(self, base_url):
self.base_url = base_url
def build(self, portstat_data, is_detail=True):
# We just ignore is_detail -- it doesn't make sense in this context.
return self._build(portstat_data)
def _build(self, portstat_data):
return portstat_data

View File

@ -1,96 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2011 Nicira Networks, 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: Brad Hall, Nicira Networks, Inc
import logging
from quantum.api import faults
from quantum.common import exceptions as qexception
from quantum.common import extensions
from quantum.extensions import _portstats_view as portstats_view
from quantum.manager import QuantumManager
from quantum import wsgi
LOG = logging.getLogger("quantum.api.portstats")
class Portstats(object):
def __init__(self):
pass
@classmethod
def get_name(cls):
return "Port Statistics"
@classmethod
def get_alias(cls):
return "portstats"
@classmethod
def get_description(cls):
return "Port Statistics"
@classmethod
def get_namespace(cls):
return "http://docs.openstack.org/ext/portstats/api/v1.0"
@classmethod
def get_updated(cls):
return "2011-12-20T10:00:00-00:00"
@classmethod
def get_resources(cls):
""" Returns all defined resources """
controller = StatsController(QuantumManager.get_plugin())
parent_resource = dict(member_name="port",
collection_name="extensions/ovs/tenants/"
":(tenant_id)/ networks/:(network_id)/ports")
return [extensions.ResourceExtension('stats', controller,
parent=parent_resource)]
class StatsController(wsgi.Controller):
_serialization_metadata = {
"application/xml": {
"attributes": {
"stats": ["rx_bytes", "rx_packets", "rx_errors",
"tx_bytes", "tx_packets", "tx_errors"]
}
}
}
def __init__(self, plugin):
self._resource_name = 'stats'
self._plugin = plugin
def _show(self, request, tenant_id, network_id, port_id):
"""Returns port statistics for a given port"""
if not hasattr(self._plugin, "get_port_stats"):
return faults.QuantumHTTPError(
qexception.NotImplementedError("get_port_stats"))
stats = self._plugin.get_port_stats(tenant_id, network_id, port_id)
builder = portstats_view.get_view_builder(request)
result = builder.build(stats, True)
return dict(stats=result)
def index(self, request, tenant_id, network_id, port_id):
return self._show(request, tenant_id, network_id, port_id)
def show(self, request, tenant_id, network_id, port_id, id):
return self._show(request, tenant_id, network_id, port_id)

View File

@ -1,258 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012, Cisco Systems, Inc.
#
# 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: Sumit Naiksatam, Cisco Systems, Inc.
import logging
from quantum.api.api_common import OperationalStatus
from quantum.common import exceptions as exc
from quantum.db import api as db
from quantum.plugins.linuxbridge.common import constants as const
from quantum.plugins.linuxbridge.common import utils as cutil
from quantum.plugins.linuxbridge.db import l2network_db as cdb
from quantum.quantum_plugin_base import QuantumPluginBase
LOG = logging.getLogger(__name__)
class LinuxBridgePlugin(QuantumPluginBase):
"""
LinuxBridgePlugin provides support for Quantum abstractions
using LinuxBridge. A new VLAN is created for each network.
It relies on an agent to perform the actual bridge configuration
on each host.
"""
def __init__(self, configfile=None):
cdb.initialize()
LOG.debug("Linux Bridge Plugin initialization done successfully")
def _get_vlan_for_tenant(self, tenant_id, **kwargs):
"""Get an available VLAN ID"""
try:
return cdb.reserve_vlanid()
except:
raise Exception("Failed to reserve VLAN ID for network")
def _release_vlan_for_tenant(self, tenant_id, net_id, **kwargs):
"""Release the ID"""
vlan_binding = cdb.get_vlan_binding(net_id)
return cdb.release_vlanid(vlan_binding[const.VLANID])
def _validate_port_state(self, port_state):
if port_state.upper() not in ('ACTIVE', 'DOWN'):
raise exc.StateInvalid(port_state=port_state)
return True
def get_all_networks(self, tenant_id, **kwargs):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
"""
LOG.debug("LinuxBridgePlugin.get_all_networks() called")
networks_list = db.network_list(tenant_id)
new_networks_list = []
for network in networks_list:
new_network_dict = cutil.make_net_dict(network[const.UUID],
network[const.NETWORKNAME],
[], network[const.OPSTATUS])
new_networks_list.append(new_network_dict)
# This plugin does not perform filtering at the moment
return new_networks_list
def get_network_details(self, tenant_id, net_id):
"""
retrieved a list of all the remote vifs that
are attached to the network
"""
LOG.debug("LinuxBridgePlugin.get_network_details() called")
db.validate_network_ownership(tenant_id, net_id)
network = db.network_get(net_id)
ports_list = db.port_list(net_id)
ports_on_net = []
for port in ports_list:
new_port = cutil.make_port_dict(port)
ports_on_net.append(new_port)
new_network = cutil.make_net_dict(network[const.UUID],
network[const.NETWORKNAME],
ports_on_net,
network[const.OPSTATUS])
return new_network
def create_network(self, tenant_id, net_name, **kwargs):
"""
Creates a new Virtual Network, and assigns it
a symbolic name.
"""
LOG.debug("LinuxBridgePlugin.create_network() called")
new_network = db.network_create(tenant_id, net_name,
op_status=OperationalStatus.UP)
new_net_id = new_network[const.UUID]
vlan_id = self._get_vlan_for_tenant(tenant_id)
cdb.add_vlan_binding(vlan_id, new_net_id)
new_net_dict = {
const.NET_ID: new_net_id,
const.NET_NAME: net_name,
const.NET_PORTS: [],
const.NET_OP_STATUS: new_network[const.OPSTATUS],
}
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.
"""
LOG.debug("LinuxBridgePlugin.delete_network() called")
db.validate_network_ownership(tenant_id, net_id)
net = db.network_get(net_id)
if net:
ports_on_net = db.port_list(net_id)
if len(ports_on_net) > 0:
for port in ports_on_net:
if port[const.INTERFACEID]:
raise exc.NetworkInUse(net_id=net_id)
for port in ports_on_net:
self.delete_port(tenant_id, net_id, port[const.UUID])
net_dict = cutil.make_net_dict(net[const.UUID],
net[const.NETWORKNAME],
[], net[const.OPSTATUS])
try:
self._release_vlan_for_tenant(tenant_id, net_id)
cdb.remove_vlan_binding(net_id)
except Exception as excp:
LOG.warning("Exception: %s" % excp)
db.network_update(net_id, tenant_id, {const.OPSTATUS:
OperationalStatus.DOWN})
db.network_destroy(net_id)
return net_dict
# Network not found
raise exc.NetworkNotFound(net_id=net_id)
def update_network(self, tenant_id, net_id, **kwargs):
"""
Updates the attributes of a particular Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.update_network() called")
db.validate_network_ownership(tenant_id, net_id)
network = db.network_update(net_id, tenant_id, **kwargs)
net_dict = cutil.make_net_dict(network[const.UUID],
network[const.NETWORKNAME],
[], network[const.OPSTATUS])
return net_dict
def get_all_ports(self, tenant_id, net_id, **kwargs):
"""
Retrieves all port identifiers belonging to the
specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.get_all_ports() called")
db.validate_network_ownership(tenant_id, net_id)
ports_list = db.port_list(net_id)
ports_on_net = []
for port in ports_list:
new_port = cutil.make_port_dict(port)
ports_on_net.append(new_port)
# This plugin does not perform filtering at the moment
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.
"""
LOG.debug("LinuxBridgePlugin.get_port_details() called")
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
"""
Creates a port on the specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.create_port() called")
db.validate_network_ownership(tenant_id, net_id)
port = db.port_create(net_id, port_state,
op_status=OperationalStatus.DOWN)
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
def update_port(self, tenant_id, net_id, port_id, **kwargs):
"""
Updates the attributes of a port on the specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.update_port() called")
db.validate_port_ownership(tenant_id, net_id, port_id)
self._validate_port_state(kwargs["state"])
port = db.port_update(port_id, net_id, **kwargs)
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
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.
"""
LOG.debug("LinuxBridgePlugin.delete_port() called")
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
attachment_id = port[const.INTERFACEID]
if not attachment_id:
db.port_destroy(port_id, net_id)
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
else:
raise exc.PortInUse(port_id=port_id, net_id=net_id,
att_id=attachment_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.
"""
LOG.debug("LinuxBridgePlugin.plug_interface() called")
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
attachment_id = port[const.INTERFACEID]
if attachment_id:
raise exc.PortInUse(port_id=port_id, net_id=net_id,
att_id=attachment_id)
db.port_set_attachment(port_id, net_id, 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.
"""
LOG.debug("LinuxBridgePlugin.unplug_interface() called")
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
attachment_id = port[const.INTERFACEID]
if attachment_id is None:
return
db.port_unset_attachment(port_id, net_id)
db.port_update(port_id, net_id, op_status=OperationalStatus.DOWN)

View File

@ -1,53 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Cisco 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: Sumit Naiksatam, Cisco Systems, Inc.
import logging
from quantum.api.api_common import OperationalStatus
from quantum.plugins.linuxbridge.common import constants as const
LOG = logging.getLogger(__name__)
def make_net_dict(net_id, net_name, ports, op_status):
"""Helper funciton"""
res = {
const.NET_ID: net_id,
const.NET_NAME: net_name,
const.NET_OP_STATUS: op_status,
}
if ports:
res[const.NET_PORTS] = ports
return res
def make_port_dict(port):
"""Helper funciton"""
if port[const.PORTSTATE] == const.PORT_UP:
op_status = port[const.OPSTATUS]
else:
op_status = OperationalStatus.DOWN
return {
const.PORT_ID: str(port[const.UUID]),
const.PORT_STATE: port[const.PORTSTATE],
const.PORT_OP_STATUS: op_status,
const.NET_ID: port[const.NETWORKID],
const.ATTACHMENT: port[const.INTERFACEID],
}

View File

@ -27,13 +27,12 @@ from quantum.db import models_v2
from quantum.openstack.common import cfg
from quantum.plugins.linuxbridge.common import config
from quantum.plugins.linuxbridge.common import exceptions as c_exc
from quantum.plugins.linuxbridge.db import l2network_models
from quantum.plugins.linuxbridge.db import l2network_models_v2
LOG = logging.getLogger(__name__)
# The global variable for the database version model
L2_MODEL = l2network_models
L2_MODEL = l2network_models_v2
def initialize(base=None):
@ -44,7 +43,6 @@ def initialize(base=None):
cfg.CONF.DATABASE.reconnect_interval})
if base:
options.update({"base": base})
L2_MODEL = l2network_models_v2
db.configure_db(options)
create_vlanids()
@ -182,7 +180,7 @@ def reserve_specific_vlanid(vlan_id):
raise q_exc.InvalidInput(error_message=msg)
session = db.get_session()
try:
rvlanid = (session.query(l2network_models.VlanID).
rvlanid = (session.query(l2network_models_v2.VlanID).
filter_by(vlan_id=vlan_id).
one())
if rvlanid["vlan_used"]:
@ -191,7 +189,7 @@ def reserve_specific_vlanid(vlan_id):
rvlanid["vlan_used"] = True
session.merge(rvlanid)
except exc.NoResultFound:
rvlanid = l2network_models.VlanID(vlan_id)
rvlanid = l2network_models_v2.VlanID(vlan_id)
LOG.debug("reserving non-dynamic vlanid %s" % vlan_id)
rvlanid["vlan_used"] = True
session.add(rvlanid)

View File

@ -1,50 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012, Cisco Systems, Inc.
#
# 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: Rohit Agarwalla, Cisco Systems, Inc.
from sqlalchemy import Column, Integer, String, Boolean
from quantum.db.models import BASE
class VlanID(BASE):
"""Represents a vlan_id usage"""
__tablename__ = 'vlan_ids'
vlan_id = Column(Integer, primary_key=True)
vlan_used = Column(Boolean)
def __init__(self, vlan_id):
self.vlan_id = vlan_id
self.vlan_used = False
def __repr__(self):
return "<VlanID(%d,%s)>" % (self.vlan_id, self.vlan_used)
class VlanBinding(BASE):
"""Represents a binding of vlan_id to network_id"""
__tablename__ = 'vlan_bindings'
vlan_id = Column(Integer, primary_key=True)
network_id = Column(String(255), nullable=False)
def __init__(self, vlan_id, network_id):
self.vlan_id = vlan_id
self.network_id = network_id
def __repr__(self):
return "<VlanBinding(%d,%s)>" % (self.vlan_id, self.network_id)

View File

@ -35,7 +35,6 @@ sys.path.append(os.getcwd())
sys.path.append(os.path.dirname(__file__))
from quantum.api.api_common import OperationalStatus
from quantum.common.test_lib import run_tests, test_config
import quantum.tests.unit
@ -47,10 +46,7 @@ if __name__ == '__main__':
# we should only invoked the tests once
invoke_once = len(sys.argv) > 1
test_config['plugin_name'] = "LinuxBridgePlugin.LinuxBridgePlugin"
test_config['plugin_name_v2'] = "lb_quantum_plugin.LinuxBridgePluginV2"
test_config['default_net_op_status'] = OperationalStatus.UP
test_config['default_port_op_status'] = OperationalStatus.DOWN
cwd = os.getcwd()
c = config.Config(stream=sys.stdout,

View File

@ -1,289 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012, Cisco Systems, Inc.
#
# 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: Rohit Agarwalla, Cisco Systems, Inc.
"""
test_database.py is an independent test suite
that tests the database api method calls
"""
import logging
import unittest2 as unittest
import quantum.db.api as db
from quantum.openstack.common import cfg
import quantum.plugins.linuxbridge.common.exceptions as c_exc
import quantum.plugins.linuxbridge.db.l2network_db as l2network_db
LOG = logging.getLogger(__name__)
class L2networkDB(object):
"""Class conisting of methods to call L2network db methods"""
def get_all_vlan_bindings(self):
"""Get all vlan binding into a list of dict"""
vlans = []
try:
for vlan_bind in l2network_db.get_all_vlan_bindings():
LOG.debug("Getting vlan bindings for vlan: %s" %
vlan_bind.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(vlan_bind.vlan_id)
vlan_dict["net-id"] = str(vlan_bind.network_id)
vlans.append(vlan_dict)
except Exception, exc:
LOG.error("Failed to get all vlan bindings: %s" % str(exc))
return vlans
def get_vlan_binding(self, network_id):
"""Get a vlan binding"""
vlan = []
try:
for vlan_bind in l2network_db.get_vlan_binding(network_id):
LOG.debug("Getting vlan binding for vlan: %s" %
vlan_bind.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(vlan_bind.vlan_id)
vlan_dict["net-id"] = str(vlan_bind.network_id)
vlan.append(vlan_dict)
except Exception, exc:
LOG.error("Failed to get vlan binding: %s" % str(exc))
return vlan
def create_vlan_binding(self, vlan_id, network_id):
"""Create a vlan binding"""
vlan_dict = {}
try:
res = l2network_db.add_vlan_binding(vlan_id, network_id)
LOG.debug("Created vlan binding for vlan: %s" % res.vlan_id)
vlan_dict["vlan-id"] = str(res.vlan_id)
vlan_dict["net-id"] = str(res.network_id)
return vlan_dict
except Exception, exc:
LOG.error("Failed to create vlan binding: %s" % str(exc))
def delete_vlan_binding(self, network_id):
"""Delete a vlan binding"""
try:
res = l2network_db.remove_vlan_binding(network_id)
LOG.debug("Deleted vlan binding for vlan: %s" % res.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(res.vlan_id)
return vlan_dict
except Exception, exc:
raise Exception("Failed to delete vlan binding: %s" % str(exc))
def update_vlan_binding(self, network_id, vlan_id):
"""Update a vlan binding"""
try:
res = l2network_db.update_vlan_binding(network_id, vlan_id)
LOG.debug("Updating vlan binding for vlan: %s" % res.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(res.vlan_id)
vlan_dict["net-id"] = str(res.network_id)
return vlan_dict
except Exception, exc:
raise Exception("Failed to update vlan binding: %s" % str(exc))
class QuantumDB(object):
"""Class conisting of methods to call Quantum db methods"""
def get_all_networks(self, tenant_id):
"""Get all networks"""
nets = []
try:
for net in db.network_list(tenant_id):
LOG.debug("Getting network: %s" % net.uuid)
net_dict = {}
net_dict["tenant-id"] = net.tenant_id
net_dict["net-id"] = str(net.uuid)
net_dict["net-name"] = net.name
nets.append(net_dict)
except Exception, exc:
LOG.error("Failed to get all networks: %s" % str(exc))
return nets
def create_network(self, tenant_id, net_name):
"""Create a network"""
net_dict = {}
try:
res = db.network_create(tenant_id,
net_name,
op_status="UP")
LOG.debug("Created network: %s" % res.uuid)
net_dict["tenant-id"] = res.tenant_id
net_dict["net-id"] = str(res.uuid)
net_dict["net-name"] = res.name
return net_dict
except Exception, exc:
LOG.error("Failed to create network: %s" % str(exc))
def delete_network(self, net_id):
"""Delete a network"""
try:
net = db.network_destroy(net_id)
LOG.debug("Deleted network: %s" % net.uuid)
net_dict = {}
net_dict["net-id"] = str(net.uuid)
return net_dict
except Exception, exc:
raise Exception("Failed to delete port: %s" % str(exc))
class L2networkDBTest(unittest.TestCase):
"""Class conisting of L2network DB unit tests"""
def setUp(self):
"""Setup for tests"""
l2network_db.initialize()
l2network_db.create_vlanids()
self.dbtest = L2networkDB()
self.quantum = QuantumDB()
LOG.debug("Setup")
def tearDown(self):
"""Tear Down"""
db.clear_db()
def test_create_vlanbinding(self):
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
self.teardown_vlanbinding()
self.teardown_network()
def test_getall_vlanbindings(self):
net1 = self.quantum.create_network("t1", "netid1")
net2 = self.quantum.create_network("t1", "netid2")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
vlan2 = self.dbtest.create_vlan_binding(20, net2["net-id"])
self.assertTrue(vlan2["vlan-id"] == "20")
vlans = self.dbtest.get_all_vlan_bindings()
self.assertTrue(len(vlans) == 2)
self.teardown_vlanbinding()
self.teardown_network()
def test_delete_vlanbinding(self):
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
self.dbtest.delete_vlan_binding(net1["net-id"])
vlans = self.dbtest.get_all_vlan_bindings()
count = 0
for vlan in vlans:
if vlan["vlan-id"] is "10":
count += 1
self.assertTrue(count == 0)
self.teardown_vlanbinding()
self.teardown_network()
def test_update_vlanbinding(self):
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
vlan1 = self.dbtest.update_vlan_binding(net1["net-id"], 11)
self.assertTrue(vlan1["vlan-id"] == "11")
self.teardown_vlanbinding()
self.teardown_network()
def test_vlanids(self):
l2network_db.create_vlanids()
vlanids = l2network_db.get_all_vlanids()
self.assertGreater(len(vlanids), 0)
vlanid = l2network_db.reserve_vlanid()
used = l2network_db.is_vlanid_used(vlanid)
self.assertTrue(used)
used = l2network_db.release_vlanid(vlanid)
self.assertFalse(used)
self.teardown_vlanbinding()
self.teardown_network()
def test_specific_vlanid_outside(self):
l2network_db.create_vlanids()
orig_count = len(l2network_db.get_all_vlanids())
self.assertGreater(orig_count, 0)
vlan_id = 7 # outside range dynamically allocated
with self.assertRaises(c_exc.VlanIDNotFound):
l2network_db.is_vlanid_used(vlan_id)
l2network_db.reserve_specific_vlanid(vlan_id)
self.assertTrue(l2network_db.is_vlanid_used(vlan_id))
count = len(l2network_db.get_all_vlanids())
self.assertEqual(count, orig_count + 1)
used = l2network_db.release_vlanid(vlan_id)
self.assertFalse(used)
with self.assertRaises(c_exc.VlanIDNotFound):
l2network_db.is_vlanid_used(vlan_id)
count = len(l2network_db.get_all_vlanids())
self.assertEqual(count, orig_count)
self.teardown_vlanbinding()
self.teardown_network()
def test_specific_vlanid_inside(self):
l2network_db.create_vlanids()
orig_count = len(l2network_db.get_all_vlanids())
self.assertGreater(orig_count, 0)
vlan_id = 1007 # inside range dynamically allocated
self.assertFalse(l2network_db.is_vlanid_used(vlan_id))
l2network_db.reserve_specific_vlanid(vlan_id)
self.assertTrue(l2network_db.is_vlanid_used(vlan_id))
count = len(l2network_db.get_all_vlanids())
self.assertEqual(count, orig_count)
used = l2network_db.release_vlanid(vlan_id)
self.assertFalse(used)
self.assertFalse(l2network_db.is_vlanid_used(vlan_id))
count = len(l2network_db.get_all_vlanids())
self.assertEqual(count, orig_count)
self.teardown_vlanbinding()
self.teardown_network()
def teardown_network(self):
"""tearDown Network table"""
LOG.debug("Tearing Down Network")
nets = self.quantum.get_all_networks("t1")
for net in nets:
netid = net["net-id"]
self.quantum.delete_network(netid)
def teardown_vlanbinding(self):
"""tearDown VlanBinding table"""
LOG.debug("Tearing Down Vlan Binding")
vlans = self.dbtest.get_all_vlan_bindings()
for vlan in vlans:
netid = vlan["net-id"]
self.dbtest.delete_vlan_binding(netid)
class ConfigurationTest(unittest.TestCase):
def test_defaults(self):
self.assertEqual('sqlite://',
cfg.CONF.DATABASE.sql_connection)
self.assertEqual(-1,
cfg.CONF.DATABASE.sql_max_retries)
self.assertEqual(2,
cfg.CONF.DATABASE.reconnect_interval)
self.assertEqual(2,
cfg.CONF.AGENT.polling_interval)
self.assertEqual('sudo',
cfg.CONF.AGENT.root_helper)
self.assertEqual(1000,
cfg.CONF.VLANS.vlan_start)
self.assertEqual(3000,
cfg.CONF.VLANS.vlan_end)
self.assertEqual('eth1',
cfg.CONF.LINUX_BRIDGE.physical_interface)

View File

@ -1,56 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
from sqlalchemy.orm import exc
import quantum.db.api as db
from quantum.plugins.openvswitch import ovs_models
def get_vlans():
session = db.get_session()
try:
bindings = (session.query(ovs_models.VlanBinding).
all())
except exc.NoResultFound:
return []
res = []
for x in bindings:
res.append((x.vlan_id, x.network_id))
return res
def add_vlan_binding(vlanid, netid):
session = db.get_session()
binding = ovs_models.VlanBinding(vlanid, netid)
session.add(binding)
session.flush()
return binding.vlan_id
def remove_vlan_binding(netid):
session = db.get_session()
try:
binding = (session.query(ovs_models.VlanBinding).
filter_by(network_id=netid).
one())
session.delete(binding)
except exc.NoResultFound:
pass
session.flush()

View File

@ -1,50 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Dan Wendlandt, Nicira Networks, Inc.
from sqlalchemy import Column, Integer, String
from quantum.db.models import BASE
class VlanBinding(BASE):
"""Represents a binding of network_id, vlan_id"""
__tablename__ = 'vlan_bindings'
vlan_id = Column(Integer, primary_key=True)
network_id = Column(String(255))
def __init__(self, vlan_id, network_id):
self.network_id = network_id
self.vlan_id = vlan_id
def __repr__(self):
return "<VlanBinding(%s,%s)>" % (self.vlan_id, self.network_id)
class TunnelIP(BASE):
"""Represents a remote IP in tunnel mode"""
__tablename__ = 'tunnel_ips'
ip_address = Column(String(255), primary_key=True)
def __init__(self, ip_address):
self.ip_address = ip_address
def __repr__(self):
return "<TunnelIP(%s)>" % (self.ip_address)

View File

@ -23,7 +23,6 @@
import logging
import os
from quantum.api.api_common import OperationalStatus
from quantum.api.v2 import attributes
from quantum.common import exceptions as q_exc
from quantum.common.utils import find_config_file
@ -32,223 +31,13 @@ from quantum.db import db_base_plugin_v2
from quantum.db import models_v2
from quantum.openstack.common import cfg
from quantum.plugins.openvswitch.common import config
from quantum.plugins.openvswitch import ovs_db
from quantum.plugins.openvswitch import ovs_db_v2
from quantum.quantum_plugin_base import QuantumPluginBase
from quantum import policy
LOG = logging.getLogger("ovs_quantum_plugin")
# Exception thrown if no more VLANs are available
class NoFreeVLANException(Exception):
# TODO(rkukura) Remove this class when removing V1 API
pass
class VlanMap(object):
# TODO(rkukura) Remove this class when removing V1 API
vlans = {}
net_ids = {}
free_vlans = set()
def __init__(self, vlan_min=1, vlan_max=4094):
if vlan_min > vlan_max:
LOG.warn("Using default VLAN values! vlan_min = %s is larger"
" than vlan_max = %s!" % (vlan_min, vlan_max))
vlan_min = 1
vlan_max = 4094
self.vlan_min = vlan_min
self.vlan_max = vlan_max
self.vlans.clear()
self.net_ids.clear()
self.free_vlans = set(xrange(self.vlan_min, self.vlan_max + 1))
def already_used(self, vlan_id, network_id):
self.free_vlans.remove(vlan_id)
self.set_vlan(vlan_id, network_id)
def set_vlan(self, vlan_id, network_id):
self.vlans[vlan_id] = network_id
self.net_ids[network_id] = vlan_id
def acquire(self, network_id):
if len(self.free_vlans):
vlan = self.free_vlans.pop()
self.set_vlan(vlan, network_id)
LOG.debug("Allocated VLAN %s for network %s" % (vlan, network_id))
return vlan
else:
raise NoFreeVLANException("No VLAN free for network %s" %
network_id)
def acquire_specific(self, vlan_id, network_id):
LOG.debug("Allocating specific VLAN %s for network %s"
% (vlan_id, network_id))
if vlan_id < 1 or vlan_id > 4094:
msg = _("Specified VLAN %s outside legal range (1-4094)") % vlan_id
raise q_exc.InvalidInput(error_message=msg)
if self.vlans.get(vlan_id):
raise q_exc.VlanIdInUse(vlan_id=vlan_id)
self.free_vlans.discard(vlan_id)
self.set_vlan(vlan_id, network_id)
def release(self, network_id):
vlan = self.net_ids.get(network_id, None)
if vlan is not None:
if vlan >= self.vlan_min and vlan <= self.vlan_max:
self.free_vlans.add(vlan)
del self.vlans[vlan]
del self.net_ids[network_id]
LOG.debug("Deallocated VLAN %s (used by network %s)" %
(vlan, network_id))
else:
LOG.error("No vlan found with network \"%s\"", network_id)
def populate_already_used(self, vlans):
for vlan_id, network_id in vlans:
LOG.debug("Adding already populated vlan %s -> %s" %
(vlan_id, network_id))
self.already_used(vlan_id, network_id)
class OVSQuantumPlugin(QuantumPluginBase):
# TODO(rkukura) Remove this class when removing V1 API
def __init__(self, configfile=None):
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
sql_max_retries = cfg.CONF.DATABASE.sql_max_retries
options.update({"sql_max_retries": sql_max_retries})
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
options.update({"reconnect_interval": reconnect_interval})
db.configure_db(options)
self.vmap = VlanMap(cfg.CONF.OVS.vlan_min, cfg.CONF.OVS.vlan_max)
# Populate the map with anything that is already present in the
# database
self.vmap.populate_already_used(ovs_db.get_vlans())
def get_all_networks(self, tenant_id, **kwargs):
nets = []
for x in db.network_list(tenant_id):
LOG.debug("Adding network: %s" % x.uuid)
nets.append(self._make_net_dict(str(x.uuid), x.name,
None, x.op_status))
return nets
def _make_net_dict(self, net_id, net_name, ports, op_status):
res = {
'net-id': net_id,
'net-name': net_name,
'net-op-status': op_status,
}
if ports:
res['net-ports'] = ports
return res
def create_network(self, tenant_id, net_name, **kwargs):
net = db.network_create(tenant_id, net_name,
op_status=OperationalStatus.UP)
try:
vlan_id = self.vmap.acquire(str(net.uuid))
except NoFreeVLANException:
db.network_destroy(net.uuid)
raise
LOG.debug("Created network: %s" % net)
ovs_db.add_vlan_binding(vlan_id, str(net.uuid))
return self._make_net_dict(str(net.uuid), net.name, [], net.op_status)
def delete_network(self, tenant_id, net_id):
db.validate_network_ownership(tenant_id, net_id)
net = db.network_get(net_id)
# Verify that no attachments are plugged into the network
for port in db.port_list(net_id):
if port.interface_id:
raise q_exc.NetworkInUse(net_id=net_id)
net = db.network_destroy(net_id)
ovs_db.remove_vlan_binding(net_id)
self.vmap.release(net_id)
return self._make_net_dict(str(net.uuid), net.name, [], net.op_status)
def get_network_details(self, tenant_id, net_id):
db.validate_network_ownership(tenant_id, net_id)
net = db.network_get(net_id)
ports = self.get_all_ports(tenant_id, net_id)
return self._make_net_dict(str(net.uuid), net.name,
ports, net.op_status)
def update_network(self, tenant_id, net_id, **kwargs):
db.validate_network_ownership(tenant_id, net_id)
net = db.network_update(net_id, tenant_id, **kwargs)
return self._make_net_dict(str(net.uuid), net.name,
None, net.op_status)
def _make_port_dict(self, port):
if port.state == "ACTIVE":
op_status = port.op_status
else:
op_status = OperationalStatus.DOWN
return {
'port-id': str(port.uuid),
'port-state': port.state,
'port-op-status': op_status,
'net-id': port.network_id,
'attachment': port.interface_id,
}
def get_all_ports(self, tenant_id, net_id, **kwargs):
ids = []
db.validate_network_ownership(tenant_id, net_id)
ports = db.port_list(net_id)
# This plugin does not perform filtering at the moment
return [{'port-id': str(p.uuid)} for p in ports]
def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
LOG.debug("Creating port with network_id: %s" % net_id)
db.validate_network_ownership(tenant_id, net_id)
port = db.port_create(net_id, port_state,
op_status=OperationalStatus.DOWN)
return self._make_port_dict(port)
def delete_port(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_destroy(port_id, net_id)
return self._make_port_dict(port)
def update_port(self, tenant_id, net_id, port_id, **kwargs):
"""
Updates the state of a port on the specified Virtual Network.
"""
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
db.port_update(port_id, net_id, **kwargs)
return self._make_port_dict(port)
def get_port_details(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
return self._make_port_dict(port)
def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
db.port_set_attachment(port_id, net_id, remote_iface_id)
def unplug_interface(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
db.port_set_attachment(port_id, net_id, "")
db.port_update(port_id, net_id, op_status=OperationalStatus.DOWN)
def get_interface_details(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
res = db.port_get(port_id, net_id)
return res.interface_id
class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
"""Implement the Quantum abstractions using Open vSwitch.

View File

@ -34,7 +34,6 @@ from nose import core
sys.path.append(os.getcwd())
sys.path.append(os.path.dirname(__file__))
from quantum.api.api_common import OperationalStatus
from quantum.common.test_lib import run_tests, test_config
import quantum.tests.unit
@ -46,10 +45,7 @@ if __name__ == '__main__':
# we should only invoked the tests once
invoke_once = len(sys.argv) > 1
test_config['plugin_name'] = "ovs_quantum_plugin.OVSQuantumPlugin"
test_config['plugin_name_v2'] = "ovs_quantum_plugin.OVSQuantumPluginV2"
test_config['default_net_op_status'] = OperationalStatus.UP
test_config['default_port_op_status'] = OperationalStatus.DOWN
cwd = os.getcwd()
c = config.Config(stream=sys.stdout,

View File

@ -1,67 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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.
import unittest
from quantum.plugins.openvswitch.ovs_quantum_plugin import (
NoFreeVLANException,
VlanMap,
)
class VlanMapTest(unittest.TestCase):
# TODO(rkukura) Remove this class when removing V1 API
def setUp(self):
self.vmap = VlanMap()
def tearDown(self):
pass
def testAddVlan(self):
vlan_id = self.vmap.acquire("foobar")
self.assertTrue(vlan_id >= self.vmap.vlan_min)
self.assertTrue(vlan_id <= self.vmap.vlan_max)
def testReleaseVlan(self):
vlan_id = self.vmap.acquire("foobar")
self.vmap.release("foobar")
def testAddRelease4kVlans(self):
vlan_id = None
num_vlans = self.vmap.vlan_max - self.vmap.vlan_min
for id in xrange(num_vlans):
vlan_id = self.vmap.acquire("net-%s" % id)
self.assertTrue(vlan_id >= self.vmap.vlan_min)
self.assertTrue(vlan_id <= self.vmap.vlan_max)
for id in xrange(num_vlans):
self.vmap.release("net-%s" % id)
def testAlreadyUsed(self):
existing_vlan = 2
self.vmap.already_used(existing_vlan, "net1")
try:
# this value is high enough that we will exhaust
# all VLANs. We want to make sure 'existing_vlan'
# is never reallocated.
num_vlans = self.vmap.vlan_max - self.vmap.vlan_min + 1
for x in xrange(num_vlans):
vlan_id = self.vmap.acquire("net-%x" % x)
self.assertTrue(vlan_id != existing_vlan)
self.fail("Did not run out of VLANs as expected")
except NoFreeVLANException:
pass # Expected exit

View File

@ -130,13 +130,12 @@ def check_ofp_mode(db):
class OVSQuantumOFPRyuAgent:
def __init__(self, integ_br, db, root_helper, target_v2_api=False):
def __init__(self, integ_br, db, root_helper):
self.root_helper = root_helper
(ofp_controller_addr, ofp_rest_api_addr) = check_ofp_mode(db)
self.nw_id_external = rest_nw_id.NW_ID_EXTERNAL
self.api = OFPClient(ofp_rest_api_addr)
self.target_v2_api = target_v2_api
self._setup_integration_br(integ_br, ofp_controller_addr)
def _setup_integration_br(self, integ_br, ofp_controller_addr):
@ -151,16 +150,10 @@ class OVSQuantumOFPRyuAgent:
def _all_bindings(self, db):
"""return interface id -> port which include network id bindings"""
if self.target_v2_api:
return dict((port.device_id, port) for port in db.ports.all())
else:
return dict((port.interface_id, port) for port in db.ports.all())
return dict((port.device_id, port) for port in db.ports.all())
def _set_port_status(self, port, status):
if self.target_v2_api:
port.status = status
else:
port.op_status = status
port.status = status
def daemon_loop(self, db):
# on startup, register all existing ports
@ -232,13 +225,12 @@ def main():
integ_br = cfg.CONF.OVS.integration_bridge
root_helper = cfg.CONF.AGENT.root_helper
target_v2_api = cfg.CONF.AGENT.target_v2_api
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
db = SqlSoup(options["sql_connection"])
LOG.info("Connecting to database \"%s\" on %s",
db.engine.url.database, db.engine.url.host)
plugin = OVSQuantumOFPRyuAgent(integ_br, db, root_helper, target_v2_api)
plugin = OVSQuantumOFPRyuAgent(integ_br, db, root_helper)
plugin.daemon_loop(db)
sys.exit(0)

View File

@ -30,7 +30,6 @@ ovs_opts = [
]
agent_opts = [
cfg.BoolOpt('target_v2_api', default=True),
cfg.IntOpt('polling_interval', default=2),
cfg.StrOpt('root_helper', default='sudo'),
]

View File

@ -1,27 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
# 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 quantum.db.api as db
from quantum.plugins.ryu.db import models
def set_ofp_servers(hosts):
session = db.get_session()
session.query(models.OFPServer).delete()
for (host_address, host_type) in hosts:
host = models.OFPServer(host_address, host_type)
session.add(host)
session.flush()

View File

@ -1,37 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
# 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.
from sqlalchemy import Column, Integer, String
from quantum.db.models import BASE
class OFPServer(BASE):
"""Openflow Server/API address"""
__tablename__ = 'ofp_server'
id = Column(Integer, primary_key=True, autoincrement=True)
address = Column(String(255)) # netloc <host ip address>:<port>
host_type = Column(String(255)) # server type
# Controller, REST_API
def __init__(self, address, host_type):
self.address = address
self.host_type = host_type
def __repr__(self):
return "<OFPServer(%s,%s,%s)>" % (self.id, self.address,
self.host_type)

View File

@ -1,173 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Isaku Yamahata
# 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: Isaku Yamahata
from abc import ABCMeta, abstractmethod
import logging as LOG
import os
from quantum.api.api_common import OperationalStatus
from quantum.common import exceptions as q_exc
import quantum.db.api as db
from quantum.openstack.common import cfg
from quantum.plugins.ryu.common import config
from quantum.quantum_plugin_base import QuantumPluginBase
LOG.getLogger(__name__)
class OVSQuantumPluginDriverBase(object):
"""
Base class for OVS quantum plugin driver
"""
__metaclass__ = ABCMeta
@abstractmethod
def create_network(self, net):
pass
@abstractmethod
def delete_network(self, net):
pass
class OVSQuantumPluginBase(QuantumPluginBase):
"""
Base class for OVS-based plugin which referes to a subclass of
OVSQuantumPluginDriverBase which is defined above.
Subclass of OVSQuantumPluginBase must set self.driver to a subclass of
OVSQuantumPluginDriverBase.
"""
def __init__(self, conf_file, mod_file, configfile=None):
super(OVSQuantumPluginBase, self).__init__()
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
sql_max_retries = cfg.CONF.DATABASE.sql_max_retries
options.update({"sql_max_retries": sql_max_retries})
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
options.update({"reconnect_interval": reconnect_interval})
db.configure_db(options)
self.conf = cfg.CONF
# Subclass must set self.driver to its own OVSQuantumPluginDriverBase
self.driver = None
def get_all_networks(self, tenant_id, **kwargs):
nets = []
for net in db.network_list(tenant_id):
LOG.debug("Adding network: %s", net.uuid)
nets.append(self._make_net_dict(str(net.uuid), net.name,
None, net.op_status))
return nets
def _make_net_dict(self, net_id, net_name, ports, op_status):
res = {'net-id': net_id,
'net-name': net_name,
'net-op-status': op_status}
if ports:
res['net-ports'] = ports
return res
def create_network(self, tenant_id, net_name, **kwargs):
net = db.network_create(tenant_id, net_name,
op_status=OperationalStatus.UP)
LOG.debug("Created network: %s", net)
self.driver.create_network(net)
return self._make_net_dict(str(net.uuid), net.name, [], net.op_status)
def delete_network(self, tenant_id, net_id):
db.validate_network_ownership(tenant_id, net_id)
net = db.network_get(net_id)
# Verify that no attachments are plugged into the network
for port in db.port_list(net_id):
if port.interface_id:
raise q_exc.NetworkInUse(net_id=net_id)
net = db.network_destroy(net_id)
self.driver.delete_network(net)
return self._make_net_dict(str(net.uuid), net.name, [], net.op_status)
def get_network_details(self, tenant_id, net_id):
db.validate_network_ownership(tenant_id, net_id)
net = db.network_get(net_id)
ports = self.get_all_ports(tenant_id, net_id)
return self._make_net_dict(str(net.uuid), net.name,
ports, net.op_status)
def update_network(self, tenant_id, net_id, **kwargs):
db.validate_network_ownership(tenant_id, net_id)
net = db.network_update(net_id, tenant_id, **kwargs)
return self._make_net_dict(str(net.uuid), net.name,
None, net.op_status)
def _make_port_dict(self, port):
if port.state == "ACTIVE":
op_status = port.op_status
else:
op_status = OperationalStatus.DOWN
return {'port-id': str(port.uuid),
'port-state': port.state,
'port-op-status': op_status,
'net-id': port.network_id,
'attachment': port.interface_id}
def get_all_ports(self, tenant_id, net_id, **kwargs):
db.validate_network_ownership(tenant_id, net_id)
ports = db.port_list(net_id)
# This plugin does not perform filtering at the moment
return [{'port-id': str(port.uuid)} for port in ports]
def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
LOG.debug("Creating port with network_id: %s", net_id)
port = db.port_create(net_id, port_state,
op_status=OperationalStatus.DOWN)
return self._make_port_dict(port)
def delete_port(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_destroy(port_id, net_id)
return self._make_port_dict(port)
def update_port(self, tenant_id, net_id, port_id, **kwargs):
"""
Updates the state of a port on the specified Virtual Network.
"""
LOG.debug("update_port() called\n")
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
db.port_update(port_id, net_id, **kwargs)
return self._make_port_dict(port)
def get_port_details(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
port = db.port_get(port_id, net_id)
return self._make_port_dict(port)
def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
db.port_set_attachment(port_id, net_id, remote_iface_id)
def unplug_interface(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
db.port_set_attachment(port_id, net_id, "")
db.port_update(port_id, net_id, op_status=OperationalStatus.DOWN)
def get_interface_details(self, tenant_id, net_id, port_id):
db.validate_port_ownership(tenant_id, net_id, port_id)
res = db.port_get(port_id, net_id)
return res.interface_id

View File

@ -34,7 +34,6 @@ from nose import core
sys.path.append(os.getcwd())
sys.path.append(os.path.dirname(__file__))
from quantum.api.api_common import OperationalStatus
from quantum.common.test_lib import run_tests, test_config
from quantum.plugins.ryu.tests.unit.utils import patch_fake_ryu_client
import quantum.tests.unit
@ -47,10 +46,7 @@ if __name__ == '__main__':
# we should only invoked the tests once
invoke_once = len(sys.argv) > 1
test_config['plugin_name'] = "ryu_quantum_plugin.RyuQuantumPlugin"
test_config['plugin_name_v2'] = "ryu_quantum_plugin.RyuQuantumPluginV2"
test_config['default_net_op_status'] = OperationalStatus.UP
test_config['default_port_op_status'] = OperationalStatus.DOWN
cwd = os.getcwd()
# patch modules for ryu.app.client and ryu.app.rest_nw_id

View File

@ -26,52 +26,13 @@ from quantum.db import api as db
from quantum.db import db_base_plugin_v2
from quantum.db import models_v2
from quantum.openstack.common import cfg
from quantum.plugins.ryu.db import api as db_api
from quantum.plugins.ryu.db import api_v2 as db_api_v2
from quantum.plugins.ryu import ofp_service_type
from quantum.plugins.ryu import ovs_quantum_plugin_base
from quantum.plugins.ryu.common import config
LOG = logging.getLogger(__name__)
class OFPRyuDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase):
def __init__(self, conf):
super(OFPRyuDriver, self).__init__()
ofp_con_host = conf.OVS.openflow_controller
ofp_api_host = conf.OVS.openflow_rest_api
if ofp_con_host is None or ofp_api_host is None:
raise q_exc.Invalid("invalid configuration. check ryu.ini")
hosts = [(ofp_con_host, ofp_service_type.CONTROLLER),
(ofp_api_host, ofp_service_type.REST_API)]
db_api.set_ofp_servers(hosts)
self.client = client.OFPClient(ofp_api_host)
self.client.update_network(rest_nw_id.NW_ID_EXTERNAL)
# register known all network list on startup
self._create_all_tenant_network()
def _create_all_tenant_network(self):
networks = db.network_all_tenant_list()
for net in networks:
self.client.update_network(net.uuid)
def create_network(self, net):
self.client.create_network(net.uuid)
def delete_network(self, net):
self.client.delete_network(net.uuid)
class RyuQuantumPlugin(ovs_quantum_plugin_base.OVSQuantumPluginBase):
def __init__(self, configfile=None):
super(RyuQuantumPlugin, self).__init__(__file__, configfile)
self.driver = OFPRyuDriver(self.conf)
class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
def __init__(self, configfile=None):
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}

View File

@ -1,42 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
# 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 mox
import stubout
import unittest
import quantum.db.api as db
from quantum.plugins.ryu.tests.unit import utils
class BaseRyuTest(unittest.TestCase):
"""base test class for Ryu unit tests"""
def setUp(self):
config = utils.get_config()
options = {"sql_connection": config.get("DATABASE", "sql_connection")}
db.configure_db(options)
self.config = config
self.mox = mox.Mox()
self.stubs = stubout.StubOutForTesting()
def tearDown(self):
self.mox.UnsetStubs()
self.stubs.UnsetAll()
self.stubs.SmartUnsetAll()
self.mox.VerifyAll()
db.clear_db()

View File

@ -1,37 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
# <yamahata at valinux co jp>
# 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.
from quantum.plugins.ryu import ovs_quantum_plugin_base
class FakePluginDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase):
def __init__(self, config):
super(FakePluginDriver, self).__init__()
conf = config.parse(config)
self.conf = conf
def create_network(self, net):
pass
def delete_network(self, net):
pass
class FakePlugin(ovs_quantum_plugin_base.OVSQuantumPluginBase):
def __init__(self, configfile=None):
super(FakePlugin, self).__init__(None, __file__, configfile)
self.driver = FakePluginDriver(self.conf)

View File

@ -1,17 +0,0 @@
# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
# Copyright (C) 2011 Isaku Yamahata <yamahata at valinux co jp>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
NW_ID_EXTERNAL = '__NW_ID_EXTERNAL__'
NW_ID_UNKNOWN = '__NW_ID_UNKNOWN__'

View File

@ -1,46 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
# 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.
class OFPClient(object):
def __init__(self, address):
super(OFPClient, self).__init__()
self.address = address
def get_networks(self):
pass
def create_network(self, network_id):
pass
def update_network(self, network_id):
pass
def delete_network(self, network_id):
pass
def get_ports(self, network_id):
pass
def create_port(self, network_id, dpid, port):
pass
def update_port(self, network_id, dpid, port):
pass
def delete_port(self, network_id, dpid, port):
pass

View File

@ -15,46 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import mox
import unittest2
from quantum.openstack.common import cfg
from quantum.plugins.ryu.tests.unit.basetest import BaseRyuTest
from quantum.plugins.ryu.tests.unit import fake_plugin
from quantum.plugins.ryu.tests.unit import utils
from quantum.plugins.ryu.common import config
class PluginBaseTest(BaseRyuTest):
"""Class conisting of OVSQuantumPluginBase unit tests"""
def setUp(self):
super(PluginBaseTest, self).setUp()
self.ini_file = utils.create_fake_ryu_ini()
def tearDown(self):
os.unlink(self.ini_file)
super(PluginBaseTest, self).tearDown()
def test_create_delete_network(self):
# mox.StubOutClassWithMocks can't be used for class with metaclass
# overrided
driver_mock = self.mox.CreateMock(fake_plugin.FakePluginDriver)
self.mox.StubOutWithMock(fake_plugin, 'FakePluginDriver',
use_mock_anything=True)
fake_plugin.FakePluginDriver(mox.IgnoreArg()).AndReturn(driver_mock)
driver_mock.create_network(mox.IgnoreArg())
driver_mock.delete_network(mox.IgnoreArg())
self.mox.ReplayAll()
plugin = fake_plugin.FakePlugin(configfile=self.ini_file)
tenant_id = 'tenant_id'
net_name = 'net_name'
ret = plugin.create_network(tenant_id, net_name)
plugin.delete_network(tenant_id, ret['net-id'])
self.mox.VerifyAll()
class ConfigurationTest(unittest2.TestCase):
"""Configuration file Tests"""
def test_defaults(self):
self.assertEqual('br-int', cfg.CONF.OVS.integration_bridge)
self.assertEqual('sqlite://', cfg.CONF.DATABASE.sql_connection)

View File

@ -0,0 +1,52 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
# 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 unittest2
from quantum.db import api as db
from quantum.db import models_v2
from quantum.openstack.common import cfg
from quantum.plugins.ryu.db import api_v2 as db_api_v2
from quantum.plugins.ryu.db import models_v2 as ryu_models_v2
from quantum.plugins.ryu import ofp_service_type
class RyuDBTest(unittest2.TestCase):
def setUp(self):
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
options.update({'base': models_v2.model_base.BASEV2})
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
options.update({"reconnect_interval": reconnect_interval})
db.configure_db(options)
self.hosts = [(cfg.CONF.OVS.openflow_controller,
ofp_service_type.CONTROLLER),
(cfg.CONF.OVS.openflow_rest_api,
ofp_service_type.REST_API)]
db_api_v2.set_ofp_servers(self.hosts)
def tearDown(self):
db.clear_db()
cfg.CONF.reset()
def test_ofp_server(self):
session = db.get_session()
servers = session.query(ryu_models_v2.OFPServer).all()
print servers
self.assertEqual(len(servers), 2)
for s in servers:
self.assertTrue((s.address, s.host_type) in self.hosts)

View File

@ -1,75 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
# 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 uuid
import quantum.db.api as db
from quantum.openstack.common import cfg
from quantum.plugins.ryu.common import config
from quantum.plugins.ryu.tests.unit.basetest import BaseRyuTest
from quantum.plugins.ryu.tests.unit import utils
from quantum.plugins.ryu.tests.unit.utils import patch_fake_ryu_client
class RyuDriverTest(BaseRyuTest):
"""Class conisting of OFPRyuDriver unit tests"""
def setUp(self):
super(RyuDriverTest, self).setUp()
self.conf = cfg.CONF
# fake up ryu.app.client and ryu.app.rest_nw_id
# With those, plugin can be tested without ryu installed
self.module_patcher = patch_fake_ryu_client()
self.module_patcher.start()
def tearDown(self):
self.module_patcher.stop()
super(RyuDriverTest, self).tearDown()
def test_ryu_driver(self):
from ryu.app import client as client_mod
from ryu.app import rest_nw_id as rest_nw_id_mod
self.mox.StubOutClassWithMocks(client_mod, 'OFPClient')
client_mock = client_mod.OFPClient(utils.FAKE_REST_ADDR)
self.mox.StubOutWithMock(client_mock, 'update_network')
self.mox.StubOutWithMock(client_mock, 'create_network')
self.mox.StubOutWithMock(client_mock, 'delete_network')
client_mock.update_network(rest_nw_id_mod.NW_ID_EXTERNAL)
uuid0 = '01234567-89ab-cdef-0123-456789abcdef'
def fake_uuid4():
return uuid0
self.stubs.Set(uuid, 'uuid4', fake_uuid4)
uuid1 = '12345678-9abc-def0-1234-56789abcdef0'
net1 = utils.Net(uuid1)
client_mock.update_network(uuid0)
client_mock.create_network(uuid1)
client_mock.delete_network(uuid1)
self.mox.ReplayAll()
db.network_create('test', uuid0)
from quantum.plugins.ryu import ryu_quantum_plugin
ryu_driver = ryu_quantum_plugin.OFPRyuDriver(self.conf)
ryu_driver.create_network(net1)
ryu_driver.delete_network(net1)
self.mox.VerifyAll()
db.network_destroy(uuid0)

View File

@ -15,60 +15,18 @@
# License for the specific language governing permissions and limitations
# under the License.
import ConfigParser
import imp
import os
from StringIO import StringIO
import tempfile
import mock
from quantum.plugins.ryu.tests.unit import fake_rest_nw_id
from quantum.plugins.ryu.tests.unit import fake_ryu_client
FAKE_CONTROLLER_ADDR = '127.0.0.1:6633'
FAKE_REST_ADDR = '127.0.0.1:8080'
FAKE_RYU_INI_TEMPLATE = """
[DATABASE]
sql_connection = sqlite:///:memory:
[OVS]
integration-bridge = br-int
openflow-controller = %s
openflow-rest-api = %s
""" % (FAKE_CONTROLLER_ADDR, FAKE_REST_ADDR)
def create_fake_ryu_ini():
fd, file_name = tempfile.mkstemp(suffix='.ini')
tmp_file = os.fdopen(fd, 'w')
tmp_file.write(FAKE_RYU_INI_TEMPLATE)
tmp_file.close()
return file_name
def get_config():
config = ConfigParser.ConfigParser()
buf_file = StringIO(FAKE_RYU_INI_TEMPLATE)
config.readfp(buf_file)
buf_file.close()
return config
def patch_fake_ryu_client():
ryu_mod = imp.new_module('ryu')
ryu_app_mod = imp.new_module('ryu.app')
ryu_mod.app = ryu_app_mod
ryu_app_mod.client = fake_ryu_client
ryu_app_mod.rest_nw_id = fake_rest_nw_id
ryu_mod = mock.Mock()
ryu_app_mod = ryu_mod.app
ryu_app_client = ryu_app_mod.client
rest_nw_id = ryu_app_mod.rest_nw_id
rest_nw_id.NW_ID_EXTERNAL = '__NW_ID_EXTERNAL__'
rest_nw_id.NW_ID_UNKNOWN = '__NW_ID_UNKNOWN__'
return mock.patch.dict('sys.modules',
{'ryu': ryu_mod,
'ryu.app': ryu_app_mod,
'ryu.app.client': fake_ryu_client,
'ryu.app.rest_nw_id': fake_rest_nw_id})
class Net(object):
def __init__(self, uuid):
self.uuid = uuid
'ryu.app.client': ryu_app_client,
'ryu.app.rest_nw_id': rest_nw_id})

View File

@ -1,344 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011, Nicira Networks, Inc.
#
# 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.
# @author: Salvatore Orlando, Citrix
import logging
from quantum.api.api_common import OperationalStatus
from quantum.common import exceptions as exc
from quantum.db import api as db
LOG = logging.getLogger('quantum.plugins.sample.SamplePlugin')
class QuantumEchoPlugin(object):
"""
QuantumEchoPlugin is a demo plugin that doesn't
do anything but demonstrated the concept of a
concrete Quantum Plugin. Any call to this plugin
will result in just a "print" to std. out with
the name of the method that was called.
"""
def get_all_networks(self, tenant_id):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
"""
print("get_all_networks() called\n")
def create_network(self, tenant_id, net_name, **kwargs):
"""
Creates a new Virtual Network, and assigns it
a symbolic name.
"""
print("create_network() called\n")
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")
def get_network_details(self, tenant_id, net_id):
"""
Deletes the Virtual Network belonging to a the
spec
"""
print("get_network_details() called\n")
def update_network(self, tenant_id, net_id, **kwargs):
print("update_network() called")
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")
def create_port(self, tenant_id, net_id, **kwargs):
"""
Creates a port on the specified Virtual Network.
"""
print("create_port() called\n")
def delete_port(self, tenant_id, net_id, port_id):
"""
Deletes a port on a specified Virtual Network,
if the port contains a remote interface attachment,
the remote interface is first un-plugged and then the port
is deleted.
"""
print("delete_port() called\n")
def update_port(self, tenant_id, net_id, port_id, **kwargs):
"""
Updates the attributes of a port on the specified Virtual Network.
"""
print("update_port() called\n")
def get_port_details(self, tenant_id, net_id, port_id):
"""
This method allows the user to retrieve a remote interface
that is attached to this particular port.
"""
print("get_port_details() called\n")
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id):
"""
Attaches a remote interface to the specified port on the
specified Virtual Network.
"""
print("plug_interface() called\n")
def unplug_interface(self, tenant_id, net_id, port_id):
"""
Detaches a remote interface from the specified port on the
specified Virtual Network.
"""
print("unplug_interface() called\n")
supported_extension_aliases = ["FOXNSOX"]
def method_to_support_foxnsox_extension(self):
print("method_to_support_foxnsox_extension() called\n")
class FakePlugin(object):
"""
FakePlugin is a demo plugin that provides
in-memory data structures to aid in quantum
client/cli/api development
"""
def __init__(self):
db.configure_db({'sql_connection': 'sqlite:///:memory:'})
FakePlugin._net_counter = 0
def _get_network(self, tenant_id, network_id):
db.validate_network_ownership(tenant_id, network_id)
try:
network = db.network_get(network_id)
except:
raise exc.NetworkNotFound(net_id=network_id)
return network
def _get_port(self, tenant_id, network_id, port_id):
db.validate_port_ownership(tenant_id, network_id, port_id)
net = self._get_network(tenant_id, network_id)
try:
port = db.port_get(port_id, network_id)
except:
raise exc.PortNotFound(net_id=network_id, port_id=port_id)
# Port must exist and belong to the appropriate network.
if port['network_id'] != net['uuid']:
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 ('ACTIVE', 'DOWN'):
raise exc.StateInvalid(port_state=port_state)
return True
def _validate_attachment(self, tenant_id, network_id, port_id,
remote_interface_id):
for port in db.port_list(network_id):
if port['interface_id'] == remote_interface_id:
raise exc.AlreadyAttached(net_id=network_id,
port_id=port_id,
att_id=port['interface_id'],
att_port_id=port['uuid'])
def get_all_networks(self, tenant_id, **kwargs):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
"""
LOG.debug("FakePlugin.get_all_networks() called")
filter_opts = kwargs.get('filter_opts', None)
if not filter_opts is None and len(filter_opts) > 0:
LOG.debug("filtering options were passed to the plugin"
"but the Fake plugin does not support them")
nets = []
for net in db.network_list(tenant_id):
net_item = {'net-id': str(net.uuid),
'net-name': net.name,
'net-op-status': net.op_status}
nets.append(net_item)
return nets
def get_network_details(self, tenant_id, net_id):
"""
retrieved a list of all the remote vifs that
are attached to the network
"""
LOG.debug("FakePlugin.get_network_details() called")
net = self._get_network(tenant_id, net_id)
# Retrieves ports for network
ports = self.get_all_ports(tenant_id, net_id)
return {'net-id': str(net.uuid),
'net-name': net.name,
'net-op-status': net.op_status,
'net-ports': ports}
def create_network(self, tenant_id, net_name, **kwargs):
"""
Creates a new Virtual Network, and assigns it
a symbolic name.
"""
LOG.debug("FakePlugin.create_network() called")
new_net = db.network_create(tenant_id, net_name)
# Put operational status UP
db.network_update(new_net.uuid, net_name,
op_status=OperationalStatus.UP)
# Return uuid for newly created network as net-id.
return {'net-id': new_net.uuid}
def delete_network(self, tenant_id, net_id):
"""
Deletes the network with the specified network identifier
belonging to the specified tenant.
"""
LOG.debug("FakePlugin.delete_network() called")
net = self._get_network(tenant_id, net_id)
# Verify that no attachments are plugged into the network
if net:
for port in db.port_list(net_id):
if port['interface_id']:
raise exc.NetworkInUse(net_id=net_id)
db.network_destroy(net_id)
return net
# Network not found
raise exc.NetworkNotFound(net_id=net_id)
def update_network(self, tenant_id, net_id, **kwargs):
"""
Updates the attributes of a particular Virtual Network.
"""
LOG.debug("FakePlugin.update_network() called")
net = db.network_update(net_id, tenant_id, **kwargs)
return net
def get_all_ports(self, tenant_id, net_id, **kwargs):
"""
Retrieves all port identifiers belonging to the
specified Virtual Network.
"""
LOG.debug("FakePlugin.get_all_ports() called")
db.validate_network_ownership(tenant_id, net_id)
filter_opts = kwargs.get('filter_opts')
if filter_opts:
LOG.debug("filtering options were passed to the plugin"
"but the Fake plugin does not support them")
port_ids = []
ports = db.port_list(net_id)
for x in ports:
d = {'port-id': str(x.uuid)}
port_ids.append(d)
return port_ids
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.
"""
LOG.debug("FakePlugin.get_port_details() called")
port = self._get_port(tenant_id, net_id, port_id)
return {'port-id': str(port.uuid),
'attachment': port.interface_id,
'port-state': port.state,
'port-op-status': port.op_status}
def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
"""
Creates a port on the specified Virtual Network.
"""
LOG.debug("FakePlugin.create_port() called")
# verify net_id
self._get_network(tenant_id, net_id)
port = db.port_create(net_id, port_state)
# Put operational status UP
db.port_update(port.uuid, net_id, op_status=OperationalStatus.UP)
port_item = {'port-id': str(port.uuid)}
return port_item
def update_port(self, tenant_id, net_id, port_id, **kwargs):
"""
Updates the attributes of a port on the specified Virtual Network.
"""
LOG.debug("FakePlugin.update_port() called")
#validate port and network ids
self._get_network(tenant_id, net_id)
self._get_port(tenant_id, net_id, port_id)
port = db.port_update(port_id, net_id, **kwargs)
port_item = {'port-id': port_id, 'port-state': port['state']}
return port_item
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.
"""
LOG.debug("FakePlugin.delete_port() called")
net = self._get_network(tenant_id, net_id)
port = self._get_port(tenant_id, net_id, port_id)
if port['interface_id']:
raise exc.PortInUse(net_id=net_id, port_id=port_id,
att_id=port['interface_id'])
try:
port = db.port_destroy(port_id, net_id)
except Exception, e:
raise Exception("Failed to delete port: %s" % str(e))
d = {}
d["port-id"] = str(port.uuid)
return d
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.
"""
LOG.debug("FakePlugin.plug_interface() called")
port = self._get_port(tenant_id, net_id, port_id)
# Validate attachment
self._validate_attachment(tenant_id, net_id, port_id,
remote_interface_id)
if port['interface_id']:
raise exc.PortInUse(net_id=net_id, port_id=port_id,
att_id=port['interface_id'])
db.port_set_attachment(port_id, net_id, 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.
"""
LOG.debug("FakePlugin.unplug_interface() called")
self._get_port(tenant_id, net_id, port_id)
# TODO(salvatore-orlando):
# Should unplug on port without attachment raise an Error?
db.port_unset_attachment(port_id, net_id)

View File

@ -1,121 +0,0 @@
# Copyright (c) 2012 OpenStack, LLC.
#
# 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 uuid
from quantum import quantum_plugin_base_v2
LOG = logging.getLogger(__name__)
class QuantumEchoPlugin(quantum_plugin_base_v2.QuantumPluginBaseV2):
"""
QuantumEchoPlugin is a demo plugin that doesn't
do anything but demonstrate the concept of a
concrete Quantum Plugin. Any call to this plugin
will result in just a log statement with the name
method that was called and its arguments.
"""
def _log(self, name, context, **kwargs):
kwarg_msg = ' '.join([('%s: |%s|' % (str(key), kwargs[key]))
for key in kwargs])
# TODO(anyone) Add a nice __repr__ and __str__ to context
#LOG.debug('%s context: %s %s' % (name, context, kwarg_msg))
LOG.debug('%s %s' % (name, kwarg_msg))
def create_subnet(self, context, subnet):
self._log("create_subnet", context, subnet=subnet)
res = {"id": str(uuid.uuid4())}
res.update(subnet)
return res
def update_subnet(self, context, id, subnet):
self._log("update_subnet", context, id=id, subnet=subnet)
res = {"id": id}
res.update(subnet)
return res
def get_subnet(self, context, id, show=None, verbose=None):
self._log("get_subnet", context, id=id, show=show,
verbose=verbose)
return {"id": id}
def delete_subnet(self, context, id):
self._log("delete_subnet", context, id=id)
def get_subnets(self, context, filters=None, show=None, verbose=None):
self._log("get_subnets", context, filters=filters, show=show,
verbose=verbose)
return []
def create_network(self, context, network):
self._log("create_network", context, network=network)
res = {"id": str(uuid.uuid4())}
res.update(network)
return res
def update_network(self, context, id, network):
self._log("update_network", context, id=id, network=network)
res = {"id": id}
res.update(network)
return res
def get_network(self, context, id, show=None, verbose=None):
self._log("get_network", context, id=id, show=show,
verbose=verbose)
return {"id": id}
def delete_network(self, context, id):
self._log("delete_network", context, id=id)
def get_networks(self, context, filters=None, show=None, verbose=None):
self._log("get_networks", context, filters=filters, show=show,
verbose=verbose)
return []
def create_port(self, context, port):
self._log("create_port", context, port=port)
res = {"id": str(uuid.uuid4())}
res.update(port)
return res
def update_port(self, context, id, port):
self._log("update_port", context, id=id, port=port)
res = {"id": id}
res.update(port)
return res
def get_port(self, context, id, show=None, verbose=None):
self._log("get_port", context, id=id, show=show,
verbose=verbose)
return {"id": id}
def delete_port(self, context, id):
self._log("delete_port", context, id=id)
def get_ports(self, context, filters=None, show=None, verbose=None):
self._log("get_ports", context, filters=filters, show=show,
verbose=verbose)
return []
supported_extension_aliases = ["FOXNSOX"]
def method_to_support_foxnsox_extension(self, context):
self._log("method_to_support_foxnsox_extension", context)

View File

@ -1,270 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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.
"""
Quantum Plug-in API specification.
QuantumPluginBase provides the definition of minimum set of
methods that needs to be implemented by a Quantum Plug-in.
"""
from abc import ABCMeta, abstractmethod
import inspect
class QuantumPluginBase(object):
__metaclass__ = ABCMeta
@abstractmethod
def get_all_networks(self, tenant_id, **kwargs):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
:param tenant_id: unique identifier for the tenant whose networks
are being retrieved by this method
:param **kwargs: options to be passed to the plugin. The following
keywork based-options can be specified:
filter_opts - options for filtering network list
:returns: a list of mapping sequences with the following signature:
[ {'net-id': uuid that uniquely identifies
the particular quantum network,
'net-name': a human-readable name associated
with network referenced by net-id
},
....
{'net-id': uuid that uniquely identifies the
particular quantum network,
'net-name': a human-readable name associated
with network referenced by net-id
}
]
:raises: None
"""
pass
@abstractmethod
def create_network(self, tenant_id, net_name, **kwargs):
"""
Creates a new Virtual Network, and assigns it
a symbolic name.
:returns: a sequence of mappings with the following signature:
{'net-id': uuid that uniquely identifies the
particular quantum network,
'net-name': a human-readable name associated
with network referenced by net-id
}
:raises:
"""
pass
@abstractmethod
def delete_network(self, tenant_id, net_id):
"""
Deletes the network with the specified network identifier
belonging to the specified tenant.
:returns: a sequence of mappings with the following signature:
{'net-id': uuid that uniquely identifies the
particular quantum network
}
:raises: exception.NetworkInUse
:raises: exception.NetworkNotFound
"""
pass
@abstractmethod
def get_network_details(self, tenant_id, net_id):
"""
Retrieves a list of all the remote vifs that
are attached to the network.
:returns: a sequence of mappings with the following signature:
{'net-id': uuid that uniquely identifies the
particular quantum network
'net-name': a human-readable name associated
with network referenced by net-id
'net-ifaces': ['vif1_on_network_uuid',
'vif2_on_network_uuid',...,'vifn_uuid']
}
:raises: exception.NetworkNotFound
"""
pass
@abstractmethod
def update_network(self, tenant_id, net_id, **kwargs):
"""
Updates the attributes of a particular Virtual Network.
:returns: a sequence of mappings representing the new network
attributes, with the following signature:
{'net-id': uuid that uniquely identifies the
particular quantum network
'net-name': the new human-readable name
associated with network referenced by net-id
}
:raises: exception.NetworkNotFound
"""
pass
@abstractmethod
def get_all_ports(self, tenant_id, net_id, **kwargs):
"""
Retrieves all port identifiers belonging to the
specified Virtual Network.
:param tenant_id: unique identifier for the tenant for which this
method is going to retrieve ports
:param net_id: unique identifiers for the network whose ports are
about to be retrieved
:param **kwargs: options to be passed to the plugin. The following
keywork based-options can be specified:
filter_opts - options for filtering network list
:returns: a list of mapping sequences with the following signature:
[ {'port-id': uuid representing a particular port
on the specified quantum network
},
....
{'port-id': uuid representing a particular port
on the specified quantum network
}
]
:raises: exception.NetworkNotFound
"""
pass
@abstractmethod
def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
"""
Creates a port on the specified Virtual Network.
:returns: a mapping sequence with the following signature:
{'port-id': uuid representing the created port
on specified quantum network
}
:raises: exception.NetworkNotFound
:raises: exception.StateInvalid
"""
pass
@abstractmethod
def update_port(self, tenant_id, net_id, port_id, **kwargs):
"""
Updates the attributes of a specific port on the
specified Virtual Network.
:returns: a mapping sequence with the following signature:
{'port-id': uuid representing the
updated port on specified quantum network
'port-state': update port state( UP or DOWN)
}
:raises: exception.StateInvalid
:raises: exception.PortNotFound
"""
pass
@abstractmethod
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.
:returns: a mapping sequence with the following signature:
{'port-id': uuid representing the deleted port
on specified quantum network
}
:raises: exception.PortInUse
:raises: exception.PortNotFound
:raises: exception.NetworkNotFound
"""
pass
@abstractmethod
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.
:returns: a mapping sequence with the following signature:
{'port-id': uuid representing the port on
specified quantum network
'net-id': uuid representing the particular
quantum network
'attachment': uuid of the virtual interface
bound to the port, None otherwise
}
:raises: exception.PortNotFound
:raises: exception.NetworkNotFound
"""
pass
@abstractmethod
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.
:returns: None
:raises: exception.NetworkNotFound
:raises: exception.PortNotFound
:raises: exception.AlreadyAttached
(? should the network automatically unplug/replug)
"""
pass
@abstractmethod
def unplug_interface(self, tenant_id, net_id, port_id):
"""
Detaches a remote interface from the specified port on the
specified Virtual Network.
:returns: None
:raises: exception.NetworkNotFound
:raises: exception.PortNotFound
"""
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).
In that case, it will check that every method
marked with the abstractmethod decorator is
provided by the plugin class.
"""
if cls is QuantumPluginBase:
for method in cls.__abstractmethods__:
method_ok = False
for base in klass.__mro__:
if method in base.__dict__:
fn_obj = base.__dict__[method]
if inspect.isfunction(fn_obj):
abstract_fn_obj = cls.__dict__[method]
arg_count = fn_obj.func_code.co_argcount
expected_arg_count = (
abstract_fn_obj.func_code.co_argcount)
method_ok = arg_count == expected_arg_count
if method_ok:
continue
return NotImplemented
return True
return NotImplemented

File diff suppressed because it is too large Load Diff

View File

@ -1,372 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2012 ????
# 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
import unittest
from lxml import etree
from webob import exc
import quantum.api.attachments as atts
import quantum.api.networks as nets
import quantum.api.ports as ports
import quantum.api.versions as versions
from quantum.common.test_lib import test_config
from quantum.openstack.common import jsonutils
import quantum.tests.unit._test_api as test_api
import quantum.tests.unit.testlib_api as testlib
class APITestV10(test_api.BaseAPIOperationsTest):
def assert_network(self, **kwargs):
self.assertEqual({'id': kwargs['id'],
'name': kwargs['name']},
kwargs['network_data'])
def assert_network_details(self, **kwargs):
self.assertEqual({'id': kwargs['id'],
'name': kwargs['name'],
'ports': [{'id': kwargs['port_id'],
'state': 'ACTIVE'}]},
kwargs['network_data'])
def assert_port(self, **kwargs):
self.assertEqual({'id': kwargs['id'],
'state': kwargs['state']},
kwargs['port_data'])
def assert_port_attachment(self, **kwargs):
self.assertEqual({'id': kwargs['id'], 'state': kwargs['state'],
'attachment': {'id': kwargs['interface_id']}},
kwargs['port_data'])
def setUp(self):
super(APITestV10, self).setUp(
'quantum.api.APIRouterV10',
{
test_api.NETS: nets.ControllerV10._serialization_metadata,
test_api.PORTS: ports.ControllerV10._serialization_metadata,
test_api.ATTS: atts.ControllerV10._serialization_metadata,
}
)
self._successful_create_code = exc.HTTPOk.code
self._network_not_found_code = 420
self._network_in_use_code = 421
self._port_not_found_code = 430
self._port_state_invalid_code = 431
self._port_in_use_code = 432
self._already_attached_code = 440
class APITestV11(test_api.BaseAPIOperationsTest):
def assert_network(self, **kwargs):
self.assertEqual({'id': kwargs['id'],
'name': kwargs['name'],
'op-status': self.net_op_status},
kwargs['network_data'])
def assert_network_details(self, **kwargs):
self.assertEqual({'id': kwargs['id'],
'name': kwargs['name'],
'op-status': self.net_op_status,
'ports': [{'id': kwargs['port_id'],
'state': 'ACTIVE',
'op-status': self.port_op_status}]},
kwargs['network_data'])
def assert_port(self, **kwargs):
self.assertEqual({'id': kwargs['id'],
'state': kwargs['state'],
'op-status': self.port_op_status},
kwargs['port_data'])
def assert_port_attachment(self, **kwargs):
self.assertEqual({'id': kwargs['id'], 'state': kwargs['state'],
'op-status': self.port_op_status,
'attachment': {'id': kwargs['interface_id']}},
kwargs['port_data'])
def setUp(self):
self.net_op_status = test_config.get('default_net_op_status',
'UNKNOWN')
self.port_op_status = test_config.get('default_port_op_status',
'UNKNOWN')
super(APITestV11, self).setUp(
'quantum.api.APIRouterV11',
{
test_api.NETS: nets.ControllerV11._serialization_metadata,
test_api.PORTS: ports.ControllerV11._serialization_metadata,
test_api.ATTS: atts.ControllerV11._serialization_metadata,
}
)
self._successful_create_code = exc.HTTPAccepted.code
self._network_not_found_code = exc.HTTPNotFound.code
self._network_in_use_code = exc.HTTPConflict.code
self._port_not_found_code = exc.HTTPNotFound.code
self._port_state_invalid_code = exc.HTTPBadRequest.code
self._port_in_use_code = exc.HTTPConflict.code
self._already_attached_code = exc.HTTPConflict.code
class APIFiltersTest(test_api.AbstractAPITest):
""" Test case for API filters.
Uses controller for API v1.1
"""
def _do_filtered_network_list_request(self, flt):
list_network_req = testlib.network_list_request(self.tenant_id,
self.fmt,
query_string=flt)
list_network_res = list_network_req.get_response(self.api)
self.assertEqual(list_network_res.status_int, 200)
network_data = (self._net_deserializers[self.content_type].
deserialize(list_network_res.body)['body'])
return network_data
def _do_filtered_port_list_request(self, flt, network_id):
list_port_req = testlib.port_list_request(self.tenant_id,
network_id,
self.fmt,
query_string=flt)
list_port_res = list_port_req.get_response(self.api)
self.assertEqual(list_port_res.status_int, 200)
port_data = (self._port_deserializers[self.content_type].
deserialize(list_port_res.body)['body'])
return port_data
def setUp(self):
super(APIFiltersTest, self).setUp(
'quantum.api.APIRouterV11',
{
test_api.NETS: nets.ControllerV11._serialization_metadata,
test_api.PORTS: ports.ControllerV11._serialization_metadata,
test_api.ATTS: atts.ControllerV11._serialization_metadata,
}
)
self._successful_create_code = exc.HTTPAccepted.code
self.net_op_status = test_config.get('default_net_op_status',
'UNKNOWN')
self.port_op_status = test_config.get('default_port_op_status',
'UNKNOWN')
self.fmt = "xml"
self.content_type = "application/%s" % self.fmt
# create data for validating filters
# Create network "test-1"
self.net1_id = self._create_network(self.fmt, name="test-1")
# Add 2 ports, 1 ACTIVE, 1 DOWN
self.port11_id = self._create_port(self.net1_id, "ACTIVE", self.fmt)
self.port12_id = self._create_port(self.net1_id, "DOWN", self.fmt)
# Put attachment "test-1-att" in active port
self._set_attachment(self.net1_id,
self.port11_id,
"test-1-att",
self.fmt)
# Create network "test-2"
# Add 2 ports, 2 ACTIVE, 0 DOWN
self.net2_id = self._create_network(self.fmt, name="test-2")
self.port21_id = self._create_port(self.net2_id, "ACTIVE", self.fmt)
self.port22_id = self._create_port(self.net2_id, "ACTIVE", self.fmt)
def test_network_name_filter(self):
flt = "name=test-1"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
self.assertEqual(network_data['networks'][0]['id'], self.net1_id)
flt = "name=non-existent"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 0
self.assertEqual(len(network_data['networks']), 0)
def test_network_op_status_filter(self):
# First filter for networks in default status
flt = "op-status=%s" % self.net_op_status
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 2
self.assertEqual(len(network_data['networks']), 2)
# And then for networks in 'DOWN' status
flt = "op-status=DOWN"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 0
self.assertEqual(len(network_data['networks']), 0)
def test_network_port_op_status_filter(self):
# First filter for networks with ports in default op status
flt = "port-op-status=%s" % self.port_op_status
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 2
self.assertEqual(len(network_data['networks']), 2)
def test_network_port_state_filter(self):
# First filter for networks with ports 'ACTIVE'
flt = "port-state=ACTIVE"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 2
self.assertEqual(len(network_data['networks']), 2)
# And then for networks with ports in 'DOWN' admin state
flt = "port-state=DOWN"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
def test_network_has_attachment_filter(self):
# First filter for networks with ports 'ACTIVE'
flt = "has-attachment=True"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
# And then for networks with ports in 'DOWN' admin state
flt = "has-attachment=False"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
def test_network_port_filter(self):
flt = "port=%s" % self.port11_id
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
self.assertEqual(network_data['networks'][0]['id'], self.net1_id)
flt = "port=%s" % self.port21_id
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
self.assertEqual(network_data['networks'][0]['id'], self.net2_id)
def test_network_attachment_filter(self):
flt = "attachment=test-1-att"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
self.assertEqual(network_data['networks'][0]['id'], self.net1_id)
flt = "attachment=non-existent"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 0
self.assertEqual(len(network_data['networks']), 0)
def test_network_multiple_filters(self):
# Add some data for having more fun
another_net_id = self._create_network(self.fmt, name="test-1")
# Add 1 ACTIVE port
self._create_port(another_net_id, "ACTIVE", self.fmt)
# Do the filtering
flt = "name=test-1&port-state=ACTIVE&attachment=test-1-att"
network_data = self._do_filtered_network_list_request(flt)
# Check network count: should return 1
self.assertEqual(len(network_data['networks']), 1)
self.assertEqual(network_data['networks'][0]['id'], self.net1_id)
def test_port_state_filter(self):
# First filter for 'ACTIVE' ports in 1st network
flt = "state=ACTIVE"
port_data = self._do_filtered_port_list_request(flt, self.net1_id)
# Check port count: should return 1
self.assertEqual(len(port_data['ports']), 1)
# And then in 2nd network
port_data = self._do_filtered_port_list_request(flt, self.net2_id)
# Check port count: should return 2
self.assertEqual(len(port_data['ports']), 2)
def test_port_op_status_filter(self):
# First filter for 'UP' ports in 1st network
flt = "op-status=%s" % self.port_op_status
port_data = self._do_filtered_port_list_request(flt, self.net1_id)
# Check port count: should return 2
self.assertEqual(len(port_data['ports']), 2)
def test_port_has_attachment_filter(self):
# First search for ports with attachments in 1st network
flt = "has-attachment=True"
port_data = self._do_filtered_port_list_request(flt, self.net1_id)
# Check port count: should return 1
self.assertEqual(len(port_data['ports']), 1)
self.assertEqual(port_data['ports'][0]['id'], self.port11_id)
# And then for ports without attachment in 2nd network
flt = "has-attachment=False"
port_data = self._do_filtered_port_list_request(flt, self.net2_id)
# Check port count: should return 2
self.assertEqual(len(port_data['ports']), 2)
def test_port_attachment_filter(self):
# First search for ports with attachments in 1st network
flt = "attachment=test-1-att"
port_data = self._do_filtered_port_list_request(flt, self.net1_id)
# Check port count: should return 1
self.assertEqual(len(port_data['ports']), 1)
self.assertEqual(port_data['ports'][0]['id'], self.port11_id)
# And then for a non-existent attachment in 2nd network
flt = "attachment=non-existent"
port_data = self._do_filtered_port_list_request(flt, self.net2_id)
# Check port count: should return 0
self.assertEqual(len(port_data['ports']), 0)
def test_port_multiple_filters(self):
flt = "op-status=%s&state=DOWN" % self.port_op_status
port_data = self._do_filtered_port_list_request(flt, self.net1_id)
# Check port count: should return 1
self.assertEqual(len(port_data['ports']), 1)
self.assertEqual(port_data['ports'][0]['id'], self.port12_id)
flt = "state=ACTIVE&attachment=test-1-att"
port_data = self._do_filtered_port_list_request(flt, self.net1_id)
# Check port count: should return 1
self.assertEqual(len(port_data['ports']), 1)
self.assertEqual(port_data['ports'][0]['id'], self.port11_id)
flt = "state=ACTIVE&has-attachment=False"
port_data = self._do_filtered_port_list_request(flt, self.net2_id)
# Check port count: should return 2
self.assertEqual(len(port_data['ports']), 2)
class APIRootTest(unittest.TestCase):
def setUp(self):
self.app = versions.Versions()
def _test_root_responds_with_versions(self, content_type):
req = testlib.create_request('/', '', content_type)
response = self.app(req)
self.assertEquals(response.status_int, 200)
return response.body
def test_root_responds_with_versions_json(self):
body = self._test_root_responds_with_versions('application/json')
data = jsonutils.loads(body)
self.assertEquals('versions', data.keys()[0])
def test_root_responds_with_versions_xml(self):
body = self._test_root_responds_with_versions('application/xml')
root = etree.fromstring(body)
self.assertEquals(root.tag, 'versions')
def test_invalid_version(self):
req = testlib.create_request('/v99.99/tenants/tenantX/networks',
'',
'application/json')
response = self.app(req)
self.assertEquals(response.status_int, 404)

View File

@ -1,127 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011, Cisco Systems, Inc.
#
# 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: Rohit Agarwalla, Cisco Systems, Inc.
"""
test_database.py is an independent test suite
that tests the database api method calls
"""
import unittest
from quantum.db import api as db
from quantum.tests.unit import database_stubs as db_stubs
class QuantumDBTest(unittest.TestCase):
"""Class consisting of Quantum DB unit tests"""
def setUp(self):
"""Setup for tests"""
db.configure_db({'sql_connection': 'sqlite:///:memory:'})
self.dbtest = db_stubs.QuantumDB()
self.tenant_id = "t1"
def tearDown(self):
"""Tear Down"""
db.clear_db()
def testa_create_network(self):
"""test to create network"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
self.assertTrue(net1["name"] == "plugin_test1")
def testb_get_networks(self):
"""test to get all networks"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
self.assertTrue(net1["name"] == "plugin_test1")
net2 = self.dbtest.create_network(self.tenant_id, "plugin_test2")
self.assertTrue(net2["name"] == "plugin_test2")
nets = self.dbtest.get_all_networks(self.tenant_id)
count = 0
for net in nets:
if "plugin_test" in net["name"]:
count += 1
self.assertTrue(count == 2)
def testc_delete_network(self):
"""test to delete network"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
self.assertTrue(net1["name"] == "plugin_test1")
self.dbtest.delete_network(net1["id"])
nets = self.dbtest.get_all_networks(self.tenant_id)
count = len(nets)
self.assertTrue(count == 0)
def testd_update_network(self):
"""test to rename network"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
self.assertTrue(net1["name"] == "plugin_test1")
net = self.dbtest.update_network(self.tenant_id, net1["id"],
{'name': "plugin_test1_renamed"})
print net
self.assertTrue(net["name"] == "plugin_test1_renamed")
def teste_create_port(self):
"""test to create port"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
port = self.dbtest.create_port(net1["id"])
self.assertTrue(port["net-id"] == net1["id"])
def testf_get_ports(self):
"""test to get ports"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
port = self.dbtest.create_port(net1["id"])
self.assertTrue(port["net-id"] == net1["id"])
ports = self.dbtest.get_all_ports(net1["id"])
count = len(ports)
self.assertTrue(count == 1)
def testf_update_port(self):
"""test to update port"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
port = self.dbtest.create_port(net1["id"])
self.dbtest.update_port(port["net-id"],
port['id'],
state='ACTIVE',
interface_id='interface_id1')
self.assertTrue(port["net-id"] == net1["id"])
ports = self.dbtest.get_all_ports(net1["id"])
new_port = ports[0]
self.assertEqual('ACTIVE', new_port['state'])
self.assertEqual('interface_id1', new_port['attachment'])
def testf_delete_port(self):
"""test to delete port"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
port = self.dbtest.create_port(net1["id"])
self.assertTrue(port["net-id"] == net1["id"])
ports = self.dbtest.get_all_ports(net1["id"])
for por in ports:
self.dbtest.delete_port(net1["id"], por["id"])
ports = self.dbtest.get_all_ports(net1["id"])
count = len(ports)
self.assertTrue(count == 0)
def testg_plug_unplug_interface(self):
"""test to plug/unplug interface"""
net1 = self.dbtest.create_network(self.tenant_id, "plugin_test1")
port1 = self.dbtest.create_port(net1["id"])
self.dbtest.plug_interface(net1["id"], port1["id"], "vif1.1")
port = self.dbtest.get_port(net1["id"], port1["id"])
self.assertTrue(port[0]["attachment"] == "vif1.1")
self.dbtest.unplug_interface(net1["id"], port1["id"])
port = self.dbtest.get_port(net1["id"], port1["id"])
self.assertTrue(port[0]["attachment"] is None)

View File

@ -19,10 +19,10 @@ import os
import unittest
import routes
import webob
from webtest import AppError
from webtest import TestApp
from quantum.api import faults
from quantum.common import config
from quantum.common import exceptions
from quantum.extensions import extensions
@ -32,7 +32,7 @@ from quantum.extensions.extensions import (
PluginAwareExtensionManager,
)
from quantum.openstack.common import jsonutils
from quantum.plugins.sample.SamplePlugin import QuantumEchoPlugin
from quantum.db.db_base_plugin_v2 import QuantumDbPluginV2
from quantum.tests.unit import BaseTest
from quantum.tests.unit.extension_stubs import (
ExtensionExpectingPluginInterface,
@ -65,6 +65,15 @@ class ExtensionsTestApp(wsgi.Router):
super(ExtensionsTestApp, self).__init__(mapper)
class FakePluginWithExtension(QuantumDbPluginV2):
"""A fake plugin used only for extension testing in this file."""
supported_extension_aliases = ["FOXNSOX"]
def method_to_support_foxnsox_extension(self, context):
self._log("method_to_support_foxnsox_extension", context)
class ResourceExtensionTest(unittest.TestCase):
class ResourceExtensionController(wsgi.Controller):
@ -76,8 +85,7 @@ class ResourceExtensionTest(unittest.TestCase):
return {'data': {'id': id}}
def notimplemented_function(self, request, id):
return faults.Quantum10HTTPError(
exceptions.NotImplementedError("notimplemented_function"))
return webob.exc.HTTPClientError(NotImplementedError())
def custom_member_action(self, request, id):
return {'member_action': 'value'}
@ -473,8 +481,9 @@ def setup_base_app():
def setup_extensions_middleware(extension_manager=None):
extension_manager = (extension_manager or
PluginAwareExtensionManager(extensions_path,
QuantumEchoPlugin()))
PluginAwareExtensionManager(
extensions_path,
FakePluginWithExtension()))
config_file = 'quantum.conf.test'
args = ['--config-file', etcdir(config_file)]
config.parse(args=args)

View File

@ -28,170 +28,3 @@ def create_request(path, body, content_type, method='GET', query_string=None):
req.headers['Accept'] = content_type
req.body = body
return req
def _network_list_request(tenant_id, format='xml', detail=False,
query_string=None):
method = 'GET'
detail_str = detail and '/detail' or ''
path = ("/tenants/%(tenant_id)s/networks"
"%(detail_str)s.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method, query_string)
def network_list_request(tenant_id, format='xml', query_string=None):
return _network_list_request(tenant_id, format, query_string=query_string)
def network_list_detail_request(tenant_id, format='xml'):
return _network_list_request(tenant_id, format, detail=True)
def _show_network_request(tenant_id, network_id, format='xml', detail=False):
method = 'GET'
detail_str = detail and '/detail' or ''
path = ("/tenants/%(tenant_id)s/networks"
"/%(network_id)s%(detail_str)s.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def show_network_request(tenant_id, network_id, format='xml'):
return _show_network_request(tenant_id, network_id, format)
def show_network_detail_request(tenant_id, network_id, format='xml'):
return _show_network_request(tenant_id, network_id, format, detail=True)
def new_network_request(tenant_id, network_name='new_name',
format='xml', custom_req_body=None):
method = 'POST'
path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
data = custom_req_body or {'network': {'name': '%s' % network_name}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def update_network_request(tenant_id, network_id, network_name, format='xml',
custom_req_body=None):
method = 'PUT'
path = ("/tenants/%(tenant_id)s/networks"
"/%(network_id)s.%(format)s") % locals()
data = custom_req_body or {'network': {'name': '%s' % network_name}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def network_delete_request(tenant_id, network_id, format='xml'):
method = 'DELETE'
path = ("/tenants/%(tenant_id)s/networks/"
"%(network_id)s.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def _port_list_request(tenant_id, network_id, format='xml',
detail=False, query_string=None):
method = 'GET'
detail_str = detail and '/detail' or ''
path = ("/tenants/%(tenant_id)s/networks/"
"%(network_id)s/ports%(detail_str)s.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method, query_string)
def port_list_request(tenant_id, network_id, format='xml', query_string=None):
return _port_list_request(tenant_id,
network_id,
format,
query_string=query_string)
def port_list_detail_request(tenant_id, network_id, format='xml'):
return _port_list_request(tenant_id, network_id,
format, detail=True)
def _show_port_request(tenant_id, network_id, port_id,
format='xml', detail=False):
method = 'GET'
detail_str = detail and '/detail' or ''
path = ("/tenants/%(tenant_id)s/networks/%(network_id)s"
"/ports/%(port_id)s%(detail_str)s.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def show_port_request(tenant_id, network_id, port_id, format='xml'):
return _show_port_request(tenant_id, network_id, port_id, format)
def show_port_detail_request(tenant_id, network_id, port_id, format='xml'):
return _show_port_request(tenant_id, network_id, port_id,
format, detail=True)
def new_port_request(tenant_id, network_id, port_state,
format='xml', custom_req_body=None):
method = 'POST'
path = ("/tenants/%(tenant_id)s/networks/"
"%(network_id)s/ports.%(format)s") % locals()
data = (custom_req_body or port_state and
{'port': {'state': '%s' % port_state}})
content_type = "application/%s" % format
body = data and Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def port_delete_request(tenant_id, network_id, port_id, format='xml'):
method = 'DELETE'
path = ("/tenants/%(tenant_id)s/networks/"
"%(network_id)s/ports/%(port_id)s.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def update_port_request(tenant_id, network_id, port_id, port_state,
format='xml', custom_req_body=None):
method = 'PUT'
path = ("/tenants/%(tenant_id)s/networks"
"/%(network_id)s/ports/%(port_id)s.%(format)s") % locals()
data = custom_req_body or {'port': {'state': '%s' % port_state}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def get_attachment_request(tenant_id, network_id, port_id, format='xml'):
method = 'GET'
path = ("/tenants/%(tenant_id)s/networks/"
"%(network_id)s/ports/%(port_id)s/"
"attachment.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def put_attachment_request(tenant_id, network_id, port_id,
attachment_id, format='xml'):
method = 'PUT'
path = ("/tenants/%(tenant_id)s/networks/"
"%(network_id)s/ports/%(port_id)s/"
"attachment.%(format)s") % locals()
data = {'attachment': {'id': attachment_id}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def delete_attachment_request(tenant_id, network_id, port_id,
attachment_id, format='xml'):
method = 'DELETE'
path = ("/tenants/%(tenant_id)s/networks/"
"%(network_id)s/ports/%(port_id)s/"
"attachment.%(format)s") % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)