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
48 changes: 36 additions & 12 deletions lisa/sut_orchestrator/libvirt/ch_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import os
import re
import secrets
import xml.etree.ElementTree as ET # noqa: N817
from pathlib import Path
from typing import List, Type
Expand All @@ -11,7 +12,11 @@
from lisa.environment import Environment
from lisa.feature import Feature
from lisa.node import Node
from lisa.sut_orchestrator.libvirt.context import NodeContext, get_node_context
from lisa.sut_orchestrator.libvirt.context import (
GuestVmType,
NodeContext,
get_node_context,
)
from lisa.sut_orchestrator.libvirt.platform import BaseLibvirtPlatform
from lisa.tools import QemuImg
from lisa.util.logger import Logger, filter_ansi_escape
Expand Down Expand Up @@ -57,13 +62,22 @@ def _configure_node(

assert isinstance(node_runbook, CloudHypervisorNodeSchema)
node_context = get_node_context(node)
if self.host_node.is_remote:
node_context.firmware_source_path = node_runbook.firmware
node_context.firmware_path = os.path.join(
self.vm_disks_dir, os.path.basename(node_runbook.firmware)
)
if node_runbook.kernel:
if self.host_node.is_remote and not node_runbook.kernel.is_remote_path:
node_context.kernel_source_path = node_runbook.kernel.path
node_context.kernel_path = os.path.join(
self.vm_disks_dir, os.path.basename(node_runbook.kernel.path)
)
else:
node_context.kernel_path = node_runbook.kernel.path
else:
node_context.firmware_path = node_runbook.firmware
if self.host_node.is_remote:
node_context.kernel_source_path = node_runbook.firmware
node_context.kernel_path = os.path.join(
self.vm_disks_dir, os.path.basename(node_runbook.firmware)
)
else:
node_context.kernel_path = node_runbook.firmware

def _create_node(
self,
Expand All @@ -72,10 +86,10 @@ def _create_node(
environment: Environment,
log: Logger,
) -> None:
if node_context.firmware_source_path:
if node_context.kernel_source_path:
self.host_node.shell.copy(
Path(node_context.firmware_source_path),
Path(node_context.firmware_path),
Path(node_context.kernel_source_path),
Path(node_context.kernel_path),
)

super()._create_node(
Expand Down Expand Up @@ -113,9 +127,19 @@ def _create_node_domain_xml(

os_type = ET.SubElement(os, "type")
os_type.text = "hvm"

os_kernel = ET.SubElement(os, "kernel")
os_kernel.text = node_context.firmware_path
os_kernel.text = node_context.kernel_path
if node_context.guest_vm_type is GuestVmType.ConfidentialVM:
launch_sec = ET.SubElement(domain, "launchSecurity")
launch_sec.attrib["type"] = "sev"
cbitpos = ET.SubElement(launch_sec, "cbitpos")
cbitpos.text = "0"
reducedphysbits = ET.SubElement(launch_sec, "reducedPhysBits")
reducedphysbits.text = "0"
policy = ET.SubElement(launch_sec, "policy")
policy.text = "0"
host_data = ET.SubElement(launch_sec, "host_data")
host_data.text = secrets.token_hex(32)

devices = ET.SubElement(domain, "devices")
if len(node_context.passthrough_devices) > 0:
Expand Down
12 changes: 10 additions & 2 deletions lisa/sut_orchestrator/libvirt/context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple

import libvirt # type: ignore
Expand Down Expand Up @@ -43,11 +44,18 @@ class DevicePassthroughContext:
managed: str = ""


@dataclass
class GuestVmType(Enum):
Standard = "Standard"
ConfidentialVM = "ConfidentialVM"


@dataclass
class NodeContext:
vm_name: str = ""
firmware_source_path: str = ""
firmware_path: str = ""
kernel_source_path: str = ""
kernel_path: str = ""
guest_vm_type: GuestVmType = GuestVmType.Standard
cloud_init_file_path: str = ""
ignition_file_path: str = ""
os_disk_source_file_path: Optional[str] = None
Expand Down
57 changes: 57 additions & 0 deletions lisa/sut_orchestrator/libvirt/features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from dataclasses import dataclass
from typing import Any, Type, cast

from dataclasses_json import dataclass_json

from lisa import features, schema, search_space
from lisa.environment import Environment
from lisa.features.security_profile import SecurityProfileType
from lisa.sut_orchestrator.libvirt.context import GuestVmType, get_node_context


@dataclass_json()
@dataclass()
class SecurityProfileSettings(features.SecurityProfileSettings):
def __hash__(self) -> int:
return hash(self._get_key())

def _get_key(self) -> str:
return f"{self.type}/{self.security_profile}/"

def _call_requirement_method(
self, method: search_space.RequirementMethod, capability: Any
) -> Any:
super_value: SecurityProfileSettings = super()._call_requirement_method(
method, capability
)
value = SecurityProfileSettings()
value.security_profile = super_value.security_profile
return value


class SecurityProfile(features.SecurityProfile):
_security_profile_mapping = {
SecurityProfileType.Standard: GuestVmType.Standard,
SecurityProfileType.CVM: GuestVmType.ConfidentialVM,
}

def _initialize(self, *args: Any, **kwargs: Any) -> None:
super()._initialize(*args, **kwargs)

@classmethod
def settings_type(cls) -> Type[schema.FeatureSettings]:
return SecurityProfileSettings

@classmethod
def on_before_deployment(cls, *args: Any, **kwargs: Any) -> None:
environment = cast(Environment, kwargs.get("environment"))
settings = kwargs.get("settings")
if not settings:
return
for node in environment.nodes._list:
assert isinstance(settings, SecurityProfileSettings)
assert isinstance(settings.security_profile, SecurityProfileType)
node_context = get_node_context(node)
node_context.guest_vm_type = cls._security_profile_mapping[
settings.security_profile
]
68 changes: 66 additions & 2 deletions lisa/sut_orchestrator/libvirt/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import pycdlib # type: ignore
import yaml

from lisa import schema, search_space
from lisa import feature, schema, search_space
from lisa.environment import Environment
from lisa.feature import Feature
from lisa.node import Node, RemoteNode, local_node_connect
Expand All @@ -42,7 +42,12 @@
Uname,
Whoami,
)
from lisa.util import LisaException, constants, get_public_key_data
from lisa.util import (
LisaException,
NotMeetRequirementException,
constants,
get_public_key_data,
)
from lisa.util.logger import Logger, filter_ansi_escape, get_logger

from . import libvirt_events_thread
Expand All @@ -54,6 +59,7 @@
get_environment_context,
get_node_context,
)
from .features import SecurityProfile, SecurityProfileSettings
from .platform_interface import IBaseLibvirtPlatform
from .schema import (
FIRMWARE_TYPE_BIOS,
Expand Down Expand Up @@ -89,6 +95,7 @@ class BaseLibvirtPlatform(Platform, IBaseLibvirtPlatform):
_supported_features: List[Type[Feature]] = [
SerialConsole,
StartStop,
SecurityProfile,
]

def __init__(self, runbook: schema.Platform) -> None:
Expand Down Expand Up @@ -193,6 +200,36 @@ def _prepare_environment(self, environment: Environment, log: Logger) -> bool:

self._configure_environment(environment, log)

if environment.runbook.nodes_requirement:
for node_space in environment.runbook.nodes_requirement:
new_settings = search_space.SetSpace[schema.FeatureSettings](
is_allow_set=True
)
if node_space.features:
for current_settings in node_space.features.items:
# reload to type specified settings
try:
settings_type = feature.get_feature_settings_type_by_name(
current_settings.type,
BaseLibvirtPlatform.supported_features(),
)
except NotMeetRequirementException as identifier:
raise LisaException(
f"platform doesn't support all features. {identifier}"
)
new_setting = schema.load_by_type(
settings_type, current_settings
)
existing_setting = feature.get_feature_settings_by_name(
new_setting.type, new_settings, True
)
if existing_setting:
new_settings.remove(existing_setting)
new_setting = existing_setting.intersect(new_setting)

new_settings.add(new_setting)
node_space.features = new_settings

return self._configure_node_capabilities(environment, log)

def _deploy_environment(self, environment: Environment, log: Logger) -> None:
Expand Down Expand Up @@ -312,10 +349,12 @@ def _create_node_capabilities(
node_capabilities.network_interface.max_nic_count = 1
node_capabilities.network_interface.nic_count = 1
node_capabilities.gpu_count = 0
security_profile_setting = SecurityProfileSettings()
node_capabilities.features = search_space.SetSpace[schema.FeatureSettings](
is_allow_set=True,
items=[
schema.FeatureSettings.create(SerialConsole.name()),
security_profile_setting,
],
)

Expand Down Expand Up @@ -564,6 +603,31 @@ def _create_nodes(
log: Logger,
) -> None:
self.host_node.shell.mkdir(Path(self.vm_disks_dir), exist_ok=True)
features_settings: Dict[str, schema.FeatureSettings] = {}

# collect all the features to handle special deployment logic. If one
# node has this, it needs to run.
nodes_requirement = environment.runbook.nodes_requirement
if nodes_requirement:
for node_space in nodes_requirement:
if not node_space.features:
continue
for feature_setting in node_space.features:
if feature_setting.type not in features_settings:
features_settings[feature_setting.type] = feature_setting

# change deployment for each feature.
for feature_type, setting in [
(t, s)
for t in self.supported_features()
for s in features_settings.values()
if t.name() == s.type
]:
feature_type.on_before_deployment(
environment=environment,
log=log,
settings=setting,
)

for node in environment.nodes.list():
node_context = get_node_context(node)
Expand Down
12 changes: 11 additions & 1 deletion lisa/sut_orchestrator/libvirt/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,20 @@ def __post_init__(self) -> None:
self.disk_img_format = DiskImageFormat.QCOW2.value


@dataclass_json()
@dataclass
class KernelSchema:
path: str = ""
is_remote_path: bool = False


@dataclass_json()
@dataclass
class CloudHypervisorNodeSchema(BaseLibvirtNodeSchema):
# DEPRECATED: use the 'kernel' field instead.
# Local path to the cloud-hypervisor firmware.
# Can be obatained from:
# Can be obtained from:
# https://github.com/cloud-hypervisor/rust-hypervisor-firmware
firmware: str = ""
# Local or remote path to the cloud-hypervisor kernel.
kernel: Optional[KernelSchema] = None
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ flake8 = [
]

isort = [
"isort ~= 5.12.0",
"isort ~= 5.13.2",
]

legacy = [
Expand Down