diff --git a/tools/striker-manage-vnc-pipes b/tools/striker-manage-vnc-pipes index 2495aec6..12619179 100755 --- a/tools/striker-manage-vnc-pipes +++ b/tools/striker-manage-vnc-pipes @@ -18,1080 +18,741 @@ if (($running_directory =~ /^\./) && ($ENV{PWD})) my $anvil = Anvil::Tools->new(); -sub call -{ - my $parameters = shift; - my $call = $parameters->{call}; - my $debug = $parameters->{debug} // 3; +my $manage_tunnel = $anvil->data->{path}{exe}{'anvil-manage-tunnel'}; +my $echo = $anvil->data->{path}{exe}{'echo'}; +my $kill = $anvil->data->{path}{exe}{'kill'}; +my $pgrep = $anvil->data->{path}{exe}{'pgrep'}; +my $ps = $anvil->data->{path}{exe}{'ps'}; +my $ss = $anvil->data->{path}{exe}{'ss'}; +my $sed = $anvil->data->{path}{exe}{'sed'}; +my $websockify = $anvil->data->{path}{exe}{'websockify'}; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "call" }); +sub drop_vnc_pipes_table +{ + my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; - return (1) if ( (not defined $call) || ($call eq "") ); + $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); +} - my $shell_output; - my $shell_return_code; +$anvil->Get->switches; - my ($output, $rcode) = $anvil->System->call({ shell_call => $call }); +$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->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { - output => $output, - rcode => $rcode - }, prefix => "call" }); +my $switch_debug = $anvil->data->{switches}{'debug'}; +my $open = $anvil->data->{switches}{'open'}; +my $server = $anvil->data->{switches}{'server'}; +my $server_uuid = $anvil->data->{switches}{'server-uuid'}; +my $server_vnc_port = $anvil->data->{switches}{'server-vnc-port'}; - return ($rcode, $output); +if (defined $server) +{ + $server_uuid //= is_uuid_v4($server) ? $server : $anvil->Get->server_uuid_from_name({ server_name => $server }); } -sub get_server_info -{ - my $parameters = shift; - my $server_host_name = $parameters->{server_host_name}; - my $server_host_uuid = $parameters->{server_host_uuid}; - my $server_name = $parameters->{server_name}; - my $server_uuid = $parameters->{server_uuid}; +$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { + open => $open, + server => $server, + server_uuid => $server_uuid, + server_vnc_port => $server_vnc_port +} }); - return if (not defined $server_uuid); +my $map_to_operation = { start => \&start_pipe, stop => \&stop_pipe }; - my $query; - my $server_info; +if (is_uuid_v4($server_uuid)) +{ + my $rcode; - # When all required server info are provided, i.e., extracted from - # libvirt domain XML, simply return the values. - if (defined $server_host_name and defined $server_host_uuid and defined $server_name) - { - $server_info = { - host_name => $server_host_name, - host_uuid => $server_host_uuid, - server_name => $server_name - }; + my $operation = $open ? "start" : "stop"; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $server_info, prefix => "get_server_info" }); + ($rcode) = $map_to_operation->{$operation}({ + debug => $switch_debug, + svr_uuid => $server_uuid, + svr_vnc_port => $server_vnc_port, + }); - return $server_info; - } + $anvil->nice_exit({ exit_code => $rcode }); +} - if (defined $server_host_uuid) - { - $query = " -SELECT a.server_name, b.host_name, b.host_uuid -FROM servers AS a, hosts AS b -WHERE server_uuid = ".$anvil->Database->quote($server_uuid)." -AND host_uuid = ".$anvil->Database->quote($server_host_uuid)." -;"; - } - else - { - $query = " -SELECT a.server_name, b.host_name, b.host_uuid -FROM servers AS a -JOIN hosts AS b ON a.server_host_uuid = b.host_uuid -WHERE server_uuid = ".$anvil->Database->quote($server_uuid)." -;"; - } +$anvil->nice_exit({ exit_code => 0 }); - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; +# +# Functions +# - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query, count => $count }, prefix => "get_server_info" }); +sub build_find_available_port_call +{ + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $start = $parameters->{start}; + my $step_operator = $parameters->{step_operator} // "+"; + my $step_size = $parameters->{step_size} || 1; - if ($count == 1) - { - my $row = $results->[0]; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "build_find_available_port_call" }); - $server_info = {}; - $server_info->{server_name} = $row->[0]; - $server_info->{host_name} = $row->[1]; - $server_info->{host_uuid} = $row->[2]; + return (1) if ( (not $step_operator =~ /^[+-]$/) + || (not is_int($step_size)) || ($step_size < 1) ); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $server_info, prefix => "get_server_info" }); - } + my $call = "ss_output=\$($ss -ant) && port=${start} && while $grep -Eq \":\${port}[[:space:]]+[^[:space:]]+\" <<<\$ss_output; do (( port ${step_operator}= $step_size )); done && $echo \$port"; - return $server_info; + return (0, $call); } -sub get_vnc_info +sub build_tunnel_call { - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $port = $parameters->{port}; - my $port_base = $parameters->{port_base} // 5900; - my $server_name = $parameters->{server_name}; - my $server_uuid = $parameters->{server_uuid}; + my $parameters = shift; + my $ctl_cmd = $parameters->{ctl_cmd} // "forward"; + my $ctl_path = $parameters->{ctl_path}; + my $debug = $parameters->{debug} || 3; + my $lport = $parameters->{lport}; + my $rport = $parameters->{rport}; + my $svr_uuid = $parameters->{svr_uuid}; - my $port_offset; - my $vnc_info; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "build_tunnel_call" }); - if ( (not defined $port) or (not $port =~ /^\d+$/) ) - { - # Requires root to access VM information. - my $shell_call = "virsh vncdisplay \"$server_name\""; + return (1) if ( (not defined $ctl_path) + || (not defined $lport) + || (not defined $rport) ); - my ($shell_return_code, $shell_output) = call({ - call => $shell_call, - host_name => $host_name, - user => "root", - }); + my $ls_prefix_opt = defined $svr_uuid ? "--tunnel-ls-prefix '$svr_uuid'" : ""; - return if ($shell_return_code != 0); + my $call = "$manage_tunnel --child --ctl-cmd $ctl_cmd --ctl-path '$ctl_path' --debug $debug --forward-lport $lport --forward-rport $rport $ls_prefix_opt"; - ($port_offset) = $shell_output =~ /:(\d+)$/; - $port = $port_base + int($port_offset); - } - else - { - $port = int($port); - } - - $vnc_info = { host_name => $host_name, port => $port }; + return (0, $call); +} - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - port_offset => $port_offset, - vnc_port => $vnc_info->{port} - }, prefix => "get_vnc_info" }); +sub build_tunnel_variable_name +{ + my ($svr_uuid, $tp_target_uuid) = @_; - return $vnc_info; + return "sshctl::${svr_uuid}::${tp_target_uuid}"; } -sub get_available_port +sub call { - 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 $parameters = shift; + my $call = $parameters->{call}; + my $debug = $parameters->{debug} || 3; - my $available_port; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "call" }); - 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"; + return (1) if ( (not defined $call) || ($call eq "") ); - my ($shell_return_code, $shell_output) = call({ host_name => $host_name, call => $shell_call }); + my ($output, $rcode) = $anvil->System->call({ shell_call => $call }); - if ($shell_return_code == 0) - { - $available_port = $shell_output; - } + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { + output => $output, + rcode => $rcode + } }); - return $available_port; + return ($rcode, $output); } -sub is_websockify_process +sub find_available_port { 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_return_code, $shell_output) = call({ host_name => $host_name, call => $shell_call }); + my $debug = $parameters->{debug} || 3; - return $shell_output =~ /websockify/ ? 1 : 0; -} - -sub is_ssh_process -{ - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_available_port" }); - my $shell_call = "ps -e -o command -h -p ".$ssh_tunnel_pid; + my ($build_rcode, $call) = build_find_available_port_call($parameters); - my ($shell_return_code, $shell_output) = call({ host_name => $host_name, call => $shell_call }); + return (1) if ($build_rcode); - return $shell_output =~ /anvil-manage-tunnel/ ? 1 : 0; + return call({ call => $call, debug => $debug }); } -sub is_websockify_exists +sub find_ws_processes { - 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 $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $ps_name = $parameters->{ps_name} // "websockify"; - # Expect 0 to 1 record(s) because each VM server can only have 1 - # websockify instance (not considering shared VNC sessions). - my $query = " -SELECT - a.server_vnc_port, - b.host_name, - a.ws_host_uuid, - a.ws_pid, - a.ws_dest_port, - a.ssh_tunnel_host_uuid -FROM vnc_pipes AS a -JOIN hosts AS b ON a.ws_host_uuid = b.host_uuid -WHERE - a.server_uuid = ".$anvil->Database->quote($server_uuid)." -AND - a.ws_host_uuid = ".$anvil->Database->quote($ws_host_uuid)." -AND - a.server_vnc_port IS NOT NULL -ORDER BY a.ws_pid DESC -;"; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_ws_processes" }); + + my $ps_call = "$pgrep -a '$ps_name' | $sed -E 's/^([[:digit:]]+).*${ps_name}[[:space:]:]+([[:digit:]]+)[[:space:]:]+([[:digit:]]+).*\$/\\1,\\2,\\3/'"; - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; + my ($rcode, $output) = call({ call => $ps_call, debug => $debug }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query, count => $count }, prefix => "is_websockify_exists" }); + return (1) if ($rcode); - my $ws_exists_info = { exists_code => 0 }; + my $result = { pids => {}, sources => {}, targets => {} }; - foreach my $row (@{$results}) + foreach my $line (split(/\n/, $output)) { - my $server_vnc_port_in_record = $row->[0]; - my $ws_host_name = $row->[1]; - my $ws_host_uuid_in_record = $row->[2]; - my $ws_pid = $row->[3]; - my $ws_dest_port = $row->[4]; - my $ssh_tunnel_host_uuid = $row->[5]; - my $clean_up_parameters = { host_name => $ws_host_name, ws_pid => $ws_pid }; + chomp($line); - $ws_exists_info->{exists_code} = 1; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { ws_line => $line } }); - 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. - stop_websockify($clean_up_parameters); + my ($pid, $sport, $tport) = split(/,/, $line); - # No need to preserve the websockify source port in - # this case because the tunnel will need to be replaced - # as well. - } - elsif ($server_vnc_port != $server_vnc_port_in_record) - { - # VNC server port mismatch; try to stop the recorded instance. - stop_websockify($clean_up_parameters); + my $process = { pid => $pid, sport => $sport, tport => $tport }; - $ws_exists_info->{ws_dest_port} = $ws_dest_port; - } - elsif (not is_websockify_process($clean_up_parameters)) - { - # The recorded instance died. - $ws_exists_info->{ws_dest_port} = $ws_dest_port; - } - else - { - # Found one websockify instance that passed all tests. - $ws_exists_info->{exists_code} = 2; - $ws_exists_info->{ws_pid} = $ws_pid; - $ws_exists_info->{ws_dest_port} = $ws_dest_port; - } + set_ws_process({ debug => $debug, process => $process, processes => $result }); } - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $ws_exists_info, prefix => "is_websockify_exists" }); - - return $ws_exists_info; + return (0, $result); } -sub is_ssh_tunnel_exists +sub find_tp_processes { - 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_dest_port = $parameters->{ws_dest_port}; - - # Expect 0 to n record(s), where n is the number of subnodes: - # n=0: no SSH tunnel connected to the server's ws instance - # n>0: at least one SSH tunnel is known, regardless of whether it is - # actually alive - # - # Order by clause ensures NULL record(s) are at first. - my $query = " -SELECT - a.ws_host_uuid, - a.ws_pid, - a.ws_dest_port, - a.ssh_tunnel_dest_port, - b.host_name, - a.ssh_tunnel_host_uuid, - a.ssh_tunnel_pid, - a.ssh_tunnel_source_port -FROM vnc_pipes AS a -JOIN hosts AS b ON a.ssh_tunnel_host_uuid = b.host_uuid -WHERE - a.server_uuid = ".$anvil->Database->quote($server_uuid)." -AND - a.ssh_tunnel_host_uuid IS NOT NULL -ORDER BY a.ws_pid DESC -;"; + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $ps_name = $parameters->{ps_name} // "anvil-manage-tunnel"; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_tp_processes" }); + + my $ps_call = "$pgrep -a '$ps_name' | $sed - E 's/^([[:digit:]]+).*--target[[:space:]]+([^[:space:]]+).*--ctl-path[[:space:]]+([^[:space:]]+).*--tunnel-ls-path[[:space:]]+([^[:space:]]+).*\$/\\1,\\2,\\3,\\4/'"; - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; + my ($rcode, $output) = call({ call => $ps_call, debug => $debug }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query, count => $count }, prefix => "is_ssh_tunnel_exists" }); + return (1) if ($rcode); - my $ssh_tunnel_exists_info = { exists_code => 0 }; + my $result = { pids => {}, targets => {} }; - foreach my $row (@{$results}) + foreach my $line (split(/\n/, $output)) { - my $row = $results->[0]; - my $ws_host_uuid_in_record = $row->[0]; - my $ws_pid_in_record = $row->[1]; - my $ws_dest_port_in_record = $row->[2]; - my $ssh_tunnel_dest_port = $row->[3]; - my $ssh_tunnel_host_name = $row->[4]; - my $ssh_tunnel_host_uuid_in_record = $row->[5]; - my $ssh_tunnel_pid = $row->[6]; - my $ssh_tunnel_source_port = $row->[7]; - my $clean_up_parameters = { host_name => $ssh_tunnel_host_name, ssh_tunnel_pid => $ssh_tunnel_pid }; - - $ssh_tunnel_exists_info->{exists_code} = 1; - - if (not defined $ws_pid_in_record and defined $ssh_tunnel_pid) - { - # Websockify instance doesn't exist but tunnel does. - # Remove the tunnel that's not connected to anything. - stop_ssh_tunnel($clean_up_parameters); + chomp($line); - clear_vnc_pipe_st({ server_uuid => $server_uuid, ws_host_uuid => $ws_host_uuid_in_record }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { tp_line => $line } }); - # No need to preserve any tunnel info because this - # tunnel doesn't need to be recreated. - } - elsif ($ssh_tunnel_source_port != $ws_dest_port_in_record) - { - # Tunnel is not pointing to the destination port of the - # websockify instance; stop it and recreate. - stop_ssh_tunnel($clean_up_parameters); + my ($pid, $target, $ctl_path, $tunnel_ls_path) = split(/,/, $line); - # Port mismatch means we need to recreate the tunnel. - $ssh_tunnel_exists_info->{ssh_tunnel_dest_port} = $ssh_tunnel_dest_port; - } - elsif (not is_ssh_process($clean_up_parameters)) - { - # The tunnel pid doesn't point to an active tunnel; - # mark as need-to-be recreated. - $ssh_tunnel_exists_info->{ssh_tunnel_dest_port} = $ssh_tunnel_dest_port; - } - else - { - # Passed all tests; tunnel considered exists. - $ssh_tunnel_exists_info->{exists_code} = 2; - $ssh_tunnel_exists_info->{ssh_tunnel_dest_port} = $ssh_tunnel_dest_port; - $ssh_tunnel_exists_info->{ssh_tunnel_pid} = $ssh_tunnel_pid; - } - } + my $process = { ctl_path => $ctl_path, pid => $pid, target => $target, tunnel_ls_path => $tunnel_ls_path }; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $ssh_tunnel_exists_info, prefix => "is_ssh_tunnel_exists" }); + set_tp_process({ debug => $debug, process => $process, processes => $result }); + } - return $ssh_tunnel_exists_info; + return (0, $result); } -sub is_websockify_in_use_by_others +sub find_tunnel { - my $parameters = shift; - my $ws_pid = $parameters->{ws_pid}; + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $svr_uuid = $parameters->{svr_uuid}; + my $tunnel_ls_path = $parameters->{tunnel_ls_path}; - my $query = "SELECT COUNT(*) FROM public.vnc_pipes WHERE ws_pid = ".$anvil->Database->quote($ws_pid).";"; + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_tunnel" }); - my $count = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0]; + return (1) if ( (not defined $svr_uuid) || (not defined $tunnel_ls_path) ); - return $count > 1 ? 1 : 0; -} + my $call = "$sed -En 's/^${svr_uuid}.*:([[:digit:]]+):.*:([[:digit:]]+)\$/\\1,\\2/p' '$tunnel_ls_path'"; -sub is_remote_host_name -{ - my $host_name = shift; + my ($rcode, $output) = call({ call => $call, debug => $debug }); - return ((defined $host_name) and ($host_name ne $anvil->data->{sys}{host_name})) ? 1 : 0; -} + return (1) if ($rcode); -sub start_websockify -{ - 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; - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $parameters, prefix => "start_websockify" }); - - 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 - }); + chomp($output); - 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_dest_port}; - } - else - { - if (not defined $source_port) - { - if (defined $ws_exists_info->{ws_dest_port}) - { - $source_port = $ws_exists_info->{ws_dest_port}; - } - else - { - my $source_port_base = 10000; - - $source_port = $source_port_base + $target_port; - } - } + my ($lport, $rport) = split(/,/, $output); - $source_port = get_available_port({ start_port => $source_port, host_name => $ws_host_name }); + return (0, { lport => $lport, rport => $rport }); +} - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - source_port => $source_port - }, prefix => "start_websockify" }); +sub get_strikers +{ + my $query = " +SELECT host_name, host_uuid +FROM hosts +WHERE host_status = 'online' AND host_type = 'striker' +;"; + my $rows = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - return if (not defined $source_port); + my $strikers = { names => {}, uuids => {} }; - my $shell_call = "websockify ".$source_port." :".$target_port." &>/dev/null & echo pid:\$!"; + foreach my $row (@{$rows}) + { + my $host_name = $row->[0]; + my $host_uuid = $row->[1]; - my ($shell_return_code, $shell_output) = call({ - call => $shell_call, - host_name => $ws_host_name, - user => "admin", - }); + $strikers->{uuids}{$host_uuid} = { name => $host_name, uuid => $host_uuid }; + $strikers->{names}{$host_name} = $host_uuid; + } - return if ($shell_return_code != 0); + return (0, $strikers); +} - my ($ws_pid) = $shell_output =~ /pid:(\d+)$/; +sub get_tunnel_variable +{ + my $parameters = shift; + my $svr_uuid = $parameters->{svr_uuid}; + my $tp_target_uuid = $parameters->{tp_target_uuid}; - $ws_info = {}; - $ws_info->{pid} = $ws_pid; - $ws_info->{source_port} = $source_port; + my $variable_name = build_tunnel_variable_name($svr_uuid, $tp_target_uuid); - if ($ws_exists_info->{exists_code} == 1) - { - $ws_info->{is_update} = 1; - } - else - { - $ws_info->{is_new} = 1; - } - } + my $query = " +SELECT variable_value +FROM variables +WHERE + variable_name = ".$anvil->Database->quote($variable_name)." +AND + variable_source_table = 'hosts' +AND + variable_source_uuid = ".$anvil->Database->quote($tp_target_uuid)." +;"; + my $rows = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); + + return (1) if (not @{$rows}); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $ws_info, prefix => "start_websockify" }); + my $end_port = $rows->[0]->[0]; - return $ws_info; + return (0, int($end_port)); } -sub stop_websockify +sub is_int { - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $ws_pid = $parameters->{ws_pid}; - - return if (not is_websockify_process($parameters)); + return $_[0] =~ /^\d+$/; +} - call({ host_name => $host_name, call => "kill $ws_pid || kill -9 $ws_pid" }); +sub is_uuid_v4 +{ + return $_[0] =~ /[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/; } -sub start_ssh_tunnel +sub set_process { - 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_host_uuid = $parameters->{ws_host_uuid}; - my $ws_dest_port = $parameters->{ws_dest_port}; - my $ssh_tunnel_dest_port = $parameters->{ssh_tunnel_dest_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_dest_port => $ws_dest_port - }); + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $handle_delete = $parameters->{handle_delete}; + my $handle_set = $parameters->{handle_set}; + my $pid = $parameters->{pid}; + my $process = $parameters->{process}; + my $processes = $parameters->{processes}; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $process, prefix => "process" }); - if ($ssh_tunnel_exists_info->{exists_code} == 2) + return (1) if (not defined $processes); + + if (defined $process) { - $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_dest_port}; + $pid = $process->{pid}; + + $handle_set->($pid, $process, $processes); } - else + elsif (defined $pid) { - if (not defined $ssh_tunnel_dest_port) - { - if (defined $ssh_tunnel_exists_info->{ssh_tunnel_dest_port}) - { - $ssh_tunnel_dest_port = $ssh_tunnel_exists_info->{ssh_tunnel_dest_port}; - } - else - { - $ssh_tunnel_dest_port = $ws_dest_port; - } - } - - $ssh_tunnel_dest_port = get_available_port({ start_port => $ssh_tunnel_dest_port }); + $process = $processes->{pids}{$pid}; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - ssh_tunnel_dest_port => $ssh_tunnel_dest_port - }, prefix => "start_ssh_tunnel" }); + $handle_delete->($pid, $process, $processes); + } - if (not defined $ssh_tunnel_dest_port) - { - return; - } + return (0); +} - my $shell_call = $anvil->data->{path}{exe}{'anvil-manage-tunnel'} - ." --remote-user admin --target ".$ws_host_name - ." --forward-local-port ".$ssh_tunnel_dest_port - ." --forward-remote-port ".$ws_dest_port - ." &>/dev/null & echo pid:\$!"; +sub set_tp_process +{ + my $parameters = shift; - my ($shell_return_code, $shell_output) = call({ host_name => $host_name, call => $shell_call }); + $parameters->{handle_delete} = sub { + my ($pid, $process, $processes) = @_; - 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_dest_port; - - if ($ssh_tunnel_exists_info->{exists_code} == 1) - { - $ssh_tunnel_info->{is_update} = 1; - } - else - { - $ssh_tunnel_info->{is_new} = 1; - } - } - } + my $target = $process->{target}; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $ssh_tunnel_info, prefix => "start_ssh_tunnel" }); + $processes->{pids}{$pid} = $process; + $processes->{targets}{$target} = $pid; + }; - return $ssh_tunnel_info; -} + $parameters->{handle_set} = sub { + my ($pid, $process, $processes) = @_; -sub stop_ssh_tunnel -{ - my $parameters = shift; - my $host_name = $parameters->{host_name}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; + my $target = $process->{target}; - return if (not is_ssh_process($parameters)); + delete $processes->{pids}{$pid}; + delete $processes->{targets}{$target}; + }; - call({ host_name => $host_name, call => "kill $ssh_tunnel_pid || kill -9 $ssh_tunnel_pid" }); + return set_process($parameters); } -sub create_vnc_pipes_table +sub set_tunnel_variable { - 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, - ws_host_uuid uuid, - ws_pid numeric, - ws_dest_port numeric, - ssh_tunnel_dest_port numeric, - ssh_tunnel_host_uuid uuid, - ssh_tunnel_pid numeric, - ssh_tunnel_source_port numeric, - modified_date timestamp with time zone not null, - unique(server_uuid, ws_host_uuid) -);"; - - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); -} + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $end_port = $parameters->{end_port}; + my $svr_uuid = $parameters->{svr_uuid}; + my $tp_target_uuid = $parameters->{tp_target_uuid}; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "set_tunnel_variable" }); + + my ($variable_uuid) = $anvil->Database->insert_or_update_variables({ + file => $THIS_FILE, + line => __LINE__, + variable_name => build_tunnel_variable_name($svr_uuid, $tp_target_uuid), + variable_source_table => "hosts", + variable_source_uuid => $tp_target_uuid, + variable_value => $end_port, + }); -sub drop_vnc_pipes_table -{ - my $query = "DROP TABLE IF EXISTS public.vnc_pipes;"; + return (1) if (not is_uuid_v4($variable_uuid)); - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); + return (0); } -sub insert_or_update_vnc_pipe +sub set_ws_process { - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - - return (1) if (not defined $server_uuid); - - my $server_vnc_port = $parameters->{server_vnc_port}; - my $ssh_tunnel_dest_port = $parameters->{ssh_tunnel_dest_port}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; - my $ssh_tunnel_pid = $parameters->{ssh_tunnel_pid}; - my $ssh_tunnel_source_port = $parameters->{ssh_tunnel_source_port}; - my $vnc_pipe_uuid = $parameters->{vnc_pipe_uuid} // $anvil->Get->uuid(); - my $ws_host_uuid = $parameters->{ws_host_uuid}; - my $ws_pid = $parameters->{ws_pid}; - my $ws_dest_port = $parameters->{ws_dest_port}; - - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $parameters, prefix => "insert_or_update_vnc_pipe" }); - - my $vnc_pipe_mdate = $anvil->Database->refresh_timestamp(); - my $quoted_vnc_pipe_mdate = $anvil->Database->quote($vnc_pipe_mdate); - - my $insert_columns = ""; - my $insert_values = ""; - my $update_values = ""; - - foreach my $column_name ( - "server_vnc_port", - "ws_host_uuid", - "ws_pid", - "ws_dest_port", - "ssh_tunnel_dest_port", - "ssh_tunnel_host_uuid", - "ssh_tunnel_pid", - "ssh_tunnel_source_port" - ) - { - my $column_value = $parameters->{$column_name}; + my $parameters = shift; - next if (not defined $column_value); + $parameters->{handle_delete} = sub { + my ($pid, $process, $processes) = @_; - my $quoted_value = ($column_value eq "NULL") - ? $column_value - : $anvil->Database->quote($column_value); + my $sport = $process->{sport}; + my $tport = $process->{tport}; - $insert_columns .= "$column_name,\n\t"; - $insert_values .= "$quoted_value,\n\t"; - $update_values .= "$column_name = $quoted_value,\n\t"; - } + $processes->{pids}{$pid} = $process; + $processes->{sources}($sport) = $pid; + $processes->{targets}{$tport} = $pid; + }; - my $query = " -INSERT INTO public.vnc_pipes ( - uuid, - server_uuid, - $insert_columns - modified_date -) VALUES ( - ".$anvil->Database->quote($vnc_pipe_uuid).", - ".$anvil->Database->quote($server_uuid).", - $insert_values - $quoted_vnc_pipe_mdate -) ON CONFLICT (server_uuid, ws_host_uuid) DO UPDATE SET - $update_values - modified_date = $quoted_vnc_pipe_mdate;"; + $parameters->{handle_set} = sub { + my ($pid, $process, $processes) = @_; - $anvil->Database->write({ query => $query, source => $THIS_FILE, line => __LINE__ }); + my $sport = $process->{sport}; + my $tport = $process->{tport}; - return (0); + delete $processes->{pids}{$pid}; + delete $processes->{sources}($sport); + delete $processes->{targets}{$tport}; + }; + + return set_process($parameters); } -sub clear_vnc_pipe_ws +sub start_pipe { my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; + my $debug = $parameters->{debug} || 3; + my $svr_uuid = $parameters->{svr_uuid}; + my $svr_vnc_port = $parameters->{svr_vnc_port}; - return (1) if (not defined $ws_host_uuid); + return (1) if (not is_uuid_v4($svr_uuid)); - insert_or_update_vnc_pipe({ - server_uuid => $server_uuid, - server_vnc_port => "NULL", - ws_dest_port => "NULL", - ws_host_uuid => $ws_host_uuid, - ws_pid => "NULL" - }); + my $common_params = { debug => $debug }; - return (0); -} + my $rcode; -sub clear_vnc_pipe_st -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; - - return (1) if (not defined $ws_host_uuid); - - insert_or_update_vnc_pipe({ - server_uuid => $server_uuid, - ssh_tunnel_dest_port => "NULL", - ssh_tunnel_host_uuid => "NULL", - ssh_tunnel_pid => "NULL", - ssh_tunnel_source_port => "NULL", - ws_host_uuid => $ws_host_uuid - }); -} + # If we don't have the server's VNC port, find it in its qemu-kvm process. + if ( (not defined $svr_vnc_port) || (not is_int($svr_vnc_port)) ) + { + ($rcode, my $svr_processes) = $anvil->Server->find_processes($common_params); -sub get_vnc_pipe -{ - my $parameters = shift; - my $server_uuid = $parameters->{server_uuid}; + my $svr_process = $svr_processes->{uuids}{$svr_uuid}; + my $svr_vnc_alive = $svr_process->{vnc_alive}; - return if (not defined $server_uuid); + return (1) if (not $svr_vnc_alive); - my $is_ws_pid = $parameters->{is_ws_pid}; - my $ssh_tunnel_host_uuid = $parameters->{ssh_tunnel_host_uuid}; - my $ws_host_uuid = $parameters->{ws_host_uuid}; + $svr_vnc_port = $svr_process->{vnc_port}; + } - my $vnc_pipe_info; + ($rcode, my $ws_processes) = find_ws_processes($common_params); - my $cond_ws_pid = defined $is_ws_pid - ? "AND a.ws_pid IS NOT NULL" : ""; - my $cond_ssht_huuid = defined $ssh_tunnel_host_uuid - ? "AND a.ssh_tunnel_host_uuid = ".$anvil->Database->quote($ssh_tunnel_host_uuid) : ""; - my $cond_ws_huuid = defined $ws_host_uuid - ? "AND a.ws_host_uuid = ".$anvil->Database->quote($ws_host_uuid) : ""; + return ($rcode) if ($rcode); - my $query = " -SELECT - a.server_vnc_port, - b.host_name, - a.ws_host_uuid, - a.ws_pid, - a.ws_dest_port, - a.ssh_tunnel_host_uuid, - a.ssh_tunnel_pid, - a.ssh_tunnel_dest_port -FROM - public.vnc_pipes AS a -JOIN - public.hosts AS b -ON - a.ws_host_uuid = b.host_uuid -WHERE - server_uuid = ".$anvil->Database->quote($server_uuid)." - $cond_ws_pid - $cond_ssht_huuid - $cond_ws_huuid -ORDER BY - a.modified_date DESC -;"; + ($rcode, my $ws_pid) = start_ws({ svr_vnc_port => $svr_vnc_port, ws_processes => $ws_processes, %$common_params }); - my $results = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ }); - my $count = @{$results}; + return ($rcode) if ($rcode); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { query => $query, count => $count }, prefix => "get_vnc_pipe" }); + ($rcode, my $hosts) = get_strikers($common_params); - if ($count == 1) - { - my $row = $results->[0]; - - $vnc_pipe_info = {}; - $vnc_pipe_info->{server_vnc_port} = $row->[0]; - $vnc_pipe_info->{host_name} = $row->[1]; - $vnc_pipe_info->{ws_host_uuid} = $row->[2]; - $vnc_pipe_info->{ws_pid} = $row->[3]; - $vnc_pipe_info->{ws_dest_port} = $row->[4]; - $vnc_pipe_info->{ssh_tunnel_host_uuid} = $row->[5]; - $vnc_pipe_info->{ssh_tunnel_pid} = $row->[6]; - $vnc_pipe_info->{ssh_tunnel_dest_port} = $row->[7]; - } + return ($rcode) if ($rcode); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $vnc_pipe_info, prefix => "get_vnc_pipe" }); + ($rcode, my $tp_processes) = find_tp_processes($common_params); - return $vnc_pipe_info; -} + return ($rcode) if ($rcode); -sub open_ws -{ - my $parameters = shift; - my $server_host_name = $parameters->{server_host_name}; - my $server_host_uuid = $parameters->{server_host_uuid}; - my $server_name = $parameters->{server_name}; - my $server_uuid = $parameters->{server_uuid}; - my $server_vnc_port = $parameters->{server_vnc_port}; + my $ws_process = $ws_processes->{pids}{$ws_pid}; - my $server_info = $parameters->{server_info} // get_server_info($parameters); + foreach my $host_uuid (keys $hosts->{uuids}) + { + my $host_name = $hosts->{uuids}{$host_uuid}{name}; - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $parameters, prefix => "open_ws" }); + ($rcode, my $tp_pid) = start_tp({ tp_processes => $tp_processes, tp_target => $host_name, %$common_params }); - return (1, "error_0313") if (not defined $server_info); + next if ($rcode); - my $vnc_info = get_vnc_info({ - host_name => $server_info->{host_name}, - port => $server_vnc_port, - server_name => $server_info->{server_name}, - server_uuid => $server_uuid - }); + my $tp_process = $tp_processes->{pids}{$tp_pid}; - return (1, "error0314") if (not defined $vnc_info); + ($rcode, my $tunnel) = start_tunnel({ + svr_uuid => $svr_uuid, + tp_ctl_path => $tp_process->{ctl_path}, + tp_target => $host_name, + tp_target_uuid => $host_uuid, + ws_sport => $ws_process->{sport}, + %$common_params, + }); - my $ws_info = start_websockify({ - server_uuid => $server_uuid, - target_port => $vnc_info->{port}, - ws_host_name => $server_info->{host_name}, - ws_host_uuid => $server_info->{host_uuid} - }); + next if ($rcode); - return (1, "error_0315") if (not defined $ws_info); + ($rcode) = set_tunnel_variable($tunnel); - if ($ws_info->{is_new} or $ws_info->{is_update}) - { - insert_or_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_dest_port => $ws_info->{source_port} - }); + if ($rcode) + { + stop_tunnel({ + lport => $ws_process->{sport}, + rport => $tunnel->{end_port}, + tp_ctl_path => $tp_process->{ctl_path}, + %$common_params, + }); + } } - return (0, $ws_info); + return (0); } -sub close_ws +sub start_tp { - my $parameters = shift; - my $host_uuid = $parameters->{host_uuid}; - my $server_uuid = $parameters->{server_uuid}; + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $tp_ctl_path = $parameters->{tp_ctl_path}; + my $tp_processes = $parameters->{tp_processes}; + my $tp_target = $parameters->{tp_target}; + my $tp_tunnel_ls_path = $parameters->{tp_tunnel_ls_path}; - my $vnc_pipe_info = $parameters->{vnc_pipe_info} // get_vnc_pipe({ server_uuid => $server_uuid, ws_host_uuid => $host_uuid }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "start_tp" }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $parameters, prefix => "close_ws" }); + return (1) if ( (not defined $tp_processes) || (not defined $tp_target) ); - return (1, "error_0317") if (not defined $vnc_pipe_info); + my $tp_ctl_name = "sshctl-${tp_target}"; - return (0) if is_websockify_in_use_by_others({ ws_pid => $vnc_pipe_info->{ws_pid} }); + $tp_ctl_path //= "~/.libnet-openssh-perl/${tp_ctl_name}"; + $tp_tunnel_ls_path //= $anvil->data->{path}{'directories'}{'tmp'}."/${tp_ctl_name}-tunnel-ls"; - stop_websockify({ host_name => $vnc_pipe_info->{host_name}, ws_pid => $vnc_pipe_info->{ws_pid} }); + my $existing_tp_pid = $tp_processes->{targets}{$tp_target}; - clear_vnc_pipe_ws({ server_uuid => $server_uuid, ws_host_uuid => $vnc_pipe_info->{ws_host_uuid} }); + return (0, $existing_tp_pid) if (defined $existing_tp_pid); - return (0); + my $tp_call = "$manage_tunnel --debug $debug --target $tp_target --ctl-path '$tp_ctl_path' --tunnel-ls-path '$tp_tunnel_ls_path' & $echo pid:\$!"; + + my ($start_rcode, $start_output) = call({ call => $tp_call, debug => $debug }); + + return (1) if ($start_rcode); + + my ($tp_pid) = $start_output =~ /pid:(\d+)$/; + + my $tp_process = { ctl_path => $tp_ctl_path, pid => $tp_pid, target => $tp_target, tunnel_ls_path => $tp_tunnel_ls_path }; + + set_tp_process({ debug => $debug, process => $tp_process, processes => $tp_processes }); + + return (0, $tp_pid); } -sub open_st +sub start_tunnel { - my $parameters = shift; - my $host_uuid = $parameters->{host_uuid}; - my $is_print = $parameters->{print} // (not $anvil->data->{switches}{'job-uuid'}); - my $server_host_uuid = $parameters->{server_host_uuid}; - my $server_uuid = $parameters->{server_uuid}; + my $parameters = shift; + my $debug = $parameters->{debug}; + my $svr_uuid = $parameters->{svr_uuid}; + my $tp_ctl_path = $parameters->{tp_ctl_path}; + my $tp_target = $parameters->{tp_target}; + my $tp_target_uuid = $parameters->{tp_target_uuid}; + my $ws_sport = $parameters->{ws_sport}; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "start_tunnel" }); - my $server_info = $parameters->{server_info} // get_server_info($parameters); + return (1) if ( (not defined $svr_uuid) + || (not defined $tp_target) + || (not defined $tp_target_uuid) + || (not defined $tp_ctl_path) || ($tp_ctl_path eq "") || (not -e $tp_ctl_path) + || (not defined $ws_sport) ); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $parameters, prefix => "open_st" }); + my $build_rcode; + my $sh_call; - return (1, "error_0313") if (not defined $server_info); + # ----- Try to find a usable port on the target host. + ($build_rcode, $sh_call) = build_find_available_port_call({ start => $ws_sport }); - my $vnc_pipe_info = get_vnc_pipe({ is_ws_pid => 1, server_uuid => $server_uuid, ws_host_uuid => $server_info->{host_uuid} }); + return (1) if ($build_rcode); - return (1, "error_0317") if (not defined $vnc_pipe_info); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { find_available_port_call => $call } }); - my $ssh_tunnel_info = start_ssh_tunnel({ - server_uuid => $server_uuid, - host_uuid => $host_uuid, - ws_host_name => $vnc_pipe_info->{host_name}, - ws_host_uuid => $vnc_pipe_info->{ws_host_uuid}, - ws_dest_port => $vnc_pipe_info->{ws_dest_port} + my ($tunnel_rport, $find_error, $find_rcode) = $anvil->Remote->call({ + no_cache => 1, + ossh_opts => [ ctl_path => $tp_ctl_path, external_master => 1 ], + shell_call => $sh_call, + target => "0.0.0.0", }); - return (1, "error_0316") if (not defined $ssh_tunnel_info); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { + find_error => $find_error, + find_rcode => $find_rcode, + tunnel_rport => $tunnel_rport, + } }); - if ($ssh_tunnel_info->{is_new} or $ssh_tunnel_info->{is_update}) - { - insert_or_update_vnc_pipe({ - server_uuid => $server_uuid, - ssh_tunnel_dest_port => $ssh_tunnel_info->{forward_port}, - ssh_tunnel_host_uuid => $host_uuid, - ssh_tunnel_pid => $ssh_tunnel_info->{pid}, - ssh_tunnel_source_port => $vnc_pipe_info->{ws_dest_port}, - ws_host_uuid => $vnc_pipe_info->{ws_host_uuid} - }); - } + return (1) if ($find_rcode); + # ----- - my $forward_port = $ssh_tunnel_info->{forward_port} // ""; + ($build_rcode, $sh_call) = build_tunnel_call({ + ctl_path => $tp_ctl_path, + debug => $debug, + lport => $ws_sport, + rport => $tunnel_rport, + svr_uuid => $svr_uuid, + }); + + return (1) if ($build_rcode); - $ssh_tunnel_info->{forward_port} = $forward_port; + my ($start_rcode) = call({ call => $sh_call, debug => $debug }); - print "protocol:ws,forward_port:$forward_port\n" if ($is_print); + return (1) if ($start_rcode); - return (0, $ssh_tunnel_info); + return (0, { debug => $debug, end_port => $tunnel_rport, svr_uuid => $svr_uuid, tp_target_uuid => $tp_target_uuid }); } -sub close_st +sub start_ws { - my $parameters = shift; - my $host_uuid = $parameters->{host_uuid}; - my $server_uuid = $parameters->{server_uuid}; + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $svr_vnc_port = $parameters->{svr_vnc_port}; + my $ws_processes = $parameters->{ws_processes}; + my $ws_sport_offset = $parameters->{ws_sport_offset} || 10000; - my $vnc_pipe_info = $parameters->{vnc_pipe_info} // get_vnc_pipe({ - server_uuid => $server_uuid, - ssh_tunnel_host_uuid => $host_uuid - }); + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "start_ws" }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $parameters, prefix => "close_st" }); + return (1) if ( (not defined $ws_processes) + || (not defined $svr_vnc_port) || (not is_int($svr_vnc_port)) + || (not is_int($ws_sport_offset)) ); - return (1, "error_0317") if (not defined $vnc_pipe_info); + my $existing_ws_pid = $ws_processes->{targets}{$svr_vnc_port}; - stop_ssh_tunnel({ ssh_tunnel_pid => $vnc_pipe_info->{ssh_tunnel_pid} }); + return (0, $existing_ws_pid) if (defined $existing_ws_pid); - clear_vnc_pipe_st({ server_uuid => $server_uuid, ws_host_uuid => $vnc_pipe_info->{ws_host_uuid} }); + my ($find_rcode, $ws_sport) = find_available_port({ debug => $debug, start => int($svr_vnc_port) + int($ws_sport_offset) }); - return (0); -} + return (1) if ($find_rcode); -$anvil->Get->switches; + my $ws_call = "$websockify $svr_vnc_port :$ws_sport &>/dev/null & $echo pid:\$!"; -$anvil->Database->connect; -$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132" }); -if (not $anvil->data->{sys}{database}{connections}) -{ - # No databases, exit. - $anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, priority => "err", key => "error_0003" }); - $anvil->nice_exit({ exit_code => 1 }); -} + my ($start_rcode, $start_output) = call({ call => $ws_call, debug => $debug }); -# 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'} - } }); -} + return (1) if ($start_rcode); -# 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_0259" - }); + my ($ws_pid) = $start_output =~ /pid:(\d+)$/; - foreach my $line (split/\n/, $anvil->data->{jobs}{job_data}) - { - if ($line =~ /server-uuid=(.*?)$/) - { - $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'} - } }); - } + my $ws_process = { pid => $ws_pid, sport => $ws_sport, tport => $svr_vnc_port }; - 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'} - } }); - } + set_ws_process({ debug => $debug, process => $ws_process, processes => $ws_processes }); - 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'} - } }); - } - } + return (0, $ws_pid); } -$anvil->Database->get_hosts(); -$anvil->Database->get_anvils(); +sub stop_pipe +{ + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $svr_uuid = $parameters->{svr_uuid}; + my $svr_vnc_port = $parameters->{svr_vnc_port}; -my $component = $anvil->data->{switches}{'component'} // "all"; -my $is_drop_table = $anvil->data->{switches}{'drop-table'}; -my $is_open = $anvil->data->{switches}{'open'}; -my $server = $anvil->data->{switches}{'server'}; -my $server_host_uuid = $anvil->data->{switches}{'server-host-uuid'}; -my $server_uuid = $anvil->data->{switches}{'server-uuid'} // $anvil->Get->server_uuid_from_name({ server_name => $server }); -my $server_vnc_port = $anvil->data->{switches}{'server-vnc-port'}; + return (1) if (not is_uuid_v4($svr_uuid)); -my $server_host_name; + my $common_params = { debug => $debug }; -if (defined $server_host_uuid) -{ - if ($server_host_uuid eq "local") - { - $server_host_uuid = $anvil->data->{sys}{host_uuid}; - $server_host_name = $anvil->data->{sys}{host_name}; - } - else + my $rcode; + + # If we don't have the server's VNC port, find it in its qemu-kvm process. + if ( (not defined $svr_vnc_port) || (not is_int($svr_vnc_port)) ) { - $server_host_name = $anvil->Get->host_name_from_uuid({ host_uuid => $server_host_uuid }); - } -} + ($rcode, my $svr_processes) = $anvil->Server->find_processes($common_params); -$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { - component => $component, - is_open => $is_open, - is_drop_table => $is_drop_table, - server => $server, - server_host_name => $server_host_name, - server_host_uuid => $server_host_uuid, - server_uuid => $server_uuid, - server_vnc_port => $server_vnc_port -} }); + my $svr_process = $svr_processes->{uuids}{$svr_uuid}; + my $svr_vnc_alive = $svr_process->{vnc_alive}; -my $map_to_operation = { - st => { close => \&close_st, open => \&open_st }, - ws => { close => \&close_ws, open => \&open_ws }, -}; + return (1) if (not $svr_vnc_alive); -if ($server_uuid =~ /[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/) -{ - create_vnc_pipes_table(); + $svr_vnc_port = $svr_process->{vnc_port}; + } - my $ops = $map_to_operation->{$component}; + ($rcode, my $ws_processes) = find_ws_processes($common_params); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => $ops }); + return ($rcode) if ($rcode); - $anvil->nice_exit({ exit_code => 1 }) if (not defined $ops); + my $ws_pid = $ws_processes->{targets}{$svr_vnc_port}; - my $op = ($is_open) ? "open" : "close"; + stop_ws({ ws_pid => $ws_pid, ws_processes => $ws_processes }); - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => { operation => $op } }); + ($rcode, my $hosts) = get_strikers($common_params); - my $host_uuid = $anvil->data->{sys}{host_uuid}; + return ($rcode) if ($rcode); - my ($is_error, $error) = $ops->{$op}({ - host_uuid => $host_uuid, - server_host_name => $server_host_name, - server_host_uuid => $server_host_uuid, - server_name => $server, - server_uuid => $server_uuid, - server_vnc_port => $server_vnc_port - }); + ($rcode, my $tp_processes) = find_tp_processes($common_params); + + return ($rcode) if ($rcode); - if ($is_error) + foreach my $host_uuid (keys $hosts->{uuids}) { - $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 1, list => { - error => $error, - host_uuid => $host_uuid, - server_uuid => $server_uuid - } }); + my $host_name = $hosts->{uuids}{$host_uuid}{name}; + my $tp_pid = $tp_processes->{targets}{$host_name}; + my $tp_process = $tp_processes->{pids}{$tp_pid}; + + ($rcode, my $tunnel_ports) = find_tunnel({ + svr_uuid => $svr_uuid, + tunnel_ls_path => $tp_process->{tunnel_ls_path}, + %$common_params, + }); - if (defined $error and $error =~ /^error_/) - { - $anvil->Job->update_progress({ - progress => 100, - message => "$error,!!server_uuid!$server_uuid!!,!!host_uuid!$host_uuid!!", - job_status => "failed" - }); - } + next if ($rcode); - $anvil->nice_exit({ exit_code => 2 }); + stop_tunnel({ %$tunnel_ports, tp_ctl_path => $tp_process->{ctl_path}, %$common_params }); } - $anvil->Job->update_progress({ - progress => 100, - message => "message_0260,!!operation!$op!!,!!server_uuid!$server_uuid!!,!!host_uuid!$host_uuid!!" - }); + return (0); } -elsif ($is_drop_table) + +sub stop_tp { - drop_vnc_pipes_table(); + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $tp_pid = $parameters->{tp_pid}; + my $tp_processes = $parameters->{tp_processes}; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "stop_tp" }); - $anvil->Job->update_progress({ progress => 100, message => "message_0261" }); + call({ debug => $debug, call => "$kill $tp_pid || $kill -9 $tp_pid" }); + + set_tp_process({ debug => $debug, pid => $tp_pid, processes => $tp_processes }); } -else + +sub stop_tunnel { - $anvil->Job->update_progress({ progress => 100, message => "message_0262" }); + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $lport = $parameters->{lport}; + my $rport = $parameters->{rport}; + my $tp_ctl_path = $parameters->{tp_ctl_path}; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "stop_tunnel" }); + + my ($build_rcode, $call) = build_tunnel_call({ + ctl_cmd => "cancel", + ctl_path => $tp_ctl_path, + debug => $debug, + lport => $lport, + rport => $rport, + }); + + return (1) if ($build_rcode); + + my ($stop_rcode) = call({ call => $call, debug => $debug }); - $anvil->nice_exit({ exit_code => 3 }); + return (1) if ($stop_rcode); + + return (0); } -$anvil->nice_exit({ exit_code => 0 }); +sub stop_ws +{ + my $parameters = shift; + my $debug = $parameters->{debug} || 3; + my $ws_pid = $parameters->{ws_pid}; + my $ws_processes = $parameters->{ws_processes}; + + $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "stop_ws" }); + + call({ debug => $debug, call => "$kill $ws_pid || $kill -9 $ws_pid" }); + + set_ws_process({ debug => $debug, pid => $ws_pid, processes => $ws_processes }); +}