55from simple_logger .logger import get_logger
66from utilities .plugins .constant import OpenAIEnpoints
77from ocp_resources .service_account import ServiceAccount
8-
98from kubernetes .dynamic import DynamicClient
109from ocp_resources .namespace import Namespace
1110from ocp_resources .llm_inference_service import LLMInferenceService
12-
11+ from ocp_resources .deployment import Deployment
12+ from timeout_sampler import TimeoutSampler
1313from utilities .llmd_utils import create_llmisvc
1414from utilities .llmd_constants import ModelStorage , ContainerImages
1515from utilities .constants import (
2929from ocp_resources .secret import Secret
3030from tests .model_serving .model_server .maas_billing .utils import get_total_tokens
3131from utilities .infra import get_data_science_cluster
32+ from utilities .constants import DscComponents
33+ from utilities .resources .rate_limit_policy import RateLimitPolicy
34+ from utilities .resources .token_rate_limit_policy import TokenRateLimitPolicy
3235from tests .model_serving .model_server .maas_billing .utils import (
3336 detect_scheme_via_llmisvc ,
3437 host_from_ingress_domain ,
3942 get_maas_models_response ,
4043 verify_chat_completions ,
4144 maas_gateway_rate_limits_patched ,
42- ensure_maas_gateway_and_policies ,
4345 detect_maas_control_plane_namespace ,
4446 get_tier_mapping_configmap ,
47+ ensure_maas_gateway_api ,
48+ ensure_maas_usage_policies ,
49+ endpoints_have_ready_addresses ,
50+ gateway_probe_reaches_maas_api ,
4551)
4652
4753LOGGER = get_logger (name = __name__ )
@@ -64,7 +70,15 @@ def request_session_http() -> Generator[requests.Session, None, None]:
6470
6571
6672@pytest .fixture (scope = "class" )
67- def minted_token (request_session_http , base_url : str , current_client_token : str ) -> str :
73+ def minted_token (
74+ request_session_http ,
75+ base_url : str ,
76+ current_client_token : str ,
77+ maas_control_plane_namespace : str ,
78+ maas_controller_enabled_latest : None ,
79+ maas_gateway_and_policies : None ,
80+ maas_api_ready : None ,
81+ ) -> str :
6882 """Mint a MaaS token once per test class and reuse it."""
6983 resp , body = mint_token (
7084 base_url = base_url ,
@@ -94,7 +108,7 @@ def model_url(
94108) -> str :
95109 deployment = maas_inference_service_tinyllama .name
96110 url = f"{ maas_scheme } ://{ maas_host } /llm/{ deployment } { CHAT_COMPLETIONS } "
97- LOGGER .info ("MaaS: constructed model_url=%s (deployment=%s)" , url , deployment )
111+ LOGGER .info (f "MaaS: constructed model_url={ url } (deployment={ deployment } )" )
98112 return url
99113
100114
@@ -440,8 +454,9 @@ def maas_token_for_actor(
440454 base_url : str ,
441455 ocp_token_for_actor : str ,
442456 maas_control_plane_namespace : str ,
443- maas_controller_enabled_latest ,
444- maas_gateway_and_policies ,
457+ maas_controller_enabled_latest : None ,
458+ maas_gateway_and_policies : None ,
459+ maas_api_ready : None ,
445460) -> str :
446461 """
447462 Mint a MaaS token once per actor (admin / free / premium) and reuse it
@@ -545,7 +560,7 @@ def maas_inference_service_tinyllama(
545560 unprivileged_model_namespace : Namespace ,
546561 model_service_account : ServiceAccount ,
547562 maas_control_plane_namespace : str ,
548- maas_gateway_and_policies ,
563+ maas_gateway_and_policies : None ,
549564) -> Generator [LLMInferenceService , None , None ]:
550565 """
551566 TinyLlama S3-backed LLMInferenceService wired through MaaS for tests.
@@ -603,6 +618,8 @@ def maas_gateway_rate_limits(
603618 admin_client : DynamicClient ,
604619 maas_gateway_and_policies ,
605620 maas_tier_mapping_cm ,
621+ maas_request_ratelimit_policy ,
622+ maas_token_ratelimit_policy ,
606623) -> Generator [None , None , None ]:
607624 with maas_gateway_rate_limits_patched (
608625 admin_client = admin_client ,
@@ -622,38 +639,45 @@ def maas_gateway_api_hostname(admin_client: DynamicClient) -> str:
622639def maas_gateway_and_policies (
623640 admin_client : DynamicClient ,
624641 maas_gateway_api_hostname : str ,
625- maas_controller_enabled_latest ,
626642) -> Generator [None , None , None ]:
627643 """
628644 Ensure MaaS Gateway + Kuadrant policies exist once per test session.
629645 """
630- with ensure_maas_gateway_and_policies (
631- admin_client = admin_client ,
632- hostname = maas_gateway_api_hostname ,
646+ with (
647+ ensure_maas_gateway_api (
648+ admin_client = admin_client ,
649+ hostname = maas_gateway_api_hostname ,
650+ ),
651+ ensure_maas_usage_policies (
652+ admin_client = admin_client ,
653+ ),
633654 ):
634655 yield
635656
636657
637658@pytest .fixture (scope = "session" )
638- def maas_controller_enabled_latest (admin_client : DynamicClient ):
659+ def maas_controller_enabled_latest (
660+ admin_client : DynamicClient ,
661+ maas_gateway_and_policies : None ,
662+ ):
639663 dsc_resource = get_data_science_cluster (client = admin_client )
640664 dsc_resource .get ()
641665
642666 original_components = dsc_resource .instance .spec .components
643667
644- kserve = original_components . get ( "kserve" ) or {}
645- maas = kserve . get ( "modelsAsService" ) or {}
646- if ( maas . get ( "managementState" ) or "Removed" ) == "Managed" :
668+ kserve = original_components [ DscComponents . KSERVE ]
669+ maas = kserve [ "modelsAsService" ]
670+ if maas [ "managementState" ] == "Managed" :
647671 dsc_resource .wait_for_condition (condition = "ModelsAsServiceReady" , status = "True" , timeout = Timeout .TIMEOUT_15MIN )
648672 yield dsc_resource
649- return
650673
651- component_patch = {"kserve" : {"modelsAsService" : {"managementState" : "Managed" }}}
674+ component_patch = {DscComponents . KSERVE : {"modelsAsService" : {"managementState" : "Managed" }}}
652675
653676 with ResourceEditor (patches = {dsc_resource : {"spec" : {"components" : component_patch }}}):
654677 dsc_resource .wait_for_condition (condition = "ModelsAsServiceReady" , status = "True" , timeout = Timeout .TIMEOUT_15MIN )
655678 dsc_resource .wait_for_condition (condition = "Ready" , status = "True" , timeout = Timeout .TIMEOUT_15MIN )
656679 yield dsc_resource
680+ dsc_resource .wait_for_condition (condition = "Ready" , status = "True" , timeout = Timeout .TIMEOUT_15MIN )
657681
658682
659683@pytest .fixture (scope = "session" )
@@ -679,3 +703,95 @@ def maas_tier_mapping_cm(
679703 )
680704
681705 return config_map
706+
707+
708+ @pytest .fixture (scope = "class" )
709+ def maas_api_ready (
710+ admin_client : DynamicClient ,
711+ request_session_http : requests .Session ,
712+ base_url : str ,
713+ ) -> None :
714+ """
715+ MaaS API readiness
716+
717+ """
718+ maas_api_deployment = Deployment (
719+ client = admin_client ,
720+ name = "maas-api" ,
721+ namespace = "opendatahub" ,
722+ )
723+ maas_api_deployment .wait_for_condition (
724+ condition = "Available" ,
725+ status = "True" ,
726+ timeout = Timeout .TIMEOUT_10MIN ,
727+ )
728+
729+ endpoints_ready = False
730+ for endpoints_ready_sample in TimeoutSampler (
731+ wait_timeout = Timeout .TIMEOUT_5MIN ,
732+ sleep = 5 ,
733+ func = endpoints_have_ready_addresses ,
734+ admin_client = admin_client ,
735+ namespace = "opendatahub" ,
736+ name = "maas-api" ,
737+ ):
738+ endpoints_ready = bool (endpoints_ready_sample )
739+ if endpoints_ready :
740+ break
741+
742+ if not endpoints_ready :
743+ pytest .fail ("MaaS API endpoints are not ready: no endpoint addresses after waiting" )
744+
745+ probe_url = f"{ base_url } /v1/models"
746+
747+ last_status_code : int | None = None
748+ last_response_body : str | None = None
749+
750+ for gateway_probe_sample in TimeoutSampler (
751+ wait_timeout = Timeout .TIMEOUT_5MIN ,
752+ sleep = 5 ,
753+ func = gateway_probe_reaches_maas_api ,
754+ http_session = request_session_http ,
755+ probe_url = probe_url ,
756+ request_timeout_seconds = 30 ,
757+ ):
758+ gateway_reachable , status_code , response_text = gateway_probe_sample
759+ last_status_code = status_code
760+ last_response_body = response_text
761+
762+ if gateway_reachable :
763+ return
764+
765+ pytest .fail (
766+ "MaaS API is not reachable via Gateway yet. "
767+ f"Last status={ last_status_code } , "
768+ f"body={ (last_response_body or '' )[:200 ]} "
769+ )
770+
771+
772+ @pytest .fixture (scope = "session" )
773+ def maas_request_ratelimit_policy (
774+ admin_client : DynamicClient ,
775+ maas_gateway_and_policies : None ,
776+ ) -> RateLimitPolicy :
777+
778+ return RateLimitPolicy (
779+ client = admin_client ,
780+ name = MAAS_RATE_LIMIT_POLICY_NAME ,
781+ namespace = MAAS_GATEWAY_NAMESPACE ,
782+ ensure_exists = True ,
783+ )
784+
785+
786+ @pytest .fixture (scope = "session" )
787+ def maas_token_ratelimit_policy (
788+ admin_client : DynamicClient ,
789+ maas_gateway_and_policies : None ,
790+ ) -> TokenRateLimitPolicy :
791+
792+ return TokenRateLimitPolicy (
793+ client = admin_client ,
794+ name = MAAS_TOKEN_RATE_LIMIT_POLICY_NAME ,
795+ namespace = MAAS_GATEWAY_NAMESPACE ,
796+ ensure_exists = True ,
797+ )
0 commit comments