Blob Blame History Raw
#
# Copyright (c) 2020 Red Hat, Inc.
#
# This file is part of nmstate
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

from operator import attrgetter
import os

from libnmstate.schema import InterfaceType
from libnmstate.schema import LinuxBridge as LB

from .base_iface import NisporPluginBaseIface
from .bridge_port_vlan import get_port_vlan_info

OPT = LB.Options


NISPOR_USER_HZ_KEYS = [
    "forward_delay",
    "ageing_time",
    "hello_time",
    "max_age",
]


NISPOR_MULTICAST_ROUTERS_INT_MAP = {
    "disabled": 0,
    "temp_query": 1,
    "perm": 2,
    "temp": 3,
}

LSM_BRIDGE_OPTIONS_2_NISPOR = {
    OPT.MAC_AGEING_TIME: "ageing_time",
    OPT.GROUP_FORWARD_MASK: "group_fwd_mask",
    OPT.MULTICAST_SNOOPING: "multicast_snooping",
    OPT.HELLO_TIMER: "hello_timer",
    OPT.GC_TIMER: "gc_timer",
    OPT.MULTICAST_ROUTER: "multicast_router",
    OPT.GROUP_ADDR: "group_addr",
    OPT.HASH_MAX: "multicast_hash_max",
    OPT.MULTICAST_LAST_MEMBER_COUNT: "multicast_last_member_count",
    OPT.MULTICAST_LAST_MEMBER_INTERVAL: "multicast_last_member_interval",
    OPT.MULTICAST_QUERIER: "multicast_querier",
    OPT.MULTICAST_QUERIER_INTERVAL: "multicast_querier_interval",
    OPT.MULTICAST_QUERY_USE_IFADDR: "multicast_query_use_ifaddr",
    OPT.MULTICAST_QUERY_INTERVAL: "multicast_query_interval",
    OPT.MULTICAST_QUERY_RESPONSE_INTERVAL: "multicast_query_response_interval",
    OPT.MULTICAST_STARTUP_QUERY_COUNT: "multicast_startup_query_count",
    OPT.MULTICAST_STARTUP_QUERY_INTERVAL: "multicast_startup_query_interval",
}


LSM_BRIDGE_STP_OPTIONS_2_NISPOR = {
    LB.STP.ENABLED: "stp_state",
    LB.STP.PRIORITY: "priority",
    LB.STP.FORWARD_DELAY: "forward_delay",
    LB.STP.HELLO_TIME: "hello_time",
    LB.STP.MAX_AGE: "max_age",
}


class NisporPluginBridgeIface(NisporPluginBaseIface):
    def __init__(self, np_iface, np_ports):
        super().__init__(np_iface)
        self._np_ports = np_ports

    @property
    def type(self):
        return InterfaceType.LINUX_BRIDGE

    def _options_to_dict(self):
        np_options = self._np_iface.options
        info = _get_nispor_options(LSM_BRIDGE_OPTIONS_2_NISPOR, np_options)
        info[LB.STP_SUBTREE] = _get_nispor_options(
            LSM_BRIDGE_STP_OPTIONS_2_NISPOR, np_options
        )
        return info

    def _ports_to_dict(self):
        info = []
        for np_port in sorted(self._np_ports, key=attrgetter("name")):
            np_sub = np_port.subordinate_state
            port_info = {
                LB.Port.NAME: np_port.name,
                LB.Port.STP_HAIRPIN_MODE: np_sub.hairpin_mode,
                LB.Port.STP_PATH_COST: np_sub.stp_path_cost,
                LB.Port.STP_PRIORITY: np_sub.stp_priority,
            }
            if self._np_iface.options.get("vlan_filtering") and np_sub.vlans:
                port_info[LB.Port.VLAN_SUBTREE] = get_port_vlan_info(np_sub)

            info.append(port_info)
        return info

    def to_dict(self, config_only):
        info = super().to_dict(config_only)
        info[LB.CONFIG_SUBTREE] = {
            LB.OPTIONS_SUBTREE: self._options_to_dict(),
            LB.PORT_SUBTREE: self._ports_to_dict(),
        }
        return info


# Some keys nispor is showing as human readable value, while lsm need integer
# TODO: Use better approach after https://github.com/nispor/nispor/issues/11
def _nispor_value_to_lsm(np_name, np_value):
    """
    Return None if convertion failed.
    Return original value is convertion is not required
    """
    value = np_value
    if np_name == "multicast_router":
        value = NISPOR_MULTICAST_ROUTERS_INT_MAP.get(np_value)
    elif np_name == "stp_state":
        value = np_value in ("kernel_stp", "user_stp")
    elif np_name == "group_addr":
        value = value.upper()
    return value


def _get_nispor_options(np_prop_map, np_options):
    user_hz = os.sysconf("SC_CLK_TCK")
    info = {}
    for key, np_prop_name in np_prop_map.items():
        if np_prop_name in np_options:
            value = _nispor_value_to_lsm(
                np_prop_name, np_options[np_prop_name]
            )
            if value is None:
                continue
            if np_prop_name in NISPOR_USER_HZ_KEYS:
                value = int(value / user_hz)
            info[key] = value
    return info