Skip to content

Commit 30f4b8b

Browse files
committed
run_exec: Rewrite test to be more reliable
Test was over-engineered, internally inconsistent, and not working. Easier to re-write in boring standard way that works than fix. Signed-off-by: Chris Evich <[email protected]>
1 parent 8dd9c65 commit 30f4b8b

File tree

2 files changed

+121
-172
lines changed

2 files changed

+121
-172
lines changed

config_defaults/subtests/docker_cli/run_exec.ini

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,13 @@ run_options_csv = --interactive
77
bash_cmd = /bin/sh
88
#: modifies the execning command in started container)
99
exec_options_csv = --interactive
10-
#: csv command prefix (docker exec ... $exec_bash_cmd $cmd)
11-
exec_bash_cmd = /bin/sh,-c
12-
#: Specifies the executed command inside the running container
13-
cmd =
1410
#: expected exit status
15-
exit_status =
16-
subsubtests = exec_true, exec_false, exec_pid_count
17-
18-
[docker_cli/run_exec/exec_true]
19-
cmd = /bin/true
2011
exit_status = 0
12+
subsubtests = exec_true, exec_false, exec_pid_count
2113

2214
[docker_cli/run_exec/exec_false]
23-
cmd = /bin/false
2415
exit_status = 1
2516

2617
[docker_cli/run_exec/exec_pid_count]
27-
exec_bash_cmd = /bin/sh
2818
#: Expected count of pid in container when command using exec is started.
29-
pid_count = 3
30-
exit_status = 0
19+
pid_count = 2

subtests/docker_cli/run_exec/run_exec.py

Lines changed: 119 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -8,212 +8,172 @@
88
Operational Summary
99
----------------------
1010
11-
#. Test execute ``/bin/true`` in running container returning zero
12-
#. Test execute ``/bin/false`` in running container returning non zero
13-
#. Test execute ``/bin/sh`` in running container and check count of pids
14-
#. Test detached execute ``/bin/sh`` in running container and check count
15-
of pids
16-
#. Smoke test try to start count of containers as fast as possible in running
17-
container.
11+
#. Execute a shell in a container and leave it idling
12+
#. In parallel, run a command in container with ``docker exec``
13+
#. Verify expected result from above step
14+
#. Cleanup shell container
15+
16+
Prerequisites
17+
---------------
18+
19+
* Test container contains ``/bin/true`` and ``/bin/false``
20+
* Test container contains ``find`` command
21+
* Test container's ``/proc`` filesystem is accessable by ``docker exec``
1822
"""
1923

24+
2025
import os
21-
import re
2226
import time
23-
from autotest.client.shared import error
24-
from autotest.client.shared import utils
2527
from dockertest.subtest import SubSubtest, SubSubtestCaller
2628
from dockertest.dockercmd import AsyncDockerCmd
2729
from dockertest.dockercmd import DockerCmd
2830
from dockertest.containers import DockerContainers
2931
from dockertest.images import DockerImage
3032
from dockertest.images import DockerImages
31-
from dockertest.xceptions import DockerTestNAError
33+
from dockertest.xceptions import DockerTestFail
34+
from dockertest.output import mustpass
35+
from dockertest.output import mustfail
3236
from dockertest.output import OutputGood
37+
from dockertest.output import OutputNotBad
38+
from dockertest.output import wait_for_output
3339
from dockertest.config import get_as_list
3440

3541

36-
class AsyncDockerCmdStdIn(AsyncDockerCmd):
37-
38-
r"""
39-
Setup a call docker subcommand as if by CLI w/ subtest config integration
40-
Execute docker subcommand with arguments and a timeout.
41-
42-
:param subtest: A subtest.Subtest (**NOT** a SubSubtest) subclass instance
43-
:param subcomd: A Subcommand or fully-formed option/argument string
44-
:param subargs: (optional) A list of strings containing additional
45-
args to subcommand
46-
:param timeout: Seconds to wait before terminating docker command
47-
None to use 'docker_timeout' config. option.
48-
:param stdin_r: File descriptor for reading input data for stdin.
49-
:param stdin: Write part of file descriptor for reading.
50-
:raises DockerTestError: on incorrect usage
51-
"""
52-
53-
verbose = True
54-
55-
stdin_r = None
56-
stdin = None
57-
58-
PIPE = -1 # file descriptor should never be negative value.
59-
60-
def __init__(self, subtest, subcmd, subargs=None, timeout=None,
61-
verbose=True, stdin_r=None, stdin=None):
62-
super(AsyncDockerCmdStdIn, self).__init__(subtest, subcmd, subargs,
63-
timeout, verbose)
64-
65-
self.stdin = stdin
66-
if stdin_r == self.PIPE:
67-
self.stdin_r, self.stdin = os.pipe()
68-
69-
super(AsyncDockerCmdStdIn, self).execute(self.stdin_r)
70-
71-
def execute(self, stdin=None):
72-
"""
73-
Unimplemented method.
74-
"""
75-
raise RuntimeError('Method is not implemented')
76-
77-
def close(self):
78-
if self.stdin_r is not None:
79-
os.close(self.stdin_r)
80-
if self.stdin is not None:
81-
os.close(self.stdin)
82-
83-
8442
class run_exec(SubSubtestCaller):
85-
config_section = 'docker_cli/run_exec'
43+
pass
8644

8745

8846
class exec_base(SubSubtest):
8947

90-
fqin = None
91-
containers = None
92-
images = None
93-
cont = None
94-
img = None
95-
9648
def init_subargs(self):
97-
self.fqin = DockerImage.full_name_from_defaults(self.config)
98-
99-
run_opts = get_as_list(self.config.get("run_options_csv"))
100-
self.sub_stuff['run_args'] = run_opts
101-
name = "--name=%s" % self.sub_stuff["container_name"]
102-
self.sub_stuff['run_args'].append(name)
103-
self.sub_stuff['run_args'].append(self.fqin)
104-
self.sub_stuff['run_args'] += self.config['bash_cmd'].split(',')
105-
106-
exec_opts = get_as_list(self.config.get("exec_options_csv"))
107-
self.sub_stuff['exec_args'] = exec_opts
108-
self.sub_stuff['exec_args'].append(self.sub_stuff["container_name"])
109-
self.sub_stuff['exec_args'] += self.config['exec_bash_cmd'].split(',')
110-
self.sub_stuff['exec_args'].append(self.config['cmd'])
111-
112-
def run_cmd(self, bash_stdin, cmd):
113-
self.logdebug("send command to container:\n%s" % (cmd))
114-
os.write(bash_stdin, cmd)
115-
116-
@staticmethod
117-
def wait_for_output(dkrcmd, pattern, timeout=120):
118-
got_pattern = lambda: pattern in dkrcmd.stdout
119-
return utils.wait_for(got_pattern,
120-
timeout,
121-
text='Waiting on container %s commend' % dkrcmd)
49+
run_args = self.sub_stuff['run_args']
50+
run_options_csv = self.config["run_options_csv"]
51+
if run_options_csv:
52+
run_args += get_as_list(run_options_csv)
53+
54+
run_args.append('--name')
55+
name = self.sub_stuff['cont'].get_unique_name()
56+
self.sub_stuff['run_name'] = name
57+
run_args.append(name)
58+
fqin = DockerImage.full_name_from_defaults(self.config)
59+
run_args.append(fqin)
60+
bash_cmd = self.config['bash_cmd']
61+
if bash_cmd:
62+
run_args += get_as_list(bash_cmd)
63+
64+
exec_args = self.sub_stuff['exec_args']
65+
exec_options_csv = self.config["exec_options_csv"]
66+
if exec_options_csv:
67+
exec_args += get_as_list(exec_options_csv)
68+
exec_args.append(name)
12269

12370
def start_base_container(self):
124-
dkrcmd = AsyncDockerCmdStdIn(self, 'run', self.sub_stuff['run_args'],
125-
stdin_r=AsyncDockerCmdStdIn.PIPE)
71+
reader, writer = os.pipe()
72+
# Exception could occur before os.close(reader) below
73+
self.sub_stuff['fds'].append(reader)
74+
self.sub_stuff['fds'].append(writer)
75+
self.sub_stuff['dkrcmd_stdin'] = writer
76+
dkrcmd = AsyncDockerCmd(self, 'run', self.sub_stuff['run_args'])
77+
dkrcmd.execute(reader)
78+
self.sub_stuff['containers'].append(self.sub_stuff['run_name'])
79+
os.close(reader) # not needed anymore
12680
self.sub_stuff['dkrcmd'] = dkrcmd
127-
self.run_cmd(dkrcmd.stdin, "echo \"Started\"\n")
128-
if self.wait_for_output(dkrcmd, "Started", 10) is None:
129-
raise DockerTestNAError("Unable to start base container:\n %s" %
130-
(dkrcmd))
131-
self.containers.append(self.sub_stuff["container_name"])
81+
os.write(writer, 'echo "Started"\n')
82+
if not wait_for_output(lambda: dkrcmd.stdout, "Started"):
83+
raise DockerTestFail("Unable to start base container:\n %s" %
84+
(dkrcmd))
13285

13386
def initialize(self):
13487
super(exec_base, self).initialize()
135-
self.containers = []
136-
self.images = []
137-
self.cont = DockerContainers(self)
138-
self.img = DockerImages(self)
139-
self.sub_stuff["container_name"] = self.cont.get_unique_name()
88+
self.sub_stuff['containers'] = []
89+
self.sub_stuff['images'] = []
90+
self.sub_stuff['cont'] = DockerContainers(self)
91+
self.sub_stuff['img'] = DockerImages(self)
92+
self.sub_stuff['fds'] = [] # every fd in here guarantee closed
93+
self.sub_stuff['dkrcmd_stdin'] = None # stdin fd for vvvvvvv
94+
self.sub_stuff['run_name'] = None # container name for vvvvvvv
95+
self.sub_stuff['run_args'] = [] # parameters for vvvvvv
96+
self.sub_stuff['dkrcmd'] = None # docker run ... container
97+
self.sub_stuff['exec_args'] = [] # parameters for vvvvvv
98+
self.sub_stuff['dkrcmd_exec'] = None # docker exec ... container
14099
self.init_subargs()
141100
self.start_base_container()
142101

143102
def postprocess(self):
144103
super(exec_base, self).postprocess() # Prints out basic info
145-
if 'dkrcmd_exec' in self.sub_stuff:
146-
# Fail test if bad command or other stdout/stderr problems detected
147-
dkrcmd = self.sub_stuff['dkrcmd_exec']
148-
OutputGood(dkrcmd.cmdresult)
149-
expected = self.config['exit_status']
150-
self.failif(dkrcmd.exit_status != expected,
151-
"Expected exit status: %s does not match command"
152-
"exit status: %s. Details: %s" %
153-
(expected, dkrcmd.exit_status, dkrcmd.cmdresult))
154-
155-
self.logdebug(dkrcmd.cmdresult)
104+
os.write(self.sub_stuff['dkrcmd_stdin'], 'exit\t')
105+
time.sleep(1)
106+
mustpass(DockerCmd(self, 'kill',
107+
[self.sub_stuff['run_name']]).execute())
108+
# It may have been killed, but exec is what we care about
109+
OutputNotBad(self.sub_stuff['dkrcmd'].cmdresult)
156110

157111
def cleanup(self):
158112
super(exec_base, self).cleanup()
159-
# Auto-converts "yes/no" to a boolean
113+
for ifd in self.sub_stuff['fds']:
114+
try:
115+
os.close(ifd)
116+
except OSError:
117+
pass
160118
if self.config['remove_after_test']:
161-
if "dkrcmd" in self.sub_stuff:
162-
self.sub_stuff['dkrcmd'].close()
163-
164-
for cont in self.containers:
165-
dkrcmd = DockerCmd(self, "rm", ['--volumes', '--force', cont])
166-
cmdresult = dkrcmd.execute()
167-
msg = (" removed test container: %s" % cont)
168-
if cmdresult.exit_status == 0:
169-
self.logdebug("Successfully" + msg)
170-
else:
171-
self.logwarning("Failed" + msg)
172-
for image in self.images:
173-
try:
174-
di = DockerImages(self.parent_subtest)
175-
self.logdebug("Removing image %s", image)
176-
di.remove_image_by_full_name(image)
177-
self.logdebug("Successfully removed test image: %s",
178-
image)
179-
except error.CmdError, e:
180-
error_text = "tagged in multiple repositories"
181-
if error_text not in e.result_obj.stderr:
182-
raise
119+
for cntnr in self.sub_stuff['containers']:
120+
DockerCmd(self, 'kill', [cntnr]).execute()
121+
DockerCmd(self, 'rm', [cntnr]).execute()
122+
for image in self.sub_stuff['images']:
123+
DockerCmd(self, 'rmi', [image])
183124

184125

185126
class exec_true(exec_base):
186127

187128
def run_once(self):
188129
super(exec_true, self).run_once()
189-
dkrcmd = AsyncDockerCmdStdIn(self, 'exec', self.sub_stuff['exec_args'])
190-
self.sub_stuff['dkrcmd_exec'] = dkrcmd
191-
dkrcmd.wait(120)
130+
subargs = self.sub_stuff['exec_args'] + ['/bin/true']
131+
dkrcmd_exec = DockerCmd(self, 'exec', subargs, timeout=60)
132+
self.sub_stuff['dkrcmd_exec'] = dkrcmd_exec
133+
dkrcmd_exec.execute()
192134

135+
def postprocess(self):
136+
dkrcmd_exec = self.sub_stuff['dkrcmd_exec']
137+
mustpass(dkrcmd_exec.cmdresult)
138+
OutputGood(dkrcmd_exec.cmdresult)
139+
super(exec_true, self).postprocess()
193140

194-
class exec_false(exec_true):
195-
pass # Only change is in configuration
141+
142+
class exec_false(exec_base):
143+
144+
def run_once(self):
145+
super(exec_false, self).run_once()
146+
subargs = self.sub_stuff['exec_args'] + ['/bin/false']
147+
dkrcmd_exec = DockerCmd(self, 'exec', subargs, timeout=60)
148+
self.sub_stuff['dkrcmd_exec'] = dkrcmd_exec
149+
dkrcmd_exec.execute()
150+
151+
def postprocess(self):
152+
dkrcmd_exec = self.sub_stuff['dkrcmd_exec']
153+
mustfail(dkrcmd_exec.cmdresult)
154+
OutputNotBad(dkrcmd_exec.cmdresult)
155+
super(exec_false, self).postprocess()
196156

197157

198158
class exec_pid_count(exec_base):
199159

200160
def run_once(self):
201-
super(exec_pid_count, self).run_once() # Prints out basic info
202-
dkrcmd = AsyncDockerCmdStdIn(self, 'exec', self.sub_stuff['exec_args'],
203-
stdin_r=AsyncDockerCmdStdIn.PIPE)
204-
self.sub_stuff['dkrcmd_exec'] = dkrcmd
205-
self.run_cmd(dkrcmd.stdin, "ls -l /proc\n")
206-
self.wait_for_output(dkrcmd, "cmdline", 10)
207-
self.run_cmd(dkrcmd.stdin, "exit\n")
208-
dkrcmd.close()
209-
dkrcmd.wait(120)
161+
super(exec_pid_count, self).run_once()
162+
subargs = self.sub_stuff['exec_args']
163+
subargs.append("find /proc -maxdepth 1 -a -type d -a "
164+
"-regextype posix-extended -a "
165+
r"-regex '/proc/[0-9]+'")
166+
dkrcmd_exec = DockerCmd(self, 'exec', subargs)
167+
self.sub_stuff['dkrcmd_exec'] = dkrcmd_exec
168+
dkrcmd_exec.execute()
210169

211170
def postprocess(self):
212-
super(exec_pid_count, self).postprocess() # Prints out basic info
213-
if 'dkrcmd_exec' in self.sub_stuff:
214-
# Check count of pids
215-
dkrcmd = self.sub_stuff['dkrcmd_exec']
216-
pids = re.findall("^dr.+ ([0-9]+).?$", dkrcmd.stdout, re.MULTILINE)
217-
self.failif(len(pids) != self.config.get("pid_count", 3),
218-
"There should be exactly 3 pids in"
219-
" container. Count of pid: %s" % len(pids))
171+
dkrcmd_exec = self.sub_stuff['dkrcmd_exec']
172+
mustpass(dkrcmd_exec.cmdresult)
173+
OutputGood(dkrcmd_exec.cmdresult)
174+
pids = dkrcmd_exec.stdout.strip().splitlines()
175+
expected = self.config["pid_count"]
176+
self.failif(len(pids) != expected,
177+
"Expecting %d pids: %s"
178+
% (expected, pids))
179+
super(exec_pid_count, self).postprocess()

0 commit comments

Comments
 (0)