Beispiel #1
0
    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
Beispiel #2
0
 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')
Beispiel #3
0
 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')
Beispiel #4
0
 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')
Beispiel #5
0
 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')
Beispiel #6
0
    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')
Beispiel #7
0
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']
Beispiel #8
0
    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']
Beispiel #9
0
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']
Beispiel #10
0
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
Beispiel #11
0
    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
Beispiel #12
0
    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')
Beispiel #13
0
 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()
Beispiel #14
0
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')
Beispiel #15
0
 def test___init__(self):
     with self.assertRaises(RuntimeError):
         self.target = ovsdb.SimpleOVSDB('atool', 'atable')