
- 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@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