|
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"] = ""
|