Skip to content

Commit

Permalink
Merge 4898e1f into fbe2692
Browse files Browse the repository at this point in the history
  • Loading branch information
warusadura authored May 26, 2023
2 parents fbe2692 + 4898e1f commit eb41d8e
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 7 deletions.
4 changes: 2 additions & 2 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,11 @@ task:
memory: 8G

setup_script: |
# EPEL is needed for python2-future, python2-junit_xml, python-flake8 and libbsd-devel.
# EPEL is needed for python2-future, python2-junit_xml, python-pathlib, python-flake8 and libbsd-devel.
# Do not fail if latest epel repository definition is already installed
yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm || :
ln -sf /usr/include/google/protobuf/descriptor.proto images/google/protobuf/descriptor.proto
yum install -y findutils gcc git gnutls-devel iproute iptables libaio-devel libasan libcap-devel libnet-devel libnl3-devel libbsd-devel make procps-ng protobuf-c-devel protobuf-devel protobuf-python python python-flake8 python-ipaddress python2-future python2-junit_xml python-yaml python-six sudo tar which e2fsprogs python2-pip rubygem-asciidoctor libselinux-devel
yum install -y findutils gcc git gnutls-devel iproute iptables libaio-devel libasan libcap-devel libnet-devel libnl3-devel libbsd-devel make procps-ng protobuf-c-devel protobuf-devel protobuf-python python python-flake8 python-ipaddress python2-future python2-junit_xml python-yaml python-six python-pathlib sudo tar which e2fsprogs python2-pip rubygem-asciidoctor libselinux-devel
# Even with selinux in permissive mode the selinux tests will be executed
# The Cirrus CI user runs as a service from selinux point of view and is
# much more restricted than a normal shell (system_u:system_r:unconfined_service_t:s0)
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ lint:
flake8 --config=scripts/flake8.cfg lib/py/images/pb2dict.py
flake8 --config=scripts/flake8.cfg lib/py/images/images.py
flake8 --config=scripts/flake8.cfg scripts/criu-ns
flake8 --config=scripts/flake8.cfg test/others/criu-ns/run.py
flake8 --config=scripts/flake8.cfg crit/setup.py
flake8 --config=scripts/flake8.cfg scripts/uninstall_module.py
flake8 --config=scripts/flake8.cfg coredump/
Expand Down
1 change: 1 addition & 0 deletions scripts/ci/run-ci-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ if [ -n "$TRAVIS" ] || [ -n "$CIRCLECI" ]; then
# Error (criu/tty.c:1014): tty: Don't have tty to inherit session from, aborting
make -C test/others/shell-job/ run
fi
make -C test/others/criu-ns/ run
make -C test/others/skip-file-rwx-check/ run
make -C test/others/rpc/ run

Expand Down
33 changes: 29 additions & 4 deletions scripts/criu-ns
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
import ctypes
import ctypes.util
import errno
Expand Down Expand Up @@ -71,7 +71,19 @@ def _wait_for_process_status(criu_pid):
try:
(pid, status) = os.wait()
if pid == criu_pid:
return os.waitstatus_to_exitcode(status)
# The following code block is based on
# os.waitstatus_to_exitcode() introduced in Python 3.9
# and we implement this for comparability with older
# versions of Python.
if os.WIFSIGNALED(status):
return os.WTERMSIG(status)
elif os.WIFEXITED(status):
return os.WEXITSTATUS(status)
elif os.WIFSTOPPED(status):
return os.WSTOPSIG(status)
else:
raise Exception("CRIU was terminated by an "
"unidentified reason")
except OSError:
return -251

Expand All @@ -81,8 +93,21 @@ def run_criu(args):
Spawn CRIU binary
"""
print(sys.argv)
os.execlp('criu', *['criu'] + args)
raise OSError(errno.ENOENT, "No such command")

if "--criu-binary" in args:
try:
opt_index = args.index("--criu-binary")
path = args[opt_index + 1]
del args[opt_index:opt_index + 2]
args.insert(0, "criu")
os.execv(path, args)
raise OSError(errno.ENOENT, "No such command")
except (ValueError, IndexError, FileNotFoundError):
raise OSError(errno.ENOENT, "--criu-binary missing argument")
else:
args.insert(0, "criu")
os.execvp("criu", args)
raise OSError(errno.ENOENT, "No such command")


# pidns_holder creates a process that is reparented to the init.
Expand Down
2 changes: 1 addition & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ all:
$(MAKE) zdtm-freezer
.PHONY: all

TESTS = unix-callback mem-snap rpc libcriu mounts/ext security pipes crit socketpairs overlayfs mnt-ext-dev shell-job skip-file-rwx-check
TESTS = unix-callback mem-snap rpc libcriu mounts/ext security pipes crit socketpairs overlayfs mnt-ext-dev shell-job criu-ns skip-file-rwx-check

other:
for t in $(TESTS); do \
Expand Down
3 changes: 3 additions & 0 deletions test/others/criu-ns/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
run:
@make -C ../.. zdtm_ct
../../zdtm_ct run.py
258 changes: 258 additions & 0 deletions test/others/criu-ns/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
#!/usr/bin/env python

import fcntl
import os
import pathlib
import pty
import shutil
import subprocess
import sys
import termios
import time


CRIU_BIN = "../../../criu/criu"
CRIU_NS = "../../../scripts/criu-ns"
IMG_DIR = "dumpdir"
DUMP_LOG = "dump.log"
RESTORE_LOG = "restore.log"
PIDFILE = "pidfile"


def check_dumpdir(path=IMG_DIR):
if os.path.isdir(path):
shutil.rmtree(path)
os.mkdir(path, 0o755)


def set_blocking(fd, blocking):
"""Implement os.set_blocking() for compatibility with Python
versions earlier than 3.5"""
flags = fcntl.fcntl(fd, fcntl.F_GETFL)

if blocking:
flags &= ~os.O_NONBLOCK
else:
flags |= os.O_NONBLOCK

fcntl.fcntl(fd, fcntl.F_SETFL, flags)


def run_task_with_own_pty(task):
fd_m, fd_s = pty.openpty()

pid = os.fork()
if pid == 0:
os.close(fd_m)
os.setsid()
os.dup2(fd_s, 0)
os.dup2(fd_s, 1)
os.dup2(fd_s, 2)
fcntl.ioctl(fd_s, termios.TIOCSCTTY, 1)
os.close(fd_s)
task()
exit(0)

os.close(fd_s)
fd_m = os.fdopen(fd_m, "rb")
set_blocking(fd_m.fileno(), False)

while True:
try:
data = fd_m.read()
except IOError:
break
if data is not None:
print(data.decode("utf-8"))

_, status = os.waitpid(pid, 0)

try:
data = fd_m.read()
except IOError as err:
print(err)

if data is not None:
print(data.decode("utf-8"))
fd_m.close()

if status != 0:
print("task %s exited badly: %d" % (task.__name__, status))
exit(1)

return 0


def create_pty():
fd_m, fd_s = pty.openpty()
return (os.fdopen(fd_m, "wb"), os.fdopen(fd_s, "wb"))


def create_isolated_dumpee():
pathlib.Path("running").touch()
fd_m, fd_s = create_pty()
pid = os.fork()
if pid == 0:
os.setsid()
os.dup2(fd_s.fileno(), 0)
os.dup2(fd_s.fileno(), 1)
os.dup2(fd_s.fileno(), 2)
fcntl.ioctl(fd_s.fileno(), termios.TIOCSCTTY, 1)
while True:
if not os.access("running", os.F_OK):
sys.exit(0)
time.sleep(1)
fd_m.close()
fd_s.close()
return pid


def criu_ns_dump(pid, shell_job=False):
cmd = [CRIU_NS, "dump", "-D", IMG_DIR, "-v4", "-t", str(pid),
"--log-file", DUMP_LOG, "--criu-binary", CRIU_BIN]
if shell_job:
cmd.append("--shell-job")
ret = subprocess.Popen(cmd).wait()
return ret


def criu_ns_restore(shell_job=False, restore_detached=False):
cmd = [CRIU_NS, "restore", "-D", IMG_DIR, "-v4", "--log-file",
RESTORE_LOG, "--criu-binary", CRIU_BIN]
if shell_job:
cmd.append("--shell-job")
if restore_detached:
cmd += ["--restore-detached", "--pidfile", PIDFILE]
ret = subprocess.Popen(cmd).wait()
return ret


def read_log_file(filename):
logfile_path = os.path.join(IMG_DIR, filename)
with open(logfile_path) as logfile:
print(logfile.read())


def test_dump_and_restore_with_shell_job():
print("Test criu-ns dump and restore with --shell-job option")
check_dumpdir()
pathlib.Path("running").touch()
pid = os.fork()
if pid == 0:
while True:
if not os.access("running", os.F_OK):
sys.exit(0)
time.sleep(1)

ret = criu_ns_dump(pid, shell_job=True)
if ret != 0:
read_log_file(DUMP_LOG)
sys.exit(ret)

os.unlink("running")
fd_m, fd_s = create_pty()
pid = os.fork()
if pid == 0:
os.setsid()
fd_m.close()
# since criu-ns takes control of the tty stdin
os.dup2(fd_s.fileno(), 0)
ret = criu_ns_restore(shell_job=True)
if ret != 0:
read_log_file(RESTORE_LOG)
sys.exit(ret)
os._exit(0)

fd_s.close()
os.waitpid(pid, 0)


def test_dump_and_restore_without_shell_job(restore_detached=False):
print("Test criu-ns dump and restore with an isolated process"
"(%d)" % restore_detached)
check_dumpdir()
pid = create_isolated_dumpee()
ret = criu_ns_dump(pid)
if ret != 0:
read_log_file(DUMP_LOG)
sys.exit(ret)

if not restore_detached:
os.unlink("running")

pid = os.fork()
if pid == 0:
os.setsid()
ret = criu_ns_restore(restore_detached=restore_detached)
if ret != 0:
read_log_file(RESTORE_LOG)
sys.exit(ret)
os._exit(0)

os.waitpid(pid, 0)


def test_dump_and_restore_in_pidns():
if os.system("grep NSpid /proc/self/status"):
return

print("Test criu-ns dump and restore in namespaces")

def _dump():
pid = create_isolated_dumpee()
ret = criu_ns_dump(pid)
if ret != 0:
read_log_file(DUMP_LOG)
sys.exit(ret)

def _restore():
ret = criu_ns_restore(restore_detached=True)
if ret != 0:
read_log_file(RESTORE_LOG)
sys.exit(ret)

def _get_restored_pid():
restored_pid = 0
pidfile_path = os.path.join(IMG_DIR, PIDFILE)
if not os.path.exists(pidfile_path):
raise FileNotFoundError("pidfile not found")
with open(pidfile_path, "r") as pidfile:
restored_pid = pidfile.read().strip()
return int(restored_pid)

def _redump():
global IMG_DIR
try:
restored_pid = _get_restored_pid()
except FileNotFoundError:
sys.exit(1)
IMG_DIR = "dumpdir2"
check_dumpdir(IMG_DIR)
ret = criu_ns_dump(restored_pid)
if ret != 0:
read_log_file(DUMP_LOG)
sys.exit(ret)

def _re_restore():
os.unlink("running")
ret = criu_ns_restore()
if ret != 0:
read_log_file(RESTORE_LOG)
sys.exit(ret)

check_dumpdir()
_dump()
_restore()
_redump()
_re_restore()


def main():
test_dump_and_restore_with_shell_job()
test_dump_and_restore_without_shell_job()
test_dump_and_restore_without_shell_job(restore_detached=True)
test_dump_and_restore_in_pidns()


if __name__ == "__main__":
run_task_with_own_pty(main)

0 comments on commit eb41d8e

Please sign in to comment.