app/models/system_console.rb
class SystemConsole < ApplicationRecord
belongs_to :vm
belongs_to :user
default_value_for :opened, false
validates :url_secret, :uniqueness_when_changed => true
def connection_params
{
:url => "ws/console/#{url_secret}",
:secret => secret,
:proto => protocol
}
end
def destroy_or_mark
if proxy_pid.nil?
destroy
return
end
update(:proxy_status => 'websocket_closed')
SystemConsole.cleanup_proxy_processes
end
def self.allocate_port
port_range_start = ::Settings.server.console_proxy_port.start
port_range_end = ::Settings.server.console_proxy_port.end
used_ports = SystemConsole.where.not(:proxy_pid => nil).where(:host_name => local_address).order(:port).pluck(:port).uniq
(port_range_start..port_range_end).each do |port_number|
return port_number if used_ports[0].nil? || used_ports[0] > port_number
used_ports.shift if used_ports[0] == port_number
end
nil
end
def self.local_address
(MiqServer.my_server.ipaddress.presence || local_address_fallback)
end
def self.local_address_fallback
require 'socket'
Socket.ip_address_list.collect(&:ip_address).reject { |address| address == '127.0.0.1' }.first
end
def self.launch_proxy(remote_address, remote_port)
local_port = allocate_port
if local_port.nil?
_log.error("No unused ports for proxy.")
return nil
end
command = AwesomeSpawn::CommandLineBuilder.new.build("/usr/bin/socat", ["TCP-LISTEN:" + local_port + ",fork", "TCP:" + remote_address + ":" + remote_port])
_log.info("Running socat proxy command: #{command}")
pid = spawn(command)
Process.detach(pid)
[local_address, local_port, pid]
end
def self.kill_proxy_process(pid)
system('/usr/bin/kill', pid.to_s)
end
def self.cleanup_proxy_processes
SystemConsole.where.not(:proxy_pid => nil).where(:host_name => local_address).each do |console|
next unless %w[websocket_closed ticket_invalid].include?(console.proxy_status)
kill_proxy_process(console.proxy_pid)
console.destroy
end
end
def self.force_vm_invalid_token(vm_id)
SystemConsole.where(:vm_id => vm_id).each do |console|
if console.proxy_pid.nil?
console.destroy
next
else
console.update(:vm_id => :nil, :proxy_status => 'ticket_invalid')
end
end
end
def self.is_local?(originating_server)
MiqServer.my_server.id == originating_server.to_i
end
def self.launch_proxy_if_not_local(console_args, originating_server, host_address, host_port)
_log.info("Originating server: #{originating_server}, local server: #{MiqServer.my_server.id}")
if ::Settings.server.console_proxy_disabled || SystemConsole.is_local?(originating_server)
console_args.update(
:host_name => host_address,
:port => host_port
)
else
SystemConsole.cleanup_proxy_processes
proxy_address, proxy_port, proxy_pid = SystemConsole.launch_proxy(host_address, host_port)
return nil if proxy_address.nil?
_log.info("Proxy server started: #{proxy_address}:#{proxy_port} <--> #{host_address}:#{host_port}")
_log.info("Proxy process PID: #{proxy_pid}")
console_args.update(
:host_name => proxy_address,
:port => proxy_port,
:proxy_status => 'proxy_running',
:proxy_pid => proxy_pid
)
end
SystemConsole.create!(console_args).connection_params
end
end