Merge "Remove v1 code from quantum-server"
This commit is contained in:
commit
ab9d1b8c7a
@ -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
|
||||
|
@ -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'
|
@ -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
|
||||
|
@ -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"
|
@ -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)
|
@ -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"
|
@ -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"
|
@ -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={})
|
@ -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)
|
@ -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
|
@ -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
|
@ -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)
|
||||
|
@ -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)
|
@ -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
|
@ -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)
|
@ -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)
|
@ -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],
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
@ -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,
|
||||
|
@ -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)
|
@ -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()
|
@ -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)
|
@ -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.
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
@ -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())
|
||||
|
||||
def _set_port_status(self, port, status):
|
||||
if self.target_v2_api:
|
||||
port.status = status
|
||||
else:
|
||||
port.op_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)
|
||||
|
@ -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'),
|
||||
]
|
||||
|
@ -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()
|
@ -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)
|
@ -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
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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()
|
@ -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)
|
@ -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__'
|
@ -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
|
@ -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)
|
52
quantum/plugins/ryu/tests/unit/test_ryu_db.py
Normal file
52
quantum/plugins/ryu/tests/unit/test_ryu_db.py
Normal 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)
|
@ -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)
|
@ -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})
|
||||
|
@ -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)
|
@ -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)
|
@ -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
@ -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)
|
@ -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)
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user