Source code for cobbler.items.network_interface

"""
All code belonging to network interfaces


Changelog (NetworkInterface):

V3.4.0 (unreleased):
    * Changes:
        * The NetworkInterface class is now a dedicated item type.
        * Constructor: ``kwargs`` can now be used to seed the item during creation.
        * ``virt_type``: str - Inheritable; One of "qemu", "kvm", "xenpv", "xenfv", "vmware", "vmwarew", "openvz" or
          "auto".
V3.3.4 (unreleased):
    * No changes
V3.3.3:
    * Changed:
        * ``to_dict()``: Accepts new parameter ``resolved``
        * ``virt_bridge``: Can now be set to ``<<inherit>>`` to get its value from the settings key
          ``default_virt_bridge``
V3.3.2:
    * No changes
V3.3.1:
    * No changes
V3.3.0:
    * This release switched from pure attributes to properties (getters/setters).
    * Added:
        * ``NetworkInterface`` is now a class.
        * Serialization still happens inside the system collection.
        * Properties have been used.
V3.2.2:
    * No changes
V3.2.1:
    * No changes
V3.2.0:
    * No changes
V3.1.2:
    * No changes
V3.1.1:
    * No changes
V3.1.0:
    * No changes
V3.0.1:
    * No changes
V3.0.0:
    * Field definitions now split of ``System`` class
V2.8.5:
    * Initial tracking of changes for the changelog.
    * Field definitions part of ``System`` class
    * Added:
        * ``mac_address``: str
        * ``connected_mode``: bool
        * ``mtu``: str
        * ``ip_address``: str
        * ``interface_type``: str - One of "na", "bond", "bond_slave", "bridge", bridge_slave", "bonded_bridge_slave",
          "infiniband"
        * ``interface_master``: str
        * ``bonding_opts``: str
        * ``bridge_opts``: str
        * ``management``: bool
        * ``static``: bool
        * ``netmask``: str
        * ``if_gateway``: str
        * ``dhcp_tag``: str
        * ``dns_name``: str
        * ``static_routes``: List[str]
        * ``virt_bridge``: str
        * ``ipv6_address``: str
        * ``ipv6_prefix``: str
        * ``ipv6_secondaries``: List[str]
        * ``ipv6_mtu``: str
        * ``ipv6_static_routes``: List[str]
        * ``ipv6_default_gateway``: str
        * ``cnames``: List[str]
"""

import copy
from ipaddress import AddressValueError
from typing import TYPE_CHECKING, Any, List, Type, Union

from cobbler import enums, utils, validate
from cobbler.cexceptions import CX
from cobbler.items.abstract.base_item import BaseItem
from cobbler.items.options.dns import DNSInterfaceOption
from cobbler.items.options.ip import IPv4Option, IPv6Option

if TYPE_CHECKING:
    from cobbler.api import CobblerAPI
    from cobbler.items.system import System

    InheritableProperty = property
else:
    from cobbler.decorator import InheritableProperty


[docs] class NetworkInterface(BaseItem): """ A subobject of a Cobbler System which represents the network interfaces """ # Constants TYPE_NAME = "network_interface" COLLECTION_TYPE = "network_interface" def __init__( self, api: "CobblerAPI", system_uid: str, *args: Any, **kwargs: Any, ) -> None: """ Constructor. :param api: The Cobbler API object which is used for resolving information. :param system_uid: The UID of the system, to which the interface is attached to. """ super().__init__(api) # Prevent attempts to clear the to_dict cache before the object is initialized. self._has_initialized = False self._bonding_opts = "" self._bridge_opts = "" self._connected_mode = False self._dhcp_tag = "" self._dns = DNSInterfaceOption(api=api, item=self) self._if_gateway = "" self._interface_master = "" self._interface_type = enums.NetworkInterfaceType.NA self._ipv4 = IPv4Option(api=api, item=self) self._ipv6 = IPv6Option(api=api, item=self) self._mac_address = "" self._management = False self._static = False self._virt_bridge = enums.VALUE_INHERITED self._system_uid = system_uid # Mark item as in-memory; gets reset in from_dict via kwargs if lazy_start is enabled self._inmemory = True if len(kwargs) > 0: self.from_dict(kwargs) if not self._has_initialized: self._has_initialized = True def __hash__(self): """ Hash table for NetworkInterfaces. Requires special handling if the uid value changes and the Item is present in set, frozenset, and dict types. :return: hash(uid). """ return hash( (self._mac_address, self._ipv4.address, self._dns.name, self._ipv6.address) )
[docs] def make_clone(self) -> "NetworkInterface": """ Clone this file object. Please manually adjust all value yourself to make the cloned object unique. :return: The cloned instance of this object. """ _dict = copy.deepcopy(self.to_dict()) # Drop attributes which are computed from other attributes computed_properties = ["uid"] for property_name in computed_properties: _dict.pop(property_name, None) return NetworkInterface(self.api, **_dict)
def _resolve(self, property_name: List[str]) -> Any: """ Resolve the ``property_name`` value in the object tree. For Network Interfaces this means that we just look up a default value in the settings. :param property_name: The property name to resolve. :raises AttributeError: In case one of the objects try to inherit from a parent that does not have ``property_name``. :return: The resolved value. """ settings_name = property_name[-1] if property_name[-1] == "owners": settings_name = "default_ownership" raw_value = self.__get_raw_value(self, property_name) if raw_value == enums.VALUE_INHERITED: return getattr(self.api.settings(), settings_name) else: return raw_value def _resolve_enum( self, property_name: List[str], enum_type: Type[enums.ConvertableEnum] ) -> Any: # The networkinterface doesn't have any enum types that need resolving return None def _resolve_list(self, property_name: List[str]) -> Any: # The networkinterface doesn't have any enum types that need resolving return None def __get_raw_value(self, obj: Any, property_name: List[str]) -> Any: """ Retrieves the raw value of a nested attribute from an object using a list of property names. :returns: The raw value of the property. :raises AttributeError: In case the property doesn't have the requested attribute. """ if hasattr(obj, f"_{property_name[0]}"): property_key = property_name.pop(0) if len(property_name) > 0: return self.__get_raw_value(getattr(obj, property_key), property_name) return getattr(obj, f"_{property_key}") raise AttributeError( f'Could not retrieve "{property_name[0]}" with obj "{obj}!' )
[docs] def check_if_valid(self): """ Checks if the current item passes logical validation. :raises CX: In case name is missing. Additionally either image or profile is required. """ super().check_if_valid() if self._system_uid == "": raise CX( f"Error with network interface {self.uid} - system_uid is required" )
@property def ipv4(self) -> IPv4Option: """ Returns the IPv4 configuration option for this network interface. """ return self._ipv4 @property def ipv6(self) -> IPv6Option: """ Returns the IPv6 configuration option for this network interface. """ return self._ipv6 @property def dns(self) -> DNSInterfaceOption: """ Returns the DNS configuration option for this network interface. """ return self._dns @property def dhcp_tag(self) -> str: """ dhcp_tag property. :getter: Returns the value for ``dhcp_tag``. :setter: Sets the value for the property ``dhcp_tag``. """ return self._dhcp_tag @dhcp_tag.setter def dhcp_tag(self, dhcp_tag: str): """ Setter for the dhcp_tag of the NetworkInterface class. :param dhcp_tag: The new dhcp tag. """ if not isinstance(dhcp_tag, str): # type: ignore raise TypeError( "Field dhcp_tag of object NetworkInterface needs to be of type str!" ) self._dhcp_tag = dhcp_tag @property def static(self) -> bool: """ static property. :getter: Returns the value for ``static``. :setter: Sets the value for the property ``static``. """ return self._static @static.setter def static(self, truthiness: bool): """ Setter for the static of the NetworkInterface class. :param truthiness: The new value if the interface is static or not. """ try: truthiness = self.api.input_boolean(truthiness) except TypeError as error: raise TypeError( "Field static of NetworkInterface needs to be of Type bool!" ) from error self._static = truthiness @property def management(self) -> bool: """ management property. :getter: Returns the value for ``management``. :setter: Sets the value for the property ``management``. """ return self._management @management.setter def management(self, truthiness: bool): """ Setter for the management of the NetworkInterface class. :param truthiness: The new value for management. """ try: truthiness = self.api.input_boolean(truthiness) except TypeError as error: raise TypeError( "Field management of object NetworkInterface needs to be of type bool!" ) from error self._management = truthiness @property def mac_address(self) -> str: """ mac_address property. :getter: Returns the value for ``mac_address``. :setter: Sets the value for the property ``mac_address``. """ return self._mac_address @mac_address.setter def mac_address(self, address: str): """ Set MAC address on interface. :param address: MAC address :raises CX: In case there a random mac can't be computed """ if self._mac_address == address: return address = validate.mac_address(address) if address == "random": # FIXME: Pass virt_type of system address = utils.get_random_mac(self.api) if address != "" and not self.api.settings().allow_duplicate_macs: matched = self.api.find_network_interface( return_list=True, mac_address=address ) if matched is None: matched = [] if not isinstance(matched, list): raise ValueError( "Unexpected search result during ip deduplication search!" ) for match in matched: raise ValueError( f'MAC address duplicate found "{address}". Object with the conflict has the name "{match.uid}"' ) old_mac_address = self._mac_address self._mac_address = address self.api.network_interfaces().update_index_value( self, "mac_address", old_mac_address, address ) @property def if_gateway(self) -> str: """ if_gateway property. :getter: Returns the value for ``if_gateway``. :setter: Sets the value for the property ``if_gateway``. """ return self._if_gateway @if_gateway.setter def if_gateway(self, gateway: str): """ Set the per-interface gateway. Exceptions are raised if the value is invalid. For details see :meth:`~cobbler.validate.ipv4_address`. :param gateway: IPv4 address for the gateway """ self._if_gateway = validate.ipv4_address(gateway) @InheritableProperty def virt_bridge(self) -> str: """ virt_bridge property. If set to ``<<inherit>>`` this will read the value from the setting "default_virt_bridge". :getter: Returns the value for ``virt_bridge``. :setter: Sets the value for the property ``virt_bridge``. """ if self._virt_bridge == enums.VALUE_INHERITED: return self.api.settings().default_virt_bridge return self._virt_bridge @virt_bridge.setter def virt_bridge(self, bridge: str): """ Setter for the virt_bridge of the NetworkInterface class. :param bridge: The new value for "virt_bridge". """ if not isinstance(bridge, str): # type: ignore raise TypeError( "Field virt_bridge of object NetworkInterface should be of type str!" ) if bridge == "": self._virt_bridge = enums.VALUE_INHERITED return self._virt_bridge = bridge @property def interface_type(self) -> enums.NetworkInterfaceType: """ interface_type property. :getter: Returns the value for ``interface_type``. :setter: Sets the value for the property ``interface_type``. """ return self._interface_type @interface_type.setter def interface_type(self, intf_type: Union[enums.NetworkInterfaceType, str]): """ Setter for the interface_type of the NetworkInterface class. :param intf_type: The interface type to be set. Will be autoconverted to the enum type if possible. """ if isinstance(intf_type, str): try: intf_type = enums.NetworkInterfaceType[intf_type.upper()] except KeyError as key_error: raise ValueError( f"intf_type choices include: {list(map(str, enums.NetworkInterfaceType))}" ) from key_error # Now it must be of the enum type if not isinstance(intf_type, enums.NetworkInterfaceType): # type: ignore raise TypeError( "interface intf_type value must be one of:" f"{','.join(list(map(str, enums.NetworkInterfaceType)))} or blank" ) self._interface_type = intf_type @property def interface_master(self) -> str: """ interface_master property. :getter: Returns the value for ``interface_master``. :setter: Sets the value for the property ``interface_master``. """ return self._interface_master @interface_master.setter def interface_master(self, interface_master: str): """ Setter for the interface_master of the NetworkInterface class. :param interface_master: The new interface master. """ if not isinstance(interface_master, str): # type: ignore raise TypeError( "Field interface_master of object NetworkInterface needs to be of type str!" ) self._interface_master = interface_master @property def bonding_opts(self) -> str: """ bonding_opts property. :getter: Returns the value for ``bonding_opts``. :setter: Sets the value for the property ``bonding_opts``. """ return self._bonding_opts @bonding_opts.setter def bonding_opts(self, bonding_opts: str): """ Setter for the bonding_opts of the NetworkInterface class. :param bonding_opts: The new bonding options for the interface. """ if not isinstance(bonding_opts, str): # type: ignore raise TypeError( "Field bonding_opts of object NetworkInterface needs to be of type str!" ) self._bonding_opts = bonding_opts @property def bridge_opts(self) -> str: """ bridge_opts property. :getter: Returns the value for ``bridge_opts``. :setter: Sets the value for the property ``bridge_opts``. """ return self._bridge_opts @bridge_opts.setter def bridge_opts(self, bridge_opts: str): """ Setter for the bridge_opts of the NetworkInterface class. :param bridge_opts: The new bridge options to set for the interface. """ if not isinstance(bridge_opts, str): # type: ignore raise TypeError( "Field bridge_opts of object NetworkInterface needs to be of type str!" ) self._bridge_opts = bridge_opts @property def ipv6_default_gateway(self) -> str: """ ipv6_default_gateway property. :getter: Returns the value for ``ipv6_default_gateway``. :setter: Sets the value for the property ``ipv6_default_gateway``. """ return self._ipv6_default_gateway @ipv6_default_gateway.setter def ipv6_default_gateway(self, address: str): """ Setter for the ipv6_default_gateway of the NetworkInterface class. :param address: The new default gateway for the interface. """ if not isinstance(address, str): # type: ignore raise TypeError( "Field ipv6_default_gateway of object NetworkInterface needs to be of type str!" ) if address == "" or utils.is_ip(address): self._ipv6_default_gateway = address.strip() return raise AddressValueError(f"invalid format of IPv6 IP address ({address})") @property def ipv6_static_routes(self) -> List[str]: """ ipv6_static_routes property. :getter: Returns the value for ``ipv6_static_routes``. :setter: Sets the value for the property `ipv6_static_routes``. """ return self._ipv6_static_routes @ipv6_static_routes.setter def ipv6_static_routes(self, routes: List[str]): """ Setter for the ipv6_static_routes of the NetworkInterface class. :param routes: The new static routes for the interface. """ self._ipv6_static_routes = self.api.input_string_or_list_no_inherit(routes) @property def connected_mode(self) -> bool: """ connected_mode property. :getter: Returns the value for ``connected_mode``. :setter: Sets the value for the property ``connected_mode``. """ return self._connected_mode @connected_mode.setter def connected_mode(self, truthiness: bool): """ Setter for the connected_mode of the NetworkInterface class. :param truthiness: The new value for connected mode of the interface. """ try: truthiness = self.api.input_boolean(truthiness) except TypeError as error: raise TypeError( "Field connected_mode of object NetworkInterface needs to be of type bool!" ) from error self._connected_mode = truthiness @property def system_name(self) -> str: """ system_name property. :getter: Returns the value for ``system_name``. :setter: Sets the value for the property ``system_name``. """ target_system = self.api.systems().listing.get(self._system_uid) if target_system is None: raise ValueError(f"Looking for system with uid {self._system_uid} failed.") return target_system.name @property def system_uid(self) -> str: """ system_uid property. :getter: Returns the value for ``system_uid``. :setter: Sets the value for the property ``system_uid``. """ return self._system_uid @system_uid.setter def system_uid(self, system_uid: str): """ Setter for the system_name of the NetworkInterface class. :param system_name: The new system_name. """ self._system_uid = system_uid @property def system(self) -> "System": """ Return the system the network interface belongs to. """ result = self.api.find_system(name=self._system_uid, return_list=False) if result is None: raise ValueError( f"System ({self._system_uid}) associated with interface ({self.uid}) not present!" ) if isinstance(result, list): raise TypeError("Result must be a single item!") return result