2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-05 12:37:54 +00:00
Files
xcat-core/xCAT-test/unit/confluent_switch_retract.t
T
Vinícius Ferrão 10eec6c2b0 test(confluent): pin the retraction of the switch topology
Cover the payload that the command sends for a node that confluent already
holds. A node that xCAT holds no topology for names every topology attribute
with no value. A value that the switch table holds is never replaced by a
clear. A renamed interface leaves only the new name with a value, and the
wildcard removes the name that is gone.

Cover that the wildcard cannot match the attributes that carry no interface,
which is why those are named on their own, and that only the request that
updates a node carries the clears.

Cover that the payload is encoded as JSON, because that is what turns an
attribute with no value into the null that confluent reads as a removal.

(cherry picked from commit 7ca32849e3fc8e04000c22490d598e17b56db962)
2026-08-27 17:57:56 -03:00

118 lines
5.5 KiB
Perl

#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use FindBin;
use Test::More;
my $plugin = File::Spec->catfile( $FindBin::Bin, '..', '..',
'xCAT-server', 'lib', 'xcat', 'plugins', 'confluent.pm' );
plan skip_all => 'confluent.pm not found' unless -r $plugin;
open( my $fh, '<', $plugin ) or die "Unable to read $plugin: $!";
my $source = do { local $/; <$fh> };
close($fh);
# The payload the command sends for a node confluent already holds. This
# mirrors the order the command builds it in: the values first, then the names
# that carry no value.
sub update_payload {
my ( $flat, $pernic ) = @_;
my %parameters;
if ( defined $flat->{switch} ) { $parameters{'net.switch'} = $flat->{switch} }
if ( defined $flat->{port} ) { $parameters{'net.switchport'} = $flat->{port} }
foreach my $nic ( keys %$pernic ) {
my $ent = $pernic->{$nic};
if ( defined $ent->{switch} ) { $parameters{"net.$nic.switch"} = $ent->{switch} }
if ( defined $ent->{port} ) { $parameters{"net.$nic.switchport"} = $ent->{port} }
}
foreach my $topo ( 'net.switch', 'net.switchport' ) {
$parameters{$topo} = undef unless ( exists $parameters{$topo} );
}
$parameters{'net.*.switch'} = undef;
$parameters{'net.*.switchport'} = undef;
return \%parameters;
}
# A node xCAT holds no topology for must name every topology attribute with no
# value, so confluent removes what it still holds.
my $p = update_payload( {}, {} );
ok( exists $p->{'net.switch'}, 'a node with no topology names the plain switch' );
is( $p->{'net.switch'}, undef, 'the plain switch carries no value' );
ok( exists $p->{'net.switchport'}, 'a node with no topology names the plain port' );
is( $p->{'net.switchport'}, undef, 'the plain port carries no value' );
is( $p->{'net.*.switch'}, undef, 'every interface switch is named with no value' );
is( $p->{'net.*.switchport'}, undef, 'every interface port is named with no value' );
# A value the switch table holds must survive beside the names that clear.
$p = update_payload( { switch => 'sw1', port => '1' }, {} );
is( $p->{'net.switch'}, 'sw1', 'a held plain switch keeps its value' );
is( $p->{'net.switchport'}, '1', 'a held plain port keeps its value' );
is( $p->{'net.*.switch'}, undef, 'the interface wildcard still clears' );
# The interface case. Confluent removes what the wildcard matches before it
# sets the rest of the request, so the current interface survives.
$p = update_payload( {}, { ib0 => { switch => 'sw2', port => '9' } } );
is( $p->{'net.ib0.switch'}, 'sw2', 'the current interface keeps its switch' );
is( $p->{'net.ib0.switchport'}, '9', 'the current interface keeps its port' );
is( $p->{'net.*.switch'}, undef, 'the interface wildcard clears the rest' );
is( $p->{'net.switch'}, undef, 'a node with only an interface clears the plain switch' );
# A renamed interface: only the new name carries a value, and the wildcard
# removes the old one.
$p = update_payload( {}, { ens1f0 => { switch => 'sw3', port => '4' } } );
is( $p->{'net.ens1f0.switch'}, 'sw3', 'the new interface name carries the switch' );
ok( !exists $p->{'net.eth0.switch'}, 'the old interface name is not named on its own' );
is( $p->{'net.*.switch'}, undef, 'the old interface name is removed by the wildcard' );
# The wildcard cannot stand in for the names that carry no interface, so those
# have to be named separately. This is why both forms are in the payload.
SKIP: {
eval { require File::FnMatch; 1 }
or skip 'File::FnMatch not available', 1;
ok( !File::FnMatch::fnmatch( 'net.*.switch', 'net.switch' ),
'the interface wildcard does not match the plain name' );
}
# Same check without the optional module, using the rule the wildcard follows.
ok( 'net.switch' !~ /^net\..+\.switch$/,
'the plain name needs its own clear because the wildcard cannot match it' );
ok( 'net.ib0.switch' =~ /^net\..+\.switch$/,
'an interface name is what the wildcard matches' );
# The command has to clear only where there is something to clear.
my ($body) = $source =~ /\nsub donodeent \{(.*?)\n\}\n/s;
ok( defined($body), 'the donodeent body was located' );
like( $body, qr/\$parameters\{'net\.\*\.switch'\}\s*= undef/,
'the command names every interface switch with no value' );
like( $body, qr/\$parameters\{'net\.\*\.switchport'\}\s*= undef/,
'the command names every interface port with no value' );
like( $body, qr/\$parameters\{\$topo\} = undef unless \(exists \$parameters\{\$topo\}\)/,
'a held value is never replaced by a clear' );
my ($updatebranch) =
$body =~ /if \(exists \$currnodes\{\$node\}\) \{(.*?)\n \} else \{/s;
ok( defined($updatebranch), 'the update branch was located' );
like( $updatebranch, qr/net\.\*\.switch/,
'the clears are on the branch that updates a node confluent holds' );
my ($createbranch) = $body =~ /\n \} else \{(.*)$/s;
ok( defined($createbranch), 'the create branch was located' );
unlike( $createbranch, qr/net\.\*\.switch/,
'the branch that creates a node sends no clear' );
# The transport has to turn no value into a JSON null, which is what confluent
# reads as a request to remove an attribute.
my $tlv = File::Spec->catfile( $FindBin::Bin, '..', '..',
'xCAT-server', 'lib', 'xcat', 'Confluent', 'TLV.pm' );
SKIP: {
skip 'TLV.pm not found', 1 unless -r $tlv;
open( my $tf, '<', $tlv ) or die $!;
my $tsrc = do { local $/; <$tf> };
close($tf);
like( $tsrc, qr/\$self->\{json\}->utf8->encode\(\$data\)/,
'the payload is encoded as JSON, which carries no value as null' );
}
done_testing();