#include <catch.hpp> #include <internal/facts/resolvers/networking_resolver.hpp> #include <facter/facts/collection.hpp> #include <facter/facts/fact.hpp> #include <facter/facts/scalar_value.hpp> #include <facter/facts/array_value.hpp> #include <facter/facts/map_value.hpp> #include "../../collection_fixture.hpp" using namespace std; using namespace facter::facts; using namespace facter::facts::resolvers; using namespace facter::testing; struct empty_networking_resolver : networking_resolver { protected: virtual data collect_data(collection& facts) override { return {}; } }; struct test_hostname_resolver : networking_resolver { protected: virtual data collect_data(collection& facts) override { data result; result.hostname = "hostname"; return result; } }; struct test_domain_resolver : networking_resolver { protected: virtual data collect_data(collection& facts) override { data result; result.domain = "domain"; return result; } }; struct test_fqdn_resolver : networking_resolver { protected: virtual data collect_data(collection& facts) override { data result; result.fqdn = "fqdn"; return result; } }; struct test_missing_fqdn_resolver : networking_resolver { protected: virtual data collect_data(collection& facts) override { data result; result.hostname = "hostname"; result.domain = "domain.com"; return result; } }; struct test_domain_with_trailing_dot : networking_resolver { protected: virtual data collect_data(collection& facts) override { data result; result.domain = "domain.com."; return result; } }; struct test_interface_resolver : networking_resolver { protected: virtual data collect_data(collection& facts) override { data result; for (int i = 0; i < 5; ++i) { string num = to_string(i); interface iface; iface.name = "iface_" + num; iface.dhcp_server = "dhcp_" + num; for (int binding_index = 0; binding_index < 2; ++binding_index) { string binding_num = to_string(binding_index); iface.ipv4_bindings.emplace_back(binding { "ip_" + num + "_" + binding_num, "netmask_" + num + "_" + binding_num, "network_" + num + "_" + binding_num }); iface.ipv6_bindings.emplace_back(binding { "ip6_" + num + "_" + binding_num, "netmask6_" + num + "_" + binding_num, "network6_" + num + "_" + binding_num }); } iface.macaddress = "macaddress_" + num; iface.mtu = i; result.interfaces.emplace_back(move(iface)); } result.primary_interface = "iface_2"; return result; } }; struct primary_interface_resolver : networking_resolver { protected: virtual data collect_data(collection& facts) override { data result; interface lo0; lo0.name = "lo0"; lo0.ipv4_bindings.emplace_back(binding { "127.0.0.1", "255.255.255.0", "127.0.0.0" }); lo0.ipv6_bindings.emplace_back(binding { "::1", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::1" }); result.interfaces.emplace_back(move(lo0)); interface en0; en0.name = "en0"; en0.ipv4_bindings.emplace_back(binding { "123.123.123.123", "255.255.255.0", "123.123.123.0" }); en0.ipv6_bindings.emplace_back(binding { "fe80::6203:8ff:fe95:2d16", "ffff:ffff:ffff:ffff::", "fe80::" }); result.interfaces.emplace_back(move(en0)); interface en1; en1.name = "en1"; en1.ipv4_bindings.emplace_back(binding { "123.123.123.124", "255.255.255.0", "123.123.123.0" }); en1.ipv6_bindings.emplace_back(binding { "fe80::6203:8ff:fe95:2d17", "ffff:ffff:ffff:ffff::", "fe80::" }); result.interfaces.emplace_back(move(en1)); return result; } }; SCENARIO("using the networking resolver") { collection_fixture facts; WHEN("data is not present") { facts.add(make_shared<empty_networking_resolver>()); THEN("facts should not be added") { REQUIRE(facts.size() == 0u); } } WHEN("only hostname is present") { facts.add(make_shared<test_hostname_resolver>()); REQUIRE(facts.size() == 3u); THEN("a flat fact is added") { auto hostname = facts.get<string_value>(fact::hostname); REQUIRE(hostname); REQUIRE(hostname->value() == "hostname"); } THEN("a structured fact is added") { auto networking = facts.get<map_value>(fact::networking); REQUIRE(networking); REQUIRE(networking->size() == 2u); auto hostname = networking->get<string_value>("hostname"); REQUIRE(hostname); REQUIRE(hostname->value() == "hostname"); auto fqdn = networking->get<string_value>("fqdn"); REQUIRE(fqdn); REQUIRE(fqdn->value() == "hostname"); } THEN("the FQDN fact is the hostname") { auto fqdn = facts.get<string_value>(fact::fqdn); REQUIRE(fqdn); REQUIRE(fqdn->value() == "hostname"); } } WHEN("only domain is present") { facts.add(make_shared<test_domain_resolver>()); REQUIRE(facts.size() == 2u); THEN("a flat fact is added") { auto domain = facts.get<string_value>(fact::domain); REQUIRE(domain); REQUIRE(domain->value() == "domain"); } THEN("a structured fact is added") { auto networking = facts.get<map_value>(fact::networking); REQUIRE(networking); REQUIRE(networking->size() == 1u); auto domain = networking->get<string_value>("domain"); REQUIRE(domain); REQUIRE(domain->value() == "domain"); } THEN("the FQDN fact is not present") { auto fqdn = facts.get<string_value>(fact::fqdn); REQUIRE_FALSE(fqdn); } } WHEN("domain contains a trailing dot, the dot is removed") { facts.add(make_shared<test_domain_with_trailing_dot>()); REQUIRE(facts.size() == 2u); THEN("a flat fact is added") { auto domain = facts.get<string_value>(fact::domain); REQUIRE(domain); REQUIRE(domain->value() == "domain.com"); } THEN("a structured fact is added") { auto networking = facts.get<map_value>(fact::networking); REQUIRE(networking); REQUIRE(networking->size() == 1u); auto domain = networking->get<string_value>("domain"); REQUIRE(domain); REQUIRE(domain->value() == "domain.com"); } THEN("the FQDN fact is not present") { auto fqdn = facts.get<string_value>(fact::fqdn); REQUIRE_FALSE(fqdn); } } WHEN("FQDN is present") { facts.add(make_shared<test_fqdn_resolver>()); REQUIRE(facts.size() == 2u); THEN("a flat fact is added") { auto fqdn = facts.get<string_value>(fact::fqdn); REQUIRE(fqdn); REQUIRE(fqdn->value() == "fqdn"); } THEN("a structured fact is added") { auto networking = facts.get<map_value>(fact::networking); REQUIRE(networking); REQUIRE(networking->size() == 1u); auto fqdn = networking->get<string_value>("fqdn"); REQUIRE(fqdn); REQUIRE(fqdn->value() == "fqdn"); } THEN("the FQDN fact is present") { auto fqdn = facts.get<string_value>(fact::fqdn); REQUIRE(fqdn); REQUIRE(fqdn->value() == "fqdn"); } } WHEN("FQDN is not present") { facts.add(make_shared<test_missing_fqdn_resolver>()); REQUIRE(facts.size() == 4u); THEN("the FQDN fact is the combination of hostname and domain") { auto svalue = facts.get<string_value>(fact::hostname); REQUIRE(svalue); REQUIRE(svalue->value() == "hostname"); svalue = facts.get<string_value>(fact::domain); REQUIRE(svalue); REQUIRE(svalue->value() == "domain.com"); svalue = facts.get<string_value>(fact::fqdn); REQUIRE(svalue); REQUIRE(svalue->value() == "hostname.domain.com"); } THEN("the FQDN in the structured fact is the combination of hostname and domain") { auto networking = facts.get<map_value>(fact::networking); REQUIRE(networking); REQUIRE(networking->size() == 3u); auto svalue = networking->get<string_value>("hostname"); REQUIRE(svalue); REQUIRE(svalue->value() == "hostname"); svalue = networking->get<string_value>("domain"); REQUIRE(svalue); REQUIRE(svalue->value() == "domain.com"); svalue = networking->get<string_value>("fqdn"); REQUIRE(svalue); REQUIRE(svalue->value() == "hostname.domain.com"); } } WHEN("network interfaces are present") { facts.add(make_shared<test_interface_resolver>()); REQUIRE(facts.size() == 50u); THEN("the DHCP servers fact is present") { auto dhcp_servers = facts.get<map_value>(fact::dhcp_servers); REQUIRE(dhcp_servers); REQUIRE(dhcp_servers->size() == 6u); for (unsigned int i = 0; i < 5; ++i) { string num = to_string(i); auto server = dhcp_servers->get<string_value>("iface_" + num); REQUIRE(server); REQUIRE(server->value() == "dhcp_" + num); } auto dhcp = dhcp_servers->get<string_value>("system"); REQUIRE(dhcp); REQUIRE(dhcp->value() == "dhcp_2"); } THEN("the interface names fact is present") { auto interfaces_list = facts.get<string_value>(fact::interfaces); REQUIRE(interfaces_list); REQUIRE(interfaces_list->value() == "iface_0,iface_1,iface_2,iface_3,iface_4"); } THEN("the interface flat facts are present") { for (unsigned int i = 0; i < 5; ++i) { string num = to_string(i); auto ip = facts.get<string_value>(fact::ipaddress + string("_iface_") + num); REQUIRE(ip); REQUIRE(ip->value() == "ip_" + num + "_0"); auto ip6 = facts.get<string_value>(fact::ipaddress6 + string("_iface_") + num); REQUIRE(ip6); REQUIRE(ip6->value() == "ip6_" + num + "_0"); auto macaddress = facts.get<string_value>(fact::macaddress + string("_iface_") + num); REQUIRE(macaddress); REQUIRE(macaddress->value() == "macaddress_" + num); auto mtu = facts.get<integer_value>(fact::mtu + string("_iface_") + num); REQUIRE(mtu); REQUIRE(mtu->value() == i); auto netmask = facts.get<string_value>(fact::netmask + string("_iface_") + num); REQUIRE(netmask); REQUIRE(netmask->value() == "netmask_" + num + "_0"); auto netmask6 = facts.get<string_value>(fact::netmask6 + string("_iface_") + num); REQUIRE(netmask6); REQUIRE(netmask6->value() == "netmask6_" + num + "_0"); auto network = facts.get<string_value>(fact::network + string("_iface_") + num); REQUIRE(network); REQUIRE(network->value() == "network_" + num + "_0"); auto network6 = facts.get<string_value>(fact::network6 + string("_iface_") + num); REQUIRE(network6); REQUIRE(network6->value() == "network6_" + num + "_0"); } } THEN("the system fact facts are present") { auto ip = facts.get<string_value>(fact::ipaddress); REQUIRE(ip); REQUIRE(ip->value() == "ip_2_0"); auto ip6 = facts.get<string_value>(fact::ipaddress6); REQUIRE(ip6); REQUIRE(ip6->value() == "ip6_2_0"); auto macaddress = facts.get<string_value>(fact::macaddress); REQUIRE(macaddress); REQUIRE(macaddress->value() == "macaddress_2"); auto netmask = facts.get<string_value>(fact::netmask); REQUIRE(netmask); REQUIRE(netmask->value() == "netmask_2_0"); auto netmask6 = facts.get<string_value>(fact::netmask6); REQUIRE(netmask6); REQUIRE(netmask6->value() == "netmask6_2_0"); auto network = facts.get<string_value>(fact::network); REQUIRE(network); REQUIRE(network->value() == "network_2_0"); auto network6 = facts.get<string_value>(fact::network6); REQUIRE(network6); REQUIRE(network6->value() == "network6_2_0"); } THEN("the networking structured fact is present") { auto networking = facts.get<map_value>(fact::networking); REQUIRE(networking); REQUIRE(networking->size() == 11u); auto primary = networking->get<string_value>("primary"); REQUIRE(primary); REQUIRE(primary->value() == "iface_2"); auto dhcp = networking->get<string_value>("dhcp"); REQUIRE(dhcp); REQUIRE(dhcp->value() == "dhcp_2"); auto ip = networking->get<string_value>("ip"); REQUIRE(ip); REQUIRE(ip->value() == "ip_2_0"); auto ip6 = networking->get<string_value>("ip6"); REQUIRE(ip6); REQUIRE(ip6->value() == "ip6_2_0"); auto macaddress = networking->get<string_value>("mac"); REQUIRE(macaddress); REQUIRE(macaddress->value() == "macaddress_2"); auto netmask = networking->get<string_value>("netmask"); REQUIRE(netmask); REQUIRE(netmask->value() == "netmask_2_0"); auto netmask6 = networking->get<string_value>("netmask6"); REQUIRE(netmask6); REQUIRE(netmask6->value() == "netmask6_2_0"); auto network = networking->get<string_value>("network"); REQUIRE(network); REQUIRE(network->value() == "network_2_0"); auto network6 = networking->get<string_value>("network6"); REQUIRE(network6); REQUIRE(network6->value() == "network6_2_0"); auto mtu = networking->get<integer_value>("mtu"); REQUIRE(mtu); REQUIRE(mtu->value() == 2); auto interfaces = networking->get<map_value>("interfaces"); REQUIRE(interfaces); for (unsigned int i = 0; i < 5; ++i) { string num = to_string(i); auto interface = interfaces->get<map_value>("iface_" + num); REQUIRE(interface); dhcp = interface->get<string_value>("dhcp"); REQUIRE(dhcp); REQUIRE(dhcp->value() == "dhcp_" + num); ip = interface->get<string_value>("ip"); REQUIRE(ip); REQUIRE(ip->value() == "ip_" + num + "_0"); ip6 = interface->get<string_value>("ip6"); REQUIRE(ip6); REQUIRE(ip6->value() == "ip6_" + num + "_0"); macaddress = interface->get<string_value>("mac"); REQUIRE(macaddress); REQUIRE(macaddress->value() == "macaddress_" + num); netmask = interface->get<string_value>("netmask"); REQUIRE(netmask); REQUIRE(netmask->value() == "netmask_" + num + "_0"); netmask6 = interface->get<string_value>("netmask6"); REQUIRE(netmask6); REQUIRE(netmask6->value() == "netmask6_" + num + "_0"); network = interface->get<string_value>("network"); REQUIRE(network); REQUIRE(network->value() == "network_" + num + "_0"); network6 = interface->get<string_value>("network6"); REQUIRE(network6); REQUIRE(network6->value() == "network6_" + num + "_0"); mtu = interface->get<integer_value>("mtu"); REQUIRE(mtu); REQUIRE(mtu->value() == i); auto bindings = interface->get<array_value>("bindings"); REQUIRE(bindings); REQUIRE(bindings->size() == 2u); for (size_t binding_index = 0; binding_index < bindings->size(); ++binding_index) { auto interface_num = to_string(binding_index); auto binding = bindings->get<map_value>(binding_index); REQUIRE(binding); auto address = binding->get<string_value>("address"); REQUIRE(address); REQUIRE(address->value() == "ip_" + num + "_" + interface_num); auto netmask = binding->get<string_value>("netmask"); REQUIRE(netmask); REQUIRE(netmask->value() == "netmask_" + num + "_" + interface_num); auto network = binding->get<string_value>("network"); REQUIRE(network); REQUIRE(network->value() == "network_" + num + "_" + interface_num); } bindings = interface->get<array_value>("bindings6"); REQUIRE(bindings); REQUIRE(bindings->size() == 2u); for (size_t binding_index = 0; binding_index < bindings->size(); ++binding_index) { auto interface_num = to_string(binding_index); auto binding = bindings->get<map_value>(binding_index); REQUIRE(binding); auto address = binding->get<string_value>("address"); REQUIRE(address); REQUIRE(address->value() == "ip6_" + num + "_" + interface_num); auto netmask = binding->get<string_value>("netmask"); REQUIRE(netmask); REQUIRE(netmask->value() == "netmask6_" + num + "_" + interface_num); auto network = binding->get<string_value>("network"); REQUIRE(network); REQUIRE(network->value() == "network6_" + num + "_" + interface_num); } } } } WHEN("the primary interface is not resolved") { facts.add(make_shared<primary_interface_resolver>()); THEN("the first interface with a valid address should be treated as primary") { REQUIRE(facts.query<string_value>("networking.primary")); REQUIRE(facts.query<string_value>("networking.primary")->value() == "en0"); } } } SCENARIO("ignored IPv4 addresses") { char const* ignored_addresses[] = { "", "127.0.0.1", "127.0.0.2", "127.1.0.0", "127.0.1.0", "169.254.7.14", "169.254.0.0", "169.254.255.255" }; for (auto s : ignored_addresses) { CAPTURE(s); REQUIRE(networking_resolver::ignored_ipv4_address(s)); } char const* accepted_addresses[] = { "169.253.0.0", "169.255.0.0", "100.100.100.100", "0.0.0.0", "1.1.1.1", "10.0.18.142", "192.168.0.1", "255.255.255.255", "128.0.0.1", }; for (auto s : accepted_addresses) { CAPTURE(s); REQUIRE_FALSE(networking_resolver::ignored_ipv4_address(s)); } } SCENARIO("ignore IPv6 adddresses") { char const* ignored_addresses[] = { "", "::1", "fe80::9c84:7ca1:794b:12ed", "fe80::75f2:2f55:823b:a513%10" }; for (auto s : ignored_addresses) { CAPTURE(s); REQUIRE(networking_resolver::ignored_ipv6_address(s)); } char const* accepted_addresses[] = { "::fe80:75f2:2f55:823b:a513", "fe7f::75f2:2f55:823b:a513%10", "::2", "::fe01", "::fe80" }; for (auto s : accepted_addresses) { CAPTURE(s); REQUIRE_FALSE(networking_resolver::ignored_ipv6_address(s)); } }