From 1b4d8dd741155e628a35695a8e1cb06791ce3ea4 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 10:38:54 +0000 Subject: [PATCH 01/11] enroll: update docstring for flavor matching --- .../understack_workflows/main/enroll_server.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/understack-workflows/understack_workflows/main/enroll_server.py b/python/understack-workflows/understack_workflows/main/enroll_server.py index c9220384..1dc843f9 100644 --- a/python/understack-workflows/understack_workflows/main/enroll_server.py +++ b/python/understack-workflows/understack_workflows/main/enroll_server.py @@ -64,6 +64,7 @@ def main(): - from BMC, discover basic hardware info: - manufacturer, model number, serial number + - CPU model(s), RAM size and local storage - list ethernet interfaces with: - name like BMC or SLOT.NIC.1-1 - MAC address @@ -85,10 +86,11 @@ def main(): - create BMC IP address assignment for BMC interface - convert our type "dhcp" IP Address to type "host" and associate it with the interface - - Find or create this baremetal node in Ironic + - Determine flavor of the server based on the information collected from BMC + - Find or create this baremetal node in Ironic - create ports with MACs (omit BMC port) and set one to PXE - TODO advance to available state - - TODO set flavor? what else? + - set flavor """ args = argument_parser().parse_args() From dffc1c4c03092ace246fc728c5c91eb96d7abbef Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 10:39:16 +0000 Subject: [PATCH 02/11] enroll/bmc: collect RAM and CPU info --- python/understack-workflows/tests/test_bmc_chassis_info.py | 2 ++ python/understack-workflows/tests/test_nautobot_device.py | 2 ++ .../understack_workflows/bmc_chassis_info.py | 4 ++++ .../understack-workflows/understack_workflows/ironic_node.py | 1 + 4 files changed, 9 insertions(+) diff --git a/python/understack-workflows/tests/test_bmc_chassis_info.py b/python/understack-workflows/tests/test_bmc_chassis_info.py index 04269934..c198ba9f 100644 --- a/python/understack-workflows/tests/test_bmc_chassis_info.py +++ b/python/understack-workflows/tests/test_bmc_chassis_info.py @@ -131,4 +131,6 @@ def test_chassis_info_R7615(): remote_switch_port_name="Ethernet1/7", ), ], + memory_gib=96, + cpu="AMD EPYC 9124 16-Core Processor", ) diff --git a/python/understack-workflows/tests/test_nautobot_device.py b/python/understack-workflows/tests/test_nautobot_device.py index ab12ac2c..5c4d39aa 100644 --- a/python/understack-workflows/tests/test_nautobot_device.py +++ b/python/understack-workflows/tests/test_nautobot_device.py @@ -119,6 +119,8 @@ def test_find_or_create(dell_nautobot_device): bios_version="1.6.10", bmc_ip_address="1.2.3.4", power_on=True, + memory_gib=96, + cpu="AMD EPYC 666 444-Core Processor", interfaces=[ InterfaceInfo( name="iDRAC", diff --git a/python/understack-workflows/understack_workflows/bmc_chassis_info.py b/python/understack-workflows/understack_workflows/bmc_chassis_info.py index c34e6102..ce425abd 100644 --- a/python/understack-workflows/understack_workflows/bmc_chassis_info.py +++ b/python/understack-workflows/understack_workflows/bmc_chassis_info.py @@ -32,6 +32,8 @@ class ChassisInfo: bios_version: str power_on: bool interfaces: list[InterfaceInfo] + memory_gib: int + cpu: str @property def bmc_interface(self) -> InterfaceInfo: @@ -79,7 +81,9 @@ def chassis_info(bmc: Bmc) -> ChassisInfo: bios_version=chassis_data["BiosVersion"], power_on=(chassis_data["PowerState"] == "On"), bmc_ip_address=bmc.ip_address, + memory_gib=chassis_data.get("MemorySummary", {}).get("TotalSystemMemoryGiB", 0), interfaces=interfaces, + cpu=chassis_data.get("ProcessorSummary", {}).get("Model", ""), ) diff --git a/python/understack-workflows/understack_workflows/ironic_node.py b/python/understack-workflows/understack_workflows/ironic_node.py index 47e6c852..1ce1fdf8 100644 --- a/python/understack-workflows/understack_workflows/ironic_node.py +++ b/python/understack-workflows/understack_workflows/ironic_node.py @@ -1,4 +1,5 @@ import ironicclient.common.apiclient.exceptions +from flavor_matcher.flavor_spec import dataclass from ironicclient.common.utils import args_array_to_patch from understack_workflows.bmc import Bmc From 3388a764849794adeae273246f775eed8e694476 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 11:59:44 +0000 Subject: [PATCH 03/11] enroll: add support for obtaining disk info --- .../bmc_chassis_info/bmc_disk.json | 148 ++++++++++++++++++ .../tests/test_bmc_disk.py | 51 ++++++ .../understack_workflows/bmc_disk.py | 51 ++++++ 3 files changed, 250 insertions(+) create mode 100644 python/understack-workflows/tests/json_samples/bmc_chassis_info/bmc_disk.json create mode 100644 python/understack-workflows/tests/test_bmc_disk.py create mode 100644 python/understack-workflows/understack_workflows/bmc_disk.py diff --git a/python/understack-workflows/tests/json_samples/bmc_chassis_info/bmc_disk.json b/python/understack-workflows/tests/json_samples/bmc_chassis_info/bmc_disk.json new file mode 100644 index 00000000..797c757d --- /dev/null +++ b/python/understack-workflows/tests/json_samples/bmc_chassis_info/bmc_disk.json @@ -0,0 +1,148 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Drive.Drive", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Drives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1", + "@odata.type": "#Drive.v1_17_0.Drive", + "Actions": { + "#Drive.SecureErase": { + "@Redfish.OperationApplyTimeSupport": { + "@odata.type": "#Settings.v1_3_5.OperationApplyTimeSupport", + "SupportedValues": [ + "Immediate", + "OnReset" + ] + }, + "target": "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Drives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1/Actions/Drive.SecureErase" + } + }, + "Assembly": { + "@odata.id": "/redfish/v1/Chassis/System.Embedded.1/Assembly" + }, + "BlockSizeBytes": 512, + "CapableSpeedGbs": 6, + "CapacityBytes": 479559942144, + "Description": "Disk 1 in Backplane 1 of RAID Controller in SL 1", + "EncryptionAbility": "None", + "EncryptionStatus": "Unencrypted", + "FailurePredicted": false, + "HotspareType": "None", + "Id": "Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1", + "Identifiers": [ + { + "DurableName": "500056b3de2d55c1", + "DurableNameFormat": "NAA" + } + ], + "Identifiers@odata.count": 1, + "Links": { + "Chassis": { + "@odata.id": "/redfish/v1/Chassis/Enclosure.Internal.0-1:RAID.SL.1-1" + }, + "Oem": { + "Dell": { + "@odata.type": "#DellOem.v1_3_0.DellOemLinks", + "CPUAffinity": [ + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Processors/CPU.Socket.1" + } + ], + "CPUAffinity@odata.count": 1 + } + }, + "PCIeFunctions": [], + "PCIeFunctions@odata.count": 0, + "Storage": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1" + }, + "Volumes": [ + { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Volumes/Disk.Virtual.238:RAID.SL.1-1" + } + ], + "Volumes@odata.count": 1 + }, + "Location": [], + "LocationIndicatorActive": null, + "Manufacturer": "MICRON", + "MediaType": "SSD", + "Model": "MTFDDAK480TDS", + "Name": "Solid State Disk 0:1:1", + "NegotiatedSpeedGbs": 6, + "Oem": { + "Dell": { + "@odata.type": "#DellDrive.v1_1_0.DellDrive", + "DellDriveSMARTAttributes": { + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Drives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1/Oem/Dell/DellDriveSMARTAttributes" + }, + "DellPhysicalDisk": { + "@odata.context": "/redfish/v1/$metadata#DellPhysicalDisk.DellPhysicalDisk", + "@odata.id": "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Drives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1/Oem/Dell/DellDrives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1", + "@odata.type": "#DellPhysicalDisk.v1_7_0.DellPhysicalDisk", + "AvailableSparePercent": null, + "Certified": "Yes", + "Connector": 0, + "CryptographicEraseCapable": "Capable", + "Description": "An instance of DellPhysicalDisk will have Physical Disk specific data.", + "DeviceProtocol": null, + "DeviceSidebandProtocol": null, + "DriveFormFactor": "2.5Inch", + "EncryptionProtocol": "None", + "ErrorDescription": null, + "ErrorRecoverable": "NotApplicable", + "ForeignKeyIdentifier": null, + "FreeSizeInBytes": 50063212544, + "Id": "Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1", + "LastSystemInventoryTime": "2024-11-11T15:30:56+00:00", + "LastUpdateTime": "2024-11-11T15:30:57+00:00", + "ManufacturingDay": 0, + "ManufacturingWeek": 0, + "ManufacturingYear": 0, + "Name": "DellPhysicalDisk", + "NonRAIDDiskCachePolicy": "Unknown", + "OperationName": "None", + "OperationPercentCompletePercent": 0, + "PCIeCapableLinkWidth": "None", + "PCIeNegotiatedLinkWidth": "None", + "PPID": "MY-0KCT7J-MCP00-26D-0A4U-A00", + "PowerStatus": "On", + "PredictiveFailureState": "SmartAlertAbsent", + "ProductID": "MTFDDAK480TDS", + "RAIDType": "Unknown", + "RaidStatus": "Online", + "SASAddress": "500056B3DE2D55C1", + "Slot": 1, + "SystemEraseCapability": "CryptographicErasePD", + "T10PICapability": "NotSupported", + "UsedSizeInBytes": 429496729600, + "WWN": "500056B3DE2D55C1" + }, + "FIPS140ComplianceDescriptor": { + "ComplianceDescriptorModuleName": null, + "ComplianceDescriptorType": null, + "ComplianceDescriptorVersion": null, + "ComplianceHardwareVersion": null, + "ComplianceOverallSecurityLevel": null, + "ComplianceRelatedStandard": null + } + } + }, + "Operations": [], + "Operations@odata.count": 0, + "PartNumber": "MY-0KCT7J-MCP00-26D-0A4U-A00", + "PhysicalLocation": { + "PartLocation": { + "LocationOrdinalValue": 1, + "LocationType": "Slot" + } + }, + "PredictedMediaLifeLeftPercent": 100, + "Protocol": "SATA", + "Revision": "D3DJ004", + "RotationSpeedRPM": null, + "SerialNumber": "222338A2192A", + "Status": { + "Health": "OK", + "HealthRollup": "OK", + "State": "Enabled" + }, + "WriteCacheEnabled": false +} diff --git a/python/understack-workflows/tests/test_bmc_disk.py b/python/understack-workflows/tests/test_bmc_disk.py new file mode 100644 index 00000000..d66ec1bf --- /dev/null +++ b/python/understack-workflows/tests/test_bmc_disk.py @@ -0,0 +1,51 @@ +import json + +import pytest +from pytest_mock import MockerFixture + +from understack_workflows.bmc_disk import Disk + +TESTED_DISK_PATH = "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1/Drives/Disk.Bay.1:Enclosure.Internal.0-1:RAID.SL.1-1" # noqa: E501 + + +@pytest.fixture +def mock_disk_data(): + with open("tests/json_samples/bmc_chassis_info/bmc_disk.json") as f: + return json.load(f) + + +@pytest.fixture +def mock_bmc(mock_disk_data, mocker: MockerFixture): + mock_bmc = mocker.Mock() + mock_bmc.redfish_request.return_value = mock_disk_data + return mock_bmc + + +def test_disk_from_path(mock_bmc): + disk = Disk.from_path(mock_bmc, TESTED_DISK_PATH) + assert isinstance(disk, Disk) + + +def test_disk_repr(mock_bmc): + disk = Disk.from_path(mock_bmc, TESTED_DISK_PATH) + assert repr(disk) == disk.name + + +def test_disk_attributes(mock_bmc): + disk = Disk.from_path(mock_bmc, TESTED_DISK_PATH) + assert disk.media_type == "SSD" + assert disk.model == "MTFDDAK480TDS" + assert disk.name == "Solid State Disk 0:1:1" + assert disk.health == "OK" + assert disk.capacity_bytes == 479559942144 + + +def test_disk_gb_conversion(): + disk = Disk( + media_type="SSD", + model="Irrelevant", + name="TestDisk", + health="OK", + capacity_bytes=479559942144, + ) + assert disk.capacity_gb == 480 diff --git a/python/understack-workflows/understack_workflows/bmc_disk.py b/python/understack-workflows/understack_workflows/bmc_disk.py new file mode 100644 index 00000000..f5533dd1 --- /dev/null +++ b/python/understack-workflows/understack_workflows/bmc_disk.py @@ -0,0 +1,51 @@ +from dataclasses import dataclass +from understack_workflows.bmc import Bmc +from understack_workflows.bmc import RedfishError +from understack_workflows.helpers import setup_logger + + +logger = setup_logger(__name__) + + +REDFISH_DISKS_PATH="/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1" + +@dataclass(frozen=True) +class Disk: + media_type: str + model: str + name: str + health: str + capacity_bytes: int + + def __repr__(self) -> str: + """Returns disk name.""" + return self.name + + @property + def capacity_gb(self) -> int: + return int(self.capacity_bytes // 10**9) + + @staticmethod + def from_path(bmc: Bmc, path: str): + disk_data = bmc.redfish_request(path) + + return Disk( + media_type=disk_data["MediaType"], + model=disk_data["Model"], + name=disk_data["Name"], + health=disk_data.get("Status", {}).get("Health", "Unknown"), + capacity_bytes=disk_data["CapacityBytes"] + ) + +def physical_disks(bmc: Bmc) -> list[Disk]: + """Retrieve list of physical physical_disks.""" + try: + disks = bmc.redfish_request(REDFISH_DISKS_PATH)["Drives"] + disk_list = [Disk.from_path(bmc, path=disk["@odata.id"]) for disk in disks] + logger.debug("Retrieved %d disks.", len(disk_list)) + return disk_list + except RedfishError as err: + logger.error("Failed retrieving disk info: %s", err) + raise (err) from err + + From 1e34c7ef0b99bec4c7e2e172578cf4a85ba9f87c Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 14:06:40 +0000 Subject: [PATCH 04/11] extract understack-flavor-matcher module This will allow us to reuse the same code betwen the custom Argo workflows for enrollment as well as Ironic hooks/inspection interfaces. Eventually when we fully migrate to using ironic hooks, this can be inlined again. --- containers/ironic/Dockerfile.ironic | 1 + .../redfish_inspect_understack.py | 6 +- .../ironic_understack/resource_class.py | 6 +- python/ironic-understack/poetry.lock | 20 +- python/ironic-understack/pyproject.toml | 1 + python/understack-flavor-matcher/README.md | 1 + .../flavor_matcher}/__init__.py | 0 .../flavor_matcher}/flavor_spec.py | 2 +- .../flavor_matcher}/machine.py | 0 .../flavor_matcher}/matcher.py | 4 +- python/understack-flavor-matcher/poetry.lock | 272 ++++++++++++++++++ .../understack-flavor-matcher/pyproject.toml | 30 ++ .../tests/__init__.py | 0 .../tests/test_flavor_spec.py | 14 +- .../tests/test_machine.py | 2 +- .../tests/test_matcher.py | 6 +- 16 files changed, 343 insertions(+), 22 deletions(-) create mode 100644 python/understack-flavor-matcher/README.md rename python/{ironic-understack/ironic_understack/tests => understack-flavor-matcher/flavor_matcher}/__init__.py (100%) rename python/{ironic-understack/ironic_understack => understack-flavor-matcher/flavor_matcher}/flavor_spec.py (98%) rename python/{ironic-understack/ironic_understack => understack-flavor-matcher/flavor_matcher}/machine.py (100%) rename python/{ironic-understack/ironic_understack => understack-flavor-matcher/flavor_matcher}/matcher.py (89%) create mode 100644 python/understack-flavor-matcher/poetry.lock create mode 100644 python/understack-flavor-matcher/pyproject.toml create mode 100644 python/understack-flavor-matcher/tests/__init__.py rename python/{ironic-understack/ironic_understack => understack-flavor-matcher}/tests/test_flavor_spec.py (93%) rename python/{ironic-understack/ironic_understack => understack-flavor-matcher}/tests/test_machine.py (93%) rename python/{ironic-understack/ironic_understack => understack-flavor-matcher}/tests/test_matcher.py (93%) diff --git a/containers/ironic/Dockerfile.ironic b/containers/ironic/Dockerfile.ironic index c95119ad..891fb547 100644 --- a/containers/ironic/Dockerfile.ironic +++ b/containers/ironic/Dockerfile.ironic @@ -10,4 +10,5 @@ RUN apt-get update && \ && apt-get clean && rm -rf /var/lib/apt/lists/* COPY python/ironic-understack /tmp/ironic-understack +COPY python/understack-flavor-matcher /tmp/ironic-understack RUN /var/lib/openstack/bin/python -m pip install --no-cache --no-cache-dir /tmp/ironic-understack sushy-oem-idrac==6.0.0 diff --git a/python/ironic-understack/ironic_understack/redfish_inspect_understack.py b/python/ironic-understack/ironic_understack/redfish_inspect_understack.py index 3df194c1..82f897aa 100644 --- a/python/ironic-understack/ironic_understack/redfish_inspect_understack.py +++ b/python/ironic-understack/ironic_understack/redfish_inspect_understack.py @@ -18,9 +18,9 @@ from ironic.drivers.modules.inspect_utils import get_inspection_data from ironic.drivers.modules.redfish.inspect import RedfishInspect from ironic.drivers.redfish import RedfishHardware -from ironic_understack.flavor_spec import FlavorSpec -from ironic_understack.machine import Machine -from ironic_understack.matcher import Matcher +from flavor_matcher.flavor_spec import FlavorSpec +from flavor_matcher.machine import Machine +from flavor_matcher.matcher import Matcher from ironic_understack.conf import CONF from oslo_log import log from oslo_utils import units diff --git a/python/ironic-understack/ironic_understack/resource_class.py b/python/ironic-understack/ironic_understack/resource_class.py index 633ab0e2..b4dc4518 100644 --- a/python/ironic-understack/ironic_understack/resource_class.py +++ b/python/ironic-understack/ironic_understack/resource_class.py @@ -2,9 +2,9 @@ from ironic.common import exception from ironic.drivers.modules.inspector.hooks import base from ironic_understack.conf import CONF -from ironic_understack.flavor_spec import FlavorSpec -from ironic_understack.machine import Machine -from ironic_understack.matcher import Matcher +from flavor_matcher.flavor_spec import FlavorSpec +from flavor_matcher.machine import Machine +from flavor_matcher.matcher import Matcher from oslo_log import log as logging LOG = logging.getLogger(__name__) diff --git a/python/ironic-understack/poetry.lock b/python/ironic-understack/poetry.lock index a37b2ad5..c14af76b 100644 --- a/python/ironic-understack/poetry.lock +++ b/python/ironic-understack/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alembic" @@ -2602,6 +2602,22 @@ files = [ {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] +[[package]] +name = "understack-flavor-matcher" +version = "0.0.0" +description = "Baremetal node flavor classifier" +optional = false +python-versions = "^3.10" +files = [] +develop = false + +[package.dependencies] +pyyaml = "^6.0" + +[package.source] +type = "directory" +url = "../understack-flavor-matcher" + [[package]] name = "urllib3" version = "2.2.3" @@ -2894,4 +2910,4 @@ ifaddr = ">=0.1.7" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "0109d68807924825b96390375aaa6d3422c5ef3f50f008a1c408387d35f92f9e" +content-hash = "6298d0d5e17a23e75361557e83e53ff89f41e1bd64d10542f7f4374301025d62" diff --git a/python/ironic-understack/pyproject.toml b/python/ironic-understack/pyproject.toml index 2503a126..3b097baf 100644 --- a/python/ironic-understack/pyproject.toml +++ b/python/ironic-understack/pyproject.toml @@ -13,6 +13,7 @@ packages = [ ironic = ">=24.1" python = "^3.10" pyyaml = "^6.0" +understack-flavor-matcher = {path = "../understack-flavor-matcher"} [tool.poetry.group.test.dependencies] pytest = "^8.3.2" diff --git a/python/understack-flavor-matcher/README.md b/python/understack-flavor-matcher/README.md new file mode 100644 index 00000000..ba61ac8c --- /dev/null +++ b/python/understack-flavor-matcher/README.md @@ -0,0 +1 @@ +Mini library to identify a hardware flavor of a machine. diff --git a/python/ironic-understack/ironic_understack/tests/__init__.py b/python/understack-flavor-matcher/flavor_matcher/__init__.py similarity index 100% rename from python/ironic-understack/ironic_understack/tests/__init__.py rename to python/understack-flavor-matcher/flavor_matcher/__init__.py diff --git a/python/ironic-understack/ironic_understack/flavor_spec.py b/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py similarity index 98% rename from python/ironic-understack/ironic_understack/flavor_spec.py rename to python/understack-flavor-matcher/flavor_matcher/flavor_spec.py index 359a16d4..4bc3aff2 100644 --- a/python/ironic-understack/ironic_understack/flavor_spec.py +++ b/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py @@ -3,7 +3,7 @@ import yaml -from ironic_understack.machine import Machine +from flavor_matcher.machine import Machine @dataclass diff --git a/python/ironic-understack/ironic_understack/machine.py b/python/understack-flavor-matcher/flavor_matcher/machine.py similarity index 100% rename from python/ironic-understack/ironic_understack/machine.py rename to python/understack-flavor-matcher/flavor_matcher/machine.py diff --git a/python/ironic-understack/ironic_understack/matcher.py b/python/understack-flavor-matcher/flavor_matcher/matcher.py similarity index 89% rename from python/ironic-understack/ironic_understack/matcher.py rename to python/understack-flavor-matcher/flavor_matcher/matcher.py index 8d9f59cf..5e009f31 100644 --- a/python/ironic-understack/ironic_understack/matcher.py +++ b/python/understack-flavor-matcher/flavor_matcher/matcher.py @@ -1,5 +1,5 @@ -from ironic_understack.machine import Machine -from ironic_understack.flavor_spec import FlavorSpec +from flavor_matcher.machine import Machine +from flavor_matcher.flavor_spec import FlavorSpec class Matcher: diff --git a/python/understack-flavor-matcher/poetry.lock b/python/understack-flavor-matcher/poetry.lock new file mode 100644 index 00000000..c83dc94a --- /dev/null +++ b/python/understack-flavor-matcher/poetry.lock @@ -0,0 +1,272 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-github-actions-annotate-failures" +version = "0.2.0" +description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-github-actions-annotate-failures-0.2.0.tar.gz", hash = "sha256:844ab626d389496e44f960b42f0a72cce29ae06d363426d17ea9ae1b4bef2288"}, + {file = "pytest_github_actions_annotate_failures-0.2.0-py3-none-any.whl", hash = "sha256:8bcef65fed503faaa0524b59cfeccc8995130972dd7b008d64193cc41b9cde85"}, +] + +[package.dependencies] +pytest = ">=4.0.0" + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "tomli" +version = "2.1.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "201b69c47c41a807d40fd9a40ffb8e628915106651d79b6ca741218d3b95c5c1" diff --git a/python/understack-flavor-matcher/pyproject.toml b/python/understack-flavor-matcher/pyproject.toml new file mode 100644 index 00000000..09a9a91e --- /dev/null +++ b/python/understack-flavor-matcher/pyproject.toml @@ -0,0 +1,30 @@ +[tool.poetry] +name = "understack-flavor-matcher" +version = "0.0.0" +description = "Baremetal node flavor classifier" +authors = ["Marek Skrobacki "] +license = "MIT" +readme = "README.md" +packages = [ + { include = "flavor_matcher" } +] + +[tool.poetry.dependencies] +python = "^3.10" +pyyaml = "^6.0" + +[tool.poetry.group.test.dependencies] +pytest = "^8.3.2" +pytest-github-actions-annotate-failures = "*" +pytest-cov = "^5.0.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-ra --cov=flavor_matcher" +testpaths = [ + "tests", +] diff --git a/python/understack-flavor-matcher/tests/__init__.py b/python/understack-flavor-matcher/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/ironic-understack/ironic_understack/tests/test_flavor_spec.py b/python/understack-flavor-matcher/tests/test_flavor_spec.py similarity index 93% rename from python/ironic-understack/ironic_understack/tests/test_flavor_spec.py rename to python/understack-flavor-matcher/tests/test_flavor_spec.py index 8e06a3bc..ab63a40e 100644 --- a/python/ironic-understack/ironic_understack/tests/test_flavor_spec.py +++ b/python/understack-flavor-matcher/tests/test_flavor_spec.py @@ -1,8 +1,8 @@ from unittest.mock import mock_open, patch import pytest -from ironic_understack.flavor_spec import FlavorSpec -from ironic_understack.machine import Machine +from flavor_matcher.flavor_spec import FlavorSpec +from flavor_matcher.machine import Machine @pytest.fixture @@ -164,7 +164,7 @@ def test_disk_too_small(flavors): machine = Machine( memory_mb=204800, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=100 ) - assert all(flavor.score_machine(machine) for flavor in flavors) == 0 + assert all(flavor.score_machine(machine) == 0 for flavor in flavors) def test_cpu_model_not_matching(flavors): @@ -196,7 +196,7 @@ def test_memory_slightly_less(flavors): memory_mb=102300, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=500 ) # Should not match because memory is slightly less - assert all(flavor.score_machine(machine) for flavor in flavors) == 0 + assert all(flavor.score_machine(machine) == 0 for flavor in flavors) def test_disk_slightly_less(flavors): @@ -205,7 +205,7 @@ def test_disk_slightly_less(flavors): memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=499 ) # Should not match because disk space is slightly less - assert all(flavor.score_machine(machine) for flavor in flavors) == 0 + assert all(flavor.score_machine(machine) == 0 for flavor in flavors) def test_memory_exact_disk_slightly_more(flavors): @@ -234,11 +234,11 @@ def test_cpu_model_not_exact_but_memory_and_disk_match(flavors): memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor v2", disk_gb=500 ) # Should not match because CPU model is not exactly listed - assert all(flavor.score_machine(machine) for flavor in flavors) == 0 + assert all(flavor.score_machine(machine) == 0 for flavor in flavors) def test_large_flavor_memory_slightly_less_disk_exact(flavors): # Machine with slightly less memory than required for the medium flavor, exact disk space machine = Machine(memory_mb=204600, cpu="Intel 80386DX", disk_gb=1800) # Should not match because memory is slightly less than required - assert all(flavor.score_machine(machine) for flavor in flavors) == 0 + assert all(flavor.score_machine(machine) == 0 for flavor in flavors) diff --git a/python/ironic-understack/ironic_understack/tests/test_machine.py b/python/understack-flavor-matcher/tests/test_machine.py similarity index 93% rename from python/ironic-understack/ironic_understack/tests/test_machine.py rename to python/understack-flavor-matcher/tests/test_machine.py index 86d7937e..aa21b5c6 100644 --- a/python/ironic-understack/ironic_understack/tests/test_machine.py +++ b/python/understack-flavor-matcher/tests/test_machine.py @@ -1,4 +1,4 @@ -from ironic_understack.machine import Machine +from flavor_matcher.machine import Machine def test_memory_gb_property(): diff --git a/python/ironic-understack/ironic_understack/tests/test_matcher.py b/python/understack-flavor-matcher/tests/test_matcher.py similarity index 93% rename from python/ironic-understack/ironic_understack/tests/test_matcher.py rename to python/understack-flavor-matcher/tests/test_matcher.py index 2e4455fc..ce627c08 100644 --- a/python/ironic-understack/ironic_understack/tests/test_matcher.py +++ b/python/understack-flavor-matcher/tests/test_matcher.py @@ -1,7 +1,7 @@ import pytest -from ironic_understack.flavor_spec import FlavorSpec -from ironic_understack.machine import Machine -from ironic_understack.matcher import Matcher +from flavor_matcher.flavor_spec import FlavorSpec +from flavor_matcher.machine import Machine +from flavor_matcher.matcher import Matcher @pytest.fixture From 2e77a18f245dbbbdbbbb1c14d0849d9b8c57fdbc Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 16:13:05 +0000 Subject: [PATCH 05/11] enroll: wire in flavor matching code --- .../understack_workflows/bmc_disk.py | 14 +++-- .../understack_workflows/flavor_detect.py | 31 ++++++++++ .../understack_workflows/ironic_node.py | 62 ++++++++++--------- .../main/enroll_server.py | 12 +++- 4 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 python/understack-workflows/understack_workflows/flavor_detect.py diff --git a/python/understack-workflows/understack_workflows/bmc_disk.py b/python/understack-workflows/understack_workflows/bmc_disk.py index f5533dd1..f65ad488 100644 --- a/python/understack-workflows/understack_workflows/bmc_disk.py +++ b/python/understack-workflows/understack_workflows/bmc_disk.py @@ -1,13 +1,15 @@ +import math from dataclasses import dataclass + from understack_workflows.bmc import Bmc from understack_workflows.bmc import RedfishError from understack_workflows.helpers import setup_logger - logger = setup_logger(__name__) -REDFISH_DISKS_PATH="/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1" +REDFISH_DISKS_PATH = "/redfish/v1/Systems/System.Embedded.1/Storage/RAID.SL.1-1" + @dataclass(frozen=True) class Disk: @@ -23,7 +25,7 @@ def __repr__(self) -> str: @property def capacity_gb(self) -> int: - return int(self.capacity_bytes // 10**9) + return math.ceil(self.capacity_bytes / 10**9) @staticmethod def from_path(bmc: Bmc, path: str): @@ -34,9 +36,10 @@ def from_path(bmc: Bmc, path: str): model=disk_data["Model"], name=disk_data["Name"], health=disk_data.get("Status", {}).get("Health", "Unknown"), - capacity_bytes=disk_data["CapacityBytes"] + capacity_bytes=disk_data["CapacityBytes"], ) + def physical_disks(bmc: Bmc) -> list[Disk]: """Retrieve list of physical physical_disks.""" try: @@ -49,3 +52,6 @@ def physical_disks(bmc: Bmc) -> list[Disk]: raise (err) from err +def smallest_disk_size(bmc: Bmc) -> int: + """Returns size of a smallest disk in a given machine (in Gigabytes).""" + return min(physical_disks(bmc), key=lambda x: x.capacity_gb).capacity_gb diff --git a/python/understack-workflows/understack_workflows/flavor_detect.py b/python/understack-workflows/understack_workflows/flavor_detect.py new file mode 100644 index 00000000..2210dd1b --- /dev/null +++ b/python/understack-workflows/understack_workflows/flavor_detect.py @@ -0,0 +1,31 @@ +from flavor_matcher.machine import Machine +from flavor_matcher.matcher import FlavorSpec +from flavor_matcher.matcher import Matcher + +from understack_workflows import bmc_disk +from understack_workflows.bmc import Bmc +from understack_workflows.bmc_chassis_info import ChassisInfo +from understack_workflows.helpers import setup_logger + +logger = setup_logger(__name__) + +FLAVORS = FlavorSpec.from_directory("/etc/understack_flavors/") +logger.info(f"Loaded {len(FLAVORS)} flavor specifications.") + + +def guess_machine_flavor(device_info: ChassisInfo, bmc: Bmc) -> str: + memory_mb = (device_info.memory_gib * 1024**3) // 10**6 + + machine = Machine( + memory_mb=memory_mb, + cpu=device_info.cpu, + disk_gb=bmc_disk.smallest_disk_size(bmc), + ) + + flavor_name = Matcher(FLAVORS).pick_best_flavor(machine) + if not flavor_name: + raise Exception( + f"Machine: {machine} could not be classified into any flavor {FLAVORS=}" + ) + logger.info(f"Device has been classified as flavor: {flavor_name.name}") + return flavor_name.name diff --git a/python/understack-workflows/understack_workflows/ironic_node.py b/python/understack-workflows/understack_workflows/ironic_node.py index 1ce1fdf8..c110ec1d 100644 --- a/python/understack-workflows/understack_workflows/ironic_node.py +++ b/python/understack-workflows/understack_workflows/ironic_node.py @@ -12,31 +12,37 @@ logger = setup_logger(__name__) -def create_or_update( - node_uuid: str, device_hostname: str, device_manufacturer: str, bmc: Bmc, logger -): +@dataclass(frozen=True) +class NodeMetadata: + uuid: str + hostname: str + manufacturer: str + resource_class: str + + @property + def driver(self): + if self.manufacturer.startswith("Dell"): + return "idrac" + else: + return "redfish" + + +def create_or_update(node_meta: NodeMetadata, bmc: Bmc): """Note interfaces/ports are not synced here, that happens elsewhere.""" client = IronicClient() - if device_manufacturer.startswith("Dell"): - driver = "idrac" - else: - driver = "redfish" - - logger.debug(f"Ensuring node with UUID {node_uuid} exists in Ironic") + logger.debug(f"Ensuring node with UUID {node_meta.uuid} exists in Ironic") try: - ironic_node = client.get_node(node_uuid) + ironic_node = client.get_node(node_meta.uuid) except ironicclient.common.apiclient.exceptions.NotFound: - logger.debug(f"Node: {node_uuid} not found in Ironic, creating.") - ironic_node = create_ironic_node( - client, node_uuid, device_hostname, driver, bmc - ) + logger.debug(f"Node: {node_meta.uuid} not found in Ironic, creating.") + ironic_node = create_ironic_node(client, node_meta, bmc) return ironic_node.provision_state # type: ignore if ironic_node.provision_state in STATES_ALLOWING_UPDATES: - update_ironic_node(client, node_uuid, device_hostname, driver, bmc) + update_ironic_node(client, node_meta, bmc) else: logger.info( - f"Device {node_uuid} in Ironic is in a " + f"Device {node_meta.uuid} in Ironic is in a " f"{ironic_node.provision_state} provision_state, " f"so no updates are allowed." ) @@ -44,40 +50,40 @@ def create_or_update( return ironic_node.provision_state -def update_ironic_node(client, node_uuid, device_hostname, driver, bmc): +def update_ironic_node(client, node_meta, bmc): updates = [ - f"name={device_hostname}", - f"driver={driver}", + f"name={node_meta.hostname}", + f"driver={node_meta.driver}", f"driver_info/redfish_address={bmc.url()}", "driver_info/redfish_verify_ca=false", f"driver_info/redfish_username={bmc.username}", f"driver_info/redfish_password={bmc.password}", + f"resource_class={node_meta.resource_class}", ] patches = args_array_to_patch("add", updates) - logger.info(f"Updating Ironic node {node_uuid} {patches=}") + logger.info(f"Updating Ironic node {node_meta.uuid} {patches=}") - response = client.update_node(node_uuid, patches) - logger.info(f"Ironic node {node_uuid} Updated: {response=}") + response = client.update_node(node_meta.uuid, patches) + logger.info(f"Ironic node {node_meta.uuid} Updated: {response=}") def create_ironic_node( client: IronicClient, - node_uuid: str, - device_hostname: str, - driver: str, + node_meta: NodeMetadata, bmc: Bmc, ) -> IronicNodeConfiguration: return client.create_node( { - "uuid": node_uuid, - "name": device_hostname, - "driver": driver, + "uuid": node_meta.uuid, + "name": node_meta.hostname, + "driver": node_meta.driver, "driver_info": { "redfish_address": bmc.url(), "redfish_verify_ca": False, "redfish_username": bmc.username, "redfish_password": bmc.password, }, + "resource_class": node_meta.resource_class, } ) diff --git a/python/understack-workflows/understack_workflows/main/enroll_server.py b/python/understack-workflows/understack_workflows/main/enroll_server.py index 1dc843f9..4450cd09 100644 --- a/python/understack-workflows/understack_workflows/main/enroll_server.py +++ b/python/understack-workflows/understack_workflows/main/enroll_server.py @@ -16,6 +16,7 @@ from understack_workflows.bmc_network_config import bmc_set_permanent_ip_addr from understack_workflows.bmc_settings import update_dell_drac_settings from understack_workflows.discover import discover_chassis_info +from understack_workflows.flavor_detect import guess_machine_flavor from understack_workflows.helpers import credential from understack_workflows.helpers import parser_nautobot_args from understack_workflows.helpers import setup_logger @@ -136,8 +137,17 @@ def enroll_server(bmc: Bmc, nautobot, old_password: str | None) -> NautobotDevic # any pending BIOS jobs, so do BIOS settings after the DRAC settings. update_dell_bios_settings(bmc, pxe_interface=pxe_interface) + flavor = guess_machine_flavor(device_info, bmc) + resource_class = f"baremetal.{flavor}" + _ironic_provision_state = ironic_node.create_or_update( - nb_device.id, nb_device.name, device_info.manufacturer, bmc, logger + ironic_node.NodeMetadata( + uuid=nb_device.id, + hostname=nb_device.name, + manufacturer=device_info.manufacturer, + resource_class=resource_class, + ), + bmc, ) logger.info(f"{nb_device.id} {_ironic_provision_state=}") From 8b9de6e282e062dea9e55b4bd29473068d97ea53 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 16:36:44 +0000 Subject: [PATCH 06/11] understack-workflows: install understack-flavor-matcher dependency --- .../Dockerfile.ironic-nautobot-client | 2 ++ python/understack-workflows/poetry.lock | 20 +++++++++++++++++-- python/understack-workflows/pyproject.toml | 1 + 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/containers/ironic-nautobot-client/Dockerfile.ironic-nautobot-client b/containers/ironic-nautobot-client/Dockerfile.ironic-nautobot-client index 40899b9a..78e1499d 100644 --- a/containers/ironic-nautobot-client/Dockerfile.ironic-nautobot-client +++ b/containers/ironic-nautobot-client/Dockerfile.ironic-nautobot-client @@ -16,6 +16,7 @@ RUN --mount=type=cache,target=/root/.cache/.pip \ # copy in the code COPY --chown=${APP_USER}:${APP_GROUP} python/understack-workflows /app +COPY --chown=${APP_USER}:${APP_GROUP} python/understack-flavor-matcher /understack-flavor-matcher # need netifaces built as a wheel RUN --mount=type=cache,target=/root/.cache/.pip pip wheel --wheel-dir /app/dist netifaces # build wheels and requirements.txt, skip hashes due to building of netifaces above which won't match @@ -35,6 +36,7 @@ WORKDIR /app RUN mkdir -p /opt/venv/wheels/ COPY --from=builder /app/dist/*.whl /app/dist/requirements.txt /opt/venv/wheels/ +COPY --chown=${APP_USER}:${APP_GROUP} python/understack-flavor-matcher /understack-flavor-matcher RUN --mount=type=cache,target=/root/.cache/.pip /opt/venv/bin/pip install --find-links /opt/venv/wheels/ --only-binary netifaces -r /opt/venv/wheels/requirements.txt understack-workflows diff --git a/python/understack-workflows/poetry.lock b/python/understack-workflows/poetry.lock index de0a22b0..4e084ee5 100644 --- a/python/understack-workflows/poetry.lock +++ b/python/understack-workflows/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "annotated-types" @@ -1533,6 +1533,22 @@ files = [ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] +[[package]] +name = "understack-flavor-matcher" +version = "0.0.0" +description = "Baremetal node flavor classifier" +optional = false +python-versions = "^3.10" +files = [] +develop = false + +[package.dependencies] +pyyaml = "^6.0" + +[package.source] +type = "directory" +url = "../understack-flavor-matcher" + [[package]] name = "urllib3" version = "2.2.3" @@ -1659,4 +1675,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.11.0" -content-hash = "1bd67ca37a025d77b90e48da9c3690fb84b830fe3a68973aa7bd6360c61ade2c" +content-hash = "332e385ac1186fe6eaea24014f23a02464f311295d587eeb2ed56426b8a0d111" diff --git a/python/understack-workflows/pyproject.toml b/python/understack-workflows/pyproject.toml index e77837d7..6483d008 100644 --- a/python/understack-workflows/pyproject.toml +++ b/python/understack-workflows/pyproject.toml @@ -29,6 +29,7 @@ pynautobot = "^2.2.1" python-ironicclient = "^5" sushy = "^5.3.0" kubernetes = "29.0.0" +understack-flavor-matcher = {path = "../understack-flavor-matcher"} [tool.peotry.group.test] optional = true From 5bf2d4355de670e5ba1e34d7c0c3f25c7396e607 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 16:46:22 +0000 Subject: [PATCH 07/11] enroll-server: mount the understack-flavors PVC The responsibility of providing that volume with fresh data is delegated to the undercloud-deploy repo as it may be different for each deployments. --- .../argo-events/workflowtemplates/enroll-server.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/workflows/argo-events/workflowtemplates/enroll-server.yaml b/workflows/argo-events/workflowtemplates/enroll-server.yaml index 4bd8d5f6..73ee9f16 100644 --- a/workflows/argo-events/workflowtemplates/enroll-server.yaml +++ b/workflows/argo-events/workflowtemplates/enroll-server.yaml @@ -71,6 +71,10 @@ spec: - mountPath: /etc/bmc_master/ name: bmc-master readOnly: true + - mountPath: /etc/understack_flavors/ + name: understack-flavors + subPath: current/flavors/ + readOnly: true env: - name: WF_NS value: "{{workflow.namespace}}" @@ -88,6 +92,10 @@ spec: - name: openstack-svc-acct secret: secretName: openstack-svc-acct + - name: understack-flavors + persistentVolumeClaim: + claimName: understack-flavors + readOnly: true - name: openstack-wait-cmd inputs: parameters: From 0fc0b1513910f19ec515ea161078828209af95ca Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Thu, 14 Nov 2024 19:12:11 +0000 Subject: [PATCH 08/11] flavors: strip the prefix --- .../flavor_matcher/flavor_spec.py | 8 ++++++++ .../understack_workflows/flavor_detect.py | 9 ++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py b/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py index 4bc3aff2..17f7c3b3 100644 --- a/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py +++ b/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py @@ -39,6 +39,14 @@ def from_yaml(yaml_str: str) -> "FlavorSpec": pci=data.get("pci", []), ) + @property + def stripped_name(self): + """Returns actual flavor name with the prod/nonprod prefix removed.""" + _, name = self.name.split(".", 1) + if not name: + raise Exception(f"Unable to strip envtype from flavor: {self.name}") + return name + @staticmethod def from_directory(directory: str = "/etc/flavors/") -> list["FlavorSpec"]: flavor_specs = [] diff --git a/python/understack-workflows/understack_workflows/flavor_detect.py b/python/understack-workflows/understack_workflows/flavor_detect.py index 2210dd1b..51340c17 100644 --- a/python/understack-workflows/understack_workflows/flavor_detect.py +++ b/python/understack-workflows/understack_workflows/flavor_detect.py @@ -1,3 +1,5 @@ +import os + from flavor_matcher.machine import Machine from flavor_matcher.matcher import FlavorSpec from flavor_matcher.matcher import Matcher @@ -8,7 +10,7 @@ from understack_workflows.helpers import setup_logger logger = setup_logger(__name__) - +ENV_TYPE = os.getenv("FLAVOR_TYPES", "nonprod") FLAVORS = FlavorSpec.from_directory("/etc/understack_flavors/") logger.info(f"Loaded {len(FLAVORS)} flavor specifications.") @@ -27,5 +29,6 @@ def guess_machine_flavor(device_info: ChassisInfo, bmc: Bmc) -> str: raise Exception( f"Machine: {machine} could not be classified into any flavor {FLAVORS=}" ) - logger.info(f"Device has been classified as flavor: {flavor_name.name}") - return flavor_name.name + logger.info(f"Device has been classified as flavor: {flavor_name.stripped_name}") + + return flavor_name.stripped_name From 6f6a96d90ac930a4fe81ec498a6d088639e2abc7 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Mon, 18 Nov 2024 11:08:56 +0000 Subject: [PATCH 09/11] flavors: match on chassis model Up till now we ignored the 'chassis model' because there was low risk of the overlap between the flavors, but turns out this is actually needed to distinguish between for example gp1.small and gp2.small. --- .../redfish_inspect_understack.py | 25 +++- .../ironic_understack/resource_class.py | 16 ++- .../flavor_matcher/flavor_spec.py | 16 ++- .../flavor_matcher/machine.py | 1 + .../tests/test_flavor_spec.py | 116 ++++++++++++++---- .../tests/test_machine.py | 8 +- .../tests/test_matcher.py | 6 +- .../understack_workflows/flavor_detect.py | 1 + 8 files changed, 152 insertions(+), 37 deletions(-) diff --git a/python/ironic-understack/ironic_understack/redfish_inspect_understack.py b/python/ironic-understack/ironic_understack/redfish_inspect_understack.py index 82f897aa..dd575e50 100644 --- a/python/ironic-understack/ironic_understack/redfish_inspect_understack.py +++ b/python/ironic-understack/ironic_understack/redfish_inspect_understack.py @@ -13,14 +13,16 @@ Redfish Inspect Interface modified for Understack """ +import re + +from flavor_matcher.flavor_spec import FlavorSpec +from flavor_matcher.machine import Machine +from flavor_matcher.matcher import Matcher from ironic.drivers.drac import IDRACHardware from ironic.drivers.modules.drac.inspect import DracRedfishInspect from ironic.drivers.modules.inspect_utils import get_inspection_data from ironic.drivers.modules.redfish.inspect import RedfishInspect from ironic.drivers.redfish import RedfishHardware -from flavor_matcher.flavor_spec import FlavorSpec -from flavor_matcher.machine import Machine -from flavor_matcher.matcher import Matcher from ironic_understack.conf import CONF from oslo_log import log from oslo_utils import units @@ -68,10 +70,27 @@ def inspect_hardware(self, task): return upstream_state smallest_disk_gb = min([disk["size"] / units.Gi for disk in inventory["disks"]]) + model_name_match = None + try: + model_name_match = re.search( + r"ModelName=(.*)\)", + inventory.get("system_vendor", {}).get("product_name", ""), + ) + except TypeError as e: + LOG.warn("Error searching for model name: %s", e) + return upstream_state + + if not model_name_match: + LOG.warn("No model_name detected. skipping flavor setting.") + return upstream_state + else: + model_name = model_name_match.group(1) + machine = Machine( memory_mb=inventory["memory"]["physical_mb"], disk_gb=smallest_disk_gb, cpu=inventory["cpu"]["model_name"], + model=model_name, ) matcher = Matcher(FLAVORS) diff --git a/python/ironic-understack/ironic_understack/resource_class.py b/python/ironic-understack/ironic_understack/resource_class.py index b4dc4518..3f5955c9 100644 --- a/python/ironic-understack/ironic_understack/resource_class.py +++ b/python/ironic-understack/ironic_understack/resource_class.py @@ -6,6 +6,7 @@ from flavor_matcher.machine import Machine from flavor_matcher.matcher import Matcher from oslo_log import log as logging +import re LOG = logging.getLogger(__name__) @@ -28,8 +29,21 @@ def __call__(self, task, inventory, plugin_data): disk_size_gb = int(int(inventory["disks"][0]["size"]) / 10**9) cpu_model_name = inventory["cpu"]["model_name"] + model_name = re.search( + r"ModelName=(.*)\)", inventory["system_vendor"]["product_name"] + ) + + if not model_name: + LOG.warn("No model_name detected. skipping flavor setting.") + raise NoMatchError("mode_name not matched") + else: + model_name = model_name.group(1) + machine = Machine( - memory_mb=memory_mb, cpu=cpu_model_name, disk_gb=disk_size_gb + memory_mb=memory_mb, + cpu=cpu_model_name, + disk_gb=disk_size_gb, + model=model_name, ) resource_class_name = self.classify(machine) diff --git a/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py b/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py index 17f7c3b3..d39fd343 100644 --- a/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py +++ b/python/understack-flavor-matcher/flavor_matcher/flavor_spec.py @@ -73,11 +73,12 @@ def score_machine(self, machine: Machine): # it cannot be used - the score should be 0. # 3. If the machine has smaller disk size than specified in the flavor, # it cannot be used - the score should be 0. - # 4. Machine must match the flavor on one of the CPU models exactly. - # 5. If the machine has exact amount memory as specified in flavor, but + # 4. If the machine's model does not match exactly, score should be 0 + # 5. Machine must match the flavor on one of the CPU models exactly. + # 6. If the machine has exact amount memory as specified in flavor, but # more disk space it is less desirable than the machine that matches # exactly on both disk and memory. - # 6. If the machine has exact amount of disk as specified in flavor, + # 7. If the machine has exact amount of disk as specified in flavor, # but more memory space it is less desirable than the machine that # matches exactly on both disk and memory. @@ -86,6 +87,7 @@ def score_machine(self, machine: Machine): machine.memory_gb == self.memory_gb and machine.disk_gb in self.drives and machine.cpu == self.cpu_model + and machine.model == self.model ): return 100 @@ -97,11 +99,15 @@ def score_machine(self, machine: Machine): if any(machine.disk_gb < drive for drive in self.drives): return 0 - # Rule 4: Machine must match the flavor on one of the CPU models exactly + # Rule 4: Machine's model must match exactly + if machine.model != self.model: + return 0 + + # Rule 5: Machine must match the flavor on one of the CPU models exactly if machine.cpu != self.cpu_model: return 0 - # Rule 5 and 6: Rank based on exact matches or excess capacity + # Rule 6 and 7: Rank based on exact matches or excess capacity score = 0 # Exact memory match gives preference diff --git a/python/understack-flavor-matcher/flavor_matcher/machine.py b/python/understack-flavor-matcher/flavor_matcher/machine.py index d1fd5c1c..cef75319 100644 --- a/python/understack-flavor-matcher/flavor_matcher/machine.py +++ b/python/understack-flavor-matcher/flavor_matcher/machine.py @@ -6,6 +6,7 @@ class Machine: memory_mb: int cpu: str disk_gb: int + model: str @property def memory_gb(self) -> int: diff --git a/python/understack-flavor-matcher/tests/test_flavor_spec.py b/python/understack-flavor-matcher/tests/test_flavor_spec.py index ab63a40e..aedfa9db 100644 --- a/python/understack-flavor-matcher/tests/test_flavor_spec.py +++ b/python/understack-flavor-matcher/tests/test_flavor_spec.py @@ -97,15 +97,40 @@ def test_empty_directory(tmp_path): def machines(): return [ # 1024 GB, exact CPU, medium - Machine(memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=1000), + Machine( + memory_mb=102400, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=1000, + model="Dell XPS1319", + ), # 800 GB, non-matching CPU - Machine(memory_mb=800000, cpu="Intel Xeon E5-2676 v3", disk_gb=500), + Machine( + memory_mb=800000, + cpu="Intel Xeon E5-2676 v3", + disk_gb=500, + model="Dell XPS1319", + ), # 200 GB, exact CPU, medium - Machine(memory_mb=200000, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=1500), + Machine( + memory_mb=200000, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=150, + model="Dell XPS1319", + ), # 300 GB, non-matching CPU - Machine(memory_mb=300000, cpu="Intel Xeon E5-2676 v3", disk_gb=500), + Machine( + memory_mb=300000, + cpu="Intel Xeon E5-2676 v3", + disk_gb=500, + model="Dell XPS1319", + ), # 409 GB, exact CPU, large - Machine(memory_mb=409600, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=2000), + Machine( + memory_mb=409600, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=2000, + model="Dell XPS1319", + ), ] @@ -115,7 +140,7 @@ def flavors(): FlavorSpec( name="small", manufacturer="Dell", - model="Fake Machine", + model="Dell XPS1319", memory_gb=100, cpu_cores=13, cpu_model="AMD EPYC 9254 245-Core Processor", @@ -135,7 +160,7 @@ def flavors(): FlavorSpec( name="large", manufacturer="Dell", - model="Fake Machine", + model="Dell XPS1319", memory_gb=400, cpu_cores=27, cpu_model="AMD EPYC 9254 245-Core Processor", @@ -147,41 +172,73 @@ def flavors(): def test_exact_match(flavors): machine = Machine( - memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=500 + memory_mb=102400, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=500, + model="Dell XPS1319", ) assert flavors[0].score_machine(machine) == 100 assert flavors[1].score_machine(machine) == 0 +def test_wrong_model_non_match(flavors): + machine = Machine( + memory_mb=102400, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=500, + model="Some other model", + ) + for flavor in flavors: + assert flavor.score_machine(machine) == 0 + + def test_memory_too_small(flavors): machine = Machine( - memory_mb=51200, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=500 + memory_mb=51200, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=500, + model="Dell XPS1319", ) - assert all(flavor.score_machine(machine) for flavor in flavors) == 0 + for flavor in flavors: + assert flavor.score_machine(machine) == 0 def test_disk_too_small(flavors): machine = Machine( - memory_mb=204800, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=100 + memory_mb=204800, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=100, + model="Dell XPS1319", ) assert all(flavor.score_machine(machine) == 0 for flavor in flavors) def test_cpu_model_not_matching(flavors): - machine = Machine(memory_mb=102400, cpu="Non-Existent CPU Model", disk_gb=500) - assert all(flavor.score_machine(machine) for flavor in flavors) == 0 + machine = Machine( + memory_mb=102400, + cpu="Non-Existent CPU Model", + disk_gb=500, + model="Dell XPS1319", + ) + assert all(flavor.score_machine(machine) == 0 for flavor in flavors) def test_memory_match_but_more_disk(flavors): machine = Machine( - memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=1000 + memory_mb=102400, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=1000, + model="Dell XPS1319", ) assert flavors[0].score_machine(machine) > 0 def test_disk_match_but_more_memory(flavors): machine = Machine( - memory_mb=204800, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=500 + memory_mb=204800, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=500, + model="Dell XPS1319", ) assert flavors[0].score_machine(machine) > 0 @@ -193,7 +250,10 @@ def test_disk_match_but_more_memory(flavors): def test_memory_slightly_less(flavors): # Machine with slightly less memory than required by the smallest flavor machine = Machine( - memory_mb=102300, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=500 + memory_mb=102300, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=500, + model="Dell XPS1319", ) # Should not match because memory is slightly less assert all(flavor.score_machine(machine) == 0 for flavor in flavors) @@ -202,7 +262,10 @@ def test_memory_slightly_less(flavors): def test_disk_slightly_less(flavors): # Machine with slightly less disk space than required by the smallest flavor machine = Machine( - memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=499 + memory_mb=102400, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=499, + model="Dell XPS1319", ) # Should not match because disk space is slightly less assert all(flavor.score_machine(machine) == 0 for flavor in flavors) @@ -211,7 +274,10 @@ def test_disk_slightly_less(flavors): def test_memory_exact_disk_slightly_more(flavors): # Machine with exact memory but slightly more disk space than required machine = Machine( - memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=501 + memory_mb=102400, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=501, + model="Dell XPS1319", ) assert flavors[0].score_machine(machine) > 0 assert flavors[1].score_machine(machine) == 0 @@ -221,7 +287,10 @@ def test_memory_exact_disk_slightly_more(flavors): def test_disk_exact_memory_slightly_more(flavors): # Machine with exact disk space but slightly more memory than required machine = Machine( - memory_mb=102500, cpu="AMD EPYC 9254 245-Core Processor", disk_gb=500 + memory_mb=102500, + cpu="AMD EPYC 9254 245-Core Processor", + disk_gb=500, + model="Dell XPS1319", ) assert flavors[0].score_machine(machine) > 0 assert flavors[1].score_machine(machine) == 0 @@ -231,7 +300,10 @@ def test_disk_exact_memory_slightly_more(flavors): def test_cpu_model_not_exact_but_memory_and_disk_match(flavors): # Machine with exact memory and disk space but CPU model is close but not exact machine = Machine( - memory_mb=102400, cpu="AMD EPYC 9254 245-Core Processor v2", disk_gb=500 + memory_mb=102400, + cpu="AMD EPYC 9254 245-Core Processor v2", + disk_gb=500, + model="Dell XPS1319", ) # Should not match because CPU model is not exactly listed assert all(flavor.score_machine(machine) == 0 for flavor in flavors) @@ -239,6 +311,8 @@ def test_cpu_model_not_exact_but_memory_and_disk_match(flavors): def test_large_flavor_memory_slightly_less_disk_exact(flavors): # Machine with slightly less memory than required for the medium flavor, exact disk space - machine = Machine(memory_mb=204600, cpu="Intel 80386DX", disk_gb=1800) + machine = Machine( + memory_mb=204600, cpu="Intel 80386DX", disk_gb=1800, model="Dell XPS1319" + ) # Should not match because memory is slightly less than required assert all(flavor.score_machine(machine) == 0 for flavor in flavors) diff --git a/python/understack-flavor-matcher/tests/test_machine.py b/python/understack-flavor-matcher/tests/test_machine.py index aa21b5c6..f9bba7af 100644 --- a/python/understack-flavor-matcher/tests/test_machine.py +++ b/python/understack-flavor-matcher/tests/test_machine.py @@ -3,17 +3,17 @@ def test_memory_gb_property(): # Test a machine with exactly 1 GB of memory - machine = Machine(memory_mb=1024, cpu="x86", disk_gb=50) + machine = Machine(memory_mb=1024, cpu="x86", disk_gb=50, model="SomeModel") assert machine.memory_gb == 1 # Test a machine with 2 GB of memory - machine = Machine(memory_mb=2048, cpu="x86", disk_gb=50) + machine = Machine(memory_mb=2048, cpu="x86", disk_gb=50, model="SomeModel") assert machine.memory_gb == 2 # Test a machine with non-exact GB memory (should floor the value) - machine = Machine(memory_mb=3072, cpu="x86", disk_gb=50) + machine = Machine(memory_mb=3072, cpu="x86", disk_gb=50, model="SomeModel") assert machine.memory_gb == 3 # Test a machine with less than 1 GB of memory - machine = Machine(memory_mb=512, cpu="x86", disk_gb=50) + machine = Machine(memory_mb=512, cpu="x86", disk_gb=50, model="SomeModel") assert machine.memory_gb == 0 diff --git a/python/understack-flavor-matcher/tests/test_matcher.py b/python/understack-flavor-matcher/tests/test_matcher.py index ce627c08..45200884 100644 --- a/python/understack-flavor-matcher/tests/test_matcher.py +++ b/python/understack-flavor-matcher/tests/test_matcher.py @@ -47,7 +47,7 @@ def matcher(sample_flavors): @pytest.fixture def machine(): - return Machine(memory_mb=8192, cpu="x86", disk_gb=50) + return Machine(memory_mb=8192, cpu="x86", disk_gb=50, model="Fake Machine") def test_match(matcher, machine): @@ -60,7 +60,7 @@ def test_match(matcher, machine): def test_match_no_flavor(matcher): # A machine that does not meet any flavor specs - machine = Machine(memory_mb=2048, cpu="x86", disk_gb=10) + machine = Machine(memory_mb=2048, cpu="x86", disk_gb=10, model="SomeModel") results = matcher.match(machine) assert len(results) == 0 @@ -74,6 +74,6 @@ def test_pick_best_flavor2(matcher, machine): def test_pick_best_flavor_no_match(matcher): # A machine that does not meet any flavor specs - machine = Machine(memory_mb=1024, cpu="ARM", disk_gb=10) + machine = Machine(memory_mb=1024, cpu="ARM", disk_gb=10, model="SomeModel") best_flavor = matcher.pick_best_flavor(machine) assert best_flavor is None diff --git a/python/understack-workflows/understack_workflows/flavor_detect.py b/python/understack-workflows/understack_workflows/flavor_detect.py index 51340c17..27865cf1 100644 --- a/python/understack-workflows/understack_workflows/flavor_detect.py +++ b/python/understack-workflows/understack_workflows/flavor_detect.py @@ -22,6 +22,7 @@ def guess_machine_flavor(device_info: ChassisInfo, bmc: Bmc) -> str: memory_mb=memory_mb, cpu=device_info.cpu, disk_gb=bmc_disk.smallest_disk_size(bmc), + model=device_info.model_number, ) flavor_name = Matcher(FLAVORS).pick_best_flavor(machine) From 28ceb4e28c764af7edca26f54ef3a75f0ad422e9 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Mon, 18 Nov 2024 13:43:10 +0000 Subject: [PATCH 10/11] fix: pre-commit setup for pyright --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 866259c1..58a0ccea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -72,10 +72,14 @@ repos: files: '^python/understack-workflows/' args: ["--threads"] additional_dependencies: + # python-pyright stupidly does not allow local paths + # https://github.com/pre-commit/pre-commit/issues/1752#issuecomment-754252663 + - "git+https://github.com/rackerlabs/understack.git@197e953#subdirectory=python/understack-flavor-matcher" - "kubernetes" - "pydantic" - "pynautobot" - "pytest" + - "pytest-mock" - "pytest_lazy_fixtures" - "python-ironicclient" - "requests" From b6565af2dbcf974a5f073bdedbce06456ab40c7f Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Mon, 18 Nov 2024 13:54:41 +0000 Subject: [PATCH 11/11] CI: don't run ironic tests There are no any tests there anymore. --- .github/workflows/code-test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/code-test.yaml b/.github/workflows/code-test.yaml index ccc9feab..2d530fa8 100644 --- a/.github/workflows/code-test.yaml +++ b/.github/workflows/code-test.yaml @@ -27,7 +27,6 @@ jobs: matrix: project: - understack-workflows - - ironic-understack - neutron-understack defaults: