Merge pull request #162 from Tsu-ba-me/issues/151-vnc-on-ws

Web UI: add endpoint to handle building pipes from client (in browser) to server VMs' VNC server
main
Digimer 3 years ago committed by GitHub
commit 18839f5b7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Anvil/Tools.pm
  2. 2
      anvil.spec.in
  3. 1
      cgi-bin/Makefile.am
  4. 154
      cgi-bin/manage_vnc_pipes
  5. 11
      share/words.xml
  6. 2
      tools/Makefile.am
  7. 1150
      tools/striker-manage-vnc-pipes
  8. 137
      tools/striker-open-ssh-tunnel

@ -1221,6 +1221,8 @@ sub _set_paths
'striker-initialize-host' => "/usr/sbin/striker-initialize-host", 'striker-initialize-host' => "/usr/sbin/striker-initialize-host",
'striker-manage-install-target' => "/usr/sbin/striker-manage-install-target", 'striker-manage-install-target' => "/usr/sbin/striker-manage-install-target",
'striker-manage-peers' => "/usr/sbin/striker-manage-peers", 'striker-manage-peers' => "/usr/sbin/striker-manage-peers",
'striker-manage-vnc-pipes' => "/usr/sbin/striker-manage-vnc-pipes",
'striker-open-ssh-tunnel' => "/usr/sbin/striker-open-ssh-tunnel",
'striker-parse-oui' => "/usr/sbin/striker-parse-oui", 'striker-parse-oui' => "/usr/sbin/striker-parse-oui",
'striker-prep-database' => "/usr/sbin/striker-prep-database", 'striker-prep-database' => "/usr/sbin/striker-prep-database",
'striker-scan-network' => "/usr/sbin/striker-scan-network", 'striker-scan-network' => "/usr/sbin/striker-scan-network",

@ -163,6 +163,7 @@ Requires: qemu-kvm
Requires: qemu-kvm-core Requires: qemu-kvm-core
Requires: virt-install Requires: virt-install
Requires: virt-top Requires: virt-top
Requires: python3-websockify
# A node is allowed to host servers and be a live migration target. It is not # 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. # allowed to host a database or be a DR host.
Conflicts: anvil-striker Conflicts: anvil-striker
@ -191,6 +192,7 @@ Requires: qemu-kvm
Requires: qemu-kvm-core Requires: qemu-kvm-core
Requires: virt-install Requires: virt-install
Requires: virt-top Requires: virt-top
Requires: python3-websockify
# A DR host is not allowed to be a live-migration target or host a database. # A DR host is not allowed to be a live-migration target or host a database.
Conflicts: anvil-striker Conflicts: anvil-striker
Conflicts: anvil-node Conflicts: anvil-node

@ -10,6 +10,7 @@ dist_cgibin_SCRIPTS = \
get_servers \ get_servers \
get_shared_storage \ get_shared_storage \
get_status \ get_status \
manage_vnc_pipes \
set_membership \ set_membership \
set_power \ set_power \
striker \ striker \

@ -0,0 +1,154 @@
#!/usr/bin/perl
#
# Manages VNC ports for server VMs that have VNC enabled.
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
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 get_vnc_pipe_info
{
my $parameters = shift;
my $server_uuid = $parameters->{server_uuid};
my $host_uuid = $parameters->{host_uuid};
my $vnc_pipe_info = { protocol => "ws" };
my $query = "
SELECT
ssh_tunnel_forward_port
FROM
public.vnc_pipes
WHERE
server_uuid = ".$anvil->Database->quote($server_uuid)."
AND
ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)."
;";
my $forward_port = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0];
$vnc_pipe_info->{forward_port} = $forward_port;
return $vnc_pipe_info;
}
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;
}
$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();
$anvil->Database->get_hosts();
$anvil->Database->get_anvils();
print $anvil->Template->get({ file => "shared.html", name => "json_headers", show_name => 0 })."\n";
my $response_body = {};
my $request_body;
if (defined $anvil->data->{cgi}{PUTDATA}{value})
{
my $is_decode_json_success = eval {
$request_body = decode_json($anvil->data->{cgi}{PUTDATA}{value});
};
if (not $is_decode_json_success)
{
$anvil->Log->entry({
source => $THIS_FILE,
line => __LINE__,
level => 0,
'print' => 1,
priority => "err",
key => "error_0304",
variables => { request_body_string => $anvil->data->{cgi}{PUTDATA}{value}, json_decode_error => $@ }
});
}
}
my $server_uuid = exists $request_body->{server_uuid} ? $request_body->{server_uuid} : $anvil->data->{switches}{'server-uuid'};
my $is_open = exists $request_body->{is_open} ? $request_body->{is_open} : $anvil->data->{switches}{'is-open'};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
server_uuid => $server_uuid,
is_open => $is_open
} });
if ($server_uuid)
{
my $host_uuid = $anvil->Get->host_uuid();
my $operation_string = defined $is_open ? "open" : "close";
my $open_string = defined $is_open ? $is_open : "";
my $job_uuid = $anvil->Database->insert_or_update_jobs({
job_command => $anvil->data->{path}{exe}{'striker-manage-vnc-pipes'},
job_data => "server-uuid=".$server_uuid."\nopen=".$open_string,
job_host_uuid => $host_uuid,
job_description => "job_0351,!!operation!".$operation_string."!!,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!",
job_name => "cgi-bin::manage_vnc_pipes::".$server_uuid."::".$operation_string,
job_progress => 0,
job_title => "job_0350"
});
# Wait until the job is complete before continuing.
while(is_job_incomplete({ job_uuid => $job_uuid }))
{
sleep(2);
}
if ($is_open)
{
$response_body = get_vnc_pipe_info({ server_uuid => $server_uuid, host_uuid => $host_uuid });
}
}
print JSON->new->utf8->encode($response_body)."\n";

@ -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_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_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_0312">The 'set_to' parameter: [#!variable!set_to!#] is invalid. It must be 'yes' or 'no'.</key>
<key name="error_0313">While opening VNC pipe, failed to get server VM information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#].</key>
<key name="error_0314">While opening VNC pipe, failed to get server VM VNC information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#].</key>
<key name="error_0315">While opening VNC pipe, failed to get websockify instance information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#].</key>
<key name="error_0316">While opening VNC pipe, failed to get SSH tunnel instance information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#].</key>
<key name="error_0317">While closing VNC pipe, failed to get VNC pipe information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#].</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_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_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_0320">The Anvil! UUID: [#!variable!anvil_uuid!#] is not valid or was not found in the database.</key>
@ -1116,6 +1121,8 @@ It should be provisioned in the next minute or two.</key>
<key name="job_0347">Both the BCN1 and SN1 links are working between the nodes. Checking corosync now...</key> <key name="job_0347">Both the BCN1 and SN1 links are working between the nodes. Checking corosync now...</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_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_0349">Loading the new corosync config exited with return code: [#!variable!return_code!#] and output: [#!variable!output!#]</key>
<key name="job_0350">Manage VNC Pipes</key>
<key name="job_0351">Perform VNC pipe operation [#!variable!operation!#] for server UUID [#!variable!server_uuid!#] from host UUID [#!variable!host_uuid!#].</key>
<key name="job_0352">Manage a server menu:</key> <key name="job_0352">Manage a server menu:</key>
<key name="job_0353">* Please enter the name of the server you want to manage</key> <key name="job_0353">* Please enter the name of the server you want to manage</key>
@ -2223,6 +2230,10 @@ Are you sure that you want to delete the server: [#!variable!server_name!#]? [Ty
<key name="message_0256">[ #!variable!number!# ]- #!variable!server_name!# - (Current state: [#!variable!server_state!#])</key> <key name="message_0256">[ #!variable!number!# ]- #!variable!server_name!# - (Current state: [#!variable!server_state!#])</key>
<key name="message_0257">-=] Please select the Anvil! hosting the server you want to manage [=-</key> <key name="message_0257">-=] Please select the Anvil! hosting the server you want to manage [=-</key>
<key name="message_0258">[ #!variable!number!# ]- #!variable!anvil_name!# - #!variable!anvil_description!#</key> <key name="message_0258">[ #!variable!number!# ]- #!variable!anvil_name!# - #!variable!anvil_description!#</key>
<key name="message_0259">Preparing to manage VNC pipes.</key>
<key name="message_0260">Finished [#!variable!operation!#] VNC pipe for server UUID [#!variable!server_uuid!#] from host UUID [#!variable!host_uuid!#].</key>
<key name="message_0261">Finished dropping VNC pipes table.</key>
<key name="message_0262">Finished managing VNC pipes; no operations happened because requirements not met.</key>
<!-- Success messages shown to the user --> <!-- Success messages shown to the user -->
<key name="ok_0001">Saved the mail server information successfully!</key> <key name="ok_0001">Saved the mail server information successfully!</key>

@ -39,6 +39,8 @@ dist_sbin_SCRIPTS = \
striker-initialize-host \ striker-initialize-host \
striker-manage-install-target \ striker-manage-install-target \
striker-manage-peers \ striker-manage-peers \
striker-manage-vnc-pipes \
striker-open-ssh-tunnel \
striker-parse-os-list \ striker-parse-os-list \
striker-parse-oui \ striker-parse-oui \
striker-prep-database \ striker-prep-database \

File diff suppressed because it is too large Load Diff

@ -0,0 +1,137 @@
#!/usr/bin/perl
#
# Open an SSH tunnel using the Net::OpenSSH module and keep it opened with an infinite loop.
#
# Note: this is a temporary solution to avoid directly calling the SSH command.
#
use strict;
use warnings;
use Anvil::Tools;
use Net::OpenSSH;
$| = 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();
my $ssh_fh;
sub open_ssh_tunnel
{
my $parameters = shift;
# Required parameters:
my $remote_user = $parameters->{remote_user};
my $target = $parameters->{target};
my $forward_local_port = $parameters->{forward_local_port};
my $forward_remote_port = $parameters->{forward_remote_port};
if ((not defined $remote_user)
or (not defined $target)
or (not defined $forward_local_port)
or (not defined $forward_remote_port))
{
return 1;
}
# Optional parameters:
my $port = $parameters->{port} ? $parameters->{port} : 22;
my $ssh_fh_key = $remote_user."\@".$target.":".$port;
my $query = "
SELECT anv.anvil_password
FROM hosts AS hos
JOIN anvils AS anv
ON hos.host_uuid = anv.anvil_node1_host_uuid
OR hos.host_uuid = anv.anvil_node2_host_uuid
OR hos.host_uuid = anv.anvil_dr1_host_uuid
WHERE hos.host_name = ".$anvil->Database->quote($target)."
;";
my $password = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0];
my ($output, $error, $return_code) = $anvil->Remote->call({
remote_user => $remote_user,
target => $target,
password => $password,
shell_call => $anvil->data->{path}{exe}{echo}." 1",
no_cache => 1,
});
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
error => $error,
return_code => $return_code
} });
if ($output eq "1")
{
$ssh_fh = $anvil->data->{cache}{ssh_fh}{$ssh_fh_key};
delete $anvil->data->{cache}{ssh_fh}{$ssh_fh_key};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
is_ssh_fh_defined => defined $ssh_fh ? 1 : 0
} });
}
$ssh_fh->system({ ssh_opts => [ "-O", "forward",
"-L0.0.0.0:".$forward_local_port.":0.0.0.0:".$forward_remote_port ] });
return 0;
}
sub close_ssh_tunnel
{
if (defined $ssh_fh->disconnect)
{
$ssh_fh->disconnect();
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
message => "SSH tunnel disconnected."
} });
}
$anvil->nice_exit({ exit_code => 0 });
}
$SIG->{INT} = \&close_ssh_tunnel;
$SIG->{TERM} = \&close_ssh_tunnel;
$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 });
}
if (open_ssh_tunnel({
remote_user => $anvil->data->{switches}{'remote-user'},
target => $anvil->data->{switches}{'target'},
port => $anvil->data->{switches}{'port'},
forward_local_port => $anvil->data->{switches}{'forward-local-port'},
forward_remote_port => $anvil->data->{switches}{'forward-remote-port'}
}) > 0)
{
$anvil->nice_exit({ exit_code => 1 });
}
my $is_ssh_tunnel_alive = 1;
while($is_ssh_tunnel_alive)
{
$is_ssh_tunnel_alive = $ssh_fh->test('echo');
sleep(60);
}
close_ssh_tunnel();
Loading…
Cancel
Save