mirror of
https://github.com/checkpoint-restore/criu
synced 2025-08-31 06:15:24 +00:00
In Py2 `range` returns a list and `xrange` creates a sequence object that evaluates lazily. In Py3 `range` is equivalent to `xrange` in Py2. Signed-off-by: Radostin Stoyanov <rstoyanov1@gmail.com> Signed-off-by: Andrei Vagin <avagin@gmail.com>
271 lines
6.1 KiB
Python
Executable File
271 lines
6.1 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')
|