From 454ab7bc8494153c1b83cddb11c9d569310e80e1 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 2 Jul 2021 20:11:52 -0400 Subject: [PATCH 01/58] fix(cgi-bin): add draft endpoint to setup VNC connection --- cgi-bin/manage_vnc_ports | 148 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100755 cgi-bin/manage_vnc_ports diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports new file mode 100755 index 00000000..14f655d3 --- /dev/null +++ b/cgi-bin/manage_vnc_ports @@ -0,0 +1,148 @@ +#!/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(); + +$anvil->Log->level({ set => 2 }); + +sub get_server_info +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $info_hash = {}; + + my $query = " +SELECT + ser.server_name, + hos.host_name +FROM + servers AS ser +JOIN + hosts AS hos +ON + ser.server_host_uuid = hos.host_uuid +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +;"; + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + + if ($count == 1) + { + my $row = $results->[0]; + my $server_name = $row->[0]; + my $host_name = $row->[1]; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + server_name => $server_name, + host_name => $host_name + } }); + + $info_hash->{server_name} = $server_name; + $info_hash->{host_name} = $host_name; + } + + return $info_hash; +} + +sub get_vnc_info +{ + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $server_name = $parameters->{server_name}; + my $port_base = 5900; + my $vnc_info = { host_name => $host_name }; + my $shell_call = "ssh root@".$host_name." \"virsh vncdisplay ".$server_name."\""; + + my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + my ($port_offset) = $shell_output =~ /:(\d+)$/; + my $port_sum = $port_base + int($port_offset); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + shell_output => $shell_output, + shell_return_code => $shell_return_code, + port_offset => $port_offset, + port_sum => $port_sum + } }); + + if ($shell_return_code == 0) + { + $vnc_info->{port} = $port_sum; + } + + return $vnc_info; +} + +$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 }); +} + +# 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 $server_info = get_server_info({ server_uuid => $server_uuid }); + my $vnc_port = get_vnc_info($server_info); +} + +print JSON->new->utf8->encode($response_body)."\n"; From 6c4f817a58bc7556d3ac36d39ab3e1d43da7aabb Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 8 Jul 2021 16:40:42 -0400 Subject: [PATCH 02/58] fix(cgi-bin): handle VMs without VNC port in get_vnc_info --- cgi-bin/manage_vnc_ports | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index 14f655d3..07b4594e 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -72,18 +72,21 @@ sub get_vnc_info my $shell_call = "ssh root@".$host_name." \"virsh vncdisplay ".$server_name."\""; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); - my ($port_offset) = $shell_output =~ /:(\d+)$/; - my $port_sum = $port_base + int($port_offset); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_output => $shell_output, - shell_return_code => $shell_return_code, - port_offset => $port_offset, - port_sum => $port_sum + shell_return_code => $shell_return_code } }); if ($shell_return_code == 0) { - $vnc_info->{port} = $port_sum; + my ($port_offset) = $shell_output =~ /:(\d+)$/; + $vnc_info->{port} = $port_base + int($port_offset); + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + port_offset => $port_offset, + vnc_port => $vnc_info->{port} + } }); } return $vnc_info; From ac6f7e38985a81243c08b875680068ac5717cde0 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 8 Jul 2021 18:42:06 -0400 Subject: [PATCH 03/58] fix(cgi-bin): add functions to create and drop vnc_pipes table --- cgi-bin/manage_vnc_ports | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index 07b4594e..b53ccdf9 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -92,6 +92,27 @@ sub get_vnc_info return $vnc_info; } +sub create_vnc_pipes_table +{ + my $query = " +CREATE TABLE IF NOT EXISTS public.vnc_pipes ( + uuid uuid not null primary key, + server_uuid uuid not null, + ws_host_uuid uuid not null, + ws_pid uuid not null, + ssh_tunnel_host_uuid uuid not null, + ssh_tunnel_pid uuid not null, + modified_date timestamp with time zone not null +);"; + my $results = $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub drop_vnc_pipes_table +{ + my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; + my $results = $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + $anvil->Get->switches; $anvil->Database->connect; @@ -146,6 +167,12 @@ if ($server_uuid) { my $server_info = get_server_info({ server_uuid => $server_uuid }); my $vnc_port = get_vnc_info($server_info); + + create_vnc_pipes_table(); +} +elsif ($anvil->data->{switches}{'drop-table'}) +{ + drop_vnc_pipes_table(); } print JSON->new->utf8->encode($response_body)."\n"; From ad01ed75ecb28f6cb8f593334259c6790d97f8d0 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 8 Jul 2021 21:32:18 -0400 Subject: [PATCH 04/58] fix(cgi-bin): add functions to insert and delete vnc pipes --- cgi-bin/manage_vnc_ports | 46 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index b53ccdf9..3bb0863f 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -104,13 +104,55 @@ CREATE TABLE IF NOT EXISTS public.vnc_pipes ( ssh_tunnel_pid uuid not null, modified_date timestamp with time zone not null );"; - my $results = $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); } sub drop_vnc_pipes_table { my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; - my $results = $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub insert_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_pid = $parameters->{ws_pid}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + + my $query = " +INSERT INTO public.vnc_pipes ( + server_uuid, ws_host_uuid, ws_pid, ssh_tunnel_host_uuid, ssh_tunnel_pid +) VALUES ( + ".$anvil->Database->quote($server_uuid).", + ".$anvil->Database->quote($ws_host_uuid).", + ".$anvil->Database->quote($ws_pid).", + ".$anvil->Database->quote($ssh_tunnel_host_uuid).", + ".$anvil->Database->quote($ssh_tunnel_pid)." +);"; + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub delete_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + + my $query = " +DELETE FROM public.vnc_pipes +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +AND + ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." +;"; + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); } $anvil->Get->switches; From 39ecca7eaf95ca0473611830ec5e244de8889b34 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 8 Jul 2021 21:32:54 -0400 Subject: [PATCH 05/58] fix(cgi-bin): draft function to setup websockify --- cgi-bin/manage_vnc_ports | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index 3bb0863f..3c6d25fc 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -92,6 +92,18 @@ sub get_vnc_info return $vnc_info; } +sub start_websockify +{ + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $target_port = $parameters->{target_port}; + my $source_port_base = 10000; + my $source_port = $source_port_base + $target_port; + my $shell_call = "ssh root@".$host_name." \"websockify ".$source_port." :".$target_port." & echo \$!\""; + + my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); +} + sub create_vnc_pipes_table { my $query = " From df33c2d1939b20a225f3a3d5b5ff80273a67890b Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 9 Jul 2021 20:09:46 -0400 Subject: [PATCH 06/58] fix(cgi-bin): draft logic for websockify setup and clean up --- cgi-bin/manage_vnc_ports | 183 ++++++++++++++++++++++++++++++++++----- 1 file changed, 163 insertions(+), 20 deletions(-) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index 3c6d25fc..e933acb6 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -31,15 +31,16 @@ sub get_server_info my $query = " SELECT ser.server_name, - hos.host_name + hos.host_name, + hos.host_uuid FROM - servers AS ser + public.servers AS ser JOIN - hosts AS hos + public.hosts AS hos ON ser.server_host_uuid = hos.host_uuid WHERE - server_uuid = ".$anvil->Database->quote($server_uuid)." + server_uuid = ".$anvil->Database->quote($server_uuid)." ;"; my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); my $count = @{$results}; @@ -49,14 +50,17 @@ WHERE my $row = $results->[0]; my $server_name = $row->[0]; my $host_name = $row->[1]; + my $host_uuid = $row->[2]; $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { server_name => $server_name, - host_name => $host_name + host_name => $host_name, + host_uuid => $host_uuid } }); $info_hash->{server_name} = $server_name; $info_hash->{host_name} = $host_name; + $info_hash->{host_uuid} = $host_uuid; } return $info_hash; @@ -69,10 +73,10 @@ sub get_vnc_info my $server_name = $parameters->{server_name}; my $port_base = 5900; my $vnc_info = { host_name => $host_name }; - my $shell_call = "ssh root@".$host_name." \"virsh vncdisplay ".$server_name."\""; + # Requires root to access VM information. + my $shell_call = "ssh -n root@".$host_name." \"virsh vncdisplay ".$server_name."\""; 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_output => $shell_output, shell_return_code => $shell_return_code @@ -92,16 +96,126 @@ sub get_vnc_info return $vnc_info; } -sub start_websockify +sub is_websockify_process { - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $target_port = $parameters->{target_port}; - my $source_port_base = 10000; - my $source_port = $source_port_base + $target_port; - my $shell_call = "ssh root@".$host_name." \"websockify ".$source_port." :".$target_port." & echo \$!\""; + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $ws_pid = $parameters->{ws_pid}; + my $shell_call = "ssh -n ".$host_name." \"ps -o comm -h -p ".$ws_pid."\""; 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_output => $shell_output, + shell_return_code => $shell_return_code + } }); + + return $shell_output eq "websockify" ? 1 : 0; +} + +sub is_websockify_exists +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $server_vnc_port = $parameters->{server_vnc_port}; + + my $query = " +SELECT + vnc.server_vnc_port, hos.host_name, vnc.ws_pid +FROM + public.vnc_pipes AS vnc +JOIN + public.hosts AS hos +ON + vnc.ws_host_uuid = hos.host_uuid +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + my $is_exists = 0; + + if ($count > 1) + { + my $row = $results->[0]; + my $server_vnc_port_in_record = $row->[0]; + my $host_name = $row->[1]; + my $ws_pid = $row->[2]; + my $clean_up_parameters = { host_name => $host_name, ws_pid => $ws_pid }; + + if ($server_vnc_port != $server_vnc_port_in_record) + { + # VNC server port mismatch/oudated; require clean up. + + stop_websockify($clean_up_parameters); + + return $is_exists; + } + + if (not is_websockify_process($clean_up_parameters)) + { + # Process died; require clean up. + return $is_exists; + } + + # Passed all tests; process considered exists. + $is_exists = 1; + } + + return $is_exists; +} + +sub start_websockify +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_name = $parameters->{host_name}; + my $target_port = $parameters->{target_port}; + my $ws_info; + + if (not is_websockify_exists({ server_uuid => $server_uuid, server_vnc_port => $target_port })) + { + my $source_port_base = 10000; + my $source_port = $source_port_base + $target_port; + my $shell_call = "ssh -n ".$host_name." 'websockify ".$source_port." :".$target_port." & echo pid:\$!'"; + + 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_output => $shell_output, + shell_return_code => $shell_return_code + } }); + + if ($shell_return_code == 0) + { + my ($ws_pid) = $shell_output =~ /^pid:(\d+)$/; + + $ws_info = {}; + $ws_info->{pid} = $ws_pid; + $ws_info->{source_port} = $source_port; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ws_pid => $ws_pid } }); + } + } + + return $ws_info; +} + +sub stop_websockify +{ + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $ws_pid = $parameters->{ws_pid}; + + if (is_websockify_process($parameters)) + { + my $shell_call = "ssh -n ".$host_name." \"kill -9 ".$ws_pid."\""; + + 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_output => $shell_output, + shell_return_code => $shell_return_code + } }); + } } sub create_vnc_pipes_table @@ -110,10 +224,11 @@ sub create_vnc_pipes_table CREATE TABLE IF NOT EXISTS public.vnc_pipes ( uuid uuid not null primary key, server_uuid uuid not null, + server_vnc_port numeric not null, ws_host_uuid uuid not null, - ws_pid uuid not null, + ws_pid numeric not null, ssh_tunnel_host_uuid uuid not null, - ssh_tunnel_pid uuid not null, + ssh_tunnel_pid numeric not null, modified_date timestamp with time zone not null );"; @@ -131,6 +246,7 @@ sub insert_vnc_pipe { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; + my $server_vnc_port = $parameters->{server_vnc_port}; my $ws_host_uuid = $parameters->{ws_host_uuid}; my $ws_pid = $parameters->{ws_pid}; my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; @@ -138,7 +254,7 @@ sub insert_vnc_pipe my $query = " INSERT INTO public.vnc_pipes ( - server_uuid, ws_host_uuid, ws_pid, ssh_tunnel_host_uuid, ssh_tunnel_pid + server_uuid, server_vnc_port, ws_host_uuid, ws_pid, ssh_tunnel_host_uuid, ssh_tunnel_pid ) VALUES ( ".$anvil->Database->quote($server_uuid).", ".$anvil->Database->quote($ws_host_uuid).", @@ -150,6 +266,24 @@ INSERT INTO public.vnc_pipes ( $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); } +sub get_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + + my $query = " +SELECT + ws_host_uuid, ws_pid, ssh_tunnel_pid +FROM + public.vnc_pipes +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +AND + ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." +;"; +} + sub delete_vnc_pipe { my $parameters = shift; @@ -219,10 +353,19 @@ $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, lis if ($server_uuid) { - my $server_info = get_server_info({ server_uuid => $server_uuid }); - my $vnc_port = get_vnc_info($server_info); + if ($is_open) + { + my $server_info = get_server_info({ server_uuid => $server_uuid }); + my $vnc_info = get_vnc_info($server_info); - create_vnc_pipes_table(); + create_vnc_pipes_table(); + + my $ws_info = start_websockify({ server_uuid => $server_uuid, host_name => $server_info->{host_name}, target_port => $vnc_info->{port} }); + } + else + { + # TODO: fill-in clean up section. + } } elsif ($anvil->data->{switches}{'drop-table'}) { From 9603974c24ed43a93449e550e860e373130a1515 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 12 Jul 2021 13:50:59 -0400 Subject: [PATCH 07/58] fix(cgi-bin): complete draft for clean up --- cgi-bin/manage_vnc_ports | 85 ++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index e933acb6..66bd5aa5 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -112,6 +112,21 @@ sub is_websockify_process return $shell_output eq "websockify" ? 1 : 0; } +sub is_ssh_process +{ + my $parameters = shift; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $shell_call = "ps -o comm -h -p ".$ssh_tunnel_pid; + + 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_output => $shell_output, + shell_return_code => $shell_return_code + } }); + + return $shell_output eq "ssh" ? 1 : 0; +} + sub is_websockify_exists { my $parameters = shift; @@ -218,6 +233,23 @@ sub stop_websockify } } +sub stop_ssh_tunnel +{ + my $parameters = shift; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + + if (is_ssh_process($parameters)) + { + my $shell_call = "kill -9 ".$ssh_tunnel_pid; + + 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_output => $shell_output, + shell_return_code => $shell_return_code + } }); + } +} + sub create_vnc_pipes_table { my $query = " @@ -268,34 +300,60 @@ INSERT INTO public.vnc_pipes ( sub get_vnc_pipe { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $vnc_pipe_info; my $query = " SELECT - ws_host_uuid, ws_pid, ssh_tunnel_pid + hos.host_name, vnc.ws_pid, vnc.ssh_tunnel_pid FROM - public.vnc_pipes + public.vnc_pipes AS vnc +JOIN + public.hosts AS hos +ON + vnc.ws_host_uuid = hos.host_uuid WHERE server_uuid = ".$anvil->Database->quote($server_uuid)." AND - ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." + ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." ;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + + if ($count == 1) + { + my $row = $results->[0]; + + $vnc_pipe_info = {}; + $vnc_pipe_info->{host_name} = $row->[0]; + $vnc_pipe_info->{ws_pid} = $row->[1]; + $vnc_pipe_info->{ssh_tunnel_pid} = $row->[2]; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + host_name => $vnc_pipe_info->{host_name}, + ws_pid => $vnc_pipe_info->{ws_pid}, + ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} + } }); + } + + return $vnc_pipe_info; } sub delete_vnc_pipe { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; my $query = " DELETE FROM public.vnc_pipes WHERE server_uuid = ".$anvil->Database->quote($server_uuid)." AND - ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." + ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." ;"; $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); @@ -364,7 +422,12 @@ if ($server_uuid) } else { - # TODO: fill-in clean up section. + my $vnc_pipe_parameters = { server_uuid => $server_uuid, host_uuid => $anvil->Get->host_uuid() }; + my $vnc_pipe_info = get_vnc_pipe($vnc_pipe_parameters); + + stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); + stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); + delete_vnc_pipe($vnc_pipe_parameters); } } elsif ($anvil->data->{switches}{'drop-table'}) From 154b4a7e152e98886982419cddcf83e0c4cfc0fd Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 12 Jul 2021 14:29:09 -0400 Subject: [PATCH 08/58] fix(cgi-bin): default function return to undefined --- cgi-bin/manage_vnc_ports | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index 66bd5aa5..f86e7f94 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -26,13 +26,11 @@ sub get_server_info { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; - my $info_hash = {}; + my $server_info; my $query = " SELECT - ser.server_name, - hos.host_name, - hos.host_uuid + ser.server_name, hos.host_name, hos.host_uuid FROM public.servers AS ser JOIN @@ -47,23 +45,21 @@ WHERE if ($count == 1) { - my $row = $results->[0]; - my $server_name = $row->[0]; - my $host_name = $row->[1]; - my $host_uuid = $row->[2]; + my $row = $results->[0]; + + $server_info = {}; + $server_info->{server_name} = $row->[0]; + $server_info->{host_name} = $row->[1]; + $server_info->{host_uuid} = $row->[2]; $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - server_name => $server_name, - host_name => $host_name, - host_uuid => $host_uuid + server_name => $server_info->{server_name}, + host_name => $server_info->{host_name}, + host_uuid => $server_info->{host_uuid} } }); - - $info_hash->{server_name} = $server_name; - $info_hash->{host_name} = $host_name; - $info_hash->{host_uuid} = $host_uuid; } - return $info_hash; + return $server_info; } sub get_vnc_info @@ -72,9 +68,9 @@ sub get_vnc_info my $host_name = $parameters->{host_name}; my $server_name = $parameters->{server_name}; my $port_base = 5900; - my $vnc_info = { host_name => $host_name }; # Requires root to access VM information. my $shell_call = "ssh -n root@".$host_name." \"virsh vncdisplay ".$server_name."\""; + my $vnc_info; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -85,6 +81,8 @@ sub get_vnc_info if ($shell_return_code == 0) { my ($port_offset) = $shell_output =~ /:(\d+)$/; + + $vnc_info = { host_name => $host_name }; $vnc_info->{port} = $port_base + int($port_offset); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { From 5208332b2d987b526ea2abdd471f734993b5a180 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 12 Jul 2021 19:53:00 -0400 Subject: [PATCH 09/58] fix(cgi-bin): complete draft of open and close VNC pipes --- cgi-bin/manage_vnc_ports | 327 ++++++++++++++++++++++++++++++++++----- 1 file changed, 284 insertions(+), 43 deletions(-) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_ports index f86e7f94..846111c8 100755 --- a/cgi-bin/manage_vnc_ports +++ b/cgi-bin/manage_vnc_ports @@ -133,7 +133,7 @@ sub is_websockify_exists my $query = " SELECT - vnc.server_vnc_port, hos.host_name, vnc.ws_pid + vnc.server_vnc_port, hos.host_name, vnc.ws_pid, vnc.ssh_tunnel_forward_port FROM public.vnc_pipes AS vnc JOIN @@ -141,19 +141,20 @@ JOIN ON vnc.ws_host_uuid = hos.host_uuid WHERE - server_uuid = ".$anvil->Database->quote($server_uuid)." + vnc.server_uuid = ".$anvil->Database->quote($server_uuid)." ;"; my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); my $count = @{$results}; - my $is_exists = 0; + my $existing_websockify; - if ($count > 1) + if ($count > 0) { my $row = $results->[0]; my $server_vnc_port_in_record = $row->[0]; my $host_name = $row->[1]; my $ws_pid = $row->[2]; + my $ssh_tunnel_forward_port = $row->[3]; my $clean_up_parameters = { host_name => $host_name, ws_pid => $ws_pid }; if ($server_vnc_port != $server_vnc_port_in_record) @@ -161,32 +162,89 @@ WHERE # VNC server port mismatch/oudated; require clean up. stop_websockify($clean_up_parameters); + stop_related($clean_up_parameters); + delete_vnc_pipe($clean_up_parameters); - return $is_exists; + return; } if (not is_websockify_process($clean_up_parameters)) { # Process died; require clean up. - return $is_exists; + + stop_related($clean_up_parameters); + delete_vnc_pipe($clean_up_parameters); + + return; } # Passed all tests; process considered exists. - $is_exists = 1; + $existing_websockify = { ws_pid => $ws_pid, ssh_tunnel_forward_port => $ssh_tunnel_forward_port }; + } + + return $existing_websockify; +} + +sub is_ssh_tunnel_exists +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + + my $query = " +SELECT + ssh_tunnel_pid +FROM + public.vnc_pipes +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +AND + ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." +;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + my $existing_ssh_tunnel; + + if ($count == 1) + { + my $row = $results->[0]; + my $ssh_tunnel_pid = $row->[0]; + my $clean_up_parameters = { ssh_tunnel_pid => $ssh_tunnel_pid }; + + if (not is_ssh_process($clean_up_parameters)) + { + # Process died; require clean up. + + stop_related($clean_up_parameters); + delete_vnc_pipe($clean_up_parameters); + + return; + } + + # Passed all tests; tunnel considered exists. + $existing_ssh_tunnel = { ssh_tunnel_pid => $ssh_tunnel_pid }; } - return $is_exists; + return $existing_ssh_tunnel; } sub start_websockify { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_name = $parameters->{host_name}; - my $target_port = $parameters->{target_port}; - my $ws_info; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_name = $parameters->{host_name}; + my $target_port = $parameters->{target_port}; + my $ws_info = {}; - if (not is_websockify_exists({ server_uuid => $server_uuid, server_vnc_port => $target_port })) + my $existing_websockify = is_websockify_exists({ server_uuid => $server_uuid, server_vnc_port => $target_port }); + + if (defined $existing_websockify) + { + $ws_info->{pid} = $existing_websockify->{ws_pid}; + $ws_info->{source_port} = $existing_websockify->{ssh_tunnel_forward_port}; + } + else { my $source_port_base = 10000; my $source_port = $source_port_base + $target_port; @@ -200,13 +258,15 @@ sub start_websockify if ($shell_return_code == 0) { - my ($ws_pid) = $shell_output =~ /^pid:(\d+)$/; + my ($ws_pid) = $shell_output =~ /pid:(\d+)$/; - $ws_info = {}; $ws_info->{pid} = $ws_pid; $ws_info->{source_port} = $source_port; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ws_pid => $ws_pid } }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + ws_pid => $ws_pid, + ws_source_port => $source_port + } }); } } @@ -231,15 +291,58 @@ sub stop_websockify } } +sub start_ssh_tunnel +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_name = $parameters->{host_name}; + my $ws_source_port = $parameters->{ws_source_port}; + my $ssh_tunnel_info = {}; + + my $existing_ssh_tunnel = is_ssh_tunnel_exists({ server_uuid => $server_uuid }); + + if (defined $existing_ssh_tunnel) + { + $ssh_tunnel_info->{pid} = $existing_ssh_tunnel->{ssh_tunnel_pid}; + } + else + { + my $shell_call = "ssh -nN -L ".$ws_source_port.":localhost:".$ws_source_port." ".$host_name." & echo pid:\$!"; + + 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_output => $shell_output, + shell_return_code => $shell_return_code + } }); + + if ($shell_return_code == 0) + { + my ($ssh_tunnel_pid) = $shell_output =~ /pid:(\d+)$/; + + $ssh_tunnel_info->{pid} = $ssh_tunnel_pid; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ssh_tunnel_pid => $ssh_tunnel_pid } }); + } + } + + return $ssh_tunnel_info; +} + sub stop_ssh_tunnel { my $parameters = shift; + my $host_name = $parameters->{host_name}; my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; if (is_ssh_process($parameters)) { my $shell_call = "kill -9 ".$ssh_tunnel_pid; + if (defined $host_name) + { + $shell_call = "ssh -n ".$host_name." \"".$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_output => $shell_output, @@ -248,6 +351,52 @@ sub stop_ssh_tunnel } } +sub stop_related +{ + my $parameters = shift; + my $ws_pid = $parameters->{ws_pid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $select_pid_field; + my $condition_pid_field; + my $condition_pid_value; + my $stop_function; + + if (defined $ws_pid) + { + $select_pid_field = "ssh_tunnel_pid"; + $condition_pid_field = "ws_pid"; + $condition_pid_value = $ws_pid; + $stop_function = \&stop_ssh_tunnel + } + elsif (defined $ssh_tunnel_pid) + { + $select_pid_field = "ws_pid"; + $condition_pid_field = "ssh_tunnel_pid"; + $condition_pid_value = $ssh_tunnel_pid; + $stop_function = \&stop_websockify + } + + my $query = " +SELECT + hos.host_name, vnc.".$select_pid_field." +FROM + public.vnc_pipes AS vnc +JOIN + public.hosts AS hos +ON + vnc.ssh_tunnel_host_uuid = hos.host_uuid +WHERE + vnc.".$condition_pid_field." = ".$anvil->Database->quote($condition_pid_value)." +;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + + foreach my $row (@{$results}) + { + $stop_function->({ host_name => $row->[0], ws_pid => $row->[1], ssh_tunnel_pid => $row->[1] }); + } +} + sub create_vnc_pipes_table { my $query = " @@ -259,6 +408,7 @@ CREATE TABLE IF NOT EXISTS public.vnc_pipes ( ws_pid numeric not null, ssh_tunnel_host_uuid uuid not null, ssh_tunnel_pid numeric not null, + ssh_tunnel_forward_port numeric not null, modified_date timestamp with time zone not null );"; @@ -274,23 +424,32 @@ sub drop_vnc_pipes_table sub insert_vnc_pipe { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $server_vnc_port = $parameters->{server_vnc_port}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; - my $ws_pid = $parameters->{ws_pid}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $server_vnc_port = $parameters->{server_vnc_port}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_pid = $parameters->{ws_pid}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; my $query = " INSERT INTO public.vnc_pipes ( - server_uuid, server_vnc_port, ws_host_uuid, ws_pid, ssh_tunnel_host_uuid, ssh_tunnel_pid + server_uuid, + server_vnc_port, + ws_host_uuid, + ws_pid, + ssh_tunnel_host_uuid, + ssh_tunnel_pid, + ssh_tunnel_forward_port ) VALUES ( ".$anvil->Database->quote($server_uuid).", + ".$anvil->Database->quote($server_vnc_port).", ".$anvil->Database->quote($ws_host_uuid).", ".$anvil->Database->quote($ws_pid).", ".$anvil->Database->quote($ssh_tunnel_host_uuid).", - ".$anvil->Database->quote($ssh_tunnel_pid)." + ".$anvil->Database->quote($ssh_tunnel_pid).", + ".$anvil->Database->quote($ssh_tunnel_forward_port)." );"; $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); @@ -342,21 +501,113 @@ AND sub delete_vnc_pipe { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $parameters->{host_uuid}; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $ws_pid = $parameters->{ws_pid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $query = " -DELETE FROM public.vnc_pipes + my $query = "DELETE FROM public.vnc_pipes "; + + if (defined $ws_pid) + { + $query = $query."WHERE ws_pid = ".$anvil->Database->quote($ws_pid).";"; + } + elsif (defined $ssh_tunnel_pid) + { + $query = $query."WHERE ssh_tunnel_pid = ".$anvil->Database->quote($ssh_tunnel_pid).";"; + } + else + { + $query = $query." WHERE server_uuid = ".$anvil->Database->quote($server_uuid)." AND ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." ;"; + } $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); } +sub open_vnc_pipe +{ + create_vnc_pipes_table(); + + my $server_info = get_server_info({ server_uuid => $server_uuid }); + + if (not defined $server_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get server VM information." + } }); + + return; + } + + my $vnc_info = get_vnc_info($server_info); + + if (not defined $vnc_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get server VM VNC information." + } }); + + return; + } + + my $ws_info = start_websockify({ server_uuid => $server_uuid, host_name => $server_info->{host_name}, target_port => $vnc_info->{port} }); + + if (not defined $ws_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get websockify instance information." + } }); + + return; + } + + my $ssh_tunnel_info = start_ssh_tunnel({ server_uuid => $server_uuid, host_name => $server_info->{host_name}, ws_source_port => $ws_info->{source_port} }); + + if (not defined $ssh_tunnel_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get SSH tunnel instance information." + } }); + + return; + } + + insert_vnc_pipe({ + server_uuid => $server_uuid, + server_vnc_port => $vnc_info->{port}, + ws_host_uuid => $server_info->{host_uuid}, + ws_pid => $ws_info->{pid}, + ssh_tunnel_host_uuid => $anvil->Get->host_uuid(), + ssh_tunnel_pid => $ssh_tunnel_info->{pid}, + ssh_tunnel_forward_port => $ws_info->{source_port} + }); +} + +sub close_vnc_pipe +{ + my $vnc_pipe_parameters = { server_uuid => $server_uuid, host_uuid => $anvil->Get->host_uuid() }; + my $vnc_pipe_info = get_vnc_pipe($vnc_pipe_parameters); + + if (not defined $vnc_pipe_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get VNC pipe information." + } }); + + return; + } + + stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); + stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); + delete_vnc_pipe($vnc_pipe_parameters); +} + $anvil->Get->switches; $anvil->Database->connect; @@ -411,21 +662,11 @@ if ($server_uuid) { if ($is_open) { - my $server_info = get_server_info({ server_uuid => $server_uuid }); - my $vnc_info = get_vnc_info($server_info); - - create_vnc_pipes_table(); - - my $ws_info = start_websockify({ server_uuid => $server_uuid, host_name => $server_info->{host_name}, target_port => $vnc_info->{port} }); + open_vnc_pipe(); } else { - my $vnc_pipe_parameters = { server_uuid => $server_uuid, host_uuid => $anvil->Get->host_uuid() }; - my $vnc_pipe_info = get_vnc_pipe($vnc_pipe_parameters); - - stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); - stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); - delete_vnc_pipe($vnc_pipe_parameters); + close_vnc_pipe(); } } elsif ($anvil->data->{switches}{'drop-table'}) From 6ba6819078daf974c80ba6713ac9881fbf7e2faa Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 10:27:39 -0400 Subject: [PATCH 10/58] chore(cgi-bin): rename manage_vnc_ports->manage_vnc_pipes --- cgi-bin/{manage_vnc_ports => manage_vnc_pipes} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cgi-bin/{manage_vnc_ports => manage_vnc_pipes} (100%) diff --git a/cgi-bin/manage_vnc_ports b/cgi-bin/manage_vnc_pipes similarity index 100% rename from cgi-bin/manage_vnc_ports rename to cgi-bin/manage_vnc_pipes From 3adbd8bca126a6bb646b276b0b292acb0a0e47e2 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 11:36:18 -0400 Subject: [PATCH 11/58] fix(cgi-bin): add missing server_uuid variables --- cgi-bin/manage_vnc_pipes | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 846111c8..557e52a5 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -417,7 +417,7 @@ CREATE TABLE IF NOT EXISTS public.vnc_pipes ( sub drop_vnc_pipes_table { - my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; + my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); } @@ -532,7 +532,8 @@ AND sub open_vnc_pipe { - create_vnc_pipes_table(); + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; my $server_info = get_server_info({ server_uuid => $server_uuid }); @@ -591,6 +592,8 @@ sub open_vnc_pipe sub close_vnc_pipe { + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; my $vnc_pipe_parameters = { server_uuid => $server_uuid, host_uuid => $anvil->Get->host_uuid() }; my $vnc_pipe_info = get_vnc_pipe($vnc_pipe_parameters); @@ -660,13 +663,17 @@ $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, lis if ($server_uuid) { + create_vnc_pipes_table(); + + my $vnc_pipe_parameters = { server_uuid => $server_uuid }; + if ($is_open) { - open_vnc_pipe(); + open_vnc_pipe($vnc_pipe_parameters); } else { - close_vnc_pipe(); + close_vnc_pipe($vnc_pipe_parameters); } } elsif ($anvil->data->{switches}{'drop-table'}) From 0f4198155fcd044ef18f8dd616fbdbc67798bb8a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 12:26:27 -0400 Subject: [PATCH 12/58] fix(cgi-bin): prevent start-websockify-in-background from hanging --- cgi-bin/manage_vnc_pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 557e52a5..8b7fc4ab 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -248,7 +248,7 @@ sub start_websockify { my $source_port_base = 10000; my $source_port = $source_port_base + $target_port; - my $shell_call = "ssh -n ".$host_name." 'websockify ".$source_port." :".$target_port." & echo pid:\$!'"; + my $shell_call = "ssh -n ".$host_name." 'websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!'"; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { From 424fdd70ddf4795d339b14bced6b6b1b7e9e40aa Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 12:53:36 -0400 Subject: [PATCH 13/58] fix(cgi-bin): try SIGTERM before SIGKILL when stopping processes --- cgi-bin/manage_vnc_pipes | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 8b7fc4ab..56415bf8 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -281,13 +281,28 @@ sub stop_websockify if (is_websockify_process($parameters)) { - my $shell_call = "ssh -n ".$host_name." \"kill -9 ".$ws_pid."\""; + my $shell_call = "ssh -n ".$host_name." \"kill ".$ws_pid."\""; + my $shell_output; + my $shell_return_code; - my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_output => $shell_output, shell_return_code => $shell_return_code } }); + + sleep(2); + + if (is_websockify_process($parameters)) + { + $shell_call = $shell_call =~ s/kill/kill -9/; + + ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + shell_output => $shell_output, + shell_return_code => $shell_return_code + } }); + } } } @@ -336,18 +351,33 @@ sub stop_ssh_tunnel if (is_ssh_process($parameters)) { - my $shell_call = "kill -9 ".$ssh_tunnel_pid; + my $shell_call = "kill ".$ssh_tunnel_pid; + my $shell_output; + my $shell_return_code; if (defined $host_name) { $shell_call = "ssh -n ".$host_name." \"".$shell_call."\""; } - my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_output => $shell_output, shell_return_code => $shell_return_code } }); + + sleep(2); + + if (is_ssh_process($parameters)) + { + $shell_call = $shell_call =~ s/kill/kill -9/; + + ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + shell_output => $shell_output, + shell_return_code => $shell_return_code + } }); + } } } From d2b43f1d5926d73f8fabfe57cd91df9be01b6bc1 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 13:39:02 -0400 Subject: [PATCH 14/58] fix(cgi-bin): add missing host_uuid param to start SSH tunnel --- cgi-bin/manage_vnc_pipes | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 56415bf8..830a28f3 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -235,12 +235,13 @@ sub start_websockify my $server_uuid = $parameters->{server_uuid}; my $host_name = $parameters->{host_name}; my $target_port = $parameters->{target_port}; - my $ws_info = {}; + my $ws_info; my $existing_websockify = is_websockify_exists({ server_uuid => $server_uuid, server_vnc_port => $target_port }); if (defined $existing_websockify) { + $ws_info = {}; $ws_info->{pid} = $existing_websockify->{ws_pid}; $ws_info->{source_port} = $existing_websockify->{ssh_tunnel_forward_port}; } @@ -260,6 +261,7 @@ sub start_websockify { my ($ws_pid) = $shell_output =~ /pid:(\d+)$/; + $ws_info = {}; $ws_info->{pid} = $ws_pid; $ws_info->{source_port} = $source_port; @@ -310,19 +312,21 @@ sub start_ssh_tunnel { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; - my $host_name = $parameters->{host_name}; + my $host_uuid = $parameters->{host_uuid}; + my $ws_host_name = $parameters->{ws_host_name}; my $ws_source_port = $parameters->{ws_source_port}; - my $ssh_tunnel_info = {}; + my $ssh_tunnel_info; - my $existing_ssh_tunnel = is_ssh_tunnel_exists({ server_uuid => $server_uuid }); + my $existing_ssh_tunnel = is_ssh_tunnel_exists({ server_uuid => $server_uuid, host_uuid => $host_uuid }); if (defined $existing_ssh_tunnel) { + $ssh_tunnel_info = {}; $ssh_tunnel_info->{pid} = $existing_ssh_tunnel->{ssh_tunnel_pid}; } else { - my $shell_call = "ssh -nN -L ".$ws_source_port.":localhost:".$ws_source_port." ".$host_name." & echo pid:\$!"; + my $shell_call = "ssh -nN -L ".$ws_source_port.":localhost:".$ws_source_port." ".$ws_host_name." & echo pid:\$!"; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -334,6 +338,7 @@ sub start_ssh_tunnel { my ($ssh_tunnel_pid) = $shell_output =~ /pid:(\d+)$/; + $ssh_tunnel_info = {}; $ssh_tunnel_info->{pid} = $ssh_tunnel_pid; $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ssh_tunnel_pid => $ssh_tunnel_pid } }); @@ -564,6 +569,7 @@ sub open_vnc_pipe { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $anvil->Get->host_uuid(); my $server_info = get_server_info({ server_uuid => $server_uuid }); @@ -598,7 +604,12 @@ sub open_vnc_pipe return; } - my $ssh_tunnel_info = start_ssh_tunnel({ server_uuid => $server_uuid, host_name => $server_info->{host_name}, ws_source_port => $ws_info->{source_port} }); + my $ssh_tunnel_info = start_ssh_tunnel({ + server_uuid => $server_uuid, + host_uuid => $host_uuid, + ws_host_name => $server_info->{host_name}, + ws_source_port => $ws_info->{source_port} + }); if (not defined $ssh_tunnel_info) { @@ -614,7 +625,7 @@ sub open_vnc_pipe server_vnc_port => $vnc_info->{port}, ws_host_uuid => $server_info->{host_uuid}, ws_pid => $ws_info->{pid}, - ssh_tunnel_host_uuid => $anvil->Get->host_uuid(), + ssh_tunnel_host_uuid => $host_uuid, ssh_tunnel_pid => $ssh_tunnel_info->{pid}, ssh_tunnel_forward_port => $ws_info->{source_port} }); From 699400a62c21b37e883b755ceac35bc9c7f126f5 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 13:54:37 -0400 Subject: [PATCH 15/58] fix(cgi-bin): add missing field when recording VNC pipe --- cgi-bin/manage_vnc_pipes | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 830a28f3..bd83da84 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -468,23 +468,30 @@ sub insert_vnc_pipe my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; + my $record_uuid = $anvil->Get->uuid(); + my $record_modified_date = $anvil->Database->refresh_timestamp(); + my $query = " INSERT INTO public.vnc_pipes ( + uuid, server_uuid, server_vnc_port, ws_host_uuid, ws_pid, ssh_tunnel_host_uuid, ssh_tunnel_pid, - ssh_tunnel_forward_port + ssh_tunnel_forward_port, + modified_date ) VALUES ( + ".$anvil->Database->quote($record_uuid).", ".$anvil->Database->quote($server_uuid).", ".$anvil->Database->quote($server_vnc_port).", ".$anvil->Database->quote($ws_host_uuid).", ".$anvil->Database->quote($ws_pid).", ".$anvil->Database->quote($ssh_tunnel_host_uuid).", ".$anvil->Database->quote($ssh_tunnel_pid).", - ".$anvil->Database->quote($ssh_tunnel_forward_port)." + ".$anvil->Database->quote($ssh_tunnel_forward_port).", + ".$anvil->Database->quote($record_modified_date)." );"; $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); From 6711b6515162d9fb965cb943e6541138015938c7 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 14:08:06 -0400 Subject: [PATCH 16/58] fix(cgi-bin): include command when logging shell calls --- cgi-bin/manage_vnc_pipes | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index bd83da84..126dbcba 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -74,6 +74,7 @@ sub get_vnc_info 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 } }); @@ -103,6 +104,7 @@ sub is_websockify_process 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 } }); @@ -118,6 +120,7 @@ sub is_ssh_process 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 } }); @@ -253,6 +256,7 @@ sub start_websockify 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 } }); @@ -289,6 +293,7 @@ sub stop_websockify ($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 } }); @@ -301,6 +306,7 @@ sub stop_websockify ($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 } }); @@ -330,6 +336,7 @@ sub start_ssh_tunnel 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 } }); @@ -367,6 +374,7 @@ sub stop_ssh_tunnel ($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 } }); @@ -379,6 +387,7 @@ sub stop_ssh_tunnel ($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 } }); From ef27e301600b17423d3f12de6e551fc39aaa87c7 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 14:18:00 -0400 Subject: [PATCH 17/58] fix(cgi-bin): don't insert when using existing pipe --- cgi-bin/manage_vnc_pipes | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 126dbcba..8d81574a 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -247,6 +247,7 @@ sub start_websockify $ws_info = {}; $ws_info->{pid} = $existing_websockify->{ws_pid}; $ws_info->{source_port} = $existing_websockify->{ssh_tunnel_forward_port}; + $ws_info->{is_existing} = 1; } else { @@ -327,8 +328,9 @@ sub start_ssh_tunnel if (defined $existing_ssh_tunnel) { - $ssh_tunnel_info = {}; - $ssh_tunnel_info->{pid} = $existing_ssh_tunnel->{ssh_tunnel_pid}; + $ssh_tunnel_info = {}; + $ssh_tunnel_info->{pid} = $existing_ssh_tunnel->{ssh_tunnel_pid}; + $ssh_tunnel_info->{is_existing} = 1; } else { @@ -636,15 +638,18 @@ sub open_vnc_pipe return; } - insert_vnc_pipe({ - server_uuid => $server_uuid, - server_vnc_port => $vnc_info->{port}, - ws_host_uuid => $server_info->{host_uuid}, - ws_pid => $ws_info->{pid}, - ssh_tunnel_host_uuid => $host_uuid, - ssh_tunnel_pid => $ssh_tunnel_info->{pid}, - ssh_tunnel_forward_port => $ws_info->{source_port} - }); + if (not $ws_info->{is_existing} or not $ssh_tunnel_info->{is_existing}) + { + insert_vnc_pipe({ + server_uuid => $server_uuid, + server_vnc_port => $vnc_info->{port}, + ws_host_uuid => $server_info->{host_uuid}, + ws_pid => $ws_info->{pid}, + ssh_tunnel_host_uuid => $host_uuid, + ssh_tunnel_pid => $ssh_tunnel_info->{pid}, + ssh_tunnel_forward_port => $ws_info->{source_port} + }); + } } sub close_vnc_pipe From dfa399041745a17445e9c7c923b425edb5f594ea Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 14:26:30 -0400 Subject: [PATCH 18/58] fix(cgi-bin): handle kill SSH tunnel on peer striker --- cgi-bin/manage_vnc_pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 8d81574a..5b0efdfe 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -369,7 +369,7 @@ sub stop_ssh_tunnel my $shell_output; my $shell_return_code; - if (defined $host_name) + if ((defined $host_name) and ($host_name ne $anvil->Get->host_name())) { $shell_call = "ssh -n ".$host_name." \"".$shell_call."\""; } From 7f8532dbb945999c078f7692d307c7e6c114cfac Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 18:53:39 -0400 Subject: [PATCH 19/58] fix(cgi-bin): draft pipe repair without destruction --- cgi-bin/manage_vnc_pipes | 277 ++++++++++++++++++++++++++++++--------- 1 file changed, 216 insertions(+), 61 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 5b0efdfe..93d187d8 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -132,11 +132,12 @@ sub is_websockify_exists { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; my $server_vnc_port = $parameters->{server_vnc_port}; my $query = " SELECT - vnc.server_vnc_port, hos.host_name, vnc.ws_pid, vnc.ssh_tunnel_forward_port + vnc.server_vnc_port, hos.host_name, hos.host_uuid, vnc.ws_pid, vnc.ws_source_port FROM public.vnc_pipes AS vnc JOIN @@ -147,89 +148,128 @@ WHERE vnc.server_uuid = ".$anvil->Database->quote($server_uuid)." ;"; - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; - my $existing_websockify; + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + my $ws_exists_info = { exists_code => 0 }; if ($count > 0) { my $row = $results->[0]; my $server_vnc_port_in_record = $row->[0]; my $host_name = $row->[1]; - my $ws_pid = $row->[2]; - my $ssh_tunnel_forward_port = $row->[3]; + my $host_uuid_in_record = $row->[2]; + my $ws_pid = $row->[3]; + my $ws_source_port = $row->[4]; my $clean_up_parameters = { host_name => $host_name, ws_pid => $ws_pid }; - if ($server_vnc_port != $server_vnc_port_in_record) - { - # VNC server port mismatch/oudated; require clean up. + $ws_exists_info->{ws_pid} = $ws_pid; + $ws_exists_info->{ws_source_port} = $ws_source_port; + $ws_exists_info->{exists_code} = 1; + if ($host_uuid != $host_uuid_in_record) + { + # VNC server host mismatch; try to stop the recorded instance. + # Likely happens after a server migration. stop_websockify($clean_up_parameters); - stop_related($clean_up_parameters); - delete_vnc_pipe($clean_up_parameters); - return; + # No need to preserve the websockify source port in + # this case because the tunnel will need to be replaced + # as well. + delete $ws_exists_info->{ws_source_port}; + + return $ws_exists_info; } - if (not is_websockify_process($clean_up_parameters)) + if ($server_vnc_port != $server_vnc_port_in_record) { - # Process died; require clean up. + # VNC server port mismatch; try to stop the recorded instance. + stop_websockify($clean_up_parameters); - stop_related($clean_up_parameters); - delete_vnc_pipe($clean_up_parameters); + return $ws_exists_info; + } - return; + if (not is_websockify_process($clean_up_parameters)) + { + # The recorded instance died. + return $ws_exists_info; } # Passed all tests; process considered exists. - $existing_websockify = { ws_pid => $ws_pid, ssh_tunnel_forward_port => $ssh_tunnel_forward_port }; + $ws_exists_info->{exists_code} = 2; } - return $existing_websockify; + return $ws_exists_info; } sub is_ssh_tunnel_exists { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $parameters->{host_uuid}; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $ssh_tunnel_host_uuid = $parameters->{host_uuid}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_source_port = $parameters->{ws_source_port}; my $query = " SELECT - ssh_tunnel_pid + ws_host_uuid, ws_source_port, ssh_tunnel_pid, 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)." + ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." ;"; - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; - my $existing_ssh_tunnel; + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + my $ssh_tunnel_exists_info = { exists_code => 0 }; if ($count == 1) { - my $row = $results->[0]; - my $ssh_tunnel_pid = $row->[0]; - my $clean_up_parameters = { ssh_tunnel_pid => $ssh_tunnel_pid }; + my $row = $results->[0]; + my $ws_host_uuid_in_record = $row->[0]; + my $ws_source_port_in_record = $row->[1]; + my $ssh_tunnel_pid = $row->[2]; + my $ssh_tunnel_forward_port = $row->[3]; + my $clean_up_parameters = { ssh_tunnel_pid => $ssh_tunnel_pid }; + + $ssh_tunnel_exists_info->{ssh_tunnel_pid} = $ssh_tunnel_pid; + $ssh_tunnel_exists_info->{ssh_tunnel_forward_port} = $ssh_tunnel_forward_port; + $ssh_tunnel_exists_info->{exists_code} = 1; + + if ($ws_host_uuid != $ws_host_uuid_in_record) + { + # Websockify host mismatch; try to stop the recorded instance. + # Likely happens after a server migration. + stop_ssh_tunnel($clean_up_parameters); - if (not is_ssh_process($clean_up_parameters)) + # No need to preserve the SSH tunnel forward port in + # this case because the websockify instance will need + # to be replaced as well. + delete $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; + + return $ssh_tunnel_exists_info; + } + + if ($ws_source_port != $ws_source_port_in_record) { - # Process died; require clean up. + # Websockify source port mismatch; try to stop the recorded instance. + stop_ssh_tunnel($clean_up_parameters); - stop_related($clean_up_parameters); - delete_vnc_pipe($clean_up_parameters); + return $ssh_tunnel_exists_info; + } - return; + if (not is_ssh_process($clean_up_parameters)) + { + # The recorded tunnel died. + return $ssh_tunnel_exists_info; } # Passed all tests; tunnel considered exists. - $existing_ssh_tunnel = { ssh_tunnel_pid => $ssh_tunnel_pid }; + $ssh_tunnel_exists_info->{exists_code} = 2; } - return $existing_ssh_tunnel; + return $ssh_tunnel_exists_info; } sub start_websockify @@ -237,23 +277,40 @@ sub start_websockify my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; my $host_name = $parameters->{host_name}; + my $host_uuid = $parameters->{host_uuid}; my $target_port = $parameters->{target_port}; + my $source_port = $parameters->{source_port}; my $ws_info; - my $existing_websockify = is_websockify_exists({ server_uuid => $server_uuid, server_vnc_port => $target_port }); + my $ws_exists_info = is_websockify_exists({ + server_uuid => $server_uuid, + host_uuid => $host_uuid, + server_vnc_port => $target_port + }); - if (defined $existing_websockify) + if ($ws_exists_info->{exists_code} == 2) { $ws_info = {}; - $ws_info->{pid} = $existing_websockify->{ws_pid}; - $ws_info->{source_port} = $existing_websockify->{ssh_tunnel_forward_port}; - $ws_info->{is_existing} = 1; + $ws_info->{pid} = $ws_exists_info->{ws_pid}; + $ws_info->{source_port} = $ws_exists_info->{ws_source_port}; } else { - my $source_port_base = 10000; - my $source_port = $source_port_base + $target_port; - my $shell_call = "ssh -n ".$host_name." 'websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!'"; + if (not defined $source_port) + { + if (defined $ws_exists_info->{ws_source_port}) + { + $source_port = $ws_exists_info->{ws_source_port}; + } + else + { + my $source_port_base = 10000; + + $source_port = $source_port_base + $target_port; + } + } + + my $shell_call = "ssh -n ".$host_name." 'websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!'"; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -269,11 +326,23 @@ sub start_websockify $ws_info = {}; $ws_info->{pid} = $ws_pid; $ws_info->{source_port} = $source_port; + $ws_info->{is_new} = 1; $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ws_pid => $ws_pid, ws_source_port => $source_port } }); + + if ($ws_exists_info->{exists_code} == 1) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + server_vnc_port => $target_port, + ws_host_uuid => $host_uuid, + ws_pid => $ws_pid, + ws_source_port => $source_port + }); + } } } @@ -288,6 +357,8 @@ sub stop_websockify if (is_websockify_process($parameters)) { + # TODO: make sure no other tunnel is using the same websockify instance. + my $shell_call = "ssh -n ".$host_name." \"kill ".$ws_pid."\""; my $shell_output; my $shell_return_code; @@ -317,24 +388,43 @@ sub stop_websockify sub start_ssh_tunnel { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $parameters->{host_uuid}; - my $ws_host_name = $parameters->{ws_host_name}; - my $ws_source_port = $parameters->{ws_source_port}; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $ws_host_name = $parameters->{ws_host_name}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_source_port = $parameters->{ws_source_port}; + my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; my $ssh_tunnel_info; - my $existing_ssh_tunnel = is_ssh_tunnel_exists({ server_uuid => $server_uuid, host_uuid => $host_uuid }); + my $ssh_tunnel_exists_info = is_ssh_tunnel_exists({ + server_uuid => $server_uuid, + ssh_tunnel_host_uuid => $host_uuid, + ws_host_uuid => $ws_host_uuid, + ws_source_port => $ws_source_port + }); - if (defined $existing_ssh_tunnel) + if ($ssh_tunnel_exists_info->{exists_code} == 2) { - $ssh_tunnel_info = {}; - $ssh_tunnel_info->{pid} = $existing_ssh_tunnel->{ssh_tunnel_pid}; - $ssh_tunnel_info->{is_existing} = 1; + $ssh_tunnel_info = {}; + $ssh_tunnel_info->{pid} = $ssh_tunnel_exists_info->{ssh_tunnel_pid}; + $ssh_tunnel_info->{forward_port} = $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; } else { - my $shell_call = "ssh -nN -L ".$ws_source_port.":localhost:".$ws_source_port." ".$ws_host_name." & echo pid:\$!"; + if (not defined $ssh_tunnel_forward_port) + { + if (defined $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}) + { + $ssh_tunnel_forward_port = $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; + } + else + { + $ssh_tunnel_forward_port = $ws_source_port; + } + } + + my $shell_call = "ssh -nN -L ".$ssh_tunnel_forward_port.":localhost:".$ws_source_port." ".$ws_host_name." & echo pid:\$!"; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -347,10 +437,22 @@ sub start_ssh_tunnel { my ($ssh_tunnel_pid) = $shell_output =~ /pid:(\d+)$/; - $ssh_tunnel_info = {}; - $ssh_tunnel_info->{pid} = $ssh_tunnel_pid; + $ssh_tunnel_info = {}; + $ssh_tunnel_info->{pid} = $ssh_tunnel_pid; + $ssh_tunnel_info->{forward_port} = $ssh_tunnel_forward_port; + $ssh_tunnel_info->{is_new} = 1; $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ssh_tunnel_pid => $ssh_tunnel_pid } }); + + if ($ssh_tunnel_exists_info->{exists_code} == 1) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + ssh_tunnel_host_uuid => $host_uuid, + ssh_tunnel_pid => $ssh_tunnel_pid, + ssh_tunnel_forward_port => $ssh_tunnel_forward_port + }); + } } } @@ -452,6 +554,7 @@ CREATE TABLE IF NOT EXISTS public.vnc_pipes ( server_vnc_port numeric not null, ws_host_uuid uuid not null, ws_pid numeric not null, + ws_source_port numeric not null, ssh_tunnel_host_uuid uuid not null, ssh_tunnel_pid numeric not null, ssh_tunnel_forward_port numeric not null, @@ -475,6 +578,7 @@ sub insert_vnc_pipe my $server_vnc_port = $parameters->{server_vnc_port}; my $ws_host_uuid = $parameters->{ws_host_uuid}; my $ws_pid = $parameters->{ws_pid}; + my $ws_source_port = $parameters->{ws_source_port}; my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; @@ -489,6 +593,7 @@ INSERT INTO public.vnc_pipes ( server_vnc_port, ws_host_uuid, ws_pid, + ws_source_port, ssh_tunnel_host_uuid, ssh_tunnel_pid, ssh_tunnel_forward_port, @@ -499,6 +604,7 @@ INSERT INTO public.vnc_pipes ( ".$anvil->Database->quote($server_vnc_port).", ".$anvil->Database->quote($ws_host_uuid).", ".$anvil->Database->quote($ws_pid).", + ".$anvil->Database->quote($ws_source_port).", ".$anvil->Database->quote($ssh_tunnel_host_uuid).", ".$anvil->Database->quote($ssh_tunnel_pid).", ".$anvil->Database->quote($ssh_tunnel_forward_port).", @@ -508,6 +614,48 @@ INSERT INTO public.vnc_pipes ( $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); } +sub update_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $server_vnc_port = $parameters->{server_vnc_port}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_pid = $parameters->{ws_pid}; + my $ws_source_port = $parameters->{ws_source_port}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; + my $set_string; + my $condition_string = "server_uuid = ".$anvil->Database->quote($server_uuid); + + if ((defined $ws_host_uuid) and (defined $ws_pid) and (defined $ws_source_port)) + { + $set_string = " +server_vnc_port = ".$anvil->Database->quote($server_vnc_port).", +ws_host_uuid = ".$anvil->Database->quote($ws_host_uuid).", +ws_pid = ".$anvil->Database->quote($ws_pid).", +ws_source_port = ".$anvil->Database->quote($ws_source_port)." +"; + } + elsif ((defined $ssh_tunnel_host_uuid) and (defined $ssh_tunnel_pid) and (defined $ssh_tunnel_forward_port)) + { + $set_string = " +ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid).", +ssh_tunnel_pid = ".$anvil->Database->quote($ssh_tunnel_pid).", +ssh_tunnel_forward_port = ".$anvil->Database->quote($ssh_tunnel_forward_port)." +"; + $condition_string = $condition_string." AND ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid); + } + + my $query = " +UPDATE public.vnc_pipes +SET ".$set_string." +WHERE ".$condition_string." +;"; + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + sub get_vnc_pipe { my $parameters = shift; @@ -611,7 +759,12 @@ sub open_vnc_pipe return; } - my $ws_info = start_websockify({ server_uuid => $server_uuid, host_name => $server_info->{host_name}, target_port => $vnc_info->{port} }); + my $ws_info = start_websockify({ + server_uuid => $server_uuid, + host_name => $server_info->{host_name}, + host_uuid => $server_info->{host_uuid}, + target_port => $vnc_info->{port} + }); if (not defined $ws_info) { @@ -626,6 +779,7 @@ sub open_vnc_pipe server_uuid => $server_uuid, host_uuid => $host_uuid, ws_host_name => $server_info->{host_name}, + ws_host_uuid => $server_info->{host_uuid}, ws_source_port => $ws_info->{source_port} }); @@ -638,16 +792,17 @@ sub open_vnc_pipe return; } - if (not $ws_info->{is_existing} or not $ssh_tunnel_info->{is_existing}) + if ($ws_info->{is_new} and $ssh_tunnel_info->{is_new}) { insert_vnc_pipe({ server_uuid => $server_uuid, server_vnc_port => $vnc_info->{port}, ws_host_uuid => $server_info->{host_uuid}, ws_pid => $ws_info->{pid}, + ws_source_port => $ws_info->{source_port}, ssh_tunnel_host_uuid => $host_uuid, ssh_tunnel_pid => $ssh_tunnel_info->{pid}, - ssh_tunnel_forward_port => $ws_info->{source_port} + ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} }); } } From 85375d69b1bce8d3cff2ddb33036e51705b03f45 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 19:01:27 -0400 Subject: [PATCH 20/58] fix(cgi-bin): param name mismatch in is_ssh_tunnel_exists --- cgi-bin/manage_vnc_pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 93d187d8..1dc3ebe3 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -205,7 +205,7 @@ sub is_ssh_tunnel_exists { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; - my $ssh_tunnel_host_uuid = $parameters->{host_uuid}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; my $ws_host_uuid = $parameters->{ws_host_uuid}; my $ws_source_port = $parameters->{ws_source_port}; From 2531252e3f8c71596fc727c4dad0ed04e6572f28 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 19:06:59 -0400 Subject: [PATCH 21/58] fix(cgi-bin): compare UUID with string comparison operators --- cgi-bin/manage_vnc_pipes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 1dc3ebe3..ae5bb2e0 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -166,7 +166,7 @@ WHERE $ws_exists_info->{ws_source_port} = $ws_source_port; $ws_exists_info->{exists_code} = 1; - if ($host_uuid != $host_uuid_in_record) + if ($host_uuid ne $host_uuid_in_record) { # VNC server host mismatch; try to stop the recorded instance. # Likely happens after a server migration. @@ -237,7 +237,7 @@ AND $ssh_tunnel_exists_info->{ssh_tunnel_forward_port} = $ssh_tunnel_forward_port; $ssh_tunnel_exists_info->{exists_code} = 1; - if ($ws_host_uuid != $ws_host_uuid_in_record) + if ($ws_host_uuid ne $ws_host_uuid_in_record) { # Websockify host mismatch; try to stop the recorded instance. # Likely happens after a server migration. From 9df976712ad4af250fe661c5e315bec2ac3dd5dd Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 19:21:24 -0400 Subject: [PATCH 22/58] fix(cgi-bin): only insert when not repairing pipe --- cgi-bin/manage_vnc_pipes | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index ae5bb2e0..54ee00fa 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -326,12 +326,6 @@ sub start_websockify $ws_info = {}; $ws_info->{pid} = $ws_pid; $ws_info->{source_port} = $source_port; - $ws_info->{is_new} = 1; - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - ws_pid => $ws_pid, - ws_source_port => $source_port - } }); if ($ws_exists_info->{exists_code} == 1) { @@ -343,6 +337,16 @@ sub start_websockify ws_source_port => $source_port }); } + else + { + $ws_info->{is_new} = 1; + } + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + ws_pid => $ws_pid, + ws_source_port => $source_port, + ws_is_new => $ws_info->{is_new} + } }); } } @@ -440,9 +444,6 @@ sub start_ssh_tunnel $ssh_tunnel_info = {}; $ssh_tunnel_info->{pid} = $ssh_tunnel_pid; $ssh_tunnel_info->{forward_port} = $ssh_tunnel_forward_port; - $ssh_tunnel_info->{is_new} = 1; - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ssh_tunnel_pid => $ssh_tunnel_pid } }); if ($ssh_tunnel_exists_info->{exists_code} == 1) { @@ -453,6 +454,16 @@ sub start_ssh_tunnel ssh_tunnel_forward_port => $ssh_tunnel_forward_port }); } + else + { + $ssh_tunnel_info->{is_new} = 1; + } + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + ssh_tunnel_pid => $ssh_tunnel_pid, + ssh_tunnel_forward_port => $ssh_tunnel_forward_port, + ssh_tunnel_is_new => $ssh_tunnel_info->{is_new} + } }); } } From 2ca5ee232b95dd19741dbe1a96cb3665b0293c86 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 13 Jul 2021 20:02:58 -0400 Subject: [PATCH 23/58] fix(cgi-bin): don't kill websockify instances in-use --- cgi-bin/manage_vnc_pipes | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 54ee00fa..b73eac06 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -272,6 +272,18 @@ AND return $ssh_tunnel_exists_info; } +sub is_websockify_in_use_by_others +{ + my $parameters = shift; + my $ws_pid = $parameters->{ws_pid}; + + my $query = "SELECT COUNT(*) FROM public.vnc_pipes WHERE ws_pid = ".$anvil->Database->quote($ws_pid).";"; + + my $count = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0]; + + return $count > 1 ? 1 : 0; +} + sub start_websockify { my $parameters = shift; @@ -361,8 +373,6 @@ sub stop_websockify if (is_websockify_process($parameters)) { - # TODO: make sure no other tunnel is using the same websockify instance. - my $shell_call = "ssh -n ".$host_name." \"kill ".$ws_pid."\""; my $shell_output; my $shell_return_code; @@ -834,8 +844,13 @@ sub close_vnc_pipe return; } - stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); + if (not is_websockify_in_use_by_others($parameters)) + { + stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); + } + stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); + delete_vnc_pipe($vnc_pipe_parameters); } From 4bd7cd6f62bfe06a16889dafe14ba8aa3d97c45f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 14 Jul 2021 11:22:37 -0400 Subject: [PATCH 24/58] fix(cgi-bin): respond with connect info after open VNC pipe --- cgi-bin/manage_vnc_pipes | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index b73eac06..8f52d1fd 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -757,6 +757,7 @@ sub open_vnc_pipe my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; my $host_uuid = $anvil->Get->host_uuid(); + my $vnc_pipe_info; my $server_info = get_server_info({ server_uuid => $server_uuid }); @@ -826,6 +827,10 @@ sub open_vnc_pipe ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} }); } + + $vnc_pipe_info = { forward_port => $ssh_tunnel_info->{forward_port} }; + + return $vnc_pipe_info; } sub close_vnc_pipe @@ -912,7 +917,10 @@ if ($server_uuid) if ($is_open) { - open_vnc_pipe($vnc_pipe_parameters); + my $vnc_pipe_info = open_vnc_pipe($vnc_pipe_parameters); + + $response_body->{protocol} = "ws"; + $response_body->{forward_port} = $vnc_pipe_info->{forward_port}; } else { From b528456f45166ada6a5d90cea4a5a4a92e3c5b39 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 14 Jul 2021 12:35:13 -0400 Subject: [PATCH 25/58] fix(cgi-bin): update records after comparisons are done --- cgi-bin/manage_vnc_pipes | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 8f52d1fd..ea1d77d2 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -341,13 +341,7 @@ sub start_websockify if ($ws_exists_info->{exists_code} == 1) { - update_vnc_pipe({ - server_uuid => $server_uuid, - server_vnc_port => $target_port, - ws_host_uuid => $host_uuid, - ws_pid => $ws_pid, - ws_source_port => $source_port - }); + $ws_info->{is_update} = 1; } else { @@ -357,6 +351,7 @@ sub start_websockify $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ws_pid => $ws_pid, ws_source_port => $source_port, + ws_is_update => $ws_info->{is_update}, ws_is_new => $ws_info->{is_new} } }); } @@ -457,12 +452,7 @@ sub start_ssh_tunnel if ($ssh_tunnel_exists_info->{exists_code} == 1) { - update_vnc_pipe({ - server_uuid => $server_uuid, - ssh_tunnel_host_uuid => $host_uuid, - ssh_tunnel_pid => $ssh_tunnel_pid, - ssh_tunnel_forward_port => $ssh_tunnel_forward_port - }); + $ssh_tunnel_info->{is_update} = 1; } else { @@ -472,6 +462,7 @@ sub start_ssh_tunnel $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { ssh_tunnel_pid => $ssh_tunnel_pid, ssh_tunnel_forward_port => $ssh_tunnel_forward_port, + ssh_tunnel_is_update => $ssh_tunnel_info->{is_update}, ssh_tunnel_is_new => $ssh_tunnel_info->{is_new} } }); } @@ -827,6 +818,25 @@ sub open_vnc_pipe ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} }); } + elsif ($ws_info->{is_update}) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + server_vnc_port => $vnc_info->{port}, + ws_host_uuid => $server_info->{host_uuid}, + ws_pid => $ws_info->{pid}, + ws_source_port => $ws_info->{source_port} + }); + } + elsif ($ssh_tunnel_info->{is_update}) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + ssh_tunnel_host_uuid => $host_uuid, + ssh_tunnel_pid => $ssh_tunnel_info->{pid}, + ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} + }); + } $vnc_pipe_info = { forward_port => $ssh_tunnel_info->{forward_port} }; From 742af987e9d932fda02d28bdeac90327824e7a4d Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 14 Jul 2021 12:52:54 -0400 Subject: [PATCH 26/58] fix(cgi-bin): provide proper params to check if websockify is in-use --- cgi-bin/manage_vnc_pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index ea1d77d2..e19d4774 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -859,7 +859,7 @@ sub close_vnc_pipe return; } - if (not is_websockify_in_use_by_others($parameters)) + if (not is_websockify_in_use_by_others({ ws_pid => $vnc_pipe_info->{ws_pid} })) { stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); } From c7f63f4a4b6750e3e5d11b69552af290b33117a4 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 14 Jul 2021 14:23:17 -0400 Subject: [PATCH 27/58] fix(cgi-bin): allow update all pipe pieces --- cgi-bin/manage_vnc_pipes | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index e19d4774..0d4bb81d 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -818,24 +818,28 @@ sub open_vnc_pipe ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} }); } - elsif ($ws_info->{is_update}) - { - update_vnc_pipe({ - server_uuid => $server_uuid, - server_vnc_port => $vnc_info->{port}, - ws_host_uuid => $server_info->{host_uuid}, - ws_pid => $ws_info->{pid}, - ws_source_port => $ws_info->{source_port} - }); - } - elsif ($ssh_tunnel_info->{is_update}) + else { - update_vnc_pipe({ - server_uuid => $server_uuid, - ssh_tunnel_host_uuid => $host_uuid, - ssh_tunnel_pid => $ssh_tunnel_info->{pid}, - ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} - }); + if ($ws_info->{is_update}) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + server_vnc_port => $vnc_info->{port}, + ws_host_uuid => $server_info->{host_uuid}, + ws_pid => $ws_info->{pid}, + ws_source_port => $ws_info->{source_port} + }); + } + + if ($ssh_tunnel_info->{is_update}) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + ssh_tunnel_host_uuid => $host_uuid, + ssh_tunnel_pid => $ssh_tunnel_info->{pid}, + ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} + }); + } } $vnc_pipe_info = { forward_port => $ssh_tunnel_info->{forward_port} }; From 23d818cfffbb4d2f7d9a80cd4cb1a026bfccd6a9 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 14 Jul 2021 20:43:26 -0400 Subject: [PATCH 28/58] fix(cgi-bin): avoid direct SSH calls --- Anvil/Tools.pm | 1 + cgi-bin/manage_vnc_pipes | 87 ++++++++++++++++----- tools/striker-start-ssh-tunnel | 133 +++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 17 deletions(-) create mode 100755 tools/striker-start-ssh-tunnel diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 4671fd0a..cd0c2df1 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1224,6 +1224,7 @@ sub _set_paths 'striker-parse-oui' => "/usr/sbin/striker-parse-oui", 'striker-prep-database' => "/usr/sbin/striker-prep-database", 'striker-scan-network' => "/usr/sbin/striker-scan-network", + 'striker-start-ssh-tunnel' => "/usr/sbin/striker-start-ssh-tunnel", stty => "/usr/bin/stty", su => "/usr/bin/su", 'subscription-manager' => "/usr/sbin/subscription-manager", diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 0d4bb81d..ab2fef68 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -69,13 +69,19 @@ sub get_vnc_info my $server_name = $parameters->{server_name}; my $port_base = 5900; # Requires root to access VM information. - my $shell_call = "ssh -n root@".$host_name." \"virsh vncdisplay ".$server_name."\""; + my $shell_call = "virsh vncdisplay ".$server_name; my $vnc_info; - my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ + target => $host_name, + remote_user => "root", + shell_call => $shell_call, + 'close' => 1 + }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call, shell_output => $shell_output, + shell_error => $shell_error, shell_return_code => $shell_return_code } }); @@ -100,12 +106,18 @@ sub is_websockify_process my $parameters = shift; my $host_name = $parameters->{host_name}; my $ws_pid = $parameters->{ws_pid}; - my $shell_call = "ssh -n ".$host_name." \"ps -o comm -h -p ".$ws_pid."\""; + my $shell_call = "ps -o comm -h -p ".$ws_pid; - my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call, shell_output => $shell_output, + shell_error => $shell_error, shell_return_code => $shell_return_code } }); @@ -322,12 +334,18 @@ sub start_websockify } } - my $shell_call = "ssh -n ".$host_name." 'websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!'"; + my $shell_call = "websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!"; - my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call, shell_output => $shell_output, + shell_error => $shell_error, shell_return_code => $shell_return_code } }); @@ -368,14 +386,22 @@ sub stop_websockify if (is_websockify_process($parameters)) { - my $shell_call = "ssh -n ".$host_name." \"kill ".$ws_pid."\""; + my $shell_call = "kill ".$ws_pid; + my $remote_call_parameters = { + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }; my $shell_output; + my $shell_error; my $shell_return_code; - ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call, shell_output => $shell_output, + shell_error => $shell_error, shell_return_code => $shell_return_code } }); @@ -383,9 +409,10 @@ sub stop_websockify if (is_websockify_process($parameters)) { - $shell_call = $shell_call =~ s/kill/kill -9/; + $shell_call = $shell_call =~ s/kill/kill -9/; + $remote_call_parameters->{shell_call} = $shell_call; - ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call, shell_output => $shell_output, @@ -433,7 +460,11 @@ sub start_ssh_tunnel } } - my $shell_call = "ssh -nN -L ".$ssh_tunnel_forward_port.":localhost:".$ws_source_port." ".$ws_host_name." & echo pid:\$!"; + my $shell_call = $anvil->data->{path}{exe}{'striker-start-ssh-tunnel'} + ." --remote-user admin --target ".$ws_host_name + ." --forward-local-port ".$ssh_tunnel_forward_port + ." --forward-remote-port ".$ws_source_port + ." & echo pid:\$!"; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -479,19 +510,31 @@ sub stop_ssh_tunnel if (is_ssh_process($parameters)) { - my $shell_call = "kill ".$ssh_tunnel_pid; + my $shell_call = "kill ".$ssh_tunnel_pid; + my $is_remote_call = (defined $host_name) and ($host_name ne $anvil->Get->host_name()) ? 1 : 0; + my $remote_call_parameters = { + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }; my $shell_output; + my $shell_error; my $shell_return_code; - if ((defined $host_name) and ($host_name ne $anvil->Get->host_name())) + if ($is_remote_call) { - $shell_call = "ssh -n ".$host_name." \"".$shell_call."\""; + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); + } + else + { + ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); } - ($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_error => $shell_error, shell_return_code => $shell_return_code } }); @@ -499,12 +542,22 @@ sub stop_ssh_tunnel if (is_ssh_process($parameters)) { - $shell_call = $shell_call =~ s/kill/kill -9/; + $shell_call = $shell_call =~ s/kill/kill -9/; + $remote_call_parameters->{shell_call} = $shell_call; + + if ($is_remote_call) + { + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); + } + else + { + ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); + } - ($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_error => $shell_error, shell_return_code => $shell_return_code } }); } diff --git a/tools/striker-start-ssh-tunnel b/tools/striker-start-ssh-tunnel new file mode 100755 index 00000000..8d659eb6 --- /dev/null +++ b/tools/striker-start-ssh-tunnel @@ -0,0 +1,133 @@ +#!/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; + +$anvil->Log->level({ set => 2 }); + +sub start_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", + "-L".$forward_local_port.":localhost:".$forward_remote_port ] }); + + return 0; +} + +sub handle_stop_signals +{ + 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} = \&handle_stop_signals; +$SIG->{TERM} = \&handle_stop_signals; + +$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 (start_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 }); +} + +while(1) +{ + sleep(1); +} From d5724c145753d4e8ec682b7e4f1ee1ff06c6546a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 15 Jul 2021 17:28:02 -0400 Subject: [PATCH 29/58] chore(tools): rename striker-start-ssh-tunnel->striker-open-ssh-tunnel --- Anvil/Tools.pm | 2 +- cgi-bin/manage_vnc_pipes | 4 ++-- tools/{striker-start-ssh-tunnel => striker-open-ssh-tunnel} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename tools/{striker-start-ssh-tunnel => striker-open-ssh-tunnel} (100%) diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index cd0c2df1..02d4ee28 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1221,10 +1221,10 @@ sub _set_paths 'striker-initialize-host' => "/usr/sbin/striker-initialize-host", 'striker-manage-install-target' => "/usr/sbin/striker-manage-install-target", 'striker-manage-peers' => "/usr/sbin/striker-manage-peers", + 'striker-open-ssh-tunnel' => "/usr/sbin/striker-open-ssh-tunnel", 'striker-parse-oui' => "/usr/sbin/striker-parse-oui", 'striker-prep-database' => "/usr/sbin/striker-prep-database", 'striker-scan-network' => "/usr/sbin/striker-scan-network", - 'striker-start-ssh-tunnel' => "/usr/sbin/striker-start-ssh-tunnel", stty => "/usr/bin/stty", su => "/usr/bin/su", 'subscription-manager' => "/usr/sbin/subscription-manager", diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index ab2fef68..46a53fe5 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -460,11 +460,11 @@ sub start_ssh_tunnel } } - my $shell_call = $anvil->data->{path}{exe}{'striker-start-ssh-tunnel'} + my $shell_call = $anvil->data->{path}{exe}{'striker-open-ssh-tunnel'} ." --remote-user admin --target ".$ws_host_name ." --forward-local-port ".$ssh_tunnel_forward_port ." --forward-remote-port ".$ws_source_port - ." & echo pid:\$!"; + ." &>/dev/null & echo pid:\$!"; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { diff --git a/tools/striker-start-ssh-tunnel b/tools/striker-open-ssh-tunnel similarity index 100% rename from tools/striker-start-ssh-tunnel rename to tools/striker-open-ssh-tunnel From fa15c715e4cb2d054be0e55901838f0402230b1b Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 15 Jul 2021 19:09:41 -0400 Subject: [PATCH 30/58] fix(cgi-bin): revise is process checks to match tunnel script --- cgi-bin/manage_vnc_pipes | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 46a53fe5..fd52e8eb 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -106,7 +106,7 @@ sub is_websockify_process my $parameters = shift; my $host_name = $parameters->{host_name}; my $ws_pid = $parameters->{ws_pid}; - my $shell_call = "ps -o comm -h -p ".$ws_pid; + my $shell_call = "ps -e -o command -h -p ".$ws_pid; my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ target => $host_name, @@ -121,14 +121,14 @@ sub is_websockify_process shell_return_code => $shell_return_code } }); - return $shell_output eq "websockify" ? 1 : 0; + return $shell_output =~ /websockify/ ? 1 : 0; } sub is_ssh_process { my $parameters = shift; my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $shell_call = "ps -o comm -h -p ".$ssh_tunnel_pid; + my $shell_call = "ps -e -o command -h -p ".$ssh_tunnel_pid; my ($shell_output, $shell_return_code) = $anvil->System->call({ shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { @@ -137,7 +137,7 @@ sub is_ssh_process shell_return_code => $shell_return_code } }); - return $shell_output eq "ssh" ? 1 : 0; + return $shell_output =~ /striker-open-ssh-tunnel/ ? 1 : 0; } sub is_websockify_exists From 5459e610aa8db2547438262a5399b48c0cb179ac Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 15 Jul 2021 19:14:53 -0400 Subject: [PATCH 31/58] fix(tools): auto-end tunnel script when connection breaks --- tools/striker-open-ssh-tunnel | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tools/striker-open-ssh-tunnel b/tools/striker-open-ssh-tunnel index 8d659eb6..4c28f6dc 100755 --- a/tools/striker-open-ssh-tunnel +++ b/tools/striker-open-ssh-tunnel @@ -24,7 +24,7 @@ my $ssh_fh; $anvil->Log->level({ set => 2 }); -sub start_ssh_tunnel +sub open_ssh_tunnel { my $parameters = shift; @@ -88,7 +88,7 @@ WHERE hos.host_name = ".$anvil->Database->quote($target)." return 0; } -sub handle_stop_signals +sub close_ssh_tunnel { if (defined $ssh_fh->disconnect) { @@ -102,8 +102,8 @@ sub handle_stop_signals $anvil->nice_exit({ exit_code => 0 }); } -$SIG->{INT} = \&handle_stop_signals; -$SIG->{TERM} = \&handle_stop_signals; +$SIG->{INT} = \&close_ssh_tunnel; +$SIG->{TERM} = \&close_ssh_tunnel; $anvil->Get->switches; @@ -116,7 +116,7 @@ if (not $anvil->data->{sys}{database}{connections}) $anvil->nice_exit({ exit_code => 1 }); } -if (start_ssh_tunnel({ +if (open_ssh_tunnel({ remote_user => $anvil->data->{switches}{'remote-user'}, target => $anvil->data->{switches}{'target'}, port => $anvil->data->{switches}{'port'}, @@ -127,7 +127,13 @@ if (start_ssh_tunnel({ $anvil->nice_exit({ exit_code => 1 }); } -while(1) +my $is_ssh_tunnel_alive = 1; + +while($is_ssh_tunnel_alive) { - sleep(1); + $is_ssh_tunnel_alive = $ssh_fh->test('echo'); + + sleep(60); } + +close_ssh_tunnel(); From 5cceb09f2af272048699a1571933dcc06e2d6c0e Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Thu, 15 Jul 2021 19:36:53 -0400 Subject: [PATCH 32/58] fix(cgi-bin): insert record on new of either websockify or ssh tunnel --- cgi-bin/manage_vnc_pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index fd52e8eb..4ce47344 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -858,7 +858,7 @@ sub open_vnc_pipe return; } - if ($ws_info->{is_new} and $ssh_tunnel_info->{is_new}) + if ($ws_info->{is_new} or $ssh_tunnel_info->{is_new}) { insert_vnc_pipe({ server_uuid => $server_uuid, From 0935b9a9900f507558fd3a54320e953c1fd928e1 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 11:38:53 -0400 Subject: [PATCH 33/58] feat(tools): move manage_vnc_pipes endpoint core logic to separate script --- tools/striker-manage-vnc-pipes | 971 +++++++++++++++++++++++++++++++++ 1 file changed, 971 insertions(+) create mode 100644 tools/striker-manage-vnc-pipes diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes new file mode 100644 index 00000000..6431aab4 --- /dev/null +++ b/tools/striker-manage-vnc-pipes @@ -0,0 +1,971 @@ +#!/usr/bin/perl +# +# Manages VNC ports for server VMs that have VNC enabled. +# + +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(); + +$anvil->Log->level({ set => 2 }); + +sub get_server_info +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $server_info; + + my $query = " +SELECT + ser.server_name, hos.host_name, hos.host_uuid +FROM + public.servers AS ser +JOIN + public.hosts AS hos +ON + ser.server_host_uuid = hos.host_uuid +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +;"; + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + + if ($count == 1) + { + my $row = $results->[0]; + + $server_info = {}; + $server_info->{server_name} = $row->[0]; + $server_info->{host_name} = $row->[1]; + $server_info->{host_uuid} = $row->[2]; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + server_name => $server_info->{server_name}, + host_name => $server_info->{host_name}, + host_uuid => $server_info->{host_uuid} + } }); + } + + return $server_info; +} + +sub get_vnc_info +{ + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $server_name = $parameters->{server_name}; + my $port_base = 5900; + # Requires root to access VM information. + my $shell_call = "virsh vncdisplay ".$server_name; + my $vnc_info; + + my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ + target => $host_name, + remote_user => "root", + shell_call => $shell_call, + 'close' => 1 + }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + shell_call => $shell_call, + shell_output => $shell_output, + shell_error => $shell_error, + shell_return_code => $shell_return_code + } }); + + if ($shell_return_code == 0) + { + my ($port_offset) = $shell_output =~ /:(\d+)$/; + + $vnc_info = { host_name => $host_name }; + $vnc_info->{port} = $port_base + int($port_offset); + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + port_offset => $port_offset, + vnc_port => $vnc_info->{port} + } }); + } + + return $vnc_info; +} + +sub is_websockify_process +{ + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $ws_pid = $parameters->{ws_pid}; + my $shell_call = "ps -e -o command -h -p ".$ws_pid; + + my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + shell_call => $shell_call, + shell_output => $shell_output, + shell_error => $shell_error, + shell_return_code => $shell_return_code + } }); + + return $shell_output =~ /websockify/ ? 1 : 0; +} + +sub is_ssh_process +{ + my $parameters = shift; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $shell_call = "ps -e -o command -h -p ".$ssh_tunnel_pid; + + 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 =~ /striker-open-ssh-tunnel/ ? 1 : 0; +} + +sub is_websockify_exists +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $server_vnc_port = $parameters->{server_vnc_port}; + + my $query = " +SELECT + vnc.server_vnc_port, hos.host_name, hos.host_uuid, vnc.ws_pid, vnc.ws_source_port +FROM + public.vnc_pipes AS vnc +JOIN + public.hosts AS hos +ON + vnc.ws_host_uuid = hos.host_uuid +WHERE + vnc.server_uuid = ".$anvil->Database->quote($server_uuid)." +;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + my $ws_exists_info = { exists_code => 0 }; + + if ($count > 0) + { + my $row = $results->[0]; + my $server_vnc_port_in_record = $row->[0]; + my $host_name = $row->[1]; + my $host_uuid_in_record = $row->[2]; + my $ws_pid = $row->[3]; + my $ws_source_port = $row->[4]; + my $clean_up_parameters = { host_name => $host_name, ws_pid => $ws_pid }; + + $ws_exists_info->{ws_pid} = $ws_pid; + $ws_exists_info->{ws_source_port} = $ws_source_port; + $ws_exists_info->{exists_code} = 1; + + if ($host_uuid ne $host_uuid_in_record) + { + # VNC server host mismatch; try to stop the recorded instance. + # Likely happens after a server migration. + stop_websockify($clean_up_parameters); + + # No need to preserve the websockify source port in + # this case because the tunnel will need to be replaced + # as well. + delete $ws_exists_info->{ws_source_port}; + + return $ws_exists_info; + } + + if ($server_vnc_port != $server_vnc_port_in_record) + { + # VNC server port mismatch; try to stop the recorded instance. + stop_websockify($clean_up_parameters); + + return $ws_exists_info; + } + + if (not is_websockify_process($clean_up_parameters)) + { + # The recorded instance died. + return $ws_exists_info; + } + + # Passed all tests; process considered exists. + $ws_exists_info->{exists_code} = 2; + } + + return $ws_exists_info; +} + +sub is_ssh_tunnel_exists +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_source_port = $parameters->{ws_source_port}; + + my $query = " +SELECT + ws_host_uuid, ws_source_port, ssh_tunnel_pid, ssh_tunnel_forward_port +FROM + public.vnc_pipes +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +AND + ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." +;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + my $ssh_tunnel_exists_info = { exists_code => 0 }; + + if ($count == 1) + { + my $row = $results->[0]; + my $ws_host_uuid_in_record = $row->[0]; + my $ws_source_port_in_record = $row->[1]; + my $ssh_tunnel_pid = $row->[2]; + my $ssh_tunnel_forward_port = $row->[3]; + my $clean_up_parameters = { ssh_tunnel_pid => $ssh_tunnel_pid }; + + $ssh_tunnel_exists_info->{ssh_tunnel_pid} = $ssh_tunnel_pid; + $ssh_tunnel_exists_info->{ssh_tunnel_forward_port} = $ssh_tunnel_forward_port; + $ssh_tunnel_exists_info->{exists_code} = 1; + + if ($ws_host_uuid ne $ws_host_uuid_in_record) + { + # Websockify host mismatch; try to stop the recorded instance. + # Likely happens after a server migration. + stop_ssh_tunnel($clean_up_parameters); + + # No need to preserve the SSH tunnel forward port in + # this case because the websockify instance will need + # to be replaced as well. + delete $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; + + return $ssh_tunnel_exists_info; + } + + if ($ws_source_port != $ws_source_port_in_record) + { + # Websockify source port mismatch; try to stop the recorded instance. + stop_ssh_tunnel($clean_up_parameters); + + return $ssh_tunnel_exists_info; + } + + if (not is_ssh_process($clean_up_parameters)) + { + # The recorded tunnel died. + return $ssh_tunnel_exists_info; + } + + # Passed all tests; tunnel considered exists. + $ssh_tunnel_exists_info->{exists_code} = 2; + } + + return $ssh_tunnel_exists_info; +} + +sub is_websockify_in_use_by_others +{ + my $parameters = shift; + my $ws_pid = $parameters->{ws_pid}; + + my $query = "SELECT COUNT(*) FROM public.vnc_pipes WHERE ws_pid = ".$anvil->Database->quote($ws_pid).";"; + + my $count = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0]; + + return $count > 1 ? 1 : 0; +} + +sub start_websockify +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_name = $parameters->{host_name}; + my $host_uuid = $parameters->{host_uuid}; + my $target_port = $parameters->{target_port}; + my $source_port = $parameters->{source_port}; + my $ws_info; + + my $ws_exists_info = is_websockify_exists({ + server_uuid => $server_uuid, + host_uuid => $host_uuid, + server_vnc_port => $target_port + }); + + if ($ws_exists_info->{exists_code} == 2) + { + $ws_info = {}; + $ws_info->{pid} = $ws_exists_info->{ws_pid}; + $ws_info->{source_port} = $ws_exists_info->{ws_source_port}; + } + else + { + if (not defined $source_port) + { + if (defined $ws_exists_info->{ws_source_port}) + { + $source_port = $ws_exists_info->{ws_source_port}; + } + else + { + my $source_port_base = 10000; + + $source_port = $source_port_base + $target_port; + } + } + + my $shell_call = "websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!"; + + my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + shell_call => $shell_call, + shell_output => $shell_output, + shell_error => $shell_error, + shell_return_code => $shell_return_code + } }); + + if ($shell_return_code == 0) + { + my ($ws_pid) = $shell_output =~ /pid:(\d+)$/; + + $ws_info = {}; + $ws_info->{pid} = $ws_pid; + $ws_info->{source_port} = $source_port; + + if ($ws_exists_info->{exists_code} == 1) + { + $ws_info->{is_update} = 1; + } + else + { + $ws_info->{is_new} = 1; + } + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + ws_pid => $ws_pid, + ws_source_port => $source_port, + ws_is_update => $ws_info->{is_update}, + ws_is_new => $ws_info->{is_new} + } }); + } + } + + return $ws_info; +} + +sub stop_websockify +{ + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $ws_pid = $parameters->{ws_pid}; + + if (is_websockify_process($parameters)) + { + my $shell_call = "kill ".$ws_pid; + my $remote_call_parameters = { + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }; + my $shell_output; + my $shell_error; + my $shell_return_code; + + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + shell_call => $shell_call, + shell_output => $shell_output, + shell_error => $shell_error, + shell_return_code => $shell_return_code + } }); + + sleep(2); + + if (is_websockify_process($parameters)) + { + $shell_call = $shell_call =~ s/kill/kill -9/; + $remote_call_parameters->{shell_call} = $shell_call; + + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); + $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 + } }); + } + } +} + +sub start_ssh_tunnel +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $ws_host_name = $parameters->{ws_host_name}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_source_port = $parameters->{ws_source_port}; + my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; + my $ssh_tunnel_info; + + my $ssh_tunnel_exists_info = is_ssh_tunnel_exists({ + server_uuid => $server_uuid, + ssh_tunnel_host_uuid => $host_uuid, + ws_host_uuid => $ws_host_uuid, + ws_source_port => $ws_source_port + }); + + if ($ssh_tunnel_exists_info->{exists_code} == 2) + { + $ssh_tunnel_info = {}; + $ssh_tunnel_info->{pid} = $ssh_tunnel_exists_info->{ssh_tunnel_pid}; + $ssh_tunnel_info->{forward_port} = $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; + } + else + { + if (not defined $ssh_tunnel_forward_port) + { + if (defined $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}) + { + $ssh_tunnel_forward_port = $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; + } + else + { + $ssh_tunnel_forward_port = $ws_source_port; + } + } + + my $shell_call = $anvil->data->{path}{exe}{'striker-open-ssh-tunnel'} + ." --remote-user admin --target ".$ws_host_name + ." --forward-local-port ".$ssh_tunnel_forward_port + ." --forward-remote-port ".$ws_source_port + ." &>/dev/null & echo pid:\$!"; + + 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 + } }); + + if ($shell_return_code == 0) + { + my ($ssh_tunnel_pid) = $shell_output =~ /pid:(\d+)$/; + + $ssh_tunnel_info = {}; + $ssh_tunnel_info->{pid} = $ssh_tunnel_pid; + $ssh_tunnel_info->{forward_port} = $ssh_tunnel_forward_port; + + if ($ssh_tunnel_exists_info->{exists_code} == 1) + { + $ssh_tunnel_info->{is_update} = 1; + } + else + { + $ssh_tunnel_info->{is_new} = 1; + } + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + ssh_tunnel_pid => $ssh_tunnel_pid, + ssh_tunnel_forward_port => $ssh_tunnel_forward_port, + ssh_tunnel_is_update => $ssh_tunnel_info->{is_update}, + ssh_tunnel_is_new => $ssh_tunnel_info->{is_new} + } }); + } + } + + return $ssh_tunnel_info; +} + +sub stop_ssh_tunnel +{ + my $parameters = shift; + my $host_name = $parameters->{host_name}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + + if (is_ssh_process($parameters)) + { + my $shell_call = "kill ".$ssh_tunnel_pid; + my $is_remote_call = (defined $host_name) and ($host_name ne $anvil->Get->host_name()) ? 1 : 0; + my $remote_call_parameters = { + target => $host_name, + remote_user => "admin", + shell_call => $shell_call, + 'close' => 1 + }; + my $shell_output; + my $shell_error; + my $shell_return_code; + + if ($is_remote_call) + { + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); + } + else + { + ($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_error => $shell_error, + shell_return_code => $shell_return_code + } }); + + sleep(2); + + if (is_ssh_process($parameters)) + { + $shell_call = $shell_call =~ s/kill/kill -9/; + $remote_call_parameters->{shell_call} = $shell_call; + + if ($is_remote_call) + { + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); + } + else + { + ($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_error => $shell_error, + shell_return_code => $shell_return_code + } }); + } + } +} + +sub stop_related +{ + my $parameters = shift; + my $ws_pid = $parameters->{ws_pid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $select_pid_field; + my $condition_pid_field; + my $condition_pid_value; + my $stop_function; + + if (defined $ws_pid) + { + $select_pid_field = "ssh_tunnel_pid"; + $condition_pid_field = "ws_pid"; + $condition_pid_value = $ws_pid; + $stop_function = \&stop_ssh_tunnel + } + elsif (defined $ssh_tunnel_pid) + { + $select_pid_field = "ws_pid"; + $condition_pid_field = "ssh_tunnel_pid"; + $condition_pid_value = $ssh_tunnel_pid; + $stop_function = \&stop_websockify + } + + my $query = " +SELECT + hos.host_name, vnc.".$select_pid_field." +FROM + public.vnc_pipes AS vnc +JOIN + public.hosts AS hos +ON + vnc.ssh_tunnel_host_uuid = hos.host_uuid +WHERE + vnc.".$condition_pid_field." = ".$anvil->Database->quote($condition_pid_value)." +;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + + foreach my $row (@{$results}) + { + $stop_function->({ host_name => $row->[0], ws_pid => $row->[1], ssh_tunnel_pid => $row->[1] }); + } +} + +sub create_vnc_pipes_table +{ + my $query = " +CREATE TABLE IF NOT EXISTS public.vnc_pipes ( + uuid uuid not null primary key, + server_uuid uuid not null, + server_vnc_port numeric not null, + ws_host_uuid uuid not null, + ws_pid numeric not null, + ws_source_port numeric not null, + ssh_tunnel_host_uuid uuid not null, + ssh_tunnel_pid numeric not null, + ssh_tunnel_forward_port numeric not null, + modified_date timestamp with time zone not null +);"; + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub drop_vnc_pipes_table +{ + my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub insert_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $server_vnc_port = $parameters->{server_vnc_port}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_pid = $parameters->{ws_pid}; + my $ws_source_port = $parameters->{ws_source_port}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; + + my $record_uuid = $anvil->Get->uuid(); + my $record_modified_date = $anvil->Database->refresh_timestamp(); + + my $query = " +INSERT INTO public.vnc_pipes ( + uuid, + server_uuid, + server_vnc_port, + ws_host_uuid, + ws_pid, + ws_source_port, + ssh_tunnel_host_uuid, + ssh_tunnel_pid, + ssh_tunnel_forward_port, + modified_date +) VALUES ( + ".$anvil->Database->quote($record_uuid).", + ".$anvil->Database->quote($server_uuid).", + ".$anvil->Database->quote($server_vnc_port).", + ".$anvil->Database->quote($ws_host_uuid).", + ".$anvil->Database->quote($ws_pid).", + ".$anvil->Database->quote($ws_source_port).", + ".$anvil->Database->quote($ssh_tunnel_host_uuid).", + ".$anvil->Database->quote($ssh_tunnel_pid).", + ".$anvil->Database->quote($ssh_tunnel_forward_port).", + ".$anvil->Database->quote($record_modified_date)." +);"; + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub update_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $server_vnc_port = $parameters->{server_vnc_port}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $ws_pid = $parameters->{ws_pid}; + my $ws_source_port = $parameters->{ws_source_port}; + my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; + my $set_string; + my $condition_string = "server_uuid = ".$anvil->Database->quote($server_uuid); + + if ((defined $ws_host_uuid) and (defined $ws_pid) and (defined $ws_source_port)) + { + $set_string = " +server_vnc_port = ".$anvil->Database->quote($server_vnc_port).", +ws_host_uuid = ".$anvil->Database->quote($ws_host_uuid).", +ws_pid = ".$anvil->Database->quote($ws_pid).", +ws_source_port = ".$anvil->Database->quote($ws_source_port)." +"; + } + elsif ((defined $ssh_tunnel_host_uuid) and (defined $ssh_tunnel_pid) and (defined $ssh_tunnel_forward_port)) + { + $set_string = " +ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid).", +ssh_tunnel_pid = ".$anvil->Database->quote($ssh_tunnel_pid).", +ssh_tunnel_forward_port = ".$anvil->Database->quote($ssh_tunnel_forward_port)." +"; + $condition_string = $condition_string." AND ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid); + } + + my $query = " +UPDATE public.vnc_pipes +SET ".$set_string." +WHERE ".$condition_string." +;"; + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub get_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $vnc_pipe_info; + + my $query = " +SELECT + hos.host_name, vnc.ws_pid, vnc.ssh_tunnel_pid +FROM + public.vnc_pipes AS vnc +JOIN + public.hosts AS hos +ON + vnc.ws_host_uuid = hos.host_uuid +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +AND + ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." +;"; + + my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $count = @{$results}; + + if ($count == 1) + { + my $row = $results->[0]; + + $vnc_pipe_info = {}; + $vnc_pipe_info->{host_name} = $row->[0]; + $vnc_pipe_info->{ws_pid} = $row->[1]; + $vnc_pipe_info->{ssh_tunnel_pid} = $row->[2]; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + host_name => $vnc_pipe_info->{host_name}, + ws_pid => $vnc_pipe_info->{ws_pid}, + ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} + } }); + } + + return $vnc_pipe_info; +} + +sub delete_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $ws_pid = $parameters->{ws_pid}; + my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + + my $query = "DELETE FROM public.vnc_pipes "; + + if (defined $ws_pid) + { + $query = $query."WHERE ws_pid = ".$anvil->Database->quote($ws_pid).";"; + } + elsif (defined $ssh_tunnel_pid) + { + $query = $query."WHERE ssh_tunnel_pid = ".$anvil->Database->quote($ssh_tunnel_pid).";"; + } + else + { + $query = $query." +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +AND + ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." +;"; + } + + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} + +sub open_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $anvil->Get->host_uuid(); + my $vnc_pipe_info; + + my $server_info = get_server_info({ server_uuid => $server_uuid }); + + if (not defined $server_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get server VM information." + } }); + + return; + } + + my $vnc_info = get_vnc_info($server_info); + + if (not defined $vnc_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get server VM VNC information." + } }); + + return; + } + + my $ws_info = start_websockify({ + server_uuid => $server_uuid, + host_name => $server_info->{host_name}, + host_uuid => $server_info->{host_uuid}, + target_port => $vnc_info->{port} + }); + + if (not defined $ws_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get websockify instance information." + } }); + + return; + } + + my $ssh_tunnel_info = start_ssh_tunnel({ + server_uuid => $server_uuid, + host_uuid => $host_uuid, + ws_host_name => $server_info->{host_name}, + ws_host_uuid => $server_info->{host_uuid}, + ws_source_port => $ws_info->{source_port} + }); + + if (not defined $ssh_tunnel_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get SSH tunnel instance information." + } }); + + return; + } + + if ($ws_info->{is_new} or $ssh_tunnel_info->{is_new}) + { + insert_vnc_pipe({ + server_uuid => $server_uuid, + server_vnc_port => $vnc_info->{port}, + ws_host_uuid => $server_info->{host_uuid}, + ws_pid => $ws_info->{pid}, + ws_source_port => $ws_info->{source_port}, + ssh_tunnel_host_uuid => $host_uuid, + ssh_tunnel_pid => $ssh_tunnel_info->{pid}, + ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} + }); + } + else + { + if ($ws_info->{is_update}) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + server_vnc_port => $vnc_info->{port}, + ws_host_uuid => $server_info->{host_uuid}, + ws_pid => $ws_info->{pid}, + ws_source_port => $ws_info->{source_port} + }); + } + + if ($ssh_tunnel_info->{is_update}) + { + update_vnc_pipe({ + server_uuid => $server_uuid, + ssh_tunnel_host_uuid => $host_uuid, + ssh_tunnel_pid => $ssh_tunnel_info->{pid}, + ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} + }); + } + } + + $vnc_pipe_info = { forward_port => $ssh_tunnel_info->{forward_port} }; + + return $vnc_pipe_info; +} + +sub close_vnc_pipe +{ + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $vnc_pipe_parameters = { server_uuid => $server_uuid, host_uuid => $anvil->Get->host_uuid() }; + my $vnc_pipe_info = get_vnc_pipe($vnc_pipe_parameters); + + if (not defined $vnc_pipe_info) + { + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + message => "Failed to get VNC pipe information." + } }); + + return; + } + + if (not is_websockify_in_use_by_others({ ws_pid => $vnc_pipe_info->{ws_pid} })) + { + stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); + } + + stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); + + delete_vnc_pipe($vnc_pipe_parameters); +} + +$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 }); +} + +$anvil->Database->get_hosts(); +$anvil->Database->get_anvils(); + +my $server_uuid = $anvil->data->{switches}{'server-uuid'}; +my $is_open = $anvil->data->{switches}{'open'}; +my $is_drop_table = $anvil->data->{switches}{'drop-table'}; + +$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + server_uuid => $server_uuid, + is_open => $is_open, + is_drop_table => $is_drop_table +} }); + +if ($server_uuid) +{ + create_vnc_pipes_table(); + + my $vnc_pipe_parameters = { server_uuid => $server_uuid }; + + if ($is_open) + { + my $vnc_pipe_info = open_vnc_pipe($vnc_pipe_parameters); + + print "protocol:ws,forward_port:".$vnc_pipe_info->{forward_port}."\n"; + } + else + { + close_vnc_pipe($vnc_pipe_parameters); + } +} +elsif ($is_drop_table) +{ + drop_vnc_pipes_table(); +} From 7d9013a60b72fc79c39b9119074b1c8bf33ea413 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 13:49:19 -0400 Subject: [PATCH 34/58] fix(tools): allow striker-manage-vnc-pipes to be executed as a job --- share/words.xml | 8 ++ tools/striker-manage-vnc-pipes | 147 +++++++++++++++++++++++++++------ 2 files changed, 128 insertions(+), 27 deletions(-) diff --git a/share/words.xml b/share/words.xml index 5e3a4916..554a2e9f 100644 --- a/share/words.xml +++ b/share/words.xml @@ -424,6 +424,11 @@ The attempt to start the servers appears to have failed. The return code '0' was 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) Failed to find the UUID column for the table: [#!variable!table!#]. The 'set_to' parameter: [#!variable!set_to!#] is invalid. It must be 'yes' or 'no'. + While opening VNC pipe, failed to get server VM information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. + While opening VNC pipe, failed to get server VM VNC information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. + While opening VNC pipe, failed to get websockify instance information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. + While opening VNC pipe, failed to get SSH tunnel instance information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. + While closing VNC pipe, failed to get VNC pipe information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. The server UUID: [#!variable!server_uuid!#] is not valid or was not found in the database. The Anvil! name: [#!variable!anvil_name!#] was not found in the database. The Anvil! UUID: [#!variable!anvil_uuid!#] is not valid or was not found in the database. @@ -2223,6 +2228,9 @@ Are you sure that you want to delete the server: [#!variable!server_name!#]? [Ty [ #!variable!number!# ]- #!variable!server_name!# - (Current state: [#!variable!server_state!#]) -=] Please select the Anvil! hosting the server you want to manage [=- [ #!variable!number!# ]- #!variable!anvil_name!# - #!variable!anvil_description!# + Preparing to manage VNC pipes. + Finished [#!variable!operation!#] VNC pipe for server UUID [#!variable!server_uuid!#] from host UUID [#!variable!host_uuid!#]. + Finished dropping VNC pipes table. Saved the mail server information successfully! diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 6431aab4..c2349b13 100644 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -798,16 +798,22 @@ sub open_vnc_pipe { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $anvil->Get->host_uuid(); + my $host_uuid = $parameters->{host_uuid}; my $vnc_pipe_info; my $server_info = get_server_info({ server_uuid => $server_uuid }); if (not defined $server_info) { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get server VM information." + $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0313", variables => { + server_uuid => $server_uuid, + host_uuid => $host_uuid } }); + $anvil->Job->update_progress({ + progress => 100, + message => "error_0313,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!", + job_status => "failed" + }); return; } @@ -816,9 +822,15 @@ sub open_vnc_pipe if (not defined $vnc_info) { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get server VM VNC information." + $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0314", variables => { + server_uuid => $server_uuid, + host_uuid => $host_uuid } }); + $anvil->Job->update_progress({ + progress => 100, + message => "error_0314,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!", + job_status => "failed" + }); return; } @@ -832,9 +844,15 @@ sub open_vnc_pipe if (not defined $ws_info) { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get websockify instance information." + $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0315", variables => { + server_uuid => $server_uuid, + host_uuid => $host_uuid } }); + $anvil->Job->update_progress({ + progress => 100, + message => "error_0315,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!", + job_status => "failed" + }); return; } @@ -849,9 +867,15 @@ sub open_vnc_pipe if (not defined $ssh_tunnel_info) { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get SSH tunnel instance information." + $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0316", variables => { + server_uuid => $server_uuid, + host_uuid => $host_uuid } }); + $anvil->Job->update_progress({ + progress => 100, + message => "error_0316,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!", + job_status => "failed" + }); return; } @@ -893,7 +917,12 @@ sub open_vnc_pipe } } - $vnc_pipe_info = { forward_port => $ssh_tunnel_info->{forward_port} }; + $anvil->Job->update_progress({ + progress => 100, + message => "message_0257,!!operation!opening!!,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!" + }); + + $vnc_pipe_info = { forward_port => defined $ssh_tunnel_info->{forward_port} ? $ssh_tunnel_info->{forward_port} : "" }; return $vnc_pipe_info; } @@ -902,14 +931,20 @@ sub close_vnc_pipe { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; - my $vnc_pipe_parameters = { server_uuid => $server_uuid, host_uuid => $anvil->Get->host_uuid() }; - my $vnc_pipe_info = get_vnc_pipe($vnc_pipe_parameters); + my $host_uuid = $parameters->{host_uuid}; + my $vnc_pipe_info = get_vnc_pipe($parameters); if (not defined $vnc_pipe_info) { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get VNC pipe information." + $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 1, key => "error_0317", variables => { + server_uuid => $server_uuid, + host_uuid => $host_uuid } }); + $anvil->Job->update_progress({ + progress => 100, + message => "error_0317,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!", + job_status => "failed" + }); return; } @@ -921,7 +956,12 @@ sub close_vnc_pipe stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); - delete_vnc_pipe($vnc_pipe_parameters); + delete_vnc_pipe($parameters); + + $anvil->Job->update_progress({ + progress => 100, + message => "message_0257,!!operation!closing!!,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!" + }); } $anvil->Get->switches; @@ -935,37 +975,90 @@ if (not $anvil->data->{sys}{database}{connections}) $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_0256" + }); + + foreach my $line (split/\n/, $anvil->data->{jobs}{job_data}) + { + if ($line =~ /server-uuid=(.*?)$/) + { + $anvil->data->{switches}{'power-off'} = $1; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + 'switches::server-uuid' => $anvil->data->{switches}{'server-uuid'} + } }); + } + + if ($line =~ /open=(.*?)$/) + { + $anvil->data->{switches}{'open'} = $1; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + 'switches::open' => $anvil->data->{switches}{'open'} + } }); + } + + if ($line =~ /drop-table=(.*?)$/) + { + $anvil->data->{switches}{'drop-table'} = $1; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + 'switches::drop-table' => $anvil->data->{switches}{'drop-table'} + } }); + } + } +} + $anvil->Database->get_hosts(); $anvil->Database->get_anvils(); -my $server_uuid = $anvil->data->{switches}{'server-uuid'}; -my $is_open = $anvil->data->{switches}{'open'}; -my $is_drop_table = $anvil->data->{switches}{'drop-table'}; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - server_uuid => $server_uuid, - is_open => $is_open, - is_drop_table => $is_drop_table + server_uuid => $anvil->data->{switches}{'server-uuid'}, + is_open => $anvil->data->{switches}{'open'}, + is_drop_table => $anvil->data->{switches}{'drop-table'} } }); -if ($server_uuid) +if ($anvil->data->{switches}{'server-uuid'}) { create_vnc_pipes_table(); - my $vnc_pipe_parameters = { server_uuid => $server_uuid }; + my $vnc_pipe_parameters = { + server_uuid => $anvil->data->{switches}{'server-uuid'}, + host_uuid => $anvil->Get->host_uuid() + }; - if ($is_open) + if ($anvil->data->{switches}{'open'}) { my $vnc_pipe_info = open_vnc_pipe($vnc_pipe_parameters); - print "protocol:ws,forward_port:".$vnc_pipe_info->{forward_port}."\n"; + if (not $anvil->data->{switches}{'job-uuid'}) + { + print "protocol:ws,forward_port:".$vnc_pipe_info->{forward_port}."\n"; + } } else { close_vnc_pipe($vnc_pipe_parameters); } } -elsif ($is_drop_table) +elsif ($anvil->data->{switches}{'drop-table'}) { drop_vnc_pipes_table(); + + $anvil->Job->update_progress({ progress => 100, message => "message_0258" }); } From 1d61c8fff729f1984a238a702cef9e0a2b6c9e4c Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 14:29:49 -0400 Subject: [PATCH 35/58] fix(cgi-bin): modify manage_vnc_pipes endpoint to trigger a job --- Anvil/Tools.pm | 1 + cgi-bin/manage_vnc_pipes | 921 +-------------------------------------- share/words.xml | 2 + 3 files changed, 26 insertions(+), 898 deletions(-) diff --git a/Anvil/Tools.pm b/Anvil/Tools.pm index 02d4ee28..e90a9d75 100644 --- a/Anvil/Tools.pm +++ b/Anvil/Tools.pm @@ -1221,6 +1221,7 @@ sub _set_paths 'striker-initialize-host' => "/usr/sbin/striker-initialize-host", 'striker-manage-install-target' => "/usr/sbin/striker-manage-install-target", '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-prep-database' => "/usr/sbin/striker-prep-database", diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 4ce47344..c9f36c24 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -22,910 +22,32 @@ my $anvil = Anvil::Tools->new(); $anvil->Log->level({ set => 2 }); -sub get_server_info +sub get_vnc_pipe_info { my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; - my $server_info; - - my $query = " -SELECT - ser.server_name, hos.host_name, hos.host_uuid -FROM - public.servers AS ser -JOIN - public.hosts AS hos -ON - ser.server_host_uuid = hos.host_uuid -WHERE - server_uuid = ".$anvil->Database->quote($server_uuid)." -;"; - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; - - if ($count == 1) - { - my $row = $results->[0]; - - $server_info = {}; - $server_info->{server_name} = $row->[0]; - $server_info->{host_name} = $row->[1]; - $server_info->{host_uuid} = $row->[2]; - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - server_name => $server_info->{server_name}, - host_name => $server_info->{host_name}, - host_uuid => $server_info->{host_uuid} - } }); - } - - return $server_info; -} - -sub get_vnc_info -{ - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $server_name = $parameters->{server_name}; - my $port_base = 5900; - # Requires root to access VM information. - my $shell_call = "virsh vncdisplay ".$server_name; - my $vnc_info; - - my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ - target => $host_name, - remote_user => "root", - shell_call => $shell_call, - 'close' => 1 - }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - shell_call => $shell_call, - shell_output => $shell_output, - shell_error => $shell_error, - shell_return_code => $shell_return_code - } }); - - if ($shell_return_code == 0) - { - my ($port_offset) = $shell_output =~ /:(\d+)$/; - - $vnc_info = { host_name => $host_name }; - $vnc_info->{port} = $port_base + int($port_offset); - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - port_offset => $port_offset, - vnc_port => $vnc_info->{port} - } }); - } - - return $vnc_info; -} - -sub is_websockify_process -{ - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $ws_pid = $parameters->{ws_pid}; - my $shell_call = "ps -e -o command -h -p ".$ws_pid; - - my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ - target => $host_name, - remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 - }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - shell_call => $shell_call, - shell_output => $shell_output, - shell_error => $shell_error, - shell_return_code => $shell_return_code - } }); - - return $shell_output =~ /websockify/ ? 1 : 0; -} - -sub is_ssh_process -{ - my $parameters = shift; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $shell_call = "ps -e -o command -h -p ".$ssh_tunnel_pid; - - 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 =~ /striker-open-ssh-tunnel/ ? 1 : 0; -} - -sub is_websockify_exists -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $parameters->{host_uuid}; - my $server_vnc_port = $parameters->{server_vnc_port}; - - my $query = " -SELECT - vnc.server_vnc_port, hos.host_name, hos.host_uuid, vnc.ws_pid, vnc.ws_source_port -FROM - public.vnc_pipes AS vnc -JOIN - public.hosts AS hos -ON - vnc.ws_host_uuid = hos.host_uuid -WHERE - vnc.server_uuid = ".$anvil->Database->quote($server_uuid)." -;"; - - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; - my $ws_exists_info = { exists_code => 0 }; - - if ($count > 0) - { - my $row = $results->[0]; - my $server_vnc_port_in_record = $row->[0]; - my $host_name = $row->[1]; - my $host_uuid_in_record = $row->[2]; - my $ws_pid = $row->[3]; - my $ws_source_port = $row->[4]; - my $clean_up_parameters = { host_name => $host_name, ws_pid => $ws_pid }; - - $ws_exists_info->{ws_pid} = $ws_pid; - $ws_exists_info->{ws_source_port} = $ws_source_port; - $ws_exists_info->{exists_code} = 1; - - if ($host_uuid ne $host_uuid_in_record) - { - # VNC server host mismatch; try to stop the recorded instance. - # Likely happens after a server migration. - stop_websockify($clean_up_parameters); - - # No need to preserve the websockify source port in - # this case because the tunnel will need to be replaced - # as well. - delete $ws_exists_info->{ws_source_port}; - - return $ws_exists_info; - } - - if ($server_vnc_port != $server_vnc_port_in_record) - { - # VNC server port mismatch; try to stop the recorded instance. - stop_websockify($clean_up_parameters); - - return $ws_exists_info; - } - - if (not is_websockify_process($clean_up_parameters)) - { - # The recorded instance died. - return $ws_exists_info; - } - - # Passed all tests; process considered exists. - $ws_exists_info->{exists_code} = 2; - } - - return $ws_exists_info; -} - -sub is_ssh_tunnel_exists -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; - my $ws_source_port = $parameters->{ws_source_port}; - - my $query = " -SELECT - ws_host_uuid, ws_source_port, ssh_tunnel_pid, ssh_tunnel_forward_port -FROM - public.vnc_pipes -WHERE - server_uuid = ".$anvil->Database->quote($server_uuid)." -AND - ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." -;"; - - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; - my $ssh_tunnel_exists_info = { exists_code => 0 }; - - if ($count == 1) - { - my $row = $results->[0]; - my $ws_host_uuid_in_record = $row->[0]; - my $ws_source_port_in_record = $row->[1]; - my $ssh_tunnel_pid = $row->[2]; - my $ssh_tunnel_forward_port = $row->[3]; - my $clean_up_parameters = { ssh_tunnel_pid => $ssh_tunnel_pid }; - - $ssh_tunnel_exists_info->{ssh_tunnel_pid} = $ssh_tunnel_pid; - $ssh_tunnel_exists_info->{ssh_tunnel_forward_port} = $ssh_tunnel_forward_port; - $ssh_tunnel_exists_info->{exists_code} = 1; - - if ($ws_host_uuid ne $ws_host_uuid_in_record) - { - # Websockify host mismatch; try to stop the recorded instance. - # Likely happens after a server migration. - stop_ssh_tunnel($clean_up_parameters); - - # No need to preserve the SSH tunnel forward port in - # this case because the websockify instance will need - # to be replaced as well. - delete $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; - - return $ssh_tunnel_exists_info; - } - - if ($ws_source_port != $ws_source_port_in_record) - { - # Websockify source port mismatch; try to stop the recorded instance. - stop_ssh_tunnel($clean_up_parameters); - - return $ssh_tunnel_exists_info; - } - - if (not is_ssh_process($clean_up_parameters)) - { - # The recorded tunnel died. - return $ssh_tunnel_exists_info; - } - - # Passed all tests; tunnel considered exists. - $ssh_tunnel_exists_info->{exists_code} = 2; - } - - return $ssh_tunnel_exists_info; -} - -sub is_websockify_in_use_by_others -{ - my $parameters = shift; - my $ws_pid = $parameters->{ws_pid}; - - my $query = "SELECT COUNT(*) FROM public.vnc_pipes WHERE ws_pid = ".$anvil->Database->quote($ws_pid).";"; - - my $count = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0]; - - return $count > 1 ? 1 : 0; -} - -sub start_websockify -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_name = $parameters->{host_name}; my $host_uuid = $parameters->{host_uuid}; - my $target_port = $parameters->{target_port}; - my $source_port = $parameters->{source_port}; - my $ws_info; - - my $ws_exists_info = is_websockify_exists({ - server_uuid => $server_uuid, - host_uuid => $host_uuid, - server_vnc_port => $target_port - }); - - if ($ws_exists_info->{exists_code} == 2) - { - $ws_info = {}; - $ws_info->{pid} = $ws_exists_info->{ws_pid}; - $ws_info->{source_port} = $ws_exists_info->{ws_source_port}; - } - else - { - if (not defined $source_port) - { - if (defined $ws_exists_info->{ws_source_port}) - { - $source_port = $ws_exists_info->{ws_source_port}; - } - else - { - my $source_port_base = 10000; - - $source_port = $source_port_base + $target_port; - } - } - - my $shell_call = "websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!"; - - my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ - target => $host_name, - remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 - }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - shell_call => $shell_call, - shell_output => $shell_output, - shell_error => $shell_error, - shell_return_code => $shell_return_code - } }); - - if ($shell_return_code == 0) - { - my ($ws_pid) = $shell_output =~ /pid:(\d+)$/; - - $ws_info = {}; - $ws_info->{pid} = $ws_pid; - $ws_info->{source_port} = $source_port; - - if ($ws_exists_info->{exists_code} == 1) - { - $ws_info->{is_update} = 1; - } - else - { - $ws_info->{is_new} = 1; - } - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - ws_pid => $ws_pid, - ws_source_port => $source_port, - ws_is_update => $ws_info->{is_update}, - ws_is_new => $ws_info->{is_new} - } }); - } - } - - return $ws_info; -} -sub stop_websockify -{ - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $ws_pid = $parameters->{ws_pid}; - - if (is_websockify_process($parameters)) - { - my $shell_call = "kill ".$ws_pid; - my $remote_call_parameters = { - target => $host_name, - remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 - }; - my $shell_output; - my $shell_error; - my $shell_return_code; - - ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - shell_call => $shell_call, - shell_output => $shell_output, - shell_error => $shell_error, - shell_return_code => $shell_return_code - } }); - - sleep(2); - - if (is_websockify_process($parameters)) - { - $shell_call = $shell_call =~ s/kill/kill -9/; - $remote_call_parameters->{shell_call} = $shell_call; - - ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); - $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 - } }); - } - } -} - -sub start_ssh_tunnel -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $parameters->{host_uuid}; - my $ws_host_name = $parameters->{ws_host_name}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; - my $ws_source_port = $parameters->{ws_source_port}; - my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; - my $ssh_tunnel_info; - - my $ssh_tunnel_exists_info = is_ssh_tunnel_exists({ - server_uuid => $server_uuid, - ssh_tunnel_host_uuid => $host_uuid, - ws_host_uuid => $ws_host_uuid, - ws_source_port => $ws_source_port - }); - - if ($ssh_tunnel_exists_info->{exists_code} == 2) - { - $ssh_tunnel_info = {}; - $ssh_tunnel_info->{pid} = $ssh_tunnel_exists_info->{ssh_tunnel_pid}; - $ssh_tunnel_info->{forward_port} = $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; - } - else - { - if (not defined $ssh_tunnel_forward_port) - { - if (defined $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}) - { - $ssh_tunnel_forward_port = $ssh_tunnel_exists_info->{ssh_tunnel_forward_port}; - } - else - { - $ssh_tunnel_forward_port = $ws_source_port; - } - } - - my $shell_call = $anvil->data->{path}{exe}{'striker-open-ssh-tunnel'} - ." --remote-user admin --target ".$ws_host_name - ." --forward-local-port ".$ssh_tunnel_forward_port - ." --forward-remote-port ".$ws_source_port - ." &>/dev/null & echo pid:\$!"; - - 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 - } }); - - if ($shell_return_code == 0) - { - my ($ssh_tunnel_pid) = $shell_output =~ /pid:(\d+)$/; - - $ssh_tunnel_info = {}; - $ssh_tunnel_info->{pid} = $ssh_tunnel_pid; - $ssh_tunnel_info->{forward_port} = $ssh_tunnel_forward_port; - - if ($ssh_tunnel_exists_info->{exists_code} == 1) - { - $ssh_tunnel_info->{is_update} = 1; - } - else - { - $ssh_tunnel_info->{is_new} = 1; - } - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - ssh_tunnel_pid => $ssh_tunnel_pid, - ssh_tunnel_forward_port => $ssh_tunnel_forward_port, - ssh_tunnel_is_update => $ssh_tunnel_info->{is_update}, - ssh_tunnel_is_new => $ssh_tunnel_info->{is_new} - } }); - } - } - - return $ssh_tunnel_info; -} - -sub stop_ssh_tunnel -{ - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - - if (is_ssh_process($parameters)) - { - my $shell_call = "kill ".$ssh_tunnel_pid; - my $is_remote_call = (defined $host_name) and ($host_name ne $anvil->Get->host_name()) ? 1 : 0; - my $remote_call_parameters = { - target => $host_name, - remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 - }; - my $shell_output; - my $shell_error; - my $shell_return_code; - - if ($is_remote_call) - { - ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); - } - else - { - ($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_error => $shell_error, - shell_return_code => $shell_return_code - } }); - - sleep(2); - - if (is_ssh_process($parameters)) - { - $shell_call = $shell_call =~ s/kill/kill -9/; - $remote_call_parameters->{shell_call} = $shell_call; - - if ($is_remote_call) - { - ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call($remote_call_parameters); - } - else - { - ($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_error => $shell_error, - shell_return_code => $shell_return_code - } }); - } - } -} - -sub stop_related -{ - my $parameters = shift; - my $ws_pid = $parameters->{ws_pid}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $select_pid_field; - my $condition_pid_field; - my $condition_pid_value; - my $stop_function; - - if (defined $ws_pid) - { - $select_pid_field = "ssh_tunnel_pid"; - $condition_pid_field = "ws_pid"; - $condition_pid_value = $ws_pid; - $stop_function = \&stop_ssh_tunnel - } - elsif (defined $ssh_tunnel_pid) - { - $select_pid_field = "ws_pid"; - $condition_pid_field = "ssh_tunnel_pid"; - $condition_pid_value = $ssh_tunnel_pid; - $stop_function = \&stop_websockify - } - - my $query = " -SELECT - hos.host_name, vnc.".$select_pid_field." -FROM - public.vnc_pipes AS vnc -JOIN - public.hosts AS hos -ON - vnc.ssh_tunnel_host_uuid = hos.host_uuid -WHERE - vnc.".$condition_pid_field." = ".$anvil->Database->quote($condition_pid_value)." -;"; - - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - - foreach my $row (@{$results}) - { - $stop_function->({ host_name => $row->[0], ws_pid => $row->[1], ssh_tunnel_pid => $row->[1] }); - } -} - -sub create_vnc_pipes_table -{ - my $query = " -CREATE TABLE IF NOT EXISTS public.vnc_pipes ( - uuid uuid not null primary key, - server_uuid uuid not null, - server_vnc_port numeric not null, - ws_host_uuid uuid not null, - ws_pid numeric not null, - ws_source_port numeric not null, - ssh_tunnel_host_uuid uuid not null, - ssh_tunnel_pid numeric not null, - ssh_tunnel_forward_port numeric not null, - modified_date timestamp with time zone not null -);"; - - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); -} - -sub drop_vnc_pipes_table -{ - my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; - - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); -} - -sub insert_vnc_pipe -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $server_vnc_port = $parameters->{server_vnc_port}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; - my $ws_pid = $parameters->{ws_pid}; - my $ws_source_port = $parameters->{ws_source_port}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; - - my $record_uuid = $anvil->Get->uuid(); - my $record_modified_date = $anvil->Database->refresh_timestamp(); - - my $query = " -INSERT INTO public.vnc_pipes ( - uuid, - server_uuid, - server_vnc_port, - ws_host_uuid, - ws_pid, - ws_source_port, - ssh_tunnel_host_uuid, - ssh_tunnel_pid, - ssh_tunnel_forward_port, - modified_date -) VALUES ( - ".$anvil->Database->quote($record_uuid).", - ".$anvil->Database->quote($server_uuid).", - ".$anvil->Database->quote($server_vnc_port).", - ".$anvil->Database->quote($ws_host_uuid).", - ".$anvil->Database->quote($ws_pid).", - ".$anvil->Database->quote($ws_source_port).", - ".$anvil->Database->quote($ssh_tunnel_host_uuid).", - ".$anvil->Database->quote($ssh_tunnel_pid).", - ".$anvil->Database->quote($ssh_tunnel_forward_port).", - ".$anvil->Database->quote($record_modified_date)." -);"; - - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); -} - -sub update_vnc_pipe -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $server_vnc_port = $parameters->{server_vnc_port}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; - my $ws_pid = $parameters->{ws_pid}; - my $ws_source_port = $parameters->{ws_source_port}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; - my $set_string; - my $condition_string = "server_uuid = ".$anvil->Database->quote($server_uuid); - - if ((defined $ws_host_uuid) and (defined $ws_pid) and (defined $ws_source_port)) - { - $set_string = " -server_vnc_port = ".$anvil->Database->quote($server_vnc_port).", -ws_host_uuid = ".$anvil->Database->quote($ws_host_uuid).", -ws_pid = ".$anvil->Database->quote($ws_pid).", -ws_source_port = ".$anvil->Database->quote($ws_source_port)." -"; - } - elsif ((defined $ssh_tunnel_host_uuid) and (defined $ssh_tunnel_pid) and (defined $ssh_tunnel_forward_port)) - { - $set_string = " -ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid).", -ssh_tunnel_pid = ".$anvil->Database->quote($ssh_tunnel_pid).", -ssh_tunnel_forward_port = ".$anvil->Database->quote($ssh_tunnel_forward_port)." -"; - $condition_string = $condition_string." AND ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid); - } - - my $query = " -UPDATE public.vnc_pipes -SET ".$set_string." -WHERE ".$condition_string." -;"; - - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); -} - -sub get_vnc_pipe -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $parameters->{host_uuid}; - my $vnc_pipe_info; + my $vnc_pipe_info = { protocol => "ws" }; my $query = " SELECT - hos.host_name, vnc.ws_pid, vnc.ssh_tunnel_pid + ssh_tunnel_forward_port FROM - public.vnc_pipes AS vnc -JOIN - public.hosts AS hos -ON - vnc.ws_host_uuid = hos.host_uuid -WHERE - server_uuid = ".$anvil->Database->quote($server_uuid)." -AND - ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." -;"; - - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; - - if ($count == 1) - { - my $row = $results->[0]; - - $vnc_pipe_info = {}; - $vnc_pipe_info->{host_name} = $row->[0]; - $vnc_pipe_info->{ws_pid} = $row->[1]; - $vnc_pipe_info->{ssh_tunnel_pid} = $row->[2]; - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - host_name => $vnc_pipe_info->{host_name}, - ws_pid => $vnc_pipe_info->{ws_pid}, - ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} - } }); - } - - return $vnc_pipe_info; -} - -sub delete_vnc_pipe -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $parameters->{host_uuid}; - my $ws_pid = $parameters->{ws_pid}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - - my $query = "DELETE FROM public.vnc_pipes "; - - if (defined $ws_pid) - { - $query = $query."WHERE ws_pid = ".$anvil->Database->quote($ws_pid).";"; - } - elsif (defined $ssh_tunnel_pid) - { - $query = $query."WHERE ssh_tunnel_pid = ".$anvil->Database->quote($ssh_tunnel_pid).";"; - } - else - { - $query = $query." + public.vnc_pipes WHERE server_uuid = ".$anvil->Database->quote($server_uuid)." AND ssh_tunnel_host_uuid = ".$anvil->Database->quote($host_uuid)." ;"; - } - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); -} + my $forward_port = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0]; -sub open_vnc_pipe -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_uuid = $anvil->Get->host_uuid(); - my $vnc_pipe_info; - - my $server_info = get_server_info({ server_uuid => $server_uuid }); - - if (not defined $server_info) - { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get server VM information." - } }); - - return; - } - - my $vnc_info = get_vnc_info($server_info); - - if (not defined $vnc_info) - { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get server VM VNC information." - } }); - - return; - } - - my $ws_info = start_websockify({ - server_uuid => $server_uuid, - host_name => $server_info->{host_name}, - host_uuid => $server_info->{host_uuid}, - target_port => $vnc_info->{port} - }); - - if (not defined $ws_info) - { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get websockify instance information." - } }); - - return; - } - - my $ssh_tunnel_info = start_ssh_tunnel({ - server_uuid => $server_uuid, - host_uuid => $host_uuid, - ws_host_name => $server_info->{host_name}, - ws_host_uuid => $server_info->{host_uuid}, - ws_source_port => $ws_info->{source_port} - }); - - if (not defined $ssh_tunnel_info) - { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get SSH tunnel instance information." - } }); - - return; - } - - if ($ws_info->{is_new} or $ssh_tunnel_info->{is_new}) - { - insert_vnc_pipe({ - server_uuid => $server_uuid, - server_vnc_port => $vnc_info->{port}, - ws_host_uuid => $server_info->{host_uuid}, - ws_pid => $ws_info->{pid}, - ws_source_port => $ws_info->{source_port}, - ssh_tunnel_host_uuid => $host_uuid, - ssh_tunnel_pid => $ssh_tunnel_info->{pid}, - ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} - }); - } - else - { - if ($ws_info->{is_update}) - { - update_vnc_pipe({ - server_uuid => $server_uuid, - server_vnc_port => $vnc_info->{port}, - ws_host_uuid => $server_info->{host_uuid}, - ws_pid => $ws_info->{pid}, - ws_source_port => $ws_info->{source_port} - }); - } - - if ($ssh_tunnel_info->{is_update}) - { - update_vnc_pipe({ - server_uuid => $server_uuid, - ssh_tunnel_host_uuid => $host_uuid, - ssh_tunnel_pid => $ssh_tunnel_info->{pid}, - ssh_tunnel_forward_port => $ssh_tunnel_info->{forward_port} - }); - } - } - - $vnc_pipe_info = { forward_port => $ssh_tunnel_info->{forward_port} }; + $vnc_pipe_info->{forward_port} = $forward_port; return $vnc_pipe_info; } -sub close_vnc_pipe -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $vnc_pipe_parameters = { server_uuid => $server_uuid, host_uuid => $anvil->Get->host_uuid() }; - my $vnc_pipe_info = get_vnc_pipe($vnc_pipe_parameters); - - if (not defined $vnc_pipe_info) - { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - message => "Failed to get VNC pipe information." - } }); - - return; - } - - if (not is_websockify_in_use_by_others({ ws_pid => $vnc_pipe_info->{ws_pid} })) - { - stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); - } - - stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); - - delete_vnc_pipe($vnc_pipe_parameters); -} - $anvil->Get->switches; $anvil->Database->connect; @@ -978,25 +100,28 @@ $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, lis if ($server_uuid) { - create_vnc_pipes_table(); - - my $vnc_pipe_parameters = { server_uuid => $server_uuid }; + my $host_uuid = $anvil->Get->host_uuid(); + my $operation_string = $is_open ? "open" : "close"; + 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=".$is_open, + 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::".$operation_string, + job_progress => 0, + job_title => "job_0350" + }); if ($is_open) { - my $vnc_pipe_info = open_vnc_pipe($vnc_pipe_parameters); + # Wait until the job is complete before fetching for the results. + while($anvil->Job->get_job_details({ job_uuid => $job_uuid })) + { + sleep(2); + } - $response_body->{protocol} = "ws"; - $response_body->{forward_port} = $vnc_pipe_info->{forward_port}; + $response_body = get_vnc_pipe_info({ server_uuid => $server_uuid, host_uuid => $host_uuid }); } - else - { - close_vnc_pipe($vnc_pipe_parameters); - } -} -elsif ($anvil->data->{switches}{'drop-table'}) -{ - drop_vnc_pipes_table(); } print JSON->new->utf8->encode($response_body)."\n"; diff --git a/share/words.xml b/share/words.xml index 554a2e9f..65a62e0a 100644 --- a/share/words.xml +++ b/share/words.xml @@ -1121,6 +1121,8 @@ It should be provisioned in the next minute or two. Both the BCN1 and SN1 links are working between the nodes. Checking corosync now... Synchronizing the new corosync config exited with return code: [#!variable!return_code!#] and output: [#!variable!output!#] Loading the new corosync config exited with return code: [#!variable!return_code!#] and output: [#!variable!output!#] + Manage VNC Pipes + Perform VNC pipe operation [#!variable!operation!#] for server UUID [#!variable!server_uuid!#] from host UUID [#!variable!host_uuid!#]. Manage a server menu: * Please enter the name of the server you want to manage From 1fec288ad094d83ada69c93cddcd115fc7d71230 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 15:04:02 -0400 Subject: [PATCH 36/58] fix(tools): make striker-manage-vnc-pipes executable --- tools/striker-manage-vnc-pipes | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tools/striker-manage-vnc-pipes diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes old mode 100644 new mode 100755 From 64de564472faa555a024151661abff0b69042caa Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 15:41:04 -0400 Subject: [PATCH 37/58] fix(cgi-bin): avoid concat error on undefined --is-open --- cgi-bin/manage_vnc_pipes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index c9f36c24..40f83d0c 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -100,6 +100,8 @@ $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, lis if ($server_uuid) { + $is_open = defined $is_open ? $is_open : ""; + my $host_uuid = $anvil->Get->host_uuid(); my $operation_string = $is_open ? "open" : "close"; my $job_uuid = $anvil->Database->insert_or_update_jobs({ From 2bef084118eb48825ce8910dbf5f143f11b4f417 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 16:08:37 -0400 Subject: [PATCH 38/58] fix(cgi-bin): always wait until job is done before responding --- cgi-bin/manage_vnc_pipes | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 40f83d0c..dbf4ffb2 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -114,14 +114,14 @@ if ($server_uuid) job_title => "job_0350" }); - if ($is_open) + # Wait until the job is complete before continuing. + while($anvil->Job->get_job_details({ job_uuid => $job_uuid })) { - # Wait until the job is complete before fetching for the results. - while($anvil->Job->get_job_details({ job_uuid => $job_uuid })) - { - sleep(2); - } + sleep(2); + } + if ($is_open) + { $response_body = get_vnc_pipe_info({ server_uuid => $server_uuid, host_uuid => $host_uuid }); } } From b2555f2337204d1ba3356c88b948b2014c3ba174 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 16:13:42 -0400 Subject: [PATCH 39/58] fix(cgi-bin): correct typo in job data in manage_vnc_pipes endpoint --- cgi-bin/manage_vnc_pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index dbf4ffb2..acbace3f 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -106,7 +106,7 @@ if ($server_uuid) my $operation_string = $is_open ? "open" : "close"; 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=".$is_open, + job_data => "server-uuid=".$server_uuid."\nopen=".$is_open, 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::".$operation_string, From ffc1fb096a6be79aa31e67ec0e3abcb7cd6aa249 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 16:18:26 -0400 Subject: [PATCH 40/58] fix(tools): correct switch name typo in striker-manage-vnc-pipes --- tools/striker-manage-vnc-pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index c2349b13..00c5c758 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -1000,7 +1000,7 @@ if ($anvil->data->{switches}{'job-uuid'}) { if ($line =~ /server-uuid=(.*?)$/) { - $anvil->data->{switches}{'power-off'} = $1; + $anvil->data->{switches}{'server-uuid'} = $1; $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { 'switches::server-uuid' => $anvil->data->{switches}{'server-uuid'} } }); From bb155a5786a57b413a1128d0bd12e338f9ea0b8a Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 16:32:08 -0400 Subject: [PATCH 41/58] fix(tools): update job progress in catch-all case --- share/words.xml | 1 + tools/striker-manage-vnc-pipes | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/share/words.xml b/share/words.xml index 65a62e0a..adf67d87 100644 --- a/share/words.xml +++ b/share/words.xml @@ -2233,6 +2233,7 @@ Are you sure that you want to delete the server: [#!variable!server_name!#]? [Ty Preparing to manage VNC pipes. 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. Saved the mail server information successfully! diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 00c5c758..670f9813 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -1062,3 +1062,7 @@ elsif ($anvil->data->{switches}{'drop-table'}) $anvil->Job->update_progress({ progress => 100, message => "message_0258" }); } +else +{ + $anvil->Job->update_progress({ progress => 100, message => "message_0259" }); +} From e79a3989ebb4cb9456200233e1e6a76ccbf80bf5 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 16:43:55 -0400 Subject: [PATCH 42/58] fix(cgi-bin): repair wait until job is complete before responding --- cgi-bin/manage_vnc_pipes | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index acbace3f..08dd149d 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -48,6 +48,25 @@ AND 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; @@ -115,7 +134,7 @@ if ($server_uuid) }); # Wait until the job is complete before continuing. - while($anvil->Job->get_job_details({ job_uuid => $job_uuid })) + while(is_job_incomplete({ job_uuid => $job_uuid })) { sleep(2); } From f8b45f5a740101e9733ac6d0b6a48877956a6e92 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 20:19:06 -0400 Subject: [PATCH 43/58] fix(cgi-bin): avoid concat blank to job_data in manage_vnc_pipes endpoint --- cgi-bin/manage_vnc_pipes | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 08dd149d..e9583e48 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -119,13 +119,12 @@ $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, lis if ($server_uuid) { - $is_open = defined $is_open ? $is_open : ""; - - my $host_uuid = $anvil->Get->host_uuid(); - my $operation_string = $is_open ? "open" : "close"; - my $job_uuid = $anvil->Database->insert_or_update_jobs({ + 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=".$is_open, + 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::".$operation_string, From e4436be17be131f64d04bd7d90f9f53875cbd89d Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 20:19:51 -0400 Subject: [PATCH 44/58] fix(tools): do checks and kills as root --- tools/striker-manage-vnc-pipes | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 670f9813..3341fa5f 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -108,9 +108,7 @@ sub is_websockify_process my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ target => $host_name, - remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 + shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call, @@ -337,8 +335,7 @@ sub start_websockify my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ target => $host_name, remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 + shell_call => $shell_call }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call, @@ -387,9 +384,7 @@ sub stop_websockify my $shell_call = "kill ".$ws_pid; my $remote_call_parameters = { target => $host_name, - remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 + shell_call => $shell_call }; my $shell_output; my $shell_error; @@ -512,9 +507,7 @@ sub stop_ssh_tunnel my $is_remote_call = (defined $host_name) and ($host_name ne $anvil->Get->host_name()) ? 1 : 0; my $remote_call_parameters = { target => $host_name, - remote_user => "admin", - shell_call => $shell_call, - 'close' => 1 + shell_call => $shell_call }; my $shell_output; my $shell_error; From 3a8f4c339bc66c8fa9464ae030cde83158bdf433 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 20:34:46 -0400 Subject: [PATCH 45/58] fix(tools): use VNC port in variables table if available --- tools/striker-manage-vnc-pipes | 68 ++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 3341fa5f..cb005a7e 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -65,35 +65,59 @@ sub get_vnc_info my $parameters = shift; my $host_name = $parameters->{host_name}; my $server_name = $parameters->{server_name}; + my $server_uuid = $parameters->{server_uuid}; my $port_base = 5900; # Requires root to access VM information. my $shell_call = "virsh vncdisplay ".$server_name; my $vnc_info; - my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ - target => $host_name, - remote_user => "root", - shell_call => $shell_call, - 'close' => 1 - }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - shell_call => $shell_call, - shell_output => $shell_output, - shell_error => $shell_error, - shell_return_code => $shell_return_code - } }); + my $query = " +SELECT + variable_value +FROM + public.variables +WHERE + variable_name = 'server::vnc_port' +AND + variable_source_table = 'servers' +AND + variable_source_uuid = ".$anvil->Database->quote($server_uuid)." +;"; - if ($shell_return_code == 0) - { - my ($port_offset) = $shell_output =~ /:(\d+)$/; + my $vnc_port_in_record = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0]; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { vnc_port_in_record => $vnc_port_in_record } }); + if ($vnc_port_in_record) + { $vnc_info = { host_name => $host_name }; - $vnc_info->{port} = $port_base + int($port_offset); - + $vnc_info->{port} = $vnc_port_in_record; + } + else + { + my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ + target => $host_name, + remote_user => "root", + shell_call => $shell_call + }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - port_offset => $port_offset, - vnc_port => $vnc_info->{port} + shell_call => $shell_call, + shell_output => $shell_output, + shell_error => $shell_error, + shell_return_code => $shell_return_code } }); + + if ($shell_return_code == 0) + { + my ($port_offset) = $shell_output =~ /:(\d+)$/; + + $vnc_info = { host_name => $host_name }; + $vnc_info->{port} = $port_base + int($port_offset); + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + port_offset => $port_offset, + vnc_port => $vnc_info->{port} + } }); + } } return $vnc_info; @@ -811,7 +835,11 @@ sub open_vnc_pipe return; } - my $vnc_info = get_vnc_info($server_info); + my $vnc_info = get_vnc_info({ + host_name => $server_info->{host_name}, + server_name => $server_info->{server_name}, + server_uuid => $server_info->{server_uuid} + }); if (not defined $vnc_info) { From 2b6e1bb51b74b368e8465a46afc013ce569199f7 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 20:38:54 -0400 Subject: [PATCH 46/58] chore(share): add missing labels to error messages --- share/words.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/share/words.xml b/share/words.xml index adf67d87..43fbafb9 100644 --- a/share/words.xml +++ b/share/words.xml @@ -424,11 +424,11 @@ The attempt to start the servers appears to have failed. The return code '0' was 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) Failed to find the UUID column for the table: [#!variable!table!#]. The 'set_to' parameter: [#!variable!set_to!#] is invalid. It must be 'yes' or 'no'. - While opening VNC pipe, failed to get server VM information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. - While opening VNC pipe, failed to get server VM VNC information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. - While opening VNC pipe, failed to get websockify instance information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. - While opening VNC pipe, failed to get SSH tunnel instance information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. - While closing VNC pipe, failed to get VNC pipe information with [#!variable!server_uuid!#] and [#!variable!host_uuid!#]. + While opening VNC pipe, failed to get server VM information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#]. + While opening VNC pipe, failed to get server VM VNC information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#]. + While opening VNC pipe, failed to get websockify instance information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#]. + While opening VNC pipe, failed to get SSH tunnel instance information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#]. + While closing VNC pipe, failed to get VNC pipe information with server UUID [#!variable!server_uuid!#] and host UUID [#!variable!host_uuid!#]. The server UUID: [#!variable!server_uuid!#] is not valid or was not found in the database. The Anvil! name: [#!variable!anvil_name!#] was not found in the database. The Anvil! UUID: [#!variable!anvil_uuid!#] is not valid or was not found in the database. From e50bfc7308aed0af3ecfacd6f6c5e27611cc6323 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 20:49:15 -0400 Subject: [PATCH 47/58] fix(tools): correct typo in passing server_uuid to get_vnc_info() --- tools/striker-manage-vnc-pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index cb005a7e..dfe2fafc 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -838,7 +838,7 @@ sub open_vnc_pipe my $vnc_info = get_vnc_info({ host_name => $server_info->{host_name}, server_name => $server_info->{server_name}, - server_uuid => $server_info->{server_uuid} + server_uuid => $server_uuid }); if (not defined $vnc_info) From 1f8e35ad0a98c6f07bded5f1fc4ad2d75326d58d Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 20:56:52 -0400 Subject: [PATCH 48/58] build(cgi-bin): include manage_vnc_pipes endpoint into makefile --- cgi-bin/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/cgi-bin/Makefile.am b/cgi-bin/Makefile.am index c35b7579..5343314e 100644 --- a/cgi-bin/Makefile.am +++ b/cgi-bin/Makefile.am @@ -10,6 +10,7 @@ dist_cgibin_SCRIPTS = \ get_servers \ get_shared_storage \ get_status \ + manage_vnc_pipes \ set_membership \ set_power \ striker \ From 549758b2f2ca5c4c87f468e1cdbdd19043192f63 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 20:57:52 -0400 Subject: [PATCH 49/58] build(tools): include support scripts for manager_vnc_pipes endpoint into makefile --- tools/Makefile.am | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/Makefile.am b/tools/Makefile.am index b2828c12..f23aa192 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -39,6 +39,8 @@ dist_sbin_SCRIPTS = \ striker-initialize-host \ striker-manage-install-target \ striker-manage-peers \ + striker-manage-vnc-pipes \ + striker-open-ssh-tunnel \ striker-parse-os-list \ striker-parse-oui \ striker-prep-database \ From b3b6da82599a8349c69644ea700a7a50784e991f Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Fri, 16 Jul 2021 21:03:41 -0400 Subject: [PATCH 50/58] chore(cgi-bin): remove debug log level from manage_vnc_pipes and its support scripts --- cgi-bin/manage_vnc_pipes | 2 -- tools/striker-manage-vnc-pipes | 2 -- tools/striker-open-ssh-tunnel | 2 -- 3 files changed, 6 deletions(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index e9583e48..421faf49 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -20,8 +20,6 @@ if (($running_directory =~ /^\./) && ($ENV{PWD})) my $anvil = Anvil::Tools->new(); -$anvil->Log->level({ set => 2 }); - sub get_vnc_pipe_info { my $parameters = shift; diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index dfe2fafc..0a9f3f31 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -18,8 +18,6 @@ if (($running_directory =~ /^\./) && ($ENV{PWD})) my $anvil = Anvil::Tools->new(); -$anvil->Log->level({ set => 2 }); - sub get_server_info { my $parameters = shift; diff --git a/tools/striker-open-ssh-tunnel b/tools/striker-open-ssh-tunnel index 4c28f6dc..7af7575d 100755 --- a/tools/striker-open-ssh-tunnel +++ b/tools/striker-open-ssh-tunnel @@ -22,8 +22,6 @@ if (($running_directory =~ /^\./) && ($ENV{PWD})) my $anvil = Anvil::Tools->new(); my $ssh_fh; -$anvil->Log->level({ set => 2 }); - sub open_ssh_tunnel { my $parameters = shift; From 7e447000b4767ead8eca2ebb59ea2cef2ff6cf57 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Mon, 19 Jul 2021 20:04:35 -0400 Subject: [PATCH 51/58] fix(cgi-bin): use unspecified instead of loopback address in SSH tunnel --- tools/striker-open-ssh-tunnel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/striker-open-ssh-tunnel b/tools/striker-open-ssh-tunnel index 7af7575d..bd0323f8 100755 --- a/tools/striker-open-ssh-tunnel +++ b/tools/striker-open-ssh-tunnel @@ -81,7 +81,7 @@ WHERE hos.host_name = ".$anvil->Database->quote($target)." } $ssh_fh->system({ ssh_opts => [ "-O", "forward", - "-L".$forward_local_port.":localhost:".$forward_remote_port ] }); + "-L0.0.0.0:".$forward_local_port.":0.0.0.0:".$forward_remote_port ] }); return 0; } From 214ef00647fdc90cf0e6ed1a56b0fb0e9b0faaf7 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 20 Jul 2021 11:49:01 -0400 Subject: [PATCH 52/58] fix(cgi-bin): allow VNC pipes for different servers to be managed in parallel --- cgi-bin/manage_vnc_pipes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 421faf49..47c8a5fb 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -125,7 +125,7 @@ if ($server_uuid) 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::".$operation_string, + job_name => "cgi-bin::manage_vnc_pipes::".$server_uuid."::".$operation_string, job_progress => 0, job_title => "job_0350" }); From cdb66019d3312181187d0c9f7523bfa57a22ec53 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 20 Jul 2021 14:34:15 -0400 Subject: [PATCH 53/58] fix(tools): avoid port conflict --- tools/striker-manage-vnc-pipes | 75 +++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 0a9f3f31..7dd9d53c 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -121,6 +121,50 @@ AND return $vnc_info; } +sub get_available_port +{ + my $parameters = shift; + my $start_port = $parameters->{start_port}; + my $host_name = $parameters->{host_name}; + my $step_operator = ((defined $parameters->{step_operator}) and ($parameters->{step_operator} =~ /^[+-]$/)) ? $parameters->{step_operator} : "+"; + my $step_size = ( + (defined $parameters->{step_size}) + and ($parameters->{step_size} =~ /^\d+$/) + and ($parameters->{step_size} > 0) + ) ? $parameters->{step_size} : 1; + my $shell_output; + my $shell_error; + my $shell_return_code; + my $available_port; + + my $shell_call = "ss_output=\$(ss --all --tcp --numeric) && port=".$start_port." && while egrep -q \":\${port}[[:space:]]+[^[:space:]]+\" <<<\$ss_output; do (( port ".$step_operator."= ".$step_size." )); done && echo \$port"; + + my $is_remote_call = is_remote_host_name($host_name); + + if ($is_remote_call) + { + ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ target => $host_name, shell_call => $shell_call }); + } + else + { + ($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_error => $shell_error, + shell_return_code => $shell_return_code + } }); + + if ($shell_return_code == 0) + { + $available_port = $shell_output; + } + + return $available_port; +} + sub is_websockify_process { my $parameters = shift; @@ -314,6 +358,13 @@ sub is_websockify_in_use_by_others return $count > 1 ? 1 : 0; } +sub is_remote_host_name +{ + my $host_name = shift; + + return ((defined $host_name) and ($host_name ne $anvil->Get->host_name())) ? 1 : 0; +} + sub start_websockify { my $parameters = shift; @@ -352,6 +403,16 @@ sub start_websockify } } + $source_port = get_available_port({ start_port => $source_port, host_name => $host_name }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + source_port => $source_port + } }); + + if (not defined $source_port) + { + return; + } + my $shell_call = "websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!"; my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ @@ -475,6 +536,16 @@ sub start_ssh_tunnel } } + $ssh_tunnel_forward_port = get_available_port({ start_port => $ssh_tunnel_forward_port }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + ssh_tunnel_forward_port => $ssh_tunnel_forward_port + } }); + + if (not defined $ssh_tunnel_forward_port) + { + return; + } + my $shell_call = $anvil->data->{path}{exe}{'striker-open-ssh-tunnel'} ." --remote-user admin --target ".$ws_host_name ." --forward-local-port ".$ssh_tunnel_forward_port @@ -526,7 +597,7 @@ sub stop_ssh_tunnel if (is_ssh_process($parameters)) { my $shell_call = "kill ".$ssh_tunnel_pid; - my $is_remote_call = (defined $host_name) and ($host_name ne $anvil->Get->host_name()) ? 1 : 0; + my $is_remote_call = is_remote_host_name($host_name); my $remote_call_parameters = { target => $host_name, shell_call => $shell_call @@ -1065,7 +1136,7 @@ if ($anvil->data->{switches}{'server-uuid'}) { my $vnc_pipe_info = open_vnc_pipe($vnc_pipe_parameters); - if (not $anvil->data->{switches}{'job-uuid'}) + if ((not $anvil->data->{switches}{'job-uuid'}) and (defined $vnc_pipe_info)) { print "protocol:ws,forward_port:".$vnc_pipe_info->{forward_port}."\n"; } From 5f6bc3d3b1564bbcdf859d7261ed28765078d247 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 20 Jul 2021 15:23:16 -0400 Subject: [PATCH 54/58] build(cgi-bin): require websockify on server VM hosts in specfile --- anvil.spec.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/anvil.spec.in b/anvil.spec.in index 1d045b3f..a7804d62 100644 --- a/anvil.spec.in +++ b/anvil.spec.in @@ -163,6 +163,7 @@ 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 @@ -191,6 +192,7 @@ 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 From 0f1c3d2435fd6db2dfe35aae958d92e620e1c383 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 20 Jul 2021 18:20:47 -0400 Subject: [PATCH 55/58] chore(tools): remove unused function from striker-manage-vnc-pipes --- tools/striker-manage-vnc-pipes | 46 ---------------------------------- 1 file changed, 46 deletions(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 7dd9d53c..76b3d673 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -648,52 +648,6 @@ sub stop_ssh_tunnel } } -sub stop_related -{ - my $parameters = shift; - my $ws_pid = $parameters->{ws_pid}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $select_pid_field; - my $condition_pid_field; - my $condition_pid_value; - my $stop_function; - - if (defined $ws_pid) - { - $select_pid_field = "ssh_tunnel_pid"; - $condition_pid_field = "ws_pid"; - $condition_pid_value = $ws_pid; - $stop_function = \&stop_ssh_tunnel - } - elsif (defined $ssh_tunnel_pid) - { - $select_pid_field = "ws_pid"; - $condition_pid_field = "ssh_tunnel_pid"; - $condition_pid_value = $ssh_tunnel_pid; - $stop_function = \&stop_websockify - } - - my $query = " -SELECT - hos.host_name, vnc.".$select_pid_field." -FROM - public.vnc_pipes AS vnc -JOIN - public.hosts AS hos -ON - vnc.ssh_tunnel_host_uuid = hos.host_uuid -WHERE - vnc.".$condition_pid_field." = ".$anvil->Database->quote($condition_pid_value)." -;"; - - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - - foreach my $row (@{$results}) - { - $stop_function->({ host_name => $row->[0], ws_pid => $row->[1], ssh_tunnel_pid => $row->[1] }); - } -} - sub create_vnc_pipes_table { my $query = " From 8da318c933f0eb53430169a26d4621b303628a81 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 21 Jul 2021 10:58:36 -0400 Subject: [PATCH 56/58] fix(tools): patch failure to fix 2nd pipe after server migration --- tools/striker-manage-vnc-pipes | 132 +++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 47 deletions(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 76b3d673..0447b33a 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -207,11 +207,12 @@ sub is_websockify_exists my $parameters = shift; my $server_uuid = $parameters->{server_uuid}; my $host_uuid = $parameters->{host_uuid}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; my $server_vnc_port = $parameters->{server_vnc_port}; my $query = " SELECT - vnc.server_vnc_port, hos.host_name, hos.host_uuid, vnc.ws_pid, vnc.ws_source_port + vnc.server_vnc_port, hos.host_name, vnc.ws_host_uuid, vnc.ws_pid, vnc.ws_source_port, vnc.ssh_tunnel_host_uuid FROM public.vnc_pipes AS vnc JOIN @@ -223,24 +224,19 @@ WHERE ;"; my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; my $ws_exists_info = { exists_code => 0 }; - if ($count > 0) + foreach my $row (@{$results}) { - my $row = $results->[0]; my $server_vnc_port_in_record = $row->[0]; - my $host_name = $row->[1]; - my $host_uuid_in_record = $row->[2]; + my $ws_host_name = $row->[1]; + my $ws_host_uuid_in_record = $row->[2]; my $ws_pid = $row->[3]; my $ws_source_port = $row->[4]; - my $clean_up_parameters = { host_name => $host_name, ws_pid => $ws_pid }; + my $ssh_tunnel_host_uuid = $row->[5]; + my $clean_up_parameters = { host_name => $ws_host_name, ws_pid => $ws_pid }; - $ws_exists_info->{ws_pid} = $ws_pid; - $ws_exists_info->{ws_source_port} = $ws_source_port; - $ws_exists_info->{exists_code} = 1; - - if ($host_uuid ne $host_uuid_in_record) + if ($ws_host_uuid ne $ws_host_uuid_in_record) { # VNC server host mismatch; try to stop the recorded instance. # Likely happens after a server migration. @@ -249,29 +245,50 @@ WHERE # No need to preserve the websockify source port in # this case because the tunnel will need to be replaced # as well. - delete $ws_exists_info->{ws_source_port}; - - return $ws_exists_info; + $ws_exists_info->{ws_source_port} = undef(); + $ws_exists_info->{exists_code} = 1; } - - if ($server_vnc_port != $server_vnc_port_in_record) + elsif ($server_vnc_port != $server_vnc_port_in_record) { # VNC server port mismatch; try to stop the recorded instance. stop_websockify($clean_up_parameters); - return $ws_exists_info; - } + if (not exists $ws_exists_info->{ws_source_port}) + { + $ws_exists_info->{ws_source_port} = $ws_source_port; + } - if (not is_websockify_process($clean_up_parameters)) + $ws_exists_info->{exists_code} = 1; + } + elsif (not is_websockify_process($clean_up_parameters)) { + if (not exists $ws_exists_info->{ws_source_port}) + { + $ws_exists_info->{ws_source_port} = $ws_source_port; + } + # The recorded instance died. - return $ws_exists_info; + $ws_exists_info->{exists_code} = 1; + } + else + { + # Found one websockify instance that passed all tests. + $ws_exists_info->{ws_pid} = $ws_pid; + $ws_exists_info->{ws_source_port} = $ws_source_port; + # Code 2: the websockify instance is not recorded for the pipe made from the current host, do update only. + # Code 3: the websockify instance is recorded for the pipe made from the current host, do nothing. + $ws_exists_info->{exists_code} = $host_uuid eq $ssh_tunnel_host_uuid ? 3 : 2; + # Don't continue the loop because all pipes should align to this verified instance. + last; } - - # Passed all tests; process considered exists. - $ws_exists_info->{exists_code} = 2; } + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + 'ws_exists_info::ws_pid' => $ws_exists_info->{ws_pid}, + 'ws_exists_info::ws_source_port' => $ws_exists_info->{ws_source_port}, + 'ws_exists_info::exists_code' => $ws_exists_info->{exists_code} + } }); + return $ws_exists_info; } @@ -367,25 +384,32 @@ sub is_remote_host_name sub start_websockify { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $host_name = $parameters->{host_name}; - my $host_uuid = $parameters->{host_uuid}; - my $target_port = $parameters->{target_port}; - my $source_port = $parameters->{source_port}; + my $parameters = shift; + my $server_uuid = $parameters->{server_uuid}; + my $host_uuid = $parameters->{host_uuid}; + my $ws_host_name = $parameters->{ws_host_name}; + my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $target_port = $parameters->{target_port}; + my $source_port = $parameters->{source_port}; my $ws_info; my $ws_exists_info = is_websockify_exists({ server_uuid => $server_uuid, host_uuid => $host_uuid, + ws_host_uuid => $ws_host_uuid, server_vnc_port => $target_port }); - if ($ws_exists_info->{exists_code} == 2) + if ($ws_exists_info->{exists_code} =~ /^[23]$/) { $ws_info = {}; $ws_info->{pid} = $ws_exists_info->{ws_pid}; $ws_info->{source_port} = $ws_exists_info->{ws_source_port}; + + if ($ws_exists_info->{exists_code} == 2) + { + $ws_info->{is_update} = 1; + } } else { @@ -403,7 +427,7 @@ sub start_websockify } } - $source_port = get_available_port({ start_port => $source_port, host_name => $host_name }); + $source_port = get_available_port({ start_port => $source_port, host_name => $ws_host_name }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { source_port => $source_port } }); @@ -416,7 +440,7 @@ sub start_websockify my $shell_call = "websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!"; my ($shell_output, $shell_error, $shell_return_code) = $anvil->Remote->call({ - target => $host_name, + target => $ws_host_name, remote_user => "admin", shell_call => $shell_call }); @@ -729,9 +753,14 @@ sub update_vnc_pipe my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; my $ssh_tunnel_forward_port = $parameters->{ssh_tunnel_forward_port}; my $set_string; - my $condition_string = "server_uuid = ".$anvil->Database->quote($server_uuid); - if ((defined $ws_host_uuid) and (defined $ws_pid) and (defined $ws_source_port)) + if ((not defined $server_uuid) or (not defined $ssh_tunnel_host_uuid)) + { + # Failed to build query condition; don't continue. + return; + } + + if ((defined $server_vnc_port) and (defined $ws_host_uuid) and (defined $ws_pid) and (defined $ws_source_port)) { $set_string = " server_vnc_port = ".$anvil->Database->quote($server_vnc_port).", @@ -740,20 +769,27 @@ ws_pid = ".$anvil->Database->quote($ws_pid).", ws_source_port = ".$anvil->Database->quote($ws_source_port)." "; } - elsif ((defined $ssh_tunnel_host_uuid) and (defined $ssh_tunnel_pid) and (defined $ssh_tunnel_forward_port)) + elsif ((defined $ssh_tunnel_pid) and (defined $ssh_tunnel_forward_port)) { $set_string = " ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid).", ssh_tunnel_pid = ".$anvil->Database->quote($ssh_tunnel_pid).", ssh_tunnel_forward_port = ".$anvil->Database->quote($ssh_tunnel_forward_port)." "; - $condition_string = $condition_string." AND ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid); + } + else + { + # Failed to build query set key-value pairs; don't continue. + return; } my $query = " UPDATE public.vnc_pipes SET ".$set_string." -WHERE ".$condition_string." +WHERE + server_uuid = ".$anvil->Database->quote($server_uuid)." +AND + ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid)." ;"; $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); @@ -880,10 +916,11 @@ sub open_vnc_pipe } my $ws_info = start_websockify({ - server_uuid => $server_uuid, - host_name => $server_info->{host_name}, - host_uuid => $server_info->{host_uuid}, - target_port => $vnc_info->{port} + server_uuid => $server_uuid, + host_uuid => $host_uuid, + ws_host_name => $server_info->{host_name}, + ws_host_uuid => $server_info->{host_uuid}, + target_port => $vnc_info->{port} }); if (not defined $ws_info) @@ -942,11 +979,12 @@ sub open_vnc_pipe if ($ws_info->{is_update}) { update_vnc_pipe({ - server_uuid => $server_uuid, - server_vnc_port => $vnc_info->{port}, - ws_host_uuid => $server_info->{host_uuid}, - ws_pid => $ws_info->{pid}, - ws_source_port => $ws_info->{source_port} + server_uuid => $server_uuid, + ssh_tunnel_host_uuid => $host_uuid, + server_vnc_port => $vnc_info->{port}, + ws_host_uuid => $server_info->{host_uuid}, + ws_pid => $ws_info->{pid}, + ws_source_port => $ws_info->{source_port} }); } From 70075ba6901c7f4d32e7242a84553eaa69274eef Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 21 Jul 2021 11:47:22 -0400 Subject: [PATCH 57/58] fix(cgi-bin): add login cookie guard to manage_vnc_pipes endpoint --- cgi-bin/manage_vnc_pipes | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cgi-bin/manage_vnc_pipes b/cgi-bin/manage_vnc_pipes index 47c8a5fb..646e85b9 100755 --- a/cgi-bin/manage_vnc_pipes +++ b/cgi-bin/manage_vnc_pipes @@ -76,6 +76,15 @@ if (not $anvil->data->{sys}{database}{connections}) $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(); From 063840ecb6a45061d61cd3110abdb72097c0eb64 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Wed, 4 Aug 2021 13:53:44 -0400 Subject: [PATCH 58/58] fix(tools): correct message_* string keys in striker-manage-vnc-pipes --- tools/striker-manage-vnc-pipes | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 0447b33a..096fe7d8 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -1001,7 +1001,7 @@ sub open_vnc_pipe $anvil->Job->update_progress({ progress => 100, - message => "message_0257,!!operation!opening!!,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!" + message => "message_0260,!!operation!opening!!,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!" }); $vnc_pipe_info = { forward_port => defined $ssh_tunnel_info->{forward_port} ? $ssh_tunnel_info->{forward_port} : "" }; @@ -1042,7 +1042,7 @@ sub close_vnc_pipe $anvil->Job->update_progress({ progress => 100, - message => "message_0257,!!operation!closing!!,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!" + message => "message_0260,!!operation!closing!!,!!server_uuid!".$server_uuid."!!,!!host_uuid!".$host_uuid."!!" }); } @@ -1075,7 +1075,7 @@ if ($anvil->data->{switches}{'job-uuid'}) progress => 1, job_picked_up_by => $$, job_picked_up_at => time, - message => "message_0256" + message => "message_0259" }); foreach my $line (split/\n/, $anvil->data->{jobs}{job_data}) @@ -1142,9 +1142,9 @@ elsif ($anvil->data->{switches}{'drop-table'}) { drop_vnc_pipes_table(); - $anvil->Job->update_progress({ progress => 100, message => "message_0258" }); + $anvil->Job->update_progress({ progress => 100, message => "message_0261" }); } else { - $anvil->Job->update_progress({ progress => 100, message => "message_0259" }); + $anvil->Job->update_progress({ progress => 100, message => "message_0262" }); }