Merge "Implement remote port function for dragonflow"
This commit is contained in:
commit
643894e447
|
@ -21,6 +21,7 @@ from neutron.agent.common import config
|
|||
from neutron.common import config as common_config
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_serialization import jsonutils
|
||||
from ryu.base import app_manager
|
||||
from ryu import cfg as ryu_cfg
|
||||
from ryu.ofproto import ofproto_common
|
||||
|
@ -78,6 +79,7 @@ class DfLocalController(object):
|
|||
self.topology = None
|
||||
self.enable_selective_topo_dist = \
|
||||
cfg.CONF.df.enable_selective_topology_distribution
|
||||
self.remote_chassis_lport_map = {}
|
||||
self.integration_bridge = cfg.CONF.df.integration_bridge
|
||||
|
||||
def run(self):
|
||||
|
@ -208,13 +210,13 @@ class DfLocalController(object):
|
|||
self.db_store.del_lswitch(lswitch_id)
|
||||
self.db_store.del_network_id(lswitch_id)
|
||||
|
||||
def _is_physical_chassis(self, chassis):
|
||||
if not chassis or chassis == constants.DRAGONFLOW_VIRTUAL_PORT:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _logical_port_process(self, lport, original_lport=None):
|
||||
chassis = lport.get_chassis()
|
||||
if not chassis or chassis == constants.DRAGONFLOW_VIRTUAL_PORT:
|
||||
LOG.debug(("Port %s has not been bound or it is a vPort ") %
|
||||
lport.get_id())
|
||||
return
|
||||
|
||||
chassis_to_ofport, lport_to_ofport = (
|
||||
self.vswitch_api.get_local_ports_to_ofport_mapping())
|
||||
local_network_id = self.get_network_id(
|
||||
|
@ -275,11 +277,56 @@ class DfLocalController(object):
|
|||
LOG.warning(_LW("No tunnel for remote logical port %s") %
|
||||
str(lport))
|
||||
|
||||
def _add_remote_port_on_chassis(self, lport):
|
||||
chassis = lport.get_chassis()
|
||||
chassis_lports = self.remote_chassis_lport_map.get(chassis)
|
||||
if not chassis_lports:
|
||||
chassis_value = {'id': chassis, 'ip': chassis,
|
||||
'tunnel_type': self.tunnel_type}
|
||||
chassis_inst = api_nb.Chassis(jsonutils.dumps(chassis_value))
|
||||
self.chassis_created(chassis_inst)
|
||||
self.remote_chassis_lport_map[chassis] = {lport.get_id()}
|
||||
else:
|
||||
chassis_lports.add(lport.get_id())
|
||||
|
||||
def _delete_remote_port_from_chassis(self, lport):
|
||||
chassis = lport.get_chassis()
|
||||
chassis_lports = self.remote_chassis_lport_map.get(chassis)
|
||||
if chassis_lports is None:
|
||||
return
|
||||
chassis_lports.remove(lport.get_id())
|
||||
if len(chassis_lports) == 0:
|
||||
self.chassis_deleted(chassis)
|
||||
del self.remote_chassis_lport_map[chassis]
|
||||
|
||||
def logical_port_created(self, lport):
|
||||
chassis = lport.get_chassis()
|
||||
if not self._is_physical_chassis(chassis):
|
||||
LOG.debug(("Port %s has not been bound or it is a vPort") %
|
||||
lport.get_id())
|
||||
return
|
||||
if lport.get_remote_vtep():
|
||||
self._add_remote_port_on_chassis(lport)
|
||||
self._logical_port_process(lport)
|
||||
|
||||
def logical_port_updated(self, lport):
|
||||
chassis = lport.get_chassis()
|
||||
if not self._is_physical_chassis(chassis):
|
||||
LOG.debug(("Port %s has not been bound or it is a vPort") %
|
||||
lport.get_id())
|
||||
return
|
||||
original_lport = self.db_store.get_port(lport.get_id())
|
||||
if not original_lport:
|
||||
if lport.get_remote_vtep():
|
||||
self._add_remote_port_on_chassis(lport)
|
||||
else:
|
||||
original_chassis = original_lport.get_chassis()
|
||||
if original_chassis != chassis:
|
||||
if original_lport.get_remote_vtep():
|
||||
self._delete_remote_port_from_chassis(original_lport)
|
||||
|
||||
if lport.get_remote_vtep():
|
||||
self._add_remote_port_on_chassis(lport)
|
||||
self._logical_port_process(lport, original_lport)
|
||||
|
||||
def logical_port_deleted(self, lport_id):
|
||||
|
@ -299,6 +346,9 @@ class DfLocalController(object):
|
|||
self.open_flow_app.notify_remove_remote_port(lport)
|
||||
self.db_store.delete_port(lport.get_id(), False)
|
||||
|
||||
if lport.get_remote_vtep():
|
||||
self._delete_remote_port_from_chassis(lport)
|
||||
|
||||
def bridge_port_updated(self, lport):
|
||||
self.open_flow_app.notify_update_bridge_port(lport)
|
||||
|
||||
|
|
|
@ -919,6 +919,9 @@ class LogicalPort(DbStoreObject):
|
|||
def get_version(self):
|
||||
return self.lport['version']
|
||||
|
||||
def get_remote_vtep(self):
|
||||
return self.lport.get('remote_vtep', False)
|
||||
|
||||
def __str__(self):
|
||||
return self.lport.__str__() + self.external_dict.__str__()
|
||||
|
||||
|
|
|
@ -18,4 +18,7 @@ DF_NETWORK_DEFAULT_NAME = 'no_network_name'
|
|||
DF_PORT_DEFAULT_NAME = 'no_port_name'
|
||||
DF_ROUTER_DEFAULT_NAME = 'no_router_name'
|
||||
DF_FIP_DEFAULT_NAME = 'no_fip_name'
|
||||
DF_REMOTE_PORT_TYPE = 'remote_port'
|
||||
DF_BINDING_PROFILE_PORT_KEY = 'port_key'
|
||||
DF_BINDING_PROFILE_HOST_IP = 'host_ip'
|
||||
DF_PORT_BINDING_PROFILE = portbindings.PROFILE
|
||||
|
|
|
@ -509,6 +509,14 @@ class DFMechDriver(driver_api.MechanismDriver):
|
|||
else:
|
||||
chassis = port.get('binding:host_id', None) or None
|
||||
|
||||
binding_profile = port.get('binding:profile')
|
||||
remote_vtep = False
|
||||
if binding_profile and binding_profile.get(
|
||||
df_const.DF_BINDING_PROFILE_PORT_KEY) ==\
|
||||
df_const.DF_REMOTE_PORT_TYPE:
|
||||
chassis = binding_profile.get(df_const.DF_BINDING_PROFILE_HOST_IP)
|
||||
remote_vtep = True
|
||||
|
||||
self.nb_api.create_lport(
|
||||
id=port['id'],
|
||||
lswitch_id=port['network_id'],
|
||||
|
@ -523,6 +531,7 @@ class DFMechDriver(driver_api.MechanismDriver):
|
|||
device_id=port.get('device_id', None),
|
||||
security_groups=port.get('security_groups', []),
|
||||
port_security_enabled=port.get(psec.PORTSECURITY, False),
|
||||
remote_vtep=remote_vtep,
|
||||
allowed_address_pairs=port.get(addr_pair.ADDRESS_PAIRS, []),
|
||||
binding_profile=port.get(portbindings.PROFILE, None),
|
||||
binding_vnic_type=port.get(portbindings.VNIC_TYPE, None))
|
||||
|
@ -577,6 +586,14 @@ class DFMechDriver(driver_api.MechanismDriver):
|
|||
else:
|
||||
chassis = updated_port.get('binding:host_id', None) or None
|
||||
|
||||
binding_profile = updated_port.get('binding:profile')
|
||||
remote_vtep = False
|
||||
if binding_profile and binding_profile.get(
|
||||
df_const.DF_BINDING_PROFILE_PORT_KEY) ==\
|
||||
df_const.DF_REMOTE_PORT_TYPE:
|
||||
chassis = binding_profile.get(df_const.DF_BINDING_PROFILE_HOST_IP)
|
||||
remote_vtep = True
|
||||
|
||||
updated_security_groups = updated_port.get('security_groups')
|
||||
if updated_security_groups:
|
||||
security_groups = updated_security_groups
|
||||
|
@ -603,7 +620,7 @@ class DFMechDriver(driver_api.MechanismDriver):
|
|||
[]),
|
||||
binding_profile=updated_port.get(portbindings.PROFILE, None),
|
||||
binding_vnic_type=updated_port.get(portbindings.VNIC_TYPE, None),
|
||||
version=updated_port['db_version'])
|
||||
version=updated_port['db_version'], remote_vtep=remote_vtep)
|
||||
|
||||
LOG.info(_LI("DFMechDriver: update port %s"), updated_port['id'])
|
||||
return updated_port
|
||||
|
|
|
@ -139,6 +139,8 @@ class OvsDBParser(object):
|
|||
for item in fs:
|
||||
if item.startswith('external_ids'):
|
||||
res['external_ids'] = self._parse_one_item(item)
|
||||
elif item.startswith('options'):
|
||||
res['options'] = self._parse_one_item(item)
|
||||
elif item.startswith('ofport '):
|
||||
res['ofport'] = self._parse_one_item(item)
|
||||
elif item.startswith('name'):
|
||||
|
@ -150,6 +152,16 @@ class OvsDBParser(object):
|
|||
interfaces = self._ovsdb_list_intefaces(specify_interface)
|
||||
return self._parse_ovsdb_interfaces(interfaces)
|
||||
|
||||
def get_tunnel_ofport(self, chassis_ip):
|
||||
interfaces = self.list_interfaces()
|
||||
for item in interfaces:
|
||||
options = item.get('options', None)
|
||||
if options:
|
||||
remote_ip = options.get('remote_ip', None)
|
||||
if remote_ip == chassis_ip:
|
||||
return item.get('ofport', None)
|
||||
return None
|
||||
|
||||
def get_ofport(self, port_id):
|
||||
interfaces = self.list_interfaces()
|
||||
for item in interfaces:
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
# 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 dragonflow.tests.common import utils
|
||||
from dragonflow.tests.fullstack import test_base
|
||||
from dragonflow.tests.fullstack import test_objects as objects
|
||||
|
||||
from neutron.agent.linux.utils import wait_until_true
|
||||
from oslo_config import cfg
|
||||
|
||||
DF_PLUGIN = 'dragonflow.neutron.plugin.DFPlugin'
|
||||
|
||||
|
||||
class TestRemotePort(test_base.DFTestBase):
|
||||
|
||||
def test_remote_port(self):
|
||||
if cfg.CONF.core_plugin == DF_PLUGIN:
|
||||
return
|
||||
|
||||
network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api))
|
||||
network_id = network.create(network={'name': 'network1'})
|
||||
self.assertTrue(network.exists())
|
||||
|
||||
subnet_info = {'network_id': network_id,
|
||||
'cidr': '192.168.150.0/24',
|
||||
'gateway_ip': '192.168.150.1',
|
||||
'ip_version': 4,
|
||||
'name': 'subnet1',
|
||||
'enable_dhcp': True}
|
||||
subnet = self.store(objects.SubnetTestObj(self.neutron,
|
||||
self.nb_api,
|
||||
network_id=network_id))
|
||||
subnet.create(subnet_info)
|
||||
self.assertTrue(subnet.exists())
|
||||
|
||||
port = self.store(objects.PortTestObj(
|
||||
self.neutron, self.nb_api, network_id))
|
||||
port_body = {
|
||||
'admin_state_up': True,
|
||||
'name': 'port1',
|
||||
'network_id': network_id,
|
||||
'binding:profile': {
|
||||
'port_key': 'remote_port',
|
||||
'host_ip': '10.10.10.10'
|
||||
}
|
||||
}
|
||||
port.create(port=port_body)
|
||||
self.assertTrue(port.exists())
|
||||
|
||||
ovsdb = utils.OvsDBParser()
|
||||
wait_until_true(
|
||||
lambda: self._get_wanted_tunnel_port(ovsdb, '10.10.10.10'),
|
||||
timeout=30, sleep=2,
|
||||
exception=Exception('Could not get wanted tunnel port')
|
||||
)
|
||||
|
||||
port.close()
|
||||
self.assertFalse(port.exists())
|
||||
|
||||
utils.wait_until_none(
|
||||
lambda: ovsdb.get_tunnel_ofport('10.10.10.10'),
|
||||
timeout=30, sleep=2,
|
||||
exception=Exception('Could not delete wanted tunnel port')
|
||||
)
|
||||
|
||||
subnet.close()
|
||||
network.close()
|
||||
|
||||
def _get_wanted_tunnel_port(self, ovsdb, chassis_ip):
|
||||
if ovsdb.get_tunnel_ofport(chassis_ip):
|
||||
return True
|
||||
return False
|
|
@ -166,6 +166,7 @@ class TestDFMechDriver(base.BaseTestCase):
|
|||
fips = [{"subnet_id": "sub-1", "ip_address": "10.0.0.1"}]
|
||||
allowed_macs = 'ff:ff:ff:ff:ff:ff'
|
||||
tunnel_key = '9999'
|
||||
binding_profile = {"port_key": "remote_port", "host_ip": "20.0.0.2"}
|
||||
|
||||
securitygroups_db.SecurityGroupDbMixin._get_security_groups_on_port = \
|
||||
mock.Mock(return_value=None)
|
||||
|
@ -174,17 +175,17 @@ class TestDFMechDriver(base.BaseTestCase):
|
|||
self.driver.nb_api.allocate_tunnel_key = mock.Mock(
|
||||
return_value=tunnel_key)
|
||||
port_context = self._get_port_context(tenant_id, network_id, port_id,
|
||||
fips)
|
||||
fips, binding_profile)
|
||||
|
||||
self.driver.create_port_postcommit(port_context)
|
||||
self.driver.nb_api.create_lport.assert_called_with(
|
||||
id=port_id, lswitch_id=network_id, topic=tenant_id,
|
||||
macs=['aabb'], ips=['10.0.0.1'],
|
||||
name='FakePort', subnets=['sub-1'],
|
||||
enabled=True, chassis=None, tunnel_key=tunnel_key,
|
||||
device_owner='compute', device_id='d1',
|
||||
enabled=True, chassis="20.0.0.2", tunnel_key=tunnel_key,
|
||||
device_owner='compute', device_id='d1', remote_vtep=True,
|
||||
port_security_enabled=False, security_groups=[],
|
||||
binding_profile=None, binding_vnic_type='ovs',
|
||||
binding_profile=binding_profile, binding_vnic_type='ovs',
|
||||
allowed_address_pairs=[], version=self.dbversion)
|
||||
|
||||
def test_update_port_postcommit(self):
|
||||
|
@ -193,6 +194,7 @@ class TestDFMechDriver(base.BaseTestCase):
|
|||
port_id = '453'
|
||||
fips = [{"subnet_id": "sub-1", "ip_address": "10.0.0.1"}]
|
||||
tunnel_key = '9999'
|
||||
binding_profile = {"port_key": "remote_port", "host_ip": "20.0.0.2"}
|
||||
|
||||
securitygroups_db.SecurityGroupDbMixin._get_security_groups_on_port = \
|
||||
mock.Mock(return_value=None)
|
||||
|
@ -200,27 +202,28 @@ class TestDFMechDriver(base.BaseTestCase):
|
|||
self.driver.nb_api.allocate_tunnel_key = mock.Mock(
|
||||
return_value=tunnel_key)
|
||||
port_context = self._get_port_context(tenant_id, network_id, port_id,
|
||||
fips)
|
||||
fips, binding_profile)
|
||||
|
||||
self.driver.update_port_postcommit(port_context)
|
||||
self.driver.nb_api.update_lport.assert_called_with(
|
||||
id=port_id, name='FakePort', topic=tenant_id,
|
||||
macs=['aabb'], ips=['10.0.0.1'],
|
||||
subnets=['sub-1'],
|
||||
enabled=True, chassis=None, port_security_enabled=False,
|
||||
enabled=True, chassis="20.0.0.2", port_security_enabled=False,
|
||||
allowed_address_pairs=[], security_groups=[],
|
||||
device_owner='compute', device_id='d1',
|
||||
binding_profile=None, binding_vnic_type='ovs',
|
||||
version=self.dbversion)
|
||||
binding_profile=binding_profile, binding_vnic_type='ovs',
|
||||
version=self.dbversion, remote_vtep=True)
|
||||
|
||||
def test_delete_port_postcommit(self):
|
||||
tenant_id = 'test'
|
||||
network_id = '123'
|
||||
port_id = '453'
|
||||
fips = [{"subnet_id": "sub-1", "ip_address": "10.0.0.1"}]
|
||||
binding_profile = {"port_key": "remote_port", "host_ip": "20.0.0.2"}
|
||||
|
||||
port_context = self._get_port_context(tenant_id, network_id, port_id,
|
||||
fips)
|
||||
fips, binding_profile)
|
||||
|
||||
self.driver.delete_port_postcommit(port_context)
|
||||
self.driver.nb_api.delete_lport.assert_called_with(
|
||||
|
@ -345,7 +348,8 @@ class TestDFMechDriver(base.BaseTestCase):
|
|||
'db_version': self.dbversion}
|
||||
return FakeContext(subnet)
|
||||
|
||||
def _get_port_context(self, tenant_id, net_id, port_id, fixed_ips):
|
||||
def _get_port_context(self, tenant_id, net_id, port_id,
|
||||
fixed_ips, binding_profile):
|
||||
# sample data for testing purpose only.
|
||||
port = {'device_id': '1234',
|
||||
'name': 'FakePort',
|
||||
|
@ -358,7 +362,7 @@ class TestDFMechDriver(base.BaseTestCase):
|
|||
'admin_state_up': True,
|
||||
'status': 'ACTIVE',
|
||||
'network_id': net_id,
|
||||
'binding:profile': None,
|
||||
'binding:profile': binding_profile,
|
||||
'binding:vnic_type': 'ovs',
|
||||
'db_version': self.dbversion}
|
||||
return FakeContext(port)
|
||||
|
|
Loading…
Reference in New Issue