[Ovirt-devel] [PATCH server] allow admin to setup iptables port forwarding on server for a vm's vnc port

Mohammed Morsi mmorsi at redhat.com
Tue Jan 27 22:32:01 UTC 2009


---
 src/app/controllers/vm_controller.rb |    2 +
 src/app/models/vm.rb                 |    4 +
 src/app/views/vm/_form.rhtml         |   15 ++++
 src/app/views/vm/show.rhtml          |    4 +
 src/db/migrate/034_add_vm_vnc.rb     |   30 +++++++
 src/task-omatic/taskomatic.rb        |    4 +
 src/task-omatic/vnc.rb               |  140 ++++++++++++++++++++++++++++++++++
 7 files changed, 199 insertions(+), 0 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 56501fd..8e1cbee 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)
@@ -345,6 +346,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
     @current_pool_id=@perm_obj.id
diff --git a/src/app/models/vm.rb b/src/app/models/vm.rb
index bf99e2d..15af463 100644
--- a/src/app/models/vm.rb
+++ b/src/app/models/vm.rb
@@ -40,6 +40,10 @@ class Vm < ActiveRecord::Base
   validates_format_of :uuid,
      :with => %r([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})
 
+  validates_numericality_of :forward_vnc_port,
+     :greater_than => 0,
+     :if => Proc.new { |vm| vm.forward_vnc }
+
   validates_numericality_of :needs_restart,
      :greater_than_or_equal_to => 0,
      :less_than_or_equal_to => 1,
diff --git a/src/app/views/vm/_form.rhtml b/src/app/views/vm/_form.rhtml
index 523e81e..486798f 100644
--- a/src/app/views/vm/_form.rhtml
+++ b/src/app/views/vm/_form.rhtml
@@ -51,6 +51,21 @@
     <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/taskomatic.rb b/src/task-omatic/taskomatic.rb
index bcb9bd3..dca6fb7 100755
--- a/src/task-omatic/taskomatic.rb
+++ b/src/task-omatic/taskomatic.rb
@@ -32,6 +32,7 @@ include Daemonize
 
 require 'task_vm'
 require 'task_storage'
+require 'vnc'
 
 class TaskOmatic
 
@@ -232,6 +233,8 @@ class TaskOmatic
       raise "Error destroying VM: #{result.text}" unless result.status == 0
     end
 
+    closeVmVncPort(vm)
+
     # undefine can fail, for instance, if we live migrated from A -> B, and
     # then we are shutting down the VM on B (because it only has "transient"
     # XML).  Therefore, just ignore undefine errors so we do the rest
@@ -303,6 +306,7 @@ class TaskOmatic
     # of places so you'll see a lot of .reloads.
     db_vm.reload
     set_vm_vnc_port(db_vm, result.description) unless result.status != 0
+    forwardVmVncPort(db_vm)
 
     # This information is not available via the libvirt interface.
     db_vm.memory_used = db_vm.memory_allocated
diff --git a/src/task-omatic/vnc.rb b/src/task-omatic/vnc.rb
new file mode 100644
index 0000000..3eb0ca6
--- /dev/null
+++ b/src/task-omatic/vnc.rb
@@ -0,0 +1,140 @@
+# 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.
+
+# TODO no ruby/libiptc wrapper exists, when
+# it does replace iptables command w/ calls to it
+ at iptables_cmd='/sbin/iptables '
+
+# TODO replace this w/ dnsruby inclusion / call
+ at dns_lookup_cmd='/usr/bin/dig'
+
+ at ip_forward_command='echo 1 > /proc/sys/net/ipv4/ip_forward'
+
+ at vnc_debug = false
+
+# TODO can this be retreived in any way
+# since machine will have both external
+# and internal network interface
+ at local_ip = '192.168.50.2'
+
+############################## '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 _forwardRules(vm)
+  ip = _findVmHostIp(vm) 
+  return " -d " + ip + " -p tcp --dport " + vm.vnc_port.to_s + " -j ACCEPT",
+         " -s " + ip + " -p tcp --sport " + vm.vnc_port.to_s + " -j ACCEPT"
+end
+
+def _natRules(vm)
+  ip = _findVmHostIp(vm) 
+
+  # TODO should a "-d external_server_ip" be added to DNAT?
+  return " -p tcp --dport " + vm.forward_vnc_port.to_s + " -j DNAT --to " + ip + ":" + vm.vnc_port.to_s,
+         " -d " + ip + " -p tcp --dport " + vm.vnc_port.to_s + " -j SNAT --to " + @local_ip
+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
+
+   forward_rule1, forward_rule2 = _forwardRules(vm)
+   forward_rule1 = @iptables_cmd + " -A FORWARD " + forward_rule1
+   forward_rule2 = @iptables_cmd + " -A FORWARD " + forward_rule2
+
+   prerouting_rule, postrouting_rule = _natRules(vm)
+   prerouting_rule = @iptables_cmd + " -t nat -A PREROUTING " + prerouting_rule
+   postrouting_rule = @iptables_cmd + " -t nat -A POSTROUTING " + postrouting_rule
+
+   _debug(" open\n forward rule 1: "     + forward_rule1 +
+          "\n forward_rule 2: "   + forward_rule2 +
+          "\n prerouting rule: "  + prerouting_rule +
+          "\n postrouting rule: " + postrouting_rule)
+
+   system(forward_rule1)
+   system(forward_rule2)
+   system(prerouting_rule)
+   system(postrouting_rule)
+   system(@ip_forward_command)
+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
+
+   forward_rule1, forward_rule2 = _forwardRules(vm)
+   forward_rule1 = @iptables_cmd + " -D FORWARD " + forward_rule1
+   forward_rule2 = @iptables_cmd + " -D FORWARD " + forward_rule2
+
+   prerouting_rule, postrouting_rule = _natRules(vm)
+   prerouting_rule = @iptables_cmd + " -t nat -D PREROUTING " + prerouting_rule
+   postrouting_rule = @iptables_cmd + " -t nat -D POSTROUTING " + postrouting_rule
+
+   _debug(" close\n forward rule 1: "     + forward_rule1 +
+          "\n forward_rule 2: "   + forward_rule2 +
+          "\n prerouting rule: "  + prerouting_rule +
+          "\n postrouting rule: " + postrouting_rule)
+
+   system(forward_rule1)
+   system(forward_rule2)
+   system(prerouting_rule)
+   system(postrouting_rule)
+end
-- 
1.6.0.6




More information about the ovirt-devel mailing list