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
6 changes: 0 additions & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@ repos:
- id: uv-lock
name: neutron-understack
args: ["-D", "python/neutron-understack"]
- id: uv-lock
name: understack-flavor-matcher
args: ["-D", "python/understack-flavor-matcher"]
- id: uv-lock
name: understack-workflows
args: ["-D", "python/understack-workflows"]
Expand All @@ -92,9 +89,6 @@ repos:
files: '^python/understack-workflows/'
args: ["--threads", "python/understack-workflows"]
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@main#subdirectory=python/understack-flavor-matcher"
- "kubernetes"
- "pydantic"
- "pynautobot"
Expand Down
4 changes: 1 addition & 3 deletions containers/ironic-nautobot-client/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# copy in the code
COPY python/understack-workflows /tmp/understack/python/understack-workflows
COPY python/understack-flavor-matcher /tmp/understack/python/understack-flavor-matcher

# install our requirements and our packages
RUN --mount=type=cache,target=/root/.cache/uv \
Expand All @@ -18,8 +17,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
--python /opt/venv/bin/python \
netifaces \
psutil==6.1.1 \
/tmp/understack/python/understack-workflows \
/tmp/understack/python/understack-flavor-matcher
/tmp/understack/python/understack-workflows

FROM python:3.12-slim AS prod
LABEL org.opencontainers.image.description="UnderStack Workflows"
Expand Down
2 changes: 0 additions & 2 deletions containers/ironic/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ RUN git -C /src/sushy fetch --unshallow --tags && \
sed -i '/sushy==.*/d' /upper-constraints.txt

COPY python/ironic-understack /src/understack/ironic-understack
COPY python/understack-flavor-matcher /src/understack/understack-flavor-matcher

ARG OPENSTACK_VERSION="required_argument"
RUN --mount=type=cache,target=/root/.cache/uv \
Expand All @@ -33,7 +32,6 @@ RUN --mount=type=cache,target=/root/.cache/uv \
/src/ironic \
/src/sushy \
/src/understack/ironic-understack \
/src/understack/understack-flavor-matcher \
proliantutils==2.16.3

COPY containers/ironic/patches /tmp/patches/
Expand Down
8 changes: 4 additions & 4 deletions docs/design-guide/device-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ During bare metal enrollment:

1. Hardware is inspected via Ironic to collect CPU, memory, drive, and network
interface data
2. The `understack-flavor-matcher` service compares inspection data against
device type resource class definitions
2. A custom Ironic inspection hook (in `python/ironic-understack`) compares
inspection data against device type resource class definitions
3. When a match is found, the resource class name is set on the Ironic node's
`resource_class` property
4. Nova flavors are created or updated based on the resource class, making the
hardware available for workload scheduling
4. Other inspection hooks set `CUSTOM_` traits on the node based on discovered
hardware capabilities (NICs, GPUs, storage controllers, etc.)

**Multiple Resource Classes**: Define multiple resource classes for the same
device type when you have common build variations of the same chassis. For
Expand Down
10 changes: 4 additions & 6 deletions docs/design-guide/flavors.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ Flavors enable operators to:

### Workflow

1. **Inspection Phase**: Custom inspection code adds traits to Ironic nodes based on discovered hardware capabilities (NICs, GPUs, storage controllers, etc.)
2. **Device-Type Matching**: Hardware inspection data is matched against device-type definitions, setting the node's `resource_class` property
3. **Flavor Matching**: Flavor definitions match nodes by `resource_class` first, then filter by trait requirements
4. **Nova Flavor Creation**: Matched flavors use the CPU, memory, and drive specifications from the device-type resource class to create Nova flavors
1. **Inspection Phase**: Custom Ironic inspection hooks set the node's `resource_class` by matching discovered hardware against device-type definitions, and add `CUSTOM_` traits based on discovered capabilities (NICs, GPUs, storage controllers, etc.)
2. **Nova Flavor Sync**: An Ansible role reads flavor and device-type definitions from ConfigMaps and creates/updates Nova flavors with the appropriate `extra_specs` for Placement-based scheduling
3. **Scheduling**: When a user requests an instance with a flavor, Nova Placement finds matching Ironic nodes by `resource_class` and traits — this is the standard Nova/Ironic scheduling path

### Data Flow

Expand Down Expand Up @@ -345,7 +344,6 @@ Validation happens at:

* **Editor time**: YAML language server validates against schema URL
* **CLI time**: `understackctl flavor add` and `validate` commands perform full schema validation
* **Runtime**: Flavor-matcher validates ConfigMap contents before processing

## Relationship to Device-Types

Expand Down Expand Up @@ -388,7 +386,7 @@ name: m1.small
resource_class: m1.small # Links to device-type resource class
```

The flavor-matcher looks up `m1.small` in device-type definitions to find the CPU (16 cores), memory (131072 MB), and drives (480 GB) when creating the Nova flavor.
The Nova flavor sync Ansible role looks up `m1.small` in device-type definitions to find the CPU (16 cores), memory (131072 MB), and drives (480 GB) when creating the Nova flavor.

**Important**: Resource class names must be unique across all device types. Each resource class name should only be defined in one device type to avoid conflicts and ensure predictable Nova flavor creation. Validation checks enforce this constraint.

Expand Down
16 changes: 7 additions & 9 deletions docs/operator-guide/flavors.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ If nodes aren't matching the expected flavor:
1. Check Ironic node traits: `openstack baremetal node show <node> -f json -c traits`
2. Verify trait names have the `CUSTOM_` prefix in Ironic
3. Confirm inspection code is properly adding traits to nodes
4. Review flavor-matcher logs for matching errors
4. Review Ironic conductor logs for inspection hook errors

### Schema Validation in Editor

Expand Down Expand Up @@ -503,15 +503,13 @@ name: m1.small
resource_class: m1.small
```

The flavor-matcher service:
The Nova flavor sync Ansible role reads both ConfigMaps and creates a Nova flavor:

1. Reads the flavor definition
2. Queries Ironic for nodes with `resource_class=m1.small`
3. Looks up the device-type `m1.small` resource class
4. Creates a Nova flavor with:
* vcpus: 16 (from `cpu.cores`)
* ram: 131072 MB (from `memory.size`)
* disk: 480 GB (from `drives[0].size`)
* vcpus: 16 (from `cpu.cores`)
* ram: 131072 MB (from `memory.size`)
* disk: 480 GB (from `drives[0].size`)

Ironic inspection hooks set `resource_class=m1.small` on matching nodes at inspection time. Nova Placement then uses the resource class and traits to schedule instances to nodes via the standard Nova/Ironic scheduling path.

This separation allows you to:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flavor_matcher.device_type import DeviceType
from flavor_matcher.device_type import ResourceClass
from flavor_matcher.machine import Machine
from ironic_understack.flavor_matcher.device_type import DeviceType
from ironic_understack.flavor_matcher.device_type import ResourceClass
from ironic_understack.flavor_matcher.machine import Machine


class Matcher:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
import re
from pathlib import Path

from flavor_matcher.device_type import DeviceType
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
Expand All @@ -26,6 +23,9 @@
from oslo_utils import units

from ironic_understack.conf import CONF
from ironic_understack.flavor_matcher.device_type import DeviceType
from ironic_understack.flavor_matcher.machine import Machine
from ironic_understack.flavor_matcher.matcher import Matcher

LOG = log.getLogger(__name__)
DEVICE_TYPES = DeviceType.from_directory(Path(CONF.ironic_understack.device_types_dir))
Expand Down
6 changes: 3 additions & 3 deletions python/ironic-understack/ironic_understack/resource_class.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# from ironic.drivers.modules.inspector.hooks import base
from pathlib import Path

from flavor_matcher.device_type import DeviceType
from flavor_matcher.machine import Machine
from flavor_matcher.matcher import Matcher
from ironic.common import exception
from ironic.drivers.modules.inspector.hooks import base
from oslo_log import log as logging

from ironic_understack.conf import CONF
from ironic_understack.flavor_matcher.device_type import DeviceType
from ironic_understack.flavor_matcher.machine import Machine
from ironic_understack.flavor_matcher.matcher import Matcher

LOG = logging.getLogger(__name__)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from flavor_matcher.device_type import DeviceType
from ironic_understack.flavor_matcher.device_type import DeviceType


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from flavor_matcher.machine import Machine
from ironic_understack.flavor_matcher.machine import Machine


def test_memory_gb_property():
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import pytest

from flavor_matcher.device_type import CpuSpec
from flavor_matcher.device_type import DeviceType
from flavor_matcher.device_type import DriveSpec
from flavor_matcher.device_type import MemorySpec
from flavor_matcher.device_type import ResourceClass
from flavor_matcher.machine import Machine
from flavor_matcher.matcher import Matcher
from ironic_understack.flavor_matcher.device_type import CpuSpec
from ironic_understack.flavor_matcher.device_type import DeviceType
from ironic_understack.flavor_matcher.device_type import DriveSpec
from ironic_understack.flavor_matcher.device_type import MemorySpec
from ironic_understack.flavor_matcher.device_type import ResourceClass
from ironic_understack.flavor_matcher.machine import Machine
from ironic_understack.flavor_matcher.matcher import Matcher


@pytest.fixture
Expand Down
4 changes: 0 additions & 4 deletions python/ironic-understack/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ license = "MIT"
dependencies = [
"ironic>=32.0,<36",
"pyyaml~=6.0",
"understack-flavor-matcher",
]

# REMOVE ME: delete this once we move away from patching a venv or we upgrade to a newer ironic base version
Expand Down Expand Up @@ -45,9 +44,6 @@ test = [
[tool.uv]
default-groups = ["test"]

[tool.uv.sources]
understack-flavor-matcher = { path = "../understack-flavor-matcher" }

[tool.hatch.build.targets.sdist]
include = ["ironic_understack"]

Expand Down
20 changes: 0 additions & 20 deletions python/ironic-understack/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion python/understack-flavor-matcher/README.md

This file was deleted.

46 changes: 0 additions & 46 deletions python/understack-flavor-matcher/pyproject.toml

This file was deleted.

Empty file.
Loading
Loading