diff --git a/Chart.yaml b/Chart.yaml index 16a4b4e..ca9252b 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: hammerdb-scale description: Modular Helm chart for parallel HammerDB scale testing across multiple databases (SQL Server, PostgreSQL, Oracle, MySQL) type: application -version: 1.1.0 +version: 2.0.2 appVersion: "5.0" keywords: - hammerdb diff --git a/README.md b/README.md index a5fe172..17d0520 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![PyPI version](https://img.shields.io/pypi/v/hammerdb-scale)](https://pypi.org/project/hammerdb-scale/) [![Python versions](https://img.shields.io/pypi/pyversions/hammerdb-scale)](https://pypi.org/project/hammerdb-scale/) -[![License](https://img.shields.io/pypi/l/hammerdb-scale)](LICENSE) +[![License](https://img.shields.io/pypi/l/hammerdb-scale)](https://github.com/PureStorage-OpenConnect/hammerdb-scale/blob/main/LICENSE) A Python CLI for orchestrating parallel HammerDB database benchmarks at scale on Kubernetes. diff --git a/pyproject.toml b/pyproject.toml index 6921073..55753b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "hammerdb-scale" -version = "2.0.1" +version = "2.0.3" description = "CLI for orchestrating parallel HammerDB database benchmarks at scale on Kubernetes" readme = {file = "README.md", content-type = "text/markdown"} license = "Apache-2.0" diff --git a/src/hammerdb_scale/chart/Chart.yaml b/src/hammerdb_scale/chart/Chart.yaml index 16a4b4e..ca9252b 100644 --- a/src/hammerdb_scale/chart/Chart.yaml +++ b/src/hammerdb_scale/chart/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: hammerdb-scale description: Modular Helm chart for parallel HammerDB scale testing across multiple databases (SQL Server, PostgreSQL, Oracle, MySQL) type: application -version: 1.1.0 +version: 2.0.2 appVersion: "5.0" keywords: - hammerdb diff --git a/src/hammerdb_scale/constants.py b/src/hammerdb_scale/constants.py index 7849a33..b10922a 100644 --- a/src/hammerdb_scale/constants.py +++ b/src/hammerdb_scale/constants.py @@ -2,7 +2,7 @@ from pathlib import Path -VERSION = "2.0.1" +VERSION = "2.0.2" DEFAULT_CONFIG_FILENAMES = ["hammerdb-scale.yaml", "hammerdb-scale.yml"] CONFIG_ENV_VAR = "HAMMERDB_SCALE_CONFIG" diff --git a/src/hammerdb_scale/k8s/jobs.py b/src/hammerdb_scale/k8s/jobs.py index 4e6de92..3ae2af2 100644 --- a/src/hammerdb_scale/k8s/jobs.py +++ b/src/hammerdb_scale/k8s/jobs.py @@ -135,8 +135,8 @@ def resolve_test_id( if cli_id: return cli_id - # Try K8s - k8s_id = _find_most_recent_k8s_test_id(namespace) + # Try K8s — filter by deployment_name if a config was provided + k8s_id = _find_most_recent_k8s_test_id(namespace, deployment_name) if k8s_id: return k8s_id @@ -155,8 +155,15 @@ def resolve_test_id( raise NoResultsError("No test runs found in K8s or local results directory.") -def _find_most_recent_k8s_test_id(namespace: str) -> str | None: - """Find the most recent test ID from Helm releases.""" +def _find_most_recent_k8s_test_id( + namespace: str, deployment_name: str | None = None +) -> str | None: + """Find the most recent test ID from Helm releases. + + When deployment_name is provided (i.e. a config file was given), + only return a test ID that belongs to that deployment. This prevents + a running job from a *different* config from shadowing stored results. + """ try: releases = helm_list(namespace) if not releases: @@ -187,6 +194,10 @@ def _find_most_recent_k8s_test_id(namespace: str) -> str | None: labels = items[0].get("metadata", {}).get("labels", {}) test_id = labels.get("hammerdb.io/test-id") if test_id: + if deployment_name and not test_id.startswith( + deployment_name + "-" + ): + continue return test_id except (KubectlError, json.JSONDecodeError): continue diff --git a/src/hammerdb_scale/k8s/naming.py b/src/hammerdb_scale/k8s/naming.py index 391a2e0..dd46f5c 100644 --- a/src/hammerdb_scale/k8s/naming.py +++ b/src/hammerdb_scale/k8s/naming.py @@ -9,9 +9,20 @@ def generate_run_hash(deployment_name: str, test_id: str) -> str: - """Deterministic 8-char hash for job naming.""" + """Deterministic 8-char hash for job naming. + + Always starts with a letter to prevent YAML parsers from + interpreting hex strings like '764303e9' as scientific notation. + """ raw = f"{deployment_name}-{test_id}" - return hashlib.sha256(raw.encode()).hexdigest()[:8] + digest = hashlib.sha256(raw.encode()).hexdigest() + # Find first 8-char window starting with a letter + for i in range(len(digest) - 7): + candidate = digest[i : i + 8] + if candidate[0].isalpha(): + return candidate + # Fallback: prefix with 'a', take 7 hex chars + return "a" + digest[:7] def generate_job_name(phase: str, target_index: int, run_hash: str) -> str: diff --git a/tests/test_naming.py b/tests/test_naming.py index 1291c47..d998537 100644 --- a/tests/test_naming.py +++ b/tests/test_naming.py @@ -34,6 +34,16 @@ def test_run_hash_different_inputs(): assert h1 != h2 +def test_run_hash_starts_with_letter(): + """Hash must start with a letter to avoid YAML scientific notation parsing. + + e.g. '764303e9' looks like 7.64303e+9 to YAML 1.1 parsers. + """ + for i in range(100): + h = generate_run_hash(f"app-{i}", f"app-{i}-20260101-{i:04d}") + assert h[0].isalpha(), f"Hash {h!r} starts with a digit" + + def test_job_name_format(): name = generate_job_name("run", 3, "a1b2c3d4") assert name == "hdb-run-03-a1b2c3d4"