1+ import datetime as dt
2+ import fcntl
13import io
24import os
3- import pty
5+ import select
46import subprocess
7+ import typing
58from pathlib import Path
69
710from test_driver .logger import AbstractLogger
811
912
13+ def readline_with_timeout (
14+ readable : typing .IO [str ], timeout : dt .timedelta
15+ ) -> typing .Generator [str ]:
16+ """
17+ Read a line from `readable` within the given `timeout`, otherwise raises `TimeoutError`.
18+
19+ Note: while the generator is running, `readable` will be in nonblocking mode.
20+ """
21+ fd = readable .fileno ()
22+ og_flags = fcntl .fcntl (fd , fcntl .F_GETFL )
23+ fcntl .fcntl (fd , fcntl .F_SETFL , og_flags | os .O_NONBLOCK )
24+
25+ try :
26+ while True :
27+ ready , _ , _ = select .select ([readable ], [], [], timeout .total_seconds ())
28+ if len (ready ) == 0 :
29+ raise TimeoutError ()
30+
31+ # Under the hood, `readline` may read more than one line from the file descriptor,
32+ # so we cannot just return to the `select`, as it may block, despite there being more
33+ # lines buffered. So, read all the lines before returning to the select. This only
34+ # works if the file descriptor is in non-blocking mode!
35+ while line := readable .readline ():
36+ yield line
37+ finally :
38+ fcntl .fcntl (fd , fcntl .F_SETFL , og_flags )
39+
40+
1041class VLan :
1142 """This class handles a VLAN that the run-vm scripts identify via its
1243 number handles. The network's lifetime equals the object's lifetime.
@@ -33,33 +64,62 @@ def __init__(self, nr: int, tmp_dir: Path, logger: AbstractLogger):
3364 os .environ [f"QEMU_VDE_SOCKET_{ self .nr } " ] = str (self .socket_dir )
3465
3566 self .logger .info ("start vlan" )
36- pty_master , pty_slave = pty .openpty ()
3767
38- # The --hub is required for the scenario determined by
39- # nixos/tests/networking.nix vlan-ping.
40- # VLAN Tagged traffic (802.1Q) seams to be blocked if a vde_switch is
41- # used without the hub mode (flood packets to all ports).
4268 self .process = subprocess .Popen (
43- ["vde_switch" , "-s" , self .socket_dir , "--dirmode" , "0700" , "--hub" ],
44- stdin = pty_slave ,
69+ [
70+ "vde_switch" ,
71+ "--sock" ,
72+ self .socket_dir ,
73+ "--dirmode" ,
74+ "0700" ,
75+ # The --hub is required for the scenario determined by
76+ # nixos/tests/networkd-and-scripted.nix vlan-ping.
77+ # VLAN Tagged traffic (802.1Q) seems to be blocked if a vde_switch is
78+ # used without the hub mode (flood packets to all ports).
79+ "--hub" ,
80+ ],
81+ bufsize = 1 , # Line buffered.
82+ stdin = subprocess .PIPE ,
4583 stdout = subprocess .PIPE ,
46- stderr = subprocess . PIPE ,
47- shell = False ,
84+ stderr = None , # Do not swallow stderr.
85+ text = True ,
4886 )
4987 self .pid = self .process .pid
50- self .fd = os .fdopen (pty_master , "w" )
51- self .fd .write ("version\n " )
5288
53- # TODO: perl version checks if this can be read from
54- # an if not, dies. we could hang here forever. Fix it.
89+ assert self .process .stdin is not None
90+ self .process .stdin .write ("showinfo\n " )
91+
92+ # showinfo's output looks like this:
93+ #
94+ # ```
95+ # vde$ showinfo
96+ # 0000 DATA END WITH '.'
97+ # VDE switch V.2.3.3
98+ # (C) Virtual Square Team (coord. R. Davoli) 2005,2006,2007 - GPLv2
99+ #
100+ # pid 82406 MAC 00:ff:62:25:47:55 uptime 45
101+ # .
102+ # 1000 Success
103+ # ```
104+ #
105+ # We read past all the output until we get to the `1000 Success`.
106+ # This serves 2 purposes:
107+ # 1. It's a nice sanity check that `vde_switch` is actually working.
108+ # 2. By the time we're done, `vde_switch` will have created the
109+ # `ctl` socket in `socket_dir`, so we don't have to wait for it to exist.
55110 assert self .process .stdout is not None
56- self .process .stdout .readline ()
57- if not (self .socket_dir / "ctl" ).exists ():
58- self .logger .error ("cannot start vde_switch" )
111+ for line in readline_with_timeout (
112+ self .process .stdout , timeout = dt .timedelta (seconds = 5 )
113+ ):
114+ if "1000 Success" in line :
115+ break
116+
117+ assert (self .socket_dir / "ctl" ).exists (), "cannot start vde_switch"
59118
60119 self .logger .info (f"running vlan (pid { self .pid } ; ctl { self .socket_dir } )" )
61120
62121 def stop (self ) -> None :
63122 self .logger .info (f"kill vlan (pid { self .pid } )" )
64- self .fd .close ()
123+ assert self .process .stdin is not None
124+ self .process .stdin .close ()
65125 self .process .terminate ()
0 commit comments