--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+use List::Util qw(first all);
+use NetAddr::IP qw(:lower);
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Tools qw(extract_param file_set_contents);
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Zones::Plugin;
+use PVE::Network::SDN::Controllers;
+use PVE::Network::SDN::Dns;
+use PVE::Network::SDN::Vnets;
+
+use PVE::RESTEnvironment;
+
+use PVE::API2::Network::SDN::Zones;
+use PVE::API2::Network::SDN::Subnets;
+use PVE::API2::Network::SDN::Vnets;
+use PVE::API2::Network::SDN::Ipams;
+
+my $TMP_ETHERS_FILE = "/tmp/ethers";
+
+my $test_state = undef;
+sub clear_test_state {
+ $test_state = {
+ locks => {},
+ datacenter_config => {},
+ subnets_config => {},
+ controller_config => {},
+ dns_config => {},
+ zones_config => {},
+ vnets_config => {},
+ macdb => {},
+ ipamdb => {},
+ ipam_config => {
+ 'ids' => {
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ }
+ },
+ };
+ PVE::Tools::file_set_contents($TMP_ETHERS_FILE, "\n");
+}
+clear_test_state();
+
+my $mocked_cfs_lock_file = sub {
+ my ($filename, $timeout, $code, @param) = @_;
+
+ die "$filename already locked\n" if ($test_state->{locks}->{$filename});
+
+ $test_state->{locks}->{$filename} = 1;
+
+ my $res = eval { $code->(@param); };
+
+ delete $test_state->{locks}->{$filename};
+
+ return $res;
+};
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+ return $sdn_config;
+}
+
+my $mocked_pve_sdn;
+$mocked_pve_sdn = Test::MockModule->new('PVE::Network::SDN');
+$mocked_pve_sdn->mock(
+ cfs_lock_file => $mocked_cfs_lock_file,
+);
+
+my $mocked_pve_tools = Test::MockModule->new('PVE::Tools');
+$mocked_pve_tools->mock(
+ lock_file => $mocked_cfs_lock_file,
+);
+
+my $mocked_sdn_zones;
+$mocked_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones');
+$mocked_sdn_zones->mock(
+ config => sub {
+ return $test_state->{zones_config};
+ },
+ write_config => sub {
+ my ($cfg) = @_;
+ $test_state->{zones_config} = $cfg;
+ },
+);
+
+my $mocked_sdn_zones_super_plugin;
+$mocked_sdn_zones_super_plugin = Test::MockModule->new('PVE::Network::SDN::Zones::Plugin');
+$mocked_sdn_zones_super_plugin->mock(
+ datacenter_config => sub {
+ return $test_state->{datacenter_config};
+ },
+);
+
+my $mocked_sdn_vnets;
+$mocked_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets');
+$mocked_sdn_vnets->mock(
+ config => sub {
+ return $test_state->{vnets_config};
+ },
+ write_config => sub {
+ my ($cfg) = @_;
+ $test_state->{vnets_config} = $cfg;
+ },
+ cfs_lock_file => $mocked_cfs_lock_file,
+);
+
+my $mocked_sdn_subnets;
+$mocked_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+$mocked_sdn_subnets->mock(
+ config => sub {
+ return $test_state->{subnets_config};
+ },
+ write_config => sub {
+ my ($cfg) = @_;
+ $test_state->{subnets_config} = $cfg;
+ },
+ cfs_lock_file => $mocked_cfs_lock_file,
+);
+
+my $mocked_sdn_controller;
+$mocked_sdn_controller = Test::MockModule->new('PVE::Network::SDN::Controllers');
+$mocked_sdn_controller->mock(
+ config => sub {
+ return $test_state->{controller_config};
+ },
+ write_config => sub {
+ my ($cfg) = @_;
+ $test_state->{controller_config} = $cfg;
+ },
+ cfs_lock_file => $mocked_cfs_lock_file,
+);
+
+
+my $mocked_sdn_dns;
+$mocked_sdn_dns = Test::MockModule->new('PVE::Network::SDN::Dns');
+$mocked_sdn_dns->mock(
+ config => sub {
+ return $test_state->{dns_config};
+ },
+ write_config => sub {
+ my ($cfg) = @_;
+ $test_state->{dns_config} = $cfg;
+ },
+ cfs_lock_file => $mocked_cfs_lock_file,
+);
+
+
+my $mocked_sdn_ipams;
+$mocked_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
+$mocked_sdn_ipams->mock(
+ config => sub {
+ return $test_state->{ipam_config};
+ },
+ write_config => sub {
+ my ($cfg) = @_;
+ $test_state->{ipam_config} = $cfg;
+ },
+ read_macdb => sub {
+ return $test_state->{macdb};
+ },
+ write_macdb => sub {
+ my ($cfg) = @_;
+ $test_state->{macdb} = $cfg;
+ },
+ cfs_lock_file => $mocked_cfs_lock_file,
+);
+
+my $ipam_plugin = PVE::Network::SDN::Ipams::Plugin->lookup("pve"); # NOTE this is hard-coded to pve
+my $mocked_ipam_plugin = Test::MockModule->new($ipam_plugin);
+$mocked_ipam_plugin->mock(
+ read_db => sub {
+ return $test_state->{ipamdb};
+ },
+ write_db => sub {
+ my ($cfg) = @_;
+ $test_state->{ipamdb} = $cfg;
+ },
+ cfs_lock_file => $mocked_cfs_lock_file,
+);
+
+my $mocked_sdn_dhcp_dnsmasq = Test::MockModule->new('PVE::Network::SDN::Dhcp::Dnsmasq');
+$mocked_sdn_dhcp_dnsmasq->mock(
+ assert_dnsmasq_installed => sub { return 1; },
+ before_configure => sub {},
+ ethers_file => sub { return "/tmp/ethers"; },
+ systemctl_service => sub {},
+ update_lease => sub {},
+);
+
+my $mocked_api_zones = Test::MockModule->new('PVE::API2::Network::SDN::Zones');
+$mocked_api_zones->mock(
+ create_etc_interfaces_sdn_dir => sub {},
+);
+
+my $rpcenv = PVE::RESTEnvironment->init('priv');
+$rpcenv->init_request();
+$rpcenv->set_language("en_US.UTF-8");
+$rpcenv->set_user('root@pam');
+
+my $mocked_rpc_env_obj = Test::MockModule->new('PVE::RESTEnvironment');
+$mocked_rpc_env_obj->mock(
+ check_any => sub { return 1; },
+);
+
+my $mocked_pve_cluster_obj = Test::MockModule->new('PVE::Cluster');
+$mocked_pve_cluster_obj->mock(
+ check_cfs_quorum => sub { return 1; },
+);
+
+# ------- TEST FUNCTIONS --------------
+
+sub nic_join {
+ my ($vnetid, $mac, $hostname, $vmid) = @_;
+ return PVE::Network::SDN::Vnets::add_next_free_cidr($vnetid, $hostname, $mac, "$vmid", undef, 1);
+}
+
+sub nic_leave {
+ my ($vnetid, $mac, $hostname) = @_;
+ return PVE::Network::SDN::Vnets::del_ips_from_mac($vnetid, $mac, $hostname);
+}
+
+sub nic_start {
+ my ($vnetid, $mac, $vmid, $hostname) = @_;
+ return PVE::Network::SDN::Vnets::add_dhcp_mapping($vnetid, $mac, $vmid, $hostname);
+}
+
+
+# ---- API HELPER FUNCTIONS FOR THE TESTS -----
+
+my $t_invalid;
+sub get_zone {
+ my ($id) = @_;
+ return eval { PVE::API2::Network::SDN::Zones->read({zone => $id}); };
+}
+# verify get_zone actually fails if invalid
+$t_invalid = get_zone("invalid");
+die("getting an invalid zone must fail") if (!$@);
+fail("getting an invalid zone must fail") if (defined $t_invalid);
+
+sub create_zone {
+ my ($params) = @_;
+ my $zoneid = $params->{zone};
+ # die if failed!
+ eval { PVE::API2::Network::SDN::Zones->create($params); };
+ die("creating zone failed: $@") if ($@);
+
+ my $zone = get_zone($zoneid);
+ die ("test setup: zone ($zoneid) not defined") if (!defined $zone);
+ return $zone;
+}
+
+sub get_vnet {
+ my ($id) = @_;
+ return eval { PVE::API2::Network::SDN::Vnets->read({vnet => $id}); };
+}
+# verify get_vnet
+$t_invalid = get_vnet("invalid");
+die("getting an invalid vnet must fail") if (!$@);
+fail("getting an invalid vnet must fail") if (defined $t_invalid);
+
+sub create_vnet {
+ my ($params) = @_;
+ my $vnetid = $params->{vnet};
+ PVE::API2::Network::SDN::Vnets->create($params);
+
+ my $vnet = get_vnet($vnetid);
+ die ("test setup: vnet ($vnetid) not defined") if (!defined $vnet);
+ return $vnet;
+}
+
+sub get_subnet {
+ my ($id) = @_;
+ return eval { PVE::API2::Network::SDN::Subnets->read({subnet => $id}); };
+}
+# verify get_subnet
+$t_invalid = get_subnet("invalid");
+die("getting an invalid subnet must fail") if (!$@);
+fail("getting an invalid subnet must fail") if (defined $t_invalid);
+
+sub create_subnet {
+ my ($params) = @_;
+ PVE::API2::Network::SDN::Subnets->create($params);
+}
+
+sub get_ipam_entries {
+ return PVE::API2::Network::SDN::Ipams->ipamindex({ipam => "pve"});
+}
+
+sub create_ip {
+ my ($param) = @_;
+ return PVE::API2::Network::SDN::Ips->ipcreate($param);
+}
+
+sub run_test {
+ my $test = shift;
+ clear_test_state();
+ $test->(@_);
+}
+
+sub get_ips_from_mac {
+ my ($mac) = @_;
+ my $ipam_entries = get_ipam_entries();
+ return grep { $_->{mac} eq $mac if defined $_->{mac} } $ipam_entries->@* if $ipam_entries;
+}
+
+sub get_ip4 {
+ my $ip4 = first { Net::IP::ip_is_ipv4($_->{ip}) } @_;
+ return $ip4->{ip} if defined $ip4;
+}
+
+sub get_ip6 {
+ my $ip6 = first { Net::IP::ip_is_ipv6($_->{ip}) } @_;
+ return $ip6->{ip} if defined $ip6;
+}
+
+
+# -------------- ACTUAL TESTS -----------------------
+
+sub test_create_vnet_with_gateway {
+ my $test_name = (split(/::/,(caller(0))[3]))[-1];
+ my $zoneid = "TESTZONE";
+ my $vnetid = "testvnet";
+
+ my $zone = create_zone({
+ type => "simple",
+ dhcp => "dnsmasq",
+ ipam => "pve",
+ zone => $zoneid,
+ });
+
+ my $vnet = create_vnet({
+ type => "vnet",
+ zone => $zoneid,
+ vnet => $vnetid,
+ });
+
+ create_subnet({
+ type => "subnet",
+ vnet => $vnetid,
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ });
+
+ my ($p) = first { $_->{gateway} == 1 } get_ipam_entries()->@*;
+ ok ($p, "$test_name: Gateway IP was created in IPAM");
+}
+run_test(\&test_create_vnet_with_gateway);
+
+
+sub test_without_subnet {
+ my $test_name = (split(/::/,(caller(0))[3]))[-1];
+
+ my $zoneid = "TESTZONE";
+ my $vnetid = "testvnet";
+
+ my $zone = create_zone({
+ type => "simple",
+ dhcp => "dnsmasq",
+ ipam => "pve",
+ zone => $zoneid,
+ });
+
+ my $vnet = create_vnet({
+ type => "vnet",
+ zone => $zoneid,
+ vnet => $vnetid,
+ });
+
+ my $hostname = "testhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $vmid = "999";
+
+ eval {
+ nic_join($vnetid, $mac, $hostname, $vmid);
+ };
+
+ if ($@) {
+ fail("$test_name: $@");
+ return;
+ }
+
+ my @ips = get_ips_from_mac($mac);
+ my $num_ips = scalar @ips;
+ is ($num_ips, 0, "$test_name: No IP allocated in IPAM");
+}
+run_test(\&test_without_subnet);
+
+
+sub test_nic_join {
+ my ($test_name, $subnets) = @_;
+
+ die "$test_name: we're expecting an array of subnets" if !$subnets;
+ my $num_subnets = scalar $subnets->@*;
+ die "$test_name: we're expecting an array of subnets. $num_subnets elements found" if ($num_subnets < 1);
+ my $num_dhcp_ranges = scalar grep { $_->{'dhcp-range'} } $subnets->@*;
+
+ my $zoneid = "TESTZONE";
+ my $vnetid = "testvnet";
+
+ my $zone = create_zone({
+ type => "simple",
+ dhcp => "dnsmasq",
+ ipam => "pve",
+ zone => $zoneid,
+ });
+
+ my $vnet = create_vnet({
+ type => "vnet",
+ zone => $zoneid,
+ vnet => $vnetid,
+ });
+
+ foreach my $subnet ($subnets->@*) {
+ $subnet->{type} = "subnet";
+ $subnet->{vnet} = $vnetid;
+ create_subnet($subnet);
+ };
+
+ my $hostname = "testhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $vmid = "999";
+
+ eval {
+ nic_join($vnetid, $mac, $hostname, $vmid);
+ };
+
+ if ($@) {
+ fail("$test_name: $@");
+ return;
+ }
+
+ my @ips = get_ips_from_mac($mac);
+ my $num_ips = scalar @ips;
+ is ($num_ips, $num_dhcp_ranges, "$test_name: Expecting $num_dhcp_ranges IPs, found $num_ips");
+ ok ((all { ($_->{vnet} eq $vnetid && $_->{zone} eq $zoneid) } @ips),
+ "$test_name: all IPs in correct vnet and zone"
+ );
+}
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv4 no dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ },
+]);
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv6 no dhcp",
+ [{
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ },
+]);
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv4+6 no dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ },
+]);
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv4 with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ },
+]);
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv6 with dhcp",
+ [{
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ },
+]);
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv4+6 with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ },
+]);
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv4 no DHCP, IPv6 with DHCP",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ },
+]);
+
+run_test(
+ \&test_nic_join,
+ "nic_join IPv4 with DHCP, IPv6 no DHCP",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ },
+]);
+
+
+sub test_nic_join_full_dhcp_range {
+ my ($test_name, $subnets, $expected_ip4, $expected_ip6) = @_;
+
+ die "$test_name: we're expecting an array of subnets" if !$subnets;
+ my $num_subnets = scalar $subnets->@*;
+ die "$test_name: we're expecting an array of subnets. $num_subnets elements found" if ($num_subnets < 1);
+
+ my $zoneid = "TESTZONE";
+ my $vnetid = "testvnet";
+
+ my $zone = create_zone({
+ type => "simple",
+ dhcp => "dnsmasq",
+ ipam => "pve",
+ zone => $zoneid,
+ });
+
+ my $vnet = create_vnet({
+ type => "vnet",
+ zone => $zoneid,
+ vnet => $vnetid,
+ });
+
+ foreach my $subnet ($subnets->@*) {
+ $subnet->{type} = "subnet";
+ $subnet->{vnet} = $vnetid;
+ create_subnet($subnet);
+ };
+
+ my $hostname = "testhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $vmid = "999";
+
+ eval {
+ nic_join($vnetid, $mac, $hostname, $vmid);
+ };
+
+ if (! $@) {
+ fail ("$test_name: nic_join() is expected to fail because we cannot allocate all IPs");
+ }
+
+ my @ips = get_ips_from_mac($mac);
+ my $num_ips = scalar @ips;
+ is ($num_ips, 0, "$test_name: No IP allocated in IPAM");
+}
+
+run_test(
+ \&test_nic_join_full_dhcp_range,
+ "nic_join IPv4 with DHCP, dhcp-range full",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.100", # the gateway uses the only available IP in the dhcp-range
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"],
+ }
+]);
+
+run_test(
+ \&test_nic_join_full_dhcp_range,
+ "nic_join IPv6 with DHCP, dhcp-range full",
+ [{
+ subnet => "8888::/64",
+ gateway => "8888::100", # the gateway uses the only available IP in the dhcp-range
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"],
+ },
+]);
+
+run_test(
+ \&test_nic_join_full_dhcp_range,
+ "nic_join IPv4+6 with DHCP, dhcp-range full for both",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.100",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::100",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"],
+ }
+]);
+
+run_test(
+ \&test_nic_join_full_dhcp_range,
+ "nic_join IPv4+6 with DHCP, dhcp-range full for IPv4",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.100", # the gateway uses the only available IP in the dhcp-range
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"],
+ }],
+);
+
+run_test(
+ \&test_nic_join_full_dhcp_range,
+ "nic_join IPv4+6 with DHCP, dhcp-range full for IPv6",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.100"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::100",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"],
+ }],
+);
+
+run_test(
+ \&test_nic_join_full_dhcp_range,
+ "nic_join IPv4 no DHCP, dhcp-range full for IPv6",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::100",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::100"],
+ }],
+);
+
+
+# -------------- nic_start
+sub test_nic_start {
+ my ($test_name, $subnets, $current_ip4, $current_ip6, $num_expected_ips) = @_;
+
+ die "$test_name: we're expecting an array of subnets" if !$subnets;
+ my $num_subnets = scalar $subnets->@*;
+ die "$test_name: we're expecting an array of subnets. $num_subnets elements found" if ($num_subnets < 1);
+
+ $num_expected_ips = scalar grep { $_->{'dhcp-range'} } $subnets->@* if !defined $num_expected_ips;
+
+ my $zoneid = "TESTZONE";
+ my $vnetid = "testvnet";
+
+ my $zone = create_zone({
+ type => "simple",
+ dhcp => "dnsmasq",
+ ipam => "pve",
+ zone => $zoneid,
+ });
+
+ my $vnet = create_vnet({
+ type => "vnet",
+ zone => $zoneid,
+ vnet => $vnetid,
+ });
+
+ foreach my $subnet ($subnets->@*) {
+ $subnet->{type} = "subnet";
+ $subnet->{vnet} = $vnetid;
+ create_subnet($subnet);
+ };
+
+ my $hostname = "testhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $vmid = "999";
+
+ if ($current_ip4) {
+ create_ip({
+ zone => $zoneid,
+ vnet => $vnetid,
+ mac => $mac,
+ ip => $current_ip4,
+ });
+ }
+
+ if ($current_ip6) {
+ create_ip({
+ zone => $zoneid,
+ vnet => $vnetid,
+ mac => $mac,
+ ip => $current_ip6,
+ });
+ }
+ my @current_ips = get_ips_from_mac($mac);
+ is ( get_ip4(@current_ips), $current_ip4, "$test_name: setup current IPv4: $current_ip4" ) if defined $current_ip4;
+ is ( get_ip6(@current_ips), $current_ip6, "$test_name: setup current IPv6: $current_ip6" ) if defined $current_ip6;
+
+ eval {
+ nic_start($vnetid, $mac, $hostname, $vmid);
+ };
+
+ if ($@) {
+ fail("$test_name: $@");
+ return;
+ }
+
+ my @ips = get_ips_from_mac($mac);
+ my $num_ips = scalar @ips;
+ is ($num_ips, $num_expected_ips, "$test_name: Expecting $num_expected_ips IPs, found $num_ips");
+ ok ((all { ($_->{vnet} eq $vnetid && $_->{zone} eq $zoneid) } @ips),
+ "$test_name: all IPs in correct vnet and zone"
+ );
+
+ is ( get_ip4(@ips), $current_ip4, "$test_name: still current IPv4: $current_ip4" ) if $current_ip4;
+ is ( get_ip6(@ips), $current_ip6, "$test_name: still current IPv6: $current_ip6" ) if $current_ip6;
+}
+
+run_test(
+ \&test_nic_start,
+ "nic_start no IP, IPv4 without dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ },
+]);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IP, IPv4 without dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ }],
+ "10.0.0.99",
+ undef,
+ 1
+);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IPv6, IPv6 without dhcp",
+ [{
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ }],
+ undef,
+ "8888::99",
+ 1
+);
+
+run_test(
+ \&test_nic_start,
+ "nic_start no IP, IPv4 subnet with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ },
+]);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IP, IPv4 subnet with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }],
+ "10.0.0.99"
+);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IP, IPv6 subnet with dhcp",
+ [{
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ }],
+ undef,
+ "8888::99"
+);
+
+run_test(
+ \&test_nic_start,
+ "nic_start IP, IPv4+6 subnet with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ },
+]);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IPv4, IPv4+6 subnet with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ }],
+ "10.0.0.99"
+);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IPv6, IPv4+6 subnet with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ }],
+ undef,
+ "8888::99"
+);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IPv4+6, IPv4+6 subnets with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ 'dhcp-range' => ["start-address=8888::100,end-address=8888::200"],
+ }],
+ "10.0.0.99",
+ "8888::99"
+);
+
+run_test(
+ \&test_nic_start,
+ "nic_start already IPv4+6, only IPv4 subnet with dhcp",
+ [{
+ subnet => "10.0.0.0/24",
+ gateway => "10.0.0.1",
+ 'dhcp-range' => ["start-address=10.0.0.100,end-address=10.0.0.200"],
+ }, {
+ subnet => "8888::/64",
+ gateway => "8888::1",
+ }],
+ "10.0.0.99",
+ "8888::99",
+ 2
+);
+
+done_testing();