[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 20:44:21 UTC 2016



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




More information about the Kimchi-devel mailing list