Skip to content

Commit

Permalink
Merge pull request #7078 from skateman/console-proxy-worker
Browse files Browse the repository at this point in the history
Websocket proxy worker for HTM5 consoles
  • Loading branch information
martinpovolny committed Apr 7, 2016
2 parents 833e4e2 + 5aec6be commit 318a31c
Show file tree
Hide file tree
Showing 46 changed files with 639 additions and 1,723 deletions.
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ GlobalVars:
- $kube_log
- $scvmm_log
- $vim_log
- $websocket_log
# In Automate methods
- $evm
#
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ gem "rails", "~> 5.0.x", :git => "git://github.com/rai
gem "rails-controller-testing", :require => false
gem "activemodel-serializers-xml", :require => false # required by draper: https://github.com/drapergem/draper/issues/697
gem "activerecord-session_store", "~>0.1.2", :require => false
gem "websocket-driver", "~>0.6.3"

gem "config", "~>1.1.0", :git => "git://github.com/Fryguy/config.git", :branch => "overwrite_arrays"
gem "deep_merge", "~>1.0.1", :git => "git://github.com/Fryguy/deep_merge.git", :branch => "overwrite_arrays"
Expand Down
57 changes: 0 additions & 57 deletions app/assets/javascripts/miq_novnc.js

This file was deleted.

46 changes: 46 additions & 0 deletions app/controllers/api_controller/vms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,28 @@ def refresh_resource_vms(type, id = nil, _data = nil)
end
end

def request_console_resource_vms(type, id = nil, data = nil)
raise BadRequestError, "Must specify an id for requesting a console for a #{type} resource" unless id

# NOTE:
# for Future ?:
# data ||= {}
# protocol = data["protocol"] || "mks"
# However, there are different entitlements for the different protocol as per miq_product_feature,
# so we may go for different action, i.e. request_console_vnc
#protocol = "mks"
protocol = data["protocol"] || "vnc"

api_action(type, id) do |klass|
vm = resource_search(id, type, klass)
api_log_info("Requesting Console #{vm_ident(vm)}")

result = validate_vm_for_remote_console(vm, protocol)
result = request_console_vm(vm, protocol) if result[:success]
result
end
end

private

def vm_ident(vm)
Expand All @@ -210,6 +232,14 @@ def validate_vm_for_action(vm, action)
action_result(validation[:available], validation[:message].to_s)
end

def validate_vm_for_remote_console(vm, protocol = nil)
protocol ||= "mks"
vm.validate_remote_console_acquire_ticket(protocol)
action_result(true, "")
rescue MiqException::RemoteConsoleNotSupportedError => err
action_result(false, err.message)
end

def start_vm(vm)
desc = "#{vm_ident(vm)} starting"
task_id = queue_object_action(vm, desc, :method_name => "start", :role => "ems_operations")
Expand Down Expand Up @@ -351,5 +381,21 @@ def reboot_guest_vm(vm)
rescue => err
action_result(false, err.to_s)
end

def request_console_vm(vm, protocol)
desc = "#{vm_ident(vm)} requesting console"
task_id = queue_object_action(vm, desc,
:method_name => "remote_console_acquire_ticket",
:role => "ems_operations",
:args => [@auth_user, protocol])
# NOTE:
# we are queuing the :remote_console_acquire_ticket and returning the task id and href.
#
# The remote console ticket/info can be stashed in the task's context_data by the *_acquire_ticket method
# context_data is returned as part of the task i.e. GET /api/tasks/:id
action_result(true, desc, :task_id => task_id)
rescue => err
action_result(false, err.to_s)
end
end
end
9 changes: 9 additions & 0 deletions app/controllers/ops_controller/settings/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,9 @@ def settings_update_save
@edit[:new].set_worker_setting!(:MiqWebServiceWorker, :count, w[:count].to_i)
@edit[:new].set_worker_setting!(:MiqWebServiceWorker, :memory_threshold, human_size_to_rails_method(w[:memory_threshold]))

w = wb[:websocket_worker]
@edit[:new].set_worker_setting!(:MiqWebsocketWorker, :count, w[:count].to_i)

@update = MiqServer.find(@sb[:selected_server_id]).get_config
when "settings_custom_logos" # Custom Logo tab
@changed = (@edit[:new] != @edit[:current].config)
Expand Down Expand Up @@ -726,6 +729,9 @@ def settings_get_form_vars
w[:count] = params[:web_service_worker_count].to_i if params[:web_service_worker_count]
w[:memory_threshold] = params[:web_service_worker_threshold] if params[:web_service_worker_threshold]

w = wb[:websocket_worker]
w[:count] = params[:websocket_worker_count].to_i if params[:websocket_worker_count]

restore_password if params[:restore_password]
set_workers_verify_status
when "settings_custom_logos" # Custom Logo tab
Expand Down Expand Up @@ -944,6 +950,9 @@ def settings_set_form_vars
@sb[:web_service_threshold] = []
@sb[:web_service_threshold] = copy_array(@sb[:threshold])

w = (wb[:websocket_worker] ||= {})
w[:count] = @edit[:current].get_raw_worker_setting(:MiqWebsocketWorker, :count) || 2

@edit[:new].config = copy_hash(@edit[:current].config)
session[:log_depot_default_verify_status] = true
set_workers_verify_status
Expand Down
38 changes: 13 additions & 25 deletions app/controllers/vm_common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,34 +127,23 @@ def websocket_use_ssl?
private :websocket_use_ssl?

def launch_html5_console
# Since the virtual console opens multiple ports, we need to specify * here!
# After the WebSocket proxying/multiplexing is done on port 443, use the following line as an override:
# override_content_security_policy_directives(:connect_src => ["'self'", "wss://#{request.env['SERVER_NAME']}"]
override_content_security_policy_directives(:connect_src => ['*'], :img_src => %w(data: 'self'))
password, host_address, host_port, _proxy_address, _proxy_port, protocol, ssl = @sb[:html5]
proto = request.ssl? ? 'wss' : 'ws'
override_content_security_policy_directives(
:connect_src => ["'self'", "#{proto}://#{request.env['HTTP_HOST']}"],
:img_src => %w(data: 'self')
)
%i(proto secret url).each { |p| params.require(p) }
@secret = j(params[:secret])
@url = j(params[:url])

case protocol
case params[:proto]
when 'spice' # spice, vnc - from rhevm
view = "vm_common/console_spice"
render(:template => 'vm_common/console_spice', :layout => false)
when nil, 'vnc' # nil - from vmware
view = "vm_common/console_vnc"
render(:template => 'vm_common/console_vnc', :layout => false)
when 'novnc_url' # from OpenStack
redirect_to host_address
return
end

proxy_options = WsProxy.start(
:host => host_address,
:host_port => host_port,
:password => password,
:ssl_target => ssl, # ssl on provider side
:encrypt => websocket_use_ssl? # ssl on web client side
)
raise _("Console access failed: proxy errror") if proxy_options.nil?

render :template => view,
:layout => false,
:locals => proxy_options
end

# VM clicked on in the explorer right cell
Expand Down Expand Up @@ -1334,7 +1323,7 @@ def console_before_task(console_type)
end
end

task_id = record.remote_console_acquire_ticket_queue(ticket_type, session[:userid], MiqServer.my_server.id)
task_id = record.remote_console_acquire_ticket_queue(ticket_type, session[:userid])
add_flash(_("Console access failed: Task start failed: ID [%{id}]") %
{:id => task_id.inspect}, :error) unless task_id.kind_of?(Fixnum)

Expand All @@ -1352,7 +1341,6 @@ def console_after_task(console_type)
add_flash(_("Console access failed: %{message}") % {:message => miq_task.message}, :error)
else
@vm = @record = identify_record(params[:id], VmOrTemplate)
@sb[console_type.to_sym] = miq_task.task_results # html5, VNC?, MKS or VMRC
end
render :update do |page|
if @flash_array
Expand All @@ -1361,7 +1349,7 @@ def console_after_task(console_type)
else # open a window to show a VNC or VMWare console
console_action = console_type == 'html5' ? 'launch_html5_console' : 'launch_vmware_console'
page << "miqSparkle(false);"
page << "window.open('#{url_for :controller => controller_name, :action => console_action, :id => @record.id}');"
page << "window.open('#{url_for(miq_task.task_results.merge(:controller => controller_name, :action => console_action, :id => @record.id))}');"
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def console_supported?(type)
%w(SPICE VNC).include?(type.upcase)
end

def remote_console_acquire_ticket(_console_type, _proxy_miq_server = nil)
def remote_console_acquire_ticket(_console_type)
url = ext_management_system.with_provider_connection(:service => "Compute") do |con|
response = con.get_vnc_console(ems_ref, 'novnc')
return nil if response.body.fetch_path('console', 'type') != 'novnc'
Expand All @@ -13,7 +13,7 @@ def remote_console_acquire_ticket(_console_type, _proxy_miq_server = nil)
return nil, url, nil, nil, nil, 'novnc_url', nil
end

def remote_console_acquire_ticket_queue(protocol, userid, proxy_miq_server = nil)
def remote_console_acquire_ticket_queue(protocol, userid)
task_opts = {
:action => "acquiring Instance #{name} #{protocol.to_s.upcase} remote console ticket for user #{userid}",
:userid => userid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,27 @@ def validate_remote_console_acquire_ticket(protocol, options = {})
"#{protocol} remote console requires the vm to be running.") if options[:check_if_running] && state != "on"
end

def remote_console_acquire_ticket(console_type, _proxy_miq_server = nil)
def remote_console_acquire_ticket(userid, console_type)
validate_remote_console_acquire_ticket(console_type)

parsed_ticket = Nokogiri::XML(provider_object.ticket)
display = provider_object.attributes[:display]

host_address = display[:address]
host_port = display[:secure_port] || display[:port]
ssl = display[:secure_port].present?
protocol = display[:type]

proxy_address = proxy_port = nil
password = parsed_ticket.xpath('action/ticket/value')[0].text
return password, host_address, host_port, proxy_address, proxy_port, protocol, ssl
SystemConsole.where(:vm_id => id).each(&:destroy)
# TODO: non-blocking SSL support in the proxy
SystemConsole.create!(
:user => User.find_by(:userid => userid),
:vm_id => id,
:host_name => display[:address],
:port => display[:secure_port] || display[:port],
:ssl => display[:secure_port].present?,
:protocol => display[:type],
:secret => parsed_ticket.xpath('action/ticket/value')[0].text,
:url_secret => SecureRandom.hex
).connection_params
end

def remote_console_acquire_ticket_queue(protocol, userid, proxy_miq_server = nil)
def remote_console_acquire_ticket_queue(protocol, userid)
task_opts = {
:action => "acquiring Vm #{name} #{protocol.to_s.upcase} remote console ticket for user #{userid}",
:userid => userid
Expand All @@ -49,7 +53,7 @@ def remote_console_acquire_ticket_queue(protocol, userid, proxy_miq_server = nil
:priority => MiqQueue::HIGH_PRIORITY,
:role => 'ems_operations',
:zone => my_zone,
:args => [protocol, proxy_miq_server]
:args => [userid, protocol]
}

MiqTask.generic_action_with_callback(task_opts, queue_opts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ def validate_remote_console_acquire_ticket(protocol, options = {})
raise(MiqException::RemoteConsoleNotSupportedError, "#{protocol} remote console requires the vm to be running.") if options[:check_if_running] && state != "on"
end

def remote_console_acquire_ticket(protocol, proxy_miq_server = nil)
send("remote_console_#{protocol.to_s.downcase}_acquire_ticket", proxy_miq_server)
def remote_console_acquire_ticket(userid, protocol)
send("remote_console_#{protocol.to_s.downcase}_acquire_ticket", userid)
end

def remote_console_acquire_ticket_queue(protocol, userid, proxy_miq_server = nil)
def remote_console_acquire_ticket_queue(protocol, userid)
task_opts = {
:action => "acquiring Vm #{name} #{protocol.to_s.upcase} remote console ticket for user #{userid}",
:userid => userid
Expand All @@ -29,7 +29,7 @@ def remote_console_acquire_ticket_queue(protocol, userid, proxy_miq_server = nil
:priority => MiqQueue::HIGH_PRIORITY,
:role => 'ems_operations',
:zone => my_zone,
:args => [protocol, proxy_miq_server]
:args => [userid, protocol]
}

MiqTask.generic_action_with_callback(task_opts, queue_opts)
Expand All @@ -39,7 +39,7 @@ def remote_console_acquire_ticket_queue(protocol, userid, proxy_miq_server = nil
# MKS
#

def remote_console_mks_acquire_ticket(_proxy_miq_server = nil)
def remote_console_mks_acquire_ticket
validate_remote_console_acquire_ticket("mks", :check_if_running => false)
ext_management_system.vm_remote_console_mks_acquire_ticket(self)
end
Expand All @@ -48,7 +48,7 @@ def remote_console_mks_acquire_ticket(_proxy_miq_server = nil)
# VMRC
#

def remote_console_vmrc_acquire_ticket(_proxy_miq_server = nil)
def remote_console_vmrc_acquire_ticket(_userid = nil)
validate_remote_console_acquire_ticket("vmrc")
ext_management_system.remote_console_vmrc_acquire_ticket
end
Expand All @@ -63,25 +63,10 @@ def validate_remote_console_vmrc_support
# VNC
#

def remote_console_vnc_acquire_ticket(proxy_miq_server = nil)
def remote_console_vnc_acquire_ticket(userid)
validate_remote_console_acquire_ticket("vnc")

if proxy_miq_server
proxy_miq_server = MiqServer.extract_objects(proxy_miq_server)
config = proxy_miq_server.get_config.config

proxy_address = config.fetch_path(:server, :vnc_proxy_address)
proxy_address = nil if proxy_address.blank?
proxy_port = config.fetch_path(:server, :vnc_proxy_port)
proxy_port = nil if proxy_port.blank?
proxy_port &&= proxy_port.to_i
else
proxy_address = proxy_port = nil
end

host_address = proxy_address ? host.guid : host.address
password = SecureRandom.base64[0, 8] # Random password from the Base64 character set

password = SecureRandom.base64[0, 8] # Random password from the Base64 character set
host_port = host.reserve_next_available_vnc_port

# Determine if any Vms on this Host already have this port, and if so, disable them
Expand All @@ -101,6 +86,16 @@ def remote_console_vnc_acquire_ticket(proxy_miq_server = nil)
end
update_attributes(:vnc_port => host_port)

return password, host_address, host_port, proxy_address, proxy_port
SystemConsole.where(:vm_id => id).each(&:destroy)
SystemConsole.create!(
:user => User.find_by(:userid => userid),
:vm_id => id,
:host_name => host.address,
:port => host_port,
:ssl => false,
:protocol => 'vnc',
:secret => password,
:url_secret => SecureRandom.hex
).connection_params
end
end
Loading

0 comments on commit 318a31c

Please sign in to comment.