Mohammed Morsi
2009-Jan-15 20:46 UTC
[Ovirt-devel] [PATCH server] add option to forward node's / vm's vnc port in server/taskomatic
this patch adds a 'Forward vnc port locally' checkbox and 'port' text box to the vm create / edit form. Fields are added to the database to support the user's selection and taskomatic is updated to configure the local firewall accordingly. this patch has been tested up to setting the fields via the wui and using these fields to correctly configure the firewall via taskomatic. testing connecting to the vm via vnc/virt/ovirt-viewer hasn't is in progress and thus this may need some additional work. --- src/app/controllers/vm_controller.rb | 2 + src/app/models/vm.rb | 4 + src/app/views/vm/_form.rhtml | 14 ++++ src/app/views/vm/show.rhtml | 4 + src/db/migrate/034_add_vm_vnc.rb | 30 +++++++++ src/task-omatic/task_vm.rb | 6 ++- src/task-omatic/vnc.rb | 109 ++++++++++++++++++++++++++++++++++ 7 files changed, 168 insertions(+), 1 deletions(-) create mode 100644 src/db/migrate/034_add_vm_vnc.rb create mode 100644 src/task-omatic/vnc.rb diff --git a/src/app/controllers/vm_controller.rb b/src/app/controllers/vm_controller.rb index 701dea8..8622b89 100644 --- a/src/app/controllers/vm_controller.rb +++ b/src/app/controllers/vm_controller.rb @@ -116,6 +116,7 @@ class VmController < ApplicationController new_storage_ids = new_storage_ids.sort.collect {|x| x.to_i } needs_restart = true unless current_storage_ids == new_storage_ids end + params[:vm][:forward_vnc] = params[:forward_vnc] params[:vm][:needs_restart] = 1 if needs_restart @vm.update_attributes!(params[:vm]) _setup_vm_provision(params) @@ -346,6 +347,7 @@ class VmController < ApplicationController vm_resource_pool.create_with_parent(hardware_pool) params[:vm][:vm_resource_pool_id] = vm_resource_pool.id end + params[:vm][:forward_vnc] = params[:forward_vnc] @vm = Vm.new(params[:vm]) @perm_obj = @vm.vm_resource_pool @redir_controller = 'resources' diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb index 227f343..cbc5ec0 100644 --- a/src/app/models/vm.rb +++ b/src/app/models/vm.rb @@ -36,6 +36,10 @@ class Vm < ActiveRecord::Base :boot_device, :memory_allocated_in_mb, :memory_allocated, :vnic_mac_addr + validates_numericality_of :forward_vnc_port, + :greater_than => 0, + :if => Proc.new { |vm| vm.forward_vnc } + acts_as_xapian :texts => [ :uuid, :description, :vnic_mac_addr, :state ], :terms => [ [ :search_users, 'U', "search_users" ] ], :eager_load => :smart_pools diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml index 523e81e..ffc9dd7 100644 --- a/src/app/views/vm/_form.rhtml +++ b/src/app/views/vm/_form.rhtml @@ -51,6 +51,20 @@ <div class="clear_row"></div> <div class="clear_row"></div> + <div style="width: 50%; float: left;"> + <%= check_box_tag_with_label "Forward vm's vnc <b>port</b> locally", "forward_vnc", 1, @vm.forward_vnc %> + </div> + <div style="width: 40%; float: left;"> + <%= text_field_with_label "", "vm", "forward_vnc_port", { :style=>"width: 80px;", :size => 7, :disabled => ! @vm.forward_vnc } %> + </div> + <div style="clear:both;"></div> + <div class="clear_row"></div> + <script type="text/javascript"> + $("#forward_vnc").click(function(){ + $("#vm_forward_vnc_port").attr("disabled", $("#forward_vnc").is(":checked") ? "" : "disabled"); + }); + </script> + <%= check_box_tag_with_label "Start VM Now? (pending current resource availability)", "start_now", nil if create or @vm.state == Vm::STATE_STOPPED %> <%= check_box_tag_with_label "Restart VM Now? (pending current resource availability)", "restart_now", nil if @vm.state == Vm::STATE_RUNNING %> diff --git a/src/app/views/vm/show.rhtml b/src/app/views/vm/show.rhtml index f361131..add29b4 100644 --- a/src/app/views/vm/show.rhtml +++ b/src/app/views/vm/show.rhtml @@ -88,6 +88,7 @@ <div id="vms_selection_id" style="display:none"><%= @vm.id %></div> <div class="selection_key"> Uuid:<br/> + <%= @vm.forward_vnc ? "VNC uri:<br/>" : "" %> Num vcpus allocated:<br/> Num vcpus used:<br/> Memory allocated:<br/> @@ -100,6 +101,9 @@ </div> <div class="selection_value"> <%=h @vm.uuid %><br/> + <%= url = request.url + url = request.url[0..(url.index('/', 8) - 1)] + ":" + @vm.forward_vnc_port.to_s + @vm.forward_vnc ? (url + "<br/>") : "" %> <%=h @vm.num_vcpus_allocated %><br/> <%=h @vm.num_vcpus_used %><br/> <%=h @vm.memory_allocated_in_mb %> MB<br/> diff --git a/src/db/migrate/034_add_vm_vnc.rb b/src/db/migrate/034_add_vm_vnc.rb new file mode 100644 index 0000000..a93e457 --- /dev/null +++ b/src/db/migrate/034_add_vm_vnc.rb @@ -0,0 +1,30 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +class AddVmVnc < ActiveRecord::Migration + def self.up + add_column :vms, :forward_vnc, :bool, :default => false + add_column :vms, :forward_vnc_port, :int, :default => 0 + end + + def self.down + drop_column :vms, :forward_vnc + drop_column :vms, :forward_vnc_port + end +end + diff --git a/src/task-omatic/task_vm.rb b/src/task-omatic/task_vm.rb index c187287..8481dd0 100644 --- a/src/task-omatic/task_vm.rb +++ b/src/task-omatic/task_vm.rb @@ -20,6 +20,7 @@ require 'rexml/document' include REXML require 'utils' +require 'vnc' gem 'cobbler' require 'cobbler' @@ -286,6 +287,8 @@ def shut_or_destroy_vm(task, which) vm_orig_state = vm.state setVmState(vm, Vm::STATE_STOPPING) + closeVmVncPort(vm) + begin conn = Libvirt::open("qemu+tcp://" + vm.host.hostname + "/system") dom = conn.lookup_domain_by_uuid(vm.uuid) @@ -428,7 +431,8 @@ def start_vm(task) dom.create setVmVncPort(vm, dom) - rescue + forwardVmVncPort(vm) + rescue Exception => ex if dom != nil dom.undefine end diff --git a/src/task-omatic/vnc.rb b/src/task-omatic/vnc.rb new file mode 100644 index 0000000..d78485a --- /dev/null +++ b/src/task-omatic/vnc.rb @@ -0,0 +1,109 @@ +# Copyright (C) 2008 Red Hat, Inc. +# Written by Mohammed Morsi <mmorsi at redhat.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# FIXME no ruby/libiptc wrapper exists, when +# it does replace iptables command w/ calls to it + at iptables_cmd='/sbin/iptables ' + +# FIXME replace this w/ dnsruby inclusion / call + at dns_lookup_cmd='/usr/bin/dig' + + at vnc_debug = false + +############################## 'private' methods + +def _debug(msg) + puts "\n" + msg + "\n" if @vnc_debug +end + +def _findVmHostIp(vm) + cmdout='/tmp/ovirtvnc' + vm.forward_vnc_port.to_s + cmd=@dns_lookup_cmd + ' ' + vm.host.hostname + + ' +noall +answer +short > ' + cmdout + + system(cmd) + + result = File.read(cmdout).rstrip + _debug( "vm host hostname resolved to " + result.to_s ) + return result +end + +def _vncPortOpen?(port) + cmdout='/tmp/ovirtvnc' + port.to_s + cmd=@iptables_cmd + ' -t nat -nL | grep ' + port.to_s + + ' > ' + cmdout + _debug("vncPortOpen? iptables command: " + cmd + + " cmdout " + cmdout) + + system(cmd) + + return File.size(cmdout) != 0 +end + +def _natRoutingFilter(vm) + return " -p tcp --dport " + vm.forward_vnc_port.to_s + + " -j DNAT --to " + _findVmHostIp(vm) + ":" + vm.forward_vnc_port.to_s + " " +end + +def _masqRoutingFilter(vm) + return " -d " + _findVmHostIp(vm) + " -j MASQUERADE " +end + +############################## 'public' methods + + +def forwardVmVncPort(vm) + return unless vm.forward_vnc + unless vm.forward_vnc_port > 0 + raise "Must specify valid port to forward " + vm.forward_vnc_port.to_s + end + + if _vncPortOpen?(vm.forward_vnc_port) + raise "Port already open " + vm.forward_vnc_port.to_s + end + + nat_rule = @iptables_cmd + " -t nat -A PREROUTING " + + _natRoutingFilter(vm) + masq_rule = @iptables_cmd + " -t nat -A POSTROUTING " + + _masqRoutingFilter(vm) + _debug("forwardVmVncPort nat_rule: " + nat_rule + + " masq_rule: " + masq_rule) + system(nat_rule) + system(masq_rule) +end + +def closeVmVncPort(vm) + # FIXME forward_vnc may have been changed while the vm is running + return unless vm.forward_vnc + unless vm.forward_vnc_port > 0 + raise "Must specify valid port to forward " + vm.forward_vnc_port.to_s + end + + unless _vncPortOpen?(vm.forward_vnc_port) + raise "Port not open " + vm.forward_vnc_port.to_s + end + + nat_rule = @iptables_cmd + " -t nat -D PREROUTING " + + _natRoutingFilter(vm) + masq_rule = @iptables_cmd + " -t nat -D POSTROUTING " + + _masqRoutingFilter(vm) + _debug("closeVmVncPort nat_rule: " + nat_rule + + " masq_rule: " + masq_rule) + system(nat_rule) + system(masq_rule) +end -- 1.6.0.6