Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: set Flavor on enroll #481

Merged
merged 11 commits into from
Nov 18, 2024
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
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