Blame libnmstate/ifaces/ovs.py

Packit b9ca78
#
Packit b9ca78
# Copyright (c) 2020 Red Hat, Inc.
Packit b9ca78
#
Packit b9ca78
# This file is part of nmstate
Packit b9ca78
#
Packit b9ca78
# This program is free software: you can redistribute it and/or modify
Packit b9ca78
# it under the terms of the GNU Lesser General Public License as published by
Packit b9ca78
# the Free Software Foundation, either version 2.1 of the License, or
Packit b9ca78
# (at your option) any later version.
Packit b9ca78
#
Packit b9ca78
# This program is distributed in the hope that it will be useful,
Packit b9ca78
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit b9ca78
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit b9ca78
# GNU Lesser General Public License for more details.
Packit b9ca78
#
Packit b9ca78
# You should have received a copy of the GNU Lesser General Public License
Packit b9ca78
# along with this program. If not, see <https://www.gnu.org/licenses/>.
Packit b9ca78
#
Packit b9ca78
Packit b9ca78
from copy import deepcopy
Packit b9ca78
from operator import itemgetter
Packit b9ca78
import subprocess
Packit b9ca78
Packit b9ca78
from libnmstate.error import NmstateValueError
Packit b9ca78
from libnmstate.schema import Interface
Packit b9ca78
from libnmstate.schema import InterfaceIP
Packit b9ca78
from libnmstate.schema import InterfaceType
Packit b9ca78
from libnmstate.schema import InterfaceState
Packit b9ca78
from libnmstate.schema import OVSBridge
Packit b9ca78
from libnmstate.schema import OVSInterface
Packit b9ca78
from libnmstate.schema import OvsDB
Packit b9ca78
Packit b9ca78
from .bridge import BridgeIface
Packit b9ca78
from .base_iface import BaseIface
Packit b9ca78
Packit b9ca78
Packit b9ca78
SYSTEMCTL_TIMEOUT_SECONDS = 5
Packit b9ca78
Packit b9ca78
Packit b9ca78
class OvsBridgeIface(BridgeIface):
Packit b9ca78
    @property
Packit b9ca78
    def _has_bond_port(self):
Packit b9ca78
        for port_config in self.port_configs:
Packit b9ca78
            if port_config.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE):
Packit b9ca78
                return True
Packit b9ca78
        return False
Packit b9ca78
Packit b9ca78
    def sort_slaves(self):
Packit b9ca78
        super().sort_slaves()
Packit b9ca78
        self._sort_bond_slaves()
Packit b9ca78
Packit b9ca78
    def _sort_bond_slaves(self):
Packit b9ca78
        # For slaves of ovs bond/link_aggregation
Packit b9ca78
        for port in self.port_configs:
Packit b9ca78
            lag = port.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE)
Packit b9ca78
            if lag:
Packit b9ca78
                lag.get(
Packit b9ca78
                    OVSBridge.Port.LinkAggregation.SLAVES_SUBTREE, []
Packit b9ca78
                ).sort(
Packit b9ca78
                    key=itemgetter(OVSBridge.Port.LinkAggregation.Slave.NAME)
Packit b9ca78
                )
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def slaves(self):
Packit b9ca78
        slaves = []
Packit b9ca78
        for port_config in self.port_configs:
Packit b9ca78
            lag = port_config.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE)
Packit b9ca78
            if lag:
Packit b9ca78
                lag_slaves = lag.get(
Packit b9ca78
                    OVSBridge.Port.LinkAggregation.SLAVES_SUBTREE, []
Packit b9ca78
                )
Packit b9ca78
                name_key = OVSBridge.Port.LinkAggregation.Slave.NAME
Packit b9ca78
                slaves += [s[name_key] for s in lag_slaves]
Packit b9ca78
            else:
Packit b9ca78
                slaves.append(port_config[OVSBridge.Port.NAME])
Packit b9ca78
        return slaves
Packit b9ca78
Packit b9ca78
    def gen_metadata(self, ifaces):
Packit b9ca78
        for slave_name in self.slaves:
Packit b9ca78
            slave_iface = ifaces[slave_name]
Packit b9ca78
            port_config = _lookup_ovs_port_by_interface(
Packit b9ca78
                self.port_configs, slave_iface.name
Packit b9ca78
            )
Packit b9ca78
            slave_iface.update(
Packit b9ca78
                {BridgeIface.BRPORT_OPTIONS_METADATA: port_config}
Packit b9ca78
            )
Packit b9ca78
            if slave_iface.type == InterfaceType.OVS_INTERFACE:
Packit b9ca78
                slave_iface.parent = self.name
Packit b9ca78
        super().gen_metadata(ifaces)
Packit b9ca78
Packit b9ca78
    def create_virtual_slave(self, slave_name):
Packit b9ca78
        """
Packit b9ca78
        When slave does not exists in merged desire state, it means it's an
Packit b9ca78
        OVS internal interface, create it.
Packit b9ca78
        """
Packit b9ca78
        slave_iface = OvsInternalIface(
Packit b9ca78
            {
Packit b9ca78
                Interface.NAME: slave_name,
Packit b9ca78
                Interface.TYPE: InterfaceType.OVS_INTERFACE,
Packit b9ca78
                Interface.STATE: InterfaceState.UP,
Packit b9ca78
            }
Packit b9ca78
        )
Packit b9ca78
        slave_iface.mark_as_changed()
Packit b9ca78
        slave_iface.set_master(self.name, self.type)
Packit b9ca78
        slave_iface.parent = self.name
Packit b9ca78
        return slave_iface
Packit b9ca78
Packit b9ca78
    def pre_edit_validation_and_cleanup(self):
Packit b9ca78
        super().pre_edit_validation_and_cleanup()
Packit b9ca78
        self._validate_ovs_lag_slave_count()
Packit b9ca78
Packit b9ca78
    def _validate_ovs_lag_slave_count(self):
Packit b9ca78
        for port in self.port_configs:
Packit b9ca78
            slaves_subtree = OVSBridge.Port.LinkAggregation.SLAVES_SUBTREE
Packit b9ca78
            lag = port.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE)
Packit b9ca78
            if lag and len(lag.get(slaves_subtree, ())) < 2:
Packit b9ca78
                raise NmstateValueError(
Packit b9ca78
                    f"OVS {self.name} LAG port {lag} has less than 2 slaves."
Packit b9ca78
                )
Packit b9ca78
Packit b9ca78
    def remove_slave(self, slave_name):
Packit b9ca78
        new_port_configs = []
Packit b9ca78
        for port in self.port_configs:
Packit b9ca78
            if port[OVSBridge.Port.NAME] == slave_name:
Packit b9ca78
                continue
Packit b9ca78
            lag = port.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE)
Packit b9ca78
            if lag:
Packit b9ca78
                new_port = deepcopy(port)
Packit b9ca78
                new_lag = new_port[OVSBridge.Port.LINK_AGGREGATION_SUBTREE]
Packit b9ca78
                lag_slaves = lag.get(
Packit b9ca78
                    OVSBridge.Port.LinkAggregation.SLAVES_SUBTREE
Packit b9ca78
                )
Packit b9ca78
                if lag_slaves:
Packit b9ca78
                    name_key = OVSBridge.Port.LinkAggregation.Slave.NAME
Packit b9ca78
                    new_lag[OVSBridge.Port.LinkAggregation.SLAVES_SUBTREE] = [
Packit b9ca78
                        s for s in lag_slaves if s[name_key] != slave_name
Packit b9ca78
                    ]
Packit b9ca78
                new_port_configs.append(new_port)
Packit b9ca78
            else:
Packit b9ca78
                new_port_configs.append(port)
Packit b9ca78
        self.raw[OVSBridge.CONFIG_SUBTREE][
Packit b9ca78
            OVSBridge.PORT_SUBTREE
Packit b9ca78
        ] = new_port_configs
Packit b9ca78
        self.sort_slaves()
Packit b9ca78
Packit b9ca78
    def state_for_verify(self):
Packit b9ca78
        state = super().state_for_verify()
Packit b9ca78
        _convert_external_ids_values_to_string(state)
Packit b9ca78
        return state
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _lookup_ovs_port_by_interface(ports, slave_name):
Packit b9ca78
    for port in ports:
Packit b9ca78
        lag_state = port.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE)
Packit b9ca78
        if lag_state and _is_ovs_lag_slave(lag_state, slave_name):
Packit b9ca78
            return port
Packit b9ca78
        elif port[OVSBridge.Port.NAME] == slave_name:
Packit b9ca78
            return port
Packit b9ca78
    return {}
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _is_ovs_lag_slave(lag_state, iface_name):
Packit b9ca78
    slaves = lag_state.get(OVSBridge.Port.LinkAggregation.SLAVES_SUBTREE, ())
Packit b9ca78
    for slave in slaves:
Packit b9ca78
        if slave[OVSBridge.Port.LinkAggregation.Slave.NAME] == iface_name:
Packit b9ca78
            return True
Packit b9ca78
    return False
Packit b9ca78
Packit b9ca78
Packit b9ca78
class OvsInternalIface(BaseIface):
Packit b9ca78
    def __init__(self, info, save_to_disk=True):
Packit b9ca78
        super().__init__(info, save_to_disk)
Packit b9ca78
        self._parent = None
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def is_virtual(self):
Packit b9ca78
        return True
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def can_have_ip_when_enslaved(self):
Packit b9ca78
        return True
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def parent(self):
Packit b9ca78
        return self._parent
Packit b9ca78
Packit b9ca78
    @parent.setter
Packit b9ca78
    def parent(self, value):
Packit b9ca78
        self._parent = value
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def need_parent(self):
Packit b9ca78
        return True
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def patch_config(self):
Packit b9ca78
        return self._info.get(OVSInterface.PATCH_CONFIG_SUBTREE)
Packit b9ca78
Packit b9ca78
    def state_for_verify(self):
Packit b9ca78
        state = super().state_for_verify()
Packit b9ca78
        _convert_external_ids_values_to_string(state)
Packit b9ca78
        return state
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def is_patch_port(self):
Packit b9ca78
        return self.patch_config and self.patch_config.get(
Packit b9ca78
            OVSInterface.Patch.PEER
Packit b9ca78
        )
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def peer(self):
Packit b9ca78
        return (
Packit b9ca78
            self.patch_config.get(OVSInterface.Patch.PEER)
Packit b9ca78
            if self.patch_config
Packit b9ca78
            else None
Packit b9ca78
        )
Packit b9ca78
Packit b9ca78
    def pre_edit_validation_and_cleanup(self):
Packit b9ca78
        super().pre_edit_validation_and_cleanup()
Packit b9ca78
        self._validate_ovs_mtu_mac_confliction()
Packit b9ca78
Packit b9ca78
    def _validate_ovs_mtu_mac_confliction(self):
Packit b9ca78
        if self.is_patch_port:
Packit b9ca78
            if (
Packit b9ca78
                self.original_dict.get(Interface.IPV4, {}).get(
Packit b9ca78
                    InterfaceIP.ENABLED
Packit b9ca78
                )
Packit b9ca78
                or self.original_dict.get(Interface.IPV6, {}).get(
Packit b9ca78
                    InterfaceIP.ENABLED
Packit b9ca78
                )
Packit b9ca78
                or self.original_dict.get(Interface.MTU)
Packit b9ca78
                or self.original_dict.get(Interface.MAC)
Packit b9ca78
            ):
Packit b9ca78
                raise NmstateValueError(
Packit b9ca78
                    "OVS Patch interface cannot contain MAC address, MTU"
Packit b9ca78
                    " or IP configuration."
Packit b9ca78
                )
Packit b9ca78
            else:
Packit b9ca78
                self._info.pop(Interface.MTU, None)
Packit b9ca78
                self._info.pop(Interface.MAC, None)
Packit b9ca78
Packit b9ca78
Packit b9ca78
def is_ovs_running():
Packit b9ca78
    try:
Packit b9ca78
        subprocess.run(
Packit b9ca78
            ("systemctl", "status", "openvswitch"),
Packit b9ca78
            stdout=subprocess.DEVNULL,
Packit b9ca78
            stderr=subprocess.DEVNULL,
Packit b9ca78
            check=True,
Packit b9ca78
            timeout=SYSTEMCTL_TIMEOUT_SECONDS,
Packit b9ca78
        )
Packit b9ca78
        return True
Packit b9ca78
    except Exception:
Packit b9ca78
        return False
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _convert_external_ids_values_to_string(iface_info):
Packit b9ca78
    external_ids = iface_info.get(OvsDB.OVS_DB_SUBTREE, {}).get(
Packit b9ca78
        OvsDB.EXTERNAL_IDS, {}
Packit b9ca78
    )
Packit b9ca78
    for key, value in external_ids.items():
Packit b9ca78
        external_ids[key] = str(value)