|
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)
|