|
8 | 8 | Operational Summary |
9 | 9 | ---------------------- |
10 | 10 |
|
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`` |
18 | 22 | """ |
19 | 23 |
|
| 24 | + |
20 | 25 | import os |
21 | | -import re |
22 | 26 | import time |
23 | | -from autotest.client.shared import error |
24 | | -from autotest.client.shared import utils |
25 | 27 | from dockertest.subtest import SubSubtest, SubSubtestCaller |
26 | 28 | from dockertest.dockercmd import AsyncDockerCmd |
27 | 29 | from dockertest.dockercmd import DockerCmd |
28 | 30 | from dockertest.containers import DockerContainers |
29 | 31 | from dockertest.images import DockerImage |
30 | 32 | 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 |
32 | 36 | from dockertest.output import OutputGood |
| 37 | +from dockertest.output import OutputNotBad |
| 38 | +from dockertest.output import wait_for_output |
33 | 39 | from dockertest.config import get_as_list |
34 | 40 |
|
35 | 41 |
|
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 | | - |
84 | 42 | class run_exec(SubSubtestCaller): |
85 | | - config_section = 'docker_cli/run_exec' |
| 43 | + pass |
86 | 44 |
|
87 | 45 |
|
88 | 46 | class exec_base(SubSubtest): |
89 | 47 |
|
90 | | - fqin = None |
91 | | - containers = None |
92 | | - images = None |
93 | | - cont = None |
94 | | - img = None |
95 | | - |
96 | 48 | 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) |
122 | 69 |
|
123 | 70 | 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 |
126 | 80 | 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)) |
132 | 85 |
|
133 | 86 | def initialize(self): |
134 | 87 | 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 |
140 | 99 | self.init_subargs() |
141 | 100 | self.start_base_container() |
142 | 101 |
|
143 | 102 | def postprocess(self): |
144 | 103 | 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) |
156 | 110 |
|
157 | 111 | def cleanup(self): |
158 | 112 | 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 |
160 | 118 | 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]) |
183 | 124 |
|
184 | 125 |
|
185 | 126 | class exec_true(exec_base): |
186 | 127 |
|
187 | 128 | def run_once(self): |
188 | 129 | 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() |
192 | 134 |
|
| 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() |
193 | 140 |
|
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() |
196 | 156 |
|
197 | 157 |
|
198 | 158 | class exec_pid_count(exec_base): |
199 | 159 |
|
200 | 160 | 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() |
210 | 169 |
|
211 | 170 | 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