Skip to content

Commit

Permalink
Merge pull request #481 from rackerlabs/flavor-on-enroll
Browse files Browse the repository at this point in the history
feat: set Flavor on enroll
  • Loading branch information
skrobul authored Nov 18, 2024
2 parents 2eaf60b + b6565af commit b5a7302
Show file tree
Hide file tree
Showing 31 changed files with 881 additions and 90 deletions.
1 change: 0 additions & 1 deletion .github/workflows/code-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
matrix:
project:
- understack-workflows
- ironic-understack
- neutron-understack

defaults:
Expand Down
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions containers/ironic/Dockerfile.ironic
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 ironic_understack.flavor_spec import FlavorSpec
from ironic_understack.machine import Machine
from ironic_understack.matcher import Matcher
from ironic_understack.conf import CONF
from oslo_log import log
from oslo_utils import units
Expand Down Expand Up @@ -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)
Expand Down
22 changes: 18 additions & 4 deletions python/ironic-understack/ironic_understack/resource_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
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
import re

LOG = logging.getLogger(__name__)

Expand All @@ -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)
Expand Down
20 changes: 18 additions & 2 deletions python/ironic-understack/poetry.lock

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

1 change: 1 addition & 0 deletions python/ironic-understack/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions python/understack-flavor-matcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Mini library to identify a hardware flavor of a machine.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import yaml

from ironic_understack.machine import Machine
from flavor_matcher.machine import Machine


@dataclass
Expand Down Expand Up @@ -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 = []
Expand All @@ -65,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.

Expand All @@ -78,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

Expand All @@ -89,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Machine:
memory_mb: int
cpu: str
disk_gb: int
model: str

@property
def memory_gb(self) -> int:
Expand Down
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
Loading

0 comments on commit b5a7302

Please sign in to comment.