Skip to content
Merged
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
11 changes: 10 additions & 1 deletion .github/workflows/continuous-integration-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- main
jobs:
Check:
timeout-minutes: 40
continue-on-error: ${{ matrix.optional || false }}
runs-on: ${{ matrix.os }}
name: >-
Expand All @@ -18,8 +19,16 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04, windows-2019]
python-version: ['3.9', '3.10']
python-version: ['3.9', '3.10', '3.11', '3.12-dev']
nox-session: [test, example]

exclude:
# Azure installation (Win, 3.12) currently hangs due to excessive pip backtracking
# Disable unit tests and retry in 2Q23
- python-version: '3.12-dev'
os: windows-2019
nox-session: test

include:

- os: ubuntu-latest
Expand Down
12 changes: 5 additions & 7 deletions docs/installation_linux.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Install LISA on Linux
Minimum System Requirements
---------------------------

1. Your favorite Linux distribution supporting Python 3.8 - 3.10
1. Your favorite Linux distribution supporting Python 3.8 or above
2. Dual core processor
3. 4 GB system memory

Expand All @@ -15,8 +15,8 @@ The following commands assume Ubuntu is being used.
Install Python on Linux
-----------------------

LISA has been tested to work with `Python 3.8 - 3.10 64-bit <https://www.python.org/>`__.
Python 3.10 is recommended. Support for 3.11+ is under development.
LISA has been tested to work with `Python >=3.8 64-bit <https://www.python.org/>`__.
Python 3.11 is recommended.
If you find that LISA is not compatible with a supported version,
`please file an issue <https://github.com/microsoft/lisa/issues/new>`__.

Expand All @@ -29,14 +29,12 @@ To check which version of Python is used on your system, run the following:
If you need to install a different Python package, there are likely packaged versions for
your distro.

Here is an example to install Python 3.10 on Ubuntu 20.04
Here is an example to install Python 3.11 on Ubuntu 22.04

.. code:: bash

sudo apt update
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt install python3.10 python3.10-dev -y
sudo apt install python3.11 python3.11-dev -y


Install system dependencies
Expand Down
2 changes: 1 addition & 1 deletion docs/installation_windows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The full installer allows greater customization and doesn't have the security re
of the Microsoft Store packages, so may be preferred in some situations.

Navigate to `Python releases for Windows <https://www.python.org/downloads/windows/>`__.
Download and install *Windows installer (64-bit)* for Python 3.8 - 3.10 64-bit.
Download and install *Windows installer (64-bit)* for Python 3.8 64-bit or above.

More information on the full installer, including installation without a GUI,
can be found `here <https://docs.python.org/3/using/windows.html#the-full-installer>`_.
Expand Down
2 changes: 1 addition & 1 deletion lisa/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def _get_environment_id() -> int:
class EnvironmentMessage(MessageBase):
type: str = "Environment"
name: str = ""
runbook: schema.Environment = schema.Environment()
runbook: schema.Environment = field(default_factory=schema.Environment)
status: EnvironmentStatus = EnvironmentStatus.New
log_folder: Path = Path()

Expand Down
3 changes: 2 additions & 1 deletion lisa/features/nvme.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import re
from dataclasses import dataclass, field
from functools import partial
from typing import Any, List, Type

from dataclasses_json import dataclass_json
Expand Down Expand Up @@ -93,7 +94,7 @@ def _get_device_from_ls(self, force_run: bool = False) -> None:
class NvmeSettings(FeatureSettings):
type: str = "Nvme"
disk_count: search_space.CountSpace = field(
default=search_space.IntRange(min=0),
default_factory=partial(search_space.IntRange, min=0),
metadata=field_metadata(decoder=search_space.decode_count_space),
)

Expand Down
6 changes: 4 additions & 2 deletions lisa/features/security_profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ def __hash__(self) -> int:
def _get_key(self) -> str:
return f"{self.type}/{self.security_profile}"

def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
def _call_requirement_method(
self, method: search_space.RequirementMethod, capability: Any
) -> Any:
value = SecurityProfileSettings()
value.security_profile = getattr(
search_space, f"{method_name}_setspace_by_priority"
search_space, f"{method.value}_setspace_by_priority"
)(
self.security_profile,
capability.security_profile,
Expand Down
66 changes: 37 additions & 29 deletions lisa/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,15 @@ def check(self, capability: Any) -> search_space.ResultReason:
def _get_key(self) -> str:
return self.type

def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
def _call_requirement_method(
self, method: search_space.RequirementMethod, capability: Any
) -> Any:
assert isinstance(capability, FeatureSettings), f"actual: {type(capability)}"
# default FeatureSetting is a place holder, nothing to do.
value = FeatureSettings.create(self.type)

# try best to intersect the extended schemas
if method_name == search_space.RequirementMethod.intersect:
if method == search_space.RequirementMethod.intersect:
if self.extended_schemas and capability and capability.extended_schemas:
value.extended_schemas = deep_update_dict(
self.extended_schemas,
Expand Down Expand Up @@ -560,20 +562,22 @@ def _get_key(self) -> str:
f"{self.disk_controller_type}"
)

def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
def _call_requirement_method(
self, method: search_space.RequirementMethod, capability: Any
) -> Any:
assert isinstance(capability, DiskOptionSettings), f"actual: {type(capability)}"
parent_value = super()._call_requirement_method(method_name, capability)
parent_value = super()._call_requirement_method(method, capability)

# convert parent type to child type
value = DiskOptionSettings()
value.extended_schemas = parent_value.extended_schemas

search_space_countspace_method = getattr(
search_space, f"{method_name}_countspace"
search_space, f"{method.value}_countspace"
)
if self.disk_type or capability.disk_type:
value.disk_type = getattr(
search_space, f"{method_name}_setspace_by_priority"
search_space, f"{method.value}_setspace_by_priority"
)(self.disk_type, capability.disk_type, disk_type_priority)
if self.data_disk_count or capability.data_disk_count:
value.data_disk_count = search_space_countspace_method(
Expand All @@ -597,7 +601,7 @@ def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
)
if self.disk_controller_type or capability.disk_controller_type:
value.disk_controller_type = getattr(
search_space, f"{method_name}_setspace_by_priority"
search_space, f"{method.value}_setspace_by_priority"
)(
self.disk_controller_type,
capability.disk_controller_type,
Expand Down Expand Up @@ -705,28 +709,30 @@ def check(self, capability: Any) -> search_space.ResultReason:

return result

def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
def _call_requirement_method(
self, method: search_space.RequirementMethod, capability: Any
) -> Any:
assert isinstance(
capability, NetworkInterfaceOptionSettings
), f"actual: {type(capability)}"
parent_value = super()._call_requirement_method(method_name, capability)
parent_value = super()._call_requirement_method(method, capability)

# convert parent type to child type
value = NetworkInterfaceOptionSettings()
value.extended_schemas = parent_value.extended_schemas

value.max_nic_count = getattr(search_space, f"{method_name}_countspace")(
value.max_nic_count = getattr(search_space, f"{method.value}_countspace")(
self.max_nic_count, capability.max_nic_count
)

if self.nic_count or capability.nic_count:
value.nic_count = getattr(search_space, f"{method_name}_countspace")(
value.nic_count = getattr(search_space, f"{method.value}_countspace")(
self.nic_count, capability.nic_count
)
else:
raise LisaException("nic_count cannot be zero")

value.data_path = getattr(search_space, f"{method_name}_setspace_by_priority")(
value.data_path = getattr(search_space, f"{method.value}_setspace_by_priority")(
self.data_path, capability.data_path, _network_data_path_priority
)
return value
Expand Down Expand Up @@ -758,21 +764,21 @@ class NodeSpace(search_space.RequirementMixin, TypedSchema, ExtendableSchemaMixi
name: str = ""
is_default: bool = field(default=False)
node_count: search_space.CountSpace = field(
default=search_space.IntRange(min=1),
default_factory=partial(search_space.IntRange, min=1),
metadata=field_metadata(decoder=search_space.decode_count_space),
)
core_count: search_space.CountSpace = field(
default=search_space.IntRange(min=1),
default_factory=partial(search_space.IntRange, min=1),
metadata=field_metadata(decoder=search_space.decode_count_space),
)
memory_mb: search_space.CountSpace = field(
default=search_space.IntRange(min=512),
default_factory=partial(search_space.IntRange, min=512),
metadata=field_metadata(decoder=search_space.decode_count_space),
)
disk: Optional[DiskOptionSettings] = None
network_interface: Optional[NetworkInterfaceOptionSettings] = None
gpu_count: search_space.CountSpace = field(
default=search_space.IntRange(min=0),
default_factory=partial(search_space.IntRange, min=0),
metadata=field_metadata(decoder=search_space.decode_count_space),
)
# all features on requirement should be included.
Expand Down Expand Up @@ -961,7 +967,9 @@ def has_feature(self, find_type: str) -> bool:

return any(feature for feature in self.features if feature.type == find_type)

def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
def _call_requirement_method(
self, method: search_space.RequirementMethod, capability: Any
) -> Any:
assert isinstance(capability, NodeSpace), f"actual: {type(capability)}"

# copy to duplicate extended schema
Expand All @@ -973,40 +981,40 @@ def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
# capability can have more node
value.node_count = capability.node_count
else:
value.node_count = getattr(search_space, f"{method_name}_countspace")(
value.node_count = getattr(search_space, f"{method.value}_countspace")(
self.node_count, capability.node_count
)
else:
raise LisaException("node_count cannot be zero")
if self.core_count or capability.core_count:
value.core_count = getattr(search_space, f"{method_name}_countspace")(
value.core_count = getattr(search_space, f"{method.value}_countspace")(
self.core_count, capability.core_count
)
else:
raise LisaException("core_count cannot be zero")
if self.memory_mb or capability.memory_mb:
value.memory_mb = getattr(search_space, f"{method_name}_countspace")(
value.memory_mb = getattr(search_space, f"{method.value}_countspace")(
self.memory_mb, capability.memory_mb
)
else:
raise LisaException("memory_mb cannot be zero")
if self.disk or capability.disk:
value.disk = getattr(search_space, method_name)(self.disk, capability.disk)
value.disk = getattr(search_space, method.value)(self.disk, capability.disk)
if self.network_interface or capability.network_interface:
value.network_interface = getattr(search_space, method_name)(
value.network_interface = getattr(search_space, method.value)(
self.network_interface, capability.network_interface
)

if self.gpu_count or capability.gpu_count:
value.gpu_count = getattr(search_space, f"{method_name}_countspace")(
value.gpu_count = getattr(search_space, f"{method.value}_countspace")(
self.gpu_count, capability.gpu_count
)
else:
value.gpu_count = 0

if (
capability.features
and method_name == search_space.RequirementMethod.generate_min_capability
and method == search_space.RequirementMethod.generate_min_capability
):
# The requirement features are ignored, if cap doesn't have it.
value.features = search_space.SetSpace[FeatureSettings](is_allow_set=True)
Expand All @@ -1018,11 +1026,11 @@ def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
self._find_feature_by_type(capability_feature.type, self.features)
or capability_feature
)
current_feature = getattr(requirement_feature, method_name)(
current_feature = getattr(requirement_feature, method.value)(
capability_feature
)
value.features.add(current_feature)
elif method_name == search_space.RequirementMethod.intersect and (
elif method == search_space.RequirementMethod.intersect and (
capability.features or self.features
):
# This is a hack to work with lisa_runner. The capability features
Expand All @@ -1032,7 +1040,7 @@ def _call_requirement_method(self, method_name: str, capability: Any) -> Any:

if (
capability.excluded_features
and method_name == search_space.RequirementMethod.generate_min_capability
and method == search_space.RequirementMethod.generate_min_capability
):
# TODO: the min value for excluded feature is not clear. It may need
# to be improved with real scenarios.
Expand All @@ -1049,11 +1057,11 @@ def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
)
or capability_feature
)
current_feature = getattr(requirement_feature, method_name)(
current_feature = getattr(requirement_feature, method.value)(
capability_feature
)
value.excluded_features.add(current_feature)
elif method_name == search_space.RequirementMethod.intersect and (
elif method == search_space.RequirementMethod.intersect and (
capability.excluded_features or self.excluded_features
):
# This is a hack to work with lisa_runner. The capability features
Expand Down
20 changes: 11 additions & 9 deletions lisa/search_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
T = TypeVar("T")


class RequirementMethod(str, Enum):
class RequirementMethod(Enum):
generate_min_capability: str = "generate_min_capability"
intersect: str = "intersect"

Expand Down Expand Up @@ -66,18 +66,20 @@ def intersect(self, capability: Any) -> Any:
self._validate_result(capability)
return self._intersect(capability)

def _call_requirement_method(self, method_name: str, capability: Any) -> Any:
raise NotImplementedError(method_name)
def _call_requirement_method(
self, method: RequirementMethod, capability: Any
) -> Any:
raise NotImplementedError(method)

def _generate_min_capability(self, capability: Any) -> Any:
return self._call_requirement_method(
method_name=RequirementMethod.generate_min_capability,
method=RequirementMethod.generate_min_capability,
capability=capability,
)

def _intersect(self, capability: Any) -> Any:
return self._call_requirement_method(
method_name=RequirementMethod.intersect, capability=capability
method=RequirementMethod.intersect, capability=capability
)

def _validate_result(self, capability: Any) -> None:
Expand Down Expand Up @@ -619,14 +621,14 @@ def check(


def _call_requirement_method(
method: str,
method: RequirementMethod,
requirement: Union[T_SEARCH_SPACE, List[T_SEARCH_SPACE], None],
capability: Union[T_SEARCH_SPACE, List[T_SEARCH_SPACE], None],
) -> Any:
check_result = check(requirement, capability)
if not check_result.result:
raise NotMeetRequirementException(
f"cannot call {method}, capability doesn't support requirement"
f"cannot call {method.value}, capability doesn't support requirement"
)

result: Optional[T_SEARCH_SPACE] = None
Expand All @@ -641,15 +643,15 @@ def _call_requirement_method(
for req_item in requirement:
temp_result = req_item.check(capability)
if temp_result.result:
temp_min = getattr(req_item, method)(capability)
temp_min = getattr(req_item, method.value)(capability)
if result is None:
result = temp_min
else:
# TODO: multiple matches found, not supported well yet
# It can be improved by implement __eq__, __lt__ functions.
result = min(result, temp_min)
elif requirement is not None:
result = getattr(requirement, method)(capability)
result = getattr(requirement, method.value)(capability)

return result

Expand Down
Loading