2 # -*- coding: utf-8 -*-
4 Auxiliary script used to send data between ports on guests.
6 @copyright: 2010 Red Hat, Inc.
7 @author: Jiri Zupka (jzupka@redhat.com)
8 @author: Lukas Doktor (ldoktor@redhat.com)
11 from threading
import Thread
12 import os
, select
, re
, random
, sys
, array
13 import fcntl
, traceback
, signal
15 DEBUGPATH
= "/sys/kernel/debug"
16 SYSFSPATH
= "/sys/class/virtio-ports/"
22 Test tools of virtio_ports.
30 self
.exit_thread
= threading
.Event()
34 self
.catch_signal
= None
35 self
.use_config
= threading
.Event()
38 def _readfile(self
, name
):
40 Read file and return content as string
42 @param name: Name of file
43 @return: Content of file as string
51 print "FAIL: Cannot open file %s" % (name
)
56 def _get_port_status(self
):
58 Get info about ports from kernel debugfs.
60 @return: Ports dictionary of port properties
63 not_present_msg
= "FAIL: There's no virtio-ports dir in debugfs"
64 if (not os
.path
.ismount(DEBUGPATH
)):
65 os
.system('mount -t debugfs none %s' % (DEBUGPATH
))
67 if not os
.path
.isdir('%s/virtio-ports' % (DEBUGPATH
)):
72 viop_names
= os
.listdir('%s/virtio-ports' % (DEBUGPATH
))
73 for name
in viop_names
:
74 open_db_file
= "%s/virtio-ports/%s" % (DEBUGPATH
, name
)
75 f
= open(open_db_file
, 'r')
82 m
= re
.match("(\S+): (\S+)", line
)
83 port
[m
.group(1)] = m
.group(2)
85 if (port
['is_console'] == "yes"):
86 port
["path"] = "/dev/hvc%s" % (port
["console_vtermno"])
87 # Console works like a serialport
89 port
["path"] = "/dev/%s" % name
91 if (not os
.path
.exists(port
['path'])):
92 print "FAIL: %s not exist" % port
['path']
94 sysfspath
= SYSFSPATH
+ name
95 if (not os
.path
.isdir(sysfspath
)):
96 print "FAIL: %s not exist" % (sysfspath
)
98 info_name
= sysfspath
+ "/name"
99 port_name
= self
._readfile
(info_name
).strip()
100 if (port_name
!= port
["name"]):
101 print ("FAIL: Port info not match \n%s - %s\n%s - %s" %
102 (info_name
, port_name
,
103 "%s/virtio-ports/%s" % (DEBUGPATH
, name
),
105 except AttributeError:
106 print ("In file " + open_db_file
+
107 " are bad data\n"+ "".join(file).strip())
108 print ("FAIL: Fail file data.")
111 ports
[port
['name']] = port
117 def init(self
, in_files
):
119 Init and check port properties.
121 self
.ports
= self
._get
_port
_status
()
123 if self
.ports
== None:
125 for item
in in_files
:
126 if (item
[1] != self
.ports
[item
[0]]["is_console"]):
128 print "FAIL: Host console is not like console on guest side\n"
129 print "PASS: Init and check virtioconsole files in system."
132 class Switch(Thread
):
134 Thread that sends data between ports.
136 def __init__ (self
, in_files
, out_files
, event
,
137 cachesize
=1024, method
=0):
139 @param in_files: Array of input files.
140 @param out_files: Array of output files.
141 @param method: Method of read/write access.
142 @param cachesize: Block to receive and send.
144 Thread
.__init
__(self
, name
="Switch")
146 self
.in_files
= in_files
147 self
.out_files
= out_files
148 self
.exit_thread
= event
151 self
.cachesize
= cachesize
154 def _none_mode(self
):
156 Read and write to device in blocking mode
159 while not self
.exit_thread
.isSet():
161 for desc
in self
.in_files
:
162 data
+= os
.read(desc
, self
.cachesize
)
164 for desc
in self
.out_files
:
168 def _poll_mode(self
):
170 Read and write to device in polling mode.
176 for fd
in self
.in_files
:
177 pi
.register(fd
, select
.POLLIN
)
179 for fd
in self
.out_files
:
180 po
.register(fd
, select
.POLLOUT
)
182 while not self
.exit_thread
.isSet():
184 t_out
= self
.out_files
186 readyf
= pi
.poll(1.0)
188 data
+= os
.read(i
[0], self
.cachesize
)
191 while ((len(t_out
) != len(readyf
)) and not
192 self
.exit_thread
.isSet()):
193 readyf
= po
.poll(1.0)
198 def _select_mode(self
):
200 Read and write to device in selecting mode.
202 while not self
.exit_thread
.isSet():
203 ret
= select
.select(self
.in_files
, [], [], 1.0)
207 data
+= os
.read(desc
, self
.cachesize
)
209 ret
= select
.select([], self
.out_files
, [], 1.0)
210 while ((len(self
.out_files
) != len(ret
[1])) and not
211 self
.exit_thread
.isSet()):
212 ret
= select
.select([], self
.out_files
, [], 1.0)
218 if (self
.method
== VirtioGuest
.LOOP_POLL
):
220 elif (self
.method
== VirtioGuest
.LOOP_SELECT
):
226 class Sender(Thread
):
228 Creates a thread which sends random blocks of data to dst port.
230 def __init__(self
, port
, event
, length
):
232 @param port: Destination port
233 @param length: Length of the random data block
235 Thread
.__init
__(self
, name
="Sender")
237 self
.exit_thread
= event
238 self
.data
= array
.array('L')
239 for i
in range(max(length
/ self
.data
.itemsize
, 1)):
240 self
.data
.append(random
.randrange(sys
.maxint
))
243 while not self
.exit_thread
.isSet():
244 os
.write(self
.port
, self
.data
)
247 def _open(self
, in_files
):
249 Open devices and return array of descriptors
251 @param in_files: Files array
252 @return: Array of descriptor
256 for item
in in_files
:
257 name
= self
.ports
[item
]["path"]
258 if (name
in self
.files
):
259 f
.append(self
.files
[name
])
262 self
.files
[name
] = os
.open(name
, os
.O_RDWR
)
263 if (self
.ports
[item
]["is_console"] == "yes"):
264 print os
.system("stty -F %s raw -echo" % (name
))
265 print os
.system("stty -F %s -a" % (name
))
266 f
.append(self
.files
[name
])
267 except Exception, inst
:
268 print "FAIL: Failed to open file %s" % (name
)
273 def pollmask_to_str(mask
):
275 Conver pool mast to string
277 @param mask: poll return mask
280 if (mask
& select
.POLLIN
):
282 if (mask
& select
.POLLPRI
):
284 if (mask
& select
.POLLOUT
):
286 if (mask
& select
.POLLERR
):
288 if (mask
& select
.POLLHUP
):
290 if (mask
& select
.POLLMSG
):
295 def poll(self
, port
, expected
, timeout
=500):
297 Pool event from device and print event like text.
301 in_f
= self
._open
([port
])
306 mask
= p
.poll(timeout
)
308 maskstr
= VirtioGuest
.pollmask_to_str(mask
[0][1])
309 if (mask
[0][1] & expected
) == expected
:
310 print "PASS: Events: " + maskstr
312 emaskstr
= VirtioGuest
.pollmask_to_str(expected
)
313 print "FAIL: Events: " + maskstr
+ " Expected: " + emaskstr
316 def lseek(self
, port
, pos
, how
):
318 Use lseek on the device. The device is unseekable so PASS is returned
319 when lseek command fails and vice versa.
321 @param port: Name of the port
323 @param how: Relativ offset os.SEEK_{SET,CUR,END}
325 fd
= self
._open
([port
])[0]
328 os
.lseek(fd
, pos
, how
)
329 except Exception, inst
:
331 print "PASS: the lseek failed as expected"
334 print "FAIL: unknown error"
336 print "FAIL: the lseek unexpectedly passed"
339 def blocking(self
, port
, mode
=False):
341 Set port function mode blocking/nonblocking
343 @param port: port to set mode
344 @param mode: False to set nonblock mode, True for block mode
346 fd
= self
._open
([port
])[0]
349 fl
= fcntl
.fcntl(fd
, fcntl
.F_GETFL
)
351 fcntl
.fcntl(fd
, fcntl
.F_SETFL
, fl | os
.O_NONBLOCK
)
353 fcntl
.fcntl(fd
, fcntl
.F_SETFL
, fl
& ~os
.O_NONBLOCK
)
355 except Exception, inst
:
356 print "FAIL: Setting (non)blocking mode: " + str(inst
)
360 print "PASS: set to blocking mode"
362 print "PASS: set to nonblocking mode"
365 def __call__(self
, sig
, frame
):
367 Call function. Used for signal handle.
369 if (sig
== signal
.SIGIO
):
370 self
.sigio_handler(sig
, frame
)
373 def sigio_handler(self
, sig
, frame
):
375 Handler for sigio operation.
377 @param sig: signal which call handler.
378 @param frame: frame of caller
382 map(p
.register
, self
.poll_fds
.keys())
387 self
.poll_fds
[mask
[0]][1] |
= mask
[1]
390 def get_sigio_poll_return(self
, port
):
392 Return PASS, FAIL and poll walue in string format.
394 @param port: Port to check poll information.
396 fd
= self
._open
([port
])[0]
398 maskstr
= VirtioGuest
.pollmask_to_str(self
.poll_fds
[fd
][1])
399 if (self
.poll_fds
[fd
][0] ^ self
.poll_fds
[fd
][1]):
400 emaskstr
= VirtioGuest
.pollmask_to_str(self
.poll_fds
[fd
][0])
401 print "FAIL: Events: " + maskstr
+ " Expected: " + emaskstr
403 print "PASS: Events: " + maskstr
404 self
.poll_fds
[fd
][1] = 0
407 def set_pool_want_return(self
, port
, poll_value
):
409 Set value to static variable.
411 @param port: Port which should be set excepted mask
412 @param poll_value: Value to check sigio signal.
414 fd
= self
._open
([port
])[0]
415 self
.poll_fds
[fd
] = [poll_value
, 0]
416 print "PASS: Events: " + VirtioGuest
.pollmask_to_str(poll_value
)
419 def catching_signal(self
):
421 return: True if should set catch signal, False if ignore signal and
422 none when configuration is not changed.
424 ret
= self
.catch_signal
425 self
.catch_signal
= None
429 def async(self
, port
, mode
=True, exp_val
= 0):
431 Set port function mode async/sync.
433 @param port: port which should be pooled.
434 @param mode: False to set sync mode, True for sync mode.
435 @param exp_val: Value which should be pooled.
437 fd
= self
._open
([port
])[0]
440 fcntl
.fcntl(fd
, fcntl
.F_SETOWN
, os
.getpid())
441 fl
= fcntl
.fcntl(fd
, fcntl
.F_GETFL
)
443 self
.use_config
.clear()
445 fcntl
.fcntl(fd
, fcntl
.F_SETFL
, fl | os
.O_ASYNC
)
446 self
.poll_fds
[fd
] = [exp_val
, 0]
447 self
.catch_signal
= True
449 del self
.poll_fds
[fd
]
450 fcntl
.fcntl(fd
, fcntl
.F_SETFL
, fl
& ~os
.O_ASYNC
)
451 self
.catch_signal
= False
453 os
.kill(os
.getpid(), signal
.SIGUSR1
)
454 self
.use_config
.wait()
456 except Exception, inst
:
457 print "FAIL: Setting (a)sync mode: " + str(inst
)
461 print "PASS: Set to async mode"
463 print "PASS: Set to sync mode"
466 def close(self
, file):
470 @param file: File to close.
473 path
= self
.ports
[file]["path"]
475 if path
in self
.files
.keys():
476 descriptor
= self
.files
[path
]
478 if descriptor
!= None:
481 except Exception, inst
:
482 print "FAIL: Closing the file: " + str(inst
)
487 def open(self
, in_file
):
491 @param in_file: Array of files.
492 @return: Array of descriptors.
494 name
= self
.ports
[in_file
]["path"]
496 self
.files
[name
] = os
.open(name
, os
.O_RDWR
)
497 if (self
.ports
[in_file
]["is_console"] == "yes"):
498 print os
.system("stty -F %s raw -echo" % (name
))
499 print "PASS: Open all filles correctly."
500 except Exception, inst
:
501 print "%s\nFAIL: Failed open file %s" % (str(inst
), name
)
504 def loopback(self
, in_files
, out_files
, cachesize
=1024, mode
=LOOP_NONE
):
506 Start a switch thread.
508 (There is a problem with multiple opens of a single file).
510 @param in_files: Array of input files.
511 @param out_files: Array of output files.
512 @param cachesize: Cachesize.
514 self
.ports
= self
._get
_port
_status
()
516 in_f
= self
._open
(in_files
)
517 out_f
= self
._open
(out_files
)
519 s
= self
.Switch(in_f
, out_f
, self
.exit_thread
, cachesize
, mode
)
521 self
.threads
.append(s
)
522 print "PASS: Start switch"
525 def exit_threads(self
):
527 Function end all running data switch.
529 self
.exit_thread
.set()
530 for th
in self
.threads
:
533 self
.exit_thread
.clear()
536 for desc
in self
.files
.itervalues():
539 print "PASS: All threads finished."
550 def send_loop_init(self
, port
, length
):
552 Prepares the sender thread. Requires clean thread structure.
554 self
.ports
= self
._get
_port
_status
()
555 in_f
= self
._open
([port
])
557 self
.threads
.append(self
.Sender(in_f
[0], self
.exit_thread
, length
))
558 print "PASS: Sender prepare"
563 Start sender data transfer. Requires senderprepare run first.
565 self
.threads
[0].start()
566 print "PASS: Sender start"
569 def send(self
, port
, length
=1, mode
=True):
571 Send a data of some length
573 @param port: Port to write data
574 @param length: Length of data
575 @param mode: True = loop mode, False = one shoot mode
577 in_f
= self
._open
([port
])
580 while len(data
) < length
:
581 data
+= "%c" % random
.randrange(255)
583 writes
= os
.write(in_f
[0], data
)
584 except Exception, inst
:
589 while (writes
< length
):
591 writes
+= os
.write(in_f
[0], data
)
592 except Exception, inst
:
595 print "PASS: Send data length %d" % writes
597 print ("FAIL: Partial send: desired %d, transfered %d" %
601 def recv(self
, port
, length
=1, buffer=1024, mode
=True):
603 Recv a data of some length
605 @param port: Port to write data
606 @param length: Length of data
607 @param mode: True = loop mode, False = one shoot mode
609 in_f
= self
._open
([port
])
613 recvs
= os
.read(in_f
[0], buffer)
614 except Exception, inst
:
617 while (len(recvs
) < length
):
619 recvs
+= os
.read(in_f
[0], buffer)
620 except Exception, inst
:
622 if len(recvs
) >= length
:
623 print "PASS: Recv data length %d" % len(recvs
)
625 print ("FAIL: Partial recv: desired %d, transfered %d" %
626 (length
, len(recvs
)))
629 def clean_port(self
, port
, buffer=1024):
630 in_f
= self
._open
([port
])
631 ret
= select
.select([in_f
[0]], [], [], 1.0)
634 buf
= os
.read(in_f
[0], buffer)
635 print ("PASS: Rest in socket: ") + str(buf
[10])
640 Check is only main thread is alive and if guest react.
642 if threading
.activeCount() == 2:
643 print ("PASS: Guest is ok no thread alive")
646 for thread
in threading
.enumerate():
647 threads
+= thread
.name
+ ", "
648 print ("FAIL: On guest run thread. Active thread:" + threads
)
653 Compile virtio_console_guest.py to speed up.
656 py_compile
.compile(sys
.path
[0] + "/virtio_console_guest.py")
657 print "PASS: compile"
664 os
.kill(os
.getpid(), signal
.SIGUSR1
)
669 Worker thread (infinite) loop of virtio_guest.
679 exc_type
, exc_value
, exc_traceback
= sys
.exc_info()
680 print "On Guest exception from: \n" + "".join(
681 traceback
.format_exception(exc_type
,
686 def sigusr_handler(sig
, frame
):
692 Main function with infinite loop to catch signal from system.
694 if (len(sys
.argv
) > 1) and (sys
.argv
[1] == "-c"):
699 slave
= Thread(target
=worker
, args
=(virt
, ))
701 signal
.signal(signal
.SIGUSR1
, sigusr_handler
)
704 catch
= virt
.catching_signal()
706 signal
.signal(signal
.SIGIO
, virt
)
708 signal
.signal(signal
.SIGIO
, signal
.SIG_DFL
)
710 virt
.use_config
.set()
711 print "PASS: guest_exit"
714 if __name__
== "__main__":