diff --git a/xCAT-server/lib/perl/xCAT/Template.pm b/xCAT-server/lib/perl/xCAT/Template.pm index d02048c88..8eba50a8f 100644 --- a/xCAT-server/lib/perl/xCAT/Template.pm +++ b/xCAT-server/lib/perl/xCAT/Template.pm @@ -1052,13 +1052,23 @@ sub kickstartnetwork { unless ($mactab) { $tmplerr = "mac table should always exist prior to template processing when doing autoula"; return; } my $ent = $mactab->getNodeAttribs($node, ['mac'], prefetchcache => 1); unless ($ent and $ent->{mac}) { $tmplerr = "missing mac data for $node"; return; } - my $suffix = xCAT::Utils->parseMacTabEntry($ent->{mac}, $node); - $suffix = lc($suffix); + my ($installnic, $primarynic); + my $nrtab = xCAT::Table->new('noderes', -create => 0); + if ($nrtab) { + my $nrent = $nrtab->getNodeAttribs($node, [ 'installnic', 'primarynic' ], prefetchcache => 1); + if ($nrent) { + $installnic = $nrent->{installnic}; + $primarynic = $nrent->{primarynic}; + } + } + my $params = install_device_params($installnic, $primarynic, $ent->{mac}, $node); + my $macaddr = defined($params->{mac}) ? lc($params->{mac}) : ''; + my $suffix = defined($params->{nicname}) ? $params->{nicname} : $macaddr; if ($::XCATSITEVALS{managedaddressmode} eq "autoula") { unless ($hoststab) { $hoststab = xCAT::Table->new('hosts', -create => 1); } $line .= "static --device=$suffix --noipv4 --ipv6="; - my $ulaaddr = autoulaaddress($suffix); + my $ulaaddr = autoulaaddress($macaddr); $hoststab->setNodeAttribs($node, { ip => $ulaaddr }); $line .= $ulaaddr; } elsif ($::XCATSITEVALS{managedaddressmode} =~ /static/) { @@ -1664,19 +1674,47 @@ sub crydb_or_locked #-------------------------------------------------------------------------------- -=head3 subiquity_install_netcfg +=head3 install_device_params Resolve the interface the INSTALLED system must bring up, in xCAT's own order: noderes.installnic, else noderes.primarynic, else match on mac.mac. Either attribute may name an interface OR carry a MAC address. xCAT::NetworkUtils::gen_net_boot_params already owns that order for the netboot kernel parameters, so it is reused here rather than re-derived -- and in - particular the install template never re-derives any part of it in shell. + particular an install template never re-derives any part of it in shell. + + Every install template that has to name the install device shares this resolution, so the + device an installer configures cannot disagree with the device the netboot kernel parameters + name. The resolution carries no boot loader dependency of any kind. Arguments: $installnic - noderes.installnic (may be undef or empty) $primarynic - noderes.primarynic (may be undef or empty) $macentry - the raw mac.mac entry (may hold |-separated, !hostname-suffixed entries) $nodename - the node the entry is resolved for + Returns: + the hash reference from gen_net_boot_params. Of interest to install templates: + nicname - the interface name, set only when installnic/primarynic names an interface + mac - the address, set whenever one is known + +=cut + +#-------------------------------------------------------------------------------- +sub install_device_params { + my ($installnic, $primarynic, $macentry, $nodename) = @_; + + my $macmac = xCAT::Utils->parseMacTabEntry(defined($macentry) ? $macentry : '', $nodename); + return xCAT::NetworkUtils->gen_net_boot_params($installnic, $primarynic, $macmac); +} + +#-------------------------------------------------------------------------------- + +=head3 subiquity_install_netcfg + + Shape install_device_params for netplan, which matches a device by MAC and optionally renames + it. + + Arguments: + as install_device_params Returns: ($setname, $macaddress) $setname - the name netplan must rename the matched device to, empty when the device is @@ -1689,8 +1727,7 @@ sub crydb_or_locked sub subiquity_install_netcfg { my ($installnic, $primarynic, $macentry, $nodename) = @_; - my $macmac = xCAT::Utils->parseMacTabEntry(defined($macentry) ? $macentry : '', $nodename); - my $params = xCAT::NetworkUtils->gen_net_boot_params($installnic, $primarynic, $macmac); + my $params = install_device_params($installnic, $primarynic, $macentry, $nodename); my $setname = defined($params->{nicname}) ? $params->{nicname} : ''; my $macaddress = defined($params->{mac}) ? lc($params->{mac}) : ''; diff --git a/xCAT-test/unit/template_install_device.t b/xCAT-test/unit/template_install_device.t new file mode 100644 index 000000000..4a5f2e518 --- /dev/null +++ b/xCAT-test/unit/template_install_device.t @@ -0,0 +1,156 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Spec; +use FindBin; +use Test::More; + +BEGIN { + push @INC, + File::Spec->catdir( $FindBin::Bin, '..', '..', 'perl-xCAT' ), + File::Spec->catdir( $FindBin::Bin, '..', '..', 'xCAT-server', 'lib', 'perl' ); +} + +eval { require xCAT::Template; 1 } + or plan skip_all => "xCAT::Template not loadable: $@"; + +my $NODE = 'n1'; +my $MAC1 = 'AA:BB:CC:DD:EE:01'; +my $MAC2 = 'AA:BB:CC:DD:EE:02'; + +# The order is installnic, then primarynic, then mac.mac. Either attribute may +# name an interface or carry an address. +my $p = xCAT::Template::install_device_params( '', '', $MAC1, $NODE ); +is( $p->{mac}, $MAC1, 'with neither attribute set the address comes from mac.mac' ); +is( $p->{nicname}, undef, 'with neither attribute set no interface is named' ); + +$p = xCAT::Template::install_device_params( 'eth1', '', $MAC1, $NODE ); +is( $p->{nicname}, 'eth1', 'installnic naming an interface wins' ); +is( $p->{mac}, $MAC1, 'the address is still resolved alongside the interface' ); + +$p = xCAT::Template::install_device_params( '', 'eth2', $MAC1, $NODE ); +is( $p->{nicname}, 'eth2', 'primarynic is used when installnic is not set' ); + +$p = xCAT::Template::install_device_params( 'eth1', 'eth2', $MAC1, $NODE ); +is( $p->{nicname}, 'eth1', 'installnic beats primarynic' ); + +$p = xCAT::Template::install_device_params( $MAC2, '', $MAC1, $NODE ); +is( $p->{mac}, $MAC2, 'installnic carrying an address names that address' ); +is( $p->{nicname}, undef, 'installnic carrying an address names no interface' ); + +$p = xCAT::Template::install_device_params( 'mac', '', $MAC1, $NODE ); +is( $p->{mac}, $MAC1, 'the keyword mac falls back to mac.mac' ); +is( $p->{nicname}, undef, 'the keyword mac names no interface' ); + +# A mac.mac entry may hold several addresses. The entry tagged with the node +# name is the one that belongs to the node. +$p = xCAT::Template::install_device_params( '', '', "$MAC1!$NODE|$MAC2!other", $NODE ); +is( $p->{mac}, $MAC1, 'the tagged entry for the node is selected' ); + +# Ubuntu keeps its own return shape over the same resolution. +my ( $setname, $macaddress ) = + xCAT::Template::subiquity_install_netcfg( 'eth1', '', $MAC1, $NODE ); +is( $setname, 'eth1', 'netplan is given the interface to rename to' ); +is( $macaddress, lc($MAC1), 'netplan is given the address, lower cased' ); + +( $setname, $macaddress ) = + xCAT::Template::subiquity_install_netcfg( '', '', $MAC1, $NODE ); +is( $setname, '', 'netplan renames nothing when no interface is named' ); +is( $macaddress, lc($MAC1), 'netplan still matches on the address' ); + +{ + package Local::TemplateInstallTable; + + sub getNodeAttribs { + my ($self) = @_; + return $self->{row}; + } + + sub setNodeAttribs { + my ( $self, undef, $attrs ) = @_; + $self->{written} = { %{$attrs} }; + return 1; + } +} + +# Exercise kickstartnetwork itself with in-memory table objects. This verifies +# the generated kickstart line rather than the text of Template.pm. +sub kickstart_network { + my ( $installnic, $primarynic, $macentry, $mode ) = @_; + + my $mactab = bless { row => { mac => $macentry } }, 'Local::TemplateInstallTable'; + my $nrtab = bless { + row => { + installnic => $installnic, + primarynic => $primarynic, + } + }, + 'Local::TemplateInstallTable'; + my $hoststab = bless {}, 'Local::TemplateInstallTable'; + my $autoula_mac; + + no warnings qw(redefine once); + local *xCAT::Table::new = sub { + my ( undef, $table ) = @_; + return $mactab if $table eq 'mac'; + return $nrtab if $table eq 'noderes'; + return $hoststab if $table eq 'hosts'; + die "Unexpected table $table"; + }; + local *xCAT::Template::autoulaaddress = sub { + ($autoula_mac) = @_; + return 'fd00::1'; + }; + local $::XCATSITEVALS{managedaddressmode} = $mode || 'dhcp'; + + my $line = xCAT::Template::kickstartnetwork(); + return ( $line, $autoula_mac, $hoststab->{written} ); +} + +sub ks_device { + my ($line) = kickstart_network(@_); + return $line =~ /--device=(\S+)/ ? $1 : undef; +} + +is( ks_device( '', '', $MAC1 ), lc($MAC1), + 'a node setting neither attribute keeps the address it has today' ); +is( ks_device( 'eth1', '', $MAC1 ), 'eth1', + 'a node setting installnic names that interface' ); +is( ks_device( $MAC2, '', $MAC1 ), lc($MAC2), + 'a node whose installnic carries an address names that address' ); + +# The defect this closes: a bare multi address entry resolves to the LAST +# address, which need not be the adapter that deploys the node. Setting +# installnic must override that. +my $BARE = "$MAC1|$MAC2"; +is( ks_device( '', '', $BARE ), lc($MAC2), + 'a bare multi address entry alone still resolves to the last address' ); +is( ks_device( 'eth0', '', $BARE ), 'eth0', + 'installnic overrides a bare multi address entry' ); + +# An interface name is case sensitive. A node on POWER carries names such as +# enP1p12s0f0, which no longer name a device once they are lower cased. Only +# an address may be lower cased. +my $MIXED = 'enP1p12s0f0'; +is( ks_device( $MIXED, '', $MAC1 ), $MIXED, + 'the kickstart keeps the case of the interface name' ); +is( ks_device( '', $MIXED, $MAC1 ), $MIXED, + 'the kickstart keeps the case of a primarynic interface name' ); +my ($mixedset) = xCAT::Template::subiquity_install_netcfg( $MIXED, '', $MAC1, $NODE ); +is( $mixedset, $MIXED, 'Ubuntu keeps the case of the interface name too' ); +is( ks_device( '', '', uc($MAC1) ), lc($MAC1), + 'an address is still lower cased' ); + +# autoula must derive the address from the hardware address even when the +# kickstart selects an interface by name. +my ( $autoula_line, $autoula_mac, $hostattrs ) = + kickstart_network( $MIXED, '', $MAC1, 'autoula' ); +is( $autoula_line, + "network --onboot=yes --bootproto=static --device=$MIXED --noipv4 --ipv6=fd00::1", + 'autoula keeps the selected interface in the kickstart line' ); +is( $autoula_mac, lc($MAC1), + 'autoula derives the address from the MAC, not the interface name' ); +is( $hostattrs->{ip}, 'fd00::1', 'the generated ULA is saved in the hosts table' ); + +done_testing();