Skip to content

Commit d3c84b6

Browse files
committed
test: New tests to validate postgres password autogeneration
1 parent bfad94d commit d3c84b6

File tree

4 files changed

+183
-0
lines changed

4 files changed

+183
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Database check tests for model catalog
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import pytest
2+
from kubernetes.dynamic import DynamicClient
3+
from ocp_resources.secret import Secret
4+
from pytest_testconfig import config as py_config
5+
from simple_logger.logger import get_logger
6+
from timeout_sampler import TimeoutSampler
7+
8+
from .utils import extract_secret_values
9+
10+
LOGGER = get_logger(name=__name__)
11+
12+
13+
@pytest.fixture(scope="class")
14+
def model_catalog_postgres_secret(admin_client: DynamicClient) -> Secret:
15+
"""Get the model-catalog-postgres secret from model registry namespace"""
16+
return Secret(
17+
client=admin_client,
18+
name="model-catalog-postgres",
19+
namespace=py_config["model_registry_namespace"],
20+
ensure_exists=True,
21+
)
22+
23+
24+
@pytest.fixture(scope="class")
25+
def model_catalog_postgres_secret_values(model_catalog_postgres_secret: Secret) -> dict[str, str]:
26+
"""Capture current values of model-catalog-postgres secret in model registry namespace"""
27+
return extract_secret_values(secret=model_catalog_postgres_secret)
28+
29+
30+
@pytest.fixture(scope="class")
31+
def recreated_model_catalog_postgres_secret(
32+
admin_client: DynamicClient, model_catalog_postgres_secret: Secret
33+
) -> dict[str, str]:
34+
"""Delete model-catalog-postgres secret and wait for it to be recreated"""
35+
model_registry_namespace = py_config["model_registry_namespace"]
36+
resource_name = "model-catalog-postgres"
37+
38+
LOGGER.info(f"Deleting secret {resource_name} in namespace {model_registry_namespace}")
39+
model_catalog_postgres_secret.delete()
40+
41+
# Wait for the secret to be recreated by the operator
42+
LOGGER.info(f"Waiting for secret {resource_name} to be recreated...")
43+
44+
recreated_secret = None
45+
for secret in TimeoutSampler(
46+
wait_timeout=120,
47+
sleep=10,
48+
func=Secret,
49+
client=admin_client,
50+
name=resource_name,
51+
namespace=model_registry_namespace,
52+
):
53+
if secret.exists:
54+
LOGGER.info(f"Secret {resource_name} has been recreated")
55+
recreated_secret = secret
56+
break
57+
58+
return extract_secret_values(secret=recreated_secret)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import pytest
2+
from kubernetes.dynamic import DynamicClient
3+
from simple_logger.logger import get_logger
4+
5+
from tests.model_registry.model_catalog.utils import get_postgres_pod_in_namespace
6+
from tests.model_registry.utils import (
7+
wait_for_model_catalog_pod_ready_after_deletion,
8+
)
9+
10+
LOGGER = get_logger(name=__name__)
11+
12+
13+
def test_model_catalog_postgres_secret_exists(model_catalog_postgres_secret_values):
14+
"""Test that model-catalog-postgres secret exists and is accessible"""
15+
secret_values = model_catalog_postgres_secret_values
16+
assert secret_values, "model-catalog-postgres secret should exist and be accessible"
17+
18+
# Log the keys (not values) for debugging
19+
LOGGER.info(f"Secret contains keys: {list(secret_values.keys())}")
20+
21+
22+
@pytest.mark.dependency(name="test_model_catalog_postgres_password_recreation")
23+
def test_model_catalog_postgres_password_recreation(
24+
model_catalog_postgres_secret_values, recreated_model_catalog_postgres_secret
25+
):
26+
"""Test that secret recreation generates new password but preserves user/database name"""
27+
# Verify database-name and database-user did NOT change
28+
unchanged_keys = ["database-name", "database-user"]
29+
for key in unchanged_keys:
30+
assert model_catalog_postgres_secret_values[key] == recreated_model_catalog_postgres_secret[key], (
31+
f"{key} should remain the same after secret recreation"
32+
)
33+
34+
# Verify database-password DID change (randomization working)
35+
assert (
36+
model_catalog_postgres_secret_values["database-password"]
37+
!= recreated_model_catalog_postgres_secret["database-password"]
38+
), "database-password should be different after secret recreation (randomized)"
39+
40+
LOGGER.info("Password randomization verified - new password generated on recreation")
41+
42+
43+
@pytest.mark.dependency(depends=["test_model_catalog_postgres_password_recreation"])
44+
def test_model_catalog_pod_ready_after_secret_recreation(admin_client: DynamicClient, model_registry_namespace: str):
45+
"""Test that model catalog pod becomes ready after secret recreation"""
46+
# delete the postgres pod first
47+
get_postgres_pod_in_namespace(admin_client=admin_client).delete()
48+
# Wait for model catalog pod to be ready after the secret deletion/recreation
49+
wait_for_model_catalog_pod_ready_after_deletion(
50+
client=admin_client, model_registry_namespace=model_registry_namespace
51+
)
52+
LOGGER.info("Model catalog pod is ready after secret recreation")
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import base64
2+
import binascii
3+
4+
from kubernetes.dynamic import DynamicClient
5+
from ocp_resources.pod import Pod
6+
from ocp_resources.secret import Secret
7+
from simple_logger.logger import get_logger
8+
from timeout_sampler import TimeoutSampler
9+
10+
from utilities.constants import Timeout
11+
12+
LOGGER = get_logger(name=__name__)
13+
14+
15+
def extract_secret_values(secret: Secret) -> dict[str, str]:
16+
"""Extract and decode secret data values from a Secret object.
17+
18+
Args:
19+
secret: The Secret object to extract values from
20+
21+
Returns:
22+
Dict mapping secret keys to decoded string values
23+
"""
24+
secret_values = {}
25+
if secret.instance.data:
26+
for key, encoded_value in secret.instance.data.items():
27+
try:
28+
decoded_value = base64.b64decode(s=encoded_value).decode(encoding="utf-8")
29+
secret_values[key] = decoded_value
30+
except (binascii.Error, UnicodeDecodeError) as e:
31+
LOGGER.warning(f"Failed to decode secret key '{key}': {e}")
32+
secret_values[key] = encoded_value # Keep encoded if decode fails
33+
34+
LOGGER.info(f"Captured secret with keys: {list(secret_values.keys())}")
35+
return secret_values
36+
37+
38+
def wait_for_model_catalog_pod_ready_after_deletion(client: DynamicClient, model_registry_namespace: str) -> None:
39+
"""Wait for model catalog pod to be ready after secret deletion/recreation.
40+
41+
Args:
42+
client: The Kubernetes client to use
43+
model_registry_namespace: The namespace where model catalog is deployed
44+
45+
Raises:
46+
TimeoutExpiredError: If pod doesn't become ready within timeout
47+
"""
48+
LOGGER.info(f"Waiting for model catalog pod to be ready in namespace {model_registry_namespace}")
49+
50+
# Wait for model catalog pod to be running and ready
51+
for pod in TimeoutSampler(
52+
wait_timeout=Timeout.TIMEOUT_5MIN,
53+
sleep=10,
54+
func=Pod.get,
55+
client=client,
56+
namespace=model_registry_namespace,
57+
label_selector="app.kubernetes.io/name=model-catalog",
58+
):
59+
pods = list(pod)
60+
if pods:
61+
catalog_pod = pods[0] # Get the first (should be only) model catalog pod
62+
63+
# Check if pod is running and has conditions
64+
if catalog_pod.status == Pod.Status.RUNNING and catalog_pod.instance.status.conditions:
65+
for condition in catalog_pod.instance.status.conditions:
66+
if condition.type == "Ready" and condition.status == "True":
67+
LOGGER.info(f"Model catalog pod {catalog_pod.name} is ready")
68+
return
69+
70+
LOGGER.info("Model catalog pod not ready yet, continuing to wait...")
71+
72+
LOGGER.error("Timeout waiting for model catalog pod to become ready")

0 commit comments

Comments
 (0)