Implement MidoNet Neutron plugin for Havana
Implement L2, L3, security groups, metadata server support for MidoNet Neutron plugin for Havana. blueprint midonet-plugin-havana Change-Id: I0dd1a2ca17d760443c4c7a464a66b6d0a2cf194a
This commit is contained in:
parent
795155c743
commit
0253740a23
@ -15,5 +15,5 @@
|
|||||||
# Virtual provider router ID
|
# Virtual provider router ID
|
||||||
# provider_router_id = 00112233-0011-0011-0011-001122334455
|
# provider_router_id = 00112233-0011-0011-0011-001122334455
|
||||||
|
|
||||||
# Virtual metadata router ID
|
# Path to midonet host uuid file
|
||||||
# metadata_router_id = ffeeddcc-ffee-ffee-ffee-ffeeddccbbaa
|
# midonet_host_uuid_path = /etc/midolman/host_uuid.properties
|
||||||
|
16
neutron/plugins/midonet/agent/__init__.py
Normal file
16
neutron/plugins/midonet/agent/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# 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.
|
140
neutron/plugins/midonet/agent/midonet_driver.py
Normal file
140
neutron/plugins/midonet/agent/midonet_driver.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# @author: Rossella Sblendido, Midokura Japan KK
|
||||||
|
# @author: Tomoe Sugihara, Midokura Japan KK
|
||||||
|
# @author: Ryu Ishimoto, Midokura Japan KK
|
||||||
|
|
||||||
|
from midonetclient import api
|
||||||
|
from oslo.config import cfg
|
||||||
|
from webob import exc as w_exc
|
||||||
|
|
||||||
|
from neutron.agent.linux import dhcp
|
||||||
|
from neutron.agent.linux import interface
|
||||||
|
from neutron.agent.linux import ip_lib
|
||||||
|
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_delegate.destroy(self.network, self.interface_name)
|
||||||
|
self._remove_config_files()
|
||||||
|
|
||||||
|
def release_lease(self, mac_address, removed_ips):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def reload_allocations(self):
|
||||||
|
"""Force the DHCP server to reload the assignment database."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def spawn_process(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MidonetInterfaceDriver(interface.LinuxInterfaceDriver):
|
||||||
|
def __init__(self, conf):
|
||||||
|
super(MidonetInterfaceDriver, self).__init__(conf)
|
||||||
|
# Read config values
|
||||||
|
midonet_conf = conf.MIDONET
|
||||||
|
midonet_uri = midonet_conf.midonet_uri
|
||||||
|
admin_user = midonet_conf.username
|
||||||
|
admin_pass = midonet_conf.password
|
||||||
|
admin_project_id = midonet_conf.project_id
|
||||||
|
|
||||||
|
self.mido_api = api.MidonetApi(midonet_uri, admin_user,
|
||||||
|
admin_pass,
|
||||||
|
project_id=admin_project_id)
|
||||||
|
|
||||||
|
def _get_host_uuid(self):
|
||||||
|
"""Get MidoNet host id from host_uuid.properties file."""
|
||||||
|
f = open(cfg.CONF.MIDONET.midonet_host_uuid_path)
|
||||||
|
lines = f.readlines()
|
||||||
|
host_uuid = filter(lambda x: x.startswith('host_uuid='),
|
||||||
|
lines)[0].strip()[len('host_uuid='):]
|
||||||
|
return host_uuid
|
||||||
|
|
||||||
|
def plug(self, network_id, port_id, device_name, mac_address,
|
||||||
|
bridge=None, namespace=None, prefix=None):
|
||||||
|
"""This method is called by the Dhcp agent or by the L3 agent
|
||||||
|
when a new network is created
|
||||||
|
"""
|
||||||
|
if not ip_lib.device_exists(device_name,
|
||||||
|
self.root_helper,
|
||||||
|
namespace=namespace):
|
||||||
|
ip = ip_lib.IPWrapper(self.root_helper)
|
||||||
|
tap_name = device_name.replace(prefix or 'tap', 'tap')
|
||||||
|
|
||||||
|
# Create ns_dev in a namespace if one is configured.
|
||||||
|
root_dev, ns_dev = ip.add_veth(tap_name,
|
||||||
|
device_name,
|
||||||
|
namespace2=namespace)
|
||||||
|
|
||||||
|
ns_dev.link.set_address(mac_address)
|
||||||
|
|
||||||
|
# Add an interface created by ovs to the namespace.
|
||||||
|
namespace_obj = ip.ensure_namespace(namespace)
|
||||||
|
namespace_obj.add_device_to_namespace(ns_dev)
|
||||||
|
|
||||||
|
ns_dev.link.set_up()
|
||||||
|
root_dev.link.set_up()
|
||||||
|
|
||||||
|
vport_id = port_id
|
||||||
|
host_dev_name = device_name
|
||||||
|
|
||||||
|
# create if-vport mapping.
|
||||||
|
host_uuid = self._get_host_uuid()
|
||||||
|
try:
|
||||||
|
host = self.mido_api.get_host(host_uuid)
|
||||||
|
except w_exc.HTTPError as e:
|
||||||
|
LOG.error(_('Failed to create a if-vport mapping on host=%s'),
|
||||||
|
host_uuid)
|
||||||
|
raise e
|
||||||
|
try:
|
||||||
|
self.mido_api.host.add_host_interface_port(
|
||||||
|
host, vport_id, host_dev_name)
|
||||||
|
except w_exc.HTTPError as e:
|
||||||
|
LOG.warn(_('Faild binding vport=%(vport) to device=%(device)'),
|
||||||
|
{"vport": vport_id, "device": host_dev_name})
|
||||||
|
else:
|
||||||
|
LOG.warn(_("Device %s already exists"), device_name)
|
||||||
|
|
||||||
|
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
|
||||||
|
# the port will be deleted by the dhcp agent that will call the plugin
|
||||||
|
device = ip_lib.IPDevice(device_name,
|
||||||
|
self.root_helper,
|
||||||
|
namespace)
|
||||||
|
device.link.delete()
|
||||||
|
LOG.debug(_("Unplugged interface '%s'"), device_name)
|
||||||
|
|
||||||
|
ip_lib.IPWrapper(
|
||||||
|
self.root_helper, namespace).garbage_collect_namespace()
|
16
neutron/plugins/midonet/common/__init__.py
Normal file
16
neutron/plugins/midonet/common/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# 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.
|
@ -35,12 +35,12 @@ midonet_opts = [
|
|||||||
cfg.StrOpt('provider_router_id',
|
cfg.StrOpt('provider_router_id',
|
||||||
default=None,
|
default=None,
|
||||||
help=_('Virtual provider router ID.')),
|
help=_('Virtual provider router ID.')),
|
||||||
cfg.StrOpt('metadata_router_id',
|
|
||||||
default=None,
|
|
||||||
help=_('Virtual metadata router ID.')),
|
|
||||||
cfg.StrOpt('mode',
|
cfg.StrOpt('mode',
|
||||||
default='dev',
|
default='dev',
|
||||||
help=_('Operational mode. Internal dev use only.'))
|
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'))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
64
neutron/plugins/midonet/common/net_util.py
Normal file
64
neutron/plugins/midonet/common/net_util.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# @author: Ryu Ishimoto, Midokura Japan KK
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
mapping = {
|
||||||
|
'tcp': constants.TCP_PROTOCOL,
|
||||||
|
'udp': constants.UDP_PROTOCOL,
|
||||||
|
'icmp': constants.ICMP_PROTOCOL
|
||||||
|
}
|
||||||
|
return mapping.get(protocol.lower())
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -62,26 +62,6 @@ def get_chain_mock(id=None, tenant_id='test-tenant', name='chain',
|
|||||||
return chain
|
return chain
|
||||||
|
|
||||||
|
|
||||||
def get_exterior_bridge_port_mock(id=None, bridge_id=None):
|
|
||||||
if id is None:
|
|
||||||
id = str(uuid.uuid4())
|
|
||||||
if bridge_id is None:
|
|
||||||
bridge_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
return get_bridge_port_mock(id=id, bridge_id=bridge_id,
|
|
||||||
type='ExteriorBridge')
|
|
||||||
|
|
||||||
|
|
||||||
def get_interior_bridge_port_mock(id=None, bridge_id=None):
|
|
||||||
if id is None:
|
|
||||||
id = str(uuid.uuid4())
|
|
||||||
if bridge_id is None:
|
|
||||||
bridge_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
return get_bridge_port_mock(id=id, bridge_id=bridge_id,
|
|
||||||
type='InteriorBridge')
|
|
||||||
|
|
||||||
|
|
||||||
def get_port_group_mock(id=None, tenant_id='test-tenant', name='pg'):
|
def get_port_group_mock(id=None, tenant_id='test-tenant', name='pg'):
|
||||||
if id is None:
|
if id is None:
|
||||||
id = str(uuid.uuid4())
|
id = str(uuid.uuid4())
|
||||||
@ -143,22 +123,19 @@ class MidonetLibMockConfig():
|
|||||||
def _create_bridge(self, tenant_id, name):
|
def _create_bridge(self, tenant_id, name):
|
||||||
return get_bridge_mock(tenant_id=tenant_id, name=name)
|
return get_bridge_mock(tenant_id=tenant_id, name=name)
|
||||||
|
|
||||||
def _create_exterior_bridge_port(self, bridge):
|
|
||||||
return get_exterior_bridge_port_mock(bridge_id=bridge.get_id())
|
|
||||||
|
|
||||||
def _create_interior_bridge_port(self, bridge):
|
|
||||||
return get_interior_bridge_port_mock(bridge_id=bridge.get_id())
|
|
||||||
|
|
||||||
def _create_subnet(self, bridge, gateway_ip, subnet_prefix, subnet_len):
|
def _create_subnet(self, bridge, gateway_ip, subnet_prefix, subnet_len):
|
||||||
return get_subnet_mock(bridge.get_id(), gateway_ip=gateway_ip,
|
return get_subnet_mock(bridge.get_id(), gateway_ip=gateway_ip,
|
||||||
subnet_prefix=subnet_prefix,
|
subnet_prefix=subnet_prefix,
|
||||||
subnet_len=subnet_len)
|
subnet_len=subnet_len)
|
||||||
|
|
||||||
|
def _add_bridge_port(self, bridge):
|
||||||
|
return get_bridge_port_mock(bridge_id=bridge.get_id())
|
||||||
|
|
||||||
def _get_bridge(self, id):
|
def _get_bridge(self, id):
|
||||||
return get_bridge_mock(id=id)
|
return get_bridge_mock(id=id)
|
||||||
|
|
||||||
def _get_port(self, id):
|
def _get_port(self, id):
|
||||||
return get_exterior_bridge_port_mock(id=id)
|
return get_bridge_port_mock(id=id)
|
||||||
|
|
||||||
def _get_router(self, id):
|
def _get_router(self, id):
|
||||||
return get_router_mock(id=id)
|
return get_router_mock(id=id)
|
||||||
@ -176,10 +153,8 @@ class MidonetLibMockConfig():
|
|||||||
self.inst.create_subnet.side_effect = self._create_subnet
|
self.inst.create_subnet.side_effect = self._create_subnet
|
||||||
|
|
||||||
# Port methods side effects
|
# Port methods side effects
|
||||||
ex_bp = self.inst.create_exterior_bridge_port
|
ex_bp = self.inst.add_bridge_port
|
||||||
ex_bp.side_effect = self._create_exterior_bridge_port
|
ex_bp.side_effect = self._add_bridge_port
|
||||||
in_bp = self.inst.create_interior_bridge_port
|
|
||||||
in_bp.side_effect = self._create_interior_bridge_port
|
|
||||||
self.inst.get_port.side_effect = self._get_port
|
self.inst.get_port.side_effect = self._get_port
|
||||||
|
|
||||||
# Router methods side effects
|
# Router methods side effects
|
||||||
@ -206,6 +181,26 @@ class MidoClientMockConfig():
|
|||||||
def _get_bridge(self, id):
|
def _get_bridge(self, id):
|
||||||
return get_bridge_mock(id=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):
|
def _get_chains(self, query=None):
|
||||||
if not self.chains_in:
|
if not self.chains_in:
|
||||||
return []
|
return []
|
||||||
@ -249,5 +244,6 @@ class MidoClientMockConfig():
|
|||||||
def setup(self):
|
def setup(self):
|
||||||
self.inst.get_bridge.side_effect = self._get_bridge
|
self.inst.get_bridge.side_effect = self._get_bridge
|
||||||
self.inst.get_chains.side_effect = self._get_chains
|
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_port_groups.side_effect = self._get_port_groups
|
||||||
self.inst.get_router.side_effect = self._get_router
|
self.inst.get_router.side_effect = self._get_router
|
||||||
|
116
neutron/tests/unit/midonet/test_midonet_driver.py
Normal file
116
neutron/tests/unit/midonet/test_midonet_driver.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# @author: Rossella Sblendido, Midokura Japan KK
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslo.config import cfg
|
||||||
|
import sys
|
||||||
|
sys.modules["midonetclient"] = mock.Mock()
|
||||||
|
|
||||||
|
from neutron.agent.common import config
|
||||||
|
from neutron.agent.linux import interface
|
||||||
|
from neutron.agent.linux import ip_lib
|
||||||
|
from neutron.agent.linux import utils
|
||||||
|
from neutron.openstack.common import uuidutils
|
||||||
|
import neutron.plugins.midonet.agent.midonet_driver as driver
|
||||||
|
from neutron.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class MidoInterfaceDriverTestCase(base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.conf = config.setup_conf()
|
||||||
|
self.conf.register_opts(interface.OPTS)
|
||||||
|
config.register_root_helper(self.conf)
|
||||||
|
self.ip_dev_p = mock.patch.object(ip_lib, 'IPDevice')
|
||||||
|
self.ip_dev = self.ip_dev_p.start()
|
||||||
|
self.ip_p = mock.patch.object(ip_lib, 'IPWrapper')
|
||||||
|
self.ip = self.ip_p.start()
|
||||||
|
self.device_exists_p = mock.patch.object(ip_lib, 'device_exists')
|
||||||
|
self.device_exists = self.device_exists_p.start()
|
||||||
|
|
||||||
|
self.api_p = mock.patch.object(sys.modules["midonetclient"].api,
|
||||||
|
'MidonetApi')
|
||||||
|
self.api = self.api_p.start()
|
||||||
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
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.'))
|
||||||
|
]
|
||||||
|
self.conf.register_opts(midonet_opts, "MIDONET")
|
||||||
|
self.driver = driver.MidonetInterfaceDriver(self.conf)
|
||||||
|
self.network_id = uuidutils.generate_uuid()
|
||||||
|
self.port_id = uuidutils.generate_uuid()
|
||||||
|
self.device_name = "tap0"
|
||||||
|
self.mac_address = "aa:bb:cc:dd:ee:ff"
|
||||||
|
self.bridge = "br-test"
|
||||||
|
self.namespace = "ns-test"
|
||||||
|
super(MidoInterfaceDriverTestCase, self).setUp()
|
||||||
|
|
||||||
|
def test_plug(self):
|
||||||
|
def device_exists(dev, root_helper=None, namespace=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.device_exists.side_effect = device_exists
|
||||||
|
root_dev = mock.Mock()
|
||||||
|
ns_dev = mock.Mock()
|
||||||
|
self.ip().add_veth = mock.Mock(return_value=(root_dev, ns_dev))
|
||||||
|
self.driver._get_host_uuid = mock.Mock(
|
||||||
|
return_value=uuidutils.generate_uuid())
|
||||||
|
with mock.patch.object(utils, 'execute'):
|
||||||
|
self.driver.plug(
|
||||||
|
self.network_id, self.port_id,
|
||||||
|
self.device_name, self.mac_address,
|
||||||
|
self.bridge, self.namespace)
|
||||||
|
|
||||||
|
expected = [mock.call(), mock.call('sudo'),
|
||||||
|
mock.call().add_veth(self.device_name,
|
||||||
|
self.device_name,
|
||||||
|
namespace2=self.namespace),
|
||||||
|
mock.call().ensure_namespace(self.namespace),
|
||||||
|
mock.call().ensure_namespace().add_device_to_namespace(
|
||||||
|
mock.ANY)]
|
||||||
|
ns_dev.assert_has_calls(
|
||||||
|
[mock.call.link.set_address(self.mac_address)])
|
||||||
|
|
||||||
|
root_dev.assert_has_calls([mock.call.link.set_up()])
|
||||||
|
ns_dev.assert_has_calls([mock.call.link.set_up()])
|
||||||
|
self.ip.assert_has_calls(expected, True)
|
||||||
|
host = mock.Mock()
|
||||||
|
self.api().get_host = mock.Mock(return_value=host)
|
||||||
|
self.api.assert_has_calls([mock.call().add_host_interface_port])
|
||||||
|
|
||||||
|
def test_unplug(self):
|
||||||
|
with mock.patch.object(utils, 'execute'):
|
||||||
|
self.driver.unplug(self.device_name, self.bridge, self.namespace)
|
||||||
|
|
||||||
|
self.ip_dev.assert_has_calls([
|
||||||
|
mock.call(self.device_name, self.driver.root_helper,
|
||||||
|
self.namespace),
|
||||||
|
mock.call().link.delete()])
|
||||||
|
self.ip.assert_has_calls(mock.call().garbage_collect_namespace())
|
@ -19,7 +19,6 @@
|
|||||||
# @author: Ryu Ishimoto, Midokura Japan KK
|
# @author: Ryu Ishimoto, Midokura Japan KK
|
||||||
# @author: Tomoe Sugihara, Midokura Japan KK
|
# @author: Tomoe Sugihara, Midokura Japan KK
|
||||||
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import testtools
|
import testtools
|
||||||
import webob.exc as w_exc
|
import webob.exc as w_exc
|
||||||
@ -33,55 +32,8 @@ def _create_test_chain(id, name, tenant_id):
|
|||||||
return {'id': id, 'name': name, 'tenant_id': tenant_id}
|
return {'id': id, 'name': name, 'tenant_id': tenant_id}
|
||||||
|
|
||||||
|
|
||||||
def _create_test_port_group(sg_id, sg_name, id, tenant_id):
|
def _create_test_port_group(id, name, tenant_id):
|
||||||
return {"id": id, "name": "OS_SG_%s_%s" % (sg_id, sg_name),
|
return {"id": id, "name": name, "tenant_id": tenant_id}
|
||||||
"tenant_id": tenant_id}
|
|
||||||
|
|
||||||
|
|
||||||
def _create_test_router_in_chain(router_id, id, tenant_id):
|
|
||||||
name = "OS_ROUTER_IN_%s" % router_id
|
|
||||||
return _create_test_chain(id, name, tenant_id)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_test_router_out_chain(router_id, id, tenant_id):
|
|
||||||
name = "OS_ROUTER_OUT_%s" % router_id
|
|
||||||
return _create_test_chain(id, name, tenant_id)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_test_rule(id, chain_id, properties):
|
|
||||||
return {"id": id, "chain_id": chain_id, "properties": properties}
|
|
||||||
|
|
||||||
|
|
||||||
def _create_test_sg_in_chain(sg_id, sg_name, id, tenant_id):
|
|
||||||
if sg_name:
|
|
||||||
name = "OS_SG_%s_%s_IN" % (sg_id, sg_name)
|
|
||||||
else:
|
|
||||||
name = "OS_SG_%s_IN" % sg_id
|
|
||||||
return _create_test_chain(id, name, tenant_id)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_test_sg_out_chain(sg_id, sg_name, id, tenant_id):
|
|
||||||
if sg_name:
|
|
||||||
name = "OS_SG_%s_%s_OUT" % (sg_id, sg_name)
|
|
||||||
else:
|
|
||||||
name = "OS_SG_%s_OUT" % sg_id
|
|
||||||
return _create_test_chain(id, name, tenant_id)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_test_sg_rule(tenant_id, sg_id, id,
|
|
||||||
direction="egress", protocol="tcp", port_min=1,
|
|
||||||
port_max=65535, src_ip='192.168.1.0/24',
|
|
||||||
src_group_id=None, ethertype=0x0800, properties=None):
|
|
||||||
return {"tenant_id": tenant_id, "security_group_id": sg_id,
|
|
||||||
"id": id, "direction": direction, "protocol": protocol,
|
|
||||||
"remote_ip_prefix": src_ip, "remote_group_id": src_group_id,
|
|
||||||
"port_range_min": port_min, "port_range_max": port_max,
|
|
||||||
"ethertype": ethertype, "external_id": None}
|
|
||||||
|
|
||||||
|
|
||||||
def _create_test_sg_chain_rule(id, chain_id, sg_rule_id):
|
|
||||||
props = {"os_sg_rule_id": sg_rule_id}
|
|
||||||
return _create_test_rule(id, chain_id, props)
|
|
||||||
|
|
||||||
|
|
||||||
class MidoClientTestCase(testtools.TestCase):
|
class MidoClientTestCase(testtools.TestCase):
|
||||||
@ -94,137 +46,59 @@ class MidoClientTestCase(testtools.TestCase):
|
|||||||
self.mock_api_cfg.setup()
|
self.mock_api_cfg.setup()
|
||||||
self.client = midonet_lib.MidoClient(self.mock_api)
|
self.client = midonet_lib.MidoClient(self.mock_api)
|
||||||
|
|
||||||
def test_create_for_sg(self):
|
def test_delete_chains_by_names(self):
|
||||||
sg_id = uuidutils.generate_uuid()
|
|
||||||
sg_name = 'test-sg'
|
|
||||||
calls = [mock.call.add_chain().tenant_id(self._tenant_id),
|
|
||||||
mock.call.add_port_group().tenant_id(self._tenant_id)]
|
|
||||||
|
|
||||||
self.client.create_for_sg(self._tenant_id, sg_id, sg_name)
|
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)
|
self.mock_api.assert_has_calls(calls, any_order=True)
|
||||||
|
|
||||||
def test_create_for_sg_rule(self):
|
def test_delete_port_group_by_name(self):
|
||||||
sg_id = uuidutils.generate_uuid()
|
|
||||||
sg_name = 'test-sg'
|
|
||||||
in_chain_id = uuidutils.generate_uuid()
|
|
||||||
out_chain_id = uuidutils.generate_uuid()
|
|
||||||
self.mock_api_cfg.chains_in = [
|
|
||||||
_create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
|
|
||||||
self._tenant_id),
|
|
||||||
_create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
|
|
||||||
self._tenant_id)]
|
|
||||||
|
|
||||||
sg_rule_id = uuidutils.generate_uuid()
|
tenant_id = uuidutils.generate_uuid()
|
||||||
sg_rule = _create_test_sg_rule(self._tenant_id, sg_id, sg_rule_id)
|
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)
|
||||||
|
|
||||||
props = {"os_sg_rule_id": sg_rule_id}
|
self.mock_api_cfg.port_groups_in = [pg1, pg2]
|
||||||
calls = [mock.call.add_rule().port_group(None).type(
|
self.client.delete_port_group_by_name(tenant_id, "pg1")
|
||||||
'accept').nw_proto(6).nw_src_address(
|
self.mock_api.delete_port_group.assert_called_once_with(pg1_id)
|
||||||
'192.168.1.0').nw_src_length(24).tp_src_start(
|
|
||||||
None).tp_src_end(None).tp_dst_start(1).tp_dst_end(
|
|
||||||
65535).properties(props).create()]
|
|
||||||
|
|
||||||
self.client.create_for_sg_rule(sg_rule)
|
def test_create_dhcp(self):
|
||||||
|
|
||||||
# Egress chain rule added
|
bridge = mock.Mock()
|
||||||
self.mock_api_cfg.chains_out[0].assert_has_calls(calls)
|
gw_call = mock.call.add_dhcp_subnet().default_gateway("192.168.1.1")
|
||||||
|
subnet_prefix_call = gw_call.subnet_prefix("192.168.1.0")
|
||||||
|
subnet_length_call = subnet_prefix_call.subnet_length(24)
|
||||||
|
|
||||||
def test_create_router_chains(self):
|
calls = [gw_call, subnet_prefix_call, subnet_length_call,
|
||||||
router = mock_lib.get_router_mock(tenant_id=self._tenant_id)
|
subnet_length_call.create()]
|
||||||
api_calls = [mock.call.add_chain().tenant_id(self._tenant_id)]
|
|
||||||
router_calls = [
|
|
||||||
mock.call.inbound_filter_id().outbound_filter_id().update()]
|
|
||||||
|
|
||||||
self.client.create_router_chains(router)
|
self.client.create_dhcp(bridge, "192.168.1.1", "192.168.1.0/24")
|
||||||
|
bridge.assert_has_calls(calls, any_order=True)
|
||||||
|
|
||||||
self.mock_api.assert_has_calls(api_calls)
|
def test_add_dhcp_host(self):
|
||||||
router.assert_has_calls(router_calls)
|
|
||||||
|
|
||||||
def test_delete_for_sg(self):
|
bridge = mock.Mock()
|
||||||
sg_id = uuidutils.generate_uuid()
|
dhcp_subnet_call = mock.call.get_dhcp_subnet("10.0.0.0_24")
|
||||||
sg_name = 'test-sg'
|
ip_addr_call = dhcp_subnet_call.add_dhcp_host().ip_addr("10.0.0.10")
|
||||||
in_chain_id = uuidutils.generate_uuid()
|
mac_addr_call = ip_addr_call.mac_addr("2A:DB:6B:8C:19:99")
|
||||||
out_chain_id = uuidutils.generate_uuid()
|
calls = [dhcp_subnet_call, ip_addr_call, mac_addr_call,
|
||||||
pg_id = uuidutils.generate_uuid()
|
mac_addr_call.create()]
|
||||||
self.mock_api_cfg.chains_in = [
|
|
||||||
_create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
|
|
||||||
self._tenant_id),
|
|
||||||
_create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
|
|
||||||
self._tenant_id)]
|
|
||||||
self.mock_api_cfg.port_groups_in = [
|
|
||||||
_create_test_port_group(sg_id, sg_name, pg_id, self._tenant_id)]
|
|
||||||
|
|
||||||
calls = [mock.call.get_chains({"tenant_id": self._tenant_id}),
|
self.client.add_dhcp_host(bridge, "10.0.0.0/24", "10.0.0.10",
|
||||||
mock.call.delete_chain(in_chain_id),
|
"2A:DB:6B:8C:19:99")
|
||||||
mock.call.delete_chain(out_chain_id),
|
bridge.assert_has_calls(calls, any_order=True)
|
||||||
mock.call.get_port_groups({"tenant_id": self._tenant_id}),
|
|
||||||
mock.call.delete_port_group(pg_id)]
|
|
||||||
|
|
||||||
self.client.delete_for_sg(self._tenant_id, sg_id, sg_name)
|
|
||||||
|
|
||||||
self.mock_api.assert_has_calls(calls)
|
|
||||||
|
|
||||||
def test_delete_for_sg_rule(self):
|
|
||||||
sg_id = uuidutils.generate_uuid()
|
|
||||||
sg_name = 'test-sg'
|
|
||||||
in_chain_id = uuidutils.generate_uuid()
|
|
||||||
out_chain_id = uuidutils.generate_uuid()
|
|
||||||
self.mock_api_cfg.chains_in = [
|
|
||||||
_create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
|
|
||||||
self._tenant_id),
|
|
||||||
_create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
|
|
||||||
self._tenant_id)]
|
|
||||||
|
|
||||||
rule_id = uuidutils.generate_uuid()
|
|
||||||
sg_rule_id = uuidutils.generate_uuid()
|
|
||||||
rule = _create_test_sg_chain_rule(rule_id, in_chain_id, sg_rule_id)
|
|
||||||
self.mock_api_cfg.chains_in[0]['rules'] = [rule]
|
|
||||||
sg_rule = _create_test_sg_rule(self._tenant_id, sg_id, sg_rule_id)
|
|
||||||
|
|
||||||
self.client.delete_for_sg_rule(sg_rule)
|
|
||||||
|
|
||||||
self.mock_api.delete_rule.assert_called_once_with(rule_id)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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_port_groups_for_sg(self):
|
|
||||||
sg_id = uuidutils.generate_uuid()
|
|
||||||
pg_id = uuidutils.generate_uuid()
|
|
||||||
self.mock_api_cfg.port_groups_in = [
|
|
||||||
_create_test_port_group(sg_id, 'test-sg', pg_id, self._tenant_id)]
|
|
||||||
|
|
||||||
pg = self.client.get_port_groups_for_sg(self._tenant_id, sg_id)
|
|
||||||
|
|
||||||
self.assertIsNotNone(pg)
|
|
||||||
self.assertEqual(pg.get_id(), pg_id)
|
|
||||||
|
|
||||||
def _create_test_rule(self, tenant_id, sg_id, rule_id, direction="egress",
|
|
||||||
protocol="tcp", port_min=1, port_max=65535,
|
|
||||||
src_ip='192.168.1.0/24', src_group_id=None,
|
|
||||||
ethertype=0x0800):
|
|
||||||
return {"tenant_id": tenant_id, "security_group_id": sg_id,
|
|
||||||
"rule_id": rule_id, "direction": direction,
|
|
||||||
"protocol": protocol,
|
|
||||||
"remote_ip_prefix": src_ip, "remote_group_id": src_group_id,
|
|
||||||
"port_range_min": port_min, "port_range_max": port_max,
|
|
||||||
"ethertype": ethertype, "id": rule_id, "external_id": None}
|
|
||||||
|
|
||||||
def test_get_router_error(self):
|
def test_get_router_error(self):
|
||||||
self.mock_api.get_router.side_effect = w_exc.HTTPInternalServerError()
|
self.mock_api.get_router.side_effect = w_exc.HTTPInternalServerError()
|
||||||
@ -236,43 +110,20 @@ class MidoClientTestCase(testtools.TestCase):
|
|||||||
self.assertRaises(midonet_lib.MidonetResourceNotFound,
|
self.assertRaises(midonet_lib.MidonetResourceNotFound,
|
||||||
self.client.get_router, uuidutils.generate_uuid())
|
self.client.get_router, uuidutils.generate_uuid())
|
||||||
|
|
||||||
def test_get_router_chains(self):
|
def test_get_bridge_error(self):
|
||||||
router_id = uuidutils.generate_uuid()
|
self.mock_api.get_bridge.side_effect = w_exc.HTTPInternalServerError()
|
||||||
in_chain_id = uuidutils.generate_uuid()
|
self.assertRaises(midonet_lib.MidonetApiException,
|
||||||
out_chain_id = uuidutils.generate_uuid()
|
self.client.get_bridge, uuidutils.generate_uuid())
|
||||||
self.mock_api_cfg.chains_in = [
|
|
||||||
_create_test_router_in_chain(router_id, in_chain_id,
|
|
||||||
self._tenant_id),
|
|
||||||
_create_test_router_out_chain(router_id, out_chain_id,
|
|
||||||
self._tenant_id)]
|
|
||||||
|
|
||||||
chains = self.client.get_router_chains(self._tenant_id, router_id)
|
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())
|
||||||
|
|
||||||
self.mock_api.assert_has_calls(mock.call.get_chains(
|
def test_get_bridge(self):
|
||||||
{"tenant_id": self._tenant_id}))
|
bridge_id = uuidutils.generate_uuid()
|
||||||
self.assertEqual(len(chains), 2)
|
|
||||||
self.assertIn('in', chains)
|
|
||||||
self.assertIn('out', chains)
|
|
||||||
self.assertEqual(chains['in'].get_id(), in_chain_id)
|
|
||||||
self.assertEqual(chains['out'].get_id(), out_chain_id)
|
|
||||||
|
|
||||||
def test_get_sg_chains(self):
|
bridge = self.client.get_bridge(bridge_id)
|
||||||
sg_id = uuidutils.generate_uuid()
|
|
||||||
sg_name = 'test-sg'
|
|
||||||
in_chain_id = uuidutils.generate_uuid()
|
|
||||||
out_chain_id = uuidutils.generate_uuid()
|
|
||||||
self.mock_api_cfg.chains_in = [
|
|
||||||
_create_test_sg_in_chain(sg_id, sg_name, in_chain_id,
|
|
||||||
self._tenant_id),
|
|
||||||
_create_test_sg_out_chain(sg_id, sg_name, out_chain_id,
|
|
||||||
self._tenant_id)]
|
|
||||||
|
|
||||||
chains = self.client.get_sg_chains(self._tenant_id, sg_id)
|
self.assertIsNotNone(bridge)
|
||||||
|
self.assertEqual(bridge.get_id(), bridge_id)
|
||||||
self.mock_api.assert_has_calls(mock.call.get_chains(
|
|
||||||
{"tenant_id": self._tenant_id}))
|
|
||||||
self.assertEqual(len(chains), 2)
|
|
||||||
self.assertIn('in', chains)
|
|
||||||
self.assertIn('out', chains)
|
|
||||||
self.assertEqual(chains['in'].get_id(), in_chain_id)
|
|
||||||
self.assertEqual(chains['out'].get_id(), out_chain_id)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user