Merge "Midonet plugin decomposition"
This commit is contained in:
commit
f9b1914c1a
|
@ -1,46 +0,0 @@
|
|||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 neutron.agent.linux import dhcp
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.midonet.common import config # noqa
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DhcpNoOpDriver(dhcp.DhcpLocalProcess):
|
||||
|
||||
@classmethod
|
||||
def existing_dhcp_networks(cls, conf, root_helper):
|
||||
"""Return a list of existing networks ids that we have configs for."""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def check_version(cls):
|
||||
"""Execute version checks on DHCP server."""
|
||||
return float(1.0)
|
||||
|
||||
def disable(self, retain_port=False):
|
||||
"""Disable DHCP for this network."""
|
||||
if not retain_port:
|
||||
self.device_manager.destroy(self.network, self.interface_name)
|
||||
self._remove_config_files()
|
||||
|
||||
def reload_allocations(self):
|
||||
"""Force the DHCP server to reload the assignment database."""
|
||||
pass
|
||||
|
||||
def spawn_process(self):
|
||||
pass
|
|
@ -1,42 +0,0 @@
|
|||
# Copyright (C) 2012 Midokura Japan K.K.
|
||||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 oslo.config import cfg
|
||||
|
||||
midonet_opts = [
|
||||
cfg.StrOpt('midonet_uri', default='http://localhost:8080/midonet-api',
|
||||
help=_('MidoNet API server URI.')),
|
||||
cfg.StrOpt('username', default='admin',
|
||||
help=_('MidoNet admin username.')),
|
||||
cfg.StrOpt('password', default='passw0rd',
|
||||
secret=True,
|
||||
help=_('MidoNet admin password.')),
|
||||
cfg.StrOpt('project_id',
|
||||
default='77777777-7777-7777-7777-777777777777',
|
||||
help=_('ID of the project that MidoNet admin user'
|
||||
'belongs to.')),
|
||||
cfg.StrOpt('provider_router_id',
|
||||
help=_('Virtual provider router ID.')),
|
||||
cfg.StrOpt('mode',
|
||||
default='dev',
|
||||
help=_('Operational mode. Internal dev use only.')),
|
||||
cfg.StrOpt('midonet_host_uuid_path',
|
||||
default='/etc/midolman/host_uuid.properties',
|
||||
help=_('Path to midonet host uuid file'))
|
||||
]
|
||||
|
||||
|
||||
cfg.CONF.register_opts(midonet_opts, "MIDONET")
|
|
@ -1,64 +0,0 @@
|
|||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 neutron.common import constants
|
||||
|
||||
|
||||
def subnet_str(cidr):
|
||||
"""Convert the cidr string to x.x.x.x_y format
|
||||
|
||||
:param cidr: CIDR in x.x.x.x/y format
|
||||
"""
|
||||
if cidr is None:
|
||||
return None
|
||||
return cidr.replace("/", "_")
|
||||
|
||||
|
||||
def net_addr(addr):
|
||||
"""Get network address prefix and length from a given address."""
|
||||
if addr is None:
|
||||
return (None, None)
|
||||
nw_addr, nw_len = addr.split('/')
|
||||
nw_len = int(nw_len)
|
||||
return nw_addr, nw_len
|
||||
|
||||
|
||||
def get_ethertype_value(ethertype):
|
||||
"""Convert string representation of ethertype to the numerical."""
|
||||
if ethertype is None:
|
||||
return None
|
||||
mapping = {
|
||||
'ipv4': 0x0800,
|
||||
'ipv6': 0x86DD,
|
||||
'arp': 0x806
|
||||
}
|
||||
return mapping.get(ethertype.lower())
|
||||
|
||||
|
||||
def get_protocol_value(protocol):
|
||||
"""Convert string representation of protocol to the numerical."""
|
||||
if protocol is None:
|
||||
return None
|
||||
|
||||
if isinstance(protocol, int):
|
||||
return protocol
|
||||
|
||||
mapping = {
|
||||
constants.PROTO_NAME_TCP: constants.PROTO_NUM_TCP,
|
||||
constants.PROTO_NAME_UDP: constants.PROTO_NUM_UDP,
|
||||
constants.PROTO_NAME_ICMP: constants.PROTO_NUM_ICMP
|
||||
}
|
||||
return mapping.get(protocol.lower())
|
|
@ -1,691 +0,0 @@
|
|||
# Copyright (C) 2012 Midokura Japan K.K.
|
||||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 midonetclient import exc
|
||||
from webob import exc as w_exc
|
||||
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.i18n import _LW
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.midonet.common import net_util
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_api_error(fn):
|
||||
"""Wrapper for methods that throws custom exceptions."""
|
||||
def wrapped(*args, **kwargs):
|
||||
try:
|
||||
return fn(*args, **kwargs)
|
||||
except (w_exc.HTTPException,
|
||||
exc.MidoApiConnectionError) as ex:
|
||||
raise MidonetApiException(msg=ex)
|
||||
return wrapped
|
||||
|
||||
|
||||
class MidonetResourceNotFound(n_exc.NotFound):
|
||||
message = _('MidoNet %(resource_type)s %(id)s could not be found')
|
||||
|
||||
|
||||
class MidonetApiException(n_exc.NeutronException):
|
||||
message = _("MidoNet API error: %(msg)s")
|
||||
|
||||
|
||||
class MidoClient(object):
|
||||
|
||||
def __init__(self, mido_api):
|
||||
self.mido_api = mido_api
|
||||
|
||||
@classmethod
|
||||
def _fill_dto(cls, dto, fields):
|
||||
for field_name, field_value in fields.iteritems():
|
||||
# We assume the setters are named the
|
||||
# same way as the attributes themselves.
|
||||
try:
|
||||
getattr(dto, field_name)(field_value)
|
||||
except AttributeError:
|
||||
pass
|
||||
return dto
|
||||
|
||||
@classmethod
|
||||
def _create_dto(cls, dto, fields):
|
||||
return cls._fill_dto(dto, fields).create()
|
||||
|
||||
@classmethod
|
||||
def _update_dto(cls, dto, fields):
|
||||
return cls._fill_dto(dto, fields).update()
|
||||
|
||||
@handle_api_error
|
||||
def create_bridge(self, **kwargs):
|
||||
"""Create a new bridge
|
||||
|
||||
:param kwargs: configuration of the new bridge
|
||||
:returns: newly created bridge
|
||||
"""
|
||||
LOG.debug("MidoClient.create_bridge called: "
|
||||
"kwargs=%(kwargs)s", {'kwargs': kwargs})
|
||||
return self._create_dto(self.mido_api.add_bridge(), kwargs)
|
||||
|
||||
@handle_api_error
|
||||
def delete_bridge(self, id):
|
||||
"""Delete a bridge
|
||||
|
||||
:param id: id of the bridge
|
||||
"""
|
||||
LOG.debug("MidoClient.delete_bridge called: id=%(id)s", {'id': id})
|
||||
return self.mido_api.delete_bridge(id)
|
||||
|
||||
@handle_api_error
|
||||
def get_bridge(self, id):
|
||||
"""Get a bridge
|
||||
|
||||
:param id: id of the bridge
|
||||
:returns: requested bridge. None if bridge does not exist.
|
||||
"""
|
||||
LOG.debug("MidoClient.get_bridge called: id=%s", id)
|
||||
try:
|
||||
return self.mido_api.get_bridge(id)
|
||||
except w_exc.HTTPNotFound:
|
||||
raise MidonetResourceNotFound(resource_type='Bridge', id=id)
|
||||
|
||||
@handle_api_error
|
||||
def update_bridge(self, id, **kwargs):
|
||||
"""Update a bridge of the given id with the new fields
|
||||
|
||||
:param id: id of the bridge
|
||||
:param kwargs: the fields to update and their values
|
||||
:returns: bridge object
|
||||
"""
|
||||
LOG.debug("MidoClient.update_bridge called: "
|
||||
"id=%(id)s, kwargs=%(kwargs)s",
|
||||
{'id': id, 'kwargs': kwargs})
|
||||
try:
|
||||
return self._update_dto(self.mido_api.get_bridge(id), kwargs)
|
||||
except w_exc.HTTPNotFound:
|
||||
raise MidonetResourceNotFound(resource_type='Bridge', id=id)
|
||||
|
||||
@handle_api_error
|
||||
def create_dhcp(self, bridge, gateway_ip, cidr, host_rts=None,
|
||||
dns_servers=None):
|
||||
"""Create a new DHCP entry
|
||||
|
||||
:param bridge: bridge object to add dhcp to
|
||||
:param gateway_ip: IP address of gateway
|
||||
:param cidr: subnet represented as x.x.x.x/y
|
||||
:param host_rts: list of routes set in the host
|
||||
:param dns_servers: list of dns servers
|
||||
:returns: newly created dhcp
|
||||
"""
|
||||
LOG.debug("MidoClient.create_dhcp called: bridge=%(bridge)s, "
|
||||
"cidr=%(cidr)s, gateway_ip=%(gateway_ip)s, "
|
||||
"host_rts=%(host_rts)s, dns_servers=%(dns_servers)s",
|
||||
{'bridge': bridge, 'cidr': cidr, 'gateway_ip': gateway_ip,
|
||||
'host_rts': host_rts, 'dns_servers': dns_servers})
|
||||
self.mido_api.add_bridge_dhcp(bridge, gateway_ip, cidr,
|
||||
host_rts=host_rts,
|
||||
dns_nservers=dns_servers)
|
||||
|
||||
@handle_api_error
|
||||
def add_dhcp_host(self, bridge, cidr, ip, mac):
|
||||
"""Add DHCP host entry
|
||||
|
||||
:param bridge: bridge the DHCP is configured for
|
||||
:param cidr: subnet represented as x.x.x.x/y
|
||||
:param ip: IP address
|
||||
:param mac: MAC address
|
||||
"""
|
||||
LOG.debug("MidoClient.add_dhcp_host called: bridge=%(bridge)s, "
|
||||
"cidr=%(cidr)s, ip=%(ip)s, mac=%(mac)s",
|
||||
{'bridge': bridge, 'cidr': cidr, 'ip': ip, 'mac': mac})
|
||||
subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
|
||||
if subnet is None:
|
||||
raise MidonetApiException(msg=_("Tried to add to"
|
||||
"non-existent DHCP"))
|
||||
|
||||
subnet.add_dhcp_host().ip_addr(ip).mac_addr(mac).create()
|
||||
|
||||
@handle_api_error
|
||||
def remove_dhcp_host(self, bridge, cidr, ip, mac):
|
||||
"""Remove DHCP host entry
|
||||
|
||||
:param bridge: bridge the DHCP is configured for
|
||||
:param cidr: subnet represented as x.x.x.x/y
|
||||
:param ip: IP address
|
||||
:param mac: MAC address
|
||||
"""
|
||||
LOG.debug("MidoClient.remove_dhcp_host called: bridge=%(bridge)s, "
|
||||
"cidr=%(cidr)s, ip=%(ip)s, mac=%(mac)s",
|
||||
{'bridge': bridge, 'cidr': cidr, 'ip': ip, 'mac': mac})
|
||||
subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
|
||||
if subnet is None:
|
||||
LOG.warn(_LW("Tried to delete mapping from non-existent subnet"))
|
||||
return
|
||||
|
||||
for dh in subnet.get_dhcp_hosts():
|
||||
if dh.get_mac_addr() == mac and dh.get_ip_addr() == ip:
|
||||
LOG.debug("MidoClient.remove_dhcp_host: Deleting %(dh)r",
|
||||
{"dh": dh})
|
||||
dh.delete()
|
||||
|
||||
@handle_api_error
|
||||
def delete_dhcp_host(self, bridge_id, cidr, ip, mac):
|
||||
"""Delete DHCP host entry
|
||||
|
||||
:param bridge_id: id of the bridge of the DHCP
|
||||
:param cidr: subnet represented as x.x.x.x/y
|
||||
:param ip: IP address
|
||||
:param mac: MAC address
|
||||
"""
|
||||
LOG.debug("MidoClient.delete_dhcp_host called: "
|
||||
"bridge_id=%(bridge_id)s, cidr=%(cidr)s, ip=%(ip)s, "
|
||||
"mac=%(mac)s", {'bridge_id': bridge_id,
|
||||
'cidr': cidr,
|
||||
'ip': ip, 'mac': mac})
|
||||
bridge = self.get_bridge(bridge_id)
|
||||
self.remove_dhcp_host(bridge, net_util.subnet_str(cidr), ip, mac)
|
||||
|
||||
@handle_api_error
|
||||
def delete_dhcp(self, bridge, cidr):
|
||||
"""Delete a DHCP entry
|
||||
|
||||
:param bridge: bridge to remove DHCP from
|
||||
:param cidr: subnet represented as x.x.x.x/y
|
||||
"""
|
||||
LOG.debug("MidoClient.delete_dhcp called: bridge=%(bridge)s, "
|
||||
"cidr=%(cidr)s",
|
||||
{'bridge': bridge, 'cidr': cidr})
|
||||
dhcp_subnets = bridge.get_dhcp_subnets()
|
||||
net_addr, net_len = net_util.net_addr(cidr)
|
||||
if not dhcp_subnets:
|
||||
raise MidonetApiException(
|
||||
msg=_("Tried to delete non-existent DHCP"))
|
||||
for dhcp in dhcp_subnets:
|
||||
if (dhcp.get_subnet_prefix() == net_addr and
|
||||
dhcp.get_subnet_length() == str(net_len)):
|
||||
dhcp.delete()
|
||||
break
|
||||
|
||||
@handle_api_error
|
||||
def delete_port(self, id, delete_chains=False):
|
||||
"""Delete a port
|
||||
|
||||
:param id: id of the port
|
||||
"""
|
||||
LOG.debug("MidoClient.delete_port called: id=%(id)s, "
|
||||
"delete_chains=%(delete_chains)s",
|
||||
{'id': id, 'delete_chains': delete_chains})
|
||||
if delete_chains:
|
||||
self.delete_port_chains(id)
|
||||
|
||||
self.mido_api.delete_port(id)
|
||||
|
||||
@handle_api_error
|
||||
def get_port(self, id):
|
||||
"""Get a port
|
||||
|
||||
:param id: id of the port
|
||||
:returns: requested port. None if it does not exist
|
||||
"""
|
||||
LOG.debug("MidoClient.get_port called: id=%(id)s", {'id': id})
|
||||
try:
|
||||
return self.mido_api.get_port(id)
|
||||
except w_exc.HTTPNotFound:
|
||||
raise MidonetResourceNotFound(resource_type='Port', id=id)
|
||||
|
||||
@handle_api_error
|
||||
def add_bridge_port(self, bridge, **kwargs):
|
||||
"""Add a port on a bridge
|
||||
|
||||
:param bridge: bridge to add a new port to
|
||||
:param kwargs: configuration of the new port
|
||||
:returns: newly created port
|
||||
"""
|
||||
LOG.debug("MidoClient.add_bridge_port called: "
|
||||
"bridge=%(bridge)s, kwargs=%(kwargs)s",
|
||||
{'bridge': bridge, 'kwargs': kwargs})
|
||||
return self._create_dto(self.mido_api.add_bridge_port(bridge), kwargs)
|
||||
|
||||
@handle_api_error
|
||||
def update_port(self, id, **kwargs):
|
||||
"""Update a port of the given id with the new fields
|
||||
|
||||
:param id: id of the port
|
||||
:param kwargs: the fields to update and their values
|
||||
"""
|
||||
LOG.debug("MidoClient.update_port called: "
|
||||
"id=%(id)s, kwargs=%(kwargs)s",
|
||||
{'id': id, 'kwargs': kwargs})
|
||||
try:
|
||||
return self._update_dto(self.mido_api.get_port(id), kwargs)
|
||||
except w_exc.HTTPNotFound:
|
||||
raise MidonetResourceNotFound(resource_type='Port', id=id)
|
||||
|
||||
@handle_api_error
|
||||
def add_router_port(self, router, **kwargs):
|
||||
"""Add a new port to an existing router.
|
||||
|
||||
:param router: router to add a new port to
|
||||
:param kwargs: configuration of the new port
|
||||
:returns: newly created port
|
||||
"""
|
||||
return self._create_dto(self.mido_api.add_router_port(router), kwargs)
|
||||
|
||||
@handle_api_error
|
||||
def create_router(self, **kwargs):
|
||||
"""Create a new router
|
||||
|
||||
:param kwargs: configuration of the new router
|
||||
:returns: newly created router
|
||||
"""
|
||||
LOG.debug("MidoClient.create_router called: "
|
||||
"kwargs=%(kwargs)s", {'kwargs': kwargs})
|
||||
return self._create_dto(self.mido_api.add_router(), kwargs)
|
||||
|
||||
@handle_api_error
|
||||
def delete_router(self, id):
|
||||
"""Delete a router
|
||||
|
||||
:param id: id of the router
|
||||
"""
|
||||
LOG.debug("MidoClient.delete_router called: id=%(id)s", {'id': id})
|
||||
return self.mido_api.delete_router(id)
|
||||
|
||||
@handle_api_error
|
||||
def get_router(self, id):
|
||||
"""Get a router with the given id
|
||||
|
||||
:param id: id of the router
|
||||
:returns: requested router object. None if it does not exist.
|
||||
"""
|
||||
LOG.debug("MidoClient.get_router called: id=%(id)s", {'id': id})
|
||||
try:
|
||||
return self.mido_api.get_router(id)
|
||||
except w_exc.HTTPNotFound:
|
||||
raise MidonetResourceNotFound(resource_type='Router', id=id)
|
||||
|
||||
@handle_api_error
|
||||
def update_router(self, id, **kwargs):
|
||||
"""Update a router of the given id with the new name
|
||||
|
||||
:param id: id of the router
|
||||
:param kwargs: the fields to update and their values
|
||||
:returns: router object
|
||||
"""
|
||||
LOG.debug("MidoClient.update_router called: "
|
||||
"id=%(id)s, kwargs=%(kwargs)s",
|
||||
{'id': id, 'kwargs': kwargs})
|
||||
try:
|
||||
return self._update_dto(self.mido_api.get_router(id), kwargs)
|
||||
except w_exc.HTTPNotFound:
|
||||
raise MidonetResourceNotFound(resource_type='Router', id=id)
|
||||
|
||||
@handle_api_error
|
||||
def delete_route(self, id):
|
||||
return self.mido_api.delete_route(id)
|
||||
|
||||
@handle_api_error
|
||||
def add_dhcp_route_option(self, bridge, cidr, gw_ip, dst_ip):
|
||||
"""Add Option121 route to subnet
|
||||
|
||||
:param bridge: Bridge to add the option route to
|
||||
:param cidr: subnet represented as x.x.x.x/y
|
||||
:param gw_ip: IP address of the next hop
|
||||
:param dst_ip: IP address of the destination, in x.x.x.x/y format
|
||||
"""
|
||||
LOG.debug("MidoClient.add_dhcp_route_option called: "
|
||||
"bridge=%(bridge)s, cidr=%(cidr)s, gw_ip=%(gw_ip)s"
|
||||
"dst_ip=%(dst_ip)s",
|
||||
{"bridge": bridge, "cidr": cidr, "gw_ip": gw_ip,
|
||||
"dst_ip": dst_ip})
|
||||
subnet = bridge.get_dhcp_subnet(net_util.subnet_str(cidr))
|
||||
if subnet is None:
|
||||
raise MidonetApiException(
|
||||
msg=_("Tried to access non-existent DHCP"))
|
||||
prefix, length = dst_ip.split("/")
|
||||
routes = [{'destinationPrefix': prefix, 'destinationLength': length,
|
||||
'gatewayAddr': gw_ip}]
|
||||
cur_routes = subnet.get_opt121_routes()
|
||||
if cur_routes:
|
||||
routes = routes + cur_routes
|
||||
subnet.opt121_routes(routes).update()
|
||||
|
||||
@handle_api_error
|
||||
def link(self, port, peer_id):
|
||||
"""Link a port to a given peerId."""
|
||||
self.mido_api.link(port, peer_id)
|
||||
|
||||
@handle_api_error
|
||||
def delete_port_routes(self, routes, port_id):
|
||||
"""Remove routes whose next hop port is the given port ID."""
|
||||
for route in routes:
|
||||
if route.get_next_hop_port() == port_id:
|
||||
self.mido_api.delete_route(route.get_id())
|
||||
|
||||
@handle_api_error
|
||||
def get_router_routes(self, router_id):
|
||||
"""Get all routes for the given router."""
|
||||
return self.mido_api.get_router_routes(router_id)
|
||||
|
||||
@handle_api_error
|
||||
def unlink(self, port):
|
||||
"""Unlink a port
|
||||
|
||||
:param port: port object
|
||||
"""
|
||||
LOG.debug("MidoClient.unlink called: port=%(port)s",
|
||||
{'port': port})
|
||||
if port.get_peer_id():
|
||||
self.mido_api.unlink(port)
|
||||
else:
|
||||
LOG.warn(_LW("Attempted to unlink a port that was not linked. %s"),
|
||||
port.get_id())
|
||||
|
||||
@handle_api_error
|
||||
def remove_rules_by_property(self, tenant_id, chain_name, key, value):
|
||||
"""Remove all the rules that match the provided key and value."""
|
||||
LOG.debug("MidoClient.remove_rules_by_property called: "
|
||||
"tenant_id=%(tenant_id)s, chain_name=%(chain_name)s"
|
||||
"key=%(key)s, value=%(value)s",
|
||||
{'tenant_id': tenant_id, 'chain_name': chain_name,
|
||||
'key': key, 'value': value})
|
||||
chain = self.get_chain_by_name(tenant_id, chain_name)
|
||||
if chain is None:
|
||||
raise MidonetResourceNotFound(resource_type='Chain',
|
||||
id=chain_name)
|
||||
|
||||
for r in chain.get_rules():
|
||||
if key in r.get_properties():
|
||||
if r.get_properties()[key] == value:
|
||||
self.mido_api.delete_rule(r.get_id())
|
||||
|
||||
@handle_api_error
|
||||
def add_router_chains(self, router, inbound_chain_name,
|
||||
outbound_chain_name):
|
||||
"""Create chains for a new router.
|
||||
|
||||
Creates inbound and outbound chains for the router with the given
|
||||
names, and the new chains are set on the router.
|
||||
|
||||
:param router: router to set chains for
|
||||
:param inbound_chain_name: Name of the inbound chain
|
||||
:param outbound_chain_name: Name of the outbound chain
|
||||
"""
|
||||
LOG.debug("MidoClient.create_router_chains called: "
|
||||
"router=%(router)s, inbound_chain_name=%(in_chain)s, "
|
||||
"outbound_chain_name=%(out_chain)s",
|
||||
{"router": router, "in_chain": inbound_chain_name,
|
||||
"out_chain": outbound_chain_name})
|
||||
tenant_id = router.get_tenant_id()
|
||||
|
||||
inbound_chain = self.mido_api.add_chain().tenant_id(tenant_id).name(
|
||||
inbound_chain_name,).create()
|
||||
outbound_chain = self.mido_api.add_chain().tenant_id(tenant_id).name(
|
||||
outbound_chain_name).create()
|
||||
|
||||
# set chains to in/out filters
|
||||
router.inbound_filter_id(inbound_chain.get_id()).outbound_filter_id(
|
||||
outbound_chain.get_id()).update()
|
||||
return inbound_chain, outbound_chain
|
||||
|
||||
@handle_api_error
|
||||
def delete_router_chains(self, id):
|
||||
"""Deletes chains of a router.
|
||||
|
||||
:param id: router ID to delete chains of
|
||||
"""
|
||||
LOG.debug("MidoClient.delete_router_chains called: "
|
||||
"id=%(id)s", {'id': id})
|
||||
router = self.get_router(id)
|
||||
if (router.get_inbound_filter_id()):
|
||||
self.mido_api.delete_chain(router.get_inbound_filter_id())
|
||||
|
||||
if (router.get_outbound_filter_id()):
|
||||
self.mido_api.delete_chain(router.get_outbound_filter_id())
|
||||
|
||||
@handle_api_error
|
||||
def delete_port_chains(self, id):
|
||||
"""Deletes chains of a port.
|
||||
|
||||
:param id: port ID to delete chains of
|
||||
"""
|
||||
LOG.debug("MidoClient.delete_port_chains called: "
|
||||
"id=%(id)s", {'id': id})
|
||||
port = self.get_port(id)
|
||||
if (port.get_inbound_filter_id()):
|
||||
self.mido_api.delete_chain(port.get_inbound_filter_id())
|
||||
|
||||
if (port.get_outbound_filter_id()):
|
||||
self.mido_api.delete_chain(port.get_outbound_filter_id())
|
||||
|
||||
@handle_api_error
|
||||
def get_link_port(self, router, peer_router_id):
|
||||
"""Setup a route on the router to the next hop router."""
|
||||
LOG.debug("MidoClient.get_link_port called: "
|
||||
"router=%(router)s, peer_router_id=%(peer_router_id)s",
|
||||
{'router': router, 'peer_router_id': peer_router_id})
|
||||
# Find the port linked between the two routers
|
||||
link_port = None
|
||||
for p in router.get_peer_ports():
|
||||
if p.get_device_id() == peer_router_id:
|
||||
link_port = p
|
||||
break
|
||||
return link_port
|
||||
|
||||
@handle_api_error
|
||||
def add_router_route(self, router, type='Normal',
|
||||
src_network_addr=None, src_network_length=None,
|
||||
dst_network_addr=None, dst_network_length=None,
|
||||
next_hop_port=None, next_hop_gateway=None,
|
||||
weight=100):
|
||||
"""Setup a route on the router."""
|
||||
return self.mido_api.add_router_route(
|
||||
router, type=type, src_network_addr=src_network_addr,
|
||||
src_network_length=src_network_length,
|
||||
dst_network_addr=dst_network_addr,
|
||||
dst_network_length=dst_network_length,
|
||||
next_hop_port=next_hop_port, next_hop_gateway=next_hop_gateway,
|
||||
weight=weight)
|
||||
|
||||
@handle_api_error
|
||||
def add_static_nat(self, tenant_id, chain_name, from_ip, to_ip, port_id,
|
||||
nat_type='dnat', **kwargs):
|
||||
"""Add a static NAT entry
|
||||
|
||||
:param tenant_id: owner fo the chain to add a NAT to
|
||||
:param chain_name: name of the chain to add a NAT to
|
||||
:param from_ip: IP to translate from
|
||||
:param from_ip: IP to translate from
|
||||
:param to_ip: IP to translate to
|
||||
:param port_id: port to match on
|
||||
:param nat_type: 'dnat' or 'snat'
|
||||
"""
|
||||
LOG.debug("MidoClient.add_static_nat called: "
|
||||
"tenant_id=%(tenant_id)s, chain_name=%(chain_name)s, "
|
||||
"from_ip=%(from_ip)s, to_ip=%(to_ip)s, "
|
||||
"port_id=%(port_id)s, nat_type=%(nat_type)s",
|
||||
{'tenant_id': tenant_id, 'chain_name': chain_name,
|
||||
'from_ip': from_ip, 'to_ip': to_ip,
|
||||
'port_id': port_id, 'nat_type': nat_type})
|
||||
if nat_type not in ['dnat', 'snat']:
|
||||
raise ValueError(_("Invalid NAT type passed in %s") % nat_type)
|
||||
|
||||
chain = self.get_chain_by_name(tenant_id, chain_name)
|
||||
nat_targets = []
|
||||
nat_targets.append(
|
||||
{'addressFrom': to_ip, 'addressTo': to_ip,
|
||||
'portFrom': 0, 'portTo': 0})
|
||||
|
||||
rule = chain.add_rule().type(nat_type).flow_action('accept').position(
|
||||
1).nat_targets(nat_targets).properties(kwargs)
|
||||
|
||||
if nat_type == 'dnat':
|
||||
rule = rule.nw_dst_address(from_ip).nw_dst_length(32).in_ports(
|
||||
[port_id])
|
||||
else:
|
||||
rule = rule.nw_src_address(from_ip).nw_src_length(32).out_ports(
|
||||
[port_id])
|
||||
|
||||
return rule.create()
|
||||
|
||||
@handle_api_error
|
||||
def add_dynamic_snat(self, tenant_id, pre_chain_name, post_chain_name,
|
||||
snat_ip, port_id, **kwargs):
|
||||
"""Add SNAT masquerading rule
|
||||
|
||||
MidoNet requires two rules on the router, one to do NAT to a range of
|
||||
ports, and another to retrieve back the original IP in the return
|
||||
flow.
|
||||
"""
|
||||
pre_chain = self.get_chain_by_name(tenant_id, pre_chain_name)
|
||||
post_chain = self.get_chain_by_name(tenant_id, post_chain_name)
|
||||
|
||||
pre_chain.add_rule().nw_dst_address(snat_ip).nw_dst_length(
|
||||
32).type('rev_snat').flow_action('accept').in_ports(
|
||||
[port_id]).properties(kwargs).position(1).create()
|
||||
|
||||
nat_targets = []
|
||||
nat_targets.append(
|
||||
{'addressFrom': snat_ip, 'addressTo': snat_ip,
|
||||
'portFrom': 1, 'portTo': 65535})
|
||||
|
||||
post_chain.add_rule().type('snat').flow_action(
|
||||
'accept').nat_targets(nat_targets).out_ports(
|
||||
[port_id]).properties(kwargs).position(1).create()
|
||||
|
||||
@handle_api_error
|
||||
def remove_static_route(self, router, ip):
|
||||
"""Remove static route for the IP
|
||||
|
||||
:param router: next hop router to remove the routes to
|
||||
:param ip: IP address of the route to remove
|
||||
"""
|
||||
LOG.debug("MidoClient.remote_static_route called: "
|
||||
"router=%(router)s, ip=%(ip)s",
|
||||
{'router': router, 'ip': ip})
|
||||
for r in router.get_routes():
|
||||
if (r.get_dst_network_addr() == ip and
|
||||
r.get_dst_network_length() == 32):
|
||||
self.mido_api.delete_route(r.get_id())
|
||||
|
||||
@handle_api_error
|
||||
def update_port_chains(self, port, inbound_chain_id, outbound_chain_id):
|
||||
"""Bind inbound and outbound chains to the port."""
|
||||
LOG.debug("MidoClient.update_port_chains called: port=%(port)s"
|
||||
"inbound_chain_id=%(inbound_chain_id)s, "
|
||||
"outbound_chain_id=%(outbound_chain_id)s",
|
||||
{"port": port, "inbound_chain_id": inbound_chain_id,
|
||||
"outbound_chain_id": outbound_chain_id})
|
||||
port.inbound_filter_id(inbound_chain_id).outbound_filter_id(
|
||||
outbound_chain_id).update()
|
||||
|
||||
@handle_api_error
|
||||
def create_chain(self, tenant_id, name):
|
||||
"""Create a new chain."""
|
||||
LOG.debug("MidoClient.create_chain called: tenant_id=%(tenant_id)s "
|
||||
" name=%(name)s", {"tenant_id": tenant_id, "name": name})
|
||||
return self.mido_api.add_chain().tenant_id(tenant_id).name(
|
||||
name).create()
|
||||
|
||||
@handle_api_error
|
||||
def delete_chain(self, id):
|
||||
"""Delete chain matching the ID."""
|
||||
LOG.debug("MidoClient.delete_chain called: id=%(id)s", {"id": id})
|
||||
self.mido_api.delete_chain(id)
|
||||
|
||||
@handle_api_error
|
||||
def delete_chains_by_names(self, tenant_id, names):
|
||||
"""Delete chains matching the names given for a tenant."""
|
||||
LOG.debug("MidoClient.delete_chains_by_names called: "
|
||||
"tenant_id=%(tenant_id)s names=%(names)s ",
|
||||
{"tenant_id": tenant_id, "names": names})
|
||||
chains = self.mido_api.get_chains({'tenant_id': tenant_id})
|
||||
for c in chains:
|
||||
if c.get_name() in names:
|
||||
self.mido_api.delete_chain(c.get_id())
|
||||
|
||||
@handle_api_error
|
||||
def get_chain_by_name(self, tenant_id, name):
|
||||
"""Get the chain by its name."""
|
||||
LOG.debug("MidoClient.get_chain_by_name called: "
|
||||
"tenant_id=%(tenant_id)s name=%(name)s ",
|
||||
{"tenant_id": tenant_id, "name": name})
|
||||
for c in self.mido_api.get_chains({'tenant_id': tenant_id}):
|
||||
if c.get_name() == name:
|
||||
return c
|
||||
return None
|
||||
|
||||
@handle_api_error
|
||||
def get_port_group_by_name(self, tenant_id, name):
|
||||
"""Get the port group by name."""
|
||||
LOG.debug("MidoClient.get_port_group_by_name called: "
|
||||
"tenant_id=%(tenant_id)s name=%(name)s ",
|
||||
{"tenant_id": tenant_id, "name": name})
|
||||
for p in self.mido_api.get_port_groups({'tenant_id': tenant_id}):
|
||||
if p.get_name() == name:
|
||||
return p
|
||||
return None
|
||||
|
||||
@handle_api_error
|
||||
def create_port_group(self, tenant_id, name):
|
||||
"""Create a port group
|
||||
|
||||
Create a new port group for a given name and ID.
|
||||
"""
|
||||
LOG.debug("MidoClient.create_port_group called: "
|
||||
"tenant_id=%(tenant_id)s name=%(name)s",
|
||||
{"tenant_id": tenant_id, "name": name})
|
||||
return self.mido_api.add_port_group().tenant_id(tenant_id).name(
|
||||
name).create()
|
||||
|
||||
@handle_api_error
|
||||
def delete_port_group_by_name(self, tenant_id, name):
|
||||
"""Delete port group matching the name given for a tenant."""
|
||||
LOG.debug("MidoClient.delete_port_group_by_name called: "
|
||||
"tenant_id=%(tenant_id)s name=%(name)s ",
|
||||
{"tenant_id": tenant_id, "name": name})
|
||||
pgs = self.mido_api.get_port_groups({'tenant_id': tenant_id})
|
||||
for pg in pgs:
|
||||
if pg.get_name() == name:
|
||||
LOG.debug("Deleting pg %(id)s", {"id": pg.get_id()})
|
||||
self.mido_api.delete_port_group(pg.get_id())
|
||||
|
||||
@handle_api_error
|
||||
def add_port_to_port_group_by_name(self, tenant_id, name, port_id):
|
||||
"""Add a port to a port group with the given name."""
|
||||
LOG.debug("MidoClient.add_port_to_port_group_by_name called: "
|
||||
"tenant_id=%(tenant_id)s name=%(name)s "
|
||||
"port_id=%(port_id)s",
|
||||
{"tenant_id": tenant_id, "name": name, "port_id": port_id})
|
||||
pg = self.get_port_group_by_name(tenant_id, name)
|
||||
if pg is None:
|
||||
raise MidonetResourceNotFound(resource_type='PortGroup', id=name)
|
||||
|
||||
pg = pg.add_port_group_port().port_id(port_id).create()
|
||||
return pg
|
||||
|
||||
@handle_api_error
|
||||
def remove_port_from_port_groups(self, port_id):
|
||||
"""Remove a port binding from all the port groups."""
|
||||
LOG.debug("MidoClient.remove_port_from_port_groups called: "
|
||||
"port_id=%(port_id)s", {"port_id": port_id})
|
||||
port = self.get_port(port_id)
|
||||
for pg in port.get_port_groups():
|
||||
pg.delete()
|
||||
|
||||
@handle_api_error
|
||||
def add_chain_rule(self, chain, action='accept', **kwargs):
|
||||
"""Create a new accept chain rule."""
|
||||
self.mido_api.add_chain_rule(chain, action, **kwargs)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
|||
neutron-plugin-midonet
|
|
@ -1,16 +0,0 @@
|
|||
[midonet]
|
||||
|
||||
# MidoNet API server URI
|
||||
midonet_uri = http://localhost:8080/midonet-api
|
||||
|
||||
# MidoNet admin username
|
||||
username = admin
|
||||
|
||||
# MidoNet admin password
|
||||
password = passw0rd
|
||||
|
||||
# Virtual provider router ID
|
||||
provider_router_id = 00112233-0011-0011-0011-001122334455
|
||||
|
||||
# Virtual metadata router ID
|
||||
metadata_router_id = ffeeddcc-ffee-ffee-ffee-ffeeddccbbaa
|
|
@ -1,261 +0,0 @@
|
|||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 mock
|
||||
import uuid
|
||||
|
||||
|
||||
def get_bridge_mock(id=None, **kwargs):
|
||||
if id is None:
|
||||
id = str(uuid.uuid4())
|
||||
|
||||
bridge = mock.Mock()
|
||||
bridge.get_id.return_value = id
|
||||
bridge.get_tenant_id.return_value = kwargs.get("tenant_id", "test-tenant")
|
||||
bridge.get_name.return_value = kwargs.get("name", "net")
|
||||
bridge.get_ports.return_value = []
|
||||
bridge.get_peer_ports.return_value = []
|
||||
bridge.get_admin_state_up.return_value = kwargs.get("admin_state_up", True)
|
||||
return bridge
|
||||
|
||||
|
||||
def get_bridge_port_mock(id=None, bridge_id=None, **kwargs):
|
||||
if id is None:
|
||||
id = str(uuid.uuid4())
|
||||
if bridge_id is None:
|
||||
bridge_id = str(uuid.uuid4())
|
||||
|
||||
port = mock.Mock()
|
||||
port.get_id.return_value = id
|
||||
port.get_bridge_id.return_value = bridge_id
|
||||
port.get_admin_state_up.return_value = kwargs.get("admin_state_up", True)
|
||||
port.get_type.return_value = "Bridge"
|
||||
port.create.return_value = port
|
||||
return port
|
||||
|
||||
|
||||
def get_chain_mock(id=None, tenant_id='test-tenant', name='chain',
|
||||
rules=None):
|
||||
if id is None:
|
||||
id = str(uuid.uuid4())
|
||||
|
||||
if rules is None:
|
||||
rules = []
|
||||
|
||||
chain = mock.Mock()
|
||||
chain.get_id.return_value = id
|
||||
chain.get_tenant_id.return_value = tenant_id
|
||||
chain.get_name.return_value = name
|
||||
chain.get_rules.return_value = rules
|
||||
return chain
|
||||
|
||||
|
||||
def get_port_group_mock(id=None, tenant_id='test-tenant', name='pg'):
|
||||
if id is None:
|
||||
id = str(uuid.uuid4())
|
||||
|
||||
port_group = mock.Mock()
|
||||
port_group.get_id.return_value = id
|
||||
port_group.get_tenant_id.return_value = tenant_id
|
||||
port_group.get_name.return_value = name
|
||||
return port_group
|
||||
|
||||
|
||||
def get_router_mock(id=None, **kwargs):
|
||||
if id is None:
|
||||
id = str(uuid.uuid4())
|
||||
|
||||
router = mock.Mock()
|
||||
router.get_id.return_value = id
|
||||
router.get_tenant_id.return_value = kwargs.get("tenant_id", "test-tenant")
|
||||
router.get_name.return_value = kwargs.get("name", "router")
|
||||
router.get_ports.return_value = []
|
||||
router.get_peer_ports.return_value = []
|
||||
router.get_routes.return_value = []
|
||||
router.get_admin_state_up.return_value = kwargs.get("admin_state_up", True)
|
||||
return router
|
||||
|
||||
|
||||
def get_rule_mock(id=None, chain_id=None, properties=None):
|
||||
if id is None:
|
||||
id = str(uuid.uuid4())
|
||||
|
||||
if chain_id is None:
|
||||
chain_id = str(uuid.uuid4())
|
||||
|
||||
if properties is None:
|
||||
properties = {}
|
||||
|
||||
rule = mock.Mock()
|
||||
rule.get_id.return_value = id
|
||||
rule.get_chain_id.return_value = chain_id
|
||||
rule.get_properties.return_value = properties
|
||||
return rule
|
||||
|
||||
|
||||
def get_subnet_mock(bridge_id=None, gateway_ip='10.0.0.1',
|
||||
subnet_prefix='10.0.0.0', subnet_len=int(24)):
|
||||
if bridge_id is None:
|
||||
bridge_id = str(uuid.uuid4())
|
||||
|
||||
subnet = mock.Mock()
|
||||
subnet.get_id.return_value = subnet_prefix + '/' + str(subnet_len)
|
||||
subnet.get_bridge_id.return_value = bridge_id
|
||||
subnet.get_default_gateway.return_value = gateway_ip
|
||||
subnet.get_subnet_prefix.return_value = subnet_prefix
|
||||
subnet.get_subnet_length.return_value = subnet_len
|
||||
return subnet
|
||||
|
||||
|
||||
class MidonetLibMockConfig(object):
|
||||
|
||||
def __init__(self, inst):
|
||||
self.inst = inst
|
||||
|
||||
def _create_bridge(self, **kwargs):
|
||||
return get_bridge_mock(**kwargs)
|
||||
|
||||
def _create_router(self, **kwargs):
|
||||
return get_router_mock(**kwargs)
|
||||
|
||||
def _create_subnet(self, bridge, gateway_ip, subnet_prefix, subnet_len):
|
||||
return get_subnet_mock(bridge.get_id(), gateway_ip=gateway_ip,
|
||||
subnet_prefix=subnet_prefix,
|
||||
subnet_len=subnet_len)
|
||||
|
||||
def _add_bridge_port(self, bridge, **kwargs):
|
||||
return get_bridge_port_mock(bridge_id=bridge.get_id(), **kwargs)
|
||||
|
||||
def _get_bridge(self, id):
|
||||
return get_bridge_mock(id=id)
|
||||
|
||||
def _get_port(self, id):
|
||||
return get_bridge_port_mock(id=id)
|
||||
|
||||
def _get_router(self, id):
|
||||
return get_router_mock(id=id)
|
||||
|
||||
def _update_bridge(self, id, **kwargs):
|
||||
return get_bridge_mock(id=id, **kwargs)
|
||||
|
||||
def setup(self):
|
||||
# Bridge methods side effects
|
||||
self.inst.create_bridge.side_effect = self._create_bridge
|
||||
self.inst.get_bridge.side_effect = self._get_bridge
|
||||
self.inst.update_bridge.side_effect = self._update_bridge
|
||||
|
||||
# Subnet methods side effects
|
||||
self.inst.create_subnet.side_effect = self._create_subnet
|
||||
|
||||
# Port methods side effects
|
||||
ex_bp = self.inst.add_bridge_port
|
||||
ex_bp.side_effect = self._add_bridge_port
|
||||
self.inst.get_port.side_effect = self._get_port
|
||||
|
||||
# Router methods side effects
|
||||
self.inst.create_router.side_effect = self._create_router
|
||||
self.inst.get_router.side_effect = self._get_router
|
||||
|
||||
|
||||
class MidoClientMockConfig(object):
|
||||
|
||||
def __init__(self, inst):
|
||||
self.inst = inst
|
||||
self.chains_in = None
|
||||
self.port_groups_in = None
|
||||
self.chains_out = None
|
||||
self.rules_out = None
|
||||
self.port_groups_out = None
|
||||
|
||||
def _get_query_tenant_id(self, query):
|
||||
if query is not None and query['tenant_id']:
|
||||
tenant_id = query['tenant_id']
|
||||
else:
|
||||
tenant_id = 'test-tenant'
|
||||
return tenant_id
|
||||
|
||||
def _get_bridge(self, id):
|
||||
return get_bridge_mock(id=id)
|
||||
|
||||
def _get_chain(self, id, query=None):
|
||||
if not self.chains_in:
|
||||
return []
|
||||
|
||||
tenant_id = self._get_query_tenant_id(query)
|
||||
for chain in self.chains_in:
|
||||
chain_id = chain['id']
|
||||
if chain_id is id:
|
||||
rule_mocks = []
|
||||
if 'rules' in chain:
|
||||
for rule in chain['rules']:
|
||||
rule_mocks.append(
|
||||
get_rule_mock(id=rule['id'],
|
||||
chain_id=id,
|
||||
properties=rule['properties']))
|
||||
|
||||
return get_chain_mock(id=chain_id, name=chain['name'],
|
||||
tenant_id=tenant_id, rules=rule_mocks)
|
||||
return None
|
||||
|
||||
def _get_chains(self, query=None):
|
||||
if not self.chains_in:
|
||||
return []
|
||||
|
||||
tenant_id = self._get_query_tenant_id(query)
|
||||
self.chains_out = []
|
||||
self.rules_out = []
|
||||
for chain in self.chains_in:
|
||||
chain_id = chain['id']
|
||||
|
||||
rule_mocks = []
|
||||
if 'rules' in chain:
|
||||
for rule in chain['rules']:
|
||||
rule_mocks.append(
|
||||
get_rule_mock(id=rule['id'],
|
||||
chain_id=id,
|
||||
properties=rule['properties']))
|
||||
self.rules_out += rule_mocks
|
||||
|
||||
self.chains_out.append(get_chain_mock(id=chain_id,
|
||||
name=chain['name'],
|
||||
tenant_id=tenant_id,
|
||||
rules=rule_mocks))
|
||||
return self.chains_out
|
||||
|
||||
def _get_port_groups(self, query=None):
|
||||
if not self.port_groups_in:
|
||||
return []
|
||||
|
||||
tenant_id = self._get_query_tenant_id(query)
|
||||
self.port_groups_out = []
|
||||
for port_group in self.port_groups_in:
|
||||
self.port_groups_out.append(get_port_group_mock(
|
||||
id=port_group['id'], name=port_group['name'],
|
||||
tenant_id=tenant_id))
|
||||
return self.port_groups_out
|
||||
|
||||
def _get_router(self, id):
|
||||
return get_router_mock(id=id)
|
||||
|
||||
def _add_bridge_port(self, bridge):
|
||||
return get_bridge_port_mock(bridge_id=bridge.get_id())
|
||||
|
||||
def setup(self):
|
||||
self.inst.get_bridge.side_effect = self._get_bridge
|
||||
self.inst.get_chains.side_effect = self._get_chains
|
||||
self.inst.get_chain.side_effect = self._get_chain
|
||||
self.inst.get_port_groups.side_effect = self._get_port_groups
|
||||
self.inst.get_router.side_effect = self._get_router
|
||||
self.inst.add_bridge_port.side_effect = self._add_bridge_port
|
|
@ -1,52 +0,0 @@
|
|||
# Copyright (C) 2012 Midokura Japan K.K.
|
||||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 mock
|
||||
|
||||
from neutron.agent.common import config
|
||||
from neutron.agent.linux import dhcp
|
||||
from neutron.common import config as base_config
|
||||
import neutron.plugins.midonet.agent.midonet_driver as driver
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class FakeNetwork(object):
|
||||
id = 'aaaabbbb-cccc-dddd-eeee-ffff00001111'
|
||||
namespace = 'qdhcp-ns'
|
||||
|
||||
|
||||
class TestDhcpNoOpDriver(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestDhcpNoOpDriver, self).setUp()
|
||||
self.conf = config.setup_conf()
|
||||
config.register_interface_driver_opts_helper(self.conf)
|
||||
self.conf.register_opts(base_config.core_opts)
|
||||
self.conf.register_opts(dhcp.OPTS)
|
||||
self.conf.enable_isolated_metadata = True
|
||||
self.conf.use_namespaces = True
|
||||
instance = mock.patch("neutron.agent.linux.dhcp.DeviceManager")
|
||||
self.mock_mgr = instance.start()
|
||||
self.makedirs = mock.patch('os.makedirs').start()
|
||||
|
||||
def test_disable_no_retain_port(self):
|
||||
dhcp_driver = driver.DhcpNoOpDriver(self.conf, FakeNetwork())
|
||||
dhcp_driver.disable(retain_port=False)
|
||||
self.assertTrue(self.mock_mgr.return_value.destroy.called)
|
||||
|
||||
def test_disable_retain_port(self):
|
||||
dhcp_driver = driver.DhcpNoOpDriver(self.conf, FakeNetwork())
|
||||
dhcp_driver.disable(retain_port=True)
|
||||
self.assertFalse(self.mock_mgr.return_value.destroy.called)
|
|
@ -1,188 +0,0 @@
|
|||
# Copyright (C) 2012 Midokura Japan K.K.
|
||||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 sys
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
import webob.exc as w_exc
|
||||
|
||||
from neutron.openstack.common import uuidutils
|
||||
with mock.patch.dict(sys.modules, {'midonetclient': mock.Mock()}):
|
||||
from neutron.plugins.midonet import midonet_lib
|
||||
import neutron.tests.unit.midonet.mock_lib as mock_lib
|
||||
|
||||
|
||||
def _create_test_chain(id, name, tenant_id):
|
||||
return {'id': id, 'name': name, 'tenant_id': tenant_id}
|
||||
|
||||
|
||||
def _create_test_port_group(id, name, tenant_id):
|
||||
return {"id": id, "name": name, "tenant_id": tenant_id}
|
||||
|
||||
|
||||
class MidoClientTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(MidoClientTestCase, self).setUp()
|
||||
self._tenant_id = 'test-tenant'
|
||||
self.mock_api = mock.Mock()
|
||||
self.mock_api_cfg = mock_lib.MidoClientMockConfig(self.mock_api)
|
||||
self.mock_api_cfg.setup()
|
||||
self.client = midonet_lib.MidoClient(self.mock_api)
|
||||
|
||||
def test_delete_chains_by_names(self):
|
||||
|
||||
tenant_id = uuidutils.generate_uuid()
|
||||
chain1_id = uuidutils.generate_uuid()
|
||||
chain1 = _create_test_chain(chain1_id, "chain1", tenant_id)
|
||||
|
||||
chain2_id = uuidutils.generate_uuid()
|
||||
chain2 = _create_test_chain(chain2_id, "chain2", tenant_id)
|
||||
|
||||
calls = [mock.call.delete_chain(chain1_id),
|
||||
mock.call.delete_chain(chain2_id)]
|
||||
self.mock_api_cfg.chains_in = [chain2, chain1]
|
||||
self.client.delete_chains_by_names(tenant_id, ["chain1", "chain2"])
|
||||
|
||||
self.mock_api.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_delete_port_group_by_name(self):
|
||||
|
||||
tenant_id = uuidutils.generate_uuid()
|
||||
pg1_id = uuidutils.generate_uuid()
|
||||
pg1 = _create_test_port_group(pg1_id, "pg1", tenant_id)
|
||||
pg2_id = uuidutils.generate_uuid()
|
||||
pg2 = _create_test_port_group(pg2_id, "pg2", tenant_id)
|
||||
|
||||
self.mock_api_cfg.port_groups_in = [pg1, pg2]
|
||||
self.client.delete_port_group_by_name(tenant_id, "pg1")
|
||||
self.mock_api.delete_port_group.assert_called_once_with(pg1_id)
|
||||
|
||||
def test_create_dhcp(self):
|
||||
|
||||
bridge = mock.Mock()
|
||||
|
||||
gateway_ip = "192.168.1.1"
|
||||
cidr = "192.168.1.0/24"
|
||||
host_rts = [{'destination': '10.0.0.0/24', 'nexthop': '10.0.0.1'},
|
||||
{'destination': '10.0.1.0/24', 'nexthop': '10.0.1.1'}]
|
||||
dns_servers = ["8.8.8.8", "8.8.4.4"]
|
||||
|
||||
dhcp_call = mock.call.add_bridge_dhcp(bridge, gateway_ip, cidr,
|
||||
host_rts=host_rts,
|
||||
dns_nservers=dns_servers)
|
||||
|
||||
self.client.create_dhcp(bridge, gateway_ip, cidr, host_rts=host_rts,
|
||||
dns_servers=dns_servers)
|
||||
self.mock_api.assert_has_calls([dhcp_call])
|
||||
|
||||
def test_delete_dhcp(self):
|
||||
|
||||
bridge = mock.Mock()
|
||||
subnet1 = mock.Mock()
|
||||
subnet1.get_subnet_prefix.return_value = "10.0.0.0"
|
||||
subnet1.get_subnet_length.return_value = "16"
|
||||
subnet2 = mock.Mock()
|
||||
subnet2.get_subnet_prefix.return_value = "10.0.0.0"
|
||||
subnet2.get_subnet_length.return_value = "24"
|
||||
subnets = mock.MagicMock(return_value=[subnet1, subnet2])
|
||||
bridge.get_dhcp_subnets.side_effect = subnets
|
||||
self.client.delete_dhcp(bridge, "10.0.0.0/24")
|
||||
bridge.assert_has_calls(mock.call.get_dhcp_subnets)
|
||||
self.assertFalse(subnet1.delete.called)
|
||||
subnet2.delete.assert_called_once_with()
|
||||
|
||||
def test_add_dhcp_host(self):
|
||||
|
||||
bridge = mock.Mock()
|
||||
dhcp_subnet_call = mock.call.get_dhcp_subnet("10.0.0.0_24")
|
||||
ip_addr_call = dhcp_subnet_call.add_dhcp_host().ip_addr("10.0.0.10")
|
||||
mac_addr_call = ip_addr_call.mac_addr("2A:DB:6B:8C:19:99")
|
||||
calls = [dhcp_subnet_call, ip_addr_call, mac_addr_call,
|
||||
mac_addr_call.create()]
|
||||
|
||||
self.client.add_dhcp_host(bridge, "10.0.0.0/24", "10.0.0.10",
|
||||
"2A:DB:6B:8C:19:99")
|
||||
bridge.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_add_dhcp_route_option(self):
|
||||
|
||||
bridge = mock.Mock()
|
||||
subnet = bridge.get_dhcp_subnet.return_value
|
||||
subnet.get_opt121_routes.return_value = None
|
||||
dhcp_subnet_call = mock.call.get_dhcp_subnet("10.0.0.0_24")
|
||||
dst_ip = "10.0.0.3/24"
|
||||
gw_ip = "10.0.0.1"
|
||||
prefix, length = dst_ip.split("/")
|
||||
routes = [{'destinationPrefix': prefix, 'destinationLength': length,
|
||||
'gatewayAddr': gw_ip}]
|
||||
opt121_routes_call = dhcp_subnet_call.opt121_routes(routes)
|
||||
calls = [dhcp_subnet_call, opt121_routes_call,
|
||||
opt121_routes_call.update()]
|
||||
|
||||
self.client.add_dhcp_route_option(bridge, "10.0.0.0/24",
|
||||
gw_ip, dst_ip)
|
||||
bridge.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_get_router_error(self):
|
||||
self.mock_api.get_router.side_effect = w_exc.HTTPInternalServerError()
|
||||
self.assertRaises(midonet_lib.MidonetApiException,
|
||||
self.client.get_router, uuidutils.generate_uuid())
|
||||
|
||||
def test_get_router_not_found(self):
|
||||
self.mock_api.get_router.side_effect = w_exc.HTTPNotFound()
|
||||
self.assertRaises(midonet_lib.MidonetResourceNotFound,
|
||||
self.client.get_router, uuidutils.generate_uuid())
|
||||
|
||||
def test_get_bridge_error(self):
|
||||
self.mock_api.get_bridge.side_effect = w_exc.HTTPInternalServerError()
|
||||
self.assertRaises(midonet_lib.MidonetApiException,
|
||||
self.client.get_bridge, uuidutils.generate_uuid())
|
||||
|
||||
def test_get_bridge_not_found(self):
|
||||
self.mock_api.get_bridge.side_effect = w_exc.HTTPNotFound()
|
||||
self.assertRaises(midonet_lib.MidonetResourceNotFound,
|
||||
self.client.get_bridge, uuidutils.generate_uuid())
|
||||
|
||||
def test_get_bridge(self):
|
||||
bridge_id = uuidutils.generate_uuid()
|
||||
|
||||
bridge = self.client.get_bridge(bridge_id)
|
||||
|
||||
self.assertIsNotNone(bridge)
|
||||
self.assertEqual(bridge.get_id(), bridge_id)
|
||||
self.assertTrue(bridge.get_admin_state_up())
|
||||
|
||||
def test_add_bridge_port(self):
|
||||
bridge_id = uuidutils.generate_uuid()
|
||||
|
||||
bridge = self.client.get_bridge(bridge_id)
|
||||
|
||||
self.assertIsNotNone(bridge)
|
||||
|
||||
port = self.client.add_bridge_port(bridge)
|
||||
|
||||
self.assertEqual(bridge.get_id(), port.get_bridge_id())
|
||||
self.assertTrue(port.get_admin_state_up())
|
||||
|
||||
def test_get_router(self):
|
||||
router_id = uuidutils.generate_uuid()
|
||||
|
||||
router = self.client.get_router(router_id)
|
||||
|
||||
self.assertIsNotNone(router)
|
||||
self.assertEqual(router.get_id(), router_id)
|
||||
self.assertTrue(router.get_admin_state_up())
|
|
@ -1,249 +0,0 @@
|
|||
# Copyright (C) 2012 Midokura Japan K.K.
|
||||
# Copyright (C) 2013 Midokura PTE LTD
|
||||
# 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 mock
|
||||
import os
|
||||
import sys
|
||||
|
||||
import neutron.common.test_lib as test_lib
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.tests.unit import _test_extension_portbindings as test_bindings
|
||||
import neutron.tests.unit.midonet.mock_lib as mock_lib
|
||||
import neutron.tests.unit.test_db_plugin as test_plugin
|
||||
import neutron.tests.unit.test_extension_security_group as sg
|
||||
import neutron.tests.unit.test_l3_plugin as test_l3_plugin
|
||||
|
||||
MIDOKURA_PKG_PATH = "neutron.plugins.midonet.plugin"
|
||||
MIDONET_PLUGIN_NAME = ('%s.MidonetPluginV2' % MIDOKURA_PKG_PATH)
|
||||
|
||||
|
||||
class MidonetPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||
|
||||
def setUp(self,
|
||||
plugin=MIDONET_PLUGIN_NAME,
|
||||
ext_mgr=None,
|
||||
service_plugins=None):
|
||||
self.mock_api = mock.patch(
|
||||
'neutron.plugins.midonet.midonet_lib.MidoClient')
|
||||
etc_path = os.path.join(os.path.dirname(__file__), 'etc')
|
||||
test_lib.test_config['config_files'] = [os.path.join(
|
||||
etc_path, 'midonet.ini.test')]
|
||||
|
||||
p = mock.patch.dict(sys.modules, {'midonetclient': mock.Mock()})
|
||||
p.start()
|
||||
# dict patches must be explicitly stopped
|
||||
self.addCleanup(p.stop)
|
||||
self.instance = self.mock_api.start()
|
||||
mock_cfg = mock_lib.MidonetLibMockConfig(self.instance.return_value)
|
||||
mock_cfg.setup()
|
||||
|
||||
self.midoclient_mock = mock.MagicMock()
|
||||
self.midoclient_mock.midonetclient.neutron.client.return_value = True
|
||||
modules = {
|
||||
'midonetclient': self.midoclient_mock,
|
||||
'midonetclient.neutron': self.midoclient_mock.neutron,
|
||||
'midonetclient.neutron.client': self.midoclient_mock.client,
|
||||
}
|
||||
|
||||
self.module_patcher = mock.patch.dict('sys.modules', modules)
|
||||
self.module_patcher.start()
|
||||
self.addCleanup(self.module_patcher.stop)
|
||||
|
||||
# import midonetclient here because it needs proper mock objects to be
|
||||
# assigned to this module first. 'midoclient_mock' object is the
|
||||
# mock object used for this module.
|
||||
from midonetclient.neutron.client import MidonetClient
|
||||
client_class = MidonetClient
|
||||
self.mock_class = client_class()
|
||||
|
||||
super(MidonetPluginV2TestCase, self).setUp(plugin=plugin)
|
||||
|
||||
|
||||
class TestMidonetNetworksV2(test_plugin.TestNetworksV2,
|
||||
MidonetPluginV2TestCase):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestMidonetL3NatTestCase(MidonetPluginV2TestCase,
|
||||
test_l3_plugin.L3NatDBIntTestCase):
|
||||
def setUp(self,
|
||||
plugin=MIDONET_PLUGIN_NAME,
|
||||
ext_mgr=None,
|
||||
service_plugins=None):
|
||||
super(TestMidonetL3NatTestCase, self).setUp(plugin=plugin,
|
||||
ext_mgr=None,
|
||||
service_plugins=None)
|
||||
|
||||
def test_floatingip_with_invalid_create_port(self):
|
||||
self._test_floatingip_with_invalid_create_port(MIDONET_PLUGIN_NAME)
|
||||
|
||||
def test_floatingip_assoc_no_port(self):
|
||||
with self.subnet(cidr='200.0.0.0/24') as public_sub:
|
||||
self._set_net_external(public_sub['subnet']['network_id'])
|
||||
res = super(TestMidonetL3NatTestCase, self)._create_floatingip(
|
||||
self.fmt, public_sub['subnet']['network_id'])
|
||||
# Cleanup
|
||||
floatingip = self.deserialize(self.fmt, res)
|
||||
self._delete('floatingips', floatingip['floatingip']['id'])
|
||||
self.assertFalse(self.instance.return_value.add_static_nat.called)
|
||||
|
||||
def test_floatingip_assoc_with_port(self):
|
||||
with self.subnet(cidr='200.0.0.0/24') as public_sub:
|
||||
self._set_net_external(public_sub['subnet']['network_id'])
|
||||
with self.port() as private_port:
|
||||
with self.router() as r:
|
||||
# We need to hook up the private subnet to the external
|
||||
# network in order to associate the fip.
|
||||
sid = private_port['port']['fixed_ips'][0]['subnet_id']
|
||||
private_sub = {'subnet': {'id': sid}}
|
||||
self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'])
|
||||
|
||||
# Check that get_link_port was called - if not, Source NAT
|
||||
# will not be set up correctly on the MidoNet side
|
||||
self.assertTrue(
|
||||
self.instance.return_value.get_link_port.called)
|
||||
|
||||
self._router_interface_action('add', r['router']['id'],
|
||||
private_sub['subnet']['id'],
|
||||
None)
|
||||
|
||||
# Create the fip.
|
||||
res = super(TestMidonetL3NatTestCase,
|
||||
self)._create_floatingip(
|
||||
self.fmt,
|
||||
public_sub['subnet']['network_id'],
|
||||
port_id=private_port['port']['id'])
|
||||
|
||||
# Cleanup the resources used for the test
|
||||
floatingip = self.deserialize(self.fmt, res)
|
||||
self._delete('floatingips', floatingip['floatingip']['id'])
|
||||
self._remove_external_gateway_from_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'])
|
||||
self._router_interface_action('remove',
|
||||
r['router']['id'],
|
||||
private_sub['subnet']['id'],
|
||||
None)
|
||||
self.assertTrue(self.instance.return_value.add_static_nat.called)
|
||||
|
||||
def test_delete_ext_net_with_disassociated_floating_ips(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestMidonetSecurityGroupsTestCase(sg.SecurityGroupDBTestCase):
|
||||
|
||||
_plugin_name = ('%s.MidonetPluginV2' % MIDOKURA_PKG_PATH)
|
||||
|
||||
def setUp(self):
|
||||
self.mock_api = mock.patch(
|
||||
'neutron.plugins.midonet.midonet_lib.MidoClient')
|
||||
etc_path = os.path.join(os.path.dirname(__file__), 'etc')
|
||||
test_lib.test_config['config_files'] = [os.path.join(
|
||||
etc_path, 'midonet.ini.test')]
|
||||
|
||||
self.instance = self.mock_api.start()
|
||||
mock_cfg = mock_lib.MidonetLibMockConfig(self.instance.return_value)
|
||||
mock_cfg.setup()
|
||||
p = mock.patch.dict(sys.modules, {'midonetclient': mock.Mock()})
|
||||
p.start()
|
||||
# dict patches must be explicitly stopped
|
||||
self.addCleanup(p.stop)
|
||||
self.midoclient_mock = mock.MagicMock()
|
||||
self.midoclient_mock.midonetclient.neutron.client.return_value = True
|
||||
modules = {
|
||||
'midonetclient': self.midoclient_mock,
|
||||
'midonetclient.neutron': self.midoclient_mock.neutron,
|
||||
'midonetclient.neutron.client': self.midoclient_mock.client,
|
||||
}
|
||||
|
||||
self.module_patcher = mock.patch.dict('sys.modules', modules)
|
||||
self.module_patcher.start()
|
||||
self.addCleanup(self.module_patcher.stop)
|
||||
|
||||
# import midonetclient here because it needs proper mock objects to be
|
||||
# assigned to this module first. 'midoclient_mock' object is the
|
||||
# mock object used for this module.
|
||||
from midonetclient.neutron.client import MidonetClient
|
||||
client_class = MidonetClient
|
||||
self.mock_class = client_class()
|
||||
|
||||
super(TestMidonetSecurityGroupsTestCase, self).setUp(self._plugin_name)
|
||||
|
||||
|
||||
class TestMidonetSecurityGroup(sg.TestSecurityGroups,
|
||||
TestMidonetSecurityGroupsTestCase):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TestMidonetSubnetsV2(test_plugin.TestSubnetsV2,
|
||||
MidonetPluginV2TestCase):
|
||||
|
||||
# IPv6 is not supported by MidoNet yet. Ignore tests that attempt to
|
||||
# create IPv6 subnet.
|
||||
def test_create_subnet_inconsistent_ipv6_cidrv4(self):
|
||||
pass
|
||||
|
||||
def test_create_subnet_inconsistent_ipv6_dns_v4(self):
|
||||
pass
|
||||
|
||||
def test_create_subnet_with_v6_allocation_pool(self):
|
||||
pass
|
||||
|
||||
def test_update_subnet_inconsistent_ipv6_gatewayv4(self):
|
||||
pass
|
||||
|
||||
def test_update_subnet_inconsistent_ipv6_hostroute_dst_v4(self):
|
||||
pass
|
||||
|
||||
def test_update_subnet_inconsistent_ipv6_hostroute_np_v4(self):
|
||||
pass
|
||||
|
||||
def test_create_subnet_inconsistent_ipv6_gatewayv4(self):
|
||||
pass
|
||||
|
||||
def test_create_subnet_dhcp_disabled(self):
|
||||
super(TestMidonetSubnetsV2, self)._test_create_subnet(
|
||||
enable_dhcp=False)
|
||||
self.assertFalse(self.instance.return_value.create_dhcp.called)
|
||||
|
||||
|
||||
class TestMidonetPortsV2(test_plugin.TestPortsV2,
|
||||
MidonetPluginV2TestCase):
|
||||
|
||||
# IPv6 is not supported by MidoNet yet. Ignore tests that attempt to
|
||||
# create IPv6 subnet.
|
||||
|
||||
def test_requested_subnet_id_v4_and_v6(self):
|
||||
pass
|
||||
|
||||
def test_vif_port_binding(self):
|
||||
with self.port(name='myname') as port:
|
||||
self.assertEqual('midonet', port['port']['binding:vif_type'])
|
||||
self.assertTrue(port['port']['admin_state_up'])
|
||||
|
||||
|
||||
class TestMidonetPluginPortBinding(test_bindings.PortBindingsTestCase,
|
||||
MidonetPluginV2TestCase):
|
||||
|
||||
VIF_TYPE = portbindings.VIF_TYPE_MIDONET
|
||||
HAS_PORT_FILTER = True
|
||||
|
||||
def setUp(self):
|
||||
super(TestMidonetPluginPortBinding, self).setUp()
|
Loading…
Reference in New Issue