|
2 | 2 |
|
3 | 3 | import pytest |
4 | 4 | from kubernetes.dynamic import DynamicClient |
| 5 | +from ocp_resources.data_science_pipelines_application import DataSciencePipelinesApplication |
5 | 6 | from ocp_resources.namespace import Namespace |
6 | 7 | from ocp_resources.persistent_volume_claim import PersistentVolumeClaim |
7 | 8 | from ocp_resources.pod import Pod |
| 9 | +from ocp_resources.route import Route |
| 10 | +from ocp_resources.secret import Secret |
| 11 | +from ocp_resources.service import Service |
| 12 | +from timeout_sampler import TimeoutExpiredError, TimeoutSampler |
8 | 13 |
|
9 | 14 | from tests.llama_stack.eval.constants import DK_CUSTOM_DATASET_IMAGE |
| 15 | +from utilities.constants import MinIo |
10 | 16 |
|
11 | 17 |
|
12 | 18 | @pytest.fixture(scope="class") |
@@ -87,3 +93,160 @@ def teardown_lmeval_job_pod(admin_client, model_namespace) -> None: |
87 | 93 | ]: |
88 | 94 | for pod in pods: |
89 | 95 | pod.delete() |
| 96 | + |
| 97 | + |
| 98 | +@pytest.fixture(scope="class") |
| 99 | +def dspa( |
| 100 | + admin_client: DynamicClient, |
| 101 | + model_namespace: Namespace, |
| 102 | + minio_pod: Pod, |
| 103 | + minio_service: Service, |
| 104 | + dspa_s3_secret: Secret, |
| 105 | +) -> Generator[DataSciencePipelinesApplication, Any, Any]: |
| 106 | + """ |
| 107 | + Creates a DataSciencePipelinesApplication with MinIO object storage. |
| 108 | + """ |
| 109 | + |
| 110 | + with DataSciencePipelinesApplication( |
| 111 | + client=admin_client, |
| 112 | + name="dspa", |
| 113 | + namespace=model_namespace.name, |
| 114 | + dsp_version="v2", |
| 115 | + pod_to_pod_tls=True, |
| 116 | + api_server={ |
| 117 | + "deploy": True, |
| 118 | + "enableOauth": True, |
| 119 | + "enableSamplePipeline": False, |
| 120 | + "cacheEnabled": True, |
| 121 | + "artifactSignedURLExpirySeconds": 60, |
| 122 | + "pipelineStore": "kubernetes", |
| 123 | + }, |
| 124 | + database={ |
| 125 | + "disableHealthCheck": False, |
| 126 | + "mariaDB": { |
| 127 | + "deploy": True, |
| 128 | + "pipelineDBName": "mlpipeline", |
| 129 | + "pvcSize": "10Gi", |
| 130 | + "username": "mlpipeline", |
| 131 | + }, |
| 132 | + }, |
| 133 | + object_storage={ |
| 134 | + "disableHealthCheck": False, |
| 135 | + "enableExternalRoute": False, |
| 136 | + "externalStorage": { |
| 137 | + "bucket": "ods-ci-ds-pipelines", |
| 138 | + "host": f"{minio_service.instance.spec.clusterIP}:{MinIo.Metadata.DEFAULT_PORT}", |
| 139 | + "region": "us-east-1", |
| 140 | + "scheme": "http", |
| 141 | + "s3CredentialsSecret": { |
| 142 | + "accessKey": "AWS_ACCESS_KEY_ID", # pragma: allowlist secret |
| 143 | + "secretKey": "AWS_SECRET_ACCESS_KEY", # pragma: allowlist secret |
| 144 | + "secretName": dspa_s3_secret.name, |
| 145 | + }, |
| 146 | + }, |
| 147 | + }, |
| 148 | + persistence_agent={ |
| 149 | + "deploy": True, |
| 150 | + "numWorkers": 2, |
| 151 | + }, |
| 152 | + scheduled_workflow={ |
| 153 | + "deploy": True, |
| 154 | + "cronScheduleTimezone": "UTC", |
| 155 | + }, |
| 156 | + ) as dspa_resource: |
| 157 | + wait_for_dspa_pods( |
| 158 | + admin_client=admin_client, |
| 159 | + namespace=model_namespace.name, |
| 160 | + dspa_name=dspa_resource.name, |
| 161 | + ) |
| 162 | + yield dspa_resource |
| 163 | + |
| 164 | + |
| 165 | +def wait_for_dspa_pods(admin_client: DynamicClient, namespace: str, dspa_name: str, timeout: int = 300) -> None: |
| 166 | + """ |
| 167 | + Wait for all DataSciencePipelinesApplication pods to be running. |
| 168 | +
|
| 169 | + Args: |
| 170 | + admin_client: The admin client to use for pod retrieval |
| 171 | + namespace: The namespace where DSPA is deployed |
| 172 | + dspa_name: The name of the DSPA resource |
| 173 | + timeout: Timeout in seconds |
| 174 | + """ |
| 175 | + |
| 176 | + label_selector = f"dspa={dspa_name}" |
| 177 | + |
| 178 | + def _all_dspa_pods_running() -> bool: |
| 179 | + pods = list(Pod.get(dyn_client=admin_client, namespace=namespace, label_selector=label_selector)) |
| 180 | + if not pods: |
| 181 | + return False |
| 182 | + return all(pod.instance.status.phase == Pod.Status.RUNNING for pod in pods) |
| 183 | + |
| 184 | + sampler = TimeoutSampler( |
| 185 | + wait_timeout=timeout, |
| 186 | + sleep=10, |
| 187 | + func=_all_dspa_pods_running, |
| 188 | + ) |
| 189 | + |
| 190 | + for is_ready in sampler: |
| 191 | + if is_ready: |
| 192 | + return |
| 193 | + |
| 194 | + |
| 195 | +@pytest.fixture(scope="class") |
| 196 | +def dspa_s3_secret( |
| 197 | + admin_client: DynamicClient, |
| 198 | + model_namespace: Namespace, |
| 199 | + minio_service: Service, |
| 200 | +) -> Generator[Secret, Any, Any]: |
| 201 | + """ |
| 202 | + Creates a secret for DSPA S3 credentials using MinIO. |
| 203 | + """ |
| 204 | + with Secret( |
| 205 | + client=admin_client, |
| 206 | + name="dashboard-dspa-secret", |
| 207 | + namespace=model_namespace.name, |
| 208 | + string_data={ |
| 209 | + "AWS_ACCESS_KEY_ID": MinIo.Credentials.ACCESS_KEY_VALUE, |
| 210 | + "AWS_SECRET_ACCESS_KEY": MinIo.Credentials.SECRET_KEY_VALUE, |
| 211 | + "AWS_DEFAULT_REGION": "us-east-1", |
| 212 | + }, |
| 213 | + ) as secret: |
| 214 | + yield secret |
| 215 | + |
| 216 | + |
| 217 | +@pytest.fixture(scope="class") |
| 218 | +def dspa_route( |
| 219 | + admin_client: DynamicClient, |
| 220 | + model_namespace: Namespace, |
| 221 | + dspa: DataSciencePipelinesApplication, |
| 222 | +) -> Generator[Route, Any, Any]: |
| 223 | + """ |
| 224 | + Retrieves the Route for the DSPA API server. |
| 225 | + """ |
| 226 | + |
| 227 | + def _get_dspa_route() -> Route | None: |
| 228 | + routes = list( |
| 229 | + Route.get( |
| 230 | + dyn_client=admin_client, |
| 231 | + namespace=model_namespace.name, |
| 232 | + name="ds-pipeline-dspa", |
| 233 | + ) |
| 234 | + ) |
| 235 | + return routes[0] if routes else None |
| 236 | + |
| 237 | + sampler = TimeoutSampler( |
| 238 | + wait_timeout=120, |
| 239 | + sleep=5, |
| 240 | + func=_get_dspa_route, |
| 241 | + ) |
| 242 | + |
| 243 | + route = None |
| 244 | + for result in sampler: |
| 245 | + if result: |
| 246 | + route = result |
| 247 | + break |
| 248 | + |
| 249 | + if not route: |
| 250 | + raise TimeoutExpiredError(f"DSPA route {dspa.name} not found in namespace {model_namespace.name}") |
| 251 | + |
| 252 | + yield route |
0 commit comments