Blame libnmstate/ifaces/bond.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
import contextlib
Packit b9ca78
import logging
Packit b9ca78
Packit b9ca78
from libnmstate.error import NmstateValueError
Packit b9ca78
from libnmstate.schema import Bond
Packit b9ca78
from libnmstate.schema import BondMode
Packit b9ca78
from libnmstate.schema import Interface
Packit b9ca78
Packit b9ca78
from .base_iface import BaseIface
Packit b9ca78
Packit b9ca78
Packit b9ca78
class BondIface(BaseIface):
Packit b9ca78
    _MODE_CHANGE_METADATA = "_bond_mode_changed"
Packit b9ca78
Packit b9ca78
    def sort_slaves(self):
Packit b9ca78
        if self.slaves:
Packit b9ca78
            self.raw[Bond.CONFIG_SUBTREE][Bond.SLAVES].sort()
Packit b9ca78
Packit b9ca78
    def __init__(self, info, save_to_disk=True):
Packit b9ca78
        super().__init__(info, save_to_disk)
Packit b9ca78
        self._normalize_options_values()
Packit b9ca78
        self._fix_bond_option_arp_monitor()
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def slaves(self):
Packit b9ca78
        return self.raw.get(Bond.CONFIG_SUBTREE, {}).get(Bond.SLAVES, [])
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def is_master(self):
Packit b9ca78
        return True
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def is_virtual(self):
Packit b9ca78
        return True
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def bond_mode(self):
Packit b9ca78
        return self.raw.get(Bond.CONFIG_SUBTREE, {}).get(Bond.MODE)
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def _bond_options(self):
Packit b9ca78
        return self.raw.get(Bond.CONFIG_SUBTREE, {}).get(
Packit b9ca78
            Bond.OPTIONS_SUBTREE, {}
Packit b9ca78
        )
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def is_bond_mode_changed(self):
Packit b9ca78
        return self.raw.get(BondIface._MODE_CHANGE_METADATA) is True
Packit b9ca78
Packit b9ca78
    def _set_bond_mode_changed_metadata(self, value):
Packit b9ca78
        self.raw[BondIface._MODE_CHANGE_METADATA] = value
Packit b9ca78
Packit b9ca78
    def _generate_bond_mode_change_metadata(self, ifaces):
Packit b9ca78
        if self.is_up:
Packit b9ca78
            cur_iface = ifaces.current_ifaces.get(self.name)
Packit b9ca78
            if cur_iface and self.bond_mode != cur_iface.bond_mode:
Packit b9ca78
                self._set_bond_mode_changed_metadata(True)
Packit b9ca78
Packit b9ca78
    def gen_metadata(self, ifaces):
Packit b9ca78
        super().gen_metadata(ifaces)
Packit b9ca78
        if not self.is_absent:
Packit b9ca78
            self._generate_bond_mode_change_metadata(ifaces)
Packit b9ca78
Packit b9ca78
    def pre_edit_validation_and_cleanup(self):
Packit b9ca78
        super().pre_edit_validation_and_cleanup()
Packit b9ca78
        if self.is_up:
Packit b9ca78
            self._discard_bond_option_when_mode_change()
Packit b9ca78
            self._validate_bond_mode()
Packit b9ca78
            self._fix_mac_restriced_mode()
Packit b9ca78
            self._validate_miimon_conflict_with_arp_interval()
Packit b9ca78
Packit b9ca78
    def _discard_bond_option_when_mode_change(self):
Packit b9ca78
        if self.is_bond_mode_changed:
Packit b9ca78
            logging.warning(
Packit b9ca78
                "Discarding all current bond options as interface "
Packit b9ca78
                f"{self.name} has bond mode changed"
Packit b9ca78
            )
Packit b9ca78
            self.raw[Bond.CONFIG_SUBTREE][
Packit b9ca78
                Bond.OPTIONS_SUBTREE
Packit b9ca78
            ] = self.original_dict.get(Bond.CONFIG_SUBTREE, {}).get(
Packit b9ca78
                Bond.OPTIONS_SUBTREE, {}
Packit b9ca78
            )
Packit b9ca78
            self._normalize_options_values()
Packit b9ca78
Packit b9ca78
    def _validate_bond_mode(self):
Packit b9ca78
        if self.bond_mode is None:
Packit b9ca78
            raise NmstateValueError(
Packit b9ca78
                f"Bond interface {self.name} does not have bond mode defined"
Packit b9ca78
            )
Packit b9ca78
Packit b9ca78
    def _fix_mac_restriced_mode(self):
Packit b9ca78
        if self.is_in_mac_restricted_mode:
Packit b9ca78
            if self.original_dict.get(Interface.MAC):
Packit b9ca78
                raise NmstateValueError(
Packit b9ca78
                    "MAC address cannot be specified in bond interface along "
Packit b9ca78
                    "with fail_over_mac active on active backup mode"
Packit b9ca78
                )
Packit b9ca78
            else:
Packit b9ca78
                self.raw.pop(Interface.MAC, None)
Packit b9ca78
Packit b9ca78
    def _validate_miimon_conflict_with_arp_interval(self):
Packit b9ca78
        bond_options = self._bond_options
Packit b9ca78
        if bond_options.get("miimon") and bond_options.get("arp_interval"):
Packit b9ca78
            raise NmstateValueError(
Packit b9ca78
                "Bond option arp_interval is conflicting with miimon, "
Packit b9ca78
                "please disable one of them by setting to 0"
Packit b9ca78
            )
Packit b9ca78
Packit b9ca78
    @staticmethod
Packit b9ca78
    def is_mac_restricted_mode(mode, bond_options):
Packit b9ca78
        return (
Packit b9ca78
            mode == BondMode.ACTIVE_BACKUP
Packit b9ca78
            and bond_options.get("fail_over_mac") == "active"
Packit b9ca78
        )
Packit b9ca78
Packit b9ca78
    @property
Packit b9ca78
    def is_in_mac_restricted_mode(self):
Packit b9ca78
        """
Packit b9ca78
        Return True when Bond option does not allow MAC address defined.
Packit b9ca78
        In MAC restricted mode means:
Packit b9ca78
            Bond mode is BondMode.ACTIVE_BACKUP
Packit b9ca78
            Bond option "fail_over_mac" is active.
Packit b9ca78
        """
Packit b9ca78
        return BondIface.is_mac_restricted_mode(
Packit b9ca78
            self.bond_mode, self._bond_options
Packit b9ca78
        )
Packit b9ca78
Packit b9ca78
    def _normalize_options_values(self):
Packit b9ca78
        if self._bond_options:
Packit b9ca78
            normalized_options = {}
Packit b9ca78
            for option_name, option_value in self._bond_options.items():
Packit b9ca78
                with contextlib.suppress(ValueError):
Packit b9ca78
                    option_value = int(option_value)
Packit b9ca78
                option_value = _get_bond_named_option_value_by_id(
Packit b9ca78
                    option_name, option_value
Packit b9ca78
                )
Packit b9ca78
                normalized_options[option_name] = option_value
Packit b9ca78
            self._bond_options.update(normalized_options)
Packit b9ca78
Packit b9ca78
    def _fix_bond_option_arp_monitor(self):
Packit b9ca78
        """
Packit b9ca78
        Adding 'arp_ip_target=""' when ARP monitor is disabled by
Packit b9ca78
        `arp_interval=0`
Packit b9ca78
        """
Packit b9ca78
        if self._bond_options:
Packit b9ca78
            _include_arp_ip_target_explictly_when_disable(
Packit b9ca78
                self.raw[Bond.CONFIG_SUBTREE][Bond.OPTIONS_SUBTREE]
Packit b9ca78
            )
Packit b9ca78
Packit b9ca78
    def state_for_verify(self):
Packit b9ca78
        state = super().state_for_verify()
Packit b9ca78
        if state.get(Bond.CONFIG_SUBTREE, {}).get(Bond.OPTIONS_SUBTREE):
Packit b9ca78
            _include_arp_ip_target_explictly_when_disable(
Packit b9ca78
                state[Bond.CONFIG_SUBTREE][Bond.OPTIONS_SUBTREE]
Packit b9ca78
            )
Packit b9ca78
        return state
Packit b9ca78
Packit b9ca78
    def remove_slave(self, slave_name):
Packit b9ca78
        self.raw[Bond.CONFIG_SUBTREE][Bond.SLAVES] = [
Packit b9ca78
            s for s in self.slaves if s != slave_name
Packit b9ca78
        ]
Packit b9ca78
        self.sort_slaves()
Packit b9ca78
Packit b9ca78
Packit b9ca78
class _BondNamedOptions:
Packit b9ca78
    AD_SELECT = "ad_select"
Packit b9ca78
    ARP_ALL_TARGETS = "arp_all_targets"
Packit b9ca78
    ARP_VALIDATE = "arp_validate"
Packit b9ca78
    FAIL_OVER_MAC = "fail_over_mac"
Packit b9ca78
    LACP_RATE = "lacp_rate"
Packit b9ca78
    MODE = "mode"
Packit b9ca78
    PRIMARY_RESELECT = "primary_reselect"
Packit b9ca78
    XMIT_HASH_POLICY = "xmit_hash_policy"
Packit b9ca78
Packit b9ca78
Packit b9ca78
_BOND_OPTIONS_NUMERIC_TO_NAMED_MAP = {
Packit b9ca78
    _BondNamedOptions.AD_SELECT: ("stable", "bandwidth", "count"),
Packit b9ca78
    _BondNamedOptions.ARP_ALL_TARGETS: ("any", "all"),
Packit b9ca78
    _BondNamedOptions.ARP_VALIDATE: (
Packit b9ca78
        "none",
Packit b9ca78
        "active",
Packit b9ca78
        "backup",
Packit b9ca78
        "all",
Packit b9ca78
        "filter",
Packit b9ca78
        "filter_active",
Packit b9ca78
        "filter_backup",
Packit b9ca78
    ),
Packit b9ca78
    _BondNamedOptions.FAIL_OVER_MAC: ("none", "active", "follow"),
Packit b9ca78
    _BondNamedOptions.LACP_RATE: ("slow", "fast"),
Packit b9ca78
    _BondNamedOptions.MODE: (
Packit b9ca78
        "balance-rr",
Packit b9ca78
        "active-backup",
Packit b9ca78
        "balance-xor",
Packit b9ca78
        "broadcast",
Packit b9ca78
        "802.3ad",
Packit b9ca78
        "balance-tlb",
Packit b9ca78
        "balance-alb",
Packit b9ca78
    ),
Packit b9ca78
    _BondNamedOptions.PRIMARY_RESELECT: ("always", "better", "failure"),
Packit b9ca78
    _BondNamedOptions.XMIT_HASH_POLICY: (
Packit b9ca78
        "layer2",
Packit b9ca78
        "layer3+4",
Packit b9ca78
        "layer2+3",
Packit b9ca78
        "encap2+3",
Packit b9ca78
        "encap3+4",
Packit b9ca78
    ),
Packit b9ca78
}
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _get_bond_named_option_value_by_id(option_name, option_id_value):
Packit b9ca78
    """
Packit b9ca78
    Given an option name and its value, return a named option value
Packit b9ca78
    if it exists.
Packit b9ca78
    Return the same option value as inputted if:
Packit b9ca78
    - The option name has no dual named and id values.
Packit b9ca78
    - The option value is not numeric.
Packit b9ca78
    - The option value has no corresponding named value (not in range).
Packit b9ca78
    """
Packit b9ca78
    option_value = _BOND_OPTIONS_NUMERIC_TO_NAMED_MAP.get(option_name)
Packit b9ca78
    if option_value:
Packit b9ca78
        with contextlib.suppress(ValueError, IndexError):
Packit b9ca78
            return option_value[int(option_id_value)]
Packit b9ca78
    return option_id_value
Packit b9ca78
Packit b9ca78
Packit b9ca78
def _include_arp_ip_target_explictly_when_disable(bond_options):
Packit b9ca78
    if (
Packit b9ca78
        bond_options.get("arp_interval") == 0
Packit b9ca78
        and "arp_ip_target" not in bond_options
Packit b9ca78
    ):
Packit b9ca78
        bond_options["arp_ip_target"] = ""