From 44cecdaf844e3a4084068d3f7ac13da3c35580e0 Mon Sep 17 00:00:00 2001 From: Radhika Lakhtakia <137429298+rlakhtakia@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:25:27 -0700 Subject: [PATCH 001/133] Update guide to add steps to deploy healthcheck policy for gke (#1475) --- site-src/guides/index.md | 74 +++++++++++++++++++++++++++++++--------- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 4e40ebaf5..a588aa4ee 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -19,6 +19,9 @@ A cluster with: - Support for [sidecar containers](https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/) (enabled by default since Kubernetes v1.29) to run the model server deployment. +Tooling: + - [Helm](https://helm.sh/docs/intro/install/) installed + ## **Steps** ### Deploy Sample Model Server @@ -80,6 +83,58 @@ A cluster with: kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/releases/latest/download/manifests.yaml ``` +### Deploy the InferencePool and Endpoint Picker Extension + + Install an InferencePool named `vllm-llama3-8b-instruct` that selects from endpoints with label `app: vllm-llama3-8b-instruct` and listening on port 8000. The Helm install command automatically installs the endpoint-picker, inferencepool along with provider specific resources. + +### Deploy the InferencePool and Endpoint Picker Extension + + Install an InferencePool named `vllm-llama3-8b-instruct` that selects from endpoints with label `app: vllm-llama3-8b-instruct` and listening on port 8000. The Helm install command automatically installs the endpoint-picker, inferencepool along with provider specific resources. + +=== "GKE" + + ```bash + export GATEWAY_PROVIDER=gke + helm install vllm-llama3-8b-instruct \ + --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ + --set provider.name=$GATEWAY_PROVIDER \ + --version v0.5.1 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool + ``` + +=== "Istio" + + ```bash + export GATEWAY_PROVIDER=none + helm install vllm-llama3-8b-instruct \ + --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ + --set provider.name=$GATEWAY_PROVIDER \ + --version v0.5.1 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool + ``` + +=== "Kgateway" + + ```bash + export GATEWAY_PROVIDER=none + helm install vllm-llama3-8b-instruct \ + --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ + --set provider.name=$GATEWAY_PROVIDER \ + --version v0.5.1 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool + ``` + +=== "Agentgateway" + + ```bash + export GATEWAY_PROVIDER=none + helm install vllm-llama3-8b-instruct \ + --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ + --set provider.name=$GATEWAY_PROVIDER \ + --version v0.5.1 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool + ``` + ### Deploy an Inference Gateway Choose one of the following options to deploy an Inference Gateway. @@ -268,22 +323,6 @@ A cluster with: kubectl get httproute llm-route -o yaml ``` - -### Deploy the InferencePool and Endpoint Picker Extension - - Install an InferencePool named `vllm-llama3-8b-instruct` that selects from endpoints with label app: vllm-llama3-8b-instruct and listening on port 8000, you can run the following command: - - ```bash - export GATEWAY_PROVIDER=none # See [README](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/config/charts/inferencepool/README.md#configuration) for valid configurations - helm install vllm-llama3-8b-instruct \ - --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ - --set provider.name=$GATEWAY_PROVIDER \ - --version v0.5.1 \ - oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool - ``` - - The Helm install automatically installs the endpoint-picker, inferencepool along with provider specific resources. - ### Deploy InferenceObjective (Optional) Deploy the sample InferenceObjective which allows you to specify priority of requests. @@ -317,10 +356,11 @@ A cluster with: 1. Uninstall the InferencePool, InferenceModel, and model server resources ```bash - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/inferencepool-resources.yaml --ignore-not-found + helm uninstall vllm-llama3-8b-instruct kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/inferenceobjective.yaml --ignore-not-found kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/cpu-deployment.yaml --ignore-not-found kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/gpu-deployment.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/sim-deployment.yaml --ignore-not-found kubectl delete secret hf-token --ignore-not-found ``` From 3ac6f317653daaf54fea68bc9923d7d9b5337315 Mon Sep 17 00:00:00 2001 From: Bob Tian Date: Tue, 9 Sep 2025 03:31:31 -0700 Subject: [PATCH 002/133] fix helm chart support for gke v1alpha2. (#1551) --- config/charts/inferencepool/README.md | 1 + config/charts/inferencepool/templates/epp-deployment.yaml | 4 ++++ config/charts/inferencepool/templates/gke.yaml | 4 ++-- config/charts/inferencepool/templates/rbac.yaml | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md index 82be6b85c..c7374bcd1 100644 --- a/config/charts/inferencepool/README.md +++ b/config/charts/inferencepool/README.md @@ -131,6 +131,7 @@ The following table list the configurable parameters of the chart. | **Parameter Name** | **Description** | |---------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| `inferencePool.apiVersion` | The API version of the InferencePool resource. Defaults to `inference.networking.k8s.io/v1`. This can be changed to `inference.networking.x-k8s.io/v1alpha2` to support older API versions. | | `inferencePool.targetPortNumber` | Target port number for the vllm backends, will be used to scrape metrics by the inference extension. Defaults to 8000. | | `inferencePool.modelServerType` | Type of the model servers in the pool, valid options are [vllm, triton-tensorrt-llm], default is vllm. | | `inferencePool.modelServers.matchLabels` | Label selector to match vllm backends managed by the inference pool. | diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml index f01699a96..dce1ed45c 100644 --- a/config/charts/inferencepool/templates/epp-deployment.yaml +++ b/config/charts/inferencepool/templates/epp-deployment.yaml @@ -27,6 +27,10 @@ spec: - {{ .Release.Name }} - --pool-namespace - {{ .Release.Namespace }} + {{- if ne .Values.inferencePool.apiVersion "inference.networking.k8s.io" }} + - --pool-group + - "{{ (split "/" .Values.inferencePool.apiVersion)._0 }}" + {{- end }} - --zap-encoder - "json" - --config-file diff --git a/config/charts/inferencepool/templates/gke.yaml b/config/charts/inferencepool/templates/gke.yaml index 92010c0d0..470063c79 100644 --- a/config/charts/inferencepool/templates/gke.yaml +++ b/config/charts/inferencepool/templates/gke.yaml @@ -9,7 +9,7 @@ metadata: {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} spec: targetRef: - group: "inference.networking.k8s.io" + group: "{{ (split "/" .Values.inferencePool.apiVersion)._0 }}" kind: InferencePool name: {{ .Release.Name }} default: @@ -28,7 +28,7 @@ metadata: {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} spec: targetRef: - group: "inference.networking.k8s.io" + group: "{{ (split "/" .Values.inferencePool.apiVersion)._0 }}" kind: InferencePool name: {{ .Release.Name }} default: diff --git a/config/charts/inferencepool/templates/rbac.yaml b/config/charts/inferencepool/templates/rbac.yaml index a8d891c32..4924e4325 100644 --- a/config/charts/inferencepool/templates/rbac.yaml +++ b/config/charts/inferencepool/templates/rbac.yaml @@ -40,9 +40,9 @@ metadata: {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} rules: - apiGroups: ["inference.networking.x-k8s.io"] - resources: ["inferenceobjectives", "inferencepools"] + resources: ["inferenceobjectives"] verbs: ["get", "watch", "list"] -- apiGroups: ["inference.networking.k8s.io"] +- apiGroups: ["{{ (split "/" .Values.inferencePool.apiVersion)._0 }}"] resources: ["inferencepools"] verbs: ["get", "watch", "list"] - apiGroups: [""] From 4cbcefeec759c324e795df9db65dd08ace886a86 Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Tue, 9 Sep 2025 17:29:53 +0300 Subject: [PATCH 003/133] chore: bump sim model server version (#1555) Signed-off-by: Nir Rozenbaum --- config/manifests/vllm/sim-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/manifests/vllm/sim-deployment.yaml b/config/manifests/vllm/sim-deployment.yaml index 17b689112..8229a01b4 100644 --- a/config/manifests/vllm/sim-deployment.yaml +++ b/config/manifests/vllm/sim-deployment.yaml @@ -14,7 +14,7 @@ spec: spec: containers: - name: vllm-sim - image: ghcr.io/llm-d/llm-d-inference-sim:v0.3.0 + image: ghcr.io/llm-d/llm-d-inference-sim:v0.4.0 imagePullPolicy: Always args: - --model From 9b95080d95355cd58c2fbc2e1cffc1f272c7f58c Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Tue, 9 Sep 2025 18:31:58 +0300 Subject: [PATCH 004/133] remove duplicated section in quickstart guide (#1553) Signed-off-by: Nir Rozenbaum --- site-src/guides/index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index a588aa4ee..acf604a20 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -87,10 +87,6 @@ Tooling: Install an InferencePool named `vllm-llama3-8b-instruct` that selects from endpoints with label `app: vllm-llama3-8b-instruct` and listening on port 8000. The Helm install command automatically installs the endpoint-picker, inferencepool along with provider specific resources. -### Deploy the InferencePool and Endpoint Picker Extension - - Install an InferencePool named `vllm-llama3-8b-instruct` that selects from endpoints with label `app: vllm-llama3-8b-instruct` and listening on port 8000. The Helm install command automatically installs the endpoint-picker, inferencepool along with provider specific resources. - === "GKE" ```bash From 4a137e62eb65e4441e06beceac183814aca8cd73 Mon Sep 17 00:00:00 2001 From: learner0810 <39400425+learner0810@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:11:56 +0800 Subject: [PATCH 005/133] Merge shuffle score pods logic (#1552) --- .../framework/plugins/picker/common.go | 18 ++++++++++++++++++ .../plugins/picker/max_score_picker.go | 12 +----------- .../framework/plugins/picker/random_picker.go | 12 +----------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/pkg/epp/scheduling/framework/plugins/picker/common.go b/pkg/epp/scheduling/framework/plugins/picker/common.go index 4bbc300da..c8655840f 100644 --- a/pkg/epp/scheduling/framework/plugins/picker/common.go +++ b/pkg/epp/scheduling/framework/plugins/picker/common.go @@ -16,6 +16,13 @@ limitations under the License. package picker +import ( + "math/rand/v2" + "time" + + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" +) + const ( DefaultMaxNumOfEndpoints = 1 // common default to all pickers ) @@ -24,3 +31,14 @@ const ( type pickerParameters struct { MaxNumOfEndpoints int `json:"maxNumOfEndpoints"` } + +func shuffleScoredPods(scoredPods []*types.ScoredPod) { + // Rand package is not safe for concurrent use, so we create a new instance. + // Source: https://pkg.go.dev/math/rand/v2#pkg-overview + randomGenerator := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 0)) + + // Shuffle in-place + randomGenerator.Shuffle(len(scoredPods), func(i, j int) { + scoredPods[i], scoredPods[j] = scoredPods[j], scoredPods[i] + }) +} diff --git a/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go b/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go index 325f735fa..47ad1f1ba 100644 --- a/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go +++ b/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go @@ -20,12 +20,9 @@ import ( "context" "encoding/json" "fmt" - "math/rand" "slices" - "time" "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" @@ -85,15 +82,8 @@ func (p *MaxScorePicker) Pick(ctx context.Context, cycleState *types.CycleState, log.FromContext(ctx).V(logutil.DEBUG).Info("Selecting pods from candidates sorted by max score", "max-num-of-endpoints", p.maxNumOfEndpoints, "num-of-candidates", len(scoredPods), "scored-pods", scoredPods) - // TODO: merge this with the logic in RandomPicker - // Rand package is not safe for concurrent use, so we create a new instance. - // Source: https://pkg.go.dev/math/rand#pkg-overview - randomGenerator := rand.New(rand.NewSource(time.Now().UnixNano())) - // Shuffle in-place - needed for random tie break when scores are equal - randomGenerator.Shuffle(len(scoredPods), func(i, j int) { - scoredPods[i], scoredPods[j] = scoredPods[j], scoredPods[i] - }) + shuffleScoredPods(scoredPods) slices.SortStableFunc(scoredPods, func(i, j *types.ScoredPod) int { // highest score first if i.Score > j.Score { diff --git a/pkg/epp/scheduling/framework/plugins/picker/random_picker.go b/pkg/epp/scheduling/framework/plugins/picker/random_picker.go index 87a1747fc..4c697d2f6 100644 --- a/pkg/epp/scheduling/framework/plugins/picker/random_picker.go +++ b/pkg/epp/scheduling/framework/plugins/picker/random_picker.go @@ -20,11 +20,8 @@ import ( "context" "encoding/json" "fmt" - "math/rand" - "time" "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" @@ -84,15 +81,8 @@ func (p *RandomPicker) Pick(ctx context.Context, _ *types.CycleState, scoredPods log.FromContext(ctx).V(logutil.DEBUG).Info("Selecting pods from candidates randomly", "max-num-of-endpoints", p.maxNumOfEndpoints, "num-of-candidates", len(scoredPods), "scored-pods", scoredPods) - // TODO: merge this with the logic in MaxScorePicker - // Rand package is not safe for concurrent use, so we create a new instance. - // Source: https://pkg.go.dev/math/rand#pkg-overview - randomGenerator := rand.New(rand.NewSource(time.Now().UnixNano())) - // Shuffle in-place - randomGenerator.Shuffle(len(scoredPods), func(i, j int) { - scoredPods[i], scoredPods[j] = scoredPods[j], scoredPods[i] - }) + shuffleScoredPods(scoredPods) // if we have enough pods to return keep only the relevant subset if p.maxNumOfEndpoints < len(scoredPods) { From 50e2d233512fed5f2fbd416865dee6917114cf93 Mon Sep 17 00:00:00 2001 From: Rahul Gurnani Date: Tue, 9 Sep 2025 21:19:56 -0700 Subject: [PATCH 006/133] Update docs with 1.0 release (#1557) --- site-src/guides/index.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index acf604a20..da8063298 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -4,10 +4,6 @@ This project is still in an alpha state and breaking changes may occur in the future. -???+ warning - - - This page is out of date with the v1.0.0 release candidate. Updates under active development This quickstart guide is intended for engineers familiar with k8s and model servers (vLLM in this instance). The goal of this guide is to get an Inference Gateway up and running! @@ -53,6 +49,10 @@ Tooling: === "CPU-Based Model Server" + ???+ warning + + CPU deployment can be unreliable i.e. the pods may crash/restart because of resource contraints. + This setup is using the formal `vllm-cpu` image, which according to the documentation can run vLLM on x86 CPU platform. For this setup, we use approximately 9.5GB of memory and 12 CPUs for each replica. @@ -94,7 +94,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v0.5.1 \ + --version v1.0.0 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -105,7 +105,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v0.5.1 \ + --version v1.0.0 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -116,7 +116,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v0.5.1 \ + --version v1.0.0 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -127,7 +127,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v0.5.1 \ + --version v1.0.0 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` From 560098153193c26f330cd29bcee9c7056362eafa Mon Sep 17 00:00:00 2001 From: learner0810 <39400425+learner0810@users.noreply.github.com> Date: Wed, 10 Sep 2025 16:51:59 +0800 Subject: [PATCH 007/133] add-envoyproxy-ai-gateway (#1554) --- .../gateway/envoyaigateway/gateway.yaml | 17 ++++++++++++++++ .../gateway/envoyaigateway/httproute.yaml | 20 +++++++++++++++++++ hack/verify-manifests.sh | 1 + 3 files changed, 38 insertions(+) create mode 100644 config/manifests/gateway/envoyaigateway/gateway.yaml create mode 100644 config/manifests/gateway/envoyaigateway/httproute.yaml diff --git a/config/manifests/gateway/envoyaigateway/gateway.yaml b/config/manifests/gateway/envoyaigateway/gateway.yaml new file mode 100644 index 000000000..1a536411b --- /dev/null +++ b/config/manifests/gateway/envoyaigateway/gateway.yaml @@ -0,0 +1,17 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: envoy-ai-gateway +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: inference-gateway +spec: + gatewayClassName: envoy-ai-gateway + listeners: + - name: http + protocol: HTTP + port: 80 diff --git a/config/manifests/gateway/envoyaigateway/httproute.yaml b/config/manifests/gateway/envoyaigateway/httproute.yaml new file mode 100644 index 000000000..e30b56140 --- /dev/null +++ b/config/manifests/gateway/envoyaigateway/httproute.yaml @@ -0,0 +1,20 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: llm-route +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: inference-gateway + rules: + - backendRefs: + - group: inference.networking.x-k8s.io + kind: InferencePool + name: vllm-llama3-8b-instruct + matches: + - path: + type: PathPrefix + value: / + timeouts: + request: 300s diff --git a/hack/verify-manifests.sh b/hack/verify-manifests.sh index 70d819bc8..dff4170ff 100755 --- a/hack/verify-manifests.sh +++ b/hack/verify-manifests.sh @@ -38,6 +38,7 @@ main() { cp ${SCRIPT_ROOT}/config/crd/bases/* "${TEMP_DIR}/" # Download external CRDs for validation + fetch_crds "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/refs/tags/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml" fetch_crds "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/refs/tags/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_gateways.yaml" fetch_crds "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/refs/tags/${GATEWAY_API_VERSION}/config/crd/standard/gateway.networking.k8s.io_httproutes.yaml" fetch_crds "https://raw.githubusercontent.com/GoogleCloudPlatform/gke-gateway-api/refs/tags/${GKE_GATEWAY_API_VERSION}/config/crd/networking.gke.io_gcpbackendpolicies.yaml" From 87b6d14cc6fe49ea73cc55827ab73735747f0dfa Mon Sep 17 00:00:00 2001 From: learner0810 <39400425+learner0810@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:24:00 +0800 Subject: [PATCH 008/133] fix-import-groups (#1560) --- pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go | 1 + pkg/epp/scheduling/framework/plugins/picker/random_picker.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go b/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go index 47ad1f1ba..33e99bd06 100644 --- a/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go +++ b/pkg/epp/scheduling/framework/plugins/picker/max_score_picker.go @@ -23,6 +23,7 @@ import ( "slices" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" diff --git a/pkg/epp/scheduling/framework/plugins/picker/random_picker.go b/pkg/epp/scheduling/framework/plugins/picker/random_picker.go index 4c697d2f6..10ad68469 100644 --- a/pkg/epp/scheduling/framework/plugins/picker/random_picker.go +++ b/pkg/epp/scheduling/framework/plugins/picker/random_picker.go @@ -22,6 +22,7 @@ import ( "fmt" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" From 97334f2108ccb91f4eca94bf609e83a837350a18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 04:19:57 -0700 Subject: [PATCH 009/133] chore(deps): bump golang.org/x/sync from 0.16.0 to 0.17.0 (#1549) Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.16.0 to 0.17.0. - [Commits](https://github.com/golang/sync/compare/v0.16.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/sync dependency-version: 0.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 28dbb0837..3f835d70a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/stretchr/testify v1.11.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/sync v0.16.0 + golang.org/x/sync v0.17.0 google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.8 k8s.io/api v0.34.0 diff --git a/go.sum b/go.sum index fca5d7209..7bf7959a5 100644 --- a/go.sum +++ b/go.sum @@ -303,8 +303,8 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 9848c0e4b40f6ca7869efc2bb9f9784ab6e61480 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:08:00 -0700 Subject: [PATCH 010/133] chore(deps): bump sigs.k8s.io/controller-tools from 0.18.0 to 0.19.0 (#1548) Bumps [sigs.k8s.io/controller-tools](https://github.com/kubernetes-sigs/controller-tools) from 0.18.0 to 0.19.0. - [Release notes](https://github.com/kubernetes-sigs/controller-tools/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-tools/blob/main/envtest-releases.yaml) - [Commits](https://github.com/kubernetes-sigs/controller-tools/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-tools dependency-version: 0.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 23 ++++++++++++----------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 3f835d70a..163e0e12b 100644 --- a/go.mod +++ b/go.mod @@ -24,14 +24,14 @@ require ( google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.8 k8s.io/api v0.34.0 - k8s.io/apiextensions-apiserver v0.33.4 + k8s.io/apiextensions-apiserver v0.34.0 k8s.io/apimachinery v0.34.0 k8s.io/client-go v0.34.0 k8s.io/code-generator v0.34.0 k8s.io/component-base v0.34.0 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/controller-runtime v0.21.0 - sigs.k8s.io/controller-tools v0.18.0 + sigs.k8s.io/controller-tools v0.19.0 sigs.k8s.io/gateway-api v1.3.0 sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 @@ -54,7 +54,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -65,7 +65,7 @@ require ( github.com/goccy/go-yaml v1.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.23.2 // indirect + github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect @@ -90,7 +90,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.7 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect @@ -124,7 +124,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.33.4 // indirect + k8s.io/apiserver v0.34.0 // indirect k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect diff --git a/go.sum b/go.sum index 7bf7959a5..f1d01ec4a 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -120,8 +120,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= -github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -228,8 +228,9 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -362,12 +363,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= -k8s.io/apiextensions-apiserver v0.33.4 h1:rtq5SeXiDbXmSwxsF0MLe2Mtv3SwprA6wp+5qh/CrOU= -k8s.io/apiextensions-apiserver v0.33.4/go.mod h1:mWXcZQkQV1GQyxeIjYApuqsn/081hhXPZwZ2URuJeSs= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.33.4 h1:6N0TEVA6kASUS3owYDIFJjUH6lgN8ogQmzZvaFFj1/Y= -k8s.io/apiserver v0.33.4/go.mod h1:8ODgXMnOoSPLMUg1aAzMFx+7wTJM+URil+INjbTZCok= +k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= +k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= k8s.io/code-generator v0.34.0 h1:Ze2i1QsvUprIlX3oHiGv09BFQRLCz+StA8qKwwFzees= @@ -386,8 +387,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUo sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= -sigs.k8s.io/controller-tools v0.18.0 h1:rGxGZCZTV2wJreeRgqVoWab/mfcumTMmSwKzoM9xrsE= -sigs.k8s.io/controller-tools v0.18.0/go.mod h1:gLKoiGBriyNh+x1rWtUQnakUYEujErjXs9pf+x/8n1U= +sigs.k8s.io/controller-tools v0.19.0 h1:OU7jrPPiZusryu6YK0jYSjPqg8Vhf8cAzluP9XGI5uk= +sigs.k8s.io/controller-tools v0.19.0/go.mod h1:y5HY/iNDFkmFla2CfQoVb2AQXMsBk4ad84iR1PLANB0= sigs.k8s.io/gateway-api v1.3.0 h1:q6okN+/UKDATola4JY7zXzx40WO4VISk7i9DIfOvr9M= sigs.k8s.io/gateway-api v1.3.0/go.mod h1:d8NV8nJbaRbEKem+5IuxkL8gJGOZ+FJ+NvOIltV8gDk= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= From 701a09efd189b99f681bad549d337a8a91a646e1 Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Wed, 10 Sep 2025 19:09:58 +0300 Subject: [PATCH 011/133] fix flake in weighted random picker (#1561) Signed-off-by: Nir Rozenbaum --- .../framework/plugins/picker/picker_test.go | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/pkg/epp/scheduling/framework/plugins/picker/picker_test.go b/pkg/epp/scheduling/framework/plugins/picker/picker_test.go index 741a49d59..022328efd 100644 --- a/pkg/epp/scheduling/framework/plugins/picker/picker_test.go +++ b/pkg/epp/scheduling/framework/plugins/picker/picker_test.go @@ -18,6 +18,7 @@ package picker import ( "context" + "math" "testing" "github.com/google/go-cmp/cmp" @@ -138,8 +139,8 @@ func TestPickMaxScorePicker(t *testing.T) { func TestPickWeightedRandomPicker(t *testing.T) { const ( - testIterations = 1000 - tolerance = 0.2 // 20% tolerance in [0,1] range + testIterations = 10000 + tolerance = 0.05 // Verify within tolerance ±5% ) pod1 := &types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}}} @@ -197,14 +198,14 @@ func TestPickWeightedRandomPicker(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { picker := NewWeightedRandomPicker(test.maxPods) - selectionCounts := make(map[string]int) - // Calculate expected probabilities based on scores + // Summarize the total score of all pods totalScore := 0.0 for _, pod := range test.input { totalScore += pod.Score } + // Calculate expected probabilities based on scores expectedProbabilities := make(map[string]float64) for _, pod := range test.input { podName := pod.GetPod().NamespacedName.Name @@ -216,20 +217,19 @@ func TestPickWeightedRandomPicker(t *testing.T) { } // Initialize selection counters for each pod + selectionCounts := make(map[string]int) for _, pod := range test.input { podName := pod.GetPod().NamespacedName.Name selectionCounts[podName] = 0 } // Run multiple iterations to gather statistical data - for i := 0; i < testIterations; i++ { + for range testIterations { result := picker.Pick(context.Background(), types.NewCycleState(), test.input) // Count selections for probability analysis - if len(result.TargetPods) > 0 { - selectedPodName := result.TargetPods[0].GetPod().NamespacedName.Name - selectionCounts[selectedPodName]++ - } + selectedPodName := result.TargetPods[0].GetPod().NamespacedName.Name + selectionCounts[selectedPodName]++ } // Verify probability distribution @@ -237,11 +237,7 @@ func TestPickWeightedRandomPicker(t *testing.T) { actualCount := selectionCounts[podName] actualProb := float64(actualCount) / float64(testIterations) - toleranceValue := expectedProb * tolerance - lowerBound := expectedProb - toleranceValue - upperBound := expectedProb + toleranceValue - - if actualProb < lowerBound || actualProb > upperBound { + if math.Abs(actualProb-expectedProb) > tolerance { t.Errorf("Pod %s: expected probability %.3f ±%.1f%%, got %.3f (count: %d/%d)", podName, expectedProb, tolerance*100, actualProb, actualCount, testIterations) } else { From 6d22ab497f348656ca524ac77dff7421fdfdbfed Mon Sep 17 00:00:00 2001 From: Gregory Pereira Date: Wed, 10 Sep 2025 13:13:57 -0400 Subject: [PATCH 012/133] Main uniquely name crbac (#1564) * uniquely name CRBAC Signed-off-by: greg pereira * bugfix with testing Signed-off-by: greg pereira --------- Signed-off-by: greg pereira --- config/charts/inferencepool/templates/_helpers.tpl | 9 +++++++++ config/charts/inferencepool/templates/rbac.yaml | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/config/charts/inferencepool/templates/_helpers.tpl b/config/charts/inferencepool/templates/_helpers.tpl index e011bb7c1..fdc9b1a2b 100644 --- a/config/charts/inferencepool/templates/_helpers.tpl +++ b/config/charts/inferencepool/templates/_helpers.tpl @@ -16,6 +16,15 @@ Inference extension name {{ $base }}-epp {{- end -}} +{{/* +Cluster RBAC unique name +*/}} +{{- define "gateway-api-inference-extension.cluster-rbac-name" -}} +{{- $base := .Release.Name | default "default-pool" | lower | trim | trunc 40 }} +{{- $ns := .Release.Namespace | default "default" | lower | trim | trunc 40 }} +{{- printf "%s-%s-epp" $base $ns | quote | trunc 84 }} +{{- end -}} + {{/* Selector labels */}} diff --git a/config/charts/inferencepool/templates/rbac.yaml b/config/charts/inferencepool/templates/rbac.yaml index 4924e4325..7ff534ff9 100644 --- a/config/charts/inferencepool/templates/rbac.yaml +++ b/config/charts/inferencepool/templates/rbac.yaml @@ -1,7 +1,7 @@ kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: {{ include "gateway-api-inference-extension.name" . }} + name: {{ include "gateway-api-inference-extension.cluster-rbac-name" . }} labels: {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} rules: @@ -21,7 +21,7 @@ rules: kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: {{ include "gateway-api-inference-extension.name" . }} + name: {{ include "gateway-api-inference-extension.cluster-rbac-name" . }} subjects: - kind: ServiceAccount name: {{ include "gateway-api-inference-extension.name" . }} @@ -29,7 +29,7 @@ subjects: roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: {{ include "gateway-api-inference-extension.name" . }} + name: {{ include "gateway-api-inference-extension.cluster-rbac-name" . }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role From 0b402cadf62390449da6657da16176f58adee0fa Mon Sep 17 00:00:00 2001 From: Rahul Gurnani Date: Wed, 10 Sep 2025 11:41:56 -0700 Subject: [PATCH 013/133] Update priority in EPP flow control from uint to int (#1518) * Update type for priority from uint to int in EPP flow control * Update tests to accomodate for priority changes * Avoid using Reverse to sort in descending order, update comments --- pkg/epp/flowcontrol/contracts/mocks/mocks.go | 12 +++--- pkg/epp/flowcontrol/contracts/registry.go | 16 ++++---- .../flowcontrol/controller/internal/filter.go | 2 +- .../controller/internal/filter_test.go | 2 +- .../controller/internal/processor.go | 2 +- .../controller/internal/processor_test.go | 41 ++++++++++--------- pkg/epp/flowcontrol/framework/mocks/mocks.go | 4 +- pkg/epp/flowcontrol/framework/policies.go | 2 +- pkg/epp/flowcontrol/registry/config.go | 22 +++++----- .../flowcontrol/registry/managedqueue_test.go | 2 +- pkg/epp/flowcontrol/registry/registry.go | 10 ++--- pkg/epp/flowcontrol/registry/registry_test.go | 2 +- pkg/epp/flowcontrol/registry/shard.go | 25 +++++------ pkg/epp/flowcontrol/registry/shard_test.go | 8 ++-- pkg/epp/flowcontrol/types/flow.go | 8 ++-- 15 files changed, 81 insertions(+), 77 deletions(-) diff --git a/pkg/epp/flowcontrol/contracts/mocks/mocks.go b/pkg/epp/flowcontrol/contracts/mocks/mocks.go index c5c8d2e3b..02f9863fb 100644 --- a/pkg/epp/flowcontrol/contracts/mocks/mocks.go +++ b/pkg/epp/flowcontrol/contracts/mocks/mocks.go @@ -48,9 +48,9 @@ type MockRegistryShard struct { IsActiveFunc func() bool ManagedQueueFunc func(key types.FlowKey) (contracts.ManagedQueue, error) IntraFlowDispatchPolicyFunc func(key types.FlowKey) (framework.IntraFlowDispatchPolicy, error) - InterFlowDispatchPolicyFunc func(priority uint) (framework.InterFlowDispatchPolicy, error) - PriorityBandAccessorFunc func(priority uint) (framework.PriorityBandAccessor, error) - AllOrderedPriorityLevelsFunc func() []uint + InterFlowDispatchPolicyFunc func(priority int) (framework.InterFlowDispatchPolicy, error) + PriorityBandAccessorFunc func(priority int) (framework.PriorityBandAccessor, error) + AllOrderedPriorityLevelsFunc func() []int StatsFunc func() contracts.ShardStats } @@ -82,21 +82,21 @@ func (m *MockRegistryShard) IntraFlowDispatchPolicy(key types.FlowKey) (framewor return nil, nil } -func (m *MockRegistryShard) InterFlowDispatchPolicy(priority uint) (framework.InterFlowDispatchPolicy, error) { +func (m *MockRegistryShard) InterFlowDispatchPolicy(priority int) (framework.InterFlowDispatchPolicy, error) { if m.InterFlowDispatchPolicyFunc != nil { return m.InterFlowDispatchPolicyFunc(priority) } return nil, nil } -func (m *MockRegistryShard) PriorityBandAccessor(priority uint) (framework.PriorityBandAccessor, error) { +func (m *MockRegistryShard) PriorityBandAccessor(priority int) (framework.PriorityBandAccessor, error) { if m.PriorityBandAccessorFunc != nil { return m.PriorityBandAccessorFunc(priority) } return nil, nil } -func (m *MockRegistryShard) AllOrderedPriorityLevels() []uint { +func (m *MockRegistryShard) AllOrderedPriorityLevels() []int { if m.AllOrderedPriorityLevelsFunc != nil { return m.AllOrderedPriorityLevelsFunc() } diff --git a/pkg/epp/flowcontrol/contracts/registry.go b/pkg/epp/flowcontrol/contracts/registry.go index de1b89ae6..e2c42fdbc 100644 --- a/pkg/epp/flowcontrol/contracts/registry.go +++ b/pkg/epp/flowcontrol/contracts/registry.go @@ -124,20 +124,20 @@ type RegistryShard interface { // InterFlowDispatchPolicy retrieves a priority band's configured `framework.InterFlowDispatchPolicy` for this shard. // The registry guarantees that a non-nil default policy is returned if none is configured for the band. // Returns an error wrapping `ErrPriorityBandNotFound` if the priority level is not configured. - InterFlowDispatchPolicy(priority uint) (framework.InterFlowDispatchPolicy, error) + InterFlowDispatchPolicy(priority int) (framework.InterFlowDispatchPolicy, error) // PriorityBandAccessor retrieves a read-only accessor for a given priority level, providing a view of the band's // state as seen by this specific shard. This is the primary entry point for inter-flow dispatch policies that need to // inspect and compare multiple flow queues within the same priority band. // Returns an error wrapping `ErrPriorityBandNotFound` if the priority level is not configured. - PriorityBandAccessor(priority uint) (framework.PriorityBandAccessor, error) + PriorityBandAccessor(priority int) (framework.PriorityBandAccessor, error) - // AllOrderedPriorityLevels returns all configured priority levels that this shard is aware of, sorted in ascending - // numerical order. This order corresponds to highest priority (lowest numeric value) to lowest priority (highest + // AllOrderedPriorityLevels returns all configured priority levels that this shard is aware of, sorted in descending + // numerical order. This order corresponds to highest priority (highest numeric value) to lowest priority (lowest // numeric value). // The returned slice provides a definitive, ordered list of priority levels for iteration, for example, by a // `controller.FlowController` worker's dispatch loop. - AllOrderedPriorityLevels() []uint + AllOrderedPriorityLevels() []int // Stats returns a snapshot of the statistics for this specific shard. Stats() ShardStats @@ -170,7 +170,7 @@ type AggregateStats struct { // TotalLen is the total number of items currently queued across the entire system. TotalLen uint64 // PerPriorityBandStats maps each configured priority level to its globally aggregated statistics. - PerPriorityBandStats map[uint]PriorityBandStats + PerPriorityBandStats map[int]PriorityBandStats } // ShardStats holds statistics for a single internal shard within the `FlowRegistry`. @@ -188,13 +188,13 @@ type ShardStats struct { // The capacity values within represent this shard's partition of the global band capacity. // The key is the numerical priority level. // All configured priority levels are guaranteed to be represented. - PerPriorityBandStats map[uint]PriorityBandStats + PerPriorityBandStats map[int]PriorityBandStats } // PriorityBandStats holds aggregated statistics for a single priority band. type PriorityBandStats struct { // Priority is the numerical priority level this struct describes. - Priority uint + Priority int // PriorityName is a human-readable name for the priority band (e.g., "Critical", "Sheddable"). // The registry configuration requires this field, so it is guaranteed to be non-empty. PriorityName string diff --git a/pkg/epp/flowcontrol/controller/internal/filter.go b/pkg/epp/flowcontrol/controller/internal/filter.go index 0a0669224..12788d4cb 100644 --- a/pkg/epp/flowcontrol/controller/internal/filter.go +++ b/pkg/epp/flowcontrol/controller/internal/filter.go @@ -101,7 +101,7 @@ func newSubsetPriorityBandAccessor(original framework.PriorityBandAccessor, allo } // Priority returns the numerical priority level of this band. -func (s *subsetPriorityBandAccessor) Priority() uint { +func (s *subsetPriorityBandAccessor) Priority() int { return s.originalAccessor.Priority() } diff --git a/pkg/epp/flowcontrol/controller/internal/filter_test.go b/pkg/epp/flowcontrol/controller/internal/filter_test.go index ceff9e83f..7e51453c4 100644 --- a/pkg/epp/flowcontrol/controller/internal/filter_test.go +++ b/pkg/epp/flowcontrol/controller/internal/filter_test.go @@ -128,7 +128,7 @@ func TestSubsetPriorityBandAccessor(t *testing.T) { t.Run("should pass through priority and name", func(t *testing.T) { t.Parallel() - assert.Equal(t, uint(10), subsetAccessor.Priority(), "Priority() should pass through from the original accessor") + assert.Equal(t, 10, subsetAccessor.Priority(), "Priority() should pass through from the original accessor") assert.Equal(t, "High", subsetAccessor.PriorityName(), "PriorityName() should pass through from the original accessor") }) diff --git a/pkg/epp/flowcontrol/controller/internal/processor.go b/pkg/epp/flowcontrol/controller/internal/processor.go index 7f9c8ee3a..abcd9f2bc 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor.go +++ b/pkg/epp/flowcontrol/controller/internal/processor.go @@ -268,7 +268,7 @@ func (sp *ShardProcessor) enqueue(item *flowItem) { // hasCapacity checks if the shard and the specific priority band have enough capacity to accommodate an item of a given // size. -func (sp *ShardProcessor) hasCapacity(priority uint, itemByteSize uint64) bool { +func (sp *ShardProcessor) hasCapacity(priority int, itemByteSize uint64) bool { if itemByteSize == 0 { return true } diff --git a/pkg/epp/flowcontrol/controller/internal/processor_test.go b/pkg/epp/flowcontrol/controller/internal/processor_test.go index 67657a9a4..81807e0e3 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor_test.go +++ b/pkg/epp/flowcontrol/controller/internal/processor_test.go @@ -44,7 +44,7 @@ import ( "errors" "fmt" "os" - "slices" + "sort" "sync" "sync/atomic" "testing" @@ -122,7 +122,7 @@ type testHarness struct { // The harness's mutex protects the single source of truth for all mock state. mu sync.Mutex queues map[types.FlowKey]*mocks.MockManagedQueue - priorityFlows map[uint][]types.FlowKey // Key: `priority` + priorityFlows map[int][]types.FlowKey // Key: `priority` // Customizable policy logic for tests to override. interFlowPolicySelectQueue func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error) @@ -139,7 +139,7 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn logger: logr.Discard(), startSignal: make(chan struct{}), queues: make(map[types.FlowKey]*mocks.MockManagedQueue), - priorityFlows: make(map[uint][]types.FlowKey), + priorityFlows: make(map[int][]types.FlowKey), } // Wire up the harness to provide the mock implementations for the shard's dependencies. @@ -153,7 +153,7 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn h.StatsFunc = func() contracts.ShardStats { return contracts.ShardStats{ TotalCapacityBytes: 1e9, - PerPriorityBandStats: map[uint]contracts.PriorityBandStats{ + PerPriorityBandStats: map[int]contracts.PriorityBandStats{ testFlow.Priority: {CapacityBytes: 1e9}, }, } @@ -249,20 +249,23 @@ func (h *testHarness) managedQueue(key types.FlowKey) (contracts.ManagedQueue, e } // allOrderedPriorityLevels provides the mock implementation for the `RegistryShard` interface. -func (h *testHarness) allOrderedPriorityLevels() []uint { +func (h *testHarness) allOrderedPriorityLevels() []int { h.mu.Lock() defer h.mu.Unlock() - prios := make([]uint, 0, len(h.priorityFlows)) + prios := make([]int, 0, len(h.priorityFlows)) for p := range h.priorityFlows { prios = append(prios, p) } - slices.Sort(prios) + sort.Slice(prios, func(i, j int) bool { + return prios[i] > prios[j] + }) + return prios } // priorityBandAccessor provides the mock implementation for the `RegistryShard` interface. It acts as a factory for a // fully-configured, stateless mock that is safe for concurrent use. -func (h *testHarness) priorityBandAccessor(p uint) (framework.PriorityBandAccessor, error) { +func (h *testHarness) priorityBandAccessor(p int) (framework.PriorityBandAccessor, error) { band := &frameworkmocks.MockPriorityBandAccessor{PriorityV: p} // Safely get a snapshot of the flow IDs under a lock. @@ -288,7 +291,7 @@ func (h *testHarness) priorityBandAccessor(p uint) (framework.PriorityBandAccess } // interFlowDispatchPolicy provides the mock implementation for the `contracts.RegistryShard` interface. -func (h *testHarness) interFlowDispatchPolicy(p uint) (framework.InterFlowDispatchPolicy, error) { +func (h *testHarness) interFlowDispatchPolicy(p int) (framework.InterFlowDispatchPolicy, error) { policy := &frameworkmocks.MockInterFlowDispatchPolicy{} // If the test provided a custom implementation, use it. if h.interFlowPolicySelectQueue != nil { @@ -362,7 +365,7 @@ func TestShardProcessor(t *testing.T) { item := h.newTestItem("req-capacity-reject", testFlow, testTTL) h.addQueue(testFlow) h.StatsFunc = func() contracts.ShardStats { - return contracts.ShardStats{PerPriorityBandStats: map[uint]contracts.PriorityBandStats{ + return contracts.ShardStats{PerPriorityBandStats: map[int]contracts.PriorityBandStats{ testFlow.Priority: {CapacityBytes: 50}, // 50 is less than item size of 100 }} } @@ -685,7 +688,7 @@ func TestShardProcessor(t *testing.T) { name: "should reject item on registry priority band lookup failure", setupHarness: func(h *testHarness) { h.addQueue(testFlow) - h.PriorityBandAccessorFunc = func(uint) (framework.PriorityBandAccessor, error) { return nil, testErr } + h.PriorityBandAccessorFunc = func(int) (framework.PriorityBandAccessor, error) { return nil, testErr } }, assert: func(t *testing.T, h *testHarness, item *flowItem) { outcome, err := item.FinalState() @@ -776,7 +779,7 @@ func TestShardProcessor(t *testing.T) { itemByteSize: 1, stats: contracts.ShardStats{ TotalCapacityBytes: 200, TotalByteSize: 100, - PerPriorityBandStats: map[uint]contracts.PriorityBandStats{ + PerPriorityBandStats: map[int]contracts.PriorityBandStats{ testFlow.Priority: {ByteSize: 50, CapacityBytes: 50}, }, }, @@ -787,7 +790,7 @@ func TestShardProcessor(t *testing.T) { itemByteSize: 1, stats: contracts.ShardStats{ TotalCapacityBytes: 200, TotalByteSize: 100, - PerPriorityBandStats: map[uint]contracts.PriorityBandStats{}, // Missing stats for priority 10 + PerPriorityBandStats: map[int]contracts.PriorityBandStats{}, // Missing stats for priority 10 }, expectHasCap: false, }, @@ -796,7 +799,7 @@ func TestShardProcessor(t *testing.T) { itemByteSize: 10, stats: contracts.ShardStats{ TotalCapacityBytes: 200, TotalByteSize: 100, - PerPriorityBandStats: map[uint]contracts.PriorityBandStats{ + PerPriorityBandStats: map[int]contracts.PriorityBandStats{ testFlow.Priority: {ByteSize: 50, CapacityBytes: 100}, }, }, @@ -854,7 +857,7 @@ func TestShardProcessor(t *testing.T) { { name: "should skip band on priority band accessor error", setupHarness: func(h *testHarness) { - h.PriorityBandAccessorFunc = func(uint) (framework.PriorityBandAccessor, error) { + h.PriorityBandAccessorFunc = func(int) (framework.PriorityBandAccessor, error) { return nil, registryErr } }, @@ -1003,8 +1006,8 @@ func TestShardProcessor(t *testing.T) { t.Parallel() // --- ARRANGE --- h := newTestHarness(t, testCleanupTick) - keyHigh := types.FlowKey{ID: "flow-high", Priority: 10} - keyLow := types.FlowKey{ID: "flow-low", Priority: 20} + keyHigh := types.FlowKey{ID: "flow-high", Priority: 20} + keyLow := types.FlowKey{ID: "flow-low", Priority: 10} qHigh := h.addQueue(keyHigh) qLow := h.addQueue(keyLow) @@ -1196,8 +1199,8 @@ func TestShardProcessor(t *testing.T) { t.Parallel() // --- ARRANGE --- h := newTestHarness(t, testCleanupTick) - h.AllOrderedPriorityLevelsFunc = func() []uint { return []uint{testFlow.Priority} } - h.PriorityBandAccessorFunc = func(p uint) (framework.PriorityBandAccessor, error) { + h.AllOrderedPriorityLevelsFunc = func() []int { return []int{testFlow.Priority} } + h.PriorityBandAccessorFunc = func(p int) (framework.PriorityBandAccessor, error) { return nil, errors.New("registry error") } diff --git a/pkg/epp/flowcontrol/framework/mocks/mocks.go b/pkg/epp/flowcontrol/framework/mocks/mocks.go index b8715b779..ff8441fde 100644 --- a/pkg/epp/flowcontrol/framework/mocks/mocks.go +++ b/pkg/epp/flowcontrol/framework/mocks/mocks.go @@ -67,14 +67,14 @@ var _ framework.FlowQueueAccessor = &MockFlowQueueAccessor{} // Simple accessors are configured with public value fields (e.g., `PriorityV`). // Complex methods with logic are configured with function fields (e.g., `IterateQueuesFunc`). type MockPriorityBandAccessor struct { - PriorityV uint + PriorityV int PriorityNameV string FlowKeysFunc func() []types.FlowKey QueueFunc func(flowID string) framework.FlowQueueAccessor IterateQueuesFunc func(callback func(queue framework.FlowQueueAccessor) (keepIterating bool)) } -func (m *MockPriorityBandAccessor) Priority() uint { return m.PriorityV } +func (m *MockPriorityBandAccessor) Priority() int { return m.PriorityV } func (m *MockPriorityBandAccessor) PriorityName() string { return m.PriorityNameV } func (m *MockPriorityBandAccessor) FlowKeys() []types.FlowKey { diff --git a/pkg/epp/flowcontrol/framework/policies.go b/pkg/epp/flowcontrol/framework/policies.go index 5fc3e646d..eeea034eb 100644 --- a/pkg/epp/flowcontrol/framework/policies.go +++ b/pkg/epp/flowcontrol/framework/policies.go @@ -168,7 +168,7 @@ type FlowQueueAccessor interface { // Conformance: Implementations MUST ensure all methods are goroutine-safe for concurrent access. type PriorityBandAccessor interface { // Priority returns the numerical priority level of this band. - Priority() uint + Priority() int // PriorityName returns the human-readable name of this priority band. PriorityName() string diff --git a/pkg/epp/flowcontrol/registry/config.go b/pkg/epp/flowcontrol/registry/config.go index d404e78f7..f9ad8e637 100644 --- a/pkg/epp/flowcontrol/registry/config.go +++ b/pkg/epp/flowcontrol/registry/config.go @@ -98,7 +98,7 @@ type Config struct { // the correct element within this specific configuration instance, preventing common "pointer-to-loop-variable" // errors, especially across deep copies or partitioning. // It is populated during validation and when the config is copied or partitioned. - priorityBandMap map[uint]*PriorityBandConfig + priorityBandMap map[int]*PriorityBandConfig // Factory functions used for plugin instantiation during configuration validation. // These enable dependency injection for unit testing the validation logic. @@ -112,9 +112,9 @@ type Config struct { // that operate at this priority level. type PriorityBandConfig struct { // Priority is the unique numerical priority level for this band. - // Convention: Lower numerical values indicate higher priority (e.g., 0 is highest). + // Convention: Lower numerical values indicate lower priority. // Required. - Priority uint + Priority int // PriorityName is a human-readable name for this priority band (e.g., "Critical", "Standard"). // It must be unique across all priority bands in the configuration. @@ -170,13 +170,13 @@ type ShardConfig struct { // priorityBandMap provides O(1) lookups of `ShardPriorityBandConfig` by priority level. // It serves as a correctness mechanism, ensuring that accessors return a safe, stable pointer to the correct element // within this specific shard configuration instance. - priorityBandMap map[uint]*ShardPriorityBandConfig + priorityBandMap map[int]*ShardPriorityBandConfig } // ShardPriorityBandConfig holds the partitioned configuration for a single priority band within a single shard. type ShardPriorityBandConfig struct { // Priority is the unique numerical priority level for this band. - Priority uint + Priority int // PriorityName is a unique human-readable name for this priority band. PriorityName string // IntraFlowDispatchPolicy is the name of the policy for dispatch within a flow's queue. @@ -192,7 +192,7 @@ type ShardPriorityBandConfig struct { // getBandConfig finds and returns the shard-level configuration for a specific priority level. // Returns an error wrapping `contracts.ErrPriorityBandNotFound` if the priority is not configured. -func (sc *ShardConfig) getBandConfig(priority uint) (*ShardPriorityBandConfig, error) { +func (sc *ShardConfig) getBandConfig(priority int) (*ShardPriorityBandConfig, error) { if band, ok := sc.priorityBandMap[priority]; ok { return band, nil } @@ -235,9 +235,9 @@ func (c *Config) validateAndApplyDefaults() error { } // Validate and default each priority band. - priorities := make(map[uint]struct{}) + priorities := make(map[int]struct{}) priorityNames := make(map[string]struct{}) - c.priorityBandMap = make(map[uint]*PriorityBandConfig, len(c.PriorityBands)) + c.priorityBandMap = make(map[int]*PriorityBandConfig, len(c.PriorityBands)) for i := range c.PriorityBands { band := &c.PriorityBands[i] @@ -326,7 +326,7 @@ func (c *Config) partition(shardIndex, totalShards int) *ShardConfig { shardCfg := &ShardConfig{ MaxBytes: partitionUint64(c.MaxBytes, shardIndex, totalShards), PriorityBands: make([]ShardPriorityBandConfig, len(c.PriorityBands)), - priorityBandMap: make(map[uint]*ShardPriorityBandConfig, len(c.PriorityBands)), + priorityBandMap: make(map[int]*ShardPriorityBandConfig, len(c.PriorityBands)), } for i, template := range c.PriorityBands { @@ -436,7 +436,7 @@ func (c *Config) deepCopy() *Config { FlowGCTimeout: c.FlowGCTimeout, EventChannelBufferSize: c.EventChannelBufferSize, PriorityBands: make([]PriorityBandConfig, len(c.PriorityBands)), - priorityBandMap: make(map[uint]*PriorityBandConfig, len(c.PriorityBands)), + priorityBandMap: make(map[int]*PriorityBandConfig, len(c.PriorityBands)), interFlowDispatchPolicyFactory: c.interFlowDispatchPolicyFactory, intraFlowDispatchPolicyFactory: c.intraFlowDispatchPolicyFactory, queueFactory: c.queueFactory, @@ -456,7 +456,7 @@ func (c *Config) deepCopy() *Config { // getBandConfig finds and returns the global configuration template for a specific priority level. // Returns an error wrapping `contracts.ErrPriorityBandNotFound` if the priority is not configured. -func (c *Config) getBandConfig(priority uint) (*PriorityBandConfig, error) { +func (c *Config) getBandConfig(priority int) (*PriorityBandConfig, error) { if band, ok := c.priorityBandMap[priority]; ok { return band, nil } diff --git a/pkg/epp/flowcontrol/registry/managedqueue_test.go b/pkg/epp/flowcontrol/registry/managedqueue_test.go index f5e3d7fa7..64e5ab80c 100644 --- a/pkg/epp/flowcontrol/registry/managedqueue_test.go +++ b/pkg/epp/flowcontrol/registry/managedqueue_test.go @@ -98,7 +98,7 @@ type mockStatsPropagator struct { byteSizeDelta atomic.Int64 } -func (p *mockStatsPropagator) propagate(_ uint, lenDelta, byteSizeDelta int64) { +func (p *mockStatsPropagator) propagate(_ int, lenDelta, byteSizeDelta int64) { p.lenDelta.Add(lenDelta) p.byteSizeDelta.Add(byteSizeDelta) } diff --git a/pkg/epp/flowcontrol/registry/registry.go b/pkg/epp/flowcontrol/registry/registry.go index 95d604ede..12f83e2ad 100644 --- a/pkg/epp/flowcontrol/registry/registry.go +++ b/pkg/epp/flowcontrol/registry/registry.go @@ -37,7 +37,7 @@ import ( // propagateStatsDeltaFunc defines the callback function used to propagate statistics changes (deltas) up the hierarchy // (Queue -> Shard -> Registry). // Implementations MUST be non-blocking (relying on atomics). -type propagateStatsDeltaFunc func(priority uint, lenDelta, byteSizeDelta int64) +type propagateStatsDeltaFunc func(priority int, lenDelta, byteSizeDelta int64) // bandStats holds the aggregated atomic statistics for a single priority band across all shards. type bandStats struct { @@ -120,7 +120,7 @@ type FlowRegistry struct { // Globally aggregated statistics, updated atomically via lock-free propagation. totalByteSize atomic.Int64 totalLen atomic.Int64 - perPriorityBandStats map[uint]*bandStats // Keyed by priority. + perPriorityBandStats map[int]*bandStats // Keyed by priority. // --- Administrative state (protected by `mu`) --- @@ -158,7 +158,7 @@ func NewFlowRegistry(config Config, logger logr.Logger, opts ...RegistryOption) logger: logger.WithName("flow-registry"), activeShards: []*registryShard{}, drainingShards: make(map[string]*registryShard), - perPriorityBandStats: make(map[uint]*bandStats, len(validatedConfig.PriorityBands)), + perPriorityBandStats: make(map[int]*bandStats, len(validatedConfig.PriorityBands)), } for _, opt := range opts { @@ -289,7 +289,7 @@ func (fr *FlowRegistry) Stats() contracts.AggregateStats { TotalCapacityBytes: fr.config.MaxBytes, TotalByteSize: uint64(fr.totalByteSize.Load()), TotalLen: uint64(fr.totalLen.Load()), - PerPriorityBandStats: make(map[uint]contracts.PriorityBandStats, len(fr.config.PriorityBands)), + PerPriorityBandStats: make(map[int]contracts.PriorityBandStats, len(fr.config.PriorityBands)), } for p, s := range fr.perPriorityBandStats { @@ -592,7 +592,7 @@ func (fr *FlowRegistry) updateAllShardsCacheLocked() { } // propagateStatsDelta is the top-level, lock-free aggregator for all statistics. -func (fr *FlowRegistry) propagateStatsDelta(priority uint, lenDelta, byteSizeDelta int64) { +func (fr *FlowRegistry) propagateStatsDelta(priority int, lenDelta, byteSizeDelta int64) { stats, ok := fr.perPriorityBandStats[priority] if !ok { panic(fmt.Sprintf("invariant violation: priority band (%d) stats missing during propagation", priority)) diff --git a/pkg/epp/flowcontrol/registry/registry_test.go b/pkg/epp/flowcontrol/registry/registry_test.go index 5ffa600d0..0e1bee194 100644 --- a/pkg/epp/flowcontrol/registry/registry_test.go +++ b/pkg/epp/flowcontrol/registry/registry_test.go @@ -589,7 +589,7 @@ func TestFlowRegistry_UpdateShardCount(t *testing.T) { } h := newRegistryTestHarness(t, harnessOptions{config: &config}) - key := types.FlowKey{ID: "flow", Priority: 10} + key := types.FlowKey{ID: "flow", Priority: highPriority} h.openConnectionOnFlow(key) err := h.fr.updateShardCount(tc.targetShardCount) diff --git a/pkg/epp/flowcontrol/registry/shard.go b/pkg/epp/flowcontrol/registry/shard.go index 36032e42c..c87a8e485 100644 --- a/pkg/epp/flowcontrol/registry/shard.go +++ b/pkg/epp/flowcontrol/registry/shard.go @@ -18,7 +18,7 @@ package registry import ( "fmt" - "slices" + "sort" "sync" "sync/atomic" @@ -76,7 +76,7 @@ type registryShard struct { // onStatsDelta is the callback used to propagate statistics changes up to the parent registry. onStatsDelta propagateStatsDeltaFunc // orderedPriorityLevels is a cached, sorted list of priority levels. - orderedPriorityLevels []uint + orderedPriorityLevels []int // --- State Protected by `mu` --- @@ -88,7 +88,7 @@ type registryShard struct { // config holds the partitioned configuration for this shard, derived from the `FlowRegistry`'s global `Config`. config *ShardConfig // priorityBands is the primary lookup table for all managed queues on this shard. - priorityBands map[uint]*priorityBand + priorityBands map[int]*priorityBand // --- Concurrent-Safe State (Atomics) --- @@ -116,7 +116,7 @@ func newShard( logger: shardLogger, config: config, onStatsDelta: onStatsDelta, - priorityBands: make(map[uint]*priorityBand, len(config.PriorityBands)), + priorityBands: make(map[int]*priorityBand, len(config.PriorityBands)), } for _, bandConfig := range config.PriorityBands { @@ -133,8 +133,9 @@ func newShard( } s.orderedPriorityLevels = append(s.orderedPriorityLevels, bandConfig.Priority) } - - slices.Sort(s.orderedPriorityLevels) + sort.Slice(s.orderedPriorityLevels, func(i, j int) bool { + return s.orderedPriorityLevels[i] > s.orderedPriorityLevels[j] + }) s.logger.V(logging.DEFAULT).Info("Registry shard initialized successfully", "priorityBandCount", len(s.priorityBands), "orderedPriorities", s.orderedPriorityLevels) return s, nil @@ -184,7 +185,7 @@ func (s *registryShard) IntraFlowDispatchPolicy(key types.FlowKey) (framework.In // InterFlowDispatchPolicy retrieves a priority band's configured `framework.InterFlowDispatchPolicy`. // This read is lock-free as the policy instance is immutable after the shard is initialized. -func (s *registryShard) InterFlowDispatchPolicy(priority uint) (framework.InterFlowDispatchPolicy, error) { +func (s *registryShard) InterFlowDispatchPolicy(priority int) (framework.InterFlowDispatchPolicy, error) { // This read is safe because the `priorityBands` map structure is immutable after initialization. band, ok := s.priorityBands[priority] if !ok { @@ -195,7 +196,7 @@ func (s *registryShard) InterFlowDispatchPolicy(priority uint) (framework.InterF } // PriorityBandAccessor retrieves a read-only view for a given priority level. -func (s *registryShard) PriorityBandAccessor(priority uint) (framework.PriorityBandAccessor, error) { +func (s *registryShard) PriorityBandAccessor(priority int) (framework.PriorityBandAccessor, error) { s.mu.RLock() defer s.mu.RUnlock() @@ -209,7 +210,7 @@ func (s *registryShard) PriorityBandAccessor(priority uint) (framework.PriorityB // AllOrderedPriorityLevels returns a cached, sorted slice of all configured priority levels for this shard. // This is a lock-free read. -func (s *registryShard) AllOrderedPriorityLevels() []uint { +func (s *registryShard) AllOrderedPriorityLevels() []int { return s.orderedPriorityLevels } @@ -230,7 +231,7 @@ func (s *registryShard) Stats() contracts.ShardStats { TotalCapacityBytes: s.config.MaxBytes, TotalByteSize: uint64(s.totalByteSize.Load()), TotalLen: uint64(s.totalLen.Load()), - PerPriorityBandStats: make(map[uint]contracts.PriorityBandStats, len(s.priorityBands)), + PerPriorityBandStats: make(map[int]contracts.PriorityBandStats, len(s.priorityBands)), } for priority, band := range s.priorityBands { @@ -325,7 +326,7 @@ func (s *registryShard) updateConfig(newConfig *ShardConfig) { // propagateStatsDelta is the single point of entry for all statistics changes within the shard. // It atomically updates the relevant band's stats, the shard's total stats, and propagates the delta to the parent // registry. -func (s *registryShard) propagateStatsDelta(priority uint, lenDelta, byteSizeDelta int64) { +func (s *registryShard) propagateStatsDelta(priority int, lenDelta, byteSizeDelta int64) { // This read is safe because the `priorityBands` map structure is immutable after initialization. band, ok := s.priorityBands[priority] if !ok { @@ -355,7 +356,7 @@ type priorityBandAccessor struct { var _ framework.PriorityBandAccessor = &priorityBandAccessor{} // Priority returns the numerical priority level of this band. -func (a *priorityBandAccessor) Priority() uint { return a.band.config.Priority } +func (a *priorityBandAccessor) Priority() int { return a.band.config.Priority } // PriorityName returns the human-readable name of this priority band. func (a *priorityBandAccessor) PriorityName() string { return a.band.config.PriorityName } diff --git a/pkg/epp/flowcontrol/registry/shard_test.go b/pkg/epp/flowcontrol/registry/shard_test.go index 214497e41..7fbda572e 100644 --- a/pkg/epp/flowcontrol/registry/shard_test.go +++ b/pkg/epp/flowcontrol/registry/shard_test.go @@ -37,11 +37,11 @@ import ( const ( // highPriority is the priority level for the "High" priority band in the test harness config. - highPriority uint = 10 + highPriority int = 20 // lowPriority is the priority level for the "Low" priority band in the test harness config. - lowPriority uint = 20 + lowPriority int = 10 // nonExistentPriority is a priority that is known not to exist in the test harness config. - nonExistentPriority uint = 99 + nonExistentPriority int = 99 ) // --- Test Harness and Mocks --- @@ -133,7 +133,7 @@ func TestShard_New(t *testing.T) { assert.Equal(t, "test-shard-1", h.shard.ID(), "Shard ID must match the value provided during construction") assert.True(t, h.shard.IsActive(), "A newly created shard must be initialized in the Active state") - assert.Equal(t, []uint{highPriority, lowPriority}, h.shard.AllOrderedPriorityLevels(), + assert.Equal(t, []int{highPriority, lowPriority}, h.shard.AllOrderedPriorityLevels(), "Shard must report configured priority levels sorted numerically (highest priority first)") bandHigh, ok := h.shard.priorityBands[highPriority] diff --git a/pkg/epp/flowcontrol/types/flow.go b/pkg/epp/flowcontrol/types/flow.go index 71017c13c..2af2d5bd0 100644 --- a/pkg/epp/flowcontrol/types/flow.go +++ b/pkg/epp/flowcontrol/types/flow.go @@ -41,7 +41,7 @@ type FlowKey struct { // // Because the `FlowKey` is immutable, changing the priority of traffic requires using a new `FlowKey`; the old flow // instance will be automatically garbage collected by the registry when it becomes idle. - Priority uint + Priority int } func (k FlowKey) String() string { @@ -49,13 +49,13 @@ func (k FlowKey) String() string { } // Compare provides a stable comparison function for two FlowKey instances, suitable for use with sorting algorithms. -// It returns -1 if the key is less than the other, 0 if they are equal, and 1 if the key is greater than the other. +// It returns 1 if the key is less than the other, 0 if they are equal, and -1 if the key is greater than the other. // The comparison is performed first by `Priority` (ascending, higher priority first) and then by `ID` (ascending). func (k FlowKey) Compare(other FlowKey) int { - if k.Priority < other.Priority { // Lower number means higher priority + if k.Priority > other.Priority { // Higher number means higher priority return -1 } - if k.Priority > other.Priority { + if k.Priority < other.Priority { return 1 } return strings.Compare(k.ID, other.ID) From cff5a2e9e9815b807af6a53cad6f87722ace4ef0 Mon Sep 17 00:00:00 2001 From: Jeff Luo Date: Wed, 10 Sep 2025 17:05:57 -0400 Subject: [PATCH 014/133] rename inference_model metrics to inference_objective (#1567) --- pkg/bbr/handlers/request_test.go | 2 +- pkg/epp/metrics/metrics.go | 44 ++-- pkg/epp/metrics/metrics_test.go | 18 +- pkg/epp/metrics/testdata/input_tokens_metric | 136 +++++----- ...lized_time_per_output_token_seconds_metric | 100 ++++---- pkg/epp/metrics/testdata/output_tokens_metric | 94 +++---- .../testdata/request_duration_seconds_metric | 232 +++++++++--------- .../testdata/request_error_total_metric | 10 +- pkg/epp/metrics/testdata/request_sizes_metric | 172 ++++++------- pkg/epp/metrics/testdata/request_total_metric | 10 +- .../metrics/testdata/response_sizes_metric | 112 ++++----- .../metrics/testdata/running_requests_metrics | 8 +- site-src/guides/metrics-and-observability.md | 20 +- test/e2e/epp/e2e_test.go | 16 +- test/integration/epp/hermetic_test.go | 82 +++---- tools/alerts/alert.yaml | 4 +- tools/dashboards/inference_gateway.json | 36 +-- 17 files changed, 546 insertions(+), 550 deletions(-) diff --git a/pkg/bbr/handlers/request_test.go b/pkg/bbr/handlers/request_test.go index e59795353..9e408fdef 100644 --- a/pkg/bbr/handlers/request_test.go +++ b/pkg/bbr/handlers/request_test.go @@ -206,7 +206,7 @@ func TestHandleRequestBody(t *testing.T) { bbr_success_total{} 1 ` - if err := metricsutils.GatherAndCompare(crmetrics.Registry, strings.NewReader(wantMetrics), "inference_model_request_total"); err != nil { + if err := metricsutils.GatherAndCompare(crmetrics.Registry, strings.NewReader(wantMetrics), "inference_objective_request_total"); err != nil { t.Error(err) } } diff --git a/pkg/epp/metrics/metrics.go b/pkg/epp/metrics/metrics.go index f5910099e..efe0d0491 100644 --- a/pkg/epp/metrics/metrics.go +++ b/pkg/epp/metrics/metrics.go @@ -31,27 +31,27 @@ import ( ) const ( - InferenceModelComponent = "inference_model" - InferencePoolComponent = "inference_pool" - InferenceExtension = "inference_extension" + InferenceObjectiveComponent = "inference_objective" + InferencePoolComponent = "inference_pool" + InferenceExtension = "inference_extension" ) var ( - // Inference Model Metrics + // Inference Objective Metrics requestCounter = prometheus.NewCounterVec( prometheus.CounterOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "request_total", - Help: metricsutil.HelpMsgWithStability("Counter of inference model requests broken out for each model and target model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Counter of inference objective requests broken out for each model and target model.", compbasemetrics.ALPHA), }, []string{"model_name", "target_model_name"}, ) requestErrCounter = prometheus.NewCounterVec( prometheus.CounterOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "request_error_total", - Help: metricsutil.HelpMsgWithStability("Counter of inference model requests errors broken out for each model and target model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Counter of inference objective requests errors broken out for each model and target model.", compbasemetrics.ALPHA), }, []string{"model_name", "target_model_name", "error_code"}, ) @@ -245,9 +245,9 @@ var ( requestLatencies = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "request_duration_seconds", - Help: metricsutil.HelpMsgWithStability("Inference model response latency distribution in seconds for each model and target model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Inference objective response latency distribution in seconds for each model and target model.", compbasemetrics.ALPHA), Buckets: []float64{ 0.005, 0.025, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.25, 1.5, 2, 3, 4, 5, 6, 8, 10, 15, 20, 30, 45, 60, 120, 180, 240, 300, 360, 480, 600, 900, 1200, 1800, 2700, 3600, @@ -258,9 +258,9 @@ var ( requestSizes = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "request_sizes", - Help: metricsutil.HelpMsgWithStability("Inference model requests size distribution in bytes for each model and target model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Inference objective requests size distribution in bytes for each model and target model.", compbasemetrics.ALPHA), // Use buckets ranging from 1000 bytes (1KB) to 10^9 bytes (1GB). Buckets: []float64{ 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, // More fine-grained up to 64KB @@ -273,9 +273,9 @@ var ( responseSizes = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "response_sizes", - Help: metricsutil.HelpMsgWithStability("Inference model responses size distribution in bytes for each model and target model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Inference objective responses size distribution in bytes for each model and target model.", compbasemetrics.ALPHA), // Most models have a response token < 8192 tokens. Each token, in average, has 4 characters. // 8192 * 4 = 32768. Buckets: []float64{1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32778, 65536}, @@ -285,9 +285,9 @@ var ( inputTokens = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "input_tokens", - Help: metricsutil.HelpMsgWithStability("Inference model input token count distribution for requests in each model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Inference objective input token count distribution for requests in each model.", compbasemetrics.ALPHA), // Most models have a input context window less than 1 million tokens. Buckets: []float64{1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32778, 65536, 131072, 262144, 524288, 1048576}, }, @@ -296,9 +296,9 @@ var ( outputTokens = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "output_tokens", - Help: metricsutil.HelpMsgWithStability("Inference model output token count distribution for requests in each model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Inference objective output token count distribution for requests in each model.", compbasemetrics.ALPHA), // Most models generates output less than 8192 tokens. Buckets: []float64{1, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192}, }, @@ -307,9 +307,9 @@ var ( runningRequests = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "running_requests", - Help: metricsutil.HelpMsgWithStability("Inference model number of running requests in each model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Inference objective number of running requests in each model.", compbasemetrics.ALPHA), }, []string{"model_name"}, ) @@ -317,9 +317,9 @@ var ( // NTPOT - Normalized Time Per Output Token NormalizedTimePerOutputToken = prometheus.NewHistogramVec( prometheus.HistogramOpts{ - Subsystem: InferenceModelComponent, + Subsystem: InferenceObjectiveComponent, Name: "normalized_time_per_output_token_seconds", - Help: metricsutil.HelpMsgWithStability("Inference model latency divided by number of output tokens in seconds for each model and target model.", compbasemetrics.ALPHA), + Help: metricsutil.HelpMsgWithStability("Inference objective latency divided by number of output tokens in seconds for each model and target model.", compbasemetrics.ALPHA), // From few milliseconds per token to multiple seconds per token Buckets: []float64{ 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0, diff --git a/pkg/epp/metrics/metrics_test.go b/pkg/epp/metrics/metrics_test.go index f1bb23f64..1bf76ecb8 100644 --- a/pkg/epp/metrics/metrics_test.go +++ b/pkg/epp/metrics/metrics_test.go @@ -30,15 +30,15 @@ import ( ) const ( - RequestTotalMetric = InferenceModelComponent + "_request_total" - RequestErrorTotalMetric = InferenceModelComponent + "_request_error_total" - RequestLatenciesMetric = InferenceModelComponent + "_request_duration_seconds" - RequestSizesMetric = InferenceModelComponent + "_request_sizes" - ResponseSizesMetric = InferenceModelComponent + "_response_sizes" - InputTokensMetric = InferenceModelComponent + "_input_tokens" - OutputTokensMetric = InferenceModelComponent + "_output_tokens" - NormalizedTimePerOutputTokenMetric = InferenceModelComponent + "_normalized_time_per_output_token_seconds" - RunningRequestsMetric = InferenceModelComponent + "_running_requests" + RequestTotalMetric = InferenceObjectiveComponent + "_request_total" + RequestErrorTotalMetric = InferenceObjectiveComponent + "_request_error_total" + RequestLatenciesMetric = InferenceObjectiveComponent + "_request_duration_seconds" + RequestSizesMetric = InferenceObjectiveComponent + "_request_sizes" + ResponseSizesMetric = InferenceObjectiveComponent + "_response_sizes" + InputTokensMetric = InferenceObjectiveComponent + "_input_tokens" + OutputTokensMetric = InferenceObjectiveComponent + "_output_tokens" + NormalizedTimePerOutputTokenMetric = InferenceObjectiveComponent + "_normalized_time_per_output_token_seconds" + RunningRequestsMetric = InferenceObjectiveComponent + "_running_requests" KVCacheAvgUsageMetric = InferencePoolComponent + "_average_kv_cache_utilization" QueueAvgSizeMetric = InferencePoolComponent + "_average_queue_size" PerPodQueueSizeMetrics = InferencePoolComponent + "_per_pod_queue_size" diff --git a/pkg/epp/metrics/testdata/input_tokens_metric b/pkg/epp/metrics/testdata/input_tokens_metric index 245c7dfa7..5ec493f52 100644 --- a/pkg/epp/metrics/testdata/input_tokens_metric +++ b/pkg/epp/metrics/testdata/input_tokens_metric @@ -1,68 +1,68 @@ -# HELP inference_model_input_tokens [ALPHA] Inference model input token count distribution for requests in each model. -# TYPE inference_model_input_tokens histogram -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="1"} 0 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="8"} 0 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="16"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="32"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="64"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="128"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="256"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="512"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="1024"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="16384"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="32778"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="65536"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="131072"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="262144"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="524288"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="1.048576e+06"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 -inference_model_input_tokens_sum{model_name="m10",target_model_name="t10"} 30 -inference_model_input_tokens_count{model_name="m10",target_model_name="t10"} 2 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="1"} 0 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="8"} 0 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="16"} 0 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="32"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="64"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="128"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="256"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="512"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="1024"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="2048"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="16384"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="32778"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="65536"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="131072"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="262144"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="524288"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="1.048576e+06"} 1 -inference_model_input_tokens_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 -inference_model_input_tokens_sum{model_name="m10",target_model_name="t11"} 30 -inference_model_input_tokens_count{model_name="m10",target_model_name="t11"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="1"} 0 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="8"} 0 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="16"} 0 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="32"} 0 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="64"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="128"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="256"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="512"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="16384"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="32778"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="65536"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="131072"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="262144"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="524288"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="1.048576e+06"} 1 -inference_model_input_tokens_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 -inference_model_input_tokens_sum{model_name="m20",target_model_name="t20"} 40 -inference_model_input_tokens_count{model_name="m20",target_model_name="t20"} 1 +# HELP inference_objective_input_tokens [ALPHA] Inference objective input token count distribution for requests in each model. +# TYPE inference_objective_input_tokens histogram +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="1"} 0 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="8"} 0 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="16"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="32"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="64"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="128"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="256"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="512"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="1024"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="16384"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="32778"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="65536"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="131072"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="262144"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="524288"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="1.048576e+06"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 +inference_objective_input_tokens_sum{model_name="m10",target_model_name="t10"} 30 +inference_objective_input_tokens_count{model_name="m10",target_model_name="t10"} 2 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="1"} 0 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="8"} 0 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="16"} 0 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="32"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="64"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="128"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="256"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="512"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="1024"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="2048"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="16384"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="32778"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="65536"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="131072"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="262144"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="524288"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="1.048576e+06"} 1 +inference_objective_input_tokens_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 +inference_objective_input_tokens_sum{model_name="m10",target_model_name="t11"} 30 +inference_objective_input_tokens_count{model_name="m10",target_model_name="t11"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="1"} 0 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="8"} 0 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="16"} 0 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="32"} 0 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="64"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="128"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="256"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="512"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="16384"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="32778"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="65536"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="131072"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="262144"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="524288"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="1.048576e+06"} 1 +inference_objective_input_tokens_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 +inference_objective_input_tokens_sum{model_name="m20",target_model_name="t20"} 40 +inference_objective_input_tokens_count{model_name="m20",target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/normalized_time_per_output_token_seconds_metric b/pkg/epp/metrics/testdata/normalized_time_per_output_token_seconds_metric index bb6e93737..0a9c83ea4 100644 --- a/pkg/epp/metrics/testdata/normalized_time_per_output_token_seconds_metric +++ b/pkg/epp/metrics/testdata/normalized_time_per_output_token_seconds_metric @@ -1,50 +1,50 @@ -# HELP inference_model_normalized_time_per_output_token_seconds [ALPHA] Inference model latency divided by number of output tokens in seconds for each model and target model. -# TYPE inference_model_normalized_time_per_output_token_seconds histogram -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.001"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.002"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.005"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.01"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.02"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.05"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.1"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.2"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.5"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="1.0"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="2.0"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="5.0"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="10.0"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="+Inf"} 2 -inference_model_normalized_time_per_output_token_seconds_sum{model_name="m10", target_model_name="t10"} 0.03 -inference_model_normalized_time_per_output_token_seconds_count{model_name="m10", target_model_name="t10"} 2 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.001"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.002"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.005"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.01"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.02"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.05"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.1"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.2"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.5"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="1.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="2.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="5.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="10.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="+Inf"} 1 -inference_model_normalized_time_per_output_token_seconds_sum{model_name="m10", target_model_name="t11"} 0.02 -inference_model_normalized_time_per_output_token_seconds_count{model_name="m10", target_model_name="t11"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.001"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.002"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.005"} 0 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.01"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.02"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.05"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.1"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.2"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.5"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="1.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="2.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="5.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="10.0"} 1 -inference_model_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="+Inf"} 1 -inference_model_normalized_time_per_output_token_seconds_sum{model_name="m20", target_model_name="t20"} 0.006 -inference_model_normalized_time_per_output_token_seconds_count{model_name="m20", target_model_name="t20"} 1 +# HELP inference_objective_normalized_time_per_output_token_seconds [ALPHA] Inference objective latency divided by number of output tokens in seconds for each model and target model. +# TYPE inference_objective_normalized_time_per_output_token_seconds histogram +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.001"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.002"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.005"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.01"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.02"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.05"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.1"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.2"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="0.5"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="1.0"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="2.0"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="5.0"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="10.0"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t10", le="+Inf"} 2 +inference_objective_normalized_time_per_output_token_seconds_sum{model_name="m10", target_model_name="t10"} 0.03 +inference_objective_normalized_time_per_output_token_seconds_count{model_name="m10", target_model_name="t10"} 2 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.001"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.002"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.005"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.01"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.02"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.05"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.1"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.2"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="0.5"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="1.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="2.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="5.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="10.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m10", target_model_name="t11", le="+Inf"} 1 +inference_objective_normalized_time_per_output_token_seconds_sum{model_name="m10", target_model_name="t11"} 0.02 +inference_objective_normalized_time_per_output_token_seconds_count{model_name="m10", target_model_name="t11"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.001"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.002"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.005"} 0 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.01"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.02"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.05"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.1"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.2"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="0.5"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="1.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="2.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="5.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="10.0"} 1 +inference_objective_normalized_time_per_output_token_seconds_bucket{model_name="m20", target_model_name="t20", le="+Inf"} 1 +inference_objective_normalized_time_per_output_token_seconds_sum{model_name="m20", target_model_name="t20"} 0.006 +inference_objective_normalized_time_per_output_token_seconds_count{model_name="m20", target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/output_tokens_metric b/pkg/epp/metrics/testdata/output_tokens_metric index 40bbe3272..5b71ca0a3 100644 --- a/pkg/epp/metrics/testdata/output_tokens_metric +++ b/pkg/epp/metrics/testdata/output_tokens_metric @@ -1,47 +1,47 @@ -# HELP inference_model_output_tokens [ALPHA] Inference model output token count distribution for requests in each model. -# TYPE inference_model_output_tokens histogram -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="1"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="8"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="16"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="32"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="64"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="128"} 1 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="256"} 2 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="512"} 2 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="1024"} 2 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 -inference_model_output_tokens_sum{model_name="m10",target_model_name="t10"} 300 -inference_model_output_tokens_count{model_name="m10",target_model_name="t10"} 2 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="1"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="8"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="16"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="32"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="64"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="128"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="256"} 0 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="512"} 1 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="1024"} 1 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="2048"} 1 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 -inference_model_output_tokens_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 -inference_model_output_tokens_sum{model_name="m10",target_model_name="t11"} 300 -inference_model_output_tokens_count{model_name="m10",target_model_name="t11"} 1 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="1"} 0 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="8"} 0 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="16"} 0 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="32"} 0 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="64"} 0 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="128"} 0 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="256"} 0 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="512"} 1 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 -inference_model_output_tokens_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 -inference_model_output_tokens_sum{model_name="m20",target_model_name="t20"} 400 -inference_model_output_tokens_count{model_name="m20",target_model_name="t20"} 1 +# HELP inference_objective_output_tokens [ALPHA] Inference objective output token count distribution for requests in each model. +# TYPE inference_objective_output_tokens histogram +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="1"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="8"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="16"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="32"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="64"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="128"} 1 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="256"} 2 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="512"} 2 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="1024"} 2 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 +inference_objective_output_tokens_sum{model_name="m10",target_model_name="t10"} 300 +inference_objective_output_tokens_count{model_name="m10",target_model_name="t10"} 2 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="1"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="8"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="16"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="32"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="64"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="128"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="256"} 0 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="512"} 1 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="1024"} 1 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="2048"} 1 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 +inference_objective_output_tokens_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 +inference_objective_output_tokens_sum{model_name="m10",target_model_name="t11"} 300 +inference_objective_output_tokens_count{model_name="m10",target_model_name="t11"} 1 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="1"} 0 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="8"} 0 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="16"} 0 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="32"} 0 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="64"} 0 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="128"} 0 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="256"} 0 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="512"} 1 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 +inference_objective_output_tokens_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 +inference_objective_output_tokens_sum{model_name="m20",target_model_name="t20"} 400 +inference_objective_output_tokens_count{model_name="m20",target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/request_duration_seconds_metric b/pkg/epp/metrics/testdata/request_duration_seconds_metric index 6c70b4ba9..cd6f0c061 100644 --- a/pkg/epp/metrics/testdata/request_duration_seconds_metric +++ b/pkg/epp/metrics/testdata/request_duration_seconds_metric @@ -1,116 +1,116 @@ -# HELP inference_model_request_duration_seconds [ALPHA] Inference model response latency distribution in seconds for each model and target model. -# TYPE inference_model_request_duration_seconds histogram -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.005"} 0 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.025"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.05"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.1"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.2"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.4"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.6"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.8"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1.0"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1.25"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1.5"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="2"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="3"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="4"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="5"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="6"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="8"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="10"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="15"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="20"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="30"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="45"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="60"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="120"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="180"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="240"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="300"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="360"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="480"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="600"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="900"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1200"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1800"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="2700"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="3600"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="Inf"} 2 -inference_model_request_duration_seconds_sum{model_name="m10", target_model_name="t10"} 1.61 -inference_model_request_duration_seconds_count{model_name="m10", target_model_name="t10"} 2 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.005"} 0 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.025"} 0 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.05"} 0 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.1"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.2"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.4"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.6"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.8"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1.25"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1.5"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="2"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="3"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="4"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="5"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="6"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="8"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="10"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="15"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="20"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="30"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="45"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="60"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="120"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="180"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="240"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="300"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="360"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="480"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="600"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="900"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1200"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1800"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="2700"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="3600"} 1 -inference_model_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 -inference_model_request_duration_seconds_sum{model_name="m10",target_model_name="t11"} 0.06 -inference_model_request_duration_seconds_count{model_name="m10",target_model_name="t11"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.005"} 0 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.025"} 0 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.05"} 0 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.1"} 0 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.2"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.4"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.6"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.8"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1.25"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1.5"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="2"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="3"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="4"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="5"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="6"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="8"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="10"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="15"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="20"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="30"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="45"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="60"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="120"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="180"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="240"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="300"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="360"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="480"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="600"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="900"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1200"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1800"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="2700"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="3600"} 1 -inference_model_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 -inference_model_request_duration_seconds_sum{model_name="m20",target_model_name="t20"} 0.12 -inference_model_request_duration_seconds_count{model_name="m20",target_model_name="t20"} 1 +# HELP inference_objective_request_duration_seconds [ALPHA] Inference objective response latency distribution in seconds for each model and target model. +# TYPE inference_objective_request_duration_seconds histogram +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.005"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.025"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.05"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.1"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.2"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.4"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.6"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="0.8"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1.0"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1.25"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1.5"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="2"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="3"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="4"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="5"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="6"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="8"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="10"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="15"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="20"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="30"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="45"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="60"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="120"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="180"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="240"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="300"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="360"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="480"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="600"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="900"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1200"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="1800"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="2700"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="3600"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10", target_model_name="t10", le="Inf"} 2 +inference_objective_request_duration_seconds_sum{model_name="m10", target_model_name="t10"} 1.61 +inference_objective_request_duration_seconds_count{model_name="m10", target_model_name="t10"} 2 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.005"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.025"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.05"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.1"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.2"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.4"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.6"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="0.8"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1.25"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1.5"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="2"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="3"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="4"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="5"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="6"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="8"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="10"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="15"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="20"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="30"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="45"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="60"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="120"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="180"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="240"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="300"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="360"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="480"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="600"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="900"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1200"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="1800"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="2700"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="3600"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 +inference_objective_request_duration_seconds_sum{model_name="m10",target_model_name="t11"} 0.06 +inference_objective_request_duration_seconds_count{model_name="m10",target_model_name="t11"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.005"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.025"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.05"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.1"} 0 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.2"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.4"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.6"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="0.8"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1.25"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1.5"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="2"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="3"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="4"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="5"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="6"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="8"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="10"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="15"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="20"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="30"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="45"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="60"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="120"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="180"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="240"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="300"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="360"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="480"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="600"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="900"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1200"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="1800"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="2700"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="3600"} 1 +inference_objective_request_duration_seconds_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 +inference_objective_request_duration_seconds_sum{model_name="m20",target_model_name="t20"} 0.12 +inference_objective_request_duration_seconds_count{model_name="m20",target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/request_error_total_metric b/pkg/epp/metrics/testdata/request_error_total_metric index 31036eb60..2a2e55364 100644 --- a/pkg/epp/metrics/testdata/request_error_total_metric +++ b/pkg/epp/metrics/testdata/request_error_total_metric @@ -1,5 +1,5 @@ -# HELP inference_model_request_error_total [ALPHA] Counter of inference model requests errors broken out for each model and target model. -# TYPE inference_model_request_error_total counter -inference_model_request_error_total{error_code="Internal", model_name="m10",target_model_name="t10"} 2 -inference_model_request_error_total{error_code="ModelServerError", model_name="m10",target_model_name="t11"} 1 -inference_model_request_error_total{error_code="InferencePoolResourceExhausted", model_name="m20",target_model_name="t20"} 1 +# HELP inference_objective_request_error_total [ALPHA] Counter of inference objective requests errors broken out for each model and target model. +# TYPE inference_objective_request_error_total counter +inference_objective_request_error_total{error_code="Internal", model_name="m10",target_model_name="t10"} 2 +inference_objective_request_error_total{error_code="ModelServerError", model_name="m10",target_model_name="t11"} 1 +inference_objective_request_error_total{error_code="InferencePoolResourceExhausted", model_name="m20",target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/request_sizes_metric b/pkg/epp/metrics/testdata/request_sizes_metric index ceca532e2..74e672591 100644 --- a/pkg/epp/metrics/testdata/request_sizes_metric +++ b/pkg/epp/metrics/testdata/request_sizes_metric @@ -1,86 +1,86 @@ -# HELP inference_model_request_sizes [ALPHA] Inference model requests size distribution in bytes for each model and target model. -# TYPE inference_model_request_sizes histogram -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="64"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="128"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="256"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="512"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1024"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="16384"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="32768"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="65536"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="131072"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="262144"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="524288"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.048576e+06"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="2.097152e+06"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="4.194304e+06"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="8.388608e+06"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.6777216e+07"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="3.3554432e+07"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="6.7108864e+07"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.34217728e+08"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="2.68435456e+08"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="5.36870912e+08"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.073741824e+09"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 -inference_model_request_sizes_sum{model_name="m10",target_model_name="t10"} 1700 -inference_model_request_sizes_count{model_name="m10",target_model_name="t10"} 2 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="64"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="128"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="256"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="512"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1024"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="2048"} 0 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="16384"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="32768"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="65536"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="131072"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="262144"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="524288"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.048576e+06"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="2.097152e+06"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="4.194304e+06"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="8.388608e+06"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.6777216e+07"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="3.3554432e+07"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="6.7108864e+07"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.34217728e+08"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="2.68435456e+08"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="5.36870912e+08"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.073741824e+09"} 1 -inference_model_request_sizes_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 -inference_model_request_sizes_sum{model_name="m10",target_model_name="t11"} 2480 -inference_model_request_sizes_count{model_name="m10",target_model_name="t11"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="64"} 0 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="128"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="256"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="512"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="16384"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="32768"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="65536"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="131072"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="262144"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="524288"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.048576e+06"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="2.097152e+06"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="4.194304e+06"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="8.388608e+06"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.6777216e+07"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="3.3554432e+07"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="6.7108864e+07"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.34217728e+08"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="2.68435456e+08"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="5.36870912e+08"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.073741824e+09"} 1 -inference_model_request_sizes_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 -inference_model_request_sizes_sum{model_name="m20",target_model_name="t20"} 80 -inference_model_request_sizes_count{model_name="m20",target_model_name="t20"} 1 +# HELP inference_objective_request_sizes [ALPHA] Inference objective requests size distribution in bytes for each model and target model. +# TYPE inference_objective_request_sizes histogram +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="64"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="128"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="256"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="512"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1024"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="16384"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="32768"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="65536"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="131072"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="262144"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="524288"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.048576e+06"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="2.097152e+06"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="4.194304e+06"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="8.388608e+06"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.6777216e+07"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="3.3554432e+07"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="6.7108864e+07"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.34217728e+08"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="2.68435456e+08"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="5.36870912e+08"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="1.073741824e+09"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 +inference_objective_request_sizes_sum{model_name="m10",target_model_name="t10"} 1700 +inference_objective_request_sizes_count{model_name="m10",target_model_name="t10"} 2 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="64"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="128"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="256"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="512"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1024"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="2048"} 0 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="16384"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="32768"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="65536"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="131072"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="262144"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="524288"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.048576e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="2.097152e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="4.194304e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="8.388608e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.6777216e+07"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="3.3554432e+07"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="6.7108864e+07"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.34217728e+08"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="2.68435456e+08"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="5.36870912e+08"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="1.073741824e+09"} 1 +inference_objective_request_sizes_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 +inference_objective_request_sizes_sum{model_name="m10",target_model_name="t11"} 2480 +inference_objective_request_sizes_count{model_name="m10",target_model_name="t11"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="64"} 0 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="128"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="256"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="512"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="16384"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="32768"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="65536"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="131072"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="262144"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="524288"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.048576e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="2.097152e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="4.194304e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="8.388608e+06"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.6777216e+07"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="3.3554432e+07"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="6.7108864e+07"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.34217728e+08"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="2.68435456e+08"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="5.36870912e+08"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="1.073741824e+09"} 1 +inference_objective_request_sizes_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 +inference_objective_request_sizes_sum{model_name="m20",target_model_name="t20"} 80 +inference_objective_request_sizes_count{model_name="m20",target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/request_total_metric b/pkg/epp/metrics/testdata/request_total_metric index 9c6f48a36..a6200fdc9 100644 --- a/pkg/epp/metrics/testdata/request_total_metric +++ b/pkg/epp/metrics/testdata/request_total_metric @@ -1,5 +1,5 @@ -# HELP inference_model_request_total [ALPHA] Counter of inference model requests broken out for each model and target model. -# TYPE inference_model_request_total counter -inference_model_request_total{model_name="m10", target_model_name="t10"} 2 -inference_model_request_total{model_name="m10", target_model_name="t11"} 1 -inference_model_request_total{model_name="m20", target_model_name="t20"} 1 +# HELP inference_objective_request_total [ALPHA] Counter of inference objective requests broken out for each model and target model. +# TYPE inference_objective_request_total counter +inference_objective_request_total{model_name="m10", target_model_name="t10"} 2 +inference_objective_request_total{model_name="m10", target_model_name="t11"} 1 +inference_objective_request_total{model_name="m20", target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/response_sizes_metric b/pkg/epp/metrics/testdata/response_sizes_metric index 7f981090c..a9ad76ecb 100644 --- a/pkg/epp/metrics/testdata/response_sizes_metric +++ b/pkg/epp/metrics/testdata/response_sizes_metric @@ -1,56 +1,56 @@ -# HELP inference_model_response_sizes [ALPHA] Inference model responses size distribution in bytes for each model and target model. -# TYPE inference_model_response_sizes histogram -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="1"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="8"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="16"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="32"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="64"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="128"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="256"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="512"} 1 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="1024"} 1 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="16384"} 2 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="32778"} 2 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="65536"} 2 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 -inference_model_response_sizes_sum{model_name="m10",target_model_name="t10"} 1700 -inference_model_response_sizes_count{model_name="m10",target_model_name="t10"} 2 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="1"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="8"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="16"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="32"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="64"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="128"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="256"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="512"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="1024"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="2048"} 0 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="16384"} 1 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="32778"} 1 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="65536"} 1 -inference_model_response_sizes_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 -inference_model_response_sizes_sum{model_name="m10",target_model_name="t11"} 2480 -inference_model_response_sizes_count{model_name="m10",target_model_name="t11"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="1"} 0 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="8"} 0 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="16"} 0 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="32"} 0 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="64"} 0 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="128"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="256"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="512"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="16384"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="32778"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="65536"} 1 -inference_model_response_sizes_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 -inference_model_response_sizes_sum{model_name="m20",target_model_name="t20"} 80 -inference_model_response_sizes_count{model_name="m20",target_model_name="t20"} 1 +# HELP inference_objective_response_sizes [ALPHA] Inference objective responses size distribution in bytes for each model and target model. +# TYPE inference_objective_response_sizes histogram +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="1"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="8"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="16"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="32"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="64"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="128"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="256"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="512"} 1 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="1024"} 1 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="2048"} 2 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="4096"} 2 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="8192"} 2 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="16384"} 2 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="32778"} 2 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="65536"} 2 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t10",le="+Inf"} 2 +inference_objective_response_sizes_sum{model_name="m10",target_model_name="t10"} 1700 +inference_objective_response_sizes_count{model_name="m10",target_model_name="t10"} 2 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="1"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="8"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="16"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="32"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="64"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="128"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="256"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="512"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="1024"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="2048"} 0 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="4096"} 1 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="8192"} 1 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="16384"} 1 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="32778"} 1 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="65536"} 1 +inference_objective_response_sizes_bucket{model_name="m10",target_model_name="t11",le="+Inf"} 1 +inference_objective_response_sizes_sum{model_name="m10",target_model_name="t11"} 2480 +inference_objective_response_sizes_count{model_name="m10",target_model_name="t11"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="1"} 0 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="8"} 0 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="16"} 0 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="32"} 0 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="64"} 0 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="128"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="256"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="512"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="1024"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="2048"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="4096"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="8192"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="16384"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="32778"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="65536"} 1 +inference_objective_response_sizes_bucket{model_name="m20",target_model_name="t20",le="+Inf"} 1 +inference_objective_response_sizes_sum{model_name="m20",target_model_name="t20"} 80 +inference_objective_response_sizes_count{model_name="m20",target_model_name="t20"} 1 diff --git a/pkg/epp/metrics/testdata/running_requests_metrics b/pkg/epp/metrics/testdata/running_requests_metrics index a880e4998..962a50fbf 100644 --- a/pkg/epp/metrics/testdata/running_requests_metrics +++ b/pkg/epp/metrics/testdata/running_requests_metrics @@ -1,4 +1,4 @@ -# HELP inference_model_running_requests [ALPHA] Inference model number of running requests in each model. -# TYPE inference_model_running_requests gauge -inference_model_running_requests{model_name="m1"} 1 -inference_model_running_requests{model_name="m2"} 1 +# HELP inference_objective_running_requests [ALPHA] Inference objective number of running requests in each model. +# TYPE inference_objective_running_requests gauge +inference_objective_running_requests{model_name="m1"} 1 +inference_objective_running_requests{model_name="m2"} 1 diff --git a/site-src/guides/metrics-and-observability.md b/site-src/guides/metrics-and-observability.md index 34b0db100..5e1f02e49 100644 --- a/site-src/guides/metrics-and-observability.md +++ b/site-src/guides/metrics-and-observability.md @@ -32,15 +32,15 @@ This guide describes the current state of exposed metrics and how to scrape them | **Metric name** | **Metric Type** |
**Description**
|
**Labels**
| **Status** | |:---------------------------------------------|:-----------------|:------------------------------------------------------------------|:-----------------------------------------------------------------------------------|:------------| -| inference_model_request_total | Counter | The counter of requests broken out for each model. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | -| inference_model_request_error_total | Counter | The counter of requests errors broken out for each model. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | -| inference_model_request_duration_seconds | Distribution | Distribution of response latency. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | +| inference_objective_request_total | Counter | The counter of requests broken out for each model. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | +| inference_objective_request_error_total | Counter | The counter of requests errors broken out for each model. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | +| inference_objective_request_duration_seconds | Distribution | Distribution of response latency. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | | normalized_time_per_output_token_seconds | Distribution | Distribution of ntpot (response latency per output token) | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | -| inference_model_request_sizes | Distribution | Distribution of request size in bytes. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | -| inference_model_response_sizes | Distribution | Distribution of response size in bytes. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | -| inference_model_input_tokens | Distribution | Distribution of input token count. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | -| inference_model_output_tokens | Distribution | Distribution of output token count. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | -| inference_model_running_requests | Gauge | Number of running requests for each model. | `model_name`=<model-name> | ALPHA | +| inference_objective_request_sizes | Distribution | Distribution of request size in bytes. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | +| inference_objective_response_sizes | Distribution | Distribution of response size in bytes. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | +| inference_objective_input_tokens | Distribution | Distribution of input token count. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | +| inference_objective_output_tokens | Distribution | Distribution of output token count. | `model_name`=<model-name>
`target_model_name`=<target-model-name> | ALPHA | +| inference_objective_running_requests | Gauge | Number of running requests for each model. | `model_name`=<model-name> | ALPHA | | inference_pool_average_kv_cache_utilization | Gauge | The average kv cache utilization for an inference server pool. | `name`=<inference-pool-name> | ALPHA | | inference_pool_average_queue_size | Gauge | The average number of requests pending in the model server queue. | `name`=<inference-pool-name> | ALPHA | | inference_pool_per_pod_queue_size | Gauge | The total number of queue for each model server pod under the inference pool | `model_server_pod`=<model-server-pod-name>
`name`=<inference-pool-name> | ALPHA | @@ -216,7 +216,7 @@ A template alert rule is available at [alert.yaml](../../tools/alerts/alert.yaml ```yaml alert: HighInferenceRequestLatencyP99 -expr: histogram_quantile(0.99, rate(inference_model_request_duration_seconds_bucket[5m])) > 10.0 # Adjust threshold as needed (e.g., 10.0 seconds) +expr: histogram_quantile(0.99, rate(inference_objective_request_duration_seconds_bucket[5m])) > 10.0 # Adjust threshold as needed (e.g., 10.0 seconds) for: 5m annotations: title: 'High latency (P99) for model {% raw %}{{ $labels.model_name }}{% endraw %}' @@ -229,7 +229,7 @@ labels: ```yaml alert: HighInferenceErrorRate -expr: sum by (model_name) (rate(inference_model_request_error_total[5m])) / sum by (model_name) (rate(inference_model_request_total[5m])) > 0.05 # Adjust threshold as needed (e.g., 5% error rate) +expr: sum by (model_name) (rate(inference_objective_request_error_total[5m])) / sum by (model_name) (rate(inference_objective_request_total[5m])) > 0.05 # Adjust threshold as needed (e.g., 5% error rate) for: 5m annotations: title: 'High error rate for model {% raw %}{{ $labels.model_name }}{% endraw %}' diff --git a/test/e2e/epp/e2e_test.go b/test/e2e/epp/e2e_test.go index e01240d8c..c8410bd59 100644 --- a/test/e2e/epp/e2e_test.go +++ b/test/e2e/epp/e2e_test.go @@ -241,19 +241,19 @@ func verifyMetrics() { ginkgo.By("Verifying metrics exposure") // Define the metrics we expect to see expectedMetrics := []string{ - "inference_model_request_total", - "inference_model_request_error_total", - "inference_model_request_duration_seconds", + "inference_objective_request_total", + "inference_objective_request_error_total", + "inference_objective_request_duration_seconds", // TODO: normalized_time_per_output_token_seconds is not actually recorded yet // "normalized_time_per_output_token_seconds", - "inference_model_request_sizes", - "inference_model_response_sizes", - "inference_model_input_tokens", - "inference_model_output_tokens", + "inference_objective_request_sizes", + "inference_objective_response_sizes", + "inference_objective_input_tokens", + "inference_objective_output_tokens", "inference_pool_average_kv_cache_utilization", "inference_pool_average_queue_size", "inference_pool_per_pod_queue_size", - "inference_model_running_requests", + "inference_objective_running_requests", "inference_pool_ready_pods", "inference_extension_info", } diff --git a/test/integration/epp/hermetic_test.go b/test/integration/epp/hermetic_test.go index a215adcf5..3dc42f8ba 100644 --- a/test/integration/epp/hermetic_test.go +++ b/test/integration/epp/hermetic_test.go @@ -133,11 +133,10 @@ func labelsToString(labels []label) string { func inferenceObjectiveRequestTotal(labels []label) string { return fmt.Sprintf(` - # HELP inference_model_request_total [ALPHA] Counter of inference model requests broken out for each model and target model. - # TYPE inference_model_request_total counter - inference_model_request_total{%s} 1 - `, labelsToString(labels), - ) + # HELP inference_objective_request_total [ALPHA] Counter of inference objective requests broken out for each model and target model. + # TYPE inference_objective_request_total counter + inference_objective_request_total{%s} 1 + `, labelsToString(labels)) } func inferencePoolReadyPods(v int, labels []label) string { @@ -145,8 +144,7 @@ func inferencePoolReadyPods(v int, labels []label) string { # HELP inference_pool_ready_pods [ALPHA] The number of ready pods in the inference server pool. # TYPE inference_pool_ready_pods gauge inference_pool_ready_pods{%s} %d - `, labelsToString(labels), v, - ) + `, labelsToString(labels), v) } func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { @@ -170,7 +168,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { podState{index: 2, queueSize: 10, kvCacheUsage: 0.2}, ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelMyModel}, {"target_model_name", modelMyModelTarget}, }), @@ -242,7 +240,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelSQLLora}, {"target_model_name", modelSQLLoraTarget}, }), @@ -277,7 +275,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { podState{index: 2, queueSize: 10, kvCacheUsage: 0.3, activeModels: []string{"foo"}}, ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelSQLLora}, {"target_model_name", modelSQLLoraTarget}, }), @@ -312,7 +310,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { podState{index: 2, queueSize: 10, kvCacheUsage: 0.9, activeModels: []string{"foo"}}, ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelSQLLora}, {"target_model_name", modelSQLLoraTarget}, }), @@ -363,8 +361,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { }, }, }, - }, - { + }, { Request: &extProcPb.ProcessingRequest_RequestBody{ RequestBody: &extProcPb.HttpBody{Body: []byte("{\"max_tokens\":100,\"model\":\"sql-lo"), EndOfStream: false}, }, @@ -382,7 +379,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { podState{index: 2, queueSize: 10, kvCacheUsage: 0.9, activeModels: []string{"foo", modelSheddableTarget}}, ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelSheddable}, {"target_model_name", modelSheddableTarget}, }), @@ -456,7 +453,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { podState{index: 2, queueSize: 10, kvCacheUsage: 0.9, activeModels: []string{"foo", modelSheddableTarget}}, ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelDirect}, {"target_model_name", modelDirect}, }), @@ -705,32 +702,31 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { }, }, }, - wantErr: false, - wantMetrics: map[string]string{`inference_model_input_tokens`: ` - # HELP inference_model_input_tokens [ALPHA] Inference model input token count distribution for requests in each model. - # TYPE inference_model_input_tokens histogram - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="1"} 0 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="8"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="16"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="32"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="64"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="128"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="256"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="512"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="1024"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="2048"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="4096"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="8192"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="16384"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="32778"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="65536"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="131072"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="262144"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="524288"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="1.048576e+06"} 1 - inference_model_input_tokens_bucket{model_name="",target_model_name="",le="+Inf"} 1 - inference_model_input_tokens_sum{model_name="",target_model_name=""} 7 - inference_model_input_tokens_count{model_name="",target_model_name=""} 1 + wantMetrics: map[string]string{`inference_objective_input_tokens`: ` + # HELP inference_objective_input_tokens [ALPHA] Inference objective input token count distribution for requests in each model. + # TYPE inference_objective_input_tokens histogram + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="1"} 0 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="8"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="16"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="32"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="64"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="128"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="256"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="512"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="1024"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="2048"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="4096"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="8192"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="16384"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="32778"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="65536"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="131072"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="262144"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="524288"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="1.048576e+06"} 1 + inference_objective_input_tokens_bucket{model_name="",target_model_name="",le="+Inf"} 1 + inference_objective_input_tokens_sum{model_name="",target_model_name=""} 7 + inference_objective_input_tokens_count{model_name="",target_model_name=""} 1 `}, wantResponses: []*extProcPb.ProcessingResponse{ integrationutils.NewResponseHeaders( @@ -808,7 +804,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelSQLLora}, {"target_model_name", modelSQLLoraTarget}, }), @@ -847,7 +843,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) { ), wantMetrics: map[string]string{ - "inference_model_request_total": inferenceObjectiveRequestTotal([]label{ + "inference_objective_request_total": inferenceObjectiveRequestTotal([]label{ {"model_name", modelSQLLora}, {"target_model_name", modelSQLLoraTarget}, }), diff --git a/tools/alerts/alert.yaml b/tools/alerts/alert.yaml index c712207a4..687123feb 100644 --- a/tools/alerts/alert.yaml +++ b/tools/alerts/alert.yaml @@ -5,7 +5,7 @@ groups: annotations: title: 'High latency (P99) for model {{ $labels.model_name }}' description: 'The 99th percentile request duration for model {{ $labels.model_name }} and target model {{ $labels.target_model_name }} has been consistently above 10.0 seconds for 5 minutes.' - expr: histogram_quantile(0.99, rate(inference_model_request_duration_seconds_bucket[5m])) > 10.0 + expr: histogram_quantile(0.99, rate(inference_objective_request_duration_seconds_bucket[5m])) > 10.0 for: 5m labels: severity: 'warning' @@ -13,7 +13,7 @@ groups: annotations: title: 'High error rate for model {{ $labels.model_name }}' description: 'The error rate for model {{ $labels.model_name }} and target model {{ $labels.target_model_name }} has been consistently above 5% for 5 minutes.' - expr: sum by (model_name) (rate(inference_model_request_error_total[5m])) / sum by (model_name) (rate(inference_model_request_total[5m])) > 0.05 + expr: sum by (model_name) (rate(inference_objective_request_error_total[5m])) / sum by (model_name) (rate(inference_objective_request_total[5m])) > 0.05 for: 5m labels: severity: 'critical' diff --git a/tools/dashboards/inference_gateway.json b/tools/dashboards/inference_gateway.json index 244f4ab14..50e4eb152 100644 --- a/tools/dashboards/inference_gateway.json +++ b/tools/dashboards/inference_gateway.json @@ -443,7 +443,7 @@ { "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_model_request_duration_seconds_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_objective_request_duration_seconds_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "includeNullMetadata": false, "legendFormat": "95%", @@ -458,7 +458,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_model_request_duration_seconds_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_objective_request_duration_seconds_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -474,7 +474,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_model_request_duration_seconds_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_objective_request_duration_seconds_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -576,7 +576,7 @@ "disableTextWrap": false, "editorMode": "builder", "exemplar": false, - "expr": "sum by(model_name, target_model_name) (rate(inference_model_request_total{}[$__rate_interval]))", + "expr": "sum by(model_name, target_model_name) (rate(inference_objective_request_total{}[$__rate_interval]))", "fullMetaSearch": false, "includeNullMetadata": true, "interval": "", @@ -678,7 +678,7 @@ "disableTextWrap": false, "editorMode": "builder", "exemplar": false, - "expr": "sum by(error_code, model_name, target_model_name) (rate(inference_model_request_error_total[$__rate_interval]))", + "expr": "sum by(error_code, model_name, target_model_name) (rate(inference_objective_request_error_total[$__rate_interval]))", "fullMetaSearch": false, "includeNullMetadata": true, "interval": "", @@ -775,7 +775,7 @@ { "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_model_request_sizes_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_objective_request_sizes_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "includeNullMetadata": false, "legendFormat": "95%", @@ -790,7 +790,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_model_request_sizes_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_objective_request_sizes_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -806,7 +806,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_model_request_sizes_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_objective_request_sizes_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -903,7 +903,7 @@ { "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_model_response_sizes_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_objective_response_sizes_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "includeNullMetadata": false, "legendFormat": "95%", @@ -918,7 +918,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_model_response_sizes_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_objective_response_sizes_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -934,7 +934,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_model_response_sizes_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_objective_response_sizes_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -1031,7 +1031,7 @@ { "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_model_input_tokens_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_objective_input_tokens_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "includeNullMetadata": false, "legendFormat": "95%", @@ -1046,7 +1046,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_model_input_tokens_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_objective_input_tokens_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -1062,7 +1062,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_model_input_tokens_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_objective_input_tokens_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -1159,7 +1159,7 @@ { "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_model_output_tokens_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.95, sum by(le) (rate(inference_objective_output_tokens_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "includeNullMetadata": false, "legendFormat": "95%", @@ -1174,7 +1174,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_model_output_tokens_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.9, sum by(le) (rate(inference_objective_output_tokens_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -1190,7 +1190,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_model_output_tokens_bucket{}[$__rate_interval])))", + "expr": "histogram_quantile(0.5, sum by(le) (rate(inference_objective_output_tokens_bucket{}[$__rate_interval])))", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -1204,7 +1204,7 @@ "type": "timeseries" } ], - "title": "Inference Model", + "title": "Inference Objective", "type": "row" }, { From fdecb8af1fc2d7d9e8e1791e2d571fac33ddbf53 Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Thu, 11 Sep 2025 17:04:10 +0300 Subject: [PATCH 015/133] added github action to add a hold label when PRs are pushed to branch other than main (#1570) Signed-off-by: Nir Rozenbaum --- .github/workflows/non-main-gatekeeper.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/non-main-gatekeeper.yml diff --git a/.github/workflows/non-main-gatekeeper.yml b/.github/workflows/non-main-gatekeeper.yml new file mode 100644 index 000000000..af91ea966 --- /dev/null +++ b/.github/workflows/non-main-gatekeeper.yml @@ -0,0 +1,18 @@ +name: Label non-main PRs + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + add-label: + runs-on: ubuntu-latest + steps: + - name: Add labels when base branch is not main + if: github.event.pull_request.base.ref != 'main' + uses: actions-ecosystem/action-add-labels@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: | + do-not-merge/hold + do-not-merge/cherry-pick-not-approved From 9f37bfae0524c18ad33aaa2be76f2820873bf25b Mon Sep 17 00:00:00 2001 From: Maroon Ayoub Date: Fri, 12 Sep 2025 00:02:08 +0300 Subject: [PATCH 016/133] Refactor LLMRequest: Structured RequestData for Completions & Chat-Completions (#1446) * - added more useful fields to types.LLMRequest: 1. cleaner API declaration 2. data fields are preserved, after-read transformations are done in plugins 3. prefix-cache scorer does not need naive templating - minor bugfixes and improvements Signed-off-by: Maroon Ayoub * removed LLMRequestData::String Signed-off-by: Maroon Ayoub * - rename LLMRequestData to LLMRequestBody - rename LLMRequest.Data to LLMRequest.Body - test refactoring after rebase Signed-off-by: Maroon Ayoub --------- Signed-off-by: Maroon Ayoub --- pkg/epp/requestcontrol/director.go | 7 +- .../framework/plugins/multi/prefix/plugin.go | 66 ++-- .../plugins/multi/prefix/plugin_test.go | 257 ++++++++++++++- pkg/epp/scheduling/types/types.go | 79 ++++- pkg/epp/util/request/body.go | 79 ++--- pkg/epp/util/request/body_test.go | 296 ++++++++++++------ 6 files changed, 590 insertions(+), 194 deletions(-) diff --git a/pkg/epp/requestcontrol/director.go b/pkg/epp/requestcontrol/director.go index 662491bdb..305031ecb 100644 --- a/pkg/epp/requestcontrol/director.go +++ b/pkg/epp/requestcontrol/director.go @@ -206,10 +206,11 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo } reqCtx.Request.Body["model"] = reqCtx.TargetModelName - prompt, err := requtil.ExtractPromptFromRequestBody(requestBodyMap) + requestBody, err := requtil.ExtractRequestBody(reqCtx.Request.Body) if err != nil { - return reqCtx, err + return reqCtx, errutil.Error{Code: errutil.BadRequest, Msg: fmt.Errorf("failed to extract request data: %w", err).Error()} } + infObjective := d.datastore.ObjectiveGet(reqCtx.ObjectiveKey) if infObjective == nil { logger.V(logutil.VERBOSE).Info("No associated InferenceObjective found, using default", "objectiveKey", reqCtx.ObjectiveKey) @@ -247,7 +248,7 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo reqCtx.SchedulingRequest = &schedulingtypes.LLMRequest{ RequestId: reqCtx.Request.Headers[requtil.RequestIdHeaderKey], TargetModel: reqCtx.TargetModelName, - Prompt: prompt, + Body: requestBody, Headers: reqCtx.Request.Headers, TTFTSLO: ttftSLO, AvgTPOTSLO: avgTPOTSLO, diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go index c87f8e8bf..8e594400f 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go @@ -130,8 +130,10 @@ func (s *SchedulingContextState) Clone() plugins.StateData { } // compile-time type assertion -var _ framework.Scorer = &Plugin{} -var _ requestcontrol.PreRequest = &Plugin{} +var ( + _ framework.Scorer = &Plugin{} + _ requestcontrol.PreRequest = &Plugin{} +) // PrefixCachePluginFactory defines the factory function for Prefix plugin. func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, handle plugins.Handle) (plugins.Plugin, error) { @@ -254,7 +256,6 @@ func (p *Plugin) matchLongestPrefix(ctx context.Context, hashes []BlockHash) map for server := range cachedServers { // Update servers with their longest prefix match. res[server]++ - } } } @@ -266,33 +267,39 @@ func (p *Plugin) matchLongestPrefix(ctx context.Context, hashes []BlockHash) map // For block i, hash(i) = hash(block i content, hash(i-1)). func hashPrompt(ctx context.Context, request *types.LLMRequest, cacheBlockSize int, maxPrefixBlocks int) []BlockHash { loggerDebug := log.FromContext(ctx).V(logutil.DEBUG) - prompt := []byte(request.Prompt) - if len(prompt) < cacheBlockSize { - loggerDebug.Info("Request body too small for prefix cache", "size", len(prompt), "block size", cacheBlockSize) + if request == nil || request.Body == nil { + loggerDebug.Info("Request or request data is nil, skipping hashing") return nil } - if len(prompt) > cacheBlockSize*maxPrefixBlocks { - loggerDebug.Info("Truncating input", "size", len(prompt), "max prefix blocks", maxPrefixBlocks, "block size", cacheBlockSize) - prompt = prompt[:maxPrefixBlocks*cacheBlockSize] + + userInput, err := getUserInputBytes(request) + if err != nil { + loggerDebug.Error(err, "Failed to get user input bytes") + return nil } - // Split the body into blocks of size cacheBlockSize. The +1 is to account for the model. - // If the last block is smaller than cacheBlockSize, it will be ignored. - res := make([]BlockHash, 0, 1+len(prompt)/cacheBlockSize) - // Add the model to the first block hash so that different models have different hashes even with the same body. - firstBlockSize := cacheBlockSize - if len(prompt) < cacheBlockSize { - firstBlockSize = len(prompt) + if len(userInput) < cacheBlockSize { + loggerDebug.Info("Request body too small for prefix cache", "size", len(userInput), "block size", cacheBlockSize) + return nil } - firstBlock := prompt[0:firstBlockSize] - firstBlockWithModel := append([]byte(request.TargetModel), firstBlock...) - res = append(res, BlockHash(xxhash.Sum64(firstBlockWithModel))) - - for i := cacheBlockSize; i+cacheBlockSize <= len(prompt); i += cacheBlockSize { - block := prompt[i : i+cacheBlockSize] - prevBlockHash := res[len(res)-1] - block = append(block, toBytes(prevBlockHash)...) - res = append(res, BlockHash(xxhash.Sum64(block))) + if len(userInput) > cacheBlockSize*maxPrefixBlocks { + loggerDebug.Info("Truncating input", "size", len(userInput), "max prefix blocks", maxPrefixBlocks, "block size", cacheBlockSize) + userInput = userInput[:maxPrefixBlocks*cacheBlockSize] + } + // Split the body into blocks of size cacheBlockSize. + // If the last block is smaller than cacheBlockSize, it will be ignored. + res := make([]BlockHash, 0, len(userInput)/cacheBlockSize) + // Add the model to the first block hash so that different models have different hashes even with the same body. + h := xxhash.New() + _, _ = h.Write([]byte(request.TargetModel)) + prevBlockHash := BlockHash(h.Sum64()) + for i := 0; i+cacheBlockSize <= len(userInput); i += cacheBlockSize { + h.Reset() + _, _ = h.Write(userInput[i : i+cacheBlockSize]) + _, _ = h.Write(toBytes(prevBlockHash)) + res = append(res, BlockHash(h.Sum64())) + + prevBlockHash = res[len(res)-1] } return res } @@ -302,3 +309,12 @@ func toBytes(i BlockHash) []byte { binary.LittleEndian.PutUint64(bytes, uint64(i)) return bytes } + +func getUserInputBytes(request *types.LLMRequest) ([]byte, error) { + if request.Body.Completions != nil { // assumed to be valid if not nil + return []byte(request.Body.Completions.Prompt), nil + } + + // must be chat-completions request at this point, return bytes of entire messages + return json.Marshal(request.Body.ChatCompletions.Messages) +} diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go index 3fbac2ce1..9f9893ba8 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go @@ -33,8 +33,7 @@ import ( "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" ) -func TestPrefixPlugin(t *testing.T) { - +func TestPrefixPluginCompletion(t *testing.T) { config := Config{ HashBlockSize: 4, MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, @@ -50,7 +49,11 @@ func TestPrefixPlugin(t *testing.T) { req1 := &types.LLMRequest{ RequestId: uuid.NewString(), TargetModel: "test-model1", - Prompt: "aaaaaa", + Body: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: "aaaaaa", + }, + }, } scores := plugin.Score(context.Background(), types.NewCycleState(), req1, pods) state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req1.RequestId, plugins.StateKey(plugin.TypedName().String())) @@ -78,7 +81,11 @@ func TestPrefixPlugin(t *testing.T) { req2 := &types.LLMRequest{ RequestId: uuid.NewString(), TargetModel: "test-model2", - Prompt: "bbbbbb", + Body: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: "bbbbbb", + }, + }, } scores = plugin.Score(context.Background(), types.NewCycleState(), req2, pods) state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req2.RequestId, plugins.StateKey(plugin.TypedName().String())) @@ -105,7 +112,11 @@ func TestPrefixPlugin(t *testing.T) { req3 := &types.LLMRequest{ RequestId: uuid.NewString(), TargetModel: "test-model1", - Prompt: "aaaabbbb", + Body: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: "aaaabbbb", + }, + }, } scores = plugin.Score(context.Background(), types.NewCycleState(), req3, pods) state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req3.RequestId, plugins.StateKey(plugin.TypedName().String())) @@ -131,7 +142,11 @@ func TestPrefixPlugin(t *testing.T) { req4 := &types.LLMRequest{ RequestId: uuid.NewString(), TargetModel: "test-model-new", - Prompt: "aaaabbbb", + Body: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: "aaaabbbb", + }, + }, } scores = plugin.Score(context.Background(), types.NewCycleState(), req4, pods) state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req4.RequestId, plugins.StateKey(plugin.TypedName().String())) @@ -157,7 +172,11 @@ func TestPrefixPlugin(t *testing.T) { req5 := &types.LLMRequest{ RequestId: uuid.NewString(), TargetModel: "test-model1", - Prompt: "aaaabbbbcccc", + Body: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: "aaaabbbbcccc", + }, + }, } scores = plugin.Score(context.Background(), types.NewCycleState(), req5, pods) state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req5.RequestId, plugins.StateKey(plugin.TypedName().String())) @@ -180,6 +199,149 @@ func TestPrefixPlugin(t *testing.T) { plugin.wg.Wait() } +func TestPrefixPluginChatCompletions(t *testing.T) { + config := Config{ + HashBlockSize: 4, + MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, + LRUCapacityPerServer: DefaultLRUCapacityPerServer, + } + plugin := New(context.Background(), config) + + pod1 := &types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}}} + pods := []types.Pod{pod1} + + // Test with chat completions request + req1 := &types.LLMRequest{ + RequestId: uuid.NewString(), + TargetModel: "test-model1", + Body: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: []types.Message{ + {Role: "user", Content: "hello world"}, + {Role: "assistant", Content: "hi there"}, + }, + }, + }, + } + scores := plugin.Score(context.Background(), types.NewCycleState(), req1, pods) + state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req1.RequestId, plugins.StateKey(plugin.TypedName().String())) + assert.NoError(t, err) + t.Logf("Chat completions - Hashes %+v, cached servers: %+v", state.PrefixHashes, state.PrefixCacheServers) + // Should have some hashes for the JSON-encoded messages + assert.Greater(t, len(state.PrefixHashes), 1, "should have hashes for chat completions") + assert.Equal(t, 0, len(state.PrefixCacheServers), "there shouldn't be any cached servers initially") + assert.Equal(t, float64(0), scores[pod1], "score for pod1") +} + +func TestPrefixPluginChatCompletionsGrowth(t *testing.T) { + config := Config{ + HashBlockSize: 8, // Use larger block size for more predictable JSON marshaling + MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, + LRUCapacityPerServer: DefaultLRUCapacityPerServer, + } + plugin := New(context.Background(), config) + + pod1 := &types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}}} + pod2 := &types.PodMetrics{Pod: &backend.Pod{NamespacedName: k8stypes.NamespacedName{Name: "pod2"}}} + pods := []types.Pod{pod1, pod2} + + // First request with initial conversation + req1 := &types.LLMRequest{ + RequestId: uuid.NewString(), + TargetModel: "test-model1", + Body: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: []types.Message{ + {Role: "system", Content: "You are a helpful assistant"}, + {Role: "user", Content: "Hello, how are you?"}, + }, + }, + }, + } + scores := plugin.Score(context.Background(), types.NewCycleState(), req1, pods) + state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req1.RequestId, plugins.StateKey(plugin.TypedName().String())) + assert.NoError(t, err) + t.Logf("Initial conversation - Hashes %+v, cached servers: %+v", len(state.PrefixHashes), state.PrefixCacheServers) + initialHashCount := len(state.PrefixHashes) + assert.Greater(t, initialHashCount, 1, "should have hashes for chat completions") + assert.Equal(t, 0, len(state.PrefixCacheServers), "there shouldn't be any cached servers initially") + assert.Equal(t, float64(0), scores[pod1], "score for pod1") + assert.Equal(t, float64(0), scores[pod2], "score for pod2") + + // Simulate pod1 was picked + schedulingResult := &types.SchedulingResult{ + PrimaryProfileName: "default", + ProfileResults: map[string]*types.ProfileRunResult{ + "default": {TargetPods: []types.Pod{pod1}}, + }, + } + plugin.PreRequest(context.Background(), req1, schedulingResult, 0) + + // Second request adds assistant response and new user message (conversation grows) + req2 := &types.LLMRequest{ + RequestId: uuid.NewString(), + TargetModel: "test-model1", + Body: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: []types.Message{ + {Role: "system", Content: "You are a helpful assistant"}, + {Role: "user", Content: "Hello, how are you?"}, + {Role: "assistant", Content: "I'm doing well, thank you! How can I help you today?"}, + {Role: "user", Content: "Can you explain how prefix caching works?"}, + }, + }, + }, + } + scores = plugin.Score(context.Background(), types.NewCycleState(), req2, pods) + state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req2.RequestId, plugins.StateKey(plugin.TypedName().String())) + assert.NoError(t, err) + t.Logf("Extended conversation - Hashes %+v, cached servers: %+v", len(state.PrefixHashes), state.PrefixCacheServers) + extendedHashCount := len(state.PrefixHashes) + assert.Greater(t, extendedHashCount, initialHashCount, "extended conversation should have more hashes") + assert.Greater(t, len(state.PrefixCacheServers), 0, "should have cached servers from prefix match") + + // Calculate expected score - pod1 should have cached the initial prefix + cachedBlocks := state.PrefixCacheServers[ServerID(pod1.GetPod().NamespacedName)] + expectedScore := float64(cachedBlocks) / float64(extendedHashCount) + assert.Equal(t, expectedScore, scores[pod1], "pod1 should have prefix cache hit") + assert.Equal(t, float64(0), scores[pod2], "pod2 should have no cache hit") + + // Simulate pod1 was picked again + plugin.PreRequest(context.Background(), req2, schedulingResult, 0) + + // Third request continues the conversation even further + req3 := &types.LLMRequest{ + RequestId: uuid.NewString(), + TargetModel: "test-model1", + Body: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: []types.Message{ + {Role: "system", Content: "You are a helpful assistant"}, + {Role: "user", Content: "Hello, how are you?"}, + {Role: "assistant", Content: "I'm doing well, thank you! How can I help you today?"}, + {Role: "user", Content: "Can you explain how prefix caching works?"}, + {Role: "assistant", Content: "Prefix caching is a technique where..."}, + {Role: "user", Content: "That's very helpful, thank you!"}, + }, + }, + }, + } + scores = plugin.Score(context.Background(), types.NewCycleState(), req3, pods) + state, err = plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req3.RequestId, plugins.StateKey(plugin.TypedName().String())) + assert.NoError(t, err) + t.Logf("Long conversation - Hashes %+v, cached servers: %+v", len(state.PrefixHashes), state.PrefixCacheServers) + longHashCount := len(state.PrefixHashes) + assert.Greater(t, longHashCount, extendedHashCount, "long conversation should have even more hashes") + assert.Greater(t, len(state.PrefixCacheServers), 0, "should have cached servers from prefix match") + + // pod1 should have an even higher cache hit rate now + cachedBlocks = state.PrefixCacheServers[ServerID(pod1.GetPod().NamespacedName)] + expectedScore = float64(cachedBlocks) / float64(longHashCount) + assert.Equal(t, expectedScore, scores[pod1], "pod1 should have higher prefix cache hit") + assert.Greater(t, scores[pod1], float64(0.5), "cache hit rate should be substantial for growing conversation") + assert.Equal(t, float64(0), scores[pod2], "pod2 should still have no cache hit") +} + // TestPrefixPluginStress is a stress test for the prefix scoring plugin, using prompts of increasing length. func BenchmarkPrefixPluginStress(b *testing.B) { blockSize := 4 @@ -213,7 +375,11 @@ func BenchmarkPrefixPluginStress(b *testing.B) { req := &types.LLMRequest{ RequestId: uuid.NewString(), TargetModel: "model-stress", - Prompt: prompt, + Body: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: prompt, + }, + }, } // First cycle: simulate scheduling and insert prefix info into the cache @@ -230,7 +396,7 @@ func BenchmarkPrefixPluginStress(b *testing.B) { // Second cycle: validate internal state state, err := plugins.ReadPluginStateKey[*SchedulingContextState](plugin.pluginState, req.RequestId, plugins.StateKey(plugin.TypedName().String())) assert.NoError(b, err) - expectedHashes := int(math.Min(float64(maxPrefixBlocks), float64(len(req.Prompt)/blockSize))) + expectedHashes := int(math.Min(float64(maxPrefixBlocks), float64(len(req.Body.Completions.Prompt)/blockSize))) assert.Equal(b, expectedHashes, len(state.PrefixHashes), "number of hashes is incorrect") } } @@ -244,3 +410,76 @@ func randomPrompt(n int) string { } return sb.String() } + +// BenchmarkPrefixPluginChatCompletionsStress is a stress test for chat completions with varying message counts and lengths +func BenchmarkPrefixPluginChatCompletionsStress(b *testing.B) { + blockSize := 8 + maxPrefixBlocks := 50000 + config := Config{ + HashBlockSize: blockSize, + MaxPrefixBlocksToMatch: maxPrefixBlocks, + LRUCapacityPerServer: DefaultLRUCapacityPerServer, + } + + plugin := New(context.Background(), config) + + // Test scenarios: varying number of messages and message lengths + scenarios := []struct { + messageCount int + messageLength int + }{ + {2, 50}, // Short conversation, short messages + {2, 500}, // Short conversation, long messages + {5, 100}, // Medium conversation, medium messages + {10, 200}, // Long conversation, medium messages + {20, 100}, // Very long conversation, medium messages + {50, 50}, // Very long conversation, short messages + {100, 25}, // Extremely long conversation, very short messages + } + + for _, scenario := range scenarios { + b.Run(fmt.Sprintf("messages_%d_length_%d", scenario.messageCount, scenario.messageLength), func(b *testing.B) { + // Generate messages for this scenario + messages := make([]types.Message, scenario.messageCount) + messages[0] = types.Message{Role: "system", Content: "You are a helpful assistant."} + + for i := 1; i < scenario.messageCount; i++ { + role := "user" + if i%2 == 0 { + role = "assistant" + } + content := randomPrompt(scenario.messageLength) + messages[i] = types.Message{Role: role, Content: content} + } + + pod := &types.PodMetrics{ + Pod: &backend.Pod{ + NamespacedName: k8stypes.NamespacedName{ + Name: fmt.Sprintf("chat-pod-%d-%d", scenario.messageCount, scenario.messageLength), + }, + }, + } + pods := []types.Pod{pod} + + req := &types.LLMRequest{ + RequestId: uuid.NewString(), + TargetModel: "chat-model-stress", + Body: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: messages, + }, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Benchmark the scoring operation + scores := plugin.Score(context.Background(), nil, req, pods) + _ = scores // Use the result to prevent optimization + + // Clean up state for next iteration + plugin.pluginState.Delete(req.RequestId) + } + }) + } +} diff --git a/pkg/epp/scheduling/types/types.go b/pkg/epp/scheduling/types/types.go index 056723dbf..f65cd07b7 100644 --- a/pkg/epp/scheduling/types/types.go +++ b/pkg/epp/scheduling/types/types.go @@ -23,14 +23,16 @@ import ( backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" ) +const nilString = "" + // LLMRequest is a structured representation of the fields we parse out of the LLMRequest body. type LLMRequest struct { // RequestId is the Envoy generated Id for the request being processed RequestId string // TargetModel is the final target model after traffic split. TargetModel string - // Prompt is the prompt that was sent in the request body. - Prompt string + // Data contains the request-body fields that we parse out as user input. + Body *LLMRequestBody // Headers is a map of the request headers. Headers map[string]string @@ -53,7 +55,75 @@ type LLMRequest struct { } func (r *LLMRequest) String() string { - return fmt.Sprintf("RequestID: %s, TargetModel: %s, PromptLength: %d, Headers: %v", r.RequestId, r.TargetModel, len(r.Prompt), r.Headers) + if r == nil { + return nilString + } + + return fmt.Sprintf("RequestID: %s, TargetModel: %s, Body: %s, Headers: %v", + r.RequestId, r.TargetModel, r.Body, r.Headers) +} + +// LLMRequestBody contains the request-body fields that we parse out as user input, +// to be used in forming scheduling decisions. +// An LLMRequestBody must contain exactly one of CompletionsRequest or ChatCompletionsRequest. +type LLMRequestBody struct { + // CompletionsRequest is the representation of the OpenAI /v1/completions request body. + Completions *CompletionsRequest `json:"completions,omitempty"` + // ChatCompletionsRequest is the representation of the OpenAI /v1/chat_completions request body. + ChatCompletions *ChatCompletionsRequest `json:"chat_completions,omitempty"` +} + +// CompletionsRequest is a structured representation of the fields we parse out of the +// /v1/completions request body. +// This struct includes fields usable for plugins and scheduling decisions - and not the entire +// API spec. +type CompletionsRequest struct { + // Prompt is the prompt that was sent in the request body. + Prompt string `json:"prompt,omitempty"` +} + +func (r *CompletionsRequest) String() string { + if r == nil { + return nilString + } + + return fmt.Sprintf("{PromptLength: %d}", len(r.Prompt)) +} + +// ChatCompletionsRequest is a structured representation of the fields we parse out of the +// /v1/chat/completions request body. +// This struct includes fields usable for plugins and scheduling decisions - and not the entire +// API spec. +type ChatCompletionsRequest struct { + /* parameters from the official OpenAI chat-completions API */ + Messages []Message `json:"messages,omitempty"` + Tools []interface{} `json:"tools,omitempty"` + /* parameters from the HuggingFace transformers chat-templates API */ + Documents []interface{} `json:"documents,omitempty"` + ChatTemplate string `json:"chat_template,omitempty"` + ReturnAssistantTokensMask bool `json:"return_assistant_tokens_mask,omitempty"` + ContinueFinalMessage bool `json:"continue_final_message,omitempty"` + AddGenerationPrompt bool `json:"add_generation_prompt,omitempty"` + ChatTemplateKWArgs map[string]interface{} `json:"chat_template_kwargs,omitempty"` +} + +func (r *ChatCompletionsRequest) String() string { + if r == nil { + return nilString + } + + messagesLen := 0 + for _, msg := range r.Messages { + messagesLen += len(msg.Content) + } + + return fmt.Sprintf("{MessagesLength: %d}", messagesLen) +} + +// Message represents a single message in a chat-completions request. +type Message struct { + Role string + Content string // TODO: support multi-modal content } type Pod interface { @@ -69,8 +139,9 @@ type ScoredPod struct { func (pm *PodMetrics) String() string { if pm == nil { - return "" + return nilString } + return fmt.Sprintf("%+v", *pm) } diff --git a/pkg/epp/util/request/body.go b/pkg/epp/util/request/body.go index 46de1fa54..07877415f 100644 --- a/pkg/epp/util/request/body.go +++ b/pkg/epp/util/request/body.go @@ -17,70 +17,43 @@ limitations under the License. package request import ( - "fmt" + "encoding/json" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error" ) -func ExtractPromptFromRequestBody(body map[string]any) (string, error) { - if _, ok := body["messages"]; ok { - return extractPromptFromMessagesField(body) +// ExtractRequestBody extracts the LLMRequestBody from the given request body map. +func ExtractRequestBody(rawBody map[string]any) (*types.LLMRequestBody, error) { + // Convert map back to JSON bytes + jsonBytes, err := json.Marshal(rawBody) + if err != nil { + return nil, errutil.Error{Code: errutil.BadRequest, Msg: "invalid request body"} } - return extractPromptField(body) -} -func extractPromptField(body map[string]any) (string, error) { - prompt, ok := body["prompt"] - if !ok { - return "", errutil.Error{Code: errutil.BadRequest, Msg: "prompt not found in request"} - } - promptStr, ok := prompt.(string) - if !ok { - return "", errutil.Error{Code: errutil.BadRequest, Msg: "prompt is not a string"} + // Try completions request first + var completions types.CompletionsRequest + if err = json.Unmarshal(jsonBytes, &completions); err == nil && completions.Prompt != "" { + return &types.LLMRequestBody{Completions: &completions}, nil } - return promptStr, nil -} -func extractPromptFromMessagesField(body map[string]any) (string, error) { - messages, ok := body["messages"] - if !ok { - return "", errutil.Error{Code: errutil.BadRequest, Msg: "messages not found in request"} - } - messageList, ok := messages.([]any) - if !ok { - return "", errutil.Error{Code: errutil.BadRequest, Msg: "messages is not a list"} - } - if len(messageList) == 0 { - return "", errutil.Error{Code: errutil.BadRequest, Msg: "messages is empty"} + // Try chat completions + var chatCompletions types.ChatCompletionsRequest + if err = json.Unmarshal(jsonBytes, &chatCompletions); err != nil { + return nil, errutil.Error{Code: errutil.BadRequest, Msg: "invalid request format"} } - prompt := "" - for _, msg := range messageList { - msgMap, ok := msg.(map[string]any) - if !ok { - continue - } - content, ok := msgMap["content"] - if !ok { - continue - } - contentStr, ok := content.(string) - if !ok { - continue - } - role, ok := msgMap["role"] - if !ok { - continue - } - roleStr, ok := role.(string) - if !ok { - continue - } - prompt += constructChatMessage(roleStr, contentStr) + if err = validateChatCompletionsMessages(chatCompletions.Messages); err != nil { + return nil, errutil.Error{Code: errutil.BadRequest, Msg: "invalid chat-completions request: " + err.Error()} } - return prompt, nil + + return &types.LLMRequestBody{ChatCompletions: &chatCompletions}, nil } -func constructChatMessage(role string, content string) string { - return fmt.Sprintf("<|im_start|>%s\n%s<|im_end|>\n", role, content) +func validateChatCompletionsMessages(messages []types.Message) error { + if len(messages) == 0 { + return errutil.Error{Code: errutil.BadRequest, Msg: "chat-completions request must have at least one message"} + } + + return nil } diff --git a/pkg/epp/util/request/body_test.go b/pkg/epp/util/request/body_test.go index ce5a93921..64ab6de11 100644 --- a/pkg/epp/util/request/body_test.go +++ b/pkg/epp/util/request/body_test.go @@ -18,16 +18,30 @@ package request import ( "testing" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" ) -func TestExtractPromptFromRequestBody(t *testing.T) { +func TestExtractRequestData(t *testing.T) { tests := []struct { name string body map[string]any - want string + want *types.LLMRequestBody wantErr bool - errType error }{ + { + name: "completions request body", + body: map[string]any{ + "model": "test", + "prompt": "test prompt", + }, + want: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: "test prompt", + }, + }, + }, { name: "chat completions request body", body: map[string]any{ @@ -39,137 +53,175 @@ func TestExtractPromptFromRequestBody(t *testing.T) { map[string]any{ "role": "user", "content": "hello", }, - map[string]any{ - "role": "assistant", "content": "hi, what can I do for you?", + }, + }, + want: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: []types.Message{ + {Role: "system", Content: "this is a system message"}, + {Role: "user", Content: "hello"}, }, }, }, - want: "<|im_start|>system\nthis is a system message<|im_end|>\n" + - "<|im_start|>user\nhello<|im_end|>\n" + - "<|im_start|>assistant\nhi, what can I do for you?<|im_end|>\n", }, { - name: "completions request body", + name: "chat completions with all optional fields", body: map[string]any{ - "model": "test", - "prompt": "test prompt", + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "tools": []any{map[string]any{"type": "function"}}, + "documents": []any{map[string]any{"content": "doc"}}, + "chat_template": "custom template", + "return_assistant_tokens_mask": true, + "continue_final_message": true, + "add_generation_prompt": true, + "chat_template_kwargs": map[string]any{"key": "value"}, + }, + want: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: []types.Message{{Role: "user", Content: "hello"}}, + Tools: []any{map[string]any{"type": "function"}}, + Documents: []any{map[string]any{"content": "doc"}}, + ChatTemplate: "custom template", + ReturnAssistantTokensMask: true, + ContinueFinalMessage: true, + AddGenerationPrompt: true, + ChatTemplateKWArgs: map[string]any{"key": "value"}, + }, }, - want: "test prompt", + }, + { + name: "nil body", + body: nil, + wantErr: true, }, { name: "invalid prompt format", + body: map[string]any{ + "model": "test", + "prompt": 123, + }, + wantErr: true, + }, + { + name: "invalid messages format", + body: map[string]any{ + "model": "test", + "messages": "invalid", + }, + wantErr: true, + }, + { + name: "neither prompt nor messages", body: map[string]any{ "model": "test", - "prompt": []any{ - map[string]any{ - "role": "system", "content": "this is a system message", - }, - map[string]any{ - "role": "user", "content": "hello", - }, - map[string]any{ - "role": "assistant", "content": "hi, what can I", - }, + }, + wantErr: true, + }, + { + name: "empty messages array", + body: map[string]any{ + "model": "test", + "messages": []any{}, + }, + wantErr: true, + }, + { + name: "message with non-string role", + body: map[string]any{ + "model": "test", + "messages": []any{ + map[string]any{"role": 123, "content": "hello"}, }, }, wantErr: true, }, { - name: "invalid messaged format", + name: "message with non-string content", body: map[string]any{ "model": "test", - "messages": map[string]any{ - "role": "system", "content": "this is a system message", + "messages": []any{ + map[string]any{"role": "user", "content": 123}, }, }, wantErr: true, }, { - name: "prompt does not exist", + name: "invalid tools format", body: map[string]any{ "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "tools": "invalid", }, wantErr: true, }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ExtractPromptFromRequestBody(tt.body) - if (err != nil) != tt.wantErr { - t.Errorf("ExtractPromptFromRequestBody() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("ExtractPromptFromRequestBody() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestExtractPromptField(t *testing.T) { - tests := []struct { - name string - body map[string]any - want string - wantErr bool - }{ { - name: "valid prompt", + name: "invalid documents format", body: map[string]any{ - "prompt": "test prompt", + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "documents": "invalid", }, - want: "test prompt", + wantErr: true, }, { - name: "prompt not found", - body: map[string]any{}, + name: "invalid chat_template format", + body: map[string]any{ + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "chat_template": 123, + }, wantErr: true, }, { - name: "non-string prompt", + name: "invalid return_assistant_tokens_mask format", body: map[string]any{ - "prompt": 123, + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "return_assistant_tokens_mask": "invalid", }, wantErr: true, }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := extractPromptField(tt.body) - if (err != nil) != tt.wantErr { - t.Errorf("extractPromptField() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("extractPromptField() got = %v, want %v", got, tt.want) - } - }) - } -} - -func TestExtractPromptFromMessagesField(t *testing.T) { - tests := []struct { - name string - body map[string]any - want string - wantErr bool - }{ { - name: "valid messages", + name: "invalid continue_final_message format", body: map[string]any{ + "model": "test", "messages": []any{ - map[string]any{"role": "user", "content": "test1"}, - map[string]any{"role": "assistant", "content": "test2"}, + map[string]any{"role": "user", "content": "hello"}, }, + "continue_final_message": "invalid", }, - want: "<|im_start|>user\ntest1<|im_end|>\n<|im_start|>assistant\ntest2<|im_end|>\n", + wantErr: true, }, { - name: "invalid messages format", + name: "invalid add_generation_prompt format", body: map[string]any{ - "messages": "invalid", + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "add_generation_prompt": "invalid", + }, + wantErr: true, + }, + { + name: "invalid chat_template_kwargs format", + body: map[string]any{ + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "chat_template_kwargs": "invalid", }, wantErr: true, }, @@ -177,31 +229,75 @@ func TestExtractPromptFromMessagesField(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := extractPromptFromMessagesField(tt.body) + got, err := ExtractRequestBody(tt.body) if (err != nil) != tt.wantErr { - t.Errorf("extractPromptFromMessagesField() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("ExtractRequestBody() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr { return } - if got != tt.want { - t.Errorf("extractPromptFromMessagesField() got = %v, want %v", got, tt.want) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("ExtractRequestBody() mismatch (-want +got):\n%s", diff) } }) } } -func TestConstructChatMessage(t *testing.T) { - tests := []struct { - role string - content string - want string - }{ - {"user", "hello", "<|im_start|>user\nhello<|im_end|>\n"}, - {"assistant", "hi", "<|im_start|>assistant\nhi<|im_end|>\n"}, +// Benchmark tests for performance comparison +func BenchmarkExtractRequestData_Completions(b *testing.B) { + body := map[string]any{ + "model": "test", + "prompt": "test prompt", } - for _, tt := range tests { - if got := constructChatMessage(tt.role, tt.content); got != tt.want { - t.Errorf("constructChatMessage() = %v, want %v", got, tt.want) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ExtractRequestBody(body) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkExtractRequestData_ChatCompletions(b *testing.B) { + body := map[string]any{ + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ExtractRequestBody(body) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkExtractRequestData_ChatCompletionsWithOptionals(b *testing.B) { + body := map[string]any{ + "model": "test", + "messages": []any{ + map[string]any{"role": "user", "content": "hello"}, + }, + "tools": []any{map[string]any{"type": "function"}}, + "documents": []any{map[string]any{"content": "doc"}}, + "chat_template": "custom template", + "return_assistant_tokens_mask": true, + "continue_final_message": true, + "add_generation_prompt": true, + "chat_template_kwargs": map[string]any{"key": "value"}, + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ExtractRequestBody(body) + if err != nil { + b.Fatal(err) } } } From 663168d5de2c2a8ccb8b25b54b258e534c997116 Mon Sep 17 00:00:00 2001 From: Sally O'Malley Date: Thu, 11 Sep 2025 21:06:06 -0400 Subject: [PATCH 017/133] epp servicemonitor (#1425) * epp servicemonitor and clusterpodmonitor templates Signed-off-by: sallyom * add monitoring chart doc Signed-off-by: sallyom --------- Signed-off-by: sallyom --- config/charts/inferencepool/README.md | 27 +++++++++++++++++++ .../templates/epp-sa-token-secret.yaml | 12 +++++++++ .../templates/epp-servicemonitor.yaml | 25 +++++++++++++++++ .../charts/inferencepool/templates/gke.yaml | 6 ++--- .../charts/inferencepool/templates/rbac.yaml | 6 +++++ config/charts/inferencepool/values.yaml | 15 ++++++++--- 6 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 config/charts/inferencepool/templates/epp-sa-token-secret.yaml create mode 100644 config/charts/inferencepool/templates/epp-servicemonitor.yaml diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md index c7374bcd1..5a5663d1a 100644 --- a/config/charts/inferencepool/README.md +++ b/config/charts/inferencepool/README.md @@ -117,6 +117,30 @@ Then apply it with: helm install vllm-llama3-8b-instruct ./config/charts/inferencepool -f values.yaml ``` +### Install with Monitoring + +To enable metrics collection and monitoring for the EndpointPicker, you can configure Prometheus ServiceMonitor creation: + +```yaml +inferenceExtension: + monitoring: + interval: "10s" + prometheus: + enabled: true + secret: + name: inference-gateway-sa-metrics-reader-secret +``` + +**Note:** Prometheus monitoring requires the Prometheus Operator and ServiceMonitor CRD to be installed in the cluster. + +For GKE environments, monitoring is automatically configured when `provider.name` is set to `gke`. + +Then apply it with: + +```txt +helm install vllm-llama3-8b-instruct ./config/charts/inferencepool -f values.yaml +``` + ## Uninstall Run the following command to uninstall the chart: @@ -147,6 +171,9 @@ The following table list the configurable parameters of the chart. | `inferenceExtension.affinity` | Affinity for the endpoint picker. Defaults to `{}`. | | `inferenceExtension.tolerations` | Tolerations for the endpoint picker. Defaults to `[]`. | | `inferenceExtension.flags.has-enable-leader-election` | Enable leader election for high availability. When enabled, only one EPP pod (the leader) will be ready to serve traffic. | +| `inferenceExtension.monitoring.interval` | Metrics scraping interval for monitoring. Defaults to `10s`. | +| `inferenceExtension.monitoring.secret.name` | Name of the service account token secret for metrics authentication. Defaults to `inference-gateway-sa-metrics-reader-secret`. | +| `inferenceExtension.monitoring.prometheus.enabled` | Enable Prometheus ServiceMonitor creation for EPP metrics collection. Defaults to `false`. | | `inferenceExtension.pluginsCustomConfig` | Custom config that is passed to EPP as inline yaml. | | `provider.name` | Name of the Inference Gateway implementation being used. Possible values: `gke`. Defaults to `none`. | diff --git a/config/charts/inferencepool/templates/epp-sa-token-secret.yaml b/config/charts/inferencepool/templates/epp-sa-token-secret.yaml new file mode 100644 index 000000000..9abee0fcd --- /dev/null +++ b/config/charts/inferencepool/templates/epp-sa-token-secret.yaml @@ -0,0 +1,12 @@ +{{- if or .Values.inferenceExtension.monitoring.prometheus.enabled .Values.inferenceExtension.monitoring.gke.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.inferenceExtension.monitoring.secret.name }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} + annotations: + kubernetes.io/service-account.name: {{ include "gateway-api-inference-extension.name" . }} +type: kubernetes.io/service-account-token +{{- end }} \ No newline at end of file diff --git a/config/charts/inferencepool/templates/epp-servicemonitor.yaml b/config/charts/inferencepool/templates/epp-servicemonitor.yaml new file mode 100644 index 000000000..e4788ba83 --- /dev/null +++ b/config/charts/inferencepool/templates/epp-servicemonitor.yaml @@ -0,0 +1,25 @@ +{{- if .Values.inferenceExtension.monitoring.prometheus.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "gateway-api-inference-extension.name" . }}-monitor + namespace: {{ .Release.Namespace }} + labels: + {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} +spec: + endpoints: + - interval: {{ .Values.inferenceExtension.monitoring.interval }} + port: "http-metrics" + path: "/metrics" + authorization: + credentials: + key: token + name: {{ .Values.inferenceExtension.monitoring.secret.name }} + jobLabel: {{ include "gateway-api-inference-extension.name" . }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "gateway-api-inference-extension.labels" . | nindent 6 }} +{{- end }} diff --git a/config/charts/inferencepool/templates/gke.yaml b/config/charts/inferencepool/templates/gke.yaml index 470063c79..f2296aafb 100644 --- a/config/charts/inferencepool/templates/gke.yaml +++ b/config/charts/inferencepool/templates/gke.yaml @@ -46,15 +46,15 @@ spec: endpoints: - port: metrics scheme: http - interval: 5s + interval: {{ .Values.inferenceExtension.monitoring.interval }} path: /metrics authorization: type: Bearer credentials: secret: - name: {{ .Values.gke.monitoringSecret.name }} + name: {{ .Values.inferenceExtension.monitoring.secret.name }} key: token - namespace: {{ .Values.gke.monitoringSecret.namespace }} + namespace: {{ .Release.Namespace }} selector: matchLabels: {{- include "gateway-api-inference-extension.selectorLabels" . | nindent 8 }} diff --git a/config/charts/inferencepool/templates/rbac.yaml b/config/charts/inferencepool/templates/rbac.yaml index 7ff534ff9..ebe68c3ea 100644 --- a/config/charts/inferencepool/templates/rbac.yaml +++ b/config/charts/inferencepool/templates/rbac.yaml @@ -17,6 +17,12 @@ rules: - subjectaccessreviews verbs: - create +{{- if .Values.inferenceExtension.monitoring.prometheus.enabled }} +- nonResourceURLs: + - "/metrics" + verbs: + - get +{{- end }} --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/config/charts/inferencepool/values.yaml b/config/charts/inferencepool/values.yaml index d45e6ed39..f61b64e37 100644 --- a/config/charts/inferencepool/values.yaml +++ b/config/charts/inferencepool/values.yaml @@ -40,6 +40,17 @@ inferenceExtension: tolerations: [] + # Monitoring configuration for EPP + monitoring: + interval: "10s" + # Service account token secret for authentication + secret: + name: inference-gateway-sa-metrics-reader-secret + + # Prometheus ServiceMonitor will be created when enabled for EPP metrics collection + prometheus: + enabled: false + inferencePool: targetPorts: - number: 8000 @@ -56,7 +67,3 @@ inferencePool: provider: name: none -gke: - monitoringSecret: - name: inference-gateway-sa-metrics-reader-secret - namespace: default From f3e403cb446a27cc61663123273f1120728f6728 Mon Sep 17 00:00:00 2001 From: kaushik mitra Date: Thu, 11 Sep 2025 23:06:08 -0700 Subject: [PATCH 018/133] remove scheduler epp flowchart (#1573) --- docs/scheduler-flowchart.png | Bin 409377 -> 0 bytes pkg/epp/README.md | 7 +------ 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 docs/scheduler-flowchart.png diff --git a/docs/scheduler-flowchart.png b/docs/scheduler-flowchart.png deleted file mode 100644 index 4459ef1ca6d3e2729cebe12e2806683e30144050..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 409377 zcmYIw2RxhK_kTKd(pCqx+M;T!+Pky}wfC-~_TDpHMq8^vY+76F8Dh0n6h-Y45ql&y zK@j;riO=u@rK^|jUIKwYSHa59v_T-M91w^Gbm2Vk zn`f$3gTTirPi-Z6P-!pYG6-}J1b+5Z*Dq}a?dzwzm%O`q_$M+zLH^vu4@(iRKK*E? zbFy;ty&?H=mJR2Wrna5e#MzMgFVzW7o9{H!^U$X&uW_sgr{;sEWk5}xPI@JgcOf?R z!(D9C3rWvO+4j3V%1>C|o_q3jeG%zp-^{ z#6XM5*1fAC3YQ#VB_M-6t>hJG(Q_x9tr~Hu|47^T|_7!uPF^0J#>xHG&x&40-;~H)#s9zrpMjaKePcnOpxyH=jGPhF5EabbRzX%4 zt1D(ZvXB1ggq3o8h%pnyODbYI@0z;oTnnjoPshv9H6tw#TQAgmR&RbVnKp{-t~{Dn zi|1#JoEM`*ZAWb#q_xRJ$dvwNl7JCkUg2Yz-)F2Z^@Idxt-b%cD5}Vy`O7=16|Ns!Hr*KXGxb+MT(t zZIKaAcg9R~H;P0-^oqxr$r296A0$cuyD;*Uw8bd^;gZ--DrlZ*> zivCRxoEHhQeDb@qMvQlUpzioUVth=WF)SK2nNMeOOl&fQq*S!BlG5->c zsgUU_ALU-zKHD_O8jab8m_{B+Mj!2aO7p#Afs}T%)i-0(Rxz7N7}wr~I=WT--Bx$Y z%(VM~o!0DsekW#ISKfGi`}bp3jf+pVZTfnyTujR*Dt~FUe@C$b*Ko^zrE7o=-1%nr z)GKFWY;ilP&Us^ZR?oc2{dY-|lGJ$MP|=#Xg(lrS!sU_kXHIk8?~iBgjA!lFmCw?M zex?(R2w_mP3tf5{A@In$Nx|frU&cmvOwgZ~1>3tLl9d5{vBG|y@r%^s&0q1)7?tk5 zpl|hz5uA(wmd@D9{KTp0OReh;J`5#eHH2>_t}@*1CSY8@j0Ub9^4g`p#YcxUrlWA* zv_=D;n#YuPKVx>CTfIE~)!oeX_d?V%zOG;o`&nw5ASbr|LA)Y%PN*@UVLI6Ee zAfv&X&+66U-&oJ}w(=W{_}AFPs0PLa)GS6QNeAoSD=z0z{rC~~Py?qm5+7otv8=N@ z6k>A+?>GQl%}H3*03iB7xzwAJP_0v#a7Lx@uBJl`@RNG(h?vb4KLxHIQE5W#W@hl5 zqD5{(JG;lt#jp?kU*0Xd-N)ZM*_7-Pdw)Lb^ls_Ll=!}HU}B-Ix%V=^$%KUQbb^p> z0u1T^_j6BGfmOq;!Kqs+0qqiCE)dAQV*|Xb6p*kTkndr>uD#2EuEYo4vTJcs{#dlY zMJO$b4_M?H6VyKac4q@~mJI!%Noool{%Wc?-#;zCbSl3v5#xsYn1~*)#n2A4d$&Vx z_n404LA#Chgj$|FM4bnH4;5UF;+t=rEbKIqNN8%{w(eceLGc@OQH^jB@@xR1-v1`_ zgACSh5br5K?2a=97Lpn5qSFFtFC^_Xs-lB0Rde(5t^Un&#MMltoX>rYw5qlNfqv<0 zXPf9`IOzB5^mGMPacr#Tl)Qv$%lKz&%QUL*{uz?HCv7}jol|~Fm^^t3u;1gmN|Q}h z7M@BYAr|4{B#i&uLAA60I>wYwHO%r+OOER2vZB!$95x|Y@>-GFY0y2s+>E=L{W@w~ zL0LxjdPJyACoBP;6If<29xU3Kk$CIIi||xmV{K(IX+I|NTPQex&G%i=H!6tIA!N%>a4d6@>OroS+plxcz{!WgMDVI^h#<9jei?WYhPQDDBRiU*xCJ^uUEpEB&B zmd2Iyww&_}zTAXT;kR<*E(jf7vKPAd;AF0*bt4l$-g<4s*r!Oi{miCf_FjHgqY-w$ z$9yD5QYJ8#@a+^xOk+S}ce#=uX1_J*Vg$?mFuWbV^*%76?yDGE5;b+Mvq&mi=v9iZ zdcjD6=!j%HUSv2`zf7h^&#Jlf4pn|eKu^4lQCBa+UtSi7PHs`}FE?%wC`l}j*hkNM z^g8s030ja*g#8$OK3B@k;A@m`wS&E}A7%L`p~9;N#-G=Ih@kWA&%?7UWas+xLzZ+k zGUwd(z3}Pt#=d**;x~dTCa!=$4;htW3@$eZstiIbxKnL@KkCvSca<&rmEfr$zwGv- z`q^byKmaeZPR#P0sMH78O`lR=yXJS34*FTl4u%KKJ1d2(w2Ib_CPL<4EPtMV2m%RA z+cb@3=UPbQ6^jO1v;9lhsoCQsth*+lU1%=mzHhacCnNC7ng2$;W#7Ey=J{N-CJ_HI z+*HDCARg_q42xmx$NK93^1BQu%e_`x`(qkz;q_^*PTNlRZNc25BN_3^CyHkW|8SGH z=*5c@clWaWhGJ?Syq z<2>$m6JWS3aG@s_9!ULY+b||F=PWxBJE(#regbT@(KtnutmsK}6eDt*485G)TC*gg zrb9!_S8eedkH4=v+Ps+Ka`ab={ydA(DNr5VzX=)MdQzRragjP;zte#%+FKnimvmKV zRVbTFLz!1^(aF44ALBzakvl6m*p=4&yfKn^*-=YF6-++v$y>ryRW*(B*IqD$SxgU!!- z-N4sw7rH1V#qqewa2urb+rIO8_FX^#v44deg-*>VQJkr}urb}LN9w_E1sA4LA!`i*%Cg?g ztQpcU0I{mAb5SL}eI3?mt32WuK7X*nZxhh;Rv^Idk#xjMvhLK?T_?xyFHNt=x-*vQ zAx0gs>yVWZT=-Rbk<7K=mklaOPwoPd_Us>Cg2cwgt4?T;#!4)XyM?AOmidU@oL%h^ z**HuaDJr;8`#e=tqVCorv1NBD6n#8DNg|+A8j3mFS*d_1$QUm@=(Xdx&1O`2yx#3W zI;>&^AfTk-#3(+WAj>4IqL+b~$sd64P=oP+5$MA_n7Ixu>MfvlN#e($MiTgn8(`@A z583YRfe@|}d%Ag|w?@@nOH%>|Zn*}eEy+wCaH55;@hIB=LG2mf^^biw4lfVa-PeFx zhhVS!`d+g*sUi<3J zhuzq5DXZW7 z8JF;tiR52jd^WZCeS%;Mfo>a2Ho8Q*lV_{kwiRhIo&o0D;y*dG5Da5eU^)89r^dGu zg^xW2l1p1Q%*a*1nBSv^uFIOoFV>@3;W@2694Bj#qpYFocF+ScZSS2gTsoLzjZAB0 zM~u7tsy!q5xM;Flgjw4VLs@8zsM-!7p?S=sv_oi@gKc#A^k_%%aPTM4h7s(s@ z(v-!+PToFq5)~-knz->~pt1i9pN<8A1}s?Z)MMXcKEXKn7|`C&2KdALLVTA~C$t!{ z9~IO(?n55n1gHF-pKRa_zId_*pqB=deH6f*=IG7-GZoB$$E*9jdOW6WbWwz!s>oWV zhV>Q71(-HyGl~L{R)Rsz?Nb8d%Od!q#VbaY)oZ*{Ic-MLkkKQE&ZKcQ%kBk(cUicD@1n5Harrjw3y0csZJ~>R9v315C z<2z&f?&(sL{SFmH*`Eo)uuB}Z1YL~7@97PDV!F-AD{nF#&M+EeXSFegKd?*s3vZ4Mx5S&K(dmJK5AJTH6fc+ zJ{D_9$7MG8b)~V{kmeRJoviUhn?hpc3vgI5rb!6PIB<3dn5Enig*1_S_Jdd8{d>F2 zzWiurRmtcvYa^I`X;Jj593T_+vIF8ETQ@X#)|_Cv7V%BUlI&ovSx{al@N?r*wwvC7 zh6`d&n3w%ws&UZs;44Z%Z1V8kps!kj4WbiEAG@AQv_$#nPz3XfPmDloHxTMTFnZ)X?1tMniOva%X!X?vyvn~FfBBpJ>`mO z!u^Wi?|lwG*)vIsE?;ommx_EZ+n}M$T$cOmS*FTDZM$DQIlQz-J~R+y%Pv&VpHD9L zq;OUdEGN%g@Vy~gxAl8*+kVj<(y%roUQ#(Gz6=PSkH?-$xBDiwzVp7LScE_<@z6$M3a z+F>9d0m(6X{t5s-K@t4ZT#@Jb4TQ(6JGIy1Ck?5uYwZWg47X^NA=AAb#K#}l=IgA5 zAfQ#y1ZZ3qr)qwIpIXJ%oy8dY;Cw`yet;J8VPASshLVhk^Sm?GeyKka_quV_yC%a< zuc!8Iy11L-nUXUeK9UJZu!6Z_Yc;;~V*8@J!hP(kF~-Hkz{U~|4w`2MQDS4_05chJtctDPBk2HLNog84QMU6q_Ekd7esJoXII(+AHQ zo9_H9^HoeCd8*i0{o@}1ANTQxS^n|^s9wL`#Rfcy4fWxvS5tF!E<)IS%W%M!C<@^}R?C zq?N#YP#~oqLw$VwRb47r{u=L+WA zIt(xB+XJ(9wc7bEUdc9S18>Q-Ao3C&r2ZCE=689m8kEf^HOkGbHeLG$sDjP!Sgm0; zi+F*P{2_w(KO6~qHR@;#cNVq=rrkJJf-R2bOqrhl2Qsm7yB z@N2TosrsFda+Pqy>h^)<|?%_t7km+>b&<`XdLl( z3@qMUKfPJxs|at|sxMT4b+dL)%l15{%>7s5_7i$C&hT-4QJ**;KB%X5te*(xbji(Y zXyuFfXD2gKTubW=&FRdI^CrF@jk%D`-YIb9Rte95nrt%|oD7EMw4aG`ke&>9+QGn` z*P%<;o`>dq7&?pI&t_oNm)77)&cU%XniCQUi{n0fNWEiI!Z`+RMHOYR=J8C>Jb!Oy zY{HLopD$N091Fk24VUS5+aA0UdvI?TP@P-*XLY8_tYd@{ccy}YNrL#KD9ZCexKRaJ z*$)W}xjx*#nK!GeG_{pxvhaJYT**vESX3`}o$sotY!&H`KZA+Xvi;H*1cn=&f^>in zYz<`bx%&RAIm+%nGs@8wB7ekXYF-fE$q8ixROA?TOc!$@g7?W7pY2=HW&K)s-t|*t zulZ(QuA3YHo&AvN>%QUPnAjpOA?-Nzlje$+zuDw>n?dL3<;OZ@X>hm!JU%Jok5Jux1r2DxvGL<;RIk^nAzY=`^7yv9tK}-#gYCa-E zA73;~z5xK;kA)urbuP3w&=@@O?KO~hkRS5)OJhzT)tHh^(g@AjDiANut5fBgR#Lwn zIjN7dv{0BbG&-)BH$HmXc0G}}oeqID^p&L5izA8ssO(Z-&A{gk8HYoj(40_T-l0#2 z`e)Z1rTgJgndnDgIc7^=U?} zOr0L!Vwg82Qp|N?&<9Cuq?@P7$RE+SIWB6hvl?v-9-zt~$o;a~S#{B9%{Z^uX!vcU zQXh}l>jt1-#pe^?ryLi;l39`LX{&+vUv(s>7Q`#&4-JUFLS84U`sQ1TpS}Mxv>#?u zh*_n{p^a<5JvaR`SyzzLt^So&=)$CkC|mU<+pWe<5}SOyqBF@*04T>^M;>| zqiYHPPinS@$HljZ$*xEQqz2-<&Xg4t<#EsL?r! z_wMyahbbSR4u(-eL%9!xUFQ-RgUmB0_xD6~D=J?73QiwR#}=;*eYDlE0|wL$P}-jb zL!)IvbPpl7?*68(JFi<+qgaD-^q#jZ3t})4^l|kKd{I`&Bs=~MYXDbp*$ZgGF0X58 z1jnd1`Khpv!t7ShINEsrRbG2Fs}_jEG@K$~oO1dehgq6#RFL_U4OVg$@J-H_Z7%9E z7_%F}RpZ6hrOn)pE&1F#c}06K$|Q;;YBoI?YWB~37Iwx9vFAK1i(qoujjGbCYt&3g z<G*cauIUY4qWtFv`aF= zmbxDsY~X0?@D~WqZd>~`XHVhvU4JZmc>qV##0O6C z#4+|jMuWiGidyYS2;RriT2Ds%h6)qaJ0+)vKU)N8ks~AESByG%4>6d{@-W0-FN%sN zy&5MNCv>YHieEr}f}M?77VLDU9$es&3-)3@pS&0LWY2a)D18{|%H&6^Q! zBj0PAsDp374Fih+X0y(}%jK$-0mN{OTZX=6Cj7z6?0BIaWoV^Jr4TO6NQM7JV#8=j znDkXR+ML@qTqm`;5iClYTlkKnuZ-qz&S_p~{x0zvBV_^r8n7m9z%>Dm3Lf1>-PZ8F zMYdpMwxp6{T(7a#aO_Fd{F*mzr*;tQyb)f17*eC515=~l3=OZ{5@|a6-n}J3T=xWi z)xYDI-~DAy6ZcU+JzzRMB}S;I3c3BxHPOase6KsW@&zf-!^OT7TBUd_?Hp*A>QFnP z424!6by)4LI2oC%mUNhSV{>%`&GL4Sf?{|wbI+}t3%p|L$dqBekj>OdozUE%3%usb z=sY<@)pLOlt!1uKGepE75Dn5wq#&Vdini)|28a1~^18i}kNH14@gyQJMS~_9+@1k> zFm%I8YnlLmUqhrqc?Wdc10mk6J_Eb0MTIlVXju%?g=|%f8T{5>NoSE49~oe=JUl81Nru-kDqT#QhrNsGf68{QbHriqLee!Zz>V zjJ?%&URBl>O;JN8wFC-@aFYMh-Jf8Le1B`;;zvM{vL^`j$a#!we>BUgW?;RnkPtfW z?w%;zy+u}_hW%eW;*myM(Pv>#;-84&F004?j8O~a_VdBqtr>F&j`5nM;Lkhb{iAGP zv^QF7w0>nBc5XZXMZ3ynt`pQd`#Q^wfOtapRDB{l1VRBZ5@(bWx0?V+&G*EfRKg=mD(o%v&$q z=HK0gC@O|wz)eAg00Xcz3D8J+40dd8R0Outf1(|U<7lV`zT1yM3dV%O1l9%hJwlMg zmMv)ld(r-)@m|m04Ij;gp%-TO(4w8KGyL6K#$16}FUXmK8>H>>t*xCvHp1d*8tQDS z9%ETmUM1tx9X$jAX4LGOXZP52JR5-f7bIo9M|OC6nr&+D?utChexv{-cG{IXy(k~c zBY(`LoRU*_5-2%%+5<|TN&!!|c1t&l2Rl2ha!1ih@)&aU3re@8!5oOwi$Z*xCYlts zrw*iKgqfVQ2Y)k#hA&Qc|(^w^!*=)q|+E zoF7-<_H;Ba`!c^xC2)Do^jgh<>spDY_l|}VJGe&D5!jm9Z;Ht-kj3|7qP@}o7Nh~0 z0BY(TfLku#>-Y@BrWQ;`$yU5kX}P?K72q;*bF8?|P8CkOyz6vG8ud%Aa0FnjI5g zs+QZ2gc)5QtpH+}Md4WHm{s>2F>BTG&c)3_AElDpjkZt!@h24@9VRT=vbsG@Dm>OP zQEcJXysw5MXCwETPZH1KX%6=@w+8;)XBxfI`nOtdP{?^$h!7-04xyHN9ttx#Xm3w1 z$jxUj3wi_Df`iONF&<+g>%hG}4Qzgv{`Fl1zG(Fs;H*Zkpy_hUMc75>(-z%j4B{5m zN9O9|yxa22G&K}r?$QXumo?*n4~MVbkM^aT@YN|X!niyZ^v!`wVs$@7AnqYUiz&4Z zDb-wC#4*pGR0Z#E4AKr`23RgcQ(f(C;02#yIs-Us25;&2)241omK$HZDt#}NS`@rxut<|>6 zqb^P35aX}Ihkgt(s}*BTDIq;xKzzh&kgp19S})@=*2*a?%7X)GfMEV>MKcaQ>dAOS z^W2lEPtErLk-tegsqrR>O&-kI8U1PTt0*6H>PpWn0^rYhr}?>HQ(g*cd{8(LY` zgjifg0M9hgb=X@X|92;a%#pyXc(H{zt-fSt{LtP9-Pbgq^b2p}Zr9U4rw69@cfy3| zY3d7v*)yS+5@ap-25kUo%aI)siltt9BWcQEetPv=pT2g0_R!&(guYfEONb+6YAZu& zH1#c$Vs_3#yDS?rugE7 z`LU`0Kajr=kb{m4LJq6V35y0^`!V5qMB|!q_3CpY3D8u*1^=(CH6NoB02DyZ3>qwr zWTN3YJP^Z)kV-RK>a>eIw2gf$A8j$aQc1ki6UUAVtFnmbbIkd+OCFfNHcHw%E$eo- zsx}{(s_s69vD7Am2l2-j!f}7ZX}h zy(OF3(F!bo&b1$cVnx_5E5V}oA3O!JP_mazJ$8>fKIm)-^Sb=Kt+oGfqyl=I_!+1H zlL^tPkH80A+z>*d4QUqiA(8{2Dan`)uy1)~>81YbNuqfj0GM~n_U$m>x2XMwjRp&k zLiYL~aa(onDyI6oqbztu?J}9C}FA+G@&syn{_tJyTsX z%8G_l3$NJ!O(xn7c~rA+1o-OvIt&t1y$)Fs;nfRDN2*JEEaCbNcK3RYDE{ea%IN_h zymspisj}`)rQd!t08faWhkwUcoF9NfqW>L;9+fzzul8!em}k8G!?}d7E@L5e5>9dt zm(#e)`ltf4%=A)=fSh+u*?Xz0&s=w5i()0x%+!I1mVJ`TB@m^f{3E92g%0Z_8q4365IKSMwHoGSZ{Dx@)Z0qR$H+-(F(A#lub4|4>Fe z&@N$4To)7E`|^mqR_ z@gf7iH1d^UeMfOq>oo9&T9e6;GJyGY`eR}EG>EP^mYG?3@Ey4WDuShY1xP)=C*?dO zD4auZADir|vx$3pT8kZVp!-&2vrETIAIsL_cK%j!#&yVqdy*YTgl>?x`!4}wsDy^^ zdA^QHoZLe33eH>5{By%(fFlw6YsFL3)XYPn7xUM0wd$Z%G^*f%`gD>-d|gpOuzvrM+w4 z@cGtC)RjQN;8WDCw>X&p+FLKTw)u_C$YFrMJj=I!I4@HJ=t%Rw<^>t1z$3${gCXwx z^L(~FzqO5@rmz*d7?=%hly1$dkj^P^H3kK7NwbsgoIcM4+42K=UI9jBc&%V~Ge0`~ zPeN)7n*})exa((EPr~&W7F&eQaGAc6DWogso7J~m9uGIO`a;2(;0j$2Gw{^bM_cu0 zrlt62jzcexgOoef`{y(3q}-ZZY}2tm!=XHJ2myXgJyVim@Q`K`P&?of(Jz=GTmmxG zE6Jh_)@YN%AJDE0p3imB00z+4+z2TTXaT648F!A9o)o66^C0FIe z9u-%*UV6Z;RUdq%Q1EIsO{8IMONj*)$xX8?m{{}OIg!|71Ne{o-Rx{MdOv+fy~8IG zWo!!c+=y!d^@oaKYI&%klPZbyT&?H4@?D9w-n>gv|B6#zpL^%4(W1m3viTe+?@X5K zWn_fJaG4kUZSQj(l1qiis<*rjP?mH|?a4d0Zgvm6CDBC2Jj=bdP21=qcStr{p`Z6= zgnqZHQg0gLEi)}PFc5=`j!+@Om=w^Z5=_L*Z0ON+$Z~_~nY}S4AKrth2ViIsD%Tl< zde|pt7h2V^+`k)M+l{6b-%#gQp*f2Js7Eg#5WU`=c7*GU<>&kr_w!ch+u7`cVTW`- zAL~bGpnd>6?ei`CVJ9i{)6}PZ>6n#^$%|tdb~KDJP;$Ybl^G>%8&ExK;d{1}N>&m2 z+9j#5I*4?Kw~Lwoa`h#e5kO7DNto$^StiCJAUxclp;DT_0P#Q|3z}hb6Tq0k(v;gt zxTclO$_R^iu>*|KnZ(Pmn;$0b0Og&{`<8~G)s}SCI7*@SiY+a`Ze1Im0;7Kv(g#Y| zf24N>xW(!<6I?anxCco3$(j9Lf7AHklDW-`o(sD45|U}y!u5;(D=Q|ii*|YkuH#qkltV}U=_|svJ5pFO_vh@f|)sA3CI+@u%S9w z`wPLPcFt)^aVe5Q8RM%p<450Lv*&WyPcm)mme<|uVh#VtosKSt5Ky61=SgmbG)8< zn`nSu?i`!e1@={#c_Y04BmGBm(ze)0^T3FhI~=e!*a!T1C^^`6XQd-!cqP@=5pXU* z`Z?@Zt|e^Zc)FXPuyMmit?!ai$2(ts&7P6FHLw~uphd@9A6x7uB^NlG1k9Rj1N15? zx$n{dBann>qnK>G_){hrJ)nI#B40oJcmZccUgQ-cNSXAzNop zT}as0?K#fJtXe>s6F#P&4YNA_Dgnen+ZbV4>nr_}muUxYg#J^t-|_V>*QT{$xc!Wl z(oFjwrOtXK|omK7XX~(ya1!V4wi$ zH5SfCru`L7Er7`Rz~s;d`A4n>m(_5G6HrS>7Q><*i!mM`CPoXR$MGAbU;4hx{7uMg z&{iUO>6!_fVGE@8>Wyf%X>}X(#ICuekKt&_Jr))s#baSP@o6{F2UR9D>!XMA#!58R z)OKIJt)JJk^)&m}@{#7|-*WA|6iHPZg&YPrla77J&@P92LrrqGp0{%_hmQn%nkjn| z{J=eG!#yOQDl)kW5XZuQMZU7eFws|}Sw5+rQ&6p*%V1|kw7Lw2g^SqXd~30Pnx9}D zv4d|h%omIvS{a9tX+u;qL%qCTXLw>kQ_R!)Q~(%x-$7= z8sv<(`b>gWN6UtBm_RcCA_0!)JRC)(W+to$q+JsDcM#^asrsru1W;T^*(>#z*sN{| zI9wtz4i_`L=JYhqgYfvIj>y)Tqaf90fGJob)%2v+U2UXIH&b4f5W4OHc}GWPikfF|pedIudb95SFVZU-$_{ad(a2xNP4e zER)3U{^Q~GLIVq{On?RcP<__smTe)Bbpk~l1Awk}kQ~b^P2s~Mc8ivk2aV4C{xK5d zAGJ&JX>sr!>Ka?t-O8~h{He+u686`wB>(9)x=9&e7Q9}Q=uHkv;vS31dLQeIO z&@FTG0w_*Je%woG+yK{HqmB>60x_A6gZK!!g90$i%1>27!K+?9fs2jX#%jq%@r5Xe zdjs5}XxBg@(9VUSD*51}3o|+H5!vz>Cz-`9t@zIG6>Z=V( zLblZtq>b``oDpbGh_ieWFU@M=>6%((B-(R{a9XjBpDRtHd(A8_r9}9y9ZPKA?BJS` z4=ya+<-)|evd`rzq0u1@AkYKohw7!Z=UoO&k`c*=5firLfvW^Zs*eH4KG z?u?f#oBIGHT{l_IKeX`wi<(?IdLo(d)*(68S`77#rk!UX7|<@XT6)Hz2K zlk0KNV&?psCpQqByFhk!4QZ%oH9K|i#)q54${QqGAcCKga>%{01kPh| z=b&vi`XYmLoYB-Z!a%21T{WOe=NCTj?)ZE4jF!@0e?@4NSq<{bmm)HQ2O{xL_;v(N zoduU+ytntO1Q>L#jCEnpGxu*tj;}QTp zB_Z2iNRNB{h*wrkrc48HYVd%16~KoJxDQtASlUa%7ATm^{OO`owsfaFNw zFZ*UDdd*6lO2UTj_|o#UrB|I>%2$dQuAa0mM0m*?Kw*F4d5j$3sX5IVY`Hdduf%mh zbOz#8o?ad6YnpAJZ;?9$dg1EzNHd^7m5`DO2Z+cN?^zzel$FcR{W2WcWEMYo0SVoK zCRb|RC-2;^leU@q3n^?2N5P>WZZDt}qU?LdcE95hv?A`z*z`Um)^(=cL}YS|^mNd&>$~97y1; z+Stg$PH4S@`M**=a^v~`?_rFX=5PtmoM?g%#a}7h)vax04vbX12=s-)tC6SJMa9|a z1j{#h5W)`*j$5$xiX!! z)Jc~g%gF<;_R~K9818@15irV+?-IxfHc_!QsvtM5CMmD*W#&nbKB9;2h2`GA?^Rt- zIdl4pn$o(cNXM*5+%|J(DX}M3NB}Pi@FG6}Rgr zEuDt_Tv2%oD&4PBfC}9qq9+2c33OMUeLTAv&6Ue%sVx$ z{_i{jN9}?lmKI3?y>wIvjYRvc3ZUmN&EjJ^^R31K9(w4aSyB7V6ERBnG6E-HUw%+d z=>PF;C*2^qk{;Tf{IxRpc?Gi|07JhLq_p@`H7d2C*3O1^x1QHeTb`T)yC(ae5UXDX zXd|m%e^Y|l*keZME0}>ZYvjkw`8%O|=>}cOFD{^dgx;fQzU)coso6JtI#X{>vVw)E2UZr0LUz*Yc4lm(qxr0;U z7vlPacDe>Hzmm2Fg!a++=zSd2@^_l_^AWvm1$fqb1^bUaXy9?V#*@ZI-7gEV02!d7 zBH)E7aWR5U{*y9jLWBQn<+aw`E!^ji0uGLC*Zcl{jZktLR=Y;Ar;~Qq$ZI9edmCKK zfu(zoIM7g(%^=KH)rvf6H%^nCY>&=L>z?0<{)e}$G9**nD62hD(Fi-gHmy3Yu9)@3{|3eB=g62y^KDQCpY9p2w9E0UL;C4HQzJ`Q(g*Zg%JZ z%jA^aYdHI;+`C$7QT~xuKy?x$&dG8n_CSL||{}ej=t~nD^p~?YMQ4rE2LrL%ou`k=8ONdYh;6ksue4wKVajeMYf~k54`TOA+Jlhv%&NG&CPxPh=EPT~%8Sz;_N9(%d2Uc=H8r ze7GSwv1P;7P5f?lzXTa>0M=om?7Qqqd!n%4D^cwPuZ@eZ8qlTC(+VsNW9;An!sc_V zlUmgL2C#|G*$y=m7mk`0VK$w_7~d`t@h1*+YTlIkD2bxFZX;QG%BWSJ-~}`DTh^&t z0Zsuc)5BZIq|7GCn7D@0?O!A~c8zi31L#Zl8^-JJPM^8(_@2bQ3;j>U+uvI#3YUy@vRQb8{Or7rL%Tzr4W4D2{A!AT8v;k(x zoSkFDl>=gy*0uD4Pv=2zYSIB{d2?MD4s{WU_A)Rqc&M?`)e>ZF^r=mr`LK1!6#rhj z!~ZgHBli;+^)xHRIm= zV+VU}Wf!EAA~SMNcoyvpddXWe3;EOSBOClMRK9Yjg{XE(Qccy@cQ5uHa;yXy3V4h1 z-5Wsn@n7fxe~@F#g;HWx-Bz2sudlC6fVIVBW1t08KE!S0lu@X3JY{ek|{-jK#?t@5NDU#!?7c_ z&H+-0{3q{O#Ze3#^^Ytd7qWoLyY8Zq4O?xST__(KDahRKW5#+5*Df$0VU)OqvTzoi zL{ovscJqPayDjbh31<*5S`Um7OWfGI*t zX0;U6`dFeiXZ=#~nXL|ti?})vuSO=W@&`W*gsjnkyOyO7)Rsx4Nns~CGmBe*Y>G|D zSW*m~c0rplya4vffQ6RBYcZ|GB8BLK(|}mqUnGsb7j#{Vcb~igda0J|)00_+>7-%H z^*Epov^qFOU3?#6Kvya#@evC|e1=qHMKW~8BUVdkavixd0DbIj3GuM^L6^#QC*Jm) zG@$N1;t-)xwmY%+9)n*Q^BfgYaS)ZTJVd6BN0Fm_u z*J9vl2DdeSla6Lgp6nf$wA*uo-uR5|+V(9>wIC(U%64;i4uFEfq->7kd$5AbAlhza z>6QD2fdR#o_HK5~d-yj>0^oxz+j6w2H!YP>$<+qF4h|0oF__He=4Py?tE=n5!NJzV z&*PvKpqO{4``8Sf+qgF*S=Dh$BWimD1hSg0k@e#`OxtS6F;!oVDpOncJ(e*`bxF#| z$OyAi8l@U=A)i6Q$&j_uDo-Y!FF}Pfv3q%az3lv8aB#2!^Jnw5K91h>*h7|8m5SJ- z;m(%ckZ_`X8sa?2kg1G>o%kbm7;~nDw$b@@K;Q98q-YXbB!DS+^4Wnmk97`+Fhtfo^)#qBOTI8ISY{LD1nGPNhBo`ICk{Y@ zAxQlzD8On)qZ!QnU4&e!ff4@`db=(#=x#qx{2C6|Cu{aMuG?i<{afsc`&47&V%(nf z%v`hk!~@V*J=)v5uSvA)-9x0qneQw)!AIrsGQmS%KP|ns^s@P5X9=v@i(-*hrMn|& zFkEjSN`6`K$4-m+NW!{lf1LcKv7n+v&!2Z72_+T{eo~?rL3BUjB`yAhKztz&c?~n< zq4O5veBv%JiamLfHDWv~K@)jHQS?%7Sqle2V^QtTzl_$c#aGv^m5h1fotv zd<3@HK%38Yp*VkQO#>ZwG47zlvkLU))o+HOK!)qid$YEc z9kMdeifnmjq{bFJFP`>4U;!TsF{{M%~Ea%m{bEd}iD>_Ebz3Z0o`tru~-XAicW z*_)bV0K2KJEI6yCVcXlW*4M5Bi=Ks@`hH;hz5`~xu5rYtvlie!^`2K0ygtT}SJ$Vm zAit!T034_X0HB5JR1H}bFXiI6a~VPhw(smNrm^hJr8FS5mG4W_25u*=Lj+s{7PC2T z6OAu}R&`CyyZA_3hCT-)R?Ap!j!^l7m_VJbOmQwi+5ZWASOY+RGNa*!;^=wG8 zBoc|h$vOTz=7R~_WlNov^I$+%k?U3BOLgH@XcxfZ=`QQI8Yhhg&u+eGS`Cfwp1&!Q zk=CryEFHQB@Rq!1`|*h7+hiy^ARo{Des`27p1JV|<CPrD~0phqj8H$|O zS8dyLbEfH;r_T&zq z;>tHNJX&JPczc%$p=;GUY{7$yoMRs>~1_&`8vYtV@G>WJ2Ct>U~@jq zh&^AaAN}b0{$Zu+Ndroy2K(fThKvz6SKfiK2W`uYT-PTZj=soq<9@t$U7n9b4Q|eO zjL&xZ{-E(*G~d(b_@_e*RPU@j5|`T#V|Bw6m)zNayoKWkTgD!=+nPAnNe7Vz<_waH z!fIBHny{1E(KrRhv)nZ2CAAH;f1U}=Nj(GcG_MmO1J1hHQFgQnA!p^Zqm15XMR<}` zJ{#1^FJa!jnWm+^xuWSV5{YX&CVSnsL`=O5b1hEUQy|WE-AWMKo;&zB_gYN#Q~T?- zy>Zl;oO<=1M|?IPJe6e=57_M{-7%^>ziGNQAwhH+Im;b!1(KP1wiD1}`*7R!KR_U}DcU0KY zd5@mrES@t{JWLzQjurY!g*OSwq{38EyO7KyN*DDQNf(3^gHci9^LiWV; zcf{0ACLBMJ^eZqHNiN8btZU5rx=V3-)R}s}r&${b>1u~@x~0Tx8(#b^)5zQT+iEw* zWc993 zd-<}~vEiO4q@XBkThjBg%u4;@O(ETaw>42)=CUgEwFYgw_t{|kIkj~v9$e)X@}2Hm*?Uy7=eV-PKbDjxiqj%amsD{KS{MW0mU|T5 z%w{9D60dkYL)dxZnJN9B^`+m}Wm24qUstL!)c@KQGzJjXDZ~|=?|R4_$y3hp=m~h+ z*3fq8_otJXLLD0vf5^tjg6izu3jGf2GTp(XA-sSmP2NWR5i(c#=w?8*-7f9mw!JIk z@=~3y_4{|_U2odk|Co@P5T`jWR(|@T8)5PK)ze_8++5^U#g4 z$YqAb4=eFjm8!x9RU=P++OXgN<(@C`M=engiodz?M|PJc1zq>D+d5(hN*THEl@XFn zS)`)s$S0LIjnbY}sGV2w&Wjf3;NmoGBC<7b`be*sd0RF zBFzuEOP>)FI+prt?-w20OQU^#0v$EaN|kd>WJ5N*-&0i%di)6rGj%#=AQfEoJ(kIV zn@1b_w8YROs&8Kn=FQ`#KQ;Kn? z$yK^UZDOu@$86vR(7hs(kQ!RkstCnuQ7YIibOcJDl{`GIrI?Er^=<@bDOaz9d3@-v zVxAJ@9#WTdbe_tSKud$A3@LyOQ>A^A$Le6%VMX;JN%g|=2_Opi^Cbo;Qo&zK4(gxa zH=i=39{@l6LJK#K{0t#4+kn4H>LDc=_^ZV+=(os^Em9+dzb142?@142^1o#Yh#UO* z|Gi16vPQQ~t}cN$Q&1&jsIca_FpymVuu=6NI31M1U%p~ux4SW$Te_dfY#q_ky~NJZ)-ALSuVPZZB8P7r3u{ zIMVd^0&K@T^vZ^h7(8rKjOAxDkAK>jRX@W_Y|+MF0hIzOpA#wYm6|?fGmDGu+*J!< zO-$J8Nz<(%Yut$Ea*gc*T<5#%$P~$39v&VJyR(@gRxvp5&0O0#aC@p7jL7>6z6i=k zaW-$v_NiVRU;)y>S!_(owH$ssf1iPGZG1IS+STF*X7`WhPJD$V7{e zS7#=9S0Wz2oLElvnGE5>Ku(N?oP7H8b` zSuGD6`)k+FV2jCTAmlte-be8e@AlL&^4hK&5y4r>V=z&__&HF}1M*k7v=MvJ^Fi-iY%dqgq(MBWNulnu=6nzSi z(nAB|9T^8;@ zC(h5HBv4m3QyjB3=r_dFZcrx_R8@%@k=LTvo0Ywbb_b(t_a`IdGQdAi7wGcb+y9t0 zF4VykBr(CorbEqY-A&VT)V?#LmK{-yEfOQtRPu1xy^oDJ!uaCOWwvOL(960%^r(ox zb6KFLwQusUa%T%MR*|XO(3EU8&tSRx8_9OpJp=pV`aHusU6N&)enp7`I`{$h@Rog{ zN(IJlahfAU&w1B>^PIX&#anb*tRZ)%4r_Y#_0%!^k+eTqX&TpNl;v$WYh;_HUEnMD!l7Nl`W;rwnfx(X#bSm` zw@AJ~A~SKHy+|VKYj0r}Mf6yi8O-I5iXUpNUHQ~%OQ77{E%)7~d?E4C?qS<)23})b zhwui^r(r{SyJ@^WD?y>XacQ)YX6J5z)4w{v9U=%xMoDwh1?*Vw-|`#-5n zJ^fB&+}}58;LodlF7zEni=*EFRGo~?bQChLv3X)5JE`qu@(q6()!aP*K>calk#CNcW_sF zZ0YS{Ny#V|W9_5zDwo~l^4u~4!^K*u#gJa5aI9;~)r+;Xox@REdpQ?sRY_Iqj`%(s z*P|~E&odFgUj<0|YeqZ@l#$HCqaX2W_NJ3|7ybao|Ih$ZARkQ)&#I*sN6ZVS3jQ0m zd-?vvZ{Nck^Kn#pqfz8#BWA}WIgn#vv6SQ+l@9akyXJ`ZK;Udt!9+T_-xrA=q4N2D zR;yPoS}x#!!eSLrF=J@&WrY+TR`v%tDNKu>K{ZR07n7T@YMCGOka$4|5IhEe=81eY6uJX}} zKss4920-k)0$BU2Ls>ZK^*L1J-J$o1I%~zA&Y&p;>s3C^2*3{|(_c95{j-m6iz0iv zUmFX2c~?L00Zwgdc27D~=+ftdOTP=?)qYEcm%+9X)zD{DH&uz*WaORS0Ju1vhJ-6H zW$5H1l@}$aEvbA}vvOY-)R>n0fU!E!kjp10u$#!Vo~A-c4c36cJ}0h00lVDv<)`9$ zngK--j!c{_HuF1G?32HJBKrai$zr+-rbv^0k7qfKi%5 zf-5OXclTfBrbWT^%xFMKS+D?4FR23(p_0rzxCX?ne@^mWA84|tpTqxj;Jxse_P{Y z5Q%ih9J<)d_NCR)h}ftN>% zq#50rY0&#m!XCFouswMH6_b$7b#peq66=Y{tou5lVx3oAGMD>c~fll)0x@%`Bo^Ycr2MX__XQb zaCfFdMLe1`t1KIqt)4k0jowuS+^;vvIp11hGh5jdSF@#?Ve0zzk)PMJC_y&NrnFDX zWRR=>(qHZv1MIsb{OD}d#^&Z^3kOc&rV5+qr&H4DDyZMgxdFLbUC?b}rwxb^u-4*! z|LqTGOtN9xhVIv<6Fu#Q`zwGxteiIj>G=NaGI_lAqj}I8S5q`#=xG{fTSm9M^v?gE z?l2n#c=Sc6im)Ha&rh`UW~=A+rIwF4$kTG_+(;aqw_va>QSA_0Z&QdD=9P7r&o7_a zuBBk~`Zd`jb8svQZ@xr1*HyM2XxeTNU}BrXn9dIn3=ovWxT)>fVTLtd*#?TW$(4{3gT4UdOZ{Q1~e9^e}yzB4xc>;1&KVZ99V&UdE&=`^0*p3{x#^;-z7m?Wp_*@9QbzFJyMWo~B6fv% zM=)2+<>VIaEmaA{m+Ny+17WxJX&WUFNK3Bn-(Q?B0)9GGio@8Btwojp0G`o$n8D_T zMGWv&G-0>D{4mC)o?1v_`#aFb8WUxe>G@k8op+a_jlA8Lz+7Tr0S`(nR4Xehiw4c& zCO2_d%n9+0FBe?CKB8_qPg(Ip$L}rIqQ%9nYT46K=Yh#jXO!DCwGASFJw}_3!|`;YDPDE}aJ=>> zn3y*6^QYTl)G?SoO>Jt?<#XElsu)bKsx){tRPNl%tHD`#{Cq(beMj-b4}W;g++}Jx zk6(E);?$!^Tu-9JgT^(dZV4-m*oq{c*ls^HlNQo}*&Z#|#Sp*KVBfgb%o`9PjCd|p zupFc1lmW>XOjFF0FoHs3ZYKDR44z3SEP%X~y7R`5P25L#Dt?6RuY(p>qWe?~r(PLn zi1?O{ho|R2omL*{FYkg3{EHG2+7t~O+Xeb0=RRKz$@9jqHZn{@Kcf~Jc@46_TM+qA z2!HcAHCzQO4FZpotLrq5vWw$`;bD7w8kliQMzmq<*w~mCc|EBU095#cVlx37ld)=d zk%>UQ?&0CMyC+1LmjR*_!}*^bVf+j|0+^Wy)M?M?Xo9lW_!%zSY0ZoUhbza2pErE!o1d>EX;4#%}EgXYDTe&?(94PLw2wKOn?4GkPwBGP@5{jYS@! z5xdbE#g(g7xm-&j!&Eqd`FUd)e%zm94VZq-E)h;P>Pir2uz7czmP?6c?Q!x@Rc$RAntTxeJqU!vP6`ptB3t&=I6$C3q_1zzC9mTt zt{fP~9Fv2SvvUT>CBXvtqIQn{owYQhy07Mp6jbSOPOa3_F!3)!RcY}Vc@rnOe(heL z?(77Sd#&Bxn-4&j>*hLinqsr|7R$_pD3D>{FpJg|JL3U8qv9Lb07I|U2IfQMD-z%T zJd=~OT-+%kvhn4vW4A1e>4!glC6c#mlSCY+egM!|d`XJEX)0&d9vi7=S8>g8x}$xh z!m)V0LzE_F87QEYuTw1ial588%b}mX3l7_avn0A*k;7db~Q`+n1{8*-eBk3@^T@g-R|hM@T7fa0|SE?aHF>%{AIoNJ0}ZORAD4##EH7vh5Z1*yM;g+C76Ev^Y;o6gMR7OJm9bn@V#ahEG9BE^My@}t@hZxVdg{U ziq$ifF7jixz`Heop@gUFz8Lfeq1{~tW$+Y~aHau<%7=*&e}q>6OO7jppV1sm<0u=p ztMXcMOp*_SaWOT3+bUaGcCEt|U%a~uqNI&*CwPD-fcenv@6f`dh8d5QPV8viAu_uI z0CL#u(`J!{6`pvHxoqk~O@qbe+D$hTfejbbl6O_}DNx#f{CX^mS(*v6@%l!ki+I&S zk$l(>uptJ1+O^6#xJxidbUe*rDgXCH;U&Kd4l)wt)e!UXPp{a{9)+xhA@~qDHF1zX zQ0Bpla$VFei|5HXJ9WROe-A9r@1l!N*t3;#7qXe)*N4w9IJWUBW@`UD%S@c)!45j% z&nSx(&XxdAPNz>KIJ9zI-O3&ZP?2+ZLEFH&@AcEdXda_P0Cn85MZi4FhDt2C%Ef7| zavQ93V<0^PH4)}Rnnf=j9lnp=ok`2Z`3=_wRez_p{|usGd~wIET>2-1#fxRbT1KvS zF4YKHH9f`ci}6Jq5a!7!aJKb}5_@_ad!$?+`*-k0)XCR(Pgn{QRHZLQ%5zsR%Q_62 zMpHAKWjT~G^`x9CQ3B&3IO@~`;k6b(D}4u_Wq0}l5SijID{a7_W2d;|M39gfd7tRM zR54xDr=>?djANdioo)W^$CSuK*=dRa^mqwWw*Y~a=$i$V9iH{IRvs2vz_`r1T#|_pTk6YtdV0bE814i7M)mD_dUzB~1dE!15N;!kA+AljZ*)mJ zQb^m7OXqlx5eVp<&AcG9j`)}$!6A!J#Qq(X?}kBSmo|6Kw*2f*Gh-M@o5-fo9k?=tPjm{6uiIET<3}{)~dlSe^+kZxeJn@ z8erMgRlG8y!Fv&G>RP$V3GD~*`adb6V&9neUNcKlTR9%QBsZ#Rf34MzI^CMXGcH?0 zh37#LbB3DbXH;1@i?Urs5Hny~+EVegK*M>G58L>pgcUa)~a4w%0^h&wPw zL(-Gm%cI*9Jb?%Fsldv$|IpvFUL^OU1`!AMqmjX0Kf@FUd^Fx=$a2u8xSd|f-c;;+ z%g*OGyUN+uH*U#Bap?&fGn76uD8};xCKw+N0=YZLMH~Q0oaVB3mj~pd5@5zPOR+@w zz)Pb0fPt%`va<3f=9(AJ*`ttG30UOK7M6e*;mXX7ppzaRyl0mS@ zp>Dqb?#e8BsoK>b`!--ngHDA0U{?d&yKx>#DSExiE!xTBrT^y+a#A36)qUmlb-G(x zJ}~A{6?g2Alks?K@So1dkOp(%-@g01VG?8C7^aEz;_*{X^;W+2tpSj>`w{F zh9v-6(|fKPV0}9vBX5{1CZqJ$fNM`LRV^#sJgIfQ|A{QgBt%>=%a{a$?FD`em;n$u z;61*!^N_q}XC}ttu%!9t!Hyi>#8x9MBPMulL?C zVa!2b#h1ZOnDr9$>bMTisTlJq;IJ3hi1*JMM1b5+P`0R3V(+-8^#a830e#2z*PY8% z%XsK0vWFX!y_Y=+!)lcIi(V@q2Hhrt!t;*T0zZlYlpd$%nN|wiLf><-lo(e_j1yb? zd{s6KB-6}EyS(IR|h^DNGC`4-Y^U zfW4e=>_7x`4-bkMpehE+fU+-u44!SAlD@#3V+!(v@Z5q*c3FqT(t)X7b>)2D%2Gq1 zMwrmMgXTyAXgioShh3`xS(n3fg6nm<-^y!u8hB1r6@YXdQilovqK<%UWCEmu5bq7F zO*>asS#+^Pl~7P+4A_kba0tidYtb6RxiwtGxmXaqlt5N>J1MetlJh}Px`+JQP|G-s zn*jCNtPR&=eHRm+HqsI6j!3+IaqRpQpjd6ZO&7vp(t#jKw4n!^>!`CfWFd3=OnYCO zq%Ib8#_+MYjy#nP`vJt-4oE>LVEd-wih|7`O2$DD0Ex-OYpb!{meKNB(jsvHOB#UL zGx*)!aiOpYlGsj|{GtIr>Emsq0qMouu9S6depw4FB7r392dHB`1dy7!0Q>@`z*^Zg zt2et{@?K4JQ2o6yn109OHSnz)%Ho?EVUGcW`3y2ysnUJ&-f~iY*$_J{a{&mB;=onk z1dX))WOo-3R~G<_BR24Ohcb~@{O%5Y^_b6h1feY$6`KGEmSV_vr+o$9UtyHxO=#Abl2_lVE!K=~M*>>T5koWPU(-LG~t}vzOWb z(~%3W0TG;@H==lifvNx_oyNJE23oxzwfoVOFdi(i71*D9B1p5nfQvBnHVzZWTS$Ze zbXIiE`}&~?8wzk{nJ_V6816gY{noqXdGZ~fZO9B4UJn*M_xGR%zGdVGP)QEnF!Dw@ z$TI-wwZRmPxniNH71Y@qjCqYfi}I#*f1W-_AYhdOrpv$yr@!2#X$K{mXoS5u*G!8q zH^WvV^f6F`!a>Gl4!9sJ2#tYc&m7dk7@R5d?QeLmp1H2@d2O;4+O>I)qL9I&pmaAN zPayZaF>G6IVAl2~6efzh>fx^C$Cg?hoLqJDW>-Jv<*x~7QAVyFG*x1@K1LTmH z8E}-Cq^Z*}0v(ChX4EbnSaP6z}D1lCGrMU`e*Q+xu!H;PCDnb1( zx4chcKCjpT$g>8tu;iSe{PL%*@=KnVXXX543=G+|d_zHbE)x z9XD$*Jg=0T251e5j+I*9WzZBU4Vv}mJ(w6_cqoS8*?;Q!CNe#r;5Osz@glMg04>t1 zhF{9P^L9K)E9}List{tSy! z8z8I(7(!oPB(!Uoc{LSk*9|3qL(1u_<>1XdFk4X!f{kFn;U2(#HQ<#TFq}JnzU&w1 zTJBTobC4-yz%DBUV0uT8B^J0az|tscBfa8`lJI4g_$PPt27b!RMO4y(bY#KU|JiF% zyPUs54W8TmTQ4J1?nTK*Au8X$YSjm=8^En3HKpPv-l-Sm3|69R7-32iL$R(my8ouMdzK8x_j; z@4bwFTO+7kP?h{uojYLr|Fyt>?O6SH6X_q5z^>BYKloSG{M#D;tEu+i3;frP)qgjU z{@$7UpXSr3lA?mzul!3ty&;zkSkE3dPzU6-UlVB{nmqsc&%dlmRn*rBbCL_`E}D)` zi~^sWxfj_|_R$~x)46L@H^dH$H zwu)5f?s}wO(v<{t?mX**UbNMrKjdDkaMG&lsIIzf*JFs^VtB4iIv5DotYI$Gy)l$g zlyx56XxVevL<{sBsa0-~t+1_Z`VR{k zQ?xf87mi1?R){@_& zT@6ut15xTd^uorP|DtsYg}P(UcX&XK;-_HLN6uW@;h!}c-P#ntX+J4Oos$kvn$}e! zZOq)1(19k>-K@bML6ujB_Mg2}+B1f1drDExjf4;>DaT>_hT1 z@BW2M+A?d*2qkT48nsNhOwzC4s~zfe#!$5P9#k1*oR!|GuqN&^6&VQ3w_0pbzIpfC z4LoVWe0B*cOH%9zA6m~Ll9@;@`Tb;NHBMS>!@F~TSRiLjxMBCR4wa32%mGRd_5*54 z`>kY95CM-NRcOeGOr~onfqr?c#=JmI+l4pgJN`9W<=XW7#N@4`977x$1nLrP{Vv06 z@oc>%1J&L9v%0+4ftm91N1j);`O9u%AAPZk)zAxe_Uvo6#@Ebk9q%#R!~~G``W9&C4puqfV=;taRulPhMC(FKcAPMj{>h z-~>v&Et8a{u>|P>g8?EFmf#uwRyMAry+1DX<~QGo7U8tC4q@(zo40=cncFA%+u_u_ zE22feWnKC$HFcdq-^`KrX?zm8NmaG!*O9Nk{K78kl)`1;P&_?0LSPcANPlq$Z@Hwn zR1?x9vOQ9w*uBgYk?5I(!QdO-fS-LE0A_KYJOS zXw!M>H$C`um5E!CggF(X@c3qojF5o%(j8s+n7maaY^Xav_ISujD}QW8;d=2XPFX?0 zW_w=!wg`E~Q-mPa1!YRhg;;A?9n^G}1;g=B=s%iTL^PP1AdC?%wra)gkyYiY%c&fHK;ifiKFl z`n|>^!bL+ULPw@&#zm~sv*~KJs~0W!#wj1&>rhwRZ*G-&?uJ}Vy^xgI-_?V*2Y1-z z92 z#-i&5Ryy}gn+z-=;4Lo6HSoClGu=R6X+;{l7zMLL+~KLFj?|3_arD>#0Lb2>x4~kk z>N?WI z`irGo9y8@ND03u7{RSEwH=5oyJ&(zIn-WTN{3&Fz6C4##QT_$2H|vt>PmI>PcZQNq zfqQ1zcYsHUke}o$@XyE(`yF_00RH^%52iXTkQP`pRWkWtKwod@=$SKT{&`g-3|LhBC!nT%a-auLB|nAwqS8P- ziYn+X6n%0Iq6>=)2UA}ImPsW!aS{&CVu#mK(fdIMkkriR$%sz<|L8W6O3{Y?N(u^X z>yh9NRFTlms8lo+6%|jN3hkVpCdz?NedNyk%Q8%F#=Jz9mwf7AH+{W#Dpgqj$4tTS zEtEcumk;LsfBS#5P+VVx5eenAi2p-|SF8Rm(SQ73A2<pphPDO>tl`D-+P5*bhM-={_E3DXMs{Ejqd(j6sD1sXOU%URt z>T|NQv-b;;x2yLG{jcRnBO+8;K3ESxLKhnVm{k86@z41X3;B;hSy@`LVL1_N!5pY5 z=Y9ZfB$|o)9Xa{>eJxUZ?=r5TO4*qIqb&c!qcfECBqt6i=?K=<;>R_nSfix~5HsE})dS8t~9nOyJ z``~^mMrzr;@m@?k;HzY*lFJ^zVK=5>e@G+%YDsbtahf-dFtOpr#To4l4RmyYeNa)Y zHz1m8t1vD^fvT8xo?(qgKVbq~8Wdf#VCv|m39m2xYf80j4cMkHChrc^EIAt^3j668 zL_l`4<(lnaSj<@+z<}~XfDmZZoO=(>O;9DrBAZ~JYC@Br!dATOh5#(RPk?^JD-Cwd zlw5GAhAQV^AsTJDMx;46P?(LMvF?!Av*Cq~JNG970q2}SZ{pK2P?UVr+zKi+BYdFz z#f^yVKO;85IUqFbKht5BerJil1c6%eVj-fxaaay@TMaPU|1+uBVY1~t=sFp%jyGsl zvcqL|b0R+a5}d#FhGit!szZODP68F0uI_HNj9f4kRn7y*?UlDPb)4iuJ%{TP#ea8FK?#m zy^e@|>eIxpGp}B~T4b!LsabvY9DUX=?-pCs<6Bp*jDy2VuS{E`BGKl%1IiRUxJYsl z5yZpjMNTPYEO->Ca29X-wC@oi;5%1ZAy+Lg8PhZQbUi^x3grChJ+m?yYLnBmA-~B( z4QJg~T*p9f=q;F~G!kD$bpZqg6OeayJ+Y{Iy~Zo&vUSFK&q(igfky!?gYTrA1l#VO zbs~x+cyFlSG}zBI7^^}aF48NR>8Ww688DMjTa7|F0SNKaY8TN|&0hJN1Qesztd zhO>K-E0oUe71oPDlZvgE{IO1CJ$rN*Q!|0URhdW;1DNW;;Od*DS!pNb7n5L`5V5aJ zM(A;A-i^-|k3EJ{C(hvfowTjb_Wv;cw!tJ_h-HpiE@M7HY%!Ad>0;aimy!azu7=Lq2AEDSib*NUF84Fa zcVKPPucvL)GCmDLz8ltA61v4V*=w)5j65l6tuvE+FT2#sLb?yF+Ap6&*3yr_kXXtm z-rdmdn;t}WT>R6(#4xaqAvjx9>8CQk!{dYt1Q1uL1Q3{fm>KqI28X9)@~(WA;oszb zb*>o9+M?>=z(!h5MV85lEVC(+Q;L%3rLgV4PIl<$^^o%@54Naz=n+AFhAdA&23a02 zoUWQ;&il@t`KII9w9JUq4WsC^4eU!K06D7N$8`g-y)k>s>oMWdI#V_yw#{>ePny@G z@|6II*JFq#?JN3ptBc<0BZiy1GcyMCF$!<@Wioq0l`^kJ%0)7rQuPL6pqlbSFve8Y zctcxaWgYcZj6UXQYEkB#{*2w>(O#jd^!AEzWZ&b$Uem-dpQcF(Zs6o4EHJ z<=#ffy(T+(6AG@n>tLa%gM~&gOHoLn0}WrwRxRcXgn4hJmPFX$SEx_5>ZMUqp6&F1 zCln*z|7#6vivIq9CwQeOwTLP-l&NQ)$R*j2 zd3UvWtDQMxJ@u}7ZY4>p2Q|(t=5;oFY9<7I)ve6jkTAlimu#0ExV}axv|h=Ja`b6_ zWx@B=#i738S~lOxaM=fq+^yKDT_)Q7U@Sw{Wrf&C79ZR|@UG@@TZVx9;*ooD$n-goMIz-#RO-@w9LoDS0zYOFDGq7J%OT?k$d93`J_7AzeRQ z9u?J9l|TyHHIiCp9cFA$t`#x9KmRy_9E_mJxdTnSd(bd5R`zuh#=Q?)w;wXlR7Fu zAjrDiY~nQg#*Wt`*2Yu8YnN-1l|)!39^Ch@A-sG+UGq!r4bz!XUE=wx8oaxOMT@FC z8+uow${Xq`pY7Nqp7=%`k~_&D(6g3!5J>*mm4O=NltLHUc%J3|IjH0=vSTx(pq{*q6)c2`Q%9e;yU4o!I-${az zNus;Hs1ZTN0FSrEPOV~wn8jHC?m~bOWIQ6uVkB|mlmr%e*L735ai-8>$uejZO&+(P zO>>py9X>U2e(_9Us=(oa$??5qa@oOEuh~5^zy{tW12kd6UzTzp>hu;1lVt)E)w4HA zg)?b{;NGYdok}aq>Lu$vsiquL81h4$Ze5&px&`Pg@zkkFb07eV$4HKGOlA#*F*!cH z*2O8MeGlB!CGI*q}dP}RM;oH!&os{<`Kd>Omxa_JF_ZW{qhpx#J18#84UJo%;vEN|`eaP5;T`D@NE5C61 zfoDLMrj~!VJr`HT0QDyUKMaQhGIAjTnOYf4-Rb>fs#`qGQ+Hz4c%z!lOw@R1yj<$( zeZAua?XTe8R(^r3#mD_Mzm(B#Xk2USOlH#N6<&`-#rv}W2+t2(a4AK^9)cv?Sw+me zyC14{C%QxS?QTl(1e?F?Iwl3AP@go2_6JsDVax69^tCTkuSyefSxaPk_E^P0M*5R! z=W||Vvx>WWZHJsd$i_n?0ZNuLD-C4)EG^$T?)DZ{^HA}zsPPQnk$Z5N`3x_am^_?+ z^dq7FB$5(q-0wzVnoCSK_&U8(P&IY7Oc{==6ZQ8XWj)BQ*}0zvN=!we7+LShD_e`n z2Nuws84O0n`*&MAuAV{c6j_&?9BrE8YK)29s}y}pI#M2=(Iu&YH?M4@k9$qkHD@?s z_kX;z_lI)gN3!7LJ7(w8j&)s;8YqE-^c@~5IB^A~lveB*!AX4%N3na9f^Vz7F%4uN z5K?i?*O6#3``|*S#5%E1Ca}PLmb$_My^6k4V|i(4H>%NyRR63CKfDHL>7?|ngkWH! z*%~?fFJG?lfqV8M1j0uoN=%ACfA0H`;^F4dd9}#ymkO&J{bek0_5|fRe07 z0C-9T$fippaLEwd^lo5*kh*H+SA$#ly-YuH6;!0;-Ggy507UByD&Eh0ZAOh23vU$# z7U-R+S?r?3lt)H)qY=EZxCEi8?t%iwvul`fnA_d+Y*kd_$`E;{0=e@cr1IhNVdJ52IEplDVju9K*>uCP z!z~dt`tZ8Pu*v*2`+!$VTPWROCNz43U@QquC^;A*q{SsBtX@SZQnm?}>F%KyIwL)& zv7Ayud9TKTzj`zer+yVLJs{Hub*4JbHg2iuv<%Nel%m3h%cm&$F~&2ai@;3_u!!kp z$s@3PN7iJ6C8P@DKvyH2=-KGlU~$FRicOp@13=Ds#G9NEPAYRrH4yK4QwqRDchBig zaf3iJ&lhWI9+t;(^C3pn^9%=L@5A8mCMCUf=2%G0)x(Y%rW@;@EL>90W)M!DF<9iS z7Cb+I*i-K*M0ST=Xwx#UY;2$~%Lx_lSKf;nvqpzBh;yx0_fT{Kr+z=@A!@vyvCPVq zaUWXu90D3E%!#a0$|2qAyu0BnBd4BMcyTTP8#|vClcE;kJ^eUhDP$gThB*YdR(?6% zFw+NS|5Mb&?cEK!w4|$2;W8=ZR~aid5_r2t4gI_Q_5%(7?A@G>Zx@dB_E-pWUpyq#Cj4=M6T@065>4 zSbSj2zka3G;epNjorF-M3Na}r#+L*HBRL;J!~XIbAkezfdussF{x!|K_3j1K?Xhn109vCbDb1imF4$eq1t~2^KmlbcalGwM>oy*KHJHMyQ=#cVN=wm zg1jDXYk>L0zA$5#Qyx{{EkA-WfF|+q_QAeGnWYFd9+x^qR+oVJjB>4xt0$e~N@!nq zC$79-=E$K#qxFi;1jQNl0dQTSVxUPOs zeIth7+kK8_e10Gd#H@R3ezeYb%C*iU-e1kk6z9{`dfCP41;T8S0b?4?^f(xQ9qR4| zQXfn%Xul=pTK*{jh$5SdxB%wu`k}V_{n>kR0y62kJXFG*RuM`ZWscn#L(y~I)9Xy# z>FG&470_T|r6N(q*>C72bGE3}#O5xy-ATCiH_}jqD$ziMAxUn^z%b;SoRZk3)Q8sw zO$(>S@5(i~f}_jS%lkQq467jm`c#H9OX2XCOC7gX6H$!p_w-!l_hV3s39R>LT>RF6+%aqGl~&gxt*ukH5b9e;!t zokR93zyad-EEz@5ezAu%ZOl)db2fv9i-|xIMI7cRtZn*$Z~c%WtnNJ%o=D$o-3}zL zt8bl5v$U8PoLsFlKM~|bSVIlJ4j|M_!Vr)~u*vu2wBt@iZ6#{)< zz5xh$U!qE^k*TABOB4)7OC!Rsx}v$kej3R1GKdl}Ea;P0>(3OXSBV&Uw=lixdiQ*4 zQ_p1k44$@qzPvFtQhtI+@p9uFOk)9r8@2&Ky`lN>l}I^IBnk$luxt(Xzo@sU3CwSM z0|EL(9Fga(G!I2li}EHKFm4;IEq&gOjOs4OPvoGgT_;SUy4A5`oY2-}kY9n0{P3g~)`4E2-+ zd!RR{Yt*nBK3!YtVwbvW41#Q;Lj;*LObJ1(J`V=)x<4XV zr3ezJ%;hHv!CV9Yw!2EuNsT#0lw8yj(vcjq7<^PF<|(a@kN5Zek`f^z=GJM z?rJK8bek|78Sf}ZoKj2#y6+op;J(Y54Kvq9+gG*00{Y+roM%t*j%n-4;k<{VO=RG- z-+uoM3bk7HLYlGxi&VYXu!P(+m*wHUbnR)7CCa`Pk5=2QflO*Yb+oN9MXz|{A^)P=jn8LZ-X zq#pdx6B%<3_?2upuZN|pV|C+AdfJl01^@T!#mhm3_>Rx^Z4CxT4B4P@oIHgL|Miv0 zhUL78+j|{dsiYFQKwo+l|L)}gFc?MPzx20(kAM(u%V`>`E6tzwJeM{QyPGV z+-cMw@vJ*2ghfam7?yjY^sO~;H zgl2)J(|Z~EpA0kL`Od7tNn&rSYO2`A+>CK3>WCnqKd#A%sw3mi4oRG^-xlLUu0S~a zn?k_$Cpz8IojPZ@dFkD}(gr)E(>C7fi@DXEL)Lf^@gX|d%0P*wHnl*zWh?*MQ)$mw zR=%>@!qiQL2ksjw$_tI%I@^>`F?f?+<}vdyUAg|&jCcmqX=r_Oe?aRKOBwt$6WXN~ zNRB(>Y5WRB-S$Ps$b5fkZB}*H0kP;t1Z|Z7-ZUjvT$7Rx=o$kuIi2?G0jk-hahaQ< z0&Gg^kqLMIg(vQT2on@QxDTT?z|`P>3V8S zu594psa~+?5hr37Z}-5t7m7$3G#MEy`CuG{(VBg;v{mqSXZTFzCyR$2w&yd1wwi`W zJOSTD53(l#5SZsc4d~@#-d$fkzlp2nwn2qmuPl~ZKIthf_+M*nut;{G#SLHZ#QyNN zFxZxPd@K4M0egWVztiv>ux=2bLH+@oL2?;akg}LR2+#*zf#hApz(LWls|#!mc$TDQ z;D&Q!P}1I$z`QyCk5eCXu*s0DNOD0sm;3>Unn}WX9waO=Zmqv381t(LT*V|jt)FBH z4#jt{+AzM{JmMwjyR6SZ^fH7Z)JcX4w|EE;vJ7=GzycTqM(J$YvpI&@`9KDV?a*%V zjPm1+_24QgEri=0Lry(VvjeQ&&9j{}-H>3(JDK*(ov(X!?ZrLsvgGck~Z&<8^1s&jMvNriT9>%;8wwPUV0rB03Itj1z@pf zp|R0kLS_O*0u1SKAmc?YDQWHNTcVFDP}H2l^EKtPnxLXC1@(MD{gZFEU6%?` zVC<<$GUb2gL%r?XXpeSPIKJ(C{}EV$*tc#Jl6uqrf=pg#W8x$&BIP+t#c2bkTpi{J zW+SL0$EI{p)w$s8fisLva}yOPx&XC632%4Kb7{}$c}E7$)mDHiV&Ye^lMLIn_u$6f zn2$a9nabxDKqboDRH*RUg1$Q)NsE_0>vje!#zsWW9`T{6NY`ip`bvNf)mFPROE@WU zOG$&u=}j>3K|RJ`b)F+Q=)2}9blWIqFUQGP_}OM zGZ0ZwKmk#Z5|EZ0BxUFrkX8^W>26~a(vF054l$H;gGz%i62nM0LwDzS*Nne&&UJ0K zf4F9?H=ekk`;B{zOE!dW;{UxD;Jow8R+f{t(_}(&n0xOU3trv*i%}MA3Rp4TcB^NR zJ@0H*S;+3gzp`&baEHU%KB!x-Gr7U0k0vf#y3q7)+K?hI3u^E08??Z1T>X!#r?it{ z1+|f5wP($Q@ImQbaT#LPH>t#KOiwq@&%;~)X$4k!LbJLQWMpVRGNbPCq`a2Ew`Jd6 zd-@Mp6L?OO9xm!iY&Jb;2kA?w9R~-~ovlQc5<6+Y41%IP@Zw7t(1eYzAw4U`e7~PS z4XDP@(@W*T)jXE;xPrI|El;c60xjo%4C-^h`-O+!G11X}H1-I*Ihz8d5;%#_P*?~y ze!D|Mcv`ad2zuHQ)QXRdbY&Y>DpBCw-++z%Mz2~Z5SZeWo3-b`ZYzEkkQ!`RS~kf$ zh5I&KxM}YDcl><-S$h?F;E_eh>34T2ZUIi&^ULPhh3o|<$CrT>&^C9SFR~^qCA=(f zX9gRZEdiAaMKFT}w*V^Abt)+^qrqGW;O|lHBB49OyGNx^o_P{bdl!1~1Dz1M;JswQ zdnVJ(z`k5SH2rpKmZc6|8o5e9&vPNJ8DLH?GJfj8MVmM^6&Hk1kv=&O@NEwj5b1DI zCYau>F>qAz-31#hZedtih)po)!%JEJ!|Ex#(Z2b$LN?m*#Njm!f=O|1c2fZM{w2UM z%>E{i>5KAsZ>>?J|8w*1;!;quk{+Cl5RYq`YQRol0*AhK)bt!g!|qbhwyR+L?8Y@C zPcsO;P#8;vzRO|};NpbI4Yq`wPX1|xAxI`CtXIQdNQE4QnIL{42<@`Ml z0*&9k18Pi;RhIst?D=0&sr9S-3#A~n?Eyz8#F|fphu#55BSOt@Bq~O`JrHWwhDGn7~IzUmCd{&jiy4 z|3HYTc`@E;JTkm7?JCYU&B6g`9YWX$4s?y?WI)EFTdT8s+|mN)KKwSVYb;*_X4oh? zZCL1Eu zB@l$UcHof(6D)tr|)igtzhw!8{-|5qfek$Bm@wysXt}9&3u)c=k^!wYMqda z+2nocPWHp9Aq1ifnh#a^l21M9Ce8E9%FUC;d=g#M%S&POhTv`ai=X}oP)#5CNJ32| z!f8s-R3q0VHy}dbM@1d}q9-w^F`PX8A4}{6dUgND9eM*e)KH0sP{oyU?#<2gtA3K; z`o7BDfekm@gHRrJWHdAo~HyD_*V{CjP(+y$BhZo z1sFadZ%ij2ts@VhAJwejK0IA2_%yEXtR^kJywM@p984w#cARFb>r>*Ze7Vz-FlujY+zTtZL9;ThYD@w0n2U|H^GaeWisGaO! z;Usn<^r_ybD-_q_Z1mJsHaXtLGHk9KRg~dr>a<#&^TzBfUs+IG zlw$An*t2ML&Petw?#RT;?FkP@u!+SQdfvNxY-3xyfoVv~NqYmGYui4gv~lo-HGSR!&WfsW!V<>ToCsx5BSM~ zt3lkNG(COsK@Y`yDXbOdwifSpwqProLeGx<=8YWJOnf+CqEy3lwan#WZYm>NU)H3@ zuNu<$I?QwTRM+MjPvZgCvou)nqSb zL%WHisaW5-aW5lXsjmyZx#1(;p2wRC_}2AkrQws~rr#28t8AU2KO@dvzAabB-8GjF zVr=tTP?%s~t)*93;Q@ocCK(jNNyW^>#U`Y}Exk$k%GBRP3blDFmZX%Ma% zS@1SwuDnGGclKByzn|H9=p*{IhEkHvJfMu(K)4aGKLvGTX!2+A_J!SEA91oy7#Qj20Gl}9yxiwvrp4T zan>p|1~w7Md{Ud*b$43ZsQw9E)zKa~x&0DWyBU3_PO(yt#O62)6JxTiVYG?fFKL9) zEM&@!e{gHD+Wmxu?Q0#cj@MI=lMDJzcg%a9Dx^8P<7@S8`N7_wg$;jTxJ*_8vIK&G zM%sg5k(LIihjhvVx?Yweg<4(s`e`t+b<93{a(rwY0S0@LfT!F6R}dCY3tx zHg$%by784opWc`qfKh~xI39@_QJyT(1%|qg-6Kc&if&9bh7EJds<;m=v`z+H)EHePzExa?>%V_O5;Jn#ZBY;*jMhHp4UaP#Ik z2jppRKnT1O+RorU{2*ttebh9MuGxwArEivGc<>G|ZzxdBkVo7mY_$VRb%bbn24tZN1e7_RYqRJ*AcWx+0!wu7!7+=?c9xoL(pM{O7e33DD*9nlI*7kYOjAIp@QCEt8FTrQ2L!-P$8#2Z_v7#-jm4t@yA*c(4csyLh6 z2*EAit?=4HyRGA3VN*@g&@Vje7j37*4Nt2pcen6OL-7T{fq~vIS7*p6yY}+K=o5W^ zxipN^j-SYmY>e7UOE($O`98}#o`!wmARD2?CG1V^pDmBNx@jVQwr-3kDzg16InQu8 z;=kK*PMYUlXkBooEV}x@^j|NInu2Q1>?JmJth0;-bx_K4a*Aiq{`u!w91GixjOS}i zk^l9_R;|%Xwfrl2`PSFSHN5*KqY$|Hriz=dL1^!}`si>qy8V2)7e3DUkxY#?$3T5@ z^wO`Mj~$9)`QM9YHB+?=u+$SpZ^SqbpwO=?T*m&=J z6vY_uEYl0udw+SXSzQOM^7mZrRN{+`3C`28y1lbG8% zyv4rF{$i9s2C)?AqL~J%=Piy5$brr*$_@bA`)X<&rk?cv(Ayzocn+x|$zNarMvF z8=_s0PA-W1D*rD!V^K+$^?t_N_etZEs0uK$BTTksbvC^1XMO*KUR;yXH)S*H&n7c)6 zTXJr7Jav|ot2jnm2a9X}91qCW-^tGP;JEO)cG?xw%0S7E_xwe#fM38-PtSC8ckm{; zEJwJ|i)4(l-$Boj`l@ZMVmdI3$!kL8ULp#2-#nV5c6?(Ba=@(cHy>1i=L{=X;moGH zr`T}Ak$QtN{lmUl@Qx%e&4qS3=ZP4^Q1Q#mo2TM07%q&Ih@zb@$&{_y>9i*O+F7c5 zi`}wKG(uLTZ?iAO5Dv8N|0XZie@|K0cz zwXH~=&NbP3+1At8DQbT%$b3la{p`<3V9c%MHowQ{W7?gnN+)@1*rZ!?;2)Q+IGs)p z3%ZLJ=vTf zAdj?U9!dQg<|>cu(E3!FcR)Kiw1STF6FcU3Y}FC?!$ zXRBSq-W#O07<%fNhTqE8in&vyfb0+Kh=c7?Ixik&0)JuKTE)$NJ*w{i7JaR8T^c{K z=4i|ZRX`*X_Rk;|ndHi|)jDy@njaXp zlP2yJow^s!HY;gY_(uziHiG5BCXd=9#~_RGg<4#;YwLO4{p8aQWd+1$*m#u3jKur% zuV2}{F{{%4wKkk1W)*FYS!_;fT^w^sKG*TzoY3j^53xgMe-Xw@QH3f0HIQl@v<z^&l|LJ;?|^z^N;IOfOu8C!-y&muKF6s|`9V zd0^P}IWZ}da;*U!Ddou|UuILQ^h%BUg1CzOL|AVj+HqWPlpVF^W6_W3hAyhfiB zvr_X3s7H{H1l|1rC?%2!)Unp{!jYN&kG8RJ%uVh?o*8LYdGuJNbA79H#cDRJ{zzR} zuKZjlqHXVMaQiRkyGtp*H!md5m|FJFUY03;9>I8Ua7o_@GxyVB9mg;1xShh7<06^0 zWn9X5A;2$pLCU+#;q{yO-?lI~vICo)Xd4op5wYHZc0b`5s{cdF zt3p5Iiv6%X#;;K7+J!tP#$1fJ#9z5owAE7a_$zDQ!dQGIVlAdSxB~f_^qN-g7}tFK z`?{z^A6dOQOxrCx*OT@9dkYrGjF!EfU$@d7r7NoBTgz>G!%$mS_|`tKg)GU7)fyow z)N>Zyxra`U?_<}FzX|+`%jSv7Wj$IhyAvG}si7;h$fA<{>6T``{i&zu`1WRv_Aw2oSvlUM;oStgI0hwMu2^Y_uaEfH zP_XND&vVO&%S|6R1>&qgj#3xp&(@EAD8G62fBn{H)am^XFTEzcQZ>I6nR9IR&gG4% zo6yov^4qkz)fJGIK}aR%$6&r)BZ$1*~9^A40*19sVV z+I*t&r^fo8;+L$74%w?$y6%X|jW&$9B0f63GEkWq9L8kPjTP2`ysX)|ix-0x%Y49J zV!r}~J@2#bPT@M=EXEx5(U$6sg|mc;EO(1Y8Z{lGa8D)IUGK&^^}}x5%<-X;4o$1u zX~O5zW8T1F7r=Couy8^pIf(>A+ouchE>-=`5ef^j>IYAxR8Ervdtw-BUA!*@XsdZ_ zlG6^C%NsR}NRcZIqjl4x(KpAJi+647DXb(S%%ZFYV*9RKzq)srfWLAjH+$k+ke|<( zg8bM)4ddQQM&qUqcYGseu~2qTG|BAOlWS2qno6c@h|WDBFfciC*IC^0Yarzw>63jO zQ#n2-$%fEq{&O)2qZFeMo29b&XICi+`y*^7i7VXxVwL4n|KHxn06& z(e>xY&|#Mul)eyC@wt-u)x|R&hOhKQeEwsW)wb$aw?3+SVD<7h!n1Db5r0~7JZR}wXMCtnPuFM;ypLrDi+EnA7jC_?KAHchVAGvc#@I#M&!7*W{$CDBFpl3*UTU2GUoODWy=#Ik`6iXJK8-z+X( zSO_X2?H-oXNA@DaE%Uvl;UcSCfv(uRcRZ=+ zp>KW<3TmEV9QN6NL6trNM z1qFEb<(XA$c-&D@z4waz$&<}-ILy8FJD$rZJsLl)apZD){{X<>$S9Y&wAL5r3&--m zb2ZI6d1!SRT8sdxF7V4wb9MV)0jjWAswi=Kc>+%ys_46w-YGYrg{ls{YCWd?fU&lC zNDN3=^{k2|sH~1<(vSHnh5g$03U|%G!+eAi%rLQur+8n-VC)$ZQ}gGFr7b0}C?q(- zG+ckazZwIhG_3JSnlA3nFkc%&>GIlqn>*XGU(u~v4>H;U=i_x(4=UAghE4Nce6dgE zy5zbdUblH=eEjA5)}4+S;vGp?!d^f-KS(G(jJ*H8$UKZJ}jvl4ufLawznWlJJW`|rWD@$o@IY&~uiJll;#Z(y_%&wE2ZY3Rc|58|sUvbbM^!+G` zZxi>Gsn&do`=aL*A5Sql_Xw3NTJZ6%PygqxCFj>$Tpe)d!pv?(F_iPIar?Ez(du;V zLMHjFy%i+mg=mh#4rdy7?|Nirdxnsu(542p|BTm@XC7JMc_T@HLM4&7OgKeAa^?Li zD)+JDeE$~lFZuOr@w&&kh-8dYt7h8v>_&+$0%dp$k%N;)9=SAn`s$-YlU0xp9~xxs zuJqR@Yg4cw5mvpr{>jOTSsHRaXG+wpSrinyr^U$)t}E)XoH0n(Q%<>E+6#3VxoqF` zzxI9!XA{G5By!E{hmFtT*9v)}(4DZS(ygw?tDkz_r@pG*7!;&1%(bbL zK$jft1+-nha^hv^8-Bx&e^w?P{~LhNRQyRBBwr~7LMy=7z0rkP3%^^Okr`c%FwV&P ziHWPHr&H{T9vpjrhgn9AI zwuV+;T%TMCpi7|=p6#Pr6{yU|752A}4l~O8ynhxX%ItNh>o+fq9I4O~_gy^xYj-?VCLN^b;;MyCM{u}tMYTX9^9M;>}%oh%+r9uY>!bz zs8l7)YnPlWd*72zJGk{M(~V2pC5A7Q85&4f6a#DS(oP4>?te$!mu`2u!FIu0iDy;l+j$gbg5;Ozy^c51~6u6}%Ve1IwW zuu861XK@$PDiZDO{9`)IW;nZaI)F(YF&(b}MR-p-vW~KId-@iw;qw#zWl0!=?k!)t z30rUE%r-@T$RXSnVao0Ao_J^Omaun$^eu8 zIx5f}_)X_-T-t0b#6^gc6841C1|+qSB9h5R(YH`?yDHkPEwNxIV<&r6q!&aV=#}qz z)%q+3bUYVw9^>v_er#>KBT#pSe_ z5Vf^qZjqguiM3tqWgIOP-SS5oS_ zQoPk|5BMPKQ_Dpe{9VJ`v1G?sUY#(<-3(<|Y#!ER4B2TvS(sme)5tWY^urZ!$wSml zIY&1PzIJ9BrM%|q%fXJdU<{tcoO#=!#&g?i%00$5Yi>ze^ttuMSc*HuU8C+;?$wHw zs_vY^GdJkl9yFV4DG<_=%^&D8D-xFbjZa?3y{WHQ?50gq>f5fp5SJ;ti}}r)jxxP_ zj?XMp11ZBXw8@)>)GT{-6;XiGgh-HnP>;WS$<@p_lKOkGr+8n-dF~IxivC6@h?dWh zXhDm7;A#dvR{Y0SuBHi3GYw!~?D9i(mY-b;bu%_UbF*ouXCCLBc0Bc#Y(4t}vhfV4 z6qZ`dP90^~OSt6h{DP9g$&QQFJIC*`oJAA|)h(_6AYz`1LTAav-Vr%s+g9`_1j$ZlA<0sj>r8^2c=sDzI=7D54-B1!?;Wn@n~5cl3r*d+5PAyv+-- zWqAFnW~W-cQ2Kpd)o4(Ca8$!`lOHuO_s#ZBDlIv=^;K^rX=ge(L!e44%u7lA$KHCq zZr=MJmWQfF`E+8>k)GJKf~c#ACI1GuFbg-#^yU_sYHtjE=d9EGZ~rpk$fezBA;8Nn zw3r!U_8J^f!lC`EgMy`RMsAoU%J;nAfz5wEq~E;PmuVu#-(Pd7$U4dOSM1y<%1Fg;ArzX#i-#?|WAcVyR_Vvp|*uB%3Ozs`;hC%or^5-n26Z5NJW> zW6C}GA{{hTAXt<+TaGK!?puf6i(M$bc=66$%tVm1fNhp#sNTYFe|jeQyW)2X1D6W4 zC7$bGxOYTgd2P`vJFUfqPK*|H-S=RRfzKR&>|}c^to51WrskHv-fWCj;fy09)V5%F zywqyx=OAQNpu%e7y01!UGL2Z~x}X^p!y7)mqt2 zq|g@^9mjhf)#>Vj*~QiI3`OwNl&TF(*c2xjP$!B=d;{^W~x_2FOw7mQl{B8P1XlEo_f@7HKer)Ed`)?I+ zOb@O7 zPFyiCCtFAucEK+*CElXS)5+6^sXq(1a0mx2|%q}L*0xlE%Rm$fARm7yIB+cQWR zkwoj8~h;vyn?*8`*$Vy}yd@Tler>M`|cW)+-T4t8S#w9k{U1n!5AR4fQE znvM^{m5JR{4})Yj06(+!2IXXzf>?tk<}BTP%1EvIgk5hbx&)lQ4f?nrK9{p_iI=g; zwoi%BOA-PZVTW*|M19OV9Pf)=9d-pf08?HBp3SrKAH2*2DZaj6;?V(zk%b83{?G76H^wuvH^j z61Q&OdyVxJQq%RXkB!Pt4(ugS&ERX(p|+-4r8T_x*31;=N1~f8c_2m!^6xb-r?CEv|*>TyUcDC5w6@wG(gCC2H zx&M$nuQDl}sjP_lRYAultP;7rPGe4qO@QjSujr;U@1ctnT!B!i7c5>b~e{hCbzPc>9LDzYG4~rs^$U&T? z7h-1`V|f^bM2wgn-CPe2jGoHkkAIUBXWpU-(sU%5pxJamZTlv7YsI?-T$2xeV`ed0 z{C3>n8cC$&4(w1j6qdl+9`TVd3M4Vo#IJG}rf>6Jca0PPLuSW;J)5`UKV0XP_fK#& zWCq(@pw}Xaxs=T9E^6GnB~@TUncBb9&E*lW*n(mYGn3L+m9~BPuSNqQ@jDVbYU1PJ z(BnQsv?x=mRhGI)V6@s&b>a0|>1rj6jS(eV&OH9a0hn+gIy3#|BY4c^a?7SZp^7zu z?NL1S;V1AbKlIqlfhC`1^fmc{Zd`ktnW>X1xR;B(Oq{ws2{uiV5KeGiihstW7b0Hf%8IUN#``PF``5=S zfqAmUCW71QgrhjLbl5nz@L`Hg2+9KdJmF`ct1xcl`Gc!iJk?gEiNsurR zBn9_~zA9N8+S{1ylA~1XiP}aEQXj^Gj$BZ8-SleqeX8gGDj~Ed{3c;=zh5O*W`R{B zz=&N8BYj$C>%A)&IARmQ?!9-Nnyt6enY6PqH^F8&(BpSFLXRcLoj^aC1844_0%r3f z1wu;Hg!>>DmN80fze;g7`L_KDaW+%~K|oeAQi1L>k~noZa~|bWip^w=>5Rg6P1?0B zICU&Iu@S=phzu4B*H0WXQo)^rzSqm9y`Nc*asSqiIY%O+1-%w#S$<9a4L-B@y7zxX zBl7!?7#+yxcHj$Uie)zQ$XCx5%urTh?5Kb4hYNqXll>zb6qvDze_QkCZ}aiik#vV4 zIa(XOvJ2x{;H;Via_SM6>WNyi0?%s~n+Hozh*L!&K!_x&*C^nT;x+1rH47Hf79=Rc zkr~_mm;HF2Yx0TlZOKtY!$T043ypssv2c`G&Lg2nnW7mSe;n#*TKejFVjOYEC)gf9 zr!q;J11r=#+6Gyysr;iAVd)uMZNAc#^y`?ml9%wjZw_28T~rbc{2-mJF$_ zpD*@*CB9djNKL^>Y}2<>%s4Cue>;uWgKH#G{-5sCaa}Z7h|8;bCM>kGJG)}7OR!bGuuJx+i9mTAztLpH> z|F_u_Me;o=ICc|k987vBg+ytXTp)G-+e7i6gb0@MFKc8}Z>S=OFK=~&1UOXoKnjrf zflVq_b(Y0^jx6CJ#&D8)V)TMjftJT(4s&E0OVm6(*ud}=eHM|`6XsWc^0(E5{e~ow z5`MQ(00!)VNUVBorpdhV2Jzp6ZV+cp(w!ES>{u8X{R$W(g+*EBW6hCyE~dta*|3%? z#26s)nYfe*e*!*w7o#Iqv1TZ~QtD$Q1KwHM9|yJ3m$fnCc-1^3f`RM?!M{`fM+_WH z`XF$~^OP=6_YHyFg{sof^Y$ilbGp>Tl=&`=2q+@Y;bo(be>$zQB;ImxNZa;JDnVPE z-*b%dcF@(sfyV7?ZTGK@aO)z+Id&*{DszvI^WA;U9uULB8ZnWoKVUAOsl{C8IgnWx zdB)5PZ`i37fyZa&m}&&}l6WzDEV`maEW;G#2lQxIUN=bUBjU%McO z>;iv+Ev=sn>;0l)aaWDzJOAyI1y?jL__2lqQxn_krjJBFVfjh)6V_?x$WuAxNPuU) z^HYLngO1h}Vl!u49mPh=Y)Y%e?HV#wk_+1+Yn@X3kWN$jHEDBG(|psX zTf%k|?}|%G%3d{IR7w)nG4k5*yLUZc5m#6k5pjdq#r+%X5L$hw-aQ=ETj;I@k6&DO z`#LrB-~OfpN#ql?WKOl^0Hclwu!(Yh{ZjWZuSaVbEs(7W0{SDF)2-p2Nq2ADNGUL= z>3CC;W6~Oi{OjcT#JowN9_i9<{CuS*fVF@_jpp56nz%vQA2mm&KBlv)P4gq3vLcDf z-awy;2If->-DQr~FDfc39(=nvT2sM>K+mMJL)qb7FX1Xm5((s<+Tr-x>uPPdg5}v$ z@Iu!Im=^O!K1|E}5f@E5W|Y@syxtqGsMrs>?>zDSAB_9S$%!+N1nhiMvH(=1zBC(e zOd<#wr(KSKGA7ih)I6k+3jPdCqK4oqEl>=3Xvx-?I8~i%`M(AN#TDqC+D6_?0gaRH ziSzU3JBwHa@561iP#S(O=PD@hsHPG7JTqw>>2tE8X#Hn7tak<2^c9xPK2#7jzcw=G zUHlTO(+ll`OBeOkLZUZUsl8{~$d$f6dFE1M0K_%}%tnlh=7GRuMAw@>aA!LQ4Ax-f z-v0l8dqOL~q4omsyKpyA#UkrYXr3r;@rxUv!LS0Xcn;Xv?^k=2x*_5?B$Gnq*RRMR#!*1gwf`*YGguQo3sHgTU-Dl6B=iyC)i5+X-QuL z=))|mgeXSBsnCs{2bv%jZ4nHIvoS`Z;bpethFtagX*X})>>kz-c8whSIupr|V_jjF z#;4jLqo|iTls>{O3jr%rSynZjcCG>7`MbOnBR3(AJ`Aai*FLjS$$hC8vRMJ zSt|697hJh|b!+e*WT)SWz?G!iaqbs2@Mk!%odUC+AaNr&-sfi0s>mXxBc^KU3$r1mnpZ|u^!whR& z*LFiLUA(whf3&KREbi?DqREk+KQzJat4>khDk#=?r6HZ*LEURF9oS6bP&PcG1x1?N$h=lw^K=ve5$wX7RuI zMFpGyj+B&=!Z!H&)&ZSgw0@OCJ%&AmrH-{700>=}*5{~RiOaKL;1a~$kb->|+)e6q zOjJTb9uUjuu&G!AbF~+um#;MnWHvz{uLhD5rbREjo`S0o0P#}&HxrkeVdzL{g*AaV zg{G{mKO?KCs6Nms$^+6SRh>h7{lhFL1A{Ca^48S|&BtmqENW<#Bg3BbhG<2NXDcQO zN??dVEO=cp%tR8SsaO8CCxSt0 z8z_pjP1*rU9g&-fPxdtg!ZwjWi&!INi&8n#rR~YJGM^9@(nt!dD0{lnh4OF&N}VR% zQ9)k52ROjvn$dz}-_t=}o1zy^V7#E50z|3+%sKwsKNe9UaHbgi_Ua>$=^^b(64L{V zjY!RsH%gGs8#D>$&Yv&b9~qNf`0=pYO$%PJss(CE3P-GimaM`Rcp!r;ei}3_dejC7 z)OdklG%Lmpi$?4Kk<@w6d`(5g5Hd4x0*8cT>>;68Kz#?Xa0iH`nFFo8q#`iK3y|GZ z*o?j|)4e+De1#b<^14km*M^cZUbR4V?!v?nTEMz0t<{U8kB>6pMlRfTTrQ+OaC_Sc zsK!WvRK%{8w;poWKQ)Y21j_4HxPmMH```NHV*(KBAeJ&Wmw`x0&%}?9J12YNo-IHy zMwpc9iG)FoOH{Kl>o#s%7)}OD&*-{Njns9?1t$%IVlDfk*)4zJkPC*?2C-BI_lGNN zabLMj_}n*N(~5cY^U~4L6|9cdPeGHvcAo@w}uCqi%e`gLQ+!l|IC!{FY6S`yAAh5^p_1ak7UoZk#WO>A zB@bi^2vZH@#{3H4Jk>myP9!4?1``8%J{~{_kb<6mZJN5; z{T8aPuY~`#TM!bN!1Ed{v&SZFkrJ508>tDQ{w}e@Z^G!r(4dRjAUg>A99NpvnqiV?p4z1to0xb%tvLqO zYQS}sjgk5qnY|8lqUsnZR`1J2EwEDv%`JD#|MvnxYtO|!bO~rgp_`hdAz!1WrcT

Qva*M6{&%$9 z)Q0NR<5)=dwZNw|O}5Wb^8hcA3gJBW7x#1m>j9Z0Y2*8M3Fn1g8Gr+Ox&+66;UKQ_ zJ3rwfE0UnfL=jiZ-zm@Hp1k^CmM7_VWpTKoFv<7S9Z{e^7*N6v)ra3ES>Z#G9E}li z0B0pb<7pXk0QHRu@lf`T5O6$%v2hxNwfp-uF>Y_)=6UR`=7Y?T3y)$Wn$Npl!f6!} z3N?9oc@L0Vpoj>_YHmNNm~S?4rz_X5?`~Wx3;~B;{#aNmJ3L3m!2pL-P}Y$| zWzEKzD!^SGsGy+G2Lg_1#V~}rFqK$KERcvsfh?U3g#3zMJQh!20{wf3B;5K)LczT> zklzPlKn@V6^hmP?Vad>AfeFIJr>CdlNvV4v?$l_*1(&oCWy7~n@2wb^WkH$8bD0}0 zFhytV#Ep!M%m9wGI2{hta&Ra%6XoGiuk+YZ2RV|0l5&=4D=dT~l_V16MX%d$0=;`Z zpgdh<_E=op5NHE85S$5v!GPiP0s(chQTe1wz1kCjeE>hNjhJ14u^9h4C$IjzW(chu z!dYHiT+C-R@E8I<3%#hM@c@q7!H^d2&O)Eu-iRYm{ml(G1BYQrQW%NQRb0S!Oq7&P z^hK^#{$>d9LiO_(ul)u%Rxd4GX;OOOEj3Sno+l;Nl76nU^B;I#N1v~1(;wT{)rwK_ z_hn-kMO>Cu!2|+1r3?^5+*`nF&~#!R*|y{LL7zTt5KWW9m0+6RFX?l}K)4M09#DkK zHLC@`p?UK19n?hn?$x190eJYjw8cX8DgvxVFyuN3$aY3b4q?^*<*t?n6bC?YNjGoZ zDtr0k954Wk39X1rBycfI5_CoPSH3#6BL3?>kT*472bo!wF5^u-roRdImS#B zWbn~yXAMw)QG%#Za~bk<7A-=)|IQ!r`E%;VbgR|=`s7fFSvcdKVIs&BSRkv72B7Z9 z&A%SdZa$uWNX1&QvDeF)k%$=a!leffh2SWnDVpy(p#7 ziY%v6?^~6&n1CbV>eZpF|EVVe`Q;10MK2=&j@DzfgS>P!G-#kMFAdTJhhF*i-$_Fd zCT)W4srzQenCF^U-P!4pu>I73jI1my&+9z*20FPK^4i*-(__BP0~(D?ENA>nv1yxT zUu!OKhI8nrCMb01ftJ5wl!dk)zyG*Wh%6mAUG@>gZ6M5+T3X77*#|PB%%FoHC$jVL z^%F6TY9ulLN;>VYzp4Z1iiY=1h`NRbQgLQObw3$Yf+>%mX22HDIkirw3*jHxL); z+$R13c&f|_*Zud~6o0?{4T5iX1r&k8oa+Az-^BJ3fLxxApI;HYBIU|MI37S`z*=*n zr$VC8!fz%Aqk75$dsrgC56yplP`UNkQd^9{@-2zjN8fKj%mr@HUK*;qM{?j z&rT3-o&vkAAi$9F?BoB3;GxWE@oOseTWNfD&n5|- zl}U=~QEOa#AUhodwA!oyGAxa5f+&Oamqom;okp&+i3ERI%31rdSHgf21WT=?d(V_A1F z7k_xTR48UbO2hXX2>o%10Zr%4t*x!eWjH6u9KHdMJhkA0KB6C3NE4!%Wtscc`vEb1 zCAo3(_Z=fI29Vx>=A?&DM5kpL8n`x;|NC=p`T9d{$wWa&z?Ij6Z*%klbww1Azw>B; z3RqC=WGlpQN`JLVd6Kprt)zp@cqI0KLMBp{^^Ukn6gtF!v0Zb}g`ZL>q=o;R>zW}{ zodf;?^l&Q#m#roR0ywhkjfSRXB(Ya1--xtd@K;{5u#04Ld0_lJT0o`BXEz}+>T|Rr zo}ja!`BY6C&^wIg*CIHgl>@oJLm8*4(9^ieC_5eJP^GW??d5w2 z+H@zfgXJ)Jkd_WRjZV{`S`v6+6xMQQLlMgNZbW#K&^YU0ak#xeBkJ}|z-DBgx&eqq zZv*vRJ?r7}oyp}15Or+l?X3qRnWWUy>ByxmB84eI6hIZPSfat@9^x(#L6h)Q4RBpJ zz}1OPBhZ>^;dEmCg+?$yv+WZLxmqIq+6_qN0U&Ug@v70^0t^XqguSfVX(8?X`AcYk z!Nijo9NcJ80ry-BBL%MUdx%P^(FXRi(6aq{f$EsP4IrwjF<(#$m<&)z0kH&~j<&Y- z5ZC$N&me?xo=h|a01o=@8=!jnR2NNOzcwGKw5Q=V{?81ikc&lw&ufAtr3xm( zOH^v6HJ&9gIy!$ZfEE`W8q0Hy^|a@IGUMt!fS%NMrhJp9HESQts9|Q}WClK+2NJ!- z=1e>AjKh_RanWf?HceGfM-V#O+WNO@<-9|%9+Vaw9wfd$asCA80b_g8<(TB3J$tq| zSdwMvb6_eXCkLW>B?Yu_XxqjO4RA06zgMmQ10B}Tn{01e7QrZ6k*BML}OF`O^#8GhDrHP2o}v}Q*2zeiVuk#`}K)u5Fs($650pEWEtfrCnr6C z*zPuvU{!kc>J^yUg2;-BwbPe>xc63Sn6SQ&C3`9~H^CZ#AM@K5O9cE_naALY^(~a{ z!_`P%JFqa*YzXl_p?9-D7G@C=8sr@-wa@_J7RphxoiTrL>kJ_%q%;kKtO>af(3fT9 z<<&rHLS)|A@fHwN&C<+Nq^8H*@cVHT)hk!#tPy=JHfW7Jr|tSOYv>fm+c`_by`xbO zeLtf?lDQli6@>!E2<2`diJJsF|Ka0DXCRmks##VBh99jZP~{94Bo25U$av-e#2x~D z-&wE)ULFWd_e0?igfDhN_>+FM#RktZwVL^zK2*)E;m^>#7pMtt-VDmwHuc zf4ZJns?eLZd#j^R7=+N!$4g`$Elb#3D7`_ER5ZaF5;}TkMYsa{-Ayx)2Wa@rBgD^+ z9aX;O88y^HMglZUY86y0p-fgI%vuL?^0JJRc8zr4hn^~^#?7y!1s8e zY8E0i!KPF)vjD4hKuP@g{SdHRq#id=YF=QWrjHM?nn0C!E8|lSixQx=7(vjzeCv@p zzB@4q0!6v5YhzHs56Ff&q8&5Q6I`cx?8Aktd~WM6IgIK@cn1oAY9IJmqOkMk z04)b5F$YBZ5gCZ9th_=%Uo6pzef@w@DERU5(h?9*-3%EAaTfxXLlw4bjf1J#>jH4! zr!#@&HXVQ?*+~x6480!ZJ<&GHm6uNoPc^!mQ<`bCBhw@jfHKgpD%qNQT^{dn&29evuzqhxSvC5Zp z1ZWCF>=MY^vukT>)8Gapuxd)IE1M0(mD~J^Xnvv@&Jy~7@$JaXsI#~EPA-`F%1KvnKNNce5}5{M*xA6P2be@{0IBl+ z54i^iZxKM?yD37FdG~-rOw1r%E_?tm5$OP4K1TwI2U}vM0+fo+)SaA4!_5Mx(m*~f zSEA-&#Ckpe#bxpludKrCP(P5|@p}!5NeIPdDkq{rQ1fWHb@L`L((Q?lWDvw4f}o%~ z1dj~LfMWDa+h-4;AmntsZ!fPN%VP=!=(_!>V6<)-8v0naYT9t7eV(VV`5`-sCnP801h9RKB(0F{BravY!=5G8m6YFy`M^f0Znlu zMxWsliEKOq+&J}mbMl@ z&^?J}La8p{X9re_Af5-TK5%iVAi+%vsSw~#mkovJfv&4+QIUwqU1UE`=BLGREnG|Z zMXbDh2xk>Ft%J9e!Ko9tY*IBWC@84ZxcN$KW)6Tj9V;#h$PXVr@V)#_0%|e9{*28< z5k5(9jCc7iYh(Et8H^x(XQ!k{0OTlt3nGgFPrJFdSK-cpM)%4t^v`_EN@iVA>-?H2 zZK3bPFn8&S;cl!mZ#5G)%{MFv!Tg@PZ&3iv0-kB$!02@I4(K6Robvnczdw}_Ovt7p zM1XHHfbviBSY+iesMc(5ZboK+!URG!APh%`hlhh6(ngBI=~Z`kH5{I1bbjis477}; ze}M_PUR9r}s|+@HQ2Gt8K=%DGrV|y#u8scoO%*w_wpIv4p;f>#C@3{c-64A5B|X7R zJFo-!dOHB&Lu(^i|JKgMSw%)fq||#Kb#!)i&iQ5A!0)2Wn7|itS&Z)e#&);(=kL9m zmivg#jy_2JfC=jP+y@}vdCGoZB#}u;oZz#z4JQM_wu8l}1m!GaV`I%MW!k%UAitj_ zY?^uJu<_#(Jqs(VJRl}Yx@~GVWI>@1Ef)!Jp6_6R+hV>nG8o84%2Eq6%NpTSKVyI! zbWDMi52R0fflf9=waOz43kv~*$^`yA}8zyZ=~_T3!!dA`#zGtVu6#;R&3CyKW#EkDQ<6XR|5qT zsPut$x33N!NWkYzO-;R~`hR$P&!DQ(ty_4jt+v``5ez8mMiCSwC`m#afQ^!qB0&Wt z2`EvJq-_=zHc3T-Z@B4GB?prW1VsGii(x~`mQ@;=vM|ouE0QO2JEvqtM}(b zI<`n^%jP4N@FNQ6w@KA{1<>j@H?~kct z#}9S%rnkM{5;N*acIA_kHm{Bghjn$y<9V$u-@lvT8)0~4Th8sH|14O#X2S*^%q1wS zT53mfiBoa2kG8989zMLC0Dq)?arlQ9igQOz!9_e2IUo9PulbpQ15Gb|_Q?mlANLJ> zu!HaV!H)}sRVI0JSa^z+o@D-gDaq1z|E8;Ym|My6t87cv&x0{lH#3VB5D*x|SMHB+ zk2?G!iPZ8Ab)?ZO^ax?@LwysG=2nZ`$K*$q|P!V|@Bv+@x~obQ@z_a=de+|=!< zvFSWPBD@2R`Loni-*NdZBu5P*=)5%wbQl;XHW85!INUB_BXNt+>DPZEk6QA^4i9^Nl~QjsoSz! ziYk88q)-P(70`IEbGIOUsnT}r&S=GlY|W7F02xTH9! z1YF-fI8ly_TPZAZ{J;jQ92J-mlITMM`K%6Eu8A3em8+J$eL9oLOg;DY0L$97@=Sbi z-kKP%($doYzP_iJOjlw|P6$UxWZB9Ih6=4|ACudw^tSy79#gD#{`^zmd!#yshU={2 zo;^FpRFBi#jj&qo+No$nfinkSYQ~jMD*-!DmQp`Y$1>vtbFeCZN~5o@k5CLbIXO6X z)nU6r2NSz9D-}9!@D5o_n4FA~#z&$?JKLDDQ3iwT(44UT0Ff@Bpw`jRF*Y^cn=r7? zq87QOrRxN)$i|Yjo7pJG@Ymr{K~C~LnF6p=;t6Pon23wp(}ZXdF9s|IQMG+sgG6 zF%&w6ax`(#Hxf{BL?}^p3ktjuDkKH=Z7ijM)a94 zmKlD#j)(gAtT8%sMxL3In@a*2B1=fnPD{qdaD-QzJ~#HDY}wvBmXYT|ZGBnJX?iR( z*V0c2&I@E)8uF#3rG;zTwu6zTo!}yqi}`W~RSB_$y$B{d<@^?`R;&Oq|I&L^)YZcs z2fmwvMaZLWmYGKNBE!0L#&km)xj z9ZdV7LDthkv<((D$%l~rMrT9o>OH50^>TJ8N?XZ#Ay?G~o8)Rt>3MWK=!yH6_ z3seY})P*PWrB}osS9^7RdDTc?vqrvT-x96_I00rr*^XAu>+5BO)h_riuy+;VIyfis zUh8!2zW2>4Z2aq`=Q9HW;*fEI+P$l9H&Z>|)dl2*$?F>{p?HXyS;85KVr(>+4)Zr2 z>zt-W+w9suZ2@~OcSGjhrUZhB5b44P#QK%Vm{v`@ByG-n?|o3Ux&8a04sMQM#lM{+3qH^~+5Yvm91M z@bI`*y|J;B!6s5)0B@q5e)a)j)F*x(?ApI;*HL20qE*6(C8PYTx(u<_?%dztg38L3 zq!JJmceK3sW`^UvObMp~k1#Pwe24mSBlN4P_PRJ0IcRjKL;juH_XVQ~6Y`MfyUU-g zO4OBCP^f^iCElUVD;$9!w5w*%-RFSwiJY0+haCw}J-P)<2 zAo*ZnXWy-_uOGlVw?fG@Z177;sO`2;+X&TFVS-02W%RX_s*Yyg!EROM;pQ$QdGCjj z2pV}Z>UJ}b*{MWE(unOE$-o9^0S|?2rA)NCDshS681x)lS#U9!umK5IuKYn*Vm&=Q zvXB|7vK`aodQ#3d+vPJjj1m`zQytmeQ+)Ty zmx%9>*N+r)*q7QD_G0>*u+2Xcnif)%AKL#+`+7#mojV@(8O)a#3_z|^+R#TYL6JLKi-p8fTFT6L|8Q?GC)bHCY| z`paCpb4EW-$nDki%~5c$z^a*j|Md9aLy3#&*dg}awMKy%$n9J^MT8#kW;28OAwvXW z(M=7GZS0(!oFrsns;}bi$s4yMa=Px?v=oeLKn1?_#6^de!mpm|u?v0H3raDA@wK6t zkEthameOK5@4XF!*DexL$ekMNLhyk`Bx7TpP&#;>V(Y9;FWS?`f&Uu)4n z^b1`)qld{+g0aGZhy3Q1m{nQ$&1*|HY=;$nvL%@GCX8J|E!lik9i4bBJ;%d4ckV1K zEM(H5o;Zl3LJ|QpIE)sdvQK3!e4};r*H3F>6@1ba?mXE1-l4)Co5b{p#{&1pl=ICp zsGuRiMP?>v&4zw?M`25Cf= zSgRM8=fm07rCX#FZ`-ww+ivL#`0YAA$jbdx03PyX}h(MF>9aQb?c)fOZ&Llyka;Llgf z%?H~{$-!~l;(j|JRmJH=b9U;l0B>u#SgAqlP+QTT3X{hqBp?-(YBPmpH+1dpA^KhTV!=vTumX?Y1j^rZV@+*s0V;#257_}!y zOhauO-p@7KKNUFKE#jgk7RjkHw)H2Y{=(RqRN1t;!|h^NOdX;5^XEHtr&VEw8{DQ{ zx&{oCl|2{y#fFkvTbPm=Ji|(*yU(rcZpb}BOev%eix)4ZGa$}P197nz(Tby^V@=Kq z>BGvkT)9p0T)I{!N3Pm&%KQ7GCHMC;wjz*M7nXu`^m+I&7Rl>QfVE0T1dTAFp(`r64&u>rvR|Jc_Jk8dsJ{Dn@*xsC%Qsl=*|Ozt@axGq{=T~eJr=xm?#k0hKN|iiUU_ve{{A2A*>j-rN}_0mMm)F?&AWI=i0iDj&!V`0^)InJryiagZ`{ zK|=ul`vm7OHsmwf_+&&`$sbwCf2d>N>)x)_j!E2(0X{p9dc9ZHj6zXRnxGtX<9RT|FdGNqA@cQ-Z z=7605CnAk<&?+b^QqOebkdo3zyg?2k)_fZSq_f}Da7KQIm+U4LECFCl2se+5&s4^6 zugK2p;Xm0z2d(-Z4#nhXy6k4e)}<$kp)4Roz6PW&wkW=2Z`a?#K+n6kcd>@pO8%%- zrqE0*6_-8lC7JIt#gAf)gIzQiF5l!suSnEAv0Q562}KIh>91kTV{c=p7I;r5xQHvh zJF={TQ$F|=f0vqL0xu@_XZqL9Jww~cC{=bSKKc}T_%>3kgEhv%$J-!71))}k(DQSa zojGOT)DTJt|I>QT^Jv z&4V9FRi?TYL8Mu>F_>Cp=nMmiAQZ6}%8|3aUfVzgyvm~_RVtm90jU^KJR?eoQVe9$ zb?quN5M@-k&zaZK-EDy|d?X38%86N>JdQp4Rxif0lP$v7f@xsqw{g?G&h({Kchs!F zp3!^)0~27yk^j(#1R}jSDXWtk2ju>JwyW2!QG`s0HDR0F&3I5~1#?Q$t6(TNeRB@$ z0|6lLI((9*e9D(wKKNY~^X+uPW+mAUv8P&G;pS=^q#j!DGxy$TT<<}OGh7qE30md! zUBeKen~g8YK&qy*JnA0kI+(B5ZLk3D1C;utLJ?6m3F*()2!Q2clqF;|5CxZ=T6Q{Y zAP0?c=x_CovI3a8b0oI+S8OnqP|H&M;ajmyzND6((6Hrw*iZAWPO^8JmJ@(#YHBL4 zq*QfJYRV)~-@eA^*6rJBPcE8ryQl_77}QWqBN{&mdR`%?p25CuUXsAa6G6V6&a&(3;*q{) z8jl0YdQv~O?H3$7gDDL0xUhdy!ifw;Y&o)m#G(2+C$@jdOu^zoaz;fb{)jEADen}! zCZjF^X=-jx&cpgHL%#I)XmbGSnvi$v_nH}Ei#`(}>!vg%BXuUwlU{K*VZBOiVES=w z-Tr;`D=oT61rqB=f>ex)Hv&7lfUl@@gqcDplmN=NU*Rb{MJ=^AxgUd+9B3;ca@ZlH zrRAB(Snf6Z%tdewW-`rj3r)w_Xmtdry6Xk0stK0dU|_@&0n&X zaB9#4r4}DNa6ri969>toV7}8SGc!uU8F%>(g!E3Mt^Pw_Y)U#1oiM|J`@2eIH&+1V zv9Ph3U{;@y@G#EXq5W-qb3gN-zj`v4?xXa>K^cmT)EVTyfxDuzI=8=&hbSuTT;cdg z86`Kj{EHVas1ZK9KpaMhDlwx7LPRGAf~ScIq%&X-%kdH{plqQ51s;|zQ10ZCFulI) z2Uq4!7sd|_->vejQTVL%kg#<$HtAcRE_g@x#hk*zS~F}YFuk{(9>mUMPOqJ6j|{M! zv86R6CUuwpN`h^{lKo+@N`d-EaEAK2>ANby2>mj!lqWCD-m-hCOEG1`;BXJ{3G zFXWIEWrl;z;09=f<}J;Uou&I&btUEV5=tPeD>0jB zs=?m8fCW`{^i}YgI=qar<)F64jexSNd{%zr^;ST0y|rRlpcQD*Tj6R4zXw>6or8o< zWt9wgsLqDm?7vt z8@%(<8}h^rfBG)Yin?mC`3S?KSd-zANa_jIkB|TA^*4P;%r%Zy!T{vDWYxfGX+Z=+ znd_hV#9X^4kenldrAUx6>6U_U70iL^A}`J!G;Cf=2*FT|zTM?d9y^Vxb58bdHy-7?;DQd$N$dlejzG zvYwlPEL_!-Q?H1a8CpOBWt7vvUnJchOk3tWd3)os7)?>xj?4bSMY+S`@0&Gq&AX!2 z`t8U@NwWcMMzV6iVIT_Aiy`ApKvoX=k;_&;k(g&$KrFV;1{l%V$eT#LDESPwl+PxY zL6yUY4gto{V()>Vd7D1{D{{~?Zq2b}Dp^gfySeoOb~_e-@yJu$3xeC-tOo*5%qN9$ zgM+OC8!t590c0a0AVhowvPs)fgej};sZ)A2ct1u=s zV?f^G&oAQjTR5*@zLu)mB;oI0lRn{}5p7cMz33z3Xd|N9+j7y3@Ku(BRuX4!WW z2pEJrN}>9{B|I_?*>r@l%FT7B1KNt{$<$}X+F5+g`f%sK;-R}>nVf(9jZwRU<*dJ^E! zLG==zjWWo-R9oGN$B2QHfu)_G9-`18DQN%{V<9;-OlE;9CUY#Fi(RC}Z2sWbB$w{0 zFb4*A=DPaP7;~5|T6HwyM@Yq)$V%eNa{ZG)7a8rSlOdu7vQkK^^}j%!51hpc2ezUUXU^OC9L_5qCqvrlR# zkaB!l{OaXPA;WhoNDT_fp%8!){f=zKCip_xq`a|Mf@eYVGT;t9Wjxp(L(Q>s>(-aB zkna7Oh-x*z)6-WG za3oZT*|vk{p;S8Y$IIV9ED;h)WjGm}R&UdBHo?K*vyFK;an|oS$Hp6{Ns^Nbx@bI^ zChiNXC3ZZdVo{xKdsn$|MA-D!X}#UP;sZ7;@pYVz`^_#`-0wMxN$&3M-mTxrY=ldDBxDsrrgd zjx8%CemZWUY#LWxC^)_C`oaF2@d@9o?#gYt_b@dn zERj*A{dM%iW@`4YBiHm!NC7f19fW$x#nb)%`6PMpAKdyA@@Zmsvrc3tNR}a`L^6(& z7$MM)B~#DKs|XqicobjPWL*_e^15A6>U;36&z)CF?Dh=Ym+yZndnYL_p=W1z{D|{* z>EOw2WIGpbf$c=G3=Ay+@OC}3QVu{iO3;91r#_qHZnhmiB*mpv#7*kb=%|x1`}X=K zk<>anJ0tN$i*7I8AwMl9$I8lD)NRi*ZTMJIapdSifqR{0%f5Dcl@%}kXe`?<=tfX~ z^rQ5%HCxMt$iYXt`X#04fCNJ5Z1o-_oZnJYPJQe-di%1?lJSIH0Bw_ece4~PBkR9) zBp6A{fzUxzlj-Q}0tt2b%T^2Bkp#;B{)0+1s$I}FhmzCw)Z6m!Uq$}Xml8lP{ zKj4sXu`?QANt+ zN6coJ2C50Gb8z6EfxE)%l z4gPazu>mRhITLp7l8qdbC)`}Vd^x;E@&4;_|j05#yE67Hbxw|n2s$Pnr z*ZGqmK3`7=nn;B_WPad+7^){vh7faxNGKM9V(LI?<=-tccAXen+KB-KmfS7NAIhJD zA;>~g0Ey@}>|$v3ky|^SL~5{+6ayfl=p#-VZG>u~!tM3)C7C)UqHl(f!LA|UgfPj6 zBA`q^&kilV>!i{5-h%d&NzEHm3Hiu4Rg|Nq97LE9iTZ2Pio}*LUR(x@NBDPO{04GD zntoB!^GAPwKM50nv`$!QeFqwc5S~c-ENWAcV4%_3#&Y@gy7lhPE@{K`Ja6tZfv^^b zJ}G}H3oja9m6$hX1{M=?Z626XVs@L(lI_X;1o;b64od1ta1OyK^z|VF?gNgUa%2#L zOl2&YWO}b4Znlbq@(|m?BSQ*ytc_4VD0f5r0J}!9TXZt%EM@-&+il#t`f5<4ZS*RDz09?UWasxdnX|zq~}``1s^!G zNK4ggyniGZOV5uz2%9W?cJ#ld-{#UNZ#o5^wF1iRF9KuJ;Wb%0|TLqz0+!vpO zSXo|PUKL`kp^3Qvz{wYeS*Y8wwgYzh!$SkY4I;Kb)`p-S*_KxTjq8Oi&yo~EEcH3( zeaNe!MTsOC24RM&^gAi%STAZF4yEXfIEg0R>gVqtRdAb?1en0Um6;%nP(82}IlF8J zEAZ*n&sNdgW66Nsmm)}sCFNU4|5Vxtk?0WRRXkuMG4o{a;tJ`k0USfh%NNC0q;6Sk z`SRsjkfV}qjuaM=2)f6T&KlC9L`~|0B5newjB%vPkd@vGqQddw#kyytWgcewF@%Z) z03c;b(lddRbuH+1a2pKK*stCaVUKw<!X?@pzAt7}Y6%|s(fGo8InLKVvg3fyLe;m^;Qi7`i?MhG1fZ~QK z97eoZ3IL=z@($S7BzIibovsIP5s1x){R$E0S)E0gU83X<@qmKJtRcsdn0V9zCk&vr zil~`8KRy2EP8Ycw?fK@Ke7AfIvaGy(2deXSoDFD} z@a2Rc96rKCkh&K#aWa3%-OR2lEyC`^IWlhqSA+a26til`$;9uloo7i@=>ptcZolRc!<_N+Now-%*Tf8Yq0W zS{Z1UNHu;Rx*ybAE;lqX>Oi^t=VYTIl>6;sfZ4x9x@>@4+ao3M81gimXN)3j-sMRd_h7e5yqY92g!~g#0HR^4^I8; zPLwtx4u4jX2latiTN-J!f(b-ArcJjRpoKJGbT4mk9#G(fEFa-p<#E&<`9f-oK(FQ* zY(6e^mYSLx0v?e@vP0Ei*M2(v5D2$2(uD&`U}{IFV;$VhK7uqsRS4KJ6;Skc4G`;) z&4(G8J-IXu4Fq4rf5R?=eC_zr7~56}Zk3yW_1K76Z%34YD7-WdY(yIfC$O$qQ1RdA zwUgAvBM0h7+EY;KM&jX6*e3*1SJ&p_UJ^|;l@P`-))yd*(0t`lN~Ea%2jG!PCnl4$js<_^^T!DJK31R>=Y9TZjniwb{ zf+^CAh0XwvjAvsnKAI)Msn=`Ng31EwVIl%S$tE{1Z@OMKI9dSEy?|H1e>KFmHximg zoq-iB2S*Bc;p^|egUaNQQp`K+1Z8IFZgSJ-S4A)U3_;S}$tjBjBwbV`2J$UPM6f%2 zL4EJ3MmeM+NE{rkI;%G)nIH;nBC_v9W1F(}(lC})t0=!q=CQ2re@F@&=Xr&rivSOT z3~(xH$azz?bP;@ci2S=*w?^vKh}<0mCiUEAsPRR+2;^1CThgR!#Q!VC$^_5rG}yic zehmp2%EPRpDF+h9&$kvwjC8sn{guQ9b|%2;5omC289VokmVmma&%OVOo1httM(&qH zt#>vE6Iwk)jN}fuBgiQuh4Ih@CZyozJUC@UWlFT1It!`0oeYLqgwO$j7->aSnTZ~8 zU?%{pQ8@TYxPKn4a6XQia!fop1me~pZnU%i4{Z~2N92+&NHHo}i(>&F-Ua_z=?FVY zq34aFswC4)UMrUDYVQ4@{_rb8xj_Z;duSkU3v7}>8^b>^7q|Xh3KR9iH;miy_QjkL`V35>AwwbzLNIwY*K0FXi zLo7YnCN=$(BqLC09|4OPOA=Bzbn2Yspj`C^5P=$w6i(K{7t-I?)t!LJsUoxqLDB!- zvZ$n_3I-N#W8c~8bH(P6)A~XgI&<tC$9c^W0aO(f~3(F6T zrv->Ri>YYJ5*T3z1XV;*3JEL}qeI%DR^Zi;N28=3@-%59!Y5XrLFh+n`Y;-RLAvk$ zI1@;9s6xcGYavLdUeUdi6k7mkAlF`LP(jfx#8V)Ti-X;IPJ|qg=yqaEt^F|=qD@7O z2Pu~zx@=Obj!L2LnTM#m(OBdVN;J5RTVUH{G&mgr!Ch&NZ~^w>5%9}}3(tuEVIdk` zWlPe&01+2T@;*3aM?tJ^4wWLT%}m9}%nhgfNnJjQUSY!s#|!4cSVpHE$NOMO5klVuAP+00bS--SWc*5ale=uf8wesD+| z)&OauIHI!x39&B`TjvjKo#%&BEbGPKincsi;>S9r5bB!Cc=F)zUMe)gG;n75O0mz}mUALJ}V+bHj)d6(?!U0d%LR`o4~zC_(;rb;itch`T^Wi`_yB ztpU&z2GDsAYfK8b%<8k!h;w2k1kcYXBJn~@NrVQ&y^VYftW+g3{z7O(KmzL7BS&Ll z0CS;J-Yzg!prQsJRbiEPyxKz!a!3dVc|+a>Fwaew$(akDo`@o?P1r@P4^bt8q+1!d z8?=9W@Da}@mvU+MmrO!NlSWKf7&D?xh;^HuA`$tlHi3&&;EUXT+pL1suNN%5g4F~z zE&9G`im$J=<9rG59$<1ZWh4=Uy9q>nVE>ae);lTQqO|f*zElCQnBJ% zEnPcbY;trCL0cLNol$%p_Sf><+}zgYW`kdUU0YT5;kOomCbAHdR7|vr;DX9}))~D- z#u@)!BgQSrWqh~;4T1cCN0E4-??fm;xV#}`{ZN_~|9N@_1<_7|D~r#_Bnm55cSdI8o8)h~R%7 zUFFyf{W>?`FA~!!QwI5v2#|(|K;I~_(yvsTSt$3$RJUnY7z*^Q>8{VN68nXS9O*ft zjRmOyN9td+>ED}1h%M}ZOTf!AVDFNPw{F`;H5b~n|Gsg_>|5?5u}RLr-PzbgUEMGj zNOfDmn>R$eiG$5*N9LWEv<@y-W&dXosDVU@aG;H1=H-!hKn7G~Sm`4S(*5vY_NMbi zZQDI(IaN?xU0;fdj-cSc50yBuoP34ziSSfMgnJ%MT@4wxPp~OTD78AqKZay6)r|mj z;{lkAnYO|R7GEw}zPt*G!a*ee&F~)x(S)8`kkAc5c^M1!7zOU{Y;aI~0z3W>0*ZT&|NQ8v>BE}#@d0a9UH4A21g)U{h0 zHIZV~il8m^IgU1H%GL>0R0RcqAvgKYgaWuSay5OU0vTQ%u9-Gj!2_ra=_blrMDfs7 z2I8yfg5h(TUIJz{?b6sO7mQLNQrGa!%Kv`Mc#_`Q=E_s4Aa>wS*nag#gdd(pfm6dA}c4f@fRynlza&={cc2BQ-LiDaV zM(1N1w5_btZr5JajnxR(JZ!>pSb5)ze7IDM$4+H0m zAJIh`TfBo_J{WK;9v{fc9L zqP;J9&<^TeV1tBHDmaDsWq!MJDv-A%8yS! zku;~l^r~xU_~O7o#UrbG?#2A9rprqdFMJpVuqPP+Umu_L+kAQ8yETCw5G_=IPSkbk zh(JQw;+a0x>l2|L{(}a*76fikI=4J4S%(;w$XH@yxxprrqFS|l*Ga`uV}BnXLOa&x zUd%u-FCiEGcAr4QKD{hp01aj3bA?j#Xs3y=cn68N;YK?8-fs4%Q$izXHsg_!m0z6G z+=qyOx#pR%jjpU6n%}-1gFy)a#?^My)6|Rv2qt`n6Y4qa(5)dBk>FdMeDf+^iQ#I4 z*!cMPp*GYRpkBKQZ4c7W9E>Cp5ce4WGto1<%w`(|3~!+8%Y_fY878F%s`M@<5U9eS z!Vtk?k%RDCDwB_H-C9g)R?r2@JVEN$h5cg)=t-x+E}S(&+`#C3igsN68-PCePydhLrP-W2u(^#s6YlW zn8Q8wao0CF$pOSB*tr-PMYsa2#3d)I`Wcy`Xo!Tnu4CoFX0V^A4v7nw-bg0r-p$I{ zQJwBBJHn>{zfYfk*}6qCl)a*&f>7PB_{EU!2QV)WXLt*tfhvH9BXjOWU$VjB3fF&U zrMa`6It+t`AxC9MX))~k->18plJ|G^9gxUI@cN~4qJWu%d(X8f_o*f}IS1m%Wl z)Aan=*MD?_Uk0q%Cy24&z)Ai@_rlk!q)kRb?*NMM%n*e!!-2z4W0P4KJ_K4Q)n(kC ztU3{3L(hgbpGnQjig031lEFMi`aKI?=9PZ_Y`I0tz^LJIKg+C+?XW5vZR$z07^J!7 znR(Nby_;8uMLb_ZI**C%@5n)?JE%XoCK~gBOnG9s%6OOUS>M>zf;qtg+dUBcu#Gdoy@0#aEKrpRhZbLTgXlv%Cm>$0g`v--0`b8 zLrDXfFyLB%E_73%OH5(Z6EoTkc3`mTJ=i3$i`Vi=(lfzfziSoPa88D(s3_^thKOFd zZCigs3B2G^H&>(>3)!|Y$ay4Y0zIa@SZ5)h^eStSjbz{M;_*BQTM!8q?lZlro|=>$ z6OJ&ON+UW&$uCR#oN~V!UFy z1{ZSO6x)MJnV&%%66TBKpeJ1O^G3gKAcaOaX&Q)EvK)F7ZHh%@k;M%HqiDZ4e}+Aw zxnKJPYe-5tkNEBKYV55d6@CEJ-VK&XpcE%ay@%46&DP-6nNfez-d|Z{N_+DD>$#1; z26~fitnT;50`?3Iv1sW$CV5$e7zsn66(C3P~sKqpdngbG}hX`R{CSZY8DDj|lV z6R~G}l}pgJUu#m5lNHA%-j#0@RahyVd>T9UG`+vzB)Te*rBoYpML3uRMKL@=1i+Iy zR*&@KgYJi8=eb=a@Q3&!Mg)#ma9PRhPCREuI)#G2mzenZiWv@qxE&@->g!&wm06OP zMEga(#=U5sNAJV`vw^Q#l>g@h4%Yg?aag}hLQR6wiy{?RXr=mfdJ51YCN(wnh3jum z&~6Kw-e1@LLHKj&_n5cc`HlK*0|CNTo2K>)-LT%O55G1t00gdfY$xqCDXKqB7-B4= zRSmiwFAC1aft`dx|1)UtG=sOx7)&exVrT^9TX*Z_j6AD?f<%)9P5>5MjQ&w)&`fUs zOj3y^OlGJs_@Q&V8nPXd(A!hH&F0|vP3FssSM10ZYCGl^0bVO`?DQNCl5bjZ5UaD) zEzhJhbhYF!;KT|%Y3QEY#6kE6Cf&eeEw4^As)IBTgR5ee1FPUTu1d9Cm>n*MC?aLj zL+fi&6^d)yY%ZcnvWLfCj53n10k&_m0S1AA7GS8t>Vl!Fm*!dEehpeTcQ(>R2k?ZO zL4>cQ+=Bg=To4+sk*1b@be1)1+H(eqmrQ41r=$CgWk!(W_jQi@gA5X-pdlhPoI8Kc z{e=R4tDlXt22$F#7)>C5N;cjEBhUd;?@QOv)a*o$ty~1>P9Fw{PiED-@VDy8Xd}=F zOS4bXOr(ns;agMdQ$S=+w#!PDS-Fj$7Lkj6!ef~)XigV`_t}RgUbdLxH{KE z49DvL6vhgJ0<@5|Jf%x!e?tC`kRn{rznAn;lD+u%;wKrif3Men;6VR-tyum4;2&P; z8=ssy#5b45yxQ37|F-#NN69l(Rt}=tlCV`yD84cnw%Jw0eD(E4&952Oo>r!CuxGO? z66IO#p||M_gngL9ik)ss1|E+T+jhEbrkTi1#Qi~A>A{cS{YFO#8Y7QE*N9?Fulv)x zW=FU*YRj+Xf`WoGbz07nn=f9at>1n3#{zeeCAkOrE4`6rqb{aaOUE_6B;iXubKI*5 z1Mr9>EU4?yri71rTioA}#<=I#I<;-%2-#I(aK2n;M=ajV%dzkz$RpSsY`Cn z_V793gbR7S4nLZ?V6r`ViA^*9ch>R$bX=xxEOzU7L(Byuw z%#~YC-Lqln&zXBlpH5mK?L^MXqrR=HXa3IpH6~yX(oT1gX0}-&xW++-LSyRoH9s&A+D+OXqG&&A9))`DY;X8x)BA+d@VtBba_(&Q{Acoxw3e}cuW z%F;^JeZvNgYW5th*^|Yw5?R{|@>TcP#nUfo&ZXTX3@uwbTAaJ0A*`&+B#QRzkj&ZF zbZh3$H2Q(_=lgalFdEOlWncPiJ?(}R**)(_%WIFDowa~@DVOjuEb8J|iFkeEkk0^0 zgYAHP_x-#@V>!l2XB?QMhJ^6k03v=v02aO0_Ic_aiRQF>}+YLQ28zWP1x*C)VKPYp;- zr|6Y{@5qN(XLB>l9BF5AP~H%yRkSy)`wWt++@n z{HG-!KDB{UC`FoDrELI^F>&QKnJdA|XD5H?aL?*%c|oE!k$8l5+g^wXBzn?loG{@!wk^&6`4bT+K(PB_j&4?o&3d!Gj$^Nj@S*FQ5cgD}h}q?fIiZcrlX8deUt*=n z8R6+B(sl(V!6i{ZN(v7ITJM_^-8s4s_1!n$n!kDG1>8TM&v7{rT*5kq5?4d$emC3H zlfs8{B6n*x8wRhC<|fv3-iYO)yUt$goG#?*O}6ZiQfZsmB$nOr{AU^@?*ebG=zI2` zp0$p0x^_zwMkZYybM=7AjG#P7i}kY| z(j=W7{pmZ+}>J)FmfrhsnBL)#dxPxx)NcyvqAfB|{IDp70c3iR6nT;};zKJKz0q zXibV&`CCPB^5@MLg&#|{$j>t&1zIN$+Va=#md*ZB%Rg3U6&iLgQRCw+C3^miS%e{d zOXRr6vhLMR`}J~NOSFA=O{u@(Yi-aGm%08&=%BzTPdzu6S>%SP3s?KKzmrceCdZ5Y zTFg1TXbzpo@jiRkvrYa`YV?iEktw!!G~3skY+x{$(RfsLvo6ms@*w?#QNQpJpqZgC zK4^O6{racLF!i%xSxup+qQ|nn9$t;sA4dHemC;5~k?Y5!UYKcrY6-u1brswNlXMpD ze4NK$+uZlZ_ONM}OdXcJ)@#1UjNUwrrwFZx@ORs~b45f+W_rLQ#U207*jD?~gK@v& zD=G`d>7%|EnQZKAyZmRk^JCX8b{6H)nRc(7Ozbh0x}Ag z*~;tw5i4X16Yf0`wUp0|e(&vS*Et)U{@yTpK)AnvZ)OmHWmMcCI~-739PEn_r}2YZli*l|)=Mrypbs5SOnk;?tJ zwL^DG)7(O0H|ef^!_m7~v-q85x?7NgL(JV`C)uem z%T|XE<<&c`aouaqo)gXi)EKgC*kEw6hSsxI6Mt4+4?ntrk4@d-LblHgnX%}d@vlrYdRlT5ED;n+X@t61S{W!P! zcIoD5!#-oCSO0o0nIkK`%M=8*ZI9r=2s{OdJKXZ{eS4>M#-r~3&!;Wt6|{Sv^XcI- z@MzfndzYOzkdo>3_VRI1+q~B_gpK_h7DsVpq{liGgM)~`ogYJrWUj{Di;~U-eonMT;CPKloX~Yum zp$?elIUIVcwAE5{iA{w-CI0Ki2=OB_PHj`m|NZy#QrN>--L`Lw=?IrJ69_fesWdr! zRNmiJHPIixVLs{`NLubLG+fVmddIC&{XYy+7`E zB00JEgev|d?>g?sX>c?F4Ih4k+O(S6rCq6xjHaN{kKfqBwvF2R$7WrKnUFR2X?z-! z=w=<=zu5ZK_!YZu7Wp5m=icBWfy(Z2{;|pD{PkBIZz4fv=#hN+t;2?Oxi-@aYhQ=Y zMiA%ZLvNREPxLNrG-7GxxoG)wq(_Nou-bSB@$cR}o4h(~+P*~0ch`aNxs2ahfYGDO&u7by7b+-w z+-<3Ik*g@MI`V?iQF>HmR3i^P$HSuYu3r*3+U3?EJh2i(NLG zlwW4Yk7v&IzUo`0zY@#8({E!`ENK(=s$<^PyHDbUnUKiH@st*{KYVUD_~?jH+B^A{ ze)HQNWp7N@{oXdw!gc&keQiu8kIeOBhj;t#-LWEKxtnU@e5ol|C+RW-Npc(P4F;zh zvQ#x-%F>I}UVOo$w!GoO_bq1Lp!zhQOj1>3LGTe7v!E;dVz0T)V&XIZ`9~$|03W^z zLtoUhWhuw*&n#j2<6Fzo9crLE!U@ev-r8ma*)Xi%w9C2Xo4KaQ=e1s>=i81BF8O@@ z;_tz|eX=@?wIN$Cz~330sz$+hr_~{q)2#Z-^XjjwtL3Q2#;e>`RIsb}e)TL$$o&dg zp)g}%nd!z7typfhuqCQnWR84gn3^jazn3fOIT82q-)T#$zEc{2qyIkqsE6NN@3IG9 z86DQ!@wOWQ_}A~nfpc@qlTVb-5Lg@#eX^L-I{W}u>sQfafNy_ZwyaEAN4B=%S-6LsNp4HhSoC{ynZDHX&`a8F<+9~{^r%AadIgVV z{JJN&n;W*QRB^(uq`#2StXENx*?hjJhF8mC{R#X+rg-N(qoti&4Z+_X#OY z*${T5p_Xr5Oo2k~=TxtP_7&w}9OslwVVgvNW=+=oc+(zrnO`hykCsIb`zH|t+Z+7W zQ>Q~d80i{%l){wG3~W8WIR98uL6=5cS^TJ%3gZ(f=hRpk*S9V-oW=H z-F=LA$hf8Fsw*kzt>#}>|IWZxE`foE&E93lIKSAQb%9b(GalY;XEs&5mF19kyGXsw zF+taR+sDEn*h6wtzJ7yjga34WJv01>u}*l3OfWu%PCki_KSjyN_jvH9 zZDPLs9UL1Ph|BOz9$XLzKZo-)I>re52#|7=0~@y%ufWqkB?tx=}b z{<Dn;@TDy+d#yE`d{n9rUHzr^D2Ot2z1w;jy**wnRh;E~ zWyh<6-~Tq8s&@m~CJ*#o=RQ7m)gyc&pjbWSYsWfRW`|8aR+Uo!c$a+~oQK;B?IEUk zV;%pPROPjM9ouCVuG{vOk9W~g`J(pR(YQ3D7yJHwSyjC3PeUO*xI=}*y!grU@IgIt zi~VGEPP+*>CFr~`$hukDc*-MQUuUaeNSNVel`+oWOQiZwH`}Db#&gRryxzkxSJoa5$JrwF@k0qQV3SN$kxf93Q|sxo%$hU~hlnPv`FZl?!t9=pVqFei|{?e|MC zA7Wl6;+Lm&B=B*rOM4{eA-S$REbYUW#{#13xR=Ou$m51;Xer}47<#W%;YR6@(beq{ z;zev7n2q0FRZeX9^k9DKXab+n(f0d2#W!`GZ~b|w;}mwRjL(8|g%}L;AyzD^iNF4J zJJ&IpGo40d?!^KEPQm{U<}}$%ssgG-H;rRO`D^_Q!UnhE(a|sm^n~csgDYX@Y$}4- z*aMY)HdQm;;Gk>%9>l5-mL3g*m8{Hg&DZViLngZge>J{*>yVF!ui$KM)1Oac0HaMc zyT$v)37k9XaQ!0hR+(V#VEw7$fT>G`CtCq*7ddD9t zo`WR{2W)Z+F*|MhD%s=1=c0&9fkyOPDXWO|yj0)cFz>Z*E?e&30XIsQCdrzc_T)1# z?R6($iD`9Y+HM815^#=l7FNs0%*wx}D+ZyR=gwSvb!ruNFP4SI)s zc&EdUCN)lo!F1)4nOlmDJe{DX5fQ+~e&jKiM2I&%%y;1Ty4{cfBx^3?<6M2CboY(Y zr=?+OLcQbg^kf$~!Dh)V#+VzJ2gfjVYC~880IRP-OKz4vHC)$G+{>6J#*op*2e7FP zDg9T?&C-(j2B}`^doHoD+el~^NeOS2SHg$FL_{ycv%o+GKeNb<{l~jBYGqjSD}nyT zQ3+G&wv#@3F%}3Ps7q+i34SO0Ju(}@F5fhcF8HaqNYCZrpE9Pa_vc-Ne|XM-MW*|Z z%~V%eq|vIefM0CtvP$h{k20R$uE9ha@I(u?^YIRTu?DAfKR#RMUptJng*x_PTw-Ki zJSN#DAM42G7MdLKP`nSg3uCS^(D2(mHTeL*ZAj}&NHV0SZI{`hc(1bmW3K8oe7l&A zRay0NSR%}7Tse_j>F52PB`tLP;*gIYzTZAGhN#zy9P(D z3R?_gC%gr6alp@Q1W|I{SX@SF)L%#TW+|gUpuc`pVsxRsAOG)}I245_O}BZ`>-LO1 zAH&a12b5Y@hj||?`4ny&S%@XP`)XvgZW+g29CX#muj$J*)LK=}oVu$Rtl*(2CYc|v zpS!zjQi2&iCAhHtL)S$V;r;&t*6`r5H|^{FPbjfkmHD;mA7R!WZU5Ut@xYn4bB+r( zddAqt9gjM*ekxy!Cxb&cN9kCV-s9JjTRv6u!=zgxuG-nSsT@yL_4Dd2=4shbeORWf zGqcyc$R^-7(tN(C=1wW+>-O-r-Wu&ZM=g15^TrdA1>SlUUP;{deHM`OGIE1kX5qb# z9+B+L{9-ku6V_LDL`-J=EWTY~?!5W8***8yIRBWt$Ap>S)2RNWANQLonhihPO=`WL zc`PzZMnPPt!gtyst-p4kk?MLm1EAF#qT>8cN{kp+saE~iWp7QkhpAmQYRMf~Ji5T% z&oFiPg+^`RZ#R+m3YH{i!u&nH@p@oFqVAn|FG|MLSb{Z7`V;`oI`HW;jz~CisiS1! zBnlV5ZU1nHceDL5nFjHN3qw=ABrlxf{JjrO;q*+iSe1QeS!ifD_pR;fFtuCx#v2^J zFSy%_*b30~#sd=r+wJ^fc2`3O-Fcb+uDu*kJj6NuCq06y5AMEMy7PTw%ANfK*NR#U z+us?tYU3mFW#}g_ee{sc(TuplxDPM4cM6eN^F1!pkiDQt_{nX0nBDTk&y{WaR?a%t zGM{d?+;8q4{APlW#reewMlYs&U2OQ-6mFa8_hrx_RLJuh_V)TmldwmLVR}aCAGgQ| zul4h4D?aJ7Z+26<+Zyr6lq^<|IRX*i9T^V!d!Fy#s}~rzjxAJKFGoZeOA9SMT%8LS zWXA3<+(nlW7Q+n^U-T~!pX)^JUk>XxX*eD&t=%}Olc24yxi$aToMNg@q9_VyrM^NegSR0 zJ4{10-@fSY(OM6#igpTgIiI#QXdB>Zev#2~!7YOx)2KVo_WMW*>Vm*Fwwx0It|3HU zWaUBCk!>^Qml+($HraNZll!=SEL?PTY7P9 zeQTP<`i33x=1Iz@gICCDV7rMJ!TP*;E`iV7b-WU=ZrppQ;nHCNW~IbW$=@OQ9nX5U z+y`ud_RiR05^mu>zK?H}6b_Z#Oaj0bvc@b%(X-gPRo3#0O&*dlHAoGb9sH;0>U*UN zIah|2dnFlfsQAFnYh>L1r^-2S@V)@WVD7xH-Bmnu%; zzvsRc2Qk2Jv!}=ByKI5!&bf;<1dR`g$mcVB7OdY*0wM0w*CtxNTE{JH)P=hCpSH~! z81)@(3b4-0Khb}rtx-bS*hA{y04SV8oPR9lIA?9nw-AqTdtv)+G6yRw`Fu4dvW}+7 zI!5iS_!K33Udc4X3$iG83mk|Zrs`x5Ye0*byobymMbY0AANT*Yi|KLMyKKX6Vs ze0p%L#bS0C6&cSYt;aexGV(RWP2q!$uoUapT^6F9An<0ao^M}F7Iy!E(?WUM*dlW0 z-KMW|k(>2Lay!!v25Z^|9|oIol#A0LA!WVJVyl!B1I7ukR~9#7exbv zCM6&!AWfQpLg>woLTDj?^b&eiib!ZG2m(?Pno?Cn2ndlHI(MS@t$Tmm@2s4&&RQh< z-S6y~XJ($6y*K3&s*{c1c5gT(FqDr$LIC8vbW0vFTBlbm5K(GsS0E_L-fYcQHOnqQ z;kknS8`)=u0tI=mH*0~AsAb0b_y&Yd3L7?vv7s1M~ex9p;xZxi-Y36WND*W}gwsRlwizZ&L za9F*lfp{#9K7(jD?#hR>vx5L@5117#c$>0We?Dswn|qEGUws6BgrO~WZPs%B;7|6a zq(gYHM*rb&xKgL|^Ho)tqxoI#BBc>7fvpo;i5#QNI%% ze_UmxJs`usWt{lR-0Xpz!amx;@V15?)C*PXUQw9xulJb;{-!D_A0dniN~qO7VdT%n zf2R}9wzPUXSe+c+QEl4Vadq6z*4OUcC6pLHL*^bZ1Pv)b8~x4YaH0IWJ~6%Lt`CPk zBR)OTmh0F=9k0p_#r%y*PB($udSg?rw|;_xi2r!Xc((5w~QOlKn1?!w+yV_>Oe$YSTyj0X86m}=yM1z zVcKwF68yC#HzNl&M?a8vm=#^oA5x zvoRv4opWy!m9gc+9ts=S{>GkjStYT`gLz!3a2>l1ZM^5;o_JXCWxe>7+g1|aIxWeQ z7(tj^C`NSPujShx2Ar|(eznJ{T+}g0)sFcnK8o(X2ANacM2F+EF{A}pIEqBwfY>xy zcoWmXjo{L|6is6Bh{8aAX7P7Qd;pZ!jpC4Ej>_3^)c$|W;iS)O`9sFpw$j3&^cqi3 zGi08D_&U5O{|tgFUgkWiqfn856N1#=0(v6_f$ZAxm_p$gOwU((D+%mF-ft-+H6`Hg zTWG&!s}@}&TDYLX+Q1VAJSD21V$_Tg4=(tT(IEyg^Y1}E+43T`T}&@yPBn-Qs|%-$ z%y@9KW^u9Y_mpOOhz z!EIdxoY#c6e*#u;Q2hW%Df}kJv)aE^-~)-R(VknrMAv6w{Y9?beuq>AQ6Zt@omvMs07njHmQz#VPvLx(!MlpfL4S{4Uls?q?#Yu)hEs zc>H^Dk$0}iiMhXaTw`fbA3%>*|7E^tM0u@`M;^D8gnn?2mmlimAsr+|u)A8it}#<0 z^5!0JCXh-5xi?GozOHF?NiGtD4gmbq3>zE$aSO{ohn-vA$I-NIk#1n#gnlDx5EFu?0~50ec-~?!Apq>Z+dz6yHmPkA3mbUlTNK=9Iso*5cspdz!(VH5_9wsVK z<@%R=&D=XE>bqpAK_bH!^k-@BA7ltLl2JDGDT&67-ABCB!Hf{<4C_nE+DsK_!t_YEyI2f zcuApY&7}`!gD8U;8a(?l;iDKY=@EkTZtAaX*{(g`5h(i^KT%)5(}=d*vFG8A%2?L0 zCXvHgC1KBLDE1PzW$4pzC$aif+t{*TwZ|*p&Yu7$YyavZLD_Z`q-*l>3A`?+yPPPy z!~5atTE|AIK^exYb9;p9hKE z^b&mpdE`vQV9w7z6FDb(m_*04!9rDupx;t6cC=)qTg15kzlr}PYrAK2lNX^(ivX~4 z;pALKu0dYUIh6;tw^whhcC)m;9&&Uub!YH*Dg1w|um}33RQ8v0;3*?#Drpb@+-R?| zUl(ai6~SVNvFowqF}ptT9iCS|yoi+}fyOd1?5Xnex=Ie{N5-?s5lXZKo9o|XN_4&X zm!uN%5{xl`s#E2(@s@Qsl$GI;%=s8*3m)U`vn}e^Dc7@ycLW}J34%6`f%0rE1R}Xu zIhmE=@*V1I8cDYQLn_%2vL<`j=XCn(!H z<39-Prn-Plvj5`kxRz4f;9e3{nj}Y{yf6w6x~Qh4tOao*9ToK8Q8IvuS>CDmR|e*l zx}W#LFEVSCDxR+=lo|(|Zvi>i=bWA0d0#9JBD%z1P)sGSb)(ru*=5o{GNcOBuegdy zo|1GJ)nZYWH}c^y1y=XVG2k;Z?*V}1H02aN0AF;KGhKrm8VX!)-}=a{9p;^z2TTaA z<$Hn8c2$e%;NY>9^FRkx84a%}M3dg|LzkOIhbJawD@tJ&Qik=9GSRFIJ#h{C4s*Tv zvliUXcJv*q3tXUBqy3xWggOonKGv*)Wn=KLG-3|>95j8}fwRS6%-E0 zw2}>mU==U7S>~aQE+9`h3;&3FI$R-!p zVt-l+R3T}>YrJsl+WdO&LU>!x5-$s!-uVAzO5_2ef^%jJTL@xo);+dpIP~~1m&pkw zRKd5$V#1Yf1Jd1BIybNSaLoTtdGBm3&?MZ?CauxgYe(_-Ki$11Co#S6z8_L^VD(q^ z+R8J@9nW{~`AZIbe;gD3zRbVc$aQh3^MV@X!gYUG%3DbU@^6KcM4mZY<1o1jf3%L|Y~^URzmIZM~LLRgT0R zeOT|e@Fv>RjMHD)s=wbA1d5Rxyw}>lSIAq#+-x)6D`q>(WPjDu8r`zCpN-1{P6fU@ zJ~gPfZ^E1 zF0W!x;X5PtusEpowxwNq?76T%TRn2}9DX;ABe}oF?b_O{KFNH->jLf_^5PAD852H8 za)v9WP$Z7qqpI$qT=;pd@1* zvmYqo8Z@XxA05w57#GRMAs(My~=}kta|FLIc6lQeOi#4i`i^6g_VIjeX1@QeG_CO zh^Be;e?$}r;yBBv81I=u>XMF#s;M-&6CFZ*T>So;& z17E{|Ko*y3i1T*8IWw+dXs|u33eiNi&n?)7OtIV4im&8x+cRxigKhf>+Y|<2ybbBPB|m?5P{X3V-hAuhK@>%# zd+l!L1^%#dfMlyT<{07bhvLg0%xIW=a4C^Udu17T<&udTIr#?&>S6;+1zvz*_qIDn z0)mZAMz=YGh9{D4|I2OpIOed_qcbOlLXf?Ea&!&_6%~C(^5rgjuUs7J4XiIR8Vr;V zD)KaOp0;{g8bxPG`N>h*FUv-yM7XQqjY>fc@faBN?s zhxFEtqduPEVcW?|x(=9x5_8IfjN^F`YH*sivc9Au;&Z2P?1Pi}XOVao+F&0XF@b@r z{1h>8(r>nV)X1$Scf}5pzlWaKf$qhlM!a|`FRleA0rAC3Rz6v@)x5TmDJ!%hkuS`= z@xrPwz1*WE*5u3tM}sqZyAn26uEAuQF$ty&f%BC8o8NFY3GAC);~}+9_hDx^Y>|b} zZK*`~9BifL{e zy_hYi%FMFiY)vQAtK}Q%jSG(^t3Wm6?q#ticOAw*d`46$xheOv)#nFDA|%?9Rc@6; zpS9sAxI^l#oDmdVlhwsYX&a2iGQ!6CT9xs=OVOlV`K{-PDYijZ_ePU`x$klKa?&IP zm<xQC)PlKeu@uU9DldHW8Rz18H45suUHiG?EO?pp2f zy`rswp3k&8Q%HVKNx!W3IJ{9ag5u62D@E$%()@y~FS&H>0saR?*(bAx^n72o+P!ay zM2GxADwT--5mqx4s%RpXmtQpfM)r^&q8WUddBOaHYqmw^XZIe4Nga%)TzeyCVTIPN z$UAH$L3aqBZKP_np>1iAgwry(d&na1dG&Ex$Bav58+>k+wZP{B=3EfB&-NeRW!C2S zd!aPg>$%~yOcm@g?DLHDh&mxW0r9qBjr>4)w7ARi$jHQh*ZqpGe)GAsco2A%<0j$6 zHiH^J+P!o=1*y2==ZdCc2YG7CQ+A4MzO0&}zE`kEnObe&TG6z;RtM2m|1G2VU7oVy zAr%u&^>+H~v_qV(-IPR>%r#C%=Bl*A3d%eLnL71ap+1I3+whU!FClk0vo5ZcQmsmE zZSXAi_vMiy?rP*ks~H|-Lj+*x>=YlCMD((3cml zt$huNp}(0dj|^(B_NM|x?|T7q&yeND$+hiCitc%0ezU=_sGNYx0%rtB``0s&w)>L4 z7cLgJ@y$1D$Ma9>E4{bZV2VRH^+>|B>+Xt-Zc0Am3p)jAaCus-C?Pu=vCgEq_;D=H zO;-Ey<-PV>*6_WFCiJJ6myQm2#jO6nBc1KDMnC3u_=Y`0`$AzxOC}mzxgs3!CiAdF zR8FQ9PT20U)Cx9ZD~QX#-DlsKDe)(%zPHeBiqYvCd9*)XO!7Wx(C*$H>5}Sf>v@oP zH6UZcR*vD#W?Lp9!=U5%2)Ct1MA`7IKCakVqYfrP$fZMUW`@m#TY4IKRFA=t?_F;N zvfuN~td_JKyz?R6yb!pH#nWJ!4M>dAixR_5g_IwF)nY0KPMx>{-{zmfm;0@{sPc;x zq6SK&b|U-9%dytzOOLe-;K9lHR=$Pu$bE9(Kf*ol6Hs9)WyiOb&%ZR3HOqd0hm)b@ zQbEiCOZqYGr-ay^=$l{5LGK_>^1C$~kdHw$e5+%(D~|C+TfBMZZkQ6?9*0F=c*v&M zaMUhBBrC%gg}Z9x>xz1&NC*OH7~A#r{QF5>gK|5s$2NVREM|vYhM=&8z)hM`sKXo zm&9l<&I2$04~N^WW8^nt84?7M2NJ$QAkOVNF~ucsI4zT3ruFMHVtBDa09~!&7@trT z6{iyf96xPpZ>nA#!>4LFlxtCM!qbEeL=}zr0U2`a^a)tLFqfyN!^~T9SZ5@dkCMBt zL>$HMLJNUT4@_N0N!gXd;W@3LJ4@(q%Y#r}$n+B%z~yO#?sJj=TVw&-cYTdV)9bMh zM_bMhKQRZ`)roF1$?3|aGh4Jj58tV@?CEiAoUhNNbTDBvk0@7N;+m~8gq?Ao)n@RK zO4NqW6HAFzu9o#byN7CHDp-^AYyTU^X10XxI|5S@wDgN+Uigq|W7yAUe@*#)eL&8o zCs({vd2i2DU3xb0A93|L$o1QGj1==x=CsgLU{I6hL(!zRA;>e3ezvuwkrm7i91gsl zvRND%X8tW&#TeZPRO40GfzVWTMnl#*WkvThTgGwl@darJdL+9*%jqGzGrwTK=lN+J7Ub|UPF^MV(%oj z*$QmH7q`Y|Cg)ymqfRa<+-bP}V#$LjnQyd@*WdVd5oV{T)22tb#}_7iG4jc1=}CO5 zdR&{WfCPz#B-WBCxY$gLaUcKYQrI6AQYrZXD+CA7`Um_h(X2oqi`SWWFFw{6@ z^Lj05(3nm;xu3|u*=d2q@f2K#w{5c6pP1>MdFw>WBoFPusnW=Odq2eJWj0hE&APkA zc5YPrmw2r8A`_dN?DR2qwlq!76B-!$FVYY$y;oZ1T^jb#f1<_UcQgITuzURf7B2Y; zIEl(NaNunPwqozI!}XJ2)Gi&$c3j+DANRG;sOv>w!b*qNF|qTcbp=bOPG+Qn?o;>X zM-m$$F|4(&7F8+UXH|uB@*+RbnO%b4+JO>|-l6ad?KC#4lvvTM2>rzKCtn;tl@=Y+ zZDm`{t0()fCW<{gNi*^f`pdYcRaFq_3z|@(X2XekjAm<})E~p~<|}8wI_DQguaXO& zD8hBXH0`4cvg1c68z{30X`3jhTbG1Swi@iar(euEf_lX z68t(N&Q`(Qt9P&gDo%gsm(>q!ZmdsScJp(uV%z z(JIdG%sM+>ExuC}SiI9`h0X@IZA;j-=rTUXF^+7T=uuA+j5EhAz(m zcl?j%>uOj%inm1?upH`=fnm*y6#_GLYUYJ{_k!-3=UQt9WwnC;kb_~S_K20 zxMS?(u5nn^NJGvnSE}%6@=k-$qKc9A;xY)s7BDs~YK_m?7r%I23yD{y3E)~7F+!+w zQ3qkMHF*cbE@372Q*L!*Mj&3&6pLG$=Y^}zf4dsre*yW&#p4I+@cP(1hsuM1HE-Bj z$JC})1V=q}k#@((E2PTjI7BeOPV<5tgWf5Q;L~BJ-a`ITeisw?2+oCfb~v78vGaU_2n70hcBSP3Id<{0 z*XcneznlmE#CGhoG2#sT_`&Hgmrf)`=e7hJ2Ux&8&w>cj`-LXWCw8l|7Poi8Bm#4R zsBd%l{uW&Ju$amiC`XqtZACs-`+KE_iv$>;K-YFZkRV7}Se1A4JVwZ($GI8+m2TcA zznK1BHhOigW=@P7uH^ci?G8T$;g{)WXF60~26yr1tQW4XTaiCm2iH^uVi+t1F+i+E z0&<#}g3@rc zm3>DeEu$EP{B8JPZgP^vR1AScSYWb&$BwO+18Px*3A$79Q8bSwy((mw*$A2Dh7c6?8kv zD|o%OAV(%fyd(s3!(ZYQ)$}QDlGCT5F`G~}r*9oXy_cCgc z$U5Nitm`sN9nBB=s+rA;bTe#k>f4nuxVu;}@le+q_OdkVS*&FHB%N zgB=1b5|U%FfOd3frV&04FhAe>*w`8!90E4fCmDm@sJ6L}qZ9Urq9><<@yw8o+;hSI z(c8E`dTY-M)CZnZ?z%IvB`L#H}Rk~6Jg`5Gw~S4&~n-vB|09> z;?c#K1J6jCNO=nq6KI7h_KSDX5T}{2Gvo#0d@8+{9V9MAHu+eBvb;WPtdsK`9)0Rf z`5+qIE{&22dcZCEr{G{8-OF0*Y!}1r@T2_*K<4BR+7u>xDfz-M3VAQme4da`(q+o> zg9b{uWKiIVXGBl4g#=E5o(Bb8x=GRB z$TS&F3VlYZg}ZE$TLIQ?UP<{KdebeQU(zGo+}fk@WZv9n)rIy>D!T#R?skd|WAoyr z)$EVLO8eqSE36!gKl#m)&2x{P%kq;$hm8#JXL&qa?j@ zZ*E;~iJZ4syXy0Yd{Zd_??wTnzgK?;u2yb^$!OzdlL49T(DG5$7~d}%x|r~CC!#7a zHFRJ0L5jSmUyUGK; z;kLt>#B^|l>v0n;WRvW5O?bOsg0p;^Ld+eBU*d6FJ8fvSPh+6XY)ZlTGZ|c#E-)7($Rwod69hzI19;YX_&U$3hOrmcmyX`$q`_JAm{c zbo00L6eg~Nmc7x9X=--dF7Wb;rFg12@ahqa0X0-tPT@IdvvCJ2Z*UsY8;DHr7757p z&-e{rD?*^j{03z%P#w5eFBlflyVXBylxznL|7eOzWRq;!NxZRy7Q$k^>oiSw{sC$# zXF?apJ^sgoyb%GYSsf_t<~JY#Y{sf|TPgY+|T4V+P09RD-gbm}Hja5`Mb zb@>Mp{feb??bgO|+Fesya z(a8sozLVxiI)L*$pf_M~B=akv!CG^hZb50QVX8Xq?l0e}COuOz^v>45Ng}Lg=POyB z_=EuWp!YO0HWs8!$hba-kfwslVc`qYaHzi=`NTFB`o;dCrVx*Kno+xGvEQ2$SqFaT z;w;toiUg)@qy|CTC78&)Q%$BA;jSiUo%rk_QD4hcTDGg=bybYVXx)50#ZmR4VYE?N z`SqCV!D9zBaDvamLjK^93a7?GwnH?1-Qskvtp6FGZI0u zK$H+j%d6?qB(s>*1c*#o1Mnq$weR#M%7kB70HkQ9RQj)KswUao;!^u}SA| z2?Ld{F^-#wUD?@dNI0M=Iv~#Gj7)&!g>EGm{Y`yl^|NCBfjo=4M807IwsRIo%HM#~ z3(7<)Uo+1QX_b3?=*6_)A;zofG9?~xSHkY@p6@x^xfiEB+aiz6eTrjV-F!^j6)-qM z`+U9)AF+e@7EX;3+GRCx2TTVzf}g8r16~y&pJeXUd?!gReL)K;fjFJ;ej@yu8pM26 zk7vDWi+!zg{g{G6>;L3VpUkc@5HX#izG+Y*pqpfUlKxP4ID6=^IGm9klCoa8f8<*8 zA7oFm117AY%b&J34{>1}*^2cm>5i8k8YY@#eoQnsV2&m!LW_<4J4Z~NmruHYpmwJX z8n{5Zu9vWg5Btx(=FQ^V4mk@glr$w?cn5P{thy9X*T54`dXN<$xk^eof{!Q)FkatU z%U)+_=!#2OuYH$6?=hp1jDLq?5@>7hdHOj{qI!1NuRh<@IOgowxyg`b^XUg=s>TI; zAG-HN40-kqvUvvoqx$aHQQ51zn{|6XR2lP~z4g&Yrr{NHc1qV8N;#4{%%c3Vfy zE1Dbp3o>ovV|f`Q!xfs9rbYMYA}u5hvNPXJj2NH!L&$fL2cR%q%&xe0BT8v-7RxPk zjeW%0s+>FP_VvMEPaFhn+jz~q#3pi^OtNZSxAjl-ZK%J8lWx|~T@CRSl5Vlg1$;}7 z!b>)D`bxI$dv4cT4qQnvIca{pG6tm`z_YSoBVkqKQ3Q zSO4O(wzJeAo0p_b!nu-rzt7+}Frdt4hTmtz?CA8sKn$WY-8zZ#(9XKe5Ry)wOgo3m z!*^WY&+{9SVk0EKu9ZlxQ*CvxVq@}|rnh^0bMBvNJjGggktzF?x0#u9QNCcSVNz5E zEE_kurzxoI2y13uXuZ3VE)(|X_F61;x@)j1jZ?uMuF<{Nuur1e zd2Bm&LBX>Bta^hl##}EGEN1D%WEaCB1O)SR$(j_bAx)-;>L15vwg*cLO(JuC?yi-?)Zv|5t*=XCXzC8cTX30Wbz}ok8VTBWE7s;P2kt^>b zP)zKF1k5Gd(G>wbLZVg6tT{w~b<`FsAcJY1na-^JLU|CZ9FT-8MLxRNp49Q*)zde; z;0lW!+1#2ZS5C3wdH8JyO)2B)-7M-@L`UtV)#gJV3X4$lvAPm-gzJO;n4qD6qK^c* z_oItKVO8JgXSSQS`JE?n#HOcuyguA{Z{}HR#Uim;>9|V1k-V`u+A8}zEu>pEzbMb> zPQ5zAN$D$iVFCg~cYW*Her$w8Lz%MUd>t2OO%ePHx9zTMNwO5p(N+z9t%_JaJB{>z zF1wW?VqSElOxVG5yFzyq{|!y8`_Zqoy${~OaY;e5w0o&#Kh?bBZd`zgcwHbx zHt44_Sw7S9*=+u|K8qJo0bdUAxSA9^FiGS!iq+L3@&*9g1Hg@;bxa*rK_U9EX zqp?x}h3~%O^Y0q*b6cf7F?*ph#vUv=D$d-{9RwgAOGLqKPuRbu)W$l@#_oY-~X zxP4<%o2}QiHtQ1>{wlEYtuF?bVvM)5??wI?b&7vBSg|xA$fKcWeQtVQ&D>$`rRU}r zipO>9npCxF|Mnus-X~AUp9Z`tC?&2)?D35+4h+ds#`+CZ!`g0M#wm)I5SCl}^|I0X zQE#=-W|{ufnUqZQ&kH;?Th{G0R6BhzWLJNb>PRhUW;NRbw1|x?LK&4I5SJIY`{dfx zLt56P(l35+#$r`lgA`fupTM7r#68`@_mgLxN5FckDTIOt&fIR2T~2PC~HIOo)<}aS9B9czf18-Z>c>C3efcE zEj~vn`Os;E41A^ksu=b0L4m~BE6$eLe`Vqhy)@Ig?QH>WGj__+sfJP@Abb^d_r4qB zEa|CioWpYRA?bCm3hv(C9(!tPQ1N%|_Bd&g7Ih;HM5#{Tnya>%e_YdzxfrQV{Dq}Q zQrnd1kxot9iz#AEl@33oDGMXnrIY2aYG0b}&0&mL@MXCt~@_Vp_L?FBIQkYwNt7CQan z^AalS%Gx(+5&nsr@7SmXMji+(QVff>+Vo3sTd0(r%}+E{KsZ-u$_FiA#g0lz`ULry zXNL#Odu7dDD_KdT++I-71&KYQZ0O%IGmEAgAOBueU%BkkO^n#e4y9P&yPp1dTPmsg z%}#K8E@@8Q8NHFk;O44R|%ayXA$vgxl;myNpZ7?Opc;+L1KCG&Ff$f-7=D z8KCl3M=eHXBX|{jGmCG`xyRhfa+oNwfZ&MW9raDP+8oWMtIp{ z5FOQ*EO%$1%=EHr#Pe#=7WTPJ=&T;|Y(zwZ=c(=RKo^?xd2YQA^#JFF$xHl(U9ZRp9lDEtH99-1| z@QdFV+sCcJRxLf{=iE$;pAN9@6PvFrOrHj}yeslQTUA1+2+ zri>F39KmDsp6={4s)Za0b^M3lsj^k;Br*icC^NGnsk&eH9L;o=P8`WG@!GhIJ$CXG z@#pfyTG?=POgKljS;_Xbvf0)6f5Z;NxQ`XEYvE?0C|CQGmL!--E2%(IgH}!9b5>`s z@r*)Z@C6doDsWkZ9%XH)b+^awrQkoETH%;aoOecYW;N{YH%+M77UddbqZ14F6X& zlG8Vi0~2wtU(bD_li2^AFgCrF)#w$-uOU}4SKpq|o2Sj`V~y^4-eP)(Vz7UTX|`6$ z^DTfvccN^W;5TS(&d?+SJFm#fc5f$OEtwNOVj5nWfQ-BJVx_V zSov4xbDx~{%UHX4Y)Dq}&AHZ-=5)8tXds8T911QHzVsZCeV|(YWh91O!ZLxn8C|#G z!Lj9x^vany38JTd*_ulGhjgwicCC>{Bp(t!r&-Z}U=4I&vBnU74 zMATB}d~Rzr^p9?C#BlLtb_c%Ngedas2w&01OXEW;1`L5z-pgc{d+$DJLZxOjDF3of zT;8u;H18{I$2w3spV(UE7z$})cZ@T^Ed;$Pd;$A@w z_Uc~+BY9J#pAx+?g0=l{nmBnfnDV+)mRsw*M;_)_S%vPlk4~~r|r*E zK6|L4T|?EJ)Igzkc(vT^r$p?~8Ht(DFznEr-vEhH!J2^2=^%b8aKUlMOxW^sSuU6; zZ#9W|N}fNWxZ30@6-r!U*IkPJky*L=+`y%-OaoIa?Ls{I*gG~vxJBJ%el;fZ`GFJW zPa_Fq-N!lQZL8O>FpC`wd+v87apLQNZVA*_q2XO$Y#A0`lfZH}Ub2R` z)!#v~N?zO84fp6?gSJDvE1{!I56|iJ?`@mSQ@$6zK^4(1R$iVPt&N))T2haO#KALI zRlIHvf2;d*Sj@PMI`-1OgD%JMJk(I>b2t17-qADmrfG;9nwb#$a?;}``Da;9<7~L| z^2>8uJeInfm*3n3_kymcdfX)` zRY^%nI0*btN7TJwAcPCstMhHuf8*BFS2!j_|CQv=LnjB{+2pl<{nhk}Fu)Ejt-rXL zLuvZkak!z#I0vJ6om(RSujcrx@qlRn=1bjXEmL&XX9wUb?E7B^Sh0r0*T3j3n0}bR zjbfI*yqeL&B+%K0{cdh9m+M+@#A*HOd;G|+gbG)+g38bosrvT;o;>{4)Z>)zRB%W3 z?!I?>u7uXaiBp|jmpp~o{Cc$sZmXS=)anR9LYOUXx9U3I>GB9}D!1cx#K)?d_te4L zQdb%=3w1N+1)bdj7JI5jSgvHwZj^s^KVH!*oaOh=+MBuVW5*?P+ZXIL9XttJf&Ce` zg0|*{MaUM)ul1Z#x9XO+$D~JCoev%C8>&7PQ$(!muL`F6#@&dh{WW##Ox4S%1r`Br zw={14^_veIlTg^qsSvm1<)4R6N&4y+Fujoq#XY6(eTSmLyUilchlqI>H zqd9BQecN~SHoI=)1c%OTH?vK*VLd#rFI7}T#v`+3WKgn0fP$Twi4R|X7NI=2m5u&+ zCtz+3lF8$R16mnquQ%9X8f9JAEY3C@^g>m7WYppVn5cr^ypOpHYDTwEU<$AX z6?Zz<#yFPEiv{=~Km)J(7VT@O>M{Hx9r z87D-d0<*hzsUG{(imT{ZT)3{RXwfDMPBaAo#Q(t(r7aMCmB@XLh<_HWC0V ziOWyIN~Zz4U8OeE=6&d-GAcl^ZK~<<6sf=YBLII zpgFP-Df^tu+V|4mEAEW?y6AK?G0Tmhb>*PvWcKC- z>AloY`z1oiI|a%(cqqrgXAftTEbJWaR=YZ(#`rv>!Wx`xzdBIHGdq>9V(s#NEZ>Pe zUdfvI@ZEZ)Z)$#qKV`f&$Ok{%3_+XRdYGwRq`#Jr+EEmcUh_5QW5#Xs@+Z*;DsKmb z%->G6Xc&=cmk3JwwAUC`XO`00Gjk$g54mS%c0%sw#kcHAWACIQ+@)wZ7ZyMCb5knu zjM|;2|6E$*^S@R|k4YYy>M1QFl6oz72d6AO2NXUrJH09=dm^B=LuzigGK|Hc@rHMW z8{>=-!A_2FVfFd`=m_!`zo4st&kQLF2C6wCR~O5xigd;+c}~k1?rzdDg1CL0N7z~q!z#Luy9Omh4~-;NQDAzYTMo(FJEyRY6b$_OPT~$ zpO-e+x3xOxIV|F^pyo{PpmudyGrr$&txnC!nRk}r=)PwKU7sM}u z8KJp^daWZiqBl09++bj{IW_Mz**>(NHbZZcmMvxbQbm#f`K8;-?wTS;URpCNoj2lP zYeDLM$|PGh#RIfY*w@-vU&!e8}~uFJT8nY<8wdM5aM^JGj`zFU0A$=ke; zFK#@vZ_T;tH+**mlJVWI=nsNBb*mLg|-IRVvjpV7`OR35YUD_qv)!k>! zHH;FZ>K4#weFZ#kii zaWwDDkf>zA_FtmmgvmGgTjmRjUDXe`UY`l6oOnzp8D?&;x4p#h@AP!Q=x3{XhlG*N zi%vk~-Qb!qFK*4~ZXMWu>~@HvH~Y2LEvCwt<4!EIQ@^W4RQr*%D0gA`X<6yxinig) z+cU=4p>?|m!X*2+&vHaKCXuh2_uoUT(!Cs+x~KGkV;0!&hKfxj`(Qo`Da*)(0lbvR zjt0SQp!E5=wGjHPvB16WB~m}|BE1+Nl&8LA$JbYu8tUZa{Rl^G}Ljw-xteO18fgQa`VJdq`skJS2O8 zB)$lx1LFMMhig*j)yxu-Z>??(YmbdrXqI)$DXQ=558Bc)$f{zSl9tFe2#*F{d4!e2 z>+@0qVWs$Z99l|yAt}4Dh*g&A#<4Q7-BMU!@FS&&T`HiCMjd@0;a$1CE}1Iw_A7&C zU)+SPKn>BYS12!{6nl&`s+i6`BtlxR3)x7LqFA7A@WmI2#Eo22-{yO7R-=9wQcW|n zWSvxnOgv}JOtPWU21VI~k@S_wbxi7lDWsOIiZ#s~nGrK_+zgK8^-F308F)C3)uRkd z(!Z4(Fmw+*Nk6lPEUYKy`P#GYZvBs;g?w6iNbQZ2{AF?IXjUiF6rK}$xZ4cJ(IVXe z;!yVa5^+#%ui#YjhhR$5lC`TwV=kh3aCCVtDeL{fT4+uM zbw(ODG&vn01rgEsNo3^rz+G?4vqqgfg1(Y|qA7LN$(uSyeb0da|5ijd@YJGE3{v_1;oB>ikfrXC5^wBGcKV(v(sS3{dy_IKYP-achr?;q}u zk9IPp2=32Jh4h=^aunYHbym7<6lYdRGB1SoTR(RCdg^ZhLQj4^>Ws{V)P}IpccC6b zv;`edo(;ct=E!b|n?JvM4!Z(u+8#m&DIv@BW4vm{5BD7hR@aN2gfhxtO5LjN9h7TB zq9-?%`D_Q}IJP=dYnxp}8tlEpE6cWoPDuJ@JWvo7avIfP=H_%-di8l96Rw=h#pAqo zmH&Ew%j|#QMhBCXM*UVB*MeG5dLvWL5k;Fhpy%!s4*x0jf=c=5f|`%=cTYgDwJa68 zSGnTl*J)$p>w~k_EBS#D)^;W@8MoJUM*^a)e~A2Cw65$2p1nr1ir01ZGLVBsBT^pC z^eAQ)b+>(k_r6@!mEND>c}fy=MCLZO37U#2f!I3@z5x7&STqgX$vwI z#?HIW;dH6XH;T98J!6lPGbj9kK7$fWhhJh^-6qfvd>Z;4Z--mu37zhvvau1o8c*6c z|Jk#k;6J)5Pt9D^RnQ|`TLmP4u%Eoo{~2+q4K@|-qN{zti?lPB*Tovn{b_Ez7X)QQ zFP~-1ayR5kd4qj^B8O4~9IAOWcY}vpeDLIS0`RA);mTVH??>A9jc%HS`kD006qIkq z6~I|rBNkrYliHX`%B7!Y21wpP6QsfplT$;d4(GlMixnPSZgPw%zf)LRbAD!RMsTFE z**u_3w>gwm7NPoWxH~uI@#?_VkFxyY+jTacx4toQtbeCqxf^bEKL<8%)_Tv3m{_D>T zWzoRi7|$BmY7(dtXiXRvhWu?NV4Z<_;z|0m{-D~HC8px*=X?UC>Q`pkXerV%E7&hy z`ezFNdB@1JYE?NEx~Vd`W7F5eNo;Yoxr#~(V1V6*Y~PF zl^DtIE`c6U01C%2Hab1CoinfWf(7>2kTTLBZpfC*iamDp$arm1l~;jO?ee34F0I=y zj$8D-c{vx$St1F}#6)(VCqHFU33dFt<8Zzovp5BLB`o=Te0GR_xS+!V?+Bf=_q^77 zbMK1I=ET+Z^$M5XL$sRgZ2B-1iM@{beeK=PdAwjML~Q(&h9LsalfWA=E~WXl@5dec z#-TP`4VkM&qJmt}EV-PY$b3ZJ2 zc90{OZ-kBN&qiI2vEimMVkdnGdj}`tTBYZ&YgSZkF6k!C*LY z?ivplTBmN9#q9MlUH@3i9zgb(;uJUPWwu2QR_Q_J!k6nBBzrynU3mTK;M&G`FptJF zD2>PaRr33`p4>}~z(Va|m6uT4sl z`E5eY(=)GwaF9o>pg(@u+HHusk(>YRA7}4brN@WDi1%L3(VnlQcnaD`hB#}o?-Uj< zPq5?U-UB#t{F@Hh$q^P=tMAVl8AuSAv26Wf3z(}m#=Ufkp2{}^rr0uc{q~{SoA-YV ztw!n^^|L2@ZRi~oFmxAMu^VDRvY4cFJ0m1 z{VpKV`!;B^`28TQh6A>Y132o1bS^eAPwC+kQ>7GU(FGRYxIC$!j0~wB8abyrS;Et9 zn$5pCFfjLneVjuZS5~gBrtF{ddFEiXYIp2VmZ6NInac}x)JIBw6b>cXDG&_E@Mt65px$ElI zZ{qv&K2M*~Q(xkrxcvM`U6tby(hyxJHq;C8^R1Ve!G(0|;CRh=`sMuK-vFQPpw^f zreN&jRSzi5;GCUmYCpTB4acE>mSMgS|&_v>D($9QuQ;0 ze4wIOVDF2$Iq%jdbS$1O%j| zTNvFq1-U;Jwb{EXcnHSyW!cXi$M}Ac7YYJtTDQ3W! zDv9}Y2%NjOq8-dDB@15#0BVrQ4DN}S&QE176Z{RO*-x2|R&gJayB$+Kua~?;=ZO zKT-PNa@&Un%ctb3BzuEmzU+0FUje1+rnH{}iHc?zgfAe#XYQV8C^0JhH+c3%f0w=9Bl30kx2?Q==6-0D3y~l`Nw!liEqm-?`|`-Uo|f>gF(b; zL$u%*@UxRO^(MCK{^P^TeYWwvzl;S}XyKQ*i$st4i~O2vP}RA+l#v+^ATOH)h)L5` z1j79!4};w4q3`=o`FQNeAzr!AMcd1}>e-J&nM?`pnN>nFqTfRvY7)3}V@Y}e6$nlU zoMw=@<{MWtZ_8F1RXn?L)#C}j(>t>A`wVSO&FS^93=Thnhpq%sO^|zA(-?D(=1#H% zku9*i3#1#_U9CmD(cCGruz_y`mK+c>aujM6+PSM#BJh}$1p9 zxBAq=Gt7kVw%rIA)BlluII99kr{J?yUkv^dP)j#8kLY{6sbfwqVUkr(&0v;6+n-RH zF1uE5SQH^T;_*>3vueLw7;u|M4q-ZJkpN{=ZW-9G?D=Zk-01{xLsosPp<|+0Mp#eP+KW2@4e*yAtyz}H z@a8ww$Uf0$?{jkLEF~H7&ZquIPGv6)I#Vx&bqW-ktxX}p_)A2fJHs=yWK|q5KQqA; zSoMz&OTJk-g$M(UDYb z>c+DPQW?M#sCG#Qrb%nGaM@TxQqanvob1+V0k& z1)y&P;Yem#HKpn3AQ@LpgVIF#JJph3!bY3dzrqZm{`%f0ZP9ikLY(O%U_rk`$JzT=DnB{`9jmIfvWVj`Ogn8~yE^2m3!=I-@Q?H{s}$q)aVw&cA##V&v zPDrq8<#&ml>EZvD@z_9HB?O+Y`+pkJ5k^&jHp)o-@>y((Y8e=B`10|NZI@NVU6eUH ze?T9&6aEl7*+M-5g91y>oHK!cWTqJacfJ$+CJ7Y6j1PVyBZt$rh`Z%)RS7&8e9P(0 zjw_Y48%`2kZdQd_qAGxIrpqsb9x%*ppme`(dJ+fPOCtUwyUxY|a@M*7NDJrJha*7; zdOetx5}0Xa8_>m?h(X&>sUX{&B)F45-baeEEHU!`bP+of0X2bm*2_uRVCeRqJa{nn z!s&brdN*b{XS2`qn!iqg@eqZjY8&hiwO_hj@VC!ke_>63freZz=YgE{kD3C#^c!Vk zm(yz7wToQrf^44S>HZHIVgyetw*=ojBQWE8k+yR`lH7%71(;WAIMK)H8o2nwiq20a zCI{8qrheVqCK^G|n&6D*9#+L*?g8lN19K1l8G!Kb=?4~|;iA>rC8++OJa*`V#n^NJ zR|@3zepcYHs=neh+K;CClG_ji5_q{Ju;l7mJ4Z9*DCuW2G006iJFGYlRfy;If?B5S z&?u(Qgf&!GM;;?Y*jy}m$y(gq#W=@~A{b$G@MUeStjK~Fbw^hm2=Q;-W?uR{dGtw? z0)ih9UZQnTOK^O!9Z*CO(B=!N{7jFF7Ia3JJYe%N=%}*>2Mue-sDq15d#Fc&QOzne z^o220l;Zi*$DQHwJ=MM3?_B$gSs~lLw_Ym@uffL;%RYZPjGElNEpIYxK`Dv;@Db|| z=*#Z7>w+=W)H#hgW-^8U1K(*SnfSgIOV9>>@nih&Uo3p8&(peSGkPs+{h8h-=y_fUM6+ufDqjn+ zpH1|(1{4?aAd!098kR|?1|Nn3NI(p9-il2hP{J(9omC5L0PBR>gLT#j@vI^l%+Fd{#Ut<^_Uxw~+n4cVG$qUbY{SEjjfx;|624xG z%ko5L(|*|RM?3`$^oz`3Z=Q}zi2HR}lxtX1O1hW zv+@7=%Mh@0rFpE2$v2Z}45$CQ_GT0Od@k{Xq~`%=9tCa2P{l&(*Q!IX|6Y-yDOJ3~ z+3`F>vok6LqsZVsiuvD5qU*lMJ#?4sZhut^fpx0=?=c%&$c397rungY%1%RX{=BuM zl+Kburj;ZP4UIij{ol7vSi{EilbI1kZ-f8+6ka^o45=Z0u2698&Odg9>`x<$@>jwJ z9hNKv55&ti3r2_GG@>$xjlH+x7G8{$M2{VF^!2QaexCb}LH9v020iYUX?z;75Gu1O zdk_2eROoTt-Q6jY2f#1CHXKdaQ>Mr%ZVCQps*eefkxn9znQx05{iN4f)_ZCpd@Iq!p41+X0j0UY&Nm z@R{l)X*mK7^Zd^RodfbsjSOP`^57q^EcTVvEDBBfvWpGhOTyW#mS;c5;hD6ayFXSe zaty!rT}>M>@Ev8Gin9}C72CAx_~1{^ zd+*Hk+hISjO*JjPy#^(d_+V3dGQFSTbdATNlA&R?P~CO>orbjBd&;+74cKup9c9G6 zGFRVaI!E8N8#?W;=)@XP>K9d&S6}f)UtmFNVSWKi?HV z&R!8Z)*w>4YFI#)LL5Gp5m}dhbHr7`fi-_HX}MEWnv+`*esb3GxSN)BD+%8H!}U4+ z+~w={Y0H%E&Z|{V#{*V+^=$NjpKzIrldRn;7pd9>=BYj}zGXJDdVfwG!`oj_mS27( zUX9Z{S%k5(3@2w+u5JL z;0HK!){MbGyZPdc^bN1KM2F21&-VE?rxic(-nLZ{qUfyJNNcihZ+8mJr_KH;xwLTtXREs5u=@THL9K&1RVQ^ zje-w>jjGr>s<8QXdSZTbe?3P2nI0xgo%Uk*XO{(1?AQT|Wa*qv@vtI_;eO3C+(O&J z=JJ%)NgHa782qQYW;fS+p;>JmmML@TCM3S69-Mll7Ks_@b2rSVhc~cM&LfmAaD>~l zr&m_i1Qrx_;z3Pg^HQ=`K5Jy1$+mh=$RNuvJe_4Jmr?{JP+w`Yw=xjpod&wfwYX^L z_&*-*@uPMubP%#4M_ip)RYL}8B$b?~*_x`iU!fTIV7Ev<6O|T6o2YF5QiM8T(_gQK z>vZB*IUbW0sGm!FTTbx){Al?pSq5)cIT>F>N<(^87cN48F-~wJSG|0TC-vI29+H2@ zfcaM)rxi!$dSbzE1xzB`*ZBnm!@}Smb0eNSXJTj6sxD4UwsKmBu*@@>?Y~(C_y@jL0`Z|x4Uwl^ zW9Pxity^S_`f(mWBN?627NFjCU#iDOL_uTwYJkl9(=SvG(EO_o4q6?h^`UeZ4^4V# zLh+kC`R)M``|oKc63X&FCR>-#i=G7bHqJCH+>&AIUWNJ^8$xL#KV0fWkCKx357Nk5 z%Ub2L1rtkh;r&uXS?S9l0L@M+-MF>!B+ili)A1})uTjhd*EnkFm`9t zIrZw(&(~oo!hm(R0G)bVLuTUgH{as^j_r+KT%Av|wL{;H5~5*YZ7w*9uuTvk7>@4z zx>=7r{Vmx(8h?CbgR58f2niWlX(#pa&Hv>De*!vO*tG4ibA=W`?MFXznwXIJiiR@9 zgV--{q0@sHk1TVrd5scRe+gl7>%+KOoZHeo<3r}qE}bhxnW|?e62QodV5PI&^z*Id zCDBF%t1dw<7rrrcI`nz2PN}RV!w?FE=dK_wS>~IFtY9T+-jXEA{^)(9>pHj96&NpW z=T7W&eQ`7$%?61Km65;^CF&?=fUmj*XT5JJ!O9 zw>7@F6kb?K{wa4gG^H`dfr+_uqIYCt#OWxpw|H2#!SnEofiAMB`w6bb-NDP|-W{lR z_@$rRY^)GeJ=th<=Lr1^jq4^=PkB?$h+zAi?aLr|YyR#VA{ZB*MM8!<%HtQJ$E1C{I{v1^5Ft~;Jo?+Pw1l|S?A z&M9H@jdAm`>VaEU?CtD4f-d~GtF!jAx8Sk_eXzh~ZPyXqbmWAl2xleKY0j_Pf{_d{ z5|u*FL|4bp2V2e&3vGmMHc!<^7DMEjx8evys?yLsSy4&w=oDtu$`xD(isn)o=HSn` z_z1xc%0F_-YfoV$Dd3W2~oBfCT_M;_eVXUIrFUY?uos+Dsy zg%Fz_7S4dHC#Cc<^I{g_XeIUd%^L}?6%t5h6c=aXq0?&F2Op(qH6b=1P#L<0^duJ+ zHe6xUNv{6nw3?{C-z7KTFCQQ*Pkf2He;pTmL(-JGdvtbACuoMYHmxt=KK=%??U)3Q5N`8P zkH1`_=3Gi4v-1Qc)5SgNZfQO;$ednrQPO9L0hsi!b@rPMDK@KvKB+tcExxYT;zMI>;UD=fgepE}}ay@K* zRBZEY@1*b;eqD{u?%m)O=`8uYB7CetI9Du}D|n~X>@$E_@=@HR=S}4QI&C=e9iwZ+ zUM>~_-=b!^Pv_9r2caTA%;zh%>~% zt4H#yS{=7ae1@WeEC1i-R@37}rL(`eS;uzzY>qpWo&+?a!&f1X?yXfm5sw5t#@72nOxZ&VOk#<_~$PkU+7Qrj8 zKOObWq>^5_~`g;>AQDcn~x{<6I9&lb$pgxJl?xL*#Vi@xa;8A zUvBtFqLWX3hKjFq7F5W#+w8X0_rpzRZfHiG^tCS|`-K3;xlhG+)hq$r+vH|L-27(| zaATIHOV@9Y|CM@DKcEHknd(jya?!fT$;;c_P>A%_c`o5p7ssljZoJg~^;XUf&r2l)oOO0sko?MCdmk@#jhTnEGQd-KHsLT_`oZr7-SZ0Yb(oZKG*kSqg{Fr zo$Vb^c}(GL)xDND#=zUZ3m=R&=*^ziX{r2J!ehlthZWm=tC}QqwBS$bejO;}HEn-E zDG{WS;G4kZCLHLdfZjil6^yQ&fFE#%VE;bCS0hl{gu?1`*qCQVR1I}A;t|?Ys-9I8 z0pD*bG04D+EU}2q8M4~%8~DW|r=r=BgZc&BeKNBAZUyGW-7zKqUMa%Ua0LqA6~-HC z<56c9<@k89sW8r_`PA9_WRa@X20z-5`=5RN3cwQ#=+j*4=)*F|t)@WnpNjtR$dNEp zxYae9?|9iSTeTEKc^VL8uY#n;rnIEG$9#$e=2hIF4(l+2_xF=&?(RC%V_MEhlWjMM z8BN5jn~x9^EtCN;{?G)@s#_8=7CZ?O4%i5!(!=1K?X`L2XOyaC4{Tb<$k*%v9e4{Q)R zt>O4*Ln?0sSHPN01;_jk>GJ@M@|^s#nDNf$QAPn3Zl0LqJGrv!&SjF{ZrkKaWA8LV zmF(%OYm-(9$YeB4NH=!8#++Til6)tdP9NPb^ekO+7CHmj&^45+tkH?g=%!|Ox`yxOZG{$t3SXwY6>haeGFb|V7R3E)jNPW9M807x<$+*6zjOCnS z6Opwg1C!whI#$*7m}XfHivMvd|ItZq+z~042WGB0i{#4ATa$K6*>P88s2h_b3eF4v zABVz;UBf@O*=Szr2l~Ci;8)-&$Lj{wmjpQSAa680gB0j`$h8T#b#%rT$jYFW0qn`7p4QF8^=&{XsIZCSe{YQJ1sszXwiwM)cP#Zfo0}#E20&XYgia zB8;n6cVdeFpLiS6tkggJ2C>}l1kwC-6uVMoZPXB4okH03%@Y;2LJ^(hf53ijao zRJ4<}WqB_JzVpTu67LN*X3y;r?$e^Rd!M)1*gg`}p$6Z>!Dju!BcoS{Li#{#ymXls zmdZIHIjL6HPz7&<@7VHq2Bg7W`L5)GrQfAD>0#eu{cSTK-Z7JNHseWxZ|JGDZ zZ_23X(FR9{_hj=FGk5TU*0MX_ho7)cJ=y6dFd6aa=opmo)*&~i&FC=Hv3<_4apAgh zdKNFDdhIXco5wxUc=x!QIBFjDso0rQq3dd0v5xIq03cbY(*UxK2Mtw*_ z#yrLGx5>&GiNoKfGbJmh?AOZ)wC>O5eS#O+aGnz)`6qX0^U~XvO_1AXXuk!4ZLsb1 zQiF+sPR|nwZg4FG%V2XDT3Mom~r`@!Ej@7kI=@}1iHg zub-q=V5Wu-kE>|D6;&%+yiN@tVfQ>>-(0+~E^Qy~lB+O3B=?ihl!$bwn<&oh8hTF) zQ__Ma1?cG@&vtxzu{#_Ay35LL*w^4K(KQUfi zt)qF}_W9QD771Kq2<+9&Egk}5u4L_QY_g38QM8^bn^$HVd1Os|z@h=$&Pm!e=>-j_ zc!A-5Y8UmfO`6IgUS{v&NjkvYarsOuY8I3GTSKw5`4?)vNNry*>@s2ZVq{f=N;XnBfowCuSu*cjDfW(0GK!O2_I)N-r1h;85Vhv-}my6%9MjB z?x@yjYTJ?KTgm|A)%@v$(GU!-&7(!@18@ApIeazOH>stxC*idlb@@o$zp-)(=+{>nCvB`4*>wIOx+ zs3*#|N6FV#^Ne*9`W?-G_}Ypwnsj>^g@{79-Dt3F?~>cd2OzU4&a;u@dz<6lrMDoa z?{J2_!(qM)Q!q#Ei8Ag_Y>-~c0#-3$h8AKPU97n%xsY!5%ak4D@)4PJT64)DEyLh{ zjc|H!RUY{jsi%GKhXAm-N(aM)=Ak+g%sE>;uJ=-iPl<$nSAYq^1o$!?IhpX>@`4Uq zK7{2e>CNT4n4x}ww$Z1hKfWuxZPb8E;KZR4+1x~M&F&PzFx){ZTs$)H$nb2z472l1 z20DK^zL^~L)cYNhEBM-Y_*+)Aw4`;|%&V}!YtshiU_KJ4iCGV$LY_uMA!XCT>4yq* z2Mo`cZJGO!B9?X-Tsu}i(-yD}$XpRAXwog(^w{qKlxF^!7alH0a992wD#-b$%oum0 zG(yhLX+5<^vYRdrtt$Q8g2tI^xXmm6K#f^cEvz$?_)o&~K4XhZR383b z*VkCQP^g=75wu*LVa&BOJ;9aKZPzIz=ibqLoP0@oMV$!&>H5+&_j(b&?aU9=E;s!- zzIU#HT(M+={eLVS>)Cfk$-)miv3;hxzPWT0i&_!L04|c52ZckRhMk01)H(tOfR#dw z-GFO<-1`zS`=hNrKivf?s`(PEty(5|V+GcEBjEd`!xXE3@veC4u0KM}(yL#+f&Xl` z9(o9!)}H?kaGZCa`?6;qf4IcXZQt(WcUuemCGT`_;=FfeiPWw4(IkqO9GpW#!t>e& z9%xBR5XjWC7365j{-*`#Rh%+)1mG}91u?~3$C?xC!MAV5+1xWXi#jyKi%@HB?Xa;A z0AOk;F`-BxxzTq#NpU@wZ1$132}q>TbKU{rn&r$>sBvBxIillg)j-(xrDPgvDA7=Q z+ToLkDEGet!sKW57*iaRDmtkC23_>TY(x=0J0KSHim)ev`f*GvFNTDkzdWhaPLR&0 zH7t42BHzUyenu&wpd*eUu46;`Zh*50VJM{h zx2PHv)ZND<4#v=eQdQYEhvbA3fREueTivP@{h%GB;_A-BpO(&2-!-VUZvRKs<0d;H zm%{8kQBl(=La#||uA-lRVasDA=zHS;$!2jihF+;v?Qr2o{kKng5;KOSS<8nxx1BXS zoM3g`WS%XV{WvIyPb0$26XKt@3xA@F`#G(zswkzGKT-ca=ji0aV!`zLi=R$|jT^#+ zX1CN{f;lc$z45_pPRRm;4kjKxuj#};VB-@%(F1`uh%~lr|Cg-{1WI?;Bz~>Kax|+) z{PC{!s@u%_bX1A&s()?Lv`}tCYDDh?PJb)yz;}a!BMLLv6z0gr%7#M!TxMd;<{^Ie z+$za1vZ2E%b4Q_OksSvbxxqSZEOd9BGGwU%kYF6YZ9D^Cws~TcqdP!ZZp5kcP*o1? zrTc%=&M9mWw;d3%3T>Tx{4P}*8u>H`jXU>d9W`%e8T^9z^J}VMw!6DW?$IuvzO~As z;U9p@DW4l0M5!d`qe4bo+T{K}U-2nzEga&rS4;G+HX>6ldHK)E1sFW5I6?i30a>LB zgPbSKMX9B4)j()xlKYX?hW2m16dXD|=ixHCnZ73SfC$;Q%5Vt?=Bywez(y6JtR2%)10AoO%y_peSetTZ-Fuq zkMrml24u3B>GUn#gB6*Z4t30d$iv)aUQO+OG@2KjzU{IUQ{_ z(Mp_Sq!5t0OlrK#tX{TF`OY`+{i@OJ{oPL|SAv({Wq;Gz$-1t6B}7kg>>lDaTgb;E z!~}&zZV_n3Y(P?(6TE;hxQf51X(s>9EV;M+V81SS45u+61Y4$VT zGjB2_13!?C@e73%)auRIGWMAOhK1YATfF1t$jK5^Gf|f5*N+<{_zze1FZP%UFaJJ% zbIy_;Do@)fLN#Y4ZWyLtSqSLIg4J3ztvmM}QnvsxMe!D$P13PFXKwf!LD~A`)}is} z4!zQ{#JkEp4cD3P;#1&M12P43gyXCG)m2>0CL^t$Z~PP-ROEBk{fJK^$<1-dig;Aw zKgiA}1^(Gw&GhP95`V$r7!(kiz@Vm8HkokIw-J$9n{YzywQXx>)LUS{3h+VWf+6$C zgxT9x^7I!RKEgJM-91&)uOr7>tjVsJet3tb^2Yxe3xjIlPK+`|L;3uifdg>Wq{Ku< z@~MY+IObFcpj3Xy(6Fxi+PP0CsP$bgOF->*TAJc^CDPD;o#i6i&yRrdr__4Fe^eI_ zN|go74&OG452mXY=0IE0*{RAuY=BdVmxC7L6X#gO*g9Moy4!|$;+`x@4K=_`yx~Dt!EqCJ#K8l zUjEK*-q_^blFAM88W|BIKylRFT*lvh@W$-yQFKm)F9=YCj$S!%8~ggPLNGw?A`Nq1 z7k%{nIo=mLE!_SB{^XGfasWwQLggwCClohGaRX`^+po-xkVw<(F@`ueKO8vjDw3o8 z`9(YeAWw5Z&`VFQ&wi$jI&>rIy#sY*Mu$+3bKIvfldo=Y30E8KgYggy0)S<~GDiMP zj7oxGX6?UwjdUZ_`GUkh=~pJV(z(^+poukM>Eh+ud1!(r3Om&ErrjrsR96)54c;sw}8&O$G2`bLkBRFMi(ZptdEgU@UIPM-=PUcB6;5@Uy$2&WXHC zZuc*#>y?=P0mFN~4qjt5?eXDmeGBBhqxz~*VuiUNwc(oWvETQ7G1euYHnPnp7e*pt zBPyZ~^uRJJHoL>=3F6<=B&dU4D!e&Z5d&0ME`?r9pknFUl@&2JFQ(T8ViwT&sr;>e z(M;GKfPY^3xB<|HKXPsbP0d^@SmyE!-9B~Cm*TneWS@qp^fjVx^?QkToki2Dtzg0B7L>r3=(g~-l-WlF~a6vcCJY!$lA zA2bcMp#SKIK3gg#3L^3hSooGgW3;Fo@ce=$4Q1#nd0 zZ()vu@hwcVd#-o9C%<6+W2$pUcg$*#BM%4ae}iYd4sRn`-=0JyIcz)&fALZ>kS#wh zCFz7JIhqH80Z5ec4&pR`@H?&g-2*XC<)X<38dgUn!7xlP+dUp%goQZQKA4))TGD7?b)_gP{R(29PQe!pD&Dc zt6%zOC)J_#Kcsp7si*MH9AZ3kb1Z}AlH<~_%Z-R%A&pD(89%S>t8b_bzz%45xj+56 zGWC8G*tcAlZxC?Sh%MmsLjUel0eP>Gyq4c<BAP?ni%tc&`#~t{PiyplvfZrDblOy&Uh9N69izFP(PceH9c&L)clO{yd<28#+L+? z@PLgg5^#zG@`IONi5&xJ%Bq+fzfdRW=ipr0iE#i(?C4R<9yZMi%j6#UfGG^Qj1e$l zV6}#?;IfY1%p@st-OBiFS$PV1ZF64iRox5@kbAc?maqQ(sUi@(Mi_2T6$g+&m3q#1 z89oV39CYCSdKbPr1NgiiFBZa6@8<3A`7jN~qba%vte8cq@dV%4oY(b@V}r2icT2fY zqh5jN>Vv4l<6`p0oU>o*H^bD-U&cmKx-AHarM^RKSRwR6rrJVu@eVuW_h0O!zfPF! z4u7b*&tFCacx^nu8iI4knNmF0a3IFEk6|u}k9%pq>xMM{+1U%}7(fBYrj*Twhe(#2 zp%T5bQ|9Wan910CnTQKa45PjlCB8xQik)3vJ6P45l@*zVA9%R1+tj2QaeiWJl{#)Y zN;7Qa@5a_u)fkPo_+}YK?|bNY7mNiWE>h7ZGho1<9Yb-(yCTMIwJ6{Z4+a=L4!3TI z#-hSdaqhbJvG9XbL|yizb$Jk!t1veoLy{s*(|xH~H63SUo-~pA-^sM0#Hhzmt&@qfjCj8+03foe30(tT+b3; zfk`AYa5|qsJ-E*?dxX}9wSt=uccnu2hp$r+GkPaPsBWN{8dyZ|>cHr+MHTfN}x5 z9IwZMxSE52<9M7noovn4CsbfG>3QcR$UHxL%0>^q81s0LU=?gd@a5*NNwz|s^^hAo z-30-y8XOmtQ!CrV9|kD_nUTNS^$js&{*^M~J7ygz=Z1#3i)e-Pn{6(@MqzAwZ>5a_ zxm?q{js;_%lXg9jGhwzIznV>3&L8H4I0k{!{f;-jsWt^uQzNBs{`iU1@+}9Sfsd$^ zUgAVel3K?8Ut2nT&p-(_q8P_X1jW-E4CJsc)6(ggXkUJKgnoun@_0e%qHx~!opwz5?IaCzEHavv|e+&yr3 zycpyTW@%cc`TdV{laS7@Iq34#5P-F^UVtI9U&1CEaFQ5*9SyG0iwo(T`-B~77kH!QqJ!3rUXHX)%i2CdD0vm=1r=u zb!$g$NB1&L?*v@w*`kuvQ13AQ~eEb%kR5@ttDr!DV+^Hg{rD~ zHFL7qdhN?iahN`$P=1M zQO&T7M(H``ne+UKD1N)%Ewv0m(?LsR=if$ry;{!h$hQblhiPNANz(J?^>-tdv+0$> z+3eJb3$U!w#ku{HMt~uYXUhVWOM7M|X%g(fV(kn}m^~X+9R7Gqj#ddeBhplKLW_nq z99_A7t7K#_xBM138YOuJ!di?Yv3iDdF8_f0!A#1RBga)S!O5;c)}=MTpatj|@V_dY zC}NnYd+TY#upgDV=wyJmf74+^3M3@u1W&Xc>SLUHpG3eOk!)H&Iyys7s295a)oY4H z{$+u`u25+-#x9*gJ9q~ zUTa42??wykmga})+Xa{06ZMh!uDhaDXon&GD-)3jD>ZhuN+L_asAxp!vheT9Cq8<{ z;VE68fN#Gan4BTG^T@<~9{`{s8fF-W#7UfCAUBeOH!FNIX!jJJO#^_V%KMpLByaB@ z0rhuzr)!-WuBuxP#F3B6BiwZ4otvvPCN4^f zs5twX`^Gv&*z1=mp=ORIi^0Mb%@6*4oO_O3QXw-?*XSk_@c40pThu*DwT$M99GY2v zvNd+Wh4OWcQGlE|?_-^B0x<9s(b9MPbsTtww#49Fp#E%5EbO)rjCFx(5B!7Mp5pRZ zfuE41ww8j0Z{?+{S|y#{O8~l9v8icRH_sQq6SsDZQUTxr&`|@`IW#=Vg7C#C30Dmd zfBElX8&~>FN}Go8h)aDcxB1@nL+6i`8t(3YjIe*MlV#rap9AIb4T5>=9SbPc?q0RR zQ3ZYAba{CXC>3xr7A?IIUyBdR2_1*$){a zVL(%P7dx<3Gq^RMrOSy#>Cnf&6H>^3$_ft?4akluEVYtzGbI0!%Qrj8$uTh2E|QaK&fQDtEc%!cI0Q_D%$_bbN>8TZGzZfgm?@ogrkF&PLs!VjSa#SsK)u00yFU5 zW#ue$zjg;$3eLXoh47aIJ>ds^LMi**i?MN6&@9q?KDm{(+J#c7)l%q!tx#H*6I!OU zRFwj!*Gc6)>2Zn+<@p<)mhS^P58`a{TIvHfKeAMB8KPLaa^+_bE65E9)rA%FoyghYu_?7_^EcJs>LV?u7C95w$JFA?dBo=* za`=vck*q3*cm#UR*TKnNo-aPU5W}SCFl`uT6et!+Llqr#o~QSHI6mF>^G!YF`_<+j z?0iFd#C06%fP&bu{sx{tjMHY1=UYHu_E&&=9!74ZM7}l1GaS@cl^5pEmUX`K`o3m% zS=l{xFk0Wu{YM9Qp=+I{OeI#!1%}O3J;NsdZfRdtRa-$>b59egiB}FCF6{pIX7hw4 z7s>q}vk=U;tehh;e(tzNCXv%+KsDx#qNtWghb6Ov4+PC~dC|2}avqdY%YQi#*n6pcW1O#N&_HZh^S(WM;vHxOD)#eA z=z6(#go-Kw!kUjQ?Mq7m8}X;s>7Q}QUSZ=`P;Py>%$ErU(r|!cUph>Vt`gw)m1Qj{ zfQkyp!KMB7p7dkFXg+LG0ht$oyYklmVDj%pqVt2!t(1mi9`5r89VOA1;p_toYPWeh z&h-o`pbi3GwqzbEq(9ez_A>+Y0f67td`kl&F{s29Af}RMPZ!O?dkr`~ETMJ8)2{B2 zhXTn1P-W$B%>oD#sOO1C&7(5;qd5%jHFtIPpPl(U3PdFm(jb~dh}%A3Olq`I^toe> zd{W4|{R|wVcHhcYVtHEq8ypNzBtC;yEK!%=FG^~o{s8jqXuI0|FA5Mr7dDoOqN&^V zboLSi1u82!Hl~9*wGZygfdIT@6bRa=!~$gHvNE8Y0_F7T&YqH?Gl@cXqw5}FgfNfo zlB1%}u`qvKctK9B_x;rfgv0L%2vb13t`cjc>p}!{U@is98ZCt3cYET0j;P%bVothk zbLk@yD+wao>Zl6;X($CrB&$2&px{40|@zJu{>w(?Ge32A|^I?Cs82OaUu?P4UK-OW|KKlEPJ*m_emFe@N7iCIONIZPmfD)gwsNiOT`oS%LTf9}@t zO4jcC8iTid4(GBU;#wKC{dZNx9r7NYwa1i_m&&QXaVoqb&}DXpXkKYkqiTWh^VPfi zwW37gOed&I6WhGPfcjPeG?|tBrXBW$$13op=nm%%`1a??+1WQ{ARM0p!Q?4A*jn%f z&>17Pve6llb>po}{@@|2)-x6I+D_>vNo}A>1hHmx1eE~p1u89RG*k{{plC!#Q({r{ zJ10Nme+?89lUkpLxp5#u!7ncbE%7cIM6RUXaBOXQ@m(CY8IA505A|hp({TW!-FC zRW;80BR{P9bAZS3R!X#voqjl1m(sLt)8vp<&PRv$qXTDKq0c$AzoWY}~ypJx;} zT$x>041Q9x5H}Uq8SZ^WQMFfJ4tzWEpD_N;G6_gzCClU-tJrdy?AmF0yz^-zIx3y@ z8R*pmZ4TR$i)yBj?n&UzYF~7Ogv%uBD*-!xsA}ir`I-gxO7lmRTTsb)QUI3#;seR` zmTK33`{nwmg+L&;U}j>i_{{}*P*s=Upyb~!A-2D|`b!Ok* z_j^;RGBKqlWm|c)4p?#HlMSBZ+qI~->!*u;dh{Si$28RnAAhRyPXqNsk^1JENs>uj z+tyc~&O_3`EB+<`CwB;_1IPfz9d*BT7*|ySHf3Dgzn+GdIjpfk$buvKiAng+rdm4C zoJ)$F5tY-T^Z%Io>ZmH4FI*4=5drB25d;J&=~j^Lkd&4#={~5mlytY0bROxDknZm8 zZVm_Tpx^J_`v+^emg~Ilyfb_De)hAUJ@az9ZY*au@1?^%%M2nmQb-1ryl)5CDf@k6 z1rPy8WmoI|dHDe@7e9E4aA@pKdM6>O?HWSPmSrsy#Hgp zGJ8D$)SptIej4gDeME$*klLE5C$t~gz+tdtp(UG^UsWGCe*7HFne#()m&#C#D(gy? z%-sH!(RG*Xi3sPmjrlNKAa>(F-6kva_(Qy}zay>d^kO^AS>6^7dilbD;2hxbQU><5 zZf$y*1sM+~&8FYH7qmb_1N3y?sBMM9Yb)kD#vh6waa_!Vlk>f4@7HL)befPJx&<1) zVbxh&a^)OF*1bf2P!V&Ym+ZlR3_1w#Bo@4bfNBM_xwYrd?B+0prc}jWxd?omFmeTs zhx&}Ai5ucF$MEgk%cx2i%qnQ_Y1`k}cW{;SUnc&R$eJ<*ogE0eYy9>$H~ZEq2amjY zo2>D+x8@A;JTmuG)6~VIV(fW#w^6>DJWGFIN(~W~tjnqA*KnVzLYLwt#00uJJ9eGi|JP zmSj!j0g_QA%IV{qCF}>8hWAZQpf^jRy-YPx`WS>~vEsvKmRFH~g4iW%Hdj#-CE54m*p+kATLG({*jzT{9Gmi$ zkEjo9fS2pbds*>v_Jnh6N@A<9L_BxDgPS7~l+bCIbtFJ*}2w#U^d+cg`_5ZeBd+9UI~Pj*I@g|2dlq%`ZlZc zwfngtaQmc(4G%gfp_};***B*VD|=XvpVRezKQ;-Vp;;lt5lTOCbA^@POt3@(b_#q? z#`~8?7@_a19|v=Ocqf)BVJ`3-arngRidNpJm{atg(K<9(k?H|9Ymhc#UhzL?qA+@+rw4!aQ* zXjclgmHai=QvSkcXrrS~SNczm-Ri;R*F+Twpq*b_T9E*K&mEv&Kfb1CD{qItaP(@w zi5U+wK`>i*0Yl{fi3LzFfW{B5iL(0o*ZKp3tiFHaUP!0qPXS#pX3R{i{t;>g-#uLK z^}w#fH1{og?$ZLfCt)DpX$B61ULGDcUjATIXYY?-!yyJ59q8N#Sc{0$nE7Y52~aEI z`pDmR9ndh%=tCPz%T_dDt2@b8>(l_U0V2j%{mrwbmnu@aZZCimZd$N^WFQT{$twu> zq{hjuEGS`fAtJEMFapyfpZ96e}~>HE3|xaC5x<$Ll|(eBlIoG?L8 z_#-(a|C4``F5dyj%JjY%FgHrBRJBx{1NJMO!kUz z1;}{Zuy|W_taPGEeg!G1Y$*TQumq}8r7F&qtrdyMUs54@E=*vU%7!@k4L3+Ao~eND zsJPvN0xFdkbeVvnyC@Jhfn?&o{sek{y5Q$VxlXc8`|^e+uhR7g)H&8S2Iq$S6%-#~ zujEDmkqMC2t0ZrOd)0^ps|n!?)T1Q=VqGlI?u^lRNl`#ls)y?-Xukk8q5`3i@H)T?uoco^L=oKkjx**!2p- z^9{?dLygm2{*XIQ?mus;?J!bV=EFoXnt%UBs`&u^@rF5$I;yMi7UBQnm>NETVye_} zh`*(6B>Ub2!mPQ7*#jYJSOxTNPoTV51|?1_7cG%|WBr8%$S^=hgyEIXKpOPq|8L`r z|26RN!RPd03+6^IT%n@9r`W!o+zQKV%mjWSR8x=eA3uDZ(A%y1NYs?>=B>MA-79fn z+jMQDviq|^W@CZ0<)!b?r~K z{|zM`8wJpYQFhZlMt%^IIy^J)^)=YmdwBIF9NDv?t4JW*`%}-wz1GCDpZ@RREzMqA zHnuNT;7tgNOFI%btz*AupMTLm1TM|_#0Cu+A%LPL>ZU-QxRdi>@6E}K%ixxyS(R4T zr<{qMq9>LOog)9YuM~?IbcXGuvH5SsqOoZ$nmn3`U-VQ4je4Lx67S${d>>*ggG+a7 zd8``SRjAyLhWs@ni;2pA??5OBG+AqUk=Dwjz0uZq5o+AKLUN*Enz!dGOpj#_jyTx> z&kT#Q>ttEG8<_VBfNG|Ak=c{EfT=07wO_Eqa4dPxkcR4kTz9dp>xZ@$bJF@favC24|daZI@ zkM@I~MEvetla-_hM;zTQ_80>*tD1gd&2|aNpUv_1z3FD4@6+Ypsq;T_`hHY;)^E;Q z!sFkFnExFbmB6MXQNyM=`b_v&=~gp4f`~@8Bbi5AQO&->H+~I0dhJ$bKUOz|q{H?t zYcizU2Op}l->6T(k5OOxep(`vhQ*BQ*Q_4I_kUHqu7C7{A6J|-M9mN;NyOAP)h+xw z@p~gu9#eNaKWjW>#gujV4icc{q3cY1HMJoboPgftz9}*%>kc>$VuFVL0Ju3Whl{$w>IrcsoBtxui>@#wuK#`wc<1HSj^m2Sx}^pqL# zK{(u3kng+AL15<@Ozy*7{o|-EZ1Dn`5CZVLHeSn4xcB1Z7$KsXDrI52=beYN7?BHx z+&{DE=Po+K=h~F}a8~JOE%Cm)ck?4!*8MWsg_Zjahi747d@Z+#Q=Fb#jTyn0zSw*- zt!bG>0jAXsj&AXUmJxj1>??!6ljV*uBfE8_|8NY|vP|Gmw;+o`VPjL`zt^DbC6E_c zayX2!tNnZaP?lX+j%{`d6w zy|9E~wZ(?2_yccs6Uo0jF7PC^u2DoME5T1;s@rj<)$HFlzYA9)r|h|)6!^j{K=j@m zV?3C$Jy~gTyDervwSL$L$=tMGAu%hWhH9*rb0D9UsHm+&nurNZe_*OP49@*&RH+f> zrApDZc)!B7fHBP4$O`K+Q(;~S%~+P|0Z z9B>?1%{jHU=7?7BJbzw9gDG6mNq29BcD3(2JMAr6919VQIW-!mr!&(^>tGljAD_*A zIH5a)3m3$*cULDbbL)w0H?q4}-S6tBx!Rv>u;b=0c_7B`pJIn>&va&}2ljB`$o{uf zL0M5wH~N>S28T!G%DOqrbH-|?*X&-7|9-5{Ekns>e+A7$u+jOv&7$u9fxVXh8e<^% zO)W1=XpHxQseyC(!{GMrjWi9ZIl7e6`=4#PCAMx#t75mrH@f5Pq-(@k|1$va%_+H> zLKRU(_1-1Kwk(VfuW)zUoi4nHGka zZ&O`r^Nn@blq`&XSBI&-!+%F;psafxh|LH=P$eo!@mmYp`7kXJc9NTQa9IEN2RVj9 zXIH08JHPj9RwLgoAr%r#N&3HrNYP9nE^L6FMg4Wq%UyW-*4K$u2*FsDM|Qpl(0~PJebL*mM^r1&cC1JHy9w+X zKM}s;zeXFg;H-#zb+Z$O;<=k~3hR0GZ)TU1V~>X=p6DGR<*9C#r1~Fi?ZgPRu6y}m z&du3QQX`q8D!i>c>6*~)OLF04_3}jve)w;G?fNY!#3#HwS)T0sh-cZE-~IcVM^ydl zMQ6Ac93&hh7~qBV5DzaEdbt0;Y31RT^EBuEtzKB(7t-iX;-6dWU=F)c_VNRM(AlgY zB7ey2@NE3BBjxPJ6E?%DW^kS7e_#572Rcb09;a@AhV{ewsv*z+)CpWZLm9tOT=o*N z>fQtUZU)WD>6*_|?U->7$d;F$j`P)5{gM3}N0Xyy8!MBs8NCtkHwopkj=C&C2mp8} z9JsCk3Q9?SVYj~DW*g!;Y6a~B5L6e*rRTy<>jQ*W@!Cm8I5O7 zly}5q--W;vvLmJhqGP9q{x0yc=iu})q(7S>=>pfHO3Q>++jVbT%d@@^Z@ufkf$-lC zWC;vJ^_=yZB#!m3hvv1*?nD_b;^Y9)j~qj`^JTLF7{eI5T_ASwST`Xf(|>DKND!^v z`;|1UGG+@C{m1@_htkaN&2@~vagzA|yIsH3Rb?fi4)y11cB$z$WMhI|xR4Zi^DS0Ri0j2T)jESO9Z9goop-fru~2Hl9~GlPW~|3=x??M_fM7;dMso9 zJb67mE1#{Fb#&fBM<4yY5gsHZPX#%Cn8%rjIj1*WTf%cWrG1|A^Vj8+H||sHu>?^U zw-iUQ*2-X_JD3OJWbG;2W32rLi+8tYoLaVN%m;T?Y7sFV{-y*--+AsBB8zWUi1}mF zQt!~$wj>lS8-5v1K~ts!PIfG((^8+iuOdTdV$%4;J8~%L`Q3=ToL;CEJX}auY`vz# z;kF}S%c9FtOCvN|maj{nHDqqwLc8hyS?d2#@qjkO8S_U<9Fv`6BVulL#LK@ft!?Vh zkox8(o#QD!pS($h<`yGIQXf&Mcy`IB6-4}tdh);)fN2)}`8h7HQ5c>#ym> z0xl3(_Y@Rx1?@cpE|v-}Cn3id*4fWjh@7&;;xXJg<{OdUozogFep;r}d(-wgL#H-; zWY225(>$ET_70yQDkAVLbv(RT#&nkl)BB97)B6$&p@PygA@Iv@ur|pB$*|d+*)!y_ zEM+aLtb*caE)xS!5-xTKCahr-xOhKoP#2H0^_JImyxp;WA-TH~*i|Q__lEd)6j&Qn z#>K_WG>RyU;gQ!?lqk+vd+^ERt1@I~Y}LnBwj0`QJ#mTaLA`8Aa^e02Q}%XF9Xwv6 zGkGI-%(L~5P{fg?p#7^<$?qQ%N_tw7Ew|P`Y%dedgVVqPJN0w0uKC7(^IWxZXz|`? zI1UxxZ>!f4*IlF^ZLTxgoiOlUaHvLqZ*pPB_7P{@8kgWZohD$KYTcx>HgI%(cGh#S zrARa<;2Zldx{D+$B+oRZqV)1z5ciW9FUM$a|Jo=*czp8S^U^;-cs@TPAiRpeG_bh# zs~n5q;PVj7z)iWRQSWheTtoOht z$h8div({F}GxHBTelTb;Cyb8>Dea|@KH$q(>rr@xaPN}%3hzQ5 zy24Ov8~P2oGD`A)2*Bwv*4h!ki%Y$xqI$1)g7dVmLhRLyZ5NR)kpqS_RHW9&IiEC~ zCd&7Tzt10QqfxLryZ+Y8L1GC`wm1;ROH0>>&iI|~EiAzZHge#MdUTd276_vxOmJnB z5k@xRDsh?j@_Y$N>PDkuDyb0PJ{IA&Le$vBJ<3o1;n%Zxz(3w3rM5u}@4Yle%A+g= z^(*qfageItWFK5+zo9riRj5R(TT>P0vGDi@E9EU5oFuW$QGBS}rxBBp3ZB2P39L1TUg=~-H!|;|p9TzL0Y@{EHK(dL(;$_7$QY6x3#^B#s zV&g)zXQSCPGNkURi#$9yI_Y?`V-s8HJ8ary?(I%e&>k{AaUe_i?J0Sz>rjj}S9OsZ zBl!Lb?{?)v0(5-h&aRodgkRB9*89R9uyBMt4lVx=r=*zh3vx&NY&`OL(ZT@z8cvI< z#C#XwsqM4=HK>HH9g>o$gwv(ay8)#{XAQJ0PN7EGrVGkGtm!|xo> zvS4z_?SVZdwnG;nFuWA_6%5v1LS0E(R18g9&--glc)kC)gvC5M;-GEolU7_5r0Tci z6H40%0FoMXjrAWw*m$2gUB5DRh31l+%>OAx0TH4VJ2%(d z_KsRDIHp7`k@wexJuPLw+e^bm*5|0|XRD$pioqBC<1^YY2y8Aq5wh( zFTeMLXZ7CC?Ql(f;1$f4J=-G=r6N31cUzWgp2PUuzkG)7>T@Y-iM#PKRO1pPE#3l3#@tW@2AZ%OKr)^0khOJM|g)x@mBF5 zY(TZRyJvNA#3y@GGg+ z#R$N$J6)c)u-SF*@oOKwNlxluxr)zVRBzFw5dZB)ASvqNJIqH*n^>w50nmfp^l8#0 z2X$=@E3|Jk8TJc6hU$kjvrFC@+ptCiwY#nn;I z_C-HtS_67gT>)J0$zmTO_*T(|jd1Q1uW_yV^<3?mcjSK_F_3vPO!0b6dk171k!jXz z3q3T^1SzH#qh(H8MyelE5v&YO3t_VXL;Bj!nK-AL~abwe80<=2k8D4SZXUOR8!f;<^yX9kSwSt3s30YjfC zVs@R!$ByT0XNy+CvLU-d8EXYGCaP~DqYw7KXa4s1GhG||Eo5c$C*z>2+Y%|6cw0Jl zn?j<@Xz7rR?Zw$LBir^iS}1ATLvIhAE119OLB{hH*MKmC8_L^VE{=Q_mZedJ1!d2V zEWkI$%ZY>T_AO4DpxY7V=k|wx!UQ$2z;;R2I3vD-RSzmEeT&mW$guMXOcRKM;F?xl zA?jO<+HG%8(oZ&pPG<@L>;rlm!0X=4wrQTG@D#{UXM#}h(ds5OliDNiWiMca+UV$g z4}Gal$%TCyaZRcx@0RZ1Nh=H951K251;tMPpfR0WTIFr*ctDWU7#Tuid-UM5)ROYMe6D6tTiVLAy z>b3?@(anP^s?LKNKY`Ayu&4vbmD1Q5i?I|R7hG=?rTPjtYY0OPhy^p*R8nzuJuJzX3soe z?#Jgg8GILb9sYck5JY6LixBf;hW{Uj0#X$&9i|0Ahv=X< z+oFwm=z~84WyeFnebTMf@&9f&T92zLJlT6 z0$!RRO>a0|?N9q*ZckAFQvmYF95#Ctwsk)QVxApkDEvu3_zYTy6B33Bd0;{-gyX?N zMmgVkyqC6IHy^8Hr52S4%e!4;r@st24-Y(ct-I_x$?KMflEaEai-Z*V$?(qsjyomi zZus4)rJbxWv?SOqB!07SFzq$dXq1bMH%AB%>k=Gy=zT2qBx6pyvyPh_52K)4d?Ax% z-X9*Doe`C{`Xw5fQ621N{dYB|3Y9sGa2}n#@a;UXsd%nd?(%y1zD$`l3k!VP{DJl) z(Zj6(i8T2h5wEYmkIo?%*VFkTUDPU^$=whTZakRW0}dnEUq0HSwcO!jcpa8ilK*aH zEmY4HuR$WjcyL#*YO*O`sgwKSgJ1hc8qa1$EYbtQ|7ii%1x~-sz$q?wt&Kb2r7|T3 zZEO2_Q3VvY)MwcbeJ>O~_Wv$%zP!XZt2&3&^o_<53~Q+SDCn~qpovPxyHj~}$eP^_%-g5&Ub%G{gOIY{Z;Fs|&M||78sbvNbzsh7#Ebhhr&s z8|BW9V5Wtd+=LKB2|x7x(Yr(ZY<=hc_3P*!!iUCF8E6S(aM!!H+g#<)J4vUvn6=%n zD}@@7J3B@|w8P2ivpr)Uwy0A6q>uU#233l%d!pm?!o-IHi0$v+Ov0D=)`8jbgk8pw zQi2GhI$@}zlQ3n>fFn8g3x}gAebG`uO!bgHjx@B->UG)1@3=oz+@55zCb&-SLJhXUx;a~o|yyx4W`RXvh=iWm}4y_XA zIWHY^Dvw+E>)LP1Yqp_P@9`+7M2{i_Ki=GstL%H$kKvFT=Z2LVO^s|8=$JNc7ckQE zRR(8@*74Nbddt>;1MvLB9WO7-p(Unw-4kqEPQ|bihTLaP{15YIq_klVqYhj{5OP@( zfn`WP)A4*YX8laZei>oXdsnlMmf^L+FCFh+o02Lk)#}SEO+sbuR_ljx2T|4M!2}ay zaRiEb79I^9a)~{YJA}}ytQ=~FWbvx%GclXIlI&+rsKCJ@p@nUszF9zHmxK?zenm^2 zpsmL4*pgYn;QN!uDI~>KSjI@F>X^IYt%BG(8hGFL25a(>eYfcs8aBnR=~9?vC{^Yw zz_&lfv@)GNI8dq{dLL8aH8sLK@bYs;vHx*xR&*L)cbAhM+f}v1EGEzWf3H97>axO zy27AoAUR8)TFJ{1Xh=LhrT$TLl^m^2u|4WOf426*%I!=E#P|=lx-lv8@16N-_`RR= z<*&hrn|mhGj+&aFa~Es$f;qrT1X{sRhpw1GJ|ApT-4{~rHx&HhFX`<2G?NqnI`{_k zrfa}IJeK%sZci7OO^e~{GMB3YU?>yEd?{#I#i<|=l~)%U{P9zdtUojfhuv@cJzD|4 zmi#ot^ye!)_qcSr{u{Bk+q|`K!jzf*gvT9?_kl@V(?lG>_v&*8d*@=))fjH)Wu@vq zD~{5=&|G>!2!fe6Fkh!6tlJCnLK9p!5ALY?O|I~gnetO=uq7W1y3BIa-|$iR^NFwk z=z#`O59iHu_5*M1gU|ZV&MBP|G~m}7C>uofi$j@;0Xdk?$Kn$bx)5dG<6{G3&e-|S z|A^`}4g|eVtGOwvrjMJ>`~Xs-RJMuvHRbiWg!Yey3*8f4)3f$dO^G+J0EXo`8-dcW z)F*&VwsTmvWmzx@pBH{9R1Tejld!FuDo3SBU0;_-)gFgGNpSJAZy4_k*A40d`@xd* z>7ZhGocEhxcpRC+`~J98p3QTjZRy9+LRFjcvhEB*)oH-ef-{Oc&dT1aE4u2V`k^{M z<#&ILYYQnI6Ixaw1*(!L?&zK>v6@T2i|xZQM{zGr?3sDjab_xOSh zc-2s;Bk?0S9x_AATdSqD5+6GPe*F1 zi`q)(+Qnr4&M+87%dNwXi^c6xMFXbl0rpCulsrHPcsj-}dF!~8W4t1}3puzNKT$eR z>P%;J81#o>boYMzoM&JZNG^zGd-u#j=D2JAoEo4o)wLfdBMuj@FNU(E&4}i`>F2++ z5N%t~J6Svd5zMZ6&0cdlg8(sc%p!Yr6EXwGA|ERkrR%;(g)&hDi(9DFoN zg7JQeu4}fIz#%I&nS95*u*h(GIk(n06jRiTNup7=_=8PBM06d@)Hv3B_D4UlCveMXiCI zk5YYq8&=0rwCPijdg6#{bVba=&x6;f*=NTuH1fQC>ZIJLenCz$LEqnbPxGpl|cxh$CI1_&tr{i1<2cU47ezi>kdB z_c>>32;r#6H_F=XCJh{o9yz--14HqvcGq#Krv*q>Tkoz8=g$6Pl!MeRYb>5-U>$S=$a!pgo6I*<`;wa&*Dgkn!bOHDyhN?uc%0VQ2B*9YvG&n!T z+HJNn`#2CFbBOtbimS$0c%|PZO&!I^71jSc>zRlM45QgFO)?s6ITKOp{IvQnV-^O7 zvv-ykWyEI~%sZJdWdEmNHCnuIONpKUQc2-%hU-^N#@g<2I&uK? z)B=w1NNgp0%>^+{W35a9lW4RY>TO+y<>mAcRoI)bRBlkwvN9ec^pu!?Y{hH9ZPWM_ z0Xu2h9Xc2R6&!{27Rwu@nVY9jr?aG_$ndxDenwgKW3z7K8*2Bg5(YYl6ARw5) z#xZ^-THGXE_s`SMXCfmrj(oQGZAfmHlr)$Kwxy4|rfMRthaZ5T?;-R-Wc52u z_<_R^=gO`@XvX?7*K{5$@EsiU$c>5(e9%KIS_EE2D@j$N9dvHIjn@i`T4DHx4XHcJMPkJ z;-cyg2}Vgm?ctvj(CGoMwAYi`YWTYRBxU2*X1+^|wZ&tHm%JvVL_;jbTrar=7ww-Q znd*x4=1J|kZ}2vAm1b8_LQH&8KkKrAyDFn)+zuj|ZjtI1V7W|zP>nyc_1{V_>yjvP z$GZ0~?(%(ykW6&C^TePTcGor}uMZx}7v8c2G`eC1Pqp>Cq#E6_A5`)03ZBnBei5?I z4?)4TtzXC%X-`Lo^u?-Es6hlA)%Jn3y10}HntpU{yo%55XzneT!W06TrucuXTbuc~ zb;fBDIC(|mMf7=k?Xx71aUp2)yxz!A*Xs3@t>5G(yFB1eu2e7XnL1J__~NJ&vN$`$}#CG@n{aANVuCNq48-VVXz?G{d$$EH#GYm(uVO*yAb z&V<2Jy}+I19xlqS*@En4ZYdIaq_Uy(y%yv1>>MfZbfxjKAXp(!em&3)BFBnC1O#gI z36&iZe-)NX?G9%|jd3WBct8h+;kDqv;>svD@2BzXZu?IJSzmtZL7GSF(#-4xQuaE( z6kkXV;1Wc^`vq_V(@s_rw-*t^X?Q~VL@Ec6R^vM) z6Xx_)4F>JIGoE#EL5arOz5#EMti*D8Ftm4pyYp8OC7ZZ6J{J8=bW~^h5Aq>a~k7SAJ7zqr&&@u@sr)0T4$#T8cd6Y)STPILE67K zFq_WogsXUd#Htc=OTNDHSaaZT=&=9>1DM$$?)imGZ*xFSf={LD9tqbU-l3PAYx%r& z!tWT8=|yJ1!C+Qz@i@nUJ_P0aDxQ|(DBm3_uY5mwV}CA!Fw4*Opt7vmu`vUP!rldgajwNf(7Wp_Nsjs&+5#c0WOGhtO} zB(1h>-x+cyA;f=Lfitz5m3mDo{5les05QYLQ_S$Zk2@9%Wsg-s1{OYH6bWh9_ zolY?eU!}JBF9}SFmUzp3M{^E7ntr{!5F4Aqd>W$>hVag1!0$M8>k77l#;GIkU(j}Y zG~l&mYtOLPs^*C`wI|AVQZ`EC^ zhq!|kO`rHqa~+muP^Vv|9I>V4%w^rT(R~%fEj(zr#Tu#xQgH%Yyg9EXgCYXPI$_r` zv7ke0J;>+cpY@`!Zxp*nD?xDkX+K#T={{DQ>i?IYq^8RM{cE}G#+fV#N8F!~wZ8IYh7Igh28VAF%Tm&=tgLwN+BW8m#QGHd)4xJ{VQon_;v&5NU4Q)A4iA9*9DZ96W zz1uARP?KKUgHoMRt(diE`;5-Lc;X*!`77szfLaYOlJ>CcUHRJW z$7`-%jaGHDU$x+ntq-^V-GD#%|g1#IIJy(R7Q$isU>nb&3OO$hIGXM&CMtrDJ8z z<|1>mTD52CNFsTkd4m^`K*?5c#}V0sccDb6=NHQ6CsTu+R&kv3jon8{7@+{xewr$t zq>v0aCH?HJ@v$zfp$?GkdF9-+qAMtF$?wU?M#6l%#m+htzjxwx2)<8X9l?VyqZ$m${~klQZH@)-4mjmWZToHnVlhtvlhG; z0Bpq!1^aC1?jOli-&N))c|1gQq2EX$^&=Mcp4i5Up2A{Y`YI8}5bEF}a*g@yl|kvu z&du-VF`Pz;SR|h)UJ9;e76=H1WuO<^)TWGJJyDQWAXDv5?Nn$?9~;PS&eJfIP_UQ~ zO6r-dPZXqNZm}p!A2yS!xdOmBVL0C_1?!#<*2Et_d_(d-=4F|13@C$Spj?YZRnwy8 zKEA_!Pf}eTI#7D00FKAT34v!aminZeMby-0MJgn1)Lz+7)b%c-+fCF(l7=;IY(}i0RSbACTM|gd zr3i#f>CdF)PQ>#2p(du5e4!9RG8C6H+{~U}T?+Tk_GYZ`iTT3TlrG|-Cp>AA#hvE6 zyhw=CzbOV~JFjgIsomyUJ9r5dXzPXPeLN-)ce}ZBfRwl;mcHOKosGn&DgF>Doy}+Z z+w6Pf9CNK!BkZi*ec!z=V2-ZRv!Rs>hspfIMz_`JJ5W9qTFHs1JD~%GYK@Dwy;BOI z*!UkoKoT6=oOd2`wHbKNIvK}Wj~X>aPcc(Fky)UZvu#=JD0rYEM>H)s1ZLU03#xuh z**&$Bz_9ibBrFZxw)ZpurvHqUf#CD8O0{@-oZjcx?A4qd*IKzoa+YU3gDr!Kxg682 z%P7^n-9v7pDGuqoq!`QqFun;HcZTm|uH?O=@#Yov#waT@Yul42%- zeq!&YUfhi+J>81kvGSNY^ZClV0v{4!m*Oj`4D(j3iYaj!95I^0hj>TDeXy+iRuo7?dpR zpXT=Fl^bXJ?|a)X-!gSi^AVA5=l4qGa_pt!@bQT>j{DZ;yXMWNjaX?H)R;e45_3+p z=k;7HC4f2*Om*Q*>~l;Ph@hN)Gw9Gu{S6Xr_=t-E7l*RFBMY7P4W;OAh3O2S$pqwY zFY)ymXUO@xtAzUo6%|?#wF4(DH_aYAcXzp-teS6>*87>!h|)ATF8VWUGi<`w;qIe$ zl)!9J_HN+_oMa!e`0$KPXD=1C~2Z^sPbJ@>3O;;<@E)?rar=~f8NvEO9qvC378 zope~A-59mpqz|9@IcAZGBN5R6mza6&Rg)VH`3W+0NxeHx{UnVgqR$xxKNYXbsRL+$ z_QG6S0XE9^iZ%+Tk$}8f&Qsa4VNj>us2_x$bnQtB>8K~8f^O$_FC$=hC6h49< zxd2Ua+Yz~(_sZP=E6ZcD|x`Bn)RA^CUIy9@&9sX{`Vw*95)=4 z9i{2#zI`j?kPfE=sdK>fu*kMF`cNH{_MY2#X(cXGG_-xZiSqq z@DP)Ay;Sw_5Q?iE(&#FCEbl9!hJaA#y25n+3^>ZuVOs9CGXYy=M14?8SgD|~3OSyM5zZ(-@h`jxxQzhn2?dbeK z;n6u2P-=vZ;REFs3fMu$=TgsiePls{M8$!rYC#O5b{16Fzxc`Wr$e-DEzZgQPY?=t z-60W>d1QB9s5oyEfj3uQ7)v3FbC1#RD}z%?xtzPC{UPNj`1-#rUavdwgrK1PnAhA8b>7dC;k{Z>|7fiv;bpG!avK!R$UI_7|{NCDfq)kG}B~T*k>`x)r{_+cH zQjw;VRc~$aN&FVc_Nl%%iR{;aO`e%X?vD)beQciDHMy6^42AMJC`)Niy zyedCl)sJNDhFn?WX36w5%m1xHG|&(97_f~4C9)!pi+%yE|jU?;M;W1 ziT5|Hg7+pB&N1HaY)$4b>SfIM%=XuSiB>Qb83QtCZX@C3uwd_JChB)U#$tM2(*kAy zJf|ia)wz(W;OvC1I#|Zc?^G(CfsXw1cD-zNcuB((jB3auTW-sDE@!}!O|y=U_Xq7b zC$=ETHrwIbf#>z&WN_&)hzWuU;dP?io~r_%Frm(t(HJ=GjMEX%57ni%ka{ov@}8I= zDT|^o3&*!YLS;+76>81Ir_J-8gg+TYXZk!jsh5UI{BOYfAQj-ph>La?VR?|X z{5Ued&Lnl-;tVm%suXu&BgLR-3ha~Ds|`a*9IrEn{io`jbC>O3`!O@}s9fV!_g%tx zXVnre3AZ9Ko2LD%7~hvC(c)h_W&%oYgAh)oWuF_ZKtW9juy|+(X_8&oGkH~@;|S|C2z=SKcEGejmqZmR1ys4WX3+!K|+%~bO3jOSUJRfNb}9$(nJZQ88nk||Hy ziN2_oZpXLvoXJL*6|N^VDyd6SQeTk2VphhK`=1sdKkL3$y>*17`Z4<@bpblo{=El& zeG!p8hf~e(dUb7JW5v6f#VMXUVdNoV#d4R^Tw4TVbvcxPX0>2dN1nAdGy_5IuQg)N zo`PrZ{+B=ZbIoB(H@RvfT{LV-K)4EBvmz`H4tYyNw*aBQb$cT8Vt45J>Ru~F0+EuC!$)I`0t zbT*wpni`eXzQi(bbZUEGHX*d4Y~rwa;sW;W>h85B+7D8O9(qB$r=>*(GpYK6Q9!QK zHE)wdqXggz$av;6W=w4~`@qksUqK%EtbY)n{lWX@qMkOHGHL2J&Zk@zmvf1#xc1R= zBd1e63wR>R1D*@gzRME_<{oxu)FRuFcahF#Wqfit!yG%7Ve*M@mReFcq6t;hmEY*A zzIt&!;l$$cbdMBI`jeFa5Dx;!w4s+R>-jYdb=V?QoY>bsM^`~7``q43!I2}nf2!<@ z&Ot-^hjVcEsVBHr8#aAi1adx{r^IsaZTyjk=l&3gm%R0Ryz)NEoGc}=)Scq zz7OdO5Coh&!w*|;k?V6hyIT=ifAMda2kU>-DV0GVnp6q3^&VkJ9mKBg!ZfeMcxSkY z5hD&LG-lUj6)AH$^~o*eZ*UxAj4zQ^EHKbZRcRAq1vCz3d?cIxIE4(qT5>6h7V){`upq+9Q9c?$8B6&WB`)VDNC zM(f`(9IaY$%VRW8+&Y*M&nHm;S-kAgkQXRp8**`!?=%p`!xtINWxE;<^nMsOgvS=GB2w2WEaARF$Q zYlr}WBv7g(i;pF$-b;Hrd2SvX9K*Ut$O1nsoQY)~$*u1i3ZF2l z+AGV|>6w8A#p~u=+m~<`z;#O>wb21@A@T>$ zv!rxg(E(Zd_|3Jy?IsY6*Tps+qdlhLb9;WTWdYocxDJvoh|b_E5AISNVsi?Z=Jf~RD26D#NaUWJg1 z2aV46O2~)73c|-)6)=3EhRV zumnlBUoI@nY?}rSf zS8)3@nA93I4OS0zPZB(NOjUvKGnWe@vF#nJl#({K>|O-L^6Hc~+MZLO_b%w~GrPED zGGXVRzYf5Oyw|@hqTiPE&G?;@rbp|)|Vv}*$V#7V3}7J4{=-Gqm=yKV4#@b@UzcJpX@FB zq{%z(jYrXEWB96sA60(XLLNFoSZ&Rc8c$Er@_s*<8ufXR0?YaQtFMi(( zD2onYBJF2SV83!$)iu%Se<<8JbMib#30Lfot2*LUiiK46!FkaYh+2Ow%>4#`qOKx_ zGu%7_lP2PKzlGSR7YM||1Ec<@POVTq!PM>Ss!1Ch;+?J6DH*d!V3TeShHf36x2iS5 zLdB;wpA<-dpom8~hd%kBO)1#MvTFGQ5Rj_{KjabIiS(y?B5s|=rja;4ZoB+@4sy5u zB;JkAx(2-PxSJ0PUjFK8(QhZA3%wWH^pezLE~aWTvN(3 zW7(~!CnJ}!(PahhyNRVttB{DeN0?qpA;XRf1mSlPqlRW2rS;t<;I z-_AX`!>(=|4cUP0-%Y8tbs7hhb*T5wozZchzrS5L(5^1E&5EtP)0HF@MC?WVkvxCe z0oMcX05Dxnq7If_|+(nm-p_t+RbkD7N#>2f{$(qUH(F$xjq!()5X55Yu3sx#A2z8d`q73)Py>P+O&o z%xCLVgIahTUb-#nY8taH?%w3a1S=mi*A+nY1WDHo5|5Pdgpt-jFGKreecIR73Vf*_WM;~FN>0ARu8IBf6P zTAg?gd*&x@h}%^;NP-Iho~tK4AR%{4JXQyoB)DD?0WP$Uj(fk=!YocTwXvHu6qn^= zAOEDcSiEc&E(2gG*Tb?zqlXgx!+1T1V3va?4bd4S-(V=+mhUAmdW+ zp*>udF1OiiXt!0K;XU zw*XBifBIrJP$WNYf;aKNDM_>$7M$7pJDaQQy#?sijl^foAtjmGe*O8U7+t@SDU zlqI2+=Mj09TkbcFAku}x&=8rDC7{jM1R=G2M-FVB8hGOiggNoK2)%d8+ zn-AMan}JJo5`IIcOyYmj{U+~1nDGnD)vGI&plrD5xmVVO6&)YpSUggS9bM-kE_YTWfCdqgQN1T^_J88^=`a5`vt)|x4r zRSh-a%r(=3`U{v6{omD#3igLu^DFy+?|_-w@s3qd=eIU3`P0v&*AxTAXEm3ePe4Y5<1cNCy}{q-F}iWC9O zHfSyMX-$4Gj>O%$_T*N%*E7)JqQWfP5*j~fEU}Tl6ngrj=XafF_IOT4TP^+;@ST6l zHis)65UpLhZnZ>ZJRW0foPVnNpYM5YhC8@z`e~J|#DaSZ7^z9k=*d@xU7=>mm7E*k z8FuHdgLuwj{a4xK&X`iW{lzEQoFkG}jiSDnb%mtc9AKeBAvG61UZv#)oj((LbpgeZ z{ih%SS`j`N(a`#dpF?!ce<=1I%0Aj|MlVjhQt3~_=3ams2T)5~`l(ngwtI-;D!N$Q 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 x3MhV(Imw%GYQ$~G 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%0D0`L!G)P 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

O?1mDB(2Hd-t6Z@b4a&VQSi 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�RliKHkWg~$ z$uG>tALl7FyGPPN*SFOyAk(8#1y#1a-|0ucF>mBZxxK@;Ey7I6Hm0BY(`8E; zIfdXS=wB;Cg98fhC(VrDP9}z^WK4MObu&>|HcT)u1syE&L@{KaxU#7%DJ@Ar3T&GZhi;oa z80l6xv~D=c1FT_TWQ2U+ed(esU>m*YVDG!yIvwBbgu}dxNf+!O16YL;*cSyEb*ekr zWW^{RE75SAopv3DPFLczbTBe~v!blC&`xXXbwj0{Z9L!u80V6k4mzMlKvBkBrVI;= zD8(7FAfw^=Rx<^4@@%%NlQxP$%e;Ps|E6Ps&gRt&av#k;ppfspr+?b^ksQqraMu|| zsi4H~?eEW3ih5-1M%H45EvgVwCV%NhSaW~tsX36(m75^0@F!<~O(#{bKAt6XpvpdK zl1N)uj%XIFf^d~GDfE2^(-{u_qb{Gc`&u{ncDsj1qxR&-Y4776z|eXo*hUm&VPMd| z+^w+CRe^}|tT|pNvzK$9{VbxarE;TlOTl@ve1|*_EhZzi0nK+77IjoCR8hgQVORIn zpn$)T&=L^?#Y;dz_kzQq6u||K^iiwHWdX}#rH${q>Dsmib8*3&gqQhSWkTFNJu(VX zaHXI6(4ocn5`)v0eAA!ZXor99W5fG7YaGQa*H){54$2WA=f|{tHurqgMYS}w7`&cf zD%k0gg>|lC>ziy(^=dNI9nQS<&f4QFIIP}RXItwmaF=H!o&Unu=TX?{U;9w3a zYr)nR{{*%RC6grNU-S*}Kuk)BN=31@RqCV9H3cUn>yb((H8JO(CU)Jq^|R0}HBo6w}WK)tQx;BpPecF(L~PoTJC*#4koV2UDVY143RLhhNQhuO17ge!0`uCm$yg6~+9xIktIr z;Kg-~d;rUP$J-d8P<%6w+~ZQ=&m7Skn(nwd+a|IKBSE}hHs3=sA1 zEN7vW+v39ayJBz*V(Xsygos)q5~n1OlzSIIIg*;AO8El+k=ciK#p1{&9F>CnUlR@hG6)?!j-N zr=B4L{knN;4n8wv#B#tYscvXS&RI}%viT|(Go{`V9&ukZ7XmBE>jzvstZx>~Ps%Vd zEsm$X_2$qGUVecQvbo?qtdfBj|7iiVb+4Mo2awwh+3>t?Oyzwfv_47X`HCZTPf&ci zch)npK>d>bVIm$_bVQ~j^6mn7!dy-O-GZUZ4vu#om$71|4_1x)lw9im7Hv&Jm) z`ue3(q)LzUDYeL&_dL@BK=spm~7{vq34X%$6I$X8&iARynL z9LpY5x9n;46EPy6l!rP3uR}P>u|JONiXV|D;VQjx!x4~ zN#qu^okDEJRAH$OBO{Z^BfJr_5qcBxmYYB(JTUz3`{%3P_8_GS%u&9K+``Q;(`MEY|MbFlyD zA`J6$ezWy&F_+%H9nO2ce?U{TGMPCnSI9mhWmkWK6|NsT6S>tJOC&Wzd};n7@@E2NK z*}Uh()WArrLuxnj%c;3?2i!Wn0^8KX^jRd3SyP_8vBl$F{v$;F`^)BiQH#yd=Op9Jj!2Zgm zy#vV0h#L+*EvB6dzIm#D^F$MMo=#GAo)+n=Yj)8$-W;9kh&`Eg+WHn>p-H&pxca^g z{-rw(Sj@eI>Cq40&tWK0rqn7$`tHI&{Ep2e`Q$7KXo#7bLiyy7OF}(<3w0f&Ieie_^H3a&To8@EI2U}(1-*^b1P@m)l zbD}VqEtL!WXr+Hm9o>q?%vzv_h;^7X5OvOdki@&MUH(EM{Uzl#A#oN!aZs<^uIL(x zZ=`+HOMC{Bh?%8xG(PN7BiVJqL{9kgzhan|f0N2@pcyBANI8*e>AyB;(#-FMOnDI&Vim9D_$~okjQmt zj1si(y+4a9j+_t7^{vowmVD66IHVv&RT5Egrn-z|O|>ufhrvgkRaa1dyV^>pxQr-t zSIYZId7UN8@ybj2+`-n3jB7i-_y%gsK;M}X%YLnyY8kFSNYQvZBM$An*>ohROox|U zCl;M#ju&k>Ub1JwTZfxAyR@meU$FKh)M|!?4waX*#}Sq1blbx7+;G6`3xJxJ63k%y zKJ~N^S?@KnOY(!Nih;0+%~0z8b$cw$i*Mo3!{#e~*d3BUh=1Ary=G4zczNg)m}4`DmxSCX;dMvNu}w zd-tI4arfVSw#gEJrP~b^c=EU|2JUunsZ^?GJjQJjGy0GK4mG~+T#&T^K5G_o0xXLp zH<>A@$X4LlXy_Y~ zAJ+X2wZE7^$3XT4SH49|`Z)>>k(#VMg%q0r7e)vs3DdUyM8M)AhE3j^gv}_gf3zm4 z6;zR6Ob91e>nxr63Iw%rNZRY2`CN+n{>ur1V%c*_$(sD?`toJh!4>&8k5|_OT;vGr3p{?ggrCB#vZ=1Sm;K!(=JqX=hJdHMek7X`K&7~m z?&dYjTz!ze8|lBV8&3M%mmt7js7{by8lR6fXGU$C-Of<{l9`8{GR`-sy`)t`?Dk#7 zV1#U5L9+-%-+jTk9}=EkG=8-*-zIw%*KEcBT&6R&kgp~n7%7mLorzOp`qo``(WOxH z+utDP8!C_!T7BLI!%K5Gg1i>um{rK-{Hs=(8*eOpQ1C)GHm)hFzVO9s;tXs`-_x}O zA!WPs1HX%QscHkv=R2G{jxR|mACN^!0W&HW9Lfn)WvJrEncDiquI45ZeH>>m_J{GIIc zDIVJ<=p;mQtB7~1`~LjO?DLzHad6<<8szYzE@6)I;Ty)@8+({a47Asi+rxH${>}z* zm_YYfTVVf|>j>g+|MP7!gCco;*R6O)umdG$cFNpk$;lA2zZ6!}2n~<5c6;j-bMTjpmlJFe z0|-^X4qSw5^LlASFJ?&c91f zC25sJl2_dxSujgjl?N{ch=2q(B#%lo-TtB%&qJ_m4Jvr7<psdbKAv);MDQ zh@-YoMPE1OIsIeF3CXni;K4be5Ic(U>~D>E<8D!>a;iXQarOb^4&LEJFfw%X=-Wu$ zvb|vop^^nJ%MimJs=UF@+LTp1_1fYMyD&0!Mm_Am=3_5rU7~~c;-X%yY~&0`*wI~a zA$f}KqVMlQ6Nr#-+wfw=RmOSbi7Ka#gtUk0DoH~%QLrSvMo3yA=PZAYlYl}5X^>!9 z3K(ykNt~x`Oup95*WuaYzS-9$Et5<&2xJs&{c8FxcvGlYzO;dgx267#Y^rtH^MY8f zZhf!B_%qGM3$5)F8I)^(coLR*85?|ThN$bbl#s-3e>xn%T>Uc&9!?gG)jd;hzVKV3 zM+>CGB}^YR9UVaK;}bwWLx;6CPs4eD1Y-Qx`~Bw%f8)I`I2}pMCt*QFkMW|#3u8k>GD7_m?GBE_+wbN6fI28OhGV49}(Jf4^M`*$?jW=~S;QqS`e z)xDgW^0Wt=F&vy9ttyS|a!Y-EsDQ|7f6H0Cz|*?+$crg0=ait;YCBAEew<)mJ9{~lfDcT;gwO5lZ*-Lu{dTJ^b3 zDYjs!BT8MJR(bK*vi46)Y3KQFo{z&MjE<%={t(apm z=TK)vAHoyY(6sffU*Qh+NM0ge&ipQwsFsNy?Kz5yqB4WAx$1v;7Q{U~4T?QCvw^O7 zBG2Qkl9I_QgF5}!iLIZ89k|?(RCWe;eAqXbdy8(`zp1&)i@?Hu?W;+FwZ52TtA~MG zW){eE95YB^nkUzZJQu8OrXk`Cc)4o{9#*2dIwRbQj*b5bpsYRo{aS1D>>b z8nK7*x`xKJ6?{y`gs({N3ydA>zvTX1tOte5|B5^(ft-!IK+IK|h|$hHhJ3g=mTC{p zrr?kK9&ZkgwPF>_`59p2g<}LoY4Zm-vdN9|ri|mJznaw2O4(+@i zu=>k0!K?(VbkgKLq9+;S3IUf`E=seI0sy2GZjFXuNlPKi9Amaw(1S&UzB##pXTx!tYQd z6r1t?#~a^#P>=yb>4U2Oyg%8VK?C4>C^)R|@jLNGKu$-=clyf`2t9pXpqTjc{K~F} z5o5vGv@z#RFBw4((Pz?%s78A3mwnjTsyUQ*^xlG5zvg8E@A+gGg^+S;CMJfWR4~2= zcVT!_B?unzWmvU`?54k><$|>agP5}fnZU&`D7oY9FQoJBvrR4(sTl|PKsiNn$2S;s zia;Z{Wuh_#Wrw8s`Q<6)y01ncezPG+WBN>a12!#P3Ol2Vy4hBU^PfYPCaNuJ?#u*) zh4qI&wKGd-p=6j-Fc1q&Rx%@+oNqYsLZIjnV=h=thC|E@TDh)*j|wSDp)ubJq-Mur z+)sjh?8){{7`*e6CZMt?m@Gs#btX4x@?AlhGYDJ9Qv#{SXGR$GMu}f z1g)251ZghfoJ*kqNFVgS1YLBKXa(Jve@CpcF-D9ytgo4$nwoN^y#xRQ0>-su4E08< zOCFn)&6ua3vbNJ~X&%2Tdgd+eGiH#?vro{+J7giMX)ZNafVyd~?Rkm1TGMiLDy=A8 zahEO#*`Nb4WNZA!7Qlr5+PwzZh$QqQZv*$C_emWHgvIIy3h{Ju{pEQYSAa9{r^p2v z5rSuNdujgJb+}Vlnw!IADV^sBlJ4u1U3LIy%D-0<pGIyb za=9Dr*p{!OS}Z6Ro|$Dm^E3CWrmhWGB2uP!7?eyU(KIso6?8sO6&`=Qp5jnt{~I0L zgj5beXqPBSAxn~kgjr}BB%Zy`Y|rGD2fsV$I0RhoXd*7T?OX}t=L;el!cw@1H=D7o z`$7f7d$iG#@Q1sp7K|CYG#nmEPzYAceYNX4;&a25d@cnkNel{Y0``qYyow;LS%>Yo zu7#FEXw5ev=i!5cTqqP9qTyJh%k$x{9YlB(O5^AeEA8;1{qfWGN5cx`nO@*3;}Dxl ziglSF#!ikZYWAD5$S7|5LSwJ3(haY6lHRB?qD!Ud;KVr9FDh=b+eJjAZG7FIGNN(} zPM~D2$c_i4VgTj+lMjq|UI&7%7= zL*^(;$xI@)m}~p1Lj}Y?Jk~TlGgFzJ0biKd&yyL=LBLMr1wr8`Eqq&h95JC>d98kv+b!u&8?7@OuYO z+KK<6%eoH=Ju9nwD8?0A&Mnpwl5n9Wl2As39$vhjIDq8m=74;2BXp*j7hp|A^w<+# zaCbrcSms)Rv~`m{vY2DY;i|n_|N6!A<-iw-QYx!gXixTeZB;B(uml$rBJEAo6%${Y z@>!;$?Du3j6H6ABxrE(v)3!fRP3b7L5`8#`=`gG~uh4_X{P4lX?TM_jJMN_FOoI|w zP+gN>g;{eR-##B^Jo3N!4}buo+o6F1=F&}VAiQL6w=dbmSO=jY)3yau`AKu5vA&M(zG_l$YJG%s&=Z8v*-7q=e3sJj1 zAIA{=ettT`CR{a8$=z=<7@-^Q8=)Ftc8I*4&75c1JRs(TcL%G|`~%w47(6DYEMh@# zz7=zaL9>;aFCPyr-yy2ZN@wHTOlQ#uEksAg&eMEDIVf-j#eT0{5|}?tyUPH zU7nqmA}c`<3MwNtZHD`>)18}pP|&bQTQk(8!}wZpU)6AWQ=6%3a?KrC-8d}cCW0zU z5Ntdq-M}ugkGiKjs>*L#lsAT^v3T5G1=AJ2;Rq^H#d_E^xnYpj9biWt0mh?VtySgTSz)&p7&Im-QMTaf3EDA=#39)*I&I*Pu zf_Nbym%eJ(9Cn8R>HitxZ^)ZI&eB~_lNyhrd2d4-$KTpVmij|U%JQ^EoQ|{5D2tQh zE#CkRm6Yj>mBMK6KivvV(Q|Ym6f$PRduiwo?pMV^>#2MM0J=G(6lry*%`ZYAH1dbe z>4$>|bM?(9ksyWHzTA{1z+3(58F;Wp>!Jk1;Pc@3d44&?SCml;~-38X(zr@r`I66cnOxtR%ViT756KMw9%Ed4^ zqf+9{UQ;Q9c;#hswlWDQhvZFXrsGtpiQr30?ps{GxmiloC+E`IbkJ9NGzV&G;_@Nk zLI+@u#+NXYBUAPl?#s}3|0Ha5K52IQVSd^m{@U`HI-f)=cCx$TM8O^hX$23OaOlnI zH_}Z=r5L)6<@C)#!pa;``o4$nJrC*`>iANPcO_x-DXk0KlnbZZCBF;La5ANzb8*_I znwBuTa~tHWv_dGk<`avP`T~|V(tQ5MHbX1_Mtj&Kaw}9If~!H42|*fyhH`&wjCZLz zyK*dKOr-w*r3V7b_qKuB={#U292$5L(5i_*ph(;q%42xP3l=e*JSyXQ-N(ANW>QCu z!nKc<25co?pm{SO>tuV;>er^#(xx@H)&-s!$1O2@^JIDAo$BjHY&su_t zSr02J!!;!-8;;MRH?YcsR#y!bD6C|5`2^6t!QE#KaTEJJifT-!dKHY%n?YzZC6hmwk zw4F$S3u|Z$Dsxk&#-a6q7n`zwtP76PlrWyIkwC3GR3`PvLYc^XKaq2M;EgQa#ja9rB>;XoOVIPI_;$)rz9ap%mx zj=!X{VZGg{iu)H0z<)wO$1W>3j?}~(Oyo+NUtVvs!M?LvwpC%b*OH%+}H5^1>w4!+s#*k0BN{8`L1cuVF8GCX;<@fw$gUmhunqC){;lc7ML? zZshg6m+eg*O_53|?P>rn%`2OOG#jK0{xA1)u>=z06O42NsxyLzor@k3=V#vMVA_{9 znT2@m7+-5^`)@z6WA)Q(*fCeD{`e8WueEt%+rv%yA@4U2sSLuS?2IOx0}cv_ZvrfV zaLsl%ddiRAPoVec0HwQ+Mc}VevAUQ!XQ+|=?V>4X^BMeE>mRx1Za3>OP$e4+DLxWL zcrZGBLaR#V>Se3xdeXRNXIc|01>H{pAqjJl4rm}{3s5jdq}dg26<4Alej;NJmKM^~ z!~yGV4d%$e&L9?=8#_pV{nKjKMI9BzXq;h%MopK&jX!XUAN%(PhY9LT<%u^@YACu_ zjIk(H)mdIFfBZY?yOYnLg9h4y1wue|1o0pw&IPy{j8SQ{cY2_qM3sRWyRh)!{w%jD zy`9ybtzW**OCgcp4xa*xpG^@*@J6MDv6n2V$1|G9Cyb6;`!}IXkRXkLz$Lo(e#2fM z4F0c1b$_tuuy|?L9ml#st%mohB})3cxWawdH_3D+9H+C5*xKi2D+%Ubi_(c~J^uqk zI#b_2lT2#`KP{lB*PJ<9i4~m7qw?5fsp3=n^CLB>*W(28Vzb_-Om5fo;<&J;khX)y z3b{7v2*Ib=QfKEqZsjpG-xU?BV57G~%fpj2$ckpCPPc^cxC0i*9JXISo6&q^GEJ37 z-1{X{WF>>iP97b7WZF4v*3-jT0*d++cDH(ch;7wf0?JvQpP)kwn3o75m`FaItUSVc zs&(L2X}2-DeD^DikO$AoXaD*51U65vNtuu5)i#!1W7M}7VV0-6Vk9PRN&fo7w=Zsw zO5c4g2jZ~)5sUe@8@d(|6OnL$j@Cc#qk;(4v_iJC$0#Nn1j%&Ckkp{`r@mIP$Z1Jx zo!ce4!~ICWOJWi2$K*<3NZb9rKR>6qBQ zDX&ZGA1x-Z!Qzk?9s3JaHU=Af=^UO|taREU_Kd3%@w5?vmZ>>{DY1tLXe+0xZ4fQ< zWP)!eERN9#{lJN1Y%1bVkaia@jCO0e)iFrgrBi5K%4mIQ(+^;m*Z=(W zKSwi-3V65aIok_^6mKcu6+?wdLI08*ena_a+*#@LZgVq=Bg3VR^kCyFl}ApK4if~m zKx)<)s@y^beI}y#fv`va7Ln(LD<~;hm)-+ZlX`+Zbt{)wz>Gri_EHAVdl8yd+|)UkF@0$kx<%$G^CGt@z0{SI}x_ZV{z9m4ZSnd5jI z0r>rrGRz4R*zg7S_|w?}Qsu4)L19D#ag~zZ_Kv zdQLAWD>#>~)Ko{`at|ca`PCVa*K0?~eUg366HlIX%15%4s8DHq5F#nO;2D<<8wQ@8 zdlee$PD+Gk+L#lVBA8#Da)joa%M|fymA(FV;kmi;5^IvZ(+eWDO4#wV>X%BagZB5z zu*9_$@}>US3?3M@qwTJ|8V&JWMjL4L8FRNM0R$Y;W>AmqcE=@J%~| zrBJ#~zdmoxW;NREJu(fENF++CHdSx|n1t}?$3K2S^z0u;g!+GVM2G|~2SJ82edr6z z+Pm0p_wQVi(N5FB-D5oqbgO^ZGC+JJ=mS1QoT1yE3$Xc+BpC(g$6PsF#2j+0pu2vN z;+%Qu@$qwHJ;=2R=c{3dRz%-Gqr=G#jo6sX_2A;~cEZwqJVoPXjtcpCU_!8jjaBe? zYj==BUZEk<2!BheWjwVdRT-Ivg1La$!r(F4Bp6eiuB9oPZ@@rGHT|ODbaAiit?ZAB zm6cx_`KeiMe%pW+WO}v5viLMJ5waWCVf`^&o0Cb-LtQ!A|(?O6`07Yf!(|;BR66t=hw2DE$}b+L(u=*a4vjzAofNL>wbXC(awpVi1IHQ zO<^L`?n)0`RH#X(H~a32j>%*s`mlLm4~~?%-YWJ}wP?l;=)hJA@bo0XZAcXvkIWdo zW6^zbz-4&SH7JgB$I(^@vuD7<${g5(oR|OeT>m(hgIK_z4s4g4rzJbd-hp=<@N~WR z*Q*9}28zisCXDwE(3|#`Kp~FMx+5f)PxiW$d=%7N?$dB9< z$Rf*i*G~lqf!hlIcrNqhzMn8pg#KiK-TFG()$e$QjkJ8!H7yhK2*XmL#mV zIuTtcvfN!540klu=EwF!`68vI_c`;)hduAx69@V9W>4W(s`bavXce^3# z*R)!9TU^-R|E)RBSb$s;x&0)xF%(S*`aCn3gs9439z)bP*w3$rS*rsBgGL`KYiJ;S z*ZL@p6Ihc#jj^<1UHh_ftJ9O45P}ttsDFn%@Vh`TrW0tdXQv}U8(6w|g^jA!46+Y{ z!nzSd8j6J3MEstsfFCXL~?6)d+Mr)Vhd?V38gwzeoCJ7oOv^Zh>Zmi zvFotGgoFr5Fwd>Wkx^c)g?j@uKtHg#UkAI4__3PCsP}gv;tQP;2GR`!w`WfJ(S{S-@BddCxnYO zEdRD9mB#1JafE0a-#b&KwCWtEI2s-MH%(urZi(x?nL?j4T$&x-VM70=KM1}wmV6}m zeOEFRI$zNl27M~ypQPOuxklliDE!XZD`cv@!2|s4BTRw2r_85r|NA4d*H_60)mujX zvI#Uo<{v~$GbPtHx2bJKEr_0Exn`FAvfG8U7Yv=hu$f&_E6qao@m$x8{~j zvp-PJgB}ywoLb+M+8xLKL1^lL#W6cLUqQVhRwJ~O$#V0@YOB4{JX|KjXVMW;Pb>~G zVZW8z*eR!)mOr^~@rV=@f(Qa3xqH#)=T!*V6ozBF;Rohu1T7j{6ojmFT0PtC#5=Mm zH$@xyAlH@B2uGNlP|sOG`H|p1$aA?<8s>ylVnE!tmrsVad9f! z$#M`s7_cenqkK_LZ9f351;hza5He&5F?i?x_YW!`2`GH1od76w3VkSG_@Ya$w<%yXq~kOhZACO=$R4k(ru{O*xPQy%O%u-w3zh2%ylnTW)MxAo?&ZA;mpWd4;H8vn1 zcOd%ei6aT=VbA9@;t&;pCEx@z2_uS@oC`v-c`|}h0Vhat?227|o{uikT-j>hkMGv_ zGkWjb;VG0taaH0Sksrr&b`Uks+(^ir-&J!sKNFx3@*(njhoW3I*9QCVeAGsSE3=0F zf26&2R8;@>JuFCfBQ4$C-5o=BNlQ0FgLHS7G>C+>(k%^wbPbIlU6Q|x?@xXIeIA#} zxYn>7?tR^J_St8jeOObOa&(WIWl8;1fScc*_xHGYSpR=IZvRqZ{4McOuz-*FHoL_v zq5}a0h5o=^fAOk4Dn?_j+Tmp4_H%%8!S4=9*0Q41 zk}X3&$+C}4H;~2I%j_gzN@?=+qY>odQmA;(+n`Rn+)Um}QY;r4*`YMAfAK-~tX9UD z2~RntrVye|BVFl?m^QLed1e>~8qJvgG$_-*iWr5GLV4XDDfk5V8GiRlN=&yMwCMZ0 zmlmH1M?fZ;aujkVf6c2G=~t4thn0IVru794Q_im59!{|{W#4u%X*8B*9pW=g7gQuu z$ob!BDSidkl|WVKK1%BG@VbH5$#mJ>3Xj%2z$ThcpFc4Xjlc*a_kZG#**T6p%>du+ zv&)TXq@*-(cH;Fe;K%){(6VweFES53{fSshL7B#ml))6ABq7HobHs7~aYYRaP$anP zNr92Dlk#UZ^`*eBxJmhZSufhm}&F59G5 z_!xQ%v3O}bk8KTh{LPvFjw6{t{J??Yh#yRn(xUFe002loRA#rK?#dz~9tJ0Vq4SP8bj_ad zsALr4er)FMv|Uq;msK17#;cTgs1QkKEomK}c2Cb)c!RLg>1tGYzg@*T)oFtN6@X5J z+)hYHT^gFlqP|Bs0dF{s(utbxWu&5P8Afr$e=qBwO_x>t&}ElLGkxoJ|4Ug3LoI){ zgeXg)dVW=%iZR;8l5W};F0YT)qbsPYEvL|m=I1k&MVD}n=XVh;yMSn*nCW3IZ~MFz z>w%yv0VJ#X8ESIj$`_SD=zD{I*4p(S8PEqnuk8$?^Ex9 zGRqK~W)HZ%1bW|&Fy0WykIs|PiB3$Y*sLElxO(Qf%4Cec0!I66Fa2r??-H|zO~I@LV&w7llfm6AbW<+G46 zS>V|Hmvek!(cA%YHEUul-={r5w+4=e+CjBu3)F#VL>kytxr35r`v z*{SA78}eAA)VJnT+`F?-=FY!+ElZ<{Q?T()jLUx-@~Q*fur7(JW0J-tDy=tHt71a! zZlL!P^|S2`jE&icG9w4Vj%*`=n(|d~g&%5W&FCc33ZFEen~_2AUeDn_36lb2qt>_q z0AVPC{VdEY4XfX1*R=Lsm{p=rI<+7k>v<3L@T13MX#GkdEvMHkSFWIo{$dKf1`X36 zkJmO;G)BO1gOXY!J-4*9RFOeQv#nZ`q0D@QRW@B{xq%D<=npb}_w4{DciZOY$oT6J z8TUr-Wo?Q!1BuI=mz4SGD(=Xf9y0uV45+JXx`5LI*pO4{=~-wBRrDc{(kq3CS-sn5HZ$VpFOiU^6KaelCP zuuD95r1+*mkGT_-^8O_R#NoI6#oYh!zd_Yla?Fq@=dI|s^C$pWXt#d$>iQ~M{}+`;iBnB*w;nOV%=iL4-&Roj%2 z!grW&xZdGX6mJ#`mU&L3Gc%?uoz&o=I-10kfAt`3edXff0=H~)Ot+mu6lbd=^T!L> zPTTD-_xd*$$x#Bwq7)O%i3VVMf~i);PeA&j*K)b&`9FaQ_y^ldtfJ|z?<)faYxFPr zILPOZX7u9>*NkuXM}Jk&P!w#B{4Q;poc)k~s`;61kR{h@7UN=QvUfcK^u0MIz&Pgf zgr4P}J5e3B(q|hz$C)-x+u2(B(P2wEBsA$hwUnC7db{7Cg)cr0--uE&CSQ0~Jr$N> z0)K^q%w)so79%hPf#uyt7p$3~CcB^#TFHZ}s`>r(B4i5VcFpWmIt?UAYwFASBd@I= z+)BrS>e~K4+c8I?5sU{*On6U{d$F4enteptFLu+7Ji^L%?s+IRc45dBbixmL-}8<5 zsTyCG$9}=gzm%q#i92nynDnXMx8Ick7oF@$hDhMhbLd7Uy~x}ycH?q+e=oOLW-t*k z3Uy%Jpc)DW%3sGs$4bbl*16_h&`xgr*(!G>v)t$AGn=5o{K{yFZd-c6Uw?w0E9wG= z|0GZ>WTamoC)%dx}(*ZE>&jsGyJOQIt~{7kD$ub@}>QQ=NxhU zdpih~h({tM9MzJ)lmIVZT_N$_AO|=Ib2MXHoc~7^0*LxqUNRxIHQD2lG#HxWtqHlP zpZ*Q9pVbGc5+~eo-2AyQqiE59KLy0T_n3T)IXwfq({W;GmfHg|6Uhe#S5>D&Fy?$zaz8E7%hI=pO#O$oOJ=odCYRjk4D-`~^>O zzVcR&hgQ;TCB5`8HB$E%cNsq#GANsiDWfIz6o**w`UA-(Cag%4f-dnGOID* zHUq;$>t0U`=)4YBLYVSHvgs`~3qh+y&7NyB4r#5lGT(oM#~`%%Bi(m@cj<>n8;y6Ky%*=SNN!3U?t_ z{+5kmXv<;L_dE$2DbRvtFU7&dj2f3P-+ez*=-zeDuEgp=fqa~ z?2gEv3LtYTqUziZT`6&aM7p~qp+zbV;31iX&==uIyUoO2b6M=_wrlV}u0Az$TM=Qs z`5b%0B$ige@zF5t2BK5)5$k7Ek!)J^g9w1O*sb^+&7q-yD$P;ClYttT4H$f(YQWkn z$^Y}SgcS4oLp%*z{dC?RZSMzRN&CAz z{UBN72bv@nvvw%ibx4KpSkulP{5nDtabku*A!+qrdNo#Zcu&z0q@LQ1Hzn=)o20T% zGt@GLZfJ#2z$`afOa?gqg7X=c6aT%Tl_20ljB>P;0b+@)a~q}Z^Ou`0q(PeWKcDpP zT^|K#n_&*(nL|1t+W)X5!6S|i!|oco3S@b1VFV_8bK=@z&kuOGKiSuua(P_>2|7TZ zUuKs{uMP;D7Gs%eq!%}NzF!=lT=kCGeO@sHJQOm~W=!LLvT}OBawAHQDV@5W`bDg|_^Y-AGLFy?zH6K`m&zWHEpM_`z%ca2j%>kllq+J!U z(k4<$Z&r|E=8m+ssXuXPgOgL(DK2!!0ykO#^j(u%lPOr(z~mPGpIav9{}Qg#|J39O z?gR}erivk+^+h4JGXwY5$3Q!KGUY$-{QF3kh`yx!F7Afy*x%w{3Q`c6aL;MHKL$R2 z`9|ep38}ujgEbz5TUhzD5j>=)`Zj`lmpRcSsfLTz`RwE(liR)Mv+cDJ(d_#R3W4|3Lj_QePfwDKWAJQ-&96ZKcjNum#Q`ZC_IJ%(2w<4V!o9iuKD~ zH62GEJsq07kL@XKq<=D|Z~Qe41%NiW`bmI|1!!iz#j?M2-Yf_DL?b*TiXufVl!H6T zbk6SRZE8SQtbUq}qAQtvfNg!_sr&OTN$dfSE(z(0PE~BaIowG7m#l@%H}2Kzc#Wl+ zCUP2aVL}7+0PI%;7cFM|dxB>WxGlqcDl5_db9tIo-Zq8O8tj>lUI4^Xx(;pL7&D+B za&d9OI5n{#miWDu_BxBxI(hc}^9~WZ)MYM4Xh&pdZ34Uk&TvfbG5!6}qa>!@-gO79 z*%*S9>=1>MBl_*`nHcFwuKrL4wmiiJ6B|lx@s_BR>+o(Uu_KFqwMj?xps3CX;sFiL zbM*81-c@Kpt#$I=>ApPMS|ozlxA*(o+!zTS>Yo%dP9+a(MW-E-D{_}-Nc8G0R5S}T zrDG#?-OrCuh0u{CAW(6E6 z@7D7DqT1x*wVy^8L%5Q@gBBzTZNYk09R@C9)SNYcjf7>feuU$tm-C$DB$L%lIvmB^(dP(lI=%QD2f~yd}!) z1%{FjOwLvv6J$s*{&0xYqf0v$A*f^@07C{Sj>{U_{@S%POV{jAkO;}8D=f2l^U}a4 z3Rw21Ap%#X5#lx9SBzH7$sPcqD_aWs^xaiGE3w;@nj>kcYO3B1quqycydi~$NUXg+q+HBU-E13cOH zX0rJwViI`ww4GFoBXKZ5hvHs4tuwQ`6E3{|Y3t80ddC6u?H!H3h0FWPj!4eqeOTg^ z(B$M!8sjf9>b;lglJDzp(&(9e*V>T7PGICA(cwY4bWA|Y>oiLcFAqU{h#Y* z|H6Hev8i{@DEBhZ3P;2qO>rR(TguI{lPG=sZP9=|NJjsv!97QHi-qDegBu!4=__ zmBWYUEwRO&;^>o)zV`z4_ElN16!kMMn2;zed1HG?pvJG)al1A{UBo|W7ZGhEr648uTem3AJhOg%r9RB}tumHV=@2$my+po_bO2nom z-n7|#(*uuT=Bqq=LnG6SB@fw6mC+Ldv_U$(CC*fp}OTsmvDb7!{G#PBHKa-Xz zVD{6neJ|&`Aa_J)P=W zjF*!nabOyLO(7I_w5)`wX~nV>+=iY{BfmC04fWW@C04olC&gY?(4-?CQ!U`ByMUI3 zdO4kt&tmw473!y=Ioh4%M>bWUi&&v9@9v~Sbn{pMiP-U#(k$PgO2}YMpxw`<=Sa0u zP=vhyJr}az#rE_%aGd|@w;>tsDBjP{^_35QM!ne0A)fr@PK270kF1RJmWl` zCiM~zZa;LZO3{8S|*0DArrFHDw& zxIhHFJaUl!@&;W=YDg4F^k|!WFld$!7R4|T21X=IGuO*O-A(Y z@ulvp(9km5AAG@gU|K@IxjOk)@Llfb=iRpEiq+4Z z0J&&0Vz@bn9c#pg3LB04!}f{c7cN}WaXkJzNPPf6deP~&M=~9bN_EA{JmVRis!Nd- zZl`m?m(^yW%a(6TGmW2prWaCc%++#Br^9StacwPDyWhRtuA1aVFWCJvZ3MU3^V@es z=wIaNm;Kz``0Q{uS}cpGIblHvKtE{sp#Y@dUBCs2M92qRyetmO-X9%A;^;lxsV{v| z*XsnJUP-i9uicry{br7=ry{=#6vS`V-7;hOWX;FdTHo*s3oDVPud(j?ODbP>%A7dm zsz(HshW)KH7XnY3+~I(Me5E|>FCoPXZKszfIf2q#-OeyVDAL`J7(#(}&}e8`Q}tCY zNrb;mqyFFk7ydRu3{W{*j9A>Byn!2l`7FKhE9_P7?G{bFm7AEOiP!eTVR(B0|HiNU zpZXEoF6-(5)L4A#bql3kHlfTZC;;3 zI(DIC?!XZc!Jv=e>rmy$y9G*=+-_uI4YR{ucr&V1$_0JfW1SH9YppID^!T)KSSz9# zGvayLuSHR>@^}e*3dKjb{QV!M?w5O-MJ)5l zfP~~J1ylxt`bwNCVSs*V3K%io_uXSwoPW0t5*-cBcDIfC#!NU`ZxvoN5{qRPOIS&3 z;E8{k!-pcRfIDwbb3vu?NPFUb{BJLSMtk9a)pm_J|H~cPeWBKJCOa$lQp#xepI)1) zt-jl{z;~!6sb%t73Do4a(H(Y@%)ZvLqw$kv@S;kY7WOo8F}7sXkt@?VZ+l%Jq7z!L zqo$@GHy)Q+%pD1f{Al1VeR?6rZ|TpUfmmphxRAqV|E2DdK1pkR=O3n`XqR@YTL^0dXlm?VN9R(w&Z-<0)H#wlYi z<05~FRmfFL{8p`~)2QTx;i`m`3E=tDoQsGS+r5yFh`v~>R*OT}$lx@pp>p;0_nIGC zOd0W%0Lv=2QNvlA%}g=FikpDl?=5I-w!Z~OvSLR7fbU7|+JuvV$8AC{uY_)UHygE@ zP-iFtS0S?e$U=24e!KL5B0AxDD-<2ZPLnw#o*6~7^!?iTDxWtGM6z$k0|>O!9voJ?GUQMqTJfMgUUPR}@SdyA$irVJioYoKuUI*iXXQ_YQa9;8 zPuo>bvF{tUvY1@=ty=w$(#6;Hxl37S{@Ic6Wj2A3Y9};1{G-qmO40YqWJERQ0EO-c z?c(xfU-;ZW2%DoK8;DgOvdrV&JfxcKUKZ$Z~nO>Ft`5l=nYT2<;Tjf8*+yexc^ zUj6bak(cS=`Bl_X8q+f=9EdEhLZq4@U8SHFmpyVbp-{11QyI|ntPydZ0CMI5fau-W0EOs4Uu#-qWYd`^QKxD`9Su^QL$mfaSb>vUW zpq8UR-Z!f`H4=tLL|V{zTB(%X&IHDc!ItXIM740P8)p3ud|42+ zkbDJb@2*0#xxxy&;|~CR9aj)lGpB*VW}BtRUYTebk40jlzDeqWMAq(M-H&MQQ*7Cl z$d7C*Ktf$`Q_#C>B)bjn^WhdhzzV2WBCtaMxeMUetZ2nQZcMxFfG^1dPgFe40r^T22XJ9?L zY;s@D9PmEI^QZ(OY@(3EmYR(pO&24edC$jo$(6f6eL+IZ* z!6km&grq4Z(aODzn#K$ST)@Rn&O+2w5zypIPp8;oPxzIDI)tCZW3wIJ$L8jfz6f-S zOzcFp*68iDfohNhw<@q=ky`C(DQq-T*bJ%2~IwIsnVFdh)f5emadw=Oljmo2)cN{+>KO|LH5*b{#?-xUKEO8cEQnIEz0rV=AJBLXl5)t+&BG05h=|;kmH=5ul|=I4hEBcB z20aKvan7@3J}>B^ktwIyu)+D&?W6MBS0P)NexW|$ToeS}X% z5viu20A6ahV7_)-q}I0tW@eCy2VMt|@hk$zu zb@De$MPv}y{r~hU7(eN)(TI1GhLxM}SeiWt=K#7HH!FX$$vH{}46#NkJFcW^W~YtJ@=i z)o1&p)}$!z8zq12P0|2Pz&nu zpg z4}q7do#`ucq2+=%PEka^HYw%o0A9`(l%M^aInxh+HxY0`wA?+vdKfHw-e%Jhj6DQ`^A*xbPx>uy;~ccLSPF z)MeEsV1*TxYVxECnMZvJ1r0^a;h|P|MfFhX=o)vMNvB_I@{Y(|8Yo5)H^JozUV#5j z9v~csl_vfXD^hwq(^9Q{q0#tjQC_HU_0;QTmX$fQ+z!9S^jFYyRN3lBWeqOd8|9+A z#?Rn~4NO4y%UT3Q{lweOe({%NBId*(;3N!44!k_Hu{UW#d|tkbY1NVNJFu6i*MUS~ z5I?Sqf6je0uRl6~6nk{Oj~pb)Hh0eumtbix$KX-{t7p^Pi{k6(xWwL7On+>S{giG` z`x+I%g0E=OK50mT8I0;-{+j>DV*_2IC`X{7z2@4E5TmZLLSOY7PPryzq~JUxDWww& z%!(~X0u8tf4iI66Df3q6d*SRoe`Bp;#)TZT7-g^lW99rc*?M2f8!(nuXU(#m^uP)i z)QQ+{=Dd-g_#egSt-VI0l>@I6@0rpN5o5b=Hopl0Gv~zhmKh8-KdYl(=-*7B)lchI zQR+wrq=$?aBhqmoJw&&2IjSlG;0NTIms%mxz%#;g2SXVhEm_p^4xlvj*U;?lKWx96m}pYGoNoQz zltFR_fi{mD7jmmS)m=H0H?g=yhM1TGio98rLetjW*n9SaO$JXR&iqoTRR_gOhjCQt z>VUmtxmFJPzKTJ5-T*!Vm0((6EgNxt9U^Ow^S>!hs9}&rMw@oaaX*cuIumfmiFQ^d z&JLr+4t8;)F?|grk@{e1xjuT}5qQ&CcDlW>Um@k@O9tIp?(l^ zD|G>^UuR(m$aSBkyJ~$;E;Rkct9m&)uZmZ(r`&$$ChQH@tE>hKwes}cWnjK0ER$Os ziHoacN%@cwi&$QPoqbc*p8lDFi8z>|9z**4M_l8T)`g7CokNwy1ejkPul|~mk z#>gm?aDFP6vjY&GW_F1ZI6C3g9(8CxN80GtMYuhhDg4Rj^{tKIxN zaM|xGTwa0*7-zX3-4JC8omzYUSVy~siCb!C{l~zQ2T_US0XhDti-p#j9zw4mHGQz= z&YcfS^^mhwOgO;ir|s{}Wbhq`LdaDZq7g4G5jjYq+P-1n*V`6bzsn7?QvcJ(Z`;q+ zW`ZSPnr1oRC>t2!{^svU>zx7m7+>71CBSEZ?@^&t6xNbOs|DA~eBQ5FFd0zBfUesQ zBo}x(Iy1;*4pOpI^1sG=GkT7Gx$~cYb&|n;9EG2xRy*yz;kS4bay~HKreUNvH)ewb z!jTC$yF7MwRl0jUJ9UZ>oB2%zO`~a3&}MqyT#qLeKnKAn^5*@j{z%&D^YMW7UU2cV z>A+wpZTZ#74-DK;=Mq*u1F(9+!S;60T%0H-=)1p8_2vsW`6u!w%DkW!%2O-i>!l{U zR+fO-*^h-T=;Er&7wL$)|8&jm7tes9Kqe3{Ey)&q33|)Ig4ad55O&y7+r2yk57+?& z8(f!=S*0ymkN0ZWV)Ob5;pL+=K!laLwSl6=qKYv^-~xzc^iIJKnT__%dKduj0X=|Z ze$@MZQ>K4grc z0iV3c=+@hQ@ZUxf9W}JI(ais}x)#O1=nA}u-`J#v$(P{aWX+ZXW(8?3N#o&LHVXn& z>Sq!O%vBrRf7}dCaW`2W=o8;FL*CJR?f)}w*fLde5fc{&z?32I!)X#m!Bd#qwY~H3 z@dx|Op}ysr&G}FADT*P3jif*cz7ZApD%YsKFsRiSAdh%_Y20fibe$1C6u^>i?kGy@ zQ@V@~{FL^N+daSj0LX9_w`hMRg_W1=>joBZhtg(Wz07ZJuS&U>zc8ZzoYsFVP_dua zK!G58(M8$zgwvO3jPw$XeW8KzmHjb^5Xvw9SMSem6yL1T=fGd-r^@aEluAGuEGob> ze)#reUIs`dWlgxQUU@nRlq>ZQ{6x$_znK-Kud#%dd69O?slH!`4WorB2rExD3Ic0ZZ4ii*DG+ z*l6CwN52j>@6794+dyT=u)Lpclbk zhhJE42^RAo0*^@nZ!T^R`BS2G>zOKg>o@DP zm(I8bVoCU^6uQz{Ddy*L@je;i&qJYMW)S&qU;;6eQ3f>Aq6Q`e1Xj@bF!WsYZG2lp z?^heYkC}Xhr+rcZC*Pmzb%kRi7UnvidHDkq@Qi)5 zlfD40Tt@)##Q&7aR|}p%gP`6j$i59o&4THvnyG(Zu zAf*EePS%u+fWJ;GcKLH^^*qZ0BCYK5SMumNW;#BRA1-BMSxLMt*OE#$>t7WEk!6Cv zC-cYDH~)d5bpnxjtCU$7cLjvqCmct^@7=~kj`T!EN8>!#c&cXl`hff9X$#cm-{~9Q z)Edh|3P!EZnD#3p!K5s;zTd7%Nb;0=-qkUG;rHI9+t=Mu(2)xE8(a^ycAnB)5U-sF zN}bK)5CaO^YhdlW)65^_CF{;NQDv*I{i2KTfz^TsA$PZwqr>NL`Gq@aD_2 zZJwVO1(93nvU%f0@zkNC>1g0+JiT6i-7l?PEv@F&uD<+%VnC9s_!#;0d~wtv*wWI1 z-@WS6%~^ncG-dKTj(I?qe3{%9!q0JZ>33CLT+CV3<#Nw@RG=R zG7;g7I5`PoY%N!U4AzX#_6lR2qzr}!i3Q$HOpv`^YcSLk{t-kd?0<&vK>~X5>X^c> z=2YZ&>X@FslGDoGmf-CLu%3~l6}zctuW}moR?q)@lict2GJo}?t9~z7v}bNGo!QnB zp5>^Gvf9_@ojvO6Jt`FFw!`)Q3H7#@tDk-7sh9_B(i}eJrYm@zcQ3ymZM{p(<}eE# z=LlJdUS}N6Ga}cy7CP!oXV*8!SUu5yU@FQ#cYatQ=kt_#Jo2!q{nFdS!b+>WyewEZ z8Riii)@Xu53%eNu4I?y=k)$m$*$N}H+Tn`58<#Z!YP6h0Q|)je$>-sb=uY{fpvJEj z{I$5er=zN+p9&?9AFmy0XA4-I`jIu5txnqOcD+DU;!CU^XHWl5!xf3d{bm$2rhVb8 znh|^-Z2@{OLHTedX<2m^K4@Z1KxM|T8#{k>r;7JhUVzh!mUZ`BO1uTy1r?ml0VXpP zxiaK~0E;u|lbr|XG9%99u0 zKLzZ19{#Wc-*so?$*R+-(;%0OZxp$xwfjTRy=pr`PEQfOr?E0`)8`tScdSThaInzoLO$JFW(k@X07Ag5NRzfNAD;_yqvO+`R{g!QWF z44x#Dc+1D8)M~|HsYoA$niQuRk!<`t_ zgMEAu7Z^aFfxPT@!|WgK}g8n8LBe2i!LlK2X%y?mbio94ulps7f^;#jjO_m^G`3re*2x>qp{E* zh1tQM@Xu|)Q2}e5-Ra;+;8VAf3u$g@x?UH|o4nmutZ;TZXGld;FN@EAy_K`~)cg3B z?JZ7k;L`_`EF)MWj=D+dO{(w{)BrNcjr$hoH4r<8(}NoLX-Y?KNsuNCScMhc=I*G` z$}jbvi6zeuP9vfsxO_lRu2u(50D*mukqiE$NpEC&96E7MA(0&fY*HUf z=a2Zxy81NfPp?)aHC3vmRmZup;<0<~-+FV(eWH;wh`XTZ`qM&atnrI{FU_K*S;_*1 zY4?Jb{f`l^LVE^Ye4C$QkMK;jVt9+UlX^UAj8fmFdFx1j+uEc$TRK<{XX;b3GDPu@ zUe^!fv;HYGhieYX%|8HVQg%bwGc?1%Cto zMj12VH^L~9?n9f(3>F1ko}pnJBnZYR!XJ#o-pvNl_m7&TyM!ug1xJ7 zFY0+BotjzP!}nL9ipeF1(KjonD#q1`%}q@a`e}!fGv6Gz-Xah5!f{lSfRlUS<5AKL z1f9yAW%&352Fk+5JpN>FzR6(G#{AZNK-o>cB4)T2F1!stGBed^wK6NpzyVE>bJ=kJq$-#awW*+qJd_@gf z^TW`_=-Nc}omdl6Q(SfL=1u_no3o9m)w;d!o=|3-v%$NlOX4X4)gE~FN6&~p=uSg7 zlY}jy;wE}fJtcrOS}$bgeuF4ve4D(y8@KMdGvgk`-nO~!O@tkm53;#do6yXWjD ze!J^#gw;`gqF@9BF)3zlEZi*vgzyuW)etWHl9;l;pwo|PMG$i%6~4WkyLKIqaMs=t zhj`{oAT|_3$~+}V7QbHV*6}dkqxa9lA1BxHcGUMin|1uqel+7LgQYjbV`Q8*0^6fT z7#}{oe)n@%`iS7%^17)g8-_}_#&t1!Dp;898zlU*02( z04er%q`ipFt?)PQ@9zk4pc`bB!doUyq9PbLWixJY=Aa}L z4xNsuwn&SU2l!3;>vo+|Ov`h6*!O^j9+zOto(+ z7m`FG@h8kY?sJS>;juM`2mC2m%iU}|LbmMIN&RNg-T=ggj1N0REtPMEh);9Uh+ z{~^uSlPlqS19>aYuXf{w8FGy|tOXZ5Yg#a5_pPQ=v7H}@pH_=WWz`~pHP7NFYFgdo zdg|<>8$46bf^BoG-BJGNxQAStJ)Jor3%-P_VyO>cV6z)S_od@E72_uDB~BgN%&w+t z?Eni7W1%M)M(ctw_?C85(r{!0l6FpACw~6Z#sdF@^j3MpegB4M1Cn2;^mg+ia9f|2eq(Tw0&w?{M?O)!>t ztNLf&EOwqiABhI!?J3_?ap&B=dmwwG{*j?e$Lh99htfEM3qTT@s24+km~B+s+9s)_B7Z(r)`2Ra(xR#b4g-p(>FwBIv{Hu^V=s8;(y<(DD+JHm<~ z>8e0M=POllLjh}vQfy<4kosXa5DUq2RfXNnoKP3!iKE1YWe`2}wX`1xv^|p4C>Ye1XI0I!Zcbj|Wqq+ot0|2?e4-19rRJw4y&pTBFk#EC`W> z$P>SK+z1RHUY*+tXI80nO$4&f)AnIxKIpQOW>o3(`zIwRd=NZ%Ekv=2DnnZ5d z*XMK_GYhHVl2tj0;5KkK(fJ`Tz$YOB*wTE!Lcvo*Qr{R}nHO$8IlRHNV$S~vg~)J) ze1WkfkqKW|Erp0$`$Su7jo`-OJGwGdLeB0sug_urn#$U=s3}Z#^0=?}XG7~@9Q&i4 zel03jRr$Si9Wj_t)IEJegvX)hO^>*O2@yg+%Ap|OwKe;kySsYN%LdLZQ|##U8Srna z;t53?_l=YvsKx;^B$h|M%GvC&CVCx#A}(p>JrGTumHrCQ>MXX~#cgoWyw`Wa85&VU z#{t$g)1W@*bkseeHe{%%hc1-Fkx%is9Z}S^xFuQ{aIwJVBkeG%{sr;3}Qp z7KYOr)uc7xn&UR`Zp`=SJ8_Veqv>-4-{8tKQ#)a^#XMn~hKQ9Zut9HLaHr#k|~SS90qtE)3AMeU?3WJGbY5x*Jx|LxVX5 zI2KWbEJYu}E|qj+_pHr{HZ33(9ek}}MWz+&GWtHCstkTfWuC|!(p zaaS-9x!{_4>f8+QE>r%|Kh^03EXab$&nteh83)T>(NgJBL&~qDp+h>w3)Duv zld{v08gSVePaC9NejHQH?(@Xq= z)LsBvx<*hC?dW@D<|Jqrqu|z8LO(zKz<`E++*qoP+1O7+8NsRTC-$lNK<1{{qx!|& z1MbA)!`1TQ^P+uLGsgf!ot8q##?zLdAJ{tH2s(??w#%JAZ&-f!3FdVJ!xLSA>r9{F`g4WF-i zlUn0AcD5J?B~%juJ-QR zRAD<7xfBMHw&Qg~jCf*-&xSQL2JV(YEP6N>fe$Dl%^gH^$M;o}U^@MeC7Yd@BPdUV zFd=WaaC(m^&BP+OV2ssUTaj)G>&V_6apLxhv;~|F?xk(K!qwZT1ePf|@As2l6KK`G z&B^^)5G5W3b(H_sOG!cu1u~BEUbx@!y~9p7w(%iYnc{l}$5)~@TB&a9K$Ak{Kf(^q zlhY`Jj8$6YndGqfu9l;(Vb5_ak45bUR)gYw7CH7TYQH?gv#tZ~G9O=cqEq(W0q&-M z6m7PLu>sTKcPyssJBvs8q!mzf(mx>wJt=K$E)xi-d^kP@q2A2Dks4MGYUU*r9dO4fa}RQFAO`QzKg#x1(N5w<^h^&U;(h7D zyo80$zpRwJcc*vWxrEhacL+Yc$*$qJJLmMV3WqcdVNVr-O8DAOW;=`PTp$bsoJzS= zi0-66kQKD&9=nP*gHs6{V8;-J!q~jZ;m3C5Q6^A4&&{T=VJ(3Ou0AJ?B*cB7i?Y^v z8bO3V8$(iPC$~#n`Id?eC!Z5Oed@%P4TdCDo9qaBSmPHweQ7r~G#WxlD7!`kv`cIFUYVLmj(rYe$}TfRgn}$94SIp`)3g#QGi2 zh}=Ij5=*fxDE*Jn`xuu;J+g5%+D@tHcX8EaNwmsx!L(xqjg5#(M_n1>YYaDZ?bojO za*VCUh094dcH?`%+!Ee92E)=r4zqlIb`bd-u3c z9S~I78&Qj)Pr+oqLnZc@Fto;Gy;}RK7WQmmeKYqh!EDVpqS49$PZ(oHV?|r1jot^A z3-u56GDKh!tFd25!$u^bZfNh`n#-AHetf=fxkY&j#30xhUh9F=zhN3DE}1y>oSJ5| z?&M1Br@?G<^do40OgriOzHO--Hu+ViEDXyP085h%viaRap3hj4NW8LQ`|KMtyvzt} z^2t(c1xBo-DE+gz51CtQ-;V`h;RW9lw4ZE`Ma=uDL-H@4#_>fa%)Z*9ULYO&n(42w zWI7Yq6a>=K{JFOKjt(I$8G}Ux=UzCyi4ZKy9&F47($k zr#Nm)Ls{utM5m3O_w;<;Pb_3zYL7YM$43NULQ0VFZFkAn+k^xL98=Tps&J8Susw-e zVeaFNccj}IYv6DdcXr;t?@#X50PTw4^cqI8S&SqSmZ3ON{*E5rf*g?{5nWGabB9F* zI+2bZ2F^a4&$5|B^;#3YDLuXI1<0bxKlsu zJ2A}ltjc6eC)nsj_=wBDTPyHt{zPc&QJ<$6f8(p_Ti_W4YOr>0~}r zkjKMe2K|#(MK$->&;)6yR?aNiH(Ymzn*o@0_%k%S`as|o}XiX&c%zf zdk_#8Gj5bTLp1JOHehKxT5M-AZMAA9jgKl0hqk&Jf~tzbB@$c8p>2=o)@6pZDke{eFLc z`D>4H&V66^^Loa0Ny9J`IP_~tNceQI+-)Z+c84Ppe~Y2Tn6>&r=ELue#9i53bC0k2 z7*Qcbxf}!mf@WeM_hNv=jM! zVdDgFB`Bns0~Lgyo3Mton>MB;!60L%e*_Qz61`3uLAfi|30L&b1F^1GO?3=P>$P@EV%g2jHcX9%mI zWk@Ocg))-~T>0=EW5i-4o^;`Y9LK|dCq6f2{ii!J%zfh0)-w#hDF9@u>>+`_SO6U5 z0GKD)RfqX|3OKB~xF55OL(oNzd+Jt!v|wDje3(Y2hp%i-8ZKhau;;y+(_WDFhq)3k zFO0({KX9@u*g!Fd*CF<|;dK-}A15k*|>Ck8S`< zp0!Sy@?xIipD~LedURj|ASL2sWjA708ofn+q{_CdJvO9OXEZj9#8(!&eZyqVZo-3( zlchyIuN}6gHt&Ivulw66)H*e*op@|{3hfOALF4-omW8HYA2n-Ku+SF*+e-AQGwOo( zoed>l-5>NL}JG z8HGr?7HDwAgr=U?%RmO8XJvIT{4QwBD9c5rAR>Mz{el@%8x-L|cN{4L-3A0CvS4|R z;SjbzjYf6eF1rGyNFLp~6RmxxyYlDazwnPozx0_l4k|hA8(h2YE(eC8vCq0Xc@1<) zwE-)fuNF|Hf=joF8GS>&#QEVIoINDa{hxHO6IwrR@u-YpO;udaxKgEk&c;6BXz+5nelDswA^k6gD>u|o_g)M#DHeKQqZno zTY1LJ()XH#2Fm36JL6|xKR9fxvC@Lo?aqei!ctVJN>yBgXdfraImE}_GX!=1MBOi{l`S4Bt0;=WSH~q%_(qX&CXuV zTLmIPkdV52^G+%jE^^|eUENS10Es(GuDnQWTb2-~?(ReO0ZJ@pc>b0un)ik8!?Zk)W;k`f_t$Z{A1jqE(MtSV#=Y3z z8NY(@ZS=mp;E2NS|4wLZZ`NIvF|)vHEEp^LC40*c*e6V!M+jkv{rhlcQZqO83!@D% z^dblS)s%SE>*##3@@$Gx_6-&&MqKT~s&(gG{86}C`;1#J0a_QS-DFxdcpg4nc7JoU zGWO?P@1zmPSrsKjrj=SHsHT_S0ZQ_aDfE@rBfV$BdFVln5?uNv`y-4}*61sSJ4~r^ zn&|@>#hPm<8UfSMt6faf1lcY$Y$J@q%8X7_cu)Ze!M7gru0d6OxePlL>iKzBb9ni- z`Wudy6DK%Ia=@>&xQp^4S|%;xjFi0on(S`M$3_7WjaYD==Ng@c!jN>);$y%#^|1lJ zXQ)4s@~VRxMZ=QNH&ZR{ntg~)q4tN%mHLujzT9slu*c1{);&JGhiW^mpk!(TzZstC ze&jh7(@V6Ee!s|8QQ5{Q(SXg61oajNus?|+1~L#brtODn(q+PC6i|%vWEx4d1aJ&U z0tUa+NW{egd&_ket~7n`4_E>p;FyxZ=MmD6e2Fu2WMVkGtYlg%E@t1zy3isp?zD@Q zaO1?9G`STri@?07aN~Br4bCjNg9+9uv~Rie7SsW$10m7m>{o8VF_6$Ph0laX_*8%_Umw*JKUQlw-_^x2(=wHkIs7_xU&`#71YHec@x2lWrq zEvg2-{*O#J4(*x8<2o;q#|&4qONt3a;mk*li=E63mhsZcmgBojZnyMp&&xtRI|e}S z?_g>89;nN#tohnGEBwChJdiYnEqdd2N!xrs^243d)6JAA_dMJA-wy}J?aa!r3sdZ{ zVD{_V%e7W%ROhc2@bblVP&nYsbLs5wWhq|PvFcW)s*L?I39lBLHyU`L(~&S3de+W+ zp@YJ9BlGqn@J!TYsxGcrfToAle*I+uPI;Mmh1yCF9z$6in?E~}2R^0ht-(;$ul&&1 zmGIYb(2>_yj90+o`p6##3OKFUMPWw!#vheBO;nIHB*&7<1Hyzlq^|w?CZ3^c)0aOT zIlbT9J~W6LY13lkO(f=Ew(vx z23L3ZU4I9~l{+ObA;`+QR4)UA-Xb3+Jz9%5O&{dO=6#}nN$Nn+Kays# z>tISw3aMKq<3oTQj`fd3$&>l7HG1kI?c@X*qp0=A7R!Ir10>4W`&FjE$pBw;yz{LQ zBXEeXAP!`8Y=9Ux`0N6^Hq$yC!^IH||8EUKsNB%RvrxeU55hro{8}5OyMS= z2EM8-J%hgO$Sgor?lJ!`c;e~Fs0`KlGV>?XV-@7)IVn-52o%i#%C%`#!mX>ahgEs$ zOe<27inR5+$wS9?$90-1s|ubGhj+rg)yR8yGu+$olo(No*TPR&)kQy;FRM4}XtDtP z?vu#YpIy0>BT>EYv!O?aGkJM{qgJAX3AxZg&xM}vZBhe`uWX`ouX%ls4n9KQ*%4_* z-8;}RotQ1dUsn9n{rQ>o#?wm--_KmK_S(=}QhGqN9U9J-_%sSl7WFD@Q~IYh`qhPG zz7`Y;KbE?V*>*9EA5tU4v^-MB&lpXn!0;tXom>Mr;E_8$gmc*|QZypVU7^o1GCvyf z>77X%Jhp(BOZ>;`!WYEk@ui5zQOY@cYEa7|QxVN)mWXHMw|m^wi_f|w4>D=PKJ{xB zzL(q;m#5zTjI05u*I}&BKE_1#{NiAP!N&tYJW-A=W`Veyw}1PtTt%m2`?RgLw~Ny) zN8Lv}bi?DZ{zNI7OWcoThY5eF8klltb6LBZr|^nG=i%k%HSE1&&XC3&0#6 zh0!}=t;+^s5Ma4H-{MOn{93Ik6+1gSuY`QnJ1fX{eT2Ha#LC*EybdW)^L_%bjtEkSjVCX)|8#15y4UJ1^X^-Jgfz{%xL@KW|pv_vCsycz_7{{XU1H1@Z=@`aV|LzWLS> zI?on;OefyWK*Hxrp;^xi507`N%Dgk&^_fL_5IrkK{ku}fu}Y#C!p|yEf}UfdKPy_Q z7?zOHh8QS$dnTYhau8vCx_t_(R@+6pIQ^jjC_FSB7dK<0R>7?Iv+~F3wJaee%U9;T z*|q_Gk3_7h7^%uTVQ50Jjxuk85EvdYuTj1a5@Soi(0o;5P<7UpSVHI&~sgS7ECL3rWGclt&f%UG!O% z2AG8;|67dcBT49C9FgHAZ8?oLca7x%J0We?` z`{R*%LPTJ|_PcMMgJ?O`+`oE&x+I4CZg92i+uQGm(+Ye96+heWV-YoK*2~QB7@g`Y zzB_dOPWAqRB&k_Guj}W2o#&e`z~8d-?sYn8-H(ZfE*umGKGuBi*~rO!KYU2Mi_LM; zv)UWF#yNLs{QY!fjqR4B1jr(Og$UnviD1DaGkc1`y>{+#;%k?+b}T`%@*cVm7M~6# z4bu-mrJ}%Ew233n?d&vGueNu&Q8d2F+q*bA3O~?&nqT1D=U!k&)H z2_=QZN$oxyts5~Hqake8dD1s_Cwypzd&ouoOzBM@Z$?WHxt-U_a3DWoVRpY+6CQXq5q|oZI;2Y zUloM@9jOR!IZtoxb}V+b%m%ZyGN!PiR+fSHQ+<(IWr#5uv3*m1*qut6cln3S{~24!>Btx*Ms-I0g#`~!-XK>8jk66f zL3N0-`uRWTc?@T2JPPu*CXdI*ejr^{_G~T>z*oO@ed8|q z_wR`cJ2QG}KNG-sw&hkzOyN3ZdVHkN9|IxZHI*WUQC65pLwX$y|6Hbu@PFOmt$XPN zS|rvk@`TxcYN)KGkPX$HQ2FN9+Sl>J`NieF^0{*zqM3x&4-PpBtAVcZwvU5Lf5VoK zp;PuHzK=tKphaVvIhV1j`QD+AN02iJxbddrL*Wznvaw?9T(~&%I zIs2b0rmzzt%g{vR)~xr!n2@F1ijC}t(?of24revN@n}XyHMV=F7EFHcub6r#Wc3xt5d|_h zwt`c$b}OK=CFhiCVS?CIZ;4OY8~j?@Vp2qygF#jcw}yt&((riDzbb_kzex4Jd?N6J zzYL&oGIiC9xnV6s`4UDn7ye_Qw`s5 z<~#9l^H%0Rfb zmwE;V_97bir-=YK6i)7C2Y@AIJI;?P3ad%JD~zAJA!a9c2Lj)(H}L|VXG<^ zvTK04!SA3hF>LUna7&emJQYb}(Ot0APK7#*v@*ws_Nnz5tpZJ=PElpRC$Omr?X`lQ z2jc37$p5!VaCpXo3Q2*ck=9ed8;npvwqsw2(jKN|3gg8M)_Zqp#r+^OzF2rT5&ER* z@Ov*4vQXMS&Xb0&;r@q!jipv{*(A9b;$Da&@~@Q@o>t_^x%y7T4g6OdEXX~#&*@ch zsshp?^;a8BcYZ^suOo0uIVGaw+tE}d0 zCEuZvPr2$^N&dCMKCzC*`NZf5>rcyfz5+Rg*c&Bah6V3=MT>huP-*;6bm2b2d(V>`GE^5(V*#@fOva*N$ zxoghlx19`X@iD~>TV__n`6E~i*0s+@N$(sF8_mMWoAp{tp%Vd+0)Ty!z-;?;OclqL zZ;&D9m@al%BwyzFkoCTA&$pk}G{jFl^wc_SY6&MP+jerTmXPq73*a|68Y}hLDwol0 z8&UaUVcp<)7#tXce@?ljFoM}%ZE(RS7#h8540lQ&e$e||;#djjJ6BQq3g{1e+P}%y zs`{+iVS(2fZ#_7t@caKD7yqY@PvU;}9Z*Cxxqv>Z#Ca4S(=FcjEx?kqq*Hys(@21< zM!gq|ik_88pZk#K|GE$VbO8A+F9bp4sPwTpHH@RjJS=Y%x^4BoRHy2>$JF8AbQ|Mf zFS-Uqa44P9VJICOs@jsW*be06R-ja2ni8+0!qzo(m2crZ+)IsH$VHMj`9`I*>5K1U3;ch!Pee~ zm#-mqIB=W2eOl_=mv%2#f&&$Hqmq+jWq1bgg73(XFh*l~W~4I=YlNJno|89kB;PrJ zpD*x*Q^yMW*6AjFJD}8;>^zcC-eEw8?CH58IcHI!L27x+3Qe{@LHIo?sUSKIDr0x| zJ0d36tkPblK_^jpiF{PC0;p)gA@_jF94X&uA`4Le{Y8VqoY3jcsAh}Pm81^H(IJ|2 zs=JWi(n56EktoKuAI4&s3no@^U#Gv0KnELXQf#Csr){Se>{NTTud@3}E_;`%RO0>+ z@d*a}6=*WldkfM32&jjiU0)=P?+Z-IIxWdb8;j!vhLc`M3`d; zfJ{eNL6J1K7Ql`g3TVDP_)6m2`*y`6D@eAPvRy;(zm+5riPeA}bdDbAcNR162$1#R zJ?i)r=gg6yv5gU+^V6EtrEfygaIZy~;M6m=D}5s(qFa@Vu3hizDXR-Exd6P9QT@f% zzaPdEg{|ES+d>lDL%YKTeD^Eh#BfS-|A(sg2ayBh8+I8Eb?(++HWm@$vcBZc;>5Bx zw=}>mPCxu$FJ19(FAjCHWq5Bxs85)^t2%oCg;Y?8fVGJ@c{yp-+x=aSXoSS4hxRV5 zRZPrV#ALM6XAn;rB`rh<93{Dei!3x#^U1f=Zy6Ot&&8 ztK@gm(3K=K@zTWCd!M#AkMu4^IT{ymj;EgsGDFzDpuz0EIbG0b0*(|OgBNw#UCz2- zwws;C;|vGQ!n&ZL{*kcDp491y3$NGKbVx9%*7#pq)$V05mKF`;t( zVp+{f3-9O4u(>H{@eO{`cyL|Y zb`1!@hqi;{i*g1(!;5NDBB~j72H|J>O?8BT#%G$$hU_Ck&OU_D@i~IfQ<-4Dp#<-L zj@5fT;P?NvH$3H+gp_6gcw=m%N{Dyx`#$2H-GBjK8REw+k%z}AujTZfeBwQFr`^bq zY6`1FjZ=VLV4JC)l3uCuaO8%2?)7RJHksWMsoO=!A7t=*l5LIWKe8lrQ0erb8@f87 zLue)L$u9*gw#LJ#jR_*&h}esL1(zJXT85@eOW7L~kX(IFq8r4|p)9tx?vnu+7;9`K zwTQ$-`hlaimnGnD16*}>oT&`N9+S^m<JYK86lTWY6Zou!#m4ZH4x5Ey8 z6CW6rZhL6ji~5180hrnqrG6Bm)~;W_lT(h#?~W@V@Urwx%y*|iF4>P;9KYL1r%y-$ zmdl=jMykS0#`CB@HBqw4-|L-6@H{6j8lr}Bxh5@##p)AfwYq5?vo^v;STdYK<#gc8 z^YcZc%>Q&8#YpRaLpQQ=Z~f$0n+)qeFmG>B1Zca%MlyTU&2an+^2)$}T~j-LOFEF% zPNfjRQ4$dZ>@+ZivbLg@eqhN}?4%(}Jn%F#?2h^r-qGklr%xoVEFZ$H z<*#(id;U~py;|$7^Vo1R-I=Xka>uxK`uoeSZEwVc8Cli5*vw6x+s*~1boAo)_7QUn zcu{mA+7xEuxkEW-QBra>d-aK9^owi=k(LRJp$TxPejTIqLK%PdVXQGx-p$1d@Nhjb zp=0+P;tPvxNk?;(2{lligh#YeQLAE|>8m9wb_mB3V|LG8#O!o=^hly9=16|jDa zFLblT>;_xF-@>MkrGlZNnG#`P+F)YjYAX}zL@xeo>cC5U%Z78;ZDm{J%LDvuATCf~ zn{#)&$_Bq90_<8vNFu^$B&roqK5yq>%-g}BF4=_yw+-Wj=xgMW#m0EBF&3IQ%aixz ze-V1EI$(qOHvT@}ED5Pj3%D&QAthA-b3I3|_EO18K4qXU)^7{=NBht%p_yD&{{B6L zn~CV>UT#h_8dCJ+N*!nTi|~xlI;14$PNXOPp4w}MsP#ap?;$dKv4hcz08sKdAZ3>BD=#) zj*TX4CyOhowr3Xwq!It@FKfaOd$%B*eiR@YiJ`P*uiZND5Gr5y9S{Z+>v z=Mjhi(X7GmlC1S!)@~W4Dcy3>D1cxRF^4PLmJ5(*Aq}1I z|2QUqk1^U6>sr#k;{Eql>a=OaEP<2%qF@}rR?bCj62g`ZeK@j3xW0*m0$Cnmh6l{Q z7&?3(b((D%cAr;%K2@Sla>RuF8J2qNEnl<~x^TIE3fGNBn(hrCag#}qx$XtT# zeS;-^(R(`T6gGei4aA_yU!6j-5Kluvn8*aocGBTu8iWkkwGodEI1LrX(p?B^?WZe9 zW?W4vh*)R36$1iMA^t)}=&t71*x!mR@Z_m8lapTLg;9vbSSn^S6CSib*KAw12=v5n z%2?N%U4MpAcxXGsrd}B=Syl|dgLCRRKk7SPVoNxRW`V5!@OPRTO=8bQSvHBRq8MBV zA1VFQ!FzC|H$&36yF7~M44CCQ#G{rB1(&>gQ}G_t*THr$3h`%R2HulR?0*j|J!%VZ z&E9iVN-{pKdmOf1=&#%DS^U+0D(=AN_~*X%?Wk4IbDSels{gc>k&KQ_ydg{_Gszw4 zKUPqxIaaPMPe+bB#qc6O7FV3y1DFs=12BPDYW9K5)Yeuapb~ny zPPi*t`Rp1!km$S3kCbO$6U<7RJ6b&U{yulxdGKP$1oAlx9iCDq792?})MNLiqtu_} z09rj-MoXJMjU8ZIxhdHNea;Ref;ZZ`U;1H!-$2H=U+Vpr)3Rv98eYv=arXn8sN>gP z7+`@cnfx;Pl8HTlr$zq>`Rxg64j|ok_nlh;HV>ZTM{gD|%VPt2Lgiqq$>_eI2t2&D z|61}9O1J%)3C(l5N9oYTKAXO^0@*p%s>|*mwG>h~)TK(@3NN33_LF z@~7|FXom>4P1`J);9S8Q#uv1XLixnZp9&ScHB02k$|_LFWC6VKPE<4A!0x1iJ9RPP zxm?L`xCaS#vHA`Uc5^c_y2Aa$ZvUzB@&XztW(Vw_BYBO%3-SFrUCfh}E{?qLC8RS{ zC(SUS7|iyRLwm`t>NE!<+sLEwo2|{7Ld=y!c~xS}byF>W_)&I8=C_}*d6Cx04P5Xb zU6a}T=O=z=Tfw+rI_SqyDkVT-tjaDREID?&ouf;KVXA$^-}kInV__3Aj;c?$SD>jZ zI64KL1{8x0_;|Z4+mkKA2nX%;LJSfI0qIi=VuOb9d7@E5Y502+BuIuj3W(CDZkH3_ zp?GcCbN{Vs|8<1{SucRT(bw@rAPXzY3C0Apj^e6DPJkB>fQMJp>TJhv_04@%A}=+6 z74TZxOqwjS^&QemONDXSb9-%*g_d`Gusu7gmZ7~|nuyytRLGH37c%iqsbx06dC06f z#0_l!=n`Uq+r*E5p_WX1#X?E3^`dB=0@sfC#&3k}VTc6&;_aY?@9E!=NeL5^0Pn+c z@=u?yGN+cX!UJ(59esFvGGm7}Zn!R#A%;_tGAx2aymOdauStEeu(2b&KTw~J&y?!Y zi8b#HOk1JkwADFpxpv{zf)Z5KA?t*kS67kFvC`>1q%&;PF&UyEB?M^H zp4IhOvfcMv5_>Q5Kr^m7hBar3v8S;cjAixLaMVF=Q(NC0Vw!%JsY~R6tU=fv)F+K5-syk3SQ@k`Ug&^(`pvH07RnOQsWr6A zNdi1)CxH(`uz{h+Ah3r5gdbs#Av_45+y`@+^Wqc=jS+frT7;>Bs4*q1Mw-A0Cm|ug zs^a3?SS_7>A$3k$+zMN2LnXJ^`Qq3yw(`T7UL1M&6Rz4Q_tw=?Z?Rxs|3KurAi!7N zl0Hy>Ni##}Ua-S|73;#1?_4uA2%$}7LTw|v1!DUwO2o+1+a4;JyvFQtW zQ!X)VLU@r5)zbgq*8c<;rLi*J0I)`t%YF#&&@Td)>P7f1E*0FR*>C4~Q<{l+KKN#?u5c@aKK@$Xh`ibB&p5r|$@SaJ`PFUQP zN`5RRF-xc)v4Je0hn{;c8iPwBD(rf0xExB#e%A}X`naWKGNN~kITV(I;ZeEplCH>2 z)fVmS+(I)!JDL+7K2K%lVYNHehOb*=XrOv}*mgJyo5U1?zWr1Wh~sZ0X=TuxlgZ8Z#i`6E>tD(=2G4o&e7($^3ojOwgHDcD~ z|LRkypoh~i|?lPo1L|=Q%D5f z;59(H(6IG+Qp3~pOKmA35U-{=dgF(j`=MJ_(Skyp4t+RrkyOCueMI@iS;kllE1=p> z^}H^9fA($E&29Oaz3^^3?Jp)^;Mp2F#Z(aXeChI}vSzmz17vXczs(59^?IyLv&k3_ zGDnVi{TFvh&tx$6g@va5Jj3^iZa)Sxz(%q=)3h0Sa;#r`K|!pb+3aX~!DzBcE*x55 z`^Tu}`H~jpKj!j*FYZ7qV50`?b-$Gbf*};I#nAsRl2HsI0L4EQPC}qBi&4(WsijTd1eORsAB*(Hl zZ<{e_>8_CWUuBNUb}I(J!2C|`=GP}oDaTW&Aj?OSi&Es^hf@7bxu|^6&2;+~)f440 zf|v=Yj_`_aP5|*tpsa2@HOI@x^2V%7`S3a5vTAJS!MIpjIC=8e= z%$}mZGul7s%s3*wQN*PuhuFb9z& z*S=j-SKQw1pH?OWdJJ8y-_Il)$>&^5zhm#V(OHLFV}MpWd`RDN>msWw-(~>x5q>SWUrAalU1q~FpUr!AP=`5eB?5bz@gB_RIgop*C)km?vbx}|l}&YByb3h= zWq30BVHj6QZmd#9M?0O7T*TUEXF*Cvp~xYp5`pcSk@oD8n}HhkT}- zG`~pml0CUDRyrzzL>J2RTuY70AT7(4%hx!d4>sdw_ZfZZ6sKF<2%C{(1j!$lfiO$P zI23H)I?Hzqs4jRn?&LGF0KIirJ;HE@2122`XC=o;7r(y&;`Yb8H{lO;7JF2R zN7=HRu>>Ljgy`tGOE`x1i#o8kz7D&bY*FX(d#()dTwqeb(2z`u8%gT<`smbV(*m0V zq-`QIQHuR?sF_}zp${)qXwtX%q zQ!EYe{$Fv{*j`S-7XOh^mlW(ve_#Sn!h4KyTEDS7ArCcdIA;o^B$YABdnKZHTD?vI zQf>$NSDYa|GyvS*_F>-N3Fh3&_dKevcO!wlRF(BDm0yqI~H zJrT|$*IiDYASv^x-$Ie@kgZ+(K9uFr6Gc2=8o?MCQ~*#-2;qDEjyO4PlY~RsGI*H> zZ7b_GZ5yHuVbWE}%HMVRJZy`O<%vd!u0#T2 zsY#TlTj(f!9h|J`sGi4 zsQpFpQl$xSYdyp0cAW$~Wi^gIZvkPy$g3<2(Qn)OG`fLFjW@YMwa2@7UEP6Y<&!1T z1dufO`0OQgpf}zyO8q_uh8&`wHc(*VVTPa7M}qIcv9TmMIR9*mEw zgg^&M>}|F7(cbOPt}t`P4LM!Zxy&2Bv{KYfmR_M!V!wajUV`F<03Ve+7DB4#hXHMvM zHvGY-%7B(Te#ZV3yqqh3dW?i<=O(iN#|H6Q|3lF~4C(;0^MCDQiXkrx!Jq4sSuEK| zwDCZqf(s+DG8!bhPq;IXC4<=ab2~!gz782ML7;jeY4UiZ*5T6VWz&v%-2HP*P-g%s zLT6UxCa*@5w=lo6o?sY4=pd$?z{!m@#<@cWUaxGQQ2dLzXDXZYf^2Vbq%d|8%Gp8< zs`pVp$Qz($741cI7c8LyllH&nf{-57;A;S zWnQ^?d2IPjVXTR;;jf@yRadMl;bj{lm(Pqc@q85w+MZ;}kl5p2Pq;D(hb-)4C!+&k zoY%giW>Oof(?Kpqg#;*lxF@@sslc)Snfl+cBmeW`KiPQh5}`&ptbQ*55>4TPHWq3Y zS`gH;#rye?Jzv|;mNMyDAVlj3*fG%MaIbX)V4}w#zfBybG$YGVBU87GC*!RKX;5L} z-r?=GoSyCALrhB?n1BS1(8~pJEG|T|uJ?Fz&gSEm8NVXFLp`=RPgy24Udq!2f>F~d z@3KUej!TEZHLor!Tj@=1bFXN;0e?w|T~7CbioYj;CQ&+G|1WIWR?9%cr1G zkuTtxMYUr6qDUi4>m|w%pp-YjzktSI_0iGh#(KbA%b@lZxRS8D%6EoKoO9WX_6`cG zsOKD60SYYapqRMUcET7>_}6_9&=89qS8R9fyu(e25d;$6L03W*^JE{l`0~BFu2<{a zr)6<#$0fYPGQ=%Wnbj);v_Fb=JO0*&bvnEZBPfpdUl{Tp8>k~_?`BcXh^7n8ZERkv zfuY}-rHCJ$$dWJsL}C4tYhjTk-U~PUAJVqr_HcI_Dc+_0@n?g#Hrl^a7YIg4qz%}@ zb9%cga;blDye#rWO!$`OW8*hW0}0KXPNq;!W#^RNyiWtO{Z@fyL#Kj6} zA|rp4;tqVb{q6TOe+ihCWI43wNO1mJe1e<_xUiOV+i_B8Y?tNati3z2vsBv=X{*Yi zS?zn2-xdts-!UDKcLe;wPdEy)fQ6916r%(tpuO?Ho#UAd!ZXclL02_e({<|QY@+K7 zL)rPVP8jSPtX<*}2{c6F8(jSUvDd{{0~H2n`OoMypvgBv@n<~!`mjIy&HM|sDiV}sc?M+c zdMWyhF=yo5IrRUYlcz#d_e3CA-5_XRDIsz_5zY5dVs$4v(PAkNvy)k>Mv{@$aLg0e z4`z-bR838nlNEi^>_$b|bML2cJbMmryO$bNdPVZz) z=f)-*9fSE(vFYMx!}g3K8a?}N*grl_3s?~%NO(C=Yc~RRMS5-+334Lghcwu`s=zu8 z<>=919y9SMwOc9PiFn;M^K?tiCb?q8XVxEkw5?}~MhhxfXGWCeFK5Ii5%a#JuLM%?6hwm8*fLEv@#?+! zvacA{Lo=!4bLT$f>;63Duf0<|(X0Y|Q$*PG?=l%%x!TuTFuj7$KhzZKd2fS6zCAMM zfEni^+-AUatFR&biw`W81#*ePUjB$hFG>M5^N6U^BE^@+?)E!r(2_`pz7fK7Z7ww6}gTyo$jMJKUY9IWK|tLa^=vL zvGUG+7^Io$3cBMAoP&G74RYU~I%9f{df!;8oXnRwU#EQ@+{QC0)Ta`L^vPo z`8u+;p-LH@vfoXt-xHshrf=Twn>nj*bOp43aEjh*7iZ>0x(N z|Lvn^D9nqf@vD_hwI8A|4)#L zCV%oO@kM5;e}jbr_)V4ipupaBz1_C(@aL5xFVnp(Knh<6@KTE(Kto^ zxnV2aOo%kbZxaZ39M6>53-Bcg_Mu9*%a<oU*up=GTDbpr@qP?qtpLb-HN)Zf`A)4uKO#(9K=f_^cU$ zsSdT{|G52x4ECDsj;vx!sy4?B2OFG^1ON?x;c`#k5veN6bIL6tiLL_M_j7vkSHd@P z^R0WNdjb?c;6$+)YuxMn=6#(i#<>&L_u_6ryqrvc-N z8`y`$oVsVQ!jzOqzcZn7KM_1Y&PTY3IoE<)3Y{9=q_(a()lTQjzxqTiCn|w^FTIb) zXYZ;1w6y1mZ;f8i_O9g6@~5*sw^7W+6DZQ)V%@yZf4Bk!-SW{M;6^pU0q5?ggC^w3 zXZ@P&W^2S~8r7It>1L51Ok6Mrtf_#Sm}fEp7= zM&%OUZyorDjwU*#%L@o2YhWB+ylY77lcHk#$`ms0or9hlWvq6#&z_%ixKGRtw!}X! zCDWbZ&Ck?n45HnYtQ7FTQYmQdizfN@@obPG?9T_J4T$?wm!o zO1`UcPmgRZ(&aWK-FvP`uhlE}%TnGsAy)#0ZTy)P{j?)4J4aOjTB%+!A}}N?s}G0i z>voatcmbGV-fktP~*iJ)GW7@S=246=9J;)p50gR>~A$-)aScA+3E< zQBA!7owOn0;dVn?-YUj<%pY6ituk7j<)oR`WAPd*CFMwf;>vb@s{z#-hU|!p43s%M zvhOBiu(~vEIQ&mHe4>X3esoWbf%nraL~n`Qu4D@*g`FcXQm{w}#G!--V)5`KJ69l( zqngyh+8^zgEWvenVSS{LWMWEqj0m*J5$H~;LNpx|MV})@eSX8oaB&@l$g^{6O|6eb z>z?_F`M;IQ?sli@km{Iq=lr~v@V;ZcP}~%l3)mx?85`KFzodOdQx&O}aRLcRXnyR( zYd$g!0}R8`?pmdv$Nmixo1)#8AjV<0S}FZAMk|&C91CKb+!%79y!kXeVCQWd|CIu!~s|a zb#fN4!QZ8fqm6!PhXS)oeTm1%R!3r6L0a@tY^L+z)(|!)@*J5tz77PoOG%4~$rq*? zpG!Q!PG(1%HMR`whO!rLTn;%ja$|dnD7?SKED<+k;Ug9 zLU47?+nZrf3hdg}9WGx`^AHDh`krITd#^LJoLHbw{wWQr=yf_NlY%Gbs9fV;64N3| zEndd1i(KzML!nz6$$icS#*C%n5R(}2hS_fvZNj(K6yTNwSu16VVivnj;YZd&R6D3N z8lI>EBvyj(@SVtUFO7$L1i7wp?uM*~g6(GISIku=HyGjPQ06uIz3SZMX z+O4N8yB+Nk``%5N|2*LU=8%jzmtmxoeKB1MO!;-4Y1qAs1Zq1A5&agyHIsw@VD->( zQ0(o5DVKf-HU5lOzrBqo9wt=1z29lpG#5hyQEo03)TkGK*wsHj zISW9vkb(8|NM^zc!ssKi969S7%4QO-KSyoeZcGQj&#oeLIaUF`Kbd0o0$fGRaR$BS z14?ce3MxVywkSXUNRp`($LIFY&YV{bZGX9brMulT!IGIAT`JtYwZ)$Q{d+-Ln`$qOdmH$XTnf!75?T}OwsETjP?#WQW0UYj zf){tY`%DOLjXia3~oDX4vOJV4x=q|R+EuG znU&PO_b6!SSTMT1kSk%GVDrrVc6}w3zc|5{XQcG()Z5AtwXW{u28CeZ;#>9v;C)bi zdOW^P3slcD`xr#;@2Ko3{8I`Enn`O}CzW(>89aQk-l}U@4fg@y15I+^ivE$BnO79# z=leCBrCrSMo`8uRe)B3ChC)Jkf)4U1K{F6hs5oxcMkhq_n?RrdG<=kKwjrA)*sc@y z$btr^4?qu0*7mh)cUrBLT=l?zov@)%yrlLVBTT6K>Z~cv?%RzC25YQC*ODYEJgjbO z=}=;6XyQC5Mdoi{8MrRp5wDCeEPfcRW4(EEcp)4!Ir5Y(o=u+Upb^{0$X?lQ8Lqh*WP!Kqo6<muvKD zUfX7Z&NKrK0miFI{f)o=vF8Im&-@(b;eJ2wWV1{-TF8{k=ZJ5qCmuizJyA~`#AL*F z{dlnfG`(e1Bq9+2n8}oVeKscpP4>HpBX1=B7w7z+sWJbf4~DkMVD%$DJx9o4iX~4E z&wE)!7Fe0{boKjRE4$49p7xMZz(Am*n;P3a6meUz3K_luR&b+xZvUrP^$VjCxb=cv z)F~=^ek$!oZy8{O0dM-Q65yvQONn-bP8W=@!G@~?dPhX-l#Wd%&WWP3o1Vb7LdXR+ zUvK=ZfnSV$#g|uIjFi5{=3A>32)m2_kF>9jYqER)*GVIaq@n^!hhh*48%$9V5Qb9H zs2~kWOF9)$RFqbM0TPo==@O(>I;6W}bi?lq*#I9OKhO8|^ABI+-W}&$*ZX>3b?!6$ zQ+!@OSO{aAn_m3tAXShBf+ZRQuSCDum|m!1X@n%KvT{;|VdJ>4>7xx7_9Cv8UvZhm zR@OzW^qF?%f>^p$#rn@Ul)_Oi%3x56gvIB^$Ugc7=EIxm)#!?e^01gJe*h z1Bfr*h9&n$NYT)SO<5mpdW<$2)GP$AVl3KP|JtVWTis^iMwgJmUww~QVWCTqq!;Ux zxWVPeL5@`Yg~@F%IZ%bEp~SYPYmF|Ut<~_|ajUEjXT|E$ z>q)Wp!`052a_`a{;>COp!dnF(>g+22aP8?bw%$9X0#w>FGgOzkwa9n~n*wT7$cgSm ziJvm_Z%f#V2PwhaK~hx>YAK6Lz5Zun=MM;;ak|3-x+$Wf2hRfpY0DwIHPVXK0y=Wk zQ)G)nH9C`t+V7*prd@8f+d#6QFYZ(%N6LjfbHgsphLe@iV7HgyxgUgN$)1>kQMA&6 z_jI{2{8z6dKVb!+ppY_lmH>J-m()w4A_|$2bOrsHPwzNd#_aWGWDj?3v*;EeI}7lrG|l-62#~F&C@5l4wem^ zeoSLP!{*rCwMNB@YL zS(1beUQ`_HzZg9D?W&r?iiCC>A$V`k(FMk|7eYWn$t)*`>~HRlIwS&PW5duCULZo{ zt<7g+=&RPaSI28Pj%!u1W=zFwETNC*&zwMVldlFiuQhe&ua|Dro&OU24Mw}Zs z5ySJV*;;G5)+3$v%joVwv9(^~&P{2MDBOg1MY+dJyeHih5b`8B|2-RYL~}n^Tr@E% zK0%<+%`Dg3{26sstlQe0+E1sS#7*%|$JZyP^}6e%k9K=6rmQbDB4;%t9TU1h z-r4Dg2biYjzAe1~Srk!I+Ez3ze^%BiKYiii(v|q*sATe^l$@V{_%6|xq~cLPLP-Ly zglt_2pCX{6tJ3q1E}f8-PpXtxG-}(2P}ESalGzdxqHCtXtxzxK;QfQ5&kN+OA3jA9 z5TNYtTHJ~OcaCvAyQPN5dyrqqNT3vrO%0?9l0dRjG2<$~B#S-6M;aVbAJt9!1xL65 zfgixowXs>B#<6cvmbWua?7N0NU&M4vPcGKVZq?cKHMNE+B zDUH@p*!zOI>QC*@aTs_Er?j1>qGW#S55}bF6)*m4@L0XVJAj}bz06|V8fS3AD9?wF+%LnuLb*VV_h825bl)sVX>NlV z7reY@xIJU;)65u!Df)umEiJmx>Qg}3yaPRsqYRVk3x}y z=RnO@)oCqG;#9&6XFdc_!H8o&cdqJJ?LmHb{n4q>V0VK39bzk^J}1Zip!77jkqufW z5`kLMP2y0m`AG^@wCtX7Fb0)n0gab<%oWUoN4Bmk`t3nZfMH~%o3rA^1s^B}-GyAtqjgr#`s3z2}NC$_xfZvU6 zeD3qLMNFJc@?c{>SJ)XG-f2|9V=wbnMV2d`!&sU+iN?mNmodv$1(AAtH>^ohAGz{o z9}>;j9e8b9Rm6M2k?L!f72sc_$V#pSwObGbAf8tH&?L0= zFUH4U%mFw*i}QMT!BGxj%}+VAJJ^3U>2+f#Ov}0VdpDx+IH@rUIF-U90Vfjx*27Vc z3+k>fwFhnuGcgWs-xbAJBrAUwxHiMdX`c&$w)bIU37yg0F9zASfJLJe0vq$WFv<>5n4vU4 zA@i0>>qkJCU|!$0F|}tel4BAlwUA~=C7>u4Yda|;>j4Gf*uMFS3d0QZJ63fmVsn)U z!SU7;IJ6&*EBIV8f%zihCMeTWMorya*7r5CYPv%bf=I|+maHF$q%q8GMZ<%Z4 z9@SuC#ncP?vkUy-;@~c@AdP;^%r>+xrK0rl(scZNTA#kQrj|HNXCyCcOPK z!$XvT>NWcVC4Q-N`~%;fJ$twZB(GdjpgimWL}?&wZ5v%Ii%^W;!uq)d9I)BTz zNMNGnVyfZ3dJCUsKco>X18X0VsLI?vlVtxbc$dN3toSWoMmeMvawHpY|7nB)j)67D zLjetbJwJNKc}=Oo04lbHIc*(Ckg&+Om;>{T)t=$dZ<1Y6V)v2g_0_=Rz}9oNhfmOFv7NtzBL*ZDIiUP( z+qmQJym&?neG(F4Xw`s4G>_i1-WE(WFQDiChJ~?Thu|vP{9rkTQFs}Iiq=$CQY!K# zkFhyR0&Bn8g9|hY3JAR7WrK?o>ps73518<@v=*NJ`Yf@I-S_S!z_Pug)L9WE`WCOZ zBz_LBUH~h5#~WVCECq_Y07SX`IcQB_RD2ViR3o0R{wpstxO ziI^243DEDUMhRNH#509zyEPr9yly(KM z9-C_HM_KU3!mgm8mlF)sBWC11$*XJN;rtU5Qg}{%T#ySeHeu37vh@h~p=||Wqb2hUY%4@VFx(sHGZ>a1*artxjO(MWPEuNTQnoZ27t1x~{PB3Sc8W zk6d{{T}6dd1~$XHc<~5(@0$^z!YCmjy=|@;x(bfa3k*^+S7T#vZ}%#^zCn=s1lYhK zr7y>D$J$A88P6#sTA|PnucSRlCqnKcw!%VUOap^e_pSNzW|Ir#Ba>#3z`uPDyo879 zmJe`s)?ArHCFD;HU^u!x2+{x*w-A*oZ(A72D+PeI@KsaUtxKDeHaReG%fxn0b%KPI z!oIfl6Nm4RDG!Dw2!vMlDrv$}T?RZyg13EYnHIc@%en57#?3ARx#akO{~h{kC+_yA zsoScMGeEMA&9=lO=ny6I9J#6t!kn_ch~W{9pVJ`XK4svrJ!NJapfm@t1t7?wpIBJv z6%J=+8s~R{2sQu752jRLN+B*9I6whiBQ=33+4)-F5)f_vf0zJ+K^9{im>a5aentPKFxbXAqY0&j`H zLt}#>#iDmObUPsD3*=$0L6LWdB)2j1DGR{D*$l3`53!bXF?;tlAm`Tx(D(BZbbH z4u0z-PbEUlPvr4I=P_%>xQhX34r9qWFL7{SFohfy7`WeiO5)HbcW_Kmsl@{CSau{` zTXq?TL|?hL6?C|7gLy9`7X(^!+=Dqcw7b_5id1J>TUQ{6BQN)Eu^UhllEh)U*sSQa zT7G5x+Ss=|V`Hr9f=Iv!y=I)uhh78)PTa;#1AE#28(l?nKmq&hqkNDHI_{~jB77Mgbo4B2~g+Aa9$ZZ)oZ-f47S4SnDPg!UFCa<4hIm%t5IQd{EV6?k#z|h(Z6qX8c&yusn5o?5ihz0lY z?GOj3PP-I9SA^Z-ayAGWhlh$5++f+0Mq=MY@#@(Nxd>8<{>MX91SD-hcGD;EObjY+ zSAapDjCa3?gLD=(E`|^|E&^@1i~eoSzyaCawD`e}oExy|9$zrKA503>rP&eEnbT%G z8nx-3rqBN#A5|oO?G7j-90X&8g{>F^!!FH`QSjOldYOK=L%nAJl} zkh&R)mCaLtGZN*I8*?_!zCp=)O?eo7lao7Y6(D9T;3R0-hY;I%swZ)lVgVof4Bgbe zXl-lwRTg3J{YRjb+g1(c*G!r{0b_Y&9Lw=7&^o1Al}8229Q(Hi)5neZg6=fV>PR`i z@+;pckR|NM&Y@(ONI zk{`*I9zc9<9^Sz}003w#0B9{7B#oau&WQ*G?vY}=9VcGegw66jT4B>H&H+16vxm34v_^0r}L?FP5E;XtBA z&$P-qjyEVLBc|7h2{&ucU664N6l0vsv+%lIc2aPLuq9jMxbvdU1t(138FbfnN$nyC zT$&BITv4@Dwo+`yc&dwLe+!Bs8!N0oeS>oM`tFCa@qI5|^lk($+7hhbeta6?2oc6t zjvvDV;P%a2OUplxTzV!LiL%-B>1~;d^a|6fYjRy&VFH{PK(8Q}W((cTA$Q0eTGT0o z@-tEGw`_LuoD|Zfc;;u$Xi|2a!Y->;z{YC%vrYOwj`?hmpTPL!NAdW{hvZhuPt!>Hqr+QwYcGUQ zR6Rd&m@Z4iq$d#vDb9%B%Ram8))FMo^>IGE)~wZxcEXEY2aCQCJ12%2UBHDffQWqw zTZ&{~6sMejLagY-0Tn@BBFB5hR@OUc)?JjreCGPz_8_9D(H0yT)sUuw1r%q;_-Vxv zyMlyv5F}Xf^k*C=jTOo#H_LansJnO>5pZE_+t)PT3XyLf{zb{_&kOwY^FWo69=ULu z?^(&~&ts~@^T$bv=NW(OuwlScZ_xl|QH;(Twj)EvsPqS?aPmYRBI5;g__}$RiwlW9 zbo!YZPIn+!2gNCQN&5Kxb$PbK0dH4R{i=6Y+p`IUU4mp8TWP(RfJ+Bb;5UtRTR5Gc zJ1qFysF~jaPt(kmIX2~naP1{V3|JET*l>m&u6-E7TmdMM%nHSUN<0 zh$}P16yca8j89v&UlDjE*7fr_**8&>iu|ftxUyn8`dz9slG27vg<(682;3$74J4>g zb}rYgNM1tEKIdZ{-tD>bZW6C1%-ZIIy9VlyLUANFrni$i?S%QlH0yYf9#^v2>;T4P z3A0t57*j}V;Maomaxl;GI~k3`xsZpXJEQP50^Lqy!q z_wvKlyTZm+fKZdl-byH8V?d`1>tbr-YFe+ZmBUC`k2oGvXK9*G4_Ri+7q#9&iwii@ zXz%y$U^&DY^tf7n*zeo3ZPn4-Vjdnzeb_5?JuQxHtUmhAX2%bh$zjCrEs|S12#PWT zWLkB(&m4lY3zXVXwMW^$fGik9@nbKJy9baJ*(o40V$qb)N@>&2oTW}_4Ps-1CzE4u+i7D z%CbgQL&J`oLIHs>jm9~k)Ytj@yXr&B)2;hhMBv@DUPPY^)aE5DSDQ*S?Y z^XdBfVFOF6k?@}LTLXa4ZFWNsYWyQt+HCVLt945ep_W-J1b1X61SGFs7BGXZxY%uV z<6&lBu=Sz(Dy-C&WR}W-;SI+E4SsIGxwE}}7U(7Hgwgpl2}^CbBZ@Dle|lSBK8C4( z9)zm>5OmS^fXTLGsSgfL**=U-F}^~c?uYumE3PM;F^y^>t+dRg9Dnnq7%)|GQT;tmQd(KCUOZhfsS2O)ns zm?GWus|mV`T+=`utwpM2aEHuxJsWR0_z&>Y|60@mL)8F@g^lMTZsI$FX#5R+`%os| z)hq2^RO;{KtzrJBT0#&=ABCe9U;TF7Hvj+QA!Ran%x|?FcFkEBwDLfc@5m16{g3It zjoB=o*?}5{%XST8FXT9OXd#Q+ZSns(#xiAmYAExE#4H3`j?(zLGZFt^L(9emc>gFV zT7xv*`q0Y%xWNwT6~H&Gu_^A?+Jqep4t#g^FTU^GaCuIkV_wU5W1P=*X9Wdk=YM|O z+w^}mHxRFp?qF|9EPIEdZxrpu&@|Bd_mN-;|WM8LCZ#S$*5V*`%+zvr~j%)Im17e^bQLlK@y6=)-&I* zWqaMU6ri+qzoGddzJr!{=UBDJ2vj#Nb={Q=!W`ys>NB1&)}pRfNtDQ=G_nhsx@gCL@w;oTOvFDOiQ8j?Zh z1_yWFmNTiqC?U`5yOa6!3w05wF1_GiOg%$d`_7cZyvm;f$6G*3yNc}p%is^Y{{^I! zpbJT`TWUM-^N(8>$Ps=xJC;f#jxhL>q3>QQ@ox~=aaVEL?PV;x*o+F>!Txv6{$q@1 zEVpY6zgGt39s-fM=kH@>Y!4*~PAIpAvyrcA@VK!-b~*Q)tMWWW2nnDpRe01Q-AJ(NXh-{;#{Iy-asoMdha@MZS&cc7Fa(XlI=I3xOdG$rS`K{Gd#x zwnVRGr_#k$cV^cu0FQZzrPVBF5B^UoJ7c{W7U*rY`*zd?bm_0JYfoqTjk({4iKyK~ zqK81;F~z^6W+woE^|Si;G?q6n300lG0gO8qZ`}W2Pi^~u6(46DoXXY?TcBF1jMt&j zpa5+!FR#&&_6L+ZkyoPGZP4S+9?URB7tb0m5)n;1`c}UK_tjJQd21V0 zkh9kQL5`quK4LaUfLfmUErRvAtzC^oQ`Nl_-Z{x#h}QlDsXPk;)Gz380+g%N>ADXtVn32uv{(}{D&50GXcuCsUm+ z2~L%jm31v&#C8RKH;I2af~@(s<~RH^!H(WH&svW(A30Q)VMl)mJmsw${8z{OKBK=C z#jZGH=Lia#$+4V|O4?fXx4S^%3NtXM-<5}ZY*);|JpQz zH0|enjq`RV(hqktnd~nnGx+aJ_LoS4(|eQ06akN2jy!eSna^Wa#(L%eHeqGUw?PGn z1)ODbHWuF*jsAxIHnl(2dMa2!jkwcLQ4mwl6tL^=aP4CRMBsP8iJ7YY9R!Dc|23%c zxdu-`b@&^Cj(>oV*}N!=9w{5>lZcTz?3cN7MDj8%$y&NHpaEK zTISc)JV690IBYL^{qs~e2ietuU)@nbXRGQs+McK(fnFn?SY41PpMl-Zr=4fYnCWDT zs4~K{eWFTNOG!|h)bwye$;1PbdkX{mTIMYs4EJ&UoPg~^2$^;4mu!xb2l#MfwBuww zfo(;BH%A=i8kA2g4J%y!zYGprd3ieWt2?DIE0rbe2$o?VN56j-$oPJdd+g2L?LODr zHwA++2VJU^n8s|A*4z$00|n>F2f4iqqP+11X-ITQ$+hO@#p(E;94FAMW2{4#^sf(+ zP=3lNQ2N3V3c~io|0!$_;n*FE(V5%p>I$f5CfYT%5d=+~oWkW2%r;2YMIQw;#@iH; zX>oA%i5d|KbU-XE85Zp_z)AN`R&y<;>Q$jIWnph#ms8vAXj}aSKTqjFU%DSN71609 zv}LLChm2wp>t5(JT3K@UJb6$3%Sz6qLDZ}qsO-3#yf&P{O4p5z%$lPJ+^=7awG%(4 z>0c_H(o3L^FQCnG<8*(*>>PIDYlp~T0i}~ArmYMUw_q=CEFI;YZF0=QIT330YES()$+N)RlljtcnPre5+4Ml$uca;MFN{M>( zad+8+6y->?ow~bpP3w!GRZD6Yw>17M{G&FX_v=680TSNY0{It2!Lh}6yUI_E@n?jn zPm>oq-!>;^d@p|J*Vw?hA6XM0UUfCTeU{lc^`^ayPik9iqNw0qM99^ysPK&2?l04y zP54yEo*+}gN>7$F!v#fl&7?Id=>jSKxgnDz-`>O2?pL8`v%1|Fomf+Uko{Gj@D;)j zM5wXYDb;Q+c^+7?)QVwa_MP<0&0MdZZmMZh7EOd7+)%X^DG}NUr0q^x^ZUF9`IpwK z>kUfFnv;^0<5F8s2tPt57Rb*eg1zI6n-gUZd^gzCVw{w=WHrf8{-%0M$q%4$bs zds8dq&C>BmEIDnTK;C+P8-~o*1bGfZIoidaa@nO+O_kEyf%A^9aV114@oLdAyiSyV z{)85ckC_{ynV<}|gc84};)3?^@u?QX&vOq1##bGLM5CM;;-CLbY_nR(tE$~mS9oBA ztv==1#LD(2OAWC$Me#7ZyPn~CkBXL?@c^WJejMrU#j3J=Rd&cVE4u@=Az8ku15uERAvR=R)~NCA*Qh#+c7M6B7YkeFWcbaSp7r(h&klU7*b?dv z!^9iSKaZ)>>NDC_)F#$lpn%OO88O6Z`@)t)AC+(5F}t8*&k zJ7fvHgwjHco|;?N6RPh+6uDBoTy{B3x%qg_kWZhk8yXr0Qa}(nJX?f}yZ+z*)DMP7 zDcPugQbaw8ONStMI@FkHU5dZ7zSWr5q*vA3oZ9AHLd-}UmQ3%}xX-p3b2S@X?#!5q z8#+x2HM#2jJ?!LTxG@Po1@z}}+E*=tf~K#sw=w@eJ~5XNb_5HFS-PpHwi^U0JsKIF z2j(O0f1_%?Vu_Z4v|0o@6kC?h}aT#(7f+#O9_wx3pUgJ4?5^J81S*oGnwSzJ_ zNlD2|#P~~bbJ?ENZ=YHyIU5qePKdN_s_T@zaqrd~UiTS^3?)c4$BFI}k*ToX&ng3&3(g z%jt1(==Meb;}bvi?QmM~0zj>NqRjgo`v;#}{R8L{-pXhtkZ8F5K4mb)wPE0HRxG|A z2f!1f2B}{ayH6?JY*i1vJ8z%%4F%&#T?ln!B8~eWtWf<3J8_)-UD;wI*h7_gBwlos zy4Z7ag2-6Sj4$~loV?q4f-LQLsfTaU^2g5zkl(HZULXTXe*R$(68-7sIZ7NuSL-5+ zU(&mkJfC;I!Hl8#sJm@ae}KaCXuLU|k}i_%<)5vmehtWg(_%YTSRu4KiP7nl$(r*E z5I<;9?5ljpmdqeE_cGY*gT(`DEFZbSYVhd5{df|xl-%^WKY|6v55E^SQPt0m;Q=e? zxyzRRrWp9>FP|&f#dYP&c9a7bAtZr*rjDjR$bzYVxv0*tmmp0RXkTqGXQqDZev|=_ z_9M2*!PV0;-ek9G_RAMXvfH@erTGU(KO0zZG7h8g(HHf^BuB}%qFxHo-u?+w-(L9Y z4eq+xS3tO^hZ7ZwpgDT=jRgqpkg_De{}C{t@toSuiuK#3S-P%Q{~%yImr%+fZ&847 zr&>M{h)*gJ>rj4KqW&8=f&eupI_Abs+}fOU?`aG#c%`l@>iG3zn~qo>Xq#W)2s!Tj zp%3u7jSy{?DJI&if%2b!l_-KNN6gG=$Q(y@6W3hO6Lj|-aK1N0kHC%_Q-2;&Czb&8 z>HLES0i<@87wFTK!%YqE5Psu~-~SvskMnJ0XxvSwI&u^*oQnV%FunGlJcn#YVvh}; z0ICn70TwHHLH57S1|ISPK2sp?=NeREFcc*MczvQ?*BCD{ur2^P7iguIaHT6M#XQgr zpvCeQ&=E)Xh3#M%uib3`?EHkhj}urjn81+(0oz)PBcrr6^ku(q{r^F&)$Le+VI8-{ zF;>c9h4MD#){bp(c({&q1P#5S)GeteI`<>W9)I(RKlqJg>+#imz|@M@Q>g&uUF2 zrVZT>IutjIdNbxH^Csuhb*7$tK7@FeA%P6E?nWJYGmH?~uxyV>HdOH(Wofsqn)3R9 z>dQ(<7aq)8f4#a=^G1Wux%nY=e@IlX#zM`Df<|Tl^m)Da1@dE6hipLHy>$%I> z9099CW@lM=6j64A^oQQu;eaHa#>~+h^(WV%jAsO^vW&r2(IciNj7_M0QZHk4mM<+_ z*NEfm>9%J~4O6#TX{6@P>FX=iEmt1ZkU>3~yewID-MYO=Er<^~_rA@dG9cbRc>N`; z|7!|+_Cy#pGVT(X(^%Cx5WxhqOn~TZ`8}XDC&5tY0i1RIfggA38=gS6J62(sV*_5t z+uF7O4)62O%4appU$x5q!3JJqV?W^j5IIkTy7s!v1Lg#qIDYfMzwF@=3<3(b#L9f^J!krsVLI!)=iZVeUbVl?Yn0mQ9ji5DEADpYmm ztS8RO^ibX9Q8`BYSoF;E{CFut9zjW~pLSCXep0&KcS#8hS6i8Wz8 zZSeT(c?n1DYijvdA&9uvM7&Oi5D<&|Rer@Uk8T9>Dyu(Ibhd+{{_ZD_;Mx0i6B**Y z>Tr|@1K21!`=-kB z_Y%R<6PWtzfH!KM2oNkk0cvOtW`iJ-#3X|9in2ziqM9-9>lIJ=!!_Rf`S8+CDN8%# zNKkoG3-tLgTWKc98`dV!YQUxpdZxm!t4rmv>P@m9dCK9=aXP-PY4B?r;mF{Syq?Eb z<>&}2>EbX88h=WT7m|{~98*0rjU&ez+&d?0B&AH=g?;fl_dsvplS$7z=#v8Z8?xj= zcwc!%qX2?P(W@eRy;5ED=2@c(KFs-@^f&qJIcg_WZi>WY;~h^PpAWf*^ObxAS{K#w z6YIX+i|(q1uprSah(OjsjYn{QROu60I$J{o@CTpp62W>xIzCO_^bT{2=S5E1)6Gfk zGZzIcJ)Kg4dt#EgkGVNwSfNwtE)M%2J>S`S{EtZ<%|h!OXbWDy3$o2(aHHi|vd&mA!gajPpj2^$mrMrw}B%v3^+MZ&d%0 zFY!7L)K@=(!-xRY=9qxv1rffSdF_j{&7|=wccW&o3|S?%WZ-5B5agC|;ledSVJh3? zh1FiOKpxT;Q$bE2R!r@s2cS!m**`ci^<{W@?Lg3Y7C_LgsFq}fn7zK&8zMI(RSnMbq9=LAuFq*ahwW@>>ahQZ>8Rp zzFG3d)8|t5^~+Le;Ro7eV-B*Vj-oDbsz1I+w$S_`9jyNSo>G;vRXl;mk_L+QrdFSl zsVk_fH>kVZs-Xceu6XIkU**!mSIv*n(c7{mu9nwih#ESR4O=TKr;EB9`O=RJtYSFE z`5(T!y?D}DKhl^h`YoH5$y>g`1TSJu;gkl8W+YsTa_D+jIchMv^-UvKpldl&Gq%t& zyKmC3wkO&;2+9|zZJQQf1c&Bc$y(pjZ z3KF{Rb)H!2^5u_T*&+niX^NPLkEv3IxuS7Y0$0vm8{^nVG#UK`&3Y8s=(@+Eo~T+WBcPy8>B2Dd+!oV* zjfL9~q3+IyW}d~x6MuqF2osr)p%vOiwV$i`n#bYOEE4<4O|O}Nz}V2|IHUe}#VPZ~ znuDo!9S_gXzdb@f8Q1B*U+in)QSeLEYu5q0-VO;vqM61nsycz6gSQ-c(YY~6mHBWW z&^lNeh{YWz79@%c>X?!-b>5KQ`LKo7nTZh?6{w79^93~CdG!H&E=^fq-2 zNfrE#d5SULHP9>dt+$fJ^h)YSZ`Yun&nLZI-t*S$kfI-JY57?ilE8kR*Y8I!uQ9kM zJFcseE*gII_TjbiE$&&cPbE=!Qm?37=|^$2m`Uiyx%7+bwL~U*dUfgq?^x(*vW@{Y zNS$*_Tyc^9Gfw4AdDN+q5>3!9jumb*rJVgOKAC(Bk2RCrppV)sOfDbaa<)j2EZ$ zh1ukpEj%JXF+`C-1ymDVu}A|@(t7Z`-<}miQ<+j%k=vkT;`zO^%IjHy)DeE;4Aw|J zX@@6``n8X5g~VUBkfzgg_lCzQ7s;x+loulCY^QHGN8PEG8@A@^mO8I0lB-H!RNOiYdmON_yR_DM*(t(7g5Ep`XVW2L0 z8idj6azohg`ERnoe;e`=7iexp=6Dnh#rfy^iNrgV&*eBCY?XAfnmH7|GIS+GjKmU{ zW|hM-oBc~g(b4NXr=xv_i95qOPc-(BjT-~8gyO{l63ms9DZAqpGkM0yD3)w$$at4{79)Dehf`@qBq*Wg+uQOxsb|o)QP>i zqYTfJqpvt=#VOfFH)0fCv+DF4bVXf{xu!eBqXs42$KU1y0>ofj9`}d`4d$d@&G;^g z97FrdkY+cPIq7JXqPSR^4Lrr&cd7)mbHEbv$+v z=54EKxWBNv)G*qTOa;k|mS4q}? z%Bz(uspO>9xB6}6QszFLRa2HeVtZ4bsi|dgOl!^E{zqA(uo{L-aZMb-&KZ{*esVO30;;IoFk){V5!LBIVTpl&86A_rIQc|vTG+8#3Hf~I(AqbKu%z^1k zi1Yi36c10!k?WzZV?C`K;`$*qRo5Z<>m#bK2`8djM$EZX(l1Bn6zO!$mkvjWXfD^9 zM&+k|s`C7DVW$3!%D0>OwlN?kpsy-}=-T+%Sk44bj>$(_ydhn6)h=rGL%u0Nl+Rm5M_)z^z8J9o_<7KzCEvQA z?oodx;oa^G;7TL2)tgRHS`H;q_dO0Pk-Vlf=roj1sHhp7A~yh?dO@9No-6mME-cj7 zr0)|oM>*q)F$bN#f!XMduJV%g-h1dWkL*;jW~+{*gGOcXiMi?h$C?28auH(do#!4c zW%Bj)F8H}?rL%ng8cy(YwCi{urq{;m$vsaGlPXd2;@q{0rXM*fXU^Pe9WQOIIC6fW zrr=(@Me`)z`%^O=>p$jvJcv-dW4igBT5C-ZE$BU@4j@njd4l@@k^GBLBogR zpO1ofRi_gzc<8aXGTXBma{oB)XQs~DAEQ^^byJ*`O+>4wmr5W|0nK#7h`%qqAqWep zcvT*#;tP(mlKUhd-cq~Cjas}SGtZ%4z|}!2N(f=p7$H{Gi=qvhok2>7K=!U^$MWS9 zeOguRE+1@QR(#EB&{eLc`l*|n;-Y~s2jXs)(rc+k?pqAewIeqLghs3$ZOzo8BX1`0_BX@A2+;LqT6z=R49v{S|Su@y?9=0pM+{fgPHF;{;}{&=*OGC;&-VdJLfF0#Tc)+l~;Q|F;@I`rI?uHi77eE-&X2gzvexfQP<C2t-#F6C!&Vk&$vkRBh zPTlGnzCzIhU8t6b&nK=UVy&b$Lp8-~f71}kJHaPr6q@8`vdlN{Q#$dX)!c2~k`*)C zD6M61eatE_+>#MLJD{ zjrDV3U^7Irgf{Cr>1j_Tzfq}HC-fJF4 zk1tUauUrJ*8&)ZkAY~pHNJXN%y|Q~>oIQOsw{^7fVsdNh;}0{)X6^@PC29gPJC1_z zb@Hc$?tf*I^;2@u4MB8vp~hWgW2rsbuWZ|C{3q%@}(6Sv!Mkx5hf)AyCSiC9j1%c*ZK_a#u1{dJB?C}Rh8K*^IPjJ%aoeh z0R_)O+viXZ_Nin?-@cDpzLNdquIXU*+}dKn@xBoe59`+c*`I0y2Pk54fDedctn)k$ zQepEtM=_K08Pr@D{@$M4{*?rwGW!I_Uc2E+Gtpb~7kQ_HpMKU14z%lPk8g+`W1O^` z=f9}e@-)6TT?jM#wSBVEn}Oz~Qc6LnqFoDcHmw4E*&8an>6%ksjIF+%3yeo#f(XCU z9Yc2C)4*Pn#fs+r?-7;mld~cnH}x~0p6@fO3S>W`!WHKM%C?FO*38UU0D!mZxm*X! zAU6W??BvYNx!GXOK1#Qxz$!B(i|82ZXknEyt}dUm_0`@)!47$bQ%wrbS-%>%!7pVM`@^$tHQx#>^WPUacX374}j^r5N z6rO2p4Fi$O_>r#V|4I&PDqU-jX2jn}fQHP8roe?jhxsqlB_8ZXTUE_x?6B^bI2WtJ zHzRG0WhNGVlN%pG#oBU{qGdsro2QYgd0J##65L`rsQmeNmVM{EHTE%oN2I{Zf`cz8 zrnR3)ScmnWIt+b8w37!2iQd0WLgh?k@l7rwkrJ|iW(|zf@^o7R@Vg0dTO!_UyRm~7x!@Zf6eaqzQaf6m@#xz+v`fO|h zk`t?gQRnW~hsXuDx+_9NHWnqqlht3!Sl%<@!OX5m>s3PI&NV2d3{E#Jg{K|&otYQ4 zj8%VF=^1wbY|@7X254;@%pnHVbPOwPf~_KzQHST4r-(j^GdpkjmWHCEE(Zn6#cp~E zE|oLo2`3^R6QM?;1u)?+`k(b(p@5pyJMYn-V~Z&X|H5&qG<=Gkg6mOig?wGLx1%k| zgQn zf=U!aOU_Q*S`Mkn4KXc`2DKcuV^&xQQM|FXUlI;_1v5R&Z=U-g^k%)w(Y~{f!eFiH zi&2%ctf-^&qZ#?2@y@li3+2_A&InbR%DB(8w%l!Uk@=I1R!)4GL7k~)WfsY@-Vi8mb_f~L`+Va zb%USvGnS*J&uMIvrHsL}?Rvj_Z*-;K?{hD51MZaCdAR(CWZmV%PAa($DZDjr2gD|W zI7XW?HZpaKKm+0C-I=aN5@===*bgOl4=o_DAV6T(20x`4rqDP#y6tC*V|qv2H>y$c zQok4t4`P4m$Y46%ilF6=eyYVPU9;#BVlW;nP5e5 zytBYvHq^d1jC}s<6V+W@@s&a-El%gG3B@KyrS5^*mkXfLk~%XflzV~?*30Ns{!P}` zXag!^-C<>0Ctuij%F?`5k?%7@Va8Y_i%x!#4Vef>MonIUx5R-D!m=I#Np7#It8XW& z2!thwYK4WynDzTupfB7ZX{8^ns?(T!VV0-PaUPxRX7=PhwRPt;Sz-I+=46AISWJaT zg2Smdt5)$o2JgPb6&rLTdCXwvVk^8I8|LXt#dvx0cq~P(rga)5sxSE8W92Y=JnA&+ z;P~Tm=BR6v8tIKkecj4x2idNYK?(OsAUbC9prL%}f@}2l6)*H;tXtWx9!#T9(L9QR$R58n@E3uO$T0cA&I@N1iOve|>YB&&HEQoikbjt=U z-&VBVeDfF5x?P`|U7Q6AfQ6~0B=&sIxBFtE{K>3u=-9=%9Hz=Q^PQUH;{2|3FNKsV zVm4Hn5$y|?cxLDr3RMgM!_!9oK+S8%+z}yn(6ns-a>vkJp37lYL>Nf&tR5V}SKQtJKWUNDEFr+)ZEcC+L! z3XJvDB>q3f-a0DEwcQ`z3W$J+qJScy)X+$GNC-oXbccj=*AN4u5{lFep>&Ucba!`y zbhq@-In3{2pLg&5p5Hp(_nfun57xsh{@}i!>%Oi}-3X&cHOev!grE&10Vw4$%CoGY zz8N7d_6k0tn&CAPc<~&52QEV8-L|!;d!T`sY8s%co*LZ5f{>;;GmGGC^4wjG&F}%r z@=S_3FE1)8;}s}xS;R)5B!v>Y_(MwH*hS#s=CT3 z8nks6#c}S&_D~Q2jHl1&-}?)k`_1bwSYpw$Jy{|-?)Q69R&wi4J<w>R=8RJXw}jy=4Wp14~2A)uIOzEOYLn(TK-?Fl=H?E_0r*CCLZhH=cix=65)9Jj0nPSocxjN*9CyG4@JII2hb;p(gFM+ztI z&Gt>E@$5ixbw|JTpMmb}o215#5H-y6Rz7BWaM+7QZT-h~T~;hXUNZC~-0b39JjTP& zfVINl+~h~YhI~fj$XYW#NKpebyPjo%GcM}f#EaYgwnOQjdnG${jZMCY8%AUtHYWHI zWI*M)lTcWwidvZ0KXdqwnHyes7qZHjx8{C`7AGMPEgu6WWM+PSA=&MLV!6DsMzxH~ zc}%C%LC>@MAw0I0^LawJdz<=|W#Xc~U$$TRT80<^VNVf*{t|L2pxcmt6Vl!l=Mm(= zu>-WG)`%Y-_a}~yKf;AAqvU@SDd=t1S8xs`Wvg2tvNaG?BhbXvU^-W(rt3>;(b;ubF8tTq|J`eqB0IaaU(PoBr@yPX^LR^(QH)J2#o)BXm}}$ zwtMZp>v^MP;MMvYPo5$!e4|>p^MmAD+Bx;(yCOK|IyC=@9TxqI;6fV5b9tUFSjWZy zMAx~p(7LR$+vC%^<2vdA1BP)U6vwy}tL4GO)_V0Wfx9~|>M@h0b9*mr6(oN73Z-ht zo_yUAbnts#_m1>WuE87LO9$&1cb4DA4kw9WucA)>u*2$vduMvo1bqXGs$@2msonpz zpsUfd5Z4rnX?S>WSrd-PFuRTQNO$LUm?cweM9f$iH&>#_e}#3X4ia@V4i112_+NC>;)M?jdqB7*G@Su zB+6-Xq!z&KZV}SjTg~N7Q+c8?xy?~1R!^B+3N&y%PZb3%F%#<7)E>#R&UA;9kbH6N z`$=QX zz-P0orfV{GZ#)eMswW^8dT}e@3S*q=?bIHCNMS{ajV;g2bGa4_O9Pr;2 z7#F3D8{EnSvlt#U9+NuaDRML97LJ8W_pa*-%|8Tj^aEmDWtkb*N~c(ie(4J67i535T(51?lg z1t*>Doj)v?y`m!s(w1w@LvI3y`+SRsbrFl|Lf3724^d$$)sd=<^N$J! zbQt~WuScItO*k9dSI>yA-vf^d+BvYyykV&NNQ;?#qpMFpLR8&RRy~l>h2z{J+EH=p z-0u|GEER5z;*iC9-=_x@wQo*RD+3qagHto(4UzGF$C8I5i8~*&!eSMCy)7r|<(8Fa z(k1Mx3$7;(J$DJtrwI#!5}(ZgU7^r&r{5=yA^arM9<}~N8qiiQ?M^w1(Xh3eQB6|u zrD6IizaJV5{DrsFmvw#^0t@+*l$cv>g|FglrSZw9-rD;k!{^Y3zTQJW8Pa+3ueU$c zt$gD4o0u31D;v8PXvh6#*YEl(DXlcuH$Z99#@sXXU;?-%3y===K7xK}5X8^td+$#;B9k~Y?D)ivK?x^n2XRQchYG2;q zj4lRb4nhXwCeQ#(zM9nT#eO1|WgvxD8^?eaeVQ~-zJCZGBBU(ooEDgHWd0UJDCW6D z>}mzmoCHfLle!mshnBd8h2=3(HdMUESExX2?gl*RbY1seqV|L8-C+7#u9V(Wy9b8~)2D{6lqu2ukU1mv z@kES1i?@u>#dS90X?F#|cvaYGkSrv!=uu$#1R2w}Upy#vtu2HaQLSvZe2(tdNVxT4 z{UIHF8K?GX?0h`~=6hL6ThZ7MPc4AStCeXk3Z2NFT;`jHK!OpnM#2?RZ)u;I?|*PR z=t&kGa+cWX>Y`rwI_bJTTT#ozA$F`Yl0SIyP<&RSzu}^B(A`^e`!p$FeukNK@*LPf zL70Oik;{dk{1&38k!(`5S5@oo!+~X)#qQ=kU-UnN@whJ26?7ATRt^V`^6n=251BK|@Y0&VgZ)X<()QKo=1{= zCu#W3JNfhKV|dCfdeB$1AqE1`Nh|mIFko}okY*LJSUI2-I8s!Yk;QN1?d#Po0WUAT zO87_w;?57&GqV>U0yRN@5X-9!Hg@F!kJ}KwEZI_2lr1wAyUsL5zh*1k>Haou!_hB_ z(Sgs!!0uTv7&*Ipg9&T@ttj-;OR2$v)S-R!|6@^_h{O5U=xi3+i0I^97X>B53TFg# zO9=`@weO3%ONLw<%VrezlT=VKc~cM9znnBjiZYSTIND$a){mA>bdFf z(G%1Bg*0f=Bi3S|LA^mZkA#|hijaG~6BlDZJabQP&IbcA2Ux{V$7L4FknuX^ll4eF z7Xpzo&B(5utNau6;f~P_<2dmWJE>V);ZYDm0RKwpU695t?2(c?coG=Y*Y488gYD#VIPa zcM`WboI-eUI;?!YFrZs1UG)mGE9e6B0>LL?>%!}Nh9!Fi$14;%eQX37f=xVYKlU0tk8YA; zLabI^{EMtlNxsXI*Ju_lO_JryRU~rfKMC+sZZw>Cxc)^==il`DziNy=%D2#9N5>@% z>G>lE*7723`eEf8d9KqJ;V0Cz<5y1BA^SiZeSNi(&Gl~m#lLL@Zlw5&d7lkK%8f;> z{+Qa-?|DUT)tJbJ{%8xTZ{r(>!p=*Av3ne{$ya`C${TVER&5>uz$`&j@uWqleUDGo z9+o0zc>|_*Q8xHa9#_&r1{dZuLYwOP0d7sBU1;Hl6W8HsP?cd#UjO%zfX$?b+bN5EYQ zszPF^Vo@uH8)87)G29GEQ!q*lOyc<(m4u)--{QBv3y^j_tvYTXLaxatddml?Z)?k-+53`2$oeF{@`fABkd&}7J;^{X%F64-{vQoe{~#033rX<)*7^Y1 zwGGV(^ve@6F|>KhDbdH_JPumeeuFTJNZtMw&5hWg)YnvHrldn`y6f13YQ&^<3=5(g zy_T+U@HTgRk~aavM$8#YFC7`i)9HMDNPEN?LaWXsNc zai(PLXQ@o%VNO-Gr=T*f*BT)a@yAYNGac9g^*NXa;Vp_ z5y8q0liDUQ<}@rQ4!>y;lc}$XX01npy;{z&%s&ejcovg&Gz012|j*k7Wfs>Tm)tdNPR^$7o|TmHH|yf3!* z)@R+euG>nwtz89g=>b4ctm|CnHb#vK3~w0giK3exN5w;h&H1;B9&WVF+; zX+k)IyJKUw&;};KKJQ>f+p=x>%y40%%-1yIV7W@XLcuCRSe zc=z%?xOO)rWZd+q%-Jo#mYFp&(YLZ<#n$iBFU>ZEJU_jCT)3%R?Q%skh1<+mZrvNb zy_F=e%gW4+(K)DZD!_l0S!r3KD@V$7d)vH& z%Hs=IFWnLPs1eyX58>%cs&WJc=8(F2cT3UYwfmQq6+%lVYaZb4gAGrii=@i^SeF$v ze{3nLENpEQrcbmfy?zJ*A_L}5jK;Zdq@#I^JiTs47D9(X=JL8;sv&B?Hpt0Abu;14 zvGHn@v9)(o$1&CH@#-3StsRnH{#>46A&N*>PIz2i02sGO=1IEGJ47k?lpRgSsYyFS zPpPP9wg(RY1TeapFw`%fKWRHs@Q`DOhA4D&*wjVt~6Pz0NniYI!TDx1cZ7VW&BwJ zxbqsqreu-_c&xtm26w2v`0O-qWBEgVj1{V@$$qVxW3!xTY!-O0M``Fm)SY;ql$CL< z=P55328!y~aTX*=_=(1FmxR@CJP|7tHUqYAk%8*v?!tXB zF&~Q?e%pHJOV(x(_QJoE1rQfxs_ytqTSO-%pb&-~IWj;>6np<|wOimpm~OCq3@Zy_ zxR|+1?zHpw?0tkk&ZWGPTk$WP@=+Te--sU#&huww{A-L0Dq0r1o1x#*>-P<aiHa&$Ec0N zhPJ=H1CqK$A+lJI)7k5oUeBdRKs?!r5$ zGz5vJn+80N;b4j~qQE#hek)vUOmOwQ>N{+{xb_`+mUpKnE3rEMMltfXqoPa&DZ~8E za=}_bC15KRX!kGeM^3j9yL*%Uf%*CzbMipWx~B2(*Ajn@h0}lq;cKtTXjTP%{idI4 zmHGFdI_2`#yiV#mBoS|=hBJ*Vb7Jgsh|&ZQ+qxjj25#f( zix>{o;|b2_IN!__-zZRS&jyb%9UbwdF~l7Cky;P890BpRN85_o+7(q|D6=2|e~@T* zj3!=pf6<#Tpbgks!A`4nyLEnXshWK}Ca;>-`92E!Dc`%FXCfczJ%5AY5kexav-PFJ z&s@x0-np2B3T`Uw#dMy^FEAAodH-YT|6f#>!7=}cy|POH$w;NX5uV7TL2@x>R<*Xu zT&~IYk1MHy1U648A<|DJ)Y=a7nYIoge=jqK_+`M1+wxZa_QxQLE|^Py#3yXDq>b?O zX$XHe>VrQVo?`^H%gi$@*Xb8rdo^CyQvQ_jRNOCct6naV5{7YQKpl;)+pK7DUq9X8 z)7_rF94vBfW0Niay$1-g?wn6HoyrfruCP|5S=X-zZu&gHlvQeGgvs5?`t1FM#$26DzwI#k{N9vRI{)ZpdbTIJxoG|l11R$+uzJ=`5E*IQs3_CAR)y_r?1DTEs8 zSSUR)Kp8Dy?y$R&%b56$;fb_$QfT~yY7;ZB%WLRV{nXQ@&t3qW`&e6T0G4xAAAih{;puP%tx;@xHRIi}RvwUUp&0uXa>g5#RmQ%>tQt;=>6 zrT0FPZE_5Z44q0_IYnP>r+5SMRI-e_db^&3Va{!NvVrvk&&o?C+tVOgJp}`vn(!ms zOKSIRX~E45FkE;-^vu1hE4&9@FvkBZ)wd+QDUm~5haS*Bq)|BcvUBbHqAvZBwc;KL zH1)O#B}g$w8%9-m~vJ z)eWBO%@EBV4GU2=Uvz${Ud?uUi+KZWb3=Nq73_Pjz>%2f3_1ZTj2v`B3r{<;U68`c zU+0Tr1hcbq?20%h7XR!QL&qKGsHsH(KFib5F_MdEb-K3#@LlVBSfi6H@Ks<-|<)jxDhs z?hTGHl7bh=Ti-WS4b-znlUPw^+3|sEzl{x31TNU1-&YR!GiemM|D)cS*K)AbJwji>_%ssXy<|b*0AEOW3X?|8bK1>o}hF3~n`VNIcchO=sa zZrOY$W3ee;GnQ1pdTnMsqq=*G0udKOCz~Vy4jZz0N2nMAP130<<6I8BQG$%x#cB(z zHHkMYR~L9%To?plbbGZIZig+2cDcmvJy;+)<+(q`(MrO}eg|q*r{$Qy>Vu$n-TUI0 zR0*j%m`>?huYym9HTVhsVyZ}Cgz$3nAWoqq!R44YU)h=0wY*RrB zvO2Me%PW>gWFcHufn7`m*07UXBCoI@Plc3sVw?%ZM=|ujKP7D&3j@7DoYn9nw3i<# z6bKaX(gsHx)$8ao<5hEm!@J}lk=rx2N+!tQ6-o}KbKFfZEcbYd;wi#&i=#JE@F*O> z#Ql0{A^+#uh96tLta?Neuvgduu9VnF+CUtB;ZndB(LWjs{6E*V681}3e{BlomEBAs z-0ya$t=kf#$>CM22V`adq`)o(UFpCLdzj0$Oj;f6zR+c#A}#^}up~g^uG#_`sO)`Z z8mqA@xLV7^r7b0*6c6(|Y4NC!^>%=%@#VAFRkPT_+GV^2U%`znGy=4gMNvs_-Y`v2 z>+5DFakI~Ag`I7*YJLPA&g=it7BwX#Nnb*k%`cix zgXb-bo`=n_p1Bm)&*)lULHNep)-eiVo_^n_3zd@t>TUDyi+K5j%zL|IfP=Y81l<^< zIxerl@yzUBjLW)LD8PHu`PH6~+_0>(r{rwnHO3HN)srvq@^Dy`f2{>Ufa$o=f9qS)&-;AZK+)U`HnfAp!U?6@(DWYU4UpX8y;{L`j{ z2zc8*L((}&^$*TBS%h>Vy72i%apPPKPBYid<(!<%VT|(Pi8}N@j61>O316c`s}Y$_ zG1c!dfXE1q)fxv|P#)ikC5sWHu4lb#ewkn#Wv>kvHLS=JH_R3G6cGdeicojTgv9)0 zL`2br)7jj%j?j$n?(HJ=CN*k*1+VXO>OX^R#2Uua5=2eb3WQvcto`7qbD7zTZbZ}& z)cwVBOZYwdD?Q;zn@IFZ&NA_TvH2vC{Au&SDfus(&#&)*%|}1404FI1Rs&`iJ;uvE z-X!DFv(n7#_@E&C^%=9L6n92ej%LHS4g_87@?rLmQXu?W-#c%slDwk^pdhpt8vb0|D#;5$OZ1YV1(2V0y}?X457* zbh4TqQ>C3hozmw5JxxYEJ`$ThnCiuTuSC7iDY&hH>2x=d8hIJ5Cf>PWy8RBo%+E7d#dIp_rI+lc_Yh5S!pyWBl<-)Q4WIwsd*1UI?&qEBWR{gEe(PWS#gb_Y*^LFE`NxPtc|+PxGE z5dHPvLR(US6S>sK9SXICSYbhWXsM$gGKcig&rS7b5(P&We0uC8K8 zOO}ZG_R~mhdvi9rpT%BO^+c={O|St78Q_{%=hZANHA&t#2K5g%_nl`g5)JS~bj-5> zrNW+OM>!>efvl78lwy99Gjh%X;Kgm<q7z)M9wkc|ftJErJ1=dg0*`Q^HXV_Ve@vjabH(Y+AVs09qQ;D=zUmlAL{VfGg( zqftCZ#081B2pY6?z%U%1Pi6{)EtYj45XCb1sB_9;ygP8zT*xquu6+yFf)Hf&?)5Tv zb*WStFKdEZh=Aa9OhHk)YfCvx?pCYbV(iePK)Lo5TRclaX=E(|ryyf-wFtOyspu&4 z7O0^>*NAH06H66vnev)P!=K1S35e#G7fY)3i-E~~D$N{9V(9=c$9VX$QF*oCRGLfF ze8n2$*t}HVUU;LL%fs@&COQAPM*^^RtJDxky!%(hL_W6C*-?NWHy~O0I~?P0F8SXW zz~^4KGs)d`f21OfpWSp|Wy5N+KtP0JQT-EWF{pBRoTEpN14eTF(qN*yQ$AXlN)Y_| zEBUWYMnL^oOmMQg4mcaSlDq-${;C#t(iiQbp0F)B;j6RH2O&lkR3gQLUo`Oa%2=Jt zvcmLTdMjU$PYJ5P;g57h^8gaDLHs%Q{W`5Ld@&V5z8Mj*iN63zaH!L1pF?*D#P40M zip`CP{kvB;rp%@`HW@9sTfKkmPWfyjWv1z=YGo>?#PrdGoIgmoKc^UdPzp-yc2XMH z>b{5sS({g}%(C=MRstz4X+2eFgFhn!UIJHZC#0uEC(CVYde8~;gNp-6vJG6x9mhx}Lvp;U(Su$EL7n?8T=E4_+?~M+^ z+heXszYd$;CWRMgqL+wB?^|o@u7Hi)38$$oSfX-9xaeb1 zkw4vZoSOT-L9?Pxs1y|oosd2p7a+0car|CFr-#gco8kSF9{=tK{BNIP0QFS>Rfn%6!GF%}Nb$gq!%ZsY z+2&D}f>7E4U|-vX^#W7Mt%#1j>tsM%6m%icPsY+h{S!+MXL`LG0Oo0d>0L%77vPj( z=bivx$AtpG)aU$0Cz6kuMLkyl$rF6}-S$H#Dmx2LtFo)bI4!_++Td_FQGUO0Qw|u8 ze$XN5UW=3hi~j8FUmOk4Yux(Iba3`;y-YW;SNY3pz+iQLnoiv#`H{Tus) z^IA=>scoUx-om06IEN$bQwm|?q6gkFjcVR9g7!8*fC^yrR0eMKUF+P-MJV+cl7)LD zt^m$tJSNQzIV>q%N~Q;co64&6{&8$e`wlT@t?%nqd3~aCUeR5?GzA7NUE=N!;NwL) zQSF>BV{(&O8t{siyOBbKROhaW4FM6>M~9W@wx16f(Gj~pr-#oY@?RmLlv0@Cl7_Pc z&SxJqh+{LxmEcpG)|0hqat9vYQbx0eH^ZH}C3GgbZYx*co8T?&P1k@9H)hW;lL;PXMJ|6gw3bREz%E01bnL7I?f&|R)7^k!_r9}0y9v^`yyb{w&@&`%jz zGWT;Qz5&Tb_;W5sK1qOxZ+nuZ+j=E@yq*Sdzp={=rJ8%aF4jMYmoo3ZWjX~*f`y(; z;)>tcUvlR|cA%F1^(Ed1Rcb34C4Oc`i*_Y$yr@!pjiLn4Uszs=F{fTfT^JC61o^dk z2Wu{Lu@~P+r+QX7I@gr)v=Y9!a?@7yMH*oiEuRVsfrj7a?dlzESXV8^4k&EIbZdq? zO*;*i8vJQeypexq0@cVay7~Gl9@7a(R-9_o<6(d%d{v8|lHGf|&LF;n7!bn+W0jlu zC*2i!T7Vv0F@7dozU+UwP^GsFBnENxdhK6?x2c`i-|Lin=wKeH9vdU4VO0cWIpR2qo?*|Xcjz^O zPXlsZZmd7*>BT;J6!XZdc)i?B@3((UE?)lhXDZPNF-07yH`hWAEpAJRFR*k4yP5d} z`4`7)hP+QUM5pQ9WUOs$+O%?SF+Q8!RXVEYk%;CoexG4sE^mGE984L(A`@Q+*Kqtm zhjibS3NpwS)^!Zyat*U~4Py=oaZCE*=9QE1Kqo=0z$lqNC6zlK6rV0&z|Y??P-K%p z%U$SsFg|Mio=v50TcF`Aap9XJpZdbVZ|Pqi=g*LkaM!g?oy}^cs4xc?%7VANiS!oJ zYx8R?%6n~VlkF#?8>&RH3;v-hvXtW{iyYF?&ZM^2+YyyDVm5yPbrRaR9!7LH1^+|r8eWWiiW8phWZ z8r&k!&2lZgV-Y6ucAPKw18-d-@G98;%YoDVXvGN&Is*lZx{41vHH0-lk7EVMVZ z=XWnm;B=v0zbMCquGdgO@eMoHj#n+jV^0l7PJfQqdr(#pQhv|dO9VR)mhQvzQ;{ct zS#w8a>06#g3KEDV%XQ#MiU%UQ9t*z=oZmwiAfo^$OvdX=?p)zky;j^LfG8YhEej5G7d+X9l}bdlX1twead5Fg@wGZjY?(7b{f8^^o9x94;RLvv#}*=r#iu1`336} zm?Hs)Jfm`Tuf)S0vEdQbqbkN;=hlG!j%bkuQE zN{mw1)+oGyx$g*Agz^5)*!wO(2~F|+2L846YZ5{<2qTpz;PW;j*%1G=d4z^1nBC%E zC#B<8T376FU1Q{z3f!b1oZu^8V?4VDa|4_=1^pDK|G5tTL*)&|;`|L|=#aaUBU6!K zrS%lh%}y8lybj&f*aEVj(Mnt0X3N#8pu2BJ{SE@nHd&-@z#yX2N##3hS*Lhu*C!%7 zG8}CAa8CyqXp84-sTC}{-y&OGV@$|J0ID6cLevwbVtSD9M+nS=i#$&mOOv{;MqSg# z=y$CLUp`wjao%&YJrZxsgtx^6-z1mk{0=>n;0yBDZGO`m!q8`bs5`J~c-~vc;)HVI zD)s5HyO2?h($h>|tn};NlSG^NicEkQxZtI~7+$)20o%2EP2|ih7dppjbfcs`q@4a? zRNM8QwDDpgkG|Mm<8!^=5iHm54q(i0uyENKpn?&FQcZK84zHAJ%cRT$a{4c5CI-~6 z7MI6^@?yE`^K5$c=i#=K>N$AMc~Kb}?zOyPI<=lDKbZzxC0R6IY~=1~c2+mD!Q&$t zakqJ-?-0sGW@JE_3J$ux&<{$BG9m{d@4(|!%=cgD4$9D_=n05I&TYLE%SB%h+>0BC(=Cq4%Xdae7=E&+gX`jfaqNm zcWw_2F#6z9y3rGBj?U@3xVlA%?YWXyP~J(9Y&#YaX~cXiCGD$Oolv>143C2^q1xLz zgqvOxeCRW%)UP)vc_q(co$vthE^HGC_;PP41X-1!rkpO7;h#+dg;Qw(tLAq0yu+i> zH-jt!u>KjVf`0Erk@Mv9gG58xC+fw8>W;H(^UaWtxbHe&A<7XiHf`U6E{)&q@q53{ zJ>vD)C;yI_)di3J5jU&#E>4zR0Q%$cN`}0oNkaV+JvWD1d&dNYpd##y>^1}jJ*ag1 zKXQ)#Vr;1E+Vj*)fPPK;uh`g|wq;@y(Au?KM?HdW1_(0iUZk@6p6k{*C#qYU)Yg{1 zmJhLB**frH!n+60fHv4N&afK!RREpkvUx+1Czp?LwOW})*HmAznO)H@YsWUcir*Zh zCfQ?+Dmvz&YJKbP!RcnPLmzn?iRDEEA(O5n{dV=U3C*{8;uV-zk0G%=)okYQgox*0 z^7lgtVJ`N?483{HDzALA?u|->lK351Yorfok#>C;HHX8a=pMC)#oXGkS~~WmYpHpS z5C1~PFRpZlP#N*{D;>CJ*3do4D~OKH<0q@*p?Bzs`%p2_YKWf^{i+O!s)h!VBwplT zWyb9wIUsY>K9#ERMmBvAZUjoTc(2HmgS>s zk%OL=z^>o5>L9adQAUk}*L$qXeg{F{;?oxnkJ|ujp+x7WmR3>0 z#>tjSHiS4}F7vt9Uvhu=D%AKCsySm(P}VbLGr#9E&g(N?wf^E3@$>)j92Qu(V;_`X0){h`IrWDF4C zPm*26`61@wZp@tn6$EQ6qx6PWzKp5DAKpY9&4pYI>fK*Bv_`*w%H+h7SNXOcD78YH zOA(8CK<1yBFZc;kuM5JHeoN6X{{*~Kl+_lqE&T2i`FG`NX6drnq~Tce$IqUVLYATh z75XS@?(GB&-{#mR*2p+zI5zZr7eYsw_tPrMLhz*`Jd=lz`El6dqt9;onv~L*Ir`u8 z$XGg(NfqK;X>kh{d1-fMw$o-U?O>H4gYahmM(H?c`Cieb|$zC37??+*b@@apfMb0TR2S#Z(MD3Ufd7&SbJKI?ooL8)9C3%EJ^B{ zGtXJ}iESFWE{tvS@Uqj>Auv6IN`j)1SReP1R5Zv9!R3wU&5%K`J^!F*!^p~86kJ2s zjJVRrk@;l(;IoyR5>`w>o=j-4wQ%_FaMgK;&eN)| zy~Bd5+32k-b7a`?$Q?Xprq8Mp>78maT(Ki>`Iiu1l&2KQBahe)^p(C@n;#a6bW3<5_CtTy!{iUgzLhGZ*^AZk; zO%8k^^35YG$-MErF=Xq@agR=3f&Hm9-LB+BK9uIW%Rjh zu+SXE?l)G}CQ*rOgSnr|40AuIstzZp5+mtfym8jvqFaI|A%y4okg~?(wAtpR6UrcRp$eTOVhl_65`(UFp z{V>=^=+TA%LvomiPzBUm0q9vu&18$ZKO4BW9)3T2t#?3{Ew$i+PpTlHT<=c>UWj*lW+b1ZpYps`8E zOBW8yUftWACZPAlZHJDhJA8kHum6vrcv+RB%?6=22eUDZXay<%8>sVUa6S z_qw0sABLnZ7RHejcasOOYI>Ng^zKj4&vr3*2N|>joH|@fUZA!4RWQlB3{Z4h0+mt) zbIexaq{ny$JC2n!*X-7J9&NUaL8Wn-0MKDW`IPnS^3dH6N^Ze-KFS6EiVE(CfG0mf zPP26Bl1iICY^CLlOll5}gFWVaIBXL6lJ&s1q+UN%9J3{bc%ZRTws5f=WE|UtYy2xJ zB;z%0iH#ZZ^6nexZAKBdE6=nCW0A7!56@j0hMg3}C_P=NA|VzulpQN0e?P8GM{S&q zQAGZM7xBM+Y>BW*<(kzHbfV@{E8s`mvwgcvB6g$?IsQ2~{Hb2OZ&url{oD+gwSMF~ zi-}#1qraU_f}noU1Y6eeD%)YE1O9C^yq_hO-*n;8h`xd+{AAc?B3Cf6v!mUN!wPO! zLsjkkR)dFMuhPT$%w+UMk$makHae1IK6Zf(b4q&TiQHEgTI6_7NzSI;iVj%7{F(+F zw>hnsK^a-ZGXzEsW~4$z-hM&`%R$%{@4jPqct5QdylAeuEj;6&NF?}>1Q#vc`rP5N zW5uVzv(M*hJFVp9a$dn#uhNc*)U&?tL7g?8GOP!kYs6h$^u&maw_S70R$BfSy4i`i zW3{U19Zq-cle1i^7T`h*SjUd5=Q50&Ht;w_|IGHdlMJ-$Fe2FYg2z8TJ95%ulO7p2 zR`9yA=;2#|H_skir^)MNjM39{%RI|{tMr;qDR-vRYRFN$6?vDYI6P45F0Pc^=X=L% zGnthh?TUztsJ*hKrg@{V7D$z(?}WafQEXDR1U}?(k`QKlm(l2ng{1a+w+96R0svdG zDDLYkVl;6VeClwJD#D`b8pN8~9JPcTWBz06A0e zFaSxBtx}kZKQMQyCvv%o`-4pIU9$v7VANwd`G`?Ekc*p! zQ=N~;-Jt#P%2$fhe%)ZbFW&^R!uLs2i{g(v#a0RU~!@EedhkaVpz7=A)hKVY2b`!%0oWq*HW~`nXB6qq@m-j ztNbTxzGwH>e2Yjw;u5w-NoA#!o<0|G91soud5M3yE7Chd{ki??+2hYYP>(xk9+s>X z5T&Mx5Oq*i^M}=*@uNs$-X+gI5<2dYri;grCi$XiXU$ef~6}TCJh_XWLf34W(`YP4kJWlJ2oZC3X;WWwbkg?DZbSd;}Olc^V(OPY93L& zq>4@Qz5Nb$MsEv8@fMn}?)t1e)!x#2Ui2eT6yR5%!mF{CHR!zH;=SetqiN+Lj>Q1N~npx zoAJc4rY~C0tg;zoE*^YEtDv&t@)>2uf1sjD_icRLCm``;@)19Jp96aRIS4hh&F^?% z1zM=Tq-qv7LQ@z%vAr&|}7jKn!*$Ls2>H z*t)92tRq9iJl0xNUG$Xtr)5)8i+tfF%9-N)Fn-8kMcEFri&yiQwBdpgUzEw`tW93u zaiqo4ns6zi&b_@(D}e?N;^_#56$&R73L4zy9m5L1dZWwvlwbdpMbYn!O606_@8#PA zvC8cwmi;~c^)#OO&$q&`-O-ES%Ip5ihdSpkZ0#d6x7{;%>cH_}ukKC=R}75~&n_2a^puB`y;Y~wnFq=L?)pIiW{~QkgZElz-$DSg9RKuO%s3;CFB;H05 zIz{m!3H0|@{F7c8+uVg*;3AA{PtmFANUD!tzU0$~oZS1O=N=6V5v=p0ULg` zA6+9MRr^-|tVdrr+8r7b(fi+NQFDttaqsJxtoNc}j;#IKqnD>o|E7rwf% zw-IV#Zl8*vm=6eM9OX;~b}Ev>6Nzdc5Y4%=1zt$fbWiXVg_R!tGK3ZfTc+bl82xH> z8l|R|IgInzUtS)FHA{O|w4RDwYVYA_q&!9A2GHz82`oX4r_kS^D4k066{K5oT?p6| z_r(L^7ofP8!-{SS52*8gbi7!;G&T0*46Bt6Ksv>1Lv|_0%rfaYFeEa*EY?^m2|mYl z+QlY>_v1>rcQK=o1;i&%4L$_En^CMPK=EqmjL8xYvkrQ_t0!k>Rh#Guin@!lh>Biv`M#Hi z@cX3w1J4}xWr0}Gexv)fP6%b1^5Vj)gF|lPd;JY)K`8w}w&%4?XU{1Zu8zNNslH1f z->i1mL-ghdh4o4RlH*oy}J! zCHwxsoY&JdHePLw*{{ch@Lu%=V8oqYZ@swCWX&O3=75L?-IDTX&l^W$H&m^1Zgb`H zGi>ubaj3tl2^euxv6@tebrSn0%$k4|JG^{bv~ zbdRR`=-c^`{MPU=(K}j+E>EbgrWGj)PbI!mP7)1Zwzz-s$}T!-7w$bEyMNJSC~$c} zHaJ7voN1B#!Eor9k-b;S=J^L>I_6=u+fLtoN_@qw4#w*ejNi9criPVZnh5S3TLGSssCB<6^2{* zkSZOr{4f8U0soAAe<20`^@I!iU0)JnBTZsBv0c7&l1`$Hh7-D?8#GJvK?T1Hw9la2;Hq?i;+mTT85@AWr(Cqb!o&?veG1 z;%lgUD-DY1>BmPL4bR4)B+8-}#=UVh_%gu34!#)Jia4 zn(d(R%ynnT*>dT|gxk?w5vRidMn-nx#V)KG1LCAlA?ZE|auAq|~UU;vWF;ZzS-4`e8ouZwN77#L6VArb{#csg$;e_fNO+TLrs326eRbbwh_;79tvm z_fH+jbCkj75O-uD?el!*|K_pHmM)!2=V!?VJ!z6>n&9Sp>ww59W)gS7L8)>=~%2u<~oIcwn%gMPFpvM&ohE>e(q^EzenxzI+B zxfw`_yI323))X-QwzD6XmQ+791akhu(90#VQ{%o%VmBAFCW%*GPP4>^LX2XaAX9>aLoLcq)NkE0ra>>%dV>fPS)&cFmDMabS#rP zb-q&ty0HUsybe7z?Je|&UW_HDXZ(EP4u%#hG$Tge1?27t(AC0tzv2}@{O%FWqZID# zthX%%Vr%lZa;C%*Xc_R_IkzWR&v~o1A@eUPwxoU|3Ph;eNbP*#Ae_SBFK_ZtaVL5&{kqf`kqO z!we-|11LR1H4vRLveJRz;m(}FQXfcQNas(7rE$AE2Rx-S z?a4b`R;*78Yjr&d?muun0T@q5|I2kpApd`&fC=J7C0xEaDWbGI>Zn%@urL?drm>a&m3Y#B-%(I;Y2U~xMaca|Jzd@jN)2nXXYaIth4W`+%f2)Pil zvEVqt22dUAje#Pv9LdxfJI%H^`I5cl<*z1P3WX%689Aqtx=s@ZL=QT|;^(XY3rv+B=8;h7MPPUvQ$w z2ow)W8FZ=jAGFXRg`re%};RG!)}E%WnyB(hbf|IFvx4nu_SwDf84b zy5%P4N*Qq7jypyex4e0pR4yp+G|iUh$CoJs8vAdYMZiD9kuVDO3Sv>Q!o24+9gR6c zzOA}#V!8PpjD#~j9W(=COqSy2PhJz{5P@HkoD3sX&Z)dekNB3p91yvFH7POXBJDfj z*b52yd4F)VC`V7yLG(tG;4#BO(4(cD(^-3d{Zawn0xY$-J4Zf3CFgFm=#rO9TEzz4 zIaaMPh2F!-aq4eI$ui7V=c z3C3FDP3P@HuepNL?H-Dk3}iUwE4jCxxW*x^vwWV`=vL7KvxKFB-(-W;m?$M`DzSoI z++%HOx)H{Vk>tKY9T<#Fl3Vahze9IL__RxoaH*sYP!iWz&`ANmSLok?`@h_8r18c= z?~Fi@^6}g*L+vt78E&}p4?9}l{ziIhho8g5aNR48r)nojFXSdtRcgclZFwYz}>shEU4vAYT zt(B4Yv~i1x)#a^i!Tp@7l1FSg2calFwIEZX$8ea4k`g9`MPBrVxaZ_~W=P5Yvrbyl zFr;ndEw0`lEr}7Aq;ep~Lh#F4cU|Y;+xk;2>K~4qbrc(HVLv?ASeULaXldts>9X>< z9)Mk3w3p*EacQ{dGAN_Ieh@p6jB)SS>1A9EDiBhVRDMwjw_Q7a* zO_?Vn%A1F56Nvksj2ae4TY{ffR$zi~Bw018BUPi!Jwm8MhcjCq=(RsCmdN zOb?yHJqk$p!FMB~{+5iN4gduL?08)q$kb-U_*gsbeR2AIXg~B0@f%gcIFZQYJ$FlB z6jZk{8Im5zqL`{1F84lf*zvmJQ-&Wh#-*8A(Cu9~9#C=LzVf+358W4+ZgXXd-=|+{ zyh6N;v`+k)Nf6c}2VVFlo{qkzGwc^b=ns8Z5Je>TcJZ@p>}CucEExwrJJ9u{8? zT954)wNow#6lzcyvx4~SsWUG%owK^{ZZW>H%#Fo_k)@YD5J}_J1pXaL*#{<_j5ots zo9-=79he~Bt)lF$v} zVz6XtQkN=K#@CTzCApDFNlv- zM|=KjeD30hi=yBy=c>0XUA=Ct~@x}iZ@{h4}2WL-=hE1 z2*|G$?3MVnf|;uVtirZA5cbQ5T(am#kRfE`Oqe@T(zbS^j1N+ZM7xSrA%kDfA1$ZN z1hTDgxSJ5i90%xNCi-?U9h%3beX<3ask1yY7yZ~JyE^jN!J;D1-k>~W2ci^yhU92>82huIqZ$B3WO1yhgb*wrF8)g^p4pr3-mZUp`S+>KZwZdfBqqW?C-Z{hZyHPYh+GvGpvlT++3T`hl(2YV`b&)`k6M+>k@mQiKxb+HUd%j! z7aKghN##dNR7yzdG$4T~SJN)~{!VrO8z=r>e!7#4^CdwL>SpDgDG(MDog>SIE1w8% zoB#dMujPN_OjjUhrcv~Htm~Z|i#mOvqZBoG6nZm4+7}8%SO!F#flI{b z2Jfs^(9;fOim9QaX=4&bPJMf9=Ly2?vqlrxdP&6l-lyLv;_l5&zLE?2Ncg4yK za|{Ba$AgLQ{_ooL_ul_Mc0sK@5Bt9CNql1ngDhox)uGOvRc92@E8jzPu zV8tGAEg61yAxWkL&u98UBj1amf`c**hrB`|QMLRHrkU%s*o`oKlWbu)TkGkSSq{B;TPuUP=rtyw%T4s`E0B-P&uYsDWuS(Gmv`+kP6gq}=yt}K9tf!SWo6b3yD}+NrM$~f=?$5{6=DlhMR6RK z;xCK6!#-=z%jJ=9<;>FlE|gp`ja+15yS+3S#3RN%!RLK4EV!Tkrn0s#G$xso5B^w7 z=5yqmKshkNPuv-|lC#GJ>4kP!G3$j=`IYUunV1;moCCe!HP7s%$p4;MFnSwDCNG5? zM4SGFMq>aPh2lp1&)R|ZS1vOLa#?^`rYWLW&_(!1@)faJf_P!1TGDc4G&C*~F)?dD z`IGoLQft!BlvOZmIPnYeFm+O!JSMWjsHi$*$B@jXXVx*k0!;GartHT+={>+@F#VOA zeJ*q=36ywHa>OB9$1jIcQc=lFQeg1U9>exYprGrVDq8{xY=;jQH_D1pdo5K_SC3CJ zxj9hyn3Fzc**1?fq$f#sr=#0hBBPk?p0#ZoqDM@K0h;8yOrQ?2$(uH>j!O}f2o829 zepMJZ$t#`?_ZyuzcS^-pvz4tWqF}C6wLaZnNYE{*<-&k7>&3^@rNJ8F^n&7T`eL`3vAGI%Am?Mov?9+B1KPJf=jM^MC~Kzxc}mL{Xy`Hf#6tpmg#)(~SrAS{$nM4q{vZ_EFzHKCT2 zQF2DnP3JW)`;4y>Pjug|mR5S(F5QhS^Ql^M*fFX{a&iD0nE;%Co8 zsiC`s!ii)B!RW3`WKF-D*95aeXViiN}^`HFlzXDu-pc}83PNvA&0320$B3pTFlM@*@xWg zr*>gt4p}+MR7y0j8^0ylHe_wnM_^S)kQ_&qoZ~Tc0$V&V_fBui(a%K9X znBoVDDUFOuaOITkbWE;}R*;CVqT0^@y>@6^uO)X~Cbp?^S~oW8lhwnVi1i#zRts?S z0Xhf*9<0GuCa9Taj~igbk(X;1bH?SYd_)=ELcc1{t)+=(-(Taj_*lM>8F2W%NA6RE zRJt-RrKEv0_Ol7;#``|~B9!*+yS$rIc=V0?za03A9*maTv=C z$X%~gbe4HI{wGUh8`7LVYF-L>=Qq+nBU_alA%YH;z`ER@eg8Yrxzc-oq(iJa1+rVH zh-1MG4b~}uh0>X?P%YN1I$^xnbsA60&c0psLhy{tHFDdoZN4fIz>Y{W6-S`iZRjwm ze%{T?Xt$opvJTJs?%Sh)GeC%8gQnLK@;v%O=HaiC}&3p{PE35p+0D>|Q2sg#;LE+Nx}^$j&*c(n9YO zJ-qJMJ0H2P7S%&ca2tewCrn0l$m;A6ScZvXKtb6spFJn;9y6=cGV3sp8cBA8E#(5qNOZ z!BJ7aQ~$q%@@7)HuS~0Q`*e%2VO%js3dSMjWr^854p8|2)SrpIQM3-X`r+9CmMLQl z9gn_BV&EJY_e|POg^PsNW$>aj>HJorXPCdgD`w)l;ub26ci>j)cZ?j4X9^c+Q=9 z-_TvV-;mQn5vAaU?kpUf&su+iICQhTL|4kXqZ>nq!GpJpo^!~y;1<6!2Q-bb3`-+A z&2=+zx!{oX=-A1M+iI)d4K2NFO1tE4DJrNDBto7VCB|wd>J|!sCWyd1bmF!{6y&oo z(ipMgCtx_Dd*7ahB+{%I0}8hK`O1z)xayZwqZPoBD)L(n{+<9piiHk_Q0nj9{_n!3 zk;?md`LdxEK%B%|2!2Dy>$qP|QqSi=NWKV*W!Ci-p7x|opm;b4RtkoWg$kWgp9so< zVmKF(nNUz_D5imO8d6uJojfM?>zI6#8vi1ake&z@&lnn`Dw}Ic5R_}cb-dDtrAu}e zv-nsq6JDnio;W7g&lW|&D%t<4M_Dr;6mfgkw@1zfQv|&Q*_Wh4<~|1d$a%tCV`Aof zIR-k~8;-3K?an7vua05uX0dHL8kliSPq~GYEO}lNmV->_VW_e2|%`I6J6sY}nYTHF;T5_c?213Tigm9vaf0S2^gKDUIHu4KkEz=K!U& zyQ5u*z(QG_rPdmTIRL}C8Hl9?Lf&X26ecV386uFLf6)t&%nGAZTgTsNs*B|28JQ+& zcdRL33rSeDmH)jKzl(-{sXpGl{cYp=Q++)G%zB%q3y#<7%Yp%8#;4TD6=@_5J0UZa zRehuvT{_!8hY8gzk4~5M93Fk%8uKb2-@W$}}Xq&&mwSZxo(m*v|ld*~b~YK^)$$K!vSu zDoSY}Ng#@}0fh@5zTT*v@qU;+74C{Jz4UUra23d4ZCVQ0aq_;WN(q>*UNG+QMlU-pj~!<*j2tT@_xck5oOfj%tw zf)e<9*@AyOkQW$P>^L&OJ* z+Asulcf+;AZWw`Pks40#LS(`gXqA~FV^ri&ffv!Fu@DJqc-H;|7hex7J*r1}vVvvP zEaOr|X*g-VoRg#^EEy_q&Hv5v(wpK$I_i`<;;#3Cm+3Z8>_>@c$Vd-^PA*wJJW<YD6 zTb09Ai_I=IFLtZ11DrX1-v`25USQL(YJ~b{(^K+Sv7W^(1noffLIOD~;{xR!V^S9A z7DS_xR5W_972rLudTTg8$)o1vIVmI&Y2Ywlg6GWasiKC^5WYCnsvx*OP&%r1K%0Ta zwYaLnBudT+*Z<`!&;;PEe4(#;Ur1Lmpe)^8=7*|Zj)=^p7j8D-BB`}K%8P(yVlb$cGKg?tQta*azvm$`c^c(5Ky{)j0{vDZ*~kA`5LIuDn#c1GR4Uj zfg7J);9k(TmAD>|NCPCPySh79^Zf=9GWAC-)a-3j1`%Q znQKj|xV_?-(Fi5W_INMY?wiAe`xk?I5po~MR2?9%?K=w2XFss(oP5-cFQ|$y=1q5B zzMgA#e45Y`X<*C0f!;Ip>!sU^3Vve2%#6rdggEGrlq!*2KVQZefH(KV#Js=@u@E1(GL$w-2Htv<7FJ)`BrJ8kKtd4stY`7^r>PSFR&5R zJP1GZ-oiezCyI<9PFg8m%#f(G8>|$MD=*=wG>FvnFW@&_S1XbjJp<8>*tB47sqNpP`M`^;8`^2*LV=`a53G*t2aicHGgV^4rlY=@Pc)4#H?{47{K_2w+$5ZkX!!Z1Jo zV`pKlUf>QGN9JYA#!%A@H-dEwq7+Ir?t=ZKjEJ&?{m|$uGiAF+p-#fZ#HKjx5u*@* zVEVyeN}#E}&_?*rMm`IaJU>sg@gU?GyTqzB*XzGfmT_f5qz6hb zdQC_(_ z@9ojeB7LFNk`QQslsuTErRMKF6g8R|7#b)Nc6^@(ZwlK>qit_qKYS=NY7LkIT8G0GO&aNbbE^MH>3TXq3pfPf_a;mWrR3uQ=|mKK zeEjQxhlKXyKmXKgX#mcu=q`%{6I+$Xc`afJbmOlOKQ4~#BZD}(VBjEWOD2u7n<25q z+&)TEfy_us#3xExS2E;_cdG4B#wnIWDOvcFAktBK%2l7`;@sBn`)Uunky%iS^yvqu zpXrYC#4?Cfv@t12@(+Y%EtN`*4V%D}6}^x4r$Tq4-z`JM@^L~oh)D-w_rlY-h+8%m zD>c3o`)4ql!XNL;J`kY1b#}mQDoP@-?QiGli8Teru!vwD(g^F}p(axUe^^`uLT3gW zP$2QRk%mew$oq8_yxbq74pcpdN-LRnP5bLOb`L^%p194zId4N|kptu@=tNj4D&+{2B}xd*{+_ zpqvCTjpmOgsCx%#e*m8N2!{J)$o~>*O5PG_Ji?KeMIU5_wJHx>J_dA5JM+$%KYK6D zV~>E)G*`It8~~2^T%I_5pWbfVgo1S@GT zkmYdsy_Gm_`KpU0k!{1zRSK*@Vr~C>=`wbQBzOS@n-h{N;VmetKNClu=*-s{VKlZy zyo=a?c>d`WJ$ufb2K*Q+iwnYy1Ylx9ACY@-rJwr+|2MCq#W@$qu~{I+*=@Po*j&a z@@#++&$(8SfDtVNqygt!Wg|(IiJF&dA}qDWWQ%M)i;ibn82Bk5V%o zk|kzsOQQHtd&pd5Hez+jLBrTN@KKP{M0zlJK8w}1W zlu#o|pr=E5V!uOIWQ$3O=L90H*>A>tmG0U#WVc-Eh6vEj*?#PjV?(|H3>c)5_BpRg zNZ3Hr{*<=#2&2ntye+{)ItIkyNRQ3*-xT0KyZ~O)eA)lq9qH0jk-~(aTR8VGh#gH- z-#trS8HHXZw`p*CJL&29axp$s_)&Z#ijV?BFNJZx$M|~n4rA%czQa!ME-=-g`Ar%f zRlk+xJzTNfi2z2$$|~}(y?{}?V8jDNJ@H#ZM);w{))7nc_%?r$-gF>|wAmoTOyt3t z&JtCC>Om`cfKXs;_PcnKFD#eVofT1aoF+@@pVrjAB$g9ZsxmK~N8JjDezDV~bnsB+ zbz@?D?bi9m^CAachPL>2zb!ZTi`P6F|8>CX z5I4vG6fUN$x<1~`YM@`P8HY_lM>n$-H#FtdH>HzC&p&OqkFQbw5~f!8JYej_1KK-; z_tF@G<(o8oUhtX`y;pG#Mw&%fCJYgWK*bfxUhW0zK7gUo!LkWOhUL$HItqk#MZe3W zX&8X8;`l5*#jKQl#);XwTIS3qc`Kj3X@8 zy&_l*-4NW<0rpu}na$vTxV`@Vn83Y1^o*IRf7lzNs#0QiRG?lX7onomPt0eG2A?4b0yJrCfd&$-nX_jyLURL^e<0>N?9C!&oIp0cov58yBplu@ol(%fC=gZ9nA>ze z(+YDd+o^c1$i>39ySs6o;@08)-gVM<<2}`%wM>`8FHOHeBwY@0=N^e`*5wn~{YVWW zG&0ZCG=O9cKqm6i^2jZb=Zbvvust*&s7$QuX@4bVkToysVjUL1XZqZpW@Ie@5$}@X zT*Z7`=g}s7vm)o3;p@JumxyQNxL!L4I+~wnZDfV@IDTe;3kOar4F;!2q2xg>o(^`{ zsZwRi4(2kMa9Mk$Yl$Q9Ze}NZzgbF|<@tc`ZpORRWgZ-b9~UCpJG}*iUOQhc2tDvV zK2o9e3Y)tmmq3))PJ;zcLw71{R*|z=;#@|#iDS?4-W7RYi6SGanaAvh3_d1Lgks0A z2g$=uU8Hh%AVKoGucU~Q?eQMW!XB3?x8yb`2+iK3)wW0*U#`#r+U0p6ADqemOPX~9 z=QZs=H59YINBQLp3LJM>fVp&RmWDMhI?lcn7#&L{mEf_QjS?g5JGGp5yCcdwIjBfH zu8vaiK9mh*Pv(>}+AJ6AJ!=S5(=mmEFZY~;#bz-4bZV!%G@Y)OxZKa48O0S1XpgAQ zuFl8zhHZ>aCJ}(TrkY9{Y)>YxseBr8LZ`m1vQPn@UOJijz&{ncD(qjx8`Hd64mF#n zW#f6?t)gj~ThR8U`s(d-tETYD1Pb>`cl4Pt|A{I#i88wRDfqGrQy_<<~UM zY$gb|VgkC`+|ovtQN8To?pJ4MloWX$Qaz_%Ml;(~t00(zJ_9B=#y2mKON4LJ0W^UeOIpVm( zR^4;?mcZcBmCnh+`s7?cIG8zq z;L?~eZ%Aoz{}+hkI0{AoRyV8oy~#|zG`ITc8K=4(a*Z3GZ~P}@dx863o0zU;JDi4w z9gcYm}FQ-LJa=N%PorigoM3=0%1x&X?ahP&#=(xO5rmCrgksgq|}x- zR;XQto>6}gO^Fi zv9m`7SAGglCb#Ob7BBHWY-i8tsSM`Zno{|EjC35gXP#Z%9$xx6=yqk&n3%(3ov%(EsA3;F`Sse}@T zf^=#(GuNp{dDq6EsUSor}mnIaQadwu|yXxd}v zH3X4PP=cqO>vbvZPZW7ib~3(yG(2DBcIUc-k9Tz^u??ec7%QNY)(!d^UfO+L5NZrs z?KX>SeI?6YFVkn3MBqg911{3t{6OLCae5T&Q=r@sk^)2S2rSUSH36d(k*RtmbyP-Y zpnq~ZTryQK;!R)eWw(*wA=JNBS^gvCxa{w0U?%(xuKcft(~pDyqT!VI+fR9DIGdLl zr`Kqg?UgC`CZs6YhgNP1i&<7bsp2{{^8t(((qbBieEVd1+UT1{YFf>#?kVl}y~`?K zs?u$|r6O*u@5d#$+IHs=jcWBIA?7ySK@suW9L91ft}TX?9+Q|S7e!wlm#1DHDNQ+C zGE%kY2p&B&P8pwVb$qMv#D72h$~<5p_iY4a$bK5aHEXONTQ+TuZKNL((yObe74>qh(xbKkGXgz;bL~zIs@$dmP`TBMzTqsMN3>35QrbqtF zyqumRkCh|+O8{{=RJ!J+fnghLc}i zYD}31+u%onafQVFwZ-?@brlPc{T_tr1dLY5!Z}uFfu2@;f{9`0TtVeMVYM{CnVEe(M7=inmBVZ>PMrVJ@Bdfz`H!0?DgS5`xL30d)5|A(%kazj;`YjRcrzl& zu+b{Fv!D0i*@&u@vwU@D&>fQ`zo8#4OR>T;d$(1-402nx-k7=?zc1T#*$K?Xp-%PC zFeO-Z!eylLL}0x$-o{?U?YKa3 z>d0K}vI#C#DWeQ-8>D*v%dq|eJ9;tuq0C$bPXE9QzqjwPx%4Mk3ke>xtbY|ts|dvp)U&v_)AxjRnu|Up|!h2dUEh+N>j}v zC_(<}mNfWgNPWTdCcsd*12*XVdg1KR^n%Z2EQZS7+j7n+lgen?_l^k1M5kg!Z9s%+ z-~!?qQev&fG;Dctpwj|kePZ>EpvL=NHwvcXlY^3{0ObJ2v(~Ko8?l;W=>auC`u@_z z-@NwUUX&Q&*psIG>T(-e2%0q(ei3rM@LVsu)ZQ2wt(XH??@CEUT?#J=ZRcS(wSdbP8rdmwb;hH}kuyXl!fJynqoMysTASxIo_Bxac!_F}aZ50Y#>C9C=XY zTv-5qgHy84(4ZSdJX~k7>U5}l&+g$b$vh0Nx%0Q>_uqBe|KsS&;P=!t6p4oU!67(QOC-C1E=ZUKe|W(zuQ*Ew%R5`HO}`2a>7 z%*k`)e7SOJO7fntV(zA{Th1G1bC=oPa8^#vbcb%$XIuw66fD$54IQ`zT)+2#C9${# zXk~Zrr&_r+rqo;(LyldCzcI8+6qjuZ6;F-ax3mOWqlm2S9 zODh^9n#*b)7BQPVR)H;4K&!Gk!|q?ckZ%TbiGXet<7+z}( zIp>cRwXCT!mKyGYIY&m*OB<=-;=ELHC;R(~8jv}~ZQFyjY00V|U>=f;io&=|dXLMv zwE-b#Lc$qmKTy;G4dxjS{z#nS^L&HhQYr3${h6zWQ?@R zl-a4}a|Bj(F)PHFUY;x;J`CT(326RquJCa3h-*e+mCDbps1fRyqy6#j_eqE@%9O;9CeAzh*q{&tl*z+ZdQ(>i z^yfMxDJ;B4iiMhLCQIaq`nz7PY3A#xyX~DPy)-|1gqjM#YDc$M7s_^s2|DiG75mOw zE$X$1*3@=t*`F}T(XHX}w4Rciuvrdo(-+nCuG`oD%Q*5P5%kM7!tGYOxzGYs$R+RK z^c`Z9D99W=R=4H-#}b+Ei>e}KT&U!)uK0tjWHaw4ElAAscL!kJ&N_b>&y>+-cytzr zTaV>ev$xf$FzM7hz7^O@k?~2q_0_#+j1TUDiLu`s=H+JMlf=aaC#i<}w1R1cJuG~v zcAl0va^){K`el>si-V&8R_s!yIb0T{y7qeT;A7PY6W8DYtD2!%4nP(rLJuIq4{>M( zgI>hGqwu9&;n5F{OlD$V_NNDCO~qABvd2tY$2M5}qU}|){W<@=DX^NZz=|?|6plK=FIRD~E+wbO<^kBxU*X^qvRz zLM=#Uqwyc`B*W>uJS^La^hFvJ+h*eU~asN@vO8xBWA zov%{|P-Rira(kq%LUePXS1*h5p0Qti&PK~hgbiNhsn3MA)NS4g2;|hwQDsiDs!xUT z+*{Gk$Q%uKz*~CJGVJw6PwYtmN}hhT+c=XuL>!$wi6j46MQJKwE7K1!X-!GV;~?FF zCB+?au?8onf4bO^QzU6ni64u6xu>PY^|PoSPCM!Khe28+8k4JiL=rw_a&zzeuFrH_Q`(Qh z5pF2J7ZT&%;QiC1{r6=0_s6_h?!V`9lRMIMR#yJCoWP!Zi@pV6db3r;pkQUz2)Hi9)}MN2Lc2j8{9?+#-jM6UN*6}0*FB5tvoR>QBb z|GMV)Qv9RLVfot+r8)aaAv=Uh$2nhHDI_F3HFj|~39y$@~%i4l+EXJ$?~2DHW_x_q2VKqyW9$x*yVBf1V((+nRsItG5Jn`>%}Xa&+;qR zvcr~pQ$Lh}X?22kEcY%x%l$$cTrp6zh4M#RQ)W+(S-gqi1Z#(Hr2W<`$hinMhx8Z_(6ZGiMdA@R-J2>GIiY{p&>Nwbdw zZ?Khf48o?6q>RXZ48_>R-cvGX+n>-XwQ?aQlVgbWh4H-Ja&Ld_Vtq&x-s<5L(90!e zn==HC=&xv~wo=Zh)TAxG6J?g#{O%`Wc4@HZf#Me_`J2+~(Q=EZBli=^X>lE5a8su8Ub?7P*6#O0*#(UCNIJ_S#0RItvHKA6U-c`j4yBe_c&dOtp`? zA*u!AhTC6#p|Y++RT+5lgHn{88!i#=zOqu@tlQ=wZfe-p7~byb4224P*1OtKI~w|{ zQVBO0#LV8ZsSX|9m;dI5ZFw-mU+#GrIZ#Bfccl^YRbZ?-2y_ndz{Qp-?DW zuf^}DxXO8u)*vHnEg|uqM%=z@jkha%q#%RVl3+|DGJH5u#C*^AcpKNjjSY{fEDfDh zu_wlo)MkTDKd7uZStW;-Viz7ICU97^8(P)VUoPqitd{0-lt@uTpF+K<(aTzNbop7Y z!TAz8p;E2>dm?u<)%!nd$xr)BLtS^@&bnZ?f0WXsl+x5Gac*GXv%B$g>yDctwB{qp zo`&0ZyXvNCrvgP>?_3h2e$oxCb6(sGb5J#EC~8FNx+OYxNQPQn@`Z(MLY{o$P9|4b z&dc0`(9h?7Q0YyJ6=g${Fn)AzH!v_Pi3yMFzbA+fe;amH9D%awdpHXM_LhUdjf;c6;7l5_Y0rDIcVp`7TAB7s{Ma8 z>ve$hBzfjZRg0>?eY(pqgqAj`Z#tv6{h5?umQl&I*U{E3q2PR-TPIOJ?n`AHlVX%A zY=9ZCQQIgGCWu6Zn4~x7y{_!iMjHTf1#WCe*8A!so0GG=)S!x?%(W4YTTLyIx5z?4 zr&uYv&dx%P7Q%U94h{}2ln#U~X`Y{J=_d4l=0P0zc>eJXA9-QiPT4!Q@z1!6;A?~D zy1jcUZ9C#3wt05Ta-7=VMCvB{aFmJy0|~WIKK$$CD@bveUt$FOTXaksR_skdoqM&c z1lv}~HV@Gmu}kA>Fv|qib#kzxY8+yAGtv(??pow$Jl9V6B_Rv>8V^e9y~O2PM4geB z@%Vi-g*;q(ek-K?%~>F>`P-TgOHu>&&)%q$QjFadQ`(@r;9RzFPPw2Ou2WMz{+Yfe zgS`5TMV>ztWv)u%OreDAoA(dBo42tLR%q`9u`CQ~=67lqs5k4+D!2L|b7`^7g$lT{ z&{S)dwJkPX({wM%^=1l@v9ftIT?_QN5nw1avW=7feYh-W_VbNAW75Iv(2Bcc znE4{Q+8Jg0{A@caj$^@2sQ+Xg|6XX&6MRH>6>x;+=Y2*52A+w<=Z(B3ypS=?ABtqf zUf)D#D$Z(O9g({8E%MBtl$dOb9D6f|S~TqMelIAoofST#PoW;i*!;xJRV6DBzsA$v zbUsI@acw6j&cc_X_Y=0aZt;Hiho(Wo_bto7IC>|xD(SMeW@Y4>t>r&<+taI?cM*%* z^LHi5oo#(orKC|}2UnYcr|N#m+UcOLy;zo>tNEz}>Q1VCojQKnjvci6`ox*o?-C30 zTNlt+tUM%^!E$XoT^u!O!weO#g>MK6H3rJcbPjTqO3A1;AzRy&RW(q4>O+doof~gW zE);9B#9L(jQnkz2RiE@wJ*W1RI==fXSWgymIxdM;$Ir|wLBofAe?BD~DJK0Oq888V zadCsx>jE+9m(;=NodCEdqnMAwmz)$BGWf-$@|UCz4$vs6^yZtHhV9x#exC@ z8OjG*ALjgjLRh`-X<`0Wg|w>6(8F8upD-Vh#87V@(e)RkYK?sFQlCsSC|*D4L!^2Y z@#SJg4Jj>-7u`}dS8LA+322_4<{Hq*8{sS)pfvDG_Sb*UdH(F=BtJEc!<3vn{{6vF z4?-VGjt177nd??tooD3wRjP9>)z-Sj<&`XpRw2tR`Hs2y?ti2i@0U1Fc2t;z0+PNo zM~S{?)`RF=(tC#lIMNyoq)Ii}-!;Wyvpz&hti14Y#*}(YPvWRFOV6Xq0aL!@4t!6( zJeyEdrMe4&jVz>Hq15aU^(({*=a#CAs}6^RCpVm3YYQl4Zue`WKKu1{OU1rG<8s%2 z`ecehFRUmeC&igKNW;6z&gRB<~j|$G=vsYO1xrI?@glJC6Z>7|| z!Vg2#w+|$iC9b59G5HE>KSpH8k#EKYy>}V}rTQ{%$`iRNsYeMb&U8FJeJ6mp#rTNv z15y3MDv=jr(#~$rSz+eSzwEkERZEcf^iZ*wg+b_-vdv6tQI*g>^)acVTnE+Bin0+O z=iKXTyq!e~zN@g30TOcOvIts}w7-|KpQVJ8ohUL=I`n%Im%#Ehj=_du!2d5@fB1rZ zQEM7vQ>-+N6p=67iI&~_E`PqW^^L?o!hX`xEEFSR+8;IQ6MC5wE^ctidcJ!I^~z24 zQ*N@kM4it>Z`GU@xtPEIyQc`J^S zs&r~yu|{(5hcd4dntum!xM6cKcy5r@O5i9NP*J4M;uCCdnxaIac%N^G*mlhm8r;*V+iiCV$r}*wF>GNW*Jgt9( zU|Tps(W8moay638;;Y3|JA*{3e}uxDay(w}4y5UcOJ4T?p~L1F)kRi!RTKGL#Dt~M zc;w#pfog#|?aIz=-@5k4Tzmv9tNHC8kQf2ZEwBZq!PBU{w37xwZ6Nm$QCtw?3XKD@5?P1dBxWQCkF`B%^x48x>VL_?k$4 z>9G1G<-$VkW$8hp#fj-))8v)UP5I|*aRW_k{6vXfy>8dk)RymV#9&r0n9xTNDch{N zBUn;GME!9&kPj~Nxy z6wCPD#q(Fs#ih(+tjjBja?ekf4%G9+F};SgO-pwqk6fcRy8C)b_OF6k?s#RTt>35l zU^$%lGn+`p68>m@t0=s{hUgVKSYdDbLgiEzV=aO-Tk(3wQPmqth`cI5KO@SG)D!t| zH61R)rFKQ0l6_Nt6}}VwAoa~6haOgV;pe2r(OK5}1>QusXs;C$mzqb`)(^DdpUjuL zYsuHHGKYrgNJdb6x$p-r`36NPJ3Biatp-J}roD)ZnyN}ER+GZa7cR^flC0!foI0A7 zTO=r-{Y?XU-rfLqgFeaW-M@Fk-|+uWyWx@KBQ?Kd$GopmKQdc~@p+b2sD_ROhL`C` zFeyeXKiNoI$!>q#s*NpWPLA|g`q|};H`$m%SlQl|p|#8}WHRZ>bh5{4p;xI%vU@s| zRvW(laBttYciF0QBX7M@udHah`sg{^h`g%lC&KZ>>4?f*rH`x0Y`ukQ(&&B2`-3LJ zPia73+x=*(ScBWDj$7xMH}o>0Aa<*DIch9FHC|@WY2v_z>vl?iN@xpoUj6@3_m^=| zwp;r+ev?WIGDstm4g%5$0|-jTPyo zpJ#ub@BZI=zx=;~&%j*Q%sSV3tYaN(t@EOjWbiT!)Snmy0s)&>nLM{Zw;wULq0O#%X^D{<0XyT~0SQj{veTr0HG2Gct2a%y=Eg8> zH3AF;A7qf!;4S>|*I58s%y*#+FGb|s92H$NcvXq`5hMh1iXXCx zETD7e_tX0Zt|{4PvO7w$jA>XcG7xC(N^xw)n_9f#Q!uw@N-|Iic(v&IgNU$~camqd9 z!eQjB96#wrp|-=f^Id#;&11i{XQjh$qT|nA?w757NRwXZ_%(4YH-uzD8g^b3?CH(5 zUp%T({f8rS+5klH(RC2OatUn~(MeF`?z z)h9S5@`wK-bj?pu!GIDk$B%U#!A8K_l7s3G$IG4@FT|N3@x+t#ok3Pd(m`M!II6zR zs8mm^p81{%UcHtPa}GL->WkEVr=;B7G&ikZcoEqTKag{WOo0^`!bKwmVbk~7h_*uN z=KTCx493~wz)xU))q?vE26|rJeKPG9;0cDhyI;77;x({}G|F!`;24AYoXcBazu4&2-`>jnC;vdrV21*aRy|KDC_kl!gFk>8DC6I*uy+L<&p7bq zyWZk2;fEcv!PpcpowOPp@8-xqyvRQsb(SB$&gi`oX8|+p8XHHMsq2k%uykc0Arcky z^yO?i@)cjS$PEyLQxe5E(EK$i!h;m8{AYM*F6p6<~P%=3TAWY;~t4@V4W zH83io7Ml-umgyE8^&=fYQPr9OYaPUymyMVe0B^cyh>lvI5u%=unEz@8GCA)0XTSf< zw-c&7Xg?ni4~?Gb-s$*XuqsPsf=2vwAr=4P&k?9$Toq0za#?kJZ|2&W=IwDeD&uGl zx%Pz+f(Q?fS7NV1K8&1|kltyKg=q|YOHy%`{)$|r6xqh+=Y82E>r3vR^kUDkB_WUR zoHm&i2hH{Y~Bq?Tr>b!dW8ylZscSoqxM_Il}eB%NWbFf5}N2VP~} ze}&v-T&o|syz|+~%Q|fmeP>6dw>8}4>!4u%m;Hh-gS8K2Ysm4Ff|grL{xiAYpYE$Z zfqmzQq4`5yTkvG%QICLJ@;})d#?kzXSx~6R{Q?jUl+-BwdzXs$>Av&>{W@~yn(Vps zp5eRatN|I$kM+>)>+F6YPh`$!XMJ75L-mV2%M2zJ|IgVQHO0nDVdV)*jgiVd3$1D_ z{TDO-J3E^G9J+|TjOD0#MeuG$@4Y#Wyn?HNdBru}!C1{KCH8jiOoY0Yl9E_jPf`n# zw>3>|S=xIlJ&3AjU`tsoA}Z?JY=Qg3sb)y&bXG|w15~D|NqPKyNo+0bec1>@ZSd&P zko4!CUi2xWs|L490jcZDAk7Oj?W1ej==pWi7-a$AG!r>BncrdfKe1RK(ZiiZM`xq( z-(DH@5r}APP1d7|KyJWg(HPm%!ZFwF;6At0Z|*oN$S6>)>z_?XtVrK3E29X0u{eXe zaU~%$r1pu;s-Ja1z)Fi=^GfB-=Zbbd7{jWq41Pej6w!8dUjI?5?%et|j)=QEys`#m zhz(^|jn}d3Jj_rWW|P>>@YEAw!BNnK#YIF8INENLeox%L=PCPQK2Eq%PnKf=*R_G? zDt~924qKP0D!l71_qo7JckBq?TH+k_uOr&AO4v}TjnJl=YazMMjOP8!O(YEVNY|H- zSN$AA68u7xuDS2eFaTK~L@Mc@@cy4EQ2qlH!2T*-^f3G#;7p7GV!N^aoT3DX3>3)I zE{|U)vMNTt#6^5Dx6V7cVuuANK@c_)7q+peRqW9Y#+$$BvXG*{tc%0}ZU&=*%mB3L zO<+CL2Z`1UBIK4}@E{M=Y;x+|IVRW4q@9wOf{iKSB@G?$=;f?+gsf&8jYri+E=Cp) z`Es9UoY8<|qmsV)zke^dVRS8VbvgIsqQFPi#dgDsZ70fybf5&S(~NU9opCSPDpJuu zBZN=kl*LHm^F+8!4pC)}*Y#P!>yy2UZp-m?*`b~4kJ;6-FJ?2`bXKog{sgLNk&F{v z0H{s^68wL=#NVrQZ?H3&Q9yvm+5h$%n@|{`WqfvMMgXA04*l56`sKb-zBBQkGJa4e zJi8!%Z8qVb-Ayw^#8~2NQVSc&FR;omhxC6G0s7z9Jj%*el zc1Hb}+Q80p-GxY_=)SvM49}5gk^P$dbNypP_3k%lYD!-WM(4dB?pHV$xi1c!qQgJ@ z1H2>XGi~)i%_{J#gz+1j4RXk69Tph>EVfI-(-4D88mn~-M^*ed&?BeC2fx4XzeLV| z^QvPhu(?91Rt|sf&ByIG0k`DOKT3%9=kT(o5{ywISG}?q4`mCyD|8>rYeni4Z#6ts z?PCQY_No=@A!0z$*XP|FBlzW)gRedi0p{DgL}}sC-#ge4YpTGMxJ%<+;%;o^GXK3T z#(-8k#`hHkG03e&d|jbr+$8NIMIwopqehw*N-b=smBmk%5=wouCcdPCMKW2lnb$U~ zY`b-$kRs2X^H=TXEW7f9KBs`l{&DHvk8C zxPR}riBo>y0W`{K-pu%of414}3CYGdBEHx{6JCRN$lNYad_O)u?X)ucWDcg%Ky;ru?gb-O4E)Be-QfqD9CPC!X(EjJp1|UMZHNE6HPPw-h|_m; zf7bZxZ~hdw0i^$7xhc$t19eM(08f*U@0n|6f;LvjytLc->R{ZME0XYZvK7P}9bd|> zn4yi@e`S19swvHxZbi;!$7Q7j(X3!IB7h(a&zQ;X)Ryzp+K^_YJs&N~@$u2G^zuY? zcdG&-bE$fy)h|W%8fTcDk;F+<+?P+Klpvj;Pfc4)*>eN+#=8?f%$9&a`k{ywKy+!u z)*>w$i46tKt;LJvM3F}ayl;ttfjC`5soRL|_6h8pk?*zkg3;#T|kZ$rH)A$$-L{Pe{l#ljJAY`givUs>(H2Yeu7 z1FAUPt_y}*e;E$zHI_VH($VImg@M?;driMevv`-d(EBc_`p~Iitcd=5izRP%}uiZT%4fbkAO!IX&$lOJFO6{tn8E=cU{tgd7qsX zh~Pop zXG0DiQWR!9O>9S;M%%N*Gc!rcgQ70+HW`(P-XZj7GEsdkz*b^@rl$OxUnQsj`;9~Y z)(q+2q`8F{J;d$tBIR#~L=oW9lfxql2Y(POgk<}eZhSWR)sE&Y1d=p=;NYSZnWtRW zVsDd4E1TPTp+pcn!#$p}K>L<(aWFF%3?@Aw@MIONpgo9lmH4i}z<~5cVDJn1iFR$) zOKNkSBE=Am)bzZz2)%5Aq8nFzGQAa$19=DHPKmr{P}ezql(PH+E{|g*I>=pKXNR5Y-g-a zN*Mwtx+m^FlBE5#a^0LuCM>5Ap4?JJUmR%@)xFVWvlxCSb&I^OZV}w>%?Uy*3ldaLqF~0%}WZ)$Y41S5a_4!>xcjKpS zqP~Ud$HDfwGOA#7Mj6*Jt3OA9{w3Nc(V^!ld);Eg?eJp0IiT|u zCE-?PiJ^AC*O2-lZUfqijKb{j7 zQ9%_$AtLe7F6rDM81-O1VYq#in&xn_c29dR-jyM^sim!ZgA_Kw$wIWt#ls>1#^ITm z<04=^Ri9kYZ82%?e=%eyK`fNNeUl8z3xYepi`TZ)*#F1|5prs)BNX_4E$pJ?R&+?k2mW&p>%+|7Gr&Sf&t7iEnu^ISv&Lm zSGN&W1K!p4d|&DJ9!O!uqX)sk?G~KduLX9rXLMtG<*^7w6M>JTR>4}!{9j#uj1@>^ zRaNMp{$B3{BjASF#`l1%?!xAWc*Y-i9dW>=mu#g^MSp*W-^l&%Z>Adpyi2!Rb6@%| z197hWDTiST{Qt+5Z~J$3J*cd%j@>Y=V8!F-=bu|%){BTiPf@140aCj5Q)3+bBp@?{ z2U4ayeuN4A3EZ4ae_QJMU#3GN$${(mDqk+$Xqek>2&KEN7ANw?pYX}M#9YI>Yi8I+ zOjTnhl{K$#yKMTU7j5SBw7hnT72C47C^;3y#`fvEF4A{0-d?A7J%jhY5l>PCmODiOSPsyq5RTsj=C=?0 zj_?1M2&D`Ggs^y)qIQ3S|DtI54c}a`U2kF9r_{!tVIIdV7h=YGhk(XDb9H56pR6*> zXC@-+ZOh-}cx||q*n5ty))9KWxgfWTr^fiHX`#-7g+*d8_Pg^T;mLzN)5@BXiAet@ zQoIdLz%3d0lr6#dNlG`r4q_TU3fY0zglM+@u4?=*PWt9GfM_oo4y*KVpu0uwC+_P6 zd1gIxmn(c$BREG-mQ$|Eq45lwcs6d(rTr8u-AxlEr^q+ds;e#>YK1xpIJxkwptc(_vZMgk)yEN?FO% zdJUteK>Id3G@{xZxI{+qB!1@GaqKqrowgQ|n{NXJHxcT|qqXmP)kIoG=fJbsfhM+_ zU>H$HNwPQ~V$EZ~-n%&>$=^Raf@=8!W4JB=u=sK2*nfu*&TQ=Sqs?aDLwN_o8rQ*^ z3jh%Qf4B#*PMqnOtleRb8jg+elymKwSIz;b$k?8QT2kd@dm*^bW!+ADQ{XKfw!Sgl z8Szj;2u2j%op}xD^}*7flhY)-1B*PZ!s5V2^TBvqfrVlh0MUv$&T1IEIRz{fMMg^Y z+i(4yS^ugzb9@u>3hlS}+g+Gqi{S?iuIA`ZKoVG|y5zuaK>&IA5-azfyV-eb`c1+k z*<&nj^{TUBnB7kee-4k&yz*>jy9wN)Ui|o@2F9R7rb_ayt^B2Jyp`J;D8)aa-#E5* zXmKU5+HE})z<&;M$E!&UP>gi{cfvW{lgCt?WSPSi>! zn)C_a+wanbfa^z}QS9Jzs+$4-5>1^q&Hf7AZXcEK5#Snc>@XjD`G>~eT>f9btfR31 z9tB)UO{uy6-5sYrOUg9fRu(=<5Tnu7%>s&#J4pYW=Nz~o8Sl*|zf;Y$>JhwzM;H!<0eQjb0>-=wLms-m4db;Xq5cAM`wEk7lun2-1p5fK{x&~M?& zM&#zZ++1Z}E$TL)tl&6jn^3;FwH37H`ZmOTK=C8AF*HQpX-oUTkGy{1Jmu~~*xbr? zv`xZk>j#0NwJGiW>Daw9sA%Q+_wrfYZVaM$w8n1VB;9{vg2%K!ty3W&P*V7MoghKf znLy(XMd|BxLE zgba(rqTGOzvfH*OBJcBesGJ%gEu+3S5e^VQhTXB5#kpfZL>j?!MbJcb`f>*bbg%G8?G56kFq?@ZReu&XZh zo@RuNMr^+xzy`x}3vi5w8rbQt{AZ|E6qLJB2=jiAP=*OFDyG`A$fAbcW|36aHk)8O zWd>c5J44^;%vQt8dnd}epgRX{y4#+3`_ohvw})%ojq2Qi11iW)_3o=g7SvV7zF3HA z^4@m(F=0RL__R*SV(2)Iurl3;CHsQW1YM-XjtlT0J=&@tJHICK#JM0ew0Q0S`i^~M z`IBzJ01vHkcIN;06y^WnmZB;t4{ju}4()eFB#sr&J-nK9XN&_sG|j6*qWh;4Y14ZP z(^8N^EkW=Qs~b*IlfP4|H-?Pt15Z=JpkBisoW26_h)Fx_VOkGQ z_}&ACo)V~x5HE?b$wu=2e!5(aTxj>Vvl?S`aUl4UROp{h+c|_j*Nz_qi8riWN=xmp)>tD;vAk|ye`;~+Y>i*Q;cj9Ym zRjchRoHDtgMu%u_NzC!(6Kp#SfL_W^VY`Q=;f*hPP1znwZk?V}E}lLRB9IZb)Woa9 zulSMYF7i`oam??|F1&ZV>Imf8eBkvd^AYRE!>8MC3t-pZlw!{|A1iAuj&qo1HZn-Q z!)45DKi;j%l}nK+++=R!@a*l!MxHGK!FmArS)K`%|DI>yKA9q%Au{7X3bm5}!F(@X1i9y-yD{22182=Ty{*ih9!{6R4;C48p ztxhi(V)L_dCy@~pN;C!SoY73>kLeFF1y=x9Nt` zxuny|4^e=gL1MODYkeTQYeV*x`7qVo1$r8|-pP`2?EW17iOgw?xxMVmvc*@H-vlv0jNMBeBa72anAI=)l;e`41{OH}uzEleV zpCCx`9c`g_U3s2XCs?YSgR}9daDjhyNK@I}P;KZ$-2&x+=HMZ7j)n*>6R$VtEchAV z7*Hyb*7-;J%=Vr{nxGdMY2n(26S+-hS&f3mQqV3PSn9=)*NZ%#{ncdP(d@}pq@6ya z-|wWsf}FM18FHKKi-4mfKcr~2Ei!bz6~Il(m>AGyvg_ND_xr;s2gXZzNT>T&n<3(u z-6*fdYJcwtReFlUocc()8?qyK(k@=O)#g)$+{O(&%`M1#XdrecEIn{>T{zn#2dt*0_9EAR!OYzmIeKz@Y^Ytt`-ut!g)2Bb3m6nf!!5%cJW7C+9aRc^p?kg1`AJM z{qo?TKO06c zH!uTbv;1K2K(95u#Tlx$R|5|<{_KOaacYTB!egp-yP@Z z9_(bGXkAM~euu-Wt(zghOcHi{p)3?wtUBDiiEqN4uK0Z{h;GC6jyB!A zg?Duq22|~HLW?5BSVV_<$2b@W^y>?3RT}($9_Q%JY=5&!$Rt5-%lX!k00Mwb zwetGPu~3x7Lw9x5TOci^QctkG1BgTAcWZ$jhLj6T+IKY?3k7nLzf)0v|AV?D9uUqH zeNzm+xShXtN5h}9F_lMDl- zM(7{9fhghNu1lyc^XDb7ZE8iEaqW%c<2s|+u0tzE_yE}q0$U}$&9zTP;!N`L^?Cpc z-0p>%>(^HBEg^tZpUrtosW!75-Gpq;df@jGU$j=fvH(fa?#k$!Pu51ph6kA@TF^G) zBPs=6pFYIJ^@t}MF7L*Hp4~+x?N5|4ezeq)oqg%86kTAN*q}Z+o?4=J_++>J_+7|C zR$}I?ZQBY^C*z~|9(n7@b4!3bM`*U*!*~wtanZ8S0z?EJI_CMoU(2{{dB~Qwb@1q&MehD9%g0bwqF9^PEyAfMo z0(6nFMhTY=5-&*P-q9dn@*b3$-ImtewVY&<53Zq=D&hTBmSbug?;gsa>0jb&%*-O8 zCC&=VOY+_{D%~6!^R_H1@6<#qG7dp-M|{+Yh`pBcy-9?xub5gTs(w(vhQ?%3U&`Gu zpDbPYSSD1LbihV={H*tR)msBiMZ5W!OJ_XI?%oAqJVH!2!rjyXsUDQpY5~?Zz+fsn zfTX{=gDN5He<~BdQ`CPKD>>EeF$4!u`1#>!bo7a93*$m*RW4JfR-7zpR`yGir(w^- z!#@im!Ub)X95=NlO_vG)cmA$o?sPmJ$)`6aBQ*v@uHZ9cFCT%?>`c_)<|eJ_MonU> zZ_xz`p@8WjN!S?6j-cC^yPObPoWt^7YWOWJ-)_hYR@nDy z1oG+EZ<(mbE-9-coKwky_l2y%Y}^ei7mJC*C#H?Ft(j66_P5rA2}Q~i%UD41Gu~nx zcL$cQTB-MseDG%SPtVz^xHEI;biL?iU_f?_X#R)9IdxTi?8f)IV((w(EhHp&C5 zvz`fB_}TKH6yLT0qV((ZOteWy$nc@+=VZ-YJMG!3wP$YXTUjHA7%D{7rc{>2N7mh3 z?+2x8V}c)05yLlx%_of#i>csZdG&Xs%qT>otaXeL=y)TNu87W)m9OT_$jesd(=&T- zAGMd_3vbq<4ko@Cb(ZoP-!93@Z)-HPC|ffmgLn$$+u2Lpvhd_N|4S8H8Se!elD^|@ z1B|s&3A3I`RNuldGT*RY(^S*XFL=ka`AYul}~db#Dro4x7q!`mJdaEJ?vS`^$A{)S`Y+S zZFIqgz0hCpN04A`>25(tgz>GM6pzJ|qHCV{-K3I`R0$^QPl56k!sU6c868^NlUX!@ z`$X^KZX`{?K#3w!o6!zS3~{2zXVoU-<^5E-=L%!Mcz|b3L+>H{BGc!H&dy5UKP6JG24X$)znzOth&_5l*6;pvN=D# z@FDCaxf-D!1n^z2OvqfGTr6w*d!B#xjjc24qL|3iTp>$M8m)R7lfh`!ckd~7&b(LE zKd&&1`B0^w``OXw8suRE{wW%k&X+=sS$#|xq%*-)OI-bZ;j?Z z;jIZWXlp_UcX9*HAjfyUsoA6%y3tvK&an~k<>9xI&jqQ0PSv%*Jl|!~ACK|Ol8UH5 z7Kuq?-8d_|qITjN_a{PV-vcQ>5ZXr#+6-2w4<{ZzAn7i1SSi0vEq)+wKHF!pB@-qM zj8NGy4BnM-0xbDO7F_acbE)0XX?>dHpxaxDZHKjy!>U_vWJt#7tJ*^{$RUH90DegA z^gy4%c|H7E`n@J&?mJY@9ZS`c*alt>GXR zjp0sji{a^Q2W|8IO!sZkPg0}wLn5Z!I|sn%T9NbIY-{EAHJn1yH>tI}Ow9=!8nN{8 zlak=p^}s1SUF?%@U4iytW>m%oA@lSAN{lvGgrA?m1nHAzyw{is5^CjXa+tpO0HH{t;oS zd8#n78R|thSMEi74b_w^UQMFotzYE`Wz`X>CLjt+<5KNM2-KFmXDiQJ=S>S20em!o>FI{RE78(QO@(6s-=8L4vUd%_N? z1)ta`*)N|z*AGTT+V(z^B!+QomwkKW!2qZp;3uW8@~{(qYekb^^6jwB&fX}GCq>M# zs*Y^N3xzfEqPWOfO5QD-&NgJ1U1~PkZ6V3SgUr5uV;LD4aIFf%W@7_3m}2{cgAvfl zV9_4;r&9EPYG8jK{T^dw-{vIq=Rl~iroPRi3+0^M06#2$tPk{#1u!oLuHO01^s>{1 zQwg9<@asUlYm@QX%}V-clR?)P>Wa8YJh>>?b@?Nhtu0AitS3lh=U}FjtLP{&0jwna zZjt8KTc#^t;a;QhxsRww+>h)H7c9Q#Rs}6y;X8w_dDdxm*{#U8uqP)cp3mr4auP8! zQSedT;7&y$dPI=9&1C7(tr>a-E8+^udt7R0tEb1$MV%HSg#AopjlFA*oEG$CS22_X zH_7*T8j?4p6H%`nWwW-Ta+1cIZ)fymR#4K|W5a7>3_dh=wLIF!t#eBNpE6yG3J1DZ zImmGpv@~lklH|R}wb=)NXKT4j*X+^-Sh^jK(N+ROutPEBF^vJ)22vcTY{fzx2HAoD zBWrybgH!xQHqO`B$}BA8G*v$;w}EH;Q2V(3t=T)W9zQ3Av>2&1RdU2oM?(l}ut%_= z?vH84NM1R4deCx}tH8@EU zonJ$dU7k5D<9&Oy)kfhqMyIujDfVc{%_aKkz*~ok^i7|K5X8u4v#uctc4ke4zw8V9|;64&|Sywx1wg!bmi zWvD?kY4SqT_%k~Lc=Y40Gp9-?HQ&T0+$7eU`)5C8e|B{t6hJzUZsiSK1|n>9hu>FJ$~)uOU(aU#?=Y|7SFe?2xpK8Ozf?Eo0uJvLW^Wt1m=Lru0# zPh{q8#oX!yvdd?Xt&fDzaM<&fk933tXOhn)Ip5f^AvXRtPv{jB(kdgIV zA3DZ|9L-Okz4FIQl76g6Hna}*I?6jQU4ylf4&PTpBigvNzC=W2sg8bssT!Qz-{!I= zbNmnJl0rDn2RdAt8LXVeTc=ug6SlgUiunZ01V22yY_APr+#w;|5HsH}Dm10MxTp)Z z^K$F3Mk^9`CBAwuYmLmTPb6K9-M_5SEW7JnG*6y$J!n$)$jmO!DB%XDIyOd; z+?=c+!%T*4UH!{s7)vOI9GK_=Yr4_V-u`>{ycq;)TZ^U2&2;JiO7Z?`RQUa>EEQ+2 zfA%9Ld(>NAXCxe$VhM`jaF;F98<#q+*tjtC(F3KhR>^&}h7l&9Wma+0kitJ(b^WS29?Hwdb3Wi@=r}1s z5`tk6I|=}Ucr7*a=2XCW#C5IKKWDZwqD=XYm7f)E(g9t!CRRn=byBN?)Yf2=`zL~` zT(9ZD%=>Z;{nAWZg}p;jE-npL$Z8|EKMGnlUHP`s8HJ6dfRSxIHm=L}Wc6EN0jjyb z;wLOLaG)`~Y-!ii$<(i>N6*C|yUya~HJl8C?jv7OgR_1NZJl8}4|#+R;%b0mHIexw zW1=E2qHwk&q0m^d_WaD}>k=x`Uj~y|Tk&&Qe7xL3 zJ36ZtD@)aeY_7ZiORFft>|jHm0Wf@G1SupgQ0K3Vs)oV5!3zF^7ez!g62>12D&JRL zR8#S}v{)$R+nx#&J~lMBolMvI=Fc&B9)q3~8NB{M2>KylXMD+z%{+INRYO0(G;}3k zAjXt!mnragoj{0j@K0;NW&49{mRZSQ27|4Gxty!hol zP+CyOR9AQjKu-0%Fbm+3Z1+PX`N~w~u<3_d<^Ie~^T{LzfQCq@?AJ8>rHUwA+JH^h zDscAclD3OjK@=N)z1eatyojdbrH(m0P&T0H5ah;e4)nMKCo*g#Gji|Pw3FM!Z{zIV z#oKrQlt2@ul}*Hewt_#RwJ??WpHM)9yKsRA1N80p#`xP`d$`nrXs8*IwURd&9`^;& zsN|Xrt!Fa0TMT3nK+O2?-tXXO5=;8h*bfBLljy};pV=OhOX^cB~)@bEW= zHN}HS=bxROM5D%jb>o~X6A+hQx7@a-KK=S-sSw^YI(lbrZq9P5l8;u<5^W%2lU<{z zqB4{;t~R(7CpTg+k`#i?h8a8ftF_>7*Zr@5h^l@3oEV~*v+#w-m|AqS4Ef1ja@*Wi zf6bu!R+tDdd;G+!mnJX|kHh!cgNeO9{5Fs~V-E&xyP*enZGt@xLxH%MamNo;&2+rh zyt5T^*|*rb)>@v~Lq>EplK+LRopqSjxS;7)33niKM$;#4vHr<*rlZH5;%jU1R8nk( ztc`4SuD`~K)2}S_@#3qP9QvAs&#JbO;c~Y@Bb-W+OczX^*D6nqmHO0T(8Ey<_$fwD z;Tl;b@X!*xr26DK8eqK9ero-5WHtK8jU;6AtysIF6})sZCLEZgW!*Tqnz{;K6DcnT zij3w*LkZ{M?s7AUZzs~m^>>z{W`j*B62ANq;=u**TjVxm;!-&y`=#Ze;vehp3eN9{ zTWXJ?fKzEgvhVGVIs^u{(efSHyKK3?D>9tw$=u(CXRP0mb5vROQiF-sboPLHUKs6e z_zqICpj-`e_13&B9Uf`l>z?t?_PI`KEO}SYw*{N2c*SjFl5oaYP;srg8XCWi%ys}W zgaI3y&3UgY$q!*OdLPUKdC~w?6(DGIi(b5RctI@a9F&&R)~>Y_(CA+xg8tQ?4qr)h z53Wm1O|2>q!BOjb1mC&4+>`jZ#G+TPHXe;OQHg!nIA2s;zkjm30AJ6mP}<;Y4Py!m z4-cn^PrhwD4P<8k{k8^sX6;}7$gIf*VymuM*XF2 zI-gtLPw_tYG1b;`D-J$pkie{dU~)Y zUS66m2um4@Q4U+f1j~umYz@3_I)sFF32Y?ssPdhKka`^HPclajvWmK1sT_ITU689E zAO$9!EjB4Hs+RB0z_98|hg|GbhQEP!SLd0jxZZ(;ccP+V1Ke95v>Zu=8CBI2vHKm~ zz4r}PA33JUeS!O7z{Na86C@H$7%`N!a>vJil5Z7{$iyu7|!sL6^E90}PO;WXrr2F|Y^TTB5z zyF8xdbC`h~lKF2?l)MT{wF9P-6kE!D{?X$ii|GKgw1<5Dui{81Hztml9aR!CD!_>A zraj$pAg60}H*fBZxG83)7*47oQ~J~{iq76hJ{Zj|Lq!tv7wEA{J-)_Fo|_JW^}^-B za@{~@Rl{{DG=8vokO62@!zNh&)vX5r?g}ufeqb7o{y}tmyBB0F>iU3IUxdE#E$(c5Ii4R{R0 zuTS&{%G;*`kBw37=`nqXZIUyDhorLDjvyc$hohlSOOR_m2x2L}&c>qK;K%!Q$WT^L z)d9QFU_HaL974H&W?5@m)X7FOjL%AU5e8SS(p-tNL+2M;V+m`o_`d`BF!j!uU5+;Q zPf_I&IpkZP%XX%|+|c&M3*=htjifbWErPm$Qknh9wM9Lx=hqFx0*Xs?gFD01iSg(- z5pmjM^#nByY^ZbH{etUY*cb5-<(izB8lPjEGIxRR&ToolOIbjbq1W!@0NCXDgoVG! zDq}b8{8G1q{_#b*Dqw1y`P`;5i_j1saVx46pkxXPPCygcvpN?MTEyKkbL3i7o@%`x zcU;XdH4m2jri}{T6i_h_zKn}|9`1e^>lq+Ed`w}Zimf|-xUv@-?g+DWNppKVv-=5& zOw43AUdfu}eGt-rEc=fNPp(5#_cVy61FDytDzcEOQ9;*cyNZ#DXPcXNubY6@f^PRG z_3rhT&$s{VPAtdty5(xGhM1$a+eW2N&MI&I0(T z3LLxP-;(;zx;o^)8{<=BR@cFS#%sgkMY@p_a0Jsp%;PuNgiM>WuK1b+;OwfD&UYdh zIixlJ0yvSz(i1sr@YJ{?e=j4M@>!!i5iC)RJzB%rx7M#~@)+^(OD@ta@-|fwps)VX z`0L8prjg0V=rroF;xH{pdyJV7;5u=PGKUk5Hw$PjwQKIHpcRu9upa_7cc9>Y$3m?Y zJ#acT8t$aoEss2-EYCZ!zah!cz}{<>WV98&#znFCF-&#j>Sv04YpHYvR`-7JsJjAs z$qq!Q91QJur%BfIM_T6*hNBg%oyE+w!{?jEMa{Q%kkYYbd*tS|9@!Z<(5zwG#ff_G z4kdNQOg!O?ULl91uq;uLqIy@UqNN&`die8Ak!eE{V=%MCfXAyOcbHm2%~_F*ZY%9S znjIP-qtTC5Ld5<&p@f2`bNcu3BvUo2<$!X1#;gmB`wLDnrGg(u-HJ)Wn|Aiw#1_fv zc+wlzz_fR05vDVQiu^wQIm|nTD>!>L40jhfl=CrM=}M1UF-f*QG@a+?arnkIs6?Ah z?9}sT!RG0Cl#Y{Xz0rfRKMr{mm(n^lDDHNgG7ceTNj?xl6mKcN{g9Q)f9XlF6Se+R z;-_Ax=TEO%$7W6+g`{3)U?}`_kk8}j&Z{=vhTz8^J@43BIUB#5P?guq-`;o5BC3DJ zM;x8If_q>a*9#CjPbLbth`vY`}z)bpo&>vaWD*tOQ7}hHNB)aH4c7Mufu!{uW@TZ!_~J=)Icj#>N3j0J&=@P zZ$n^~+1%TwL~DAvty0y09`JQ-1L>sy8DRLG7yo;9?>|5eB*Gm1-;P>&Pds)T@S57m zwv<1v1jO5<_A)ThF6Uzhs9I~*t1;KU0w2H(UqUOPyY48_g@ zn2S&U$Po+EfW<6omSFEwU)YwP!MGlUE7d9@{6A9Ym-X;+VND05;4^hHR`K59ORbU_=uU<#)66x4jeJ^;GRwGX4sUJv9+C=PXoZ+4$eEawVLdC z>qhZ34|6!}I?tNnK11`6Y~OkpPUJ8$*22$M`~`PR?`-I}*60&(CTNJ2USK)z+_CQL ztfN5ISOs+xR9Hl{SmcuL?Lz|eXzQeQ4eX|EaRF&(e$2Os&;rSsH$ST;onaNlOGdT? zkah^gd?cX?5IM~|ODAH1D&Ato^duA+zD(wU{q-x)nyo)aRT}~CcXim>(NgvFy58^) z@#N$7vfdFAOt#D`UXh3zOPyVfZ(YF&n@NLd3%C_N@t46YltQ5(z_7G>jc<+)P1bR^PWYPOUh~GZ!#A^FF3ufR zoXmkpZJvp;P1bocXXM1s3-_7Hn?8Xv!9ag#*`o@3(YL^{LHx_TygRJ#SWoy?nh&3W zs<@!h6a-%c0inr|u~LJ`y8Yz2mT%i}iHPzYK$OP|Jhj0JB8I(eFa7ZTMvBhmEy1c( zF;8M^EJUmJl$&Mmqpnj+iK7kCki`sbqQR;hB^`fiv1%dvn(WjM3m)!yM&-(QQb))R z-5Bk%gelk;6?x^Q?6VLqX>77R$cUbzF8pk#+qiH$SwIvqqqwgmn{~_@9g^`)IWTE- zK0kDna`JkZgKCK|P*JvE``TL*#`v1A?t8ZnVv#j9`<+)WqC=u+t#(Sf{C^#o63@k} z=vo4h@z$@iWH!b%fZS|j9FrviqTI->cWYM9HsusMt|{t^_EcFZ9$^yS5Xbarh(Cy) zqjK=>m{89>KfZdc2m3Yty)MQAY7_v8&ty;KMobK+D6(RKKT64wLP^LDC^=?4kKN+3oh7&7IzHH3p)0$aYd?c@m8ca&5J4c*gkGKW zP^>>&n47GC7Unqa5WoSiP8P_z;sn|%*Tcha)gF%MBd6{)Jg4JiXTVQVc{t`ui|Lkr zbsx_1R}%Oi)mAy99zQ38tdhmwN=}o3_Jd=|)3Jr5a|Z7cQz#YdMiQeieaROM$~Os) z<-4nycgPOPI}a$L!XJNnUp@fiRFn~C24`pwv16qI%0|2BA z8j#!&!29zuvbquxM8cNnD?ZBkNcEJ9eD(L2m`Unb5r+LdL9Uo;GEG+4n9kLxc zJW#}!@K}Me;9wPyW>xSrP?WNg$`fZZBr-NC|B*sYwtf0T1V= zB|^e8-oKYT^>r-DXK5_g0*AZoDMaZUD$XeS9ol!C(-=MK<5Ao5k-6waY=i238N=Nb zX1u)WpX|}E^tM{fh8av7pmXc(alkniITX0jdDUQj#e%%=tIQy9)O8JPh|ocIsc9!Q z4?}UHTdhceALW!ijBvdoGrX_d`md&3_m{hOf2D`fPHrp$je_?pZd4>-R(|E#jTyY( z#fbkz`XS^}H^PQ~@Y&jf%m8fyOAM}^`z-t8QK&^-U+w5ZH#(|me8Dyr^9kz7jTb}; z@FK5VvS4dPOgrWs4p(+Ej6~=Rs11?(yudLQ=J{{LIN zBW(0NaoIHi4nZn9&g{7G)>K_V+*qdV-9@KYq+Od=0A|A6j&9^c3SUsQiPMmsdMS9Vl_Us=vSgCMoGi((v+aWlIx9{G?)STf?N|y`|2bf_^6c8prG&g+Z|qs4)qqIYinUO`TI zOgX8@Vb*BjlM(fK+J8C(qAfhi-B9xieP1eZ&-DS2LpNMj-YLnt3VmO>0np>QYsp^$ z@smTm#$-0x*&a(p+dw;^hI#p@)CQK$V*$}I;25utN}z7i)9?lWk~|GlZ0H?cqaP!A z`XMw(-Gb08)p*)T4rtPki87lqK1D^vR$m7P2kksPkdXDzSqRp%WGieS|dMMroOu-2*;vV0XNTO=@9jso;VJ>joaa-p^zd&Hm-|I~FZPJ0y-2J+G(u-I zBpFL@LHQicl7IEBi{S>IJ%@0yu-rEG+vA|}E@~I`HO&16U)h)Za|{Vs;_5dnK)t;; zR@AgsnyumCBVbN_>=g&*WZy1tpv`xp#DXkKCC&`!=uek^hc7MjaMt&nTiABYguZ0t zi^;$nEF8y$5Rqk?d#RBc>2gcs4xOX~leI?^HdO&?u@A8V$(V$XR1-usa+O$_V@~&1 zSlHQHMPE&PL1{&@tE#wLNhdJ*o=6j2DLiZaI+7+!bg(w8oO@>83HJ&v(na5AVZl+3 z;TdjH%kodU*eU{UK@EWyQmJ=1B>1Y(i`Y-);PqxSedXrnwh*jGSR znRQ_+qJpSMgY>0aN;;GV;Yvw^(v5UU3eq4Y(#@s2Te>?0q>=7!{&SuA#`$Kx|Nqxw zITz;MyWaP_`|SNZ&)#Qm6|Ow>ZP2z*oso{%|1Y6q2^?djK}ELz|M)2n7A7FIMr$?k zqJsYtg;`akN(G#Rh9+DXXly)x(4KiyCe)mv*fo7Hxi1)R5tr+VCs4X#VI{;ql8fV+ zV8JOrJXZqD>s!>2pV zotn0n`7UF*%=+RE;--%pYA$-(f6Yd#SZKw{(+W9ER#{W&_a~THYgm7y=iQpF!I95+HQ2&vJ)6>1UtbR%%rkDgO=X2~ zDV2>-cwfUDw=gx|pN>0(GK*eBizv@b{7iQNLtrpmD<`(;xDZw`l{&F$^& zvN+!{ei3-1;S4;qG=hYcektpASRw&IVapFh(nyKqC--ok> zG!8fIX?XegewgRAYJ5h5a&dJz(2DLKoO~-B9AC_+kHSvnsDV!obKiK91kU|Bi_CYH zis_*xRNJh1dU+w5&el)kmg)D#7*3YbK*-3-kR%QI;s#Ub^>lS{UC0;L*8G&TT(D$i zWkp>?P@t(|A!87QXfkYY4MK=<>arDz#n=gt2{@DD$*fCcAS@bAYw4MaIoQJcenCO4 zYs0W2w;R{57yfKyCV855)kjaT!kBV)BjVyrmEMLw%K#p4e2h-^i?+7*`@>aPhW$vc zg-CJ!rUBVit~CLda~ttA2J0|!4J*QTk&%(<%zSp6xH1X6M%(^Gd`@p0Z?z}C>}?v} zoo_DNkc^;|@bK_ZC1;vwj)=fySP0)toXOXEose2kyZ<0VCf=CLV{N6|S~Eeu_-6kD zvp~brrR8E97G9`iLjn&G4R6(AzLoUG#5K%j=MbK?Gbw znHclpkX>b1x>S_*##o-HVvXI_`)-B`Ts=NA1i9&I+oKdaHWVfh{$oHyfAQi)%rom& zEF}^M;V{rndX@j1*(qeWpd@dGvX7WiD!J0y^)Fbb){4`)_F zxZ%&yz6kH5PjttXn@*&N*ca#H7#Xe(BpVJSdDxbPMp-ZSCl>g)q?Z@}VLjoKLpOcbqge(CGJ(a^tn z5kF8VB7LGptt5=_2GL5<23E%-v$%6~-*VlMfU$*=?l$e+`O!Oy&r!vT%7q#YO5}EFSqeZ>L9ufjy9xGKftJPa zbNwFNkpivRjJ|4;uKIatR-$bhl163RbFZ?&j2NTGPp|UjW>{uXMGM0iS zp^y!b3h}tQwX`hE$Z8x%>%+TYi`}_zmFw9#rt4ibud(MPT8dExE(i1_A>5s*ndGwd z!e7ueozP`a`A?HHOS#}cfmOf9T{yz{dtowVy|8Apu`irb+*v-eO)N<&yX0hc(DOT6&mZy(uytrDZ@@ z03I1-Lx1=fn7mGJ47;etJA}`JS%J|*51{Bl>*-ONp2Af_@)9d_CXwtL5{N~97*tK6 z9pvNZKcln;U{KS_j9B=hPbb?o26KY!18 z{Kt&GzyIn!1xEcX9jo=+{c8l60vZ?^8t0Zu!yA2^ncE~E``m1IDI6LjYO5ZR$?tZ# z=QB9}Ma}$0gX>jbF_-*I)9^sD&|sE4)qx&u8a;DGGw2addF!W4-o8NTXG*?8z+>0L`Lq6FyV-VJ!yuxYj4ZSSj5f6g)19<35I+9<=K&ds zXSlC4gk7au#yh_G`5#M&yAX~f`)+<-_~rj{hOhm=mQsY6YWn~FHrmg(ykQT#)MSL@ z9auc*19q1{_y@k?*38UIhAbftfU&;7RrjpERv1a$$4oGKZ6XqZZH;MvdG&^zA~aGm z<}nd3l1gfV@O})pZMLS3Lr-7d7$+g06J|BR;>Jb*U}9T?h3|w$^3;^VTYCmG;n>B8 z$SJe&yZ(HZ{m#s1G0Sk_oUpUiwJ|jdkwku%L+#M^&TuN7@q7)@1~DQ>L?v}gZ@7)Q zK*98HfB)yMNlD{-;{Rh^wqFS@Wt>)`M8y4Y46gz!32fyFk!H|>d^-9{>p#{V{F==I zFsY*BNtNASc54p-?(57x{FkqS@G2H)PK%l|KQgPR)M+5SfAAdi;ZwsvfMYbDn(qOQutyHCZckd*AM!+L|udy!}yt=;FX)hbsG>Hi4`moNO9N`Z8dgCxydMLO-;iO_i(Vk|P=pCxaDe;faw&o;^UiEl`NP@MlNC~58Zjy|s% z>=ed=@y8W^4h?;09ZqjO(IakNjg_%PCB`w!a$*quuWteSGwBp;Fswu@=f52EXLnY( z)9$I!iN^P)8Ya>(ozHsL{K;W0J?d3S7gJxN0CD#h23A_9<4r%XQ}BN_u`DAAK&j`` z&5*rt@Gjsr5hv63p%D=Zb8`EwS;AFeJ|8BWwtsM`SH8auIpCD+qvqZEQH8P7a8sX3 z6IJDUxH<>|U=efhZZxaGzhEpPqI~8@4ow7$F!L8Tl%ugcEF0gvP#n`I2q0Q#h9EBhrFe?}+!P*2q=|u1Twk4Ny^7-; z)K7q;Sm`QyZ$J}B*w{vJwE-Uf%I91z`{mX9_s@j&H)iT;rim{*qP|a6ShV|N(UG8K zkkB)R)A;p6z@F5eRd17i%HdqlV{Oz^&F6;z-{oTB@ol2v|J~?rV77_ z;dLyQW$X_`dI!jke~>UX9$uD)05x@{augi8>`vRy;)4C^VdZNv~enh-=-}qavE{YpcJvM+nxX@>?cHrTn$wJ^aZ?I2fd$(26Seh6aJ9 zHmO&?Z{Tn2NDmO!tj&j92)}Qg^9~#eFMa9^$aNJMfmsObW^}gBx`11K^T(}1=mTj zbv8x2Stxb7zLE*cIw&(1|2 zK<@>mu!8#u{KIq;owvC#P*AmbqMng#A9uvL?0DG zc%^Br}2*|;isbg-8rrnFgU9zdUS-U3gW#Id!f zrw3s$t}O`P2UG`MP<_w3n!UO90kYb+j8;G>DWtv#5;++ItVGVOXfy=kw``4_SqU;{Uo2h{S$TXRn^!r~!Lz8>yC@5N2rT$zJ)JLax{!qK*Tk5IT&{{hxmxbQ~Vq=-`3C`MIAw`mg2q`wOr%@!l|jH(Tvg7I`gP6kIA) zXC7x9IbzC6%uodKxi&;srIgM?`!|a}))3iD2!mjnb;yrmgtT0al-EZ*qv|5X}m$8d^DCtWewCi={Km-q9+nVGM1p`W$& z5Ri0YW%_7&J3ml#h1;@ahl!Skd4t4Sh;pq%5w_hn93TZ?GnkGw0FK@h%h8`{0;)4b zLx{{k)bIHt0+p1X+Sx!*SbpKY#3A~=!taMytrzEurUb`H%?&Y>1k7V*EC9 z33NQr-x*9c`OI%^@eU@;xaiTmVVp4PvAhOtTm>52sd=oIdpaF>f0$G=xAG8edXe_q zv3B)j&T37eCD<@}(5?^Dg^-(5h6)>Bb}^xWx;5`&O_m(U0noiMFK!W zf{6vpN7UV}jw)9YoCAwfd#bEgcKdjC<}S|;?pAZ=?2==|bKBAax!k;GqQ4)Nss1#j z*-%=vy|$;?cH^1zv1#o3?K}69#6pNfr@t_C30|M-q;76)1%b$e@$TI_(fHJ!6p?`4 zVWVaBe)Ex%#K*A7K~>_Pr2<@O2s-jrk3Q zvp-t?H|mEYj32TNsx0;a*b|*GFT2aj21^wggy{xJsasGvj*WDtdqDhgk40BL9i_yY zHd6E$m5#n30Xl_B(zQ0Ynrh`G6ip5q(nnL*{GamhLl)*{a9_n)T6~kC$mJ@&A+R&$ z4@Lo6yd(Kzw=26_G@W>D)R1`>UboNP!bdEWeSyPhV4K^h$-R9E{GRw9e$Vat%mBoa zfy~eW_Yi?=&dCZ3i;-wnt8wwr_$5Oou996$92_5@ypeKoO?<5ep@hg`?zYYPNM~kA z6k4bei1Qttog6<*MlwABT6@_-!@=3T#iHscX$y(XT=c~!cd=pD_! z?8x7GRMx>N|7y}1c9pL{Kq%jlS zYmlM)LPugwZow_53XeS9+?lR-f;3lu)EVt17gB+RoqD2NWO$$QX2tF9*g-2)NH#}i zkmG>qz4l$Ow%H_w>A1(O$+PdKw8Y9-g_;na+4uU&1p0%9kUpv=H4pwc4$85^$yiTIvE)>b=*hcT2iY_Z_*P_@kX1v#|@ zdane8AWx;_-abV(`B_mwK|w*#mcn_eAy{CZvlbeLcu$z1`$*;P{pjs+eW;|M2 z4luGITH%{qWe*@oOdjIMK$zLhRCnDF8I=offhtGwp z;6Fpne|y^3oF1$&C@Dkle!AzOt_`fou_zKAyW>0rO-*xq4NfmV4B^>%TmN0@_~Z3^ z3WRM+-rmGBocgb|qEiS}^Jl2|37!Rs6?A6ED?k{1-G{i)2b=RzahpHfb5I|uf9^Ev z9DN>CXuzeFZrUvCK#Sk7&s$UMMHPL}4#HlDb%o(D>f@vK3X??Y{`B{`wBqo~w%~?y z7&zuCl2@;>V*o$}#g9`V4igj8>)XeB$!0ZnF<%#ro5FKtmwC-5*~lpkHh^J zDicIT`kKlkYe##V*AA`+c?5`)fHWMSxUBk()X;km^ncsbUwTQBGTgY%caN}6&o1&N zCrw0swA+Tn0$M%d;qRW)2EfIJd2LFwhQ%A!YW-Yi(n~7{`9p~lB7io5EUe_O<=`eH znY(`hG!kYQh$jn44(lT{pm60rXw*pe#8zBDWczra%tYyMV;mL2ClMwtnorH!`9nkw zZNdbIj=li_;j}(95C{UF(_tg~xcVKxU4^)a@bHg%-Cy3F?f(QCke!2M&{OvJfbFG% z1e7ZA#GMDw$Cg}rcf6@!Kd@!~rrG>?k4avLJ%jPaK=5cn`yT%b<;`iMy^Vkch$M{v zQp^5pbH2Y1H?qO2X{P(t&Ce|#0V6b*fj?o>_aLpY@n*9*_NL;y z@}=0pz2q0e;;B6kks~4i91cUzxx5C_T4I%&hHO3Nbg2;rU?vZdGtH-~KZ`YgdW43z z2Kv3AF&lw(d9o(8?Md_S!-o$fVc~EMa3EWe&cW#m;|BvfyXWAGYDv%lqE|orNV9&3e&L4Di^$fQ8`>NQ&M8R+y9A-(~1P!Z_mg# z8{^ZtG-M#t@zLr1Q`{@bFfJEfA~Dp2<}95(ahX>_PEObmV??18h0$C>|4+TfnX|i& zm|RPKWH{hF(j(L(>xO&M`))gxMpvYp)0V1v&8SgY!?x2P?9=y1MgW zAZlmmXv7{bU_RdB32<3|T4WGjTGbVY&mN*+g7mR^_Cr-#!#JVbIHz}J_57-nJPJ5= z9NAro#V0Olb>-I!q8e&<4QS_uW1xW|aJlc!Qsluo0Y?;A=ij4FWR2hq#Yr=T} z6%|zL&$G>k`M78Z)vh@MS*g%Je~CeMd<6FZo}K~rsm`(A6kfD(UhZ&pY|J-Rk*AUC zHMaLpIcSbqU?wjU8}j$Qt4h&Q+W&#mCPJa*wu4aMk5LWCdg6|WX-asjy@+d;kU zQ9r3ED(ao-l9!RMnAfplEZY=bTffA;(oacN>y$*<+Tk3`Dtw3AnuQ4UB*vVi+Ij#B z_wukzROPGbv;$^4YP{HwGt8~JtRp?!Cn|I;tMV~{yc|ET=3bgUU>RbSh4K-LU}wWL zac1~M8aU$kBHeY5;-14g3$9Ubwt4du?`Mzqwcwq^)L>F86`(0(*D3#sB(z#!*I&F? zzv92Ct7-IBet{qY^ace)5-NbsmJnkbY5s^Vm!_a5O_U4&Kf|Jz2W420be+?H5l46^|}J*Khqd=X}p2vY$V&Sbf&gH~Zkiu=4bCetqnzGHkO zpvd8r1aW4*{0(kWLae@D-wYSlRWH{56WQ-D?ii?<0|=g>EN!uiOmS z?M%7fEM*nn7ch6sA8^&5H!5N0UjM7Q8S-FZ-kWAGquXK^FQCsLWbpkP1j0g8GW zE*5y(Tw#u7qLc6H;g*4$b|*(HX}_#By5*jCg@w?;nCx=K%WmN3T5-b1X^WU<%mo}Z z%ePPC6k#pjPX-~XxH+k8x z2cn-X*@`#>YVggN3RW`l#+opa$YXl@wa3T8V@C9$O(jI)Eg|FlRVeHVBgsueVO46o zooaL_8*)j~DIq}#1Na(FY)k|C8nS~0IY2EEei+B%%Jqi+NAj09hXExm4hm=3GoL1~=7T<`b1C94I8uUOMr@-Z~^3ag&c9OZiU5|U= zs#RK(Qg2%}{lQq0GDG@Qv1r!3Vp@GICnc1Rt(flo2h8gk586K9FMdUvkAI~a8L^)v z=yqLtcpg{~fC1s;bJgI_RogR`%fFw^YjlJFY{r%*Zah0r8g7V?knj`#Q7Ds)*rN zW)tP*$F)dEyD#LlW3v zh}P*Ek0Lk20zNfPO<6`fD*#$|h%Ol8H+=8TYj>zJK4Z!meGvONIu^$jp?m zG84-J!HOzaz41HR!ImaJpbKbvIrL}=^+1{Fi9W9BWnXesZjeCC7C|(UjYf~?`1+ny zF~G@gK|F3fHXsZM>hYo;_tXIg>xFToW&G#DTSu3EpF#?7j<-02tPRkEo_G58P!=2n zgaW=Lo|qH0{q5b+bvkUF-IINd`S=z3Z-`%*RP*5>WCPyh>W!Y zf&=Ytb{Q+H+|+ae7xZ!GvntcR7gWMUNKpJ1!ki9N>8FnR{*+0`&^=2_-+*p6p@6_Z zAmdpK8kF4s1)l}|kXzc?AbivN0$)$bJkb{yk$;=>91GmUd)`>4fWJ|YzrCgpZIVa6 zUCaajC@0$Ojec4^)j-i5;n?P^f9pG(47cqu651XohDbLM4>@#)+=w7hO9OYc)P5Q* zITwnl%J2jlT!*Y=YhnBlA1BsxT#TV9pMff{n*YSMCAHt4qwAOjb(WhDy=ZNHBX)kD zG3ZGW`F-my@&2YuKvi-=gbO>Oq-GKiZpD;m>uok$zJIM~sC6AUH_XTnlX2i=>d`P-^mJH^xRP0oY_R{Tw3=LF=>*F<2P zl88w!IG`UH@g>;uS2SzxYFL=QWlmvq`ejS(q)iAg=Fw0YYG*&hb@&IbS>2z4i#s-DL)ZQ zLH9egmZ+ILjtS!4K!Osjx5hAaJHBoFOjjl*m=rQ5z{7JN0D!v_bbOI^(&uM-Tru8M z0u+boFnwd8u8R(`)coNDYjosSVI5LBiKn;4u%LX?Rt7x0%*%5$e<2U5FJ`*or& zTj!%A7n5fGE(}|B(iTt7cCM77-d%OMI7MJmfLG*vK4H6fUVU z5yf07l{=a!o=0kXcrn9i-xTOB;wh`syTgE_L)|%QmQ-Zu za!s+E18o>S58UhovygZ5v(iP|F@D4R#&jg0tz~U3=z_mV3O3U7GOK<-o-3mJWx9B` zMN{pIAI;nR+wH1*!g)dSPF$9V4*DtIMOkb3O;wh{#ajmb)jPtY$n99vUtq8~BdKNuTA}ydbHo=>bX7x&B4%ceR zMu-S)UQpMwX$&H3|2(h4*N{aP_X6YZBRb*i#t`+Lh-i;k5$>(d3rU5#lr=i7bl|sW49+ zx#UQb((TJ*fG7J+80k`f^u3r+DvPF4Fry|>({M@w$KtTxSxRDu9mFwG3>5ht?$s)g z=Nq)mdT!S-4(0@=iYp13Upz#wl7t?N2^cBGjZD$48-n+3u$jKOQ$Q=qo+5NayGT7W z9Dd@WCtQdjvEGf<{3H$H9X%{V5za4gil3^Q(h+nq7yR;PBUj>OaKWe$G;cCzElVbmr(-VU2ZXw|1-O48Hlbr&wIZhlo;$Oky~Aj{T+tKQMII+%B8nj##MkH{>L+ zEgM{(>(mRM{A3Ej@jXn}I@PYv2Sr%tt3g`3!}d3;-QPwmh=mui*@LW9SnhgLp$0mo zMrlk)H1sJE7qoK>)1ytK$&gElp;aAP(=Hf79kM~Dh_aW+_w%#%#D2+<=YBI8RL&5C ze5Y=Q^LnNtx0C2vbE2>Hr|-nQU`=7Z&LPKV2(XZLylHS}s7iKps;_fiflR{bBZa|s zxiHpJ$8u9O{GFcaeoEuWqFegnNw^@AVci)L*-k-*vfRIZJrCJIWC97B*Az%!-2{R0 zNSgBCNc{7`nu|1^TMN%{cIK6T2>`W3nVyKC{p)*>U)TAGneY%3u0Q_s$6W|v8^YTe zG7k&ZGQXtOL`;vC@@eXyZJ(b+F)74m{qUh~Z_L0xRc~swN#jB8pfo35?{Fb9Q{K(N7X3-c$oX?t_;I{GHMqW0#i_Aqp zhfKe|VGK9Ba8(26z*dn6d_o4ow9@DR}il)Ub`-3sBN42P__LqoW8s4YlJQ<1u?;* zx+->{n!&FbS>=8!Zotd7|1@=zT0R=+uJ4v3W_JN4c{;pi>5Tu-shBQXfw)5;)JW_*0?+pyl)QPbLidzJ)A~GJ|scft@DI7R#cGF~d>3o|7V% zbF-`JfjxZF{KDF`B4C@=Kc?)ft!PvkYm!g&AlzHMv8`A;SQDn;bumLrSm*F-X+O9> zQED;q9#Q9$3rMSY!%k6XDhn>eM4fk>nVJi6@;SP^_1Xl=p19>t6xi zJz6#Rh?4e=e#SPyE&^%J&K*Z4|{{WG{QMOfCjQ3-WIjbW>M?){ufhM!zjFd7}^xBX2Fgl25d(JNqIKS4{a@{ zBg6V{RRI4nA3exic2l^pN~j^-SeE|QvmXOBSmv9<_sSIN>9g3h+vu^<7kP3(`zsvO z7XUZ;vp+hkbGtmlq_Chc4*C9-TvuOzDvn1@WK{xMc1oAv`JY|@!Va{-yShvhiO}3GF8XZ{$x~y;qWY!1sskwBDx7fNYLf+jSeOArrxWm_2FXY4_gx=r$T%J0kOg)S9V(%-gX=)P{#PbMOl+=9pek(-UYvh=~xL15wAFQ&o4XFTm5XvWRr#E!l_^CTS{qttV^vy0hHjC zlOxU8rA0;EPoX84ozlqo@$ou-3>nElySae1o+4Jlr=wU8p#8BEB#$EO^=8jMH>3=_ z?)-FAdjy4$bc|;}-+71H_qTnkV^+nvxuw-}v)I{GZDNK49(tzQzH8CDBUfoxc4$~r zcr;RXFdTOaw>hqxc~KppV(Z3)hKBAQFAN=#iH3@C$~J>-g3pU$P4xdxJ>08^1?ewvGw8p$s92pY&qGIsuk#=Dkg)AU?pRSGCg@DBt&ZvY<-EaFkK5d13z0<1}RBS5> z;S6%Nx_xZQW2B+6Sqs*j8);&AZ6~*>=p(j@SV0B9|6A*u-JC&#w_*-XPRblu9ZHJx zMyK(tmO%9n60^nnwkn0y*})y;Wcb+5qKp@(&ZV|+wZI@ZHlQa2@0hRLlr!Zhe0=hy zZGAIV%BHd(X+FF)alFmb1P*BS4=*dTI}U%J&n zqo_mJT3dT7DdV;$v$cSLfUvMIuHK)iFiFuydY;aSz-aDS`x3Pe60UDvdG1`mW{bDR~eupg_q2Oxers122H1?y0?nj@t3QXN6kD7z5 zBv|fzvJnl+{2UgyQ0&ql=i8p0Xs=V|v^r?o6A;QreDcms+Ui-w+D|xTMv_d!=i_^} z@bm|3X^AFVu!;)TlNY)dv*#P)tPM8LQj?YI$n8{V)oYRRpIu zzByMU%5BVg+tM9#1rCkn+G5U}+6dmZJIA%kr3VS6F1I1Q&Wh4|KWO=OeEeDxIX?5S2&lKR|1o@mk1x8$#y0|c$h;4jld?P zeN@)P2Xo&gj}m~hnRXPXl*9k98SvB-HWlb-pMp{+3Vh$X-@N%R57vkb-^iZQbC}{u zPY_h1@qD@Lp0r<2dz7@Jl2R}eWpyHpOTQA-R;OnfmMkL=`G$ z;F27u*>7IIbGkE(5-3%7*tfMhlv*V_#tPcJH`^y#3Fu(Iw^0-jsROkHD9u@?*hhtbC$Se#`M z4X#78SldIV>Cwkzn)QR!Qu_Zvw6;4gE&?%h9=Vlk`6d}pcghHB*qR(Du&^U(g53HfOJKbRP zwvq>(G_M;r73y~DOmD;dYJyqJNJM=B;HW zH?E@h@MsV9BWQ8&1?&X~7L1p%g6M8C9jLtKM!<&4uM2{#uy-s?qLc*O8HT{ua2@nh8cM&Mv zEp6oU-(vZhs}B|B)&^v!gNjoF%XTp_7kqYH@z*jfs@GafP;`1ruR zC#3TGFc~U{g+sW1=pg90*BW7MDcOZw8m3BmJ9i7mG1UE6XHHnE%W~TCg;vQfY z_dpTsr_0;{_TxPIlAhjP5q(u&7P&r%2467?MkHS95@iSh#ehR2RHW#54@FLE>72Hp zQ*6|cE|T*8MD*HvXvR_|8%2K_bu=Xkmt>}Fvyn^PC>Q_C=-eW4dS8gU4+j}iq)nm> z;f#+o9nRuzpR~9XcL9X$E*Z(Q*AjZJyZeW7a?vmmldeU^m*J-Fn^|8DQ(fPcB^JZN zct>TAgv-&Bgk8HjIs{d-{U1kec(1laCCP3C8#?z&ug4^xPw-Yh11Ag^YaQdb|AjRg zMM&q&n}*#IPH(#v@MIR5I!Q3p-t^u{Ur-Hg8s0=5o1g06PhI8oFV{(^WJQ7kjJZ0s z#h>bwT5Ag451dF)OazcFV)L zQu)E_J5HoZozg_Kp5fSB;uq#K`*-Vn*{f(L0l2pkHZOllCPm0HDmzU=$)VAT+a)NY zPs>uwGPg#EsMS!ti=26fNlWx0opFT>@fnWWP4SZ&j(pi6+$T?oGFq73%ETm}%g?re zhP#Pm+Tpa_MB*}KYcIn|`HYTKbH*~pr3CT{xcx5?DWEECT_T^*6SZ~vbFLmVNAg{y z_9{-JVarP0duJsJzX_W~PP5-);h%Tt5g^()?FAc8RP&luQbqjuN!nzt*%39;-wc#| zV`yE1X1CkVm)Sw8eBiveT698l8Mbr%t=%G@&HUC#whQ5_I7bVQd?AY)0;&*IS1J=| z+?h=iEqzRh1W&=AqD?F_JSktm%io`Qu^-hg%g53IqQs><${tASi%u5~awMJb5e8a` z=-FLaBM{e?zYLgDWPl)@pze6aUR2~|b}LR+Uf9Jov7We@sqd86@XYPuSUl?*iw1NI zV(#0RO!e;hex#>t)siDwKQUifO$tANF7BG8&VhWfUVBi+#_MT=Ks)Cl{~6`b0=lcG z!NDgQH)sp}FvDKGP*TDmivm}8H373@-r(Uv%amnrP%ps38QFl{Fl-mOYGvXN>zUxj zl=(L~YdxYTfo5drF3hXJ$mTuucG3=<*Ija|oNj38{Tff^q=!G_CT82_WX!bltL-CQ z-tqrJ(Qx^ma9O55N{*_h5+{#mgl5w!-F&L_`bk^xSBtot`ICiI$Sp!4zO zH_f8~`NVpcBl-|HP@OAA>`BC8X$tQ)*F(eTS4ZmXaLFTHk2 zH;1WTft(8KMTb1{b94!DZ|9*U%JdGy#bra!pr0rlRTvpQK5~isOR)qLoO%Xy6Et)h z(?={CK~!kPtO~?5&W#*Nw`&oeW*f>v6q!{p#Gz7c)goFn6Goww+}?^g8HlBV%+Z)U z6p>jz`YLr=kaZL za-W#uo5tXQjQU_IQ>}~t^#be81747^1C?{7cySN@7 zoj%FS`>Q-M?u;9yoIHzZAHBH^eQbDK?lZS#pb_ss)p-M@##_U_4^fivh6!EtX~&_5 z^(Dv*w4s7D92AEyFzJcFqLY(oOSo(NtWsjHsyR0n!Fouv6tRf=$ByT4Ot({eiNP2r zHRdxAjG3@+S4=KKlFLDv0rtC%HZsv8)FP}yRaK&@Bh>@wPw9w7P*!}sA_$bAdPK3oghM;OQ*uAqL>tJjQw6NR*vQji z-62*4FrW?WD%pS6gZ#}!W;!8aGpPH5)BCt6iOkktLJdB_jZ{4n@LZ+os)b%2Dps^T zg9?)45zUr%A`yjVDbR2BK1I=K&Mg7jh}Q{E7PGvlpiIyskw{^OeT6b?4bd9^u-K#} z$}^as%n&tNVp^0q8*O50TGl2#)|P}Hw2CWw-P~)fqwm$5E@}Vj;IQPm0^x))TF=kt zDftNX4~Li6wB2NgCj+o%KX|eF9A_Fzjw+Pqk+d?3b?T^ebjhWJKO0jvNjo^C5Yp+= z$~)}Tp}nG@Qw$5Nef~MU@4Ub#^48DfG%6^=dN_KboZJ%(jDUN2+({=%252g<`Xi&B znp^NFTqqF-`M2fCgNkuoHy(iM;RU`&IybFt-Xge8^IK|`t!2`I)c~>2?b!P4POrE@ zwrlfVL*f}TcFD&RFV|Uel`A!hRl{OYrbl+^(BH(-fxaOKG ztL5X^ljMxbwF{=}mL8f0tks$8u6qtHlNz87LBF*k@yajFi%ojQ(9(zvVluIU21|M5 z*T`nF&Mm~UFF$CI!Qk+nRH$Tkq|89-usVluG85@tf@7j%*CfV*+}>1i-B*4ib{5v+ zhXnl%j`-EnLqpWDtj&=fc7WTbo&%v}9tfXk==u{m03vLDw^2uSm?G`habDAY0L_xL z85AIU-fz}nmWU_o`LrU_uR>9m-I<1DU_ALdi&0O1rba`|oEQ%|A8CTS^pZ{UN<`ywcP? zx=7}Cxk%QNYQHg~+5V9u*`SRAMM$Gw%gPp9}u!! z$Z&Ta_@T)9n%GDyOynX^lRUr0S-nG2`=M^&vN*xld_)!*dYd*o#DAT9Ry@lG=5xL; z2jR)8{aM2(Z80>boQ8LFWSHOONV_J{$+})ZxuiVuG~jc1jxb@UtyM8A!;t(N+G3Wl zQ8FN>Wz-w?!E3%wy-Noml2uG+DN5yT-Q4sy#@TH&SPIK{!)JOrHGfJAPk#Xl2GJBSQU9&PTc`Y zo7u<2Zxr#>#xAGr>~LEBeKdVloPh{zoU5=xImRfAg_*VM;EA`IE zEk5}ZD6n#me8_GthG%S|ukvhJG`4rl`N0c8PoAJv6gvW?L^IBj`}ikQp$iP!AA~F>>C!H7qROlK$I840C9>8ahJ35bFi{WcjDWB@ zpFI;EDJAAwt=|xMS&Ke;h?(gH8z{Wx<#l1A4V8OYO-bFg(ae2DZHN9XQ=&U&Z1lOK zJ$k~ScW3W11gl$|;ziemd#%=YemD2WO6)BM7BdeVT`<3tU%5O)XQtEe3m=oH-w4fA z0m>LCW}QGN$TB=iIPY?H8B8)tJ2DbkcDuAg;diyx^Ki8yblmCc>Kbej4aKK;L^w+5 zva_R{E z^|1mwuySv2B^_Z{cKMz<({I|w`irCV zH@C{K>Jcu=w+8E#EynU$pH@`euD?!}blM*#r;?51<*glQmJh@UI3%B~=#GEJUe&q( zv&ML!{*w|P+kB@`s~P$WNmV}KPhA1$M6Z3Rr-vq0dQFksnq=qV$RKEpde z0njGu_`CV2*g$-opZ(Rl>Htn`#Rk7S{A_{p&}m0UY5uys1~aqaE2#9$;eyWYmG6frBZ8nEZ+vWI&nGH^RzMS6k%etO!B5a_k8)FYefkM z7Hkc1QQ@|znJSF{76oyR%Hmy{d3Bag)67B`?hn*bZDWPY@%K$R7+yx(y1Y9ZmR!b= z;i({C%8ebg(@A{pu<4Ee*SKLtloIKbP5%T6k!I*d5EKOIb^t*bknZm8`Znj@<2mm=_rBk|)?Tyz413MMe(Lu$i6D^Ehg|ht zYr;#@)`(A4GIwNV3+=^iKY{@_gEjh{RhcI1Eb`3s+XXy69{aTU&L92dnj~m+LJE7w zc<7!*LG)b% z&`{{Wo718v+hv5W9V$>m^wN`sstQon0SzDsP{B(yyP1ZCSH{ICW|)_y68OASP4)^Q z@(IUr+UvHs_!MSzgeqi!e-xyTR2kJX&j))4C7OpKa_9TAc~>nq3l1v@(=~OO7ZFza zuU5;ttaX*9OjVu-eI2xEf!&8Qi&}Yn{ID3UxHhpcs99+_Rec$IEnREluD5nNTnHEn zhqcYwg}`b8JKyl3b*=&}FboK1 z^GPJYh3=6iB> z_f`uX8opsZrHIiq5&v41pRpu*U7e?vDsyZqn{iP1MOG4z;>&@(J-IBwMI;xU@N+oD zN5w;}0T`3~^WFg`>eu&&avJQrk=k@nXKTbk4xLyoV?oF>g`Gn+;~lqqtgEXIjADV^ zvXkPu3>`04m$zq{s&Fws8W~R7%6Vax27D>$VQUBJ&l287?%|Wi)3SvW=3J(C$Xydi zi?ewN2Gq$Kzyw~W)~W2Z=jWD0vD%?HhBZjeSR4*C`!MQN>{KUXl0Jks{j|SNbtteV z@H#iLRianvrn6=X_rJ7!&7`7FJa@IuXp4wm!gWyD(f&-R_Gro6k14?%^fuS3ST97C zF8m2otfUH6{rmagUYqnEcY{xgUu|DuszoQOWvL4+@c!s1g)EX_#~MrrQBOte@D z3pq}w?}^`0YRjp9N}M9Uz^l+WZWjE+?RilQie;ikVlkIr<&Tck zy%D^)b1_Kvz2OzA5I4DUJ>tDd=w42P!SRvS9L!UAJ=dH}*{y#MaKPgh6Vnqo`xvm{ z+MHf+w%kfSQ^EoCrOLVb-Gl+dHR#qjB);m6bN z?r!1u6}@MHzdG?#H7n!$M1|$T=@|915nM$a+dKlp2DIXQ@?{!pRxp)D-7H7oT6!Tp3hqt+FD3GH@yL zANWqyN7ypY1hrc6xU#WU`+3acyM1K8NTR@mDW2!F4o&|f^w1A~jL)fnX5^zsr zl2}?T^^$i7a~&a_ZJ1*24u+YqOc(wF%X#!gd%;<3dz~P0LoV;@$`+A@Y9F!KdIZ zVcLUZ??x4fKFCC>Uk|$SaN5BJVqdE^)>xfgm3?pd>Z7;@I87Tc$DJ9YKQ-2dT{ zHJp)usIEaikIM-SroaTeh|qh1ya#lQSDvf&Y{+?NJ}m> z_`$*|5rbv5vLaNu!w=6`cB9{G2j{-JM_kb4Io`aJEw~QOp#4=F-C8!l=0e7ua8}_w zfy^C=7%^`u$3A56$U4^ItIR5sV5CRFK4xsFOt}PZvPa0@iI5BTF8CPM8mBhLk-0=8Q z_|G&zXx{fK{WB5wr({04nU)b&8!xduk!s7(c~zQ|2{{Sm3k*(3Pkg4x&?N(4F!eZ< znWkSuCmx%mZD~*)DI)m7LU@xTEnn>yZpz9xg}qu8Fp)lKq{@_{dt7#YKG>7rO)*Ps zkrgRxrJVdThjoY>`W}ewd>5c0JOy#m!7UM(i8u0~#LPlzRy)12xp^3A4k(v@D|_Nmvxmz`)y+HY+MHjekT5)yb)Q=1W)1 ztPckZ%ge=CETFdvA9c|g#T1?sbsk`Y!UN$6GOBTLvM(w`mXJyV8K%OZSR|dSmHLM; zIz~Byv@hBp`qVBLztKHib=xFd^9(;&x@}jPZ?r~0wG#Yb_^@SDTF$0IdrH&Qp6-V0 zU-1f&{3A$zJK@nyb|=sg36xM<%0s&cPr4&x0DFd^XRJNUc;pXb@NKzClSe!l2g-Rc z&2ubgQK7}3oTjwR1})nABr!hWy_2+xC9fnLC@nf{*8PoLUX=i$4jUjKp2P6* z0c`JSw%hp-+6^K_rwIEGCjQltzw z@1e>WcXT|HSm=s*dFnzLK{-<0B6NnKLL*8ZI;t3;?o~0Q220gUTaa}hpi?P)W>^sC z1uxJvT63FO&qhc*ZYCGmB9h^^MYz19Neu;1y5$&dFJM>xMDG7SG?8Fdr|dBFEdQ7Y zp#Vx=X0|8lL3&?8pE=drzcXWyaH{*3sVs`aFoO-Kv6>c+w^0nLF-=LZU7;`ARCA`V zw`;mozc{AD$+hzHf&LuNt9kmdV_kP@VDJYez$%K;A07HKcQkA2-1z^=G5>zY{yA`` zf%AF{Ea`oKWAPA~gxU^HQ8wvZq3jA0Nl?f_{hR{pyS!*{YpsCkd5}*GJTWTZ#EG2_ zDxb)ShUN)mp+ZO#4E?^ZykcZ2^kH>?1_qM3z05OER>y?_@{4rqM=(VpC^@^di}8=g$8 zOQIb)!|=X1c=!5}LeWW4B{oERRKBmLE%Y-PeoBlVgpn+?XW^p^UFZ>U&M#Wxl`=Y( zQ~K}II-#;|dob$+Z^T-<`iDW195%9ntAgx25rsfP_?N~pHFHL#G)8Nfre2od>KshQ zyF$jjLdH|w1I7%tu&{jSWpO`LoG}502^F70U}~uqiyUu68~LXXl{QQK|ENu1yiuRq zGqD3)pjFDk&X(8m+|g5ZIPr zhp>J^PU_2~6RNZaZDZ!Pk#QfeV7gq%Y$FRF%*m1{ti#k9SSq9$Q-kHyeOO>vF8*dP z1dU={T##qTWK(voz=Tp1ao|oF2?Itm$VXnz)fL0W%kZ!-Y;^m@wsCPxY*Jv(08ii*!7E^^ZY|ei!R}H^|_JwmscH!2^dB&_KighAcee(|xj} z`gyCcgD-%h^gQg}mXFX8q^?jTMwFOvt*hTi<)HK13I-E=dj@c2W5w-fbrW|g*L8T$ zc%;!RfZB~hwgc8>18nGr)+C+(I-TG2nAb)B^0rwG8JF$s{~OK2^yAdzs!2&YS$RHM;1Y&UfmHQdFc6FRXIr zXsav@Kf=^Fb=08yEgxg!oSfYG!Bh114zf|~XHx2MOf+twSowafJy_UT&Yt>_RzLg# ze!)NpPY*lEk!{}FQ{RJ`7LUmaz=Bhm#gn*TKED(gn3bI>Y)j(FngJ_-%nP~2=s`cH z5f#?-A-8LEY>H)9;gaRq8B#4Sf^&agr@x>@O4_~;HQ%3Qrt>kYGUa zSjds#x+4q-AV`~-Cl+&bvM&RpR@lFO2>+iBgYVwW)VPZz?UN8iMTMyI{p(SWZONL_ zy@~UrslJ|7Wy1=#nqxnlA9k9ai zbKN_hTA+I#H#EMA@;F7f`~W8H3UoY>bu6(|@Nm}m*sS#4zQWl@-AT`HQq46|K~_Ue zSCh3?XsH(o#|R^G(cB>MnF{lKlwSoEf-F^>vUH_KJKC5;zaFH=BL%_Y3{txzq0nD_ z6vCcka;qKl3(2H+A0{Xc&7`u}x`EjP%wC{pGq=*xEc1q!QBRa(4PT&)A0_mvzGQu* zJX@$)_{}NLm9K4{GB0U|@27C2=5{lpxUsbU!)mJHM_|}s5Q$&r;mnG%)&`M=D3agpNqTGsA-(Ot`M z3ww_l@~^9@PjP0q1%*}i?ZlE=z3(~|OWyTN_|A|$-6ORByiCwbr#{_&C}2=|YhKCt zs;i~^!Od5gloQ76d^us&;e=+uYhY4p0s4tJpK@rhpTJX19#Yn+SOw{SRBu}H*lLYO zU1(8>5pK8bIhi;S?lFZGm`%1wJp+Mc;juh^C3@jV&+E zobnuZ?1KYR#dGTD*F?<_>h*;Iiy%QYEE!_XQ&ZSWIntnBTq|5F7Cng={x~)pqoDJY z;dJZJ5)mwTwQ9_5WxG3seCif|B@YZ(A=U0CyiI`re_khngsuxk56L=cjS!tb_a<6u z{&?np5|U;8p$u1@E!MMmcK>Jx<@&Bc7NQ~{gcaYB zxRRyARotbjuN7?yb}MhfmU(aFkB{s!!j~QBEGP?zYsETMD-9o6z6D51#dGe0Z4o&x z3GVlo+|Xna`{*#wkxW~<{HwSz1f#Su8Km+&PgKo>6`p25oC6;uihN|qqOONMpM`?T z)lW(KqG@ItylBy&&Bt$mw0Ma_Hf-18H+G}BWxN$lV| zn7oG|`SK!q$(k?B!DG5#E%KR&QYay|6@z({7I{aar^-Fy${#4p9E}d$MK_Vwc2*j< z8x!^)JbE?@YGdzujT~4!sgJ&%Kk~A-$Lj;h5mpHVOq$l zoyN1j?;Y4d^fyYM+$9=mD5ni{I;UL=$Ibb4k)`3iWj9gh#w&i9kZwSJ9Cqv@*@8lg zO!s~?yPD6dj`yF80`QG%2RZ{-%*@>VMkomYMeqeP%xQ#peNkcI<(p?893aJuj&nG; z5fotZu$co<|Y4-V?Wn1?U8FGeoP&KrHg<+kf? z14B8ND(*T|xmTvb;UNLs?d#p09rjyD=FDfS1+ur1ZQ1I%V^g{btm~*($*yf*7!@r~ zc^30cV~?M}hpauL6d)UBAY~PE2m7(Cl;ZcrcQ`8^Fg>Z%_pq~4!O2Z+L~Sk8+i+gM z2W+?J#bJHC*hRjXnmval`(C%Fa8$p9aSas;4*5@CewQ${{ctt(?LpU@5hd5PAl6q8 z{!Cr6yvMQ^)*z3(ay7`(i2hRNr&vjT5hgayUvS_jhsJUjXnsbC>v5V(j z>e9i22VjOaYU-o)7DRbRn--LsxQtAqcb6#ZhU2p!afKQcpm^5AJUA~5M2C-OvikON zRUN`JElBC943>B9E-DoWkBilu#J|%el%@IuiRFvfk?D46Ziv& zh2mJqBHyGbxUhz*SNPDpM6V)X{Y^!@L5aRNEMbiuTNyHM`lI9x3Da8&yY7(+^E?sM zCx~+eUmhjw8|#LRf^tXS;pYP;MFo`ry^NuF1SBYhv-iS9vOIKuPgJ4ToNua1-1f*z z_Zpc*VZLt_ambv6*@4d5Ff73d4rCF_C477F3q#z0h&g=|fr7f+7)0uKKiPf@;_m|~ znQv<#bINIvhGn9)p`k(ePusu5c7~jZtLBMssNwEiH2?JkGyBQC!2R@LUqXbpK-|rX zx4Yhd(Xjve@F8f7W zkp&t&V9XQYe=vxjQMQn8k0Lc@p;C(|&&gLW?BUA-GgXtYe2FNLe}N1WXNY^jpsonYvHn2Va5yT_x+O>ZR3t@O#28Skq=>lk0$8j= zJ0VB?jIatRkFJV3@9Ouooe6GVCHtb`#63;3ETUuWCqxLy%QbmrF#0=YW@(CU6`O2E zE9P9`f~_jEUX`@5cSqEjYd`nn$(LFeHrS4oH(FO-w7{aL4Pz=`SWu+veLG;_>5EI@ zzIAYs7ugM|?#lio{!O9Z2U2R^Ij4J~cJ3xGrWD=uBs1^)B}=-*=v8(ba*w@M8bOGi zqHv!V`vR+lC}1M{u<+oO74V@4e2)sl|3o~$q0jHbtw*y~yPkJHObFzOP6i(IurdFT zVeVpz2bN&PN#NTLz=rLH6Pwy3K4To23@<5juou8fTSX0B{KER|ygBXzQ`&uNz;y()@ujPsCp=-$Lh`eqVD366VY*#R>meSC)Nf*&IgGGZ4-!%# zh+W-eRNc(rk?TfkR%D=b&C9+j4Fn~G#Z-oX5mB{c-uIj?Zkkq#Emn%bjRq|?Hw~lK z-55l&^P|y?_Q-bH&xt9RE$J5v>+>)TC|k>y9Kx)A^1%H48D1n(Dq*AZN>7ATFyVx+ zvFq+zWWXm8+5DhfN&c~iHA%OIxUjkeoU8Okc#r1QKTl+$l11pNraLJsud9wbCiusN zCA!pG>k^eM?Z>6@OsfT>uyczE8;O(xuxrx&<~S?Vk!FXx(&SVMbfeSZnYl`_lvG)XsGJkc zw=Hlb+^iTg9|Of$3SO}Fb+QCXT(4TLrb9hzf|ke)(mP9~N9+pu^>$trZO_$nWmTbt z_POt?ZO0|qw5`%4Ak}}eF45jVt)H|5mjkxSF!G*)@MNW)_Uw&Wtq;%V%xh;qJ?{q7 zL*+c@5I$)K<}Jf5FO`@4_e?y2+!FcVcu5hMIA;vsYHhvQfG^Tm!Wf+h0ivw;E2 zC^e4vMx6hE^nj^hS+wepS0N(%6O9OU{9%1)-v{KkYdra(`M;GMD*z_tNUl-dZuQ{6 zjf3czv=o_oJ?Tf}0WRW0NOR_N1qc(@Ti#KNrKUFx-|lm%}e#2A<_HclfOG^ZQ2!R8B+NA_rI<--pK!Z{`R2%VRW zXxo{vWjGc>S5*x7*GIHlAr3sNg|HLz&a@>4UPMp!+>}sNjqogTGDR@$!MY_$xyzU1 ziBSU18og>S{Y2}8%)ncXg%7uDXrs4ALZ=Yq8nZ<^4m8cGO^iW|Sp;=Un-E~AFBbH5 z^5pK5*+S8^jOYLM$bPFO0HUj)0m?4*9jfHFU5YY9i%v*0y#r!vT!EXB$tRQbf83F1&6TuEgFM3pF~rPq6+ z(Yfj@Cg;+}^eHO5zJ-AUTXb27TP?)4Xqotd`KQYwS#VkGA2?i0J&CGXmJS+`Ahku) z{!l2&QFtRQReET=lN_>Eh(`r`+d_f+WLy52ry}6Hz0KlsEBstl)$`e2 zF}ovF;NpcHwjeMljWY1jxM-!N_#zO1c|#^nlae}TDuyqzqq@^X*o64`3Dp9Fd1U?AGvsyNqVXvS zPouSFk{GR&CL0gl$D?-cTM->a#$`=(B$waAeefoe2Yq}qj|<3Qwb{3%-Y5JK0Tj(+-I zV%6Tl_lDUe=8{#u1p}Y3RT%h^?$5ja;anRkOSWj}By=j5s+lSN?1@(E*^+xK)_FiH z;TMZ(!K?@cA354tDaR8I+76amuD_(3sKmhy0|kb%qScU^mSYmRu}y;7;5qo`eM;(S zmY|8m#07P~#Ak!%rhuWXRJ=Q+jz#h&{)A67p>x52R7+2%FjnYZ(ivZcE#~}__-r4i ziOBkx6HPmz*p7S7YZ2$%!kzO|X6mr#!ph?;*FwUbIm|@9t{*Sq31~57FY+`_{ru%| z>&a=1tBb?6Kf(cfRD%}%dMv~i(L&CcFCt4PK|z)n<+OUb{NfX*EG(ouqhtY{d5U+t zr<0hak@1}{R-}S16YuCVtgaJ2N^?hD$Kwa0J{8ifQv~UrM>NTkS)5(wFa3sKzX1}Ev%W9kh8fKfcStez%nvkvO=p9il{> z4)IPJYs3+y@lHgl;A-yU4&rIXgam@}16XinQ+J4ScFgBs2^B=R53x0kPFO~r^iVnU zVf;TR?~*IIvnEV#E`>}GDv!t+_a6k#dkdXcc{CjMNR*Njp5|0KYcoZK$Y2&tj zrJ7}x*t5XEq>)zQmb$P&n6`8`zqkC*F$I=A#>;cs*@<3k&)};&?MM*8VKcolFs)Y^ zVRyBO@#!%0oCOnzq-dt`X<9gF+wlaFrFv8mB^nd~^#3l=binMG%DRz)B;(>uXtrhQ z-lvoz9&-1XBY#NK)Fj!(=$XV~@$vF}@R3Lh%!^hys{Xi%_$BH&v926Zp}Pm+%C_=1 z7nxQjn47BE=0kXDNhM3uDOem__geJLbag+Yi12J^Tv{%ek%;?Zi6p&aO{4eSG`>hfEXYO}Ew?X`QLazUHs&Kk zSGw6kobW7A=-;&M|Dk^+1%aT}U?kwRF%u~%I=W<}PKO~c&kgp}xMXHS3q^cywKHNv zjmBBnESB>=Eh0RU+GL&2BnUPu#@kuEdW?>&)YGY;+*GjsOxm~2kq2XO5bP6(_K_Jzq0@4ei-1j-Cn|_|K z+)F$mR%T#%a%!-rnQdg|9;3h)uWerTO1)^7A6C4PSfTOxxr<+<5={j<%eper_w~ca z9X0n_IvnoqQ^Cs*2L`*z6o)0HOfp+o+l4qxe06fVWtrwgrOi%lmQ37u+nZD1 znAAVFYZ1G&eo;VC*Fw-zDKAV_W<7J~AYS5{Ext_jM`UjL7J&Nxmr;C!*1me*dFr5v zAUx*43H?FZUxaH?qu(*FK!t^?$T~f88(hjElMn+uF$4|s;Xgxbf2ssC#Eo{oLZ_@m z1DrK_(a#t)HP5>w?Dygr!FT;$FoRE7xJW7-M;xy2d{=#0sH4xa^@OgSIA7D4(6OD8 zW?UlDU&E0_P0vc*^eq}L4A}i1F>|q;8frmokh&ss-Hzm90;|bXd{oO1k>N(R<%2 zSm7GUAPB-=4KPb<2NGH(7}|*iCH^Lr$rYGS>>gzNz~@naWgzd%>B|A*c?7Lbf|auD z;P)~aVcTjS%w>cV9jY>MX(7L@on4Z^YCmf7WX$D_BqCJlX&3Z%mmCvsIU$aRzc%AF zuZlt7^uRphwS`Xljkgf_n%#<%M)( zo*&rnGsn_c{g8dXY9N~EM!z6MXCFJ1kcmX?`Gkj{v~;u!+@N{P6(;b07N zcFu@g9v9JPIIV^T9v zFBSls6;s>ZgteODFVt%WTYuan=u0d^`q|d9R?wq{Dw|Ti?4&sl^QD z2Z>j*8HSWg#jCbO&?iV)*x<^Vh8!CP*n1wvW_;V&2)@2*e7jyb-DXhwp2y;odQBLS zc3JZ`lJ1%}5@05NHCJpk@3W<2J9CEtX?dADn0MK%LbE8>Eh>3#hYd-#>uRKa+u0y` zyM$a%4H$gBH=CugtY8`$t8M@R97=Z#xg>r+jZLK95>>P z6P5w`OLYHK#9$jtaMFQ!xW*Xjb6%TX$e(U|6WwEz3e*wCSvnU`x(t;Z7$O5K!aFxU z;s0V06ld0OAAWM%&kI~N-o3(OqgwyNVv{}rJeEnr8g+O2u%vVDa!WGSMM6{*Sj7oj zos?%5l#VnRj;AhwcR}>6U$~D`nbOcM<`F+I@7__>6@KxQbJ`;{z!lZr-sJcC6ew?M<3PBRuujnNL*H<5PNocwJUICP4^-Ccv&g5^kcZciP}Vf(X- z+LqOh7?V-bju}YX*90GnC^xI~@h=Z?>1ejQg`(qz)93dZuD7oPkTv&FX=4=wRyM;g z7UQ_{H1CdKK@G~H2T)s=MAeH^!7PGz?I-rQBO(vzhhTu8Ge=0VDvQRk8YUOtYyR$& zbM?xBEAdjvb6^;yvo%r~R|hJ-{j>7)xr3%aLgyglp1#ZGyttCE4FxLaS4GJt1INQM zqe6Pvlesg|?Hnqtj8qayKH{cPGI6rTOW($K{0^K$P@S8|0Exv(4 ze3;AWV==3X@kb0*7w}@&rn>k+SHv&vp1pX(8<;=Ip@w9j_2Fu2qo>^)1v4F~hzJ+N z6q`DnQ%T{Pyl+vnvx`=m;4Wlo;$typfsBTRs={;X#F_4&dO^ZXyYM@)xMaUmQ6}V> zFWvuI<=PhBo0@5FmUUlH$*97uGO@*+4h}=G@ba_Y4H*W+T`i-~kr&hzmkvmO(IO8v zgitX-@w*`Fj!az$lN2Nq`!VqYI;mAqY}n&pM3!N&P;mhalvDNYNT)DHO%Qo|iKIPw zXfv7m2)4`XU7Ff!0nbf4i4f$fu%_|@`memh=^>2t(j^#y%uK=*KKn@}@$re~%TIs( z`en4t+fn271A5Phnf4ueUercgsT`lE1cN9wA)*8}G(41=Hn#mLTsNL_$&4;>vba3z z=uNG0_WfaV!bGB}6T_jT`xaKJU*CA%f_>(FGxZo-KjCsr-~Lp8+M^BZCs@dgclf({8@7Rp|0W`KDP$U^H(JI zu#Ua^$pq}CBA}Ox%w$Gp*CuRK6Mwn3zSlR&tZRB(cX@P~ zjP5_kjW%TCwvg`=P~)G%?Pwo|MnDuj0(phB#9<*(tozk6*bHW(O?jF)2Z6FhG;5+M zgsG_IJq|P_4$0z1&nDQSf0gv2^#>G*j6*=$xIccE9&Jx1vf4=Gw)a512wqp3F_0UGSvFQ7P8}2!|6VU1_or$66}>)D#JJszI|G3~q=O zGA+Lqn3qBH$@WdZOk1vGVAkUkSDLwcpmgR{R`B|YY+n=3EY4A_u*-bkM)Ty!K<71P zt@D;tQcJ?nMk*1sRL=`I%FK}>JSB;zXv5wLea5TV_+#ItuQQ=V2M{1sJ(C%;fn#z zLts`=UywfV(gB46iOK&I@+KGn#Se0Hoz#G|RxA;qP3rHKXuah6daVc99Q8ZsDS?Ic zsIZbeDS#Kjp+$*Fy=F%yx2M#pV`FG9#klVu@Qr zz*9Uq(kwqogdQ?~>^vDsR64YJ4*u!+S%w*w0W4Q)i3?SBjz;T&0p$RVT6Rs)xB04U zrsGxABOWG{?bz2)YIx?{ZM^uX1A62^o~d{=UtoTc6Ag;^Ymw(>T;(14JSm8Km^}`C z0;uLNknZUL-f@N`-x65Qv8>l=$L>fHy$Jna7w@2W@4@kN%a?7CI7Ipcq9?Yhj(P+6A~$Y2D2^4g~#sr5kPb`YCAfN@os zGcwV7#RyE^W;mf9bz~At+8Hz*(5RG}4V|8%#uJMWgJIxjk$>2rZ3@1gRlFa;vDrdN zHLel2<77K*T>Lv`_**JZ+KISUkz<2-M^|Lm&+4E4vA2;pW({?y=K0->Icwq)oWD+59U^HJ+gqyP#3?+Z>gX7PL zKTUd-D5Y#?Ot`DbX#w}vEU1}qTAw5iFVgk+br&<>awUi3189_LqTz05nPJd!{Y(wq zp^n&qMdU{~G_5|A_hqAQvc_jMX0BubxMk`IUpkt(7i7Ziq*?CMThs%FPMsDqQ)>;O zFnb{>wY&&+m+2^(;tqBS*M|+q*H@fdHODGl=D6toFH*Y$@_nah z!u0lAh=e_qox4czDJ2Lj>rZrc)2>e>MyhtbBRKQ}?XIWoOq{LbZMIt1r;oH#-}N)P zE_|~yv3<0F&RX2(Sr|3*voOWb2}`*o_An(;-fU%%ENiS}zE;Qkt_{fKawXYjccp{< zVmQdZdL@^$V_f_~VPnJwWPO6qc78u!O3Ta8jsHpq!1kL&q6UC^nP*X}Jhk~asnKPcX9 zXwa%Y__5vmQR3v@)rE^U+4XtR_S{A|XZ!io*)z}mZXG(3UAm3^8R>Muy=D``N+~^O z4Z@f=IDnO1?zX`CkuUc!qtb)sdxh~DulEvW4(+@|r_LJifIjLI56_iNp9n+&nRn3J z`m0U5CQ*>XpDawCCiwh?K|? z`mlyDrJCoB-EoTWxuw_ePvx%m_@{ywtBS?MISy(yn+LD{ZwUud-rIn^ki~#~nqQH0 z;O3Nb^tmZoL7<0m{LjG6drWXF;LkJjtcE>tir~R0>)i0IEl;0=Bw;CNoTI**cfq3) zt7OXa`d?+~y;ws*D0VYwvoDF$61N5g`6BLXxHVi(OLUBKdQf{`Zu@+3O)PknVK*60 z0SroYO!Pa|n^mGQ`v>!jcCJL?XTgopFck@mvWv*CGq7zhvNtDNhnPQqxb-rU&CDBL zh)iGFRUa1lwBq!N5lrb(JFb=xA@Y)rh$%x%+sC{Ig|GE0Lz~Nu4)BgfYz!#lB*+)e zf_@6t1zr2j2kxAj1^?JtpY7mH12-g@r8P! zm7Pzz%=vF$czJGy8@I}Lg)_jCjLwe)=aqFXNUr8T`}k@=b^WegPe>#l^2`ZI_wES7-}Q8SDteI+5p^aw0`v;xKfv1jok07i)AIYBZ=t}wZ;FDJ{x4}7 zAeE}&LYi-cXdOfp+nX$B%R_U^EzcQHscddo5N)2(!qp@GE^dSqiJ6xe@rSdY5Cj0rPo=_iygEiHFeU9JVRH3IZRV%jEb@1Y#sATRgi zIh0LIgqCDzaoBk%EbZZ|S{r28!9P*c(Nme+3}>8DTvZpJo>O18>Q(xcd9V40=zI>* zkg8qk6f|Amx+Bjt=ZdVcGz$S4#Ojh8b=dG*^Op!+^OQ86ERNJ4zNtHYmtfY{Kw69t<8y1-;7l8`IpueLH7(&tNb<|? z^7L}if$?Id^!$^>F^!D$)4E;8J~=x&-$|5k-If?_^UT19XpdTzXwn|8>y4sZ+Qut3$2nP@A;n#Lwis-Q+t7OEmpPMDI3} zTQHDgJ4k?Fn*}A>nbb9=Z4uLwHwA4^M(`T#iqtLe+dX3;Zo!z|6SVIzvL18XntBoK=n)nD6X>+D)jttppVzv-N~cl}}?A@+m% z%#g{BF)3mp9*w7X1s?`3NWqY>qQ~i+vyE@+aZ;SOnuYGx3f5l)7HF@3_h6Bz-zF+i zUMt`lR!Wx#$|Q)AR>J-PQhvK7zYk6QH~e`8lGIP>bR z+Y&Ruo_%WQDn$fhPn}J_=E0&m>9+f!lKn8Q%Og2`Bw;`u#Fte^kNjmEO5f=7{Y#s^ z&f=>XoUP0ABir`M>kBoiXQtQ!2)&P?u6|aUossl$ zAun8QvF0uC4bYauFW;gQeM9JI*u4X@zD0URQTZp5N>5CpoE!BqKCgWw#{clgX-+;Vp_W8o76W6Zcbf-j@cx6x zb!WB#uzC5z$Qj@9()N7sZFY#WwaKsn2?}zA#r0xkj|_Kq&JjcPetMWr`f6`lbzyDSA`sq-g7rOBk3(WiM!qv7S%ri3-LBr{N z{;vCz130YQamxR*(ay_-3abUwdHHhC6Sa}XhtCyxMXcn_ z*NJl)?bqRaGXF#t`-*~HcfBdA!eK^4V|A(zxhZrrU#{)0*klJEQ}${hgL(59GxQyWOT$k^9}l? z&0ocjpQ7tKlG9+D*}4&zU*Rtm6~CQ&BH=GD+=UQ6*#>eNj5sB+Y4+e)jNb?>ULfgg)3ufbb#Qq_zv6rzaR=k9KbvIZP_XR z)3iK7qXsxegyEZwME&x4;)pkWmEBk%h)LkNeN^XF55@`~+hpOC`2+!}QK% zqTYA6)+%|9#CE6$aVT8t_ggfUbA#!#)XZ97f^jV;R-CyZM9wajc}H`wMi*g4*f=+s z##eBmebw?X+lM7!6C+`!V)~R06`A#2-8p&0m0D3$P{UJ^UX-`>AOrlMXX*XbtvF&t z9VDirjZUe;S_#U2aMW487VrV`c}*I6y(=GSg#C=1TKsdiH(0P7@A|0xn2umi`0;8G z2F)`Oou)wMviu#H$B~Da&(Po*V~2cE<-P<5?7}qC#ZW1cda`kvI;eFwPiFzk zrmJfx)gLNmFSoN3y*IoEy91>UlZ?wh6tatqUa5}3BBAo~>|!)`sF;O?q9E>QDOg)-V<7MeX+7R4mR=KJbGKOnmG zE-MzKE%7l4LPTpE?Mq@QBHVGdJ${Pcu>T?2%=!2-gjb7|d(eN%!g9W)TfB$3NXgpl!yQ;aOCBj*i5Lyx6{_@@SweyhQ^{lfh=beJteGDTAo$LAC7Wmo|SN;Fi(o z=Kl2m@%G+PQT55%=&OPXh)7m)kQ@{wNt7(HfhK1Ok|kpU4JbJXNY0IyOD-yc>qPEJWSW>yOx9@LehSHQluu$|O8AnO@t#SKw$HEH}CZRV-Qu z4trUheC9n+{W2|18?^R{r$;QDu+y!Z_=M3!CO7 zj+y9UZ3wd6X68d$@~6e|AJx!l^>?){tO_@+sNnx^Rm z;MMQ9FH&bVy&0vT`q_l))?wl>T0ZxbeA9SF?-TU6MW|}~m%`E0I^(~k{r|<({^f$= zmji!Tvu)xZOKxOzXPD?9P7@bx3#kX!y9 z=lJ)#X0z0#3$T_h*|A^TQ|VXalFOCS3G4}%<5?n*PvRkjy^Tsq>trACV`O8Hy6d}p z)~LvBLWWQJLyiM5thVfoM{X_u~VbxM~lwFp;|lj$9lDA>m^?;sa{(= z=St~%ZG?>s52uU!L@P1ZEt?lcMTio3#`QAB%Fw&KE~RvlFW%Q?hvIz)Gh1X#G&8e~ zk?GsMY&c^M8}+@D{bXF5BKmN2-O(Bkd4pG^>!P;XfI$i=EZNMkgy-`>LaB`jncnf% z`)*|$BvPdS*o|`GO1!>x;ZF(sm# zH)4l9y+)W2$PQGj@fvK4e8-!ip5C@CrJnl`8j~_Zb#h*`zumqx-C$> zs0i@I9ca4#!#uZ|#uP)CD#pGL?0MP0%{0tq^tQQR!*nGTTw!IU?$&N3uM$6RMu0oB z-o-DDuu@Yh>Qc|6lV5vDRJ&BG1hRO(QbZq(4Y*L`(d65rwAURXOV1)c#Po~eD*)Q( zU6&`F#kWw16mK^%C$Z}TXKF;oSch%%2d|Ne$BIYBeczs+dlCkBwcq!@R5saH+bm!9 zXQZmXx-=2)cS#YO>(3GCOXm})_PR5ETU-p2YotK&t+f%qrguWDIh|UAF{6+x2Ghn= zRQIP6S1YIQ5f7F+meSi~MypJUjJe!uA+6H$1U*s(A-uI%iTFiGho1(*2fY**=nr1$sh}+V>^;k9PbP!Zct;>5%?~qwW2Aer1TY zcK%>;j=#6%4;KTf9)!96*9W1vQAV4X-dj`U`Fk|Z}tThAQrr{^JT{R^+2 z&RVZ?MTLK-bej^|vu(!M_Ru>oKXN@!`l8OFX)v$xK@I%;M|?+NWLdtNO8(wd>4F1$ zEsbQ=&cQrX%+$tC+$ot9w)v89Oi{&V^t4#*30QG<(%XUHUZQ5){AG}*r=0|(Pe}9w zFCu-RYY6l|bB{mc8q5GNy(`0ujS?_6Z z68GdtsV%X_E=~p(imAN;{anl%dwbg-nh)?y){N5$ zVG>a^HeVOn#yi(-`sD12zi&AcrWU#$rm|#tLvpH!qkq z9&Lq`f-K)WDENq_v^6g3rj|U`#l<(2*VoutMn<%b9l*`_mKy@Jl3o<*v66gM;r=+f zEAVsw=871(v-Tj@E8!PMWC$4K`M0#=zi@K>8%_*y{}!je*Q*@9|N4_&IsJXYp?&YY z@hb>pa#i*BUd(l?`6z}u&f}QJ-DRL7g!Ob{=OD-ky>wUePKQta>5)&kk*62-l&R^(T!nVE>pm-;wQhqoVgYQ_(b2v0_O1-99GsW}<-k zLam*5wtLd@>$&cZWVd>7Mb}w;D6_>dbN0wLFeKTuek8RfkU4-!&*DRL0%23_yA$R9 z?rcPTw*dtb6teL{fwSY(u|$HV-u4aFw99i(V?5EDA+gTuI~P$Lv5yv0krx-tBDge3 z!L$?)+hpr{d#iN4aOXw{VING2eJO<=4%+V;sxsEuQ zDY}HsWEDtF^kl_ z{hGAN(_-HT$O%_s(iF4Tzo`+!U&e7eD?@s?uPozNjon|D;RSR`b5^J}7J1N% z#|Ewx5kh-B>;}!)q|K|u&=u&F&>gE8;$K`s`Uwx#|HLH#{5~S_0ey)8jo^PM@Iah? z)pb&PhZ+SdRy2i=TYc!#`)UFZU(J{l^tsnMzL{kV?clnKWJP);?t#v0!c` zQuw9>qT}U@fMwt_vP)0BRhG@>5(`2qLrjtZ;iX*&{FcJ|e61d4iwb5yv&hdM_FbRE z-G{JER9>0r!)@^v1qio05=)e#xUDiH5KBGQVY9Z=?n;O0P2ZZm6m#=3&<0rA`$;*q z+kJ9<>#~nk^C7NjVxD4|Wugu4bH^~cWIZ-6s;EP=GbLMK_P5N^{#9D}RCUpX&_91stN(+ zsA0Y+9IEysOzd9V)$=*Ds6AyU*_7{39{v9!lP=!ZKJP#5Coesi!~M1rRvD;kw`L*k z#)b8b;xV2%bB~xSoI~|B%VG~Ralw7{%h&ExEFd5j1;nuUGe4z-?Jt_^#yoqRO8wsZ zit=6{q6#>d_C?}k9c4vKjN+$E0qMQ&r!0hT_KD(d$J)q@Q$(K5(nyq@`o@mPy)Sp4 z`l0Ah?$7jIc`vW6@{4s|FgKF4kaTg93`emh}mt66kP8G8atbzE;DzfB)U`m;AcxeA=Htim4UJQZPrrF&mhWn&}682W|u zPHX{CnV(l&50#<|R;%0}KsY4{zb)d(j^Y3nb#a6$D`CGqBHQTO*_~A+o5EB4vV1fb z3c8}#EELH|akP2&lx9vY{p0_p=<$!y31GABpMmSI7hiCG6+MbG6L)#4u85LO@yXi1GQ#QITF>}UNRFkS#c>h|^X00zP| z=6VQA(P`qonQ0ZhzrYV*?}J~6c;_K|IpMvs_fve|``_vtDf>-}tY+lCs5im%Kqk;c zZ%Th42=5jwu0o6{zt7Ab$y_uuiSK^lrq33q(!KKXnqnkvIdMBy{N-K$d%nBQqVc`mI8|yF0`j$WW9Z)|ox_Hk{zr zW*~0ClG|6y#~?QrqLB`<(J8uGzu4K>($L~xAN;=pGMmT%wNs$s714n+ahXduHs%15wP|Q&ISXeRx-rYyi zjmT_-p?}gLQE1n!d43EbOdx`PP}D6`4au5pl-e!D3+G${7Q+3kAw{E~Brtb;W?;k! z_)PX#YGC$}$&Y(>`ts_O_(e!gE&N_=LK+2nOIBLc^c0Y$& z;bms-Le8fJVfJEFm&=;+-GDx~cwsXV^@6dE!HMVNurm*-i++b*hqU_BmAMA{uQOL? zo}aVLcqV%nbJTBR!8roDN2Q)cmc;BXI<9~hD@>7`37?7Kr6ky@*rwCMi)MY%6@a9- z{Mxlg*_BFA}b!ih5J1UELvP6)q^vM1q!L5_;#v zwo9HWx56x>WXh-2+G}`qw{f4tXSHh>^!^t}(?>&_{|k_`e}SZ3)Hu%Zo5IuI`TD@; zUm)GzOc=bHjo&EsL7pTQgPolsb68!6-St@s2l|Y-o_rgQQ8X#5Ovg570_fk8;B9HK zb3expkCpL^p7IcfQ?9t7!1##L|+D=_bxl{En-Tx6`*yv9kv zIrOQ(Tc4JgTw%p9Zs5pe>Bxl$ z-4y4hlI9_Ea#9mQ=W|pmDS{S02Jz8KSg{ab(YN};|9E+k^n>WRqn)9*^HGJyzFz50 zo9>idCi~k6LQKYncXGzKF`2bvKgN}Vd&~~BW#-oWt8ADRxuOcJ)MC4p?V>nSp3MR7 z?%s$MkHD;_=$;O{khkMg>z9v_yOI6~)7n$r`v2MExo`mqs)0U*lG6Xtx%~yxe@F{t zJUQccU5rt9%!ub@LBrU*G)%&6HtXPqs+dTKZ98F&ZorycY;+Hxm|$a0AbYKz)J3%M zrUx9#0utd2?bZ(t>+eib)vETmM5Oxl26Js5vUk*pKgO7(L4E3Q<~S4H}H{o175 z3Yz`z13{AyQ%{I>e4V_>U@~{-z9g952{H4<0wvJCgmTfU{#ZZmdUuKxu&JjBI4tyynSF@1T|>wW zW(%&C|Cx!vQ8$)K;YTm}LO%xnB!OjuRP7kD95{AM3L1((AhKio;P^-_uv|Au3iT># zP1zZJ`?!xgq(HkP!~zz{Z?wnN&$Wv&%+O;CEZ1`0rAhh(9u5`Gh~&I<0%FVg(*F3= zUoiOXM;H}=9Vjx!<2U8Nzt=EYxj+E@Kk>Q^j=Z`LzVO*xBqY=orXuu(zO&a1!(xAz zH8^bc6ck1mrm&r-$pTD=uxhXrs1GvVy6O|?{6ZxKGu(F}iZapix*fYV_z#KfEA8OrnDigmIQRagtLQl0b9kx})YU&g~on>7moS0_c8 zd3ug_q>-5y%J+BmKIdZfb7=~mwY;D4$Q;5wcn}ftjsG`&SiNl2FvKIoAt6zNa z>TY_)@&ZJZ8l3%&-*}(sWXT%$S;MJ*D)a{{whHffK?rM!IkSY4g;SzglQs0Z&*vOJ z6-u>Kj;tcE@ig`z=^Za@*J^<>&@s=Uyti;8H*!Xd_yA~HZ09}F|7Q~nZXE$?z-wzN zYSjOM@&9VHL>j-F*-D(ekd&9@y}O)vDk*L*4&g?2E|P{yF%s2oFNiNOW6QP<5ykJuXl>A4U(U3)s2E+DDL zOx-mLyBM0roBLSQkocDPRpJpg*gy0IX3zT3>~YhCSep3fqt&O*B5MWTgEE;l%;zdm|wF?2F&kro?C*jskHfO6T$em|6~(O8$A=ORMh3;#K0rkw_?$Mvqc_tJyH& z5*;6*z9Ph$3k{SXFwa5&+GwIM79C5HjUdZGu(mHfKc~m?pU091VKIrE`HDx3k-#9Y zfQ<+##SORkr=fVxu-T5}9w@6yu!#~kiOdJ?Va4hX1S39B#Wd{XxQ-R}P9V^*?auca zezN428^~+^EHP#*uUe(@Drrr5NuLTWad(W0X3}P-rjoWti74$hG$?YxGl|Z8qpE7F z_W@nmx)TN+7lp7u5U$bbeZ#ZHr<&|?yP^hc@~^VkKtK;htCdv#2^DJC3a%Cfj4j!( zsA+BCtm#0P2Yv>u-D!B<5sSFhW7!S)q^5KK7Phh>z%8`AaESn+F4!3Tk|g0y=6O6dSV$fH578 zJjo0CjYAMN{gt6}af<+?weXR1Fj#|E955Idg{Nat`{$6B77&}JvTCJMuNl>KI5?gg7>$&sOH>7Tm*T3J?7 zrPr9H&>;lzA=;^tF=>ep)%#ac4`UaaDN5*n^ggABOC)WYt*J4dB_1yFo`sMHbrfSg zBfEO`)=8Zp!vTYQr-d5t=G15pj)HxFu&-uQ#X#@MXuWYjo9_l)P$ty94AmeC+-^A$ zKP_8KMqFP8n*@0!#RrYS_@yM6kx#}B@Ao1|$ zsT7@e2-A}(P*TiOS}YaB?9(AmlSCtFJw)T2(7mPF%l9)!82)&o0xcEkqa+I9-ouxqy{=fGupG5y#BVVDKPp}z6t|kt0|TIl>-`v< zk>>d;>JNbFQivV>@(a@0zgf>lxBLB9vv&FWq5u8hS0}&7wihoeyk@r5bO!FP<{WeI zTq%!U5RDo03Gj>M*qw9=p5;YtW;ucki258>RgZi%kNntzt)kIa`S`M|@!6Y18Lx=~ z#0WA92}~RYFHe75uuz%95nOeqLaYI)4Hlt_Rd4f@I4*BCr|&f`zBkypIXQImm=T$J zUx7ZPidG>|d~}NoAA?wWmR0&t_c|DJH0;a$U_;E3*E^TrRG33NE@{aDVRWc35bo_J=+-DFrI@&bR z&q~pub<8Bdgja%XYTBc>yGRI==?dhH{>D0FRu)6~b%#pOeRr{J1mue-^*F~7j26<$ zs~$64WPUR=VmEUUKQD>5iZXKUhmI2drvS*2CBuH4UNtc@Yazv)7kO$f>nIu1q{J30zOBUcT^<8=xLN-aEvfyzqK!m zso`S75hqL7d%tR}rdzK`lD^G+9G#sz4!@i7*!)dmkxoAy+$6&c+-OS>8=y8uIVmgR zU4F8qzFDJ*ZGtGg^1d4=YZr|wy=%LrTOYrA^7AAz-#+hLS%69uDs>{(s1y>YnIaWm z!D~I7R#T{@keMrX9V3>0IuRq($M1oZ7w9qAJII*3w(1Ogcr;KG8OzcorL%csaJBF?fGTLrRLLVF zjy9G{y!3qzwD_dEeKXMza>`8hshOVkPh;|*2!Mn zFlcf=RL8o_5Sf0toE{=WABvu?ub92cRI+8A=y)dipqX58AoP4v^MtT4$qSWGyqRA0 zn$spGEf9$i!kwzjb>e~Zw`_RnIv-=UIWk@o*|pqw^-{7jk^4>n2c5q=-A5jtqYR6* z8rR1Nsju>ob=5LruD6_5F?|s)E45Qu0en9E@%ta&S#J3v((UT*1H1ghn!|s0SF0~R z4J6r1A&vZJf#j5Z>1P)EHvy zG?|2}wE{m|lp-?$h}rri7*j>moP`K>KfRGam4e1tP z`P`$7;7F;Bc78AW{BD=aRL!;A4rHWmecckfzcZ^a7*jOq{2{Mv(~tHd5=vo+3i*3V zEijT`DU8vEXxrBwH6M_Q2`Z<)E|4SI|3*EfruV z0Gi%BXVauJemTY(P}Ues5Ea~gl@I?pCiVUk?Ple7W*7G_-d|q09q>M&YI4cMK1Tx?6(R^TyTw=EmiENsJnL9pXdPcq1;-e3Cowden9e4Zj;$UVjw` z+PI=V^>lrvv)>U3au+2LCFo>JEXOmf(YA2PS z9O$@F`YPN*`m&~>Sj-a5x&H#Zx@h+|lquv2-1l3VE1 zUgkm@xPwU)vkct9$U}Vet>1%Ta^RuhvmH0?aE{4xtC)u(=NwnpQ=B!y2(m4VwiqRU z`7zi0x()Ntr^$z_S$dTv`~`}VDF$Hqne%nT(K71Fw_j-IlF6yBekD?D|AXk!T#Gu3 z5IoRDmjn0cII+CSkqxJPGn3w}Xl_pq)J!C9$>+_`yiF(;7MgW7bsZU`yg--+?d#Hd z7MvygnRTkkxFxC()A1Lsl8Ck_)g{|2@9ir4(O&PR@UfYhQ^(6U9Y0&v8u;1NW^~hN zc$;OWsHfnBLOZCL%V}9(XRmfGxNus54bKwY^0&KfBNRH!%7p*UBmZ*M+QzprKsdH- zb^3pG$bW+vR`5sLh;y9hb#nqax+JUNUFEAzKg3>$LiJLc{G*yrAa!i`(Wju-ZL!oT zhYNc#vY^gxzyk{@DY}zY)poX%M5gbbWp1(OFKhcvk|oEWRC{p32H%H573VR3@bepa}&g zkzGSQzP)&f?q(E0@40PJjyJctT3Z_Nh<37MD;^Md*RUiRtT^)()m)oN^$O$q;6i+#Q7vzHcm-q%Ydi2_?X7qw^C4M%Z4SEXHn z=Nc9x)yW$hqsD~k+|`_pVeKYmA*ZA~i76=R>xMCtH|PpjXe7AC7ta`&TrHH)ks(82vN zL3u9izKC2*PzVhq^wggz4Ax$~1MCIF4@=)}ZUHTwhjD1i-$M}|C&FFhn$N~SYo9ui zN8}|96a7<+I_N!-# z$GH5~wZ<)2I188jq=l+etkb(k18bQt;#Z68FQ%C;bay9bXTLNbTnQ~7l8G#J_OauqBp;P(n{UQ( zya$K1y{0TMhdremS3S#U@+LY6G`0{)?F99Wy>YvHe$Qo7JMtl=NJdl?mk@9+al?9* znFjdL%Bc%Ny~~#=EEQT0{ytARgSZDGEQ<*OdEfLGA;%gwrTNafOKLop+D%w;3X} zbJ;Y5Cn`pIy*YWF+q}2tGc+eMwM1CR&9~LfBr=AG5kHL$)&`ZuB4z zstBi1B=)5r0DJDXA+`UnoT;u8el5BENIGKqwPJjw82n*p=D zczT!a-Hf!v*rL-`>H&pH@5?7x?4uNDwFVQFAU)_xL}q~G8C2&U)rKmw5%%pIW3^ab zb(XN0^^IZqY=WNbXz?#*LJt|)&5^e9v7OxXB)yBaKB6y~39#7SepLSHo2(7&I%Fe@ zE6r1uOWBT9>>{eTVqpInqMJ=rwf2f|B<+%m1e9yJ0$n(XC@?)TKZZrJ?SGbaQ&z2F z7nByOJz}IppW8LBbtGFUrG~{o zAiJjJYO6}8_9i3ikW!3H3Y4;a5k<$>&0-Og4Q>5Ps?f9gc~U6}7UO zUv^Bka2WfeII|pmKFSuEwok$zKDqN|!W)N5kOo2U}^+20l$3ngarwx;HvfffYrXSkOERM)X$7ofU2walpm zzh}9B(qBT@Dej-w|9ofb_g{2rS$zYvB#XB(@pDJ}AO>fiCIAW?cKAxO9MZ}y0@=p` zQ3+m|^?--q!a9SoPknM^6urU^Df zv7oU14?WdaLy}|{{E)*|7C}*iZ;@7CGu6u>j5hchEX1gJ*DdQ}9Q5KBe_%{YZ9ZzK80sxSSmxa0#u`?w3&O^ll58~yBSTB&2tyl zq3MHp1>{w^o<&XGb$7cYCd*DfC{c!DUX+Q+Px>P`rrdIQe`L*gjHF!VymV#L{c_xz zHiJ?!x@o-YsjRI+g4K@rbk^DT?468bBrJ1|hA-^2gXv-=WDbl5+$YB;q_TThT=}u* zH?((!d&(uTqO1JcJeyHbuhA7-zKt~&6w2XeWeCb#+VMIkOjq@~po{a%GrG=TDjp(B zIj%AiqHdB6o6>U~PqN6b$a;~uIL6PDNqO=3|SMW5(=Mmyc?>z6`WSAWgw8b9g&Nv zSZ$QDz1RjGg~{5Ps0!HLO4vfy&=1v4)7S684MKU-O4KDKAH6%n_v_Yln|=Kh4nJ1T z;dbqbc*v|zQQocUnlCj%b|eQTaX!PKi)_Di0lRWc?(WK@O%jjuq8gX83ul}~R;64P zQG0#%URpIj4n@``7#Z3-X{bUcd6?3nOE$!cp4<(I?jD6aSiGvehM2~Fqxhm)vgU9OZ`-jS(9TfgA*3@#}%%i zTnc6D@0kr}l`=1m1e4O~A%aQg6y*OAw%I(uykPQysNcHNSH__LcO{C@sZ(VkWj7?N zO0*i4fENbH=y<}aW}n&*IJ4?$&zHo>o3bv;ZVnl;Epb3t(8q~gGBO(9m)IfYC=P5^ zy~E0l;KT5HsQfSfg3fpwaSG=^WVu5Y8JRzlDh$KIeAC;pND~?Bn`kZkg+<3~iB{>S zWU?1Sy~G-Y%0WClf$-WYR#&PrLmPxk_yHE6rDv zUfD{Ny;^ry48s%hS01GX6)7{@u2{~gbL-%~!*)~hzMQQfxm0ffmxV}&xgv}B*Aj|_ zL8JyQ-G%xn3&A|x6t{Jw78gXs$sw!T=|fll$cZDpPb5-GRQzZc#B6fG8*SH~m?!9q zF&5)776U4kvnI=yAm5^x7XDbnqWZCCV=0E7d4`aoCj&05F*`Hf8%2|+s|(h8^_Nzt zZwuAxg5dDt%^H3qlqJDi5>Z-6azI!+3+=z5B!A~J!LuRMxBl57{#gn5{`$kLm5gG= zw&Zvr27hmquMfLpS#EZ2C{l$FE8mt@y)%>9M}z>Y8&y|h@k&xMi;RqF)b*wb)Zn>p ztER4cbcMI-iiluI3uZH;ReL%Qj%K$lr=KXL@iY|CEHx)5PNC6Yo#bIjp;0eu2-5Ht zrK*Z$&wcq^dno*hG&i@_&^79?yjPP2Jd+#0%$TwtTRAXgVRt)f=b2edh%h9SX?W6S zwzm1YioD8YI*ApN^zlPDiBgA_H(gr?Vzywcwwp=%Y=aihhrMe*=Gd4`6_qE{a-eZF zbmQG1C@9FtX&f&WzojNY!l1z;5Ic5H^cZ(bXnmyiuEtA;u=-4<&HYU>ul+b9XNQyC z648^e6m3?f{6J;Vt?uz}!dKh;M(DDxkvJQlkMVANtDu>4Y>sv7>Sq%kJ;LeBZpJsa zypF<*#)iQlqc1jxJOUM)96+F|rb&JH`=|Yn;Pi^-)ousgt-qMm#}#qoscJiq@7shm z1toQF=UE~F_*zqm0ajU|aPo->HlTHQy6wI7V+_%B@7(3GBlzxxGqC3X^=dD-z3grP zhl4c*IupeyR#>mDKWE7U>66U17B!9fE+tJ!F~leQsmwTU{$q?-4GV}k4Cebs52M1g z+ZR=G1q&8}FlOEFHjZDpd22@VnyQr9w0NePq!ER-<)!P0XLHUs$4^oOiB;>1I0Paz z6c>g@AgiXTF~HMAUKa)Gv^nF!E~U1PTnw@vQcgU>r5v_z*(wtA`mA#h8fO$wjT^aG zbu;++A!XS`hWOFpr(BCttrWK%RUTFQJzdSYEB*qddmOsuHkBzCpdXhyM;e|YkRc{P zrN)aGWXP+0_59qLIemus=GPhfiQ}BQ+2#h*>$3Z9*CzAQhrDrhFr37zUCP?#wz*ZDa!34h6y zSUSr}+LX_L<-K}KommzpNHt~GtSzQfl1d^#0w2ec^;jTkL_KmX#!omf4`Xfwa%pyP zj0FzZOuSy0&07yE%DwwKN7ag^(24d9a{nqeE*NQ-snXOh*d)1<)}+lA&(E3^d{=0u z$`o5=m_)R}~ z$_Bjv6RzB3C^aEr>LsQTL5l{pkD8EDeR`w=K4G9Y5PZ$PG>AWo1}0)KS&HB?;yEU0_;a1j%v_~=*!Sk-%gq%7RMhs&x-t>D zPIZ}vKMzd}YMIHQ!R+Z5{V;b{j`>m#WEf5m0?1{ zMvN^T_N5#rHM1};0oh)dAsn8ommXT`vJy1 z;4(yEYL!)n13EKzUA?F_z>1O*SGTTFjuh7Lnl!cIb+?%-I|}#aEWQ3}Txc0ZR)-R< z#~1F)>ReR&J)8TRRT^tzL3R)4@UE^FUtXcpZ!p_Nvb$c0(Ly}l(u)G1{S%(86(j(J z<7QC6b1r`X?3O}18}YI;3mNEwH1Kq}h;*}ZU?!DP!c2Lj&K~3kfdl`0j>$+2y9rBN zraUhZv0Z5hC`v|pfdvN!M5#RGoS(?UaRu2i7<2BR&qT!PNQ~`#3`i53j^48~Gm|F! z2_CF)xXcd6!L~kT)7zvrb3m+Ge@L2i((Px2R`n`N$!m9QzY8bUMfQer!>;$F*cxWj zK;2l4ovh~IeqI)DQ9-eQv<>R%(Z`b(Sq7Y(@nR8hnnD8G^n|5RVHcnGJJGUSj|eJB z_gCg95{`}8{=WLtas`)dcT$mXUECS&^dVchlxnJRyZ4*pKks~9-ym1zG$kSmE`FYCNIoTg|YwA3fU6UMa4pH|D>b>PyDq0Qf(L;gj z5(7X-t}L$4dR7^p>F3}Ktf~L#-`U!M1Z_&V^h~}OT#lNcbe6zUn7-??Suw)>BS`}%T@^A!1K>XmAy>m)^*+==BGQfSQPqA_{O zJ<uoBOtqGGsnB#fZnrYzARDck-W94g>Dnl9tFSGf(TX$v9`1xA6{as zbXl_It*%6mW>wPhQTE8Q3*}e~@bbCn$C)TUYuaC_GFj@;Da&POxDKL1m+Z{c8wi~| zu(X_Dw^iD-=8)O1D&s9{?M8jGFgg>IX20yI49_K!V~&1~GRBVg8sEqn^7A;*c*6Ts zrt0WtimMCCEzWsiE+Nk&b$Ktrr;;r5EV<56yi8qZYg*xOcJ`& zpPFK0Mk;7;j{!Pxv#NXkA4)0$-M6WPr%SKJm)ST|RiE*_q?ikiKZ zvby*c?%OTid@n3rBK{8@MZz*i5EuCgQCL8nJlth-H%;)T<-7{B~nqeX^rdLiy!I(#*Ct zrmDIxV@uignfuyb=sg3qLMzpB2x>mFAu2IJp-DxUpdp}y#GMcvC+p{hba7kYYq|>8 zVofR@+575Q<=Knp>N21lj+Y#ACfq%WM{2ctw!0~KU6HK<8@U0nQSrv zGb81SLY;k?L!MkC@A;RHBJ`}E0=FQ1WxZ~M598Rs=65NJhOn8K>yB=R)CO5OE(Pw4 ztu|BV=Xi4#4|U(f3C~!(+C7BgRQUXdH{jJDA6Mvr+w@0If6sn#RDfXoj^mu8MhtFa zKaoE)RE{0s8_K|@x(LLm_9r-2zOCKW9A^8uj6@+ir;A`8I#x7Bw#x=q7)+3(uxypZ z8tl)WjfK8=b*Cn z+vL$#sQntRcDwm~dh=15-DJ%Sqq)w9#ap^LCiuxEkwtNHT1FN3d$M@&hBqUdEL7EV zwOlEsrNNXWIlL2OI-lRli#Fxy_t>m{`+AXpiyMDzn=*34sMt%}FQj7B4Aw?=b{=bsogiz4rqW9nCqG=gm5Q|V z2D-MWT#7=~(SF)sm@TeX_<7{deCZ4W=Xc@03VMI|+=r30(m99xQ?UKhO8=3vv{C@{ zDcLWqi-rE5Q_b&QAMt!vca}^GuBzWn3;qh?ou4>+S{7!~YR^tCFJ9C&E%n(aUrtoK zT*lo%*<2BJ+4x-J7^VIg7!gVGE=EmgZv)xR@>(s1rqS^9R%n~z+phi2(hB<-T z$zZoHS^DZCZTJ$arZDE4PYkTbY2v(YhioJD-*0CMrbL+0_CY4qsxw5*apE3sLtnLc zYRGA|oU^9n#^Pquu{rvtAeFr<`k7DTLHP1rU+cHeQx4ClWh&1fq->j&Xb4A@;l;7X zvJ!LTc0aVKT`Bc&L$c5ArKkA3RV>8yAF+FCiv6vfkzl zKk1CN3WG#j+r3jUm!?)jhS=o_c7E)E?EOzs09hyAMWz#gO0t~&IO(S*p#!xbVkr2Y^GV8 zgEH(p>cJLCd7d6s#@Y0F+4_nd?q$xzJcX$814Aho9%DvnVi5^a2#g+G@YnOmmCde?gaTW)hv0$eI zL7{0QHh$ZlM~ntGEl7lRH=I zJ7O8~PqWH9l{?F&Uqn^9KF6}LV9aC+^I`V}+QfX3NZ8c$SEdG1C)u$^W}BfA`IJA* z=Av5G&u(zlF{;n{EWPzV=bjzKrn=*NTo__w>~(#cSo@L#hZ*cOdUfl^j`k^^xZ|zY7yPM z1r8-zRKemP%0=_NBJMO>>^mzpR&a^`^ayT|ZauW_98UzUVB`?RO(M}3m}0b{)m)`h z^P^1R>JB|$2a7o@9w*K&AKWp{7+q;D7Xf+?c(zn_`2)}0ALBhtCK@iUiWInecwEp# zY(|o%Wn%sIUlVQDHKk=w#Na3cfiFf)%K^vmtGl;*u>!2ghzota%FM>esXVl)^?Vf9 zq)Bs&%i4}tJdcPgz0SS2KyNBRA{P-liQ|u?G~@Oj+nIz*hz9gFFU&m9!_fG4#Kz^4 z*1s7ski?743AuFzF#PpS#%~a7a-VzD(9?kd&<*+D-lLWq_w$)sqQFT5Eo^5-i9ARA z9|wRGS?^SlG^sxP|L)R@8w!Wr(gQI7;YpplG2*Yj?SQ%U<`r^eVcu_``}7$Rf;#4l z!}`vk*L@f~`FmFpZsg{Otf*rorcAitJ3{ZwZbsrTs=f(zCV(A~i~wymho^5#+@%Q1 zb}fZLSd2Fl8gcYJk(4UP{qkfXEp#?j%H)D$^pUc7s~K0gtn8ACyb#qCoRsrt=KGs% zo7fWV=jP%{Pfd0vI~XIKSxLT~W?`G2pFF!x@Gi9v*DMi|RK55VZc!fGCAuIIj^c7- z)6E<4F-wowQD#jp)n;S%1O!)@s_M-5-E5scOY>5#F9x3E+8u@odYK0e8%)dd^RXJt zd{Os`QR3Cjbb*WSU8)+mw7HMnNj+>Ko<0~9^;$2`;~f;sx6LrNQM0FwLMm4Csp@6X z07{LGY8gJeUKD~X>q+!8-{~GcTsES~tl}}sp3$o0I*=weehvSC^&wF?Ge<$0q}aHp zk4=EG*g+wU^}dKeO^}xI&inNFQJ>8Zq6dBVj$Yr8ap*J;n=pEQeK%IylYZ9B)?+RU ztl6dwKdhM|;+%=>^I-*!wZnm5);+694?|iJdva)JZTd`*x%Nl#?%Nn>^ZOzYhW5TN zMD^bL-L{~c%ge)-ThLv{Th|xih#SgTzk$P>hUi(Vb^F`tc~m1cyGfkfuH^rBmltNI ziXwNaVe%hl0bv`2^1mlDKvMg0MRhjtXrsz*YJB$o#&{b1SJh5N_TVOZDe&m#iBEX|8LB{7tRvmz8`_?Q~1JbrWLu{eiYzZk8+NcyF@ z+0c81PY6Svq^8XTn39~eb*;p?uCI?mftO1%oI&cF?A%D>LLQf z7>=lZmJnWWhcb{i!t?I(QjKAh#RMo*siEt%X|pe9uXGJh>orUC;fgDCfO0Rk&_>0} zxq*a3NaGGbV8{1m0NK+#!g<)f4yx*N{gJ$C$6+F~qV_x}kT|4fLied%8qlj2giNDe zmp7mTd^@=u^vHd-QAV6Zb|S!89eLAyL-}%i{=M&DoMn7I`ec91m4GZ|44q%BfwONB;)^K?>*z1%DSlW85B_%MWrbS zsEBkJq$ynyP&$NO6$B(S={*RjCY%CFl!u$RB;oe#dYEfwuZS6^EPc3f zCfKp^MV>TLTsZMyuRpek$1S(*+JU#N?=tPM3%+LdZ8RaKt#o}lBHO-m^h2U@@@jf! z_Bh!Vd{ixKECYNCZY(|Z+wcVp&^S5q7~C;)dEppXohcCRmSJ7bp-@u7#dugXM0hyE zASYN={h&6d99KU*SeU%xAX|+JcduAmY}{CBAIb9mjJ(3i<4&(8h_UOvuO6f9N-iHR zoXT<6M$)Hcsrd5si3v7aSooJB(@ym=UCj$6T8h0q+G$D3{01z(d@qBzOtG^P2sJS# zkB+fCiSlI{d-Sa(MAhV~f17Aanz+_S;i8sJsybT>yASxr6F9S8+pbe_xh_baUKXis zzDaVLxD|i8tfW+!S-*dNeOu46H8aqZ@88zyRZJ)!op_ave$T7bjM@2H9^P5 zQFCz_^ zUvTy9zkX%x(#wJsIty6I4Id-lZWXOp!4X!*@4nU=YncjjaCE}i7Q^GN(AK-(9EW*a zyncRMhx)J(hHbN#ja^nq^u0+dBO5x;CQf5E;`FoE#S+Uhwv}rYxV+bwBR}{3mXO?I z{2;@4#h?Z$kJx9aTC6lHpO@uRPolo0P%R%TPTkXRoN|`g{!B-VQ?919R{*K1RF~0Q zjZJu*T;#0==gmnJx{MDmOQWbRHjQ8$ERp|I#bxsm_EDcc>}@9Osd(dR|Mgl5+K~t@ zS(+_x-RWW`_3km6=E5S*?!Nn-uwXDS=b*OlsFr8$G8owo8Z}heWTx~d^jv;0?4cTV zF@!d?UV#3+P*AU1znDug(=l07;m0k#jXfOVlC4ecUKhQ|b8E*hIyHsa!nOu1Fhb7k zaY6lrLZ~=<(97g)_&O)NN;4mO%jzJ|1u@4jB-upI5u@$%^{Mf)KZU>IczY9&?22An zJV!%pi~oLYbQXxV<3|GBele9b1pW9Xfm%-9X*WP>C#ZNagI%NMR!E$p(2(zq{A1$J zoTEtvtgO`l`>8BT{&-=LKg5MYSb)8hof2*{3i~soF(CHi3_t4?ucG}zrwB&IP6;f5 zl5wy`wROQ-6I-sd5wals6=@=a_gW7Usa8+fNp~Eq$#tt-6vq_tz%pK!-~(wmLU7Dd zURj}+O|m1iwAtfA=T?r#qTL>7nv3~S1s2bKUfe@>4JXkZG#_XTB*(;2N@Wi9%?35# zhq$ToA2g_-=%?M6CZ=uFwJ-FhOjIAF2+%fa^bzaec(o%%$_wkcAbv)}rxzaEplKhT6Ut%SYZv}1=8Ke;5zE?^(DE~7 z#MfG>6KeS0+~i&TyXy`@ZaQeY@N9*h3li>gzE(>;%Cx0I1Da{63TXyH=n@^|Ti;M# zLRmuZVk_lLrk0oUta)_5Gh6JJd;AeguBA)w2`f^?8EDsxC}+MaS^2{Iqw=DT4|G#x zfcgiWQp|hIXcgCM-88T!_+bAbqvZ!vw&3RZb23RddM%rzeYeRHxQaPdX+B%Tz=333 zf1G=l&_Ly^1COReQo%gWgvRm>oDa;dUi{=3vGnKBiN+QM#zQU^~; zaCI;7p0^v17=cqgErH_oDeAVK_W}s`J&&BKnMpWY!K&RXwrK0+FwW5Pna^cAe%STL z`|nRIlo|y13_5@1%GJ1=UF(m8*?xYR&|8<|8Hzv%?|yC*N_rVjR+zq&P-Hr5YAz~j zTMi58QWhSz>U4DB)k|sj{VKdLWQ=B$+P#va+asQr627k6Y3pXwY2rI09HJG2u5^#g zQOr8xMadauRa}}?pPOOlEn#EpKic#u_fDk0(}|wO1g-sDndTEq>agiVg~=DiLoO9n zo#|1sNRG(a!ZS_d#X)jMHZu;kSwxpsqtne-^jTGgl6#FhGj>C1v|mJ0AB%Jzq+~{y zloZZJ2=KkXt$u6)Ta6h@XNo=T8naMfb9eBeXP8alvQ>b#$%7>A1eYjfcTSFT26w0L zQk7n|AGf|If1+)$ccu=nk3N{NM&Gqw$M6lOwMDsB0l}?!8C^Wu_~B^A=G3`Uf5QSX z-hK291Qq>HRsXxG{)_B+2o8+2jwIBI^kR`)2i;*bD#e-Awt1kt(M`YdF0WJ4Twi!*ubbhb(^46XaH&Lx-6v;#!OokHiXVa!&3aZnUT34=E9JW%~L~ZWD1m zt3_l`5DDc8E=)&a`IN8=N;gY`JtfR%Op;NYx*Jc;Du_FV1OOG#lR z*6*ZHPkNl@tGhgf4-hW4Ewtg2jl0e^*f607WBf+@6gA17vHmVrEmh^C`vp7}S-tnM zr+lWw#3HQ`&!V^&&@Zb7T@JRE3LoG!#A+gIMRmB}ub#GZo_QZSzw3w7Uxgd&2h&#f zUmKjOK8MI6*kv*x7HKHsZ9;l9t1YbyS8w;uBuEj=iUb95CQ|N?=^0LA7*|TBKs*bv zO+Wl+P!Ir>L(K1|nXeObk>9{clKx$W<&KFk?Aq~RN4K!kF2Y|k@??~aT5L#wXl`%5 z;5T@Yi{M!b4emelM>FVoJzifi&fMnOM2jKey0DPopvT!4J4-LVOb2b`4BJwqe`%^l zTbAY-b=UhfC+15Pn|5FK7<)$=cWl1M^=u*?sId&IUjOt~+jzT0*cMlc*v^?lcR8fd zi>efyxoq1Q#(6(3+bw8-U%a?^+L4pi)>92VPA5fKgTLguO104*8AM9sc2L!0zL}Zf zm5S3hSSV-U*`4jkhqa-57TDJN`It46UZY!hpUnjr)Hl`JS^UQyAmMt&bWUWqNCq}C`)6`lm|CUXOa*%$ud;W#K zO;v49a|4uVqBd`+euS%LBjpgPv(`s+K^E1~HFej>$5`8_r3cq3FN}MVW2EoWP)|R9 z?VVS(Q@F+kGPA4&>4EA0dimp3U;3h>JoeL-jcHOm+T2stR~=$?xdu1aijHa3`{O0O zLvZolA;sdZN}-NF+Jfp4EjaNryItLyX>DNF?XGX*|H4v0vX&z=NLDs7C{m89 z1TG6`is5;eV2jM|`2EN5(TOGZb?RqcZ#C7+Gz%+~?{ZE@Fo-365?j(JD^D1P+(--MJci!?fLf*QcryMM55c%Na=J@sSxZwG#w2hCrcaNP|B-QLUE*b|q zk=K~n>zl`74^MV#Bj#V9-Wx%>ENGNlKO?;g?#)JHE*Wfjo(h(*Xfcd1iEFOsH&2$( zKiJX9;AKqcGjC-Uu@_4h!4ry_BMrFiaBAhLuvW(5<`e7poeSkR7OY!`+S}O#_3mhs z>!BxK>P;M*##w*mWxAksDCNatQ>Lr^bn^wp6tNA|p?&oAUyA3y!rGDx`Dq*v;jXQ} zmkZp2*Xz;Eu_Di5C{%K~Ter3{Q9F?eY%=9bX-j>^73lJ98;ap8#R<=@wo&TgmEC`v ztf8PBjmJ%O$9ZdLja*h^-NYvVWgbJ#TDn~sSMCE=B=#W34nJkB;+1K%y1T{=61e!G z0uYHRZs%D=I-Dr7dq{$*Q|x#APw@I3nYrOa#NL$D=&yvdIe~N zBdQruOYQn(z`WTzj zQOaN8_rU?Niw0qDmA?*{!%#haMSIk_@2}C)s79dk(?^Q4dTkZQ@6iU9J*t@!FP6Mc zXVD#RlVsSKg&ewdUB-t=!b4iCxQMH7VDKh)!HiV7)~A397u=f`XZ5)w3rCHTG`A=U z7vx1iF39OwbXxGbZD5jcWGYW)D3~d@&TEwqD_hP?H)nIkG?#LgcTdyQ&$!R6@o>P(26phP=ah+Rue}~iJrDD_O zTt>)ni$^g0h>P=5Smf=E+2>rwsUuWA1AEI%E;-(Laq?XsIm7OY!&jHw8({d{Mj<9u z(puPyb$97;zYw}VUAJC0STpt`SRYmpN6Lg78Y_*79W<3a=syStTAM-amA{KUHRM4^zB;F*vR(+Ow2;g?lBSDU7Mb!Tp zHQdL)8_~Yh6GPG7F(tMHQ*31I5f#!uIZl;q(_1@{m@IWHR4e4|ej-9QEXhV{GfIjw zzVECiGrqPBY4f!$bZc%sZ9H?y(C~OXeJpyQKZjup1D`ubYE9q(8wn69+T85GF6E?6CB zmP%J@Nso$mdh(_*dnssu-eX=<9nA!e0Th3(7k6BB;q0$^!KYWJ-8I@tC0ba9y==r& zM7bNI`~2QPgra%H^nAQddu<@PywcdHB^o|B%z)`zA*qKQAMbp{cxf4>60*7!dT`0c zgfLWGSsY`SGCH|Zv@Wpb#F<&z-IKloJYZ_D7`I6#8sO9no;aqEE6?nOrEu=_Fsj|! zff)b~81Qrw{AUk%19`xgXR1U!;Qc75=`FCTiFZzQF<^H{Z3lpqLZ4gTs(3V`|MD?& zn=$o*8^S`f%eiEj{B6O<7dL1-ZlX(k0~C@{^c@#dh08fmPtrVv1bg+oVa!UltA5HO z7b7WHL;j$&zIR8{rz(Im;*)6p&Z+v8;moCv!(L+Y> zJK%wGdr|4tE*T2KMxUh&s+6h4su#Kg_B(F?m50BX&epYSolPcx*hxCDC(kc42{G;_ zYP(oOLT{{>b=K|)+Y)J;x-{_|;l_y7Pck?u$1r_Wy^MxU zbA`(+3YM=F%)1mWn$5$*W-93oc7Cr~ju3^Xs-(*< zK^Yb9#`ti$^KHA|EPGi67vFYMEt4~;bgf{LDy>6YxkfW@=*ZHzF}-wE7UhG=yMJ@GAq6LKvgrJRrU8`+3uW_ADvn9~$k%XG*f4Tv zUVw_3!gy=RbM>C}85Q_MM~MCkwY4D5+IivF@kv{99R&MRRKpXmjVzQ6CZTdXnM3|= z3UvIe$YF~8pS!ho6_D=xJyxuUVgzkAlvc^U(NUt2B zaME}N|7_Z+bV)mD|viWB!-nN^z9IN7pYm!#lPmmH0!J(qLhW7eIk zYA1P=w|AoFD=>UvH8ZFkpNmgc`81LIJJp}Q;_juqTBs>KIKZK2XoYz6;diwPl$GI{ zF!|`7w;q1Q3rtQO$PoF)_}z+83%nQO-+WgNo9y|~*M#@s7L z81o8Br2`{GgOo?4Rt?zT2;^i-j7UG$N3Q_NS4S3X6*%wM zx*fnXC_Hz{+==eF1ux+CowD?OR%-*@W^I?s+b&;?JFVm%#LA%Vi?FIUe>*i3A>m3#J)O|V3jEW*>LPFpMm6}|= zMv1X@gE{hUx%ur;^JsG6>+GnZnPai{Ty8QS#M0sR3&ik=MHT%XSchI={491jV>fCt z2nik`F%*zjC}i%y%FW&)c+c5+OD_16hHwu28R_j)1}RKJxrJ70ISb9x5(;aj>9{fH z>vyQn`U2Tq!uv^k1DaHVtvt6o{10!oH24lF*`l2dp?+U-&pTkONqA7$Aoe${4b&4Q zOgkW5Xg8nm>Agpdp2s}S*9q9Vu_~QGf8Aq!!dlaF-rhE3{gSnFQnl|MukCQfN7wsw zreC#r#ge}g%=AaJdZa419>eVv1_-4X4ZzC%U*xhUrSGJH?G}g}b0P`^+z&vFKuS0? zx_V0bjxjiK-r8b5nM^cKe18m#-!;Jc3QkAApdk8On8bg>9cdm=Jis$r^Zx1cem`u% z2|f_UAsI-l=lDPZW|aD)Qor#s1HpDGz@th-ZC>(!a`4w{V8u>|V`aY)b#OR~^s**{ zWvD;+bLjt>km%n0&xHS34E@gw{};$az~p~e_+O)*|D}`vWn?1F=KoLW#62onN?16} zG%sE0a>|3N``k6Hji;m&wV}@F)znp@1<~IQ-7W`pM)z0rh{QcO^7$TAA|%H9mZj?{ z@?r4th7iy7PI;HHx?{)x_#<77{V}Kt-6F z{LBv>{n~Xv!b@gf?EUe=e=JV=I5=7le<&V!LOny%d8uj(mlkW+_4QL=0_IJlDSw>T zV%cu0$u6sltE*(z(&}B(r0xX5Y{C($!uXk$k*#W-mS{fTF~#=Xokl6b%6Y@s?JQi4Z9#->i(AbK}HS_2Otjuu2kg$ENI{*LYdSN0^>SnkCK*C!LcG83*> zxGlvyW-@UYxnQ(B`&*3DG&zMPtB?cB9O?`rcIXh1k#zhXhzIV|&t?|veJk1|wuYK| z-B-IwdX2qu>?#(Dn?&^W_0taDvwZ9Dgs@C`@L&~hxd)Dn^#`@$+o{A`@j?^i|9HEC(`-`5#W*@^#J2+PbRhL&%9DwW$m-^UW;!Wixs1|k6pe}R8*ww;giO&<87RP zpSLW!`{~Fju~wzlPX%r@d&Bf+&%W9JXtG~#GLyoLPX} z4)p87`fK9_;AET`V;T8BxOlj$GTB}ux8aBaaWG8Z zC@!f2pyS;<*Y294kud{B77mjIzS`(eCD6>PCG1bIu}PJA?>JU%RV`sGCD+b^2W|O; zqfDGyg*+#@Osjj=b@%XZI_C|5ye}#&O~H9?YO9YU54et)1xFA=O2}wSCE#C z(3VbmL;vo6-RG0!no1de+~7ZQ>^%Q1xueG-55HobbB@DTOizr&ip6_9X-C9A=+XCD zsOsIVjFzftAF!{%JDSZJ6X=C)bPb(5Wq0g?5v1|@by_sxr16=_&xNl){Oxy=!$(h^ zm;Ub`(nWvwLuPVi96$B*wV5;ETBrlhbjgVKTIZ3YTC zw6k?jfyXlj`yC>a!&dvGIw zTDw#Nh1le$>$4UmcEhHhUtHjK7^~~^TB=GhsX??3G%E5{QKKqX6@=aPw^#D;n}gH4 z924t9PVM#nq!f-=Z}1@n9(XD516SrY1_28tr8rTcE&RL%7roTMPWw27bes3|8#$0~ zUu`Uvj)%IB`jO=mkdhl2+l{{ajoH|tCm&!=shO7XA7C5kE)`;X^w-W{E}dW2RK^t2 z5!pya_VdeVzf$vU92D9ssDoWpfAf*!^wiS{zKC@khKqqKx&fdmW67|qfjD7%{N;^X z1{E6HR$cFIJD}C&Wy6>xV&2BzkmIn4s0kHYEFCJ~(#d_!ygy1tdN+Ol?HKkHi(K7G zBQvwu{Ck;|9E&a3_B@~Oj-v2^FQNV5tnXflZ|nxveLSRe)Sn_B(H<{hwm4Mw5@FzY zOuXo3mgp3@xRyNQg8#4F@VuHW35hC3TYc}sPcC}C<2ty;c-SBfRRsM)W#HE?Vz?(@;dcWRY)pepbMA^n+iBxs zkVKWIGc&Xd+@898#m`al;}y-=haX@?T4)53Ep&bXu94M7eYMs>88d=xr(J7x8$3Wq z{SOA=>}>5=61}yl)?n~7bee3gIJlt2m)B=Ro$oK+%mf*_ZVpv5a)%xM;o9E=vtz9g2A)I1n6kFByBl!EZg2* zQ$MHKY>d7&4(cI?eVk5B^WtJbyQISx{>FQFZ!3(SciFD#?a zrQ%{(mNktw3K@ua++ScKU3B!QxRxU8Lcq@(D@{gmRNUpy2WP7Y&)DLhTBE9j4b(+D zXFgK!ec7Gt=zD)xhF3g-L~bX`x;NM1!|!Buh`j|mpWXE)O3NCK&o1L3A`RdsV?)3r z+>cR>O@yoRo@sFlI;6^xy-wI$nzpkC`P5-Pudx9<1@&!0Jf7FM@+y>L-6tb;^7GK! zfxg>;eZB{HiiQ|mwn>d7oxZ}s!6b@WZZE55H_KaO&u2Q$Z45#o2*?<$t<;o9Ia0FT zfPid`7?3+g1TFvCYZ_J%iRzR2x}#|9$NV>6Yo`iWBNT*;85;%0x%PHfGyvjP_P z@Q(L~z#>;h11K#NXGf~M-a2>PhS1a|J4sH|#fq@M^U_~w12rBdC3#upt*$S7FYgKQ zNw#;=m~6cQN93)^#votVpHKn*ISzOzFbIx-nNzd2%xF0g-m=@N@4~fglU?c+k-(5QMx-^iqW!?v+ z@K3f-I1g+-_$EZh#7C$n*J$udzk$l2MXasq z`b?+6ko#Jz$av*iTQIe8vq>YPo1s#SAP06Bx8%}mL~aj2)!q0ZjuQsfH(MYqVV5}v_nx-BHy*pq`{cW7FY z4ipLxdid<#Nu*XyqCj41FpoCsnSU7h0cM=*3i2Wx+2gA4bKdy=O8O;`^b|6qicbFs z?0>C&<|eqNQpdM~^j!^4r~?4lGM~jG!ODIx3e}+QUmw%04$~f_si~2Z(U^e3#BB)t z)4$v(xtVu${JB6r!igT>>KD?t!Zv*`loJFjRcrtv>q!aGlZ!JVw!iY*4ix%(D7&;t z?#ucnylVE!yMb9;bF?sL`2tJN$9>%DYZWVY{I-CVKglkR~&lyB` z!8;rlih3KURK;H^OU)z(jJ=RK(X^YPe$HJ1DV7S0-d&%C{fWv@$@|nP&&-&x^Tjg8 z^O75C`(2KaYd>q7_}Z5o$*pnbgR6**EEDrJ$(3iSbpS|SZ^XiniVLN};|~? z`|GWAhPD!wxIy*vRqMBfp2htWGiC1=CUjJ zdWzh#sTlvpksqloFJ8i{AyUWqtd*T2YG-Y_5!9}c62gf6?W>a!ARED{$>L)eL^BUw z#oCo~F!`)^+OWr+h*Wt7#|>0^IK6dhyGF_;!{{}imj`+Rp$*-|k!0E6u^_J8&r__i z)LppY3OC^%U767g{x>*AI`_9 zxV~cz>gmr*SGL+_PWPhAF8Et~_=ur&;PuROs4GL_U z=L_#Yogl?exq{}5E=xP7IW6qmwJVSxZopEmE(?PC;p$@PIQf)KjuQaW20p|}u+0EE z12+DYCG_*|aUoE3mAw|RH+JQUbKL+Z+66o}aFHQGJ-6;=_!6dld9RXYl_3?5i3_Fa zSB--VYhWP8@@r?!2Ybe<(>_21nj_SJvp(vJ+s^z$^KU*vZv{M&1XlkEgqfkC!9U#jdvNEZ zbnRWl?py=hdBi~6UQP&u$drn+ehtE>_yr1hecqs5g*iU-zWDA;ibJ=uh&hT$#Ln=` z+iL=I$OhndG;7oCd}pjtDxh>7i?Pn-a5n-=X^!A<0L2mQmwtU@svpvM75&CC{%ppH z1rMKb2JBN%$O-N!_#rQQWUi)tfN4cx(ulkFuuQ{ z#swU`NW*IH($AjLAqOBpm_)m8oWnhWVl;0VERcXh5jdsLxizzb83JurS#SxQ zn2u*R0ZD@zZ(y+hQY~@!zW&N*&(Jvq)`x;fvU2ZF@r+NUSS zgPqzXL_9a=--HzDq-<-ZK*z`g5bjgy0A&CMHs|4KWhc4Iy|64;U`G6e|*`WWHB; zsmFl*_3N{vf409g8@T3j+M5j_{Ma0^Kh8{-JqnmOXKz4(v7fQFwrqOE>dm9uYNvu3NL1@DVz2-t3G{;(i23NZH6)&fKr#>B zqeKKGsi0=@_o{HIGOO;i*B~Kt8c4{p4vc$l4ZDqx1@nc{aB1H|&0%YN z?Xu;=nEG|`4MSy47zZE98I1Z^c?R^nwe=cO* zb8qgAq>raky!f@blEM}6;!Se?NffV9c^%|S_}z^bN}7FgaheC{oARR>mz!eiqAGfrThj8OCwd|kEm3h=9x8GnB`b` zFV~W-kgwa_#sMiu+uAoEVW*F3G@ezCM zW$e9D|60&W z{6%Ue8Q>twD}})$#DBiuZ?B~5f*=jziF-^ePv-#Sw5n>EwT8;WvGTcGE?o^HBO~uc zr-T+#%W{`F?FXo9an$e1*V@F!AJZALza})PBCZe`54M?Kj$r#DRV-paZBl#t7F1al zw25+ofa>ov>)!_9GY$x*u*Vd17G@lV&TZGG>(bRzKg38Wh*OcpvgvppS^@Xv(W~2|0cK-$&pO|Zb~D&lzVuSX-%%x`iY*kv-U7y? zv&w60UoEAkU3^Vx@lo0?)e}l)CboEY(|N!$Yy?saaLh;eYj0tFeQUSbK=CwGKpMd8 zszBqeUtpnZw={b*Z9j7JRS%|TPStl8W(aCmQq(oz1!ka7CpF7pSuP~KjiCgw@AJ&| z#jkemyxL2Ey)=5l!Iyu=UNuj!tzEIvgLJ<5Ig>_!?B|wh4hXc0bC@ih`Qkx4e%JZF zTNl#7sWYaMfji2Li*7RP;Z{c<5y3HmFc%@)_p@EvoD1))Dfy}(6 z2{&}8E-O6+?5coV=(#suCi+X@5!$Am$+G;ug#95;oRr|54-c;{RjwtgCP;1sIxT3B z#xnux1(-%Gf62a}RhZvxameCvO@3g^{V#6>*rK@gUbXgFCislKHVbXq-2pwIaq|B& z%|eRm!Ah9#a|pa3V0SgZRPQY^bR$*6K=c>R#BqFf2D!Z+1YXaK(`;e7AS&sFmgjxY zA3NYOkW&Z-&)XR!st}Qb22WqF+8SA@=R@Qru@=F(u$a>OZ&QVIcU zM9zBx>sk@hUlBs2h51nlN1#Al?esJ>6G{FNk>HwMWD1O~;O?g1wuRE4N0x za*SI5(J&5`@^OD)aM9k|qcW5d5n_x0J(YPpTR|&+PQc>J{pEYe{a}St?w){o8Goh7 z+vIy?Yi|SPE77$(Fz30qP%@rf&=#~S*T@XMTkq}FyiqC=Rnl`pou}M0pIsTe^b4Q> z&M40N%WMBIPMo65cV3uVC$5Ni;J>yh{oY~yXu{(w`Pb7AVq}) z^HpEP+LmCma?3g60jD|dlW_u>fbi^51r-2EXv_@2gPU(`U;R2>9H4m7xk5oCUOKLU zYjNYo945+aNAZ9il!qEB4Ke&+se{@2!l1|&%J+a!Mis2lmkuZs#^M}67}bMe5*MgOyIYuZsMPnbHrX z3xF5L)ZaS|?8m79RgKN&c|;8Gy%vZ8`9AZF#vuF8=JqQFYb$DP?*_lkE)T5jm>nea z>k<_(c{>b0;Nf4K2e7XmSWTn5dJ!3SZKGHfG$4g%>xu(pAzX)9p9 zh!kM$YufcOL;&c=Uq2kbfBeVK|NgcPZm=2){x@L5{epl?Wu`moasO<9@2@0UfWHb) zLkkljeG(zs*>-JB5WKfp9a#HZmZ|bDt^GTAGl3XYa`Ju@SWUreC9vVrvQWjWS;qYj z9g6_?AR!o>f%Rt(=nw{LE8g*l0p^nM z8EklBGB5yVrXj5&u@LxXrS!Aluf}i`5lb2OA!2)QUtAej+mRYHeT|vARKK+L!-pTj zYlvQrfeIb&%i$1=r~?DgXz>OUF~E0|pr3=k`U@Vm5W+KCL_7dd>1*Ui#UstMvLWvz zW(VLu0FJOkul^5?{*L$m&)^8fs>)|nE@WKnGna~Je>bE~J6HfYq zM$!!AvOG|GY=4%QJPnPUo-<2H77x1)IvV6}oIJm50tF)VzeB=*1=2soax+8~Im_;V zGU}Auh==87EmTNWucz{v;~nARtDjjMK-2eaUS!yV#SdfarSRQSLhkM7e^ z=sn4=d=1114@gJFg8Vm#1e^3BaO1}lojcT6566SAz&+P%;_Jh0So;1pDa6POUFw(x zZ-_G{q4;}ae*WP;aOjNRsbz?HyA8+r<-*k4@*$cxMxk+n1K|8@Bc(^JvTj~JIim@J zTxD|0;U8{Z4nPIHA3u@u>Y;QJh{z;)`ORjDIzDB6i!~LHlcX88C+oF^pEuh zHG%~?<2QO6p?QjpB^8^>b7&$nXAOW0s?j20TwMPET(5wcc(ewMiIfqA&_w09JCAqa z&>>aq{*=ggJ)OxH*A48!o^M#q!Cp=KES6)L}fo8Zs1A$6PDQ*C6uif9;Pn4`e8fqJ_wx zkyDxhO5+cEKgx?;TrTujo6-ftey|5({LV8SGl0OR(AhXpz)riBv8coG#y}qSZFK1N4Q#N8Jwqwddk}%G`~J>#@q_)f6)<+RT-)86qZ{m0_t9aK>M9cP#XfnEK)q+XN6p`K3o2I=(E=x?*)u+u%&@+`~Y9J zk@_A+{&50Tn07(tNnPDDDl;H)3q>&JmR-{!zkml)toF3fGyKWtUqmWFQIx7fq9c8DM9iuA7?^Xmx$o77ZonyYZ|}7G#`A#jKxpvo@XM1bGds1E2(^<>nlIA` zX`G+g9uEs+bnQ!;QK^G^80=1)Tk#qDwhvHuEYIko832QI%m9`k81#sh=LOND6O?4I zZH@)5Dn8N&G36SGRwI=j{Sj5016-jJn|-=~)>w@F#@TyqFfik1uSjp@?0lSI$Hs0OFmP=PT%@HjPA&@rLUhs~;e1L4tyZhY zCDHtzR{e*!l|hjq)HHsX7&3j2m%q5s%61~{Ou8U2YnN7bjvo?;7#o3pff4j?wwuYY z#2JW@1>>^Z(Kn$-#d$zt%q_=d8nS1NnzhcngQWl^z^OxICv2QuW&DsSVUnUienT}I zk<^geK%zSPD2th}9GC|}rh&=Ud4GwpxHJ}QvTvHgc0ZMjfn2=E@;HOR5SZ7UE*bH( zhC(Vt*~-~_rX$H=cNRIVf`B`B1&WB4_bB@eR!xC`7iw?f{v{;*=GO()T0Mik#P&WL z+IxlGsQ=I=mCxPc7HxXKi;$SZ)S6FCz^|K}a)w;&)pOYE`!ZWCh1N#nc3 zn8CXCBj!th+v~l^NczwX%tR0L4{NmA91kK5!~4^eV;!yWfS5+#`D_C+dl<@lk_#YD zv`+~F46)E7LfHp7-ePgum>2SZD0Ol<17uW}VgccMlf15SbHI8>z5shY;K<|+RVzCg z9S-<#hg(RI5~RK zuRlfFQ#Z%*l3oCvNCK6X>Zo zcIyX!3cAjK`OdrB;TLnNwiR+`1=GjE-$?@P)AXjUxYS1QA=L$=Ph<830Bd32^hBRH zPP%@+5LjpIwLrt+t?ixqp&Qg+R1l9@n?tPuhS}BHe@2O>W^T|j3wM};(4h_YAW}>H z$V-WbK}u$o-aJOcaWRlep`5cAkYgt1WJ)Z5z-W1AXW00hKYWiZstPS ztP3Wuhbr=xUFw>Z*qKAaR6`(LLz^1|o<~xPJ?=aZYOvLV^hxh?dHB}#R51CS_P@Tt z#sntDKrwcgLr>+$vjFOLWy)L~Ml%SHe71%~LPc@uyE4a%yEBx5LcyxGNp4mU*{vd$ zbGv&zfBm5OxX92+Rq>UZn~R0LAcOMJiyqGaJK>wo0)MSyQ@;F>fN%mZK)ngL;B?HJP?bbVc4##(KsBt9dkB2m)R~>w~Kv%5{{S9Zq~PC#@sIgi_4l7zx8W>>F@*Y!uWI!L!=JPybXfd z8DnD&=zmoJ8$I)+34rCx19gWDf?RO{q$a(Z($1I<-C(}P2%yAdGZPYjIy9WMyoa9> z?4pNVwFGm!{`G54Q#KA))B$KidNe)DI2w*p^xL`B7QQg*BP}M7N{l7&?W4mk*46bo`GrLlu7g+ zk9iJ-WOg?v2o&u+kEcJv;%5sa20*F%Oy3-lvQje`f^W|Ls1GpFv-g2?uM?@7V8OR- zfZf%u1Wwq$4<#STwKwhe@MCh;B&r1#twED5(Uz!~f9XA5lHBCD6;X3SbSClY#0#0# z;URlqfT+V>T^UMNRXg43uVzJRc3PndvDPi7dj|;7=s_ZEO^ZkD&gKDX)&h@aKOyd- zd7K&OCQ4`1V)N}O!T5<6+`;{}XFB~d2bf#JArd;x`gAD8nn|#cJR;yLs)z*s_oUni zNzf|u_=u25>EuvSj?ClnL;ld4(DDdBzN2yGvTtg4Qr9t=5F?)B;#DgR3=XxVbYS+^ zUtnqIF&Wtj9&c=uT^8~~{GrS&h46ms*vtWBJdir=^%{_Vi+QfksQ-z|YXj^!H6*%p z^#cC}Qyaotfp%P{9ul@mc>b+ngj6GJ)<;@|?@byHo4$&yizfuWzmIM2Hdha8yc=l)$#)X}_(G!#RrN_6}EET_M1~ z$%=(l1Dt+IOS1JoDsO#oh29a6!lVK3;FAlnzYH(65#WxKM$B16R3K`? z;94q|tO1GYcL7ztZ1BR&hwkqp4*SlS7_l<&)NF@VpBaz+D|CX{iGI^HQfs#|y zu5zW}j|U$g9V=T_7Vc;ZDLxVp4zn=Ve|$_)pyfkYO9+FCp}7*b6lyl49 zOP&KjS3C#Bxg~z{enffCLf(|VZ{P=5&H-tk)X#tQ`zwj!08pC?c(sX0&=TiCkF22c z3Qi$jB=iuiu~UpdU41C%u{!Y@=l~R>JLh-mv9VXo70!7~ylh2CDntD<3x0S4l>tLG zoRUBHa%l>|SF^wBzB3tJkmo!GBTp!r#%S+t8V0)w0iGnoL&bu~#{FGVafm>~K*|QmpU6mTwr;VDt|hp=3H2 zZRmLWor_zYR_2FpaJuL;rPTg|;>Tftu7ifO=Kn7kjKZ1 zj>|$#`5Wv!b_SY0-irDBnO>9$BE7$+<^aEB26}m@r{Ia_FwZI3F7=~@CSI?<5GB^X zJ0~k(SvY^sSN%_4{pK0zN1))*%A-G|8YZ!+JM`ew@w2AnzqQ6ohyjI96ewU;YRQ=E zpf8IzgS9AK}~Ui zrGnnvdIK&vL^K(Zn&9u16+#q>*KJSApCiGKlMX^(zbyXch#OK+ zQ2=ROUi_QX;tf#y^#SE0@1w#8Uq9L?08VfWlK-fu$eEjEbTauN>x1d0KPt>t>{z!; zY(0lsARG?2DicD;H7McDEgcKe&A)eB7zrA3$UklKuo@t!fhM#b@;w{n42SR9BJu|M zxp%v%@4EpU7I4%jWoYz>8a$HA0^Y8#JbnF_Rl;w6C%{w#CvAu#GL%(QhM+skLg+~R zs}UfM4;M?ysGeB<`f0q5O7*$3KX5^E#$k`?IOsSDoziV2MXF^mYV)Xgs#}_Unm)Ql zqjVZNoH)k9ymJfx)!%j3cM<}?ci}H^CMMe->Bb<%6h6tYA_DZlwayCKnX-7UoVoOr z(4btSiXLX(`v;SJzR#c&g;6{PQ6WfJJj4NjX>@CYMv7(;?{>OM7Qh<)`l$>5v_ycR zxI1@HM2ZJBo^MOMR|J+g`XjP`w;*qYy2Gwx3M}%W&mqy<-ck)S#NWvV)7J0rtU-(y zr@I9>mf!$7R@nqeAMyqmvWi3%H00L}0htCh?R4HOK2E^Dzn!W9p_u|2(3gY*h~X0l z^12nEJe3vwnn4f(J?wU~PPK+FyLGfE=tQj)E~El-p3Oi8{s$OoZ-V5v9Sel?XT;qf z@a{)Uz3YC90LM5Q05QuHD{RZ@J_VGj!G}&$fO`jehPk6%yYf1DNS zi6!Fxnve=&wpp*$4@rHm92BFFoY9)P5bz9Tpi1t~NV5K}(U9A|IQgJT3V-5j^v zh8!Ac*^W#uiELJYgwqHL2zv2;jsqpHBY;W~0cvuoohDUC53!>vxVN=*Kv)`wToFiB z1b3AzLDy#rRQ(KKs$jHuGh2?X_d^KqQYc@Y3TIQb8(oZ&Vbq)Bsj+yM3Y?0EMj+vr z1(S3lHRN|{n_xKbQ|j0H*0^8nxiQLNm6rJJtkw18G?iNlPJq8;1fJ*%$rxX%FvD^1 zr4KNI!EBXP6+|Hjy6$Sm#C}v}KG<$_nj5x4#;t~l><;RIY@b*dhK%>*EI|SCBCk^@ zjbXUlB^aU)(^TTSSydLoFuo)bkeqZH5x@N56oYGe$pqwTMRY392 z59CXRN7+gJT!&qHY03N*iZ;`#!~OaAs@65^R5MNh!q`|lKr@ivtl=gYxWszaYcFZ( z=aiHualQtO2dQi!DG{T7Z;EIDi>Gy0-ImD2R9YSk?_C~cx&d!XbONp;+KcTa5*t5K z13f?$cwapc@xkvTvS4>*&zFNgnoa@&k&``{=f|)8wB3g@p_v?cFkJCxS2!$s16*U# ztO^IN6vqWNri@TcMDPBi(CL5(j!(Qq&+o7Uu>dDYCm7yH(}0y~T|Z^(_e%#=dkj{f zrK@Q`M8(l@3S47M!o`96oiPNM1Wsu9?te zk38r;1raNJ?}Ru(s>=MBJOIf<{>zMhp}_5MN41uK4qAE|Y_dyk4eB5GNkML4%lE;= zv@C~%Kt>6Yb_QFoMhaO$?WVyG$NzZ~HKD;E4J{_>zitS+MdwH0ntZw^8~Bf?4p^QO z-Y4#tCYMgU3vBts=s7X8Q-fw+hm(Pb!H*LPF#n4KM1t?TT@uNG#WUZh7ZU?T7O*dcB?pWP_T`J17V(BCP3n-s$75F;XUr%!1rK1&Uc1nzIdr&*8s1YsQGR z6BU~b?6T^s~jKya`rrpM5AunNg&->nK|Hs7qY0eloFqcvVid5KWoB^J9 z>?#7g4#sw2i28pQq6`AvfvVi`fT~M-)j9j}m<13?NFU6Jn4n@xHuj5ipQd=Lm`=go zXk7nA%E%q4V%mex?6#gitpS8;Y5;V~MS5440i?|By1$P8?6HmE;Sbz{<5Nc2d8DTO=mb!NKl5puPO##*HZl`h~hfUBs{+j&KurUkY+pbg|3Zn zmVn9Zw+vEj+knaR>}{#JU<}7bY9DayxU7R{f&8dSG|>jp;!{&nu?vS+OZE^25tB8< z_RM*sG4E<`16k24wLY*3R}lR6InedV;z0XT|d2k;~fgHfOS$u z6bS>vh;SZP{aEK6I(d=@v`yxzn=_`9+z;3-sh!q9u~5)9t-pl};{noIS6u91dfY31 z;5=aj5LIkEIZxQ~FHpSp*BNCm0XYc#b>mcJD7Xs>85{*1J-J&<8Xb>D^@q~S$;aL~PY z{UytE{X+;=>TE1wG$T4QLVv3F;#MF+RxfDL6%18xTa@K_p%*)IHeIrsR1brYL9L*S zW*;3OKHAH&mXpPux=QyjGqFmmBgz{-eHJ$0C@`}UvKEY3M^UjBa!jtqWOZhfCWCA+ z;R_i29&^Tql<8;(mw;Lm?X@3{OhhPMa>ede)Y;mKJ7^9^q9ISZjZ%;A25KsJfHPc0L?dvMbRMN?$pG2L2X{o=^4B3M4h(?bgxX>L>fCl!0}(X*inRUo3yWP z6u#{aL`SuYOa-dQ`Jt%bnsJ}h6j!3Dp+t-i_V=0fr{DnmU_94w)(kxQ9OyA7q;m%j z9XXdM`q}3t5XC5azw1fkpG;+o>Cu|Iz$4gYn*_0ZlQIdB8uP>dNLWU505o2@N&DJv zG=NKQaViC?Hu9ASJ>HiEFAlu1)-uughnpa}t(-|suM6To^ z8ui~#OJAIm^xS;uoE3biA9to39iz?F-1$q0^SAMC2;7c2J#L>axMtfUIPbd@ltmGl zW~<_}g=%ZYs3!nt!^OU03fLOZycf6!hZ<|sZU!nx1Zq~1{^0FcU0t8P%2z8Eit|^rpRf zu3XCEy!Ys1e-PW{4VbQ^v(>`=n7du1tYh(I--}*8)|!w(T1x|OWBGa8*iD{I1sYga zYGU_LZ2e0<>6fR>MAtdG0DAHQs#a*FNaNl?_f})3QB+vlp6)jguAL(A-h)Eoq_X5{ zM_}sKM;=WH5rp7z0_qw0ddJMzC$U#Ux#$rq5O0vH(fwKq9)_Z+Sfz&th(-t)Y<_8` ziT6wMj$nRo@RopyG8g98V&$WyumxK8BE;Hx2Rr) zfdHJ|vP4Ds%&?lzV@QC@#dlv}`!w55Jw>##a0elPxK;6P0n%N9#$H5sUHo2R^3%65 zAMM+Qy7~b(D9Pe^)gb8*_+X;dcPlT1rewRV0BtnyO-*b>3>Uz1JW#LhYJ-SN!tE!7mUXHX8b5o*z>Z%*$yEiu&aD^9F2vcAeza;7_QuA#rJ>Q zR)mnc#&yrrAs}#iv=3-urPGf8>oge$P`DB(8^ep#(!8MrE$d6VXUe0ozdnFY^TgXu zsXy)f^|ZnEw7yO2VtgTcmk_hH&fk9`v$gA0vJ0z^#X-ng!x#O`QMsh`(?MRmj3iKV z%eH+*al5JM#rg1(218=x-me@C<1|?u@OD()JJ$~sHMzDB%1X`JHE-=Zxe$Q|7qsTj zsSg}h#wt4ztyahT*X2KNaV?Uk%?;~=TJWy_`_H`;$3l>6eTBmv{1;ud7*7N4MY6Qv^8blCLNaFjNDv&HfO>_n?QVFe} z5TWI|qU$Vj_E@JPI!Ej5d-e-ar^4rh&=Wd-itV2ZF{>PD1z)wUn7VMMfyQrLtp#m2 zx&3uqyEj3gpZo?zEDRbJ%Xt`TA&XIutYtn&6Mt^5H+(y8qNPuaV!Qw>1ZFwa?lRW^ z_-63C`lc3iQmXf~w4D89rfu4a@X=^9?G+XDyGE=VppFG%pZBD+yJltU$x@DyR476F z{Nc2^Ccsd)*>v|)?i|^(gHRxZ2M+$HD`Y|kTD^!ym@-2=!nY}s_wi8>(o8XO^w zPk?pWHkO5F0E{x=tgqIcHp@uPLydQzW=pOrji3aIYi#HeaJ0hMZt_K(DVo}hb>aiB zb@_|#(@fryR3e3*65xA<^s`ll`3 z(DqqG$}RKy9E#|}jAH;VDS3G;X0~{}qVVwe#+pbLsWaZmHDqrQ;KgIb8 zLN|JzXCwO64aB#{CHuilsKMiVkIsgP(22aQ(qg3?`ncucdK*$H3M0WzQ-N$EETWxC ze+0eTNt1(0II!fpuqcmWdWvBW5@Z^J?6b{P2}oUY;qJHtwAB{r-u^aUE^q%u(6VL) zE&C%d2-a$hUh_WZLp% z3%>;G=gTR}bFaH?&r6tqnWCk+fF9KW`ysnq;OBhy6cE;(zB?`#7(WA$t$R`b0-gX$ z$f57ryg`mOO{c%f#J)dG(+1R`+~zB{BrcAF%Kt@JQZY$Kob8O=%VsYgKBFUFl&vCR zQ)2t1;b;E$3$?W$!lqZ*R^x+Z>w47{99(!!07gzJ@5!C!X-(L(;L3KSRc<7&_DFB;c}br=C~#+s7Q7=~)eXhKBYVGUa_nn#5L!B<6tnWkot$8=8cq1af0I=&27caoLb_R0T#&f!t185Gk@UQ6u%;-Xcx>&v)y0}y6h5npl@^TciXkHki_mC7UttJ(^QzdIiwpjq#xhyiK?p# zzivE8n!@(2+kq=X;p@-K4C0nHhn>;F;-9%Eq3$o(gMx@7=%+_h7se7Y(NROMm*^wrW>ca3UxlQuFVJYZhW4}D1G^5S8FB%^ zlNQa?ftzTlKK`-1LH)W~u(U~N8Z<+I_g<883Rs=~=|s|HrQ5F$6Sa!ji&zbn?4#_m zRK}T6qJIe5IG6~Eoia9)$!n=jvF(1j;eqW6&z=BSYS?>AiMdEEPf$TF{8)}qE8-&w zGXgB|UyY@#iD3$|HqHC4dbKEf8z4JBf$M3{Uiz3Q;)O`#)mHSE`wqO$Q>o(jbNNd= zHx53kDoaXPtE98>mgL68*fa`V2Zr}6#fvb)l+0sZ*QWT4nOMB_iB**3k!KOH#&v7? zR^q1_pmZE8JwD~oY$AmC4Xq)cL<=sandDOO*FxDZxhm}T{pi5?DDt*g`{v$rBMb?m zrXt+#AFdn2Va6a}lw^?o-1#D%yR;=k+wA`U& z-*GFdYZF`CJjcYGZXkh(Xj~vfn}2_|rja#J>mZQQb$foZ4L0B`)_iV>ylc?84r#rX z`wpgUc{2){y`-!pxqtNZ_NvIRj(uOvmZp>H{g~@J{(#RdyTF=Jo|6%G6*K8A@%4np)3hnDPflBhk8Y6`p z`GgOzz?-oHU7SZJOlVaR*Nq4rWwQID1u%rEAF4BiT}g?U#*gj8vo7bo|*8OZN-Df?{)zmI|w_k2_ak4g*ar z!jwG3-n}NoxQ1mYYp{~jmEJqSO-gFEZyHzrMbekLt9xS8{BM z4y{HNq}rcP^*e&5CG#Y9K?1W(o8n4g#ueB2ne1Li6KN!ynRRB|g%I=nnp6u}*zCBr z|1LW9b9SGMVg+x-5sFzJ{;OpcxRK7L*t$^sP%Y7fpSivz`0yRc zXE%(-P4|yB<7R}IM?Gb7F7PN$l;Ac=+=*#7ycCl~Q2-^Xu>KH_Rdb-9K}U>rMRxpt zwSv=|$BzuJuWTyhLM-v zXn;2(L1JlllGz;l$H4ir4~LlBZz_I6u^xgeUYl5!sU9ezV#Bf&#$7Ovt*NEIrLo1y zvN6;pyIy7)bv0MdQ8idOYvQLLQH14)HhglAMBUVnf2f!LFz3j05fKqFzy2+^_U=-? z{A74>|Kf$p*SvN)rFG2F4mriP5PWN!n6yco}SCok#7+GapAOoypySR z$w!H)aKn8x9itr0k3SL~HQ`Yc9<`CzKIJ>YAR`Piqvb6lXGa)bh2d2gUMYj=$9V`j zI>PWeGJyP@cm6wqj8!20sfdV-&DvEfng8&}3~r_Wj#&_n{BH&e;hGEkGymB$`a6MA zc*zKRawF4@zj{Y_u?Tbce|RkmGugizjz2zp!lNcUYQm%TH#1;ikP!x%f7*?lpF1Pz WsCC-qK7hg^YgZeuy7bL9zyAg7G|Eo^ diff --git a/site-src/images/serve-mul-gen-AI-models.png b/site-src/images/serve-mul-gen-AI-models.png deleted file mode 100644 index 957a054f15cf0f8c88b405e397ed8ad7cd13d10d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 412887 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 (4_Gy_#pv1 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 - -## GIEP Definitions - -### GIEP States - -Each GIEP has a state, which tracks where it is in the GIEP process. - -GIEPs can move to some states from any other state: - - * **Declined**: The GIEP has been declined and further work will not occur. - * **Deferred:** We do not currently have bandwidth to handle this GIEP, it may - be revisited in the future. - * **Declined:** This proposal was considered by the community but ultimately - rejected. - * **Withdrawn:** This proposal was considered by the community but ultimately - withdrawn by the author. - -There is a special state to cover Memorandum GIEPs: - - * **Memorandum**: These GIEPs either: - * Document an agreement for further work, creating no spec changes - themselves, or - * Update the GIEP process. - -API GIEPs flow through a number of states, which generally correspond to the -level of stability of the change described in the GIEP: - - * **Provisional:** The goals described by this GIEP have consensus but - implementation details have not been agreed to yet. - * **Prototyping:** An extension of `Provisional` which can be opted in to in - order to indicate to the community that there are some active practical - tests and experiments going on which are intended to be a part of the - development of this GIEP. This may include APIs or code, but that content - _must_ not be distributed with releases. - * **Implementable:** The goals and implementation details described by this - GIEP have consensus but have not been fully implemented yet. - * **Experimental:** This GIEP has been implemented and is part of the - "Experimental" release channel. Breaking changes are still possible, up to - and including complete removal and moving to `Rejected`. - * **Standard:** This GIEP has been implemented and is part of the "Standard" - release channel. It should be quite stable. - * **Completed**: All implementation work on this API GIEP has been completed. - -### Relationships between GIEPs - -GIEPs can have relationships between them. At this time, there are three -possible relationships: - -* **Obsoletes** and its backreference **ObsoletedBy**: when a GIEP is made - obsolete by another GIEP, and has its functionality completely replaced. The - Obsoleted GIEP is moved to the **Declined** state. -* **Extends** and its backreference **ExtendedBy**: when a GIEP has additional - details or implementation added in another GIEP. -* **SeeAlso**: when a GIEP is relevant to another GIEP, but is not affected in - any other defined way. - -Relationships are tracked in the YAML metadata files accompanying each GIEP. - -### GIEP metadata file - -Each GIEP has a YAML file containing metadata alongside it, please keep it up to -date as changes to the GIEP occur. - -In particular, note the `authors`, and `changelog` fields, please keep those up -to date. - -## Process - -### 1. Discuss with the community - -Before creating a GIEP, share your high level idea with the community. There are -several places this may be done: - -- A [new GitHub - Discussion](https://github.com/kubernetes-sigs/gateway-api-inference-extension/discussions/new) -- On our [Slack Channel](https://kubernetes.slack.com/archives/C08E3RZMT2P) -- On one of our [community - meetings](https://gateway-api-inference-extension.sigs.k8s.io/contributing/?h=meetings#meetings) - -Please default to GitHub discussions: they work a lot like GitHub issues which -makes them easy to search. - -### 2. Create an Issue -[Create a GIEP -issue](https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/new) -in the repo describing your change. At this point, you should copy the outcome -of any other conversations or documents into this document. - -### 3. Agree on the Goals -Although it can be tempting to start writing out all the details of your -proposal, it's important to first ensure we all agree on the goals. - -For API GIEPs, the first version of your GIEP should aim for a "Provisional" -status and leave out any implementation details, focusing primarily on "Goals" -and "Non-Goals". - -For Memorandum GIEPs, the first version of your GIEP will be the only one, as -Memorandums have only a single stage - `Accepted`. - -### 3. Document Implementation Details -Now that everyone agrees on the goals, it is time to start writing out your -proposed implementation details. These implementation details should be very -thorough, including the proposed API spec, and covering any relevant edge cases. -Note that it may be helpful to use a shared doc for part of this phase to enable -faster iteration on potential designs. - -It is likely that throughout this process, you will discuss a variety of -alternatives. Be sure to document all of these in the GIEP, and why we decided -against them. At this stage, the GIEP should be targeting the "Implementable" -stage. - -### 4. Implement the GIEP as "Experimental" - -With the GIEP marked as "Implementable", it is time to actually make those -proposed changes in our API. In some cases, these changes will be documentation -only, but in most cases, some API changes will also be required. It is important -that every new feature of the API is marked as "Experimental" when it is -introduced. Within the API, we use `` tags to denote -experimental fields. Within Golang packages (conformance tests, CLIs, e.t.c.) we -use the `experimental` Golang build tag to denote experimental functionality. - -Some other requirements must be met before marking a GIEP `Experimental`: - -- the graduation criteria to reach `Standard` MUST be filled out -- a proposed probationary period (see next section) must be included in the GIEP - and approved by maintainers. - -Before changes are released they MUST be documented. GIEPs that have not been -both implemented and documented before a release cut off will be excluded from -the release. - -#### Probationary Period - -Any GIEP in the `Experimental` phase is automatically under a "probationary -period" where it will come up for re-assessment if its graduation criteria are -not met within a given time period. GIEPs that wish to move into `Experimental` -status MUST document a proposed period (6 months is the suggested default) that -MUST be approved by maintainers. Maintainers MAY select an alternative time -duration for a probationary period if deemed appropriate, and will document -their reasoning. - -> **Rationale**: This probationary period exists to avoid GIEPs getting "stale" -> and to provide guidance to implementations about how relevant features should -> be used, given that they are not guaranteed to become supported. - -At the end of a probationary period if the GIEP has not been able to resolve its -graduation criteria it will move to "Rejected" status. In extenuating -circumstances an extension of that period may be accepted by approval from -maintainers. GIEPs which are `Rejected` in this way are removed from the -experimental CRDs and more or less put on hold. GIEPs may be allowed to move -back into `Experimental` status from `Rejected` for another probationary period -if a new strategy for achieving their graduation criteria can be established. -Any such plan to take a GIEP "off the shelf" must be reviewed and accepted by -the maintainers. - -> **Warning**: It is extremely important** that projects which implement -> `Experimental` features clearly document that these features may be removed in -> future releases. - -### 5. Graduate the GIEP to "Standard" - -Once this feature has met the [graduation -criteria](/concepts/versioning/#graduation-criteria), it is time to graduate it -to the "Standard" channel of the API. Depending on the feature, this may include -any of the following: - -1. Graduating the resource to beta -2. Graduating fields to "standard" by removing `` tags -3. Graduating a concept to "standard" by updating documentation - -### 6. Close out the GIEP issue - -The GIEP issue should only be closed once the feature has: -- Moved to the standard channel for distribution (if necessary) -- Moved to a "v1" `apiVersion` for CRDs -- been completely implemented and has wide acceptance (for process changes). - -In short, the GIEP issue should only be closed when the work is "done" (whatever -that means for that GIEP). - -## Format - -GIEPs should match the format of the template found in -[GIEP-696](/GIEPs/GIEP-696). - -## Out of scope - -What is out of scope: see [text from KEP][kep-when-to-use]. Examples: - -* Bug fixes -* Small changes (API validation, documentation, fixups). It is always possible - that the reviewers will determine a "small" change ends up requiring a GIEP. - -## FAQ - -#### Why is it named GIEP? -To avoid potential confusion if people start following the cross references to -the full GEP or KEP process. - -#### Why have a different process than mainline? -Gateway API has some differences with most upstream KEPs. Notably Gateway API -intentionally avoids including any implementation with the project, so this -process is focused entirely on the substance of the API. As this project is -based on CRDs it also has an entirely separately release process, and has -developed concepts like "release channels" that do not exist in upstream. - -#### Is it ok to discuss using shared docs, scratch docs etc? -Yes, this can be a helpful intermediate step when iterating on design details. -It is important that all major feedback, discussions, and alternatives -considered in that step are represented in the GIEP though. A key goal of GIEPs -is to show why we made a decision and which alternatives were considered. If -separate docs are used, it's important that we can still see all relevant -context and decisions in the final GIEP. - -#### When should I mark a GIEP as `Prototyping` as opposed to `Provisional`? -The `Prototyping` status carries the same base meaning as `Provisional` in that -consensus is not complete between stakeholders and we're not ready to move -toward releasing content yet. You should use `Prototyping` to indicate to your -fellow community members that we're in a state of active practical tests and -experiments which are intended to help us learn and iterate on the GIEP. These -can include distributing content, but not under any release channel. - -#### Should I implement support for `Experimental` channel features? -Ultimately one of the main ways to get something into `Standard` is for it to -mature through the `Experimental` phase, so we really _need_ people to implement -these features and provide feedback in order to have progress. That said, the -graduation of a feature past `Experimental` is not a forgone conclusion. Before -implementing an experimental feature, you should: - -* Clearly document that support for the feature is experimental and may - disappear in the future. -* Have a plan in place for how you would handle the removal of this feature from - the API. - -[kep]: https://github.com/kubernetes/enhancements -[kep-when-to-use]: - https://github.com/kubernetes/enhancements/tree/master/keps#do-i-have-to-use-the-kep-process From b0fbffbc5aa4d62de4a27c3ef7e68de448ed75cf Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Mon, 29 Sep 2025 00:28:16 +0300 Subject: [PATCH 072/133] chore: update helm to use latest release instead of rc (#1662) Signed-off-by: Nir Rozenbaum --- site-src/guides/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 611675025..b0faa4971 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -91,7 +91,7 @@ Tooling: ```bash export GATEWAY_PROVIDER=gke - export IGW_CHART_VERSION=v1.0.1-rc.1 + export IGW_CHART_VERSION=v1.0.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ @@ -103,7 +103,7 @@ Tooling: ```bash export GATEWAY_PROVIDER=istio - export IGW_CHART_VERSION=v1.0.1-rc.1 + export IGW_CHART_VERSION=v1.0.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ @@ -115,7 +115,7 @@ Tooling: ```bash export GATEWAY_PROVIDER=none - export IGW_CHART_VERSION=v1.0.1-rc.1 + export IGW_CHART_VERSION=v1.0.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ @@ -127,7 +127,7 @@ Tooling: ```bash export GATEWAY_PROVIDER=none - export IGW_CHART_VERSION=v1.0.1-rc.1 + export IGW_CHART_VERSION=v1.0.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ From 05ec72cdb346e045002659435b8f40fd103f60cd Mon Sep 17 00:00:00 2001 From: Murphy Chen Date: Tue, 30 Sep 2025 00:10:18 +0800 Subject: [PATCH 073/133] support vLLM cache salting in prefix aware scorer (#1646) * support vLLM cache salting in prefix aware scorer * Apply suggestions from code review Co-authored-by: Cong Liu * fix lint --------- Co-authored-by: Cong Liu --- .../framework/plugins/multi/prefix/plugin.go | 6 ++- pkg/epp/scheduling/types/types.go | 16 ++++++++ pkg/epp/util/request/body_test.go | 38 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go index 4633825d9..00ae9a7dc 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go @@ -263,7 +263,7 @@ func (p *Plugin) matchLongestPrefix(ctx context.Context, hashes []BlockHash) map } // hashPrompt divides the prompt into blocks and calculate the prefix cache for each block. -// hash(0) is the hash of the model name, since different models generally don't share prefix cache. +// hash[0] is calculated including the model name and cache_salt(if provided), since different models generally don't share prefix cache. // For block i, hash(i) = hash(block i content, hash(i-1)). func hashPrompt(ctx context.Context, request *types.LLMRequest, cacheBlockSize int, maxPrefixBlocks int) []BlockHash { loggerDebug := log.FromContext(ctx).V(logutil.DEBUG) @@ -292,6 +292,10 @@ func hashPrompt(ctx context.Context, request *types.LLMRequest, cacheBlockSize i // Add the model to the first block hash so that different models have different hashes even with the same body. h := xxhash.New() _, _ = h.Write([]byte(request.TargetModel)) + if cacheSalt := request.Body.CacheSalt(); cacheSalt != "" { + _, _ = h.Write([]byte(cacheSalt)) + } + prevBlockHash := BlockHash(h.Sum64()) for i := 0; i+cacheBlockSize <= len(userInput); i += cacheBlockSize { h.Reset() diff --git a/pkg/epp/scheduling/types/types.go b/pkg/epp/scheduling/types/types.go index f65cd07b7..349dac105 100644 --- a/pkg/epp/scheduling/types/types.go +++ b/pkg/epp/scheduling/types/types.go @@ -73,6 +73,18 @@ type LLMRequestBody struct { ChatCompletions *ChatCompletionsRequest `json:"chat_completions,omitempty"` } +func (r *LLMRequestBody) CacheSalt() string { + if r.ChatCompletions == nil && r.Completions == nil { + return "" + } + + if r.ChatCompletions != nil { + return r.ChatCompletions.CacheSalt + } + + return r.Completions.CacheSalt +} + // CompletionsRequest is a structured representation of the fields we parse out of the // /v1/completions request body. // This struct includes fields usable for plugins and scheduling decisions - and not the entire @@ -80,6 +92,8 @@ type LLMRequestBody struct { type CompletionsRequest struct { // Prompt is the prompt that was sent in the request body. Prompt string `json:"prompt,omitempty"` + // CacheSalt is an optional request parameter to isolate prefix caches for security reasons. + CacheSalt string `json:"cache_salt,omitempty"` } func (r *CompletionsRequest) String() string { @@ -105,6 +119,8 @@ type ChatCompletionsRequest struct { ContinueFinalMessage bool `json:"continue_final_message,omitempty"` AddGenerationPrompt bool `json:"add_generation_prompt,omitempty"` ChatTemplateKWArgs map[string]interface{} `json:"chat_template_kwargs,omitempty"` + // CacheSalt is an optional request parameter to isolate prefix caches for security reasons. + CacheSalt string `json:"cache_salt,omitempty"` } func (r *ChatCompletionsRequest) String() string { diff --git a/pkg/epp/util/request/body_test.go b/pkg/epp/util/request/body_test.go index 64ab6de11..51389e561 100644 --- a/pkg/epp/util/request/body_test.go +++ b/pkg/epp/util/request/body_test.go @@ -225,6 +225,44 @@ func TestExtractRequestData(t *testing.T) { }, wantErr: true, }, + { + name: "completions request with cache_salt", + body: map[string]any{ + "model": "test", + "prompt": "test prompt", + "cache_salt": "Z3V2bmV3aGxza3ZubGFoZ3Zud3V3ZWZ2bmd0b3V2bnZmc2xpZ3RoZ2x2aQ==", + }, + want: &types.LLMRequestBody{ + Completions: &types.CompletionsRequest{ + Prompt: "test prompt", + CacheSalt: "Z3V2bmV3aGxza3ZubGFoZ3Zud3V3ZWZ2bmd0b3V2bnZmc2xpZ3RoZ2x2aQ==", + }, + }, + }, + { + name: "chat completions request with cache_salt", + body: map[string]any{ + "model": "test", + "messages": []any{ + map[string]any{ + "role": "system", "content": "this is a system message", + }, + map[string]any{ + "role": "user", "content": "hello", + }, + }, + "cache_salt": "Z3V2bmV3aGxza3ZubGFoZ3Zud3V3ZWZ2bmd0b3V2bnZmc2xpZ3RoZ2x2aQ==", + }, + want: &types.LLMRequestBody{ + ChatCompletions: &types.ChatCompletionsRequest{ + Messages: []types.Message{ + {Role: "system", Content: "this is a system message"}, + {Role: "user", Content: "hello"}, + }, + CacheSalt: "Z3V2bmV3aGxza3ZubGFoZ3Zud3V3ZWZ2bmd0b3V2bnZmc2xpZ3RoZ2x2aQ==", + }, + }, + }, } for _, tt := range tests { From cc2c3f3de5e78c74ef2d6e189e5ad508f9a3bb43 Mon Sep 17 00:00:00 2001 From: Kellen Swain Date: Mon, 29 Sep 2025 16:30:16 -0700 Subject: [PATCH 074/133] Small documentation update to include how to _use_ InferenceObjective in the API summary (#1659) --- site-src/api-types/inferenceobjective.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site-src/api-types/inferenceobjective.md b/site-src/api-types/inferenceobjective.md index 9b7fe744c..4ce6a7ccd 100644 --- a/site-src/api-types/inferenceobjective.md +++ b/site-src/api-types/inferenceobjective.md @@ -9,6 +9,10 @@ The **InferenceObjective** API defines a set of serving objectives of the specific request it is associated with. This CRD currently houses only `Priority` but will be expanded to include fields such as SLO attainment. +## Usage + +To associate a request to the InferencePool with a specific InferenceObjective, the system uses a specific header: `x-gateway-inference-objective` with the value of the header set to the InferenceObjective metadata name. So the calling client must set the header key/value on the request to associate the selected InferenceObjective. If no InferenceObjective is selected, default values are used. + ## Spec -The full spec of the InferenceModel is defined [here](/reference/x-spec/#inferenceobjective). \ No newline at end of file +The full spec of the InferenceObjective is defined [here](/reference/x-spec/#inferenceobjective). \ No newline at end of file From 217e7b12b993bd586ba1f5c69de0baef302cc1af Mon Sep 17 00:00:00 2001 From: Kellen Swain Date: Tue, 30 Sep 2025 02:18:18 -0700 Subject: [PATCH 075/133] update make target naming in helm chart pushs commands (#1666) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 952b2aa4f..b85a4d5dd 100644 --- a/Makefile +++ b/Makefile @@ -339,11 +339,11 @@ uninstall: generate kustomize ## Uninstall CRDs from the K8s cluster specified i ##@ Helm .PHONY: inferencepool-helm-chart-push -inferencepool-helm-chart-push: yq helm +inferencepool-helm-chart-push: yq helm-install CHART=inferencepool EXTRA_TAG="$(EXTRA_TAG)" IMAGE_REGISTRY="$(IMAGE_REGISTRY)" YQ="$(YQ)" HELM="$(HELM)" ./hack/push-chart.sh .PHONY: bbr-helm-chart-push -bbr-helm-chart-push: yq helm +bbr-helm-chart-push: yq helm-install CHART=body-based-routing EXTRA_TAG="$(EXTRA_TAG)" IMAGE_REGISTRY="$(IMAGE_REGISTRY)" YQ="$(YQ)" HELM="$(HELM)" ./hack/push-chart.sh ##@ Release From b2c89a80c2a3f7f2fd18968c98a45c34f875f2cd Mon Sep 17 00:00:00 2001 From: Beka Modebadze <58038950+bexxmodd@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:14:16 -0700 Subject: [PATCH 076/133] Proposal for Multi-Cluster InferencePools (#1374) * Proposal for mc inference gateway. * Formatting updates. * Give PR number to the proposal. * Adding author(s) * Removed ds_store files. * Removed ds_store file * Formatting updates. * Replaced lists to use asterisks. * Removed local from supported values. * Add details to MCI proposal Signed-off-by: Daneyon Hansen * Makes EPP Port Singular Signed-off-by: Daneyon Hansen * Removes Routing Mode Config and Resolved Open Questions Signed-off-by: Daneyon Hansen * Update docs/proposals/1374-multi-cluster-inference/README.md Co-authored-by: Ryan Zhang * Implement Sept 15 Meeting Feedback Signed-off-by: Daneyon Hansen * Resolves review feedback Signed-off-by: Daneyon Hansen * Adds TBD InferencePool status condition Signed-off-by: Daneyon Hansen * Adds sync topology and mods workflow Signed-off-by: Daneyon Hansen * Updates InferencePoolImport status to include parents Signed-off-by: Daneyon Hansen * Minor fixed based on robscott review feedback Signed-off-by: Daneyon Hansen * Refactors InferencePoolImport status Signed-off-by: Daneyon Hansen * Updates status.clusters refs Signed-off-by: Daneyon Hansen * Removes controller type and adds top-level conditions Signed-off-by: Daneyon Hansen --------- Signed-off-by: Daneyon Hansen Co-authored-by: Daneyon Hansen Co-authored-by: Ryan Zhang --- .../1374-multi-cluster-inference/README.md | 493 ++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 docs/proposals/1374-multi-cluster-inference/README.md diff --git a/docs/proposals/1374-multi-cluster-inference/README.md b/docs/proposals/1374-multi-cluster-inference/README.md new file mode 100644 index 000000000..321b0d2a4 --- /dev/null +++ b/docs/proposals/1374-multi-cluster-inference/README.md @@ -0,0 +1,493 @@ +# Multi-Cluster InferencePools + +Author(s): @danehans, @bexxmodd, @robscott + +## Proposal Status + + ***Draft*** + +## Summary + +An Inference Gateway (IG) provides efficient routing to LLM workloads in Kubernetes by sending requests to an Endpoint Picker (EPP) associated with +an [InferencePool](https://gateway-api-inference-extension.sigs.k8s.io/api-types/inferencepool/) and routing the request to a backend model server +based on the EPP-provided endpoint. Although other multi-cluster inference approaches may exist, this proposal extends the current model to support +multi-cluster routing so capacity in one cluster can serve traffic originating in another cluster or outside the clusters. + +### Why Multi-Cluster? + +GPU capacity is scarce and fragmented. Many users operate multiple clusters across regions and providers. A single cluster rarely satisfies peak or +sustained demand, so a prescribed approach is required to share GPU capacity across clusters by: + +- Exporting an InferencePool from a source (“exporting”) cluster. +- Importing the exported InferencePool into one or more destination (“importing”) clusters with enough detail for IGs to route requests to the associated + remote model server Pods. + +### Goals + +- Enable IGs to route to a group of common model server Pods, e.g. InferencePools, that exist in different clusters. +- Align the UX with familiar [Multi-Cluster Services (MCS)](https://multicluster.sigs.k8s.io/concepts/multicluster-services-api/) concepts (export/import). +- Keep the API simple and implementation-agnostic. + +### Non-Goals + +- Managing DNS or automatic naming. +- Over-specifying implementation details to satisfy a single approach to Multi-Cluster InferencePools. + +## Design Proposal + +The Multi-Cluster InferencePools (MCIP) model will largely follow the Multi-Cluster Services (MCS) model, with a few key differences: + +- DNS and ClusterIP resolution will be omitted, e.g. ClusterSetIP. +- A separate export resource will be avoided, e.g. ServiceExport, by inlining the concept within InferencePool. + +An InferencePoolImport resource is introduced that is meant to be fully managed by a controller. This resource provides the information +required for IGs to route LLM requests to model server endpoints of an InferencePool in remote clusters. How the IG routes the request to the remote +cluster is implementation-specific. + +### Routing Modes + +An implementation must support at least one of the following routing modes: + +- Endpoint Mode: An IG of an importing cluster routes to endpoints selected by the EPP of the exported InferencePool. Pod and Service network connectivity + MUST exist between cluster members. +- Parent Mode: An IG of an importing cluster routes to parents, e.g. Gateways, of the exported InferencePool. Parent connectivity MUST exist between cluster + members. + +### Sync Topology (Implementation-Specific) + +An implementation must support at least one of the following distribution topologies. The API does not change between them (same export annotation and InferencePoolImport). + +1. **Hub/Spoke** + - A hub controller has visibility into member clusters. + - It watches exported InferencePools and creates/updates the corresponding InferencePoolImport (same namespace/name) in each member cluster. + - Typical when a central control plane has K8s API server access for each member cluster. + - Consider [KEP-5339-style](https://github.com/kubernetes/enhancements/tree/master/keps/sig-multicluster/5339-clusterprofile-plugin-credentials) pluggable credential issuance to avoid hub-stored long-lived secrets. + +2. **Push/Pull** + - A cluster-local controller watches exported InferencePools and publishes export state to a central hub. + - A cluster-local controller watches the central hub and CRUDs the local InferencePoolImport. + - Typical when you want no hub-stored member credentials, looser coupling, and fleet-scale fan-out. + +### Workflow + +1. **Export an InferencePool:** An [Inference Platform Owner](https://gateway-api-inference-extension.sigs.k8s.io/concepts/roles-and-personas/) + exports an InferencePool by annotating it. +2. **Distribution (topology-dependent, API-agnostic):** + - **Hub/Spoke:** A central hub controller watches exported InferencePools and mirrors a same-name/namespace InferencePoolImport into each member cluster, updating `status.controllers[]` to reflect the managing controller, exporting clusters, etc.. + - **Push/Pull:** A cluster-local controller watches exported InferencePools and publishes export records to a central hub. In each member cluster, a controller watches the hub and CRUDs the local InferencePoolImport (same name/namespace) and maintains `status.controllers[]`. +3. **Importing Controller (common):** + - Watches local InferencePoolImport and: + - Programs the IG dataplane based on the supported routing mode. + - If this controller differs from the Exporting Controller, populate `status.controllers[]`. + - Manages `status.controllers[].parentRefs` with the Group, Kind, and Name of the local parent resource, e.g. Gateway, of the InferencePoolImport. +4. **Data Path:** + The data path is dependent on the export mode selected by the implementation. + - Endpoint Mode: Client → local IG → (make scheduling decision) → local/remote EPP → selected model server endpoint → response. + - Parent Mode: Client → local IG → (make scheduling decision) → local EPP/remote parent → remote EPP → selected model server endpoint → response. + +### InferencePoolImport Naming + +The exporting controller will create an InferencePoolImport resource using the exported InferencePool namespace and name. A cluster name entry in +`status.controllers[]` is added for each cluster that exports an InferencePool with the same ns/name. + +**Note:** EPP ns/name sameness is not required. + +### InferencePool Selection + +InferencePool selection is implementation-specific. The following are examples of how an IG may select one exported InferencePool over another: + +- **Metrics-based:** Scrape EPP-exposed metrics (e.g., ready pods) to bias InferencePool choice. +- **Active-Passive:** Basic EPP readiness checks (gRPC health). + +**Note:** When an exported InferencePool is selected by an IG, standard EPP semantics are used to select endpoints of that pool. + +### API Changes + +#### InferencePool Annotation + +The following annotation is being proposed to indicate the desire to export the InferencePool to member clusters of a ClusterSet. + +The `inference.networking.x-k8s.io/export` annotation key indicates a desire to export the InferencePool: + +```yaml +inference.networking.x-k8s.io/export: "" +``` + +Supported Values: + +- `ClusterSet` – export to all members of the current [ClusterSet](https://multicluster.sigs.k8s.io/api-types/cluster-set/). + +**Note:** Additional annotations, e.g. region/domain scoping, filter clusters in the ClusterSet, routing mode configuration, etc. and +potentially adding an InferencePoolExport resource may be considered in the future. + +#### InferencePool Status + +An implementation MUST set a parent status entry with a parentRef of kind `InferencePoolImport` and the ns/name of the exported InferencePool. +This informs the user that the request to export the InferencePool has been recognized by the implementation, along with the implementation's +unique `ControllerName`. `ControllerName` is a domain/path string that indicates the name of the controller that wrote the status entry. + +An `Exported` parent condition type is being added to surface status of the exported InferencePool. An implementation MUST set +this status condition to `True` when the annotated InferencePool has been exported to all member clusters of the ClusterSet and `False` +for all other reasons. When the export annotation is removed from the InferencePool, an implementation MUST remove this condition type. + +#### InferencePoolImport + +A cluster-local, controller-managed resource that represents an imported InferencePool. It primarily communicates a relationship between an exported +InferencePool and the exporting cluster name. It is not user-authored; status carries the effective import. Inference Platform Owners can reference +the InferencePoolImport, even if the local cluster does not have an InferencePool. In the context of Gateway API, it means that an HTTPRoute can be +configured to reference an InferencePoolImport to route matching requests to remote InferencePool endpoints. This API will be used almost exclusively +for tracking endpoints, but unlike MCS, we actually have two distinct sets of endpoints to track: + +1. Endpoint Picker Extensions (EPPs) +2. InferencePool parents, e.g. Gateways + +Key ideas: + +- Map exported InferencePool to exporting controller and cluster. +- Name/namespace sameness with the exported InferencePool (avoids extra indirection). +- Conditions: Surface a controller-level status condition to indicate that the InferencePoolImport is ready to be used. +- Conditions: Surface parent-level status conditions to indicate that the InferencePoolImport is referenced by a parent, e.g. Gateway. + +See the full Go type below for additional details. + +## Controller Responsibilities + +**Export Controller:** + +- Discover exported InferencePools. +- For each ClusterSet member cluster, CRUD InferencePoolImport (mirrored namespace/name). +- Populate the exported InferencePool with status to indicate a unique name of the managing controller and an `Exported` condition that + indicates the status of the exported pool. +- Populate an InferencePoolImport `status.controllers[]` entry with the managing controller name, cluster, and status conditions associated + with the exported InferencePool. + +**Import Controller:** + +- Watch InferencePoolImports. +- Program the IG data plane to route matching requests based on the supported routing mode. +- Manage InferencePoolImport `status.controllers[].parentRefs` with a cluster-local parentRef, e.g. Gateway, when the InferencePoolImport + is referenced by a managed HTTProute. + +## Examples + +### Exporting Cluster (Cluster A) Manifests + +In this example, Cluster A exports the InferencePool to all clusters in the ClusterSet. This will +cause the exporting controller to create an InferencePoolImport resource in all clusters. + +```yaml +apiVersion: inference.networking.k8s.io/v1 +kind: InferencePool +metadata: + name: llm-pool + namespace: example + annotations: + inference.networking.x-k8s.io/export: "ClusterSet" # Export the pool to all clusters in the ClusterSet +spec: + endpointPickerRef: + name: epp + portNumber: 9002 + selector: + matchLabels: + app: my-model + targetPorts: + - number: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: epp + namespace: example +spec: + selector: + app: epp + ports: + - name: ext-proc + port: 9002 + targetPort: 9002 + appProtocol: http2 + type: LoadBalancer # EPP exposed via LoadBalancer +``` + +### Importing Cluster (Cluster B) Manifests + +In this example, the Inference Platform Owner has configured an HTTPRoute to route to endpoints of the Cluster A InferencePool +by referencing the InferencePoolImport as a `backendRef`. The parent IG(s) of the HTTPRoute are responsible for routing to the +endpoints selected by the exported InferencePool's EPP. + +The InferencePoolImport is controller-managed; shown here only to illustrate the expected status shape. + +```yaml +apiVersion: inference.networking.x-k8s.io/v1alpha1 +kind: InferencePoolImport +metadata: + name: llm-pool # mirrors exporting InferencePool name + namespace: example # mirrors exporting InferencePool namespace +status: + controllers: + - controllerName: example.com/mcip-controller + type: MultiCluster + exportingClusters: + - name: cluster-a + - controllerName: example.com/ig-controller + type: GatewayClass + parents: + - parentRef: + group: gateway.networking.k8s.io + kind: Gateway + name: inf-gw # Cluster-local parent, e.g. gateway + conditions: + - type: Accepted + status: "True" +--- +# Route in the importing cluster that targets the imported pool +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: llm-route + namespace: example +spec: + parentRefs: + - name: inf-gw + hostnames: + - my.model.com + rules: + - matches: + - path: + type: PathPrefix + value: /completions + backendRefs: + - group: inference.networking.x-k8s.io + kind: InferencePoolImport + name: llm-pool +``` + +An implementation MUST conform to Gateway API specifications, including when the HTTPRoute contains InferencePool and InferencePoolImport `backendRefs`, +e.g. `weight`-based load balancing. In the following example, traffic MUST be split equally between Cluster A and B InferencePool endpoints when +using the following `backendRefs`: + +```yaml + backendRefs: + - group: inference.networking.k8s.io + kind: InferencePool + name: llm-pool + weight: 50 + - group: inference.networking.x-k8s.io + kind: InferencePoolImport + name: llm-pool + weight: 50 +``` + +**Note:** The above example does not export the local "llm-pool" InferencePool. If this InferencePool was exported, it would be included in +the example InferencePoolImport and the implementation would be responsible for balancing the traffic between the two pools. + +### Go Types + +The following Go types define the InferencePoolImport API being introduced by this proposal. Note that a separate Pull Request will be used +to finalize and merge all Go types into the repository. + +```go +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" +) + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Namespaced,shortName=ipimp +// +kubebuilder:subresource:status +// +// InferencePoolImport represents an imported InferencePool from another cluster. +// This resource is controller-managed; users typically do not author it directly. +type InferencePoolImport struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the InferencePoolImport. + Spec InferencePoolImportSpec `json:"spec,omitempty"` + + // Status defines the current state of the InferencePoolImport. + Status InferencePoolImportStatus `json:"status,omitempty"` +} + +// Unused but defined for potential future use. +type InferencePoolImportSpec struct{} + +type InferencePoolImportStatus struct { + // Controllers is a list of controllers that are responsible for managing this InferencePoolImport. + // + // +kubebuilder:validation:Required + Controllers []ImportController `json:"controllers"` +} + +// ImportController defines a controller that is responsible for managing this InferencePoolImport. +type ImportController struct { + // Name is a domain/path string that indicates the name of the controller that manages this + // InferencePoolImport. This corresponds to the GatewayClass controllerName field when the + // controller will manage parents of type "Gateway". Otherwise, the name is implementation-specific. + // + // Example: "example.net/import-controller". + // + // The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes + // names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + // + // A controller MUST populate this field when writing status and ensure that entries to status + // populated with their controller name are removed when they are no longer necessary. + // + // +required + Name ControllerName `json:"name"` + + // ExportingClusters is a list of clusters that exported the InferencePool associated with this + // InferencePoolImport. Required when the controller is responsible for CRUD'ing the InferencePoolImport + // from the exported InferencePool(s). + // + // +optional + ExportingClusters []ExportingCluster `json:"exportingClusters"` + + // Parents is a list of parent resources, typically Gateways, that are associated with the + // InferencePoolImport, and the status of the InferencePoolImport with respect to each parent. + // + // Required when the controller manages the InferencePoolImport as an HTTPRoute backendRef. The controller + // must add an entry for each parent it manages and remove the parent entry when the controller no longer + // considers the InferencePoolImport to be associated with that parent. + // + // +optional + // +listType=atomic + Parents []v1.ParentStatus `json:"parents,omitempty"` + + // Conditions track the state of the InferencePoolImport. + // + // Known condition types are: + // + // * "Accepted" + // + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=8 + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// ControllerName is the name of a controller that manages a resource. It must be a domain prefixed path. +// +// Valid values include: +// +// * "example.com/bar" +// +// Invalid values include: +// +// * "example.com" - must include path +// * "foo.example.com" - must include path +// +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$` +type ControllerName string + +// ExportingCluster defines a cluster that exported the InferencePool associated to this InferencePoolImport. +type ExportingCluster struct { + // Name of the exporting cluster (must be unique within the list). + // + // +kubebuilder:validation:Required + Name string `json:"name"` +} + +// +kubebuilder:object:root=true +type InferencePoolImportList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []InferencePoolImport `json:"items"` +} +``` + +### Failure Mode + +EPP failure modes continue to work as-is and are independent of MCIP. + +#### EPP Selection + +Since an IG decides which EPP to use for endpoint selection when multiple InferencePool/InferencePoolImport `backendRefs` exist, +an implementation MAY use EPP metrics and/or health data to make a load-balancing decision. + +## Alternatives + +### Option 1: Reuse MCS API for EPP + +Reuse MCS to export EPP Services. This approach provides simple infra, but may be confusing to users (you “export EPPs” not pools) and +requires a separate MCS parent export for parent-based inter-cluster routing. + +**Pros**: + +- Reuses existing MCS infrastructure. +- Relatively simple to implement. + +**Cons**: + +- Referencing InferencePools in other clusters requires you to create an InferencePool locally. +- In this model, you don’t actually choose to export an InferencePool, you export the EPP or InferencePool parent(s) service, that could lead to confusion. +- InferencePool is meant to be a replacement for a Service so it may seem counterintuitive for a user to create a Service to achieve multi-cluster inference. + +## Option 2: New MCS API + +One of the key pain points we’re seeing here is that the current iteration of the MCS API requires a tight coupling between name/namespace and kind, with Service being the only kind of backend supported right now. This goes against the broader SIG-Network direction of introducing more focused kinds of backends (like InferencePool). To address this, we could create a resource that has an `exportRef` that allows for exporting different types of resources. + +While we were at it, we could combine the separate `export` and `import` resources that exist today, with `export` acting as the (optional) spec of this new resource, and `import` acting as `status` of the resource. Instead of `import` resources being automatically created, users would create them wherever they wanted to reference or export something to a MultiClusterService. + +Here’s a very rough example: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: MultiClusterService +metadata: + name: epp + namespace: example +spec: + exportRef: + group: v1 + kind: Service + name: epp + scope: ClusterSet +status: + conditions: + - type: Accepted + status: "True" + message: "MultiClusterService has been accepted" + lastTransitionTime: "2025-03-30T01:33:51Z" + targetCount: 1 + ports: + - protocol: TCP + appProtocol: HTTP + port: 8080 +``` + +### Open Questions + +#### EPP Discovery + +- Should EPP Deployment/Pod discovery be standardized (labels/port names) for health/metrics auto-discovery? + +#### Security + +- Provide a standard way to bootstrap mTLS between importing IG and exported EPP/parents, e.g. use BackendTLSPolicy? + +#### Ownership and Lifecycle + +- Garbage collection when export is withdrawn (delete import?) and how to drain traffic safely. See [this comment](https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/1374#discussion_r2357523781) for additional context. + +### Prior Art + +- [GEP-1748: Gateway API Interaction with Multi-Cluster Services](https://gateway-api.sigs.k8s.io/geps/gep-1748/) +- [Envoy Gateway with Multi-Cluster Services](https://gateway.envoyproxy.io/latest/tasks/traffic/multicluster-service/) +- [Multi-Cluster Service API](https://multicluster.sigs.k8s.io/concepts/multicluster-services-api/) + +### References + +- [Initial Multi-Cluster Inference Design Doc](https://docs.google.com/document/d/1QGvG9ToaJ72vlCBdJe--hmrmLtgOV_ptJi9D58QMD2w/edit?tab=t.0#heading=h.q6xiq2fzcaia) + +### Notes for reviewers + +- The InferencePoolImport CRD is intentionally status-only to keep the UX simple and controller-driven. +- The InferencePool namespace sameness simplifies identity and lets HTTPRoute authors reference imports without new indirection. From 68d7440d19f134f926bf3dd24cee02982c95735d Mon Sep 17 00:00:00 2001 From: mnmehta <30246802+mnmehta@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:16:55 -0700 Subject: [PATCH 077/133] Update inference_gateway.json (#1676) Fix type in dashboard title --- tools/dashboards/inference_gateway.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dashboards/inference_gateway.json b/tools/dashboards/inference_gateway.json index 50e4eb152..949b03ffe 100644 --- a/tools/dashboards/inference_gateway.json +++ b/tools/dashboards/inference_gateway.json @@ -36,7 +36,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": "# Inferece Gateway Dashboard\n\nPlease see https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/pkg/epp/metrics for more details of underlying metrics used in the dashboard.", + "content": "# Inference Gateway Dashboard\n\nPlease see https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/pkg/epp/metrics for more details of underlying metrics used in the dashboard.", "mode": "markdown" }, "pluginVersion": "10.2.4", From 62e74de6e37172d91fde9b6373b749e33f60f985 Mon Sep 17 00:00:00 2001 From: Dawid Nowak Date: Sat, 4 Oct 2025 20:34:57 +0000 Subject: [PATCH 078/133] Adding Kubvernor to the list of implementors (#1313) * Adding Kubvernor section to the implementors list Signed-off-by: Dawid Nowak * Addressing PR comments Signed-off-by: Dawid Nowak * Addressing PR comments Signed-off-by: Dawid Nowak * Update site-src/implementations/gateways.md Co-authored-by: Nir Rozenbaum * PR Review * PR Review Signed-off-by: Dawid Nowak * Removing extra file Signed-off-by: Dawid Nowak * Removing conformance mention from the description --------- Signed-off-by: Dawid Nowak Co-authored-by: Nir Rozenbaum --- site-src/implementations/gateways.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/site-src/implementations/gateways.md b/site-src/implementations/gateways.md index 4ce052b0e..62abe017f 100644 --- a/site-src/implementations/gateways.md +++ b/site-src/implementations/gateways.md @@ -8,6 +8,7 @@ This project has several implementations that are planned or in progress: * [Google Kubernetes Engine][4] * [Istio][5] * [Kgateway][6] +* [Kubvernor][7] [1]:#agentgateway [2]:#alibaba-cloud-container-service-for-kubernetes @@ -15,6 +16,7 @@ This project has several implementations that are planned or in progress: [4]:#google-kubernetes-engine [5]:#istio [6]:#kgateway +[7]:#kubvernor ## Agentgateway @@ -96,3 +98,11 @@ Issue](https://github.com/istio/istio/issues/55768). implementation that can run [independently](https://gateway-api-inference-extension.sigs.k8s.io/guides/#__tabbed_3_3), as an [Istio waypoint](https://kgateway.dev/blog/extend-istio-ambient-kgateway-waypoint/), or within your [llm-d infrastructure](https://github.com/llm-d-incubation/llm-d-infra) to improve accelerator (GPU) utilization for AI inference workloads. + +## Kubvernor + +[Kubvernor Rust API Gateway][krg] is an open-source, highly experimental implementation of API controller in Rust programming language. Currently, Kubvernor supports Envoy Proxy. The project aims to be as generic as possible so Kubvernor can be used to manage/deploy different gateways (Envoy, Nginx, HAProxy, etc.). See the docs for the [usage][krgu]. + +[krg]:https://github.com/kubvernor/kubvernor +[krgu]: https://github.com/kubvernor/kubvernor/blob/main/README.md + From d0aedb7cbd42de99a8f14c90c081628a0dab418b Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Mon, 6 Oct 2025 22:08:58 -0700 Subject: [PATCH 079/133] Multi-Cluster: Adds v1alpha1 API Types and Docs (#1658) * Multi-Cluster: Adds v1alpha1 API Types and Docs Signed-off-by: Daneyon Hansen * Resolves initial review comments Signed-off-by: Daneyon Hansen * Makes ControllerName optional Signed-off-by: Daneyon Hansen --------- Signed-off-by: Daneyon Hansen --- Makefile | 17 +- api/v1/inferencepool_types.go | 66 ++++ apix/v1alpha1/doc.go | 22 ++ apix/v1alpha1/inferencepoolimport_types.go | 143 ++++++++ apix/v1alpha1/shared_types.go | 24 ++ apix/v1alpha1/zz_generated.deepcopy.go | 156 +++++++++ apix/v1alpha1/zz_generated.register.go | 70 ++++ .../applyconfiguration/api/v1/parentstatus.go | 14 +- .../apix/v1alpha1/exportingcluster.go | 43 +++ .../apix/v1alpha1/importcontroller.go | 87 +++++ .../apix/v1alpha1/inferencepoolimport.go | 233 +++++++++++++ .../v1alpha1/inferencepoolimportstatus.go | 44 +++ client-go/applyconfiguration/utils.go | 12 + client-go/clientset/versioned/clientset.go | 13 + .../versioned/fake/clientset_generated.go | 7 + .../clientset/versioned/fake/register.go | 2 + .../clientset/versioned/scheme/register.go | 2 + .../typed/apix/v1alpha1/apix_client.go | 101 ++++++ .../versioned/typed/apix/v1alpha1/doc.go | 20 ++ .../versioned/typed/apix/v1alpha1/fake/doc.go | 20 ++ .../apix/v1alpha1/fake/fake_apix_client.go | 40 +++ .../v1alpha1/fake/fake_inferencepoolimport.go | 53 +++ .../apix/v1alpha1/generated_expansion.go | 21 ++ .../apix/v1alpha1/inferencepoolimport.go | 74 ++++ .../externalversions/apix/interface.go | 8 + .../apix/v1alpha1/inferencepoolimport.go | 102 ++++++ .../apix/v1alpha1/interface.go | 45 +++ .../informers/externalversions/generic.go | 5 + .../apix/v1alpha1/expansion_generated.go | 27 ++ .../apix/v1alpha1/inferencepoolimport.go | 70 ++++ ...ence.networking.k8s.io_inferencepools.yaml | 19 + ...working.x-k8s.io_inferencepoolimports.yaml | 330 ++++++++++++++++++ crd-ref-docs.yaml | 2 +- mkdocs.yml | 8 +- pkg/generator/main.go | 1 + site-src/api-types/inferenceobjective.md | 2 +- site-src/api-types/inferencepoolimport.md | 26 ++ site-src/reference/spec.md | 29 +- site-src/reference/x-v1a1-spec.md | 126 +++++++ .../reference/{x-spec.md => x-v1a2-spec.md} | 0 40 files changed, 2074 insertions(+), 10 deletions(-) create mode 100644 apix/v1alpha1/doc.go create mode 100644 apix/v1alpha1/inferencepoolimport_types.go create mode 100644 apix/v1alpha1/shared_types.go create mode 100644 apix/v1alpha1/zz_generated.deepcopy.go create mode 100644 apix/v1alpha1/zz_generated.register.go create mode 100644 client-go/applyconfiguration/apix/v1alpha1/exportingcluster.go create mode 100644 client-go/applyconfiguration/apix/v1alpha1/importcontroller.go create mode 100644 client-go/applyconfiguration/apix/v1alpha1/inferencepoolimport.go create mode 100644 client-go/applyconfiguration/apix/v1alpha1/inferencepoolimportstatus.go create mode 100644 client-go/clientset/versioned/typed/apix/v1alpha1/apix_client.go create mode 100644 client-go/clientset/versioned/typed/apix/v1alpha1/doc.go create mode 100644 client-go/clientset/versioned/typed/apix/v1alpha1/fake/doc.go create mode 100644 client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_apix_client.go create mode 100644 client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_inferencepoolimport.go create mode 100644 client-go/clientset/versioned/typed/apix/v1alpha1/generated_expansion.go create mode 100644 client-go/clientset/versioned/typed/apix/v1alpha1/inferencepoolimport.go create mode 100644 client-go/informers/externalversions/apix/v1alpha1/inferencepoolimport.go create mode 100644 client-go/informers/externalversions/apix/v1alpha1/interface.go create mode 100644 client-go/listers/apix/v1alpha1/expansion_generated.go create mode 100644 client-go/listers/apix/v1alpha1/inferencepoolimport.go create mode 100644 config/crd/bases/inference.networking.x-k8s.io_inferencepoolimports.yaml create mode 100644 site-src/api-types/inferencepoolimport.md create mode 100644 site-src/reference/x-v1a1-spec.md rename site-src/reference/{x-spec.md => x-v1a2-spec.md} (100%) diff --git a/Makefile b/Makefile index b85a4d5dd..b002538e0 100644 --- a/Makefile +++ b/Makefile @@ -307,13 +307,24 @@ live-docs: docker build -t gaie/mkdocs hack/mkdocs/image docker run --rm -it -p 3000:3000 -v ${PWD}:/docs gaie/mkdocs -.PHONY: apix-ref-docs -apix-ref-docs: crd-ref-docs +.PHONY: api-ref-docs-all +api-ref-docs-all: apix-v1a1-ref-docs apix-v1a2-ref-docs api-ref-docs + +.PHONY: apix-v1a1-ref-docs +apix-v1a1-ref-docs: crd-ref-docs + ${CRD_REF_DOCS} \ + --source-path=${PWD}/apix/v1alpha1 \ + --config=crd-ref-docs.yaml \ + --renderer=markdown \ + --output-path=${PWD}/site-src/reference/x-v1a1-spec.md + +.PHONY: apix-v1a2-ref-docs +apix-v1a2-ref-docs: crd-ref-docs ${CRD_REF_DOCS} \ --source-path=${PWD}/apix/v1alpha2 \ --config=crd-ref-docs.yaml \ --renderer=markdown \ - --output-path=${PWD}/site-src/reference/x-spec.md + --output-path=${PWD}/site-src/reference/x-v1a2-spec.md .PHONY: api-ref-docs api-ref-docs: crd-ref-docs diff --git a/api/v1/inferencepool_types.go b/api/v1/inferencepool_types.go index 76026df64..92e7aff14 100644 --- a/api/v1/inferencepool_types.go +++ b/api/v1/inferencepool_types.go @@ -202,8 +202,42 @@ type ParentStatus struct { // // +required ParentRef ParentReference `json:"parentRef,omitzero"` + + // ControllerName is a domain/path string that indicates the name of the controller that + // wrote this status. This corresponds with the GatewayClass controllerName field when the + // parentRef references a Gateway kind. + // + // Example: "example.net/gateway-controller". + // + // The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes names: + // + // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + // + // Controllers MAY populate this field when writing status. When populating this field, controllers + // should ensure that entries to status populated with their ControllerName are cleaned up when they + // are no longer necessary. + // + // +optional + ControllerName ControllerName `json:"controllerName,omitempty"` } +// ControllerName is the name of a controller that manages ParentStatus. It must be a domain prefixed +// path. +// +// Valid values include: +// +// * "example.com/bar" +// +// Invalid values include: +// +// * "example.com" - must include path +// * "foo.example.com" - must include path +// +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$` +type ControllerName string + // InferencePoolConditionType is a type of status condition for the InferencePool. type InferencePoolConditionType string @@ -274,6 +308,38 @@ const ( InferencePoolReasonInvalidExtensionRef InferencePoolReason = "InvalidExtensionRef" ) +const ( + // InferencePoolConditionExported is a type of condition that indicates whether the + // controller was able to export the InferencePool to the specified clusters. + // + // Possible reasons for this condition to be True are: + // + // * "Exported" + // + // Possible reasons for this condition to be False are: + // + // * "NotRequested" + // * "NotSupported" + // + // Controllers MAY raise this condition with other reasons, but should + // prefer to use the reasons listed above to improve interoperability. + InferencePoolConditionExported InferencePoolConditionType = "Exported" + + // InferencePoolReasonExported is a reason used with the "Exported" condition when the + // condition is true. + InferencePoolReasonExported InferencePoolReason = "Exported" + + // InferencePoolReasonNotRequested is a reason used with the "Exported" condition when the + // condition is false and no export was requested by the InferencePool. This indicates a + // deliberate non-action rather than an error. + InferencePoolReasonNotRequested InferencePoolReason = "NotRequested" + + // InferencePoolReasonNotSupported is a reason used with the "Exported" condition when the + // condition is false and the export was requested but is not supported by the implementation. + // Controllers should include details in the condition message. + InferencePoolReasonNotSupported InferencePoolReason = "NotSupported" +) + // ParentReference identifies an API object. It is used to associate the InferencePool with a // parent resource, such as a Gateway. type ParentReference struct { diff --git a/apix/v1alpha1/doc.go b/apix/v1alpha1/doc.go new file mode 100644 index 000000000..122c3b952 --- /dev/null +++ b/apix/v1alpha1/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1alpha1 contains API Schema definitions for the +// inference.networking.x-k8s.io API group. +// +// +kubebuilder:object:generate=true +// +groupName=inference.networking.x-k8s.io +package v1alpha1 diff --git a/apix/v1alpha1/inferencepoolimport_types.go b/apix/v1alpha1/inferencepoolimport_types.go new file mode 100644 index 000000000..2239aa3d0 --- /dev/null +++ b/apix/v1alpha1/inferencepoolimport_types.go @@ -0,0 +1,143 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" +) + +// InferencePoolImport is the Schema for the InferencePoolImports API. +// +// +kubebuilder:object:root=true +// +kubebuilder:resource:shortName=infpimp +// +kubebuilder:subresource:status +// +kubebuilder:storageversion +// +genclient +type InferencePoolImport struct { + metav1.TypeMeta `json:",inline"` + + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Status defines the observed state of the InferencePoolImport. + // + // +optional + //nolint:kubeapilinter // status should not be a pointer. + Status InferencePoolImportStatus `json:"status,omitempty"` +} + +// InferencePoolImportList contains a list of InferencePoolImports. +// +// +kubebuilder:object:root=true +type InferencePoolImportList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []InferencePoolImport `json:"items"` +} + +// InferencePoolImportStatus defines the observed state of the InferencePoolImport. +type InferencePoolImportStatus struct { + // Controllers is a list of controllers that are responsible for managing the InferencePoolImport. + // + // +listType=map + // +listMapKey=name + // +kubebuilder:validation:MaxItems=8 + // +kubebuilder:validation:Required + Controllers []ImportController `json:"controllers"` +} + +// ImportController defines a controller that is responsible for managing the InferencePoolImport. +type ImportController struct { + // Name is a domain/path string that indicates the name of the controller that manages the + // InferencePoolImport. Name corresponds to the GatewayClass controllerName field when the + // controller will manage parents of type "Gateway". Otherwise, the name is implementation-specific. + // + // Example: "example.net/import-controller". + // + // The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes + // names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + // + // A controller MUST populate this field when writing status and ensure that entries to status + // populated with their controller name are removed when they are no longer necessary. + // + // +required + Name ControllerName `json:"name"` + + // ExportingClusters is a list of clusters that exported the InferencePool(s) that back the + // InferencePoolImport. Required when the controller is responsible for CRUD'ing the InferencePoolImport + // from the exported InferencePool(s). + // + // +optional + ExportingClusters []ExportingCluster `json:"exportingClusters,omitempty"` + + // Parents is a list of parent resources, typically Gateways, that are associated with the + // InferencePoolImport, and the status of the InferencePoolImport with respect to each parent. + // + // Ancestor would be a more accurate name, but Parent is consistent with InferencePool terminology. + // + // Required when the controller manages the InferencePoolImport as an HTTPRoute backendRef. The controller + // must add an entry for each parent it manages and remove the parent entry when the controller no longer + // considers the InferencePoolImport to be associated with that parent. + // + // +optional + // +listType=atomic + Parents []v1.ParentStatus `json:"parents,omitempty"` + + // Conditions track the state of the InferencePoolImport. + // + // Known condition types are: + // + // * "Accepted" + // + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=8 + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// ControllerName is the name of a controller that manages a resource. It must be a domain prefixed path. +// +// Valid values include: +// +// - "example.com/bar" +// +// Invalid values include: +// +// - "example.com" - must include path +// - "foo.example.com" - must include path +// +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$` +type ControllerName string + +// ClusterName is the name of a cluster that exported the InferencePool. +// +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=253 +type ClusterName string + +// ExportingCluster defines a cluster that exported the InferencePool that backs this InferencePoolImport. +type ExportingCluster struct { + // Name of the exporting cluster (must be unique within the list). + // + // +kubebuilder:validation:Required + Name ClusterName `json:"name"` +} diff --git a/apix/v1alpha1/shared_types.go b/apix/v1alpha1/shared_types.go new file mode 100644 index 000000000..56fd71d33 --- /dev/null +++ b/apix/v1alpha1/shared_types.go @@ -0,0 +1,24 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +// ExportAnnotationKey is the annotation key used to export an InferencePool. +var ExportAnnotationKey = "inference.networking.x-k8s.io/export" + +// ExportAnnotationVal is the annotation value used to export an InferencePool +// to all clusters. +var ExportAnnotationVal = "ClusterSet" diff --git a/apix/v1alpha1/zz_generated.deepcopy.go b/apix/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 000000000..f6f182616 --- /dev/null +++ b/apix/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,156 @@ +//go:build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/gateway-api-inference-extension/api/v1" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExportingCluster) DeepCopyInto(out *ExportingCluster) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportingCluster. +func (in *ExportingCluster) DeepCopy() *ExportingCluster { + if in == nil { + return nil + } + out := new(ExportingCluster) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImportController) DeepCopyInto(out *ImportController) { + *out = *in + if in.ExportingClusters != nil { + in, out := &in.ExportingClusters, &out.ExportingClusters + *out = make([]ExportingCluster, len(*in)) + copy(*out, *in) + } + if in.Parents != nil { + in, out := &in.Parents, &out.Parents + *out = make([]v1.ParentStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImportController. +func (in *ImportController) DeepCopy() *ImportController { + if in == nil { + return nil + } + out := new(ImportController) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InferencePoolImport) DeepCopyInto(out *InferencePoolImport) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferencePoolImport. +func (in *InferencePoolImport) DeepCopy() *InferencePoolImport { + if in == nil { + return nil + } + out := new(InferencePoolImport) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InferencePoolImport) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InferencePoolImportList) DeepCopyInto(out *InferencePoolImportList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]InferencePoolImport, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferencePoolImportList. +func (in *InferencePoolImportList) DeepCopy() *InferencePoolImportList { + if in == nil { + return nil + } + out := new(InferencePoolImportList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InferencePoolImportList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InferencePoolImportStatus) DeepCopyInto(out *InferencePoolImportStatus) { + *out = *in + if in.Controllers != nil { + in, out := &in.Controllers, &out.Controllers + *out = make([]ImportController, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InferencePoolImportStatus. +func (in *InferencePoolImportStatus) DeepCopy() *InferencePoolImportStatus { + if in == nil { + return nil + } + out := new(InferencePoolImportStatus) + in.DeepCopyInto(out) + return out +} diff --git a/apix/v1alpha1/zz_generated.register.go b/apix/v1alpha1/zz_generated.register.go new file mode 100644 index 000000000..4894b76aa --- /dev/null +++ b/apix/v1alpha1/zz_generated.register.go @@ -0,0 +1,70 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by register-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName specifies the group name used to register the objects. +const GroupName = "inference.networking.x-k8s.io" + +// GroupVersion specifies the group and the version used to register the objects. +var GroupVersion = v1.GroupVersion{Group: GroupName, Version: "v1alpha1"} + +// SchemeGroupVersion is group version used to register these objects +// Deprecated: use GroupVersion instead. +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + // Deprecated: use Install instead + AddToScheme = localSchemeBuilder.AddToScheme + Install = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &InferencePoolImport{}, + &InferencePoolImportList{}, + ) + // AddToGroupVersion allows the serialization of client types like ListOptions. + v1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/client-go/applyconfiguration/api/v1/parentstatus.go b/client-go/applyconfiguration/api/v1/parentstatus.go index 5accb0f45..929df5815 100644 --- a/client-go/applyconfiguration/api/v1/parentstatus.go +++ b/client-go/applyconfiguration/api/v1/parentstatus.go @@ -20,13 +20,15 @@ package v1 import ( metav1 "k8s.io/client-go/applyconfigurations/meta/v1" + apiv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" ) // ParentStatusApplyConfiguration represents a declarative configuration of the ParentStatus type for use // with apply. type ParentStatusApplyConfiguration struct { - Conditions []metav1.ConditionApplyConfiguration `json:"conditions,omitempty"` - ParentRef *ParentReferenceApplyConfiguration `json:"parentRef,omitempty"` + Conditions []metav1.ConditionApplyConfiguration `json:"conditions,omitempty"` + ParentRef *ParentReferenceApplyConfiguration `json:"parentRef,omitempty"` + ControllerName *apiv1.ControllerName `json:"controllerName,omitempty"` } // ParentStatusApplyConfiguration constructs a declarative configuration of the ParentStatus type for use with @@ -55,3 +57,11 @@ func (b *ParentStatusApplyConfiguration) WithParentRef(value *ParentReferenceApp b.ParentRef = value return b } + +// WithControllerName sets the ControllerName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ControllerName field is set to the value of the last call. +func (b *ParentStatusApplyConfiguration) WithControllerName(value apiv1.ControllerName) *ParentStatusApplyConfiguration { + b.ControllerName = &value + return b +} diff --git a/client-go/applyconfiguration/apix/v1alpha1/exportingcluster.go b/client-go/applyconfiguration/apix/v1alpha1/exportingcluster.go new file mode 100644 index 000000000..d1b2db058 --- /dev/null +++ b/client-go/applyconfiguration/apix/v1alpha1/exportingcluster.go @@ -0,0 +1,43 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" +) + +// ExportingClusterApplyConfiguration represents a declarative configuration of the ExportingCluster type for use +// with apply. +type ExportingClusterApplyConfiguration struct { + Name *apixv1alpha1.ClusterName `json:"name,omitempty"` +} + +// ExportingClusterApplyConfiguration constructs a declarative configuration of the ExportingCluster type for use with +// apply. +func ExportingCluster() *ExportingClusterApplyConfiguration { + return &ExportingClusterApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ExportingClusterApplyConfiguration) WithName(value apixv1alpha1.ClusterName) *ExportingClusterApplyConfiguration { + b.Name = &value + return b +} diff --git a/client-go/applyconfiguration/apix/v1alpha1/importcontroller.go b/client-go/applyconfiguration/apix/v1alpha1/importcontroller.go new file mode 100644 index 000000000..1c30787ed --- /dev/null +++ b/client-go/applyconfiguration/apix/v1alpha1/importcontroller.go @@ -0,0 +1,87 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" + v1 "sigs.k8s.io/gateway-api-inference-extension/client-go/applyconfiguration/api/v1" +) + +// ImportControllerApplyConfiguration represents a declarative configuration of the ImportController type for use +// with apply. +type ImportControllerApplyConfiguration struct { + Name *apixv1alpha1.ControllerName `json:"name,omitempty"` + ExportingClusters []ExportingClusterApplyConfiguration `json:"exportingClusters,omitempty"` + Parents []v1.ParentStatusApplyConfiguration `json:"parents,omitempty"` + Conditions []metav1.ConditionApplyConfiguration `json:"conditions,omitempty"` +} + +// ImportControllerApplyConfiguration constructs a declarative configuration of the ImportController type for use with +// apply. +func ImportController() *ImportControllerApplyConfiguration { + return &ImportControllerApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ImportControllerApplyConfiguration) WithName(value apixv1alpha1.ControllerName) *ImportControllerApplyConfiguration { + b.Name = &value + return b +} + +// WithExportingClusters adds the given value to the ExportingClusters field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ExportingClusters field. +func (b *ImportControllerApplyConfiguration) WithExportingClusters(values ...*ExportingClusterApplyConfiguration) *ImportControllerApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithExportingClusters") + } + b.ExportingClusters = append(b.ExportingClusters, *values[i]) + } + return b +} + +// WithParents adds the given value to the Parents field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Parents field. +func (b *ImportControllerApplyConfiguration) WithParents(values ...*v1.ParentStatusApplyConfiguration) *ImportControllerApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithParents") + } + b.Parents = append(b.Parents, *values[i]) + } + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *ImportControllerApplyConfiguration) WithConditions(values ...*metav1.ConditionApplyConfiguration) *ImportControllerApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} diff --git a/client-go/applyconfiguration/apix/v1alpha1/inferencepoolimport.go b/client-go/applyconfiguration/apix/v1alpha1/inferencepoolimport.go new file mode 100644 index 000000000..f29c03845 --- /dev/null +++ b/client-go/applyconfiguration/apix/v1alpha1/inferencepoolimport.go @@ -0,0 +1,233 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// InferencePoolImportApplyConfiguration represents a declarative configuration of the InferencePoolImport type for use +// with apply. +type InferencePoolImportApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Status *InferencePoolImportStatusApplyConfiguration `json:"status,omitempty"` +} + +// InferencePoolImport constructs a declarative configuration of the InferencePoolImport type for use with +// apply. +func InferencePoolImport(name, namespace string) *InferencePoolImportApplyConfiguration { + b := &InferencePoolImportApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("InferencePoolImport") + b.WithAPIVersion("inference.networking.x-k8s.io/v1alpha1") + return b +} +func (b InferencePoolImportApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithKind(value string) *InferencePoolImportApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithAPIVersion(value string) *InferencePoolImportApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithName(value string) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithGenerateName(value string) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithNamespace(value string) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithUID(value types.UID) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithResourceVersion(value string) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithGeneration(value int64) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithCreationTimestamp(value metav1.Time) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *InferencePoolImportApplyConfiguration) WithLabels(entries map[string]string) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *InferencePoolImportApplyConfiguration) WithAnnotations(entries map[string]string) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *InferencePoolImportApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *InferencePoolImportApplyConfiguration) WithFinalizers(values ...string) *InferencePoolImportApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *InferencePoolImportApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *InferencePoolImportApplyConfiguration) WithStatus(value *InferencePoolImportStatusApplyConfiguration) *InferencePoolImportApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *InferencePoolImportApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *InferencePoolImportApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *InferencePoolImportApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *InferencePoolImportApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/client-go/applyconfiguration/apix/v1alpha1/inferencepoolimportstatus.go b/client-go/applyconfiguration/apix/v1alpha1/inferencepoolimportstatus.go new file mode 100644 index 000000000..9c2141481 --- /dev/null +++ b/client-go/applyconfiguration/apix/v1alpha1/inferencepoolimportstatus.go @@ -0,0 +1,44 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// InferencePoolImportStatusApplyConfiguration represents a declarative configuration of the InferencePoolImportStatus type for use +// with apply. +type InferencePoolImportStatusApplyConfiguration struct { + Controllers []ImportControllerApplyConfiguration `json:"controllers,omitempty"` +} + +// InferencePoolImportStatusApplyConfiguration constructs a declarative configuration of the InferencePoolImportStatus type for use with +// apply. +func InferencePoolImportStatus() *InferencePoolImportStatusApplyConfiguration { + return &InferencePoolImportStatusApplyConfiguration{} +} + +// WithControllers adds the given value to the Controllers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Controllers field. +func (b *InferencePoolImportStatusApplyConfiguration) WithControllers(values ...*ImportControllerApplyConfiguration) *InferencePoolImportStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithControllers") + } + b.Controllers = append(b.Controllers, *values[i]) + } + return b +} diff --git a/client-go/applyconfiguration/utils.go b/client-go/applyconfiguration/utils.go index 8e11c0082..7e4ea0915 100644 --- a/client-go/applyconfiguration/utils.go +++ b/client-go/applyconfiguration/utils.go @@ -23,8 +23,10 @@ import ( schema "k8s.io/apimachinery/pkg/runtime/schema" managedfields "k8s.io/apimachinery/pkg/util/managedfields" v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" + v1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" v1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2" apiv1 "sigs.k8s.io/gateway-api-inference-extension/client-go/applyconfiguration/api/v1" + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/applyconfiguration/apix/v1alpha1" apixv1alpha2 "sigs.k8s.io/gateway-api-inference-extension/client-go/applyconfiguration/apix/v1alpha2" internal "sigs.k8s.io/gateway-api-inference-extension/client-go/applyconfiguration/internal" ) @@ -51,6 +53,16 @@ func ForKind(kind schema.GroupVersionKind) interface{} { case v1.SchemeGroupVersion.WithKind("Port"): return &apiv1.PortApplyConfiguration{} + // Group=inference.networking.x-k8s.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithKind("ExportingCluster"): + return &apixv1alpha1.ExportingClusterApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ImportController"): + return &apixv1alpha1.ImportControllerApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("InferencePoolImport"): + return &apixv1alpha1.InferencePoolImportApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("InferencePoolImportStatus"): + return &apixv1alpha1.InferencePoolImportStatusApplyConfiguration{} + // Group=inference.networking.x-k8s.io, Version=v1alpha2 case v1alpha2.SchemeGroupVersion.WithKind("Extension"): return &apixv1alpha2.ExtensionApplyConfiguration{} diff --git a/client-go/clientset/versioned/clientset.go b/client-go/clientset/versioned/clientset.go index 928ab89f1..0ea4a00a2 100644 --- a/client-go/clientset/versioned/clientset.go +++ b/client-go/clientset/versioned/clientset.go @@ -26,12 +26,14 @@ import ( rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" inferencev1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/api/v1" + inferencev1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha1" xinferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha2" ) type Interface interface { Discovery() discovery.DiscoveryInterface InferenceV1() inferencev1.InferenceV1Interface + InferenceV1alpha1() inferencev1alpha1.InferenceV1alpha1Interface XInferenceV1alpha2() xinferencev1alpha2.XInferenceV1alpha2Interface } @@ -39,6 +41,7 @@ type Interface interface { type Clientset struct { *discovery.DiscoveryClient inferenceV1 *inferencev1.InferenceV1Client + inferenceV1alpha1 *inferencev1alpha1.InferenceV1alpha1Client xInferenceV1alpha2 *xinferencev1alpha2.XInferenceV1alpha2Client } @@ -47,6 +50,11 @@ func (c *Clientset) InferenceV1() inferencev1.InferenceV1Interface { return c.inferenceV1 } +// InferenceV1alpha1 retrieves the InferenceV1alpha1Client +func (c *Clientset) InferenceV1alpha1() inferencev1alpha1.InferenceV1alpha1Interface { + return c.inferenceV1alpha1 +} + // XInferenceV1alpha2 retrieves the XInferenceV1alpha2Client func (c *Clientset) XInferenceV1alpha2() xinferencev1alpha2.XInferenceV1alpha2Interface { return c.xInferenceV1alpha2 @@ -100,6 +108,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } + cs.inferenceV1alpha1, err = inferencev1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.xInferenceV1alpha2, err = xinferencev1alpha2.NewForConfigAndClient(&configShallowCopy, httpClient) if err != nil { return nil, err @@ -126,6 +138,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { func New(c rest.Interface) *Clientset { var cs Clientset cs.inferenceV1 = inferencev1.New(c) + cs.inferenceV1alpha1 = inferencev1alpha1.New(c) cs.xInferenceV1alpha2 = xinferencev1alpha2.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) diff --git a/client-go/clientset/versioned/fake/clientset_generated.go b/client-go/clientset/versioned/fake/clientset_generated.go index a0709d723..baa42d884 100644 --- a/client-go/clientset/versioned/fake/clientset_generated.go +++ b/client-go/clientset/versioned/fake/clientset_generated.go @@ -29,6 +29,8 @@ import ( clientset "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned" inferencev1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/api/v1" fakeinferencev1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/api/v1/fake" + inferencev1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha1" + fakeinferencev1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha1/fake" xinferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha2" fakexinferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha2/fake" ) @@ -132,6 +134,11 @@ func (c *Clientset) InferenceV1() inferencev1.InferenceV1Interface { return &fakeinferencev1.FakeInferenceV1{Fake: &c.Fake} } +// InferenceV1alpha1 retrieves the InferenceV1alpha1Client +func (c *Clientset) InferenceV1alpha1() inferencev1alpha1.InferenceV1alpha1Interface { + return &fakeinferencev1alpha1.FakeInferenceV1alpha1{Fake: &c.Fake} +} + // XInferenceV1alpha2 retrieves the XInferenceV1alpha2Client func (c *Clientset) XInferenceV1alpha2() xinferencev1alpha2.XInferenceV1alpha2Interface { return &fakexinferencev1alpha2.FakeXInferenceV1alpha2{Fake: &c.Fake} diff --git a/client-go/clientset/versioned/fake/register.go b/client-go/clientset/versioned/fake/register.go index 5c6d338ce..412308d9e 100644 --- a/client-go/clientset/versioned/fake/register.go +++ b/client-go/clientset/versioned/fake/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" inferencev1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" + inferencev1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" xinferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2" ) @@ -33,6 +34,7 @@ var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ inferencev1.AddToScheme, + inferencev1alpha1.AddToScheme, xinferencev1alpha2.AddToScheme, } diff --git a/client-go/clientset/versioned/scheme/register.go b/client-go/clientset/versioned/scheme/register.go index 7836df4f5..47bdc7c33 100644 --- a/client-go/clientset/versioned/scheme/register.go +++ b/client-go/clientset/versioned/scheme/register.go @@ -25,6 +25,7 @@ import ( serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" inferencev1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" + inferencev1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" xinferencev1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2" ) @@ -33,6 +34,7 @@ var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ inferencev1.AddToScheme, + inferencev1alpha1.AddToScheme, xinferencev1alpha2.AddToScheme, } diff --git a/client-go/clientset/versioned/typed/apix/v1alpha1/apix_client.go b/client-go/clientset/versioned/typed/apix/v1alpha1/apix_client.go new file mode 100644 index 000000000..726c4103b --- /dev/null +++ b/client-go/clientset/versioned/typed/apix/v1alpha1/apix_client.go @@ -0,0 +1,101 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + http "net/http" + + rest "k8s.io/client-go/rest" + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" + scheme "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/scheme" +) + +type InferenceV1alpha1Interface interface { + RESTClient() rest.Interface + InferencePoolImportsGetter +} + +// InferenceV1alpha1Client is used to interact with features provided by the inference.networking.x-k8s.io group. +type InferenceV1alpha1Client struct { + restClient rest.Interface +} + +func (c *InferenceV1alpha1Client) InferencePoolImports(namespace string) InferencePoolImportInterface { + return newInferencePoolImports(c, namespace) +} + +// NewForConfig creates a new InferenceV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*InferenceV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new InferenceV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*InferenceV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &InferenceV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new InferenceV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *InferenceV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new InferenceV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *InferenceV1alpha1Client { + return &InferenceV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) { + gv := apixv1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *InferenceV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/client-go/clientset/versioned/typed/apix/v1alpha1/doc.go b/client-go/clientset/versioned/typed/apix/v1alpha1/doc.go new file mode 100644 index 000000000..df51baa4d --- /dev/null +++ b/client-go/clientset/versioned/typed/apix/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/client-go/clientset/versioned/typed/apix/v1alpha1/fake/doc.go b/client-go/clientset/versioned/typed/apix/v1alpha1/fake/doc.go new file mode 100644 index 000000000..16f443990 --- /dev/null +++ b/client-go/clientset/versioned/typed/apix/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_apix_client.go b/client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_apix_client.go new file mode 100644 index 000000000..1de7688eb --- /dev/null +++ b/client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_apix_client.go @@ -0,0 +1,40 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" + v1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha1" +) + +type FakeInferenceV1alpha1 struct { + *testing.Fake +} + +func (c *FakeInferenceV1alpha1) InferencePoolImports(namespace string) v1alpha1.InferencePoolImportInterface { + return newFakeInferencePoolImports(c, namespace) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeInferenceV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_inferencepoolimport.go b/client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_inferencepoolimport.go new file mode 100644 index 000000000..7a6938f9d --- /dev/null +++ b/client-go/clientset/versioned/typed/apix/v1alpha1/fake/fake_inferencepoolimport.go @@ -0,0 +1,53 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + gentype "k8s.io/client-go/gentype" + v1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/applyconfiguration/apix/v1alpha1" + typedapixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/typed/apix/v1alpha1" +) + +// fakeInferencePoolImports implements InferencePoolImportInterface +type fakeInferencePoolImports struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.InferencePoolImport, *v1alpha1.InferencePoolImportList, *apixv1alpha1.InferencePoolImportApplyConfiguration] + Fake *FakeInferenceV1alpha1 +} + +func newFakeInferencePoolImports(fake *FakeInferenceV1alpha1, namespace string) typedapixv1alpha1.InferencePoolImportInterface { + return &fakeInferencePoolImports{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.InferencePoolImport, *v1alpha1.InferencePoolImportList, *apixv1alpha1.InferencePoolImportApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("inferencepoolimports"), + v1alpha1.SchemeGroupVersion.WithKind("InferencePoolImport"), + func() *v1alpha1.InferencePoolImport { return &v1alpha1.InferencePoolImport{} }, + func() *v1alpha1.InferencePoolImportList { return &v1alpha1.InferencePoolImportList{} }, + func(dst, src *v1alpha1.InferencePoolImportList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.InferencePoolImportList) []*v1alpha1.InferencePoolImport { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.InferencePoolImportList, items []*v1alpha1.InferencePoolImport) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/client-go/clientset/versioned/typed/apix/v1alpha1/generated_expansion.go b/client-go/clientset/versioned/typed/apix/v1alpha1/generated_expansion.go new file mode 100644 index 000000000..0969f5b0c --- /dev/null +++ b/client-go/clientset/versioned/typed/apix/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type InferencePoolImportExpansion interface{} diff --git a/client-go/clientset/versioned/typed/apix/v1alpha1/inferencepoolimport.go b/client-go/clientset/versioned/typed/apix/v1alpha1/inferencepoolimport.go new file mode 100644 index 000000000..f68766d9a --- /dev/null +++ b/client-go/clientset/versioned/typed/apix/v1alpha1/inferencepoolimport.go @@ -0,0 +1,74 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" + applyconfigurationapixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/applyconfiguration/apix/v1alpha1" + scheme "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned/scheme" +) + +// InferencePoolImportsGetter has a method to return a InferencePoolImportInterface. +// A group's client should implement this interface. +type InferencePoolImportsGetter interface { + InferencePoolImports(namespace string) InferencePoolImportInterface +} + +// InferencePoolImportInterface has methods to work with InferencePoolImport resources. +type InferencePoolImportInterface interface { + Create(ctx context.Context, inferencePoolImport *apixv1alpha1.InferencePoolImport, opts v1.CreateOptions) (*apixv1alpha1.InferencePoolImport, error) + Update(ctx context.Context, inferencePoolImport *apixv1alpha1.InferencePoolImport, opts v1.UpdateOptions) (*apixv1alpha1.InferencePoolImport, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, inferencePoolImport *apixv1alpha1.InferencePoolImport, opts v1.UpdateOptions) (*apixv1alpha1.InferencePoolImport, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*apixv1alpha1.InferencePoolImport, error) + List(ctx context.Context, opts v1.ListOptions) (*apixv1alpha1.InferencePoolImportList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *apixv1alpha1.InferencePoolImport, err error) + Apply(ctx context.Context, inferencePoolImport *applyconfigurationapixv1alpha1.InferencePoolImportApplyConfiguration, opts v1.ApplyOptions) (result *apixv1alpha1.InferencePoolImport, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, inferencePoolImport *applyconfigurationapixv1alpha1.InferencePoolImportApplyConfiguration, opts v1.ApplyOptions) (result *apixv1alpha1.InferencePoolImport, err error) + InferencePoolImportExpansion +} + +// inferencePoolImports implements InferencePoolImportInterface +type inferencePoolImports struct { + *gentype.ClientWithListAndApply[*apixv1alpha1.InferencePoolImport, *apixv1alpha1.InferencePoolImportList, *applyconfigurationapixv1alpha1.InferencePoolImportApplyConfiguration] +} + +// newInferencePoolImports returns a InferencePoolImports +func newInferencePoolImports(c *InferenceV1alpha1Client, namespace string) *inferencePoolImports { + return &inferencePoolImports{ + gentype.NewClientWithListAndApply[*apixv1alpha1.InferencePoolImport, *apixv1alpha1.InferencePoolImportList, *applyconfigurationapixv1alpha1.InferencePoolImportApplyConfiguration]( + "inferencepoolimports", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *apixv1alpha1.InferencePoolImport { return &apixv1alpha1.InferencePoolImport{} }, + func() *apixv1alpha1.InferencePoolImportList { return &apixv1alpha1.InferencePoolImportList{} }, + ), + } +} diff --git a/client-go/informers/externalversions/apix/interface.go b/client-go/informers/externalversions/apix/interface.go index 26e18173f..426cbcdd9 100644 --- a/client-go/informers/externalversions/apix/interface.go +++ b/client-go/informers/externalversions/apix/interface.go @@ -19,12 +19,15 @@ limitations under the License. package apix import ( + v1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/informers/externalversions/apix/v1alpha1" v1alpha2 "sigs.k8s.io/gateway-api-inference-extension/client-go/informers/externalversions/apix/v1alpha2" internalinterfaces "sigs.k8s.io/gateway-api-inference-extension/client-go/informers/externalversions/internalinterfaces" ) // Interface provides access to each of this group's versions. type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface // V1alpha2 provides access to shared informers for resources in V1alpha2. V1alpha2() v1alpha2.Interface } @@ -40,6 +43,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} + // V1alpha2 returns a new v1alpha2.Interface. func (g *group) V1alpha2() v1alpha2.Interface { return v1alpha2.New(g.factory, g.namespace, g.tweakListOptions) diff --git a/client-go/informers/externalversions/apix/v1alpha1/inferencepoolimport.go b/client-go/informers/externalversions/apix/v1alpha1/inferencepoolimport.go new file mode 100644 index 000000000..9013e02a5 --- /dev/null +++ b/client-go/informers/externalversions/apix/v1alpha1/inferencepoolimport.go @@ -0,0 +1,102 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + gatewayapiinferenceextensionapixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" + versioned "sigs.k8s.io/gateway-api-inference-extension/client-go/clientset/versioned" + internalinterfaces "sigs.k8s.io/gateway-api-inference-extension/client-go/informers/externalversions/internalinterfaces" + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/client-go/listers/apix/v1alpha1" +) + +// InferencePoolImportInformer provides access to a shared informer and lister for +// InferencePoolImports. +type InferencePoolImportInformer interface { + Informer() cache.SharedIndexInformer + Lister() apixv1alpha1.InferencePoolImportLister +} + +type inferencePoolImportInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewInferencePoolImportInformer constructs a new informer for InferencePoolImport type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewInferencePoolImportInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredInferencePoolImportInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredInferencePoolImportInformer constructs a new informer for InferencePoolImport type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredInferencePoolImportInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.InferenceV1alpha1().InferencePoolImports(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.InferenceV1alpha1().InferencePoolImports(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.InferenceV1alpha1().InferencePoolImports(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.InferenceV1alpha1().InferencePoolImports(namespace).Watch(ctx, options) + }, + }, + &gatewayapiinferenceextensionapixv1alpha1.InferencePoolImport{}, + resyncPeriod, + indexers, + ) +} + +func (f *inferencePoolImportInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredInferencePoolImportInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *inferencePoolImportInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&gatewayapiinferenceextensionapixv1alpha1.InferencePoolImport{}, f.defaultInformer) +} + +func (f *inferencePoolImportInformer) Lister() apixv1alpha1.InferencePoolImportLister { + return apixv1alpha1.NewInferencePoolImportLister(f.Informer().GetIndexer()) +} diff --git a/client-go/informers/externalversions/apix/v1alpha1/interface.go b/client-go/informers/externalversions/apix/v1alpha1/interface.go new file mode 100644 index 000000000..50e84d5b1 --- /dev/null +++ b/client-go/informers/externalversions/apix/v1alpha1/interface.go @@ -0,0 +1,45 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "sigs.k8s.io/gateway-api-inference-extension/client-go/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // InferencePoolImports returns a InferencePoolImportInformer. + InferencePoolImports() InferencePoolImportInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// InferencePoolImports returns a InferencePoolImportInformer. +func (v *version) InferencePoolImports() InferencePoolImportInformer { + return &inferencePoolImportInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/client-go/informers/externalversions/generic.go b/client-go/informers/externalversions/generic.go index 03c95ee05..2fe29156b 100644 --- a/client-go/informers/externalversions/generic.go +++ b/client-go/informers/externalversions/generic.go @@ -24,6 +24,7 @@ import ( schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" + v1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" v1alpha2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2" ) @@ -57,6 +58,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v1.SchemeGroupVersion.WithResource("inferencepools"): return &genericInformer{resource: resource.GroupResource(), informer: f.Inference().V1().InferencePools().Informer()}, nil + // Group=inference.networking.x-k8s.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("inferencepoolimports"): + return &genericInformer{resource: resource.GroupResource(), informer: f.XInference().V1alpha1().InferencePoolImports().Informer()}, nil + // Group=inference.networking.x-k8s.io, Version=v1alpha2 case v1alpha2.SchemeGroupVersion.WithResource("inferenceobjectives"): return &genericInformer{resource: resource.GroupResource(), informer: f.XInference().V1alpha2().InferenceObjectives().Informer()}, nil diff --git a/client-go/listers/apix/v1alpha1/expansion_generated.go b/client-go/listers/apix/v1alpha1/expansion_generated.go new file mode 100644 index 000000000..7c0d32cbb --- /dev/null +++ b/client-go/listers/apix/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +// InferencePoolImportListerExpansion allows custom methods to be added to +// InferencePoolImportLister. +type InferencePoolImportListerExpansion interface{} + +// InferencePoolImportNamespaceListerExpansion allows custom methods to be added to +// InferencePoolImportNamespaceLister. +type InferencePoolImportNamespaceListerExpansion interface{} diff --git a/client-go/listers/apix/v1alpha1/inferencepoolimport.go b/client-go/listers/apix/v1alpha1/inferencepoolimport.go new file mode 100644 index 000000000..653bbfa45 --- /dev/null +++ b/client-go/listers/apix/v1alpha1/inferencepoolimport.go @@ -0,0 +1,70 @@ +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" + apixv1alpha1 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1" +) + +// InferencePoolImportLister helps list InferencePoolImports. +// All objects returned here must be treated as read-only. +type InferencePoolImportLister interface { + // List lists all InferencePoolImports in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apixv1alpha1.InferencePoolImport, err error) + // InferencePoolImports returns an object that can list and get InferencePoolImports. + InferencePoolImports(namespace string) InferencePoolImportNamespaceLister + InferencePoolImportListerExpansion +} + +// inferencePoolImportLister implements the InferencePoolImportLister interface. +type inferencePoolImportLister struct { + listers.ResourceIndexer[*apixv1alpha1.InferencePoolImport] +} + +// NewInferencePoolImportLister returns a new InferencePoolImportLister. +func NewInferencePoolImportLister(indexer cache.Indexer) InferencePoolImportLister { + return &inferencePoolImportLister{listers.New[*apixv1alpha1.InferencePoolImport](indexer, apixv1alpha1.Resource("inferencepoolimport"))} +} + +// InferencePoolImports returns an object that can list and get InferencePoolImports. +func (s *inferencePoolImportLister) InferencePoolImports(namespace string) InferencePoolImportNamespaceLister { + return inferencePoolImportNamespaceLister{listers.NewNamespaced[*apixv1alpha1.InferencePoolImport](s.ResourceIndexer, namespace)} +} + +// InferencePoolImportNamespaceLister helps list and get InferencePoolImports. +// All objects returned here must be treated as read-only. +type InferencePoolImportNamespaceLister interface { + // List lists all InferencePoolImports in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*apixv1alpha1.InferencePoolImport, err error) + // Get retrieves the InferencePoolImport from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*apixv1alpha1.InferencePoolImport, error) + InferencePoolImportNamespaceListerExpansion +} + +// inferencePoolImportNamespaceLister implements the InferencePoolImportNamespaceLister +// interface. +type inferencePoolImportNamespaceLister struct { + listers.ResourceIndexer[*apixv1alpha1.InferencePoolImport] +} diff --git a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml index 2ccaf2306..26ac568ea 100644 --- a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml +++ b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml @@ -271,6 +271,25 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the controller that + wrote this status. This corresponds with the GatewayClass controllerName field when the + parentRef references a Gateway kind. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes names: + + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + + Controllers MAY populate this field when writing status. When populating this field, controllers + should ensure that entries to status populated with their ControllerName are cleaned up when they + are no longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string parentRef: description: |- ParentRef is used to identify the parent resource that this status diff --git a/config/crd/bases/inference.networking.x-k8s.io_inferencepoolimports.yaml b/config/crd/bases/inference.networking.x-k8s.io_inferencepoolimports.yaml new file mode 100644 index 000000000..318e6d46f --- /dev/null +++ b/config/crd/bases/inference.networking.x-k8s.io_inferencepoolimports.yaml @@ -0,0 +1,330 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + inference.networking.k8s.io/bundle-version: main-dev + name: inferencepoolimports.inference.networking.x-k8s.io +spec: + group: inference.networking.x-k8s.io + names: + kind: InferencePoolImport + listKind: InferencePoolImportList + plural: inferencepoolimports + shortNames: + - infpimp + singular: inferencepoolimport + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: InferencePoolImport is the Schema for the InferencePoolImports + API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + status: + description: Status defines the observed state of the InferencePoolImport. + properties: + controllers: + description: Controllers is a list of controllers that are responsible + for managing the InferencePoolImport. + items: + description: ImportController defines a controller that is responsible + for managing the InferencePoolImport. + properties: + conditions: + description: |- + Conditions track the state of the InferencePoolImport. + + Known condition types are: + + * "Accepted" + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + exportingClusters: + description: |- + ExportingClusters is a list of clusters that exported the InferencePool(s) that back the + InferencePoolImport. Required when the controller is responsible for CRUD'ing the InferencePoolImport + from the exported InferencePool(s). + items: + description: ExportingCluster defines a cluster that exported + the InferencePool that backs this InferencePoolImport. + properties: + name: + description: Name of the exporting cluster (must be unique + within the list). + maxLength: 253 + minLength: 1 + type: string + required: + - name + type: object + type: array + name: + description: |- + Name is a domain/path string that indicates the name of the controller that manages the + InferencePoolImport. Name corresponds to the GatewayClass controllerName field when the + controller will manage parents of type "Gateway". Otherwise, the name is implementation-specific. + + Example: "example.net/import-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes + names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + A controller MUST populate this field when writing status and ensure that entries to status + populated with their controller name are removed when they are no longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parents: + description: |- + Parents is a list of parent resources, typically Gateways, that are associated with the + InferencePoolImport, and the status of the InferencePoolImport with respect to each parent. + + Ancestor would be a more accurate name, but Parent is consistent with InferencePool terminology. + + Required when the controller manages the InferencePoolImport as an HTTPRoute backendRef. The controller + must add an entry for each parent it manages and remove the parent entry when the controller no longer + considers the InferencePoolImport to be associated with that parent. + items: + description: ParentStatus defines the observed state of InferencePool + from a Parent, i.e. Gateway. + properties: + conditions: + description: |- + Conditions is a list of status conditions that provide information about the observed + state of the InferencePool. This field is required to be set by the controller that + manages the InferencePool. + + Supported condition types are: + + * "Accepted" + * "ResolvedRefs" + items: + description: Condition contains details for one aspect + of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, + False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in + foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the controller that + wrote this status. This corresponds with the GatewayClass controllerName field when the + parentRef references a Gateway kind. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes names: + + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + + Controllers MAY populate this field when writing status. When populating this field, controllers + should ensure that entries to status populated with their ControllerName are cleaned up when they + are no longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef is used to identify the parent resource that this status + is associated with. It is used to match the InferencePool with the parent + resource, such as a Gateway. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent API object. When unspecified, the referent is assumed + to be in the "gateway.networking.k8s.io" API group. + maxLength: 253 + minLength: 0 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is the kind of the referent API object. When unspecified, the referent is assumed + to be a "Gateway" kind. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent API + object. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details: https://gateway-api.sigs.k8s.io/api-types/referencegrant/ + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - parentRef + type: object + type: array + x-kubernetes-list-type: atomic + required: + - name + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + required: + - controllers + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/crd-ref-docs.yaml b/crd-ref-docs.yaml index d00e6d09b..5fefbffd6 100644 --- a/crd-ref-docs.yaml +++ b/crd-ref-docs.yaml @@ -4,7 +4,7 @@ processor: ignoreTypes: - - "(InferencePool|InferenceObjective)List$" + - "(InferencePool|InferenceObjective|InferencePoolImport)List$" # RE2 regular expressions describing type fields that should be excluded from the generated documentation. ignoreFields: - "TypeMeta$" diff --git a/mkdocs.yml b/mkdocs.yml index b746f743d..4c285c2e8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,11 +82,15 @@ nav: - Benchmark: performance/benchmark/index.md - Regression Testing: performance/regression-testing/index.md - Reference: - - API Reference: reference/spec.md - - Alpha API Reference: reference/x-spec.md + - v1 API Reference: reference/spec.md + - v1alpha1 API Reference: + - reference/x-v1a1-spec.md + - v1alpha2 API Reference: + - reference/x-v1a2-spec.md - API Types: - InferencePool: api-types/inferencepool.md - InferenceObjective: api-types/inferenceobjective.md + - InferencePoolImport: api-types/inferencepoolimport.md - Enhancements: - Overview: enhancements/overview.md - Contributing: diff --git a/pkg/generator/main.go b/pkg/generator/main.go index 3cb70a508..c663588a9 100644 --- a/pkg/generator/main.go +++ b/pkg/generator/main.go @@ -35,6 +35,7 @@ import ( func main() { roots, err := loader.LoadRoots( "k8s.io/apimachinery/pkg/runtime/schema", // Needed to parse generated register functions. + "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha1", "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2", "sigs.k8s.io/gateway-api-inference-extension/api/v1", ) diff --git a/site-src/api-types/inferenceobjective.md b/site-src/api-types/inferenceobjective.md index 4ce6a7ccd..8c48651d6 100644 --- a/site-src/api-types/inferenceobjective.md +++ b/site-src/api-types/inferenceobjective.md @@ -15,4 +15,4 @@ To associate a request to the InferencePool with a specific InferenceObjective, ## Spec -The full spec of the InferenceObjective is defined [here](/reference/x-spec/#inferenceobjective). \ No newline at end of file +The full spec of the InferenceObjective is defined [here](/reference/x-v1-a2-spec/#inferenceobjective). diff --git a/site-src/api-types/inferencepoolimport.md b/site-src/api-types/inferencepoolimport.md new file mode 100644 index 000000000..aa58247b2 --- /dev/null +++ b/site-src/api-types/inferencepoolimport.md @@ -0,0 +1,26 @@ +# Inference Pool Import + +??? example "Alpha since v1.1.0" + + The `InferencePoolImport` resource is alpha and may have breaking changes in + future releases of the API. + +## Background + +The **InferencePoolImport** API is a cluster-local, controller-managed resource that represents an imported InferencePool. +It primarily communicates a relationship between an exported InferencePool and the exporting cluster name. It is not +user-authored; status carries the effective import. Inference Platform Owners can reference the InferencePoolImport, +even if the local cluster does not have an InferencePool. In the context of Gateway API, it means that an HTTPRoute can +be configured to reference an InferencePoolImport to route matching requests to endpoints of backing InferencePools. + +Key ideas: + +- Map an exported InferencePool to exporting controller and cluster. +- Name/namespace sameness with the exported InferencePool (avoids extra indirection). +- Conditions: Surface a controller-level status condition to indicate whether the InferencePoolImport is ready for use. +- Conditions: Surface parent-level status conditions to indicate whether the InferencePoolImport is referenced by a parent, + e.g. Gateway. + +## Spec + +The full spec of the InferencePoolImport is defined [here](/reference/x-v1a1-spec/#inferencepoolimport). diff --git a/site-src/reference/spec.md b/site-src/reference/spec.md index 11080fe24..666ebe36b 100644 --- a/site-src/reference/spec.md +++ b/site-src/reference/spec.md @@ -15,6 +15,32 @@ inference.networking.k8s.io API group. +#### ControllerName + +_Underlying type:_ _string_ + +ControllerName is the name of a controller that manages ParentStatus. It must be a domain prefixed +path. + +Valid values include: + +* "example.com/bar" + +Invalid values include: + +* "example.com" - must include path +* "foo.example.com" - must include path + +_Validation:_ +- MaxLength: 253 +- MinLength: 1 +- Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$` + +_Appears in:_ +- [ParentStatus](#parentstatus) + + + #### EndpointPickerFailureMode _Underlying type:_ _string_ @@ -124,7 +150,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | | `selector` _[LabelSelector](#labelselector)_ | Selector determines which Pods are members of this inference pool.
It matches Pods by their labels only within the same namespace; cross-namespace
selection is not supported.
The structure of this LabelSelector is intentionally simple to be compatible
with Kubernetes Service selectors, as some implementations may translate
this configuration into a Service resource. | | | -| `targetPorts` _[Port](#port) array_ | TargetPorts defines a list of ports that are exposed by this InferencePool.
Currently, the list may only include a single port definition. | | MaxItems: 1
MinItems: 1
| +| `targetPorts` _[Port](#port) array_ | TargetPorts defines a list of ports that are exposed by this InferencePool.
Every port will be treated as a distinctive endpoint by EPP,
addressable as a 'podIP:portNumber' combination. | | MaxItems: 8
MinItems: 1
| | `endpointPickerRef` _[EndpointPickerRef](#endpointpickerref)_ | EndpointPickerRef is a reference to the Endpoint Picker extension and its
associated configuration. | | | @@ -329,6 +355,7 @@ _Appears in:_ | --- | --- | --- | --- | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions is a list of status conditions that provide information about the observed
state of the InferencePool. This field is required to be set by the controller that
manages the InferencePool.
Supported condition types are:
* "Accepted"
* "ResolvedRefs" | | MaxItems: 8
| | `parentRef` _[ParentReference](#parentreference)_ | ParentRef is used to identify the parent resource that this status
is associated with. It is used to match the InferencePool with the parent
resource, such as a Gateway. | | | +| `controllerName` _[ControllerName](#controllername)_ | ControllerName is a domain/path string that indicates the name of the controller that
wrote this status. This corresponds with the GatewayClass controllerName field when the
parentRef references a Gateway kind.
Example: "example.net/gateway-controller".
The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes names:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
Controllers MAY populate this field when writing status. When populating this field, controllers
should ensure that entries to status populated with their ControllerName are cleaned up when they
are no longer necessary. | | MaxLength: 253
MinLength: 1
Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$`
| #### Port diff --git a/site-src/reference/x-v1a1-spec.md b/site-src/reference/x-v1a1-spec.md new file mode 100644 index 000000000..55bec76e0 --- /dev/null +++ b/site-src/reference/x-v1a1-spec.md @@ -0,0 +1,126 @@ +# API Reference + +## Packages +- [inference.networking.x-k8s.io/v1alpha1](#inferencenetworkingx-k8siov1alpha1) + + +## inference.networking.x-k8s.io/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the +inference.networking.x-k8s.io API group. + + +### Resource Types +- [InferencePoolImport](#inferencepoolimport) + + + +#### ClusterName + +_Underlying type:_ _string_ + +ClusterName is the name of a cluster that exported the InferencePool. + +_Validation:_ +- MaxLength: 253 +- MinLength: 1 + +_Appears in:_ +- [ExportingCluster](#exportingcluster) + + + +#### ControllerName + +_Underlying type:_ _string_ + +ControllerName is the name of a controller that manages a resource. It must be a domain prefixed path. + +Valid values include: + + - "example.com/bar" + +Invalid values include: + + - "example.com" - must include path + - "foo.example.com" - must include path + +_Validation:_ +- MaxLength: 253 +- MinLength: 1 +- Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$` + +_Appears in:_ +- [ImportController](#importcontroller) + + + +#### ExportingCluster + + + +ExportingCluster defines a cluster that exported the InferencePool that backs this InferencePoolImport. + + + +_Appears in:_ +- [ImportController](#importcontroller) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[ClusterName](#clustername)_ | Name of the exporting cluster (must be unique within the list). | | MaxLength: 253
MinLength: 1
Required: \{\}
| + + +#### ImportController + + + +ImportController defines a controller that is responsible for managing the InferencePoolImport. + + + +_Appears in:_ +- [InferencePoolImportStatus](#inferencepoolimportstatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `name` _[ControllerName](#controllername)_ | Name is a domain/path string that indicates the name of the controller that manages the
InferencePoolImport. Name corresponds to the GatewayClass controllerName field when the
controller will manage parents of type "Gateway". Otherwise, the name is implementation-specific.
Example: "example.net/import-controller".
The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are valid Kubernetes
names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).
A controller MUST populate this field when writing status and ensure that entries to status
populated with their controller name are removed when they are no longer necessary. | | MaxLength: 253
MinLength: 1
Pattern: `^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$`
| +| `exportingClusters` _[ExportingCluster](#exportingcluster) array_ | ExportingClusters is a list of clusters that exported the InferencePool(s) that back the
InferencePoolImport. Required when the controller is responsible for CRUD'ing the InferencePoolImport
from the exported InferencePool(s). | | | +| `parents` _ParentStatus array_ | Parents is a list of parent resources, typically Gateways, that are associated with the
InferencePoolImport, and the status of the InferencePoolImport with respect to each parent.
Ancestor would be a more accurate name, but Parent is consistent with InferencePool terminology.
Required when the controller manages the InferencePoolImport as an HTTPRoute backendRef. The controller
must add an entry for each parent it manages and remove the parent entry when the controller no longer
considers the InferencePoolImport to be associated with that parent. | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | Conditions track the state of the InferencePoolImport.
Known condition types are:
* "Accepted" | | MaxItems: 8
| + + +#### InferencePoolImport + + + +InferencePoolImport is the Schema for the InferencePoolImports API. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `inference.networking.x-k8s.io/v1alpha1` | | | +| `kind` _string_ | `InferencePoolImport` | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `status` _[InferencePoolImportStatus](#inferencepoolimportstatus)_ | Status defines the observed state of the InferencePoolImport. | | | + + +#### InferencePoolImportStatus + + + +InferencePoolImportStatus defines the observed state of the InferencePoolImport. + + + +_Appears in:_ +- [InferencePoolImport](#inferencepoolimport) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `controllers` _[ImportController](#importcontroller) array_ | Controllers is a list of controllers that are responsible for managing the InferencePoolImport. | | MaxItems: 8
Required: \{\}
| + + diff --git a/site-src/reference/x-spec.md b/site-src/reference/x-v1a2-spec.md similarity index 100% rename from site-src/reference/x-spec.md rename to site-src/reference/x-v1a2-spec.md From 0b59b90129986029c3a464118ed3b8ddeb3c7d35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:48:59 -0700 Subject: [PATCH 080/133] chore(deps): bump google.golang.org/protobuf from 1.36.9 to 1.36.10 (#1684) Bumps google.golang.org/protobuf from 1.36.9 to 1.36.10. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1f9a97e4a..509ae1abd 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/sync v0.17.0 google.golang.org/grpc v1.75.1 - google.golang.org/protobuf v1.36.9 + google.golang.org/protobuf v1.36.10 k8s.io/api v0.34.1 k8s.io/apiextensions-apiserver v0.34.1 k8s.io/apimachinery v0.34.1 diff --git a/go.sum b/go.sum index 6586a872b..d1980f78c 100644 --- a/go.sum +++ b/go.sum @@ -345,8 +345,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 46c307a5983c282c46582dac2708ba413fd87762 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 22:49:06 -0700 Subject: [PATCH 081/133] chore(deps): bump github.com/envoyproxy/go-control-plane/envoy (#1686) Bumps [github.com/envoyproxy/go-control-plane/envoy](https://github.com/envoyproxy/go-control-plane) from 1.32.4 to 1.35.0. - [Release notes](https://github.com/envoyproxy/go-control-plane/releases) - [Changelog](https://github.com/envoyproxy/go-control-plane/blob/main/CHANGELOG.md) - [Commits](https://github.com/envoyproxy/go-control-plane/compare/envoy/v1.32.4...envoy/v1.35.0) --- updated-dependencies: - dependency-name: github.com/envoyproxy/go-control-plane/envoy dependency-version: 1.35.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 509ae1abd..c4fb67d70 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 require ( github.com/cespare/xxhash/v2 v2.3.0 github.com/elastic/crd-ref-docs v0.2.0 - github.com/envoyproxy/go-control-plane/envoy v1.32.4 + github.com/envoyproxy/go-control-plane/envoy v1.35.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/zapr v1.3.0 github.com/google/go-cmp v0.7.0 @@ -101,7 +101,7 @@ require ( go.opentelemetry.io/otel/metric v1.37.0 // indirect go.opentelemetry.io/otel/sdk v1.37.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect - go.opentelemetry.io/proto/otlp v1.6.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/go.sum b/go.sum index d1980f78c..08997bddb 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/elastic/crd-ref-docs v0.2.0 h1:U17MyGX71j4qfKTvYxbR4qZGoA1hc2thy7kseG github.com/elastic/crd-ref-docs v0.2.0/go.mod h1:0bklkJhTG7nC6AVsdDi0wt5bGoqvzdZSzMMQkilZ6XM= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= +github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= @@ -266,8 +266,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= -go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= From 32cacba03744c96bb4ee36bdc652a14c8dca8959 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 00:26:59 -0700 Subject: [PATCH 082/133] chore(deps): bump google.golang.org/grpc from 1.75.1 to 1.76.0 (#1685) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.1 to 1.76.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.75.1...v1.76.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.76.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index c4fb67d70..8e8dd336d 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.17.0 - google.golang.org/grpc v1.75.1 + google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 k8s.io/api v0.34.1 k8s.io/apiextensions-apiserver v0.34.1 @@ -118,8 +118,8 @@ require ( golang.org/x/tools v0.37.0 // indirect golang.org/x/tools/go/expect v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 08997bddb..d22adbb02 100644 --- a/go.sum +++ b/go.sum @@ -339,12 +339,12 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 1139562d5f7c6f625c69c20a9c3162ddb16fa1d5 Mon Sep 17 00:00:00 2001 From: Shmuel Kallner Date: Tue, 7 Oct 2025 20:15:01 +0300 Subject: [PATCH 083/133] Use actual platform architecture when building images (#1681) Signed-off-by: Shmuel Kallner --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b002538e0..33a187b97 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,8 @@ SHELL = /usr/bin/env bash -o pipefail GIT_COMMIT_SHA ?= "$(shell git rev-parse HEAD 2>/dev/null)" GIT_TAG ?= $(shell git describe --tags --dirty --always) -PLATFORMS ?= linux/amd64 +TARGETARCH ?= $(shell go env GOARCH) +PLATFORMS ?= linux/$(TARGETARCH) DOCKER_BUILDX_CMD ?= docker buildx IMAGE_BUILD_CMD ?= $(DOCKER_BUILDX_CMD) build IMAGE_BUILD_EXTRA_OPTS ?= From 0db1585e4615a3732a04bf74f8e9825e255ee65f Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Thu, 9 Oct 2025 16:37:02 +0300 Subject: [PATCH 084/133] chore: bump controller-tools version in Makefile (#1694) Signed-off-by: Nir Rozenbaum --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 33a187b97..d9561707b 100644 --- a/Makefile +++ b/Makefile @@ -396,7 +396,7 @@ GCI = $(LOCALBIN)/gci ## Tool Versions KUSTOMIZE_VERSION ?= v5.4.3 -CONTROLLER_TOOLS_VERSION ?= v0.17.0 +CONTROLLER_TOOLS_VERSION ?= v0.19.0 ENVTEST_VERSION ?= release-0.19 CRD_REF_DOCS_VERSION ?= v0.2.0 GOLANGCI_LINT_VERSION ?= v2.3.0 From 477ebbbbc2af358be1039770bfbbef6ec7d16277 Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Thu, 9 Oct 2025 12:35:00 -0700 Subject: [PATCH 085/133] Dep: Bumps Gateway API to v1.4.0 (#1691) * Bumps Gateway API Dep to v1.4.0 Signed-off-by: Daneyon Hansen * Updates deprecated funcs Signed-off-by: Daneyon Hansen * Bumps controller-tools to match go.mod Signed-off-by: Daneyon Hansen * Ignores type comp in EPP reconciler tests Signed-off-by: Daneyon Hansen --------- Signed-off-by: Daneyon Hansen --- .../tests/gateway_following_epp_routing.go | 2 +- conformance/utils/traffic/traffic.go | 58 ++++++++++++++----- go.mod | 30 +++++----- go.sum | 52 ++++++++--------- .../inferencepool_reconciler_test.go | 10 +++- 5 files changed, 95 insertions(+), 57 deletions(-) diff --git a/conformance/tests/gateway_following_epp_routing.go b/conformance/tests/gateway_following_epp_routing.go index c320ad35e..eaa618141 100644 --- a/conformance/tests/gateway_following_epp_routing.go +++ b/conformance/tests/gateway_following_epp_routing.go @@ -174,7 +174,7 @@ func assertTrafficOnlyReachesToExpectedPods(t *testing.T, suite *suite.Conforman if err != nil { return fmt.Errorf("failed to roundtrip request: %w", err) } - if err := gwhttp.CompareRequest(t, &req, cReq, cRes, expected); err != nil { + if err := gwhttp.CompareRoundTrip(t, &req, cReq, cRes, expected); err != nil { return fmt.Errorf("response expectation failed for request: %w", err) } diff --git a/conformance/utils/traffic/traffic.go b/conformance/utils/traffic/traffic.go index f53cc3236..8acaefad9 100644 --- a/conformance/utils/traffic/traffic.go +++ b/conformance/utils/traffic/traffic.go @@ -169,24 +169,56 @@ func waitForConvergeToExpected( tlog.Logf(t, "Request passed") } -// CompareRequestWithWildcardStatus compares requests with wildcard status code support. -// It treats a single-digit expected code (e.g., 4) as a class wildcard (4xx), -// while standard 3-digit codes are matched exactly. -func CompareRequestWithWildcardStatus(t *testing.T, req *roundtripper.Request, cReq *roundtripper.CapturedRequest, cRes *roundtripper.CapturedResponse, expected gwhttp.ExpectedResponse) error { - if expected.Response.StatusCode < 1 || expected.Response.StatusCode >= 100 { - return gwhttp.CompareRequest(t, req, cReq, cRes, expected) +// CompareRequestWithWildcardStatus compares requests allowing single-digit +// status "classes" (e.g. 5 => any 5xx) via ExpectedResponse.Response.StatusCodes. +// If no class wildcards are present, it defers to CompareRoundTrip. +func CompareRequestWithWildcardStatus( + t *testing.T, + req *roundtripper.Request, + cReq *roundtripper.CapturedRequest, + cRes *roundtripper.CapturedResponse, + expected gwhttp.ExpectedResponse, +) error { + // Separate specific status codes (>=100) from wildcard classes (1..9). + var wildcardClasses []int + var specificCodes []int + seenClass := map[int]struct{}{} + seenCode := map[int]struct{}{} + + for _, sc := range expected.Response.StatusCodes { + switch { + case sc >= 100: + if _, ok := seenCode[sc]; !ok { + specificCodes = append(specificCodes, sc) + seenCode[sc] = struct{}{} + } + case sc >= 1 && sc <= 9: + if _, ok := seenClass[sc]; !ok { + wildcardClasses = append(wildcardClasses, sc) + seenClass[sc] = struct{}{} + } + } } - expectedClass := expected.Response.StatusCode + // No wildcard classes? Defer to standard comparator. + if len(wildcardClasses) == 0 { + return gwhttp.CompareRoundTrip(t, req, cReq, cRes, expected) + } + + // If the concrete status matches any wildcard class, materialize it and compare. actualClass := cRes.StatusCode / 100 - if expectedClass != actualClass { - return fmt.Errorf("expected status code class %dxx, but got %d", expectedClass, cRes.StatusCode) + for _, wc := range wildcardClasses { + if actualClass == wc { + modified := expected + // Keep any specific codes that were provided, but ensure the actual status is allowed. + modified.Response.StatusCodes = append(append([]int(nil), specificCodes...), cRes.StatusCode) + return gwhttp.CompareRoundTrip(t, req, cReq, cRes, modified) + } } - // StatusCode Class matches; update status code on a copy to allow the standard comparator to pass. - modifiedExpected := expected - modifiedExpected.Response.StatusCode = cRes.StatusCode - return gwhttp.CompareRequest(t, req, cReq, cRes, modifiedExpected) + // Wildcards were provided but none matched the actual class—fail clearly. + return fmt.Errorf("expected status code class to be one of %vxx, got %d", + wildcardClasses, cRes.StatusCode) } // TODO: https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/1031 diff --git a/go.mod b/go.mod index 8e8dd336d..6d339f6c4 100644 --- a/go.mod +++ b/go.mod @@ -29,10 +29,11 @@ require ( k8s.io/client-go v0.34.1 k8s.io/code-generator v0.34.1 k8s.io/component-base v0.34.1 - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 - sigs.k8s.io/controller-runtime v0.21.0 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/controller-runtime v0.22.1 + // Update the CONTROLLER_TOOLS_VERSION in Makefile when bumping controller-tools. sigs.k8s.io/controller-tools v0.19.0 - sigs.k8s.io/gateway-api v1.3.0 + sigs.k8s.io/gateway-api v1.4.0 sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.6.0 ) @@ -50,16 +51,17 @@ require ( github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.2 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/goccy/go-yaml v1.18.0 // indirect @@ -77,7 +79,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/spdystream v0.5.0 // indirect @@ -85,10 +87,9 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.7 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect @@ -116,19 +117,18 @@ require ( golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.37.0 // indirect - golang.org/x/tools/go/expect v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.34.1 // indirect - k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect + k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect + k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index d22adbb02..81d12272f 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= github.com/elastic/crd-ref-docs v0.2.0 h1:U17MyGX71j4qfKTvYxbR4qZGoA1hc2thy7kseGYmP+o= github.com/elastic/crd-ref-docs v0.2.0/go.mod h1:0bklkJhTG7nC6AVsdDi0wt5bGoqvzdZSzMMQkilZ6XM= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= @@ -98,12 +98,12 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA= +github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= @@ -169,8 +169,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -217,8 +217,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/prometheus/prometheus v0.306.0 h1:Q0Pvz/ZKS6vVWCa1VSgNyNJlEe8hxdRlKklFg7SRhNw= github.com/prometheus/prometheus v0.306.0/go.mod h1:7hMSGyZHt0dcmZ5r4kFPJ/vxPQU99N5/BGwSPDxeZrQ= github.com/prometheus/sigv4 v0.2.0 h1:qDFKnHYFswJxdzGeRP63c4HlH3Vbn1Yf/Ao2zabtVXk= @@ -341,8 +341,8 @@ google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= @@ -350,8 +350,8 @@ google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -375,24 +375,24 @@ k8s.io/code-generator v0.34.1 h1:WpphT26E+j7tEgIUfFr5WfbJrktCGzB3JoJH9149xYc= k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608yqvg= k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= -k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= -k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d h1:qUrYOinhdAUL0xxhA4gPqogPBaS9nIq2l2kTb6pmeB0= +k8s.io/gengo/v2 v2.0.0-20250820003526-c297c0c1eb9d/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3 h1:liMHz39T5dJO1aOKHLvwaCjDbf07wVh6yaUlTpunnkE= +k8s.io/kube-openapi v0.0.0-20250814151709-d7b6acb124c3/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= -sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= +sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= +sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= sigs.k8s.io/controller-tools v0.19.0 h1:OU7jrPPiZusryu6YK0jYSjPqg8Vhf8cAzluP9XGI5uk= sigs.k8s.io/controller-tools v0.19.0/go.mod h1:y5HY/iNDFkmFla2CfQoVb2AQXMsBk4ad84iR1PLANB0= -sigs.k8s.io/gateway-api v1.3.0 h1:q6okN+/UKDATola4JY7zXzx40WO4VISk7i9DIfOvr9M= -sigs.k8s.io/gateway-api v1.3.0/go.mod h1:d8NV8nJbaRbEKem+5IuxkL8gJGOZ+FJ+NvOIltV8gDk= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= diff --git a/pkg/epp/controller/inferencepool_reconciler_test.go b/pkg/epp/controller/inferencepool_reconciler_test.go index 7f6938533..553fcc74a 100644 --- a/pkg/epp/controller/inferencepool_reconciler_test.go +++ b/pkg/epp/controller/inferencepool_reconciler_test.go @@ -24,6 +24,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -180,7 +181,9 @@ type diffStoreParams struct { func diffStore(datastore datastore.Datastore, params diffStoreParams) string { gotPool, _ := datastore.PoolGet() - if diff := cmp.Diff(params.wantPool, gotPool); diff != "" { + // controller-runtime fake client may not populate TypeMeta (APIVersion/Kind). + // Ignore it when comparing pools. + if diff := cmp.Diff(params.wantPool, gotPool, cmpopts.IgnoreTypes(metav1.TypeMeta{})); diff != "" { return "pool:" + diff } @@ -333,7 +336,10 @@ func xDiffStore(t *testing.T, datastore datastore.Datastore, params xDiffStorePa if err != nil { t.Fatalf("failed to convert InferencePool to XInferencePool: %v", err) } - if diff := cmp.Diff(params.wantPool, gotXPool); diff != "" { + + // controller-runtime fake client may not populate TypeMeta (APIVersion/Kind). + // Ignore it when comparing pools. + if diff := cmp.Diff(params.wantPool, gotXPool, cmpopts.IgnoreTypes(metav1.TypeMeta{})); diff != "" { return "pool:" + diff } From 0beaacc73d3a18b4a35cb9c4bc76a41e83a5e23b Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Thu, 9 Oct 2025 22:55:01 +0300 Subject: [PATCH 086/133] updated link to the updated recordings youtube channel (#1692) Signed-off-by: Nir Rozenbaum --- site-src/contributing/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site-src/contributing/index.md b/site-src/contributing/index.md index 7f56b828e..eb5da1710 100644 --- a/site-src/contributing/index.md +++ b/site-src/contributing/index.md @@ -46,4 +46,4 @@ doc. Feel free to add topics for discussion at an upcoming meeting. All meetings are recorded and automatically uploaded to the [WG-Serving meetings YouTube -playlist](https://www.youtube.com/playlist?list=PL69nYSiGNLP30qNanabU75ayPK7OPNAAS). +playlist](https://www.youtube.com/playlist?list=PL69nYSiGNLP2io2Gg92njBfh-DX9sk7O3). From 63b3ef044dd9a4e93cc5dd6960f21f104874a511 Mon Sep 17 00:00:00 2001 From: Zhengke Zhou Date: Fri, 10 Oct 2025 10:26:59 +0800 Subject: [PATCH 087/133] docs: add epp version history (#1360) * docs: add epp version history Signed-off-by: zhengkezhou1 * remove rc version Signed-off-by: zhengkezhou1 * update version Signed-off-by: zhengkezhou1 --------- Signed-off-by: zhengkezhou1 --- docs/proposals/004-endpoint-picker-protocol/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/proposals/004-endpoint-picker-protocol/README.md b/docs/proposals/004-endpoint-picker-protocol/README.md index 03b96bb71..919bb6311 100644 --- a/docs/proposals/004-endpoint-picker-protocol/README.md +++ b/docs/proposals/004-endpoint-picker-protocol/README.md @@ -14,6 +14,15 @@ This doc defines the protocol between the EPP and the proxy (e.g, Envoy). The EPP MUST implement the Envoy [external processing service](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_proc/v3/ext_proc.proto) protocol. +## Version History + +| Version | Date | Changes | +|----------|------------|--------------------------------------------------| +| v1.0.0 | 2025-07-29 | Added status metadata field for picked endpoints | +| v0.4.0 | 2025-06-03 | Added support for multiple fallback endpoints | +| v0.3.0 | 2025-03-14 | Added subsetting and fallback support | +| v0.2.0 | 2025-02-22 | Initial protocol definition | + ## Endpoint Subset [REQUEST: Data Plane -> EPP] From 18ea9c0c03985b8d98369f7ac9870e56b8731056 Mon Sep 17 00:00:00 2001 From: Murphy Chen Date: Fri, 10 Oct 2025 11:54:59 +0800 Subject: [PATCH 088/133] [traces] init the trace sdk (#1638) * typo * apply review's suggestion * update * move otel env to helm template * update * skip lint * fix lint * add file header * update README.md * typo * apply review's suggestion --- cmd/epp/runner/runner.go | 8 + config/charts/inferencepool/README.md | 67 ++++++--- .../templates/epp-deployment.yaml | 30 ++++ config/charts/inferencepool/values.yaml | 8 +- go.mod | 11 +- go.sum | 22 +-- pkg/common/telemetry.go | 140 ++++++++++++++++++ 7 files changed, 246 insertions(+), 40 deletions(-) create mode 100644 pkg/common/telemetry.go diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go index 61f235225..a077a6d28 100644 --- a/cmd/epp/runner/runner.go +++ b/cmd/epp/runner/runner.go @@ -110,6 +110,7 @@ var ( modelServerMetricsScheme = flag.String("model-server-metrics-scheme", "http", "Scheme to scrape metrics from pods") modelServerMetricsHttpsInsecureSkipVerify = flag.Bool("model-server-metrics-https-insecure-skip-verify", true, "When using 'https' scheme for 'model-server-metrics-scheme', configure 'InsecureSkipVerify' (default to true)") haEnableLeaderElection = flag.Bool("ha-enable-leader-election", false, "Enables leader election for high availability. When enabled, readiness probes will only pass on the leader.") + tracing = flag.Bool("tracing", true, "Enables emitting traces") // Latency Predictor Flag enableLatencyPredictor = flag.Bool("enable-latency-predictor", false, "Enable the regression-based latency predictor and scheduler scorer.") @@ -148,6 +149,13 @@ func (r *Runner) Run(ctx context.Context) error { flag.Parse() initLogging(&opts) + if *tracing { + err := common.InitTracing(ctx, setupLog) + if err != nil { + return err + } + } + setupLog.Info("GIE build", "commit-sha", version.CommitSHA, "build-ref", version.BuildRef) // Validate flags diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md index 41fee834d..f6354bfee 100644 --- a/config/charts/inferencepool/README.md +++ b/config/charts/inferencepool/README.md @@ -166,30 +166,34 @@ $ helm uninstall pool-1 The following table list the configurable parameters of the chart. -| **Parameter Name** | **Description** | -|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------| -| `inferencePool.apiVersion` | The API version of the InferencePool resource. Defaults to `inference.networking.k8s.io/v1`. This can be changed to `inference.networking.x-k8s.io/v1alpha2` to support older API versions. | -| `inferencePool.targetPortNumber` | Target port number for the vllm backends, will be used to scrape metrics by the inference extension. Defaults to 8000. | -| `inferencePool.modelServerType` | Type of the model servers in the pool, valid options are [vllm, triton-tensorrt-llm], default is vllm. | -| `inferencePool.modelServers.matchLabels` | Label selector to match vllm backends managed by the inference pool. | -| `inferenceExtension.replicas` | Number of replicas for the endpoint picker extension service. If More than one replica is used, EPP will run in HA active-passive mode. Defaults to `1`. | -| `inferenceExtension.image.name` | Name of the container image used for the endpoint picker. | -| `inferenceExtension.image.hub` | Registry URL where the endpoint picker image is hosted. | -| `inferenceExtension.image.tag` | Image tag of the endpoint picker. | -| `inferenceExtension.image.pullPolicy` | Image pull policy for the container. Possible values: `Always`, `IfNotPresent`, or `Never`. Defaults to `Always`. | -| `inferenceExtension.env` | List of environment variables to set in the endpoint picker container as free-form YAML. Defaults to `[]`. | -| `inferenceExtension.extraContainerPorts` | List of additional container ports to expose. Defaults to `[]`. | -| `inferenceExtension.extraServicePorts` | List of additional service ports to expose. Defaults to `[]`. | -| `inferenceExtension.flags` | List of flags which are passed through to endpoint picker. Example flags, enable-pprof, grpc-port etc. Refer [runner.go](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/cmd/epp/runner/runner.go) for complete list. | -| `inferenceExtension.affinity` | Affinity for the endpoint picker. Defaults to `{}`. | -| `inferenceExtension.tolerations` | Tolerations for the endpoint picker. Defaults to `[]`. | | -| `inferenceExtension.monitoring.interval` | Metrics scraping interval for monitoring. Defaults to `10s`. | -| `inferenceExtension.monitoring.secret.name` | Name of the service account token secret for metrics authentication. Defaults to `inference-gateway-sa-metrics-reader-secret`. | -| `inferenceExtension.monitoring.prometheus.enabled` | Enable Prometheus ServiceMonitor creation for EPP metrics collection. Defaults to `false`. | -| `inferenceExtension.monitoring.gke.enabled` | Enable GKE monitoring resources (`PodMonitoring` and RBAC). Defaults to `false`. | -| `inferenceExtension.pluginsCustomConfig` | Custom config that is passed to EPP as inline yaml. | -| `provider.name` | Name of the Inference Gateway implementation being used. Possible values: [`none`, `gke`, or `istio`]. Defaults to `none`. | -| `provider.gke.autopilot` | Set to `true` if the cluster is a GKE Autopilot cluster. This is only used if `provider.name` is `gke`. Defaults to `false`. | +| **Parameter Name** | **Description** | +|----------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `inferencePool.apiVersion` | The API version of the InferencePool resource. Defaults to `inference.networking.k8s.io/v1`. This can be changed to `inference.networking.x-k8s.io/v1alpha2` to support older API versions. | +| `inferencePool.targetPortNumber` | Target port number for the vllm backends, will be used to scrape metrics by the inference extension. Defaults to 8000. | +| `inferencePool.modelServerType` | Type of the model servers in the pool, valid options are [vllm, triton-tensorrt-llm], default is vllm. | +| `inferencePool.modelServers.matchLabels` | Label selector to match vllm backends managed by the inference pool. | +| `inferenceExtension.replicas` | Number of replicas for the endpoint picker extension service. If More than one replica is used, EPP will run in HA active-passive mode. Defaults to `1`. | +| `inferenceExtension.image.name` | Name of the container image used for the endpoint picker. | +| `inferenceExtension.image.hub` | Registry URL where the endpoint picker image is hosted. | +| `inferenceExtension.image.tag` | Image tag of the endpoint picker. | +| `inferenceExtension.image.pullPolicy` | Image pull policy for the container. Possible values: `Always`, `IfNotPresent`, or `Never`. Defaults to `Always`. | +| `inferenceExtension.env` | List of environment variables to set in the endpoint picker container as free-form YAML. Defaults to `[]`. | +| `inferenceExtension.extraContainerPorts` | List of additional container ports to expose. Defaults to `[]`. | +| `inferenceExtension.extraServicePorts` | List of additional service ports to expose. Defaults to `[]`. | +| `inferenceExtension.flags` | List of flags which are passed through to endpoint picker. Example flags, enable-pprof, grpc-port etc. Refer [runner.go](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/cmd/epp/runner/runner.go) for complete list. | +| `inferenceExtension.affinity` | Affinity for the endpoint picker. Defaults to `{}`. | +| `inferenceExtension.tolerations` | Tolerations for the endpoint picker. Defaults to `[]`. | +| `inferenceExtension.monitoring.interval` | Metrics scraping interval for monitoring. Defaults to `10s`. | +| `inferenceExtension.monitoring.secret.name` | Name of the service account token secret for metrics authentication. Defaults to `inference-gateway-sa-metrics-reader-secret`. | +| `inferenceExtension.monitoring.prometheus.enabled` | Enable Prometheus ServiceMonitor creation for EPP metrics collection. Defaults to `false`. | +| `inferenceExtension.monitoring.gke.enabled` | Enable GKE monitoring resources (`PodMonitoring` and RBAC). Defaults to `false`. | +| `inferenceExtension.pluginsCustomConfig` | Custom config that is passed to EPP as inline yaml. | +| `inferenceExtension.tracing.enabled` | Enables or disables OpenTelemetry tracing globally for the EndpointPicker. | +| `inferenceExtension.tracing.otelExporterEndpoint` | OpenTelemetry collector endpoint. | +| `inferenceExtension.tracing.sampling.sampler` | The trace sampler to use. Currently, only `parentbased_traceidratio` is supported. This sampler respects the parent span’s sampling decision when present, and applies the configured ratio for root spans. | +| `inferenceExtension.tracing.sampling.samplerArg` | Sampler-specific argument. For `parentbased_traceidratio`, this defines the base sampling rate for new traces (root spans), as a float string in the range [0.0, 1.0]. For example, "0.1" enables 10% sampling. | +| `provider.name` | Name of the Inference Gateway implementation being used. Possible values: [`none`, `gke`, or `istio`]. Defaults to `none`. | +| `provider.gke.autopilot` | Set to `true` if the cluster is a GKE Autopilot cluster. This is only used if `provider.name` is `gke`. Defaults to `false`. | ### Provider Specific Configuration @@ -214,6 +218,21 @@ These are the options available to you with `provider.name` set to `istio`: | `istio.destinationRule.host` | Custom host value for the destination rule. If not set this will use the default value which is derrived from the epp service name and release namespace to gerenate a valid service address. | | `istio.destinationRule.trafficPolicy.connectionPool` | Configure the connectionPool level settings of the traffic policy | +#### OpenTelemetry + +The EndpointPicker supports OpenTelemetry-based tracing. To enable trace collection, use the following configuration: +```yaml +inferenceExtension: + tracing: + enabled: true + otelExporterEndpoint: "http://localhost:4317" + sampling: + sampler: "parentbased_traceidratio" + samplerArg: "0.1" +``` +Make sure that the `otelExporterEndpoint` points to your OpenTelemetry collector endpoint. +Current only the `parentbased_traceidratio` sampler is supported. You can adjust the base sampling ratio using the `samplerArg` (e.g., 0.1 means 10% of traces will be sampled). + ## Notes This chart will only deploy an InferencePool and its corresponding EndpointPicker extension. Before install the chart, please make sure that the inference extension CRDs are installed in the cluster. For more details, please refer to the [getting started guide](https://gateway-api-inference-extension.sigs.k8s.io/guides/). diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml index f012c2e47..120240217 100644 --- a/config/charts/inferencepool/templates/epp-deployment.yaml +++ b/config/charts/inferencepool/templates/epp-deployment.yaml @@ -62,6 +62,12 @@ spec: - "--{{ .name }}" - "{{ .value }}" {{- end }} + - "--tracing" + {{- if .Values.inferenceExtension.tracing.enabled }} + - "true" + {{- else }} + - "false" + {{- end }} ports: - name: grpc containerPort: 9002 @@ -101,6 +107,30 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + {{- if .Values.inferenceExtension.tracing.enabled }} + - name: OTEL_SERVICE_NAME + value: "gateway-api-inference-extension" + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: {{ .Values.inferenceExtension.tracing.otelExporterEndpoint | quote }} + - name: OTEL_TRACES_EXPORTER + value: "otlp" + - name: OTEL_RESOURCE_ATTRIBUTES_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + - name: OTEL_RESOURCE_ATTRIBUTES_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: OTEL_RESOURCE_ATTRIBUTES + value: 'k8s.namespace.name=$(NAMESPACE),k8s.node.name=$(OTEL_RESOURCE_ATTRIBUTES_NODE_NAME),k8s.pod.name=$(OTEL_RESOURCE_ATTRIBUTES_POD_NAME)' + - name: OTEL_TRACES_SAMPLER + value: {{ .Values.inferenceExtension.tracing.sampling.sampler | quote }} + - name: OTEL_TRACES_SAMPLER_ARG + value: {{ .Values.inferenceExtension.tracing.sampling.samplerArg | quote }} + {{- end }} {{- if .Values.inferenceExtension.env }} {{- toYaml .Values.inferenceExtension.env | nindent 8 }} {{- end }} diff --git a/config/charts/inferencepool/values.yaml b/config/charts/inferencepool/values.yaml index 91d6a48e6..f901f7f0f 100644 --- a/config/charts/inferencepool/values.yaml +++ b/config/charts/inferencepool/values.yaml @@ -53,6 +53,12 @@ inferenceExtension: gke: enabled: false + tracing: + enabled: false + otelExporterEndpoint: "http://localhost:4317" + sampling: + sampler: "parentbased_traceidratio" + samplerArg: "0.1" inferencePool: targetPorts: @@ -85,4 +91,4 @@ istio: trafficPolicy: {} # connectionPool: # http: - # maxRequestsPerConnection: 256000 + # maxRequestsPerConnection: 256000 \ No newline at end of file diff --git a/go.mod b/go.mod index 6d339f6c4..fd8c44513 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,10 @@ require ( github.com/prometheus/common v0.66.1 github.com/prometheus/prometheus v0.306.0 github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.17.0 @@ -96,12 +100,9 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect diff --git a/go.sum b/go.sum index 81d12272f..e017ad17a 100644 --- a/go.sum +++ b/go.sum @@ -252,20 +252,22 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= -go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= diff --git a/pkg/common/telemetry.go b/pkg/common/telemetry.go new file mode 100644 index 000000000..3723e0f7d --- /dev/null +++ b/pkg/common/telemetry.go @@ -0,0 +1,140 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "context" + "fmt" + "os" + "strconv" + + "github.com/go-logr/logr" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging" + "sigs.k8s.io/gateway-api-inference-extension/version" +) + +type errorHandler struct { + logger logr.Logger +} + +func (h *errorHandler) Handle(err error) { + h.logger.V(logging.DEFAULT).Error(err, "trace error occurred") +} + +func InitTracing(ctx context.Context, logger logr.Logger) error { + logger = logger.WithName("trace") + loggerWrap := &errorHandler{logger: logger} + + _, ok := os.LookupEnv("OTEL_SERVICE_NAME") + if !ok { + os.Setenv("OTEL_SERVICE_NAME", "gateway-api-inference-extension") + } + + _, ok = os.LookupEnv("OTEL_EXPORTER_OTLP_ENDPOINT") + if !ok { + os.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317") + } + + traceExporter, err := initTraceExporter(ctx, logger) + if err != nil { + loggerWrap.Handle(fmt.Errorf("%s: %v", "init trace exporter failed", err)) + return err + } + + // Go SDK doesn't have an automatic sampler, handle manually + samplerType, ok := os.LookupEnv("OTEL_TRACES_SAMPLER") + if !ok { + samplerType = "parentbased_traceidratio" + } + samplerARG, ok := os.LookupEnv("OTEL_TRACES_SAMPLER_ARG") + if !ok { + samplerARG = "0.1" + } + + sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1)) + if samplerType == "parentbased_traceidratio" { + fraction, err := strconv.ParseFloat(samplerARG, 64) + if err != nil { + fraction = 0.1 + } + + sampler = sdktrace.ParentBased(sdktrace.TraceIDRatioBased(fraction)) + } else { + loggerWrap.Handle(fmt.Errorf("unsupported sampler type: %s, fallback to parentbased_traceidratio with 0.1 Ratio", samplerType)) + } + + opt := []sdktrace.TracerProviderOption{ + sdktrace.WithBatcher(traceExporter), + sdktrace.WithSampler(sampler), + sdktrace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceVersionKey.String(version.BuildRef), + )), + } + + tracerProvider := sdktrace.NewTracerProvider(opt...) + otel.SetTracerProvider(tracerProvider) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) + otel.SetErrorHandler(loggerWrap) + + go func() { + <-ctx.Done() + err := tracerProvider.Shutdown(context.Background()) + if err != nil { + loggerWrap.Handle(fmt.Errorf("%s: %v", "failed to shutdown TraceProvider", err)) + } + + logger.V(logging.DEFAULT).Info("trace provider shutting down") + }() + + return nil +} + +// initTraceExporter create a SpanExporter +// support exporter type +// - console: export spans in console for development use case +// - otlp: export spans through gRPC to an opentelemetry collector +func initTraceExporter(ctx context.Context, logger logr.Logger) (sdktrace.SpanExporter, error) { + var traceExporter sdktrace.SpanExporter + traceExporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()) + if err != nil { + return nil, fmt.Errorf("failed to create stdouttrace exporter: %w", err) + } + + exporterType, ok := os.LookupEnv("OTEL_TRACES_EXPORTER") + if !ok { + exporterType = "console" + } + + logger.Info("init OTel trace exporter", "type", exporterType) + if exporterType == "otlp" { + traceExporter, err = otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure()) + if err != nil { + return nil, fmt.Errorf("failed to create otlp-grcp exporter: %w", err) + } + } + + return traceExporter, nil +} From 9de0af6ee2c941ec6830fa6ac8c427f52f469c48 Mon Sep 17 00:00:00 2001 From: Kfir Toledo Date: Fri, 10 Oct 2025 21:47:01 +0300 Subject: [PATCH 089/133] feat: Remove deleted pod from prefix-cache scorer (#1376) Signed-off-by: Kfir Toledo --- cmd/epp/runner/runner.go | 7 ++- pkg/epp/plugins/handle.go | 17 +++++- .../framework/plugins/multi/prefix/indexer.go | 32 ++++++++++ .../plugins/multi/prefix/indexer_test.go | 60 +++++++++++++++++++ .../framework/plugins/multi/prefix/plugin.go | 39 +++++++++++- test/utils/handle.go | 5 ++ 6 files changed, 155 insertions(+), 5 deletions(-) diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go index a077a6d28..8dac40a3c 100644 --- a/cmd/epp/runner/runner.go +++ b/cmd/epp/runner/runner.go @@ -279,7 +279,7 @@ func (r *Runner) Run(ctx context.Context) error { } // =================================================================== - err = r.parsePluginsConfiguration(ctx, predictor, datastore) + err = r.parsePluginsConfiguration(ctx, predictor, datastore, datastore) if err != nil { setupLog.Error(err, "Failed to parse the configuration") return err @@ -370,7 +370,7 @@ func (r *Runner) registerLatencyPredictorPlugins(predictor latencypredictor.Pred plugins.Register(picker.WeightedRandomPickerType, picker.WeightedRandomPickerFactory) } -func (r *Runner) parsePluginsConfiguration(ctx context.Context, predictor latencypredictor.PredictorInterface, datastore datastore.Datastore) error { +func (r *Runner) parsePluginsConfiguration(ctx context.Context, predictor latencypredictor.PredictorInterface, datastore datastore.Datastore, ds datastore.Datastore) error { if *configText == "" && *configFile == "" { return nil // configuring through code, not through file } @@ -395,8 +395,9 @@ func (r *Runner) parsePluginsConfiguration(ctx context.Context, predictor latenc setupLog.Info("Registering latency predictor plugins") r.registerLatencyPredictorPlugins(predictor, datastore) } - handle := plugins.NewEppHandle(ctx) + handle := plugins.NewEppHandle(ctx, ds.PodList) config, err := loader.LoadConfig(configBytes, handle, logger) + if err != nil { return fmt.Errorf("failed to load the configuration - %w", err) } diff --git a/pkg/epp/plugins/handle.go b/pkg/epp/plugins/handle.go index 8c9153cf1..c074e9076 100644 --- a/pkg/epp/plugins/handle.go +++ b/pkg/epp/plugins/handle.go @@ -19,6 +19,8 @@ package plugins import ( "context" "fmt" + + backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" ) // Handle provides plugins a set of standard data and tools to work with @@ -27,6 +29,9 @@ type Handle interface { Context() context.Context HandlePlugins + + // PodList lists pods matching the given predicate. + PodList(predicate func(backendmetrics.PodMetrics) bool) []backendmetrics.PodMetrics } // HandlePlugins defines a set of APIs to work with instantiated plugins @@ -44,10 +49,14 @@ type HandlePlugins interface { GetAllPluginsWithNames() map[string]Plugin } +// PodListFunc is a function type that filters and returns a list of pod metrics +type PodListFunc func(predicate func(backendmetrics.PodMetrics) bool) []backendmetrics.PodMetrics + // eppHandle is an implementation of the interface plugins.Handle type eppHandle struct { ctx context.Context HandlePlugins + podList PodListFunc } // Context returns a context the plugins can use, if they need one @@ -84,12 +93,18 @@ func (h *eppHandlePlugins) GetAllPluginsWithNames() map[string]Plugin { return h.plugins } -func NewEppHandle(ctx context.Context) Handle { +// PodList lists pods matching the given predicate. +func (h *eppHandle) PodList(predicate func(backendmetrics.PodMetrics) bool) []backendmetrics.PodMetrics { + return h.podList(predicate) +} + +func NewEppHandle(ctx context.Context, podList PodListFunc) Handle { return &eppHandle{ ctx: ctx, HandlePlugins: &eppHandlePlugins{ plugins: map[string]Plugin{}, }, + podList: podList, } } diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer.go index bd9e2c96e..8b68132dc 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer.go @@ -149,3 +149,35 @@ func (i *indexer) reportLRUSize(ctx context.Context, interval time.Duration) { i.mu.RUnlock() } } + +// RemovePod removes a pod and its associated entries from the indexer. +func (i *indexer) RemovePod(pod ServerID) { + i.mu.RLock() + lruCache, exists := i.podToLRU[pod] + i.mu.RUnlock() + + if !exists { + return + } + + // Remove all hashes associated with the pod from hashToPods (triggers eviction callbacks). + for _, hash := range lruCache.Keys() { + lruCache.Remove(hash) + } + + i.mu.Lock() + delete(i.podToLRU, pod) + i.mu.Unlock() +} + +// Pods returns the list of all pods currently tracked in the indexer. +func (i *indexer) Pods() []ServerID { + i.mu.RLock() + defer i.mu.RUnlock() + + pods := make([]ServerID, 0, len(i.podToLRU)) + for pod := range i.podToLRU { + pods = append(pods, pod) + } + return pods +} diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer_test.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer_test.go index 6d4fcc5f4..c35af8e27 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer_test.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/indexer_test.go @@ -46,3 +46,63 @@ func TestIndexer_AddAndGet(t *testing.T) { servers = i.Get(BlockHash(4)) assert.Empty(t, servers, "Cache should not contain non-existent hash") } + +func TestIndexer_RemovePodAndEviction(t *testing.T) { + const indexerSize = 10 + + i := newIndexer(context.Background(), indexerSize) + + server1 := ServerID{Namespace: "default", Name: "server1"} + server2 := ServerID{Namespace: "default", Name: "server2"} + + // Add indexerSize hashes to both servers + var hashes []BlockHash + for j := 0; j < indexerSize; j++ { + h := BlockHash(j) + hashes = append(hashes, h) + i.Add([]BlockHash{h}, server1) + i.Add([]BlockHash{h}, server2) + } + + // Ensure all entries are added + assert.Equal(t, indexerSize, i.podToLRU[server1].Len(), "server1 should have 10 entries") + assert.Equal(t, indexerSize, i.podToLRU[server2].Len(), "server2 should have 10 entries") + + // Ensure each hash in hashToPods maps to both server1 and server2 + for _, h := range hashes { + pods := i.hashToPods[h] + assert.Len(t, pods, 2, "Each hash should be associated with exactly 2 pods") + assert.Contains(t, pods, server1, "hash should be associated with server1") + assert.Contains(t, pods, server2, "hash should be associated with server2") + } + + // Add indexerSize hash to server1 → should evict BlockHash(0) + evictedHash := BlockHash(0) + newHash := BlockHash(indexerSize) + i.Add([]BlockHash{newHash}, server1) + + // server1 LRU should still be at max capacity + assert.Equal(t, indexerSize, i.podToLRU[server1].Len(), "server1 LRU should maintain max size") + + // BlockHash(0) should no longer have server1 in hashToPods + pods := i.Get(evictedHash) + assert.NotContains(t, pods, server1, "server1 should be evicted from hashToPods for hash 0") + assert.Contains(t, pods, server2, "server2 should still have hash 0") + + // Remove server2 + i.RemovePod(server2) + + // hashToPods for hash 0 should now be empty + pods = i.Get(evictedHash) + assert.NotContains(t, pods, server2, "server2 should be removed from hash 0") + assert.Empty(t, pods, "hash 0 should have no pods after both eviction and removal") + + // All remaining hashes should map only to server1 + for hash, pods := range i.hashToPods { + assert.Len(t, pods, 1, "hash %v should have only 1 pod after server2 removal", hash) + assert.Contains(t, pods, server1, "hash %v should only contain server1", hash) + } + + // Ensure hashToPods contains exactly indexerSize hashes (post-eviction and server2 removal) + assert.Len(t, i.hashToPods, indexerSize, "hashToPods should contain %d hashes after cleanup", indexerSize) +} diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go index 00ae9a7dc..aa85ab182 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go @@ -22,11 +22,13 @@ import ( "encoding/json" "fmt" "sync" + "time" "github.com/cespare/xxhash/v2" k8stypes "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/log" + backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/requestcontrol" @@ -57,6 +59,10 @@ const ( PrefixCachePluginType = "prefix-cache-scorer" ) +const ( + PodActiveCheckInterval = 2 * time.Minute +) + var DefaultConfig = Config{ BlockSize: DefaultBlockSize, MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, @@ -93,6 +99,8 @@ type podSet map[ServerID]struct{} type Indexer interface { Get(hash BlockHash) podSet Add(hashes []BlockHash, server ServerID) + RemovePod(server ServerID) + Pods() []ServerID } // BlockHash is a hash of the block of request body. @@ -149,7 +157,9 @@ func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, handle } } - return New(handle.Context(), parameters).WithName(name), nil + p := New(handle.Context(), parameters).WithName(name) + go p.CleanUpInactivePods(handle.Context(), handle) + return p, nil } // New initializes a new prefix Plugin and returns its pointer. @@ -262,6 +272,33 @@ func (p *Plugin) matchLongestPrefix(ctx context.Context, hashes []BlockHash) map return res } +// CleanUpInactivePods starts a goroutine that watches for inactive pods. +func (m *Plugin) CleanUpInactivePods(ctx context.Context, handle plugins.Handle) { + logger := log.FromContext(ctx).V(logutil.VERBOSE) + ticker := time.NewTicker(PodActiveCheckInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + activePodMetrics := handle.PodList(func(_ backendmetrics.PodMetrics) bool { return true }) + activePods := make(map[ServerID]struct{}, len(activePodMetrics)) + for _, pm := range activePodMetrics { + activePods[ServerID(pm.GetPod().NamespacedName)] = struct{}{} + } + + for _, pod := range m.indexer.Pods() { + if _, ok := activePods[pod]; !ok { + m.indexer.RemovePod(pod) + logger.Info("Removed pod not in active set", "pod", pod) + } + } + } + } +} + // hashPrompt divides the prompt into blocks and calculate the prefix cache for each block. // hash[0] is calculated including the model name and cache_salt(if provided), since different models generally don't share prefix cache. // For block i, hash(i) = hash(block i content, hash(i-1)). diff --git a/test/utils/handle.go b/test/utils/handle.go index 4a29dda87..273539f81 100644 --- a/test/utils/handle.go +++ b/test/utils/handle.go @@ -19,6 +19,7 @@ package utils import ( "context" + backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins" ) @@ -33,6 +34,10 @@ func (h *testHandle) Context() context.Context { return h.ctx } +func (h *testHandle) PodList(predicate func(backendmetrics.PodMetrics) bool) []backendmetrics.PodMetrics { + return []backendmetrics.PodMetrics{} +} + type testHandlePlugins struct { plugins map[string]plugins.Plugin } From 2366f0ce827b83930f59e5bd545786a861157ad7 Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Mon, 13 Oct 2025 13:05:33 -0700 Subject: [PATCH 090/133] Conformance: Adds Weight-Based Traffic Splitting Test (#1669) * Conformance: Adds Weight-Based Traffic Splitting Test Signed-off-by: Daneyon Hansen * Resolves zetxqx review feedback Signed-off-by: Daneyon Hansen --------- Signed-off-by: Daneyon Hansen --- .../tests/gateway_weighted_two_pools.go | 221 ++++++++++++++++++ .../tests/gateway_weighted_two_pools.yaml | 30 +++ 2 files changed, 251 insertions(+) create mode 100644 conformance/tests/gateway_weighted_two_pools.go create mode 100644 conformance/tests/gateway_weighted_two_pools.yaml diff --git a/conformance/tests/gateway_weighted_two_pools.go b/conformance/tests/gateway_weighted_two_pools.go new file mode 100644 index 000000000..bbe3cbb18 --- /dev/null +++ b/conformance/tests/gateway_weighted_two_pools.go @@ -0,0 +1,221 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "fmt" + "math" + "net/http" + "strings" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + "k8s.io/apimachinery/pkg/types" + gwhttp "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" + + "sigs.k8s.io/gateway-api-inference-extension/conformance/resources" + k8sutils "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api-inference-extension/conformance/utils/traffic" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/framework/plugins/test" +) + +func init() { + ConformanceTests = append(ConformanceTests, GatewayWeightedAcrossTwoInferencePools) +} + +// GatewayWeightedAcrossTwoInferencePools verifies that Gateway splits traffic across two +// InferencePools according to backendRef weights, and that each request is routed to an +// endpoint of the selected InferencePool. +var GatewayWeightedAcrossTwoInferencePools = suite.ConformanceTest{ + ShortName: "GatewayWeightedAcrossTwoInferencePools", + Description: "Gateway should split traffic across two InferencePools based on backendRef weights and route only to endpoints of the selected InferencePool", + Manifests: []string{"tests/gateway_weighted_two_pools.yaml"}, + Features: []features.FeatureName{ + features.SupportGateway, + features.FeatureName("SupportInferencePool"), + }, + Test: func(t *testing.T, s *suite.ConformanceTestSuite) { + const ( + hostname = "primary.example.com" + path = "/weighted-two-pools-test" + + // Sample size so the weight signal dominates random noise. + totalRequests = 200 + concurrentRequests = 5 + + // These route weights must match the test manifest. + primaryWeight = 70 + secondaryWeight = 30 + ) + + // Objects under test. + httpRouteNN := types.NamespacedName{Name: "httproute-weighted-two-pools", Namespace: resources.AppBackendNamespace} + gatewayNN := resources.PrimaryGatewayNN + primaryPoolNN := resources.PrimaryInferencePoolNN + secondaryPoolNN := types.NamespacedName{Name: "secondary-inference-pool", Namespace: resources.AppBackendNamespace} + + // Labels for the two deployments defined in base.yaml. + primaryLabels := map[string]string{"app": "primary-inference-model-server"} + secondaryLabels := map[string]string{"app": "secondary-inference-model-server"} + + t.Log("Verifying HTTPRoute and both InferencePools are accepted and the Gateway has an address.") + k8sutils.HTTPRouteMustBeAcceptedAndResolved(t, s.Client, s.TimeoutConfig, httpRouteNN, gatewayNN) + k8sutils.InferencePoolMustBeAcceptedByParent(t, s.Client, primaryPoolNN, gatewayNN) + k8sutils.InferencePoolMustBeAcceptedByParent(t, s.Client, secondaryPoolNN, gatewayNN) + gwAddr := k8sutils.GetGatewayEndpoint(t, s.Client, s.TimeoutConfig, gatewayNN) + + // Discover pods for each pool and build quick lookup sets. + t.Logf("Fetching primary backend pods with labels: %v", primaryLabels) + primaryPods, err := k8sutils.GetPodsWithLabel(t, s.Client, resources.AppBackendNamespace, primaryLabels, s.TimeoutConfig) + require.NoError(t, err) + require.Len(t, primaryPods, 3) // base.yaml uses 3 replicas + + t.Logf("Fetching secondary backend pods with labels: %v", secondaryLabels) + secondaryPods, err := k8sutils.GetPodsWithLabel(t, s.Client, resources.AppBackendNamespace, secondaryLabels, s.TimeoutConfig) + require.NoError(t, err) + require.Len(t, secondaryPods, 3) // base.yaml uses 3 replicas + + primaryPodNames := make([]string, 0, len(primaryPods)) + primaryPodIPs := make([]string, 0, len(primaryPods)) + for _, p := range primaryPods { + require.NotEmpty(t, p.Status.PodIP, "primary pod %s has no IP yet", p.Name) + primaryPodNames = append(primaryPodNames, p.Name) + primaryPodIPs = append(primaryPodIPs, p.Status.PodIP) + } + + secondaryPodNames := make([]string, 0, len(secondaryPods)) + secondaryPodIPs := make([]string, 0, len(secondaryPods)) + for _, p := range secondaryPods { + require.NotEmpty(t, p.Status.PodIP, "secondary pod %s has no IP yet", p.Name) + secondaryPodNames = append(secondaryPodNames, p.Name) + secondaryPodIPs = append(secondaryPodIPs, p.Status.PodIP) + } + + // Send one targeted request per backend Pod to ensure EPP readiness. + allIPs := append(append([]string{}, primaryPodIPs...), secondaryPodIPs...) + allNames := append(append([]string{}, primaryPodNames...), secondaryPodNames...) + for i := 0; i < len(allIPs); i++ { + traffic.MakeRequestAndExpectSuccess( + t, + s.RoundTripper, + s.TimeoutConfig, + gwAddr, + traffic.Request{ + Host: hostname, + Path: path, + Headers: map[string]string{ + test.HeaderTestEppEndPointSelectionKey: allIPs[i], + }, + Method: http.MethodPost, + Body: `{"model":"conformance-fake-model","prompt":"Warmup"}`, + Backend: allNames[i], + Namespace: resources.AppBackendNamespace, + }, + ) + } + + // Provide a union list of eligible endpoints for the test. Each pool's EPP + // should filter to endpoints that actually belong to its pool. + eppHeaderValue := strings.Join(allIPs, ",") + + requestBody := `{ + "model": "conformance-fake-model", + "prompt": "Write as if you were a critic: San Francisco" + }` + + // Build quick lookup sets for attributing each hit to a pool by backend pod name. + primarySet := make(map[string]struct{}, len(primaryPodNames)) + for _, n := range primaryPodNames { + primarySet[n] = struct{}{} + } + secondarySet := make(map[string]struct{}, len(secondaryPodNames)) + for _, n := range secondaryPodNames { + secondarySet[n] = struct{}{} + } + + headers := map[string]string{ + test.HeaderTestEppEndPointSelectionKey: eppHeaderValue, + } + expected := gwhttp.ExpectedResponse{ + Request: gwhttp.Request{ + Host: hostname, + Path: path, + Method: http.MethodPost, + Headers: headers, + }, + Response: gwhttp.Response{ + StatusCode: http.StatusOK, + }, + Namespace: resources.AppBackendNamespace, + } + req := gwhttp.MakeRequest(t, &expected, gwAddr, "HTTP", "http") + + var primaryHits, secondaryHits atomic.Int64 + var g errgroup.Group + g.SetLimit(concurrentRequests) + + for i := 0; i < totalRequests; i++ { + g.Go(func() error { + cReq, cRes, err := traffic.MakeCallRoundTripper(t, s.RoundTripper, &traffic.RequestWithBody{ + Request: req, + Body: strings.NewReader(requestBody), + }) + if err != nil { + return fmt.Errorf("failed to roundtrip request: %w", err) + } + if err := gwhttp.CompareRoundTrip(t, &req, cReq, cRes, expected); err != nil { + return fmt.Errorf("response expectation failed: %w", err) + } + + // Attribute response to pool by backend pod name. + if _, ok := primarySet[cReq.Pod]; ok { + primaryHits.Add(1) + } else if _, ok := secondarySet[cReq.Pod]; ok { + secondaryHits.Add(1) + } else { + return fmt.Errorf("request was handled by unexpected pod %q (not in either pool)", cReq.Pod) + } + return nil + }) + } + require.NoError(t, g.Wait(), "requests failed") + + ph := float64(primaryHits.Load()) + sh := float64(secondaryHits.Load()) + total := ph + sh + require.Equal(t, int64(totalRequests), int64(total), "sum of hits must equal number of attempts") + require.Greater(t, total, 0.0) + + observedPrimary := ph / total + expectedPrimary := float64(primaryWeight) / float64(primaryWeight+secondaryWeight) + + // Allow either a 10 percentage-point absolute error, or a 3-sigma binomial CI. + sigma := math.Sqrt(expectedPrimary * (1.0 - expectedPrimary) / total) + absTolerance := math.Max(0.10, 3.0*sigma) + + diff := math.Abs(observedPrimary - expectedPrimary) + require.LessOrEqualf(t, diff, absTolerance, + "weighted split out of bounds: observed primary=%.3f (hits=%d/%d), expected=%.3f, tolerance=±%.3f", + observedPrimary, int64(ph), int64(total), expectedPrimary, absTolerance) + t.Logf("Weighted split OK: primary=%.3f (hits=%d/%d), expected=%.3f, tolerance=±%.3f; secondary hits=%d", + observedPrimary, int64(ph), int64(total), expectedPrimary, absTolerance, int64(sh)) + }, +} diff --git a/conformance/tests/gateway_weighted_two_pools.yaml b/conformance/tests/gateway_weighted_two_pools.yaml new file mode 100644 index 000000000..288282243 --- /dev/null +++ b/conformance/tests/gateway_weighted_two_pools.yaml @@ -0,0 +1,30 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-weighted-two-pools + namespace: gateway-conformance-app-backend +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: conformance-primary + namespace: gateway-conformance-infra + sectionName: http + hostnames: + - "primary.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /weighted-two-pools-test + backendRefs: + # 70% of traffic goes to the primary pool + - group: inference.networking.k8s.io + kind: InferencePool + name: primary-inference-pool + weight: 70 + # 30% of traffic goes to the secondary pool + - group: inference.networking.k8s.io + kind: InferencePool + name: secondary-inference-pool + weight: 30 From e611ad252d724dee54b1a52ddbbd2b456bbd5ba2 Mon Sep 17 00:00:00 2001 From: Luke Van Drie Date: Mon, 13 Oct 2025 16:45:33 -0700 Subject: [PATCH 091/133] feat(fc): Initial wiring of the flow control layer (#1701) * feat(fc): Initial wiring of the flow control layer This commit introduces the initial integration of the new Flow Control layer into the Endpoint Picker (EPP). The primary goal is to provide a mechanism for more sophisticated request admission control, queuing, and multitenancy management. This new layer is gated by the ENABLE_EXPERIMENTAL_FLOW_CONTROL_LAYER feature flag. When enabled, the Director delegates admission control decisions to the new flowController component. The legacy saturation-based shedding logic is preserved for when the feature is disabled. Comprehensive unit tests for the admitRequest function have been added to validate the behavior of both the legacy and new flow control paths. * refactor: Abstract away admission control logic This commit refactors the admission control logic introduced in the previous commit. - Introduces an AdmissionController interface. - Implements LegacyAdmissionController for existing behavior. - Implements FlowControlAdmissionController for the new flow control path. - Moves flow control request adaptation into FlowControlAdmissionController. - Updates Director to use the AdmissionController interface. - Runner now injects the appropriate controller based on the feature flag. - Splits and refactors tests for better focus and isolation. This improves modularity, testability, and separation of concerns. * feat: Address feedback on admission control - Introduce requtil.IsSheddable() for clarity - Move FairnessID defaulting to handler package - Add test for default FairnessID * fix: admission.go boilerplate header --- cmd/epp/runner/runner.go | 59 ++++- pkg/epp/handlers/request.go | 12 + pkg/epp/handlers/request_test.go | 30 +++ pkg/epp/requestcontrol/admission.go | 219 ++++++++++++++++++ pkg/epp/requestcontrol/admission_test.go | 282 +++++++++++++++++++++++ pkg/epp/requestcontrol/director.go | 71 ++---- pkg/epp/requestcontrol/director_test.go | 165 +++++++++---- pkg/epp/server/runserver.go | 3 +- pkg/epp/util/request/sheddable.go | 22 ++ test/integration/epp/hermetic_test.go | 3 +- 10 files changed, 766 insertions(+), 100 deletions(-) create mode 100644 pkg/epp/requestcontrol/admission.go create mode 100644 pkg/epp/requestcontrol/admission_test.go create mode 100644 pkg/epp/util/request/sheddable.go diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go index 8dac40a3c..d162c3d4b 100644 --- a/cmd/epp/runner/runner.go +++ b/cmd/epp/runner/runner.go @@ -51,6 +51,9 @@ import ( "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer" dlmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datalayer/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol" + fccontroller "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/controller" + fcregistry "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/registry" latencypredictor "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/latencypredictorasync" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics/collectors" @@ -71,11 +74,25 @@ import ( ) const ( - // enableExperimentalDatalayerV2 defines the environment variable - // used as feature flag for the pluggable data layer. + // enableExperimentalDatalayerV2 defines the environment variable used as feature flag for the pluggable data layer. enableExperimentalDatalayerV2 = "ENABLE_EXPERIMENTAL_DATALAYER_V2" + // enableExperimentalFlowControlLayer defines the environment variable used as a feature flag for the pluggable flow + // control layer. + enableExperimentalFlowControlLayer = "ENABLE_EXPERIMENTAL_FLOW_CONTROL_LAYER" ) +// TODO: this is hardcoded for POC only. This needs to be hooked up to our text-based config story. +var flowControlConfig = flowcontrol.Config{ + Controller: fccontroller.Config{}, // Use all defaults. + Registry: fcregistry.Config{ + // Define domain of accepted priority levels as this field is required. Use defaults for all optional fields. + // TODO: this should not be hardcoded. + PriorityBands: []fcregistry.PriorityBandConfig{ + {Priority: 0, PriorityName: "Default"}, + }, + }, +} + var ( grpcPort = flag.Int("grpc-port", runserver.DefaultGrpcPort, "The gRPC port used for communicating with Envoy proxy") grpcHealthPort = flag.Int("grpc-health-port", runserver.DefaultGrpcHealthPort, "The port used for gRPC liveness and readiness probes") @@ -298,7 +315,43 @@ func (r *Runner) Run(ctx context.Context) error { saturationDetector := saturationdetector.NewDetector(sdConfig, setupLog) - director := requestcontrol.NewDirectorWithConfig(datastore, scheduler, saturationDetector, r.requestControlConfig) + // --- Admission Control Initialization --- + enableFlowControl := env.GetEnvBool(enableExperimentalFlowControlLayer, false, setupLog) + var admissionController requestcontrol.AdmissionController + if enableFlowControl { + setupLog.Info("Initializing experimental Flow Control layer") + fcCfg, err := flowControlConfig.ValidateAndApplyDefaults() + if err != nil { + setupLog.Error(err, "failed to initialize Flow Control layer") + return fmt.Errorf("invalid Flow Control config: %w", err) + } + + registry, err := fcregistry.NewFlowRegistry(fcCfg.Registry, setupLog) + if err != nil { + return fmt.Errorf("failed to initialize Flow Registry: %w", err) + } + fc, err := fccontroller.NewFlowController( + ctx, + fcCfg.Controller, + registry, + saturationDetector, + setupLog, + ) + if err != nil { + return fmt.Errorf("failed to initialize Flow Controller: %w", err) + } + go registry.Run(ctx) + admissionController = requestcontrol.NewFlowControlAdmissionController(saturationDetector, fc) + } else { + setupLog.Info("Experimental Flow Control layer is disabled, using legacy admission control") + admissionController = requestcontrol.NewLegacyAdmissionController(saturationDetector) + } + + director := requestcontrol.NewDirectorWithConfig( + datastore, + scheduler, + admissionController, + r.requestControlConfig) // --- Setup ExtProc Server Runner --- serverRunner := &runserver.ExtProcServerRunner{ diff --git a/pkg/epp/handlers/request.go b/pkg/epp/handlers/request.go index 7f8122195..0e04289a7 100644 --- a/pkg/epp/handlers/request.go +++ b/pkg/epp/handlers/request.go @@ -29,6 +29,13 @@ import ( errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error" ) +const ( + // defaultFairnessID is the default fairness ID used when no ID is provided in the request. + // This ensures that requests without explicit fairness identifiers are still grouped and managed by the Flow Control + // system. + defaultFairnessID = "default-flow" +) + func (s *StreamingServer) HandleRequestHeaders(reqCtx *RequestContext, req *extProcPb.ProcessingRequest_RequestHeaders) error { reqCtx.RequestReceivedTimestamp = time.Now() @@ -80,6 +87,11 @@ func (s *StreamingServer) HandleRequestHeaders(reqCtx *RequestContext, req *extP delete(reqCtx.Request.Headers, header.Key) } } + + if reqCtx.FairnessID == "" { + reqCtx.FairnessID = defaultFairnessID + } + return nil } diff --git a/pkg/epp/handlers/request_test.go b/pkg/epp/handlers/request_test.go index 4ae207803..a3ef90cb4 100644 --- a/pkg/epp/handlers/request_test.go +++ b/pkg/epp/handlers/request_test.go @@ -21,6 +21,7 @@ import ( configPb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" + "github.com/stretchr/testify/assert" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata" ) @@ -66,3 +67,32 @@ func TestHandleRequestHeaders(t *testing.T) { t.Errorf("expected fairness ID header to be removed from request headers, but it was not") } } + +func TestHandleRequestHeaders_DefaultFairnessID(t *testing.T) { + t.Parallel() + + server := &StreamingServer{} + reqCtx := &RequestContext{ + Request: &Request{ + Headers: make(map[string]string), + }, + } + + req := &extProcPb.ProcessingRequest_RequestHeaders{ + RequestHeaders: &extProcPb.HttpHeaders{ + Headers: &configPb.HeaderMap{ + Headers: []*configPb.HeaderValue{ + { + Key: "x-test-header", + Value: "test-value", + }, + }, + }, + EndOfStream: false, + }, + } + + err := server.HandleRequestHeaders(reqCtx, req) + assert.NoError(t, err, "expected no error") + assert.Equal(t, defaultFairnessID, reqCtx.FairnessID, "expected fairness ID to be defaulted") +} diff --git a/pkg/epp/requestcontrol/admission.go b/pkg/epp/requestcontrol/admission.go new file mode 100644 index 000000000..383d2844a --- /dev/null +++ b/pkg/epp/requestcontrol/admission.go @@ -0,0 +1,219 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package requestcontrol + +import ( + "context" + "time" + + "sigs.k8s.io/controller-runtime/pkg/log" + + backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers" + errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error" + logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging" + requtil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/request" +) + +// AdmissionController defines the interface for making admission control decisions. +// Implementations of this interface determine whether an incoming inference request should be accepted or rejected +// based on various criteria such as system load, fairness, priority, and available capacity. +type AdmissionController interface { + // Admit determines if a request should be admitted. + // It is called by the Director for each incoming request. + // + // Args: + // ctx: The request context, carrying deadlines, cancellation signals, and logger. + // reqCtx: The handlers.RequestContext containing details about the incoming request. + // candidatePods: A list of potential backend pods that can serve the request. + // priority: The priority level of the request, as determined by the InferenceObjective. + // + // Returns: + // - nil: If the request is admitted and should proceed to scheduling. + // - errutil.Error: If the request is rejected. + Admit( + ctx context.Context, + reqCtx *handlers.RequestContext, + candidatePods []backendmetrics.PodMetrics, + priority int, + ) error +} + +// saturationDetector defines the minimal interface required for checking if the backend pool is saturated. +type saturationDetector interface { + IsSaturated(ctx context.Context, candidatePods []backendmetrics.PodMetrics) bool +} + +// flowController defines the minimal interface required by FlowControlAdmissionController for enqueuing requests and +// waiting for an admission outcome. +type flowController interface { + EnqueueAndWait(req types.FlowControlRequest) (types.QueueOutcome, error) +} + +// rejectIfSheddableAndSaturated checks if a request should be immediately rejected because it's sheddable +// (priority < 0) and the system is saturated. +func rejectIfSheddableAndSaturated( + ctx context.Context, + sd saturationDetector, + reqCtx *handlers.RequestContext, + candidatePods []backendmetrics.PodMetrics, + priority int, +) error { + if requtil.IsSheddable(priority) { + logger := log.FromContext(ctx) + if sd.IsSaturated(ctx, candidatePods) { + logger.V(logutil.TRACE).Info("Request rejected: system saturated and request is sheddable", + "requestID", reqCtx.SchedulingRequest.RequestId) + return errutil.Error{ + Code: errutil.InferencePoolResourceExhausted, + Msg: "system saturated, sheddable request dropped", + } + } + } + return nil +} + +// --- LegacyAdmissionController --- + +// LegacyAdmissionController implements saturation-based admission control. +// It rejects sheddable requests (priority < 0) if the saturationDetector indicates that the system is currently +// saturated. Non-sheddable requests always bypass the saturation check. +type LegacyAdmissionController struct { + saturationDetector saturationDetector +} + +// NewLegacyAdmissionController creates a new LegacyAdmissionController. +func NewLegacyAdmissionController(sd saturationDetector) *LegacyAdmissionController { + return &LegacyAdmissionController{saturationDetector: sd} +} + +// Admit implements the AdmissionController interface for the legacy strategy. +// It checks for saturation only for requests with priority < 0. +func (lac *LegacyAdmissionController) Admit( + ctx context.Context, + reqCtx *handlers.RequestContext, + candidatePods []backendmetrics.PodMetrics, + priority int, +) error { + logger := log.FromContext(ctx) + logger.V(logutil.TRACE).Info("Executing LegacyAdmissionController", + "priority", priority, "fairnessID", reqCtx.FairnessID) + if err := rejectIfSheddableAndSaturated(ctx, lac.saturationDetector, reqCtx, candidatePods, priority); err != nil { + return err + } + logger.V(logutil.TRACE).Info("Request admitted", "requestID", reqCtx.SchedulingRequest.RequestId) + return nil +} + +// --- FlowControlAdmissionController --- + +// FlowControlAdmissionController delegates admission decisions to the Flow Control layer. +// It first checks if the request is sheddable and the system is saturated, rejecting immediately if both conditions are +// true. Otherwise, it uses the provided flowController to enqueue the request and await an outcome. +type FlowControlAdmissionController struct { + saturationDetector saturationDetector + flowController flowController +} + +// NewFlowControlAdmissionController creates a new FlowControlAdmissionController. +// It requires a SaturationDetector and a flowController instance. +func NewFlowControlAdmissionController(sd saturationDetector, fc flowController) *FlowControlAdmissionController { + return &FlowControlAdmissionController{ + saturationDetector: sd, + flowController: fc, + } +} + +// Admit implements the AdmissionController interface by checking for saturation on sheddable requests first, then +// deferring to the Flow Control system. +func (fcac *FlowControlAdmissionController) Admit( + ctx context.Context, + reqCtx *handlers.RequestContext, + candidatePods []backendmetrics.PodMetrics, + priority int, +) error { + logger := log.FromContext(ctx) + logger.V(logutil.TRACE).Info("Executing FlowControlAdmissionController", + "requestID", reqCtx.SchedulingRequest.RequestId, "priority", priority, "fairnessID", reqCtx.FairnessID) + if err := rejectIfSheddableAndSaturated(ctx, fcac.saturationDetector, reqCtx, candidatePods, priority); err != nil { + return err + } + + logger.V(logutil.TRACE).Info("Request proceeding to flow control", "requestID", reqCtx.SchedulingRequest.RequestId) + + fcReq := &flowControlRequest{ + ctx: ctx, + requestID: reqCtx.SchedulingRequest.RequestId, + fairnessID: reqCtx.FairnessID, + priority: priority, + requestByteSize: uint64(reqCtx.RequestSize), + candidatePods: candidatePods, + } + + outcome, err := fcac.flowController.EnqueueAndWait(fcReq) + logger.V(logutil.DEBUG).Info("Flow control outcome", + "requestID", reqCtx.SchedulingRequest.RequestId, "outcome", outcome, "error", err) + return translateFlowControlOutcome(outcome, err) +} + +// flowControlRequest is an adapter that implements the types.FlowControlRequest interface. +type flowControlRequest struct { + ctx context.Context + requestID string + fairnessID string + priority int + requestByteSize uint64 + candidatePods []backendmetrics.PodMetrics +} + +var _ types.FlowControlRequest = &flowControlRequest{} + +func (r *flowControlRequest) Context() context.Context { return r.ctx } +func (r *flowControlRequest) ID() string { return r.requestID } +func (r *flowControlRequest) InitialEffectiveTTL() time.Duration { return 0 } // Use controller default. +func (r *flowControlRequest) ByteSize() uint64 { return r.requestByteSize } +func (r *flowControlRequest) CandidatePodsForScheduling() []backendmetrics.PodMetrics { + return r.candidatePods +} +func (r *flowControlRequest) FlowKey() types.FlowKey { + return types.FlowKey{ID: r.fairnessID, Priority: r.priority} +} + +// translateFlowControlOutcome maps the context-rich outcome of the Flow Control layer to the public errutil.Error +// contract used by the Director. +func translateFlowControlOutcome(outcome types.QueueOutcome, err error) error { + msg := "request rejected by flow control" + if err != nil { + msg = err.Error() + } + + switch outcome { + case types.QueueOutcomeDispatched: + return nil + case types.QueueOutcomeRejectedCapacity: + return errutil.Error{Code: errutil.InferencePoolResourceExhausted, Msg: msg} + case types.QueueOutcomeEvictedTTL: + return errutil.Error{Code: errutil.ServiceUnavailable, Msg: "request timed out in queue: " + msg} + case types.QueueOutcomeEvictedContextCancelled: + return errutil.Error{Code: errutil.ServiceUnavailable, Msg: "client disconnected: " + msg} + case types.QueueOutcomeRejectedOther, types.QueueOutcomeEvictedOther: + return errutil.Error{Code: errutil.Internal, Msg: "internal flow control error: " + msg} + default: + return errutil.Error{Code: errutil.Internal, Msg: "unhandled flow control outcome: " + msg} + } +} diff --git a/pkg/epp/requestcontrol/admission_test.go b/pkg/epp/requestcontrol/admission_test.go new file mode 100644 index 000000000..002c50f06 --- /dev/null +++ b/pkg/epp/requestcontrol/admission_test.go @@ -0,0 +1,282 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package requestcontrol + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" + fctypes "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers" + schedulingtypes "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types" + errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error" + logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging" +) + +// --- Mocks --- + +type mockSaturationDetector struct { + isSaturated bool +} + +func (m *mockSaturationDetector) IsSaturated(_ context.Context, _ []backendmetrics.PodMetrics) bool { + return m.isSaturated +} + +type mockFlowController struct { + outcome fctypes.QueueOutcome + err error + called bool +} + +func (m *mockFlowController) EnqueueAndWait(_ fctypes.FlowControlRequest) (fctypes.QueueOutcome, error) { + m.called = true + return m.outcome, m.err +} + +func TestLegacyAdmissionController_Admit(t *testing.T) { + t.Parallel() + ctx := logutil.NewTestLoggerIntoContext(context.Background()) + candidatePods := []backendmetrics.PodMetrics{} + reqCtx := &handlers.RequestContext{ + SchedulingRequest: &schedulingtypes.LLMRequest{RequestId: "test-req"}, + } + + testCases := []struct { + name string + priority int + isSaturated bool + expectErr bool + expectErrCode string + expectErrSubstr string + }{ + { + name: "non_sheddable_saturated_admit", + priority: 0, + isSaturated: true, + expectErr: false, + }, + { + name: "sheddable_not_saturated_admit", + priority: -1, + isSaturated: false, + expectErr: false, + }, + { + name: "sheddable_saturated_reject", + priority: -1, + isSaturated: true, + expectErr: true, + expectErrCode: errutil.InferencePoolResourceExhausted, + expectErrSubstr: "system saturated, sheddable request dropped", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + saturationDetector := &mockSaturationDetector{isSaturated: tc.isSaturated} + ac := NewLegacyAdmissionController(saturationDetector) + + err := ac.Admit(ctx, reqCtx, candidatePods, tc.priority) + + if !tc.expectErr { + assert.NoError(t, err, "Admit() should not have returned an error for scenario: %s", tc.name) + } else { + require.Error(t, err, "Admit() should have returned an error for scenario: %s", tc.name) + var e errutil.Error + if assert.ErrorAs(t, err, &e, "error should be of type errutil.Error") { + assert.Equal(t, tc.expectErrCode, e.Code, "incorrect error code for scenario: %s", tc.name) + assert.Contains(t, e.Msg, tc.expectErrSubstr, "incorrect error message substring for scenario: %s", tc.name) + } + } + }) + } +} + +func TestFlowControlRequestAdapter(t *testing.T) { + t.Parallel() + ctx := context.Background() + candidatePods := []backendmetrics.PodMetrics{&backendmetrics.FakePodMetrics{}} + + testCases := []struct { + name string + requestID string + fairnessID string + priority int + requestByteSize uint64 + expectFlowKey fctypes.FlowKey + }{ + { + name: "simple", + requestID: "req-1", + fairnessID: "flow-1", + priority: 10, + requestByteSize: 1024, + expectFlowKey: fctypes.FlowKey{ID: "flow-1", Priority: 10}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + fcReq := &flowControlRequest{ + ctx: ctx, + requestID: tc.requestID, + fairnessID: tc.fairnessID, + priority: tc.priority, + requestByteSize: tc.requestByteSize, + candidatePods: candidatePods, + } + + assert.Equal(t, ctx, fcReq.Context(), "Context() mismatch") + assert.Equal(t, tc.requestID, fcReq.ID(), "ID() mismatch") + assert.Equal(t, tc.requestByteSize, fcReq.ByteSize(), "ByteSize() mismatch") + assert.Equal(t, candidatePods, fcReq.CandidatePodsForScheduling(), "CandidatePodsForScheduling() mismatch") + assert.Equal(t, tc.expectFlowKey, fcReq.FlowKey(), "FlowKey() mismatch") + assert.Zero(t, fcReq.InitialEffectiveTTL(), "InitialEffectiveTTL() should be zero") + }) + } +} +func TestFlowControlAdmissionController_Admit(t *testing.T) { + t.Parallel() + ctx := logutil.NewTestLoggerIntoContext(context.Background()) + candidatePods := []backendmetrics.PodMetrics{} + + reqCtx := &handlers.RequestContext{ + SchedulingRequest: &schedulingtypes.LLMRequest{RequestId: "test-req"}, + } + + testCases := []struct { + name string + priority int + isSaturated bool + fcOutcome fctypes.QueueOutcome + fcErr error + expectErr bool + expectErrCode string + expectErrSubstr string + expectFCSkipped bool + }{ + { + name: "sheddable_saturated_reject", + priority: -1, + isSaturated: true, + expectErr: true, + expectErrCode: errutil.InferencePoolResourceExhausted, + expectErrSubstr: "system saturated, sheddable request dropped", + expectFCSkipped: true, + }, + { + name: "sheddable_not_saturated_dispatch", + priority: -1, + isSaturated: false, + fcOutcome: fctypes.QueueOutcomeDispatched, + expectErr: false, + }, + { + name: "non_sheddable_saturated_dispatch", + priority: 0, + isSaturated: true, + fcOutcome: fctypes.QueueOutcomeDispatched, + expectErr: false, + }, + { + name: "fc_reject_capacity", + priority: 0, + fcOutcome: fctypes.QueueOutcomeRejectedCapacity, + expectErr: true, + expectErrCode: errutil.InferencePoolResourceExhausted, + expectErrSubstr: "request rejected by flow control", + }, + { + name: "fc_evict_ttl", + priority: 0, + fcOutcome: fctypes.QueueOutcomeEvictedTTL, + fcErr: errors.New("timeout"), + expectErr: true, + expectErrCode: errutil.ServiceUnavailable, + expectErrSubstr: "request timed out in queue: timeout", + }, + { + name: "fc_evict_context_cancelled", + priority: 0, + fcOutcome: fctypes.QueueOutcomeEvictedContextCancelled, + expectErr: true, + expectErrCode: errutil.ServiceUnavailable, + expectErrSubstr: "client disconnected", + }, + { + name: "fc_reject_other", + priority: 0, + fcOutcome: fctypes.QueueOutcomeRejectedOther, + expectErr: true, + expectErrCode: errutil.Internal, + expectErrSubstr: "internal flow control error", + }, + { + name: "fc_evict_other", + priority: 0, + fcOutcome: fctypes.QueueOutcomeEvictedOther, + fcErr: errors.New("internal error"), + expectErr: true, + expectErrCode: errutil.Internal, + expectErrSubstr: "internal flow control error: internal error", + }, + { + name: "fc_unhandled_outcome", + priority: 0, + fcOutcome: fctypes.QueueOutcomeNotYetFinalized, + expectErr: true, + expectErrCode: errutil.Internal, + expectErrSubstr: "unhandled flow control outcome", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + sd := &mockSaturationDetector{isSaturated: tc.isSaturated} + fc := &mockFlowController{outcome: tc.fcOutcome, err: tc.fcErr} + ac := NewFlowControlAdmissionController(sd, fc) + + err := ac.Admit(ctx, reqCtx, candidatePods, tc.priority) + + if tc.expectFCSkipped { + assert.False(t, fc.called, "FlowController should not have been called for scenario: %s", tc.name) + } else { + assert.True(t, fc.called, "FlowController should have been called for scenario: %s", tc.name) + } + + if !tc.expectErr { + assert.NoError(t, err, "Admit() returned an unexpected error for scenario: %s", tc.name) + } else { + require.Error(t, err, "Admit() should have returned an error for scenario: %s", tc.name) + var e errutil.Error + if assert.ErrorAs(t, err, &e, "error should be of type errutil.Error") { + assert.Equal(t, tc.expectErrCode, e.Code, "incorrect error code for scenario: %s", tc.name) + assert.Contains(t, e.Msg, tc.expectErrSubstr, "incorrect error message substring for scenario: %s", tc.name) + } + } + }) + } +} diff --git a/pkg/epp/requestcontrol/director.go b/pkg/epp/requestcontrol/director.go index 305031ecb..0cf1ee5a8 100644 --- a/pkg/epp/requestcontrol/director.go +++ b/pkg/epp/requestcontrol/director.go @@ -148,17 +148,17 @@ type Scheduler interface { Schedule(ctx context.Context, request *schedulingtypes.LLMRequest, candidatePods []schedulingtypes.Pod) (result *schedulingtypes.SchedulingResult, err error) } -// SaturationDetector provides a signal indicating whether the backends are considered saturated. -type SaturationDetector interface { - IsSaturated(ctx context.Context, candidatePods []backendmetrics.PodMetrics) bool -} - // NewDirectorWithConfig creates a new Director instance with all dependencies. -func NewDirectorWithConfig(datastore datastore.Datastore, scheduler Scheduler, saturationDetector SaturationDetector, config *Config) *Director { +func NewDirectorWithConfig( + datastore datastore.Datastore, + scheduler Scheduler, + admissionController AdmissionController, + config *Config, +) *Director { return &Director{ datastore: datastore, scheduler: scheduler, - saturationDetector: saturationDetector, + admissionController: admissionController, preRequestPlugins: config.preRequestPlugins, postResponsePlugins: config.postResponsePlugins, postResponseChunkPlugins: config.postResponseChunkPlugins, @@ -167,11 +167,19 @@ func NewDirectorWithConfig(datastore datastore.Datastore, scheduler Scheduler, s } } -// Director orchestrates the request handling flow, including scheduling. +// Director orchestrates the request handling flow after initial parsing by the handler. +// Its responsibilities include: +// - Retrieving request metadata and relevant objectives. +// - Determining candidate pods. +// - Performing admission control via the AdmissionController. +// - Scheduling the request to target pod(s) via the Scheduler. +// - Running PreRequest plugins. +// - Preparing the request context for the Envoy ext_proc filter to route the request. +// - Running PostResponse plugins. type Director struct { - datastore datastore.Datastore + datastore Datastore scheduler Scheduler - saturationDetector SaturationDetector + admissionController AdmissionController preRequestPlugins []PreRequest postResponsePlugins []PostResponse postResponseChunkPlugins []PostResponseChunk @@ -267,24 +275,16 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo return reqCtx, errutil.Error{Code: errutil.ServiceUnavailable, Msg: "failed to find candidate pods for serving the request"} } - // TODO - // 1. Create datastore request object - // 2. Read/Write and maybe Drop to it during Schedule() and admitRequest() - // 3. Add it to the scheduled pod's RequestPriorityQueue - // 4. Drop from pod's RequestPriorityQueue and datastore global map when request is fully processed - - // + if err := d.admissionController.Admit(ctx, reqCtx, candidatePods, *infObjective.Spec.Priority); err != nil { + logger.V(logutil.DEFAULT).Info("Request rejected by admission control", "error", err) + return reqCtx, err + } result, err := d.scheduler.Schedule(ctx, reqCtx.SchedulingRequest, d.toSchedulerPodMetrics(candidatePods)) if err != nil { return reqCtx, errutil.Error{Code: errutil.InferencePoolResourceExhausted, Msg: fmt.Errorf("failed to find target pod: %w", err).Error()} } - // Admission Control check - if err := d.admitRequest(ctx, candidatePods, reqCtx.SchedulingRequest, *infObjective.Spec.Priority, reqCtx.FairnessID); err != nil { - return reqCtx, err - } - // --- 4. Prepare Request (Populates RequestContext and call PreRequest plugins) --- // Insert target endpoint to instruct Envoy to route requests to the specified target pod and attach the port number. // Invoke PreRequest registered plugins. @@ -296,33 +296,6 @@ func (d *Director) HandleRequest(ctx context.Context, reqCtx *handlers.RequestCo return reqCtx, nil } -// admitRequest handles admission control to decide whether or not to accept the request -// based on the request priority and system saturation state. -func (d *Director) admitRequest(ctx context.Context, candidatePods []backendmetrics.PodMetrics, request *schedulingtypes.LLMRequest, requestPriority int, fairnessID string) error { - logger := log.FromContext(ctx) - - logger.V(logutil.DEBUG).Info("Entering Flow Control", "priority", requestPriority, "fairnessID", fairnessID) - - // This will be removed in favor of a more robust implementation (Flow Control) in the very near future. - // TODO: Make this a configurable value. - // Tracking issue https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/1347 - if requestPriority >= 0 { - logger.V(logutil.DEBUG).Info("Non-sheddable request bypassing saturation check.") - return nil - } else { - logger.V(logutil.DEBUG).Info("Sheddable request subject to saturation check.") - } - - if d.saturationDetector.IsSaturated(ctx, candidatePods) || !request.HasValidPod { // Assuming non-nil Saturation Detector - return errutil.Error{ - Code: errutil.InferencePoolResourceExhausted, - Msg: "system saturated, sheddable request dropped", - } - } - - return nil -} - // getCandidatePodsForScheduling gets the list of relevant endpoints for the scheduling cycle from the datastore. // according to EPP protocol, if "x-gateway-destination-endpoint-subset" is set on the request metadata and specifies // a subset of endpoints, only these endpoints will be considered as candidates for the scheduler. diff --git a/pkg/epp/requestcontrol/director_test.go b/pkg/epp/requestcontrol/director_test.go index 61a8b31be..041cf498b 100644 --- a/pkg/epp/requestcontrol/director_test.go +++ b/pkg/epp/requestcontrol/director_test.go @@ -55,12 +55,17 @@ import ( // --- Mocks --- -type mockSaturationDetector struct { - isSaturated bool +type mockAdmissionController struct { + admitErr error } -func (m *mockSaturationDetector) IsSaturated(_ context.Context, _ []backendmetrics.PodMetrics) bool { - return m.isSaturated +func (m *mockAdmissionController) Admit( + _ context.Context, + _ *handlers.RequestContext, + _ []backendmetrics.PodMetrics, + _ int, +) error { + return m.admitErr } // Updated mock scheduler to handle the new Schedule method signature @@ -129,10 +134,14 @@ type mockDatastore struct { func (ds *mockDatastore) PoolSet(ctx context.Context, reader client.Reader, pool *v1.InferencePool) error { return nil } -func (ds *mockDatastore) PoolGet() (*v1.InferencePool, error) { return nil, nil } -func (ds *mockDatastore) PoolHasSynced() bool { return true } -func (ds *mockDatastore) PoolLabelsMatch(podLabels map[string]string) bool { return true } -func (ds *mockDatastore) ObjectiveGet(_ string) *v1alpha2.InferenceObjective { return nil } +func (ds *mockDatastore) PoolGet() (*v1.InferencePool, error) { + return nil, nil +} +func (ds *mockDatastore) PoolHasSynced() bool { return true } +func (ds *mockDatastore) PoolLabelsMatch(podLabels map[string]string) bool { return true } +func (ds *mockDatastore) ObjectiveGet(_ string) *v1alpha2.InferenceObjective { + return nil +} func (ds *mockDatastore) PodList(predicate func(backendmetrics.PodMetrics) bool) []backendmetrics.PodMetrics { res := []backendmetrics.PodMetrics{} for _, pod := range ds.pods { @@ -337,24 +346,24 @@ func TestDirector_HandleRequest(t *testing.T) { } tests := []struct { - name string - reqBodyMap map[string]any - mockSaturationDetector *mockSaturationDetector - inferenceObjectiveName string - schedulerMockSetup func(m *mockScheduler) - predictorMockSetup func(m *mockPredictor) // NEW: Add predictor setup - wantErrCode string // Expected errutil code string - wantReqCtx *handlers.RequestContext // Fields to check in the returned RequestContext - wantMutatedBodyModel string // Expected model in reqCtx.Request.Body after PostDispatch - targetModelName string // Expected model name after target model resolution + name string + reqBodyMap map[string]any + mockAdmissionController *mockAdmissionController + inferenceObjectiveName string + schedulerMockSetup func(m *mockScheduler) + predictorMockSetup func(m *mockPredictor) // NEW: Add predictor setup + wantErrCode string // Expected errutil code string + wantReqCtx *handlers.RequestContext // Fields to check in the returned RequestContext + wantMutatedBodyModel string // Expected model in reqCtx.Request.Body after PostDispatch + targetModelName string // Expected model name after target model resolution }{ { - name: "successful completions request (critical, saturation ignored)", + name: "successful completions request", reqBodyMap: map[string]any{ "model": model, "prompt": "critical prompt", }, - mockSaturationDetector: &mockSaturationDetector{isSaturated: true}, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = defaultSuccessfulScheduleResults }, @@ -378,7 +387,7 @@ func TestDirector_HandleRequest(t *testing.T) { "model": modelSheddable, "prompt": "test prompt", }, - mockSaturationDetector: &mockSaturationDetector{isSaturated: true}, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = defaultSuccessfulScheduleResults }, @@ -405,7 +414,39 @@ func TestDirector_HandleRequest(t *testing.T) { wantErrCode: errutil.InferencePoolResourceExhausted, }, { - name: "successful chat completions request (default critical, saturation ignored)", + name: "non-critical request dropped due to saturation", + reqBodyMap: map[string]any{ + "model": modelSheddable, + "prompt": "test prompt", + }, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, + schedulerMockSetup: func(m *mockScheduler) { + m.scheduleResults = defaultSuccessfulScheduleResults + }, + wantReqCtx: &handlers.RequestContext{ + ObjectiveKey: objectiveNameSheddable, + TargetModelName: model, + TargetPod: &backend.Pod{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "pod1"}, + Address: "192.168.1.100", + RunningRequests: &datalayer.RequestPriorityQueue{}, // Empty but initialized + }, + TargetEndpoint: "192.168.1.100:8000,192.168.2.100:8000,192.168.4.100:8000", + }, + predictorMockSetup: func(m *mockPredictor) { + // Mock prediction that violates SLOs + m.PredictFunc = func(ctx context.Context, req latencypredictor.PredictionRequest) (*latencypredictor.PredictionResponse, error) { + return &latencypredictor.PredictionResponse{ + TTFT: 150.0, // Above SLO of 100 + TPOT: 80.0, // Above SLO of 50 + }, nil + } + }, + inferenceObjectiveName: objectiveNameSheddable, + wantErrCode: errutil.InferencePoolResourceExhausted, + }, + { + name: "successful chat completions request", reqBodyMap: map[string]any{ "model": model, "messages": []any{ @@ -415,7 +456,7 @@ func TestDirector_HandleRequest(t *testing.T) { }, }, }, - mockSaturationDetector: &mockSaturationDetector{isSaturated: true}, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = defaultSuccessfulScheduleResults }, @@ -436,7 +477,7 @@ func TestDirector_HandleRequest(t *testing.T) { "model": model, // Critical model "prompt": "test prompt", }, - mockSaturationDetector: &mockSaturationDetector{isSaturated: true}, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = defaultSuccessfulScheduleResults }, @@ -462,7 +503,38 @@ func TestDirector_HandleRequest(t *testing.T) { targetModelName: model, }, { - name: "successful chat completions request with multiple messages (critical, saturation ignored)", + name: "critical request succeeds despite saturation", + reqBodyMap: map[string]any{ + "model": model, // Critical model + "prompt": "test prompt", + }, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, + schedulerMockSetup: func(m *mockScheduler) { + m.scheduleResults = defaultSuccessfulScheduleResults + }, + predictorMockSetup: func(m *mockPredictor) { + // Mock prediction that violates SLOs + m.PredictFunc = func(ctx context.Context, req latencypredictor.PredictionRequest) (*latencypredictor.PredictionResponse, error) { + return &latencypredictor.PredictionResponse{ + TTFT: 150.0, // Above SLO of 100 + TPOT: 80.0, // Above SLO of 50 + }, nil + } + }, + wantReqCtx: &handlers.RequestContext{ + TargetModelName: model, + TargetPod: &backend.Pod{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "pod1"}, + Address: "192.168.1.100", + RunningRequests: &datalayer.RequestPriorityQueue{}, // Empty but initialized + }, + TargetEndpoint: "192.168.1.100:8000,192.168.2.100:8000,192.168.4.100:8000", + }, + wantMutatedBodyModel: model, + targetModelName: model, + }, + { + name: "successful chat completions request with multiple messages", reqBodyMap: map[string]any{ "model": model, "messages": []any{ @@ -476,6 +548,7 @@ func TestDirector_HandleRequest(t *testing.T) { }, }, }, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = defaultSuccessfulScheduleResults }, @@ -499,7 +572,7 @@ func TestDirector_HandleRequest(t *testing.T) { "model": modelSheddable, "prompt": "sheddable prompt", }, - mockSaturationDetector: &mockSaturationDetector{isSaturated: false}, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = defaultSuccessfulScheduleResults }, @@ -507,9 +580,8 @@ func TestDirector_HandleRequest(t *testing.T) { ObjectiveKey: objectiveNameSheddable, TargetModelName: modelSheddable, TargetPod: &backend.Pod{ - NamespacedName: types.NamespacedName{Namespace: "default", Name: "pod1"}, - Address: "192.168.1.100", - RunningRequests: &datalayer.RequestPriorityQueue{}, // Empty but initialized + NamespacedName: types.NamespacedName{Namespace: "default", Name: "pod1"}, + Address: "192.168.1.100", }, TargetEndpoint: "192.168.1.100:8000,192.168.2.100:8000,192.168.4.100:8000", }, @@ -523,7 +595,7 @@ func TestDirector_HandleRequest(t *testing.T) { "model": modelWithResolvedTarget, "prompt": "prompt for target resolution", }, - mockSaturationDetector: &mockSaturationDetector{isSaturated: false}, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = defaultSuccessfulScheduleResults }, @@ -561,28 +633,27 @@ func TestDirector_HandleRequest(t *testing.T) { "model": "food-review-1", "prompt": "test prompt", }, - mockSaturationDetector: &mockSaturationDetector{isSaturated: false}, - inferenceObjectiveName: "food-review-1", - targetModelName: "food-review-1", + mockAdmissionController: &mockAdmissionController{admitErr: nil}, + inferenceObjectiveName: "food-review-1", + targetModelName: "food-review-1", }, { - name: "request dropped (sheddable, saturated)", + name: "request rejected by admission controller", reqBodyMap: map[string]any{ "model": modelSheddable, "prompt": "sheddable prompt", }, - inferenceObjectiveName: objectiveNameSheddable, - mockSaturationDetector: &mockSaturationDetector{isSaturated: true}, - wantErrCode: errutil.InferencePoolResourceExhausted, + inferenceObjectiveName: objectiveNameSheddable, + mockAdmissionController: &mockAdmissionController{admitErr: errutil.Error{Code: errutil.InferencePoolResourceExhausted, Msg: "simulated admission rejection"}}, + wantErrCode: errutil.InferencePoolResourceExhausted, }, { - name: "model not found, expect err", - reqBodyMap: map[string]any{"prompt": "p"}, - mockSaturationDetector: &mockSaturationDetector{isSaturated: false}, - wantErrCode: errutil.BadRequest, + name: "model not found, expect err", + reqBodyMap: map[string]any{"prompt": "p"}, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, + wantErrCode: errutil.BadRequest, }, - { name: "prompt or messages not found, expect err", reqBodyMap: map[string]any{"model": model}, @@ -602,6 +673,7 @@ func TestDirector_HandleRequest(t *testing.T) { "model": model, "prompt": "prompt that causes scheduler error", }, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleErr = errors.New("simulated scheduler failure") }, @@ -614,6 +686,7 @@ func TestDirector_HandleRequest(t *testing.T) { "model": model, "prompt": "prompt for nil,nil scheduler return", }, + mockAdmissionController: &mockAdmissionController{admitErr: nil}, schedulerMockSetup: func(m *mockScheduler) { m.scheduleResults = nil m.scheduleErr = nil @@ -636,9 +709,9 @@ func TestDirector_HandleRequest(t *testing.T) { if test.predictorMockSetup != nil { mockPred = &mockPredictor{} test.predictorMockSetup(mockPred) - director = NewDirectorWithConfig(ds, mockSched, test.mockSaturationDetector, NewConfig()) + director = NewDirectorWithConfig(ds, mockSched, test.mockAdmissionController, NewConfig()) } else { - director = NewDirectorWithConfig(ds, mockSched, test.mockSaturationDetector, NewConfig()) + director = NewDirectorWithConfig(ds, mockSched, test.mockAdmissionController, NewConfig()) } reqCtx := &handlers.RequestContext{ @@ -766,7 +839,7 @@ func TestGetCandidatePodsForScheduling(t *testing.T) { ds := &mockDatastore{pods: testInput} for _, test := range tests { t.Run(test.name, func(t *testing.T) { - director := NewDirectorWithConfig(ds, &mockScheduler{}, &mockSaturationDetector{}, NewConfig()) + director := NewDirectorWithConfig(ds, &mockScheduler{}, &mockAdmissionController{}, NewConfig()) got := director.getCandidatePodsForScheduling(context.Background(), test.metadata) @@ -835,7 +908,7 @@ func TestDirector_HandleResponse(t *testing.T) { ctx := logutil.NewTestLoggerIntoContext(context.Background()) ds := datastore.NewDatastore(t.Context(), nil) mockSched := &mockScheduler{} - director := NewDirectorWithConfig(ds, mockSched, nil, NewConfig().WithPostResponsePlugins(pr1)) + director := NewDirectorWithConfig(ds, mockSched, &mockAdmissionController{}, NewConfig().WithPostResponsePlugins(pr1)) reqCtx := &handlers.RequestContext{ Request: &handlers.Request{ diff --git a/pkg/epp/server/runserver.go b/pkg/epp/server/runserver.go index b945985ce..7c84ed59b 100644 --- a/pkg/epp/server/runserver.go +++ b/pkg/epp/server/runserver.go @@ -43,6 +43,7 @@ import ( "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers" latencypredictor "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/latencypredictorasync" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/requestcontrol" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/saturationdetector" ) // ExtProcServerRunner provides methods to manage an external process server. @@ -57,7 +58,7 @@ type ExtProcServerRunner struct { RefreshPrometheusMetricsInterval time.Duration MetricsStalenessThreshold time.Duration Director *requestcontrol.Director - SaturationDetector requestcontrol.SaturationDetector + SaturationDetector *saturationdetector.Detector UseExperimentalDatalayerV2 bool // Pluggable data layer feature flag LatencyPredictor latencypredictor.PredictorInterface diff --git a/pkg/epp/util/request/sheddable.go b/pkg/epp/util/request/sheddable.go new file mode 100644 index 000000000..c2f32c1f2 --- /dev/null +++ b/pkg/epp/util/request/sheddable.go @@ -0,0 +1,22 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package request + +// IsSheddable determines if a request is considered sheddable based on its priority. +func IsSheddable(priority int) bool { + return priority < 0 +} diff --git a/test/integration/epp/hermetic_test.go b/test/integration/epp/hermetic_test.go index 3dc42f8ba..c2e100f79 100644 --- a/test/integration/epp/hermetic_test.go +++ b/test/integration/epp/hermetic_test.go @@ -1180,7 +1180,8 @@ func BeforeSuite() func() { } detector := saturationdetector.NewDetector(sdConfig, logger.WithName("saturation-detector")) serverRunner.SaturationDetector = detector - serverRunner.Director = requestcontrol.NewDirectorWithConfig(serverRunner.Datastore, scheduler, detector, requestcontrol.NewConfig()) + admissionController := requestcontrol.NewLegacyAdmissionController(detector) + serverRunner.Director = requestcontrol.NewDirectorWithConfig(serverRunner.Datastore, scheduler, admissionController, requestcontrol.NewConfig()) serverRunner.SecureServing = false if err := serverRunner.SetupWithManager(context.Background(), mgr); err != nil { From ec45f5baee8ab58716adecf219b4d9352ef34a59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:17:36 -0700 Subject: [PATCH 092/133] chore(deps): bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#1709) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.36.0 to 1.38.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.36.0...v1.38.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc dependency-version: 1.38.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index fd8c44513..37b03d8a4 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/prometheus/prometheus v0.306.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 go.opentelemetry.io/otel/sdk v1.38.0 go.uber.org/multierr v1.11.0 @@ -51,7 +51,7 @@ require ( github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v5 v5.0.2 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect @@ -76,7 +76,7 @@ require ( github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -100,10 +100,10 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect - go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect @@ -119,7 +119,7 @@ require ( golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.37.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index e017ad17a..c39048766 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= -github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= @@ -143,8 +143,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= @@ -254,10 +254,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6h go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= @@ -268,8 +268,8 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= -go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -341,8 +341,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= -google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= From a393de056922a453326b476e4b88466007073455 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:17:43 -0700 Subject: [PATCH 093/133] chore(deps): bump sigs.k8s.io/controller-runtime from 0.22.1 to 0.22.3 (#1711) Bumps [sigs.k8s.io/controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) from 0.22.1 to 0.22.3. - [Release notes](https://github.com/kubernetes-sigs/controller-runtime/releases) - [Changelog](https://github.com/kubernetes-sigs/controller-runtime/blob/main/RELEASE.md) - [Commits](https://github.com/kubernetes-sigs/controller-runtime/compare/v0.22.1...v0.22.3) --- updated-dependencies: - dependency-name: sigs.k8s.io/controller-runtime dependency-version: 0.22.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 37b03d8a4..151e9426d 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( k8s.io/code-generator v0.34.1 k8s.io/component-base v0.34.1 k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d - sigs.k8s.io/controller-runtime v0.22.1 + sigs.k8s.io/controller-runtime v0.22.3 // Update the CONTROLLER_TOOLS_VERSION in Makefile when bumping controller-tools. sigs.k8s.io/controller-tools v0.19.0 sigs.k8s.io/gateway-api v1.4.0 diff --git a/go.sum b/go.sum index c39048766..086c0877d 100644 --- a/go.sum +++ b/go.sum @@ -387,8 +387,8 @@ k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPG k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= -sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/controller-tools v0.19.0 h1:OU7jrPPiZusryu6YK0jYSjPqg8Vhf8cAzluP9XGI5uk= sigs.k8s.io/controller-tools v0.19.0/go.mod h1:y5HY/iNDFkmFla2CfQoVb2AQXMsBk4ad84iR1PLANB0= sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= From be0cfee82f651eb29cc0b65bd0c41266ee0da0d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:17:49 -0700 Subject: [PATCH 094/133] chore(deps): bump github.com/onsi/ginkgo/v2 from 2.25.3 to 2.26.0 (#1712) Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.25.3 to 2.26.0. - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.25.3...v2.26.0) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 151e9426d..a281dede8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/onsi/ginkgo/v2 v2.25.3 + github.com/onsi/ginkgo/v2 v2.26.0 github.com/onsi/gomega v1.38.2 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 diff --git a/go.sum b/go.sum index 086c0877d..2fb99c360 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,12 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo= +github.com/gkampitakis/go-snaps v0.5.14/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -155,6 +161,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -171,6 +179,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -196,8 +208,8 @@ github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= -github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE= +github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -244,6 +256,14 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 880747c954887a5f8698c4a3e5baa0d221911f6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:59:35 -0700 Subject: [PATCH 095/133] chore(deps): bump github.com/prometheus/common from 0.66.1 to 0.67.1 (#1710) Bumps [github.com/prometheus/common](https://github.com/prometheus/common) from 0.66.1 to 0.67.1. - [Release notes](https://github.com/prometheus/common/releases) - [Changelog](https://github.com/prometheus/common/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/common/compare/v0.66.1...v0.67.1) --- updated-dependencies: - dependency-name: github.com/prometheus/common dependency-version: 0.67.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index a281dede8..9c63e3944 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/onsi/gomega v1.38.2 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.66.1 + github.com/prometheus/common v0.67.1 github.com/prometheus/prometheus v0.306.0 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/otel v1.38.0 @@ -106,13 +106,13 @@ require ( go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/mod v0.28.0 // indirect golang.org/x/net v0.44.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/term v0.35.0 // indirect golang.org/x/text v0.29.0 // indirect diff --git a/go.sum b/go.sum index 2fb99c360..1805d4281 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= +github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/prometheus/prometheus v0.306.0 h1:Q0Pvz/ZKS6vVWCa1VSgNyNJlEe8hxdRlKklFg7SRhNw= @@ -300,8 +300,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -321,8 +321,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 6573a27a6b0f3051ea094be5ff7e09bf796ae445 Mon Sep 17 00:00:00 2001 From: David Jumani Date: Tue, 14 Oct 2025 06:43:36 -0400 Subject: [PATCH 096/133] rename conformance infra namespace (#1667) --- conformance/resources/base.yaml | 42 +++++++++---------- conformance/resources/resourcename.go | 4 +- conformance/scripts/istio/Makefile | 10 ++--- .../tests/epp_unavailable_fail_open.yaml | 4 +- .../tests/gateway_following_epp_routing.yaml | 5 +-- .../httproute_invalid_inferencepool_ref.yaml | 4 +- ...ute_multiple_gateways_different_pools.yaml | 8 ++-- conformance/tests/inferencepool_accepted.yaml | 6 +-- ...ferencepool_httproute_port_validation.yaml | 12 +++--- .../inferencepool_invalid_epp_service.yaml | 6 +-- ...cepool_multiple_rules_different_pools.yaml | 4 +- .../inferencepool_resolvedrefs_condition.yaml | 8 ++-- 12 files changed, 56 insertions(+), 57 deletions(-) diff --git a/conformance/resources/base.yaml b/conformance/resources/base.yaml index a421f5006..2e1b378c3 100644 --- a/conformance/resources/base.yaml +++ b/conformance/resources/base.yaml @@ -7,16 +7,16 @@ apiVersion: v1 kind: Namespace metadata: - name: gateway-conformance-infra + name: inference-conformance-infra labels: - gateway-conformance: infra + inference-conformance: infra --- apiVersion: v1 kind: Namespace metadata: - name: gateway-conformance-app-backend + name: inference-conformance-app-backend labels: - gateway-conformance: backend + inference-conformance: backend --- # A basic Gateway resource that allows HTTPRoutes from the same namespace. # Tests can use this as a parent reference for routes that target InferencePools. @@ -24,7 +24,7 @@ apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra spec: gatewayClassName: "{GATEWAY_CLASS_NAME}" listeners: @@ -42,7 +42,7 @@ apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: conformance-secondary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra spec: gatewayClassName: "{GATEWAY_CLASS_NAME}" listeners: @@ -55,14 +55,14 @@ spec: from: All ### The following defines the essential resources for the gateway conformance test. -### All resources are created in the 'gateway-conformance-app-backend' namespace. +### All resources are created in the 'inference-conformance-app-backend' namespace. --- # Deploys a mock backend service to act as a model server. apiVersion: apps/v1 kind: Deployment metadata: name: primary-inference-model-server-deployment - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend labels: app: primary-inference-model-server spec: @@ -106,7 +106,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: secondary-inference-model-server-deployment - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend labels: app: secondary-inference-model-server spec: @@ -150,7 +150,7 @@ apiVersion: inference.networking.k8s.io/v1 kind: InferencePool metadata: name: primary-inference-pool - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: selector: matchLabels: @@ -167,7 +167,7 @@ apiVersion: v1 kind: Service metadata: name: primary-endpoint-picker-svc - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: selector: app: primary-app-backend-epp @@ -183,7 +183,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: primary-app-endpoint-picker - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend labels: app: primary-app-backend-epp spec: @@ -206,7 +206,7 @@ spec: - --pool-name - "primary-inference-pool" - --pool-namespace - - "gateway-conformance-app-backend" + - "inference-conformance-app-backend" - --v - "4" - --zap-encoder @@ -247,7 +247,7 @@ apiVersion: inference.networking.k8s.io/v1 kind: InferencePool metadata: name: secondary-inference-pool - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: selector: matchLabels: @@ -265,7 +265,7 @@ apiVersion: v1 kind: Service metadata: name: secondary-endpoint-picker-svc - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: selector: app: secondary-app-backend-epp @@ -281,7 +281,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: secondary-app-endpoint-picker - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend labels: app: secondary-app-backend-epp spec: @@ -304,7 +304,7 @@ spec: - --pool-name - "secondary-inference-pool" - --pool-namespace - - "gateway-conformance-app-backend" + - "inference-conformance-app-backend" - --v - "4" - --zap-encoder @@ -344,7 +344,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: plugins-config - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend data: conformance-plugins.yaml: | apiVersion: inference.networking.x-k8s.io/v1alpha1 @@ -361,7 +361,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: inference-model-reader - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend rules: - apiGroups: ["inference.networking.x-k8s.io"] resources: ["inferenceobjectives", "inferencepools"] @@ -377,11 +377,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: epp-to-inference-model-reader - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend subjects: - kind: ServiceAccount name: default - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend roleRef: kind: Role name: inference-model-reader diff --git a/conformance/resources/resourcename.go b/conformance/resources/resourcename.go index 4c0c7b4c2..4ac6437fe 100644 --- a/conformance/resources/resourcename.go +++ b/conformance/resources/resourcename.go @@ -19,8 +19,8 @@ package resources import "k8s.io/apimachinery/pkg/types" const ( - AppBackendNamespace = "gateway-conformance-app-backend" - InfraNamespace = "gateway-conformance-infra" + AppBackendNamespace = "inference-conformance-app-backend" + InfraNamespace = "inference-conformance-infra" PrimaryGatewayName = "conformance-primary" SecondaryGatewayName = "conformance-secondary" diff --git a/conformance/scripts/istio/Makefile b/conformance/scripts/istio/Makefile index 309d8c459..cfce3cc9c 100644 --- a/conformance/scripts/istio/Makefile +++ b/conformance/scripts/istio/Makefile @@ -3,7 +3,7 @@ GATEWAY_API_VERSION ?= v1.3.0 INFERENCE_EXTENSION_VERSION ?= v0.4.0 ISTIO_VERSION ?= 1.27-alpha.0551127f00634403cddd4634567e65a8ecc499a7 -ISTIO_HUB ?= +ISTIO_HUB ?= ISTIO_PROFILE ?= minimal # Conformance test variables @@ -72,7 +72,7 @@ apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: primary-endpoint-picker-tls - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: host: primary-endpoint-picker-svc trafficPolicy: @@ -84,7 +84,7 @@ apiVersion: networking.istio.io/v1 kind: DestinationRule metadata: name: secondary-endpoint-picker-tls - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: host: secondary-endpoint-picker-svc trafficPolicy: @@ -432,7 +432,7 @@ setup-crds: setup-gateway-api-crds setup-inference-extension-crds # Setup TLS for EPP setup-tls: @echo "Setting up TLS for EPP..." - -kubectl create namespace gateway-conformance-app-backend || true + -kubectl create namespace inference-conformance-app-backend || true $(file >/tmp/tls-destination-rules.yaml,$(TLS_DESTINATION_RULES)) kubectl apply -f /tmp/tls-destination-rules.yaml @rm -f /tmp/tls-destination-rules.yaml @@ -518,7 +518,7 @@ readme-update: $(REPORT_BASE_DIR)/README.md # Clean up resources clean: @echo "Cleaning up..." - kubectl delete namespace gateway-conformance-app-backend --ignore-not-found=true + kubectl delete namespace inference-conformance-app-backend --ignore-not-found=true @echo "Cleaning up downloaded istioctl binaries..." @rm -f $(ISTIOCTL_DIR)/istioctl-* @echo "Note: If using minikube, run 'minikube delete' to completely clean up" diff --git a/conformance/tests/epp_unavailable_fail_open.yaml b/conformance/tests/epp_unavailable_fail_open.yaml index 9388a017d..df3a6f0f5 100644 --- a/conformance/tests/epp_unavailable_fail_open.yaml +++ b/conformance/tests/epp_unavailable_fail_open.yaml @@ -2,13 +2,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-for-failopen-pool-gw - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-secondary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http hostnames: - "secondary.example.com" diff --git a/conformance/tests/gateway_following_epp_routing.yaml b/conformance/tests/gateway_following_epp_routing.yaml index c4db6386e..d0f441af7 100644 --- a/conformance/tests/gateway_following_epp_routing.yaml +++ b/conformance/tests/gateway_following_epp_routing.yaml @@ -2,13 +2,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-for-primary-gw - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http hostnames: - "primary.example.com" @@ -21,4 +21,3 @@ spec: - path: type: PathPrefix value: /primary-gateway-test - \ No newline at end of file diff --git a/conformance/tests/httproute_invalid_inferencepool_ref.yaml b/conformance/tests/httproute_invalid_inferencepool_ref.yaml index 15e7ad597..a0954cd4a 100644 --- a/conformance/tests/httproute_invalid_inferencepool_ref.yaml +++ b/conformance/tests/httproute_invalid_inferencepool_ref.yaml @@ -2,13 +2,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-to-non-existent-pool - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http rules: - backendRefs: diff --git a/conformance/tests/httproute_multiple_gateways_different_pools.yaml b/conformance/tests/httproute_multiple_gateways_different_pools.yaml index caded16d8..17bf494f8 100644 --- a/conformance/tests/httproute_multiple_gateways_different_pools.yaml +++ b/conformance/tests/httproute_multiple_gateways_different_pools.yaml @@ -3,12 +3,12 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: route-for-primary-gateway - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra hostnames: - "primary.example.com" rules: @@ -25,12 +25,12 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: route-for-secondary-gateway - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - kind: Gateway name: conformance-secondary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra hostnames: - "secondary.example.com" rules: diff --git a/conformance/tests/inferencepool_accepted.yaml b/conformance/tests/inferencepool_accepted.yaml index 59710bae4..b155ae2c9 100644 --- a/conformance/tests/inferencepool_accepted.yaml +++ b/conformance/tests/inferencepool_accepted.yaml @@ -3,20 +3,20 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-for-inferencepool-accepted - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http rules: - backendRefs: - group: inference.networking.k8s.io kind: InferencePool name: primary-inference-pool - # namespace: gateway-conformance-app-backend - is omitted since it is in the same namespace as HTTPRoute + # namespace: inference-conformance-app-backend - is omitted since it is in the same namespace as HTTPRoute matches: - path: type: PathPrefix diff --git a/conformance/tests/inferencepool_httproute_port_validation.yaml b/conformance/tests/inferencepool_httproute_port_validation.yaml index 9c78117d9..53d8455c3 100644 --- a/conformance/tests/inferencepool_httproute_port_validation.yaml +++ b/conformance/tests/inferencepool_httproute_port_validation.yaml @@ -4,13 +4,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-pool-port-unspecified - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http hostnames: - "port-unspecified.example.com" @@ -30,13 +30,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-pool-port-matching - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http hostnames: - "port-matching.example.com" @@ -56,13 +56,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-pool-port-non-matching - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http hostnames: - "port-non-matching.example.com" diff --git a/conformance/tests/inferencepool_invalid_epp_service.yaml b/conformance/tests/inferencepool_invalid_epp_service.yaml index b3dc70e19..3f3b8c49e 100644 --- a/conformance/tests/inferencepool_invalid_epp_service.yaml +++ b/conformance/tests/inferencepool_invalid_epp_service.yaml @@ -2,7 +2,7 @@ apiVersion: inference.networking.k8s.io/v1 kind: InferencePool metadata: name: pool-with-invalid-epp - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: selector: matchLabels: @@ -19,11 +19,11 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-for-invalid-epp-pool - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra rules: - backendRefs: - name: pool-with-invalid-epp diff --git a/conformance/tests/inferencepool_multiple_rules_different_pools.yaml b/conformance/tests/inferencepool_multiple_rules_different_pools.yaml index 2dd8f4a6e..0539a112e 100644 --- a/conformance/tests/inferencepool_multiple_rules_different_pools.yaml +++ b/conformance/tests/inferencepool_multiple_rules_different_pools.yaml @@ -3,11 +3,11 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-multiple-rules-different-pools - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra rules: - matches: - path: diff --git a/conformance/tests/inferencepool_resolvedrefs_condition.yaml b/conformance/tests/inferencepool_resolvedrefs_condition.yaml index 8947168a8..05a2318e9 100644 --- a/conformance/tests/inferencepool_resolvedrefs_condition.yaml +++ b/conformance/tests/inferencepool_resolvedrefs_condition.yaml @@ -8,13 +8,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-for-primary-gw - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-primary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http hostnames: - "primary.example.com" @@ -33,13 +33,13 @@ apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httproute-for-secondary-gw - namespace: gateway-conformance-app-backend + namespace: inference-conformance-app-backend spec: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: conformance-secondary - namespace: gateway-conformance-infra + namespace: inference-conformance-infra sectionName: http hostnames: - "secondary.example.com" From 9a4023f5dc31ca8314fde2cf83494b9274f0d88b Mon Sep 17 00:00:00 2001 From: Killian Golds <91667190+KillianGolds@users.noreply.github.com> Date: Tue, 14 Oct 2025 18:19:37 +0100 Subject: [PATCH 097/133] Fix LabelSelector validation markers for map field (#1679) * Fix LabelSelector validation markers for map field Changed MinItems/MaxItems to MinProperties/MaxProperties for the MatchLabels field in LabelSelector, as it is a map type, not an array. This resolves controller-gen CRD generation errors. Signed-off-by: Killian Golds * Add CRD generation to make generate target Add controller-gen crd to the generate target to validate kubebuilder markers during development and CI runs. Previously, make generate only ran controller-gen object, which generates DeepCopy methods but does not validate CRD markers like MinItems, MaxItems, MinProperties, etc. This meant invalid markers could be merged without detection, only to cause failures in downstream projects that run full CRD generation. By adding CRD generation to the generate target: - Invalid kubebuilder markers are caught immediately during development - CI will fail if markers are incorrect or CRDs are out of sync - Prevents downstream projects from encountering CRD generation errors - Aligns with the target's existing documentation which states it generates CustomResourceDefinition objects This change would have caught the MinItems/MaxItems issue fixed in commit 40cecfd before it was merged. Signed-off-by: Killian Golds * Regenerate CRDs with updated validation markers This regenerates the CRDs to include the MinProperties/MaxProperties validation for the MatchLabels map field. The generated CRD now includes: - minProperties: 1 - maxProperties: 64 These properties correctly validate the map field, replacing the incorrect MinItems/MaxItems markers that were causing controller-gen failures. Signed-off-by: Killian Golds --------- Signed-off-by: Killian Golds --- Makefile | 1 + api/v1/shared_types.go | 4 ++-- .../crd/bases/inference.networking.k8s.io_inferencepools.yaml | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d9561707b..16dad0ae9 100644 --- a/Makefile +++ b/Makefile @@ -100,6 +100,7 @@ help: ## Display this help. .PHONY: generate generate: controller-gen code-generator ## Generate WebhookConfiguration, ClusterRole, CustomResourceDefinition objects, code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate/boilerplate.generatego.txt" paths="./..." + $(CONTROLLER_GEN) crd output:dir="./config/crd/bases" paths="./..." ./hack/update-codegen.sh # Use same code-generator version as k8s.io/api diff --git a/api/v1/shared_types.go b/api/v1/shared_types.go index bc315fd4f..5d5d3e763 100644 --- a/api/v1/shared_types.go +++ b/api/v1/shared_types.go @@ -137,7 +137,7 @@ type LabelSelector struct { // The matching logic is an AND operation on all entries. // // +required - // +kubebuilder:validation:MinItems=1 - // +kubebuilder:validation:MaxItems=64 + // +kubebuilder:validation:MinProperties=1 + // +kubebuilder:validation:MaxProperties=64 MatchLabels map[LabelKey]LabelValue `json:"matchLabels,omitempty"` } diff --git a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml index 26ac568ea..a3f769633 100644 --- a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml +++ b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml @@ -147,6 +147,8 @@ spec: MatchLabels contains a set of required {key,value} pairs. An object must match every label in this map to be selected. The matching logic is an AND operation on all entries. + maxProperties: 64 + minProperties: 1 type: object required: - matchLabels From c91e716e82d4e52b905d4875d16e04619a4644a3 Mon Sep 17 00:00:00 2001 From: Luke Van Drie Date: Tue, 14 Oct 2025 11:55:35 -0700 Subject: [PATCH 098/133] feat: Flow Control context refactor (#1702) * refactor(flowcontrol): Harden FlowItem for concurrent finalization Introduces a more robust, thread-safe implementation of FlowItem to prevent race conditions during asynchronous request finalization. Key changes: - Replaces simple channel-based signaling with atomic.Pointer for `finalState` and `handle`. This provides safer, non-blocking access to the item's state from multiple goroutines (Controller & Processor). - Splits finalization logic into two idempotent methods: 1. `Finalize()`: For external, context-driven expirations. 2. `FinalizeWithOutcome()`: For synchronous, decision-based outcomes. - Adds comprehensive, table-driven unit tests to validate the new idempotency and outcome-inference logic. * refactor(flowcontrol): Adapt ShardProcessor to new FlowItem lifecycle Updates the ShardProcessor to align with the new, hardened FlowItem and its explicit lifecycle management. This change simplifies the processor's responsibilities: - The processor no longer infers item expiry. It now relies on the FlowItem's own final state, which is set externally by the controller for context-based expirations. - The background `runExpiryCleanup` goroutine is replaced with a `runCleanupSweep`. This new function periodically scans for and removes "zombie" items that were finalized externally, simplifying the processor's logic and improving separation of concerns. - The ownership contract is clarified. `Submit` and `SubmitOrBlock` now return an error on failure, indicating ownership remains with the caller. The processor only takes ownership on successful handoff. * feat(flowcontrol): Introduce explicit context handling in Controller This commit completes the refactoring of the request lifecycle by making context management explicit and adapting the FlowController to the new internal components. This is the final commit in a series: 1. Harden FlowItem for concurrent finalization. 2. Adapt ShardProcessor to new FlowItem lifecycle. 3. This commit: Introduce explicit context handling. Key changes: - The `FlowControlRequest` interface no longer includes `Context()`. - `FlowController.EnqueueAndWait` now accepts a `context.Context` as its first argument, making the request lifecycle explicit and caller-controlled. - The controller now actively monitors this context and will initiate an "asynchronous finalization" if the context expires after an item has been handed off to a processor. - Adds extensive integration and concurrency tests to validate the new end-to-end lifecycle management under contention. * Address reviewer feedback on FC context handling - Removes the optimistic finalization check in `processor.go::dispatchItem`. - Removes the `reqCtx.Done()` check in the pre-distribution select block in `controller.go::EnqueueAndWait`. - Keeps the optimistic finalization check in `processor.go::enqueue` with an updated comment clarifying its purpose. - Adds comments to `Submit` and `SubmitOrBlock` in `processor.go` to clarify non-blocking/blocking behavior. - Updates the comment for `awaitFinalization` in `controller.go` to clarify its blocking nature. - Adjusts tests in `controller_test.go` to match the logic changes: - Removes redundant `OnReqCtxCancelledBeforeDistribution` test. - Fixes `OnReqCtxExpiredBeforeDistribution` test to correctly simulate context expiry during the distribution phase.' --- pkg/epp/flowcontrol/controller/controller.go | 247 ++++-- .../flowcontrol/controller/controller_test.go | 732 +++++++++++++++--- .../flowcontrol/controller/internal/item.go | 166 ++-- .../controller/internal/item_test.go | 195 ++++- .../controller/internal/processor.go | 373 ++++----- .../controller/internal/processor_test.go | 358 +++------ pkg/epp/flowcontrol/types/mocks/mocks.go | 9 - pkg/epp/flowcontrol/types/request.go | 6 - pkg/epp/requestcontrol/admission.go | 7 +- pkg/epp/requestcontrol/admission_test.go | 8 +- 10 files changed, 1368 insertions(+), 733 deletions(-) diff --git a/pkg/epp/flowcontrol/controller/controller.go b/pkg/epp/flowcontrol/controller/controller.go index aeb0bdd87..c1f9a3004 100644 --- a/pkg/epp/flowcontrol/controller/controller.go +++ b/pkg/epp/flowcontrol/controller/controller.go @@ -32,6 +32,7 @@ import ( "time" "github.com/go-logr/logr" + k8srand "k8s.io/apimachinery/pkg/util/rand" "k8s.io/utils/clock" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" @@ -57,10 +58,11 @@ type shardProcessor interface { // shardProcessorFactory defines the signature for a function that creates a `shardProcessor`. // This enables dependency injection for testing. type shardProcessorFactory func( + ctx context.Context, shard contracts.RegistryShard, saturationDetector contracts.SaturationDetector, - clock clock.Clock, - expiryCleanupInterval time.Duration, + clock clock.WithTicker, + cleanupSweepInterval time.Duration, enqueueChannelBufferSize int, logger logr.Logger, ) shardProcessor @@ -79,6 +81,14 @@ type managedWorker struct { // // The controller's `Run` loop executes periodically, acting as a garbage collector that keeps the pool of running // workers synchronized with the dynamic shard topology of the `FlowRegistry`. +// +// Request Lifecycle Management: +// +// 1. Asynchronous Finalization (Controller-Owned): The Controller actively monitors the request Context +// (TTL/Cancellation) in EnqueueAndWait. If the Context expires, the Controller immediately Finalizes the item and +// unblocks the caller. +// 2. Synchronous Finalization (Processor-Owned): The Processor handles Dispatch, Capacity Rejection, and Shutdown. +// 3. Cleanup (Processor-Owned): The Processor periodically sweeps externally finalized items to reclaim capacity. type FlowController struct { // --- Immutable dependencies (set at construction) --- @@ -129,18 +139,20 @@ func NewFlowController( // Use the real shard processor implementation by default. fc.shardProcessorFactory = func( + ctx context.Context, shard contracts.RegistryShard, saturationDetector contracts.SaturationDetector, - clock clock.Clock, - expiryCleanupInterval time.Duration, + clock clock.WithTicker, + cleanupSweepInterval time.Duration, enqueueChannelBufferSize int, logger logr.Logger, ) shardProcessor { return internal.NewShardProcessor( + ctx, shard, saturationDetector, clock, - expiryCleanupInterval, + cleanupSweepInterval, enqueueChannelBufferSize, logger) } @@ -189,63 +201,162 @@ func (fc *FlowController) run(ctx context.Context) { // stack and its `context.Context`. The system only needs to signal this specific goroutine to unblock it. // - Direct Backpressure: If queues are full, `EnqueueAndWait` returns an error immediately, providing direct // backpressure to the caller. -func (fc *FlowController) EnqueueAndWait(req types.FlowControlRequest) (types.QueueOutcome, error) { +func (fc *FlowController) EnqueueAndWait( + ctx context.Context, + req types.FlowControlRequest, +) (types.QueueOutcome, error) { if req == nil { return types.QueueOutcomeRejectedOther, errors.New("request cannot be nil") } - effectiveTTL := req.InitialEffectiveTTL() - if effectiveTTL <= 0 { - effectiveTTL = fc.config.DefaultRequestTTL - } - enqueueTime := fc.clock.Now() + // 1. Create the derived context that governs this request's lifecycle (Parent Cancellation + TTL). + reqCtx, cancel, enqueueTime := fc.createRequestContext(ctx, req) + defer cancel() + + // 2. Enter the distribution loop to find a home for the request. + // This loop is responsible for retrying on ErrShardDraining. for { - select { + + select { // Non-blocking check on controller lifecycle. case <-fc.parentCtx.Done(): return types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning) default: - // The controller is running, proceed. } - // We must create a fresh `FlowItem` on each attempt since finalization is idempotent. - // However, we use the original, preserved `enqueueTime`. - item := internal.NewItem(req, effectiveTTL, enqueueTime) - if outcome, err := fc.distributeRequest(item); err != nil { - return outcome, fmt.Errorf("%w: %w", types.ErrRejected, err) + // Attempt to distribute the request once. + item, err := fc.tryDistribution(reqCtx, req, enqueueTime) + if err != nil { + // Distribution failed terminally (e.g., no shards, context cancelled during blocking submit). + // The item has already been finalized by tryDistribution. + finalState := item.FinalState() + return finalState.Outcome, finalState.Err } - // Block until the request is finalized (dispatched, rejected, or evicted). - // The finalization logic internally monitors for context cancellation and TTL expiry. - finalState := <-item.Done() - if errors.Is(finalState.Err, contracts.ErrShardDraining) { - fc.logger.V(logutil.DEBUG).Info("Shard is draining, retrying request", "requestID", req.ID()) - // Benign race with the chosen `contracts.RegistryShard` becoming Draining post selection but before the item was - // enqueued into its respective `contracts.ManagedQueue`. Simply try again. + // Distribution was successful; ownership of the item has been transferred to a processor. + // Now, we block here in awaitFinalization until the request is finalized by either the processor (e.g., dispatched, + // rejected) or the controller itself (e.g., caller's context cancelled/TTL expired). + outcome, err := fc.awaitFinalization(reqCtx, item) + if errors.Is(err, contracts.ErrShardDraining) { + // This is a benign race condition where the chosen shard started draining after acceptance. + fc.logger.V(logutil.DEBUG).Info("Selected shard is Draining, retrying request distribution", + "flowKey", req.FlowKey(), "requestID", req.ID()) + // Introduce a small, randomized delay (1-10ms) to prevent tight spinning loops and thundering herds during retry + // scenarios (e.g., shard draining) + // TODO: Replace this with a more sophisticated backoff strategy when our data parallelism story matures. + // For now, this is more than sufficient. + jitterMs := k8srand.Intn(10) + 1 + fc.clock.Sleep(time.Duration(jitterMs) * time.Millisecond) continue } + // The outcome is terminal (Dispatched, Evicted, or a non-retriable rejection). + return outcome, err + } +} + +var errNoShards = errors.New("no viable active shards available") + +// tryDistribution handles a single attempt to select a shard and submit a request. +// If this function returns an error, it guarantees that the provided `item` has been finalized. +func (fc *FlowController) tryDistribution( + reqCtx context.Context, + req types.FlowControlRequest, + enqueueTime time.Time, +) (*internal.FlowItem, error) { + // Calculate effective TTL for item initialization (reqCtx is the enforcement mechanism). + effectiveTTL := fc.config.DefaultRequestTTL + if deadline, ok := reqCtx.Deadline(); ok { + if ttl := deadline.Sub(enqueueTime); ttl > 0 { + effectiveTTL = ttl + } + } + + // We must create a fresh FlowItem on each attempt as finalization is per-lifecycle. + item := internal.NewItem(req, effectiveTTL, enqueueTime) + + candidates, err := fc.selectDistributionCandidates(item.OriginalRequest().FlowKey()) + if err != nil { + outcome := types.QueueOutcomeRejectedOther + if errors.Is(err, errNoShards) { + outcome = types.QueueOutcomeRejectedCapacity + } + finalErr := fmt.Errorf("%w: request not accepted: %w", types.ErrRejected, err) + item.FinalizeWithOutcome(outcome, finalErr) + return item, finalErr + } + + outcome, err := fc.distributeRequest(reqCtx, item, candidates) + if err == nil { + // Success: Ownership of the item has been transferred to the processor. + return item, nil + } + + // For any distribution error, the controller retains ownership and must finalize the item. + var finalErr error + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + // We propagate the original context error here, EnqueueAndWait will rely on item.FinalState().Err. + finalErr = err + item.Finalize(context.Cause(reqCtx)) + } else { // e.g., + finalErr = fmt.Errorf("%w: request not accepted: %w", types.ErrRejected, err) + item.FinalizeWithOutcome(outcome, finalErr) + } + return item, finalErr +} + +// awaitFinalization blocks until an item is finalized, either by the processor (synchronously) or by the controller +// itself due to context expiry (asynchronously). +func (fc *FlowController) awaitFinalization( + reqCtx context.Context, + item *internal.FlowItem, +) (types.QueueOutcome, error) { + select { + case <-reqCtx.Done(): + // Asynchronous Finalization (Controller-initiated): + // The request Context expired (Cancellation/TTL) while the item was being processed. + cause := context.Cause(reqCtx) + item.Finalize(cause) + + // The processor will eventually discard this "zombie" item during its cleanup sweep. + finalState := item.FinalState() + return finalState.Outcome, finalState.Err + + case finalState := <-item.Done(): + // Synchronous Finalization (Processor-initiated): + // The processor finalized the item (Dispatch, Reject, Shutdown). return finalState.Outcome, finalState.Err } } -// distributeRequest implements a flow-aware, two-phase "Join-Shortest-Queue-by-Bytes" (JSQ-Bytes) distribution strategy -// with graceful backpressure. It selects the optimal worker for a given item and attempts to submit it. -// -// The algorithm operates as follows: -// 1. Candidate Selection: It identifies all Active shards for the item's flow and ranks them by the current byte size -// of that flow's queue, from least to most loaded. -// 2. Phase 1 (Non-blocking Fast Failover): It iterates through the ranked candidates and attempts a non-blocking -// submission. The first successful submission wins. -// 3. Phase 2 (Blocking Fallback): If all non-blocking attempts fail, it performs a single blocking submission to the -// least-loaded candidate, providing backpressure. -func (fc *FlowController) distributeRequest(item *internal.FlowItem) (types.QueueOutcome, error) { - key := item.OriginalRequest().FlowKey() - reqID := item.OriginalRequest().ID() - type candidate struct { - processor shardProcessor - shardID string - byteSize uint64 +// createRequestContext derives the context that governs a request's lifecycle, enforcing the TTL deadline. +func (fc *FlowController) createRequestContext( + ctx context.Context, + req types.FlowControlRequest, +) (context.Context, context.CancelFunc, time.Time) { + enqueueTime := fc.clock.Now() + effectiveTTL := req.InitialEffectiveTTL() + if effectiveTTL <= 0 { + effectiveTTL = fc.config.DefaultRequestTTL } + + if effectiveTTL > 0 { + reqCtx, cancel := context.WithDeadlineCause(ctx, enqueueTime.Add(effectiveTTL), types.ErrTTLExpired) + return reqCtx, cancel, enqueueTime + } + reqCtx, cancel := context.WithCancel(ctx) + return reqCtx, cancel, enqueueTime +} + +// candidate holds the information needed to evaluate a shard as a potential target for a request. +type candidate struct { + processor shardProcessor + shardID string + byteSize uint64 +} + +// selectDistributionCandidates identifies all Active shards for the item's flow and ranks them by the current byte size +// of that flow's queue, from least to most loaded. +func (fc *FlowController) selectDistributionCandidates(key types.FlowKey) ([]candidate, error) { var candidates []candidate err := fc.registry.WithConnection(key, func(conn contracts.ActiveFlowConnection) error { shards := conn.ActiveShards() @@ -262,41 +373,58 @@ func (fc *FlowController) distributeRequest(item *internal.FlowItem) (types.Queu return nil }) if err != nil { - return types.QueueOutcomeRejectedOther, fmt.Errorf("failed to acquire lease for request %q (flow %s): %w", - reqID, key, err) + return nil, fmt.Errorf("failed to acquire lease for flow %s: %w", key, err) } if len(candidates) == 0 { - return types.QueueOutcomeRejectedCapacity, fmt.Errorf("no viable Active shards available for request %q (flow %s)", - reqID, key) + return nil, fmt.Errorf("%w for flow %s", errNoShards, key) } slices.SortFunc(candidates, func(a, b candidate) int { return cmp.Compare(a.byteSize, b.byteSize) }) - // --- Phase 1: Fast, non-blocking failover attempt --- + return candidates, nil +} + +// distributeRequest implements a flow-aware, two-phase "Join-Shortest-Queue-by-Bytes" (JSQ-Bytes) distribution strategy +// with graceful backpressure. It attempts to submit an item to the best-ranked candidate from the provided list. +// +// The algorithm operates as follows: +// 1. Phase 1 (Non-blocking Fast Failover): It iterates through the ranked candidates and attempts a non-blocking +// submission. The first successful submission wins. +// 2. Phase 2 (Blocking Fallback): If all non-blocking attempts fail, it performs a single blocking submission to the +// least-loaded candidate, providing backpressure. +// +// The provided context (ctx) is used for the blocking submission phase (SubmitOrBlock). +// +// Ownership Contract: +// - Returns nil: Success. Ownership transferred to Processor. +// - Returns error: Failure (Context expiry, shutdown,, etc.). +// Ownership retained by Controller. The Controller MUST finalize the item. +func (fc *FlowController) distributeRequest( + ctx context.Context, + item *internal.FlowItem, + candidates []candidate, +) (types.QueueOutcome, error) { + reqID := item.OriginalRequest().ID() for _, c := range candidates { if err := c.processor.Submit(item); err == nil { - return types.QueueOutcomeNotYetFinalized, nil // Success + return types.QueueOutcomeNotYetFinalized, nil } - fc.logger.V(logutil.DEBUG).Info("Processor busy during fast failover, trying next candidate", + fc.logger.V(logutil.TRACE).Info("Processor busy during fast failover, trying next candidate", "shardID", c.shardID, "requestID", reqID) } - // --- Phase 2: All processors busy. Attempt a single blocking send to the best candidate. --- + // All processors are busy. Attempt a single blocking submission to the least-loaded candidate. bestCandidate := candidates[0] - fc.logger.V(logutil.DEBUG).Info("All processors busy, attempting blocking submit to best candidate", - "shardID", bestCandidate.shardID, "requestID", reqID, "queueByteSize", bestCandidate.byteSize) - - err = bestCandidate.processor.SubmitOrBlock(item.OriginalRequest().Context(), item) + fc.logger.V(logutil.TRACE).Info("All processors busy, attempting blocking submit to best candidate", + "shardID", bestCandidate.shardID, "requestID", reqID) + err := bestCandidate.processor.SubmitOrBlock(ctx, item) if err != nil { - // If even the blocking attempt fails (e.g., context cancelled or processor shut down), the request is definitively - // rejected. - return types.QueueOutcomeRejectedCapacity, fmt.Errorf( - "all viable shard processors are at capacity for request %q (flow %s): %w", reqID, key, err) + return types.QueueOutcomeRejectedOther, fmt.Errorf("%w: request not accepted: %w", types.ErrRejected, err) } - return types.QueueOutcomeNotYetFinalized, nil + return types.QueueOutcomeNotYetFinalized, nil // Success, ownership transferred. } // getOrStartWorker implements the lazy-loading and startup of shard processors. @@ -311,6 +439,7 @@ func (fc *FlowController) getOrStartWorker(shard contracts.RegistryShard) *manag // Construct a new worker, but do not start its processor goroutine yet. processorCtx, cancel := context.WithCancel(fc.parentCtx) processor := fc.shardProcessorFactory( + processorCtx, shard, fc.saturationDetector, fc.clock, diff --git a/pkg/epp/flowcontrol/controller/controller_test.go b/pkg/epp/flowcontrol/controller/controller_test.go index c832e2d6d..74802f2df 100644 --- a/pkg/epp/flowcontrol/controller/controller_test.go +++ b/pkg/epp/flowcontrol/controller/controller_test.go @@ -14,6 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Note on Time-Based Lifecycle Tests: +// Tests validating the controller's handling of request TTLs (e.g., OnReqCtxTimeout*) rely on real-time timers +// (context.WithDeadline). The injected testclock.FakeClock is used to control the timing of internal loops (like +// reconciliation), but it cannot manipulate the timers used by the standard context package. Therefore, these specific +// tests use time.Sleep or assertions on real-time durations. + package controller import ( @@ -68,23 +74,40 @@ func withShardProcessorFactory(factory shardProcessorFactory) flowControllerOpti // testHarness holds the `FlowController` and its dependencies under test. type testHarness struct { - fc *FlowController - cfg Config - mockRegistry *mockRegistryClient - mockDetector *mocks.MockSaturationDetector + fc *FlowController + cfg Config + // clock is the clock interface used by the controller. + clock clock.WithTicker + mockRegistry *mockRegistryClient + mockDetector *mocks.MockSaturationDetector + // mockClock provides access to FakeClock methods (Step, HasWaiters) if and only if the underlying clock is a + // FakeClock. mockClock *testclock.FakeClock mockProcessorFactory *mockShardProcessorFactory } // newUnitHarness creates a test environment with a mock processor factory, suitable for focused unit tests of the -// controller's logic. It starts the controller's run loop and returns a cancel function to stop it. +// controller's logic. It starts the controller's run loop using the provided context for lifecycle management. func newUnitHarness(t *testing.T, ctx context.Context, cfg Config, registry *mockRegistryClient) *testHarness { t.Helper() mockDetector := &mocks.MockSaturationDetector{} + + // Initialize the FakeClock with the current system time. + // The controller implementation uses the injected clock to calculate the deadline timestamp,vbut uses the standard + // context.WithDeadline (which relies on the system clock) to enforce it. + // If the FakeClock's time is far from the system time, deadlines calculated based on the FakeClockvmight already be + // expired according to the system clock, causing immediate TTL failures. mockClock := testclock.NewFakeClock(time.Now()) + mockProcessorFactory := &mockShardProcessorFactory{ processors: make(map[string]*mockShardProcessor), } + + // Default the registry if nil, simplifying tests that don't focus on registry interaction. + if registry == nil { + registry = &mockRegistryClient{} + } + opts := []flowControllerOption{ withRegistryClient(registry), withClock(mockClock), @@ -96,6 +119,7 @@ func newUnitHarness(t *testing.T, ctx context.Context, cfg Config, registry *moc h := &testHarness{ fc: fc, cfg: cfg, + clock: mockClock, mockRegistry: registry, mockDetector: mockDetector, mockClock: mockClock, @@ -109,7 +133,13 @@ func newUnitHarness(t *testing.T, ctx context.Context, cfg Config, registry *moc func newIntegrationHarness(t *testing.T, ctx context.Context, cfg Config, registry *mockRegistryClient) *testHarness { t.Helper() mockDetector := &mocks.MockSaturationDetector{} + // Align FakeClock with system time. See explanation in newUnitHarness. + mockClock := testclock.NewFakeClock(time.Now()) + if registry == nil { + registry = &mockRegistryClient{} + } + opts := []flowControllerOption{ withRegistryClient(registry), withClock(mockClock), @@ -120,6 +150,7 @@ func newIntegrationHarness(t *testing.T, ctx context.Context, cfg Config, regist h := &testHarness{ fc: fc, cfg: cfg, + clock: mockClock, mockRegistry: registry, mockDetector: mockDetector, mockClock: mockClock, @@ -166,9 +197,11 @@ func (m *mockRegistryClient) ShardStats() []contracts.ShardStats { type mockShardProcessor struct { SubmitFunc func(item *internal.FlowItem) error SubmitOrBlockFunc func(ctx context.Context, item *internal.FlowItem) error - runCtx context.Context - runCtxMu sync.RWMutex - runStarted chan struct{} + // runCtx captures the context provided to the Run method for lifecycle assertions. + runCtx context.Context + runCtxMu sync.RWMutex + // runStarted is closed when the Run method is called, allowing tests to synchronize with worker startup. + runStarted chan struct{} } func (m *mockShardProcessor) Submit(item *internal.FlowItem) error { @@ -192,9 +225,11 @@ func (m *mockShardProcessor) Run(ctx context.Context) { if m.runStarted != nil { close(m.runStarted) } + // Block until the context is cancelled, simulating a running worker. <-ctx.Done() } +// Context returns the context captured during the Run method call. func (m *mockShardProcessor) Context() context.Context { m.runCtxMu.RLock() defer m.runCtxMu.RUnlock() @@ -207,10 +242,12 @@ type mockShardProcessorFactory struct { processors map[string]*mockShardProcessor } +// new is the factory function conforming to the `shardProcessorFactory` signature. func (f *mockShardProcessorFactory) new( + _ context.Context, // The factory does not use the lifecycle context; it's passed to the processor's Run method later. shard contracts.RegistryShard, _ contracts.SaturationDetector, - _ clock.Clock, + _ clock.WithTicker, _ time.Duration, _ int, _ logr.Logger, @@ -220,7 +257,7 @@ func (f *mockShardProcessorFactory) new( if proc, ok := f.processors[shard.ID()]; ok { return proc } - // Return a default mock processor if one is not registered. + // Return a default mock processor if one is not explicitly registered by the test. return &mockShardProcessor{} } @@ -262,9 +299,8 @@ func (b *mockShardBuilder) build() contracts.RegistryShard { var defaultFlowKey = types.FlowKey{ID: "test-flow", Priority: 100} -func newTestRequest(ctx context.Context, key types.FlowKey) *typesmocks.MockFlowControlRequest { +func newTestRequest(key types.FlowKey) *typesmocks.MockFlowControlRequest { return &typesmocks.MockFlowControlRequest{ - Ctx: ctx, FlowKeyV: key, ByteSizeV: 100, IDV: "req-" + key.ID, @@ -273,6 +309,8 @@ func newTestRequest(ctx context.Context, key types.FlowKey) *typesmocks.MockFlow // --- Test Cases --- +// TestFlowController_EnqueueAndWait covers the primary API entry point, focusing on validation, distribution logic, +// retries, and the request lifecycle (including post-distribution cancellation/timeout). func TestFlowController_EnqueueAndWait(t *testing.T) { t.Parallel() @@ -281,37 +319,80 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { t.Run("OnNilRequest", func(t *testing.T) { t.Parallel() - h := newUnitHarness(t, t.Context(), Config{}, &mockRegistryClient{}) + h := newUnitHarness(t, t.Context(), Config{}, nil) - outcome, err := h.fc.EnqueueAndWait(nil) + outcome, err := h.fc.EnqueueAndWait(context.Background(), nil) require.Error(t, err, "EnqueueAndWait must reject a nil request") - assert.Equal(t, "request cannot be nil", err.Error()) - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "outcome should be QueueOutcomeRejectedOther") + assert.Equal(t, "request cannot be nil", err.Error(), "error message must be specific") + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, + "outcome should be QueueOutcomeRejectedOther for invalid inputs") }) + t.Run("OnReqCtxExpiredBeforeDistribution", func(t *testing.T) { + t.Parallel() + // Test that if the request context provided to EnqueueAndWait is already expired, it returns immediately. + h := newUnitHarness(t, t.Context(), Config{DefaultRequestTTL: 1 * time.Minute}, nil) + + // Configure registry to return a shard. + shardA := newMockShard("shard-A").build() + h.mockRegistry.WithConnectionFunc = func(_ types.FlowKey, fn func(_ contracts.ActiveFlowConnection) error) error { + return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{shardA}}) + } + // Configure processor to block until context expiry. + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + SubmitOrBlockFunc: func(ctx context.Context, _ *internal.FlowItem) error { + <-ctx.Done() // Wait for the context to be done. + return context.Cause(ctx) // Return the cause. + }, + } + + req := newTestRequest(defaultFlowKey) + // Use a context with a deadline in the past. + reqCtx, cancel := context.WithDeadlineCause( + context.Background(), + h.clock.Now().Add(-1*time.Second), + types.ErrTTLExpired) + defer cancel() + + outcome, err := h.fc.EnqueueAndWait(reqCtx, req) + require.Error(t, err, "EnqueueAndWait must fail if request context deadline is exceeded") + assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") + assert.ErrorIs(t, err, types.ErrTTLExpired, "error should wrap types.ErrTTLExpired from the context cause") + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "outcome should be QueueOutcomeRejectedOther") + }) t.Run("OnControllerShutdown", func(t *testing.T) { t.Parallel() + // Create a context specifically for the controller's lifecycle. ctx, cancel := context.WithCancel(t.Context()) - h := newUnitHarness(t, ctx, Config{}, &mockRegistryClient{}) + h := newUnitHarness(t, ctx, Config{}, nil) cancel() // Immediately stop the controller. - req := newTestRequest(context.Background(), defaultFlowKey) - outcome, err := h.fc.EnqueueAndWait(req) + // Wait for the controller's run loop and all workers (none in this case) to exit. + // We need to wait because the shutdown process is asynchronous. + h.fc.wg.Wait() + + req := newTestRequest(defaultFlowKey) + // The request context is valid, but the controller itself is stopped. + outcome, err := h.fc.EnqueueAndWait(context.Background(), req) require.Error(t, err, "EnqueueAndWait must reject requests if controller is not running") assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "error should wrap ErrFlowControllerNotRunning") - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "outcome should be QueueOutcomeRejectedOther") + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, + "outcome should be QueueOutcomeRejectedOther on shutdown") }) t.Run("OnNoShardsAvailable", func(t *testing.T) { t.Parallel() - h := newUnitHarness(t, t.Context(), Config{}, &mockRegistryClient{}) + // The default mockRegistryClient returns an empty list of ActiveShards. + h := newUnitHarness(t, t.Context(), Config{}, nil) - req := newTestRequest(context.Background(), defaultFlowKey) - outcome, err := h.fc.EnqueueAndWait(req) + req := newTestRequest(defaultFlowKey) + outcome, err := h.fc.EnqueueAndWait(context.Background(), req) require.Error(t, err, "EnqueueAndWait must reject requests if no shards are available") assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") - assert.Equal(t, types.QueueOutcomeRejectedCapacity, outcome, "outcome should be QueueOutcomeRejectedCapacity") + assert.Equal(t, types.QueueOutcomeRejectedCapacity, outcome, + "outcome should be QueueOutcomeRejectedCapacity when no shards exist for the flow") }) t.Run("OnRegistryConnectionError", func(t *testing.T) { @@ -319,7 +400,8 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { mockRegistry := &mockRegistryClient{} h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) - expectedErr := errors.New("connection failed") + expectedErr := errors.New("simulated connection failure") + // Configure the registry to fail when attempting to retrieve ActiveFlowConnection. mockRegistry.WithConnectionFunc = func( _ types.FlowKey, _ func(conn contracts.ActiveFlowConnection) error, @@ -327,23 +409,27 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { return expectedErr } - req := newTestRequest(context.Background(), defaultFlowKey) - outcome, err := h.fc.EnqueueAndWait(req) + req := newTestRequest(defaultFlowKey) + outcome, err := h.fc.EnqueueAndWait(context.Background(), req) require.Error(t, err, "EnqueueAndWait must reject requests if registry connection fails") assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") assert.ErrorIs(t, err, expectedErr, "error should wrap the underlying connection error") - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "outcome should be QueueOutcomeRejectedOther") + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, + "outcome should be QueueOutcomeRejectedOther for transient registry errors") }) + // This test validates the documented invariant handling in distributeRequest. t.Run("PanicsOnManagedQueueError", func(t *testing.T) { t.Parallel() mockRegistry := &mockRegistryClient{} h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + // Create a faulty shard that successfully leases the flow but fails to return the + // ManagedQueue. faultyShard := &mocks.MockRegistryShard{ IDFunc: func() string { return "faulty-shard" }, ManagedQueueFunc: func(_ types.FlowKey) (contracts.ManagedQueue, error) { - return nil, errors.New("queue retrieval failed") + return nil, errors.New("invariant violation: queue retrieval failed") }, } mockRegistry.WithConnectionFunc = func( @@ -353,22 +439,30 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{faultyShard}}) } - req := newTestRequest(context.Background(), defaultFlowKey) + req := newTestRequest(defaultFlowKey) assert.Panics(t, func() { - _, _ = h.fc.EnqueueAndWait(req) - }, "EnqueueAndWait did not panic as expected on a ManagedQueue error") + _, _ = h.fc.EnqueueAndWait(context.Background(), req) + }, "EnqueueAndWait must panic when a registry implementation violates the ManagedQueue contract") }) }) + // Distribution tests validate the JSQ-Bytes algorithm, the two-phase submission strategy, and error handling during + // the handoff, including time-based failures during blocking fallback. t.Run("Distribution", func(t *testing.T) { t.Parallel() + // Define a long default TTL to prevent unexpected timeouts unless a test case explicitly sets a shorter one. + const defaultTestTTL = 5 * time.Second + testCases := []struct { name string shards []contracts.RegistryShard setupProcessors func(t *testing.T, h *testHarness) + // requestTTL overrides the default TTL for time-sensitive tests. + requestTTL time.Duration expectedOutcome types.QueueOutcome expectErr bool + expectErrIs error }{ { name: "SubmitSucceeds_NonBlocking_WithSingleActiveShard", @@ -376,7 +470,8 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { setupProcessors: func(t *testing.T, h *testHarness) { h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ SubmitFunc: func(item *internal.FlowItem) error { - go item.Finalize(types.QueueOutcomeDispatched, nil) + // Simulate asynchronous processing and successful dispatch. + go item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) return nil }, } @@ -386,19 +481,20 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { { name: "DistributesToLeastLoadedShard_WithMultipleActiveShards", shards: []contracts.RegistryShard{ - newMockShard("shard-A").withByteSize(1000).build(), - newMockShard("shard-B").withByteSize(100).build(), + newMockShard("shard-A").withByteSize(1000).build(), // More loaded + newMockShard("shard-B").withByteSize(100).build(), // Least loaded }, setupProcessors: func(t *testing.T, h *testHarness) { h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ SubmitFunc: func(_ *internal.FlowItem) error { - t.Error("Submit was called on the more loaded shard (shard-A), which is incorrect") + t.Error("Submit was called on the more loaded shard (shard-A); JSQ-Bytes algorithm failed") return internal.ErrProcessorBusy }, } h.mockProcessorFactory.processors["shard-B"] = &mockShardProcessor{ SubmitFunc: func(item *internal.FlowItem) error { - go item.Finalize(types.QueueOutcomeDispatched, nil) + item.SetHandle(&typesmocks.MockQueueItemHandle{}) + go item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) return nil }, } @@ -412,13 +508,16 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { newMockShard("shard-B").withByteSize(100).build(), }, setupProcessors: func(t *testing.T, h *testHarness) { + // Both processors reject the initial non-blocking Submit. h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, } + // Shard-B is the least loaded, so it should receive the blocking fallback (SubmitOrBlock). h.mockProcessorFactory.processors["shard-B"] = &mockShardProcessor{ SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, SubmitOrBlockFunc: func(_ context.Context, item *internal.FlowItem) error { - go item.Finalize(types.QueueOutcomeDispatched, nil) + // The blocking call succeeds. + go item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) return nil }, } @@ -426,24 +525,78 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { expectedOutcome: types.QueueOutcomeDispatched, }, { - name: "Rejects_AfterBlocking_WithAllProcessorsRemainingBusy", + // Validates the scenario where the request's TTL expires while the controller is blocked waiting for capacity. + // NOTE: This relies on real time passing, as context.WithDeadline timers cannot be controlled by FakeClock. + name: "Rejects_AfterBlocking_WhenTTL_Expires", + shards: []contracts.RegistryShard{newMockShard("shard-A").build()}, + requestTTL: 50 * time.Millisecond, // Short TTL to keep the test fast. + setupProcessors: func(t *testing.T, h *testHarness) { + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + // Reject the non-blocking attempt. + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + // Block the fallback attempt until the context (carrying the TTL deadline) expires. + SubmitOrBlockFunc: func(ctx context.Context, _ *internal.FlowItem) error { + <-ctx.Done() + return ctx.Err() + }, + } + }, + // No runActions needed; we rely on the real-time timer to expire. + // When the blocking call fails due to context expiry, the outcome is RejectedOther. + expectedOutcome: types.QueueOutcomeRejectedOther, + expectErr: true, + // The error must reflect the specific cause of the context cancellation (ErrTTLExpired). + expectErrIs: types.ErrTTLExpired, + }, + { + name: "Rejects_OnProcessorShutdownDuringSubmit", shards: []contracts.RegistryShard{newMockShard("shard-A").build()}, setupProcessors: func(t *testing.T, h *testHarness) { h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ - SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, - SubmitOrBlockFunc: func(_ context.Context, _ *internal.FlowItem) error { return context.DeadlineExceeded }, + // Simulate the processor shutting down during the non-blocking handoff. + SubmitFunc: func(_ *internal.FlowItem) error { return types.ErrFlowControllerNotRunning }, + SubmitOrBlockFunc: func(_ context.Context, _ *internal.FlowItem) error { + return types.ErrFlowControllerNotRunning + }, } }, - expectedOutcome: types.QueueOutcomeRejectedCapacity, + expectedOutcome: types.QueueOutcomeRejectedOther, expectErr: true, + expectErrIs: types.ErrFlowControllerNotRunning, + }, + { + name: "Rejects_OnProcessorShutdownDuringSubmitOrBlock", + shards: []contracts.RegistryShard{newMockShard("shard-A").build()}, + setupProcessors: func(t *testing.T, h *testHarness) { + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + // Simulate the processor shutting down during the blocking handoff. + SubmitOrBlockFunc: func(_ context.Context, _ *internal.FlowItem) error { + return types.ErrFlowControllerNotRunning + }, + } + }, + expectedOutcome: types.QueueOutcomeRejectedOther, + expectErr: true, + expectErrIs: types.ErrFlowControllerNotRunning, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() + + // Arrange mockRegistry := &mockRegistryClient{} - h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + + // Configure the harness with the appropriate TTL. + harnessConfig := Config{DefaultRequestTTL: defaultTestTTL} + if tc.requestTTL > 0 { + harnessConfig.DefaultRequestTTL = tc.requestTTL + } + h := newUnitHarness(t, t.Context(), harnessConfig, mockRegistry) + + // Configure the registry to return the specified shards. mockRegistry.WithConnectionFunc = func( _ types.FlowKey, fn func(conn contracts.ActiveFlowConnection) error, @@ -451,11 +604,33 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { return fn(&mockActiveFlowConnection{ActiveShardsV: tc.shards}) } tc.setupProcessors(t, h) - outcome, err := h.fc.EnqueueAndWait(newTestRequest(context.Background(), defaultFlowKey)) + + // Act + var outcome types.QueueOutcome + var err error + + startTime := time.Now() // Capture real start time for duration checks. + // Use a background context for the parent; the request lifecycle is governed by the config/derived context. + outcome, err = h.fc.EnqueueAndWait(context.Background(), newTestRequest(defaultFlowKey)) + + // Assert if tc.expectErr { - require.Error(t, err, "expected an error but got nil") + require.Error(t, err, "expected an error during EnqueueAndWait but got nil") + assert.ErrorIs(t, err, tc.expectErrIs, "error should wrap the expected underlying cause") + // All failures during the distribution phase (capacity, timeout, shutdown) should result in a rejection. + assert.ErrorIs(t, err, types.ErrRejected, "rejection errors must wrap types.ErrRejected") + + // Specific assertion for real-time TTL tests. + if errors.Is(tc.expectErrIs, types.ErrTTLExpired) { + duration := time.Since(startTime) + // Ensure the test didn't return instantly. Use a tolerance for CI environments. + // This validates that the real-time wait actually occurred. + assert.GreaterOrEqual(t, duration, tc.requestTTL-30*time.Millisecond, + "EnqueueAndWait returned faster than the TTL allows, indicating the timer did not function correctly") + } + } else { - require.NoError(t, err, "expected no error but got: %v", err) + require.NoError(t, err, "expected no error during EnqueueAndWait but got: %v", err) } assert.Equal(t, tc.expectedOutcome, outcome, "outcome did not match expected value") }) @@ -465,6 +640,8 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { t.Run("Retry", func(t *testing.T) { t.Parallel() + // This test specifically validates the behavior when the request context is cancelled externally while the + // controller is blocked in the SubmitOrBlock phase. t.Run("Rejects_OnRequestContextCancelledWhileBlocking", func(t *testing.T) { t.Parallel() mockRegistry := &mockRegistryClient{ @@ -477,22 +654,34 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { }) }, } - h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + // Use a long TTL to ensure the failure is due to cancellation, not timeout. + h := newUnitHarness(t, t.Context(), Config{DefaultRequestTTL: 10 * time.Second}, mockRegistry) h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ - SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, - SubmitOrBlockFunc: func(ctx context.Context, _ *internal.FlowItem) error { <-ctx.Done(); return ctx.Err() }, + // Reject non-blocking attempt. + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + // Block the fallback attempt until the context is cancelled. + SubmitOrBlockFunc: func(ctx context.Context, _ *internal.FlowItem) error { + <-ctx.Done() + return ctx.Err() + }, } + + // Create a cancellable context for the request. reqCtx, cancelReq := context.WithCancel(context.Background()) - go func() { time.Sleep(50 * time.Millisecond); cancelReq() }() + // Cancel the request shortly after starting the operation. + // We use real time sleep here as we are testing external cancellation signals interacting with the context. + go func() { time.Sleep(10 * time.Millisecond); cancelReq() }() - outcome, err := h.fc.EnqueueAndWait(newTestRequest(reqCtx, defaultFlowKey)) + outcome, err := h.fc.EnqueueAndWait(reqCtx, newTestRequest(defaultFlowKey)) require.Error(t, err, "EnqueueAndWait must fail when context is cancelled during a blocking submit") assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") - assert.ErrorIs(t, err, context.Canceled, "error should wrap the underlying ctx.Err()") - assert.Equal(t, types.QueueOutcomeRejectedCapacity, outcome, "outcome should be QueueOutcomeRejectedCapacity") + assert.ErrorIs(t, err, context.Canceled, "error should wrap the underlying ctx.Err() (context.Canceled)") + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, + "outcome should be QueueOutcomeRejectedOther when cancelled during distribution") }) + // This test validates the retry mechanism when a processor reports that its shard is draining. t.Run("RetriesAndSucceeds_OnProcessorReportsShardDraining", func(t *testing.T) { t.Parallel() var callCount atomic.Int32 @@ -504,106 +693,278 @@ func TestFlowController_EnqueueAndWait(t *testing.T) { attempt := callCount.Add(1) shardA := newMockShard("shard-A").withByteSize(100).build() shardB := newMockShard("shard-B").withByteSize(1000).build() + if attempt == 1 { + // Attempt 1: Shard A is the least loaded and is selected. return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{shardA, shardB}}) } + // Attempt 2 (Retry): Assume Shard A is now draining and removed from the active set by the registry. return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{shardB}}) }, } - h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + // Use a long TTL to ensure retries don't time out. + h := newUnitHarness(t, t.Context(), Config{DefaultRequestTTL: 10 * time.Second}, mockRegistry) + + // Configure Shard A's processor to reject the request due to draining. h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ SubmitFunc: func(item *internal.FlowItem) error { - go item.Finalize(types.QueueOutcomeRejectedOther, contracts.ErrShardDraining) + // The processor accepts the item but then asynchronously finalizes it with ErrShardDraining. + item.SetHandle(&typesmocks.MockQueueItemHandle{}) + go item.FinalizeWithOutcome(types.QueueOutcomeRejectedOther, contracts.ErrShardDraining) return nil }, } + // Configure Shard B's processor to successfully dispatch the request on the retry. h.mockProcessorFactory.processors["shard-B"] = &mockShardProcessor{ SubmitFunc: func(item *internal.FlowItem) error { - go item.Finalize(types.QueueOutcomeDispatched, nil) + go item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) return nil }, } - outcome, err := h.fc.EnqueueAndWait(newTestRequest(context.Background(), defaultFlowKey)) + // Act + outcome, err := h.fc.EnqueueAndWait(context.Background(), newTestRequest(defaultFlowKey)) + + // Assert require.NoError(t, err, "EnqueueAndWait must succeed after retrying on a healthy shard") assert.Equal(t, types.QueueOutcomeDispatched, outcome, "outcome should be QueueOutcomeDispatched") assert.Equal(t, int32(2), callCount.Load(), "registry must be consulted for Active shards on each retry attempt") }) }) + + // Lifecycle covers the post-distribution phase, focusing on how the controller handles context cancellation and TTL + // expiry while the request is buffered or queued by the processor (Asynchronous Finalization). + t.Run("Lifecycle", func(t *testing.T) { + t.Parallel() + + // Validates that the controller correctly initiates asynchronous finalization when the request context is cancelled + // after ownership has been transferred to the processor. + t.Run("OnReqCtxCancelledAfterDistribution", func(t *testing.T) { + t.Parallel() + // Use a long TTL to ensure the failure is due to cancellation. + h := newUnitHarness(t, t.Context(), Config{DefaultRequestTTL: 10 * time.Second}, nil) + + shardA := newMockShard("shard-A").build() + h.mockRegistry.WithConnectionFunc = func(_ types.FlowKey, fn func(_ contracts.ActiveFlowConnection) error) error { + return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{shardA}}) + } + + // Channel for synchronization. + itemSubmitted := make(chan *internal.FlowItem, 1) + + // Configure the processor to accept the item but never finalize it, simulating a queued request. + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(item *internal.FlowItem) error { + item.SetHandle(&typesmocks.MockQueueItemHandle{}) + itemSubmitted <- item + return nil + }, + } + + reqCtx, cancelReq := context.WithCancel(context.Background()) + req := newTestRequest(defaultFlowKey) + + var outcome types.QueueOutcome + var err error + done := make(chan struct{}) + go func() { + outcome, err = h.fc.EnqueueAndWait(reqCtx, req) + close(done) + }() + + // 1. Wait for the item to be successfully distributed. + var item *internal.FlowItem + select { + case item = <-itemSubmitted: + // Success. Ownership has transferred. EnqueueAndWait is now in the select loop. + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for item to be submitted to the processor") + } + + // 2. Cancel the request context. + cancelReq() + + // 3. Wait for EnqueueAndWait to return. + select { + case <-done: + // Success. The controller detected the cancellation and unblocked the caller. + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for EnqueueAndWait to return after cancellation") + } + + // 4. Assertions for EnqueueAndWait's return values. + require.Error(t, err, "EnqueueAndWait should return an error when the request is cancelled post-distribution") + // The outcome should be Evicted (as the handle was set). + assert.ErrorIs(t, err, types.ErrEvicted, "error should wrap ErrEvicted") + // The underlying cause must be propagated. + assert.ErrorIs(t, err, types.ErrContextCancelled, "error should wrap ErrContextCancelled") + assert.Equal(t, types.QueueOutcomeEvictedContextCancelled, outcome, "outcome should be EvictedContextCancelled") + + // 5. Assert that the FlowItem itself was indeed finalized by the controller. + finalState := item.FinalState() + require.NotNil(t, finalState, "Item should have been finalized asynchronously by the controller") + assert.Equal(t, types.QueueOutcomeEvictedContextCancelled, finalState.Outcome, + "Item's internal outcome must match the returned outcome") + }) + + // Validates the asynchronous finalization path due to TTL expiry. + // Note: This relies on real time passing, as context.WithDeadline timers cannot be controlled by FakeClock. + t.Run("OnReqCtxTimeoutAfterDistribution", func(t *testing.T) { + t.Parallel() + // Configure a short TTL to keep the test reasonably fast. + const requestTTL = 50 * time.Millisecond + h := newUnitHarness(t, t.Context(), Config{DefaultRequestTTL: requestTTL}, nil) + + shardA := newMockShard("shard-A").build() + h.mockRegistry.WithConnectionFunc = func(_ types.FlowKey, fn func(_ contracts.ActiveFlowConnection) error) error { + return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{shardA}}) + } + + itemSubmitted := make(chan *internal.FlowItem, 1) + + // Configure the processor to accept the item but never finalize it. + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(item *internal.FlowItem) error { + item.SetHandle(&typesmocks.MockQueueItemHandle{}) + itemSubmitted <- item + return nil + }, + } + + req := newTestRequest(defaultFlowKey) + // Use a context for the call itself that won't time out independently. + enqueueCtx, enqueueCancel := context.WithTimeout(context.Background(), 2*time.Second) + defer enqueueCancel() + + var outcome types.QueueOutcome + var err error + done := make(chan struct{}) + + startTime := time.Now() // Capture start time to validate duration. + go func() { + outcome, err = h.fc.EnqueueAndWait(enqueueCtx, req) + close(done) + }() + + // 1. Wait for the item to be submitted. + var item *internal.FlowItem + select { + case item = <-itemSubmitted: + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for item to be submitted to the processor") + } + + // 2.Wait for the TTL to expire (Real time). We do NOT call Step(). + // Wait for EnqueueAndWait to return due to the TTL expiry. + select { + case <-done: + // Success. Now validate that enough time actually passed. + duration := time.Since(startTime) + assert.GreaterOrEqual(t, duration, requestTTL-30*time.Millisecond, // tolerance for CI environments + "EnqueueAndWait returned faster than the TTL allows, indicating the timer did not function correctly") + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for EnqueueAndWait to return after TTL expiry") + } + + // 4. Assertions for EnqueueAndWait's return values. + require.Error(t, err, "EnqueueAndWait should return an error when TTL expires post-distribution") + assert.ErrorIs(t, err, types.ErrEvicted, "error should wrap ErrEvicted") + assert.ErrorIs(t, err, types.ErrTTLExpired, "error should wrap the underlying cause (types.ErrTTLExpired)") + assert.Equal(t, types.QueueOutcomeEvictedTTL, outcome, "outcome should be EvictedTTL") + + // 5. Assert FlowItem final state. + finalState := item.FinalState() + require.NotNil(t, finalState, "Item should have been finalized asynchronously by the controller") + assert.Equal(t, types.QueueOutcomeEvictedTTL, finalState.Outcome, + "Item's internal outcome must match the returned outcome") + }) + }) } -func TestFlowController_Lifecycle(t *testing.T) { +// TestFlowController_WorkerManagement covers the lifecycle of the shard processors (workers), including startup, +// reconciliation (garbage collection), and shutdown. +func TestFlowController_WorkerManagement(t *testing.T) { t.Parallel() + // Reconciliation validates that the controller correctly identifies and shuts down workers whose shards no longer + // exist in the registry. t.Run("Reconciliation", func(t *testing.T) { t.Parallel() + // Setup: A registry that initially knows about "shard-A" and "stale-shard", but later only reports "shard-A". mockRegistry := &mockRegistryClient{ - // Configure the mock registry to report the new state without the stale shard. ShardStatsFunc: func() []contracts.ShardStats { + // The current state of the world according to the registry. return []contracts.ShardStats{{ID: "shard-A"}} }} h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) - // Pre-populate the controller with initial workers. + // Pre-populate the controller with initial workers, simulating a previous state. initialShards := []string{"shard-A", "stale-shard"} for _, shardID := range initialShards { currentShardID := shardID + // Initialize the processor mocks with the channel needed to synchronize startup. h.mockProcessorFactory.processors[currentShardID] = &mockShardProcessor{runStarted: make(chan struct{})} shard := &mocks.MockRegistryShard{IDFunc: func() string { return currentShardID }} + // Start the worker using the internal mechanism. h.fc.getOrStartWorker(shard) } require.Len(t, h.mockProcessorFactory.processors, 2, "pre-condition: initial workers not set up correctly") - // Wait for all workers to have started and set their contexts before proceeding with the test. + // Wait for all worker goroutines to have started and captured their contexts. for id, p := range h.mockProcessorFactory.processors { proc := p select { case <-proc.runStarted: - // Success + // Worker is running. case <-time.After(2 * time.Second): t.Fatalf("timed out waiting for worker %s to start", id) } } - // Manually trigger the reconciliation loop logic. + // Act: Manually trigger the reconciliation logic. h.fc.reconcileProcessors() t.Run("StaleWorkerIsCancelled", func(t *testing.T) { staleProc := h.mockProcessorFactory.processors["stale-shard"] require.NotNil(t, staleProc.Context(), "precondition: stale processor context should have been captured") + // The context of the removed worker must be cancelled to signal shutdown. select { case <-staleProc.Context().Done(): - // Success + // Success: Context was cancelled. case <-time.After(100 * time.Millisecond): - t.Error("context of removed worker must be cancelled") + t.Error("context of the stale worker was not cancelled during reconciliation") } }) t.Run("ActiveWorkerIsNotCancelled", func(t *testing.T) { activeProc := h.mockProcessorFactory.processors["shard-A"] require.NotNil(t, activeProc.Context(), "precondition: active processor context should have been captured") + // The context of an active worker must remain open. select { case <-activeProc.Context().Done(): - t.Error("context of remaining worker must not be cancelled") + t.Error("context of the active worker was incorrectly cancelled during reconciliation") default: - // Success + // Success: Context is still active. } }) t.Run("WorkerMapIsUpdated", func(t *testing.T) { + // The stale worker must be removed from the controller's concurrent map. _, ok := h.fc.workers.Load("stale-shard") - assert.False(t, ok, "stale worker must be removed from the controller's map") + assert.False(t, ok, "stale worker must be deleted from the controller's map") _, ok = h.fc.workers.Load("shard-A") assert.True(t, ok, "active worker must remain in the controller's map") }) }) + // Validates that the reconciliation loop runs periodically based on the configured interval. t.Run("Reconciliation_IsTriggeredByTicker", func(t *testing.T) { t.Parallel() - reconciliationInterval := 10 * time.Second + const reconciliationInterval = 10 * time.Second mockRegistry := &mockRegistryClient{} + // Count the number of times the reconciliation logic (which calls ShardStats) runs. var reconcileCount atomic.Int32 mockRegistry.ShardStatsFunc = func() []contracts.ShardStats { reconcileCount.Add(1) @@ -611,45 +972,55 @@ func TestFlowController_Lifecycle(t *testing.T) { } h := newUnitHarness(t, t.Context(), Config{ProcessorReconciliationInterval: reconciliationInterval}, mockRegistry) + // Ensure we are using the FakeClock specifically for this test, as we need Step/HasWaiters. + require.NotNil(t, h.mockClock, "This test requires the harness to be using FakeClock") // Wait for the reconciliation loop to start and create the ticker. - // This prevents a race where the clock is stepped before the ticker is registered. - require.Eventually(t, h.mockClock.HasWaiters, time.Second, 10*time.Millisecond, "ticker was not created") + // This prevents a race where the clock is stepped before the ticker is registered with the FakeClock. + require.Eventually(t, h.mockClock.HasWaiters, time.Second, 10*time.Millisecond, + "reconciliation ticker was not created") - // Advance the clock to trigger the next reconciliation. + // Advance the clock to trigger the first reconciliation. h.mockClock.Step(reconciliationInterval) assert.Eventually(t, func() bool { return reconcileCount.Load() == 1 - }, time.Second, 10*time.Millisecond, "reconciliation was not triggered by the ticker") + }, time.Second, 10*time.Millisecond, "reconciliation was not triggered by the first ticker event") // Advance the clock again to ensure it continues to fire. h.mockClock.Step(reconciliationInterval) assert.Eventually(t, func() bool { return reconcileCount.Load() == 2 - }, time.Second, 10*time.Millisecond, "reconciliation did not fire on the second tick") + }, time.Second, 10*time.Millisecond, "reconciliation did not fire on the second ticker event") }) + // Validates the atomicity of worker creation and ensures resource cleanup for the loser of the race. t.Run("WorkerCreationRace", func(t *testing.T) { t.Parallel() - // This test requires manual control over the shard processor factory to deterministically create a race. + // This test orchestrates a deterministic race condition. factoryEntered := make(chan *mockShardProcessor, 2) continueFactory := make(chan struct{}) + // Map to store the construction context for each processor instance, allowing us to verify cleanup. + constructionContexts := sync.Map{} - h := newUnitHarness(t, t.Context(), Config{}, &mockRegistryClient{}) + h := newUnitHarness(t, t.Context(), Config{}, nil) + + // Inject a custom factory to control the timing of worker creation. h.fc.shardProcessorFactory = func( + ctx context.Context, // The context created by getOrStartWorker for the potential new processor. shard contracts.RegistryShard, _ contracts.SaturationDetector, - _ clock.Clock, + _ clock.WithTicker, _ time.Duration, _ int, _ logr.Logger, ) shardProcessor { - // This factory function will be called by `startNewWorker`. - // We use channels to pause execution here, allowing two goroutines to enter this function before one "wins" - // the `LoadOrStore` race. + // This function is called by getOrStartWorker before the LoadOrStore check. proc := &mockShardProcessor{runStarted: make(chan struct{})} + constructionContexts.Store(proc, ctx) // Capture the construction context. + + // Signal entry and then block, allowing another goroutine to enter. factoryEntered <- proc <-continueFactory return proc @@ -669,18 +1040,17 @@ func TestFlowController_Lifecycle(t *testing.T) { h.fc.getOrStartWorker(shard) }() - // Wait for both goroutines to have entered the factory and created a processor. - // This confirms they both missed the initial `workers.Load` check. + // 1. Wait for both goroutines to enter the factory and create their respective processor instances. proc1 := <-factoryEntered proc2 := <-factoryEntered - // Unblock both goroutines, allowing them to race to `workers.LoadOrStore`. + // 2. Unblock both goroutines, allowing them to race to workers.LoadOrStore. close(continueFactory) wg.Wait() - // One processor "won" and was stored, the other "lost" and should have been cancelled. + // 3. Identify the winner and the loser. actual, ok := h.fc.workers.Load("race-shard") - require.True(t, ok, "a worker should have been stored in the map") + require.True(t, ok, "a worker must have been successfully stored in the map") storedWorker := actual.(*managedWorker) winnerProc := storedWorker.processor.(*mockShardProcessor) @@ -692,16 +1062,17 @@ func TestFlowController_Lifecycle(t *testing.T) { loserProc = proc1 } - // Wait for the `Run` method to be called on the winning processor to ensure its context is available. + // 4. Validate the state of the winning processor. + // Wait for the Run method to be called on the winner (only the winner should start). select { case <-winnerProc.runStarted: - // Success. + // Success. case <-time.After(1 * time.Second): - t.Fatal("timed out waiting for winning worker to start") + t.Fatal("timed out waiting for the winning worker's Run method to be called") } // The winning processor's context must remain active. - require.NotNil(t, winnerProc.Context(), "winner's context should not be nil") + require.NotNil(t, winnerProc.Context(), "winner's context should not be nil (Run was called)") select { case <-winnerProc.Context().Done(): t.Error("context of the winning worker should not be cancelled") @@ -709,49 +1080,61 @@ func TestFlowController_Lifecycle(t *testing.T) { // Success } - // The losing processor's `Run` method must not be called, and its context should be nil. + // 5. Validate the state of the losing processor and resource cleanup. + // The losing processor's Run method must NOT be called. select { case <-loserProc.runStarted: - t.Error("Run was called on the losing worker, but it should not have been") + t.Error("Run was incorrectly called on the losing worker") default: // Success } - assert.Nil(t, loserProc.Context(), "loser's context should be nil as Run is never called") + + // Verify the context created for the loser during construction was cancelled by getOrStartWorker. + loserCtxRaw, ok := constructionContexts.Load(loserProc) + require.True(t, ok, "loser processor construction context should have been captured") + loserCtx := loserCtxRaw.(context.Context) + + select { + case <-loserCtx.Done(): + // Success: Context was cancelled, preventing resource leaks. + case <-time.After(100 * time.Millisecond): + t.Error("context of the losing worker was not cancelled, this will leak resources") + } }) } -func TestFlowController_Concurrency(t *testing.T) { - const ( - numShards = 4 - numGoroutines = 50 - numRequests = 200 - ) - - // Set up a realistic registry that vends real components to the processor. +// Helper function to create a realistic mock registry environment for integration/concurrency tests. +func setupRegistryForConcurrency(t *testing.T, numShards int, flowKey types.FlowKey) *mockRegistryClient { + t.Helper() mockRegistry := &mockRegistryClient{} shards := make([]contracts.RegistryShard, numShards) - queues := make(map[string]contracts.ManagedQueue) + + // Configure the shards and their dependencies required by the real ShardProcessor implementation. for i := range numShards { + // Capture loop variables for closures. shardID := fmt.Sprintf("shard-%d", i) - queues[shardID] = &mocks.MockManagedQueue{FlowKeyV: defaultFlowKey} // Use the high-fidelity mock queue. + // Use high-fidelity mock queues (MockManagedQueue) that implement the necessary interfaces and synchronization. + currentQueue := &mocks.MockManagedQueue{FlowKeyV: flowKey} + shards[i] = &mocks.MockRegistryShard{ IDFunc: func() string { return shardID }, ManagedQueueFunc: func(_ types.FlowKey) (contracts.ManagedQueue, error) { - return queues[shardID], nil + return currentQueue, nil }, - AllOrderedPriorityLevelsFunc: func() []int { return []int{100} }, + // Configuration required for ShardProcessor initialization and dispatch logic. + AllOrderedPriorityLevelsFunc: func() []int { return []int{flowKey.Priority} }, PriorityBandAccessorFunc: func(priority int) (framework.PriorityBandAccessor, error) { - if priority == 100 { + if priority == flowKey.Priority { return &frameworkmocks.MockPriorityBandAccessor{ - PriorityNameV: "high", - PriorityV: 100, + PriorityV: priority, IterateQueuesFunc: func(f func(framework.FlowQueueAccessor) bool) { - f(queues[shardID].FlowQueueAccessor()) + f(currentQueue.FlowQueueAccessor()) }, }, nil } return nil, fmt.Errorf("unexpected priority %d", priority) }, + // Configure dispatch policies (FIFO). IntraFlowDispatchPolicyFunc: func(_ types.FlowKey) (framework.IntraFlowDispatchPolicy, error) { return &frameworkmocks.MockIntraFlowDispatchPolicy{ SelectItemFunc: func(qa framework.FlowQueueAccessor) (types.QueueItemAccessor, error) { @@ -762,26 +1145,29 @@ func TestFlowController_Concurrency(t *testing.T) { InterFlowDispatchPolicyFunc: func(_ int) (framework.InterFlowDispatchPolicy, error) { return &frameworkmocks.MockInterFlowDispatchPolicy{ SelectQueueFunc: func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error) { - return queues[shardID].FlowQueueAccessor(), nil + return currentQueue.FlowQueueAccessor(), nil }, }, nil }, + // Configure stats reporting based on the live state of the mock queues. StatsFunc: func() contracts.ShardStats { return contracts.ShardStats{ ID: shardID, - TotalLen: uint64(queues[shardID].Len()), - TotalByteSize: queues[shardID].ByteSize(), + TotalLen: uint64(currentQueue.Len()), + TotalByteSize: currentQueue.ByteSize(), PerPriorityBandStats: map[int]contracts.PriorityBandStats{ - 100: { - Len: uint64(queues[shardID].Len()), - ByteSize: queues[shardID].ByteSize(), - CapacityBytes: 1e9, // Effectively unlimited capacity + flowKey.Priority: { + Len: uint64(currentQueue.Len()), + ByteSize: currentQueue.ByteSize(), + CapacityBytes: 1e9, // Effectively unlimited capacity to ensure dispatch success. }, }, } }, } } + + // Configure the registry connection. mockRegistry.WithConnectionFunc = func(_ types.FlowKey, fn func(conn contracts.ActiveFlowConnection) error) error { return fn(&mockActiveFlowConnection{ActiveShardsV: shards}) } @@ -792,25 +1178,121 @@ func TestFlowController_Concurrency(t *testing.T) { } return stats } + return mockRegistry +} + +// TestFlowController_Concurrency_Distribution performs an integration test under high contention, using real +// ShardProcessors. +// It validates the thread-safety of the distribution logic and the overall system throughput. +func TestFlowController_Concurrency_Distribution(t *testing.T) { + const ( + numShards = 4 + numGoroutines = 50 + numRequests = 200 + ) + + // Arrange + mockRegistry := setupRegistryForConcurrency(t, numShards, defaultFlowKey) + + // Initialize the integration harness with real ShardProcessors. h := newIntegrationHarness(t, t.Context(), Config{ - // Use a generous buffer to prevent flakes in the test due to transient queuing delays. + // Use a generous buffer to focus the test on distribution logic rather than backpressure. EnqueueChannelBufferSize: numRequests, - DefaultRequestTTL: 1 * time.Second, + DefaultRequestTTL: 5 * time.Second, ExpiryCleanupInterval: 100 * time.Millisecond, }, mockRegistry) + // Act: Hammer the controller concurrently. var wg sync.WaitGroup wg.Add(numGoroutines) outcomes := make(chan types.QueueOutcome, numRequests) - for range numGoroutines { + + for i := range numGoroutines { + goroutineID := i go func() { defer wg.Done() - for range numRequests / numGoroutines { - req := newTestRequest(logr.NewContext(context.Background(), logr.Discard()), defaultFlowKey) - outcome, err := h.fc.EnqueueAndWait(req) + for j := range numRequests / numGoroutines { + req := newTestRequest(defaultFlowKey) + req.IDV = fmt.Sprintf("req-distrib-%d-%d", goroutineID, j) + + // Use a reasonable timeout for the individual request context. + reqCtx, cancel := context.WithTimeout(t.Context(), 5*time.Second) + defer cancel() + + ctx := logr.NewContext(reqCtx, logr.Discard()) + outcome, err := h.fc.EnqueueAndWait(ctx, req) + if err != nil { + // Use t.Errorf for concurrent tests to report failures without halting execution. + t.Errorf("EnqueueAndWait failed unexpectedly under load: %v", err) + } + outcomes <- outcome + } + }() + } + + // Wait for all requests to complete. + wg.Wait() + close(outcomes) + + // Assert: All requests should be successfully dispatched. + successCount := 0 + for outcome := range outcomes { + if outcome == types.QueueOutcomeDispatched { + successCount++ + } + } + require.Equal(t, numRequests, successCount, + "all concurrent requests must be dispatched successfully without errors or data races") +} + +// TestFlowController_Concurrency_Backpressure specifically targets the blocking submission path (SubmitOrBlock) by +// configuring the processors with zero buffer capacity. +func TestFlowController_Concurrency_Backpressure(t *testing.T) { + if testing.Short() { + t.Skip("Skipping concurrency integration test in short mode.") + } + t.Parallel() + + const ( + numShards = 2 + numGoroutines = 20 + // Fewer requests than the distribution test, as the blocking path is inherently slower. + numRequests = 40 + ) + + // Arrange: Set up the registry environment. + mockRegistry := setupRegistryForConcurrency(t, numShards, defaultFlowKey) + + // Use the integration harness with a configuration designed to induce backpressure. + h := newIntegrationHarness(t, t.Context(), Config{ + // Zero buffer forces immediate use of SubmitOrBlock if the processor loop is busy. + EnqueueChannelBufferSize: 0, + // Generous TTL to ensure timeouts are not the cause of failure. + DefaultRequestTTL: 10 * time.Second, + ExpiryCleanupInterval: 100 * time.Millisecond, + }, mockRegistry) + + // Act: Concurrently submit requests. + var wg sync.WaitGroup + wg.Add(numGoroutines) + outcomes := make(chan types.QueueOutcome, numRequests) + + for i := range numGoroutines { + goroutineID := i + go func() { + defer wg.Done() + for j := range numRequests / numGoroutines { + req := newTestRequest(defaultFlowKey) + req.IDV = fmt.Sprintf("req-backpressure-%d-%d", goroutineID, j) + + // Use a reasonable timeout for the individual request context to ensure the test finishes promptly if a + // deadlock occurs. + reqCtx, cancel := context.WithTimeout(t.Context(), 5*time.Second) + defer cancel() + + outcome, err := h.fc.EnqueueAndWait(logr.NewContext(reqCtx, logr.Discard()), req) if err != nil { - // Use `t.Errorf` for concurrent tests to avoid halting execution on a single failure. - t.Errorf("EnqueueAndWait failed unexpectedly: %v", err) + t.Errorf("EnqueueAndWait failed unexpectedly under backpressure for request %s: %v", req.ID(), err) } outcomes <- outcome } @@ -819,11 +1301,13 @@ func TestFlowController_Concurrency(t *testing.T) { wg.Wait() close(outcomes) + // Assert: Verify successful dispatch despite high contention and zero buffer. successCount := 0 for outcome := range outcomes { if outcome == types.QueueOutcomeDispatched { successCount++ } } - require.Equal(t, numRequests, successCount, "all concurrent requests should be dispatched successfully") + require.Equal(t, numRequests, successCount, + "all concurrent requests should be dispatched successfully even under high contention and zero buffer capacity") } diff --git a/pkg/epp/flowcontrol/controller/internal/item.go b/pkg/epp/flowcontrol/controller/internal/item.go index d5bdaaf2e..31a28d473 100644 --- a/pkg/epp/flowcontrol/controller/internal/item.go +++ b/pkg/epp/flowcontrol/controller/internal/item.go @@ -17,97 +17,171 @@ limitations under the License. package internal import ( + "context" + "errors" + "fmt" "sync" + "sync/atomic" "time" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" ) -// FinalState encapsulates the terminal outcome of a `FlowItem`'s lifecycle. -// It is sent over the item's `Done()` channel exactly once. +// FinalState encapsulates the terminal outcome of a FlowItem's lifecycle. type FinalState struct { Outcome types.QueueOutcome Err error } -// FlowItem is the internal representation of a request managed by the `FlowController`. +// FlowItem is the internal representation of a request managed by the Flow Controller. +// +// # Lifecycle Management +// +// Finalization (determining outcome) can be initiated by the Controller (e.g., Context expiry) or the Processor (e.g., +// Dispatch/Reject). It sets the outcome and signals the waiting goroutine. +// +// # Synchronization +// +// Atomic operations synchronize state across the Controller and Processor goroutines: +// - finalState (atomic.Pointer): Safely publishes the outcome. +// - handle (atomic.Pointer): Safely publishes the queue admission status. type FlowItem struct { - // --- Immutable fields (set at creation) --- + // --- Immutable fields during a single lifecycle --- enqueueTime time.Time effectiveTTL time.Duration originalRequest types.FlowControlRequest - handle types.QueueItemHandle - // --- Finalization state (protected by onceFinalize) --- + // --- Synchronized State --- - // done is closed exactly once when the item is finalized. - // The closing of this channel establishes a "happens-before" memory barrier, guaranteeing that writes to `outcome` - // and `err` are visible to any goroutine that has successfully read from `done`. - done chan FinalState + // handle stores the types.QueueItemHandle atomically. + // Written by the Processor (SetHandle) when admitted. + // Read by inferOutcome (called by Finalize) to infer the outcome (Rejected vs. Evicted). + // Distinguishing between pre-admission (Rejection) and post-admission (Eviction) during asynchronous finalization + // relies on whether this handle is nil or non-nil. + handle atomic.Pointer[types.QueueItemHandle] - // finalState is safely visible to any goroutine after it has confirmed the channel is closed. - finalState FinalState + // finalState holds the result of the finalization. Stored atomically once. + // Use FinalState() for safe access. + finalState atomic.Pointer[FinalState] - // onceFinalize ensures the `finalize()` logic is idempotent. + // --- Finalization Signaling --- + + // done is the channel used to signal the completion of the item's lifecycle. + // Buffered to size 1 to prevent Finalize from blocking. + done chan *FinalState + + // onceFinalize ensures the finalization logic runs exactly once per lifecycle. onceFinalize sync.Once } -// ensure FlowItem implements the interface. var _ types.QueueItemAccessor = &FlowItem{} -// NewItem creates a new `FlowItem`. +// NewItem allocates and initializes a new FlowItem for a request lifecycle. func NewItem(req types.FlowControlRequest, effectiveTTL time.Duration, enqueueTime time.Time) *FlowItem { return &FlowItem{ enqueueTime: enqueueTime, effectiveTTL: effectiveTTL, originalRequest: req, - // Buffer to size one, preventing finalizing goroutine (e.g., the dispatcher) from blocking if the waiting - // goroutine has already timed out and is no longer reading. - done: make(chan FinalState, 1), + done: make(chan *FinalState, 1), } } -// EnqueueTime returns the time the item was logically accepted by the `FlowController` for queuing. This is used as the -// basis for TTL calculations. +// EnqueueTime returns the time the item was logically accepted by the FlowController. func (fi *FlowItem) EnqueueTime() time.Time { return fi.enqueueTime } -// EffectiveTTL returns the actual time-to-live assigned to this item by the `FlowController`. +// EffectiveTTL returns the actual time-to-live assigned to this item. func (fi *FlowItem) EffectiveTTL() time.Duration { return fi.effectiveTTL } -// OriginalRequest returns the original, underlying `types.FlowControlRequest` object. +// OriginalRequest returns the original types.FlowControlRequest object. func (fi *FlowItem) OriginalRequest() types.FlowControlRequest { return fi.originalRequest } -// Handle returns the `types.QueueItemHandle` that uniquely identifies this item within a specific queue instance. It -// returns nil if the item has not yet been added to a queue. -func (fi *FlowItem) Handle() types.QueueItemHandle { return fi.handle } +// Done returns a read-only channel that will receive the FinalState pointer exactly once. +func (fi *FlowItem) Done() <-chan *FinalState { return fi.done } -// SetHandle associates a `types.QueueItemHandle` with this item. This method is called by a `framework.SafeQueue` -// implementation immediately after the item is added to the queue. -func (fi *FlowItem) SetHandle(handle types.QueueItemHandle) { fi.handle = handle } +// FinalState returns the FinalState if the item has been finalized, or nil otherwise. +// Safe for concurrent access. +func (fi *FlowItem) FinalState() *FinalState { return fi.finalState.Load() } -// Done returns a channel that is closed when the item has been finalized (e.g., dispatched, rejected, or evicted). -func (fi *FlowItem) Done() <-chan FinalState { - return fi.done +// Handle returns the types.QueueItemHandle for this item within a queue. +// Returns nil if the item is not in a queue. Safe for concurrent access. +func (fi *FlowItem) Handle() types.QueueItemHandle { + ptr := fi.handle.Load() + if ptr == nil { + return nil + } + return *ptr } -// Finalize sets the item's terminal state and signals the waiting goroutine by closing its `done` channel idempotently. -// This method is idempotent and is the single point where an item's lifecycle concludes. -// It is intended to be called only by the component that owns the item's lifecycle, such as a `ShardProcessor`. -func (fi *FlowItem) Finalize(outcome types.QueueOutcome, err error) { +// SetHandle associates a types.QueueItemHandle with this item. Called by the queue implementation (via Processor). +// Safe for concurrent access. +func (fi *FlowItem) SetHandle(handle types.QueueItemHandle) { fi.handle.Store(&handle) } + +// Finalize determines the item's terminal state based on the provided cause (e.g., Context error) and the item's +// current admission status (queued or not). +// +// This method is intended for asynchronous finalization initiated by the Controller (e.g., TTL expiry). +// It is idempotent. +func (fi *FlowItem) Finalize(cause error) { fi.onceFinalize.Do(func() { - finalState := FinalState{Outcome: outcome, Err: err} - fi.finalState = finalState - fi.done <- finalState - close(fi.done) + // Atomically load the handle to determine if the item was admitted to a queue. + // This synchronization is critical for correctly inferring the outcome across goroutines. + isQueued := fi.Handle() != nil + outcome, finalErr := inferOutcome(cause, isQueued) + fi.finalizeInternal(outcome, finalErr) }) } -// isFinalized checks if the item has been finalized without blocking or consuming the final state. -// It is a side-effect-free check used by the `ShardProcessor` as a defensive measure to avoid operating on -// already-completed items. -func (fi *FlowItem) isFinalized() bool { - // A buffered channel of size 1 can be safely and non-blockingly checked by its length. - // If the finalize function has run, it will have sent a value, and the length will be 1. - return len(fi.done) > 0 +// FinalizeWithOutcome sets the item's terminal state explicitly. +// +// This method is intended for synchronous finalization by the Processor (Dispatch, Reject) or the Controller +// (Distribution failure). +// It is idempotent. +func (fi *FlowItem) FinalizeWithOutcome(outcome types.QueueOutcome, err error) { + fi.onceFinalize.Do(func() { + fi.finalizeInternal(outcome, err) + }) +} + +// finalizeInternal is the core finalization logic. It must be called within the sync.Once.Do block. +// It captures the state, stores it atomically, and signals the Done channel. +func (fi *FlowItem) finalizeInternal(outcome types.QueueOutcome, err error) { + finalState := &FinalState{ + Outcome: outcome, + Err: err, + } + + // Atomically store the pointer. This is the critical memory barrier that publishes the state safely. + fi.finalState.Store(finalState) + + fi.done <- finalState + close(fi.done) +} + +// inferOutcome determines the correct QueueOutcome and Error based on the cause of finalization and whether the item +// was already admitted to a queue. +func inferOutcome(cause error, isQueued bool) (types.QueueOutcome, error) { + var specificErr error + var outcomeIfEvicted types.QueueOutcome + switch { + case errors.Is(cause, types.ErrTTLExpired) || errors.Is(cause, context.DeadlineExceeded): + specificErr = types.ErrTTLExpired + outcomeIfEvicted = types.QueueOutcomeEvictedTTL + case errors.Is(cause, context.Canceled): + specificErr = fmt.Errorf("%w: %w", types.ErrContextCancelled, cause) + outcomeIfEvicted = types.QueueOutcomeEvictedContextCancelled + default: + // Handle other potential causes (e.g., custom context errors). + specificErr = cause + outcomeIfEvicted = types.QueueOutcomeEvictedOther + } + + if isQueued { + // The item was in the queue when it expired/cancelled. + return outcomeIfEvicted, fmt.Errorf("%w: %w", types.ErrEvicted, specificErr) + } + + // The item was not yet in the queue (e.g., buffered in enqueueChan). + // We treat this as a rejection, as it never formally consumed queue capacity. + return types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, specificErr) } diff --git a/pkg/epp/flowcontrol/controller/internal/item_test.go b/pkg/epp/flowcontrol/controller/internal/item_test.go index 1f713e913..9b7b627c2 100644 --- a/pkg/epp/flowcontrol/controller/internal/item_test.go +++ b/pkg/epp/flowcontrol/controller/internal/item_test.go @@ -31,12 +31,16 @@ import ( func TestFlowItem_New(t *testing.T) { t.Parallel() - req := typesmocks.NewMockFlowControlRequest(100, "req-1", types.FlowKey{}, context.Background()) + req := typesmocks.NewMockFlowControlRequest(100, "req-1", types.FlowKey{}) - item := NewItem(req, time.Minute, time.Now()) + enqueueTime := time.Now() + item := NewItem(req, time.Minute, enqueueTime) require.NotNil(t, item, "NewItem should not return a nil item") - assert.False(t, item.isFinalized(), "a new item must not be in a finalized state") + assert.Equal(t, enqueueTime, item.EnqueueTime(), "EnqueueTime should be populated") + assert.Equal(t, time.Minute, item.EffectiveTTL(), "EffectiveTTL should be populated") + assert.Same(t, req, item.OriginalRequest(), "OriginalRequest should be populated") + assert.Nil(t, item.FinalState(), "a new item must not have a final state") select { case <-item.Done(): t.Fatal("Done() channel for a new item must block, but it was closed") @@ -55,21 +59,178 @@ func TestFlowItem_Handle(t *testing.T) { func TestFlowItem_Finalize_Idempotency(t *testing.T) { t.Parallel() - req := typesmocks.NewMockFlowControlRequest(100, "req-1", types.FlowKey{}, context.Background()) - item := NewItem(req, time.Minute, time.Now()) - expectedErr := errors.New("first-error") + now := time.Now() + req := typesmocks.NewMockFlowControlRequest(100, "req-1", types.FlowKey{}) - item.Finalize(types.QueueOutcomeEvictedTTL, expectedErr) - item.Finalize(types.QueueOutcomeDispatched, nil) // Should take no effect + testCases := []struct { + name string + firstCall func(item *FlowItem) + secondCall func(item *FlowItem) + expectedOutcome types.QueueOutcome + expectedErrIs error + }{ + { + name: "Finalize then Finalize", + firstCall: func(item *FlowItem) { + item.Finalize(types.ErrTTLExpired) + }, + secondCall: func(item *FlowItem) { + item.Finalize(context.Canceled) + }, + expectedOutcome: types.QueueOutcomeRejectedOther, + expectedErrIs: types.ErrTTLExpired, + }, + { + name: "Finalize then FinalizeWithOutcome", + firstCall: func(item *FlowItem) { + item.Finalize(types.ErrTTLExpired) + }, + secondCall: func(item *FlowItem) { + item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) + }, + expectedOutcome: types.QueueOutcomeRejectedOther, + expectedErrIs: types.ErrTTLExpired, + }, + { + name: "FinalizeWithOutcome then FinalizeWithOutcome", + firstCall: func(item *FlowItem) { + item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) + }, + secondCall: func(item *FlowItem) { + item.FinalizeWithOutcome(types.QueueOutcomeRejectedCapacity, errors.New("rejected")) + }, + expectedOutcome: types.QueueOutcomeDispatched, + expectedErrIs: nil, + }, + { + name: "FinalizeWithOutcome then Finalize", + firstCall: func(item *FlowItem) { + item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) + }, + secondCall: func(item *FlowItem) { + item.Finalize(types.ErrTTLExpired) + }, + expectedOutcome: types.QueueOutcomeDispatched, + expectedErrIs: nil, + }, + } - assert.True(t, item.isFinalized(), "item must be in a finalized state after a call to finalize()") - select { - case finalState, ok := <-item.Done(): - require.True(t, ok, "Done() channel should be readable with a value, not just closed") - assert.Equal(t, types.QueueOutcomeEvictedTTL, finalState.Outcome, - "the outcome from Done() must match the first finalized outcome") - assert.Equal(t, expectedErr, finalState.Err, "the error from Done() must match the first finalized error") - case <-time.After(50 * time.Millisecond): - t.Fatal("Done() channel must not block after finalization") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + item := NewItem(req, time.Minute, now) + + // First call + tc.firstCall(item) + + // Second call + tc.secondCall(item) + + // Check FinalState() + finalState := item.FinalState() + require.NotNil(t, finalState, "FinalState should not be nil") + assert.Equal(t, tc.expectedOutcome, finalState.Outcome, "Outcome should match the first call") + if tc.expectedErrIs != nil { + assert.ErrorIs(t, finalState.Err, tc.expectedErrIs, "Error should match the first call") + } else { + assert.NoError(t, finalState.Err, "Error should be nil") + } + + // Check Done channel + select { + case state, ok := <-item.Done(): + require.True(t, ok, "Done channel should be readable") + assert.Equal(t, tc.expectedOutcome, state.Outcome, "Done channel outcome should match the first call") + if tc.expectedErrIs != nil { + assert.ErrorIs(t, state.Err, tc.expectedErrIs, "Done channel error should match the first call") + } else { + assert.NoError(t, state.Err, "Done channel error should be nil") + } + case <-time.After(50 * time.Millisecond): + t.Fatal("Done channel should have received the state") + } + }) + } +} + +func TestFlowItem_Finalize_InferOutcome(t *testing.T) { + t.Parallel() + now := time.Now() + + testCases := []struct { + name string + cause error + isQueued bool + expectOutcome types.QueueOutcome + expectErrIs error + }{ + { + name: "queued TTL expired", + cause: types.ErrTTLExpired, + isQueued: true, + expectOutcome: types.QueueOutcomeEvictedTTL, + expectErrIs: types.ErrTTLExpired, + }, + { + name: "queued context cancelled", + cause: context.Canceled, + isQueued: true, + expectOutcome: types.QueueOutcomeEvictedContextCancelled, + expectErrIs: types.ErrContextCancelled, + }, + { + name: "queued other error", + cause: errors.New("other cause"), + isQueued: true, + expectOutcome: types.QueueOutcomeEvictedOther, + expectErrIs: types.ErrEvicted, + }, + { + name: "not queued TTL expired", + cause: types.ErrTTLExpired, + isQueued: false, + expectOutcome: types.QueueOutcomeRejectedOther, + expectErrIs: types.ErrTTLExpired, + }, + { + name: "not queued context cancelled", + cause: context.Canceled, + isQueued: false, + expectOutcome: types.QueueOutcomeRejectedOther, + expectErrIs: types.ErrContextCancelled, + }, + { + name: "nil cause queued", + cause: nil, + isQueued: true, + expectOutcome: types.QueueOutcomeEvictedOther, + expectErrIs: types.ErrEvicted, + }, + { + name: "nil cause not queued", + cause: nil, + isQueued: false, + expectOutcome: types.QueueOutcomeRejectedOther, + expectErrIs: types.ErrRejected, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + req := typesmocks.NewMockFlowControlRequest(100, "req-1", types.FlowKey{}) + item := NewItem(req, time.Minute, now) + if tc.isQueued { + item.SetHandle(&typesmocks.MockQueueItemHandle{}) + } + + item.Finalize(tc.cause) + + finalState := item.FinalState() + require.NotNil(t, finalState, "FinalState should not be nil") + assert.Equal(t, tc.expectOutcome, finalState.Outcome, "Unexpected outcome") + require.Error(t, finalState.Err, "An error should be set") + assert.ErrorIs(t, finalState.Err, tc.expectErrIs, "Unexpected error type") + }) } } diff --git a/pkg/epp/flowcontrol/controller/internal/processor.go b/pkg/epp/flowcontrol/controller/internal/processor.go index 9b6bb705f..e3cccc196 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor.go +++ b/pkg/epp/flowcontrol/controller/internal/processor.go @@ -27,7 +27,6 @@ import ( "github.com/go-logr/logr" "k8s.io/utils/clock" - "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework" @@ -44,106 +43,116 @@ const maxCleanupWorkers = 4 // This is used as a signal for the `controller.FlowController`'s "fast failover" logic. var ErrProcessorBusy = errors.New("shard processor is busy") -// ShardProcessor is the core worker of the `controller.FlowController`. -// It is paired one-to-one with a `contracts.RegistryShard` instance and is responsible for all request lifecycle -// operations on that shard. It acts as the "data plane" worker that executes against the concurrent-safe state provided -// by its shard. +// ShardProcessor is the core worker of the FlowController. // -// # Concurrency Model: The Single-Writer Actor +// It is paired one-to-one with a RegistryShard instance and is responsible for all request lifecycle operations on that +// shard, from the point an item is successfully submitted to it. // -// To ensure correctness and high performance, the processor uses a single-goroutine, actor-based model. The main `Run` -// loop is the sole "writer" for all state-mutating operations, particularly enqueueing. This makes complex transactions -// inherently atomic without coarse-grained locks. +// # Request Lifecycle Management & Ownership +// +// The ShardProcessor takes ownership of a FlowItem only after it has been successfully sent to its internal enqueueChan +// via Submit or SubmitOrBlock (i.e., when these methods return nil). +// Once the Processor takes ownership, it is solely responsible for ensuring that item.Finalize() or +// item.FinalizeWithOutcome() is called exactly once for that item, under all circumstances (dispatch, rejection, sweep, +// or shutdown). +// +// If Submit or SubmitOrBlock return an error, ownership remains with the caller (the Controller), which must then +// handle the finalization. // -// # Concurrency Guarantees +// # Concurrency Model // -// 1. Safe Enqueueing: The "check-then-act" sequence for capacity is safe because it is only ever performed by the -// single `Run` goroutine. -// 2. Idempotent Finalization: The primary internal race condition is between the main `dispatchCycle` and the -// background `runExpiryCleanup` goroutine, both of which might try to finalize an item. This is resolved by the -// `FlowItem.Finalize` method, which uses `sync.Once` to guarantee that only the first attempt to finalize an item -// succeeds. +// To ensure correctness and high performance, the processor uses a single-goroutine, actor-based model. The main run +// loop is the sole writer for all state-mutating operations. This makes complex transactions (like capacity checks) +// inherently atomic without coarse-grained locks. type ShardProcessor struct { - shard contracts.RegistryShard - saturationDetector contracts.SaturationDetector - clock clock.Clock - expiryCleanupInterval time.Duration - logger logr.Logger + shard contracts.RegistryShard + saturationDetector contracts.SaturationDetector + clock clock.WithTicker + cleanupSweepInterval time.Duration + logger logr.Logger + + // lifecycleCtx controls the processor's lifetime. Monitored by Submit* methods for safe shutdown. + lifecycleCtx context.Context - // enqueueChan is the entry point for new requests to be processed by this shard's `Run` loop. + // enqueueChan is the entry point for new requests. enqueueChan chan *FlowItem - // wg is used to wait for background tasks like expiry cleanup to complete on shutdown. + + // wg is used to wait for background tasks (cleanup sweep) to complete on shutdown. wg sync.WaitGroup isShuttingDown atomic.Bool shutdownOnce sync.Once } -// NewShardProcessor creates a new `ShardProcessor` instance. +// NewShardProcessor creates a new ShardProcessor instance. func NewShardProcessor( + ctx context.Context, shard contracts.RegistryShard, saturationDetector contracts.SaturationDetector, - clock clock.Clock, - expiryCleanupInterval time.Duration, + clock clock.WithTicker, + cleanupSweepInterval time.Duration, enqueueChannelBufferSize int, logger logr.Logger, ) *ShardProcessor { return &ShardProcessor{ - shard: shard, - saturationDetector: saturationDetector, - clock: clock, - expiryCleanupInterval: expiryCleanupInterval, - logger: logger, - // A buffered channel decouples the processor from the distributor, allowing for a fast, asynchronous handoff of new - // requests. - enqueueChan: make(chan *FlowItem, enqueueChannelBufferSize), + shard: shard, + saturationDetector: saturationDetector, + clock: clock, + cleanupSweepInterval: cleanupSweepInterval, + logger: logger, + lifecycleCtx: ctx, + enqueueChan: make(chan *FlowItem, enqueueChannelBufferSize), } } -// Submit attempts a non-blocking handoff of an item to the processor's internal channel for asynchronous processing. +// Submit attempts a non-blocking handoff of an item to the processor's internal enqueue channel. // -// It returns nil if the item was accepted by the processor, or if the processor is shutting down (in which case the -// item is immediately finalized with a shutdown error). In both cases, a nil return means the item's lifecycle has been -// handled by this processor and the caller should not retry. -// It returns `ErrProcessorBusy` if the processor's channel is momentarily full, signaling that the caller should try -// another processor. +// Ownership Contract: +// - Returns nil: The item was successfully handed off. +// The ShardProcessor takes responsibility for calling Finalize on the item. +// - Returns error: The item was not handed off. +// Ownership of the FlowItem remains with the caller, who is responsible for calling Finalize. +// +// Possible errors: +// - ErrProcessorBusy: The processor's input channel is full. +// - types.ErrFlowControllerNotRunning: The processor is shutting down. func (sp *ShardProcessor) Submit(item *FlowItem) error { if sp.isShuttingDown.Load() { - item.Finalize(types.QueueOutcomeRejectedOther, - fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning)) - return nil // Success from the caller's perspective; the item is terminal. + return types.ErrFlowControllerNotRunning } - - select { + select { // The default case makes this select non-blocking. case sp.enqueueChan <- item: - return nil // Success + return nil // Ownership transferred. + case <-sp.lifecycleCtx.Done(): + return types.ErrFlowControllerNotRunning default: - // The channel buffer is full, signaling transient backpressure. return ErrProcessorBusy } } -// SubmitOrBlock performs a blocking submission of an item to the processor's internal channel. -// It will wait until either the submission succeeds or the provided context is cancelled. +// SubmitOrBlock performs a blocking handoff of an item to the processor's internal enqueue channel. +// It waits until the item is handed off, the caller's context is cancelled, or the processor shuts down. // -// This method is the fallback used by the distributor when all processors are busy, providing graceful backpressure -// instead of immediate rejection. +// Ownership Contract: +// - Returns nil: The item was successfully handed off. +// The ShardProcessor takes responsibility for calling Finalize on the item. +// - Returns error: The item was not handed off. +// Ownership of the FlowItem remains with the caller, who is responsible for calling Finalize. // -// It returns the `ctx.Err()` if the context is cancelled during the wait. +// Possible errors: +// - ctx.Err(): The provided context was cancelled or its deadline exceeded. +// - types.ErrFlowControllerNotRunning: The processor is shutting down. func (sp *ShardProcessor) SubmitOrBlock(ctx context.Context, item *FlowItem) error { if sp.isShuttingDown.Load() { - // Here, we return an error because the caller, expecting to block, was prevented from doing so by the shutdown. - // This is a failure of the operation. - item.Finalize(types.QueueOutcomeRejectedOther, - fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning)) return types.ErrFlowControllerNotRunning } - select { + select { // The absence of a default case makes this call blocking. case sp.enqueueChan <- item: - return nil // Success + return nil // Ownership transferred. case <-ctx.Done(): - // The caller's context was cancelled while we were blocked. return ctx.Err() + case <-sp.lifecycleCtx.Done(): + return types.ErrFlowControllerNotRunning } } @@ -155,7 +164,7 @@ func (sp *ShardProcessor) Run(ctx context.Context) { defer sp.logger.V(logutil.DEFAULT).Info("Shard processor run loop stopped.") sp.wg.Add(1) - go sp.runExpiryCleanup(ctx) + go sp.runCleanupSweep(ctx) // This is the main worker loop. It continuously processes incoming requests and dispatches queued requests until the // context is cancelled. The `select` statement has three cases: @@ -204,70 +213,54 @@ func (sp *ShardProcessor) enqueue(item *FlowItem) { req := item.OriginalRequest() key := req.FlowKey() - logger := log.FromContext(req.Context()).WithName("enqueue").WithValues( - "flowKey", key, - "flowID", key.ID, - "priority", key.Priority, - "reqID", req.ID(), - "reqByteSize", req.ByteSize(), - ) + // --- Optimistic External Finalization Check --- + // Check if the item was finalized by the Controller (due to TTL/cancellation) while it was buffered in enqueueChan. + // This is an optimistic check to avoid unnecessary processing on items already considered dead. + // The ultimate guarantee of cleanup for any races is the runCleanupSweep mechanism. + if finalState := item.FinalState(); finalState != nil { + sp.logger.V(logutil.TRACE).Info("Item finalized externally before processing, discarding.", + "outcome", finalState.Outcome, "err", finalState.Err, "flowKey", key, "reqID", req.ID()) + return + } + // --- Configuration Validation --- managedQ, err := sp.shard.ManagedQueue(key) if err != nil { finalErr := fmt.Errorf("configuration error: failed to get queue for flow key %s: %w", key, err) - logger.Error(finalErr, "Rejecting item.") - item.Finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) + sp.logger.Error(finalErr, "Rejecting item.", "flowKey", key, "reqID", req.ID()) + item.FinalizeWithOutcome(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) return } band, err := sp.shard.PriorityBandAccessor(key.Priority) if err != nil { finalErr := fmt.Errorf("configuration error: failed to get priority band for priority %d: %w", key.Priority, err) - logger.Error(finalErr, "Rejecting item.") - item.Finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) + sp.logger.Error(finalErr, "Rejecting item.", "flowKey", key, "reqID", req.ID()) + item.FinalizeWithOutcome(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) return } - logger = logger.WithValues("priorityName", band.PriorityName()) + // --- Capacity Check --- + // This check is safe because it is performed by the single-writer Run goroutine. if !sp.hasCapacity(key.Priority, req.ByteSize()) { - // This is an expected outcome, not a system error. Log at the default level with rich context. - stats := sp.shard.Stats() - bandStats := stats.PerPriorityBandStats[key.Priority] - logger.V(logutil.DEFAULT).Info("Rejecting request, queue at capacity", - "outcome", types.QueueOutcomeRejectedCapacity, - "shardTotalBytes", stats.TotalByteSize, - "shardCapacityBytes", stats.TotalCapacityBytes, - "bandTotalBytes", bandStats.ByteSize, - "bandCapacityBytes", bandStats.CapacityBytes, - ) - item.Finalize(types.QueueOutcomeRejectedCapacity, fmt.Errorf("%w: %w", types.ErrRejected, types.ErrQueueAtCapacity)) - return - } - - // This is an optimistic check to prevent a needless add/remove cycle for an item that was finalized (e.g., context - // cancelled) during the handoff to this processor. A race condition still exists where an item can be finalized - // after this check but before the `Add` call completes. - // - // This is considered acceptable because: - // 1. The race window is extremely small. - // 2. The background `runExpiryCleanup` goroutine acts as the ultimate guarantor of correctness, as it will - // eventually find and evict any finalized item that slips through this check and is added to a queue. - if item.isFinalized() { - finalState := item.finalState - outcome, err := finalState.Outcome, finalState.Err - logger.V(logutil.VERBOSE).Info("Item finalized before adding to queue, ignoring.", "outcome", outcome, "err", err) + sp.logger.V(logutil.DEBUG).Info("Rejecting request, queue at capacity", + "flowKey", key, "reqID", req.ID(), "priorityName", band.PriorityName(), "reqByteSize", req.ByteSize()) + item.FinalizeWithOutcome(types.QueueOutcomeRejectedCapacity, fmt.Errorf("%w: %w", + types.ErrRejected, types.ErrQueueAtCapacity)) return } - // This is the point of commitment. After this call, the item is officially in the queue and is the responsibility of - // the dispatch or cleanup loops to finalize. + // --- Commitment Point --- + // The item is admitted. The ManagedQueue.Add implementation is responsible for calling item.SetHandle() atomically. if err := managedQ.Add(item); err != nil { finalErr := fmt.Errorf("failed to add item to queue for flow key %s: %w", key, err) - logger.Error(finalErr, "Rejecting item.") - item.Finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) + sp.logger.Error(finalErr, "Rejecting item post-admission.", + "flowKey", key, "reqID", req.ID(), "priorityName", band.PriorityName()) + item.FinalizeWithOutcome(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) return } - logger.V(logutil.TRACE).Info("Item enqueued.") + sp.logger.V(logutil.TRACE).Info("Item enqueued.", + "flowKey", key, "reqID", req.ID(), "priorityName", band.PriorityName()) } // hasCapacity checks if the shard and the specific priority band have enough capacity to accommodate an item of a given @@ -414,145 +407,102 @@ func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, logger l return fmt.Errorf("failed to remove item %q from queue for flow %s: %w", req.ID(), req.FlowKey(), err) } - // Final check for expiry/cancellation right before dispatch. removedItem := removedItemAcc.(*FlowItem) - isExpired, outcome, expiryErr := checkItemExpiry(removedItem, sp.clock.Now()) - if isExpired { - // Ensure we always have a non-nil error to wrap for consistent logging and error handling. - finalErr := expiryErr - if finalErr == nil { - finalErr = errors.New("item finalized before dispatch") - } - logger.V(logutil.VERBOSE).Info("Item expired at time of dispatch, evicting", "outcome", outcome, - "err", finalErr) - removedItem.Finalize(outcome, fmt.Errorf("%w: %w", types.ErrEvicted, finalErr)) - // Return an error to signal that the dispatch did not succeed. - return fmt.Errorf("item %q expired before dispatch: %w", req.ID(), finalErr) - } - - // Finalize the item as dispatched. - removedItem.Finalize(types.QueueOutcomeDispatched, nil) - logger.V(logutil.TRACE).Info("Item dispatched.") + sp.logger.V(logutil.TRACE).Info("Item dispatched.", "flowKey", req.FlowKey(), "reqID", req.ID()) + removedItem.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) return nil } -// checkItemExpiry provides the authoritative check to determine if an item should be evicted due to TTL expiry or -// context cancellation. -// -// It serves as a safeguard against race conditions. Its first action is to check if the item has already been finalized -// by a competing goroutine (e.g., the cleanup loop finalizing an item the dispatch loop is trying to process).= -// This ensures that the final outcome is decided exactly once. -func checkItemExpiry( - itemAcc types.QueueItemAccessor, - now time.Time, -) (isExpired bool, outcome types.QueueOutcome, err error) { - item := itemAcc.(*FlowItem) - - // This check is a critical defense against race conditions. If another goroutine (e.g., the cleanup loop) has - // already finalized this item, we must respect that outcome. - if item.isFinalized() { - finalState := item.finalState - return true, finalState.Outcome, finalState.Err - } - - // Check if the request's context has been cancelled. - if ctxErr := item.OriginalRequest().Context().Err(); ctxErr != nil { - return true, types.QueueOutcomeEvictedContextCancelled, fmt.Errorf("%w: %w", types.ErrContextCancelled, ctxErr) - } - - // Check if the item has outlived its TTL. - if item.EffectiveTTL() > 0 && now.Sub(item.EnqueueTime()) > item.EffectiveTTL() { - return true, types.QueueOutcomeEvictedTTL, types.ErrTTLExpired - } - - return false, types.QueueOutcomeNotYetFinalized, nil -} - -// runExpiryCleanup starts a background goroutine that periodically scans all queues on the shard for expired items. -func (sp *ShardProcessor) runExpiryCleanup(ctx context.Context) { +// runCleanupSweep starts a background goroutine that periodically scans all queues for externally finalized items +// ("zombie" items) and removes them in batches. +func (sp *ShardProcessor) runCleanupSweep(ctx context.Context) { defer sp.wg.Done() - logger := sp.logger.WithName("runExpiryCleanup") - logger.V(logutil.DEFAULT).Info("Shard expiry cleanup goroutine starting.") - defer logger.V(logutil.DEFAULT).Info("Shard expiry cleanup goroutine stopped.") + logger := sp.logger.WithName("runCleanupSweep") + logger.V(logutil.DEFAULT).Info("Shard cleanup sweep goroutine starting.") + defer logger.V(logutil.DEFAULT).Info("Shard cleanup sweep goroutine stopped.") - ticker := time.NewTicker(sp.expiryCleanupInterval) + ticker := sp.clock.NewTicker(sp.cleanupSweepInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return - case now := <-ticker.C: - sp.cleanupExpired(now) + case <-ticker.C(): + sp.sweepFinalizedItems() } } } -// cleanupExpired performs a single scan of all queues on the shard, removing and finalizing any items that have -// expired. -func (sp *ShardProcessor) cleanupExpired(now time.Time) { - processFn := func(managedQ contracts.ManagedQueue, queueLogger logr.Logger) { - // This predicate identifies items to be removed by the Cleanup call. - predicate := func(item types.QueueItemAccessor) bool { - isExpired, _, _ := checkItemExpiry(item, now) - return isExpired +// sweepFinalizedItems performs a single scan of all queues, removing finalized items in batch and releasing their +// memory. +func (sp *ShardProcessor) sweepFinalizedItems() { + processFn := func(managedQ contracts.ManagedQueue, logger logr.Logger) { + key := managedQ.FlowQueueAccessor().FlowKey() + predicate := func(itemAcc types.QueueItemAccessor) bool { + return itemAcc.(*FlowItem).FinalState() != nil } - removedItems, err := managedQ.Cleanup(predicate) if err != nil { - queueLogger.Error(err, "Error during ManagedQueue Cleanup") + logger.Error(err, "Error during ManagedQueue Cleanup", "flowKey", key) } - - // Finalize all the items that were removed. - sp.finalizeExpiredItems(removedItems, now, queueLogger) + logger.V(logutil.DEBUG).Info("Swept finalized items and released capacity.", + "flowKey", key, "count", len(removedItems)) } - sp.processAllQueuesConcurrently("cleanupExpired", processFn) + sp.processAllQueuesConcurrently("sweepFinalizedItems", processFn) } -// shutdown handles the graceful termination of the processor, ensuring any pending items in the enqueue channel or in -// the queues are finalized correctly. +// shutdown handles the graceful termination of the processor, ensuring all pending items (in channel and queues) are +// Finalized. func (sp *ShardProcessor) shutdown() { sp.shutdownOnce.Do(func() { - // Set the atomic bool so that any new calls to Enqueue will fail fast. sp.isShuttingDown.Store(true) sp.logger.V(logutil.DEFAULT).Info("Shard processor shutting down.") - // Drain the channel BEFORE closing it. This prevents a panic from any goroutine that is currently blocked trying to - // send to the channel. We read until it's empty. - DrainLoop: + DrainLoop: // Drain the enqueueChan to finalize buffered items. for { select { case item := <-sp.enqueueChan: - if item == nil { // This is a safeguard against logic errors in the distributor. + if item == nil { continue } - item.Finalize(types.QueueOutcomeRejectedOther, + // Finalize buffered items. + item.FinalizeWithOutcome(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning)) default: - // The channel is empty, we can now safely close it. break DrainLoop } } - close(sp.enqueueChan) - - // Evict all remaining items from the queues. + // We do not close enqueueChan because external goroutines (Controller) send on it. + // The channel will be garbage collected when the processor terminates. sp.evictAll() }) } -// evictAll drains all queues on the shard and finalizes every item with a shutdown error. +// evictAll drains all queues on the shard, finalizes every item, and releases their memory. func (sp *ShardProcessor) evictAll() { - processFn := func(managedQ contracts.ManagedQueue, queueLogger logr.Logger) { + processFn := func(managedQ contracts.ManagedQueue, logger logr.Logger) { + key := managedQ.FlowQueueAccessor().FlowKey() removedItems, err := managedQ.Drain() if err != nil { - queueLogger.Error(err, "Error during ManagedQueue Drain") + logger.Error(err, "Error during ManagedQueue Drain", "flowKey", key) } - // Finalize all the items that were removed. - getOutcome := func(_ types.QueueItemAccessor) (types.QueueOutcome, error) { - return types.QueueOutcomeEvictedOther, fmt.Errorf("%w: %w", types.ErrEvicted, types.ErrFlowControllerNotRunning) + outcome := types.QueueOutcomeEvictedOther + errShutdown := fmt.Errorf("%w: %w", types.ErrEvicted, types.ErrFlowControllerNotRunning) + for _, i := range removedItems { + item, ok := i.(*FlowItem) + if !ok { + logger.Error(fmt.Errorf("internal error: unexpected type %T", i), + "Panic condition detected during shutdown", "flowKey", key) + continue + } + + // Finalization is idempotent; safe to call even if already finalized externally. + item.FinalizeWithOutcome(outcome, errShutdown) + logger.V(logutil.TRACE).Info("Item evicted during shutdown.", + "flowKey", key, "reqID", item.OriginalRequest().ID()) } - sp.finalizeItems(removedItems, queueLogger, getOutcome) } sp.processAllQueuesConcurrently("evictAll", processFn) } @@ -620,38 +570,3 @@ func (sp *ShardProcessor) processAllQueuesConcurrently( close(tasks) // Close the channel to signal workers to exit. wg.Wait() // Wait for all workers to finish. } - -// finalizeItems is a helper to iterate over a slice of items, safely cast them, and finalize them with an outcome -// determined by the `getOutcome` function. -func (sp *ShardProcessor) finalizeItems( - items []types.QueueItemAccessor, - logger logr.Logger, - getOutcome func(item types.QueueItemAccessor) (types.QueueOutcome, error), -) { - for _, i := range items { - item, ok := i.(*FlowItem) - if !ok { - unexpectedItemErr := fmt.Errorf("internal error: item %q of type %T is not a *FlowItem", - i.OriginalRequest().ID(), i) - logger.Error(unexpectedItemErr, "Panic condition detected during finalization", "item", i) - continue - } - - outcome, err := getOutcome(i) - item.Finalize(outcome, err) - logger.V(logutil.TRACE).Info("Item finalized", "reqID", item.OriginalRequest().ID(), - "outcome", outcome, "err", err) - } -} - -// finalizeExpiredItems is a specialized version of finalizeItems for items that are known to be expired. -// It determines the precise reason for expiry and finalizes the item accordingly. -func (sp *ShardProcessor) finalizeExpiredItems(items []types.QueueItemAccessor, now time.Time, logger logr.Logger) { - getOutcome := func(item types.QueueItemAccessor) (types.QueueOutcome, error) { - // We don't need the `isExpired` boolean here because we know it's true, but this function conveniently returns the - // precise outcome and error. - _, outcome, expiryErr := checkItemExpiry(item, now) - return outcome, fmt.Errorf("%w: %w", types.ErrEvicted, expiryErr) - } - sp.finalizeItems(items, logger, getOutcome) -} diff --git a/pkg/epp/flowcontrol/controller/internal/processor_test.go b/pkg/epp/flowcontrol/controller/internal/processor_test.go index 4c31d7ae4..2280b82d7 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor_test.go +++ b/pkg/epp/flowcontrol/controller/internal/processor_test.go @@ -100,6 +100,7 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn queues: make(map[types.FlowKey]*mocks.MockManagedQueue), priorityFlows: make(map[int][]types.FlowKey), } + h.ctx, h.cancel = context.WithCancel(context.Background()) // Wire up the harness to provide the mock implementations for the shard's dependencies. h.ManagedQueueFunc = h.managedQueue @@ -118,7 +119,14 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn } } - h.processor = NewShardProcessor(h, h.saturationDetector, h.clock, expiryCleanupInterval, 100, h.logger) + h.processor = NewShardProcessor( + h.ctx, + h, + h.saturationDetector, + h.clock, + expiryCleanupInterval, + 100, + h.logger) require.NotNil(t, h.processor, "NewShardProcessor should not return nil") t.Cleanup(func() { h.Stop() }) @@ -170,8 +178,7 @@ func (h *testHarness) waitForFinalization(item *FlowItem) (types.QueueOutcome, e // newTestItem creates a new FlowItem for testing purposes. func (h *testHarness) newTestItem(id string, key types.FlowKey, ttl time.Duration) *FlowItem { h.t.Helper() - ctx := log.IntoContext(context.Background(), h.logger) - req := typesmocks.NewMockFlowControlRequest(100, id, key, ctx) + req := typesmocks.NewMockFlowControlRequest(100, id, key) return NewItem(req, ttl, h.clock.Now()) } @@ -365,61 +372,11 @@ func TestShardProcessor(t *testing.T) { h.Start() h.Go() h.Stop() // Stop the processor, then immediately try to enqueue. - require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail, even on shutdown") + require.ErrorIs(t, h.processor.Submit(item), types.ErrFlowControllerNotRunning, + "Submit should return ErrFlowControllerNotRunning on shutdown") // --- ASSERT --- - outcome, err := h.waitForFinalization(item) - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "The outcome should be RejectedOther") - require.Error(t, err, "An eviction on shutdown should produce an error") - assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "The error should be of type ErrFlowControllerNotRunning") - }) - - t.Run("should evict item on TTL expiry via background cleanup", func(t *testing.T) { - t.Parallel() - // --- ARRANGE --- - h := newTestHarness(t, testCleanupTick) - item := h.newTestItem("req-expired-evict", testFlow, testShortTTL) - h.addQueue(testFlow) - - // --- ACT --- - h.Start() - require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail") - h.Go() - - h.clock.Step(testShortTTL * 2) // Let time pass for the item to expire. - // Manually invoke the cleanup logic to simulate a tick of the cleanup loop deterministically. - h.processor.cleanupExpired(h.clock.Now()) - - // --- ASSERT --- - outcome, err := h.waitForFinalization(item) - assert.Equal(t, types.QueueOutcomeEvictedTTL, outcome, "The final outcome should be EvictedTTL") - require.Error(t, err, "A TTL eviction should produce an error") - assert.ErrorIs(t, err, types.ErrTTLExpired, "The error should be of type ErrTTLExpired") - }) - - t.Run("should evict item on context cancellation", func(t *testing.T) { - t.Parallel() - // --- ARRANGE --- - h := newTestHarness(t, testCleanupTick) - ctx, cancel := context.WithCancel(context.Background()) - req := typesmocks.NewMockFlowControlRequest(100, "req-ctx-cancel", testFlow, ctx) - item := NewItem(req, testTTL, h.clock.Now()) - h.addQueue(testFlow) - - // --- ACT --- - h.Start() - require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail") - h.Go() - cancel() // Cancel the context after the item is enqueued. - // Manually invoke the cleanup logic to deterministically check for the cancelled context. - h.processor.cleanupExpired(h.clock.Now()) - - // --- ASSERT --- - outcome, err := h.waitForFinalization(item) - assert.Equal(t, types.QueueOutcomeEvictedContextCancelled, outcome, - "The outcome should be EvictedContextCancelled") - require.Error(t, err, "A context cancellation eviction should produce an error") - assert.ErrorIs(t, err, types.ErrContextCancelled, "The error should be of type ErrContextCancelled") + assert.Nil(t, item.FinalState(), "Item should not be finalized by the processor") }) t.Run("should evict a queued item on shutdown", func(t *testing.T) { @@ -444,7 +401,8 @@ func TestShardProcessor(t *testing.T) { outcome, err := h.waitForFinalization(item) assert.Equal(t, types.QueueOutcomeEvictedOther, outcome, "The outcome should be EvictedOther") require.Error(t, err, "An eviction on shutdown should produce an error") - assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "The error should be of type ErrFlowControllerNotRunning") + assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, + "The error should be of type ErrFlowControllerNotRunning") }) t.Run("should handle concurrent enqueues and dispatch all items", func(t *testing.T) { @@ -454,7 +412,7 @@ func TestShardProcessor(t *testing.T) { const numConcurrentItems = 20 q := h.addQueue(testFlow) itemsToTest := make([]*FlowItem, 0, numConcurrentItems) - for i := 0; i < numConcurrentItems; i++ { + for i := range numConcurrentItems { item := h.newTestItem(fmt.Sprintf("req-concurrent-%d", i), testFlow, testTTL) itemsToTest = append(itemsToTest, item) } @@ -495,16 +453,26 @@ func TestShardProcessor(t *testing.T) { // Use channels to pause the dispatch cycle right before it would remove the item. policyCanProceed := make(chan struct{}) itemIsBeingDispatched := make(chan struct{}) + var signalOnce sync.Once + var removedItem types.QueueItemAccessor require.NoError(t, q.Add(item)) // Add the item directly to the queue. // Override the queue's `RemoveFunc` to pause the dispatch goroutine at a critical moment. q.RemoveFunc = func(h types.QueueItemHandle) (types.QueueItemAccessor, error) { - close(itemIsBeingDispatched) // 1. Signal that dispatch is happening. - <-policyCanProceed // 2. Wait for the test to tell us to continue. - // 4. After we unblock, the item will have already been finalized by the cleanup logic, so we simulate the - // real-world outcome of a failed remove. - return nil, fmt.Errorf("item with handle %v not found", h) + var err error + signalOnce.Do(func() { + removedItem = item + close(itemIsBeingDispatched) // 1. Signal that dispatch is happening. + <-policyCanProceed // 2. Wait for the test to tell us to continue. + // 4. After we unblock, the item will have already been finalized by the cleanup logic. + // We simulate the item no longer being found. + err = fmt.Errorf("item with handle %v not found", h) + }) + if removedItem == item { + return item, nil // Return the item on the first call + } + return nil, err // Return error on subsequent calls } // --- ACT --- @@ -513,20 +481,23 @@ func TestShardProcessor(t *testing.T) { h.Go() // Wait for the dispatch cycle to select our item and pause inside our mock `RemoveFunc`. - <-itemIsBeingDispatched + select { + case <-itemIsBeingDispatched: + case <-time.After(testWaitTimeout): + t.Fatal("Timed out waiting for item to be dispatched") + } // 3. The dispatch goroutine is now paused. We can now safely win the "race" by running cleanup logic. h.clock.Step(testShortTTL * 2) - h.processor.cleanupExpired(h.clock.Now()) // This will remove and finalize the item. + item.Finalize(types.ErrTTLExpired) // This will finalize the item with RejectedOther. - // 5. Un-pause the dispatch goroutine. It will now fail to remove the item and the `dispatchCycle` will - // correctly conclude without finalizing the item a second time. + // 5. Un-pause the dispatch goroutine. close(policyCanProceed) // --- ASSERT --- - // The item's final state should be from the cleanup logic (EvictedTTL), not the dispatch logic. + // The item's final state should be from the Finalize call above. outcome, err := h.waitForFinalization(item) - assert.Equal(t, types.QueueOutcomeEvictedTTL, outcome, "The outcome should be EvictedTTL from the cleanup routine") + assert.Equal(t, types.QueueOutcomeEvictedTTL, outcome, "The outcome should be EvictedTTL from the Finalize call") require.Error(t, err, "A TTL eviction should produce an error") assert.ErrorIs(t, err, types.ErrTTLExpired, "The error should be of type ErrTTLExpired") }) @@ -594,9 +565,10 @@ func TestShardProcessor(t *testing.T) { h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) { return nil, testErr } }, assert: func(t *testing.T, h *testHarness, item *FlowItem) { - assert.Equal(t, types.QueueOutcomeRejectedOther, item.finalState.Outcome, "Outcome should be RejectedOther") - require.Error(t, item.finalState.Err, "An error should be returned") - assert.ErrorIs(t, item.finalState.Err, testErr, "The underlying error should be preserved") + assert.Equal(t, types.QueueOutcomeRejectedOther, item.FinalState().Outcome, + "Outcome should be RejectedOther") + require.Error(t, item.FinalState().Err, "An error should be returned") + assert.ErrorIs(t, item.FinalState().Err, testErr, "The underlying error should be preserved") }, }, { @@ -606,9 +578,10 @@ func TestShardProcessor(t *testing.T) { h.PriorityBandAccessorFunc = func(int) (framework.PriorityBandAccessor, error) { return nil, testErr } }, assert: func(t *testing.T, h *testHarness, item *FlowItem) { - assert.Equal(t, types.QueueOutcomeRejectedOther, item.finalState.Outcome, "Outcome should be RejectedOther") - require.Error(t, item.finalState.Err, "An error should be returned") - assert.ErrorIs(t, item.finalState.Err, testErr, "The underlying error should be preserved") + assert.Equal(t, types.QueueOutcomeRejectedOther, item.FinalState().Outcome, + "Outcome should be RejectedOther") + require.Error(t, item.FinalState().Err, "An error should be returned") + assert.ErrorIs(t, item.FinalState().Err, testErr, "The underlying error should be preserved") }, }, { @@ -618,9 +591,10 @@ func TestShardProcessor(t *testing.T) { mockQueue.AddFunc = func(types.QueueItemAccessor) error { return testErr } }, assert: func(t *testing.T, h *testHarness, item *FlowItem) { - assert.Equal(t, types.QueueOutcomeRejectedOther, item.finalState.Outcome, "Outcome should be RejectedOther") - require.Error(t, item.finalState.Err, "An error should be returned") - assert.ErrorIs(t, item.finalState.Err, testErr, "The underlying error should be preserved") + assert.Equal(t, types.QueueOutcomeRejectedOther, item.FinalState().Outcome, + "Outcome should be RejectedOther") + require.Error(t, item.FinalState().Err, "An error should be returned") + assert.ErrorIs(t, item.FinalState().Err, testErr, "The underlying error should be preserved") }, }, { @@ -640,13 +614,13 @@ func TestShardProcessor(t *testing.T) { item: func() *FlowItem { // Create a pre-finalized item. item := newTestHarness(t, 0).newTestItem("req-finalized", testFlow, testTTL) - item.Finalize(types.QueueOutcomeDispatched, nil) + item.FinalizeWithOutcome(types.QueueOutcomeDispatched, nil) return item }(), assert: func(t *testing.T, h *testHarness, item *FlowItem) { // The item was already finalized, so its state should not change. - assert.Equal(t, types.QueueOutcomeDispatched, item.finalState.Outcome, "Outcome should remain unchanged") - assert.NoError(t, item.finalState.Err, "Error should remain unchanged") + assert.Equal(t, types.QueueOutcomeDispatched, item.FinalState().Outcome, "Outcome should remain unchanged") + assert.NoError(t, item.FinalState().Err, "Error should remain unchanged") }, }, } @@ -905,9 +879,9 @@ func TestShardProcessor(t *testing.T) { // Verify all high-priority items are gone and low-priority items remain. for _, item := range highPrioItems { - assert.Equal(t, types.QueueOutcomeDispatched, item.finalState.Outcome, + assert.Equal(t, types.QueueOutcomeDispatched, item.FinalState().Outcome, "High-priority item should be dispatched") - assert.NoError(t, item.finalState.Err, "Dispatched high-priority item should not have an error") + assert.NoError(t, item.FinalState().Err, "Dispatched high-priority item should not have an error") } assert.Equal(t, numItems, qLow.Len(), "Low-priority queue should still be full") @@ -967,11 +941,12 @@ func TestShardProcessor(t *testing.T) { } }) - t.Run("should evict item that expires at moment of dispatch", func(t *testing.T) { + t.Run("should not dispatch already finalized item", func(t *testing.T) { t.Parallel() // --- ARRANGE --- h := newTestHarness(t, testCleanupTick) - item := h.newTestItem("req-expired-dispatch", testFlow, testShortTTL) + item := h.newTestItem("req-already-finalized", testFlow, testTTL) + item.FinalizeWithOutcome(types.QueueOutcomeRejectedOther, errors.New("already done")) h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) { return &mocks.MockManagedQueue{ @@ -982,43 +957,61 @@ func TestShardProcessor(t *testing.T) { } // --- ACT --- - h.clock.Step(testShortTTL * 2) // Make the item expire. err := h.processor.dispatchItem(item, h.logger) // --- ASSERT --- - // First, check the error returned by `dispatchItem`. - require.Error(t, err, "dispatchItem should return an error for an expired item") - assert.ErrorIs(t, err, types.ErrTTLExpired, "The error should be of type ErrTTLExpired") - - // Second, check the final state of the item itself. - assert.Equal(t, types.QueueOutcomeEvictedTTL, item.finalState.Outcome, - "The item's final outcome should be EvictedTTL") - require.Error(t, item.finalState.Err, "The item's final state should contain an error") - assert.ErrorIs(t, item.finalState.Err, types.ErrTTLExpired, - "The item's final error should be of type ErrTTLExpired") + require.NoError(t, err, "dispatchItem should return no error for an already finalized item") + + // Check the final state of the item itself - it should not have changed. + finalState := item.FinalState() + require.NotNil(t, finalState, "Item must be finalized") + assert.Equal(t, types.QueueOutcomeRejectedOther, finalState.Outcome, + "The item's final outcome should be RejectedOther") + assert.ErrorContains(t, finalState.Err, "already done", + "The error should be the one from the first Finalize call") }) }) t.Run("cleanup and utility methods", func(t *testing.T) { t.Parallel() - t.Run("should remove and finalize expired items", func(t *testing.T) { + t.Run("should sweep externally finalized items", func(t *testing.T) { t.Parallel() // --- ARRANGE --- h := newTestHarness(t, testCleanupTick) - // Create an item that is already expired relative to the cleanup time. - item := h.newTestItem("req-expired", testFlow, 1*time.Millisecond) + item := h.newTestItem("req-external-finalized", testFlow, testTTL) q := h.addQueue(testFlow) - require.NoError(t, q.Add(item)) - cleanupTime := h.clock.Now().Add(10 * time.Millisecond) + require.NoError(t, q.Add(item), "Failed to add item to queue") + + // Externally finalize the item + item.Finalize(context.Canceled) + require.NotNil(t, item.FinalState(), "Item should be finalized") // --- ACT --- - h.processor.cleanupExpired(cleanupTime) + h.processor.sweepFinalizedItems() // --- ASSERT --- - assert.Equal(t, types.QueueOutcomeEvictedTTL, item.finalState.Outcome, "Item outcome should be EvictedTTL") - require.Error(t, item.finalState.Err, "Item should have an error") - assert.ErrorIs(t, item.finalState.Err, types.ErrTTLExpired, "Item error should be ErrTTLExpired") + assert.Equal(t, 0, q.Len(), "Queue should be empty after sweep") + finalState := item.FinalState() + assert.Equal(t, types.QueueOutcomeEvictedContextCancelled, finalState.Outcome, + "Outcome should be EvictedContextCancelled") + assert.ErrorIs(t, finalState.Err, types.ErrContextCancelled, "Error should be ErrContextCancelled") + }) + + t.Run("should not sweep items not finalized", func(t *testing.T) { + t.Parallel() + // --- ARRANGE --- + h := newTestHarness(t, testCleanupTick) + item := h.newTestItem("req-not-finalized", testFlow, testTTL) + q := h.addQueue(testFlow) + require.NoError(t, q.Add(item), "Failed to add item to queue") + + // --- ACT --- + h.processor.sweepFinalizedItems() + + // --- ASSERT --- + assert.Equal(t, 1, q.Len(), "Queue should still contain the item") + assert.Nil(t, item.FinalState(), "Item should not be finalized") }) t.Run("should evict all items on shutdown", func(t *testing.T) { @@ -1033,9 +1026,10 @@ func TestShardProcessor(t *testing.T) { h.processor.evictAll() // --- ASSERT --- - assert.Equal(t, types.QueueOutcomeEvictedOther, item.finalState.Outcome, "Item outcome should be EvictedOther") - require.Error(t, item.finalState.Err, "Item should have an error") - assert.ErrorIs(t, item.finalState.Err, types.ErrFlowControllerNotRunning, + assert.Equal(t, types.QueueOutcomeEvictedOther, item.FinalState().Outcome, + "Item outcome should be EvictedOther") + require.Error(t, item.FinalState().Err, "Item should have an error") + assert.ErrorIs(t, item.FinalState().Err, types.ErrFlowControllerNotRunning, "Item error should be ErrFlowControllerNotRunning") }) @@ -1055,25 +1049,6 @@ func TestShardProcessor(t *testing.T) { }, "processAllQueuesConcurrently should not panic on registry errors") }) - t.Run("should handle items of an unexpected type gracefully during finalization", func(t *testing.T) { - t.Parallel() - // --- ARRANGE --- - h := newTestHarness(t, testCleanupTick) - item := &typesmocks.MockQueueItemAccessor{ - OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "bad-item", testFlow, context.Background()), - } - items := []types.QueueItemAccessor{item} - - // --- ACT & ASSERT --- - // The test passes if this call completes without panicking. - assert.NotPanics(t, func() { - getOutcome := func(types.QueueItemAccessor) (types.QueueOutcome, error) { - return types.QueueOutcomeEvictedOther, nil - } - h.processor.finalizeItems(items, h.logger, getOutcome) - }, "finalizeItems should not panic on unexpected item types") - }) - t.Run("should process all queues with a worker pool", func(t *testing.T) { t.Parallel() // --- ARRANGE --- @@ -1122,6 +1097,26 @@ func TestShardProcessor(t *testing.T) { require.Error(t, err, "Submit must return an error when the channel is full") assert.ErrorIs(t, err, ErrProcessorBusy, "The returned error must be ErrProcessorBusy") }) + + t.Run("should return ErrFlowControllerNotRunning if lifecycleCtx is cancelled", func(t *testing.T) { + t.Parallel() + h := newTestHarness(t, testCleanupTick) + h.Start() + h.Go() // Ensure the Run loop has started + h.cancel() // Cancel the lifecycle context + h.Stop() // Wait for the processor to fully stop + + item := h.newTestItem("item-ctx-cancel", testFlow, testTTL) + err := h.processor.Submit(item) + require.ErrorIs(t, err, types.ErrFlowControllerNotRunning, + "Submit must return ErrFlowControllerNotRunning when lifecycleCtx is cancelled") + assert.Nil(t, item.FinalState(), "Item should not be finalized by Submit") + + err = h.processor.SubmitOrBlock(context.Background(), item) + require.ErrorIs(t, err, types.ErrFlowControllerNotRunning, + "SubmitOrBlock must return ErrFlowControllerNotRunning when lifecycleCtx is cancelled") + assert.Nil(t, item.FinalState(), "Item should not be finalized by SubmitOrBlock") + }) }) t.Run("SubmitOrBlock", func(t *testing.T) { @@ -1195,114 +1190,9 @@ func TestShardProcessor(t *testing.T) { require.Error(t, err, "SubmitOrBlock should return an error when shutting down") assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "The error should be ErrFlowControllerNotRunning") - outcome, err := h.waitForFinalization(item) - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "The outcome should be RejectedOther") - require.Error(t, err, "Finalization should include an error") - assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, - "The finalization error should be ErrFlowControllerNotRunning") + // Item should not be finalized by the processor + assert.Nil(t, item.FinalState(), "Item should not be finalized by the processor") }) }) }) } - -func TestCheckItemExpiry(t *testing.T) { - t.Parallel() - - // --- ARRANGE --- - now := time.Now() - ctxCancelled, cancel := context.WithCancel(context.Background()) - cancel() // Cancel the context immediately. - - testCases := []struct { - name string - item types.QueueItemAccessor - now time.Time - expectExpired bool - expectOutcome types.QueueOutcome - expectErr error - }{ - { - name: "should not be expired if TTL is not reached and context is active", - item: NewItem( - typesmocks.NewMockFlowControlRequest(100, "req-not-expired", testFlow, context.Background()), - testTTL, - now), - now: now.Add(30 * time.Second), - expectExpired: false, - expectOutcome: types.QueueOutcomeNotYetFinalized, - expectErr: nil, - }, - { - name: "should not be expired if TTL is disabled (0)", - item: NewItem( - typesmocks.NewMockFlowControlRequest(100, "req-not-expired-no-ttl", testFlow, context.Background()), - 0, - now), - now: now.Add(30 * time.Second), - expectExpired: false, - expectOutcome: types.QueueOutcomeNotYetFinalized, - expectErr: nil, - }, - { - name: "should be expired if TTL is exceeded", - item: NewItem( - typesmocks.NewMockFlowControlRequest(100, "req-ttl-expired", testFlow, context.Background()), - time.Second, - now), - now: now.Add(2 * time.Second), - expectExpired: true, - expectOutcome: types.QueueOutcomeEvictedTTL, - expectErr: types.ErrTTLExpired, - }, - { - name: "should be expired if context is cancelled", - item: NewItem( - typesmocks.NewMockFlowControlRequest(100, "req-ctx-cancelled", testFlow, ctxCancelled), - testTTL, - now), - now: now, - expectExpired: true, - expectOutcome: types.QueueOutcomeEvictedContextCancelled, - expectErr: types.ErrContextCancelled, - }, - { - name: "should be expired if already finalized", - item: func() types.QueueItemAccessor { - i := NewItem( - typesmocks.NewMockFlowControlRequest(100, "req-finalized", testFlow, context.Background()), - testTTL, - now) - i.Finalize(types.QueueOutcomeDispatched, nil) - return i - }(), - now: now, - expectExpired: true, - expectOutcome: types.QueueOutcomeDispatched, - expectErr: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - // --- ACT --- - isExpired, outcome, err := checkItemExpiry(tc.item, tc.now) - - // --- ASSERT --- - assert.Equal(t, tc.expectExpired, isExpired, "Expired status should match expected value") - assert.Equal(t, tc.expectOutcome, outcome, "Outcome should match expected value") - - if tc.expectErr != nil { - require.Error(t, err, "An error was expected") - // Use ErrorIs for sentinel errors, ErrorContains for general messages. - if errors.Is(tc.expectErr, types.ErrTTLExpired) || errors.Is(tc.expectErr, types.ErrContextCancelled) { - assert.ErrorIs(t, err, tc.expectErr, "The specific error type should be correct") - } else { - assert.ErrorContains(t, err, tc.expectErr.Error(), "The error message should contain the expected text") - } - } else { - assert.NoError(t, err, "No error was expected") - } - }) - } -} diff --git a/pkg/epp/flowcontrol/types/mocks/mocks.go b/pkg/epp/flowcontrol/types/mocks/mocks.go index c52c5c2db..5fabf3683 100644 --- a/pkg/epp/flowcontrol/types/mocks/mocks.go +++ b/pkg/epp/flowcontrol/types/mocks/mocks.go @@ -19,7 +19,6 @@ limitations under the License. package mocks import ( - "context" "time" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" @@ -28,7 +27,6 @@ import ( // MockFlowControlRequest provides a mock implementation of the `types.FlowControlRequest` interface. type MockFlowControlRequest struct { - Ctx context.Context FlowKeyV types.FlowKey ByteSizeV uint64 InitialEffectiveTTLV time.Duration @@ -41,20 +39,14 @@ func NewMockFlowControlRequest( byteSize uint64, id string, key types.FlowKey, - ctx context.Context, ) *MockFlowControlRequest { - if ctx == nil { - ctx = context.Background() - } return &MockFlowControlRequest{ ByteSizeV: byteSize, IDV: id, FlowKeyV: key, - Ctx: ctx, } } -func (m *MockFlowControlRequest) Context() context.Context { return m.Ctx } func (m *MockFlowControlRequest) FlowKey() types.FlowKey { return m.FlowKeyV } func (m *MockFlowControlRequest) ByteSize() uint64 { return m.ByteSizeV } func (m *MockFlowControlRequest) InitialEffectiveTTL() time.Duration { return m.InitialEffectiveTTLV } @@ -114,7 +106,6 @@ func NewMockQueueItemAccessor(byteSize uint64, reqID string, key types.FlowKey) byteSize, reqID, key, - context.Background(), ), HandleV: &MockQueueItemHandle{}, } diff --git a/pkg/epp/flowcontrol/types/request.go b/pkg/epp/flowcontrol/types/request.go index 255d3bc45..e427b0aba 100644 --- a/pkg/epp/flowcontrol/types/request.go +++ b/pkg/epp/flowcontrol/types/request.go @@ -17,7 +17,6 @@ limitations under the License. package types import ( - "context" "time" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" @@ -30,11 +29,6 @@ import ( // wraps this object with its own internal structures (which implement `QueueItemAccessor`) to manage the request's // lifecycle without modifying the original. type FlowControlRequest interface { - // Context returns the request's context. The `controller.FlowController` uses this for monitoring cancellation (e.g., - // if the client disconnects or a request-scoped timeout occurs), which can lead to the request being evicted from a - // queue. - Context() context.Context - // FlowKey returns the composite key that uniquely identifies the flow instance this request belongs to. // The `controller.FlowController` uses this key as the primary identifier to look up the correct // `contracts.ManagedQueue` and configured `framework.IntraFlowDispatchPolicy` from a `contracts.RegistryShard`. diff --git a/pkg/epp/requestcontrol/admission.go b/pkg/epp/requestcontrol/admission.go index 383d2844a..69fd5adf8 100644 --- a/pkg/epp/requestcontrol/admission.go +++ b/pkg/epp/requestcontrol/admission.go @@ -62,7 +62,7 @@ type saturationDetector interface { // flowController defines the minimal interface required by FlowControlAdmissionController for enqueuing requests and // waiting for an admission outcome. type flowController interface { - EnqueueAndWait(req types.FlowControlRequest) (types.QueueOutcome, error) + EnqueueAndWait(ctx context.Context, req types.FlowControlRequest) (types.QueueOutcome, error) } // rejectIfSheddableAndSaturated checks if a request should be immediately rejected because it's sheddable @@ -157,7 +157,6 @@ func (fcac *FlowControlAdmissionController) Admit( logger.V(logutil.TRACE).Info("Request proceeding to flow control", "requestID", reqCtx.SchedulingRequest.RequestId) fcReq := &flowControlRequest{ - ctx: ctx, requestID: reqCtx.SchedulingRequest.RequestId, fairnessID: reqCtx.FairnessID, priority: priority, @@ -165,7 +164,7 @@ func (fcac *FlowControlAdmissionController) Admit( candidatePods: candidatePods, } - outcome, err := fcac.flowController.EnqueueAndWait(fcReq) + outcome, err := fcac.flowController.EnqueueAndWait(ctx, fcReq) logger.V(logutil.DEBUG).Info("Flow control outcome", "requestID", reqCtx.SchedulingRequest.RequestId, "outcome", outcome, "error", err) return translateFlowControlOutcome(outcome, err) @@ -173,7 +172,6 @@ func (fcac *FlowControlAdmissionController) Admit( // flowControlRequest is an adapter that implements the types.FlowControlRequest interface. type flowControlRequest struct { - ctx context.Context requestID string fairnessID string priority int @@ -183,7 +181,6 @@ type flowControlRequest struct { var _ types.FlowControlRequest = &flowControlRequest{} -func (r *flowControlRequest) Context() context.Context { return r.ctx } func (r *flowControlRequest) ID() string { return r.requestID } func (r *flowControlRequest) InitialEffectiveTTL() time.Duration { return 0 } // Use controller default. func (r *flowControlRequest) ByteSize() uint64 { return r.requestByteSize } diff --git a/pkg/epp/requestcontrol/admission_test.go b/pkg/epp/requestcontrol/admission_test.go index 002c50f06..085778200 100644 --- a/pkg/epp/requestcontrol/admission_test.go +++ b/pkg/epp/requestcontrol/admission_test.go @@ -48,7 +48,10 @@ type mockFlowController struct { called bool } -func (m *mockFlowController) EnqueueAndWait(_ fctypes.FlowControlRequest) (fctypes.QueueOutcome, error) { +func (m *mockFlowController) EnqueueAndWait( + _ context.Context, + _ fctypes.FlowControlRequest, +) (fctypes.QueueOutcome, error) { m.called = true return m.outcome, m.err } @@ -115,7 +118,6 @@ func TestLegacyAdmissionController_Admit(t *testing.T) { func TestFlowControlRequestAdapter(t *testing.T) { t.Parallel() - ctx := context.Background() candidatePods := []backendmetrics.PodMetrics{&backendmetrics.FakePodMetrics{}} testCases := []struct { @@ -140,7 +142,6 @@ func TestFlowControlRequestAdapter(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() fcReq := &flowControlRequest{ - ctx: ctx, requestID: tc.requestID, fairnessID: tc.fairnessID, priority: tc.priority, @@ -148,7 +149,6 @@ func TestFlowControlRequestAdapter(t *testing.T) { candidatePods: candidatePods, } - assert.Equal(t, ctx, fcReq.Context(), "Context() mismatch") assert.Equal(t, tc.requestID, fcReq.ID(), "ID() mismatch") assert.Equal(t, tc.requestByteSize, fcReq.ByteSize(), "ByteSize() mismatch") assert.Equal(t, candidatePods, fcReq.CandidatePodsForScheduling(), "CandidatePodsForScheduling() mismatch") From c71339b0891b359463bedbb6b45a28cf8f384034 Mon Sep 17 00:00:00 2001 From: Luke Van Drie Date: Tue, 14 Oct 2025 13:21:34 -0700 Subject: [PATCH 099/133] feat: Add initial Flow Control metrics (#1714) * feat: Add initial Flow Control metrics Introduces initial Prometheus metrics for the experimental Flow Contorl layer in EPP. This change adds the following metrics: - inference_extension_flow_control_request_queue_duration_seconds: A histogram to track the total time requests spend in the Flow Control layer, from invocation of EnqueueAndWait to final outcome. - inference_extension_flow_control_queue_size: A gauge to track the number of requests currently being managed by the Flow Control layer. These metrics are labeled by fairness_id, priority, and outcome (for the duration metric). * Rebase onto HEAd and resolve conflicts. --- pkg/epp/flowcontrol/controller/controller.go | 9 +- .../flowcontrol/controller/internal/item.go | 6 + pkg/epp/metrics/metrics.go | 42 +++++- pkg/epp/metrics/metrics_test.go | 130 ++++++++++++++++-- site-src/guides/metrics-and-observability.md | 9 ++ 5 files changed, 184 insertions(+), 12 deletions(-) diff --git a/pkg/epp/flowcontrol/controller/controller.go b/pkg/epp/flowcontrol/controller/controller.go index c1f9a3004..93d0330c7 100644 --- a/pkg/epp/flowcontrol/controller/controller.go +++ b/pkg/epp/flowcontrol/controller/controller.go @@ -28,6 +28,7 @@ import ( "errors" "fmt" "slices" + "strconv" "sync" "time" @@ -38,6 +39,7 @@ import ( "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/controller/internal" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics" logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging" ) @@ -209,6 +211,12 @@ func (fc *FlowController) EnqueueAndWait( return types.QueueOutcomeRejectedOther, errors.New("request cannot be nil") } + flowKey := req.FlowKey() + fairnessID := flowKey.ID + priority := strconv.Itoa(flowKey.Priority) + metrics.IncFlowControlQueueSize(fairnessID, priority) + defer metrics.DecFlowControlQueueSize(fairnessID, priority) + // 1. Create the derived context that governs this request's lifecycle (Parent Cancellation + TTL). reqCtx, cancel, enqueueTime := fc.createRequestContext(ctx, req) defer cancel() @@ -216,7 +224,6 @@ func (fc *FlowController) EnqueueAndWait( // 2. Enter the distribution loop to find a home for the request. // This loop is responsible for retrying on ErrShardDraining. for { - select { // Non-blocking check on controller lifecycle. case <-fc.parentCtx.Done(): return types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning) diff --git a/pkg/epp/flowcontrol/controller/internal/item.go b/pkg/epp/flowcontrol/controller/internal/item.go index 31a28d473..f0d5d3286 100644 --- a/pkg/epp/flowcontrol/controller/internal/item.go +++ b/pkg/epp/flowcontrol/controller/internal/item.go @@ -20,11 +20,13 @@ import ( "context" "errors" "fmt" + "strconv" "sync" "sync/atomic" "time" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics" ) // FinalState encapsulates the terminal outcome of a FlowItem's lifecycle. @@ -154,6 +156,10 @@ func (fi *FlowItem) finalizeInternal(outcome types.QueueOutcome, err error) { // Atomically store the pointer. This is the critical memory barrier that publishes the state safely. fi.finalState.Store(finalState) + duration := time.Since(fi.enqueueTime) + flowKey := fi.originalRequest.FlowKey() + metrics.RecordFlowControlRequestQueueDuration(flowKey.ID, strconv.Itoa(flowKey.Priority), outcome.String(), duration) + fi.done <- finalState close(fi.done) } diff --git a/pkg/epp/metrics/metrics.go b/pkg/epp/metrics/metrics.go index efe0d0491..773ef41e9 100644 --- a/pkg/epp/metrics/metrics.go +++ b/pkg/epp/metrics/metrics.go @@ -422,6 +422,28 @@ var ( }, []string{"commit", "build_ref"}, ) + + // Flow Control Metrics + flowControlRequestQueueDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Subsystem: InferenceExtension, + Name: "flow_control_request_queue_duration_seconds", + Help: metricsutil.HelpMsgWithStability("Distribution of the total time requests spend in the EPP flow control layer, measured from the start of the EnqueueAndWait call until a final outcome is reached.", compbasemetrics.ALPHA), + Buckets: []float64{ + 0.0001, 0.0005, 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, + }, + }, + []string{"fairness_id", "priority", "outcome"}, + ) + + flowControlQueueSize = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: InferenceExtension, + Name: "flow_control_queue_size", + Help: metricsutil.HelpMsgWithStability("Current number of requests being actively managed by the EPP flow control layer, from the start of the EnqueueAndWait call until a final outcome is reached.", compbasemetrics.ALPHA), + }, + []string{"fairness_id", "priority"}, + ) ) var registerMetrics sync.Once @@ -473,7 +495,8 @@ func Register(customCollectors ...prometheus.Collector) { metrics.Registry.MustRegister(PrefixCacheSize) metrics.Registry.MustRegister(PrefixCacheHitRatio) metrics.Registry.MustRegister(PrefixCacheHitLength) - + metrics.Registry.MustRegister(flowControlRequestQueueDuration) + metrics.Registry.MustRegister(flowControlQueueSize) for _, collector := range customCollectors { metrics.Registry.MustRegister(collector) } @@ -500,6 +523,8 @@ func Reset() { PrefixCacheSize.Reset() PrefixCacheHitRatio.Reset() PrefixCacheHitLength.Reset() + flowControlRequestQueueDuration.Reset() + flowControlQueueSize.Reset() requestTPOT.Reset() requestTTFT.Reset() @@ -770,6 +795,21 @@ func RecordInferenceExtensionInfo(commitSha, buildRef string) { InferenceExtensionInfo.WithLabelValues(commitSha, buildRef).Set(1) } +// RecordFlowControlRequestQueueDuration records the duration a request spent in the Flow Control layer. +func RecordFlowControlRequestQueueDuration(fairnessID, priority, outcome string, duration time.Duration) { + flowControlRequestQueueDuration.WithLabelValues(fairnessID, priority, outcome).Observe(duration.Seconds()) +} + +// IncFlowControlQueueSize increments the Flow Control queue size gauge. +func IncFlowControlQueueSize(fairnessID, priority string) { + flowControlQueueSize.WithLabelValues(fairnessID, priority).Inc() +} + +// DecFlowControlQueueSize decrements the Flow Control queue size gauge. +func DecFlowControlQueueSize(fairnessID, priority string) { + flowControlQueueSize.WithLabelValues(fairnessID, priority).Dec() +} + // SetTTFTSLOThreshold sets the TTFT SLO threshold for a model. // This allows dynamic threshold management and makes the threshold visible in metrics. func SetTTFTSLOThreshold(modelName, targetModelName string, threshold float64) { diff --git a/pkg/epp/metrics/metrics_test.go b/pkg/epp/metrics/metrics_test.go index 1bf76ecb8..c63f1c2f5 100644 --- a/pkg/epp/metrics/metrics_test.go +++ b/pkg/epp/metrics/metrics_test.go @@ -22,6 +22,9 @@ import ( "testing" "time" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" "k8s.io/component-base/metrics/testutil" "sigs.k8s.io/controller-runtime/pkg/metrics" @@ -48,7 +51,14 @@ const ( RequestTPOTPredictionsMAPEMetric = InferenceModelComponent + "_request_tpot_predictions_mape" ) +func TestMain(m *testing.M) { + // Register all metrics once for the entire test suite. + Register() + os.Exit(m.Run()) +} + func TestRecordRequestCounterandSizes(t *testing.T) { + Reset() type requests struct { modelName string targetModelName string @@ -82,7 +92,6 @@ func TestRecordRequestCounterandSizes(t *testing.T) { }, }, }} - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, req := range scenario.reqs { @@ -118,6 +127,7 @@ func TestRecordRequestCounterandSizes(t *testing.T) { } func TestRecordRequestErrorCounter(t *testing.T) { + Reset() type requests struct { modelName string targetModelName string @@ -154,7 +164,6 @@ func TestRecordRequestErrorCounter(t *testing.T) { }, }, } - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, req := range scenario.reqs { @@ -178,6 +187,7 @@ func TestRecordRequestErrorCounter(t *testing.T) { } func TestRecordRequestLatencies(t *testing.T) { + Reset() ctx := logutil.NewTestLoggerIntoContext(context.Background()) timeBaseline := time.Now() type requests struct { @@ -233,7 +243,6 @@ func TestRecordRequestLatencies(t *testing.T) { invalid: true, }, } - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, req := range scenario.reqs { @@ -260,6 +269,7 @@ func TestRecordRequestLatencies(t *testing.T) { } func TestRecordNormalizedTimePerOutputToken(t *testing.T) { + Reset() ctx := logutil.NewTestLoggerIntoContext(context.Background()) timeBaseline := time.Now() type tokenRequests struct { @@ -334,7 +344,6 @@ func TestRecordNormalizedTimePerOutputToken(t *testing.T) { invalid: true, }, } - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, req := range scenario.reqs { @@ -361,6 +370,7 @@ func TestRecordNormalizedTimePerOutputToken(t *testing.T) { } func TestRecordResponseMetrics(t *testing.T) { + Reset() type responses struct { modelName string targetModelName string @@ -404,7 +414,6 @@ func TestRecordResponseMetrics(t *testing.T) { }, }, }} - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, resp := range scenario.resp { @@ -455,6 +464,7 @@ func TestRecordResponseMetrics(t *testing.T) { } func TestRunningRequestsMetrics(t *testing.T) { + Reset() type request struct { modelName string complete bool // true -> request is completed, false -> running request @@ -487,7 +497,6 @@ func TestRunningRequestsMetrics(t *testing.T) { }, } - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, req := range scenario.requests { @@ -515,6 +524,7 @@ func TestRunningRequestsMetrics(t *testing.T) { } func TestInferencePoolMetrics(t *testing.T) { + Reset() scenarios := []struct { name string poolName string @@ -528,7 +538,6 @@ func TestInferencePoolMetrics(t *testing.T) { queueSizeAvg: 0.4, }, } - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { RecordInferencePoolAvgKVCache(scenario.poolName, scenario.kvCacheAvg) @@ -564,6 +573,7 @@ func TestInferencePoolMetrics(t *testing.T) { } func TestPluginProcessingLatencies(t *testing.T) { + Reset() type pluginLatency struct { extensionPoint string pluginType string @@ -604,7 +614,6 @@ func TestPluginProcessingLatencies(t *testing.T) { }, }, } - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, latency := range scenario.latencies { @@ -628,6 +637,7 @@ func TestPluginProcessingLatencies(t *testing.T) { } func TestSchedulerE2ELatency(t *testing.T) { + Reset() scenarios := []struct { name string durations []time.Duration @@ -647,7 +657,6 @@ func TestSchedulerE2ELatency(t *testing.T) { }, }, } - Register() for _, scenario := range scenarios { t.Run(scenario.name, func(t *testing.T) { for _, duration := range scenario.durations { @@ -671,6 +680,7 @@ func TestSchedulerE2ELatency(t *testing.T) { } func TestPrefixCacheMetrics(t *testing.T) { + Reset() const ( PrefixCacheSizeMetric = InferenceExtension + "_prefix_indexer_size" PrefixCacheHitRatioMetric = InferenceExtension + "_prefix_indexer_hit_ratio" @@ -717,7 +727,6 @@ func TestPrefixCacheMetrics(t *testing.T) { }, } - Register() t.Run(scenario.name, func(t *testing.T) { // Record cache size metrics for _, size := range scenario.cacheSizes { @@ -772,3 +781,104 @@ func TestPrefixCacheMetrics(t *testing.T) { } }) } + +func getHistogramVecLabelValues(t *testing.T, h *prometheus.HistogramVec, labelValues ...string) (*dto.Histogram, error) { + t.Helper() + m, err := h.GetMetricWithLabelValues(labelValues...) + if err != nil { + return nil, err + } + metricDto := &dto.Metric{} + if err := m.(prometheus.Histogram).Write(metricDto); err != nil { + return nil, err + } + return metricDto.GetHistogram(), nil +} + +func TestFlowControlQueueDurationMetric(t *testing.T) { + Reset() + + records := []struct { + fairnessID string + priority string + outcome string + duration time.Duration + }{ + {fairnessID: "user-a", priority: "100", outcome: "Dispatched", duration: 10 * time.Millisecond}, + {fairnessID: "user-a", priority: "100", outcome: "Dispatched", duration: 20 * time.Millisecond}, + {fairnessID: "user-b", priority: "100", outcome: "RejectedCapacity", duration: 5 * time.Millisecond}, + {fairnessID: "user-a", priority: "50", outcome: "Dispatched", duration: 100 * time.Millisecond}, + } + + for _, rec := range records { + RecordFlowControlRequestQueueDuration(rec.fairnessID, rec.priority, rec.outcome, rec.duration) + } + + testCases := []struct { + name string + labels prometheus.Labels + expectCount uint64 + expectSum float64 + }{ + { + name: "user-a, prio 100, dispatched", + labels: prometheus.Labels{"fairness_id": "user-a", "priority": "100", "outcome": "Dispatched"}, + expectCount: 2, + expectSum: 0.03, // 0.01 + 0.02 + }, + { + name: "user-b, prio 100, rejected", + labels: prometheus.Labels{"fairness_id": "user-b", "priority": "100", "outcome": "RejectedCapacity"}, + expectCount: 1, + expectSum: 0.005, + }, + { + name: "user-a, prio 50, dispatched", + labels: prometheus.Labels{"fairness_id": "user-a", "priority": "50", "outcome": "Dispatched"}, + expectCount: 1, + expectSum: 0.1, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + labels := []string{tc.labels["fairness_id"], tc.labels["priority"], tc.labels["outcome"]} + hist, err := getHistogramVecLabelValues(t, flowControlRequestQueueDuration, labels...) + require.NoError(t, err, "Failed to get histogram for labels %v", tc.labels) + require.Equal(t, tc.expectCount, hist.GetSampleCount(), "Sample count mismatch for labels %v", tc.labels) + require.InDelta(t, tc.expectSum, hist.GetSampleSum(), 0.00001, "Sample sum mismatch for labels %v", tc.labels) + }) + } +} + +func TestFlowControlQueueSizeMetric(t *testing.T) { + Reset() + + // Basic Inc/Dec + IncFlowControlQueueSize("user-a", "100") + val, err := testutil.GetGaugeMetricValue(flowControlQueueSize.WithLabelValues("user-a", "100")) + require.NoError(t, err, "Failed to get gauge value for user-a/100 after Inc") + require.Equal(t, 1.0, val, "Gauge value should be 1 after Inc for user-a/100") + + DecFlowControlQueueSize("user-a", "100") + val, err = testutil.GetGaugeMetricValue(flowControlQueueSize.WithLabelValues("user-a", "100")) + require.NoError(t, err, "Failed to get gauge value for user-a/100 after Dec") + require.Equal(t, 0.0, val, "Gauge value should be 0 after Dec for user-a/100") + + // Multiple labels + IncFlowControlQueueSize("user-b", "200") + IncFlowControlQueueSize("user-b", "200") + val, err = testutil.GetGaugeMetricValue(flowControlQueueSize.WithLabelValues("user-b", "200")) + require.NoError(t, err, "Failed to get gauge value for user-b/200") + require.Equal(t, 2.0, val, "Gauge value should be 2 for user-b/200") + + DecFlowControlQueueSize("user-b", "200") + val, err = testutil.GetGaugeMetricValue(flowControlQueueSize.WithLabelValues("user-b", "200")) + require.NoError(t, err, "Failed to get gauge value for user-b/200 after one Dec") + require.Equal(t, 1.0, val, "Gauge value should be 1 for user-b/200 after one Dec") + + // Non-existent labels + val, err = testutil.GetGaugeMetricValue(flowControlQueueSize.WithLabelValues("user-c", "100")) + require.NoError(t, err, "Failed to get gauge value for non-existent user-c/100") + require.Equal(t, 0.0, val, "Gauge value for non-existent labels should be 0") +} diff --git a/site-src/guides/metrics-and-observability.md b/site-src/guides/metrics-and-observability.md index 5e1f02e49..8bcd95277 100644 --- a/site-src/guides/metrics-and-observability.md +++ b/site-src/guides/metrics-and-observability.md @@ -53,6 +53,15 @@ This guide describes the current state of exposed metrics and how to scrape them |:---------------------------|:-----------------|:-------------------------------------------------|:------------------------------------------|:------------| | lora_syncer_adapter_status | Gauge | Status of LoRA adapters (1=loaded, 0=not_loaded) | `adapter_name`=<adapter-id> | ALPHA | +### Flow Control Metrics (Experimental) + +These metrics provide insights into the experimental flow control layer within the EPP. + +| **Metric name** | **Metric Type** |
**Description**
|
**Labels**
| **Status** | +|:---|:---|:---|:---|:---| +| inference_extension_flow_control_request_queue_duration_seconds | Distribution | Distribution of the total time requests spend in the flow control layer. This is measured from the moment a request enters the `EnqueueAndWait` function until it reaches a final outcome (e.g., Dispatched, Rejected, Evicted). | `fairness_id`=<flow-id>
`priority`=<flow-priority>
`outcome`=<QueueOutcome> | ALPHA | +| inference_extension_flow_control_queue_size | Gauge | The current number of requests being actively managed by the flow control layer. This counts requests from the moment they enter the `EnqueueAndWait` function until they reach a final outcome. | `fairness_id`=<flow-id>
`priority`=<flow-priority> | ALPHA | + ## Scrape Metrics & Pprof profiles The metrics endpoints are exposed on different ports by default: From cd195f543c84c64ed366db43eb8f8683f63a93cf Mon Sep 17 00:00:00 2001 From: capri-xiyue <52932582+capri-xiyue@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:35:33 -0700 Subject: [PATCH 100/133] Docs: added migration guide (#1558) * added migration guide * updated index * changed typo * updated the index Signed-off-by: Xiyue Yu * updated docs * fixed comments --------- Signed-off-by: Xiyue Yu --- mkdocs.yml | 1 + site-src/guides/ga-migration.md | 274 ++++++++++++++++++++++++++++ site-src/images/alpha-stage.png | Bin 0 -> 299803 bytes site-src/images/ga-stage.png | Bin 0 -> 317309 bytes site-src/images/migration-stage.png | Bin 0 -> 438155 bytes 5 files changed, 275 insertions(+) create mode 100644 site-src/guides/ga-migration.md create mode 100644 site-src/images/alpha-stage.png create mode 100644 site-src/images/ga-stage.png create mode 100644 site-src/images/migration-stage.png diff --git a/mkdocs.yml b/mkdocs.yml index 4c285c2e8..f2807cb32 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,6 +74,7 @@ nav: - Configuration Guide: - Configuring the plugins via configuration YAML file: guides/epp-configuration/config-text.md - Prefix Cache Aware Plugin: guides/epp-configuration/prefix-aware.md + - Migration Guide: guides/ga-migration.md - Troubleshooting Guide: guides/troubleshooting.md - Implementer Guides: - Getting started: guides/implementers.md diff --git a/site-src/guides/ga-migration.md b/site-src/guides/ga-migration.md new file mode 100644 index 000000000..c56dea23d --- /dev/null +++ b/site-src/guides/ga-migration.md @@ -0,0 +1,274 @@ +# Inference Gateway: Migrating from v1alpha2 to v1 API + +## Introduction + +This guide provides a comprehensive walkthrough for migrating your Inference Gateway setup from the alpha `v1alpha2` API to the generally available `v1` API. +This document is intended for platform administrators and networking specialists +who are currently using the `v1alpha2` version of the Inference Gateway and +want to upgrade to the `v1` version to leverage the latest features and improvements. + +Before you start the migration, ensure you are familiar with the concepts and deployment of the Inference Gateway. + +*** + +## Before you begin + +Before starting the migration, it's important to determine if this guide is necessary for your setup. + +### Checking for Existing v1alpha2 APIs + +To check if you are actively using the `v1alpha2` Inference Gateway APIs, run the following command: + +```bash +kubectl get inferencepools.inference.networking.x-k8s.io --all-namespaces +``` + +* If this command returns one or more `InferencePool` resources, you are using the `v1alpha2` API and should proceed with this migration guide. +* If the command returns `No resources found`, you are not using the `v1alpha2` `InferencePool` and do not need to follow this migration guide. You can proceed with a fresh installation of the `v1` Inference Gateway. + +*** + +## Migration Paths + +There are two paths for migrating from `v1alpha2` to `v1`: + +1. **Simple Migration (with downtime):** This path is for users who can afford a short period of downtime. It involves deleting the old `v1alpha2` resources and CRDs before installing the new `v1` versions. +2. **Zero-Downtime Migration:** This path is for users who need to migrate without any service interruption. It involves running both `v1alpha2` and `v1` stacks side-by-side and gradually shifting traffic. + +*** + +## Simple Migration (with downtime) + +This approach is faster and simpler but will result in a brief period of downtime while the resources are being updated. It is the recommended path if you do not require a zero-downtime migration. + +### 1. Delete Existing v1alpha2 Resources + +**Option a: Uninstall using Helm.** + +```bash +helm uninstall +``` + +**Option b: Manually delete alpha `InferencePool` resources.** + +If you are not using Helm, you will need to manually delete all resources associated with your `v1alpha2` deployment. The key is to remove the `HTTPRoute`'s reference to the old `InferencePool` and then delete the `v1alpha2` resources themselves. + +1. **Update or Delete the `HTTPRoute`**: Modify the `HTTPRoute` to remove the `backendRef` that points to the `v1alpha2` `InferencePool`. +2. **Delete the `InferencePool` and associated resources**: You must delete the `v1alpha2` `InferencePool`, any `InferenceModel` (or 'InferenceObjective') resources that point to it, and the corresponding Endpoint Picker (EPP) Deployment and Service. +3. **Delete the `v1alpha2` CRDs**: Once all `v1alpha2` custom resources are deleted, you can remove the CRD definitions from your cluster. + ```bash + # You can change the version to the one you installed `v1alpha2` CRDs + export VERSION="v0.3.0" + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/releases/download/${VERSION}/manifests.yaml + ``` + +### 2. Install v1 Resources + +After cleaning up the old resources, you can proceed with a fresh installation of the `v1` Inference Gateway. +This involves deploying a new EPP image compatible with the `v1` API and installing the new `v1` CRDs. +You can then create a new v1 InferencePool with its corresponding InferenceObjective resources, and a new HTTPRoute that directs traffic to your new `v1` InferencePool. + + +### 3. Verify the Deployment + +After a few minutes, verify that your new `v1` stack is correctly serving traffic. You should have a **`PROGRAMMED`** gateway. + +```bash +❯ kubectl get gateway -o wide +NAME CLASS ADDRESS PROGRAMMED AGE + inference-gateway True 10m +``` + +Curl the endpoint to make sure you are getting a successful response with a **200** response code. + +```bash +IP=$(kubectl get gateway/ -o jsonpath='{.status.addresses[0].value}') +PORT=80 + +curl -i ${IP}:${PORT}/v1/completions -H 'Content-Type: application/json' -d '{ +"model": "", +"prompt": "", +"max_tokens": 100, +"temperature": 0 +}' +``` + +*** + +## Zero-Downtime Migration + +This migration path is designed for users who cannot afford any service interruption. Assuming you already have the following stack shown in the diagram + +Inference Gateway Alpha Stage + +### A Note on Interacting with Multiple API Versions + +During the zero-downtime migration, both `v1alpha2` and `v1` CRDs will be installed on your cluster. This can create ambiguity when using `kubectl` to query for `InferencePool` resources. To ensure you are interacting with the correct version, you **must** use the full resource name: + +* **For v1alpha2**: `kubectl get inferencepools.inference.networking.x-k8s.io` +* **For v1**: `kubectl get inferencepools.inference.networking.k8s.io` + +The `v1` API also provides a convenient short name, `infpool`, which can be used to query `v1` resources specifically: + +```bash +kubectl get infpool +``` + +This guide will use these full names or the short name for `v1` to avoid ambiguity. + +*** + +### Stage 1: Side-by-side v1 Deployment + +In this stage, you will deploy the new `v1` `InferencePool` stack alongside the existing `v1alpha2` stack. This allows for a safe, gradual migration. + +After finishing all the steps in this stage, you’ll have the following infrastructure shown in the following diagram + +Inference Gateway Migration Stage + +**1. Install v1 CRDs** + +```bash +RELEASE=v1.0.0 +kubectl apply -f [https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/$RELEASE/config/crd/bases/inference.networking.x-k8s.io_inferenceobjectives.yaml](https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/$RELEASE/config/crd/bases/inference.networking.x-k8s.io_inferenceobjectives.yaml) +``` + +**2. Install the v1 `InferencePool`** + +Use Helm to install a new `v1` `InferencePool` with a distinct release name (e.g., `vllm-llama3-8b-instruct-ga`). + +```bash +helm install vllm-llama3-8b-instruct-ga \ + --set inferencePool.modelServers.matchLabels.app= \ + --set provider.name= \ + --version $RELEASE \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool +``` + +**3. Create the v1 `InferenceObjective`** + +The `v1` API replaces `InferenceModel` with `InferenceObjective`. Create the new resources, referencing the new `v1` `InferencePool`. + +```yaml +kubectl apply -f - < + +You should have a **`PROGRAMMED`** gateway: +```bash +❯ kubectl get gateway -o wide +NAME CLASS ADDRESS PROGRAMMED AGE +inference-gateway inference-gateway True 10m +``` + +Curl the endpoint and verify a **200** response code: +```bash +IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}') +PORT=80 + +curl -i ${IP}:${PORT}/v1/completions -H 'Content-Type: application/json' -d '{ +"model": "", +"prompt": "", +"max_tokens": 100, +"temperature": 0 +}' +``` + +**3. Clean Up v1alpha2 Resources** + +After confirming the `v1` stack is fully operational, safely remove the old `v1alpha2` resources. diff --git a/site-src/images/alpha-stage.png b/site-src/images/alpha-stage.png new file mode 100644 index 0000000000000000000000000000000000000000..7ba1ca5a72d7221c08ac1890597ac98007d8ca6f GIT binary patch literal 299803 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

E4?mu?7Np+|Iaf^LDU%XUO=@CBp%7;QAU z{NL64<<=1;?tHklZ%~*S zN=hl*j#=S(C^|TNWP3(kpaLFp;;wz-c>>QY!VN^KOPh8l! zoR!F>m?%Ft)}U^0I!!M(Cp#J2tu&a0#8t3>>RLRN9}EJ~yU33M&6e!wf>d1eOMnWA zSe*k{BWhRc(bv&1Ci1%*&v|->UB)sQ8h_w;!4{h%g@^bVgOpO43C+tMaYM1`G&Xdd zjo84}ZnLHbE0t30xg6@k9ZJ3eOef3@7@=wia+Q`6)FP5hRXHLZjad)tjKCxlH^Ob* zV<~}b@oz5cPMW)>Q*wy~m5d~@+b>Hlp6=Op+3u>FE$t{lBWa%j9uxNvmCdf&lGpbK zFDPYJC=y{j>K7^PX$u~sM*zK4%FWy&=+GA@MkQvM2b z^GNlv0MSvyfnXt?_ZM~ki?YIhRG{_~6mV=Ww4bGF_P!iGXq_%mLFQ&k!kVtum|RP< zo%6-9e&7OyS0-uss60y$X8B5iw<(csP~FQBtfc>ki1x=Zv>^BI*%yM@hA$OY?dt@n z1ouo#s*k+>^IV0>(K4cXy{l|f-}6tCs4I=v$;6lZC`Rzoivj%lagWm})1ZRG=4ar& z%@8POga8r!!rK+RHWszTwt~EzWOQPDqB=Dap8Y!fg;_EQCFGc$Zpg5Qs(`_8{u5u) zug~!X+PO4eC-Ofuaxol7rTEmrg(P2LxA4ID0)d_d?Z-~}b!)`l49P>Q@QvWEX~#vt zY|Z*F7b}-RqMvvydz#NzEWm42%gD5$q{5zHwFqXyfYDfq6D+8EO~E;O+(Tl4w0~CL zsipDHAJYZKuuhdux&ClA@IIuIG$Q@Di3@EtMoOrpUAWlUT%nwP+nD*0Ot0?TP%peW zn?BC)Ncyjl?~iu2Z*_vY?qn+&NqVfy&8P2-4GH{*k4HYghKh7paWh(IG)3s?k3lb*wD?E?pvYs^In~Kjb|0#@u=!$w8rh{i z%f-svC(3cP1(DD5h+g>q#2L?bV4_0Hoh4QTz>s%eT487lYsW8hMW-Dd*y8C8b@&qF z3H{bk4Jw0sk=avZSb*Thk_cEEOMk6Ip34|{^q$NJ(@&9ob^b2ZA0PaLoBWCCW zOG1p5Cr*Zub2p_zBZY*1M|jJE+dkFL6s+U}O33~FwxJflyczQY94EWwo^&5E|mi? zH7o5iQ>f1EgHVnZpNtMNiOYv4-0I-{6wk*B8PE-XK0ir@Q{rXRyXRM!EkCGSWeq#1 zmR$A3OJ|t9xbV@b>{G&D#D^dWK9`Q0CpR!4^ElgJpvrq+sy6jB{j4)0QgvZB)Iveg4)S&Cf4p zCMM$)ddv+V%o#~V{m>?K`9pQ8z>VIJ<^~RGiUc;SYVUM8h8XarV_@HB`{h<#*V2K3 zcuKVJSq&xnvG+QYUR@hqtwt!vf)}}aKAQ-*12}&!p#tR8>6(f;-h{3{ipaIGgiPQp%#1mpQ29~OO^#j*? zPy?W~;{+QE%4{LY^1pTQ3A2QJhCXQli6l@!i}?Tm$8%p=uv}WC+L|;tJUWFQY@9}~ zo^?bwjuR*a^k_a693Ww_%i2A3OJur@?i4Y)vyQ@Qsc)~kgNMgSKnkq{N_}WIw5FE&pa?ce9J??o6sR3*pT3Bo6SI82hwg%1?SBk??~z|>CmMBlg^3F(sgb%{<}sCdV{t#dzeBF_F&O; zap+wa^6q}r4*wf`rHl!~Q|#sK&oUjZcen_T`Zfj)WK3H}y!CYBeYQ|S6tU1?O=xhz z`AL@?X)u0e<>Sy+M>}t>$V~((bA(S9JhuWC5V;}9D*Plu_O*y0UqP}|bLXc&_+_}3&Fr;#Z<&;`wH1bv&+|Sew-1X`sg7|lBTrTchk0dj zpCT_asQiLoDX+G4%m|gi9kOqiqWjFTlSYDIl)aCVWBlmXIY0tLbc1ybVq$L&ePUeG z>rB`un6J#h6ky5H7+kk>F<#sRW1?nPGSGg+r$wp{D{$Si;qw+{mu%CBtvRFL3H(s!@@_=$Z9xXeSs!FvON3J6-g>m@ z;Ss-g{iZ$FH~(JxP(DrN*^VdGHLrW~$lcDV@t#Wp=S9K99Z`~I#G66`?c z*f;o{^;t_wgasob4*qi~cFH5P1#8{B9A>Ji&r#>?n;2lHnu4zFiametO5YLoF&ESm zEF;IO^pB<>w4yRUgCM)Qllz!AAf^ky*rVgeAYUw zJPpr2P)0ZM{hGM^vjRpcQpLlpc#^zqg2rv6Xdvw3G?0`QC3y`%9it&*{D-yrzeO`1 z5KxB{991mDe{TY{DYrG322xlZBTK>pId=5qDqg*(rIK^tu_BU`@(u$6h<4>ZWL|xr z@HE!ue|G^y$`N)0VaQ>=hWZ}p;yPGIN!9`YnA$yg$N)KhBk@wlU2O7PI{QoUARIT^xC;&2(V@B)12~5a3HiO5tne7+nD|fu z*HpjYI|&pj=V6=10)eDqy-%n7p)AEbYn$QtCsvvA2_r|UE5l)ePci;oB5v!`Om}J6 z@s-4JM4KCLJb>0!NijBJW z4A27K$&|f4!DILpKKn$I9)%e9JPL)i@!Initz>x%s1+D!$JhgX0eXea7M0C)$5p#L z`3esITc2TVCo@ERi(yg`G}c9N5e`4>_ic#8oY%7H9yw3|QWGC?mt(8&iG=y6{3J!8 z0E9w*GCFj!j@^!)P5qr=&(4C1Z*iCyI0f|88?8&VTmD-7zF)F@-(K8vSoXh>NOx)9 zq-Av{%$siE|2t8Hi&m{(o{vTv-Qf?{jCaPg8$Wq3z1F^r^=LT$R419w+b>JxH%F8! zOfhRM<{VP`z5#;gtLRzttNZSSqux&|P&*p#8B*x5*s?T$iLVzWQ1>U5mLq&MJa@BZ z2_<8j-1px;DV20zZ{>jpLXOji>*Ye_im(aoWv4M65dMes!F|^6r=sGhfd4-%-lF=X z*CkBoCiC}njtBMr;GT$RGJ$hJBcCz z4wxgajmT#Ofq1au=4s{G;>Zl*J-<)>x?S0!Nbs<6-(K6G`h?yv^$-;3Mf>29R~S&3 zNJKXvljCCMN`&`k2%zvo3qXt+x!o~8+~jZIN8ZPN^0b({mCHg*CPaIY!~Kso>fT&=V3yq zd^IM94h4im2j5=IeUnylewMQ67 z4Ei{QECJpg;E?{Q6_9&wxDBjyd>uWFb$`}i)^V>XAeDG#d14C+2>F#>>QSaxupFh6$o%v?YFavYo@LPN3wXU^xv30B9 zmh^d9+y=Q4YW>8nV6}HNx8Aca&pIYMWwkq)=yUO678fHT%935(uTpZ=YREw6tC5kI zVsM5ZR^r5f-4)qx`f#U3{yuf1D1!hM<1*-K;A22eMHI@Z!=xlNcwnR^5=5PAYb)!A z*Z^%baRFXhu5 ztUp!u^S=$|Zvh~<#J{YO&e+?tY=;U7{fe#33%=*3*4v&F*+aQu++)kD%P=%`=W?Hd#zb1EAB{ki53TVSk zvS74=EOiPHK{Dl?(sEC*E6(1HGii#dRweI~jvXM~E%RFzDq1&nF@4EJYN@HMyQwO zeF`olX#DnTfCXCY9lV(I6MV|!bNW^MTDRLYEz}ist&kf7T1WENAcTl_r7E*UM$08D z-Doi>SiaHMO(muH=cs+}v_Pv=pBn#;UjFyk80Ml%HOn&IE(Q?41&L4r=Z(=z(h zE~pA;swqf>4xJA4wWMV)5PS{|MrPhewvl1ar5(Ty4%~{mM4Kc>W(nvK|7vR_>(~MU%o-2Rw38DYCfu>QU zpbXFaT~EvLa70Mx>$^0}>0)|ir+yG&uZIrrOp5h?X0ZB0T}Ulv>)Qh#x$6NyG}R=& ze`!wY$|jZaQfqN7I`izRK3IRQ$!7AFZfB;75g|nz)q*Ws;ch$Xa~Y(^j#zv91)E+xt^yTAHnbN})x zBlot6lW?Y!6Ord?=H7WAE*t%@=CnF&(Q76&xTXBX5`7@G(qb8laVu#g{+osf-2DSY zXS54%HA*rw3A+qE%Md0KvbDiP)t5Syvt==ov63}ttD@_H$-4X4D`-r>L#wM;v{-BjL`JY% zC&HpbNs!6)Awfm2h#^7b47GWG(uZd3yAHS&TrntM7_J3J@7(>}+5==+5@XcFeo|8j zFH6Weu-3E7d6N@_VzYTBa@LsYVueOqHv%IJCilw|qR@m|epdWVgmQXtt_m=n>pAJg3IECb~S+rP&wU4MM?ph6x6vh)H zjEeM{pZdeYN%yj>=Fgnk8eftyF#5<@|1=Ukmq%oCR~Fa9Ctf-{jBxke5`0z|gxu|; z$3WxbVEyx!4iGX3B^vlD*iv{zn$6+)76CLZR075#B!nLgCke{y0^|8_4FvL5RXGql zHYX|CqRCAu+BAdaxFfSO29n{h->T&M7n1wThV%&@iOyBXeo}Z*cNN6kT(Tir-O5WA z6BkHsn%rWu6^kyj-Ya{jLDM8^I7GGchyl=nnjBCLtu_A0--7>ny$*P{y-Op1@}Tog zV#0ou=pzgY;(}bRpc#ejI%rooXH|9}qa271fR8p%s8t=z&}7k^!hgHC{gtzYIr7pm z&1FGI=(An z0&T5g%=0Wy@v0m}o)RH-kW&93@i{c~%E}V^RR4%;dPzE=wtei3{DK?sBx|}OK9YWT zM@wmtq2C!b50tZnzshcrWn>*~-0uB3f5$ncp}guw6v+>x^AWpG9h;aKKzsB-pmZxO^{sddeoDC8oLUYyfFF=`&0BkY}@wO&XLJD!4VIG-s!P~Mt+ zW_2e{N0laG>J4=w4B5jn+MD0uKd~dxXkL(4z7y0KRV98mQeM7ZI`&bIXp)rC0NKU^ z=ORa61 z)!08jBG7DdM8uHJUp$3HipkA)k_zrS2TDP?4^{(5#6h2S3G|X{FBb(ilRd|e6XSo9 zI0Y>ug_Ro?*>3Dv1@H3CWM4k^`uL3;t1W>)K%{_VigjW0D|!oFlTp}}%^ioyweM&r zdxjh3p2(8PxaUE5Z>&#Njg2HMV%wY!=9xvbY6fH1g7xbkED|zWn;b z-MjI__XyD;viBZ0@v`}3^QfuOc63?Fe+v0|xa+j8BFZ|({EfB#u4A9yfE;6m>jcjK z{D03<`1twJeMCn|soJIW_~C;AFD^g-VikuCrxl+8O@U2US^Ov;sd7Bt@UeVc)OgVa87_3(VMS6%UkQ%a3S``3!1;cIJ~ zVUo$p?RYq*#Z|$X3Q=*j)t$>ljbuD1dCOf91lM&Bv7!T5&M)Q~oy`=NBl!?BgJX-j z=1SJ&t}5T)uh8>cl%$3(_#|sfw$iJzbkdD8eG(Ixb~v-B*F(SfD(R9>yVBGW6HFCT zHNy>vt^;+uvu0F~-iFg;Wx4y9w4M-tVT=J0W~T!K>$el1B*Yii=mza;ZNjq+J{<98 zscR5Y>OW-&2}&VblM{JHnF9$V08&`&2!sAf(q6RyI+1eABwaiW#y|DVtNWNd;7x>8 zUjKI~PYA=HRrUpL8BVdtA}pVYkrJPH-v13MFaV2845`6X50o#cbw_8PG-AQ>VM%|) z5g*FXd7Go6hr1mpD*GfowBTckP#oa*OpJ;9Y zJ;JO3k{~jwn+iw&wg9XpKMQE8Y{f3U`6lfnpj2NH)9q9xK}?hifVhH;Kf&`A00_M+ zcfC}Yv}rRBWey)?|Ex|T9h_tY{%E)ByXNIfy|eqSIt|JpZ_5_OFU)vKl0fb|R+bk1 zM_E4?1HJrGd=g|R1@}>ZMCs`*4L+lD8+fz-2T|FvEpsTX<%n*3q>3Z}`tcc{FJTgc zGk~22|IGm?Bt?IQio|#pw6#)5de0e}dt~`#L23#*nw)qMG?6MfHne-5&e*V_dSi!! z7Bz8*X?O1VL|3XnJNrsc5*}Zm%B$R0k(xEN**JDom}yzQq=DOV8uC7++VfeNn%<{x zH7;b3KS;nNk?TZk%8|flZ@vC<0nplJjQcl}wP=SO(j$-B)0kYROJC%=sbd99GzBCA zjWnp|-JNX^zKzzj;$@LkiDfo3e3;0NE5R4>R3(!z2+k{IW-1eSExP;$!Sl^&1|HWCV$cqoN;lM5W^l?Y`F6&=2HidIjE%wEBrmJY)H z4OaP-g5wo`d4Dx5F_b{1a4D-XG5kf)MH6D8$!l^`{M`=VQ!uW71vEadaj+)W(~f#C z40K@HGF|JBS~Q=-C%vwmMOgoZgnQ({kp?SXiT^8;3dl~jnuZPV-$m|gsKFLvrEqD? z*tgMC1{Nfa$aF5FZgQ9%u2^*9qWb_2wg5J)$k4Wc&q z3q9bN43zm$+Z}8{g5!CfCVZjB8PF!YR~Lef^Bs4IgsT=0%P6c3JdU@XKFUC-Rf=wa zv}y5C*tu$*Q--ZcncnLBd*n?)d@U^rAJ^L4>|?$Y{Eyz1t=C8n-kZ@z7mk;o#@%mk z68Y2}XEP;?Tp%gF#eGVw5-{u0#0 zTqZ+uRk?MXG&;iS?e2-m5;j|y;Vu$6Pd7kK4}OMaYgQydOUxgUWj#b-^-Pm77<5fB=B&T zcPJYLZ|#I=>O&Nh!7@5cFjU(u$fX9A)XCbSUjKA3q$>}Q@$M^IKtu6@i>N82;t7}h z`h?!TdMB`wka2OKfjW()HTWCbI`fBR zA#%y6u3~YA52jZzY5$(j9BGicQ|atL|JqE6h@NnEbl+nlbU8}30L-wK(Mks_KEvTS z=prfPdP?SPiZsqy=H7_P(&7A2o7-G5_8L>-&{~kJ+YU1-;vp_OZ6Fh**O-)E@0Yh5 zSNj;5iSkM~1(fZ*pDB(RTCdZpm*45j58t^G9t$$&>q)v` zafr{yfii6GQTn?2-*&7p+#7i)!G(iLz#e!HF4U82XYs09>F@Yp>PMJRSyJY2sr(ae z;5!a?3jQtGP`J7SmSjeDh50r#U$gFwT#ekE8@buKiK-i5Q?2x6BmU9!e>A)}8l2Bq zM4G)w^KaJ1Pl3p6gZQsBkRDs94;e)2$A+I23`ToS2sH1P()H=Xlu-M?*7)+XY1j?deTpUR#So%zpeLMs9MPdY3`MIL*Er z3ep&y=x$a$?Y7j@;>XplYng!XGH|MYJRug+k&QR?Q?`m()qqrWf6UBQc}SidU=36QKvR@wGh$c@!WnB- zYruk64do!YAHNh0rM^~=#3~*?lZbG9XE`|3^)=6)ERsG?ca9NKJ!b- zLP|&n6Y|^FSe>^pEuEy304cHdPb7_Ig1BqdTbdkgvnjfN*jc_>DSP^KOh&ap#qT?{ zQh9!H-zW=kViiOg$HwsPrLw$Y*xzdYRSWrierj||di#3xb-I3ww{#ihpVB1@&bLu` ze^2JqzY2eIIAx&o1MB~<=Vb!EDP{QA2s1Wd$Z>Grbq-FZkK=@1{~%6|xo%mWI3<}C ztrL*(Jl*v<_C0zQ>sPlef?5? z*7u&>pQJGPK zM&be;G)WP$`zPxo*FYGcG!+%=oIjcj8;r9oB{!j%xeWRv2}?@2hL4#4(D>)=KzQ?0 zK#( z%adNJNlB8MNRa;$|MO^8z<`uBP~@He=UEHjiX}QNlPmH9p@(cZ$4rHSq{ zO1Pd#7^I^h51-~8x53W3BPhi#Z1ML`w$*3+>rG30K)kqGfaHjINC<`{U+`RT^YKSE&y6G9xXo_$BE=0Okv^|r`bqXLV*-}doC)My zN+X-K3yO@aInu1J&jGhr-8vkk)?9{?AclL@h6!Di{-PcK8V^c zY%Do+;-Z{(ziXE7j)>HjZ!)W~SkNa`b`Qi;5Rw+Au4pgX|{F3c*aN>ME1VHGRU!A%Bh5Bk)3WbCqGIm7Q- zKCA8R%celZw2B=^`SNcM9h{hONYzJt^+3ekM5<##vKyCt04!C5l{!bum#TR@F06yopYbi#% zYxJZ-BtkiJK3MkHg0!wVOZIOPlLEJ4YV&9R-cn)yn_%!k@NYE>N=dt4XQEe)Rz-}T zlr=b*BbbT-bWyT<1t%@5%P+}&^haCc|~P^3oHH0IM( z*s})k zPQT3PU>pN_%L8c$Or?&?(Uz%W;3+M=eN=WklErKdaT5RSbC)ivNLqtC5A2Cv41&?9 ztgIM;DQE(Z)!bq)tuEOS} zben?{LK;Prg!E|B&DTS*v2ru!Bk8|GoF&2Ev1Q=oW0Oe2CgAl-Q|9k_1s5*1)1W_f zFFj61-?5QNO~#Vo$E!O(k#4=S27?Wrn`{jfTz7<}o;oq7&rco}WM2){rle=>)2KAWP`1Dvw@c3+ zwGrpl2&>lR;wJ|+$023mEBT`IHwIBU@xV1@p1(DjL3tVoJs@6Em?DKpHDcNR4fi6= z$H;9}*>=7G8}f<}*%)$$@w!f9(cc`vr&xeq0B-^()I?a(b$pw`rKIJO^Hd3p^Fn$3;*;${AZgtlIr4l;zg1 zv>A++=tKW~QnB|D+H8}17;(x646DO#QZ(l#15KM(7vD!|W`k!8|D!jx)dk0Y>orf2g z@^iL-CHn?QHj83qEL6~BfO?C+TEQ;+$_Z5uB;bY=$*&Zl}>0+@;kknkQ?W?ZYKK_CnY9FEHV-Gy2aE4VMO<8UQIk{MqSlB_WWdZqb@9h|CaN=Pq5CASt{juI?FZWse@=hll;18V@s;a6|FwMe*ZGT75+GLk}G zU#&ofk9L7(@T-peILwwPlh8l<^$rp4Fwe@#NYe;u)6_i!(2{vPpb&Y&8iwiTp`NI?7|Uo$!Lz4i|N=n8%URZJrvAD{P%Jms#@aKg(i!K~yFjN18@`w5~jYn`@ow!fzEpJ~2- zG6HZ+^ACQT0wqB6ew#v`KeXxU2Cs4lQ*ckW+Q|grYO;p{PaB?I&F`Z*?k|4PQ2>|nls~XNf4u%le*G}v*jt3b=qG+W-ha-0J`@f&)sP88 zSurdPejgU&{~Q*8lLVbksC{L&(Cgiq2nSkQv2^ZbJ~zwUYOzi_-x!rDU!O!CRE1Gi zL@`p|b34mKlms{*`O}}(PLIqui<*<%Z{^Jjdv`~5UP|IH(B-p&(d3E!Q$rp6!9EZT zYpaNiu|tByisHMMi#z3rb1PR6!K#K2b(}f_i!`~eUvAS`a+UJ~*mAt~K8HA69ldxz zTk`mjs!*|9t;UVnO}j}XoT_Pla~+n;$&!v1ZL~R(5yGls1POQ}?BPSODvr}M9 z7!hJNJ=a-c-2S)>O|E@8H~JB6n(B8#!_5vB32+hk$VBPo)16u8{A`lXQ|R~VU}A5- zHIdG#F#!)cmYCx7U(4JP2X4?|#tv#D^t_qmom)%e76wC0#4+gE%l?hei=@CX)uo&? zI@g5IV|-xpnZatzAAhhpvY1FQSiSHUvtWslPu~+Bo8LkfC-Pq#OkNuM`PDzT*1Qm3 z5}U?LfJj(rbDOuE0AR3+3~Pv>ybC9jV%$4!;|u9H?#lD8CDJJkQOs6?@i;GF2MvrW zm}aiWa#`u-$EHQ~IYt%Cw(`uMB!1m+C71O6^g{G4m%CM>N5^H&={#fMY;}H6>hWs7 z*=Qx)C3m?+hNN?kK0v7lMf&|lPn35Tm6#RFQvKKo5C{Aici!-93M$i|;Hbx}(*9}A zJ%tytzN{gC=dAbTo~Hytn+m5qUBv_hylAMn?NoVBE|o6>pahj9ZJ9(!aoHfHNJfCJv|Pi@CFVNPdS@E@ei6poZd02>A0^y934;5Q_Hc=sf5SRbuEvTtc z46=-q{wN;&GkD>UJCS|dSnLy&E|PbZ?OvT@EP#%h2zaw_*V+b8(3@%wszV3KbdR4+ zeNQh}JPfkz| z_cGp7U-;hY9G~?9v%+!73`6CIyFOmM+qI;PHk6i9YBhvex?U>z#Or=)hKx{=aH_J< z-0QXErmH#blh}faTYJf0pe^y<-S+k_d@hm-ourjcP5vz7NUV1L2(z3QPy1I} z6XkdzgfmM~I+h^8=q0Q$!0@tb5Fof)-kxZJ4HCiS?`N-A=OT`Fe)0lZldkKGmjfzE zx@=^;DJ#^;k&qu-Zu4Y&NSJeW5g7_ zs0rizRA#`@%eN}=P^-ZuV0pIN&Su4R5BvHIg)yjmq|CQtEfK{oN8GtilJw24jF4uav$kM6@lTbK;Dz90JSD*HdDP1 z4@mNQ!(1Vi`iN$A;-9y{bnWNq=x>w-C%c5;E?-O&I$(=eI)$19RXY7lbWaY+q5dUv z-YH!Q)XBxcXj*3O*F?Ny*8y2WB-2#7w@h?5CMIl~!*9yY^g^<6F1NUGWfMY{F3~sJ zC`=-fw6xB0-9Erfu81OHoy>Uf>Z7H#J$j`rKVEMv3A@%80jfdTw7;9QYVWtri-m^E zU$eypY{8jJSxR)Nc14fh*l}MzlDcgk8~wURznYWE$3RddT2(21g=v$_p-=dD_Z;ub zrlN49Y=PD4FNyqwy8GiyS_aqkv=md1wkV@Im%V(FxxB1g{gxg%dp?gj0HcLI zZ0jREc|W-S=+8^wjfOt_J-ZztxG(a(EYsUX0o7?3<22Bs){4VTbK%wJC+%R-pNJ*( zKaedLwtEJ5W#)9C z9z^EZ-Dw9%Q>V|b@=v1+0ori|hYm(|?c(AWoA^yBoSG*b#na-1To>r@JkVe>EZQGh zKNPzzZ24Q$Ydh)o0wS3tYubT?I(?}yUh_qU1Kt@EX-2^o_Ew9 zRx{S>ZgEM@^T22@(c!GEPeL6<2^P=$k@m!Vx?m}eQq!CO(J)_A1TI;M|!|083vh6lI zWTc<51&ZFSYbW)f5FT);Syu^7u4Z4?ca)a;;D!)9xR|wn&SA$YNTK_iL7WP z5kKWG=8m8^#>UB8O{{wwKy%2M9nG1oK#bFa{gxpM;lG071CgMQ)jH$Zl_(i}dV2#_ zK$kc?)Qt?*6{OR+`xmbm;hBKEFQ9ZEK&N_?P@DG@k|soh#YqJn?w30Z4}9%fGx9b{ z?}oL(Lb?T3hsQL(7F(W6!$)m2lyscBNezZdl;lnja7UW5nBQM58qlxOC-sPw5c!Jc zjwdB){hBm?5{N_VBT8pD30R1Ss+AuxF0Wx9PY}`_a`g@oz>HFn&E|ki@K^4KoTNnu zQb7tGM{z@0dep;N>`WA@GfoJ`pgx`92)Mlo>BZF$zu3#D)4dJXJhUNZ7K`abCn=nK z?NC_M>pVOfKFa<}T}37NRNMJyg{}GZ$JrY^_Pr*G^)ZU$lZ#^l0aSuz;m3mR7f1NX zk4aQjGPQ5r$4O8#h!;<(1LfnNfUPcBt<0sHuZa3~p`34ycQ>D2yPeZuE%EX3+Z{x| zIR3k%cHUZ{GuipM`+iu{(yFUX9QDPJ9yYxiHh;^5ryQKMm5Q)uQzayY-r6Fd1Rt_f zB@xBWPNbKdC{kG65TR?VeD)~V-JaWOo|^LU)10qjZ!c_(NAvit@%r+NK$yXQymf_y zD2YARt^Nkf5Ff%xI@DyKqKTnRrkxkf=DuaSHCYNMEOS!vw(osV*>j8WHeEm0J9;w> zgrZplsLk%xEQP)5q1DIbWUxi_7WLA zxy>W_^78!oOK|;i){&AZRRFX*Hk)07(v&~c&3BV?m-LZyx823JJ12*krg(t}H&@Rr zPB%sg^ri7U6mMv-SD=iGV5S4FOuHV_$94;G)nrm?lIFh^i%7E;+}iQfmfQdj*-(fZ z7gIbNj76CrE9~OaFYie$1Ul)bLhi+@(dQ!bmO&`-GL`dhk!}h3dWr9UG_xww(n>nZ zmA8Ujq>@Ka9b#4{a~bhJPOhe=FC#DGh%9d`y3XmQL<-YvuAi%1n6xQr*3Y$C4K|rj zkJv0lmUnA0y+S&zbTur^*3bW8=b!8@5%|fC_ZlVo zN7*keK~oA1@0#?tO@q;x?ksagdGGqdXd8fT(fq}!3q)vama@N0TVG{QhyP+Pzjx_X zcd<))VNV>m=+oEavbrBBwnf3?uG^W^U+r)kI(2k>t`SSCu*eFCFFZoltK)0xHl<)I z1AzK}F7uput>W~$2JD)$8)Ed{vum9IFyLxmQxR@KYzW{0qGqs@SZHI@yd~6}K8lRb zCZ20_^%Cwp^^&s8Y6;tH!x-(k#aZsx{Cr)Q$03PB=SLn8LY5nAwoAJLhD#)fFP=Xw z`EpBzWhJpDoU|jdw(@J!+Ro2lb8|!1DJj3OFf^(&x2G_p(XM8gtM;+r=keo1?PL;b zfj%Fx-k}NuSJ&8PKQ9>P;Mi#nf!ufi{jRWJJ%fAKs_;*A8^wWuwUR}2q5~{RSy_+X zovS%nx2-M(A?%8qD_8#L!5bycTQ&L~J(k_&S{sI2C&%5p7i^o}<3(C2%vd40uW#%iSW z59s=w7zOP3ZP!+9uc*X&0kvx^pMKiW?R>6 zF)E1#LWjnWk0nAmLK<1lHSqA-+%zlhjg#&c&ta{*LDJ0zFF(B9WIb_SOt`q|OH`!; zby85fKMma;*G0}whKe9Zevj6>q}CZrIGO6Vdcy(8JU9rB!;$w4aP)9EOv$M}zZES| zSQuwE-Jm^}$6mWvH)ixnic(a|MT^f7+v_FA99VDC;q>h8R>2G5*mvX~Fa5|w7;bE~ zr75H`Fx=7zbaJbh83Iy zKv-{ZV?*z)&EsEsYDaxyy|2p_iBpmJE34!e+q+8IqTFOW;gMZh)9uiW|2j5W;MU~t zE~QQa!cIa+g)%In=q)ylkXGReUrG=ZIwo%C?@@*^@7@fD+yL+qe2Koh(MZf!k500S zjr>sDs~PrM{3|6tfY`jS1vV@^D{M8H<(7aI9Bu1u($7NQ=5bQ#*T>4w&-tHEk^=y zncS9}gi#(M>sqv4H?wIc33Xv6^bZ$mmNv@5KE^x^Ajx=5TCA5!UW+7kR`gSV@15@T z?wkhZQwG}a7%#EQ(-8rad~+}9p7a@hp1M$jvS4wxg7#(_ST`ciYpzlGEN}I;W0{Jn zPvaIWnBfb~ZFD+(P>{#2f4-|^!01FKB~oDJ>br0hj@6}}{`?}CfPVLlyBjFBg17k< z9r(ct_xnb@`Aod!&Vrff1Vwe`g2lX&u3F04hTQ?|sbjw||+OJ5KO5c)o@VxFPd(SlO@==$8cP&KT}SVvNR zfW|NL6EvI>3l2)$GugFi>IaJ{L={x}GVT+ju=#169THAe8DvqPeyee3V$M$UhxWPu z7)+!2TD0E(QSwr8efUSZpwYAPPDEsnJIEd$+xJryVDeJXp1z*wF%nq2*quAC?nsCL zMz(}vq1FAnPws%1XDsP_ZtB>;y)2XCXfMpNn9sRq0vv98XCofZQ6FLzV*2~52{&E_ z%Hxa1$%_63Qv#g`u3)YW79IlN3;LN$olT)0iSi!itwm%B7Qtx@wC z-Ooj2gy(nuzU(RPKza+g80;TFdP#|~p~B4&(E?HT?M(NR#9<9x9*h>pgJS!5gJiP9 zaL13GTrW)c(Jr5ugmGJnAscPOIm$T8%xkY8_+f=b^t> zik&S>$o^sJkkbrvp9KD0n0Nmq0@VCn5+V`8(jn6 zvspfd%c1b~D<)HZsqJTI_UlR=;qqZ;5O4*Bo@2kD*V66stqbGrUdH=Z=H}+4a=2)L zwbrhh-Cqu?sY7q>s?2oqVtilExh;E2VIeypFV})0TJU-N1zW0=;Kzf#a1F`KEwyZy z0$Rm-7Zb?{`kaqWlxs~g39tQbCdJmv^iy}=9I?}=uyhLd0`=Y#ndGDIv|(XO1SBsfeelG+wON$5;k7nKg|xKDR8 zmKn_}gQNq4vyVrbnQd>*OlaShA|K;(wrv$VbMB^#Dy(%Kn7CgX7E=Xgf8jRbc*gD= zVMr1tq91Ma2}8;jOyrH`drALR+Pj-5A~D$+$E*omH-sG*qu(fhYy2JLSSq+Ob2q6k z-j!i$yO&i8v3{YNGg88;V(j@j`|O2?=;7Lv@DkJ9pkuJ4@9s}}6|1BC%KlvWo0&L+ zhTierpa_a7A6tWet$X5;ZGac7 zY=;2prIpUs5Aa9}go14gX`5BVri=yVqnNN#h*N)Q?7%X5f-oh0%JgZHDq(ywZJ!*j z)9Hh-6vS7!GWGRVLD$jUG zQLj>ma>o!D{Omn^a@&1-&bI{}Qz|sjn&4t#%4ev0Shp(Lb*s3t;IyDea~Qm?XM4@t z=|~iq(0(o;;LB-@KfYonhF#$}i@q!Ht!61}q=0VxT%OX6VVME?xiMSWQ&Qzdp7q(n zuiXbjkDBr%Bay=>dSX(DTQ61fjS8}lced3xG}g?9V$NO{f60+4l2y!bd*r%}PewHo z;B^b>DbF!ESX8@kQI{hnGd(u>8Ir{BCW*7Dm}cKl7fW>T$^R*bn#kiNHpi)_JzwTj zw8x)8q_pv={`gA!7f&=(#}>#sAFd)v^xi>Y{NrQw?Ed^p>vWac{Y7Z z;I$zs!Z0AcmFRzg78kggGvZ4mX1qxpT8`t_abzY;=qe>a$FY;uaGUhybSh-$u%cS5 zfA94giO&_gOOHH8+$Q2mcX)W?ZKdZhY>T-x71yEKep3fr8H1XEhaO`wlFmZ0FQIQw zi2dhu;a^`VBENzKqVi8q3BgK3@J!8~SSms`FLj#MTL}r6@+_*}D3ZH#ENuci8HGP|g?W%MuDz<1X1s$$+IhgyiM2@@Y+>LgPj5^RY-4C*A#C zV@>J}U|qu}4OHtbE4-3Qk$R&CJ~^(3w3O$OZkkd?j5BQAYr!* zxj3wOi%tHNxE|#UZbx zXPV!Klp{iVD_w;@zbgGZYZ-l7yJ}8>@s$9tMv{S%FFtGQg$9~xO!*1Zbk|+1E&($4 zQ!nSZ1HT!BBxI~Ef-1Sl)4kAnaUO#4!i*`A|6`z#k|66~1YKQl>=&?(?buoZqoUrs ztpk!sD(Mu2{f&o#UXZVs1Cm!k$f8usCSMt7d=)RVDesB7fKKz3#h5;k*FI1_D7yp; z?-~%E$EE56|+j$ z8x%F!`U4GhdSDAB|I^pAJ*@E}5Y1uG@%L$3XljdS-owwN?Ljj5cI z7)8^t?$`O_M_9T3w$@b1Y1CE+ zYYLK`SuY7`nfjJ}XXD6Gr*>W6*!o-=xOnPo`+IJ@@^x;d*)AQ8fc9Lc$i7>71bz@E%1HN*rQcEVm_9XJ0A19{G z#caRakZ|b69Io%?mrj+oG7(t}uWi*3I(w$`=Ip|3uq9Y3Uf{3mwGntwkSe`Ed(Vc_DNMJ9aOd$G!X)YdA;qRvxb#t#!=n2^@$=iUXvg zOes74NyDtX@XaKX8)?PhqoJ8LRiR=>JE86SB=@iO@u3FUE$ejKt0cG}&&nC59vXVb1^|FTDogYIk z=;MsHb_MAsAv{nz)BWdjoSWiI2+0bg4=R3bCe2Op?F_4-{Yth|7h$V&JenR`iHEf2 zud}hFj(H1^b?x>c9xx;%Ke%BV-g@~r&GA3rVzW4ux0ZWybLi*~H7rT_oe`#ncr=JI z=t%@oX;qe=giH=qQrkZKn+QL<|J)SA$KNjksS!DI?e+Jz(=IC>*M$c}JZ_MrTb@ck zm-(vtC)(H$I?uBi4p)66is|{Yool2G3U7K@?NJ7?FHO|sgV*;y8G~GA#VOAhhsL4u z=wRlo7lP4mcF3qnE{j{CI#C+++*B>qi{(#}1?*@-2xO^!+xJBZV)eGm$+%Oq+aZ96 z^VPs!&M{VWtC#HqMqtzX?IS;V&kK_7WdLqV)9oG-TFa=Gv#?gxT45a{EeQNYa7RsKxq z>uNA!DE3}na~BQAREVjAiDc(TDVumqkNta#+fQozX>so4i^TO`}i5s&lsCcF)I)+BmnO?7si!?JRcKdyf`q$+FDdID6eXi1vGPVj?kFmR`b!=v9issyxEoH2uKIC7ec> z_L*RIh(sd=N{(TME-ceTG?L?%;D(pq^`wP-PUFp5k=g$t>not5(7JFHgA!?w?k*{5 zkS>X#8>PFE4udXfX&Jhukyh!B0VD*Z8|j932EF(G@4dBVu~-Al4Cm~#cA;HPPXz=3&ac>o8eJTDqVzQkfy{fuJ^U`6M2T@<-nNQxiT7`pR z|9*=bY1Z=Qv2}t+NWL{^$7@=*+SBCauD``O2NI$4!qz?4*VAD3&p zpjw#IoUh(t9+agbEufy>{D69%{`$D6MR-tf@*;^Sl;LPqGk|qbmZ@6~Zh? zl}H7lU+0c<(HAI;2`Wr;qbq4;)|sKNJC?nv4D+T+OhnX)#&gSBHec$sFP$H@bXrb* zM&@Xd4bo66Q#X#~cPpKPV(MRX2Q^o(?W2v^Xv}?z6p}1i9YnA=|1ImX+2hIx?%{JK zJANm6$olS$<6XF+u)b(D08K+K$}&2vm*2jPhyo8uj)&!-4zcbZ|9%+tp^Ep+-9u&k zg(ZE666GGb_0QPiU14{*344&ok`O?`$UaXvpXv=?&{@$^TB#$0(RkPUw#i>CVu^X2 z17S<+n4}8YW>(&o)5V3UDN@nGsL%w`2a12PX7JUB9i#{BwR{-WMv>K{kxRiqVM4i^ zVldi=!i61Jl@q?MqSa&$MnUmX;z$QpJo7olkYjsmp!x~vidk4QCeIv!fSsY7$IoHx z=v!v0z~QwdXQA3a@w$b{TmoKDSYbZyCF6U~N8^!oYOOq+Xj_x8h0~6J2K4ltv)&u_ zfrJUp&JYWWknb5BJxRK58J}pZk9(XJ*$CQ%m2fUf6y0`2azqzhS{yW1y@JtKW=_6$ z-rcbAFerZc_&iho-aBvkebvA!qnXUWMMAD;&X|`Q5kGb{ahRdsJg6P8y|zVXDP!<6 zm@Jn&-c>nX`1Ry`b)0uoD0ogfO2mEUwGb4Ix)##%`o2GKmct)*QwR>*@u37qBu)OL zLdaYUi{YM>Mp~$te29spD+_ylYBIbup_`1-?F`Y=<>c;F!{!|y zNC`qtZ|c|VrWp2Pzwc`sXYx2a27-li^~eDGNair!B$GYO*^N*tvPFL zWZl_Gp|7O=!%yA{yfEX{sEkYxZ1a)L`Ip_kGjA<%M4Eqn@7!XrVo81$b$s0t`%b%sSbo^gz+Ul>m`ji)(!a* zB6Z5z)4fMEW~*;Tt2}!}lkf>3o4;c35OTXF+00e?`1H{2_$(ZrT9QYcdK3um&eY@K z_S>JIspCzZK7b}fv?`PQv%&L~0Jp$({-Lh|+dnwqB;VTHw04Ld_}2k_730E;g0- zyjmgAZ^`j83UzFN_~=3&mz%kIo-+qu^IY<{CVV|B-qS}*_pGqn#MHSup?Jsd4au!z zTkcMZ#I8k5-=S>H|Hu;4@@3sDbmgzFu~k~oe?>eopnL# zU&K7!SD{gQq0NyC74xVZq1w9_pa4KVwZ-uCx$&G4NoVT>+iB+*kcDJ{{3156I`7c( zTgH5k2R`durejO-f@*Br@CY0r>5lbGiKDnhGT`TheE&WSsv(F%adP_VweM1cggl6u z0vPitD&pee+e5M2`uXWl^{WrJG?Wx0R`ektvTE{Z6dA<`H6t%pttpM0P2e00L?GJgAe65B7C-KiqHEgQ#^ z?l?1>Ay)p^+F9kQ%Cm(=0&6^a^`*V#utZp;tk*k1dHi>I5t({tXnuaL_h25$0(OK= zNHC$_VHr*X#s}{920_Z7{QN(BomC0hH_h+-Elm5fa*NyT(uLo5K3Fsc!omE`>n&bfUcO_Z)1H)*1yL1G9g&s-XD ze%1M}rVF~t@vSOTggDBj374e00g!U3`aBE-4L;+%;b@`y6f}=Vc%6h*>^{oLp5CeN zK&9o+g!MTn-}PknUcJ|DGb!1PE{&nV_m;WCfIn*oa@j$NR$R%F60b;rchnCC#s>My z&xT+n4@DKiTXbl28qE^7+_wB{;sir>u5(Tn42sjguLNHzsWqq|7aBIjb^nO&4h9|k zdyCQwUL_y?dOrAO^23u5#ri;>hV1<6S67}oExu{#ZT@fGAyp)h2KCcLGFqV}eQqmW zGKpZ$d1$HxDLRu!>#-(%+jFSIDO(eeYl!jeenfE)5`fXnr4nQ> z(TU3v936`%2C5amJXO^=k2J_9@Ci$aAc6&Qn9G>L-u+qz$xW5TvK?kg57*noR|=bD z&6~0Dzw1;B9%7*Pw~G0_wR4XKgXNA>-;Z4uS0?byl2j0`-Y2kycPv{W>rFcq2kTY$e*{0=l> zbL(hTuYd+3f94i)K1a>Yt_flSujklxNyDK^LNYcgxa>Y23lo}*@`^s9zH2_!erBq| zNr>b}b7H!%w+%Uyqhv<5EPYvU?wD)W&7-MFaoECR!w?H~rj=`kEaeC1BAide*OY+2 zw<=Vtdfri2SBG_gO$T?&yt~<0M+!H+W`q z=YBEmd?<80G~&W?x5a!}oXaeHhmq9C=!wGSz!lx$MJUN(o8ieP3|%U#@^ApcVWD_m zs|X(Q6S-QePQMf}V?9q9Fl?4YZhJQ*eQYNwi|s`aC%r;h0Uhs*%CHzNOmo@l!<9>6 zByhc|$IFq6rshz6PV^^Bhkp(Ja7Us933IXpL4xHC7E}Xfpae|zs@?*Xg#Wdy^B!Mi zZdSC9u2q7i>BC@j*9ki-Y4?%Bmet>U{9bzr8kk4h*v|0uWUT-TnL-=N8+D~VH)Xjza^O&HmCy;$E(tEVf{DKxiEqJe~6 z=+wU^vz>I+tDJsMifpk`$GQ~Vs*r&AAP7mrIV8*1G6&N~N2Szey(;tr;@?7q9OD7P zFK9w3cCZe_KO~p@r8I&L&&`i=FCR_JpGasB`6v`Rc!@F30Wy~_eHwn_0fb>Xm{%#( zw6vSS(1dR^G7{M+k5O`YDC~o?_&h=^l$p&Z&zH8)q1&++QS=fL(F^%%24eH8^!n`a zyrJWuOl#-t5`j>bNi4-uvwOHF3qSZd;*EnxjM*#Smqw*O8N*v)+d68la=I_GCMv5+ zo#!sab830iiP@*+A|KDnLbI=!tq$%W61(5f`Bwj&nV`F0|Ah0zf&?S&)(76h0{*F8 zUPBtz^N~Wxw9kJ|pMPHduIerQkViKUU^;1Tjl7>hgCq5}L8jvXuj>BE(%k%Uw?V~k zcG_);m}Ty>Qi8Kuk>{9Ph!Yf_&9M98?Ab2AT;_r(jT{G^m19fO)I=V!_Q-~as)#%B zbNTU|On~Lc-L)~<9gm|-iW-R*ob@{l@3^cG6U@7aWwzF+CC2SalRU({PU7M^q!RKZ zl(91+DQIgl*l07oB*%5#;CX$w$>Soj&{z8-p&DBW+kz`mn8mr6qtjNOdUA_VgRs0j z^Cz#$|s++E}{1>Ae zH6=uv*;DGFNwAiS){KXdSKp~KIAiFJG7URiVoU|lQ!@jYUMIttKJqZ3tqp6fR6cK~ zjCb7MJI*%jbc-*`xRPcanrWX7FY4L^ucEryfy>p)>wL8Gy~?h)1&mZv!HCXpFRPxV zT1mNlDoRi(9#*K9qetOk8F>%itT!&umv~6WGLY&v;e&^^Kk(rTmj?*1sWH-^2`oHf z;;@RvTqqO#@H$`slz`YIi`Y9E0FS*b@=&ktNM3^zotI$99&6>=RBV==^#eU z5^~q@PwXydP34bs(`)r6-Sez#aE zZf>zX{{5W3pE#2*pf0GdpM|vPv4{NNJA>|vJe_Cx&vE7OpIS7C$(VTs*qABo_ou(P zo+!_pEJTbn_>{AGyw>Y%+WTC@g~QO$5cBph{vn1jkiox9d6y+dK~Du{q~Yu9>uJ*5 z+1%nX5wX-nI{ddKCu>x`R8^$k!brPUl1*=9;2azR3 zsyP*bWRdZ{7vVb3iIZM1U2!!Ev4@3Jj$23xYz|crs-IPSvgz@0Fw~I^p)_SMN>Pz6 zx~kSiw`^N7)sg(=Ax3?OS%y2ii=LT@Lk@M@Lq{r>1$TGObg~`Xf%`l;gIl=PFbBSv9&#~Fd^+1{($_i_u>X?0PPqY)+rKc*q_-u<38T`)FfHbHG3Zo z!gSm$^_270wX?#1EsXhG9edtyv#lm2g~F=94QUnb+2GF2`~DV<6(1QvG1BevYMs0- zBD_x9ZunT{>M~gMo3iFq2Hzvo8V^i3 zC<{@1-p;#~A=Et++{L&bbeR2w6YUZmsv8Q|t|V#A4&q<9cxiEzA`3K|?vLeb#R+27 z5yX|dyYKbrf0>{g)X#HU=;qYvCWLHm^doZZ=Pm2>o6dfoWmy_E>A0$pt9)Fxmq;d{ z{b;&m5+mvFHr_$dzy!21E^S@`m*Y40cZd@Rd_+Q$i@RL~AOdXKU5F0<8F1%W24yVC`G%ZM1u<6oa5S~Zx zph5umXfL<4{>s`JhWPK+-YtR?BZTU4Ok%K54l$Q`Agx|U<+0o^v+lQ}lrA6?_>7jL z6x(M*qKopHOi=zH@y57DcJQ%l0pQdgF8@=&eaO>W!h*B}>S7)-$#{R*qJ;)o=73#| z%=jXux@tQ_n4Sz{kdLrV9A>U`Td7W=)EQHn8ZHdQ?-4Z(y%>3fsnEjzZJ}dttlt#Z*uazaA_GvJ(}2v-p5erqtrWHHOp#;JzO@WW^(;n(x7Ak>Y5GaE0157lWT__+xc4eyq@JCG$$Ro zj?pDhYM-riJ^%hb&Rl_z_gJa^V!psk9o$k)MaO0>XPul@$g$WNrNr6cLg_0+^{=cz z0v#;H@OSQ-V-yHe%})4wqYZBL7K3?#`8|ld!o6D`I1gu5zH|s1$LGQUDh|=df5R>> zHu&R#suV5MKfntO@T1Jbq6Gg$Wq4o~f}1a$La&b~tfCJwM+3vb1tO2agPn*`-fm3m zL3{W~Bcd0CiwAwM&#C=T9O;n-jP4bc>9RuQBa{GzP`@W%{7FU7;3Tt0td|z<&gr1K zm&L^_=r{vHP1m(6RL75&C0y3tnsN)VGDThF_CMNkTownY-U%)*q?pAQNVQY;n7;3n z*W4ZM3rHz2O1e}la$?_?Gx6zK4PbHuRb?I(vAF)L{oluNtksrk)dt^vv&`>`ZWQ7T z@^Kw)A^)T|Xsk;5lZ>{BJ_*fxvZyvKnd45hjGqQuuQ44cYVtsWdurLyqrmqa}=?D3+ctpCCeTk_=m z5-cJjB3INtK0ZgQaGc|Fx<4O8KDx4wj*QHHQy|<#Ay_B+evlb_4d+|o>-&4<)dqwT zcz(Ilp(nQv9)R6Kz|_lq!=8%%iMaysSVH$X(hC!qvKfjUt5sL+hl&Gq>6iL-7gv^ccBGDq60@bMZn-q)6ox<^O;lKZYPL>{xsNDL_Tu%HRa4+r3X)5;AFf>VS~2crUTHyoyK ziT`FsU`*C{u#VfV&EmJJ>5doA2d%|Kw12F;2t5?zadpvX!!4kwC8?*QPQcgm%_`%gFZ-h~hyT%V)N9ELtR3Mf8%-uYWiyb+~{YA<- zY$!6ndN-iFLM?D5=)H&f!i$PpPVoi)M2(Bb+HJ5RKQTEF6Ek zeDh?9!0>ahT1_2Wj(lut%qewDbZfe>muTmJUW5b7oLjLpxJHj-XJpB9AY*#1)ery0 zel-V(!;*Xzxnqo27gr(mXsHTc%fz^P$C8+1`qN-(GXXGj);;U|=}(hwDk&O9n1ujG zpvC-oTHRnefvnR&vmjIfu6N_RESHl=nI>|q;w!!QOScJ2{+m_?OTqJEOxF5`Z|E^z zLN<4rF5=k5&!2>(n%Wg&RQE-T3d21;FFcwKG}LM(nZo`5fhfaY|If?+CRlIYkb=ZqqG|Dn;t^4p1UeqYikBvrn#`{_eW zb)ml8D+bFnRSz>3Lidzx3cQxe%mn?eIdVzwD@@ z8UpOKE!J?A1PiI#iWta3>t4IC9yQts)duE_&`HxT%9vv2P=_>Gto&dVd&_C){HMzG zIKcfTr$ufY%?!nfeu69FD+sTXa^%uKF0@66U*Bb0?>V~AkZ!5~cSLR7q@IWCVZ1V_ z!|C?6*vOB>(di=J~FtS+rTA#ugxre5yQPsr?3(3 zUqAjb^0~wxrCPGsI7jQ7zhp5Y@JYRIo}sGVB4vM84iUTt)5V1WPTUp141NsRtT3#TnfvrgT%aH4C!EuN3@kwy(KYI=rT<432V7ase zwq?L*JqDt7BUmUJ;@a3Ik6eO&Bq(?)XZ)df0!sv<9O0d>e>L*r@RVA4I-vVsv5~*OI5vDWr&nX3$Fo zRd6|4xmT3Z5r!XCUt1)Z6VL~{_G=kxILXD#F2=2PAIcjv7`imQ83KSj=D^pO{PqYD zAIhn!Mf#HS3k;vjQ}%Dy;YA&W!l=}iakED9#GX39*8{`pxiinWe=Kv4@(Wi!_-SajJFal;uY4)CQKo(Rc&O1yR zx7;o`+9-EfAe)mZnzS(**Nx3fi;E>(-~W-i6T>A5LDL*B{z*8%OA#l4ZTdLPAFT71 zTcRYtX+BjOrqXr)LC}x^X671T;9QP6?okjR_jZN0aX`ky3kuwS5v!GWzbc|)OxiqO z>{P0PbSdb*CaEP}mk-wkS_-(Z*;FP)@3lEPL_TTaYZkucb^_I5+szl+u()n)RdtG8 zSP9-!!c0O_3@)!5np!c8;?LgJNxq63@!tPq?<)prE0RZ9!ew z4L2q;Bh8W=X%9YLb!sfyT)B2?j1`RFKRBEiPoMER4aY0}Axk{mqav5wmO==lmjw+D zAB-mFZip+vIHtpWZdW2+T`!b{>SvY~0&*F&Nw4{w{!0ZE!7~1ghKgf2FoKm$wGt?Q z0{4BNL&1s_J|ML9!DrJrOv!&?5hmej(>+KmX4?XJs**a+| z;75mFKRWmyKiUbpD414`A43yf0%*vJjaL)Yn(qxzEI0ewbzOEdMc^TaZ7E|y<-;Bx zR&=eKnB3O3ENIBcfs3UmmC#+C8&ds%!Xb}sCL6G^kmoO-I#!juC*6r-b>)zYWn@@B z>6b_v$*bzsO5pdPg9-_G6F)y8d(f4y72<;hDZ~k3010_QvSq?vvetWJzHSk6nd?`g ztRYc!G)f!d3^5+6$#Jp;7GJlPcJK@<(arn4ncvs>MH%9yWu(TD^d zfL_8=-SJ8>g;mOV8|e6f|8q~L8%!au4JB~RLvEa<3g1NFEAD?n6%|OxHa(`^KNNaC z@;{*pPQ$+8L0A1BAOjIBI|x;MG?MU)9O!b>(w*J~Ms!U~X!=E917}O6RV7A$z~S!fN0jB>LhZo6eGPE9ivqlqXB|`%!vPO&6Q*?Kk8f=v9oWRC zc4n?KrUOVakE>IvEH?D&Jm`2 z0RmVd($drV*5uo)k^ls5rw;x0Wca!M^B{lyCyXq_09X3UH6r?NWn3!za`g>ol|#E? ze|sJ-<_s;mwUXn41s)alzkitwyhJGN=kls)w9Sdwj5(UCEnZ44EF>*>SjQrXgPX6v zGu)U{FGAHNU%VVTpP*F35BSd{4L6zchzKDipfU~C@jWkK(K`&fCZ;3*Bngfw z9`0?!g1<9}4$ELx?N}1s{3Gais^jQ-*Ur7ABzjD=^kXX1uH^2^w~&9-Zmd%Nk}Nko zWsfK=+24i}jD(4->xDl|K>Y*0_T5iafGu1^Af&fs(_Ig)FBf4j#)t@fX$X++a9L?; zn}TmlD;`3IFeYh?J?c{!IMiSqWw6fcuNLZb7}dq!-pl=4CePR1zZUQa@J(uwb-tU9 zj6D=yuHQxVCdG`8<6nORjRIbG&6tSDD=;6I$c^UpATm^AYnH6*e z#vj~!=F_sHcuiTTkyX=QAev$p=}+SPPtVvBs#fTDRuskISD{kh7c&1U6fhhp5eBHO z(suBTl54H~_3<}&y0O+!Z749=}ny?qg^Ye{KmnM$}eg2r(`-dRHFGaKiznH`=t zh$PVck@W!}C~F*F7uO2|acjwxx3aV1cCva-sy2%V26^E&H=>kT6?UD+3Hw#EcLQ;r z^mILNK@}Ecqa$}tNY6)8X>{pOraZTx?!xv({u#1jNq}v-m$~o&WPg_=s%l~ z@s})_&l_Bis7(nkd?4HV!0^5n2RxUnL%Iv@)QU`R7DLt8`OuIs^gXS@R8Ai5Lr13! zF+?y38ICbdosU@gy(uqGqBfHHPs@qENRk6}!6BUx>LV4N*j1oa$$j(F_~e~MWa?p- zWYO#7zBEqHnlhy`C+*1Bq+`d)_XLzzI5Amv&r1*wf6J0Tc+i43ygx(lfn|T|%KMW- z7CIuZw~NlmeG^`|?3ZP917!gI$eR_ERA!Qa_GY5ZiM| zNxUTjvzw*x@`Qv>sh4Lf{-@S~0p{4Ak*Ze$wzPsiW1dOBm!wUyzzPCF2XNK|*dfi< z0F&(;H}wq6U*^z_2&l(+v)*D4&B`maD6MnPB!&U3<$tyR5+ZlK!gK(|SM}n#x2ejC+X{lh8t2=EFglW(W>-FaY^U)Uf|k*3Yn~AM{Kch}9mW1; zTZOR{7`tW5PE?D<_k|XEzrAlTw{OZ*Ew;6F29krMB4ZOriZLIXiDrY~Gc^WB`G4*C zk_tu+kOt3^y&< z0xZwjxF?A_jnh79w8VBIle21L_nwn}bTpe8+pHMa6$d0puloUPA7Zi;mS4k@u4L2P z?0kb9kx?BAbUl*mDl}AN_DY@?JNJJ2Eh#Yr4&5zG=}JlT73;8`RpdCsi2tXmhHKTudEovsS{Ty!T*}`&sga8Y?Rm*CdG+ zmkSaE6BZpe(M?avFv8Kkv))N&QH?A z*_3ObgZrOK_4y-sQEa{Ne(!t|{RuA;GyzOEc;31PFESd1zK6pj6?q}pRmphrDFH_K zKGu}XN-(Xg*?Au*Zi#nG+%I{bj=bLey%TV!){gr!A3sR-CrK2H=5hbf0kBV>K^Edr zA4FtWn!<}_|2PKo3+YUA(M^&=SgCD+*{J8kZCnUbioGJEGW2s?RUN`0g+ZhHp<3S? z>|TQ_I<(p+G@D#N_56SaFE~`izS2G4Mt8^bi0awV2H{-8?6z_(}Dq zLAISU(Jr*hL|5XPj`44&sBBuk*`th#_vxo1c{WKSL+ELQ1i@M}CpArU7FWl9%TMRr zES`Hf1w zrr#isj8z9Y?h8a$EQbw@SDcWE!3)sIa4Hl_WKOw^%P$!o4s!HuU*41EIz_1PV>jFMP{KUX)qUe`-zOZ+nnAZwEKbc|?2V8u;) zyzrGIqakC9tdOtm)zfpHq`5i`?Lq^C6HcZ94J|8 z%l_nw?mYI&pI|B!O}CExB! z7VUKJg5OyM3sL0b`Gu#)WU01#PdrPLO18-U99_qwBcKg^qa^_cj%*+Pg=R%zPE#fA z-}FA*@k81KAU=}(rGfKNS_^1u>iOt9G+Z41U!`V20dFz{5YeHt?a*gTN-%tL{{DSp zkYlrSPILC%q8(5`;cBQMLz}X&06DlhQ?ANoI;15RO^bU~wWQf#^F6hEbge7>U9wN1 zBunJWqo&}(92t$Cd3?q6obD^>UipO2&BYK|21?-D9^n-QDw0uk4r^l2S>rM1DfEs9 zSMqXs?7fgndRATkWB#&5D9FSI-SM7GN;(OP)I5hp@LzZ}5fqiZrZt49x3EA1=2N`t zxJc{^_t>Z?rQ-M+Jupg4&Et*zTYI&vN2a*L&f%lEY0{PlrCD)2=Cm7Q znZlsqOx6HVa6(dT@f@gURaVEV>CH7p*Q``mO0tT0c2&iP{o>gqVHOmm#KOK1If))E zW~W07-NYCq$37LK^;auU1(#nvw%QRF)*7qw@(PstcmO0pOZ_a#X*m#6 z;)27m&nq%}|2)g6jv+hZ=4dtfY z5Ois7IG(%M+5ArLHX4Vvxa?AGcqR-?ql6T^fEd1GbWXI(QC%%lZ6A4%FZm9|MK-tu ze8SuY^h-&hJz7SrUMmva@u!%d;!ZB^FT7D1m42Pz8qvb6Un^b8;u@OGRj6dWXmrIr zN7P_-Aj;1~*;+o6qiC?O8=hcf2d=OFFv;wMhP7K;lg-h-uRNWrZe#hjp8U7=ZGpY@ z{}kAw2vL&m^I?g*$@*tNiSr>v*ZY3~#b95AW4vmpU&oWXAi&eYNdwubC#di=*9Z}K z?Q&zXr(s}tLojr_DDf)}&gok_{RUBZ`eq)Lp|XvKwq6u@?9uak1$WbOY*&8CjQP?$ z-W|o}H0%2qRAZBLPNiC^`o*(|jE(6^S;Peo4NGNJ6g4>-NtjW$bz3oV9G|pCj*MTU z&o*tu+LV&P$&=Y2x!^7XnZL*ZhBkqZHYV#6nSuUDXtFZoNDC4qpE;b``H2Vc3rAC1AZ)^SgNlTO!6EE4D zbC4K_qfKEjQzyaeZxy|4S@jlDfy0`i-$E$Aw_z{7crpXPJDr~j87-nc7|bFsp1>3b zI7SG&Z!W8Ax)BWXLaQ1c#Hst(KjuLY~8* z#i<`_=tH+(&+gBd0B6;pm9aR=1`^!ikQOozU*fwj(r^fZTX>{GER+}|OoV6(fK%(L z7L(DrbbdPco>Zk{X?Ib{Mn4o*b)2YHt@kRY;AbSZSqapfF^NkeSd2_eyc-WvWc7v8 zW$~B_Ghib#{C)d3s+NcGGWyz_xtKP|Rs$YzIrCCB>jw3%ko2zwBxPpi^5-2!Be%(t zO6QYIckV@h2Dgp*qB8(FF7mWDn+*la*~RPuB4Pudh&p2$Xsm z8)n49xoMHmiB4V2>M(3+iqTMF?Ft802Cr_pQv`(akGJqPaUxFj<>lpC#c5XdOmnll zQ6mBsmNekx{R3(L!>j;og&_;KKjg>#&s!zoxo~tu$1Q5}9F9DpJXF*V^#`3EB-Yj0 z=s-rV2%pV@r{9u^&`#yH2=8LX`F%+6;2creB7f&$2sUEW2vE?Q+@QX`G|VU<<>`n>U)joI7I zBi0VSzsdU#5GfNB>XZp?E7#{3pQT_b?~E+v@ojr`(?}Lq!Ca0sLAMAnS(^4eA{)a4 z?R$Hvb1a_cTMux168otU`oI(x6E1o@y3mzD3QFTKt~He}jv~!=t83cC?KFNn zeHF8&6bAhmkWwv1aEdsOYzzlKHBnkIY*!-5=G#^>F63UBsEq$8zriV)eyvplnX9uO zlKs)%t4TpQGDx{MDX6yc`3^e-No_d(<8-meb;3_S^)h00R%^nmpa=IN?|;7p4W_h9 z_j=J)LM$P@x?RTLV$Qr*bG2|D3+A_lKw(Xd5u>li_>m6Phl+CK^XKY=dCT%V?X`y2 zu34ygaeAO+7CVMFPcd9ia(pZSLwpQRz=Q?$*Tf3FOwowAzxxSkr`9xj=a?>&S0@ni z@Q#?2+c_S%1Ymlb%37CLG#s=tFbIQ6O9JI7CRgQ`)A#NfXSS2Chj(IA1*8)hbp_Fz zu%N?+DUUH)>>c7xXsdfvs`n)yooj~aT=E;!6qSE)VoYFpekt4vgjOEbIZNwFPwy%a z!-S{E%*tdEYl3wym1(b=S?Q0<6h?dYo1$9g`1o<9hZkWD#^QkJeJ7~dtk-a)EJ378 z#daoCQa_*N+lr0K4PZ|C&gGOO;(1Fz?He=w22d8t6m!xjB4xI@`< zc$cn_@%~h9VgCD`3m-L!5+D^H?#f71=}K{P@-ZA^VuN-r$9N5f{c4dOxcHf{D@|Mg zVG{DjqHin7si5cB#nthlM87YL4}3qn^el~J3BZj<&p|2`hxmy2K15#W`!Nva*Kmse zO2l zBQ<5D%n2aW!b3Y$Or0<%S?fnKjqy#nm}v^JTSPv;Jd~2)H#*A`KiKemr7DC7FaCDx z{Zu=%XLGFGLa2rvjY9In-9^F%a6pIdsw%bL>kU{%uKROUcP^&L(6QCdxtz8mb$tp4 z-&gbw*HqN?+`5mumtPP~YBy?rk+!mg0KQZ?0qw%YPJ;NWa+>e8t&si~MZdFHyH#&N?SB#CJCR$!AwTvu!p}%-fFMsjH zY1U|pk(xy@B$hUF%flHGv(!yrTG#DAy1iKoZwuWQ1bsLk4aC=`+Iyoxojd%Ef82_u z<3=JlpQiR7Ek5))s1tDJGjH`Jb@Xq*ASo+7VpA}q_R%_Qa@cbRbReUWFiSTg`6__s z!d%cd9*-%jr&N%!G-rmFZmj*>87QE=q1?(W>0X|%!jRlsDE<=c(LsMX5J^0kB3k}i7L z_0epM=vo~Mg=tm~2yO{@;GT}gJ7`|*w&B6buHLb@R85)y@Gg~4ii zpuuyq4N}6(T?~Xh+U&$L^tP?JX#FNpS%D*OA?D62jHJe7oKb*r9GhsY`cquujyj2=uOP zODx*C;uEQ}Fe2e5&2?1izxMt)5Lmi3edZsntADb;5SY^M$85U}wUsn2TX*(NP`BO1E5ubv$>-2WT0_S6SfTap)-z9OCl9T$+kwAlF_S+6&vioG2H11ntyv zUK9=!VZP(Y5&0C%Cw$;jsd7@|YC^Kewi@97%z2nLNvKK_72ISS;21yndKg6$Y}cOMT~_ zxLLKGrNTw?66mzi)!)LlsNoYjnV2QOV;=2?vu$WHNnZL|D1eE0rt3o1r*SkK72027 z&I(+!LLaKm2OAsvYRfiirk-AHg<*LVwchFG9dymiv zqAWY!ILD@68}yjC>3M-Fbq>Twhe=_1uOj&L+0n_-exs(>9%-!n38}GicPMJGj>dB_ z+#op7yi(Hxg73%gn}k#V`hcqp|0N6mr#3UP!UG8WL-Diob3(mEg!|#3fa9Qjd+R?P zd_n4(>K9cmW{VKXYV`oIDSMZ&HZg2d#N+-l3)JUcSj9O&Dn^nY^e!!jkD+!6ghT-Z zMha-ls42bCY)+cicClSae}wgDu$1ji1G(q!N?Mgg;SOzGM&e+O2?JWjTFEZ+))J6- z?}w~8QXU`~E)F=L{h?4@?t?~@(@7NwaoPQa*DqYLH0y69&4@#Z*h&MO)CKn3qF#y2 z%y224XvUT1Io({?Oyqa($@_ZoKny7pKO6E4SJK4#7DAlQT~j zYR0?L5@uK}WQFt@8ea^n68FQ2ac;b{b?MPEX_y3DNBBld5xcUduM@rBo3OunDbUnaW3=cyb+N0x z&?O)Hu$IpH`1`N~OSOdg|X*YR6I}WVMTAQ@mk3CHm^IRS&AndUX-3V&@&M z2#5{lWqvTcG5x!_<#O&-MZ?}g6NjY{TaI-~Bu2H(w}77V)LTDG{#V2LHh;NFH!R>^ zxjzWiZE%&c(XU*I#W-5UCrVb@*1w=4P)9D8?MHB?=sT+s+HC?y1mb(mo|x?_1svJF zaN8|V-A-^4zveN+z-86%TUcREV$XPO_k+>?^_guiN9e8@=)?uHDY(35xICFnm=vQ! z^102oxcDnp$!Qc5;%J#u6_K$$Hdb+>^N)UAc`74M2Vprt`HMdkFEQj`am~4U5o=n;69J&P_8p_)Tix z5{23HsO951jwR^Jc}z)AS%jrL1Rb8p2V|%%_N|l);ExB{6s3k`?f8|FCsMV9I{$?5 zd=NFz^2*UGTMBt(`nZC**m%3~v#%@e@(($pE6+G=h$5`3nioUq~7qVsR=;n{`r1L^nvXVX;w(bf*1rfxO9)`=np@@#M); z!KlK$nP#F6bnG*nf?%D*(%GDK)Ms6L#<~(L>9D;@&GnPz>E%n$Pv%PM%<*>!mthuz-%onzOC_NMgcERM##R$qZ^}=hw3+vM`;={A0&K`9~C7 zakKD{C zlL5Pq{OBq#ldOYTt%WgNfAYRrsRY|Dks@Ll5uah(NC&pysExB^5g@IoPm4z?-fqCa z;C!wI)0Uu;w-a@6k$_lVG{;uiO~3lm7b0d*P4o8Sx4@{|4xgPhV2SNBk3q-N|4?U- zZY|-j3?}G`ySr zl$p`F@t6uFfzDr?Z4$&#`a{07hU8s!Ucz~Mssdcr=dw}e4 zxAfkA{lySjzDawBCWbyyv>!_nhzgzVpvqFthjCYu&Z(_}y#Qe0heOw)WvfaEn+@6==VpzR9-smnIlJCsDyFx;aiv$pwW^Hvn+X|G znbwvDsefbDzr*5er5&hjP$}t1Zu^Fp`lu$FXFh_j+wrbiOB|oJP9v17Gte23op!b* zREHAxTPpSy8WHQU|z=w7kgy20?gpMaUPQ&2|d8({D@ft~tJd&&vl_y33k< z@x0)B%%*lJv@B;~y$ocZ*t6QS(>x6p2Q#^+o8Gp&X~XnD8!`bYs~{dQr`#o=!uhY5 ztAA}udl3W){b!|@4Ywt+uVhM<2lyUvuna$DKgj^ILwKAsM6VL!{PJGHZSRkKA0s+W z2j}!S7*vt&M`UX;cz79XJK2My9+2w82=yNm1kn#TIK!`TU1w5y6LSA6X@hpc#8s84 z9*h1L)Fqbm=pJwqo?^3+A)EHMsjC|Y=YuLeI0~!0ypV^)ddMF!nePyBjj{~$eYGxC z8zwZgR?AsTS37=YiFls}zWhlk@g@ZQQ<{HpHI6Qc#G9&-(p6#Wj6 zvCk~@)<`&A?Y~5Jym>tE%IYNgS5B%?UXQjR`#Jgj1NZQkS;ZE$J0%2$#!~O^@VNgh znL@q#nZ9TM&b0{yS^g`R=U#X3_%Gc$+2{&nu%O{RIHWz2LHj9nH#)Q$RTc@rMDjU` zTsPwK%o`6hBbClo@E3aAd;w>>$;QMJ+cWk+ z8XWnCtXu{qVvs2I=LGdfi$_f9;3zcHONGIAN6v0^32%nzaB25knP^kn8HD{t2e zK{(OOA7eW!FBLZ$=k<7OyT*t>3fe-|X8CFU&i?qj9Zv?RtvSQY(Xr?5`mvzhD^UHo zIcl)GLNQ&ZrZBxdVHO{0ijFSOp1&$@p^@CH@J2+;?I1)(MTfJ)Qj1e^Gf(@dEK|~` z=e%s(B+@3K+FC6TWVUlyzuv|ZAU_d7ga0cL+}+)!+E`w=RwYhykMbe~5rk!yvbn~5 z;!^%@j{4z`4?vq=b-t?iP8j%Pud}rObZzf$c})<_ejim)5_6^Rh0UP}IDDd|zB_WH zvbR}NS5SV<_#KnkJ;a_h+qVe51N_N7$uIJyNHSTO36K4mQ1+SZfoyQ9)!Nl|6(yk} zhLtZP^QNX9f@k|uX+5RgX%M}i_gF)%J=@j8e_wnfqMs`ZW#|4Q;x~=pdVsG)!s}Q9|W#16gRgd&u z@aW=D&$Guv;Y?*&T9_Z zbI>D;z1$1S$^14sQ2t89P)}4p=xt6c#l7brvg6|qsC#8x@viAz*Dyjhc852h-kelU zc+QX)3-mKsLr946Z?tir?9z4jH`MfQffF4?__s~IHXIghj~1>q>{BtoHNDbtUBSD+ zp7~IuVM&i?3kbpaCf+3c%i%ljw||9h?mC!cB`qybJ!E1~d(D^~l2rWgNVHzfS)e>D zvq@pqF#Naj_ zqTg((pXD-k&B`K{@+=;m-(S#&-?{VZm$7l0kImbPvI)*!zEB|WL$cQSEDlCji|0ZJ zxaKCigesfieIdIXzrjFwPfrie#+;YiD(p9l0gbHct&n$DQKX__2QJo3 zmcln`371*ieEZWf-zIiH7|8wfk^W?cB%BegDKNKD8b*>lKPo)JORmTu@ zG}sGpmtUEyA8^EQNzN8O|Mo#A^`L&xrB89Per&v$mlj z{^jnHo@GjdP)nVT*nRqrLXS`LMuOCB#mJO;=5)n%0INWC@Z88su6FhvG|~X?1}xXP z=ZjZlrMBO~=JX`*YwzJw+fcY9D!k@EUO!>;lVtaC)8RBY*!HFV%UolrSg);3UHA1s zbOy7zbJ1P^Txg7MZNgs$nW4sLcxxf$u!cz z@O-mxAdvF7vv=-L$SF%J_f0e`IPy$vQ+i=n2umcg>RHub5eVbAzyH%!Uf_XM7!}N&uNrCTqD^`bR ztc!Zto?1v%>HX@9%;?J(rrzIstxv_-H81+BSY^sP`JyoIi;M-qgU^pNUVPe$*ypgvpARScv{)!hTxKQbJLx$vf2S@n zinYjn|B#DPoMMCW`9+~FJp9=GKT% z&G-$9rnY2kDq=Cj?7TJhC>(kc~v zFh98Ly`kA@$amPPs?^wBr+h#Q7C|)gpL~&^llaCghy=)iqmn6r&f~0DT zm}Q69%amv{#g;E55`Ue+Ir*Tu6|tBSt^diQ>kEGGL)p;lQmQRoPjJs{^gR;3!Qz* zp_#Y08BSF>2HaZu!~EG_;C=OX&e7D3qnc(naEcr1?|(LU({5c=c<-9}pu*BM*IvQw z3Y4gYm*ze_y1}Rsp-a^Y-kmiQYy^{IyFUiE2Dqu$(NdPKmZx^KM2AJ%*RGMqVw`b3pyiKye%=Pk;SdgZP~HhB8O z4MN%-VMI7eRr{<92%gU}Ff;q*5G}(Sv=3jIUb9>Dm!V|b8F?_kvR19~e0V}!tL2%j zezDKd{9oxr`6#V3@>R|Ym4TOqI{h={BvHO1>sbc^+r*4FBT@Io<9j-0E@hJCP8TQ7 zdYrE?oV{)vloApaxnZ)URfBxeE(^1bo%d8iA86=*Qu7|QEo#5v`lH%m{w1zXqX&xs zGBe!ydse1CpF&&Rf#AEG_C%*vgN)-h-kTiq!;l`Gq}k+U z?Z(TPPl+3eL#}HXxE1XAtQgbuZ8N!Tq6R#9LTVoc<<>KAzRuwkI5ZbDuNjt#W4VIu zqE+(U72RENT@Mip9mw&EA|jeUHh9{XD3qgl(X)xo)*EiCs>tuPkZwEGwk;)9J8&OY ztL@3^7EBde*N#tIWA)42eVkX zwHS$qa4#-;c=2ylAd#r`2b&7tezqKg#>1L@Rw4X|O-5kB1oc zUV6N{nO&A9^=R^l`}P#S2FtaC6j*`u95FNyT@M&To#Wv#w9{_mM6>HLD@Gq#41T$S zZ+4!zD_oL8^jMR1Y-7SQxoK$kH3v~u*GnRg1a}2-Z7p3^SMdXtN%)~8>LSKL!^p^) z!aJSl0;a__2c|d2elA?N36Jq{BJR@rRNN{!mb~gdwzsEZ!yXqytuR{U5@oc%`BKO# zQ)_i^&G(GxSt_s3k`4#s`xitxFcm@vj@`rECg}wDKdm>S%qa6_u-h5|@Hs-nT-=xa zY84q(MRDKd1E#gEh2*AhJqsJDpXMUciFy;9ut#fe;Cf29`qTu7*rnFK8CGN$s)fiQ zjX+?|C1+>@Av3_RI(TJaA11EiyRyC5sqMY8#8QEadSx0W9c*5G_=|s+`=jd2@vrR5 z%RK10-IX6XiJuBa!w^wb8dh^eh{OG*j_E?%8RQ*D>sxGYVL~ZAvZ#PwgC1X8aDc_# z!DF8zJ$>Jm8rBWusO(^Xhf+b|R9azJlsA@Dr%oqLlH<^Exu-;ol(v^QaKvLs_BvKa z88%*St6^=1yrbpKhKM!lq;HG0^`$2~)-^eQb(4=4K3JxMJLxHb9_aa=buQfF{C(OZ z-s3^Tjnn`i*&F35i4jkvyzMXfI{`CTppujl?cJN?eMOR^BRcx!sgnYkHvv^380TJp zjv%p%09F>4EHD-FvF<@P;m0)!DAy87qHIllZxe?fR~S|(qLUmpQb;rOg15Tt8J~9o2c7z(Oso8ZnAglksk&7D1o;tDiOFyUb?Y^V%((3HMQ3bjRcT zh1&M{w9STnO*At_8%*yJ<5wmV)+vqKn(>I6B^Jd$HOrg?8kemL_k1|?4Op2FnoLz^ z4Se!MjG{i6H>PT-vG%KSmX#@m^c^}r?6nQ- zndPJvy-y@IbEhi4C^QAub!!d}()U{CGXd~>lIxVv-%@9D2oRYJ31qFyK`GHkV+W

@ExF*od*oDpsKYlU#SKRD^`UggwtwhXBlW$sP!{lxI8 zNLR7DK03RXA{!d8(ebCWdm-zW0uww-fzf2>vIEC_{t_LjB`LQq1tl8*)$pqBk>Wi_sONL!<%E7DbdOE4Z45N4hCjd zq&^f3g*HzJZZaaH=6ecTKV7Kt01TX6?*eA_*!x%dKug5A?WNoMi=9$%Oj9JSaB);? zt4!S^Ww+!M*ym?XC*BJ95F>*^r*VA5ZA(D-s&OC=GqRR|y1yN!ry#Z>driL0FeVNb zb(AN_D+7MQL;SkFng?t^4J^r>5gKeq8i~(QhhyKn$M7D(Ct4(@w_QXH`*E)F#C$EM5ZkfLk1XGWcmE zNAeobx0rbe#da!m(J8R< zdhNQxEd|wN0_Ft#6Wo#CpR0x-oBgO-g@PaT-8#6X(XuOR>*K$ zOyG3ZSbZ7F>TnwvnkMsq0e)d9u!F8$Edo4LiHT|E19Z$gL&JG$?8nZ4sa?Ot@CXtE zEAkgtu{`zK9=AB)cT3zKbc%+hz?_u9yQzOqCZZHD=F6q@CI1uI)Ix)QJuFI3$aMN4 z(FGZrL~JMR6|94kJB=Vv^DuV8!24i)byi@yGAo^ypMZxFaS};MoyZ;?KA7fPKRF6O z2)_=T6Fr=>Zsbg)z1B{^dFezuPIUsBr21_zNK|>MHz}52bL({_+=q z)C_;N%M`@@@Y1_P@hLFsU+#Co8X_Q*W)U#^$%29o8MSrg&R?ez`FmnvV4-g6PY(Ak zHB=^)JHWhu;$Y?@$3=cG-%p|dTAfbO(Geb^$F@G|7Wh{h|Eu-ik^+(7rXZf4K z?-(dlVL(pzckyul^C(<`o5DykJRIYb0BiG=()sh}d%1^UXLKdkKysu%bF{L+hTa2M_^zhcx=IpA?G-DHf z8mvN*_ZQqjCx@k6K8M4y7+Ej)aUpJVEbHCw?_2$zH(HA1iKQ#2_we}@R}S|dRL(?2 zTEg<9cg1myLCQ+KApb$!l(Ay`)>6h~SYpr6_8XZ{>P)X(kS<$mDG_g4V`?$(DTqQ` zRLVe6*K=06MA}qrz1TVUoh!=^P4-Z!+LpFT2#6HQ$dvN+XlZSI*Er&4Q*oyG>P5yG zPRm?NW63Agt-H%Cg1ZTimXHd#kY&g0ReOuETOX6-X}h^y0BcoeV^63z z2fWBABIhK?*q^X|=Mmtyw=)aUBpG9t{6Mv(Fw^Ae=}*w_L|5gcRW0@Dp0qiQr9Af~ zEOTarq*D&i?%!A>@e7z z54h4;7mTiA>ktuWGpB0ncqMRs{0DOjIagGTo>wTh7}^&tT#Sc*F(d_c*6l8&Yb172 zema40rfc7fM^+RwP1a4j?$rcMl^{5&sHpM_%s&36jo&Dcs(vkdu&rr(Zpq_dM0DWH z&wrDZZcM`87#Bk$ApIO=+$nK)B;Tb@#&z(o>^YLq+v2zV` zZl5V$78BnHA8ypTC}MEF?^`+nA}E|gpmJ(+wc)b?MbplBx=Hr1OLY5ixm?RZuS~6V zbAWVOpsGiUP1l14F{hHnAL%**nx7jTYmT*-%j_$y`U|8T*4KRb`Vx`dRvzUt$a2HadX;%t0 zL;_P_tQvEqCl>GJS({6Uwq55>A_9keuZtXmiyeAz9~zfjPeeH`o*ER$_zEaOqP4kd zqG|~cw^I%d6Eu$5;B^Q$cB> zad6>hX@M=^&TzbW5D0r&tg`J1Tx=&Y`7cjWQu3 zcG6*xO_BqcSkI2QNhYL;!s;uCpJUEvN-kicQS5KGO?_EFI5Jfd)Y3C5j0hn!v#s7^ zpBtig!sw#=B6j}-e>NvM_Drv9SXTA~i`(JUqR&&JGcWHGNuqwVnfw2d5)u}eV;3Kk5nxm^)Pp`{rbtlkJkQD8=@_ISXVrOSwO5< zN3$vQB<~diqI4FcZ=oaBx_Fu`J9Xox?y60)y%nE1ly6c{_8=)A4v5 zQ8H4&FVMI*L%Oi*goDYw2WwvYs%QeNT-vq{ra8ui)-~dua8anh!Msv@hOyqu@vOsp zFq^AYpEddtE98ADtgWMS#I)G8nT0opq~RrP;Xh);w2Mk*E6;mdxpZH9gzA{S{7eflIz zRyo-6J{6e{vmQ7#eLAY{8$E7|o7Em=+ZzffTQ@1(OPa%C3Udu)o12>OEVM=?=k_iY ztaVHmm`)>_Tdh9qS(_I1t;CLrISHr?#t+V}c(bFGeLDpbHa4W3KRfIN>>gSdccROQ zhhvC#=aThivSwG~*KE7@OzI1`(&BD@H3o7QFVYM0IWOk07C(wiQ{bTS+EmCan~#!TElyP8!I4iU%&-ybR1 z$TrqzJ+otnCR0o4^BjW|H(H|`nMI!H-rzoB?%$56!Ft*jN#^Ydy(m7k?5JWmPWtsW zLHy}^r}x1dl!0F0yiWXW%o9Lbv zErs;fhk{2%k<;vC4?Xs$`=*k^#q$QN-z^orXp+)Q^Vx7ik1w{zRZr(KBX<}=enY}gM-&o+H6Ii$7-XetJqt3T_ds2p!!f!-)qA{d#`aqsBhzC9Yq4uxnqy#&P%*xt#S9} z=obXy5>xpv#YlwST4RzE=qybpXrhm4xz z(p((yDA{MB+~C0?_6`N0JV_SjxIm+B3;e{Hk3S%L%?s0m|{~-fEs-d;q*$#*~uz zQ4k{Qz%lNwZ4blKxn)}C;Ge3`iNqiJ%yDB7%EOxGtGB2L?i4quht`LTjXnCh5ta9IUurgC!NYv7giP2D%u@$0B z(<|>UD-ZTp zXrxV)r!6%XEfxXuz;Mv=%wbma9lAwJ;`Audk8EOZjr#6}cFLfYGm=N;P@jvunv>I{ z*gY1?9bKvRzvCEX(oa6PYOpacCG;X=*6w@f;Zy-PW4bxnAP4_NHOcI#yb!YZjhzjG zCw1{u7rIt9Um`HsF0?KmIdOx%IZJ1F+_zF$LLwsd59QFN#lXE{`{=wiP0@s(o%|G| z)ElO6{}BDe#Kyx}unMlhE{P&-r=+XNA)EDy=T#|ibO?zmBF|ePqaDG<6YYe0|Fl`Z zOph)c-IYr^*k^1IJiNY8uIOsCnf+}|!N|FS3=xDnBc-fmh~h=L93R$nOwH}zC<)`w z`DK%v%M-TA>P7TY2W3w>nQq`Euiv6$CZACT|T_$))=3EUeZf^&f}zOQJrlpDg`!H zlF2jOeE-}+^(U!JZJQ;6)|{Sn*WdCACDTCQkQVi$s`?^=Nk(SD4hZl#1I@o=St5kM zS2=~wPdoM~LR%mCwpx#+3=m(BZLDM<0bvW9?lU7WT}|ngdwY zkb1>ldqf^*v=Qz$nwOjQPN_1gGE<8{Xl}8tfk4UGhCt}D^>#x0=AmitkL*GM{B40nko21#bIkt@EEtJ*z+yU5Zv!pGcMa4z)sSpZY+ z&0-&Uk(mo6h<*a2pu*Lt@Qqvv53;*@CDp;SH|uSyM&fd*u!xb|OsiX*Z}J-Sxjd$m zxhX>L-iLXRu~ji(t-~Ki2p;dtXN4r1b9!>a8fccWSCj#SJi9E{T~+t+I&v$fR@&6l*OC6x{jkJgIJ zq|s^FCGpF&EFdtV;u*>f)pXdjpvWUJ$Xs-7I^!_+{&LsNZ22Fe*Lt?68$9Zh_hyI< zJhhVC?czFZ26VV&XlHl>;i$pl%c3KuZShp2v-`PGXEtFv$@U1_*EHz+4ztLQ31#Q| zI7d=o_MJkLo|$PMblZewpCdKrHsX?bt*NETz>I2 zlr69fu~%)ckUAuuGXv4AgNrvM?TJ}k!>PQ2-7{)dh}{VHrMlx9UM{hw!>4lnis&C# z6YnO-CBoDx)90V1z%(8XVL`D0_1vlSpHRe4!U4*-^mWfwDa8p$Iz4Uy$t~pJrR5oJ zG#+@XCzgO>9Q1FK{BgTw$nA7g6@#DVi*GwHE`GP}yPg(EI6fH9MfR;G>HC?)i?JCn zasmVQTL(wxfw1wV)=Bfhcu9`o<)YUeQIFb1dwxz#Om^_Vx;yJqYs5i`anUGW1oNmv zPh-bCZ5R2ja)1guFoJWv%JP-hZfACbg4Dq0A&{~X8!@V>>LGv|g(_|jIaLst9-1%4 zpGgd=;f%1?8Zl1rJZPKM9J_Tl!btbUYUWmzbC8EED$1wvh6RoV!w@s!*_!5r6(0@% zR1TS1i}r2tmT#FYA)1}}>f;5gf59`vCPcTJ2X7#IJ|-2xoV(a~qVl<-zO6ZQp2=&` z_{(DD+!m_a+wMp98v;aVrME`nzK>eiHmge2B~8ORsVGU-5;u^}7V+Mvy`>da)&5w! zjm99BAn%g+=6%K~*X1aRX8Y_UV|^a|z){k^A#V7VAkwyJ)13IqCB)q9&y<<>`wfbX z`H523g?~M_zoNRrRF#%Qv>&Tuw={7HA?mR2y|&3>Aoma|>AJ^^;lt6Ql|Edivk?N*)G7iQIW|sx>|9@W+0cAQY zrb`aTaWGaau7~IF5oOiS9zuKa3vD1ft;Tm(U-%6Mz~c@_yA9s$@E%!Peb^2%18Pn`KsnM`k$*C@o`t>K0e|0fXe~alQG|z zZ>`l(Jq;En1bLqcY@8RVJU|@5+T{GkKEFe9zlE$sm(ZK zjlovQ8;9>|haW6AmmXXzQYfkdI_umqJUw&U_3?15BH}A-|I|E~Ph`L&sRE*+aX;1NH6$n;ILp^}T_1k1aCH(_Sxp^c#Eo z1q#vVm71M~$5ry%pFH{%$Zp14(wN<|VfT>EPRFIWG4y3XrN~9xXEX6(vPjQkHDxqq z_BnBnAg;rzl#(RRC3?n&QS%SqOcQ?WF~xlhuXaArOrH*SuW7ChK9JsgRKcokCmyck zCJ}w~>E@0`(@}NsBA3t|@-}i6Rm z1Vtk>^B9%IU|ZsgDMFGRLYD%SZ&G7(ZOJ4KUyva*i!phli=gYLX7IHeV%Pqv> zQL-yZPmA2grdcvfeAx01bukKkH!<*-k^E%0*Bi?f;h%0SfI8ZzSAq*Z0r2)*-1L(? zw_Kv1Rsg(33l;fmTijM6l-51WY56;|v-!q9SzVG-e9Z*nfhrs4J?it2sqcMqLt@S?b?7{?&7u|d;K$^x_S6lC_vDyTRr>BkyUqN8hOvj$DtkAY!**6VPr7_RM~ zG<)4ceI$9a__79=xJ8!$n`K3i1GHy2GaoPIUzl|sB@9=HryaQG7Vh5J4vK85M-b=mMp{8k4fT`vcRNfc!niOwfHUDb)nn6q}+3FUOEawf;4-t>PtFgz5 zwU0X$(92{r#oU88)hp2ANB6UG@3havQO^xkBL=P@JkyT$Ot z75VgvW-c+--KDhsvL|ArUQoiQbUg}W&lehyCxQ7>3Q%gzLp;Lh3j4(E1(EYP%rqZP zA*B$$sj`a4mc$IPl@Hk_D-J{X87$V2QcV}M-8Ah(Yyq-NXTBL2WGxws=eo!q)_B_oQDUv7B^v_Jad{*&SZ@!gOEL?h7;WiiBBco+bBj(ZT z>IR1+$u1rBYtzAno2pYquiM`|dY)jqxWLu<%zMMzlq9dCZoj2z&{8X_d$w%}l%t2r z<6^+;STOO_gJQUwmqfKY-V94}I8s98g~A?@YW2F3wJ!DbqRV@gR;_q*J=rDBoL-Hg zk|=%y)_1<5zY@5kZnIWWFDZDAJF+0jI4AOr{;9f-?vVNGAyG&Xbf%0G5%aHj#7zSZltfdg*_(-Y#3fwqm2kEad6l~I-FUElcedV+_FN=Dv>k)|iJs$Y?jk8H zfeeMdr+ZVb%HC&TbR{R3=w6Y%3Sb;s{5ZE#=ukR+WnK1SWcmdf+eKX8ScjGeWW9UL z`6M+OR71}9_ixl}`a?GL=j7hY61kP?ee9eHn$me#Fy`8L z`s%FgHEfq;;U%Vp%;WyRsn*ol9(NfYl?*ndNxNk5ppRPKn)kuX{@Rk+F`qKOWfBW% zw)m|!9c#js*zE-^{tKAu!;PA?@1>rh76(g5!p0y|iK}vZ-a7!U_DFC|=AViKiJZS* zOaMHd-!__G{6MC9&nM}FzRpt%rE2H*Q}?W&(iWan<4*U(>%g#Wm686(0vvF+pQuq@ zIZFiUc+)9Q?_7oAwcoIfMtq=5ne+xAH!|YAd5LVRIXYP1TOp}*`!WQc$dIkecUY(& z^m)^;^&KCP$VFx{Z6D-Fo=5i4rfTs*;1AvY4`M5Q20JB{dsxDT>0o_#)~A%rQIaT6 z*NgNwsL;RyQ;FeDc7C(FkUiz z3;3%fUi|~d8^hrt$-H}C&T)yS!p0FMdY0z11R*&5tR+XR7YCQ@ES{gcnU0e>P+9a1y-;_N>ckjQ~50ct9~i}9>J z#JWrb4+VG>9Tbpv3fjkP;|$SY_Z-Ksl)3-J$f$8vk1Mz@r?O-sCS}TY_SmLAl7@$s zKL%Jo0KDC+6ko6g8R7^F+bX)y!h-vVjB~ditt2t9#c=Yrvv`8G`Cxq(Urt@!6@-?7 z*BzZCpC@q|(J_sfwBAKmv9~P8`q#qBC&%&!>I=+pDTKMFKi#Z=nhse zkq%n){UPblgX+_%?!>`dFFO?5g`cO@DQSy&$$R}RvtZ64$DuL-;(Tx{=QUn+BBAnR z6N*aGN>*Jd|NOCg9^!9OdY`|&1jVdWdP$C7gB%VlSc08y9IPuE;1MHwu-GP!`KlU_ zLXtslF0-sq&`!N}9xO0{KFzU<74CN3w>^b zHCsb$Yb#V-J@{wzJi162pSaY2`BVU+>QLD#{&f!bqc_q5t@zfiFbrGJ)hO!j$|;L<7Bn<*2@Gyg2-o~HE`+8=lLtI~QC8IMe9!b-j0EtVN9 z?QFhmZHRpv8*4GbdZThHUu1vm?xb67M7POmkQr6+SLubR$P%##?T#ZhW2K5+^XyZ{zzO7Q|Z--m&P2 zYKeYLr0%`Z;3@OMq(+J;VtmpDxn7Mw$KDV~##-sJ&eTUOR_5;RFwPgFFj`OaP6om0 z(Jh%idn1+;)cKkPh)&_`Rcj1_i_g+;vXtc5+k)t1t)nsNO+Ny2t4i;v#B!=OorrHM za?=D9AiiY@WeDqq-#FSwEn*!hsCaj`A}srr?rY(_w;Q(RS!pyd{@cwZD(e%WbRNe8 z@$Hx9Mk=ux_d{?}T>Mp(zR`!`n9+QtRVHU2l!t*B{iydZ%rBqBoein3{CXn#?f1CT znI;ZP{V~lA;Ef{YmW(XPp1B~?a@3WpRBhh^YPd{_p;ekbmW3~W#mT8Ey4oE^uo*9X z>yde}+~8>wrL0$hl+e%oCAz-VQW&zGK{P+dA%e{8Xxm*jE1PYw_t2^eUY}|;oRv=w z+@od-m|HQkUR}yQ=G*I|uh}cE$fsgk_f298#cpr<(P!sGH4?q!6n5sb8C9*+SKxoA z&`2xX)0ePraTQn@8L#W1P_X!-VCcv^P62HX3a65uH^KkpTC4!nq<1Uy zt#j~63Y+c=Iwgf}>V4o3C{Z}`b@-n+4eusYZ@DZwz%b?w)Mf30W#iY7&+XVgT7EaYHMCXZ%hWZ3FNrd{ z;c;K={ibq8POo5Jo{DYR+{obl_Jxn%lV3+aOUPRp!xDBxpP5ax(d(^y3zfGozwcy; zFR?PjNroMj4V7j~FTkL%n0lPlo1tG41sw6F(ma_Kax&^Joxv^PCT5I~>Pv!GbEthA z;g&E~r?hj5Zvx=NA&JJ%+~e-G@SXJ+@+$JtdxI-9?gB&c7oXn8ujrBi^Mie$)I*rO%-6j#RrQ z8;sU8r_(9zBI! zo0$#k@Lbg&(tY~Oj~bNkCQ;o+W^z>?9ptI+w6@S2HYE({f*u!rQ@8by)Y)cvGh&{- zmmAW!yp4K-3ioh>L^rp8F_V4nHtdiJ6Tbg^_^ychqxMWFcM?ZfJ9%nGNg-d~%{w+E z>CUiS#o^w%ad0upig!x#ADQR`g^XA2x2arrd60a0A5{qm&EP)iGU1m$!^ve^g+Xx6 zoc;$f2h65D2-sf5hdsyJ0nnppu5Yb3!K3_1fH=|9(Co<7E=i(7qpq1`=wHrF`-~g_ z6(^0nnMHNm^Q8FjIb~9dmF<-1t?pFzDv!>N(W4aNP$l$gVV}WX6A7j(lIYT~%U7j_ zU4T+>%C14qg38vT0PBUEciaJ{8W*<_nLNE5!H^6pAa#eVsq z$_)Z3kg(xBa8M8T-8V^URnPNn+Z_#umCA@q1joCKdJ;1l7s`ImHJzzvYhjzp3m1=p zvfe?(Ge|%69QWq;z9o7tJTL*M*s+O~q|82sTI?A?$G&7Q~$C0ew{Eq57iA+rJvoH9BXcz4e2STr^jP{e4BHT(rn9 zSqrW1tdVvoj_oH)jRX#;p}%EoEj)Eh0>FdWo@bX&tU!hghzU?kEqzUT#MQ=h!=m$N zn{9hU_?~S1lY${B0gg%O+*2E1*WLgy?yfg!7Z4RZT-<*|0WU)Xe5K$%M{}kR%J_QY z$DS0u3c;=Ko6W~AL-s*L{lch{6z-yvd7g#MBa6OIRZHt!dNq07FL=TryG5geia1WV4+6#NTXjMQJ@_BwcGAUXmq;x}bT}gQ)OTzKLCKw0QWAe5%t^bj~z zc_rWD!ogurqH~cW7Pybt8EwE;KVGl{2&NN-eD(qAxfQ?u;M6BtgqD$guj2$OD3=We zahhMZ`rBWUs5Jnd*kWFZZ6JK>vga`X5kWDo}_M9ME#@M^|_nWw=W5p&d zgs->o>H>sf%h#_)Kf$9V62Kr6&(dQTz~1sZu?y_Pe~CSb@!gn^n4OLuhwb{~yR5wS ztPN| z61Dti0km34lOADyS@FV1<7 zrAX2@($M|;8XwSXV~Y0ETJ{*(85nj=fhiUjdJ<@#00DjO&|@YjbC))8X>I!W?AoIz zsoXtUPhTwt?CvJ|PgcJA|Kwmje#BdwR7xB&ZeO(*d=(|r~S1Tqu9l#qF z4e*LV39~2EJ0&56n_bL12l@p+i0%UxByn(%{=vDD*rhFSj}ZPCvst-?qt>{A6&q47NE<)wEo}vwLm+U)|CqYp9g7?IyAT6%{Dab zW4$TSf$~(;|FA+7tbg8LzRm(jpTwq43;@x<>}UR^%mN`#=diQ=cmlvnj9$0=f(NbH z7)(;3_|{<=1o_ut{VH(cE|9!X0aD^+Q*>e&?2>``+wKXu{HL;PbJA%r4VeGmG-*@d zJ?5naFnS0WjaLc=jNV5yynr7109p)a767kx#X49{Q}UlKw|G*|qw-F|+0kixtr2c0G#Kp)rEbI^VA z{>HzgI7nVVm0NcB&}+YAFrQuYOe<7|g^X4$`mD`Ggf)O~R_{D1zH2%15VX>B2_wZp za^e7ff97vR4g^o2o*HL>xa$`T#&Acze+V@to{-B9G#YU16HK+7q#Lz^c>n+Kmwwkk zb|?N`^LNsYPYPJz2MR8w6;7{$B)N>tbLS-5zn@A%kNjG3YBBx`N1$w62Ba9?d;k&J zBBt*v0{V@`=UdtT;cx=f(?jL_(ei2`0U;B~drMJeG+x_t2`OluUE`24jwu1gj(lJ% z9H@pS#K*_C22xX*8OX;z$@_Wl@<*RM+z5CR^X*MGP069W+UGf{Pr_bH{kpd0$8 z&DNk9pdkUs9ePOqn|X3jztH~9|JPT)tMFP{IaM*qM25m%lkFQF|i6c@+QA_$y|CAGBynwl4 zYy#a7659*%aT>px&7VvT8mSFp)4Bf)RX<_Votvjk7m{C}+=6aW0ykL&%y5U1uHIKw zOeX^QFst0$w-D@7Dok|9zWw3}(DJX7Zcc?55s zmH)gGpJw%enxSHeXzo5HARzc)^d-3%rsL2%Yro z7rhe#F9Uyk`iPq6+#l|9fJrUpUH$L5A4E?-2DN4pv0jjZ3X@iV^CqNs!|fBG`lME} z&r1kW1gcF8jYMLd|9jMamD2m+b8PQJTq5e$SMcZyz|;8m8zAs|o%@<}jE}g^K={?B z7r*$+Ff_kOkOA$^Lbqv8oHYRulSNAr)_**;pTLL`gI%itjRbV~4;}_c`+*aWF5(09 zIxyT(e_dCCtXT}S?OKdex_pM*&Xdxo_;P}k8;yO}r!ENXq_pqSRVhu_Y?ToEk(Bmt z;fDb-+MsAAPZFvxG=ZdicsdmRXG8fr1bJ#Zduq_#=flf3*>8>UH^fug1DMdObD_+? zRR!Gi1?urq7Ko!CBZZXe_T6sC&kg`AKX3SPf$a<$Af-C1ckGJ2RCmG-?wKWN*5@@2 z-`c13@N;ej{sc5BAIO<3dBtl0WTLhPh{J)7rzzCnbu!~0kO3332R(J5E~#KR{!q~! zs6P&dJ@khwNQc?T5dIb=@ZbsH(2${NmPl5Efg<^0W3?fY*MTtu_g9ok`pjv7E%5@FToNLp6Bda;CZhhU3F7y<{p!K``n5;` z;NL*k$-nQ9g#~Z5gFgg<5TCuPC3; z-VG8}S;Cg0Kcb3zBC5AQDBIt-5DuN|Bbyk@^xK5|2~b@^8A$M+iR7N-EzkYVM?z-E z4ceTyEn6=#5<)uDy<2b6Q$Xf0j)`fx%tA}*UXXBN86|3i!4kY4J7qyP?LhNTA?zsn zy@~tZ1$qAh;>7cR%)d`v_uO@X+EV} zwZ^-smXB&$E%c4omlMx&lb+K13Y5L2qiZ{P_DS%_m-#g1k1Rm(aKG05|LD#FMiQ|r z5Cj8sL{RDVrMN!?_5a-yr~<6Rnv;7h)NTmaeP8u8|A}+!X8>lqo7vTR(hmb|4{|jp z+XJK!|NbBq@Qc5+{CET&B9o*qU5^yIW^Ncu4#xfN2L$?pUFnnsm`@TXSLA6-<(ERf z0>%ZhISL56rj!%=CsaLw_-sW1!v7DU>iM(&5hH{I1oy)PCFpbkO-u0_!9x7D|4(~Y z9u8$2^&{RySqgbe(Pqh7BFZu5{&oFvYU0>JtdH;AX%{+52XZ@XXpZh$BM@~u45|4qYUx*IzRfskstkOq$ z84EHkz8>)RyQ{24LyU!!>&0MEbj}wyx+6L8hsLWw4&o<2+dU-wK!@`J-Xz1k+Wfej zCaZ_IciIzX+PjDqd31iM5j@C?)PO^bfLEahll7?QVhVCLwnJ*q4erB-6)tHV+c^*d zc{{;#FIR-FCk?(Av8Q|Vxj2Tzxl9SZ{-z1V7F0;U`&Lop@vi(Er)EZ6K09#x23A5P zIEKC*K9_u7`WzD#R~GL|Zr&Q*KJS3YlmSDGxp5LtOPLcs;By@e1DlR;x7n!+fB|os@rehrfR?Q%i*U-T26bx9UG~$)_ZURa(n|)R9cTd*(1g8Zf+ltxj3us z2fl+3TuCzSIJS&4u31eoOx)Y1ojeLk`v$oN$+vIbM`;>3b&ucwb^%khnD_aRU7eN1 zJfDsgmH1>!O6IjkA2aEQELxT0M>7z#c@GD;h%y{O?2Sgi-r<0*58&f&!(Qr$_59p| zJdJ5|w8TvylM&7cHgbmjyx%Ud4lyN*kekiwitiFN2~eQ{w~tIdpQ=9Y-7fG(^JM9f z$GWQF7BB1v>^^H#BB)zR%49zUAWIn!2t(g(Nr?gb1Y($nc&22=j%8sSJ@eP9W=~<8 zIqHoV5LPYW+-d_zK*qy>dyCv(Bip%+xN=6#uEWPjAx5F(GN{-*UnINb$mjQpj~a<< zm}Ohkz(ZM0mN6bkUXd=7#vf`GN2CJ397v-cx4)>T*OpNe-fW&sET(;p_*^DG)ZCLgw1${f+bwp6i#_x2qWqwPa%L5jZwQWZ}!aVfC=7W2x>qznbxu6{RxBwb5HT>Iks zd{Q)vfS>8y%U`^&lw^jnN4>qiUV_dExfR8KVvj#87Kao@^G-YB3}!5Id*z-O(gx(g zhg3cdkwB~%;TBNKN!RY?u*1Fqmt1_EKPT@{|53mSp&@H;=DIA4t~0l?^$plOkQ%N9 zO#F!X9C9N&2CiY3&lq6cJP@-={DNil$oBAIusx-;XR6=Hx)BjWx@Q831KF`c$k&9s z%oz-ofc0<}e?RQ~C8y01SLj}#&%=NAY_RXl@Kir!yrI&*Roh_;*5*%Nz=w#VItf%T z5y&G2#%^<9(u8ZuXV?Z@4TL%HwMh4u4ZqEg;o!awA8F>M(>N=&@Pn$MpEO0=GYbYK z2_XeKuw&;gF!R4Me{Sm>$0K*i`Y%C*#w09u$Jg_Y2vPhWZa?$-`yb`-&@yBOLslH0 zl-XA<^PstO3y`$YjZ_(goyEY;Bz?m@+rc?Uc3C_`Y#+=<4mEJRv+-pqrGTE-opBtX z5VF_`wy>P6b9sShwepW7+Fm$9{{dpT^FqMVgsaJQ^{p~EtxZ`_3~d zMq%xHFCBD?IWHfwisB>P4N@*2{$%UqT#H9xr}ip;GI2#rLzqsr_*8icdAr$mZJi{YwM{qR+mCpv@L&c4+%! zE)hherXeDwc7tUFBb}?as#j33@0{=#egngW0Yn_`w9w>>>nOF<^XV;*oVsidNkvKj zThDNON}R)V`7E}NEnj#6X~e}y<(QY`sfP6X%|YMY2f!etrW2>+%b}@vWkx}az#GL$ z_(> zhS_Y0;x^3>QIGg={C%z`a^T8a<^ZI*D>i>7A|bmFdo1zqblC;0eTq_W6e1{`PZD#S zQ8>;{?R05NmfmD!d4zDIi)C

VVdH)`LJRb>t$T0LKl!>| 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 z zFwziyxZG?N{s4gfQ_!tMA4X-z1LfO_zE zvpg}cLXpiV#dF!I`vPBYebK=!MY4@&b(MDonjUpC`&x+f-y4`M35+EcY4diB)RU`L zTW=pT3{Dnu!@gclZ;=UB`B5B5fPz+t173OZE0g9#Hif0M2=N53hr8$Q5lQ7lJLaFa zSfcqrGHb-}L*WQb=)Lt|>XNZsB5KS0BrCujmr~-gs67t^9bb>*Jt#WtZ_Aja`0ikL z2OK-yn_@pKxMBL-69pGGU4a-ZPBtWIQ-qtqgIe8l$$7FQKfFoyiW)JJ>k+qDbkzYM zuZ&gUrJ9+4d@}Vg8zCNOj{7^|*ol8v$UR)=3EiYco}P`B(NUI4qq4@sLh@*>9cO7- zH=Ck?#lo-n)Ot)vZW?_aL#Zl&KgrGF#{j^|WfUFN`^r+*Sbu^s^a|Q|6bC)+^W`pwT{ zocZ6pWRI=sU$1ZP-hA~x=CuF)m2wPVWeYMeEfb2WhVi8^q;8_u?=JN%vApC}T{bJl`9+RxQ#mq$ z=j`Jo#%Q6@%i{6dE_&e{ zu~=w`f9;NrGf+X4QVfyV3rl7;w$$9YsrC!r>L_hq!%7OZx-(i5pE4*vvErR_>eQB+ zEG$LP67^x>mpCWrzE)l-Yf}=;Ha?h8LsaqkM?+yU^7xcz=(?I;&EBe2TM^|Ph8*3R z55EQ1;Swar7=7Bnp@&j89}SqZ1mu3jQsjDF9M?$C4Vy6)V}rOy=2L6owdmskVeDLq zM!khTav9=4*D_1zc9H!Hv*g;w{mS2%F&j)cUc4l>PWL(a!8=-_M;&SktmRE6oYDGA z(h7VHZt-j>I&p{3tw^m%&O58zbpmZ`GGr-yFu(9;70-vn4vUkHu=x>ztPX&EIC;4f&<| z9k?O)8#V-gstPMN&dRou8pZkkhJC&Fl*UryQA4QX_IS`E<#)nTY1Iu3qzGwL1FPqt z^*8H&C=M~9T4n>7hEBkg;b&BRTqzAqbzm`e;kXU|y_)|fCc(g)vp&0Q9eDmMw{8`A z9+*s7dR|(`&h1IGoP+Omz@4lbO{&F!gP~mlz ziMvroPn*`tV;O3WAVbJ)gkQE;pKYw`eP1o3i_KzvO#^{YHU%LgjS#0m8G`YCs02`m zJ)kJ~Q(Viwwb?y?6`sWrF?{!dBKP==dyr*1npKwun*oN~+~O5y;70wl4&X=wHW4vI zc2VEnq3Vmv#D55E-z^l+pV6WAT&#B|(S?{>@PWb(53nG+-iw&=!sTC_NL#i=)%Z1w zswH8BJYLvrTfJcZ5Wx<0b@B~q)*g;o!s+VLse58_92?Hqkm|WvZPUo(cqV~&H}c+R z2m~HIYI7EL`0p=>jQir{EwVl0vy-s9v0r1=@CEm3UFJ$ZlURWY z*A=IDPv+q)!3&!!2K82~x|*pqs$fV)!fuqp^QY`(iC=VEgB6^Z{3W%sla^d%uDjT` z$i*eb*QrT`(i<%yn0GNLxNA4@R@*mu*i!o7{+v_`;Xx7-s-JlL>TTtsWgxH z%k>YTEcDvv(a)oMMvU&#Kq*d@zua2-SGW3j9rxrJ4wpQT3BF4yn!DAx>T$qI(k$>g zy`ZBnyGG!XEJmLQc_}Ex7rnlXva2ueUd+q5gcc_pQxlL$2sf7K za*b{DA~PDl%|dN|dln!Ee=d7;OR%dA!Gia`~Cn{?!DYfiV`(>21D&I+e zv|Mej;qjEaM|hiO3y%7Lwma6g%8o?_(WAG*)^7VwS#Yvf>lIM$pv6`>rq-Yp8gr=^ z>{$rkL6T9?lYXlPu>265!2oHXib$=7jeP-Gjp&<({J%N=U$U=2wT!xARU?gE@(7kv zksra+*Xb7C6Uwfvg|H7Ic#wz;p0K6PNsPDjl5uRbi0qO+1;!*0QhtJ9y-q&!%a^EV z-6gD0?TH~R4iqAv@dy7jaie$Gao$bDbKD+hm@Yk#A-k2BOBTVj_vu}jOhis1*JBB- zPs*n&=`#e!yTIGO$R0zQjNilhE_9_kBR^sm;ELaM{n@0-P0w{Eq2kh5N6@tvBsW z|A<&K-mwrh#FBkChn%C|SpiODxJpG5&LltQL_?nV$lchL>Ds2+!rj=#7L|n=XPw0K z*I6Dm4J18;>sm|a{jS&ag3bGX7_V0W#Eo8aXz$yU;MbdN6i~Wir7=F614qYLo>KgL zswUf-_Fc1QIN0qk&@{Qa_+5c)WG#rL$X_7qAMbG=9Xtjlu| zRy6-q9|o%B{cO4-Gu$7HuXnu2ACo@=zw@+Nw8IQl|1!EyNg3m0t)+rvAz7708%Htm zyTK}Kv^oYgx~L@T1AQDbnosL`%A%ntjE}*%)#01G^75!1R;SL4(1~}z9d%E{|J&BVdsLVKIeIO$LLa3U6KstimqwfU>vD7+w z4F7leT?0Eqr>2gP3M?iTBjXn$9r-s}nj_>D<*TPr;L zce&4j3QFyezlM$+a`)62p!~7A>_`mJP@`_Jd6rI>7?Y#a2LTm{_k9vSp(F7HHf|R> z&>rnN)2m|sgWR5=xY9f_nhfdaCYq;LfKn$5w+4V-m6D|nlPBGo@o*CQL8`MP4Sybk z8H#@YU2rVb$HkQJPgV9dO{6f;&4~=cN&FN4a+o`tPGT_t6)UOs=~rZjsdCW!3Ptma zpzBysr)qMsEvsSI0_w~<5|I)%pe|{DOm>^0I;k|C3a|hlfjWvldw^maG_?+#pbGA| zg*BRafRZT*n8=Gi^igcHmDEvoZr4r{3IhcZhV|y;ip8gJvs%(Gh9n<@B+{q$u4q@g za=*!Zu^={Uf46I3d+F2tY!r^DX}EYmNwVtwAbaA7zSu~2f*GslA!6>(IgMdyOXfV> zdB1%|uUw47q>Qnl(x`1BT1M>*jFTtFHJB`n;T}%3e%xwwbriRUpHKHi?QWfNk}!8+ zHphTpr^QhdL>epi`o#A5WWcmj_z$$qz!;I6pif@<-$cLfbeH@Q>-K(hNc{&|kkWZd zD}lF;uU$`8{f87kOrlaN)p$w|IRjvA{WFm_G(28dJ9hZ@!26})Nc$rkiKHMtqm+yg zDek6AvK|@o(!Q4MJTwc@(E=~sp(VyEfJa+!$?owKnp(m zRe^+uY8*Tk=zFHA=IJ+(+`$Q>8G)QalO^6@*pgPi+|kQddaKZLEolk;lqzG&b&btF zNh#I%e%#I;3i^1r5Om-k@_G+jVvQc8U7v$JHKZ`XYP8?cEL6zl!Ec)}p>K2{&O}>& z?=QzU)9dXRWG0t5C`U}h#EcMcZj*6|4yPX~pY3rS{i0Pztu&Sjr>SB1kWr-;BLY<> zPd&IfN>bxwE5PVfr>IO)gU{eX9kVl8BoVxQ=S*GIDWBll!57fi$lv;xTU4wFm9^^0 zNW0x9=(@ssPxsR92tU=`+`7U`5mGu*vAgoUnqEb(TGgt7)lQg(%A>VxG$jy*DF9fd zNo5l$x-!5;xrlqpbR_wkjC$i0hD$2<41-6=5QBgDOzu#_|L6hV5CtC1Btxo_*KN&CMs1c%@1wUh`PQX+S~UGDQ_2H`X1ncxM&f2Pox{l-nX( z&(>n8mszxyUgp?OV&VV158dIWJRZgYR!_qZ`W`vt$03vv1AKRpXtzO+?YwHN+CS!0 z9zc{Anw_7)4`#xzgcupJzq_TX6P-Rzr1TBn@9BFrzYmBku}W>=UugAin9erAp6d`_ zR|--I2HITOmUW;|8TjJEgeHCZk#l8IwQPA|S0e{Atq$cM-K7)he8G!dRa>Kb3{JTV z#t;ZTWA?{9nK${CRvFQTN}I%*r6HfnFd+}HYxO}AK~PQFx;JDSu#E<<%;vWH{bliI zXb>nST>-iDY@DSzhi=~Gk9785~4{Rm$E0E4-%Or?A9 zxSXVH`oi;5qbpUX6EexIB<_#SatA59-f2r8>h}wNeCl2AaP@RJR7ys@yg28zHj$_z zEXJ#ZRkb@#sZj=dnA*57Zq=;KyWZs}E9&Q=;Yj-%uAI;s%u(AAOR?5qKv&7f+yh)@ zJryaHOEdTQv0&JXS0e{eSSG>^+zpbYRMhnbu?Z`?=vHR&$Sbqsl#0W0$kIEJzgO8= zHWJS8%GdPCafCb`bgaxpHM<>)#1I>_QrPsYU+OFRAw31tufyG#9HJ@@@FWR-uouWCh(HqVEiaW+zok}mft#{qX|B}l;-fjtU8^Of+ zQ$2Vt#N4Xa4_EJln`z1#WPPLsaV)(3Oe4F?1mRlhczyy;q%0Y)VljSfaM zj0Hx^Opg2t!;2C?s`CdGs}V|WkFx*hNQ@3xopQGtKa6|Kf`Xv>1w_ZNpUJ;%2eE9f%n-$!e|BXgiwv+#= zF6u7#1E;|(V|;zJ`zVG6uTQY0buyiL3majzm8IQM<(Hj7e~nh_nr2YmuKrXIISWuPij%gM>wRve;vbc`227+T^+u2|1V=;oWl6E;mXgQ990voWL<4| z`}+5saL{);-}fJp?d38g0m!+P&g&xgR~nINQ}JgZAh7KJky%386ox;ZWr4oPHDyd~ zMG?Waj6*Jup4yBS$(gM}XOrzwL(S4_m*>c3J*J#Grs_ET^Sp}2vyY2SWO~?3GJaE` zDSNhTf}RT8=4IGM-FEI*e* zMD3`yhMf9MDfWCvqE06Ud_F+G_Ha}yLQ|6hZU2;7d9P%#q=I!%nlU_;t0`D^Wx!i{ z%6in<_~m9_$P{{M>suONgehplg!lQ4b(L$4iorB&sK!UmPL4ihIhKQHC_K@?xOvx6 z&5Hd@upfP02b~}COa^vxMbvk`nK02 z(6y5K&0{P5mMKb^P4^hR^neSE>|v&)$K_Hr);{l)A+wWvlT2R@fs8vR@% z_J~kI2h08^%l&SP;vkh!ti+3}1@4R;S?Fzwi3^`p()w!_ zA}A0G1xR8{D7C~v8c^4<3>TkJoxHX!=ayFXv(_&r7!+@I5s!mxF3zV^S9>?PU5se( z^eK-Kau`+TtD5tr?7zEZC{n#1gYjki#%xUb0{!sxXXmEcqcYysiQ)=AzF6&&Jtu5v zvs_O!y`ax>ZC8h$nS03@(tGx?y4<@Az7^;@@<@+I-5LleZ!Mu9U9A&bHK`Nm!Vh&p0GC4{!wYrOvY1{f>n7;JnU(+fuhhT})@ak6u#p(-;)o7$7p)7sh1 zmO7yisWiG_2aT_+{a+>y6P&qo*KXFCec^QueQ|Ko-bAJ)XCz-gMs(XXfcGav>@#aU zZ7-P~{(V!#1eh&OGgVl3E^HOAd(N6gzlGNOB-=Yl+v>HgE!iM`4gLciLN!>^R0R0% zpoF_d8S-04lhxs}Bb z6V;7~Aa-y>IeTmA?enu9`Dx%guWznbcE$@D_Yy=#zFAO4GpoFq#0Rc}aa{QWQ_~7S zs=&M?eR22o6of7}T9YO>zffRX)1`pc9Tw;NC}!UtgKh>1MtydrU_spesWePGwA!u; z=cxh<)ybV#ioZquedjLy;t+FE$3h&4-cF@)Xex)2KP)xl0MzdntxN7ti)k)e-Roze z%s_S6{?!IIG>gdIl5m+dW8O|=@`nZk0RsPl3j22DQm}cvf1bRrw@Lsd;3JE}ZBo5^ zN}^*K`TKbS;MsMR2tBt%U?DM(a<&gR zrtabOyi5Cd)99j4@?B$!H{o@baxz8Xi2)szf_31*h5NCJ8H=byWN>4_ea8!JtKJM= zCPGiOSI_*ye#Tp)=vPK!^8ofNB7qxIzO;n8)t%dOn5)OmE1bts(F+j!&dM5}5dMoR z)5g$=qGK#!cods<{&|t3mIm$-?^lGEVsWf=(_K{5c+guecy^4uG!?TSO9Kl*&B&Bl zq0O3QZzjJ(HLEMCEYk-&CeJ=j=?dHK)e8WI4qY8+S=AKC^(!drRZCtfcZUv?Cn80p zwU-=ucdxyx^w-yqERJTPu7R zkD@l>qAqTfF!SbZJKGxhx_Zuf=i3f=>uQvjmlz8s~EsssU{1M&u2d(c5nVC4H;QQI^;`;44E z;rCAjd(gyg7zvmf8v%Iy26)mlRwv(O`y#&*?%5oDREP5KnU5(}YB&A8T5KI^j>@xO zvTVB^+!S4s`-N9uphWLpktl_OfNb4_)vOd?G|gV$LX!k7Lrj0OwFqf#uvKczd+SEA zacC41QZ~K99-X)>-p!@u@!pX;X|ps0HIxn}1!VyoE)tx4mlm$iHvmxVF{#?b`?xrI z8_O`A_kAl3@ej>3)<73~eR*wn@eh8!d~-DQ8q^>Ei(rxQarO4}u5DH9 z7nB0DAz+X%Zv7zyCHY|Olsbw_?$(3e>X{zSXmtKdrOj27o~?wneChbtr9|Fgzdp^k z+J*ok!JpBpBp%yv=73*OZ_)OZp_^NxF5jkcGTM|wKYC(9)*CYfJtFd~;B8YRNHqx- zRdNryG5YVoA+55fOQYDpa2N?~ZLRtZXDHK&)D!y+m`sJ*IcOo9BZu3yvl)nK5;?dtH!7eGlmHDSPcg6~QHV&Ja}=jct|z_Kk#EXI z%R(!+k%fUGG+Bb1z0|S$j{ow`pLIM?Lk$1ZrgS+V?->M+e7&^Il>eUg<6*)c$>@R+@a~d&L)Y=IL%T3icR<4aLH9Ft3QKE zm}S6>sk?Q)c79xEvKyd6IKrhrMhK_J<4eEjv_vrLSHHfWz?lEH&_Oto5vErSWM!Lj z0O^@CFlh!(1X#hH8tSb!DyoIo(X1mxBabbpYB;s&Z{$LeX1T8;QQ)egN^NDk?+lmh zyU@WNh3MR;Q2TKNfC$>_&WAp2H%lA7iM66`k1-9N^)z*SF_qXN+3x;0?RG|^a|wWu%Vok;=& zl}3y?YF$FNJ|@?_e9OV}fuMVrvkX|_g@AlaC>9T`JE{X7VI@3GtI2~0_WVR#uV*+7{pPE$Nn&8_3__tLQz%M_rE31_czVqyB7X2%q-=Qfy-R%)jauAI8OWkuBbAZtw-Z8bd>rqU>*1Ti6CZOm?0Msz_6Ss zu^60h%sMs+#oRLr2PnCQ=uq%~i(pdUI`D*_0gXiMvG?}?4LCErl+X-W-9Dn|tlMy) z7CmJ=4@EXmx^vTFir4C)hREl4-GHg(F!@WfNZlW8-;(Yt)m*-E7XjG5SeG$Mb z>%Vw}G;9$}K<8$Bo{ld3U>pQ&2?c z5a&Qg+V$DbF*CB984ugpi_5iipF5`RdjiRaai950?B;&;@-V=)AuozYdZP=B)O$fZ z+rWo=pVrR;*FN-Z1M?3^#>1I>RdC9Uz$(UAk$lL>9O+M=DBm@~WlslYt?wyBo!nV$DBcSx%bpz8VFx*TcR zkfrdUQ46~w#dl>`2b);o-hab6OVkLPb{TtL>=Veu*Uan+JpOoY(lVq#aRS2&$E^U7 z54ru1z#_!%O}hWr%hhWK(fByF9;RgS_3qqF`Q$51p!W-`|7?)|XM|H3uMDI|DoRAn zlDPqoRTf{_Jn&c@(G~DM#cu!jfc#3%pdNUd79nqCt^>*oI%i@*xAym~*uM@1UHyJV zeaMw333C)-8%X4@8p!)@whG<6^@lm1?8+}RJK-tQ$08(!eKJ~ptKsMWBKuQD$?@f- zJqnUK2L0?I4#E_5MGwCc#@DSwFDlvH@QExs{{p(5yGi zggDbTuZZle}5qVJBzHU=wRmhUkGIYPsj!o{Rlt z83cN(TmHiecGnlD{WaP=v|=kjx@YaMC9s|aq9#lF;N7vFx;FfKvp-G?bp@=gy$v51m}q0oc=Od%J9g5iv|3nF#9gH6!M%Q>RsiwjY3)ccc! zj`z9J@wm@VvA-v(ciiRGA&}Slm8aH~iF)gm=`>M!e{|LS+Hx6U_Lbf*NZ3M5tQNs7 zv`yRdOVSfm{X1+|`tN>#q`aot7bK13a@12JtNwMZpR+fxT}0U*Ihr2FetjT3F00+H z@}Q|M2eaYmEzpT+9&`e+-EtRjD6&6p(4|4a3)t^o;>OC5Z^POt>s{3Z{=A!F5-06b zEaNqr8tjeYRH;sI-dPY&=cl-PT8HdrbrMGpW_RuC|?46-7L)1-4*`y)sf(5n2rwK+JHK2V8rSd%yXSC4MUmD z9yHf##nOFFY{=@F3eo)}5@Z@dLtwKE%gU+$dP97;(Cklp(r|@q8+?g@_TiJ1%z8gr z#4kq+ViK<5IhNEKrbcYMC~iI2OSoOn2fAeTqcSDL z{ir=!Px8ptyFc6!5Z2zJDP{+`TW)Jhrm%DYeH^Xea7Ak4nld|~EUXP*IPq~EQjx6MF7MrLFA zL1`u6wux^{Rom)(ZxFWsnRR&&fmhvsjBG`;T}9?|MjGH`o$h#CF41|qU0yp}QAK@1 z`5Ph^;bE5tWJ7!*7Xf8NSSqf^#Jvr9iBmDO8y}s~D?tvQ!wWmcl@~q)C4J8I-8P{^ z9YOd5bLKz!Y1M{J=^DOmWt4l#*!N&d3-ezJ#0yJ|A%?JqbonXv!Y1 z``(-7yZ`=$46!zxj_F-caTu~g=X*&X?P$Jw$&)*HIs1h&!&S-@2=w(FgW>`n-?Sq|b$nW4cYoUs*BH{g+{0q~%d;r5r zj9nKZ1{~M{B|~dPK)ohI))8i!}S-zWUeY|%ulIaKa?4SLuTQrPb)q^A#8azGQ70g= z8pUupjtvsQ8INPvChaXWB&QQCeYJ7eG2O+m#cDtHLdI+r10BLCClU%nv zvKO5vwV%o=q3&b0fT7S{4hXW(yglCPhQ7Z|?F%Ss_VPTQRloSOQw5fydXV1_-kHl| z*q*Hy%nf$g{uyTRKPl3G;hwg?IWdgbj>P+tnK7@w{(|g(YFz7TfPPRvn}n6#@>(|lZNy0i@$YH{7v&RSn)}b_n=-0nJ*(XDt$@0%EMR z`mE%xZ> zkWvJsq@^3AySrPOQR(h3QR$Kv5Re8bX{4n?y1To(;X9)~@ALe=`0rb57HiEg%y6Hx z&)(O*_O@~_XPOR__`v;4K4>-n_YL4Zwxm$i zg}oUNL$zc12L#~n&n~tnpRYY#Z{g=keOLP!(nob>E`|&l>uMLPu5ntYJlmNM;+~iP z8BG=8M71?mLo9>srBNn8d7Rqlh<(2|9lrmnuO(!?pS>gA4@JNxd@I&`bF7E|Nzw#P zGuMz686BuaM^e%gil}5aw@9MeUT!@So2@sfh$x_kT#=PqjwqMu=^{b4Ig8r?t7%@; z71=z)ee_nU-wsBbm- zlKs@6qmWG`Fo0K()HKB_G_Ru1_|wTS2|cugpC*3yUxE|#>?LqjNEZjyj(?UV0GP2o zA`X7^f0QY$??9&UyvgA^;9n4Y6F4}%78K)Zr(wggJ;K5~0A(wj;P9^o8T?@r9So>v zb;bck8+smt=d@HVO8a^fZDUz=xQ4R(UNbiVlGYATM!G+o2h;oNUeK#+%Mo#)0>{o$ z%(7D0OHAMT%`tCS=fs1iwZH*KahgKMs6Pt-gGab;)u)Q`s8HmwQV-Y}?4v~eC=r$2 zEfl3}wMQ2@h%Y&!w?7T`#ue*@l69cs^;B42IpO-kxCLvfwZ$G7m7poVG_`~~3%NDT zVusBYm7pu5x~<IUWN=A z!rWBtK1y4lT`|NZ5|M%)(-6u=(3p0x%Efy2Z__NaKWGCqpkV3Q??}Qxg9+ZI^R1@KYtgd0w>g|wZ+supFqiHdqDRuHxp-UHNA0X^C&75}qKE=8>(t?T zb%G=zw`KFG@dU44eTN^6mQD{$pm4!#xC-BS-)VsG%lV3S`kS3ej7M>lakAMWu**R& zTYPY*gP-LSwoLtoeG^%vONsH~$-LO$(mGUEQ+A-zS0Wk9e|Q(9c(4W^J)Cv9|Ez%l zIBneRA%*|-)#=bm{>dcw6mZrVt#VVUP$2M82efy*>m>O<(pXUB2fhrh&6s>44SbOJ z{sN7s*7};v;x`#T#eqy~+5E@0qc_q~u4z8wk)6@3jiZ+cA-S(uWa3}REUrg1Bul=~ zStHOM{l=m*6tCMKsvPoyf^B%kx_R8MXMLbVk#(2>Z$AXf{W1SE3d@?+ECIJ2qL;tR zkmXpRyiW}MQ8@5JSilceVXS)ZK;x3WilyW~yA7>{e{{3f{S&U=Ekm!gFO>hk7XXEt z8Yk7_IXHgB8%iFt0idR_VCI;0koC|{{J)@ye1Q~PsV-bxOVqn05ZW~JiOcRSxZ1XxuBglOO`u^pOAdau-r#*qt9@#USm&8HY5lrNxihKuD}bnJ zF%3Lt7QXr8jArT?f2)V;$+bw3dB!9^knAh%DtA2zdLA${c}%NSTf|V~?Wy ze#!zYfaTI zeL~4VkQ**2dsS_JtKxaStlLYOHP0Au*6W_TH6wqq#a=)L#&Z-*Wkk_@s-k{IgUee? z)lbw>@1cxc_;{5rTp-zBJEqp)A!tbFQ{U?%g4$nNE63lx`eY06noPAxN9-TR$OR<= z4eBNm|5KWJaRvSNQGCx;%#%@oM9LPip(B%JUTzUa(SQ1}#H@*G{A0XWQYQIsBC{Lt0ba~3d z!0OIWI-1NX@x=A+hPy9C(8#It8zi9m{b>Da<=t8vjj}sqL-7N&CEzf?&Y*?C& zv=uG^HAW43-!-=TERdkA`6eO+6> zJ)K&!@Rd-cQl5ipu{rL>g}g$Ou2-zgV>I{trqU`y0%ah-1?@v;ww77Ocja2e!-Yk! zkXQQer_YCdRAPD6FU$|o_3K>I@F2!27ncTeaMSi9UrW#@d`zpveY9R0H#r)1e^M_~ zps90Srqw<-{c5P^E+6*`=P^~W<0iqOku5UhaFx6|e1a0#LuHVIz+Za^gbIS*#&eqd zf%<{G;OSC0zX|0(r{u-M-_}xskpo(5nJU9`;P`|u+-c@`JQq5(b|!tEc|85nI=Wr8 z%M&Jt+pJXZ+8LASGprU1&mFu)E#(`s|Jq#yl#b2_PfDVA7ywT3kR(_>C@bpWN2jwa zRy7xS$!Vz-G8sa(t$_7~1*`q(m#2dBm^D^%bl7(<@h!adt+fs7Y^KY3yJKi=eocEA z0Q{vp4?>EXC^zVLqSR@NJ2>4q6lF72Rjm-zFe=xqF)yknif`7?O?`LWPmJY+8MoT<68tD%m^S&0KX$k zj0V-4RGJf@7g>g3CA&+m6SEX?o)OMu@s2{}_XZ(|KqL@++F0~+W(4g8riOA`0IeS)Ihff z0_vKQ&G>(>z!o|xWadco2hE=sph@!oO%2dQ0S*MWKRe4&?|z42KR9-^2vR2-htcuQ zRmP&vY)KxvV1c%Vxx&c(%(0@N>`m$iEqYnkX@+;(4TmMGL(St3L95GtD! z9H@HZ1As3yIIGT*+KL`Y4}UZU47;>1pn{p|HKS)j`1AN9&@?q1y}7^zPNJhv8LBQVL>QH|T;m~$k*7r+dgL&nA>R~1%J?YaAGN}O%Xiq#$X z7$AwNnyXs^_q5+&`AX5J$7Q8&)Hz-_iI}Q>E22*2BcNWau3qPQrwe)ku;uj(;>kyS z)Hn%vj#7u@ot*p+*vrRVSGwaQ*^6FDq=fJh7aA4^noMc1$dl`ru5<-8$5W+x@9fMb z9vIcY(D*!pzaSI32?`Ti?6K?aF=l8EpT8g4;d(m<=j!HRF;?>FZ_HnUqWHC{b$LpF}Me9cId zCR}Tm*c_h2U9hwZWeJy$X$7{ZadQD*>e4PI@JRwDa&6(ik0C=V&k#+LL0%P+y zxTQR^@fGb3zw1zYc(Mi|!AgYkZQ={9p^IBtHDpL1uS58M{4O&E=$G1-GR(BoVmBXS zo1;2Ok}4@41;W=V<%~5dL{wDHH8)Y1Jy3FszspkrN(+O0Fu5bP&~CRdV(;dhct&*n z4zj#fxLu<%iQ&I+H|%lAyy z$s=`B5RJG(El7k2j7D~OU0uPH zspT=0#-*J7SI>`-phLCJ_}sll2fwc|ws>R+A@#?2g#~o}_TaV#Q1^Xd`-A`F_{p?;=$Ls?ve*c|6>$AWEHcGc6j_glr)5-*cqz-PJc*k^8dtXiY%Qv|ZQ;|Vth!I=i24o`1JG(bEc>P6v-+N`;6=y-|1W`=gBGe@?ihl)+D40O&L4@ z4EZY@@9b=8N56=3AqzI0t^ROOu^CbSxzVw#_cjMJYD~kaC&Scthhtg*vvhplhq%dq z{(NDpV*1+|$4v5WUE-!c-&Psul0Oxsc|B;~d+=Bkp{! z7#XmuqLSw!rf>9Ot)2BSs1!o8SVv0}L$5~v5Gukxp<+QwFX~<*uhJQ#O|Ec_TkE#v zl~FxWt_Q-HZ_}I>YXr}+nSY8j4DSLW4a`kAfR%gD;}Qpg(+G3aLZGhMdqL(!1)(=PWF9kfKshki8%1rv1 zudn5o82S)UZVes33lLh6sYyYQD3mW`sHxhV{%0|+Fu+Zu*mlnth>*X&dQr|w^g5go z7VO~)$b0~Fz))T69=oXAGT>Gz!p}ca9Su%RS57W2WE7XEj6yH^k}M+5Rxk*g z{j;CbZ*PD}{9`F2toG8EKEBX86<=|0?^LYg%y^AQwG!RGn(%~>J z_w`O6NXKD56!w*hCSd^Ow`spCAk^e?IePNGMI=(dLE<71VU|D|+MFWj6Mmf2w{*3d zFBMM%s}kbWQ+yeo2?Qw%N0%XQLoE*E7Xud5el98Inj=oGjh%8v(oz)L)S$d9H=D~K z1Yp)GcnER7!Pj46EHp(_ntC>F5>otRG7 zPs53Mf8$z)(HN^L9d}`_RQ`r-LpU|7u2*H~5#TP!5$@=2w#sIe}6t_P}gkMX%iX8WTD z^=9DNc!m=Fdlg>QXdK+ zY8Ajdn^sKv4ivOA^%z+}%x`yItT2AoC{qZ2o8O=p-ka~!UZ2Z(ixG4mkL9t?z_vBe zZ0$=8gw8G3s4s11p6uLu#0$Su!QR|m{t#iVAsKd+N){AWRV}M*IT-VUi$p9hM{$mi zfVx8_S%85DB@Aszho@zLvsh1Z-JK!UY^SC{V-uEbd#pXZ(6G_@Fk+(mEg>jP<$P0Q zShE<)%R=ic|Dj04g;!g7_u+`0Hbc19n5+6)kRH_SO~R9(E!VMyVtr;oEWC0YiW!ij z3Yl?1%kX&mll72Pgy#G-l?4V&GUTvo_V{B80IuN(!vER76pK69t>h2L$qx*CH^7K(QKgggZc~*L}16D(|)X;E#^z~Dn1ACgb$mz zYaAhh2tPf*&kvqdP7LGmW0Yq;FofZ9#ymEbh>)Wvq!Fn&Hq#A3`JAo#t=>fZ-sYc{ zMLyiN!AjqCCa@C@`Dd}|B}&dh&@37C&^?PnjvFYSBPp;f%_NmQ{TTX!Dx-r!;Y(-6 zgzfJ+D*&}GNCVI8He}{dj1=^a|F^ZWHh9~Ps(WMrZ6fFhhH4Z0(26X3-xPvcEfb^+ zv1g2aB$<}Wh&GgvYt}roup@%_O?xvEY1DOb&GLi}7=FX!Xf=zY#bR7m6%rUYir%3> zR9Ra;q!euA#e>pvHtX|tHXGd2`*17pfP&YH)#!!u-eu3k{8SkGqU_l;qgW-)vBdIX z3&NOxiPkzDX{OU%4GuIQl@->q0|~Y@6<*^RnUA#x)fVGZLV;)7kIN{ac55-#DLqlS z=>ppihR|mAex5^%+`6mx@R!n1u?16tzGkzXZ2T0JBV>6BG*c)vu13AdEiWXxsI_v! zQ<`K+*e*k;-_(D68OIU5D-{3+2FCa4H(fN4iK&&!l9Fn%taK(le(3v<%WEf-L?wPE zMpt$J@PiYXUZGAYGTj=}&tsGRCr=DN76KUwn$tGW=++rch|Wv5MDi;th}MVhjnr#};R}k# z0rQbaNn`yqL(?$jaM|E_?=N(^(%o;hFMvD%bl(55_F`bZOH{spR2Hhxvd~TD--cWv z74%K_=6BfPs0;6j004UsEBe1E$OUa+%^@2l{L~!Ji1(_M!}lY-r+iA)Q=fhJ(;j{X zYcRXwWAT>yz_;MxQXQAQH@C&LC71nPuH>0aI?@7~$3J-=9hk+cAR?cXP3cPTO@ltk zZ}KC0w+Pk%M5|lH%B`XXr%Pm2#T4wR`oB&EjuBfJ@%33sY+qQyzu3a-^sVI)y8|T2ePjk-LFGS4o>Bf=M68iZ46qN#(KBq zj)$mrcua}FTns}1Q<-8o>9jctpd*?(8*0x}n}D*}c4tI?aj|z)n)z|`kO7B zC3h5rFdcK?c(%Oi>R*;B8UP(yp?aZ?^TmXl+?TRfRSni*L)5CYjn@2h|0}gXhaedx zq3e7S(nb3&351uYv`ZTAWLKDwA;?BOpk66Uc@_txKBX=K$Ig9;V3zIr25TKgc1q^1)DcB?BiHpDWA;0K~=tIAG3>|SQDkFu{)Jo}yHBa`f=4MIva}U_TKw2<-Q1psE zVZhE7RbA~H1F89B6^04!M}{9&5!++spKa#PW}lCHd`{z|x58uF9m50tQOqpWKIa0J zHkTNeAvi2jud!hR--np=XN+qQqNv+P*cYE=g4wvQxBVHU$h{#$U#(PQF00?W3eVGN z$w?4I?__etYsR1?u)}Y%7%8FTBSL6IML{V~&_}T3j)VG20ZEh>x57d!{^8Oc1?}iJ ze|HA_ysIQ*Fv$!ehI`$mIZ{15PJL}B{{M9%U26^~e2neDl)9NZ2b?2;mrbZ?G_Ys{ zE5+C7N*{|2MK17cNIhpklEaEqL>lX=s5jj?M*iw#S*Cb>;y#(8!YNaBtU{yZEESQ$wK$lMn>14R4%u1C zE}M|5AM4p_dARZn z2x^9M#D3#9JQF01%C*6Km$e%lrc}u)VXIcAy;C3T!Lr*Im~^Js6dYCiGtZcoFL8nP zvZG>K<0Q_8K7L6(Z)rc{Z2~P~e`LQVQF0Q)G{@VsC7M^~B*dEc3$v()ZWULg@7;6X zfX|0?C$7%tU&@*+tRxsD9wns~3nH&G`G+%*#xT~5LpiMVxkeJBn%hVZ6qsP^<#8-G zdn`1;?Qh{x;`8EBt#Ro3mZvW9?j9eR#6{dGXoHd)TN<$4_}Q)sLNdok^9m1_XjMfp8@%-!;AM?HEi~^P z(VbWlZ0uX=`R~DeWq5Zs^8g^5`PzUMM>}%UnLeE=Vw%X%N||sa_dJ;3(q5;WnZZ`< zVyfbl_yZ;dSqRD(fo?Ry9!GjN!R@53iONGxl6;DeOyc!sof`!aX+M=eS?WmBAtlKY$Z@5%lccL97@j zQ4jn>v9qlJ7I`%@|4pcu0RM$MC!)$)wR27RplxD+<061B$I{nP=(HU+qu|L2Mht=r!;ENQlRPVY8 z7CWql6uz)byJ3yNU%r@Q_qar~?x5`xMG9m-yzRDQGyZdwpiNABc|A6?+ z=lb|%;ly{zvWP-I2K)v^`K?zkH`^7nE-fcK-Dw-R24qgju#Wzj1u$8fnISFWUe4hs zDW;N|cKqw;n`$9jg^kwi)Z)NXI3chn;L-HtAtJ=M>Faa2IcDE#WXM*}Sg>wdQ57Ya zr6133A%>48WeIm7@h;jVVrUdUo`!41_vVt~N$Q#dI z!Pv1eC;lKr98OW)Z2YR?!P6v-&9!8U6$Vm&qu!Y0QlmZvir^S@#gDyjx4+w&_rLF} z4{+Hyn@wsRkEMOVo`oKz1w=F}^2n7>yU`oNlLh2ZiIGy4P>C;jV;s#`19OhIK4UX# zegT86thNUjC-spbHc_fa11B+|t;`8Yb;5p)&cn00&(8Pfb)P-QUUh^E6=yPf*at%M z=^ z`&hg}R&^;Azqqm<;B>rd#8AsMWX3z0l{$TSb(4@mWp>oWeB`+kF|S%V!+qiuaDNwI z?8Pdca*lUK5nKF98e$?#3Apgm$&ihHC!NlY{)p_NFOn(xDT%6#SKf@b^-ck~=jm>Z z4D6;Eh>%}Co#zJBieES7ePr&RXt4}dHU47Th`>O508Y%qX(V8%$++%+43~?_HORh#JvOI)2x!xnQ z|1v^K`&sA6aEW)y)@_ebfY)a*i+Qy-VREI-?q&x|yL?OWvgt(w0z;~4%M$Km(?#@t z!|SbBnSl-zhIyRqCj=Uc+xs7KOxxN&Uw4CsWD(dJPF4Rl^nXn10o;zvgXVZEE)vhl z_Q!=7G!IR%8nExLaBeTpndQG}iJ7i-weXsc)rT~DESZrm`|X@kU$x}&7x|JIhTVNb zajV9^%di!B4Drn9+DWY%@@-k)DS2`jF=c(?G#{5ZH110#%w{o&U3MJ0GoJ`hkIyoO z$1r^-AD=pyhOhA=r)dM<3cP>Z@pp578Q~nzpYl~8IIe-;u`9?OMSxa;p-&-7HR__Qmmj^4QNQusad1r`4|pyQ>jvE|DLlB7=JAYBX@j5A?y+5&1mq3o zA#op{^k4FF-KE|LuCEdc#1eMS&g#W2ReXmEnKly`prq zM$U99&XsY1bx9v3;d~!VG*eF%A4O)v_)Vo4KDJQBS0zijP(MZbu0xvD^gh)`bxfCg zqbw3uZY%bEiC1HGjv}cT-1jSytE~b|3nDA@h6c;w4-%}oZ zWZKqBqxkK6-#K7NeS`W|izQ*jH%mX&-KY!EDn>RBf*zMEb!y(c%oq|s4wBMy8Xdht zgxq|!i7V$y)Ch=U&olk`rF8bPr^>W_srM*tR2+FFQy>`+@5G0c$eiEuh)Ly)R*#<8 z$yE6RUq*&nqdTEEo!`5P`%bMvDqNJUNnO_N&p+QSkpqcrP??5VU)1O8>F!u+q14K6 zR(yPY&{*}?H8R4z@SOBV2c}#%z!Uk*_SK?A>&mL*<&>WMHx%=@j4}Cq(=qOvPlDA? zldEHj=G|Z0nR|CL3vRBg=8rE)ch}^EYVN$C)#1z&K{1bIHH{l+9D7~F(D_!*AkFQ=msJRLiDpxfv9ZW;z#GIS`y+(Cy7*e;M_0(+i z&6Z*eP7v>Bz2(e$ojt>Pdh{uQ7{aL$7eVRi=})?2qmi>7tE4L8Q|A~*&>x{ZFZ-ma zFd{=>?kS|WoAjW3D&xujR)h}(i?zwG@Njb!4?P}3y0tZLmJADT9)VyPKnC4bazYrD z3wJtFk4Lrd53(@AHCa?XlJ{-3Qs^brT}ohfvskya4T?lt(uq@M(bp6=$c+o1P;yqS z%t(ckv3{$w_SVZ|sbY!P<8#+-czP&!>C-2f+Edy6gFYlhGEV$_HDz$U4)Ib@-AhjE zmu|Nc&(&uopMGZj08%$QH=%cnVfyV>movD2ObvUrocjZHNP+_ukK-@ZD7_gl8TG!* z6|2Y6dL9V&ZUh<#;EQOyXQb78%O97o66qeXVN;S^(LCOR!<0K6kQ!Lhiu3bTwzF5Q zN|=PxNufz-qTK!Nb^MS?pHy`Guc7$q#Gnu`7~M+I-SM78{rWq$)6V$pPK=2dTv-=s z6mCv&PFNO2FhU?^st5zs zzPl)ibm$G$FS8n%P%u@nt5kEsrcs9~^$0HMaKmoD5!$i6q$r#z4{*MBP?@vf%)X~u zB4Ter$ZkY`HW^6MBbNF7C2b57uh*TKL00~xfvz7Dk?m|HtmDSTqi$LvmHDDegc#bU z@g2V0-%;gvZ1KH7dv{=jUmu3gvB8V^b){?!=&pZtUE`Riumz!oW`=_jyH;%28&8yS?<1g;etL-nF)v`p;X+JaN-{s}g zOY7BNz8kaV7FuVVW82B#ImS}tOyI7>cXMs^G&PzG%*wdPZg-l?h`_t;fBhN1ig@V2 zCjPPTW`*z%`{r@gGJB+Nv}4f%Rx%(2)Syj|+wf-!IumEHuG>({%ZIb)$7CuHkH4h3 zcRu6cv0M8l&5eWkD=AjOb5f=(JoRqKVSvE>YUhV>ea)v;p*XmTXUD|6yBdYG4XPiE zLw9A&LQ_=Awwq|Q>$aFM6zD5Q^R$C$V`y^kU-yeF0FX!eRvXRg?ih_f#50XQp4w`( z8kxPo%00R4Ee!G65AdE>o%qX>gMqKVE8pQ537Gy`Fo0x z2qNV0($@Sombbx3NeL|($0x>c8^I=vIQ+$Hl!uZth*n|XEcGPLir_={#g+jk_#l>^ z^xe!XA5E-=``wiEjWlJ(uZ>m`(gtO5s^`+8GO6RTlC%$LvI%9SnVTaOWS-rKr49K@ zF4q;w5u7aBCKNGd%egR4cRLTB&OmS_*AB>-(WN#T*QwLv*`wkUL%^oi!QbhhH+lt# zwHD1f9t|)Dt9Y5DwvkNI(pJu^ z9r5?s?_(Y(iwN20Dt5f$&yP=aBuX3%cjP1T&%8eWKA8>Nxejqjv||14;y|m+6+h`-WIAs0t&XI%8vU{M zaU=VEM8?(`!9(+di`31rKS&Ca)pX~94m+_R;9ZoAmJk;g8^i?a3MojLs*8@??k9aHlg z?G&>uJx4eUVxl>lr1CUY>!C?9_j^-{S`MpU?F7AwF*~+R3lG|&J?Bav`}qlECx&eg zQh5nfxXl@rVGpHlR|RjlTc0dfVkIj7D!^p}V7>K@jAo-17nxow2HuCWdxx#IYUgX4 z>U!{%A)8MR%h5sTr2tWLyq4wfqwNa^?3M24z}weJ#=j@7i`@~y{Vhyb1r7iT5_#rN`4*!xGtsQxA97+Tnec84nRD2ygkfTqg{4v;4S4RgLCUz6^cQweFlUQzsUjUaPJ| zy_4&r3~fER?hB;$GODx82ZA=yHR#dt!>bl9^4&icmfK_Vly{F3opd{4a3tqxbzktZ z7pSKYdlFdGc%7r8B$bEiH@F)O#ig3FDk8cI%%xB|8DtlwAIDoK#5f3>qxea&5P)l4l{^>Q=Zqn%*CCJy+HB^u zN#S!7fhg6vB4PN7K{mgJ=HzV7Ygi^+wLR7w8J@1P86M5A!KeJ;^Hwv7VOwarRs4mU z{c)p(&79aE-63i~S_E-pbcpdKU2R}Y6H@ay?<9;IGV~kwV;+1gb_o{jlJj2px(A-pDhGOb0&kWoEMU4~m35>rL7-&kmP`?4ozY1swKi;5p4%evO#sHmol z?z52?@upihVaDq>brxi_5SB*cTN~b|K*Q_7wtXXpS&h3~6B*!v-0P0{H(`8$IH4ef z-k>t%WS@gLjUa>yyhJ=j)6#g@!12l0dXR2Ld@ZUGa_460c3blwROkq@j>T9I3=h#8 zcF=oeEy9W@JrD6lRYpc)k7=B0J|}e*r8J6Vc%a5nZ?V}30md%o^5W*Z_{G%OHl;Q@ zo;WJ1utav{Zi|vr)}nQMnA$NLvW~pT zOaZSWEt6$R%Jq(WZjoVs#hBGz-l{ZztHgxzA9j~`VLOMhp02w2d%JnoJreL%J2amB z-k2hSp%-yQdew3ib9AEh{yt`C^1oic{Q@N@gq~3Xw}@mT5cd?i)zYC*q&sHKE98Sb z8KU|w6?L^QGDzjoMKHqK?UX>T<37(Le)U9pSw<#O1n(%Rpb6qnz+2{_aR_QkQR<>lyB?-t*iZcfdX&Rs}yl=Mfpdgl)VJa5+NIxDD~ z-&5a;;7N47cZ=p!@++PuC8A+xZsO}qlvpf7*=Lu}i*@kHy!My+O-Ux*U1)H#b3MH)c!Wh~=-xtgui(jTPQs5k7A~b!E%X%c%1!n| z$%U9>#zO<_p?ZWgN+;a`C9#(l6r>3xzWUWzbD zW96xVGBmop76EID^bC{y!9yL;jTKclBI_WrxV>>F%EF?N(sZ-3YjH8~!vd!$5D}uf zdAC~|Ln2Fgbrzqnwc34Cz^uA)km9q_YooqBo|#Q3kR4;Df?Mu!#P!8muU6Pk22bE? zD1N=?;n3IJa1@=*apL%HmTfYq}qVmJ4#;bgRNCMypc3iKfWi}=%sae(${e( z!WlL2>Cb%n2|Vvf!~>~7g- zYuy6wCV#|3^T5)TU&ccan&;&X_+o3n%j`dFKpn5t}GzM#&!%_-Utu>5Bx7%5#C%Un$MwOJ>Jr z=Qh)^vKRzB%DcwITm65tzX9}j2Ywnbzul;i3dtuhz!LihWxpO>Rkg^bkoj~u&yO5i z)`=rRkBH&K!fQh8N1L5wFhSTn?JjoP$%ljP8+K1-EGKkw^671bptkPl+3)06VXtv3abYLKMSq;SM_Y zS?e{vJqWvg!(NYhZ)^6Y)KEq?I>3dEk%KFu_`vCChCPu@^_*n(^}+^5T!W=_e@bI! z&$y$aE%P3yyAXai)mVW7DzV=%4w<(a4(^JH{KlID%(&C(r$dSJXZYod(ZNQrc}jOX z`3|}E#rGf(sD!}BvTcv}(Vr0VdWr9`6LXt?mVRao z@h_*L(KvgemTqfZL)Gwkq7JZotc)ASX0YFsYQ7rJ?$?G7MX}eiZC4*mFD6r4WPhgW zwx3Km<}|%I8U4a8O;B$&^_ek-qoPro5cw|=^Z;&2L5RWghcdZaW?<#bvH-a|aPJeo z59Vj^u&|5*A}xtUyhKkMdXi3Hg)W9Ngy5lsiEbh|Rq=CkVr5(weoUwHvd^xTl7Yzo zO)EK%^TKzr<<(`;JDS@p%@cAtDH)NdrB;^F_~piIm^S1thtmj2)c0oXxm_$QF)Dkh zEbqV|5xTaljtC-Iv!EX10=->C?Z2^a2#%_3HfpJzogb(4dY1njvfUP{g;L&fy ziU}gWOmJ;DJG2-hG(-;0L$OtD_(1z^O}~G1fS1f&!yIK2F=Wx=KGLDwa94qQuUeRp z+KP7eR<^jcUen9JA8XYn&->JVB=^jN(xD^kMMLv=5~oezeuW6kZ@vYZ)%kuT2a$+m z6E7TPvFB5Oya_(1nT0# zgGj_<7c{YtkSKb(rI|X%gmWCR-a^TcmCq?BUL%lKGa z<~FNkL!}xe-e{3_FpuYn?~pE$3H^)+!aZw@X)HW_T*+&HxPM^QYY2e5S+(elxa{9v zU#PiW_xTS36D^=XW3(RN_j+}`-#!-pMwWNg*nAHI4ej{TBSc80l9{{KwC|@j@YI{`fj`Zl6E6}^g=G~+OYL1EoN+yh`!@D&+wA}uN zQhpvWdvdVn1-lu94-fA3=qK)2OeEU)a>E35F$BSrHr=G~%Xvs>Fc#iT=j*3w%0Y-) zxOwg$rquQ`BqW@ebExiaBRCjzBwdQ;GeOpOf<*ufWTB8lnZgvX3LDI@qRm$Y*aq_IBqy?7J-_cTn*B{q|t?JB$n#T0+ZAr5?hjU z&^CUz+r%7Y1O|ATO`tBzkyhkoOJGBfZWKM>>7<>vOKhPFAY~611@7cwKTI@r+b@0j zG91^yJuo2cxx{&m-SxxUa(iA7Z~1Lo#}Fa(PGO%*V?>Ar(NX*RMER#jIbHR7b#L$i zgbCwogB_JTwGhoyy}}*oScUS>z*x-=w+J0CS|4?PAwfC4$Rsk7il=3aeII4`OMZX z_EuIm8Mj-=NFU|9wJLX(4|U!Z<_V?HYpYYk{&R2x5q!bmc}~QS#jZ_a8ff3i9!erX zN@kV7DGpBxPJ_DUCTVEW8x>}M)We3|@b;QFh%9b9v^l<+LRYLa7%}4OdSri4hU{g~ zh8n%w4Z_>2qoov5F;XE$=1a2L$ZDl!G$dXKS4__%Lo6z&KbHp>HuOs|HtU z&a&ef-xm<|yqBi9kK!1D$aektJjvUbnQWM3k{KC(;LcL#vJ*+F8N6W>jr+B-y`Miy zF5E-(e|?MZ5G0$Uan$%O+Qc6L+R7K*DsS`XP0hiT8BUVHO$#8izGu zejHv|odg1Qk=O+{qL56A+)cW_h~~F{bp@;$mf|}ON05cV(l2MDHG~GW?$2oX6@R!- z24sWJur8m^Kq5;itC5_|eU4FJf6$1{isH$E^5?Y~?a0*kqRnQ%&htNtVHQnN3HXJh zfqS@JemEoGBTVfnud$gn9?kU*W1-x&nM2C{x$MnBv;Mw357zW*qNvomV-BO-boFVp zbKB$fY~lK(E_>_}%}y4Dhj#*O9wMlhkJ?f`n3&E*&4(p*9$&F{t4tfKvu{`1_Qt`M zdv6N@Zc-IJ>7cE+!kY}|vnNg3ugM?k^52wyeB@*A@TEIm=@grPVxI5V^1s?@=Co5~#)N_|E+lYP6-t#Ayc2>CjCw%Kckk+>_oQIM5B(?Q zEfNA)nr?@5?9b^2yf2_?NK*uI9vb+R8oQPDzT4}~ipg?xJv_n+yYj~M^B3tQ+Itf^QwK^s1 ztc0I->vtIC*3%EYMohz>Co9Z|uI=qf^pipcmJf1{?()XM@>NEW+n!t%VU|u=lN4%2 z!yFrDFx9XzPdo0Xv6frjGJ1LW@=b2^6s=8fq0j{OzjK`_ujA{aaRvq!AmsheuO}8ZNh^n$ckS+utVnDM*$UAp z?!4#NOx`L+b5r$dQ<1*c8{NskJQ@}o$~>YHgfO`ioT+)g;P8$L9D3#D`0>Dq#ysXq zdna;#sFFjy*zkfx2e;~e!>=`RsC>wUj0e5!Gn+-B@kvErp@nkTJyyJ)+Y=&LIvV9d zdht)HQ>Lelj=PLuU+Q%8X|v@JgB~tbG+*EH&<2Z$u`%DJ|4hULwqPI!Il#a5IH_B+ z1-h%8Akpa%su?tt=LRVz{ZCIfeO5sN;4AXKbn^m$dMrWj4HOt8@uoS@g=~}Ue-Nw0 z8*2Ak^l}Ily||vY#}~Zk+5C!Yy1mm&+-BJZ859;e@M9fZ|8K*%@_%A&E7bd~a45 zW^vwR-Sa#9J|#d(pLQX}9igKF2mWrtE7V)-Hl8KxXNhdm6y><}KvO%$)1SM$t7(%K zZT|cx)2TK87h`W76=fH-jVmgpbO}-t(jeX4jDU1Af*>U&B3%NK(lB&)N{28Y3MeTp zE#2Myok5@HdEejfkMCQ{HA|S_o^$WB_qDHm?S1H)AM&o8*ke4VCm4yl)dss=+3uL9 z&fsx4g&bsVR4X;~bbcuP6~EsDm`f zmt8~X#<+?N!q(K86hTgn?8y8ywaHFx>#$!9wwmLN8>U$y1G?=FF+ZN!_-i`p@H9f1 zPTwZ&4r*D|%yKJT=N7fuEhLhu+!QQs5@Z6f?=_7isIl$%Zo8pEgBRL}C`J*kf!}?d z{V~EdG&%qnHGN9>8)+H`ym%W`DEHpqScd{^Xp!<~{7u>*_B#d7PG+_%^(Wu>sIRQi z!se>D`;3-8?hG%%E|==$#G>pL!{i*tUE!d>?B5Yj6&{l}^v>xh;FvN?lXg@bB4uVy z|FnMWo};*_3>Pu1c_-`kkFkOhJR&D4Qu*-#fZ(SqKkI@HBefs96Q_Uu@tNj#H#km@ zQ_Es+!|~Ow{ZO&J?}W<8GmGw;@|?Lc4?2r4!J<9yRtM{EYtXCxO!wR^^XgCYqTr-g zQ$i1w=pr zI;N0?ERg3}dP=;-NQeauKyYxl9))P6M+JKEPMTBarVue#SJMum4`4 zwy;1$I_@Y)@WIAZGB_S4XH3h$e3kp;)hQTSnq$L~+kWR_tRj;ofDW~Td2{==&$iurC zOYza5^oLiPqsqzkaY`wlb6E$L13a&A2v{QwylhwVu)Kx&v* zS3t@3iDL8L!l=K$w^9v+A@>^eseJ%a#-8Y=1}VDNOw@xkQ5yn4x_o$a*j4 zj`oYueN1gn6)Nb*#*MLX;~_0T={c6`h`5LgRQkGN&=e5J4zZrG2ymA(^-cHBFPOn5 zF%808`}_3Gi#Fte@3iLnPiI5~&#vRNV8EFWdldyraMc!?{-9g_Rj&V{F<~iR1yO(} z4@|&a-Pcl!#X|LRG2b7YO_&b~#@%U?6NOKEn~SHVy*5M&!H|Pit!2-_ps!|DIXZZ* z&kJusOBj7CW~1Z!K}qaEXH4`Fi)gyQumVF~IeDK!qckUxb%rHDh-wp7XGB`rVMZ}g z$+$dF2ZuXiI{N;g7Ep-`%~hE`!@Q0p_xCGG((QKmzR8d07L}y;rsv%`R$A6rNUi0_ zaDisxcx`d1P9E#R&cYN*5Bs@mjpj+P>;8wckNPhL&rSpwwdf^;$A>kbxHnn!4TyLL zxYn*-F?LeaUqRmB$|d9t*J#B~R0qY#5lb0`4DNvZ%d~O@HQ<7tC8Xc|XF~)5GuIkd zEdCRVT5!SZ;#U5*Ri`4XS_XLjzZ#f#vmxbIfQqTQ+5vI$T9Bo+2j^)Wgej|%s`UL1mpqm-C|B)C}Ytelag)I!mFm&!tg>Pn*0$qad_ zwXuC7Hq$$)S?^+_)XQXa(0BGnwz3(8$=;?s>DJC-EKfG=5AkO!F_F(6w7hQK^Di(9 zaMjhQ*l3Q1t)9C);#y~lL!*hSHincNL|3;G^;Md7;ON4ywtwYve};!v<%?(kdRUF={Q{0RCA>WTSJ*?b}yqa1&tdVGr?cNbpQV zZLr@OjrF@YSVn$SuH3&K#m7nN7N`#)g&(Z;^G z^HFsepu9r5{pQ|mNEWeTYEvrk3WV!nu63W)3XEOyO(G}p=q42fgP=xE`KL);e^?MK`(NS?-E1Qr)bD(&>^=Ai; z>JfSkwtpwdtkqI0%RwKg_1-R`dlpG`kGtTUJeKg;g}(!MAY|-zI%sc?Wq- zZxx@aNDbWjJ9~-henFcB5gOSpxo(V$&yoHj;8Ji4VN9D(M*jTb1!6P(&yp8e#hy4;s!pp7I%R(uys4+ZB% zTReZ|-YTJ%Q{fXstEQ_!@SBM`TG$<5CO}y~)R864$wF=sLhzJ)V8Q)wr8hptiXfEy z=LZmpi#2GE&RGsgg#xHjWh_UE?(4`U9b2>QVZTYQ|5lcfD!GK+Wv?6LUkK=dc&YgL z(XEk`>;8t4^6f~LRwC%vz9Q*^&r{{H&GR*BPS#S3kfmj2+<%y9F(ib>LKqy(J5njp zb%OU|c73U2B1TwGGeByO!>J7$-W@4^!(-R6g5@s#T&ta63KkKp9?Rd);1dK-^um4X zJpbStE09`O#Nm(s8*#wa`vwCy?F6?9E_$OlG)2O*BbW_g_ZRY%P+PLew&LBUd&)hv z!+oU%U`45geQC$6a>4c|3I;~T&exwGtg5M$z9y!R4dfxbqEQW2;oTPEnlLml`UuXn zAGt%qW09aJkrcPnw%@Bylj%>^A4Zr9XVu#u`J~>y5NJWl&r%b^m_yM+I%@F!?UG)v z-X6*(zw@}e?+OWFzqVWmOWPCe9JyJUd;71z$(CxWU@Qtf-vXLJo%_TZy?zZk^w^g| z?ONsj%a7_XR>fP2WK;f8Ay)r!nsVA;MT(d2&vvOI zqD5?9l7U#D=!3GMlGBz>n9!?3sZ68PtUN#T zs3R|(_BcF5ZGGjT^Y8yEbcuWxiw>y(XWPbL(hj1dk z$mEp8Y~-K{sNH&=fRg=t)~QU}+|mKk@cY8TlNRlDHVfZE90XEF$J1xO@o&q{CKcfAZ%?V*~-uzJ*?j z_7``6x9dj(Z`rX4`YN42P5*JjhN7*&Ez>`J`rIol)cUTz#)&aHf~SZhlU+GBkX;38 z#2O!V%bp75lER#P;3vNgz)BdnnBRMz*mD{*x^}FU+Su3(6}=QLq|DA&f$JVLHW%d`=nntjg&!~fl1*(qq2iZeO>`#e++loX){ODIOdi8U z#GCjYTCaa)jj^z{5@8M19OL)w)_sy$#2o_EQsv?Q&;zo8t$MT=)GyXDjIgk3vNqQn z!+Zyp+1SjFuoQuAt+tpn@BNK9=->_LXrzB2gMJDEyv-ODWBs?PxLDv7VH5WOQTzhq z4^iAuv6I8hD?}CbE+kMM7*a-v))uOc{4WJfnz*PpW_@y|kfkhL4Fz4Y*&mZXZKoS^ z6eXg@8}P`lOZgPsd0sYW(WpsGns{A+3%FP&bDAcx4vpoN|&U@$7x+ig`t`8C=Ra8_GCDrNi zd1p3YIUF1+$&zpSrL&~u{b&x{oeq<*cVP@s0IHZQPP;F@Es z`uu#e@9R4PI6_^N>bqB3Fv_P+md6aM6JUIYrEazPkjcy{ITMEa4}W9rj$H#G$>icT04CrI2<+&vnH zrZ%IzE(LlJ=!NS% z!pVgn7y9juqMMl9OM}%MUJ_L?Rrw^UQAOtjrLPyyQa)7L_e|R5u@My@|4qB*Q z8GJ?hkCH?#7~$613_243q^SrZkL#EWOq>7bqJ5kIUjUoWm;6U_2U7i865tBieaPID ze**L6(u&ZLib)b`#G{4e>U{ifS?>Zufyt-ESR3082eGUs)a4IumemKbQZF(JAUJmS zX4k=~i(J`*%Liw}ObURyBcR8XTjelI$08ZYZS+U48}c+_|7-Ct5JjRIT*<#&j~ODD z)^Wld#rTh|?Ue;rYwgzVlRNYyj#3TK9t#m(dEiSnsa?dA-o*`V0p+OGE|vQ$vsq7j z_ys)meJwQ@3jUWhAo7{nU&bcbcJ~mMZA4U#d~?|!!DUZ4XH>6)CuXg-PAFD>L*0IK zjKbilM;NAEDPJ9VP?`S8ef|~MNeioqv=d7tKKV62AIlP>xZ9i_N7j z&o1$@#UBxLy|YjoJs{4%;8`zTl7l}e48E)@zNV>w<3_q|N5v?J8wbnfrj+h&GN^}` z2vai3XU9S1i)m?=W&3$>FNu5k08pqvofQqs`}|7yrXlf$LBI_IK+*4INThfR0m2FU z8+`i{-FU$Fdp++zQYzU(;8%7#?I807&SH^8KzM=u=eW{Jer>eBa1_V@{MZD=zG45l z6H4MOv3_OON(Pxw-tC-EbjpVtbPu-VK+$itFVf}s?ufOZSfiG%#R1HkK^mR6gwbOx z=5#%0D32|$*Q75h%++0WrjAb~g+kQ9U()LLQvV(POyy!D*>mi>pzlKAC?{>Bf2ySf}D6%gs%4 zq(Z=(NSg<&_wUZhzZi;%eY@Lf-J`NLN)NG4(ikq5yx}WvB6%yq>OZ-&3!wgQ)cpw& zd?pey0<5s@-p#J}B=HXA7+SgF5lDN;Vr36(!<;^T{PGWsXi>VWed0<=sML2kwOTFv zhEbO<0txA2lS|;kKPK@_vPHwMOtORRXRF2ptu|02cScil<~-QRAv~O+^Zdl6I`?>| zD>3Em?`DTYBCh$Q)CKa89qxk(=n6nf^4I!foCCiNgU)?;P{NV>=Y|EJm8RT3duu5r zjjkOAC!e`W#1mARrN(L6-m4aTdn``xFDr!Ce?G}z&FFRGcYD)(LBNiYFESuti)0Vffq+*YO}>6#hq8B0G1}EoCVKwswO~&#+H*eJRd6)&QU&Q4 zi_4ocqqoP}(p`7!9wY^X2D^gB)~UR7NNv7NHOInI+~g8_a{bqo^T%(m_E-R1m=J^^ zrzB99UQPitX*xlUxJN{~bgIDga97)ZSBO!P-(@vuf>1Z>mGVnenEWtbTTuqn4Pp`x z|F%>is?&&M05^_J2IaOHBIu1dVUOO_nraXqD#&?Yhk?|~>fYpf8r(2q%fdTfB>%NQ zE&*55wj1=itZseOJ6I8aYA>QO&J`o#fUJ=8IAx68u-R4GD8p#3J!v6Ez@S?#lsZj% zV6l?u`(Rh}8zn!SE|6Av_1hY8c+<?`q(XX~dIw0B^_l7{`kwZZ!8dK$d$%>AP z>&J--?Q3iJ+IAKT?lfx8scDE%waiJdt(U2xnb0)8NGaDjJ!JYq1`u-Bv z#1l9HU9TYl&iIk4U0{LT_m@PSu$1xId?Q?qr{!o+$jGHMVCiD{+#8!#J{zS8NX3P) zU4t5JF2N(GE~8?XvcPTkpaM}ySXLfuG|Z&OP1?BOo&J4PKm-A8`?tN*c8o^{80g5z zs_7>$trebJ&Fzegx0|CNp#hMmB{9$j&h)TBz1SIiCGd9R{n7%09`~Fgu^P$vhrI>~ z3mQ<@qobo|eth&+xpJ<^b~3v_kwOX2v&}xuNAxYqy*8ng-q;aBC&rc4HQ?9l&oEzl zAiUBGL^~z}UJMNelDx`^fY%;#iO#3w(z^!uyqeMpuiw51q8h4z=ymGld&?XA4C3S} zzm`*YoZ5IXSVQnuUd|9=b|D3#B`usiJo*Pos;$G+Afnl@$JEn~ZmO%`xxU%l&kD$uNBF#XZp%`DC2zrIZbU3mzZh29- z?(T6 zzQyiDANX4TB&D&YF&o0*I(F0hQjl?rc7vs;HSF}v0I7!%I-Sr7=3p_)OY98 z|9_x>*e4*oHGk&&y)&-`Uid{unwOOSeG^{R_J37--oB_bemmS2b9XO?u}Flz<@l9h zFedcGj^xffH4$qG1TL4Fk`GZd@}U8LL<{$q3A0MHm?{`b0If{W>>ZvGTLwz8fxtk+ zX_4uJ-&-E&IN<7CT-*j%>St8jixV*F7+Q&lj>tH{@ljV{)2J5Rqch4fsGl7!bFTzv zHH8J9PZ45ZoH4Z`M4jrz`tl5J_IKdOyu7@w^rb#{ke7Fw$&A=7M^n~oSJsuABQ{TE z-}HN3*+OzcbJ?VjZxRv89|%&%UYk!k`*5gIEQU=O-Q*9(=0f!g5Y2eH8=!Xdx|f=? z{{4<45WggHc%_(JUw$uLpw5(|=0{{O4fz^?@o9bqg}W+}mQQsJe$Ac@a?p~_t}4mA zV2Nc?@fNYxn8;Rizc2GlM*&ny)lx|lK!I8j!;h8x{CMiR|AXjNX>Jv%`sg@e~i zaM$L#<_r!4b^xd{XgYdY=7AuM*pwkz;wKeSD2|v2yp&PLPK-*!=L6N;&IAo+!^w1R zaR*%X=5GHJT=IPYw_%;%!H*5_Ec+6WQBVZ$;y%JDxL;8b z(j)kTCE;NI0iHO-sw|A+BAk4(UN&cjO?j0gm)HEwPMHUnZ7Y`QIHO)pW@uH8O*+^J zokTO~`zoW#`LCi~=a3Wd@%MIc6i?px;nxDGp80s;=|V<>k1XgC++Noz3(RtHJT2sn zbuOz)UYgt;s8|OiAn-H#)hA~jvlgd)076>h+bWf?b^L@oU zPBYd=G64~KNb^J@&r`D7#1JiLVaxY9I!;+MqU_`~mAl`B2|TgH~DA3dlFdK2nof1o3vpU(~0$ z*zFM_rPjg zAhyKToSVwjncAbB`D4eWY#PkWr-ykMdsQ#H64fb{HFFfa7Oqntat>($B}fp$C3RRM zgNO3=XXPN*BDhbf?(2<*^BcvW&Ib;#1YP}T7}?V zk$rS7E8Z*JfugdQ4Pob$|cX+=I!$SzdKG-2J#v5-k48x^YG z@_ytKMSG{q0fyA8%~pacms8zHhz#iH%|1xrEG)v<1HEHx6K+9|+8LK|5$GI_% z;;K&-#y9FO>LTMy#~L>v7u4#KZ}$1@RfoXa3n3Q$!@T0{$K$d1n?1BZN2hl!Adz$> zBvLWUZXM@GJ>Kth z73cKZD~5ifg_vB+R5LxlTr*W{Z+N?j>A|Z^WDx;XzGl+Wo{?d3|9DnWa$x|iw!oUI z+kZ~6o4S~j>&T??@ffmlKE}!U7w-RvsIM@~{?eqI2-=Baj*FKLr}G#If(OP+P|Rm$ zIgycwn@1fl@(FXesk)_D{147%p)SyHLC(2z8RkC>#jqEslb(wcyzGpYxvT zg`@w=JowSQumzcT*+WiXoP1GW8_Eu}>m%HC1Rn@N3Z&=WMRr06Wm&37Q2Nf#rw#8` z(Gj4Je9BAiP>(w!yy4e!L5H5EP5Q7XOKeg(=nI`IsA3Q2(7F$`cJorGP8{JJ~GHljU~K4mAdxR z$^KKCWQnXc7Wfyh6*Gz-f#PezDtB)Y(gx#ye{53rwL+j8n`|`sl&3REuBkPyH=4wL!nPLsUC4 zL8RgHt85aFEN2sZkQH=u?yf7Tz8)mm3b^+uNBs+K+qcJLi;fo6^Bl7Gp%_+hS+D!M zfs%OlPR@U_l%oKlcJ>?Ey+M-4FYRKu2*vrRBKYe&CUsx4)N1_VAfFkzviFz*Sjv5@ z=gQ@N%7X?*=r$=DgCe77!t%;H=C5|0poJC#7b)tq%)yiSL#AbBJdBi!kc%l6Xa%L? zRb8B_X>AtOv)yfKD1|JI8l5!b+|0**sxx%~oKiA<+?!(3U!KM1mbeYCev61!K$qS?D zxctmcN6+3X-tVz8Y;;8-6DkaO|HG|M})6rby*5Sx#B8#!Jsk2?f z5PDxyvdCyw%NN~F;R;ziiiN>5i%6XXLX2+v&2OaXt(K~)EC&r$uHsIF<2vdcM5Ri< zD7tU+9TmMpZq3;91e*R3Y88$o4MgIR6$nERM3p{$rJk%qBqP`B>zTur3K0oT?9@9Ruvz-Il*BMYt^}zQpK2|*cyhn?+R}~_rh7snysX!m z06qUP4O;SYiXX8P|eYF|j=z#AKzEVhd3w`J zO<(*(z;z9`2YU4xo9-f8KYQsls~6)638KhL8@#|CBDNM_fc6Bjw@}voRIjb*oH^CmsUaQAimq(PND6ZHJC!pt@n&(cc7sWWM%` zEpsE;THPyy*-@cpLcVHC_1HoU=G>ytMY=A-b*UNPSLA)xipYC%t(2;7t3J^6}wk--U-638+0O zUW{3EUO1`}9s(j!UQ&SeL~@tFWsq<-gktMk^JrV!RkhWwkNfugj8j-#o605M;rf6d z`clu6{myX6=vMHGnbjggbKWqa`w&*}96I3@Uneo-#nQkGit=Fa$IqsRp|-BNEcX~& ziLQCym%)e~Xo_+D9kF5tHv5p-xAY1bq$UXq>4Q4YJ>31u0Ig^-blgS1cM7S8hm&aH z^lzYlZ(45K@sd?SF@SB$lJcuEO?hB5-u+}+!N|Hzp!+iO!QE+uNHL5W3&FAT+WZoG z#8W&P{uId60GUcsV+M3Sy$^){mP2mHQ8CI=KqIuplAig94Isq678{(JXb4DV3Bndo z%_%F~1#KWCJMwJk1W|-DciWXCS=1}&Jhdq3a-=xsBQxZK;FFG+!xVY1HZk_U{MfSi5OP_rD9rt?}w5&x4e;lrgD1{okAIpMy%W7lT44iPbRPE_EAtV#Njd#q$Bb-&YJ!C7h{~__<+>M( z?-=at?#A2SnRxUd2lbZz_xHmssXeCI4;{1qj8>7AQsE$Gmm%9kq*THoIBAaNFQ}ZJ zrGAcMg*G#FaHm3rv6ivY>g0sd0DT;~)rwPy%UG_zf0&)-&xIq7kO^mai0~u?A4|{` z(`K>X5n&JVGnxF-4|GK{b?@#KYBd!%#u!SPb*+eyu9|IVZJJ7P)8Z1!UXcuZ&e^4% z*qqcf>T;etIpQVnn_M>pSK%R1uGU<`e8~#^>;jc(n-7#FB@Uuq5JvT6&Z;U;PSBJZ zhvZjX@8}SxK!P~hwy~HQg^40z+Ei;7_SQ`&1#sl*oi>#d*Xrw^*;Dlp-XQ9Edt>5) zOlMKZcYLDRkg`1W{>GtdM}yt;y!aJe(ww4eUQv1CjVxq|yB;&o?_R2XcebkCf^{YE z6ec;B?F9}F%pnS?%q&OSBQboLnF1cy^T-W$i*cbl9Rul6F~{c!Eh0^2e0S6%*?_KC z7GI9WVOb!G&61F$L*&TzNkI5aUJP0i@1)9Tp@I{8pa0vQbfaQJ19}=QB%iiVl)tGE z?A3P}W8#|x^bRmvo)!r;7}f94RwCP55Iv&4A(?LK98FF(nlkvjcnAPHqUc3=_kiObZomjh&#-?OMa5k1Ams1QOUoW7Or z)AfUiFi8#xbO=6BN{-#r3$2pkGIO`}HRlSx)iQ_NCB`g5ocGUo68l;n$AY)z?q^TH zW#In$DN^!)ij3hSUjx2u!rZ~sCsq!|DAnWA@T8h+h@KN!9E^P~WCT$By1tM(Ii6$k z52}B}_=*qKs+34s+)tg|`cK3+4+rFDa2!?lxmZ5W0*yM&n8^_;NfoaY=%{?E z2O3^~j%m~S1!&ND>3BB;E|I8ADmUi@xqQmraxYbO@~d_jUEgr?BX*1^l|zNLf=)MS zlkCR$H|NWU(x&ZlF+o{^7P2pI@hSQ6b$YUM*;D#BI?LJhmpD^FWy5^C=i}bx_g8cl z911lv9#&iG57z}5^nu|o_V}%3{tK(DRQAMqKJ!E>b%7=tl2YF-3xXrsHp5cj&qz%} z)S?9lOcz91lq!7uDRv3qlWN%!6-2V!oJx?<%}kGgzUOCE468}EHxGt^3j|(-4kz+p zxbEJYM#gOdfI#MI%CO-FdhbK;BPxD%ihc-qfDNC!t(?W=$A&>q|6fBOf0_`)OeqCHdPi7Q{J7IK9XDWmk)v3>Z_aBQWK6L#qu0h*M>CuT-p=f*mC z#hHS+lV{ayGN9pdDBY3Ma+je?ufasouk5^byFVQ>?FZ(aCzyGerWk2YibMVXP@T6> zwIx9osbE0GYv5t0JVT%YKcJ8JQyd9=f|xUh5xvM}a8TwKqWN{7KWoSFuw6Kj%j_e$ z)~-Z-fliac6CAw*4C5bCB3|+tRI1prCBeb2)Ov^_(h&te(|%>gAy43|mmhDnAmM~a zC5KW^)3<8vwoDEceGRDixEnqwVUkquT**v6QcGakmE2qRLr@S46D`ju8Wwmuqgv9L zlkakQd^q|B$e$9Qu4}pU_J?)CnA6KRVIZ)#;rtxJamSx6vR#%@=kvmJNr>|+Lj*l~ zWv?W?Fy`&Sf{f6sSLKoUp=F;4(ynS6w+Mj1W&@vm&P56zh}y5F{qNgHQ#}7>(bfC) z?dFI_G(AK?RD|O?dbu=ME6pYp=-hSMU)o8Xhm%}{7or;}Fzs4xrd_|qVj3t^Cio3W zb9|kzLECk59MwkYS@wIygS22Z(Ya@T8z~Qz&Q!yd*M893S5Te>=$@RNRfaRjYO2Y{ zBq;%MxHRMtMxPwuT2oX;{@$=TEECLxdlW;Q##MMozI&d*&Aw!#QI3GVvhtp2zCwa=Z-6ny9jazsB|C7%2)BUX`OHo#YQYQvt@omsLdF@Sq1t_ z((3GcHiJ%@akrHRrq5uBGRL+>Z(}?5sgS0I(s;hR&nV3a=X!4mx2H!E&;e?t&{qP+boWcCSi=j#hDZn zKni*sM^@!rxb>=GTdO;ZYZW8yX(!2VLtqf@^l6ML8JPrEPp{#5hr_>tOMwSt9+ z={Fo{ns9k-nHZ`AomaQESZxj$VnU-n(}wplu}FQ~x>oSnqQ9ERTF+t}p&z`YV|0D% z61bJO5KE1fwXhheEG_dx_PVw=ow>nqRB`nA52lWIBaP7_{XFw*wqfRwdgRNMLXorT z9~@K9`e6(LjMO1nrIm%Dl?soKC2v*%)D(es$<-7wppnqwPD2AL+yx}WHj|!moPH15 zBGpbfX=Dvu%1p!=T(f@$3T3qgE=Ew(vGpGb%LZlH>nQv?)FetKrO-6rVk-EIp%@2V zI^C!oow*oxB8PM)znPp+^Md-GL4*8liII4|x-7#$y?P6*&8uY^dzyUP<}Im_jetrt zs3mb9>Bz3H!1F})@~*^PoEm2vz4MMkM_abpkhV18>(tzobEW6;fykz;#380j;|z<; zYs}j9DF^^^U3!AZzy`twY``G5JtsqcUXffyGO>B&8eZ18aesdp* z1iH;O51d2W*+S9PGi$Z0(pCQ3RdZlSMh4gg?VRPAn*L zirN?`F?{RUTzd;hyhKhX6sPa}Q`q_mQKXdC&XBh1pg4G|UzG9LCr|%MTnkB#RHtGvJVe<<{lP zgrn$;sDi9l1y&);p2SNp-JeQ7R10hT5a0NHm%&sn&6z6qS5k$Gh)nlebFNc!_UN80 z@vg`N7c#Z49ORk2=n0lCO?4ykwH)mwPK{AacebmB10qC!?H6 zcyIW0g{kHC-3&;TH@kF z4Imys8`C?5J^R-05p#?h*P=~6djsz_D`zjBN7VwXk630NeDai(QdAV z1+?+#TNHgV6-qC;oIh)*4cIvZt#i3ob>i1I|6_m#&#E5eE%A2ZqQ~i!*5yRBEYQ4$qHJyd~Bxjp!_@@{CjZ0Tk ziNqUh%|{5uvzGx^p@Gku7F!WThb|xuoDJ|Q@-#uQu%~A8N?9~l^TWAmm&xgEIR#O` z&+y12s)*BTw6MwPSKnqKgtG9q8mNAr^aBA`#?b}dVXH^@lctDlS?$3*4f#4 zf45`E0q2s+5(<-V6FjhN$?M!OV5Vu3t zQ6yRFXHtFHSw*2lQE{7CR; zGo5;L#V6OiEkfnTBAUpTou&Hv=sVTuLcNu#u#rK}?n6?%@DmXou771hi%*m3Z*UgYw{HvL!Cp7h<2>`9FpXV0~P z223(hH~%HpF~&0UZ$u@g_%v3mePe!XRwE|XkM#Um=IyOlDbhxpg#F4&!0d|LO_W7k zUEWd|4JLeUL(IRt9|;{8c98URUyE40a|G4s36ZgS`i01w^7=qGwr^i!KRnZa#vDN0 zXIrwv{bBJyA`KW_kl^5 z|K@Qj*z&L!JAM_zrbRe!s^f?`+r{|Ie1Qgd;;|Z)6oGd?olwC@PjTOX|C>pJ@ie0d zE0zYkfq72+W=?M%4amM&>r1p_2<}ED?i>p3@q0|2-bZ(N$h>-z_kfZ@6x!gWcU-ma zJyekI>Z_}IF^3%I$5yJmwIow8n3F-j`^|yy6JLE$1(qIf&MPuKFxv!6`b4!;`$8dF zgvDPJGYbrMFe!(M-Eusu5R5&YaZ}*znHR?Ke{I(RKNASkk2VjS zH3WRKYid6%Q)p@hlmu1pVq@hI$zNgPma~=#CN97i8Qv>p;T!)7ma`N{E8uSrzB8mGgulPF{<&Y@3u+3m?}Ebp~T>gjR7-et^; zX|%|c$-%u2H&1MzG8i+{XVpYAzQLzaEn3Xm=gxQGTOP(y7iZ`CtNqnk`d(uUkMoGF zes^I$+dQnbUW|F{7T_+!MMnqUk8M2v;WSe6Wu|ZIY6IOMMUXIldtxvJVu%EVD9XKk zZ6J3LI*%1$p!kqGQqKVezEQvGB`Lf+@L6+MOxS7p7tR1{?MDy(--kupp0Q?zmp=uZ z&S&~!-d{|5uYV*HGQRRW`EnuLpJzr?zch#U$ck|9;ymq=vaIRQp8!7G4Bfy=uXtqp z^t#67T(V3zquM4#@OvH8hjCc(=)DkF*T>^V^G&R2+XzyZ7lYR}#CDbIVoWm@vHVlL z0;?~GtES>gi#Dht43X}_sr1;8pph>90@C+T-E&r2pEBRY7Q1!(<^hWSe6|bL)tQYQ zm~_jhtWISB2MLujf(N{9;8npx^W=LREIT>wBwMaW@D9^XCsF>ydKZ^HSo0R8B-=w9 z|1YJY*Ou$LYCdH=_4TUdH9XllA19pF=#yU8Cy+XgFbud2o+%4CksH5Ty93qHH2u^W zjJ{ss+37i&I%S%)iPBkamP&Q%f4Vh6GBq0f0@`>Cbr6PxQLw9H3R=izS=W2tLR?P* z(ifjH#iL23H-bbm{wiv)yG1?zI?#1VsdBJ7=6RgGO|(&qsu4CB#a)yt#kDU`$G3sNNL4>b3-J$Sh6Z-^utiKj(z z@ZhMZ5{xKVG%~pJ@MDd|n&M#&+MUZH)(6=H%T;^&jWx>GyltVI&6-Xh4@s} z8GBBgCg0uXUyhMX?r=kM7d~qh+`D9J){8~w=EC1vjvsW15iGzx8JP8Y?MdGJ+LLvi z#|Ru!^0Rjw>uRo4)oJUeq-lUp5(+Px3n}C`WK?QYh8H8m!n}K$YvJ-FWLrmEOPGjp zC&#)y;rMDUhpEJ__yZX?_QTcO>z=o}zXGI-n6^HzLl?eL9|u?6AE%DsxH0|Ui6@~> zHuQ|+GGQchm)9_Hf3|?IlqEvSo#9EQmVKOmHo{#Fc}g6gGWoVP%-NCLi>kQvqpzB( zs<=-vn!A}^sFG4*(}?|MLiLjb+lKPaT~v3|7AnrR2&7f=lkqzHtWdt}8gME#Z&(zW z(PZ z5YjEBjTWt>8tcW3(QDcqCe7fmte3Ff0gi6Az4~+eZMJw}W!-8EmXsdtUw<|)Dq*16 zZ>BoiFq}a37RxTxz z@h#twiy-H(H#e(OxcTyw%iQcPUp^~pa1g+)>BgaF&cDQdfZ<6nv>A}(@dOzzl5i2y z$hV3Y@@fa}yMU@@FI`j>TgnP!gP&?lU+h>FywR|-V&2ISFOM`MH}zMmsI)4A_du)k zQ?HIuG^=5nEPC3pp#qMm7mJ>PJHwx4&D07ODU0SJF=c#jlNb)7^H_C`%9m}WD7O6e zLnbhtv}!0U)^C3XdeHhtuke3ZPqeFl=kG$Vte>E@gfdNZCs)etbvNS_>wNA-FPQY~ zNl^Tt!4v-?)a`twE6sFH5UQJ3)ezn=mPE21(DW-wlcR{k2j%$a`~LR#nAOT`=(f$9 zMSj`OZ1NB-=q@VDy|?6TKALw5uVUL47g&}uaKy^w)H&gNpQ=59uFUNn5e;Y_EJx1{tS!n)F%0U)_u|c zwEO4|Pqae-W(_bce6UYYI|or&`c|CH@?WS}e+j^*vHhce9=A<}k6tN-!o9-S#iyI&UzBRn z4|-~W0f!WMy;VyYMqk=%1j#)WxejtgiIBHzD>t6LaM(YVqANPJ+;?i%-8Wnt$&q|n z>p<=kgG0F!+n56$gsf8WWxQLo9#XPW--Mi-2W(j%XB0RW{^Au^luA~|$rE6JHFI-8 zVequ2rDwrpci#g?aL>ms5V(31ZxBBpXq)6dRHdA{0~II$QS7lPlh%))_xob*gp-rL zN!=A7COug*V|10SsJ{cnYTkWKQ#(L-6Nr8C!6!+#wJU;Pmx=Ta`x_b3eGetN=R^$m zQ6v2Yv_>mVN=_$g@IDSPGPCCRrqQHp1R_J;VO;0_a>YU z?+%oNujf{sY<=a_~N4rDx4 z)`XSCIufjx43c;Y9L9T;>;z5qdRBkMJF?f?-?(V3sW4u&Wcrl3?3M_fqp%x-REhwxYyp4C|{JFN+OC7z0`FPQ;0O+V{N!hc2$>cj}nKQouRqhwe^jWyYZ?pQa` z{>BRd_kQ&xG^@9Zo&@jjYxzcycr$T|L#ECS*DU?DxK8jHcMki!nixc{&+-UrEKFM^ zhnLLu*SOcaQw~M@sxzm@@<=!Q3W~$#4;CxS@)EuKI+Serm)sPYR7woAEa2NrDf=j! z)mfwtD>l95TXlD!uEjnn5+%l_OE0cTJK2{p1%H=uIM1Y1%j?%_!)@PE9`zNQ-+{j4 zX5WPP+PG|v$ET_AYktzWW7|;8v&=^RuI|)x+k%YusLcSQxJEIKAX(RJ#K`y5yNEMv z0{U*sBFTH=C7Qd1ta8&H{9-#`RWog5JRv;}8T;@$?$?{a)ZD_kS9@!EX}lyS1CM6T zPV`*%eOsQE>zdyh!F(7{w8G~!%RzkI!lcRfizM_k)62_3L%t-mwku4UW8>t`2BeWH zq5?$Cs9G-YVAd1eii&-Le&?I%e|Yik9KG1@F2N^0Y}=Yu2jRDT*shCdImH2*Y(&RC z*<+LzY(_hIyyLiT`f{-r>PdhBo!aS}KoVgWoL-my%`}~P5O4$N%x@z>g9vlIz$dqO zAvr9wMAYvu(ODap`!!Dbc7JPu^?C)NDKwp&Q8uqt*N0Z%PVA3jy)T3}7 zYZ!6<&D5n{VQHk1)c=9x>DoZ~Pj#PFT3NEHA_7Z00jJGhvuGuT8d(HIYlT6p_D*Uk zrpwK*8Bn(BeV2d6Y2qhccm#!sg^8GG@jGvm87MDX}5scut628MDc4B$xr^*&Ek9I1xLAG5^|BCM;=F3 zaJ0sQ^(hTZZgGYtw#Xz+E!B{ynPq{Q+B2cc=^58b!zDLcy0LbRoF4L!5BBi$Q>W8< zleq2^2RH$4W!dV*`N4*<2Fb?D!Yf*1tF4yN>!!;b_uWk{yx>v{r~2i5@iMY!8Z}>D zU4BkPhZ@#w=UqIuZM+u6OC35^@%UZlSQN)`@$HVOo7!PlQyZ{_GzUalVZPFb8M=e9bpLi6X=ob3WvAxsCqjO|Wr{)Q&7#*NWzJze(sP&i%VTjDquUjYFQ zx0Lzyl+mFPlJEI;xrgs3BQ+0u44)-jgzIzx2(ofX-UxUw|J!pM0I&b!LGAG=cNWQ_ zTVPI1x@WT9EikWy_PyJZAhi`b&2Wq;{Z9>)>O7^ zTIv9+C|fB+<~LJm@>>L)ri{1?>_%Q}2?zWQ@4mZHI48kc-@6pI16oG8@Wvaqe~gw` zOnF@LYu=+gCQTY2g)TrV8r8=Wq0`%w<>>{vz16(WV&iy4FUAYy9Ku};3ksfBO&<(q zc)$yeV&|)@UUy)+TT~}IC=07=CY*Dh4EIl_rKAl1vQ|tTr4(nvd$(e8_Ck^}S&VD@ z+|EPhje)NyL@P#TTdTxo*>pl%gSJSaOHsQ}V#GE-YOsjcJKdOG^{MCoVePx) zss7);Kgkv*p87)TVgwcoR8c;M*rY^NgoqAnt-!%GRBQk9a(I<@>{M8K$LWwz~hLzuRTw z3*OCFRb=OPLnW^>vVp_9E&!154D#g}PQ^w_e}OE|6@RXorU#CaX=?d%U9>*0mfRbS zYv?P(8+FF6%@aeP!14exEBKwJ5O!rIxyrp3`!W)lG!EW=L~uyI;x&;tP|mI%H!&_* zopWW!$75=7g1+ljl^^&|fDlF4uCK#CKpm?Mmc9;q)2MT9Uu|VD7Jl>FJvSbI z^oOA;l7Dvuoa#lOAvP>yL15L_!KyisFYd5{Es#(H#eHZjp>A@f0Uz`B+4ibmTV(?``_fV@m8JP8YQb&S31dFGQVO<{57s7;_ID_1A)xF&{M2^Xx6uNNVNu`T{KYFSKD=_hxlVnG$3jlx z&q?^7gM?W3f@_UBP{bd7Wle5OX^ID*H0M9orx*!95&lOkAD%RTS~D>(%;+FF;X|QW zV|p$cn2bFe@q}}yyC=tIBS*x>qT+xrP)?huSCNnV40~dILst!FQ8I&M%ly%^#Gch3 z4=?WKeJ~WDl-^)Y_&tZWvuSqt%LE`2fhofRB7Dpf>Skz(E=8u)d1M2o4QoH zp=Sg>qwq0u@0>{gLrw5W&hILn=N)I8q5?0L)>F*(2Ligs0u%;Zm3<{3HFR7|>#14$ zCfjH`r5tFinH_s;XHj68@)vxXp)?SZZ=^V06h0u6kI@Nxn0I(6Q ziR*686S;vLGH8^KI6oThyOWRj-6?F{pO^Le_D0Lg1H`Zvds6A+T)W~2l;Zo5<7}8T z`oBf#?~0RkEIW|3+k*$MRK)z2mOrrGT{1GxIM1?1O+Z)qkI^M&IiCfje(ie|x&Ju` z;Ifi+3PW)&5_oGG!dhAmu&<1`q*k0#n7w;G2>KFKzzJ$l9hOx3bFRt93K#_HKsxM# z4_F3`>$v?8V8&cbnQ>C{*Sh?n=VO9^o?hVn5v+pZ zMNSF`{m(43T^9y0<@uYyS>U?*llO1a|Dz`?y+B*dM?!_*_-tU2)Cn{wNUAbmJL^r{ zf+1EAg3tIWb-M7(2-&Ot0x8-~vFLGk+PBg%=9#!(JiSl^fc82TrM_x>oqpX}Js!HM zK9IKhyHe>E2^$O)gsZtVL7EwJ!hbP_H5 z)ivs22})-7U>7h#AnG-p)K_te%DYoM$>#@SIKg39_ua->-#=U*>)Mwj@ZUt>wEN#u z3_`l)2Z6S^8o~TEa894-d+<(=$o29|;WcW*8D+oL2KCj{d6mW?98tn{>01Mtt8%}R zaky$oqmHRyvI6vMMIwLKa|rvfYl1GmYJWLSuJmKG%X!XRb`p6?L>HIuE>-a^Ws&8x zsiyT3YjAU}Fr=}=Y``*igbiHh1`Cy^2{yqQCES_sYJdwTfQ)^>dR`2i&C`rbc1RiR zfqb(%HEw>p1^FVOYvZTyhkJh+7byZbS>ZHhDZvRk({( zM+~%wI$p`B$F7>wiF&y{O4_k!-R`=9ra?l1(J0+~zsT>{FLL3J0xju&lvMH^Xg{d( zvd$HB_wK062Db;@G7#3NV`f~Y_6V%&dd-bR$!SNhm!BBeXti zcCbDBxKRJEdcaaA-l=se9Oz`RMszl&HrvdvGPL78xWgPD!wRI5Pn3L z^BP)fTzY2a)v4fgwpC{Sx7v@YF?+r>_~()WRcL@r=2!okjeu+x&M`!)KymJ-jEZB@ zjfsR$z~5YrKDp)Qpz;yA6&i-OduS8>Ys3rorg&M64g8S{d>b2^t2{homV8_$EMWO* zWcR3cxrI#7*Eu)M9kupKIG)MlbjBQrFt<+PH8~^@fT$#hFrPa4Xa_UM5gev=x0}*{ zJfkZM|6c+TvnNxFvCXM5Xqi31X$`e!3&z$xCFU)*+pW5*l?T>u2DRIDS~}U$x{!Ugv0^4%O@;+bjFXMIJ?^vYVZ|9JV79)$C9!V-~~n70y0NF~T}6(vWOe@hrC^ zUW1LyqB{l~n}w}W<(cn8g>C-oZSU~$Ztdz)+tm_t7`vv~aPZ)Aml2EdWnNKJE5J`) zsrc0pyE*Dy8rdzdXPu`~^}}m%q$foZSX#UwC}MP3&MD?WSr!mfg&R%YrZq(6^Z3zz7i%!vG=j)I`%WtULbu#V>p4eTb&+lL; zvCT9mi+a{qhAzD6Lx-f>7|AmaH~W&&JL{Wq@M{lJy7>R1bdfI`2REZHS_UbQTkmW3 zqy$?RX+D`1$$geCgv|(M;FFh^@46{TybItql36EK95Mo`z+l;ogybJnw|s8Y8P3zt z)4_D<^>t4iDIRZpI?-zeIi=!>)!S9H?s*-K>bwSJ!c(n-Dc)IHFs+vW3Vs=JgMDjV zdHivy+IiKjQZK}p5@D^)U2bEyI8d8&uKsb=;SBl}S-sE6jM(~*yXx)O7h!iPOdHik zTlI8|Sr}_wNvl$A{Lx&i2wiEPgR{1;m)AF@j|Wp#lyv$3;s_0lF!s|qcAGRENHO+> z-TVFWZN2%&WY6^-A%XI*b33bBZ;gVk5MH*QQf7V-i@q{dO^v&=^LX=gyWB=b{D4*D zda<{h>C%ihKO{YQC@0p?ld>sxp7I{#ZuD3l`3sPDd&ckJP)UWMnprkH|=zHuU- z?SMr>kZiz(#EZ#0QJE!!)GCRq%5y^-_G8+d`_Hfk&*PZ zUPu5-$3Fj1SSh$$w*p0b82!OtJL${MNH(E0W$O?aGY<%prz)*{~vMoHZ})zw0lVT5_Ew*58buj@}>^QC$XkMrUq z{EnW6j`{ZA+8`4aiB3Xt8IbcvcdBZmImj-zZl-0oM!yIvu(W&f)nmC{j7Y0Psk{*x z%n9F}>n*YQq9nf1_tOmI;Jz-!yrv*VT-a6mdcb?3`y~hD`tt!k!8YR?4&&U>c}01D zS8{~vf&H7YoxVj1Yrv$>d}iN%ixOR&NjAlK+r|^TSEPvCv32cX&Uh_)U92fNyFIp) z8^F=I@ucA7wzkF9o}xz$OysKxH@R-Cm7BwL)?3F-#xcPOafKbC)b(X53RZ^2CChud~tiJzucsyb8$$d5B5lJ6h(-3eM23qSJHUJ{+v*RC`#VTy)gRn z*Rl4Bj)H#DLH!ps3eDe8F$wBPbbF@8^>;%8=-PSuq>{rLz)|idi4K5}>#N!Ak!&e)(mUeb}6*wwo7#Pex--Tvo|0jk0p`vR+S0dHk+R#Jto-W+QH zkd9B3Za;&lKV}H#EaVljOa|3^J@EH5zY952Sr%#ntjJpVt) zHb~|bkX8*EBGNY(m~`vKsK;q>`;$fBUj)Hvs6L&(v(Co_EnW5$l`Qnb(V9@+gxaA@ zl%WBH;W$6Y#TjqYC1NW8s8;DOYV_SgRH@#D(^-^M&YnZeUn$d%4<1-*v)Hbp^X_a% zMWq_a8I0~&^$U^@TeEa6W^0f1^-T~XhA*~ya;(KNSo)BHFtI>nO&b7;d6T|hH#M}h zKdVzQ~}#;Z+)3qEEbX_8;>mSrk0yhO#d!6)YpHcLTg zzOUS+<-6lp(H$k7IDLl&9cyU~9UkMY@wznEB@MrByxzI=YBIB4RAnSZie&JTvU(ia zn0>>tJ4Qp(q|K}>GsDxN(s&$IZoQd(gT|rG>U9Ti0_(WSOwwp7MnGk;g&)mt#7Dy{ zIaV|OI%=*3qrlMjE^xhn#`$}XrS6>itvO*@#HUAUNzWSxn>-X7_(aPK=e^e>tQ+(S zlP=qvNLvs6R3Mj>@}BJ3sU@5ZsEnIs&mxm3qm*WJDwy2aKbqv1>E1K$aS`%MVeOyw z&bFL1wm9;NSI;UuAwWK-2%s2vi;DZQLDN?9MVW&u=f!+widzH5a?Ho7MSXdBxgB7I z!BnpviMr*41Nxwq&48mL`^gOSNXT}U$(^HNb4Q7g$#B0M{uBru$!%LU>};iOXXhEJ zh3%``R~$TzD9xB}mm;cFU0V#zYB&41w1>qaZhPE0kei>#v266|pB2l2@f&;}t9W+I zrfIT5xVXFYh(@``{cN)B_M2mdUXdrtQvSMU;rcVmVgbgvl@+f~38sfEMrYwGdc4LJ zO2@xjPFfd4Y|WGS4%%9TB3$7XxVW{k8Sm9O#C3x&`@B^9UCXSnzh?mmn&j7vy*(BR zh3#Q&;47+kRZ3}QU+(2xv9#q@oEBNjqOLCk&5pxK$1zq1hc?#sc_HO?k4#e&I2Khn zP}=#WXH(J32l0)120Bv(*fKZAQW|{1*8Qy;l+GeO$E8NiUg*K%S{_7oS}t`9>4r_g zQ|l`ZrrL4ZBn7Uj?y^;CtMlV1eP>nq!mpy_ktkj=hIcTc-lF&}gJ!t(z{9~uqt%Sd z(nHX=hizB7AhTfBWuqzqGmd!sAf@k5qeyJ9D59=QjWC*{dsk44Ly?f!V`G$Q3 zY1QWF+l$p(Slb!2_bO+Ov1|62YpY0!>-Q(EU9C}p5mJU~!e??S;hsjuzMFb3@bEI5 zas5JJ?ZOJWE4z1MnHzqm)`|V*YvqF!vS*XYot@X2_2)4N2owI44zc$FJdNJ%7w=SaV>vgU{s+R@f449`WOX_s-vnL&SH!xXiU_3PhTR#R>m%E=}i~sUi9* zc57Ao=JEQX`F^)%r$ie60i$P8-C;|^bc3VM<0IapQTuv(KXLEPFW71WnggxC!Q9EC zs{+!8uR9K0)B}Cr)GVMGOQkI7&!pk;CWU9ZG4{Zf(&u2QL#{uj{`WiS~{#MDPa{SB1 zb9cbIU}FcfUUkX+9OJv*ccWpHQhYLqtrU{9LXuxx4MmOn!@^dpMyL%s;@ZRM4ZM*c z$)TT9KIWgbX@YIPZdsgvDYDjo?(S^Vsh-{AmPnR$=1nj66=GHu0;@ccs714w1!OKSg85iF4`z~ztxWEtQi*nlJAB5M_f zupKeP^*oo?gp*`yPAPw1eyI9}WNvI@-86apbxFoT&!p()5~CdrY_bd}jrTe?K;*=~A&r0};)mm#+kshP2x>{4UY zdNDj9iH&_fIA8?V8Pi}_T^!v%C{8X?schx)kJm~d$KSsf;+N7%tv`9n!<*F~Usf{` zFzk{tHTYmIg*Al1BgnNp>PEXsjeW5 zn$ya`q+dg%<%(~g_{8I{wI)Yii@N!Q7B6DK-nCOV53vQi$#v9{GNCevbCDRT{^^Y!lbT5c_k8gLJ4 zw}hIvqsnjje$I6sdT_U3QGa|fv6SB>`~B+gP`AYb({jfPKP(scXY2j;IoOlEhaNyo zV{Yl?*Z4TdR*dIMf`~noR$uWkG z=k7Q9fWOvUrE%kjPh zN#(30$n0k5FnLcHo(Ja_kBBnpIJ)(5#Yi;7?DY*49s?Ac%j zZa$SY%P-g&LEt+I@~lCXC8x^Xhur7^?spN-n1{Z1D&oQER`PF7jINfHCcDiLz@{`C zCgGk-dOdl*HAX(odz>1`D%!h*;Y_miY#q1lVpckdy@z)-r@KIj4-xb5ut{oq7yPyJ4{LF3Bs*~)5P!O0{q`>C$c;RuRnMmH=| z505&OQ7$^K8}3&ymfdZ1qtF0pDIFU$Wd<&0d71VcjtBSaGUBZ~+L*lDfy(HLe6zgq zGvuB+&beIYInS^gYVl50JF4WMG(a&-RU?<*HY+wy_+_Sl-qg)!B%=uaPqpy~n zaSZJ)shEF=W3>XO-(j`(o+95;Ck0~u_w$qrA?o_C`bdX{{rgmF`@dVGuUF{KmVb<~~7K0Dc=~6rMqs)m5hU&Mpv&-cA5=%rhirrCG4;Kxo%tlrmB}IQt zFw(UF7Bfohjm0O z^t>N1Prt>o6mSgTGzor)N4jSTvJtsm>uO$6z$-+8u0ff;-+TUkKt#~*flq5*oPJcE z!tTR3kQm!G!W14S)YrhmU?ri0aUG3=+4Xt%0-e8C=#e>_9R=^hlC~VK=;j-yag@>w z)Gk`y*b-VXuIQi9D{<00syD{-GTzS$hte1m$xE=NkQ9?uC0wp%`6zG&@CdF;!j&NU zoi#p_`X6*Wlr0yuE>GaWi-K|;r#WALS4byUcv^Nds1++gh0MlDs@DT!zhiCNHZd7@ zJGk}YWYU#m`shSeCYzN^}d6X3OsHJN05*o z@pg>jBeb)BI<~cAFf)g4K&CGfwpdZ6_t>v=3=3{2k1RZ+S=k~m zdB|emI12wdW<>8xidh~XJZ2Y=-gNyo&#P&_@^E-(EiluvVdBo<5AR!Z5qi!WEw<+M zxv-guH_|`viLdwlWDwdB@vyS`^}$E53l=Q&XLzFwg#87|8F#)h zvhvu5-~MiQT_m=ELVDXteDBcY%w4!<#R&eVV1qf}LL!s>ma|d=pRc?DT4qgMbJJt| zS`xMW_mzPWzlFk(nV3rH^C+0dK?9RhVeKG)Vxvw%?n{r>rI<+=_Sq~UW8ITHw!mIA*G9C@PFu!RwOH$tH_PwXEe-8r=zWbr6txE zNx#nQ&*`wlPvD-~5_^UcoB_N|y0AeUDvuJ8Ah$B0OE%IUgxb`W$@{vaaTCOzZ3ETE zBxrcIjrF+Tjp+&t`#5dA&eVk9Rf$TWIEa;3p3c)05WXnl{Oh+u z+pD?Ml@Et|qs)RU0gncLSNkzvuU)CkaX}R`qWw`IgK`#bKrwc|uzHR&M^bXlZ6>H# z&}nYDU_+GvBYe2Im+lltu!JJ>KJz=4yR40t4h_1S2jSR|7JsfNllpwjNgbqi$_`2{KE)5 zJ$`d3KRaOX9EmY|{?8f#i8$tl{ee8rEFWH@q9J^&FPOHaUv?gKqx>X_sMhi6wy9BV zTEp?3)S5-s{7)&cqTZ=U7(?$W!Fa`S`(M>&W7@FTTZ{L`X$0Q0+Doi_>RF%LoHTD9 zKe5@*MMcl9%tig)X{(c&t5GLdTCF-@>6uDE@+CG7Zr3jHA}4N3nC)Z)8$Hq1A8ig= z2{!Ul3lw#a<*9#Ym1no?vgx%uzi zC0L;0_?2*<43&dpODelxEFKM;FR9LXtsHYr2R?8#@JD*XJyrC}>}X-U08i5GS1+zs z3X7v9;m>p>+?L18=ntL%YJsk7f4>3VW`I_S9u~3~xl6paK;iy{XHC1E+k+>P@#``ULg|5F@{-X+9+$@__3t)ujw5|iNj)P+3m`SJ&F z+Nb>lCG!IYx^KD@caBzjvkfBV`_U0&Yu}d7XcyH51#eo^ej1(~wc{{dZRlsJ*zo!( zZ(1Om?80Vc*&VnOTUv;{VX@0M?2jhhc-p3(#H}AAbA0=;lRm3c<=LG$A3|o9YR_f7 zLIUQif@{==yaz+=BO=thUVcADv-PK_U>+XzyG5=`KbGeLjt~d2jYz(zRB+L;F5_23 zAf`ul8VVs$lSPmC>38Dy9>3-m|Gp+=-Y{+}bLFb@g%#Z0SLE_HBf|7q%(vO;>A{#q zu`YZDFYU0UZ7%(MXn%p_(ZhaD{Vkg#LMhUEYi%L&=QBxd@Q?jPa+8m|MFFR#3^Qxb z>)*2dmTy}wa*{o317}||R~GID)mANIT+}p@Vs=JaSLJ6T2Kn;Soqc}%KpQLEk;Dfn zXn_gBzv(Do60fR5{S~^$KZQMoA`TyLaBv8Y9~M_Co-~bOB6RSv4}jH@KwUP4vco?h zQ!4#tGujtQG3`UaT!;f^6tCgQp-9Q{JH|{K)7aOyO@a!p8}Jb^N$Ca~Kg-S`cu?6m z_+u}OE(jLDCF* z;F3vzI=dWxTDnm!U}&@R#tBL)>c(DsQqj5$hM!r?Up;De@RQ~A+f1O4f5Yfo!f37BM!s?g-TcETR1H!>{^D+lQF~jd*>fOYfS@#^OH}!3;YLh~hn)oFJ-jT){Kbz|po6ZVJD;@#M+4{PHZnagfx7zaA z=df(|RbaAPu5B1|S;nW@-xtjT(9orcAj53H(XS=$Z0K~Nx1+lUWA8DSqt7+<@vX@I zW@@U!SbL8N%pE`dj|=ND-KBCWAG>!zHE!d`0#=X~fjuv7@;R40^+GpQC-rX0+&k9l z+-#*OE=OOMNuS>`i>?5wC9L)N07^+3->PzO5Y*WC?ChOPAJeZCW&hr(b&N(;jDMBl z=sv*5+}6x8Yr02(X;W$RTB+N^=6DEPzt*DOS}k-92YWl9Qo#J08vTjGUj}>o&GRL$ zp1Gxw9!4G&(+iFbr8vrKSB>S?ZZAp`WMnh6(=>9=<2FSbft8$GSDY^^q6>UF53YR%_(@#CwF8l`C#B zDn~0^^TX)MvHj0dtRa6|LjM51yVC55F(9ZC1q{UWLa#u{3hmP^`I6>42e)>(p5%aDj}`IiKtmcMzu2X^xydnP%2D^^ z(a^ql2G@$u%u`M3f_fKJZpz=xE-0Q5qTP9Df2}-sd&L%na@GMsItDp~n4g52{&w#U zO3F_G;hGGjsD+Q^$H1KK+Vwy6of?w_O*d zN}f&NEh6f85Jo~joD*bh)iU}kG5c#mp0g9pnNOBSK5!#^PANy5yRbR9E#A$8&jRN+ ze$%fe6ieAu3lCIcb_B#7g0u<@xB|CFwE62+NSgnFWxW>T+Y`^pG;wBWoy0l+B17hj> znekdb-}x6)pr%DT*3fq1fNZX-@>6l8^wUZ!mK#`9cJ}=)CPb(mNJvs#5!6+y|J9*b z`_4y*^s2;KmL|Ow9$ahB=BxZ{56;xaJx{}fqcMBch_$^4Y=*4@t^45_Y^9FQ#-s+X zFl33Jrx8Sh#IRHU5`p*=(su?)$Beyghnhw+`(EEujq*4I#1L7z&j>IJX@>O2`bAfp z@f#Pq6PHTu25OZ`==Tn8fb0M9JxXoaHdE8&;Rq^~^|8S21O2w`>ezd7H)8O zkBa5psBp#F*DnBPaPhrT8g6~3UxF^b^qAt=)Vvk;7v4Nn9cZNU<|%|?d!Sf&;*Dgl z0ZYnRIJaLf$6yO{Z?>}W9sh#sJVLCoqRy)g%t%rDQQ?{!i{lN|kM(^X9>&$J54pe& zh`6NfU$8AD86-}+etRD69kzWunC7j0ygHAh3wqThZufy&tJe9GE^XC@`#{D-+HTzK zlc^Z2cd4)QMutZXGjp$R>>eKl`SYqui8igP(qsS zut*HG=C;A%S4kmI{q*g*v0V2cC;WH`Oip%buxCkF(xMkcSKTjNgY2Jz361LT+ zH5u+OfsAOUg!`D*!?|v|G`P-Bh`bB;DRNh>Hgv~UYv?^P33!?^y{7plD#e}o>awI; zSt74dm9BnbapmgJN6Z(maE`SsM#Gz%2tCZRTXR@B`Hk=SqUQ4>kq<%wuxZBAPlI*Q51OVnL*F6j{jZv{6>O+$0n|Fh81O6`mc z0mh+f(NnL?HOuhX*Y73vJ)E7bZKXIaJ^CsjuLsl3hcztntcDE~s4ijiG)ieKlKc+x zrSeY-Dj#%nYno#nsQ3FpSM))_35~q9aM_G+z=V*k$(#7@)Tb2_<9QsF;k8_I(ZE6G zrwc02^5!XY*&DFe*LsoJ&(kgFkI?%I7_fWncLxxw?iC?aNy*m;Yqa&H6FZs;-m_3Yd+Vh+|4QZaa)O9^EdoKuJ&uxAmzJMhKP%N8IHc^(z z@p`dM_@hCT*G5kBCXS9u8jEtNES#meFRGYJl00A03QJOErZVoC()`dkSa0fBDtE_z zbVmQ99Qn;y6t9TKz)AG0;1+%c?`o>-5{Wmx$P>&S=C;ocJLv7>(^n*-62_j#j;uS+ z9Zpe&==w9w1r0w1wXt=C?js(8@2?8ElFQ9!f3IWBNn=rnXHl;cLosBZ5daB+^;ECb z*yq{BgqShyBH<#WPNd_&hwx$p+W0@m`Ia{ zulRnGcHZu%c~KMV(#Z?T7?En$c_0CmIEAcVu4rfRMnWb3Aoxm@2Y~`w+k8gQ>rgZ9 zdpO`_j3g@^FRL@1GFyiJW&mdnnztTuY_YO#sH?z=lzA-q3=;Tw+7<#I?=9P8D$Hd! zDS)CzGNeWOLiyH_`>K{bLB}T&FqnrgE8l1*q4-k#%@-peTF5%l+H0<3;Ihhb87`{Oaw6p3g zlud)O%^)hU0+>Obr`dRH*B z=cqt|=B?eG++OqO-20--2WcshZ=_}KYK+>6HIgo#bJEr;Wj2N=i=$TL`p@ZAm0jCJ z5(!kXfZoPx<(F*OmvWGn{gE6orv?U&VD_eYJby=7nth2LaZ2)bpmXIJ@qJe;slUH^ z^rKcdmsA0xb})n zT1ParTGOPMPms~itjE{}7KRgGHknWsIX(~vsP^qqx9u}0<&6ocg|eUK^rk(1*&kbV znEZ^De3LJ(F=;8FE`I258q!b))t>0;?ER@R&n+2sqHTd_JgN_7r&Vh2s_{YNPGYT7 z!kF`<^5vhWDWAQA^6ClA+ce+=6|-fXp5-Kw$Wtfg8T=j-HWaqK^Cmuht81WFE2d3I zJIl2+&n97UMQ&NDHh!@>JfcZ?%66EW>M9S4(!n~GA2mSjKqfjrTCYD+Mj;g^Ge`8s zJ4{;p$?;w$@O(q%f#PA#pAUt~NVPG`?%SP-E@^|-dk|ibL1AENmPPMY!BG$dsj;p+LzS~wVCs%xaCo&$M#6L z&zougTHlY{ie1cdo#D1d4tC|0%tGCSm0kFgW$8Lud#dzCcYRLW7W?n&mpf4R_lYKw zK5bWhslsDcF4A98W}^>-9SI1?_ghnm^~X=v=$AY$&Rr=Vd0ktpom=o~R@GR8jJ$Cs zap$=&<#ZYGA;Ei2M^PF#UY?GfCcx%mv+e2UzYW(Bu`jWY2~{zHk`DrmYYj2J&83H0abm)$mK8|XV3eL+|){B34pAfEIwnIUy! zhh?2Eb)z#KiMl6=Drz#)Jix-A(F(L)!Ncw8Bzm0^gTJ}<`Z@QU*D*cWyvWc-I@qv1 zZ@x6bU$qJ~?LW`)7x6CK@8|+p)YN2Zc)BJ$p z!C4>P!sU(ra1&6AKL>G~!-ch1(4q2P?S+gD8`Q&c=CY~}XnJbbX>T<m*e#tyk9%#~+h1DPw3ABD&rKV|rJU3~AoJu)bfEAg#p{Z~Y-R%l4dpwNi79 znRx}ZT?x@(-<2{|r;~kJRVfJ_Js;ahr4xLe!*z*mP=Vum_)#0PnY?dx4UNjR$u=Dn z|IoRPwY!-%*Y}uxt^fJe_gr9;Z^hoZ;K9!@L@~d8`}UR?HT7?6Rf_wUBtapW?9a=m zM~K^II_Qm}=3uB~jbw!qRc7xKpi=xP&{s6`>9JS_5SV$NvydL{l+RsKG@Hur2&&JH z|Awr8G}9h8g{vNaCk|dF5+czJm8G43iIjC};g0hykN1?Jngtah#92Tyqup4(p4R&SB{~c2H>7kW_vc7gX`dRM2kwuJ zc^_Z19Y}05Q&v^DlLTrZ-f4MI>GBYhe)DAX76i?Xs|3o>J(COU1Ngw(;T2zZrq#)#}U&xrs+Vv4kaO73<+B?Pkj z^4s}mV5-baLTK=*LCDOKWjhNem5W7}Aq;L<0cn69A;8x{BMBUQrp#@_IbYNAFO!Yz zIoHQuf~<(_JLnx@ahA%%iTh=52|w&7%IY2^G#3BrX#@3AH6<8%wKuuM1EosN0fO2< zB}Q^!tNL|p2ExfRZoNuIQmR>n4`W*b4s`8i^%fFy4!rWf*%#2M^p=wr2F<31)K3<_ zyRToK7bTzVoLGNhw}c~l1N3YP&OyCzVhkc?ij7Y-LH}eT_Pf3YwENhkkbjGz%&MT> z`<=bvga03c0UASq4j`&NsIL(z&_d z^mH~_5wF)S3=W{p?G|exmnyJa*bkoP|1tJ|;mJ*ffxIrUFt7mm*BJ{TIUPg&?%i(Y zud39Q5q|-?{{$fGaxs zVzW}a`hmGixANR{PW7I7c)Y?Puir4w$siLn(7W$*D8#<#7QoW>}FF1}m@>w!8G~_BhX^SrU zb0uW&6I;cWGDP@o*%mDBh>OFv1n4beQu|@lHxA+lKZDjvr2=&A4(|pD$vxU}wk0uW z!kR_K+{aV<0L5Fh*Ff(GGC17<^xZ`<0F84Dc=V_3TVU?LOqc=Mi0ik5_A*5e0rJ@{ z0~We%wqG+zl@wGE?7*r)715=${BfW%Q*MN&ET%AJq+QftJBm*%OT%KM{tWGnQ}w(_ zT#(-py{BA~r7&k>Jt&GOGsh*1;%06atJ+3k#`BX+-7i8-|G&R(Q$h9ob)vP&01z4t6=XhjC3pmYh$lsGL#b%+l&f%Py{*U}ul|0Z65gVi zS!=TVu!cQ1!aU)ePR=Kggm`Gb96~o2O+8#<A;RpG_gtK8en{H}5|9 zTeHJOk1{`Lo#|D2yeNjRb{l|5FZY|)H!CtBwO|qhtfbyX<_)miPk%k7J$)GTME_f) z5K&v7`I+|nqr1Vk4(2hSy=-Q%I_qHM#_?yUQ5{rXLu^c!E!WCb|55&oFp&zY-g+=G=+r8Pgo8xIEuqu2fRmR6gxh2VEM2 zvfszR6*@GElHjd~(|Zp2=oTxeNmP(7rj>o_E)avXp;k78euDrvOHKH)9Rqc6 zk7k)aIXypJ@X62EaYt(w3baK<&w3UL#sJAELy)KP0&Si(3>v(Y^O`O#wY6QG*9A=| zq)oz`T-r(jspugQG!Vp&7a44bBSy3H^@F#9hpPE#@Dc`RmLHR3|c_e{nbT zV+~X?O|9o&3E7^B@Kw2D*gH$cr(Rfsq?2J;C?Y8=J}{Canot>lGO`Dd+wl8!dr8=lS!dDp<=qTJS+4XC)$VRu+n>1fjwuGcqt_L{LCK=K~PoC8I0B|Hfid z%HT6GhA_?-vSZ-AU&5o`Ko+wMnsFhj{6+RBFX%=X{2ER_vvgNIE-{g=`qzHG*__nL zWlN5~@rn;5Ds78AZ`GUHn5A%Tcm+NhcOI{xd@`~2{y(cZPYeNuw@N?fP7R_kbkXjI zeR=q=Gr<~z1&;`@QpvKs2co&yraT9pC94FP_*Qe1hY!xL-JMcSz-4H38;rY{+=`cc zrzRdm{)FZi0UgZ@k#!-&X#Hw$oy3;~B;32_rWB{Q?j$Q17vSOyBCCP2O+PZ<~f|JB;z{W@LISmAe|tX;WF3haT{WG z0s;_o@qaGs4@g?Z(VMtIC^-E-uLhoT>;0|~FqbmQUZJN0h{kZW?SC!+KHe*0#JH$Q zolpQYUXOcCdx^Ak&)T6Y;QyNJ2h3pWrw6CHj9~vZ=8rBZ=@&HDfAv$(>XY$Vm0w}F zfdYT|KZCMGmDi5`)nj2mU+3RU*i8cq^3(Ks4aQ99ymcDG4VL<|pi4jubs5$#z`)#z zdaHR#NXc~-EO6ssK>r8NMZS^!2M}rxi;z@?p}Aw<)1-0`G5LH*i+fpps_o}vAUV0N zX{t`;_u)7Er;h!G;%0T#{eKJdtiWs;9ZiYf^ z^2gZWR>dL#L4kC70uR>bvU!lY_s5zTLc0G0vL0P=shS3ml+Y&!S%(|5wY*C{6p|0O zZ=1oP83NNZmlV~)lKYJD@8gESJ(tBJ&|qE5>dk1=!J|ni#OWI^*jn9%e_RlLG^Ag8 zST@KP4yb#kIUEm-4#>-6_VB&E)mjmt5L3lI^a| zXnI{lep_-K@h(Uexg0HIbWb<^@R;7Q?oG*sBbu~JdYSxO-VC2wb};fq1N7gyJjY-Rpxpyqp@vu)`Wj3oXzzC4mF7rVw#3@K`1W|M1f8{>&)zOh1=#BrvH;z#mRiUv3f4 z;E%Q^T8@06+-7+C(ZuH!K|vS=cU9Y}arkABwa5X<6vYSDbqH+w|A>NvN*^0md{m^p zej15S@3PNix~T8;)7_*!75tM6P};rgC$O@*z1M~BpaTN%pjef+_jGE+8GIHXc0L#UQt^uw802nv{``i6sj08AOZJulQT8w9e+kW6GYhCxy|Oy? zP^&B)P(&~PzmXzfHz_(1#kd01dw;C!zg!zmkp=6&H+4CI@y*=O{MG*oQe>yXcEAw` z1LKwdfQcV^5C)P-yScdr6R=92vgstJ!nfuHpBzr}zexM;c&hjR@!N@;(NI=}XsOH+ zDkJB%6Ol5qH4xb|vs1}U870Zq&>#-kTZE92>@7+5-s|^#pXHpRy1&ok`^SCU)A2s9 z*Yow<>-BtlKf~{g%yr%-v>EpgoC^Pl%-#`^`ajG}2S!H9066hGwGWtUPZG~++>itq z@cf+ZC}j5BYHgw+X9k|O8fANnzp@yI`L z+XfzmZ$_%12=Y5$o2P~Kf1i^J(*v=Hb9-y6f!ZjCbJxK=4W{T%sg@gX+hXK^e17+$ z%7TI6F`tot1oW1@BXU13rS2yM7oh)7!zUdE6AyH{xre@g@*{?w`=OVX#I#AkQ-a%d z4)GxZy*O}tRw5VP0tGkbkyL9XepsLP|EYy(wNrIf)u(rx_D5Lf^99T_D~z=F4$9gn zL#tSAjO7;_)hUyEM0oZg11au#2jSs41@LSZWIMV9k0wGUHlIJh1MMZ1&-@_0zg>Wj z&vIfuwxcU`TF?3E(ck3Ee7rDN=<@jV?@@)dJoWDk&wx)2wI^)ZimpRA_D1u3rLEjE zBxcuw+9UAEA2sSu@V`z$e0H4ggd@rL5sI_y)6R%hv>Gs9%nb`2YZMlhV3x0hQtpm_ zMmLfkUP1E!y$z+4v+d?hR}kK51Hk}9dWjPZr{#ILR_Z8oL|w`CJ8(+dCDgjVT1&j% zXn+0HR%sYNRbp0>=Y*G-e=qXDWRLT&{Z3u0H!_adJhi!^Rfqc(>*%&h$lgLWV%G}0 zI_6>GEC7^F=u^X`q8<~H=eIzQ)zM;vy%nDY%JT_5fdSBE5ulEkW-LXwwL&*V>Lxq9 z?}zt$qd_Ob5T|vR&xmWasrR!0LNB=HBb@yAY`_i#rE==rKXBFbo9K6_Znmk)>9uf! zCDJKBZKMe?Bv(RVjn5iZ`lN<9Q6wPvUIgjT4{#@Cwf$C8Fy|<@^GkY!ZU@2;w{*MN zAT0$EZ_yEvHGoeZ6r5na?w2Z6P$W)?!E%4&rR#50Hxmrzs)oW0pUy-LK7ae#`4c~0 zNroaS^U`zVZ4de|IP3cD#?*=hm?)@oa-BN7LF?^`j~N#5NL#i z(0ca_@rZyk@-Xd!Z!B(&Rjk1XuRA4r+6y7h!BG}6{)J$ceQ+fW6T!vTM2zYj7^jl3 z-r{$;pja5pjxpk&ue`{kh0?hSO_}+3vihPc?qvS`w1S-je-^^1TrSedfMY;_n1e59 za+)9A;r0q9kXKk#1jCns=fez^%-EGc1lbxEKklQiAio%Wj$9gOLm1jc?f4HSPx!N8 zRSM25{e>A@e+sU|c_?-AJGZDlK<3<>Iu1|1P2qb$ny3nI8-lb(jm=c-{$P%UuuRD> zrX!zbX*BPYUKcn1OIV;@VD-gMww9G&TX%|&hwS(qrAsjJ-z=gIM(L||)B zfJNqOH1k~?II^?ru$nH_7bjZ@`@gAe~^dPkX<; zske@6y)9=QhwYV#g19nkI8--p=(T?^zIoocrE~Fb0~nnW+fad!1;zgmeTII{q5pL7 z;6#~U`&Ue&#k->bCuHG~2NJ?SCCo;rdMvW+o=0nre2!VMc&^g%HV%z1Pls2dbo|nt z#>Xe_WVH;43f@ykj;VW(Ms_iabxM8P75|-tLo?C&8YkT;st$VbF)KTZqsDB|<8nwh zDpfP4uSkyfym+W_?4^-&MXG7#HZN=G1me)w+W1qejnh?xhq<&uiB!9Zo@IFach-!f zs{8lXPoB)_&6L*a^&$WH~>FYDc5|pEQE_XW-zkgw7806Tp zCNIxwYD|&xXgtk-d^7v&!$fEj@-_v%7UV(t@Yh!wiEHqqo@E! zcQ7h*h0ti)lQT{Zl{`|OC*!WO@UR|u@~2w-rGS9}aRT=Mv<)ZWP~-j>uh{5Ozrerx zhLoAZeCb6)0Lrg|_vqpDx<#}!SxIfiPV6V!$5&fW=5KAS@di)Az6HbvIYzd%h+g zV2TE#kh=P7fg!&dKV^0zCeiWqO<9vt@SnihC~e50r22CoIVf^)Beu0CWXEUm$I|Od z4vI`&{B%VGn3%^Ch0?jEsVs&7ZVI?ab}2bpqwNo2!4|lYHBC{jyZ7?UBXD`lsLD6 z4_Q+gJ}b-rxbv9qf(s8!*xtz)wHim_AsJKz3`96Gl7!Pn35=`5rd;DbDwp#feP7~# zf(Q>fTpL9Yu#QfW^q-vvdKvBID%L+LWVcoOLvqGRkK=Y{*UB&)7!|hL{8NY~OM>Bp zuqR2pfLH0{4!f)`ShU|PT+^B^u|sAv;71mgkrf*QC0VfrklG(t78xuA^lnJGY*m-? z!gvM~3Mvd)MO~?_VW{(<8TSzn7766+K1!I9#i>0_**A8n_(LGlMh-Kw*tp`mfz2qL z>KlGsAv9U<>zRjBUle+|%keT7N>npm7#K;s+EE;PwS&J}=M!XnNm}&tZpGAeh}#hv zu9$#@OcvSxLf=GlfZj}Of6jl1;jzognZPw%dc7?n!(s$}3r~cjs5{Po#;KG@<~MUn z%;`?bU0sGaY$N=bN;`19j(c6Kuft2VO5nsb87LjGwo(hDP9Ys^|NF)qPEGA?OH$^S zgRKpH%(Vz~_4izZoR|(2H#fi;K2HOJY*wl~rxZTIQDL{H&w2X!7CbXoyhlx%%H451 zpgmxD$sPMdOO|;TvN<0O-(o?t(_%7TneA@;qsZ3*w0#3&@g+(!a3k@yxsX5O(Vdb` zT$|=TuhkK=XH&>LIJ0AiOnwbste+noJ4ND)n>pLyw~40?zFu#56of!r(|RW}4-sL8 z#y2@qOWF(EO1|j!W`*dL&UhWHA(HZZxV0Vj2U1A-o5gg zjBn6_sEBmOowdV*0*Qt~bD9{eo5mr_02l^iw6^vorZde{E)kIFjeO34 zcH;D*AhaYs(Cn*GlpnAV-tHx&=M3FjWT&sW{*z8)a^L|$i=e~DY5E4L9gOu05LQr> z!Bl2j_(nB!M!6)#5RJRx$rb62Vy4x!S(^<8%dF^vIbB#xe}v_4zmv7iZS4|LkJHyR z8=t#=c3VXf!anI^47as_QQUMcbZRg%ec)R7(BKnouscS9-;g&!mSd`k4g-o z&cVbJ3(^h{aAiFT$%5hS#M4zXNNLrtAF$~Yk}S=Re{Atta%$v$dB?s0IbOt8d3E*D zIhF4EK7N-D7~D_pQb~cITbSx*5$*#dd&yH#jPldNzI>AH{65R?jEEhI=7|~@iF+8F zIh5ZGRoXlEFUgkh4YmC^mNMqe-67pS!n(&A2Lrl<{=(f%f+9%LdXafH$OH%*;U z9YrA(!&A0`{?TDDu@fmW6$2OsfnKl8;h0) zHHMYen9U0noEOt7FQQRDhX}=ifjUm{fuBbCN}mI8S|}kgC$YWc>iFya+oz`In%~Yy zNFMO3zJ`}KSz5mAx*azJ>pS`D`^ijQEEA?kOe6PuPw8+)!aTW4&=G4i+% z3dDv*;Q0n4wx`2+=0|kI($nw?Q|&CtXz(}&9{yKUL}JUxw9`|lx27@ZVQ;St=zM7- zjBthFKN!56jqoH?fNguZhne6@Z01qiG$H$HFZdE-y1db@CC{#lvwaCGx+B+a@XRFt z`-|LNe~F>w1G1$l{_fDE_u+;^;#~$H$8L0_s;XI>6s||c`y>H<0dPkT~xN6Of|OcLvIbHTe&x?55E`wc^acTFWSNf#1DpmNXN#u)9>iPshm zJW;tQkHM;3+%x(B(Oe$F4Z~5p+aKcL1cS^)^TYRI%Sr!sa=bXclJu zvkSihyR0h`(n0(kIvC1U8O?ar4X!s>Y)nYaxz2r;dCq9`z}*q>fzaX4Q1SgeesV^w zI<0GZlkLQ;UC!JXb=ulz6T>vaC!X%ax>E|_mz*QC`m>S;4(SXTKlAV+dgc)RY5s#< z(|AC^EwggYOd$IGEpRa2T^Lz25h3VX^my&KzoKqTg{o!aj;Zmk4*v7r3y9Pb7)~8I zLZyB~X;eZ%_G;@pCLs%pc@eyz*e>h>rn&Oe-hO}R<0VI{d&8TiX5;djg;;|#u4x_@ z)?hZ%{+QXTxnWMWp@6G}=Twx$+ceblB}sUq#DHWd)uGs{f?>n}_VJN)=`kY?simRUdL`syg9mvNUd*v)h9 z@9TX^L;qDp;&G-K56+Ux0O2WRiF-E2DtElZ4iBjtJy#%_m5Mo(uXkACY2Nf3rL{?R zF{{JYz6%-Liv?w=tBtwmo}61h>{`$Pxd@ycSK0|u{XSha@`>s`8uj7snSmpAr z1y^6=fcsVmm2p7z2oH_<$t*hNUvH18luY{S-~G6zxz8C6rgEw+>hBDRFm?H%sIp|+ zLqtrhK&?SgQDmx}UwBvH6OiqcXO8RQ$fVjC0CfZ21A=sozp2*0v^LR=TUrJdM7?Yv z1Qb#%b#H$a)(?d}#D<55qWb6ld0DKLbr=Fthsrc4enAu)a5LMc@%jfpqAlLN-muG6 zXOEs?Lk#$;XSNS1uWZ>65=fJc_+CNN24}2SEd}8`6;i0vIiPdKL4V)Szsd`&e!8u1 zs%Y=NdX{^`AyGyPh^ zVb|le!8dfylzxUyPA_G7jDv8TZ7A6^^SG8bfrTqFP`9*8)5Rn9!&LujwPP~=CHe7? zuIcL)RS6dvQ$A$c*Vh!X#P2q!)3H%?9+Ee9cD%$Q26_)I@0Jz@gTlp4W_tca{YdwC zV8D-MEL(d~gRe)42Ghy5+l5Ccb?`O^bNfQ6WQjEGBwqa9vYa|>f8lzsopqw~rzpd7 zPEw#HFWL2c5z@KJ`8R@h#oIX$sGc`qU(AwUsF7EB9ydX~fl?)wuYvnvTWpV|Pyz%i6V5 zBIQPXoM&#LN9lvP%iQkGH6M&S>egx;;?`=0Q$$2QL>K~|qFa46{;KaGk%MZGFoCRt zzgI2pFQiT3gNcx0YKdxYoLM7AT=g76DRm9qk)qq?g089W=+Yet-UFI})&ty2cy9!Y zQ0ybRglFjP!NiwySuLTt3U(Z_yYcQA+5SK|9hc!7!J53j^e4T!ygQ~PP&%XZH-m$(pDxdu;5_l4=4e|l(A-u`M@**of^+1LJ!!4M_h0ejNzNg_WUqd(# z5mU1vWpqeCWwh-?uyt)(e|!IcLWde&qrN=HLw`)}`(Qz3-z{c>=S+@D9NBg0AtZRh zR2xA7g{~edwJEHXH;6Ojx=hFu6?N4HqcbI&-(J2!L*UPgw)>x9Mb@qap%u+UU#;U0 z7;0>GrPu0B5}R+5$GIhR0>JZqewruSjN<~!hz5e{u$>#!_D%mBzTcS^FiY_#7{U8U zvFd=1E9-qpP_9Ntbnv;am+x_zFIt9RE}2cd43c?|#h+o7);J@8U~|6vu4-3bu&k$< zyxzagF=@rUECj@Dnr<#vRRl3s_=*+y=G&goxT0KcCs_UU&ItJH0|m8Y~S_4^ZjXSRu`S@ z=J{*&Ryc7+^T?yD+%g}*xZ=u$1|vdjOU5#YTuH#Tm8mh2OJO}2@AnKlAjRYsc1aS% ztuJZra-gyCbpAPX9+AL@`WKGqd_5%vLXEjgza!nD`G)=W9o`Yp34Y;d7=oP2-FtQF z_}^ZHYB4{Sm*P-FhJJYlsvTOrZ#Pr_HRnA&fqsP-#6MtV*I$Mw1h6@B=?**hGfk+s z*q9$cexv4sal4cG=pwz7;9^6{Qr3}(Qyh^l?BW?x4pFWDLdcGGd2Ny00$P{g7Lq?( zDMXAIz81?J(cy}!ardh3eVzG(9=F?OOug;6>;OT4U9Xw;MdH{aoStT6I=#JVsHC~D zEAUZHt)_(bZx~viiD@cQj}-hJ~;FF@V^79Np-W)kp(O|*LY3BpJUR{)Q22_*^10wP^ z(>qn)?7Bd?HJZl;HAw zj_{q$i_tz#?~Q=&V~g=^4ULREhQUUraLV08->2X444$j`ZHPT-2j!ZU$eKPhA!KsMR6SLdfOFVhEXzy+z3fD%j0rHZJz z`hb(*?$_tXh9CS-ub7E z-_7jro$Gm+XhD6vww>ZT+-|2$o&qOOS$CTDk>@K}4Y43O@T`lwi9zrZnX0sS>c{!7 z!;-7j=$4VO4tXM6kd|-dnra?8nG5(6o(s43$HtV&x3{5Rv6k#LprR*| zVRf3$?~1#-qt9lnZunp*cKeW6m`^Q*BFDRVygq$%ehF75v6uJ{t8zLbAuS}`9)+Z* zNh~xK@gK@PtG!?H#uMb!mBNZ=>iECF+b$LzkGat_XoXv@I+i`zFwgbOg$jc3bD%3d zAKB?peNQ9;(Wt43W7PAsi;f|chpRzY4)Alr*P|f)VQzYm-OPmG=1_`?nhJzhS37O@ z|JT~47zfY~hZvm%CyP_zjcJ}tu_db8tD1drBwV6AkD}1t$E5|kX7&g8?3oith<@rc zHkE&5oh>vsk;~}Q(>(qqZqNGeO;tYpP#!tcAsSkDC*-^LfabsvVhm2fR~TOQH>x0~ zFh8h>y>X0=>CJucjCyi^ZX;h7`3a~|=#_oY;z>>+pDmajL*q!D=N4$K(8wIsq#d81 zylVJ%tD6jXa-yWH_=f@cCB+#C+c1Bjb#Ss2=KoUC7gj^28?*E zDUS=x|2M|LJ@b(Uoz&;Q`uCt{l=di2OO6qg&edoSa;t$C3whA#Cj60G$nhqM=An05 z>%LD^>R$Xd23^?x5-hVbD&cxGQ>j@0$) zZXJsN)}Z1Z(QGKtjA9>dT6V-Gu|lM$pQdbGSMIR|)8<~bsYuDUj2n0=KBD}x&-9N} zf4qOyXc;j!0E2CKV3b~zoS`Ey$D-lN>lhU+ zI#~X6bQV*vv?^T%8&y7q0@I>z;T&ri_dhPZ3)!5E+%K04X|tZ+T}s$eJWV8hB>Toq zTx`u$)6%8@>Nv~Pk6!GzPLY6>?Vs9oNws!rKBC&Q;sR=tTZ&F*!8)tw5Ev`@AMi5I zB@mVltM)R-U&m=n`R6Ml?LXr>ETnY6O+LI8Nd{2BLG@$wd3w!Fn;2hQ?yxx9m}Igh z*>rw=VZ6i%qfy?zDW3to;faM+;XLwCI#h8zviLOyq%%Vv9i2aLakc0F8XRr9%MHQW74 zD&JXfhY#453e~vG#TLnDFD{!{9PHm$KOVIa8PQPC0q$ahCJ+55TU%2jE85OwR0~8J z9j0qOBJnYapcR&2Fa?vZ5Xu##KS5csWqwur>-esYCcAzov5X{FGtTnr)3XNcpgj z2&P2!L|)xgwVLvxhS?nxjpUE`C{{qO4e1kDJ2&R*U0m=B{CXlQzRON0KQoUQ>h#a3 zI`FyUf;2+H%el`koNM>G{Ek6+sC;$0ZGXeihjzP$=eEZ|wTHK$ zielW0*6QTj8uDCT^>N$XljOg~$vHEW9;;*NBDkl{2>B+h6RkmaP&>I~Cr5U#Qf`|W zJdi7#t?g0U*V|9;oY{=;|3ERaf5KJrg|k0KMOmFcs7B{ZUd$#Ct#m7*@gyKEu{B6~ zJ=T1z&f*#N4U)8`I2##>5koygubDDsmEt^Wo6)w)y>*RoZOlDnIg4{LrIJ&^ba|AB3{`DDwy`9zbsltXPAL@V{rf{m8V~#h z3zY3NAq1v`=nQm<;On>Sj57JQSIlGU#tAt}>eWdb+c4QL%-xt87z^-`;*EasVtdA7 zOQTK7`!mQdz>msUMUoQYhf-*2st)eGq(WNc*B=y_Qf%iZhu~(|i~P{6mWsxBN&E@I z#gUkXFFgk-@QD8+N>B^VQ54s(y(GpV503noO}~X6zcCn~)m;nB15K&SugBVF2i?Ss zRSNOKRK-ikI($36cFmO|23@a61!&@NU=7$0u>}hMt1UQ6Gh#RBX4NJ9(Q&XPaerTH z@-D&&4@1G)$%Zvodllm|vO+eI zm7lahm%EkTHrE5%pNVNz3M#}xxfFZ+>!JT}rf34swbjQPw5NYH$OZNA>Q%C7bDH^7 z^U>0S%2wccLX7jBQ4ee728y`8lf2qNggn+BqdCx-oxDS}=n7j_)Qm^bN$31+b({bjA!ek7de9-fR zr0}A#$-AImDRMAhi>%3nAXedgfB{_0OvE|@GUBV0zWhd(Psafn&W7PGU+X?QIaqkc zY3xILtiyQZuQ&xS(bqr&%k~yW-X~wV1WM+K59&;*oVNzFE>MZZF7R}5bEdIsT{afG zhEH|dZ)BhDpSF^8B5Lu{ZNvs|)*uPA$#mh%B#4gG3L!c=!s|;=Y-E}yd;yiMwS`_4 zt9_sSuzk3UwT%rSKl%$XIu#EJDNt1vUS9l*zmirp+=qFs5O#O)Y}mc{mRJ{O`^k{o z1V#RfA65z>TAN`VXl?&=CTyEV%XYHHh+m$ZZRcL?y82!vJ$LM-#$-E%pDmg3#O&>I z$dqttmJPcwSfVR`^jDam#3KZTXWQJv%G_72+cH;vnm@OYd&}A=mFRZ^+q`%w6)EXV zf&$1%zWp+vL$&$^x~70n?(K~19r4ZSc5cp&vjk(X9DKTj+lU#63NGo)RRoiJ@^6{= zJk>R!XP3G&egRK-Cap`l^-j%9Tj_0_n?KztE0g~{2^yf`FN#a$q9^Gnzx-fmi78YF zz4Ntok;x!6HHv>Vr#(TzP@N884s<%$|HKm6sAZu&CA9Jad&OPEG~p_~L5gc@ORQsU z6aml@;iUkeqPFq;RJZoR>E%hq@Z+>ubKgb}jzX6fl1VHawSh>6c|_uuth0n)fz+G; z?s7Nxb=+s04e~uyaTMlP&3gZR(gw+sGd&-xma(Wdr@USLMEv=O7dpHg88z*~Rb-dnvwzq92rS99rp7o z<`CPlgf9dn2=5Z-EF>Dmg4(Awh1iAITs)#1WHRn|-N5%&~A(`8qDf){<6wr`|b&DD-nv?nNzhV#IM(zROEORInsMxi%&%pkFynD-V*?ds%* zAHfdz($qt9o`wFkNI|v;D*tZt?!6-7P&z31wpzP2Tez<&bvwZd#(^tD?Fu7^>qlIa z(B2p$au2Ht68%vz!9r94XM=2;j!f_xUtizzV%a0b#Y4mH?s@#!cZU2c^AX3m9ywee z{}mVVLm=YN01-}Q((@kjzy-Y$zTA9s;0fi!JhUj+rYvSl_C;p??(P<@iGglUyhm!d z76T<%eE4W*C08auNgg=M9;kiDwm;{)_h(*-<{^4I7g`UAGKrfuF%cM23biX+T3I|m zL1y~R_3MZuXbIPz-I zlVFyAY&a+r%*Z^QZlUDVqq(QxH)^(m`i?oaA)mY2UNE|bG zu$ss}w+B`d%><7)(B+;SzPz?1(x>hr3Y`&#RIdy@|3}qfsDaT)Hg&(hIDM$5^(!cj z=C4G>5sWZ%naw_aa;Aknc$^XFW3CBJT1L&t6^M_ESxPLcijtEGBhEy~TE$h`wI1Fm zeqD{5zzZ3ue61`bjRl3+ii`GD%ealEhaXIyj)Lm1t1Bd3i8(~G<#6z70~aX{@1H$= zt5>a>A(lOt&_YlHbT9wW-5n&z-vN1?dixmY{`+n9W@-I2211@?IM6I>LA@^Svc|7? z3AY(W6s}-FCjPK9D*7-JKiD zk4W)N&O6irvxW5w^P|GU<1L{Bv9Ga&z~EPARUOeI?;rILv4t#!W@P+snR;;c%4~N+ zLS@>cz{(36>NTghDAY=Y1Pby(dRMFk&Q4|8t#cdit;xrSJrvLo4#Sj-xaK-H$+v+& zCDQG9_69J_r~M6=lsT~m7i>Q)Zn$&~XV8{7OLr5^7AgCUE>qo_8DGm(?e5g-G_vCv zYw4k-6Xt{g-?q?GfUJo4o27SzsGe66HotmB;DMKCp+u|0=y@QJNYU3t)B~oz6;fD5namQ`iNS1S} zZxJEOL;*YnyEkaHEkH7?FurSh-V?!~Jj}uYf@G`fAnwzp^;&MRwI5c5h27(!Vi3^B z$H7MwL`B=fh@@Xs=$4oR*%CVcoFK*FspBa;kv)E*F3IK$!J+77#PzE{pI*N08>T6^ zEpaYyS?xL80qj;!>9$4>1kJ?Z4bypcfN@xu?r;9;@1+FC& zQ^WR&Jy-ir?vw>E^L?uz34VNa%HD1J?JzgFf+u6^k1h4jQ6LhpTQNy&Q}dHZ3f1-g z-}vq&LK=ie8Wg^4xq9Rss-GEVfRJ?5=B!?>oyouPk;PbbS#5QlG=0}U4;MnX+_dmb z;-|vEOjRbo;-fAd88M#{59BkczAmzJNSW{Tf7yAP^(l6PAni@&88tB>*t@k8g9NYT zR2b5yALcT0ZpP!ndXmy6e9*<t~I-(jyqX|DRx3o)K#ceLA&yyote*-jR>+N>jmSqi9 zzsQAyp1Z{Lc1^#Kgs|HHYM9G3cz7Agpj?a2Zm{{j>U`V0T^_u#hY+S#^7{zi$t+`q z_6U7{?DIRz(!V*mpibCKJ5Ny{`RQ?}J35b!s}5H~%k*o$AwNO-c(@#wqD!S4!o5&- zgDs2(<=Jy1k4+kr?k*hA!FF(IdLSnX)XEXkDa|_agX(}#+(Rrb)@Pc$Fd0q@x zC`Xmt67bE5DA}$z*OO?UyfPp;_erT1A@J-*60$S3YHn0i%c%rCEEtWABG09V3(`P? ziDHqE8U*HNO4sUKi-{I&3HG1}h%i@KL6cmB#Ob>B{QF(!C$*AIwGPe=Ji8ilI|u<5 zZ3TUJ(>>6KhdB2J?dO*3dbOu;Nia8`7hPpRl4{57qI@tWy@mhfzg_~uiW}?0839Uc15Yq__v5MjEiBNDW*|`jNVXo}*x5^XF z-fJ}(6QZ}6CEb54T!n(Omh8JCI})I5WQQ>$WqXp0Y-ysJv(_c4jp?YRwxvrY)MifEGHaZQ$}kX1_i+Y)b(pQXDZaAPbuny_ z1G``KwPlP`apKft#|vIl7d#$&PygzZd%eP6#9088e^)PkZMjfDPD+Fe(`dl?Vat+q zex+D-|7~s!R)@RAu}c?e)h5>@WVBJ^1#Dlvb=LH&5%(vSsleIN5AF(`i}H*Yo@DR( z59knxjE3K4Jb|Gi6p;7`VTPk>^CC)AFP)p6m~b0tPXB#+j7cp85sYY~!|zA(9@@t8 z?dm)~aPH8z^LvyI|M>s>WGWUmQjc`HP-x6+%v0^JN83N#UKRJwTSkggC0d>Jw+?K0 zGYxVVM9p%kmWp(wR!@(AKN^7Vr8npop2+xqE22wi%*C&3XGeZ$dfjMbs=+nS>R^9P z4Tk~C&xz|N1)?7+Y*TFc|G*uN?i}ss53k%#i9e*zkePYfd?7aTD zlnAHUEHL!F=&O0p?0yS24)N=BgIUsqyaYO2i6<FyKI%NI(vBfS zj*OO?0OIm$osMWzB$~j(0yB7Ze3ynX?nV_E1Q3E;?x@Y}l@(O}(8nB@?b2+6;f!PgxOh^w2CpS1Zbk4`uk}ezLP+@3=*)J2y zxQ_d9u##HbrQ=gmd3k!B#eOC+&z%sWK9vs_9rEL}!uhqe*VRKQ4g*UxTtr+9Rh@nc z3EGPVU^0%ar64y(oHfPDU>j7wxS|GIGWj75NesvUyE=8vy_(R){+kS8xF+n=lt&fp zK86Ab#SJiIL(J}>#t8oQp2S`Yr=M+@iqvbD37mlmpy?YC^YvKX3)u+{bLW;56o)|T z2BL)~M>q`j2YgPbl_uSH#*?f;zHPW;SNmAD%fDC6S~3o;zS2UxnYlLzTv~nKVR-Qi z<$`SyfR;0i(o|nlNIm=lCgJ?7tA-#`bjx(ZLZ)7AnJO_|@p&gSwAVr@Qna>xc~5Bq zazDLMF!k0SvpPJAK{?qj2RV3m?RqUVQp}INgJ_pWC-FQba7_1#wbX+LUinAY{k901 zfOtCT*(z$&-Ka zt8ExJ*pK~$H1cQf;W|N$q&q9;&4?kX+8gNP!rzShO?4isV?j%>QkiUmWU!G+z8|pJ zNy2?jHYbU^Y6QHh?D^y}6)iC;%o~gKOI|ra#r(QdEzIt)>b7654k$N%o-=9S!r;9{ zI&d>qGgZWCuDw0>QohGj8a|Er<^*`O)rN;~J)LCM)ldv0oINFV_K?YJ1F8l$ESR5XC_mPpC|V4htIWDQM;Swpn$J``NuIn6w#{vgzf3N(y98yJEg z)%$Sm;uPA6VvB^7obTUX3}LxlJd`uiE}yjT7nLbRMnY={J(5I@oX(RBeM40|P*)=gA47}1v#Y&L;lv;EGCfF(oy-|kipUwm zf?NnOKj%l;yQRz+yin`iKHO1wzAIWikq=>kNNoFW`puwmLk|eSXk`iWp2>7r0@9Zl zB$V-uPEESH-0J{eDK{(gPsplL1x=n63R54*MPd&#Av#n+dP04Qt}OGo zUTU@Z>j=B|W-o3jaT*Lcsy9Dl^9P#zdK z9obV!hENx~nWKci@O7k({~9h^hxx43Tx>(ZVhFFuBXzRJC6|<_7pKI4VQ|jv zRqOKB$%NczuAA zqE1w;t@xALMlKHTc>CVc+Pn!94U&$FKxf6K*QVwQ{U$jP?ni^G7&rfK1%L**Hl*?8 z>uZJAk=le_%SJhLDvk<$ITJ*o3vaw6io(w4NLV+feeX55_)HFnW#>l8uOnG5+(?;hr=u3!O?+^97!2!?S~# z%n}kcBx(rZTdS&n(^@4O51myUuWSFKDhvrIP`~DJL+7toFSKh(l~W~O4QVm6liYW> z%R{}-Hz$r(4}nibI^B7qq-t#WZJWZWDA0Q9sw!F(C{Sd_eAiGxhP0@|6+moHkjl4v z)M+GI8a^4d*2NkQy~Y|r!M#?<_=mqRq72_h?ebFF{dGQ7E7Li03EUx{mx12|-2e%! z%B8 zHB1}Xn=G~vHL@oQatCC*nOe0O-T|ur>TU?7(qD4#w5?&hH4mGu<#n>JyLU~dG>NDbLXX$}YT960 zI>&AQ!-*30%P}92X)g56R^#J^8M?D5Vr+?^ESLT*m~LIn~H8>GAmbxM_kJ~#NUw>14e z>2fm%#*;zwkNu_px(7_T;~o}5IyPYFTmJpGJvGr9>}#_fcf0xhwsArLZno5p1f^5y zHtM6OtG7Yi+YXtLOKTh_$J{~(w1}#XQrK|E70?EZ4BM7bc^*~_h&x;B8=WXd$(KHV zlNt||4$riiL%LO&;4J)#gFV;dZ|fE`HE2=FEqavb3-5A(+h{yvtcNIbu+UWnp4T+*dB#As)-by*If zU4QoU5CeB;il08>bLd(~NRaxq28z~r-1$hiZz<4@VS+jLj1+tDn4lEG4zFnqIm}$5 zo((OCgG()l>c@HNqebcyMF`H0T$Eh1)UtC~fpsk{>Y#Kq4ec<5wx@o`D980ul@tDfUGE;Ou{3h5B;g_3bUItpl# zIKvPq^DB;7(o)c`Zm^B}uCV5VlTe9ao#-dIwl!SYX*%;2L$P)Qt^@ z$1Y&nad%C7Nb)LTM3WGS+-vibZzj!IAjc0Q5I(W(c>tw}U#5j4SrL;Xq_fHMb$@H} zfxeb;Bcj``!b)0>;E2fe2S+yBvheW<+iyLr(Zs7!FY4iao{aQzS6g$`2i9@2{AW z-#S?i2Bwoa#o1t+=BIf{=*XOmi8V9RA)OAXjzwX^rz!nT%oshctLDvr-ftxr)QWGY zZ0)oSFkeFtOLZ77+b{XdzvHtcuP*qf}l?H_%_UH87GY6-iAYoAvf{Qx*s3 zvD_Vr;wDWSTMk?E;2QyNqSYa_~c@ zUmUd9u;GzU22Ih{720ao3k9|^-3=%_^hp0^UeHS6A`bgJrl**j2Du_zw{A7vm$!Gd z+|7-gh3}*DA8C3;M^s1GWF-o`P0Wt8?MX{-?nz8be^M^<`h2#=;^N}M;Cz$BN0Xk$ ztKwSKP^^nh)?|yO8``)Sn9cK9dmSOoMnGo>B&HWa9A;i4)l@d5Xzo1S^GE%eR!z4V(7}|?*;Nfv{{etQg*+}YPD5$ z?|(bGT=s1`e9e(VfooIQZ$)Wc>V(U7%Ql zOvm6A$;QX%+UCqBLfS0x`lTB$gqc!TOh^a4i#Q@bJ*6q`-z{G}PiV-=)DC+lRV!l0%Z#Tz5fi7YUma>u1UMP#Y7Hrk~IkM?B&5^&T@48{BpUwrP>brZ;|h=h$Ko*BKzHLE%2)= zwK%p@#C*%hjLxPNN=nR6>KA@BYh-M6DSsqgNrS37v9#*rBv2~Xu-;!NC5(F2a2=F< zso<9?r`^p~g^%+0S^XIM69XWZw;FmcTQ6Z`X~!jY|LLD&CEWLKiNA1i>weq56#KRX zvH7&teB7_Xm1xy9$`ci$U+K$6oc)qPwE$+rhN&h48Q&AWMFRK7ImBPunz`kunjI;l z)cecMAxCpIScIgY|8KowEZtuPa^C`tm8-g1$Sy4Kf^oRbV(jl@Qr2g9C5l6igW0mL!r+sC!x0|e%W=EUetFhQ>srEjW8=%y z?KR>n5AYqo#y1BFWgN<=i zKwcAtQiI=ZfLn@u!?f@c5c0h?Uc?BlS@Q)^u^&Ojx-D0+r*(Tr8#{{q`1_A;K49wf zF4N3r&zf>I94&MHWq=)xwdb3js_GngetE>4VsS*Vmx4AmzmK>b_i$juBN5f@bR6TX-4)-g=Njx1irpH>v=UmlC)ul zP(>i4V}G$*7K6K17Q+TSw=TJW7$wBfO=R6iF}i``n7D_>j)uo|D{@LyJsL>q^@`Z% zJd;4eo*7+TXYU$DRtlh?i9Ze4JtAHF#DB#4+O(n0rW%@=4vF(=8U?KzCY#eEYaKQ5 zh9*)ODO#nC{Ghs*SPWByqB3eq45|2#3I-ntLm9`!> z@XxV6aH@r(b_462xB#mko0bm#^Xe;^?fd7Z>!!bjT92B1eRcdtOazHJO@)TW_G^4C zY6#pDcj*Ri;xcy<^AM_Fo4Z+ua3y{US)e${YIOLpt`(=zq2PhfO*SW+#7&F{0{<5# zh_SD%|51R8v7{8@X^u~>_*KYo@Aw{)MN)ErE^ zALU>Qei$BdIyjyrU}Yc|q#o3;%zGxn8lC~!>SD+F2b6ffiwtT07h^+dJLfl~*!DX} z%{C>aSadkdIy3J&`@m5J+o#Gc^jjZIRs)1Op|(+f*R$IB>a@`X8EO`&RStxIOA?xlem*XZ6KARo5V$ zOxGMW9jA_b$K`-0m)OWBhTvIk=JN7}2fP{twRxc^^VM+a#rXLctBliN? zT{+pxe7Vp|-h?1CikwKK+3@zos@mxOs+wiLT@8VKt@&=pzbVw~GU@$$xB0|%<35+} zPV33f0sFss?YvJCbryy2x2Zb=1J^~YRTU~fCSUrPWUiJ_b{9$E9egZ}e7>1dSN!oTYBbH+D^Ec z0Y2OL^kf)|d|+$WpDpd)BPi7vP0#3Zvoi`|Qa3E$WjWp;*^zII1iweA5bhVSP-~cQ zZtxW5b1e7#8FR(Q+Z0P48cNkYzMF2?KKImWwvU9Ke0D?NW!ugpI&OKuV)WE3hIR!< zappy*`kxgs_}&v->ECkm@Atpp5;M9WWxgoAPyh2%kxiL;xFjTIgcUMV(=j0rDFp_j zw7#yg!so7~DlciX=@MK5!b5` zp#FCt>fdEP!q55zRa6S>zKFSPG4<(-b!{3

-wOcXyKa( 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ⅆ}r$&XP0*M4(0He|5X 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 La>wG2=w16?S$B>i From 4a882b14a0ea66cd2472d985afaf8293d5ee5dde Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Tue, 16 Sep 2025 00:10:11 +0300 Subject: [PATCH 034/133] cleanup of quickstart and readme (#1588) Signed-off-by: Nir Rozenbaum --- README.md | 3 --- site-src/guides/index.md | 46 +++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ff193dfb1..51aaf2829 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,6 @@ This is achieved by leveraging Envoy's [External Processing] (ext-proc) to exten [Inference Gateway]:#concepts-and-definitions - -> ***NOTE*** : As we prep for our `v1` release, some of our docs may fall out of scope, we are working hard to get these up to date and they will be ready by the time we launch `v1`. Thanks! - ## New! Inference Gateway has partnered with vLLM to accelerate LLM serving optimizations with [llm-d](https://llm-d.ai/blog/llm-d-announce)! diff --git a/site-src/guides/index.md b/site-src/guides/index.md index af926acc1..9fe5c7a8d 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -360,35 +360,32 @@ Tooling: kubectl delete secret hf-token --ignore-not-found ``` - 1. Uninstall the Gateway API resources - - ```bash - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/gateway.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/healthcheck.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/gcp-backend-policy.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/httproute.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/gateway.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/destination-rule.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/httproute.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/kgateway/gateway.yaml --ignore-not-found - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/kgateway/httproute.yaml --ignore-not-found - ``` - 1. Uninstall the Gateway API Inference Extension CRDs ```bash kubectl delete -k https://github.com/kubernetes-sigs/gateway-api-inference-extension/config/crd --ignore-not-found ``` - + 1. Choose one of the following options to cleanup the Inference Gateway. === "GKE" - No further clean up is needed. + ```bash + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/gateway.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/healthcheck.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/gcp-backend-policy.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/gke/httproute.yaml --ignore-not-found + ``` === "Istio" - The following instructions assume you would like to clean up ALL Istio resources that were created in this quickstart guide. + ```bash + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/gateway.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/destination-rule.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/httproute.yaml --ignore-not-found + ``` + + The following steps assume you would like to clean up ALL Istio resources that were created in this quickstart guide. 1. Uninstall All Istio resources @@ -402,10 +399,14 @@ Tooling: kubectl delete ns istio-system ``` - === "Kgateway" - The following instructions assume you would like to cleanup ALL Kgateway resources that were created in this quickstart guide. + ```bash + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/kgateway/gateway.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/kgateway/httproute.yaml --ignore-not-found + ``` + + The following steps assume you would like to cleanup ALL Kgateway resources that were created in this quickstart guide. 1. Uninstall Kgateway @@ -427,7 +428,12 @@ Tooling: === "Agentgateway" - The following instructions assume you would like to cleanup ALL Kgateway resources that were created in this quickstart guide. + ```bash + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/kgateway/gateway.yaml --ignore-not-found + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/kgateway/httproute.yaml --ignore-not-found + ``` + + The following steps assume you would like to cleanup ALL Kgateway resources that were created in this quickstart guide. 1. Uninstall Kgateway From 0db3033245cb9e54aeb4115f4ae00fb82ea8a82e Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Mon, 15 Sep 2025 16:48:08 -0700 Subject: [PATCH 035/133] Conformance: Adds Report for Kgateway with Agentgateway (#1587) Signed-off-by: Daneyon Hansen --- .../reports/v1.0.0/gateway/kgateway/README.md | 3 + .../gateway/kgateway/agentgateway/README.md | 79 +++++++++++++++++++ .../inference-v2.1.0-main-report.yaml | 23 ++++++ 3 files changed, 105 insertions(+) create mode 100644 conformance/reports/v1.0.0/gateway/kgateway/agentgateway/README.md create mode 100644 conformance/reports/v1.0.0/gateway/kgateway/agentgateway/inference-v2.1.0-main-report.yaml diff --git a/conformance/reports/v1.0.0/gateway/kgateway/README.md b/conformance/reports/v1.0.0/gateway/kgateway/README.md index 020f42f78..c64981f35 100644 --- a/conformance/reports/v1.0.0/gateway/kgateway/README.md +++ b/conformance/reports/v1.0.0/gateway/kgateway/README.md @@ -1,5 +1,8 @@ # Kgateway +This guide provides the steps for running Gateway conformance tests against [kgateway](https://kgateway.dev/) with the default +([Envoy](https://www.envoyproxy.io/)) data plane. + ## Table of Contents | Extension Version Tested | Profile Tested | Implementation Version | Mode | Report | diff --git a/conformance/reports/v1.0.0/gateway/kgateway/agentgateway/README.md b/conformance/reports/v1.0.0/gateway/kgateway/agentgateway/README.md new file mode 100644 index 000000000..7f35d2b2a --- /dev/null +++ b/conformance/reports/v1.0.0/gateway/kgateway/agentgateway/README.md @@ -0,0 +1,79 @@ +# Kgateway with agentgateway + +This guide provides the steps for running Gateway conformance tests against [kgateway](https://kgateway.dev/) with the +([agentgateway](https://agentgateway.dev/)) data plane. + +## Table of Contents + +| Extension Version Tested | Profile Tested | Implementation Version | Mode | Report | +|--------------------------|----------------|------------------------|---------|----------------------------------------------------------------------------| +| v1.0.0 | Gateway | v2.1.0-main | default | [v2.1.0-main report](./inference-v2.1.0-main-report.yaml) | + +## Reproduce + +This is a mirror of the kgateway [conformance test] with the default (Envoy) data plane [conformance test](../README.md). + +### Prerequisites + +In order to run the conformance tests, the following prerequisites must be met: + +- The [kubectl](https://kubernetes.io/docs/tasks/tools/) command-line tool installed and configured for the active cluster context. +- The [helm](https://github.com/helm/helm), [git](https://git-scm.com/downloads), and [make](https://www.gnu.org/software/make/) command-line tools installed. + +### Steps + +1. Set the environment variables use by the proceeding steps: + + ```sh + # The kgateway version + export VERSION=v2.1.0-main + # Skip building and loading the kgateway images + export SKIP_DOCKER=true + # Install Gateway API and Inference Extension CRDs + export CONFORMANCE=true + ``` + +2. Clone the kgateway repository and checkout the release: + + ```sh + git clone -b $VERSION https://github.com/kgateway-dev/kgateway.git && cd kgateway + ``` + +3. Create a KinD cluster: + + ```sh + make kind-setup + ``` + +4. Install the kgateway CRDs: + + ```sh + helm upgrade -i --create-namespace --namespace kgateway-system \ + --version $VERSION kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds + ``` + +5. Install kgateway with Inference Extension and agentgateway enabled: + + ```sh + helm upgrade -i --namespace kgateway-system --version $VERSION \ + kgateway oci://cr.kgateway.dev/kgateway-dev/charts/kgateway \ + --set inferenceExtension.enabled=true --set agentGateway.enabled=true + ``` + +6. Wait for the kgateway rollout to complete: + + ```sh + kubectl rollout status deploy/kgateway -n kgateway-system + ``` + +7. Run the conformance tests: + + ```sh + CONFORMANCE_GATEWAY_CLASS=agentgateway make gie-conformance + ``` + +8. View and verify the conformance report: + + ```sh + cat _test/conformance/inference-$VERSION-report.yaml + ``` diff --git a/conformance/reports/v1.0.0/gateway/kgateway/agentgateway/inference-v2.1.0-main-report.yaml b/conformance/reports/v1.0.0/gateway/kgateway/agentgateway/inference-v2.1.0-main-report.yaml new file mode 100644 index 000000000..9e4063b5c --- /dev/null +++ b/conformance/reports/v1.0.0/gateway/kgateway/agentgateway/inference-v2.1.0-main-report.yaml @@ -0,0 +1,23 @@ +GatewayAPIInferenceExtensionVersion: v1.0.0 +apiVersion: gateway.networking.k8s.io/v1 +date: "2025-09-14T10:03:01-07:01" +gatewayAPIChannel: experimental +gatewayAPIVersion: v1.3.0 +implementation: + contact: + - github.com/kgateway-dev/kgateway/issues/new/choose + organization: kgateway-dev + project: kgateway + url: github.com/kgateway-dev/kgateway + version: v2.1.0-main +kind: ConformanceReport +mode: default +profiles: +- core: + result: success + statistics: + Failed: 0 + Passed: 9 + Skipped: 0 + name: Gateway + summary: Core tests succeeded. From 0bff44eeb3147ad2890fe222ef4895551cf2ba1b Mon Sep 17 00:00:00 2001 From: Luke Van Drie Date: Mon, 15 Sep 2025 19:32:07 -0500 Subject: [PATCH 036/133] refactor: flow control config (#1581) * refactor: Standardize FC config validation pattern Introduces a consistent `ValidateAndApplyDefaults` method to the configuration structs in the `flowcontrol/controller` and `flowcontrol/registry` packages. This change refactors the configuration handling to follow a unified pattern: - Configuration structs now have a `ValidateAndApplyDefaults` method that returns a new, validated config object without mutating the original. - Constructors for `FlowController` and `FlowRegistry` now assume they receive a valid configuration, simplifying their logic and pushing the responsibility of validation to the caller. - The `deepCopy` logic is corrected to ensure test assertions for immutability pass reliably. This improves the clarity and robustness of configuration management within the flow control module, creating a consistent foundation for future wiring work. * feat(flowcontrol): Add bundled Flow Control config Introduces a new top-level `Config` for the Flow Control layer. This config bundles the configurations for the `controller` and `registry` packages, providing a single, unified point of entry fo validation and default application. This simplifies the management and initialization of the flow control system by centralizing its configuration. * fix: Update controller tests with valid config The previous commit introduced a unified and validated configuration for the flow control system, requiring callers to pass a pre-validated cofig to the controller and registry respectively. This change updates the controller tests to provide a valid configuration instead of relying on the now-removed defaulting logic in the constructor. --- pkg/epp/flowcontrol/config.go | 50 ++++++++++ pkg/epp/flowcontrol/config_test.go | 91 +++++++++++++++++ pkg/epp/flowcontrol/controller/config.go | 50 ++++------ pkg/epp/flowcontrol/controller/config_test.go | 4 +- pkg/epp/flowcontrol/controller/controller.go | 7 +- .../flowcontrol/controller/controller_test.go | 18 +--- pkg/epp/flowcontrol/registry/config.go | 99 ++++++++++--------- pkg/epp/flowcontrol/registry/config_test.go | 30 +++--- pkg/epp/flowcontrol/registry/registry.go | 12 +-- pkg/epp/flowcontrol/registry/registry_test.go | 75 +------------- pkg/epp/flowcontrol/registry/shard_test.go | 4 +- 11 files changed, 245 insertions(+), 195 deletions(-) create mode 100644 pkg/epp/flowcontrol/config.go create mode 100644 pkg/epp/flowcontrol/config_test.go diff --git a/pkg/epp/flowcontrol/config.go b/pkg/epp/flowcontrol/config.go new file mode 100644 index 000000000..edc23abad --- /dev/null +++ b/pkg/epp/flowcontrol/config.go @@ -0,0 +1,50 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flowcontrol + +import ( + "fmt" + + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/controller" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/registry" +) + +// Config is the top-level configuration for the entire flow control module. +// It embeds the configurations for the controller and the registry, providing a single point of entry for validation +// and initialization. +type Config struct { + Controller controller.Config + Registry registry.Config +} + +// ValidateAndApplyDefaults checks the configuration for validity and populates any empty fields with system defaults. +// It delegates validation to the underlying controller and registry configurations. +// It returns a new, validated `Config` object and does not mutate the receiver. +func (c *Config) ValidateAndApplyDefaults() (*Config, error) { + validatedControllerCfg, err := c.Controller.ValidateAndApplyDefaults() + if err != nil { + return nil, fmt.Errorf("controller config validation failed: %w", err) + } + validatedRegistryCfg, err := c.Registry.ValidateAndApplyDefaults() + if err != nil { + return nil, fmt.Errorf("registry config validation failed: %w", err) + } + return &Config{ + Controller: *validatedControllerCfg, + Registry: *validatedRegistryCfg, + }, nil +} diff --git a/pkg/epp/flowcontrol/config_test.go b/pkg/epp/flowcontrol/config_test.go new file mode 100644 index 000000000..713abee77 --- /dev/null +++ b/pkg/epp/flowcontrol/config_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flowcontrol + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/controller" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/registry" +) + +func TestConfig_ValidateAndApplyDefaults(t *testing.T) { + t.Parallel() + + // A minimal valid registry config, which is required for the success case. + validRegistryConfig := registry.Config{ + PriorityBands: []registry.PriorityBandConfig{ + {Priority: 1, PriorityName: "TestBand"}, + }, + } + + testCases := []struct { + name string + input Config + expectErr bool + expectedErrIs error + }{ + { + name: "ShouldSucceed_WhenSubConfigsAreValid", + input: Config{ + Controller: controller.Config{}, + Registry: validRegistryConfig, + }, + expectErr: false, + }, + { + name: "ShouldFail_WhenControllerConfigIsInvalid", + input: Config{ + Controller: controller.Config{ + DefaultRequestTTL: -1 * time.Second, + }, + Registry: validRegistryConfig, + }, + expectErr: true, + }, + { + name: "ShouldFail_WhenRegistryConfigIsInvalid", + input: Config{ + Controller: controller.Config{}, + Registry: registry.Config{ + PriorityBands: []registry.PriorityBandConfig{}, + }, + }, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + originalInput := tc.input + validatedCfg, err := tc.input.ValidateAndApplyDefaults() + + if tc.expectErr { + require.Error(t, err, "expected an error but got nil") + } else { + require.NoError(t, err, "expected no error but got: %v", err) + require.NotNil(t, validatedCfg, "validatedCfg should not be nil on success") + } + + assert.Equal(t, originalInput, tc.input, "input config should not be mutated") + }) + } +} diff --git a/pkg/epp/flowcontrol/controller/config.go b/pkg/epp/flowcontrol/controller/config.go index 17c1429aa..e542c4d6b 100644 --- a/pkg/epp/flowcontrol/controller/config.go +++ b/pkg/epp/flowcontrol/controller/config.go @@ -53,46 +53,38 @@ type Config struct { EnqueueChannelBufferSize int } -// newConfig performs validation and initialization, returning a guaranteed-valid `Config` object. -// This is the required constructor for creating a new configuration. -// It does not mutate the input `cfg`. -func newConfig(cfg Config) (*Config, error) { - newCfg := cfg.deepCopy() - if err := newCfg.validateAndApplyDefaults(); err != nil { - return nil, err - } - return newCfg, nil -} +// ValidateAndApplyDefaults checks the global configuration for validity and then creates a new `Config` object, +// populating any empty fields with system defaults. +// It does not mutate the receiver. +func (c *Config) ValidateAndApplyDefaults() (*Config, error) { + cfg := c.deepCopy() -// validateAndApplyDefaults checks the global configuration for validity and then mutates the receiver to populate any -// empty fields with system defaults. -func (c *Config) validateAndApplyDefaults() error { // --- Validation --- - if c.DefaultRequestTTL < 0 { - return fmt.Errorf("DefaultRequestTTL cannot be negative, but got %v", c.DefaultRequestTTL) + if cfg.DefaultRequestTTL < 0 { + return nil, fmt.Errorf("DefaultRequestTTL cannot be negative, but got %v", cfg.DefaultRequestTTL) } - if c.ExpiryCleanupInterval < 0 { - return fmt.Errorf("ExpiryCleanupInterval cannot be negative, but got %v", c.ExpiryCleanupInterval) + if cfg.ExpiryCleanupInterval < 0 { + return nil, fmt.Errorf("ExpiryCleanupInterval cannot be negative, but got %v", cfg.ExpiryCleanupInterval) } - if c.ProcessorReconciliationInterval < 0 { - return fmt.Errorf("ProcessorReconciliationInterval cannot be negative, but got %v", - c.ProcessorReconciliationInterval) + if cfg.ProcessorReconciliationInterval < 0 { + return nil, fmt.Errorf("ProcessorReconciliationInterval cannot be negative, but got %v", + cfg.ProcessorReconciliationInterval) } - if c.EnqueueChannelBufferSize < 0 { - return fmt.Errorf("EnqueueChannelBufferSize cannot be negative, but got %d", c.EnqueueChannelBufferSize) + if cfg.EnqueueChannelBufferSize < 0 { + return nil, fmt.Errorf("EnqueueChannelBufferSize cannot be negative, but got %d", cfg.EnqueueChannelBufferSize) } // --- Defaulting --- - if c.ExpiryCleanupInterval == 0 { - c.ExpiryCleanupInterval = defaultExpiryCleanupInterval + if cfg.ExpiryCleanupInterval == 0 { + cfg.ExpiryCleanupInterval = defaultExpiryCleanupInterval } - if c.ProcessorReconciliationInterval == 0 { - c.ProcessorReconciliationInterval = defaultProcessorReconciliationInterval + if cfg.ProcessorReconciliationInterval == 0 { + cfg.ProcessorReconciliationInterval = defaultProcessorReconciliationInterval } - if c.EnqueueChannelBufferSize == 0 { - c.EnqueueChannelBufferSize = defaultEnqueueChannelBufferSize + if cfg.EnqueueChannelBufferSize == 0 { + cfg.EnqueueChannelBufferSize = defaultEnqueueChannelBufferSize } - return nil + return cfg, nil } // deepCopy creates a deep copy of the `Config` object. diff --git a/pkg/epp/flowcontrol/controller/config_test.go b/pkg/epp/flowcontrol/controller/config_test.go index a89db25ff..710df9fa7 100644 --- a/pkg/epp/flowcontrol/controller/config_test.go +++ b/pkg/epp/flowcontrol/controller/config_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewConfig(t *testing.T) { +func TestConfig_ValidateAndApplyDefaults(t *testing.T) { t.Parallel() testCases := []struct { @@ -88,7 +88,7 @@ func TestNewConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() originalInput := tc.input.deepCopy() - validatedCfg, err := newConfig(tc.input) + validatedCfg, err := tc.input.ValidateAndApplyDefaults() if tc.expectErr { require.Error(t, err, "expected an error but got nil") diff --git a/pkg/epp/flowcontrol/controller/controller.go b/pkg/epp/flowcontrol/controller/controller.go index e25eda969..8449159b1 100644 --- a/pkg/epp/flowcontrol/controller/controller.go +++ b/pkg/epp/flowcontrol/controller/controller.go @@ -118,13 +118,8 @@ func NewFlowController( logger logr.Logger, opts ...flowControllerOption, ) (*FlowController, error) { - validatedConfig, err := newConfig(config) - if err != nil { - return nil, fmt.Errorf("invalid flow controller configuration: %w", err) - } - fc := &FlowController{ - config: *validatedConfig, + config: *config.deepCopy(), registry: registry, saturationDetector: sd, clock: clock.RealClock{}, diff --git a/pkg/epp/flowcontrol/controller/controller_test.go b/pkg/epp/flowcontrol/controller/controller_test.go index 511bae8ce..435cdb510 100644 --- a/pkg/epp/flowcontrol/controller/controller_test.go +++ b/pkg/epp/flowcontrol/controller/controller_test.go @@ -273,23 +273,6 @@ func newTestRequest(ctx context.Context, key types.FlowKey) *typesmocks.MockFlow // --- Test Cases --- -func TestNewFlowController(t *testing.T) { - t.Parallel() - - t.Run("ErrorOnInvalidConfig", func(t *testing.T) { - t.Parallel() - invalidCfg := Config{ProcessorReconciliationInterval: -1 * time.Second} - _, err := NewFlowController( - context.Background(), - invalidCfg, - &mockRegistryClient{}, - &mocks.MockSaturationDetector{}, - logr.Discard(), - ) - require.Error(t, err, "NewFlowController must return an error for invalid configuration") - }) -} - func TestFlowController_EnqueueAndWait(t *testing.T) { t.Parallel() @@ -813,6 +796,7 @@ func TestFlowController_Concurrency(t *testing.T) { // Use a generous buffer to prevent flakes in the test due to transient queuing delays. EnqueueChannelBufferSize: numRequests, DefaultRequestTTL: 1 * time.Second, + ExpiryCleanupInterval: 100 * time.Millisecond, }, mockRegistry) var wg sync.WaitGroup diff --git a/pkg/epp/flowcontrol/registry/config.go b/pkg/epp/flowcontrol/registry/config.go index f9ad8e637..af345a665 100644 --- a/pkg/epp/flowcontrol/registry/config.go +++ b/pkg/epp/flowcontrol/registry/config.go @@ -112,7 +112,7 @@ type Config struct { // that operate at this priority level. type PriorityBandConfig struct { // Priority is the unique numerical priority level for this band. - // Convention: Lower numerical values indicate lower priority. + // Convention: Highest numeric value corresponds to highest priority (centered on 0). // Required. Priority int @@ -140,20 +140,6 @@ type PriorityBandConfig struct { MaxBytes uint64 } -// NewConfig performs validation and initialization, returning a guaranteed-valid `Config` object. -// This is the required constructor for creating a new configuration. It applies provided functional options (primarily -// for testing) and does not mutate the input `cfg`. -func NewConfig(cfg Config, opts ...configOption) (*Config, error) { - newCfg := cfg.deepCopy() - for _, opt := range opts { - opt(newCfg) - } - if err := newCfg.validateAndApplyDefaults(); err != nil { - return nil, err - } - return newCfg, nil -} - // ============================================================================= // Shard-Level Configuration // ============================================================================= @@ -205,52 +191,55 @@ func (sc *ShardConfig) getBandConfig(priority int) (*ShardPriorityBandConfig, er // --- Validation and Defaulting --- -// validateAndApplyDefaults checks the global configuration for validity (including plugin compatibility) and mutates -// the receiver to populate any empty fields with system defaults. It also initializes internal lookup maps. -func (c *Config) validateAndApplyDefaults() error { +// ValidateAndApplyDefaults checks the global configuration for validity and then creates a new `Config` object, +// populating any empty fields with system defaults. +// It does not mutate the receiver. +func (c *Config) ValidateAndApplyDefaults() (*Config, error) { + cfg := c.deepCopy() + // Apply defaults to top-level fields. - if c.InitialShardCount <= 0 { - c.InitialShardCount = defaultInitialShardCount + if cfg.InitialShardCount <= 0 { + cfg.InitialShardCount = defaultInitialShardCount } - if c.FlowGCTimeout <= 0 { - c.FlowGCTimeout = defaultFlowGCTimeout + if cfg.FlowGCTimeout <= 0 { + cfg.FlowGCTimeout = defaultFlowGCTimeout } - if c.EventChannelBufferSize <= 0 { - c.EventChannelBufferSize = defaultEventChannelBufferSize + if cfg.EventChannelBufferSize <= 0 { + cfg.EventChannelBufferSize = defaultEventChannelBufferSize } // Ensure the DI factories are initialized for production use if `NewConfig` was called without options. - if c.interFlowDispatchPolicyFactory == nil { - c.interFlowDispatchPolicyFactory = inter.NewPolicyFromName + if cfg.interFlowDispatchPolicyFactory == nil { + cfg.interFlowDispatchPolicyFactory = inter.NewPolicyFromName } - if c.intraFlowDispatchPolicyFactory == nil { - c.intraFlowDispatchPolicyFactory = intra.NewPolicyFromName + if cfg.intraFlowDispatchPolicyFactory == nil { + cfg.intraFlowDispatchPolicyFactory = intra.NewPolicyFromName } - if c.queueFactory == nil { - c.queueFactory = queue.NewQueueFromName + if cfg.queueFactory == nil { + cfg.queueFactory = queue.NewQueueFromName } - if len(c.PriorityBands) == 0 { - return errors.New("config validation failed: at least one priority band must be defined") + if len(cfg.PriorityBands) == 0 { + return nil, errors.New("config validation failed: at least one priority band must be defined") } // Validate and default each priority band. priorities := make(map[int]struct{}) priorityNames := make(map[string]struct{}) - c.priorityBandMap = make(map[int]*PriorityBandConfig, len(c.PriorityBands)) + cfg.priorityBandMap = make(map[int]*PriorityBandConfig, len(cfg.PriorityBands)) - for i := range c.PriorityBands { - band := &c.PriorityBands[i] + for i := range cfg.PriorityBands { + band := &cfg.PriorityBands[i] if _, exists := priorities[band.Priority]; exists { - return fmt.Errorf("config validation failed: duplicate priority level %d found", band.Priority) + return nil, fmt.Errorf("config validation failed: duplicate priority level %d found", band.Priority) } priorities[band.Priority] = struct{}{} if band.PriorityName == "" { - return fmt.Errorf("config validation failed: PriorityName is required for priority band %d", band.Priority) + return nil, fmt.Errorf("config validation failed: PriorityName is required for priority band %d", band.Priority) } if _, exists := priorityNames[band.PriorityName]; exists { - return fmt.Errorf("config validation failed: duplicate priority name %q found", band.PriorityName) + return nil, fmt.Errorf("config validation failed: duplicate priority name %q found", band.PriorityName) } priorityNames[band.PriorityName] = struct{}{} @@ -267,12 +256,12 @@ func (c *Config) validateAndApplyDefaults() error { band.MaxBytes = defaultPriorityBandMaxBytes } - if err := c.validateBandCompatibility(*band); err != nil { - return err + if err := cfg.validateBandCompatibility(*band); err != nil { + return nil, err } - c.priorityBandMap[band.Priority] = band + cfg.priorityBandMap[band.Priority] = band } - return nil + return cfg, nil } // validateBandCompatibility verifies that a band's configured queue type has the necessary capabilities. @@ -423,6 +412,18 @@ func withQueueFactory(factory queueFactory) configOption { } } +// newConfig creates a new validated and defaulted `Config` object. +// It applies provided test-only functional options before validation and defaulting. +// It does not mutate the input `cfg`. +// test-only +func newConfig(cfg Config, opts ...configOption) (*Config, error) { + newCfg := cfg.deepCopy() + for _, opt := range opts { + opt(newCfg) + } + return newCfg.ValidateAndApplyDefaults() +} + // --- Internal Utilities --- // deepCopy creates a deep copy of the `Config` object. @@ -436,7 +437,6 @@ func (c *Config) deepCopy() *Config { FlowGCTimeout: c.FlowGCTimeout, EventChannelBufferSize: c.EventChannelBufferSize, PriorityBands: make([]PriorityBandConfig, len(c.PriorityBands)), - priorityBandMap: make(map[int]*PriorityBandConfig, len(c.PriorityBands)), interFlowDispatchPolicyFactory: c.interFlowDispatchPolicyFactory, intraFlowDispatchPolicyFactory: c.intraFlowDispatchPolicyFactory, queueFactory: c.queueFactory, @@ -445,11 +445,14 @@ func (c *Config) deepCopy() *Config { // PriorityBandConfig contains only value types, so a slice copy is sufficient for a deep copy. copy(newCfg.PriorityBands, c.PriorityBands) - // Crucial: We must rebuild the map and take the address of the elements within the new slice (`newCfg.PriorityBands`) - // to ensure the map pointers are correct for the newly created `Config` instance. - for i := range newCfg.PriorityBands { - band := &newCfg.PriorityBands[i] - newCfg.priorityBandMap[band.Priority] = band + if c.priorityBandMap != nil { + newCfg.priorityBandMap = make(map[int]*PriorityBandConfig, len(c.PriorityBands)) + // Crucial: We must rebuild the map and take the address of the elements within the new slice (`newCfg.PriorityBands`) + // to ensure the map pointers are correct for the newly created `Config` instance. + for i := range newCfg.PriorityBands { + band := &newCfg.PriorityBands[i] + newCfg.priorityBandMap[band.Priority] = band + } } return newCfg } diff --git a/pkg/epp/flowcontrol/registry/config_test.go b/pkg/epp/flowcontrol/registry/config_test.go index ea9c19b7d..47814ae6e 100644 --- a/pkg/epp/flowcontrol/registry/config_test.go +++ b/pkg/epp/flowcontrol/registry/config_test.go @@ -35,7 +35,7 @@ import ( "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/plugins/queue/listqueue" ) -func TestConfig_NewConfig(t *testing.T) { +func TestConfig_ValidateAndApplyDefaults(t *testing.T) { t.Parallel() testCases := []struct { @@ -286,20 +286,24 @@ func TestConfig_NewConfig(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() - originalInputCopy := tc.input.deepCopy() - newCfg, err := NewConfig(tc.input, tc.opts...) + originalInput := tc.input.deepCopy() + validatedCfg, err := newConfig(tc.input, tc.opts...) + if tc.expectErr { - require.Error(t, err, "NewConfig should have returned an error") + require.Error(t, err, "expected an error but got nil") if tc.expectedErrIs != nil { - assert.ErrorIs(t, err, tc.expectedErrIs, "Error should wrap the expected error type") + assert.ErrorIs(t, err, tc.expectedErrIs, "error should wrap the expected error type") } - assert.Nil(t, newCfg, "On error, the returned config should be nil") + assert.Nil(t, validatedCfg, "validatedCfg should be nil on error") } else { - require.NoError(t, err, "NewConfig should not have returned an error") - require.NotNil(t, newCfg, "On success, the returned config should not be nil") + require.NoError(t, err, "expected no error but got: %v", err) + require.NotNil(t, validatedCfg, "validatedCfg should not be nil on success") if tc.assertion != nil { - tc.assertion(t, *originalInputCopy, newCfg) + tc.assertion(t, *originalInput, validatedCfg) } + + // Ensure the original config is not mutated. + assert.Equal(t, *originalInput, tc.input, "input config should not be mutated") } }) } @@ -307,7 +311,7 @@ func TestConfig_NewConfig(t *testing.T) { func TestConfig_Partition(t *testing.T) { t.Parallel() - baseCfg, err := NewConfig(Config{ + baseCfg, err := newConfig(Config{ MaxBytes: 103, // Will not distribute evenly PriorityBands: []PriorityBandConfig{ {Priority: 1, PriorityName: "High", MaxBytes: 55}, // Will not distribute evenly @@ -391,7 +395,7 @@ func TestConfig_Partition(t *testing.T) { func TestConfig_GetBandConfig(t *testing.T) { t.Parallel() - cfg, err := NewConfig(Config{ + cfg, err := newConfig(Config{ PriorityBands: []PriorityBandConfig{ {Priority: 10, PriorityName: "High"}, }, @@ -427,7 +431,7 @@ func TestConfig_DeepCopy(t *testing.T) { }, } // Create a fully initialized "original" config to be the source of the copy. - original, err := NewConfig(baseCfg) + original, err := newConfig(baseCfg) require.NoError(t, err, "Setup for deep copy should not fail") t.Run("ShouldReturnNil_ForNilReceiver", func(t *testing.T) { @@ -481,7 +485,7 @@ func TestConfig_DeepCopy(t *testing.T) { func TestShardConfig_GetBandConfig(t *testing.T) { t.Parallel() - baseCfg, err := NewConfig(Config{ + baseCfg, err := newConfig(Config{ PriorityBands: []PriorityBandConfig{ {Priority: 10, PriorityName: "High"}, {Priority: 20, PriorityName: "Low"}, diff --git a/pkg/epp/flowcontrol/registry/registry.go b/pkg/epp/flowcontrol/registry/registry.go index 5f295541d..3a73ef706 100644 --- a/pkg/epp/flowcontrol/registry/registry.go +++ b/pkg/epp/flowcontrol/registry/registry.go @@ -148,17 +148,13 @@ func withClock(clk clock.WithTickerAndDelayedExecution) RegistryOption { // NewFlowRegistry creates and initializes a new `FlowRegistry` instance. func NewFlowRegistry(config Config, logger logr.Logger, opts ...RegistryOption) (*FlowRegistry, error) { - validatedConfig, err := NewConfig(config) - if err != nil { - return nil, fmt.Errorf("master configuration is invalid: %w", err) - } - + cfg := config.deepCopy() fr := &FlowRegistry{ - config: validatedConfig, + config: cfg, logger: logger.WithName("flow-registry"), activeShards: []*registryShard{}, drainingShards: make(map[string]*registryShard), - perPriorityBandStats: make(map[int]*bandStats, len(validatedConfig.PriorityBands)), + perPriorityBandStats: make(map[int]*bandStats, len(cfg.PriorityBands)), } for _, opt := range opts { @@ -173,7 +169,7 @@ func NewFlowRegistry(config Config, logger logr.Logger, opts ...RegistryOption) fr.perPriorityBandStats[band.Priority] = &bandStats{} } - if err := fr.updateShardCount(validatedConfig.InitialShardCount); err != nil { + if err := fr.updateShardCount(cfg.InitialShardCount); err != nil { return nil, fmt.Errorf("failed to initialize shards: %w", err) } fr.logger.V(logging.DEFAULT).Info("FlowRegistry initialized successfully") diff --git a/pkg/epp/flowcontrol/registry/registry_test.go b/pkg/epp/flowcontrol/registry/registry_test.go index b63b151c2..b5bc322cb 100644 --- a/pkg/epp/flowcontrol/registry/registry_test.go +++ b/pkg/epp/flowcontrol/registry/registry_test.go @@ -73,9 +73,12 @@ func newRegistryTestHarness(t *testing.T, opts harnessOptions) *registryTestHarn config.InitialShardCount = opts.initialShardCount } + validatedCfg, err := config.ValidateAndApplyDefaults() + require.NoError(t, err, "Test setup: validating config should not fail") + fakeClock := testclock.NewFakeClock(time.Now()) registryOpts := []RegistryOption{withClock(fakeClock)} - fr, err := NewFlowRegistry(config, logr.Discard(), registryOpts...) + fr, err := NewFlowRegistry(*validatedCfg, logr.Discard(), registryOpts...) require.NoError(t, err, "Test setup: NewFlowRegistry should not fail") // Start the GC loop in the background. @@ -132,69 +135,9 @@ func (h *registryTestHarness) openConnectionOnFlow(key types.FlowKey) { func TestFlowRegistry_New(t *testing.T) { t.Parallel() - t.Run("ShouldApplyDefaults_WhenInitialized", func(t *testing.T) { - t.Parallel() - config := Config{PriorityBands: []PriorityBandConfig{{Priority: highPriority, PriorityName: "DefaultedBand"}}} - fr, err := NewFlowRegistry(config, logr.Discard()) - require.NoError(t, err, "Creating a valid registry with defaults should not fail") - assert.Equal(t, defaultInitialShardCount, fr.config.InitialShardCount, "InitialShardCount should be defaulted") - assert.Equal(t, defaultFlowGCTimeout, fr.config.FlowGCTimeout, "FlowGCTimeout should be defaulted") - assert.Equal(t, defaultEventChannelBufferSize, fr.config.EventChannelBufferSize, - "EventChannelBufferSize should be defaulted") - assert.Len(t, fr.allShards, defaultInitialShardCount, - "Registry should be initialized with the default number of shards") - bandConf, err := fr.config.getBandConfig(highPriority) - require.NoError(t, err, "Getting the defaulted band config should not fail") - assert.Equal(t, defaultPriorityBandMaxBytes, bandConf.MaxBytes, "Priority band MaxBytes should be defaulted") - }) - - t.Run("ShouldFail_OnInvalidConfiguration", func(t *testing.T) { - t.Parallel() - testCases := []struct { - name string - config Config - expectErrSubStr string - }{ - { - name: "WhenNoPriorityBandsAreDefined", - config: Config{}, - expectErrSubStr: "at least one priority band must be defined", - }, - { - name: "WhenPriorityLevelsAreDuplicated", - config: Config{ - PriorityBands: []PriorityBandConfig{ - {Priority: highPriority, PriorityName: "A"}, - {Priority: highPriority, PriorityName: "B"}, - }, - }, - expectErrSubStr: fmt.Sprintf("duplicate priority level %d", highPriority), - }, - { - name: "WhenPriorityNamesAreDuplicated", - config: Config{ - PriorityBands: []PriorityBandConfig{ - {Priority: highPriority, PriorityName: "A"}, - {Priority: lowPriority, PriorityName: "A"}, - }, - }, - expectErrSubStr: `duplicate priority name "A"`, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - _, err := NewFlowRegistry(tc.config, logr.Discard()) - require.Error(t, err, "NewFlowRegistry should fail with an invalid config") - assert.Contains(t, err.Error(), tc.expectErrSubStr, "Error message should contain the expected reason") - }) - } - }) - t.Run("ShouldFail_WhenInitialShardCreationFails", func(t *testing.T) { t.Parallel() - config, err := NewConfig( + config, err := newConfig( Config{PriorityBands: []PriorityBandConfig{{Priority: highPriority, PriorityName: "A"}}}, withInterFlowDispatchPolicyFactory(func(inter.RegisteredPolicyName) (framework.InterFlowDispatchPolicy, error) { return nil, errors.New("injected factory failure") @@ -544,14 +487,6 @@ func TestFlowRegistry_UpdateShardCount(t *testing.T) { expectedPartitionedGlobalCapacities: map[uint64]int{25: 4}, expectedPartitionedBandCapacities: map[uint64]int{12: 2, 13: 2}, }, - { - name: "Succeeds_ScaleUp_FromZero", - initialShardCount: 0, - targetShardCount: 4, - expectedActiveCount: 4, - expectedPartitionedGlobalCapacities: map[uint64]int{25: 4}, - expectedPartitionedBandCapacities: map[uint64]int{12: 2, 13: 2}, - }, { name: "Succeeds_ScaleDown_ToOne", initialShardCount: 3, diff --git a/pkg/epp/flowcontrol/registry/shard_test.go b/pkg/epp/flowcontrol/registry/shard_test.go index afb2fac34..23bf81325 100644 --- a/pkg/epp/flowcontrol/registry/shard_test.go +++ b/pkg/epp/flowcontrol/registry/shard_test.go @@ -59,7 +59,7 @@ type shardTestHarness struct { // newShardTestHarness initializes a `shardTestHarness` with a default configuration. func newShardTestHarness(t *testing.T) *shardTestHarness { t.Helper() - globalConfig, err := NewConfig(Config{ + globalConfig, err := newConfig(Config{ PriorityBands: []PriorityBandConfig{ {Priority: highPriority, PriorityName: "High"}, {Priority: lowPriority, PriorityName: "Low"}, @@ -146,7 +146,7 @@ func TestShard_New(t *testing.T) { t.Run("ShouldFail_WhenInterFlowPolicyFactoryFails", func(t *testing.T) { t.Parallel() - shardConfig, _ := NewConfig(Config{PriorityBands: []PriorityBandConfig{ + shardConfig, _ := newConfig(Config{PriorityBands: []PriorityBandConfig{ {Priority: highPriority, PriorityName: "High"}, }}) failingFactory := func(inter.RegisteredPolicyName) (framework.InterFlowDispatchPolicy, error) { From a673d90e0a5154b05cba6b8f1dff1289f7b88730 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:32:14 -0700 Subject: [PATCH 037/133] chore(deps): bump google.golang.org/grpc from 1.75.0 to 1.75.1 (#1596) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.75.0 to 1.75.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.75.0...v1.75.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.75.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index aff181480..54478ae63 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.17.0 - google.golang.org/grpc v1.75.0 + google.golang.org/grpc v1.75.1 google.golang.org/protobuf v1.36.8 k8s.io/api v0.34.0 k8s.io/apiextensions-apiserver v0.34.0 diff --git a/go.sum b/go.sum index 1f9a3bb1f..a1fbded1d 100644 --- a/go.sum +++ b/go.sum @@ -343,8 +343,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1: google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= -google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 1dd678bd9dd72959bca0b87a166d8f0cf892c75b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:32:21 -0700 Subject: [PATCH 038/133] chore(deps): bump github.com/onsi/ginkgo/v2 from 2.25.1 to 2.25.3 (#1597) Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.25.1 to 2.25.3. - [Release notes](https://github.com/onsi/ginkgo/releases) - [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/ginkgo/compare/v2.25.1...v2.25.3) --- updated-dependencies: - dependency-name: github.com/onsi/ginkgo/v2 dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 54478ae63..2f80bb1c4 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 - github.com/onsi/ginkgo/v2 v2.25.1 + github.com/onsi/ginkgo/v2 v2.25.3 github.com/onsi/gomega v1.38.2 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 diff --git a/go.sum b/go.sum index a1fbded1d..4a45989e6 100644 --- a/go.sum +++ b/go.sum @@ -196,8 +196,8 @@ github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY= -github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk= +github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= +github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= From 8e3c7e27a3686735ea5f3f54eca42390e447f86f Mon Sep 17 00:00:00 2001 From: Rahul Gurnani Date: Mon, 15 Sep 2025 17:52:07 -0700 Subject: [PATCH 039/133] Deprecate inferencepool-resources.yaml (#1586) * Update docs to use helm charts * Update formatting * Remove inferencepool resources yaml --- hack/release-quickstart.sh | 10 ++---- site-src/implementations/model-servers.md | 39 ++++++++++------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/hack/release-quickstart.sh b/hack/release-quickstart.sh index 22c705184..04b79a3ef 100755 --- a/hack/release-quickstart.sh +++ b/hack/release-quickstart.sh @@ -74,25 +74,21 @@ sed -i.bak "s|kubectl apply -k https://github.com/kubernetes-sigs/gateway-api-in # ----------------------------------------------------------------------------- # Update image references # ----------------------------------------------------------------------------- -EPP="config/manifests/inferencepool-resources.yaml" #TODO: Put all helm values files into an array to loop over EPP_HELM="config/charts/inferencepool/values.yaml" BBR_HELM="config/charts/body-based-routing/values.yaml" CONFORMANCE_MANIFESTS="conformance/resources/base.yaml" -echo "Updating ${EPP}, ${EPP_HELM}, ${BBR_HELM}, and ${CONFORMANCE_MANIFESTS} ..." +echo "Updating ${EPP_HELM}, ${BBR_HELM}, and ${CONFORMANCE_MANIFESTS} ..." # Update the container tag. -sed -i.bak -E "s|(us-central1-docker\.pkg\.dev/k8s-staging-images/gateway-api-inference-extension/epp:)[^\"[:space:]]+|\1${RELEASE_TAG}|g" "$EPP" sed -i.bak -E "s|(tag: )[^\"[:space:]]+|\1${RELEASE_TAG}|g" "$EPP_HELM" sed -i.bak -E "s|(tag: )[^\"[:space:]]+|\1${RELEASE_TAG}|g" "$BBR_HELM" sed -i.bak -E "s|(us-central1-docker\.pkg\.dev/k8s-staging-images/gateway-api-inference-extension/epp:)[^\"[:space:]]+|\1${RELEASE_TAG}|g" "$CONFORMANCE_MANIFESTS" # Update the container image pull policy. -sed -i.bak '/us-central1-docker.pkg.dev\/k8s-staging-images\/gateway-api-inference-extension\/epp/{n;s/Always/IfNotPresent/;}' "$EPP" sed -i.bak '/us-central1-docker.pkg.dev\/k8s-staging-images\/gateway-api-inference-extension\/epp/{n;s/Always/IfNotPresent/;}' "$CONFORMANCE_MANIFESTS" # Update the container registry. -sed -i.bak -E "s|us-central1-docker\.pkg\.dev/k8s-staging-images|registry.k8s.io|g" "$EPP" sed -i.bak -E "s|us-central1-docker\.pkg\.dev/k8s-staging-images|registry.k8s.io|g" "$EPP_HELM" sed -i.bak -E "s|us-central1-docker\.pkg\.dev/k8s-staging-images|registry.k8s.io|g" "$BBR_HELM" sed -i.bak -E "s|us-central1-docker\.pkg\.dev/k8s-staging-images|registry.k8s.io|g" "$CONFORMANCE_MANIFESTS" @@ -139,8 +135,8 @@ sed -i.bak -E "s|us-central1-docker\.pkg\.dev/k8s-staging-images|registry.k8s.io # ----------------------------------------------------------------------------- # Stage the changes # ----------------------------------------------------------------------------- -echo "Staging $VERSION_FILE $UPDATED_CRD $README $EPP $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY files..." -git add $VERSION_FILE $UPDATED_CRD $README $EPP $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY +echo "Staging $VERSION_FILE $UPDATED_CRD $README $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY files..." +git add $VERSION_FILE $UPDATED_CRD $README $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY # ----------------------------------------------------------------------------- # Cleanup backup files and finish diff --git a/site-src/implementations/model-servers.md b/site-src/implementations/model-servers.md index da9968fad..ed57e1252 100644 --- a/site-src/implementations/model-servers.md +++ b/site-src/implementations/model-servers.md @@ -19,34 +19,29 @@ vLLM is configured as the default in the [endpoint picker extension](https://git Triton specific metric names need to be specified when starting the EPP. -### Option 1: Use Helm +Use `--set inferencePool.modelServerType=triton-tensorrt-llm` to install the `inferencepool` via helm. See the [`inferencepool` helm guide](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/config/charts/inferencepool/README.md) for more details. -Use `--set inferencePool.modelServerType=triton-tensorrt-llm` to install the [`inferencepool` via helm](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/42eb5ff1c5af1275df43ac384df0ddf20da95134/config/charts/inferencepool). See the [`inferencepool` helm guide](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/42eb5ff1c5af1275df43ac384df0ddf20da95134/config/charts/inferencepool/README.md) for more details. + Add the following to the `flags` in the helm chart as [flags to EPP](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/29ea29028496a638b162ff287c62c0087211bbe5/config/charts/inferencepool/values.yaml#L36) -### Option 2: Edit EPP deployment yaml - - Add the following to the `args` of the [EPP deployment](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/42eb5ff1c5af1275df43ac384df0ddf20da95134/config/manifests/inferencepool-resources.yaml#L32) - - ``` -- --total-queued-requests-metric -- "nv_trt_llm_request_metrics{request_type=waiting}" -- --kv-cache-usage-percentage-metric -- "nv_trt_llm_kv_cache_block_metrics{kv_cache_block_type=fraction}" -- --lora-info-metric -- "" # Set an empty metric to disable LoRA metric scraping as they are not supported by Triton yet. +``` +- name=total-queued-requests-metric + value="nv_trt_llm_request_metrics{request_type=waiting}" +- name=kv-cache-usage-percentage-metric + value="nv_trt_llm_kv_cache_block_metrics{kv_cache_block_type=fraction}" +- name=lora-info-metric + value="" # Set an empty metric to disable LoRA metric scraping as they are not supported by Triton yet. ``` ## SGLang -### Edit EPP deployment yaml + Add the following `flags` while deploying using helm charts in the [EPP deployment](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/29ea29028496a638b162ff287c62c0087211bbe5/config/charts/inferencepool/values.yaml#L36) - Add the following to the `args` of the [EPP deployment](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/42eb5ff1c5af1275df43ac384df0ddf20da95134/config/manifests/inferencepool-resources.yaml#L32) ``` -- --totalQueuedRequestsMetric -- "sglang:num_queue_reqs" -- --kvCacheUsagePercentageMetric -- "sglang:token_usage" -- --lora-info-metric -- "" # Set an empty metric to disable LoRA metric scraping as they are not supported by SGLang yet. -``` +- name=total-queued-requests-metric + value="sglang:num_queue_reqs" +- name=kv-cache-usage-percentage-metric + value="sglang:token_usage" +- name=lora-info-metric + value="" # Set an empty metric to disable LoRA metric scraping as they are not supported by SGLang yet. +``` \ No newline at end of file From fae5538dd02a0b0a1481ef50899af02d40a63e38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 01:20:11 -0700 Subject: [PATCH 040/133] chore(deps): bump the kubernetes group with 6 updates (#1594) Bumps the kubernetes group with 6 updates: | Package | From | To | | --- | --- | --- | | [k8s.io/api](https://github.com/kubernetes/api) | `0.34.0` | `0.34.1` | | [k8s.io/apiextensions-apiserver](https://github.com/kubernetes/apiextensions-apiserver) | `0.34.0` | `0.34.1` | | [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) | `0.34.0` | `0.34.1` | | [k8s.io/client-go](https://github.com/kubernetes/client-go) | `0.34.0` | `0.34.1` | | [k8s.io/code-generator](https://github.com/kubernetes/code-generator) | `0.34.0` | `0.34.1` | | [k8s.io/component-base](https://github.com/kubernetes/component-base) | `0.34.0` | `0.34.1` | Updates `k8s.io/api` from 0.34.0 to 0.34.1 - [Commits](https://github.com/kubernetes/api/compare/v0.34.0...v0.34.1) Updates `k8s.io/apiextensions-apiserver` from 0.34.0 to 0.34.1 - [Release notes](https://github.com/kubernetes/apiextensions-apiserver/releases) - [Commits](https://github.com/kubernetes/apiextensions-apiserver/compare/v0.34.0...v0.34.1) Updates `k8s.io/apimachinery` from 0.34.0 to 0.34.1 - [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.0...v0.34.1) Updates `k8s.io/client-go` from 0.34.0 to 0.34.1 - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.34.0...v0.34.1) Updates `k8s.io/code-generator` from 0.34.0 to 0.34.1 - [Commits](https://github.com/kubernetes/code-generator/compare/v0.34.0...v0.34.1) Updates `k8s.io/component-base` from 0.34.0 to 0.34.1 - [Commits](https://github.com/kubernetes/component-base/compare/v0.34.0...v0.34.1) --- updated-dependencies: - dependency-name: k8s.io/api dependency-version: 0.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kubernetes - dependency-name: k8s.io/apiextensions-apiserver dependency-version: 0.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kubernetes - dependency-name: k8s.io/apimachinery dependency-version: 0.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kubernetes - dependency-name: k8s.io/client-go dependency-version: 0.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kubernetes - dependency-name: k8s.io/code-generator dependency-version: 0.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kubernetes - dependency-name: k8s.io/component-base dependency-version: 0.34.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: kubernetes ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 2f80bb1c4..077e74706 100644 --- a/go.mod +++ b/go.mod @@ -23,12 +23,12 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/grpc v1.75.1 google.golang.org/protobuf v1.36.8 - k8s.io/api v0.34.0 - k8s.io/apiextensions-apiserver v0.34.0 - k8s.io/apimachinery v0.34.0 - k8s.io/client-go v0.34.0 - k8s.io/code-generator v0.34.0 - k8s.io/component-base v0.34.0 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/code-generator v0.34.1 + k8s.io/component-base v0.34.1 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/controller-runtime v0.21.0 sigs.k8s.io/controller-tools v0.19.0 @@ -124,7 +124,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.34.0 // indirect + k8s.io/apiserver v0.34.1 // indirect k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect diff --git a/go.sum b/go.sum index 4a45989e6..08db402dc 100644 --- a/go.sum +++ b/go.sum @@ -361,20 +361,20 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= -k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= -k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= -k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= -k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= -k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= -k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= -k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= -k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= -k8s.io/code-generator v0.34.0 h1:Ze2i1QsvUprIlX3oHiGv09BFQRLCz+StA8qKwwFzees= -k8s.io/code-generator v0.34.0/go.mod h1:Py2+4w2HXItL8CGhks8uI/wS3Y93wPKO/9mBQUYNua0= -k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= -k8s.io/component-base v0.34.0/go.mod h1:RSCqUdvIjjrEm81epPcjQ/DS+49fADvGSCkIP3IC6vg= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/code-generator v0.34.1 h1:WpphT26E+j7tEgIUfFr5WfbJrktCGzB3JoJH9149xYc= +k8s.io/code-generator v0.34.1/go.mod h1:DeWjekbDnJWRwpw3s0Jat87c+e0TgkxoR4ar608yqvg= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f h1:SLb+kxmzfA87x4E4brQzB33VBbT2+x7Zq9ROIHmGn9Q= k8s.io/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= From cd5d62714d22121a0a6a27bba00db0d09268feaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 04:16:12 -0700 Subject: [PATCH 041/133] chore(deps): bump google.golang.org/protobuf from 1.36.8 to 1.36.9 (#1595) Bumps google.golang.org/protobuf from 1.36.8 to 1.36.9. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 077e74706..58277951a 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/sync v0.17.0 google.golang.org/grpc v1.75.1 - google.golang.org/protobuf v1.36.8 + google.golang.org/protobuf v1.36.9 k8s.io/api v0.34.1 k8s.io/apiextensions-apiserver v0.34.1 k8s.io/apimachinery v0.34.1 diff --git a/go.sum b/go.sum index 08db402dc..817b54b73 100644 --- a/go.sum +++ b/go.sum @@ -345,8 +345,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From c78f3d1bebd79bb2b9da803bf647382c7e71e8ef Mon Sep 17 00:00:00 2001 From: Ricardo Pchevuzinske Katz Date: Tue, 16 Sep 2025 14:56:12 -0300 Subject: [PATCH 042/133] Add makefile entries for api-lint (#1384) --- .github/workflows/kal.yml | 8 ++------ Makefile | 12 +++++++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/kal.yml b/.github/workflows/kal.yml index 618256e88..17b6281f5 100644 --- a/.github/workflows/kal.yml +++ b/.github/workflows/kal.yml @@ -20,9 +20,5 @@ jobs: persist-credentials: false - name: Set up Go uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # tag=v5.5.0 - - name: Install Golang CI Lint - run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.2.1 - - name: Build KAL - run: golangci-lint custom - - name: run api linter - run: ./bin/golangci-kube-api-linter run -c ./.golangci-kal.yml ./... + - name: Run API Linter + run: make api-lint \ No newline at end of file diff --git a/Makefile b/Makefile index 8113c33b2..7e5c5e9a3 100644 --- a/Makefile +++ b/Makefile @@ -160,8 +160,12 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes ci-lint: golangci-lint $(GOLANGCI_LINT) run --timeout 15m0s +.PHONY: api-lint +api-lint: golangci-api-lint + $(GOLANGCI_API_LINT) run -c .golangci-kal.yml --timeout 15m0s ./... + .PHONY: verify -verify: vet fmt-verify generate ci-lint verify-all +verify: vet fmt-verify generate ci-lint api-lint verify-all git --no-pager diff --exit-code config api client-go .PHONY: verify-crds @@ -366,6 +370,7 @@ CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest CRD_REF_DOCS ?= $(LOCALBIN)/crd-ref-docs GOLANGCI_LINT = $(LOCALBIN)/golangci-lint +GOLANGCI_API_LINT = $(LOCALBIN)/golangci-kube-api-linter HELM = $(PROJECT_DIR)/bin/helm YQ = $(PROJECT_DIR)/bin/yq KUBECTL_VALIDATE = $(PROJECT_DIR)/bin/kubectl-validate @@ -407,6 +412,11 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) +.PHONY: golangci-api-lint +golangci-api-lint: golangci-lint $(GOLANGCI_API_LINT) ## Download golangci-lint locally if necessary before building KAL +$(GOLANGCI_API_LINT): + $(GOLANGCI_LINT) custom + .PHONY: yq yq: ## Download yq locally if necessary. GOBIN=$(PROJECT_DIR)/bin GO111MODULE=on go install github.com/mikefarah/yq/v4@$(YQ_VERSION) From faa53ce310a774fcd1933b8fb5bb7af34d5403d5 Mon Sep 17 00:00:00 2001 From: Shmuel Kallner Date: Wed, 17 Sep 2025 00:02:11 +0300 Subject: [PATCH 043/133] test: Refactor end to end test code to enable reuse downstream (#1515) * Added a test configuration structure Signed-off-by: Shmuel Kallner * Explot new test configuration structure Signed-off-by: Shmuel Kallner * Refactored private test helper functions into common test code Signed-off-by: Shmuel Kallner * Changes due to refactoring of private test helper functions Signed-off-by: Shmuel Kallner * Create RBAC and ConfigMap before deployment Signed-off-by: Shmuel Kallner * Fixed review issues Signed-off-by: Shmuel Kallner --------- Signed-off-by: Shmuel Kallner --- test/e2e/epp/e2e_suite_test.go | 310 ++++-------------- test/e2e/epp/e2e_test.go | 58 ++-- test/testdata/inferencepool-e2e.yaml | 162 ++++----- .../inferencepool-leader-election-e2e.yaml | 170 +++++----- test/utils/utils.go | 285 +++++++++++++--- 5 files changed, 501 insertions(+), 484 deletions(-) diff --git a/test/e2e/epp/e2e_suite_test.go b/test/e2e/epp/e2e_suite_test.go index e6e5b83ba..b41f108f8 100644 --- a/test/e2e/epp/e2e_suite_test.go +++ b/test/e2e/epp/e2e_suite_test.go @@ -17,7 +17,6 @@ limitations under the License. package epp import ( - "context" "errors" "fmt" "os" @@ -29,34 +28,20 @@ import ( "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/config" infextv1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" infextv1a2 "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/env" testutils "sigs.k8s.io/gateway-api-inference-extension/test/utils" ) const ( - // defaultExistsTimeout is the default timeout for a resource to exist in the api server. - defaultExistsTimeout = 30 * time.Second - // defaultReadyTimeout is the default timeout for a resource to report a ready state. - defaultReadyTimeout = 3 * time.Minute - // defaultModelReadyTimeout is the default timeout for the model server deployment to report a ready state. - defaultModelReadyTimeout = 10 * time.Minute // defaultCurlTimeout is the default timeout for the curl command to get a response. defaultCurlTimeout = 30 * time.Second - // defaultInterval is the default interval to check if a resource exists or ready conditions. - defaultInterval = time.Millisecond * 250 // defaultCurlInterval is the default interval to run the test curl command. defaultCurlInterval = time.Second * 5 // defaultNsName is the default name of the Namespace used for tests. Can override using the E2E_NS environment variable. @@ -100,13 +85,8 @@ const ( const e2eLeaderElectionEnabledEnvVar = "E2E_LEADER_ELECTION_ENABLED" var ( - ctx = context.Background() - cli client.Client + testConfig *testutils.TestConfig // Required for exec'ing in curl pod - kubeCli *kubernetes.Clientset - scheme = runtime.NewScheme() - cfg = config.GetConfigOrDie() - nsName string e2eImage string leaderElectionEnabled bool ) @@ -119,10 +99,12 @@ func TestAPIs(t *testing.T) { } var _ = ginkgo.BeforeSuite(func() { - nsName = os.Getenv("E2E_NS") + nsName := os.Getenv("E2E_NS") if nsName == "" { nsName = defaultNsName } + testConfig = testutils.NewTestConfig(nsName) + e2eImage = os.Getenv("E2E_IMAGE") gomega.Expect(e2eImage).NotTo(gomega.BeEmpty(), "E2E_IMAGE environment variable is not set") @@ -143,11 +125,11 @@ func setupInfra() { // run this before createNs to fail fast in case it doesn't. modelServerManifestPath := readModelServerManifestPath() - createNamespace(cli, nsName) + createNamespace(testConfig) modelServerManifestArray := getYamlsFromModelServerManifest(modelServerManifestPath) if strings.Contains(modelServerManifestArray[0], "hf-token") { - createHfSecret(cli, modelServerSecretManifest) + createHfSecret(testConfig, modelServerSecretManifest) } crds := map[string]string{ "inferencepools.inference.networking.x-k8s.io": xInferPoolManifest, @@ -155,19 +137,19 @@ func setupInfra() { "inferencepools.inference.networking.k8s.io": inferPoolManifest, } - createCRDs(cli, crds) + createCRDs(testConfig, crds) inferExtManifestPath := inferExtManifestDefault if leaderElectionEnabled { inferExtManifestPath = inferExtManifestLeaderElection } - createInferExt(cli, inferExtManifestPath) - createClient(cli, clientManifest) - createEnvoy(cli, envoyManifest) - createMetricsRbac(cli, metricsRbacManifest) + createInferExt(testConfig, inferExtManifestPath) + createClient(testConfig, clientManifest) + createEnvoy(testConfig, envoyManifest) + createMetricsRbac(testConfig, metricsRbacManifest) // Run this step last, as it requires additional time for the model server to become ready. ginkgo.By("Creating model server resources from manifest: " + modelServerManifestPath) - createModelServer(cli, modelServerManifestArray) + createModelServer(testConfig, modelServerManifestArray) } var _ = ginkgo.AfterSuite(func() { @@ -192,77 +174,57 @@ var _ = ginkgo.AfterSuite(func() { // setupSuite initializes the test suite by setting up the Kubernetes client, // loading required API schemes, and validating configuration. func setupSuite() { - gomega.ExpectWithOffset(1, cfg).NotTo(gomega.BeNil()) - - err := clientgoscheme.AddToScheme(scheme) + err := clientgoscheme.AddToScheme(testConfig.Scheme) gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) - err = apiextv1.AddToScheme(scheme) + err = apiextv1.AddToScheme(testConfig.Scheme) gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) - err = infextv1a2.Install(scheme) + err = infextv1a2.Install(testConfig.Scheme) gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) - err = infextv1.Install(scheme) + err = infextv1.Install(testConfig.Scheme) gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred()) - cli, err = client.New(cfg, client.Options{Scheme: scheme}) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - gomega.Expect(cli).NotTo(gomega.BeNil()) - - kubeCli, err = kubernetes.NewForConfig(cfg) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - gomega.Expect(kubeCli).NotTo(gomega.BeNil()) + testConfig.CreateCli() } func cleanupResources() { - if cli == nil { + if testConfig.K8sClient == nil { return // could happen if BeforeSuite had an error } - gomega.Expect(testutils.DeleteClusterResources(ctx, cli)).To(gomega.Succeed()) - gomega.Expect(testutils.DeleteNamespacedResources(ctx, cli, nsName)).To(gomega.Succeed()) + gomega.Expect(testutils.DeleteClusterResources(testConfig)).To(gomega.Succeed()) + gomega.Expect(testutils.DeleteNamespacedResources(testConfig)).To(gomega.Succeed()) } func cleanupInferModelResources() { - gomega.Expect(testutils.DeleteInferenceObjectiveResources(ctx, cli, nsName)).To(gomega.Succeed()) -} - -func getTimeout(key string, fallback time.Duration) time.Duration { - if value, ok := os.LookupEnv(key); ok { - if parsed, err := time.ParseDuration(value); err == nil { - return parsed - } - } - return fallback + gomega.Expect(testutils.DeleteInferenceObjectiveResources(testConfig)).To(gomega.Succeed()) } var ( - existsTimeout = getTimeout("EXISTS_TIMEOUT", defaultExistsTimeout) - readyTimeout = getTimeout("READY_TIMEOUT", defaultReadyTimeout) - modelReadyTimeout = getTimeout("MODEL_READY_TIMEOUT", defaultModelReadyTimeout) - curlTimeout = getTimeout("CURL_TIMEOUT", defaultCurlTimeout) - interval = defaultInterval - curlInterval = defaultCurlInterval + curlTimeout = env.GetEnvDuration("CURL_TIMEOUT", defaultCurlTimeout, ginkgo.GinkgoLogr) + curlInterval = defaultCurlInterval ) -func createNamespace(k8sClient client.Client, ns string) { - ginkgo.By("Creating e2e namespace: " + ns) +func createNamespace(testConfig *testutils.TestConfig) { + ginkgo.By("Creating e2e namespace: " + testConfig.NsName) obj := &corev1.Namespace{ ObjectMeta: v1.ObjectMeta{ - Name: ns, + Name: testConfig.NsName, }, } - err := k8sClient.Create(ctx, obj) + err := testConfig.K8sClient.Create(testConfig.Context, obj) gomega.Expect(err).NotTo(gomega.HaveOccurred(), "Failed to create e2e test namespace") } // namespaceExists ensures that a specified namespace exists and is ready for use. -func namespaceExists(k8sClient client.Client, ns string) { - ginkgo.By("Ensuring namespace exists: " + ns) - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Name: ns}, &corev1.Namespace{}) - }, existsTimeout, interval) +func namespaceExists(testConfig *testutils.TestConfig) { + ginkgo.By("Ensuring namespace exists: " + testConfig.NsName) + testutils.EventuallyExists(testConfig, func() error { + return testConfig.K8sClient.Get(testConfig.Context, + types.NamespacedName{Name: testConfig.NsName}, &corev1.Namespace{}) + }) } // readModelServerManifestPath reads from env var the absolute filepath to model server deployment for testing. @@ -275,57 +237,39 @@ func readModelServerManifestPath() string { func getYamlsFromModelServerManifest(modelServerManifestPath string) []string { ginkgo.By("Ensuring the model server manifest points to an existing file") - modelServerManifestArray := readYaml(modelServerManifestPath) + modelServerManifestArray := testutils.ReadYaml(modelServerManifestPath) gomega.Expect(modelServerManifestArray).NotTo(gomega.BeEmpty()) return modelServerManifestArray } // createCRDs creates the Inference Extension CRDs used for testing. -func createCRDs(k8sClient client.Client, crds map[string]string) { - for name, path := range crds { +func createCRDs(testConfig *testutils.TestConfig, crds map[string]string) { + for _, path := range crds { ginkgo.By("Creating CRD resource from manifest: " + path) - applyYAMLFile(k8sClient, path) - - // Wait for the CRD to exist. - crd := &apiextv1.CustomResourceDefinition{} - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Name: name}, crd) - }, existsTimeout, interval) - - // Wait for the CRD to be established. - testutils.CRDEstablished(ctx, k8sClient, crd, readyTimeout, interval) + testutils.ApplyYAMLFile(testConfig, path) } } // createClient creates the client pod used for testing from the given filePath. -func createClient(k8sClient client.Client, filePath string) { +func createClient(testConfig *testutils.TestConfig, filePath string) { ginkgo.By("Creating client resources from manifest: " + filePath) - applyYAMLFile(k8sClient, filePath) - - // Wait for the pod to exist. - pod := &corev1.Pod{} - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: "curl"}, pod) - }, existsTimeout, interval) - - // Wait for the pod to be ready. - testutils.PodReady(ctx, k8sClient, pod, readyTimeout, interval) + testutils.ApplyYAMLFile(testConfig, filePath) } // createMetricsRbac creates the metrics RBAC resources from the manifest file. -func createMetricsRbac(k8sClient client.Client, filePath string) { - inManifests := readYaml(filePath) +func createMetricsRbac(testConfig *testutils.TestConfig, filePath string) { + inManifests := testutils.ReadYaml(filePath) ginkgo.By("Replacing placeholder namespace with E2E_NS environment variable") outManifests := []string{} for _, m := range inManifests { - outManifests = append(outManifests, strings.ReplaceAll(m, "$E2E_NS", nsName)) + outManifests = append(outManifests, strings.ReplaceAll(m, "$E2E_NS", testConfig.NsName)) } ginkgo.By("Creating RBAC resources for scraping metrics from manifest: " + filePath) - createObjsFromYaml(k8sClient, outManifests) + testutils.CreateObjsFromYaml(testConfig, outManifests) // wait for sa token to exist - testutils.EventuallyExists(ctx, func() error { - token, err := getMetricsReaderToken(k8sClient) + testutils.EventuallyExists(testConfig, func() error { + token, err := getMetricsReaderToken(testConfig.K8sClient) if err != nil { return err } @@ -333,30 +277,21 @@ func createMetricsRbac(k8sClient client.Client, filePath string) { return errors.New("failed to get metrics reader token") } return nil - }, existsTimeout, interval) + }) } // createModelServer creates the model server resources used for testing from the given filePaths. -func createModelServer(k8sClient client.Client, modelServerManifestArray []string) { - createObjsFromYaml(k8sClient, modelServerManifestArray) - - // Wait for the deployment to exist. - deploy := &appsv1.Deployment{} - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: modelServerName}, deploy) - }, existsTimeout, interval) - - // Wait for the deployment to be available. - testutils.DeploymentAvailable(ctx, k8sClient, deploy, modelReadyTimeout, interval) +func createModelServer(testConfig *testutils.TestConfig, modelServerManifestArray []string) { + testutils.CreateObjsFromYaml(testConfig, modelServerManifestArray) } // createHfSecret read HF_TOKEN from env var and creates a secret that contains the access token. -func createHfSecret(k8sClient client.Client, secretPath string) { +func createHfSecret(testConfig *testutils.TestConfig, secretPath string) { ginkgo.By("Ensuring the HF_TOKEN environment variable is set") token := os.Getenv("HF_TOKEN") gomega.Expect(token).NotTo(gomega.BeEmpty(), "HF_TOKEN is not set") - inManifests := readYaml(secretPath) + inManifests := testutils.ReadYaml(secretPath) ginkgo.By("Replacing placeholder secret data with HF_TOKEN environment variable") outManifests := []string{} for _, m := range inManifests { @@ -364,152 +299,49 @@ func createHfSecret(k8sClient client.Client, secretPath string) { } ginkgo.By("Creating model server secret resource") - createObjsFromYaml(k8sClient, outManifests) - - // Wait for the secret to exist before proceeding with test. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: "hf-token"}, &corev1.Secret{}) - }, existsTimeout, interval) + testutils.CreateObjsFromYaml(testConfig, outManifests) } // createEnvoy creates the envoy proxy resources used for testing from the given filePath. -func createEnvoy(k8sClient client.Client, filePath string) { - inManifests := readYaml(filePath) +func createEnvoy(testConfig *testutils.TestConfig, filePath string) { + inManifests := testutils.ReadYaml(filePath) ginkgo.By("Replacing placeholder namespace with E2E_NS environment variable") outManifests := []string{} for _, m := range inManifests { - outManifests = append(outManifests, strings.ReplaceAll(m, "$E2E_NS", nsName)) + outManifests = append(outManifests, strings.ReplaceAll(m, "$E2E_NS", testConfig.NsName)) } ginkgo.By("Creating envoy proxy resources from manifest: " + filePath) - createObjsFromYaml(k8sClient, outManifests) - - // Wait for the configmap to exist before proceeding with test. - cfgMap := &corev1.ConfigMap{} - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: envoyName}, cfgMap) - }, existsTimeout, interval) - - // Wait for the deployment to exist. - deploy := &appsv1.Deployment{} - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: envoyName}, deploy) - }, existsTimeout, interval) - - // Wait for the deployment to be available. - testutils.DeploymentAvailable(ctx, k8sClient, deploy, readyTimeout, interval) - - // Wait for the service to exist. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: envoyName}, &corev1.Service{}) - }, existsTimeout, interval) + testutils.CreateObjsFromYaml(testConfig, outManifests) } // createInferExt creates the inference extension resources used for testing from the given filePath. -func createInferExt(k8sClient client.Client, filePath string) { - inManifests := readYaml(filePath) +func createInferExt(testConfig *testutils.TestConfig, filePath string) { + inManifests := testutils.ReadYaml(filePath) ginkgo.By("Replacing placeholders with environment variables") outManifests := []string{} + replacer := strings.NewReplacer( + "$E2E_NS", testConfig.NsName, + "$E2E_IMAGE", e2eImage, + ) for _, manifest := range inManifests { - replacer := strings.NewReplacer( - "$E2E_NS", nsName, - "$E2E_IMAGE", e2eImage, - ) outManifests = append(outManifests, replacer.Replace(manifest)) } ginkgo.By("Creating inference extension resources from manifest: " + filePath) - createObjsFromYaml(k8sClient, outManifests) - - // Wait for the serviceaccount to exist. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, &corev1.ServiceAccount{}) - }, existsTimeout, interval) - - // Wait for the role to exist. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: "pod-read"}, &rbacv1.Role{}) - }, existsTimeout, interval) - - // Wait for the rolebinding to exist. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: "pod-read-binding"}, &rbacv1.RoleBinding{}) - }, existsTimeout, interval) - - // Wait for the clusterrole to exist. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Name: "auth-reviewer"}, &rbacv1.ClusterRole{}) - }, existsTimeout, interval) - - // Wait for the clusterrolebinding to exist. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Name: "auth-reviewer-binding"}, &rbacv1.ClusterRoleBinding{}) - }, existsTimeout, interval) + testutils.CreateObjsFromYaml(testConfig, outManifests) // Wait for the deployment to exist. - deploy := &appsv1.Deployment{} - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, deploy) - }, existsTimeout, interval) - + deploy := &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{ + Name: inferExtName, + Namespace: testConfig.NsName, + }, + } if leaderElectionEnabled { // With leader election enabled, only 1 replica will be "Ready" at any given time (the leader). - testutils.DeploymentReadyReplicas(ctx, k8sClient, deploy, 1, modelReadyTimeout, interval) + testutils.DeploymentReadyReplicas(testConfig, deploy, 1) } else { - testutils.DeploymentAvailable(ctx, k8sClient, deploy, modelReadyTimeout, interval) - } - - // Wait for the service to exist. - testutils.EventuallyExists(ctx, func() error { - return k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, &corev1.Service{}) - }, existsTimeout, interval) -} - -// applyYAMLFile reads a file containing YAML (possibly multiple docs) -// and applies each object to the cluster. -func applyYAMLFile(k8sClient client.Client, filePath string) { - // Create the resources from the manifest file - createObjsFromYaml(k8sClient, readYaml(filePath)) -} - -func readYaml(filePath string) []string { - ginkgo.By("Reading YAML file: " + filePath) - yamlBytes, err := os.ReadFile(filePath) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - - // Split multiple docs, if needed - return strings.Split(string(yamlBytes), "\n---") -} - -func createObjsFromYaml(k8sClient client.Client, docs []string) { - // For each doc, decode and create - decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer() - for _, doc := range docs { - trimmed := strings.TrimSpace(doc) - if trimmed == "" { - continue - } - // Decode into a runtime.Object - obj, gvk, decodeErr := decoder.Decode([]byte(trimmed), nil, nil) - gomega.Expect(decodeErr).NotTo(gomega.HaveOccurred(), - "Failed to decode YAML document to a Kubernetes object") - - ginkgo.By(fmt.Sprintf("Decoded GVK: %s", gvk)) - - unstrObj, ok := obj.(*unstructured.Unstructured) - if !ok { - // Fallback if it's a typed object - unstrObj = &unstructured.Unstructured{} - // Convert typed to unstructured - err := scheme.Convert(obj, unstrObj, nil) - gomega.Expect(err).NotTo(gomega.HaveOccurred()) - } - - unstrObj.SetNamespace(nsName) - - // Create the object - err := k8sClient.Create(ctx, unstrObj) - gomega.Expect(err).NotTo(gomega.HaveOccurred(), - "Failed to create object from YAML") + testutils.DeploymentAvailable(testConfig, deploy) } } diff --git a/test/e2e/epp/e2e_test.go b/test/e2e/epp/e2e_test.go index c8410bd59..c3ff49e58 100644 --- a/test/e2e/epp/e2e_test.go +++ b/test/e2e/epp/e2e_test.go @@ -43,23 +43,23 @@ var _ = ginkgo.Describe("InferencePool", func() { var infObjective *v1alpha2.InferenceObjective ginkgo.BeforeEach(func() { ginkgo.By("Waiting for the namespace to exist.") - namespaceExists(cli, nsName) + namespaceExists(testConfig) ginkgo.By("Creating an InferenceObjective resource") - infObjective = newInferenceObjective(nsName) - gomega.Expect(cli.Create(ctx, infObjective)).To(gomega.Succeed()) + infObjective = newInferenceObjective(testConfig.NsName) + gomega.Expect(testConfig.K8sClient.Create(testConfig.Context, infObjective)).To(gomega.Succeed()) ginkgo.By("Ensuring the InferenceObjective resource exists in the namespace") gomega.Eventually(func() error { - return cli.Get(ctx, types.NamespacedName{Namespace: infObjective.Namespace, Name: infObjective.Name}, infObjective) - }, existsTimeout, interval).Should(gomega.Succeed()) + return testConfig.K8sClient.Get(testConfig.Context, types.NamespacedName{Namespace: infObjective.Namespace, Name: infObjective.Name}, infObjective) + }, testConfig.ExistsTimeout, testConfig.Interval).Should(gomega.Succeed()) }) ginkgo.AfterEach(func() { ginkgo.By("Deleting the InferenceObjective test resource.") cleanupInferModelResources() gomega.Eventually(func() error { - err := cli.Get(ctx, types.NamespacedName{Namespace: infObjective.Namespace, Name: infObjective.Name}, infObjective) + err := testConfig.K8sClient.Get(testConfig.Context, types.NamespacedName{Namespace: infObjective.Namespace, Name: infObjective.Name}, infObjective) if err == nil { return errors.New("InferenceObjective resource still exists") } @@ -67,7 +67,7 @@ var _ = ginkgo.Describe("InferencePool", func() { return nil } return nil - }, existsTimeout, interval).Should(gomega.Succeed()) + }, testConfig.ExistsTimeout, testConfig.Interval).Should(gomega.Succeed()) }) ginkgo.When("The Inference Extension is running", func() { @@ -89,7 +89,7 @@ var _ = ginkgo.Describe("InferencePool", func() { ginkgo.By("Verifying that exactly one EPP pod is ready") gomega.Eventually(func(g gomega.Gomega) { podList := &corev1.PodList{} - err := cli.List(ctx, podList, client.InNamespace(nsName), client.MatchingLabels{"app": inferExtName}) + err := testConfig.K8sClient.List(testConfig.Context, podList, client.InNamespace(testConfig.NsName), client.MatchingLabels{"app": inferExtName}) g.Expect(err).NotTo(gomega.HaveOccurred()) // The deployment should have 3 replicas for leader election. @@ -104,7 +104,7 @@ var _ = ginkgo.Describe("InferencePool", func() { } } g.Expect(readyPods).To(gomega.Equal(1), "Expected exactly one pod to be ready") - }, readyTimeout, interval).Should(gomega.Succeed()) + }, testConfig.ReadyTimeout, testConfig.Interval).Should(gomega.Succeed()) }) ginkgo.It("Should successfully failover and serve traffic after the leader pod is deleted", func() { @@ -121,26 +121,26 @@ var _ = ginkgo.Describe("InferencePool", func() { ginkgo.By("Found initial leader pod: " + oldLeaderPod.Name) ginkgo.By(fmt.Sprintf("Deleting leader pod %s to trigger failover", oldLeaderPod.Name)) - gomega.Expect(cli.Delete(ctx, oldLeaderPod)).To(gomega.Succeed()) + gomega.Expect(testConfig.K8sClient.Delete(testConfig.Context, oldLeaderPod)).To(gomega.Succeed()) ginkgo.By("STEP 3: Waiting for a new leader to be elected") // The deployment controller will create a new pod. We need to wait for the total number of pods // to be back to 3, and for one of the other pods to become the new leader. deploy := &appsv1.Deployment{} gomega.Eventually(func() error { - return cli.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, deploy) - }, existsTimeout, interval).Should(gomega.Succeed()) + return testConfig.K8sClient.Get(testConfig.Context, types.NamespacedName{Namespace: testConfig.NsName, Name: inferExtName}, deploy) + }, testConfig.ExistsTimeout, testConfig.Interval).Should(gomega.Succeed()) // Wait for one replica to become ready again. - testutils.DeploymentReadyReplicas(ctx, cli, deploy, 1, readyTimeout, interval) + testutils.DeploymentReadyReplicas(testConfig, deploy, 1) // Also wait for the total number of replicas to be back to 3. gomega.Eventually(func(g gomega.Gomega) { d := &appsv1.Deployment{} - err := cli.Get(ctx, types.NamespacedName{Namespace: nsName, Name: inferExtName}, d) + err := testConfig.K8sClient.Get(testConfig.Context, types.NamespacedName{Namespace: testConfig.NsName, Name: inferExtName}, d) g.Expect(err).NotTo(gomega.HaveOccurred()) g.Expect(d.Status.Replicas).To(gomega.Equal(int32(3)), "Deployment should have 3 replicas") - }, readyTimeout, interval).Should(gomega.Succeed()) + }, testConfig.ReadyTimeout, testConfig.Interval).Should(gomega.Succeed()) ginkgo.By("STEP 4: Verifying a new, different leader is elected") var newLeaderPod *corev1.Pod @@ -152,7 +152,7 @@ var _ = ginkgo.Describe("InferencePool", func() { // This guards against a race condition where we might find the old leader // before its status is updated to NotReady. g.Expect(newLeaderPod.Name).NotTo(gomega.Equal(oldLeaderPod.Name), "The new leader should not be the same as the old deleted leader") - }, readyTimeout, interval).Should(gomega.Succeed()) + }, testConfig.ReadyTimeout, testConfig.Interval).Should(gomega.Succeed()) ginkgo.By("Found new leader pod: " + newLeaderPod.Name) ginkgo.By("STEP 5: Verifying the new leader is working correctly after failover") @@ -207,11 +207,11 @@ func verifyTrafficRouting() { // Ensure the expected responses include the InferenceObjective target model names. var expected []string expected = append(expected, targetModelName) - curlCmd := getCurlCommand(envoyName, nsName, envoyPort, modelName, curlTimeout, t.api, t.promptOrMessages, false) + curlCmd := getCurlCommand(envoyName, testConfig.NsName, envoyPort, modelName, curlTimeout, t.api, t.promptOrMessages, false) actual := make(map[string]int) gomega.Eventually(func() error { - resp, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd) + resp, err := testutils.ExecCommandInPod(testConfig, "curl", "curl", curlCmd) if err != nil { return err } @@ -232,7 +232,7 @@ func verifyTrafficRouting() { return fmt.Errorf("actual (%v) != expected (%v); resp=%q", got, expected, resp) } return nil - }, readyTimeout, curlInterval).Should(gomega.Succeed()) + }, testConfig.ReadyTimeout, curlInterval).Should(gomega.Succeed()) } } @@ -260,18 +260,18 @@ func verifyMetrics() { // Generate traffic by sending requests through the inference extension ginkgo.By("Generating traffic through the inference extension") - curlCmd := getCurlCommand(envoyName, nsName, envoyPort, modelName, curlTimeout, "/completions", "Write as if you were a critic: San Francisco", true) + curlCmd := getCurlCommand(envoyName, testConfig.NsName, envoyPort, modelName, curlTimeout, "/completions", "Write as if you were a critic: San Francisco", true) // Run the curl command multiple times to generate some metrics data for i := 0; i < 5; i++ { - _, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd) + _, err := testutils.ExecCommandInPod(testConfig, "curl", "curl", curlCmd) gomega.Expect(err).NotTo(gomega.HaveOccurred()) } // modify the curl command to generate some error metrics curlCmd[len(curlCmd)-1] = "invalid input" for i := 0; i < 5; i++ { - _, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", curlCmd) + _, err := testutils.ExecCommandInPod(testConfig, "curl", "curl", curlCmd) gomega.Expect(err).NotTo(gomega.HaveOccurred()) } @@ -282,11 +282,11 @@ func verifyMetrics() { // Get the authorization token for reading metrics token := "" gomega.Eventually(func(g gomega.Gomega) { - t, err := getMetricsReaderToken(cli) + t, err := getMetricsReaderToken(testConfig.K8sClient) g.Expect(err).NotTo(gomega.HaveOccurred()) g.Expect(t).NotTo(gomega.BeEmpty()) token = t - }, existsTimeout, interval).Should(gomega.Succeed()) + }, testConfig.ExistsTimeout, testConfig.Interval).Should(gomega.Succeed()) // Construct the metric scraping curl command using Pod IP metricScrapeCmd := getMetricsScrapeCommand(podIP, token) @@ -294,7 +294,7 @@ func verifyMetrics() { ginkgo.By("Verifying that all expected metrics are present.") gomega.Eventually(func() error { // Execute the metrics scrape command inside the curl pod - resp, err := testutils.ExecCommandInPod(ctx, cfg, scheme, kubeCli, nsName, "curl", "curl", metricScrapeCmd) + resp, err := testutils.ExecCommandInPod(testConfig, "curl", "curl", metricScrapeCmd) if err != nil { return err } @@ -309,12 +309,12 @@ func verifyMetrics() { } } return nil - }, readyTimeout, curlInterval).Should(gomega.Succeed()) + }, testConfig.ReadyTimeout, curlInterval).Should(gomega.Succeed()) } func getMetricsReaderToken(k8sClient client.Client) (string, error) { secret := &corev1.Secret{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: nsName, Name: metricsReaderSecretName}, secret) + err := k8sClient.Get(testConfig.Context, types.NamespacedName{Namespace: testConfig.NsName, Name: metricsReaderSecretName}, secret) if err != nil { return "", err } @@ -327,7 +327,7 @@ func findReadyPod() *corev1.Pod { var readyPod *corev1.Pod gomega.Eventually(func(g gomega.Gomega) { podList := &corev1.PodList{} - err := cli.List(ctx, podList, client.InNamespace(nsName), client.MatchingLabels{"app": inferExtName}) + err := testConfig.K8sClient.List(testConfig.Context, podList, client.InNamespace(testConfig.NsName), client.MatchingLabels{"app": inferExtName}) g.Expect(err).NotTo(gomega.HaveOccurred()) foundReadyPod := false @@ -346,7 +346,7 @@ func findReadyPod() *corev1.Pod { } } g.Expect(foundReadyPod).To(gomega.BeTrue(), "No ready EPP pod found") - }, readyTimeout, interval).Should(gomega.Succeed()) + }, testConfig.ReadyTimeout, testConfig.Interval).Should(gomega.Succeed()) return readyPod } diff --git a/test/testdata/inferencepool-e2e.yaml b/test/testdata/inferencepool-e2e.yaml index b8d7fb697..77d454e6f 100644 --- a/test/testdata/inferencepool-e2e.yaml +++ b/test/testdata/inferencepool-e2e.yaml @@ -37,6 +37,87 @@ metadata: name: vllm-llama3-8b-instruct-epp namespace: $E2E_NS --- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: pod-read + namespace: $E2E_NS +rules: +- apiGroups: [ "inference.networking.x-k8s.io" ] + resources: [ "inferenceobjectives", "inferencepools" ] + verbs: [ "get", "watch", "list" ] +- apiGroups: [ "inference.networking.k8s.io" ] + resources: [ "inferencepools" ] + verbs: [ "get", "watch", "list" ] +- apiGroups: [ "" ] + resources: [ "pods" ] + verbs: [ "get", "watch", "list" ] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: pod-read-binding + namespace: $E2E_NS +subjects: +- kind: ServiceAccount + name: vllm-llama3-8b-instruct-epp + namespace: $E2E_NS +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: pod-read +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: auth-reviewer +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: auth-reviewer-binding +subjects: +- kind: ServiceAccount + name: vllm-llama3-8b-instruct-epp + namespace: $E2E_NS +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: auth-reviewer +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: plugins-config + namespace: $E2E_NS +data: + default-plugins.yaml: | + apiVersion: inference.networking.x-k8s.io/v1alpha1 + kind: EndpointPickerConfig + plugins: + - type: queue-scorer + - type: kv-cache-utilization-scorer + - type: prefix-cache-scorer + schedulingProfiles: + - name: default + plugins: + - pluginRef: queue-scorer + - pluginRef: kv-cache-utilization-scorer + - pluginRef: prefix-cache-scorer +--- apiVersion: apps/v1 kind: Deployment metadata: @@ -100,84 +181,3 @@ spec: - name: plugins-config-volume configMap: name: plugins-config ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: plugins-config - namespace: $E2E_NS -data: - default-plugins.yaml: | - apiVersion: inference.networking.x-k8s.io/v1alpha1 - kind: EndpointPickerConfig - plugins: - - type: queue-scorer - - type: kv-cache-utilization-scorer - - type: prefix-cache-scorer - schedulingProfiles: - - name: default - plugins: - - pluginRef: queue-scorer - - pluginRef: kv-cache-utilization-scorer - - pluginRef: prefix-cache-scorer ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: pod-read - namespace: $E2E_NS -rules: -- apiGroups: [ "inference.networking.x-k8s.io" ] - resources: [ "inferenceobjectives", "inferencepools" ] - verbs: [ "get", "watch", "list" ] -- apiGroups: [ "inference.networking.k8s.io" ] - resources: [ "inferencepools" ] - verbs: [ "get", "watch", "list" ] -- apiGroups: [ "" ] - resources: [ "pods" ] - verbs: [ "get", "watch", "list" ] ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: pod-read-binding - namespace: $E2E_NS -subjects: -- kind: ServiceAccount - name: vllm-llama3-8b-instruct-epp - namespace: $E2E_NS -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: pod-read ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: auth-reviewer -rules: -- apiGroups: - - authentication.k8s.io - resources: - - tokenreviews - verbs: - - create -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: auth-reviewer-binding -subjects: -- kind: ServiceAccount - name: vllm-llama3-8b-instruct-epp - namespace: $E2E_NS -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: auth-reviewer diff --git a/test/testdata/inferencepool-leader-election-e2e.yaml b/test/testdata/inferencepool-leader-election-e2e.yaml index 9ba5dcb4a..976fbbd02 100644 --- a/test/testdata/inferencepool-leader-election-e2e.yaml +++ b/test/testdata/inferencepool-leader-election-e2e.yaml @@ -35,91 +35,6 @@ metadata: name: vllm-llama3-8b-instruct-epp namespace: $E2E_NS --- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: vllm-llama3-8b-instruct-epp - namespace: $E2E_NS - labels: - app: vllm-llama3-8b-instruct-epp -spec: - replicas: 3 - selector: - matchLabels: - app: vllm-llama3-8b-instruct-epp - template: - metadata: - labels: - app: vllm-llama3-8b-instruct-epp - spec: - serviceAccountName: vllm-llama3-8b-instruct-epp - # Conservatively, this timeout should mirror the longest grace period of the pods within the pool - terminationGracePeriodSeconds: 130 - containers: - - name: epp - image: $E2E_IMAGE - imagePullPolicy: IfNotPresent - args: - - --pool-name - - "vllm-llama3-8b-instruct" - - --pool-namespace - - "$E2E_NS" - - --v - - "4" - - --zap-encoder - - "json" - - --grpc-port - - "9002" - - --grpc-health-port - - "9003" - - --ha-enable-leader-election - - "--config-file" - - "/config/default-plugins.yaml" - ports: - - containerPort: 9002 - - containerPort: 9003 - - name: metrics - containerPort: 9090 - livenessProbe: - grpc: - port: 9003 - service: liveness - initialDelaySeconds: 5 - periodSeconds: 10 - readinessProbe: - grpc: - port: 9003 - service: readiness - initialDelaySeconds: 5 - periodSeconds: 10 - volumeMounts: - - name: plugins-config-volume - mountPath: "/config" - volumes: - - name: plugins-config-volume - configMap: - name: plugins-config ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: plugins-config - namespace: $E2E_NS -data: - default-plugins.yaml: | - apiVersion: inference.networking.x-k8s.io/v1alpha1 - kind: EndpointPickerConfig - plugins: - - type: queue-scorer - - type: kv-cache-utilization-scorer - - type: prefix-cache-scorer - schedulingProfiles: - - name: default - plugins: - - pluginRef: queue-scorer - - pluginRef: kv-cache-utilization-scorer - - pluginRef: prefix-cache-scorer ---- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -207,3 +122,88 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: auth-reviewer +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: plugins-config + namespace: $E2E_NS +data: + default-plugins.yaml: | + apiVersion: inference.networking.x-k8s.io/v1alpha1 + kind: EndpointPickerConfig + plugins: + - type: queue-scorer + - type: kv-cache-utilization-scorer + - type: prefix-cache-scorer + schedulingProfiles: + - name: default + plugins: + - pluginRef: queue-scorer + - pluginRef: kv-cache-utilization-scorer + - pluginRef: prefix-cache-scorer +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vllm-llama3-8b-instruct-epp + namespace: $E2E_NS + labels: + app: vllm-llama3-8b-instruct-epp +spec: + replicas: 3 + selector: + matchLabels: + app: vllm-llama3-8b-instruct-epp + template: + metadata: + labels: + app: vllm-llama3-8b-instruct-epp + spec: + serviceAccountName: vllm-llama3-8b-instruct-epp + # Conservatively, this timeout should mirror the longest grace period of the pods within the pool + terminationGracePeriodSeconds: 130 + containers: + - name: epp + image: $E2E_IMAGE + imagePullPolicy: IfNotPresent + args: + - --pool-name + - "vllm-llama3-8b-instruct" + - --pool-namespace + - "$E2E_NS" + - --v + - "4" + - --zap-encoder + - "json" + - --grpc-port + - "9002" + - --grpc-health-port + - "9003" + - --ha-enable-leader-election + - "--config-file" + - "/config/default-plugins.yaml" + ports: + - containerPort: 9002 + - containerPort: 9003 + - name: metrics + containerPort: 9090 + livenessProbe: + grpc: + port: 9003 + service: liveness + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + grpc: + port: 9003 + service: readiness + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: plugins-config-volume + mountPath: "/config" + volumes: + - name: plugins-config-volume + configMap: + name: plugins-config diff --git a/test/utils/utils.go b/test/utils/utils.go index 8cc19b7ac..b65834cb6 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -20,6 +20,8 @@ import ( "bytes" "context" "fmt" + "os" + "strings" "time" "github.com/onsi/ginkgo/v2" @@ -30,25 +32,84 @@ import ( apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" v1 "sigs.k8s.io/gateway-api-inference-extension/api/v1" "sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/env" ) +const ( + // defaultExistsTimeout is the default timeout for a resource to exist in the api server. + defaultExistsTimeout = 30 * time.Second + // defaultReadyTimeout is the default timeout for a resource to report a ready state. + defaultReadyTimeout = 3 * time.Minute + // defaultModelReadyTimeout is the default timeout for the model server deployment to report a ready state. + defaultModelReadyTimeout = 10 * time.Minute + // defaultInterval is the default interval to check if a resource exists or ready conditions. + defaultInterval = time.Millisecond * 250 +) + +// TestConfig groups various fields together for use in the test helpers +type TestConfig struct { + Context context.Context + KubeCli *kubernetes.Clientset + K8sClient client.Client + RestConfig *rest.Config + NsName string + Scheme *runtime.Scheme + ExistsTimeout time.Duration + ReadyTimeout time.Duration + ModelReadyTimeout time.Duration + Interval time.Duration +} + +// NewTestConfig creates a new TestConfig instance +func NewTestConfig(nsName string) *TestConfig { + cfg := config.GetConfigOrDie() + gomega.Expect(cfg).NotTo(gomega.BeNil()) + + kubeCli, err := kubernetes.NewForConfig(cfg) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(kubeCli).NotTo(gomega.BeNil()) + + return &TestConfig{ + Context: context.Background(), + KubeCli: kubeCli, + NsName: nsName, + RestConfig: cfg, + Scheme: runtime.NewScheme(), + ExistsTimeout: env.GetEnvDuration("EXISTS_TIMEOUT", defaultExistsTimeout, ginkgo.GinkgoLogr), + ReadyTimeout: env.GetEnvDuration("READY_TIMEOUT", defaultReadyTimeout, ginkgo.GinkgoLogr), + ModelReadyTimeout: env.GetEnvDuration("MODEL_READY_TIMEOUT", defaultModelReadyTimeout, ginkgo.GinkgoLogr), + Interval: defaultInterval, + } +} + +// CreateCli creates the Kubernetes client used in the tests, invoked after the scheme has been setup. +func (testConfig *TestConfig) CreateCli() { + var err error + testConfig.K8sClient, err = client.New(testConfig.RestConfig, client.Options{Scheme: testConfig.Scheme}) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(testConfig.K8sClient).NotTo(gomega.BeNil()) +} + // DeleteClusterResources deletes all cluster-scoped objects the tests typically create. -func DeleteClusterResources(ctx context.Context, cli client.Client) error { +func DeleteClusterResources(testConfig *TestConfig) error { binding := &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "auth-reviewer-binding", }, } - err := cli.Delete(ctx, binding, client.PropagationPolicy(metav1.DeletePropagationForeground)) + err := testConfig.K8sClient.Delete(testConfig.Context, binding, client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -57,7 +118,7 @@ func DeleteClusterResources(ctx context.Context, cli client.Client) error { Name: "auth-reviewer", }, } - err = cli.Delete(ctx, role, client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.Delete(testConfig.Context, role, client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -66,7 +127,7 @@ func DeleteClusterResources(ctx context.Context, cli client.Client) error { Name: "inference-gateway-sa-metrics-reader-role-binding", }, } - err = cli.Delete(ctx, metricsReaderBinding, client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.Delete(testConfig.Context, metricsReaderBinding, client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -75,7 +136,7 @@ func DeleteClusterResources(ctx context.Context, cli client.Client) error { Name: "inference-gateway-metrics-reader", }, } - err = cli.Delete(ctx, metricsReaderRole, client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.Delete(testConfig.Context, metricsReaderRole, client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -84,7 +145,7 @@ func DeleteClusterResources(ctx context.Context, cli client.Client) error { Name: "inferenceobjectives.inference.networking.x-k8s.io", }, } - err = cli.Delete(ctx, model, client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.Delete(testConfig.Context, model, client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -93,7 +154,7 @@ func DeleteClusterResources(ctx context.Context, cli client.Client) error { Name: "inferencepools.inference.networking.x-k8s.io", }, } - err = cli.Delete(ctx, pool, client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.Delete(testConfig.Context, pool, client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -102,49 +163,49 @@ func DeleteClusterResources(ctx context.Context, cli client.Client) error { // DeleteNamespacedResources deletes all namespace-scoped objects the tests typically create. // The given namespace will also be deleted if it's not "default". -func DeleteNamespacedResources(ctx context.Context, cli client.Client, ns string) error { - if ns == "" { +func DeleteNamespacedResources(testConfig *TestConfig) error { + if testConfig.NsName == "" { return nil } - err := cli.DeleteAllOf(ctx, &appsv1.Deployment{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err := testConfig.K8sClient.DeleteAllOf(testConfig.Context, &appsv1.Deployment{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - err = cli.DeleteAllOf(ctx, &corev1.Service{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.DeleteAllOf(testConfig.Context, &corev1.Service{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - err = cli.DeleteAllOf(ctx, &corev1.Pod{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.DeleteAllOf(testConfig.Context, &corev1.Pod{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - err = cli.DeleteAllOf(ctx, &corev1.ConfigMap{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.DeleteAllOf(testConfig.Context, &corev1.ConfigMap{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - err = cli.DeleteAllOf(ctx, &corev1.Secret{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.DeleteAllOf(testConfig.Context, &corev1.Secret{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - err = cli.DeleteAllOf(ctx, &corev1.ServiceAccount{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.DeleteAllOf(testConfig.Context, &corev1.ServiceAccount{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - err = cli.DeleteAllOf(ctx, &v1.InferencePool{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.DeleteAllOf(testConfig.Context, &v1.InferencePool{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - err = cli.DeleteAllOf(ctx, &v1alpha2.InferenceObjective{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err = testConfig.K8sClient.DeleteAllOf(testConfig.Context, &v1alpha2.InferenceObjective{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } - if ns != "default" { + if testConfig.NsName != "default" { ns := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: ns, + Name: testConfig.NsName, }, } - if err := cli.Delete(ctx, ns, client.PropagationPolicy(metav1.DeletePropagationForeground)); err != nil && !apierrors.IsNotFound(err) { + if err := testConfig.K8sClient.Delete(testConfig.Context, ns, client.PropagationPolicy(metav1.DeletePropagationForeground)); err != nil && !apierrors.IsNotFound(err) { return err } } @@ -152,11 +213,11 @@ func DeleteNamespacedResources(ctx context.Context, cli client.Client, ns string } // DeleteInferenceObjectiveResources deletes all InferenceObjective objects in the given namespace. -func DeleteInferenceObjectiveResources(ctx context.Context, cli client.Client, ns string) error { - if ns == "" { +func DeleteInferenceObjectiveResources(testConfig *TestConfig) error { + if testConfig.NsName == "" { return nil } - err := cli.DeleteAllOf(ctx, &v1alpha2.InferenceObjective{}, client.InNamespace(ns), client.PropagationPolicy(metav1.DeletePropagationForeground)) + err := testConfig.K8sClient.DeleteAllOf(testConfig.Context, &v1alpha2.InferenceObjective{}, client.InNamespace(testConfig.NsName), client.PropagationPolicy(metav1.DeletePropagationForeground)) if err != nil && !apierrors.IsNotFound(err) { return err } @@ -164,7 +225,7 @@ func DeleteInferenceObjectiveResources(ctx context.Context, cli client.Client, n } // PodReady checks if the given Pod reports the "Ready" status condition before the given timeout. -func PodReady(ctx context.Context, cli client.Client, pod *corev1.Pod, timeout, interval time.Duration) { +func PodReady(testConfig *TestConfig, pod *corev1.Pod) { ginkgo.By(fmt.Sprintf("Checking pod %s/%s status is: %s", pod.Namespace, pod.Name, corev1.PodReady)) conditions := []corev1.PodCondition{ { @@ -172,13 +233,14 @@ func PodReady(ctx context.Context, cli client.Client, pod *corev1.Pod, timeout, Status: corev1.ConditionTrue, }, } - gomega.Eventually(checkPodStatus, timeout, interval).WithArguments(ctx, cli, pod, conditions).Should(gomega.BeTrue()) + gomega.Eventually(checkPodStatus, testConfig.ExistsTimeout, testConfig.Interval). + WithArguments(testConfig, pod, conditions).Should(gomega.BeTrue()) } // checkPodStatus checks if the given Pod status matches the expected conditions. -func checkPodStatus(ctx context.Context, cli client.Client, pod *corev1.Pod, conditions []corev1.PodCondition) (bool, error) { +func checkPodStatus(testConfig *TestConfig, pod *corev1.Pod, conditions []corev1.PodCondition) (bool, error) { var fetchedPod corev1.Pod - if err := cli.Get(ctx, types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}, &fetchedPod); err != nil { + if err := testConfig.K8sClient.Get(testConfig.Context, types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}, &fetchedPod); err != nil { return false, err } found := 0 @@ -193,7 +255,7 @@ func checkPodStatus(ctx context.Context, cli client.Client, pod *corev1.Pod, con } // DeploymentAvailable checks if the given Deployment reports the "Available" status condition before the given timeout. -func DeploymentAvailable(ctx context.Context, cli client.Client, deploy *appsv1.Deployment, timeout, interval time.Duration) { +func DeploymentAvailable(testConfig *TestConfig, deploy *appsv1.Deployment) { ginkgo.By(fmt.Sprintf("Checking if deployment %s/%s status is: %s", deploy.Namespace, deploy.Name, appsv1.DeploymentAvailable)) conditions := []appsv1.DeploymentCondition{ { @@ -201,19 +263,21 @@ func DeploymentAvailable(ctx context.Context, cli client.Client, deploy *appsv1. Status: corev1.ConditionTrue, }, } - gomega.Eventually(checkDeploymentStatus, timeout, interval).WithArguments(ctx, cli, deploy, conditions).Should(gomega.BeTrue()) + gomega.Eventually(checkDeploymentStatus, testConfig.ModelReadyTimeout, testConfig.Interval). + WithArguments(testConfig.Context, testConfig.K8sClient, deploy, conditions). + Should(gomega.BeTrue()) } // DeploymentReadyReplicas checks if the given Deployment has at least `count` ready replicas before the given timeout. -func DeploymentReadyReplicas(ctx context.Context, cli client.Client, deploy *appsv1.Deployment, count int, timeout, interval time.Duration) { +func DeploymentReadyReplicas(testConfig *TestConfig, deploy *appsv1.Deployment, count int) { ginkgo.By(fmt.Sprintf("Checking if deployment %s/%s has at least %d ready replica(s)", deploy.Namespace, deploy.Name, count)) gomega.Eventually(func(g gomega.Gomega) { var fetchedDeploy appsv1.Deployment - err := cli.Get(ctx, types.NamespacedName{Namespace: deploy.Namespace, Name: deploy.Name}, &fetchedDeploy) + err := testConfig.K8sClient.Get(testConfig.Context, types.NamespacedName{Namespace: deploy.Namespace, Name: deploy.Name}, &fetchedDeploy) g.Expect(err).NotTo(gomega.HaveOccurred()) g.Expect(fetchedDeploy.Status.ReadyReplicas).To(gomega.BeNumerically(">=", count), fmt.Sprintf("Deployment only has %d ready replicas, want at least %d", fetchedDeploy.Status.ReadyReplicas, count)) - }, timeout, interval).Should(gomega.Succeed()) + }, testConfig.ModelReadyTimeout, testConfig.Interval).Should(gomega.Succeed()) } // checkDeploymentStatus checks if the given Deployment status matches the expected conditions. @@ -234,7 +298,7 @@ func checkDeploymentStatus(ctx context.Context, cli client.Client, deploy *appsv } // CRDEstablished checks if the given CRD reports the "Established" status condition before the given timeout. -func CRDEstablished(ctx context.Context, cli client.Client, crd *apiextv1.CustomResourceDefinition, timeout, interval time.Duration) { +func CRDEstablished(testConfig *TestConfig, crd *apiextv1.CustomResourceDefinition) { ginkgo.By(fmt.Sprintf("Checking CRD %s status is: %s", crd.Name, apiextv1.Established)) conditions := []apiextv1.CustomResourceDefinitionCondition{ { @@ -242,7 +306,9 @@ func CRDEstablished(ctx context.Context, cli client.Client, crd *apiextv1.Custom Status: apiextv1.ConditionTrue, }, } - gomega.Eventually(checkCrdStatus, timeout, interval).WithArguments(ctx, cli, crd, conditions).Should(gomega.BeTrue()) + gomega.Eventually(checkCrdStatus, testConfig.ReadyTimeout, testConfig.Interval). + WithArguments(testConfig.Context, testConfig.K8sClient, crd, conditions). + Should(gomega.BeTrue()) } // checkCrdStatus checks if the given CRD status matches the expected conditions. @@ -268,22 +334,14 @@ func checkCrdStatus( } // ExecCommandInPod runs a command in a given container of a given Pod, returning combined stdout+stderr. -func ExecCommandInPod( - ctx context.Context, - cfg *rest.Config, - scheme *runtime.Scheme, - kubeClient *kubernetes.Clientset, - podNamespace, podName, containerName string, - cmd []string, -) (string, error) { - - parameterCodec := runtime.NewParameterCodec(scheme) +func ExecCommandInPod(testConfig *TestConfig, podName, containerName string, cmd []string) (string, error) { + parameterCodec := runtime.NewParameterCodec(testConfig.Scheme) - req := kubeClient.CoreV1().RESTClient(). + req := testConfig.KubeCli.CoreV1().RESTClient(). Post(). Resource("pods"). Name(podName). - Namespace(podNamespace). + Namespace(testConfig.NsName). SubResource("exec"). VersionedParams(&corev1.PodExecOptions{ Container: containerName, @@ -294,13 +352,13 @@ func ExecCommandInPod( TTY: false, }, parameterCodec) - exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL()) + exec, err := remotecommand.NewSPDYExecutor(testConfig.RestConfig, "POST", req.URL()) if err != nil { return "", fmt.Errorf("could not initialize executor: %w", err) } var stdout, stderr bytes.Buffer - execErr := exec.StreamWithContext(ctx, remotecommand.StreamOptions{ + execErr := exec.StreamWithContext(testConfig.Context, remotecommand.StreamOptions{ Stdout: &stdout, Stderr: &stderr, }) @@ -316,8 +374,135 @@ func ExecCommandInPod( // EventuallyExists checks if a Kubernetes resource exists and returns nil if successful. // It takes a function `getResource` which retrieves the resource and returns an error if it doesn't exist. -func EventuallyExists(ctx context.Context, getResource func() error, timeout, interval time.Duration) { +func EventuallyExists(testConfig *TestConfig, getResource func() error) { gomega.Eventually(func() error { return getResource() - }, timeout, interval).Should(gomega.Succeed()) + }, testConfig.ExistsTimeout, testConfig.Interval).Should(gomega.Succeed()) +} + +// CreateObjsFromYaml creates K8S objects from yaml and waits for them to be instantiated +func CreateObjsFromYaml(testConfig *TestConfig, docs []string) []string { + objNames := []string{} + + // For each doc, decode and create + decoder := serializer.NewCodecFactory(testConfig.Scheme).UniversalDeserializer() + for _, doc := range docs { + trimmed := strings.TrimSpace(doc) + if trimmed == "" { + continue + } + // Decode into a runtime.Object + obj, gvk, decodeErr := decoder.Decode([]byte(trimmed), nil, nil) + gomega.Expect(decodeErr).NotTo(gomega.HaveOccurred(), + "Failed to decode YAML document to a Kubernetes object") + + ginkgo.By(fmt.Sprintf("Decoded GVK: %s", gvk)) + + unstrObj, ok := obj.(*unstructured.Unstructured) + if !ok { + // Fallback if it's a typed object + unstrObj = &unstructured.Unstructured{} + // Convert typed to unstructured + err := testConfig.Scheme.Convert(obj, unstrObj, nil) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + } + + unstrObj.SetNamespace(testConfig.NsName) + kind := unstrObj.GetKind() + name := unstrObj.GetName() + objNames = append(objNames, kind+"/"+name) + + // Create the object + err := testConfig.K8sClient.Create(testConfig.Context, unstrObj, &client.CreateOptions{}) + gomega.Expect(err).NotTo(gomega.HaveOccurred(), + "Failed to create object from YAML") + + // Wait for the created object to exist. + clientObj := getClientObject(kind) + EventuallyExists(testConfig, func() error { + return testConfig.K8sClient.Get(testConfig.Context, + types.NamespacedName{Namespace: testConfig.NsName, Name: name}, clientObj) + }) + + switch kind { + case "CustomResourceDefinition": + // Wait for the CRD to be established. + CRDEstablished(testConfig, clientObj.(*apiextv1.CustomResourceDefinition)) + case "Deployment": + // Wait for the deployment to be available. + DeploymentAvailable(testConfig, clientObj.(*appsv1.Deployment)) + case "Pod": + // Wait for the pod to be ready. + PodReady(testConfig, clientObj.(*corev1.Pod)) + } + } + return objNames +} + +// DeleteObjects deletes set of Kubernetes objects in the form of kind/name +func DeleteObjects(testConfig *TestConfig, kindAndNames []string) { + for _, kindAndName := range kindAndNames { + split := strings.Split(kindAndName, "/") + clientObj := getClientObject(split[0]) + err := testConfig.K8sClient.Get(testConfig.Context, + types.NamespacedName{Namespace: testConfig.NsName, Name: split[1]}, clientObj) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + err = testConfig.K8sClient.Delete(testConfig.Context, clientObj) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Eventually(func() bool { + clientObj := getClientObject(split[0]) + err := testConfig.K8sClient.Get(testConfig.Context, + types.NamespacedName{Namespace: testConfig.NsName, Name: split[1]}, clientObj) + return apierrors.IsNotFound(err) + }, testConfig.ExistsTimeout, testConfig.Interval).Should(gomega.BeTrue()) + } +} + +// applyYAMLFile reads a file containing YAML (possibly multiple docs) +// and applies each object to the cluster. +func ApplyYAMLFile(testConfig *TestConfig, filePath string) { + // Create the resources from the manifest file + CreateObjsFromYaml(testConfig, ReadYaml(filePath)) +} + +// ReadYaml is a helper function to read in K8S YAML files and split by the --- separator +func ReadYaml(filePath string) []string { + ginkgo.By("Reading YAML file: " + filePath) + yamlBytes, err := os.ReadFile(filePath) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + + // Split multiple docs, if needed + return strings.Split(string(yamlBytes), "\n---") +} + +func getClientObject(kind string) client.Object { + switch strings.ToLower(kind) { + case "clusterrole": + return &rbacv1.ClusterRole{} + case "clusterrolebinding": + return &rbacv1.ClusterRoleBinding{} + case "configmap": + return &corev1.ConfigMap{} + case "customresourcedefinition": + return &apiextv1.CustomResourceDefinition{} + case "deployment": + return &appsv1.Deployment{} + case "inferencepool": + return &v1.InferencePool{} + case "pod": + return &corev1.Pod{} + case "role": + return &rbacv1.Role{} + case "rolebinding": + return &rbacv1.RoleBinding{} + case "secret": + return &corev1.Secret{} + case "service": + return &corev1.Service{} + case "serviceaccount": + return &corev1.ServiceAccount{} + default: + ginkgo.Fail("unsupported K8S kind "+kind, 1) + return nil + } } From 5846e24922067760c5f6555a5a1730f4247fca56 Mon Sep 17 00:00:00 2001 From: Sanjeev Rampal Date: Tue, 16 Sep 2025 18:48:09 -0400 Subject: [PATCH 044/133] Add BBR user guide, yaml for model-aware routing (#1498) --- README.md | 3 +- .../manifests/bbr-example/httproute_bbr.yaml | 51 ++++ .../manifests/bbr-example/vllm-phi4-mini.yaml | 88 ++++++ pkg/bbr/README.md | 4 - site-src/guides/index.md | 6 +- .../guides/serve-multiple-genai-models.md | 252 ++++++++++++------ site-src/index.md | 1 + 7 files changed, 314 insertions(+), 91 deletions(-) create mode 100644 config/manifests/bbr-example/httproute_bbr.yaml create mode 100644 config/manifests/bbr-example/vllm-phi4-mini.yaml diff --git a/README.md b/README.md index 51aaf2829..19e801b82 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ The following specific terms to this project: performance, availability and capabilities to optimize routing. Includes things like [Prefix Cache] status or [LoRA Adapters] availability. - **Endpoint Picker(EPP)**: An implementation of an `Inference Scheduler` with additional Routing, Flow, and Request Control layers to allow for sophisticated routing strategies. Additional info on the architecture of the EPP [here](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/docs/proposals/0683-epp-architecture-proposal). - +- **Body Based Router(BBR)**: An optional additional [ext-proc](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_proc_filter) server that parses the http body of the inference prompt message and extracts information (currently the model name for OpenAI API style messages) into a format which can then be used by the gateway for routing purposes. Additional info [here](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/pkg/bbr/README.md) and in the documentation [user guides](https://gateway-api-inference-extension.sigs.k8s.io/guides/). + The following are key industry terms that are important to understand for this project: diff --git a/config/manifests/bbr-example/httproute_bbr.yaml b/config/manifests/bbr-example/httproute_bbr.yaml new file mode 100644 index 000000000..8702546dc --- /dev/null +++ b/config/manifests/bbr-example/httproute_bbr.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: llm-llama-route +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: inference-gateway + rules: + - backendRefs: + - group: inference.networking.k8s.io + kind: InferencePool + name: vllm-llama3-8b-instruct + matches: + - path: + type: PathPrefix + value: / + headers: + - type: Exact + name: X-Gateway-Model-Name + value: 'meta-llama/Llama-3.1-8B-Instruct' + timeouts: + request: 300s +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: llm-phi4-route +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: inference-gateway + rules: + - backendRefs: + - group: inference.networking.k8s.io + kind: InferencePool + name: vllm-phi4-mini-instruct + matches: + - path: + type: PathPrefix + value: / + headers: + - type: Exact + name: X-Gateway-Model-Name + value: 'microsoft/Phi-4-mini-instruct' + timeouts: + request: 300s +--- diff --git a/config/manifests/bbr-example/vllm-phi4-mini.yaml b/config/manifests/bbr-example/vllm-phi4-mini.yaml new file mode 100644 index 000000000..7f7827cb9 --- /dev/null +++ b/config/manifests/bbr-example/vllm-phi4-mini.yaml @@ -0,0 +1,88 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: phi4-mini + namespace: default +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + volumeMode: Filesystem +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: phi4-mini + namespace: default + labels: + app: phi4-mini +spec: + replicas: 1 + selector: + matchLabels: + app: phi4-mini + template: + metadata: + labels: + app: phi4-mini + spec: + volumes: + - name: cache-volume + persistentVolumeClaim: + claimName: phi4-mini + containers: + - name: phi4-mini + image: vllm/vllm-openai:latest + command: ["/bin/sh", "-c"] + args: [ + "vllm serve microsoft/Phi-4-mini-instruct --trust-remote-code --enable-chunked-prefill" + ] + env: + - name: HUGGING_FACE_HUB_TOKEN + valueFrom: + secretKeyRef: + name: hf-token + key: token + ports: + - containerPort: 8000 + resources: + limits: + nvidia.com/gpu: "1" + requests: + nvidia.com/gpu: "1" + volumeMounts: + - mountPath: /root/.cache/huggingface + name: cache-volume + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 600 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 600 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: phi4-mini + namespace: default +spec: + ports: + - name: http-phi4-mini + port: 80 + protocol: TCP + targetPort: 8000 + # The label selector should match the deployment labels & it is useful for prefix caching feature + selector: + app: phi4-mini + sessionAffinity: None + type: ClusterIP + diff --git a/pkg/bbr/README.md b/pkg/bbr/README.md index b5b6f770d..80ab38354 100644 --- a/pkg/bbr/README.md +++ b/pkg/bbr/README.md @@ -8,7 +8,3 @@ body of the HTTP request. However, most implementations do not support routing based on the request body. This extension helps bridge that gap for clients. This extension works by parsing the request body. If it finds a `model` parameter in the request body, it will copy the value of that parameter into a request header. - -This extension is intended to be paired with an `ext_proc` capable Gateway. There is not -a standard way to represent this kind of extension in Gateway API yet, so we recommend -referring to implementation-specific documentation for how to deploy this extension. diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 9fe5c7a8d..e7dd8611b 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -101,7 +101,7 @@ Tooling: === "Istio" ```bash - export GATEWAY_PROVIDER=none + export GATEWAY_PROVIDER=istio helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ @@ -319,6 +319,10 @@ Tooling: kubectl get httproute llm-route -o yaml ``` +### Deploy the Body Based Router Extension (Optional) + + This guide shows how to get started with serving only 1 base model type per L7 URL path. If in addition, you wish to exercise model-aware routing such that more than 1 base model is served at the same L7 url path, that requires use of the (optional) Body Based Routing (BBR) extension which is described in a following section of the guide, namely the [`Serving Multiple GenAI Models`](serve-multiple-genai-models.md) section. + ### Deploy InferenceObjective (Optional) Deploy the sample InferenceObjective which allows you to specify priority of requests. diff --git a/site-src/guides/serve-multiple-genai-models.md b/site-src/guides/serve-multiple-genai-models.md index 1d90767d0..0beee86fb 100644 --- a/site-src/guides/serve-multiple-genai-models.md +++ b/site-src/guides/serve-multiple-genai-models.md @@ -1,10 +1,8 @@ # Serve multiple generative AI models A company wants to deploy multiple large language models (LLMs) to a cluster to serve different workloads. -For example, they might want to deploy a Gemma3 model for a chatbot interface and a DeepSeek model for a recommendation application. -The company needs to ensure optimal serving performance for these LLMs. -By using an Inference Gateway, you can deploy these LLMs on your cluster with your chosen accelerator configuration in an `InferencePool`. -You can then route requests based on the model name (such as `chatbot` and `recommender`). +For example, they might want to deploy a Gemma3 model for a chatbot interface and a DeepSeek model for a recommendation application (or as in the example in this guide, a combination of a Llama3 model and a smaller Phi4 model).. You may choose to locate these 2 models at 2 different L7 url paths and follow the steps described in the [`Getting started`](index.md) guide for each such model as already described. However you may also need to serve multiple models located at the same L7 url path and rely on parsing information such as +the Model name in the LLM prompt requests as defined in the OpenAI API format which is commonly used by most models. For such Model-aware routing, you can use the Body-Based Routing feature as described in this guide. ## How @@ -13,73 +11,156 @@ The model name is extracted by [Body-Based routing](https://github.com/kubernete from the request body to the header. The header is then matched to dispatch requests to different `InferencePool` (and their EPPs) instances. -### Deploy Body-Based Routing +### Example Model-Aware Routing using Body-Based Routing (BBR) -To enable body-based routing, you need to deploy the Body-Based Routing ExtProc server using Helm. Depending on your Gateway provider, you can use one of the following commands: +This guide assumes you have already setup the cluster for basic model serving as described in the [`Getting started`](index.md) guide and this guide describes the additional steps needed from that point onwards in order to deploy and exercise an example of routing across multiple models. + + +### Deploy Body-Based Routing Extension + +To enable body-based routing, you need to deploy the Body-Based Routing ExtProc server using Helm. This is a separate ExtProc server from the EndPoint Picker and when installed, is automatically inserted at the start of the gateway's ExtProc chain ahead of other EtxProc servers such as EPP. + +First install this server. Depending on your Gateway provider, you can use one of the following commands: === "GKE" ```bash helm install body-based-router \ - --set provider.name=gke \ - --version v0.5.1 \ - oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing + --set provider.name=gke \ + --version v1.0.0 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing ``` === "Istio" ```bash helm install body-based-router \ - --set provider.name=istio \ - --version v0.5.1 \ - oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing + --set provider.name=istio \ + --version v1.0.0 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing ``` === "Other" ```bash helm install body-based-router \ - --version v0.5.1 \ - oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing + --version v1.0.0 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing + ``` + +Once this is installed, verify that the BBR pod is running without errors using the command `kubectl get pods`. + +### Serving a Second Base Model +Next deploy the second base model that will be served from the same L7 path (which is `/`) as the `meta-llama/Llama-3.1-8B-Instruct` model already being served after following the steps from the [`Getting started`](index.md) guide. In this example the 2nd model is `microsoft/Phi-4-mini-instruct` a relatively small model ( about 3B parameters) from HuggingFace. Note that for this exercise, there need to be atleast 2 GPUs available on the system one each for the two models being served. Serve the second model via the following command. + +```bash +kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api-inference-extension/refs/heads/main/config/manifests/bbr-example/vllm-phi4-mini.yaml +``` +Once this is installed, and after allowing for model download and startup time which can last several minutes, verify that the pod with this 2nd LLM phi4-mini, is running without errors using the command `kubectl get pods`. + +### Deploy the 2nd InferencePool and Endpoint Picker Extension +We also want to use an InferencePool and EndPoint Picker for this second model in addition to the Body Based Router in order to be able to schedule across multiple endpoints or LORA adapters within each base model. Hence we create these for our second model as follows. + +=== "GKE" + + ```bash + export GATEWAY_PROVIDER=gke + helm install vllm-phi4-mini-instruct \ + --set inferencePool.modelServers.matchLabels.app=phi4-mini \ + --set provider.name=$GATEWAY_PROVIDER \ + --version v1.0.0 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool + ``` + +=== "Istio" + + ```bash + export GATEWAY_PROVIDER=istio + helm install vllm-phi4-mini-instruct \ + --set inferencePool.modelServers.matchLabels.app=phi4-mini \ + --set provider.name=$GATEWAY_PROVIDER \ + --version v1.0.0 \ + oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` +After executing this, very that you see two InferencePools and two EPP pods, one per base model type, running without errors, using the CLIs `kubectl get inferencepools` and `kubectl get pods`. + ### Configure HTTPRoute -This example illustrates a conceptual example regarding how to use the `HTTPRoute` object to route based on model name like “chatbot” or “recommender” to `InferencePool`. +Before configuring the httproutes for the models, we need to delete the prior httproute created for the vllm-llama3-8b-instruct model because we will alter the routing to now also match on the model name as determined by the `X-Gateway-Model-Name` http header that will get inserted by the BBR extension after parsing the Model name from the body of the LLM request message. + +```bash +kubectl delete httproute llm-route +``` + +Now configure new HTTPRoutes, one per each model we want to serve via BBR using the following command which configures both routes. Also examine this manifest file, to see how the `X-Gateway-Model-Name` is used for a header match in the Gateway's rules to route requests to the correct Backend based on model name. For convenience the manifest is also listed below in order to view this routing configuration. + +```bash +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/bbr-example/httproute_bbr.yaml +``` ```yaml +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: routes-to-llms + name: llm-llama-route spec: parentRefs: - - name: inference-gateway + - group: gateway.networking.k8s.io + kind: Gateway + name: inference-gateway rules: - - matches: - - headers: - - type: Exact - name: X-Gateway-Model-Name # (1)! - value: chatbot - path: + - backendRefs: + - group: inference.networking.k8s.io + kind: InferencePool + name: vllm-llama3-8b-instruct + matches: + - path: type: PathPrefix value: / - backendRefs: - - name: gemma3 - group: inference.networking.x-k8s.io + headers: + - type: Exact + name: X-Gateway-Model-Name # (1)! + value: 'meta-llama/Llama-3.1-8B-Instruct' + timeouts: + request: 300s +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: llm-phi4-route +spec: + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: inference-gateway + rules: + - backendRefs: + - group: inference.networking.k8s.io kind: InferencePool - - matches: - - headers: - - type: Exact - name: X-Gateway-Model-Name # (2)! - value: recommender - path: + name: vllm-phi4-mini-instruct + matches: + - path: type: PathPrefix value: / - backendRefs: - - name: deepseek-r1 - group: inference.networking.x-k8s.io - kind: InferencePool + headers: + - type: Exact + name: X-Gateway-Model-Name # (2)! + value: 'microsoft/Phi-4-mini-instruct' + timeouts: + request: 300s +--- +``` + +Confirm that the HTTPRoute status conditions include `Accepted=True` and `ResolvedRefs=True` for both routes: + +```bash +kubectl get httproute llm-llama-route -o yaml +``` + +```bash +kubectl get httproute llm-phi4-route -o yaml ``` 1. [BBR](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) is being used to copy the model name from the request body to the header with key `X-Gateway-Model-Name`. The header can then be used in the `HTTPRoute` to route requests to different `InferencePool` instances. @@ -88,58 +169,59 @@ spec: ## Try it out 1. Get the gateway IP: -```bash -IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}'); PORT=80 -``` + ```bash + IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}'); PORT=80 + ``` === "Chat Completions API" - 1. Send a few requests to model `chatbot` as follows: - ```bash - curl -X POST -i ${IP}:${PORT}/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "chatbot", - "messages": [{"role": "user", "content": "What is the color of the sky?"}], - "max_tokens": 100, - "temperature": 0 - }' - ``` - - 2. Send a few requests to model `recommender` as follows: - ```bash - curl -X POST -i ${IP}:${PORT}/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "recommender", - "messages": [{"role": "user", "content": "Give me restaurant recommendations in Paris"}], - "max_tokens": 100, - "temperature": 0 - }' - ``` + 1. Send a few requests to Llama model as follows: + ```bash + curl -X POST -i ${IP}:${PORT}/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "meta-llama/Llama-3.1-8B-Instruct", + "prompt": "Linux is said to be an open source kernel because ", + "max_tokens": 100, + "temperature": 0 + }' + ``` + + 2. Send a few requests to the Phi4 as follows: + ```bash + curl -X POST -i ${IP}:${PORT}/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "microsoft/Phi-4-mini-instruct", + "prompt": "2+2 is ", + "max_tokens": 20, + "temperature": 0 + }' + ``` === "Completions API" - 1. Send a few requests to model `chatbot` as follows: - ```bash - curl -X POST -i ${IP}:${PORT}/v1/completions \ - -H 'Content-Type: application/json' \ - -d '{ - "model": "chatbot", - "prompt": "What is the color of the sky", - "max_tokens": 100, - "temperature": 0 - }' - ``` - - 2. Send a few requests to model `recommender` as follows: - ```bash - curl -X POST -i ${IP}:${PORT}/v1/completions \ - -H 'Content-Type: application/json' \ - -d '{ - "model": "recommender", - "prompt": "Give me restaurant recommendations in Paris", - "max_tokens": 100, - "temperature": 0 - }' - ``` + 1. Send a few requests to Llama model as follows: + ```bash + curl -X POST -i ${IP}:${PORT}/v1/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "meta-llama/Llama-3.1-8B-Instruct", + "prompt": "Linux is said to be an open source kernel because ", + "max_tokens": 100, + "temperature": 0 + }' + ``` + + 2. Send a few requests to the Phi4 as follows: + ```bash + curl -X POST -i ${IP}:${PORT}/v1/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "microsoft/Phi-4-mini-instruct", + "prompt": "2+2 is ", + "max_tokens": 20, + "temperature": 0 + }' + ``` + diff --git a/site-src/index.md b/site-src/index.md index cf1ddb32a..0fbb338f8 100644 --- a/site-src/index.md +++ b/site-src/index.md @@ -25,6 +25,7 @@ The following specific terms to this project: performance, availability and capabilities to optimize routing. Includes things like [Prefix Cache](https://docs.vllm.ai/en/stable/design/v1/prefix_caching.html) status or [LoRA Adapters](https://docs.vllm.ai/en/stable/features/lora.html) availability. - **Endpoint Picker(EPP)**: An implementation of an `Inference Scheduler` with additional Routing, Flow, and Request Control layers to allow for sophisticated routing strategies. Additional info on the architecture of the EPP [here](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/docs/proposals/0683-epp-architecture-proposal). +- **Body Based Router(BBR)**: An additional (and optional) implementation of an extension that extracts information from the body portion of the inference request, currently the model name attribute from the body of an OpenAI API request, which can then be used by the gateway to perform model-aware functions such as routing/scheduling. This may be used along with the EPP in order to have a combination of model picking and endpoint picking functionality. [Inference Gateway]:#concepts-and-definitions From 720ed0a33c482244b92ee81570b1c4fcbcf29882 Mon Sep 17 00:00:00 2001 From: Bob Tian Date: Tue, 16 Sep 2025 16:08:09 -0700 Subject: [PATCH 045/133] fix(conformance): Use pinned version of EPP for conformance test instead of main. (#1262) * Use pinned version of EPP for conformance test instead of main. * update pinned version. --- conformance/resources/base.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conformance/resources/base.yaml b/conformance/resources/base.yaml index 7f4a7fa20..a421f5006 100644 --- a/conformance/resources/base.yaml +++ b/conformance/resources/base.yaml @@ -200,7 +200,7 @@ spec: terminationGracePeriodSeconds: 130 containers: - name: epp - image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:main + image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v1.0.0 imagePullPolicy: Always args: - --pool-name @@ -298,7 +298,7 @@ spec: terminationGracePeriodSeconds: 130 containers: - name: epp - image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:main + image: us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/epp:v1.0.0 imagePullPolicy: Always args: - --pool-name From ff771e1f483d688c747d12a95a52f26797540b83 Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Tue, 16 Sep 2025 23:00:10 -0700 Subject: [PATCH 046/133] Updates gateway manifests for v1 InferencePool (#1603) Signed-off-by: Daneyon Hansen --- config/manifests/gateway/agentgateway/httproute.yaml | 2 +- config/manifests/gateway/envoyaigateway/httproute.yaml | 2 +- config/manifests/gateway/istio/httproute.yaml | 2 +- config/manifests/gateway/kgateway/httproute.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/manifests/gateway/agentgateway/httproute.yaml b/config/manifests/gateway/agentgateway/httproute.yaml index 18e90ced6..18c450708 100644 --- a/config/manifests/gateway/agentgateway/httproute.yaml +++ b/config/manifests/gateway/agentgateway/httproute.yaml @@ -9,7 +9,7 @@ spec: name: inference-gateway rules: - backendRefs: - - group: inference.networking.x-k8s.io + - group: inference.networking.k8s.io kind: InferencePool name: vllm-llama3-8b-instruct matches: diff --git a/config/manifests/gateway/envoyaigateway/httproute.yaml b/config/manifests/gateway/envoyaigateway/httproute.yaml index e30b56140..e685940fe 100644 --- a/config/manifests/gateway/envoyaigateway/httproute.yaml +++ b/config/manifests/gateway/envoyaigateway/httproute.yaml @@ -9,7 +9,7 @@ spec: name: inference-gateway rules: - backendRefs: - - group: inference.networking.x-k8s.io + - group: inference.networking.k8s.io kind: InferencePool name: vllm-llama3-8b-instruct matches: diff --git a/config/manifests/gateway/istio/httproute.yaml b/config/manifests/gateway/istio/httproute.yaml index 18e90ced6..18c450708 100644 --- a/config/manifests/gateway/istio/httproute.yaml +++ b/config/manifests/gateway/istio/httproute.yaml @@ -9,7 +9,7 @@ spec: name: inference-gateway rules: - backendRefs: - - group: inference.networking.x-k8s.io + - group: inference.networking.k8s.io kind: InferencePool name: vllm-llama3-8b-instruct matches: diff --git a/config/manifests/gateway/kgateway/httproute.yaml b/config/manifests/gateway/kgateway/httproute.yaml index 18e90ced6..18c450708 100644 --- a/config/manifests/gateway/kgateway/httproute.yaml +++ b/config/manifests/gateway/kgateway/httproute.yaml @@ -9,7 +9,7 @@ spec: name: inference-gateway rules: - backendRefs: - - group: inference.networking.x-k8s.io + - group: inference.networking.k8s.io kind: InferencePool name: vllm-llama3-8b-instruct matches: From c19ed87ed5c17c392de9d20035f93c62e714cb28 Mon Sep 17 00:00:00 2001 From: Sage <80211083+sagiahrac@users.noreply.github.com> Date: Wed, 17 Sep 2025 12:58:12 +0300 Subject: [PATCH 047/133] duplicate fix (#1605) Signed-off-by: Sage Ahrac --- site-src/guides/serve-multiple-genai-models.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/site-src/guides/serve-multiple-genai-models.md b/site-src/guides/serve-multiple-genai-models.md index 0beee86fb..aede7a677 100644 --- a/site-src/guides/serve-multiple-genai-models.md +++ b/site-src/guides/serve-multiple-genai-models.md @@ -163,8 +163,7 @@ kubectl get httproute llm-llama-route -o yaml kubectl get httproute llm-phi4-route -o yaml ``` -1. [BBR](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) is being used to copy the model name from the request body to the header with key `X-Gateway-Model-Name`. The header can then be used in the `HTTPRoute` to route requests to different `InferencePool` instances. -2. [BBR](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) is being used to copy the model name from the request body to the header with key `X-Gateway-Model-Name`. The header can then be used in the `HTTPRoute` to route requests to different `InferencePool` instances. +[BBR](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) is being used to copy the model name from the request body to the header with key `X-Gateway-Model-Name`. The header can then be used in the `HTTPRoute` to route requests to different `InferencePool` instances. ## Try it out From 5953cb90158cf1f80e2e8a710cd5ace0cbab1454 Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Wed, 17 Sep 2025 12:34:09 -0700 Subject: [PATCH 048/133] Docs: Updates kgateway and agentgateway Implementations for v1.0.0 (#1607) * WIP: Bump kgateway in quickstart Signed-off-by: Daneyon Hansen * Docs: Updates kgateway and agentgateway implementations Signed-off-by: Daneyon Hansen --------- Signed-off-by: Daneyon Hansen --- site-src/guides/index.md | 11 +++++------ site-src/implementations/gateways.md | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index e7dd8611b..2b08f1ba6 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -225,9 +225,8 @@ Tooling: === "Kgateway" - [Kgateway](https://kgateway.dev/) recently added support for inference extension as a **technical preview**. This means do not - run Kgateway with inference extension in production environments. Refer to [Issue 10411](https://github.com/kgateway-dev/kgateway/issues/10411) - for the list of caveats, supported features, etc. + [Kgateway](https://kgateway.dev/) added Inference Gateway support as a **technical preview** in the + [v2.0.0 release](https://github.com/kgateway-dev/kgateway/releases/tag/v2.0.0). InferencePool v1.0.0 is currently supported in the latest [rolling release](https://github.com/kgateway-dev/kgateway/releases/tag/v2.1.0-main), which includes the latest changes but may be unstable until the [v2.1.0 release](https://github.com/kgateway-dev/kgateway/milestone/58) is published. 1. Requirements @@ -237,7 +236,7 @@ Tooling: 2. Set the Kgateway version and install the Kgateway CRDs. ```bash - KGTW_VERSION=v2.0.4 + KGTW_VERSION=v2.1.0-main helm upgrade -i --create-namespace --namespace kgateway-system --version $KGTW_VERSION kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds ``` @@ -274,7 +273,7 @@ Tooling: === "Agentgateway" - [Agentgateway](https://agentgateway.dev/) is a purpose-built proxy designed for AI workloads, and comes with native support for inference routing. Agentgateway integrates with [Kgateway](https://kgateway.dev/) as it's control plane. + [Agentgateway](https://agentgateway.dev/) is a purpose-built proxy designed for AI workloads, and comes with native support for Inference Gateway. Agentgateway integrates with [Kgateway](https://kgateway.dev/) as it's control plane. InferencePool v1.0.0 is currently supported in the latest [rolling release](https://github.com/kgateway-dev/kgateway/releases/tag/v2.1.0-main), which includes the latest changes but may be unstable until the [v2.1.0 release](https://github.com/kgateway-dev/kgateway/milestone/58) is published. 1. Requirements @@ -284,7 +283,7 @@ Tooling: 2. Set the Kgateway version and install the Kgateway CRDs. ```bash - KGTW_VERSION=v2.0.4 + KGTW_VERSION=v2.1.0-main helm upgrade -i --create-namespace --namespace kgateway-system --version $KGTW_VERSION kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds ``` diff --git a/site-src/implementations/gateways.md b/site-src/implementations/gateways.md index 5ab1e0920..4ce052b0e 100644 --- a/site-src/implementations/gateways.md +++ b/site-src/implementations/gateways.md @@ -18,9 +18,13 @@ This project has several implementations that are planned or in progress: ## Agentgateway -[Agentgateway](https://agentgateway.dev/) is an open source Gateway API implementation focusing on AI use cases, including LLM consumption, LLM serving, agent-to-agent ([A2A](https://a2aproject.github.io/A2A/latest/)), and agent-to-tool ([MCP](https://modelcontextprotocol.io/introduction)). It is the first and only proxy designed specifically for the Kubernetes Gateway API, powered by a high performance and scalable Rust dataplane implementation. +[Agentgateway](https://agentgateway.dev/) is an open source Gateway API and Inference Gateway +[v1.0.0 conformant](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/conformance/reports/v1.0.0/gateway/kgateway/agentgateway) +implementation focusing on AI use cases, including LLM consumption, LLM serving, agent-to-agent ([A2A](https://a2aproject.github.io/A2A/latest/)), +and agent-to-tool ([MCP](https://modelcontextprotocol.io/introduction)). It is the first and only proxy designed specifically for the Kubernetes Gateway API +that is powered by a high performance and scalable Rust dataplane. -Agentgateway comes with native support for Gateway API Inference Extension, powered by the [Kgateway](https://kgateway.dev/) control plane. +Agentgateway can run independently or can be managed by [Kgateway](https://kgateway.dev/). ## Alibaba Cloud Container Service for Kubernetes @@ -41,7 +45,6 @@ by [this Issue](https://github.com/AliyunContainerService/ack-gateway-api/issues [ack-gie]:https://www.alibabacloud.com/help/en/ack/product-overview/ack-gateway-with-inference-extension [ack-gie-usage]:https://www.alibabacloud.com/help/en/ack/ack-managed-and-ack-dedicated/user-guide/intelligent-routing-and-traffic-management-with-ack-gateway-inference-extension - ## Envoy AI Gateway [Envoy AI Gateway][aigw-home] is an open source project built on top of @@ -88,8 +91,8 @@ Issue](https://github.com/istio/istio/issues/55768). ## Kgateway -[Kgateway](https://kgateway.dev/) is a Gateway API Inference Extension -[conformant](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/conformance/reports/v0.5.1/gateway/kgateway) -gateway that can run [independently](https://gateway-api-inference-extension.sigs.k8s.io/guides/#__tabbed_3_3), as an [Istio waypoint](https://kgateway.dev/blog/extend-istio-ambient-kgateway-waypoint/), -or within your [llm-d infrastructure](https://github.com/llm-d-incubation/llm-d-infra) to improve accelerator (GPU) -utilization for AI inference workloads. +[Kgateway](https://kgateway.dev/) is a Gateway API and Inference Gateway +[v1.0.0 conformant](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/conformance/reports/v1.0.0/gateway/kgateway) +implementation that can run [independently](https://gateway-api-inference-extension.sigs.k8s.io/guides/#__tabbed_3_3), as an +[Istio waypoint](https://kgateway.dev/blog/extend-istio-ambient-kgateway-waypoint/), or within your +[llm-d infrastructure](https://github.com/llm-d-incubation/llm-d-infra) to improve accelerator (GPU) utilization for AI inference workloads. From 8b214c468731ace4fe921954a0d7e67e5f99f326 Mon Sep 17 00:00:00 2001 From: Sanjeev Rampal Date: Thu, 18 Sep 2025 11:12:15 -0400 Subject: [PATCH 049/133] Fix some markdown formatting errors (#1609) * Fix some markdown formatting errors * Update site-src/guides/index.md Co-authored-by: Abdullah Gharaibeh <40361897+ahg-g@users.noreply.github.com> --------- Co-authored-by: Abdullah Gharaibeh <40361897+ahg-g@users.noreply.github.com> --- mkdocs.yml | 1 + site-src/guides/index.md | 11 +++++------ site-src/guides/serve-multiple-genai-models.md | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 8262f6fb0..c90b1bb49 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ theme: favicon: images/favicon-64.png features: - content.code.annotate + - content.code.copy - search.highlight - navigation.tabs - navigation.top diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 2b08f1ba6..6eb42a27f 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -318,19 +318,14 @@ Tooling: kubectl get httproute llm-route -o yaml ``` -### Deploy the Body Based Router Extension (Optional) - - This guide shows how to get started with serving only 1 base model type per L7 URL path. If in addition, you wish to exercise model-aware routing such that more than 1 base model is served at the same L7 url path, that requires use of the (optional) Body Based Routing (BBR) extension which is described in a following section of the guide, namely the [`Serving Multiple GenAI Models`](serve-multiple-genai-models.md) section. - ### Deploy InferenceObjective (Optional) - Deploy the sample InferenceObjective which allows you to specify priority of requests. +Deploy the sample InferenceObjective which allows you to specify priority of requests. ```bash kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/inferenceobjective.yaml ``` - ### Try it out Wait until the gateway is ready. @@ -347,6 +342,10 @@ Tooling: }' ``` +### Deploy the Body Based Router Extension (Optional) + +This guide has shown how to get started with serving a single base model type per L7 URL path. If after this exercise, you wish to continue on to exercise model-aware routing such that more than 1 base model is served at the same L7 url path, that requires use of the (optional) Body Based Routing (BBR) extension which is described in a separate section of the documentation, namely the [`Serving Multiple GenAI Models`](serve-multiple-genai-models.md) section. If you wish to exercise that function, then retain the setup you have deployed so far from this guide and move on to the additional steps described in [that guide](serve-multiple-genai-models.md) or else move on to the following section to cleanup your setup. + ### Cleanup The following instructions assume you would like to cleanup ALL resources that were created in this quickstart guide. diff --git a/site-src/guides/serve-multiple-genai-models.md b/site-src/guides/serve-multiple-genai-models.md index aede7a677..2064f999a 100644 --- a/site-src/guides/serve-multiple-genai-models.md +++ b/site-src/guides/serve-multiple-genai-models.md @@ -83,7 +83,7 @@ We also want to use an InferencePool and EndPoint Picker for this second model i oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` -After executing this, very that you see two InferencePools and two EPP pods, one per base model type, running without errors, using the CLIs `kubectl get inferencepools` and `kubectl get pods`. +After executing this, verify that you see two InferencePools and two EPP pods, one per base model type, running without errors, using the CLIs `kubectl get inferencepools` and `kubectl get pods`. ### Configure HTTPRoute @@ -100,7 +100,7 @@ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extens ``` ```yaml ---- +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: @@ -121,11 +121,12 @@ spec: value: / headers: - type: Exact + #Body-Based routing(https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) is being used to copy the model name from the request body to the header. name: X-Gateway-Model-Name # (1)! value: 'meta-llama/Llama-3.1-8B-Instruct' timeouts: request: 300s ---- +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: @@ -146,14 +147,15 @@ spec: value: / headers: - type: Exact - name: X-Gateway-Model-Name # (2)! + #Body-Based routing(https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) is being used to copy the model name from the request body to the header. + name: X-Gateway-Model-Name value: 'microsoft/Phi-4-mini-instruct' timeouts: request: 300s ---- +--- ``` -Confirm that the HTTPRoute status conditions include `Accepted=True` and `ResolvedRefs=True` for both routes: +Before testing the setup, confirm that the HTTPRoute status conditions include `Accepted=True` and `ResolvedRefs=True` for both routes using the following commands. ```bash kubectl get httproute llm-llama-route -o yaml @@ -163,8 +165,6 @@ kubectl get httproute llm-llama-route -o yaml kubectl get httproute llm-phi4-route -o yaml ``` -[BBR](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) is being used to copy the model name from the request body to the header with key `X-Gateway-Model-Name`. The header can then be used in the `HTTPRoute` to route requests to different `InferencePool` instances. - ## Try it out 1. Get the gateway IP: From a3a583eb1860f4f54f46a3083e4cc19920403de9 Mon Sep 17 00:00:00 2001 From: Yizheng Jiao Date: Thu, 18 Sep 2025 11:14:12 -0700 Subject: [PATCH 050/133] helm: add NAMESPACE env var to EPP deployment for pool namespace (#1610) * helm: add NAMESPACE env var to EPP deployment for pool namespace fallback * remove comment --- config/charts/inferencepool/templates/epp-deployment.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml index dce1ed45c..abdc05a2c 100644 --- a/config/charts/inferencepool/templates/epp-deployment.yaml +++ b/config/charts/inferencepool/templates/epp-deployment.yaml @@ -25,8 +25,6 @@ spec: args: - --pool-name - {{ .Release.Name }} - - --pool-namespace - - {{ .Release.Namespace }} {{- if ne .Values.inferencePool.apiVersion "inference.networking.k8s.io" }} - --pool-group - "{{ (split "/" .Values.inferencePool.apiVersion)._0 }}" @@ -81,8 +79,12 @@ spec: {{- end }} initialDelaySeconds: 5 periodSeconds: 10 - {{- if .Values.inferenceExtension.env }} env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- if .Values.inferenceExtension.env }} {{- toYaml .Values.inferenceExtension.env | nindent 8 }} {{- end }} volumeMounts: From 410aa22840d6627416fc860130b2a74e432068e9 Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Thu, 18 Sep 2025 21:14:19 +0300 Subject: [PATCH 051/133] chore: bump sim version (#1612) Signed-off-by: Nir Rozenbaum --- config/manifests/vllm/sim-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/manifests/vllm/sim-deployment.yaml b/config/manifests/vllm/sim-deployment.yaml index 8229a01b4..2415c1066 100644 --- a/config/manifests/vllm/sim-deployment.yaml +++ b/config/manifests/vllm/sim-deployment.yaml @@ -14,7 +14,7 @@ spec: spec: containers: - name: vllm-sim - image: ghcr.io/llm-d/llm-d-inference-sim:v0.4.0 + image: ghcr.io/llm-d/llm-d-inference-sim:v0.5.0 imagePullPolicy: Always args: - --model From d3d5333e3a387ab4d10e470e0e485f09d182c376 Mon Sep 17 00:00:00 2001 From: Bob Tian Date: Fri, 19 Sep 2025 10:26:12 -0700 Subject: [PATCH 052/133] add gke monitoring helm support. (#1600) * fix gke monitoring. * change to namespaced resources as much as possible. * update helm chart readme. * resolve nits. * move autopilot to provider.gke. --- config/charts/inferencepool/README.md | 6 +- .../templates/epp-sa-token-secret.yaml | 2 +- .../charts/inferencepool/templates/gke.yaml | 89 ++++++++++++++++++- config/charts/inferencepool/values.yaml | 8 ++ 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md index 5a5663d1a..b6629d2b8 100644 --- a/config/charts/inferencepool/README.md +++ b/config/charts/inferencepool/README.md @@ -133,7 +133,9 @@ inferenceExtension: **Note:** Prometheus monitoring requires the Prometheus Operator and ServiceMonitor CRD to be installed in the cluster. -For GKE environments, monitoring is automatically configured when `provider.name` is set to `gke`. +For GKE environments, monitoring is enabled by setting `provider.name` to `gke` and `inferenceExtension.monitoring.gke.enabled` to `true`. This will create the necessary `PodMonitoring` and RBAC resources for metrics collection. + +If you are using a GKE Autopilot cluster, you also need to set `provider.gke.autopilot` to `true`. Then apply it with: @@ -174,8 +176,10 @@ The following table list the configurable parameters of the chart. | `inferenceExtension.monitoring.interval` | Metrics scraping interval for monitoring. Defaults to `10s`. | | `inferenceExtension.monitoring.secret.name` | Name of the service account token secret for metrics authentication. Defaults to `inference-gateway-sa-metrics-reader-secret`. | | `inferenceExtension.monitoring.prometheus.enabled` | Enable Prometheus ServiceMonitor creation for EPP metrics collection. Defaults to `false`. | +| `inferenceExtension.monitoring.gke.enabled` | Enable GKE monitoring resources (`PodMonitoring` and RBAC). Defaults to `false`. | | `inferenceExtension.pluginsCustomConfig` | Custom config that is passed to EPP as inline yaml. | | `provider.name` | Name of the Inference Gateway implementation being used. Possible values: `gke`. Defaults to `none`. | +| `provider.gke.autopilot` | Set to `true` if the cluster is a GKE Autopilot cluster. This is only used if `provider.name` is `gke`. Defaults to `false`. | ## Notes diff --git a/config/charts/inferencepool/templates/epp-sa-token-secret.yaml b/config/charts/inferencepool/templates/epp-sa-token-secret.yaml index 9abee0fcd..df54b3475 100644 --- a/config/charts/inferencepool/templates/epp-sa-token-secret.yaml +++ b/config/charts/inferencepool/templates/epp-sa-token-secret.yaml @@ -1,4 +1,4 @@ -{{- if or .Values.inferenceExtension.monitoring.prometheus.enabled .Values.inferenceExtension.monitoring.gke.enabled }} +{{- if .Values.inferenceExtension.monitoring.prometheus.enabled }} apiVersion: v1 kind: Secret metadata: diff --git a/config/charts/inferencepool/templates/gke.yaml b/config/charts/inferencepool/templates/gke.yaml index f2296aafb..59e186a94 100644 --- a/config/charts/inferencepool/templates/gke.yaml +++ b/config/charts/inferencepool/templates/gke.yaml @@ -35,11 +35,44 @@ spec: timeoutSec: 300 # 5-minute timeout (adjust as needed) logging: enabled: true # log all requests by default +{{- if .Values.inferenceExtension.monitoring.gke.enabled }} +{{- $metricsReadSA := printf "%s-metrics-reader-sa" .Release.Name -}} +{{- $metricsReadSecretName := printf "%s-metrics-reader-secret" .Release.Name -}} +{{- $metricsReadRoleName := printf "%s-%s-metrics-reader" .Release.Namespace .Release.Name -}} +{{- $metricsReadRoleBindingName := printf "%s-%s-metrics-reader-role-binding" .Release.Namespace .Release.Name -}} +{{- $secretReadRoleName := printf "%s-metrics-reader-secret-read" .Release.Name -}} +{{- $gmpNamespace := "gmp-system" -}} +{{- $isAutopilot := false -}} +{{- with .Values.provider.gke }} + {{- $isAutopilot = .autopilot | default false -}} +{{- end }} +{{- if $isAutopilot -}} +{{- $gmpNamespace = "gke-gmp-system" -}} +{{- end -}} +{{- $gmpCollectorRoleBindingName := printf "%s:collector:%s-%s-metrics-reader-secret-read" $gmpNamespace .Release.Namespace .Release.Name -}} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $metricsReadSA }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ $metricsReadSecretName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} + annotations: + kubernetes.io/service-account.name: {{ $metricsReadSA }} +type: kubernetes.io/service-account-token --- apiVersion: monitoring.googleapis.com/v1 -kind: ClusterPodMonitoring +kind: PodMonitoring metadata: - name: {{ .Release.Namespace }}-{{ .Release.Name }} + name: {{ .Release.Name }} + namespace: {{ .Release.Namespace }} labels: {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} spec: @@ -52,10 +85,58 @@ spec: type: Bearer credentials: secret: - name: {{ .Values.inferenceExtension.monitoring.secret.name }} + name: {{ $metricsReadSecretName }} key: token - namespace: {{ .Release.Namespace }} selector: matchLabels: {{- include "gateway-api-inference-extension.selectorLabels" . | nindent 8 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ $metricsReadRoleName }} +rules: +- nonResourceURLs: + - /metrics + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ $metricsReadRoleBindingName }} +subjects: +- kind: ServiceAccount + name: {{ $metricsReadSA }} + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ $metricsReadRoleName }} + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $secretReadRoleName }} +rules: +- resources: + - secrets + apiGroups: [""] + verbs: ["get", "list", "watch"] + resourceNames: [{{ $metricsReadSecretName | quote }}] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $gmpCollectorRoleBindingName }} + namespace: {{ .Release.Namespace }} +roleRef: + name: {{ $secretReadRoleName }} + kind: Role + apiGroup: rbac.authorization.k8s.io +subjects: +- name: collector + namespace: {{ $gmpNamespace }} + kind: ServiceAccount +{{- end }} {{- end }} diff --git a/config/charts/inferencepool/values.yaml b/config/charts/inferencepool/values.yaml index f61b64e37..6476bd800 100644 --- a/config/charts/inferencepool/values.yaml +++ b/config/charts/inferencepool/values.yaml @@ -50,6 +50,9 @@ inferenceExtension: # Prometheus ServiceMonitor will be created when enabled for EPP metrics collection prometheus: enabled: false + + gke: + enabled: false inferencePool: targetPorts: @@ -67,3 +70,8 @@ inferencePool: provider: name: none + # GKE-specific configuration. + # This block is only used if name is "gke". + gke: + # Set to true if the cluster is an Autopilot cluster. + autopilot: false From a0a762d5351b9e83a83ff04c73fac3f79d15d0eb Mon Sep 17 00:00:00 2001 From: Murphy Chen Date: Sat, 20 Sep 2025 03:00:14 +0800 Subject: [PATCH 053/133] Rename prefix scorer HashBlockSize to BlockSize (#1613) --- pkg/epp/config/loader/configloader_test.go | 12 ++++++------ .../framework/plugins/multi/prefix/plugin.go | 14 +++++++------- .../framework/plugins/multi/prefix/plugin_test.go | 10 +++++----- site-src/guides/epp-configuration/config-text.md | 8 ++++---- site-src/guides/epp-configuration/prefix-aware.md | 2 +- test/testdata/configloader_1_test.yaml | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/epp/config/loader/configloader_test.go b/pkg/epp/config/loader/configloader_test.go index 5bf5a6608..ec1b18f4f 100644 --- a/pkg/epp/config/loader/configloader_test.go +++ b/pkg/epp/config/loader/configloader_test.go @@ -73,7 +73,7 @@ func TestLoadRawConfiguration(t *testing.T) { }, { Type: test2Type, - Parameters: json.RawMessage("{\"hashBlockSize\":32}"), + Parameters: json.RawMessage("{\"blockSize\":32}"), }, { Name: "testPicker", @@ -175,7 +175,7 @@ func TestLoadRawConfigurationWithDefaults(t *testing.T) { { Name: test2Type, Type: test2Type, - Parameters: json.RawMessage("{\"hashBlockSize\":32}"), + Parameters: json.RawMessage("{\"blockSize\":32}"), }, { Name: "testPicker", @@ -464,7 +464,7 @@ plugins: type: test-profile-handler - type: test-two parameters: - hashBlockSize: 32 + blockSize: 32 - name: testPicker type: test-picker schedulingProfiles: @@ -772,7 +772,7 @@ plugins: - name: prefixCacheScorer type: prefix-cache-scorer parameters: - hashBlockSize: 32 + blockSize: 32 - name: maxScorePicker type: max-score-picker - name: profileHandler @@ -797,7 +797,7 @@ plugins: - name: prefixCacheScorer type: prefix-cache-scorer parameters: - hashBlockSize: 32 + blockSize: 32 schedulingProfiles: - name: default plugins: @@ -831,7 +831,7 @@ plugins: - name: prefixCacheScorer type: prefix-cache-scorer parameters: - hashBlockSize: asdf + blockSize: asdf schedulingProfiles: - name: default plugins: diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go index 8e594400f..4633825d9 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin.go @@ -37,7 +37,7 @@ import ( const ( // vLLM default token block size is 16, and a good guess of average characters per token is 4. - DefaultHashBlockSize = 64 + DefaultBlockSize = 64 // The maximum number of blocks to match. Two long requests with the same prefix up to this // limit will be indistinguishable. // This parameter provides a trade-off between cache size, prefix matching speed and matching @@ -58,15 +58,15 @@ const ( ) var DefaultConfig = Config{ - HashBlockSize: DefaultHashBlockSize, + BlockSize: DefaultBlockSize, MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, LRUCapacityPerServer: DefaultLRUCapacityPerServer, } type Config struct { - // The input prompt is broken into sizes of HashBlockSize to calculate block hashes . Requests + // The input prompt is broken into sizes of BlockSize to calculate block hashes . Requests // with length shorter than the block size will be ignored. - HashBlockSize int `json:"hashBlockSize"` + BlockSize int `json:"blockSize"` // MaxPrefixBlocksToMatch is the maximum number of prefix blocks to match. Input beyond this limit will // be ignored. MaxPrefixBlocksToMatch int `json:"maxPrefixBlocksToMatch"` @@ -138,7 +138,7 @@ var ( // PrefixCachePluginFactory defines the factory function for Prefix plugin. func PrefixCachePluginFactory(name string, rawParameters json.RawMessage, handle plugins.Handle) (plugins.Plugin, error) { parameters := Config{ - HashBlockSize: DefaultHashBlockSize, + BlockSize: DefaultBlockSize, MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, LRUCapacityPerServer: DefaultLRUCapacityPerServer, } @@ -186,7 +186,7 @@ func (p *Plugin) WithName(name string) *Plugin { func (p *Plugin) Score(ctx context.Context, cycleState *types.CycleState, request *types.LLMRequest, pods []types.Pod) map[types.Pod]float64 { // pre score step, hashing prompt and find longest prefix match. - hashes := hashPrompt(ctx, request, p.config.HashBlockSize, p.config.MaxPrefixBlocksToMatch) + hashes := hashPrompt(ctx, request, p.config.BlockSize, p.config.MaxPrefixBlocksToMatch) state := &SchedulingContextState{ PrefixHashes: hashes, PrefixCacheServers: p.matchLongestPrefix(ctx, hashes), @@ -237,7 +237,7 @@ func (p *Plugin) PreRequest(ctx context.Context, request *types.LLMRequest, sche total := len(state.PrefixHashes) matchLen := state.PrefixCacheServers[ServerID(targetPod.NamespacedName)] - metrics.RecordPrefixCacheMatch(matchLen*p.config.HashBlockSize, total*p.config.HashBlockSize) + metrics.RecordPrefixCacheMatch(matchLen*p.config.BlockSize, total*p.config.BlockSize) } // matchLongestPrefix returns a map of servers and length of prefix that each server caches. diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go index df2eae67c..54a00abc1 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go @@ -35,7 +35,7 @@ import ( func TestPrefixPluginCompletion(t *testing.T) { config := Config{ - HashBlockSize: 4, + BlockSize: 4, MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, LRUCapacityPerServer: DefaultLRUCapacityPerServer, } @@ -201,7 +201,7 @@ func TestPrefixPluginCompletion(t *testing.T) { func TestPrefixPluginChatCompletions(t *testing.T) { config := Config{ - HashBlockSize: 4, + BlockSize: 4, MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, LRUCapacityPerServer: DefaultLRUCapacityPerServer, } @@ -235,7 +235,7 @@ func TestPrefixPluginChatCompletions(t *testing.T) { func TestPrefixPluginChatCompletionsGrowth(t *testing.T) { config := Config{ - HashBlockSize: 8, // Use larger block size for more predictable JSON marshaling + BlockSize: 8, // Use larger block size for more predictable JSON marshaling MaxPrefixBlocksToMatch: DefaultMaxPrefixBlocks, LRUCapacityPerServer: DefaultLRUCapacityPerServer, } @@ -349,7 +349,7 @@ func BenchmarkPrefixPluginStress(b *testing.B) { blockSize := 4 maxPrefixBlocks := 50000 config := Config{ - HashBlockSize: blockSize, + BlockSize: blockSize, MaxPrefixBlocksToMatch: maxPrefixBlocks, LRUCapacityPerServer: DefaultLRUCapacityPerServer, } @@ -418,7 +418,7 @@ func BenchmarkPrefixPluginChatCompletionsStress(b *testing.B) { blockSize := 8 maxPrefixBlocks := 50000 config := Config{ - HashBlockSize: blockSize, + BlockSize: blockSize, MaxPrefixBlocksToMatch: maxPrefixBlocks, LRUCapacityPerServer: DefaultLRUCapacityPerServer, } diff --git a/site-src/guides/epp-configuration/config-text.md b/site-src/guides/epp-configuration/config-text.md index cdb3714cb..382edba26 100644 --- a/site-src/guides/epp-configuration/config-text.md +++ b/site-src/guides/epp-configuration/config-text.md @@ -85,7 +85,7 @@ kind: EndpointPickerConfig plugins: - type: prefix-cache-scorer parameters: - hashBlockSize: 5 + blockSize: 5 maxPrefixBlocksToMatch: 256 lruCapacityPerServer: 31250 schedulingProfiles: @@ -152,7 +152,7 @@ spec: plugins: - type: prefix-cache-scorer parameters: - hashBlockSize: 5 + blockSize: 5 maxPrefixBlocksToMatch: 256 lruCapacityPerServer: 31250 schedulingProfiles: @@ -171,7 +171,7 @@ kind: EndpointPickerConfig plugins: - type: prefix-cache-scorer parameters: - hashBlockSize: 5 + blockSize: 5 maxPrefixBlocksToMatch: 256 lruCapacityPerServer: 31250 - type: single-profile-handler @@ -201,7 +201,7 @@ Scores pods based on the amount of the prompt is believed to be in the pod's KvC - *Type*: prefix-cache-scorer - *Parameters*: - - `hashBlockSize` specified the size of the blocks to break up the input prompt when + - `blockSize` specified the size of the blocks to break up the input prompt when calculating the block hashes. If not specified defaults to `64` - `maxPrefixBlocksToMatch` specifies the maximum number of prefix blocks to match. If not specified defaults to `256` diff --git a/site-src/guides/epp-configuration/prefix-aware.md b/site-src/guides/epp-configuration/prefix-aware.md index 9c1074be9..88573c466 100644 --- a/site-src/guides/epp-configuration/prefix-aware.md +++ b/site-src/guides/epp-configuration/prefix-aware.md @@ -14,7 +14,7 @@ Like any other plugins, the prefix cache aware plugin can be enabled/disabled vi The prefix cache plugin exposes the following advanced configuration parameters: -* `hashBlockSize`: The plugin matches prefixes in the unit of blocks. This is the size +* `blockSize`: The plugin matches prefixes in the unit of blocks. This is the size of each block in number of bytes. vLLM default block size is 16 tokens. Assume 4 characters per token, the default is set to 64 in EPP. The default is recommended unless performance is critical for use cases with extremely long inputs. diff --git a/test/testdata/configloader_1_test.yaml b/test/testdata/configloader_1_test.yaml index f1f167efb..db75a4265 100644 --- a/test/testdata/configloader_1_test.yaml +++ b/test/testdata/configloader_1_test.yaml @@ -9,7 +9,7 @@ plugins: type: test-profile-handler - type: test-two parameters: - hashBlockSize: 32 + blockSize: 32 - name: testPicker type: test-picker From c239d0959c48c02436b2c51079909cc873de6303 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Fri, 19 Sep 2025 15:06:19 -0700 Subject: [PATCH 054/133] Consolidate ha config into a single enableLeaderElection, also fix rolling update stuck bug (#1620) --- config/charts/inferencepool/README.md | 34 ++++++++++++------- .../templates/epp-deployment.yaml | 30 ++++++++++++---- .../charts/inferencepool/templates/gke.yaml | 4 +++ 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md index b6629d2b8..9a8be09f9 100644 --- a/config/charts/inferencepool/README.md +++ b/config/charts/inferencepool/README.md @@ -103,19 +103,30 @@ $ helm install triton-llama3-8b-instruct \ To deploy the EndpointPicker in a high-availability (HA) active-passive configuration, you can enable leader election. When enabled, the EPP deployment will have multiple replicas, but only one "leader" replica will be active and ready to process traffic at any given time. If the leader pod fails, another pod will be elected as the new leader, ensuring service continuity. -To enable HA, set `inferenceExtension.flags.has-enable-leader-election` to `true` and increase the number of replicas in your `values.yaml` file: +To enable HA, set `inferenceExtension.enableLeaderElection` to `true`. -```yaml -inferenceExtension: - replicas: 3 - has-enable-leader-election: true -``` +* Via `--set` flag: -Then apply it with: + ```txt + helm install vllm-llama3-8b-instruct \ + --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ + --set inferenceExtension.enableLeaderElection=true \ + --set provider=[none|gke] \ + oci://us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/charts/inferencepool --version v0 + ``` -```txt -helm install vllm-llama3-8b-instruct ./config/charts/inferencepool -f values.yaml -``` +* Via `values.yaml`: + + ```yaml + inferenceExtension: + enableLeaderElection: true + ``` + + Then apply it with: + + ```txt + helm install vllm-llama3-8b-instruct ./config/charts/inferencepool -f values.yaml + ``` ### Install with Monitoring @@ -171,8 +182,7 @@ The following table list the configurable parameters of the chart. | `inferenceExtension.extraServicePorts` | List of additional service ports to expose. Defaults to `[]`. | | `inferenceExtension.flags` | List of flags which are passed through to endpoint picker. Example flags, enable-pprof, grpc-port etc. Refer [runner.go](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/cmd/epp/runner/runner.go) for complete list. | | `inferenceExtension.affinity` | Affinity for the endpoint picker. Defaults to `{}`. | -| `inferenceExtension.tolerations` | Tolerations for the endpoint picker. Defaults to `[]`. | -| `inferenceExtension.flags.has-enable-leader-election` | Enable leader election for high availability. When enabled, only one EPP pod (the leader) will be ready to serve traffic. | +| `inferenceExtension.tolerations` | Tolerations for the endpoint picker. Defaults to `[]`. | | | `inferenceExtension.monitoring.interval` | Metrics scraping interval for monitoring. Defaults to `10s`. | | `inferenceExtension.monitoring.secret.name` | Name of the service account token secret for metrics authentication. Defaults to `inference-gateway-sa-metrics-reader-secret`. | | `inferenceExtension.monitoring.prometheus.enabled` | Enable Prometheus ServiceMonitor creation for EPP metrics collection. Defaults to `false`. | diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml index abdc05a2c..d37ba3e94 100644 --- a/config/charts/inferencepool/templates/epp-deployment.yaml +++ b/config/charts/inferencepool/templates/epp-deployment.yaml @@ -6,7 +6,19 @@ metadata: labels: {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} spec: - replicas: {{ .Values.inferenceExtension.replicas | default 1 }} + {{- if .Values.inferenceExtension.enableLeaderElection }} + replicas: 3 + {{- else }} + replicas: 1 + {{- end }} + strategy: + # The current recommended EPP deployment pattern is to have a single active replica. This ensures + # optimal performance of the stateful operations such prefix cache aware scorer. + # The Recreate strategy the old replica is killed immediately, and allow the new replica(s) to + # quickly take over. This is particularly important in the high availability set up with leader + # election, as the rolling update strategy would prevent the old leader being killed because + # otherwise the maxUnavailable would be 100%. + type: Recreate selector: matchLabels: {{- include "gateway-api-inference-extension.selectorLabels" . | nindent 6 }} @@ -33,10 +45,6 @@ spec: - "json" - --config-file - "/config/{{ .Values.inferenceExtension.pluginsConfigFile }}" - {{- range .Values.inferenceExtension.flags }} - - "--{{ .name }}" - - "{{ .value }}" - {{- end }} {{- if eq (.Values.inferencePool.modelServerType | default "vllm") "triton-tensorrt-llm" }} - --total-queued-requests-metric - "nv_trt_llm_request_metrics{request_type=waiting}" @@ -45,6 +53,14 @@ spec: - --lora-info-metric - "" # Set an empty metric to disable LoRA metric scraping as they are not supported by Triton yet. {{- end }} + {{- if .Values.inferenceExtension.enableLeaderElection }} + - --ha-enable-leader-election + {{- end }} + # Pass additional flags via the inferenceExtension.flags field in values.yaml. + {{- range .Values.inferenceExtension.flags }} + - "--{{ .name }}" + - "{{ .value }}" + {{- end }} ports: - name: grpc containerPort: 9002 @@ -77,8 +93,8 @@ spec: port: 9003 service: inference-extension {{- end }} - initialDelaySeconds: 5 - periodSeconds: 10 + periodSeconds: 2 + env: - name: NAMESPACE valueFrom: diff --git a/config/charts/inferencepool/templates/gke.yaml b/config/charts/inferencepool/templates/gke.yaml index 59e186a94..a0118f5f4 100644 --- a/config/charts/inferencepool/templates/gke.yaml +++ b/config/charts/inferencepool/templates/gke.yaml @@ -13,6 +13,10 @@ spec: kind: InferencePool name: {{ .Release.Name }} default: + # Set a more aggressive health check than the default 5s for faster switch + # over during EPP rollout. + timeoutSec: 2 + checkIntervalSec: 2 config: type: HTTP httpHealthCheck: From 0c630116cfe7cbc67e914ca008b18d800538229c Mon Sep 17 00:00:00 2001 From: Murphy Chen Date: Sat, 20 Sep 2025 07:08:11 +0800 Subject: [PATCH 055/133] improve the readable of test-unit make target (#1614) --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7e5c5e9a3..d9f3a6132 100644 --- a/Makefile +++ b/Makefile @@ -138,7 +138,9 @@ test: generate fmt vet envtest image-build verify-crds ## Run tests. .PHONY: test-unit test-unit: ## Run unit tests. - CGO_ENABLED=1 KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./pkg/... -race -coverprofile cover.out + CGO_ENABLED=1 KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./pkg/... -race -coverprofile cover.out; \ + go tool cover -func=cover.out; \ + rm cover.out .PHONY: test-integration test-integration: envtest ## Run integration tests. From 7eaf468f01180a076963b98951526edec09cb91c Mon Sep 17 00:00:00 2001 From: Luke Van Drie Date: Fri, 19 Sep 2025 19:32:11 -0500 Subject: [PATCH 056/133] feat: Adapt flow control to per-request saturation (#1622) This commit refactors the flow control `ShardProcessor` to align with the new `SaturationDetector` contract (introduced in 7d84fb92), which evaluates saturation for a specific set of candidate pods rather than for the entire pool. This change fundamentally alters the dispatching logic to prioritize strict fairness and priority over work conservation. The `BandFilter` abstraction has been removed, and the `ShardProcessor` now performs a post-selection viability check. After policies select the fairest request, the `SaturationDetector` is called with the candidate pods for only that specific request. If the check fails, the processor stops the entire dispatch cycle for the current tick, enforcing Head-of-Line blocking to prevent priority inversion. This new model correctly upholds a strict fairness and priority contract. However, it introduces a known trade-off where the system may leave resources idle if the fairest request is blocked, rather than finding other viable work (the "noisy neighbor" problem). --- pkg/epp/flowcontrol/contracts/mocks/mocks.go | 7 +- .../contracts/saturationdetector.go | 10 +- pkg/epp/flowcontrol/controller/controller.go | 9 +- .../flowcontrol/controller/controller_test.go | 4 +- .../flowcontrol/controller/internal/filter.go | 144 --------------- .../controller/internal/filter_test.go | 174 ------------------ .../controller/internal/processor.go | 61 +++--- .../controller/internal/processor_test.go | 122 +++--------- pkg/epp/flowcontrol/types/mocks/mocks.go | 20 +- pkg/epp/flowcontrol/types/request.go | 7 + 10 files changed, 95 insertions(+), 463 deletions(-) delete mode 100644 pkg/epp/flowcontrol/controller/internal/filter.go delete mode 100644 pkg/epp/flowcontrol/controller/internal/filter_test.go diff --git a/pkg/epp/flowcontrol/contracts/mocks/mocks.go b/pkg/epp/flowcontrol/contracts/mocks/mocks.go index 02f9863fb..10f093b11 100644 --- a/pkg/epp/flowcontrol/contracts/mocks/mocks.go +++ b/pkg/epp/flowcontrol/contracts/mocks/mocks.go @@ -34,6 +34,7 @@ import ( "fmt" "sync" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" @@ -112,12 +113,12 @@ func (m *MockRegistryShard) Stats() contracts.ShardStats { // MockSaturationDetector is a simple "stub-style" mock for testing. type MockSaturationDetector struct { - IsSaturatedFunc func(ctx context.Context) bool + IsSaturatedFunc func(ctx context.Context, candidatePods []metrics.PodMetrics) bool } -func (m *MockSaturationDetector) IsSaturated(ctx context.Context) bool { +func (m *MockSaturationDetector) IsSaturated(ctx context.Context, candidatePods []metrics.PodMetrics) bool { if m.IsSaturatedFunc != nil { - return m.IsSaturatedFunc(ctx) + return m.IsSaturatedFunc(ctx, candidatePods) } return false } diff --git a/pkg/epp/flowcontrol/contracts/saturationdetector.go b/pkg/epp/flowcontrol/contracts/saturationdetector.go index 91d2406c5..15037d50a 100644 --- a/pkg/epp/flowcontrol/contracts/saturationdetector.go +++ b/pkg/epp/flowcontrol/contracts/saturationdetector.go @@ -16,7 +16,11 @@ limitations under the License. package contracts -import "context" +import ( + "context" + + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" +) // SaturationDetector defines the contract for a component that provides real-time load signals to the // `controller.FlowController`. @@ -32,8 +36,8 @@ import "context" // // Implementations MUST be goroutine-safe. type SaturationDetector interface { - // IsSaturated returns true if the system's backend resources are considered saturated. + // IsSaturated returns true if the system's backend resources are considered saturated for a set of candidate pods. // `controller.FlowController`'s dispatch workers call this method to decide whether to pause or throttle dispatch // operations to prevent overwhelming the backends. - IsSaturated(ctx context.Context) bool + IsSaturated(ctx context.Context, candidatePods []metrics.PodMetrics) bool } diff --git a/pkg/epp/flowcontrol/controller/controller.go b/pkg/epp/flowcontrol/controller/controller.go index 8449159b1..aeb0bdd87 100644 --- a/pkg/epp/flowcontrol/controller/controller.go +++ b/pkg/epp/flowcontrol/controller/controller.go @@ -58,7 +58,7 @@ type shardProcessor interface { // This enables dependency injection for testing. type shardProcessorFactory func( shard contracts.RegistryShard, - dispatchFilter internal.BandFilter, + saturationDetector contracts.SaturationDetector, clock clock.Clock, expiryCleanupInterval time.Duration, enqueueChannelBufferSize int, @@ -130,7 +130,7 @@ func NewFlowController( // Use the real shard processor implementation by default. fc.shardProcessorFactory = func( shard contracts.RegistryShard, - dispatchFilter internal.BandFilter, + saturationDetector contracts.SaturationDetector, clock clock.Clock, expiryCleanupInterval time.Duration, enqueueChannelBufferSize int, @@ -138,7 +138,7 @@ func NewFlowController( ) shardProcessor { return internal.NewShardProcessor( shard, - dispatchFilter, + saturationDetector, clock, expiryCleanupInterval, enqueueChannelBufferSize, @@ -310,10 +310,9 @@ func (fc *FlowController) getOrStartWorker(shard contracts.RegistryShard) *manag // Construct a new worker, but do not start its processor goroutine yet. processorCtx, cancel := context.WithCancel(fc.parentCtx) - dispatchFilter := internal.NewSaturationFilter(fc.saturationDetector) processor := fc.shardProcessorFactory( shard, - dispatchFilter, + fc.saturationDetector, fc.clock, fc.config.ExpiryCleanupInterval, fc.config.EnqueueChannelBufferSize, diff --git a/pkg/epp/flowcontrol/controller/controller_test.go b/pkg/epp/flowcontrol/controller/controller_test.go index 435cdb510..c832e2d6d 100644 --- a/pkg/epp/flowcontrol/controller/controller_test.go +++ b/pkg/epp/flowcontrol/controller/controller_test.go @@ -209,7 +209,7 @@ type mockShardProcessorFactory struct { func (f *mockShardProcessorFactory) new( shard contracts.RegistryShard, - _ internal.BandFilter, + _ contracts.SaturationDetector, _ clock.Clock, _ time.Duration, _ int, @@ -640,7 +640,7 @@ func TestFlowController_Lifecycle(t *testing.T) { h := newUnitHarness(t, t.Context(), Config{}, &mockRegistryClient{}) h.fc.shardProcessorFactory = func( shard contracts.RegistryShard, - _ internal.BandFilter, + _ contracts.SaturationDetector, _ clock.Clock, _ time.Duration, _ int, diff --git a/pkg/epp/flowcontrol/controller/internal/filter.go b/pkg/epp/flowcontrol/controller/internal/filter.go deleted file mode 100644 index 12788d4cb..000000000 --- a/pkg/epp/flowcontrol/controller/internal/filter.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package internal - -import ( - "context" - - "github.com/go-logr/logr" - - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework" - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" - logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging" -) - -// BandFilter is a function that acts as a pre-policy gate. It takes a complete view of a priority band and returns a -// potentially filtered `framework.PriorityBandAccessor` containing only the flows that are viable candidates for a -// subsequent policy decision. It can also return a boolean signal to pause the entire operation for the band. -// -// This abstraction decouples the logic of determining *viability* (e.g., is a flow subject to backpressure?) from the -// logic of *selection* (e.g., which of the viable flows is the fairest to pick next?). This separation simplifies the -// mental model for policy authors, who can focus solely on selection logic without needing to account for external -// gating signals. -// -// Because filters are applied before the band is passed to a policy, they are inherently composable. Multiple filters -// can be chained to apply different viability criteria. For example, a future filter could be developed to temporarily -// exclude a "misbehaving" flow that is causing repeated errors, quarantining it from policy consideration. -// -// A nil returned `PriorityBandAccessor` indicates that no filtering was necessary and the original accessor should be -// used. This provides a zero-allocation fast path for the common case where no flows are being filtered. -type BandFilter func( - ctx context.Context, - band framework.PriorityBandAccessor, - logger logr.Logger, -) (filteredBand framework.PriorityBandAccessor, shouldPause bool) - -// NewSaturationFilter creates a `BandFilter` that uses the provided `contracts.SaturationDetector` to determine which -// flows are dispatchable. This is the standard filter used in the production `FlowController` for the dispatch -// operation. -func NewSaturationFilter(sd contracts.SaturationDetector) BandFilter { - return func( - ctx context.Context, - band framework.PriorityBandAccessor, - logger logr.Logger, - ) (framework.PriorityBandAccessor, bool) { - // Phase 1: Implement the current global saturation check. - if sd.IsSaturated(ctx) { - logger.V(logutil.VERBOSE).Info("System saturated, pausing dispatch for this shard.") - return nil, true // Pause dispatching for all bands. - } - - // Phase 2 (Future): This is where per-flow saturation logic would go. - // It would iterate `band`, call `IsSaturated(ctx, flowID)`, and build a filtered map of allowed flows, - // then return `newSubsetPriorityBandAccessor(band, allowedFlows)`. - // For now, no per-flow filtering is done. Return nil to signal the fast path. - return nil, false // Do not pause, and do not filter any flows. - } -} - -// subsetPriorityBandAccessor provides a view of a priority band that is restricted to a specific subset of flows. -// It implements the `framework.PriorityBandAccessor` interface, ensuring that any policy operating on it will only -// see the allowed flows, regardless of which accessor method is used. This provides correctness by construction. -// -// For performance, it pre-computes a slice of the allowed flows at creation time, making subsequent calls to -// `FlowKeys()` an O(1) operation with zero allocations. -type subsetPriorityBandAccessor struct { - originalAccessor framework.PriorityBandAccessor - allowedFlows map[types.FlowKey]struct{} - allowedFlowsSlice []types.FlowKey -} - -var _ framework.PriorityBandAccessor = &subsetPriorityBandAccessor{} - -// newSubsetPriorityBandAccessor creates a new filtered view of a priority band. -func newSubsetPriorityBandAccessor(original framework.PriorityBandAccessor, allowed []types.FlowKey) *subsetPriorityBandAccessor { - // Pre-compute the map for efficient lookups in `Queue()` and `IterateQueues()`. - allowedMap := make(map[types.FlowKey]struct{}, len(allowed)) - for _, k := range allowed { - allowedMap[k] = struct{}{} - } - - return &subsetPriorityBandAccessor{ - originalAccessor: original, - allowedFlows: allowedMap, - allowedFlowsSlice: allowed, - } -} - -// Priority returns the numerical priority level of this band. -func (s *subsetPriorityBandAccessor) Priority() int { - return s.originalAccessor.Priority() -} - -// PriorityName returns the human-readable name of this priority band. -func (s *subsetPriorityBandAccessor) PriorityName() string { - return s.originalAccessor.PriorityName() -} - -// FlowKeys returns a slice of the composite `types.FlowKey`s for every flow instance currently active within this -// priority band that are in the allowed subset. -// This is an O(1) operation because the slice is pre-computed at creation. -func (s *subsetPriorityBandAccessor) FlowKeys() []types.FlowKey { - return s.allowedFlowsSlice -} - -// Queue returns a `framework.FlowQueueAccessor` for the specified `ID` within this priority band, but only if it is -// in the allowed subset. This is an O(1) map lookup. If the flow is not in the allowed subset, it returns nil. -func (s *subsetPriorityBandAccessor) Queue(id string) framework.FlowQueueAccessor { - key := types.FlowKey{ID: id, Priority: s.Priority()} - if _, ok := s.allowedFlows[key]; !ok { - return nil - } - return s.originalAccessor.Queue(id) -} - -// IterateQueues executes the given `callback` for each `framework.FlowQueueAccessor` in the allowed subset of this -// priority band. The iteration stops if the callback returns false. -// This implementation delegates to the original accessor's iterator and applies the filter, which is more robust and -// efficient than iterating over a pre-computed slice of IDs. -func (s *subsetPriorityBandAccessor) IterateQueues(callback func(queue framework.FlowQueueAccessor) bool) { - s.originalAccessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool { - if _, ok := s.allowedFlows[queue.FlowKey()]; ok { - // This queue is in the allowed set, so execute the callback. - if !callback(queue) { - return false // The callback requested to stop, so we stop the outer iteration too. - } - } - return true // Continue iterating over the original set. - }) -} diff --git a/pkg/epp/flowcontrol/controller/internal/filter_test.go b/pkg/epp/flowcontrol/controller/internal/filter_test.go deleted file mode 100644 index 7e51453c4..000000000 --- a/pkg/epp/flowcontrol/controller/internal/filter_test.go +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright 2025 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package internal - -import ( - "context" - "sort" - "testing" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts/mocks" - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework" - frameworkmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/mocks" - "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" -) - -func TestNewSaturationFilter(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - isSaturated bool - expectShouldPause bool - expectFilteredBandNil bool - }{ - { - name: "should not pause or filter when system is not saturated", - isSaturated: false, - expectShouldPause: false, - expectFilteredBandNil: true, // nil band signals the fast path - }, - { - name: "should pause when system is saturated", - isSaturated: true, - expectShouldPause: true, - expectFilteredBandNil: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - // --- ARRANGE --- - mockSD := &mocks.MockSaturationDetector{IsSaturatedFunc: func(ctx context.Context) bool { return tc.isSaturated }} - filter := NewSaturationFilter(mockSD) - require.NotNil(t, filter, "NewSaturationFilter should not return nil") - - mockBand := &frameworkmocks.MockPriorityBandAccessor{} - - // --- ACT --- - filteredBand, shouldPause := filter(context.Background(), mockBand, logr.Discard()) - - // --- ASSERT --- - assert.Equal(t, tc.expectShouldPause, shouldPause, "The filter's pause signal should match the expected value") - - if tc.expectFilteredBandNil { - assert.Nil(t, filteredBand, "Expected filtered band to be nil") - } else { - assert.NotNil(t, filteredBand, "Expected a non-nil filtered band") - } - }) - } -} - -func TestSubsetPriorityBandAccessor(t *testing.T) { - t.Parallel() - - // --- ARRANGE --- - // Setup a mock original accessor that knows about three flows. - flowAKey := types.FlowKey{ID: "flow-a", Priority: 10} - flowBKey := types.FlowKey{ID: "flow-b", Priority: 10} - flowCKey := types.FlowKey{ID: "flow-c", Priority: 10} - - mockQueueA := &frameworkmocks.MockFlowQueueAccessor{FlowKeyV: flowAKey} - mockQueueB := &frameworkmocks.MockFlowQueueAccessor{FlowKeyV: flowBKey} - mockQueueC := &frameworkmocks.MockFlowQueueAccessor{FlowKeyV: flowCKey} - - originalAccessor := &frameworkmocks.MockPriorityBandAccessor{ - PriorityV: 10, - PriorityNameV: "High", - FlowKeysFunc: func() []types.FlowKey { - return []types.FlowKey{flowAKey, flowBKey, flowCKey} - }, - QueueFunc: func(id string) framework.FlowQueueAccessor { - switch id { - case "flow-a": - return mockQueueA - case "flow-b": - return mockQueueB - case "flow-c": - return mockQueueC - } - return nil - }, - IterateQueuesFunc: func(callback func(queue framework.FlowQueueAccessor) bool) { - if !callback(mockQueueA) { - return - } - if !callback(mockQueueB) { - return - } - callback(mockQueueC) - }, - } - - // Create a subset view that only allows two of the flows. - allowedFlows := []types.FlowKey{flowAKey, flowCKey} - subsetAccessor := newSubsetPriorityBandAccessor(originalAccessor, allowedFlows) - require.NotNil(t, subsetAccessor, "newSubsetPriorityBandAccessor should not return nil") - - t.Run("should pass through priority and name", func(t *testing.T) { - t.Parallel() - assert.Equal(t, 10, subsetAccessor.Priority(), "Priority() should pass through from the original accessor") - assert.Equal(t, "High", subsetAccessor.PriorityName(), - "PriorityName() should pass through from the original accessor") - }) - - t.Run("should only return allowed flow keys", func(t *testing.T) { - t.Parallel() - flowKeys := subsetAccessor.FlowKeys() - // Sort for consistent comparison, as the pre-computed slice order is not guaranteed. - sort.Slice(flowKeys, func(i, j int) bool { - return flowKeys[i].ID < flowKeys[j].ID - }) - assert.Equal(t, []types.FlowKey{flowAKey, flowCKey}, flowKeys, "FlowKeys() should only return the allowed subset") - }) - - t.Run("should only return queues for allowed flows", func(t *testing.T) { - t.Parallel() - assert.Same(t, mockQueueA, subsetAccessor.Queue("flow-a"), "Should return queue for allowed flow 'a'") - assert.Nil(t, subsetAccessor.Queue("flow-b"), "Should not return queue for disallowed flow 'b'") - assert.Same(t, mockQueueC, subsetAccessor.Queue("flow-c"), "Should return queue for allowed flow 'c'") - }) - - t.Run("should only iterate over allowed queues", func(t *testing.T) { - t.Parallel() - var iterated []string - subsetAccessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool { - iterated = append(iterated, queue.FlowKey().ID) - return true - }) - // Sort for consistent comparison, as iteration order is not guaranteed. - sort.Strings(iterated) - assert.Equal(t, []string{"flow-a", "flow-c"}, iterated, "IterateQueues() should only visit allowed flows") - }) - - t.Run("should stop iteration if callback returns false", func(t *testing.T) { - t.Parallel() - var iterated []string - subsetAccessor.IterateQueues(func(queue framework.FlowQueueAccessor) bool { - iterated = append(iterated, queue.FlowKey().ID) - return false // Exit after the first item. - }) - assert.Len(t, iterated, 1, "Iteration should have stopped after one item") - }) -} diff --git a/pkg/epp/flowcontrol/controller/internal/processor.go b/pkg/epp/flowcontrol/controller/internal/processor.go index fa0118270..9b6bb705f 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor.go +++ b/pkg/epp/flowcontrol/controller/internal/processor.go @@ -65,7 +65,7 @@ var ErrProcessorBusy = errors.New("shard processor is busy") // succeeds. type ShardProcessor struct { shard contracts.RegistryShard - dispatchFilter BandFilter + saturationDetector contracts.SaturationDetector clock clock.Clock expiryCleanupInterval time.Duration logger logr.Logger @@ -81,7 +81,7 @@ type ShardProcessor struct { // NewShardProcessor creates a new `ShardProcessor` instance. func NewShardProcessor( shard contracts.RegistryShard, - dispatchFilter BandFilter, + saturationDetector contracts.SaturationDetector, clock clock.Clock, expiryCleanupInterval time.Duration, enqueueChannelBufferSize int, @@ -89,7 +89,7 @@ func NewShardProcessor( ) *ShardProcessor { return &ShardProcessor{ shard: shard, - dispatchFilter: dispatchFilter, + saturationDetector: saturationDetector, clock: clock, expiryCleanupInterval: expiryCleanupInterval, logger: logger, @@ -290,25 +290,31 @@ func (sp *ShardProcessor) hasCapacity(priority int, itemByteSize uint64) bool { // dispatchCycle attempts to dispatch a single item by iterating through all priority bands from highest to lowest. // It applies the configured policies for each band to select an item and then attempts to dispatch it. -// It returns true if an item was successfully dispatched, and false otherwise, indicating that no work was done in this -// cycle. +// It returns true if an item was successfully dispatched, and false otherwise. // // # Error Handling Philosophy: Failure Isolation & Work Conservation // -// The engine is designed to be resilient to failures in individual policies or transient errors within a specific flow. -// The core principle is failure isolation. A problem in one priority band must not be allowed to halt processing for -// other, healthy bands. +// A problem in one priority band (e.g., a failing policy) must not halt processing for other, healthy bands. +// Therefore, any error during selection or dispatch for a given band is logged, and the processor immediately continues +// to the next-lower priority band to maximize system throughput. // -// To achieve this, any error encountered during the selection or dispatch process for a given priority band is treated -// as a non-critical failure for that band, in this cycle. The processor will log the error for observability and then -// immediately continue its attempt to find work in the next-lower priority band. This promotes work conservation and -// maximizes system throughput even in the presence of partial failures. +// # Strict Policy Adherence vs. Work Conservation +// +// This function's logic strictly adheres to the scheduling decisions made by the configured policies, even at the cost +// of work conservation. After the inter-flow (fairness) and intra-flow (ordering) policies select a request (e.g., +// `A_1` from flow `A`), a post-selection viability check is performed. +// +// If request `A_1` targets saturated backends, this function will stop the entire dispatch cycle for the current tick. +// It will NOT attempt to find other work (like request `B_1` or `A_2`). Instead, it respects the policy decision that +// `A_1` is next and enforces Head-of-Line blocking on it. +// +// # Future Extension Point +// +// The iteration over priority bands is currently a simple, strict-priority loop. This could be abstracted into a third +// policy tier (e.g., an `InterBandDispatchPolicy`) if more complex scheduling between bands, such as Weighted Fair +// Queuing (WFQ), is ever required. func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool { baseLogger := sp.logger.WithName("dispatchCycle") - - // FUTURE EXTENSION POINT: The iteration over priority bands is currently a simple, strict-priority loop. - // This could be abstracted into a third policy tier (e.g., an `InterBandDispatchPolicy`) if more complex scheduling - // between bands, such as Weighted Fair Queuing (WFQ), is ever required. For now, strict priority is sufficient. for _, priority := range sp.shard.AllOrderedPriorityLevels() { originalBand, err := sp.shard.PriorityBandAccessor(priority) if err != nil { @@ -317,27 +323,16 @@ func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool { } logger := baseLogger.WithValues("priority", priority, "priorityName", originalBand.PriorityName()) - // Apply the configured filter to get a view of only the dispatchable flows. - dispatchableBand, shouldPause := sp.dispatchFilter(ctx, originalBand, logger) - if shouldPause { - return false // A global gate told us to stop the entire cycle. - } - if dispatchableBand == nil { - // A nil return from the filter indicates the fast path: no filtering was needed. - dispatchableBand = originalBand - } - - // Pass the (potentially filtered) band to the policies. - item, err := sp.selectItem(dispatchableBand, logger) + item, err := sp.selectItem(originalBand, logger) if err != nil { logger.Error(err, "Failed to select item, skipping priority band for this cycle") continue } if item == nil { - // This is the common case where a priority band has no items to dispatch. logger.V(logutil.TRACE).Info("No item selected by dispatch policies, skipping band") continue } + logger = logger.WithValues( "flowKey", item.OriginalRequest().FlowKey(), "flowID", item.OriginalRequest().FlowKey().ID, @@ -345,14 +340,18 @@ func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool { "reqID", item.OriginalRequest().ID(), "reqByteSize", item.OriginalRequest().ByteSize()) + candidatePods := item.OriginalRequest().CandidatePodsForScheduling() + if sp.saturationDetector.IsSaturated(ctx, candidatePods) { + logger.V(logutil.VERBOSE).Info("Policy's chosen item is for a saturated flow; pausing dispatch and blocking on HoL") + return false + } + if err := sp.dispatchItem(item, logger); err != nil { logger.Error(err, "Failed to dispatch item, skipping priority band for this cycle") continue } - // A successful dispatch occurred, so we return true to signal that work was done. return true } - // No items were dispatched in this cycle across all priority bands. return false } diff --git a/pkg/epp/flowcontrol/controller/internal/processor_test.go b/pkg/epp/flowcontrol/controller/internal/processor_test.go index 461cb3440..4c31d7ae4 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor_test.go +++ b/pkg/epp/flowcontrol/controller/internal/processor_test.go @@ -14,29 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// -// A Note on the Testing Strategy for `ShardProcessor` -// -// The `ShardProcessor` is a complex concurrent orchestrator. Testing it with concrete implementations would lead to -// flaky, non-deterministic tests. Therefore, we use a high-fidelity `testHarness` with stateful mocks to enable -// reliable and deterministic testing. This is a deliberate and necessary choice for several key reasons: -// -// 1. Deterministic Race Simulation: The harness allows us to pause mock execution at critical moments, making it -// possible to deterministically simulate and verify the processor's behavior during race conditions (e.g., the -// dispatch vs. expiry race). This is impossible with concrete implementations without resorting to unreliable -// sleeps. -// -// 2. Failure Mode Simulation: We can trigger specific, on-demand errors from dependencies to verify the processor's -// resilience and complex error-handling logic (e.g., the `errIntraFlow` circuit breaker). -// -// 3. Interaction and Isolation Testing: Mocks allow us to isolate the `ShardProcessor` from its dependencies. This -// ensures that tests are verifying the processor's orchestration logic (i.e., that it calls its dependencies -// correctly) and are not affected by confounding bugs in those dependencies. -// -// In summary, this is a prerequisite for reliably testing a concurrent engine, not just a simple data -// structure. -// - package internal import ( @@ -57,6 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts/mocks" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework" @@ -93,9 +71,10 @@ type testHarness struct { startSignal chan struct{} // Core components under test - processor *ShardProcessor - clock *testclock.FakeClock - logger logr.Logger + processor *ShardProcessor + clock *testclock.FakeClock + logger logr.Logger + saturationDetector *mocks.MockSaturationDetector // --- Centralized Mock State --- // The harness's mutex protects the single source of truth for all mock state. @@ -112,13 +91,14 @@ type testHarness struct { func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarness { t.Helper() h := &testHarness{ - t: t, - MockRegistryShard: &mocks.MockRegistryShard{}, - clock: testclock.NewFakeClock(time.Now()), - logger: logr.Discard(), - startSignal: make(chan struct{}), - queues: make(map[types.FlowKey]*mocks.MockManagedQueue), - priorityFlows: make(map[int][]types.FlowKey), + t: t, + MockRegistryShard: &mocks.MockRegistryShard{}, + clock: testclock.NewFakeClock(time.Now()), + logger: logr.Discard(), + saturationDetector: &mocks.MockSaturationDetector{}, + startSignal: make(chan struct{}), + queues: make(map[types.FlowKey]*mocks.MockManagedQueue), + priorityFlows: make(map[int][]types.FlowKey), } // Wire up the harness to provide the mock implementations for the shard's dependencies. @@ -138,15 +118,7 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn } } - // Use a default pass-through filter. - filter := func( - ctx context.Context, - band framework.PriorityBandAccessor, - logger logr.Logger, - ) (framework.PriorityBandAccessor, bool) { - return nil, false - } - h.processor = NewShardProcessor(h, filter, h.clock, expiryCleanupInterval, 100, h.logger) + h.processor = NewShardProcessor(h, h.saturationDetector, h.clock, expiryCleanupInterval, 100, h.logger) require.NotNil(t, h.processor, "NewShardProcessor should not return nil") t.Cleanup(func() { h.Stop() }) @@ -779,17 +751,19 @@ func TestShardProcessor(t *testing.T) { expectDidDispatch: false, }, { - name: "should stop dispatching when filter signals pause", + name: "should block dispatch on HoL saturation", setupHarness: func(h *testHarness) { - // Add an item that *could* be dispatched to prove the pause is effective. - q := h.addQueue(testFlow) - require.NoError(t, q.Add(h.newTestItem("item", testFlow, testTTL))) - h.processor.dispatchFilter = func( - _ context.Context, - _ framework.PriorityBandAccessor, - _ logr.Logger, - ) (framework.PriorityBandAccessor, bool) { - return nil, true // Signal pause. + // Add a high-priority item that will be selected but is saturated. + qHigh := h.addQueue(testFlow) // priority 10 + require.NoError(t, qHigh.Add(h.newTestItem("item-high", testFlow, testTTL))) + + // Add a low-priority, viable item. + keyLow := types.FlowKey{ID: "flow-low", Priority: 5} + qLow := h.addQueue(keyLow) + require.NoError(t, qLow.Add(h.newTestItem("item-low", keyLow, testTTL))) + + h.saturationDetector.IsSaturatedFunc = func(_ context.Context, _ []metrics.PodMetrics) bool { + return true } }, expectDidDispatch: false, @@ -898,50 +872,6 @@ func TestShardProcessor(t *testing.T) { } }) - t.Run("should use filtered view of queues when filter is active", func(t *testing.T) { - t.Parallel() - // --- ARRANGE --- - h := newTestHarness(t, testCleanupTick) - flowB := types.FlowKey{ID: "flow-b", Priority: testFlow.Priority} - h.addQueue(testFlow) - qB := h.addQueue(flowB) - itemB := h.newTestItem("item-b", flowB, testTTL) - require.NoError(t, qB.Add(itemB)) - - // This filter only allows flow-b. - h.processor.dispatchFilter = func( - _ context.Context, - originalBand framework.PriorityBandAccessor, - _ logr.Logger, - ) (framework.PriorityBandAccessor, bool) { - return newSubsetPriorityBandAccessor(originalBand, []types.FlowKey{flowB}), false - } - - // This policy will be given the filtered view, so it should only see flow-b. - h.interFlowPolicySelectQueue = func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error) { - var flowIDs []string - band.IterateQueues(func(fqa framework.FlowQueueAccessor) bool { - flowIDs = append(flowIDs, fqa.FlowKey().ID) - return true - }) - // This is the core assertion of the test. - require.ElementsMatch(t, []string{flowB.ID}, flowIDs, "Policy should only see the filtered flow") - - // Select flow-b to prove the chain works. - q, _ := h.managedQueue(flowB) - return q.FlowQueueAccessor(), nil - } - - // --- ACT --- - dispatched := h.processor.dispatchCycle(context.Background()) - - // --- ASSERT --- - assert.True(t, dispatched, "An item should have been dispatched from the filtered flow") - assert.Equal(t, types.QueueOutcomeDispatched, itemB.finalState.Outcome, - "The dispatched item's outcome should be correct") - assert.NoError(t, itemB.finalState.Err, "The dispatched item should not have an error") - }) - t.Run("should guarantee strict priority by starving lower priority items", func(t *testing.T) { t.Parallel() // --- ARRANGE --- diff --git a/pkg/epp/flowcontrol/types/mocks/mocks.go b/pkg/epp/flowcontrol/types/mocks/mocks.go index dbef031d7..c52c5c2db 100644 --- a/pkg/epp/flowcontrol/types/mocks/mocks.go +++ b/pkg/epp/flowcontrol/types/mocks/mocks.go @@ -22,16 +22,18 @@ import ( "context" "time" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" ) // MockFlowControlRequest provides a mock implementation of the `types.FlowControlRequest` interface. type MockFlowControlRequest struct { - Ctx context.Context - FlowKeyV types.FlowKey - ByteSizeV uint64 - InitialEffectiveTTLV time.Duration - IDV string + Ctx context.Context + FlowKeyV types.FlowKey + ByteSizeV uint64 + InitialEffectiveTTLV time.Duration + IDV string + CandidatePodsForSchedulingV []*metrics.FakePodMetrics } // NewMockFlowControlRequest creates a new `MockFlowControlRequest` instance. @@ -58,6 +60,14 @@ func (m *MockFlowControlRequest) ByteSize() uint64 { return m. func (m *MockFlowControlRequest) InitialEffectiveTTL() time.Duration { return m.InitialEffectiveTTLV } func (m *MockFlowControlRequest) ID() string { return m.IDV } +func (m *MockFlowControlRequest) CandidatePodsForScheduling() []metrics.PodMetrics { + pods := make([]metrics.PodMetrics, 0, len(m.CandidatePodsForSchedulingV)) + for i, pod := range m.CandidatePodsForSchedulingV { + pods[i] = pod + } + return pods +} + var _ types.FlowControlRequest = &MockFlowControlRequest{} // MockQueueItemHandle provides a mock implementation of the `types.QueueItemHandle` interface. diff --git a/pkg/epp/flowcontrol/types/request.go b/pkg/epp/flowcontrol/types/request.go index 5dfc18eb6..255d3bc45 100644 --- a/pkg/epp/flowcontrol/types/request.go +++ b/pkg/epp/flowcontrol/types/request.go @@ -19,6 +19,8 @@ package types import ( "context" "time" + + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics" ) // FlowControlRequest is the contract for an incoming request submitted to the `controller.FlowController`. It @@ -49,6 +51,11 @@ type FlowControlRequest interface { // applied. InitialEffectiveTTL() time.Duration + // CandidatePodsForScheduling passes through a set of candidate pods a request may be admitted to. + // This is necessary for invoking `contracts.SaturationDetector.IsSaturated`, but it is otherwise unused in the Flow + // Control system. + CandidatePodsForScheduling() []metrics.PodMetrics + // ID returns an optional, user-facing unique identifier for this specific request. It is intended for logging, // tracing, and observability. The `controller.FlowController` does not use this ID for dispatching decisions; it uses // the internal, opaque `QueueItemHandle`. From 55bfafc187c008126970322196584ae09135b2d6 Mon Sep 17 00:00:00 2001 From: Bob Tian Date: Fri, 19 Sep 2025 19:04:15 -0700 Subject: [PATCH 057/133] fix missing GCPBakendPolicy. (#1623) --- config/charts/inferencepool/templates/gke.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/charts/inferencepool/templates/gke.yaml b/config/charts/inferencepool/templates/gke.yaml index a0118f5f4..77855c35a 100644 --- a/config/charts/inferencepool/templates/gke.yaml +++ b/config/charts/inferencepool/templates/gke.yaml @@ -39,6 +39,7 @@ spec: timeoutSec: 300 # 5-minute timeout (adjust as needed) logging: enabled: true # log all requests by default +--- {{- if .Values.inferenceExtension.monitoring.gke.enabled }} {{- $metricsReadSA := printf "%s-metrics-reader-sa" .Release.Name -}} {{- $metricsReadSecretName := printf "%s-metrics-reader-secret" .Release.Name -}} @@ -54,7 +55,6 @@ spec: {{- $gmpNamespace = "gke-gmp-system" -}} {{- end -}} {{- $gmpCollectorRoleBindingName := printf "%s:collector:%s-%s-metrics-reader-secret-read" $gmpNamespace .Release.Namespace .Release.Name -}} ---- apiVersion: v1 kind: ServiceAccount metadata: From 370126cf2db9b2dd94ec09d56fb1d10e695b1ec5 Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Mon, 22 Sep 2025 22:42:14 +0300 Subject: [PATCH 058/133] use replicas field in helm to decide if EPP should run in HA mode (#1628) Signed-off-by: Nir Rozenbaum --- config/charts/inferencepool/README.md | 10 +++++----- .../inferencepool/templates/epp-deployment.yaml | 12 ++++-------- .../templates/leader-election-rbac.yaml | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md index 9a8be09f9..c768f6130 100644 --- a/config/charts/inferencepool/README.md +++ b/config/charts/inferencepool/README.md @@ -101,16 +101,16 @@ $ helm install triton-llama3-8b-instruct \ ### Install with High Availability (HA) -To deploy the EndpointPicker in a high-availability (HA) active-passive configuration, you can enable leader election. When enabled, the EPP deployment will have multiple replicas, but only one "leader" replica will be active and ready to process traffic at any given time. If the leader pod fails, another pod will be elected as the new leader, ensuring service continuity. +To deploy the EndpointPicker in a high-availability (HA) active-passive configuration set replicas to be greater than one. In such a setup, only one "leader" replica will be active and ready to process traffic at any given time. If the leader pod fails, another pod will be elected as the new leader, ensuring service continuity. -To enable HA, set `inferenceExtension.enableLeaderElection` to `true`. +To enable HA, set `inferenceExtension.replicas` to a number greater than 1. * Via `--set` flag: ```txt helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ - --set inferenceExtension.enableLeaderElection=true \ + --set inferenceExtension.replicas=3 \ --set provider=[none|gke] \ oci://us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/charts/inferencepool --version v0 ``` @@ -119,7 +119,7 @@ To enable HA, set `inferenceExtension.enableLeaderElection` to `true`. ```yaml inferenceExtension: - enableLeaderElection: true + replicas: 3 ``` Then apply it with: @@ -172,7 +172,7 @@ The following table list the configurable parameters of the chart. | `inferencePool.targetPortNumber` | Target port number for the vllm backends, will be used to scrape metrics by the inference extension. Defaults to 8000. | | `inferencePool.modelServerType` | Type of the model servers in the pool, valid options are [vllm, triton-tensorrt-llm], default is vllm. | | `inferencePool.modelServers.matchLabels` | Label selector to match vllm backends managed by the inference pool. | -| `inferenceExtension.replicas` | Number of replicas for the endpoint picker extension service. Defaults to `1`. | +| `inferenceExtension.replicas` | Number of replicas for the endpoint picker extension service. If More than one replica is used, EPP will run in HA active-passive mode. Defaults to `1`. | | `inferenceExtension.image.name` | Name of the container image used for the endpoint picker. | | `inferenceExtension.image.hub` | Registry URL where the endpoint picker image is hosted. | | `inferenceExtension.image.tag` | Image tag of the endpoint picker. | diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml index d37ba3e94..6ed60b20d 100644 --- a/config/charts/inferencepool/templates/epp-deployment.yaml +++ b/config/charts/inferencepool/templates/epp-deployment.yaml @@ -6,11 +6,7 @@ metadata: labels: {{- include "gateway-api-inference-extension.labels" . | nindent 4 }} spec: - {{- if .Values.inferenceExtension.enableLeaderElection }} - replicas: 3 - {{- else }} - replicas: 1 - {{- end }} + replicas: {{ .Values.inferenceExtension.replicas | default 1 }} strategy: # The current recommended EPP deployment pattern is to have a single active replica. This ensures # optimal performance of the stateful operations such prefix cache aware scorer. @@ -53,7 +49,7 @@ spec: - --lora-info-metric - "" # Set an empty metric to disable LoRA metric scraping as they are not supported by Triton yet. {{- end }} - {{- if .Values.inferenceExtension.enableLeaderElection }} + {{- if gt .Values.inferenceExtension.replicas 1 }} - --ha-enable-leader-election {{- end }} # Pass additional flags via the inferenceExtension.flags field in values.yaml. @@ -72,7 +68,7 @@ spec: {{- toYaml .Values.inferenceExtension.extraContainerPorts | nindent 8 }} {{- end }} livenessProbe: - {{- if .Values.inferenceExtension.enableLeaderElection }} + {{- if gt .Values.inferenceExtension.replicas 1 }} grpc: port: 9003 service: liveness @@ -84,7 +80,7 @@ spec: initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: - {{- if .Values.inferenceExtension.enableLeaderElection }} + {{- if gt .Values.inferenceExtension.replicas 1 }} grpc: port: 9003 service: readiness diff --git a/config/charts/inferencepool/templates/leader-election-rbac.yaml b/config/charts/inferencepool/templates/leader-election-rbac.yaml index 923bdd6f4..8816dac7d 100644 --- a/config/charts/inferencepool/templates/leader-election-rbac.yaml +++ b/config/charts/inferencepool/templates/leader-election-rbac.yaml @@ -1,4 +1,4 @@ -{{- if .Values.inferenceExtension.enableLeaderElection }} +{{- if gt .Values.inferenceExtension.replicas 1 }} --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 From 564754cc5bb00796bc002e5b841ceccb6869f362 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Mon, 22 Sep 2025 14:44:13 -0700 Subject: [PATCH 059/133] Explicitly set pool-namespace flag for wider compatibility (#1633) --- config/charts/inferencepool/templates/epp-deployment.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml index 6ed60b20d..39f74f194 100644 --- a/config/charts/inferencepool/templates/epp-deployment.yaml +++ b/config/charts/inferencepool/templates/epp-deployment.yaml @@ -33,6 +33,11 @@ spec: args: - --pool-name - {{ .Release.Name }} + # The pool namespace is optional because EPP can default to the NAMESPACE env var. + # We still keep this here so that the template works with older versions of EPP, or other + # distros of EPP which may not have implemented the NAMESPACE env var defaulting behavior. + - --pool-namespace + - {{ .Release.Namespace }} {{- if ne .Values.inferencePool.apiVersion "inference.networking.k8s.io" }} - --pool-group - "{{ (split "/" .Values.inferencePool.apiVersion)._0 }}" From 30eabad03b7883c4348c95e8758d4d6f234054fd Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Mon, 22 Sep 2025 15:04:13 -0700 Subject: [PATCH 060/133] Bug fix - error calling gt: incompatible types for comparison: float64 and int (#1630) --- config/charts/inferencepool/templates/epp-deployment.yaml | 6 +++--- .../inferencepool/templates/leader-election-rbac.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/charts/inferencepool/templates/epp-deployment.yaml b/config/charts/inferencepool/templates/epp-deployment.yaml index 39f74f194..f012c2e47 100644 --- a/config/charts/inferencepool/templates/epp-deployment.yaml +++ b/config/charts/inferencepool/templates/epp-deployment.yaml @@ -54,7 +54,7 @@ spec: - --lora-info-metric - "" # Set an empty metric to disable LoRA metric scraping as they are not supported by Triton yet. {{- end }} - {{- if gt .Values.inferenceExtension.replicas 1 }} + {{- if gt (.Values.inferenceExtension.replicas | int) 1 }} - --ha-enable-leader-election {{- end }} # Pass additional flags via the inferenceExtension.flags field in values.yaml. @@ -73,7 +73,7 @@ spec: {{- toYaml .Values.inferenceExtension.extraContainerPorts | nindent 8 }} {{- end }} livenessProbe: - {{- if gt .Values.inferenceExtension.replicas 1 }} + {{- if gt (.Values.inferenceExtension.replicas | int) 1 }} grpc: port: 9003 service: liveness @@ -85,7 +85,7 @@ spec: initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: - {{- if gt .Values.inferenceExtension.replicas 1 }} + {{- if gt (.Values.inferenceExtension.replicas | int) 1 }} grpc: port: 9003 service: readiness diff --git a/config/charts/inferencepool/templates/leader-election-rbac.yaml b/config/charts/inferencepool/templates/leader-election-rbac.yaml index 8816dac7d..11b3dd516 100644 --- a/config/charts/inferencepool/templates/leader-election-rbac.yaml +++ b/config/charts/inferencepool/templates/leader-election-rbac.yaml @@ -1,4 +1,4 @@ -{{- if gt .Values.inferenceExtension.replicas 1 }} +{{- if gt (.Values.inferenceExtension.replicas | int) 1 }} --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 From d3eda83f8d3c201622c5311eff6c5e73be6c0a1e Mon Sep 17 00:00:00 2001 From: Gregory Pereira Date: Mon, 22 Sep 2025 17:36:13 -0500 Subject: [PATCH 061/133] enable istio as a provider + configuring destinationRule (#1381) * enable istio as a provider + configuring destinationRule Signed-off-by: greg pereira * document provider specific configurations Signed-off-by: greg pereira * remove default option, always create DesitnaitonRule with istio provider Signed-off-by: greg pereira --------- Signed-off-by: greg pereira --- config/charts/inferencepool/README.md | 29 +++++++++++++++++-- .../charts/inferencepool/templates/istio.yaml | 16 ++++++++++ config/charts/inferencepool/values.yaml | 11 +++++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 config/charts/inferencepool/templates/istio.yaml diff --git a/config/charts/inferencepool/README.md b/config/charts/inferencepool/README.md index c768f6130..41fee834d 100644 --- a/config/charts/inferencepool/README.md +++ b/config/charts/inferencepool/README.md @@ -16,7 +16,7 @@ To install via the latest published chart in staging (--version v0 indicates la ```txt $ helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ - --set provider.name=[none|gke] \ + --set provider.name=[none|gke|istio] \ oci://us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/charts/inferencepool --version v0 ``` @@ -95,7 +95,7 @@ Use `--set inferencePool.modelServerType=triton-tensorrt-llm` to install for Tri $ helm install triton-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=triton-llama3-8b-instruct \ --set inferencePool.modelServerType=triton-tensorrt-llm \ - --set provider.name=[none|gke] \ + --set provider.name=[none|gke|istio] \ oci://us-central1-docker.pkg.dev/k8s-staging-images/gateway-api-inference-extension/charts/inferencepool --version v0 ``` @@ -188,9 +188,32 @@ The following table list the configurable parameters of the chart. | `inferenceExtension.monitoring.prometheus.enabled` | Enable Prometheus ServiceMonitor creation for EPP metrics collection. Defaults to `false`. | | `inferenceExtension.monitoring.gke.enabled` | Enable GKE monitoring resources (`PodMonitoring` and RBAC). Defaults to `false`. | | `inferenceExtension.pluginsCustomConfig` | Custom config that is passed to EPP as inline yaml. | -| `provider.name` | Name of the Inference Gateway implementation being used. Possible values: `gke`. Defaults to `none`. | +| `provider.name` | Name of the Inference Gateway implementation being used. Possible values: [`none`, `gke`, or `istio`]. Defaults to `none`. | | `provider.gke.autopilot` | Set to `true` if the cluster is a GKE Autopilot cluster. This is only used if `provider.name` is `gke`. Defaults to `false`. | +### Provider Specific Configuration + +This section should document any Gateway provider specific values configurations. + +#### GKE + +These are the options available to you with `provider.name` set to `gke`: + +| **Parameter Name** | **Description** | +|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| `gke.monitoringSecret.name` | The name of the monitoring secret to be used. Defaults to `inference-gateway-sa-metrics-reader-secret`. | +| `gke.monitoringSecret.namespace` | The namespace that the monitoring secret lives in. Defaults to `default`. | + + +#### Istio + +These are the options available to you with `provider.name` set to `istio`: + +| **Parameter Name** | **Description** | +|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| `istio.destinationRule.host` | Custom host value for the destination rule. If not set this will use the default value which is derrived from the epp service name and release namespace to gerenate a valid service address. | +| `istio.destinationRule.trafficPolicy.connectionPool` | Configure the connectionPool level settings of the traffic policy | + ## Notes This chart will only deploy an InferencePool and its corresponding EndpointPicker extension. Before install the chart, please make sure that the inference extension CRDs are installed in the cluster. For more details, please refer to the [getting started guide](https://gateway-api-inference-extension.sigs.k8s.io/guides/). diff --git a/config/charts/inferencepool/templates/istio.yaml b/config/charts/inferencepool/templates/istio.yaml new file mode 100644 index 000000000..b50c0b021 --- /dev/null +++ b/config/charts/inferencepool/templates/istio.yaml @@ -0,0 +1,16 @@ +{{- if eq .Values.provider.name "istio" }} +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: {{ include "gateway-api-inference-extension.name" . }} +spec: + host: {{ .Values.istio.destinationRule.host | default (printf "%s.%s.svc.cluster.local" (include "gateway-api-inference-extension.name" .) .Release.Namespace) }} + trafficPolicy: + tls: + mode: SIMPLE + insecureSkipVerify: true + {{- if .Values.istio.destinationRule.trafficPolicy.connectionPool }} + connectionPool: + {{- .Values.istio.destinationRule.trafficPolicy.connectionPool | toYaml | nindent 6 }} + {{- end }} +{{- end }} diff --git a/config/charts/inferencepool/values.yaml b/config/charts/inferencepool/values.yaml index 6476bd800..91d6a48e6 100644 --- a/config/charts/inferencepool/values.yaml +++ b/config/charts/inferencepool/values.yaml @@ -67,6 +67,7 @@ inferencePool: # This will soon be deprecated when upstream GW providers support v1, just doing something simple for now. targetPortNumber: 8000 +# Options: ["gke", "istio", "none"] provider: name: none @@ -75,3 +76,13 @@ provider: gke: # Set to true if the cluster is an Autopilot cluster. autopilot: false + +istio: + destinationRule: + # Provide a way to override the default calculated host + host: "" + # Optional: Enables customization of the traffic policy + trafficPolicy: {} + # connectionPool: + # http: + # maxRequestsPerConnection: 256000 From 8c4c2acf3532a98feb37cd669fd1b501bba80e3b Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Tue, 23 Sep 2025 14:00:15 -0700 Subject: [PATCH 062/133] Update to v1.0.1-rc1 helm chart which fixed many bugs (#1641) --- site-src/guides/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 6eb42a27f..ae1b707ca 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -94,7 +94,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.0 \ + --version v1.0.1-rc.1 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -105,7 +105,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.0 \ + --version v1.0.1-rc.1 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -116,7 +116,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.0 \ + --version v1.0.1-rc.1 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -127,7 +127,7 @@ Tooling: helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.0 \ + --version v1.0.1-rc.1 \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` From 6e3f1db133e35c9120e9175dfa6424861ae126aa Mon Sep 17 00:00:00 2001 From: Kay Yan Date: Wed, 24 Sep 2025 18:22:17 +0800 Subject: [PATCH 063/133] fix: Correct indentation for MkDocs Admonitions display issue (#1643) Signed-off-by: Kay Yan --- site-src/guides/index.md | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index ae1b707ca..15339aa95 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -37,45 +37,45 @@ Tooling: === "GPU-Based Model Server" - For this setup, you will need 3 GPUs to run the sample model server. Adjust the number of replicas in `./config/manifests/vllm/gpu-deployment.yaml` as needed. - Create a Hugging Face secret to download the model [meta-llama/Llama-3.1-8B-Instruct](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct). Ensure that the token grants access to this model. + For this setup, you will need 3 GPUs to run the sample model server. Adjust the number of replicas in `./config/manifests/vllm/gpu-deployment.yaml` as needed. + Create a Hugging Face secret to download the model [meta-llama/Llama-3.1-8B-Instruct](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct). Ensure that the token grants access to this model. - Deploy a sample vLLM deployment with the proper protocol to work with the LLM Instance Gateway. + Deploy a sample vLLM deployment with the proper protocol to work with the LLM Instance Gateway. - ```bash - kubectl create secret generic hf-token --from-literal=token=$HF_TOKEN # Your Hugging Face Token with access to the set of Llama models - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/gpu-deployment.yaml - ``` + ```bash + kubectl create secret generic hf-token --from-literal=token=$HF_TOKEN # Your Hugging Face Token with access to the set of Llama models + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/gpu-deployment.yaml + ``` === "CPU-Based Model Server" - ???+ warning + ???+ warning - CPU deployment can be unreliable i.e. the pods may crash/restart because of resource contraints. + CPU deployment can be unreliable i.e. the pods may crash/restart because of resource contraints. - This setup is using the formal `vllm-cpu` image, which according to the documentation can run vLLM on x86 CPU platform. - For this setup, we use approximately 9.5GB of memory and 12 CPUs for each replica. + This setup is using the formal `vllm-cpu` image, which according to the documentation can run vLLM on x86 CPU platform. + For this setup, we use approximately 9.5GB of memory and 12 CPUs for each replica. - While it is possible to deploy the model server with less resources, this is not recommended. For example, in our tests, loading the model using 8GB of memory and 1 CPU was possible but took almost 3.5 minutes and inference requests took unreasonable time. In general, there is a tradeoff between the memory and CPU we allocate to our pods and the performance. The more memory and CPU we allocate the better performance we can get. + While it is possible to deploy the model server with less resources, this is not recommended. For example, in our tests, loading the model using 8GB of memory and 1 CPU was possible but took almost 3.5 minutes and inference requests took unreasonable time. In general, there is a tradeoff between the memory and CPU we allocate to our pods and the performance. The more memory and CPU we allocate the better performance we can get. - After running multiple configurations of these values we decided in this sample to use 9.5GB of memory and 12 CPUs for each replica, which gives reasonable response times. You can increase those numbers and potentially may even get better response times. For modifying the allocated resources, adjust the numbers in [cpu-deployment.yaml](https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/cpu-deployment.yaml) as needed. + After running multiple configurations of these values we decided in this sample to use 9.5GB of memory and 12 CPUs for each replica, which gives reasonable response times. You can increase those numbers and potentially may even get better response times. For modifying the allocated resources, adjust the numbers in [cpu-deployment.yaml](https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/cpu-deployment.yaml) as needed. - Deploy a sample vLLM deployment with the proper protocol to work with the LLM Instance Gateway. + Deploy a sample vLLM deployment with the proper protocol to work with the LLM Instance Gateway. - ```bash - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/cpu-deployment.yaml - ``` + ```bash + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/cpu-deployment.yaml + ``` === "vLLM Simulator Model Server" - This option uses the [vLLM simulator](https://github.com/llm-d/llm-d-inference-sim/tree/main) to simulate a backend model server. - This setup uses the least amount of compute resources, does not require GPU's, and is ideal for test/dev environments. + This option uses the [vLLM simulator](https://github.com/llm-d/llm-d-inference-sim/tree/main) to simulate a backend model server. + This setup uses the least amount of compute resources, does not require GPU's, and is ideal for test/dev environments. - To deploy the vLLM simulator, run the following command. + To deploy the vLLM simulator, run the following command. - ```bash - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/sim-deployment.yaml - ``` + ```bash + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/vllm/sim-deployment.yaml + ``` ### Install the Inference Extension CRDs From 0a147c231aa84393ee29a264130a1b585d76a2ce Mon Sep 17 00:00:00 2001 From: learner0810 <39400425+learner0810@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:08:21 +0800 Subject: [PATCH 064/133] Automatically update chart versions in guides (#1645) --- hack/release-quickstart.sh | 8 ++++++-- site-src/guides/index.md | 12 ++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/hack/release-quickstart.sh b/hack/release-quickstart.sh index 04b79a3ef..813146dee 100755 --- a/hack/release-quickstart.sh +++ b/hack/release-quickstart.sh @@ -132,11 +132,15 @@ sed -i.bak '/us-central1-docker.pkg.dev\/k8s-staging-images\/gateway-api-inferen # Update the container registry for lora-syncer in vLLM CPU and GPU deployment manifests. sed -i.bak -E "s|us-central1-docker\.pkg\.dev/k8s-staging-images|registry.k8s.io|g" "$VLLM_GPU_DEPLOY" "$VLLM_CPU_DEPLOY" +# Update IGW_CHART_VERSION in quickstart guide to match the current release tag +GUIDES_INDEX="site-src/guides/index.md" +sed -i.bak -E "s/export IGW_CHART_VERSION=v[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?/export IGW_CHART_VERSION=${RELEASE_TAG}/g" "$GUIDES_INDEX" + # ----------------------------------------------------------------------------- # Stage the changes # ----------------------------------------------------------------------------- -echo "Staging $VERSION_FILE $UPDATED_CRD $README $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY files..." -git add $VERSION_FILE $UPDATED_CRD $README $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY +echo "Staging $VERSION_FILE $UPDATED_CRD $README $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY $GUIDES_INDEX files..." +git add $VERSION_FILE $UPDATED_CRD $README $EPP_HELM $BBR_HELM $CONFORMANCE_MANIFESTS $VLLM_GPU_DEPLOY $VLLM_CPU_DEPLOY $VLLM_SIM_DEPLOY $GUIDES_INDEX # ----------------------------------------------------------------------------- # Cleanup backup files and finish diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 15339aa95..6c7b2c528 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -91,10 +91,11 @@ Tooling: ```bash export GATEWAY_PROVIDER=gke + export IGW_CHART_VERSION=v1.0.1-rc.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.1-rc.1 \ + --version $IGW_CHART_VERSION \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -102,10 +103,11 @@ Tooling: ```bash export GATEWAY_PROVIDER=istio + export IGW_CHART_VERSION=v1.0.1-rc.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.1-rc.1 \ + --version $IGW_CHART_VERSION \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -113,10 +115,11 @@ Tooling: ```bash export GATEWAY_PROVIDER=none + export IGW_CHART_VERSION=v1.0.1-rc.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.1-rc.1 \ + --version $IGW_CHART_VERSION \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` @@ -124,10 +127,11 @@ Tooling: ```bash export GATEWAY_PROVIDER=none + export IGW_CHART_VERSION=v1.0.1-rc.1 helm install vllm-llama3-8b-instruct \ --set inferencePool.modelServers.matchLabels.app=vllm-llama3-8b-instruct \ --set provider.name=$GATEWAY_PROVIDER \ - --version v1.0.1-rc.1 \ + --version $IGW_CHART_VERSION \ oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool ``` From 3e71c9ff6206e373e76e49a93a7f900991358c9e Mon Sep 17 00:00:00 2001 From: Murphy Chen Date: Thu, 25 Sep 2025 18:32:16 +0800 Subject: [PATCH 065/133] [chore] Fix serve-multiple-genai-models.md mistake (#1647) * Fix serve-multiple-genai-models.md error * update --- .../guides/serve-multiple-genai-models.md | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/site-src/guides/serve-multiple-genai-models.md b/site-src/guides/serve-multiple-genai-models.md index 2064f999a..25198e88f 100644 --- a/site-src/guides/serve-multiple-genai-models.md +++ b/site-src/guides/serve-multiple-genai-models.md @@ -180,9 +180,18 @@ kubectl get httproute llm-phi4-route -o yaml -H "Content-Type: application/json" \ -d '{ "model": "meta-llama/Llama-3.1-8B-Instruct", - "prompt": "Linux is said to be an open source kernel because ", "max_tokens": 100, - "temperature": 0 + "temperature": 0, + "messages": [ + { + "role": "developer", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Linux is said to be an open source kernel because " + } + ] }' ``` @@ -192,9 +201,18 @@ kubectl get httproute llm-phi4-route -o yaml -H "Content-Type: application/json" \ -d '{ "model": "microsoft/Phi-4-mini-instruct", - "prompt": "2+2 is ", - "max_tokens": 20, - "temperature": 0 + "max_tokens": 100, + "temperature": 0, + "messages": [ + { + "role": "developer", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "2+2 is " + } + ] }' ``` From db130c217e1912aaaf624d1ab71358b4cd201536 Mon Sep 17 00:00:00 2001 From: Nir Rozenbaum Date: Thu, 25 Sep 2025 17:32:20 +0300 Subject: [PATCH 066/133] remove istio destinationrule creation from quickstart (#1648) Signed-off-by: Nir Rozenbaum --- site-src/guides/index.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 6c7b2c528..611675025 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -196,13 +196,7 @@ Tooling: ./istioctl install --set tag=$TAG --set hub=gcr.io/istio-testing --set values.pilot.env.ENABLE_GATEWAY_API_INFERENCE_EXTENSION=true ``` - 3. If you run the Endpoint Picker (EPP) with the `--secure-serving` flag set to `true` (the default mode), it is currently using a self-signed certificate. As a security measure, Istio does not trust self-signed certificates by default. As a temporary workaround, you can apply the destination rule to bypass TLS verification for EPP. A more secure TLS implementation in EPP is being discussed in [Issue 582](https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/582). - - ```bash - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/destination-rule.yaml - ``` - - 4. Deploy Gateway + 3. Deploy Gateway ```bash kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/gateway.yaml @@ -215,13 +209,13 @@ Tooling: inference-gateway inference-gateway True 22s ``` - 6. Deploy the HTTPRoute + 4. Deploy the HTTPRoute ```bash kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/raw/main/config/manifests/gateway/istio/httproute.yaml ``` - 7. Confirm that the HTTPRoute status conditions include `Accepted=True` and `ResolvedRefs=True`: + 5. Confirm that the HTTPRoute status conditions include `Accepted=True` and `ResolvedRefs=True`: ```bash kubectl get httproute llm-route -o yaml From a4c3afa70081f269254acff2f307bc9db3fe1f95 Mon Sep 17 00:00:00 2001 From: learner0810 <39400425+learner0810@users.noreply.github.com> Date: Fri, 26 Sep 2025 21:28:19 +0800 Subject: [PATCH 067/133] Adds WeightedRandomPicker plugin description (#1657) --- site-src/guides/epp-configuration/config-text.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/site-src/guides/epp-configuration/config-text.md b/site-src/guides/epp-configuration/config-text.md index 382edba26..43a0e6cf7 100644 --- a/site-src/guides/epp-configuration/config-text.md +++ b/site-src/guides/epp-configuration/config-text.md @@ -235,6 +235,15 @@ Picks a random pod from the list of candidates. - `maxNumOfEndpoints`: Maximum number of endpoints to pick from the list of candidates. If not specified defaults to `1`. +#### **WeightedRandomPicker** + +Picks pod(s) from the list of candidates based on weighted random sampling using A-Res algorithm. + +- *Type*: weighted-random-picker +- *Parameters*: + - `maxNumOfEndpoints`: Maximum number of endpoints to pick from the list of candidates. If not + specified defaults to `1`. + #### **KvCacheScorer** Scores the candidate pods based on their KV cache utilization. From 27fc196abed285da6a1b22041e5aeaf83f25b259 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:32:18 -0700 Subject: [PATCH 068/133] chore(deps): bump github.com/prometheus/prometheus (#1634) Bumps [github.com/prometheus/prometheus](https://github.com/prometheus/prometheus) from 0.305.0 to 0.306.0. - [Release notes](https://github.com/prometheus/prometheus/releases) - [Changelog](https://github.com/prometheus/prometheus/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/prometheus/compare/v0.305.0...v0.306.0) --- updated-dependencies: - dependency-name: github.com/prometheus/prometheus dependency-version: 0.306.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 58277951a..9fdd6d1d9 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.66.1 - github.com/prometheus/prometheus v0.305.0 + github.com/prometheus/prometheus v0.306.0 github.com/stretchr/testify v1.11.1 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 diff --git a/go.sum b/go.sum index 817b54b73..d6ab7de1d 100644 --- a/go.sum +++ b/go.sum @@ -219,8 +219,8 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/prometheus/prometheus v0.305.0 h1:UO/LsM32/E9yBDtvQj8tN+WwhbyWKR10lO35vmFLx0U= -github.com/prometheus/prometheus v0.305.0/go.mod h1:JG+jKIDUJ9Bn97anZiCjwCxRyAx+lpcEQ0QnZlUlbwY= +github.com/prometheus/prometheus v0.306.0 h1:Q0Pvz/ZKS6vVWCa1VSgNyNJlEe8hxdRlKklFg7SRhNw= +github.com/prometheus/prometheus v0.306.0/go.mod h1:7hMSGyZHt0dcmZ5r4kFPJ/vxPQU99N5/BGwSPDxeZrQ= github.com/prometheus/sigv4 v0.2.0 h1:qDFKnHYFswJxdzGeRP63c4HlH3Vbn1Yf/Ao2zabtVXk= github.com/prometheus/sigv4 v0.2.0/go.mod h1:D04rqmAaPPEUkjRQxGqjoxdyJuyCh6E0M18fZr0zBiE= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -337,8 +337,8 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.238.0 h1:+EldkglWIg/pWjkq97sd+XxH7PxakNYoe/rkSTbnvOs= -google.golang.org/api v0.238.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= +google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= +google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= From 7d633b8dd446add72f16615a9895345f4ecdda2e Mon Sep 17 00:00:00 2001 From: Siyi Wang <32469039+syw14@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:28:16 -0700 Subject: [PATCH 069/133] Loosen validation for inference pool crd to support up to 8 ports per inference pool (#1653) * Add multi-port support * Fix wording * Update dependency to resolve test failure * update inference pool cel test case --- api/v1/inferencepool_types.go | 6 ++-- ...ence.networking.k8s.io_inferencepools.yaml | 8 ++++-- go.mod | 14 +++++----- go.sum | 28 +++++++++---------- test/cel/inferencepool_test.go | 14 ++++++++++ 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/api/v1/inferencepool_types.go b/api/v1/inferencepool_types.go index 2c7b52705..76026df64 100644 --- a/api/v1/inferencepool_types.go +++ b/api/v1/inferencepool_types.go @@ -70,10 +70,12 @@ type InferencePoolSpec struct { Selector LabelSelector `json:"selector,omitzero"` // TargetPorts defines a list of ports that are exposed by this InferencePool. - // Currently, the list may only include a single port definition. + // Every port will be treated as a distinctive endpoint by EPP, + // addressable as a 'podIP:portNumber' combination. // // +kubebuilder:validation:MinItems=1 - // +kubebuilder:validation:MaxItems=1 + // +kubebuilder:validation:MaxItems=8 + // +kubebuilder:validation:XValidation:message="port number must be unique",rule="self.all(p1, self.exists_one(p2, p1.number==p2.number))" // +listType=atomic // +required TargetPorts []Port `json:"targetPorts,omitempty"` diff --git a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml index ed325bea6..2ccaf2306 100644 --- a/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml +++ b/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml @@ -154,7 +154,8 @@ spec: targetPorts: description: |- TargetPorts defines a list of ports that are exposed by this InferencePool. - Currently, the list may only include a single port definition. + Every port will be treated as a distinctive endpoint by EPP, + addressable as a 'podIP:portNumber' combination. items: description: Port defines the network port that will be exposed by this InferencePool. @@ -170,10 +171,13 @@ spec: required: - number type: object - maxItems: 1 + maxItems: 8 minItems: 1 type: array x-kubernetes-list-type: atomic + x-kubernetes-validations: + - message: port number must be unique + rule: self.all(p1, self.exists_one(p2, p1.number==p2.number)) required: - endpointPickerRef - selector diff --git a/go.mod b/go.mod index 9fdd6d1d9..1f9a97e4a 100644 --- a/go.mod +++ b/go.mod @@ -106,16 +106,16 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.41.0 // indirect + golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.44.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/tools v0.37.0 // indirect golang.org/x/tools/go/expect v0.1.1-deprecated // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect diff --git a/go.sum b/go.sum index d6ab7de1d..6586a872b 100644 --- a/go.sum +++ b/go.sum @@ -285,20 +285,20 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -309,22 +309,22 @@ golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= diff --git a/test/cel/inferencepool_test.go b/test/cel/inferencepool_test.go index 8b3ba3ea5..deccb6e03 100644 --- a/test/cel/inferencepool_test.go +++ b/test/cel/inferencepool_test.go @@ -80,6 +80,20 @@ func TestValidateInferencePool(t *testing.T) { }, wantErrors: []string{"port is required when kind is 'Service' or unspecified (defaults to 'Service')"}, }, + { + desc: "passes validation with multiple unique port numbers", + mutate: func(ip *v1.InferencePool) { + ip.Spec.TargetPorts = []v1.Port{{Number: 8000}, {Number: 80}, {Number: 8081}, {Number: 443}} + }, + wantErrors: nil, + }, + { + desc: "fails validation with port numbers containing duplicates", + mutate: func(ip *v1.InferencePool) { + ip.Spec.TargetPorts = []v1.Port{{Number: 8000}, {Number: 80}, {Number: 8000}, {Number: 443}} + }, + wantErrors: []string{"port number must be unique"}, + }, } for _, tc := range testCases { From bbe070e9b710359615221aef4fee9a34f59cec69 Mon Sep 17 00:00:00 2001 From: Kellen Swain Date: Fri, 26 Sep 2025 13:24:15 -0700 Subject: [PATCH 070/133] Adding simple helm CI (#1635) --- Makefile | 11 +++++++--- hack/verify-helm.sh | 43 ++++++++++++++++++++++++++++++++++++++++ hack/verify-manifests.sh | 2 ++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100755 hack/verify-helm.sh diff --git a/Makefile b/Makefile index d9f3a6132..952b2aa4f 100644 --- a/Makefile +++ b/Makefile @@ -133,7 +133,7 @@ vet: ## Run go vet against code. go vet ./... .PHONY: test -test: generate fmt vet envtest image-build verify-crds ## Run tests. +test: generate fmt vet envtest image-build verify-crds verify-helm-charts ## Run tests. CGO_ENABLED=1 KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e | grep -v /conformance) -race -coverprofile cover.out .PHONY: test-unit @@ -174,6 +174,10 @@ verify: vet fmt-verify generate ci-lint api-lint verify-all verify-crds: kubectl-validate hack/verify-manifests.sh +.PHONY: verify-helm-charts +verify-helm-charts: helm-install + hack/verify-helm.sh + # Run static analysis. .PHONY: verify-all verify-all: @@ -423,8 +427,9 @@ $(GOLANGCI_API_LINT): yq: ## Download yq locally if necessary. GOBIN=$(PROJECT_DIR)/bin GO111MODULE=on go install github.com/mikefarah/yq/v4@$(YQ_VERSION) -.PHONY: helm -helm: ## Download helm locally if necessary. +.PHONY: helm-install +helm-install: $(HELM) ## Download helm locally if necessary. +$(HELM): $(LOCALBIN) GOBIN=$(PROJECT_DIR)/bin GO111MODULE=on go install helm.sh/helm/v3/cmd/helm@$(HELM_VERSION) .PHONY: kubectl-validate diff --git a/hack/verify-helm.sh b/hack/verify-helm.sh new file mode 100755 index 000000000..0388b6e24 --- /dev/null +++ b/hack/verify-helm.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Copyright 2025 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/.. + +declare -A test_cases_inference_pool + +# InferencePool Helm Chart test cases +test_cases_inference_pool["basic"]="--set inferencePool.modelServers.matchLabels.app=llm-instance-gateway" +test_cases_inference_pool["gke-provider"]="--set provider.name=gke --set inferencePool.modelServers.matchLabels.app=llm-instance-gateway" +test_cases_inference_pool["multiple-replicas"]="--set inferencePool.replicas=3 --set inferencePool.modelServers.matchLabels.app=llm-instance-gateway" + +# Run the install command in case this script runs from a different bash +# source (such as in the verify-all script) +make helm-install + +# Running tests cases +echo "Running helm template command for inferencePool chart..." +# Loop through the keys of the associative array +for key in "${!test_cases_inference_pool[@]}"; do + echo "Running test: $key" + ${SCRIPT_ROOT}/bin/helm template ${SCRIPT_ROOT}/config/charts/inferencepool ${test_cases_inference_pool[$key]} --output-dir="${SCRIPT_ROOT}/bin" + if [ $? -ne 0 ]; then + echo "Helm template command failed for test: $key" + exit 1 + fi +done + + + diff --git a/hack/verify-manifests.sh b/hack/verify-manifests.sh index dff4170ff..d74c27701 100755 --- a/hack/verify-manifests.sh +++ b/hack/verify-manifests.sh @@ -45,6 +45,8 @@ main() { fetch_crds "https://raw.githubusercontent.com/GoogleCloudPlatform/gke-gateway-api/refs/tags/${GKE_GATEWAY_API_VERSION}/config/crd/networking.gke.io_healthcheckpolicies.yaml" fetch_crds "https://raw.githubusercontent.com/istio/istio/refs/tags/${ISTIO_VERSION}/manifests/charts/base/files/crd-all.gen.yaml" + # Run the install command in case this script runs from a different bash + # source (such as in the verify-all script) make kubectl-validate ${SCRIPT_ROOT}/bin/kubectl-validate "${TEMP_DIR}" From 3bf69fa171954647b28cc9b960db2a37cc85344a Mon Sep 17 00:00:00 2001 From: Kellen Swain Date: Fri, 26 Sep 2025 14:34:14 -0700 Subject: [PATCH 071/133] Updating proposal process to reflect how we are currently operating (#1660) --- mkdocs.yml | 2 +- site-src/enhancements/overview.md | 6 + site-src/gieps/giep-116/index.md | 47 ----- site-src/gieps/giep-116/metadata.yaml | 32 --- site-src/gieps/overview.md | 272 -------------------------- 5 files changed, 7 insertions(+), 352 deletions(-) create mode 100644 site-src/enhancements/overview.md delete mode 100644 site-src/gieps/giep-116/index.md delete mode 100644 site-src/gieps/giep-116/metadata.yaml delete mode 100644 site-src/gieps/overview.md diff --git a/mkdocs.yml b/mkdocs.yml index c90b1bb49..b746f743d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -88,6 +88,6 @@ nav: - InferencePool: api-types/inferencepool.md - InferenceObjective: api-types/inferenceobjective.md - Enhancements: - - Overview: gieps/overview.md + - Overview: enhancements/overview.md - Contributing: - How to Get Involved: contributing/index.md diff --git a/site-src/enhancements/overview.md b/site-src/enhancements/overview.md new file mode 100644 index 000000000..ad2355e60 --- /dev/null +++ b/site-src/enhancements/overview.md @@ -0,0 +1,6 @@ +# Inference Gateway Proposal process + +Our current proposal process is intentionally light-weight. If you have a proposal you are interested in sharing, please follow these steps: + +1. Cut an issue or bring a topic to the weekly meeting! +2. Assuming positive signal, or if more context is needed please add a proposal, following the style and naming conventions shown here: https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/docs/proposals \ No newline at end of file diff --git a/site-src/gieps/giep-116/index.md b/site-src/gieps/giep-116/index.md deleted file mode 100644 index 4d678da22..000000000 --- a/site-src/gieps/giep-116/index.md +++ /dev/null @@ -1,47 +0,0 @@ -# GIEP-116: GIEP template - -* Issue: [#0](https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/116) -* Status: Provisional|Implementable|Experimental|Standard|Deferred|Rejected|Withdrawn|Replaced - -(See status definitions [here](overview.md#status).) - -## TLDR - -(1-2 sentence summary of the proposal) - -## Goals - -(Primary goals of this proposal.) - -## Non-Goals - -(What is out of scope for this proposal.) - -## Introduction - -(Can link to external doc -- but we should bias towards copying -the content into the GEP as online documents are easier to lose --- e.g. owner messes up the permissions, accidental deletion) - -## API - -(... details, can point to PR with changes) - -## Conformance Details - -(This section describes the names to be used for the feature or -features in conformance tests and profiles. - -These should be `CamelCase` names that specify the feature as -precisely as possible, and are particularly important for -Extended features, since they may be surfaced to users.) - -## Alternatives - -(List other design alternatives and why we did not go in that -direction) - -## References - -(Add any additional document links. Again, we should try to avoid -too much content not in version control to avoid broken links) diff --git a/site-src/gieps/giep-116/metadata.yaml b/site-src/gieps/giep-116/metadata.yaml deleted file mode 100644 index 56d101834..000000000 --- a/site-src/gieps/giep-116/metadata.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: internal.gateway.networking.k8s.io/v1alpha1 -kind: GIEPDetails -number: 116 -name: GIEP template -status: Completed -# Any authors who contribute to the GEP in any way should be listed here using -# their Github handle. -authors: - - robscott -relationships: - # obsoletes indicates that a GEP makes the linked GEP obsolete, and completely - # replaces that GEP. The obsoleted GEP MUST have its obsoletedBy field - # set back to this GEP, and MUST be moved to Declined. - obsoletes: {} - obsoletedBy: {} - # extends indicates that a GEP extends the linkned GEP, adding more detail - # or additional implementation. The extended GEP MUST have its extendedBy - # field set back to this GEP. - extends: {} - extendedBy: {} - # seeAlso indicates other GEPs that are relevant in some way without being - # covered by an existing relationship. - seeAlso: {} -# references is a list of hyperlinks to relevant external references. -# It's intended to be used for storing Github discussions, Google docs, etc. -references: {} -# featureNames is a list of the feature names introduced by the GEP, if there -# are any. This will allow us to track which feature was introduced by which GEP. -featureNames: {} -# changelog is a list of hyperlinks to PRs that make changes to the GEP, in -# ascending date order. -changelog: {} diff --git a/site-src/gieps/overview.md b/site-src/gieps/overview.md deleted file mode 100644 index df28fae84..000000000 --- a/site-src/gieps/overview.md +++ /dev/null @@ -1,272 +0,0 @@ -# Gateway Inference Enhancement Proposal (GIEP) - -Gateway Inference Enhancement Proposals (GIEPs) serve a similar purpose to the -[GEP](https://gateway-api.sigs.k8s.io/GEPs/overview/) process for the main -Gateway API project: - -1. Ensure that changes to the API follow a known process and discussion in the - OSS community. -1. Make changes and proposals discoverable (current and future). -1. Document design ideas, tradeoffs, decisions that were made for historical - reference. -1. Record the results of larger community discussions. -1. Record changes to the GIEP process itself. - -## Process - -This diagram shows the state diagram of the GIEP process at a high level, but -the details are below. - -

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 bTSbypWKD`$f6XUo4c6v%XDa@>^7sD&WD;(U diff --git a/pkg/epp/README.md b/pkg/epp/README.md index df5c21375..966aed5f2 100644 --- a/pkg/epp/README.md +++ b/pkg/epp/README.md @@ -20,9 +20,4 @@ An EPP instance handles a single `InferencePool` (and so for each `InferencePool - The EPP generates metrics to enhance observability. - It reports InferenceObjective-level metrics, further broken down by target model. - Detailed information regarding metrics can be found on the [website](https://gateway-api-inference-extension.sigs.k8s.io/guides/metrics/). - - -## Scheduling Algorithm -The scheduling package implements request scheduling algorithms for load balancing requests across backend pods in an inference gateway. The scheduler ensures efficient resource utilization while maintaining low latency and prioritizing critical requests. It applies a series of filters based on metrics and heuristics to select the best pod for a given request. The following flow chart summarizes the current scheduling algorithm - -Scheduling Algorithm + \ No newline at end of file From 18130dea8179218593f617226855de81f0c11185 Mon Sep 17 00:00:00 2001 From: DamianSawicki <86234282+DamianSawicki@users.noreply.github.com> Date: Fri, 12 Sep 2025 16:38:09 +0200 Subject: [PATCH 019/133] Fixes to overview.md and inferencemodel.md (#1281) * Replace Gateway API with Inference Extension This replaces references regarding Gateway API with references regarding Gateway API Inference Extension (or vice versa, as appropriate) in site-src/gieps/overview.md * Replace spec with x-spec in a link This fixes a link to the reference page for Inference Model. --- site-src/api-types/inferencemodel.md | 2 +- site-src/gieps/overview.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site-src/api-types/inferencemodel.md b/site-src/api-types/inferencemodel.md index 54fe57397..fbf27ec43 100644 --- a/site-src/api-types/inferencemodel.md +++ b/site-src/api-types/inferencemodel.md @@ -16,4 +16,4 @@ An InferenceModel allows the Inference Workload Owner to define: ## Spec -The full spec of the InferenceModel is defined [here](/reference/spec/#inferencemodel). \ No newline at end of file +The full spec of the InferenceModel is defined [here](/reference/x-spec/#inferencemodel). \ No newline at end of file diff --git a/site-src/gieps/overview.md b/site-src/gieps/overview.md index 438a0ffe0..df28fae84 100644 --- a/site-src/gieps/overview.md +++ b/site-src/gieps/overview.md @@ -1,7 +1,7 @@ # Gateway Inference Enhancement Proposal (GIEP) Gateway Inference Enhancement Proposals (GIEPs) serve a similar purpose to the -[GIEP](https://gateway-api.sigs.k8s.io/GIEPs/overview/) process for the main +[GEP](https://gateway-api.sigs.k8s.io/GEPs/overview/) process for the main Gateway API project: 1. Ensure that changes to the API follow a known process and discussion in the @@ -107,17 +107,17 @@ Before creating a GIEP, share your high level idea with the community. There are several places this may be done: - A [new GitHub - Discussion](https://github.com/kubernetes-sigs/gateway-api/discussions/new) -- On our [Slack Channel](https://kubernetes.slack.com/archives/CR0H13KGA) + Discussion](https://github.com/kubernetes-sigs/gateway-api-inference-extension/discussions/new) +- On our [Slack Channel](https://kubernetes.slack.com/archives/C08E3RZMT2P) - On one of our [community - meetings](https://gateway-api.sigs.k8s.io/contributing/?h=meetings#meetings) + meetings](https://gateway-api-inference-extension.sigs.k8s.io/contributing/?h=meetings#meetings) Please default to GitHub discussions: they work a lot like GitHub issues which makes them easy to search. ### 2. Create an Issue [Create a GIEP -issue](https://github.com/kubernetes-sigs/gateway-api/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md) +issue](https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/new) in the repo describing your change. At this point, you should copy the outcome of any other conversations or documents into this document. From ac77ccdc50a11e5bf8c5896468e40bce6712856f Mon Sep 17 00:00:00 2001 From: Maroon Ayoub Date: Fri, 12 Sep 2025 19:06:07 +0300 Subject: [PATCH 020/133] fix prefix plugin unit-test (#1575) Signed-off-by: Maroon Ayoub --- .../scheduling/framework/plugins/multi/prefix/plugin_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go index 9f9893ba8..df2eae67c 100644 --- a/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go +++ b/pkg/epp/scheduling/framework/plugins/multi/prefix/plugin_test.go @@ -276,6 +276,7 @@ func TestPrefixPluginChatCompletionsGrowth(t *testing.T) { }, } plugin.PreRequest(context.Background(), req1, schedulingResult, 0) + plugin.wg.Wait() // Second request adds assistant response and new user message (conversation grows) req2 := &types.LLMRequest{ @@ -308,6 +309,7 @@ func TestPrefixPluginChatCompletionsGrowth(t *testing.T) { // Simulate pod1 was picked again plugin.PreRequest(context.Background(), req2, schedulingResult, 0) + plugin.wg.Wait() // Third request continues the conversation even further req3 := &types.LLMRequest{ From afcd22121c2b1d421f2ea7a350dbc9fa5fcf03ea Mon Sep 17 00:00:00 2001 From: Abdullah Gharaibeh <40361897+ahg-g@users.noreply.github.com> Date: Fri, 12 Sep 2025 09:30:09 -0700 Subject: [PATCH 021/133] Update the endpoint picker diagram (#1572) Replacing InferenceModel with InferenceObjective --- docs/endpoint-picker.svg | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/endpoint-picker.svg b/docs/endpoint-picker.svg index 3ec8eed4e..6f4b9cf81 100644 --- a/docs/endpoint-picker.svg +++ b/docs/endpoint-picker.svg @@ -1,3 +1,5 @@ -Endpoint PickerServiceModelServerL7 Proxy / Gateway InferencePool API Selects - the model servers (the endpoints) - the endpoint picker serviceModel ServerProtocolTrafficExtensionProtocolGateway ControllerClientTrafficConfiguresWatchesWatches InferenceModel API Defines - the model/adapter to serve - the serving objectives for the modelObservabilityMetrics ScrapingObservabilityDashboardsStandard GatewayElementsInference ExtensionElementsInference Gateway \ No newline at end of file + + +Endpoint PickerServiceModelServerL7 Proxy / Gateway InferencePool API Selects - the model servers (the endpoints) - the endpoint picker serviceModel ServerProtocolTrafficExtensionProtocolGateway ControllerClientTrafficConfiguresWatchesWatches InferenceObjective API Defines - serving objectives for matching requestsObservabilityProtocolObservabilityDashboards \ No newline at end of file From fd3147f71533af7ac0ecc1f9353460b3882ffe81 Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Fri, 12 Sep 2025 09:58:09 -0700 Subject: [PATCH 022/133] Docs: Updates Intro Diagram (#1577) Signed-off-by: Daneyon Hansen --- site-src/images/resource-model.png | Bin 103027 -> 71805 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/site-src/images/resource-model.png b/site-src/images/resource-model.png index 0910c5b7a9d23fa18a851e5437c71a7aa9e35314..216c856e2fa2879bce4ad6318de5fb10b1119eb9 100644 GIT binary patch literal 71805 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îvAUoauWdG4LKc61644!d(WQ9Y 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%0A}~UN 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≪h%iO;12?b0T?KW1Z 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 From a587f09024bf07a1200b9b024fc49c69820e6fcd Mon Sep 17 00:00:00 2001 From: Daneyon Hansen Date: Fri, 12 Sep 2025 11:52:09 -0700 Subject: [PATCH 023/133] Conformance: Adds v1.0.0 COnformance Report for Kgateway (#1579) Signed-off-by: Daneyon Hansen --- .../reports/v1.0.0/gateway/kgateway/README.md | 75 +++++++++++++++++++ .../inference-v2.1.0-main-report.yaml | 23 ++++++ 2 files changed, 98 insertions(+) create mode 100644 conformance/reports/v1.0.0/gateway/kgateway/README.md create mode 100644 conformance/reports/v1.0.0/gateway/kgateway/inference-v2.1.0-main-report.yaml diff --git a/conformance/reports/v1.0.0/gateway/kgateway/README.md b/conformance/reports/v1.0.0/gateway/kgateway/README.md new file mode 100644 index 000000000..020f42f78 --- /dev/null +++ b/conformance/reports/v1.0.0/gateway/kgateway/README.md @@ -0,0 +1,75 @@ +# Kgateway + +## Table of Contents + +| Extension Version Tested | Profile Tested | Implementation Version | Mode | Report | +|--------------------------|----------------|------------------------|---------|----------------------------------------------------------------------------| +| v1.0.0 | Gateway | v2.1.0-main | default | [v2.1.0-main report](./inference-v2.1.0-main-report.yaml) | + +## Reproduce + +This is a mirror of the kgateway [inference conformance GHA workflow](https://github.com/kgateway-dev/kgateway/blob/v2.0.x/.github/actions/kube-inference-extension-conformance-tests/action.yaml). + +### Prerequisites + +In order to run the conformance tests, the following prerequisites must be met: + +- The [kubectl](https://kubernetes.io/docs/tasks/tools/) command-line tool installed and configured for the active cluster context. +- The [helm](https://github.com/helm/helm), [git](https://git-scm.com/downloads), and [make](https://www.gnu.org/software/make/) command-line tools installed. + +### Steps + +1. Set the environment variables use by the proceeding steps: + + ```sh + # The kgateway version + export VERSION=v2.1.0-main + # Skip building and loading the kgateway images + export SKIP_DOCKER=true + # Install Gateway API and Inference Extension CRDs + export CONFORMANCE=true + ``` + +2. Clone the kgateway repository and checkout the release: + + ```sh + git clone -b $VERSION https://github.com/kgateway-dev/kgateway.git && cd kgateway + ``` + +3. Create a KinD cluster: + + ```sh + make kind-setup + ``` + +4. Install the kgateway CRDs: + + ```sh + helm upgrade -i --create-namespace --namespace kgateway-system \ + --version $VERSION kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds + ``` + +5. Install kgateway with Inference Extension enabled: + + ```sh + helm upgrade -i --namespace kgateway-system --version $VERSION \ + kgateway oci://cr.kgateway.dev/kgateway-dev/charts/kgateway --set inferenceExtension.enabled=true + ``` + +6. Wait for the kgateway rollout to complete: + + ```sh + kubectl rollout status deploy/kgateway -n kgateway-system + ``` + +7. Run the conformance tests: + + ```sh + make gie-conformance + ``` + +8. View and verify the conformance report: + + ```sh + cat _test/conformance/inference-$VERSION-report.yaml + ``` diff --git a/conformance/reports/v1.0.0/gateway/kgateway/inference-v2.1.0-main-report.yaml b/conformance/reports/v1.0.0/gateway/kgateway/inference-v2.1.0-main-report.yaml new file mode 100644 index 000000000..3bee5ac54 --- /dev/null +++ b/conformance/reports/v1.0.0/gateway/kgateway/inference-v2.1.0-main-report.yaml @@ -0,0 +1,23 @@ +GatewayAPIInferenceExtensionVersion: v1.0.0 +apiVersion: gateway.networking.k8s.io/v1 +date: "2025-09-12T11:05:01-07:00" +gatewayAPIChannel: experimental +gatewayAPIVersion: v1.3.0 +implementation: + contact: + - github.com/kgateway-dev/kgateway/issues/new/choose + organization: kgateway-dev + project: kgateway + url: github.com/kgateway-dev/kgateway + version: v2.1.0-main +kind: ConformanceReport +mode: default +profiles: +- core: + result: success + statistics: + Failed: 0 + Passed: 9 + Skipped: 0 + name: Gateway + summary: Core tests succeeded. From b821b8440fd1828f08d5ce41467bbae8ee4ebbdb Mon Sep 17 00:00:00 2001 From: Kellen Swain Date: Fri, 12 Sep 2025 15:58:07 -0700 Subject: [PATCH 024/133] Updating the the doc site (#1500) * Updating the guides in the doc site * adding priority and capacity section --- mkdocs.yml | 7 +- site-src/api-types/inferencemodel.md | 19 -- site-src/api-types/inferenceobjective.md | 14 + site-src/api-types/inferencepool.md | 5 +- site-src/concepts/api-overview.md | 4 +- site-src/concepts/priority-and-capacity.md | 17 ++ site-src/concepts/roles-and-personas.md | 2 +- site-src/guides/adapter-rollout.md | 53 +--- .../guides/epp-configuration/config-text.md | 24 +- site-src/guides/implementers.md | 4 +- site-src/guides/index.md | 2 +- site-src/guides/inferencepool-rollout.md | 282 +----------------- .../guides/serve-multiple-genai-models.md | 2 +- .../guides/serve-multiple-lora-adapters.md | 98 ------ site-src/guides/troubleshooting.md | 2 +- site-src/index.md | 8 +- site-src/reference/x-spec.md | 6 - 17 files changed, 63 insertions(+), 486 deletions(-) delete mode 100644 site-src/api-types/inferencemodel.md create mode 100644 site-src/api-types/inferenceobjective.md create mode 100644 site-src/concepts/priority-and-capacity.md delete mode 100644 site-src/guides/serve-multiple-lora-adapters.md diff --git a/mkdocs.yml b/mkdocs.yml index 78f1cb81c..8262f6fb0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -56,6 +56,7 @@ nav: Design Principles: concepts/design-principles.md Conformance: concepts/conformance.md Roles and Personas: concepts/roles-and-personas.md + Priority and Capacity: concepts/priority-and-capacity.md - Implementations: - Gateways: implementations/gateways.md - Model Servers: implementations/model-servers.md @@ -65,13 +66,12 @@ nav: - Getting started: guides/index.md - Use Cases: - Serve Multiple GenAI models: guides/serve-multiple-genai-models.md - - Serve Multiple LoRA adapters: guides/serve-multiple-lora-adapters.md - Rollout: - Adapter Rollout: guides/adapter-rollout.md - InferencePool Rollout: guides/inferencepool-rollout.md - Metrics and Observability: guides/metrics-and-observability.md - Configuration Guide: - - Configuring the plugins via configuration files or text: guides/epp-configuration/config-text.md + - Configuring the plugins via configuration YAML file: guides/epp-configuration/config-text.md - Prefix Cache Aware Plugin: guides/epp-configuration/prefix-aware.md - Troubleshooting Guide: guides/troubleshooting.md - Implementer Guides: @@ -82,9 +82,10 @@ nav: - Regression Testing: performance/regression-testing/index.md - Reference: - API Reference: reference/spec.md + - Alpha API Reference: reference/x-spec.md - API Types: - InferencePool: api-types/inferencepool.md - - InferenceModel: api-types/inferencemodel.md + - InferenceObjective: api-types/inferenceobjective.md - Enhancements: - Overview: gieps/overview.md - Contributing: diff --git a/site-src/api-types/inferencemodel.md b/site-src/api-types/inferencemodel.md deleted file mode 100644 index fbf27ec43..000000000 --- a/site-src/api-types/inferencemodel.md +++ /dev/null @@ -1,19 +0,0 @@ -# Inference Model - -??? example "Alpha since v0.1.0" - - The `InferenceModel` resource is alpha and may have breaking changes in - future releases of the API. - -## Background - -An InferenceModel allows the Inference Workload Owner to define: - -- Which Model/LoRA adapter(s) to consume. - - Mapping from a client facing model name to the target model name in the InferencePool. - - InferenceModel allows for traffic splitting between adapters _in the same InferencePool_ to allow for new LoRA adapter versions to be easily rolled out. -- Criticality of the requests to the InferenceModel. - -## Spec - -The full spec of the InferenceModel is defined [here](/reference/x-spec/#inferencemodel). \ No newline at end of file diff --git a/site-src/api-types/inferenceobjective.md b/site-src/api-types/inferenceobjective.md new file mode 100644 index 000000000..9b7fe744c --- /dev/null +++ b/site-src/api-types/inferenceobjective.md @@ -0,0 +1,14 @@ +# Inference Objective + +??? example "Alpha since v1.0.0" + + The `InferenceObjective` resource is alpha and may have breaking changes in + future releases of the API. + +## Background + +The **InferenceObjective** API defines a set of serving objectives of the specific request it is associated with. This CRD currently houses only `Priority` but will be expanded to include fields such as SLO attainment. + +## Spec + +The full spec of the InferenceModel is defined [here](/reference/x-spec/#inferenceobjective). \ No newline at end of file diff --git a/site-src/api-types/inferencepool.md b/site-src/api-types/inferencepool.md index c4481b1ad..8922d0d11 100644 --- a/site-src/api-types/inferencepool.md +++ b/site-src/api-types/inferencepool.md @@ -1,9 +1,8 @@ # Inference Pool -??? example "Alpha since v0.1.0" +??? success example "GA since v1.0.0" - The `InferencePool` resource is alpha and may have breaking changes in - future releases of the API. + The `InferencePool` resource has been graduated to v1 and is considered stable. ## Background diff --git a/site-src/concepts/api-overview.md b/site-src/concepts/api-overview.md index ab07a1d2d..01ee25431 100644 --- a/site-src/concepts/api-overview.md +++ b/site-src/concepts/api-overview.md @@ -23,6 +23,6 @@ each aligning with a specific user persona in the Generative AI serving workflow InferencePool represents a set of Inference-focused Pods and an extension that will be used to route to them. Within the broader Gateway API resource model, this resource is considered a "backend". In practice, that means that you'd replace a Kubernetes Service with an InferencePool. This resource has some similarities to Service (a way to select Pods and specify a port), but has some unique capabilities. With InferencePool, you can configure a routing extension as well as inference-specific routing optimizations. For more information on this resource, refer to our [InferencePool documentation](/api-types/inferencepool) or go directly to the [InferencePool spec](/reference/spec/#inferencepool). -### InferenceModel +### InferenceObjective -An InferenceModel represents a model or adapter, and configuration associated with that model. This resource enables you to configure the relative criticality of a model, and allows you to seamlessly translate the requested model name to one or more backend model names. Multiple InferenceModels can be attached to an InferencePool. For more information on this resource, refer to our [InferenceModel documentation](/api-types/inferencemodel) or go directly to the [InferenceModel spec](/reference/spec/#inferencemodel). +An InferenceObjective represents the objectives of a specific request. A single InferenceObjective is associated with a request, and multiple requests with different InferenceObjectives can be attached to an InferencePool. For more information on this resource, refer to our [InferenceObjective documentation](/api-types/inferenceobjective) or go directly to the [InferenceObjective spec](/reference/spec/#inferenceobjective). diff --git a/site-src/concepts/priority-and-capacity.md b/site-src/concepts/priority-and-capacity.md new file mode 100644 index 000000000..367aebfdf --- /dev/null +++ b/site-src/concepts/priority-and-capacity.md @@ -0,0 +1,17 @@ +# Priority and Capacity + +The InferenceObjective creates the definition of `Priority` which describes how requests interact with each other, this naturally interacts with total pool capacity, and properly understanding and configuring these behaviors is important in allowing a pool to handle requests of different priority. + +## Priority (in flow control) + +It should be noted that priority is currently only used in [Capacity](#capacity), and that the description below is how Priority will be consumed in the `Flow Control` model. + +Priority is a simple stack rank; the higher the number, the higher the priority. Should no priority for a request be specified, the default value is zero. Requests of higher priority are _always_ selected first when requests are queued. Requests of equal priority currently operate on a FCFS basis. + +## Capacity + +The current capacity model uses configurable [thresholds](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/35b14a10a9830d1a9e3850913539066ebc8fb317/pkg/epp/saturationdetector/saturationdetector.go#L49) to determine if the entire pool is saturated. The calculation is to simply iterate through each endpoint in the pool, and if all are above all thresholds, the pool is considered `saturated`. In the event of saturation, all requests with a negative priority will be rejected, and other requests will be scheduled and queued on the model servers. + +## Future work + +The Flow Control system is nearing completion and will add more nuance to the Priority and Capacity model: proper priority enforcement, more articulate capacity tracking, queuing at the Inference Gateway level, etc. This documentation will be updated when the Flow Control has finished implementation. \ No newline at end of file diff --git a/site-src/concepts/roles-and-personas.md b/site-src/concepts/roles-and-personas.md index 0746adbfb..f1d17a59d 100644 --- a/site-src/concepts/roles-and-personas.md +++ b/site-src/concepts/roles-and-personas.md @@ -17,7 +17,7 @@ The Inference Platform Admin creates and manages the infrastructure necessary to An Inference Workload Owner persona owns and manages one or many Generative AI Workloads (LLM focused *currently*). This includes: -- Defining criticality +- Defining priority - Managing fine-tunes - LoRA Adapters - System Prompts diff --git a/site-src/guides/adapter-rollout.md b/site-src/guides/adapter-rollout.md index 0936d2913..7d6611c92 100644 --- a/site-src/guides/adapter-rollout.md +++ b/site-src/guides/adapter-rollout.md @@ -3,7 +3,6 @@ The goal of this guide is to show you how to perform incremental roll out operations, which gradually deploy new versions of your inference infrastructure. You can update LoRA adapters and Inference Pool with minimal service disruption. -This page also provides guidance on traffic splitting and rollbacks to help ensure reliable deployments for LoRA adapters rollout. LoRA adapter rollouts let you deploy new versions of LoRA adapters in phases, without altering the underlying base model or infrastructure. @@ -49,36 +48,7 @@ data: The new adapter version is applied to the model servers live, without requiring a restart. - -### Direct traffic to the new adapter version - -Modify the InferenceModel to configure a canary rollout with traffic splitting. In this example, 10% of traffic for food-review model will be sent to the new ***food-review-2*** adapter. - - -```bash -kubectl edit inferencemodel food-review -``` - -Change the targetModels list in InferenceModel to match the following: - - -```yaml -apiVersion: inference.networking.x-k8s.io/v1alpha2 -kind: InferenceModel -metadata: - name: food-review -spec: - criticality: 1 - poolRef: - name: vllm-llama3-8b-instruct - targetModels: - - name: food-review-1 - weight: 90 - - name: food-review-2 - weight: 10 -``` - -The above configuration means one in every ten requests should be sent to the new version. Try it out: +Try it out: 1. Get the gateway IP: ```bash @@ -88,7 +58,7 @@ IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].va 2. Send a few requests as follows: ```bash curl -i ${IP}:${PORT}/v1/completions -H 'Content-Type: application/json' -d '{ -"model": "food-review", +"model": "food-review-2", "prompt": "Write as if you were a critic: San Francisco", "max_tokens": 100, "temperature": 0 @@ -97,23 +67,6 @@ curl -i ${IP}:${PORT}/v1/completions -H 'Content-Type: application/json' -d '{ ### Finish the rollout - -Modify the InferenceModel to direct 100% of the traffic to the latest version of the adapter. - -```yaml -apiVersion: inference.networking.x-k8s.io/v1alpha2 -kind: InferenceModel -metadata: - name: food-review -spec: - criticality: 1 - poolRef: - name: vllm-llama3-8b-instruct - targetModels: - - name: food-review-2 - weight: 100 -``` - Unload the older versions from the servers by updating the LoRA syncer ConfigMap to list the older version under the `ensureNotExist` list: ```yaml @@ -137,5 +90,5 @@ data: source: Kawon/llama3.1-food-finetune_v14_r8 ``` -With this, all requests should be served by the new adapter version. +With this, the new adapter version should be available for all incoming requests. diff --git a/site-src/guides/epp-configuration/config-text.md b/site-src/guides/epp-configuration/config-text.md index 6df19db80..cdb3714cb 100644 --- a/site-src/guides/epp-configuration/config-text.md +++ b/site-src/guides/epp-configuration/config-text.md @@ -1,17 +1,14 @@ -# Configuring Plugins via text +# Configuring Plugins via YAML The set of lifecycle hooks (plugins) that are used by the Inference Gateway (IGW) is determined by how -it is configured. The IGW can be configured in several ways, either by code or via text. +it is configured. The IGW is primarily configured via a configuration file. -If configured by code either a set of predetermined environment variables must be used or one must -fork the IGW and change code. - -A simpler way to congigure the IGW is to use a text based configuration. This text is in YAML format -and can either be in a file or specified in-line as a parameter. The configuration defines the set of +The YAML file can either be specified as a path to a file or in-line as a parameter. The configuration defines the set of plugins to be instantiated along with their parameters. Each plugin can also be given a name, enabling -the same plugin type to be instantiated multiple times, if needed. +the same plugin type to be instantiated multiple times, if needed (such as when configuring multiple scheduling profiles). -Also defined is a set of SchedulingProfiles, which determine the set of plugins to be used when scheduling a request. If one is not defailed, a default one names `default` will be added and will reference all of the +Also defined is a set of SchedulingProfiles, which determine the set of plugins to be used when scheduling a request. +If no scheduling profile is specified, a default profile, named `default` will be added and will reference all of the instantiated plugins. The set of plugins instantiated can include a Profile Handler, which determines which SchedulingProfiles @@ -22,12 +19,9 @@ In addition, the set of instantiated plugins can also include a picker, which ch the request is scheduled after filtering and scoring. If one is not referenced in a SchedulingProfile, an instance of `MaxScorePicker` will be added to the SchedulingProfile in question. -It should be noted that while the configuration text looks like a Kubernetes Custom Resource, it is -**NOT** a Kubernetes Custom Resource. Kubernetes infrastructure is used to load the configuration -text and in the future will also help in versioning the text. - -It should also be noted that even when the configuration text is loaded from a file, it is loaded at -the Endpoint-Picker's (EPP) startup and changes to the file at runtime are ignored. +***NOTE***: While the configuration text looks like a Kubernetes CRD, it is +**NOT** a Kubernetes CRD. Specifically, the config is not reconciled upon, and is only read on startup. +This behavior is intentional, as augmenting the scheduling config without redeploying the EPP is not supported. The configuration text has the following form: ```yaml diff --git a/site-src/guides/implementers.md b/site-src/guides/implementers.md index 6fce01657..d42fa8d91 100644 --- a/site-src/guides/implementers.md +++ b/site-src/guides/implementers.md @@ -157,8 +157,8 @@ An example of a similar approach is Kuadrant’s [WASM Shim](https://github.com/ Here are some tips for testing your controller end-to-end: - **Focus on Key Scenarios**: Add common scenarios like creating, updating, and deleting InferencePool resources, as well as different routing rules that target InferencePool backends. -- **Verify Routing Behaviors**: Design more complex routing scenarios and verify that requests are correctly routed to the appropriate model server pods within the InferencePool based on the InferenceModel configuration. -- **Test Error Handling**: Verify that the controller correctly handles scenarios like unsupported model names or resource constraints (if criticality-based shedding is implemented). Test with state transitions (such as constant requests while Pods behind EPP are being replaced and Pods behind InferencePool are being replaced) to ensure that the system is resilient to failures and can automatically recover by redirecting traffic to healthy Pods. +- **Verify Routing Behaviors**: Design more complex routing scenarios and verify that requests are correctly routed to the appropriate model server pods within the InferencePool. +- **Test Error Handling**: Verify that the controller correctly handles scenarios like unsupported model names or resource constraints (if priority-based shedding is implemented). Test with state transitions (such as constant requests while Pods behind EPP are being replaced and Pods behind InferencePool are being replaced) to ensure that the system is resilient to failures and can automatically recover by redirecting traffic to healthy Pods. - **Using Reference EPP Implementation + Echoserver**: You can use the [reference EPP implementation](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/pkg/epp) for testing your controller end-to-end. Instead of a full-fledged model server, a simple mock server (like the [echoserver](https://github.com/kubernetes-sigs/ingress-controller-conformance/tree/master/images/echoserver)) can be very useful for verifying routing to ensure the correct pod received the request. - **Performance Test**: Run end-to-end [benchmarks](https://gateway-api-inference-extension.sigs.k8s.io/performance/benchmark/) to make sure that your inference gateway can achieve the latency target that is desired. diff --git a/site-src/guides/index.md b/site-src/guides/index.md index da8063298..3d47ff33a 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -349,7 +349,7 @@ Tooling: The following instructions assume you would like to cleanup ALL resources that were created in this quickstart guide. Please be careful not to delete resources you'd like to keep. - 1. Uninstall the InferencePool, InferenceModel, and model server resources + 1. Uninstall the InferencePool, InferenceObjective and model server resources ```bash helm uninstall vllm-llama3-8b-instruct diff --git a/site-src/guides/inferencepool-rollout.md b/site-src/guides/inferencepool-rollout.md index f3d929466..b274a7262 100644 --- a/site-src/guides/inferencepool-rollout.md +++ b/site-src/guides/inferencepool-rollout.md @@ -34,7 +34,6 @@ teams can ensure stability and performance, quickly identifying and reverting an 1. **Deploy new infrastructure**: Create a new InferencePool configured with the new node(compute/accelerator) / model server / base model that you chose. 1. **Configure traffic splitting**: Use an HTTPRoute to split traffic between the existing InferencePool and the new InferencePool. The `backendRefs.weight` field controls the traffic percentage allocated to each pool. -1. **Maintain InferenceModel integrity**: Retain the existing InferenceModel configuration to ensure uniform model behavior across both node configurations or base model versions or model server versions. 1. **Preserve rollback capability**: Retain the original nodes and InferencePool during the roll out to facilitate a rollback if necessary. ## Example @@ -45,276 +44,10 @@ Follow the steps in the [main guide](index.md) ### Deploy new infrastructure You start with an existing InferencePool named vllm-llama3-8b-instruct. -To replace the original InferencePool, you create a new InferencePool named vllm-llama3-8b-instruct-new along with -InferenceModels and Endpoint Picker Extension configured with the updated node specifications of `nvidia-h100-80gb` accelerator type, +To replace the original InferencePool, you create a new InferencePool, configured to select the pods with the `nvidia-h100-80gb` accelerator type. -```yaml -kubectl apply -f - < ``` With this, all requests should be served by the new Inference Pool. diff --git a/site-src/guides/serve-multiple-genai-models.md b/site-src/guides/serve-multiple-genai-models.md index 92bfedaca..a2e4e51d5 100644 --- a/site-src/guides/serve-multiple-genai-models.md +++ b/site-src/guides/serve-multiple-genai-models.md @@ -4,7 +4,7 @@ A company wants to deploy multiple large language models (LLMs) to a cluster to For example, they might want to deploy a Gemma3 model for a chatbot interface and a DeepSeek model for a recommendation application. The company needs to ensure optimal serving performance for these LLMs. By using an Inference Gateway, you can deploy these LLMs on your cluster with your chosen accelerator configuration in an `InferencePool`. -You can then route requests based on the model name (such as `chatbot` and `recommender`) and the `Criticality` property. +You can then route requests based on the model name (such as `chatbot` and `recommender`). ## How diff --git a/site-src/guides/serve-multiple-lora-adapters.md b/site-src/guides/serve-multiple-lora-adapters.md deleted file mode 100644 index 59cfe7208..000000000 --- a/site-src/guides/serve-multiple-lora-adapters.md +++ /dev/null @@ -1,98 +0,0 @@ -# Serve LoRA adapters on a shared pool -A company wants to serve LLMs for document analysis and focuses on audiences in multiple languages, such as English and Spanish. -They have a fine-tuned LoRA adapter for each language, but need to efficiently use their GPU and TPU capacity. -You can use an Inference Gateway to deploy dynamic LoRA fine-tuned adapters for each language (for example, `english-bot` and `spanish-bot`) on a common base model and accelerator. -This lets you reduce the number of required accelerators by densely packing multiple models in a shared pool. - -## How -The following diagram illustrates how Inference Gateway serves multiple LoRA adapters on a shared pool. -![Serving LoRA adapters on a shared pool](../images/serve-LoRA-adapters.png) -This example illustrates how you can densely serve multiple LoRA adapters with distinct workload performance objectives on a common InferencePool. -```yaml -apiVersion: gateway.networking.x-k8s.io/v1alpha1 -kind: InferencePool -metadata: - name: gemma3 -spec: - selector: - pool: gemma3 -``` -Let us say we have a couple of LoRA adapters named “english-bot” and “spanish-bot” for the Gemma3 base model. -You can create an `InferenceModel` resource and associate these LoRA adapters to the relevant InferencePool resource. -In this case, we associate these LoRA adapters to the gemma3 InferencePool resource created above. - -```yaml -apiVersion: inference.networking.x-k8s.io/v1alpha2 -kind: InferenceModel -metadata: - name: english-bot -spec: - criticality: 1 - poolRef: - name: gemma3 - ---- -apiVersion: inference.networking.x-k8s.io/v1alpha2 -kind: InferenceModel -metadata: - name: spanish-bot -spec: - criticality: 2 - poolRef: - name: gemma3 - -``` -Now, you can route your requests from the gateway using the `HTTPRoute` object. -```yaml -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: inference-gateway -spec: - listeners: - - protocol: HTTP - port: 80 - name: http - ---- -kind: HTTPRoute -apiVersion: gateway.networking.k8s.io/v1 -metadata: - name: routes-to-llms -spec: - parentRefs: - - name: inference-gateway - rules: - - matches: - path: - type: PathPrefix - value: / - backendRefs: - - name: gemma3 - kind: InferencePool -``` - -## Try it out - -1. Get the gateway IP: -```bash -IP=$(kubectl get gateway/inference-gateway -o jsonpath='{.status.addresses[0].value}'); PORT=80 -``` -2. Send a few requests to model "english-bot" as follows: -```bash -curl -i ${IP}:${PORT}/v1/completions -H 'Content-Type: application/json' -d '{ -"model": "english-bot", -"prompt": "What is the color of the sky", -"max_tokens": 100, -"temperature": 0 -}' -``` -3. Send a few requests to model "spanish-bot" as follows: -```bash -curl -i ${IP}:${PORT}/v1/completions -H 'Content-Type: application/json' -d '{ -"model": "spanish-bot", -"prompt": "¿De qué color es...?", -"max_tokens": 100, -"temperature": 0 -}' -``` \ No newline at end of file diff --git a/site-src/guides/troubleshooting.md b/site-src/guides/troubleshooting.md index c741d59bf..1f08619f7 100644 --- a/site-src/guides/troubleshooting.md +++ b/site-src/guides/troubleshooting.md @@ -16,7 +16,7 @@ This is a default gateway error, meaning the request never reached a backend ser ## 429 Too Many Requests ### `system saturated, sheddable request dropped` -This error indicates that the entire inference pool has exceeded its saturation thresholds. This means the system is under heavy load and is shedding non-critical requests. To address this, check the following: +This error indicates that the entire inference pool has exceeded its saturation thresholds. This means the system is under heavy load and is shedding low priority requests. To address this, check the following: * gateway-api-inference-extension version: * **v0.5.1 and earlier**: Verify you're using an `InferenceModel` and that its `criticality` is set to `Critical`. This ensures requests are queued on the model servers instead of being dropped. diff --git a/site-src/index.md b/site-src/index.md index a2892c5b8..cf1ddb32a 100644 --- a/site-src/index.md +++ b/site-src/index.md @@ -1,11 +1,5 @@ # Introduction -???+ warning - - - Some portions of this site may be out of date with the v1.0.0 release candidate. - Updates under active development! - Gateway API Inference Extension is an official Kubernetes project that optimizes self-hosting Generative Models on Kubernetes. The overall resource model focuses on 2 new inference-focused @@ -49,7 +43,7 @@ in a higher level **AI Gateways** like [LiteLLM](https://www.litellm.ai/), [Gloo - **Model-aware routing**: Instead of simply routing based on the path of the request, an **[inference gateway]** allows you to route to models based on the model names. This is enabled by support for GenAI Inference API specifications (such as OpenAI API) in the gateway implementations such as in Envoy Proxy. This model-aware routing also extends to Low-Rank Adaptation (LoRA) fine-tuned models. -- **Serving priority**: an **[inference gateway]** allows you to specify the serving priority of your models. For example, you can specify that your models for online inference of chat tasks (which is more latency sensitive) have a higher [*Criticality*](/reference/spec/#criticality) than a model for latency tolerant tasks such as a summarization. +- **Serving priority**: an **[inference gateway]** allows you to specify the serving priority of your models. For example, you can specify that your models for online inference of chat tasks (which is more latency sensitive) have a higher [*Priority*](/reference/spec/#priority) than a model for latency tolerant tasks such as a summarization. - **Model rollouts**: an **[inference gateway]** allows you to incrementally roll out new model versions by traffic splitting definitions based on the model names. diff --git a/site-src/reference/x-spec.md b/site-src/reference/x-spec.md index 9151dcda6..c1a57ce3f 100644 --- a/site-src/reference/x-spec.md +++ b/site-src/reference/x-spec.md @@ -123,12 +123,6 @@ performance and latency goals for the model. These workloads are expected to operate within an InferencePool sharing compute capacity with other InferenceObjectives, defined by the Inference Platform Admin. -InferenceObjective's modelName (not the ObjectMeta name) is unique for a given InferencePool, -if the name is reused, an error will be shown on the status of a -InferenceObjective that attempted to reuse. The oldest InferenceObjective, based on -creation timestamp, will be selected to remain valid. In the event of a race -condition, one will be selected at random. - _Appears in:_ From 35933d2cf892f7349c76d37a18d342653cf6f348 Mon Sep 17 00:00:00 2001 From: Luke Van Drie Date: Fri, 12 Sep 2025 18:52:14 -0500 Subject: [PATCH 025/133] feat: Add top-level Flow Controller (#1525) * feat(flowcontrol): Refactor FlowRegistry contracts This commit refactors some of the core Flow Control contracts to improve clarity and better align with their intended roles. The goal is to create a more intuitive and robust interface for the upcoming top-level FlowController. Key changes include: - The `FlowRegistryClient` interface is renamed to `FlowRegistryDataPlane` to more accurately reflect its role in the high-throughput request path. - The `FlowRegistryAdmin` interface is renamed to `FlowRegistryObserver` to clarify its read-only, observational nature. - The `ActiveFlowConnection.Shards()` method is renamed to `ActiveFlowConnection.ActiveShards()` to make it explicit that it returns only active, schedulable shards. This removes ambiguity for the distributor logic. - `ShardStats` is enriched with `ID` and `IsActive` fields, providing consumers with more context about the shard's state at the time the snapshot was taken. - The registry implementation has been updated to match these new contract definitions. * refactor: Adapt ShardProcessor to a worker role This commit refactors the `ShardProcessor` to function as a stateful worker managed by a higher-level supervisor. This is a preparatory step for the introduction of the new top-level `FlowController`. The public API of the processor is changed from a direct `Enqueue` method to a more sophisticated, channel-based submission model with `Submit` (non-blocking) and `SubmitOrBlock` (blocking). This decouples the producer from the processor's main loop, enabling better backpressure signals and higher throughput. Key changes include: - Introduction of `Submit` and `SubmitOrBlock` for asynchronous request handoff. - `FlowItem`'s finalization logic is improved to be more robust and channel-based. - Error handling within the dispatch cycle is refactored (no logic change) to be more clear about how it promotes work conservation by isolating failures to a single priority band. * feat: Introduce the FlowController supervisor This commit introduces the `FlowController`, a high-throughput, sharded supervisor that orchestrates a pool of stateful `ShardProcessor` workers. This new component is the central processing engine of the Flow Control system, implementing a "supervisor-worker" pattern. Key features of the `FlowController` include: - Supervisor-Worker Architecture: Acts as a stateless supervisor, managing the lifecycle of stateful `ShardProcessor` workers. It includes a reconciliation loop to garbage-collect workers for stale shards. - Flow-Aware Load Balancing: Implements a "Join-Shortest-Queue-by-Bytes" (JSQ-Bytes) algorithm to distribute incoming requests to the least-loaded worker, promoting emergent fairness. - Synchronous API: Exposes a blocking `EnqueueAndWait` method, which simplifies client integration (e.g., with Envoy `ext_proc`) and provides direct backpressure. - Lazy Worker Initialization: Workers are created on-demand when a shard shard first becomes active to conserve resources and reduce contention on the hot path. - Configuration: A new `Config` object allows for tuning parameters like TTLs, buffer sizes, and reconciliation intervals. * docs: Update comments to align with FlowController This commit updates documentation and code comments across various framework components to align with the concepts and architecture introduced by the `FlowController`. Key changes include: - FCFS Policy: Clarified the distinction between "logical" and "physical" enqueue time and the behavioral trade-offs when pairing with different queue capabilities. - ListQueue: Expanded the documentation to explain its role as a high-performance, approximate FCFS queue in the context of the `FlowController`'s retry mechanics. - Request Types: Refined the comments for `QueueItemAccessor` to be more precise about the meaning of `EnqueueTime`. * refactor Simplify controller Lifecycle This commit refactors the `FlowController` to simplify its startup and shutdown lifecycle, making it more robust and easier to reason about. It also incorporates several smaller improvements based on reviewer feedback. The primary change addresses a complex lifecycle implementation that used an `atomic.Bool` (`isRunning`) and a `ready` channel to manage state. Key changes: - **Simplified Lifecycle:** The controller's lifecycle is now tied directly to a `context` passed into `NewFlowController`. The `Run` method has been unexported, and the main `run` loop is started as a goroutine from the constructor. This eliminates the `ready` channel and `isRunning` flag in addition to simplifying the interface for callers. - **Robust Worker Creation:** The `getOrStartWorker` logic has been improved to ensure that in a race to create a worker, the "losing" goroutine correctly cleans up its resources and does not start a redundant processor. This fixes a bug where the losing worker would evict all items from its queues on shutdown which were shared instances with the winning worker resulting in premature request finalization. - **Comment Reduction:** The extensive explanatory comments in `distributeRequest` have been condensed to be more concise while retaining the essential details of the algorithm. - **Minor Cleanups:** - The initial, unnecessary call to `reconcileProcessors()` at startup has been removed. - Error messages have been clarified (e.g., "acquire lease" instead of "establish connection"). - A typed error for nil requests was replaced with a standard `errors.New`. --- pkg/epp/flowcontrol/contracts/registry.go | 39 +- pkg/epp/flowcontrol/controller/config.go | 110 +++ pkg/epp/flowcontrol/controller/config_test.go | 135 +++ pkg/epp/flowcontrol/controller/controller.go | 391 ++++++++ .../flowcontrol/controller/controller_test.go | 845 ++++++++++++++++++ pkg/epp/flowcontrol/controller/doc.go | 110 +-- .../flowcontrol/controller/internal/doc.go | 37 +- .../flowcontrol/controller/internal/item.go | 154 ++-- .../controller/internal/item_test.go | 61 +- .../controller/internal/processor.go | 291 +++--- .../controller/internal/processor_test.go | 361 ++++---- .../policies/intraflow/dispatch/fcfs/fcfs.go | 35 +- .../intraflow/dispatch/fcfs/fcfs_test.go | 3 +- .../plugins/queue/listqueue/listqueue.go | 31 +- pkg/epp/flowcontrol/registry/config_test.go | 22 +- pkg/epp/flowcontrol/registry/connection.go | 6 +- pkg/epp/flowcontrol/registry/registry.go | 4 +- pkg/epp/flowcontrol/registry/registry_test.go | 40 +- pkg/epp/flowcontrol/registry/shard.go | 2 + pkg/epp/flowcontrol/registry/shard_test.go | 2 + pkg/epp/flowcontrol/types/errors.go | 11 +- pkg/epp/flowcontrol/types/request.go | 3 +- 22 files changed, 2043 insertions(+), 650 deletions(-) create mode 100644 pkg/epp/flowcontrol/controller/config.go create mode 100644 pkg/epp/flowcontrol/controller/config_test.go create mode 100644 pkg/epp/flowcontrol/controller/controller.go create mode 100644 pkg/epp/flowcontrol/controller/controller_test.go diff --git a/pkg/epp/flowcontrol/contracts/registry.go b/pkg/epp/flowcontrol/contracts/registry.go index e2c42fdbc..fe0b790b9 100644 --- a/pkg/epp/flowcontrol/contracts/registry.go +++ b/pkg/epp/flowcontrol/contracts/registry.go @@ -22,8 +22,8 @@ import ( ) // FlowRegistry is the complete interface for the global flow control plane. -// It composes the client-facing data path interface and the administrative interface. A concrete implementation of this -// interface is the single source of truth for all flow control state. +// It composes all role-based interfaces. A concrete implementation of this interface is the single source of truth for +// all flow control state. // // # Conformance: Implementations MUST be goroutine-safe. // @@ -48,22 +48,21 @@ import ( // 2. Capacity Partitioning: Global and per-band capacity limits must be uniformly partitioned across all Active // shards. type FlowRegistry interface { - FlowRegistryClient - FlowRegistryAdmin + FlowRegistryObserver + FlowRegistryDataPlane } -// FlowRegistryAdmin defines the administrative interface for the global control plane. -type FlowRegistryAdmin interface { - // Stats returns globally aggregated statistics for the entire `FlowRegistry`. +// FlowRegistryObserver defines the read-only, observation interface for the registry. +type FlowRegistryObserver interface { + // Stats returns a near-consistent snapshot globally aggregated statistics for the entire `FlowRegistry`. Stats() AggregateStats - // ShardStats returns a slice of statistics, one for each internal shard. + // ShardStats returns a near-consistent slice of statistics snapshots, one for each `RegistryShard`. ShardStats() []ShardStats } -// FlowRegistryClient defines the primary, client-facing interface for the registry. -// This is the interface that the `controller.FlowController`'s data path depends upon. -type FlowRegistryClient interface { +// FlowRegistryDataPlane defines the high-throughput, request-path interface for the registry. +type FlowRegistryDataPlane interface { // WithConnection manages a scoped, leased session for a given flow. // It is the primary and sole entry point for interacting with the data path. // @@ -90,9 +89,8 @@ type FlowRegistryClient interface { // Its purpose is to ensure that any interaction with the flow's state (e.g., accessing its shards and queues) occurs // safely while the flow is guaranteed to be protected from garbage collection. type ActiveFlowConnection interface { - // Shards returns a stable snapshot of accessors for all internal state shards (both Active and Draining). - // Consumers MUST check `RegistryShard.IsActive()` before routing new work to a shard from this slice. - Shards() []RegistryShard + // ActiveShards returns a stable snapshot of accessors for all Active internal state shards. + ActiveShards() []RegistryShard } // RegistryShard defines the interface for a single slice (shard) of the `FlowRegistry`'s state. @@ -139,7 +137,7 @@ type RegistryShard interface { // `controller.FlowController` worker's dispatch loop. AllOrderedPriorityLevels() []int - // Stats returns a snapshot of the statistics for this specific shard. + // Stats returns a near consistent snapshot of the shard's state. Stats() ShardStats } @@ -162,6 +160,7 @@ type ManagedQueue interface { } // AggregateStats holds globally aggregated statistics for the entire `FlowRegistry`. +// It is a read-only data object representing a near-consistent snapshot of the registry's state. type AggregateStats struct { // TotalCapacityBytes is the globally configured maximum total byte size limit across all priority bands and shards. TotalCapacityBytes uint64 @@ -173,8 +172,15 @@ type AggregateStats struct { PerPriorityBandStats map[int]PriorityBandStats } -// ShardStats holds statistics for a single internal shard within the `FlowRegistry`. +// ShardStats holds statistics and identifying information for a `RegistryShard` within the `FlowRegistry`. +// It is a read-only data object representing a near-consistent snapshot of the shard's state. type ShardStats struct { + // ID is the unique, stable identifier for this shard. + ID string + // IsActive indicates if the shard was accepting new work at the time this stats snapshot was generated. + // A value of `false` means the shard is in the process of being gracefully drained. + // Due to the concurrent nature of the system, this state could change immediately after the snapshot is taken. + IsActive bool // TotalCapacityBytes is the optional, maximum total byte size limit aggregated across all priority bands within this // shard. Its value represents the globally configured limit for the `FlowRegistry` partitioned for this shard. // The `controller.FlowController` enforces this limit in addition to any per-band capacity limits. @@ -192,6 +198,7 @@ type ShardStats struct { } // PriorityBandStats holds aggregated statistics for a single priority band. +// It is a read-only data object representing a near-consistent snapshot of the priority band's state. type PriorityBandStats struct { // Priority is the numerical priority level this struct describes. Priority int diff --git a/pkg/epp/flowcontrol/controller/config.go b/pkg/epp/flowcontrol/controller/config.go new file mode 100644 index 000000000..17c1429aa --- /dev/null +++ b/pkg/epp/flowcontrol/controller/config.go @@ -0,0 +1,110 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "fmt" + "time" +) + +const ( + // defaultExpiryCleanupInterval is the default frequency for scanning for expired items. + defaultExpiryCleanupInterval = 1 * time.Second + // defaultProcessorReconciliationInterval is the default frequency for the supervisor loop. + defaultProcessorReconciliationInterval = 5 * time.Second + // defaultEnqueueChannelBufferSize is the default size of a worker's incoming request buffer. + defaultEnqueueChannelBufferSize = 100 +) + +// Config holds the configuration for the `FlowController`. +type Config struct { + // DefaultRequestTTL is the default Time-To-Live applied to requests that do not + // specify their own TTL hint. + // Optional: If zero, no TTL is applied by default and we rely solely on request context cancellation. + DefaultRequestTTL time.Duration + + // ExpiryCleanupInterval is the interval at which each shard processor scans its queues for expired items. + // Optional: Defaults to `defaultExpiryCleanupInterval` (1 second). + ExpiryCleanupInterval time.Duration + + // ProcessorReconciliationInterval is the frequency at which the `FlowController`'s supervisor loop garbage collects + // stale workers. + // Optional: Defaults to `defaultProcessorReconciliationInterval` (5 seconds). + ProcessorReconciliationInterval time.Duration + + // EnqueueChannelBufferSize is the size of the buffered channel that accepts incoming requests for each shard + // processor. This buffer acts as a shock absorber, decoupling the high-frequency distributor from the processor's + // serial execution loop and allowing the system to handle short bursts of traffic without blocking. + // Optional: Defaults to `defaultEnqueueChannelBufferSize` (100). + EnqueueChannelBufferSize int +} + +// newConfig performs validation and initialization, returning a guaranteed-valid `Config` object. +// This is the required constructor for creating a new configuration. +// It does not mutate the input `cfg`. +func newConfig(cfg Config) (*Config, error) { + newCfg := cfg.deepCopy() + if err := newCfg.validateAndApplyDefaults(); err != nil { + return nil, err + } + return newCfg, nil +} + +// validateAndApplyDefaults checks the global configuration for validity and then mutates the receiver to populate any +// empty fields with system defaults. +func (c *Config) validateAndApplyDefaults() error { + // --- Validation --- + if c.DefaultRequestTTL < 0 { + return fmt.Errorf("DefaultRequestTTL cannot be negative, but got %v", c.DefaultRequestTTL) + } + if c.ExpiryCleanupInterval < 0 { + return fmt.Errorf("ExpiryCleanupInterval cannot be negative, but got %v", c.ExpiryCleanupInterval) + } + if c.ProcessorReconciliationInterval < 0 { + return fmt.Errorf("ProcessorReconciliationInterval cannot be negative, but got %v", + c.ProcessorReconciliationInterval) + } + if c.EnqueueChannelBufferSize < 0 { + return fmt.Errorf("EnqueueChannelBufferSize cannot be negative, but got %d", c.EnqueueChannelBufferSize) + } + + // --- Defaulting --- + if c.ExpiryCleanupInterval == 0 { + c.ExpiryCleanupInterval = defaultExpiryCleanupInterval + } + if c.ProcessorReconciliationInterval == 0 { + c.ProcessorReconciliationInterval = defaultProcessorReconciliationInterval + } + if c.EnqueueChannelBufferSize == 0 { + c.EnqueueChannelBufferSize = defaultEnqueueChannelBufferSize + } + return nil +} + +// deepCopy creates a deep copy of the `Config` object. +func (c *Config) deepCopy() *Config { + if c == nil { + return nil + } + newCfg := &Config{ + DefaultRequestTTL: c.DefaultRequestTTL, + ExpiryCleanupInterval: c.ExpiryCleanupInterval, + ProcessorReconciliationInterval: c.ProcessorReconciliationInterval, + EnqueueChannelBufferSize: c.EnqueueChannelBufferSize, + } + return newCfg +} diff --git a/pkg/epp/flowcontrol/controller/config_test.go b/pkg/epp/flowcontrol/controller/config_test.go new file mode 100644 index 000000000..a89db25ff --- /dev/null +++ b/pkg/epp/flowcontrol/controller/config_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewConfig(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + input Config + expectErr bool + expectedCfg Config + shouldDefault bool + }{ + { + name: "ValidConfig_NoChanges", + input: Config{ + DefaultRequestTTL: 10 * time.Second, + ExpiryCleanupInterval: 2 * time.Second, + ProcessorReconciliationInterval: 10 * time.Second, + EnqueueChannelBufferSize: 200, + }, + expectErr: false, + expectedCfg: Config{ + DefaultRequestTTL: 10 * time.Second, + ExpiryCleanupInterval: 2 * time.Second, + ProcessorReconciliationInterval: 10 * time.Second, + EnqueueChannelBufferSize: 200, + }, + }, + { + name: "EmptyConfig_ShouldApplyDefaults", + input: Config{}, + expectErr: false, + expectedCfg: Config{ + DefaultRequestTTL: 0, + ExpiryCleanupInterval: defaultExpiryCleanupInterval, + ProcessorReconciliationInterval: defaultProcessorReconciliationInterval, + EnqueueChannelBufferSize: defaultEnqueueChannelBufferSize, + }, + shouldDefault: true, + }, + { + name: "NegativeDefaultRequestTTL_Invalid", + input: Config{DefaultRequestTTL: -1}, + expectErr: true, + }, + { + name: "NegativeExpiryCleanupInterval_Invalid", + input: Config{ExpiryCleanupInterval: -1}, + expectErr: true, + }, + { + name: "NegativeProcessorReconciliationInterval_Invalid", + input: Config{ProcessorReconciliationInterval: -1}, + expectErr: true, + }, + { + name: "NegativeEnqueueChannelBufferSize_Invalid", + input: Config{EnqueueChannelBufferSize: -1}, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + originalInput := tc.input.deepCopy() + validatedCfg, err := newConfig(tc.input) + + if tc.expectErr { + require.Error(t, err, "expected an error but got nil") + assert.Nil(t, validatedCfg, "validatedCfg should be nil on error") + } else { + require.NoError(t, err, "expected no error but got: %v", err) + require.NotNil(t, validatedCfg, "validatedCfg should not be nil on success") + assert.Equal(t, tc.expectedCfg, *validatedCfg, "validatedCfg should match expected config") + + // Ensure the original config is not mutated. + assert.Equal(t, *originalInput, tc.input, "input config should not be mutated") + } + }) + } +} + +func TestConfig_DeepCopy(t *testing.T) { + t.Parallel() + + t.Run("ShouldReturnNil_ForNilReceiver", func(t *testing.T) { + t.Parallel() + var nilConfig *Config + assert.Nil(t, nilConfig.deepCopy(), "Deep copy of a nil config should be nil") + }) + + t.Run("ShouldCreateIdenticalButSeparateObject", func(t *testing.T) { + t.Parallel() + original := &Config{ + DefaultRequestTTL: 1 * time.Second, + ExpiryCleanupInterval: 2 * time.Second, + ProcessorReconciliationInterval: 3 * time.Second, + EnqueueChannelBufferSize: 4, + } + clone := original.deepCopy() + + require.NotSame(t, original, clone, "Clone should be a new object in memory") + assert.Equal(t, *original, *clone, "Cloned object should have identical values") + + // Modify the clone and ensure the original is unchanged. + clone.DefaultRequestTTL = 99 * time.Second + assert.NotEqual(t, original.DefaultRequestTTL, clone.DefaultRequestTTL, + "Original should not be mutated after clone is changed") + }) +} diff --git a/pkg/epp/flowcontrol/controller/controller.go b/pkg/epp/flowcontrol/controller/controller.go new file mode 100644 index 000000000..e25eda969 --- /dev/null +++ b/pkg/epp/flowcontrol/controller/controller.go @@ -0,0 +1,391 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package controller contains the implementation of the `FlowController` engine. +// +// The FlowController is the central processing engine of the Flow Control system. It is a sharded, high-throughput +// component responsible for managing the lifecycle of all incoming requests. It achieves this by acting as a stateless +// supervisor that orchestrates a pool of stateful workers (`internal.ShardProcessor`), distributing incoming requests +// among them using a sophisticated load-balancing algorithm. +package controller + +import ( + "cmp" + "context" + "errors" + "fmt" + "slices" + "sync" + "time" + + "github.com/go-logr/logr" + "k8s.io/utils/clock" + + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/controller/internal" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" + logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging" +) + +// registryClient defines the minimal interface that the `FlowController` needs to interact with the `FlowRegistry`. +type registryClient interface { + contracts.FlowRegistryObserver + contracts.FlowRegistryDataPlane +} + +// shardProcessor is the minimal internal interface that the `FlowController` requires from its workers. +// This abstraction allows for the injection of mock processors during testing. +type shardProcessor interface { + Run(ctx context.Context) + Submit(item *internal.FlowItem) error + SubmitOrBlock(ctx context.Context, item *internal.FlowItem) error +} + +// shardProcessorFactory defines the signature for a function that creates a `shardProcessor`. +// This enables dependency injection for testing. +type shardProcessorFactory func( + shard contracts.RegistryShard, + dispatchFilter internal.BandFilter, + clock clock.Clock, + expiryCleanupInterval time.Duration, + enqueueChannelBufferSize int, + logger logr.Logger, +) shardProcessor + +var _ shardProcessor = &internal.ShardProcessor{} + +// managedWorker holds the state for a single supervised worker. +type managedWorker struct { + processor shardProcessor + cancel context.CancelFunc +} + +// FlowController is the central, high-throughput engine of the Flow Control system. +// It is designed as a stateless distributor that orchestrates a pool of stateful workers (`internal.ShardProcessor`), +// following a "supervisor-worker" pattern. +// +// The controller's `Run` loop executes periodically, acting as a garbage collector that keeps the pool of running +// workers synchronized with the dynamic shard topology of the `FlowRegistry`. +type FlowController struct { + // --- Immutable dependencies (set at construction) --- + + config Config + registry registryClient + saturationDetector contracts.SaturationDetector + clock clock.WithTicker + logger logr.Logger + shardProcessorFactory shardProcessorFactory + + // --- Lifecycle state --- + + // parentCtx is the root context for the controller's lifecycle, established when `Run` is called. + // It is the parent for all long-lived worker goroutines. + parentCtx context.Context + + // --- Concurrent state --- + + // workers is a highly concurrent map storing the `managedWorker` for each shard. + // It is the controller's source of truth for the worker pool. + // The key is the shard ID (`string`), and the value is a `*managedWorker`. + workers sync.Map + + wg sync.WaitGroup +} + +// flowControllerOption is a function that applies a configuration change to a `FlowController`. +// test-only +type flowControllerOption func(*FlowController) + +// NewFlowController creates a new `FlowController` instance. +func NewFlowController( + ctx context.Context, + config Config, + registry contracts.FlowRegistry, + sd contracts.SaturationDetector, + logger logr.Logger, + opts ...flowControllerOption, +) (*FlowController, error) { + validatedConfig, err := newConfig(config) + if err != nil { + return nil, fmt.Errorf("invalid flow controller configuration: %w", err) + } + + fc := &FlowController{ + config: *validatedConfig, + registry: registry, + saturationDetector: sd, + clock: clock.RealClock{}, + logger: logger.WithName("flow-controller"), + parentCtx: ctx, + } + + // Use the real shard processor implementation by default. + fc.shardProcessorFactory = func( + shard contracts.RegistryShard, + dispatchFilter internal.BandFilter, + clock clock.Clock, + expiryCleanupInterval time.Duration, + enqueueChannelBufferSize int, + logger logr.Logger, + ) shardProcessor { + return internal.NewShardProcessor( + shard, + dispatchFilter, + clock, + expiryCleanupInterval, + enqueueChannelBufferSize, + logger) + } + + for _, opt := range opts { + opt(fc) + } + + go fc.run(ctx) + return fc, nil +} + +// run starts the `FlowController`'s main reconciliation loop. +// This loop is responsible for garbage collecting workers whose shards no longer exist in the registry. +// This method blocks until the provided context is cancelled and ALL worker goroutines have fully terminated. +func (fc *FlowController) run(ctx context.Context) { + fc.logger.Info("Starting FlowController reconciliation loop.") + defer fc.logger.Info("FlowController reconciliation loop stopped.") + + ticker := fc.clock.NewTicker(fc.config.ProcessorReconciliationInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + fc.shutdown() + return + case <-ticker.C(): + fc.reconcileProcessors() + } + } +} + +// EnqueueAndWait is the primary, synchronous entry point to the Flow Control system. It submits a request and blocks +// until the request reaches a terminal outcome (dispatched, rejected, or evicted). +// +// # Design Rationale: The Synchronous Model +// +// This blocking model is deliberately chosen for its simplicity and robustness, especially in the context of Envoy +// External Processing (`ext_proc`), which operates on a stream-based protocol. +// +// - `ext_proc` Alignment: A single goroutine typically manages the stream for a given HTTP request. +// `EnqueueAndWait` fits this perfectly: the request-handling goroutine calls it, blocks, and upon return, has a +// definitive outcome to act upon. +// - Simplified State Management: The state of a "waiting" request is implicitly managed by the blocked goroutine's +// stack and its `context.Context`. The system only needs to signal this specific goroutine to unblock it. +// - Direct Backpressure: If queues are full, `EnqueueAndWait` returns an error immediately, providing direct +// backpressure to the caller. +func (fc *FlowController) EnqueueAndWait(req types.FlowControlRequest) (types.QueueOutcome, error) { + if req == nil { + return types.QueueOutcomeRejectedOther, errors.New("request cannot be nil") + } + effectiveTTL := req.InitialEffectiveTTL() + if effectiveTTL <= 0 { + effectiveTTL = fc.config.DefaultRequestTTL + } + enqueueTime := fc.clock.Now() + + for { + select { + case <-fc.parentCtx.Done(): + return types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning) + default: + // The controller is running, proceed. + } + + // We must create a fresh `FlowItem` on each attempt since finalization is idempotent. + // However, we use the original, preserved `enqueueTime`. + item := internal.NewItem(req, effectiveTTL, enqueueTime) + if outcome, err := fc.distributeRequest(item); err != nil { + return outcome, fmt.Errorf("%w: %w", types.ErrRejected, err) + } + + // Block until the request is finalized (dispatched, rejected, or evicted). + // The finalization logic internally monitors for context cancellation and TTL expiry. + finalState := <-item.Done() + if errors.Is(finalState.Err, contracts.ErrShardDraining) { + fc.logger.V(logutil.DEBUG).Info("Shard is draining, retrying request", "requestID", req.ID()) + // Benign race with the chosen `contracts.RegistryShard` becoming Draining post selection but before the item was + // enqueued into its respective `contracts.ManagedQueue`. Simply try again. + continue + } + + return finalState.Outcome, finalState.Err + } +} + +// distributeRequest implements a flow-aware, two-phase "Join-Shortest-Queue-by-Bytes" (JSQ-Bytes) distribution strategy +// with graceful backpressure. It selects the optimal worker for a given item and attempts to submit it. +// +// The algorithm operates as follows: +// 1. Candidate Selection: It identifies all Active shards for the item's flow and ranks them by the current byte size +// of that flow's queue, from least to most loaded. +// 2. Phase 1 (Non-blocking Fast Failover): It iterates through the ranked candidates and attempts a non-blocking +// submission. The first successful submission wins. +// 3. Phase 2 (Blocking Fallback): If all non-blocking attempts fail, it performs a single blocking submission to the +// least-loaded candidate, providing backpressure. +func (fc *FlowController) distributeRequest(item *internal.FlowItem) (types.QueueOutcome, error) { + key := item.OriginalRequest().FlowKey() + reqID := item.OriginalRequest().ID() + type candidate struct { + processor shardProcessor + shardID string + byteSize uint64 + } + var candidates []candidate + err := fc.registry.WithConnection(key, func(conn contracts.ActiveFlowConnection) error { + shards := conn.ActiveShards() + candidates = make([]candidate, len(shards)) + for i, shard := range shards { + worker := fc.getOrStartWorker(shard) + mq, err := shard.ManagedQueue(key) + if err != nil { + panic(fmt.Sprintf("invariant violation: ManagedQueue for leased flow %s failed on shard %s: %v", + key, shard.ID(), err)) + } + candidates[i] = candidate{worker.processor, shard.ID(), mq.ByteSize()} + } + return nil + }) + if err != nil { + return types.QueueOutcomeRejectedOther, fmt.Errorf("failed to acquire lease for request %q (flow %s): %w", + reqID, key, err) + } + + if len(candidates) == 0 { + return types.QueueOutcomeRejectedCapacity, fmt.Errorf("no viable Active shards available for request %q (flow %s)", + reqID, key) + } + + slices.SortFunc(candidates, func(a, b candidate) int { + return cmp.Compare(a.byteSize, b.byteSize) + }) + + // --- Phase 1: Fast, non-blocking failover attempt --- + for _, c := range candidates { + if err := c.processor.Submit(item); err == nil { + return types.QueueOutcomeNotYetFinalized, nil // Success + } + fc.logger.V(logutil.DEBUG).Info("Processor busy during fast failover, trying next candidate", + "shardID", c.shardID, "requestID", reqID) + } + + // --- Phase 2: All processors busy. Attempt a single blocking send to the best candidate. --- + bestCandidate := candidates[0] + fc.logger.V(logutil.DEBUG).Info("All processors busy, attempting blocking submit to best candidate", + "shardID", bestCandidate.shardID, "requestID", reqID, "queueByteSize", bestCandidate.byteSize) + + err = bestCandidate.processor.SubmitOrBlock(item.OriginalRequest().Context(), item) + if err != nil { + // If even the blocking attempt fails (e.g., context cancelled or processor shut down), the request is definitively + // rejected. + return types.QueueOutcomeRejectedCapacity, fmt.Errorf( + "all viable shard processors are at capacity for request %q (flow %s): %w", reqID, key, err) + } + return types.QueueOutcomeNotYetFinalized, nil +} + +// getOrStartWorker implements the lazy-loading and startup of shard processors. +// It attempts to retrieve an existing worker for a shard. If one doesn't exist, it constructs a new worker and attempts +// to register it atomically. The worker's processor goroutine is only started *after* it has successfully been +// registered, preventing race conditions where multiple goroutines create and start the same worker. +func (fc *FlowController) getOrStartWorker(shard contracts.RegistryShard) *managedWorker { + if w, ok := fc.workers.Load(shard.ID()); ok { + return w.(*managedWorker) + } + + // Construct a new worker, but do not start its processor goroutine yet. + processorCtx, cancel := context.WithCancel(fc.parentCtx) + dispatchFilter := internal.NewSaturationFilter(fc.saturationDetector) + processor := fc.shardProcessorFactory( + shard, + dispatchFilter, + fc.clock, + fc.config.ExpiryCleanupInterval, + fc.config.EnqueueChannelBufferSize, + fc.logger.WithValues("shardID", shard.ID()), + ) + newWorker := &managedWorker{ + processor: processor, + cancel: cancel, + } + + // Atomically load or store. This is the critical step for preventing race conditions. + actual, loaded := fc.workers.LoadOrStore(shard.ID(), newWorker) + if loaded { + // Another goroutine beat us to it. The `newWorker` we created was not stored. + // We must cancel the context we created for it to prevent a leak, but we do not need to do anything else, as its + // processor was never started. + cancel() + return actual.(*managedWorker) + } + + // We won the race. The `newWorker` was successfully stored. + // Now, and only now, do we start the processor's long-running goroutine. + fc.wg.Add(1) + go func() { + defer fc.wg.Done() + processor.Run(processorCtx) + }() + + return newWorker +} + +// reconcileProcessors is the supervisor's core garbage collection loop. +// It fetches the current list of Active shards from the registry and removes any workers whose corresponding shards +// have been fully drained and garbage collected by the registry. +func (fc *FlowController) reconcileProcessors() { + stats := fc.registry.ShardStats() + shards := make(map[string]struct{}, len(stats)) // `map[shardID] -> isActive` + for _, s := range stats { + shards[s.ID] = struct{}{} + } + + fc.workers.Range(func(key, value any) bool { + shardID := key.(string) + worker := value.(*managedWorker) + + // GC check: Is the shard no longer in the registry at all? + if _, exists := shards[shardID]; !exists { + fc.logger.Info("Stale worker detected for GC'd shard, shutting down.", "shardID", shardID) + worker.cancel() + fc.workers.Delete(shardID) + } + return true + }) +} + +// shutdown gracefully terminates all running `shardProcessor` goroutines. +// It signals all workers to stop and waits for them to complete their shutdown procedures. +func (fc *FlowController) shutdown() { + fc.logger.Info("Shutting down FlowController and all shard processors.") + fc.workers.Range(func(key, value any) bool { + shardID := key.(string) + worker := value.(*managedWorker) + fc.logger.V(logutil.VERBOSE).Info("Sending shutdown signal to processor", "shardID", shardID) + worker.cancel() + return true + }) + + fc.wg.Wait() + fc.logger.Info("All shard processors have shut down.") +} diff --git a/pkg/epp/flowcontrol/controller/controller_test.go b/pkg/epp/flowcontrol/controller/controller_test.go new file mode 100644 index 000000000..511bae8ce --- /dev/null +++ b/pkg/epp/flowcontrol/controller/controller_test.go @@ -0,0 +1,845 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/utils/clock" + testclock "k8s.io/utils/clock/testing" + + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts/mocks" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/controller/internal" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework" + frameworkmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/framework/mocks" + "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" + typesmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types/mocks" +) + +// --- Test Harness & Fixtures --- + +// withClock returns a test-only option to inject a clock. +// test-only +func withClock(c clock.WithTicker) flowControllerOption { + return func(fc *FlowController) { + fc.clock = c + } +} + +// withRegistryClient returns a test-only option to inject a mock or fake registry client. +// test-only +func withRegistryClient(client registryClient) flowControllerOption { + return func(fc *FlowController) { + fc.registry = client + } +} + +// withShardProcessorFactory returns a test-only option to inject a processor factory. +// test-only +func withShardProcessorFactory(factory shardProcessorFactory) flowControllerOption { + return func(fc *FlowController) { + fc.shardProcessorFactory = factory + } +} + +// testHarness holds the `FlowController` and its dependencies under test. +type testHarness struct { + fc *FlowController + cfg Config + mockRegistry *mockRegistryClient + mockDetector *mocks.MockSaturationDetector + mockClock *testclock.FakeClock + mockProcessorFactory *mockShardProcessorFactory +} + +// newUnitHarness creates a test environment with a mock processor factory, suitable for focused unit tests of the +// controller's logic. It starts the controller's run loop and returns a cancel function to stop it. +func newUnitHarness(t *testing.T, ctx context.Context, cfg Config, registry *mockRegistryClient) *testHarness { + t.Helper() + mockDetector := &mocks.MockSaturationDetector{} + mockClock := testclock.NewFakeClock(time.Now()) + mockProcessorFactory := &mockShardProcessorFactory{ + processors: make(map[string]*mockShardProcessor), + } + opts := []flowControllerOption{ + withRegistryClient(registry), + withClock(mockClock), + withShardProcessorFactory(mockProcessorFactory.new), + } + fc, err := NewFlowController(ctx, cfg, registry, mockDetector, logr.Discard(), opts...) + require.NoError(t, err, "failed to create FlowController for unit test harness") + + h := &testHarness{ + fc: fc, + cfg: cfg, + mockRegistry: registry, + mockDetector: mockDetector, + mockClock: mockClock, + mockProcessorFactory: mockProcessorFactory, + } + return h +} + +// newIntegrationHarness creates a test environment that uses real `ShardProcessor`s, suitable for integration tests +// validating the controller-processor interaction. +func newIntegrationHarness(t *testing.T, ctx context.Context, cfg Config, registry *mockRegistryClient) *testHarness { + t.Helper() + mockDetector := &mocks.MockSaturationDetector{} + mockClock := testclock.NewFakeClock(time.Now()) + opts := []flowControllerOption{ + withRegistryClient(registry), + withClock(mockClock), + } + fc, err := NewFlowController(ctx, cfg, registry, mockDetector, logr.Discard(), opts...) + require.NoError(t, err, "failed to create FlowController for integration test harness") + + h := &testHarness{ + fc: fc, + cfg: cfg, + mockRegistry: registry, + mockDetector: mockDetector, + mockClock: mockClock, + } + return h +} + +// mockActiveFlowConnection is a local mock for the `contracts.ActiveFlowConnection` interface. +type mockActiveFlowConnection struct { + contracts.ActiveFlowConnection + ActiveShardsV []contracts.RegistryShard +} + +func (m *mockActiveFlowConnection) ActiveShards() []contracts.RegistryShard { + return m.ActiveShardsV +} + +// mockRegistryClient is a mock for the private `registryClient` interface. +type mockRegistryClient struct { + contracts.FlowRegistryObserver + contracts.FlowRegistryDataPlane + WithConnectionFunc func(key types.FlowKey, fn func(conn contracts.ActiveFlowConnection) error) error + ShardStatsFunc func() []contracts.ShardStats +} + +func (m *mockRegistryClient) WithConnection( + key types.FlowKey, + fn func(conn contracts.ActiveFlowConnection) error, +) error { + if m.WithConnectionFunc != nil { + return m.WithConnectionFunc(key, fn) + } + return fn(&mockActiveFlowConnection{}) +} + +func (m *mockRegistryClient) ShardStats() []contracts.ShardStats { + if m.ShardStatsFunc != nil { + return m.ShardStatsFunc() + } + return nil +} + +// mockShardProcessor is a mock for the internal `shardProcessor` interface. +type mockShardProcessor struct { + SubmitFunc func(item *internal.FlowItem) error + SubmitOrBlockFunc func(ctx context.Context, item *internal.FlowItem) error + runCtx context.Context + runCtxMu sync.RWMutex + runStarted chan struct{} +} + +func (m *mockShardProcessor) Submit(item *internal.FlowItem) error { + if m.SubmitFunc != nil { + return m.SubmitFunc(item) + } + return nil +} + +func (m *mockShardProcessor) SubmitOrBlock(ctx context.Context, item *internal.FlowItem) error { + if m.SubmitOrBlockFunc != nil { + return m.SubmitOrBlockFunc(ctx, item) + } + return nil +} + +func (m *mockShardProcessor) Run(ctx context.Context) { + m.runCtxMu.Lock() + m.runCtx = ctx + m.runCtxMu.Unlock() + if m.runStarted != nil { + close(m.runStarted) + } + <-ctx.Done() +} + +func (m *mockShardProcessor) Context() context.Context { + m.runCtxMu.RLock() + defer m.runCtxMu.RUnlock() + return m.runCtx +} + +// mockShardProcessorFactory allows tests to inject specific `mockShardProcessor` instances. +type mockShardProcessorFactory struct { + mu sync.Mutex + processors map[string]*mockShardProcessor +} + +func (f *mockShardProcessorFactory) new( + shard contracts.RegistryShard, + _ internal.BandFilter, + _ clock.Clock, + _ time.Duration, + _ int, + _ logr.Logger, +) shardProcessor { + f.mu.Lock() + defer f.mu.Unlock() + if proc, ok := f.processors[shard.ID()]; ok { + return proc + } + // Return a default mock processor if one is not registered. + return &mockShardProcessor{} +} + +// stubManagedQueue is a simple stub for the `contracts.ManagedQueue` interface. +type stubManagedQueue struct { + contracts.ManagedQueue + byteSizeV uint64 +} + +func (s *stubManagedQueue) ByteSize() uint64 { return s.byteSizeV } + +func (s *stubManagedQueue) FlowQueueAccessor() framework.FlowQueueAccessor { + return &frameworkmocks.MockFlowQueueAccessor{ByteSizeV: s.byteSizeV} +} + +// mockShardBuilder is a fixture to declaratively build mock `contracts.RegistryShard` for tests. +type mockShardBuilder struct { + id string + byteSize uint64 +} + +func newMockShard(id string) *mockShardBuilder { + return &mockShardBuilder{id: id} +} + +func (b *mockShardBuilder) withByteSize(size uint64) *mockShardBuilder { + b.byteSize = size + return b +} + +func (b *mockShardBuilder) build() contracts.RegistryShard { + return &mocks.MockRegistryShard{ + IDFunc: func() string { return b.id }, + ManagedQueueFunc: func(_ types.FlowKey) (contracts.ManagedQueue, error) { + return &stubManagedQueue{byteSizeV: b.byteSize}, nil + }, + } +} + +var defaultFlowKey = types.FlowKey{ID: "test-flow", Priority: 100} + +func newTestRequest(ctx context.Context, key types.FlowKey) *typesmocks.MockFlowControlRequest { + return &typesmocks.MockFlowControlRequest{ + Ctx: ctx, + FlowKeyV: key, + ByteSizeV: 100, + IDV: "req-" + key.ID, + } +} + +// --- Test Cases --- + +func TestNewFlowController(t *testing.T) { + t.Parallel() + + t.Run("ErrorOnInvalidConfig", func(t *testing.T) { + t.Parallel() + invalidCfg := Config{ProcessorReconciliationInterval: -1 * time.Second} + _, err := NewFlowController( + context.Background(), + invalidCfg, + &mockRegistryClient{}, + &mocks.MockSaturationDetector{}, + logr.Discard(), + ) + require.Error(t, err, "NewFlowController must return an error for invalid configuration") + }) +} + +func TestFlowController_EnqueueAndWait(t *testing.T) { + t.Parallel() + + t.Run("Rejections", func(t *testing.T) { + t.Parallel() + + t.Run("OnNilRequest", func(t *testing.T) { + t.Parallel() + h := newUnitHarness(t, t.Context(), Config{}, &mockRegistryClient{}) + + outcome, err := h.fc.EnqueueAndWait(nil) + require.Error(t, err, "EnqueueAndWait must reject a nil request") + assert.Equal(t, "request cannot be nil", err.Error()) + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "outcome should be QueueOutcomeRejectedOther") + }) + + t.Run("OnControllerShutdown", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(t.Context()) + h := newUnitHarness(t, ctx, Config{}, &mockRegistryClient{}) + cancel() // Immediately stop the controller. + + req := newTestRequest(context.Background(), defaultFlowKey) + outcome, err := h.fc.EnqueueAndWait(req) + require.Error(t, err, "EnqueueAndWait must reject requests if controller is not running") + assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") + assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "error should wrap ErrFlowControllerNotRunning") + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "outcome should be QueueOutcomeRejectedOther") + }) + + t.Run("OnNoShardsAvailable", func(t *testing.T) { + t.Parallel() + h := newUnitHarness(t, t.Context(), Config{}, &mockRegistryClient{}) + + req := newTestRequest(context.Background(), defaultFlowKey) + outcome, err := h.fc.EnqueueAndWait(req) + require.Error(t, err, "EnqueueAndWait must reject requests if no shards are available") + assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") + assert.Equal(t, types.QueueOutcomeRejectedCapacity, outcome, "outcome should be QueueOutcomeRejectedCapacity") + }) + + t.Run("OnRegistryConnectionError", func(t *testing.T) { + t.Parallel() + mockRegistry := &mockRegistryClient{} + h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + + expectedErr := errors.New("connection failed") + mockRegistry.WithConnectionFunc = func( + _ types.FlowKey, + _ func(conn contracts.ActiveFlowConnection) error, + ) error { + return expectedErr + } + + req := newTestRequest(context.Background(), defaultFlowKey) + outcome, err := h.fc.EnqueueAndWait(req) + require.Error(t, err, "EnqueueAndWait must reject requests if registry connection fails") + assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") + assert.ErrorIs(t, err, expectedErr, "error should wrap the underlying connection error") + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "outcome should be QueueOutcomeRejectedOther") + }) + + t.Run("PanicsOnManagedQueueError", func(t *testing.T) { + t.Parallel() + mockRegistry := &mockRegistryClient{} + h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + + faultyShard := &mocks.MockRegistryShard{ + IDFunc: func() string { return "faulty-shard" }, + ManagedQueueFunc: func(_ types.FlowKey) (contracts.ManagedQueue, error) { + return nil, errors.New("queue retrieval failed") + }, + } + mockRegistry.WithConnectionFunc = func( + _ types.FlowKey, + fn func(conn contracts.ActiveFlowConnection) error, + ) error { + return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{faultyShard}}) + } + + req := newTestRequest(context.Background(), defaultFlowKey) + assert.Panics(t, func() { + _, _ = h.fc.EnqueueAndWait(req) + }, "EnqueueAndWait did not panic as expected on a ManagedQueue error") + }) + }) + + t.Run("Distribution", func(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + shards []contracts.RegistryShard + setupProcessors func(t *testing.T, h *testHarness) + expectedOutcome types.QueueOutcome + expectErr bool + }{ + { + name: "SubmitSucceeds_NonBlocking_WithSingleActiveShard", + shards: []contracts.RegistryShard{newMockShard("shard-A").build()}, + setupProcessors: func(t *testing.T, h *testHarness) { + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(item *internal.FlowItem) error { + go item.Finalize(types.QueueOutcomeDispatched, nil) + return nil + }, + } + }, + expectedOutcome: types.QueueOutcomeDispatched, + }, + { + name: "DistributesToLeastLoadedShard_WithMultipleActiveShards", + shards: []contracts.RegistryShard{ + newMockShard("shard-A").withByteSize(1000).build(), + newMockShard("shard-B").withByteSize(100).build(), + }, + setupProcessors: func(t *testing.T, h *testHarness) { + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(_ *internal.FlowItem) error { + t.Error("Submit was called on the more loaded shard (shard-A), which is incorrect") + return internal.ErrProcessorBusy + }, + } + h.mockProcessorFactory.processors["shard-B"] = &mockShardProcessor{ + SubmitFunc: func(item *internal.FlowItem) error { + go item.Finalize(types.QueueOutcomeDispatched, nil) + return nil + }, + } + }, + expectedOutcome: types.QueueOutcomeDispatched, + }, + { + name: "SubmitSucceeds_AfterBlocking_WithAllProcessorsBusy", + shards: []contracts.RegistryShard{ + newMockShard("shard-A").withByteSize(1000).build(), + newMockShard("shard-B").withByteSize(100).build(), + }, + setupProcessors: func(t *testing.T, h *testHarness) { + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + } + h.mockProcessorFactory.processors["shard-B"] = &mockShardProcessor{ + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + SubmitOrBlockFunc: func(_ context.Context, item *internal.FlowItem) error { + go item.Finalize(types.QueueOutcomeDispatched, nil) + return nil + }, + } + }, + expectedOutcome: types.QueueOutcomeDispatched, + }, + { + name: "Rejects_AfterBlocking_WithAllProcessorsRemainingBusy", + shards: []contracts.RegistryShard{newMockShard("shard-A").build()}, + setupProcessors: func(t *testing.T, h *testHarness) { + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + SubmitOrBlockFunc: func(_ context.Context, _ *internal.FlowItem) error { return context.DeadlineExceeded }, + } + }, + expectedOutcome: types.QueueOutcomeRejectedCapacity, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + mockRegistry := &mockRegistryClient{} + h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + mockRegistry.WithConnectionFunc = func( + _ types.FlowKey, + fn func(conn contracts.ActiveFlowConnection) error, + ) error { + return fn(&mockActiveFlowConnection{ActiveShardsV: tc.shards}) + } + tc.setupProcessors(t, h) + outcome, err := h.fc.EnqueueAndWait(newTestRequest(context.Background(), defaultFlowKey)) + if tc.expectErr { + require.Error(t, err, "expected an error but got nil") + } else { + require.NoError(t, err, "expected no error but got: %v", err) + } + assert.Equal(t, tc.expectedOutcome, outcome, "outcome did not match expected value") + }) + } + }) + + t.Run("Retry", func(t *testing.T) { + t.Parallel() + + t.Run("Rejects_OnRequestContextCancelledWhileBlocking", func(t *testing.T) { + t.Parallel() + mockRegistry := &mockRegistryClient{ + WithConnectionFunc: func( + _ types.FlowKey, + fn func(conn contracts.ActiveFlowConnection, + ) error) error { + return fn(&mockActiveFlowConnection{ + ActiveShardsV: []contracts.RegistryShard{newMockShard("shard-A").build()}, + }) + }, + } + h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(_ *internal.FlowItem) error { return internal.ErrProcessorBusy }, + SubmitOrBlockFunc: func(ctx context.Context, _ *internal.FlowItem) error { <-ctx.Done(); return ctx.Err() }, + } + reqCtx, cancelReq := context.WithCancel(context.Background()) + go func() { time.Sleep(50 * time.Millisecond); cancelReq() }() + + outcome, err := h.fc.EnqueueAndWait(newTestRequest(reqCtx, defaultFlowKey)) + + require.Error(t, err, "EnqueueAndWait must fail when context is cancelled during a blocking submit") + assert.ErrorIs(t, err, types.ErrRejected, "error should wrap ErrRejected") + assert.ErrorIs(t, err, context.Canceled, "error should wrap the underlying ctx.Err()") + assert.Equal(t, types.QueueOutcomeRejectedCapacity, outcome, "outcome should be QueueOutcomeRejectedCapacity") + }) + + t.Run("RetriesAndSucceeds_OnProcessorReportsShardDraining", func(t *testing.T) { + t.Parallel() + var callCount atomic.Int32 + mockRegistry := &mockRegistryClient{ + WithConnectionFunc: func( + _ types.FlowKey, + fn func(conn contracts.ActiveFlowConnection) error, + ) error { + attempt := callCount.Add(1) + shardA := newMockShard("shard-A").withByteSize(100).build() + shardB := newMockShard("shard-B").withByteSize(1000).build() + if attempt == 1 { + return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{shardA, shardB}}) + } + return fn(&mockActiveFlowConnection{ActiveShardsV: []contracts.RegistryShard{shardB}}) + }, + } + h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + h.mockProcessorFactory.processors["shard-A"] = &mockShardProcessor{ + SubmitFunc: func(item *internal.FlowItem) error { + go item.Finalize(types.QueueOutcomeRejectedOther, contracts.ErrShardDraining) + return nil + }, + } + h.mockProcessorFactory.processors["shard-B"] = &mockShardProcessor{ + SubmitFunc: func(item *internal.FlowItem) error { + go item.Finalize(types.QueueOutcomeDispatched, nil) + return nil + }, + } + + outcome, err := h.fc.EnqueueAndWait(newTestRequest(context.Background(), defaultFlowKey)) + require.NoError(t, err, "EnqueueAndWait must succeed after retrying on a healthy shard") + assert.Equal(t, types.QueueOutcomeDispatched, outcome, "outcome should be QueueOutcomeDispatched") + assert.Equal(t, int32(2), callCount.Load(), "registry must be consulted for Active shards on each retry attempt") + }) + }) +} + +func TestFlowController_Lifecycle(t *testing.T) { + t.Parallel() + + t.Run("Reconciliation", func(t *testing.T) { + t.Parallel() + + mockRegistry := &mockRegistryClient{ + // Configure the mock registry to report the new state without the stale shard. + ShardStatsFunc: func() []contracts.ShardStats { + return []contracts.ShardStats{{ID: "shard-A"}} + }} + h := newUnitHarness(t, t.Context(), Config{}, mockRegistry) + + // Pre-populate the controller with initial workers. + initialShards := []string{"shard-A", "stale-shard"} + for _, shardID := range initialShards { + currentShardID := shardID + h.mockProcessorFactory.processors[currentShardID] = &mockShardProcessor{runStarted: make(chan struct{})} + shard := &mocks.MockRegistryShard{IDFunc: func() string { return currentShardID }} + h.fc.getOrStartWorker(shard) + } + require.Len(t, h.mockProcessorFactory.processors, 2, "pre-condition: initial workers not set up correctly") + + // Wait for all workers to have started and set their contexts before proceeding with the test. + for id, p := range h.mockProcessorFactory.processors { + proc := p + select { + case <-proc.runStarted: + // Success + case <-time.After(2 * time.Second): + t.Fatalf("timed out waiting for worker %s to start", id) + } + } + + // Manually trigger the reconciliation loop logic. + h.fc.reconcileProcessors() + + t.Run("StaleWorkerIsCancelled", func(t *testing.T) { + staleProc := h.mockProcessorFactory.processors["stale-shard"] + require.NotNil(t, staleProc.Context(), "precondition: stale processor context should have been captured") + select { + case <-staleProc.Context().Done(): + // Success + case <-time.After(100 * time.Millisecond): + t.Error("context of removed worker must be cancelled") + } + }) + + t.Run("ActiveWorkerIsNotCancelled", func(t *testing.T) { + activeProc := h.mockProcessorFactory.processors["shard-A"] + require.NotNil(t, activeProc.Context(), "precondition: active processor context should have been captured") + select { + case <-activeProc.Context().Done(): + t.Error("context of remaining worker must not be cancelled") + default: + // Success + } + }) + + t.Run("WorkerMapIsUpdated", func(t *testing.T) { + _, ok := h.fc.workers.Load("stale-shard") + assert.False(t, ok, "stale worker must be removed from the controller's map") + _, ok = h.fc.workers.Load("shard-A") + assert.True(t, ok, "active worker must remain in the controller's map") + }) + }) + + t.Run("Reconciliation_IsTriggeredByTicker", func(t *testing.T) { + t.Parallel() + reconciliationInterval := 10 * time.Second + mockRegistry := &mockRegistryClient{} + + var reconcileCount atomic.Int32 + mockRegistry.ShardStatsFunc = func() []contracts.ShardStats { + reconcileCount.Add(1) + return nil + } + + h := newUnitHarness(t, t.Context(), Config{ProcessorReconciliationInterval: reconciliationInterval}, mockRegistry) + + // Wait for the reconciliation loop to start and create the ticker. + // This prevents a race where the clock is stepped before the ticker is registered. + require.Eventually(t, h.mockClock.HasWaiters, time.Second, 10*time.Millisecond, "ticker was not created") + + // Advance the clock to trigger the next reconciliation. + h.mockClock.Step(reconciliationInterval) + + assert.Eventually(t, func() bool { + return reconcileCount.Load() == 1 + }, time.Second, 10*time.Millisecond, "reconciliation was not triggered by the ticker") + + // Advance the clock again to ensure it continues to fire. + h.mockClock.Step(reconciliationInterval) + assert.Eventually(t, func() bool { + return reconcileCount.Load() == 2 + }, time.Second, 10*time.Millisecond, "reconciliation did not fire on the second tick") + }) + + t.Run("WorkerCreationRace", func(t *testing.T) { + t.Parallel() + + // This test requires manual control over the shard processor factory to deterministically create a race. + factoryEntered := make(chan *mockShardProcessor, 2) + continueFactory := make(chan struct{}) + + h := newUnitHarness(t, t.Context(), Config{}, &mockRegistryClient{}) + h.fc.shardProcessorFactory = func( + shard contracts.RegistryShard, + _ internal.BandFilter, + _ clock.Clock, + _ time.Duration, + _ int, + _ logr.Logger, + ) shardProcessor { + // This factory function will be called by `startNewWorker`. + // We use channels to pause execution here, allowing two goroutines to enter this function before one "wins" + // the `LoadOrStore` race. + proc := &mockShardProcessor{runStarted: make(chan struct{})} + factoryEntered <- proc + <-continueFactory + return proc + } + + shard := newMockShard("race-shard").build() + var wg sync.WaitGroup + wg.Add(2) + + // Start two goroutines that will race to create the same worker. + go func() { + defer wg.Done() + h.fc.getOrStartWorker(shard) + }() + go func() { + defer wg.Done() + h.fc.getOrStartWorker(shard) + }() + + // Wait for both goroutines to have entered the factory and created a processor. + // This confirms they both missed the initial `workers.Load` check. + proc1 := <-factoryEntered + proc2 := <-factoryEntered + + // Unblock both goroutines, allowing them to race to `workers.LoadOrStore`. + close(continueFactory) + wg.Wait() + + // One processor "won" and was stored, the other "lost" and should have been cancelled. + actual, ok := h.fc.workers.Load("race-shard") + require.True(t, ok, "a worker should have been stored in the map") + + storedWorker := actual.(*managedWorker) + winnerProc := storedWorker.processor.(*mockShardProcessor) + + var loserProc *mockShardProcessor + if winnerProc == proc1 { + loserProc = proc2 + } else { + loserProc = proc1 + } + + // Wait for the `Run` method to be called on the winning processor to ensure its context is available. + select { + case <-winnerProc.runStarted: + // Success. + case <-time.After(1 * time.Second): + t.Fatal("timed out waiting for winning worker to start") + } + + // The winning processor's context must remain active. + require.NotNil(t, winnerProc.Context(), "winner's context should not be nil") + select { + case <-winnerProc.Context().Done(): + t.Error("context of the winning worker should not be cancelled") + default: + // Success + } + + // The losing processor's `Run` method must not be called, and its context should be nil. + select { + case <-loserProc.runStarted: + t.Error("Run was called on the losing worker, but it should not have been") + default: + // Success + } + assert.Nil(t, loserProc.Context(), "loser's context should be nil as Run is never called") + }) +} + +func TestFlowController_Concurrency(t *testing.T) { + const ( + numShards = 4 + numGoroutines = 50 + numRequests = 200 + ) + + // Set up a realistic registry that vends real components to the processor. + mockRegistry := &mockRegistryClient{} + shards := make([]contracts.RegistryShard, numShards) + queues := make(map[string]contracts.ManagedQueue) + for i := range numShards { + shardID := fmt.Sprintf("shard-%d", i) + queues[shardID] = &mocks.MockManagedQueue{FlowKeyV: defaultFlowKey} // Use the high-fidelity mock queue. + shards[i] = &mocks.MockRegistryShard{ + IDFunc: func() string { return shardID }, + ManagedQueueFunc: func(_ types.FlowKey) (contracts.ManagedQueue, error) { + return queues[shardID], nil + }, + AllOrderedPriorityLevelsFunc: func() []int { return []int{100} }, + PriorityBandAccessorFunc: func(priority int) (framework.PriorityBandAccessor, error) { + if priority == 100 { + return &frameworkmocks.MockPriorityBandAccessor{ + PriorityNameV: "high", + PriorityV: 100, + IterateQueuesFunc: func(f func(framework.FlowQueueAccessor) bool) { + f(queues[shardID].FlowQueueAccessor()) + }, + }, nil + } + return nil, fmt.Errorf("unexpected priority %d", priority) + }, + IntraFlowDispatchPolicyFunc: func(_ types.FlowKey) (framework.IntraFlowDispatchPolicy, error) { + return &frameworkmocks.MockIntraFlowDispatchPolicy{ + SelectItemFunc: func(qa framework.FlowQueueAccessor) (types.QueueItemAccessor, error) { + return qa.PeekHead() + }, + }, nil + }, + InterFlowDispatchPolicyFunc: func(_ int) (framework.InterFlowDispatchPolicy, error) { + return &frameworkmocks.MockInterFlowDispatchPolicy{ + SelectQueueFunc: func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error) { + return queues[shardID].FlowQueueAccessor(), nil + }, + }, nil + }, + StatsFunc: func() contracts.ShardStats { + return contracts.ShardStats{ + ID: shardID, + TotalLen: uint64(queues[shardID].Len()), + TotalByteSize: queues[shardID].ByteSize(), + PerPriorityBandStats: map[int]contracts.PriorityBandStats{ + 100: { + Len: uint64(queues[shardID].Len()), + ByteSize: queues[shardID].ByteSize(), + CapacityBytes: 1e9, // Effectively unlimited capacity + }, + }, + } + }, + } + } + mockRegistry.WithConnectionFunc = func(_ types.FlowKey, fn func(conn contracts.ActiveFlowConnection) error) error { + return fn(&mockActiveFlowConnection{ActiveShardsV: shards}) + } + mockRegistry.ShardStatsFunc = func() []contracts.ShardStats { + stats := make([]contracts.ShardStats, len(shards)) + for i, shard := range shards { + stats[i] = shard.Stats() + } + return stats + } + h := newIntegrationHarness(t, t.Context(), Config{ + // Use a generous buffer to prevent flakes in the test due to transient queuing delays. + EnqueueChannelBufferSize: numRequests, + DefaultRequestTTL: 1 * time.Second, + }, mockRegistry) + + var wg sync.WaitGroup + wg.Add(numGoroutines) + outcomes := make(chan types.QueueOutcome, numRequests) + for range numGoroutines { + go func() { + defer wg.Done() + for range numRequests / numGoroutines { + req := newTestRequest(logr.NewContext(context.Background(), logr.Discard()), defaultFlowKey) + outcome, err := h.fc.EnqueueAndWait(req) + if err != nil { + // Use `t.Errorf` for concurrent tests to avoid halting execution on a single failure. + t.Errorf("EnqueueAndWait failed unexpectedly: %v", err) + } + outcomes <- outcome + } + }() + } + wg.Wait() + close(outcomes) + + successCount := 0 + for outcome := range outcomes { + if outcome == types.QueueOutcomeDispatched { + successCount++ + } + } + require.Equal(t, numRequests, successCount, "all concurrent requests should be dispatched successfully") +} diff --git a/pkg/epp/flowcontrol/controller/doc.go b/pkg/epp/flowcontrol/controller/doc.go index 8c96bbc18..4f2620c5a 100644 --- a/pkg/epp/flowcontrol/controller/doc.go +++ b/pkg/epp/flowcontrol/controller/doc.go @@ -18,105 +18,19 @@ limitations under the License. // // # Overview // -// The `FlowController` is the central processing engine of the flow control system. It is a sharded, high-throughput -// component responsible for managing the lifecycle of all incoming requests—from initial submission via the synchronous -// `EnqueueAndWait` method to a terminal outcome (dispatch, rejection, or eviction). It achieves this by orchestrating -// its dependencies—the `contracts.FlowRegistry`, the pluggable `Policy` framework, and the -// `contracts.SaturationDetector`—to make continuous, state-aware decisions. +// The `FlowController` is the central processing engine of the Flow Control system. It acts as a stateless supervisor +// that orchestrates a pool of stateful workers (`internal.ShardProcessor`), managing the lifecycle of all incoming +// requests from initial submission to a terminal outcome (dispatch, rejection, or eviction). // -// # Architecture: The Processor-Shard Relationship +// # Architecture: Supervisor-Worker Pattern // -// The `FlowController` engine is designed around a clear separation of state and execution. This "control plane vs. -// data plane" separation is key to enabling dynamic, concurrent-safe configuration updates. +// This package implements a classic "supervisor-worker" pattern to achieve high throughput and dynamic scalability. // -// - The `contracts.FlowRegistry` is the **control plane**. It is the single source of truth for all configuration. -// When an administrative action occurs (e.g., `RegisterOrUpdateFlow`), the `contracts.FlowRegistry` is responsible -// for safely applying that change to each of its managed `contracts.RegistryShard` instances. -// -// - The `contracts.RegistryShard` is the **concurrent-safe state port**. It defines the contract for a state store -// that holds the `contracts.ManagedQueue` and framework `Policy` instances for a single shard. -// -// - The `internal.ShardProcessor` is the **data plane worker**. It is given a single `contracts.RegistryShard` to -// operate on. Its main `dispatchCycle` continuously acquires a read lock on the shard to get a consistent view of -// the active queues and policies, and then executes its dispatch logic. -// -// This separation is what enables dynamic updates. The `internal.ShardProcessor` is stateless; it simply executes -// against the state presented by its `contracts.RegistryShard` on each cycle. This allows the control plane -// (`contracts.FlowRegistry`) to safely change that state in the background. -// -// # Architectural Deep Dive: The `EnqueueAndWait` Model -// -// A fundamental design choice is the synchronous, blocking `EnqueueAndWait` method. In the context of the Gateway API -// Inference Extension's Endpoint Picker (EPP), which operates as an Envoy External Processing (`ext_proc`) server, this -// model is deliberately chosen for its simplicity and robustness. -// -// - Alignment with `ext_proc`: The `ext_proc` protocol is stream-based. A single goroutine within the EPP manages the -// stream for a given HTTP request. `EnqueueAndWait` fits this perfectly: the request-handling goroutine calls it, -// blocks, and upon return, has the definitive outcome. It can then immediately act on that outcome, maintaining -// clear request-goroutine affinity. -// -// - Simplified State Management: The state of a "waiting" request is implicitly managed by the blocked goroutine's -// stack and its `context.Context`. The `FlowController` only needs to signal this specific goroutine to unblock it. -// -// - Direct Backpressure: If queues are full, `EnqueueAndWait` returns `types.ErrQueueAtCapacity`. This provides -// immediate, direct backpressure to the earliest point of contact. -// -// # Architectural Deep Dive: The Sharded Model & JSQ-Bytes -// -// The `FlowController` is built on a sharded architecture to enable parallel processing and prevent a central dispatch -// loop from becoming a bottleneck. The `FlowController` consists of a top-level manager and a pool of independent -// `internal.ShardProcessor` workers. The `contracts.FlowRegistry` guarantees that every logical flow is represented by -// a distinct queue instance on every active shard. -// -// This architecture trades deterministic global state for high throughput and scalability. The key challenge, and the -// system's most critical assumption, revolves around ensuring this distributed model can still achieve global fairness -// objectives. -// -// ## The Critical Assumption: Homogeneity Within Flows -// -// The effectiveness of the sharded model hinges on a critical assumption: while the system as a whole manages a -// heterogeneous set of flows, the traffic *within a single logical flow* is assumed to be roughly homogeneous in its -// characteristics. A logical flow is intended to represent a single workload or tenant; therefore, the most -// unpredictable variables (effecting decode behavior) are expected to be statistically similar *within* that flow. -// -// ## The Hedge: Join the Shortest Queue by Bytes (JSQ-Bytes) -// -// To make this assumption as robust as possible, the `FlowController` uses a "Join the Shortest Queue by Bytes -// (JSQ-Bytes)" algorithm. `ByteSize` is an excellent proxy for the resources the `FlowController` explicitly manages -// (host memory pressure and queuing capacity) and is also a reasonable proxy for prefill compute time. -// -// Crucially, the goal of the distributor is not to perfectly predict backend compute time, but to intelligently balance -// the load at the controller level. JSQ-Bytes achieves this by: -// -// 1. Reflecting True Load: It distributes work based on each shard's current queue size in bytes—a direct measure of -// its memory and capacity congestion. -// -// 2. Adapting to Congestion: The byte-size of a queue is a real-time signal of a shard's overall congestion. If a -// shard is slow (e.g., due to long-decoding downstream requests), its queues will remain full, and JSQ-Bytes will -// adaptively steer new work away. -// -// 3. Hedging Against Assumption Violations: This adaptive, self-correcting nature makes it a powerful hedge. It -// doesn't just distribute; it actively *load balances* based on the most relevant feedback available. -// -// # Architectural Deep Dive: Pre-Policy Gating -// -// Before policies are invoked, the `internal.ShardProcessor` applies an `internal.BandFilter`. This function determines -// which flows within a priority band are eligible for a given operation (e.g., dispatch). This pattern is a deliberate -// architectural choice to decouple the logic of *viability* from the logic of *selection*. -// -// - An `internal.BandFilter` (e.g., the `internal.NewSaturationFilter`) determines if a flow is viable based on -// external signals like backend load. -// - The `framework.InterFlowDispatchPolicy` then selects from among the viable candidates based on its own fairness -// logic. -// -// This abstraction provides two major benefits: -// -// 1. Low Contributor Burden: It makes the mental model for policy contributors significantly simpler. An author of a -// new fairness policy does not need to be concerned with the complexities of saturation detection or other gating -// concerns. They are given a simple, pre-filtered view of the world and can focus solely on their selection logic. -// -// 2. Correctness by Construction: The `internal.subsetPriorityBandAccessor` wrapper guarantees that a policy operates -// on a consistent, filtered view, regardless of which accessor method it calls (`FlowIDs`, `Queue`, etc.). This -// prevents an entire class of subtle bugs where a policy might otherwise see a stale or unfiltered view of the -// system state. +// - The `FlowController` (Supervisor): The public-facing API of the system. Its primary responsibilities are to +// execute a distribution algorithm to select the optimal worker for a new request and to manage the lifecycle of +// the worker pool, ensuring it stays synchronized with the underlying shard topology defined by the +// `contracts.FlowRegistry`. +// - The `internal.ShardProcessor` (Worker): A stateful, single-goroutine actor responsible for the entire lifecycle +// of requests on a single shard. The supervisor manages a pool of these workers, one for each +// `contracts.RegistryShard`. package controller diff --git a/pkg/epp/flowcontrol/controller/internal/doc.go b/pkg/epp/flowcontrol/controller/internal/doc.go index 3f39b5791..083654682 100644 --- a/pkg/epp/flowcontrol/controller/internal/doc.go +++ b/pkg/epp/flowcontrol/controller/internal/doc.go @@ -14,34 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package internal provides the core worker implementation for the `controller.FlowController`. +// Package internal provides the core worker implementation for the controller.FlowController. // -// The components in this package are the private, internal building blocks of the `controller` package. This separation -// enforces a clean public API at the `controller` level and allows the internal mechanics of the engine to evolve -// independently. +// The components in this package are the private, internal building blocks of the controller. This separation enforces +// a clean public API at the `controller` level and allows the internal mechanics of the engine to evolve independently. // -// # Design Philosophy: A Single-Writer Actor Model +// # Design Philosophy: The Single-Writer Actor Model // -// The concurrency model for this package is deliberately built around a single-writer, channel-based actor pattern, as -// implemented in the `ShardProcessor`. While a simple lock-based approach might seem easier, it is insufficient for the -// system's requirements. The "enqueue" operation is a complex, stateful transaction that requires a **hierarchical -// capacity check** against both the overall shard and a request's specific priority band. +// The concurrency model for this package is built around a single-writer, channel-based actor pattern, as implemented +// in the `ShardProcessor`. All state-mutating operations for a given shard (primarily enqueuing new requests) are +// funneled through a single "Run" goroutine. // -// A coarse, shard-wide lock would be required to make this transaction atomic, creating a major performance bottleneck -// and causing head-of-line blocking at the top-level `controller.FlowController`. The single-writer model, where all -// state mutations are funneled through a single goroutine, makes this transaction atomic *without locks*. -// -// This design provides two critical benefits: -// 1. **Decoupling:** The `controller.FlowController` is decoupled via a non-blocking channel send, allowing for much -// higher throughput. -// 2. **Backpressure:** The state of the channel buffer serves as a high-fidelity, real-time backpressure signal, -// enabling more intelligent load balancing. -// -// # Future-Proofing for Complex Transactions -// -// This model's true power is that it provides a robust foundation for future features like **displacement** (a -// high-priority item evicting lower-priority ones). This is an "all-or-nothing" atomic transaction that is -// exceptionally difficult to implement correctly in a lock-free or coarse-grained locking model without significant -// performance penalties. The single-writer model contains the performance cost of such a potentially long transaction -// to the single `ShardProcessor`, preventing it from blocking the entire `controller.FlowController`. +// This design makes complex, multi-step transactions (like a hierarchical capacity check against both a shard's total +// limit and a priority band's limit) inherently atomic without locks. This avoids the performance bottleneck of a +// coarse, shard-wide lock and allows the top-level `controller.FlowController` to remain decoupled and highly +// concurrent. package internal diff --git a/pkg/epp/flowcontrol/controller/internal/item.go b/pkg/epp/flowcontrol/controller/internal/item.go index 86aeb8a0c..d5bdaaf2e 100644 --- a/pkg/epp/flowcontrol/controller/internal/item.go +++ b/pkg/epp/flowcontrol/controller/internal/item.go @@ -18,140 +18,96 @@ package internal import ( "sync" - "sync/atomic" "time" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" ) -// flowItem is the internal representation of a request managed by the `FlowController`. It implements the -// `types.QueueItemAccessor` interface, which is the primary view of the item used by queue and policy implementations. -// It wraps the original `types.FlowControlRequest` and adds metadata for queuing, lifecycle management, and policy -// interaction. -// -// # Concurrency -// -// The `finalize` method is the primary point of concurrency concern. It is designed to be atomic and idempotent through -// the use of `sync.Once`. This guarantees that an item's final outcome can be set exactly once, even if multiple -// goroutines (e.g., the main dispatch loop and the expiry cleanup loop) race to finalize it. All other fields are set -// at creation time and are not modified thereafter, making them safe for concurrent access. -type flowItem struct { - // enqueueTime is the timestamp when the item was logically accepted by the `FlowController`. - enqueueTime time.Time - // effectiveTTL is the actual time-to-live assigned to this item. - effectiveTTL time.Duration - // originalRequest is the underlying request object. +// FinalState encapsulates the terminal outcome of a `FlowItem`'s lifecycle. +// It is sent over the item's `Done()` channel exactly once. +type FinalState struct { + Outcome types.QueueOutcome + Err error +} + +// FlowItem is the internal representation of a request managed by the `FlowController`. +type FlowItem struct { + // --- Immutable fields (set at creation) --- + + enqueueTime time.Time + effectiveTTL time.Duration originalRequest types.FlowControlRequest - // handle is the unique identifier for this item within a specific queue instance. - handle types.QueueItemHandle - - // done is closed exactly once when the item is finalized (dispatched or evicted/rejected). - done chan struct{} - // err stores the final error state if the item was not successfully dispatched. - // It is written to exactly once, protected by `onceFinalize`. - err atomic.Value // Stores error - // outcome stores the final `types.QueueOutcome` of the item's lifecycle. - // It is written to exactly once, protected by `onceFinalize`. - outcome atomic.Value // Stores `types.QueueOutcome` + handle types.QueueItemHandle + + // --- Finalization state (protected by onceFinalize) --- + + // done is closed exactly once when the item is finalized. + // The closing of this channel establishes a "happens-before" memory barrier, guaranteeing that writes to `outcome` + // and `err` are visible to any goroutine that has successfully read from `done`. + done chan FinalState + + // finalState is safely visible to any goroutine after it has confirmed the channel is closed. + finalState FinalState + // onceFinalize ensures the `finalize()` logic is idempotent. onceFinalize sync.Once } -// ensure flowItem implements the interface. -var _ types.QueueItemAccessor = &flowItem{} +// ensure FlowItem implements the interface. +var _ types.QueueItemAccessor = &FlowItem{} -// NewItem creates a new `flowItem`, which is the internal representation of a request inside the `FlowController`. -// This constructor is exported so that the parent `controller` package can create items to be passed into the -// `internal` package's processors. It initializes the item with a "NotYetFinalized" outcome and an open `done` channel. -func NewItem(req types.FlowControlRequest, effectiveTTL time.Duration, enqueueTime time.Time) *flowItem { - fi := &flowItem{ +// NewItem creates a new `FlowItem`. +func NewItem(req types.FlowControlRequest, effectiveTTL time.Duration, enqueueTime time.Time) *FlowItem { + return &FlowItem{ enqueueTime: enqueueTime, effectiveTTL: effectiveTTL, originalRequest: req, - done: make(chan struct{}), + // Buffer to size one, preventing finalizing goroutine (e.g., the dispatcher) from blocking if the waiting + // goroutine has already timed out and is no longer reading. + done: make(chan FinalState, 1), } - // Initialize the outcome to its zero state. - fi.outcome.Store(types.QueueOutcomeNotYetFinalized) - return fi } // EnqueueTime returns the time the item was logically accepted by the `FlowController` for queuing. This is used as the // basis for TTL calculations. -func (fi *flowItem) EnqueueTime() time.Time { return fi.enqueueTime } +func (fi *FlowItem) EnqueueTime() time.Time { return fi.enqueueTime } // EffectiveTTL returns the actual time-to-live assigned to this item by the `FlowController`. -func (fi *flowItem) EffectiveTTL() time.Duration { return fi.effectiveTTL } +func (fi *FlowItem) EffectiveTTL() time.Duration { return fi.effectiveTTL } // OriginalRequest returns the original, underlying `types.FlowControlRequest` object. -func (fi *flowItem) OriginalRequest() types.FlowControlRequest { return fi.originalRequest } +func (fi *FlowItem) OriginalRequest() types.FlowControlRequest { return fi.originalRequest } // Handle returns the `types.QueueItemHandle` that uniquely identifies this item within a specific queue instance. It // returns nil if the item has not yet been added to a queue. -func (fi *flowItem) Handle() types.QueueItemHandle { return fi.handle } +func (fi *FlowItem) Handle() types.QueueItemHandle { return fi.handle } // SetHandle associates a `types.QueueItemHandle` with this item. This method is called by a `framework.SafeQueue` // implementation immediately after the item is added to the queue. -func (fi *flowItem) SetHandle(handle types.QueueItemHandle) { fi.handle = handle } - -// Done returns a channel that is closed when the item has been finalized (e.g., dispatched or evicted). -// This is the primary mechanism for consumers to wait for an item's outcome. It is designed to be used in a `select` -// statement, allowing the caller to simultaneously wait for other events, such as context cancellation. -// -// # Example Usage -// -// select { -// case <-item.Done(): -// outcome, err := item.FinalState() -// // ... handle outcome -// case <-ctx.Done(): -// // ... handle cancellation -// } -func (fi *flowItem) Done() <-chan struct{} { - return fi.done -} - -// FinalState returns the terminal outcome and error for the item. -// -// CRITICAL: This method must only be called after the channel returned by `Done()` has been closed. Calling it before -// the item is finalized may result in a race condition where the final state has not yet been written. -func (fi *flowItem) FinalState() (types.QueueOutcome, error) { - outcomeVal := fi.outcome.Load() - errVal := fi.err.Load() - - var finalOutcome types.QueueOutcome - if oc, ok := outcomeVal.(types.QueueOutcome); ok { - finalOutcome = oc - } else { - // This case should not happen if finalize is always called correctly, but we default to a safe value. - finalOutcome = types.QueueOutcomeNotYetFinalized - } +func (fi *FlowItem) SetHandle(handle types.QueueItemHandle) { fi.handle = handle } - var finalErr error - if e, ok := errVal.(error); ok { - finalErr = e - } - return finalOutcome, finalErr +// Done returns a channel that is closed when the item has been finalized (e.g., dispatched, rejected, or evicted). +func (fi *FlowItem) Done() <-chan FinalState { + return fi.done } -// finalize sets the item's terminal state (`outcome`, `error`) and closes its `done` channel idempotently using -// `sync.Once`. This is the single, internal point where an item's lifecycle within the `FlowController` concludes. -func (fi *flowItem) finalize(outcome types.QueueOutcome, err error) { +// Finalize sets the item's terminal state and signals the waiting goroutine by closing its `done` channel idempotently. +// This method is idempotent and is the single point where an item's lifecycle concludes. +// It is intended to be called only by the component that owns the item's lifecycle, such as a `ShardProcessor`. +func (fi *FlowItem) Finalize(outcome types.QueueOutcome, err error) { fi.onceFinalize.Do(func() { - if err != nil { - fi.err.Store(err) - } - fi.outcome.Store(outcome) + finalState := FinalState{Outcome: outcome, Err: err} + fi.finalState = finalState + fi.done <- finalState close(fi.done) }) } -// isFinalized checks if the item has been finalized without blocking. It is used internally by the `ShardProcessor` as -// a defensive check to avoid operating on items that have already been completed. -func (fi *flowItem) isFinalized() bool { - select { - case <-fi.done: - return true - default: - return false - } +// isFinalized checks if the item has been finalized without blocking or consuming the final state. +// It is a side-effect-free check used by the `ShardProcessor` as a defensive measure to avoid operating on +// already-completed items. +func (fi *FlowItem) isFinalized() bool { + // A buffered channel of size 1 can be safely and non-blockingly checked by its length. + // If the finalize function has run, it will have sent a value, and the length will be 1. + return len(fi.done) > 0 } diff --git a/pkg/epp/flowcontrol/controller/internal/item_test.go b/pkg/epp/flowcontrol/controller/internal/item_test.go index d50aaed41..1f713e913 100644 --- a/pkg/epp/flowcontrol/controller/internal/item_test.go +++ b/pkg/epp/flowcontrol/controller/internal/item_test.go @@ -18,6 +18,7 @@ package internal import ( "context" + "errors" "testing" "time" @@ -28,25 +29,47 @@ import ( typesmocks "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types/mocks" ) -func TestItem(t *testing.T) { +func TestFlowItem_New(t *testing.T) { t.Parallel() + req := typesmocks.NewMockFlowControlRequest(100, "req-1", types.FlowKey{}, context.Background()) - t.Run("should correctly set and get handle", func(t *testing.T) { - t.Parallel() - item := &flowItem{} - handle := &typesmocks.MockQueueItemHandle{} - item.SetHandle(handle) - assert.Same(t, handle, item.Handle(), "Handle() should retrieve the same handle instance set by SetHandle()") - }) - - t.Run("should have a non-finalized state upon creation", func(t *testing.T) { - t.Parallel() - key := types.FlowKey{ID: "flow-a", Priority: 10} - req := typesmocks.NewMockFlowControlRequest(100, "req-1", key, context.Background()) - item := NewItem(req, time.Minute, time.Now()) - require.NotNil(t, item, "NewItem should not return nil") - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeNotYetFinalized, outcome, "A new item's outcome should be NotYetFinalized") - assert.NoError(t, err, "A new item should have a nil error") - }) + item := NewItem(req, time.Minute, time.Now()) + + require.NotNil(t, item, "NewItem should not return a nil item") + assert.False(t, item.isFinalized(), "a new item must not be in a finalized state") + select { + case <-item.Done(): + t.Fatal("Done() channel for a new item must block, but it was closed") + default: + // This is the expected path, as the channel would have blocked. + } +} + +func TestFlowItem_Handle(t *testing.T) { + t.Parallel() + item := &FlowItem{} + handle := &typesmocks.MockQueueItemHandle{} + item.SetHandle(handle) + assert.Same(t, handle, item.Handle(), "Handle() must retrieve the identical handle instance set by SetHandle()") +} + +func TestFlowItem_Finalize_Idempotency(t *testing.T) { + t.Parallel() + req := typesmocks.NewMockFlowControlRequest(100, "req-1", types.FlowKey{}, context.Background()) + item := NewItem(req, time.Minute, time.Now()) + expectedErr := errors.New("first-error") + + item.Finalize(types.QueueOutcomeEvictedTTL, expectedErr) + item.Finalize(types.QueueOutcomeDispatched, nil) // Should take no effect + + assert.True(t, item.isFinalized(), "item must be in a finalized state after a call to finalize()") + select { + case finalState, ok := <-item.Done(): + require.True(t, ok, "Done() channel should be readable with a value, not just closed") + assert.Equal(t, types.QueueOutcomeEvictedTTL, finalState.Outcome, + "the outcome from Done() must match the first finalized outcome") + assert.Equal(t, expectedErr, finalState.Err, "the error from Done() must match the first finalized error") + case <-time.After(50 * time.Millisecond): + t.Fatal("Done() channel must not block after finalization") + } } diff --git a/pkg/epp/flowcontrol/controller/internal/processor.go b/pkg/epp/flowcontrol/controller/internal/processor.go index abcd9f2bc..fa0118270 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor.go +++ b/pkg/epp/flowcontrol/controller/internal/processor.go @@ -26,6 +26,7 @@ import ( "time" "github.com/go-logr/logr" + "k8s.io/utils/clock" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/contracts" @@ -34,70 +35,43 @@ import ( logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging" ) -const ( - // enqueueChannelBufferSize sets the size of the buffered channel that accepts incoming requests for the shard - // processor. This buffer acts as a "shock absorber," decoupling the upstream distributor from the processor's main - // loop and allowing the system to handle short, intense bursts of traffic without blocking the distributor. - enqueueChannelBufferSize = 100 +// maxCleanupWorkers caps the number of concurrent workers for background cleanup tasks. This prevents a single shard +// from overwhelming the Go scheduler with too many goroutines. +const maxCleanupWorkers = 4 - // maxCleanupWorkers caps the number of concurrent workers for background cleanup tasks. This prevents a single shard - // from overwhelming the Go scheduler with too many goroutines. - maxCleanupWorkers = 4 -) - -var ( - // errInterFlow is a sentinel error for failures during the inter-flow dispatch phase (e.g., a - // `framework.InterFlowDispatchPolicy` fails to select a queue). - // - // Strategy: When this error is encountered, the dispatch cycle aborts processing for the current priority band and - // immediately moves to the next, promoting work conservation. A failure in one band should not halt progress in - // others. - errInterFlow = errors.New("inter-flow policy failure") - - // errIntraFlow is a sentinel error for failures *after* a specific flow's queue has been selected (e.g., a - // `framework.IntraFlowDispatchPolicy` fails or a queue `Remove` fails). - // - // Strategy: When this error is encountered, the dispatch cycle aborts processing for the entire priority band for the - // current cycle. This acts as a critical circuit breaker. A stateless inter-flow policy could otherwise repeatedly - // select the same problematic queue in a tight loop of failures. Halting the band for one cycle prevents this. - errIntraFlow = errors.New("intra-flow operation failure") -) - -// clock defines an interface for getting the current time, allowing for dependency injection in tests. -type clock interface { - Now() time.Time -} +// ErrProcessorBusy is a sentinel error returned by a the processor's `Submit` method. +// It indicates that the processor's internal buffer is momentarily full and cannot accept new work. +// This is used as a signal for the `controller.FlowController`'s "fast failover" logic. +var ErrProcessorBusy = errors.New("shard processor is busy") -// ShardProcessor is the core worker of the `controller.FlowController`. It is paired one-to-one with a -// `contracts.RegistryShard` instance and is responsible for all request lifecycle operations on that shard, including -// enqueueing, dispatching, and expiry cleanup. It acts as the "data plane" worker that executes against the -// concurrent-safe state provided by its shard. +// ShardProcessor is the core worker of the `controller.FlowController`. +// It is paired one-to-one with a `contracts.RegistryShard` instance and is responsible for all request lifecycle +// operations on that shard. It acts as the "data plane" worker that executes against the concurrent-safe state provided +// by its shard. // -// For a full rationale on the single-writer concurrency model, see the package-level documentation in `doc.go`. +// # Concurrency Model: The Single-Writer Actor // -// # Concurrency Guarantees and Race Conditions +// To ensure correctness and high performance, the processor uses a single-goroutine, actor-based model. The main `Run` +// loop is the sole "writer" for all state-mutating operations, particularly enqueueing. This makes complex transactions +// inherently atomic without coarse-grained locks. // -// This model provides two key guarantees: +// # Concurrency Guarantees // -// 1. **Safe Enqueueing**: The `Run` method's goroutine has exclusive ownership of all operations that *add* items to -// queues. This makes the "check-then-act" sequence in `enqueue` (calling `hasCapacity` then `managedQ.Add`) -// inherently atomic from a writer's perspective, preventing capacity breaches. While the background -// `runExpiryCleanup` goroutine can concurrently *remove* items, this is a benign race; a concurrent removal only -// creates more available capacity, ensuring the `hasCapacity` check remains valid. -// -// 2. **Idempotent Finalization**: The primary internal race is between the main `dispatchCycle` and the background -// `runExpiryCleanup` goroutine, which might try to finalize the same `flowItem` simultaneously. This race is -// resolved by the `flowItem.finalize` method, which uses `sync.Once` to guarantee that only one of these goroutines -// can set the item's final state. +// 1. Safe Enqueueing: The "check-then-act" sequence for capacity is safe because it is only ever performed by the +// single `Run` goroutine. +// 2. Idempotent Finalization: The primary internal race condition is between the main `dispatchCycle` and the +// background `runExpiryCleanup` goroutine, both of which might try to finalize an item. This is resolved by the +// `FlowItem.Finalize` method, which uses `sync.Once` to guarantee that only the first attempt to finalize an item +// succeeds. type ShardProcessor struct { shard contracts.RegistryShard dispatchFilter BandFilter - clock clock + clock clock.Clock expiryCleanupInterval time.Duration logger logr.Logger // enqueueChan is the entry point for new requests to be processed by this shard's `Run` loop. - enqueueChan chan *flowItem + enqueueChan chan *FlowItem // wg is used to wait for background tasks like expiry cleanup to complete on shutdown. wg sync.WaitGroup isShuttingDown atomic.Bool @@ -108,8 +82,9 @@ type ShardProcessor struct { func NewShardProcessor( shard contracts.RegistryShard, dispatchFilter BandFilter, - clock clock, + clock clock.Clock, expiryCleanupInterval time.Duration, + enqueueChannelBufferSize int, logger logr.Logger, ) *ShardProcessor { return &ShardProcessor{ @@ -120,22 +95,61 @@ func NewShardProcessor( logger: logger, // A buffered channel decouples the processor from the distributor, allowing for a fast, asynchronous handoff of new // requests. - enqueueChan: make(chan *flowItem, enqueueChannelBufferSize), + enqueueChan: make(chan *FlowItem, enqueueChannelBufferSize), } } -// Run is the main operational loop for the shard processor. It must be run as a goroutine. +// Submit attempts a non-blocking handoff of an item to the processor's internal channel for asynchronous processing. // -// # Loop Strategy: Interleaving Enqueue and Dispatch +// It returns nil if the item was accepted by the processor, or if the processor is shutting down (in which case the +// item is immediately finalized with a shutdown error). In both cases, a nil return means the item's lifecycle has been +// handled by this processor and the caller should not retry. +// It returns `ErrProcessorBusy` if the processor's channel is momentarily full, signaling that the caller should try +// another processor. +func (sp *ShardProcessor) Submit(item *FlowItem) error { + if sp.isShuttingDown.Load() { + item.Finalize(types.QueueOutcomeRejectedOther, + fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning)) + return nil // Success from the caller's perspective; the item is terminal. + } + + select { + case sp.enqueueChan <- item: + return nil // Success + default: + // The channel buffer is full, signaling transient backpressure. + return ErrProcessorBusy + } +} + +// SubmitOrBlock performs a blocking submission of an item to the processor's internal channel. +// It will wait until either the submission succeeds or the provided context is cancelled. // -// The loop uses a `select` statement to interleave two primary tasks: -// 1. Accepting new requests from the `enqueueChan`. -// 2. Attempting to dispatch existing requests from queues via `dispatchCycle`. +// This method is the fallback used by the distributor when all processors are busy, providing graceful backpressure +// instead of immediate rejection. // -// This strategy is crucial for balancing responsiveness and throughput. When a new item arrives, it is immediately -// enqueued, and a dispatch cycle is triggered. This gives high-priority new arrivals a chance to be dispatched quickly. -// When no new items are arriving, the loop's `default` case continuously calls `dispatchCycle` to drain the existing -// backlog, ensuring work continues. +// It returns the `ctx.Err()` if the context is cancelled during the wait. +func (sp *ShardProcessor) SubmitOrBlock(ctx context.Context, item *FlowItem) error { + if sp.isShuttingDown.Load() { + // Here, we return an error because the caller, expecting to block, was prevented from doing so by the shutdown. + // This is a failure of the operation. + item.Finalize(types.QueueOutcomeRejectedOther, + fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning)) + return types.ErrFlowControllerNotRunning + } + + select { + case sp.enqueueChan <- item: + return nil // Success + case <-ctx.Done(): + // The caller's context was cancelled while we were blocked. + return ctx.Err() + } +} + +// Run is the main operational loop for the shard processor. It must be run as a goroutine. +// It uses a `select` statement to interleave accepting new requests with dispatching existing ones, balancing +// responsiveness with throughput. func (sp *ShardProcessor) Run(ctx context.Context) { sp.logger.V(logutil.DEFAULT).Info("Shard processor run loop starting.") defer sp.logger.V(logutil.DEFAULT).Info("Shard processor run loop stopped.") @@ -148,10 +162,8 @@ func (sp *ShardProcessor) Run(ctx context.Context) { // // 1. Context Cancellation: The highest priority is shutting down. If the context's `Done` channel is closed, the // loop will drain all queues and exit. This is the primary exit condition. - // // 2. New Item Arrival: If an item is available on `enqueueChan`, it will be processed. This ensures that the // processor is responsive to new work. - // // 3. Default (Dispatch): If neither of the above cases is ready, the `default` case executes, ensuring the loop is // non-blocking. It continuously attempts to dispatch items from the existing backlog, preventing starvation and // ensuring queues are drained. @@ -175,8 +187,9 @@ func (sp *ShardProcessor) Run(ctx context.Context) { sp.enqueue(item) sp.dispatchCycle(ctx) default: + // If no new items are arriving, continuously try to dispatch from the backlog. if !sp.dispatchCycle(ctx) { - // If no work was done, yield to other goroutines to prevent a tight, busy-loop when idle, but allow for + // If no work was done, yield to the scheduler to prevent a tight, busy-loop when idle, while still allowing for // immediate rescheduling. runtime.Gosched() } @@ -184,20 +197,10 @@ func (sp *ShardProcessor) Run(ctx context.Context) { } } -// Enqueue sends a new flow item to the processor's internal channel for asynchronous processing by its main `Run` loop. -// If the processor is shutting down, it immediately finalizes the item with a shutdown error. -func (sp *ShardProcessor) Enqueue(item *flowItem) { - if sp.isShuttingDown.Load() { - item.finalize(types.QueueOutcomeRejectedOther, - fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerShutdown)) - return - } - sp.enqueueChan <- item -} - -// enqueue is the internal implementation for adding a new item to a managed queue. It is always run from the single -// main `Run` goroutine, making its "check-then-act" logic for capacity safe. -func (sp *ShardProcessor) enqueue(item *flowItem) { +// enqueue is responsible for adding a new item to its designated queue. It is always run from the single main `Run` +// goroutine, which makes its multi-step "check-then-act" logic for capacity management inherently atomic and safe from +// race conditions. +func (sp *ShardProcessor) enqueue(item *FlowItem) { req := item.OriginalRequest() key := req.FlowKey() @@ -213,7 +216,7 @@ func (sp *ShardProcessor) enqueue(item *flowItem) { if err != nil { finalErr := fmt.Errorf("configuration error: failed to get queue for flow key %s: %w", key, err) logger.Error(finalErr, "Rejecting item.") - item.finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) + item.Finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) return } @@ -221,7 +224,7 @@ func (sp *ShardProcessor) enqueue(item *flowItem) { if err != nil { finalErr := fmt.Errorf("configuration error: failed to get priority band for priority %d: %w", key.Priority, err) logger.Error(finalErr, "Rejecting item.") - item.finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) + item.Finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) return } logger = logger.WithValues("priorityName", band.PriorityName()) @@ -237,7 +240,7 @@ func (sp *ShardProcessor) enqueue(item *flowItem) { "bandTotalBytes", bandStats.ByteSize, "bandCapacityBytes", bandStats.CapacityBytes, ) - item.finalize(types.QueueOutcomeRejectedCapacity, fmt.Errorf("%w: %w", types.ErrRejected, types.ErrQueueAtCapacity)) + item.Finalize(types.QueueOutcomeRejectedCapacity, fmt.Errorf("%w: %w", types.ErrRejected, types.ErrQueueAtCapacity)) return } @@ -250,7 +253,8 @@ func (sp *ShardProcessor) enqueue(item *flowItem) { // 2. The background `runExpiryCleanup` goroutine acts as the ultimate guarantor of correctness, as it will // eventually find and evict any finalized item that slips through this check and is added to a queue. if item.isFinalized() { - outcome, err := item.FinalState() + finalState := item.finalState + outcome, err := finalState.Outcome, finalState.Err logger.V(logutil.VERBOSE).Info("Item finalized before adding to queue, ignoring.", "outcome", outcome, "err", err) return } @@ -260,14 +264,14 @@ func (sp *ShardProcessor) enqueue(item *flowItem) { if err := managedQ.Add(item); err != nil { finalErr := fmt.Errorf("failed to add item to queue for flow key %s: %w", key, err) logger.Error(finalErr, "Rejecting item.") - item.finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) + item.Finalize(types.QueueOutcomeRejectedOther, fmt.Errorf("%w: %w", types.ErrRejected, finalErr)) return } logger.V(logutil.TRACE).Info("Item enqueued.") } // hasCapacity checks if the shard and the specific priority band have enough capacity to accommodate an item of a given -// size. +// size. This check is only safe because it is called from the single-writer `enqueue` method. func (sp *ShardProcessor) hasCapacity(priority int, itemByteSize uint64) bool { if itemByteSize == 0 { return true @@ -286,22 +290,19 @@ func (sp *ShardProcessor) hasCapacity(priority int, itemByteSize uint64) bool { // dispatchCycle attempts to dispatch a single item by iterating through all priority bands from highest to lowest. // It applies the configured policies for each band to select an item and then attempts to dispatch it. -// It returns true if an item was successfully dispatched, and false otherwise. -// -// # Error Handling Philosophy +// It returns true if an item was successfully dispatched, and false otherwise, indicating that no work was done in this +// cycle. // -// The engine employs a robust, two-tiered error handling strategy to isolate failures and maximize system availability. -// This is managed via the `errInterFlow` and `errIntraFlow` sentinel errors. +// # Error Handling Philosophy: Failure Isolation & Work Conservation // -// - Inter-Flow Failures: If a failure occurs while selecting a flow (e.g., the `InterFlowDispatchPolicy` fails), the -// processor aborts the *current priority band* and immediately moves to the next one. This promotes work -// conservation, ensuring a single misconfigured band does not halt progress for the entire system. +// The engine is designed to be resilient to failures in individual policies or transient errors within a specific flow. +// The core principle is failure isolation. A problem in one priority band must not be allowed to halt processing for +// other, healthy bands. // -// - Intra-Flow Failures: If a failure occurs *after* a flow has been selected (e.g., the `IntraFlowDispatchPolicy` -// fails), the processor aborts the *entire priority band* for the current cycle. This is a critical circuit -// breaker. An inter-flow policy that is not stateful with respect to past failures could otherwise repeatedly -// select the same problematic queue, causing a tight loop of failures. Halting the band for one cycle prevents -// this. +// To achieve this, any error encountered during the selection or dispatch process for a given priority band is treated +// as a non-critical failure for that band, in this cycle. The processor will log the error for observability and then +// immediately continue its attempt to find work in the next-lower priority band. This promotes work conservation and +// maximizes system throughput even in the presence of partial failures. func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool { baseLogger := sp.logger.WithName("dispatchCycle") @@ -329,12 +330,7 @@ func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool { // Pass the (potentially filtered) band to the policies. item, err := sp.selectItem(dispatchableBand, logger) if err != nil { - // The error handling strategy depends on the type of failure (inter- vs. intra-flow). - if errors.Is(err, errIntraFlow) { - logger.Error(err, "Intra-flow policy failure, skipping priority band for this cycle") - } else { - logger.Error(err, "Inter-flow policy or configuration failure, skipping priority band for this cycle") - } + logger.Error(err, "Failed to select item, skipping priority band for this cycle") continue } if item == nil { @@ -350,7 +346,6 @@ func (sp *ShardProcessor) dispatchCycle(ctx context.Context) bool { "reqByteSize", item.OriginalRequest().ByteSize()) if err := sp.dispatchItem(item, logger); err != nil { - // All errors from dispatchItem are considered intra-flow and unrecoverable for this band in this cycle. logger.Error(err, "Failed to dispatch item, skipping priority band for this cycle") continue } @@ -369,12 +364,11 @@ func (sp *ShardProcessor) selectItem( ) (types.QueueItemAccessor, error) { interP, err := sp.shard.InterFlowDispatchPolicy(band.Priority()) if err != nil { - return nil, fmt.Errorf("%w: could not get InterFlowDispatchPolicy: %w", errInterFlow, err) + return nil, fmt.Errorf("could not get InterFlowDispatchPolicy: %w", err) } queue, err := interP.SelectQueue(band) if err != nil { - return nil, fmt.Errorf("%w: InterFlowDispatchPolicy %q failed to select queue: %w", - errInterFlow, interP.Name(), err) + return nil, fmt.Errorf("InterFlowDispatchPolicy %q failed to select queue: %w", interP.Name(), err) } if queue == nil { logger.V(logutil.TRACE).Info("No queue selected by InterFlowDispatchPolicy") @@ -387,13 +381,11 @@ func (sp *ShardProcessor) selectItem( "selectedFlowPriority", key.Priority) intraP, err := sp.shard.IntraFlowDispatchPolicy(key) if err != nil { - // This is an intra-flow failure because we have already successfully selected a queue. - return nil, fmt.Errorf("%w: could not get IntraFlowDispatchPolicy for flow %q: %w", errIntraFlow, key, err) + return nil, fmt.Errorf("could not get IntraFlowDispatchPolicy for flow %s: %w", key, err) } item, err := intraP.SelectItem(queue) if err != nil { - return nil, fmt.Errorf("%w: IntraFlowDispatchPolicy %q failed to select item for flow %q: %w", - errIntraFlow, intraP.Name(), key, err) + return nil, fmt.Errorf("IntraFlowDispatchPolicy %q failed to select item for flow %s: %w", intraP.Name(), key, err) } if item == nil { logger.V(logutil.TRACE).Info("No item selected by IntraFlowDispatchPolicy") @@ -403,7 +395,7 @@ func (sp *ShardProcessor) selectItem( } // dispatchItem handles the final steps of dispatching an item after it has been selected by policies. This includes -// removing it from its queue, checking for last-minute expiry, and finalizing its outcome. +// removing it from its queue and finalizing its outcome. func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, logger logr.Logger) error { logger = logger.WithName("dispatchItem") @@ -411,7 +403,7 @@ func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, logger l // We must look up the queue by its specific priority, as a flow might have draining queues at other levels. managedQ, err := sp.shard.ManagedQueue(req.FlowKey()) if err != nil { - return fmt.Errorf("%w: failed to get ManagedQueue for flow %q: %w", errIntraFlow, req.FlowKey(), err) + return fmt.Errorf("failed to get ManagedQueue for flow %s: %w", req.FlowKey(), err) } // The core mutation: remove the item from the queue. @@ -420,21 +412,11 @@ func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, logger l // This can happen benignly if the item was already removed by the expiry cleanup loop between the time it was // selected by the policy and the time this function is called. logger.V(logutil.VERBOSE).Info("Item already removed from queue, likely by expiry cleanup", "err", err) - return fmt.Errorf("%w: failed to remove item %q from queue for flow %q: %w", - errIntraFlow, req.ID(), req.FlowKey(), err) - } - - removedItem, ok := removedItemAcc.(*flowItem) - if !ok { - // This indicates a severe logic error where a queue returns an item of an unexpected type. This violates a - // core system invariant: all items managed by the processor must be of type *flowItem. This is an unrecoverable - // state for this shard. - unexpectedItemErr := fmt.Errorf("%w: internal error: item %q of type %T is not a *flowItem", - errIntraFlow, removedItemAcc.OriginalRequest().ID(), removedItemAcc) - panic(unexpectedItemErr) + return fmt.Errorf("failed to remove item %q from queue for flow %s: %w", req.ID(), req.FlowKey(), err) } // Final check for expiry/cancellation right before dispatch. + removedItem := removedItemAcc.(*FlowItem) isExpired, outcome, expiryErr := checkItemExpiry(removedItem, sp.clock.Now()) if isExpired { // Ensure we always have a non-nil error to wrap for consistent logging and error handling. @@ -444,43 +426,34 @@ func (sp *ShardProcessor) dispatchItem(itemAcc types.QueueItemAccessor, logger l } logger.V(logutil.VERBOSE).Info("Item expired at time of dispatch, evicting", "outcome", outcome, "err", finalErr) - removedItem.finalize(outcome, fmt.Errorf("%w: %w", types.ErrEvicted, finalErr)) + removedItem.Finalize(outcome, fmt.Errorf("%w: %w", types.ErrEvicted, finalErr)) // Return an error to signal that the dispatch did not succeed. - return fmt.Errorf("%w: item %q expired before dispatch: %w", errIntraFlow, req.ID(), finalErr) + return fmt.Errorf("item %q expired before dispatch: %w", req.ID(), finalErr) } // Finalize the item as dispatched. - removedItem.finalize(types.QueueOutcomeDispatched, nil) + removedItem.Finalize(types.QueueOutcomeDispatched, nil) logger.V(logutil.TRACE).Info("Item dispatched.") return nil } -// checkItemExpiry checks if an item has been cancelled (via its context) or has exceeded its TTL. It returns true if -// the item is expired, along with the corresponding outcome and error. +// checkItemExpiry provides the authoritative check to determine if an item should be evicted due to TTL expiry or +// context cancellation. // -// This function provides "defense in depth" against race conditions. It is the authoritative check that is called from -// multiple locations (the dispatch loop and the cleanup loop) to determine if an item should be evicted. Its first -// action is to check if the item has *already* been finalized by a competing goroutine, ensuring that the final outcome -// is decided exactly once. +// It serves as a safeguard against race conditions. Its first action is to check if the item has already been finalized +// by a competing goroutine (e.g., the cleanup loop finalizing an item the dispatch loop is trying to process).= +// This ensures that the final outcome is decided exactly once. func checkItemExpiry( itemAcc types.QueueItemAccessor, now time.Time, ) (isExpired bool, outcome types.QueueOutcome, err error) { - item, ok := itemAcc.(*flowItem) - if !ok { - // This indicates a severe logic error where a queue returns an item of an unexpected type. This violates a - // core system invariant: all items managed by the processor must be of type *flowItem. This is an unrecoverable - // state for this shard. - unexpectedItemErr := fmt.Errorf("internal error: item %q of type %T is not a *flowItem", - itemAcc.OriginalRequest().ID(), itemAcc) - panic(unexpectedItemErr) - } + item := itemAcc.(*FlowItem) // This check is a critical defense against race conditions. If another goroutine (e.g., the cleanup loop) has // already finalized this item, we must respect that outcome. if item.isFinalized() { - outcome, err := item.FinalState() - return true, outcome, err + finalState := item.finalState + return true, finalState.Outcome, finalState.Err } // Check if the request's context has been cancelled. @@ -517,7 +490,7 @@ func (sp *ShardProcessor) runExpiryCleanup(ctx context.Context) { } // cleanupExpired performs a single scan of all queues on the shard, removing and finalizing any items that have -// expired due to TTL or context cancellation. +// expired. func (sp *ShardProcessor) cleanupExpired(now time.Time) { processFn := func(managedQ contracts.ManagedQueue, queueLogger logr.Logger) { // This predicate identifies items to be removed by the Cleanup call. @@ -537,9 +510,8 @@ func (sp *ShardProcessor) cleanupExpired(now time.Time) { sp.processAllQueuesConcurrently("cleanupExpired", processFn) } -// shutdown handles the graceful termination of the processor. It uses sync.Once to guarantee that the shutdown logic is -// executed exactly once, regardless of whether it's triggered by context cancellation or the closing of the enqueue -// channel. +// shutdown handles the graceful termination of the processor, ensuring any pending items in the enqueue channel or in +// the queues are finalized correctly. func (sp *ShardProcessor) shutdown() { sp.shutdownOnce.Do(func() { // Set the atomic bool so that any new calls to Enqueue will fail fast. @@ -555,8 +527,8 @@ func (sp *ShardProcessor) shutdown() { if item == nil { // This is a safeguard against logic errors in the distributor. continue } - item.finalize(types.QueueOutcomeRejectedOther, - fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerShutdown)) + item.Finalize(types.QueueOutcomeRejectedOther, + fmt.Errorf("%w: %w", types.ErrRejected, types.ErrFlowControllerNotRunning)) default: // The channel is empty, we can now safely close it. break DrainLoop @@ -569,8 +541,7 @@ func (sp *ShardProcessor) shutdown() { }) } -// evictAll drains all queues on the shard and finalizes every item with a shutdown error. This is called when the -// processor is shutting down to ensure no requests are left in a pending state. +// evictAll drains all queues on the shard and finalizes every item with a shutdown error. func (sp *ShardProcessor) evictAll() { processFn := func(managedQ contracts.ManagedQueue, queueLogger logr.Logger) { removedItems, err := managedQ.Drain() @@ -580,7 +551,7 @@ func (sp *ShardProcessor) evictAll() { // Finalize all the items that were removed. getOutcome := func(_ types.QueueItemAccessor) (types.QueueOutcome, error) { - return types.QueueOutcomeEvictedOther, fmt.Errorf("%w: %w", types.ErrEvicted, types.ErrFlowControllerShutdown) + return types.QueueOutcomeEvictedOther, fmt.Errorf("%w: %w", types.ErrEvicted, types.ErrFlowControllerNotRunning) } sp.finalizeItems(removedItems, queueLogger, getOutcome) } @@ -659,23 +630,23 @@ func (sp *ShardProcessor) finalizeItems( getOutcome func(item types.QueueItemAccessor) (types.QueueOutcome, error), ) { for _, i := range items { - item, ok := i.(*flowItem) + item, ok := i.(*FlowItem) if !ok { - unexpectedItemErr := fmt.Errorf("internal error: item %q of type %T is not a *flowItem", + unexpectedItemErr := fmt.Errorf("internal error: item %q of type %T is not a *FlowItem", i.OriginalRequest().ID(), i) logger.Error(unexpectedItemErr, "Panic condition detected during finalization", "item", i) continue } outcome, err := getOutcome(i) - item.finalize(outcome, err) + item.Finalize(outcome, err) logger.V(logutil.TRACE).Info("Item finalized", "reqID", item.OriginalRequest().ID(), "outcome", outcome, "err", err) } } -// finalizeExpiredItems is a specialized version of finalizeItems for items that are known to be expired. It determines -// the precise reason for expiry and finalizes the item accordingly. +// finalizeExpiredItems is a specialized version of finalizeItems for items that are known to be expired. +// It determines the precise reason for expiry and finalizes the item accordingly. func (sp *ShardProcessor) finalizeExpiredItems(items []types.QueueItemAccessor, now time.Time, logger logr.Logger) { getOutcome := func(item types.QueueItemAccessor) (types.QueueOutcome, error) { // We don't need the `isExpired` boolean here because we know it's true, but this function conveniently returns the diff --git a/pkg/epp/flowcontrol/controller/internal/processor_test.go b/pkg/epp/flowcontrol/controller/internal/processor_test.go index 81807e0e3..461cb3440 100644 --- a/pkg/epp/flowcontrol/controller/internal/processor_test.go +++ b/pkg/epp/flowcontrol/controller/internal/processor_test.go @@ -53,6 +53,7 @@ import ( "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + testclock "k8s.io/utils/clock/testing" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" @@ -79,28 +80,6 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -// mockClock allows for controlling time in tests. -type mockClock struct { - mu sync.Mutex - currentTime time.Time -} - -func newMockClock() *mockClock { - return &mockClock{currentTime: time.Now()} -} - -func (c *mockClock) Now() time.Time { - c.mu.Lock() - defer c.mu.Unlock() - return c.currentTime -} - -func (c *mockClock) Advance(d time.Duration) { - c.mu.Lock() - defer c.mu.Unlock() - c.currentTime = c.currentTime.Add(d) -} - // testHarness provides a unified, mock-based testing environment for the ShardProcessor. It centralizes all mock state // and provides helper methods for setting up tests and managing the processor's lifecycle. type testHarness struct { @@ -115,7 +94,7 @@ type testHarness struct { // Core components under test processor *ShardProcessor - mockClock *mockClock + clock *testclock.FakeClock logger logr.Logger // --- Centralized Mock State --- @@ -135,7 +114,7 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn h := &testHarness{ t: t, MockRegistryShard: &mocks.MockRegistryShard{}, - mockClock: newMockClock(), + clock: testclock.NewFakeClock(time.Now()), logger: logr.Discard(), startSignal: make(chan struct{}), queues: make(map[types.FlowKey]*mocks.MockManagedQueue), @@ -167,8 +146,11 @@ func newTestHarness(t *testing.T, expiryCleanupInterval time.Duration) *testHarn ) (framework.PriorityBandAccessor, bool) { return nil, false } - h.processor = NewShardProcessor(h, filter, h.mockClock, expiryCleanupInterval, h.logger) + h.processor = NewShardProcessor(h, filter, h.clock, expiryCleanupInterval, 100, h.logger) require.NotNil(t, h.processor, "NewShardProcessor should not return nil") + + t.Cleanup(func() { h.Stop() }) + return h } @@ -202,23 +184,23 @@ func (h *testHarness) Stop() { } // waitForFinalization blocks until an item is finalized or a timeout is reached. -func (h *testHarness) waitForFinalization(item *flowItem) (types.QueueOutcome, error) { +func (h *testHarness) waitForFinalization(item *FlowItem) (types.QueueOutcome, error) { h.t.Helper() select { - case <-item.Done(): - return item.FinalState() + case finalState := <-item.Done(): + return finalState.Outcome, finalState.Err case <-time.After(testWaitTimeout): h.t.Fatalf("Timed out waiting for item %q to be finalized", item.OriginalRequest().ID()) return types.QueueOutcomeNotYetFinalized, nil } } -// newTestItem creates a new flowItem for testing purposes. -func (h *testHarness) newTestItem(id string, key types.FlowKey, ttl time.Duration) *flowItem { +// newTestItem creates a new FlowItem for testing purposes. +func (h *testHarness) newTestItem(id string, key types.FlowKey, ttl time.Duration) *FlowItem { h.t.Helper() ctx := log.IntoContext(context.Background(), h.logger) req := typesmocks.NewMockFlowControlRequest(100, id, key, ctx) - return NewItem(req, ttl, h.mockClock.Now()) + return NewItem(req, ttl, h.clock.Now()) } // addQueue centrally registers a new mock queue for a given flow, ensuring all harness components are aware of it. @@ -226,13 +208,9 @@ func (h *testHarness) addQueue(key types.FlowKey) *mocks.MockManagedQueue { h.t.Helper() h.mu.Lock() defer h.mu.Unlock() - mockQueue := &mocks.MockManagedQueue{FlowKeyV: key} h.queues[key] = mockQueue - - // Add the key to the correct priority band, creating the band if needed. h.priorityFlows[key.Priority] = append(h.priorityFlows[key.Priority], key) - return mockQueue } @@ -334,9 +312,9 @@ func (h *testHarness) intraFlowDispatchPolicy(types.FlowKey) (framework.IntraFlo func TestShardProcessor(t *testing.T) { t.Parallel() - // Lifecycle tests use the processor's main `Run` loop to verify the complete end-to-end lifecycle of a request, from + // Integration tests use the processor's main `Run` loop to verify the complete end-to-end lifecycle of a request, from // `Enqueue` to its final outcome. - t.Run("Lifecycle", func(t *testing.T) { + t.Run("Integration", func(t *testing.T) { t.Parallel() t.Run("should dispatch item successfully", func(t *testing.T) { @@ -344,12 +322,11 @@ func TestShardProcessor(t *testing.T) { // --- ARRANGE --- h := newTestHarness(t, testCleanupTick) item := h.newTestItem("req-dispatch-success", testFlow, testTTL) - h.addQueue(types.FlowKey{ID: testFlow.ID, Priority: testFlow.Priority}) + h.addQueue(testFlow) // --- ACT --- h.Start() - defer h.Stop() - h.processor.Enqueue(item) + require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail") h.Go() // --- ASSERT --- @@ -372,8 +349,7 @@ func TestShardProcessor(t *testing.T) { // --- ACT --- h.Start() - defer h.Stop() - h.processor.Enqueue(item) + require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail") h.Go() // --- ASSERT --- @@ -396,7 +372,7 @@ func TestShardProcessor(t *testing.T) { // --- ACT --- h.Start() defer h.Stop() - h.processor.Enqueue(item) + require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail") h.Go() // --- ASSERT --- @@ -416,15 +392,14 @@ func TestShardProcessor(t *testing.T) { // --- ACT --- h.Start() h.Go() - // Stop the processor, then immediately try to enqueue. - h.Stop() - h.processor.Enqueue(item) + h.Stop() // Stop the processor, then immediately try to enqueue. + require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail, even on shutdown") // --- ASSERT --- outcome, err := h.waitForFinalization(item) assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "The outcome should be RejectedOther") require.Error(t, err, "An eviction on shutdown should produce an error") - assert.ErrorIs(t, err, types.ErrFlowControllerShutdown, "The error should be of type ErrFlowControllerShutdown") + assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "The error should be of type ErrFlowControllerNotRunning") }) t.Run("should evict item on TTL expiry via background cleanup", func(t *testing.T) { @@ -436,13 +411,12 @@ func TestShardProcessor(t *testing.T) { // --- ACT --- h.Start() - defer h.Stop() - h.processor.Enqueue(item) + require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail") h.Go() - // Let time pass for the item to expire and for the background cleanup to run. - h.mockClock.Advance(testShortTTL * 2) - time.Sleep(testCleanupTick * 3) // Allow the cleanup goroutine time to run. + h.clock.Step(testShortTTL * 2) // Let time pass for the item to expire. + // Manually invoke the cleanup logic to simulate a tick of the cleanup loop deterministically. + h.processor.cleanupExpired(h.clock.Now()) // --- ASSERT --- outcome, err := h.waitForFinalization(item) @@ -451,52 +425,22 @@ func TestShardProcessor(t *testing.T) { assert.ErrorIs(t, err, types.ErrTTLExpired, "The error should be of type ErrTTLExpired") }) - t.Run("should evict item at moment of dispatch if TTL has expired", func(t *testing.T) { - t.Parallel() - // --- ARRANGE --- - h := newTestHarness(t, 1*time.Hour) // Disable background cleanup to isolate dispatch logic. - item := h.newTestItem("req-expired-dispatch-evict", testFlow, testShortTTL) - mockQueue := h.addQueue(testFlow) - require.NoError(t, mockQueue.Add(item), "Adding item to mock queue should not fail") - - // Have the policy select the item, but then advance time so it's expired by the time dispatchItem actually runs. - h.interFlowPolicySelectQueue = func(band framework.PriorityBandAccessor) (framework.FlowQueueAccessor, error) { - h.mockClock.Advance(testShortTTL * 2) - return mockQueue.FlowQueueAccessor(), nil - } - - // --- ACT --- - h.Start() - defer h.Stop() - h.Go() - - // The run loop will pick up the item and attempt dispatch, which will fail internally. - // We need a small sleep to allow the non-blocking run loop to process. - time.Sleep(50 * time.Millisecond) - - // --- ASSERT --- - outcome, err := h.waitForFinalization(item) - assert.Equal(t, types.QueueOutcomeEvictedTTL, outcome, "The final outcome should be EvictedTTL") - require.Error(t, err, "An eviction at dispatch time should produce an error") - assert.ErrorIs(t, err, types.ErrTTLExpired, "The error should be of type ErrTTLExpired") - }) - t.Run("should evict item on context cancellation", func(t *testing.T) { t.Parallel() // --- ARRANGE --- h := newTestHarness(t, testCleanupTick) ctx, cancel := context.WithCancel(context.Background()) req := typesmocks.NewMockFlowControlRequest(100, "req-ctx-cancel", testFlow, ctx) - item := NewItem(req, testTTL, h.mockClock.Now()) + item := NewItem(req, testTTL, h.clock.Now()) h.addQueue(testFlow) // --- ACT --- h.Start() - defer h.Stop() - h.processor.Enqueue(item) + require.NoError(t, h.processor.Submit(item), "precondition: Submit should not fail") h.Go() - cancel() // Cancel the context *after* the item is enqueued. - time.Sleep(testCleanupTick * 3) // Allow the cleanup goroutine time to run. + cancel() // Cancel the context after the item is enqueued. + // Manually invoke the cleanup logic to deterministically check for the cancelled context. + h.processor.cleanupExpired(h.clock.Now()) // --- ASSERT --- outcome, err := h.waitForFinalization(item) @@ -528,7 +472,7 @@ func TestShardProcessor(t *testing.T) { outcome, err := h.waitForFinalization(item) assert.Equal(t, types.QueueOutcomeEvictedOther, outcome, "The outcome should be EvictedOther") require.Error(t, err, "An eviction on shutdown should produce an error") - assert.ErrorIs(t, err, types.ErrFlowControllerShutdown, "The error should be of type ErrFlowControllerShutdown") + assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "The error should be of type ErrFlowControllerNotRunning") }) t.Run("should handle concurrent enqueues and dispatch all items", func(t *testing.T) { @@ -537,7 +481,7 @@ func TestShardProcessor(t *testing.T) { h := newTestHarness(t, testCleanupTick) const numConcurrentItems = 20 q := h.addQueue(testFlow) - itemsToTest := make([]*flowItem, 0, numConcurrentItems) + itemsToTest := make([]*FlowItem, 0, numConcurrentItems) for i := 0; i < numConcurrentItems; i++ { item := h.newTestItem(fmt.Sprintf("req-concurrent-%d", i), testFlow, testTTL) itemsToTest = append(itemsToTest, item) @@ -549,9 +493,9 @@ func TestShardProcessor(t *testing.T) { var wg sync.WaitGroup for _, item := range itemsToTest { wg.Add(1) - go func(fi *flowItem) { + go func(fi *FlowItem) { defer wg.Done() - h.processor.Enqueue(fi) + require.NoError(t, h.processor.Submit(fi), "Submit should not fail") }(item) } h.Go() @@ -600,8 +544,8 @@ func TestShardProcessor(t *testing.T) { <-itemIsBeingDispatched // 3. The dispatch goroutine is now paused. We can now safely win the "race" by running cleanup logic. - h.mockClock.Advance(testShortTTL * 2) - h.processor.cleanupExpired(h.mockClock.Now()) // This will remove and finalize the item. + h.clock.Step(testShortTTL * 2) + h.processor.cleanupExpired(h.clock.Now()) // This will remove and finalize the item. // 5. Un-pause the dispatch goroutine. It will now fail to remove the item and the `dispatchCycle` will // correctly conclude without finalizing the item a second time. @@ -650,7 +594,7 @@ func TestShardProcessor(t *testing.T) { h.Start() defer h.Stop() h.Go() - h.processor.Enqueue(nil) + require.NoError(t, h.processor.Submit(nil), "Submit should not fail") // --- ASSERT --- // Allow a moment for the processor to potentially process the nil item. @@ -669,19 +613,18 @@ func TestShardProcessor(t *testing.T) { testCases := []struct { name string setupHarness func(h *testHarness) - item *flowItem - assert func(t *testing.T, h *testHarness, item *flowItem) + item *FlowItem + assert func(t *testing.T, h *testHarness, item *FlowItem) }{ { name: "should reject item on registry queue lookup failure", setupHarness: func(h *testHarness) { h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) { return nil, testErr } }, - assert: func(t *testing.T, h *testHarness, item *flowItem) { - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "Outcome should be RejectedOther") - require.Error(t, err, "An error should be returned") - assert.ErrorIs(t, err, testErr, "The underlying error should be preserved") + assert: func(t *testing.T, h *testHarness, item *FlowItem) { + assert.Equal(t, types.QueueOutcomeRejectedOther, item.finalState.Outcome, "Outcome should be RejectedOther") + require.Error(t, item.finalState.Err, "An error should be returned") + assert.ErrorIs(t, item.finalState.Err, testErr, "The underlying error should be preserved") }, }, { @@ -690,11 +633,10 @@ func TestShardProcessor(t *testing.T) { h.addQueue(testFlow) h.PriorityBandAccessorFunc = func(int) (framework.PriorityBandAccessor, error) { return nil, testErr } }, - assert: func(t *testing.T, h *testHarness, item *flowItem) { - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "Outcome should be RejectedOther") - require.Error(t, err, "An error should be returned") - assert.ErrorIs(t, err, testErr, "The underlying error should be preserved") + assert: func(t *testing.T, h *testHarness, item *FlowItem) { + assert.Equal(t, types.QueueOutcomeRejectedOther, item.finalState.Outcome, "Outcome should be RejectedOther") + require.Error(t, item.finalState.Err, "An error should be returned") + assert.ErrorIs(t, item.finalState.Err, testErr, "The underlying error should be preserved") }, }, { @@ -703,11 +645,10 @@ func TestShardProcessor(t *testing.T) { mockQueue := h.addQueue(testFlow) mockQueue.AddFunc = func(types.QueueItemAccessor) error { return testErr } }, - assert: func(t *testing.T, h *testHarness, item *flowItem) { - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "Outcome should be RejectedOther") - require.Error(t, err, "An error should be returned") - assert.ErrorIs(t, err, testErr, "The underlying error should be preserved") + assert: func(t *testing.T, h *testHarness, item *FlowItem) { + assert.Equal(t, types.QueueOutcomeRejectedOther, item.finalState.Outcome, "Outcome should be RejectedOther") + require.Error(t, item.finalState.Err, "An error should be returned") + assert.ErrorIs(t, item.finalState.Err, testErr, "The underlying error should be preserved") }, }, { @@ -724,17 +665,16 @@ func TestShardProcessor(t *testing.T) { assert.Equal(t, 0, addCallCount, "Queue.Add should not have been called for a finalized item") }) }, - item: func() *flowItem { + item: func() *FlowItem { // Create a pre-finalized item. item := newTestHarness(t, 0).newTestItem("req-finalized", testFlow, testTTL) - item.finalize(types.QueueOutcomeDispatched, nil) + item.Finalize(types.QueueOutcomeDispatched, nil) return item }(), - assert: func(t *testing.T, h *testHarness, item *flowItem) { + assert: func(t *testing.T, h *testHarness, item *FlowItem) { // The item was already finalized, so its state should not change. - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeDispatched, outcome, "Outcome should remain unchanged") - assert.NoError(t, err, "Error should remain unchanged") + assert.Equal(t, types.QueueOutcomeDispatched, item.finalState.Outcome, "Outcome should remain unchanged") + assert.NoError(t, item.finalState.Err, "Error should remain unchanged") }, }, } @@ -997,9 +937,9 @@ func TestShardProcessor(t *testing.T) { // --- ASSERT --- assert.True(t, dispatched, "An item should have been dispatched from the filtered flow") - outcome, err := itemB.FinalState() - assert.Equal(t, types.QueueOutcomeDispatched, outcome, "The dispatched item's outcome should be correct") - assert.NoError(t, err, "The dispatched item should not have an error") + assert.Equal(t, types.QueueOutcomeDispatched, itemB.finalState.Outcome, + "The dispatched item's outcome should be correct") + assert.NoError(t, itemB.finalState.Err, "The dispatched item should not have an error") }) t.Run("should guarantee strict priority by starving lower priority items", func(t *testing.T) { @@ -1012,8 +952,8 @@ func TestShardProcessor(t *testing.T) { qLow := h.addQueue(keyLow) const numItems = 3 - highPrioItems := make([]*flowItem, numItems) - lowPrioItems := make([]*flowItem, numItems) + highPrioItems := make([]*FlowItem, numItems) + lowPrioItems := make([]*FlowItem, numItems) for i := range numItems { // Add high priority items. itemH := h.newTestItem(fmt.Sprintf("req-high-%d", i), keyHigh, testTTL) @@ -1035,9 +975,9 @@ func TestShardProcessor(t *testing.T) { // Verify all high-priority items are gone and low-priority items remain. for _, item := range highPrioItems { - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeDispatched, outcome, "High-priority item should be dispatched") - assert.NoError(t, err, "Dispatched high-priority item should not have an error") + assert.Equal(t, types.QueueOutcomeDispatched, item.finalState.Outcome, + "High-priority item should be dispatched") + assert.NoError(t, item.finalState.Err, "Dispatched high-priority item should not have an error") } assert.Equal(t, numItems, qLow.Len(), "Low-priority queue should still be full") @@ -1112,7 +1052,7 @@ func TestShardProcessor(t *testing.T) { } // --- ACT --- - h.mockClock.Advance(testShortTTL * 2) // Make the item expire. + h.clock.Step(testShortTTL * 2) // Make the item expire. err := h.processor.dispatchItem(item, h.logger) // --- ASSERT --- @@ -1121,36 +1061,11 @@ func TestShardProcessor(t *testing.T) { assert.ErrorIs(t, err, types.ErrTTLExpired, "The error should be of type ErrTTLExpired") // Second, check the final state of the item itself. - outcome, finalErr := item.FinalState() - assert.Equal(t, types.QueueOutcomeEvictedTTL, outcome, "The item's final outcome should be EvictedTTL") - require.Error(t, finalErr, "The item's final state should contain an error") - assert.ErrorIs(t, finalErr, types.ErrTTLExpired, "The item's final error should be of type ErrTTLExpired") - }) - - t.Run("should panic if queue returns item of wrong type", func(t *testing.T) { - t.Parallel() - // --- ARRANGE --- - h := newTestHarness(t, testCleanupTick) - badItem := &typesmocks.MockQueueItemAccessor{ - OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "bad-item", testFlow, context.Background()), - } - - h.ManagedQueueFunc = func(types.FlowKey) (contracts.ManagedQueue, error) { - return &mocks.MockManagedQueue{ - RemoveFunc: func(types.QueueItemHandle) (types.QueueItemAccessor, error) { - return badItem, nil - }, - }, nil - } - - itemToDispatch := h.newTestItem("req-dispatch-panic", testFlow, testTTL) - expectedPanicMsg := fmt.Sprintf("%s: internal error: item %q of type %T is not a *flowItem", - errIntraFlow, "bad-item", badItem) - - // --- ACT & ASSERT --- - assert.PanicsWithError(t, expectedPanicMsg, func() { - _ = h.processor.dispatchItem(itemToDispatch, h.logger) - }, "A type mismatch from a queue should cause a panic") + assert.Equal(t, types.QueueOutcomeEvictedTTL, item.finalState.Outcome, + "The item's final outcome should be EvictedTTL") + require.Error(t, item.finalState.Err, "The item's final state should contain an error") + assert.ErrorIs(t, item.finalState.Err, types.ErrTTLExpired, + "The item's final error should be of type ErrTTLExpired") }) }) @@ -1165,16 +1080,15 @@ func TestShardProcessor(t *testing.T) { item := h.newTestItem("req-expired", testFlow, 1*time.Millisecond) q := h.addQueue(testFlow) require.NoError(t, q.Add(item)) - cleanupTime := h.mockClock.Now().Add(10 * time.Millisecond) + cleanupTime := h.clock.Now().Add(10 * time.Millisecond) // --- ACT --- h.processor.cleanupExpired(cleanupTime) // --- ASSERT --- - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeEvictedTTL, outcome, "Item outcome should be EvictedTTL") - require.Error(t, err, "Item should have an error") - assert.ErrorIs(t, err, types.ErrTTLExpired, "Item error should be ErrTTLExpired") + assert.Equal(t, types.QueueOutcomeEvictedTTL, item.finalState.Outcome, "Item outcome should be EvictedTTL") + require.Error(t, item.finalState.Err, "Item should have an error") + assert.ErrorIs(t, item.finalState.Err, types.ErrTTLExpired, "Item error should be ErrTTLExpired") }) t.Run("should evict all items on shutdown", func(t *testing.T) { @@ -1189,10 +1103,10 @@ func TestShardProcessor(t *testing.T) { h.processor.evictAll() // --- ASSERT --- - outcome, err := item.FinalState() - assert.Equal(t, types.QueueOutcomeEvictedOther, outcome, "Item outcome should be EvictedOther") - require.Error(t, err, "Item should have an error") - assert.ErrorIs(t, err, types.ErrFlowControllerShutdown, "Item error should be ErrFlowControllerShutdown") + assert.Equal(t, types.QueueOutcomeEvictedOther, item.finalState.Outcome, "Item outcome should be EvictedOther") + require.Error(t, item.finalState.Err, "Item should have an error") + assert.ErrorIs(t, item.finalState.Err, types.ErrFlowControllerNotRunning, + "Item error should be ErrFlowControllerNotRunning") }) t.Run("should handle registry errors gracefully during concurrent processing", func(t *testing.T) { @@ -1260,6 +1174,105 @@ func TestShardProcessor(t *testing.T) { }) }) }) + + t.Run("Public API", func(t *testing.T) { + t.Parallel() + + t.Run("Submit", func(t *testing.T) { + t.Parallel() + + t.Run("should return ErrProcessorBusy when channel is full", func(t *testing.T) { + t.Parallel() + h := newTestHarness(t, testCleanupTick) + h.processor.enqueueChan = make(chan *FlowItem, 1) + h.processor.enqueueChan <- h.newTestItem("item-filler", testFlow, testTTL) // Fill the channel to capacity. + + // The next submit should be non-blocking and fail immediately. + err := h.processor.Submit(h.newTestItem("item-to-reject", testFlow, testTTL)) + require.Error(t, err, "Submit must return an error when the channel is full") + assert.ErrorIs(t, err, ErrProcessorBusy, "The returned error must be ErrProcessorBusy") + }) + }) + + t.Run("SubmitOrBlock", func(t *testing.T) { + t.Parallel() + + t.Run("should block when channel is full and succeed when space becomes available", func(t *testing.T) { + t.Parallel() + h := newTestHarness(t, testCleanupTick) + h.processor.enqueueChan = make(chan *FlowItem, 1) + h.processor.enqueueChan <- h.newTestItem("item-filler", testFlow, testTTL) // Fill the channel to capacity. + + itemToSubmit := h.newTestItem("item-to-block", testFlow, testTTL) + submitErr := make(chan error, 1) + + // Run `SubmitOrBlock` in a separate goroutine, as it will block. + go func() { + submitErr <- h.processor.SubmitOrBlock(context.Background(), itemToSubmit) + }() + + // Prove that the call is blocking by ensuring it hasn't returned an error yet. + time.Sleep(20 * time.Millisecond) + require.Len(t, submitErr, 0, "SubmitOrBlock should be blocking and not have returned yet") + <-h.processor.enqueueChan // Make space in the channel. This should unblock the goroutine. + + select { + case err := <-submitErr: + require.NoError(t, err, "SubmitOrBlock should succeed and return no error after being unblocked") + case <-time.After(testWaitTimeout): + t.Fatal("SubmitOrBlock did not return after space was made in the channel") + } + }) + + t.Run("should unblock and return context error on cancellation", func(t *testing.T) { + t.Parallel() + h := newTestHarness(t, testCleanupTick) + h.processor.enqueueChan = make(chan *FlowItem) // Use an unbuffered channel to guarantee the first send blocks. + itemToSubmit := h.newTestItem("item-to-cancel", testFlow, testTTL) + submitErr := make(chan error, 1) + ctx, cancel := context.WithCancel(context.Background()) + + // Run `SubmitOrBlock` in a separate goroutine, as it will block. + go func() { + submitErr <- h.processor.SubmitOrBlock(ctx, itemToSubmit) + }() + + // Prove that the call is blocking. + time.Sleep(20 * time.Millisecond) + require.Len(t, submitErr, 0, "SubmitOrBlock should be blocking and not have returned yet") + cancel() // Cancel the context. This should unblock the goroutine. + + select { + case err := <-submitErr: + require.Error(t, err, "SubmitOrBlock should return an error after context cancellation") + assert.ErrorIs(t, err, context.Canceled, "The returned error must be context.Canceled") + case <-time.After(testWaitTimeout): + t.Fatal("SubmitOrBlock did not return after context was cancelled") + } + }) + + t.Run("should reject immediately if shutting down", func(t *testing.T) { + t.Parallel() + h := newTestHarness(t, testCleanupTick) + item := h.newTestItem("req-shutdown-reject", testFlow, testTTL) + h.addQueue(testFlow) + + h.Start() + h.Go() + h.Stop() // Stop the processor, then immediately try to enqueue. + err := h.processor.SubmitOrBlock(context.Background(), item) + + require.Error(t, err, "SubmitOrBlock should return an error when shutting down") + assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, "The error should be ErrFlowControllerNotRunning") + + outcome, err := h.waitForFinalization(item) + assert.Equal(t, types.QueueOutcomeRejectedOther, outcome, "The outcome should be RejectedOther") + require.Error(t, err, "Finalization should include an error") + assert.ErrorIs(t, err, types.ErrFlowControllerNotRunning, + "The finalization error should be ErrFlowControllerNotRunning") + }) + }) + }) } func TestCheckItemExpiry(t *testing.T) { @@ -1329,7 +1342,7 @@ func TestCheckItemExpiry(t *testing.T) { typesmocks.NewMockFlowControlRequest(100, "req-finalized", testFlow, context.Background()), testTTL, now) - i.finalize(types.QueueOutcomeDispatched, nil) + i.Finalize(types.QueueOutcomeDispatched, nil) return i }(), now: now, @@ -1362,20 +1375,4 @@ func TestCheckItemExpiry(t *testing.T) { } }) } - - t.Run("should panic on item of an unexpected type", func(t *testing.T) { - t.Parallel() - // --- ARRANGE --- - badItem := &typesmocks.MockQueueItemAccessor{ - OriginalRequestV: typesmocks.NewMockFlowControlRequest(0, "item-bad-type", testFlow, context.Background()), - } - - expectedPanicMsg := fmt.Sprintf("internal error: item %q of type %T is not a *flowItem", - badItem.OriginalRequestV.ID(), badItem) - - // --- ACT & ASSERT --- - assert.PanicsWithError(t, expectedPanicMsg, func() { - _, _, _ = checkItemExpiry(badItem, time.Now()) - }, "A type mismatch from a queue should cause a panic") - }) } diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs.go b/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs.go index edcc02ac6..7addb9d13 100644 --- a/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs.go +++ b/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs.go @@ -26,6 +26,32 @@ import ( ) // FCFSPolicyName is the name of the FCFS policy implementation. +// +// This policy implements a First-Come, First-Served (FCFS) strategy by selecting the item with the earliest logical +// enqueue time. +// +// # Behavior and Queue Pairing +// +// The behavioral guarantees of this policy are critically dependent on the capabilities of the `framework.SafeQueue` it +// is paired with. The system distinguishes between: +// - "Logical Enqueue Time": The timestamp when a request first arrives at the `controller.FlowController`. +// - "Physical Enqueue Time": The timestamp when a request is added to a specific shard's queue, which happens later. +// +// This policy's behavior changes accordingly: +// - Paired with a `CapabilityPriorityConfigurable` queue, it provides strict FCFS ordering based on logical enqueue +// time, aligning with this policy's vended `framework.ItemComparator`. +// This configuration ensures that requests are processed in the order they arrived at the controller, providing the +// most intuitive behavior. +// - Paired with a `CapabilityFIFO` queue, it provides approximate FCFS ordering based on physical arrival order at +// the `framework.SafeQueue`. +// This configuration offers higher performance at the cost of strict logical-time ordering, as the +// `controller.FlowController`'s "bounce-and-retry" mechanic for Draining shards means a bounced request may be +// processed after a request that logically darrived later. +// +// Given that true end-to-end ordering is non-deterministic in a distributd system, this policy defaults to pairing with +// a CapabilityFIFO` queue (like "ListQueue") to prioritize performance and high throughput. For users who require the +// strictest possible logical-time ordering that this layer can provide, explicitly pairing this policy with a +// `CapabilityPriorityConfigurable` queue is recommended. const FCFSPolicyName = "FCFS" func init() { @@ -35,7 +61,9 @@ func init() { }) } -// fcfs (First-Come, First-Served) implements the `framework.IntraFlowDispatchPolicy` interface. +// fcfs is the internal implementation of the FCFS policy. +// See the documentation for the exported `FCFSPolicyName` constant for detailed user-facing information about its +// behavior. type fcfs struct { comparator framework.ItemComparator } @@ -70,9 +98,10 @@ func (p *fcfs) Comparator() framework.ItemComparator { return p.comparator } -// RequiredQueueCapabilities specifies that this policy needs a queue that supports FIFO operations. +// RequiredQueueCapabilities returns an empty slice, indicating that this policy can operate with any queue. +// See the `FCFSPolicyName` constant's documentation for details on the behavioral trade-offs. func (p *fcfs) RequiredQueueCapabilities() []framework.QueueCapability { - return []framework.QueueCapability{framework.CapabilityFIFO} + return []framework.QueueCapability{} } // --- enqueueTimeComparator --- diff --git a/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go b/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go index 45c144238..cc6bceecf 100644 --- a/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go +++ b/pkg/epp/flowcontrol/framework/plugins/policies/intraflow/dispatch/fcfs/fcfs_test.go @@ -41,8 +41,7 @@ func TestFCFS_RequiredQueueCapabilities(t *testing.T) { t.Parallel() policy := newFCFS() caps := policy.RequiredQueueCapabilities() - require.Len(t, caps, 1, "RequiredQueueCapabilities should return one capability") - assert.Equal(t, framework.CapabilityFIFO, caps[0], "Required capability should be FIFO") + require.Empty(t, caps, "No required capabilities should be returned") } func TestFCFS_SelectItem(t *testing.T) { diff --git a/pkg/epp/flowcontrol/framework/plugins/queue/listqueue/listqueue.go b/pkg/epp/flowcontrol/framework/plugins/queue/listqueue/listqueue.go index 8e123b631..792e3a46d 100644 --- a/pkg/epp/flowcontrol/framework/plugins/queue/listqueue/listqueue.go +++ b/pkg/epp/flowcontrol/framework/plugins/queue/listqueue/listqueue.go @@ -14,8 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package listqueue provides a simple, concurrent-safe queue implementation using a standard library -// `container/list.List` as the underlying data structure for FIFO (First-In, First-Out) behavior. +// Package listqueue provides a high-performance, concurrent-safe FIFO (First-In, First-Out) implementation of +// implementation of the `framework.SafeQueue` based on the standard library's `container/list`. package listqueue import ( @@ -28,7 +28,28 @@ import ( "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/flowcontrol/types" ) -// ListQueueName is the name of the list queue implementation. +// ListQueueName is the name of the list-based queue implementation. +// +// This queue provides a high-performance, low-overhead implementation based on a standard `container/list`. +// It advertises the `CapabilityFIFO`. +// +// # Behavioral Guarantees +// +// The core guarantee of this queue is strict physical First-In, First-Out (FIFO) ordering. It processes items in the +// exact order they are added to the queue on a specific shard. +// +// # Performance and Trade-offs +// +// Because the physical insertion order may not match a request's logical arrival time (due to the +// `controller.FlowController`'s internal "bounce-and-retry" mechanic), this queue provides an*approximate FCFS behavior +// from a system-wide perspective. +// +// Given that true end-to-end ordering is non-deterministic in a distributed system, this high-performance queue is the +// recommended default for most FCFS-like policies. It prioritizes throughput and efficiency, which aligns with the +// primary goal of the Flow Control system. +// +// For workloads that require the strictest possible logical-time ordering this layer can provide, explicitly using a +// queue that supports `CapabilityPriorityConfigurable` is the appropriate choice. const ListQueueName = "ListQueue" func init() { @@ -39,8 +60,8 @@ func init() { }) } -// listQueue implements the `framework.SafeQueue` interface using a standard `container/list.List` for FIFO behavior. -// This implementation is concurrent-safe. +// listQueue is the internal implementation of the ListQueue. +// See the documentation for the exported `ListQueueName` constant for detailed user-facing information. type listQueue struct { requests *list.List byteSize atomic.Uint64 diff --git a/pkg/epp/flowcontrol/registry/config_test.go b/pkg/epp/flowcontrol/registry/config_test.go index 376282ffe..ea9c19b7d 100644 --- a/pkg/epp/flowcontrol/registry/config_test.go +++ b/pkg/epp/flowcontrol/registry/config_test.go @@ -211,16 +211,26 @@ func TestConfig_NewConfig(t *testing.T) { name: "ShouldError_WhenQueueFactoryFails", input: Config{ PriorityBands: []PriorityBandConfig{{ - Priority: 1, - PriorityName: "High", - Queue: queue.RegisteredQueueName("failing-queue"), + Priority: 1, + PriorityName: "High", + Queue: queue.RegisteredQueueName("failing-queue"), + IntraFlowDispatchPolicy: intra.RegisteredPolicyName("policy-with-req"), }}, }, expectErr: true, - opts: []configOption{withQueueFactory( - func(_ queue.RegisteredQueueName, _ framework.ItemComparator) (framework.SafeQueue, error) { + opts: []configOption{ + withIntraFlowDispatchPolicyFactory( // Forces queue instance creation for validating capabilities. + func(name intra.RegisteredPolicyName) (framework.IntraFlowDispatchPolicy, error) { + return &mocks.MockIntraFlowDispatchPolicy{ + NameV: string(name), + RequiredQueueCapabilitiesV: []framework.QueueCapability{"required-capability"}, + }, nil + }, + ), + withQueueFactory(func(_ queue.RegisteredQueueName, _ framework.ItemComparator) (framework.SafeQueue, error) { return nil, errors.New("queue creation failed") - })}, + }), + }, }, { name: "ShouldError_WhenPolicyFactoryFails", diff --git a/pkg/epp/flowcontrol/registry/connection.go b/pkg/epp/flowcontrol/registry/connection.go index 995f23c13..cb9831655 100644 --- a/pkg/epp/flowcontrol/registry/connection.go +++ b/pkg/epp/flowcontrol/registry/connection.go @@ -31,13 +31,13 @@ type connection struct { var _ contracts.ActiveFlowConnection = &connection{} // Shards returns a stable snapshot of accessors for all internal state shards. -func (c *connection) Shards() []contracts.RegistryShard { +func (c *connection) ActiveShards() []contracts.RegistryShard { c.registry.mu.RLock() defer c.registry.mu.RUnlock() // Return a copy to ensure the caller cannot modify the registry's internal slice. - shardsCopy := make([]contracts.RegistryShard, len(c.registry.allShards)) - for i, s := range c.registry.allShards { + shardsCopy := make([]contracts.RegistryShard, len(c.registry.activeShards)) + for i, s := range c.registry.activeShards { shardsCopy[i] = s } return shardsCopy diff --git a/pkg/epp/flowcontrol/registry/registry.go b/pkg/epp/flowcontrol/registry/registry.go index 12f83e2ad..5f295541d 100644 --- a/pkg/epp/flowcontrol/registry/registry.go +++ b/pkg/epp/flowcontrol/registry/registry.go @@ -198,7 +198,7 @@ func (fr *FlowRegistry) Run(ctx context.Context) { } } -// --- `contracts.FlowRegistryClient` Implementation --- +// --- `contracts.FlowRegistryDataPlane` Implementation --- // Connect establishes a session for a given flow, acquiring a lifecycle lease. // This is the primary entry point for the data path. @@ -275,7 +275,7 @@ func (fr *FlowRegistry) prepareNewFlow(key types.FlowKey) (*flowState, error) { return &flowState{key: key}, nil } -// --- `contracts.FlowRegistryAdmin` Implementation --- +// --- `contracts.FlowRegistryObserver` Implementation --- // Stats returns globally aggregated statistics for the entire `FlowRegistry`. // diff --git a/pkg/epp/flowcontrol/registry/registry_test.go b/pkg/epp/flowcontrol/registry/registry_test.go index 0e1bee194..b63b151c2 100644 --- a/pkg/epp/flowcontrol/registry/registry_test.go +++ b/pkg/epp/flowcontrol/registry/registry_test.go @@ -261,7 +261,7 @@ func TestFlowRegistry_WithConnection_AndHandle(t *testing.T) { assert.ErrorContains(t, err, "injected factory failure", "The returned error must propagate the reason") }) - t.Run("Handle_Shards_ShouldReturnAllShardsAndBeACopy", func(t *testing.T) { + t.Run("Handle_Shards_ShouldReturnAllActiveShardsAndBeACopy", func(t *testing.T) { t.Parallel() // Create a registry with a known mixed topology of Active and Draining shards. h := newRegistryTestHarness(t, harnessOptions{initialShardCount: 3}) @@ -272,9 +272,9 @@ func TestFlowRegistry_WithConnection_AndHandle(t *testing.T) { key := types.FlowKey{ID: "test-flow", Priority: highPriority} err = h.fr.WithConnection(key, func(conn contracts.ActiveFlowConnection) error { - shards := conn.Shards() + shards := conn.ActiveShards() - assert.Len(t, shards, 3, "Shards() must return all configured shards, including Draining ones") + assert.Len(t, shards, 2, "ActiveShards() must only return the Active shards") // Assert it's a copy by maliciously modifying it. require.NotEmpty(t, shards, "Test setup assumes shards are present") @@ -285,8 +285,8 @@ func TestFlowRegistry_WithConnection_AndHandle(t *testing.T) { require.NoError(t, err) // Prove the registry's internal state was not mutated by the modification. - assert.NotNil(t, h.fr.allShards[0], - "Modifying the slice returned by Shards() must not affect the registry's internal state") + assert.NotNil(t, h.fr.activeShards[0], + "Modifying the slice returned by ActiveShards() must not affect the registry's internal state") }) } @@ -323,6 +323,9 @@ func TestFlowRegistry_Stats(t *testing.T) { require.Len(t, shardStats, 2, "Should return stats for 2 shards") var totalShardLen, totalShardBytes uint64 for _, ss := range shardStats { + assert.True(t, ss.IsActive, "All shards should be active in this test") + assert.NotEmpty(t, ss.PerPriorityBandStats, "Each shard should have stats for its priority bands") + assert.NotEmpty(t, ss.ID, "Each shard should have a non-empty ID") totalShardLen += ss.TotalLen totalShardBytes += ss.TotalByteSize } @@ -600,24 +603,19 @@ func TestFlowRegistry_UpdateShardCount(t *testing.T) { require.NoError(t, err, "UpdateShardCount should not have returned an error") } - var finalActiveCount, finalDrainingCount int globalCapacities := make(map[uint64]int) bandCapacities := make(map[uint64]int) - err = h.fr.WithConnection(key, func(conn contracts.ActiveFlowConnection) error { - for _, shard := range conn.Shards() { - if shard.IsActive() { - finalActiveCount++ - stats := shard.Stats() - globalCapacities[stats.TotalCapacityBytes]++ - bandCapacities[stats.PerPriorityBandStats[highPriority].CapacityBytes]++ - h.assertFlowExists(key, "Shard %s should contain the existing flow", shard.ID()) - } else { - finalDrainingCount++ - } - } - return nil - }) - require.NoError(t, err, "WithConnection should not fail") + + h.fr.mu.RLock() + finalActiveCount := len(h.fr.activeShards) + finalDrainingCount := len(h.fr.drainingShards) + for _, shard := range h.fr.activeShards { + stats := shard.Stats() + globalCapacities[stats.TotalCapacityBytes]++ + bandCapacities[stats.PerPriorityBandStats[highPriority].CapacityBytes]++ + h.assertFlowExists(key, "Shard %s should contain the existing flow", shard.ID()) + } + h.fr.mu.RUnlock() expectedDrainingCount := 0 if tc.initialShardCount > tc.expectedActiveCount { diff --git a/pkg/epp/flowcontrol/registry/shard.go b/pkg/epp/flowcontrol/registry/shard.go index c87a8e485..4a7918e79 100644 --- a/pkg/epp/flowcontrol/registry/shard.go +++ b/pkg/epp/flowcontrol/registry/shard.go @@ -228,6 +228,8 @@ func (s *registryShard) Stats() contracts.ShardStats { // Casts from `int64` to `uint64` are safe because the non-negative invariant is strictly enforced at the // `managedQueue` level. stats := contracts.ShardStats{ + ID: s.id, + IsActive: s.IsActive(), TotalCapacityBytes: s.config.MaxBytes, TotalByteSize: uint64(s.totalByteSize.Load()), TotalLen: uint64(s.totalLen.Load()), diff --git a/pkg/epp/flowcontrol/registry/shard_test.go b/pkg/epp/flowcontrol/registry/shard_test.go index 7fbda572e..afb2fac34 100644 --- a/pkg/epp/flowcontrol/registry/shard_test.go +++ b/pkg/epp/flowcontrol/registry/shard_test.go @@ -165,6 +165,8 @@ func TestShard_Stats(t *testing.T) { stats := h.shard.Stats() + assert.Equal(t, h.shard.ID(), stats.ID, "Stats ID must match the shard ID") + assert.True(t, stats.IsActive, "Shard must report itself as active in the stats snapshot") assert.Equal(t, uint64(2), stats.TotalLen, "Total shard length must aggregate counts from all bands") assert.Equal(t, uint64(150), stats.TotalByteSize, "Total shard byte size must aggregate sizes from all bands") diff --git a/pkg/epp/flowcontrol/types/errors.go b/pkg/epp/flowcontrol/types/errors.go index f7dffbd4d..8c966bb45 100644 --- a/pkg/epp/flowcontrol/types/errors.go +++ b/pkg/epp/flowcontrol/types/errors.go @@ -43,11 +43,8 @@ var ( // The following errors can occur before a request is formally added to a `framework.SafeQueue`. When returned by // `FlowController.EnqueueAndWait()`, these specific errors will typically be wrapped by `ErrRejected`. var ( - // ErrNilRequest indicates that a nil `types.FlowControlRequest` was provided. - ErrNilRequest = errors.New("FlowControlRequest cannot be nil") - // ErrQueueAtCapacity indicates that a request could not be enqueued because queue capacity limits were met. - ErrQueueAtCapacity = errors.New("queue at capacity and displacement failed to make space") + ErrQueueAtCapacity = errors.New("queue at capacity") ) // --- Post-Enqueue Eviction Errors --- @@ -68,10 +65,10 @@ var ( // --- General `controller.FlowController` Errors --- var ( - // ErrFlowControllerShutdown indicates that an operation could not complete or an item was evicted because the - // `controller.FlowController` is shutting down or has stopped. + // ErrFlowControllerNotRunning indicates that an operation could not complete or an item was evicted because the + // `controller.FlowController` is not running or is in the process of shutting down. // // When returned by `FlowController.EnqueueAndWait()`, this will be wrapped by `ErrRejected` (if rejection happens // before internal queuing) or `ErrEvicted` (if eviction happens after internal queuing). - ErrFlowControllerShutdown = errors.New("FlowController is shutting down") + ErrFlowControllerNotRunning = errors.New("flow controller is not running") ) diff --git a/pkg/epp/flowcontrol/types/request.go b/pkg/epp/flowcontrol/types/request.go index 756940704..5dfc18eb6 100644 --- a/pkg/epp/flowcontrol/types/request.go +++ b/pkg/epp/flowcontrol/types/request.go @@ -92,7 +92,8 @@ type QueueItemAccessor interface { OriginalRequest() FlowControlRequest // EnqueueTime is the timestamp when the item was logically accepted by the `controller.FlowController` for queuing - // (i.e., when `controller.FlowController.EnqueueAndWait()` was called). + // (i.e., when `controller.FlowController.EnqueueAndWait()` was called). It does not reflect the time the request + // landed in a `framework.SafeQueue` instance. EnqueueTime() time.Time // EffectiveTTL is the actual Time-To-Live assigned to this item by the `controller.FlowController`, taking into From a43eba4f3874031cabd37c278dbb0a99f1e2a97f Mon Sep 17 00:00:00 2001 From: Hung Tran <40334379+phuhung273@users.noreply.github.com> Date: Sun, 14 Sep 2025 02:42:07 +0700 Subject: [PATCH 026/133] chore: remove unneeded logging before returning err (#1544) --- internal/runnable/grpc.go | 6 ++---- pkg/bbr/handlers/server.go | 3 --- pkg/bbr/server/runserver.go | 4 ++-- pkg/epp/controller/inferenceobjective_reconciler.go | 4 ++-- pkg/epp/controller/inferencepool_reconciler.go | 13 ++++--------- pkg/epp/controller/pod_reconciler.go | 4 ++-- pkg/epp/handlers/response.go | 4 ++-- pkg/epp/handlers/server.go | 3 --- pkg/epp/server/runserver.go | 3 +-- 9 files changed, 15 insertions(+), 29 deletions(-) diff --git a/internal/runnable/grpc.go b/internal/runnable/grpc.go index 82b7b85e2..5db5f9ed5 100644 --- a/internal/runnable/grpc.go +++ b/internal/runnable/grpc.go @@ -37,8 +37,7 @@ func GRPCServer(name string, srv *grpc.Server, port int) manager.Runnable { // Start listening. lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { - log.Error(err, "gRPC server failed to listen") - return err + return fmt.Errorf("gRPC server failed to listen - %w", err) } log.Info("gRPC server listening", "port", port) @@ -59,8 +58,7 @@ func GRPCServer(name string, srv *grpc.Server, port int) manager.Runnable { // Keep serving until terminated. if err := srv.Serve(lis); err != nil && err != grpc.ErrServerStopped { - log.Error(err, "gRPC server failed") - return err + return fmt.Errorf("gRPC server failed - %w", err) } log.Info("gRPC server terminated") return nil diff --git a/pkg/bbr/handlers/server.go b/pkg/bbr/handlers/server.go index 499d6af28..6488453be 100644 --- a/pkg/bbr/handlers/server.go +++ b/pkg/bbr/handlers/server.go @@ -61,9 +61,6 @@ func (s *Server) Process(srv extProcPb.ExternalProcessor_ProcessServer) error { return nil } if recvErr != nil { - // This error occurs very frequently, though it doesn't seem to have any impact. - // TODO Figure out if we can remove this noise. - loggerVerbose.Error(recvErr, "Cannot receive stream request") return status.Errorf(codes.Unknown, "cannot receive stream request: %v", recvErr) } diff --git a/pkg/bbr/server/runserver.go b/pkg/bbr/server/runserver.go index ec2880b24..ac6ac414e 100644 --- a/pkg/bbr/server/runserver.go +++ b/pkg/bbr/server/runserver.go @@ -19,6 +19,7 @@ package server import ( "context" "crypto/tls" + "fmt" extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" "github.com/go-logr/logr" @@ -54,8 +55,7 @@ func (r *ExtProcServerRunner) AsRunnable(logger logr.Logger) manager.Runnable { if r.SecureServing { cert, err := tlsutil.CreateSelfSignedTLSCertificate(logger) if err != nil { - logger.Error(err, "Failed to create self signed certificate") - return err + return fmt.Errorf("failed to create self signed certificate - %w", err) } creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}) srv = grpc.NewServer(grpc.Creds(creds)) diff --git a/pkg/epp/controller/inferenceobjective_reconciler.go b/pkg/epp/controller/inferenceobjective_reconciler.go index 53bce8646..c8ac5a6c3 100644 --- a/pkg/epp/controller/inferenceobjective_reconciler.go +++ b/pkg/epp/controller/inferenceobjective_reconciler.go @@ -18,6 +18,7 @@ package controller import ( "context" + "fmt" "k8s.io/apimachinery/pkg/api/errors" ctrl "sigs.k8s.io/controller-runtime" @@ -48,8 +49,7 @@ func (c *InferenceObjectiveReconciler) Reconcile(ctx context.Context, req ctrl.R notFound := false if err := c.Get(ctx, req.NamespacedName, infObjective); err != nil { if !errors.IsNotFound(err) { - logger.Error(err, "Unable to get InferenceObjective") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("unable to get InferenceObjective - %w", err) } notFound = true } diff --git a/pkg/epp/controller/inferencepool_reconciler.go b/pkg/epp/controller/inferencepool_reconciler.go index d8b7668e2..3b52de0ae 100644 --- a/pkg/epp/controller/inferencepool_reconciler.go +++ b/pkg/epp/controller/inferencepool_reconciler.go @@ -56,9 +56,7 @@ func (c *InferencePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reques obj = &v1alpha2.InferencePool{} default: // Handle unsupported groups gracefully. - err := fmt.Errorf("unsupported API group: %s", c.PoolGKNN.Group) - logger.Error(err, "Cannot reconcile InferencePool") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("cannot reconcile InferencePool - unsupported API group: %s", c.PoolGKNN.Group) } // 2. Perform a single, generic fetch for the object. @@ -68,8 +66,7 @@ func (c *InferencePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reques c.Datastore.Clear() return ctrl.Result{}, nil } - logger.Error(err, "Unable to get InferencePool") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("unable to get InferencePool - %w", err) } // 3. Perform common checks using the client.Object interface. @@ -90,16 +87,14 @@ func (c *InferencePoolReconciler) Reconcile(ctx context.Context, req ctrl.Reques var err error err = pool.ConvertTo(v1infPool) if err != nil { - logger.Error(err, "Failed to convert XInferencePool to InferencePool") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("failed to convert XInferencePool to InferencePool - %w", err) } default: return ctrl.Result{}, fmt.Errorf("unsupported API group: %s", c.PoolGKNN.Group) } if err := c.Datastore.PoolSet(ctx, c.Reader, v1infPool); err != nil { - logger.Error(err, "Failed to update datastore") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("failed to update datastore - %w", err) } return ctrl.Result{}, nil diff --git a/pkg/epp/controller/pod_reconciler.go b/pkg/epp/controller/pod_reconciler.go index 3cd7c2574..ce77b2cfd 100644 --- a/pkg/epp/controller/pod_reconciler.go +++ b/pkg/epp/controller/pod_reconciler.go @@ -18,6 +18,7 @@ package controller import ( "context" + "fmt" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -55,8 +56,7 @@ func (c *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R c.Datastore.PodDelete(req.NamespacedName) return ctrl.Result{}, nil } - logger.V(logutil.DEFAULT).Error(err, "Unable to get pod") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("unable to get pod - %w", err) } c.updateDatastore(logger, pod) diff --git a/pkg/epp/handlers/response.go b/pkg/epp/handlers/response.go index d0c3b020a..7a3b3038b 100644 --- a/pkg/epp/handlers/response.go +++ b/pkg/epp/handlers/response.go @@ -19,6 +19,7 @@ package handlers import ( "context" "encoding/json" + "fmt" "strings" configPb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" @@ -41,8 +42,7 @@ func (s *StreamingServer) HandleResponseBody(ctx context.Context, reqCtx *Reques logger := log.FromContext(ctx) responseBytes, err := json.Marshal(response) if err != nil { - logger.V(logutil.DEFAULT).Error(err, "error marshalling responseBody") - return reqCtx, err + return reqCtx, fmt.Errorf("error marshalling responseBody - %w", err) } if response["usage"] != nil { usg := response["usage"].(map[string]any) diff --git a/pkg/epp/handlers/server.go b/pkg/epp/handlers/server.go index 0e0a1d03d..9dd63ced9 100644 --- a/pkg/epp/handlers/server.go +++ b/pkg/epp/handlers/server.go @@ -195,9 +195,6 @@ func (s *StreamingServer) Process(srv extProcPb.ExternalProcessor_ProcessServer) return nil } if recvErr != nil { - // This error occurs very frequently, though it doesn't seem to have any impact. - // TODO Figure out if we can remove this noise. - logger.V(logutil.DEFAULT).Error(err, "Cannot receive stream request") return status.Errorf(codes.Unknown, "cannot receive stream request: %v", err) } diff --git a/pkg/epp/server/runserver.go b/pkg/epp/server/runserver.go index de3cb1023..b945985ce 100644 --- a/pkg/epp/server/runserver.go +++ b/pkg/epp/server/runserver.go @@ -162,8 +162,7 @@ func (r *ExtProcServerRunner) AsRunnable(logger logr.Logger) manager.Runnable { cert, err = tlsutil.CreateSelfSignedTLSCertificate(logger) } if err != nil { - logger.Error(err, "Failed to create self signed certificate") - return err + return fmt.Errorf("failed to create self signed certificate - %w", err) } creds := credentials.NewTLS(&tls.Config{ From 5f6fcede74de12c926c71a6d7cc88554aca0267a Mon Sep 17 00:00:00 2001 From: "bin.pan" Date: Mon, 15 Sep 2025 19:34:11 +0800 Subject: [PATCH 027/133] update istio version to v1.28 in guide document (#1582) --- site-src/guides/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site-src/guides/index.md b/site-src/guides/index.md index 3d47ff33a..af926acc1 100644 --- a/site-src/guides/index.md +++ b/site-src/guides/index.md @@ -178,7 +178,7 @@ Tooling: 2. Install Istio ``` - TAG=$(curl https://storage.googleapis.com/istio-build/dev/1.27-dev) + TAG=$(curl https://storage.googleapis.com/istio-build/dev/1.28-dev) # on Linux wget https://storage.googleapis.com/istio-build/dev/$TAG/istioctl-$TAG-linux-amd64.tar.gz tar -xvf istioctl-$TAG-linux-amd64.tar.gz From 6799a5b0c114f5905b88d5e466bde60f0462fab0 Mon Sep 17 00:00:00 2001 From: Jared Tan Date: Mon, 15 Sep 2025 20:40:12 +0800 Subject: [PATCH 028/133] docs: dashboards README metrics link fix (#1589) --- tools/dashboards/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dashboards/README.md b/tools/dashboards/README.md index 21282bf23..564d8a6df 100644 --- a/tools/dashboards/README.md +++ b/tools/dashboards/README.md @@ -4,7 +4,7 @@ This documentation provides instructions for setting up grafana dashboards to se ## Requirements -Please follow [metrics](https://gateway-api-inference-extension.sigs.k8s.io/guides/metrics/?h=metrics) page to configure the proxy to enable all metrics. +Please follow [metrics](https://gateway-api-inference-extension.sigs.k8s.io/guides/metrics-and-observability/) page to configure the proxy to enable all metrics. ## Load Inference Extension dashboard into Grafana From fd57038c5d35cbee712de1cf3fb53913bd681eec Mon Sep 17 00:00:00 2001 From: Jeff Luo Date: Mon, 15 Sep 2025 10:16:13 -0400 Subject: [PATCH 029/133] fix(deps): update prometheus/common to v0.66.1 and prometheus/client_golang to v1.23.1 (#1580) --- go.mod | 6 +++--- go.sum | 10 ++++++---- pkg/epp/backend/metrics/metrics.go | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 163e0e12b..e8651d514 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,11 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/onsi/ginkgo/v2 v2.25.1 github.com/onsi/gomega v1.38.2 - github.com/prometheus/client_golang v1.23.0 + github.com/prometheus/client_golang v1.23.1 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.65.0 + github.com/prometheus/common v0.66.1 github.com/prometheus/prometheus v0.305.0 - github.com/stretchr/testify v1.11.0 + github.com/stretchr/testify v1.11.1 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index f1d01ec4a..7da7f450c 100644 --- a/go.sum +++ b/go.sum @@ -213,10 +213,12 @@ github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= +github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y= +github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/prometheus v0.305.0 h1:UO/LsM32/E9yBDtvQj8tN+WwhbyWKR10lO35vmFLx0U= @@ -242,8 +244,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/pkg/epp/backend/metrics/metrics.go b/pkg/epp/backend/metrics/metrics.go index 8927b1b12..5d3b52bbb 100644 --- a/pkg/epp/backend/metrics/metrics.go +++ b/pkg/epp/backend/metrics/metrics.go @@ -25,6 +25,7 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" + "github.com/prometheus/common/model" "go.uber.org/multierr" "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend" @@ -65,7 +66,7 @@ func (p *PodMetricsClientImpl) FetchMetrics(ctx context.Context, pod *backend.Po return nil, fmt.Errorf("unexpected status code from %s: %v", pod.NamespacedName, resp.StatusCode) } - parser := expfmt.TextParser{} + parser := expfmt.NewTextParser(model.LegacyValidation) metricFamilies, err := parser.TextToMetricFamilies(resp.Body) if err != nil { return nil, err From b372db89e24e2bb4807c3ab63ad8b317b38ce3c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 07:56:11 -0700 Subject: [PATCH 030/133] chore(deps): bump github.com/prometheus/client_golang (#1550) Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.23.0 to 1.23.2. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.23.0...v1.23.2) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-version: 1.23.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index e8651d514..aff181480 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/onsi/ginkgo/v2 v2.25.1 github.com/onsi/gomega v1.38.2 - github.com/prometheus/client_golang v1.23.1 + github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 github.com/prometheus/common v0.66.1 github.com/prometheus/prometheus v0.305.0 diff --git a/go.sum b/go.sum index 7da7f450c..1f9a3bb1f 100644 --- a/go.sum +++ b/go.sum @@ -211,10 +211,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= -github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y= -github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= From 04ae59211dd3da61b13d2913b3f5aec183dfc09e Mon Sep 17 00:00:00 2001 From: David Breitgand Date: Mon, 15 Sep 2025 21:52:08 +0300 Subject: [PATCH 031/133] One liner: add COPY api ./api command to resolve import dependency in /pkg/epp/datalayer/factory.go:28:2: no required module provides package sigs.k8s.io/gateway-api-inference-extension/api/v1 (#1585) --- bbr.Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/bbr.Dockerfile b/bbr.Dockerfile index 36ae378cc..1c294c4c2 100644 --- a/bbr.Dockerfile +++ b/bbr.Dockerfile @@ -18,6 +18,7 @@ RUN go mod download COPY cmd/bbr ./cmd COPY pkg ./pkg COPY internal ./internal +COPY api ./api WORKDIR /src/cmd RUN go build -o /bbr From f486835413d485feb2561c5ada37c5c2153d2c7d Mon Sep 17 00:00:00 2001 From: Yizheng Jiao Date: Mon, 15 Sep 2025 13:40:07 -0700 Subject: [PATCH 032/133] =?UTF-8?q?Enhance=20pool=20namespace=20resolution?= =?UTF-8?q?:=20use=20flag=20if=20set,=20else=20NAMESPACE=20en=E2=80=A6=20(?= =?UTF-8?q?#1578)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enhance pool namespace resolution: use flag if set, else NAMESPACE env var, else default; add documentation for behavior * address comments * treat empty string as unset, use flag value if non-empty, else env var, else default * address comments * update doc * relocate document --- cmd/epp/runner/runner.go | 15 ++++++++-- site-src/guides/epp-configuration/flags.md | 33 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 site-src/guides/epp-configuration/flags.md diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go index 7d25fc7c7..61f235225 100644 --- a/cmd/epp/runner/runner.go +++ b/cmd/epp/runner/runner.go @@ -83,7 +83,7 @@ var ( enablePprof = flag.Bool("enable-pprof", runserver.DefaultEnablePprof, "Enables pprof handlers. Defaults to true. Set to false to disable pprof handlers.") poolName = flag.String("pool-name", runserver.DefaultPoolName, "Name of the InferencePool this Endpoint Picker is associated with.") poolGroup = flag.String("pool-group", runserver.DefaultPoolGroup, "group of the InferencePool this Endpoint Picker is associated with.") - poolNamespace = flag.String("pool-namespace", runserver.DefaultPoolNamespace, "Namespace of the InferencePool this Endpoint Picker is associated with.") + poolNamespace = flag.String("pool-namespace", "", "Namespace of the InferencePool this Endpoint Picker is associated with.") logVerbosity = flag.Int("v", logging.DEFAULT, "number for the log level verbosity") secureServing = flag.Bool("secure-serving", runserver.DefaultSecureServing, "Enables secure serving. Defaults to true.") healthChecking = flag.Bool("health-checking", runserver.DefaultHealthChecking, "Enables health checking") @@ -195,9 +195,20 @@ func (r *Runner) Run(ctx context.Context) error { FilterProvider: filters.WithAuthenticationAndAuthorization, } + // Determine pool namespace: if --pool-namespace is non-empty, use it; else NAMESPACE env var; else default + resolvePoolNamespace := func() string { + if *poolNamespace != "" { + return *poolNamespace + } + if nsEnv := os.Getenv("NAMESPACE"); nsEnv != "" { + return nsEnv + } + return runserver.DefaultPoolNamespace + } + resolvedPoolNamespace := resolvePoolNamespace() poolNamespacedName := types.NamespacedName{ Name: *poolName, - Namespace: *poolNamespace, + Namespace: resolvedPoolNamespace, } poolGroupKind := schema.GroupKind{ Group: *poolGroup, diff --git a/site-src/guides/epp-configuration/flags.md b/site-src/guides/epp-configuration/flags.md new file mode 100644 index 000000000..715466822 --- /dev/null +++ b/site-src/guides/epp-configuration/flags.md @@ -0,0 +1,33 @@ +# EPP Configuration Flags + +This page documents selected configuration flags for the Endpoint Picker (EPP) binary. Most flags are self-explanatory via their `--help` descriptions; only flags with nuanced or non-obvious behavior are detailed here. + +## --pool-namespace + +**Description:** +Specifies the namespace of the InferencePool this Endpoint Picker is associated with. + +**Resolution order:** +1. If `--pool-namespace` is set to a non-empty value, its value is used. +2. If the flag is not set (i.e., left empty), the `NAMESPACE` environment variable is checked. If set, its value is used. +3. If neither is set, the namespace defaults to `default`. + +This allows the EPP to automatically use the namespace it is running in (when the `NAMESPACE` env var is set via Kubernetes Downward API), without requiring explicit configuration. If you want to force the use of the default namespace, explicitly set `--pool-namespace=default`. If you want to use the environment variable or fallback, leave the flag unset or set it to an empty string. + +**Example manifest snippet to set the env var from pod metadata:** + +```yaml +env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +``` + +--- + +For a full list of flags, run: + +``` +EPP_BINARY --help +``` From ba9afc6bc613e4a13b6cb1c0b10123ab2e9be64d Mon Sep 17 00:00:00 2001 From: Kellen Swain Date: Mon, 15 Sep 2025 13:40:14 -0700 Subject: [PATCH 033/133] Scrubbing out more InferenceModel references (#1592) --- site-src/guides/serve-multiple-genai-models.md | 1 - site-src/images/inference-overview.svg | 2 +- site-src/images/serve-LoRA-adapters.png | Bin 379551 -> 0 bytes site-src/images/serve-mul-gen-AI-models.png | Bin 412887 -> 0 bytes 4 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 site-src/images/serve-LoRA-adapters.png delete mode 100644 site-src/images/serve-mul-gen-AI-models.png diff --git a/site-src/guides/serve-multiple-genai-models.md b/site-src/guides/serve-multiple-genai-models.md index a2e4e51d5..1d90767d0 100644 --- a/site-src/guides/serve-multiple-genai-models.md +++ b/site-src/guides/serve-multiple-genai-models.md @@ -12,7 +12,6 @@ The following diagram illustrates how an Inference Gateway routes requests to di The model name is extracted by [Body-Based routing](https://github.com/kubernetes-sigs/gateway-api-inference-extension/blob/main/pkg/bbr/README.md) (BBR) from the request body to the header. The header is then matched to dispatch requests to different `InferencePool` (and their EPPs) instances. -![Serving multiple generative AI models](../images/serve-mul-gen-AI-models.png) ### Deploy Body-Based Routing diff --git a/site-src/images/inference-overview.svg b/site-src/images/inference-overview.svg index a82c09e26..8524ebbea 100644 --- a/site-src/images/inference-overview.svg +++ b/site-src/images/inference-overview.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/site-src/images/serve-LoRA-adapters.png b/site-src/images/serve-LoRA-adapters.png deleted file mode 100644 index e33dc708ab67fd7ebce79f3a10dd84afb4654bb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 379551 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 M#GfU`vq=w@ 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