diff --git a/src/rhsmlib/facts/all.py b/src/rhsmlib/facts/all.py index 5c261ba54a..887cbac3c9 100644 --- a/src/rhsmlib/facts/all.py +++ b/src/rhsmlib/facts/all.py @@ -19,6 +19,7 @@ from rhsmlib.facts import kpatch from rhsmlib.facts import cloud_facts from rhsmlib.facts import pkg_arches +from rhsmlib.facts import network class AllFactsCollector(collector.FactsCollector): @@ -27,6 +28,7 @@ def __init__(self): collector.StaticFactsCollector, host_collector.HostCollector, hwprobe.HardwareCollector, + network.NetworkCollector, custom.CustomFactsCollector, insights.InsightsCollector, kpatch.KPatchCollector, diff --git a/src/rhsmlib/facts/hwprobe.py b/src/rhsmlib/facts/hwprobe.py index 0ea16f7720..b1ffb78551 100644 --- a/src/rhsmlib/facts/hwprobe.py +++ b/src/rhsmlib/facts/hwprobe.py @@ -20,11 +20,9 @@ import os import platform import re -import socket import subprocess import sys -from collections import defaultdict from datetime import datetime, timedelta from rhsmlib.facts import cpuinfo from rhsmlib.facts import collector @@ -33,13 +31,6 @@ log = logging.getLogger(__name__) -# There is no python3 version of python-ethtool -try: - import ethtool -except ImportError: - log.warning("Unable to import the 'ethtool' module.") - ethtool = None - class ClassicCheck: def is_registered_with_classic(self) -> bool: @@ -117,8 +108,6 @@ def __init__( self.get_proc_stat, self.get_cpu_info, self.get_ls_cpu_info, - self.get_network_info, - self.get_network_interfaces, ] def get_uname_info(self) -> Dict[str, str]: @@ -714,228 +703,6 @@ def parse_list(json_list) -> None: return lscpu_info - def _get_ipv4_addr_list(self) -> List[str]: - """ - When DNS record is not configured properly for the system, then try to - get list of all IPv4 addresses from all devices. Return 127.0.0.1 only - in situation when there is only loopback device. - :return: list of IPv4 addresses - """ - addr_list: List[str] = [] - interface_info: List[ethtool.etherinfo] = ethtool.get_interfaces_info(ethtool.get_devices()) - for info in interface_info: - for addr in info.get_ipv4_addresses(): - if addr.address != "127.0.0.1": - addr_list.append(addr.address) - if len(addr_list) == 0: - addr_list = ["127.0.0.1"] - return addr_list - - def _get_ipv6_addr_list(self) -> List[str]: - """ - When DNS record is not configured properly for the system, then try to - get list of all IPv6 addresses from all devices. Return ::1 only - in situation when there no device with valid global IPv6 address. - :return: list of IPv6 addresses - """ - addr_list: List[str] = [] - interface_info: List[ethtool.etherinfo] = ethtool.get_interfaces_info(ethtool.get_devices()) - for info in interface_info: - for addr in info.get_ipv6_addresses(): - if addr.scope == "universe": - addr_list.append(addr.address) - if len(addr_list) == 0: - addr_list = ["::1"] - return addr_list - - def get_network_info(self) -> Dict[str, str]: - """ - Try to get information about network: hostname, FQDN, IPv4, IPv6 addresses - """ - net_info: Dict[str, str] = {} - try: - hostname: str = socket.gethostname() - net_info["network.hostname"] = hostname - - try: - # We do not use socket.getfqdn(), because we need - # to mimic behaviour of 'hostname -f' command and be - # compatible with puppet and katello - infolist: List[tuple] = socket.getaddrinfo( - hostname, # (host) hostname - None, # (port) no need to specify port - socket.AF_UNSPEC, # (family) IPv4/IPv6 - socket.SOCK_DGRAM, # (type) hostname uses SOCK_DGRAM - 0, # (proto) no need to specify transport protocol - socket.AI_CANONNAME, # (flags) we DO NEED to get canonical name - ) - except Exception: - net_info["network.fqdn"] = hostname - else: - # getaddrinfo has to return at least one item - # and canonical name can't be empty string. - # Note: when hostname is for some reason equal to - # one of CNAME in DNS record, then canonical name - # (FQDN) will be different from hostname - if len(infolist) > 0 and infolist[0][3] != "": - net_info["network.fqdn"] = infolist[0][3] - else: - net_info["network.fqdn"] = hostname - - try: - info: List[tuple] = socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM) - ip_list = set([x[4][0] for x in info]) - net_info["network.ipv4_address"] = ", ".join(ip_list) - except Exception as err: - log.debug("Error during resolving IPv4 address of hostname: %s, %s" % (hostname, err)) - net_info["network.ipv4_address"] = ", ".join(self._get_ipv4_addr_list()) - - try: - info: List[tuple] = socket.getaddrinfo(hostname, None, socket.AF_INET6, socket.SOCK_STREAM) - ip_list = set([x[4][0] for x in info]) - net_info["network.ipv6_address"] = ", ".join(ip_list) - except Exception as err: - log.debug("Error during resolving IPv6 address of hostname: %s, %s" % (hostname, err)) - net_info["network.ipv6_address"] = ", ".join(self._get_ipv6_addr_list()) - - except Exception as err: - log.warning("Error reading networking information: %s", err) - - return net_info - - def _should_get_mac_address(self, device: str) -> bool: - return not (device.startswith("sit") or device.startswith("lo")) - - def get_network_interfaces(self) -> Dict[str, str]: - netinfdict: Dict[str, str] = {} - old_ipv4_metakeys: List[str] = ["ipv4_address", "ipv4_netmask", "ipv4_broadcast"] - ipv4_metakeys: List[str] = ["address", "netmask", "broadcast"] - ipv6_metakeys: List[str] = ["address", "netmask"] - try: - interfaces_info: List[ethtool.etherinfo] = ethtool.get_interfaces_info(ethtool.get_devices()) - for info in interfaces_info: - mac_address: str = info.mac_address - device: str = info.device - # Omit mac addresses for sit and lo device types. See BZ838123 - # mac address are per interface, not per address - if self._should_get_mac_address(device): - key: str = ".".join(["net.interface", device, "mac_address"]) - netinfdict[key] = mac_address - - # collect the IPv6 information by device, and by scope - ipv6_values: Dict[str, Dict[str, List[str]]] = defaultdict(lambda: defaultdict(list)) - # all of our supported versions of python-ethtool support - # get_ipv6_addresses - for addr in info.get_ipv6_addresses(): - # ethtool returns a different scope for "public" IPv6 addresses - # on different versions of RHEL. EL5 is "global", while EL6 is - # "universe". Make them consistent. - scope: str = addr.scope - if scope == "universe": - scope = "global" - - for mkey in ipv6_metakeys: - # we could specify a default here... that could hide - # api breakage though and unit testing hw detect is... meh - attr = getattr(addr, mkey) or "Unknown" - ipv6_values[mkey][scope].append(str(attr)) - for meta_key, mapping_values in ipv6_values.items(): - for scope, values in mapping_values.items(): - key: str = "net.interface.{device}.ipv6_{key}.{scope}".format( - device=info.device, key=meta_key, scope=scope - ) - list_key = key + "_list" - netinfdict[key] = values[0] - netinfdict[list_key] = ", ".join(values) - - # However, old version of python-ethtool do not support - # get_ipv4_address - # - # python-ethtool's api changed between rhel6.3 and rhel6.4 - # (0.6-1.el6 to 0.6-2.el6) - # (without revving the upstream version... bad python-ethtool!) - # note that 0.6-5.el5 (from rhel5.9) has the old api - # - # previously, we got the 'ipv4_address' from the etherinfo object - # directly. In the new api, that isn't exposed, so we get the list - # of addresses on the interface, and populate the info from there. - # - # That api change as to address bz #759150. The bug there was that - # python-ethtool only showed one ip address per interface. To - # accomdate the finer grained info, the api changed... - if hasattr(info, "get_ipv4_addresses"): - # collect the IPv4 information by device - ipv4_values: Dict[str, List[str]] = defaultdict(list) - for addr in info.get_ipv4_addresses(): - for mkey in ipv4_metakeys: - attr: str = getattr(addr, mkey) or "Unknown" - ipv4_values[mkey].append(str(attr)) - for meta_key, values in ipv4_values.items(): - # append 'ipv4_' to match the older interface and keeps facts - # consistent - key: str = "net.interface.{device}.ipv4_{key}".format( - device=info.device, key=meta_key - ) - list_key: str = key + "_list" - netinfdict[key] = values[0] - netinfdict[list_key] = ", ".join(values) - # check to see if we are actually an ipv4 interface - elif hasattr(info, "ipv4_address"): - for mkey in old_ipv4_metakeys: - key = ".".join(["net.interface", device, mkey]) - attr = getattr(info, mkey) or "Unknown" - netinfdict[key] = attr - # otherwise we are ipv6 and we handled that already - - # Bonded devices can have their hardware address changed. - # - # Here the 'bond_interface' refers to the name of device bonding other - # network interfaces under single virtual one. - # - # If we find the bond link (if the file exists), we are a bonded device - # and we need to check the /proc/net/bonding information to see what the - # 'permanent' hardware address for this bonded device is. - bond_interface: Optional[str] - try: - bond_link: str = os.readlink("/sys/class/net/%s/master" % info.device) - bond_interface = os.path.basename(bond_link) - # FIXME - except Exception: - bond_interface = None - - if bond_interface: - address: str = self._get_permanent_hardware_address(bond_interface, info.device) - key: str = ".".join(["net.interface", info.device, "permanent_mac_address"]) - netinfdict[key] = address - - except Exception as e: - log.exception(e) - log.warning("Error reading network interface information: %s", e) - return netinfdict - - # from rhn-client-tools hardware.py - # see bz#785666 - def _get_permanent_hardware_address(self, bond_interface: str, seeked_interface: str) -> str: - address: str = "" - try: - bond_interface_file: TextIO = open("/proc/net/bonding/%s" % bond_interface, "r") - except OSError: - return address - - permanent_interface_found: bool = False - for line in bond_interface_file.readlines(): - if permanent_interface_found and line.find("Permanent HW addr: ") != -1: - address = line.split()[3].upper() - break - - if line.find("Slave Interface: ") != -1: - interface_name: str = line.split()[2] - if interface_name == seeked_interface: - permanent_interface_found = True - - bond_interface_file.close() - return address - if __name__ == "__main__": _LIBPATH = "/usr/share/rhsm" diff --git a/src/rhsmlib/facts/network.py b/src/rhsmlib/facts/network.py new file mode 100644 index 0000000000..3a4d309a14 --- /dev/null +++ b/src/rhsmlib/facts/network.py @@ -0,0 +1,190 @@ +# Copyright (c) 2023 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public License, +# version 2 (GPLv2). There is NO WARRANTY for this software, express or +# implied, including the implied warranties of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 +# along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +# +# Red Hat trademarks are not licensed under GPLv2. No permission is +# granted to use or replicate Red Hat trademarks that are incorporated +# in this software or its documentation. +import json +import logging +import socket +import subprocess +from typing import Callable, Dict, List, Literal, Union + +from rhsmlib.facts import collector + +log = logging.getLogger(__name__) + + +class NetworkCollector(collector.FactsCollector): + def __init__( + self, + arch: str = None, + prefix: str = None, + testing: bool = None, + collected_hw_info: Dict[str, Union[str, int, bool, None]] = None, + ): + super().__init__(arch=arch, prefix=prefix, testing=testing, collected_hw_info=None) + + self.hardware_methods: List[Callable] = [ + self.get_network, + self.get_interfaces, + ] + + def _query_ip_command(self) -> List[dict]: + """Call system's 'ip' command and return its value as a dictionary.""" + output = subprocess.run(["ip", "--json", "address"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if output.stderr != b"": + log.error(f"Could not query 'ip' for network facts: {output.stderr}") + return [] + stdout: str = output.stdout.decode("utf-8") + return json.loads(stdout) + + def _get_fqdn(self) -> str: + """Obtain system's FQDN.""" + # We use this approach because '/etc/bin/hostname -f' cannot be read + # under RHEL's SELinux policy (RHBZ 1447722). + # We must also stay compatible with Puppet and Katello: they use + # 'hostname -f' which prefers IPv4, but 'socket.getfqdn()' prefers + # IPv6 (RHBZ 1401394). + hostname: str = socket.gethostname() + + try: + addrinfo: list[tuple] = socket.getaddrinfo( + # host, port, family, type, proto, flags + hostname, + None, + socket.AF_UNSPEC, + socket.SOCK_DGRAM, + 0, + socket.AI_CANONNAME, + ) + + # getaddrinfo() returns 1+ items. The first one contains the + # canonical name, the rest of the items contain empty string. + # Note: When hostname is equal to one of CNAME in DNS record, then + # canonical name will be different from hostname. + if len(addrinfo) > 0 and addrinfo[0][3] != "": + return addrinfo[0][3] + except socket.gaierror as exc: + log.debug(f"Could not obtain hostname using getaddrinfo: {exc}") + + return hostname + + def _extract_address_list(self, family: Literal["inet", "inet6"], data: List[dict]) -> List[str]: + """Extract IPv4 or IPv6 addresses from 'ip' output. + + The list excludes loopback and link-local addresses. + """ + result: List[str] = [] + for interface in data: + if "LOOPBACK" in interface["flags"]: + continue + result += [address["local"] for address in interface["addr_info"] if address["family"] == family] + return result + + def get_network(self) -> dict: + """Get general network facts. + + Hostname, FQDN and a list of IPv4/IPv6 addresses get collected here. + + Resulting facts have 'network.' prefix. + """ + # These socket functions work even with no network available + data = self._query_ip_command() + + result: Dict[str, str] = { + "network.hostname": socket.gethostname(), + "network.fqdn": self._get_fqdn(), + "network.ipv4_address": ", ".join(self._extract_address_list("inet", data)), + "network.ipv6_address": ", ".join(self._extract_address_list("inet6", data)), + } + return result + + def get_interfaces(self) -> dict: + """Get detailed network interface facts. + + Interface names, IPv4/IPv6 addresses and masks get collected here. + + Resulting facts have 'net.' prefix. + """ + data = self._query_ip_command() + result: Dict[str, Union[str, int]] = {} + + for interface in data: + prefix: str = f"net.interface.{interface['ifname']}" + + # MAC address + skip_mac: bool = "LOOPBACK" in interface["flags"] or "NOARP" in interface["flags"] + # Loopback has a MAC address of '00:00:00:00:00:00'. + # Tunnels have their MAC randomized every time, see BZ#838123. + if not skip_mac: + result[f"{prefix}.mac_address"] = interface["address"] + if "permaddr" in interface: + # Wireless interfaces have permanent address and temporary + # address they use to identify to unknown access points. + result[f"{prefix}.permanent_mac_address"] = interface["permaddr"] + + # IP address + ipv4_addresses: List[str] = [] + ipv4_broadcasts: List[str] = [] + ipv4_netmasks: List[int] = [] + + ipv6_global_addresses: List[str] = [] + ipv6_link_addresses: List[str] = [] + ipv6_host_addresses: List[str] = [] + ipv6_global_netmasks: List[int] = [] + ipv6_link_netmasks: List[int] = [] + ipv6_host_netmasks: List[int] = [] + + for address in interface["addr_info"]: + if address["family"] == "inet": + ipv4_addresses.append(address["local"]) + # Localhost does not have a broadcast address + if "broadcast" in address: + ipv4_broadcasts.append(address["broadcast"]) + else: + # FIXME Should localhost's broadcast be simply omitted? + ipv4_broadcasts.append("Unknown") + ipv4_netmasks.append(address["prefixlen"]) + + elif address["family"] == "inet6": + if address["scope"] == "global": + ipv6_global_addresses.append(address["local"]) + ipv6_global_netmasks.append(address["prefixlen"]) + elif address["scope"] == "link": + ipv6_link_addresses.append(address["local"]) + ipv6_link_netmasks.append(address["prefixlen"]) + elif address["scope"] == "host": + ipv6_host_addresses.append(address["local"]) + ipv6_host_netmasks.append(address["prefixlen"]) + + def add_addresses(infix: str, items: list) -> None: + """Fill in the 'result' dictionary. + + :param infix: Fact name. + :param items: List of addresses or masks. + :return: Nothing, the 'result' dictionary is updated in-place. + """ + if not len(items): + return + result[f"{prefix}.{infix}"] = str(items[0]) + result[f"{prefix}.{infix}_list"] = ", ".join([str(x) for x in items]) + + add_addresses("ipv4_address", ipv4_addresses) + add_addresses("ipv4_broadcast", ipv4_broadcasts) + add_addresses("ipv4_netmask", ipv4_netmasks) + + add_addresses("ipv6_address.global", ipv6_global_addresses) + add_addresses("ipv6_netmask.global", ipv6_global_netmasks) + add_addresses("ipv6_address.link", ipv6_link_addresses) + add_addresses("ipv6_netmask.link", ipv6_link_netmasks) + add_addresses("ipv6_address.host", ipv6_host_addresses) + add_addresses("ipv6_netmask.host", ipv6_host_netmasks) + + return result diff --git a/subscription-manager.spec b/subscription-manager.spec index 46c8605c9c..9e266f9b86 100644 --- a/subscription-manager.spec +++ b/subscription-manager.spec @@ -130,7 +130,7 @@ Source2: subscription-manager-rpmlintrc # nesting is required since RPM requires the various preamble directives to be # at the start of a line making meaningful indentation impossible. -Requires: %{py_package_prefix}-ethtool +Requires: iproute Requires: %{py_package_prefix}-iniparse Requires: %{py_package_prefix}-decorator Requires: virt-what diff --git a/test/rhsmlib/dbus/test_facts.py b/test/rhsmlib/dbus/test_facts.py index 8367ea8396..a8fb729209 100644 --- a/test/rhsmlib/dbus/test_facts.py +++ b/test/rhsmlib/dbus/test_facts.py @@ -33,13 +33,19 @@ def setUpClass(cls) -> None: cls.addClassCleanup(get_virt_info_patch.stop) # Do not collect network facts, as they can cause issues in containers - get_network_info_patch = mock.patch( - "rhsmlib.facts.hwprobe.HardwareCollector.get_network_info", - name="get_network_info", + get_network_patch = mock.patch( + "rhsmlib.facts.network.NetworkCollector.get_network", name="get_network" ) - cls.patches["get_network_info"] = get_network_info_patch.start() - cls.patches["get_network_info"].return_value = {} - cls.addClassCleanup(get_network_info_patch.stop) + cls.patches["get_network"] = get_network_patch.start() + cls.patches["get_network"].return_value = {} + cls.addClassCleanup(get_network_patch.stop) + + get_interfaces_patch = mock.patch( + "rhsmlib.facts.network.NetworkCollector.get_interfaces", name="get_interfaces" + ) + cls.patches["get_interfaces"] = get_interfaces_patch.start() + cls.patches["get_interfaces"].return_value = {} + cls.addClassCleanup(get_interfaces_patch.stop) super().setUpClass() diff --git a/test/rhsmlib/facts/network_data/laptop.json b/test/rhsmlib/facts/network_data/laptop.json new file mode 100644 index 0000000000..7e87a9850a --- /dev/null +++ b/test/rhsmlib/facts/network_data/laptop.json @@ -0,0 +1,167 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 2, + "ifname": "enp0s20f0u2u1", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "a4:ae:12:01:02:03", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "10.0.0.56", + "prefixlen": 24, + "broadcast": "10.0.0.255", + "scope": "global", + "dynamic": true, + "noprefixroute": true, + "label": "enp0s20f0u2u1", + "valid_life_time": 70589, + "preferred_life_time": 70589 + }, + { + "family": "inet6", + "local": "2620:52:0:0:0:1:2:3", + "prefixlen": 64, + "scope": "global", + "dynamic": true, + "noprefixroute": true, + "valid_life_time": 2591945, + "preferred_life_time": 604745 + }, + { + "family": "inet6", + "local": "fe80::0:1:2:3", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 3, + "ifname": "enp0s31f6", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "38:f3:ab:01:02:03", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [] + }, + { + "ifindex": 4, + "ifname": "wlp0s20f3", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "d6:73:ed:01:02:03", + "broadcast": "ff:ff:ff:ff:ff:ff", + "permaddr": "28:d0:ea:01:02:03", + "addr_info": [] + }, + { + "ifindex": 5, + "ifname": "virbr0", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "52:54:00:01:02:03", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "192.168.122.1", + "prefixlen": 24, + "broadcast": "192.168.122.255", + "scope": "global", + "label": "virbr0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "2038:dead:beef::1", + "prefixlen": 96, + "scope": "global", + "tentative": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + } +] diff --git a/test/rhsmlib/facts/network_data/server.json b/test/rhsmlib/facts/network_data/server.json new file mode 100644 index 0000000000..77b69b0cb3 --- /dev/null +++ b/test/rhsmlib/facts/network_data/server.json @@ -0,0 +1,839 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 2, + "ifname": "tg3_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "3c:ec:ef:aa:bb:cc", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp198s0f0", + "eno1" + ], + "addr_info": [ + { + "family": "inet", + "local": "10.40.144.71", + "prefixlen": 24, + "broadcast": "10.40.144.255", + "scope": "global", + "noprefixroute": true, + "label": "tg3_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "2620:52::71", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80:::6972", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 3, + "ifname": "bnxt_en_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "bc:97:e1:aa:bb:cc", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp129s0f0np0" + ], + "addr_info": [ + { + "family": "inet", + "local": "172.18.0.29", + "prefixlen": 24, + "broadcast": "172.18.0.255", + "scope": "global", + "noprefixroute": true, + "label": "bnxt_en_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:4::1d", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::8880", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 4, + "ifname": "tg3_1", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "3c:ec:ef:41:69:73", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp198s0f1", + "eno2" + ], + "addr_info": [] + }, + { + "ifindex": 5, + "ifname": "bnxt_en_1", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "bc:97:e1:da:88:81", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp129s0f1np1" + ], + "addr_info": [] + }, + { + "ifindex": 6, + "ifname": "ixgbe_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "90:e2:ba:c9:a1:50", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp1s0f0" + ], + "addr_info": [ + { + "family": "inet", + "local": "172.16.2.35", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.36", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.37", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.38", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.39", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.40", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.41", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.42", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.43", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.2.44", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "ixgbe_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::23", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::24", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::25", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::26", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::27", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::28", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::29", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::2a", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::2b", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::2c", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::92e2:baff:fec9:a150", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 7, + "ifname": "ixgbe_1", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "90:e2:ba:c9:a1:51", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp1s0f1" + ], + "addr_info": [ + { + "family": "inet", + "local": "172.16.3.9", + "prefixlen": 24, + "broadcast": "172.16.3.255", + "scope": "global", + "noprefixroute": true, + "label": "ixgbe_1", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:3::9", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::92e2:baff:fec9:a151", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 8, + "ifname": "mlx5_core_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "1c:34:da:aa:bb:cc", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp65s0np0" + ], + "addr_info": [ + { + "family": "inet", + "local": "172.16.0.37", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.38", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.39", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.40", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.41", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.42", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.43", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.44", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.45", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet", + "local": "172.16.0.46", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "secondary": true, + "noprefixroute": true, + "label": "mlx5_core_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::25", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::26", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::27", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::28", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::29", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::2a", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::2b", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::2c", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::2d", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::2e", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::1e34:daff:fe4e:77f0", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 9, + "link": "bnxt_en_0", + "ifname": "bnxt_en_0.510", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "bc:97:e1:aa:bb:cc", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "172.16.10.9", + "prefixlen": 24, + "broadcast": "172.16.10.255", + "scope": "global", + "noprefixroute": true, + "label": "bnxt_en_0.510", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:a::9", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::8880", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 10, + "link": "mlx5_core_0", + "ifname": "mlx5_core_0.510", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "1c:34:da:aa:bb:cc", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "172.16.6.21", + "prefixlen": 24, + "broadcast": "172.16.6.255", + "scope": "global", + "noprefixroute": true, + "label": "mlx5_core_0.510", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:6::15", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::1e34:daff:fe4e:77f0", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 11, + "link": null, + "ifname": "ip_vti0", + "flags": [ + "NOARP" + ], + "mtu": 1480, + "qdisc": "noop", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ipip", + "address": "0.0.0.0", + "broadcast": "0.0.0.0", + "addr_info": [] + } +] diff --git a/test/rhsmlib/facts/network_data/server_bond.json b/test/rhsmlib/facts/network_data/server_bond.json new file mode 100644 index 0000000000..5cffe12f1e --- /dev/null +++ b/test/rhsmlib/facts/network_data/server_bond.json @@ -0,0 +1,633 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 2, + "ifname": "tg3_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "08:94:ef:00:01:02", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp22s0f0", + "eno1" + ], + "addr_info": [ + { + "family": "inet", + "local": "10.0.3.21", + "prefixlen": 24, + "broadcast": "10.0.3.255", + "scope": "global", + "noprefixroute": true, + "label": "tg3_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "2620:52::21", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::13af", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 3, + "ifname": "tg3_1", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "08:94:ef:02:03:04", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp22s0f1", + "eno2" + ], + "addr_info": [ + { + "family": "inet6", + "local": "2620:52::13b0", + "prefixlen": 64, + "scope": "global", + "dynamic": true, + "mngtmpaddr": true, + "valid_life_time": 2591685, + "preferred_life_time": 604485 + }, + { + "family": "inet6", + "local": "fe80::13b0", + "prefixlen": 64, + "scope": "link", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 4, + "ifname": "tg3_2", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "08:94:ef:03:03:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp22s0f2", + "eno3" + ], + "addr_info": [] + }, + { + "ifindex": 5, + "ifname": "tg3_3", + "flags": [ + "NO-CARRIER", + "BROADCAST", + "MULTICAST", + "UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "DOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "08:94:ef:03:03:02", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp22s0f3", + "eno4" + ], + "addr_info": [] + }, + { + "ifindex": 6, + "ifname": "bnx2x_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "SLAVE", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "master": "bond0", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "00:0e:1e:03:02:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp129s0f0", + "ens2f0" + ], + "addr_info": [] + }, + { + "ifindex": 7, + "ifname": "bnx2x_1", + "flags": [ + "BROADCAST", + "MULTICAST", + "SLAVE", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "master": "bond0", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "00:0e:1e:03:02:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "permaddr": "00:0e:1e:03:02:03", + "altnames": [ + "enp129s0f1", + "ens2f1" + ], + "addr_info": [] + }, + { + "ifindex": 8, + "ifname": "ixgbe_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "SLAVE", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "master": "bond1", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "90:e2:ba:04:05:06", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp6s0f0", + "ens1f0" + ], + "addr_info": [] + }, + { + "ifindex": 9, + "ifname": "qede_0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "f4:e9:d4:04:06:08", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp11s0f0", + "ens3f0" + ], + "addr_info": [ + { + "family": "inet", + "local": "172.17.0.3", + "prefixlen": 24, + "broadcast": "172.17.0.255", + "scope": "global", + "noprefixroute": true, + "label": "qede_0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:4::3", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::db82", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 10, + "ifname": "ixgbe_1", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "90:e2:ba:03:05:07", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp6s0f1", + "ens1f1" + ], + "addr_info": [ + { + "family": "inet", + "local": "172.16.1.5", + "prefixlen": 24, + "broadcast": "172.16.1.255", + "scope": "global", + "noprefixroute": true, + "label": "ixgbe_1", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:1::5", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::af2d", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 11, + "ifname": "qede_1", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "mq", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "f4:e9:d4:01:04:07", + "broadcast": "ff:ff:ff:ff:ff:ff", + "altnames": [ + "enp11s0f1", + "ens3f1" + ], + "addr_info": [ + { + "family": "inet", + "local": "172.16.5.3", + "prefixlen": 24, + "broadcast": "172.16.5.255", + "scope": "global", + "noprefixroute": true, + "label": "qede_1", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:5::3", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::db83", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 12, + "ifname": "enp0s20u13u5", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "0a:94:ef:01:04:08", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [] + }, + { + "ifindex": 15, + "ifname": "bond0", + "flags": [ + "BROADCAST", + "MULTICAST", + "MASTER", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "00:0e:1e:03:02:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "172.16.0.73", + "prefixlen": 24, + "broadcast": "172.16.0.255", + "scope": "global", + "noprefixroute": true, + "label": "bond0", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00::49", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::ccd1", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 16, + "link": "bond0", + "ifname": "bond0.510", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "00:0e:1e:03:02:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "172.16.6.29", + "prefixlen": 24, + "broadcast": "172.16.6.255", + "scope": "global", + "noprefixroute": true, + "label": "bond0.510", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:6::1d", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::21d0", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 17, + "ifname": "bond1", + "flags": [ + "BROADCAST", + "MULTICAST", + "MASTER", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "90:e2:ba:04:05:06", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "172.16.2.65", + "prefixlen": 24, + "broadcast": "172.16.2.255", + "scope": "global", + "noprefixroute": true, + "label": "bond1", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:2::41", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::cb", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 18, + "link": "bond1", + "ifname": "bond1.510", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "90:e2:ba:04:05:06", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "172.16.8.17", + "prefixlen": 24, + "broadcast": "172.16.8.255", + "scope": "global", + "noprefixroute": true, + "label": "bond1.510", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fd00:0:0:8::11", + "prefixlen": 64, + "scope": "global", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "fe80::af2c", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + } +] diff --git a/test/rhsmlib/facts/network_data/vm_bond.json b/test/rhsmlib/facts/network_data/vm_bond.json new file mode 100644 index 0000000000..52ef64b26d --- /dev/null +++ b/test/rhsmlib/facts/network_data/vm_bond.json @@ -0,0 +1,134 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 2, + "ifname": "enp1s0", + "flags": [ + "BROADCAST", + "MULTICAST", + "SLAVE", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "master": "bond0", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "72:4b:f3:aa:aa:aa", + "broadcast": "ff:ff:ff:ff:ff:ff", + "permaddr": "52:54:00:00:00:01", + "addr_info": [] + }, + { + "ifindex": 3, + "ifname": "enp2s0", + "flags": [ + "BROADCAST", + "MULTICAST", + "SLAVE", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "master": "bond0", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "72:4b:f3:aa:aa:aa", + "broadcast": "ff:ff:ff:ff:ff:ff", + "permaddr": "52:54:00:00:00:02", + "addr_info": [] + }, + { + "ifindex": 4, + "ifname": "bond0", + "flags": [ + "BROADCAST", + "MULTICAST", + "MASTER", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "72:4b:f3:aa:aa:aa", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "192.168.122.35", + "prefixlen": 24, + "broadcast": "192.168.122.255", + "scope": "global", + "dynamic": true, + "noprefixroute": true, + "label": "bond0", + "valid_life_time": 3380, + "preferred_life_time": 3380 + }, + { + "family": "inet6", + "local": "2038:dead:beef::1", + "prefixlen": 128, + "scope": "global", + "dynamic": true, + "noprefixroute": true, + "valid_life_time": 86181, + "preferred_life_time": 86181 + }, + { + "family": "inet6", + "local": "fe80::1", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + } +] diff --git a/test/rhsmlib/facts/network_data/vm_no_connection.json b/test/rhsmlib/facts/network_data/vm_no_connection.json new file mode 100644 index 0000000000..a34f1a2dbb --- /dev/null +++ b/test/rhsmlib/facts/network_data/vm_no_connection.json @@ -0,0 +1,38 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + } +] diff --git a/test/rhsmlib/facts/network_data/vm_no_connection_ipv4.json b/test/rhsmlib/facts/network_data/vm_no_connection_ipv4.json new file mode 100644 index 0000000000..586c96e910 --- /dev/null +++ b/test/rhsmlib/facts/network_data/vm_no_connection_ipv4.json @@ -0,0 +1,30 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + } +] diff --git a/test/rhsmlib/facts/network_data/vm_no_connection_ipv6.json b/test/rhsmlib/facts/network_data/vm_no_connection_ipv6.json new file mode 100644 index 0000000000..d2faca9f47 --- /dev/null +++ b/test/rhsmlib/facts/network_data/vm_no_connection_ipv6.json @@ -0,0 +1,29 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + } +] diff --git a/test/rhsmlib/facts/network_data/vm_team.json b/test/rhsmlib/facts/network_data/vm_team.json new file mode 100644 index 0000000000..52ad1158d4 --- /dev/null +++ b/test/rhsmlib/facts/network_data/vm_team.json @@ -0,0 +1,149 @@ +[ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 2, + "ifname": "enp1s0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "52:54:00:dd:19:07", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "192.168.122.204", + "prefixlen": 24, + "broadcast": "192.168.122.255", + "scope": "global", + "dynamic": true, + "noprefixroute": true, + "label": "enp1s0", + "valid_life_time": 3532, + "preferred_life_time": 3532 + }, + { + "family": "inet6", + "local": "2038:dead:beef::1a9", + "prefixlen": 128, + "scope": "global", + "dynamic": true, + "noprefixroute": true, + "valid_life_time": 86332, + "preferred_life_time": 86332 + }, + { + "family": "inet6", + "local": "fe80::5054:ff:fedd:1907", + "prefixlen": 64, + "scope": "link", + "noprefixroute": true, + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 3, + "ifname": "enp7s0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "master": "team0", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "52:54:00:00:00:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [] + }, + { + "ifindex": 4, + "ifname": "enp8s0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "master": "team0", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "52:54:00:00:00:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "permaddr": "52:54:00:00:00:02", + "addr_info": [] + }, + { + "ifindex": 5, + "ifname": "team0", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "noqueue", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "52:54:00:00:00:01", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [] + } +] diff --git a/test/rhsmlib/facts/test_hwprobe.py b/test/rhsmlib/facts/test_hwprobe.py index 69f7dabc04..4fcb7cf2e2 100644 --- a/test/rhsmlib/facts/test_hwprobe.py +++ b/test/rhsmlib/facts/test_hwprobe.py @@ -12,8 +12,6 @@ # in this software or its documentation. import unittest -import io - from unittest.mock import patch from unittest.mock import Mock from unittest.mock import mock_open @@ -576,165 +574,6 @@ def test_meminfo(self): for key in mem: assert key in ["memory.memtotal", "memory.swaptotal"] - # this test will probably fail on a machine with - # no network. - def test_networkinfo(self): - hw = hwprobe.HardwareCollector() - net = hw.get_network_info() - expected = set(["network.fqdn", "network.hostname", "network.ipv4_address", "network.ipv6_address"]) - self.assertEqual(expected, set(net.keys())) - - def test_network_interfaces(self): - hw = hwprobe.HardwareCollector() - net_int = hw.get_network_interfaces() - self.assertEqual(net_int["net.interface.lo.ipv4_address"], "127.0.0.1") - self.assertFalse("net.interface.lo.mac_address" in net_int) - self.assertFalse("net.interface.sit0.mac_address" in net_int) - - # simulate some wacky interfaces - @patch("ethtool.get_devices") - @patch("ethtool.get_interfaces_info") - def test_network_interfaces_none(self, MockGetInterfacesInfo, MockGetDevices): - hw = hwprobe.HardwareCollector() - net_int = hw.get_network_interfaces() - self.assertEqual(net_int, {}) - - @patch("ethtool.get_devices") - @patch("ethtool.get_interfaces_info") - def test_network_interfaces_multiple_ipv4(self, MockGetInterfacesInfo, MockGetDevices): - hw = hwprobe.HardwareCollector() - - MockGetDevices.return_value = ["eth0"] - mock_info = Mock(mac_address="00:00:00:00:00:00", device="eth0") - mock_info.get_ipv6_addresses.return_value = [] - mock_ipv4s = [ - Mock(address="10.0.0.1", netmask="24", broadcast="Unknown"), - Mock(address="10.0.0.2", netmask="24", broadcast="Unknown"), - ] - mock_info.get_ipv4_addresses = Mock(return_value=mock_ipv4s) - MockGetInterfacesInfo.return_value = [mock_info] - - net_int = hw.get_network_interfaces() - - self.assertEqual(net_int["net.interface.eth0.ipv4_address"], "10.0.0.1") - self.assertEqual(net_int["net.interface.eth0.ipv4_address_list"], "10.0.0.1, 10.0.0.2") - - @patch("ethtool.get_devices") - @patch("ethtool.get_interfaces_info") - def test_network_interfaces_multiple_ipv6(self, MockGetInterfacesInfo, MockGetDevices): - hw = hwprobe.HardwareCollector() - - MockGetDevices.return_value = ["eth0"] - mock_info = Mock(mac_address="00:00:00:00:00:00", device="eth0") - mock_info.get_ipv4_addresses.return_value = [] - mock_ipv6s = [ - Mock(address="::1", netmask="/128", scope="link"), - Mock(address="fe80::f00d:f00d:f00d:f00d", netmask="/64", scope="link"), - ] - mock_info.get_ipv6_addresses = Mock(return_value=mock_ipv6s) - MockGetInterfacesInfo.return_value = [mock_info] - - net_int = hw.get_network_interfaces() - - self.assertEqual(net_int["net.interface.eth0.ipv6_address.link"], "::1") - self.assertEqual( - net_int["net.interface.eth0.ipv6_address.link_list"], "::1, fe80::f00d:f00d:f00d:f00d" - ) - - @patch("ethtool.get_devices") - @patch("ethtool.get_interfaces_info") - def test_network_interfaces_just_lo(self, MockGetInterfacesInfo, MockGetDevices): - hw = hwprobe.HardwareCollector() - MockGetDevices.return_value = ["lo"] - mock_info = Mock(mac_address="00:00:00:00:00:00", device="lo") - - mock_info.get_ipv6_addresses.return_value = [] - mock_ipv4 = Mock(address="127.0.0.1", netmask="24", broadcase="Unknown") - mock_info.get_ipv4_addresses = Mock(return_value=[mock_ipv4]) - MockGetInterfacesInfo.return_value = [mock_info] - net_int = hw.get_network_interfaces() - self.assertEqual(net_int["net.interface.lo.ipv4_address"], "127.0.0.1") - self.assertFalse("net.interface.lo.mac_address" in net_int) - - @patch("ethtool.get_devices") - @patch("ethtool.get_interfaces_info") - def test_network_interfaces_sit(self, MockGetInterfacesInfo, MockGetDevices): - hw = hwprobe.HardwareCollector() - MockGetDevices.return_value = ["sit0"] - mock_ipv6 = Mock(address="::1", netmask="/128", scope="global") - - mock_info = Mock(mac_address="00:00:00:00:00:00", device="sit0") - mock_info.get_ipv6_addresses.return_value = [mock_ipv6] - mock_info.get_ipv4_addresses.return_value = [] - MockGetInterfacesInfo.return_value = [mock_info] - - net_int = hw.get_network_interfaces() - # ignore mac address for sit* interfaces (bz #838123) - self.assertFalse("net.interface.sit0.mac_address" in net_int) - - @patch("ethtool.get_devices") - @patch("ethtool.get_interfaces_info") - def test_network_interfaces_just_lo_ethtool_no_get_ipv4_addresses( - self, MockGetInterfacesInfo, MockGetDevices - ): - hw = hwprobe.HardwareCollector() - MockGetDevices.return_value = ["lo"] - mock_info = Mock( - mac_address="00:00:00:00:00:00", - device="lo", - ipv4_address="127.0.0.1", - ipv4_netmask="24", - ipv4_broadcast="Unknown", - ) - mock_info.get_ipv6_addresses.return_value = [] - - # mock etherinfo not having a get_ipv4_addresses method - # if this fails, you need a mock that supports deleting - # attributes from mocks, ala mock 1.0+ - try: - del mock_info.get_ipv4_addresses - except AttributeError: - self.fail("You probably need a newer version of 'mock' installed, 1.0 or newer") - - MockGetInterfacesInfo.return_value = [mock_info] - - net_int = hw.get_network_interfaces() - self.assertEqual(net_int["net.interface.lo.ipv4_address"], "127.0.0.1") - self.assertFalse("net.interface.lo.mac_address" in net_int) - - @patch("ethtool.get_devices") - @patch("ethtool.get_interfaces_info") - def test_network_interfaces_just_lo_ipv6(self, MockGetInterfacesInfo, MockGetDevices): - hw = hwprobe.HardwareCollector() - MockGetDevices.return_value = ["lo"] - - mock_ipv6 = Mock(address="::1", netmask="/128", scope="global") - - mock_info = Mock(mac_address="00:00:00:00:00:00", device="lo") - mock_info.get_ipv6_addresses.return_value = [mock_ipv6] - mock_info.get_ipv4_addresses.return_value = [] - MockGetInterfacesInfo.return_value = [mock_info] - - net_int = hw.get_network_interfaces() - self.assertEqual(net_int["net.interface.lo.ipv6_address.global"], "::1") - self.assertFalse("net.interface.lo.mac_address" in net_int) - - @patch(OPEN_FUNCTION) - def test_get_slave_hwaddr_rr(self, MockOpen): - MockOpen.return_value = io.StringIO(PROC_BONDING_RR) - hw = hwprobe.HardwareCollector() - slave_hw = hw._get_permanent_hardware_address("bond0", "eth0") - # note we .upper the result - self.assertEqual("52:54:00:07:03:BA", slave_hw) - - @patch(OPEN_FUNCTION) - def test_get_slave_hwaddr_alb(self, MockOpen): - MockOpen.return_value = io.StringIO(PROC_BONDING_ALB) - hw = hwprobe.HardwareCollector() - slave_hw = hw._get_permanent_hardware_address("bond0", "eth0") - # note we .upper the result - self.assertEqual("52:54:00:07:03:BA", slave_hw) - def test_parse_s390_sysinfo_empty(self): cpu_count = 0 sysinfo_lines = [] diff --git a/test/rhsmlib/facts/test_network.py b/test/rhsmlib/facts/test_network.py new file mode 100644 index 0000000000..823aef00b0 --- /dev/null +++ b/test/rhsmlib/facts/test_network.py @@ -0,0 +1,427 @@ +import json +import pathlib +import unittest +from unittest.mock import patch + +import rhsmlib.facts.network + + +def load_data_file(name: str) -> dict: + this = pathlib.Path(__file__).absolute() + file = this.parent / "network_data" / name + if not file.is_file(): + raise FileNotFoundError(f"File {file!s} does not exist.") + with file.open("r") as handle: + data = json.load(handle) + return data + + +DATA = { + "laptop.json": load_data_file("laptop.json"), + "vm_no_connection.json": load_data_file("vm_no_connection.json"), + "vm_no_connection_ipv4.json": load_data_file("vm_no_connection_ipv4.json"), + "vm_no_connection_ipv6.json": load_data_file("vm_no_connection_ipv6.json"), + "vm_bond.json": load_data_file("vm_bond.json"), + "vm_team.json": load_data_file("vm_team.json"), + "server.json": load_data_file("server.json"), + "server_bond.json": load_data_file("server_bond.json"), +} +"""Real-life `ip --json a` captures. + +- laptop.json: Workstation. + Loopback, enabled ethernet (loc v4, link+global v6), disabled ethernet, disabled Wi-Fi, libvirt (v4, v6). +- vm_no_connection.json: Virtual machine. + Loopback. +- vm_no_connection_ipv4.json: Virtual machine. + Loopback with IPv6 disabled. +- vm_no_connection_ipv6.json: Virtual machine. + Loopback with IPv4 disabled. +- vm_bond.json: Virtual machine. + Loopback, two ethernet connections joined into one bond. +- vm_team.json: Virtual machine. + Loopback, ethernet connection, two ethernet connections joined into one team. +- server.json: Server. + 8 enabled interfaces, 3 disabled interfaces. Total of 59 addresses (local v4, local & link v6). +- server_bond.json: Server. + 10 enabled interfaces, 4 bonded interfaces, 2 disabled interfaces. Total of 28 addresses. +""" + + +class TestNetworkCollector(unittest.TestCase): + def setUp(self) -> None: + query_patch = patch("rhsmlib.facts.network.NetworkCollector._query_ip_command") + self.query = query_patch.start() + self.addCleanup(query_patch.stop) + + # This tells pytest not to cut off the differing JSON output on error + self.maxDiff = None + + def test_get_network(self): + """Test the function on laptop data.""" + hostname_patch = patch("socket.gethostname") + fqdn_patch = patch("socket.getfqdn") + + expected = { + "network.fqdn": "fake.example.com", + "network.hostname": "fake.example.com", + "network.ipv4_address": "10.0.0.56, 192.168.122.1", + "network.ipv6_address": "2620:52:0:0:0:1:2:3, 2038:dead:beef::1", + } + + self.query.return_value = DATA["laptop.json"] + hostname_patch.start().return_value = expected["network.hostname"] + fqdn_patch.start().return_value = expected["network.fqdn"] + + collector = rhsmlib.facts.network.NetworkCollector() + + self.assertEqual(collector.get_network(), expected) + + def test_get_network__no_network(self): + """Test the function when the system does not have the network.""" + hostname_patch = patch("socket.gethostname") + fqdn_patch = patch("socket.getfqdn") + + expected = { + "network.fqdn": "fake.example.com", + "network.hostname": "fake.example.com", + "network.ipv4_address": "", + "network.ipv6_address": "", + } + + self.query.return_value = {} + hostname_patch.start().return_value = expected["network.hostname"] + fqdn_patch.start().return_value = expected["network.fqdn"] + + collector = rhsmlib.facts.network.NetworkCollector() + + self.assertEqual(collector.get_network(), expected) + + def test_get_interfaces__nothing(self): + """System with no network.""" + # FIXME This may not be even possible, loopback should exist every time. + expected = {} + self.query.return_value = {} + collector = rhsmlib.facts.network.NetworkCollector() + result = collector.get_interfaces() + + self.assertEqual(expected, result) + + def test_get_interfaces__omit_mac(self): + """Ensure that selected interface do not contain MAC address.""" + # TODO Find a system with sit interface as an example (BZ#838123). + self.query.return_value = DATA["laptop.json"] + + self.query.return_value = [ + { + "ifname": "lo", + "flags": ["LOOPBACK", "UP", "LOWER_UP"], + "address": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295, + }, + ], + }, + { + "ifname": "renamed-lo", + "flags": ["LOOPBACK", "UP", "LOWER_UP"], + "address": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295, + }, + ], + }, + ] + + collector = rhsmlib.facts.network.NetworkCollector() + result = collector.get_interfaces() + + self.assertNotIn("net.interface.lo.mac_address", result.keys()) + self.assertNotIn("net.interface.renamed-lo.mac_address", result.keys()) + self.assertNotIn("net.interface.sit0.mac_address", result.keys()) + + def test_get_interfaces__more_v4s(self): + """An interface can have more than one IPv4 address.""" + expected = { + "net.interface.fake0.mac_address": "00:11:22:33:44:55", + "net.interface.fake0.ipv4_address": "10.0.1.10", + "net.interface.fake0.ipv4_address_list": "10.0.1.10, 10.0.2.247", + "net.interface.fake0.ipv4_broadcast": "10.0.1.255", + "net.interface.fake0.ipv4_broadcast_list": "10.0.1.255, 10.0.2.255", + "net.interface.fake0.ipv4_netmask": "24", + "net.interface.fake0.ipv4_netmask_list": "24, 28", + } + + self.query.return_value = [ + { + "ifname": "fake0", + "flags": ["BROADCAST", "MULTICAST", "UP", "LOWER_UP"], + "address": "00:11:22:33:44:55", + "addr_info": [ + { + "family": "inet", + "local": "10.0.1.10", + "prefixlen": 24, + "scope": "global", + "broadcast": "10.0.1.255", + }, + { + "family": "inet", + "local": "10.0.2.247", + "prefixlen": 28, + "scope": "global", + "broadcast": "10.0.2.255", + }, + ], + } + ] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + self.assertEqual(expected, result) + + def test_get_interfaces__more_v6s(self): + """An interface can have more than one IPv6 address.""" + expected = { + "net.interface.fake0.mac_address": "00:11:22:33:44:55", + "net.interface.fake0.ipv6_address.global": "2620:52:0:0:0:1:2:3", + "net.interface.fake0.ipv6_address.global_list": "2620:52:0:0:0:1:2:3, 2620:52:0:0:0:1:2:4", + "net.interface.fake0.ipv6_netmask.global": "64", + "net.interface.fake0.ipv6_netmask.global_list": "64, 64", + } + + self.query.return_value = [ + { + "ifname": "fake0", + "address": "00:11:22:33:44:55", + "flags": ["BROADCAST", "MULTICAST", "UP", "LOWER_UP"], + "addr_info": [ + { + "family": "inet6", + "local": "2620:52:0:0:0:1:2:3", + "prefixlen": 64, + "scope": "global", + }, + { + "family": "inet6", + "local": "2620:52:0:0:0:1:2:4", + "prefixlen": 64, + "scope": "global", + }, + ], + } + ] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + print(result) + + self.assertEqual(expected, result) + + def test_get_interfaces__no_connection(self): + """The system may not have a connection.""" + expected = { + "net.interface.lo.ipv4_address": "127.0.0.1", + "net.interface.lo.ipv4_address_list": "127.0.0.1", + "net.interface.lo.ipv4_broadcast": "Unknown", + "net.interface.lo.ipv4_broadcast_list": "Unknown", + "net.interface.lo.ipv4_netmask": "8", + "net.interface.lo.ipv4_netmask_list": "8", + "net.interface.lo.ipv6_address.host": "::1", + "net.interface.lo.ipv6_address.host_list": "::1", + "net.interface.lo.ipv6_netmask.host": "128", + "net.interface.lo.ipv6_netmask.host_list": "128", + } + + self.query.return_value = DATA["vm_no_connection.json"] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + self.assertEqual(expected, result) + + def test_get_interfaces__no_connection__ipv4_only(self): + """The system may not have a connection with IPv6 disabled.""" + expected = { + "net.interface.lo.ipv4_address": "127.0.0.1", + "net.interface.lo.ipv4_address_list": "127.0.0.1", + "net.interface.lo.ipv4_broadcast": "Unknown", + "net.interface.lo.ipv4_broadcast_list": "Unknown", + "net.interface.lo.ipv4_netmask": "8", + "net.interface.lo.ipv4_netmask_list": "8", + } + + self.query.return_value = DATA["vm_no_connection_ipv4.json"] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + self.assertEqual(expected, result) + + def test_get_interfaces__no_connection__ipv6_only(self): + """The system may not have a connection with IPv4 disabled.""" + expected = { + "net.interface.lo.ipv6_address.host": "::1", + "net.interface.lo.ipv6_address.host_list": "::1", + "net.interface.lo.ipv6_netmask.host": "128", + "net.interface.lo.ipv6_netmask.host_list": "128", + } + + self.query.return_value = DATA["vm_no_connection_ipv6.json"] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + self.assertEqual(expected, result) + + def test_get_interfaces__emoji(self): + """An interface can have more than one IPv4 address.""" + self.query.return_value = [ + { + "ifname": "🍉", + "flags": ["BROADCAST", "MULTICAST", "UP", "LOWER_UP"], + "address": "00:11:22:33:44:55", + "addr_info": [ + { + "family": "inet", + "local": "10.0.1.10", + "prefixlen": 24, + "scope": "global", + "broadcast": "10.0.1.255", + }, + ], + } + ] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + self.assertIn("net.interface.\N{WATERMELON}.ipv4_address", result) + + def test_get_interfaces__laptop(self): + """Test the function on laptop.json data.""" + expected = { + "net.interface.lo.ipv4_address": "127.0.0.1", + "net.interface.lo.ipv4_address_list": "127.0.0.1", + "net.interface.lo.ipv4_broadcast": "Unknown", + "net.interface.lo.ipv4_broadcast_list": "Unknown", + "net.interface.lo.ipv4_netmask": "8", + "net.interface.lo.ipv4_netmask_list": "8", + "net.interface.lo.ipv6_address.host": "::1", + "net.interface.lo.ipv6_address.host_list": "::1", + "net.interface.lo.ipv6_netmask.host": "128", + "net.interface.lo.ipv6_netmask.host_list": "128", + "net.interface.enp0s20f0u2u1.mac_address": "a4:ae:12:01:02:03", + "net.interface.enp0s20f0u2u1.ipv4_address": "10.0.0.56", + "net.interface.enp0s20f0u2u1.ipv4_address_list": "10.0.0.56", + "net.interface.enp0s20f0u2u1.ipv4_broadcast": "10.0.0.255", + "net.interface.enp0s20f0u2u1.ipv4_broadcast_list": "10.0.0.255", + "net.interface.enp0s20f0u2u1.ipv4_netmask": "24", + "net.interface.enp0s20f0u2u1.ipv4_netmask_list": "24", + "net.interface.enp0s20f0u2u1.ipv6_address.global": "2620:52:0:0:0:1:2:3", + "net.interface.enp0s20f0u2u1.ipv6_address.global_list": "2620:52:0:0:0:1:2:3", + "net.interface.enp0s20f0u2u1.ipv6_address.link": "fe80::0:1:2:3", + "net.interface.enp0s20f0u2u1.ipv6_address.link_list": "fe80::0:1:2:3", + "net.interface.enp0s20f0u2u1.ipv6_netmask.global": "64", + "net.interface.enp0s20f0u2u1.ipv6_netmask.global_list": "64", + "net.interface.enp0s20f0u2u1.ipv6_netmask.link": "64", + "net.interface.enp0s20f0u2u1.ipv6_netmask.link_list": "64", + "net.interface.enp0s31f6.mac_address": "38:f3:ab:01:02:03", + "net.interface.wlp0s20f3.mac_address": "d6:73:ed:01:02:03", + "net.interface.wlp0s20f3.permanent_mac_address": "28:d0:ea:01:02:03", + "net.interface.virbr0.mac_address": "52:54:00:01:02:03", + "net.interface.virbr0.ipv4_address": "192.168.122.1", + "net.interface.virbr0.ipv4_address_list": "192.168.122.1", + "net.interface.virbr0.ipv4_broadcast": "192.168.122.255", + "net.interface.virbr0.ipv4_broadcast_list": "192.168.122.255", + "net.interface.virbr0.ipv4_netmask": "24", + "net.interface.virbr0.ipv4_netmask_list": "24", + "net.interface.virbr0.ipv6_address.global": "2038:dead:beef::1", + "net.interface.virbr0.ipv6_address.global_list": "2038:dead:beef::1", + "net.interface.virbr0.ipv6_netmask.global": "96", + "net.interface.virbr0.ipv6_netmask.global_list": "96", + } + + self.query.return_value = DATA["laptop.json"] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + self.assertEqual(expected, result) + + def test_get_interfaces__vm_bond(self): + """Test the function on vm_bond.json data.""" + expected = { + "net.interface.lo.ipv4_address": "127.0.0.1", + "net.interface.lo.ipv4_address_list": "127.0.0.1", + "net.interface.lo.ipv4_broadcast": "Unknown", + "net.interface.lo.ipv4_broadcast_list": "Unknown", + "net.interface.lo.ipv4_netmask": "8", + "net.interface.lo.ipv4_netmask_list": "8", + "net.interface.lo.ipv6_address.host": "::1", + "net.interface.lo.ipv6_address.host_list": "::1", + "net.interface.lo.ipv6_netmask.host": "128", + "net.interface.lo.ipv6_netmask.host_list": "128", + "net.interface.enp1s0.permanent_mac_address": "52:54:00:00:00:01", + "net.interface.enp1s0.mac_address": "72:4b:f3:aa:aa:aa", + "net.interface.enp2s0.permanent_mac_address": "52:54:00:00:00:02", + "net.interface.enp2s0.mac_address": "72:4b:f3:aa:aa:aa", + "net.interface.bond0.mac_address": "72:4b:f3:aa:aa:aa", + "net.interface.bond0.ipv4_address": "192.168.122.35", + "net.interface.bond0.ipv4_address_list": "192.168.122.35", + "net.interface.bond0.ipv4_broadcast": "192.168.122.255", + "net.interface.bond0.ipv4_broadcast_list": "192.168.122.255", + "net.interface.bond0.ipv4_netmask": "24", + "net.interface.bond0.ipv4_netmask_list": "24", + "net.interface.bond0.ipv6_address.global": "2038:dead:beef::1", + "net.interface.bond0.ipv6_address.global_list": "2038:dead:beef::1", + "net.interface.bond0.ipv6_address.link": "fe80::1", + "net.interface.bond0.ipv6_address.link_list": "fe80::1", + "net.interface.bond0.ipv6_netmask.global": "128", + "net.interface.bond0.ipv6_netmask.global_list": "128", + "net.interface.bond0.ipv6_netmask.link": "64", + "net.interface.bond0.ipv6_netmask.link_list": "64", + } + + self.query.return_value = DATA["vm_bond.json"] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + self.assertEqual(expected, result) + + def test_get_interfaces__vm_team(self): + """Test the function on vm_team.json data.""" + self.query.return_value = DATA["vm_team.json"] + + collector = rhsmlib.facts.network.NetworkCollector() + + result = collector.get_interfaces() + + # The second interface should contain permanent MAC address, see RHBZ#2077757 + self.assertEqual(result["net.interface.enp7s0.mac_address"], "52:54:00:00:00:01") + self.assertEqual(result["net.interface.enp8s0.mac_address"], "52:54:00:00:00:01") + self.assertEqual(result["net.interface.enp8s0.permanent_mac_address"], "52:54:00:00:00:02") + self.assertEqual(result["net.interface.team0.mac_address"], "52:54:00:00:00:01")