def test_add_bridge_bond(self): self.patch_object(ovs.subprocess, 'check_call') self.patch_object(ovs, '_dict_to_vsctl_set') self._dict_to_vsctl_set.return_value = [['--', 'fakekey=fakevalue']] portdata = { 'bond-mode': 'balance-tcp', 'lacp': 'active', 'other-config': { 'lacp-time': 'fast', }, } ifdatamap = { 'eth0': { 'type': 'dpdk', 'mtu-request': '9000', 'options': { 'dpdk-devargs': '0000:01:00.0', }, }, 'eth1': { 'type': 'dpdk', 'mtu-request': '9000', 'options': { 'dpdk-devargs': '0000:02:00.0', }, }, } ovs.add_bridge_bond('br-ex', 'bond42', ['eth0', 'eth1'], portdata, ifdatamap) self._dict_to_vsctl_set.assert_has_calls([ mock.call(portdata, 'port', 'bond42'), mock.call(ifdatamap['eth0'], 'Interface', 'eth0'), mock.call(ifdatamap['eth1'], 'Interface', 'eth1'), ], any_order=True) self.check_call.assert_called_once_with([ 'ovs-vsctl', '--', '--may-exist', 'add-bond', 'br-ex', 'bond42', 'eth0', 'eth1', '--', 'fakekey=fakevalue', '--', 'fakekey=fakevalue', '--', 'fakekey=fakevalue' ])
def configure_bridges(self): """Configure Open vSwitch bridges ports and interfaces.""" if self.check_if_paused() != (None, None): ch_core.hookenv.log( 'Unit is paused, defer Open vSwitch bridge ' 'port interface configuration tasks.', level=ch_core.hookenv.INFO) return bpi = os_context.BridgePortInterfaceMap(bridges_key=self.bridges_key) bond_config = os_context.BondConfig() ch_core.hookenv.log('BridgePortInterfaceMap: "{}"'.format(bpi.items()), level=ch_core.hookenv.DEBUG) # build map of bridges to ovn networks with existing if-mapping on host # and at the same time build ovn-bridge-mappings string ovn_br_map_str = '' ovnbridges = collections.defaultdict(list) config_obm = self.config['ovn-bridge-mappings'] or '' for pair in sorted(config_obm.split()): network, bridge = pair.split(':', 1) if bridge in bpi: ovnbridges[bridge].append(network) if ovn_br_map_str: ovn_br_map_str += ',' ovn_br_map_str += '{}:{}'.format(network, bridge) bridges = ch_ovsdb.SimpleOVSDB('ovs-vsctl').bridge ports = ch_ovsdb.SimpleOVSDB('ovs-vsctl').port for bridge in bridges.find('external_ids:charm-ovn-chassis=managed'): # remove bridges and ports that are managed by us and no longer in # config if bridge['name'] not in bpi and bridge['name'] != 'br-int': ch_core.hookenv.log( 'removing bridge "{}" as it is no longer' 'present in configuration for this unit.'.format( bridge['name']), level=ch_core.hookenv.DEBUG) ch_ovs.del_bridge(bridge['name']) else: for port in ports.find( 'external_ids:charm-ovn-chassis={}'.format( bridge['name'])): if port['name'] not in bpi[bridge['name']]: ch_core.hookenv.log( 'removing port "{}" from bridge ' '"{}" as it is no longer present ' 'in configuration for this unit.'.format( port['name'], bridge['name']), level=ch_core.hookenv.DEBUG) ch_ovs.del_bridge_port(bridge['name'], port['name']) brdata = { 'external-ids': { 'charm-ovn-chassis': 'managed' }, 'protocols': 'OpenFlow13,OpenFlow15', } if self.options.enable_dpdk: brdata.update({'datapath-type': 'netdev'}) else: brdata.update({'datapath-type': 'system'}) # we always update the integration bridge to make sure it has settings # apropriate for the current charm configuration ch_ovs.add_bridge( 'br-int', brdata={ **brdata, **{ # for the integration bridge we want the datapath to await # controller action before adding any flows. This is to avoid # switching packets between isolated logical networks before # `ovn-controller` starts up. 'fail-mode': 'secure', # Suppress in-band control flows for the integration bridge, # refer to ovn-architecture(7) for more details. 'other-config': { 'disable-in-band': 'true' }, }, }) for br in bpi: if br not in ovnbridges: continue ch_ovs.add_bridge( br, brdata={ **brdata, # for bridges used for external connectivity we want the # datapath to act like an ordinary MAC-learning switch. **{ 'fail-mode': 'standalone' }, }) for port in bpi[br]: ifdatamap = bpi.get_ifdatamap(br, port) ifdatamap = { port: { **ifdata, **{ 'external-ids': { 'charm-ovn-chassis': br } }, } for port, ifdata in ifdatamap.items() } if len(ifdatamap) > 1: ch_ovs.add_bridge_bond(br, port, list(ifdatamap.keys()), bond_config.get_ovs_portdata(port), ifdatamap) else: ch_ovs.add_bridge_port( br, port, ifdata=ifdatamap.get(port, {}), linkup=not self.options.enable_dpdk, promisc=None, portdata={'external-ids': { 'charm-ovn-chassis': br }}) opvs = ch_ovsdb.SimpleOVSDB('ovs-vsctl').open_vswitch if ovn_br_map_str: opvs.set('.', 'external_ids:ovn-bridge-mappings', ovn_br_map_str) # NOTE(fnordahl): Workaround for LP: #1848757 opvs.set('.', 'external_ids:ovn-cms-options', 'enable-chassis-as-gw') else: opvs.remove('.', 'external_ids', 'ovn-bridge-mappings') # NOTE(fnordahl): Workaround for LP: #1848757 opvs.remove('.', 'external_ids', 'ovn-cms-options')
def configure_ovs(): """Configure the OVS plugin. This function uses the config.yaml parameters ext-port, data-port and bridge-mappings to configure the bridges and ports on the ovs on the unit. Note that the ext-port is deprecated and data-port/bridge-mappings are preferred. Thus, if data-port is set, then ext-port is ignored (and if set, then it is removed from the set of bridges unless it is defined in bridge-mappings/data-port). A warning is issued, if both data-port and ext-port are set. """ status_set('maintenance', 'Configuring ovs') if not service_running('openvswitch-switch'): full_restart() # all bridges use the same datapath_type brdata = { 'datapath-type': determine_datapath_type(), } brdata.update(generate_external_ids()) add_bridge(INT_BRIDGE, brdata=brdata) add_bridge(EXT_BRIDGE, brdata=brdata) # If data-port is defined in the config, we can ignore ext-port value # and log an error to the unit's log if config('data-port') and config('ext-port'): log( "Both ext-port and data-port are set. ext-port is deprecated" " and is not used when data-port is set.", level=ERROR) ext_port_ctx = None if use_dvr(): ext_port_ctx = ExternalPortContext()() # Set ext-port only if data-port isn't defined. if not config('data-port') and ext_port_ctx and ext_port_ctx['ext_port']: add_bridge_port(EXT_BRIDGE, ext_port_ctx['ext_port'], ifdata=generate_external_ids(EXT_BRIDGE), portdata=generate_external_ids(EXT_BRIDGE)) modern_ovs = ovs_has_late_dpdk_init() bridgemaps = None portmaps = None if not use_dpdk(): # NOTE(jamespage): # Its possible to support both hardware offloaded 'direct' ports # and default 'openvswitch' ports on the same hypervisor, so # configure bridge mappings in addition to any hardware offload # enablement. portmaps = DataPortContext()() bridgemaps = parse_bridge_mappings(config('bridge-mappings')) for br in bridgemaps.values(): add_bridge(br, brdata=brdata) if not portmaps: continue for port, _br in portmaps.items(): if _br == br: if not is_linuxbridge_interface(port): add_bridge_port(br, port, promisc=True, ifdata=generate_external_ids(br), portdata=generate_external_ids(br)) else: log('{} is a Linux bridge: using Linux bridges in the ' 'data-port config is deprecated for removal after ' '21.10 release of OpenStack charms.'.format(port), level=WARNING) add_ovsbridge_linuxbridge( br, port, ifdata=generate_external_ids(br), portdata=generate_external_ids(br)) # NOTE(jamespage): # hw-offload and dpdk are mutually exclusive so log and error # and skip any subsequent DPDK configuration if use_dpdk() and use_hw_offload(): log( 'DPDK and Hardware offload are mutually exclusive, ' 'please disable enable-dpdk or enable-hardware-offload', level=ERROR) elif use_dpdk(): log('Configuring bridges with DPDK', level=DEBUG) # TODO(sahid): We should also take into account the # "physical-network-mtus" in case different MTUs are # configured based on physical networks. global_mtu = ( neutron_ovs_context.NeutronAPIContext()()['global_physnet_mtu']) dpdk_context = OVSDPDKDeviceContext() devices = dpdk_context.devices() portmaps = parse_data_port_mappings(config('data-port')) bridgemaps = parse_bridge_mappings(config('bridge-mappings')) bridge_port_interface_map = BridgePortInterfaceMap() bond_config = BondConfig() for br, port_iface_map in bridge_port_interface_map.items(): log('Adding DPDK bridge: {}:{}'.format(br, brdata), level=DEBUG) add_bridge(br, brdata=brdata) if modern_ovs: for port in port_iface_map.keys(): ifdatamap = bridge_port_interface_map.get_ifdatamap( br, port) # set external-ids for all interfaces for iface in ifdatamap: ifdatamap[iface].update(generate_external_ids(br)) # NOTE: DPDK bonds are referenced by name and can be found # in the data-port config, regular DPDK ports are # referenced by MAC addresses and their names should # never be found in data-port if port in portmaps.keys(): portdata = bond_config.get_ovs_portdata(port) portdata.update(generate_external_ids(br)) log('Adding DPDK bond: {}({}) to bridge: {}'.format( port, list(ifdatamap.keys()), br), level=DEBUG) add_bridge_bond(br, port, list(ifdatamap.keys()), portdata=portdata, ifdatamap=ifdatamap) else: log('Adding DPDK port: {} to bridge: {}'.format( port, br), level=DEBUG) ifdata = ifdatamap[port] add_bridge_port(br, port, ifdata=ifdata, portdata=generate_external_ids(br), linkup=False, promisc=None) if not modern_ovs: # port enumeration in legacy OVS-DPDK must follow alphabetic order # of the PCI addresses dev_idx = 0 for pci, mac in sorted(devices.items()): # if mac.entity is a bridge, then the port can be added # directly, otherwise it is a bond (supported only in # modern_ovs) or misconfiguration if mac.entity in bridgemaps.values(): ifdata = {'type': 'dpdk', 'mtu-request': global_mtu} ifdata.update(generate_external_ids(mac.entity)) ifname = 'dpdk{}'.format(dev_idx) log('Adding DPDK port {}:{} to bridge {}'.format( ifname, ifdata, mac.entity), level=DEBUG) add_bridge_port(mac.entity, ifname, ifdata=ifdata, portdata=generate_external_ids(mac.entity), linkup=False, promisc=None) else: log('DPDK device {} skipped, {} is not a bridge'.format( pci, mac.entity), level=WARNING) dev_idx += 1 target = config('ipfix-target') bridges = [INT_BRIDGE, EXT_BRIDGE] if bridgemaps: bridges.extend(bridgemaps.values()) elif portmaps: bridges.extend([bridge_mac.entity for bridge_mac in portmaps.values()]) if target: for bridge in bridges: disable_ipfix(bridge) enable_ipfix(bridge, target) else: # NOTE: removing ipfix setting from a bridge is idempotent and # will pass regardless of the existence of the setting for bridge in bridges: disable_ipfix(bridge) # Ensure this runs so that mtu is applied to data-port interfaces if # provided. # NOTE(ajkavanagh) for pause/resume we don't gate this as it's not a # running service, but rather running a few commands. if not init_is_systemd(): service_restart('os-charm-phy-nic-mtu')