[Kimchi-devel] [PATCH v2] [Kimchi 4/4] Check if guest is listening to serial before connecting to it

Jose Ricardo Ziviani joserz at linux.vnet.ibm.com
Wed Mar 16 17:25:19 UTC 2016


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




More information about the Kimchi-devel mailing list