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
51 changes: 50 additions & 1 deletion nodescraper/models/analyzerargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,61 @@
# SOFTWARE.
#
###############################################################################
from pydantic import BaseModel
from typing import Any

from pydantic import BaseModel, model_validator


class AnalyzerArgs(BaseModel):
"""Base class for all analyzer arguments.

This class provides automatic string stripping for all string values
in analyzer args. All analyzer args classes should inherit from this
directly.

"""

model_config = {"extra": "forbid", "exclude_none": True}

@model_validator(mode="before")
@classmethod
def strip_string_values(cls, data: Any) -> Any:
"""Strip whitespace from all string values in analyzer args.

This validator recursively processes:
- String values: strips whitespace
- Lists: strips strings in lists
- Dicts: strips string values in dicts
- Other types: left unchanged

Args:
data: The input data to validate

Returns:
The data with all string values stripped
"""
if isinstance(data, dict):
return {k: cls._strip_value(v) for k, v in data.items()}
return data

@classmethod
def _strip_value(cls, value: Any) -> Any:
"""Recursively strip string values.

Args:
value: The value to process

Returns:
The processed value
"""
if isinstance(value, str):
return value.strip()
elif isinstance(value, list):
return [cls._strip_value(item) for item in value]
elif isinstance(value, dict):
return {k: cls._strip_value(v) for k, v in value.items()}
return value

@classmethod
def build_from_model(cls, datamodel):
"""Build analyzer args instance from data model object
Expand Down
4 changes: 2 additions & 2 deletions nodescraper/plugins/inband/memory/analyzer_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
# SOFTWARE.
#
###############################################################################
from pydantic import BaseModel
from nodescraper.models.analyzerargs import AnalyzerArgs


class MemoryAnalyzerArgs(BaseModel):
class MemoryAnalyzerArgs(AnalyzerArgs):
ratio: float = 0.66
memory_threshold: str = "30Gi"
52 changes: 28 additions & 24 deletions nodescraper/plugins/inband/package/package_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def regex_version_data(

value_found = False
for name, version in package_data.items():
self.logger.debug("Package data: %s, %s", name, version)
key_search_res = key_search.search(name)
if key_search_res:
value_found = True
Expand All @@ -79,16 +80,16 @@ def regex_version_data(
return value_found

def package_regex_search(
self, package_data: dict[str, str], exp_packge_data: dict[str, Optional[str]]
self, package_data: dict[str, str], exp_package_data: dict[str, Optional[str]]
):
"""Searches the package data for the expected package and version using regex

Args:
package_data (dict[str, str]): a dictionary of package names and versions
exp_packge_data (dict[str, Optional[str]]): a dictionary of expected package names and versions
exp_package_data (dict[str, Optional[str]]): a dictionary of expected package names and versions
"""
not_found_keys = []
for exp_key, exp_value in exp_packge_data.items():
for exp_key, exp_value in exp_package_data.items():
try:
if exp_value is not None:
value_search = re.compile(exp_value)
Expand Down Expand Up @@ -125,44 +126,46 @@ def package_regex_search(
return not_found_keys

def package_exact_match(
self, package_data: dict[str, str], exp_packge_data: dict[str, Optional[str]]
self, package_data: dict[str, str], exp_package_data: dict[str, Optional[str]]
):
"""Checks the package data for the expected package and version using exact match

Args:
package_data (dict[str, str]): a dictionary of package names and versions
exp_packge_data (dict[str, Optional[str]]): a dictionary of expected package names and versions
exp_package_data (dict[str, Optional[str]]): a dictionary of expected package names and versions
"""
not_found_match = []
not_found_version = []
for exp_key, exp_value in exp_packge_data.items():
self.logger.info(exp_key)
for exp_key, exp_value in exp_package_data.items():
self.logger.info("Expected value: %s, %s", exp_key, exp_value)
version = package_data.get(exp_key)
if exp_value is None:
self.logger.info("Found version: %s", version)
if version is None:
# package not found
not_found_version.append((exp_key, exp_value))
self._log_event(
EventCategory.APPLICATION,
f"Package {exp_key} not found in the package list",
EventPriority.ERROR,
{
"expected_package": exp_key,
"found_package": None,
"expected_version": exp_value,
"found_version": None,
},
)
elif exp_value is None:
# allow any version when expected version is None
if version is None:
# package not found
not_found_version.append((exp_key, version))
self._log_event(
EventCategory.APPLICATION,
f"Package {exp_key} not found in the package list",
EventPriority.ERROR,
{
"expected_package": exp_key,
"found_package": None,
"expected_version": exp_value,
"found_version": None,
},
)
continue
elif version != exp_value:
not_found_match.append((exp_key, version))
self._log_event(
EventCategory.APPLICATION,
f"Package {exp_key} Version Mismatch, Expected {exp_key} but found {version}",
f"Package {exp_key} Version Mismatch, Expected {exp_value} but found {version}",
EventPriority.ERROR,
{
"expected_package": exp_key,
"found_package": exp_key if version else None,
"found_package": exp_key,
"expected_version": exp_value,
"found_version": version,
},
Expand Down Expand Up @@ -191,6 +194,7 @@ def analyze_data(
self.result.message = f"Packages not found: {not_found_keys}"
self.result.status = ExecutionStatus.ERROR
else:
self.logger.info("Expected packages: %s", list(args.exp_package_ver.keys()))
not_found_match, not_found_version = self.package_exact_match(
data.version_info, args.exp_package_ver
)
Expand Down
5 changes: 3 additions & 2 deletions nodescraper/plugins/inband/rocm/analyzer_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@
###############################################################################
from typing import Union

from pydantic import BaseModel, Field, field_validator
from pydantic import Field, field_validator

from nodescraper.models.analyzerargs import AnalyzerArgs
from nodescraper.plugins.inband.rocm.rocmdata import RocmDataModel


class RocmAnalyzerArgs(BaseModel):
class RocmAnalyzerArgs(AnalyzerArgs):
exp_rocm: Union[str, list] = Field(default_factory=list)

@field_validator("exp_rocm", mode="before")
Expand Down
2 changes: 1 addition & 1 deletion nodescraper/plugins/inband/rocm/rocm_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def analyze_data(
return self.result

self.result.message = (
f"ROCm version mismatch! Expected: {rocm_version}, actual: {args.exp_rocm}"
f"ROCm version mismatch! Expected: {args.exp_rocm}, actual: {data.rocm_version}"
)
self.result.status = ExecutionStatus.ERROR
self._log_event(
Expand Down
6 changes: 4 additions & 2 deletions nodescraper/plugins/inband/storage/analyzer_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
###############################################################################
from typing import Optional

from pydantic import BaseModel, Field
from pydantic import Field

from nodescraper.models.analyzerargs import AnalyzerArgs

class StorageAnalyzerArgs(BaseModel):

class StorageAnalyzerArgs(AnalyzerArgs):
min_required_free_space_abs: Optional[str] = None
min_required_free_space_prct: Optional[int] = None
ignore_devices: Optional[list[str]] = Field(default_factory=list)
Expand Down