Reviewed-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
On 01/07/2014 06:56 AM, Mark Wu wrote:
Currently, we start a new weksockify proxy on every vm's connect
action.
It causes trouble to allow the vnc traffic in firewall because we choose
a random port for each vm. And it's also obversed that vnc connection fails
because of race between client and proxy server. This patch changes to use
websockify's target_cfg option instead of target_port. Then kimchi can
dynamically add proxy configuration for a vm. It adds a map of target
host/port and token pair to websockify's cfg on the connect request. With
this change, all vnc traffic can be forwared in one tcp port, and therefore
it's possible to add firewall rule for kimchi's vnc remote access.
Signed-off-by: Mark Wu <wudxw(a)linux.vnet.ibm.com>
---
src/kimchi/model.py | 14 ++++++--------
src/kimchi/server.py | 5 +++++
src/kimchi/vnc.py | 41 +++++++++++++++++++++++------------------
ui/js/src/kimchi.api.js | 6 ++++--
4 files changed, 38 insertions(+), 28 deletions(-)
diff --git a/src/kimchi/model.py b/src/kimchi/model.py
index a21fcf7..10e6c3f 100644
--- a/src/kimchi/model.py
+++ b/src/kimchi/model.py
@@ -129,7 +129,6 @@ class Model(object):
self.libvirt_uri = libvirt_uri or 'qemu:///system'
self.conn = LibvirtConnection(self.libvirt_uri)
self.objstore = ObjectStore(objstore_loc)
- self.graphics_ports = {}
self.next_taskid = 1
self.stats = {}
self.host_stats = defaultdict(int)
@@ -504,10 +503,7 @@ class Model(object):
info = dom.info()
state = Model.dom_state_map[info[0]]
screenshot = None
- graphics_type, _ = self._vm_get_graphics(name)
- # 'port' must remain None until a connect call is issued
- graphics_port = (self.graphics_ports.get(name, None) if state ==
'running'
- else None)
+ graphics_type, graphics_port = self._vm_get_graphics(name)
try:
if state == 'running':
screenshot = self.vmscreenshot_lookup(name)
@@ -567,6 +563,8 @@ class Model(object):
with self.objstore as session:
session.delete('vm', dom.UUIDString(), ignore_missing=True)
+ vnc.remove_proxy_token(name)
+
def vm_start(self, name):
dom = self._get_vm(name)
dom.create()
@@ -596,10 +594,10 @@ class Model(object):
def vm_connect(self, name):
graphics, port = self._vm_get_graphics(name)
if graphics == "vnc" and port != None:
- port = vnc.new_ws_proxy(port)
- self.graphics_ports[name] = port
+ vnc.add_proxy_token(name, port)
else:
- raise OperationFailed("Unable to find VNC port in %s" % name)
+ raise OperationFailed("Only able to connect to running vm's vnc
"
+ "graphics.")
def vms_create(self, params):
conn = self.conn.get()
diff --git a/src/kimchi/server.py b/src/kimchi/server.py
index 1467c87..b820263 100644
--- a/src/kimchi/server.py
+++ b/src/kimchi/server.py
@@ -32,6 +32,7 @@ from kimchi import auth
from kimchi import config
from kimchi import model
from kimchi import mockmodel
+from kimchi import vnc
from kimchi.root import Root
from kimchi.utils import get_enabled_plugins, import_class
@@ -188,6 +189,10 @@ class Server(object):
else:
model_instance = model.Model()
+ if isinstance(model_instance, model.Model):
+ vnc_ws_proxy = vnc.new_ws_proxy()
+ cherrypy.engine.subscribe('exit', vnc_ws_proxy.kill)
+
self.app = cherrypy.tree.mount(Root(model_instance, dev_env),
config=self.configObj)
self._load_plugins()
diff --git a/src/kimchi/vnc.py b/src/kimchi/vnc.py
index 089d64a..dc70b46 100644
--- a/src/kimchi/vnc.py
+++ b/src/kimchi/vnc.py
@@ -21,33 +21,38 @@
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+import errno
import os
-import socket
import subprocess
-import sys
-from contextlib import closing
+from kimchi.config import config
-from kimchi.exception import OperationFailed
+WS_TOKENS_DIR = '/var/lib/kimchi/vnc-tokens'
-def _getFreePort():
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- with closing(sock):
- try:
- sock.bind(("0.0.0.0", 0))
- except:
- raise OperationFailed("Could not find a free port")
+def new_ws_proxy():
+ try:
+ os.makedirs(WS_TOKENS_DIR, mode=0755)
+ except OSError as e:
+ if e.errno == errno.EEXIST:
+ pass
- return sock.getsockname()[1]
-
-def new_ws_proxy(target_port):
- src_port = _getFreePort()
cmd = os.path.join(os.path.dirname(__file__), 'websockify.py')
- args = ['python', cmd, str(src_port), '--timeout', '10',
- '--idle-timeout', '10', 'localhost:%s' %
target_port]
+ args = ['python', cmd, config.get('novnc',
'vnc_proxy_port'),
+ '--target-config', WS_TOKENS_DIR]
p = subprocess.Popen(args, close_fds=True)
+ return p
+
+
+def add_proxy_token(name, port):
+ with open(os.path.join(WS_TOKENS_DIR, name), 'w') as f:
+ f.write('%s: localhost:%s' % (name.encode('utf-8'), port))
+
- return src_port
+def remove_proxy_token(name):
+ try:
+ os.unlink(os.path.join(WS_TOKENS_DIR, name))
+ except OSError:
+ pass
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index fbcf4a2..a4608ff 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -291,17 +291,19 @@ var kimchi = {
dataType : 'json'
}).done(function(data, textStatus, xhr) {
http_port = data['http_port'];
+ proxy_port = data['vnc_proxy_port'];
kimchi.requestJSON({
url : "/vms/" + encodeURIComponent(vm) +
"/connect",
type : "POST",
dataType : "json"
- }).done(function(data, textStatus, xhr) {
+ }).done(function() {
/**
* Due to problems with web sockets and self-signed
* certificates, for now we will always redirect to http
*/
url = 'http://' + location.hostname + ':' + http_port;
- url += "/vnc_auto.html?port=" + data.graphics.port;
+ url += "/vnc_auto.html?port=" + proxy_port;
+ url += "&path=?token=" + encodeURIComponent(vm);
window.open(url);
});
}).error(function() {