From febf5b9f9c025513d606087234537ebe77f63376 Mon Sep 17 00:00:00 2001 From: Hong Hui Xiao Date: Wed, 12 Oct 2016 11:42:19 +0800 Subject: [PATCH] Create virtual tunnel port when specified When enable_virtual_tunnel_port, a virtual tunnel port will be created for each tunnel type in tunnel_types. Change-Id: Ia748dcf1d35b342e6dbb97253c631b1ee3496e3f Partially-implements: blueprint virtual-tunnel-port-support --- dragonflow/common/common_params.py | 6 +++ dragonflow/controller/df_local_controller.py | 37 ++++++++++++++++-- dragonflow/db/api_nb.py | 9 +++++ dragonflow/ovsdb/commands.py | 38 +++++++++++++++++++ dragonflow/ovsdb/impl_idl.py | 3 ++ dragonflow/ovsdb/objects.py | 26 ++++++++++--- dragonflow/ovsdb/vswitch_impl.py | 18 +++++++++ .../tests/fullstack/test_ovsdb_monitor.py | 29 ++++++++++++-- 8 files changed, 154 insertions(+), 12 deletions(-) diff --git a/dragonflow/common/common_params.py b/dragonflow/common/common_params.py index d2d2fc546..c0b8e898e 100644 --- a/dragonflow/common/common_params.py +++ b/dragonflow/common/common_params.py @@ -34,6 +34,12 @@ DF_OPTS = [ cfg.StrOpt('tunnel_type', default='geneve', help=_('The encapsulation type for the tunnel')), + cfg.BoolOpt('enable_virtual_tunnel_port', + default=False, + help=_("Enable virtual tunnel port")), + cfg.ListOpt('tunnel_types', + default=['geneve', 'vxlan', 'gre'], + help=_("The encapsulation types for the tunnels")), cfg.StrOpt('apps_list', default='l2_app.L2App,' 'l3_proactive_app.L3ProactiveApp,' diff --git a/dragonflow/controller/df_local_controller.py b/dragonflow/controller/df_local_controller.py index 677c04349..99df59b7d 100644 --- a/dragonflow/controller/df_local_controller.py +++ b/dragonflow/controller/df_local_controller.py @@ -58,7 +58,13 @@ class DfLocalController(object): self.db_store = db_store.DbStore() self.chassis_name = chassis_name self.ip = cfg.CONF.df.local_ip - self.tunnel_type = cfg.CONF.df.tunnel_type + if cfg.CONF.df.enable_virtual_tunnel_port: + # Virtual tunnel port support multiple tunnel types together + self.tunnel_types = cfg.CONF.df.tunnel_types + else: + # TODO(xiaohhui): This should be removed once virtual tunnel port + # is implemented. + self.tunnel_types = cfg.CONF.df.tunnel_type self.sync_finished = False self.port_status_notifier = None nb_driver = df_utils.load_driver( @@ -321,7 +327,7 @@ class DfLocalController(object): chassis_lports = self.db_store.get_lports_by_remote_chassis(chassis) if not chassis_lports: chassis_value = {'id': chassis, 'ip': chassis, - 'tunnel_type': self.tunnel_type} + 'tunnel_type': self.tunnel_types} chassis_inst = models.Chassis(jsonutils.dumps(chassis_value)) self.chassis_created(chassis_inst) self.db_store.add_remote_chassis_lport(chassis, lport.get_id()) @@ -433,13 +439,36 @@ class DfLocalController(object): def register_chassis(self): chassis = self.nb_api.get_chassis(self.chassis_name) # TODO(gsagie) Support tunnel type change here ? - if chassis is None: self.nb_api.add_chassis(self.chassis_name, self.ip, - self.tunnel_type) + self.tunnel_types) + else: + if cfg.CONF.df.enable_virtual_tunnel_port: + old_tunnel_types = chassis.get_encap_type() + if (not isinstance(old_tunnel_types, list) or + set(self.tunnel_types) != set(old_tunnel_types)): + # There are 2 cases that needs update tunnel type in + # chassis. 1) User changes tunnel types in conf file + # 2) An old controller support only one type tunnel switch + # to support virtual tunnel port. + self.nb_api.update_chassis(self.chassis_name, + tunnel_type=self.tunnel_types) def create_tunnels(self): + if cfg.CONF.df.enable_virtual_tunnel_port: + tunnel_ports = self.vswitch_api.get_virtual_tunnel_ports() + for tunnel_port in tunnel_ports: + if tunnel_port.get_tunnel_type() not in self.tunnel_types: + self.vswitch_api.delete_port(tunnel_port) + + for t in self.tunnel_types: + # The customized ovs idl will ingore the command if the port + # already exists. + self.vswitch_api.add_virtual_tunnel_port(t) + + return + tunnel_ports = {} t_ports = self.vswitch_api.get_tunnel_ports() for t_port in t_ports: diff --git a/dragonflow/db/api_nb.py b/dragonflow/db/api_nb.py index 938f6e5e5..404cc6182 100644 --- a/dragonflow/db/api_nb.py +++ b/dragonflow/db/api_nb.py @@ -380,6 +380,15 @@ class NbApi(object): self.driver.create_key(db_models.Chassis.table_name, id, chassis_json, None) + def update_chassis(self, id, **columns): + chassis_json = self.driver.get_key('chassis', id) + chassis = jsonutils.loads(chassis_json) + for col, val in columns.items(): + chassis[col] = val + + chassis_json = jsonutils.dumps(chassis) + self.driver.set_key('chassis', id, chassis_json, None) + def get_lswitch(self, id, topic=None): try: lswitch_value = self.driver.get_key( diff --git a/dragonflow/ovsdb/commands.py b/dragonflow/ovsdb/commands.py index 6f7e3d377..75c7e7e01 100644 --- a/dragonflow/ovsdb/commands.py +++ b/dragonflow/ovsdb/commands.py @@ -86,3 +86,41 @@ class AddTunnelPort(commands.BaseCommand): ports = getattr(bridge, 'ports', []) ports.append(port) bridge.ports = ports + + +class AddVirtualTunnelPort(commands.BaseCommand): + def __init__(self, api, tunnel_type): + super(AddVirtualTunnelPort, self).__init__(api) + self.tunnel_type = tunnel_type + self.integration_bridge = cfg.CONF.df.integration_bridge + self.port = tunnel_type + "-vtp" + + def run_idl(self, txn): + port = idlutils.row_by_value(self.api.idl, 'Port', 'name', + self.port, None) + if port: + return + + bridge = idlutils.row_by_value(self.api.idl, 'Bridge', + 'name', self.integration_bridge) + + port = txn.insert(self.api._tables['Port']) + port.name = self.port + bridge.verify('ports') + ports = getattr(bridge, 'ports', []) + ports.append(port) + bridge.ports = ports + + iface = txn.insert(self.api._tables['Interface']) + txn.expected_ifaces.add(iface.uuid) + iface.name = self.port + iface.type = self.tunnel_type + options_dict = getattr(iface, 'options', {}) + options_dict['remote_ip'] = 'flow' + options_dict['key'] = 'flow' + options_dict['local_ip'] = cfg.CONF.df.local_ip + iface.options = options_dict + port.verify('interfaces') + ifaces = getattr(port, 'interfaces', []) + ifaces.append(iface) + port.interfaces = ifaces diff --git a/dragonflow/ovsdb/impl_idl.py b/dragonflow/ovsdb/impl_idl.py index 70f0c7291..987186b91 100644 --- a/dragonflow/ovsdb/impl_idl.py +++ b/dragonflow/ovsdb/impl_idl.py @@ -174,3 +174,6 @@ class DFOvsdbApi(impl_idl.OvsdbIdl): def add_patch_port(self, bridge, port, remote_name): return commands.AddPatchPort(self, bridge, port, remote_name) + + def add_virtual_tunnel_port(self, tunnel_type): + return commands.AddVirtualTunnelPort(self, tunnel_type) diff --git a/dragonflow/ovsdb/objects.py b/dragonflow/ovsdb/objects.py index 5184ce351..f80b2569e 100644 --- a/dragonflow/ovsdb/objects.py +++ b/dragonflow/ovsdb/objects.py @@ -130,15 +130,31 @@ class LocalInterface(object): self.remote_chassis_id)) -class OvsdbTunnelPort(object): +class OvsdbPort(object): + + def __init__(self, name): + super(OvsdbPort, self).__init__() + self.name = name + + def get_name(self): + return self.name + + +class OvsdbTunnelPort(OvsdbPort): def __init__(self, name, chassis_id): - super(OvsdbTunnelPort, self).__init__() - self.name = name + super(OvsdbTunnelPort, self).__init__(name) self.chassis_id = chassis_id def get_chassis_id(self): return self.chassis_id - def get_name(self): - return self.name + +class OvsdbVirtuaTunnelPort(OvsdbPort): + + def __init__(self, name, tunnel_type): + super(OvsdbVirtuaTunnelPort, self).__init__(name) + self.tunnel_type = tunnel_type + + def get_tunnel_type(self): + return self.tunnel_type diff --git a/dragonflow/ovsdb/vswitch_impl.py b/dragonflow/ovsdb/vswitch_impl.py index 7e36d1a22..e062e5ea4 100644 --- a/dragonflow/ovsdb/vswitch_impl.py +++ b/dragonflow/ovsdb/vswitch_impl.py @@ -98,9 +98,27 @@ class OvsApi(object): res.append(objects.OvsdbTunnelPort(port.name, chassis_id)) return res + def get_virtual_tunnel_ports(self): + ifaces = self.ovsdb.db_find( + 'Interface', ('options', '=', {'remote_ip': 'flow'}), + columns=['name', 'type']).execute() + tunnel_ports = [] + for iface in ifaces: + if (self.integration_bridge != + self._get_bridge_for_iface(iface['name'])): + continue + + tunnel_ports.append(objects.OvsdbVirtuaTunnelPort(iface['name'], + iface['type'])) + + return tunnel_ports + def add_tunnel_port(self, chassis): self.ovsdb.add_tunnel_port(chassis).execute() + def add_virtual_tunnel_port(self, tunnel_type): + self.ovsdb.add_virtual_tunnel_port(tunnel_type).execute() + def delete_port(self, switch_port): self.ovsdb.del_port(switch_port.get_name(), self.integration_bridge).execute() diff --git a/dragonflow/tests/fullstack/test_ovsdb_monitor.py b/dragonflow/tests/fullstack/test_ovsdb_monitor.py index 59cf75fca..426b7143d 100644 --- a/dragonflow/tests/fullstack/test_ovsdb_monitor.py +++ b/dragonflow/tests/fullstack/test_ovsdb_monitor.py @@ -24,9 +24,6 @@ class TestOvsdbMonitor(test_base.DFTestBase): self.vswitch_api = vswitch_impl.OvsApi(self.local_ip) self.vswitch_api.initialize(self.nb_api) - def tearDown(self): - super(TestOvsdbMonitor, self).tearDown() - def _check_wanted_vm_online(self, update, mac): if update.table != "ovsinterface": return False @@ -187,3 +184,29 @@ class TestOvsdbMonitor(test_base.DFTestBase): timeout=const.DEFAULT_RESOURCE_READY_TIMEOUT, sleep=1, exception=Exception('Port was not deleted') ) + + def test_virtual_tunnel_port(self): + def _clear_vtp(): + for tunnel_port in self.vswitch_api.get_virtual_tunnel_ports(): + self.vswitch_api.delete_port(tunnel_port) + + self.addCleanup(_clear_vtp) + + self.vswitch_api.add_virtual_tunnel_port('vxlan') + self.vswitch_api.add_virtual_tunnel_port('geneve') + + tunnel_ports = self.vswitch_api.get_virtual_tunnel_ports() + self.assertEqual(2, len(tunnel_ports)) + tunnel_types = set() + for t in tunnel_ports: + self.assertEqual(t.get_tunnel_type() + "-vtp", + t.get_name()) + tunnel_types.add(t.get_tunnel_type()) + + self.assertEqual({'vxlan', 'geneve'}, tunnel_types) + + for t in tunnel_ports: + self.vswitch_api.delete_port(t) + + tunnel_ports = self.vswitch_api.get_virtual_tunnel_ports() + self.assertFalse(tunnel_ports)