2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-20 22:55:40 +00:00

fix(imgport): put back the image rows when a later definition write fails

set_config writes the osimage, linuximage and kit rows one by one. When
a later write failed, the rows written before it stayed. An image could
keep a new linuximage row, with a new rootimgdir, beside its previous
osimage row.

set_config now keeps the osimage and linuximage rows that it replaces,
disabled rows included. When a write or its commit fails, or bundle
data of an unexpected shape dies between the writes, it checks each row
that it tried to write. It writes changed rows back and removes the rows
that it added, with NULL values as NULL. It skips a row that another
writer changed or filled, so that change stays. set_config reports the
rows that did not go back, also to a caller that asks for their number.
Kit rows stay as written, because other images can use them.
This commit is contained in:
Vinícius Ferrão
2026-10-17 00:16:04 -03:00
parent a72899b5a1
commit 89a8fc78f4
+112 -42
View File
@@ -1571,8 +1571,9 @@ sub create_symlink {
}
sub set_config {
my $data = shift;
my $callback = shift;
my $data = shift;
my $callback = shift;
my $rows_left = shift; # gets the number of rows that a failed write leaves changed
my $ostab = xCAT::Table->new('osimage', -create => 1, -autocommit => 0);
my $linuxtab = xCAT::Table->new('linuximage', -create => 1, -autocommit => 0);
my $kittab = xCAT::Table->new('kit', -create => 1, -autocommit => 0);
@@ -1608,12 +1609,24 @@ sub set_config {
$callback->({ data => ["Adding $osimage"] }) if $::VERBOSE;
# The rows of the image that this import wrote, with the values they had, for restore_rows.
my @written;
# setAttribs returns nothing for a new row without values, 0 after a write, and undef with an optional error on failure.
# Other images can use a kit row, so the kit rows of a failed import stay.
my $write = sub {
my ($tab, $key, $values) = @_;
my ($tab, $key, $values, $shared) = @_;
$values = {%$values}; # The caller fills the same hash for the next table.
my $previous = $shared ? 0 : table_row($tab, $key);
if (!defined($previous)) {
$callback->({ error => ["Cannot read the $tab->{tabname} table."], errorcode => [1] });
return 0;
}
my @result = eval { $tab->setAttribs($key, $values) };
my $error = $@ =~ s/\s+\z//r;
$error = $result[1] // 'no reply from the database' if !$error && @result && !defined($result[0]);
# A write or commit that fails can still change the row, so restore_rows checks each row that was tried.
push @written, [ $tab, $key, $values, $previous ] if !$shared;
if (!$error) {
return 1 if eval { $tab->commit; 1 };
$callback->({ error => ["Cannot commit the $tab->{tabname} table: " . ($@ =~ s/\s+\z//r)], errorcode => [1] });
@@ -1630,52 +1643,60 @@ sub set_config {
return 0;
};
# now we make a quick hash of what we want to put into this
my $hash_tmp = $data->{osimage};
foreach my $key (keys %$hash_tmp) {
$keyhash{$key} = $hash_tmp->{$key};
}
$write->($ostab, { imagename => $osimage }, \%keyhash) or return 0;
%keyhash = ();
my $hash_tmp1 = $data->{linuximage};
foreach my $key (keys %$hash_tmp1) {
$keyhash{$key} = $hash_tmp1->{$key};
}
$write->($linuxtab, { imagename => $osimage }, \%keyhash) or return 0;
my $kit = $data->{kit};
foreach my $k (keys %$kit) {
my $kithash = $kit->{$k};
%keyhash = ();
foreach my $key (keys %$kithash) {
$keyhash{$key} = $kithash->{$key};
# Bundle data of an unexpected shape can die between the writes, and the rows written before go back then too.
my $done = eval {
# now we make a quick hash of what we want to put into this
my $hash_tmp = $data->{osimage};
foreach my $key (keys %$hash_tmp) {
$keyhash{$key} = $hash_tmp->{$key};
}
$write->($kittab, { kitname => $k }, \%keyhash) or return 0;
}
$write->($ostab, { imagename => $osimage }, \%keyhash) or return 0;
my $kitrepo = $data->{kitrepo};
foreach my $k (keys %$kitrepo) {
my $kitrepohash = $kitrepo->{$k};
%keyhash = ();
foreach my $key (keys %$kitrepohash) {
$keyhash{$key} = $kitrepohash->{$key};
my $hash_tmp1 = $data->{linuximage};
foreach my $key (keys %$hash_tmp1) {
$keyhash{$key} = $hash_tmp1->{$key};
}
$write->($kitrepotab, { kitreponame => $k }, \%keyhash) or return 0;
}
my $kitcomp = $data->{kitcomp};
foreach my $k (keys %$kitcomp) {
my $kitcomphash = $kitcomp->{$k};
%keyhash = ();
foreach my $key (keys %$kitcomphash) {
$keyhash{$key} = $kitcomphash->{$key};
$write->($linuxtab, { imagename => $osimage }, \%keyhash) or return 0;
my $kit = $data->{kit};
foreach my $k (keys %$kit) {
my $kithash = $kit->{$k};
%keyhash = ();
foreach my $key (keys %$kithash) {
$keyhash{$key} = $kithash->{$key};
}
$write->($kittab, { kitname => $k }, \%keyhash, 1) or return 0;
}
$write->($kitcomptab, { kitcompname => $k }, \%keyhash) or return 0;
}
return 1;
my $kitrepo = $data->{kitrepo};
foreach my $k (keys %$kitrepo) {
my $kitrepohash = $kitrepo->{$k};
%keyhash = ();
foreach my $key (keys %$kitrepohash) {
$keyhash{$key} = $kitrepohash->{$key};
}
$write->($kitrepotab, { kitreponame => $k }, \%keyhash, 1) or return 0;
}
my $kitcomp = $data->{kitcomp};
foreach my $k (keys %$kitcomp) {
my $kitcomphash = $kitcomp->{$k};
%keyhash = ();
foreach my $key (keys %$kitcomphash) {
$keyhash{$key} = $kitcomphash->{$key};
}
$write->($kitcomptab, { kitcompname => $k }, \%keyhash, 1) or return 0;
}
1;
};
return 1 if $done;
$callback->({ error => ["Cannot write the image definition: " . ($@ =~ s/\s+\z//r)], errorcode => [1] }) if !defined($done);
my $left = restore_rows(\@written, $callback);
$$rows_left = $left if $rows_left;
return 0;
}
# Returns the row of a table with the key, 0 when there is none, or undef when the table cannot be read.
@@ -1701,6 +1722,55 @@ sub holds_row {
return $row && has_values($row, $values);
}
# Writes back the rows of a definition write that failed, newest first, and returns the number of rows left changed.
sub restore_rows {
my ($written, $callback) = @_;
my $left = 0;
foreach my $change (reverse @$written) {
my ($tab, $key, $values, $previous) = @$change;
my $row = join(', ', map { "$_=$key->{$_}" } sort keys %$key);
my $current = table_row($tab, $key);
if (!defined($current)) {
$callback->({ error => ["The previous $tab->{tabname} row of $row is not back: the table cannot be read."], errorcode => [1] });
$left++;
next;
}
# A write that did not take effect left the row as it was.
next if $previous ? $current && has_values($current, { map { ($_ => $previous->{$_}) } keys %$values }) : !$current;
# A row that another host changed, or that another writer filled more of after this import added it, stays.
my @filled = $previous ? () : grep { !exists($values->{$_}) && !exists($key->{$_}) && ($current->{$_} // '') ne '' } keys %{ $current || {} };
if (!$current || !has_values($current, $values) || @filled) {
$callback->({ warning => ["The $tab->{tabname} row of $row changed after this import wrote it, so the change stays."] });
$left++;
next;
}
my $back;
if ($previous) {
# An undefined value goes back as NULL, which a read of the disable column takes as enabled, unlike ''.
my %old = map { ($_ => $previous->{$_}) } keys %$values;
# setAttribs writes the values of its key into the SQL text, so the update matches only the key.
eval { $tab->setAttribs($key, \%old); $tab->commit };
$back = holds_row($tab, $key, \%old);
} else {
# delEntries binds its condition, so the written values in it keep a row that another writer changed.
my %ours = (%$key, map { ($_ => $values->{$_}) } grep { defined($values->{$_}) } keys %$values);
eval { $tab->delEntries(\%ours); $tab->commit };
$current = table_row($tab, $key);
$back = defined($current) && !$current;
}
if ($back) {
$callback->({ data => [ $previous ? " Put back the previous $tab->{tabname} row of $row." : " Removed the new $tab->{tabname} row of $row." ] });
} else {
$callback->({ error => ["The previous $tab->{tabname} row of $row is not back."], errorcode => [1] });
$left++;
}
}
return $left;
}
#an adapter to convert the manifest structure from 2.7 to 2.8
sub manifest_adapter {
my $data = shift;