2021-11-04 23:20:41 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
'''kvm-test - boot a kvm with a test iso, possibly building that test iso first
|
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
kvm-test -q --install -o --boot
|
2021-11-04 23:20:41 +00:00
|
|
|
slimy build, install, overwrite existing image if it exists,
|
|
|
|
and boot the result after install
|
|
|
|
|
|
|
|
See kvm-test -h for options and more examples.
|
|
|
|
'''
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
import contextlib
|
|
|
|
import copy
|
|
|
|
import crypt
|
|
|
|
import os
|
|
|
|
import random
|
2022-01-04 17:58:09 +00:00
|
|
|
import shlex
|
2021-11-04 23:20:41 +00:00
|
|
|
import socket
|
2021-12-01 23:48:12 +00:00
|
|
|
import subprocess
|
2021-11-04 23:20:41 +00:00
|
|
|
import sys
|
|
|
|
import tempfile
|
Fix -drive option from kvm-test.sh
The function drive() used to return a string in the following format:
"-drive file=/path/to/iso,..."
However, qemu/kvm expects "-drive" to be an argument and
"file=/path/to/iso,..." to be another argument.
The command was constructed as below since the beginning:
kvm = [
"kvm",
"-cdrom", "custom.iso", # <- OK
"-drive file=/path/to/iso,...", # <- NOK
]
Before 06ac3f92, we would join all the arguments using spaces before
executing the kvm command. Therefore we would luckily end up with a
correct command:
" ".join(kvm) -> "kvm -cdrom custom.iso -drive file=/path/to/iso,..."
However, now that we supply the command to subprocess.run directly, the
problem shows up.
Fixed by returning a tuple("-drive", "file=/path/to/iso,...") from
the drive() function.
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-01-13 09:25:06 +00:00
|
|
|
from typing import Tuple
|
2021-11-04 23:20:41 +00:00
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
|
|
|
cfg = '''
|
2021-12-01 23:48:12 +00:00
|
|
|
default_mem: '8G'
|
2021-11-04 23:20:41 +00:00
|
|
|
iso:
|
|
|
|
basedir: /srv/iso
|
|
|
|
release:
|
|
|
|
edge: jammy/subiquity-edge/jammy-live-server-subiquity-edge-amd64.iso
|
|
|
|
canary: jammy/jammy-desktop-canary-amd64.iso
|
|
|
|
jammy: jammy/jammy-live-server-amd64.iso
|
2021-12-01 23:48:12 +00:00
|
|
|
desktop: jammy/jammy-desktop-amd64.iso
|
2021-11-04 23:20:41 +00:00
|
|
|
impish: impish/ubuntu-21.10-live-server-amd64.iso
|
|
|
|
hirsute: hirsute/ubuntu-21.04-live-server-amd64.iso
|
|
|
|
groovy: groovy/ubuntu-20.10-live-server-amd64.iso
|
|
|
|
focal: focal/ubuntu-20.04.3-live-server-amd64.iso
|
|
|
|
bionic: bionic/bionic-live-server-amd64.iso
|
|
|
|
default: edge
|
|
|
|
'''
|
|
|
|
|
2022-01-12 21:56:40 +00:00
|
|
|
|
2021-11-04 23:20:41 +00:00
|
|
|
def salted_crypt(plaintext_password):
|
|
|
|
# match subiquity documentation
|
|
|
|
salt = '$6$exDY1mhS4KUYCE/2'
|
|
|
|
return crypt.crypt(plaintext_password, salt)
|
|
|
|
|
|
|
|
|
|
|
|
class Context:
|
|
|
|
def __init__(self, args):
|
|
|
|
self.config = self.load_config()
|
|
|
|
self.args = args
|
|
|
|
self.release = args.release
|
2022-01-04 17:58:09 +00:00
|
|
|
self.default_mem = self.config.get('default_mem', '8G')
|
2021-11-04 23:20:41 +00:00
|
|
|
if not self.release:
|
|
|
|
self.release = self.config["iso"]["default"]
|
|
|
|
iso = self.config["iso"]
|
|
|
|
try:
|
|
|
|
self.baseiso = os.path.join(iso["basedir"],
|
|
|
|
iso["release"][self.release])
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
self.curdir = os.getcwd()
|
|
|
|
self.iso = f'/tmp/kvm-test/{self.release}-test.iso'
|
|
|
|
self.hostname = f'{self.release}-test'
|
|
|
|
self.target = f'/tmp/kvm-test/{self.hostname}.img'
|
|
|
|
self.password = salted_crypt('ubuntu')
|
|
|
|
self.cloudconfig = f'''\
|
|
|
|
#cloud-config
|
|
|
|
autoinstall:
|
|
|
|
version: 1
|
|
|
|
locale:
|
|
|
|
en_US.UTF-8
|
|
|
|
ssh:
|
|
|
|
install-server: true
|
|
|
|
allow-pw: true
|
|
|
|
identity:
|
|
|
|
hostname: {self.hostname}
|
|
|
|
password: "{self.password}"
|
|
|
|
username: ubuntu
|
|
|
|
'''
|
|
|
|
|
|
|
|
def merge(self, a, b):
|
|
|
|
'''Take a pair of dictionaries, and provide the merged result.
|
|
|
|
Assumes that any key conflicts have values that are themselves
|
|
|
|
dictionaries and raises TypeError if found otherwise.'''
|
|
|
|
result = copy.deepcopy(a)
|
|
|
|
|
|
|
|
for key in b:
|
|
|
|
if key in result:
|
|
|
|
left = result[key]
|
|
|
|
right = b[key]
|
|
|
|
if type(left) is not dict or type(right) is not dict:
|
|
|
|
result[key] = right
|
|
|
|
else:
|
|
|
|
result[key] = self.merge(left, right)
|
|
|
|
else:
|
|
|
|
result[key] = b[key]
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
def load_config(self):
|
|
|
|
result = yaml.safe_load(cfg)
|
|
|
|
homecfg = f'{os.environ["HOME"]}/.kvm-test.yaml'
|
|
|
|
if os.path.exists(homecfg):
|
|
|
|
with open(homecfg, 'r') as f:
|
|
|
|
result = self.merge(result, yaml.safe_load(f))
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
|
description='''\
|
|
|
|
Test isos and images written to /tmp/kvm-test
|
|
|
|
|
|
|
|
Sample usage:
|
|
|
|
kvm-test --build -q --install -o -a --boot
|
|
|
|
slimy build, run install, overwrite existing image, use autoinstall,
|
|
|
|
boot final resulting image
|
|
|
|
|
|
|
|
kvm-test --install -bo -rfocal
|
|
|
|
boot the focal base iso unmodified and run install manually
|
|
|
|
|
2022-01-12 20:36:41 +00:00
|
|
|
If DEBOOTSTRAP_PROXY is set, that will be passed to snapcraft to pick up
|
|
|
|
packages from a cache.
|
|
|
|
|
2021-11-04 23:20:41 +00:00
|
|
|
See 'cfg' in script for expected layout of iso files,
|
|
|
|
which can be managed with ~/.kvm-test.yaml''')
|
|
|
|
parser.add_argument('-a', '--autoinstall', default=False,
|
|
|
|
action='store_true', help='use autoinstall')
|
|
|
|
parser.add_argument('-b', '--base', default=False, action='store_true',
|
|
|
|
help='use base iso')
|
2021-11-05 03:03:35 +00:00
|
|
|
parser.add_argument('--basesnap', default=None, action='store',
|
|
|
|
help='use slimy-update-snap on this snap')
|
2021-11-05 03:04:18 +00:00
|
|
|
parser.add_argument('--snap', default=None, action='store',
|
|
|
|
help='inject this snap into the ISO')
|
2021-11-04 23:20:41 +00:00
|
|
|
parser.add_argument('-B', '--bios', action='store_true', default=False,
|
2021-12-01 23:48:12 +00:00
|
|
|
help='boot in BIOS mode (default mode is UEFI)')
|
|
|
|
parser.add_argument('-c', '--channel', action='store',
|
2021-11-04 23:20:41 +00:00
|
|
|
help='build iso with snap from channel')
|
|
|
|
parser.add_argument('-d', '--disksize', default='12G', action='store',
|
|
|
|
help='size of disk to create (12G default)')
|
|
|
|
parser.add_argument('-f', '--autoinstall-file', action='store',
|
|
|
|
type=argparse.FileType(),
|
|
|
|
help='load autoinstall from file')
|
|
|
|
parser.add_argument('-i', '--img', action='store', help='use this img')
|
|
|
|
parser.add_argument('-n', '--nets', action='store', default=1, type=int,
|
2022-01-25 00:03:00 +00:00
|
|
|
help='''number of network interfaces.
|
|
|
|
0=no network, -1=deadnet''')
|
2021-11-04 23:20:41 +00:00
|
|
|
parser.add_argument('-o', '--overwrite', default=False, action='store_true',
|
|
|
|
help='allow overwrite of the target image')
|
|
|
|
parser.add_argument('-q', '--quick', default=False, action='store_true',
|
|
|
|
help='build iso with quick-test-this-branch')
|
|
|
|
parser.add_argument('-r', '--release', action='store', help='target release')
|
|
|
|
parser.add_argument('-s', '--serial', default=False, action='store_true',
|
|
|
|
help='attach to serial console')
|
|
|
|
parser.add_argument('-S', '--sound', default=False, action='store_true',
|
|
|
|
help='enable sound')
|
2021-12-01 23:48:12 +00:00
|
|
|
parser.add_argument('--iso', action='store', help='use this iso')
|
2021-11-04 23:20:41 +00:00
|
|
|
parser.add_argument('-u', '--update', action='store',
|
|
|
|
help='subiquity-channel argument')
|
2021-12-01 23:48:12 +00:00
|
|
|
parser.add_argument('-m', '--memory', action='store',
|
|
|
|
help='memory for VM')
|
2021-11-04 23:20:41 +00:00
|
|
|
parser.add_argument('--save', action='store_true',
|
|
|
|
help='preserve built snap')
|
|
|
|
parser.add_argument('--reuse', action='store_true',
|
2021-12-01 23:48:12 +00:00
|
|
|
help='reuse previously saved snap. Implies --save')
|
2021-11-04 23:20:41 +00:00
|
|
|
parser.add_argument('--build', default=False, action='store_true',
|
|
|
|
help='build test iso')
|
|
|
|
parser.add_argument('--install', default=False, action='store_true',
|
|
|
|
help='''install from iso - one must either build a test
|
|
|
|
iso, use a base iso, or reuse previous test iso''')
|
|
|
|
parser.add_argument('--boot', default=False, action='store_true',
|
|
|
|
help='boot test image')
|
|
|
|
|
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
def parse_args():
|
|
|
|
ctx = Context(parser.parse_args())
|
|
|
|
if ctx.args.quick or ctx.args.basesnap or ctx.args.snap \
|
|
|
|
or ctx.args.channel or ctx.args.reuse:
|
|
|
|
ctx.args.build = True
|
|
|
|
if ctx.args.reuse:
|
|
|
|
ctx.args.save = True
|
2022-01-12 20:36:41 +00:00
|
|
|
|
|
|
|
ctx.livefs_editor = os.environ.get('LIVEFS_EDITOR')
|
|
|
|
if not ctx.livefs_editor:
|
|
|
|
raise Exception('Obtain a copy of livefs-editor and point ' +
|
|
|
|
'LIVEFS_EDITOR to it\n'
|
|
|
|
'https://github.com/mwhudson/livefs-editor')
|
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
return ctx
|
|
|
|
|
|
|
|
|
|
|
|
def run(cmd):
|
|
|
|
if isinstance(cmd, str):
|
|
|
|
cmd_str = cmd
|
2022-01-04 17:58:09 +00:00
|
|
|
cmd_array = shlex.split(cmd)
|
2021-12-01 23:48:12 +00:00
|
|
|
else:
|
2022-01-04 17:58:09 +00:00
|
|
|
cmd_str = shlex.join(cmd)
|
2021-12-01 23:48:12 +00:00
|
|
|
cmd_array = cmd
|
|
|
|
# semi-simulate "bash -x"
|
2022-01-04 17:58:09 +00:00
|
|
|
print(f'+ {cmd_str}', file=sys.stderr)
|
2021-12-01 23:48:12 +00:00
|
|
|
subprocess.run(cmd_array, check=True)
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
def assert_exists(path):
|
|
|
|
if not os.path.exists(path):
|
2022-01-04 17:58:09 +00:00
|
|
|
raise Exception(f'Expected file {path} not found')
|
|
|
|
|
|
|
|
|
|
|
|
def remove_if_exists(path):
|
|
|
|
if os.path.exists(path):
|
|
|
|
os.remove(path)
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def delete_later(path):
|
|
|
|
try:
|
|
|
|
yield path
|
|
|
|
finally:
|
2022-01-04 17:58:09 +00:00
|
|
|
remove_if_exists(path)
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def noop(path):
|
|
|
|
yield path
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
2022-01-20 10:59:48 +00:00
|
|
|
def mounter(src, dest):
|
|
|
|
run(["fuseiso", src, dest])
|
2021-11-04 23:20:41 +00:00
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
2022-01-20 10:59:48 +00:00
|
|
|
run(["fusermount", "-u", dest])
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
def livefs_edit(ctx, *args):
|
2022-01-12 20:36:41 +00:00
|
|
|
livefs_editor = os.environ['LIVEFS_EDITOR']
|
|
|
|
run(['sudo', f'PYTHONPATH={livefs_editor}', 'python3', '-m', 'livefs_edit',
|
2022-01-12 21:56:40 +00:00
|
|
|
ctx.baseiso, ctx.iso, *args])
|
2021-12-01 23:48:12 +00:00
|
|
|
|
|
|
|
|
2021-11-04 23:20:41 +00:00
|
|
|
def build(ctx):
|
2022-01-04 17:58:09 +00:00
|
|
|
remove_if_exists(ctx.iso)
|
2021-11-04 23:20:41 +00:00
|
|
|
project = os.path.basename(os.getcwd())
|
|
|
|
|
2022-01-12 20:36:41 +00:00
|
|
|
snapargs = '--debug'
|
2022-01-13 19:25:12 +00:00
|
|
|
http_proxy = os.environ.get('DEBOOTSTRAP_PROXY')
|
2022-01-12 20:36:41 +00:00
|
|
|
if http_proxy:
|
|
|
|
snapargs += f' --http-proxy={http_proxy}'
|
|
|
|
|
2021-11-04 23:20:41 +00:00
|
|
|
snap_manager = noop if ctx.args.save else delete_later
|
2021-12-01 23:48:12 +00:00
|
|
|
if project == 'subiquity':
|
2021-11-04 23:20:41 +00:00
|
|
|
if ctx.args.quick:
|
2021-11-08 23:44:35 +00:00
|
|
|
run(f'sudo ./scripts/quick-test-this-branch.sh {ctx.baseiso} \
|
|
|
|
{ctx.iso}')
|
2021-11-05 03:03:35 +00:00
|
|
|
elif ctx.args.basesnap:
|
|
|
|
with snap_manager('subiquity_test.snap') as snap:
|
|
|
|
run(f'sudo ./scripts/slimy-update-snap.sh {ctx.args.basesnap} \
|
|
|
|
{snap}')
|
|
|
|
run(f'sudo ./scripts/inject-subiquity-snap.sh {ctx.baseiso} \
|
|
|
|
{snap} {ctx.iso}')
|
2021-11-05 03:04:18 +00:00
|
|
|
elif ctx.args.snap:
|
|
|
|
run(f'sudo ./scripts/inject-subiquity-snap.sh {ctx.baseiso} \
|
|
|
|
{ctx.args.snap} {ctx.iso}')
|
2021-11-04 23:20:41 +00:00
|
|
|
elif ctx.args.channel:
|
2021-12-01 23:48:12 +00:00
|
|
|
livefs_edit(ctx, '--add-snap-from-store', 'core20', 'stable',
|
|
|
|
'--add-snap-from-store', 'subiquity',
|
|
|
|
ctx.args.channel)
|
2021-11-04 23:20:41 +00:00
|
|
|
else:
|
|
|
|
with snap_manager('subiquity_test.snap') as snap:
|
|
|
|
if not ctx.args.reuse:
|
2021-12-01 23:48:12 +00:00
|
|
|
run('snapcraft clean --use-lxd')
|
2022-01-12 20:36:41 +00:00
|
|
|
run(f'snapcraft snap --use-lxd --output {snap} {snapargs}')
|
2021-12-01 23:48:12 +00:00
|
|
|
assert_exists(snap)
|
|
|
|
livefs_edit(ctx, '--add-snap-from-store', 'core20', 'stable',
|
2022-01-12 21:56:40 +00:00
|
|
|
'--inject-snap', snap)
|
2021-11-04 23:20:41 +00:00
|
|
|
elif project == 'ubuntu-desktop-installer':
|
|
|
|
with snap_manager('udi_test.snap') as snap:
|
2021-12-01 23:48:12 +00:00
|
|
|
run('snapcraft clean --use-lxd')
|
2022-01-12 20:36:41 +00:00
|
|
|
run(f'snapcraft snap --use-lxd --output {snap} {snapargs}')
|
2021-12-01 23:48:12 +00:00
|
|
|
assert_exists(snap)
|
|
|
|
run(f'sudo ./scripts/inject-snap {ctx.baseiso} {ctx.iso} {snap}')
|
2021-11-04 23:20:41 +00:00
|
|
|
else:
|
2021-11-05 03:05:00 +00:00
|
|
|
raise Exception(f'do not know how to build {project}')
|
2021-11-04 23:20:41 +00:00
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
assert_exists(ctx.iso)
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def write(dest, data):
|
|
|
|
with open(dest, 'w') as destfile:
|
|
|
|
destfile.write(data)
|
|
|
|
|
|
|
|
|
|
|
|
def touch(dest):
|
|
|
|
with open(dest, 'w'):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def create_seed(cloudconfig, tempdir):
|
|
|
|
write(f'{tempdir}/user-data', cloudconfig)
|
|
|
|
touch(f'{tempdir}/meta-data')
|
|
|
|
seed = f'{tempdir}/seed.iso'
|
|
|
|
run(f'cloud-localds {seed} {tempdir}/user-data {tempdir}/meta-data')
|
|
|
|
return seed
|
|
|
|
|
|
|
|
|
Fix -drive option from kvm-test.sh
The function drive() used to return a string in the following format:
"-drive file=/path/to/iso,..."
However, qemu/kvm expects "-drive" to be an argument and
"file=/path/to/iso,..." to be another argument.
The command was constructed as below since the beginning:
kvm = [
"kvm",
"-cdrom", "custom.iso", # <- OK
"-drive file=/path/to/iso,...", # <- NOK
]
Before 06ac3f92, we would join all the arguments using spaces before
executing the kvm command. Therefore we would luckily end up with a
correct command:
" ".join(kvm) -> "kvm -cdrom custom.iso -drive file=/path/to/iso,..."
However, now that we supply the command to subprocess.run directly, the
problem shows up.
Fixed by returning a tuple("-drive", "file=/path/to/iso,...") from
the drive() function.
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-01-13 09:25:06 +00:00
|
|
|
def drive(path, format='qcow2') -> Tuple[str, str]:
|
|
|
|
""" Return a tuple (-drive, <options>) that can be passed to kvm """
|
2021-11-04 23:20:41 +00:00
|
|
|
kwargs = []
|
|
|
|
serial = None
|
|
|
|
cparam = 'writethrough'
|
2021-12-01 23:48:12 +00:00
|
|
|
kwargs.append(f'file={path}')
|
|
|
|
kwargs.append(f'format={format}')
|
|
|
|
kwargs.append(f'cache={cparam}')
|
|
|
|
kwargs.append('if=virtio')
|
2021-11-04 23:20:41 +00:00
|
|
|
if serial:
|
2021-12-01 23:48:12 +00:00
|
|
|
kwargs.append(f'serial={serial}')
|
2021-11-04 23:20:41 +00:00
|
|
|
|
Fix -drive option from kvm-test.sh
The function drive() used to return a string in the following format:
"-drive file=/path/to/iso,..."
However, qemu/kvm expects "-drive" to be an argument and
"file=/path/to/iso,..." to be another argument.
The command was constructed as below since the beginning:
kvm = [
"kvm",
"-cdrom", "custom.iso", # <- OK
"-drive file=/path/to/iso,...", # <- NOK
]
Before 06ac3f92, we would join all the arguments using spaces before
executing the kvm command. Therefore we would luckily end up with a
correct command:
" ".join(kvm) -> "kvm -cdrom custom.iso -drive file=/path/to/iso,..."
However, now that we supply the command to subprocess.run directly, the
problem shows up.
Fixed by returning a tuple("-drive", "file=/path/to/iso,...") from
the drive() function.
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-01-13 09:25:06 +00:00
|
|
|
return ('-drive', ','.join(kwargs))
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
class PortFinder:
|
|
|
|
def __init__(self):
|
|
|
|
self.finder = self.port_generator()
|
|
|
|
|
|
|
|
def port_generator(self):
|
|
|
|
for port in range(2222, 8000):
|
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
|
|
res = sock.connect_ex(('localhost', port))
|
|
|
|
if res != 0:
|
|
|
|
yield port
|
|
|
|
|
|
|
|
def get(self):
|
|
|
|
return next(self.finder)
|
|
|
|
|
|
|
|
|
|
|
|
def nets(ctx):
|
|
|
|
if ctx.args.nets > 0:
|
2022-01-25 00:03:00 +00:00
|
|
|
ports = PortFinder()
|
2021-12-01 23:48:12 +00:00
|
|
|
ret = []
|
2021-11-04 23:20:41 +00:00
|
|
|
for _ in range(ctx.args.nets):
|
|
|
|
port = ports.get()
|
2021-12-01 23:48:12 +00:00
|
|
|
ret.extend(('-nic',
|
|
|
|
'user,model=virtio-net-pci,' +
|
|
|
|
f'hostfwd=tcp::{port}-:22'))
|
2022-01-25 00:03:00 +00:00
|
|
|
return ret
|
|
|
|
elif ctx.args.nets == 0:
|
|
|
|
# no network
|
|
|
|
return ('-nic', 'none')
|
2021-11-04 23:20:41 +00:00
|
|
|
else:
|
2022-01-25 00:03:00 +00:00
|
|
|
# nic present but restricted - simulate deadnet environment
|
|
|
|
return ('-nic', 'user,model=virtio-net-pci,restrict=on')
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def bios(ctx):
|
|
|
|
ret = []
|
|
|
|
# https://help.ubuntu.com/community/UEFI
|
|
|
|
if not ctx.args.bios:
|
2021-12-01 23:48:12 +00:00
|
|
|
ret = ['-bios', '/usr/share/qemu/OVMF.fd']
|
2021-11-04 23:20:41 +00:00
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def memory(ctx):
|
2021-12-01 23:48:12 +00:00
|
|
|
return ['-m', ctx.args.memory or ctx.default_mem]
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def kvm_common(ctx):
|
|
|
|
ret = ['kvm', '-no-reboot']
|
|
|
|
ret.extend(('-vga', 'virtio'))
|
|
|
|
ret.extend(memory(ctx))
|
|
|
|
ret.extend(bios(ctx))
|
|
|
|
ret.extend(nets(ctx))
|
|
|
|
if ctx.args.sound:
|
|
|
|
ret.extend(('-device', 'AC97', '-device', 'usb-ehci'))
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def get_initrd(mntdir):
|
|
|
|
for initrd in ('initrd', 'initrd.lz', 'initrd.lz4'):
|
|
|
|
path = f'{mntdir}/casper/{initrd}'
|
|
|
|
if os.path.exists(path):
|
|
|
|
return path
|
|
|
|
raise Exception('initrd not found')
|
|
|
|
|
|
|
|
|
|
|
|
def install(ctx):
|
|
|
|
if os.path.exists(ctx.target):
|
|
|
|
if ctx.args.overwrite:
|
|
|
|
os.remove(ctx.target)
|
2021-12-01 23:48:12 +00:00
|
|
|
else:
|
2022-01-12 20:36:41 +00:00
|
|
|
raise Exception('refusing to overwrite existing image, use the ' +
|
|
|
|
'-o option to allow overwriting')
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
|
|
mntdir = f'{tempdir}/mnt'
|
|
|
|
os.mkdir(mntdir)
|
|
|
|
appends = []
|
|
|
|
|
|
|
|
kvm = kvm_common(ctx)
|
|
|
|
|
2022-01-04 17:58:09 +00:00
|
|
|
if ctx.args.iso:
|
|
|
|
iso = ctx.args.iso
|
2021-11-04 23:20:41 +00:00
|
|
|
elif ctx.args.base:
|
|
|
|
iso = ctx.baseiso
|
|
|
|
else:
|
|
|
|
iso = ctx.iso
|
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
kvm.extend(('-cdrom', iso))
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
if ctx.args.serial:
|
2021-12-01 23:48:12 +00:00
|
|
|
kvm.append('-nographic')
|
|
|
|
appends.append('console=ttyS0')
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
if ctx.args.autoinstall or ctx.args.autoinstall_file:
|
|
|
|
if ctx.args.autoinstall_file:
|
|
|
|
ctx.cloudconfig = ctx.args.autoinstall_file.read()
|
2022-01-12 20:36:41 +00:00
|
|
|
kvm.extend(drive(create_seed(ctx.cloudconfig, tempdir), 'raw'))
|
2021-12-01 23:48:12 +00:00
|
|
|
appends.append('autoinstall')
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
if ctx.args.update:
|
2021-12-01 23:48:12 +00:00
|
|
|
appends.append('subiquity-channel=' + ctx.args.update)
|
2021-11-04 23:20:41 +00:00
|
|
|
|
2022-01-12 20:36:41 +00:00
|
|
|
kvm.extend(drive(ctx.target))
|
2021-11-04 23:20:41 +00:00
|
|
|
if not os.path.exists(ctx.target) or ctx.args.overwrite:
|
|
|
|
run(f'qemu-img create -f qcow2 {ctx.target} {ctx.args.disksize}')
|
|
|
|
|
2021-12-01 23:48:12 +00:00
|
|
|
if len(appends) > 0:
|
2022-01-20 10:59:48 +00:00
|
|
|
with mounter(iso, mntdir):
|
2021-11-04 23:20:41 +00:00
|
|
|
# if we're passing kernel args, we need to manually specify
|
|
|
|
# kernel / initrd
|
2021-12-01 23:48:12 +00:00
|
|
|
kvm.extend(('-kernel', f'{mntdir}/casper/vmlinuz'))
|
|
|
|
kvm.extend(('-initrd', get_initrd(mntdir)))
|
2022-01-13 10:15:28 +00:00
|
|
|
kvm.extend(('-append', ' '.join(appends)))
|
2021-12-01 23:48:12 +00:00
|
|
|
run(kvm)
|
|
|
|
else:
|
|
|
|
run(kvm)
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def boot(ctx):
|
|
|
|
target = ctx.target
|
|
|
|
if ctx.args.img:
|
|
|
|
target = ctx.args.img
|
|
|
|
|
|
|
|
kvm = kvm_common(ctx)
|
2022-01-12 20:36:41 +00:00
|
|
|
kvm.extend(drive(target))
|
2021-12-01 23:48:12 +00:00
|
|
|
run(kvm)
|
2021-11-04 23:20:41 +00:00
|
|
|
|
|
|
|
|
2022-01-04 17:58:09 +00:00
|
|
|
def help():
|
2021-11-04 23:20:41 +00:00
|
|
|
parser.print_usage()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
2021-12-01 23:48:12 +00:00
|
|
|
ctx = parse_args()
|
2021-11-04 23:20:41 +00:00
|
|
|
except TypeError:
|
|
|
|
help()
|
|
|
|
|
|
|
|
if ctx.args.base and ctx.args.build:
|
|
|
|
raise Exception('cannot use base iso and build')
|
|
|
|
|
|
|
|
os.makedirs('/tmp/kvm-test', exist_ok=True)
|
|
|
|
|
|
|
|
if ctx.args.build:
|
|
|
|
build(ctx)
|
|
|
|
if ctx.args.install:
|
|
|
|
install(ctx)
|
|
|
|
if ctx.args.boot:
|
|
|
|
boot(ctx)
|
|
|
|
if True not in (ctx.args.build, ctx.args.install, ctx.args.boot):
|
|
|
|
parser.print_help()
|