diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm
index 168fccb6..f358106e 100644
--- a/Anvil/Tools.pm
+++ b/Anvil/Tools.pm
@@ -1106,6 +1106,7 @@ sub _set_paths
'anvil-delete-server' => "/usr/sbin/anvil-delete-server",
'anvil-download-file' => "/usr/sbin/anvil-download-file",
'anvil-file-details' => "/usr/sbin/anvil-file-details",
+ 'anvil-get-server-screenshot' => "/usr/sbin/anvil-get-server-screenshot",
'anvil-join-anvil' => "/usr/sbin/anvil-join-anvil",
'anvil-maintenance-mode' => "/usr/sbin/anvil-maintenance-mode",
'anvil-manage-firewall' => "/usr/sbin/anvil-manage-firewall",
diff --git a/anvil.spec.in b/anvil.spec.in
index a7804d62..d1952b3b 100644
--- a/anvil.spec.in
+++ b/anvil.spec.in
@@ -157,13 +157,14 @@ Requires: libvirt-daemon
Requires: libvirt-daemon-driver-qemu
Requires: libvirt-daemon-kvm
Requires: libvirt-docs
+Requires: netpbm-progs
Requires: pacemaker
Requires: pcs
+Requires: python3-websockify
Requires: qemu-kvm
Requires: qemu-kvm-core
Requires: virt-install
Requires: virt-top
-Requires: python3-websockify
# A node is allowed to host servers and be a live migration target. It is not
# allowed to host a database or be a DR host.
Conflicts: anvil-striker
@@ -188,11 +189,12 @@ Requires: libvirt-daemon
Requires: libvirt-daemon-driver-qemu
Requires: libvirt-daemon-kvm
Requires: libvirt-docs
+Requires: netpbm-progs
+Requires: python3-websockify
Requires: qemu-kvm
Requires: qemu-kvm-core
Requires: virt-install
Requires: virt-top
-Requires: python3-websockify
# A DR host is not allowed to be a live-migration target or host a database.
Conflicts: anvil-striker
Conflicts: anvil-node
diff --git a/cgi-bin/Makefile.am b/cgi-bin/Makefile.am
index 5343314e..92fc1a55 100644
--- a/cgi-bin/Makefile.am
+++ b/cgi-bin/Makefile.am
@@ -7,6 +7,7 @@ dist_cgibin_SCRIPTS = \
get_memory \
get_networks \
get_replicated_storage \
+ get_server_screenshot \
get_servers \
get_shared_storage \
get_status \
diff --git a/cgi-bin/get_server_screenshot b/cgi-bin/get_server_screenshot
new file mode 100755
index 00000000..184de9ec
--- /dev/null
+++ b/cgi-bin/get_server_screenshot
@@ -0,0 +1,136 @@
+#!/usr/bin/perl
+#
+# Gets a server VM's screenshot and convert it to a Base64 string.
+#
+
+use strict;
+use warnings;
+use Anvil::Tools;
+use JSON;
+
+$| = 1;
+
+my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
+my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
+if (($running_directory =~ /^\./) && ($ENV{PWD}))
+{
+ $running_directory =~ s/^\./$ENV{PWD}/;
+}
+
+my $anvil = Anvil::Tools->new();
+
+sub is_job_incomplete
+{
+ my $parameters = shift;
+ my $job_uuid = $parameters->{job_uuid};
+
+ my $query = "
+SELECT
+ job_progress
+FROM
+ public.jobs
+WHERE
+ job_uuid = ".$anvil->Database->quote($job_uuid)."
+;";
+
+ my $job_progress = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0];
+
+ return $job_progress == 100 ? 0 : 1;
+}
+
+sub get_server_host_uuid
+{
+ my $parameters = shift;
+ my $server_uuid = $parameters->{server_uuid};
+
+ my $query = "
+SELECT
+ server_host_uuid
+FROM
+ public.servers
+WHERE
+ server_uuid = ".$anvil->Database->quote($server_uuid)."
+;";
+
+ return $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0];
+}
+
+sub get_screenshot
+{
+ my $parameters = shift;
+ my $server_uuid = $parameters->{server_uuid};
+ my $server_host_uuid = $parameters->{server_host_uuid};
+ my $resize_args = defined $parameters->{resize_args} ? $parameters->{resize_args} : "512x512";
+
+ my ($job_uuid) = $anvil->Database->insert_or_update_jobs({
+ job_command => $anvil->data->{path}{exe}{'anvil-get-server-screenshot'},
+ job_data => "server-uuid=".$server_uuid."\nresize=".$resize_args,
+ job_host_uuid => $server_host_uuid,
+ job_description => "job_0357",
+ job_name => "cgi-bin::get_server_screenshot::".$server_uuid,
+ job_progress => 0,
+ job_title => "job_0356"
+ });
+
+ # Wait until the job is complete before continuing.
+ while(is_job_incomplete({ job_uuid => $job_uuid }))
+ {
+ sleep(2);
+ }
+
+ my $query = "
+SELECT state_note
+FROM public.states
+WHERE state_name = ".$anvil->Database->quote("server_screenshot::".$server_uuid)."
+;";
+
+ my $encoded_image = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0];
+
+ return $encoded_image;
+}
+
+$anvil->Get->switches;
+
+$anvil->Database->connect;
+$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132"});
+if (not $anvil->data->{sys}{database}{connections})
+{
+ # No databases, exit.
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, priority => "err", key => "error_0003"});
+ $anvil->nice_exit({exit_code => 1});
+}
+
+my $cookie_problem = $anvil->Account->read_cookies();
+
+# Don't do anything data-related if the user is not logged in.
+if ($cookie_problem)
+{
+ $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, priority => "err", key => "error_0307" });
+ $anvil->nice_exit({ exit_code => 1 });
+}
+
+# Read in any CGI variables, if needed.
+$anvil->Get->cgi();
+
+print $anvil->Template->get({ file => "shared.html", name => "json_headers", show_name => 0 })."\n";
+
+my $server_uuid = defined $anvil->data->{cgi}{server_uuid}{value} ? $anvil->data->{cgi}{server_uuid}{value} : $anvil->data->{switches}{'server-uuid'};
+my $resize_args = defined $anvil->data->{cgi}{resize}{value} ? $anvil->data->{cgi}{resize}{value} : $anvil->data->{switches}{'resize'};
+
+my $response_body = {};
+
+if ($server_uuid)
+{
+ my $encoded_image = get_screenshot({
+ server_uuid => $server_uuid,
+ server_host_uuid => get_server_host_uuid({ server_uuid => $server_uuid }),
+ resize_args => $resize_args
+ });
+
+ if (defined $encoded_image)
+ {
+ $response_body->{screenshot} = $encoded_image;
+ }
+}
+
+print JSON->new->utf8->encode($response_body)."\n";
diff --git a/share/words.xml b/share/words.xml
index 5e521616..368f2463 100644
--- a/share/words.xml
+++ b/share/words.xml
@@ -1148,6 +1148,8 @@ It should be provisioned in the next minute or two.
* Please enter the name of the server you want to manage
-=] Servers available to manage on the Anvil! [#!variable!anvil_name!#] [=-
-=] Managing the server: [#!variable!server_name!#] on the Anvil!: [#!variable!anvil_name!#]
+ Get Server VM Screenshot
+ Fetch a screenshot of the specified server VM and represent it as a Base64 string.
Starting: [#!variable!program!#].
@@ -2256,6 +2258,10 @@ Are you sure that you want to delete the server: [#!variable!server_name!#]? [Ty
Finished [#!variable!operation!#] VNC pipe for server UUID [#!variable!server_uuid!#] from host UUID [#!variable!host_uuid!#].
Finished dropping VNC pipes table.
Finished managing VNC pipes; no operations happened because requirements not met.
+ Preparing to get server VM screenshot.
+ Finished getting server VM screenshot.
+ Failed to get server VM screenshot; got non-zero return code.
+ Finished attempting to get server VM screenshot; no operations happened because requirements not met.
Saved the mail server information successfully!
diff --git a/tools/Makefile.am b/tools/Makefile.am
index f23aa192..d2b6c074 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -14,6 +14,7 @@ dist_sbin_SCRIPTS = \
anvil-delete-server \
anvil-download-file \
anvil-file-details \
+ anvil-get-server-screenshot \
anvil-join-anvil \
anvil-maintenance-mode \
anvil-manage-files \
diff --git a/tools/anvil-get-server-screenshot b/tools/anvil-get-server-screenshot
new file mode 100755
index 00000000..57579c4e
--- /dev/null
+++ b/tools/anvil-get-server-screenshot
@@ -0,0 +1,186 @@
+#!/usr/bin/perl
+#
+#
+#
+
+use strict;
+use warnings;
+use Anvil::Tools;
+
+$| = 1;
+
+my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
+my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
+if (($running_directory =~ /^\./) && ($ENV{PWD}))
+{
+ $running_directory =~ s/^\./$ENV{PWD}/;
+}
+
+my $anvil = Anvil::Tools->new();
+
+sub system_call
+{
+ my $parameters = shift;
+ my $shell_call = $parameters->{shell_call};
+
+ my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call });
+ $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ shell_call => $shell_call,
+ shell_output => $shell_output,
+ shell_return_code => $shell_return_code
+ } });
+
+ return ($shell_output, $shell_return_code);
+}
+
+sub is_existing_server_screenshot_outdated
+{
+ my $parameters = shift;
+ my $server_uuid = $parameters->{server_uuid};
+
+ my ($encoded_image, $variable_uuid, $variable_mtime) = $anvil->Database->read_variable({ variable_name => "server_screenshot::".$server_uuid });
+
+ my $time_difference = time - $variable_mtime;
+
+ return $time_difference > 120 ? 1 : 0;
+}
+
+sub get_server_screenshot
+{
+ my $parameters = shift;
+ my $server_uuid = $parameters->{server_uuid};
+ my ($resize_x, $resize_y) = split(/x/ , $parameters->{resize_args});
+
+ my $shell_call = "virsh screenshot --domain ".$server_uuid." --file /dev/stdout | sed 's/Screenshot.*//'";
+
+ if ($resize_x =~ /^\d+$/ && $resize_y =~ /^\d+$/)
+ {
+ $shell_call .= " | pamscale -quiet -xyfit ".$resize_x." ".$resize_y;
+ $shell_call .= " | pamtopng -quiet";
+ }
+
+ $shell_call .= " | base64 --wrap 0";
+
+ my ($shell_output, $shell_return_code) = system_call({ shell_call => $shell_call });
+
+ return $shell_return_code == 0 ? $shell_output : undef;
+}
+
+sub insert_server_screenshot
+{
+ my $parameters = shift;
+ my $server_uuid = $parameters->{server_uuid};
+ my $encoded_image = $parameters->{encoded_image};
+
+ $anvil->Database->insert_or_update_states({
+ state_name => "server_screenshot::".$server_uuid,
+ state_note => $encoded_image
+ });
+}
+
+$anvil->Get->switches;
+
+$anvil->Database->connect;
+$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132"});
+if (not $anvil->data->{sys}{database}{connections})
+{
+ # No databases, exit.
+ $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, priority => "err", key => "error_0003"});
+ $anvil->nice_exit({exit_code => 1});
+}
+
+# Try to get a job UUID if not given.
+if (not $anvil->data->{switches}{'job-uuid'})
+{
+ $anvil->data->{switches}{'job-uuid'} = $anvil->Job->get_job_uuid({ program => $THIS_FILE });
+ $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ "switches::job-uuid" => $anvil->data->{switches}{'job-uuid'}
+ } });
+}
+
+# Handle this script as a job when job UUID is provided.
+if ($anvil->data->{switches}{'job-uuid'})
+{
+ $anvil->Job->clear();
+ $anvil->Job->get_job_details();
+ $anvil->Job->update_progress({
+ progress => 1,
+ job_picked_up_by => $$,
+ job_picked_up_at => time,
+ message => "message_0263"
+ });
+
+ foreach my $line (split/\n/, $anvil->data->{jobs}{job_data})
+ {
+ if ($line =~ /server-uuid=(.*?)$/)
+ {
+ $anvil->data->{switches}{'server-uuid'} = $1;
+ }
+
+ if ($line =~ /resize=(.*?)$/)
+ {
+ $anvil->data->{switches}{'resize'} = $1;
+ }
+
+ if ($line =~ /stdout=(.*?)$/)
+ {
+ $anvil->data->{switches}{'stdout'} = $1;
+ }
+ }
+}
+
+my $server_uuid = $anvil->data->{switches}{'server-uuid'};
+my $resize_args = $anvil->data->{switches}{'resize'};
+my $is_stdout = $anvil->data->{switches}{'stdout'};
+my $job_uuid = $anvil->data->{switches}{'job-uuid'};
+
+$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
+ server_uuid => $server_uuid,
+ resize_args => $resize_args,
+ is_stdout => $is_stdout,
+ job_uuid => $job_uuid
+} });
+
+if ($server_uuid)
+{
+ my $encoded_image;
+
+ if ($is_stdout)
+ {
+ $encoded_image = get_server_screenshot({ server_uuid => $server_uuid, resize_args => $resize_args });
+
+ if (defined $encoded_image)
+ {
+ print($encoded_image);
+
+ $anvil->Job->update_progress({ progress => 100, message => "message_0264" });
+ }
+ else
+ {
+ $anvil->Job->update_progress({ progress => 100, message => "message_0265" });
+ }
+ }
+ elsif (is_existing_server_screenshot_outdated({ server_uuid => $server_uuid }))
+ {
+ $encoded_image = get_server_screenshot({ server_uuid => $server_uuid, resize_args => $resize_args });
+
+ if (defined $encoded_image)
+ {
+ insert_server_screenshot({ server_uuid => $server_uuid, encoded_image => $encoded_image });
+
+ $anvil->Job->update_progress({ progress => 100, message => "message_0264" });
+ }
+ else
+ {
+ $anvil->Job->update_progress({ progress => 100, message => "message_0265" });
+ }
+ }
+ else
+ {
+ $anvil->Job->update_progress({ progress => 100, message => "message_0266" });
+ }
+}
+else
+{
+ $anvil->Job->update_progress({ progress => 100, message => "message_0266" });
+}