atex 0.8__py3-none-any.whl → 0.10__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 (44) hide show
  1. atex/aggregator/__init__.py +60 -0
  2. atex/aggregator/json.py +96 -0
  3. atex/cli/__init__.py +11 -1
  4. atex/cli/fmf.py +73 -23
  5. atex/cli/libvirt.py +128 -0
  6. atex/cli/testingfarm.py +60 -3
  7. atex/connection/__init__.py +13 -11
  8. atex/connection/podman.py +61 -0
  9. atex/connection/ssh.py +38 -47
  10. atex/executor/executor.py +144 -119
  11. atex/executor/reporter.py +66 -71
  12. atex/executor/scripts.py +13 -5
  13. atex/executor/testcontrol.py +43 -30
  14. atex/fmf.py +94 -74
  15. atex/orchestrator/__init__.py +76 -2
  16. atex/orchestrator/adhoc.py +465 -0
  17. atex/{provision → provisioner}/__init__.py +54 -42
  18. atex/provisioner/libvirt/__init__.py +2 -0
  19. atex/provisioner/libvirt/libvirt.py +472 -0
  20. atex/provisioner/libvirt/locking.py +170 -0
  21. atex/{provision → provisioner}/libvirt/setup-libvirt.sh +21 -1
  22. atex/provisioner/podman/__init__.py +2 -0
  23. atex/provisioner/podman/podman.py +169 -0
  24. atex/{provision → provisioner}/testingfarm/api.py +121 -69
  25. atex/{provision → provisioner}/testingfarm/testingfarm.py +44 -52
  26. atex/util/libvirt.py +18 -0
  27. atex/util/log.py +53 -43
  28. atex/util/named_mapping.py +158 -0
  29. atex/util/subprocess.py +46 -12
  30. atex/util/threads.py +71 -20
  31. atex-0.10.dist-info/METADATA +86 -0
  32. atex-0.10.dist-info/RECORD +44 -0
  33. atex/orchestrator/aggregator.py +0 -106
  34. atex/orchestrator/orchestrator.py +0 -324
  35. atex/provision/libvirt/__init__.py +0 -24
  36. atex/provision/podman/README +0 -59
  37. atex/provision/podman/host_container.sh +0 -74
  38. atex-0.8.dist-info/METADATA +0 -197
  39. atex-0.8.dist-info/RECORD +0 -37
  40. /atex/{provision → provisioner}/libvirt/VM_PROVISION +0 -0
  41. /atex/{provision → provisioner}/testingfarm/__init__.py +0 -0
  42. {atex-0.8.dist-info → atex-0.10.dist-info}/WHEEL +0 -0
  43. {atex-0.8.dist-info → atex-0.10.dist-info}/entry_points.txt +0 -0
  44. {atex-0.8.dist-info → atex-0.10.dist-info}/licenses/COPYING.txt +0 -0
@@ -1,24 +0,0 @@
1
- from .. import base
2
- from ... import util, ssh
3
-
4
-
5
- class LibvirtProvisioner(base.Provisioner):
6
- number = 123
7
-
8
- def reserve(self):
9
- util.debug(f"reserving {self.number}")
10
-
11
- # TODO: as simple attribute, to be guaranteed set when reserve() returns,
12
- # can be overriden by a getter function if you need to keep track
13
- # how many times it was accessed
14
- def connection(self):
15
- #return {"Hostname": "1.2.3.4", "User": "root", "IdentityFile": ...}
16
- util.debug(f"returning ssh for {self.number}")
17
- return ssh.SSHConn({"Hostname": "1.2.3.4", "User": "root"})
18
-
19
- def release(self):
20
- util.debug(f"releasing {self.number}")
21
-
22
- def alive(self):
23
- util.debug(f"always alive: {self.number}")
24
- return True
@@ -1,59 +0,0 @@
1
-
2
- making a podman image from the currently installed OS:
3
-
4
- 1) dnf install into a separate installroot
5
-
6
- dnf
7
- --installroot=$INSTALLROOT \
8
- --setopt=install_weak_deps=False \
9
- --setopt=tsflags=nodocs \
10
- -y groupinstall minimal-environment
11
-
12
- as root (doesn't work well with unshare, maybe could work via bwrap (bubblewrap))
13
-
14
- maybe the unprivileged solution is pulling image from hub + installing @minimal-environment
15
- into it (perhaps via podman build)
16
-
17
-
18
- 2) post process it
19
-
20
- echo -n > "$INSTALLROOT/etc/machine-id"
21
- echo container > "$INSTALLROOT/etc/hostname"
22
-
23
- rm -rf "$INSTALLROOT/etc/yum.repos.d"
24
- cp -f /etc/yum.repos.d/* "$INSTALLROOT/etc/yum.repos.d/."
25
- cp -f /etc/pki/rpm-gpg/* "$INSTALLROOT/etc/pki/rpm-gpg/."
26
-
27
- echo install_weak_deps=False >> "$INSTALLROOT/etc/dnf/dnf.conf"
28
- echo tsflags=nodocs >> "$INSTALLROOT/etc/dnf/dnf.conf"
29
-
30
- ln -sf \
31
- /usr/lib/systemd/system/multi-user.target \
32
- "$INSTALLROOT/etc/systemd/system/default.target"
33
-
34
- # disable auditd
35
- # disable other services
36
- # set root password
37
-
38
- dnf clean all --installroot="$INSTALLROOT"
39
-
40
-
41
- 3) pack it
42
-
43
- tar --xattrs -C "$INSTALLROOT" -cvf tarball.tar .
44
-
45
- rm -rf "$INSTALLROOT"
46
-
47
-
48
- 4) import it to podman
49
-
50
- podman import --change 'CMD ["/sbin/init"]' tarball.tar my-image-name
51
-
52
-
53
- 5) run it
54
-
55
- podman {run,create} --systemd=always --cgroups=split ...
56
-
57
-
58
-
59
- ------------------------------
@@ -1,74 +0,0 @@
1
- #!/bin/bash
2
-
3
- if [[ $# -lt 1 ]]; then
4
- echo "usage: $0 <podman-image-name>" >&2
5
- exit 1
6
- fi
7
- image_name="$1"
8
-
9
- set -e -x
10
-
11
- tmpdir=$(mktemp -d -p /var/tmp)
12
- trap "rm -rf '$tmpdir'" EXIT
13
-
14
- installroot="$tmpdir/root"
15
-
16
- dnf \
17
- --installroot="$installroot" \
18
- --setopt=install_weak_deps=False \
19
- --setopt=tsflags=nodocs \
20
- -q -y groupinstall minimal-environment
21
-
22
- echo -n > "$installroot/etc/machine-id"
23
- #echo container > "$installroot/etc/hostname"
24
-
25
- cp -f /etc/yum.repos.d/* "$installroot/etc/yum.repos.d/."
26
- cp -f /etc/pki/rpm-gpg/* "$installroot/etc/pki/rpm-gpg/."
27
-
28
- echo install_weak_deps=False >> "$installroot/etc/dnf/dnf.conf"
29
- echo tsflags=nodocs >> "$installroot/etc/dnf/dnf.conf"
30
-
31
- ln -sf \
32
- /usr/lib/systemd/system/multi-user.target \
33
- "$installroot/etc/systemd/system/default.target"
34
-
35
- systemctl --root="$installroot" disable \
36
- auditd.service crond.service rhsmcertd.service sshd.service
37
-
38
- #encrypted=$(openssl passwd -6 somepass)
39
- #usermod --root="$installroot" --password "$encrypted" root
40
-
41
- dnf clean packages --installroot="$installroot"
42
-
43
- tar --xattrs -C "$installroot" -cf "$tmpdir/packed.tar" .
44
-
45
- rm -rf "$installroot"
46
-
47
- podman import \
48
- --change 'CMD ["/sbin/init"]' \
49
- "$tmpdir/packed.tar" "$image_name"
50
-
51
- # start as
52
- # podmn {run,create} --systemd=always --cgroups=split --device /dev/kvm ...
53
- #
54
- # podman run -t -i \
55
- # --systemd=always --cgroups=split \
56
- # --device /dev/kvm \
57
- # --network=bridge \
58
- # --cap-add NET_ADMIN --cap-add NET_RAW --cap-add SYS_MODULE \
59
- # --mount type=bind,src=/lib/modules,dst=/lib/modules,ro \
60
- # --mount type=bind,src=/proc/sys/net,dst=/proc/sys/net,rw \
61
- # my_container
62
- #
63
- # as unprivileged user:
64
- # podman run -t -i \
65
- # --systemd=always --cgroups=split --network=bridge --privileged \
66
- # my_container
67
- #
68
- # container setup:
69
- # dnf -y install libvirt-daemon qemu-kvm libvirt-client libvirt-daemon-driver-qemu virt-install libvirt-daemon-driver-storage libvirt-daemon-config-network
70
- # echo $'user = "root"\ngroup = "root"\nremember_owner = 0' >> /etc/libvirt/qemu.conf
71
- # systemctl start virtqemud.socket virtstoraged.socket virtnetworkd.socket
72
- # virsh net-start default
73
- # virt-install --install fedora40 --disk /var/lib/libvirt/images/foo.qcow2,size=20 --console pty --check disk_size=off --unattended --graphics none
74
-
@@ -1,197 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: atex
3
- Version: 0.8
4
- Summary: Ad-hoc Test EXecutor
5
- Project-URL: Homepage, https://github.com/RHSecurityCompliance/atex
6
- License-Expression: GPL-3.0-or-later
7
- License-File: COPYING.txt
8
- Classifier: Operating System :: POSIX :: Linux
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Topic :: Software Development :: Testing
11
- Requires-Python: >=3.11
12
- Requires-Dist: fmf>=1.6
13
- Requires-Dist: urllib3<3,>=2
14
- Description-Content-Type: text/markdown
15
-
16
- # ATEX = Ad-hoc Test EXecutor
17
-
18
- A collections of Python APIs to provision operating systems, collect
19
- and execute [FMF](https://github.com/teemtee/fmf/)-style tests, gather
20
- and organize their results and generate reports from those results.
21
-
22
- The name comes from a (fairly unique to FMF/TMT ecosystem) approach that
23
- allows provisioning a pool of systems and scheduling tests on them as one would
24
- on an ad-hoc pool of thread/process workers - once a worker becomes free,
25
- it receives a test to run.
26
- This is in contrast to splitting a large list of N tests onto M workers
27
- like N/M, which yields significant time penalties due to tests having
28
- very varies runtimes.
29
-
30
- Above all, this project is meant to be a toolbox, not a silver-plate solution.
31
- Use its Python APIs to build a CLI tool for your specific use case.
32
- The CLI tool provided here is just for demonstration / testing, not for serious
33
- use - we want to avoid huge modular CLIs for Every Possible Scenario. That's
34
- the job of the Python API. Any CLI should be simple by nature.
35
-
36
- ---
37
-
38
- THIS PROJECT IS HEAVILY WIP, THINGS WILL MOVE AROUND, CHANGE AND OTHERWISE
39
- BREAK. DO NOT USE IT (for now).
40
-
41
- ---
42
-
43
- ## License
44
-
45
- Unless specified otherwise, any content within this repository is distributed
46
- under the GNU GPLv3 license, see the [COPYING.txt](COPYING.txt) file for more.
47
-
48
- ## Parallelism and cleanup
49
-
50
- There are effectively 3 methods of running things in parallel in Python:
51
-
52
- - `threading.Thread` (and related `concurrent.futures` classes)
53
- - `multiprocessing.Process` (and related `concurrent.futures` classes)
54
- - `asyncio`
55
-
56
- and there is no clear winner (in terms of cleanup on `SIGTERM` or Ctrl-C):
57
-
58
- - `Thread` has signal handlers only in the main thread and is unable to
59
- interrupt any running threads without super ugly workarounds like `sleep(1)`
60
- in every thread, checking some "pls exit" variable
61
- - `Process` is too heavyweight and makes sharing native Python objects hard,
62
- but it does handle signals in each process individually
63
- - `asyncio` handles interrupting perfectly (every `try`/`except`/`finally`
64
- completes just fine, `KeyboardInterrupt` is raised in every async context),
65
- but async python is still (3.14) too weird and unsupported
66
- - `asyncio` effectively re-implements `subprocess` with a slightly different
67
- API, same with `asyncio.Transport` and derivatives reimplementing `socket`
68
- - 3rd party libraries like `requests` or `urllib3` don't support it, one needs
69
- to resort to spawning these in separate threads anyway
70
- - same with `os.*` functions and syscalls
71
- - every thing exposed via API needs to have 2 copies - async and non-async,
72
- making it unbearable
73
- - other stdlib bugs, ie. "large" reads returning BlockingIOError sometimes
74
-
75
- The approach chosen by this project was to use `threading.Thread`, and
76
- implement thread safety for classes and their functions that need it.
77
- For example:
78
-
79
- ```python
80
- class MachineReserver:
81
- def __init__(self):
82
- self.lock = threading.RLock()
83
- self.job = None
84
- self.proc = None
85
-
86
- def reserve(self, ...):
87
- try:
88
- ...
89
- job = schedule_new_job_on_external_service()
90
- with self.lock:
91
- self.job = job
92
- ...
93
- while not reserved(self.job):
94
- time.sleep(60)
95
- ...
96
- with self.lock:
97
- self.proc = subprocess.Popen(["ssh", f"{user}@{host}", ...)
98
- ...
99
- return machine
100
- except Exception:
101
- self.abort()
102
- raise
103
-
104
- def abort(self):
105
- with self.lock:
106
- if self.job:
107
- cancel_external_service(self.job)
108
- self.job = None
109
- if self.proc:
110
- self.proc.kill()
111
- self.proc = None
112
- ```
113
-
114
- Here, it is expected for `.reserve()` to be called in a long-running thread that
115
- provisions a new machine on some external service, waits for it to be installed
116
- and reserved, connects an ssh session to it and returns it back.
117
-
118
- But equally, `.abort()` can be called from an external thread and clean up any
119
- non-pythonic resources (external jobs, processes, temporary files, etc.) at
120
- which point **we don't care what happens to .reserve()**, it will probably fail
121
- with some exception, but doesn't do any harm.
122
-
123
- Here is where `daemon=True` threads come in handy - we can simply call `.abort()`
124
- from a `KeyboardInterrupt` (or `SIGTERM`) handle in the main thread, and just
125
- exit, automatically killing any leftover threads that are uselessly sleeping.
126
- (Realistically, we might want to spawn new threads to run many `.abort()`s in
127
- parallel, but the main thread can wait for those just fine.)
128
-
129
- It is not perfect, but it's probably the best Python can do.
130
-
131
- Note that races can still occur between a resource being reserved and written
132
- to `self.*` for `.abort()` to free, so resource de-allocation is not 100%
133
- guaranteed, but single-threaded interrupting has the same issue.
134
- Do have fallbacks (ie. max reserve times on the external service).
135
-
136
- Also note that `.reserve()` and `.abort()` could be also called by a context
137
- manager as `__enter__` and `__exit__`, ie. by a non-threaded caller (running
138
- everything in the main thread).
139
-
140
-
141
- ## Unsorted notes
142
-
143
- TODO: codestyle from contest
144
-
145
- ```
146
- - this is not tmt, the goal is to make a python toolbox *for* making runcontest
147
- style tools easily, not to replace those tools with tmt-style CLI syntax
148
-
149
- - the whole point is to make usecase-targeted easy-to-use tools that don't
150
- intimidate users with 1 KB long command line, and runcontest is a nice example
151
-
152
- - TL;DR - use a modular pythonic approach, not a modular CLI like tmt
153
-
154
-
155
- - Orchestrator with
156
- - add_provisioner(<class>, max_workers=1) # will instantiate <class> at most max_workers at a time
157
- - algo
158
- - for all provisioner classes, spawns classes*max_workers as new Threads
159
- - waits for any .reserve() to return
160
- - creates a new Thread for minitmt, gives it p.get_ssh() details
161
- - minitmt will
162
- - establish a SSHConn
163
- - install test deps, copy test repo over, prepare socket dir on SUT, etc.
164
- - run the test in the background as
165
- f=os.open('some/test/log', os.WRONLY); subprocess.Popen(..., stdout=f, stderr=f, stdin=subprocess.DEVNULL)
166
- - read/process Unix sock results in the foreground, non-blocking,
167
- probably calling some Orchestrator-provided function to store results persistently
168
- - regularly check Popen proc status, re-accept UNIX sock connection, etc., etc.
169
- - minitmt also has some Thread-independent way to .cancel(), killing the proc, closing SSHConn, etc.
170
-
171
- - while waiting for minitmt Threads to finish, to re-assign existing Provisioner instances
172
- to new minitmt Threads, .. Orchestrator uses some logic to select, which TestRun
173
- would be ideal to run next
174
- - TestRun probably has some "fitness" function that returns some priority number
175
- when given a Provisioner instance (?) ...
176
- - something from minitmt would also have access to the Provisioner instance
177
- - the idea is to allow some logic to set "hey I set up nested VM snapshot on this thing"
178
- on the Provisioner instance, and if another /hardening/oscap TestRun finds
179
- a Provisioner instance like that, it would return high priority
180
- - ...
181
- - similar to "fitness" like function, we need some "applicability" function
182
- - if TestRun is mixed to RHEL-9 && x86_64, we need it to return True
183
- for a Provisioner instance that provides RHEL-9 and x86_64, but False otherwise
184
-
185
- - basically Orchestrator has
186
- - .add_provisioner()
187
- - .run_test() # called with an exclusively-borrowed Provisioner instance
188
- - if Provisioner is_alive()==False after .run_test(), instantiate a new one from the same inst.__class__
189
- - if test failed and reruns > 0, try run_test() again (or maybe re-queue the test)
190
- - .output_result() # called by run_test() to persistently log a test result
191
- - .applicable() # return True if a passed TestRun is meant for a passed Platform (Provisioner?)
192
- - if no TestRun returns True, the Provisioner is .release()d because we don't need it anymore
193
- - .fitness() # return -inf / 0 / +inf with how much should a passed TestRun run on a Provisioner
194
- - MAYBE combine applicable() and fitness() into one function, next_test() ?
195
- - given the free Provisioner and a list of TestRuns, select which should run next on the Provisioner
196
- - if none is chosen, .release() the Provisioner without replacement, continue
197
- ```
atex-0.8.dist-info/RECORD DELETED
@@ -1,37 +0,0 @@
1
- atex/__init__.py,sha256=LdX67gprtHYeAkjLhFPKzpc7ECv2rHxUbHKDGbGXO1c,517
2
- atex/fmf.py,sha256=ofbrJx2362qHAxERS-WulK4TMpbp0C4HQ-Js917Ll9w,7871
3
- atex/cli/__init__.py,sha256=erHv68SsybRbdgJ60013y9jVqY1ec-cb9T9ThPCJ_HY,2408
4
- atex/cli/fmf.py,sha256=5DbA-3rfbFZ41fJ5z7Tiz5FmuZhXNC7gRAQfIGX7pXc,2516
5
- atex/cli/testingfarm.py,sha256=wdN26TE9jZ0ozet-JBQQgIcRi0WIV3u_i-7_YYi_SUg,7248
6
- atex/connection/__init__.py,sha256=xFwGOvlFez1lIt1AD6WXgEEIbsF22pSpQFv41GEAGAI,3798
7
- atex/connection/ssh.py,sha256=vrrSfVdQoz5kWiZbiPuM8KGneMl2Tlb0VeJIHTFSSYs,13626
8
- atex/executor/__init__.py,sha256=XCfhi7QDELjey7N1uzhMjc46Kp1Jsd5bOCf52I27SCE,85
9
- atex/executor/duration.py,sha256=x06sItKOZi6XA8KszQwZGpIb1Z_L-HWqIwZKo2SDo0s,1759
10
- atex/executor/executor.py,sha256=QYYSlEfBZIm95NhM1gwd2ROeshSAavYu2DP_4TTHlQs,14770
11
- atex/executor/reporter.py,sha256=nW_Uls3R4Ev80a2ZNJl3nxAYrcYhXk5Cy9nAUMlYPrc,3326
12
- atex/executor/scripts.py,sha256=yE4Lbfu-TPkBcB5t15-t-tF79H8pBJWbWP6MKRSvKsw,5356
13
- atex/executor/testcontrol.py,sha256=-rfihfE6kryIGurFrHBPSS8ANaIJkzX-zfpOO8To-9o,12204
14
- atex/orchestrator/__init__.py,sha256=eF-6ix5rFEu85fBFzgSdTYau7bNTkIQndAU7QqeI-FA,105
15
- atex/orchestrator/aggregator.py,sha256=5-8nHVeW6kwImoEYOsQqsx6UBdbKc5xuj6qlg7dtOF8,3642
16
- atex/orchestrator/orchestrator.py,sha256=tQu_d8_9y3rOLHskb694NJKNvxplQWAZ2R452Sy3AXw,12056
17
- atex/provision/__init__.py,sha256=2d_hRVPxXF5BVbQ_Gn1OR-F2xuqRn8O0yyVbvSrtTIg,4043
18
- atex/provision/libvirt/VM_PROVISION,sha256=7pkZ-ozgTyK4qNGC-E-HUznr4IhbosWSASbB72Gknl8,2664
19
- atex/provision/libvirt/__init__.py,sha256=mAkGtciZsXdR9MVVrjm3OWNXZqTs_33-J1qAszFA0k4,768
20
- atex/provision/libvirt/setup-libvirt.sh,sha256=CXrEFdrj8CSHXQZCd2RWuRvTmw7QYFTVhZeLuhhXooI,1855
21
- atex/provision/podman/README,sha256=kgP3vcTfWW9gcQzmXnyucjgWbqjNqm_ZM--pnqNTXRg,1345
22
- atex/provision/podman/host_container.sh,sha256=buCNz0BlsHY5I64sMSTGQHkvzEK0aeIhpGJXWCQVMXk,2283
23
- atex/provision/testingfarm/__init__.py,sha256=kZncgLGdRCR4FMaRQr2GTwJ8vjlA-24ri8JO2ueZJuw,113
24
- atex/provision/testingfarm/api.py,sha256=jiEJhYxMTzRihayceHcnDnGKNZJisYWn2o_TAdCI2Xo,19943
25
- atex/provision/testingfarm/testingfarm.py,sha256=wp8W3bwOmQdO-UUOdqu_JLtOZTGaNg-wERFfLySwZmI,8587
26
- atex/util/__init__.py,sha256=cWHFbtQ4mDlKe6lXyPDWRmWJOTcHDGfVuW_-GYa8hB0,1473
27
- atex/util/dedent.py,sha256=SEuJMtLzqz3dQ7g7qyZzEJ9VYynVlk52tQCJY-FveXo,603
28
- atex/util/log.py,sha256=KZkuw4jl8YTUOHZ4wNBrfDeg16VpLa82-IZYFHfqwgk,1995
29
- atex/util/path.py,sha256=x-kXqiWCVodfZWbEwtC5A8LFvutpDIPYv2m0boZSlXU,504
30
- atex/util/ssh_keygen.py,sha256=9yuSl2yBV7pG3Qfsf9tossVC00nbIUrAeLdbwTykpjk,384
31
- atex/util/subprocess.py,sha256=IQT9QHe2kMaaO_XPSry-DwObYstGsq6_QdwdbhYDjko,1826
32
- atex/util/threads.py,sha256=bezDIEIMcQinmG7f5E2K6_mHJQOlwx7W3I9CKkCYAYA,1830
33
- atex-0.8.dist-info/METADATA,sha256=dvXW146ZvIfyzqPqGbKmhTNScLTZM7C5K0FLrGNGIJ0,8981
34
- atex-0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- atex-0.8.dist-info/entry_points.txt,sha256=pLqJdcfeyQTgup2h6dWb6SvkHhtOl-W5Eg9zV8moK0o,39
36
- atex-0.8.dist-info/licenses/COPYING.txt,sha256=oEuj51jdmbXcCUy7pZ-KE0BNcJTR1okudRp5zQ0yWnU,670
37
- atex-0.8.dist-info/RECORD,,
File without changes
File without changes
File without changes