1717# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
1818
1919import argparse
20+ import asyncio
2021import os
2122import subprocess
22- import sys
2323from collections .abc import Sequence
24+ from pathlib import Path
2425
25- from lib .constants import BOTS_DIR , TEST_DIR
26- from machine import testvm
26+ from lib import testthing
27+ from lib .constants import BOTS_DIR , IMAGES_DIR , MACHINE_DIR , TEST_DIR
28+ from lib .testmap import get_test_image
2729
2830opt_quick : bool = False
2931opt_verbose : bool = False
30- opt_build_options : str = ''
32+ opt_build_options : str = ""
3133stdout_disposition : int | None = None
3234
3335
3436def prepare_install_image (base_image : str , install_image : str , resize : str | None , fresh : bool ) -> str :
3537 """Create the necessary layered image for the build/install"""
3638
3739 if "/" not in base_image :
38- base_image = os .path .join (testvm . IMAGES_DIR , base_image )
40+ base_image = os .path .join (IMAGES_DIR , base_image )
3941 if "/" not in install_image :
4042 install_image = os .path .join (os .path .join (TEST_DIR , "images" ), os .path .basename (install_image ))
4143
@@ -54,8 +56,16 @@ def prepare_install_image(base_image: str, install_image: str, resize: str | Non
5456 install_image_dir = os .path .dirname (install_image )
5557 os .makedirs (install_image_dir , exist_ok = True )
5658 base_image = os .path .realpath (base_image )
57- subprocess .check_call (["qemu-img" , "create" , "-q" , "-f" , "qcow2" ,
58- "-o" , f"backing_file={ base_image } ,backing_fmt=qcow2" , qcow2_image ])
59+ subprocess .check_call ([
60+ "qemu-img" ,
61+ "create" ,
62+ "-q" ,
63+ "-f" ,
64+ "qcow2" ,
65+ "-o" ,
66+ f"backing_file={ base_image } ,backing_fmt=qcow2" ,
67+ qcow2_image ,
68+ ])
5969 if os .path .lexists (install_image ):
6070 os .unlink (install_image )
6171 os .symlink (os .path .basename (qcow2_image ), install_image )
@@ -70,76 +80,84 @@ class ActionBase(argparse.Action):
7080 """Keep an ordered list of actions"""
7181
7282 @staticmethod
73- def execute (machine_instance : testvm . Machine , argument : str ) -> None :
83+ async def execute (machine_instance : testthing . VirtualMachine , argument : str , / ) -> None :
7484 raise NotImplementedError
7585
7686 def __call__ (
7787 self ,
7888 parser : argparse .ArgumentParser ,
7989 namespace : argparse .Namespace ,
8090 value : str | Sequence [str ] | None ,
81- option_string : object = None
91+ option_string : object = None ,
8292 ) -> None :
8393 getattr (namespace , self .dest ).append ((self .execute , value ))
8494
8595
8696class InstallAction (ActionBase ):
8797 """Install local rpm or distro package"""
98+
8899 @staticmethod
89- def execute (machine_instance : testvm . Machine , package : str ) -> None :
100+ async def execute (machine_instance : testthing . VirtualMachine , package : str ) -> None :
90101 # If we have a '/' in the package name, or if a file with that name
91102 # exists in the current directory, then assume that this is a package
92103 # we're uploading from the host.
93- if '/' in package or os .path .isfile (package ):
104+ if "/" in package or os .path .isfile (package ):
94105 dest = "/var/tmp/" + os .path .basename (package )
95- machine_instance .upload ([ os .path .abspath (package )], dest )
106+ await machine_instance .scp ( os .path .abspath (package ), f"vm: { dest } " )
96107 package = dest
97108
98109 # requesting install of Python wheel?
99- if package .endswith ('.whl' ):
100- machine_instance .execute (f"python3 -m pip install --no-index --prefix=/usr/local { package } " , timeout = 120 )
110+ if package .endswith (".whl" ):
111+ await asyncio .wait_for (
112+ machine_instance .execute (f"python3 -m pip install --no-index --prefix=/usr/local { package } " ),
113+ timeout = 120 ,
114+ )
101115 return
102116
103117 # this will fail if neither is available -- exception is clear enough, this is a developer tool
104- out = machine_instance .execute ("which dnf || which yum || which apt-get" )
105- if ' dnf' in out :
118+ out = await machine_instance .execute ("which dnf || which yum || which apt-get || which pacman " )
119+ if " dnf" in out :
106120 install_command = "dnf install -y"
107- elif ' yum' in out :
121+ elif " yum" in out :
108122 install_command = "yum --setopt=skip_missing_names_on_install=False -y install"
109- else :
123+ elif "apt-get" in out :
110124 install_command = "apt-get install -y"
125+ elif "pacman" in out :
126+ install_command = "pacman -S --noconfirm"
127+ else :
128+ raise NotImplementedError (f"unknown build platform: { out } " )
111129
112- machine_instance .execute (f"{ install_command } { package } " , timeout = 1800 )
130+ await machine_instance .execute (f"{ install_command } { package } " )
113131
114132
115133class BuildAction (ActionBase ):
116134 """Build and install distribution package(s) from dist tarball or source RPM"""
117135
118136 @staticmethod
119- def execute (machine_instance : testvm . Machine , source : str ) -> None :
137+ async def execute (machine_instance : testthing . VirtualMachine , source : str ) -> None :
120138 # upload the tarball or srpm
121139 sourcename = os .path .basename (source )
122140
123141 vm_source = os .path .join ("/var/tmp" , sourcename )
124- machine_instance .upload ([ source ], vm_source , relative_dir = ". " )
142+ await machine_instance .scp ( source , f"vm: { vm_source } " )
125143
126144 # this will fail if neither is available -- exception is clear enough, this is a developer tool
127- out = machine_instance .execute ("(which pbuilder || which mock || which pacman) 2>/dev/null" )
128- if ' pbuilder' in out :
129- BuildAction .build_deb (machine_instance , vm_source )
130- elif ' mock' in out :
131- BuildAction .build_rpm (machine_instance , vm_source )
132- elif ' pacman' in out :
133- BuildAction .build_arch (machine_instance , vm_source )
145+ out = await machine_instance .execute ("(which pbuilder || which mock || which pacman) 2>/dev/null" )
146+ if " pbuilder" in out :
147+ await BuildAction .build_deb (machine_instance , vm_source )
148+ elif " mock" in out :
149+ await BuildAction .build_rpm (machine_instance , vm_source )
150+ elif " pacman" in out :
151+ await BuildAction .build_arch (machine_instance , vm_source )
134152 else :
135153 raise NotImplementedError (f"unknown build platform: { out } " )
136154
137155 @staticmethod
138- def build_deb (machine : testvm . Machine , vm_source : str ) -> None :
139- build_opts = ' nocheck' if opt_quick else ''
156+ async def build_deb (machine : testthing . VirtualMachine , vm_source : str ) -> None :
157+ build_opts = " nocheck" if opt_quick else ""
140158
141159 # build source packge
142- machine .execute (f"""
160+ await machine .execute (f"""
143161 set -eu
144162 rm -rf /var/tmp/build
145163 mkdir -p /var/tmp/build
@@ -156,55 +174,64 @@ class BuildAction(ActionBase):
156174 dpkg-buildpackage -S -us -uc -nc""" )
157175
158176 # build binary packages
159- machine .execute (f"cd /var/tmp/build; DEB_BUILD_OPTIONS='{ build_opts } ' pbuilder build --buildresult . "
160- f"{ opt_build_options } *.dsc" , timeout = 1800 , stdout = stdout_disposition )
177+ await machine .execute (
178+ f"cd /var/tmp/build; DEB_BUILD_OPTIONS='{ build_opts } ' pbuilder build --buildresult . "
179+ f"{ opt_build_options } *.dsc" ,
180+ stdout = stdout_disposition ,
181+ )
161182
162183 # install packages
163- machine .execute ("dpkg -i /var/tmp/build/*.deb" )
184+ await machine .execute ("dpkg -i /var/tmp/build/*.deb" )
164185
165186 @staticmethod
166- def build_rpm (machine : testvm . Machine , vm_source : str ) -> None :
167- mock_opts = ''
187+ async def build_rpm (machine : testthing . VirtualMachine , vm_source : str ) -> None :
188+ mock_opts = ""
168189 if opt_verbose :
169- mock_opts += ' --verbose'
190+ mock_opts += " --verbose"
170191 if opt_quick :
171- mock_opts += ' --nocheck'
192+ mock_opts += " --nocheck"
172193 if opt_build_options :
173- mock_opts += ' ' + opt_build_options
194+ mock_opts += " " + opt_build_options
174195
175196 # build source package, unless this is running against an srpm already
176197 if vm_source .endswith (".src.rpm" ):
177198 srpm = vm_source
178199 else :
179- machine .execute (f"""
200+ await machine .execute (f"""
180201 set -eu
181202 rm -rf /var/tmp/build
182203 su builder -c 'rpmbuild --define "_topdir /var/tmp/build" -ts "{ vm_source } "'
183204 """ )
184205 srpm = "/var/tmp/build/SRPMS/*.src.rpm"
185206
186207 # HACK: mock in openSUSE must be called through sudo (but still originally as builder)
187- if "opensuse" in machine .execute ("cat /etc/os-release" ):
208+ if "opensuse" in await machine .execute ("cat /etc/os-release" ):
188209 mock_sudo = "sudo"
189210 else :
190211 mock_sudo = ""
191212
192213 # build binary RPMs from srpm; disable all repositorys as mock insists on
193214 # calling `dnf builddep`, which insists on a cache; our test VMs don't have a cache,
194215 # as the mock is offline and pre-installed
195- machine .execute (f"su builder -c '{ mock_sudo } mock --no-clean --no-cleanup-after --disablerepo=* "
196- f"--offline --resultdir /var/tmp/build { mock_opts } --rebuild { srpm } '" ,
197- timeout = 1800 , stdout = stdout_disposition )
216+ await machine .execute (
217+ f"su builder -c '{ mock_sudo } mock --no-clean --no-cleanup-after --disablerepo=* "
218+ f"--offline --resultdir /var/tmp/build { mock_opts } --rebuild { srpm } '" ,
219+ stdout = stdout_disposition ,
220+ )
198221
199222 # install RPMs
200- machine .execute ('packages=$(find /var/tmp/build -name "*.rpm" -not -name "*.src.rpm"); '
201- f'rpm -U --force --verbose { "--nodigest --nosignature" if opt_quick else "" } $packages' )
223+ await machine .execute (
224+ 'packages=$(find /var/tmp/build -name "*.rpm" -not -name "*.src.rpm"); '
225+ f"rpm -U --force --verbose { '--nodigest --nosignature' if opt_quick else '' } $packages"
226+ )
202227
203228 @staticmethod
204- def build_arch (machine : testvm . Machine , vm_source : str ) -> None :
229+ async def build_arch (machine : testthing . VirtualMachine , vm_source : str ) -> None :
205230 # unpack source tree's arch packaging directory (PKGBUILD refers to some files)
206231 # and set PKGBUILD variables
207- machine .write ("/var/tmp/mkbuild.sh" , f"""#!/bin/sh
232+ await machine .write (
233+ "/var/tmp/mkbuild.sh" ,
234+ f"""#!/bin/sh
208235 set -eu
209236 rm -rf /var/tmp/build
210237 mkdir -p /var/tmp/build
@@ -215,52 +242,49 @@ class BuildAction(ActionBase):
215242 cp "$archdir"/* .
216243 # tarball must be in same directory as PKGBUILD
217244 cp '{ vm_source } ' /var/tmp/build/
218- """ , perm = "755" )
219- machine .execute (f"su builder { opt_build_options } /var/tmp/mkbuild.sh" )
245+ """ ,
246+ perm = "755" ,
247+ )
248+ await machine .execute (f"su builder { opt_build_options } /var/tmp/mkbuild.sh" )
220249
221250 # build binaries
222- machine .execute ("cd /var/tmp/build; makechrootpkg -r /var/lib/archbuild/cockpit -U builder" ,
223- timeout = 1800 , stdout = stdout_disposition )
251+ await machine .execute (
252+ "cd /var/tmp/build; makechrootpkg -r /var/lib/archbuild/cockpit -U builder" ,
253+ stdout = stdout_disposition ,
254+ )
224255
225256 # install packages
226- machine .execute ("pacman -U --noconfirm /var/tmp/build/*.pkg.tar.zst" )
257+ await machine .execute ("pacman -U --noconfirm /var/tmp/build/*.pkg.tar.zst" )
227258
228259
229260class RunCommandAction (ActionBase ):
230261 @staticmethod
231- def execute (machine_instance : testvm .Machine , command : str ) -> None :
232- try :
233- machine_instance .execute (command , timeout = 1800 )
234- except subprocess .CalledProcessError as e :
235- sys .stderr .write ("%s\n " % e )
236- sys .exit (e .returncode )
262+ async def execute (machine_instance : testthing .VirtualMachine , command : str ) -> None :
263+ await machine_instance .execute (command )
237264
238265
239266class ScriptAction (ActionBase ):
240267 @staticmethod
241- def execute (machine_instance : testvm . Machine , script : str ) -> None :
268+ async def execute (machine_instance : testthing . VirtualMachine , script : str ) -> None :
242269 uploadpath = "/var/tmp/" + os .path .basename (script )
243- machine_instance .upload ([os .path .abspath (script )], uploadpath )
244- machine_instance .execute ("chmod a+x %s" % uploadpath )
245- try :
246- machine_instance .execute (uploadpath , timeout = 1800 )
247- except subprocess .CalledProcessError as e :
248- sys .stderr .write ("%s\n " % e )
249- sys .exit (e .returncode )
270+ await machine_instance .scp (os .path .abspath (script ), f"vm:{ uploadpath } " )
271+ await machine_instance .execute ("chmod a+x %s" % uploadpath )
272+ await machine_instance .execute (uploadpath )
250273
251274
252275class UploadAction (ActionBase ):
253276 @staticmethod
254- def execute (machine_instance : testvm . Machine , srcdest : str ) -> None :
277+ async def execute (machine_instance : testthing . VirtualMachine , srcdest : str ) -> None :
255278 src , dest = srcdest .split (":" )
256279 abssrc = os .path .abspath (src )
257280 # preserve trailing / for rsync compatibility
258- if src .endswith ('/' ):
259- abssrc += '/'
260- machine_instance .upload ([ abssrc ], dest )
281+ if src .endswith ("/" ):
282+ abssrc += "/"
283+ await machine_instance .scp ( abssrc , f"vm: { dest } " )
261284
262285
263- def main () -> None :
286+ async def main () -> None :
287+ # fmt: off
264288 parser = argparse .ArgumentParser (
265289 description = ('Run command inside or install packages into a Cockpit virtual machine. '
266290 'All actions can be specified multiple times and run in the given order.' ))
@@ -284,7 +308,7 @@ def main() -> None:
284308 help = "Additional options for mock/pbuilder/arch builder" )
285309 parser .add_argument ('--resize' , help = "Resize the image. Size in bytes with using K, M, or G suffix." )
286310 parser .add_argument ('-n' , '--no-network' , action = 'store_true' , help = 'Do not connect the machine to the Internet' )
287- parser .add_argument ('--cpus' , type = int , default = None ,
311+ parser .add_argument ('--cpus' , type = int , default = 2 ,
288312 help = "Number of CPUs for the virtual machine" )
289313 parser .add_argument ('--memory-mb' , type = int , default = 2048 ,
290314 help = "RAM size for the virtual machine" )
@@ -294,6 +318,7 @@ def main() -> None:
294318 help = 'Disable tests during package build with --build' )
295319 parser .add_argument ('image' , help = 'The image to use (destination name when using --base-image)' )
296320 parser .add_argument ('--sit' , action = 'store_true' , help = 'Sit and wait if any VM action fails' )
321+ # fmt: on
297322 args = parser .parse_args ()
298323
299324 if not args .actions and not args .resize :
@@ -302,7 +327,7 @@ def main() -> None:
302327 if not args .base_image :
303328 args .base_image = os .path .basename (args .image )
304329
305- args .base_image = testvm . get_test_image (args .base_image )
330+ args .base_image = get_test_image (args .base_image )
306331
307332 global opt_quick , opt_verbose , opt_build_options , stdout_disposition
308333 opt_quick = args .quick
@@ -311,30 +336,27 @@ def main() -> None:
311336 if not args .verbose :
312337 stdout_disposition = subprocess .DEVNULL
313338
314- if '/' not in args .base_image :
339+ if "/" not in args .base_image :
315340 subprocess .check_call ([os .path .join (BOTS_DIR , "image-download" ), args .base_image ])
316- network = testvm .VirtNetwork (0 , image = args .base_image )
317- machine = testvm .VirtMachine (maintain = True ,
318- verbose = args .verbose ,
319- networking = network .host (restrict = args .no_network ),
320- image = prepare_install_image (args .base_image , args .image , args .resize , args .fresh ),
321- cpus = args .cpus ,
322- memory_mb = args .memory_mb )
323- machine .start ()
324- machine .wait_boot ()
325- try :
326- for (handler , arg ) in args .actions :
327- handler (machine , arg )
328- except Exception as e :
329- if args .sit :
330- print (e , file = sys .stderr )
331- print (machine .diagnose (), file = sys .stderr )
332- print ("Press RET to continue..." )
333- sys .stdin .readline ()
334- raise e
335- finally :
336- machine .stop ()
337-
338-
339- if __name__ == '__main__' :
340- main ()
341+
342+ cockpit_test_identity = Path (MACHINE_DIR ) / "identity"
343+ cockpit_test_identity .chmod (0o600 )
344+
345+ with testthing .cli_helper (), testthing .IpcDirectory () as ipc :
346+ async with testthing .VirtualMachine (
347+ prepare_install_image (args .base_image , args .image , args .resize , args .fresh ),
348+ cpus = args .cpus ,
349+ identity = (cockpit_test_identity , None ),
350+ ipc = ipc ,
351+ memory = f"{ args .memory_mb } M" ,
352+ networks = [] if args .no_network else [testthing .Network ("user" )],
353+ sit = args .sit ,
354+ snapshot = False ,
355+ verbose = args .verbose ,
356+ ) as vm :
357+ for handler , arg in args .actions :
358+ await asyncio .wait_for (handler (vm , arg ), timeout = 1800 )
359+
360+
361+ if __name__ == "__main__" :
362+ asyncio .run (main ())
0 commit comments