atex 0.5__py3-none-any.whl → 0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. atex/__init__.py +2 -12
  2. atex/cli/__init__.py +13 -13
  3. atex/cli/fmf.py +93 -0
  4. atex/cli/testingfarm.py +71 -61
  5. atex/connection/__init__.py +117 -0
  6. atex/connection/ssh.py +390 -0
  7. atex/executor/__init__.py +2 -0
  8. atex/executor/duration.py +60 -0
  9. atex/executor/executor.py +378 -0
  10. atex/executor/reporter.py +106 -0
  11. atex/executor/scripts.py +155 -0
  12. atex/executor/testcontrol.py +353 -0
  13. atex/fmf.py +217 -0
  14. atex/orchestrator/__init__.py +2 -0
  15. atex/orchestrator/aggregator.py +106 -0
  16. atex/orchestrator/orchestrator.py +324 -0
  17. atex/provision/__init__.py +101 -90
  18. atex/provision/libvirt/VM_PROVISION +8 -0
  19. atex/provision/libvirt/__init__.py +4 -4
  20. atex/provision/podman/README +59 -0
  21. atex/provision/podman/host_container.sh +74 -0
  22. atex/provision/testingfarm/__init__.py +2 -0
  23. atex/{testingfarm.py → provision/testingfarm/api.py} +170 -132
  24. atex/provision/testingfarm/testingfarm.py +236 -0
  25. atex/util/__init__.py +5 -10
  26. atex/util/dedent.py +1 -1
  27. atex/util/log.py +20 -12
  28. atex/util/path.py +16 -0
  29. atex/util/ssh_keygen.py +14 -0
  30. atex/util/subprocess.py +14 -13
  31. atex/util/threads.py +55 -0
  32. {atex-0.5.dist-info → atex-0.8.dist-info}/METADATA +97 -2
  33. atex-0.8.dist-info/RECORD +37 -0
  34. atex/cli/minitmt.py +0 -82
  35. atex/minitmt/__init__.py +0 -115
  36. atex/minitmt/fmf.py +0 -168
  37. atex/minitmt/report.py +0 -174
  38. atex/minitmt/scripts.py +0 -51
  39. atex/minitmt/testme.py +0 -3
  40. atex/orchestrator.py +0 -38
  41. atex/ssh.py +0 -320
  42. atex/util/lockable_class.py +0 -38
  43. atex-0.5.dist-info/RECORD +0 -26
  44. {atex-0.5.dist-info → atex-0.8.dist-info}/WHEEL +0 -0
  45. {atex-0.5.dist-info → atex-0.8.dist-info}/entry_points.txt +0 -0
  46. {atex-0.5.dist-info → atex-0.8.dist-info}/licenses/COPYING.txt +0 -0
atex/cli/minitmt.py DELETED
@@ -1,82 +0,0 @@
1
- import re
2
- import pprint
3
-
4
- #from .. import util
5
- from ..minitmt import fmf
6
-
7
-
8
- def _get_context(args):
9
- context = {}
10
- if args.context:
11
- for c in args.context:
12
- key, value = c.split('=', 1)
13
- context[key] = value
14
- return context or None
15
-
16
-
17
- def discover(args):
18
- result = fmf.FMFData(args.root, args.plan, context=_get_context(args))
19
- for test in result.tests:
20
- print(test.name)
21
-
22
-
23
- def show(args):
24
- result = fmf.FMFData(args.root, args.plan, context=_get_context(args))
25
- for test in result.tests:
26
- if re.match(args.test, test.name):
27
- pprint.pprint(test.data)
28
- break
29
- else:
30
- print(f"Not reachable via {args.plan} discovery: {args.test}")
31
- raise SystemExit(1)
32
-
33
-
34
- def parse_args(parser):
35
- parser.add_argument('--root', default='.', help="path to directory with fmf tests")
36
- parser.add_argument('--context', '-c', help="tmt style key=value context", action='append')
37
- cmds = parser.add_subparsers(
38
- dest='_cmd', help="minitmt feature", metavar='<cmd>', required=True,
39
- )
40
-
41
- cmd = cmds.add_parser(
42
- 'discover', aliases=('di',),
43
- help="list tests, post-processed by tmt plans",
44
- )
45
- cmd.add_argument('plan', help="tmt plan to use for discovery")
46
-
47
- cmd = cmds.add_parser(
48
- 'show',
49
- help="show fmf data of a test",
50
- )
51
- cmd.add_argument('plan', help="tmt plan to use for discovery")
52
- cmd.add_argument('test', help="fmf style test regex")
53
-
54
- cmd = cmds.add_parser(
55
- 'execute', aliases=('ex',),
56
- help="run a plan (or test) on a remote system",
57
- )
58
- grp = cmd.add_mutually_exclusive_group()
59
- grp.add_argument('--test', '-t', help="fmf style test regex")
60
- grp.add_argument('--plan', '-p', help="tmt plan name (path) inside metadata root")
61
- cmd.add_argument('--ssh-identity', '-i', help="path to a ssh keyfile for login")
62
- cmd.add_argument('user_host', help="ssh style user@host of the remote")
63
-
64
-
65
- def main(args):
66
- if args._cmd in ('discover', 'di'):
67
- discover(args)
68
- elif args._cmd == 'show':
69
- show(args)
70
- elif args._cmd in ('execute', 'ex'):
71
- #execute(args)
72
- print("not implemented yet")
73
- else:
74
- raise RuntimeError(f"unknown args: {args}")
75
-
76
-
77
- CLI_SPEC = {
78
- 'aliases': ('tmt',),
79
- 'help': "simple test executor using atex.minitmt",
80
- 'args': parse_args,
81
- 'main': main,
82
- }
atex/minitmt/__init__.py DELETED
@@ -1,115 +0,0 @@
1
- import os
2
- import random
3
-
4
- from pathlib import Path
5
-
6
- # TODO: TMT_PLAN_ENVIRONMENT_FILE
7
-
8
- # TODO: install rsync on the guest as part of setup
9
-
10
- # TODO: in Orchestrator, when a Provisioner becomes free, have it pick a test
11
- # from the appropriate tests[platform] per the Provisioner's platform
12
-
13
-
14
- def _random_string(length):
15
- return ''.join(
16
- random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=length),
17
- )
18
-
19
-
20
- class Preparator:
21
- """
22
- Set of utilities for preparing a newly acquired/reserved machine for
23
- running tests, by installing global package requirements, copying all
24
- tests over, executing tmt plan 'prepare' step, etc.
25
- """
26
- def __init__(self, ssh_conn):
27
- self.conn = ssh_conn
28
-
29
- def copy_tests(self):
30
- pass
31
-
32
- def run_prepare_scripts(self):
33
- pass
34
-
35
- def __enter__(self):
36
- self.conn.connect()
37
- return self
38
-
39
- def __exit__(self, exc_type, exc_value, traceback):
40
- self.conn.disconnect()
41
-
42
-
43
- # TODO: have Executor take a finished Preparator instance as input?
44
- # - for extracting copied tests location
45
- # - for extracting TMT_PLAN_ENVIRONMENT_FILE location
46
- # - etc.
47
-
48
-
49
- class Executor:
50
- """
51
- Helper for running one test on a remote system and processing results
52
- and uploaded files by that test.
53
- """
54
- def __init__(self, fmf_test, ssh_conn):
55
- self.fmf_test = fmf_test
56
- self.conn = ssh_conn
57
- self.remote_socket = self.local_socket = None
58
-
59
- def __enter__(self):
60
- # generate a (hopefully) unique test control socket name
61
- # and modify the SSHConn instance to use it
62
- rand_name = f'atex-control-{_random_string(50)}.sock'
63
- self.local_socket = Path(os.environ.get('TMPDIR', '/tmp')) / rand_name
64
- self.remote_socket = f'/tmp/{rand_name}'
65
- self.conn.options['RemoteForward'] = f'{self.remote_socket} {self.local_socket}'
66
- self.conn.connect()
67
- return self
68
-
69
- def __exit__(self, exc_type, exc_value, traceback):
70
- self.conn.ssh(f'rm -f {self.remote_socket}')
71
- self.local_socket.unlink()
72
- self.remote_socket = self.local_socket = None
73
- self.conn.disconnect()
74
-
75
- # execute all prepares (how:install and how:shell) via ssh
76
- def prepare(self):
77
- # TODO: check via __some_attr (named / prefixed after our class)
78
- # whether this reserved system has been prepared already ... ?
79
- # ^^^^ in Orchestrator
80
- #
81
- # TODO: copy root of fmf metadata to some /var/tmp/somedir to run tests from
82
- #
83
- # TODO: move prepare out, possibly to class-less function,
84
- # we don't want it running over an SSHConn that would set up socket forwarding
85
- # only to tear it back down, when executed from Orchestrator for setup only
86
- #
87
- # TODO: install rsync
88
- pass
89
-
90
- def run_script(self, script, duration=None, shell='/bin/bash', **kwargs):
91
- self.conn.ssh(shell, input=script.encode())
92
-
93
- # run one test via ssh and parse its results on-the-fly,
94
- # write out logs
95
- def run_test(self, fmf_test, reporter):
96
- # TODO: pass environment from test fmf metadata
97
- # TODO: watch for test duration, etc. metadata
98
- # TODO: logging of stdout+stderr to hidden file, doing 'ln' from it to
99
- # test-named 'testout' files
100
- # - generate hidden name suffix via:
101
- # ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=20))
102
- output_logfile = \
103
- reporter.files_dir(fmf_test.name) / f'.test_output_{self._random_string(50)}.log'
104
- output_logfile = os.open(reporter.files_dir(fmf_test.name), os.O_WRONLY | os.O_CREAT)
105
- try:
106
- #self.conn.ssh(
107
- pass
108
- finally:
109
- os.close(output_logfile)
110
- # TODO: create temp dir on remote via 'mktemp -d', then call
111
- # self.conn.add_remote_forward(...) with socket path inside that tmpdir
112
-
113
- # TODO: run tests by passing stdout/stderr via pre-opened fd so we don't handle it in code
114
-
115
- # TODO: read unix socket as nonblocking, check test subprocess.Popen proc status every 0.1sec
atex/minitmt/fmf.py DELETED
@@ -1,168 +0,0 @@
1
- import re
2
- import collections
3
- from pathlib import Path
4
-
5
- # from system-wide sys.path
6
- import fmf
7
-
8
- # name: fmf path to the test as string, ie. /some/test
9
- # data: dict of the parsed fmf metadata (ie. {'tag': ... , 'environment': ...})
10
- # dir: relative pathlib.Path of the test .fmf to repo root, ie. some/test
11
- # (may be different from name for "virtual" tests that share the same dir)
12
- FMFTest = collections.namedtuple('FMFTest', ['name', 'data', 'dir'])
13
-
14
-
15
- class FMFData:
16
- """
17
- Helper class for reading and querying fmf metadata from the filesystem.
18
- """
19
- # TODO: usage example ^^^^
20
-
21
- @staticmethod
22
- def _listlike(data, key):
23
- """
24
- Get a piece of fmf metadata as an iterable regardless of whether it was
25
- defined as a dict or a list.
26
-
27
- This is needed because many fmf metadata keys can be used either as
28
- some_key: 123
29
- or as lists via YAML syntax
30
- some_key:
31
- - 123
32
- - 456
33
- and, for simplicity, we want to always deal with lists (iterables).
34
- """
35
- if value := data.get(key):
36
- return value if isinstance(value, list) else (value,)
37
- else:
38
- return ()
39
-
40
- def __init__(self, fmf_tree, plan_name, context=None):
41
- """
42
- 'fmf_tree' is filesystem path somewhere inside fmf metadata tree,
43
- or a root fmf.Tree instance.
44
-
45
- 'plan_name' is fmf identifier (like /some/thing) of a tmt plan
46
- to use for discovering tests.
47
-
48
- 'context' is a dict like {'distro': 'rhel-9.6'} used for filtering
49
- discovered tests.
50
- """
51
- self.prepare_pkgs = []
52
- self.prepare_scripts = []
53
- self.tests = []
54
-
55
- tree = fmf_tree.copy() if isinstance(fmf_tree, fmf.Tree) else fmf.Tree(fmf_tree)
56
- ctx = fmf.Context(**context) if context else fmf.Context()
57
- tree.adjust(context=ctx)
58
-
59
- self.fmf_root = tree.root
60
-
61
- # lookup the plan first
62
- plan = tree.find(plan_name)
63
- if not plan:
64
- raise ValueError(f"plan {plan_name} not found in {tree.root}")
65
- if 'test' in plan.data:
66
- raise ValueError(f"plan {plan_name} appears to be a test")
67
-
68
- # gather all prepare scripts / packages
69
- #
70
- # prepare:
71
- # - how: install
72
- # package:
73
- # - some-rpm-name
74
- # - how: shell
75
- # script:
76
- # - some-command
77
- for entry in self._listlike(plan.data, 'prepare'):
78
- if 'how' not in entry:
79
- continue
80
- if entry['how'] == 'install':
81
- self.prepare_pkgs += self._listlike(entry, 'package')
82
- elif entry['how'] == 'shell':
83
- self.prepare_scripts += self._listlike(entry, 'script')
84
-
85
- # gather all tests selected by the plan
86
- #
87
- # discover:
88
- # - how: fmf
89
- # filter:
90
- # - tag:some_tag
91
- # test:
92
- # - some-test-regex
93
- # exclude:
94
- # - some-test-regex
95
- if 'discover' in plan.data:
96
- discover = plan.data['discover']
97
- if not isinstance(discover, list):
98
- discover = (discover,)
99
-
100
- for entry in discover:
101
- if entry.get('how') != 'fmf':
102
- continue
103
-
104
- filtering = {}
105
- for meta_name in ('filter', 'test', 'exclude'):
106
- if value := self._listlike(entry, meta_name):
107
- filtering[meta_name] = value
108
-
109
- children = tree.prune(
110
- names=filtering.get('test'),
111
- filters=filtering.get('filter'),
112
- )
113
- for child in children:
114
- # excludes not supported by .prune(), we have to do it here
115
- excludes = filtering.get('exclude')
116
- if excludes and any(re.match(x, child.name) for x in excludes):
117
- continue
118
- # only enabled tests
119
- if 'enabled' in child.data and not child.data['enabled']:
120
- continue
121
- # no manual tests
122
- if child.data.get('manual'):
123
- continue
124
- # after adjusting above, any adjusts are useless, free some space
125
- if 'adjust' in child.data:
126
- del child.data['adjust']
127
- # ie. ['/abs/path/to/some.fmf', '/abs/path/to/some/node.fmf']
128
- source_dir = Path(child.sources[-1]).parent.relative_to(self.fmf_root)
129
- self.tests.append(
130
- FMFTest(name=child.name, data=child.data, dir=source_dir),
131
- )
132
-
133
-
134
- # Some extra notes for fmf.prune() arguments:
135
- #
136
- # Set 'names' to filter by a list of fmf node names, ie.
137
- # ['/some/test', '/another/test']
138
- #
139
- # Set 'filters' to filter by a list of fmf-style filter expressions, see
140
- # https://fmf.readthedocs.io/en/stable/modules.html#fmf.filter
141
- #
142
- # Set 'conditions' to filter by a list of python expressions whose namespace
143
- # locals() are set up to be a dictionary of the tree. When any of the
144
- # expressions returns True, the tree is returned, ie.
145
- # ['environment["FOO"] == "BAR"']
146
- # ['"enabled" not in locals() or enabled']
147
- # Note that KeyError is silently ignored and treated as False.
148
- #
149
- # Set 'context' to a dictionary to post-process the tree metadata with
150
- # adjust expressions (that may be present in a tree) using the specified
151
- # context. Any other filters are applied afterwards to allow modification
152
- # of tree metadata by the adjust expressions. Ie.
153
- # {'distro': 'rhel-9.6.0', 'arch': 'x86_64'}
154
-
155
- Platform = collections.namedtuple('Platform', ['distro', 'arch'])
156
-
157
-
158
- def combine_platforms(fmf_path, plan_name, platforms):
159
- # TODO: document
160
- fmf_datas = {}
161
- tree = fmf.Tree(fmf_path)
162
- for platform in platforms:
163
- context = {'distro': platform.distro, 'arch': platform.arch}
164
- fmf_datas[platform] = FMFData(tree, plan_name, context=context)
165
- return fmf_datas
166
-
167
- # TODO: in Orchestrator, when a Provisioner becomes free, have it pick a test
168
- # from the appropriate tests[platform] per the Provisioner's platform
atex/minitmt/report.py DELETED
@@ -1,174 +0,0 @@
1
- import os
2
- import csv
3
- import gzip
4
- import ctypes
5
- import ctypes.util
6
- import contextlib
7
- from pathlib import Path
8
-
9
- from .. import util
10
-
11
-
12
- libc = ctypes.CDLL(ctypes.util.find_library('c'))
13
-
14
- # int linkat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, int flags)
15
- libc.linkat.argtypes = (
16
- ctypes.c_int,
17
- ctypes.c_char_p,
18
- ctypes.c_int,
19
- ctypes.c_char_p,
20
- ctypes.c_int,
21
- )
22
- libc.linkat.restype = ctypes.c_int
23
-
24
- # fcntl.h:#define AT_EMPTY_PATH 0x1000 /* Allow empty relative pathname */
25
- AT_EMPTY_PATH = 0x1000
26
-
27
- # fcntl.h:#define AT_FDCWD -100 /* Special value used to indicate
28
- AT_FDCWD = -100
29
-
30
-
31
- def linkat(*args):
32
- if (ret := libc.linkat(*args)) == -1:
33
- errno = ctypes.get_errno()
34
- raise OSError(errno, os.strerror(errno))
35
- return ret
36
-
37
-
38
- class CSVReporter(util.LockableClass):
39
- """
40
- Stores reported results as a GZIP-ed CSV and files (logs) under a related
41
- directory.
42
-
43
- with CSVReporter('file/storage/dir', 'results.csv.gz') as reporter:
44
- sub = reporter.make_subreporter('rhel-9', 'x86_64')
45
- sub({'name': '/some/test', 'status': 'pass'})
46
- sub({'name': '/another/test', 'status': 'pass'})
47
- ...
48
- sub = reporter.make_subreporter('rhel-9', 'ppc64le')
49
- ...
50
- sock = accept_unix_connection()
51
- reporter.store_file('/some/test', 'debug.log', sock, 1234)
52
- """
53
- class _ExcelWithUnixNewline(csv.excel):
54
- lineterminator = '\n'
55
-
56
- def __init__(self, storage_dir, results_file):
57
- super().__init__()
58
- self.storage_dir = Path(storage_dir)
59
- if self.storage_dir.exists():
60
- raise FileExistsError(f"{storage_dir} already exists")
61
- self.results_file = Path(results_file)
62
- if self.results_file.exists():
63
- raise FileExistsError(f"{self.results_file} already exists")
64
- self.storage_dir.mkdir()
65
- self.csv_writer = None
66
- self.results_gzip_handle = None
67
-
68
- def __enter__(self):
69
- f = gzip.open(self.results_file, 'wt', newline='')
70
- try:
71
- self.csv_writer = csv.writer(f, dialect=self._ExcelWithUnixNewline)
72
- except:
73
- f.close()
74
- raise
75
- self.results_gzip_handle = f
76
- return self
77
-
78
- def __exit__(self, exc_type, exc_value, traceback):
79
- self.results_gzip_handle.close()
80
- self.results_gzip_handle = None
81
- self.csv_writer = None
82
-
83
- def report(self, distro, arch, status, name, note, *files):
84
- """
85
- Persistently write out details of a test result.
86
- """
87
- with self.lock:
88
- self.csv_writer.writerow((distro, arch, status, name, note, *files))
89
-
90
- @staticmethod
91
- def _normalize_path(path):
92
- # the magic here is to treat any dangerous path as starting at /
93
- # and resolve any weird constructs relative to /, and then simply
94
- # strip off the leading / and use it as a relative path
95
- path = path.lstrip('/')
96
- path = os.path.normpath(f'/{path}')
97
- return path[1:]
98
-
99
- def make_subreporter(self, distro, arch):
100
- """
101
- Return a preconfigured reporter instance, suitable for use
102
- by an Executor.
103
- """
104
- def reporter(result_line):
105
- if 'files' in result_line:
106
- files = (self._normalize_path(x['name']) for x in result_line['files'])
107
- else:
108
- files = ()
109
- self.report(
110
- distro, arch, result_line['status'], result_line['name'],
111
- result_line.get('note', ''), *files,
112
- )
113
- return reporter
114
-
115
- def _files_dir(self, result_name):
116
- dir_path = self.storage_dir / result_name.lstrip('/')
117
- dir_path.mkdir(parents=True, exist_ok=True)
118
- return dir_path
119
-
120
- def _files_file(self, result_name, file_name):
121
- file_name = self._normalize_path(file_name)
122
- return self._files_dir(result_name) / file_name
123
-
124
- @contextlib.contextmanager
125
- def open_tmpfile(self, open_mode=os.O_WRONLY):
126
- flags = open_mode | os.O_TMPFILE
127
- fd = os.open(self.storage_dir, flags, 0o644)
128
- try:
129
- yield fd
130
- finally:
131
- os.close(fd)
132
- # def open_tmpfile(self, result_name, open_mode=os.O_WRONLY):
133
- # """
134
- # Open an anonymous (name-less) file for writing, in a directory relevant
135
- # to 'result_name' and yield its file descriptor (int) as context, closing
136
- # it when the context is exited.
137
- # """
138
- # flags = open_mode | os.O_TMPFILE
139
- # fd = os.open(self._files_dir(result_name), flags, 0o644)
140
- # try:
141
- # yield fd
142
- # finally:
143
- # os.close(fd)
144
-
145
- def link_tmpfile_to(self, result_name, file_name, fd):
146
- """
147
- Store a file named 'file_name' in a directory relevant to 'result_name'
148
- whose 'fd' (a file descriptor) was created by open_tmpfile().
149
-
150
- This function can be called multiple times with the same 'fd', and
151
- does not close or otherwise alter the descriptor.
152
- """
153
- final_path = self._files_file(result_name, file_name)
154
- linkat(fd, b'', AT_FDCWD, bytes(final_path), AT_EMPTY_PATH)
155
-
156
- def store_file(self, result_name, file_name, in_fd, count):
157
- """
158
- Read 'count' bytes of binary data from an OS file descriptor 'in_fd'
159
- and store them under 'result_name' as a file (or relative path)
160
- named 'file_name', creating it.
161
- """
162
- final_path = self._files_file(result_name, file_name)
163
- # be as efficient as possible, let the kernel handle big data
164
- out_fd = None
165
- try:
166
- out_fd = os.open(final_path, os.O_WRONLY | os.O_CREAT)
167
- while count > 0:
168
- written = os.sendfile(out_fd, in_fd, None, count)
169
- if written == 0:
170
- raise RuntimeError(f"got unexpected EOF when receiving {final_path}")
171
- count -= written
172
- finally:
173
- if out_fd:
174
- os.close(out_fd)
atex/minitmt/scripts.py DELETED
@@ -1,51 +0,0 @@
1
- #from .. import util
2
-
3
- #run_test = util.dedent(fr'''
4
- # # create a temp dir for everything, send it to the controller
5
- # tmpdir=$(mktemp -d /var/tmp/atex-XXXXXXXXX)
6
- # echo "tmpdir=$tmpdir"
7
- #
8
- # # remove transient files if interrupted
9
- # trap "rm -rf \"$tmpdir\"" INT
10
- #
11
- # # wait for result reporting unix socket to be created by sshd
12
- # socket=$tmpdir/results.sock
13
- # while [[ ! -e $socket ]]; do sleep 0.1; done
14
- # echo "socket=$socket"
15
- #
16
- # # tell the controller to start logging test output
17
- # echo ---
18
- #
19
- # # install test dependencies
20
- # rpms=( {' '.join(requires)} )
21
- # to_install=()
22
- # for rpm in "${{rpms[@]}}"; do
23
- # rpm -q --quiet "$rpm" || to_install+=("$rpm")
24
- # done
25
- # dnf -y --setopt=install_weak_deps=False install "${{to_install[@]}}"
26
- #
27
- # # run the test
28
- # ...
29
- # rc=$?
30
- #
31
- # # test finished, clean up
32
- # rm -rf "$tmpdir"
33
- #
34
- # exit $rc
35
- #''')
36
-
37
- # TODO: have another version of ^^^^ for re-execution of test after a reboot
38
- # or disconnect that sets tmpdir= from us (reusing on-disk test CWD)
39
- # rather than creating a new one
40
- # - the second script needs to rm -f the unix socket before echoing
41
- # something back to let us re-create it via a new ssh channel open
42
- # because StreamLocalBindUnlink doesn't seem to work
43
-
44
-
45
- # TODO: call ssh with -oStreamLocalBindUnlink=yes to re-initialize
46
- # the listening socket after guest reboot
47
- #
48
- # -R /var/tmp/atex-BlaBla/results.sock:/var/tmp/controller.sock
49
- #
50
- # (make sure to start listening on /var/tmp/controller.sock before
51
- # calling ssh to run the test)
atex/minitmt/testme.py DELETED
@@ -1,3 +0,0 @@
1
- from ..util import debug
2
-
3
- debug("foo")
atex/orchestrator.py DELETED
@@ -1,38 +0,0 @@
1
- from . import util
2
-
3
-
4
- class Orchestrator:
5
- """
6
- A scheduler for parallel execution on multiple resources (machines/systems).
7
-
8
- Given a list of Provisioner-derived class instances, it attempts to reserve
9
- resources and uses them on-demand as they become available, calling run()
10
- on each.
11
-
12
- Note that run() and report() always run in a separate threads (are allowed
13
- to block), and may access instance attributes, which are transparently
14
- guarded by a thread-aware mutex.
15
-
16
- """
17
-
18
- def __init__(self):
19
- pass
20
- # TODO: configure via args, max workers, etc.
21
-
22
- # def reserve(self, provisioner):
23
- # # call provisioner.reserve(), return its return
24
- # ...
25
-
26
- def add_provisioner(self, provisioner):
27
- # add to a self.* list of provisioners to be used for getting machines
28
- ...
29
-
30
- def run(self, provisioner):
31
- # run tests, if destructive, call provisioner.release()
32
- # returns anything
33
- ...
34
-
35
- def report(self):
36
- # gets return from run
37
- # writes it out to somewhere else
38
- ...