os-tester 1.1.0.dev8__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.
- os_tester/__init__.py +0 -0
- os_tester/debug_plot.py +77 -0
- os_tester/stages.py +127 -0
- os_tester/vm.py +496 -0
- os_tester-1.1.0.dev8.dist-info/METADATA +293 -0
- os_tester-1.1.0.dev8.dist-info/RECORD +9 -0
- os_tester-1.1.0.dev8.dist-info/WHEEL +5 -0
- os_tester-1.1.0.dev8.dist-info/licenses/LICENSE +674 -0
- os_tester-1.1.0.dev8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: os-tester
|
|
3
|
+
Version: 1.1.0.dev8
|
|
4
|
+
Summary: A Python pip package to automate testing of whole operating systems with an image recognition based approach and libvirt (qemu). Inspired by openQA.
|
|
5
|
+
Author-email: Fabian Sauter <fabian.sauter+pip@apsensing.com>
|
|
6
|
+
Maintainer-email: Fabian Sauter <fabian.sauter+pip@apsensing.com>
|
|
7
|
+
License: GNU General Public License v3 (GPLv3)
|
|
8
|
+
Project-URL: Homepage, https://github.com/AP-Sensing/os-tester
|
|
9
|
+
Project-URL: Repository, https://github.com/AP-Sensing/os-tester
|
|
10
|
+
Project-URL: Issues, https://github.com/AP-Sensing/os-tester/issues
|
|
11
|
+
Keywords: testing,os,qemu,libvirt
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: Implementation
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Requires-Python: >=3.7
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: scikit-image
|
|
22
|
+
Requires-Dist: opencv-python-headless
|
|
23
|
+
Requires-Dist: libvirt-python
|
|
24
|
+
Requires-Dist: PyYAML
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: matplotlib
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# OS Tester
|
|
30
|
+
A Python pip package to automate testing of whole operating systems with an image recognition based approach and libvirt (qemu). Inspired by openQA.
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from os_tester.vm import vm
|
|
38
|
+
from os_tester.stages import stages
|
|
39
|
+
import libvirt
|
|
40
|
+
|
|
41
|
+
# SELinux Policy for allowing Qemu to access image files:
|
|
42
|
+
# ausearch -c 'qemu-system-x86' --raw | audit2allow -M my-qemusystemx86
|
|
43
|
+
# semodule -X 300 -i my-qemusystemx86.pp
|
|
44
|
+
|
|
45
|
+
# NVME: http://blog.frankenmichl.de/2018/02/13/add-nvme-device-to-vm/
|
|
46
|
+
# dd if=/dev/zero of=/tmp/test_vm_1.img bs=1M count=8192
|
|
47
|
+
# Or:
|
|
48
|
+
# qemu-img create -f qcow2 /tmp/test_vm_1.qcow2 8G
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_vm_xml(name: str, title: str, uuid: str, isoPath: str, vmImagePath: str) -> str:
|
|
52
|
+
ramGiB: int = 2
|
|
53
|
+
numCpus: int = 2
|
|
54
|
+
|
|
55
|
+
return f"""
|
|
56
|
+
<domain type="kvm" xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
|
|
57
|
+
<name>{name}</name>
|
|
58
|
+
<uuid>{uuid}</uuid>
|
|
59
|
+
<title>{title}</title>
|
|
60
|
+
<memory unit="GiB">{ramGiB}</memory>
|
|
61
|
+
<currentMemory unit="GiB">{ramGiB}</currentMemory>
|
|
62
|
+
<vcpu placement="static">{numCpus}</vcpu>
|
|
63
|
+
<os firmware='efi'>
|
|
64
|
+
<!--To get a list of all machines: qemu-system-x86_64 -machine help-->
|
|
65
|
+
<type arch="x86_64" machine="q35">hvm</type>
|
|
66
|
+
<bootmenu enable="yes"/>
|
|
67
|
+
<boot dev="hd"/>
|
|
68
|
+
<boot dev="cdrom"/>
|
|
69
|
+
</os>
|
|
70
|
+
<features>
|
|
71
|
+
<acpi/>
|
|
72
|
+
<apic/>
|
|
73
|
+
</features>
|
|
74
|
+
<clock offset="localtime">
|
|
75
|
+
<timer name="rtc" tickpolicy="catchup"/>
|
|
76
|
+
<timer name="pit" tickpolicy="delay"/>
|
|
77
|
+
<timer name="hpet" present="no"/>
|
|
78
|
+
</clock>
|
|
79
|
+
<on_poweroff>destroy</on_poweroff>
|
|
80
|
+
<on_reboot>restart</on_reboot>
|
|
81
|
+
<on_crash>restart</on_crash>
|
|
82
|
+
<pm>
|
|
83
|
+
<suspend-to-mem enabled="no"/>
|
|
84
|
+
<suspend-to-disk enabled="no"/>
|
|
85
|
+
</pm>
|
|
86
|
+
<devices>
|
|
87
|
+
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
|
88
|
+
<controller type='pci' index='0' model='pcie-root'/>
|
|
89
|
+
<controller type='pci' index='1' model='pcie-root-port'>
|
|
90
|
+
<model name='pcie-root-port'/>
|
|
91
|
+
<target chassis='1' port='0x10'/>
|
|
92
|
+
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
|
|
93
|
+
</controller>
|
|
94
|
+
<disk type="file" device="cdrom">
|
|
95
|
+
<driver name="qemu" type="raw"/>
|
|
96
|
+
<source file="{isoPath}" startupPolicy="mandatory"/>
|
|
97
|
+
<target dev="hdc" bus="sata"/>
|
|
98
|
+
<readonly/>
|
|
99
|
+
<address type="drive" controller="0" bus="0" target="0" unit="2"/>
|
|
100
|
+
</disk>
|
|
101
|
+
<interface type="user">
|
|
102
|
+
<mac address="52:54:00:8d:ce:97"/>
|
|
103
|
+
<model type="virtio"/>
|
|
104
|
+
<address type="pci" domain="0x0000" bus="0x01" slot="0x00" function="0x00"/>
|
|
105
|
+
</interface>
|
|
106
|
+
<serial type="pty">
|
|
107
|
+
<target type="isa-serial" port="0">
|
|
108
|
+
<model name="isa-serial"/>
|
|
109
|
+
</target>
|
|
110
|
+
</serial>
|
|
111
|
+
<console type="pty">
|
|
112
|
+
<target type="serial" port="0"/>
|
|
113
|
+
</console>
|
|
114
|
+
<input type="tablet" bus="usb">
|
|
115
|
+
<address type="usb" bus="0" port="2"/>
|
|
116
|
+
</input>
|
|
117
|
+
<input type="mouse" bus="ps2"/>
|
|
118
|
+
<input type="keyboard" bus="ps2"/>
|
|
119
|
+
<memballoon model="virtio">
|
|
120
|
+
<address type="pci" domain="0x0000" bus="0x05" slot="0x00" function="0x0"/>
|
|
121
|
+
</memballoon>
|
|
122
|
+
</devices>
|
|
123
|
+
<qemu:commandline>
|
|
124
|
+
<qemu:arg value='-drive'/>
|
|
125
|
+
<qemu:arg value='file={vmImagePath},format=qcow2,if=none,id=nvdisk1,media=disk'/>
|
|
126
|
+
<qemu:arg value='-device'/>
|
|
127
|
+
<qemu:arg value='nvme,bootindex=1,drive=nvdisk1,serial=1234,id=nvme0,bus=pcie.0,addr=0x04'/>
|
|
128
|
+
</qemu:commandline>
|
|
129
|
+
</domain>
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
# Connect to qemu
|
|
134
|
+
conn: libvirt.virConnect = libvirt.open("qemu:///system")
|
|
135
|
+
|
|
136
|
+
uuid: str = "1e6cae9f-41d7-4fca-8033-fbd538a65173" # Replace with your (random?) UUID
|
|
137
|
+
vmObj: vm = vm(conn, uuid, debugPlt=False)
|
|
138
|
+
|
|
139
|
+
# Delete eventually existing VMs
|
|
140
|
+
if vmObj.try_load():
|
|
141
|
+
print(f"Deleting existing VM for UUID '{uuid}'...")
|
|
142
|
+
vmObj.destroy()
|
|
143
|
+
exit(0)
|
|
144
|
+
print(f"VM destroyed.")
|
|
145
|
+
else:
|
|
146
|
+
print(f"No existing VM found for UUID '{uuid}'.")
|
|
147
|
+
|
|
148
|
+
# Create and start a new VM
|
|
149
|
+
vmXml: str = get_vm_xml(
|
|
150
|
+
"test_vm_1",
|
|
151
|
+
"Test_VM_1",
|
|
152
|
+
uuid,
|
|
153
|
+
"<PATH_TO_THE_ISO_FILE_TO_BOOT_FROM>",
|
|
154
|
+
"test_vm_1.qcow2", # qemu-img create -f qcow2 test_vm_1.qcow2 8G
|
|
155
|
+
)
|
|
156
|
+
vmObj.create(vmXml)
|
|
157
|
+
|
|
158
|
+
# Load stages automation.
|
|
159
|
+
# We expect the `stages.yml` and referenced files inside the stages directory.
|
|
160
|
+
basePath: str = "stages"
|
|
161
|
+
stagesObj: stages = stages(basePath)
|
|
162
|
+
print(stagesObj)
|
|
163
|
+
|
|
164
|
+
vmObj.run_stages(stagesObj)
|
|
165
|
+
|
|
166
|
+
print("All stages done. Exiting...")
|
|
167
|
+
conn.close()
|
|
168
|
+
exit(0)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Stages
|
|
172
|
+
Stages are defined as a YAML file. The schema for it is available under [`stages_schema.yml`](stages_schema.yml).
|
|
173
|
+
The following shows an example of such a file:
|
|
174
|
+
```yaml
|
|
175
|
+
stages:
|
|
176
|
+
- stage: Bootloader Selection
|
|
177
|
+
timeout_s: 15
|
|
178
|
+
paths:
|
|
179
|
+
- path:
|
|
180
|
+
checks:
|
|
181
|
+
- file:
|
|
182
|
+
path: 0.png
|
|
183
|
+
mse_leq: 0.1
|
|
184
|
+
ssim_geq: 0.99
|
|
185
|
+
actions:
|
|
186
|
+
- keyboard_key:
|
|
187
|
+
value: up
|
|
188
|
+
duration_s: 0.25
|
|
189
|
+
- keyboard_key:
|
|
190
|
+
value: ret
|
|
191
|
+
duration_s: 0.25
|
|
192
|
+
nextStage: Installation Started
|
|
193
|
+
- path:
|
|
194
|
+
checks:
|
|
195
|
+
- file:
|
|
196
|
+
path: 0_1.png
|
|
197
|
+
mse_leq: 0.1
|
|
198
|
+
ssim_geq: 0.99
|
|
199
|
+
actions:
|
|
200
|
+
- keyboard_key:
|
|
201
|
+
value: up
|
|
202
|
+
duration_s: 0.25
|
|
203
|
+
- keyboard_key:
|
|
204
|
+
value: up
|
|
205
|
+
duration_s: 0.25
|
|
206
|
+
- keyboard_key:
|
|
207
|
+
value: ret
|
|
208
|
+
duration_s: 0.25
|
|
209
|
+
nextStage: Installation Started
|
|
210
|
+
|
|
211
|
+
- stage: Installation Started
|
|
212
|
+
timeout_s: 600
|
|
213
|
+
paths:
|
|
214
|
+
- path:
|
|
215
|
+
checks:
|
|
216
|
+
- file:
|
|
217
|
+
path: 1.png
|
|
218
|
+
mse_leq: 0.1
|
|
219
|
+
ssim_geq: 0.99
|
|
220
|
+
actions:
|
|
221
|
+
- keyboard_key:
|
|
222
|
+
value: up
|
|
223
|
+
duration_s: 0.25
|
|
224
|
+
nextStage: Installation Complete
|
|
225
|
+
|
|
226
|
+
- stage: Installation Complete
|
|
227
|
+
timeout_s: 600
|
|
228
|
+
paths:
|
|
229
|
+
- path:
|
|
230
|
+
checks:
|
|
231
|
+
- file:
|
|
232
|
+
path: 2.png
|
|
233
|
+
mse_leq: 0.1
|
|
234
|
+
ssim_geq: 0.99
|
|
235
|
+
actions:
|
|
236
|
+
- keyboard_key:
|
|
237
|
+
value: tab
|
|
238
|
+
duration_s: 0.25
|
|
239
|
+
- keyboard_key:
|
|
240
|
+
value: tab
|
|
241
|
+
duration_s: 0.25
|
|
242
|
+
- keyboard_key:
|
|
243
|
+
value: ret
|
|
244
|
+
duration_s: 0.25
|
|
245
|
+
nextStage: Enter LUKS Password
|
|
246
|
+
|
|
247
|
+
- stage: Enter LUKS Password
|
|
248
|
+
timeout_s: 600
|
|
249
|
+
paths:
|
|
250
|
+
- path:
|
|
251
|
+
checks:
|
|
252
|
+
- file:
|
|
253
|
+
path: 3.png
|
|
254
|
+
mse_leq: 0.1
|
|
255
|
+
ssim_geq: 0.99
|
|
256
|
+
actions:
|
|
257
|
+
- keyboard_text:
|
|
258
|
+
value: something
|
|
259
|
+
duration_s: 0.25
|
|
260
|
+
- keyboard_key:
|
|
261
|
+
value: ret
|
|
262
|
+
duration_s: 0.25
|
|
263
|
+
nextStage: None
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Building the pip-Package
|
|
268
|
+
|
|
269
|
+
To build the pip package run:
|
|
270
|
+
```bash
|
|
271
|
+
rm -rf dist/
|
|
272
|
+
python3 -m build
|
|
273
|
+
```
|
|
274
|
+
The output is then available inside the `dist/` directory.
|
|
275
|
+
|
|
276
|
+
## Upload
|
|
277
|
+
```bash
|
|
278
|
+
twine upload dist/*
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## pre-commit
|
|
282
|
+
Before committing you have to run `pre-commit` to check for linting and type errors.
|
|
283
|
+
For this first install `pre-commit`.
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
dnf install pre-commit
|
|
287
|
+
pre-commit install
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
To run `pre-commit` manually run:
|
|
291
|
+
```bash
|
|
292
|
+
pre-commit run --all-files
|
|
293
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
os_tester/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
os_tester/debug_plot.py,sha256=czTzWOanbqB46Q1_KDPT0u36dnGQ_yBNv7f5Tr1Hxhs,2706
|
|
3
|
+
os_tester/stages.py,sha256=6y19TfzDhlGayoREXSvEJohQlMZJcIvRxTvoZZL7x0I,3778
|
|
4
|
+
os_tester/vm.py,sha256=7X4nq0_IYdlVqOonNvncpF4rFIk-OiYrZ7N1FTD_oZE,17225
|
|
5
|
+
os_tester-1.1.0.dev8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
6
|
+
os_tester-1.1.0.dev8.dist-info/METADATA,sha256=zmcFiIIuGfzpA2DWYb7A8v9YFlB7XoFWsy8NWQljEEE,8556
|
|
7
|
+
os_tester-1.1.0.dev8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
os_tester-1.1.0.dev8.dist-info/top_level.txt,sha256=pgV5qkt2c8hLJbwyn1zZCG1Vq0YgJN25QoubK25YyDQ,10
|
|
9
|
+
os_tester-1.1.0.dev8.dist-info/RECORD,,
|