From b3623827502f62be70602f8baca5b9401baf4af1 Mon Sep 17 00:00:00 2001 From: Ruth Netser Date: Wed, 18 Dec 2024 16:31:18 +0200 Subject: [PATCH 1/7] Create size-labeler.yml --- .github/workflows/size-labeler.yml | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/size-labeler.yml diff --git a/.github/workflows/size-labeler.yml b/.github/workflows/size-labeler.yml new file mode 100644 index 000000000..8d0fcbd94 --- /dev/null +++ b/.github/workflows/size-labeler.yml @@ -0,0 +1,31 @@ +name: PR Size Labeler +on: + pull_request: + types: [opened, synchronize] + +permissions: + pull-requests: write + contents: write + +jobs: + label-size: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox uv + + - name: Run size labeler + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_NUMBER: ${{github.event.pull_request.number}} + run: uv run python .github/workflows/scripts/size_labeler.py From 3c6a875ef85e8e5dee1e7e4125f756b6874f842e Mon Sep 17 00:00:00 2001 From: Ruth Netser Date: Wed, 18 Dec 2024 16:32:36 +0200 Subject: [PATCH 2/7] Delete .github/workflows/size-labeler.yml --- .github/workflows/size-labeler.yml | 31 ------------------------------ 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/size-labeler.yml diff --git a/.github/workflows/size-labeler.yml b/.github/workflows/size-labeler.yml deleted file mode 100644 index 8d0fcbd94..000000000 --- a/.github/workflows/size-labeler.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: PR Size Labeler -on: - pull_request: - types: [opened, synchronize] - -permissions: - pull-requests: write - contents: write - -jobs: - label-size: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.13' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox uv - - - name: Run size labeler - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_PR_NUMBER: ${{github.event.pull_request.number}} - run: uv run python .github/workflows/scripts/size_labeler.py From 17938d6d99533a1882ccc07e00bfd352a594cd8f Mon Sep 17 00:00:00 2001 From: rnetser Date: Sun, 2 Feb 2025 19:22:27 +0200 Subject: [PATCH 3/7] model mesh - add auth tests --- .../model_server/authentication/conftest.py | 78 +++++++++-- .../test_model_mesh_authentication.py | 123 ++++++++++++++++++ tests/model_serving/model_server/conftest.py | 59 ++++++++- .../model_server/model_mesh/conftest.py | 67 ---------- .../model_server/ovms/model_mesh/conftest.py | 15 +-- utilities/infra.py | 11 +- 6 files changed, 253 insertions(+), 100 deletions(-) create mode 100644 tests/model_serving/model_server/authentication/test_model_mesh_authentication.py delete mode 100644 tests/model_serving/model_server/model_mesh/conftest.py diff --git a/tests/model_serving/model_server/authentication/conftest.py b/tests/model_serving/model_server/authentication/conftest.py index 0e3aa5d97..f0f8d28fa 100644 --- a/tests/model_serving/model_server/authentication/conftest.py +++ b/tests/model_serving/model_server/authentication/conftest.py @@ -1,4 +1,3 @@ -import shlex from typing import Any, Generator from urllib.parse import urlparse @@ -13,9 +12,8 @@ from ocp_resources.secret import Secret from ocp_resources.service_account import ServiceAccount from ocp_resources.serving_runtime import ServingRuntime -from pyhelper_utils.shell import run_command -from utilities.infra import create_isvc_view_role, create_ns, s3_endpoint_secret, create_inference_token +from utilities.infra import create_resource_view_role, create_ns, s3_endpoint_secret, create_inference_token from tests.model_serving.model_server.utils import create_isvc from utilities.constants import ( KServeDeploymentType, @@ -85,9 +83,9 @@ def http_view_role( admin_client: DynamicClient, http_s3_caikit_serverless_inference_service: InferenceService, ) -> Role: - with create_isvc_view_role( + with create_resource_view_role( client=admin_client, - isvc=http_s3_caikit_serverless_inference_service, + resource=http_s3_caikit_serverless_inference_service, name=f"{http_s3_caikit_serverless_inference_service.name}-view", resource_names=[http_s3_caikit_serverless_inference_service.name], ) as role: @@ -99,9 +97,9 @@ def http_raw_view_role( admin_client: DynamicClient, http_s3_caikit_raw_inference_service: InferenceService, ) -> Role: - with create_isvc_view_role( + with create_resource_view_role( client=admin_client, - isvc=http_s3_caikit_raw_inference_service, + resource=http_s3_caikit_raw_inference_service, name=f"{http_s3_caikit_raw_inference_service.name}-view", resource_names=[http_s3_caikit_raw_inference_service.name], ) as role: @@ -192,9 +190,9 @@ def patched_remove_raw_authentication_isvc( @pytest.fixture(scope="class") def grpc_view_role(admin_client: DynamicClient, grpc_s3_inference_service: InferenceService) -> Role: - with create_isvc_view_role( + with create_resource_view_role( client=admin_client, - isvc=grpc_s3_inference_service, + resource=grpc_s3_inference_service, name=f"{grpc_s3_inference_service.name}-view", resource_names=[grpc_s3_inference_service.name], ) as role: @@ -222,11 +220,7 @@ def grpc_role_binding( @pytest.fixture(scope="class") def grpc_inference_token(grpc_model_service_account: ServiceAccount, grpc_role_binding: RoleBinding) -> str: - return run_command( - command=shlex.split( - f"oc create token -n {grpc_model_service_account.namespace} {grpc_model_service_account.name}" - ) - )[1].strip() + return create_inference_token(model_service_account=grpc_model_service_account) @pytest.fixture(scope="class") @@ -364,3 +358,59 @@ def http_s3_caikit_tgis_serving_runtime( enable_grpc=False, ) as model_runtime: yield model_runtime + + +@pytest.fixture() +def patched_remove_authentication_model_mesh_isvc( + admin_client: DynamicClient, + http_s3_openvino_model_mesh_inference_service: InferenceService, +) -> InferenceService: + with ResourceEditor( + patches={ + http_s3_openvino_model_mesh_inference_service: { + "metadata": { + "annotations": {Annotations.KserveAuth.SECURITY: "false"}, + } + } + } + ): + yield http_s3_openvino_model_mesh_inference_service + + +@pytest.fixture(scope="class") +def http_model_mesh_view_role( + admin_client: DynamicClient, + http_s3_ovms_model_mesh_serving_runtime: ServingRuntime, +) -> Role: + with create_resource_view_role( + client=admin_client, + resource=http_s3_ovms_model_mesh_serving_runtime, + name=f"{http_s3_ovms_model_mesh_serving_runtime.name}-view", + resource_names=[http_s3_ovms_model_mesh_serving_runtime.name], + ) as role: + yield role + + +@pytest.fixture(scope="class") +def http_model_mesh_role_binding( + admin_client: DynamicClient, + http_model_mesh_view_role: Role, + model_service_account: ServiceAccount, +) -> RoleBinding: + with RoleBinding( + client=admin_client, + namespace=model_service_account.namespace, + name=f"{Protocols.HTTP}-{model_service_account.name}-view", + role_ref_name=http_model_mesh_view_role.name, + role_ref_kind=http_model_mesh_view_role.kind, + subjects_kind=model_service_account.kind, + subjects_name=model_service_account.name, + ) as rb: + yield rb + + +@pytest.fixture(scope="class") +def http_model_mesh_inference_token( + model_service_account: ServiceAccount, http_model_mesh_role_binding: RoleBinding +) -> str: + return create_inference_token(model_service_account=model_service_account) diff --git a/tests/model_serving/model_server/authentication/test_model_mesh_authentication.py b/tests/model_serving/model_server/authentication/test_model_mesh_authentication.py new file mode 100644 index 000000000..b08a2b3ee --- /dev/null +++ b/tests/model_serving/model_server/authentication/test_model_mesh_authentication.py @@ -0,0 +1,123 @@ +import pytest + +from tests.model_serving.model_server.utils import verify_inference_response +from utilities.constants import ( + ModelFormat, + ModelStoragePath, + Protocols, +) +from utilities.inference_utils import Inference +from utilities.manifests.openvino import OPENVINO_INFERENCE_CONFIG +from utilities.manifests.tensorflow import TENSORFLOW_INFERENCE_CONFIG + +pytestmark = [pytest.mark.modelmesh, pytest.mark.sanity] + + +@pytest.mark.parametrize( + "model_namespace, http_s3_ovms_model_mesh_serving_runtime, http_s3_openvino_model_mesh_inference_service", + [ + pytest.param( + {"name": "model-mesh-multi-authentication", "modelmesh-enabled": True}, + {"enable-auth": True}, + {"model-path": ModelStoragePath.OPENVINO_EXAMPLE_MODEL}, + ) + ], + indirect=True, +) +class TestModelMeshAuthentication: + @pytest.mark.dependency(name="test_model_mesh_model_authentication_openvino_inference_with_tensorflow") + def test_model_mesh_model_authentication_openvino_inference_with_tensorflow( + self, http_s3_openvino_model_mesh_inference_service, http_model_mesh_inference_token + ): + """Verify model query with token using REST""" + verify_inference_response( + inference_service=http_s3_openvino_model_mesh_inference_service, + inference_config=OPENVINO_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + use_default_query=True, + token=http_model_mesh_inference_token, + ) + + @pytest.mark.dependency(name="test_model_mesh_disabled_model_authentication") + def test_model_mesh_disabled_model_authentication(self, patched_remove_authentication_model_mesh_isvc): + """Verify model query after authentication is disabled""" + verify_inference_response( + inference_service=patched_remove_authentication_model_mesh_isvc, + inference_config=OPENVINO_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + use_default_query=True, + ) + + @pytest.mark.dependency(depends=["test_model_mesh_disabled_model_authentication"]) + def test_model_mesh_re_enabled_model_authentication( + self, http_s3_openvino_model_mesh_inference_service, http_model_mesh_inference_token + ): + """Verify model query after authentication is re-enabled""" + verify_inference_response( + inference_service=http_s3_openvino_model_mesh_inference_service, + inference_config=OPENVINO_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + use_default_query=True, + token=http_model_mesh_inference_token, + ) + + @pytest.mark.dependency(depends=["test_model_mesh_model_authentication_openvino_inference_with_tensorflow"]) + def test_model_mesh_model_authentication_using_invalid_token(self, http_s3_openvino_model_mesh_inference_service): + """Verify model query with an invalid token""" + verify_inference_response( + inference_service=http_s3_openvino_model_mesh_inference_service, + inference_config=OPENVINO_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + use_default_query=True, + token="dummy", + authorized_user=False, + ) + + @pytest.mark.dependency(depends=["test_model_mesh_model_authentication_openvino_inference_with_tensorflow"]) + def test_model_mesh_model_authentication_without_token(self, http_s3_openvino_model_mesh_inference_service): + """Verify model query without providing a token""" + verify_inference_response( + inference_service=http_s3_openvino_model_mesh_inference_service, + inference_config=OPENVINO_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + use_default_query=True, + authorized_user=False, + ) + + @pytest.mark.parametrize( + "http_s3_ovms_external_route_model_mesh_serving_runtime, http_s3_openvino_second_model_mesh_inference_service", + [ + pytest.param( + {"enable-auth": True}, + { + "model-path": ModelStoragePath.TENSORFLOW_MODEL, + "model-format": ModelFormat.TENSORFLOW, + "runtime-fixture-name": "http_s3_ovms_external_route_model_mesh_serving_runtime", + "model-version": "2", + }, + ) + ], + indirect=True, + ) + def test_model_mesh_block_cross_model_authentication( + self, + http_s3_ovms_external_route_model_mesh_serving_runtime, + http_s3_openvino_model_mesh_inference_service, + http_s3_openvino_second_model_mesh_inference_service, + http_model_mesh_inference_token, + ): + """Verify model query with a second model's token is blocked""" + verify_inference_response( + inference_service=http_s3_openvino_second_model_mesh_inference_service, + inference_config=TENSORFLOW_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + use_default_query=True, + token=http_model_mesh_inference_token, + authorized_user=False, + ) diff --git a/tests/model_serving/model_server/conftest.py b/tests/model_serving/model_server/conftest.py index 51a1beb0a..2f008b71c 100644 --- a/tests/model_serving/model_server/conftest.py +++ b/tests/model_serving/model_server/conftest.py @@ -17,15 +17,18 @@ from pytest_testconfig import config as py_config from tests.model_serving.model_server.utils import create_isvc -from utilities.constants import DscComponents, StorageClassName from utilities.constants import ( + DscComponents, KServeDeploymentType, - ModelAndFormat, ModelFormat, ModelInferenceRuntime, - ModelVersion, Protocols, RuntimeTemplates, + StorageClassName, +) +from utilities.constants import ( + ModelAndFormat, + ModelVersion, ) from utilities.infra import s3_endpoint_secret from utilities.data_science_cluster_utils import update_components_in_dsc @@ -396,3 +399,53 @@ def http_s3_tensorflow_model_mesh_inference_service( model_version="2", ) as isvc: yield isvc + + +@pytest.fixture(scope="class") +def http_s3_ovms_external_route_model_mesh_serving_runtime( + request: FixtureRequest, + admin_client: DynamicClient, + model_namespace: Namespace, +) -> ServingRuntime: + with ServingRuntimeFromTemplate( + client=admin_client, + namespace=model_namespace.name, + name=f"{Protocols.HTTP}-{ModelInferenceRuntime.OPENVINO_RUNTIME}-exposed", + template_name=RuntimeTemplates.OVMS_MODEL_MESH, + multi_model=True, + protocol="REST", + resources={ + "ovms": { + "requests": {"cpu": "1", "memory": "4Gi"}, + "limits": {"cpu": "2", "memory": "8Gi"}, + }, + }, + enable_external_route=True, + enable_auth=request.param.get("enable-auth"), + ) as model_runtime: + yield model_runtime + + +@pytest.fixture(scope="class") +def http_s3_openvino_second_model_mesh_inference_service( + request: FixtureRequest, + admin_client: DynamicClient, + model_namespace: Namespace, + ci_model_mesh_endpoint_s3_secret: Secret, + model_mesh_model_service_account: ServiceAccount, +) -> InferenceService: + # Dynamically select the used ServingRuntime by passing "runtime-fixture-name" request.param + runtime = request.getfixturevalue(argname=request.param["runtime-fixture-name"]) + with create_isvc( + client=admin_client, + name=f"{Protocols.HTTP}-{ModelFormat.OPENVINO}-2", + namespace=model_namespace.name, + runtime=runtime.name, + model_service_account=model_mesh_model_service_account.name, + storage_key=ci_model_mesh_endpoint_s3_secret.name, + storage_path=request.param["model-path"], + model_format=request.param["model-format"], + deployment_mode=KServeDeploymentType.MODEL_MESH, + model_version=request.param["model-version"], + ) as isvc: + yield isvc diff --git a/tests/model_serving/model_server/model_mesh/conftest.py b/tests/model_serving/model_server/model_mesh/conftest.py deleted file mode 100644 index b5bbc92bc..000000000 --- a/tests/model_serving/model_server/model_mesh/conftest.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest -from _pytest.fixtures import FixtureRequest -from kubernetes.dynamic import DynamicClient -from ocp_resources.inference_service import InferenceService -from ocp_resources.namespace import Namespace -from ocp_resources.secret import Secret -from ocp_resources.service_account import ServiceAccount -from ocp_resources.serving_runtime import ServingRuntime - -from tests.model_serving.model_server.utils import create_isvc -from utilities.constants import ( - KServeDeploymentType, - ModelFormat, - ModelInferenceRuntime, - Protocols, - RuntimeTemplates, -) -from utilities.serving_runtime import ServingRuntimeFromTemplate - - -@pytest.fixture(scope="class") -def http_s3_openvino_second_model_mesh_inference_service( - request: FixtureRequest, - admin_client: DynamicClient, - model_namespace: Namespace, - ci_model_mesh_endpoint_s3_secret: Secret, - model_mesh_model_service_account: ServiceAccount, -) -> InferenceService: - # Dynamically select the used ServingRuntime by passing "runtime-fixture-name" request.param - runtime = request.getfixturevalue(argname=request.param["runtime-fixture-name"]) - with create_isvc( - client=admin_client, - name=f"{Protocols.HTTP}-{ModelFormat.OPENVINO}-2", - namespace=model_namespace.name, - runtime=runtime.name, - model_service_account=model_mesh_model_service_account.name, - storage_key=ci_model_mesh_endpoint_s3_secret.name, - storage_path=request.param["model-path"], - model_format=request.param["model-format"], - deployment_mode=KServeDeploymentType.MODEL_MESH, - model_version=request.param["model-version"], - ) as isvc: - yield isvc - - -@pytest.fixture(scope="class") -def http_s3_ovms_external_route_model_mesh_serving_runtime( - request: FixtureRequest, - admin_client: DynamicClient, - model_namespace: Namespace, -) -> ServingRuntime: - with ServingRuntimeFromTemplate( - client=admin_client, - namespace=model_namespace.name, - name=f"{Protocols.HTTP}-{ModelInferenceRuntime.OPENVINO_RUNTIME}-exposed", - template_name=RuntimeTemplates.OVMS_MODEL_MESH, - multi_model=True, - protocol="REST", - resources={ - "ovms": { - "requests": {"cpu": "1", "memory": "4Gi"}, - "limits": {"cpu": "2", "memory": "8Gi"}, - }, - }, - enable_external_route=True, - ) as model_runtime: - yield model_runtime diff --git a/tests/model_serving/model_server/ovms/model_mesh/conftest.py b/tests/model_serving/model_server/ovms/model_mesh/conftest.py index 095c05dbb..77fbe6fa0 100644 --- a/tests/model_serving/model_server/ovms/model_mesh/conftest.py +++ b/tests/model_serving/model_server/ovms/model_mesh/conftest.py @@ -1,5 +1,3 @@ -import shlex - import pytest from kubernetes.dynamic import DynamicClient from ocp_resources.resource import ResourceEditor @@ -7,12 +5,11 @@ from ocp_resources.role_binding import RoleBinding from ocp_resources.service_account import ServiceAccount from ocp_resources.serving_runtime import ServingRuntime -from pyhelper_utils.shell import run_command from utilities.constants import ( Protocols, ) -from utilities.infra import create_isvc_view_role +from utilities.infra import create_inference_token, create_resource_view_role @pytest.fixture(scope="class") @@ -20,9 +17,9 @@ def model_mesh_view_role( admin_client: DynamicClient, http_s3_openvino_model_mesh_inference_service: ServingRuntime, ) -> Role: - with create_isvc_view_role( + with create_resource_view_role( client=admin_client, - isvc=http_s3_openvino_model_mesh_inference_service, + resource=http_s3_openvino_model_mesh_inference_service, name=f"{http_s3_openvino_model_mesh_inference_service.name}-view", resource_names=[http_s3_openvino_model_mesh_inference_service.name], ) as role: @@ -52,11 +49,7 @@ def model_mesh_inference_token( model_mesh_model_service_account: ServiceAccount, model_mesh_role_binding: RoleBinding, ) -> str: - return run_command( - command=shlex.split( - f"oc create token -n {model_mesh_model_service_account.namespace} {model_mesh_model_service_account.name}" - ) - )[1].strip() + return create_inference_token(model_service_account=model_mesh_model_service_account) @pytest.fixture() diff --git a/utilities/infra.py b/utilities/infra.py index d1ac5f625..9c9bc41b0 100644 --- a/utilities/infra.py +++ b/utilities/infra.py @@ -134,16 +134,17 @@ def s3_endpoint_secret( @contextmanager -def create_isvc_view_role( +def create_resource_view_role( client: DynamicClient, - isvc: InferenceService, + resource: InferenceService | ServingRuntime, name: str, resource_names: Optional[List[str]] = None, ) -> Role: + resources_type = "inferenceservices" if isinstance(resource, InferenceService) else "servingruntimes" rules = [ { - "apiGroups": [isvc.api_group], - "resources": ["inferenceservices"], + "apiGroups": [resource.api_group], + "resources": [resources_type], "verbs": ["get"], }, ] @@ -154,7 +155,7 @@ def create_isvc_view_role( with Role( client=client, name=name, - namespace=isvc.namespace, + namespace=resource.namespace, rules=rules, ) as role: yield role From 44a31208d3d1c9bb64aefd29ff35d6910344a36a Mon Sep 17 00:00:00 2001 From: rnetser Date: Sun, 2 Feb 2025 19:43:30 +0200 Subject: [PATCH 4/7] xx --- .../model_server/authentication/conftest.py | 78 ++--------- .../test_model_mesh_authentication.py | 123 ------------------ tests/model_serving/model_server/conftest.py | 59 +-------- .../model_server/model_mesh/conftest.py | 67 ++++++++++ .../model_server/ovms/model_mesh/conftest.py | 15 ++- utilities/infra.py | 11 +- 6 files changed, 100 insertions(+), 253 deletions(-) delete mode 100644 tests/model_serving/model_server/authentication/test_model_mesh_authentication.py create mode 100644 tests/model_serving/model_server/model_mesh/conftest.py diff --git a/tests/model_serving/model_server/authentication/conftest.py b/tests/model_serving/model_server/authentication/conftest.py index f0f8d28fa..0e3aa5d97 100644 --- a/tests/model_serving/model_server/authentication/conftest.py +++ b/tests/model_serving/model_server/authentication/conftest.py @@ -1,3 +1,4 @@ +import shlex from typing import Any, Generator from urllib.parse import urlparse @@ -12,8 +13,9 @@ from ocp_resources.secret import Secret from ocp_resources.service_account import ServiceAccount from ocp_resources.serving_runtime import ServingRuntime +from pyhelper_utils.shell import run_command -from utilities.infra import create_resource_view_role, create_ns, s3_endpoint_secret, create_inference_token +from utilities.infra import create_isvc_view_role, create_ns, s3_endpoint_secret, create_inference_token from tests.model_serving.model_server.utils import create_isvc from utilities.constants import ( KServeDeploymentType, @@ -83,9 +85,9 @@ def http_view_role( admin_client: DynamicClient, http_s3_caikit_serverless_inference_service: InferenceService, ) -> Role: - with create_resource_view_role( + with create_isvc_view_role( client=admin_client, - resource=http_s3_caikit_serverless_inference_service, + isvc=http_s3_caikit_serverless_inference_service, name=f"{http_s3_caikit_serverless_inference_service.name}-view", resource_names=[http_s3_caikit_serverless_inference_service.name], ) as role: @@ -97,9 +99,9 @@ def http_raw_view_role( admin_client: DynamicClient, http_s3_caikit_raw_inference_service: InferenceService, ) -> Role: - with create_resource_view_role( + with create_isvc_view_role( client=admin_client, - resource=http_s3_caikit_raw_inference_service, + isvc=http_s3_caikit_raw_inference_service, name=f"{http_s3_caikit_raw_inference_service.name}-view", resource_names=[http_s3_caikit_raw_inference_service.name], ) as role: @@ -190,9 +192,9 @@ def patched_remove_raw_authentication_isvc( @pytest.fixture(scope="class") def grpc_view_role(admin_client: DynamicClient, grpc_s3_inference_service: InferenceService) -> Role: - with create_resource_view_role( + with create_isvc_view_role( client=admin_client, - resource=grpc_s3_inference_service, + isvc=grpc_s3_inference_service, name=f"{grpc_s3_inference_service.name}-view", resource_names=[grpc_s3_inference_service.name], ) as role: @@ -220,7 +222,11 @@ def grpc_role_binding( @pytest.fixture(scope="class") def grpc_inference_token(grpc_model_service_account: ServiceAccount, grpc_role_binding: RoleBinding) -> str: - return create_inference_token(model_service_account=grpc_model_service_account) + return run_command( + command=shlex.split( + f"oc create token -n {grpc_model_service_account.namespace} {grpc_model_service_account.name}" + ) + )[1].strip() @pytest.fixture(scope="class") @@ -358,59 +364,3 @@ def http_s3_caikit_tgis_serving_runtime( enable_grpc=False, ) as model_runtime: yield model_runtime - - -@pytest.fixture() -def patched_remove_authentication_model_mesh_isvc( - admin_client: DynamicClient, - http_s3_openvino_model_mesh_inference_service: InferenceService, -) -> InferenceService: - with ResourceEditor( - patches={ - http_s3_openvino_model_mesh_inference_service: { - "metadata": { - "annotations": {Annotations.KserveAuth.SECURITY: "false"}, - } - } - } - ): - yield http_s3_openvino_model_mesh_inference_service - - -@pytest.fixture(scope="class") -def http_model_mesh_view_role( - admin_client: DynamicClient, - http_s3_ovms_model_mesh_serving_runtime: ServingRuntime, -) -> Role: - with create_resource_view_role( - client=admin_client, - resource=http_s3_ovms_model_mesh_serving_runtime, - name=f"{http_s3_ovms_model_mesh_serving_runtime.name}-view", - resource_names=[http_s3_ovms_model_mesh_serving_runtime.name], - ) as role: - yield role - - -@pytest.fixture(scope="class") -def http_model_mesh_role_binding( - admin_client: DynamicClient, - http_model_mesh_view_role: Role, - model_service_account: ServiceAccount, -) -> RoleBinding: - with RoleBinding( - client=admin_client, - namespace=model_service_account.namespace, - name=f"{Protocols.HTTP}-{model_service_account.name}-view", - role_ref_name=http_model_mesh_view_role.name, - role_ref_kind=http_model_mesh_view_role.kind, - subjects_kind=model_service_account.kind, - subjects_name=model_service_account.name, - ) as rb: - yield rb - - -@pytest.fixture(scope="class") -def http_model_mesh_inference_token( - model_service_account: ServiceAccount, http_model_mesh_role_binding: RoleBinding -) -> str: - return create_inference_token(model_service_account=model_service_account) diff --git a/tests/model_serving/model_server/authentication/test_model_mesh_authentication.py b/tests/model_serving/model_server/authentication/test_model_mesh_authentication.py deleted file mode 100644 index b08a2b3ee..000000000 --- a/tests/model_serving/model_server/authentication/test_model_mesh_authentication.py +++ /dev/null @@ -1,123 +0,0 @@ -import pytest - -from tests.model_serving.model_server.utils import verify_inference_response -from utilities.constants import ( - ModelFormat, - ModelStoragePath, - Protocols, -) -from utilities.inference_utils import Inference -from utilities.manifests.openvino import OPENVINO_INFERENCE_CONFIG -from utilities.manifests.tensorflow import TENSORFLOW_INFERENCE_CONFIG - -pytestmark = [pytest.mark.modelmesh, pytest.mark.sanity] - - -@pytest.mark.parametrize( - "model_namespace, http_s3_ovms_model_mesh_serving_runtime, http_s3_openvino_model_mesh_inference_service", - [ - pytest.param( - {"name": "model-mesh-multi-authentication", "modelmesh-enabled": True}, - {"enable-auth": True}, - {"model-path": ModelStoragePath.OPENVINO_EXAMPLE_MODEL}, - ) - ], - indirect=True, -) -class TestModelMeshAuthentication: - @pytest.mark.dependency(name="test_model_mesh_model_authentication_openvino_inference_with_tensorflow") - def test_model_mesh_model_authentication_openvino_inference_with_tensorflow( - self, http_s3_openvino_model_mesh_inference_service, http_model_mesh_inference_token - ): - """Verify model query with token using REST""" - verify_inference_response( - inference_service=http_s3_openvino_model_mesh_inference_service, - inference_config=OPENVINO_INFERENCE_CONFIG, - inference_type=Inference.INFER, - protocol=Protocols.HTTPS, - use_default_query=True, - token=http_model_mesh_inference_token, - ) - - @pytest.mark.dependency(name="test_model_mesh_disabled_model_authentication") - def test_model_mesh_disabled_model_authentication(self, patched_remove_authentication_model_mesh_isvc): - """Verify model query after authentication is disabled""" - verify_inference_response( - inference_service=patched_remove_authentication_model_mesh_isvc, - inference_config=OPENVINO_INFERENCE_CONFIG, - inference_type=Inference.INFER, - protocol=Protocols.HTTPS, - use_default_query=True, - ) - - @pytest.mark.dependency(depends=["test_model_mesh_disabled_model_authentication"]) - def test_model_mesh_re_enabled_model_authentication( - self, http_s3_openvino_model_mesh_inference_service, http_model_mesh_inference_token - ): - """Verify model query after authentication is re-enabled""" - verify_inference_response( - inference_service=http_s3_openvino_model_mesh_inference_service, - inference_config=OPENVINO_INFERENCE_CONFIG, - inference_type=Inference.INFER, - protocol=Protocols.HTTPS, - use_default_query=True, - token=http_model_mesh_inference_token, - ) - - @pytest.mark.dependency(depends=["test_model_mesh_model_authentication_openvino_inference_with_tensorflow"]) - def test_model_mesh_model_authentication_using_invalid_token(self, http_s3_openvino_model_mesh_inference_service): - """Verify model query with an invalid token""" - verify_inference_response( - inference_service=http_s3_openvino_model_mesh_inference_service, - inference_config=OPENVINO_INFERENCE_CONFIG, - inference_type=Inference.INFER, - protocol=Protocols.HTTPS, - use_default_query=True, - token="dummy", - authorized_user=False, - ) - - @pytest.mark.dependency(depends=["test_model_mesh_model_authentication_openvino_inference_with_tensorflow"]) - def test_model_mesh_model_authentication_without_token(self, http_s3_openvino_model_mesh_inference_service): - """Verify model query without providing a token""" - verify_inference_response( - inference_service=http_s3_openvino_model_mesh_inference_service, - inference_config=OPENVINO_INFERENCE_CONFIG, - inference_type=Inference.INFER, - protocol=Protocols.HTTPS, - use_default_query=True, - authorized_user=False, - ) - - @pytest.mark.parametrize( - "http_s3_ovms_external_route_model_mesh_serving_runtime, http_s3_openvino_second_model_mesh_inference_service", - [ - pytest.param( - {"enable-auth": True}, - { - "model-path": ModelStoragePath.TENSORFLOW_MODEL, - "model-format": ModelFormat.TENSORFLOW, - "runtime-fixture-name": "http_s3_ovms_external_route_model_mesh_serving_runtime", - "model-version": "2", - }, - ) - ], - indirect=True, - ) - def test_model_mesh_block_cross_model_authentication( - self, - http_s3_ovms_external_route_model_mesh_serving_runtime, - http_s3_openvino_model_mesh_inference_service, - http_s3_openvino_second_model_mesh_inference_service, - http_model_mesh_inference_token, - ): - """Verify model query with a second model's token is blocked""" - verify_inference_response( - inference_service=http_s3_openvino_second_model_mesh_inference_service, - inference_config=TENSORFLOW_INFERENCE_CONFIG, - inference_type=Inference.INFER, - protocol=Protocols.HTTPS, - use_default_query=True, - token=http_model_mesh_inference_token, - authorized_user=False, - ) diff --git a/tests/model_serving/model_server/conftest.py b/tests/model_serving/model_server/conftest.py index 2f008b71c..51a1beb0a 100644 --- a/tests/model_serving/model_server/conftest.py +++ b/tests/model_serving/model_server/conftest.py @@ -17,18 +17,15 @@ from pytest_testconfig import config as py_config from tests.model_serving.model_server.utils import create_isvc +from utilities.constants import DscComponents, StorageClassName from utilities.constants import ( - DscComponents, KServeDeploymentType, + ModelAndFormat, ModelFormat, ModelInferenceRuntime, + ModelVersion, Protocols, RuntimeTemplates, - StorageClassName, -) -from utilities.constants import ( - ModelAndFormat, - ModelVersion, ) from utilities.infra import s3_endpoint_secret from utilities.data_science_cluster_utils import update_components_in_dsc @@ -399,53 +396,3 @@ def http_s3_tensorflow_model_mesh_inference_service( model_version="2", ) as isvc: yield isvc - - -@pytest.fixture(scope="class") -def http_s3_ovms_external_route_model_mesh_serving_runtime( - request: FixtureRequest, - admin_client: DynamicClient, - model_namespace: Namespace, -) -> ServingRuntime: - with ServingRuntimeFromTemplate( - client=admin_client, - namespace=model_namespace.name, - name=f"{Protocols.HTTP}-{ModelInferenceRuntime.OPENVINO_RUNTIME}-exposed", - template_name=RuntimeTemplates.OVMS_MODEL_MESH, - multi_model=True, - protocol="REST", - resources={ - "ovms": { - "requests": {"cpu": "1", "memory": "4Gi"}, - "limits": {"cpu": "2", "memory": "8Gi"}, - }, - }, - enable_external_route=True, - enable_auth=request.param.get("enable-auth"), - ) as model_runtime: - yield model_runtime - - -@pytest.fixture(scope="class") -def http_s3_openvino_second_model_mesh_inference_service( - request: FixtureRequest, - admin_client: DynamicClient, - model_namespace: Namespace, - ci_model_mesh_endpoint_s3_secret: Secret, - model_mesh_model_service_account: ServiceAccount, -) -> InferenceService: - # Dynamically select the used ServingRuntime by passing "runtime-fixture-name" request.param - runtime = request.getfixturevalue(argname=request.param["runtime-fixture-name"]) - with create_isvc( - client=admin_client, - name=f"{Protocols.HTTP}-{ModelFormat.OPENVINO}-2", - namespace=model_namespace.name, - runtime=runtime.name, - model_service_account=model_mesh_model_service_account.name, - storage_key=ci_model_mesh_endpoint_s3_secret.name, - storage_path=request.param["model-path"], - model_format=request.param["model-format"], - deployment_mode=KServeDeploymentType.MODEL_MESH, - model_version=request.param["model-version"], - ) as isvc: - yield isvc diff --git a/tests/model_serving/model_server/model_mesh/conftest.py b/tests/model_serving/model_server/model_mesh/conftest.py new file mode 100644 index 000000000..b5bbc92bc --- /dev/null +++ b/tests/model_serving/model_server/model_mesh/conftest.py @@ -0,0 +1,67 @@ +import pytest +from _pytest.fixtures import FixtureRequest +from kubernetes.dynamic import DynamicClient +from ocp_resources.inference_service import InferenceService +from ocp_resources.namespace import Namespace +from ocp_resources.secret import Secret +from ocp_resources.service_account import ServiceAccount +from ocp_resources.serving_runtime import ServingRuntime + +from tests.model_serving.model_server.utils import create_isvc +from utilities.constants import ( + KServeDeploymentType, + ModelFormat, + ModelInferenceRuntime, + Protocols, + RuntimeTemplates, +) +from utilities.serving_runtime import ServingRuntimeFromTemplate + + +@pytest.fixture(scope="class") +def http_s3_openvino_second_model_mesh_inference_service( + request: FixtureRequest, + admin_client: DynamicClient, + model_namespace: Namespace, + ci_model_mesh_endpoint_s3_secret: Secret, + model_mesh_model_service_account: ServiceAccount, +) -> InferenceService: + # Dynamically select the used ServingRuntime by passing "runtime-fixture-name" request.param + runtime = request.getfixturevalue(argname=request.param["runtime-fixture-name"]) + with create_isvc( + client=admin_client, + name=f"{Protocols.HTTP}-{ModelFormat.OPENVINO}-2", + namespace=model_namespace.name, + runtime=runtime.name, + model_service_account=model_mesh_model_service_account.name, + storage_key=ci_model_mesh_endpoint_s3_secret.name, + storage_path=request.param["model-path"], + model_format=request.param["model-format"], + deployment_mode=KServeDeploymentType.MODEL_MESH, + model_version=request.param["model-version"], + ) as isvc: + yield isvc + + +@pytest.fixture(scope="class") +def http_s3_ovms_external_route_model_mesh_serving_runtime( + request: FixtureRequest, + admin_client: DynamicClient, + model_namespace: Namespace, +) -> ServingRuntime: + with ServingRuntimeFromTemplate( + client=admin_client, + namespace=model_namespace.name, + name=f"{Protocols.HTTP}-{ModelInferenceRuntime.OPENVINO_RUNTIME}-exposed", + template_name=RuntimeTemplates.OVMS_MODEL_MESH, + multi_model=True, + protocol="REST", + resources={ + "ovms": { + "requests": {"cpu": "1", "memory": "4Gi"}, + "limits": {"cpu": "2", "memory": "8Gi"}, + }, + }, + enable_external_route=True, + ) as model_runtime: + yield model_runtime diff --git a/tests/model_serving/model_server/ovms/model_mesh/conftest.py b/tests/model_serving/model_server/ovms/model_mesh/conftest.py index 77fbe6fa0..095c05dbb 100644 --- a/tests/model_serving/model_server/ovms/model_mesh/conftest.py +++ b/tests/model_serving/model_server/ovms/model_mesh/conftest.py @@ -1,3 +1,5 @@ +import shlex + import pytest from kubernetes.dynamic import DynamicClient from ocp_resources.resource import ResourceEditor @@ -5,11 +7,12 @@ from ocp_resources.role_binding import RoleBinding from ocp_resources.service_account import ServiceAccount from ocp_resources.serving_runtime import ServingRuntime +from pyhelper_utils.shell import run_command from utilities.constants import ( Protocols, ) -from utilities.infra import create_inference_token, create_resource_view_role +from utilities.infra import create_isvc_view_role @pytest.fixture(scope="class") @@ -17,9 +20,9 @@ def model_mesh_view_role( admin_client: DynamicClient, http_s3_openvino_model_mesh_inference_service: ServingRuntime, ) -> Role: - with create_resource_view_role( + with create_isvc_view_role( client=admin_client, - resource=http_s3_openvino_model_mesh_inference_service, + isvc=http_s3_openvino_model_mesh_inference_service, name=f"{http_s3_openvino_model_mesh_inference_service.name}-view", resource_names=[http_s3_openvino_model_mesh_inference_service.name], ) as role: @@ -49,7 +52,11 @@ def model_mesh_inference_token( model_mesh_model_service_account: ServiceAccount, model_mesh_role_binding: RoleBinding, ) -> str: - return create_inference_token(model_service_account=model_mesh_model_service_account) + return run_command( + command=shlex.split( + f"oc create token -n {model_mesh_model_service_account.namespace} {model_mesh_model_service_account.name}" + ) + )[1].strip() @pytest.fixture() diff --git a/utilities/infra.py b/utilities/infra.py index 9c9bc41b0..d1ac5f625 100644 --- a/utilities/infra.py +++ b/utilities/infra.py @@ -134,17 +134,16 @@ def s3_endpoint_secret( @contextmanager -def create_resource_view_role( +def create_isvc_view_role( client: DynamicClient, - resource: InferenceService | ServingRuntime, + isvc: InferenceService, name: str, resource_names: Optional[List[str]] = None, ) -> Role: - resources_type = "inferenceservices" if isinstance(resource, InferenceService) else "servingruntimes" rules = [ { - "apiGroups": [resource.api_group], - "resources": [resources_type], + "apiGroups": [isvc.api_group], + "resources": ["inferenceservices"], "verbs": ["get"], }, ] @@ -155,7 +154,7 @@ def create_resource_view_role( with Role( client=client, name=name, - namespace=resource.namespace, + namespace=isvc.namespace, rules=rules, ) as role: yield role From 2a7c6512ee101c3162c6b9fa07330cca073f9e7b Mon Sep 17 00:00:00 2001 From: rnetser Date: Mon, 14 Apr 2025 20:32:05 +0300 Subject: [PATCH 5/7] feat: add auth test to upgrade --- tests/model_serving/model_server/conftest.py | 6 +- .../model_server/upgrade/conftest.py | 400 ++++++++++++++---- .../model_server/upgrade/test_upgrade.py | 43 +- utilities/inference_utils.py | 3 + utilities/infra.py | 14 +- utilities/serving_runtime.py | 3 + 6 files changed, 372 insertions(+), 97 deletions(-) diff --git a/tests/model_serving/model_server/conftest.py b/tests/model_serving/model_server/conftest.py index f42e29c04..ef6092fef 100644 --- a/tests/model_serving/model_server/conftest.py +++ b/tests/model_serving/model_server/conftest.py @@ -70,7 +70,7 @@ def models_endpoint_s3_secret( models_s3_bucket_endpoint: str, ) -> Generator[Secret, Any, Any]: with s3_endpoint_secret( - admin_client=admin_client, + client=admin_client, name="models-bucket-secret", namespace=model_namespace.name, aws_access_key=aws_access_key_id, @@ -319,7 +319,7 @@ def ci_endpoint_s3_secret( ci_s3_bucket_endpoint: str, ) -> Generator[Secret, Any, Any]: with s3_endpoint_secret( - admin_client=admin_client, + client=admin_client, name="ci-bucket-secret", namespace=model_namespace.name, aws_access_key=aws_access_key_id, @@ -575,7 +575,7 @@ def unprivileged_models_endpoint_s3_secret( models_s3_bucket_endpoint: str, ) -> Generator[Secret, Any, Any]: with s3_endpoint_secret( - admin_client=unprivileged_client, + client=unprivileged_client, name="models-bucket-secret", namespace=unprivileged_model_namespace.name, aws_access_key=aws_access_key_id, diff --git a/tests/model_serving/model_server/upgrade/conftest.py b/tests/model_serving/model_server/upgrade/conftest.py index f111c352c..815f55d0e 100644 --- a/tests/model_serving/model_server/upgrade/conftest.py +++ b/tests/model_serving/model_server/upgrade/conftest.py @@ -1,10 +1,11 @@ -import os from typing import Any, Generator import pytest from kubernetes.dynamic import DynamicClient from ocp_resources.inference_service import InferenceService from ocp_resources.namespace import Namespace +from ocp_resources.role import Role +from ocp_resources.role_binding import RoleBinding from ocp_resources.secret import Secret from ocp_resources.service_account import ServiceAccount from ocp_resources.serving_runtime import ServingRuntime @@ -20,49 +21,48 @@ RuntimeTemplates, ) from utilities.inference_utils import create_isvc -from utilities.infra import create_ns, s3_endpoint_secret +from utilities.infra import create_inference_token, create_isvc_view_role, create_ns, s3_endpoint_secret from utilities.serving_runtime import ServingRuntimeFromTemplate LOGGER = get_logger(name=__name__) -UPGRADE_NAMESPACE: str = "upgrade-model-server" - -UPGRADE_RESOURCES: str = ( - f"{{Namespace: {{{UPGRADE_NAMESPACE:}}}," - f"ServingRuntime: {{onnx-serverless: {UPGRADE_NAMESPACE}," - f"caikit-raw: {UPGRADE_NAMESPACE},ovms-model-mesh: {UPGRADE_NAMESPACE}}}," - f"InferenceService: {{onnx-serverless: {UPGRADE_NAMESPACE}," - f"caikit-raw: {UPGRADE_NAMESPACE}, ovms-model-mesh: {UPGRADE_NAMESPACE}}}," - f"Secret: {{ci-bucket-secret: {UPGRADE_NAMESPACE}, models-bucket-secret: {UPGRADE_NAMESPACE}}}," - f"ServiceAccount: {{models-bucket-sa: {UPGRADE_NAMESPACE}}}}}" -) @pytest.fixture(scope="session") -def skipped_teardown_resources(pytestconfig: pytest.Config) -> None: - if not pytestconfig.option.delete_pre_upgrade_resources: - LOGGER.info(f"Setting `SKIP_RESOURCE_TEARDOWN` environment variable to {UPGRADE_RESOURCES}") - os.environ["SKIP_RESOURCE_TEARDOWN"] = UPGRADE_RESOURCES - +def teardown_resources(pytestconfig: pytest.Config) -> bool: + if delete_pre_upgrade_resources := pytestconfig.option.delete_pre_upgrade_resources: + LOGGER.warning("Resources will be deleted") -@pytest.fixture(scope="session") -def reused_resources() -> None: - LOGGER.info(f"Setting `REUSE_IF_RESOURCE_EXISTS` environment variable to {UPGRADE_RESOURCES}") - os.environ["REUSE_IF_RESOURCE_EXISTS"] = UPGRADE_RESOURCES + return delete_pre_upgrade_resources @pytest.fixture(scope="session") def model_namespace_scope_session( + pytestconfig: pytest.Config, admin_client: DynamicClient, + teardown_resources: bool, ) -> Generator[Namespace, Any, Any]: - with create_ns( - admin_client=admin_client, name=UPGRADE_NAMESPACE, model_mesh_enabled=True, add_dashboard_label=True - ) as ns: + name = "upgrade-model-server" + ns = Namespace(client=admin_client, name=name) + + if pytestconfig.option.post_upgrade: yield ns + ns.clean_up() + + else: + with create_ns( + admin_client=admin_client, + name=name, + model_mesh_enabled=True, + add_dashboard_label=True, + teardown=teardown_resources, + ) as ns: + yield ns @pytest.fixture(scope="session") def models_endpoint_s3_secret_scope_session( + pytestconfig: pytest.Config, admin_client: DynamicClient, model_namespace_scope_session: Namespace, aws_access_key_id: str, @@ -70,22 +70,36 @@ def models_endpoint_s3_secret_scope_session( models_s3_bucket_name: str, models_s3_bucket_region: str, models_s3_bucket_endpoint: str, + teardown_resources: bool, ) -> Generator[Secret, Any, Any]: - with s3_endpoint_secret( - admin_client=admin_client, - name="models-bucket-secret", - namespace=model_namespace_scope_session.name, - aws_access_key=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_s3_region=models_s3_bucket_region, - aws_s3_bucket=models_s3_bucket_name, - aws_s3_endpoint=models_s3_bucket_endpoint, - ) as secret: + secret_kwargs = { + "client": admin_client, + "name": "models-bucket-secret", + "namespace": model_namespace_scope_session.name, + } + + secret = Secret(**secret_kwargs) + + if pytestconfig.option.post_upgrade: yield secret + secret.clean_up() + + else: + with s3_endpoint_secret( + **secret_kwargs, + aws_access_key=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_s3_region=models_s3_bucket_region, + aws_s3_bucket=models_s3_bucket_name, + aws_s3_endpoint=models_s3_bucket_endpoint, + teardown=teardown_resources, + ) as secret: + yield secret @pytest.fixture(scope="session") def ci_endpoint_s3_secret_scope_session( + pytestconfig: pytest.Config, admin_client: DynamicClient, model_namespace_scope_session: Namespace, aws_access_key_id: str, @@ -93,53 +107,95 @@ def ci_endpoint_s3_secret_scope_session( ci_s3_bucket_name: str, ci_s3_bucket_region: str, ci_s3_bucket_endpoint: str, + teardown_resources: bool, ) -> Generator[Secret, Any, Any]: - with s3_endpoint_secret( - admin_client=admin_client, - name="ci-bucket-secret", - namespace=model_namespace_scope_session.name, - aws_access_key=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - aws_s3_region=ci_s3_bucket_region, - aws_s3_bucket=ci_s3_bucket_name, - aws_s3_endpoint=ci_s3_bucket_endpoint, - ) as secret: + secret_kwargs = { + "client": admin_client, + "name": "ci-bucket-secret", + "namespace": model_namespace_scope_session.name, + } + + secret = Secret(**secret_kwargs) + + if pytestconfig.option.post_upgrade: yield secret + secret.clean_up() + + else: + with s3_endpoint_secret( + **secret_kwargs, + aws_access_key=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_s3_region=ci_s3_bucket_region, + aws_s3_bucket=ci_s3_bucket_name, + aws_s3_endpoint=ci_s3_bucket_endpoint, + teardown=teardown_resources, + ) as secret: + yield secret @pytest.fixture(scope="session") def model_mesh_model_service_account_scope_session( - admin_client: DynamicClient, ci_endpoint_s3_secret_scope_session: Secret + pytestconfig: pytest.Config, + admin_client: DynamicClient, + ci_endpoint_s3_secret_scope_session: Secret, + teardown_resources: bool, ) -> Generator[ServiceAccount, Any, Any]: - with ServiceAccount( - client=admin_client, - namespace=ci_endpoint_s3_secret_scope_session.namespace, - name="models-bucket-sa", - secrets=[{"name": ci_endpoint_s3_secret_scope_session.name}], - ) as sa: + sa_kwargs = { + "client": admin_client, + "name": "models-bucket-sa", + "namespace": ci_endpoint_s3_secret_scope_session.namespace, + } + + sa = ServiceAccount(**sa_kwargs) + + if pytestconfig.option.post_upgrade: yield sa + sa.clean_up() + + else: + with ServiceAccount( + **sa_kwargs, + secrets=[{"name": ci_endpoint_s3_secret_scope_session.name}], + teardown=teardown_resources, + ) as sa: + yield sa @pytest.fixture(scope="session") def openvino_serverless_serving_runtime_scope_session( + pytestconfig: pytest.Config, admin_client: DynamicClient, model_namespace_scope_session: Namespace, + teardown_resources: bool, ) -> Generator[ServingRuntime, Any, Any]: - with ServingRuntimeFromTemplate( - client=admin_client, - name="onnx-serverless", - namespace=model_namespace_scope_session.name, - template_name=RuntimeTemplates.OVMS_KSERVE, - multi_model=False, - resources={ - ModelFormat.OVMS: { - "requests": {"cpu": "1", "memory": "4Gi"}, - "limits": {"cpu": "2", "memory": "8Gi"}, - } - }, - model_format_name={ModelFormat.ONNX: ModelVersion.OPSET13}, - ) as model_runtime: + runtime_kwargs = { + "client": admin_client, + "name": "onnx-serverless", + "namespace": model_namespace_scope_session.name, + } + + model_runtime = ServingRuntime(**runtime_kwargs) + + if pytestconfig.option.post_upgrade: yield model_runtime + model_runtime.clean_up() + + else: + with ServingRuntimeFromTemplate( + **runtime_kwargs, + template_name=RuntimeTemplates.OVMS_KSERVE, + multi_model=False, + resources={ + ModelFormat.OVMS: { + "requests": {"cpu": "1", "memory": "4Gi"}, + "limits": {"cpu": "2", "memory": "8Gi"}, + } + }, + model_format_name={ModelFormat.ONNX: ModelVersion.OPSET13}, + teardown=teardown_resources, + ) as model_runtime: + yield model_runtime @pytest.fixture(scope="session") @@ -148,6 +204,7 @@ def ovms_serverless_inference_service_scope_session( admin_client: DynamicClient, openvino_serverless_serving_runtime_scope_session: ServingRuntime, ci_endpoint_s3_secret_scope_session: Secret, + teardown_resources: bool, ) -> Generator[InferenceService, Any, Any]: isvc_kwargs = { "client": admin_client, @@ -169,6 +226,7 @@ def ovms_serverless_inference_service_scope_session( model_format=ModelAndFormat.OPENVINO_IR, deployment_mode=KServeDeploymentType.SERVERLESS, model_version=ModelVersion.OPSET13, + teardown=teardown_resources, **isvc_kwargs, ) as isvc: yield isvc @@ -176,18 +234,32 @@ def ovms_serverless_inference_service_scope_session( @pytest.fixture(scope="session") def caikit_raw_serving_runtime_scope_session( + pytestconfig: pytest.Config, admin_client: DynamicClient, model_namespace_scope_session: Namespace, + teardown_resources: bool, ) -> Generator[ServingRuntime, Any, Any]: - with ServingRuntimeFromTemplate( - client=admin_client, - name="caikit-raw", - namespace=model_namespace_scope_session.name, - template_name=RuntimeTemplates.CAIKIT_STANDALONE_SERVING, - multi_model=False, - enable_http=True, - ) as model_runtime: + runtime_kwargs = { + "client": admin_client, + "name": "caikit-raw", + "namespace": model_namespace_scope_session.name, + } + + model_runtime = ServingRuntime(**runtime_kwargs) + + if pytestconfig.option.post_upgrade: yield model_runtime + model_runtime.clean_up() + + else: + with ServingRuntimeFromTemplate( + **runtime_kwargs, + template_name=RuntimeTemplates.CAIKIT_STANDALONE_SERVING, + multi_model=False, + enable_http=True, + teardown=teardown_resources, + ) as model_runtime: + yield model_runtime @pytest.fixture(scope="session") @@ -196,6 +268,7 @@ def caikit_raw_inference_service_scope_session( admin_client: DynamicClient, caikit_raw_serving_runtime_scope_session: ServingRuntime, models_endpoint_s3_secret_scope_session: Secret, + teardown_resources: bool, ) -> Generator[InferenceService, Any, Any]: isvc_kwargs = { "client": admin_client, @@ -218,6 +291,7 @@ def caikit_raw_inference_service_scope_session( storage_key=models_endpoint_s3_secret_scope_session.name, storage_path=ModelStoragePath.EMBEDDING_MODEL, external_route=True, + teardown=teardown_resources, **isvc_kwargs, ) as isvc: yield isvc @@ -225,24 +299,38 @@ def caikit_raw_inference_service_scope_session( @pytest.fixture(scope="session") def s3_ovms_model_mesh_serving_runtime_scope_session( + pytestconfig: pytest.Config, admin_client: DynamicClient, model_namespace_scope_session: Namespace, + teardown_resources: bool, ) -> Generator[ServingRuntime, Any, Any]: - with ServingRuntimeFromTemplate( - client=admin_client, - name="ovms-model-mesh", - namespace=model_namespace_scope_session.name, - template_name=RuntimeTemplates.OVMS_MODEL_MESH, - multi_model=True, - protocol=Protocols.REST.upper(), - resources={ - ModelFormat.OVMS: { - "requests": {"cpu": "1", "memory": "4Gi"}, - "limits": {"cpu": "2", "memory": "8Gi"}, - } - }, - ) as model_runtime: + runtime_kwargs = { + "client": admin_client, + "name": "ovms-model-mesh", + "namespace": model_namespace_scope_session.name, + } + + model_runtime = ServingRuntime(**runtime_kwargs) + + if pytestconfig.option.post_upgrade: yield model_runtime + model_runtime.clean_up() + + else: + with ServingRuntimeFromTemplate( + **runtime_kwargs, + template_name=RuntimeTemplates.OVMS_MODEL_MESH, + multi_model=True, + protocol=Protocols.REST.upper(), + resources={ + ModelFormat.OVMS: { + "requests": {"cpu": "1", "memory": "4Gi"}, + "limits": {"cpu": "2", "memory": "8Gi"}, + } + }, + teardown=teardown_resources, + ) as model_runtime: + yield model_runtime @pytest.fixture(scope="session") @@ -252,6 +340,7 @@ def openvino_model_mesh_inference_service_scope_session( s3_ovms_model_mesh_serving_runtime_scope_session: ServingRuntime, ci_endpoint_s3_secret_scope_session: Secret, model_mesh_model_service_account_scope_session: ServiceAccount, + teardown_resources: bool, ) -> Generator[InferenceService, Any, Any]: isvc_kwargs = { "client": admin_client, @@ -274,6 +363,141 @@ def openvino_model_mesh_inference_service_scope_session( model_format=ModelAndFormat.OPENVINO_IR, deployment_mode=KServeDeploymentType.MODEL_MESH, model_version=ModelVersion.OPSET1, + teardown=teardown_resources, + **isvc_kwargs, + ) as isvc: + yield isvc + + +@pytest.fixture(scope="session") +def model_service_account_scope_session( + pytestconfig: pytest.Config, + admin_client: DynamicClient, + ci_endpoint_s3_secret_scope_session: Secret, + teardown_resources: bool, +) -> Generator[ServiceAccount, Any, Any]: + sa_kwargs = { + "client": admin_client, + "name": "upgrade-models-bucket-sa", + "namespace": ci_endpoint_s3_secret_scope_session.namespace, + } + + sa = ServiceAccount(**sa_kwargs) + + if pytestconfig.option.post_upgrade: + yield sa + sa.clean_up() + + else: + with ServiceAccount( + client=admin_client, + namespace=ci_endpoint_s3_secret_scope_session.namespace, + name="upgrade-models-bucket-sa", + secrets=[{"name": ci_endpoint_s3_secret_scope_session.name}], + ) as sa: + yield sa + + +@pytest.fixture(scope="session") +def http_view_role_scope_session( + pytestconfig: pytest.Config, + admin_client: DynamicClient, + ovms_authenticated_serverless_inference_service_scope_session: InferenceService, + teardown_resources: bool, +) -> Generator[Role, Any, Any]: + role_kwargs = { + "client": admin_client, + "name": f"{ovms_authenticated_serverless_inference_service_scope_session.name}-view", + } + + role = Role( + **role_kwargs, + namespace=ovms_authenticated_serverless_inference_service_scope_session.namespace, + ) + + if pytestconfig.option.post_upgrade: + yield role + role.clean_up() + + else: + with create_isvc_view_role( + **role_kwargs, + isvc=ovms_authenticated_serverless_inference_service_scope_session, + resource_names=[ovms_authenticated_serverless_inference_service_scope_session.name], + teardown=teardown_resources, + ) as role: + yield role + + +@pytest.fixture(scope="session") +def http_role_binding_scope_session( + pytestconfig: pytest.Config, + admin_client: DynamicClient, + http_view_role_scope_session: Role, + model_service_account_scope_session: ServiceAccount, + ovms_authenticated_serverless_inference_service_scope_session: InferenceService, + teardown_resources: bool, +) -> Generator[RoleBinding, Any, Any]: + rb_kwargs = { + "client": admin_client, + "name": f"{model_service_account_scope_session.name}-view", + "namespace": ovms_authenticated_serverless_inference_service_scope_session.namespace, + } + + rb = RoleBinding(**rb_kwargs) + + if pytestconfig.option.post_upgrade: + yield rb + rb.clean_up() + + else: + with RoleBinding( + **rb_kwargs, + role_ref_name=http_view_role_scope_session.name, + role_ref_kind=http_view_role_scope_session.kind, + subjects_kind=model_service_account_scope_session.kind, + subjects_name=model_service_account_scope_session.name, + ) as rb: + yield rb + + +@pytest.fixture(scope="session") +def http_inference_token_scope_session( + model_service_account_scope_session: ServiceAccount, http_role_binding_scope_session: RoleBinding +) -> str: + return create_inference_token(model_service_account=model_service_account_scope_session) + + +@pytest.fixture(scope="session") +def ovms_authenticated_serverless_inference_service_scope_session( + pytestconfig: pytest.Config, + admin_client: DynamicClient, + openvino_serverless_serving_runtime_scope_session: ServingRuntime, + ci_endpoint_s3_secret_scope_session: Secret, + teardown_resources: bool, +) -> Generator[InferenceService, Any, Any]: + isvc_kwargs = { + "client": admin_client, + "name": f"{openvino_serverless_serving_runtime_scope_session.name}-auth", + "namespace": openvino_serverless_serving_runtime_scope_session.namespace, + } + + isvc = InferenceService(**isvc_kwargs) + + if pytestconfig.option.post_upgrade: + yield isvc + isvc.clean_up() + + else: + with create_isvc( + runtime=openvino_serverless_serving_runtime_scope_session.name, + storage_path="test-dir", + storage_key=ci_endpoint_s3_secret_scope_session.name, + model_format=ModelAndFormat.OPENVINO_IR, + deployment_mode=KServeDeploymentType.SERVERLESS, + model_version=ModelVersion.OPSET13, + enable_auth=True, + teardown=teardown_resources, **isvc_kwargs, ) as isvc: yield isvc diff --git a/tests/model_serving/model_server/upgrade/test_upgrade.py b/tests/model_serving/model_server/upgrade/test_upgrade.py index 74f3a0b64..938add0f3 100644 --- a/tests/model_serving/model_server/upgrade/test_upgrade.py +++ b/tests/model_serving/model_server/upgrade/test_upgrade.py @@ -11,7 +11,7 @@ # TODO: add auth, external route and grpc tests -@pytest.mark.usefixtures("valid_aws_config", "skipped_teardown_resources") +@pytest.mark.usefixtures("valid_aws_config") class TestPreUpgradeModelServer: @pytest.mark.pre_upgrade @pytest.mark.serverless @@ -50,8 +50,22 @@ def test_model_mesh_openvino_pre_upgrade_inference(self, openvino_model_mesh_inf use_default_query=True, ) + @pytest.mark.pre_upgrade + @pytest.mark.serverless + def test_serverless_authenticated_onnx_pre_upgrade_inference( + self, ovms_authenticated_serverless_inference_service_scope_session, http_inference_token_scope_session + ): + """Verify that kserve Serverless with auth ONNX model can be queried using REST before upgrade""" + verify_inference_response( + inference_service=ovms_authenticated_serverless_inference_service_scope_session, + inference_config=ONNX_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + token=http_inference_token_scope_session, + use_default_query=True, + ) + -@pytest.mark.usefixtures("reused_resources") class TestPostUpgradeModelServer: @pytest.mark.post_upgrade @pytest.mark.serverless @@ -117,3 +131,28 @@ def test_model_mesh_openvino_post_upgrade_inference(self, openvino_model_mesh_in protocol=Protocols.HTTP, use_default_query=True, ) + + @pytest.mark.post_upgrade + @pytest.mark.serverless + @pytest.mark.dependency(name="test_serverless_authenticated_onnx_post_upgrade_inference_service_exists") + def test_serverless_authenticated_onnx_post_upgrade_inference_service_exists( + self, ovms_authenticated_serverless_inference_service_scope_session + ): + """Test that the serverless inference service exists after upgrade""" + assert ovms_authenticated_serverless_inference_service_scope_session.exists + + @pytest.mark.post_upgrade + @pytest.mark.serverless + @pytest.mark.dependency(depends=["test_serverless_authenticated_onnx_post_upgrade_inference_service_exists"]) + def test_serverless_authenticated_onnx_post_upgrade_inference( + self, ovms_authenticated_serverless_inference_service_scope_session, http_inference_token_scope_session + ): + """Verify that kserve Serverless with auth ONNX model can be queried using REST before upgrade""" + verify_inference_response( + inference_service=ovms_authenticated_serverless_inference_service_scope_session, + inference_config=ONNX_INFERENCE_CONFIG, + inference_type=Inference.INFER, + protocol=Protocols.HTTPS, + token=http_inference_token_scope_session, + use_default_query=True, + ) diff --git a/utilities/inference_utils.py b/utilities/inference_utils.py index d3de333b0..fe8ae0225 100644 --- a/utilities/inference_utils.py +++ b/utilities/inference_utils.py @@ -527,6 +527,7 @@ def create_isvc( scale_metric: str | None = None, scale_target: int | None = None, model_env_variables: list[dict[str, str]] | None = None, + teardown: bool = True, ) -> Generator[InferenceService, Any, Any]: """ Create InferenceService object. @@ -559,6 +560,7 @@ def create_isvc( scale_metric (str): Scale metric scale_target (int): Scale target model_env_variables (list[dict[str, str]]): Model environment variables + teardown (bool): Teardown Yields: InferenceService: InferenceService object @@ -651,6 +653,7 @@ def create_isvc( annotations=_annotations, predictor=predictor_dict, label=labels, + teardown=teardown, ) as inference_service: timeout_watch = TimeoutWatch(timeout=timeout) diff --git a/utilities/infra.py b/utilities/infra.py index 2decdb350..88875aa94 100644 --- a/utilities/infra.py +++ b/utilities/infra.py @@ -216,7 +216,7 @@ def wait_for_inference_deployment_replicas( @contextmanager def s3_endpoint_secret( - admin_client: DynamicClient, + client: DynamicClient, name: str, namespace: str, aws_access_key: str, @@ -224,12 +224,13 @@ def s3_endpoint_secret( aws_s3_bucket: str, aws_s3_endpoint: str, aws_s3_region: str, + teardown: bool = True, ) -> Generator[Secret, Any, Any]: """ Create S3 endpoint secret. Args: - admin_client (DynamicClient): Dynamic client. + client (DynamicClient): Dynamic client. name (str): Secret name. namespace (str): Secret namespace name. aws_access_key (str): Secret access key. @@ -237,12 +238,13 @@ def s3_endpoint_secret( aws_s3_bucket (str): Secret s3 bucket. aws_s3_endpoint (str): Secret s3 endpoint. aws_s3_region (str): Secret s3 region. + teardown (bool): Whether to delete the secret. Yield: Secret: Secret object """ - secret_kwargs = {"client": admin_client, "name": name, "namespace": namespace} + secret_kwargs = {"client": client, "name": name, "namespace": namespace} secret = Secret(**secret_kwargs) if secret.exists: @@ -265,6 +267,7 @@ def s3_endpoint_secret( aws_s3_region=aws_s3_region, ), wait_for_resource=True, + teardown=teardown, **secret_kwargs, ) as secret: yield secret @@ -276,6 +279,7 @@ def create_isvc_view_role( isvc: InferenceService, name: str, resource_names: Optional[list[str]] = None, + teardown: bool = True, ) -> Generator[Role, Any, Any]: """ Create a view role for an InferenceService. @@ -285,6 +289,7 @@ def create_isvc_view_role( isvc (InferenceService): InferenceService object. name (str): Role name. resource_names (list[str]): Resource names to be attached to role. + teardown (bool): Whether to delete the role. Yields: Role: Role object. @@ -306,6 +311,7 @@ def create_isvc_view_role( name=name, namespace=isvc.namespace, rules=rules, + teardown=teardown, ) as role: yield role @@ -711,7 +717,7 @@ def get_product_version(admin_client: DynamicClient) -> Version: return Version.parse(operator_version) -def get_dsci_applications_namespace(client: DynamicClient, dsci_name: str = "default-dsci") -> str: +def get_dsci_applications_namespace(client: DynamicClient, dsci_name: str = "example") -> str: """ Get the namespace where DSCI applications are deployed. diff --git a/utilities/serving_runtime.py b/utilities/serving_runtime.py index 760662138..77cf310c2 100644 --- a/utilities/serving_runtime.py +++ b/utilities/serving_runtime.py @@ -31,6 +31,7 @@ def __init__( volumes: list[dict[str, Any]] | None = None, containers: dict[str, dict[str, Any]] | None = None, support_tgis_open_ai_endpoints: bool = False, + teardown: bool = True, ): """ ServingRuntimeFromTemplate class @@ -57,6 +58,7 @@ def __init__( to the serving runtime support_tgis_open_ai_endpoints (bool): Whether to support TGIS and OpenAI endpoints using a single entry point + teardown (bool): Whether to teardown the serving runtime or not """ self.admin_client = client @@ -87,6 +89,7 @@ def __init__( super().__init__( client=self.unprivileged_client or self.admin_client, kind_dict=self.model_dict, + teardown=teardown, ) def get_model_template(self) -> Template: From 2cad5c03086e8fffead106a3f79a81724b4a57bc Mon Sep 17 00:00:00 2001 From: rnetser Date: Mon, 14 Apr 2025 20:44:32 +0300 Subject: [PATCH 6/7] feat: add auth test to upgrade feat: add auth test to upgrade --- tests/model_serving/model_server/upgrade/conftest.py | 2 ++ utilities/infra.py | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/model_serving/model_server/upgrade/conftest.py b/tests/model_serving/model_server/upgrade/conftest.py index 815f55d0e..210672480 100644 --- a/tests/model_serving/model_server/upgrade/conftest.py +++ b/tests/model_serving/model_server/upgrade/conftest.py @@ -394,6 +394,7 @@ def model_service_account_scope_session( namespace=ci_endpoint_s3_secret_scope_session.namespace, name="upgrade-models-bucket-sa", secrets=[{"name": ci_endpoint_s3_secret_scope_session.name}], + teardown=teardown_resources, ) as sa: yield sa @@ -457,6 +458,7 @@ def http_role_binding_scope_session( role_ref_kind=http_view_role_scope_session.kind, subjects_kind=model_service_account_scope_session.kind, subjects_name=model_service_account_scope_session.name, + teardown=teardown_resources, ) as rb: yield rb diff --git a/utilities/infra.py b/utilities/infra.py index 88875aa94..1efa6bc08 100644 --- a/utilities/infra.py +++ b/utilities/infra.py @@ -114,14 +114,16 @@ def create_ns( project.wait_for_status(status=project.Status.ACTIVE, timeout=Timeout.TIMEOUT_2MIN) yield project - wait_for_serverless_pods_deletion(resource=project, admin_client=admin_client) + if teardown: + wait_for_serverless_pods_deletion(resource=project, admin_client=admin_client) else: with Namespace(**namespace_kwargs) as ns: ns.wait_for_status(status=Namespace.Status.ACTIVE, timeout=Timeout.TIMEOUT_2MIN) yield ns - wait_for_serverless_pods_deletion(resource=ns, admin_client=admin_client) + if teardown: + wait_for_serverless_pods_deletion(resource=ns, admin_client=admin_client) def wait_for_replicas_in_deployment(deployment: Deployment, replicas: int) -> None: From fc3deba2df0f95664b471c8afce7bd04d319e924 Mon Sep 17 00:00:00 2001 From: rnetser Date: Mon, 14 Apr 2025 21:03:18 +0300 Subject: [PATCH 7/7] fix: dsci name in func --- utilities/infra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utilities/infra.py b/utilities/infra.py index 1efa6bc08..760c0c070 100644 --- a/utilities/infra.py +++ b/utilities/infra.py @@ -719,7 +719,7 @@ def get_product_version(admin_client: DynamicClient) -> Version: return Version.parse(operator_version) -def get_dsci_applications_namespace(client: DynamicClient, dsci_name: str = "example") -> str: +def get_dsci_applications_namespace(client: DynamicClient, dsci_name: str = "default-dsci") -> str: """ Get the namespace where DSCI applications are deployed.