[Kimchi-devel] [PATCH v5] [Kimchi 3/8] Implement the backend to support web serial console

Jose Ricardo Ziviani joserz at linux.vnet.ibm.com
Sat Feb 13 00:42:07 UTC 2016


 - This commit implements kimchi backend necessary to receive a web
   request to open the serial console for a particular guest, add
   the security token and starts the server.

Signed-off-by: Jose Ricardo Ziviani <joserz at linux.vnet.ibm.com>
---
 control/vms.py |  3 ++-
 i18n.py        |  3 +++
 model/vms.py   | 37 +++++++++++++++++++++++++++++++++++++
 websocket.py   | 41 ++++++++++++++++++++++++++++++++++++++---
 4 files changed, 80 insertions(+), 4 deletions(-)

diff --git a/control/vms.py b/control/vms.py
index 96bdb20..e9f01e1 100644
--- a/control/vms.py
+++ b/control/vms.py
@@ -1,7 +1,7 @@
 #
 # Project Kimchi
 #
-# Copyright IBM, Corp. 2013-2015
+# Copyright IBM, Corp. 2013-2016
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -55,6 +55,7 @@ class VM(Resource):
                                                           'password'])
         self.suspend = self.generate_action_handler('suspend')
         self.resume = self.generate_action_handler('resume')
+        self.serial = self.generate_action_handler('serial')
 
     @property
     def data(self):
diff --git a/i18n.py b/i18n.py
index bd36efa..59e75f7 100644
--- a/i18n.py
+++ b/i18n.py
@@ -131,6 +131,9 @@ messages = {
     "KCHVM0073E": _("Unable to update the following parameters while the VM is offline: %(params)s"),
     "KCHVM0074E": _("Unable to update the following parameters while the VM is online: %(params)s"),
 
+    "KCHVM0076E": _("VM %(name)s must have serial and console defined to open a web serial console"),
+    "KCHVM0077E": _("Impossible to get the serial console of %(name)s"),
+
     "KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."),
     "KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."),
     "KCHVMHDEV0003E": _("No IOMMU groups found. Host PCI pass through needs IOMMU group to function correctly. "
diff --git a/model/vms.py b/model/vms.py
index da341bf..23e0df9 100644
--- a/model/vms.py
+++ b/model/vms.py
@@ -46,6 +46,7 @@ from wok.xmlutils.utils import xml_item_remove, xml_item_update
 
 from wok.plugins.kimchi import model
 from wok.plugins.kimchi import websocket
+from wok.plugins.kimchi import serialconsole
 from wok.plugins.kimchi.config import READONLY_POOL_TYPE, get_kimchi_version
 from wok.plugins.kimchi.kvmusertests import UserTests
 from wok.plugins.kimchi.model.config import CapabilitiesModel
@@ -234,6 +235,7 @@ class VMModel(object):
         cls = import_class('plugins.kimchi.model.vmsnapshots.VMSnapshotsModel')
         self.vmsnapshots = cls(**kargs)
         self.stats = {}
+        self._serial_procs = []
 
     def has_topology(self, dom):
         xml = dom.XMLDesc(0)
@@ -1181,6 +1183,12 @@ class VMModel(object):
         else:
             memory = info[2] >> 10
 
+        # assure there is no zombie process left
+        for proc in self._serial_procs[:]:
+            if not proc.is_alive():
+                proc.join(1)
+                self._serial_procs.remove(proc)
+
         return {'name': name,
                 'state': state,
                 'stats': res,
@@ -1365,6 +1373,20 @@ class VMModel(object):
             raise OperationFailed("KCHVM0022E",
                                   {'name': name, 'err': e.get_error_message()})
 
+    def _vm_check_serial(self, name):
+        dom = self.get_vm(name, self.conn)
+        xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
+
+        expr = "/domain/devices/serial/@type"
+        if not xpath_get_text(xml, expr):
+            return False
+
+        expr = "/domain/devices/console/@type"
+        if not xpath_get_text(xml, expr):
+            return False
+
+        return True
+
     def _vm_get_graphics(self, name):
         dom = self.get_vm(name, self.conn)
         xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
@@ -1396,6 +1418,21 @@ class VMModel(object):
         return (graphics_type, graphics_listen, graphics_port,
                 graphics_passwd, graphics_passwdValidTo)
 
+    def serial(self, name):
+        if not self._vm_check_serial(name):
+            raise OperationFailed("KCHVM0076E", {'name': name})
+
+        websocket.add_proxy_token(name.encode('utf-8')+'-console',
+                                  '/tmp/%s' % name.encode('utf-8'), True)
+
+        try:
+            self._serial_procs.append(
+                serialconsole.main(name.encode('utf-8'),
+                                   self.conn.get().getURI()))
+        except Exception as e:
+            wok_log.error(e.message)
+            raise OperationFailed("KCHVM0077E", {'name': name})
+
     def connect(self, name):
         # (type, listen, port, passwd, passwdValidTo)
         graphics_port = self._vm_get_graphics(name)[2]
diff --git a/websocket.py b/websocket.py
index 5b681af..4879bc8 100644
--- a/websocket.py
+++ b/websocket.py
@@ -34,10 +34,37 @@ try:
 except ImportError:
     tokenFile = False
 
+try:
+    from websockify import ProxyRequestHandler as request_proxy
+except:
+    from websockify import WebSocketProxy as request_proxy
+
 
 WS_TOKENS_DIR = os.path.join(PluginPaths('kimchi').state_dir, 'ws-tokens')
 
 
+class CustomHandler(request_proxy):
+
+    def get_target(self, target_plugin, path):
+        if issubclass(CustomHandler, object):
+            target = super(CustomHandler, self).get_target(target_plugin,
+                                                           path)
+        else:
+            target = request_proxy.get_target(self, target_plugin, path)
+
+        if target[0] == 'unix_socket':
+            try:
+                self.server.unix_target = target[1]
+            except:
+                self.unix_target = target[1]
+        else:
+            try:
+                self.server.unix_target = None
+            except:
+                self.unix_target = None
+        return target
+
+
 def new_ws_proxy():
     try:
         os.makedirs(WS_TOKENS_DIR, mode=0755)
@@ -64,7 +91,12 @@ def new_ws_proxy():
         params['token_plugin'] = TokenFile(src=WS_TOKENS_DIR)
 
     def start_proxy():
-        server = WebSocketProxy(**params)
+        try:
+            server = WebSocketProxy(RequestHandlerClass=CustomHandler,
+                                    **params)
+        except TypeError:
+            server = CustomHandler(**params)
+
         server.start_server()
 
     proc = Process(target=start_proxy)
@@ -72,7 +104,7 @@ def new_ws_proxy():
     return proc
 
 
-def add_proxy_token(name, port):
+def add_proxy_token(name, port, is_unix_socket=False):
     with open(os.path.join(WS_TOKENS_DIR, name), 'w') as f:
         """
         From python documentation base64.urlsafe_b64encode(s)
@@ -82,7 +114,10 @@ def add_proxy_token(name, port):
         So remove it when needed as base64 can work well without it.
         """
         name = base64.urlsafe_b64encode(name).rstrip('=')
-        f.write('%s: localhost:%s' % (name.encode('utf-8'), port))
+        if is_unix_socket:
+            f.write('%s: unix_socket:%s' % (name.encode('utf-8'), port))
+        else:
+            f.write('%s: localhost:%s' % (name.encode('utf-8'), port))
 
 
 def remove_proxy_token(name):
-- 
1.9.1




More information about the Kimchi-devel mailing list