Merge pull request #163 from ClusterLabs/anvil-tools-dev

Anvil tools dev
main
Digimer 3 years ago committed by GitHub
commit 09c4b3fa57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      Anvil/Tools/Cluster.pm
  2. 48
      Anvil/Tools/Job.pm
  3. 4
      scancore-agents/scan-network/scan-network
  4. 7
      share/words.xml
  5. 275
      tools/anvil-manage-server
  6. 6
      tools/anvil-provision-server

@ -2483,6 +2483,10 @@ sub manage_fence_delay
prefer => $prefer,
}});
### NOTE: We don't really need this anymore, though there is one reason we might (to be decided later);
### See: https://clusterlabs.org/pacemaker/doc/2.1/Pacemaker_Explained/singlehtml/index.html#cluster-options
### - priority-fencing-delay
# Are we a node?
my $host_type = $anvil->Get->host_type({debug => $debug});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { host_type => $host_type }});
@ -2552,6 +2556,8 @@ sub manage_fence_delay
}
}
### TODO: We don't need to specify the full argument list, we only need to set 'delay=""' to delete
### the delay, and 'delay="15"' to add it.
my $preferred_node = "";
foreach my $node_name (sort {$a cmp $b} keys %{$anvil->data->{fence_method}})
{

@ -541,6 +541,10 @@ Parameters;
This is the UUID of the job to update. If it isn't set, but C<< jobs::job_uuid >> is set, it will be used. If that is also not set,
=head3 log_level (optional)
If set to a numeric level, the job's message will also be logged. This is designed to simplify code as most job progress messages will also want to be logged.
=head3 message (optional)
If set, this message will be appended to C<< job_status >>. If set to 'C<< clear >>', previous records will be removed.
@ -551,10 +555,26 @@ NOTE: This is in the format C<< <key>[,!!<variable_name1>!<variable_value1>[,...
If set, this is used for the C<< job_picked_up_by >> column. If it isn't set, the process ID of the caller is used.
=head3 print (optional, default '1')
If C<< log_level >> is set, this can be set to C<< 1 >> to print the log entry to STDOUT, or C<< 0 >> to not.
=head3 priority (optional)
If C<< log_level >> is set, this can be set to the priority to use when logging (see C<< Alert->entry >>).
=head3 progress (required)
This is a number to set the current progress to.
=head3 secure (optional, default '0')
If C<< log_level >> is set, this can be set to C<< 1 >> to indicate that it contains sensitive data, like a password.
=head3 variables (optional)
This can be set as a hash reference containing key / variable pairs to inject into the message key. the C<< variable => value >> pairs will be appended to the C<< message >> key automatically. This is meant to simplify when an alert is also being longed, or when a large number of variables are being injected into the string.
=cut
sub update_progress
{
@ -565,16 +585,32 @@ sub update_progress
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Job->update_progress()" }});
my $job_uuid = defined $parameter->{job_uuid} ? $parameter->{job_uuid} : "";
my $log_level = defined $parameter->{log_level} ? $parameter->{log_level} : "";
my $message = defined $parameter->{message} ? $parameter->{message} : "";
my $picked_up_by = defined $parameter->{picked_up_by} ? $parameter->{picked_up_by} : "";
my $print = defined $parameter->{'print'} ? $parameter->{'print'} : 1;
my $priority = defined $parameter->{priority} ? $parameter->{priority} : "";
my $progress = defined $parameter->{progress} ? $parameter->{progress} : "";
my $secure = defined $parameter->{secure} ? $parameter->{secure} : "";
my $variables = defined $parameter->{variables} ? $parameter->{variables} : "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
job_uuid => $job_uuid,
picked_up_by => $picked_up_by,
'print' => $print,
progress => $progress,
log_level => $log_level,
message => $message,
job_uuid => $job_uuid,
variables => $variables,
secure => $secure,
}});
# Log before anything else, in case we abort the job update.
if (($message ne "clear") && ($log_level =~ /^\d+$/))
{
# Log this message.
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $log_level, 'print' => $print, secure => $secure, priority => $priority, key => $message, variables => $variables});
}
if ($picked_up_by eq "")
{
$picked_up_by = $$;
@ -618,6 +654,16 @@ sub update_progress
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { "sys::last_update" => $anvil->data->{sys}{last_update} }});
}
# Add variables to the message, if required
if (ref($variables) eq "HASH")
{
foreach my $variable (sort {$a cmp $b} keys %{$variables})
{
my $value = defined $variables->{$variable} ? $variables->{$variable} : "undefined:".$variable;
$message .= ",!!".$variable."!".$value."!!";
}
}
# Get the current job_status and append this new one.
my $job_picked_up_at = 0;
my $job_status = "";

@ -317,7 +317,7 @@ sub collect_data
{
# We're in a bond.
my $target = readlink($full_path."/master");
my $bond_master = ($target =~ /^.*\/(.*)$/)[0];
$bond_master = ($target =~ /^.*\/(.*)$/)[0];
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
target => $target,
bond_master => $bond_master
@ -3060,7 +3060,7 @@ AND
my ($health_uuid) = $anvil->Database->insert_or_update_health({
debug => 2,
health_agent_name => $THIS_FILE,
source_name => $source_name,
health_source_name => $source_name,
health_source_weight => 1,
});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { health_uuid => $health_uuid }});

@ -424,6 +424,11 @@ The attempt to start the servers appears to have failed. The return code '0' was
<key name="error_0310">I tried to remove the fence delay from the node: [#!variable!node!#], but it doesn't appear to have worked. The preferred node is: [#!variable!current!#] ('--' means there is no preferred node)</key>
<key name="error_0311">Failed to find the UUID column for the table: [#!variable!table!#].</key>
<key name="error_0312">The 'set_to' parameter: [#!variable!set_to!#] is invalid. It must be 'yes' or 'no'.</key>
<key name="error_0318">The server UUID: [#!variable!server_uuid!#] is not valid or was not found in the database.</key>
<key name="error_0319">The Anvil! name: [#!variable!anvil_name!#] was not found in the database.</key>
<key name="error_0320">The Anvil! UUID: [#!variable!anvil_uuid!#] is not valid or was not found in the database.</key>
<key name="error_0321">There are no Anvil! systems yet in the database, nothing to do.</key>
<key name="error_0322">There are no servers yet in the database, nothing to do.</key>
<!-- Files templates -->
<!-- NOTE: Translating these files requires an understanding of which lines are translatable -->
@ -1112,6 +1117,8 @@ It should be provisioned in the next minute or two.</key>
<key name="job_0348">Synchronizing the new corosync config exited with return code: [#!variable!return_code!#] and output: [#!variable!output!#]</key>
<key name="job_0349">Loading the new corosync config exited with return code: [#!variable!return_code!#] and output: [#!variable!output!#]</key>
<key name="job_0352">Manage a server menu:</key>
<!-- Log entries -->
<key name="log_0001">Starting: [#!variable!program!#].</key>
<key name="log_0002">

@ -293,7 +293,7 @@ sub interactive_question
$anvil->data->{target_server}{anvil_dr1_host_name} = $anvil->data->{hosts}{host_uuid}{$dr1_host_uuid}{host_name};;
}
### Pull out details pf the server.
### Pull out details of the server.
# How much RAM are we using and how much is configured?
$anvil->data->{target_server}{server_ram_in_use} = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_ram_in_use};
$anvil->data->{target_server}{server_host_uuid} = $server_host_uuid;
@ -391,11 +391,28 @@ sub interactive_question
# "servers::server_uuid::${server_uuid}::server_boot_time" => $anvil->data->{servers}{server_uuid}{$server_uuid}{server_boot_time},
# }});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"target_server::server_name" => $anvil->data->{target_server}{server_name},
}});
}
else
{
# Bad server UUID. If it's a job, abort. Otherwise, clear and go back to
my $variables = { server_uuid => $anvil->data->{target_server}{server_uuid} };
$anvil->Jobs->update_progress({
progress => 100,
message => "error_0318",
variables => $variables,
job_status => "failed",
log_level => 1,
priority => "err",
});
if ($anvil->data->{switches}{'job-uuid'})
{
$anvil->nice_exit({exit_code => 1});
}
$anvil->data->{target_server}{server_uuid} = "";
}
}
### Anvil
@ -421,7 +438,6 @@ sub interactive_question
# Do we know or can we find the Anvil! UUID?
$anvil->data->{target_server}{anvil_uuid} = $anvil->data->{switches}{'anvil-uuid'} ? $anvil->data->{switches}{'anvil-uuid'} : "";
$anvil->data->{target_server}{anvil_name} = $anvil->data->{switches}{'anvil-name'} ? $anvil->data->{switches}{'anvil-name'} : "";
if ((not $anvil->data->{target_server}{anvil_uuid}) && (not $anvil->data->{target_server}{anvil_name}))
{
# Nothing given. Is this host a node, perhaps?
@ -437,20 +453,89 @@ sub interactive_question
"target_server::anvil_uuid" => $anvil->data->{target_server}{anvil_uuid},
}});
}
else
{
# If there is only one Anvil!, choose it
my $known_anvils = keys %{$anvil->data->{anvils}{anvil_name}};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { known_anvils => $known_anvils }});
if (not $known_anvils)
my $variables = { server_uuid => $anvil->data->{target_server}{server_uuid} };
$anvil->Jobs->update_progress({
progress => 100,
message => "error_0321",
job_status => "failed",
log_level => 1,
priority => "err",
});
$anvil->nice_exit({exit_code => 1});
}
elsif ($known_anvils == 1)
{
foreach my $anvil_name (keys %{$anvil->data->{anvils}{anvil_name}})
{
$anvil->data->{target_server}{anvil_name} = $anvil_name;
$anvil->data->{target_server}{anvil_uuid} = $anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_uuid};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"target_server::anvil_name" => $anvil->data->{target_server}{anvil_name},
"target_server::anvil_uuid" => $anvil->data->{target_server}{anvil_uuid},
}});
}
}
}
}
elsif (not $anvil->data->{target_server}{anvil_uuid})
{
# We have a name, get the UUID.
$anvil->data->{target_server}{anvil_uuid} = $anvil->Cluster->get_anvil_uuid({anvil_name => $anvil->data->{target_server}{anvil_name}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "target_server::anvil_uuid" => $anvil->data->{target_server}{anvil_uuid} }});
# If the name is bad, error out if not interactive
if (not $anvil->data->{target_server}{anvil_uuid})
{
# Bad server UUID. If it's a job, abort. Otherwise, clear and go back to
my $variables = { anvil_name => $anvil->data->{target_server}{anvil_name} };
$anvil->Jobs->update_progress({
progress => 100,
message => "error_0319",
variables => $variables,
job_status => "failed",
log_level => 1,
priority => "err",
});
if ($anvil->data->{switches}{'job-uuid'})
{
$anvil->nice_exit({exit_code => 1});
}
$anvil->data->{target_server}{anvil_name} = "";
}
}
elsif (not $anvil->data->{target_server}{anvil_name})
{
$anvil->data->{target_server}{anvil_name} = $anvil->Cluster->get_anvil_name({anvil_uuid => $anvil->data->{target_server}{anvil_uuid}});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { "target_server::anvil_name" => $anvil->data->{target_server}{anvil_name} }});
# If the name is bad, error out if not interactive
if (not $anvil->data->{target_server}{anvil_name})
{
# Bad server UUID. If it's a job, abort. Otherwise, clear and go back to
my $variables = { anvil_uuid => $anvil->data->{target_server}{anvil_uuid} };
$anvil->Jobs->update_progress({
progress => 100,
message => "error_0320",
variables => $variables,
job_status => "failed",
log_level => 1,
priority => "err",
});
if ($anvil->data->{switches}{'job-uuid'})
{
$anvil->nice_exit({exit_code => 1});
}
$anvil->data->{target_server}{anvil_uuid} = "";
}
}
# If we don't have an Anvil! UUID, and if this is a node, load the anvil_uuid automatically.
# If we don't have an Anvil! UUID at this point, we need to ask the user.
my $termios = new POSIX::Termios;
$termios->getattr;
my $ospeed = $termios->getospeed;
@ -458,7 +543,187 @@ sub interactive_question
my $terminal = Tgetent Term::Cap { TERM => undef, OSPEED => $ospeed };
$terminal->Trequire(qw/ce ku kd/);
if (not $anvil->data->{target_server}{anvil_uuid})
{
interactive_ask_anvil_name($anvil, $terminal);
}
return(0);
}
sub interactive_ask_anvil_name
{
my ($anvil, $terminal) = @_;
my $retry = 0;
while(1)
{
print $terminal->Tputs('cl');
print $anvil->Words->string({key => "job_0352"})."\n";
print $anvil->Words->string({key => "job_0151", variables => { anvil_name => $anvil->data->{target_server}{anvil_name} }})."\n\n\n";
# Show all the current server names.
if ($retry)
{
print $anvil->Words->string({key => "job_0152"})."\n\n";
}
print $anvil->Words->string({key => "job_0153"})."\n";
foreach my $anvil_name (sort {$a cmp $b} keys %{$anvil->data->{anvils}{anvil_name}})
{
print "- ".$anvil_name.": - ".$anvil->data->{anvils}{anvil_name}{$anvil_name}{anvil_description}."\n";
}
print $terminal->Tgoto('cm', 0, 2)."? ";
my $answer = <STDIN>;
chomp $answer;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
if ((not $answer) && ($default_anvil))
{
$answer = $default_anvil;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
}
# Reload in case a new anvil! was saved while we waited.
$anvil->Database->get_anvils();
if (exists $anvil->data->{anvils}{anvil_name}{$answer})
{
# Valid.
$anvil->data->{target_server}{anvil_name} = $answer;
$anvil->data->{target_server}{anvil_uuid} = $anvil->Cluster->get_anvil_uuid({anvil_name => $answer});
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"target_server::anvil_name" => $anvil->data->{target_server}{anvil_name},
"target_server::anvil_uuid" => $anvil->data->{target_server}{anvil_uuid},
}});
last;
}
else
{
$retry = 1;
}
}
interactive_question($anvil);
return(0);
}
sub interactive_ask_server_name
{
my ($anvil, $terminal) = @_;
$anvil->Database->get_servers({debug => 2});
### TODO: Figure out how many rows we have and break the server list into columns if too long.
my $retry = 0;
my $duplicate = "";
while(1)
{
my $default = "";
if ($anvil->data->{switches}{name})
{
$default = $anvil->data->{switches}{name};
}
print $terminal->Tputs('cl');
print $anvil->Words->string({key => "job_0150"})."\n";
print $anvil->Words->string({key => "job_0151", variables => { anvil_name => $anvil->data->{target_server}{anvil_name} }})."\n";
print $anvil->Words->string({key => "job_0157", variables => { server_name => $anvil->data->{target_server}{name} }})."\n\n\n";
# Show all the current server names.
if ($retry)
{
if ($duplicate)
{
print $anvil->Words->string({key => "job_0219", variables => { server_name => $duplicate }})."\n\n";
$duplicate = "";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { duplicate => $duplicate }});
}
else
{
print $anvil->Words->string({key => "job_0159"})."\n\n";
}
}
my $anvil_uuid = $anvil->data->{target_server}{anvil_uuid};
print $anvil->Words->string({key => "job_0160", variables => { anvil_name => $anvil->data->{target_server}{anvil_name} }})."\n";
foreach my $server_name (sort {$a cmp $b} keys %{$anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}})
{
my $server_uuid = $anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}{$server_name}{server_uuid};
my $server_state = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_state};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
server_uuid => $server_uuid,
server_state => $server_state,
}});
if ($server_state eq "DELETED")
{
### NOTE: This could get cluttered, so for now we'll not show them.
#print $anvil->Words->string({key => "message_0220", variables => { server_name => $server_name }})."\n";
}
else
{
print $anvil->Words->string({key => "message_0219", variables => {
server_name => $server_name,
server_state => $server_state,
}})."\n";
}
}
print $terminal->Tgoto('cm', 0, 3)."? ";
my $answer = <STDIN>;
chomp $answer;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
if ((not $answer) && ($default))
{
$answer = $default;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
}
# Reload in case a new anvil! was saved while we waited.
$anvil->Database->get_servers();
if ($answer)
{
# Duplicate?
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { answer => $answer }});
if (exists $anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}{$answer})
{
my $server_uuid = $anvil->data->{servers}{anvil_uuid}{$anvil_uuid}{server_name}{$answer}{server_uuid};
my $server_state = $anvil->data->{servers}{server_uuid}{$server_uuid}{server_state};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
server_uuid => $server_uuid,
server_state => $server_state,
}});
if ($server_state eq "DELETED")
{
# Valid, we can re-use deleted server names. We'll also re-use the
# UUID, if the user didn't specifically specify a UUID.
$anvil->data->{target_server}{name} = $answer;
$anvil->data->{target_server}{uuid} = $server_uuid if not $anvil->data->{target_server}{uuid};
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"target_server::name" => $anvil->data->{target_server}{name},
"target_server::uuid" => $anvil->data->{target_server}{uuid},
}});
}
else
{
# Invalid, duplicate.
$duplicate = $answer;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { duplicate => $duplicate }});
}
}
else
{
# Valid.
$anvil->data->{target_server}{name} = $answer;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => {
"target_server::name" => $anvil->data->{target_server}{name},
}});
}
last;
}
else
{
$retry = 1;
}
}
return(0);
}

@ -428,8 +428,10 @@ sub provision_server
say_memory => $say_memory,
}});
### TODO: Support user-selected IFN. For now, we hard-code it to 'ifn_bridge1'
### Support disk images (ie: sysprep) via '--import'. The device used for booting is the first device specified via "--disk"
### TODO: Support user-selected IFN. For now, we hard-code it to 'ifn_bridge1' Also allow a MAC to be
### set with '--network ifn1_bridge1[:mac],ifn2_bridge1[:mac]...'.
### Support disk images (ie: sysprep) via '--import'. The device used for booting is the first
### device specified via "--disk"
### Consider support for TPM, RNG and watchdog devices
my $shell_call = $anvil->data->{path}{exe}{'virt-install'}." --connect qemu:///system \\\n";
$shell_call .= "--name ".$server." \\\n";

Loading…
Cancel
Save