diff --git a/xCAT-server/lib/xcat/plugins/dhcp.pm b/xCAT-server/lib/xcat/plugins/dhcp.pm index 0d579aa57..322abb598 100644 --- a/xCAT-server/lib/xcat/plugins/dhcp.pm +++ b/xCAT-server/lib/xcat/plugins/dhcp.pm @@ -232,22 +232,41 @@ sub _isc_static_host_fallback sub _delete_isc_static_host { - my $node = shift; + my ($node, $config, $hostname) = @_; + $config ||= \@dhcpconf; + + my $start_marker = defined($hostname) + ? qr/^#xCAT host declaration for \Q$node\E aka host \Q$hostname\E start$/ + : qr/^#xCAT host declaration for \Q$node\E aka host .* start$/; + my $end_marker = defined($hostname) + ? qr/^(?:\}\s*)?#xCAT host declaration for \Q$node\E aka host \Q$hostname\E end$/ + : qr/^(?:\}\s*)?#xCAT host declaration for \Q$node\E aka host .* end$/; my @updated; my $skip = 0; - foreach my $line (@dhcpconf) { - if ($line =~ /^#xCAT host declaration for \Q$node\E\b.* start$/) { + foreach my $line (@{$config}) { + if ($line =~ $start_marker) { $skip = 1; next; } - if ($skip && $line =~ /^#xCAT host declaration for \Q$node\E\b.* end$/) { + if ($skip && $line =~ $end_marker) { $skip = 0; next; } push @updated, $line unless $skip; } - @dhcpconf = @updated; + @{$config} = @updated; +} + +sub _begin_isc_static_host_update +{ + my ($node, $enabled, $config) = @_; + + return 0 unless $enabled; + $config ||= \@dhcpconf; + my $line_count = scalar(@{$config}); + _delete_isc_static_host($node, $config); + return scalar(@{$config}) != $line_count; } sub _static_host_statements @@ -261,21 +280,51 @@ sub _static_host_statements return $statements; } +sub _node_host_statements +{ + my ($node, $statements) = @_; + + return 'ddns-hostname \"' . $node . '\"; send host-name \"' + . $node . '\";' . ($statements || ''); +} + +sub _noip_hostname +{ + my ($node, $mac) = @_; + my $hostname = $node . '-noip' . $mac; + $hostname =~ s/://g; + return $hostname; +} + sub _add_isc_static_host { - my ($node, $hostname, $mac, $ip, $statements) = @_; + my ($node, $hostname, $mac, $hardwaretype, $mgtifname, $ip, $statements, + $has_infiniband_identity, $config, $update_started) = @_; + $config ||= \@dhcpconf; return unless $ip && $ip ne 'DENIED'; - _delete_isc_static_host($node); + if ($update_started && !${$update_started}) { + $restartdhcp = 1 if _begin_isc_static_host_update($node, 1, $config); + ${$update_started} = 1; + } + + $mac = normalize_mac($mac) || $mac; + _delete_isc_static_host($node, $config, $hostname); my $host_statements = _static_host_statements($statements); - push @dhcpconf, "#xCAT host declaration for $node aka host $hostname start\n"; - push @dhcpconf, "host $hostname {\n"; - push @dhcpconf, " hardware ethernet $mac;\n"; - push @dhcpconf, " fixed-address $ip;\n"; - push @dhcpconf, " $host_statements\n" if $host_statements; - push @dhcpconf, "} #xCAT host declaration for $node aka host $hostname end\n"; + my $hardware_kind = $hardwaretype == 32 ? 'infiniband' : 'ethernet'; + push @{$config}, "#xCAT host declaration for $node aka host $hostname start\n"; + push @{$config}, "host $hostname {\n"; + push @{$config}, " hardware $hardware_kind $mac;\n"; + push @{$config}, " fixed-address $ip;\n"; + push @{$config}, " $host_statements\n" if $host_statements; + push @{$config}, "}\n"; + push @{$config}, _infiniband_twin_static_lines( + $hostname, $mac, $hardwaretype, $mgtifname, $ip, + $host_statements, $has_infiniband_identity + ); + push @{$config}, "#xCAT host declaration for $node aka host $hostname end\n"; $restartdhcp = 1; } @@ -491,6 +540,7 @@ sub delnode if ($ent and $ent->{mac}) { my @macs = split(/\|/, $ent->{mac}); + my $has_infiniband_identity = _infiniband_identity_present(@macs); my $mace; my $count = 0; foreach $mace (@macs) @@ -505,13 +555,19 @@ sub delnode } #Default to hostname equal to nodename unless ($mac) { next; } #Skip corrupt format - if (!grep /:/, $mac) { + my $normalized_mac = normalize_mac($mac); + if ($normalized_mac) { + $mac = $normalized_mac; + } elsif (!grep /:/, $mac) { $mac = lc($mac); $mac =~ s/(\w{2})/$1:/g; $mac =~ s/:$//; } my $hostname = $hname; my %client_nethash = xCAT::DBobjUtils->getNetwkInfo([$node]); + my $hardwaretype = _hardware_type_for( + $mac, $client_nethash{$node}{mgtifname} + ); if ($client_nethash{$node}{mgtifname} =~ /hf/) { if (scalar(@macs) > 1) { @@ -532,14 +588,21 @@ sub delnode print $omshell "remove\n"; print $omshell "close\n"; - if ($mac) + my ($ibnamecommands, $ibaddresscommands) = + _infiniband_twin_delete_commands( + $hostname, $mac, $hardwaretype, + $client_nethash{$node}{mgtifname}, + _omapi_pre_create_cleanup_supported(), + $has_infiniband_identity + ); + print $omshell $ibnamecommands if ($ibnamecommands); + + my $hardwarecommands = + _hardware_address_delete_commands($mac, $hardwaretype); + if ($hardwarecommands) { - print $omshell "new host\n"; - print $omshell "set hardware-address = " . $mac - . "\n"; #find and destroy mac conflict - print $omshell "open\n"; - print $omshell "remove\n"; - print $omshell "close\n"; + print $omshell $hardwarecommands; + print $omshell $ibaddresscommands if ($ibaddresscommands); } if ($inetn and _omapi_ip_lookup_supported()) { @@ -649,6 +712,182 @@ sub addnode6 { } +sub _infiniband_twin_mac +{ + my $mac = shift; + + $mac = normalize_mac($mac); + return unless defined($mac) and length($mac) == 17; + return substr($mac, 0, 8) . ":03:00" . substr($mac, 8); +} + +sub _infiniband_identity_present +{ + foreach my $entry (@_) { + my ($address) = split(/!/, $entry); + $address = normalize_mac($address); + return 1 + if defined($address) + && (length($address) == 23 || length($address) == 26); + } + return 0; +} + +sub _hardware_type_for +{ + my ($mac, $mgtifname) = @_; + + return 37 if defined($mgtifname) && $mgtifname =~ /hf/; + $mac = normalize_mac($mac) || $mac || ''; + return 32 if length($mac) == 23 || length($mac) == 26; + return 1; +} + +sub _is_infiniband_interface +{ + my $mgtifname = shift; + return defined($mgtifname) && $mgtifname =~ /(?:^|!)ib[^!]*(?:!|$)/; +} + +sub _infiniband_twin_identity +{ + my ($hostname, $mac, $hardwaretype, $mgtifname, + $has_infiniband_identity) = @_; + my $ibmac = _infiniband_twin_mac($mac); + + return if $hardwaretype != 1 + || !$ibmac + || !_is_infiniband_interface($mgtifname) + || $has_infiniband_identity; + return ("$hostname-xcat-ib", $ibmac); +} + +sub _infiniband_twin_create_commands +{ + my ($hostname, $mac, $hardwaretype, $mgtifname, $ip, $hoststatements, + $cleanup_supported, $has_infiniband_identity) = @_; + my ($ibhostname, $ibmac) = _infiniband_twin_identity( + $hostname, $mac, $hardwaretype, $mgtifname, + $has_infiniband_identity + ); + + return unless $ibmac; + + my $commands = ''; + if ($cleanup_supported && _is_infiniband_interface($mgtifname)) { + $commands .= "new host\n" + . "set name = \"$ibhostname\"\n" + . "open\n" + . "remove\n" + . "close\n"; + } + $commands .= "new host\n" + . "set name = \"$ibhostname\"\n" + . "set hardware-address = $ibmac\n" + . "set dhcp-client-identifier = $ibmac\n" + . "set hardware-type = 32\n"; + + if ($ip eq "DENIED") { + $commands .= "set statements = \"deny booting;\"\n"; + } else { + $commands .= "set ip-address = $ip\n" if ($ip); + $commands .= "set statements = \"$hoststatements\"\n" if ($hoststatements); + } + + return $commands . "create\nclose\n"; +} + +sub _infiniband_twin_static_lines +{ + my ($hostname, $mac, $hardwaretype, $mgtifname, $ip, $hoststatements, + $has_infiniband_identity) = @_; + my ($ibhostname, $ibmac) = _infiniband_twin_identity( + $hostname, $mac, $hardwaretype, $mgtifname, + $has_infiniband_identity + ); + + return unless $ibmac && $ip && $ip ne 'DENIED'; + + my @lines = ( + "host $ibhostname {\n", + " hardware infiniband $ibmac;\n", + " fixed-address $ip;\n", + ); + push @lines, " $hoststatements\n" if $hoststatements; + push @lines, "}\n"; + return @lines; +} + +sub _infiniband_twin_delete_commands +{ + my ($hostname, $mac, $hardwaretype, $mgtifname, $cleanup_supported, + $has_infiniband_identity) = @_; + my $ibmac = _infiniband_twin_mac($mac); + return unless $ibmac; + if (!$cleanup_supported) { + my (undef, $current_ibmac) = _infiniband_twin_identity( + $hostname, $mac, $hardwaretype, $mgtifname, + $has_infiniband_identity + ); + return unless $current_ibmac; + } + my $ibhostname = "$hostname-xcat-ib"; + + my $namecommands = "new host\n" + . "set name = \"$ibhostname\"\n" + . "open\n" + . "remove\n" + . "close\n"; + my $addresscommands; + + if ($cleanup_supported && _is_infiniband_interface($mgtifname)) { + $addresscommands = "new host\n" + . "set hardware-address = $ibmac\n" + . "set hardware-type = 32\n" + . "open\n" + . "remove\n" + . "close\n"; + } + + return ($namecommands, $addresscommands); +} + +sub _hardware_address_delete_commands +{ + my ($mac, $hardwaretype) = @_; + return unless $mac; + + my $commands = "new host\n" + . "set hardware-address = $mac\n"; + $commands .= "set hardware-type = $hardwaretype\n" + if $hardwaretype != 1; + + return $commands . "open\nremove\nclose\n"; +} + +sub _infiniband_twin_update_commands +{ + my ($hostname, $mac, $hardwaretype, $mgtifname, $ip, $hoststatements, + $cleanup_supported, $has_infiniband_identity) = @_; + my ($namecommands, $addresscommands); + + if ($cleanup_supported) { + ($namecommands, $addresscommands) = + _infiniband_twin_delete_commands( + $hostname, $mac, $hardwaretype, $mgtifname, + $cleanup_supported, $has_infiniband_identity + ); + undef $addresscommands if $has_infiniband_identity; + } + my $createcommands = _infiniband_twin_create_commands( + $hostname, $mac, $hardwaretype, $mgtifname, $ip, + $hoststatements, 0, $has_infiniband_identity + ); + + return ($namecommands, $addresscommands, $createcommands); +} + + sub addnode { @@ -772,6 +1011,9 @@ sub addnode } my @macs = split(/\|/, $ent->{mac}); + my $has_infiniband_identity = _infiniband_identity_present(@macs); + my $static_host_fallback = _isc_static_host_fallback(); + my $static_host_update_started = 0; my $mace; my $deflstaments = $lstatements; my $count = 0; @@ -797,10 +1039,11 @@ sub addnode ); next; } + my $noip_mac = $mac; + $mac = normalize_mac($mac); my $ip = getipaddr($hname, OnlyV4 => 1); if ($hname eq '*NOIP*') { - $hname = $node . "-noip" . $mac; - $hname =~ s/://g; + $hname = _noip_hostname($node, $noip_mac); $ip = 'DENIED'; # } #if 'guess_next_server', inherit from the network provided value... see how this pans out @@ -928,17 +1171,13 @@ sub addnode } else { - if (!grep /:/, $mac) { - $mac = lc($mac); - $mac =~ s/(\w{2})/$1:/g; - $mac =~ s/:$//; - } my $hostname = $hname; - my $hardwaretype = 1; my %client_nethash = xCAT::DBobjUtils->getNetwkInfo([$node]); - if ($client_nethash{$node}{mgtifname} =~ /hf/) + my $hardwaretype = _hardware_type_for( + $mac, $client_nethash{$node}{mgtifname} + ); + if ($hardwaretype == 37) { - $hardwaretype = 37; if (scalar(@macs) > 1) { if ($hname !~ /^(.*)-hf(.*)$/) { $hostname = $hname . "-hf" . $count; @@ -946,27 +1185,35 @@ sub addnode $hostname = $1 . "-hf" . $count; } } - } elsif (length($mac) == 23 || length($mac) == 26) { # 8 or 9 bytes of mac address - # Currently the only thing that has 8 or 9 bytes is an infiniband - # or infiniband like device, which is type 32 (0x20). - $hardwaretype = 32; } - if (_isc_static_host_fallback()) { + if ($static_host_fallback) { if ($ip ne "DENIED") { - if ($lstatements) { - $lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";' . $lstatements; - } else { - $lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";'; - } + $lstatements = _node_host_statements($node, $lstatements); } else { $lstatements = "deny booting;"; } - _add_isc_static_host($node, $hostname, $mac, $ip, $lstatements); + _add_isc_static_host( + $node, $hostname, $mac, $hardwaretype, + $client_nethash{$node}{mgtifname}, $ip, $lstatements, + $has_infiniband_identity, undef, + \$static_host_update_started + ); $count = $count + 2; next; } + if ($ip ne "DENIED") { + $lstatements = _node_host_statements($node, $lstatements); + } + my ($ibnamecommands, $ibaddresscommands, $ibcreatecommands) = + _infiniband_twin_update_commands( + $hostname, $mac, $hardwaretype, + $client_nethash{$node}{mgtifname}, $ip, $lstatements, + _omapi_pre_create_cleanup_supported(), + $has_infiniband_identity + ); + #syslog("local4|err", "Setting $node ($hname|$ip) to " . $mac); if (_omapi_pre_create_cleanup_supported()) { print $omshell "new host\n"; @@ -975,6 +1222,7 @@ sub addnode print $omshell "open\n"; print $omshell "remove\n"; print $omshell "close\n"; + print $omshell $ibnamecommands if ($ibnamecommands); } if ($ip and $ip ne 'DENIED' and _omapi_ip_lookup_supported()) { print $omshell "new host\n"; @@ -984,12 +1232,9 @@ sub addnode print $omshell "close\n"; } if (_omapi_pre_create_cleanup_supported()) { - print $omshell "new host\n"; - print $omshell "set hardware-address = " . $mac - . "\n"; #find and destroy mac conflict - print $omshell "open\n"; - print $omshell "remove\n"; - print $omshell "close\n"; + print $omshell + _hardware_address_delete_commands($mac, $hardwaretype); + print $omshell $ibaddresscommands if ($ibaddresscommands); } print $omshell "new host\n"; print $omshell "set name = \"$hostname\"\n"; @@ -1006,18 +1251,12 @@ sub addnode if ($ip) { print $omshell "set ip-address = $ip\n"; } - if ($lstatements) - { - $lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";' . $lstatements; - - } else { - $lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";'; - } print $omshell "set statements = \"$lstatements\"\n"; } print $omshell "create\n"; print $omshell "close\n"; + print $omshell $ibcreatecommands if ($ibcreatecommands); unless ($::XCATSITEVALS{externaldhcpservers}) { unless (grep /#definition for host $node aka host $hostname/, @dhcpconf) { @@ -3319,7 +3558,7 @@ sub kea_xnba_client_classes_for_nodes ); } -sub kea_normalize_mac +sub normalize_mac { my ($mac) = @_; @@ -3329,6 +3568,11 @@ sub kea_normalize_mac return lc($mac); } +sub kea_normalize_mac +{ + return normalize_mac(shift); +} + sub kea_node_reservations6 { my ( $backend, $config, $node ) = @_; diff --git a/xCAT-test/unit/dhcp_infiniband_twin.t b/xCAT-test/unit/dhcp_infiniband_twin.t new file mode 100644 index 000000000..ea4d79e8a --- /dev/null +++ b/xCAT-test/unit/dhcp_infiniband_twin.t @@ -0,0 +1,343 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../xCAT-server/lib"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use lib "$FindBin::Bin/../../perl-xCAT"; + +use Test::More; + +$ENV{XCATCFG} ||= 'SQLite:/tmp'; + +my $source_dhcp_plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/dhcp.pm"; +if ( -f $source_dhcp_plugin ) { + require $source_dhcp_plugin; +} else { + require xCAT_plugin::dhcp; +} + +sub twin { return xCAT_plugin::dhcp::_infiniband_twin_mac( $_[0] ); } + +is( + xCAT_plugin::dhcp::normalize_mac('B8-3F-D2-4A-68-AA'), + 'b8:3f:d2:4a:68:aa', + 'the shared MAC normalizer canonicalizes case and separators', +); +is( twin('b8:3f:d2:4a:68:aa'), 'b8:3f:d2:03:00:4a:68:aa', + 'the port GUID of the first adapter is derived from its mac' ); +is( twin('b8:3f:d2:4a:68:b2'), 'b8:3f:d2:03:00:4a:68:b2', + 'the port GUID of the second adapter is derived from its mac' ); +is( twin('b8-3f-d2-4a-68-aa'), 'b8:3f:d2:03:00:4a:68:aa', + 'a dash-separated mac produces a canonical InfiniBand identity' ); +is( length( twin('b8:3f:d2:4a:68:aa') ), 23, + 'the derived address is eight bytes' ); + +foreach my $other ( 'b8:3f:d2:03:00:4a:68:aa', '00:11:22:33:44', '', 'notamac' ) { + is( twin($other), undef, "'$other' gives no derived address" ); +} +is( twin(undef), undef, 'no mac gives no derived address' ); +ok( + xCAT_plugin::dhcp::_infiniband_identity_present( + 'b8:3f:d2:4a:68:aa!node01', + 'b8:3f:d2:03:00:4a:68:aa!node01-ib', + ), + 'an explicit InfiniBand identity is detected in a node mac list', +); +ok( + !xCAT_plugin::dhcp::_infiniband_identity_present( + 'b83fd203004a68aa!node01-ib', + ), + 'a colonless address rejected by addnode is not treated as an identity', +); +ok( + xCAT_plugin::dhcp::_infiniband_identity_present( + 'b8-3f-d2-03-00-4a-68-aa!node01-ib', + ), + 'a valid dashed InfiniBand identity is detected in a node mac list', +); +ok( + !xCAT_plugin::dhcp::_infiniband_identity_present( + 'b8:3f:d2:4a:68:aa!node01', + ), + 'an Ethernet-only mac list needs the derived identity', +); +is( + xCAT_plugin::dhcp::_hardware_type_for('b8:3f:d2:4a:68:aa', 'eth0'), + 1, + 'an Ethernet interface uses the Ethernet hardware type', +); +is( + xCAT_plugin::dhcp::_hardware_type_for( + 'b8:3f:d2:03:00:4a:68:aa', 'ib0' + ), + 32, + 'an eight-byte fabric address uses the InfiniBand hardware type', +); +is( + xCAT_plugin::dhcp::_hardware_type_for('b8:3f:d2:4a:68:aa', 'hf0'), + 37, + 'an HFI interface uses the HFI hardware type', +); + +is( + xCAT_plugin::dhcp::_node_host_statements('node01', ''), + 'ddns-hostname \"node01\"; send host-name \"node01\";', + 'the default host statements identify the node', +); +is( + xCAT_plugin::dhcp::_node_host_statements( + 'node01', 'filename = \"bootfile\";' + ), + 'ddns-hostname \"node01\"; send host-name \"node01\";filename = \"bootfile\";', + 'node identity is prepended to existing host statements', +); +is( + xCAT_plugin::dhcp::_noip_hostname('node01', 'B8:3F:D2:4A:68:AA'), + 'node01-noipB83FD24A68AA', + 'the denied-host name preserves the legacy MAC case', +); +is( + xCAT_plugin::dhcp::_noip_hostname('node01', 'B8-3F-D2-4A-68-AA'), + 'node01-noipB8-3F-D2-4A-68-AA', + 'the denied-host name preserves legacy dash separators', +); + +my $create_commands = <<'OMAPI'; +new host +set name = "node01-xcat-ib" +open +remove +close +new host +set name = "node01-xcat-ib" +set hardware-address = b8:3f:d2:03:00:4a:68:aa +set dhcp-client-identifier = b8:3f:d2:03:00:4a:68:aa +set hardware-type = 32 +set ip-address = 192.0.2.10 +set statements = "ddns-hostname \"node01\"; send host-name \"node01\";" +create +close +OMAPI + +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', '192.0.2.10', + 'ddns-hostname \"node01\"; send host-name \"node01\";', 1, 0 + ), + $create_commands, + 'an Ethernet identity on an IPoIB network creates the InfiniBand twin', +); + +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, '!service!ib0', '192.0.2.10', + 'ddns-hostname \"node01\"; send host-name \"node01\";', 1, 0 + ), + $create_commands, + 'a relayed IPoIB interface creates the InfiniBand twin', +); + +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', 'DENIED', '', 1, 0 + ), + join( '', ( split( /^/m, $create_commands ) )[ 0 .. 9 ] ) + . "set statements = \"deny booting;\"\ncreate\nclose\n", + 'a denied Ethernet identity creates a denied InfiniBand twin', +); + +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 32, 'ib0', '192.0.2.10', '', 1, 1 + ), + undef, + 'an existing InfiniBand identity does not create another twin', +); +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'eth0', '192.0.2.10', '', 1, 0 + ), + undef, + 'an Ethernet network does not create an InfiniBand twin', +); + +my $create_without_cleanup = <<'OMAPI'; +new host +set name = "node01-xcat-ib" +set hardware-address = b8:3f:d2:03:00:4a:68:aa +set dhcp-client-identifier = b8:3f:d2:03:00:4a:68:aa +set hardware-type = 32 +set ip-address = 192.0.2.10 +set statements = "ddns-hostname \"node01\"; send host-name \"node01\";" +create +close +OMAPI +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', '192.0.2.10', + 'ddns-hostname \"node01\"; send host-name \"node01\";', 0, 0 + ), + $create_without_cleanup, + 'the Ubuntu-limited OMAPI path creates the twin without a failed-open cleanup', +); +my $create_without_address = $create_without_cleanup; +$create_without_address =~ s/^set ip-address = .*\n//m; +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', undef, + 'ddns-hostname \"node01\"; send host-name \"node01\";', 0, 0 + ), + $create_without_address, + 'an unresolved IP omits only the twin address', +); +is( + xCAT_plugin::dhcp::_infiniband_twin_create_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', '192.0.2.10', '', 1, 1 + ), + undef, + 'an explicit InfiniBand identity suppresses the derived twin', +); + +is_deeply( + [ xCAT_plugin::dhcp::_infiniband_twin_static_lines( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', '192.0.2.10', + 'option host-name "node01";', 0 + ) ], + [ + "host node01-xcat-ib {\n", + " hardware infiniband b8:3f:d2:03:00:4a:68:aa;\n", + " fixed-address 192.0.2.10;\n", + " option host-name \"node01\";\n", + "}\n", + ], + 'the static-host fallback declares the derived InfiniBand identity', +); + +my $delete_name_commands = <<'OMAPI'; +new host +set name = "node01-xcat-ib" +open +remove +close +OMAPI +my $delete_address_commands = <<'OMAPI'; +new host +set hardware-address = b8:3f:d2:03:00:4a:68:aa +set hardware-type = 32 +open +remove +close +OMAPI + +is( + xCAT_plugin::dhcp::_hardware_address_delete_commands( + 'b8:3f:d2:4a:68:aa', 1 + ), + "new host\nset hardware-address = b8:3f:d2:4a:68:aa\nopen\nremove\nclose\n", + 'Ethernet cleanup keeps the legacy hardware-address lookup', +); +is( + xCAT_plugin::dhcp::_hardware_address_delete_commands( + 'b8:3f:d2:03:00:4a:68:aa', 32 + ), + $delete_address_commands, + 'InfiniBand cleanup includes its OMAPI hardware type', +); + +my @delete_commands = xCAT_plugin::dhcp::_infiniband_twin_delete_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', 1, 0 +); +is_deeply( + \@delete_commands, + [ $delete_name_commands, $delete_address_commands ], + 'removing a node produces separate name and address cleanup commands', +); +my @moved_network_delete_commands = + xCAT_plugin::dhcp::_infiniband_twin_delete_commands( + 'node01', 'b8-3f-d2-4a-68-aa', 1, 'eth0', 1, 1 + ); +is_deeply( + \@moved_network_delete_commands, + [ $delete_name_commands, undef ], + 'removing a moved node cleans up its old twin by name only', +); +my @limited_delete_commands = + xCAT_plugin::dhcp::_infiniband_twin_delete_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', 0, 0 + ); +is_deeply( + \@limited_delete_commands, + [ $delete_name_commands, undef ], + 'the Ubuntu-limited OMAPI path still removes the twin by name', +); +my @limited_ethernet_delete_commands = + xCAT_plugin::dhcp::_infiniband_twin_delete_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'eth0', 0, 0 + ); +is_deeply( + \@limited_ethernet_delete_commands, + [], + 'the Ubuntu-limited OMAPI path avoids a failed-open cleanup on Ethernet', +); + +my @moved_network_update_commands = + xCAT_plugin::dhcp::_infiniband_twin_update_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'eth0', '192.0.2.10', + 'ddns-hostname \"node01\"; send host-name \"node01\";', 1, 0 + ); +is_deeply( + \@moved_network_update_commands, + [ $delete_name_commands, undef, undef ], + 're-registering a node on Ethernet removes its former twin by name only', +); + +my @infiniband_update_commands = + xCAT_plugin::dhcp::_infiniband_twin_update_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', '192.0.2.10', + 'ddns-hostname \"node01\"; send host-name \"node01\";', 1, 0 + ); +is_deeply( + \@infiniband_update_commands, + [ $delete_name_commands, $delete_address_commands, $create_without_cleanup ], + 're-registering an IPoIB node replaces its generated twin', +); + +my @limited_update_commands = + xCAT_plugin::dhcp::_infiniband_twin_update_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', '192.0.2.10', + 'ddns-hostname \"node01\"; send host-name \"node01\";', 0, 0 + ); +is_deeply( + \@limited_update_commands, + [ undef, undef, $create_without_cleanup ], + 'the limited OMAPI path does not attempt a failed-open cleanup', +); + +my @explicit_identity_update_commands = + xCAT_plugin::dhcp::_infiniband_twin_update_commands( + 'node01', 'b8:3f:d2:4a:68:aa', 1, 'ib0', '192.0.2.10', + 'ddns-hostname \"node01\"; send host-name \"node01\";', 1, 1 + ); +is_deeply( + \@explicit_identity_update_commands, + [ $delete_name_commands, undef, undef ], + 'updating an explicit InfiniBand identity does not remove it by address', +); + +# The mgtifname of a network can name more than one interface, separated by !. +# The InfiniBand interface is not always the last one, so a test that only +# matches the last name leaves a node on ib0!eth0 without its second entry. +foreach my $ifname (qw(ib0 ib0.8001 eth0!ib0 ib0!eth0 ib0!ib1 bond0!ib2)) { + ok( xCAT_plugin::dhcp::_is_infiniband_interface($ifname), + "'$ifname' is served by an InfiniBand interface" ); +} +foreach my $ifname (qw(eth0 eth0!eth1 bond0 hf0)) { + ok( !xCAT_plugin::dhcp::_is_infiniband_interface($ifname), + "'$ifname' is not served by an InfiniBand interface" ); +} +ok( !xCAT_plugin::dhcp::_is_infiniband_interface(undef), + 'a network with no interface name is not served by InfiniBand' ); + +done_testing(); diff --git a/xCAT-test/unit/dhcp_static_host_markers.t b/xCAT-test/unit/dhcp_static_host_markers.t new file mode 100644 index 000000000..8409a1965 --- /dev/null +++ b/xCAT-test/unit/dhcp_static_host_markers.t @@ -0,0 +1,184 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/../../xCAT-server/lib"; +use lib "$FindBin::Bin/../../xCAT-server/lib/perl"; +use lib "$FindBin::Bin/../../perl-xCAT"; + +use Test::More; + +$ENV{XCATCFG} ||= 'SQLite:/tmp'; + +my $source_dhcp_plugin = "$FindBin::Bin/../../xCAT-server/lib/xcat/plugins/dhcp.pm"; +if ( -f $source_dhcp_plugin ) { + require $source_dhcp_plugin; +} else { + require xCAT_plugin::dhcp; +} + +my @config; + +my @infiniband_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node00', 'node00', 'b8:3f:d2:03:00:4a:68:aa', 32, + 'ib0', '192.0.2.10', '', 1, \@infiniband_config, +); +like( + join( '', @infiniband_config ), + qr/^\s*hardware infiniband b8:3f:d2:03:00:4a:68:aa;$/m, + 'the static-host fallback keeps an explicit InfiniBand hardware type', +); +unlike( + join( '', @infiniband_config ), + qr/^\s*hardware ethernet b8:3f:d2:03:00:4a:68:aa;$/m, + 'the static-host fallback does not label an InfiniBand identity as Ethernet', +); + +my @dashed_mac_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node00', 'node00', 'B8-3F-D2-4A-68-AA', 1, + 'eth0', '192.0.2.11', '', 0, \@dashed_mac_config, +); +like( + join( '', @dashed_mac_config ), + qr/^\s*hardware ethernet b8:3f:d2:4a:68:aa;$/m, + 'the static-host fallback emits a valid canonical dashed MAC', +); + +xCAT_plugin::dhcp::_add_isc_static_host( + 'node01', 'node01', '00:11:22:33:44:55', 1, + 'eth0', '192.0.2.1', '', 0, \@config, +); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node02', 'node02', '00:11:22:33:44:66', 1, + 'eth0', '192.0.2.2', '', 0, \@config, +); + +like( + join( '', @config ), + qr/^#xCAT host declaration for node01 aka host node01 end$/m, + 'the static host end marker occupies its own line', +); + +xCAT_plugin::dhcp::_delete_isc_static_host('node01', \@config); +my $remaining = join( '', @config ); +unlike($remaining, qr/\bnode01\b/, 'the selected static host is removed'); +like($remaining, qr/\bnode02\b/, 'the following static host is preserved'); + +my @legacy_config = ( + "#xCAT host declaration for node01 aka host node01 start\n", + "host node01 {\n", + " hardware ethernet 00:11:22:33:44:55;\n", + " fixed-address 192.0.2.1;\n", + "} #xCAT host declaration for node01 aka host node01 end\n", + "#xCAT host declaration for node02 aka host node02 start\n", + "host node02 {\n", + " hardware ethernet 00:11:22:33:44:66;\n", + " fixed-address 192.0.2.2;\n", + "} #xCAT host declaration for node02 aka host node02 end\n", +); +xCAT_plugin::dhcp::_delete_isc_static_host('node01', \@legacy_config); +my $legacy_remaining = join( '', @legacy_config ); +unlike($legacy_remaining, qr/\bnode01\b/, + 'a static host written by an older xCAT release is removed'); +like($legacy_remaining, qr/\bnode02\b/, + 'deleting an old-format host preserves the following host'); + +my @multi_host_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node03', 'node03', '00:11:22:33:44:77', 1, + 'eth0', '192.0.2.3', '', 0, \@multi_host_config, +); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node03', 'node03-ib', '00:11:22:33:44:88', 32, + 'ib0', '192.0.2.3', '', 1, \@multi_host_config, +); +my $multi_host = join( '', @multi_host_config ); +like($multi_host, qr/^host node03 \{$/m, + 'adding a second identity preserves the first host for a node'); +like($multi_host, qr/^host node03-ib \{$/m, + 'adding a second identity records its own host for the node'); + +my @prefix_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node01', 'node01', '00:11:22:33:44:99', 1, + 'eth0', '192.0.2.1', '', 0, \@prefix_config, +); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node01-ib', 'node01-ib', '00:11:22:33:44:aa', 1, + 'eth0', '192.0.2.4', '', 0, \@prefix_config, +); +xCAT_plugin::dhcp::_delete_isc_static_host('node01', \@prefix_config); +my $prefix_remaining = join( '', @prefix_config ); +unlike($prefix_remaining, qr/^host node01 \{$/m, + 'node-wide deletion removes the exact node'); +like($prefix_remaining, qr/^host node01-ib \{$/m, + 'node-wide deletion preserves a node with the same prefix'); + +my @replacement_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node04', 'node04-old', '00:11:22:33:44:bb', 1, + 'eth0', '192.0.2.5', '', 0, \@replacement_config, +); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node04', 'node04-old-ib', '00:11:22:33:44:cc', 32, + 'ib0', '192.0.2.5', '', 1, \@replacement_config, +); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node05', 'node05', '00:11:22:33:44:dd', 1, + 'eth0', '192.0.2.6', '', 0, \@replacement_config, +); +ok( + xCAT_plugin::dhcp::_begin_isc_static_host_update( + 'node04', 1, \@replacement_config, + ), + 'removing stale identities marks the static configuration as changed', +); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node04', 'node04-current', '00:11:22:33:44:ee', 1, + 'eth0', '192.0.2.5', '', 0, \@replacement_config, +); +my $replacement = join( '', @replacement_config ); +unlike($replacement, qr/^host node04-old(?:-ib)? \{$/m, + 're-registering a node removes identities that are no longer present'); +like($replacement, qr/^host node04-current \{$/m, + 're-registering a node writes its current identity'); +like($replacement, qr/^host node05 \{$/m, + 're-registering a node preserves other nodes'); +ok( + !xCAT_plugin::dhcp::_begin_isc_static_host_update( + 'node04', 0, \@replacement_config, + ), + 'a disabled static update leaves the configuration unchanged', +); + +my @lazy_config; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node06', 'node06-old', '00:11:22:33:44:11', 1, + 'eth0', '192.0.2.7', '', 0, \@lazy_config, +); +my $before_failed_update = join( '', @lazy_config ); +my $update_started = 0; +xCAT_plugin::dhcp::_add_isc_static_host( + 'node06', 'node06-current', '00:11:22:33:44:22', 1, + 'eth0', undef, '', 0, \@lazy_config, \$update_started, +); +is(join( '', @lazy_config ), $before_failed_update, + 'an unresolved replacement preserves the existing static host'); +is($update_started, 0, + 'an unresolved replacement does not begin the static update'); +xCAT_plugin::dhcp::_add_isc_static_host( + 'node06', 'node06-current', '00:11:22:33:44:22', 1, + 'eth0', '192.0.2.8', '', 0, \@lazy_config, \$update_started, +); +my $lazy_replacement = join( '', @lazy_config ); +unlike($lazy_replacement, qr/^host node06-old \{$/m, + 'the first valid replacement removes stale node identities'); +like($lazy_replacement, qr/^host node06-current \{$/m, + 'the first valid replacement writes the current identity'); +is($update_started, 1, + 'the successful replacement records that cleanup has started'); + +done_testing();