mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-26 19:49:12 +00:00
As discussed on the mailing list, current .py files formatting does not conform to the world standard, so we should better reformat it. For this the yapf tool is used. The command I used was yapf -i $(find -name *.py) Signed-off-by: Pavel Emelyanov <xemul@virtuozzo.com>
278 lines
7 KiB
Python
Executable file
278 lines
7 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
import argparse
|
|
import os
|
|
import signal
|
|
import socket
|
|
import time
|
|
import sys
|
|
import subprocess
|
|
|
|
criu_bin = '../../criu/criu'
|
|
|
|
|
|
def mix(nr_tasks, nr_pipes):
|
|
# Returned is the list of combinations.
|
|
# Each combination is the lists of pipe descriptors.
|
|
# Each pipe descriptor is a 2-elemtn tuple, that contains values
|
|
# for R and W ends of pipes, each being a bit-field denoting in
|
|
# which tasks the respective end should be opened or not.
|
|
|
|
# First -- make a full set of combinations for a single pipe.
|
|
max_idx = 1 << nr_tasks
|
|
pipe_mix = [[(r, w)] for r in range(0, max_idx) for w in range(0, max_idx)]
|
|
|
|
# Now, for every pipe throw another one into the game making
|
|
# all possible combinations of what was seen before with the
|
|
# newbie.
|
|
pipes_mix = pipe_mix
|
|
for t in range(1, nr_pipes):
|
|
pipes_mix = [o + n for o in pipes_mix for n in pipe_mix]
|
|
|
|
return pipes_mix
|
|
|
|
|
|
# Called by a test sub-process. It just closes the not needed ends
|
|
# of pipes and sleeps waiting for death.
|
|
def make_pipes(task_nr, nr_pipes, pipes, comb, status_pipe):
|
|
print('\t\tMake pipes for %d' % task_nr)
|
|
# We need to make sure that pipes have their
|
|
# ends according to comb for task_nr
|
|
|
|
for i in range(0, nr_pipes):
|
|
# Read end
|
|
if not (comb[i][0] & (1 << task_nr)):
|
|
os.close(pipes[i][0])
|
|
# Write end
|
|
if not (comb[i][1] & (1 << task_nr)):
|
|
os.close(pipes[i][1])
|
|
|
|
os.write(status_pipe, '0')
|
|
os.close(status_pipe)
|
|
while True:
|
|
time.sleep(100)
|
|
|
|
|
|
def get_pipe_ino(pid, fd):
|
|
try:
|
|
return os.stat('/proc/%d/fd/%d' % (pid, fd)).st_ino
|
|
except:
|
|
return None
|
|
|
|
|
|
def get_pipe_rw(pid, fd):
|
|
for l in open('/proc/%d/fdinfo/%d' % (pid, fd)):
|
|
if l.startswith('flags:'):
|
|
f = l.split(None, 1)[1][-2]
|
|
if f == '0':
|
|
return 0 # Read
|
|
elif f == '1':
|
|
return 1 # Write
|
|
break
|
|
|
|
raise Exception('Unexpected fdinfo contents')
|
|
|
|
|
|
def check_pipe_y(pid, fd, rw, inos):
|
|
ino = get_pipe_ino(pid, fd)
|
|
if ino == None:
|
|
return 'missing '
|
|
if not inos.has_key(fd):
|
|
inos[fd] = ino
|
|
elif inos[fd] != ino:
|
|
return 'wrong '
|
|
mod = get_pipe_rw(pid, fd)
|
|
if mod != rw:
|
|
return 'badmode '
|
|
return None
|
|
|
|
|
|
def check_pipe_n(pid, fd):
|
|
ino = get_pipe_ino(pid, fd)
|
|
if ino == None:
|
|
return None
|
|
else:
|
|
return 'present '
|
|
|
|
|
|
def check_pipe_end(kids, fd, comb, rw, inos):
|
|
t_nr = 0
|
|
for t_pid in kids:
|
|
if comb & (1 << t_nr):
|
|
res = check_pipe_y(t_pid, fd, rw, inos)
|
|
else:
|
|
res = check_pipe_n(t_pid, fd)
|
|
if res != None:
|
|
return res + 'kid(%d)' % t_nr
|
|
t_nr += 1
|
|
return None
|
|
|
|
|
|
def check_pipe(kids, fds, comb, inos):
|
|
for e in (0, 1): # 0 == R, 1 == W, see get_pipe_rw()
|
|
res = check_pipe_end(kids, fds[e], comb[e], e, inos)
|
|
if res != None:
|
|
return res + 'end(%d)' % e
|
|
return None
|
|
|
|
|
|
def check_pipes(kids, pipes, comb):
|
|
# Kids contain pids
|
|
# Pipes contain pipe FDs
|
|
# Comb contain list of pairs of bits for RW ends
|
|
p_nr = 0
|
|
p_inos = {}
|
|
for p_fds in pipes:
|
|
res = check_pipe(kids, p_fds, comb[p_nr], p_inos)
|
|
if res != None:
|
|
return res + 'pipe(%d)' % p_nr
|
|
p_nr += 1
|
|
|
|
return None
|
|
|
|
|
|
# Run by test main process. It opens pipes, then forks kids that
|
|
# will contain needed pipe ends, then report back that it's ready
|
|
# and waits for a signal (unix socket message) to start checking
|
|
# the kids' FD tables.
|
|
def make_comb(comb, opts, status_pipe):
|
|
print('\tMake pipes')
|
|
# 1st -- make needed pipes
|
|
pipes = []
|
|
for p in range(0, opts.pipes):
|
|
pipes.append(os.pipe())
|
|
|
|
# Fork the kids that'll make pipes
|
|
kc_pipe = os.pipe()
|
|
kids = []
|
|
for t in range(0, opts.tasks):
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
os.close(status_pipe)
|
|
os.close(kc_pipe[0])
|
|
make_pipes(t, opts.pipes, pipes, comb, kc_pipe[1])
|
|
sys.exit(1)
|
|
kids.append(pid)
|
|
|
|
os.close(kc_pipe[1])
|
|
for p in pipes:
|
|
os.close(p[0])
|
|
os.close(p[1])
|
|
|
|
# Wait for kids to get ready
|
|
k_res = ''
|
|
while True:
|
|
v = os.read(kc_pipe[0], 16)
|
|
if v == '':
|
|
break
|
|
k_res += v
|
|
os.close(kc_pipe[0])
|
|
|
|
ex_code = 1
|
|
if k_res == '0' * opts.tasks:
|
|
print('\tWait for C/R')
|
|
cmd_sk = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
|
|
cmd_sk.bind('\0CRIUPCSK')
|
|
|
|
# Kids are ready, so is socket for kicking us. Notify the
|
|
# parent task that we are good to go.
|
|
os.write(status_pipe, '0')
|
|
os.close(status_pipe)
|
|
v = cmd_sk.recv(16)
|
|
if v == '0':
|
|
print('\tCheck pipes')
|
|
res = check_pipes(kids, pipes, comb)
|
|
if res == None:
|
|
ex_code = 0
|
|
else:
|
|
print('\tFAIL %s' % res)
|
|
|
|
# Just kill kids, all checks are done by us, we don't need'em any more
|
|
for t in kids:
|
|
os.kill(t, signal.SIGKILL)
|
|
os.waitpid(t, 0)
|
|
|
|
return ex_code
|
|
|
|
|
|
def cr_test(pid):
|
|
print('C/R test')
|
|
img_dir = 'pimg_%d' % pid
|
|
try:
|
|
os.mkdir(img_dir)
|
|
subprocess.check_call([
|
|
criu_bin, 'dump', '-t',
|
|
'%d' % pid, '-D', img_dir, '-o', 'dump.log', '-v4', '-j'
|
|
])
|
|
except:
|
|
print('`- dump fail')
|
|
return False
|
|
|
|
try:
|
|
os.waitpid(pid, 0)
|
|
subprocess.check_call([
|
|
criu_bin, 'restore', '-D', img_dir, '-o', 'rst.log', '-v4', '-j',
|
|
'-d', '-S'
|
|
])
|
|
except:
|
|
print('`- restore fail')
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def run(comb, opts):
|
|
print('Checking %r' % comb)
|
|
cpipe = os.pipe()
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
os.close(cpipe[0])
|
|
ret = make_comb(comb, opts, cpipe[1])
|
|
sys.exit(ret)
|
|
|
|
# Wait for the main process to get ready
|
|
os.close(cpipe[1])
|
|
res = os.read(cpipe[0], 16)
|
|
os.close(cpipe[0])
|
|
|
|
if res == '0':
|
|
res = cr_test(pid)
|
|
|
|
print('Wake up test')
|
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
|
|
if res:
|
|
res = '0'
|
|
else:
|
|
res = 'X'
|
|
try:
|
|
# Kick the test to check its state
|
|
s.sendto(res, '\0CRIUPCSK')
|
|
except:
|
|
# Restore might have failed or smth else happened
|
|
os.kill(pid, signal.SIGKILL)
|
|
s.close()
|
|
|
|
# Wait for the guy to exit and get the result (PASS/FAIL)
|
|
p, st = os.waitpid(pid, 0)
|
|
if os.WIFEXITED(st):
|
|
st = os.WEXITSTATUS(st)
|
|
|
|
print('Done (%d, pid == %d)' % (st, pid))
|
|
return st == 0
|
|
|
|
|
|
p = argparse.ArgumentParser("CRIU test suite")
|
|
p.add_argument("--tasks", help="Number of tasks", default='2')
|
|
p.add_argument("--pipes", help="Number of pipes", default='2')
|
|
opts = p.parse_args()
|
|
opts.tasks = int(opts.tasks)
|
|
opts.pipes = int(opts.pipes)
|
|
|
|
pipe_combs = mix(opts.tasks, opts.pipes)
|
|
|
|
for comb in pipe_combs:
|
|
if not run(comb, opts):
|
|
print('FAIL')
|
|
break
|
|
else:
|
|
print('PASS')
|