Blame libnmstate/ifaces/linux_bridge.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 libnmstate.error import NmstateValueError
Packit b9ca78
from libnmstate.schema import LinuxBridge
Packit b9ca78
Packit b9ca78
from .bridge import BridgeIface
Packit b9ca78
Packit b9ca78
# The aging_time, forward_delay, hello_time, max_age options are multipled by
Packit b9ca78
# 100(USER_HZ) when apply to kernel(via NM), so they are not impacted by this
Packit b9ca78
# integer round up/down issue.
Packit b9ca78
INTEGER_ROUNDED_OPTIONS = [
Packit b9ca78
    LinuxBridge.Options.MULTICAST_LAST_MEMBER_INTERVAL,
Packit b9ca78
    LinuxBridge.Options.MULTICAST_MEMBERSHIP_INTERVAL,
Packit b9ca78
    LinuxBridge.Options.MULTICAST_QUERIER_INTERVAL,
Packit b9ca78
    LinuxBridge.Options.MULTICAST_QUERY_RESPONSE_INTERVAL,
Packit b9ca78
    LinuxBridge.Options.MULTICAST_STARTUP_QUERY_INTERVAL,
Packit b9ca78
]
Packit b9ca78
Packit b9ca78
Packit b9ca78
class LinuxBridgeIface(BridgeIface):
Packit b9ca78
    @property
Packit b9ca78
    def _options(self):
Packit b9ca78
        return self.raw.get(LinuxBridge.CONFIG_SUBTREE, {}).get(
Packit b9ca78
            LinuxBridge.OPTIONS_SUBTREE, {}
Packit b9ca78
        )
Packit b9ca78
Packit b9ca78
    def pre_edit_validation_and_cleanup(self):
Packit b9ca78
        self._validate()
Packit b9ca78
        self._fix_vlan_filtering_mode()
Packit b9ca78
        super().pre_edit_validation_and_cleanup()
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def slaves(self):
Packit b9ca78
        return [p[LinuxBridge.Port.NAME] for p in self.port_configs]
Packit b9ca78
Packit b9ca78
    def _validate(self):
Packit b9ca78
        self._validate_vlan_filtering_trunk_tags()
Packit b9ca78
        self._validate_vlan_filtering_tag()
Packit b9ca78
        self._validate_vlan_filtering_enable_native()
Packit b9ca78
Packit b9ca78
    def _validate_vlan_filtering_trunk_tags(self):
Packit b9ca78
        for port_config in self.original_dict.get(
Packit b9ca78
            LinuxBridge.CONFIG_SUBTREE, {}
Packit b9ca78
        ).get(LinuxBridge.PORT_SUBTREE, []):
Packit b9ca78
            port_vlan_state = port_config.get(
Packit b9ca78
                LinuxBridge.Port.VLAN_SUBTREE, {}
Packit b9ca78
            )
Packit b9ca78
            vlan_mode = port_vlan_state.get(LinuxBridge.Port.Vlan.MODE)
Packit b9ca78
            trunk_tags = port_vlan_state.get(
Packit b9ca78
                LinuxBridge.Port.Vlan.TRUNK_TAGS, []
Packit b9ca78
            )
Packit b9ca78
Packit b9ca78
            if vlan_mode == LinuxBridge.Port.Vlan.Mode.ACCESS:
Packit b9ca78
                if trunk_tags:
Packit b9ca78
                    raise NmstateValueError(
Packit b9ca78
                        "Access port cannot have trunk tags"
Packit b9ca78
                    )
Packit b9ca78
            elif port_vlan_state:
Packit b9ca78
                if not trunk_tags:
Packit b9ca78
                    raise NmstateValueError(
Packit b9ca78
                        "A trunk port needs to specify trunk tags"
Packit b9ca78
                    )
Packit b9ca78
            for trunk_tag in trunk_tags:
Packit b9ca78
                _assert_vlan_filtering_trunk_tag(trunk_tag)
Packit b9ca78
Packit b9ca78
    def _validate_vlan_filtering_tag(self):
Packit b9ca78
        """
Packit b9ca78
        The "tag" is valid in access mode or tunk mode with
Packit b9ca78
        "enable-native:True".
Packit b9ca78
        """
Packit b9ca78
        for port_config in self.original_dict.get(
Packit b9ca78
            LinuxBridge.CONFIG_SUBTREE, {}
Packit b9ca78
        ).get(LinuxBridge.PORT_SUBTREE, []):
Packit b9ca78
            vlan_config = _get_port_vlan_config(port_config)
Packit b9ca78
            if (
Packit b9ca78
                vlan_config.get(LinuxBridge.Port.Vlan.TAG)
Packit b9ca78
                and _vlan_is_trunk_mode(vlan_config)
Packit b9ca78
                and not _vlan_is_enable_native(vlan_config)
Packit b9ca78
            ):
Packit b9ca78
                raise NmstateValueError(
Packit b9ca78
                    "Tag cannot be use in trunk mode without enable-native"
Packit b9ca78
                )
Packit b9ca78
Packit b9ca78
    def _validate_vlan_filtering_enable_native(self):
Packit b9ca78
        for port_config in self.original_dict.get(
Packit b9ca78
            LinuxBridge.CONFIG_SUBTREE, {}
Packit b9ca78
        ).get(LinuxBridge.PORT_SUBTREE, []):
Packit b9ca78
            vlan_config = _get_port_vlan_config(port_config)
Packit b9ca78
            if _vlan_is_access_mode(vlan_config) and _vlan_is_enable_native(
Packit b9ca78
                vlan_config
Packit b9ca78
            ):
Packit b9ca78
                raise NmstateValueError(
Packit b9ca78
                    "enable-native cannot be set in access mode"
Packit b9ca78
                )
Packit b9ca78
Packit b9ca78
    def _fix_vlan_filtering_mode(self):
Packit b9ca78
        for port_config in self.port_configs:
Packit b9ca78
            _vlan_config_clean_up(_get_port_vlan_config(port_config))
Packit b9ca78
Packit b9ca78
    def gen_metadata(self, ifaces):
Packit b9ca78
        super().gen_metadata(ifaces)
Packit b9ca78
        if not self.is_absent:
Packit b9ca78
            for port_config in self.port_configs:
Packit b9ca78
                ifaces[port_config[LinuxBridge.Port.NAME]].update(
Packit b9ca78
                    {BridgeIface.BRPORT_OPTIONS_METADATA: port_config}
Packit b9ca78
                )
Packit b9ca78
Packit b9ca78
    def remove_slave(self, slave_name):
Packit b9ca78
        if self._bridge_config:
Packit b9ca78
            self.raw[LinuxBridge.CONFIG_SUBTREE][LinuxBridge.PORT_SUBTREE] = [
Packit b9ca78
                port_config
Packit b9ca78
                for port_config in self.port_configs
Packit b9ca78
                if port_config[LinuxBridge.Port.NAME] != slave_name
Packit b9ca78
            ]
Packit b9ca78
        self.sort_slaves()
Packit b9ca78
Packit b9ca78
    @staticmethod
Packit b9ca78
    def is_integer_rounded(iface_state, current_iface_state):
Packit b9ca78
        for key, value in iface_state._options.items():
Packit b9ca78
            if key in INTEGER_ROUNDED_OPTIONS:
Packit b9ca78
                try:
Packit b9ca78
                    value = int(value)
Packit b9ca78
                    cur_value = int(current_iface_state._options.get(key))
Packit b9ca78
                except (TypeError, ValueError):
Packit b9ca78
                    continue
Packit b9ca78
                # With 250 HZ and 100 USER_HZ, every 8,000,000 will have 1
Packit b9ca78
                # deviation, caused by:
Packit b9ca78
                # * kernel set the value using clock_t_to_jiffies():
Packit b9ca78
                #       jiffies = int(clock * 100 / 250)
Packit b9ca78
                # * kernel showing the value using jiffies_to_clock_t():
Packit b9ca78
                #       clock =  int(int(jiffies * ( (10 ** 9 + 250/2) / 250)
Packit b9ca78
                #                    / 10 ** 9 * 100)
Packit b9ca78
                #
Packit b9ca78
                # The number 8,000,000 is found by exhaustion.
Packit b9ca78
                # There is no good way to detect kernel HZ in user space. Hence
Packit b9ca78
                # we check whether certain value is rounded.
Packit b9ca78
                if cur_value != value:
Packit b9ca78
                    if value >= 8 * (10 ** 6):
Packit b9ca78
                        if abs(value - cur_value) <= int(
Packit b9ca78
                            value / 8 * (10 ** 6)
Packit b9ca78
                        ):
Packit b9ca78
                            return key, value, cur_value
Packit b9ca78
                    else:
Packit b9ca78
                        if abs(value - cur_value) == 1:
Packit b9ca78
                            return key, value, cur_value
Packit b9ca78
Packit b9ca78
        return None, None, None
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _assert_vlan_filtering_trunk_tag(trunk_tag_state):
Packit b9ca78
    vlan_id = trunk_tag_state.get(LinuxBridge.Port.Vlan.TrunkTags.ID)
Packit b9ca78
    vlan_id_range = trunk_tag_state.get(
Packit b9ca78
        LinuxBridge.Port.Vlan.TrunkTags.ID_RANGE
Packit b9ca78
    )
Packit b9ca78
Packit b9ca78
    if vlan_id and vlan_id_range:
Packit b9ca78
        raise NmstateValueError(
Packit b9ca78
            "Trunk port cannot be configured by both id and range: {}".format(
Packit b9ca78
                trunk_tag_state
Packit b9ca78
            )
Packit b9ca78
        )
Packit b9ca78
    elif vlan_id_range:
Packit b9ca78
        if not (
Packit b9ca78
            {
Packit b9ca78
                LinuxBridge.Port.Vlan.TrunkTags.MIN_RANGE,
Packit b9ca78
                LinuxBridge.Port.Vlan.TrunkTags.MAX_RANGE,
Packit b9ca78
            }
Packit b9ca78
            <= set(vlan_id_range)
Packit b9ca78
        ):
Packit b9ca78
            raise NmstateValueError(
Packit b9ca78
                "Trunk port range requires min / max keys: {}".format(
Packit b9ca78
                    vlan_id_range
Packit b9ca78
                )
Packit b9ca78
            )
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _get_port_vlan_config(port_config):
Packit b9ca78
    return port_config.get(LinuxBridge.Port.VLAN_SUBTREE, {})
Packit b9ca78
Packit b9ca78
Packit b9ca78
# TODO: Group them into class _LinuxBridgePort
Packit b9ca78
def _vlan_is_access_mode(vlan_config):
Packit b9ca78
    return (
Packit b9ca78
        vlan_config.get(LinuxBridge.Port.Vlan.MODE)
Packit b9ca78
        == LinuxBridge.Port.Vlan.Mode.ACCESS
Packit b9ca78
    )
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _vlan_is_trunk_mode(vlan_config):
Packit b9ca78
    return (
Packit b9ca78
        vlan_config.get(LinuxBridge.Port.Vlan.MODE)
Packit b9ca78
        == LinuxBridge.Port.Vlan.Mode.TRUNK
Packit b9ca78
    )
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _vlan_is_enable_native(vlan_config):
Packit b9ca78
    return vlan_config.get(LinuxBridge.Port.Vlan.ENABLE_NATIVE) is True
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _vlan_config_clean_up(vlan_config):
Packit b9ca78
    _vlan_remove_enable_native_if_access_mode(vlan_config)
Packit b9ca78
    _vlan_remove_tag_if_trunk_mode_without_enable_native(vlan_config)
Packit b9ca78
    _vlan_remove_trunk_tag_if_access_mode(vlan_config)
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _vlan_remove_enable_native_if_access_mode(vlan_config):
Packit b9ca78
    if _vlan_is_access_mode(vlan_config):
Packit b9ca78
        vlan_config.pop(LinuxBridge.Port.Vlan.ENABLE_NATIVE, None)
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _vlan_remove_tag_if_trunk_mode_without_enable_native(vlan_config):
Packit b9ca78
    if _vlan_is_trunk_mode(vlan_config) and not _vlan_is_enable_native(
Packit b9ca78
        vlan_config
Packit b9ca78
    ):
Packit b9ca78
        vlan_config.pop(LinuxBridge.Port.Vlan.TAG, None)
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _vlan_remove_trunk_tag_if_access_mode(vlan_config):
Packit b9ca78
    if _vlan_is_access_mode(vlan_config):
Packit b9ca78
        vlan_config.pop(LinuxBridge.Port.Vlan.TRUNK_TAGS, None)