Skip to content

Commit b840a9f

Browse files
committed
change: Add test for async job that uses S3 and OCI connection
1 parent 163feb5 commit b840a9f

File tree

4 files changed

+114
-135
lines changed

4 files changed

+114
-135
lines changed

tests/model_registry/async_job/conftest.py

Lines changed: 107 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@
2929
from model_registry import ModelRegistry as ModelRegistryClient
3030

3131
from utilities.infra import create_ns
32-
from utilities.constants import OCIRegistry, MinIo, Protocols, Labels
32+
from utilities.constants import OCIRegistry, MinIo, Protocols, Labels, ApiGroups
3333
from utilities.general import b64_encoded_string
34-
from tests.model_registry.async_job.utils import get_async_job_s3_secret_dict, upload_test_model_to_minio_from_image
34+
from tests.model_registry.async_job.utils import upload_test_model_to_minio_from_image
3535
from tests.model_registry.utils import get_mr_service_by_label, get_endpoint_from_mr_service
3636
from tests.model_registry.async_job.constants import REPO_NAME
37+
from utilities.general import get_s3_secret_dict
3738

3839

3940
# We need to upstream this to the wrapper library
@@ -57,24 +58,31 @@ def s3_secret_for_async_job(
5758
service_account: ServiceAccount,
5859
minio_service: Service,
5960
) -> Generator[Secret, Any, Any]:
60-
"""Create S3 credentials secret for async upload job"""
61+
"""Create S3 data connection for async upload job"""
6162
# Construct MinIO endpoint from service
6263
minio_endpoint = (
6364
f"http://{minio_service.name}.{minio_service.namespace}.svc.cluster.local:{MinIo.Metadata.DEFAULT_PORT}"
6465
)
6566

6667
with Secret(
6768
client=admin_client,
68-
name=f"async-job-s3-secret-{shortuuid.uuid().lower()}",
69+
name=f"async-job-s3-connection-{shortuuid.uuid().lower()}",
6970
namespace=service_account.namespace,
70-
data_dict=get_async_job_s3_secret_dict(
71-
access_key=MinIo.Credentials.ACCESS_KEY_VALUE,
72-
secret_access_key=MinIo.Credentials.SECRET_KEY_VALUE,
73-
s3_bucket=MinIo.Buckets.MODELMESH_EXAMPLE_MODELS,
74-
s3_endpoint=minio_endpoint,
75-
s3_region="us-east-1", # Default region for MinIO
71+
data_dict=get_s3_secret_dict(
72+
aws_access_key=MinIo.Credentials.ACCESS_KEY_VALUE,
73+
aws_secret_access_key=MinIo.Credentials.SECRET_KEY_VALUE,
74+
aws_s3_bucket=MinIo.Buckets.MODELMESH_EXAMPLE_MODELS,
75+
aws_s3_endpoint=minio_endpoint,
76+
aws_default_region="us-east-1", # Default region for MinIO
7677
),
77-
type="Opaque",
78+
label={
79+
Labels.OpenDataHub.DASHBOARD: "true",
80+
Labels.OpenDataHubIo.MANAGED: "true",
81+
},
82+
annotations={
83+
f"{ApiGroups.OPENDATAHUB_IO}/connection-type": "s3",
84+
"openshift.io/display-name": "My S3 Credentials",
85+
},
7886
) as secret:
7987
yield secret
8088

@@ -85,27 +93,36 @@ def oci_secret_for_async_job(
8593
service_account: ServiceAccount,
8694
oci_registry_host: str,
8795
) -> Generator[Secret, Any, Any]:
88-
"""Create OCI registry credentials secret for async upload job"""
96+
"""Create OCI registry data connection for async upload job"""
8997

9098
# Create anonymous dockerconfig for OCI registry (no authentication)
91-
# This matches the zot registry setup which allows anonymous access
9299
dockerconfig = {
93100
"auths": {
94101
f"{oci_registry_host}:{OCIRegistry.Metadata.DEFAULT_PORT}": {
95102
"auth": "",
96-
"email": "user@example.com", # Anonymous access
103+
"email": "user@example.com",
97104
}
98105
}
99106
}
100107

108+
data_dict = {
109+
".dockerconfigjson": b64_encoded_string(json.dumps(dockerconfig)),
110+
"ACCESS_TYPE": b64_encoded_string(json.dumps('["Push,Pull"]')),
111+
"OCI_HOST": b64_encoded_string(json.dumps(f"{oci_registry_host}:{OCIRegistry.Metadata.DEFAULT_PORT}")),
112+
}
113+
101114
with Secret(
102115
client=admin_client,
103-
name=f"async-job-oci-secret-{shortuuid.uuid().lower()}",
116+
name=f"async-job-oci-connection-{shortuuid.uuid().lower()}",
104117
namespace=service_account.namespace,
105-
data_dict={
106-
".dockerconfigjson": b64_encoded_string(json.dumps(dockerconfig)),
107-
"ACCESS_TYPE": b64_encoded_string(json.dumps('["Push,Pull"]')),
108-
"OCI_HOST": b64_encoded_string(json.dumps(f"{oci_registry_host}:{OCIRegistry.Metadata.DEFAULT_PORT}")),
118+
data_dict=data_dict,
119+
label={
120+
Labels.OpenDataHub.DASHBOARD: "true",
121+
Labels.OpenDataHubIo.MANAGED: "true",
122+
},
123+
annotations={
124+
f"{ApiGroups.OPENDATAHUB_IO}/connection-type-ref": "oci-v1",
125+
"openshift.io/display-name": "My OCI Credentials",
109126
},
110127
type="kubernetes.io/dockerconfigjson",
111128
) as secret:
@@ -125,18 +142,78 @@ def model_sync_async_job(
125142
mr_access_role_binding: RoleBinding,
126143
teardown_resources: bool,
127144
) -> Generator[Job, Any, Any]:
128-
"""Core Job fixture focused on Job deployment and configuration"""
129-
# Get dynamic OCI URI from route
130-
dynamic_oci_uri = f"{oci_registry_host}/{REPO_NAME}"
131-
132-
# Get model registry service and endpoint
133-
mr_instance = model_registry_instance[0] # Use first instance
145+
"""
146+
Job fixture for async model upload with mounted secret files.
147+
148+
This fixture creates a Kubernetes Job that:
149+
1. Mounts S3 credentials for source model access
150+
2. Mounts OCI credentials for destination registry
151+
3. Configures environment variables for model sync parameters
152+
4. Waits for job completion before yielding
153+
154+
Args:
155+
admin_client: Kubernetes client for resource management
156+
sa_token: Service account token for Model Registry authentication
157+
service_account: Service account for the job
158+
model_registry_namespace: Namespace containing Model Registry
159+
model_registry_instance: List of Model Registry instances
160+
s3_secret_for_async_job: Secret containing S3 credentials
161+
oci_secret_for_async_job: Secret containing OCI registry credentials
162+
oci_registry_host: OCI registry hostname
163+
mr_access_role_binding: Role binding for Model Registry access
164+
teardown_resources: Whether to clean up resources after test
165+
166+
Returns:
167+
Generator yielding the created Job resource
168+
"""
169+
170+
# Get Model Registry service endpoint for connection
171+
mr_instance = model_registry_instance[0]
134172
mr_service = get_mr_service_by_label(
135173
client=admin_client, namespace_name=model_registry_namespace, mr_instance=mr_instance
136174
)
137175
mr_endpoint = get_endpoint_from_mr_service(svc=mr_service, protocol=Protocols.REST)
138176
mr_host = mr_endpoint.split(":")[0]
139-
mr_port = mr_endpoint.split(":")[1]
177+
178+
# Volume mounts for credentials
179+
volume_mounts = [
180+
{
181+
"name": "source-credentials",
182+
"readOnly": True,
183+
"mountPath": VOLUME_MOUNTS["SOURCE_CREDS_PATH"],
184+
},
185+
{
186+
"name": "destination-credentials",
187+
"readOnly": True,
188+
"mountPath": VOLUME_MOUNTS["DEST_CREDS_PATH"],
189+
},
190+
]
191+
192+
environment_variables = [
193+
# Source configuration - S3 credentials and model location
194+
{"name": "MODEL_SYNC_SOURCE_TYPE", "value": MODEL_SYNC_CONFIG["SOURCE_TYPE"]},
195+
{"name": "MODEL_SYNC_SOURCE_AWS_KEY", "value": MODEL_SYNC_CONFIG["SOURCE_AWS_KEY"]},
196+
{"name": "MODEL_SYNC_SOURCE_S3_CREDENTIALS_PATH", "value": VOLUME_MOUNTS["SOURCE_CREDS_PATH"]},
197+
# Model identification parameters
198+
{"name": "MODEL_SYNC_MODEL_ID", "value": MODEL_SYNC_CONFIG["MODEL_ID"]},
199+
{"name": "MODEL_SYNC_MODEL_VERSION_ID", "value": MODEL_SYNC_CONFIG["MODEL_VERSION_ID"]},
200+
{"name": "MODEL_SYNC_MODEL_ARTIFACT_ID", "value": MODEL_SYNC_CONFIG["MODEL_ARTIFACT_ID"]},
201+
# Model Registry connection parameters
202+
{"name": "MODEL_SYNC_REGISTRY_SERVER_ADDRESS", "value": f"https://{mr_host}"},
203+
{"name": "MODEL_SYNC_REGISTRY_USER_TOKEN", "value": sa_token},
204+
{"name": "MODEL_SYNC_REGISTRY_IS_SECURE", "value": "False"},
205+
# OCI destination configuration
206+
{
207+
"name": "MODEL_SYNC_DESTINATION_OCI_REGISTRY",
208+
"value": f"{oci_registry_host}:{OCIRegistry.Metadata.DEFAULT_PORT}",
209+
},
210+
{"name": "MODEL_SYNC_DESTINATION_OCI_URI", "value": f"{oci_registry_host}/{REPO_NAME}"},
211+
{"name": "MODEL_SYNC_DESTINATION_OCI_BASE_IMAGE", "value": MODEL_SYNC_CONFIG["DESTINATION_OCI_BASE_IMAGE"]},
212+
{
213+
"name": "MODEL_SYNC_DESTINATION_OCI_ENABLE_TLS_VERIFY",
214+
"value": MODEL_SYNC_CONFIG["DESTINATION_OCI_ENABLE_TLS_VERIFY"],
215+
},
216+
]
140217

141218
with JobWithVolumes(
142219
client=admin_client,
@@ -149,84 +226,13 @@ def model_sync_async_job(
149226
{
150227
"name": "async-upload",
151228
"image": ASYNC_UPLOAD_IMAGE,
152-
"volumeMounts": [
153-
{
154-
"name": "source-credentials",
155-
"readOnly": True,
156-
"mountPath": VOLUME_MOUNTS["SOURCE_CREDS_PATH"],
157-
},
158-
{
159-
"name": "destination-credentials",
160-
"readOnly": True,
161-
"mountPath": VOLUME_MOUNTS["DEST_CREDS_PATH"],
162-
},
163-
],
164-
"env": [
165-
# Proxy settings
166-
{"name": "HTTP_PROXY", "value": ""},
167-
{"name": "HTTPS_PROXY", "value": ""},
168-
{"name": "NO_PROXY", "value": "*.svc.cluster.local"},
169-
# Source configuration
170-
{"name": "MODEL_SYNC_SOURCE_TYPE", "value": MODEL_SYNC_CONFIG["SOURCE_TYPE"]},
171-
{"name": "MODEL_SYNC_SOURCE_AWS_KEY", "value": MODEL_SYNC_CONFIG["SOURCE_AWS_KEY"]},
172-
{
173-
"name": "MODEL_SYNC_SOURCE_S3_CREDENTIALS_PATH",
174-
"value": VOLUME_MOUNTS["SOURCE_CREDS_PATH"],
175-
},
176-
# Destination configuration
177-
{"name": "MODEL_SYNC_DESTINATION_TYPE", "value": MODEL_SYNC_CONFIG["DESTINATION_TYPE"]},
178-
{
179-
"name": "MODEL_SYNC_DESTINATION_OCI_URI",
180-
"value": f"{dynamic_oci_uri}",
181-
},
182-
{
183-
"name": "MODEL_SYNC_DESTINATION_OCI_REGISTRY",
184-
"value": f"{oci_registry_host}:{OCIRegistry.Metadata.DEFAULT_PORT}",
185-
},
186-
{
187-
"name": "MODEL_SYNC_DESTINATION_OCI_CREDENTIALS_PATH",
188-
"value": VOLUME_MOUNTS["DEST_DOCKERCONFIG_PATH"],
189-
},
190-
{
191-
"name": "MODEL_SYNC_DESTINATION_OCI_BASE_IMAGE",
192-
"value": MODEL_SYNC_CONFIG["DESTINATION_OCI_BASE_IMAGE"],
193-
},
194-
{
195-
"name": "MODEL_SYNC_DESTINATION_OCI_ENABLE_TLS_VERIFY",
196-
"value": MODEL_SYNC_CONFIG["DESTINATION_OCI_ENABLE_TLS_VERIFY"],
197-
},
198-
# Model parameters
199-
{"name": "MODEL_SYNC_MODEL_ID", "value": MODEL_SYNC_CONFIG["MODEL_ID"]},
200-
{"name": "MODEL_SYNC_MODEL_VERSION_ID", "value": MODEL_SYNC_CONFIG["MODEL_VERSION_ID"]},
201-
{
202-
"name": "MODEL_SYNC_MODEL_ARTIFACT_ID",
203-
"value": MODEL_SYNC_CONFIG["MODEL_ARTIFACT_ID"],
204-
},
205-
# Model Registry client params
206-
{
207-
"name": "MODEL_SYNC_REGISTRY_SERVER_ADDRESS",
208-
"value": f"https://{mr_host}",
209-
},
210-
{"name": "MODEL_SYNC_REGISTRY_PORT", "value": mr_port},
211-
{"name": "MODEL_SYNC_REGISTRY_AUTHOR", "value": "RHOAI async job test"},
212-
{"name": "MODEL_SYNC_REGISTRY_USER_TOKEN", "value": sa_token},
213-
{"name": "MODEL_SYNC_REGISTRY_IS_SECURE", "value": "False"},
214-
],
229+
"volumeMounts": volume_mounts,
230+
"env": environment_variables,
215231
}
216232
],
217233
volumes=[
218-
{
219-
"name": "source-credentials",
220-
"secret": {
221-
"secretName": s3_secret_for_async_job.name,
222-
},
223-
},
224-
{
225-
"name": "destination-credentials",
226-
"secret": {
227-
"secretName": oci_secret_for_async_job.name,
228-
},
229-
},
234+
{"name": "source-credentials", "secret": {"secretName": s3_secret_for_async_job.name}},
235+
{"name": "destination-credentials", "secret": {"secretName": oci_secret_for_async_job.name}},
230236
],
231237
teardown=teardown_resources,
232238
) as job:

tests/model_registry/async_job/test_async_upload_e2e.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@
5959
indirect=True,
6060
)
6161
class TestAsyncUploadE2E:
62-
"""RHOAIENG-29344: Test for async upload job with real MinIO, OCI registry, and Model Registry"""
62+
"""RHOAIENG-32501: Test for async upload job with real MinIO, OCI registry, Connection Secrets and Model Registry"""
6363

64+
# FAILS until https://github.com/kubeflow/model-registry/pull/1499 is merged
6465
@pytest.mark.dependency(name="job_creation_and_pod_spawning")
6566
def test_job_creation_and_pod_spawning(
6667
self: Self,

tests/model_registry/async_job/utils.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from ocp_resources.pod import Pod
55
from ocp_resources.service import Service
66
from utilities.constants import MinIo
7-
from utilities.general import b64_encoded_string
87
from simple_logger.logger import get_logger
98

109
LOGGER = get_logger(name=__name__)
@@ -43,36 +42,6 @@ def pull_manifest_from_oci_registry(registry_url: str, repo: str, tag: str) -> d
4342
return response.json()
4443

4544

46-
def get_async_job_s3_secret_dict(
47-
access_key: str,
48-
secret_access_key: str,
49-
s3_bucket: str,
50-
s3_endpoint: str,
51-
s3_region: str,
52-
) -> dict[str, str]:
53-
"""
54-
Returns a dictionary of s3 secret values
55-
56-
Args:
57-
access_key (str): access key
58-
secret_access_key (str): secret key
59-
s3_bucket (str): S3 bucket
60-
s3_endpoint (str): S3 endpoint
61-
s3_region (str): S3 region
62-
63-
Returns:
64-
dict[str, str]: A dictionary of s3 secret encoded values
65-
66-
"""
67-
return {
68-
"AWS_ACCESS_KEY_ID": b64_encoded_string(string_to_encode=access_key),
69-
"AWS_SECRET_ACCESS_KEY": b64_encoded_string(string_to_encode=secret_access_key),
70-
"AWS_BUCKET": b64_encoded_string(string_to_encode=s3_bucket),
71-
"AWS_ENDPOINT_URL": b64_encoded_string(string_to_encode=s3_endpoint),
72-
"AWS_REGION": b64_encoded_string(string_to_encode=s3_region),
73-
}
74-
75-
7645
def upload_test_model_to_minio_from_image(
7746
admin_client: DynamicClient,
7847
namespace: str,

utilities/general.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ def get_s3_secret_dict(
2727
aws_secret_access_key: str,
2828
aws_s3_bucket: str,
2929
aws_s3_endpoint: str,
30-
aws_s3_region: str,
30+
aws_s3_region: str | None = None,
31+
aws_default_region: str | None = None,
3132
) -> dict[str, str]:
3233
"""
3334
Returns a dictionary of s3 secret values
@@ -38,17 +39,19 @@ def get_s3_secret_dict(
3839
aws_s3_bucket (str): AWS S3 bucket
3940
aws_s3_endpoint (str): AWS S3 endpoint
4041
aws_s3_region (str): AWS S3 region
42+
aws_default_region (str): AWS default region
4143
4244
Returns:
4345
dict[str, str]: A dictionary of s3 secret encoded values
4446
4547
"""
48+
region = aws_default_region or aws_s3_region or "us-east-1"
4649
return {
4750
"AWS_ACCESS_KEY_ID": b64_encoded_string(string_to_encode=aws_access_key),
4851
"AWS_SECRET_ACCESS_KEY": b64_encoded_string(string_to_encode=aws_secret_access_key),
4952
"AWS_S3_BUCKET": b64_encoded_string(string_to_encode=aws_s3_bucket),
5053
"AWS_S3_ENDPOINT": b64_encoded_string(string_to_encode=aws_s3_endpoint),
51-
"AWS_DEFAULT_REGION": b64_encoded_string(string_to_encode=aws_s3_region),
54+
"AWS_DEFAULT_REGION": b64_encoded_string(string_to_encode=region),
5255
}
5356

5457

0 commit comments

Comments
 (0)