[PATCH v2] [Kimchi 0/4] Fix issues on web serial console

v2: - applied code review This patchset contains some issues found in the web serial console code as well as some small improvements. Jose Ricardo Ziviani (4): Move unix socket files from /tmp to /run Improve log messages printed by the serial console Make serial console timeout configurable Check if guest is listening to serial before connecting to it i18n.py | 2 + kimchi.conf | 3 ++ model/vms.py | 26 ++++++++-- serialconsole.py | 117 +++++++++++++++++++++++++++++++-------------- tests/test_model.py | 4 +- ui/serial/html/serial.html | 4 -- 6 files changed, 109 insertions(+), 47 deletions(-) -- 1.9.1

- /run is a temporary fs (tmpfs) more adequate to store this kind of file. Signed-off-by: Jose Ricardo Ziviani <joserz@linux.vnet.ibm.com> --- i18n.py | 1 + model/vms.py | 11 ++++++++++- serialconsole.py | 5 +++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/i18n.py b/i18n.py index 707b74b..7abae66 100644 --- a/i18n.py +++ b/i18n.py @@ -135,6 +135,7 @@ messages = { "KCHVM0078E": _("Memory or Maximum Memory value is higher than amount supported by the host: %(memHost)sMiB."), "KCHVM0079E": _("Memory or Maximum Memory value is higher than maximum amount recommended: %(value)sTiB"), "KCHVM0080E": _("Cannot update Maximum Memory when guest is running."), + "KCHVM0081E": _("Impossible to create %(dir)s directory."), "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."), diff --git a/model/vms.py b/model/vms.py index 195c879..e86f8b8 100644 --- a/model/vms.py +++ b/model/vms.py @@ -1467,8 +1467,17 @@ class VMModel(object): if not self._vm_check_serial(name): raise OperationFailed("KCHVM0076E", {'name': name}) + if not os.path.isdir(serialconsole.BASE_DIRECTORY): + try: + os.mkdir(serialconsole.BASE_DIRECTORY) + + except OSError as e: + raise OperationFailed("KCHVM0081E", + {'dir': serialconsole.BASE_DIRECTORY}) + websocket.add_proxy_token(name.encode('utf-8')+'-console', - '/tmp/%s' % name.encode('utf-8'), True) + os.path.join(serialconsole.BASE_DIRECTORY, + name.encode('utf-8')), True) try: self._serial_procs.append( diff --git a/serialconsole.py b/serialconsole.py index 1eb48b1..fa6f6b4 100644 --- a/serialconsole.py +++ b/serialconsole.py @@ -35,6 +35,7 @@ from wok.plugins.kimchi import model SOCKET_QUEUE_BACKLOG = 0 DEFAULT_TIMEOUT = 120 # seconds CTRL_Q = '\x11' +BASE_DIRECTORY = '/run' class SocketServer(Process): @@ -61,13 +62,13 @@ class SocketServer(Process): def __init__(self, guest_name, URI): """Constructs a unix socket server. - Listens to connections on /tmp/<guest name>. + Listens to connections on /run/<guest name>. """ Process.__init__(self) self._guest_name = guest_name self._uri = URI - self._server_addr = '/tmp/%s' % guest_name + self._server_addr = os.path.join(BASE_DIRECTORY, guest_name) if os.path.exists(self._server_addr): wok_log.error('Cannot connect to %s due to an existing ' 'connection', guest_name) -- 1.9.1

- Include the process name in the log to highlight messages from different process and thus make is easier to debug. Signed-off-by: Jose Ricardo Ziviani <joserz@linux.vnet.ibm.com> --- serialconsole.py | 56 ++++++++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/serialconsole.py b/serialconsole.py index fa6f6b4..5295f68 100644 --- a/serialconsole.py +++ b/serialconsole.py @@ -27,9 +27,8 @@ import time from multiprocessing import Process - -from wok.utils import wok_log from wok.plugins.kimchi import model +from wok.utils import wok_log SOCKET_QUEUE_BACKLOG = 0 @@ -70,8 +69,6 @@ class SocketServer(Process): self._uri = URI self._server_addr = os.path.join(BASE_DIRECTORY, guest_name) if os.path.exists(self._server_addr): - wok_log.error('Cannot connect to %s due to an existing ' - 'connection', guest_name) raise RuntimeError('There is an existing connection to %s' % guest_name) @@ -82,7 +79,8 @@ class SocketServer(Process): 1) self._socket.bind(self._server_addr) self._socket.listen(SOCKET_QUEUE_BACKLOG) - wok_log.info('socket server to guest %s created', guest_name) + wok_log.info('[%s] socket server to guest %s created', self.name, + guest_name) def run(self): """Implements customized run method from Process. @@ -98,7 +96,8 @@ class SocketServer(Process): data = stream.recv(1024) except Exception as e: - wok_log.info('Error when reading from console: %s', e.message) + wok_log.info('[%s] Error when reading from console: %s', + self.name, e.message) return # return if no data received or client socket(opaque) is not valid @@ -125,11 +124,11 @@ class SocketServer(Process): """ libvirt.virEventRegisterDefaultImpl() try: - guest = LibvirtGuest(self._guest_name, self._uri) + guest = LibvirtGuest(self._guest_name, self._uri, self.name) except Exception as e: - wok_log.error('Cannot open the guest %s due to %s', - self._guest_name, e.message) + wok_log.error('[%s] Cannot open the guest %s due to %s', + self.name, self._guest_name, e.message) self._socket.close() sys.exit(1) @@ -147,8 +146,8 @@ class SocketServer(Process): pass finally: - wok_log.info("Shutting down the socket server to %s console", - self._guest_name) + wok_log.info("[%s] Shutting down the socket server to %s console", + self.name, self._guest_name) self._socket.close() if os.path.exists(self._server_addr): os.unlink(self._server_addr) @@ -157,7 +156,8 @@ class SocketServer(Process): console.eventRemoveCallback() except Exception as e: - wok_log.info('Callback is probably removed: %s', e.message) + wok_log.info('[%s] Callback is probably removed: %s', + self.name, e.message) guest.close() @@ -171,8 +171,7 @@ class SocketServer(Process): """ client, client_addr = self._socket.accept() client.settimeout(DEFAULT_TIMEOUT) - wok_log.info('Client %s connected to %s', - str(client_addr), + wok_log.info('[%s] Client connected to %s', self.name, self._guest_name) # register the callback to receive any data from the console @@ -191,9 +190,8 @@ class SocketServer(Process): data = client.recv(1024) except Exception as e: - wok_log.info('Client %s disconnected from %s: %s', - str(client_addr), - self._guest_name, + wok_log.info('[%s] Client %s disconnected from %s: %s', + self.name, str(client_addr), self._guest_name, e.message) break @@ -206,8 +204,8 @@ class SocketServer(Process): console.send(data) except: - wok_log.info('Console of %s is not accessible', - self._guest_name) + wok_log.info('[%s] Console of %s is not accessible', + self.name, self._guest_name) break # clear used resources when the connection is closed and, if possible, @@ -222,26 +220,25 @@ class SocketServer(Process): class LibvirtGuest(object): - def __init__(self, guest_name, uri): + def __init__(self, guest_name, uri, process_name): """ Constructs a guest object that opens a connection to libvirt and searchs for a particular guest, provided by the caller. """ + self._proc_name = process_name try: libvirt = model.libvirtconnection.LibvirtConnection(uri) self._guest = model.vms.VMModel.get_vm(guest_name, libvirt) except Exception as e: - wok_log.error('Cannot open guest %s: %s', guest_name, e.message) + wok_log.error('[%s] Cannot open guest %s: %s', self._proc_name, + guest_name, e.message) raise self._libvirt = libvirt.get() self._name = guest_name self._stream = None - def get_name(self): - return self._name - def is_running(self): """ Checks if this guest is currently in a running state. @@ -258,8 +255,8 @@ class LibvirtGuest(object): # guest must be in a running state to get its console counter = 10 while not self.is_running(): - wok_log.info('Guest %s is not running, waiting for it', - self._name) + wok_log.info('[%s] Guest %s is not running, waiting for it', + self._proc_name, self._name) counter -= 1 if counter <= 0: @@ -269,8 +266,8 @@ class LibvirtGuest(object): # attach a stream in the guest console so we can read from/write to it if self._stream is None: - wok_log.info('Opening the console for guest %s', - self._name) + wok_log.info('[%s] Opening the console for guest %s', + self._proc_name, self._name) self._stream = self._libvirt.newStream(libvirt.VIR_STREAM_NONBLOCK) self._guest.openConsole(None, self._stream, @@ -295,8 +292,7 @@ def main(guest_name, URI): server = SocketServer(guest_name, URI='qemu:///system') except Exception as e: - wok_log.error('Cannot create the socket server for %s due to %s', - guest_name, e.message) + wok_log.error('Cannot create the socket server: %s', e.message) raise server.start() -- 1.9.1

- Add a new parameter in config.py to make it easier for users to configure the serial console timeout. Signed-off-by: Jose Ricardo Ziviani <joserz@linux.vnet.ibm.com> --- kimchi.conf | 3 +++ serialconsole.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/kimchi.conf b/kimchi.conf index 1bfd6f9..9e0f20d 100644 --- a/kimchi.conf +++ b/kimchi.conf @@ -10,6 +10,9 @@ uri = "/plugins/kimchi" # in the same network. Check README-federation for more details. federation = False +# Serial console timeout (seconds) +SERIAL_CONSOLE_TIMEOUT = 120 + # Automatically create ISO pool on server start up create_iso_pool = True diff --git a/serialconsole.py b/serialconsole.py index 5295f68..349689f 100644 --- a/serialconsole.py +++ b/serialconsole.py @@ -28,11 +28,11 @@ import time from multiprocessing import Process from wok.plugins.kimchi import model +from wok.plugins.kimchi.config import config from wok.utils import wok_log SOCKET_QUEUE_BACKLOG = 0 -DEFAULT_TIMEOUT = 120 # seconds CTRL_Q = '\x11' BASE_DIRECTORY = '/run' @@ -170,7 +170,8 @@ class SocketServer(Process): client console. """ client, client_addr = self._socket.accept() - client.settimeout(DEFAULT_TIMEOUT) + client.settimeout(config.get('kimchi', {}). + get('SERIAL_CONSOLE_TIMEOUT', 120)) wok_log.info('[%s] Client connected to %s', self.name, self._guest_name) -- 1.9.1

- This commit implements code that will check if the selected guest is responding to data sent to its serial port before allowing client connections to it. If it's not responding, an error message will be displayed. - There are 2 cases where the error message will be displayed: 1) guest is set up correctly but kernel is not sending boot msgs, for example: I configured 'console=ttyS0' in my guest grub.conf and I just booted it. I'll only be able to get the serial after the kernel starts sending messages to ttyS0. 2) guest is not configured to use the serial. Signed-off-by: Jose Ricardo Ziviani <joserz@linux.vnet.ibm.com> --- i18n.py | 1 + model/vms.py | 15 ++++++++++--- serialconsole.py | 55 +++++++++++++++++++++++++++++++++++++++++----- tests/test_model.py | 4 ++-- ui/serial/html/serial.html | 4 ---- 5 files changed, 65 insertions(+), 14 deletions(-) diff --git a/i18n.py b/i18n.py index 7abae66..013fdb7 100644 --- a/i18n.py +++ b/i18n.py @@ -136,6 +136,7 @@ messages = { "KCHVM0079E": _("Memory or Maximum Memory value is higher than maximum amount recommended: %(value)sTiB"), "KCHVM0080E": _("Cannot update Maximum Memory when guest is running."), "KCHVM0081E": _("Impossible to create %(dir)s directory."), + "KCHVM0082E": _("Either the guest %(name)s did not start to listen to the serial or it is not configured to use the serial console."), "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."), diff --git a/model/vms.py b/model/vms.py index e86f8b8..03ffdae 100644 --- a/model/vms.py +++ b/model/vms.py @@ -1480,9 +1480,18 @@ class VMModel(object): name.encode('utf-8')), True) try: - self._serial_procs.append( - serialconsole.main(name.encode('utf-8'), - self.conn.get().getURI())) + proc = serialconsole.main(name.encode('utf-8'), + self.conn.get().getURI()) + + proc.join(2) + if not proc.is_alive(): + raise OperationFailed("KCHVM0082E", {'name': name}) + + self._serial_procs.append(proc) + + except OperationFailed: + raise + except Exception as e: wok_log.error(e.message) raise OperationFailed("KCHVM0077E", {'name': name}) diff --git a/serialconsole.py b/serialconsole.py index 349689f..d290798 100644 --- a/serialconsole.py +++ b/serialconsole.py @@ -87,6 +87,35 @@ class SocketServer(Process): """ self.listen() + def _is_vm_listening_serial(self, console): + """Checks if the guest is listening (reading/writing) to the serial + console. + """ + is_listening = [] + + def _test_output(stream, event, opaque): + is_listening.append(1) + + def _event_loop(): + while not is_listening: + libvirt.virEventRunDefaultImpl() + + console.eventAddCallback(libvirt.VIR_STREAM_EVENT_READABLE, + _test_output, + None) + libvirt_loop = threading.Thread(target=_event_loop) + libvirt_loop.start() + + console.send("\n") + libvirt_loop.join(1) + + if not libvirt_loop.is_alive(): + console.eventRemoveCallback() + return True + + console.eventRemoveCallback() + return False + def _send_to_client(self, stream, event, opaque): """Handles libvirt stream readable events. @@ -139,6 +168,14 @@ class SocketServer(Process): console = None try: console = guest.get_console() + if console is None: + wok_log.error('[%s] Cannot get the console to %s', + self.name, self._guest_name) + return + + if not self._is_vm_listening_serial(console): + sys.exit(1) + self._listen(guest, console) # clear resources aquired when the process is killed @@ -191,9 +228,8 @@ class SocketServer(Process): data = client.recv(1024) except Exception as e: - wok_log.info('[%s] Client %s disconnected from %s: %s', - self.name, str(client_addr), self._guest_name, - e.message) + wok_log.info('[%s] Client disconnected from %s: %s', + self.name, self._guest_name, e.message) break if not data or data == CTRL_Q: @@ -283,14 +319,14 @@ class LibvirtGuest(object): # guest -def main(guest_name, URI): +def main(guest_name, URI='qemu:///system'): """Main entry point to create a socket server. Starts a new socket server to listen messages to/from the guest. """ server = None try: - server = SocketServer(guest_name, URI='qemu:///system') + server = SocketServer(guest_name, URI) except Exception as e: wok_log.error('Cannot create the socket server: %s', e.message) @@ -304,6 +340,15 @@ if __name__ == '__main__': """Executes a stand alone instance of the socket server. This may be useful for testing/debugging. + + In order to debug, add the path before importing kimchi/wok code: + sys.path.append('../../../') + + start the server: + python serialconsole.py <guest_name> + + and, on another terminal, run: + netcat -U /run/<guest_name> """ argc = len(sys.argv) if argc != 2: diff --git a/tests/test_model.py b/tests/test_model.py index 0229b3d..fc7ee10 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -341,8 +341,8 @@ class ModelTests(unittest.TestCase): inst.vm_start('kimchi-serial') rollback.prependDefer(inst.vm_poweroff, 'kimchi-serial') - inst.vm_serial('kimchi-serial') - self.assertTrue(os.path.exists('/tmp/kimchi-serial')) + with self.assertRaises(OperationFailed): + inst.vm_serial('kimchi-serial') inst.template_delete('test') diff --git a/ui/serial/html/serial.html b/ui/serial/html/serial.html index 8010a59..e58a282 100644 --- a/ui/serial/html/serial.html +++ b/ui/serial/html/serial.html @@ -79,10 +79,6 @@ socket.send(window.btoa(data)); }); - socket.onopen = function() { - socket.send(window.btoa('\n')); - }; - socket.onmessage = function(event) { var message = event.data; term.write(window.atob(message)); -- 1.9.1

On 03/16/2016 02:25 PM, Jose Ricardo Ziviani wrote:
- This commit implements code that will check if the selected guest is responding to data sent to its serial port before allowing client connections to it. If it's not responding, an error message will be displayed. - There are 2 cases where the error message will be displayed: 1) guest is set up correctly but kernel is not sending boot msgs, for example: I configured 'console=ttyS0' in my guest grub.conf and I just booted it. I'll only be able to get the serial after the kernel starts sending messages to ttyS0. 2) guest is not configured to use the serial.
Signed-off-by: Jose Ricardo Ziviani <joserz@linux.vnet.ibm.com> --- i18n.py | 1 + model/vms.py | 15 ++++++++++--- serialconsole.py | 55 +++++++++++++++++++++++++++++++++++++++++----- tests/test_model.py | 4 ++-- ui/serial/html/serial.html | 4 ---- 5 files changed, 65 insertions(+), 14 deletions(-)
diff --git a/i18n.py b/i18n.py index 7abae66..013fdb7 100644 --- a/i18n.py +++ b/i18n.py @@ -136,6 +136,7 @@ messages = { "KCHVM0079E": _("Memory or Maximum Memory value is higher than maximum amount recommended: %(value)sTiB"), "KCHVM0080E": _("Cannot update Maximum Memory when guest is running."), "KCHVM0081E": _("Impossible to create %(dir)s directory."), + "KCHVM0082E": _("Either the guest %(name)s did not start to listen to the serial or it is not configured to use the serial console."),
"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."), diff --git a/model/vms.py b/model/vms.py index e86f8b8..03ffdae 100644 --- a/model/vms.py +++ b/model/vms.py @@ -1480,9 +1480,18 @@ class VMModel(object): name.encode('utf-8')), True)
try: - self._serial_procs.append( - serialconsole.main(name.encode('utf-8'), - self.conn.get().getURI())) + proc = serialconsole.main(name.encode('utf-8'), + self.conn.get().getURI()) + + proc.join(2) + if not proc.is_alive(): + raise OperationFailed("KCHVM0082E", {'name': name}) + + self._serial_procs.append(proc) + + except OperationFailed: + raise + except Exception as e: wok_log.error(e.message) raise OperationFailed("KCHVM0077E", {'name': name}) diff --git a/serialconsole.py b/serialconsole.py index 349689f..d290798 100644 --- a/serialconsole.py +++ b/serialconsole.py @@ -87,6 +87,35 @@ class SocketServer(Process): """ self.listen()
+ def _is_vm_listening_serial(self, console): + """Checks if the guest is listening (reading/writing) to the serial + console. + """ + is_listening = [] + + def _test_output(stream, event, opaque): + is_listening.append(1) + + def _event_loop(): + while not is_listening: + libvirt.virEventRunDefaultImpl() + + console.eventAddCallback(libvirt.VIR_STREAM_EVENT_READABLE, + _test_output, + None) Using Thread is safe? No exception can be raise? + libvirt_loop = threading.Thread(target=_event_loop) + libvirt_loop.start() + + console.send("\n") + libvirt_loop.join(1) + + if not libvirt_loop.is_alive(): + console.eventRemoveCallback() + return True + + console.eventRemoveCallback() + return False + def _send_to_client(self, stream, event, opaque): """Handles libvirt stream readable events.
@@ -139,6 +168,14 @@ class SocketServer(Process): console = None try: console = guest.get_console() + if console is None: + wok_log.error('[%s] Cannot get the console to %s', + self.name, self._guest_name) + return + + if not self._is_vm_listening_serial(console): + sys.exit(1) + self._listen(guest, console)
# clear resources aquired when the process is killed @@ -191,9 +228,8 @@ class SocketServer(Process): data = client.recv(1024)
except Exception as e: - wok_log.info('[%s] Client %s disconnected from %s: %s', - self.name, str(client_addr), self._guest_name, - e.message) + wok_log.info('[%s] Client disconnected from %s: %s', + self.name, self._guest_name, e.message) break
if not data or data == CTRL_Q: @@ -283,14 +319,14 @@ class LibvirtGuest(object): # guest
-def main(guest_name, URI): +def main(guest_name, URI='qemu:///system'): """Main entry point to create a socket server.
Starts a new socket server to listen messages to/from the guest. """ server = None try: - server = SocketServer(guest_name, URI='qemu:///system') + server = SocketServer(guest_name, URI)
except Exception as e: wok_log.error('Cannot create the socket server: %s', e.message) @@ -304,6 +340,15 @@ if __name__ == '__main__': """Executes a stand alone instance of the socket server.
This may be useful for testing/debugging. + + In order to debug, add the path before importing kimchi/wok code: + sys.path.append('../../../') + + start the server: + python serialconsole.py <guest_name> + + and, on another terminal, run: + netcat -U /run/<guest_name> """ argc = len(sys.argv) if argc != 2: diff --git a/tests/test_model.py b/tests/test_model.py index 0229b3d..fc7ee10 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -341,8 +341,8 @@ class ModelTests(unittest.TestCase): inst.vm_start('kimchi-serial') rollback.prependDefer(inst.vm_poweroff, 'kimchi-serial')
- inst.vm_serial('kimchi-serial') - self.assertTrue(os.path.exists('/tmp/kimchi-serial')) + with self.assertRaises(OperationFailed): + inst.vm_serial('kimchi-serial')
inst.template_delete('test')
diff --git a/ui/serial/html/serial.html b/ui/serial/html/serial.html index 8010a59..e58a282 100644 --- a/ui/serial/html/serial.html +++ b/ui/serial/html/serial.html @@ -79,10 +79,6 @@ socket.send(window.btoa(data)); });
- socket.onopen = function() { - socket.send(window.btoa('\n')); - }; - socket.onmessage = function(event) { var message = event.data; term.write(window.atob(message));
-- Ramon Nunes Medeiros Kimchi Developer Linux Technology Center Brazil IBM Systems & Technology Group Phone : +55 19 2132 7878 ramonn@br.ibm.com

On 16-03-2016 17:25, Ramon Medeiros wrote:
On 03/16/2016 02:25 PM, Jose Ricardo Ziviani wrote:
- This commit implements code that will check if the selected guest is responding to data sent to its serial port before allowing client connections to it. If it's not responding, an error message will be displayed. - There are 2 cases where the error message will be displayed: 1) guest is set up correctly but kernel is not sending boot msgs, for example: I configured 'console=ttyS0' in my guest grub.conf and I just booted it. I'll only be able to get the serial after the kernel starts sending messages to ttyS0. 2) guest is not configured to use the serial.
Signed-off-by: Jose Ricardo Ziviani <joserz@linux.vnet.ibm.com> --- i18n.py | 1 + model/vms.py | 15 ++++++++++--- serialconsole.py | 55 +++++++++++++++++++++++++++++++++++++++++----- tests/test_model.py | 4 ++-- ui/serial/html/serial.html | 4 ---- 5 files changed, 65 insertions(+), 14 deletions(-)
diff --git a/i18n.py b/i18n.py index 7abae66..013fdb7 100644 --- a/i18n.py +++ b/i18n.py @@ -136,6 +136,7 @@ messages = { "KCHVM0079E": _("Memory or Maximum Memory value is higher than maximum amount recommended: %(value)sTiB"), "KCHVM0080E": _("Cannot update Maximum Memory when guest is running."), "KCHVM0081E": _("Impossible to create %(dir)s directory."), + "KCHVM0082E": _("Either the guest %(name)s did not start to listen to the serial or it is not configured to use the serial console."),
"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."), diff --git a/model/vms.py b/model/vms.py index e86f8b8..03ffdae 100644 --- a/model/vms.py +++ b/model/vms.py @@ -1480,9 +1480,18 @@ class VMModel(object):
name.encode('utf-8')), True)
try: - self._serial_procs.append( - serialconsole.main(name.encode('utf-8'), - self.conn.get().getURI())) + proc = serialconsole.main(name.encode('utf-8'), + self.conn.get().getURI()) + + proc.join(2) + if not proc.is_alive(): + raise OperationFailed("KCHVM0082E", {'name': name}) + + self._serial_procs.append(proc) + + except OperationFailed: + raise + except Exception as e: wok_log.error(e.message) raise OperationFailed("KCHVM0077E", {'name': name}) diff --git a/serialconsole.py b/serialconsole.py index 349689f..d290798 100644 --- a/serialconsole.py +++ b/serialconsole.py @@ -87,6 +87,35 @@ class SocketServer(Process): """ self.listen()
+ def _is_vm_listening_serial(self, console): + """Checks if the guest is listening (reading/writing) to the serial + console. + """ + is_listening = [] + + def _test_output(stream, event, opaque): + is_listening.append(1) + + def _event_loop(): + while not is_listening: + libvirt.virEventRunDefaultImpl() + + console.eventAddCallback(libvirt.VIR_STREAM_EVENT_READABLE, + _test_output, + None) Using Thread is safe? No exception can be raise?
Libvirt recommends to run the eventloop in a thread, otherwise it is almost impractical since virEventRunDefaultImpl will block from some microseconds but time enough to make _is_vm_listening_serial fails. In this case, even if virEventRunDefaultImpl blocks forever, or even if it fails for any unexpected reason that forces the thread to keep alive, the code libvirt_loop.join(1) will try to wait for it for 1 second, then will exit(1) the process. Note that this code already runs in a separate process (it's a new python interpretor, totally isolated from kimchi). It means that the python interpretor will be killed with its threads together.
+ libvirt_loop = threading.Thread(target=_event_loop) + libvirt_loop.start() + + console.send("\n") + libvirt_loop.join(1) + + if not libvirt_loop.is_alive(): + console.eventRemoveCallback() + return True + + console.eventRemoveCallback() + return False + def _send_to_client(self, stream, event, opaque): """Handles libvirt stream readable events.
@@ -139,6 +168,14 @@ class SocketServer(Process): console = None try: console = guest.get_console() + if console is None: + wok_log.error('[%s] Cannot get the console to %s', + self.name, self._guest_name) + return + + if not self._is_vm_listening_serial(console): + sys.exit(1) + self._listen(guest, console)
# clear resources aquired when the process is killed @@ -191,9 +228,8 @@ class SocketServer(Process): data = client.recv(1024)
except Exception as e: - wok_log.info('[%s] Client %s disconnected from %s: %s', - self.name, str(client_addr), self._guest_name, - e.message) + wok_log.info('[%s] Client disconnected from %s: %s', + self.name, self._guest_name, e.message) break
if not data or data == CTRL_Q: @@ -283,14 +319,14 @@ class LibvirtGuest(object): # guest
-def main(guest_name, URI): +def main(guest_name, URI='qemu:///system'): """Main entry point to create a socket server.
Starts a new socket server to listen messages to/from the guest. """ server = None try: - server = SocketServer(guest_name, URI='qemu:///system') + server = SocketServer(guest_name, URI)
except Exception as e: wok_log.error('Cannot create the socket server: %s', e.message) @@ -304,6 +340,15 @@ if __name__ == '__main__': """Executes a stand alone instance of the socket server.
This may be useful for testing/debugging. + + In order to debug, add the path before importing kimchi/wok code: + sys.path.append('../../../') + + start the server: + python serialconsole.py <guest_name> + + and, on another terminal, run: + netcat -U /run/<guest_name> """ argc = len(sys.argv) if argc != 2: diff --git a/tests/test_model.py b/tests/test_model.py index 0229b3d..fc7ee10 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -341,8 +341,8 @@ class ModelTests(unittest.TestCase): inst.vm_start('kimchi-serial') rollback.prependDefer(inst.vm_poweroff, 'kimchi-serial')
- inst.vm_serial('kimchi-serial') - self.assertTrue(os.path.exists('/tmp/kimchi-serial')) + with self.assertRaises(OperationFailed): + inst.vm_serial('kimchi-serial')
inst.template_delete('test')
diff --git a/ui/serial/html/serial.html b/ui/serial/html/serial.html index 8010a59..e58a282 100644 --- a/ui/serial/html/serial.html +++ b/ui/serial/html/serial.html @@ -79,10 +79,6 @@ socket.send(window.btoa(data)); });
- socket.onopen = function() { - socket.send(window.btoa('\n')); - }; - socket.onmessage = function(event) { var message = event.data; term.write(window.atob(message));
-- Jose Ricardo Ziviani ----------------------------- Software Engineer Linux Technology Center - IBM

Reviewed-By: Lucio Correia <luciojhc@linux.vnet.ibm.com> On 16-03-2016 14:25, Jose Ricardo Ziviani wrote:
v2: - applied code review
This patchset contains some issues found in the web serial console code as well as some small improvements.
Jose Ricardo Ziviani (4): Move unix socket files from /tmp to /run Improve log messages printed by the serial console Make serial console timeout configurable Check if guest is listening to serial before connecting to it
i18n.py | 2 + kimchi.conf | 3 ++ model/vms.py | 26 ++++++++-- serialconsole.py | 117 +++++++++++++++++++++++++++++++-------------- tests/test_model.py | 4 +- ui/serial/html/serial.html | 4 -- 6 files changed, 109 insertions(+), 47 deletions(-)
-- Lucio Correia Software Engineer IBM LTC Brazil
participants (4)
-
Aline Manera
-
Jose Ricardo Ziviani
-
Lucio Correia
-
Ramon Medeiros