Skip to content

Commit 49fd5c0

Browse files
committed
Add virtualization feature
1 parent f0ba1c2 commit 49fd5c0

File tree

8 files changed

+271
-3
lines changed

8 files changed

+271
-3
lines changed

lisa/features/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
)
3535
from .serial_console import SerialConsole
3636
from .startstop import StartStop, StopState, VMStatus
37+
from .virtualization import Virtualization
3738

3839
__all__ = [
3940
"ACC",
@@ -73,4 +74,5 @@
7374
"VMStatus",
7475
"Synthetic",
7576
"StartStop",
77+
"Virtualization",
7678
]

lisa/features/virtualization.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT license.
3+
4+
from functools import partial
5+
from typing import Any, Optional, Type
6+
7+
from lisa import schema, search_space
8+
from lisa.feature import Feature
9+
from lisa.util import constants
10+
11+
12+
class Virtualization(Feature):
13+
"""
14+
Virtualization platform detection and filtering feature.
15+
16+
This feature identifies the hypervisor or virtualization environment where
17+
the test is running and allows tests to specify compatibility requirements.
18+
The host_type field indicates which virtualization platform is in use.
19+
20+
The host_type field can contain these values:
21+
- BareMetal: Physical hardware without virtualization
22+
- HyperV: Microsoft Hyper-V hypervisor
23+
- QEMU: QEMU/KVM virtualization stack
24+
- CloudHypervisor: Cloud Hypervisor
25+
- None: Unknown
26+
27+
Tests can use this feature to ensure they only run on supported
28+
hypervisors or to skip execution on platforms where they have known
29+
compatibility issues.
30+
"""
31+
32+
@classmethod
33+
def settings_type(cls) -> Type[schema.FeatureSettings]:
34+
return schema.VirtualizationSettings
35+
36+
@classmethod
37+
def name(cls) -> str:
38+
return constants.FEATURE_VIRTUALIZATION
39+
40+
@classmethod
41+
def can_disable(cls) -> bool:
42+
return False
43+
44+
def enabled(self) -> bool:
45+
return True
46+
47+
@classmethod
48+
def create_setting(
49+
cls, *args: Any, **kwargs: Any
50+
) -> Optional[schema.FeatureSettings]:
51+
"""
52+
Base implementation returns platform-agnostic settings.
53+
54+
Platforms should override this to detect their actual hypervisor.
55+
"""
56+
return schema.VirtualizationSettings(host_type=None)
57+
58+
59+
# Helper functions for creating virtualization requirements
60+
61+
62+
def _single_host_type_requirement(
63+
host_type: schema.VirtualizationHostType,
64+
) -> partial[schema.VirtualizationSettings]:
65+
"""
66+
Create a virtualization requirement for a specific host type.
67+
68+
This helper function creates a partial VirtualizationSettings that can be
69+
used as a test requirement to filter tests by virtualization platform.
70+
71+
Args:
72+
host_type: The specific virtualization host type to require
73+
74+
Returns:
75+
Partial VirtualizationSettings configured for the specified host type
76+
"""
77+
return partial(
78+
schema.VirtualizationSettings,
79+
host_type=search_space.SetSpace(True, [host_type]),
80+
)
81+
82+
83+
# Host type requirements - predefined partial functions for common use cases
84+
BareMetalHostType = _single_host_type_requirement(
85+
schema.VirtualizationHostType.BareMetal
86+
)
87+
HyperVHostType = _single_host_type_requirement(schema.VirtualizationHostType.HyperV)
88+
QemuHostType = _single_host_type_requirement(schema.VirtualizationHostType.QEMU)
89+
CloudHypervisorHostType = _single_host_type_requirement(
90+
schema.VirtualizationHostType.CloudHypervisor
91+
)

lisa/microsoft/testsuites/core/azure_image_standard.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
)
2020
from lisa.base_tools.uname import Uname
2121
from lisa.features import Disk
22+
from lisa.features.virtualization import HyperVHostType
2223
from lisa.operating_system import (
2324
BSD,
2425
SLES,
@@ -721,7 +722,10 @@ def verify_os_update(self, node: Node) -> None:
721722
has length greater than zero.
722723
""",
723724
priority=2,
724-
requirement=simple_requirement(supported_platform_type=[AZURE, READY, HYPERV]),
725+
requirement=simple_requirement(
726+
supported_platform_type=[AZURE, READY, HYPERV],
727+
supported_features=[HyperVHostType()],
728+
),
725729
)
726730
def verify_hv_kvp_daemon_installed(self, node: Node) -> None:
727731
if isinstance(node.os, Debian):

lisa/schema.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,22 @@ class ResourceDiskType(str, Enum):
451451
NVME = constants.STORAGE_INTERFACE_TYPE_NVME
452452

453453

454+
class VirtualizationHostType(str, Enum):
455+
"""
456+
Virtualization host type enumeration.
457+
Defines the type of hypervisor or virtualization platform being used.
458+
"""
459+
460+
# Physical hardware without virtualization
461+
BareMetal = "BareMetal"
462+
# Microsoft Hyper-V hypervisor
463+
HyperV = "HyperV"
464+
# QEMU/KVM virtualization
465+
QEMU = "QEMU"
466+
# Cloud Hypervisor
467+
CloudHypervisor = "CloudHypervisor"
468+
469+
454470
disk_controller_type_priority: List[DiskControllerType] = [
455471
DiskControllerType.SCSI,
456472
DiskControllerType.NVME,
@@ -847,6 +863,104 @@ def _call_requirement_method(
847863
return value
848864

849865

866+
@dataclass_json()
867+
@dataclass()
868+
class VirtualizationSettings(FeatureSettings):
869+
"""
870+
Virtualization feature settings to specify the host type.
871+
Used to indicate the type of hypervisor or virtualization platform.
872+
"""
873+
874+
type: str = constants.FEATURE_VIRTUALIZATION
875+
# Host type - specifies the virtualization platform being used
876+
# Default is None (no restrictions/requirements)
877+
host_type: Optional[
878+
Union[
879+
search_space.SetSpace[VirtualizationHostType],
880+
VirtualizationHostType,
881+
]
882+
] = field(
883+
default=None,
884+
metadata=field_metadata(
885+
decoder=partial(
886+
search_space.decode_nullable_set_space,
887+
base_type=VirtualizationHostType,
888+
default_values=[],
889+
)
890+
),
891+
)
892+
893+
def __eq__(self, o: object) -> bool:
894+
assert isinstance(o, VirtualizationSettings), f"actual: {type(o)}"
895+
return super().__eq__(o) and self.host_type == o.host_type
896+
897+
def __hash__(self) -> int:
898+
return hash(self._get_key())
899+
900+
def _get_key(self) -> str:
901+
# Include host_type in key for different hash values
902+
host_type_str = str(self.host_type) if self.host_type else "None"
903+
return f"{self.type}_{host_type_str}"
904+
905+
def __repr__(self) -> str:
906+
return f"type:{self.type}, " f"host_type:{self.host_type}"
907+
908+
def check(self, capability: Any) -> search_space.ResultReason:
909+
result = super().check(capability)
910+
assert isinstance(capability, VirtualizationSettings)
911+
912+
# Check host_type compatibility
913+
if self.host_type is not None:
914+
if capability.host_type is None:
915+
# Requirement specifies host_type but capability doesn't provide it
916+
result.add_reason(
917+
f"requirement specifies host_type {self.host_type} "
918+
f"but capability has no host_type specified"
919+
)
920+
else:
921+
# Both have host_type, check compatibility
922+
result.merge(
923+
search_space.check_setspace(self.host_type, capability.host_type)
924+
)
925+
926+
return result
927+
928+
def _call_requirement_method(
929+
self, method: search_space.RequirementMethod, capability: Any
930+
) -> Any:
931+
assert isinstance(capability, VirtualizationSettings)
932+
value = type(self)()
933+
934+
# Handle host_type intersection/generation based on method
935+
if self.host_type is not None and capability.host_type is not None:
936+
# Convert single values to SetSpace if needed
937+
if isinstance(self.host_type, VirtualizationHostType):
938+
self_host_type = search_space.SetSpace(
939+
is_allow_set=True, items=[self.host_type]
940+
)
941+
else:
942+
self_host_type = self.host_type
943+
944+
if isinstance(capability.host_type, VirtualizationHostType):
945+
cap_host_type = search_space.SetSpace(
946+
is_allow_set=True, items=[capability.host_type]
947+
)
948+
else:
949+
cap_host_type = capability.host_type
950+
951+
# Use SetSpace methods directly
952+
if method == search_space.RequirementMethod.intersect:
953+
value.host_type = self_host_type.intersect(cap_host_type)
954+
elif method == search_space.RequirementMethod.generate_min_capability:
955+
value.host_type = self_host_type.generate_min_capability(cap_host_type)
956+
elif capability.host_type is not None:
957+
value.host_type = capability.host_type
958+
else:
959+
value.host_type = self.host_type
960+
961+
return value
962+
963+
850964
@dataclass_json()
851965
@dataclass()
852966
class FeaturesSpace(

lisa/sut_orchestrator/azure/features.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3863,3 +3863,31 @@ def _prepare_azure_file_share(
38633863
sudo=True,
38643864
append=True,
38653865
)
3866+
3867+
3868+
class Virtualization(AzureFeatureMixin, features.Virtualization):
3869+
"""
3870+
Azure-specific implementation of Virtualization feature.
3871+
3872+
Automatically sets host_type to HyperV for Azure VMs since Azure
3873+
uses Microsoft Hyper-V hypervisor technology as its underlying
3874+
virtualization platform. Azure's hypervisor is based on Hyper-V.
3875+
"""
3876+
3877+
@classmethod
3878+
def create_setting(
3879+
cls, *args: Any, **kwargs: Any
3880+
) -> Optional[schema.FeatureSettings]:
3881+
"""
3882+
Create Azure virtualization settings.
3883+
3884+
Azure VMs always run on Microsoft Hyper-V hypervisor technology,
3885+
so we set host_type to HyperV by default. Azure's underlying
3886+
virtualization is based on the Hyper-V hypervisor.
3887+
3888+
Returns:
3889+
VirtualizationSettings with host_type=HyperV
3890+
"""
3891+
return schema.VirtualizationSettings(
3892+
host_type=schema.VirtualizationHostType.HyperV
3893+
)

lisa/sut_orchestrator/azure/platform_.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ def supported_features(cls) -> List[Type[feature.Feature]]:
513513
features.Availability,
514514
features.Infiniband,
515515
features.Hibernation,
516+
features.Virtualization,
516517
]
517518

518519
def _prepare_environment(self, environment: Environment, log: Logger) -> bool:

lisa/sut_orchestrator/hyperv/platform_.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import PurePath
55
from typing import Any, List, Optional, Type, cast
66

7-
from lisa import feature, schema, search_space
7+
from lisa import feature, features, schema, search_space
88
from lisa.environment import Environment
99
from lisa.node import RemoteNode
1010
from lisa.platform_ import Platform
@@ -36,7 +36,7 @@ def type_name(cls) -> str:
3636

3737
@classmethod
3838
def supported_features(cls) -> List[Type[feature.Feature]]:
39-
return [SerialConsole]
39+
return [SerialConsole, Virtualization]
4040

4141
def _initialize(self, *args: Any, **kwargs: Any) -> None:
4242
self._hyperv_runbook = self._get_hyperv_runbook()
@@ -383,3 +383,29 @@ def _delete_nodes(self, environment: Environment, log: Logger) -> None:
383383
for node in environment.nodes.list()
384384
]
385385
)
386+
387+
388+
class Virtualization(features.Virtualization):
389+
"""
390+
HyperV-specific implementation of Virtualization feature.
391+
392+
Automatically sets host_type to HyperV since nodes on HyperV platform
393+
are always running under Microsoft Hyper-V hypervisor.
394+
"""
395+
396+
@classmethod
397+
def create_setting(
398+
cls, *args: Any, **kwargs: Any
399+
) -> Optional[schema.FeatureSettings]:
400+
"""
401+
Create HyperV virtualization settings.
402+
403+
HyperV VMs always run under Microsoft Hyper-V hypervisor, so we set
404+
host_type to HyperV by default.
405+
406+
Returns:
407+
VirtualizationSettings with host_type=HyperV
408+
"""
409+
return schema.VirtualizationSettings(
410+
host_type=schema.VirtualizationHostType.HyperV
411+
)

lisa/util/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343

4444
# feature names
4545
FEATURE_DISK = "Disk"
46+
# Virtualization feature to specify host type (BareMetal, HyperV, etc.)
47+
FEATURE_VIRTUALIZATION = "Virtualization"
4648

4749
# list types
4850
LIST = "list"

0 commit comments

Comments
 (0)