Skip to content

Commit 1a7df26

Browse files
fix: prevent mkdef from creating partial objects on validation error
Add preflight validation to defmk that runs before any destructive operations. Objects that fail only_if, groups-required, or group- specific checks are removed from FINALATTRS before the OBJ loop, preventing data loss from failed forced replacements and partial object creation. Semantics: - mkdef is per-object atomic. In a multi-object command, valid objects are created and invalid ones are rejected (command returns non-zero). - Command-level errors (e.g. from setFINALattrs) block ALL objects. Per-object preflight failures only block that specific object. - mkdef -f: old object deleted only after replacement passes preflight. - chdef: unchanged. Partial update semantics preserved. DBobjUtils.pm: - Add preflight_validate_mkdef() — checks only_if, groups requirement, dynamic group attrs/wherevals, static group members+-w conflict. Detects dynamic groups via both opt_d and grouptype=dynamic. - Fix setobjdefs only_if main loop to check group inheritance, not just the post-loop error handler. Ensures inherited attrs are written. - Remove dead if(0) block (disabled since 2009 refactor). DBobjectdefs.pm: - Separate command-level errors from per-object preflight failures. - Call preflight unconditionally. Failed objects removed per-object. - Command-level errors clear all FINALATTRS (whole-command atomic). - Empty-FINALATTRS early return for no-attributes case. Note: CLI attribute name validation is not in scope. Unknown attrs passed on the command line (not via -z) are silently ignored by setFINALattrs — this is pre-existing behavior. Fixes xcat2#3706 Fixes xcat2#2765
1 parent f006dc6 commit 1a7df26

3 files changed

Lines changed: 190 additions & 7 deletions

File tree

perl-xCAT/xCAT/DBobjUtils.pm

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,16 @@ sub setobjdefs
10321032
# as well as the attrs for this object that may be
10331033
# already set in DB
10341034

1035-
if (!($objhash{$objname}{$check_attr}) && !($DBattrvals{$objname}{$check_attr})) {
1035+
# Also check group inheritance for the condition
1036+
my $grp_has_check_attr = 0;
1037+
foreach my $tmpgrp (@tmplgrplist) {
1038+
if ($DBgroupsattr{$tmpgrp}{$check_attr}) {
1039+
$grp_has_check_attr = 1;
1040+
last;
1041+
}
1042+
}
1043+
1044+
if (!($objhash{$objname}{$check_attr}) && !($DBattrvals{$objname}{$check_attr}) && !$grp_has_check_attr) {
10361045

10371046
# if I didn't already check for this attr
10381047
my $rsp;
@@ -1063,7 +1072,17 @@ sub setobjdefs
10631072
next;
10641073
}
10651074

1066-
if (!($objhash{$objname}{$check_attr} =~ /\b$check_value\b/) && !($DBattrvals{$objname}{$check_attr} =~ /\b$check_value\b/)) {
1075+
my $grp_matches_check_value = 0;
1076+
foreach my $tmpgrp (@tmplgrplist) {
1077+
if ($DBgroupsattr{$tmpgrp}{$check_attr} && $DBgroupsattr{$tmpgrp}{$check_attr} =~ /\b$check_value\b/) {
1078+
$grp_matches_check_value = 1;
1079+
last;
1080+
}
1081+
}
1082+
1083+
my $obj_val = $objhash{$objname}{$check_attr} || '';
1084+
my $db_val = $DBattrvals{$objname}{$check_attr} || '';
1085+
if (!($obj_val =~ /\b$check_value\b/) && !($db_val =~ /\b$check_value\b/) && !$grp_matches_check_value) {
10671086
if ($invalidattr->{$attr_name}->{valid} != 1) {
10681087
$invalidattr->{$attr_name}->{valid} = 0;
10691088
$invalidattr->{$attr_name}->{condition}=$check_attr;
@@ -2782,6 +2801,54 @@ sub preflight_validate_mkdef
27822801
}
27832802
}
27842803

2804+
# Check: group-specific constraints
2805+
# Mirrors the group-handling logic in defmk's OBJ loop exactly:
2806+
# 1. If grouptype not set by user:
2807+
# - opt_d: reject if user attrs beyond objtype exist
2808+
# - else: treat as static
2809+
# 2. If dynamic (via opt_d or grouptype=dynamic): require wherevals
2810+
# 3. If static: reject members + -w conflict
2811+
if ($type eq 'group') {
2812+
my $grouptype_set = defined($objhash_ref->{$objname}{grouptype}) &&
2813+
$objhash_ref->{$objname}{grouptype};
2814+
my $is_dynamic = 0;
2815+
2816+
if (!$grouptype_set) {
2817+
if ($opts{opt_d}) {
2818+
# -d flag: reject if any user-provided attrs beyond objtype
2819+
# This matches: if (scalar(keys %{$::FINALATTRS{$obj}}) > 1)
2820+
if (scalar(keys %{$objhash_ref->{$objname}}) > 1) {
2821+
push @{$failures{$objname}}, {
2822+
attr => 'group',
2823+
message => "Can not assign attributes to dynamic node group \'$objname\'.",
2824+
};
2825+
}
2826+
$is_dynamic = 1;
2827+
}
2828+
# else: static, handled below
2829+
} elsif ($objhash_ref->{$objname}{grouptype} eq 'dynamic') {
2830+
$is_dynamic = 1;
2831+
}
2832+
2833+
if ($is_dynamic) {
2834+
# Dynamic groups require wherevals or -w
2835+
if (!$objhash_ref->{$objname}{wherevals} && !$opts{opt_w}) {
2836+
push @{$failures{$objname}}, {
2837+
attr => 'wherevals',
2838+
message => "The \'where\' attributes and values were not provided for dynamic group \'$objname\'.",
2839+
};
2840+
}
2841+
} else {
2842+
# Static groups cannot combine members list with -w
2843+
if ($opts{opt_w} && $objhash_ref->{$objname}{members}) {
2844+
push @{$failures{$objname}}, {
2845+
attr => 'members',
2846+
message => "Cannot use a list of members together with the \'-w\' option.",
2847+
};
2848+
}
2849+
}
2850+
}
2851+
27852852
# Check: only_if conditions from Schema
27862853
my $datatype = $xCAT::Schema::defspec{$type};
27872854
next unless $datatype;

xCAT-server/lib/xcat/plugins/DBobjectdefs.pm

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,21 +1671,37 @@ sub defmk
16711671
# Pull all the pieces together for the final hash
16721672
# - combines the command line attrs and input file attrs if provided
16731673
#
1674+
# Track command-level errors separately from per-object failures.
1675+
# Command-level errors (invalid attr names, bad type) mean the
1676+
# entire command's attribute set is suspect and no object should
1677+
# be created. Per-object failures (only_if, missing groups) only
1678+
# reject that specific object.
1679+
my $command_level_error = 0;
16741680
if (&setFINALattrs != 0)
16751681
{
16761682
$error = 1;
1683+
$command_level_error = 1;
1684+
}
1685+
1686+
my $numobjrequest = scalar(@::allobjnames);
1687+
1688+
# If the command itself is invalid, do not proceed with any objects.
1689+
if ($command_level_error && %::FINALATTRS)
1690+
{
1691+
%::FINALATTRS = ();
16771692
}
16781693

16791694
# Preflight validation: check all object-level reject conditions
16801695
# before any destructive operations (-f deletion, group mutation).
16811696
# Objects that fail are removed from FINALATTRS so they never reach
1682-
# rmobjdefs() or group member mutation.
1683-
my $numobjrequest = scalar(@::allobjnames);
1684-
if (!$error && %::FINALATTRS)
1697+
# rmobjdefs() or group member mutation. Valid objects proceed.
1698+
if (%::FINALATTRS)
16851699
{
16861700
my %preflight_failures = xCAT::DBobjUtils->preflight_validate_mkdef(
16871701
\%::FINALATTRS,
16881702
use_group_attrs => 1,
1703+
opt_d => $::opt_d,
1704+
opt_w => $::opt_w,
16891705
);
16901706

16911707
foreach my $obj (keys %preflight_failures) {
@@ -1699,8 +1715,8 @@ sub defmk
16991715
}
17001716
}
17011717

1702-
# If no objects survived preflight, return early with useful message
1703-
if (!$error && !%::FINALATTRS && $numobjrequest > 0)
1718+
# If no objects survived, return early with useful message
1719+
if (!%::FINALATTRS && $numobjrequest > 0)
17041720
{
17051721
my $is_node_type = (grep { $_ eq "node" } @::finalTypeList) ||
17061722
($::opt_t && $::opt_t =~ /\bnode\b/);

xCAT-test/autotest/testcase/mkdef/cases0

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,103 @@ check:rc==0
501501
check:output=~bmc=10.0.0.1
502502
cmd:rmdef -t node testnode_onlyif
503503
end
504+
505+
start:mkdef_inherited_validation_via_group
506+
description:mkdef with only_if satisfied by group inheritance should write the attr
507+
label:mn_only,ci_test,db
508+
cmd:rmdef -t node testnode_inherit
509+
cmd:chtab node=openbmcgrp nodehm.mgt=openbmc
510+
check:rc==0
511+
cmd:mkdef -t node -o testnode_inherit groups=openbmcgrp bmc=10.0.0.1
512+
check:rc==0
513+
cmd:lsdef -i bmc testnode_inherit
514+
check:rc==0
515+
check:output=~bmc=10.0.0.1
516+
cmd:rmdef -t node testnode_inherit
517+
cmd:chtab -d node=openbmcgrp nodehm
518+
end
519+
520+
start:mkdef_f_only_if_failure_preserves_old_bmc
521+
description:mkdef -f with only_if failure should preserve original bmc value
522+
label:mn_only,ci_test,db
523+
cmd:rmdef -t node testnode_keepbmc
524+
cmd:mkdef -t node -o testnode_keepbmc groups=oldgroup mgt=openbmc bmc=1.2.3.4
525+
check:rc==0
526+
cmd:mkdef -f -t node -o testnode_keepbmc groups=newgroup bmc=5.6.7.8
527+
check:rc!=0
528+
cmd:lsdef -i bmc testnode_keepbmc
529+
check:rc==0
530+
check:output=~bmc=1.2.3.4
531+
cmd:rmdef -t node testnode_keepbmc
532+
end
533+
534+
start:mkdef_multi_object_valid_invalid
535+
description:single mkdef command with one valid and one invalid object (per-object atomic)
536+
label:mn_only,ci_test,db
537+
cmd:rmdef -t node testnode_good
538+
cmd:rmdef -t node testnode_bad
539+
cmd:printf "testnode_good:\n objtype=node\n groups=test\n mgt=openbmc\n bmc=10.0.0.1\n\ntestnode_bad:\n objtype=node\n groups=test\n bmc=10.0.0.2\n" | mkdef -z
540+
check:rc!=0
541+
cmd:lsdef -i bmc testnode_good
542+
check:rc==0
543+
check:output=~bmc=10.0.0.1
544+
cmd:lsdef testnode_bad
545+
check:rc!=0
546+
cmd:rmdef -t node testnode_good
547+
cmd:rmdef -t node testnode_bad
548+
end
549+
550+
start:mkdef_f_dynamic_group_missing_wherevals_preserves_old
551+
description:mkdef -f with grouptype=dynamic but no wherevals should preserve old group
552+
label:mn_only,ci_test,db
553+
cmd:rmdef -t node testmember1
554+
cmd:rmdef -t group testkeepgrp
555+
cmd:mkdef -t node -o testmember1 groups=all
556+
check:rc==0
557+
cmd:mkdef -t group -o testkeepgrp members=testmember1
558+
check:rc==0
559+
cmd:mkdef -f -t group -o testkeepgrp grouptype=dynamic
560+
check:rc!=0
561+
cmd:lsdef -t group testkeepgrp
562+
check:rc==0
563+
cmd:lsdef -s testkeepgrp
564+
check:output=~testmember1
565+
cmd:rmdef -t group testkeepgrp
566+
cmd:rmdef -t node testmember1
567+
end
568+
569+
start:mkdef_f_dynamic_group_members_w_conflict_preserves_old
570+
description:mkdef -f -d -w with members should preserve old group
571+
label:mn_only,ci_test,db
572+
cmd:rmdef -t node testmember2
573+
cmd:rmdef -t group testkeepgrp2
574+
cmd:mkdef -t node -o testmember2 groups=all
575+
check:rc==0
576+
cmd:mkdef -t group -o testkeepgrp2 members=testmember2
577+
check:rc==0
578+
cmd:mkdef -f -t group -o testkeepgrp2 -d -w mgt==openbmc members=testmember2
579+
check:rc!=0
580+
cmd:lsdef -t group testkeepgrp2
581+
check:rc==0
582+
cmd:lsdef -s testkeepgrp2
583+
check:output=~testmember2
584+
cmd:rmdef -t group testkeepgrp2
585+
cmd:rmdef -t node testmember2
586+
end
587+
588+
start:mkdef_failed_group_no_member_mutation
589+
description:failed group creation should not mutate member node groups
590+
label:mn_only,ci_test,db
591+
cmd:rmdef -t node testmember3
592+
cmd:rmdef -t group testbadgrp
593+
cmd:mkdef -t node -o testmember3 groups=all
594+
check:rc==0
595+
cmd:mkdef -t group -o testbadgrp members=testmember3 bmc=10.0.0.1
596+
check:rc!=0
597+
cmd:lsdef -t group testbadgrp
598+
check:rc!=0
599+
cmd:lsdef -i groups testmember3
600+
check:output!~testbadgrp
601+
cmd:rmdef -t node testmember3
602+
cmd:rmdef -t group testbadgrp
603+
end

0 commit comments

Comments
 (0)