Merge "Implement remote port function for dragonflow"

This commit is contained in:
Jenkins 2016-09-26 18:50:27 +00:00 committed by Gerrit Code Review
commit 643894e447
7 changed files with 187 additions and 17 deletions

View File

@ -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)

View File

@ -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__()

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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)