Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions kernel_install_dep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ install_kselftest_deps_8() {
echo
sudo dnf -y groupinstall 'Development Tools'
sudo dnf -y install epel-release
# EPEL repos use $releasever which breaks when vault-pinned to a minor version (e.g. 8.6)
sudo sed -i 's/\$releasever/8/g' /etc/yum.repos.d/epel*.repo
sudo dnf config-manager --set-enabled powertools
sudo dnf -y install --enablerepo=devel \
VirtualGL \
alsa-lib-devel \
Expand Down Expand Up @@ -43,9 +46,11 @@ install_kselftest_deps_8() {
nftables \
nmap-ncat \
numactl-devel \
openssl \
openssl-devel \
perf \
popt-devel \
python3 \
python3-pip \
rsync \
socat \
Expand All @@ -71,6 +76,9 @@ install_kselftest_deps_8() {
pyyaml \
scapy \
tftpy

sudo dnf -y update
sudo systemctl restart sshd
}

install_kselftest_deps_9() {
Expand All @@ -79,8 +87,9 @@ install_kselftest_deps_9() {
echo
sudo dnf -y groupinstall 'Development Tools'
sudo dnf -y install epel-release
# EPEL repos use $releasever which breaks when vault-pinned to a minor version (e.g. 9.2)
sudo sed -i 's/\$releasever/9/g' /etc/yum.repos.d/epel*.repo
sudo dnf -y install --enablerepo=crb,devel \
VirtualGL \
alsa-lib-devel \
bc \
clang \
Expand All @@ -92,7 +101,6 @@ install_kselftest_deps_9() {
ethtool \
fuse \
fuse-devel \
gcc-toolset-13-libasan-devel \
glibc \
glibc-static \
iperf3 \
Expand Down Expand Up @@ -126,9 +134,15 @@ install_kselftest_deps_9() {
tcpdump \
teamd \
traceroute \
virtme-ng \
wget

# Not available on all 9.x minor releases
sudo dnf -y install --enablerepo=crb,devel \
VirtualGL \
gcc-toolset-13-libasan-devel \
virtme-ng \
|| echo "Optional packages not available or install failed; continuing."

pip3 install --user \
jsonschema \
netaddr \
Expand All @@ -140,6 +154,9 @@ install_kselftest_deps_9() {
scapy \
tftpy \
wheel

sudo dnf -y update
sudo systemctl restart sshd
}

install_kselftest_deps_10() {
Expand Down Expand Up @@ -208,6 +225,9 @@ install_kselftest_deps_10() {
scapy \
tftpy \
wheel

sudo dnf -y update
sudo systemctl restart sshd
}

case "${VERSION_ID%%.*}" in
Expand Down
4 changes: 3 additions & 1 deletion kt/commands/vm/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@
@click.option("--test", is_flag=True, help="Build the kernel and run kselftests")
@click.option("--vcpus", type=int, default=12, help="Number of virtual CPUs (default: 12)")
@click.option("--memory", type=int, default=32768, help="Memory in MiB (default: 32768)")
@click.option("--no-depot", is_flag=True, help="Skip depot client installation and channel setup")
Comment thread
PlaidCat marked this conversation as resolved.
@click.argument("kernel_workspace", required=False, shell_complete=ShellCompletion.show_kernel_workspaces)
def vm(kernel_workspace, console, destroy, override, override_base, list_all, test, vcpus, memory):
def vm(kernel_workspace, console, destroy, override, override_base, list_all, test, vcpus, memory, no_depot):
if not list_all and not kernel_workspace:
raise click.UsageError("kernel_workspace is required unless --list-all is specified")

Expand All @@ -86,4 +87,5 @@ def vm(kernel_workspace, console, destroy, override, override_base, list_all, te
test=test,
vcpus=vcpus,
memory=memory,
no_depot=no_depot,
)
8 changes: 7 additions & 1 deletion kt/commands/vm/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def main(
test: bool = False,
vcpus: int = 12,
memory: int = 32768,
no_depot: bool = False,
):
if list_all:
VmCommand.list_all()
Expand All @@ -31,7 +32,12 @@ def main(
return

vm_instance = Vm.setup_and_spinup(
kernel_workspace_name=name, override=override, override_base=override_base, vcpus=vcpus, memory=memory
kernel_workspace_name=name,
override=override,
override_base=override_base,
vcpus=vcpus,
memory=memory,
no_depot=no_depot,
)
config = Config.load()

Expand Down
20 changes: 17 additions & 3 deletions kt/data/cloud_init.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ ssh_pwauth: true
package_upgrade: False

mounts:
- ["mount_tag_mock_scratch", "SHARED_DIR_PLACEHOLDER", "virtiofs", "rw,relatime,context=unconfined_u:object_r:mock_var_lib_t:s0"]
- ["SHARED_DIR_PLACEHOLDER", "/var/lib/mock", "bind", "defaults,bind"]
- - mount_tag_mock_scratch
- SHARED_DIR_PLACEHOLDER
- virtiofs
- rw,relatime,context=unconfined_u:object_r:mock_var_lib_t:s0
- - SHARED_DIR_PLACEHOLDER
- /var/lib/mock
- bind
- defaults,bind

# Change working directory after boot
# Setting up homedir does not work without tricks, because
Expand All @@ -32,4 +38,12 @@ write_files:
# and then root is the owner
# workaround to change the owner to user
runcmd:
- [chown, USER_PLACEHOLDER:USER_PLACEHOLDER, HOMEDIR_PLACEHOLDER]
- - chown
- USER_PLACEHOLDER:USER_PLACEHOLDER
- HOMEDIR_PLACEHOLDER

power_state:
mode: reboot
message: "Rebooting after install"
timeout: 30
condition: true
Comment thread
PlaidCat marked this conversation as resolved.
9 changes: 9 additions & 0 deletions kt/data/kernels.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ kernels:
dist_git_branch: lts86-8
mock_config: rocky-lts86
automated: true
vm_image_url: https://dl.rockylinux.org/vault/rocky/8.6/images/Rocky-8-GenericCloud-8.6.20220702.0.x86_64.qcow2
depot_channels:
- lts-8.6

lts-9.2:
src_tree_root: kernel-src-tree
Expand All @@ -27,6 +30,9 @@ kernels:
dist_git_branch: lts92-9
mock_config: rocky-lts92
automated: true
vm_image_url: https://dl.rockylinux.org/vault/rocky/9.2/images/x86_64/Rocky-9-GenericCloud-Base-9.2-20230513.0.x86_64.qcow2
depot_channels:
- lts-9.2

lts-9.6:
src_tree_root: kernel-src-tree
Expand All @@ -35,6 +41,9 @@ kernels:
dist_git_branch: lts96-9
mock_config: rocky-lts96
automated: true
vm_image_url: https://dl.rockylinux.org/vault/rocky/9.6/images/x86_64/Rocky-9-GenericCloud-Base-9.6-20250531.0.x86_64.qcow2
depot_channels:
- lts-9.6

fipslegacy-8.6:
src_tree_root: kernel-src-tree
Expand Down
3 changes: 3 additions & 0 deletions kt/ktlib/kernels.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class KernelInfo:

automated: bool

vm_image_url: str | None = None
depot_channels: list[str] | None = None


@dataclass
class KernelsInfo:
Expand Down
89 changes: 74 additions & 15 deletions kt/ktlib/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
import time
from dataclasses import dataclass

import oyaml as yaml
import wget
from git import Repo
from pathlib3x import Path
from ruamel.yaml import YAML

from urllib.parse import urlparse

from kt.ktlib.config import Config
from kt.ktlib.kernel_workspace import KernelWorkspace
from kt.ktlib.kernels import KernelsInfo
from kt.ktlib.local import LocalCommand
from kt.ktlib.ssh import SshCommand
from kt.ktlib.util import Constants
Expand Down Expand Up @@ -43,18 +46,29 @@ class Vm:
cloud_init_path: Path
name: str
kernel_workspace: KernelWorkspace
vm_image_url: str | None = None
depot_channels: list[str] | None = None

@classmethod
def load(cls, config: Config, kernel_workspace: KernelWorkspace):
def load(
cls,
config: Config,
kernel_workspace: KernelWorkspace,
vm_image_url: str | None = None,
depot_channels: list[str] | None = None,
):
kernel_workspace_str = kernel_workspace.folder.name
kernel_name = cls._extract_kernel_name(kernel_workspace_str)
vm_major_version = cls._extract_major(kernel_name)
vm_major_minor_version = cls._extract_major_minor(kernel_name)

# Image source paths construction
qcow2_source_path = config.images_source_dir / Path(
cls._qcow2_name(vm_major_minor_version=vm_major_minor_version)
)
# Image source paths construction — use pinned URL basename if set, else default
if vm_image_url:
source_image_name = Path(urlparse(vm_image_url).path).name
else:
source_image_name = cls._qcow2_name(vm_major_minor_version=vm_major_minor_version)

qcow2_source_path = config.images_source_dir / Path(source_image_name)

# Actual current image paths construction
work_dir = config.images_dir / Path(kernel_workspace_str)
Expand All @@ -69,6 +83,8 @@ def load(cls, config: Config, kernel_workspace: KernelWorkspace):
cloud_init_path=cloud_init_path,
name=kernel_workspace_str,
kernel_workspace=kernel_workspace,
vm_image_url=vm_image_url,
depot_channels=depot_channels,
)

@classmethod
Expand Down Expand Up @@ -104,7 +120,19 @@ def load_from_workspace(cls, kernel_workspace_name: str):
config = Config.load()
kernel_workpath = config.kernels_dir / kernel_workspace_name
kernel_workspace = KernelWorkspace.load_from_filepath(folder=kernel_workpath)
return cls.load(config=config, kernel_workspace=kernel_workspace)

vm_image_url = None
depot_channels = None
kernel_name = cls._extract_kernel_name(kernel_workspace_name)
kernels_info = KernelsInfo.from_yaml(config=config)
kernel_info = kernels_info.kernels.get(kernel_name)
if kernel_info:
vm_image_url = kernel_info.vm_image_url
depot_channels = kernel_info.depot_channels

return cls.load(
config=config, kernel_workspace=kernel_workspace, vm_image_url=vm_image_url, depot_channels=depot_channels
)

@classmethod
def setup_and_spinup(
Expand All @@ -114,6 +142,7 @@ def setup_and_spinup(
override_base: bool = False,
vcpus: int = 12,
memory: int = 32768,
no_depot: bool = False,
):
"""
Setup and spin up a VM from a kernel workspace name.
Expand All @@ -124,6 +153,7 @@ def setup_and_spinup(
override_base: If True, destroy and recreate the VM but override the base image as well
vcpus: Number of virtual CPUs
memory: Memory in MiB
no_depot: If True, skip depot client installation and channel setup

Returns:
VmInstance: The running VM instance
Expand All @@ -135,11 +165,13 @@ def setup_and_spinup(
vm.destroy()

vm.setup(override_base=override_base)
vm_instance = vm.spin_up(config=config, vcpus=vcpus, memory=memory)
vm_instance = vm.spin_up(config=config, vcpus=vcpus, memory=memory, no_depot=no_depot)

return vm_instance

def _get_vm_url(self):
if self.vm_image_url:
return self.vm_image_url
return f"{Constants.BASE_URL}/{self.vm_major_minor_version}/images/x86_64/{Constants.DEFAULT_VM_BASE}-{self.vm_major_version}-{Constants.QCOW2_TRAIL}"

def _download_source_image(self, override_base: bool = False):
Expand All @@ -156,10 +188,16 @@ def _download_source_image(self, override_base: bool = False):
logging.info(f"Downloading image from {self._get_vm_url()}")
wget.download(self._get_vm_url(), out=str(self.qcow2_source_path))

def _setup_cloud_init(self, config: Config):
def _setup_cloud_init(self, config: Config, no_depot: bool = False):
yaml = YAML()
yaml.preserve_quotes = True
yaml.width = 4096
yaml.default_flow_style = False
yaml.best_sequence_indent = 2
Comment thread
PlaidCat marked this conversation as resolved.

data = None
with open(CLOUD_INIT_BASE_PATH) as f:
data = yaml.safe_load(f)
data = yaml.load(f)

# replace placeholders with user data
data["users"][0]["name"] = config.user
Expand All @@ -185,18 +223,39 @@ def _setup_cloud_init(self, config: Config):
data["runcmd"][0][1] = f"{config.user}:{config.user}"
data["runcmd"][0][2] = os.environ["HOME"]

# Pin dnf to vault for kernels with a pinned VM image (Rocky only, not CentOS/cbr)
if self.vm_image_url:
data["runcmd"].append(f'echo "{self.vm_major_minor_version}" > /etc/dnf/vars/releasever')
Comment thread
PlaidCat marked this conversation as resolved.
data["runcmd"].append('echo "vault/rocky" > /etc/dnf/vars/contentdir')
data["runcmd"].append(
"cd /etc/yum.repos.d/ && for f in *.repo; do "
'sed -i -e "s/^mirrorlist=/#mirrorlist=/" -e "s/#baseurl=/baseurl=/" "$f"; done'
)
data["runcmd"].append("dnf clean all")

# Depot: install the client and login+enable if credentials are present
if not no_depot:
data["runcmd"].append(
"dnf install -y https://depot.ciq.com/public/files/depot-client/depot/depot.x86_64.rpm"
)
depot_user = os.environ.get("DEPOT_USER")
depot_token = os.environ.get("DEPOT_TOKEN")
if depot_user and depot_token and self.depot_channels:
data["runcmd"].append(f"depot login -u {depot_user} -t {depot_token}")
for channel in self.depot_channels:
data["runcmd"].append(f"depot enable {channel} -y")
Comment thread
PlaidCat marked this conversation as resolved.

# Install packages needed later
data["runcmd"].append([str(config.base_path / Path("kernel-src-tree-tools") / Path("kernel_install_dep.sh"))])
# Write this to image cloud_init
with open(self.cloud_init_path, "w") as f:
f.write("#cloud-config\n")
yaml.dump(data, f)

def _create_image(self, config: Config, vcpus: int = 12, memory: int = 32768):
def _create_image(self, config: Config, vcpus: int = 12, memory: int = 32768, no_depot: bool = False):
# Make sure the dir exists
self.qcow2_path.parent.mkdir(parents=True, exist_ok=True)

self._setup_cloud_init(config=config)
self._setup_cloud_init(config=config, no_depot=no_depot)
# Copy qcow2 image to work dir
self.qcow2_source_path.copy(self.qcow2_path)

Expand Down Expand Up @@ -228,11 +287,11 @@ def _resize_disk(self):
def setup(self, override_base: bool = False):
self._download_source_image(override_base=override_base)

def spin_up(self, config: Config, vcpus: int = 12, memory: int = 32768) -> VmInstance:
def spin_up(self, config: Config, vcpus: int = 12, memory: int = 32768, no_depot: bool = False) -> VmInstance:
if not VirtHelper.exists(vm_name=self.name):
logging.info(f"VM {self.name} does not exist, creating from scratch...")

self._create_image(config=config, vcpus=vcpus, memory=memory)
self._create_image(config=config, vcpus=vcpus, memory=memory, no_depot=no_depot)
return VmInstance(name=self.name, kernel_workspace=self.kernel_workspace)

logging.info(f"Vm {self.name} already exists")
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ dependencies = [
"click",
"pathlib3x",
"python3-wget",
"oyaml",
"ruamel.yaml",
"pyyaml",
"pexpect",
"jira",
"requests",
Expand Down
Loading