def configure_ovs_dpdk(self): """Configure DPDK specific bits in Open vSwitch. :returns: Whether something changed :rtype: bool """ something_changed = False dpdk_context = os_context.OVSDPDKDeviceContext( bridges_key=self.bridges_key) opvs = ch_ovsdb.SimpleOVSDB('ovs-vsctl').open_vswitch other_config_fmt = 'other_config:{}' for row in opvs: for k, v in ( ('dpdk-lcore-mask', dpdk_context.cpu_mask()), ('dpdk-socket-mem', dpdk_context.socket_memory()), ('dpdk-init', 'true'), ('dpdk-extra', dpdk_context.pci_whitelist()), ): if row.get(other_config_fmt.format(k)) != v: something_changed = True if v: opvs.set('.', other_config_fmt.format(k), v) else: opvs.remove('.', 'other_config', k) return something_changed
def test_set(self): self.target = ovsdb.SimpleOVSDB('ovs-vsctl') self.patch_object(ovsdb.utils, '_run') self.target.interface.set('1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids:other', 'value') self._run.assert_called_once_with( 'ovs-vsctl', 'set', 'interface', '1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids:other=value')
def test_remove(self): self.target = ovsdb.SimpleOVSDB('ovs-vsctl') self.patch_object(ovsdb.utils, '_run') self.target.interface.remove('1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids', 'other') self._run.assert_called_once_with( 'ovs-vsctl', 'remove', 'interface', '1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids', 'other')
def test_clear(self): self.target = ovsdb.SimpleOVSDB('ovs-vsctl', 'atable') self.patch_object(ovsdb.utils, '_run') self.target.clear('1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids') self._run.assert_called_once_with( 'ovs-vsctl', 'clear', 'atable', '1e21ba48-61ff-4b32-b35e-cb80411da351', 'external_ids')
def test__find_tbl(self): self.target = ovsdb.SimpleOVSDB('ovs-vsctl') self.patch_object(ovsdb.utils, '_run') self._run.return_value = VSCTL_BRIDGE_TBL self.maxDiff = None expect = { '_uuid': uuid.UUID('1e21ba48-61ff-4b32-b35e-cb80411da351'), 'auto_attach': [], 'controller': [], 'datapath_id': '0000a0369fdd3890', 'datapath_type': '', 'datapath_version': '<unknown>', 'external_ids': { 'charm-ovn-chassis': 'managed', 'other': 'value', }, 'fail_mode': [], 'flood_vlans': [], 'flow_tables': {}, 'ipfix': [], 'mcast_snooping_enable': False, 'mirrors': [], 'name': 'br-test', 'netflow': [], 'other_config': {}, 'ports': [ uuid.UUID('617f9359-77e2-41be-8af6-4c44e7a6bcc3'), uuid.UUID('da840476-8809-4107-8733-591f4696f056') ], 'protocols': ['OpenFlow10', 'OpenFlow13', 'OpenFlow14'], 'rstp_enable': False, 'rstp_status': {}, 'sflow': [], 'status': {}, 'stp_enable': False } # this in effect also tests the __iter__ front end method for el in self.target.bridge: self.assertDictEqual(el, expect) break self._run.assert_called_once_with('ovs-vsctl', '-f', 'json', 'find', 'bridge') self._run.reset_mock() # this in effect also tests the find front end method for el in self.target.bridge.find(condition='name=br-test'): break self._run.assert_called_once_with('ovs-vsctl', '-f', 'json', 'find', 'bridge', 'name=br-test')
def configure_ovs(self, sb_conn): """Global Open vSwitch configuration tasks. :param sb_conn: Comma separated string of OVSDB connection methods. :type sb_conn: str Note that running this method will restart the ``openvswitch-switch`` service if required. """ if self.check_if_paused() != (None, None): ch_core.hookenv.log( 'Unit is paused, defer global Open vSwitch ' 'configuration tasks.', level=ch_core.hookenv.INFO) return # Must make sure the service runs otherwise calls to ``ovs-vsctl`` will # hang. ch_core.host.service_start('openvswitch-switch') restart_required = False # NOTE(fnordahl): Due to what is probably a bug in Open vSwitch # subsequent calls to ``ovs-vsctl set-ssl`` will hang indefinitely # Work around this by passing ``--no-wait``. self.run('ovs-vsctl', '--no-wait', 'set-ssl', self.options.ovn_key, self.options.ovn_cert, self.options.ovn_ca_cert) # The local ``ovn-controller`` process will retrieve information about # how to connect to OVN from the local Open vSwitch database. self.run('ovs-vsctl', 'set', 'open', '.', 'external-ids:ovn-encap-type=geneve', '--', 'set', 'open', '.', 'external-ids:ovn-encap-ip={}'.format(self.get_data_ip()), '--', 'set', 'open', '.', 'external-ids:system-id={}'.format(self.get_ovs_hostname())) self.run('ovs-vsctl', 'set', 'open', '.', 'external-ids:ovn-remote={}'.format(sb_conn)) if self.enable_openstack: # OpenStack Nova expects the local OVSDB server to listen to # TCP port 6640 on localhost. We use this for the OVN metadata # agent too, as it allows us to run it as a non-root user. # LP: #1852200 target = 'ptcp:6640:127.0.0.1' for el in ch_ovsdb.SimpleOVSDB('ovs-vsctl').manager.find( 'target="{}"'.format(target)): break else: self.run('ovs-vsctl', '--id', '@manager', 'create', 'Manager', 'target="{}"'.format(target), '--', 'add', 'Open_vSwitch', '.', 'manager_options', '@manager') if self.options.enable_hardware_offload: restart_required = self.configure_ovs_hw_offload() elif self.options.enable_dpdk: restart_required = self.configure_ovs_dpdk() if restart_required: ch_core.host.service_restart('openvswitch-switch')
def uuid_for_port(port_name): """Get UUID of named port. :param port_name: Name of port. :type port_name: str :returns: Port UUID. :rtype: Optional[uuid.UUID] """ for port in ch_ovsdb.SimpleOVSDB('ovs-vsctl').port.find( 'name={}'.format(port_name)): return port['_uuid']
def get_ovs_hostname(): """Get hostname (FQDN) from Open vSwitch. :returns: Hostname as configured in Open vSwitch :rtype: str :raises: KeyError """ # The Open vSwitch ``ovs-ctl`` script has already done the dirty work # of retrieving the hosts FQDN, use it. # # In the unlikely event of the hostname not being set in the database # we want to error out as this will cause malfunction. for row in ch_ovsdb.SimpleOVSDB('ovs-vsctl').open_vswitch: return row['external_ids']['hostname']
def bridge_for_port(port_uuid): """Find which bridge a port is on. :param port_uuid: UUID of port. :type port_uuid: uuid.UUID :returns: Name of bridge or None. :rtype: Optional[str] """ for bridge in ch_ovsdb.SimpleOVSDB('ovs-vsctl').bridge: # If there is a single port on a bridge the ports property will not be # a list. ref: juju/charm-helpers#510 if (isinstance(bridge['ports'], list) and port_uuid in bridge['ports'] or port_uuid == bridge['ports']): return bridge['name']
def patch_ports_on_bridge(bridge): """Find patch ports on a bridge. :param bridge: Name of bridge :type bridge: str :returns: Iterator with bridge and port name for both ends of a patch. :rtype: Iterator[Patch[PatchPort[str,str],PatchPort[str,str]]] :raises: ValueError """ # On any given vSwitch there will be a small number of patch ports, so we # start by iterating over ports with type `patch` then look up which bridge # they belong to and act on any ports that match the criteria. for interface in ch_ovsdb.SimpleOVSDB('ovs-vsctl').interface.find( 'type=patch'): for port in ch_ovsdb.SimpleOVSDB('ovs-vsctl').port.find( 'name={}'.format(interface['name'])): if bridge_for_port(port['_uuid']) == bridge: this_end = PatchPort(bridge, port['name']) other_end = PatchPort( bridge_for_port(uuid_for_port( interface['options']['peer'])), interface['options']['peer']) yield (Patch(this_end, other_end)) # We expect one result and it is ok if it turns out to be a port # for a different bridge. However we need a break here to satisfy # the for/else check which is in place to detect interface referring # to non-existent port. break else: raise ValueError('Port for interface named "{}" does unexpectedly ' 'not exist.'.format(interface['name'])) else: # Allow our caller to handle no patch ports found gracefully, in # reference to PEP479 just doing a return will provide a empty iterator # and not None. return
def configure_ovs_hw_offload(self): """Configure hardware offload specific bits in Open vSwitch. :returns: Whether something changed :rtype: bool """ something_changed = False opvs = ch_ovsdb.SimpleOVSDB('ovs-vsctl').open_vswitch other_config_fmt = 'other_config:{}' for row in opvs: for k, v in (('hw-offload', 'true'), ('max-idle', '30000'), ): if row.get(other_config_fmt.format(k)) != v: something_changed = True if v: opvs.set('.', other_config_fmt.format(k), v) else: opvs.remove('.', 'other_config', k) return something_changed
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 test___init__(self): with self.assertRaises(RuntimeError): self.target = ovsdb.SimpleOVSDB('atool') with self.assertRaises(AttributeError): self.target = ovsdb.SimpleOVSDB('ovs-vsctl') self.target.unknown_table.find()
def remove_per_bridge_controllers(): """Remove per bridge controllers.""" bridges = ch_ovsdb.SimpleOVSDB('ovs-vsctl').bridge for bridge in bridges: if bridge['controller']: bridges.clear(str(bridge['_uuid']), 'controller')
def test___init__(self): with self.assertRaises(RuntimeError): self.target = ovsdb.SimpleOVSDB('atool', 'atable')