From c4b4d0068012f06980595437b3bc39c73cace8ef Mon Sep 17 00:00:00 2001 From: Derek Wang Date: Mon, 9 Sep 2024 10:09:07 -0700 Subject: [PATCH] feat: rolling update for Pipeline Vertex (#2040) Signed-off-by: Derek Wang --- api/json-schema/schema.json | 21 +- api/openapi-spec/swagger.json | 21 +- config/advanced-install/minimal-crds.yaml | 2 +- .../full/numaflow.numaproj.io_pipelines.yaml | 17 + .../full/numaflow.numaproj.io_vertices.yaml | 24 +- .../numaflow.numaproj.io_vertices.yaml | 2 +- config/install.yaml | 41 +- config/namespace-install.yaml | 41 +- docs/APIs.md | 59 +- .../configuration/sidecar-containers.md | 23 +- .../configuration/update-strategy.md | 60 + mkdocs.yml | 37 +- pkg/apis/numaflow/v1alpha1/generated.pb.go | 1119 +++++++++-------- pkg/apis/numaflow/v1alpha1/generated.proto | 37 +- .../v1alpha1/mono_vertex_types_test.go | 369 ++++++ .../numaflow/v1alpha1/openapi_generated.go | 38 +- pkg/apis/numaflow/v1alpha1/vertex_types.go | 48 +- .../numaflow/v1alpha1/vertex_types_test.go | 29 +- pkg/reconciler/monovertex/controller.go | 48 +- pkg/reconciler/monovertex/controller_test.go | 217 ++-- pkg/reconciler/pipeline/controller.go | 54 +- pkg/reconciler/pipeline/controller_test.go | 46 +- pkg/reconciler/pipeline/validate.go | 8 + pkg/reconciler/pipeline/validate_test.go | 41 + pkg/reconciler/vertex/controller.go | 419 ++++-- pkg/reconciler/vertex/controller_test.go | 354 ++++-- .../src/models/abstract_vertex.rs | 3 + .../numaflow-models/src/models/vertex_spec.rs | 3 + .../src/models/vertex_status.rs | 19 +- 29 files changed, 2205 insertions(+), 995 deletions(-) create mode 100644 docs/user-guide/reference/configuration/update-strategy.md diff --git a/api/json-schema/schema.json b/api/json-schema/schema.json index 44e18ef85a..bf1f28f594 100644 --- a/api/json-schema/schema.json +++ b/api/json-schema/schema.json @@ -17785,6 +17785,10 @@ "udf": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDF" }, + "updateStrategy": { + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UpdateStrategy", + "description": "The strategy to use to replace existing pods with new ones." + }, "volumes": { "items": { "$ref": "#/definitions/io.k8s.api.core.v1.Volume" @@ -20608,6 +20612,10 @@ "udf": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDF" }, + "updateStrategy": { + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UpdateStrategy", + "description": "The strategy to use to replace existing pods with new ones." + }, "volumes": { "items": { "$ref": "#/definitions/io.k8s.api.core.v1.Volume" @@ -20639,11 +20647,11 @@ "x-kubernetes-patch-strategy": "merge" }, "currentHash": { - "description": "If not empty, indicates the version of the Vertex used to generate Pods in the sequence [0,currentReplicas).", + "description": "If not empty, indicates the current version of the Vertex used to generate Pods.", "type": "string" }, - "currentReplicas": { - "description": "The number of Pods created by the controller from the Vertex version indicated by currentHash.", + "desiredReplicas": { + "description": "The number of desired replicas.", "format": "int64", "type": "integer" }, @@ -20679,9 +20687,14 @@ "type": "string" }, "updateHash": { - "description": "If not empty, indicates the version of the Vertx used to generate Pods in the sequence [replicas-updatedReplicas,replicas)", + "description": "If not empty, indicates the updated version of the Vertex used to generate Pods.", "type": "string" }, + "updatedReadyReplicas": { + "description": "The number of ready Pods created by the controller from the Vertex version indicated by updateHash.", + "format": "int64", + "type": "integer" + }, "updatedReplicas": { "description": "The number of Pods created by the controller from the Vertex version indicated by updateHash.", "format": "int64", diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 7067416291..91e6e43fb6 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -17793,6 +17793,10 @@ "udf": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDF" }, + "updateStrategy": { + "description": "The strategy to use to replace existing pods with new ones.", + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UpdateStrategy" + }, "volumes": { "type": "array", "items": { @@ -20590,6 +20594,10 @@ "udf": { "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDF" }, + "updateStrategy": { + "description": "The strategy to use to replace existing pods with new ones.", + "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UpdateStrategy" + }, "volumes": { "type": "array", "items": { @@ -20617,11 +20625,11 @@ "x-kubernetes-patch-strategy": "merge" }, "currentHash": { - "description": "If not empty, indicates the version of the Vertex used to generate Pods in the sequence [0,currentReplicas).", + "description": "If not empty, indicates the current version of the Vertex used to generate Pods.", "type": "string" }, - "currentReplicas": { - "description": "The number of Pods created by the controller from the Vertex version indicated by currentHash.", + "desiredReplicas": { + "description": "The number of desired replicas.", "type": "integer", "format": "int64" }, @@ -20657,9 +20665,14 @@ "type": "string" }, "updateHash": { - "description": "If not empty, indicates the version of the Vertx used to generate Pods in the sequence [replicas-updatedReplicas,replicas)", + "description": "If not empty, indicates the updated version of the Vertex used to generate Pods.", "type": "string" }, + "updatedReadyReplicas": { + "description": "The number of ready Pods created by the controller from the Vertex version indicated by updateHash.", + "type": "integer", + "format": "int64" + }, "updatedReplicas": { "description": "The number of Pods created by the controller from the Vertex version indicated by updateHash.", "type": "integer", diff --git a/config/advanced-install/minimal-crds.yaml b/config/advanced-install/minimal-crds.yaml index a8eac9fc22..edf1df3528 100644 --- a/config/advanced-install/minimal-crds.yaml +++ b/config/advanced-install/minimal-crds.yaml @@ -206,7 +206,7 @@ spec: - jsonPath: .status.phase name: Phase type: string - - jsonPath: .spec.replicas + - jsonPath: .status.desiredReplicas name: Desired type: string - jsonPath: .status.replicas diff --git a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml index 6fc509dc96..40db1f403e 100644 --- a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml +++ b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml @@ -9037,6 +9037,23 @@ spec: - window type: object type: object + updateStrategy: + default: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate + properties: + rollingUpdate: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object volumes: items: properties: diff --git a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml index e7bb52a8d8..1f02fd2b35 100644 --- a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml +++ b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml @@ -21,7 +21,7 @@ spec: - jsonPath: .status.phase name: Phase type: string - - jsonPath: .spec.replicas + - jsonPath: .status.desiredReplicas name: Desired type: string - jsonPath: .status.replicas @@ -4701,6 +4701,23 @@ spec: - window type: object type: object + updateStrategy: + default: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate + properties: + rollingUpdate: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object volumes: items: properties: @@ -5486,7 +5503,7 @@ spec: type: array currentHash: type: string - currentReplicas: + desiredReplicas: format: int32 type: integer lastScaledAt: @@ -5515,6 +5532,9 @@ spec: type: string updateHash: type: string + updatedReadyReplicas: + format: int32 + type: integer updatedReplicas: format: int32 type: integer diff --git a/config/base/crds/minimal/numaflow.numaproj.io_vertices.yaml b/config/base/crds/minimal/numaflow.numaproj.io_vertices.yaml index 68a95ee056..56799a7791 100644 --- a/config/base/crds/minimal/numaflow.numaproj.io_vertices.yaml +++ b/config/base/crds/minimal/numaflow.numaproj.io_vertices.yaml @@ -17,7 +17,7 @@ spec: - jsonPath: .status.phase name: Phase type: string - - jsonPath: .spec.replicas + - jsonPath: .status.desiredReplicas name: Desired type: string - jsonPath: .status.replicas diff --git a/config/install.yaml b/config/install.yaml index d19f2fa2f2..c3db75767a 100644 --- a/config/install.yaml +++ b/config/install.yaml @@ -17310,6 +17310,23 @@ spec: - window type: object type: object + updateStrategy: + default: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate + properties: + rollingUpdate: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object volumes: items: properties: @@ -18168,7 +18185,7 @@ spec: - jsonPath: .status.phase name: Phase type: string - - jsonPath: .spec.replicas + - jsonPath: .status.desiredReplicas name: Desired type: string - jsonPath: .status.replicas @@ -22848,6 +22865,23 @@ spec: - window type: object type: object + updateStrategy: + default: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate + properties: + rollingUpdate: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object volumes: items: properties: @@ -23633,7 +23667,7 @@ spec: type: array currentHash: type: string - currentReplicas: + desiredReplicas: format: int32 type: integer lastScaledAt: @@ -23662,6 +23696,9 @@ spec: type: string updateHash: type: string + updatedReadyReplicas: + format: int32 + type: integer updatedReplicas: format: int32 type: integer diff --git a/config/namespace-install.yaml b/config/namespace-install.yaml index 5919b0fcf8..5162dc6ad7 100644 --- a/config/namespace-install.yaml +++ b/config/namespace-install.yaml @@ -17310,6 +17310,23 @@ spec: - window type: object type: object + updateStrategy: + default: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate + properties: + rollingUpdate: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object volumes: items: properties: @@ -18168,7 +18185,7 @@ spec: - jsonPath: .status.phase name: Phase type: string - - jsonPath: .spec.replicas + - jsonPath: .status.desiredReplicas name: Desired type: string - jsonPath: .status.replicas @@ -22848,6 +22865,23 @@ spec: - window type: object type: object + updateStrategy: + default: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate + properties: + rollingUpdate: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object volumes: items: properties: @@ -23633,7 +23667,7 @@ spec: type: array currentHash: type: string - currentReplicas: + desiredReplicas: format: int32 type: integer lastScaledAt: @@ -23662,6 +23696,9 @@ spec: type: string updateHash: type: string + updatedReadyReplicas: + format: int32 + type: integer updatedReplicas: format: int32 type: integer diff --git a/docs/APIs.md b/docs/APIs.md index bd71cf0592..da6ab6eeb1 100644 --- a/docs/APIs.md +++ b/docs/APIs.md @@ -840,6 +840,27 @@ Container template for the side inputs watcher container. + + + + +updateStrategy
+ UpdateStrategy + + + + + +(Optional) +

+ +The strategy to use to replace existing pods with new ones. +

+ + + + + @@ -10804,6 +10825,7 @@ UpdateStrategy

(Appears on: +AbstractVertex, MonoVertexSpec)

@@ -11593,6 +11615,25 @@ labels match the selector). +desiredReplicas
uint32 + + + + +(Optional) +

+ +The number of desired replicas. +

+ + + + + + + + + selector
string @@ -11694,7 +11735,7 @@ The number of pods targeted by this Vertex with a Ready Condition. -currentReplicas
uint32 +updatedReplicas
uint32 @@ -11702,7 +11743,7 @@ The number of pods targeted by this Vertex with a Ready Condition.

The number of Pods created by the controller from the Vertex version -indicated by currentHash. +indicated by updateHash.

@@ -11713,15 +11754,15 @@ indicated by currentHash. -updatedReplicas
uint32 +updatedReadyReplicas
uint32

-The number of Pods created by the controller from the Vertex version -indicated by updateHash. +The number of ready Pods created by the controller from the Vertex +version indicated by updateHash.

@@ -11739,8 +11780,8 @@ indicated by updateHash.

-If not empty, indicates the version of the Vertex used to generate Pods -in the sequence \[0,currentReplicas). +If not empty, indicates the current version of the Vertex used to +generate Pods.

@@ -11758,8 +11799,8 @@ in the sequence \[0,currentReplicas).

-If not empty, indicates the version of the Vertx used to generate Pods -in the sequence \[replicas-updatedReplicas,replicas) +If not empty, indicates the updated version of the Vertex used to +generate Pods.

diff --git a/docs/user-guide/reference/configuration/sidecar-containers.md b/docs/user-guide/reference/configuration/sidecar-containers.md index 24aaadbcfe..72d07634c6 100644 --- a/docs/user-guide/reference/configuration/sidecar-containers.md +++ b/docs/user-guide/reference/configuration/sidecar-containers.md @@ -1,6 +1,6 @@ # Sidecar Containers -Additional "[sidecar](https://kubernetes.io/docs/concepts/workloads/pods/#how-pods-manage-multiple-containers)" containers can be provided for `udf` and `sink` vertices. `source` vertices do not currently support sidecars. +Additional "[sidecar](https://kubernetes.io/docs/concepts/workloads/pods/#how-pods-manage-multiple-containers)" containers can be provided for `source`, `udf` and `sink` vertices. The following example shows how to add a sidecar container to a `udf` vertex. @@ -15,7 +15,12 @@ spec: sidecars: - name: my-sidecar image: busybox:latest - command: ["/bin/sh", "-c", "echo \"my-sidecar is running!\" && tail -f /dev/null"] + command: + [ + "/bin/sh", + "-c", + 'echo "my-sidecar is running!" && tail -f /dev/null', + ] udf: container: image: my-function:latest @@ -42,14 +47,24 @@ spec: sidecars: - name: my-sidecar image: alpine:latest - command: ["/bin/sh", "-c", "apk add socat && socat UNIX-LISTEN:/path/to/my-sidecar-mount-path/my.sock - && tail -f /dev/null"] + command: + [ + "/bin/sh", + "-c", + "apk add socat && socat UNIX-LISTEN:/path/to/my-sidecar-mount-path/my.sock - && tail -f /dev/null", + ] volumeMounts: - mountPath: /path/to/my-sidecar-mount-path name: my-udf-volume udf: container: image: alpine:latest - command: ["/bin/sh", "-c", "apk add socat && echo \"hello\" | socat UNIX-CONNECT:/path/to/my-udf-mount-path/my.sock,forever - && tail -f /dev/null"] + command: + [ + "/bin/sh", + "-c", + 'apk add socat && echo "hello" | socat UNIX-CONNECT:/path/to/my-udf-mount-path/my.sock,forever - && tail -f /dev/null', + ] volumeMounts: - mountPath: /path/to/my-udf-mount-path name: my-udf-volume diff --git a/docs/user-guide/reference/configuration/update-strategy.md b/docs/user-guide/reference/configuration/update-strategy.md new file mode 100644 index 0000000000..e105be5c89 --- /dev/null +++ b/docs/user-guide/reference/configuration/update-strategy.md @@ -0,0 +1,60 @@ +# Update Strategy + +When spec changes, the `RollingUpdate` update strategy is used to update pods in a `Pipeline` or `MonoVertex` by default, which means that the update is done in a rolling fashion. The default configuration is as below. + +```yaml +updateStrategy: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate +``` + +- `maxUnavailable`: The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: `5`) or a percentage of total pods at the start of update (ex: `10%`). Absolute number is calculated from percentage by rounding up. Defaults to `25%`. + +## How It Works + +The `RollingUpdate` strategy in Numaflow works more like the `RollingUpdate` strategy in `StatefulSet` rather than `Deployment`. It does not create `maxUnavailable` new pods and wait for them to be ready before terminating the old pods. Instead it replaces `maxUnavailable` number of pods with the new spec, then waits for them to be ready before updating the next batch. + +For example, if there are 20 pods running, and `maxUnavailable` is set to the default `25%`, during the update, 5 pods will be unavailable at the same time. The update will be done in 4 batches. If your application has a long startup time, and you are sensitive to the unavailability caused tail latency, you should set `maxUnavailable` to a smaller value, and adjust the `scale.min` if it's needed. + +During rolling update, [autoscaling](../autoscaling.md) will not be triggered for that particular Vertex or MonoVertex. + +## Examples + +A `Pipeline` example. + +```yaml +apiVersion: numaflow.numaproj.io/v1alpha1 +kind: Pipeline +metadata: + name: simple-pipeline +spec: + vertices: + - name: my-vertex + updateStrategy: + rollingUpdate: + maxUnavailable: 25% + type: RollingUpdate +``` + +A `MonoVertex` example. + +```yaml +apiVersion: numaflow.numaproj.io/v1alpha1 +kind: MonoVertex +metadata: + name: my-mvtx +spec: + source: + udsource: + container: + image: my-image1 + sink: + udsink: + container: + image: my-image2 + updateStrategy: + rollingUpdate: + maxUnavailable: 2 + type: RollingUpdate +``` diff --git a/mkdocs.yml b/mkdocs.yml index 7e57a6af21..55dac8b1c1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -93,17 +93,18 @@ nav: - user-guide/reference/join-vertex.md - user-guide/reference/multi-partition.md - user-guide/reference/side-inputs.md - - Configuration: - - user-guide/reference/configuration/pod-specifications.md - - user-guide/reference/configuration/container-resources.md - - user-guide/reference/configuration/volumes.md - - user-guide/reference/configuration/environment-variables.md - - user-guide/reference/configuration/labels-and-annotations.md - - user-guide/reference/configuration/init-containers.md - - user-guide/reference/configuration/sidecar-containers.md - - user-guide/reference/configuration/pipeline-customization.md - - user-guide/reference/configuration/istio.md - - user-guide/reference/configuration/max-message-size.md + - Configuration: + - user-guide/reference/configuration/pod-specifications.md + - user-guide/reference/configuration/container-resources.md + - user-guide/reference/configuration/volumes.md + - user-guide/reference/configuration/environment-variables.md + - user-guide/reference/configuration/labels-and-annotations.md + - user-guide/reference/configuration/init-containers.md + - user-guide/reference/configuration/sidecar-containers.md + - user-guide/reference/configuration/pipeline-customization.md + - user-guide/reference/configuration/istio.md + - user-guide/reference/configuration/max-message-size.md + - user-guide/reference/configuration/update-strategy.md - user-guide/reference/kustomize/kustomize.md - APIs.md - Use Cases: @@ -116,15 +117,15 @@ nav: - Configuration: - Controller Configuration: "operations/controller-configmap.md" - UI Server: - - Access Path: "operations/ui/ui-access-path.md" - - Authentication: - - Overview: "operations/ui/authn/authentication.md" - - SSO with Dex: "operations/ui/authn/dex.md" - - Local Users: "operations/ui/authn/local-users.md" - - Authorization: "operations/ui/authz/rbac.md" + - Access Path: "operations/ui/ui-access-path.md" + - Authentication: + - Overview: "operations/ui/authn/authentication.md" + - SSO with Dex: "operations/ui/authn/dex.md" + - Local Users: "operations/ui/authn/local-users.md" + - Authorization: "operations/ui/authz/rbac.md" - operations/metrics/metrics.md - operations/grafana.md - - Security: operations/security.md + - Security: operations/security.md - Contributor Guide: - development/development.md - Specifications: diff --git a/pkg/apis/numaflow/v1alpha1/generated.pb.go b/pkg/apis/numaflow/v1alpha1/generated.pb.go index 5297014695..65ac7601e5 100644 --- a/pkg/apis/numaflow/v1alpha1/generated.pb.go +++ b/pkg/apis/numaflow/v1alpha1/generated.pb.go @@ -2822,502 +2822,501 @@ func init() { } var fileDescriptor_9d0d1b17d3865563 = []byte{ - // 7907 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x25, 0x47, - 0x76, 0x9e, 0xee, 0xff, 0xbd, 0xe7, 0xf2, 0x6f, 0x6a, 0x7e, 0xc4, 0x19, 0x8d, 0x86, 0xe3, 0x96, - 0x25, 0x8f, 0x63, 0x9b, 0x8c, 0x68, 0xfd, 0xad, 0xed, 0x5d, 0x89, 0x97, 0x1c, 0x72, 0x38, 0x43, - 0xce, 0x70, 0xcf, 0x25, 0x47, 0x5a, 0x2b, 0x5e, 0xa5, 0xd9, 0x5d, 0xbc, 0x6c, 0xb1, 0x6f, 0xf7, - 0x55, 0x77, 0x5f, 0xce, 0x50, 0x4e, 0xb0, 0xb6, 0x95, 0x40, 0x0a, 0x82, 0x20, 0x81, 0x9f, 0x0c, - 0x04, 0x4e, 0x90, 0x20, 0x80, 0x1f, 0x0c, 0xe7, 0x21, 0xc0, 0xe6, 0xc1, 0x40, 0xe2, 0x38, 0x08, - 0x92, 0x4d, 0x90, 0x9f, 0x45, 0x10, 0x20, 0xca, 0x0b, 0x91, 0x65, 0x90, 0x87, 0x04, 0x88, 0x61, - 0xc4, 0x48, 0xec, 0x0c, 0x16, 0xd9, 0xa0, 0xfe, 0xfa, 0xef, 0xf6, 0x9d, 0x21, 0x6f, 0x93, 0xa3, - 0x51, 0xac, 0xb7, 0xee, 0x3a, 0xa7, 0xbe, 0x53, 0x55, 0x5d, 0x5d, 0x75, 0xea, 0x9c, 0x53, 0x55, - 0xb0, 0xd2, 0xb1, 0x82, 0xdd, 0xfe, 0xf6, 0xac, 0xe1, 0x76, 0xe7, 0x9c, 0x7e, 0x57, 0xef, 0x79, - 0xee, 0x87, 0xfc, 0x61, 0xc7, 0x76, 0x1f, 0xcc, 0xf5, 0xf6, 0x3a, 0x73, 0x7a, 0xcf, 0xf2, 0xa3, - 0x94, 0xfd, 0x57, 0x75, 0xbb, 0xb7, 0xab, 0xbf, 0x3a, 0xd7, 0xa1, 0x0e, 0xf5, 0xf4, 0x80, 0x9a, - 0xb3, 0x3d, 0xcf, 0x0d, 0x5c, 0xf2, 0x66, 0x04, 0x34, 0xab, 0x80, 0x66, 0x55, 0xb6, 0xd9, 0xde, - 0x5e, 0x67, 0x96, 0x01, 0x45, 0x29, 0x0a, 0xe8, 0xca, 0xcf, 0xc4, 0x4a, 0xd0, 0x71, 0x3b, 0xee, - 0x1c, 0xc7, 0xdb, 0xee, 0xef, 0xf0, 0x37, 0xfe, 0xc2, 0x9f, 0x84, 0x9c, 0x2b, 0xda, 0xde, 0x5b, - 0xfe, 0xac, 0xe5, 0xb2, 0x62, 0xcd, 0x19, 0xae, 0x47, 0xe7, 0xf6, 0x07, 0xca, 0x72, 0xe5, 0xb5, - 0x88, 0xa7, 0xab, 0x1b, 0xbb, 0x96, 0x43, 0xbd, 0x03, 0x55, 0x97, 0x39, 0x8f, 0xfa, 0x6e, 0xdf, - 0x33, 0xe8, 0x89, 0x72, 0xf9, 0x73, 0x5d, 0x1a, 0xe8, 0x59, 0xb2, 0xe6, 0x86, 0xe5, 0xf2, 0xfa, - 0x4e, 0x60, 0x75, 0x07, 0xc5, 0xbc, 0xf1, 0xa4, 0x0c, 0xbe, 0xb1, 0x4b, 0xbb, 0xfa, 0x40, 0xbe, - 0x9f, 0x1d, 0x96, 0xaf, 0x1f, 0x58, 0xf6, 0x9c, 0xe5, 0x04, 0x7e, 0xe0, 0xa5, 0x33, 0x69, 0xbf, - 0x0f, 0x70, 0x7e, 0x61, 0xdb, 0x0f, 0x3c, 0xdd, 0x08, 0x36, 0x5c, 0x73, 0x93, 0x76, 0x7b, 0xb6, - 0x1e, 0x50, 0xb2, 0x07, 0x75, 0x56, 0x21, 0x53, 0x0f, 0xf4, 0xe9, 0xc2, 0xf5, 0xc2, 0x8d, 0xe6, - 0xfc, 0xc2, 0xec, 0x88, 0x1f, 0x70, 0x76, 0x5d, 0x02, 0xb5, 0xc6, 0x8e, 0x0e, 0x67, 0xea, 0xea, - 0x0d, 0x43, 0x01, 0xe4, 0x37, 0x0a, 0x30, 0xe6, 0xb8, 0x26, 0x6d, 0x53, 0x9b, 0x1a, 0x81, 0xeb, - 0x4d, 0x17, 0xaf, 0x97, 0x6e, 0x34, 0xe7, 0xbf, 0x3d, 0xb2, 0xc4, 0x8c, 0x1a, 0xcd, 0xde, 0x8d, - 0x09, 0xb8, 0xe9, 0x04, 0xde, 0x41, 0xeb, 0xc2, 0xf7, 0x0e, 0x67, 0x9e, 0x3b, 0x3a, 0x9c, 0x19, - 0x8b, 0x93, 0x30, 0x51, 0x12, 0xb2, 0x05, 0xcd, 0xc0, 0xb5, 0x59, 0x93, 0x59, 0xae, 0xe3, 0x4f, - 0x97, 0x78, 0xc1, 0xae, 0xcd, 0x8a, 0xa6, 0x66, 0xe2, 0x67, 0x59, 0x1f, 0x9b, 0xdd, 0x7f, 0x75, - 0x76, 0x33, 0x64, 0x6b, 0x9d, 0x97, 0xc0, 0xcd, 0x28, 0xcd, 0xc7, 0x38, 0x0e, 0xa1, 0x30, 0xe9, - 0x53, 0xa3, 0xef, 0x59, 0xc1, 0xc1, 0xa2, 0xeb, 0x04, 0xf4, 0x61, 0x30, 0x5d, 0xe6, 0xad, 0xfc, - 0x4a, 0x16, 0xf4, 0x86, 0x6b, 0xb6, 0x93, 0xdc, 0xad, 0xf3, 0x47, 0x87, 0x33, 0x93, 0xa9, 0x44, - 0x4c, 0x63, 0x12, 0x07, 0xa6, 0xac, 0xae, 0xde, 0xa1, 0x1b, 0x7d, 0xdb, 0x6e, 0x53, 0xc3, 0xa3, - 0x81, 0x3f, 0x5d, 0xe1, 0x55, 0xb8, 0x91, 0x25, 0x67, 0xcd, 0x35, 0x74, 0xfb, 0xde, 0xf6, 0x87, - 0xd4, 0x08, 0x90, 0xee, 0x50, 0x8f, 0x3a, 0x06, 0x6d, 0x4d, 0xcb, 0xca, 0x4c, 0xad, 0xa6, 0x90, - 0x70, 0x00, 0x9b, 0xac, 0xc0, 0xb9, 0x9e, 0x67, 0xb9, 0xbc, 0x08, 0xb6, 0xee, 0xfb, 0x77, 0xf5, - 0x2e, 0x9d, 0xae, 0x5e, 0x2f, 0xdc, 0x68, 0xb4, 0x2e, 0x4b, 0x98, 0x73, 0x1b, 0x69, 0x06, 0x1c, - 0xcc, 0x43, 0x6e, 0x40, 0x5d, 0x25, 0x4e, 0xd7, 0xae, 0x17, 0x6e, 0x54, 0x44, 0xdf, 0x51, 0x79, - 0x31, 0xa4, 0x92, 0x65, 0xa8, 0xeb, 0x3b, 0x3b, 0x96, 0xc3, 0x38, 0xeb, 0xbc, 0x09, 0xaf, 0x66, - 0x55, 0x6d, 0x41, 0xf2, 0x08, 0x1c, 0xf5, 0x86, 0x61, 0x5e, 0x72, 0x1b, 0x88, 0x4f, 0xbd, 0x7d, - 0xcb, 0xa0, 0x0b, 0x86, 0xe1, 0xf6, 0x9d, 0x80, 0x97, 0xbd, 0xc1, 0xcb, 0x7e, 0x45, 0x96, 0x9d, - 0xb4, 0x07, 0x38, 0x30, 0x23, 0x17, 0x79, 0x07, 0xa6, 0xe4, 0xbf, 0x1a, 0xb5, 0x02, 0x70, 0xa4, - 0x0b, 0xac, 0x21, 0x31, 0x45, 0xc3, 0x01, 0x6e, 0x62, 0xc2, 0x55, 0xbd, 0x1f, 0xb8, 0x5d, 0x06, - 0x99, 0x14, 0xba, 0xe9, 0xee, 0x51, 0x67, 0xba, 0x79, 0xbd, 0x70, 0xa3, 0xde, 0xba, 0x7e, 0x74, - 0x38, 0x73, 0x75, 0xe1, 0x31, 0x7c, 0xf8, 0x58, 0x14, 0x72, 0x0f, 0x1a, 0xa6, 0xe3, 0x6f, 0xb8, - 0xb6, 0x65, 0x1c, 0x4c, 0x8f, 0xf1, 0x02, 0xbe, 0x2a, 0xab, 0xda, 0x58, 0xba, 0xdb, 0x16, 0x84, - 0x47, 0x87, 0x33, 0x57, 0x07, 0x87, 0xd4, 0xd9, 0x90, 0x8e, 0x11, 0x06, 0x59, 0xe7, 0x80, 0x8b, - 0xae, 0xb3, 0x63, 0x75, 0xa6, 0xc7, 0xf9, 0xd7, 0xb8, 0x3e, 0xa4, 0x43, 0x2f, 0xdd, 0x6d, 0x0b, - 0xbe, 0xd6, 0xb8, 0x14, 0x27, 0x5e, 0x31, 0x42, 0x20, 0x26, 0x4c, 0xa8, 0xc1, 0x78, 0xd1, 0xd6, - 0xad, 0xae, 0x3f, 0x3d, 0xc1, 0x3b, 0xef, 0x8f, 0x0f, 0xc1, 0xc4, 0x38, 0x73, 0xeb, 0x92, 0xac, - 0xca, 0x44, 0x22, 0xd9, 0xc7, 0x14, 0xe6, 0x95, 0xb7, 0xe1, 0xdc, 0xc0, 0xd8, 0x40, 0xa6, 0xa0, - 0xb4, 0x47, 0x0f, 0xf8, 0xd0, 0xd7, 0x40, 0xf6, 0x48, 0x2e, 0x40, 0x65, 0x5f, 0xb7, 0xfb, 0x74, - 0xba, 0xc8, 0xd3, 0xc4, 0xcb, 0xcf, 0x15, 0xdf, 0x2a, 0x68, 0x7f, 0xb7, 0x04, 0x63, 0x6a, 0xc4, - 0x69, 0x5b, 0xce, 0x1e, 0x79, 0x17, 0x4a, 0xb6, 0xdb, 0x91, 0xe3, 0xe6, 0x2f, 0x8c, 0x3c, 0x8a, - 0xad, 0xb9, 0x9d, 0x56, 0xed, 0xe8, 0x70, 0xa6, 0xb4, 0xe6, 0x76, 0x90, 0x21, 0x12, 0x03, 0x2a, - 0x7b, 0xfa, 0xce, 0x9e, 0xce, 0xcb, 0xd0, 0x9c, 0x6f, 0x8d, 0x0c, 0x7d, 0x87, 0xa1, 0xb0, 0xb2, - 0xb6, 0x1a, 0x47, 0x87, 0x33, 0x15, 0xfe, 0x8a, 0x02, 0x9b, 0xb8, 0xd0, 0xd8, 0xb6, 0x75, 0x63, - 0x6f, 0xd7, 0xb5, 0xe9, 0x74, 0x29, 0xa7, 0xa0, 0x96, 0x42, 0x12, 0x9f, 0x39, 0x7c, 0xc5, 0x48, - 0x06, 0x31, 0xa0, 0xda, 0x37, 0x7d, 0xcb, 0xd9, 0x93, 0x63, 0xe0, 0xdb, 0x23, 0x4b, 0xdb, 0x5a, - 0xe2, 0x75, 0x82, 0xa3, 0xc3, 0x99, 0xaa, 0x78, 0x46, 0x09, 0xad, 0xfd, 0x41, 0x13, 0x26, 0xd4, - 0x47, 0xba, 0x4f, 0xbd, 0x80, 0x3e, 0x24, 0xd7, 0xa1, 0xec, 0xb0, 0x5f, 0x93, 0x7f, 0xe4, 0xd6, - 0x98, 0xec, 0x2e, 0x65, 0xfe, 0x4b, 0x72, 0x0a, 0x2b, 0x99, 0xe8, 0x2a, 0xb2, 0xc1, 0x47, 0x2f, - 0x59, 0x9b, 0xc3, 0x88, 0x92, 0x89, 0x67, 0x94, 0xd0, 0xe4, 0x7d, 0x28, 0xf3, 0xca, 0x8b, 0xa6, - 0xfe, 0xfa, 0xe8, 0x22, 0x58, 0xd5, 0xeb, 0xac, 0x06, 0xbc, 0xe2, 0x1c, 0x94, 0x75, 0xc5, 0xbe, - 0xb9, 0x23, 0x1b, 0xf6, 0x17, 0x72, 0x34, 0xec, 0xb2, 0xe8, 0x8a, 0x5b, 0x4b, 0xcb, 0xc8, 0x10, - 0xc9, 0x5f, 0x2f, 0xc0, 0x39, 0xc3, 0x75, 0x02, 0x9d, 0xe9, 0x19, 0x6a, 0x92, 0x9d, 0xae, 0x70, - 0x39, 0xb7, 0x47, 0x96, 0xb3, 0x98, 0x46, 0x6c, 0x5d, 0x64, 0x73, 0xc6, 0x40, 0x32, 0x0e, 0xca, - 0x26, 0x7f, 0xb3, 0x00, 0x17, 0xd9, 0x58, 0x3e, 0xc0, 0xcc, 0x67, 0xa0, 0xd3, 0x2d, 0xd5, 0xe5, - 0xa3, 0xc3, 0x99, 0x8b, 0xab, 0x59, 0xc2, 0x30, 0xbb, 0x0c, 0xac, 0x74, 0xe7, 0xf5, 0x41, 0xb5, - 0x84, 0xcf, 0x6e, 0xcd, 0xf9, 0xb5, 0xd3, 0x54, 0x75, 0x5a, 0x2f, 0xc8, 0xae, 0x9c, 0xa5, 0xd9, - 0x61, 0x56, 0x29, 0xc8, 0x4d, 0xa8, 0xed, 0xbb, 0x76, 0xbf, 0x4b, 0xfd, 0xe9, 0x3a, 0x1f, 0x62, - 0xaf, 0x64, 0x0d, 0xb1, 0xf7, 0x39, 0x4b, 0x6b, 0x52, 0xc2, 0xd7, 0xc4, 0xbb, 0x8f, 0x2a, 0x2f, - 0xb1, 0xa0, 0x6a, 0x5b, 0x5d, 0x2b, 0xf0, 0xf9, 0xc4, 0xd9, 0x9c, 0xbf, 0x39, 0x72, 0xb5, 0xc4, - 0x2f, 0xba, 0xc6, 0xc1, 0xc4, 0x5f, 0x23, 0x9e, 0x51, 0x0a, 0x60, 0x43, 0xa1, 0x6f, 0xe8, 0xb6, - 0x98, 0x58, 0x9b, 0xf3, 0xdf, 0x18, 0xfd, 0xb7, 0x61, 0x28, 0xad, 0x71, 0x59, 0xa7, 0x0a, 0x7f, - 0x45, 0x81, 0x4d, 0x7e, 0x09, 0x26, 0x12, 0x5f, 0xd3, 0x9f, 0x6e, 0xf2, 0xd6, 0x79, 0x31, 0xab, - 0x75, 0x42, 0xae, 0x68, 0xe6, 0x49, 0xf4, 0x10, 0x1f, 0x53, 0x60, 0xe4, 0x0e, 0xd4, 0x7d, 0xcb, - 0xa4, 0x86, 0xee, 0xf9, 0xd3, 0x63, 0xc7, 0x01, 0x9e, 0x92, 0xc0, 0xf5, 0xb6, 0xcc, 0x86, 0x21, - 0x00, 0x99, 0x05, 0xe8, 0xe9, 0x5e, 0x60, 0x09, 0x45, 0x75, 0x9c, 0x2b, 0x4d, 0x13, 0x47, 0x87, - 0x33, 0xb0, 0x11, 0xa6, 0x62, 0x8c, 0x83, 0xf1, 0xb3, 0xbc, 0xab, 0x4e, 0xaf, 0x1f, 0x88, 0x89, - 0xb5, 0x21, 0xf8, 0xdb, 0x61, 0x2a, 0xc6, 0x38, 0xc8, 0xef, 0x14, 0xe0, 0x85, 0xe8, 0x75, 0xf0, - 0x27, 0x9b, 0x3c, 0xf5, 0x9f, 0x6c, 0xe6, 0xe8, 0x70, 0xe6, 0x85, 0xf6, 0x70, 0x91, 0xf8, 0xb8, - 0xf2, 0x68, 0xef, 0xc2, 0xf8, 0x42, 0x3f, 0xd8, 0x75, 0x3d, 0xeb, 0x63, 0xae, 0x74, 0x93, 0x65, - 0xa8, 0x04, 0x5c, 0x79, 0x12, 0xf3, 0xf2, 0xcb, 0x59, 0x4d, 0x2d, 0x14, 0xd9, 0x3b, 0xf4, 0x40, - 0x69, 0x03, 0x62, 0x7e, 0x14, 0xca, 0x94, 0xc8, 0xae, 0xfd, 0xa5, 0x02, 0xd4, 0x5a, 0xba, 0xb1, - 0xe7, 0xee, 0xec, 0x90, 0xf7, 0xa0, 0x6e, 0x39, 0x01, 0xf5, 0xf6, 0x75, 0x5b, 0xc2, 0xce, 0xc6, - 0x60, 0xc3, 0x65, 0x58, 0x54, 0x6f, 0xb6, 0xe6, 0x61, 0x82, 0x96, 0xfa, 0x72, 0xad, 0xc0, 0xf5, - 0xd1, 0x55, 0x89, 0x81, 0x21, 0x1a, 0x99, 0x81, 0x8a, 0x1f, 0xd0, 0x9e, 0xcf, 0x67, 0x9e, 0x71, - 0x51, 0x8c, 0x36, 0x4b, 0x40, 0x91, 0xae, 0xfd, 0x9d, 0x02, 0x34, 0x5a, 0xba, 0x6f, 0x19, 0xac, - 0x96, 0x64, 0x11, 0xca, 0x7d, 0x9f, 0x7a, 0x27, 0xab, 0x1b, 0x9f, 0x2c, 0xb6, 0x7c, 0xea, 0x21, - 0xcf, 0x4c, 0xee, 0x41, 0xbd, 0xa7, 0xfb, 0xfe, 0x03, 0xd7, 0x33, 0xe5, 0x84, 0x77, 0x4c, 0x20, - 0xa1, 0x9c, 0xcb, 0xac, 0x18, 0x82, 0x68, 0x4d, 0x88, 0x66, 0x7c, 0xed, 0x8f, 0x0a, 0x70, 0xbe, - 0xd5, 0xdf, 0xd9, 0xa1, 0x9e, 0xd4, 0x45, 0xa5, 0x96, 0x47, 0xa1, 0xe2, 0x51, 0xd3, 0xf2, 0x65, - 0xd9, 0x97, 0x46, 0xee, 0x41, 0xc8, 0x50, 0xa4, 0x52, 0xc9, 0xdb, 0x8b, 0x27, 0xa0, 0x40, 0x27, - 0x7d, 0x68, 0x7c, 0x48, 0xd9, 0x1a, 0x98, 0xea, 0x5d, 0x59, 0xbb, 0x5b, 0x23, 0x8b, 0xba, 0x4d, - 0x83, 0x36, 0x47, 0x8a, 0xeb, 0xb0, 0x61, 0x22, 0x46, 0x92, 0xb4, 0xdf, 0xaf, 0xc0, 0xd8, 0xa2, - 0xdb, 0xdd, 0xb6, 0x1c, 0x6a, 0xde, 0x34, 0x3b, 0x94, 0x7c, 0x00, 0x65, 0x6a, 0x76, 0xa8, 0xac, - 0xed, 0xe8, 0xd3, 0x3d, 0x03, 0x8b, 0x94, 0x16, 0xf6, 0x86, 0x1c, 0x98, 0xac, 0xc1, 0xc4, 0x8e, - 0xe7, 0x76, 0xc5, 0x08, 0xba, 0x79, 0xd0, 0x93, 0x1a, 0x6b, 0xeb, 0xc7, 0xd5, 0xa8, 0xb4, 0x9c, - 0xa0, 0x3e, 0x3a, 0x9c, 0x81, 0xe8, 0x0d, 0x53, 0x79, 0xc9, 0x7b, 0x30, 0x1d, 0xa5, 0x84, 0x43, - 0xc9, 0x22, 0x5b, 0x44, 0x70, 0x8d, 0xa5, 0xd2, 0xba, 0x7a, 0x74, 0x38, 0x33, 0xbd, 0x3c, 0x84, - 0x07, 0x87, 0xe6, 0x26, 0x9f, 0x16, 0x60, 0x2a, 0x22, 0x8a, 0xe1, 0x5d, 0x2a, 0x2a, 0xa7, 0x34, - 0x6f, 0xf0, 0xd5, 0xd6, 0x72, 0x4a, 0x04, 0x0e, 0x08, 0x25, 0xcb, 0x30, 0x16, 0xb8, 0xb1, 0xf6, - 0xaa, 0xf0, 0xf6, 0xd2, 0x94, 0x79, 0x60, 0xd3, 0x1d, 0xda, 0x5a, 0x89, 0x7c, 0x04, 0xe1, 0x92, - 0x7a, 0x4f, 0xb5, 0x54, 0x95, 0xb7, 0xd4, 0x95, 0xa3, 0xc3, 0x99, 0x4b, 0x9b, 0x99, 0x1c, 0x38, - 0x24, 0x27, 0xf9, 0xd5, 0x02, 0x4c, 0x28, 0x92, 0x6c, 0xa3, 0xda, 0x69, 0xb6, 0x11, 0x61, 0x3d, - 0x62, 0x33, 0x21, 0x00, 0x53, 0x02, 0xb5, 0x3f, 0x29, 0x43, 0x23, 0x1c, 0x60, 0xc9, 0x4b, 0x50, - 0xe1, 0x0b, 0x7f, 0xa9, 0x37, 0x87, 0x33, 0x27, 0xb7, 0x0f, 0xa0, 0xa0, 0x91, 0x97, 0xa1, 0x66, - 0xb8, 0xdd, 0xae, 0xee, 0x98, 0xdc, 0x98, 0xd3, 0x68, 0x35, 0x99, 0xc2, 0xb0, 0x28, 0x92, 0x50, - 0xd1, 0xc8, 0x55, 0x28, 0xeb, 0x5e, 0x47, 0xd8, 0x55, 0x1a, 0x62, 0x3c, 0x5a, 0xf0, 0x3a, 0x3e, - 0xf2, 0x54, 0xf2, 0x35, 0x28, 0x51, 0x67, 0x7f, 0xba, 0x3c, 0x5c, 0x23, 0xb9, 0xe9, 0xec, 0xdf, - 0xd7, 0xbd, 0x56, 0x53, 0x96, 0xa1, 0x74, 0xd3, 0xd9, 0x47, 0x96, 0x87, 0xac, 0x41, 0x8d, 0x3a, - 0xfb, 0xec, 0xdb, 0x4b, 0x83, 0xc7, 0x8f, 0x0d, 0xc9, 0xce, 0x58, 0xa4, 0x72, 0x1e, 0xea, 0x35, - 0x32, 0x19, 0x15, 0x04, 0xf9, 0x16, 0x8c, 0x09, 0x15, 0x67, 0x9d, 0x7d, 0x13, 0x7f, 0xba, 0xca, - 0x21, 0x67, 0x86, 0xeb, 0x48, 0x9c, 0x2f, 0x32, 0x30, 0xc5, 0x12, 0x7d, 0x4c, 0x40, 0x91, 0x6f, - 0x41, 0x43, 0xad, 0x47, 0xd5, 0x97, 0xcd, 0xb4, 0xcd, 0xa8, 0x45, 0x2c, 0xd2, 0x8f, 0xfa, 0x96, - 0x47, 0xbb, 0xd4, 0x09, 0xfc, 0xd6, 0x39, 0xb5, 0x5a, 0x57, 0x54, 0x1f, 0x23, 0x34, 0xb2, 0x3d, - 0x68, 0x64, 0x12, 0x16, 0x92, 0x97, 0x86, 0x8c, 0xea, 0x23, 0x58, 0x98, 0xbe, 0x0d, 0x93, 0xa1, - 0x15, 0x48, 0x1a, 0x12, 0x84, 0xcd, 0xe4, 0x35, 0x96, 0x7d, 0x35, 0x49, 0x7a, 0x74, 0x38, 0xf3, - 0x62, 0x86, 0x29, 0x21, 0x62, 0xc0, 0x34, 0x98, 0xf6, 0x7b, 0x25, 0x18, 0xd4, 0xfe, 0x93, 0x8d, - 0x56, 0x38, 0xed, 0x46, 0x4b, 0x57, 0x48, 0x0c, 0x9f, 0x6f, 0xc9, 0x6c, 0xf9, 0x2b, 0x95, 0xf5, - 0x61, 0x4a, 0xa7, 0xfd, 0x61, 0x9e, 0x95, 0x7f, 0x47, 0xfb, 0xac, 0x0c, 0x13, 0x4b, 0x3a, 0xed, - 0xba, 0xce, 0x13, 0xd7, 0x42, 0x85, 0x67, 0x62, 0x2d, 0x74, 0x03, 0xea, 0x1e, 0xed, 0xd9, 0x96, - 0xa1, 0x0b, 0xe5, 0x4b, 0xda, 0x1e, 0x51, 0xa6, 0x61, 0x48, 0x1d, 0xb2, 0x06, 0x2e, 0x3d, 0x93, - 0x6b, 0xe0, 0xf2, 0x17, 0xbf, 0x06, 0xd6, 0x7e, 0xb5, 0x08, 0x5c, 0x51, 0x21, 0xd7, 0xa1, 0xcc, - 0x26, 0xe1, 0xb4, 0xe5, 0x85, 0x77, 0x1c, 0x4e, 0x21, 0x57, 0xa0, 0x18, 0xb8, 0xf2, 0xcf, 0x03, - 0x49, 0x2f, 0x6e, 0xba, 0x58, 0x0c, 0x5c, 0xf2, 0x31, 0x80, 0xe1, 0x3a, 0xa6, 0xa5, 0x4c, 0xf2, - 0xf9, 0x2a, 0xb6, 0xec, 0x7a, 0x0f, 0x74, 0xcf, 0x5c, 0x0c, 0x11, 0xc5, 0x2a, 0x28, 0x7a, 0xc7, - 0x98, 0x34, 0xf2, 0x36, 0x54, 0x5d, 0x67, 0xb9, 0x6f, 0xdb, 0xbc, 0x41, 0x1b, 0xad, 0x9f, 0x60, - 0x4b, 0xd3, 0x7b, 0x3c, 0xe5, 0xd1, 0xe1, 0xcc, 0x65, 0xa1, 0xdf, 0xb2, 0xb7, 0x77, 0x3d, 0x2b, - 0xb0, 0x9c, 0x4e, 0x3b, 0xf0, 0xf4, 0x80, 0x76, 0x0e, 0x50, 0x66, 0xd3, 0x7e, 0xbd, 0x00, 0xcd, - 0x65, 0xeb, 0x21, 0x35, 0xdf, 0xb5, 0x1c, 0xd3, 0x7d, 0x40, 0x10, 0xaa, 0x36, 0x75, 0x3a, 0xc1, - 0xee, 0x88, 0xeb, 0x07, 0xb1, 0x36, 0xe6, 0x08, 0x28, 0x91, 0xc8, 0x1c, 0x34, 0x84, 0xf6, 0x69, - 0x39, 0x1d, 0xde, 0x86, 0xf5, 0x68, 0xd0, 0x6b, 0x2b, 0x02, 0x46, 0x3c, 0xda, 0x01, 0x9c, 0x1b, - 0x68, 0x06, 0x62, 0x42, 0x39, 0xd0, 0x3b, 0x6a, 0x7c, 0x5d, 0x1e, 0xb9, 0x81, 0x37, 0xf5, 0x4e, - 0xac, 0x71, 0xf9, 0x1c, 0xbf, 0xa9, 0xb3, 0x39, 0x9e, 0xa1, 0x6b, 0x3f, 0x2c, 0x40, 0x7d, 0xb9, - 0xef, 0x18, 0x7c, 0x89, 0xf6, 0x64, 0x8b, 0x9c, 0x52, 0x18, 0x8a, 0x99, 0x0a, 0x43, 0x1f, 0xaa, - 0x7b, 0x0f, 0x42, 0x85, 0xa2, 0x39, 0xbf, 0x3e, 0x7a, 0xaf, 0x90, 0x45, 0x9a, 0xbd, 0xc3, 0xf1, - 0x84, 0xc3, 0x68, 0x42, 0x16, 0xa8, 0x7a, 0xe7, 0x5d, 0x2e, 0x54, 0x0a, 0xbb, 0xf2, 0x35, 0x68, - 0xc6, 0xd8, 0x4e, 0x64, 0x3b, 0xfe, 0x87, 0x65, 0xa8, 0xae, 0xb4, 0xdb, 0x0b, 0x1b, 0xab, 0xe4, - 0x75, 0x68, 0x4a, 0x5f, 0xc2, 0xdd, 0xa8, 0x0d, 0x42, 0x57, 0x52, 0x3b, 0x22, 0x61, 0x9c, 0x8f, - 0xa9, 0x63, 0x1e, 0xd5, 0xed, 0xae, 0xfc, 0x59, 0x42, 0x75, 0x0c, 0x59, 0x22, 0x0a, 0x1a, 0xd1, - 0x61, 0x82, 0xad, 0xf0, 0x58, 0x13, 0x8a, 0xd5, 0x9b, 0xfc, 0x6d, 0x8e, 0xb9, 0xbe, 0xe3, 0x4a, - 0xe2, 0x56, 0x02, 0x00, 0x53, 0x80, 0xe4, 0x2d, 0xa8, 0xeb, 0xfd, 0x60, 0x97, 0x2b, 0xd0, 0xe2, - 0xdf, 0xb8, 0xca, 0x5d, 0x2d, 0x32, 0xed, 0xd1, 0xe1, 0xcc, 0xd8, 0x1d, 0x6c, 0xbd, 0xae, 0xde, - 0x31, 0xe4, 0x66, 0x85, 0x53, 0x2b, 0x46, 0x59, 0xb8, 0xca, 0x89, 0x0b, 0xb7, 0x91, 0x00, 0xc0, - 0x14, 0x20, 0x79, 0x1f, 0xc6, 0xf6, 0xe8, 0x41, 0xa0, 0x6f, 0x4b, 0x01, 0xd5, 0x93, 0x08, 0x98, - 0x62, 0x2a, 0xdc, 0x9d, 0x58, 0x76, 0x4c, 0x80, 0x11, 0x1f, 0x2e, 0xec, 0x51, 0x6f, 0x9b, 0x7a, - 0xae, 0x5c, 0x7d, 0x4a, 0x21, 0xb5, 0x93, 0x08, 0x99, 0x3e, 0x3a, 0x9c, 0xb9, 0x70, 0x27, 0x03, - 0x06, 0x33, 0xc1, 0xb5, 0xff, 0x53, 0x84, 0xc9, 0x15, 0xe1, 0xcc, 0x75, 0x3d, 0x31, 0x09, 0x93, - 0xcb, 0x50, 0xf2, 0x7a, 0x7d, 0xde, 0x73, 0x4a, 0xc2, 0x5c, 0x8b, 0x1b, 0x5b, 0xc8, 0xd2, 0xc8, - 0x7b, 0x50, 0x37, 0xe5, 0x90, 0x21, 0x17, 0xbf, 0x23, 0x19, 0x2a, 0xd4, 0x1b, 0x86, 0x68, 0x4c, - 0xd3, 0xef, 0xfa, 0x9d, 0xb6, 0xf5, 0x31, 0x95, 0xeb, 0x41, 0xae, 0xe9, 0xaf, 0x8b, 0x24, 0x54, - 0x34, 0x36, 0xab, 0xee, 0xd1, 0x03, 0xb1, 0x1a, 0x2a, 0x47, 0xb3, 0xea, 0x1d, 0x99, 0x86, 0x21, - 0x95, 0xcc, 0xa8, 0x9f, 0x85, 0xf5, 0x82, 0xb2, 0x58, 0xc9, 0xdf, 0x67, 0x09, 0xf2, 0xbf, 0x61, - 0x43, 0xe6, 0x87, 0x56, 0x10, 0x50, 0x4f, 0x7e, 0xc6, 0x91, 0x86, 0xcc, 0xdb, 0x1c, 0x01, 0x25, - 0x12, 0xf9, 0x29, 0x68, 0x70, 0xf0, 0x96, 0xed, 0x6e, 0xf3, 0x0f, 0xd7, 0x10, 0x6b, 0xfa, 0xfb, - 0x2a, 0x11, 0x23, 0xba, 0xf6, 0xa3, 0x22, 0x5c, 0x5a, 0xa1, 0x81, 0xd0, 0x6a, 0x96, 0x68, 0xcf, - 0x76, 0x0f, 0x98, 0x6a, 0x89, 0xf4, 0x23, 0xf2, 0x0e, 0x80, 0xe5, 0x6f, 0xb7, 0xf7, 0x0d, 0xfe, - 0x1f, 0x88, 0x7f, 0xf8, 0xba, 0xfc, 0x25, 0x61, 0xb5, 0xdd, 0x92, 0x94, 0x47, 0x89, 0x37, 0x8c, - 0xe5, 0x89, 0x96, 0x57, 0xc5, 0xc7, 0x2c, 0xaf, 0xda, 0x00, 0xbd, 0x48, 0x41, 0x2d, 0x71, 0xce, - 0x9f, 0x55, 0x62, 0x4e, 0xa2, 0x9b, 0xc6, 0x60, 0xf2, 0xa8, 0x8c, 0x0e, 0x4c, 0x99, 0x74, 0x47, - 0xef, 0xdb, 0x41, 0xa8, 0x54, 0xcb, 0x9f, 0xf8, 0xf8, 0x7a, 0x79, 0xe8, 0x68, 0x5e, 0x4a, 0x21, - 0xe1, 0x00, 0xb6, 0xf6, 0xbb, 0x25, 0xb8, 0xb2, 0x42, 0x83, 0xd0, 0xe2, 0x22, 0x47, 0xc7, 0x76, - 0x8f, 0x1a, 0xec, 0x2b, 0x7c, 0x5a, 0x80, 0xaa, 0xad, 0x6f, 0x53, 0x9b, 0xcd, 0x5e, 0xac, 0x36, - 0x1f, 0x8c, 0x3c, 0x11, 0x0c, 0x97, 0x32, 0xbb, 0xc6, 0x25, 0xa4, 0xa6, 0x06, 0x91, 0x88, 0x52, - 0x3c, 0x1b, 0xd4, 0x0d, 0xbb, 0xef, 0x07, 0xd4, 0xdb, 0x70, 0xbd, 0x40, 0xea, 0x93, 0xe1, 0xa0, - 0xbe, 0x18, 0x91, 0x30, 0xce, 0x47, 0xe6, 0x01, 0x0c, 0xdb, 0xa2, 0x4e, 0xc0, 0x73, 0x89, 0xff, - 0x8a, 0xa8, 0xef, 0xbb, 0x18, 0x52, 0x30, 0xc6, 0xc5, 0x44, 0x75, 0x5d, 0xc7, 0x0a, 0x5c, 0x21, - 0xaa, 0x9c, 0x14, 0xb5, 0x1e, 0x91, 0x30, 0xce, 0xc7, 0xb3, 0xd1, 0xc0, 0xb3, 0x0c, 0x9f, 0x67, - 0xab, 0xa4, 0xb2, 0x45, 0x24, 0x8c, 0xf3, 0xb1, 0x39, 0x2f, 0x56, 0xff, 0x13, 0xcd, 0x79, 0xbf, - 0xdd, 0x80, 0x6b, 0x89, 0x66, 0x0d, 0xf4, 0x80, 0xee, 0xf4, 0xed, 0x36, 0x0d, 0xd4, 0x07, 0x1c, - 0x71, 0x2e, 0xfc, 0xab, 0xd1, 0x77, 0x17, 0x21, 0x24, 0xc6, 0xe9, 0x7c, 0xf7, 0x81, 0x02, 0x1e, - 0xeb, 0xdb, 0xcf, 0x41, 0xc3, 0xd1, 0x03, 0x9f, 0xff, 0xb8, 0xf2, 0x1f, 0x0d, 0xd5, 0xb0, 0xbb, - 0x8a, 0x80, 0x11, 0x0f, 0xd9, 0x80, 0x0b, 0xb2, 0x89, 0x6f, 0x3e, 0xec, 0xb9, 0x5e, 0x40, 0x3d, - 0x91, 0x57, 0x4e, 0xa7, 0x32, 0xef, 0x85, 0xf5, 0x0c, 0x1e, 0xcc, 0xcc, 0x49, 0xd6, 0xe1, 0xbc, - 0x21, 0xdc, 0xea, 0xd4, 0x76, 0x75, 0x53, 0x01, 0x0a, 0x03, 0x57, 0xb8, 0x34, 0x5a, 0x1c, 0x64, - 0xc1, 0xac, 0x7c, 0xe9, 0xde, 0x5c, 0x1d, 0xa9, 0x37, 0xd7, 0x46, 0xe9, 0xcd, 0xf5, 0xd1, 0x7a, - 0x73, 0xe3, 0x78, 0xbd, 0x99, 0xb5, 0x3c, 0xeb, 0x47, 0xd4, 0x63, 0xea, 0x89, 0x98, 0x61, 0x63, - 0x51, 0x1b, 0x61, 0xcb, 0xb7, 0x33, 0x78, 0x30, 0x33, 0x27, 0xd9, 0x86, 0x2b, 0x22, 0xfd, 0xa6, - 0x63, 0x78, 0x07, 0x3d, 0x36, 0xf1, 0xc4, 0x70, 0x9b, 0x09, 0x0b, 0xe3, 0x95, 0xf6, 0x50, 0x4e, - 0x7c, 0x0c, 0x0a, 0xf9, 0x79, 0x18, 0x17, 0x5f, 0x69, 0x5d, 0xef, 0x71, 0x58, 0x11, 0xc3, 0x71, - 0x51, 0xc2, 0x8e, 0x2f, 0xc6, 0x89, 0x98, 0xe4, 0x25, 0x0b, 0x30, 0xd9, 0xdb, 0x37, 0xd8, 0xe3, - 0xea, 0xce, 0x5d, 0x4a, 0x4d, 0x6a, 0x72, 0xa7, 0x51, 0xa3, 0xf5, 0xbc, 0x32, 0x74, 0x6c, 0x24, - 0xc9, 0x98, 0xe6, 0x27, 0x6f, 0xc1, 0x98, 0x1f, 0xe8, 0x5e, 0x20, 0xcd, 0x7a, 0xd3, 0x13, 0x22, - 0xc6, 0x45, 0x59, 0xbd, 0xda, 0x31, 0x1a, 0x26, 0x38, 0x33, 0xe7, 0x8b, 0xc9, 0xb3, 0x9b, 0x2f, - 0xf2, 0x8c, 0x56, 0xff, 0xa2, 0x08, 0xd7, 0x57, 0x68, 0xb0, 0xee, 0x3a, 0xd2, 0x28, 0x9a, 0x35, - 0xed, 0x1f, 0xcb, 0x26, 0x9a, 0x9c, 0xb4, 0x8b, 0xa7, 0x3a, 0x69, 0x97, 0x4e, 0x69, 0xd2, 0x2e, - 0x9f, 0xe1, 0xa4, 0xfd, 0x8f, 0x8b, 0xf0, 0x7c, 0xa2, 0x25, 0x37, 0x5c, 0x53, 0x0d, 0xf8, 0x5f, - 0x35, 0xe0, 0x31, 0x1a, 0xf0, 0x91, 0xd0, 0x3b, 0xb9, 0x5b, 0x2b, 0xa5, 0xf1, 0x7c, 0x92, 0xd6, - 0x78, 0xde, 0xcf, 0x33, 0xf3, 0x65, 0x48, 0x38, 0xd6, 0x8c, 0x77, 0x1b, 0x88, 0x27, 0x9d, 0x70, - 0xc2, 0xf4, 0x13, 0x53, 0x7a, 0xc2, 0x20, 0x3a, 0x1c, 0xe0, 0xc0, 0x8c, 0x5c, 0xa4, 0x0d, 0x17, - 0x7d, 0xea, 0x04, 0x96, 0x43, 0xed, 0x24, 0x9c, 0xd0, 0x86, 0x5e, 0x94, 0x70, 0x17, 0xdb, 0x59, - 0x4c, 0x98, 0x9d, 0x37, 0xcf, 0x38, 0xf0, 0xaf, 0x81, 0xab, 0x9c, 0xa2, 0x69, 0x4e, 0x4d, 0x63, - 0xf9, 0x34, 0xad, 0xb1, 0x7c, 0x90, 0xff, 0xbb, 0x8d, 0xa6, 0xad, 0xcc, 0x03, 0xf0, 0xaf, 0x10, - 0x57, 0x57, 0xc2, 0x49, 0x1a, 0x43, 0x0a, 0xc6, 0xb8, 0xd8, 0x04, 0xa4, 0xda, 0x39, 0xae, 0xa9, - 0x84, 0x13, 0x50, 0x3b, 0x4e, 0xc4, 0x24, 0xef, 0x50, 0x6d, 0xa7, 0x32, 0xb2, 0xb6, 0x73, 0x1b, - 0x48, 0xc2, 0xf0, 0x28, 0xf0, 0xaa, 0xc9, 0x18, 0xce, 0xd5, 0x01, 0x0e, 0xcc, 0xc8, 0x35, 0xa4, - 0x2b, 0xd7, 0x4e, 0xb7, 0x2b, 0xd7, 0x47, 0xef, 0xca, 0xe4, 0x03, 0xb8, 0xcc, 0x45, 0xc9, 0xf6, - 0x49, 0x02, 0x0b, 0xbd, 0xe7, 0xc7, 0x24, 0xf0, 0x65, 0x1c, 0xc6, 0x88, 0xc3, 0x31, 0xd8, 0xf7, - 0x31, 0x3c, 0x6a, 0x32, 0xe1, 0xba, 0x3d, 0x5c, 0x27, 0x5a, 0xcc, 0xe0, 0xc1, 0xcc, 0x9c, 0xac, - 0x8b, 0x05, 0xac, 0x1b, 0xea, 0xdb, 0x36, 0x35, 0x65, 0x0c, 0x6b, 0xd8, 0xc5, 0x36, 0xd7, 0xda, - 0x92, 0x82, 0x31, 0xae, 0x2c, 0x35, 0x65, 0xec, 0x84, 0x6a, 0xca, 0x0a, 0xb7, 0xd2, 0xef, 0x24, - 0xb4, 0x21, 0xa9, 0xeb, 0x84, 0x51, 0xc9, 0x8b, 0x69, 0x06, 0x1c, 0xcc, 0xc3, 0xb5, 0x44, 0xc3, - 0xb3, 0x7a, 0x81, 0x9f, 0xc4, 0x9a, 0x48, 0x69, 0x89, 0x19, 0x3c, 0x98, 0x99, 0x93, 0xe9, 0xe7, - 0xbb, 0x54, 0xb7, 0x83, 0xdd, 0x24, 0xe0, 0x64, 0x52, 0x3f, 0xbf, 0x35, 0xc8, 0x82, 0x59, 0xf9, - 0x32, 0x27, 0xa4, 0xa9, 0x67, 0x53, 0xad, 0xfa, 0xb5, 0x12, 0x5c, 0x5e, 0xa1, 0x41, 0x18, 0xde, - 0xf3, 0x95, 0x19, 0xe5, 0x0b, 0x30, 0xa3, 0xfc, 0x56, 0x05, 0xce, 0xaf, 0xd0, 0x60, 0x40, 0x1b, - 0xfb, 0x53, 0xda, 0xfc, 0xeb, 0x70, 0x3e, 0x8a, 0x28, 0x6b, 0x07, 0xae, 0x27, 0xe6, 0xf2, 0xd4, - 0x6a, 0xb9, 0x3d, 0xc8, 0x82, 0x59, 0xf9, 0xc8, 0xb7, 0xe0, 0x79, 0x3e, 0xd5, 0x3b, 0x1d, 0x61, - 0x9f, 0x15, 0xc6, 0x84, 0xd8, 0x9e, 0x88, 0x19, 0x09, 0xf9, 0x7c, 0x3b, 0x9b, 0x0d, 0x87, 0xe5, - 0x27, 0xdf, 0x81, 0xb1, 0x9e, 0xd5, 0xa3, 0xb6, 0xe5, 0x70, 0xfd, 0x2c, 0x77, 0x48, 0xc8, 0x46, - 0x0c, 0x2c, 0x5a, 0xc0, 0xc5, 0x53, 0x31, 0x21, 0x30, 0xb3, 0xa7, 0xd6, 0xcf, 0xb0, 0xa7, 0xfe, - 0xcf, 0x22, 0xd4, 0x56, 0x3c, 0xb7, 0xdf, 0x6b, 0x1d, 0x90, 0x0e, 0x54, 0x1f, 0x70, 0xe7, 0x99, - 0x74, 0x4d, 0x8d, 0x1e, 0x95, 0x2d, 0x7c, 0x70, 0x91, 0x4a, 0x24, 0xde, 0x51, 0xc2, 0xb3, 0x4e, - 0xbc, 0x47, 0x0f, 0xa8, 0x29, 0x7d, 0x68, 0x61, 0x27, 0xbe, 0xc3, 0x12, 0x51, 0xd0, 0x48, 0x17, - 0x26, 0x75, 0xdb, 0x76, 0x1f, 0x50, 0x73, 0x4d, 0x0f, 0xa8, 0x43, 0x7d, 0xe5, 0x92, 0x3c, 0xa9, - 0x59, 0x9a, 0xfb, 0xf5, 0x17, 0x92, 0x50, 0x98, 0xc6, 0x26, 0x1f, 0x42, 0xcd, 0x0f, 0x5c, 0x4f, - 0x29, 0x5b, 0xcd, 0xf9, 0xc5, 0xd1, 0x3f, 0x7a, 0xeb, 0x9b, 0x6d, 0x01, 0x25, 0x6c, 0xf6, 0xf2, - 0x05, 0x95, 0x00, 0xed, 0x37, 0x0b, 0x00, 0xb7, 0x36, 0x37, 0x37, 0xa4, 0x7b, 0xc1, 0x84, 0xb2, - 0xde, 0x0f, 0x1d, 0x95, 0xa3, 0x3b, 0x04, 0x13, 0x61, 0x99, 0xd2, 0x87, 0xd7, 0x0f, 0x76, 0x91, - 0xa3, 0x93, 0x9f, 0x84, 0x9a, 0x54, 0x90, 0x65, 0xb3, 0x87, 0xa1, 0x05, 0x52, 0x89, 0x46, 0x45, - 0xd7, 0xfe, 0x41, 0x11, 0x60, 0xd5, 0xb4, 0x69, 0x5b, 0x05, 0xd2, 0x37, 0x82, 0x5d, 0x8f, 0xfa, - 0xbb, 0xae, 0x6d, 0x8e, 0xe8, 0x4d, 0xe5, 0x36, 0xff, 0x4d, 0x05, 0x82, 0x11, 0x1e, 0x31, 0x61, - 0xcc, 0x0f, 0x68, 0x4f, 0x45, 0x6a, 0x8e, 0xe8, 0x44, 0x99, 0x12, 0x76, 0x91, 0x08, 0x07, 0x13, - 0xa8, 0x44, 0x87, 0xa6, 0xe5, 0x18, 0xe2, 0x07, 0x69, 0x1d, 0x8c, 0xd8, 0x91, 0x26, 0xd9, 0x8a, - 0x63, 0x35, 0x82, 0xc1, 0x38, 0xa6, 0xf6, 0x87, 0x45, 0xb8, 0xc4, 0xe5, 0xb1, 0x62, 0x24, 0xe2, - 0x31, 0xc9, 0x9f, 0x1f, 0xd8, 0xf4, 0xf7, 0x67, 0x8f, 0x27, 0x5a, 0xec, 0x19, 0x5b, 0xa7, 0x81, - 0x1e, 0xe9, 0x73, 0x51, 0x5a, 0x6c, 0xa7, 0x5f, 0x1f, 0xca, 0x3e, 0x1b, 0xaf, 0x44, 0xeb, 0xb5, - 0x47, 0xee, 0x42, 0xd9, 0x15, 0xe0, 0xa3, 0x57, 0xe8, 0x35, 0xe6, 0xa3, 0x16, 0x17, 0x47, 0xfe, - 0x22, 0x54, 0xfd, 0x40, 0x0f, 0xfa, 0xea, 0xd7, 0xdc, 0x3a, 0x6d, 0xc1, 0x1c, 0x3c, 0x1a, 0x47, - 0xc4, 0x3b, 0x4a, 0xa1, 0xda, 0x1f, 0x16, 0xe0, 0x4a, 0x76, 0xc6, 0x35, 0xcb, 0x0f, 0xc8, 0x9f, - 0x1b, 0x68, 0xf6, 0x63, 0x7e, 0x71, 0x96, 0x9b, 0x37, 0x7a, 0x18, 0x17, 0xae, 0x52, 0x62, 0x4d, - 0x1e, 0x40, 0xc5, 0x0a, 0x68, 0x57, 0xad, 0x2f, 0xef, 0x9d, 0x72, 0xd5, 0x63, 0x53, 0x3b, 0x93, - 0x82, 0x42, 0x98, 0xf6, 0x59, 0x71, 0x58, 0x95, 0xf9, 0xf4, 0x61, 0x27, 0x63, 0x7e, 0xef, 0xe4, - 0x8b, 0xf9, 0x4d, 0x16, 0x68, 0x30, 0xf4, 0xf7, 0x2f, 0x0c, 0x86, 0xfe, 0xde, 0xcb, 0x1f, 0xfa, - 0x9b, 0x6a, 0x86, 0xa1, 0x11, 0xc0, 0x9f, 0x97, 0xe0, 0xea, 0xe3, 0xba, 0x0d, 0x9b, 0xcf, 0x64, - 0xef, 0xcc, 0x3b, 0x9f, 0x3d, 0xbe, 0x1f, 0x92, 0x79, 0xa8, 0xf4, 0x76, 0x75, 0x5f, 0x29, 0x65, - 0x6a, 0xc1, 0x52, 0xd9, 0x60, 0x89, 0x8f, 0xd8, 0xa0, 0xc1, 0x95, 0x39, 0xfe, 0x8a, 0x82, 0x95, - 0x0d, 0xc7, 0x5d, 0xea, 0xfb, 0x91, 0x4d, 0x20, 0x1c, 0x8e, 0xd7, 0x45, 0x32, 0x2a, 0x3a, 0x09, - 0xa0, 0x2a, 0x4c, 0xcc, 0x72, 0x66, 0x1a, 0x3d, 0x90, 0x2b, 0x23, 0x4c, 0x3c, 0xaa, 0x94, 0xf4, - 0x56, 0x48, 0x59, 0x64, 0x16, 0xca, 0x41, 0x14, 0xb4, 0xab, 0x96, 0xe6, 0xe5, 0x0c, 0xfd, 0x94, - 0xf3, 0xb1, 0x85, 0xbd, 0xbb, 0xcd, 0x8d, 0xea, 0xa6, 0xf4, 0x9f, 0x5b, 0xae, 0xc3, 0x15, 0xb2, - 0x52, 0xb4, 0xb0, 0xbf, 0x37, 0xc0, 0x81, 0x19, 0xb9, 0xb4, 0x7f, 0x57, 0x87, 0x4b, 0xd9, 0xfd, - 0x81, 0xb5, 0xdb, 0x3e, 0xf5, 0x7c, 0x86, 0x5d, 0x48, 0xb6, 0xdb, 0x7d, 0x91, 0x8c, 0x8a, 0xfe, - 0xa5, 0x0e, 0x38, 0xfb, 0xad, 0x02, 0x5c, 0xf6, 0xa4, 0x8f, 0xe8, 0x69, 0x04, 0x9d, 0xbd, 0x28, - 0xcc, 0x19, 0x43, 0x04, 0xe2, 0xf0, 0xb2, 0x90, 0xbf, 0x57, 0x80, 0xe9, 0x6e, 0xca, 0xce, 0x71, - 0x86, 0xfb, 0xd6, 0x78, 0x54, 0xfc, 0xfa, 0x10, 0x79, 0x38, 0xb4, 0x24, 0xe4, 0x3b, 0xd0, 0xec, - 0xb1, 0x7e, 0xe1, 0x07, 0xd4, 0x31, 0xd4, 0xd6, 0xb5, 0xd1, 0xff, 0xa4, 0x8d, 0x08, 0x4b, 0x85, - 0xa2, 0x09, 0xfd, 0x20, 0x46, 0xc0, 0xb8, 0xc4, 0x67, 0x7c, 0xa3, 0xda, 0x0d, 0xa8, 0xfb, 0x34, - 0x08, 0x2c, 0xa7, 0x23, 0xd6, 0x1b, 0x0d, 0xf1, 0xaf, 0xb4, 0x65, 0x1a, 0x86, 0x54, 0xf2, 0x53, - 0xd0, 0xe0, 0x2e, 0xa7, 0x05, 0xaf, 0xe3, 0x4f, 0x37, 0x78, 0xb8, 0xd8, 0xb8, 0x08, 0x80, 0x93, - 0x89, 0x18, 0xd1, 0xc9, 0x6b, 0x30, 0xb6, 0xcd, 0x7f, 0x5f, 0xb9, 0x77, 0x59, 0xd8, 0xb8, 0xb8, - 0xb6, 0xd6, 0x8a, 0xa5, 0x63, 0x82, 0x8b, 0xcc, 0x03, 0xd0, 0xd0, 0x2f, 0x97, 0xb6, 0x67, 0x45, - 0x1e, 0x3b, 0x8c, 0x71, 0x91, 0x17, 0xa1, 0x14, 0xd8, 0x3e, 0xb7, 0x61, 0xd5, 0xa3, 0x25, 0xe8, - 0xe6, 0x5a, 0x1b, 0x59, 0xba, 0xf6, 0xa3, 0x02, 0x4c, 0xa6, 0x36, 0x97, 0xb0, 0x2c, 0x7d, 0xcf, - 0x96, 0xc3, 0x48, 0x98, 0x65, 0x0b, 0xd7, 0x90, 0xa5, 0x93, 0x0f, 0xa4, 0x5a, 0x5e, 0xcc, 0x79, - 0x4c, 0xc3, 0x5d, 0x3d, 0xf0, 0x99, 0x1e, 0x3e, 0xa0, 0x91, 0x73, 0x37, 0x5f, 0x54, 0x1e, 0x39, - 0x0f, 0xc4, 0xdc, 0x7c, 0x11, 0x0d, 0x13, 0x9c, 0x29, 0x83, 0x5f, 0xf9, 0x38, 0x06, 0x3f, 0xed, - 0xd7, 0x8b, 0xb1, 0x16, 0x90, 0x9a, 0xfd, 0x13, 0x5a, 0xe0, 0x15, 0x36, 0x81, 0x86, 0x93, 0x7b, - 0x23, 0x3e, 0xff, 0xf1, 0xc9, 0x58, 0x52, 0xc9, 0xbb, 0xa2, 0xed, 0x4b, 0x39, 0x37, 0xc3, 0x6e, - 0xae, 0xb5, 0x45, 0x74, 0x95, 0xfa, 0x6a, 0xe1, 0x27, 0x28, 0x9f, 0xd1, 0x27, 0xd0, 0xfe, 0x55, - 0x09, 0x9a, 0xb7, 0xdd, 0xed, 0x2f, 0x49, 0x04, 0x75, 0xf6, 0x34, 0x55, 0xfc, 0x02, 0xa7, 0xa9, - 0x2d, 0x78, 0x3e, 0x08, 0xec, 0x36, 0x35, 0x5c, 0xc7, 0xf4, 0x17, 0x76, 0x02, 0xea, 0x2d, 0x5b, - 0x8e, 0xe5, 0xef, 0x52, 0x53, 0xba, 0x93, 0x5e, 0x38, 0x3a, 0x9c, 0x79, 0x7e, 0x73, 0x73, 0x2d, - 0x8b, 0x05, 0x87, 0xe5, 0xe5, 0xc3, 0x86, 0xd8, 0x09, 0xc8, 0x77, 0xca, 0xc8, 0x98, 0x1b, 0x31, - 0x6c, 0xc4, 0xd2, 0x31, 0xc1, 0xa5, 0x7d, 0xb7, 0x08, 0x8d, 0x70, 0x03, 0x3e, 0x79, 0x19, 0x6a, - 0xdb, 0x9e, 0xbb, 0x47, 0x3d, 0xe1, 0xb9, 0x93, 0x3b, 0x65, 0x5a, 0x22, 0x09, 0x15, 0x8d, 0xbc, - 0x04, 0x95, 0xc0, 0xed, 0x59, 0x46, 0xda, 0xa0, 0xb6, 0xc9, 0x12, 0x51, 0xd0, 0xce, 0xae, 0x83, - 0xbf, 0x92, 0x50, 0xed, 0x1a, 0x43, 0x95, 0xb1, 0xf7, 0xa1, 0xec, 0xeb, 0xbe, 0x2d, 0xe7, 0xd3, - 0x1c, 0x7b, 0xd9, 0x17, 0xda, 0x6b, 0x72, 0x2f, 0xfb, 0x42, 0x7b, 0x0d, 0x39, 0xa8, 0xf6, 0x27, - 0x45, 0x68, 0x8a, 0x76, 0x13, 0xa3, 0xc2, 0x69, 0xb6, 0xdc, 0xdb, 0x3c, 0x94, 0xc2, 0xef, 0x77, - 0xa9, 0xc7, 0xcd, 0x4c, 0x72, 0x90, 0x8b, 0xfb, 0x07, 0x22, 0x62, 0x18, 0x4e, 0x11, 0x25, 0xa9, - 0xa6, 0x2f, 0x9f, 0x61, 0xd3, 0x57, 0x8e, 0xd5, 0xf4, 0xd5, 0xb3, 0x68, 0xfa, 0x4f, 0x8b, 0xd0, - 0x58, 0xb3, 0x76, 0xa8, 0x71, 0x60, 0xd8, 0x7c, 0x4f, 0xa0, 0x49, 0x6d, 0x1a, 0xd0, 0x15, 0x4f, - 0x37, 0xe8, 0x06, 0xf5, 0x2c, 0x7e, 0x40, 0x0d, 0xfb, 0x3f, 0xf8, 0x08, 0x24, 0xf7, 0x04, 0x2e, - 0x0d, 0xe1, 0xc1, 0xa1, 0xb9, 0xc9, 0x2a, 0x8c, 0x99, 0xd4, 0xb7, 0x3c, 0x6a, 0x6e, 0xc4, 0x16, - 0x2a, 0x2f, 0xab, 0xa9, 0x66, 0x29, 0x46, 0x7b, 0x74, 0x38, 0x33, 0xae, 0x0c, 0x94, 0x62, 0xc5, - 0x92, 0xc8, 0xca, 0x7e, 0xf9, 0x9e, 0xde, 0xf7, 0xb3, 0xca, 0x18, 0xfb, 0xe5, 0x37, 0xb2, 0x59, - 0x70, 0x58, 0x5e, 0xad, 0x02, 0xa5, 0x35, 0xb7, 0xa3, 0x7d, 0x56, 0x82, 0xf0, 0x24, 0x23, 0xf2, - 0x57, 0x0a, 0xd0, 0xd4, 0x1d, 0xc7, 0x0d, 0xe4, 0x29, 0x41, 0xc2, 0x03, 0x8f, 0xb9, 0x0f, 0x4c, - 0x9a, 0x5d, 0x88, 0x40, 0x85, 0xf3, 0x36, 0x74, 0x28, 0xc7, 0x28, 0x18, 0x97, 0x4d, 0xfa, 0x29, - 0x7f, 0xf2, 0x7a, 0xfe, 0x52, 0x1c, 0xc3, 0x7b, 0x7c, 0xe5, 0x1b, 0x30, 0x95, 0x2e, 0xec, 0x49, - 0xdc, 0x41, 0xb9, 0x1c, 0xf3, 0x45, 0x80, 0x28, 0xa6, 0xe4, 0x29, 0x18, 0xb1, 0xac, 0x84, 0x11, - 0x6b, 0x65, 0xf4, 0x06, 0x0e, 0x0b, 0x3d, 0xd4, 0x70, 0xf5, 0x51, 0xca, 0x70, 0xb5, 0x7a, 0x1a, - 0xc2, 0x1e, 0x6f, 0xac, 0xfa, 0xfb, 0x05, 0x98, 0x8a, 0x98, 0xe5, 0x0e, 0xd9, 0x37, 0x61, 0xdc, - 0xa3, 0xba, 0xd9, 0xd2, 0x03, 0x63, 0x97, 0x87, 0x7a, 0x17, 0x78, 0x6c, 0xf6, 0xb9, 0xa3, 0xc3, - 0x99, 0x71, 0x8c, 0x13, 0x30, 0xc9, 0x47, 0x74, 0x68, 0xb2, 0x84, 0x4d, 0xab, 0x4b, 0xdd, 0x7e, - 0x30, 0xa2, 0xd5, 0x94, 0x2f, 0x58, 0x30, 0x82, 0xc1, 0x38, 0xa6, 0xf6, 0x79, 0x01, 0x26, 0xe2, - 0x05, 0x3e, 0x73, 0x8b, 0xda, 0x6e, 0xd2, 0xa2, 0xb6, 0x78, 0x0a, 0xdf, 0x64, 0x88, 0x15, 0xed, - 0x13, 0x88, 0x57, 0x8d, 0x5b, 0xce, 0xe2, 0xc6, 0x82, 0xc2, 0x63, 0x8d, 0x05, 0x5f, 0xfe, 0xc3, - 0x6b, 0x86, 0x69, 0xb9, 0xe5, 0x67, 0x58, 0xcb, 0xfd, 0x22, 0x4f, 0xc0, 0x89, 0x9d, 0xe2, 0x52, - 0xcd, 0x71, 0x8a, 0x4b, 0x37, 0x3c, 0xc5, 0xa5, 0x76, 0x6a, 0x83, 0xce, 0x71, 0x4e, 0x72, 0xa9, - 0x3f, 0xd5, 0x93, 0x5c, 0x1a, 0x67, 0x75, 0x92, 0x0b, 0xe4, 0x3d, 0xc9, 0xe5, 0x93, 0x02, 0x4c, - 0x98, 0x89, 0x1d, 0xb3, 0xdc, 0xb6, 0x90, 0x67, 0xaa, 0x49, 0x6e, 0xc0, 0x15, 0x5b, 0xa6, 0x92, - 0x69, 0x98, 0x12, 0x49, 0x3e, 0x2d, 0xc0, 0x44, 0xbf, 0x67, 0xea, 0x41, 0x68, 0x38, 0xe2, 0x46, - 0x8b, 0x3c, 0xa5, 0xd8, 0x4a, 0xc0, 0x45, 0x8d, 0x9b, 0x4c, 0xc7, 0x94, 0x58, 0xed, 0x8f, 0x6b, - 0xf1, 0x19, 0xe9, 0x69, 0x1b, 0xcd, 0xdf, 0x48, 0x1a, 0xcd, 0xaf, 0xa7, 0x8d, 0xe6, 0x93, 0xb1, - 0x78, 0xd6, 0xb8, 0xe1, 0xfc, 0xa7, 0x63, 0x03, 0x75, 0x89, 0x9f, 0xe1, 0x12, 0x7e, 0xf3, 0x8c, - 0xc1, 0x7a, 0x01, 0x26, 0xa5, 0xf6, 0xaa, 0x88, 0x7c, 0x94, 0x1b, 0x8f, 0xc2, 0x9c, 0x96, 0x92, - 0x64, 0x4c, 0xf3, 0x33, 0x81, 0xbe, 0x3a, 0x40, 0x53, 0x2c, 0x15, 0xa2, 0x4e, 0xa6, 0x0e, 0xb7, - 0x0c, 0x39, 0xd8, 0xb2, 0xc2, 0xa3, 0xba, 0x2f, 0x4d, 0xdf, 0xb1, 0x65, 0x05, 0xf2, 0x54, 0x94, - 0xd4, 0xb8, 0xfd, 0xbf, 0xf6, 0x04, 0xfb, 0xbf, 0x0e, 0x4d, 0x5b, 0xf7, 0x03, 0xf1, 0x35, 0x4d, - 0xf9, 0x3b, 0xff, 0x99, 0xe3, 0x4d, 0xbc, 0x6c, 0x32, 0x8f, 0xb4, 0xdb, 0xb5, 0x08, 0x06, 0xe3, - 0x98, 0xc4, 0x84, 0x31, 0xf6, 0xca, 0x7f, 0x6d, 0x73, 0x21, 0x90, 0xc7, 0x4c, 0x9d, 0x44, 0x46, - 0x68, 0xb6, 0x5a, 0x8b, 0xe1, 0x60, 0x02, 0x75, 0x88, 0x8b, 0x00, 0x46, 0x71, 0x11, 0x90, 0x9f, - 0x17, 0x9a, 0xd3, 0x41, 0xf8, 0x59, 0x9b, 0xfc, 0xb3, 0x86, 0x21, 0x92, 0x18, 0x27, 0x62, 0x92, - 0x97, 0xf5, 0x8a, 0xbe, 0x6c, 0x06, 0x95, 0x7d, 0x2c, 0xd9, 0x2b, 0xb6, 0x92, 0x64, 0x4c, 0xf3, - 0x93, 0x0d, 0xb8, 0x10, 0x26, 0xc5, 0x8b, 0x31, 0xce, 0x71, 0xc2, 0x98, 0xb5, 0xad, 0x0c, 0x1e, - 0xcc, 0xcc, 0xc9, 0x37, 0x81, 0xf4, 0x3d, 0x8f, 0x3a, 0xc1, 0x2d, 0xdd, 0xdf, 0x95, 0xc1, 0x6f, - 0xd1, 0x26, 0x90, 0x88, 0x84, 0x71, 0x3e, 0x32, 0x0f, 0x20, 0xe0, 0x78, 0xae, 0xc9, 0x64, 0x7c, - 0xe9, 0x56, 0x48, 0xc1, 0x18, 0x97, 0xf6, 0x49, 0x03, 0x9a, 0x77, 0xf5, 0xc0, 0xda, 0xa7, 0xdc, - 0x9f, 0x77, 0x36, 0x4e, 0x95, 0xbf, 0x55, 0x80, 0x4b, 0xc9, 0xa0, 0xcd, 0x33, 0xf4, 0xac, 0xf0, - 0x23, 0x60, 0x30, 0x53, 0x1a, 0x0e, 0x29, 0x05, 0xf7, 0xb1, 0x0c, 0xc4, 0x80, 0x9e, 0xb5, 0x8f, - 0xa5, 0x3d, 0x4c, 0x20, 0x0e, 0x2f, 0xcb, 0x97, 0xc5, 0xc7, 0xf2, 0x6c, 0x9f, 0x14, 0x98, 0xf2, - 0x00, 0xd5, 0x9e, 0x19, 0x0f, 0x50, 0xfd, 0x99, 0x50, 0xbb, 0x7b, 0x31, 0x0f, 0x50, 0x23, 0x67, - 0x24, 0x92, 0xdc, 0xe7, 0x20, 0xd0, 0x86, 0x79, 0x92, 0xf8, 0x11, 0x05, 0xca, 0x32, 0xcf, 0xb4, - 0xd5, 0x6d, 0xdd, 0xb7, 0x0c, 0xa9, 0x76, 0xe4, 0x38, 0x19, 0x55, 0x9d, 0xdd, 0x26, 0x02, 0x16, - 0xf8, 0x2b, 0x0a, 0xec, 0xe8, 0xa8, 0xba, 0x62, 0xae, 0xa3, 0xea, 0xc8, 0x22, 0x94, 0x9d, 0x3d, - 0x7a, 0x70, 0xb2, 0xcd, 0xfe, 0x7c, 0x15, 0x76, 0xf7, 0x0e, 0x3d, 0x40, 0x9e, 0x59, 0xfb, 0x6e, - 0x11, 0x80, 0x55, 0xff, 0x78, 0xbe, 0x98, 0x9f, 0x84, 0x9a, 0xdf, 0xe7, 0x56, 0x13, 0xa9, 0x30, - 0x45, 0xe1, 0x5b, 0x22, 0x19, 0x15, 0x9d, 0xbc, 0x04, 0x95, 0x8f, 0xfa, 0xb4, 0xaf, 0x02, 0x0b, - 0x42, 0xc5, 0xfd, 0x9b, 0x2c, 0x11, 0x05, 0xed, 0xec, 0xec, 0xaa, 0xca, 0x67, 0x53, 0x39, 0x2b, - 0x9f, 0x4d, 0x03, 0x6a, 0x77, 0x5d, 0x1e, 0x0d, 0xaa, 0xfd, 0xf7, 0x22, 0x40, 0x14, 0x6d, 0x47, - 0x7e, 0xb3, 0x00, 0x17, 0xc3, 0x1f, 0x2e, 0x10, 0xeb, 0x2f, 0x7e, 0x18, 0x71, 0x6e, 0xff, 0x4d, - 0xd6, 0xcf, 0xce, 0x47, 0xa0, 0x8d, 0x2c, 0x71, 0x98, 0x5d, 0x0a, 0x82, 0x50, 0xa7, 0xdd, 0x5e, - 0x70, 0xb0, 0x64, 0x79, 0xb2, 0x07, 0x66, 0x06, 0x75, 0xde, 0x94, 0x3c, 0x22, 0xab, 0x34, 0x12, - 0xf0, 0x9f, 0x48, 0x51, 0x30, 0xc4, 0x21, 0xbb, 0x50, 0x77, 0xdc, 0x0f, 0x7c, 0xd6, 0x1c, 0xb2, - 0x3b, 0xbe, 0x33, 0x7a, 0x93, 0x8b, 0x66, 0x15, 0xf6, 0x7e, 0xf9, 0x82, 0x35, 0x47, 0x36, 0xf6, - 0x6f, 0x14, 0xe1, 0x7c, 0x46, 0x3b, 0x90, 0x77, 0x60, 0x4a, 0x06, 0x36, 0x46, 0xa7, 0x72, 0x17, - 0xa2, 0x53, 0xb9, 0xdb, 0x29, 0x1a, 0x0e, 0x70, 0x93, 0x0f, 0x00, 0x74, 0xc3, 0xa0, 0xbe, 0xbf, - 0xee, 0x9a, 0x6a, 0x3d, 0xf0, 0x36, 0x53, 0x5f, 0x16, 0xc2, 0xd4, 0x47, 0x87, 0x33, 0x3f, 0x93, - 0x15, 0xab, 0x9c, 0x6a, 0xe7, 0x28, 0x03, 0xc6, 0x20, 0xc9, 0xb7, 0x01, 0xc4, 0x22, 0x3c, 0x3c, - 0x4e, 0xe1, 0x09, 0x96, 0xab, 0x59, 0x75, 0x70, 0xd5, 0xec, 0x37, 0xfb, 0xba, 0x13, 0x58, 0xc1, - 0x81, 0x38, 0xbd, 0xe6, 0x7e, 0x88, 0x82, 0x31, 0x44, 0xed, 0x9f, 0x17, 0xa1, 0xae, 0x6c, 0xe6, - 0x4f, 0xc1, 0x50, 0xda, 0x49, 0x18, 0x4a, 0x4f, 0x29, 0x3a, 0x39, 0xcb, 0x4c, 0xea, 0xa6, 0xcc, - 0xa4, 0x2b, 0xf9, 0x45, 0x3d, 0xde, 0x48, 0xfa, 0x3b, 0x45, 0x98, 0x50, 0xac, 0x79, 0x4d, 0xa4, - 0x5f, 0x87, 0x49, 0x11, 0x55, 0xb0, 0xae, 0x3f, 0x14, 0x07, 0xf9, 0xf0, 0x06, 0x2b, 0x8b, 0x80, - 0xe0, 0x56, 0x92, 0x84, 0x69, 0x5e, 0xd6, 0xad, 0x45, 0xd2, 0x16, 0x5b, 0x84, 0x09, 0x3f, 0xa4, - 0x58, 0x6f, 0xf2, 0x6e, 0xdd, 0x4a, 0xd1, 0x70, 0x80, 0x3b, 0x6d, 0xa3, 0x2d, 0x9f, 0x81, 0x8d, - 0xf6, 0x3f, 0x14, 0x60, 0x2c, 0x6a, 0xaf, 0x33, 0xb7, 0xd0, 0xee, 0x24, 0x2d, 0xb4, 0x0b, 0xb9, - 0xbb, 0xc3, 0x10, 0xfb, 0xec, 0x5f, 0xab, 0x41, 0x22, 0x48, 0x9e, 0x6c, 0xc3, 0x15, 0x2b, 0x33, - 0xd4, 0x2f, 0x36, 0xda, 0x84, 0xbb, 0xbe, 0x57, 0x87, 0x72, 0xe2, 0x63, 0x50, 0x48, 0x1f, 0xea, - 0xfb, 0xd4, 0x0b, 0x2c, 0x83, 0xaa, 0xfa, 0xad, 0xe4, 0x56, 0xc9, 0xa4, 0x15, 0x3a, 0x6c, 0xd3, - 0xfb, 0x52, 0x00, 0x86, 0xa2, 0xc8, 0x36, 0x54, 0xa8, 0xd9, 0xa1, 0xea, 0x68, 0xa5, 0x9c, 0x07, - 0x97, 0x86, 0xed, 0xc9, 0xde, 0x7c, 0x14, 0xd0, 0xc4, 0x87, 0x86, 0xad, 0xbc, 0x8c, 0xb2, 0x1f, - 0x8e, 0xae, 0x60, 0x85, 0xfe, 0xca, 0xe8, 0xd4, 0x85, 0x30, 0x09, 0x23, 0x39, 0x64, 0x2f, 0x34, - 0x77, 0x56, 0x4e, 0x69, 0xf0, 0x78, 0x8c, 0xb1, 0xd3, 0x87, 0xc6, 0x03, 0x3d, 0xa0, 0x5e, 0x57, - 0xf7, 0xf6, 0xe4, 0x6a, 0x63, 0xf4, 0x1a, 0xbe, 0xab, 0x90, 0xa2, 0x1a, 0x86, 0x49, 0x18, 0xc9, - 0x21, 0x2e, 0x34, 0x02, 0xa9, 0x3e, 0x2b, 0x9b, 0xee, 0xe8, 0x42, 0x95, 0x22, 0xee, 0xcb, 0x60, - 0x79, 0xf5, 0x8a, 0x91, 0x0c, 0xb2, 0x9f, 0x38, 0x5b, 0x5a, 0x9c, 0x28, 0xde, 0xca, 0xe1, 0x1b, - 0x90, 0x50, 0xd1, 0x74, 0x93, 0x7d, 0x46, 0xb5, 0xf6, 0xbf, 0x2a, 0xd1, 0xb0, 0xfc, 0xb4, 0xed, - 0x84, 0xaf, 0x25, 0xed, 0x84, 0xd7, 0xd2, 0x76, 0xc2, 0x94, 0xb3, 0xfa, 0xe4, 0xe1, 0xb5, 0x29, - 0xf3, 0x5a, 0xf9, 0x0c, 0xcc, 0x6b, 0xaf, 0x42, 0x73, 0x9f, 0x8f, 0x04, 0xe2, 0x9c, 0xa6, 0x0a, - 0x9f, 0x46, 0xf8, 0xc8, 0x7e, 0x3f, 0x4a, 0xc6, 0x38, 0x0f, 0xcb, 0x22, 0x6f, 0xd3, 0x08, 0x0f, - 0xba, 0x95, 0x59, 0xda, 0x51, 0x32, 0xc6, 0x79, 0x78, 0x64, 0x9e, 0xe5, 0xec, 0x89, 0x0c, 0x35, - 0x9e, 0x41, 0x44, 0xe6, 0xa9, 0x44, 0x8c, 0xe8, 0xe4, 0x06, 0xd4, 0xfb, 0xe6, 0x8e, 0xe0, 0xad, - 0x73, 0x5e, 0xae, 0x61, 0x6e, 0x2d, 0x2d, 0xcb, 0x73, 0xa3, 0x14, 0x95, 0x95, 0xa4, 0xab, 0xf7, - 0x14, 0x81, 0xaf, 0x0d, 0x65, 0x49, 0xd6, 0xa3, 0x64, 0x8c, 0xf3, 0x90, 0x9f, 0x83, 0x09, 0x8f, - 0x9a, 0x7d, 0x83, 0x86, 0xb9, 0x80, 0xe7, 0x22, 0xe2, 0xda, 0x90, 0x38, 0x05, 0x53, 0x9c, 0x43, - 0x8c, 0x84, 0xcd, 0x91, 0x8c, 0x84, 0xdf, 0x80, 0x09, 0xd3, 0xd3, 0x2d, 0x87, 0x9a, 0xf7, 0x1c, - 0x1e, 0x91, 0x20, 0xe3, 0x03, 0x43, 0x0b, 0xf9, 0x52, 0x82, 0x8a, 0x29, 0x6e, 0xed, 0x0f, 0x0a, - 0x40, 0x06, 0x23, 0xe1, 0xc9, 0x2e, 0x54, 0x1d, 0x6e, 0x3d, 0xcb, 0x7d, 0xb4, 0x76, 0xcc, 0x08, - 0x27, 0x86, 0x35, 0x99, 0x20, 0xf1, 0x89, 0x03, 0x75, 0xfa, 0x30, 0xa0, 0x9e, 0x13, 0xee, 0x8c, - 0x39, 0x9d, 0x63, 0xbc, 0xc5, 0x6a, 0x42, 0x22, 0x63, 0x28, 0x43, 0xfb, 0xa3, 0x22, 0x34, 0x63, - 0x7c, 0x4f, 0x5a, 0x94, 0xf2, 0xcd, 0xf9, 0xc2, 0x68, 0xb5, 0xe5, 0xd9, 0xf2, 0x0f, 0x8d, 0x6d, - 0xce, 0x97, 0x24, 0x5c, 0xc3, 0x38, 0x1f, 0x99, 0x07, 0xe8, 0xea, 0x7e, 0x40, 0x3d, 0x3e, 0x7b, - 0xa7, 0xb6, 0xc4, 0xaf, 0x87, 0x14, 0x8c, 0x71, 0x91, 0xeb, 0xf2, 0x20, 0xf6, 0x72, 0xf2, 0x08, - 0xc3, 0x21, 0xa7, 0xac, 0x57, 0x4e, 0xe1, 0x94, 0x75, 0xd2, 0x81, 0x29, 0x55, 0x6a, 0x45, 0x3d, - 0xd9, 0x01, 0x77, 0x62, 0xfd, 0x93, 0x82, 0xc0, 0x01, 0x50, 0xed, 0xbb, 0x05, 0x18, 0x4f, 0x98, - 0x4c, 0xc4, 0xe1, 0x83, 0x6a, 0x1f, 0x47, 0xe2, 0xf0, 0xc1, 0xd8, 0xf6, 0x8b, 0x57, 0xa0, 0x2a, - 0x1a, 0x28, 0x1d, 0x9e, 0x29, 0x9a, 0x10, 0x25, 0x95, 0x8d, 0x85, 0xd2, 0x28, 0x9b, 0x1e, 0x0b, - 0xa5, 0xd5, 0x16, 0x15, 0x5d, 0xf8, 0x3a, 0x44, 0xe9, 0x64, 0x4b, 0xc7, 0x7c, 0x1d, 0x22, 0x1d, - 0x43, 0x0e, 0xed, 0xf7, 0x78, 0xb9, 0x03, 0xef, 0x20, 0x5c, 0x0b, 0x76, 0xa0, 0x26, 0x43, 0xf2, - 0xe4, 0xaf, 0xf1, 0x4e, 0x0e, 0x3b, 0x0e, 0xc7, 0x91, 0xc1, 0x67, 0xba, 0xb1, 0x77, 0x6f, 0x67, - 0x07, 0x15, 0x3a, 0xb9, 0x09, 0x0d, 0xd7, 0x59, 0xd6, 0x2d, 0xbb, 0xef, 0xa9, 0x99, 0xe1, 0x27, - 0xd8, 0x58, 0x77, 0x4f, 0x25, 0x3e, 0x3a, 0x9c, 0xb9, 0x14, 0xbe, 0x24, 0x0a, 0x89, 0x51, 0x4e, - 0xed, 0x2f, 0x17, 0xe0, 0x22, 0xba, 0xb6, 0x6d, 0x39, 0x9d, 0xa4, 0xb3, 0x8c, 0xd8, 0x30, 0xd1, - 0xd5, 0x1f, 0x6e, 0x39, 0xfa, 0xbe, 0x6e, 0xd9, 0xfa, 0xb6, 0x4d, 0x9f, 0xb8, 0x96, 0xeb, 0x07, - 0x96, 0x3d, 0x2b, 0xae, 0x83, 0x9b, 0x5d, 0x75, 0x82, 0x7b, 0x5e, 0x3b, 0xf0, 0x2c, 0xa7, 0x23, - 0x06, 0xbd, 0xf5, 0x04, 0x16, 0xa6, 0xb0, 0xb5, 0x3f, 0x2e, 0x01, 0x0f, 0x0b, 0x23, 0x6f, 0x42, - 0xa3, 0x4b, 0x8d, 0x5d, 0xdd, 0xb1, 0x7c, 0x75, 0x8c, 0xeb, 0x65, 0x56, 0xaf, 0x75, 0x95, 0xf8, - 0x88, 0x7d, 0x8a, 0x85, 0xf6, 0x1a, 0xdf, 0x79, 0x11, 0xf1, 0x12, 0x03, 0xaa, 0x1d, 0xdf, 0xd7, - 0x7b, 0x56, 0xee, 0xa8, 0x04, 0x71, 0x6c, 0xa6, 0x18, 0x8e, 0xc4, 0x33, 0x4a, 0x68, 0x62, 0x40, - 0xa5, 0x67, 0xeb, 0x96, 0x93, 0xfb, 0xfa, 0x22, 0x56, 0x83, 0x0d, 0x86, 0x24, 0x8c, 0x6b, 0xfc, - 0x11, 0x05, 0x36, 0xe9, 0x43, 0xd3, 0x37, 0x3c, 0xbd, 0xeb, 0xef, 0xea, 0xf3, 0xaf, 0xbf, 0x91, - 0x5b, 0x5d, 0x8d, 0x44, 0x89, 0xd9, 0x73, 0x11, 0x17, 0xd6, 0xdb, 0xb7, 0x16, 0xe6, 0x5f, 0x7f, - 0x03, 0xe3, 0x72, 0xe2, 0x62, 0x5f, 0x7f, 0x75, 0x5e, 0x8e, 0x20, 0xa7, 0x2e, 0xf6, 0xf5, 0x57, - 0xe7, 0x31, 0x2e, 0x47, 0xfb, 0xdf, 0x05, 0x68, 0x84, 0xbc, 0x64, 0x0b, 0x80, 0x8d, 0x65, 0xf2, - 0xa0, 0xcb, 0x13, 0x5d, 0x3a, 0xc1, 0xed, 0x13, 0x5b, 0x61, 0x66, 0x8c, 0x01, 0x65, 0x9c, 0x04, - 0x5a, 0x3c, 0xed, 0x93, 0x40, 0xe7, 0xa0, 0xb1, 0xab, 0x3b, 0xa6, 0xbf, 0xab, 0xef, 0x89, 0x21, - 0x3d, 0x76, 0x36, 0xee, 0x2d, 0x45, 0xc0, 0x88, 0x47, 0xfb, 0xa7, 0x55, 0x10, 0xa1, 0x04, 0x6c, - 0xd0, 0x31, 0x2d, 0x5f, 0xc4, 0xb2, 0x17, 0x78, 0xce, 0x70, 0xd0, 0x59, 0x92, 0xe9, 0x18, 0x72, - 0x90, 0xcb, 0x50, 0xea, 0x5a, 0x8e, 0xf4, 0x3d, 0x71, 0xd3, 0xe3, 0xba, 0xe5, 0x20, 0x4b, 0xe3, - 0x24, 0xfd, 0xa1, 0x0c, 0x43, 0x14, 0x24, 0xfd, 0x21, 0xb2, 0x34, 0xf2, 0x75, 0x98, 0xb4, 0x5d, - 0x77, 0x8f, 0x0d, 0x1f, 0x2a, 0x5a, 0x51, 0xf8, 0x81, 0xb9, 0x31, 0x60, 0x2d, 0x49, 0xc2, 0x34, - 0x2f, 0xd9, 0x82, 0xe7, 0x3f, 0xa6, 0x9e, 0x2b, 0xc7, 0xcb, 0xb6, 0x4d, 0x69, 0x4f, 0xc1, 0x08, - 0x65, 0x8e, 0x07, 0x3d, 0xfe, 0x62, 0x36, 0x0b, 0x0e, 0xcb, 0xcb, 0xc3, 0xa7, 0x75, 0xaf, 0x43, - 0x83, 0x0d, 0xcf, 0x35, 0xa8, 0xef, 0x5b, 0x4e, 0x47, 0xc1, 0x56, 0x23, 0xd8, 0xcd, 0x6c, 0x16, - 0x1c, 0x96, 0x97, 0xbc, 0x07, 0xd3, 0x82, 0x24, 0xd4, 0x96, 0x05, 0x31, 0xcc, 0x58, 0xb6, 0xba, - 0xf5, 0x6f, 0x5c, 0x78, 0x78, 0x36, 0x87, 0xf0, 0xe0, 0xd0, 0xdc, 0xe4, 0x36, 0x4c, 0x29, 0xff, - 0xde, 0x06, 0xf5, 0xda, 0x61, 0x78, 0xc9, 0x78, 0xeb, 0x1a, 0x5b, 0x79, 0x2f, 0xd1, 0x9e, 0x47, - 0x8d, 0xb8, 0x9f, 0x54, 0x71, 0xe1, 0x40, 0x3e, 0x82, 0x70, 0x89, 0xc7, 0x90, 0x6c, 0xf5, 0x16, - 0x5d, 0xd7, 0x36, 0xdd, 0x07, 0x8e, 0xaa, 0xbb, 0x50, 0x31, 0xb9, 0x4b, 0xaf, 0x9d, 0xc9, 0x81, - 0x43, 0x72, 0xb2, 0x9a, 0x73, 0xca, 0x92, 0xfb, 0xc0, 0x49, 0xa3, 0x42, 0x54, 0xf3, 0xf6, 0x10, - 0x1e, 0x1c, 0x9a, 0x9b, 0x2c, 0x03, 0x49, 0xd7, 0x60, 0xab, 0x27, 0x9d, 0xce, 0x97, 0xc4, 0x99, - 0x35, 0x69, 0x2a, 0x66, 0xe4, 0x20, 0x6b, 0x70, 0x21, 0x9d, 0xca, 0xc4, 0x49, 0xff, 0x33, 0x3f, - 0xad, 0x16, 0x33, 0xe8, 0x98, 0x99, 0x4b, 0xfb, 0x67, 0x45, 0x18, 0x4f, 0x1c, 0x72, 0xf0, 0xcc, - 0x6d, 0x26, 0x67, 0x6b, 0x81, 0xae, 0xdf, 0x59, 0x5d, 0xba, 0x45, 0x75, 0x93, 0x7a, 0x77, 0xa8, - 0x3a, 0x90, 0x42, 0x4c, 0x8b, 0x09, 0x0a, 0xa6, 0x38, 0xc9, 0x0e, 0x54, 0x84, 0x65, 0x3b, 0xef, - 0xf5, 0x25, 0xaa, 0x8d, 0xb8, 0x79, 0x5b, 0xde, 0xf9, 0xe3, 0x7a, 0x14, 0x05, 0xbc, 0x16, 0xc0, - 0x58, 0x9c, 0x83, 0x0d, 0x24, 0x91, 0xda, 0x5b, 0x4b, 0xa8, 0xbc, 0xab, 0x50, 0x0a, 0x82, 0x51, - 0xb7, 0xa9, 0x0b, 0x4f, 0xc9, 0xe6, 0x1a, 0x32, 0x0c, 0x6d, 0x87, 0x7d, 0x3b, 0xdf, 0xb7, 0x5c, - 0x47, 0x9e, 0x59, 0xbe, 0x05, 0xb5, 0x40, 0x1a, 0x0b, 0x47, 0xdb, 0x66, 0xcf, 0x75, 0x25, 0x65, - 0x28, 0x54, 0x58, 0xda, 0x7f, 0x2c, 0x42, 0x23, 0x5c, 0xd8, 0x1f, 0xe3, 0x2c, 0x70, 0x17, 0x1a, - 0x61, 0x0c, 0x5c, 0xee, 0x1b, 0x11, 0xa3, 0xd0, 0x2c, 0xbe, 0x16, 0x0d, 0x5f, 0x31, 0x92, 0x11, - 0x8f, 0xaf, 0x2b, 0xe5, 0x88, 0xaf, 0xeb, 0x41, 0x2d, 0xf0, 0xac, 0x4e, 0x47, 0xae, 0x12, 0xf2, - 0x04, 0xd8, 0x85, 0xcd, 0xb5, 0x29, 0x00, 0x65, 0xcb, 0x8a, 0x17, 0x54, 0x62, 0xb4, 0x0f, 0x61, - 0x2a, 0xcd, 0xc9, 0x55, 0x68, 0x63, 0x97, 0x9a, 0x7d, 0x5b, 0xb5, 0x71, 0xa4, 0x42, 0xcb, 0x74, - 0x0c, 0x39, 0xd8, 0x32, 0x9c, 0x7d, 0xa6, 0x8f, 0x5d, 0x47, 0xa9, 0xb1, 0x7c, 0x35, 0xb2, 0x29, - 0xd3, 0x30, 0xa4, 0x6a, 0xff, 0xad, 0x04, 0x97, 0x23, 0xf3, 0xcc, 0xba, 0xee, 0xe8, 0x9d, 0x63, - 0x5c, 0x83, 0xf7, 0xd5, 0xc6, 0xa5, 0x93, 0x5e, 0xe8, 0x50, 0x7a, 0x06, 0x2e, 0x74, 0xf8, 0xbf, - 0x45, 0xe0, 0xf1, 0xba, 0xe4, 0x3b, 0x30, 0xa6, 0xc7, 0x6e, 0x40, 0x95, 0x9f, 0xf3, 0x66, 0xee, - 0xcf, 0xc9, 0xc3, 0x82, 0xc3, 0x90, 0xad, 0x78, 0x2a, 0x26, 0x04, 0x12, 0x17, 0xea, 0x3b, 0xba, - 0x6d, 0x33, 0x5d, 0x28, 0xb7, 0xbb, 0x29, 0x21, 0x9c, 0x77, 0xf3, 0x65, 0x09, 0x8d, 0xa1, 0x10, - 0xf2, 0x49, 0x01, 0xc6, 0xbd, 0xf8, 0x72, 0x4d, 0x7e, 0x90, 0x3c, 0xc1, 0x08, 0x31, 0xb4, 0x78, - 0x80, 0x58, 0x7c, 0x4d, 0x98, 0x94, 0xa9, 0xfd, 0xd7, 0x02, 0x8c, 0xb7, 0x6d, 0xcb, 0xb4, 0x9c, - 0xce, 0x19, 0xde, 0x27, 0x71, 0x0f, 0x2a, 0xbe, 0x6d, 0x99, 0x74, 0xc4, 0xd9, 0x44, 0xcc, 0x63, - 0x0c, 0x00, 0x05, 0x4e, 0xf2, 0x82, 0x8a, 0xd2, 0x31, 0x2e, 0xa8, 0xf8, 0x61, 0x15, 0x64, 0xe4, - 0x39, 0xe9, 0x43, 0xa3, 0xa3, 0xce, 0xbd, 0x97, 0x75, 0xbc, 0x95, 0xe3, 0xcc, 0xc4, 0xc4, 0x09, - 0xfa, 0x62, 0xec, 0x0f, 0x13, 0x31, 0x92, 0x44, 0x68, 0xf2, 0xea, 0xdd, 0xa5, 0x9c, 0x57, 0xef, - 0x0a, 0x71, 0x83, 0x97, 0xef, 0xea, 0x50, 0xde, 0x0d, 0x82, 0x9e, 0xec, 0x4c, 0xa3, 0x6f, 0x2d, - 0x88, 0x8e, 0xed, 0x11, 0x3a, 0x11, 0x7b, 0x47, 0x0e, 0xcd, 0x44, 0x38, 0x7a, 0x78, 0xd5, 0xda, - 0x62, 0xae, 0xc0, 0x87, 0xb8, 0x08, 0xf6, 0x8e, 0x1c, 0x9a, 0xfc, 0x32, 0x34, 0x03, 0x4f, 0x77, - 0xfc, 0x1d, 0xd7, 0xeb, 0x52, 0x4f, 0xae, 0x51, 0x97, 0x73, 0xdc, 0x3e, 0xbb, 0x19, 0xa1, 0x09, - 0x8f, 0x6a, 0x22, 0x09, 0xe3, 0xd2, 0xc8, 0x1e, 0xd4, 0xfb, 0xa6, 0x28, 0x98, 0x34, 0x83, 0x2d, - 0xe4, 0xb9, 0x50, 0x38, 0x16, 0xd6, 0xa0, 0xde, 0x30, 0x14, 0x90, 0xbc, 0x55, 0xb0, 0x76, 0x5a, - 0xb7, 0x0a, 0xc6, 0x7b, 0x63, 0xd6, 0x99, 0x22, 0xa4, 0x2b, 0xf5, 0x5a, 0xa7, 0x23, 0xa3, 0xb2, - 0x96, 0x73, 0xab, 0x9c, 0x42, 0x64, 0x33, 0xd4, 0x8d, 0x9d, 0x0e, 0x2a, 0x19, 0x5a, 0x17, 0xa4, - 0xb7, 0x83, 0x18, 0x89, 0xbb, 0x77, 0xc4, 0x46, 0xb7, 0xb9, 0xe3, 0x8d, 0x07, 0xe1, 0x25, 0x30, - 0xb1, 0xb3, 0xbf, 0x33, 0x2f, 0xd9, 0xd1, 0xfe, 0x53, 0x11, 0x4a, 0x9b, 0x6b, 0x6d, 0x71, 0x9e, - 0x27, 0xbf, 0xd8, 0x8a, 0xb6, 0xf7, 0xac, 0xde, 0x7d, 0xea, 0x59, 0x3b, 0x07, 0x72, 0xe9, 0x1d, - 0x3b, 0xcf, 0x33, 0xcd, 0x81, 0x19, 0xb9, 0xc8, 0xfb, 0x30, 0x66, 0xe8, 0x8b, 0xd4, 0x0b, 0x46, - 0x31, 0x2c, 0xf0, 0x1d, 0xbd, 0x8b, 0x0b, 0x51, 0x76, 0x4c, 0x80, 0x91, 0x2d, 0x00, 0x23, 0x82, - 0x2e, 0x9d, 0xd8, 0x1c, 0x12, 0x03, 0x8e, 0x01, 0x11, 0x84, 0xc6, 0x1e, 0x63, 0xe5, 0xa8, 0xe5, - 0x93, 0xa0, 0xf2, 0x9e, 0x73, 0x47, 0xe5, 0xc5, 0x08, 0x46, 0x73, 0x60, 0x3c, 0x71, 0x21, 0x0f, - 0xf9, 0x1a, 0xd4, 0xdd, 0x5e, 0x6c, 0x38, 0x6d, 0xf0, 0xf8, 0xcf, 0xfa, 0x3d, 0x99, 0xf6, 0xe8, - 0x70, 0x66, 0x7c, 0xcd, 0xed, 0x58, 0x86, 0x4a, 0xc0, 0x90, 0x9d, 0x68, 0x50, 0xe5, 0xdb, 0xf0, - 0xd4, 0x75, 0x3c, 0x7c, 0xee, 0xe0, 0x37, 0x66, 0xf8, 0x28, 0x29, 0xda, 0xaf, 0x94, 0x21, 0xf2, - 0x11, 0x12, 0x1f, 0xaa, 0x62, 0x9b, 0x81, 0x1c, 0xb9, 0xcf, 0x74, 0x47, 0x83, 0x14, 0x45, 0x3a, - 0x50, 0xfa, 0xd0, 0xdd, 0xce, 0x3d, 0x70, 0xc7, 0xf6, 0xdf, 0x0b, 0x5b, 0x59, 0x2c, 0x01, 0x99, - 0x04, 0xf2, 0xb7, 0x0b, 0x70, 0xce, 0x4f, 0xab, 0xbe, 0xb2, 0x3b, 0x60, 0x7e, 0x1d, 0x3f, 0xad, - 0x4c, 0xcb, 0x40, 0xdd, 0x61, 0x64, 0x1c, 0x2c, 0x0b, 0x6b, 0x7f, 0xe1, 0xbc, 0x93, 0xdd, 0x69, - 0x25, 0xe7, 0x25, 0x92, 0xc9, 0xf6, 0x4f, 0xa6, 0xa1, 0x14, 0xa5, 0xfd, 0x5a, 0x11, 0x9a, 0xb1, - 0xd1, 0x3a, 0xf7, 0x2d, 0x4f, 0x0f, 0x53, 0xb7, 0x3c, 0x6d, 0x8c, 0xee, 0xcb, 0x8e, 0x4a, 0x75, - 0xd6, 0x17, 0x3d, 0xfd, 0xcb, 0x22, 0x94, 0xb6, 0x96, 0x96, 0x93, 0x8b, 0xd6, 0xc2, 0x53, 0x58, - 0xb4, 0xee, 0x42, 0x6d, 0xbb, 0x6f, 0xd9, 0x81, 0xe5, 0xe4, 0x3e, 0x21, 0x44, 0x5d, 0x8a, 0x25, - 0x7d, 0x1d, 0x02, 0x15, 0x15, 0x3c, 0xe9, 0x40, 0xad, 0x23, 0x8e, 0x68, 0xcc, 0x1d, 0xe1, 0x27, - 0x8f, 0x7a, 0x14, 0x82, 0xe4, 0x0b, 0x2a, 0x74, 0xed, 0x00, 0xe4, 0xed, 0xfe, 0x4f, 0xbd, 0x35, - 0xb5, 0x5f, 0x86, 0x50, 0x0b, 0x78, 0xfa, 0xc2, 0xff, 0x47, 0x01, 0x92, 0x8a, 0xcf, 0xd3, 0xef, - 0x4d, 0x7b, 0xe9, 0xde, 0xb4, 0x74, 0x1a, 0x3f, 0x5f, 0x76, 0x87, 0xd2, 0xfe, 0x7d, 0x01, 0x52, - 0x7b, 0xc3, 0xc8, 0x1b, 0xf2, 0xb4, 0xaf, 0x64, 0x28, 0x95, 0x3a, 0xed, 0x8b, 0x24, 0xb9, 0x63, - 0xa7, 0x7e, 0x7d, 0xca, 0x96, 0x6b, 0x71, 0x07, 0x9a, 0x2c, 0xfe, 0xdd, 0xd1, 0x97, 0x6b, 0x59, - 0xee, 0x38, 0x19, 0xee, 0x17, 0x27, 0x61, 0x52, 0xae, 0xf6, 0x4f, 0x8a, 0x50, 0x7d, 0x6a, 0x5b, - 0xd5, 0x69, 0x22, 0x02, 0x73, 0x31, 0xe7, 0x68, 0x3f, 0x34, 0xfe, 0xb2, 0x9b, 0x8a, 0xbf, 0xcc, - 0x7b, 0x37, 0xf1, 0x13, 0xa2, 0x2f, 0xff, 0x6d, 0x01, 0xe4, 0x5c, 0xb3, 0xea, 0xf8, 0x81, 0xee, - 0x18, 0x94, 0x18, 0xe1, 0xc4, 0x96, 0x37, 0xcc, 0x47, 0x86, 0xc2, 0x09, 0x5d, 0x86, 0x3f, 0xab, - 0x89, 0x8c, 0xfc, 0x34, 0xd4, 0x77, 0x5d, 0x3f, 0xe0, 0x93, 0x57, 0x31, 0x69, 0x32, 0xbb, 0x25, - 0xd3, 0x31, 0xe4, 0x48, 0xbb, 0xb3, 0x2b, 0xc3, 0xdd, 0xd9, 0xda, 0x6f, 0x17, 0x61, 0xec, 0xcb, - 0xb2, 0xdf, 0x3e, 0x2b, 0x5e, 0xb5, 0x94, 0x33, 0x5e, 0xb5, 0x7c, 0x92, 0x78, 0x55, 0xed, 0xfb, - 0x05, 0x80, 0xa7, 0xb6, 0xd9, 0xdf, 0x4c, 0x86, 0x92, 0xe6, 0xee, 0x57, 0xd9, 0x81, 0xa4, 0xff, - 0xa8, 0xa2, 0xaa, 0xc4, 0xc3, 0x48, 0x3f, 0x2d, 0xc0, 0x84, 0x9e, 0x08, 0xcd, 0xcc, 0xad, 0x2f, - 0xa7, 0x22, 0x3d, 0xc3, 0xc8, 0xa2, 0x64, 0x3a, 0xa6, 0xc4, 0x92, 0xb7, 0xa2, 0x83, 0xa6, 0xef, - 0x46, 0xdd, 0x7e, 0xe0, 0x84, 0x68, 0xae, 0xbb, 0x25, 0x38, 0x9f, 0x10, 0x0a, 0x5b, 0x3a, 0x95, - 0x50, 0xd8, 0xf8, 0x26, 0xbf, 0xf2, 0x63, 0x37, 0xf9, 0xed, 0x43, 0x63, 0xc7, 0x73, 0xbb, 0x3c, - 0xda, 0x54, 0xde, 0x6a, 0x7c, 0x33, 0xc7, 0x44, 0x19, 0xdd, 0xe7, 0x1f, 0x19, 0xae, 0x96, 0x15, - 0x3e, 0x46, 0xa2, 0xb8, 0xad, 0xdf, 0x15, 0x52, 0xab, 0xa7, 0x29, 0x35, 0x1c, 0x4b, 0x36, 0x05, - 0x3a, 0x2a, 0x31, 0xc9, 0x08, 0xd3, 0xda, 0xd3, 0x89, 0x30, 0xd5, 0x7e, 0xb7, 0xaa, 0x06, 0xb0, - 0x67, 0xee, 0x4c, 0xd3, 0xdc, 0x5b, 0xb3, 0xe3, 0xfb, 0xaa, 0xcb, 0x27, 0xd8, 0x57, 0x5d, 0x39, - 0xee, 0xbe, 0xea, 0xea, 0x13, 0x02, 0x3f, 0xd3, 0x9b, 0x9e, 0x6b, 0x4f, 0x71, 0xd3, 0x73, 0xfd, - 0x74, 0x36, 0x3d, 0x37, 0x4e, 0xb6, 0xe9, 0x59, 0xee, 0x1b, 0x0e, 0xb3, 0x43, 0x72, 0xd3, 0xf3, - 0x62, 0x92, 0x8c, 0x69, 0xfe, 0xac, 0x7d, 0xd3, 0xcd, 0x13, 0xee, 0x9b, 0x4e, 0xed, 0x72, 0x1e, - 0x1b, 0x69, 0x97, 0xf3, 0xf8, 0xb1, 0x76, 0x39, 0x1f, 0x96, 0x20, 0xb5, 0x74, 0xfe, 0xca, 0x4d, - 0xf6, 0xff, 0x95, 0x9b, 0xec, 0xb3, 0x22, 0x44, 0xc3, 0xe6, 0x09, 0xc3, 0x88, 0xde, 0x83, 0x7a, - 0x57, 0x7f, 0xb8, 0x44, 0x6d, 0xfd, 0x20, 0xcf, 0xc5, 0xbd, 0xeb, 0x12, 0x03, 0x43, 0x34, 0xe2, - 0x03, 0x58, 0xe1, 0xe1, 0xf9, 0xb9, 0x1d, 0x0e, 0xd1, 0x39, 0xfc, 0xc2, 0xa4, 0x19, 0xbd, 0x63, - 0x4c, 0x8c, 0xf6, 0x6f, 0x8a, 0x20, 0x6f, 0x59, 0x20, 0x14, 0x2a, 0x3b, 0xd6, 0x43, 0x6a, 0xe6, - 0x0e, 0x4e, 0x8e, 0x5d, 0xa7, 0x2e, 0x3c, 0x2a, 0x3c, 0x01, 0x05, 0x3a, 0x37, 0x95, 0x0b, 0x0f, - 0x99, 0x6c, 0xbf, 0x1c, 0xa6, 0xf2, 0xb8, 0xa7, 0x4d, 0x9a, 0xca, 0x45, 0x12, 0x2a, 0x19, 0xc2, - 0x32, 0xcf, 0x83, 0x25, 0x72, 0x3b, 0x04, 0x13, 0x41, 0x17, 0xca, 0x32, 0xef, 0x8b, 0x63, 0x0e, - 0xa4, 0x8c, 0xd6, 0x2f, 0x7d, 0xef, 0x07, 0xd7, 0x9e, 0xfb, 0xfe, 0x0f, 0xae, 0x3d, 0xf7, 0xf9, - 0x0f, 0xae, 0x3d, 0xf7, 0x2b, 0x47, 0xd7, 0x0a, 0xdf, 0x3b, 0xba, 0x56, 0xf8, 0xfe, 0xd1, 0xb5, - 0xc2, 0xe7, 0x47, 0xd7, 0x0a, 0xff, 0xf9, 0xe8, 0x5a, 0xe1, 0x6f, 0xfc, 0x97, 0x6b, 0xcf, 0xfd, - 0xe2, 0x9b, 0x51, 0x11, 0xe6, 0x54, 0x11, 0xe6, 0x94, 0xc0, 0xb9, 0xde, 0x5e, 0x67, 0x8e, 0x15, - 0x21, 0x4a, 0x51, 0x45, 0xf8, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x90, 0x35, 0xbe, 0x7d, 0xfe, - 0x99, 0x00, 0x00, + // 7889 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6b, 0x6c, 0x25, 0xd9, + 0xb5, 0xd6, 0x9c, 0x97, 0xcf, 0x39, 0xeb, 0xf8, 0xd5, 0xbb, 0x1f, 0xe3, 0xee, 0xe9, 0x69, 0xf7, + 0xad, 0xb9, 0x33, 0xb7, 0x2f, 0xf7, 0x5e, 0x9b, 0xf1, 0x9d, 0x57, 0xee, 0xbd, 0xc9, 0x8c, 0x8f, + 0xdd, 0x76, 0xbb, 0xdb, 0xee, 0x76, 0xd6, 0xb1, 0x7b, 0x26, 0x77, 0x48, 0x86, 0x72, 0xd5, 0xf6, + 0x71, 0x8d, 0xeb, 0x54, 0x9d, 0xa9, 0xaa, 0xe3, 0x6e, 0x4f, 0x40, 0x79, 0x0c, 0x68, 0x06, 0x01, + 0x02, 0xe5, 0x57, 0x24, 0x14, 0x10, 0x08, 0x29, 0x3f, 0xa2, 0xf0, 0x03, 0x29, 0xfc, 0x40, 0x82, + 0x10, 0x84, 0x20, 0x20, 0x1e, 0x11, 0x42, 0x62, 0xf8, 0x63, 0x11, 0x23, 0x7e, 0x80, 0x04, 0x8a, + 0x88, 0x20, 0xa1, 0x15, 0x11, 0xb4, 0x5f, 0xf5, 0x3a, 0x75, 0xba, 0xed, 0x53, 0x76, 0x4f, 0x0f, + 0xcc, 0xbf, 0xaa, 0xbd, 0xd6, 0xfe, 0xd6, 0xae, 0x5d, 0xbb, 0xf6, 0x5e, 0x7b, 0xad, 0xb5, 0x57, + 0xc1, 0x72, 0xdb, 0x0a, 0x76, 0x7a, 0x5b, 0x33, 0x86, 0xdb, 0x99, 0x75, 0x7a, 0x1d, 0xbd, 0xeb, + 0xb9, 0xef, 0xf2, 0x8b, 0x6d, 0xdb, 0xbd, 0x37, 0xdb, 0xdd, 0x6d, 0xcf, 0xea, 0x5d, 0xcb, 0x8f, + 0x4a, 0xf6, 0x5e, 0xd4, 0xed, 0xee, 0x8e, 0xfe, 0xe2, 0x6c, 0x9b, 0x3a, 0xd4, 0xd3, 0x03, 0x6a, + 0xce, 0x74, 0x3d, 0x37, 0x70, 0xc9, 0xab, 0x11, 0xd0, 0x8c, 0x02, 0x9a, 0x51, 0xd5, 0x66, 0xba, + 0xbb, 0xed, 0x19, 0x06, 0x14, 0x95, 0x28, 0xa0, 0x4b, 0xbf, 0x17, 0x6b, 0x41, 0xdb, 0x6d, 0xbb, + 0xb3, 0x1c, 0x6f, 0xab, 0xb7, 0xcd, 0xef, 0xf8, 0x0d, 0xbf, 0x12, 0x72, 0x2e, 0x69, 0xbb, 0xaf, + 0xf9, 0x33, 0x96, 0xcb, 0x9a, 0x35, 0x6b, 0xb8, 0x1e, 0x9d, 0xdd, 0xeb, 0x6b, 0xcb, 0xa5, 0x97, + 0x22, 0x9e, 0x8e, 0x6e, 0xec, 0x58, 0x0e, 0xf5, 0xf6, 0xd5, 0xb3, 0xcc, 0x7a, 0xd4, 0x77, 0x7b, + 0x9e, 0x41, 0x8f, 0x55, 0xcb, 0x9f, 0xed, 0xd0, 0x40, 0xcf, 0x92, 0x35, 0x3b, 0xa8, 0x96, 0xd7, + 0x73, 0x02, 0xab, 0xd3, 0x2f, 0xe6, 0x95, 0x47, 0x55, 0xf0, 0x8d, 0x1d, 0xda, 0xd1, 0xfb, 0xea, + 0xfd, 0xfe, 0xa0, 0x7a, 0xbd, 0xc0, 0xb2, 0x67, 0x2d, 0x27, 0xf0, 0x03, 0x2f, 0x5d, 0x49, 0xfb, + 0x11, 0xc0, 0xd9, 0xf9, 0x2d, 0x3f, 0xf0, 0x74, 0x23, 0x58, 0x77, 0xcd, 0x0d, 0xda, 0xe9, 0xda, + 0x7a, 0x40, 0xc9, 0x2e, 0xd4, 0xd8, 0x03, 0x99, 0x7a, 0xa0, 0x4f, 0x15, 0xae, 0x16, 0xae, 0x35, + 0xe6, 0xe6, 0x67, 0x86, 0x7c, 0x81, 0x33, 0x6b, 0x12, 0xa8, 0x39, 0x7a, 0x78, 0x30, 0x5d, 0x53, + 0x77, 0x18, 0x0a, 0x20, 0xdf, 0x2e, 0xc0, 0xa8, 0xe3, 0x9a, 0xb4, 0x45, 0x6d, 0x6a, 0x04, 0xae, + 0x37, 0x55, 0xbc, 0x5a, 0xba, 0xd6, 0x98, 0xfb, 0xca, 0xd0, 0x12, 0x33, 0x9e, 0x68, 0xe6, 0x76, + 0x4c, 0xc0, 0x75, 0x27, 0xf0, 0xf6, 0x9b, 0xe7, 0x7e, 0x7c, 0x30, 0xfd, 0xd4, 0xe1, 0xc1, 0xf4, + 0x68, 0x9c, 0x84, 0x89, 0x96, 0x90, 0x4d, 0x68, 0x04, 0xae, 0xcd, 0xba, 0xcc, 0x72, 0x1d, 0x7f, + 0xaa, 0xc4, 0x1b, 0x76, 0x65, 0x46, 0x74, 0x35, 0x13, 0x3f, 0xc3, 0xc6, 0xd8, 0xcc, 0xde, 0x8b, + 0x33, 0x1b, 0x21, 0x5b, 0xf3, 0xac, 0x04, 0x6e, 0x44, 0x65, 0x3e, 0xc6, 0x71, 0x08, 0x85, 0x09, + 0x9f, 0x1a, 0x3d, 0xcf, 0x0a, 0xf6, 0x17, 0x5c, 0x27, 0xa0, 0xf7, 0x83, 0xa9, 0x32, 0xef, 0xe5, + 0x17, 0xb2, 0xa0, 0xd7, 0x5d, 0xb3, 0x95, 0xe4, 0x6e, 0x9e, 0x3d, 0x3c, 0x98, 0x9e, 0x48, 0x15, + 0x62, 0x1a, 0x93, 0x38, 0x30, 0x69, 0x75, 0xf4, 0x36, 0x5d, 0xef, 0xd9, 0x76, 0x8b, 0x1a, 0x1e, + 0x0d, 0xfc, 0xa9, 0x0a, 0x7f, 0x84, 0x6b, 0x59, 0x72, 0x56, 0x5d, 0x43, 0xb7, 0xef, 0x6c, 0xbd, + 0x4b, 0x8d, 0x00, 0xe9, 0x36, 0xf5, 0xa8, 0x63, 0xd0, 0xe6, 0x94, 0x7c, 0x98, 0xc9, 0x95, 0x14, + 0x12, 0xf6, 0x61, 0x93, 0x65, 0x38, 0xd3, 0xf5, 0x2c, 0x97, 0x37, 0xc1, 0xd6, 0x7d, 0xff, 0xb6, + 0xde, 0xa1, 0x53, 0x23, 0x57, 0x0b, 0xd7, 0xea, 0xcd, 0x8b, 0x12, 0xe6, 0xcc, 0x7a, 0x9a, 0x01, + 0xfb, 0xeb, 0x90, 0x6b, 0x50, 0x53, 0x85, 0x53, 0xd5, 0xab, 0x85, 0x6b, 0x15, 0x31, 0x76, 0x54, + 0x5d, 0x0c, 0xa9, 0x64, 0x09, 0x6a, 0xfa, 0xf6, 0xb6, 0xe5, 0x30, 0xce, 0x1a, 0xef, 0xc2, 0xcb, + 0x59, 0x8f, 0x36, 0x2f, 0x79, 0x04, 0x8e, 0xba, 0xc3, 0xb0, 0x2e, 0xb9, 0x09, 0xc4, 0xa7, 0xde, + 0x9e, 0x65, 0xd0, 0x79, 0xc3, 0x70, 0x7b, 0x4e, 0xc0, 0xdb, 0x5e, 0xe7, 0x6d, 0xbf, 0x24, 0xdb, + 0x4e, 0x5a, 0x7d, 0x1c, 0x98, 0x51, 0x8b, 0xbc, 0x01, 0x93, 0xf2, 0x5b, 0x8d, 0x7a, 0x01, 0x38, + 0xd2, 0x39, 0xd6, 0x91, 0x98, 0xa2, 0x61, 0x1f, 0x37, 0x31, 0xe1, 0xb2, 0xde, 0x0b, 0xdc, 0x0e, + 0x83, 0x4c, 0x0a, 0xdd, 0x70, 0x77, 0xa9, 0x33, 0xd5, 0xb8, 0x5a, 0xb8, 0x56, 0x6b, 0x5e, 0x3d, + 0x3c, 0x98, 0xbe, 0x3c, 0xff, 0x10, 0x3e, 0x7c, 0x28, 0x0a, 0xb9, 0x03, 0x75, 0xd3, 0xf1, 0xd7, + 0x5d, 0xdb, 0x32, 0xf6, 0xa7, 0x46, 0x79, 0x03, 0x5f, 0x94, 0x8f, 0x5a, 0x5f, 0xbc, 0xdd, 0x12, + 0x84, 0x07, 0x07, 0xd3, 0x97, 0xfb, 0xa7, 0xd4, 0x99, 0x90, 0x8e, 0x11, 0x06, 0x59, 0xe3, 0x80, + 0x0b, 0xae, 0xb3, 0x6d, 0xb5, 0xa7, 0xc6, 0xf8, 0xdb, 0xb8, 0x3a, 0x60, 0x40, 0x2f, 0xde, 0x6e, + 0x09, 0xbe, 0xe6, 0x98, 0x14, 0x27, 0x6e, 0x31, 0x42, 0x20, 0x26, 0x8c, 0xab, 0xc9, 0x78, 0xc1, + 0xd6, 0xad, 0x8e, 0x3f, 0x35, 0xce, 0x07, 0xef, 0x6f, 0x0e, 0xc0, 0xc4, 0x38, 0x73, 0xf3, 0x82, + 0x7c, 0x94, 0xf1, 0x44, 0xb1, 0x8f, 0x29, 0xcc, 0x4b, 0xaf, 0xc3, 0x99, 0xbe, 0xb9, 0x81, 0x4c, + 0x42, 0x69, 0x97, 0xee, 0xf3, 0xa9, 0xaf, 0x8e, 0xec, 0x92, 0x9c, 0x83, 0xca, 0x9e, 0x6e, 0xf7, + 0xe8, 0x54, 0x91, 0x97, 0x89, 0x9b, 0x3f, 0x28, 0xbe, 0x56, 0xd0, 0xfe, 0x56, 0x09, 0x46, 0xd5, + 0x8c, 0xd3, 0xb2, 0x9c, 0x5d, 0xf2, 0x26, 0x94, 0x6c, 0xb7, 0x2d, 0xe7, 0xcd, 0x3f, 0x1a, 0x7a, + 0x16, 0x5b, 0x75, 0xdb, 0xcd, 0xea, 0xe1, 0xc1, 0x74, 0x69, 0xd5, 0x6d, 0x23, 0x43, 0x24, 0x06, + 0x54, 0x76, 0xf5, 0xed, 0x5d, 0x9d, 0xb7, 0xa1, 0x31, 0xd7, 0x1c, 0x1a, 0xfa, 0x16, 0x43, 0x61, + 0x6d, 0x6d, 0xd6, 0x0f, 0x0f, 0xa6, 0x2b, 0xfc, 0x16, 0x05, 0x36, 0x71, 0xa1, 0xbe, 0x65, 0xeb, + 0xc6, 0xee, 0x8e, 0x6b, 0xd3, 0xa9, 0x52, 0x4e, 0x41, 0x4d, 0x85, 0x24, 0x5e, 0x73, 0x78, 0x8b, + 0x91, 0x0c, 0x62, 0xc0, 0x48, 0xcf, 0xf4, 0x2d, 0x67, 0x57, 0xce, 0x81, 0xaf, 0x0f, 0x2d, 0x6d, + 0x73, 0x91, 0x3f, 0x13, 0x1c, 0x1e, 0x4c, 0x8f, 0x88, 0x6b, 0x94, 0xd0, 0xda, 0x2f, 0x47, 0x61, + 0x5c, 0xbd, 0xa4, 0xbb, 0xd4, 0x0b, 0xe8, 0x7d, 0x72, 0x15, 0xca, 0x0e, 0xfb, 0x34, 0xf9, 0x4b, + 0x6e, 0x8e, 0xca, 0xe1, 0x52, 0xe6, 0x9f, 0x24, 0xa7, 0xb0, 0x96, 0x89, 0xa1, 0x22, 0x3b, 0x7c, + 0xf8, 0x96, 0xb5, 0x38, 0x8c, 0x68, 0x99, 0xb8, 0x46, 0x09, 0x4d, 0xde, 0x86, 0x32, 0x7f, 0x78, + 0xd1, 0xd5, 0x9f, 0x1f, 0x5e, 0x04, 0x7b, 0xf4, 0x1a, 0x7b, 0x02, 0xfe, 0xe0, 0x1c, 0x94, 0x0d, + 0xc5, 0x9e, 0xb9, 0x2d, 0x3b, 0xf6, 0x8f, 0x72, 0x74, 0xec, 0x92, 0x18, 0x8a, 0x9b, 0x8b, 0x4b, + 0xc8, 0x10, 0xc9, 0x5f, 0x29, 0xc0, 0x19, 0xc3, 0x75, 0x02, 0x9d, 0xe9, 0x19, 0x6a, 0x91, 0x9d, + 0xaa, 0x70, 0x39, 0x37, 0x87, 0x96, 0xb3, 0x90, 0x46, 0x6c, 0x9e, 0x67, 0x6b, 0x46, 0x5f, 0x31, + 0xf6, 0xcb, 0x26, 0x7f, 0xad, 0x00, 0xe7, 0xd9, 0x5c, 0xde, 0xc7, 0xcc, 0x57, 0xa0, 0x93, 0x6d, + 0xd5, 0xc5, 0xc3, 0x83, 0xe9, 0xf3, 0x2b, 0x59, 0xc2, 0x30, 0xbb, 0x0d, 0xac, 0x75, 0x67, 0xf5, + 0x7e, 0xb5, 0x84, 0xaf, 0x6e, 0x8d, 0xb9, 0xd5, 0x93, 0x54, 0x75, 0x9a, 0xcf, 0xc8, 0xa1, 0x9c, + 0xa5, 0xd9, 0x61, 0x56, 0x2b, 0xc8, 0x75, 0xa8, 0xee, 0xb9, 0x76, 0xaf, 0x43, 0xfd, 0xa9, 0x1a, + 0x9f, 0x62, 0x2f, 0x65, 0x4d, 0xb1, 0x77, 0x39, 0x4b, 0x73, 0x42, 0xc2, 0x57, 0xc5, 0xbd, 0x8f, + 0xaa, 0x2e, 0xb1, 0x60, 0xc4, 0xb6, 0x3a, 0x56, 0xe0, 0xf3, 0x85, 0xb3, 0x31, 0x77, 0x7d, 0xe8, + 0xc7, 0x12, 0x9f, 0xe8, 0x2a, 0x07, 0x13, 0x5f, 0x8d, 0xb8, 0x46, 0x29, 0x80, 0x4d, 0x85, 0xbe, + 0xa1, 0xdb, 0x62, 0x61, 0x6d, 0xcc, 0x7d, 0x61, 0xf8, 0xcf, 0x86, 0xa1, 0x34, 0xc7, 0xe4, 0x33, + 0x55, 0xf8, 0x2d, 0x0a, 0x6c, 0xf2, 0x65, 0x18, 0x4f, 0xbc, 0x4d, 0x7f, 0xaa, 0xc1, 0x7b, 0xe7, + 0xd9, 0xac, 0xde, 0x09, 0xb9, 0xa2, 0x95, 0x27, 0x31, 0x42, 0x7c, 0x4c, 0x81, 0x91, 0x5b, 0x50, + 0xf3, 0x2d, 0x93, 0x1a, 0xba, 0xe7, 0x4f, 0x8d, 0x1e, 0x05, 0x78, 0x52, 0x02, 0xd7, 0x5a, 0xb2, + 0x1a, 0x86, 0x00, 0x64, 0x06, 0xa0, 0xab, 0x7b, 0x81, 0x25, 0x14, 0xd5, 0x31, 0xae, 0x34, 0x8d, + 0x1f, 0x1e, 0x4c, 0xc3, 0x7a, 0x58, 0x8a, 0x31, 0x0e, 0xc6, 0xcf, 0xea, 0xae, 0x38, 0xdd, 0x5e, + 0x20, 0x16, 0xd6, 0xba, 0xe0, 0x6f, 0x85, 0xa5, 0x18, 0xe3, 0x20, 0xdf, 0x2f, 0xc0, 0x33, 0xd1, + 0x6d, 0xff, 0x47, 0x36, 0x71, 0xe2, 0x1f, 0xd9, 0xf4, 0xe1, 0xc1, 0xf4, 0x33, 0xad, 0xc1, 0x22, + 0xf1, 0x61, 0xed, 0x21, 0x1f, 0x16, 0x60, 0xbc, 0xd7, 0x35, 0xf5, 0x80, 0xb6, 0x02, 0xb6, 0xe3, + 0x69, 0xef, 0x4f, 0x4d, 0xf2, 0x26, 0x2e, 0x0f, 0x3f, 0x0b, 0x26, 0xe0, 0xa2, 0xd7, 0x9c, 0x2c, + 0xc7, 0x94, 0x58, 0xed, 0x4d, 0x18, 0x9b, 0xef, 0x05, 0x3b, 0xae, 0x67, 0xbd, 0xcf, 0xd5, 0x7f, + 0xb2, 0x04, 0x95, 0x80, 0xab, 0x71, 0x42, 0x43, 0x78, 0x3e, 0xeb, 0xa5, 0x0b, 0x95, 0xfa, 0x16, + 0xdd, 0x57, 0x7a, 0x89, 0x58, 0xa9, 0x85, 0x5a, 0x27, 0xaa, 0x6b, 0x7f, 0xae, 0x00, 0xd5, 0xa6, + 0x6e, 0xec, 0xba, 0xdb, 0xdb, 0xe4, 0x2d, 0xa8, 0x59, 0x4e, 0x40, 0xbd, 0x3d, 0xdd, 0x96, 0xb0, + 0x33, 0x31, 0xd8, 0x70, 0x43, 0x18, 0x3d, 0x1e, 0xdb, 0x7d, 0x31, 0x41, 0x8b, 0x3d, 0xb9, 0x6b, + 0xe1, 0x9a, 0xf1, 0x8a, 0xc4, 0xc0, 0x10, 0x8d, 0x4c, 0x43, 0xc5, 0x0f, 0x68, 0xd7, 0xe7, 0x6b, + 0xe0, 0x98, 0x68, 0x46, 0x8b, 0x15, 0xa0, 0x28, 0xd7, 0xfe, 0x66, 0x01, 0xea, 0x4d, 0xdd, 0xb7, + 0x0c, 0xf6, 0x94, 0x64, 0x01, 0xca, 0x3d, 0x9f, 0x7a, 0xc7, 0x7b, 0x36, 0xbe, 0x6c, 0x6d, 0xfa, + 0xd4, 0x43, 0x5e, 0x99, 0xdc, 0x81, 0x5a, 0x57, 0xf7, 0xfd, 0x7b, 0xae, 0x67, 0xca, 0xa5, 0xf7, + 0x88, 0x40, 0x62, 0x9b, 0x20, 0xab, 0x62, 0x08, 0xa2, 0x35, 0x20, 0xd2, 0x3d, 0xb4, 0x9f, 0x17, + 0xe0, 0x6c, 0xb3, 0xb7, 0xbd, 0x4d, 0x3d, 0xa9, 0x15, 0x4b, 0x7d, 0x93, 0x42, 0xc5, 0xa3, 0xa6, + 0xe5, 0xcb, 0xb6, 0x2f, 0x0e, 0x3d, 0x50, 0x90, 0xa1, 0x48, 0xf5, 0x96, 0xf7, 0x17, 0x2f, 0x40, + 0x81, 0x4e, 0x7a, 0x50, 0x7f, 0x97, 0xb2, 0xdd, 0x38, 0xd5, 0x3b, 0xf2, 0xe9, 0x6e, 0x0c, 0x2d, + 0xea, 0x26, 0x0d, 0x5a, 0x1c, 0x29, 0xae, 0x4d, 0x87, 0x85, 0x18, 0x49, 0xd2, 0x7e, 0x54, 0x81, + 0xd1, 0x05, 0xb7, 0xb3, 0x65, 0x39, 0xd4, 0xbc, 0x6e, 0xb6, 0x29, 0x79, 0x07, 0xca, 0xd4, 0x6c, + 0x53, 0xf9, 0xb4, 0xc3, 0x2b, 0x1e, 0x0c, 0x2c, 0x52, 0x9f, 0xd8, 0x1d, 0x72, 0x60, 0xb2, 0x0a, + 0xe3, 0xdb, 0x9e, 0xdb, 0x11, 0x73, 0xf9, 0xc6, 0x7e, 0x57, 0xea, 0xce, 0xcd, 0xdf, 0x54, 0x1f, + 0xce, 0x52, 0x82, 0xfa, 0xe0, 0x60, 0x1a, 0xa2, 0x3b, 0x4c, 0xd5, 0x25, 0x6f, 0xc1, 0x54, 0x54, + 0x12, 0x4e, 0x6a, 0x0b, 0x6c, 0x3b, 0xc3, 0x75, 0xa7, 0x4a, 0xf3, 0xf2, 0xe1, 0xc1, 0xf4, 0xd4, + 0xd2, 0x00, 0x1e, 0x1c, 0x58, 0x9b, 0x4d, 0x15, 0x93, 0x11, 0x51, 0x2c, 0x34, 0x52, 0x65, 0x3a, + 0xa1, 0x15, 0x8c, 0xef, 0xfb, 0x96, 0x52, 0x22, 0xb0, 0x4f, 0x28, 0x59, 0x82, 0xd1, 0xc0, 0x8d, + 0xf5, 0x57, 0x85, 0xf7, 0x97, 0xa6, 0x0c, 0x15, 0x1b, 0xee, 0xc0, 0xde, 0x4a, 0xd4, 0x23, 0x08, + 0x17, 0xd4, 0x7d, 0xaa, 0xa7, 0x46, 0x78, 0x4f, 0x5d, 0x3a, 0x3c, 0x98, 0xbe, 0xb0, 0x91, 0xc9, + 0x81, 0x03, 0x6a, 0x92, 0x6f, 0x14, 0x60, 0x5c, 0x91, 0x64, 0x1f, 0x55, 0x4f, 0xb2, 0x8f, 0x08, + 0x1b, 0x11, 0x1b, 0x09, 0x01, 0x98, 0x12, 0xa8, 0xfd, 0xb2, 0x0c, 0xf5, 0x70, 0xaa, 0x27, 0xcf, + 0x41, 0x85, 0x9b, 0x20, 0xa4, 0x06, 0x1f, 0xae, 0xe1, 0xdc, 0x52, 0x81, 0x82, 0x46, 0x9e, 0x87, + 0xaa, 0xe1, 0x76, 0x3a, 0xba, 0x63, 0x72, 0xb3, 0x52, 0xbd, 0xd9, 0x60, 0xaa, 0xcb, 0x82, 0x28, + 0x42, 0x45, 0x23, 0x97, 0xa1, 0xac, 0x7b, 0x6d, 0x61, 0xe1, 0xa9, 0x8b, 0xf9, 0x68, 0xde, 0x6b, + 0xfb, 0xc8, 0x4b, 0xc9, 0xe7, 0xa0, 0x44, 0x9d, 0xbd, 0xa9, 0xf2, 0x60, 0xdd, 0xe8, 0xba, 0xb3, + 0x77, 0x57, 0xf7, 0x9a, 0x0d, 0xd9, 0x86, 0xd2, 0x75, 0x67, 0x0f, 0x59, 0x1d, 0xb2, 0x0a, 0x55, + 0xea, 0xec, 0xb1, 0x77, 0x2f, 0x4d, 0x2f, 0xbf, 0x31, 0xa0, 0x3a, 0x63, 0x91, 0xdb, 0x84, 0x50, + 0xc3, 0x92, 0xc5, 0xa8, 0x20, 0xc8, 0x97, 0x60, 0x54, 0x28, 0x5b, 0x6b, 0xec, 0x9d, 0xf8, 0x53, + 0x23, 0x1c, 0x72, 0x7a, 0xb0, 0xb6, 0xc6, 0xf9, 0x22, 0x53, 0x57, 0xac, 0xd0, 0xc7, 0x04, 0x14, + 0xf9, 0x12, 0xd4, 0xd5, 0xce, 0x58, 0xbd, 0xd9, 0x4c, 0x2b, 0x91, 0xda, 0x4e, 0x23, 0x7d, 0xaf, + 0x67, 0x79, 0xb4, 0x43, 0x9d, 0xc0, 0x6f, 0x9e, 0x51, 0x76, 0x03, 0x45, 0xf5, 0x31, 0x42, 0x23, + 0x5b, 0xfd, 0xe6, 0x2e, 0x61, 0xab, 0x79, 0x6e, 0xc0, 0xac, 0x3e, 0x84, 0xad, 0xeb, 0x2b, 0x30, + 0x11, 0xda, 0xa3, 0xa4, 0x49, 0x43, 0x58, 0x6f, 0x5e, 0x62, 0xd5, 0x57, 0x92, 0xa4, 0x07, 0x07, + 0xd3, 0xcf, 0x66, 0x18, 0x35, 0x22, 0x06, 0x4c, 0x83, 0x69, 0x3f, 0x2c, 0x41, 0xff, 0x3e, 0x24, + 0xd9, 0x69, 0x85, 0x93, 0xee, 0xb4, 0xf4, 0x03, 0x89, 0xe9, 0xf3, 0x35, 0x59, 0x2d, 0xff, 0x43, + 0x65, 0xbd, 0x98, 0xd2, 0x49, 0xbf, 0x98, 0x27, 0xe5, 0xdb, 0xd1, 0x3e, 0x2a, 0xc3, 0xf8, 0xa2, + 0x4e, 0x3b, 0xae, 0xf3, 0xc8, 0x5d, 0x59, 0xe1, 0x89, 0xd8, 0x95, 0x5d, 0x83, 0x9a, 0x47, 0xbb, + 0xb6, 0x65, 0xe8, 0x42, 0xf9, 0x92, 0x56, 0x50, 0x94, 0x65, 0x18, 0x52, 0x07, 0xec, 0xc6, 0x4b, + 0x4f, 0xe4, 0x6e, 0xbc, 0xfc, 0xc9, 0xef, 0xc6, 0xb5, 0x6f, 0x14, 0x81, 0x2b, 0x2a, 0xe4, 0x2a, + 0x94, 0xd9, 0x22, 0x9c, 0xb6, 0x01, 0xf1, 0x81, 0xc3, 0x29, 0xe4, 0x12, 0x14, 0x03, 0x57, 0x7e, + 0x79, 0x20, 0xe9, 0xc5, 0x0d, 0x17, 0x8b, 0x81, 0x4b, 0xde, 0x07, 0x30, 0x5c, 0xc7, 0xb4, 0x94, + 0x73, 0x20, 0xdf, 0x83, 0x2d, 0xb9, 0xde, 0x3d, 0xdd, 0x33, 0x17, 0x42, 0x44, 0xb1, 0x1f, 0x8b, + 0xee, 0x31, 0x26, 0x8d, 0xbc, 0x0e, 0x23, 0xae, 0xb3, 0xd4, 0xb3, 0x6d, 0xde, 0xa1, 0xf5, 0xe6, + 0x6f, 0xb1, 0x4d, 0xf2, 0x1d, 0x5e, 0xf2, 0xe0, 0x60, 0xfa, 0xa2, 0xd0, 0x6f, 0xd9, 0xdd, 0x9b, + 0x9e, 0x15, 0x58, 0x4e, 0x3b, 0xdc, 0x9e, 0xc8, 0x6a, 0xda, 0xb7, 0x0a, 0xd0, 0x58, 0xb2, 0xee, + 0x53, 0xf3, 0x4d, 0xcb, 0x31, 0xdd, 0x7b, 0x04, 0x61, 0xc4, 0xa6, 0x4e, 0x3b, 0xd8, 0x19, 0x72, + 0xff, 0x20, 0x76, 0xe9, 0x1c, 0x01, 0x25, 0x12, 0x99, 0x85, 0xba, 0xd0, 0x3e, 0x2d, 0xa7, 0xcd, + 0xfb, 0xb0, 0x16, 0x4d, 0x7a, 0x2d, 0x45, 0xc0, 0x88, 0x47, 0xdb, 0x87, 0x33, 0x7d, 0xdd, 0x40, + 0x4c, 0x28, 0x07, 0x7a, 0x5b, 0xcd, 0xaf, 0x4b, 0x43, 0x77, 0xf0, 0x86, 0xde, 0x8e, 0x75, 0x2e, + 0x5f, 0xe3, 0x37, 0x74, 0xb6, 0xc6, 0x33, 0x74, 0xed, 0x57, 0x05, 0xa8, 0x2d, 0xf5, 0x1c, 0x83, + 0x6f, 0xd1, 0x1e, 0x6d, 0x1b, 0x54, 0x0a, 0x43, 0x31, 0x53, 0x61, 0xe8, 0xc1, 0xc8, 0xee, 0xbd, + 0x50, 0xa1, 0x68, 0xcc, 0xad, 0x0d, 0x3f, 0x2a, 0x64, 0x93, 0x66, 0x6e, 0x71, 0x3c, 0xe1, 0xba, + 0x1a, 0x97, 0x0d, 0x1a, 0xb9, 0xf5, 0x26, 0x17, 0x2a, 0x85, 0x5d, 0xfa, 0x1c, 0x34, 0x62, 0x6c, + 0xc7, 0xb2, 0x62, 0xff, 0xbd, 0x32, 0x8c, 0x2c, 0xb7, 0x5a, 0xf3, 0xeb, 0x2b, 0xe4, 0x65, 0x68, + 0x48, 0xaf, 0xc6, 0xed, 0xa8, 0x0f, 0x42, 0xa7, 0x56, 0x2b, 0x22, 0x61, 0x9c, 0x8f, 0xa9, 0x63, + 0x1e, 0xd5, 0xed, 0x8e, 0xfc, 0x58, 0x42, 0x75, 0x0c, 0x59, 0x21, 0x0a, 0x1a, 0xd1, 0x61, 0x9c, + 0xed, 0xf0, 0x58, 0x17, 0x8a, 0xdd, 0x9b, 0xfc, 0x6c, 0x8e, 0xb8, 0xbf, 0xe3, 0x4a, 0xe2, 0x66, + 0x02, 0x00, 0x53, 0x80, 0xe4, 0x35, 0xa8, 0xe9, 0xbd, 0x60, 0x87, 0x2b, 0xd0, 0xe2, 0xdb, 0xb8, + 0xcc, 0x9d, 0x3e, 0xb2, 0xec, 0xc1, 0xc1, 0xf4, 0xe8, 0x2d, 0x6c, 0xbe, 0xac, 0xee, 0x31, 0xe4, + 0x66, 0x8d, 0x53, 0x3b, 0x46, 0xd9, 0xb8, 0xca, 0xb1, 0x1b, 0xb7, 0x9e, 0x00, 0xc0, 0x14, 0x20, + 0x79, 0x1b, 0x46, 0x77, 0xe9, 0x7e, 0xa0, 0x6f, 0x49, 0x01, 0x23, 0xc7, 0x11, 0x30, 0xc9, 0x54, + 0xb8, 0x5b, 0xb1, 0xea, 0x98, 0x00, 0x23, 0x3e, 0x9c, 0xdb, 0xa5, 0xde, 0x16, 0xf5, 0x5c, 0xb9, + 0xfb, 0x94, 0x42, 0xaa, 0xc7, 0x11, 0x32, 0x75, 0x78, 0x30, 0x7d, 0xee, 0x56, 0x06, 0x0c, 0x66, + 0x82, 0x6b, 0xff, 0xbb, 0x08, 0x13, 0xcb, 0xc2, 0xad, 0xec, 0x7a, 0x62, 0x11, 0x26, 0x17, 0xa1, + 0xe4, 0x75, 0x7b, 0x7c, 0xe4, 0x94, 0x84, 0xe1, 0x18, 0xd7, 0x37, 0x91, 0x95, 0x91, 0xb7, 0xa0, + 0x66, 0xca, 0x29, 0x43, 0x6e, 0x7e, 0x87, 0x32, 0x54, 0xa8, 0x3b, 0x0c, 0xd1, 0x98, 0xa6, 0xdf, + 0xf1, 0xdb, 0x2d, 0xeb, 0x7d, 0x2a, 0xf7, 0x83, 0x5c, 0xd3, 0x5f, 0x13, 0x45, 0xa8, 0x68, 0x6c, + 0x55, 0xdd, 0xa5, 0xfb, 0x62, 0x37, 0x54, 0x8e, 0x56, 0xd5, 0x5b, 0xb2, 0x0c, 0x43, 0x2a, 0x99, + 0x56, 0x1f, 0x0b, 0x1b, 0x05, 0x65, 0xb1, 0x93, 0xbf, 0xcb, 0x0a, 0xe4, 0x77, 0xc3, 0xa6, 0xcc, + 0x77, 0xad, 0x20, 0xa0, 0x9e, 0x7c, 0x8d, 0x43, 0x4d, 0x99, 0x37, 0x39, 0x02, 0x4a, 0x24, 0xf2, + 0x3b, 0x50, 0xe7, 0xe0, 0x4d, 0xdb, 0xdd, 0xe2, 0x2f, 0xae, 0x2e, 0xf6, 0xf4, 0x77, 0x55, 0x21, + 0x46, 0x74, 0xed, 0xd7, 0x45, 0xb8, 0xb0, 0x4c, 0x03, 0xa1, 0xd5, 0x2c, 0xd2, 0xae, 0xed, 0xee, + 0x33, 0xd5, 0x12, 0xe9, 0x7b, 0xe4, 0x0d, 0x00, 0xcb, 0xdf, 0x6a, 0xed, 0x19, 0xfc, 0x3b, 0x10, + 0xdf, 0xf0, 0x55, 0xf9, 0x49, 0xc2, 0x4a, 0xab, 0x29, 0x29, 0x0f, 0x12, 0x77, 0x18, 0xab, 0x13, + 0x6d, 0xaf, 0x8a, 0x0f, 0xd9, 0x5e, 0xb5, 0x00, 0xba, 0x91, 0x82, 0x5a, 0xe2, 0x9c, 0xbf, 0xaf, + 0xc4, 0x1c, 0x47, 0x37, 0x8d, 0xc1, 0xe4, 0x51, 0x19, 0x1d, 0x98, 0x34, 0xe9, 0xb6, 0xde, 0xb3, + 0x83, 0x50, 0xa9, 0x96, 0x1f, 0xf1, 0xd1, 0xf5, 0xf2, 0xd0, 0xe5, 0xbd, 0x98, 0x42, 0xc2, 0x3e, + 0x6c, 0xed, 0xef, 0x97, 0xe0, 0xd2, 0x32, 0x0d, 0x42, 0x8b, 0x8b, 0x9c, 0x1d, 0x5b, 0x5d, 0x6a, + 0xb0, 0xb7, 0xf0, 0x61, 0x01, 0x46, 0x6c, 0x7d, 0x8b, 0xda, 0x6c, 0xf5, 0x62, 0x4f, 0xf3, 0xce, + 0xd0, 0x0b, 0xc1, 0x60, 0x29, 0x33, 0xab, 0x5c, 0x42, 0x6a, 0x69, 0x10, 0x85, 0x28, 0xc5, 0xb3, + 0x49, 0xdd, 0xb0, 0x7b, 0x7e, 0x40, 0xbd, 0x75, 0xd7, 0x0b, 0xa4, 0x3e, 0x19, 0x4e, 0xea, 0x0b, + 0x11, 0x09, 0xe3, 0x7c, 0x64, 0x0e, 0xc0, 0xb0, 0x2d, 0xea, 0x04, 0xbc, 0x96, 0xf8, 0xae, 0x88, + 0x7a, 0xbf, 0x0b, 0x21, 0x05, 0x63, 0x5c, 0x4c, 0x54, 0xc7, 0x75, 0xac, 0xc0, 0x15, 0xa2, 0xca, + 0x49, 0x51, 0x6b, 0x11, 0x09, 0xe3, 0x7c, 0xbc, 0x1a, 0x0d, 0x3c, 0xcb, 0xf0, 0x79, 0xb5, 0x4a, + 0xaa, 0x5a, 0x44, 0xc2, 0x38, 0x1f, 0x5b, 0xf3, 0x62, 0xcf, 0x7f, 0xac, 0x35, 0xef, 0x7b, 0x75, + 0xb8, 0x92, 0xe8, 0xd6, 0x40, 0x0f, 0xe8, 0x76, 0xcf, 0x6e, 0xd1, 0x40, 0xbd, 0xc0, 0x21, 0xd7, + 0xc2, 0xbf, 0x18, 0xbd, 0x77, 0x11, 0xcc, 0x62, 0x9c, 0xcc, 0x7b, 0xef, 0x6b, 0xe0, 0x91, 0xde, + 0xfd, 0x2c, 0xd4, 0x1d, 0x3d, 0xf0, 0xf9, 0x87, 0x2b, 0xbf, 0xd1, 0x50, 0x0d, 0xbb, 0xad, 0x08, + 0x18, 0xf1, 0x90, 0x75, 0x38, 0x27, 0xbb, 0xf8, 0xfa, 0xfd, 0xae, 0xeb, 0x05, 0xd4, 0x13, 0x75, + 0xe5, 0x72, 0x2a, 0xeb, 0x9e, 0x5b, 0xcb, 0xe0, 0xc1, 0xcc, 0x9a, 0x64, 0x0d, 0xce, 0x1a, 0xc2, + 0xc1, 0x4f, 0x6d, 0x57, 0x37, 0x15, 0xa0, 0x30, 0x70, 0x85, 0x5b, 0xa3, 0x85, 0x7e, 0x16, 0xcc, + 0xaa, 0x97, 0x1e, 0xcd, 0x23, 0x43, 0x8d, 0xe6, 0xea, 0x30, 0xa3, 0xb9, 0x36, 0xdc, 0x68, 0xae, + 0x1f, 0x6d, 0x34, 0xb3, 0x9e, 0x67, 0xe3, 0x88, 0x7a, 0x4c, 0x3d, 0x11, 0x2b, 0x6c, 0x2c, 0x7e, + 0x24, 0xec, 0xf9, 0x56, 0x06, 0x0f, 0x66, 0xd6, 0x24, 0x5b, 0x70, 0x49, 0x94, 0x5f, 0x77, 0x0c, + 0x6f, 0xbf, 0xcb, 0x16, 0x9e, 0x18, 0x6e, 0x23, 0x61, 0x61, 0xbc, 0xd4, 0x1a, 0xc8, 0x89, 0x0f, + 0x41, 0x21, 0x7f, 0x08, 0x63, 0xe2, 0x2d, 0xad, 0xe9, 0x5d, 0x0e, 0x2b, 0xa2, 0x49, 0xce, 0x4b, + 0xd8, 0xb1, 0x85, 0x38, 0x11, 0x93, 0xbc, 0x64, 0x1e, 0x26, 0xba, 0x7b, 0x06, 0xbb, 0x5c, 0xd9, + 0xbe, 0x4d, 0xa9, 0x49, 0x4d, 0xee, 0xbe, 0xaa, 0x37, 0x9f, 0x56, 0x86, 0x8e, 0xf5, 0x24, 0x19, + 0xd3, 0xfc, 0xe4, 0x35, 0x18, 0xf5, 0x03, 0xdd, 0x0b, 0xa4, 0x59, 0x6f, 0x6a, 0x5c, 0x44, 0xdb, + 0x28, 0xab, 0x57, 0x2b, 0x46, 0xc3, 0x04, 0x67, 0xe6, 0x7a, 0x31, 0x71, 0x7a, 0xeb, 0x45, 0x9e, + 0xd9, 0xea, 0x9f, 0x15, 0xe1, 0xea, 0x32, 0x0d, 0xd6, 0x5c, 0x47, 0x1a, 0x45, 0xb3, 0x96, 0xfd, + 0x23, 0xd9, 0x44, 0x93, 0x8b, 0x76, 0xf1, 0x44, 0x17, 0xed, 0xd2, 0x09, 0x2d, 0xda, 0xe5, 0x53, + 0x5c, 0xb4, 0xff, 0x61, 0x11, 0x9e, 0x4e, 0xf4, 0xe4, 0xba, 0x6b, 0xaa, 0x09, 0xff, 0xb3, 0x0e, + 0x3c, 0x42, 0x07, 0x3e, 0x10, 0x7a, 0x27, 0x77, 0x6b, 0xa5, 0x34, 0x9e, 0x0f, 0xd2, 0x1a, 0xcf, + 0xdb, 0x79, 0x56, 0xbe, 0x0c, 0x09, 0x47, 0x5a, 0xf1, 0x6e, 0x02, 0xf1, 0xa4, 0x13, 0x4e, 0x98, + 0x7e, 0x62, 0x4a, 0x4f, 0x18, 0xce, 0x87, 0x7d, 0x1c, 0x98, 0x51, 0x8b, 0xb4, 0xe0, 0xbc, 0x4f, + 0x9d, 0xc0, 0x72, 0xa8, 0x9d, 0x84, 0x13, 0xda, 0xd0, 0xb3, 0x12, 0xee, 0x7c, 0x2b, 0x8b, 0x09, + 0xb3, 0xeb, 0xe6, 0x99, 0x07, 0xfe, 0x25, 0x70, 0x95, 0x53, 0x74, 0xcd, 0x89, 0x69, 0x2c, 0x1f, + 0xa6, 0x35, 0x96, 0x77, 0xf2, 0xbf, 0xb7, 0xe1, 0xb4, 0x95, 0x39, 0x00, 0xfe, 0x16, 0xe2, 0xea, + 0x4a, 0xb8, 0x48, 0x63, 0x48, 0xc1, 0x18, 0x17, 0x5b, 0x80, 0x54, 0x3f, 0xc7, 0x35, 0x95, 0x70, + 0x01, 0x6a, 0xc5, 0x89, 0x98, 0xe4, 0x1d, 0xa8, 0xed, 0x54, 0x86, 0xd6, 0x76, 0x6e, 0x02, 0x49, + 0x18, 0x1e, 0x05, 0xde, 0x48, 0x32, 0x9a, 0x74, 0xa5, 0x8f, 0x03, 0x33, 0x6a, 0x0d, 0x18, 0xca, + 0xd5, 0x93, 0x1d, 0xca, 0xb5, 0xe1, 0x87, 0x32, 0x79, 0x07, 0x2e, 0x72, 0x51, 0xb2, 0x7f, 0x92, + 0xc0, 0x42, 0xef, 0xf9, 0x0d, 0x09, 0x7c, 0x11, 0x07, 0x31, 0xe2, 0x60, 0x0c, 0xf6, 0x7e, 0x0c, + 0x8f, 0x9a, 0x4c, 0xb8, 0x6e, 0x0f, 0xd6, 0x89, 0x16, 0x32, 0x78, 0x30, 0xb3, 0x26, 0x1b, 0x62, + 0x01, 0x1b, 0x86, 0xfa, 0x96, 0x4d, 0x4d, 0x19, 0x4d, 0x1b, 0x0e, 0xb1, 0x8d, 0xd5, 0x96, 0xa4, + 0x60, 0x8c, 0x2b, 0x4b, 0x4d, 0x19, 0x3d, 0xa6, 0x9a, 0xb2, 0xcc, 0xad, 0xf4, 0xdb, 0x09, 0x6d, + 0x48, 0xea, 0x3a, 0x61, 0x7c, 0xf4, 0x42, 0x9a, 0x01, 0xfb, 0xeb, 0x70, 0x2d, 0xd1, 0xf0, 0xac, + 0x6e, 0xe0, 0x27, 0xb1, 0xc6, 0x53, 0x5a, 0x62, 0x06, 0x0f, 0x66, 0xd6, 0x64, 0xfa, 0xf9, 0x0e, + 0xd5, 0xed, 0x60, 0x27, 0x09, 0x38, 0x91, 0xd4, 0xcf, 0x6f, 0xf4, 0xb3, 0x60, 0x56, 0xbd, 0xcc, + 0x05, 0x69, 0xf2, 0xc9, 0x54, 0xab, 0xbe, 0x59, 0x82, 0x8b, 0xcb, 0x34, 0x08, 0x03, 0x8d, 0x3e, + 0x33, 0xa3, 0x7c, 0x02, 0x66, 0x94, 0xef, 0x56, 0xe0, 0xec, 0x32, 0x0d, 0xfa, 0xb4, 0xb1, 0xff, + 0x4f, 0xbb, 0x7f, 0x0d, 0xce, 0x46, 0xb1, 0x6d, 0xad, 0xc0, 0xf5, 0xc4, 0x5a, 0x9e, 0xda, 0x2d, + 0xb7, 0xfa, 0x59, 0x30, 0xab, 0x1e, 0xf9, 0x12, 0x3c, 0xcd, 0x97, 0x7a, 0xa7, 0x2d, 0xec, 0xb3, + 0xc2, 0x98, 0x10, 0x3b, 0x9d, 0x31, 0x2d, 0x21, 0x9f, 0x6e, 0x65, 0xb3, 0xe1, 0xa0, 0xfa, 0xe4, + 0x6b, 0x30, 0xda, 0xb5, 0xba, 0xd4, 0xb6, 0x1c, 0xae, 0x9f, 0xe5, 0x0e, 0x09, 0x59, 0x8f, 0x81, + 0x45, 0x1b, 0xb8, 0x78, 0x29, 0x26, 0x04, 0x66, 0x8e, 0xd4, 0xda, 0x29, 0x8e, 0xd4, 0xff, 0x51, + 0x84, 0xea, 0xb2, 0xe7, 0xf6, 0xba, 0xcd, 0x7d, 0xd2, 0x86, 0x91, 0x7b, 0xdc, 0x79, 0x26, 0x5d, + 0x53, 0xc3, 0xc7, 0x87, 0x0b, 0x1f, 0x5c, 0xa4, 0x12, 0x89, 0x7b, 0x94, 0xf0, 0x6c, 0x10, 0xef, + 0xd2, 0x7d, 0x6a, 0x4a, 0x1f, 0x5a, 0x38, 0x88, 0x6f, 0xb1, 0x42, 0x14, 0x34, 0xd2, 0x81, 0x09, + 0xdd, 0xb6, 0xdd, 0x7b, 0xd4, 0x5c, 0xd5, 0x03, 0xea, 0x50, 0x5f, 0xb9, 0x24, 0x8f, 0x6b, 0x96, + 0xe6, 0x7e, 0xfd, 0xf9, 0x24, 0x14, 0xa6, 0xb1, 0xc9, 0xbb, 0x50, 0xf5, 0x03, 0xd7, 0x53, 0xca, + 0x56, 0x63, 0x6e, 0x61, 0xf8, 0x97, 0xde, 0xfc, 0x62, 0x4b, 0x40, 0x09, 0x9b, 0xbd, 0xbc, 0x41, + 0x25, 0x40, 0xfb, 0x4e, 0x01, 0xe0, 0xc6, 0xc6, 0xc6, 0xba, 0x74, 0x2f, 0x98, 0x50, 0xd6, 0x7b, + 0xa1, 0xa3, 0x72, 0x78, 0x87, 0x60, 0x22, 0x2c, 0x53, 0xfa, 0xf0, 0x7a, 0xc1, 0x0e, 0x72, 0x74, + 0xf2, 0xdb, 0x50, 0x95, 0x0a, 0xb2, 0xec, 0xf6, 0x30, 0xb4, 0x40, 0x2a, 0xd1, 0xa8, 0xe8, 0xda, + 0xdf, 0x2d, 0x02, 0xac, 0x98, 0x36, 0x6d, 0xa9, 0x90, 0xfe, 0x7a, 0xb0, 0xe3, 0x51, 0x7f, 0xc7, + 0xb5, 0xcd, 0x21, 0xbd, 0xa9, 0xdc, 0xe6, 0xbf, 0xa1, 0x40, 0x30, 0xc2, 0x23, 0x26, 0x8c, 0xfa, + 0x01, 0xed, 0xaa, 0x48, 0xcd, 0x21, 0x9d, 0x28, 0x93, 0xc2, 0x2e, 0x12, 0xe1, 0x60, 0x02, 0x95, + 0xe8, 0xd0, 0xb0, 0x1c, 0x43, 0x7c, 0x20, 0xcd, 0xfd, 0x21, 0x07, 0xd2, 0x04, 0xdb, 0x71, 0xac, + 0x44, 0x30, 0x18, 0xc7, 0xd4, 0x7e, 0x56, 0x84, 0x0b, 0x5c, 0x1e, 0x6b, 0x46, 0x22, 0x1e, 0x93, + 0xfc, 0xe9, 0xbe, 0xe3, 0x87, 0x7f, 0xf2, 0x68, 0xa2, 0xc5, 0xe9, 0xb5, 0x35, 0x1a, 0xe8, 0x91, + 0x3e, 0x17, 0x95, 0xc5, 0xce, 0x1c, 0xf6, 0xa0, 0xec, 0xb3, 0xf9, 0x4a, 0xf4, 0x5e, 0x6b, 0xe8, + 0x21, 0x94, 0xfd, 0x00, 0x7c, 0xf6, 0x0a, 0xbd, 0xc6, 0x7c, 0xd6, 0xe2, 0xe2, 0xc8, 0x9f, 0x85, + 0x11, 0x3f, 0xd0, 0x83, 0x9e, 0xfa, 0x34, 0x37, 0x4f, 0x5a, 0x30, 0x07, 0x8f, 0xe6, 0x11, 0x71, + 0x8f, 0x52, 0xa8, 0xf6, 0xb3, 0x02, 0x5c, 0xca, 0xae, 0xb8, 0x6a, 0xf9, 0x01, 0xf9, 0x53, 0x7d, + 0xdd, 0x7e, 0xc4, 0x37, 0xce, 0x6a, 0xf3, 0x4e, 0x0f, 0x23, 0xd4, 0x55, 0x49, 0xac, 0xcb, 0x03, + 0xa8, 0x58, 0x01, 0xed, 0xa8, 0xfd, 0xe5, 0x9d, 0x13, 0x7e, 0xf4, 0xd8, 0xd2, 0xce, 0xa4, 0xa0, + 0x10, 0xa6, 0x7d, 0x54, 0x1c, 0xf4, 0xc8, 0x7c, 0xf9, 0xb0, 0x93, 0x31, 0xbf, 0xb7, 0xf2, 0xc5, + 0xfc, 0x26, 0x1b, 0xd4, 0x1f, 0xfa, 0xfb, 0x67, 0xfa, 0x43, 0x7f, 0xef, 0xe4, 0x0f, 0xfd, 0x4d, + 0x75, 0xc3, 0xc0, 0x08, 0xe0, 0x8f, 0x4b, 0x70, 0xf9, 0x61, 0xc3, 0x86, 0xad, 0x67, 0x72, 0x74, + 0xe6, 0x5d, 0xcf, 0x1e, 0x3e, 0x0e, 0xc9, 0x1c, 0x54, 0xba, 0x3b, 0xba, 0xaf, 0x94, 0x32, 0xb5, + 0x61, 0xa9, 0xac, 0xb3, 0xc2, 0x07, 0x6c, 0xd2, 0xe0, 0xca, 0x1c, 0xbf, 0x45, 0xc1, 0xca, 0xa6, + 0xe3, 0x0e, 0xf5, 0xfd, 0xc8, 0x26, 0x10, 0x4e, 0xc7, 0x6b, 0xa2, 0x18, 0x15, 0x9d, 0x04, 0x30, + 0x22, 0x4c, 0xcc, 0x72, 0x65, 0x1a, 0x3e, 0x90, 0x2b, 0x23, 0x4c, 0x3c, 0x7a, 0x28, 0xe9, 0xad, + 0x90, 0xb2, 0xc8, 0x0c, 0x94, 0x83, 0x28, 0x68, 0x57, 0x6d, 0xcd, 0xcb, 0x19, 0xfa, 0x29, 0xe7, + 0x63, 0x1b, 0x7b, 0x77, 0x8b, 0x1b, 0xd5, 0x4d, 0xe9, 0x3f, 0xb7, 0x5c, 0x87, 0x2b, 0x64, 0xa5, + 0x68, 0x63, 0x7f, 0xa7, 0x8f, 0x03, 0x33, 0x6a, 0x69, 0xff, 0xa6, 0x06, 0x17, 0xb2, 0xc7, 0x03, + 0xeb, 0xb7, 0x3d, 0xea, 0xf9, 0x0c, 0xbb, 0x90, 0xec, 0xb7, 0xbb, 0xa2, 0x18, 0x15, 0xfd, 0x53, + 0x1d, 0x70, 0xf6, 0xdd, 0x02, 0x5c, 0xf4, 0xa4, 0x8f, 0xe8, 0x71, 0x04, 0x9d, 0x3d, 0x2b, 0xcc, + 0x19, 0x03, 0x04, 0xe2, 0xe0, 0xb6, 0x90, 0xbf, 0x5d, 0x80, 0xa9, 0x4e, 0xca, 0xce, 0x71, 0x8a, + 0x27, 0xe8, 0x78, 0x54, 0xfc, 0xda, 0x00, 0x79, 0x38, 0xb0, 0x25, 0xe4, 0x6b, 0xd0, 0xe8, 0xb2, + 0x71, 0xe1, 0x07, 0xd4, 0x31, 0xd4, 0x21, 0xba, 0xe1, 0xbf, 0xa4, 0xf5, 0x08, 0x2b, 0x3c, 0x41, + 0xc3, 0xf5, 0x83, 0x18, 0x01, 0xe3, 0x12, 0x9f, 0xf0, 0x23, 0x73, 0xd7, 0xa0, 0xe6, 0xd3, 0x20, + 0xb0, 0x9c, 0xb6, 0xd8, 0x6f, 0xd4, 0xc5, 0xb7, 0xd2, 0x92, 0x65, 0x18, 0x52, 0xc9, 0xef, 0x40, + 0x9d, 0xbb, 0x9c, 0xe6, 0xbd, 0xb6, 0x3f, 0x55, 0xe7, 0xe1, 0x62, 0x63, 0x22, 0x00, 0x4e, 0x16, + 0x62, 0x44, 0x27, 0x2f, 0xc1, 0xe8, 0x16, 0xff, 0x7c, 0xe5, 0x29, 0x6a, 0x61, 0xe3, 0xe2, 0xda, + 0x5a, 0x33, 0x56, 0x8e, 0x09, 0x2e, 0x32, 0x07, 0x40, 0x43, 0xbf, 0x5c, 0xda, 0x9e, 0x15, 0x79, + 0xec, 0x30, 0xc6, 0x45, 0x9e, 0x85, 0x52, 0x60, 0xfb, 0xdc, 0x86, 0x55, 0x8b, 0xb6, 0xa0, 0x1b, + 0xab, 0x2d, 0x64, 0xe5, 0xda, 0xaf, 0x0b, 0x30, 0x91, 0x3a, 0x5c, 0xc2, 0xaa, 0xf4, 0x3c, 0x5b, + 0x4e, 0x23, 0x61, 0x95, 0x4d, 0x5c, 0x45, 0x56, 0x4e, 0xde, 0x91, 0x6a, 0x79, 0x31, 0x67, 0xc2, + 0x88, 0xdb, 0x7a, 0xe0, 0x33, 0x3d, 0xbc, 0x4f, 0x23, 0xe7, 0x6e, 0xbe, 0xa8, 0x3d, 0x72, 0x1d, + 0x88, 0xb9, 0xf9, 0x22, 0x1a, 0x26, 0x38, 0x53, 0x06, 0xbf, 0xf2, 0x51, 0x0c, 0x7e, 0xda, 0xb7, + 0x8a, 0xb1, 0x1e, 0x90, 0x9a, 0xfd, 0x23, 0x7a, 0xe0, 0x05, 0xb6, 0x80, 0x86, 0x8b, 0x7b, 0x3d, + 0xbe, 0xfe, 0xf1, 0xc5, 0x58, 0x52, 0xc9, 0x9b, 0xa2, 0xef, 0x4b, 0x39, 0x8f, 0xe5, 0x6e, 0xac, + 0xb6, 0x44, 0x74, 0x95, 0x7a, 0x6b, 0xe1, 0x2b, 0x28, 0x9f, 0xd2, 0x2b, 0xd0, 0xfe, 0x45, 0x09, + 0x1a, 0x37, 0xdd, 0xad, 0x4f, 0x49, 0x04, 0x75, 0xf6, 0x32, 0x55, 0xfc, 0x04, 0x97, 0xa9, 0x4d, + 0x78, 0x3a, 0x08, 0xec, 0x16, 0x35, 0x5c, 0xc7, 0xf4, 0xe7, 0xb7, 0x03, 0xea, 0x2d, 0x59, 0x8e, + 0xe5, 0xef, 0x50, 0x53, 0xba, 0x93, 0x9e, 0x39, 0x3c, 0x98, 0x7e, 0x7a, 0x63, 0x63, 0x35, 0x8b, + 0x05, 0x07, 0xd5, 0xe5, 0xd3, 0x86, 0x38, 0x09, 0xc8, 0x4f, 0xca, 0xc8, 0x98, 0x1b, 0x31, 0x6d, + 0xc4, 0xca, 0x31, 0xc1, 0xa5, 0xfd, 0xa0, 0x08, 0xf5, 0x30, 0x15, 0x00, 0x79, 0x1e, 0xaa, 0x5b, + 0x9e, 0xbb, 0x4b, 0x3d, 0xe1, 0xb9, 0x93, 0x27, 0x65, 0x9a, 0xa2, 0x08, 0x15, 0x8d, 0x3c, 0x07, + 0x95, 0xc0, 0xed, 0x5a, 0x46, 0xda, 0xa0, 0xb6, 0xc1, 0x0a, 0x51, 0xd0, 0x4e, 0x6f, 0x80, 0xbf, + 0x90, 0x50, 0xed, 0xea, 0x03, 0x95, 0xb1, 0xb7, 0xa1, 0xec, 0xeb, 0xbe, 0x2d, 0xd7, 0xd3, 0x1c, + 0xa7, 0xea, 0xe7, 0x5b, 0xab, 0xf2, 0x54, 0xfd, 0x7c, 0x6b, 0x15, 0x39, 0xa8, 0xf6, 0xcb, 0x22, + 0x34, 0x44, 0xbf, 0x89, 0x59, 0xe1, 0x24, 0x7b, 0xee, 0x75, 0x1e, 0x4a, 0xe1, 0xf7, 0x3a, 0xd4, + 0xe3, 0x66, 0x26, 0x39, 0xc9, 0xc5, 0xfd, 0x03, 0x11, 0x31, 0x0c, 0xa7, 0x88, 0x8a, 0x54, 0xd7, + 0x97, 0x4f, 0xb1, 0xeb, 0x2b, 0x47, 0xea, 0xfa, 0x91, 0xd3, 0xe8, 0xfa, 0x0f, 0x8b, 0x50, 0x5f, + 0xb5, 0xb6, 0xa9, 0xb1, 0x6f, 0xd8, 0xfc, 0x4c, 0xa0, 0x49, 0x6d, 0x1a, 0xd0, 0x65, 0x4f, 0x37, + 0xe8, 0x3a, 0xf5, 0x2c, 0x9e, 0x2a, 0x87, 0x7d, 0x1f, 0x7c, 0x06, 0x92, 0x67, 0x02, 0x17, 0x07, + 0xf0, 0xe0, 0xc0, 0xda, 0x64, 0x05, 0x46, 0x4d, 0xea, 0x5b, 0x1e, 0x35, 0xd7, 0x63, 0x1b, 0x95, + 0xe7, 0xd5, 0x52, 0xb3, 0x18, 0xa3, 0x3d, 0x38, 0x98, 0x1e, 0x53, 0x06, 0x4a, 0xb1, 0x63, 0x49, + 0x54, 0x65, 0x9f, 0x7c, 0x57, 0xef, 0xf9, 0x59, 0x6d, 0x8c, 0x7d, 0xf2, 0xeb, 0xd9, 0x2c, 0x38, + 0xa8, 0xae, 0x56, 0x81, 0xd2, 0xaa, 0xdb, 0xd6, 0x3e, 0x2a, 0x41, 0x98, 0x53, 0x89, 0xfc, 0x85, + 0x02, 0x34, 0x74, 0xc7, 0x71, 0x03, 0x99, 0xaf, 0x48, 0x78, 0xe0, 0x31, 0x77, 0xea, 0xa6, 0x99, + 0xf9, 0x08, 0x54, 0x38, 0x6f, 0x43, 0x87, 0x72, 0x8c, 0x82, 0x71, 0xd9, 0xa4, 0x97, 0xf2, 0x27, + 0xaf, 0xe5, 0x6f, 0xc5, 0x11, 0xbc, 0xc7, 0x97, 0xbe, 0x00, 0x93, 0xe9, 0xc6, 0x1e, 0xc7, 0x1d, + 0x94, 0xcb, 0x31, 0x5f, 0x04, 0x88, 0x62, 0x4a, 0x1e, 0x83, 0x11, 0xcb, 0x4a, 0x18, 0xb1, 0x86, + 0x3f, 0xd8, 0x1e, 0x35, 0x7a, 0xa0, 0xe1, 0xea, 0xbd, 0x94, 0xe1, 0x6a, 0xe5, 0x24, 0x84, 0x3d, + 0xdc, 0x58, 0xf5, 0x77, 0x0a, 0x30, 0x19, 0x31, 0xcb, 0x13, 0xb2, 0xaf, 0xc2, 0x98, 0x47, 0x75, + 0xb3, 0xa9, 0x07, 0xc6, 0x0e, 0x0f, 0xf5, 0x2e, 0xf0, 0xd8, 0xec, 0x33, 0x87, 0x07, 0xd3, 0x63, + 0x18, 0x27, 0x60, 0x92, 0x8f, 0xe8, 0xd0, 0x60, 0x05, 0x1b, 0x56, 0x87, 0xba, 0xbd, 0x60, 0x48, + 0xab, 0x29, 0xdf, 0xb0, 0x60, 0x04, 0x83, 0x71, 0x4c, 0xed, 0xe3, 0x02, 0x8c, 0xc7, 0x1b, 0x7c, + 0xea, 0x16, 0xb5, 0x9d, 0xa4, 0x45, 0x6d, 0xe1, 0x04, 0xde, 0xc9, 0x00, 0x2b, 0xda, 0x07, 0x10, + 0x7f, 0x34, 0x6e, 0x39, 0x8b, 0x1b, 0x0b, 0x0a, 0x0f, 0x35, 0x16, 0x7c, 0xfa, 0xd3, 0xe8, 0x0c, + 0xd2, 0x72, 0xcb, 0x4f, 0xb0, 0x96, 0xfb, 0x49, 0xe6, 0xe2, 0x89, 0xe5, 0x93, 0x19, 0xc9, 0x91, + 0x4f, 0xa6, 0x13, 0xe6, 0x93, 0xa9, 0x9e, 0xd8, 0xa4, 0x73, 0x94, 0x9c, 0x32, 0xb5, 0xc7, 0x9a, + 0x53, 0xa6, 0x7e, 0x5a, 0x39, 0x65, 0x20, 0x6f, 0x4e, 0x99, 0x0f, 0x0a, 0x30, 0x6e, 0x26, 0x4e, + 0xcc, 0x72, 0xdb, 0x42, 0x9e, 0xa5, 0x26, 0x79, 0x00, 0x57, 0x1c, 0x99, 0x4a, 0x96, 0x61, 0x4a, + 0x64, 0x56, 0x26, 0x97, 0xd1, 0x4f, 0x26, 0x93, 0xcb, 0x2f, 0xaa, 0xf1, 0x15, 0xe9, 0x71, 0x1b, + 0xcd, 0x5f, 0x49, 0x1a, 0xcd, 0xaf, 0xa6, 0x8d, 0xe6, 0x13, 0xb1, 0x78, 0xd6, 0xb8, 0xe1, 0xfc, + 0x77, 0x63, 0x13, 0x75, 0x89, 0xe7, 0x70, 0x09, 0xdf, 0x79, 0xc6, 0x64, 0x3d, 0x0f, 0x13, 0x52, + 0x7b, 0x55, 0x44, 0x3e, 0xcb, 0x8d, 0x45, 0x61, 0x4e, 0x8b, 0x49, 0x32, 0xa6, 0xf9, 0x99, 0x40, + 0x5f, 0xa5, 0xf2, 0x14, 0x5b, 0x85, 0x68, 0x90, 0xa9, 0x34, 0x9b, 0x21, 0x07, 0xdb, 0x56, 0x78, + 0x54, 0xf7, 0xa5, 0xe9, 0x3b, 0xb6, 0xad, 0x40, 0x5e, 0x8a, 0x92, 0x1a, 0xb7, 0xff, 0x57, 0x1f, + 0x61, 0xff, 0xd7, 0xa1, 0x61, 0xeb, 0x7e, 0x20, 0xde, 0xa6, 0x29, 0x3f, 0xe7, 0x3f, 0x71, 0xb4, + 0x85, 0x97, 0x2d, 0xe6, 0x91, 0x76, 0xbb, 0x1a, 0xc1, 0x60, 0x1c, 0x93, 0x98, 0x30, 0xca, 0x6e, + 0xf9, 0xa7, 0x6d, 0xce, 0x07, 0x32, 0xe1, 0xd5, 0x71, 0x64, 0x84, 0x66, 0xab, 0xd5, 0x18, 0x0e, + 0x26, 0x50, 0x07, 0xb8, 0x08, 0x60, 0x18, 0x17, 0x01, 0xf9, 0x43, 0xa1, 0x39, 0xed, 0x87, 0xaf, + 0xb5, 0xc1, 0x5f, 0x6b, 0x18, 0x22, 0x89, 0x71, 0x22, 0x26, 0x79, 0xd9, 0xa8, 0xe8, 0xc9, 0x6e, + 0x50, 0xd5, 0x47, 0x93, 0xa3, 0x62, 0x33, 0x49, 0xc6, 0x34, 0x3f, 0x59, 0x87, 0x73, 0x61, 0x51, + 0xbc, 0x19, 0x63, 0x1c, 0x27, 0x8c, 0x59, 0xdb, 0xcc, 0xe0, 0xc1, 0xcc, 0x9a, 0xfc, 0x10, 0x48, + 0xcf, 0xf3, 0xa8, 0x13, 0xdc, 0xd0, 0xfd, 0x1d, 0x19, 0xfc, 0x16, 0x1d, 0x02, 0x89, 0x48, 0x18, + 0xe7, 0x23, 0x73, 0x00, 0x02, 0x8e, 0xd7, 0x9a, 0x48, 0xc6, 0x97, 0x6e, 0x86, 0x14, 0x8c, 0x71, + 0x69, 0x1f, 0xd4, 0xa1, 0x71, 0x5b, 0x0f, 0xac, 0x3d, 0xca, 0xfd, 0x79, 0xa7, 0xe3, 0x54, 0xf9, + 0xeb, 0x05, 0xb8, 0x90, 0x0c, 0xda, 0x3c, 0x45, 0xcf, 0x0a, 0x4f, 0x01, 0x83, 0x99, 0xd2, 0x70, + 0x40, 0x2b, 0xb8, 0x8f, 0xa5, 0x2f, 0x06, 0xf4, 0xb4, 0x7d, 0x2c, 0xad, 0x41, 0x02, 0x71, 0x70, + 0x5b, 0x3e, 0x2d, 0x3e, 0x96, 0x27, 0x3b, 0x67, 0x61, 0xca, 0x03, 0x54, 0x7d, 0x62, 0x3c, 0x40, + 0xb5, 0x27, 0x42, 0xed, 0xee, 0xc6, 0x3c, 0x40, 0xf5, 0x9c, 0x91, 0x48, 0xf2, 0x9c, 0x83, 0x40, + 0x1b, 0xe4, 0x49, 0xe2, 0x29, 0x0a, 0x94, 0x65, 0x9e, 0x69, 0xab, 0x5b, 0xba, 0x6f, 0x19, 0x52, + 0xed, 0xc8, 0x91, 0xa3, 0x55, 0xe5, 0x6e, 0x13, 0x01, 0x0b, 0xfc, 0x16, 0x05, 0x76, 0x94, 0xaa, + 0xae, 0x98, 0x2b, 0x55, 0x1d, 0x59, 0x80, 0xb2, 0xb3, 0x4b, 0xf7, 0x8f, 0x77, 0xd8, 0x9f, 0xef, + 0xc2, 0x6e, 0xdf, 0xa2, 0xfb, 0xc8, 0x2b, 0x6b, 0x3f, 0x28, 0x02, 0xb0, 0xc7, 0x3f, 0x9a, 0x2f, + 0xe6, 0xb7, 0xa1, 0xea, 0xf7, 0xb8, 0xd5, 0x44, 0x2a, 0x4c, 0x51, 0xf8, 0x96, 0x28, 0x46, 0x45, + 0x27, 0xcf, 0x41, 0xe5, 0xbd, 0x1e, 0xed, 0xa9, 0xc0, 0x82, 0x50, 0x71, 0xff, 0x22, 0x2b, 0x44, + 0x41, 0x3b, 0x3d, 0xbb, 0xaa, 0xf2, 0xd9, 0x54, 0x4e, 0xcb, 0x67, 0x53, 0x87, 0xea, 0x6d, 0x97, + 0x47, 0x83, 0x6a, 0xff, 0xb5, 0x08, 0x10, 0x45, 0xdb, 0x91, 0xef, 0x14, 0xe0, 0x7c, 0xf8, 0xc1, + 0x05, 0x62, 0xff, 0xc5, 0xd3, 0x22, 0xe7, 0xf6, 0xdf, 0x64, 0x7d, 0xec, 0x7c, 0x06, 0x5a, 0xcf, + 0x12, 0x87, 0xd9, 0xad, 0x20, 0x08, 0x35, 0xda, 0xe9, 0x06, 0xfb, 0x8b, 0x96, 0x27, 0x47, 0x60, + 0x66, 0x50, 0xe7, 0x75, 0xc9, 0x23, 0xaa, 0x4a, 0x23, 0x01, 0xff, 0x88, 0x14, 0x05, 0x43, 0x1c, + 0xb2, 0x03, 0x35, 0xc7, 0x7d, 0xc7, 0x67, 0xdd, 0x21, 0x87, 0xe3, 0x1b, 0xc3, 0x77, 0xb9, 0xe8, + 0x56, 0x61, 0xef, 0x97, 0x37, 0x58, 0x75, 0x64, 0x67, 0x7f, 0xbb, 0x08, 0x67, 0x33, 0xfa, 0x81, + 0xbc, 0x01, 0x93, 0x32, 0xb0, 0x31, 0xca, 0x0f, 0x5e, 0x88, 0xf2, 0x83, 0xb7, 0x52, 0x34, 0xec, + 0xe3, 0x26, 0xef, 0x00, 0xe8, 0x86, 0x41, 0x7d, 0x7f, 0xcd, 0x35, 0xd5, 0x7e, 0xe0, 0x75, 0xa6, + 0xbe, 0xcc, 0x87, 0xa5, 0x0f, 0x0e, 0xa6, 0x7f, 0x2f, 0x2b, 0x56, 0x39, 0xd5, 0xcf, 0x51, 0x05, + 0x8c, 0x41, 0x92, 0xaf, 0x00, 0x88, 0x4d, 0x78, 0x98, 0x4e, 0xe1, 0x11, 0x96, 0xab, 0x19, 0x95, + 0xb8, 0x6a, 0xe6, 0x8b, 0x3d, 0xdd, 0x09, 0xac, 0x60, 0x5f, 0x64, 0xaf, 0xb9, 0x1b, 0xa2, 0x60, + 0x0c, 0x51, 0xfb, 0xa7, 0x45, 0xa8, 0x29, 0x9b, 0xf9, 0x63, 0x30, 0x94, 0xb6, 0x13, 0x86, 0xd2, + 0x13, 0x8a, 0x4e, 0xce, 0x32, 0x93, 0xba, 0x29, 0x33, 0xe9, 0x72, 0x7e, 0x51, 0x0f, 0x37, 0x92, + 0x7e, 0xbf, 0x08, 0xe3, 0x8a, 0x35, 0xaf, 0x89, 0xf4, 0xf3, 0x30, 0x21, 0xa2, 0x0a, 0xd6, 0xf4, + 0xfb, 0x22, 0x91, 0x0f, 0xef, 0xb0, 0xb2, 0x08, 0x08, 0x6e, 0x26, 0x49, 0x98, 0xe6, 0x65, 0xc3, + 0x5a, 0x14, 0x6d, 0xb2, 0x4d, 0x98, 0xf0, 0x43, 0x8a, 0xfd, 0x26, 0x1f, 0xd6, 0xcd, 0x14, 0x0d, + 0xfb, 0xb8, 0xd3, 0x36, 0xda, 0xf2, 0x29, 0xd8, 0x68, 0xff, 0x5d, 0x01, 0x46, 0xa3, 0xfe, 0x3a, + 0x75, 0x0b, 0xed, 0x76, 0xd2, 0x42, 0x3b, 0x9f, 0x7b, 0x38, 0x0c, 0xb0, 0xcf, 0xfe, 0xe5, 0x2a, + 0x24, 0x82, 0xe4, 0xc9, 0x16, 0x5c, 0xb2, 0x32, 0x43, 0xfd, 0x62, 0xb3, 0x4d, 0x78, 0xea, 0x7b, + 0x65, 0x20, 0x27, 0x3e, 0x04, 0x85, 0xf4, 0xa0, 0xb6, 0x47, 0xbd, 0xc0, 0x32, 0xa8, 0x7a, 0xbe, + 0xe5, 0xdc, 0x2a, 0x99, 0xb4, 0x42, 0x87, 0x7d, 0x7a, 0x57, 0x0a, 0xc0, 0x50, 0x14, 0xd9, 0x82, + 0x0a, 0x35, 0xdb, 0x54, 0xa5, 0x56, 0xca, 0x99, 0xb8, 0x34, 0xec, 0x4f, 0x76, 0xe7, 0xa3, 0x80, + 0x26, 0x3e, 0xd4, 0x6d, 0xe5, 0x65, 0x94, 0xe3, 0x70, 0x78, 0x05, 0x2b, 0xf4, 0x57, 0x46, 0x59, + 0x17, 0xc2, 0x22, 0x8c, 0xe4, 0x90, 0xdd, 0xd0, 0xdc, 0x59, 0x39, 0xa1, 0xc9, 0xe3, 0x21, 0xc6, + 0x4e, 0x1f, 0xea, 0xf7, 0xf4, 0x80, 0x7a, 0x1d, 0xdd, 0xdb, 0x95, 0xbb, 0x8d, 0xe1, 0x9f, 0xf0, + 0x4d, 0x85, 0x14, 0x3d, 0x61, 0x58, 0x84, 0x91, 0x1c, 0xe2, 0x42, 0x3d, 0x90, 0xea, 0xb3, 0xb2, + 0xe9, 0x0e, 0x2f, 0x54, 0x29, 0xe2, 0xbe, 0x0c, 0x96, 0x57, 0xb7, 0x18, 0xc9, 0x20, 0x7b, 0x89, + 0x2c, 0xd7, 0x22, 0xb7, 0x79, 0x33, 0x87, 0x6f, 0x40, 0x42, 0x45, 0xcb, 0x4d, 0x76, 0xb6, 0x6c, + 0xed, 0x7f, 0x56, 0xa2, 0x69, 0xf9, 0x71, 0xdb, 0x09, 0x5f, 0x4a, 0xda, 0x09, 0xaf, 0xa4, 0xed, + 0x84, 0x29, 0x67, 0xf5, 0xf1, 0xc3, 0x6b, 0x53, 0xe6, 0xb5, 0xf2, 0x29, 0x98, 0xd7, 0x5e, 0x84, + 0xc6, 0x1e, 0x9f, 0x09, 0x44, 0x9e, 0xa6, 0x0a, 0x5f, 0x46, 0xf8, 0xcc, 0x7e, 0x37, 0x2a, 0xc6, + 0x38, 0x0f, 0xab, 0x22, 0xff, 0xeb, 0x11, 0x26, 0xba, 0x95, 0x55, 0x5a, 0x51, 0x31, 0xc6, 0x79, + 0x78, 0x64, 0x9e, 0xe5, 0xec, 0x8a, 0x0a, 0x55, 0x5e, 0x41, 0x44, 0xe6, 0xa9, 0x42, 0x8c, 0xe8, + 0xe4, 0x1a, 0xd4, 0x7a, 0xe6, 0xb6, 0xe0, 0xad, 0x71, 0x5e, 0xae, 0x61, 0x6e, 0x2e, 0x2e, 0xc9, + 0xbc, 0x51, 0x8a, 0xca, 0x5a, 0xd2, 0xd1, 0xbb, 0x8a, 0xc0, 0xf7, 0x86, 0xb2, 0x25, 0x6b, 0x51, + 0x31, 0xc6, 0x79, 0xc8, 0x1f, 0xc0, 0xb8, 0x47, 0xcd, 0x9e, 0x41, 0xc3, 0x5a, 0xc0, 0x6b, 0x11, + 0xf1, 0x03, 0x93, 0x38, 0x05, 0x53, 0x9c, 0x03, 0x8c, 0x84, 0x8d, 0xa1, 0x8c, 0x84, 0x5f, 0x80, + 0x71, 0xd3, 0xd3, 0x2d, 0x87, 0x9a, 0x77, 0x1c, 0x1e, 0x91, 0x20, 0xe3, 0x03, 0x43, 0x0b, 0xf9, + 0x62, 0x82, 0x8a, 0x29, 0x6e, 0xed, 0xbf, 0x17, 0x80, 0xf4, 0x47, 0xc2, 0x93, 0x1d, 0x18, 0x71, + 0xb8, 0xf5, 0x2c, 0x77, 0x6a, 0xed, 0x98, 0x11, 0x4e, 0x4c, 0x6b, 0xb2, 0x40, 0xe2, 0x13, 0x07, + 0x6a, 0xf4, 0x7e, 0x40, 0x3d, 0x27, 0x3c, 0x19, 0x73, 0x32, 0x69, 0xbc, 0xc5, 0x6e, 0x42, 0x22, + 0x63, 0x28, 0x43, 0xfb, 0x79, 0x11, 0x1a, 0x31, 0xbe, 0x47, 0x6d, 0x4a, 0xf9, 0xe1, 0x7c, 0x61, + 0xb4, 0xda, 0xf4, 0x6c, 0xf9, 0x85, 0xc6, 0x0e, 0xe7, 0x4b, 0x12, 0xae, 0x62, 0x9c, 0x8f, 0xcc, + 0x01, 0x74, 0x74, 0x3f, 0xa0, 0x1e, 0x5f, 0xbd, 0x53, 0x47, 0xe2, 0xd7, 0x42, 0x0a, 0xc6, 0xb8, + 0xc8, 0x55, 0x99, 0x88, 0xbd, 0x9c, 0x4c, 0x61, 0x38, 0x20, 0xcb, 0x7a, 0xe5, 0x04, 0xb2, 0xac, + 0x93, 0x36, 0x4c, 0xaa, 0x56, 0x2b, 0xea, 0xf1, 0x12, 0xdc, 0x89, 0xfd, 0x4f, 0x0a, 0x02, 0xfb, + 0x40, 0xb5, 0x1f, 0x14, 0x60, 0x2c, 0x61, 0x32, 0x11, 0xc9, 0x07, 0xd5, 0x39, 0x8e, 0x44, 0xf2, + 0xc1, 0xd8, 0xf1, 0x8b, 0x17, 0x60, 0x44, 0x74, 0x50, 0x3a, 0x3c, 0x53, 0x74, 0x21, 0x4a, 0x2a, + 0x9b, 0x0b, 0xa5, 0x51, 0x36, 0x3d, 0x17, 0x4a, 0xab, 0x2d, 0x2a, 0xba, 0xf0, 0x75, 0x88, 0xd6, + 0xc9, 0x9e, 0x8e, 0xf9, 0x3a, 0x44, 0x39, 0x86, 0x1c, 0xda, 0x0f, 0x79, 0xbb, 0x03, 0x6f, 0x3f, + 0xdc, 0x0b, 0xb6, 0xa1, 0x2a, 0x43, 0xf2, 0xe4, 0xa7, 0xf1, 0x46, 0x0e, 0x3b, 0x0e, 0xc7, 0x91, + 0xc1, 0x67, 0xba, 0xb1, 0x7b, 0x67, 0x7b, 0x1b, 0x15, 0x3a, 0xb9, 0x0e, 0x75, 0xd7, 0x59, 0xd2, + 0x2d, 0xbb, 0xe7, 0xa9, 0x95, 0xe1, 0xb7, 0xd8, 0x5c, 0x77, 0x47, 0x15, 0x3e, 0x38, 0x98, 0xbe, + 0x10, 0xde, 0x24, 0x1a, 0x89, 0x51, 0x4d, 0xed, 0xcf, 0x17, 0xe0, 0x3c, 0xba, 0xb6, 0x6d, 0x39, + 0xed, 0xa4, 0xb3, 0x8c, 0xd8, 0x30, 0xde, 0xd1, 0xef, 0x6f, 0x3a, 0xfa, 0x9e, 0x6e, 0xd9, 0xfa, + 0x96, 0x4d, 0x1f, 0xb9, 0x97, 0xeb, 0x05, 0x96, 0x3d, 0x23, 0x7e, 0x4c, 0x37, 0xb3, 0xe2, 0x04, + 0x77, 0xbc, 0x56, 0xe0, 0x59, 0x4e, 0x5b, 0x4c, 0x7a, 0x6b, 0x09, 0x2c, 0x4c, 0x61, 0x6b, 0xbf, + 0x28, 0x01, 0x0f, 0x0b, 0x23, 0xaf, 0x42, 0xbd, 0x43, 0x8d, 0x1d, 0xdd, 0xb1, 0x7c, 0x95, 0xc6, + 0xf5, 0x22, 0x7b, 0xae, 0x35, 0x55, 0xf8, 0x80, 0xbd, 0x8a, 0xf9, 0xd6, 0x2a, 0x3f, 0x79, 0x11, + 0xf1, 0x12, 0x03, 0x46, 0xda, 0xbe, 0xaf, 0x77, 0xad, 0xdc, 0x51, 0x09, 0x22, 0x6d, 0xa6, 0x98, + 0x8e, 0xc4, 0x35, 0x4a, 0x68, 0x62, 0x40, 0xa5, 0x6b, 0xeb, 0x96, 0x93, 0xfb, 0x47, 0x4a, 0xec, + 0x09, 0xd6, 0x19, 0x92, 0x30, 0xae, 0xf1, 0x4b, 0x14, 0xd8, 0xa4, 0x07, 0x0d, 0xdf, 0xf0, 0xf4, + 0x8e, 0xbf, 0xa3, 0xcf, 0xbd, 0xfc, 0x4a, 0x6e, 0x75, 0x35, 0x12, 0x25, 0x56, 0xcf, 0x05, 0x9c, + 0x5f, 0x6b, 0xdd, 0x98, 0x9f, 0x7b, 0xf9, 0x15, 0x8c, 0xcb, 0x89, 0x8b, 0x7d, 0xf9, 0xc5, 0x39, + 0x39, 0x83, 0x9c, 0xb8, 0xd8, 0x97, 0x5f, 0x9c, 0xc3, 0xb8, 0x1c, 0xed, 0x7f, 0x15, 0xa0, 0x1e, + 0xf2, 0x92, 0x4d, 0x00, 0x36, 0x97, 0xc9, 0x44, 0x97, 0xc7, 0xfa, 0xe9, 0x04, 0xb7, 0x4f, 0x6c, + 0x86, 0x95, 0x31, 0x06, 0x94, 0x91, 0x09, 0xb4, 0x78, 0xd2, 0x99, 0x40, 0x67, 0xa1, 0xbe, 0xa3, + 0x3b, 0xa6, 0xbf, 0xa3, 0xef, 0x8a, 0x29, 0x3d, 0x96, 0x1b, 0xf7, 0x86, 0x22, 0x60, 0xc4, 0xa3, + 0xfd, 0xe3, 0x11, 0x10, 0xa1, 0x04, 0x6c, 0xd2, 0x31, 0x2d, 0x5f, 0xc4, 0xb2, 0x17, 0x78, 0xcd, + 0x70, 0xd2, 0x59, 0x94, 0xe5, 0x18, 0x72, 0x90, 0x8b, 0x50, 0xea, 0x58, 0x8e, 0xf4, 0x3d, 0x71, + 0xd3, 0xe3, 0x9a, 0xe5, 0x20, 0x2b, 0xe3, 0x24, 0xfd, 0xbe, 0x0c, 0x43, 0x14, 0x24, 0xfd, 0x3e, + 0xb2, 0x32, 0xf2, 0x79, 0x98, 0xb0, 0x5d, 0x77, 0x97, 0x4d, 0x1f, 0x2a, 0x5a, 0x51, 0xf8, 0x81, + 0xb9, 0x31, 0x60, 0x35, 0x49, 0xc2, 0x34, 0x2f, 0xd9, 0x84, 0xa7, 0xdf, 0xa7, 0x9e, 0x2b, 0xe7, + 0xcb, 0x96, 0x4d, 0x69, 0x57, 0xc1, 0x08, 0x65, 0x8e, 0x07, 0x3d, 0xfe, 0x71, 0x36, 0x0b, 0x0e, + 0xaa, 0xcb, 0xc3, 0xa7, 0x75, 0xaf, 0x4d, 0x83, 0x75, 0xcf, 0x35, 0xa8, 0xef, 0x5b, 0x4e, 0x5b, + 0xc1, 0x8e, 0x44, 0xb0, 0x1b, 0xd9, 0x2c, 0x38, 0xa8, 0x2e, 0x79, 0x0b, 0xa6, 0x04, 0x49, 0xa8, + 0x2d, 0xf3, 0x62, 0x9a, 0xb1, 0x6c, 0xf5, 0xff, 0xc1, 0x31, 0xe1, 0xe1, 0xd9, 0x18, 0xc0, 0x83, + 0x03, 0x6b, 0x93, 0x9b, 0x30, 0xa9, 0xfc, 0x7b, 0xeb, 0xd4, 0x6b, 0x85, 0xe1, 0x25, 0x63, 0xcd, + 0x2b, 0x6c, 0xe7, 0xbd, 0x48, 0xbb, 0x1e, 0x35, 0xe2, 0x7e, 0x52, 0xc5, 0x85, 0x7d, 0xf5, 0x08, + 0xc2, 0x05, 0x1e, 0x43, 0xb2, 0xd9, 0x5d, 0x70, 0x5d, 0xdb, 0x74, 0xef, 0x39, 0xea, 0xd9, 0x85, + 0x8a, 0xc9, 0x5d, 0x7a, 0xad, 0x4c, 0x0e, 0x1c, 0x50, 0x93, 0x3d, 0x39, 0xa7, 0x2c, 0xba, 0xf7, + 0x9c, 0x34, 0x2a, 0x44, 0x4f, 0xde, 0x1a, 0xc0, 0x83, 0x03, 0x6b, 0x93, 0x25, 0x20, 0xe9, 0x27, + 0xd8, 0xec, 0x4a, 0xa7, 0xf3, 0x05, 0x91, 0xb3, 0x26, 0x4d, 0xc5, 0x8c, 0x1a, 0x64, 0x15, 0xce, + 0xa5, 0x4b, 0x99, 0x38, 0xe9, 0x7f, 0xe6, 0xd9, 0x6a, 0x31, 0x83, 0x8e, 0x99, 0xb5, 0xb4, 0x7f, + 0x52, 0x84, 0xb1, 0x44, 0x92, 0x83, 0x27, 0xee, 0x30, 0x39, 0xdb, 0x0b, 0x74, 0xfc, 0xf6, 0xca, + 0xe2, 0x0d, 0xaa, 0x9b, 0xd4, 0xbb, 0x45, 0x55, 0x42, 0x0a, 0xb1, 0x2c, 0x26, 0x28, 0x98, 0xe2, + 0x24, 0xdb, 0x50, 0x11, 0x96, 0xed, 0xbc, 0xbf, 0x2f, 0x51, 0x7d, 0xc4, 0xcd, 0xdb, 0xf2, 0x9f, + 0x3f, 0xae, 0x47, 0x51, 0xc0, 0x6b, 0x01, 0x8c, 0xc6, 0x39, 0xd8, 0x44, 0x12, 0xa9, 0xbd, 0xd5, + 0x84, 0xca, 0xbb, 0x02, 0xa5, 0x20, 0x18, 0xf6, 0x98, 0xba, 0xf0, 0x94, 0x6c, 0xac, 0x22, 0xc3, + 0xd0, 0xb6, 0xd9, 0xbb, 0xf3, 0x7d, 0xcb, 0x75, 0x64, 0xce, 0xf2, 0x4d, 0xa8, 0x06, 0xd2, 0x58, + 0x38, 0xdc, 0x31, 0x7b, 0xae, 0x2b, 0x29, 0x43, 0xa1, 0xc2, 0xd2, 0xfe, 0x7d, 0x11, 0xea, 0xe1, + 0xc6, 0xfe, 0x08, 0xb9, 0xc0, 0x5d, 0xa8, 0x87, 0x31, 0x70, 0xb9, 0xff, 0xcd, 0x18, 0x85, 0x66, + 0xf1, 0xbd, 0x68, 0x78, 0x8b, 0x91, 0x8c, 0x78, 0x7c, 0x5d, 0x29, 0x47, 0x7c, 0x5d, 0x17, 0xaa, + 0x81, 0x67, 0xb5, 0xdb, 0x72, 0x97, 0x90, 0x27, 0xc0, 0x2e, 0xec, 0xae, 0x0d, 0x01, 0x28, 0x7b, + 0x56, 0xdc, 0xa0, 0x12, 0xa3, 0xbd, 0x0b, 0x93, 0x69, 0x4e, 0xae, 0x42, 0x1b, 0x3b, 0xd4, 0xec, + 0xd9, 0xaa, 0x8f, 0x23, 0x15, 0x5a, 0x96, 0x63, 0xc8, 0xc1, 0xb6, 0xe1, 0xec, 0x35, 0xbd, 0xef, + 0x3a, 0x4a, 0x8d, 0xe5, 0xbb, 0x91, 0x0d, 0x59, 0x86, 0x21, 0x55, 0xfb, 0x2f, 0x25, 0xb8, 0x18, + 0x99, 0x67, 0xd6, 0x74, 0x47, 0x6f, 0x1f, 0xe1, 0x87, 0x7c, 0x9f, 0x1d, 0x5c, 0x3a, 0xee, 0x0f, + 0x1d, 0x4a, 0x4f, 0xc0, 0x0f, 0x1d, 0xfe, 0x4f, 0x11, 0x78, 0xbc, 0x2e, 0xf9, 0x1a, 0x8c, 0xea, + 0xb1, 0x7f, 0xb1, 0xca, 0xd7, 0x79, 0x3d, 0xf7, 0xeb, 0xe4, 0x61, 0xc1, 0x61, 0xc8, 0x56, 0xbc, + 0x14, 0x13, 0x02, 0x89, 0x0b, 0xb5, 0x6d, 0xdd, 0xb6, 0x99, 0x2e, 0x94, 0xdb, 0xdd, 0x94, 0x10, + 0xce, 0x87, 0xf9, 0x92, 0x84, 0xc6, 0x50, 0x08, 0xf9, 0xa0, 0x00, 0x63, 0x5e, 0x7c, 0xbb, 0x26, + 0x5f, 0x48, 0x9e, 0x60, 0x84, 0x18, 0x5a, 0x3c, 0x40, 0x2c, 0xbe, 0x27, 0x4c, 0xca, 0xd4, 0xfe, + 0x73, 0x01, 0xc6, 0x5a, 0xb6, 0x65, 0x5a, 0x4e, 0xfb, 0x14, 0xff, 0x27, 0x71, 0x07, 0x2a, 0xbe, + 0x6d, 0x99, 0x74, 0xc8, 0xd5, 0x44, 0xac, 0x63, 0x0c, 0x00, 0x05, 0x4e, 0xf2, 0x07, 0x15, 0xa5, + 0x23, 0xfc, 0xa0, 0xe2, 0x57, 0x23, 0x20, 0x23, 0xcf, 0x49, 0x0f, 0xea, 0x6d, 0x95, 0xf7, 0x5e, + 0x3e, 0xe3, 0x8d, 0x1c, 0x39, 0x13, 0x13, 0x19, 0xf4, 0xc5, 0xdc, 0x1f, 0x16, 0x62, 0x24, 0x89, + 0xd0, 0xe4, 0x4f, 0x80, 0x17, 0x73, 0xfe, 0x04, 0x58, 0x88, 0xeb, 0xff, 0x0d, 0xb0, 0x0e, 0xe5, + 0x9d, 0x20, 0xe8, 0xca, 0xc1, 0x34, 0xfc, 0xd1, 0x82, 0x28, 0x6d, 0x8f, 0xd0, 0x89, 0xd8, 0x3d, + 0x72, 0x68, 0x26, 0xc2, 0xd1, 0xc3, 0x5f, 0xad, 0x2d, 0xe4, 0x0a, 0x7c, 0x88, 0x8b, 0x60, 0xf7, + 0xc8, 0xa1, 0xc9, 0x57, 0xa1, 0x11, 0x78, 0xba, 0xe3, 0x6f, 0xbb, 0x5e, 0x87, 0x7a, 0x72, 0x8f, + 0xba, 0x94, 0xe3, 0x3f, 0xb8, 0x1b, 0x11, 0x9a, 0xf0, 0xa8, 0x26, 0x8a, 0x30, 0x2e, 0x8d, 0xec, + 0x42, 0xad, 0x67, 0x8a, 0x86, 0x49, 0x33, 0xd8, 0x7c, 0x9e, 0x5f, 0x1b, 0xc7, 0xc2, 0x1a, 0xd4, + 0x1d, 0x86, 0x02, 0x92, 0x7f, 0x15, 0xac, 0x9e, 0xd4, 0x5f, 0x05, 0xe3, 0xa3, 0x31, 0x2b, 0xa7, + 0x08, 0xe9, 0x48, 0xbd, 0xd6, 0x69, 0xcb, 0xa8, 0xac, 0xa5, 0xdc, 0x2a, 0xa7, 0x10, 0xd9, 0x08, + 0x75, 0x63, 0xa7, 0x8d, 0x4a, 0x86, 0xd6, 0x01, 0xe9, 0xed, 0x20, 0x46, 0xe2, 0xdf, 0x3b, 0xe2, + 0xa0, 0xdb, 0xec, 0xd1, 0xe6, 0x83, 0xf0, 0x27, 0x30, 0xb1, 0xdc, 0xdf, 0x99, 0x3f, 0xd9, 0xd1, + 0xfe, 0x43, 0x11, 0x4a, 0x1b, 0xab, 0x2d, 0x91, 0xcf, 0x93, 0xff, 0xd8, 0x8a, 0xb6, 0x76, 0xad, + 0xee, 0x5d, 0xea, 0x59, 0xdb, 0xfb, 0x72, 0xeb, 0x1d, 0xcb, 0xe7, 0x99, 0xe6, 0xc0, 0x8c, 0x5a, + 0xe4, 0x6d, 0x18, 0x35, 0xf4, 0x05, 0xea, 0x05, 0xc3, 0x18, 0x16, 0xf8, 0x89, 0xde, 0x85, 0xf9, + 0xa8, 0x3a, 0x26, 0xc0, 0xc8, 0x26, 0x80, 0x11, 0x41, 0x97, 0x8e, 0x6d, 0x0e, 0x89, 0x01, 0xc7, + 0x80, 0x08, 0x42, 0x7d, 0x97, 0xb1, 0x72, 0xd4, 0xf2, 0x71, 0x50, 0xf9, 0xc8, 0xb9, 0xa5, 0xea, + 0x62, 0x04, 0xa3, 0x39, 0x30, 0x96, 0xf8, 0x21, 0x0f, 0xf9, 0x1c, 0xd4, 0xdc, 0x6e, 0x6c, 0x3a, + 0xad, 0xf3, 0xf8, 0xcf, 0xda, 0x1d, 0x59, 0xf6, 0xe0, 0x60, 0x7a, 0x6c, 0xd5, 0x6d, 0x5b, 0x86, + 0x2a, 0xc0, 0x90, 0x9d, 0x68, 0x30, 0xc2, 0x8f, 0xe1, 0xa9, 0xdf, 0xf1, 0xf0, 0xb5, 0x83, 0xff, + 0x31, 0xc3, 0x47, 0x49, 0xd1, 0xbe, 0x5e, 0x86, 0xc8, 0x47, 0x48, 0x7c, 0x18, 0x11, 0xc7, 0x0c, + 0xe4, 0xcc, 0x7d, 0xaa, 0x27, 0x1a, 0xa4, 0x28, 0xd2, 0x86, 0xd2, 0xbb, 0xee, 0x56, 0xee, 0x89, + 0x3b, 0x76, 0xfe, 0x5e, 0xd8, 0xca, 0x62, 0x05, 0xc8, 0x24, 0x90, 0xbf, 0x51, 0x80, 0x33, 0x7e, + 0x5a, 0xf5, 0x95, 0xc3, 0x01, 0xf3, 0xeb, 0xf8, 0x69, 0x65, 0x5a, 0x06, 0xea, 0x0e, 0x22, 0x63, + 0x7f, 0x5b, 0x58, 0xff, 0x0b, 0xe7, 0x9d, 0x1c, 0x4e, 0xcb, 0x39, 0x7f, 0x22, 0x99, 0xec, 0xff, + 0x64, 0x19, 0x4a, 0x51, 0xda, 0x37, 0x8b, 0xd0, 0x88, 0xcd, 0xd6, 0xb9, 0xff, 0xf2, 0x74, 0x3f, + 0xf5, 0x97, 0xa7, 0xf5, 0xe1, 0x7d, 0xd9, 0x51, 0xab, 0x4e, 0xfb, 0x47, 0x4f, 0xff, 0xbc, 0x08, + 0xa5, 0xcd, 0xc5, 0xa5, 0xe4, 0xa6, 0xb5, 0xf0, 0x18, 0x36, 0xad, 0x3b, 0x50, 0xdd, 0xea, 0x59, + 0x76, 0x60, 0x39, 0xb9, 0x33, 0x84, 0xa8, 0x9f, 0x62, 0x49, 0x5f, 0x87, 0x40, 0x45, 0x05, 0x4f, + 0xda, 0x50, 0x6d, 0x8b, 0x14, 0x8d, 0xb9, 0x23, 0xfc, 0x64, 0xaa, 0x47, 0x21, 0x48, 0xde, 0xa0, + 0x42, 0xd7, 0xf6, 0x61, 0x64, 0x73, 0x51, 0xaa, 0xfd, 0x8f, 0xb7, 0x37, 0xb5, 0xaf, 0x42, 0xa8, + 0x05, 0x3c, 0x7e, 0xe1, 0xff, 0xad, 0x00, 0x49, 0xc5, 0xe7, 0xf1, 0x8f, 0xa6, 0xdd, 0xf4, 0x68, + 0x5a, 0x3c, 0x89, 0x8f, 0x2f, 0x7b, 0x40, 0x69, 0xff, 0xb6, 0x00, 0xa9, 0xb3, 0x61, 0xe4, 0x15, + 0x99, 0xed, 0x2b, 0x19, 0x4a, 0xa5, 0xb2, 0x7d, 0x91, 0x24, 0x77, 0x2c, 0xeb, 0xd7, 0x87, 0x6c, + 0xbb, 0x16, 0x77, 0xa0, 0xc9, 0xe6, 0xdf, 0x1e, 0x7e, 0xbb, 0x96, 0xe5, 0x8e, 0x93, 0xe1, 0x7e, + 0x71, 0x12, 0x26, 0xe5, 0x6a, 0xff, 0xa8, 0x08, 0x23, 0x8f, 0xed, 0xa8, 0x3a, 0x4d, 0x44, 0x60, + 0x2e, 0xe4, 0x9c, 0xed, 0x07, 0xc6, 0x5f, 0x76, 0x52, 0xf1, 0x97, 0x79, 0xff, 0x4d, 0xfc, 0x88, + 0xe8, 0xcb, 0x7f, 0x5d, 0x00, 0xb9, 0xd6, 0xac, 0x38, 0x7e, 0xa0, 0x3b, 0x06, 0x25, 0x46, 0xb8, + 0xb0, 0xe5, 0x0d, 0xf3, 0x91, 0xa1, 0x70, 0x42, 0x97, 0xe1, 0xd7, 0x6a, 0x21, 0x23, 0xbf, 0x0b, + 0xb5, 0x1d, 0xd7, 0x0f, 0xf8, 0xe2, 0x55, 0x4c, 0x9a, 0xcc, 0x6e, 0xc8, 0x72, 0x0c, 0x39, 0xd2, + 0xee, 0xec, 0xca, 0x60, 0x77, 0xb6, 0xf6, 0xbd, 0x22, 0x8c, 0x7e, 0x5a, 0xce, 0xdb, 0x67, 0xc5, + 0xab, 0x96, 0x72, 0xc6, 0xab, 0x96, 0x8f, 0x13, 0xaf, 0xaa, 0xfd, 0xa4, 0x00, 0xf0, 0xd8, 0x0e, + 0xfb, 0x9b, 0xc9, 0x50, 0xd2, 0xdc, 0xe3, 0x2a, 0x3b, 0x90, 0xf4, 0x1f, 0x54, 0xd4, 0x23, 0xf1, + 0x30, 0xd2, 0x0f, 0x0b, 0x30, 0xae, 0x27, 0x42, 0x33, 0x73, 0xeb, 0xcb, 0xa9, 0x48, 0xcf, 0x30, + 0xb2, 0x28, 0x59, 0x8e, 0x29, 0xb1, 0xe4, 0xb5, 0x28, 0xd1, 0xf4, 0xed, 0x68, 0xd8, 0xf7, 0x65, + 0x88, 0xe6, 0xba, 0x5b, 0x82, 0xf3, 0x11, 0xa1, 0xb0, 0xa5, 0x13, 0x09, 0x85, 0x8d, 0x1f, 0xf2, + 0x2b, 0x3f, 0xf4, 0x90, 0xdf, 0x1e, 0xd4, 0xb7, 0x3d, 0xb7, 0xc3, 0xa3, 0x4d, 0xe5, 0x5f, 0x8d, + 0xaf, 0xe7, 0x58, 0x28, 0xa3, 0xff, 0xf9, 0x47, 0x86, 0xab, 0x25, 0x85, 0x8f, 0x91, 0x28, 0x6e, + 0xeb, 0x77, 0x85, 0xd4, 0x91, 0x93, 0x94, 0x1a, 0xce, 0x25, 0x1b, 0x02, 0x1d, 0x95, 0x98, 0x64, + 0x84, 0x69, 0xf5, 0xf1, 0x44, 0x98, 0x6a, 0x7f, 0xa9, 0xaa, 0x26, 0xb0, 0x27, 0x2e, 0xa7, 0xe9, + 0x67, 0x47, 0xb3, 0xdb, 0xb4, 0xef, 0xdc, 0x74, 0xed, 0x31, 0x9e, 0x9b, 0xae, 0x9f, 0xcc, 0xb9, + 0x69, 0xc8, 0x77, 0x6e, 0xba, 0x71, 0x42, 0xe7, 0xa6, 0x47, 0x4f, 0xea, 0xdc, 0xf4, 0xd8, 0x50, + 0xe7, 0xa6, 0xc7, 0x8f, 0x74, 0x6e, 0xfa, 0xa0, 0x04, 0xa9, 0xcd, 0xf8, 0x67, 0x8e, 0xb7, 0xff, + 0xa7, 0x1c, 0x6f, 0x1f, 0x15, 0x21, 0x9a, 0x88, 0x8f, 0x19, 0x98, 0xf4, 0x16, 0xd4, 0x3a, 0xfa, + 0xfd, 0x45, 0x6a, 0xeb, 0xfb, 0x79, 0x7e, 0x05, 0xbc, 0x26, 0x31, 0x30, 0x44, 0x23, 0x3e, 0x80, + 0x15, 0xa6, 0xe3, 0xcf, 0xed, 0xc2, 0x88, 0x32, 0xfb, 0x0b, 0x23, 0x69, 0x74, 0x8f, 0x31, 0x31, + 0xda, 0xbf, 0x2a, 0x82, 0xfc, 0x6f, 0x03, 0xa1, 0x50, 0xd9, 0xb6, 0xee, 0x53, 0x33, 0x77, 0xb8, + 0x73, 0xec, 0x07, 0xed, 0xc2, 0x47, 0xc3, 0x0b, 0x50, 0xa0, 0x73, 0xe3, 0xbb, 0xf0, 0xb9, 0xc9, + 0xfe, 0xcb, 0x61, 0x7c, 0x8f, 0xfb, 0xee, 0xa4, 0xf1, 0x5d, 0x14, 0xa1, 0x92, 0x21, 0x6c, 0xfd, + 0x3c, 0xfc, 0x22, 0xb7, 0x8b, 0x31, 0x11, 0xc6, 0xa1, 0x6c, 0xfd, 0xbe, 0x48, 0x9c, 0x20, 0x65, + 0x34, 0xbf, 0xfc, 0xe3, 0x9f, 0x5e, 0x79, 0xea, 0x27, 0x3f, 0xbd, 0xf2, 0xd4, 0xc7, 0x3f, 0xbd, + 0xf2, 0xd4, 0xd7, 0x0f, 0xaf, 0x14, 0x7e, 0x7c, 0x78, 0xa5, 0xf0, 0x93, 0xc3, 0x2b, 0x85, 0x8f, + 0x0f, 0xaf, 0x14, 0xfe, 0xe3, 0xe1, 0x95, 0xc2, 0x5f, 0xfd, 0x4f, 0x57, 0x9e, 0xfa, 0xe3, 0x57, + 0xa3, 0x26, 0xcc, 0xaa, 0x26, 0xcc, 0x2a, 0x81, 0xb3, 0xdd, 0xdd, 0xf6, 0x2c, 0x6b, 0x42, 0x54, + 0xa2, 0x9a, 0xf0, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xc4, 0xe2, 0xd0, 0x0e, 0xda, 0x9a, 0x00, + 0x00, } func (m *AbstractPodTemplate) Marshal() (dAtA []byte, err error) { @@ -3585,6 +3584,18 @@ func (m *AbstractVertex) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.UpdateStrategy.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x82 if m.SideInputsContainerTemplate != nil { { size, err := m.SideInputsContainerTemplate.MarshalToSizedBuffer(dAtA[:i]) @@ -9142,24 +9153,24 @@ func (m *VertexStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.UpdateHash) i = encodeVarintGenerated(dAtA, i, uint64(len(m.UpdateHash))) i-- - dAtA[i] = 0x6a + dAtA[i] = 0x72 i -= len(m.CurrentHash) copy(dAtA[i:], m.CurrentHash) i = encodeVarintGenerated(dAtA, i, uint64(len(m.CurrentHash))) i-- - dAtA[i] = 0x62 + dAtA[i] = 0x6a + i = encodeVarintGenerated(dAtA, i, uint64(m.UpdatedReadyReplicas)) + i-- + dAtA[i] = 0x60 i = encodeVarintGenerated(dAtA, i, uint64(m.UpdatedReplicas)) i-- dAtA[i] = 0x58 - i = encodeVarintGenerated(dAtA, i, uint64(m.CurrentReplicas)) - i-- - dAtA[i] = 0x50 i = encodeVarintGenerated(dAtA, i, uint64(m.ReadyReplicas)) i-- - dAtA[i] = 0x48 + dAtA[i] = 0x50 i = encodeVarintGenerated(dAtA, i, uint64(m.ObservedGeneration)) i-- - dAtA[i] = 0x40 + dAtA[i] = 0x48 { size, err := m.LastScaledAt.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -9169,22 +9180,25 @@ func (m *VertexStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x3a + dAtA[i] = 0x42 i -= len(m.Message) copy(dAtA[i:], m.Message) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) i-- - dAtA[i] = 0x32 + dAtA[i] = 0x3a i -= len(m.Reason) copy(dAtA[i:], m.Reason) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Reason))) i-- - dAtA[i] = 0x2a + dAtA[i] = 0x32 i -= len(m.Selector) copy(dAtA[i:], m.Selector) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Selector))) i-- - dAtA[i] = 0x22 + dAtA[i] = 0x2a + i = encodeVarintGenerated(dAtA, i, uint64(m.DesiredReplicas)) + i-- + dAtA[i] = 0x20 i = encodeVarintGenerated(dAtA, i, uint64(m.Replicas)) i-- dAtA[i] = 0x18 @@ -9547,6 +9561,8 @@ func (m *AbstractVertex) Size() (n int) { l = m.SideInputsContainerTemplate.Size() n += 1 + l + sovGenerated(uint64(l)) } + l = m.UpdateStrategy.Size() + n += 2 + l + sovGenerated(uint64(l)) return n } @@ -11540,6 +11556,7 @@ func (m *VertexStatus) Size() (n int) { l = len(m.Phase) n += 1 + l + sovGenerated(uint64(l)) n += 1 + sovGenerated(uint64(m.Replicas)) + n += 1 + sovGenerated(uint64(m.DesiredReplicas)) l = len(m.Selector) n += 1 + l + sovGenerated(uint64(l)) l = len(m.Reason) @@ -11550,8 +11567,8 @@ func (m *VertexStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) n += 1 + sovGenerated(uint64(m.ObservedGeneration)) n += 1 + sovGenerated(uint64(m.ReadyReplicas)) - n += 1 + sovGenerated(uint64(m.CurrentReplicas)) n += 1 + sovGenerated(uint64(m.UpdatedReplicas)) + n += 1 + sovGenerated(uint64(m.UpdatedReadyReplicas)) l = len(m.CurrentHash) n += 1 + l + sovGenerated(uint64(l)) l = len(m.UpdateHash) @@ -11719,6 +11736,7 @@ func (this *AbstractVertex) String() string { `Partitions:` + valueToStringGenerated(this.Partitions) + `,`, `SideInputs:` + fmt.Sprintf("%v", this.SideInputs) + `,`, `SideInputsContainerTemplate:` + strings.Replace(this.SideInputsContainerTemplate.String(), "ContainerTemplate", "ContainerTemplate", 1) + `,`, + `UpdateStrategy:` + strings.Replace(strings.Replace(this.UpdateStrategy.String(), "UpdateStrategy", "UpdateStrategy", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -13099,14 +13117,15 @@ func (this *VertexStatus) String() string { `Status:` + strings.Replace(strings.Replace(this.Status.String(), "Status", "Status", 1), `&`, ``, 1) + `,`, `Phase:` + fmt.Sprintf("%v", this.Phase) + `,`, `Replicas:` + fmt.Sprintf("%v", this.Replicas) + `,`, + `DesiredReplicas:` + fmt.Sprintf("%v", this.DesiredReplicas) + `,`, `Selector:` + fmt.Sprintf("%v", this.Selector) + `,`, `Reason:` + fmt.Sprintf("%v", this.Reason) + `,`, `Message:` + fmt.Sprintf("%v", this.Message) + `,`, `LastScaledAt:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.LastScaledAt), "Time", "v11.Time", 1), `&`, ``, 1) + `,`, `ObservedGeneration:` + fmt.Sprintf("%v", this.ObservedGeneration) + `,`, `ReadyReplicas:` + fmt.Sprintf("%v", this.ReadyReplicas) + `,`, - `CurrentReplicas:` + fmt.Sprintf("%v", this.CurrentReplicas) + `,`, `UpdatedReplicas:` + fmt.Sprintf("%v", this.UpdatedReplicas) + `,`, + `UpdatedReadyReplicas:` + fmt.Sprintf("%v", this.UpdatedReadyReplicas) + `,`, `CurrentHash:` + fmt.Sprintf("%v", this.CurrentHash) + `,`, `UpdateHash:` + fmt.Sprintf("%v", this.UpdateHash) + `,`, `}`, @@ -14477,6 +14496,39 @@ func (m *AbstractVertex) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdateStrategy", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.UpdateStrategy.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -31445,6 +31497,25 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { } } case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DesiredReplicas", wireType) + } + m.DesiredReplicas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DesiredReplicas |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Selector", wireType) } @@ -31476,7 +31547,7 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { } m.Selector = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 5: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) } @@ -31508,7 +31579,7 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { } m.Reason = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 6: + case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) } @@ -31540,7 +31611,7 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { } m.Message = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 7: + case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastScaledAt", wireType) } @@ -31573,7 +31644,7 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 8: + case 9: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ObservedGeneration", wireType) } @@ -31592,7 +31663,7 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { break } } - case 9: + case 10: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ReadyReplicas", wireType) } @@ -31611,11 +31682,11 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { break } } - case 10: + case 11: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CurrentReplicas", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedReplicas", wireType) } - m.CurrentReplicas = 0 + m.UpdatedReplicas = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -31625,16 +31696,16 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.CurrentReplicas |= uint32(b&0x7F) << shift + m.UpdatedReplicas |= uint32(b&0x7F) << shift if b < 0x80 { break } } - case 11: + case 12: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field UpdatedReplicas", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedReadyReplicas", wireType) } - m.UpdatedReplicas = 0 + m.UpdatedReadyReplicas = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -31644,12 +31715,12 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.UpdatedReplicas |= uint32(b&0x7F) << shift + m.UpdatedReadyReplicas |= uint32(b&0x7F) << shift if b < 0x80 { break } } - case 12: + case 13: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field CurrentHash", wireType) } @@ -31681,7 +31752,7 @@ func (m *VertexStatus) Unmarshal(dAtA []byte) error { } m.CurrentHash = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 13: + case 14: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field UpdateHash", wireType) } diff --git a/pkg/apis/numaflow/v1alpha1/generated.proto b/pkg/apis/numaflow/v1alpha1/generated.proto index 78e10457a0..c927033e7e 100644 --- a/pkg/apis/numaflow/v1alpha1/generated.proto +++ b/pkg/apis/numaflow/v1alpha1/generated.proto @@ -201,6 +201,11 @@ message AbstractVertex { // Container template for the side inputs watcher container. // +optional optional ContainerTemplate sideInputsContainerTemplate = 15; + + // The strategy to use to replace existing pods with new ones. + // +kubebuilder:default={"type": "RollingUpdate", "rollingUpdate": {"maxUnavailable": "25%"}} + // +optional + optional UpdateStrategy updateStrategy = 16; } message Authorization { @@ -1642,7 +1647,7 @@ message UpdateStrategy { // +kubebuilder:subresource:status // +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector // +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` -// +kubebuilder:printcolumn:name="Desired",type=string,JSONPath=`.spec.replicas` +// +kubebuilder:printcolumn:name="Desired",type=string,JSONPath=`.status.desiredReplicas` // +kubebuilder:printcolumn:name="Current",type=string,JSONPath=`.status.replicas` // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.readyReplicas` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` @@ -1732,38 +1737,42 @@ message VertexStatus { // +optional optional uint32 replicas = 3; + // The number of desired replicas. + // +optional + optional uint32 desiredReplicas = 4; + // +optional - optional string selector = 4; + optional string selector = 5; // +optional - optional string reason = 5; + optional string reason = 6; // +optional - optional string message = 6; + optional string message = 7; // Time of last scaling operation. // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastScaledAt = 7; + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastScaledAt = 8; // The generation observed by the Vertex controller. // +optional - optional int64 observedGeneration = 8; + optional int64 observedGeneration = 9; // The number of pods targeted by this Vertex with a Ready Condition. // +optional - optional uint32 readyReplicas = 9; - - // The number of Pods created by the controller from the Vertex version indicated by currentHash. - optional uint32 currentReplicas = 10; + optional uint32 readyReplicas = 10; // The number of Pods created by the controller from the Vertex version indicated by updateHash. optional uint32 updatedReplicas = 11; - // If not empty, indicates the version of the Vertex used to generate Pods in the sequence [0,currentReplicas). - optional string currentHash = 12; + // The number of ready Pods created by the controller from the Vertex version indicated by updateHash. + optional uint32 updatedReadyReplicas = 12; + + // If not empty, indicates the current version of the Vertex used to generate Pods. + optional string currentHash = 13; - // If not empty, indicates the version of the Vertx used to generate Pods in the sequence [replicas-updatedReplicas,replicas) - optional string updateHash = 13; + // If not empty, indicates the updated version of the Vertex used to generate Pods. + optional string updateHash = 14; } message VertexTemplate { diff --git a/pkg/apis/numaflow/v1alpha1/mono_vertex_types_test.go b/pkg/apis/numaflow/v1alpha1/mono_vertex_types_test.go index aeb809db05..665acd8b32 100644 --- a/pkg/apis/numaflow/v1alpha1/mono_vertex_types_test.go +++ b/pkg/apis/numaflow/v1alpha1/mono_vertex_types_test.go @@ -18,6 +18,7 @@ package v1alpha1 import ( "testing" + "time" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" @@ -88,6 +89,31 @@ func TestMonoVertex_MarkPhaseRunning(t *testing.T) { } } +func TestMonoVertex_MarkDaemonUnHealthy(t *testing.T) { + mvs := MonoVertexStatus{} + mvs.MarkDaemonUnHealthy("reason", "message") + + for _, condition := range mvs.Conditions { + if condition.Type == string(MonoVertexConditionDaemonHealthy) { + if condition.Status != metav1.ConditionFalse { + t.Errorf("MarkDaemonUnHealthy should set the DaemonHealthy condition to false, got %v", condition.Status) + } + if condition.Reason != "reason" { + t.Errorf("MarkDaemonUnHealthy should set the Reason to 'reason', got %s", condition.Reason) + } + if condition.Message != "message" { + t.Errorf("MarkDaemonUnHealthy should set the Message to 'message', got %s", condition.Message) + } + } + } +} + +func TestMonoVertex_SetObservedGeneration(t *testing.T) { + mvs := MonoVertexStatus{} + mvs.SetObservedGeneration(1) + assert.Equal(t, int64(1), mvs.ObservedGeneration) +} + func TestMonoVertex_IsHealthy(t *testing.T) { mvs := MonoVertexStatus{} @@ -171,3 +197,346 @@ func TestMonoVertexGetPodSpec(t *testing.T) { assert.Contains(t, envNames, EnvMonoVertexObject) }) } + +func TestMonoVertexLimits_GetReadBatchSize(t *testing.T) { + t.Run("default value", func(t *testing.T) { + mvl := MonoVertexLimits{} + assert.Equal(t, uint64(DefaultReadBatchSize), mvl.GetReadBatchSize()) + }) + + t.Run("custom value", func(t *testing.T) { + customSize := uint64(1000) + mvl := MonoVertexLimits{ReadBatchSize: &customSize} + assert.Equal(t, customSize, mvl.GetReadBatchSize()) + }) + +} + +func TestMonoVertexLimits_GetReadTimeout(t *testing.T) { + t.Run("default value", func(t *testing.T) { + mvl := MonoVertexLimits{} + assert.Equal(t, DefaultReadTimeout, mvl.GetReadTimeout()) + }) + + t.Run("custom value", func(t *testing.T) { + customTimeout := metav1.Duration{Duration: 5 * time.Second} + mvl := MonoVertexLimits{ReadTimeout: &customTimeout} + assert.Equal(t, 5*time.Second, mvl.GetReadTimeout()) + }) +} + +func TestMonoVertex_GetDaemonDeploymentName(t *testing.T) { + mv := MonoVertex{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vertex", + }, + } + expected := "test-vertex-mv-daemon" + assert.Equal(t, expected, mv.GetDaemonDeploymentName()) +} + +func TestMonoVertex_GetDaemonServiceURL(t *testing.T) { + mv := MonoVertex{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vertex", + Namespace: "test-namespace", + }, + } + expected := "test-vertex-mv-daemon-svc.test-namespace.svc:4327" + assert.Equal(t, expected, mv.GetDaemonServiceURL()) +} + +func TestMonoVertex_Scalable(t *testing.T) { + t.Run("scalable when not disabled", func(t *testing.T) { + mv := MonoVertex{ + Spec: MonoVertexSpec{ + Scale: Scale{ + Disabled: false, + }, + }, + } + assert.True(t, mv.Scalable()) + }) + + t.Run("not scalable when disabled", func(t *testing.T) { + mv := MonoVertex{ + Spec: MonoVertexSpec{ + Scale: Scale{ + Disabled: true, + }, + }, + } + assert.False(t, mv.Scalable()) + }) +} + +func TestMonoVertex_GetReplicas(t *testing.T) { + t.Run("default replicas", func(t *testing.T) { + mv := MonoVertex{} + assert.Equal(t, 1, mv.getReplicas()) + }) + + t.Run("custom replicas", func(t *testing.T) { + replicas := int32(3) + mv := MonoVertex{ + Spec: MonoVertexSpec{ + Replicas: &replicas, + }, + } + assert.Equal(t, 3, mv.getReplicas()) + }) +} + +func TestMonoVertex_CalculateReplicas(t *testing.T) { + t.Run("auto scaling disabled", func(t *testing.T) { + replicas := int32(5) + mv := MonoVertex{ + Spec: MonoVertexSpec{ + Replicas: &replicas, + Scale: Scale{ + Disabled: true, + }, + }, + } + assert.Equal(t, 5, mv.CalculateReplicas()) + }) + + t.Run("auto scaling enabled, within range", func(t *testing.T) { + replicas := int32(3) + mv := MonoVertex{ + Spec: MonoVertexSpec{ + Replicas: &replicas, + Scale: Scale{ + Disabled: false, + Min: ptr.To[int32](1), + Max: ptr.To[int32](5), + }, + }, + } + assert.Equal(t, 3, mv.CalculateReplicas()) + }) + + t.Run("auto scaling enabled, below min", func(t *testing.T) { + replicas := int32(0) + mv := MonoVertex{ + Spec: MonoVertexSpec{ + Replicas: &replicas, + Scale: Scale{ + Disabled: false, + Min: ptr.To[int32](2), + Max: ptr.To[int32](5), + }, + }, + } + assert.Equal(t, 2, mv.CalculateReplicas()) + }) + + t.Run("auto scaling enabled, above max", func(t *testing.T) { + replicas := int32(10) + mv := MonoVertex{ + Spec: MonoVertexSpec{ + Replicas: &replicas, + Scale: Scale{ + Disabled: false, + Min: ptr.To[int32](2), + Max: ptr.To[int32](5), + }, + }, + } + assert.Equal(t, 5, mv.CalculateReplicas()) + }) +} + +func TestMonoVertex_GetServiceObj(t *testing.T) { + mv := MonoVertex{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vertex", + Namespace: "test-namespace", + }, + } + + t.Run("non-headless service", func(t *testing.T) { + svc := mv.getServiceObj("test-service", false, 8080, "http") + assert.Equal(t, "test-service", svc.Name) + assert.Equal(t, "test-namespace", svc.Namespace) + assert.Equal(t, 1, len(svc.Spec.Ports)) + assert.Equal(t, int32(8080), svc.Spec.Ports[0].Port) + assert.Equal(t, "http", svc.Spec.Ports[0].Name) + assert.NotEqual(t, "None", svc.Spec.ClusterIP) + }) + + t.Run("headless service", func(t *testing.T) { + svc := mv.getServiceObj("test-headless-service", true, 9090, "grpc") + assert.Equal(t, "test-headless-service", svc.Name) + assert.Equal(t, "test-namespace", svc.Namespace) + assert.Equal(t, 1, len(svc.Spec.Ports)) + assert.Equal(t, int32(9090), svc.Spec.Ports[0].Port) + assert.Equal(t, "grpc", svc.Spec.Ports[0].Name) + assert.Equal(t, "None", svc.Spec.ClusterIP) + }) + + t.Run("verify labels", func(t *testing.T) { + svc := mv.getServiceObj("test-label-service", false, 7070, "metrics") + expectedLabels := map[string]string{ + KeyPartOf: Project, + KeyManagedBy: ControllerMonoVertex, + KeyComponent: ComponentMonoVertex, + KeyMonoVertexName: "test-vertex", + } + assert.Equal(t, expectedLabels, svc.Labels) + }) + + t.Run("verify selector", func(t *testing.T) { + svc := mv.getServiceObj("test-selector-service", false, 6060, "admin") + expectedSelector := map[string]string{ + KeyPartOf: Project, + KeyManagedBy: ControllerMonoVertex, + KeyComponent: ComponentMonoVertex, + KeyMonoVertexName: "test-vertex", + } + assert.Equal(t, expectedSelector, svc.Spec.Selector) + }) +} + +func TestMonoVertex_GetServiceObjs(t *testing.T) { + mv := MonoVertex{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vertex", + Namespace: "test-namespace", + }, + } + + t.Run("verify service objects", func(t *testing.T) { + services := mv.GetServiceObjs() + assert.Equal(t, 1, len(services), "Expected 1 service object") + + headlessService := services[0] + assert.Equal(t, mv.GetHeadlessServiceName(), headlessService.Name) + assert.Equal(t, "test-namespace", headlessService.Namespace) + assert.Equal(t, "None", headlessService.Spec.ClusterIP) + assert.Equal(t, 1, len(headlessService.Spec.Ports)) + assert.Equal(t, int32(MonoVertexMetricsPort), headlessService.Spec.Ports[0].Port) + assert.Equal(t, MonoVertexMetricsPortName, headlessService.Spec.Ports[0].Name) + }) +} + +func TestMonoVertex_GetDaemonDeploymentObj(t *testing.T) { + mv := MonoVertex{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vertex", + Namespace: "test-namespace", + }, + Spec: MonoVertexSpec{}, + } + + t.Run("basic deployment object", func(t *testing.T) { + req := GetMonoVertexDaemonDeploymentReq{ + Image: "test-image:latest", + PullPolicy: corev1.PullAlways, + DefaultResources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + } + + deployment, err := mv.GetDaemonDeploymentObj(req) + assert.NoError(t, err) + assert.NotNil(t, deployment) + assert.Equal(t, mv.GetDaemonDeploymentName(), deployment.Name) + assert.Equal(t, mv.Namespace, deployment.Namespace) + assert.Equal(t, "test-image:latest", deployment.Spec.Template.Spec.Containers[0].Image) + assert.Equal(t, corev1.PullAlways, deployment.Spec.Template.Spec.Containers[0].ImagePullPolicy) + assert.Equal(t, resource.MustParse("100m"), deployment.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU]) + assert.Equal(t, resource.MustParse("128Mi"), deployment.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory]) + }) + + t.Run("with custom environment variables", func(t *testing.T) { + req := GetMonoVertexDaemonDeploymentReq{ + Image: "test-image:v1", + Env: []corev1.EnvVar{ + {Name: "CUSTOM_ENV", Value: "custom_value"}, + }, + } + + deployment, err := mv.GetDaemonDeploymentObj(req) + assert.NoError(t, err) + assert.NotNil(t, deployment) + + envVars := deployment.Spec.Template.Spec.Containers[0].Env + assert.Contains(t, envVars, corev1.EnvVar{Name: "CUSTOM_ENV", Value: "custom_value"}) + }) + + t.Run("with daemon template", func(t *testing.T) { + mv.Spec.DaemonTemplate = &DaemonTemplate{ + Replicas: ptr.To[int32](3), + AbstractPodTemplate: AbstractPodTemplate{ + NodeSelector: map[string]string{"node": "special"}, + }, + ContainerTemplate: &ContainerTemplate{ + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("200m"), + }, + }, + }, + } + + req := GetMonoVertexDaemonDeploymentReq{ + Image: "test-image:v2", + } + + deployment, err := mv.GetDaemonDeploymentObj(req) + assert.NoError(t, err) + assert.NotNil(t, deployment) + assert.Equal(t, int32(3), *deployment.Spec.Replicas) + assert.Equal(t, "special", deployment.Spec.Template.Spec.NodeSelector["node"]) + assert.Equal(t, resource.MustParse("200m"), deployment.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU]) + }) + + t.Run("verify probes", func(t *testing.T) { + req := GetMonoVertexDaemonDeploymentReq{ + Image: "test-image:v3", + } + + deployment, err := mv.GetDaemonDeploymentObj(req) + assert.NoError(t, err) + assert.NotNil(t, deployment) + + container := deployment.Spec.Template.Spec.Containers[0] + assert.NotNil(t, container.ReadinessProbe) + assert.NotNil(t, container.LivenessProbe) + + assert.Equal(t, int32(MonoVertexDaemonServicePort), container.ReadinessProbe.HTTPGet.Port.IntVal) + assert.Equal(t, "/readyz", container.ReadinessProbe.HTTPGet.Path) + assert.Equal(t, corev1.URISchemeHTTPS, container.ReadinessProbe.HTTPGet.Scheme) + + assert.Equal(t, int32(MonoVertexDaemonServicePort), container.LivenessProbe.HTTPGet.Port.IntVal) + assert.Equal(t, "/livez", container.LivenessProbe.HTTPGet.Path) + assert.Equal(t, corev1.URISchemeHTTPS, container.LivenessProbe.HTTPGet.Scheme) + }) + + t.Run("verify labels and owner references", func(t *testing.T) { + req := GetMonoVertexDaemonDeploymentReq{ + Image: "test-image:v4", + } + + deployment, err := mv.GetDaemonDeploymentObj(req) + assert.NoError(t, err) + assert.NotNil(t, deployment) + + expectedLabels := map[string]string{ + KeyPartOf: Project, + KeyManagedBy: ControllerMonoVertex, + KeyComponent: ComponentMonoVertexDaemon, + KeyAppName: mv.GetDaemonDeploymentName(), + KeyMonoVertexName: mv.Name, + } + assert.Equal(t, expectedLabels, deployment.Labels) + + assert.Len(t, deployment.OwnerReferences, 1) + assert.Equal(t, mv.Name, deployment.OwnerReferences[0].Name) + assert.Equal(t, MonoVertexGroupVersionKind.Kind, deployment.OwnerReferences[0].Kind) + }) +} diff --git a/pkg/apis/numaflow/v1alpha1/openapi_generated.go b/pkg/apis/numaflow/v1alpha1/openapi_generated.go index 765dd96681..50a03d54a3 100644 --- a/pkg/apis/numaflow/v1alpha1/openapi_generated.go +++ b/pkg/apis/numaflow/v1alpha1/openapi_generated.go @@ -583,12 +583,19 @@ func schema_pkg_apis_numaflow_v1alpha1_AbstractVertex(ref common.ReferenceCallba Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ContainerTemplate"), }, }, + "updateStrategy": { + SchemaProps: spec.SchemaProps{ + Description: "The strategy to use to replace existing pods with new ones.", + Default: map[string]interface{}{}, + Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UpdateStrategy"), + }, + }, }, Required: []string{"name"}, }, }, Dependencies: []string{ - "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ContainerTemplate", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Metadata", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Scale", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Sink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Source", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDF", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.VertexLimits", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, + "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ContainerTemplate", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Metadata", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Scale", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Sink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Source", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDF", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UpdateStrategy", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.VertexLimits", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, } } @@ -5731,6 +5738,13 @@ func schema_pkg_apis_numaflow_v1alpha1_VertexSpec(ref common.ReferenceCallback) Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ContainerTemplate"), }, }, + "updateStrategy": { + SchemaProps: spec.SchemaProps{ + Description: "The strategy to use to replace existing pods with new ones.", + Default: map[string]interface{}{}, + Ref: ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UpdateStrategy"), + }, + }, "pipelineName": { SchemaProps: spec.SchemaProps{ Default: "", @@ -5789,7 +5803,7 @@ func schema_pkg_apis_numaflow_v1alpha1_VertexSpec(ref common.ReferenceCallback) }, }, Dependencies: []string{ - "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.CombinedEdge", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ContainerTemplate", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Metadata", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Scale", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Sink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Source", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDF", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.VertexLimits", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Watermark", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, + "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.CombinedEdge", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ContainerTemplate", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Metadata", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Scale", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Sink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Source", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDF", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UpdateStrategy", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.VertexLimits", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Watermark", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume"}, } } @@ -5834,6 +5848,14 @@ func schema_pkg_apis_numaflow_v1alpha1_VertexStatus(ref common.ReferenceCallback Format: "int64", }, }, + "desiredReplicas": { + SchemaProps: spec.SchemaProps{ + Description: "The number of desired replicas.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, "selector": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, @@ -5872,30 +5894,30 @@ func schema_pkg_apis_numaflow_v1alpha1_VertexStatus(ref common.ReferenceCallback Format: "int64", }, }, - "currentReplicas": { + "updatedReplicas": { SchemaProps: spec.SchemaProps{ - Description: "The number of Pods created by the controller from the Vertex version indicated by currentHash.", + Description: "The number of Pods created by the controller from the Vertex version indicated by updateHash.", Type: []string{"integer"}, Format: "int64", }, }, - "updatedReplicas": { + "updatedReadyReplicas": { SchemaProps: spec.SchemaProps{ - Description: "The number of Pods created by the controller from the Vertex version indicated by updateHash.", + Description: "The number of ready Pods created by the controller from the Vertex version indicated by updateHash.", Type: []string{"integer"}, Format: "int64", }, }, "currentHash": { SchemaProps: spec.SchemaProps{ - Description: "If not empty, indicates the version of the Vertex used to generate Pods in the sequence [0,currentReplicas).", + Description: "If not empty, indicates the current version of the Vertex used to generate Pods.", Type: []string{"string"}, Format: "", }, }, "updateHash": { SchemaProps: spec.SchemaProps{ - Description: "If not empty, indicates the version of the Vertx used to generate Pods in the sequence [replicas-updatedReplicas,replicas)", + Description: "If not empty, indicates the updated version of the Vertex used to generate Pods.", Type: []string{"string"}, Format: "", }, diff --git a/pkg/apis/numaflow/v1alpha1/vertex_types.go b/pkg/apis/numaflow/v1alpha1/vertex_types.go index 0b5ec7efc9..965e9f4bcc 100644 --- a/pkg/apis/numaflow/v1alpha1/vertex_types.go +++ b/pkg/apis/numaflow/v1alpha1/vertex_types.go @@ -37,6 +37,8 @@ const ( VertexPhaseRunning VertexPhase = "Running" VertexPhaseFailed VertexPhase = "Failed" + // VertexConditionDeployed has the status True when the vertex related sub resources are deployed. + VertexConditionDeployed ConditionType = "Deployed" // VertexConditionPodsHealthy has the status True when all the vertex pods are healthy. VertexConditionPodsHealthy ConditionType = "PodsHealthy" ) @@ -58,7 +60,7 @@ const NumaflowRustBinary = "/bin/numaflow-rs" // +kubebuilder:subresource:status // +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector // +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` -// +kubebuilder:printcolumn:name="Desired",type=string,JSONPath=`.spec.replicas` +// +kubebuilder:printcolumn:name="Desired",type=string,JSONPath=`.status.desiredReplicas` // +kubebuilder:printcolumn:name="Current",type=string,JSONPath=`.status.replicas` // +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.readyReplicas` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` @@ -608,6 +610,10 @@ type AbstractVertex struct { // Container template for the side inputs watcher container. // +optional SideInputsContainerTemplate *ContainerTemplate `json:"sideInputsContainerTemplate,omitempty" protobuf:"bytes,15,opt,name=sideInputsContainerTemplate"` + // The strategy to use to replace existing pods with new ones. + // +kubebuilder:default={"type": "RollingUpdate", "rollingUpdate": {"maxUnavailable": "25%"}} + // +optional + UpdateStrategy UpdateStrategy `json:"updateStrategy,omitempty" protobuf:"bytes,16,opt,name=updateStrategy"` } func (av AbstractVertex) GetVertexType() VertexType { @@ -715,29 +721,32 @@ type VertexStatus struct { // Total number of non-terminated pods targeted by this Vertex (their labels match the selector). // +optional Replicas uint32 `json:"replicas" protobuf:"varint,3,opt,name=replicas"` + // The number of desired replicas. + // +optional + DesiredReplicas uint32 `json:"desiredReplicas" protobuf:"varint,4,opt,name=desiredReplicas"` // +optional - Selector string `json:"selector,omitempty" protobuf:"bytes,4,opt,name=selector"` + Selector string `json:"selector,omitempty" protobuf:"bytes,5,opt,name=selector"` // +optional - Reason string `json:"reason,omitempty" protobuf:"bytes,5,opt,name=reason"` + Reason string `json:"reason,omitempty" protobuf:"bytes,6,opt,name=reason"` // +optional - Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"` + Message string `json:"message,omitempty" protobuf:"bytes,7,opt,name=message"` // Time of last scaling operation. // +optional - LastScaledAt metav1.Time `json:"lastScaledAt,omitempty" protobuf:"bytes,7,opt,name=lastScaledAt"` + LastScaledAt metav1.Time `json:"lastScaledAt,omitempty" protobuf:"bytes,8,opt,name=lastScaledAt"` // The generation observed by the Vertex controller. // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,8,opt,name=observedGeneration"` + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,9,opt,name=observedGeneration"` // The number of pods targeted by this Vertex with a Ready Condition. // +optional - ReadyReplicas uint32 `json:"readyReplicas,omitempty" protobuf:"varint,9,opt,name=readyReplicas"` - // The number of Pods created by the controller from the Vertex version indicated by currentHash. - CurrentReplicas uint32 `json:"currentReplicas,omitempty" protobuf:"varint,10,opt,name=currentReplicas"` + ReadyReplicas uint32 `json:"readyReplicas,omitempty" protobuf:"varint,10,opt,name=readyReplicas"` // The number of Pods created by the controller from the Vertex version indicated by updateHash. UpdatedReplicas uint32 `json:"updatedReplicas,omitempty" protobuf:"varint,11,opt,name=updatedReplicas"` - // If not empty, indicates the version of the Vertex used to generate Pods in the sequence [0,currentReplicas). - CurrentHash string `json:"currentHash,omitempty" protobuf:"bytes,12,opt,name=currentHash"` - // If not empty, indicates the version of the Vertx used to generate Pods in the sequence [replicas-updatedReplicas,replicas) - UpdateHash string `json:"updateHash,omitempty" protobuf:"bytes,13,opt,name=updateHash"` + // The number of ready Pods created by the controller from the Vertex version indicated by updateHash. + UpdatedReadyReplicas uint32 `json:"updatedReadyReplicas,omitempty" protobuf:"varint,12,opt,name=updatedReadyReplicas"` + // If not empty, indicates the current version of the Vertex used to generate Pods. + CurrentHash string `json:"currentHash,omitempty" protobuf:"bytes,13,opt,name=currentHash"` + // If not empty, indicates the updated version of the Vertex used to generate Pods. + UpdateHash string `json:"updateHash,omitempty" protobuf:"bytes,14,opt,name=updateHash"` } func (vs *VertexStatus) MarkPhase(phase VertexPhase, reason, message string) { @@ -756,6 +765,17 @@ func (vs *VertexStatus) MarkPhaseRunning() { vs.MarkPhase(VertexPhaseRunning, "", "") } +// MarkDeployed set the Vertex has it's sub resources deployed. +func (vs *VertexStatus) MarkDeployed() { + vs.MarkTrue(VertexConditionDeployed) +} + +// MarkDeployFailed set the Vertex deployment failed +func (vs *VertexStatus) MarkDeployFailed(reason, message string) { + vs.MarkFalse(VertexConditionDeployed, reason, message) + vs.MarkPhaseFailed(reason, message) +} + // MarkPodNotHealthy marks the pod not healthy with the given reason and message. func (vs *VertexStatus) MarkPodNotHealthy(reason, message string) { vs.MarkFalse(VertexConditionPodsHealthy, reason, message) @@ -770,7 +790,7 @@ func (vs *VertexStatus) MarkPodHealthy(reason, message string) { // InitConditions sets conditions to Unknown state. func (vs *VertexStatus) InitConditions() { - vs.InitializeConditions(VertexConditionPodsHealthy) + vs.InitializeConditions(VertexConditionDeployed, VertexConditionPodsHealthy) } // IsHealthy indicates whether the vertex is healthy or not diff --git a/pkg/apis/numaflow/v1alpha1/vertex_types_test.go b/pkg/apis/numaflow/v1alpha1/vertex_types_test.go index 1a4534c3ec..1f5572c424 100644 --- a/pkg/apis/numaflow/v1alpha1/vertex_types_test.go +++ b/pkg/apis/numaflow/v1alpha1/vertex_types_test.go @@ -565,10 +565,37 @@ func Test_VertexMarkPodHealthy(t *testing.T) { } } +func Test_VertexMarkDeployed(t *testing.T) { + s := VertexStatus{} + s.MarkDeployed() + for _, c := range s.Conditions { + if c.Type == string(VertexConditionDeployed) { + assert.Equal(t, metav1.ConditionTrue, c.Status) + assert.Equal(t, "Successful", c.Reason) + assert.Equal(t, "Successful", c.Message) + } + } +} + +func Test_VertexMarkDeployFailed(t *testing.T) { + s := VertexStatus{} + s.MarkDeployFailed("reason", "message") + assert.Equal(t, VertexPhaseFailed, s.Phase) + assert.Equal(t, "reason", s.Reason) + assert.Equal(t, "message", s.Message) + for _, c := range s.Conditions { + if c.Type == string(VertexConditionDeployed) { + assert.Equal(t, metav1.ConditionFalse, c.Status) + assert.Equal(t, "reason", c.Reason) + assert.Equal(t, "message", c.Message) + } + } +} + func Test_VertexInitConditions(t *testing.T) { v := VertexStatus{} v.InitConditions() - assert.Equal(t, 1, len(v.Conditions)) + assert.Equal(t, 2, len(v.Conditions)) for _, c := range v.Conditions { assert.Equal(t, metav1.ConditionUnknown, c.Status) } diff --git a/pkg/reconciler/monovertex/controller.go b/pkg/reconciler/monovertex/controller.go index a7b7a90c40..9aca247a05 100644 --- a/pkg/reconciler/monovertex/controller.go +++ b/pkg/reconciler/monovertex/controller.go @@ -118,6 +118,7 @@ func (mr *monoVertexReconciler) reconcile(ctx context.Context, monoVtx *dfv1.Mon if err := mr.orchestrateFixedResources(ctx, monoVtx); err != nil { monoVtx.Status.MarkDeployFailed("OrchestrateFixedResourcesFailed", err.Error()) + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "OrchestrateFixedResourcesFailed", "OrchestrateFixedResourcesFailed: %s", err.Error()) return ctrl.Result{}, err } @@ -125,6 +126,7 @@ func (mr *monoVertexReconciler) reconcile(ctx context.Context, monoVtx *dfv1.Mon if err := mr.orchestratePods(ctx, monoVtx); err != nil { monoVtx.Status.MarkDeployFailed("OrchestratePodsFailed", err.Error()) + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "OrchestratePodsFailed", "OrchestratePodsFailed: %s", err.Error()) return ctrl.Result{}, err } @@ -399,8 +401,8 @@ func (mr *monoVertexReconciler) createOrUpdateMonoVtxServices(ctx context.Contex if existingSvc.GetAnnotations()[dfv1.KeyHash] != svcHash { if err := mr.client.Delete(ctx, &existingSvc); err != nil { if !apierrors.IsNotFound(err) { - mr.markDeploymentFailedAndLogEvent(monoVtx, true, log, "DelSvcFailed", err.Error(), "Failed to delete existing mono vertex service", zap.String("service", existingSvc.Name), zap.Error(err)) - return err + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "DelSvcFailed", "Failed to delete existing mono vertex service %s: %s", existingSvc.Name, err.Error()) + return fmt.Errorf("failed to delete existing mono vertex service %s: %w", existingSvc.Name, err) } } else { log.Infow("Deleted a stale mono vertex service to recreate", zap.String("service", existingSvc.Name)) @@ -417,8 +419,8 @@ func (mr *monoVertexReconciler) createOrUpdateMonoVtxServices(ctx context.Contex if apierrors.IsAlreadyExists(err) { continue } - mr.markDeploymentFailedAndLogEvent(monoVtx, true, log, "CreateSvcFailed", err.Error(), "Failed to create a mono vertex service", zap.String("service", s.Name), zap.Error(err)) - return err + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "CreateSvcFailed", "Failed to create a mono vertex service %s: %s", s.Name, err.Error()) + return fmt.Errorf("failed to create a mono vertex service %s: %w", s.Name, err) } else { log.Infow("Succeeded to create a mono vertex service", zap.String("service", s.Name)) mr.recorder.Eventf(monoVtx, corev1.EventTypeNormal, "CreateSvcSuccess", "Succeeded to create mono vertex service %s", s.Name) @@ -428,8 +430,8 @@ func (mr *monoVertexReconciler) createOrUpdateMonoVtxServices(ctx context.Contex for _, v := range existingSvcs { // clean up stale services if err := mr.client.Delete(ctx, &v); err != nil { if !apierrors.IsNotFound(err) { - mr.markDeploymentFailedAndLogEvent(monoVtx, true, log, "DelSvcFailed", err.Error(), "Failed to delete mono vertex service not in use", zap.String("service", v.Name), zap.Error(err)) - return err + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "DelSvcFailed", "Failed to delete mono vertex service %s: %s", v.Name, err.Error()) + return fmt.Errorf("failed to delete mono vertex service %s: %w", v.Name, err) } } else { log.Infow("Deleted a stale mono vertx service", zap.String("service", v.Name)) @@ -463,20 +465,19 @@ func (mr *monoVertexReconciler) createOrUpdateDaemonService(ctx context.Context, if apierrors.IsNotFound(err) { needToCreatDaemonSvc = true } else { - mr.markDeploymentFailedAndLogEvent(monoVtx, false, log, "FindDaemonSvcFailed", err.Error(), "Failed to find existing mono vtx daemon service", zap.String("service", svc.Name), zap.Error(err)) - return err + return fmt.Errorf("failed to find existing mono vertex daemon service: %w", err) } } else if existingSvc.GetAnnotations()[dfv1.KeyHash] != svcHash { if err := mr.client.Delete(ctx, existingSvc); err != nil && !apierrors.IsNotFound(err) { - mr.markDeploymentFailedAndLogEvent(monoVtx, true, log, "DelDaemonSvcFailed", err.Error(), "Failed to delete existing mono vtx daemon service", zap.String("service", existingSvc.Name), zap.Error(err)) - return err + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "DelDaemonSvcFailed", "Failed to delete existing mono vertex daemon service %s: %s", existingSvc.Name, err.Error()) + return fmt.Errorf("failed to delete existing mono vertex daemon service %s: %w", existingSvc.Name, err) } needToCreatDaemonSvc = true } if needToCreatDaemonSvc { if err := mr.client.Create(ctx, svc); err != nil { - mr.markDeploymentFailedAndLogEvent(monoVtx, true, log, "CreateDaemonSvcFailed", err.Error(), "Failed to create mono vtx daemon service", zap.String("service", svc.Name), zap.Error(err)) - return err + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "CreateDaemonSvcFailed", "Failed to create a mono vertex daemon service %s: %s", svc.Name, err.Error()) + return fmt.Errorf("failed to create a mono vertex daemon service %s: %w", svc.Name, err) } log.Infow("Succeeded to create a mono vertex daemon service", zap.String("service", svc.Name)) mr.recorder.Eventf(monoVtx, corev1.EventTypeNormal, "CreateMonoVtxDaemonSvcSuccess", "Succeeded to create a mono vertex daemon service %s", svc.Name) @@ -496,8 +497,7 @@ func (mr *monoVertexReconciler) createOrUpdateDaemonDeployment(ctx context.Conte } deploy, err := monoVtx.GetDaemonDeploymentObj(req) if err != nil { - mr.markDeploymentFailedAndLogEvent(monoVtx, false, log, "BuildDaemonDeployFailed", err.Error(), "Failed to build mono verex daemon deployment spec", zap.Error(err)) - return err + return fmt.Errorf("failed to build mono vertex daemon deployment spec: %w", err) } deployHash := sharedutil.MustHash(deploy.Spec) deploy.Annotations = map[string]string{dfv1.KeyHash: deployHash} @@ -505,8 +505,7 @@ func (mr *monoVertexReconciler) createOrUpdateDaemonDeployment(ctx context.Conte needToCreate := false if err := mr.client.Get(ctx, types.NamespacedName{Namespace: monoVtx.Namespace, Name: deploy.Name}, existingDeploy); err != nil { if !apierrors.IsNotFound(err) { - mr.markDeploymentFailedAndLogEvent(monoVtx, false, log, "FindDaemonDeployFailed", err.Error(), "Failed to find existing mono vertex daemon deployment", zap.String("deployment", deploy.Name), zap.Error(err)) - return err + return fmt.Errorf("failed to find existing mono vertex daemon deployment: %w", err) } else { needToCreate = true } @@ -514,16 +513,16 @@ func (mr *monoVertexReconciler) createOrUpdateDaemonDeployment(ctx context.Conte if existingDeploy.GetAnnotations()[dfv1.KeyHash] != deployHash { // Delete and recreate, to avoid updating immutable fields problem. if err := mr.client.Delete(ctx, existingDeploy); err != nil { - mr.markDeploymentFailedAndLogEvent(monoVtx, true, log, "DeleteOldDaemonDeployFailed", err.Error(), "Failed to delete the outdated daemon deployment", zap.String("deployment", existingDeploy.Name), zap.Error(err)) - return err + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "DeleteOldDaemonDeployFailed", "Failed to delete the outdated daemon deployment %s: %s", existingDeploy.Name, err.Error()) + return fmt.Errorf("failed to delete the outdated daemon deployment %s: %w", existingDeploy.Name, err) } needToCreate = true } } if needToCreate { if err := mr.client.Create(ctx, deploy); err != nil && !apierrors.IsAlreadyExists(err) { - mr.markDeploymentFailedAndLogEvent(monoVtx, true, log, "CreateDaemonDeployFailed", err.Error(), "Failed to create a mono vertex daemon deployment", zap.String("deployment", deploy.Name), zap.Error(err)) - return err + mr.recorder.Eventf(monoVtx, corev1.EventTypeWarning, "CreateDaemonDeployFailed", "Failed to create a mono vertex daemon deployment %s: %s", deploy.Name, err.Error()) + return fmt.Errorf("failed to create a mono vertex daemon deployment %s: %w", deploy.Name, err) } log.Infow("Succeeded to create/recreate a mono vertex daemon deployment", zap.String("deployment", deploy.Name)) mr.recorder.Eventf(monoVtx, corev1.EventTypeNormal, "CreateDaemonDeploySuccess", "Succeeded to create/recreate a mono vertex daemon deployment %s", deploy.Name) @@ -548,15 +547,6 @@ func (mr *monoVertexReconciler) buildPodSpec(monoVtx *dfv1.MonoVertex) (*corev1. return podSpec, nil } -// Helper function for warning event types -func (mr *monoVertexReconciler) markDeploymentFailedAndLogEvent(monoVtx *dfv1.MonoVertex, recordEvent bool, log *zap.SugaredLogger, reason, message, logMsg string, logWith ...interface{}) { - log.Errorw(logMsg, logWith) - monoVtx.Status.MarkDeployFailed(reason, message) - if recordEvent { - mr.recorder.Event(monoVtx, corev1.EventTypeWarning, reason, message) - } -} - // checkChildrenResourceStatus checks the status of the children resources of the mono vertex func (mr *monoVertexReconciler) checkChildrenResourceStatus(ctx context.Context, monoVtx *dfv1.MonoVertex) error { defer func() { diff --git a/pkg/reconciler/monovertex/controller_test.go b/pkg/reconciler/monovertex/controller_test.go index e9c4f9fdea..8e1f179db4 100644 --- a/pkg/reconciler/monovertex/controller_test.go +++ b/pkg/reconciler/monovertex/controller_test.go @@ -20,20 +20,25 @@ import ( "context" "strings" "testing" + "time" "go.uber.org/zap/zaptest" appv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/record" "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" "github.com/numaproj/numaflow/pkg/reconciler" "github.com/numaproj/numaflow/pkg/reconciler/monovertex/scaling" + sharedutil "github.com/numaproj/numaflow/pkg/shared/util" "github.com/stretchr/testify/assert" ) @@ -98,17 +103,62 @@ func Test_NewReconciler(t *testing.T) { assert.True(t, ok) } -func Test_BuildPodSpec(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, nil) - cl := fake.NewClientBuilder().Build() - r := &monoVertexReconciler{ +func fakeReconciler(t *testing.T, cl client.WithWatch) *monoVertexReconciler { + t.Helper() + return &monoVertexReconciler{ client: cl, scheme: scheme.Scheme, - config: fakeConfig, + config: reconciler.FakeGlobalConfig(t, nil), image: testFlowImage, logger: zaptest.NewLogger(t).Sugar(), recorder: record.NewFakeRecorder(64), + scaler: scaling.NewScaler(cl), } +} + +func TestReconcile(t *testing.T) { + t.Run("test not found", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: "not-exist", + Namespace: testNamespace, + }, + } + _, err := r.Reconcile(context.TODO(), req) + // Return nil when not found + assert.NoError(t, err) + }) + + t.Run("test found", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testMonoVtx.DeepCopy() + err := cl.Create(context.TODO(), testObj) + assert.NoError(t, err) + o := &dfv1.MonoVertex{} + err = cl.Get(context.TODO(), types.NamespacedName{ + Namespace: testObj.Namespace, + Name: testObj.Name, + }, o) + assert.NoError(t, err) + assert.Equal(t, testObj.Name, o.Name) + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: testObj.Name, + Namespace: testObj.Namespace, + }, + } + _, err = r.Reconcile(context.TODO(), req) + assert.Error(t, err) + assert.ErrorContains(t, err, "not found") + }) +} + +func Test_BuildPodSpec(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) t.Run("test has everything", func(t *testing.T) { testObj := testMonoVtx.DeepCopy() spec, err := r.buildPodSpec(testObj) @@ -137,16 +187,8 @@ func Test_BuildPodSpec(t *testing.T) { } func Test_createOrUpdateDaemonDeployment(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, nil) cl := fake.NewClientBuilder().Build() - r := &monoVertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) t.Run("test everything from scratch for daemon deployment", func(t *testing.T) { testObj := testMonoVtx.DeepCopy() @@ -163,16 +205,8 @@ func Test_createOrUpdateDaemonDeployment(t *testing.T) { } func Test_createOrUpdateDaemonService(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, nil) cl := fake.NewClientBuilder().Build() - r := &monoVertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) t.Run("test everything from scratch for daemon service", func(t *testing.T) { testObj := testMonoVtx.DeepCopy() @@ -189,16 +223,8 @@ func Test_createOrUpdateDaemonService(t *testing.T) { } func Test_createOrUpdateMonoVtxServices(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, nil) cl := fake.NewClientBuilder().Build() - r := &monoVertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) t.Run("test everything from scratch for monovtx service", func(t *testing.T) { testObj := testMonoVtx.DeepCopy() @@ -218,17 +244,10 @@ func Test_createOrUpdateMonoVtxServices(t *testing.T) { } func Test_orchestratePods(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, nil) - cl := fake.NewClientBuilder().Build() - r := &monoVertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + t.Run("test orchestratePodsFromTo and cleanUpPodsFromTo", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) testObj := testMonoVtx.DeepCopy() hash := "test-hasssssh" podSpec, err := r.buildPodSpec(testObj) @@ -250,6 +269,8 @@ func Test_orchestratePods(t *testing.T) { }) t.Run("test orchestratePods", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) testObj := testMonoVtx.DeepCopy() err := r.orchestratePods(context.TODO(), testObj) assert.NoError(t, err) @@ -263,16 +284,8 @@ func Test_orchestratePods(t *testing.T) { } func Test_orchestrateFixedResources(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, nil) cl := fake.NewClientBuilder().Build() - r := &monoVertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testMonoVtx.DeepCopy() err := r.orchestrateFixedResources(context.TODO(), testObj) assert.NoError(t, err) @@ -294,23 +307,87 @@ func Test_orchestrateFixedResources(t *testing.T) { } func Test_reconcile(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, nil) - cl := fake.NewClientBuilder().Build() - r := &monoVertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - scaler: scaling.NewScaler(cl), - } - testObj := testMonoVtx.DeepCopy() - _, err := r.reconcile(context.TODO(), testObj) - assert.NoError(t, err) - var daemonDeployment appv1.Deployment - err = r.client.Get(context.TODO(), client.ObjectKey{Namespace: testObj.GetNamespace(), Name: testObj.GetDaemonDeploymentName()}, - &daemonDeployment) - assert.NoError(t, err) - assert.Equal(t, testObj.GetDaemonDeploymentName(), daemonDeployment.Name) + + t.Run("test deletion", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testMonoVtx.DeepCopy() + testObj.DeletionTimestamp = &metav1.Time{Time: time.Now()} + _, err := r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + }) + + t.Run("test okay", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testMonoVtx.DeepCopy() + _, err := r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + var daemonDeployment appv1.Deployment + err = r.client.Get(context.TODO(), client.ObjectKey{Namespace: testObj.GetNamespace(), Name: testObj.GetDaemonDeploymentName()}, + &daemonDeployment) + assert.NoError(t, err) + assert.Equal(t, testObj.GetDaemonDeploymentName(), daemonDeployment.Name) + }) + + t.Run("test reconcile rolling update", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testMonoVtx.DeepCopy() + testObj.Spec.Replicas = ptr.To[int32](3) + _, err := r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + pods := &corev1.PodList{} + selector, _ := labels.Parse(dfv1.KeyComponent + "=" + dfv1.ComponentMonoVertex + "," + dfv1.KeyMonoVertexName + "=" + testObj.Name) + err = r.client.List(context.TODO(), pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 3, len(pods.Items)) + + podSpec, _ := r.buildPodSpec(testObj) + hash := sharedutil.MustHash(podSpec) + testObj.Status.Replicas = 3 + testObj.Status.ReadyReplicas = 3 + testObj.Status.UpdateHash = hash + testObj.Status.CurrentHash = hash + + // Reduce desired replicas + testObj.Spec.Replicas = ptr.To[int32](2) + _, err = r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + err = r.client.List(context.TODO(), pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 2, len(pods.Items)) + assert.Equal(t, uint32(2), testObj.Status.Replicas) + assert.Equal(t, uint32(2), testObj.Status.UpdatedReplicas) + + // updatedReplicas > desiredReplicas + testObj.Status.UpdatedReplicas = 3 + _, err = r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + assert.Equal(t, uint32(2), testObj.Status.UpdatedReplicas) + + // Clean up + testObj.Spec.Replicas = ptr.To[int32](0) + testObj.Spec.Scale.Min = ptr.To[int32](0) + _, err = r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + err = r.client.List(context.TODO(), pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 0, len(pods.Items)) + + // rolling update + testObj.Spec.Replicas = ptr.To[int32](20) + testObj.Status.UpdatedReplicas = 20 + testObj.Status.UpdatedReadyReplicas = 20 + testObj.Status.Replicas = 20 + testObj.Status.CurrentHash = "123456" + testObj.Status.UpdateHash = "123456" + _, err = r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + err = r.client.List(context.TODO(), pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 5, len(pods.Items)) + assert.Equal(t, uint32(20), testObj.Status.Replicas) + assert.Equal(t, uint32(5), testObj.Status.UpdatedReplicas) + }) } diff --git a/pkg/reconciler/pipeline/controller.go b/pkg/reconciler/pipeline/controller.go index 955344a8c1..d8b989f2d6 100644 --- a/pkg/reconciler/pipeline/controller.go +++ b/pkg/reconciler/pipeline/controller.go @@ -145,13 +145,28 @@ func (r *pipelineReconciler) reconcile(ctx context.Context, pl *dfv1.Pipeline) ( pl.Status.InitConditions() pl.Status.SetObservedGeneration(pl.Generation) + + if !controllerutil.ContainsFinalizer(pl, finalizerName) { + controllerutil.AddFinalizer(pl, finalizerName) + } + if err := ValidatePipeline(pl); err != nil { + r.recorder.Eventf(pl, corev1.EventTypeWarning, "ValidatePipelineFailed", "Invalid pipeline: %s", err.Error()) + pl.Status.MarkNotConfigured("InvalidSpec", err.Error()) + return ctrl.Result{}, err + } + pl.Status.SetVertexCounts(pl.Spec.Vertices) + pl.Status.MarkConfigured() + // Orchestrate pipeline sub resources. // This should be happening in all cases to ensure a clean initialization regardless of the lifecycle phase. // Eg: even for a pipeline started with desiredPhase = Pause, we should still create the resources for the pipeline. if err := r.reconcileFixedResources(ctx, pl); err != nil { - r.recorder.Eventf(pl, corev1.EventTypeWarning, "ReconcilePipelineFailed", "Failed to reconcile pipeline: %v", err.Error()) + r.recorder.Eventf(pl, corev1.EventTypeWarning, "ReconcileFixedResourcesFailed", "Failed to reconcile pipeline sub resources: %s", err.Error()) + pl.Status.MarkDeployFailed("ReconcileFixedResourcesFailed", err.Error()) return ctrl.Result{}, err } + pl.Status.MarkDeployed() + // If the pipeline has a lifecycle change, then do not update the phase as // this should happen only after the required configs for the lifecycle changes // have been applied. @@ -203,17 +218,6 @@ func isLifecycleChange(pl *dfv1.Pipeline) bool { // reconcileFixedResources do the jobs of creating fixed resources such as daemon service, vertex objects, and ISB management jobs, etc func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *dfv1.Pipeline) error { log := logging.FromContext(ctx) - if !controllerutil.ContainsFinalizer(pl, finalizerName) { - controllerutil.AddFinalizer(pl, finalizerName) - } - if err := ValidatePipeline(pl); err != nil { - log.Errorw("Validation failed", zap.Error(err)) - pl.Status.MarkNotConfigured("InvalidSpec", err.Error()) - return err - } - pl.Status.SetVertexCounts(pl.Spec.Vertices) - pl.Status.MarkConfigured() - isbSvc := &dfv1.InterStepBufferService{} isbSvcName := dfv1.DefaultISBSvcName if len(pl.Spec.InterStepBufferServiceName) > 0 { @@ -222,16 +226,13 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df err := r.client.Get(ctx, types.NamespacedName{Namespace: pl.Namespace, Name: isbSvcName}, isbSvc) if err != nil { if apierrors.IsNotFound(err) { - pl.Status.MarkDeployFailed("ISBSvcNotFound", "ISB Service not found.") log.Errorw("ISB Service not found", zap.String("isbsvc", isbSvcName), zap.Error(err)) return fmt.Errorf("isbsvc %s not found", isbSvcName) } - pl.Status.MarkDeployFailed("GetISBSvcFailed", err.Error()) log.Errorw("Failed to get ISB Service", zap.String("isbsvc", isbSvcName), zap.Error(err)) return err } if !isbSvc.Status.IsHealthy() { - pl.Status.MarkDeployFailed("ISBSvcNotHealthy", "ISB Service not healthy.") log.Errorw("ISB Service is not in healthy status", zap.String("isbsvc", isbSvcName), zap.Error(err)) return fmt.Errorf("isbsvc not healthy") } @@ -239,16 +240,13 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df // Create or update the Side Inputs Manager deployments if err := r.createOrUpdateSIMDeployments(ctx, pl, isbSvc.Status.Config); err != nil { log.Errorw("Failed to create or update Side Inputs Manager deployments", zap.Error(err)) - pl.Status.MarkDeployFailed("CreateOrUpdateSIMDeploymentsFailed", err.Error()) r.recorder.Eventf(pl, corev1.EventTypeWarning, "CreateOrUpdateSIMDeploymentsFailed", "Failed to create or update Side Inputs Manager deployments: %w", err.Error()) - return err + return fmt.Errorf("failed to create or update SIM deployments: %w", err) } existingObjs, err := r.findExistingVertices(ctx, pl) if err != nil { - log.Errorw("Failed to find existing vertices", zap.Error(err)) - pl.Status.MarkDeployFailed("ListVerticesFailed", err.Error()) - return err + return fmt.Errorf("failed to find existing vertices: %w", err) } oldBuffers := make(map[string]string) newBuffers := make(map[string]string) @@ -286,7 +284,6 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df if apierrors.IsAlreadyExists(err) { // probably somebody else already created it continue } else { - pl.Status.MarkDeployFailed("CreateVertexFailed", err.Error()) r.recorder.Eventf(pl, corev1.EventTypeWarning, "CreateVertexFailed", "Failed to create vertex: %w", err.Error()) return fmt.Errorf("failed to create vertex, err: %w", err) } @@ -298,7 +295,6 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df oldObj.Spec = newObj.Spec oldObj.Annotations[dfv1.KeyHash] = newObj.GetAnnotations()[dfv1.KeyHash] if err := r.client.Update(ctx, &oldObj); err != nil { - pl.Status.MarkDeployFailed("UpdateVertexFailed", err.Error()) r.recorder.Eventf(pl, corev1.EventTypeWarning, "UpdateVertexFailed", "Failed to update vertex: %w", err.Error()) return fmt.Errorf("failed to update vertex, err: %w", err) } @@ -310,7 +306,6 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df } for _, v := range existingObjs { if err := r.client.Delete(ctx, &v); err != nil { - pl.Status.MarkDeployFailed("DeleteStaleVertexFailed", err.Error()) r.recorder.Eventf(pl, corev1.EventTypeWarning, "DeleteStaleVertexFailed", "Failed to delete vertex: %w", err.Error()) return fmt.Errorf("failed to delete vertex, err: %w", err) } @@ -336,10 +331,11 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df args = append(args, fmt.Sprintf("--serving-source-streams=%s", strings.Join(pl.GetServingSourceStreamNames(), ","))) batchJob := buildISBBatchJob(pl, r.image, isbSvc.Status.Config, "isbsvc-create", args, "cre") if err := r.client.Create(ctx, batchJob); err != nil && !apierrors.IsAlreadyExists(err) { - pl.Status.MarkDeployFailed("CreateISBSvcCreatingJobFailed", err.Error()) - return fmt.Errorf("failed to create ISB Svc creating job, err: %w", err) + r.recorder.Eventf(pl, corev1.EventTypeWarning, "CreateJobForISBCeationFailed", "Failed to create a Job: %w", err.Error()) + return fmt.Errorf("failed to create ISB creating job, err: %w", err) } - log.Infow("Created a job successfully for ISB Svc creating", zap.Any("buffers", bfs), zap.Any("buckets", bks), zap.Any("servingStreams", pl.GetServingSourceStreamNames())) + log.Infow("Created a job successfully for ISB creating", zap.Any("buffers", bfs), zap.Any("buckets", bks), zap.Any("servingStreams", pl.GetServingSourceStreamNames())) + r.recorder.Eventf(pl, corev1.EventTypeNormal, "CreateJobForISBCeationSuccessful", "Create ISB creation job successfully") } if len(oldBuffers) > 0 || len(oldBuckets) > 0 { @@ -354,10 +350,11 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df args := []string{fmt.Sprintf("--buffers=%s", strings.Join(bfs, ",")), fmt.Sprintf("--buckets=%s", strings.Join(bks, ","))} batchJob := buildISBBatchJob(pl, r.image, isbSvc.Status.Config, "isbsvc-delete", args, "del") if err := r.client.Create(ctx, batchJob); err != nil && !apierrors.IsAlreadyExists(err) { - pl.Status.MarkDeployFailed("CreateISBSvcDeletingJobFailed", err.Error()) - return fmt.Errorf("failed to create ISB Svc deleting job, err: %w", err) + r.recorder.Eventf(pl, corev1.EventTypeWarning, "CreateJobForISBDeletionFailed", "Failed to create a Job: %w", err.Error()) + return fmt.Errorf("failed to create ISB deleting job, err: %w", err) } log.Infow("Created ISB Svc deleting job successfully", zap.Any("buffers", bfs), zap.Any("buckets", bks)) + r.recorder.Eventf(pl, corev1.EventTypeNormal, "CreateJobForISBDeletionSuccessful", "Create ISB deletion job successfully") } // Daemon service @@ -369,7 +366,6 @@ func (r *pipelineReconciler) reconcileFixedResources(ctx context.Context, pl *df return err } - pl.Status.MarkDeployed() return nil } diff --git a/pkg/reconciler/pipeline/controller_test.go b/pkg/reconciler/pipeline/controller_test.go index 2a1762aa4b..e130f49656 100644 --- a/pkg/reconciler/pipeline/controller_test.go +++ b/pkg/reconciler/pipeline/controller_test.go @@ -30,9 +30,11 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/record" "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -173,6 +175,46 @@ func Test_NewReconciler(t *testing.T) { assert.True(t, ok) } +func TestReconcile(t *testing.T) { + t.Run("test not found", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: "not-exist", + Namespace: testNamespace, + }, + } + _, err := r.Reconcile(context.TODO(), req) + // Return nil when not found + assert.NoError(t, err) + }) + + t.Run("test found", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testPipeline.DeepCopy() + err := cl.Create(context.TODO(), testObj) + assert.NoError(t, err) + o := &dfv1.Pipeline{} + err = cl.Get(context.TODO(), types.NamespacedName{ + Namespace: testObj.Namespace, + Name: testObj.Name, + }, o) + assert.NoError(t, err) + assert.Equal(t, testObj.Name, o.Name) + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: testObj.Name, + Namespace: testObj.Namespace, + }, + } + _, err = r.Reconcile(context.TODO(), req) + assert.Error(t, err) + assert.ErrorContains(t, err, "not found") + }) +} + func Test_reconcile(t *testing.T) { ctx := context.TODO() @@ -249,7 +291,7 @@ func Test_reconcile(t *testing.T) { _, err = r.reconcile(ctx, testObj) assert.Error(t, err) events := getEvents(t, r) - assert.Contains(t, events, "Warning ReconcilePipelineFailed Failed to reconcile pipeline: the length of the pipeline name plus the vertex name is over the max limit. (very-very-very-loooooooooooooooooooooooooooooooooooong-input), [must be no more than 63 characters]") + assert.Contains(t, events, "Warning ValidatePipelineFailed Invalid pipeline: the length of the pipeline name plus the vertex name is over the max limit. (very-very-very-loooooooooooooooooooooooooooooooooooong-input), [must be no more than 63 characters]") }) t.Run("test reconcile - duplicate vertex", func(t *testing.T) { @@ -267,7 +309,7 @@ func Test_reconcile(t *testing.T) { _, err = r.reconcile(ctx, testObj) assert.Error(t, err) events := getEvents(t, r) - assert.Contains(t, events, "Warning ReconcilePipelineFailed Failed to reconcile pipeline: duplicate vertex name \"input\"") + assert.Contains(t, events, "Warning ValidatePipelineFailed Invalid pipeline: duplicate vertex name \"input\"") }) } diff --git a/pkg/reconciler/pipeline/validate.go b/pkg/reconciler/pipeline/validate.go index 2a98c2e665..7304147c16 100644 --- a/pkg/reconciler/pipeline/validate.go +++ b/pkg/reconciler/pipeline/validate.go @@ -19,6 +19,7 @@ package pipeline import ( "fmt" + "k8s.io/apimachinery/pkg/util/intstr" k8svalidation "k8s.io/apimachinery/pkg/util/validation" dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" @@ -256,6 +257,13 @@ func validateVertex(v dfv1.AbstractVertex) error { return fmt.Errorf("vertex %q: partitions should not > 1 for source vertices", v.Name) } } + // Validate the update strategy. + maxUvail := v.UpdateStrategy.GetRollingUpdateStrategy().GetMaxUnavailable() + _, err := intstr.GetScaledValueFromIntOrPercent(&maxUvail, 1, true) // maxUnavailable should be an interger or a percentage in string + if err != nil { + return fmt.Errorf("vertex %q: invalid maxUnavailable: %v", v.Name, err) + } + for _, ic := range v.InitContainers { if isReservedContainerName(ic.Name) { return fmt.Errorf("vertex %q: init container name %q is reserved for containers created by numaflow", v.Name, ic.Name) diff --git a/pkg/reconciler/pipeline/validate_test.go b/pkg/reconciler/pipeline/validate_test.go index f116e04825..8f7a272d89 100644 --- a/pkg/reconciler/pipeline/validate_test.go +++ b/pkg/reconciler/pipeline/validate_test.go @@ -25,6 +25,7 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" ) @@ -642,6 +643,46 @@ func TestValidateVertex(t *testing.T) { assert.Contains(t, err.Error(), "can not be 0") }) + t.Run("rollingUpdateStrategy - invalid maxUnavailable", func(t *testing.T) { + v := dfv1.AbstractVertex{ + Name: "my-vertex", + UpdateStrategy: dfv1.UpdateStrategy{ + RollingUpdate: &dfv1.RollingUpdateStrategy{ + MaxUnavailable: ptr.To[intstr.IntOrString](intstr.FromString("10")), + }, + }, + } + err := validateVertex(v) + assert.Error(t, err) + assert.Contains(t, err.Error(), "string is not a percentage") + }) + + t.Run("rollingUpdateStrategy - good percentage maxUnavailable", func(t *testing.T) { + v := dfv1.AbstractVertex{ + Name: "my-vertex", + UpdateStrategy: dfv1.UpdateStrategy{ + RollingUpdate: &dfv1.RollingUpdateStrategy{ + MaxUnavailable: ptr.To[intstr.IntOrString](intstr.FromString("10%")), + }, + }, + } + err := validateVertex(v) + assert.NoError(t, err) + }) + + t.Run("rollingUpdateStrategy - good integer maxUnavailable", func(t *testing.T) { + v := dfv1.AbstractVertex{ + Name: "my-vertex", + UpdateStrategy: dfv1.UpdateStrategy{ + RollingUpdate: &dfv1.RollingUpdateStrategy{ + MaxUnavailable: ptr.To[intstr.IntOrString](intstr.FromInt(3)), + }, + }, + } + err := validateVertex(v) + assert.NoError(t, err) + }) + t.Run("good init container", func(t *testing.T) { v := dfv1.AbstractVertex{Name: "my-vertex", InitContainers: goodContainers} err := validateVertex(v) diff --git a/pkg/reconciler/vertex/controller.go b/pkg/reconciler/vertex/controller.go index c1e6b8febb..8789b5d89a 100644 --- a/pkg/reconciler/vertex/controller.go +++ b/pkg/reconciler/vertex/controller.go @@ -31,6 +31,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -95,8 +96,11 @@ func (r *vertexReconciler) reconcile(ctx context.Context, vertex *dfv1.Vertex) ( return ctrl.Result{}, nil } + vertex.Status.InitConditions() vertex.Status.SetObservedGeneration(vertex.Generation) + desiredReplicas := vertex.GetReplicas() + isbSvc := &dfv1.InterStepBufferService{} isbSvcName := dfv1.DefaultISBSvcName if len(vertex.Spec.InterStepBufferServiceName) > 0 { @@ -123,132 +127,199 @@ func (r *vertexReconciler) reconcile(ctx context.Context, vertex *dfv1.Vertex) ( r.scaler.StartWatching(vertexKey) } + // Create PVCs for reduce vertex + if vertex.IsReduceUDF() { + if err := r.buildReduceVertexPVCs(ctx, vertex); err != nil { + vertex.Status.MarkDeployFailed("BuildReduceVertexPVCsFailed", err.Error()) + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "BuildReduceVertexPVCsFailed", err.Error()) + return ctrl.Result{}, err + } + } + + // Create services + if err := r.createOrUpdateServices(ctx, vertex); err != nil { + vertex.Status.MarkDeployFailed("CreateOrUpdateServicesFailed", err.Error()) + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "CreateOrUpdateServicesFailed", err.Error()) + return ctrl.Result{}, err + } + + pipeline := &dfv1.Pipeline{} + if err := r.client.Get(ctx, types.NamespacedName{Namespace: vertex.Namespace, Name: vertex.Spec.PipelineName}, pipeline); err != nil { + log.Errorw("Failed to get pipeline object", zap.Error(err)) + vertex.Status.MarkDeployFailed("GetPipelineFailed", err.Error()) + return ctrl.Result{}, err + } + + // Create pods + if err := r.orchestratePods(ctx, vertex, pipeline, isbSvc); err != nil { + vertex.Status.MarkDeployFailed("OrchestratePodsFailed", err.Error()) + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "OrchestratePodsFailed", err.Error()) + return ctrl.Result{}, err + } + + vertex.Status.MarkDeployed() + + // Mark it running before checking the status of the pods + vertex.Status.MarkPhaseRunning() + + // Check status of the pods + var podList corev1.PodList + selector, _ := labels.Parse(dfv1.KeyPipelineName + "=" + vertex.Spec.PipelineName + "," + dfv1.KeyVertexName + "=" + vertex.Spec.Name) + if err := r.client.List(ctx, &podList, &client.ListOptions{Namespace: vertex.GetNamespace(), LabelSelector: selector}); err != nil { + vertex.Status.MarkPodNotHealthy("ListVerticesPodsFailed", err.Error()) + return ctrl.Result{}, fmt.Errorf("failed to get pods of a vertex: %w", err) + } + readyPods := reconciler.NumOfReadyPods(podList) + if readyPods > desiredReplicas { // It might happen in some corner cases, such as during rollout + readyPods = desiredReplicas + } + vertex.Status.ReadyReplicas = uint32(readyPods) + if healthy, reason, msg := reconciler.CheckPodsStatus(&podList); healthy { + vertex.Status.MarkPodHealthy(reason, msg) + } else { + // Do not need to explicitly requeue, since the it keeps watching the status change of the pods + vertex.Status.MarkPodNotHealthy(reason, msg) + } + + return ctrl.Result{}, nil +} + +func (r *vertexReconciler) orchestratePods(ctx context.Context, vertex *dfv1.Vertex, pipeline *dfv1.Pipeline, isbSvc *dfv1.InterStepBufferService) error { + log := logging.FromContext(ctx) desiredReplicas := vertex.GetReplicas() + vertex.Status.DesiredReplicas = uint32(desiredReplicas) + // Set metrics defer func() { reconciler.VertexDesiredReplicas.WithLabelValues(vertex.Namespace, vertex.Spec.PipelineName, vertex.Spec.Name).Set(float64(desiredReplicas)) reconciler.VertexCurrentReplicas.WithLabelValues(vertex.Namespace, vertex.Spec.PipelineName, vertex.Spec.Name).Set(float64(vertex.Status.Replicas)) }() - if vertex.IsReduceUDF() { - if x := vertex.Spec.UDF.GroupBy.Storage; x != nil && x.PersistentVolumeClaim != nil { - for i := 0; i < desiredReplicas; i++ { - newPvc, err := r.buildReduceVertexPVCSpec(vertex, i) - if err != nil { - log.Errorw("Error building a PVC spec", zap.Error(err)) - vertex.Status.MarkPhaseFailed("BuildPVCSpecFailed", err.Error()) - return ctrl.Result{}, err - } - hash := sharedutil.MustHash(newPvc.Spec) - newPvc.SetAnnotations(map[string]string{dfv1.KeyHash: hash}) - existingPvc := &corev1.PersistentVolumeClaim{} - if err := r.client.Get(ctx, types.NamespacedName{Namespace: vertex.Namespace, Name: newPvc.Name}, existingPvc); err != nil { - if !apierrors.IsNotFound(err) { - log.Errorw("Error finding existing PVC", zap.Error(err)) - vertex.Status.MarkPhaseFailed("FindExistingPVCFailed", err.Error()) - return ctrl.Result{}, err - } - if err := r.client.Create(ctx, newPvc); err != nil && !apierrors.IsAlreadyExists(err) { - r.markPhaseFailedAndLogEvent(vertex, log, "CreatePVCFailed", err.Error(), "Error creating a PVC", zap.Error(err)) - return ctrl.Result{}, err - } - r.recorder.Eventf(vertex, corev1.EventTypeNormal, "CreatePVCSuccess", "Successfully created PVC %s", newPvc.Name) - } else { - if existingPvc.GetAnnotations()[dfv1.KeyHash] != hash { - // TODO: deal with spec difference - if false { - log.Debug("TODO: check spec difference") - } - } - } - } - } + // Build pod spec of the 1st replica to calculate the hash, which is used to determine whether the pod spec is changed + tmpSpec, err := r.buildPodSpec(vertex, pipeline, isbSvc.Status.Config, 0) + if err != nil { + return fmt.Errorf("failed to build a pod spec: %w", err) + } + hash := sharedutil.MustHash(tmpSpec) + if vertex.Status.UpdateHash != hash { // New spec, or still processing last update, while new update is coming + vertex.Status.UpdateHash = hash + vertex.Status.UpdatedReplicas = 0 + vertex.Status.UpdatedReadyReplicas = 0 } - // Create services - // Note: We purposely put service reconciliation before pod, - // to prevent pod reconciliation failure from blocking service creation. - // It's ok to keep failing to scale up/down pods (e.g., due to quota), - // but without services, certain platform functionalities will be broken. - // E.g., the vertex processing rate calculation relies on the headless service to determine the number of active pods. - existingSvcs, err := r.findExistingServices(ctx, vertex) - if err != nil { - log.Errorw("Failed to find existing services", zap.Error(err)) - vertex.Status.MarkPhaseFailed("FindExistingSvcsFailed", err.Error()) - return ctrl.Result{}, err + // Manually or automatically scaled down + if currentReplicas := int(vertex.Status.Replicas); currentReplicas > desiredReplicas { + if err := r.cleanUpPodsFromTo(ctx, vertex, desiredReplicas, currentReplicas); err != nil { + return fmt.Errorf("failed to clean up vertex pods [%v, %v): %w", desiredReplicas, currentReplicas, err) + } + vertex.Status.Replicas = uint32(desiredReplicas) } - for _, s := range vertex.GetServiceObjs() { - svcHash := sharedutil.MustHash(s.Spec) - s.Annotations = map[string]string{dfv1.KeyHash: svcHash} - needToCreate := false - if existingSvc, existing := existingSvcs[s.Name]; existing { - if existingSvc.GetAnnotations()[dfv1.KeyHash] != svcHash { - if err := r.client.Delete(ctx, &existingSvc); err != nil { - if !apierrors.IsNotFound(err) { - r.markPhaseFailedAndLogEvent(vertex, log, "DelSvcFailed", err.Error(), "Failed to delete existing service", zap.String("service", existingSvc.Name), zap.Error(err)) - return ctrl.Result{}, err + updatedReplicas := int(vertex.Status.UpdatedReplicas) + if updatedReplicas > desiredReplicas { + updatedReplicas = desiredReplicas + vertex.Status.UpdatedReplicas = uint32(updatedReplicas) + } + + if updatedReplicas > 0 { + // Make sure [0 - updatedReplicas] with hash are in place + if err := r.orchestratePodsFromTo(ctx, vertex, pipeline, isbSvc, 0, updatedReplicas, hash); err != nil { + return fmt.Errorf("failed to orchestrate vertex pods [0, %v): %w", updatedReplicas, err) + } + // Wait for the updated pods to be ready before moving on + if vertex.Status.UpdatedReadyReplicas != vertex.Status.UpdatedReplicas { + updatedReadyReplicas := 0 + existingPods, err := r.findExistingPods(ctx, vertex, 0, updatedReplicas) + if err != nil { + return fmt.Errorf("failed to get pods of a vertex: %w", err) + } + for _, pod := range existingPods { + if pod.GetAnnotations()[dfv1.KeyHash] == vertex.Status.UpdateHash { + if reconciler.IsPodReady(pod) { + updatedReadyReplicas++ } - } else { - log.Infow("Deleted a stale service to recreate", zap.String("service", existingSvc.Name)) - r.recorder.Eventf(vertex, corev1.EventTypeNormal, "DelSvcSuccess", "Deleted stale service %s to recreate", existingSvc.Name) } - needToCreate = true } - delete(existingSvcs, s.Name) - } else { - needToCreate = true - } - if needToCreate { - if err := r.client.Create(ctx, s); err != nil { - if apierrors.IsAlreadyExists(err) { - continue - } - r.markPhaseFailedAndLogEvent(vertex, log, "CreateSvcFailed", err.Error(), "Failed to create a service", zap.String("service", s.Name), zap.Error(err)) - return ctrl.Result{}, err - } else { - log.Infow("Succeeded to create a service", zap.String("service", s.Name)) - r.recorder.Eventf(vertex, corev1.EventTypeNormal, "CreateSvcSuccess", "Succeeded to create service %s", s.Name) + vertex.Status.UpdatedReadyReplicas = uint32(updatedReadyReplicas) + if updatedReadyReplicas < updatedReplicas { + return nil } } } - for _, v := range existingSvcs { // clean up stale services - if err := r.client.Delete(ctx, &v); err != nil { - if !apierrors.IsNotFound(err) { - r.markPhaseFailedAndLogEvent(vertex, log, "DelSvcFailed", err.Error(), "Failed to delete service not in use", zap.String("service", v.Name), zap.Error(err)) - return ctrl.Result{}, err + + if vertex.Status.UpdateHash == vertex.Status.CurrentHash || + vertex.Status.CurrentHash == "" { + // 1. Regular scaling operation 2. First time + // create (desiredReplicas-updatedReplicas) pods directly + if desiredReplicas > updatedReplicas { + if err := r.orchestratePodsFromTo(ctx, vertex, pipeline, isbSvc, updatedReplicas, desiredReplicas, hash); err != nil { + return fmt.Errorf("failed to orchestrate vertex pods [%v, %v): %w", updatedReplicas, desiredReplicas, err) } - } else { - log.Infow("Deleted a stale service", zap.String("service", v.Name)) - r.recorder.Eventf(vertex, corev1.EventTypeNormal, "DelSvcSuccess", "Deleted stale service %s", v.Name) + } + vertex.Status.UpdatedReplicas = uint32(desiredReplicas) + vertex.Status.CurrentHash = vertex.Status.UpdateHash + } else { // Update scenario + if updatedReplicas >= desiredReplicas { + return nil + } + + // Create more pods + if vertex.Spec.UpdateStrategy.GetUpdateStrategyType() != dfv1.RollingUpdateStrategyType { + // Revisit later, we only support rolling update for now + return nil + } + + // Calculate the to be updated replicas based on the max unavailable configuration + maxUnavailConf := vertex.Spec.UpdateStrategy.GetRollingUpdateStrategy().GetMaxUnavailable() + toBeUpdated, err := intstr.GetScaledValueFromIntOrPercent(&maxUnavailConf, desiredReplicas, true) + if err != nil { // This should never happen since we have validated the configuration + return fmt.Errorf("invalid max unavailable configuration in rollingUpdate: %w", err) + } + if updatedReplicas+toBeUpdated > desiredReplicas { + toBeUpdated = desiredReplicas - updatedReplicas + } + log.Infof("Rolling update %d replicas, [%d, %d)", toBeUpdated, updatedReplicas, updatedReplicas+toBeUpdated) + + // Create pods [updatedReplicas, updatedReplicas+toBeUpdated), and clean up any pods in that range that has a different hash + if err := r.orchestratePodsFromTo(ctx, vertex, pipeline, isbSvc, updatedReplicas, updatedReplicas+toBeUpdated, vertex.Status.UpdateHash); err != nil { + return fmt.Errorf("failed to orchestrate pods [%v, %v)]: %w", updatedReplicas, updatedReplicas+toBeUpdated, err) + } + vertex.Status.UpdatedReplicas = uint32(updatedReplicas + toBeUpdated) + if vertex.Status.UpdatedReplicas == uint32(desiredReplicas) { + vertex.Status.CurrentHash = vertex.Status.UpdateHash } } - pipeline := &dfv1.Pipeline{} - if err := r.client.Get(ctx, types.NamespacedName{Namespace: vertex.Namespace, Name: vertex.Spec.PipelineName}, pipeline); err != nil { - log.Errorw("Failed to get pipeline object", zap.Error(err)) - vertex.Status.MarkPhaseFailed("GetPipelineFailed", err.Error()) - return ctrl.Result{}, err + currentReplicas := int(vertex.Status.Replicas) + if currentReplicas != desiredReplicas { + log.Infow("Pipeline Vertex replicas changed", "currentReplicas", currentReplicas, "desiredReplicas", desiredReplicas) + r.recorder.Eventf(vertex, corev1.EventTypeNormal, "ReplicasScaled", "Replicas changed from %d to %d", currentReplicas, desiredReplicas) + vertex.Status.Replicas = uint32(desiredReplicas) + vertex.Status.LastScaledAt = metav1.Time{Time: time.Now()} } - // Create pods - existingPods, err := r.findExistingPods(ctx, vertex) + if vertex.Status.Selector == "" { + selector, _ := labels.Parse(dfv1.KeyPipelineName + "=" + vertex.Spec.PipelineName + "," + dfv1.KeyVertexName + "=" + vertex.Spec.Name) + vertex.Status.Selector = selector.String() + } + + return nil +} + +func (r *vertexReconciler) orchestratePodsFromTo(ctx context.Context, vertex *dfv1.Vertex, pipeline *dfv1.Pipeline, isbSvc *dfv1.InterStepBufferService, fromReplica, toReplica int, newHash string) error { + log := logging.FromContext(ctx) + existingPods, err := r.findExistingPods(ctx, vertex, fromReplica, toReplica) if err != nil { - log.Errorw("Failed to find existing pods", zap.Error(err)) - vertex.Status.MarkPhaseFailed("FindExistingPodFailed", err.Error()) - return ctrl.Result{}, err + return fmt.Errorf("failed to find existing pods: %w", err) } - for replica := 0; replica < desiredReplicas; replica++ { + for replica := fromReplica; replica < toReplica; replica++ { podSpec, err := r.buildPodSpec(vertex, pipeline, isbSvc.Status.Config, replica) if err != nil { - log.Errorw("Failed to generate pod spec", zap.Error(err)) - vertex.Status.MarkPhaseFailed("PodSpecGenFailed", err.Error()) - return ctrl.Result{}, err + return fmt.Errorf("failed to generate pod spec: %w", err) } - hash := sharedutil.MustHash(podSpec) podNamePrefix := fmt.Sprintf("%s-%d-", vertex.Name, replica) needToCreate := true for existingPodName, existingPod := range existingPods { if strings.HasPrefix(existingPodName, podNamePrefix) { - if existingPod.GetAnnotations()[dfv1.KeyHash] == hash && existingPod.Status.Phase != corev1.PodFailed { + if existingPod.GetAnnotations()[dfv1.KeyHash] == newHash && existingPod.Status.Phase != corev1.PodFailed { needToCreate = false delete(existingPods, existingPodName) } @@ -272,7 +343,7 @@ func (r *vertexReconciler) reconcile(ctx context.Context, vertex *dfv1.Vertex) ( labels[dfv1.KeyAppName] = vertex.Name labels[dfv1.KeyPipelineName] = vertex.Spec.PipelineName labels[dfv1.KeyVertexName] = vertex.Spec.Name - annotations[dfv1.KeyHash] = hash + annotations[dfv1.KeyHash] = newHash annotations[dfv1.KeyReplica] = strconv.Itoa(replica) if vertex.IsMapUDF() || vertex.IsReduceUDF() { annotations[dfv1.KeyDefaultContainer] = dfv1.CtrUdf @@ -297,52 +368,73 @@ func (r *vertexReconciler) reconcile(ctx context.Context, vertex *dfv1.Vertex) ( } pod.Spec.Hostname = fmt.Sprintf("%s-%d", vertex.Name, replica) if err := r.client.Create(ctx, pod); err != nil { - r.markPhaseFailedAndLogEvent(vertex, log, "CreatePodFailed", err.Error(), "Failed to created pod", zap.Error(err)) - return ctrl.Result{}, err + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "CreatePodFailed", "Failed to create a pod %s", pod.Name) + return fmt.Errorf("failed to create a vertex pod: %w", err) } log.Infow("Succeeded to create a pod", zap.String("pod", pod.Name)) - r.recorder.Eventf(vertex, corev1.EventTypeNormal, "CreatePodSuccess", "Succeeded to create pod %s", pod.Name) + r.recorder.Eventf(vertex, corev1.EventTypeNormal, "CreatePodSuccess", "Succeeded to create a pod %s", pod.Name) } } for _, v := range existingPods { if err := r.client.Delete(ctx, &v); err != nil && !apierrors.IsNotFound(err) { - r.markPhaseFailedAndLogEvent(vertex, log, "DelPodFailed", err.Error(), "Failed to delete pod", zap.Error(err)) - return ctrl.Result{}, err + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "DelPodFailed", "Failed to delete pod %s", v.Name) + return fmt.Errorf("failed to delete a vertex pod %s: %w", v.Name, err) } } + return nil +} - currentReplicas := int(vertex.Status.Replicas) - if currentReplicas != desiredReplicas || vertex.Status.Selector == "" { - log.Infow("Pipeline Vertex replicas changed", "currentReplicas", currentReplicas, "desiredReplicas", desiredReplicas) - r.recorder.Eventf(vertex, corev1.EventTypeNormal, "ReplicasScaled", "Replicas changed from %d to %d", currentReplicas, desiredReplicas) - vertex.Status.Replicas = uint32(desiredReplicas) - vertex.Status.LastScaledAt = metav1.Time{Time: time.Now()} +func (r *vertexReconciler) cleanUpPodsFromTo(ctx context.Context, vertex *dfv1.Vertex, fromReplica, toReplica int) error { + log := logging.FromContext(ctx) + existingPods, err := r.findExistingPods(ctx, vertex, fromReplica, toReplica) + if err != nil { + return fmt.Errorf("failed to find existing pods: %w", err) } - selector, _ := labels.Parse(dfv1.KeyPipelineName + "=" + vertex.Spec.PipelineName + "," + dfv1.KeyVertexName + "=" + vertex.Spec.Name) - vertex.Status.Selector = selector.String() - - // Mark it running before checking the status of the pods - vertex.Status.MarkPhaseRunning() - // Check status of the pods - var podList corev1.PodList - if err := r.client.List(ctx, &podList, &client.ListOptions{Namespace: vertex.GetNamespace(), LabelSelector: selector}); err != nil { - vertex.Status.MarkPodNotHealthy("ListVerticesPodsFailed", err.Error()) - return ctrl.Result{}, fmt.Errorf("failed to get pods of a vertex: %w", err) + for _, pod := range existingPods { + if err := r.client.Delete(ctx, &pod); err != nil { + return fmt.Errorf("failed to delete pod %s: %w", pod.Name, err) + } + log.Infof("Deleted Vertx pod %q", pod.Name) + r.recorder.Eventf(vertex, corev1.EventTypeNormal, "DeletePodSuccess", "Succeeded to delete a vertex pod %s", pod.Name) } - readyPods := reconciler.NumOfReadyPods(podList) - if readyPods > desiredReplicas { // It might happen in some corner cases, such as during rollout - readyPods = desiredReplicas + return nil +} + +func (r *vertexReconciler) buildReduceVertexPVCs(ctx context.Context, vertex *dfv1.Vertex) error { + if !vertex.IsReduceUDF() { + return nil } - vertex.Status.ReadyReplicas = uint32(readyPods) - if healthy, reason, msg := reconciler.CheckPodsStatus(&podList); healthy { - vertex.Status.MarkPodHealthy(reason, msg) - } else { - // Do not need to explicitly requeue, since the it keeps watching the status change of the pods - vertex.Status.MarkPodNotHealthy(reason, msg) + if x := vertex.Spec.UDF.GroupBy.Storage; x != nil && x.PersistentVolumeClaim != nil { + log := logging.FromContext(ctx) + for i := 0; i < vertex.GetPartitionCount(); i++ { + newPvc, err := r.buildReduceVertexPVCSpec(vertex, i) + if err != nil { + return fmt.Errorf("failed to build a PVC spec: %w", err) + } + hash := sharedutil.MustHash(newPvc.Spec) + newPvc.SetAnnotations(map[string]string{dfv1.KeyHash: hash}) + existingPvc := &corev1.PersistentVolumeClaim{} + if err := r.client.Get(ctx, types.NamespacedName{Namespace: vertex.Namespace, Name: newPvc.Name}, existingPvc); err != nil { + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to find existing PVC: %w", err) + } + if err := r.client.Create(ctx, newPvc); err != nil && !apierrors.IsAlreadyExists(err) { + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "CreatePVCFailed", "Error creating a PVC: %s", err.Error()) + return fmt.Errorf("failed to create a PVC: %w", err) + } + r.recorder.Eventf(vertex, corev1.EventTypeNormal, "CreatePVCSuccess", "Successfully created PVC %s", newPvc.Name) + } else { + if existingPvc.GetAnnotations()[dfv1.KeyHash] != hash { + // TODO: deal with spec difference + if false { + log.Debug("TODO: check spec difference") + } + } + } + } } - - return ctrl.Result{}, nil + return nil } func (r *vertexReconciler) buildReduceVertexPVCSpec(vertex *dfv1.Vertex, replicaIndex int) (*corev1.PersistentVolumeClaim, error) { @@ -364,6 +456,60 @@ func (r *vertexReconciler) buildReduceVertexPVCSpec(vertex *dfv1.Vertex, replica return &newPvc, nil } +func (r *vertexReconciler) createOrUpdateServices(ctx context.Context, vertex *dfv1.Vertex) error { + log := logging.FromContext(ctx) + existingSvcs, err := r.findExistingServices(ctx, vertex) + if err != nil { + return fmt.Errorf("failed to find existing services: %w", err) + } + for _, s := range vertex.GetServiceObjs() { + svcHash := sharedutil.MustHash(s.Spec) + s.Annotations = map[string]string{dfv1.KeyHash: svcHash} + needToCreate := false + if existingSvc, existing := existingSvcs[s.Name]; existing { + if existingSvc.GetAnnotations()[dfv1.KeyHash] != svcHash { + if err := r.client.Delete(ctx, &existingSvc); err != nil { + if !apierrors.IsNotFound(err) { + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "DelSvcFailed", "Error deleting existing service: %s", err.Error()) + return fmt.Errorf("failed to delete existing service: %w", err) + } + } else { + log.Infow("Deleted a stale service to recreate", zap.String("service", existingSvc.Name)) + r.recorder.Eventf(vertex, corev1.EventTypeNormal, "DelSvcSuccess", "Deleted stale service %s to recreate", existingSvc.Name) + } + needToCreate = true + } + delete(existingSvcs, s.Name) + } else { + needToCreate = true + } + if needToCreate { + if err := r.client.Create(ctx, s); err != nil { + if apierrors.IsAlreadyExists(err) { + continue + } + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "CreateSvcFailed", "Error creating a service: %s", err.Error()) + return fmt.Errorf("failed to create a service: %w", err) + } else { + log.Infow("Succeeded to create a service", zap.String("service", s.Name)) + r.recorder.Eventf(vertex, corev1.EventTypeNormal, "CreateSvcSuccess", "Succeeded to create service %s", s.Name) + } + } + } + for _, v := range existingSvcs { // clean up stale services + if err := r.client.Delete(ctx, &v); err != nil { + if !apierrors.IsNotFound(err) { + r.recorder.Eventf(vertex, corev1.EventTypeWarning, "DelSvcFailed", "Error deleting existing service that is not in use: %s", err.Error()) + return fmt.Errorf("failed to delete existing service that is not in use: %w", err) + } + } else { + log.Infow("Deleted a stale service", zap.String("service", v.Name)) + r.recorder.Eventf(vertex, corev1.EventTypeNormal, "DelSvcSuccess", "Deleted stale service %s", v.Name) + } + } + return nil +} + func (r *vertexReconciler) buildPodSpec(vertex *dfv1.Vertex, pl *dfv1.Pipeline, isbSvcConfig dfv1.BufferServiceConfig, replicaIndex int) (*corev1.PodSpec, error) { isbSvcType, envs := sharedutil.GetIsbSvcEnvVars(isbSvcConfig) podSpec, err := vertex.GetPodSpec(dfv1.GetVertexPodSpecReq{ @@ -425,7 +571,7 @@ func (r *vertexReconciler) buildPodSpec(vertex *dfv1.Vertex, pl *dfv1.Pipeline, return podSpec, nil } -func (r *vertexReconciler) findExistingPods(ctx context.Context, vertex *dfv1.Vertex) (map[string]corev1.Pod, error) { +func (r *vertexReconciler) findExistingPods(ctx context.Context, vertex *dfv1.Vertex, fromReplica, toReplica int) (map[string]corev1.Pod, error) { pods := &corev1.PodList{} selector, _ := labels.Parse(dfv1.KeyPipelineName + "=" + vertex.Spec.PipelineName + "," + dfv1.KeyVertexName + "=" + vertex.Spec.Name) if err := r.client.List(ctx, pods, &client.ListOptions{Namespace: vertex.Namespace, LabelSelector: selector}); err != nil { @@ -437,7 +583,11 @@ func (r *vertexReconciler) findExistingPods(ctx context.Context, vertex *dfv1.Ve // Ignore pods being deleted continue } - result[v.Name] = v + replicaStr := v.GetAnnotations()[dfv1.KeyReplica] + replica, _ := strconv.Atoi(replicaStr) + if replica >= fromReplica && replica < toReplica { + result[v.Name] = v + } } return result, nil } @@ -454,10 +604,3 @@ func (r *vertexReconciler) findExistingServices(ctx context.Context, vertex *dfv } return result, nil } - -// Helper function for warning event types -func (r *vertexReconciler) markPhaseFailedAndLogEvent(vertex *dfv1.Vertex, log *zap.SugaredLogger, reason, message, logMsg string, logWith ...interface{}) { - log.Errorw(logMsg, logWith) - vertex.Status.MarkPhaseFailed(reason, message) - r.recorder.Event(vertex, corev1.EventTypeWarning, reason, message) -} diff --git a/pkg/reconciler/vertex/controller_test.go b/pkg/reconciler/vertex/controller_test.go index dedc25898e..4a2faa56b0 100644 --- a/pkg/reconciler/vertex/controller_test.go +++ b/pkg/reconciler/vertex/controller_test.go @@ -20,6 +20,7 @@ import ( "context" "strings" "testing" + "time" "github.com/stretchr/testify/assert" "go.uber.org/zap/zaptest" @@ -28,14 +29,18 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/record" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1" "github.com/numaproj/numaflow/pkg/reconciler" "github.com/numaproj/numaflow/pkg/reconciler/vertex/scaling" + sharedutil "github.com/numaproj/numaflow/pkg/shared/util" ) const ( @@ -166,6 +171,19 @@ func init() { _ = corev1.AddToScheme(scheme.Scheme) } +func fakeReconciler(t *testing.T, cl client.WithWatch) *vertexReconciler { + t.Helper() + return &vertexReconciler{ + client: cl, + scheme: scheme.Scheme, + config: reconciler.FakeGlobalConfig(t, fakeGlobalISBSvcConfig), + image: testFlowImage, + logger: zaptest.NewLogger(t).Sugar(), + recorder: record.NewFakeRecorder(64), + scaler: scaling.NewScaler(cl), + } +} + func Test_NewReconciler(t *testing.T) { cl := fake.NewClientBuilder().Build() r := NewReconciler(cl, scheme.Scheme, reconciler.FakeGlobalConfig(t, fakeGlobalISBSvcConfig), testFlowImage, scaling.NewScaler(cl), zaptest.NewLogger(t).Sugar(), record.NewFakeRecorder(64)) @@ -173,19 +191,51 @@ func Test_NewReconciler(t *testing.T) { assert.True(t, ok) } +func TestReconcile(t *testing.T) { + t.Run("test not found", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: "not-exist", + Namespace: testNamespace, + }, + } + _, err := r.Reconcile(context.TODO(), req) + // Return nil when not found + assert.NoError(t, err) + }) + + t.Run("test found", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testVertex.DeepCopy() + err := cl.Create(context.TODO(), testObj) + assert.NoError(t, err) + o := &dfv1.Vertex{} + err = cl.Get(context.TODO(), types.NamespacedName{ + Namespace: testObj.Namespace, + Name: testObj.Name, + }, o) + assert.NoError(t, err) + assert.Equal(t, testObj.Name, o.Name) + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: testObj.Name, + Namespace: testObj.Namespace, + }, + } + _, err = r.Reconcile(context.TODO(), req) + assert.Error(t, err) + assert.ErrorContains(t, err, "not found") + }) +} + func Test_BuildPodSpec(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, fakeGlobalISBSvcConfig) t.Run("test source", func(t *testing.T) { cl := fake.NewClientBuilder().Build() - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testSrcVertex.DeepCopy() spec, err := r.buildPodSpec(testObj, testPipeline, fakeIsbSvcConfig, 0) assert.NoError(t, err) @@ -211,14 +261,7 @@ func Test_BuildPodSpec(t *testing.T) { t.Run("test source with transformer", func(t *testing.T) { cl := fake.NewClientBuilder().Build() - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testSrcVertex.DeepCopy() testObj.Spec.Source = &dfv1.Source{ HTTP: &dfv1.HTTPSource{}, @@ -236,14 +279,7 @@ func Test_BuildPodSpec(t *testing.T) { t.Run("test user-defined source with transformer", func(t *testing.T) { cl := fake.NewClientBuilder().Build() - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testSrcVertex.DeepCopy() testObj.Spec.Source = &dfv1.Source{ UDSource: &dfv1.UDSource{ @@ -265,14 +301,7 @@ func Test_BuildPodSpec(t *testing.T) { t.Run("test sink", func(t *testing.T) { cl := fake.NewClientBuilder().Build() - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Name = "test-pl-output" testObj.Spec.Name = "output" @@ -303,14 +332,7 @@ func Test_BuildPodSpec(t *testing.T) { t.Run("test user-defined sink", func(t *testing.T) { cl := fake.NewClientBuilder().Build() - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Name = "test-pl-output" testObj.Spec.Name = "output" @@ -340,14 +362,7 @@ func Test_BuildPodSpec(t *testing.T) { t.Run("test map udf", func(t *testing.T) { cl := fake.NewClientBuilder().Build() - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Spec.UDF = &dfv1.UDF{ Builtin: &dfv1.Function{ @@ -378,14 +393,7 @@ func Test_BuildPodSpec(t *testing.T) { t.Run("test reduce udf", func(t *testing.T) { cl := fake.NewClientBuilder().Build() - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() volSize, _ := resource.ParseQuantity("1Gi") testObj.Spec.UDF = &dfv1.UDF{ @@ -422,7 +430,54 @@ func Test_BuildPodSpec(t *testing.T) { } func Test_reconcile(t *testing.T) { - fakeConfig := reconciler.FakeGlobalConfig(t, fakeGlobalISBSvcConfig) + + t.Run("test deletion", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testVertex.DeepCopy() + testObj.DeletionTimestamp = &metav1.Time{ + Time: time.Now(), + } + _, err := r.reconcile(context.TODO(), testObj) + assert.NoError(t, err) + }) + + t.Run("test no isbsvc", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testObj := testVertex.DeepCopy() + _, err := r.reconcile(context.TODO(), testObj) + assert.Error(t, err) + assert.ErrorContains(t, err, "not found") + assert.Equal(t, testObj.Status.Phase, dfv1.VertexPhaseFailed) + assert.Equal(t, testObj.Status.Reason, "ISBSvcNotFound") + assert.Contains(t, testObj.Status.Message, "not found") + }) + + t.Run("test isbsvc unhealthy", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + r := fakeReconciler(t, cl) + testIsbSvc := testNativeRedisIsbSvc.DeepCopy() + testIsbSvc.Status.MarkConfigured() + err := cl.Create(context.TODO(), testIsbSvc) + assert.Nil(t, err) + testPl := testPipeline.DeepCopy() + err = cl.Create(context.TODO(), testPl) + assert.Nil(t, err) + testObj := testVertex.DeepCopy() + testObj.Spec.Source = &dfv1.Source{ + HTTP: &dfv1.HTTPSource{ + Service: true, + }, + } + _, err = r.reconcile(context.TODO(), testObj) + assert.Error(t, err) + assert.ErrorContains(t, err, "not healthy") + assert.Equal(t, testObj.Status.Phase, dfv1.VertexPhaseFailed) + assert.Equal(t, testObj.Status.Reason, "ISBSvcNotHealthy") + assert.Contains(t, testObj.Status.Message, "not healthy") + }) + t.Run("test reconcile source", func(t *testing.T) { cl := fake.NewClientBuilder().Build() ctx := context.TODO() @@ -434,15 +489,7 @@ func Test_reconcile(t *testing.T) { testPl := testPipeline.DeepCopy() err = cl.Create(ctx, testPl) assert.Nil(t, err) - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - scaler: scaling.NewScaler(cl), - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Spec.Source = &dfv1.Source{ HTTP: &dfv1.HTTPSource{ @@ -486,15 +533,7 @@ func Test_reconcile(t *testing.T) { testPl := testPipeline.DeepCopy() err = cl.Create(ctx, testPl) assert.Nil(t, err) - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - scaler: scaling.NewScaler(cl), - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Spec.Sink = &dfv1.Sink{} _, err = r.reconcile(ctx, testObj) @@ -519,15 +558,7 @@ func Test_reconcile(t *testing.T) { testPl := testPipeline.DeepCopy() err = cl.Create(ctx, testPl) assert.Nil(t, err) - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - scaler: scaling.NewScaler(cl), - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Spec.UDF = &dfv1.UDF{ Builtin: &dfv1.Function{ @@ -545,7 +576,7 @@ func Test_reconcile(t *testing.T) { assert.Equal(t, 2, len(pods.Items[0].Spec.Containers)) }) - t.Run("test reconcile vertex with customization", func(t *testing.T) { + t.Run("test reconcile reduce udf", func(t *testing.T) { cl := fake.NewClientBuilder().Build() ctx := context.TODO() testIsbSvc := testNativeRedisIsbSvc.DeepCopy() @@ -556,15 +587,54 @@ func Test_reconcile(t *testing.T) { testPl := testPipeline.DeepCopy() err = cl.Create(ctx, testPl) assert.Nil(t, err) - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - scaler: scaling.NewScaler(cl), - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), + r := fakeReconciler(t, cl) + testObj := testVertex.DeepCopy() + testObj.Spec.UDF = &dfv1.UDF{ + Container: &dfv1.Container{ + Image: "my-image", + }, + GroupBy: &dfv1.GroupBy{ + Window: dfv1.Window{ + Fixed: &dfv1.FixedWindow{ + Length: &metav1.Duration{ + Duration: 10 * time.Second, + }, + }, + }, + Storage: &dfv1.PBQStorage{ + PersistentVolumeClaim: &dfv1.PersistenceStrategy{ + AccessMode: ptr.To[corev1.PersistentVolumeAccessMode](corev1.ReadWriteOnce), + }, + }, + }, } + _, err = r.reconcile(ctx, testObj) + assert.NoError(t, err) + pods := &corev1.PodList{} + selector, _ := labels.Parse(dfv1.KeyPipelineName + "=" + testPipelineName + "," + dfv1.KeyVertexName + "=" + testVertexSpecName) + err = r.client.List(ctx, pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 1, len(pods.Items)) + assert.True(t, strings.HasPrefix(pods.Items[0].Name, testVertexName+"-0-")) + assert.Equal(t, 2, len(pods.Items[0].Spec.Containers)) + pvc := &corev1.PersistentVolumeClaim{} + err = r.client.Get(ctx, types.NamespacedName{Name: dfv1.GeneratePBQStoragePVCName(testPl.Name, testObj.Spec.Name, 0), Namespace: testNamespace}, pvc) + assert.NoError(t, err) + assert.Equal(t, dfv1.GeneratePBQStoragePVCName(testPl.Name, testObj.Spec.Name, 0), pvc.Name) + }) + + t.Run("test reconcile vertex with customization", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + ctx := context.TODO() + testIsbSvc := testNativeRedisIsbSvc.DeepCopy() + testIsbSvc.Status.MarkConfigured() + testIsbSvc.Status.MarkDeployed() + err := cl.Create(ctx, testIsbSvc) + assert.Nil(t, err) + testPl := testPipeline.DeepCopy() + err = cl.Create(ctx, testPl) + assert.Nil(t, err) + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Spec.Sink = &dfv1.Sink{} testObj.Spec.ContainerTemplate = &dfv1.ContainerTemplate{ @@ -629,15 +699,7 @@ func Test_reconcile(t *testing.T) { testPl.Spec.Vertices[1].SideInputs = []string{"s1"} err = cl.Create(ctx, testPl) assert.Nil(t, err) - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: fakeConfig, - image: testFlowImage, - scaler: scaling.NewScaler(cl), - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Spec.UDF = &dfv1.UDF{ Builtin: &dfv1.Function{ @@ -656,10 +718,84 @@ func Test_reconcile(t *testing.T) { assert.Equal(t, 3, len(pods.Items[0].Spec.Containers)) assert.Equal(t, 2, len(pods.Items[0].Spec.InitContainers)) }) + + t.Run("test reconcile rolling update", func(t *testing.T) { + cl := fake.NewClientBuilder().Build() + ctx := context.TODO() + testIsbSvc := testNativeRedisIsbSvc.DeepCopy() + testIsbSvc.Status.MarkConfigured() + testIsbSvc.Status.MarkDeployed() + err := cl.Create(ctx, testIsbSvc) + assert.Nil(t, err) + testPl := testPipeline.DeepCopy() + err = cl.Create(ctx, testPl) + assert.Nil(t, err) + r := fakeReconciler(t, cl) + testObj := testVertex.DeepCopy() + testObj.Spec.UDF = &dfv1.UDF{ + Builtin: &dfv1.Function{ + Name: "cat", + }, + } + testObj.Spec.Replicas = ptr.To[int32](3) + _, err = r.reconcile(ctx, testObj) + assert.NoError(t, err) + pods := &corev1.PodList{} + selector, _ := labels.Parse(dfv1.KeyPipelineName + "=" + testPipelineName + "," + dfv1.KeyVertexName + "=" + testVertexSpecName) + err = r.client.List(ctx, pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 3, len(pods.Items)) + + tmpSpec, _ := r.buildPodSpec(testObj, testPl, testIsbSvc.Status.Config, 0) + hash := sharedutil.MustHash(tmpSpec) + testObj.Status.Replicas = 3 + testObj.Status.ReadyReplicas = 3 + testObj.Status.UpdateHash = hash + testObj.Status.CurrentHash = hash + + // Reduce desired replicas + testObj.Spec.Replicas = ptr.To[int32](2) + _, err = r.reconcile(ctx, testObj) + assert.NoError(t, err) + err = r.client.List(ctx, pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 2, len(pods.Items)) + assert.Equal(t, uint32(2), testObj.Status.Replicas) + assert.Equal(t, uint32(2), testObj.Status.UpdatedReplicas) + + // updatedReplicas > desiredReplicas + testObj.Status.UpdatedReplicas = 3 + _, err = r.reconcile(ctx, testObj) + assert.NoError(t, err) + assert.Equal(t, uint32(2), testObj.Status.UpdatedReplicas) + + // Clean up + testObj.Spec.Replicas = ptr.To[int32](0) + _, err = r.reconcile(ctx, testObj) + assert.NoError(t, err) + err = r.client.List(ctx, pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 0, len(pods.Items)) + + // rolling update + testObj.Spec.Replicas = ptr.To[int32](20) + testObj.Status.UpdatedReplicas = 20 + testObj.Status.UpdatedReadyReplicas = 20 + testObj.Status.Replicas = 20 + testObj.Status.CurrentHash = "123456" + testObj.Status.UpdateHash = "123456" + _, err = r.reconcile(ctx, testObj) + assert.NoError(t, err) + err = r.client.List(ctx, pods, &client.ListOptions{Namespace: testNamespace, LabelSelector: selector}) + assert.NoError(t, err) + assert.Equal(t, 5, len(pods.Items)) + assert.Equal(t, uint32(20), testObj.Status.Replicas) + assert.Equal(t, uint32(5), testObj.Status.UpdatedReplicas) + }) } func Test_reconcileEvents(t *testing.T) { - t.Run("test reconcile - isbsvc doesn't exist", func(t *testing.T) { + t.Run("test reconcile - events", func(t *testing.T) { cl := fake.NewClientBuilder().Build() ctx := context.TODO() testIsbSvc := testNativeRedisIsbSvc.DeepCopy() @@ -670,15 +806,7 @@ func Test_reconcileEvents(t *testing.T) { testPl := testPipeline.DeepCopy() err = cl.Create(ctx, testPl) assert.Nil(t, err) - r := &vertexReconciler{ - client: cl, - scheme: scheme.Scheme, - config: reconciler.FakeGlobalConfig(t, fakeGlobalISBSvcConfig), - image: testFlowImage, - scaler: scaling.NewScaler(cl), - logger: zaptest.NewLogger(t).Sugar(), - recorder: record.NewFakeRecorder(64), - } + r := fakeReconciler(t, cl) testObj := testVertex.DeepCopy() testObj.Spec.UDF = &dfv1.UDF{ Builtin: &dfv1.Function{ diff --git a/rust/numaflow-models/src/models/abstract_vertex.rs b/rust/numaflow-models/src/models/abstract_vertex.rs index 23fb85c813..6dffd1237a 100644 --- a/rust/numaflow-models/src/models/abstract_vertex.rs +++ b/rust/numaflow-models/src/models/abstract_vertex.rs @@ -95,6 +95,8 @@ pub struct AbstractVertex { pub tolerations: Option>, #[serde(rename = "udf", skip_serializing_if = "Option::is_none")] pub udf: Option>, + #[serde(rename = "updateStrategy", skip_serializing_if = "Option::is_none")] + pub update_strategy: Option>, #[serde(rename = "volumes", skip_serializing_if = "Option::is_none")] pub volumes: Option>, } @@ -129,6 +131,7 @@ impl AbstractVertex { source: None, tolerations: None, udf: None, + update_strategy: None, volumes: None, } } diff --git a/rust/numaflow-models/src/models/vertex_spec.rs b/rust/numaflow-models/src/models/vertex_spec.rs index a647ecc7ae..7583c1d6ac 100644 --- a/rust/numaflow-models/src/models/vertex_spec.rs +++ b/rust/numaflow-models/src/models/vertex_spec.rs @@ -108,6 +108,8 @@ pub struct VertexSpec { pub tolerations: Option>, #[serde(rename = "udf", skip_serializing_if = "Option::is_none")] pub udf: Option>, + #[serde(rename = "updateStrategy", skip_serializing_if = "Option::is_none")] + pub update_strategy: Option>, #[serde(rename = "volumes", skip_serializing_if = "Option::is_none")] pub volumes: Option>, #[serde(rename = "watermark", skip_serializing_if = "Option::is_none")] @@ -149,6 +151,7 @@ impl VertexSpec { to_edges: None, tolerations: None, udf: None, + update_strategy: None, volumes: None, watermark: None, } diff --git a/rust/numaflow-models/src/models/vertex_status.rs b/rust/numaflow-models/src/models/vertex_status.rs index 950ffa9ba5..30cb952f6d 100644 --- a/rust/numaflow-models/src/models/vertex_status.rs +++ b/rust/numaflow-models/src/models/vertex_status.rs @@ -21,12 +21,12 @@ pub struct VertexStatus { /// Conditions are the latest available observations of a resource's current state. #[serde(rename = "conditions", skip_serializing_if = "Option::is_none")] pub conditions: Option>, - /// If not empty, indicates the version of the Vertex used to generate Pods in the sequence [0,currentReplicas). + /// If not empty, indicates the current version of the Vertex used to generate Pods. #[serde(rename = "currentHash", skip_serializing_if = "Option::is_none")] pub current_hash: Option, - /// The number of Pods created by the controller from the Vertex version indicated by currentHash. - #[serde(rename = "currentReplicas", skip_serializing_if = "Option::is_none")] - pub current_replicas: Option, + /// The number of desired replicas. + #[serde(rename = "desiredReplicas", skip_serializing_if = "Option::is_none")] + pub desired_replicas: Option, #[serde(rename = "lastScaledAt", skip_serializing_if = "Option::is_none")] pub last_scaled_at: Option, #[serde(rename = "message", skip_serializing_if = "Option::is_none")] @@ -46,9 +46,15 @@ pub struct VertexStatus { pub replicas: Option, #[serde(rename = "selector", skip_serializing_if = "Option::is_none")] pub selector: Option, - /// If not empty, indicates the version of the Vertx used to generate Pods in the sequence [replicas-updatedReplicas,replicas) + /// If not empty, indicates the updated version of the Vertex used to generate Pods. #[serde(rename = "updateHash", skip_serializing_if = "Option::is_none")] pub update_hash: Option, + /// The number of ready Pods created by the controller from the Vertex version indicated by updateHash. + #[serde( + rename = "updatedReadyReplicas", + skip_serializing_if = "Option::is_none" + )] + pub updated_ready_replicas: Option, /// The number of Pods created by the controller from the Vertex version indicated by updateHash. #[serde(rename = "updatedReplicas", skip_serializing_if = "Option::is_none")] pub updated_replicas: Option, @@ -59,7 +65,7 @@ impl VertexStatus { VertexStatus { conditions: None, current_hash: None, - current_replicas: None, + desired_replicas: None, last_scaled_at: None, message: None, observed_generation: None, @@ -69,6 +75,7 @@ impl VertexStatus { replicas: None, selector: None, update_hash: None, + updated_ready_replicas: None, updated_replicas: None, } }