mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-08-26 08:36:42 +00:00
bcf6f9059a
fix(dbobjutils): match exact only-if values
345 lines
12 KiB
Perl
345 lines
12 KiB
Perl
#!/usr/bin/env perl
|
|
use strict;
|
|
use warnings;
|
|
|
|
BEGIN {
|
|
$ENV{XCATCFG} ||= 'SQLite:/tmp';
|
|
}
|
|
|
|
use FindBin;
|
|
use lib "$FindBin::Bin/../../perl-xCAT";
|
|
|
|
use Scalar::Util qw(refaddr);
|
|
use Test::More;
|
|
|
|
use xCAT::DBobjUtils;
|
|
|
|
my %missing_mgt = (
|
|
objtype => 'node',
|
|
groups => 'test',
|
|
bmc => '10.0.0.1',
|
|
);
|
|
my @failures = xCAT::DBobjUtils->validate_only_if_attrs('node01', 'node', \%missing_mgt, {});
|
|
is(scalar @failures, 1, 'bmc without mgt fails only_if validation');
|
|
like($failures[0]->{message}, qr/mgt value is .*openbmc/, 'failure explains accepted mgt values');
|
|
|
|
my %substring_openbmc = (
|
|
objtype => 'node',
|
|
groups => 'test',
|
|
mgt => 'openbmc-redfish',
|
|
bmc => '10.0.0.1',
|
|
);
|
|
@failures = xCAT::DBobjUtils->validate_only_if_attrs('node01', 'node', \%substring_openbmc, {});
|
|
is(scalar @failures, 1, 'substring-only mgt value fails bmc only_if validation');
|
|
like($failures[0]->{message}, qr/mgt value is .*openbmc/, 'substring rejection explains accepted mgt values');
|
|
|
|
my %explicit_openbmc = (
|
|
objtype => 'node',
|
|
groups => 'test',
|
|
mgt => 'openbmc',
|
|
bmc => '10.0.0.1',
|
|
);
|
|
@failures = xCAT::DBobjUtils->validate_only_if_attrs('node01', 'node', \%explicit_openbmc, {});
|
|
is(scalar @failures, 0, 'explicit mgt=openbmc satisfies bmc only_if validation');
|
|
|
|
my %existing_openbmc = (
|
|
objtype => 'node',
|
|
groups => 'test',
|
|
bmc => '10.0.0.1',
|
|
);
|
|
my %dbattrs = (mgt => 'openbmc');
|
|
@failures = xCAT::DBobjUtils->validate_only_if_attrs('node01', 'node', \%existing_openbmc, \%dbattrs);
|
|
is(scalar @failures, 0, 'existing mgt=openbmc satisfies bmc only_if validation');
|
|
|
|
my %group_openbmc = (
|
|
objtype => 'node',
|
|
groups => 'openbmcgrp',
|
|
bmc => '10.0.0.1',
|
|
);
|
|
my %groupattrs = (openbmcgrp => { mgt => 'openbmc' });
|
|
@failures = xCAT::DBobjUtils->validate_only_if_attrs('node01', 'node', \%group_openbmc, {}, \%groupattrs);
|
|
is(scalar @failures, 0, 'group mgt=openbmc satisfies bmc only_if validation');
|
|
|
|
sub check_exact_only_if_value {
|
|
my ($source, $actual, $required, $matches, $description) = @_;
|
|
my (%attrs, %dbattrs, %groupattrs);
|
|
|
|
$attrs{selector} = $actual if $source eq 'explicit';
|
|
$dbattrs{selector} = $actual if $source eq 'database';
|
|
$groupattrs{exactgroup}{selector} = $actual if $source eq 'group';
|
|
|
|
is(
|
|
xCAT::DBobjUtils::_only_if_value_matches(
|
|
\%attrs,
|
|
\%dbattrs,
|
|
\%groupattrs,
|
|
'selector',
|
|
$required,
|
|
),
|
|
$matches,
|
|
"$source source $description",
|
|
);
|
|
}
|
|
|
|
my @exact_match_cases = (
|
|
['pdu', 'pdu', 1, 'matches a single value'],
|
|
['openbmc,pdu', 'pdu', 1, 'matches an exact list element'],
|
|
[' openbmc , pdu ', 'pdu', 1, 'matches a whitespace-padded list element'],
|
|
[0, '0', 1, 'matches a defined zero'],
|
|
['not-pdu', 'pdu', 0, 'rejects a hyphenated prefix'],
|
|
['pdu-extra', 'pdu', 0, 'rejects a hyphenated suffix'],
|
|
['pdu.extra', 'pdu', 0, 'rejects a dotted suffix'],
|
|
['.pdu', 'pdu', 0, 'rejects a punctuated prefix'],
|
|
['pdu.', 'pdu', 0, 'rejects a punctuated suffix'],
|
|
['.pdu', '.pdu', 1, 'matches a leading-punctuation value exactly'],
|
|
['pdu.', 'pdu.', 1, 'matches a trailing-punctuation value exactly'],
|
|
['', 'pdu', 0, 'rejects an empty value'],
|
|
[undef, 'pdu', 0, 'rejects an undefined value'],
|
|
);
|
|
|
|
foreach my $source (qw(explicit database group)) {
|
|
check_exact_only_if_value($source, @$_) for @exact_match_cases;
|
|
}
|
|
|
|
is(
|
|
xCAT::DBobjUtils::_only_if_value_matches(
|
|
{ selector => 'not-pdu' },
|
|
{ selector => 'pdu.extra' },
|
|
{ exactgroup => { selector => ' openbmc , pdu ' } },
|
|
'selector',
|
|
'pdu',
|
|
),
|
|
1,
|
|
'later exact source match preserves source OR semantics',
|
|
);
|
|
|
|
sub check_literal_only_if_read {
|
|
my ($selector, $expected_payload, $description) = @_;
|
|
|
|
local $xCAT::Schema::defspec{node} = {
|
|
objkey => 'name',
|
|
attrs => [
|
|
{
|
|
attr_name => 'selector',
|
|
tabentry => 'route_source.selector',
|
|
access_tabentry => 'route_source.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=axb',
|
|
tabentry => 'route_expected.payload',
|
|
access_tabentry => 'route_expected.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=a.b',
|
|
tabentry => 'route_wrong.payload',
|
|
access_tabentry => 'route_wrong.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=not-pdu',
|
|
tabentry => 'route_not_pdu.payload',
|
|
access_tabentry => 'route_not_pdu.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=pdu.extra',
|
|
tabentry => 'route_pdu_extra.payload',
|
|
access_tabentry => 'route_pdu_extra.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=pdu',
|
|
tabentry => 'route_pdu.payload',
|
|
access_tabentry => 'route_pdu.node=attr:name',
|
|
},
|
|
],
|
|
};
|
|
|
|
my @tables = qw(
|
|
route_source route_expected route_wrong route_not_pdu
|
|
route_pdu_extra route_pdu
|
|
);
|
|
local @xCAT::Schema::tabspec{@tables};
|
|
@xCAT::Schema::tabspec{@tables} = map { { nodecol => 'node' } } @tables;
|
|
|
|
no warnings 'redefine';
|
|
local *xCAT::DBobjUtils::getobjattrs = sub {
|
|
return (
|
|
route_source => { node01 => { selector => $selector } },
|
|
route_expected => { node01 => { payload => 'literal axb' } },
|
|
route_wrong => { node01 => { payload => 'literal a.b' } },
|
|
route_not_pdu => { node01 => { payload => 'literal not-pdu' } },
|
|
route_pdu_extra => { node01 => { payload => 'literal pdu.extra' } },
|
|
route_pdu => { node01 => { payload => 'literal pdu' } },
|
|
);
|
|
};
|
|
|
|
local $::ATTRLIST = '';
|
|
my %objects = (node01 => 'node');
|
|
my %defs = xCAT::DBobjUtils->getobjdefs(
|
|
\%objects,
|
|
0,
|
|
['selector', 'payload'],
|
|
);
|
|
|
|
is($defs{node01}{payload}, $expected_payload, $description);
|
|
}
|
|
|
|
check_literal_only_if_read('axb', 'literal axb', 'read routing treats regex punctuation literally');
|
|
check_literal_only_if_read('not-pdu', 'literal not-pdu', 'read routing rejects a hyphenated substring match');
|
|
check_literal_only_if_read('pdu.extra', 'literal pdu.extra', 'read routing rejects a dotted substring match');
|
|
check_literal_only_if_read('other,pdu', 'literal pdu', 'read routing accepts an exact comma-list element');
|
|
|
|
{
|
|
package DBobjUtilsOnlyIf::TableRecorder;
|
|
|
|
sub setAttribs {
|
|
my ($self, $keys, $updates) = @_;
|
|
push @{ $self->{writes} }, {
|
|
table => $self->{table},
|
|
keys => { %$keys },
|
|
updates => { %$updates },
|
|
};
|
|
return (1, undef);
|
|
}
|
|
|
|
sub commit { return 1; }
|
|
}
|
|
|
|
sub check_literal_only_if_routing {
|
|
my ($source, $selector, $expected_table, $description) = @_;
|
|
my @writes;
|
|
|
|
local $xCAT::Schema::defspec{routing_fixture} = {
|
|
objkey => 'name',
|
|
attrhash => {},
|
|
attrs => [
|
|
{
|
|
attr_name => 'groups',
|
|
tabentry => 'route_source.groups',
|
|
access_tabentry => 'route_source.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'selector',
|
|
tabentry => 'route_source.selector',
|
|
access_tabentry => 'route_source.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=a.b',
|
|
tabentry => 'route_wrong.payload',
|
|
access_tabentry => 'route_wrong.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=axb',
|
|
tabentry => 'route_expected.payload',
|
|
access_tabentry => 'route_expected.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=pdu',
|
|
tabentry => 'route_pdu.payload',
|
|
access_tabentry => 'route_pdu.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=not-pdu',
|
|
tabentry => 'route_not_pdu.payload',
|
|
access_tabentry => 'route_not_pdu.node=attr:name',
|
|
},
|
|
{
|
|
attr_name => 'payload',
|
|
only_if => 'selector=pdu.extra',
|
|
tabentry => 'route_pdu_extra.payload',
|
|
access_tabentry => 'route_pdu_extra.node=attr:name',
|
|
},
|
|
],
|
|
};
|
|
|
|
no warnings 'redefine';
|
|
local *xCAT::DBobjUtils::getobjdefs = sub {
|
|
my ($class, $objects) = @_;
|
|
|
|
return (node01 => { selector => $selector })
|
|
if $source eq 'database' && exists $objects->{node01};
|
|
return (literalgroup => { selector => $selector })
|
|
if $source eq 'group' && exists $objects->{literalgroup};
|
|
return ();
|
|
};
|
|
local *xCAT::Table::getTableSchema = sub {
|
|
return { keys => ['node'] };
|
|
};
|
|
local *xCAT::Table::new = sub {
|
|
my ($class, $table) = @_;
|
|
return bless {
|
|
table => $table,
|
|
writes => \@writes,
|
|
}, 'DBobjUtilsOnlyIf::TableRecorder';
|
|
};
|
|
|
|
my %attrs = (
|
|
objtype => 'routing_fixture',
|
|
payload => 'stored',
|
|
);
|
|
$attrs{selector} = $selector if $source eq 'explicit';
|
|
$attrs{groups} = 'literalgroup' if $source eq 'group';
|
|
|
|
my %objects = (node01 => \%attrs);
|
|
my $rc = xCAT::DBobjUtils->setobjdefs(\%objects);
|
|
|
|
is($rc, 0, "$source source $description passes only_if validation");
|
|
my @payload_tables = sort map { $_->{table} }
|
|
grep { exists $_->{updates}{payload} } @writes;
|
|
is_deeply(
|
|
\@payload_tables,
|
|
[$expected_table],
|
|
"$source source $description routes through only the exact only_if entry",
|
|
);
|
|
}
|
|
|
|
foreach my $source (qw(explicit database group)) {
|
|
check_literal_only_if_routing($source, 'axb', 'route_expected', 'literal value');
|
|
check_literal_only_if_routing($source, 'not-pdu', 'route_not_pdu', 'hyphenated value');
|
|
check_literal_only_if_routing($source, 'pdu.extra', 'route_pdu_extra', 'dotted value');
|
|
check_literal_only_if_routing($source, 'other,pdu', 'route_pdu', 'comma-list value');
|
|
}
|
|
|
|
my %external_entries;
|
|
foreach my $type (keys %xCAT::ExtTab::ext_defspec) {
|
|
foreach my $entry (@{ $xCAT::ExtTab::ext_defspec{$type}{attrs} || [] }) {
|
|
$external_entries{refaddr($entry)} = 1;
|
|
}
|
|
}
|
|
|
|
my @inconsistent_only_if;
|
|
my @falsey_only_if;
|
|
my $checked_only_if = 0;
|
|
foreach my $type (sort keys %xCAT::Schema::defspec) {
|
|
foreach my $entry (@{ $xCAT::Schema::defspec{$type}{attrs} || [] }) {
|
|
next if $external_entries{refaddr($entry)};
|
|
next unless exists($entry->{only_if});
|
|
my $condition = $entry->{only_if};
|
|
$checked_only_if++;
|
|
if ($condition !~ /^([^!=]+)=(.+)$/) {
|
|
push @inconsistent_only_if, "$type.$entry->{attr_name}:$condition";
|
|
next;
|
|
}
|
|
my ($validator_key, $validator_value) = ($1, $2);
|
|
my ($router_key, $router_value) = split(/=/, $condition);
|
|
if ($validator_key ne $router_key || $validator_value ne $router_value) {
|
|
push @inconsistent_only_if, "$type.$entry->{attr_name}:$condition";
|
|
next;
|
|
}
|
|
push @falsey_only_if, "$type.$entry->{attr_name}:$condition" unless $validator_value;
|
|
}
|
|
}
|
|
|
|
cmp_ok($checked_only_if, '>', 0, 'core only_if schema conditions are examined');
|
|
is_deeply(\@inconsistent_only_if, [], 'validator and router parse core only_if conditions consistently');
|
|
is_deeply(\@falsey_only_if, [], 'core only_if schema values are truthy for routing');
|
|
|
|
done_testing();
|