Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Websocket proxy worker for HTM5 consoles #7078

Merged
merged 4 commits into from
Apr 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you call queue_object_action in the api controller code, does it go here or directly to the remote_console_acquire_ticket method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@martinpovolny that's a question to you

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fryguy : yes, that's my understanding. See app/controllers/api_controller/action.rb

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @martinpovolny on this removal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fryguy : the removal originates in our previous discussion about this

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