On 12/27/2013 09:52 AM, Mark Wu wrote:
On 12/27/2013 12:11 AM, Aline Manera wrote:
>
> Just a small comment below
>
> On 12/24/2013 12:41 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>
>> ---
>> Changes:
>> change to add token on connect rather than vm's start to work with
>> vms started by external tools, like virt-manager (Per Royce)
>>
>> src/kimchi/model.py | 14 ++++++--------
>> src/kimchi/server.py | 5 +++++
>> src/kimchi/vnc.py | 42
>> +++++++++++++++++++++++-------------------
>> ui/js/src/kimchi.api.js | 6 ++++--
>> 4 files changed, 38 insertions(+), 29 deletions(-)
>>
>> diff --git a/src/kimchi/model.py b/src/kimchi/model.py
>> index 1a547ec..ba7e53c 100644
>> --- a/src/kimchi/model.py
>> +++ b/src/kimchi/model.py
>> @@ -128,7 +128,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)
>> @@ -503,10 +502,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)
>> @@ -566,6 +562,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()
>> @@ -595,10 +593,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 6ff6fa0..9afe2ec 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 import_class, get_enabled_plugins
>>
>> @@ -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..e476002 100644
>> --- a/src/kimchi/vnc.py
>> +++ b/src/kimchi/vnc.py
>> @@ -21,33 +21,37 @@
>> # 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
>
> 2 lines
I don't understand why we need two blank lines for it. What's the
rule are you following here?
We are using the following rule:
import ...
import ...
import ...
<2 lines>
from ... import ...
from ... import ...
<2 lines>
import kimchi...
from kimchi import ...
<2 lines>
All those blocks must be in alphabetic order
>
>> +from kimchi.config import config
>>
>> -from contextlib import closing
>>
>> +WS_TOKENS_DIR = '/var/lib/kimchi/vnc-tokens'
>>
>> -from kimchi.exception import OperationFailed
>>
>> +def new_ws_proxy():
>> + try:
>> + os.makedirs(WS_TOKENS_DIR, mode=0755)
>> + except OSError as e:
>> + if e.errno == errno.EEXIST:
>> + pass
>>
>> -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")
>> -
>> - 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('server',
'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() {
>