diff --git a/config_defaults/subtests/docker_cli/commit.ini b/config_defaults/subtests/docker_cli/commit.ini index 4d12a5943..54a40a304 100644 --- a/config_defaults/subtests/docker_cli/commit.ini +++ b/config_defaults/subtests/docker_cli/commit.ini @@ -14,8 +14,6 @@ docker_expected_result = PASS commit_author = Author_name #: Commit message commit_message = Message -#: Run params -commit_run_params = '{"Cmd": ["ls", "/etc"], "PortSpecs": ["22"]}' #: Changed files commit_changed_files = /var/i @@ -24,8 +22,6 @@ commit_changed_files = /var/i commit_author = Author_name #: Commit message commit_message = Message -#: Run params -commit_run_params = '{"Cmd": ["ls", "/etc"], "PortSpecs": ["22"]}' #: Changed files commit_changed_files = /var/i #: Expected output after executing the image's default command diff --git a/config_defaults/subtests/docker_cli/create.ini b/config_defaults/subtests/docker_cli/create.ini index ca6ded9a5..ac115373a 100644 --- a/config_defaults/subtests/docker_cli/create.ini +++ b/config_defaults/subtests/docker_cli/create.ini @@ -33,7 +33,8 @@ cmd = '/bin/command/does/not/exist' [docker_cli/create/create_remote_tag] #: Name of a image on a remote registry appropriate to environment -__example__ = remote_image_fqin +__example__ = remote_image_fqin, docker_timeout +docker_timeout = 60 #: Fully qualified image name of **remote** repository to test automatic pull. remote_image_fqin = stackbrew/centos:7 run_options_csv = diff --git a/config_defaults/subtests/docker_cli/dockerhelp.ini b/config_defaults/subtests/docker_cli/dockerhelp.ini index 043529070..7c9778cd1 100644 --- a/config_defaults/subtests/docker_cli/dockerhelp.ini +++ b/config_defaults/subtests/docker_cli/dockerhelp.ini @@ -12,38 +12,38 @@ subsubtests = help_simple #: section default. generate_subsubtest_list = yes #: Space separated list of all the docker commands to check -help_commands: attach - build - commit - cp - diff - events - export - history - images - import - info - inspect - kill - load - login - logs - port - ps - pull - push - restart - rm - rmi - run - save - search - start - stop - tag - top - version - wait +help_commands = attach + build + commit + cp + diff + events + export + history + images + import + info + inspect + kill + load + login + logs + port + ps + pull + push + restart + rm + rmi + run + save + search + start + stop + tag + top + version + wait [docker_cli/dockerhelp/help_simple] #: CSV list of docker options where a zero-exit code is expected (though a diff --git a/config_defaults/subtests/docker_cli/iptable.ini b/config_defaults/subtests/docker_cli/iptable.ini index 0952455e9..c1b576cdb 100644 --- a/config_defaults/subtests/docker_cli/iptable.ini +++ b/config_defaults/subtests/docker_cli/iptable.ini @@ -1,6 +1,6 @@ [docker_cli/iptable] subsubtests = iptable_remove - -[docker_cli/iptable/iptable_remove] -#: container's shell -bash_cmd = /bin/bash +#: Arguments to pass to run command in addition to -d and --name +run_args_csv = --expose,1234,--publish,1234:1234 +#: container's shell command +bash_cmd = /bin/bash -c 'sleep 3s' diff --git a/config_defaults/subtests/docker_cli/run.ini b/config_defaults/subtests/docker_cli/run.ini index 5c0c3c761..db083e392 100644 --- a/config_defaults/subtests/docker_cli/run.ini +++ b/config_defaults/subtests/docker_cli/run.ini @@ -1,5 +1,5 @@ [docker_cli/run] -subsubtests = run_true,run_false,run_interactive,run_attach_stdout,run_remote_tag,run_names,run_passwd +subsubtests = run_true,run_false,run_interactive,run_attach_stdout,run_remote_tag,run_names,run_passwd,run_ping #: The most basic sub-subtests can be generated from a generic #: class at runtime, set 'yes' to enable this feature. generate_generic = no @@ -33,6 +33,14 @@ expected_status = "Password set" cmd = 'passwd --status root | grep -qv %(expected_status)s' exit_status = 0 +[docker_cli/run/run_ping] +generate_generic = yes +__example__ = ping_url +#: Host to ping +ping_url = www.google.com +cmd = 'ping -c 10 -q %(ping_url)s' +exit_status = 0 + [docker_cli/run/run_names] cmd = sleep 2s # Test will find container by CID instead of name, CID must appear @@ -64,7 +72,8 @@ exit_status = 0 [docker_cli/run/run_remote_tag] #: Change this to an image remotely available within test environment -__example__ = remote_image_fqin +__example__ = remote_image_fqin, docker_timeout +docker_timeout = 60 #: Fully qualified image name stored on a remote registry, not local. remote_image_fqin = stackbrew/centos:7 run_options_csv = diff --git a/config_defaults/subtests/docker_cli/start.ini b/config_defaults/subtests/docker_cli/start.ini index 230a52e47..f855d20e8 100644 --- a/config_defaults/subtests/docker_cli/start.ini +++ b/config_defaults/subtests/docker_cli/start.ini @@ -13,6 +13,8 @@ run_cmd = [docker_cli/start/simple] docker_timeout = 60 +#: Expected string to see when starting non-running container +missing_msg = o such #: modifies the running container options run_options_csv = --interactive=true docker_attach = no diff --git a/config_defaults/subtests/docker_cli/wait.ini b/config_defaults/subtests/docker_cli/wait.ini index 6be9d3ea2..bdc1f615b 100644 --- a/config_defaults/subtests/docker_cli/wait.ini +++ b/config_defaults/subtests/docker_cli/wait.ini @@ -1,37 +1,63 @@ [docker_cli/wait] -subsubtests = no_wait,wait_first,wait_last,wait_missing -docker_timeout = 60 -#: modifies the running container options -run_options_csv = --detach,--interactive -#: list of used containers (use _$name to override config variables) -containers = cont0 cont1 cont2 -#: executed command on container called ``_cont0`` -exec_cmd_cont0 = sleep 10; exit 1 -#: executed command on container called ``_cont1`` -exec_cmd_cont1 = exit 2 -#: executed command on container called ``_cont2`` -exec_cmd_cont2 = exit 3 -#: Identify containers by True=name,False=id,RANDOM=random -use_names = RANDOM -#: random_seed - can be set to override the initial random seed used in test -random_seed = -#: which containers we should wait for. Either use index of the -#: the container or '_' + string. The leading char will be removed! -wait_for = -#: Error regular expression which should match on missing container name ``%%s`` -missing_stderr = Error response from daemon: wait: no such \w+: %%s -#: When 'yes', invert regex failure meaning for test result -invert_missing = no - -[docker_cli/wait/no_wait] -wait_for = 1 2 - -[docker_cli/wait/wait_first] -wait_for = 0 1 2 - -[docker_cli/wait/wait_last] -wait_for = 2 1 0 - -[docker_cli/wait/wait_missing] -invert_missing = yes -wait_for = _i_hope_this_container_does_not_exist 0 1 2 _this_one_is_also_missing +subsubtests = Simple, Multi, Sig + +#: CSV list of strings describing how containers are setup, "CREATE", "RUN" +#: or "NONE". Each value represents a separate container and all ``target_*`` +#: lists must contain exactly the same number of items. +target_setups = RUN + +#: CSV list of operations to perform on each container during the test +#: wait command: +#: {n} +#: {t} * ``STOP`` - Use ``docker stop`` on the target container +#: {t} * ``KILL`` - Use ``docker kill`` on the target container +#: {t} * ```` - Send signal number ```` to target container +#: {t} * ``REMV`` - Use ``docker rm --force`` on the target container +#: {t} * ``NONE`` - Do nothing +#: {n} +target_waits = NONE + +#: CSV list of sleep times for each target, must have same number of items +#: as ``target_setups`` and ``target_waits``. Do not rely on timing for +#: precise synchronization! +target_sleeps = 0.5 + +#: Expected exit code from wait command +exit = 0 + +#: (optional) Regular expression that much match wait command stdout +stdout = ^0\n$ + +#: (optional) Regular expression that much match wait command stderr +stderr = + +#: Timeout for the target docker command in seconds +target_timeout = 10 + +#: Timeout for the wait command itself +docker_timeout = 20 + +#: Quoted command line to pass to each target container +target_cmd = bash,-c,'trap "echo \"Received Signal 30\"" 30; sleep @SLEEP@s' + +#: CSV run command line options minus --name & image for each target container +target_run = --detach + +#: Run target containers with more verbosity under all conditions +target_verbose = no + +#: Run wait command with more verbosity +wait_verbose = no + +[docker_cli/wait/Multi] +docker_timeout = 20 +target_setups = RUN, RUN, RUN +target_waits = NONE, KILL, NONE +target_sleeps = 1, 5, 3 +stdout = ^0\n137\n0$ + +[docker_cli/wait/Sig] +docker_timeout = 20 +target_setups = RUN +target_waits = 30 +target_sleeps = 0.5 diff --git a/dockertest/containers.py b/dockertest/containers.py index 76a662cb2..5ed63e3df 100644 --- a/dockertest/containers.py +++ b/dockertest/containers.py @@ -564,8 +564,7 @@ def clean_all(self, containers): else: preserve_cnames = [] preserve_cnames = set(preserve_cnames) - # TODO: Set non-verbose once code stabalized - self.verbose = True + self.verbose = False try: for name in containers: name = name.strip() diff --git a/dockertest/dockercmd.py b/dockertest/dockercmd.py index e806754b8..d38d8224b 100644 --- a/dockertest/dockercmd.py +++ b/dockertest/dockercmd.py @@ -276,12 +276,13 @@ def execute(self, stdin=None): str_stdin = " <<< %s" % stdin else: # Nothing str_stdin = "" - self.subtest.logdebug("Execute %s%s", self.command, str_stdin) + else: + str_stdin = "" + if self.verbose: + self.subtest.logdebug("Executing %s%s", str(self), str_stdin) self.cmdresult = utils.run(self.command, timeout=self.timeout, stdin=stdin, verbose=False, ignore_status=True) - if self.verbose: - self.subtest.logdebug(str(self)) # Return value, not reference return self.cmdresult @@ -319,6 +320,9 @@ def execute(self, stdin=None): str_stdin = " <<< %s" % stdin else: str_stdin = "" + else: + str_stdin = "" + if self.verbose: self.subtest.logdebug("Async-execute: %s%s", str(self), str_stdin) self._async_job = utils.AsyncJob(self.command, verbose=False, stdin=stdin, close_fds=True) diff --git a/dockertest/images.py b/dockertest/images.py index eceac47f6..d8ab350a1 100644 --- a/dockertest/images.py +++ b/dockertest/images.py @@ -380,6 +380,12 @@ def docker_cmd_check(self, cmd, timeout=None): OutputGood(result) return result + def full_name_from_defaults(self): + """ + Return ``DockerImage.full_name_from_defaults(self.subtest.config)`` + """ + return DockerImage.full_name_from_defaults(self.subtest.config) + def get_unique_name(self, prefix="", suffix="", length=4): """ Get unique name for a new image @@ -600,7 +606,7 @@ def clean_all(self, fqins): preserve_fqins.append( DockerImage.full_name_from_defaults(self.subtest.config)) preserve_fqins = set(preserve_fqins) - self.verbose = True + self.verbose = False try: for name in fqins: if name in preserve_fqins: diff --git a/dockertest/output.py b/dockertest/output.py index d07af6e66..4f5a94aec 100644 --- a/dockertest/output.py +++ b/dockertest/output.py @@ -599,7 +599,7 @@ class DockerTime(datetime.datetime): # pylint: disable=R0903 For example, the FinishedAt time of a still-running container :param isostr: ISO 8601 format string :param sep: Optional separation character ('T' by default) - :raise: DockerValueError if isostr is unparseable + :raise: ValueError if isostr is unparseable """ class UTC(datetime.tzinfo): @@ -641,39 +641,103 @@ def dst(cls, dt): del dt # not needed return cls.ZERO + class UTCOffset(datetime.tzinfo): + """Fixed offset in hours and minutes from UTC.""" + + def __init__(self, offset_string): + super(DockerTime.UTCOffset, self).__init__() + numbers = offset_string.split(':') + hours = int(numbers[0]) + minutes = int(numbers[1]) + self.__offset = datetime.timedelta(hours=hours, minutes=minutes) + self.__name = "UTC%s" % offset_string + + def utcoffset(self, dt): + del dt # not used, but specified in base + return self.__offset + + def tzname(self, dt): + del dt # not used, but specified in base + return self.__name + + def dst(self, dt): + del dt # not used, but specified in base + return DockerTime.UTC.ZERO + def __new__(cls, isostr, sep=None): if sep is None: sep = 'T' # datetime can output zulu time but not consume it. - base = "%s%s%s" % (r"(\d{4})-(\d{2})-(\d{2})", + base = "%s%s%s" % (r"(\s*\d{4})-(\d{2})-(\d{2})", sep, r"(\d{2}):(\d{2}):(\d{2})") - regex = re.compile(base + "Z") - keys = ('year', 'month', 'day', - 'hour', 'minute', 'second') - mobj = regex.search(isostr) - if bool(mobj): - values = list(mobj.groups()) - else: # Try with interpreted microseconds - regex = re.compile(base + r"\.(\d+)") - keys = keys + ('microsecond',) - mobj = regex.search(isostr) - if bool(mobj): - values = list(mobj.groups()) - # Convert seconds decimal fraction into microseconds - sec_frac = float("0.%s" % values[-1]) - values[-1] = int(sec_frac * 1000000) - else: - raise xceptions.DockerValueError("Malformed zulu string %s" - % isostr) - # Regex groups are all strings, convert to integers - values = [int(value) for value in values] - dargs = dict(zip(keys, tuple(values))) - dargs['tzinfo'] = cls.UTC() + keys = ['year', 'month', 'day', + 'hour', 'minute', 'second'] + values = [] + # Order is significant, some parsers depend on one-another + parsers = [cls.__new_tzoffset__, cls.__new_zulu__, cls.__new_us__] + for parser in parsers: + if parser(isostr, base, values, keys, cls.UTC()): + break # Parsers return True on success + if values == []: # No parser was succesful + raise ValueError("Malformed date time string %s" % isostr) + # Convert any strings into integers + for index, value in enumerate(values): + if isinstance(value, basestring): + values[index] = int(value) + dargs = dict(zip(tuple(keys), tuple(values))) return super(DockerTime, cls).__new__(cls, **dargs) + @classmethod + def __new_zulu__(cls, isostr, base, values, keys, tzn): + # Killall letter Z and z's + isostr.replace('z', ' ') + isostr.replace('Z', ' ') + # may or may not have fractional seconds + has_us = cls.__new_us__(isostr, base, values, keys, tzn) + if has_us: + return True + else: + regex = re.compile(base) + mobj = regex.search(isostr) + if mobj: + values += list(mobj.groups()) + keys.append('tzinfo') + values.append(tzn) + return True + return False + + @classmethod + def __new_us__(cls, isostr, base, values, keys, tzn): + # Try with interpreted microseconds + regex = re.compile(base + r"\.(\d+)") + mobj = regex.search(isostr) + if mobj: + values += list(mobj.groups()) + # Convert seconds decimal fraction into microseconds + sec_frac = float("0.%s" % values[-1]) + values[-1] = int(sec_frac * 1000000) + keys.append('microsecond') + values.append(tzn) + keys.append('tzinfo') + return True + return False + + @classmethod + def __new_tzoffset__(cls, isostr, base, values, keys, tzn): + # Check if ends with +/-00:00 timezone offset, optional + # non-capturing seconds-fraction parsed by __new_us__() + regex = re.compile(base + r"(?:\.(\d+))?([+-]{1}\d{2}:\d{2})") + mobj = regex.search(isostr) + if mobj: + tzn = cls.UTCOffset(mobj.group(8)) + # Remove timezone from string, attempt parsing with __new_us__ + isostr = isostr[0:len(isostr) - len(mobj.group(8))] + return cls.__new_us__(isostr, base, values, keys, tzn) + return False + def is_undefined(self): """ Return True if this instance represents an undefined date & time """ - return self == self.UTC.EPOCH + return self - self.UTC.singleton.EPOCH == self.UTC.ZERO diff --git a/dockertest/output_unittests.py b/dockertest/output_unittests.py index 204411962..71bdaffcb 100755 --- a/dockertest/output_unittests.py +++ b/dockertest/output_unittests.py @@ -343,7 +343,7 @@ def test_zero_point_zero(self): import datetime epoch_str = "0001-01-01T00:00:00.0Z" epoch_dt = self.dockertime(epoch_str) - expected = epoch_dt.tzinfo.EPOCH + expected = self.dockertime.UTC.EPOCH self.assertEqual(epoch_dt, expected) @@ -386,7 +386,7 @@ def test_sometime_point_more(self): def test_is_undefined(self): dt = self.dockertime("0001-01-01T00:00:00Z") - self.assertTrue(dt.is_undefined) + self.assertTrue(dt.is_undefined()) def test_isoformat(self): # Have to normalize representation first for comparison @@ -396,5 +396,38 @@ def test_isoformat(self): test_isoformat = dt.isoformat() self.assertEqual(normalized_isoformat, test_isoformat) + def test_offset_point_some(self): + import datetime + isostr = "2015-03-02T17:04:20.569+12:34" + dt = self.dockertime(isostr) + tz = self.dockertime.UTCOffset("+12:34") + expected = datetime.datetime(year=2015, month=3, day=2, + hour=17, minute=4, second=20, + microsecond=569000, tzinfo=tz) + self.assertEqual(dt, expected) + + def test_in_junk(self): + import datetime + isostr = " 2015-03-02T17:04:20.569+12:34 ahhhh! 2015-03-02T 17:04:20" + dt = self.dockertime(isostr) + tz = self.dockertime.UTCOffset("+12:34") + expected = datetime.datetime(year=2015, month=3, day=2, + hour=17, minute=4, second=20, + microsecond=569000, tzinfo=tz) + self.assertEqual(dt, expected) + + def test_in_other_junk(self): + import datetime + isostr = " ahhhh!2015-03-02T17:04:20z2015-03-02 17:04:20" + dt = self.dockertime(isostr) + tz = self.dockertime.UTC() + expected = datetime.datetime(year=2015, month=3, day=2, + hour=17, minute=4, second=20, + tzinfo=tz) + self.assertEqual(dt, expected) + + def test_unparsable(self): + self.assertRaises(ValueError, self.dockertime, "2015-03-02 17:04:20z") + if __name__ == '__main__': unittest.main() diff --git a/dockertest/subtestbase.py b/dockertest/subtestbase.py index d6a8ffc50..dc1637a22 100644 --- a/dockertest/subtestbase.py +++ b/dockertest/subtestbase.py @@ -58,7 +58,7 @@ def initialize(self): # Issue warnings for failed to customize suggested options not_customized = self.config.get('__example__', None) if not_customized is not None and not_customized is not '': - self.logwarning("WARNING: Recommended options not customized:") + self.logdebug("WARNING: Recommended options not customized:") for nco in get_as_list(not_customized): self.logwarning("WARNING: %s" % nco) self.logwarning("WARNING: Test results may be externaly " diff --git a/subtests/docker_cli/commit/commit.py b/subtests/docker_cli/commit/commit.py index 91ec9693b..3456962bc 100644 --- a/subtests/docker_cli/commit/commit.py +++ b/subtests/docker_cli/commit/commit.py @@ -8,11 +8,11 @@ -------------------- #. Make new image name. -#. Make changes in image by docker run [dockerand_data_prepare_cmd]. +#. Make changes in image by docker run. #. commit changes. #. check if committed image exists. #. check if values in changed files for image are correct. -#. remote committed image from local repo. +#. remove committed image from local repo. """ import time @@ -65,7 +65,6 @@ def initialize(self): def complete_docker_command_line(self): c_author = self.config["commit_author"] c_msg = self.config["commit_message"] - run_params = self.config["commit_run_params"] repo_addr = self.sub_stuff["new_image_name"] cmd = [] @@ -73,8 +72,6 @@ def complete_docker_command_line(self): cmd.append("-a %s" % c_author) if c_msg: cmd.append("-m %s" % c_msg) - if run_params: - cmd.append("--run=%s" % run_params) cmd.append(self.sub_stuff["container"]) cmd.append(repo_addr) diff --git a/subtests/docker_cli/events/events.py b/subtests/docker_cli/events/events.py index 1d3e22bc6..1703eb8d6 100644 --- a/subtests/docker_cli/events/events.py +++ b/subtests/docker_cli/events/events.py @@ -24,8 +24,8 @@ import re from string import Template import time -import datetime from dockertest.subtest import Subtest +from dockertest.output import DockerTime from dockertest.containers import DockerContainers from dockertest.images import DockerImage from dockertest.dockercmd import DockerCmd @@ -33,28 +33,17 @@ from dockertest.dockercmd import AsyncDockerCmd from dockertest.xceptions import DockerValueError + cid_regex = re.compile(r'\s+([a-z0-9]{64})\:\s+') -dt_regex = re.compile(r'(\d{4})-(\d{2})-(\d{2})\S+' # date part - r'(\d{2}):(\d{2}):(\d{2})\S+' # time part - r'([+-]\d{2}):(\d{2})\s+') # UTC offset part +fqin_regex = DockerImage.repo_split_p source_regex = re.compile(r'\s+\(from\s+\S+\)\s+') operation_regex = re.compile(r'\s+(\w+)$') # final word chars def event_dt(line): try: - (year, month, day, - hour, minute, second, - o_hour, o_minutes) = dt_regex.search(line).groups() - - # utc offset - utc_offset = datetime.timedelta(hours=int(o_hour), - minutes=int(o_minutes)) - dt = datetime.datetime(int(year), int(month), int(day), - int(hour), int(minute), int(second)) - dt += utc_offset - return dt - except (AttributeError, TypeError, ValueError): # regex.search() failed + return DockerTime(line) + except (AttributeError, TypeError, ValueError): # failed return None @@ -66,6 +55,14 @@ def event_cid(line): return None +def event_fqin(line): + mobj = fqin_regex.search(line) + if mobj is not None: + return mobj.group(0) + else: + return None + + def event_source(line): mobj = source_regex.search(line) if mobj is not None: @@ -103,17 +100,19 @@ def is_dupe_event(needle, haystack): def parse_event(line): """ - Return tuple(CID, {DETAILS}) from parsing line + Return tuple(CID/FQIN, {DETAILS}) from parsing line :param line: String-like containing a single event line - :returns: tuple(CID, {DETAILS}) from parsing line or None if unparseable + :returns: (CID/FQIN, {DETAILS}) from parsing line or None if unparseable """ - cid = event_cid(line) + identifier = event_cid(line) + if identifier is None: + identifier = event_fqin(line) details = event_details(line) - if cid is None or details['datetime'] is None: + if identifier is None or details['datetime'] is None: return None # unparseable line else: - return (cid, details) + return (identifier, details) def parse_events(lines, slop=None): @@ -148,22 +147,22 @@ def parse_events(lines, slop=None): return result -def events_by_cid(events_list, previous=None): +def events_by_id(events_list, previous=None): """ - Return a dictionary, mapping of cid to de-duplicated event-details list + Return a dictionary, mapping of CID or FQIN to de-duplicated details list - :param events_list: List of tuple(CID, {DETAILS}) from parse_events() - :param previous: Possibly overlapping prior result from events_by_cid() - :returns: dict-like mapping cid to de-duplicated event-details list + :param events_list: List of tuple(CID/FQIN, {DETAILS}) from parse_events() + :param previous: Possibly overlapping prior result from events_by_id() + :returns: dict-like mapping CID/FQIN to de-duplicated event-details list """ if previous is None: dct = {} else: dct = previous # in-place update - for cid, details in events_list: - previous_events = dct.get(cid) + for _id, details in events_list: + previous_events = dct.get(_id) if previous_events is None: - previous_events = dct[cid] = [] # in-place update (below) + previous_events = dct[_id] = [] # in-place update (below) if not is_dupe_event(details, previous_events): # don't assume it belongs at end previous_events.append(details) @@ -237,7 +236,7 @@ def postprocess(self): # one-line (about) minimum self.failif(len(stdout) < 80, "Output too short: '%s'" % stdout) all_events = parse_events(stdout) - cid_events = events_by_cid(all_events) + cid_events = events_by_id(all_events) cid = self.stuff['nfdc_cid'] self.failif(cid not in cid_events, 'Test container cid %s does not appear in events' % cid) diff --git a/subtests/docker_cli/iptable/iptable.py b/subtests/docker_cli/iptable/iptable.py index 0039f010b..54ab927fc 100644 --- a/subtests/docker_cli/iptable/iptable.py +++ b/subtests/docker_cli/iptable/iptable.py @@ -38,19 +38,14 @@ class iptable(SubSubtestCallerSimultaneous): class iptable_base(SubSubtest): - def _init_stuff(self): - """ - Initialize stuff parameters - """ + def init_stuff(self): self.sub_stuff['subargs'] = None - self.sub_stuff['rule'] = [] - self.sub_stuff['result'] = True - self.sub_stuff['result_info'] = [] self.sub_stuff['name'] = None - self.sub_stuff['net_device_list'] = self.read_veth_device() - self.sub_stuff['net_device'] = None + self.sub_stuff['rules_before'] = [] + self.sub_stuff['rules_during'] = [] + self.sub_stuff['rules_after'] = [] - def _init_subargs(self): + def init_subargs(self): """ Initialize basic arguments that will use for start container Will return a list 'args' @@ -60,66 +55,92 @@ def _init_subargs(self): name = docker_containers.get_unique_name() self.sub_stuff['name'] = name bash_cmd = self.config['bash_cmd'] - args = ["--name=%s" % name, - image, - bash_cmd] - + args = ["--name=%s" % name] + args += get_as_list(self.config['run_args_csv']) + args += [image, bash_cmd] return args - @staticmethod - def read_veth_device(): + def cntnr_veth_map(self): """ - Temp method to get container net device name + Return mapping of container names to veth* devices """ + # map ifindex's to ``veth*`` names on host tmp_cmd = 'brctl show' - cmd_result = utils.run(tmp_cmd) - vnet = re.findall(r'veth\w+', cmd_result.stdout) - - return vnet + cmd_result = utils.run(tmp_cmd, verbose=False) + veths = re.findall(r'veth\w+', cmd_result.stdout) + ifindex = [int(open('/sys/class/net/%s/ifindex' % veth, 'r').read()) + for veth in veths] + index_host = dict(zip(ifindex, veths)) + self.logdebug("Host index to veth: %s", index_host) + + # map container eth0 ifindex's to names + dc = DockerContainers(self) + names = dc.list_container_names() + jsons = [dc.json_by_name(name)[0] for name in names] + njs = dict(zip(names, jsons)) + result = {} + for name in [_name for (_name, json) in njs.iteritems() + if json["NetworkSettings"]["MacAddress"] != ""]: + subargs = [name, 'cat', '/sys/class/net/eth0/ifindex'] + dkrcmd = DockerCmd(self, 'exec', subargs, verbose=False) + dkrcmd.execute() + if dkrcmd.exit_status == 0: + # Host's ifindex always one greater than container's + ifindex = int(dkrcmd.stdout) + 1 + # State could have changed during looping + if ifindex in index_host: + result[name] = index_host[ifindex] + else: + self.logdebug("Host veth %s dissapeared while mapping %s", + ifindex, name) + else: + self.logdebug("Can't examine eth0 for container %s", name) + self.logdebug("Container names to veth: %s", result) + return result @staticmethod - def read_iptable_rules(vnet): - """ - Find container related iptable rule - param vnet: The container's virtual net card, for now - can get it through 'brctl show' after container - started + def read_iptable_rules(veth): """ - tmp_cmd = 'iptables -L -n -v' - net_device = vnet - container_rule = [] - tmp_rules = utils.run(tmp_cmd) - tmp_rules_list = tmp_rules.stdout.splitlines() - - for rule in tmp_rules_list: - if net_device in rule: - line = rule - container_rule.append(line) + Find container related iptable rules - return container_rule + param veth: Container's virtual net card, None for all rules + """ + iptables_cmd = 'iptables -L -n -v' + iptables_rules = utils.run(iptables_cmd, verbose=False) + rules = iptables_rules.stdout.splitlines() + if veth is None: + return rules + return [rule for rule in rules if rule.find(veth) > -1] def initialize(self): super(iptable_base, self).initialize() - self._init_stuff() - self.sub_stuff['subargs'] = self._init_subargs() + self.init_stuff() + self.sub_stuff['rules_before'] = self.read_iptable_rules(None) + self.logdebug("Rules before:\n%s", + '\n'.join(self.sub_stuff['rules_before'])) + self.sub_stuff['subargs'] = self.init_subargs() def run_once(self): super(iptable_base, self).run_once() subargs = self.sub_stuff['subargs'] - mustpass(DockerCmd(self, 'run -d -t', subargs, verbose=True).execute()) + mustpass(DockerCmd(self, 'run -d', subargs, verbose=True).execute()) + self.sub_stuff['rules_during'] = self.read_iptable_rules(None) + self.logdebug("Rules during:\n%s", + '\n'.join(self.sub_stuff['rules_during'])) def postprocess(self): super(iptable_base, self).postprocess() + name = self.sub_stuff['name'] + DockerContainers(self).wait_by_name(name) + + self.sub_stuff['rules_after'] = self.read_iptable_rules(None) + self.logdebug("Rules after:\n%s", + '\n'.join(self.sub_stuff['rules_after'])) def cleanup(self): super(iptable_base, self).cleanup() if self.config['remove_after_test']: - preserve_cnames = get_as_list(self.config['preserve_cnames']) - if self.sub_stuff['name'] in preserve_cnames: - return - DockerCmd(self, 'rm', - ['--force', '--volumes', - self.sub_stuff['name']]).execute() + DockerContainers(self).clean_all([self.sub_stuff['name']]) class iptable_remove(iptable_base): @@ -128,32 +149,38 @@ class iptable_remove(iptable_base): Test if container iptable rules are removed after stopped """ + def init_stuff(self): + super(iptable_remove, self).init_stuff() + self.sub_stuff['cntnr_before'] = set() + self.sub_stuff['cntnr_during'] = set() + + def initialize(self): + super(iptable_remove, self).initialize() + name = self.sub_stuff['name'] + veth = self.cntnr_veth_map().get(name) + if veth is not None: + self.sub_stuff['cntnr_before'] = set(self.read_iptable_rules(veth)) + def run_once(self): super(iptable_remove, self).run_once() - before_net = set(self.sub_stuff['net_device_list']) - after_net = set(self.read_veth_device()) - net_device_list = list(after_net.difference(before_net)) - - self.failif(len(net_device_list) != 1, - "Can't obtain network device of the tested container," - "difference of list of net devices before/after is %s" - % net_device_list) - self.sub_stuff['net_device'] = net_device_list.pop() + name = self.sub_stuff['name'] + veth = self.cntnr_veth_map().get(name) + if veth is not None: + self.sub_stuff['cntnr_during'] = set(self.read_iptable_rules(veth)) def postprocess(self): - net_device = self.sub_stuff['net_device'] - container_rules = lambda: self.read_iptable_rules(net_device) - added_rules = utils.wait_for(container_rules, 10, step=0.1) - self.failif(not added_rules, "No rules added when container started.") - self.loginfo("Container %s\niptable rule list %s:" % - (self.sub_stuff['name'], added_rules)) - - mustpass(DockerCmd(self, 'stop', - ["-t 0", self.sub_stuff['name']]).execute()) - - container_rules = lambda: not self.read_iptable_rules(net_device) - removed_rules = utils.wait_for(container_rules, 10, step=0.1) - self.failif(not removed_rules, "Container %s iptable rules not " - "removed in 10s after stop. Rules:\n%s" - % (self.sub_stuff['name'], - self.read_iptable_rules(net_device))) + super(iptable_remove, self).postprocess() + name = self.sub_stuff['name'] + try: + DockerContainers(self).remove_by_name(name) + except ValueError: + pass # already removed + cntnr_before = self.sub_stuff['cntnr_before'] + cntnr_during = self.sub_stuff['cntnr_during'] + veth = self.cntnr_veth_map().get(name) + if veth is not None: + cntnr_after = set(self.read_iptable_rules(veth)) + self.failif(cntnr_before, "Rules found before run") + self.failif(not cntnr_during, "No rules were added") + self.failif(cntnr_after & cntnr_during, + "Rules left over after removal") diff --git a/subtests/docker_cli/run_volumes/run_volumes.py b/subtests/docker_cli/run_volumes/run_volumes.py index 01dd686f0..b0f350a33 100644 --- a/subtests/docker_cli/run_volumes/run_volumes.py +++ b/subtests/docker_cli/run_volumes/run_volumes.py @@ -155,6 +155,19 @@ def try_rm(subtest, cidfilename, cmdresult): except IOError: subtest.logdebug("Container never ran for %s", cmdresult) + def cleanup(self): + for test_data in self.sub_stuff['path_info']: + write_path = os.path.join(test_data['host_path'], + test_data['write_fn']) + read_path = os.path.join(test_data['host_path'], + test_data['read_fn']) + if write_path is not None and os.path.isfile(write_path): + os.unlink(write_path) + self.logdebug("Removed %s", write_path) + if read_path is not None and os.path.isfile(read_path): + os.unlink(read_path) + self.logdebug("Removed %s", read_path) + class volumes_rw(volumes_base): @@ -221,7 +234,6 @@ def postprocess(self): self.failif(wh != rh, msg) def cleanup(self): - super(volumes_rw, self).cleanup() if self.config['remove_after_test']: if self.sub_stuff.get('cmdresults') is None: self.logdebug("No commands ran, nothing to clean up") @@ -235,14 +247,4 @@ def cleanup(self): self.logwarning("Cleanup problem detected: ValueError: %s", str(detail)) continue - for test_data in self.sub_stuff['path_info']: - write_path = os.path.join(test_data['host_path'], - test_data['write_fn']) - read_path = os.path.join(test_data['host_path'], - test_data['read_fn']) - if write_path is not None and os.path.isfile(write_path): - os.unlink(write_path) - self.logdebug("Removed %s", write_path) - if read_path is not None and os.path.isfile(read_path): - os.unlink(read_path) - self.logdebug("Removed %s", read_path) + super(volumes_rw, self).cleanup() diff --git a/subtests/docker_cli/start/simple.py b/subtests/docker_cli/start/simple.py index 66ad2620c..32fd10abf 100644 --- a/subtests/docker_cli/start/simple.py +++ b/subtests/docker_cli/start/simple.py @@ -62,9 +62,10 @@ def run_once(self): err_msg = ("Start of the %s container failed, but '%s' message is not " "in the output:\n%s") # Nonexisting container + missing_msg = self.config['missing_msg'] result = mustfail(DockerCmd(self, "start", [name]).execute()) - self.failif("No such container" not in str(result), err_msg - % ("non-existing", 'No such container', result)) + self.failif(missing_msg not in str(result), err_msg + % ("non-existing", missing_msg, result)) # Running container self._start_container(name) diff --git a/subtests/docker_cli/wait/wait.py b/subtests/docker_cli/wait/wait.py index e7b3d8262..a5dec766a 100644 --- a/subtests/docker_cli/wait/wait.py +++ b/subtests/docker_cli/wait/wait.py @@ -2,342 +2,237 @@ Summary --------- -Test usage of docker 'wait' command +Test the docker wait operation on containers in various states Operational Summary ---------------------- -#. starts all containers defined in containers -#. prepares the wait command -#. prepares the expected results -#. executes the test command in all containers -#. executes the wait command -#. waits until all containers should be finished -#. analyze results +#. Prepare wait target container +#. Execute docker wait on container +#. Verify results """ -import random -import re -import time -from dockertest import config -from dockertest import subtest +import re +from collections import namedtuple +from collections import OrderedDict +from dockertest.subtest import SubSubtest +from dockertest.subtest import SubSubtestCaller +from dockertest.images import DockerImages from dockertest.containers import DockerContainers -from dockertest.dockercmd import AsyncDockerCmd -from dockertest.dockercmd import DockerCmd -from dockertest.images import DockerImage from dockertest.output import OutputGood -from dockertest.output import OutputNotBad -from dockertest.subtest import SubSubtest -from dockertest.xceptions import DockerTestError -from dockertest.xceptions import DockerTestNAError +from dockertest.dockercmd import DockerCmd +from dockertest.dockercmd import AsyncDockerCmd +from dockertest.config import Config +from dockertest.config import get_as_list -class wait(subtest.SubSubtestCaller): +class wait(SubSubtestCaller): + pass - """ Subtest caller """ - config_section = 'docker_cli/wait' +Target = namedtuple('Target', ['name', 'fqin', 'setup', 'wait', 'sleep']) -class wait_base(SubSubtest): - """ Base class """ - re_sleep = re.compile(r'sleep (\d+)') - re_exit = re.compile(r'exit (\d+)') +class WaitBase(SubSubtest): - # TODO: Check other tests/upcoming tests, add to config module? - def get_object_config(self, obj_name, key, default=None): - return self.config.get(key + '_' + obj_name, - self.config.get(key, default)) + def init_utils(self): + sss = self.sub_stuff + sss['dc'] = DockerContainers(self) + sss['di'] = DockerImages(self) def init_substuff(self): - # sub_stuff['containers'] is list of dicts containing: - # 'result' - DockerCmd process (detached) - # 'id' - id or name of the container - # 'exit_status' - expected exit code after test command - # 'test_cmd' - AsyncDockerCmd of the test command (attach ps) - # 'test_cmd_stdin' - stdin used by 'test_cmd' - # 'sleep_time' - how long it takes after test_cmd before exit - self.sub_stuff['containers'] = [] - self.sub_stuff['wait_cmd'] = None - self.sub_stuff['wait_stdout'] = None # Expected wait stdout output - self.sub_stuff['wait_stderr'] = None # Expected wait stderr output - self.sub_stuff['wait_result'] = None - self.sub_stuff['wait_duration'] = None # Wait for tested conts - self.sub_stuff['wait_should_fail'] = None # Expected wait failure - # Sleep after wait finishes (for non-tested containers to finish - self.sub_stuff['sleep_after'] = None - - def init_container(self, name): - subargs = self.get_object_config(name, 'run_options_csv') - if subargs: - subargs = [arg for arg in - self.config['run_options_csv'].split(',')] + self.sub_stuff['targets'] = OrderedDict() + self.sub_stuff['dkrcmd'] = None + + def target_dkrcmd(self, target): + # Validate setup command + if target.setup == 'none': + return None + elif target.setup == 'run': + command = 'run' + elif target.setup == 'create': + command = 'create' else: - subargs = [] - image = DockerImage.full_name_from_defaults(self.config) - subargs.append(image) - subargs.append("bash") - cont = {'result': DockerCmd(self, 'run', subargs, 10)} - self.sub_stuff['containers'].append(cont) - cont_id = cont['result'].execute().stdout.strip() - cont['id'] = cont_id - - # Cmd must contain one "exit $exit_status" - cmd = self.get_object_config(name, 'exec_cmd') - cont['exit_status'] = self.re_exit.findall(cmd)[0] - sleep = self.re_sleep.findall(cmd) - if sleep: - sleep = int(sleep[0]) - cont['sleep_time'] = sleep + raise ValueError("Unsupported target setup string: " + "%s" % target.setup) + subargs = get_as_list(self.config['target_run']) + subargs.append('--name') + subargs.append(target.name) + subargs.append(target.fqin) + target_cmd = self.config['target_cmd'].replace('@SLEEP@', + str(target.sleep)) + subargs += get_as_list(target_cmd) + timeout = self.config['target_timeout'] + return DockerCmd(self, command, subargs, + verbose=self.config['target_verbose'], + timeout=timeout) + + def target_wait_dkrcmd(self, name, fqin, setup, wait_opr, sleep): + del sleep # not used for now + del setup # not used for now + del fqin # not used for now + if wait_opr == 'stop': + cmd = 'stop' + subargs = [name] + elif wait_opr == 'kill': + cmd = 'kill' + subargs = [name] + elif wait_opr == 'remv': + cmd = 'rm' + subargs = ['--force', '--volumes', name] + elif wait_opr == 'none': + return None + elif wait_opr.isdigit(): + cmd = 'kill' + subargs = ['--signal', wait_opr, name] else: - cont['sleep_time'] = 0 - cont['test_cmd'] = AsyncDockerCmd(self, "attach", [cont_id]) - cont['test_cmd_stdin'] = cmd - - def init_use_names(self, use_names='IDS'): - if use_names == 'IDS': # IDs are already set - return - else: - if use_names == 'RANDOM': # log the current seed - try: - seed = self.config["random_seed"] - except ValueError: - seed = random.random() - self.logdebug("Using random seed: %s", seed) - rand = random.Random(seed) - conts = self.sub_stuff['containers'] - containers = DockerContainers(self) - containers = containers.list_containers() - cont_ids = [cont['id'] for cont in conts] - for cont in containers: - if cont.long_id in cont_ids: - if use_names == 'RANDOM' and rand.choice((True, False)): - continue # 50% chance of using id vs. name - # replace the id with name - cont_idx = cont_ids.index(cont.long_id) - conts[cont_idx]['id'] = cont.container_name - - def init_wait_for(self, wait_for, subargs): - if not wait_for: - raise DockerTestNAError("No container specified in config. to " - "wait_for.") - conts = self.sub_stuff['containers'] - end = self.config['invert_missing'] - wait_duration = 0 - wait_stdout = [] - wait_stderr = [] - - for cont in wait_for.split(' '): # digit or _$STRING - if cont.isdigit(): - cont = conts[int(cont)] - subargs.append(cont['id']) - wait_stdout.append(cont['exit_status']) - wait_duration = max(wait_duration, cont['sleep_time']) - else: - subargs.append(cont[1:]) - regex = self.config['missing_stderr'] % cont[1:] - wait_stderr.append(regex) - end = True - self.sub_stuff['wait_stdout'] = wait_stdout - self.sub_stuff['wait_stderr'] = wait_stderr - self.sub_stuff['wait_should_fail'] = end - self.sub_stuff['wait_duration'] = wait_duration - self.sub_stuff['wait_cmd'] = DockerCmd(self, 'wait', subargs, - wait_duration + 20) - max_duration = max(conts, key=lambda x: x['sleep_time'])['sleep_time'] - self.sub_stuff['sleep_after'] = max(0, max_duration - wait_duration) - - def prep_wait_cmd(self, wait_options_csv=None): - if wait_options_csv is not None: - subargs = [arg for arg in - self.config['wait_options_csv'].split(',')] - else: - subargs = [] - self.init_wait_for(self.config['wait_for'], subargs) + raise ValueError("Unsupported target_wait %s for target %s" + % (wait_opr, name)) + return AsyncDockerCmd(self, cmd, subargs, + self.config['target_verbose']) + + def init_targets(self): + sss = self.sub_stuff + target_setups = get_as_list(self.config['target_setups']) + # Single values will auto-convert to int's, convert back to string + target_waits = get_as_list(str(self.config['target_waits'])) + target_sleeps = get_as_list(str(self.config['target_sleeps'])) + fqin = sss['di'].full_name_from_defaults() + for index, setup in enumerate(target_setups): + name = sss['dc'].get_unique_name(setup) + sleep = float(target_sleeps[index]) + wait_opr = target_waits[index].lower() + wait_dkr = self.target_wait_dkrcmd(name, fqin, + setup, wait_opr.lower(), sleep) + target = Target(name=name, fqin=fqin, + setup=setup.lower(), + wait=wait_dkr, sleep=sleep) + sss['targets'][target] = self.target_dkrcmd(target) + + def execute_targets(self): + for dkrcmd in self.sub_stuff['targets'].itervalues(): + dkrcmd.execute() # blocking + detached + + def execute_target_waits(self): + for target in self.sub_stuff['targets']: + if target.wait is not None: + self.logdebug("Target %s", target.name) + target.wait.execute() # async + + def finish_target_waits(self): + for target in self.sub_stuff['targets']: + if target.wait is not None: + target.wait.wait(self.config['target_timeout']) + if self.config['target_verbose']: + self.logdebug("Final target %s details: %s", + target.name, target.wait) def initialize(self): - super(wait_base, self).initialize() - config.none_if_empty(self.config) + super(WaitBase, self).initialize() + self.init_utils() self.init_substuff() - - # Container - for name in self.config['containers'].split(): - self.init_container(name) - - self.init_use_names(self.config.get('use_names', False)) - - # Prepare the "wait" command - self.prep_wait_cmd(self.config.get('wait_options_csv')) + self.init_targets() def run_once(self): - super(wait_base, self).run_once() - for cont in self.sub_stuff['containers']: - self.logdebug("Executing %s, stdin %s", cont['test_cmd'], - cont['test_cmd_stdin']) - cont['test_cmd'].execute(cont['test_cmd_stdin'] + "\n") - self.sub_stuff['wait_cmd'].execute() - self.sub_stuff['wait_results'] = self.sub_stuff['wait_cmd'].cmdresult - self.logdebug("Wait finished, sleeping for %ss for non-tested " - "containers to finish.", self.sub_stuff['sleep_after']) - time.sleep(self.sub_stuff['sleep_after']) + super(WaitBase, self).run_once() + sss = self.sub_stuff + subargs = [target.name for target in sss['targets']] + # timeout set automatically from docker_timeout + sss['dkrcmd'] = AsyncDockerCmd(self, 'wait', subargs, + verbose=self.config['wait_verbose']) + self.execute_targets() + sss['dkrcmd'].execute() + self.execute_target_waits() + sss['dkrcmd'].wait(self.config['docker_timeout']) + self.finish_target_waits() + + def pproc_outputgood(self): + self.logdebug("Checking output sanity") + OutputGood(self.sub_stuff['dkrcmd'].cmdresult) + + def pproc_exit(self): + self.logdebug("Checking wait exit code") + _exit = self.config['exit'] + if not str(_exit).isdigit(): + return + dkrcmd_exit = self.sub_stuff['dkrcmd'].exit_status + expect_exit = int(_exit) + self.failif(dkrcmd_exit != expect_exit, + "Wait exit %d != %d" % (dkrcmd_exit, expect_exit)) + + def pproc_stdio(self, which): + stdio = self.config[which] + if not isinstance(stdio, basestring) or stdio == '': + self.logdebug("Not checking %s", which) + return + self.logdebug("Checking %s", which) + regex = re.compile(stdio) + dkrcmd_stdio = getattr(self.sub_stuff['dkrcmd'], which) + self.failif(not regex.search(dkrcmd_stdio), + "Wait %s didn't match regex %s in %s" + % (which, stdio, dkrcmd_stdio)) + + def pproc_target(self, target, dkrcmd): + self.logdebug("Checking target %s", target.name) + exit_status = dkrcmd.exit_status + if exit_status != 0: + msg = ("Target container %s non-zero exit(%d), " + "see debuglog for details" + % (target.name, exit_status)) + self.logwarning(msg) + self.logdebug(str(dkrcmd)) + + def pproc_target_waits(self, target): + if target.wait is None: + return # Nothing to check + self.logdebug("Checking target %s wait command", target.name) + if self.config['target_verbose']: + self.logdebug("Details: %s", target.wait) + exit_status = target.wait.exit_status + if exit_status != 0: + msg = ("Target container %s wait command, non-zero exit(%d), " + "see debuglog for details" + % (target.name, exit_status)) + self.logwarning(msg) + self.logdebug(str(target.wait)) def postprocess(self): - # Check if execution took the right time (SIGTERM 0s vs. SIGKILL 10s) - super(wait_base, self).postprocess() - wait_results = self.sub_stuff['wait_results'] - - for stdio_name in ('stdout', 'stderr'): - result = getattr(wait_results, stdio_name) - one_matched = False - paterns = self.sub_stuff['wait_%s' % stdio_name] - if not paterns: - continue - for pattern in paterns: - regex = re.compile(pattern, re.MULTILINE) - if bool(regex.search(result)): - one_matched = True - break - if self.sub_stuff['wait_should_fail']: - condition = one_matched - else: - condition = not one_matched - self.failif(condition, - "Expected %s match one of '%s' in %s:\n%s" - % (condition, - self.sub_stuff['wait_%s' % stdio_name], - stdio_name, result)) - OutputNotBad(wait_results) - if self.sub_stuff['wait_should_fail']: - self.failif(wait_results.exit_status == 0, - "Wait command should have failed but " - "passed instead: %s" % wait_results) - else: - OutputGood(wait_results) - self.failif(wait_results.exit_status != 0, - "Wait exit_status should be " - "zero, but is %s instead" % wait_results.exit_status) - exp = self.sub_stuff['wait_duration'] - self.failif(wait_results.duration > exp + 3, - "Execution of wait took longer," - " than expected. (%s %s+-3s)" - % (wait_results.duration, exp)) - self.failif(wait_results.duration < exp - 3, - "Execution of wait took less, " - "than expected. (%s %s+-3s)" - % (wait_results.duration, exp)) - for cmd in (cont['test_cmd'] - for cont in self.sub_stuff['containers']): - self.failif(not cmd.done, "Wait passed even thought one of the " - "test commands execution did not finish...\n%s") - OutputGood(cmd.wait(0)) + super(WaitBase, self).postprocess() + for target in self.sub_stuff['targets']: + self.pproc_target_waits(target) + for target, dkrcmd in self.sub_stuff['targets'].iteritems(): + self.pproc_target(target, dkrcmd) + if self.config['wait_verbose']: + self.logdebug("Details of wait command: %s", + self.sub_stuff['dkrcmd']) + self.pproc_outputgood() + self.pproc_exit() + self.pproc_stdio('stderr') + self.pproc_stdio('stdout') def cleanup(self): - # Removes the docker safely - failures = [] - super(wait_base, self).cleanup() - if not self.sub_stuff.get('containers'): - return # Docker was not created, we are clean - containers = DockerContainers(self).list_containers() - test_conts = self.sub_stuff.get('containers') - for cont in test_conts: - if 'id' not in cont: # Execution failed, we don't have id - failures.append("Container execution failed, can't verify what" - "/if remained in system: %s" - % cont['result']) - if 'test_cmd' in cont: - if not cont['test_cmd'].done: - # Actual killing happens below - failures.append("Test cmd %s had to be killed." - % (cont['test_cmd'])) - cont_ids = [cont['id'] for cont in test_conts] - for cont in containers: - if cont.long_id in cont_ids or cont.container_name in cont_ids: - cmdresult = DockerCmd(self, 'rm', - ['--force', '--volumes', - cont.long_id]).execute() - if cmdresult.exit_status != 0: - failures.append("Fail to remove container %s: %s" - % (cont.long_id, cmdresult)) - if failures: - raise DockerTestError("Cleanup failed:\n%s" % failures) - - -class no_wait(wait_base): - - """ - Test usage of docker 'wait' command (waits only for containers, which - should already exited. Expected execution duration is 0s) - - initialize: - 1) starts all containers defined in containers - 2) prepares the wait command - 3) prepares the expected results - run_once: - 4) executes the test command in all containers - 5) executes the wait command - 6) waits until all containers should be finished - postprocess: - 7) analyze results - """ - pass - - -class wait_first(wait_base): - - """ - Test usage of docker 'wait' command (first container exits after 10s, - others immediately. Expected execution duration is 10s) - - initialize: - 1) starts all containers defined in containers - 2) prepares the wait command - 3) prepares the expected results - run_once: - 4) executes the test command in all containers - 5) executes the wait command - 6) waits until all containers should be finished - postprocess: - 7) analyze results - """ - pass - - -class wait_last(wait_base): - - """ - Test usage of docker 'wait' command (last container exits after 10s, - others immediately. Expected execution duration is 10s) - - initialize: - 1) starts all containers defined in containers - 2) prepares the wait command - 3) prepares the expected results - run_once: - 4) executes the test command in all containers - 5) executes the wait command - 6) waits until all containers should be finished - postprocess: - 7) analyze results - """ - pass - - -class wait_missing(wait_base): - - """ - Test usage of docker 'wait' command (first and last containers doesn't - exist, second takes 10s to finish and the rest should finish immediately. - Expected execution duration is 10s with 2 exceptions) - - initialize: - 1) starts all containers defined in containers - 2) prepares the wait command - 3) prepares the expected results - run_once: - 4) executes the test command in all containers - 5) executes the wait command - 6) waits until all containers should be finished - postprocess: - 7) analyze results - """ - pass + super(WaitBase, self).cleanup() + sss = self.sub_stuff + self.sub_stuff['dc'].clean_all([target.name + for target in sss['targets']]) + + +# Generate any generic sub-subtests not found in this or other modules +def generic_factory(name): + + class Generic(WaitBase): + pass + + Generic.__name__ = name + return Generic + +subname = 'docker_cli/wait' +config = Config() +subsubnames = get_as_list(config[subname]['subsubtests']) +ssconfigs = [] +globes = globals() +for ssname in subsubnames: + if ssname not in globes: + cls = generic_factory(ssname) + # Inject generated class into THIS module's namespace + globes[cls.__name__] = cls