From 20e00e4ae20bd5036f32ab670157825bfd219cda Mon Sep 17 00:00:00 2001 From: n888 Date: Tue, 18 Jul 2023 10:26:04 -0700 Subject: [PATCH] feat: Support Multiple ALB Ingresses (#2639) * Support Multi ALB Ingress Signed-off-by: n888 * Fix comments and alb_test ARNs Signed-off-by: n888 * Fix e2e alb-canary-rollout: rm wildcard ingress path for alb controller Signed-off-by: n888 * Add e2e TestALBCanaryUpdateMultiIngress Signed-off-by: n888 * Add e2e TestALBCanaryUpdateMultiIngress Signed-off-by: n888 * e2e alb-canary-rollout use ImplementationSpecific wildcard path Signed-off-by: n888 * add e2e TestALBPingPongUpdateMultiIngress Signed-off-by: n888 * add e2e TestALBExperimentStepMultiIngress, TestALBExperimentStepNoSetWeightMultiIngress Signed-off-by: n888 * e2e alb headerroute: fix existing ing.class and simplify alb annotations Signed-off-by: n888 * add e2e TestAlbHeaderRouteMultiIngress Signed-off-by: n888 * fix lint alb_test.go Signed-off-by: n888 * update codegen Signed-off-by: n888 * TestAlbHeaderRouteMultiIngress use unique multi ingress resource names Signed-off-by: n888 * Initial Status.ALBs support Signed-off-by: n888 * clean up comments Signed-off-by: n888 * codegen Signed-off-by: n888 * only set ingress field for status.albs, and not status.alb Signed-off-by: n888 * add status.alb.ingress, but keep as optional Signed-off-by: n888 * improve determining whether to update status.albs or status.alb Signed-off-by: n888 * codegen Signed-off-by: n888 * codegen Signed-off-by: n888 * update both ALB and ALBs status Signed-off-by: Tung Huynh * fix ALBs status does not update when number of ingresses changes Signed-off-by: Tung Huynh * update tests Signed-off-by: Tung Huynh * add a little docs Signed-off-by: zachaller * github trigger re-run Signed-off-by: zachaller --------- Signed-off-by: n888 Signed-off-by: Tung Huynh Signed-off-by: zachaller Co-authored-by: Tung Huynh Co-authored-by: zachaller --- docs/features/traffic-management/alb.md | 5 + manifests/crds/rollout-crd.yaml | 50 +- manifests/install.yaml | 50 +- pkg/apiclient/rollout/rollout.swagger.json | 25 +- pkg/apis/api-rules/violation_exceptions.list | 3 + pkg/apis/rollouts/v1alpha1/generated.pb.go | 1165 +++++++++------- pkg/apis/rollouts/v1alpha1/generated.proto | 15 +- .../rollouts/v1alpha1/openapi_generated.go | 48 +- pkg/apis/rollouts/v1alpha1/types.go | 14 +- .../v1alpha1/zz_generated.deepcopy.go | 10 + .../validation/validation_references.go | 52 +- .../validation/validation_references_test.go | 250 +++- rollout/controller.go | 32 +- rollout/controller_test.go | 92 ++ rollout/trafficrouting/alb/alb.go | 451 ++++--- rollout/trafficrouting/alb/alb_test.go | 1192 ++++++++++++++++- ...multi-ingress-experiment-no-setweight.yaml | 123 ++ .../rollout-alb-multi-ingress-experiment.yaml | 120 ++ test/e2e/aws_test.go | 208 +++ .../alb-canary-multi-ingress-rollout.yaml | 118 ++ test/e2e/functional/alb-canary-rollout.yaml | 4 +- .../alb-pingpong-multi-ingress-rollout.yaml | 106 ++ .../alb-header-route-multi-ingress.yaml | 112 ++ test/e2e/header-routing/alb-header-route.yaml | 16 +- test/fixtures/common.go | 12 + ui/src/models/rollout/generated/api.ts | 22 +- utils/ingress/ingress.go | 22 + utils/ingress/ingress_test.go | 77 +- 28 files changed, 3567 insertions(+), 827 deletions(-) create mode 100644 test/e2e/alb/rollout-alb-multi-ingress-experiment-no-setweight.yaml create mode 100644 test/e2e/alb/rollout-alb-multi-ingress-experiment.yaml create mode 100644 test/e2e/functional/alb-canary-multi-ingress-rollout.yaml create mode 100644 test/e2e/functional/alb-pingpong-multi-ingress-rollout.yaml create mode 100644 test/e2e/header-routing/alb-header-route-multi-ingress.yaml diff --git a/docs/features/traffic-management/alb.md b/docs/features/traffic-management/alb.md index 5530d27ea2..0ac0ff6173 100644 --- a/docs/features/traffic-management/alb.md +++ b/docs/features/traffic-management/alb.md @@ -51,6 +51,11 @@ spec: # the AWS Load Balancer Controller to split traffic between the canary and stable # Service, according to the desired traffic weight (required). ingress: ingress + # If you want to controll multiple ingress resources you can use the ingresses field, if ingresses is specified + # the ingress field will need to be omitted. + ingresses: + - ingress-1 + - ingress-2 # Reference to a Service that the Ingress must target in one of the rules (optional). # If omitted, uses canary.stableService. rootService: root-service diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index 48b5ba105b..5428a3a2e4 100755 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -732,6 +732,10 @@ spec: type: string ingress: type: string + ingresses: + items: + type: string + type: array rootService: type: string servicePort: @@ -749,7 +753,6 @@ spec: - enabled type: object required: - - ingress - servicePort type: object ambassador: @@ -3320,6 +3323,8 @@ spec: - arn - name type: object + ingress: + type: string loadBalancer: properties: arn: @@ -3345,6 +3350,49 @@ spec: - name type: object type: object + albs: + items: + properties: + canaryTargetGroup: + properties: + arn: + type: string + fullName: + type: string + name: + type: string + required: + - arn + - name + type: object + ingress: + type: string + loadBalancer: + properties: + arn: + type: string + fullName: + type: string + name: + type: string + required: + - arn + - name + type: object + stableTargetGroup: + properties: + arn: + type: string + fullName: + type: string + name: + type: string + required: + - arn + - name + type: object + type: object + type: array availableReplicas: format: int32 type: integer diff --git a/manifests/install.yaml b/manifests/install.yaml index 4e8964c5f3..b93a60d32b 100755 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -12002,6 +12002,10 @@ spec: type: string ingress: type: string + ingresses: + items: + type: string + type: array rootService: type: string servicePort: @@ -12019,7 +12023,6 @@ spec: - enabled type: object required: - - ingress - servicePort type: object ambassador: @@ -14590,6 +14593,8 @@ spec: - arn - name type: object + ingress: + type: string loadBalancer: properties: arn: @@ -14615,6 +14620,49 @@ spec: - name type: object type: object + albs: + items: + properties: + canaryTargetGroup: + properties: + arn: + type: string + fullName: + type: string + name: + type: string + required: + - arn + - name + type: object + ingress: + type: string + loadBalancer: + properties: + arn: + type: string + fullName: + type: string + name: + type: string + required: + - arn + - name + type: object + stableTargetGroup: + properties: + arn: + type: string + fullName: + type: string + name: + type: string + required: + - arn + - name + type: object + type: object + type: array availableReplicas: format: int32 type: integer diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index 68b207b930..fb30b4b62e 100755 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -507,6 +507,9 @@ }, "stableTargetGroup": { "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AwsResourceRef" + }, + "ingress": { + "type": "string" } } }, @@ -526,13 +529,20 @@ "type": "string", "title": "RootService references the service in the ingress to the controller should add the action to" }, - "stickinessConfig": { - "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StickinessConfig", - "title": "AdditionalForwardConfig allows to specify further settings on the ForwaredConfig\n+optional" - }, "annotationPrefix": { "type": "string", "title": "AnnotationPrefix has to match the configured annotation prefix on the alb ingress controller\n+optional" + }, + "stickinessConfig": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StickinessConfig", + "title": "StickinessConfig refers to the duration-based stickiness of the target groups associated with an `Ingress`\n+optional" + }, + "ingresses": { + "type": "array", + "items": { + "type": "string" + }, + "title": "Ingresses refers to the name of an `Ingress` resource in the same namespace as the `Rollout` in a multi ingress scenario\n+optional" } }, "title": "ALBTrafficRouting configuration for ALB ingress controller to control traffic routing" @@ -1600,6 +1610,13 @@ "alb": { "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.ALBStatus", "title": "/ ALB keeps information regarding the ALB and TargetGroups" + }, + "albs": { + "type": "array", + "items": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.ALBStatus" + }, + "title": "/ ALBs keeps information regarding multiple ALBs and TargetGroups in a multi ingress scenario" } }, "title": "RolloutStatus is the status for a Rollout resource" diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index 2b177e4ce3..33491b0435 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -1,3 +1,4 @@ +API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,ALBTrafficRouting,Ingresses API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,AmbassadorTrafficRouting,Mappings API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,AnalysisRunSpec,Args API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,AnalysisRunSpec,DryRun @@ -35,6 +36,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutExperimentStep,Analyses API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutExperimentStep,Templates API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutExperimentStepAnalysisTemplateRef,Args +API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,ALBs API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,Conditions API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,PauseConditions API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutTrafficRouting,ManagedRoutes @@ -44,5 +46,6 @@ API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,TrafficWeights,Additional API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,WebMetric,Headers API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,MetricProvider,SkyWalking +API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,ALBs API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,HPAReplicas API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,Sigv4Config,RoleARN diff --git a/pkg/apis/rollouts/v1alpha1/generated.pb.go b/pkg/apis/rollouts/v1alpha1/generated.pb.go index b2face819c..f4bf7a3e67 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.pb.go +++ b/pkg/apis/rollouts/v1alpha1/generated.pb.go @@ -3292,516 +3292,519 @@ func init() { } var fileDescriptor_e0e705f843545fab = []byte{ - // 8134 bytes of a gzipped FileDescriptorProto + // 8179 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6d, 0x6c, 0x24, 0xd9, 0x71, 0xd8, 0x35, 0x87, 0x43, 0x72, 0x8a, 0x5c, 0x92, 0xfb, 0x76, 0x57, 0xcb, 0xe3, 0xdd, 0xee, - 0x9c, 0xfa, 0x8c, 0xcb, 0xc9, 0x3e, 0x91, 0xd2, 0x7d, 0x24, 0x27, 0x9d, 0x72, 0xc9, 0x0c, 0xb9, + 0x9c, 0xfa, 0x8c, 0xcb, 0xca, 0x3a, 0x91, 0xd2, 0x7d, 0x24, 0x27, 0x9d, 0x72, 0xc9, 0x0c, 0xb9, 0x7b, 0xcb, 0x3d, 0x72, 0x6f, 0xb6, 0x86, 0x7b, 0x2b, 0x4b, 0x3a, 0x5b, 0xcd, 0x99, 0xc7, 0x61, 0x2f, 0x67, 0xba, 0xc7, 0xdd, 0x3d, 0xdc, 0xe5, 0xe9, 0x60, 0x9d, 0x2c, 0x9c, 0xa2, 0x18, 0x12, - 0xac, 0xc4, 0x16, 0x82, 0x20, 0x41, 0xa0, 0x18, 0x06, 0xec, 0xc4, 0xfe, 0x65, 0x24, 0xc8, 0x1f, - 0x03, 0x31, 0xe2, 0x2f, 0xfd, 0x71, 0x20, 0xff, 0x48, 0x64, 0x07, 0x30, 0x1d, 0x51, 0xf9, 0x93, - 0x20, 0x81, 0x61, 0xc0, 0x41, 0xe0, 0xfd, 0x11, 0x04, 0xef, 0xb3, 0x5f, 0xf7, 0xf4, 0x70, 0x67, - 0x38, 0xcd, 0xd5, 0x25, 0xf1, 0xbf, 0x99, 0x57, 0xf5, 0xaa, 0xaa, 0xdf, 0x67, 0xbd, 0x7a, 0x55, - 0xf5, 0x60, 0xb3, 0xe5, 0x46, 0x7b, 0xbd, 0x9d, 0x95, 0x86, 0xdf, 0x59, 0x75, 0x82, 0x96, 0xdf, - 0x0d, 0xfc, 0x7b, 0xfc, 0xc7, 0xc7, 0x03, 0xbf, 0xdd, 0xf6, 0x7b, 0x51, 0xb8, 0xda, 0xdd, 0x6f, - 0xad, 0x3a, 0x5d, 0x37, 0x5c, 0xd5, 0x25, 0x07, 0x9f, 0x74, 0xda, 0xdd, 0x3d, 0xe7, 0x93, 0xab, - 0x2d, 0xea, 0xd1, 0xc0, 0x89, 0x68, 0x73, 0xa5, 0x1b, 0xf8, 0x91, 0x4f, 0x3e, 0x13, 0x53, 0x5b, - 0x51, 0xd4, 0xf8, 0x8f, 0x9f, 0x56, 0x75, 0x57, 0xba, 0xfb, 0xad, 0x15, 0x46, 0x6d, 0x45, 0x97, - 0x28, 0x6a, 0xcb, 0x1f, 0x37, 0x64, 0x69, 0xf9, 0x2d, 0x7f, 0x95, 0x13, 0xdd, 0xe9, 0xed, 0xf2, - 0x7f, 0xfc, 0x0f, 0xff, 0x25, 0x98, 0x2d, 0x3f, 0xbb, 0xff, 0x6a, 0xb8, 0xe2, 0xfa, 0x4c, 0xb6, - 0xd5, 0x1d, 0x27, 0x6a, 0xec, 0xad, 0x1e, 0xf4, 0x49, 0xb4, 0x6c, 0x1b, 0x48, 0x0d, 0x3f, 0xa0, - 0x59, 0x38, 0x2f, 0xc7, 0x38, 0x1d, 0xa7, 0xb1, 0xe7, 0x7a, 0x34, 0x38, 0x8c, 0xbf, 0xba, 0x43, - 0x23, 0x27, 0xab, 0xd6, 0xea, 0xa0, 0x5a, 0x41, 0xcf, 0x8b, 0xdc, 0x0e, 0xed, 0xab, 0xf0, 0x37, - 0x1f, 0x55, 0x21, 0x6c, 0xec, 0xd1, 0x8e, 0xd3, 0x57, 0xef, 0xa5, 0x41, 0xf5, 0x7a, 0x91, 0xdb, - 0x5e, 0x75, 0xbd, 0x28, 0x8c, 0x82, 0x74, 0x25, 0xfb, 0x77, 0x0b, 0x50, 0xaa, 0x6c, 0x56, 0xeb, - 0x91, 0x13, 0xf5, 0x42, 0xf2, 0x35, 0x0b, 0xe6, 0xda, 0xbe, 0xd3, 0xac, 0x3a, 0x6d, 0xc7, 0x6b, - 0xd0, 0x60, 0xc9, 0x7a, 0xc6, 0x7a, 0x7e, 0xf6, 0xc5, 0xcd, 0x95, 0x71, 0xfa, 0x6b, 0xa5, 0x72, - 0x3f, 0x44, 0x1a, 0xfa, 0xbd, 0xa0, 0x41, 0x91, 0xee, 0x56, 0x2f, 0x7e, 0xf7, 0xa8, 0xfc, 0xc4, - 0xf1, 0x51, 0x79, 0x6e, 0xd3, 0xe0, 0x84, 0x09, 0xbe, 0xe4, 0xdb, 0x16, 0x9c, 0x6f, 0x38, 0x9e, - 0x13, 0x1c, 0x6e, 0x3b, 0x41, 0x8b, 0x46, 0x6f, 0x04, 0x7e, 0xaf, 0xbb, 0x34, 0x71, 0x06, 0xd2, - 0x3c, 0x29, 0xa5, 0x39, 0xbf, 0x96, 0x66, 0x87, 0xfd, 0x12, 0x70, 0xb9, 0xc2, 0xc8, 0xd9, 0x69, - 0x53, 0x53, 0xae, 0xc2, 0x59, 0xca, 0x55, 0x4f, 0xb3, 0xc3, 0x7e, 0x09, 0xec, 0x0f, 0x0a, 0x70, - 0xbe, 0xb2, 0x59, 0xdd, 0x0e, 0x9c, 0xdd, 0x5d, 0xb7, 0x81, 0x7e, 0x2f, 0x72, 0xbd, 0x16, 0xf9, - 0x18, 0x4c, 0xbb, 0x5e, 0x2b, 0xa0, 0x61, 0xc8, 0x3b, 0xb2, 0x54, 0x5d, 0x90, 0x44, 0xa7, 0x37, - 0x44, 0x31, 0x2a, 0x38, 0x79, 0x05, 0x66, 0x43, 0x1a, 0x1c, 0xb8, 0x0d, 0x5a, 0xf3, 0x83, 0x88, - 0xb7, 0x74, 0xb1, 0x7a, 0x41, 0xa2, 0xcf, 0xd6, 0x63, 0x10, 0x9a, 0x78, 0xac, 0x5a, 0xe0, 0xfb, - 0x91, 0x84, 0xf3, 0x86, 0x28, 0xc5, 0xd5, 0x30, 0x06, 0xa1, 0x89, 0x47, 0xbe, 0x65, 0xc1, 0x62, - 0x18, 0xb9, 0x8d, 0x7d, 0xd7, 0xa3, 0x61, 0xb8, 0xe6, 0x7b, 0xbb, 0x6e, 0x6b, 0xa9, 0xc8, 0x5b, - 0xf1, 0xd6, 0x78, 0xad, 0x58, 0x4f, 0x51, 0xad, 0x5e, 0x3c, 0x3e, 0x2a, 0x2f, 0xa6, 0x4b, 0xb1, - 0x8f, 0x3b, 0x59, 0x87, 0x45, 0xc7, 0xf3, 0xfc, 0xc8, 0x89, 0x5c, 0xdf, 0xab, 0x05, 0x74, 0xd7, - 0x7d, 0xb0, 0x34, 0xc9, 0x3f, 0x67, 0x49, 0x7e, 0xce, 0x62, 0x25, 0x05, 0xc7, 0xbe, 0x1a, 0xf6, - 0x3a, 0x2c, 0x55, 0x3a, 0x3b, 0x4e, 0x18, 0x3a, 0x4d, 0x3f, 0x48, 0xf5, 0xc6, 0xf3, 0x30, 0xd3, - 0x71, 0xba, 0x5d, 0xd7, 0x6b, 0xb1, 0xee, 0x28, 0x3c, 0x5f, 0xaa, 0xce, 0x1d, 0x1f, 0x95, 0x67, - 0xb6, 0x64, 0x19, 0x6a, 0xa8, 0xfd, 0x27, 0x13, 0x30, 0x5b, 0xf1, 0x9c, 0xf6, 0x61, 0xe8, 0x86, - 0xd8, 0xf3, 0xc8, 0x17, 0x61, 0x86, 0xad, 0x2e, 0x4d, 0x27, 0x72, 0xe4, 0x8c, 0xfc, 0xc4, 0x8a, - 0x98, 0xec, 0x2b, 0xe6, 0x64, 0x8f, 0xdb, 0x85, 0x61, 0xaf, 0x1c, 0x7c, 0x72, 0xe5, 0xad, 0x9d, - 0x7b, 0xb4, 0x11, 0x6d, 0xd1, 0xc8, 0xa9, 0x12, 0xf9, 0x15, 0x10, 0x97, 0xa1, 0xa6, 0x4a, 0x7c, - 0x98, 0x0c, 0xbb, 0xb4, 0x21, 0x67, 0xd8, 0xd6, 0x98, 0x23, 0x39, 0x16, 0xbd, 0xde, 0xa5, 0x8d, - 0xea, 0x9c, 0x64, 0x3d, 0xc9, 0xfe, 0x21, 0x67, 0x44, 0xee, 0xc3, 0x54, 0xc8, 0xd7, 0x1c, 0x39, - 0x79, 0xde, 0xca, 0x8f, 0x25, 0x27, 0x5b, 0x9d, 0x97, 0x4c, 0xa7, 0xc4, 0x7f, 0x94, 0xec, 0xec, - 0xff, 0x64, 0xc1, 0x05, 0x03, 0xbb, 0x12, 0xb4, 0x7a, 0x1d, 0xea, 0x45, 0xe4, 0x19, 0x98, 0xf4, - 0x9c, 0x0e, 0x95, 0x13, 0x45, 0x8b, 0x7c, 0xcb, 0xe9, 0x50, 0xe4, 0x10, 0xf2, 0x2c, 0x14, 0x0f, - 0x9c, 0x76, 0x8f, 0xf2, 0x46, 0x2a, 0x55, 0xcf, 0x49, 0x94, 0xe2, 0xdb, 0xac, 0x10, 0x05, 0x8c, - 0xbc, 0x07, 0x25, 0xfe, 0xe3, 0x7a, 0xe0, 0x77, 0x72, 0xfa, 0x34, 0x29, 0xe1, 0xdb, 0x8a, 0x6c, - 0xf5, 0xdc, 0xf1, 0x51, 0xb9, 0xa4, 0xff, 0x62, 0xcc, 0xd0, 0xfe, 0x33, 0x0b, 0x16, 0x8c, 0x8f, - 0xdb, 0x74, 0xc3, 0x88, 0x7c, 0xa1, 0x6f, 0xf0, 0xac, 0x0c, 0x37, 0x78, 0x58, 0x6d, 0x3e, 0x74, - 0x16, 0xe5, 0x97, 0xce, 0xa8, 0x12, 0x63, 0xe0, 0x78, 0x50, 0x74, 0x23, 0xda, 0x09, 0x97, 0x26, - 0x9e, 0x29, 0x3c, 0x3f, 0xfb, 0xe2, 0x46, 0x6e, 0xdd, 0x18, 0xb7, 0xef, 0x06, 0xa3, 0x8f, 0x82, - 0x8d, 0xfd, 0xaf, 0x0a, 0x89, 0xee, 0xdb, 0x52, 0x72, 0x7c, 0x60, 0xc1, 0x54, 0xdb, 0xd9, 0xa1, - 0x6d, 0x31, 0xb7, 0x66, 0x5f, 0x7c, 0x27, 0x37, 0x49, 0x14, 0x8f, 0x95, 0x4d, 0x4e, 0xff, 0x9a, - 0x17, 0x05, 0x87, 0xf1, 0xf0, 0x12, 0x85, 0x28, 0x99, 0x93, 0x7f, 0x6c, 0xc1, 0x6c, 0xbc, 0x2a, - 0xa8, 0x66, 0xd9, 0xc9, 0x5f, 0x98, 0x78, 0x31, 0x92, 0x12, 0xe9, 0x55, 0xd7, 0x80, 0xa0, 0x29, - 0xcb, 0xf2, 0xa7, 0x60, 0xd6, 0xf8, 0x04, 0xb2, 0x08, 0x85, 0x7d, 0x7a, 0x28, 0x06, 0x3c, 0xb2, - 0x9f, 0xe4, 0x62, 0x62, 0x84, 0xcb, 0x21, 0xfd, 0xe9, 0x89, 0x57, 0xad, 0xe5, 0xd7, 0x61, 0x31, - 0xcd, 0x70, 0x94, 0xfa, 0xf6, 0x6f, 0x4e, 0x26, 0x06, 0x26, 0x5b, 0x08, 0x88, 0x0f, 0xd3, 0x1d, - 0x1a, 0x05, 0x6e, 0x43, 0x75, 0xd9, 0xfa, 0x78, 0xad, 0xb4, 0xc5, 0x89, 0xc5, 0x7b, 0x9c, 0xf8, - 0x1f, 0xa2, 0xe2, 0x42, 0xf6, 0x60, 0xd2, 0x09, 0x5a, 0xaa, 0x4f, 0xae, 0xe7, 0x33, 0x2d, 0xe3, - 0xa5, 0xa2, 0x12, 0xb4, 0x42, 0xe4, 0x1c, 0xc8, 0x2a, 0x94, 0x22, 0x1a, 0x74, 0x5c, 0xcf, 0x89, - 0xc4, 0xa6, 0x38, 0x53, 0x3d, 0x2f, 0xd1, 0x4a, 0xdb, 0x0a, 0x80, 0x31, 0x0e, 0x69, 0xc3, 0x54, - 0x33, 0x38, 0xc4, 0x9e, 0xb7, 0x34, 0x99, 0x47, 0x53, 0xac, 0x73, 0x5a, 0xf1, 0x20, 0x15, 0xff, - 0x51, 0xf2, 0x20, 0xbf, 0x62, 0xc1, 0xc5, 0x0e, 0x75, 0xc2, 0x5e, 0x40, 0xd9, 0x27, 0x20, 0x8d, - 0xa8, 0xc7, 0x3a, 0x76, 0xa9, 0xc8, 0x99, 0xe3, 0xb8, 0xfd, 0xd0, 0x4f, 0xb9, 0xfa, 0xb4, 0x14, - 0xe5, 0x62, 0x16, 0x14, 0x33, 0xa5, 0xb1, 0xff, 0x64, 0x12, 0xce, 0xf7, 0x2d, 0xec, 0xe4, 0x65, - 0x28, 0x76, 0xf7, 0x9c, 0x50, 0xad, 0xd4, 0x57, 0xd5, 0x32, 0x51, 0x63, 0x85, 0x0f, 0x8f, 0xca, - 0xe7, 0x54, 0x15, 0x5e, 0x80, 0x02, 0x99, 0xa9, 0x42, 0x1d, 0x1a, 0x86, 0x4e, 0x4b, 0x2d, 0xdf, - 0xc6, 0x30, 0xe1, 0xc5, 0xa8, 0xe0, 0xe4, 0xef, 0x59, 0x70, 0x4e, 0x0c, 0x19, 0xa4, 0x61, 0xaf, - 0x1d, 0xb1, 0x2d, 0x8a, 0x35, 0xcb, 0xcd, 0x3c, 0x86, 0xa7, 0x20, 0x59, 0xbd, 0x24, 0xb9, 0x9f, - 0x33, 0x4b, 0x43, 0x4c, 0xf2, 0x25, 0x77, 0xa1, 0x14, 0x46, 0x4e, 0x10, 0xd1, 0x66, 0x25, 0xe2, - 0xca, 0xc8, 0xec, 0x8b, 0x3f, 0x3e, 0xdc, 0xda, 0xbd, 0xed, 0x76, 0xa8, 0xd8, 0x27, 0xea, 0x8a, - 0x00, 0xc6, 0xb4, 0xc8, 0x7b, 0x00, 0x41, 0xcf, 0xab, 0xf7, 0x3a, 0x1d, 0x27, 0x38, 0x94, 0x8a, - 0xd7, 0x8d, 0xf1, 0x3e, 0x0f, 0x35, 0xbd, 0x58, 0xd5, 0x88, 0xcb, 0xd0, 0xe0, 0x47, 0xbe, 0x62, - 0xc1, 0x39, 0x31, 0x12, 0x95, 0x04, 0x53, 0x39, 0x4b, 0x70, 0x9e, 0x35, 0xed, 0xba, 0xc9, 0x02, - 0x93, 0x1c, 0xed, 0xff, 0x90, 0x54, 0x03, 0xea, 0x11, 0x3b, 0x14, 0xb5, 0x0e, 0xc9, 0xe7, 0xe1, - 0xc9, 0xb0, 0xd7, 0x68, 0xd0, 0x30, 0xdc, 0xed, 0xb5, 0xb1, 0xe7, 0xdd, 0x70, 0xc3, 0xc8, 0x0f, - 0x0e, 0x37, 0xdd, 0x8e, 0x1b, 0xf1, 0x11, 0x57, 0xac, 0x5e, 0x39, 0x3e, 0x2a, 0x3f, 0x59, 0x1f, - 0x84, 0x84, 0x83, 0xeb, 0x13, 0x07, 0x9e, 0xea, 0x79, 0x83, 0xc9, 0x0b, 0xa5, 0xbb, 0x7c, 0x7c, - 0x54, 0x7e, 0xea, 0xce, 0x60, 0x34, 0x3c, 0x89, 0x86, 0xfd, 0xdf, 0x2c, 0xb6, 0x52, 0x8b, 0xef, - 0xda, 0xa6, 0x9d, 0x6e, 0x9b, 0xad, 0x2e, 0x67, 0xaf, 0x3f, 0x46, 0x09, 0xfd, 0x11, 0xf3, 0xd9, - 0xee, 0x94, 0xfc, 0x83, 0x94, 0x48, 0xfb, 0xbf, 0x5a, 0x70, 0x31, 0x8d, 0xfc, 0x18, 0x74, 0x9e, - 0x30, 0xa9, 0xf3, 0xdc, 0xca, 0xf7, 0x6b, 0x07, 0x28, 0x3e, 0x5f, 0x9b, 0xec, 0xff, 0xd6, 0xff, - 0xd7, 0xb7, 0xd1, 0x78, 0x57, 0x2c, 0xfc, 0x28, 0x77, 0xc5, 0xc9, 0x0f, 0xd5, 0xae, 0xf8, 0x6b, - 0x93, 0x30, 0x57, 0xf1, 0x22, 0xb7, 0xb2, 0xbb, 0xeb, 0x7a, 0x6e, 0x74, 0x48, 0xbe, 0x31, 0x01, - 0xab, 0xdd, 0x80, 0xee, 0xd2, 0x20, 0xa0, 0xcd, 0xf5, 0x5e, 0xe0, 0x7a, 0xad, 0x7a, 0x63, 0x8f, - 0x36, 0x7b, 0x6d, 0xd7, 0x6b, 0x6d, 0xb4, 0x3c, 0x5f, 0x17, 0x5f, 0x7b, 0x40, 0x1b, 0x3d, 0xfe, - 0x49, 0x62, 0x52, 0x74, 0xc6, 0xfb, 0xa4, 0xda, 0x68, 0x4c, 0xab, 0x2f, 0x1d, 0x1f, 0x95, 0x57, - 0x47, 0xac, 0x84, 0xa3, 0x7e, 0x1a, 0xf9, 0xfa, 0x04, 0xac, 0x04, 0xf4, 0x67, 0x7a, 0xee, 0xf0, - 0xad, 0x21, 0x56, 0xad, 0xf6, 0x98, 0xdb, 0xcf, 0x48, 0x3c, 0xab, 0x2f, 0x1e, 0x1f, 0x95, 0x47, - 0xac, 0x83, 0x23, 0x7e, 0x97, 0x5d, 0x83, 0xd9, 0x4a, 0xd7, 0x0d, 0xdd, 0x07, 0xe8, 0xf7, 0x22, - 0x3a, 0xc4, 0x11, 0xb7, 0x0c, 0xc5, 0xa0, 0xd7, 0xa6, 0x62, 0x6e, 0x97, 0xaa, 0x25, 0xb6, 0x0a, - 0x21, 0x2b, 0x40, 0x51, 0x6e, 0xff, 0x1c, 0x5b, 0x71, 0x39, 0xc9, 0x94, 0x71, 0xe3, 0x1e, 0x14, - 0x03, 0xc6, 0x44, 0x8e, 0xac, 0x71, 0xcf, 0x81, 0xb1, 0xd4, 0x52, 0x08, 0xf6, 0x13, 0x05, 0x0b, - 0xfb, 0x77, 0x26, 0xe0, 0x52, 0xa5, 0xdb, 0xdd, 0xa2, 0xe1, 0x5e, 0x4a, 0x8a, 0x5f, 0xb0, 0x60, - 0xfe, 0xc0, 0x0d, 0xa2, 0x9e, 0xd3, 0x56, 0x26, 0x29, 0x21, 0x4f, 0x7d, 0x5c, 0x79, 0x38, 0xb7, - 0xb7, 0x13, 0xa4, 0xab, 0xe4, 0xf8, 0xa8, 0x3c, 0x9f, 0x2c, 0xc3, 0x14, 0x7b, 0xf2, 0x8f, 0x2c, - 0x58, 0x94, 0x45, 0xb7, 0xfc, 0x26, 0x35, 0xed, 0x98, 0x77, 0xf2, 0x94, 0x49, 0x13, 0x17, 0x06, - 0xaf, 0x74, 0x29, 0xf6, 0x09, 0x61, 0xff, 0x8f, 0x09, 0xb8, 0x3c, 0x80, 0x06, 0xf9, 0x55, 0x0b, - 0x2e, 0x0a, 0xe3, 0xa7, 0x01, 0x42, 0xba, 0x2b, 0x5b, 0xf3, 0x27, 0xf3, 0x96, 0x1c, 0xd9, 0x14, - 0xa7, 0x5e, 0x83, 0x56, 0x97, 0xd8, 0x6a, 0xb8, 0x96, 0xc1, 0x1a, 0x33, 0x05, 0xe2, 0x92, 0x0a, - 0x73, 0x68, 0x4a, 0xd2, 0x89, 0xc7, 0x22, 0x69, 0x3d, 0x83, 0x35, 0x66, 0x0a, 0x64, 0xff, 0x1d, - 0x78, 0xea, 0x04, 0x72, 0x8f, 0x9e, 0x9c, 0xf6, 0x3b, 0x7a, 0xd4, 0x27, 0xc7, 0xdc, 0x10, 0xf3, - 0xda, 0x86, 0x29, 0x3e, 0x75, 0xd4, 0xc4, 0x06, 0xb6, 0xfd, 0xf1, 0x39, 0x15, 0xa2, 0x84, 0xd8, - 0xbf, 0x63, 0xc1, 0xcc, 0x08, 0xd6, 0xb0, 0x72, 0xd2, 0x1a, 0x56, 0xea, 0xb3, 0x84, 0x45, 0xfd, - 0x96, 0xb0, 0x37, 0xc6, 0xeb, 0x8d, 0x61, 0x2c, 0x60, 0x7f, 0x6e, 0xc1, 0xf9, 0x3e, 0x8b, 0x19, - 0xd9, 0x83, 0x8b, 0x5d, 0xbf, 0xa9, 0xd4, 0xa6, 0x1b, 0x4e, 0xb8, 0xc7, 0x61, 0xf2, 0xf3, 0x5e, - 0x66, 0x3d, 0x59, 0xcb, 0x80, 0x3f, 0x3c, 0x2a, 0x2f, 0x69, 0x22, 0x29, 0x04, 0xcc, 0xa4, 0x48, - 0xba, 0x30, 0xb3, 0xeb, 0xd2, 0x76, 0x33, 0x1e, 0x82, 0x63, 0x2a, 0x48, 0xd7, 0x25, 0x35, 0x61, - 0x2c, 0x56, 0xff, 0x50, 0x73, 0xb1, 0xbf, 0x0c, 0xf3, 0xc9, 0xab, 0x83, 0x21, 0x3a, 0xef, 0x0a, - 0x14, 0x9c, 0xc0, 0x93, 0x5d, 0x37, 0x2b, 0x11, 0x0a, 0x15, 0xbc, 0x85, 0xac, 0x9c, 0xbc, 0x00, - 0x33, 0xbb, 0xbd, 0x76, 0x9b, 0x55, 0x90, 0x26, 0x7d, 0xad, 0x0e, 0x5f, 0x97, 0xe5, 0xa8, 0x31, - 0xec, 0xbf, 0x9a, 0x84, 0x85, 0x6a, 0xbb, 0x47, 0xdf, 0x08, 0x28, 0x55, 0x87, 0xf4, 0x0a, 0x2c, - 0x74, 0x03, 0x7a, 0xe0, 0xd2, 0xfb, 0x75, 0xda, 0xa6, 0x8d, 0xc8, 0x0f, 0xa4, 0x34, 0x97, 0x25, - 0xa1, 0x85, 0x5a, 0x12, 0x8c, 0x69, 0x7c, 0xf2, 0x3a, 0xcc, 0x3b, 0x8d, 0xc8, 0x3d, 0xa0, 0x9a, - 0x82, 0x10, 0xf7, 0x23, 0x92, 0xc2, 0x7c, 0x25, 0x01, 0xc5, 0x14, 0x36, 0xf9, 0x02, 0x2c, 0x85, - 0x0d, 0xa7, 0x4d, 0xef, 0x74, 0x25, 0xab, 0xb5, 0x3d, 0xda, 0xd8, 0xaf, 0xf9, 0xae, 0x17, 0x49, - 0x93, 0xcc, 0x33, 0x92, 0xd2, 0x52, 0x7d, 0x00, 0x1e, 0x0e, 0xa4, 0x40, 0xfe, 0xad, 0x05, 0x57, - 0xba, 0x01, 0xad, 0x05, 0x7e, 0xc7, 0x67, 0x7b, 0x6d, 0x9f, 0x9d, 0x42, 0x9e, 0xd7, 0xdf, 0x1e, - 0x53, 0xa9, 0x10, 0x25, 0xfd, 0xe6, 0xed, 0x8f, 0x1e, 0x1f, 0x95, 0xaf, 0xd4, 0x4e, 0x12, 0x00, - 0x4f, 0x96, 0x8f, 0xfc, 0x3b, 0x0b, 0xae, 0x76, 0xfd, 0x30, 0x3a, 0xe1, 0x13, 0x8a, 0x67, 0xfa, - 0x09, 0xf6, 0xf1, 0x51, 0xf9, 0x6a, 0xed, 0x44, 0x09, 0xf0, 0x11, 0x12, 0xda, 0xc7, 0xb3, 0x70, - 0xde, 0x18, 0x7b, 0xf2, 0x10, 0xff, 0x1a, 0x9c, 0x53, 0x83, 0x21, 0x56, 0x02, 0x4a, 0xb1, 0xd1, - 0xa5, 0x62, 0x02, 0x31, 0x89, 0xcb, 0xc6, 0x9d, 0x1e, 0x8a, 0xa2, 0x76, 0x6a, 0xdc, 0xd5, 0x12, - 0x50, 0x4c, 0x61, 0x93, 0x0d, 0xb8, 0x20, 0x4b, 0x90, 0x76, 0xdb, 0x6e, 0xc3, 0x59, 0xf3, 0x7b, - 0x72, 0xc8, 0x15, 0xab, 0x97, 0x8f, 0x8f, 0xca, 0x17, 0x6a, 0xfd, 0x60, 0xcc, 0xaa, 0x43, 0x36, - 0xe1, 0xa2, 0xd3, 0x8b, 0x7c, 0xfd, 0xfd, 0xd7, 0x3c, 0xb6, 0xaf, 0x34, 0xf9, 0xd0, 0x9a, 0x11, - 0x1b, 0x50, 0x25, 0x03, 0x8e, 0x99, 0xb5, 0x48, 0x2d, 0x45, 0xad, 0x4e, 0x1b, 0xbe, 0xd7, 0x14, - 0xbd, 0x5c, 0x8c, 0x8f, 0x22, 0x95, 0x0c, 0x1c, 0xcc, 0xac, 0x49, 0xda, 0x30, 0xdf, 0x71, 0x1e, - 0xdc, 0xf1, 0x9c, 0x03, 0xc7, 0x6d, 0x33, 0x26, 0xd2, 0x90, 0x33, 0xd8, 0xba, 0xd0, 0x8b, 0xdc, - 0xf6, 0x8a, 0xb8, 0x8a, 0x5e, 0xd9, 0xf0, 0xa2, 0xb7, 0x82, 0x7a, 0xc4, 0x54, 0x56, 0xa1, 0x4a, - 0x6d, 0x25, 0x68, 0x61, 0x8a, 0x36, 0x79, 0x0b, 0x2e, 0xf1, 0xe9, 0xb8, 0xee, 0xdf, 0xf7, 0xd6, - 0x69, 0xdb, 0x39, 0x54, 0x1f, 0x30, 0xcd, 0x3f, 0xe0, 0xc9, 0xe3, 0xa3, 0xf2, 0xa5, 0x7a, 0x16, - 0x02, 0x66, 0xd7, 0x23, 0x0e, 0x3c, 0x95, 0x04, 0x20, 0x3d, 0x70, 0x43, 0xd7, 0xf7, 0x84, 0x39, - 0x66, 0x26, 0x36, 0xc7, 0xd4, 0x07, 0xa3, 0xe1, 0x49, 0x34, 0xc8, 0x3f, 0xb1, 0xe0, 0x62, 0xd6, - 0x34, 0x5c, 0x2a, 0xe5, 0x71, 0xd1, 0x96, 0x9a, 0x5a, 0x62, 0x44, 0x64, 0x2e, 0x0a, 0x99, 0x42, - 0x90, 0xf7, 0x2d, 0x98, 0x73, 0x8c, 0xa3, 0xe4, 0x12, 0x70, 0xa9, 0x6e, 0x8e, 0x6b, 0xd0, 0x88, - 0x29, 0x56, 0x17, 0x8f, 0x8f, 0xca, 0x89, 0xe3, 0x2a, 0x26, 0x38, 0x92, 0x7f, 0x66, 0xc1, 0xa5, - 0xcc, 0x39, 0xbe, 0x34, 0x7b, 0x16, 0x2d, 0xc4, 0x07, 0x49, 0xf6, 0x9a, 0x93, 0x2d, 0x06, 0xf9, - 0x96, 0xa5, 0xb7, 0x32, 0x75, 0xf7, 0xb2, 0x34, 0xc7, 0x45, 0xbb, 0x3d, 0xe6, 0xe9, 0x39, 0x56, - 0x1f, 0x14, 0xe1, 0xea, 0x05, 0x63, 0x67, 0x54, 0x85, 0x98, 0x66, 0x4f, 0xbe, 0x69, 0xa9, 0xad, - 0x51, 0x4b, 0x74, 0xee, 0xac, 0x24, 0x22, 0xf1, 0x4e, 0xab, 0x05, 0x4a, 0x31, 0x27, 0x3f, 0x05, - 0xcb, 0xce, 0x8e, 0x1f, 0x44, 0x99, 0x93, 0x6f, 0x69, 0x9e, 0x4f, 0xa3, 0xab, 0xc7, 0x47, 0xe5, - 0xe5, 0xca, 0x40, 0x2c, 0x3c, 0x81, 0x82, 0xfd, 0x1b, 0x45, 0x98, 0x13, 0x47, 0x02, 0xb9, 0x75, - 0xfd, 0x96, 0x05, 0x4f, 0x37, 0x7a, 0x41, 0x40, 0xbd, 0xa8, 0x1e, 0xd1, 0x6e, 0xff, 0xc6, 0x65, - 0x9d, 0xe9, 0xc6, 0xf5, 0xcc, 0xf1, 0x51, 0xf9, 0xe9, 0xb5, 0x13, 0xf8, 0xe3, 0x89, 0xd2, 0x91, - 0x7f, 0x6f, 0x81, 0x2d, 0x11, 0xaa, 0x4e, 0x63, 0xbf, 0x15, 0xf8, 0x3d, 0xaf, 0xd9, 0xff, 0x11, - 0x13, 0x67, 0xfa, 0x11, 0xcf, 0x1d, 0x1f, 0x95, 0xed, 0xb5, 0x47, 0x4a, 0x81, 0x43, 0x48, 0x4a, - 0xde, 0x80, 0xf3, 0x12, 0xeb, 0xda, 0x83, 0x2e, 0x0d, 0x5c, 0xa6, 0x7c, 0x4b, 0xc5, 0x31, 0x76, - 0xaf, 0x49, 0x23, 0x60, 0x7f, 0x1d, 0x12, 0xc2, 0xf4, 0x7d, 0xea, 0xb6, 0xf6, 0x22, 0xa5, 0x3e, - 0x8d, 0xe9, 0x53, 0x23, 0xcd, 0x03, 0x77, 0x05, 0xcd, 0xea, 0xec, 0xf1, 0x51, 0x79, 0x5a, 0xfe, - 0x41, 0xc5, 0x89, 0xdc, 0x82, 0x79, 0x71, 0x60, 0xab, 0xb9, 0x5e, 0xab, 0xe6, 0x7b, 0xc2, 0x13, - 0xa5, 0x54, 0x7d, 0x4e, 0x6d, 0xf8, 0xf5, 0x04, 0xf4, 0xe1, 0x51, 0x79, 0x4e, 0xfd, 0xde, 0x3e, - 0xec, 0x52, 0x4c, 0xd5, 0xb6, 0x7f, 0x7f, 0x0a, 0x40, 0x0d, 0x57, 0xda, 0x25, 0x3f, 0x01, 0xa5, - 0x90, 0x46, 0x82, 0xab, 0xbc, 0x41, 0x10, 0x17, 0x33, 0xaa, 0x10, 0x63, 0x38, 0xd9, 0x87, 0x62, - 0xd7, 0xe9, 0x85, 0x54, 0x76, 0xfe, 0xcd, 0x5c, 0x3a, 0xbf, 0xc6, 0x28, 0x8a, 0x13, 0x1a, 0xff, - 0x89, 0x82, 0x07, 0xf9, 0xaa, 0x05, 0x40, 0x93, 0x1d, 0x36, 0xb6, 0xa5, 0x44, 0xb2, 0x8c, 0xfb, - 0x94, 0xb5, 0x41, 0x75, 0xfe, 0xf8, 0xa8, 0x0c, 0x46, 0xd7, 0x1b, 0x6c, 0xc9, 0x7d, 0x98, 0x71, - 0xd4, 0x9a, 0x3f, 0x79, 0x16, 0x6b, 0x3e, 0x3f, 0x38, 0xe9, 0x41, 0xab, 0x99, 0x91, 0xaf, 0x5b, - 0x30, 0x1f, 0xd2, 0x48, 0x76, 0x15, 0x5b, 0x79, 0xa4, 0xc2, 0x3b, 0xe6, 0xa0, 0xab, 0x27, 0x68, - 0x8a, 0x15, 0x34, 0x59, 0x86, 0x29, 0xbe, 0x4a, 0x94, 0x1b, 0xd4, 0x69, 0xd2, 0x80, 0x9f, 0xcb, - 0xa5, 0x26, 0x35, 0xbe, 0x28, 0x06, 0x4d, 0x2d, 0x8a, 0x51, 0x86, 0x29, 0xbe, 0x4a, 0x94, 0x2d, - 0x37, 0x08, 0x7c, 0x29, 0xca, 0x4c, 0x4e, 0xa2, 0x18, 0x34, 0xb5, 0x28, 0x46, 0x19, 0xa6, 0xf8, - 0xda, 0xdf, 0x39, 0x07, 0xf3, 0x6a, 0x22, 0xc5, 0x9a, 0xbd, 0x30, 0x03, 0x0d, 0xd0, 0xec, 0xd7, - 0x4c, 0x20, 0x26, 0x71, 0x59, 0x65, 0x31, 0x55, 0x93, 0x8a, 0xbd, 0xae, 0x5c, 0x37, 0x81, 0x98, - 0xc4, 0x25, 0x1d, 0x28, 0x86, 0x11, 0xed, 0xaa, 0xcb, 0xe0, 0x31, 0xef, 0x2a, 0xe3, 0xf5, 0x21, - 0xbe, 0xee, 0x61, 0xff, 0x42, 0x14, 0x5c, 0xb8, 0x25, 0x33, 0x4a, 0x18, 0x37, 0xe5, 0xe4, 0xc8, - 0x67, 0x7e, 0x26, 0xed, 0xa6, 0xa2, 0x37, 0x92, 0x65, 0x98, 0x62, 0x9f, 0xa1, 0xec, 0x17, 0xcf, - 0x50, 0xd9, 0xff, 0x1c, 0xcc, 0x74, 0x9c, 0x07, 0xf5, 0x5e, 0xd0, 0x3a, 0xfd, 0xa1, 0x42, 0xba, - 0xd7, 0x09, 0x2a, 0xa8, 0xe9, 0x91, 0xaf, 0x58, 0xc6, 0x92, 0x33, 0xcd, 0x89, 0xdf, 0xcd, 0x77, - 0xc9, 0xd1, 0x7b, 0xe5, 0xc0, 0xc5, 0xa7, 0x4f, 0xf5, 0x9e, 0x79, 0xec, 0xaa, 0x37, 0x53, 0x23, - 0xc5, 0x04, 0xd1, 0x6a, 0x64, 0xe9, 0x4c, 0xd5, 0xc8, 0xb5, 0x04, 0x33, 0x4c, 0x31, 0xe7, 0xf2, - 0x88, 0x39, 0xa7, 0xe5, 0x81, 0x33, 0x95, 0xa7, 0x9e, 0x60, 0x86, 0x29, 0xe6, 0x83, 0xcf, 0x9b, - 0xb3, 0x67, 0x73, 0xde, 0x9c, 0xcb, 0xe1, 0xbc, 0x79, 0xb2, 0x2a, 0x7e, 0x6e, 0x5c, 0x55, 0x9c, - 0xdc, 0x04, 0xd2, 0x3c, 0xf4, 0x9c, 0x8e, 0xdb, 0x90, 0x8b, 0x25, 0xdf, 0x36, 0xe7, 0xb9, 0x3d, - 0x62, 0x59, 0x2e, 0x64, 0x64, 0xbd, 0x0f, 0x03, 0x33, 0x6a, 0x91, 0x08, 0x66, 0xba, 0x4a, 0xe3, - 0x5a, 0xc8, 0x63, 0xf4, 0x2b, 0x0d, 0x4c, 0xf8, 0x0b, 0xb0, 0x89, 0xa7, 0x4a, 0x50, 0x73, 0x22, - 0x9b, 0x70, 0xb1, 0xe3, 0x7a, 0x35, 0xbf, 0x19, 0xd6, 0x68, 0x20, 0xad, 0x2d, 0x75, 0x1a, 0x2d, - 0x2d, 0xf2, 0xb6, 0xe1, 0x27, 0xe8, 0xad, 0x0c, 0x38, 0x66, 0xd6, 0xb2, 0xff, 0xa7, 0x05, 0x8b, - 0x6b, 0x6d, 0xbf, 0xd7, 0xbc, 0xeb, 0x44, 0x8d, 0x3d, 0x71, 0x55, 0x4e, 0x5e, 0x87, 0x19, 0xd7, - 0x8b, 0x68, 0x70, 0xe0, 0xb4, 0xe5, 0xfe, 0x64, 0x2b, 0xf3, 0xe9, 0x86, 0x2c, 0x7f, 0x78, 0x54, - 0x9e, 0x5f, 0xef, 0x05, 0xdc, 0x9d, 0x4e, 0xac, 0x56, 0xa8, 0xeb, 0x90, 0xef, 0x58, 0x70, 0x5e, - 0x5c, 0xb6, 0xaf, 0x3b, 0x91, 0x73, 0xbb, 0x47, 0x03, 0x97, 0xaa, 0xeb, 0xf6, 0x31, 0x17, 0xaa, - 0xb4, 0xac, 0x8a, 0xc1, 0x61, 0xac, 0xa8, 0x6f, 0xa5, 0x39, 0x63, 0xbf, 0x30, 0xf6, 0x2f, 0x16, - 0xe0, 0xc9, 0x81, 0xb4, 0xc8, 0x32, 0x4c, 0xb8, 0x4d, 0xf9, 0xe9, 0x20, 0xe9, 0x4e, 0x6c, 0x34, - 0x71, 0xc2, 0x6d, 0x92, 0x15, 0xae, 0x73, 0x06, 0x34, 0x0c, 0xd5, 0xcd, 0x6b, 0x49, 0xab, 0x87, - 0xb2, 0x14, 0x0d, 0x0c, 0x52, 0x86, 0x22, 0x77, 0xad, 0x94, 0xe7, 0x09, 0xae, 0xc5, 0x72, 0x2f, - 0x46, 0x14, 0xe5, 0xe4, 0xe7, 0x2c, 0x00, 0x21, 0x20, 0x3b, 0x8d, 0xc8, 0x5d, 0x12, 0xf3, 0x6d, - 0x26, 0x46, 0x59, 0x48, 0x19, 0xff, 0x47, 0x83, 0x2b, 0xd9, 0x86, 0x29, 0xa6, 0xd0, 0xfa, 0xcd, - 0x53, 0x6f, 0x8a, 0xfc, 0x4a, 0xa6, 0xc6, 0x69, 0xa0, 0xa4, 0xc5, 0xda, 0x2a, 0xa0, 0x51, 0x2f, - 0xf0, 0x58, 0xd3, 0xf2, 0x6d, 0x70, 0x46, 0x48, 0x81, 0xba, 0x14, 0x0d, 0x0c, 0xfb, 0xdf, 0x4c, - 0xc0, 0xc5, 0x2c, 0xd1, 0xd9, 0x6e, 0x33, 0x25, 0xa4, 0x95, 0x47, 0xe3, 0xcf, 0xe6, 0xdf, 0x3e, - 0xd2, 0x6f, 0x44, 0x7b, 0x57, 0x48, 0xcf, 0x36, 0xc9, 0x97, 0x7c, 0x56, 0xb7, 0xd0, 0xc4, 0x29, - 0x5b, 0x48, 0x53, 0x4e, 0xb5, 0xd2, 0x33, 0x30, 0x19, 0xb2, 0x9e, 0x2f, 0x24, 0xaf, 0x3b, 0x78, - 0x1f, 0x71, 0x08, 0xc3, 0xe8, 0x79, 0x6e, 0x24, 0xfd, 0xf9, 0x35, 0xc6, 0x1d, 0xcf, 0x8d, 0x90, - 0x43, 0xec, 0x6f, 0x4f, 0xc0, 0xf2, 0xe0, 0x8f, 0x22, 0xdf, 0xb6, 0x00, 0x9a, 0xec, 0xb8, 0x12, - 0x72, 0xa7, 0x5e, 0xe1, 0x67, 0xe3, 0x9c, 0x55, 0x1b, 0xae, 0x2b, 0x4e, 0xb1, 0xd3, 0x95, 0x2e, - 0x0a, 0xd1, 0x10, 0x84, 0xbc, 0xa8, 0x86, 0x3e, 0xbf, 0xaa, 0x11, 0x93, 0x49, 0xd7, 0xd9, 0xd2, - 0x10, 0x34, 0xb0, 0xd8, 0x79, 0xd4, 0x73, 0x3a, 0x34, 0xec, 0x3a, 0x3a, 0x60, 0x83, 0x9f, 0x47, - 0x6f, 0xa9, 0x42, 0x8c, 0xe1, 0x76, 0x1b, 0x9e, 0x1d, 0x42, 0xce, 0x9c, 0x9c, 0xe7, 0xed, 0xbf, - 0xb0, 0xe0, 0xf2, 0x5a, 0xbb, 0x17, 0x46, 0x34, 0xf8, 0xff, 0xc6, 0x87, 0xed, 0x7f, 0x59, 0xf0, - 0xd4, 0x80, 0x6f, 0x7e, 0x0c, 0xae, 0x6c, 0xef, 0x26, 0x5d, 0xd9, 0xee, 0x8c, 0x3b, 0xa4, 0x33, - 0xbf, 0x63, 0x80, 0x47, 0xdb, 0xaf, 0x59, 0x70, 0x8e, 0x2d, 0x5b, 0x4d, 0xbf, 0x95, 0xd3, 0xc6, - 0xf9, 0x2c, 0x14, 0x7f, 0x86, 0x6d, 0x40, 0xe9, 0x41, 0xc6, 0x77, 0x25, 0x14, 0x30, 0x36, 0x67, - 0x9c, 0xae, 0xfb, 0x36, 0x0d, 0xf8, 0x06, 0x54, 0x48, 0xce, 0x99, 0x8a, 0x86, 0xa0, 0x81, 0x65, - 0x7f, 0x06, 0xa4, 0xb3, 0x58, 0x6a, 0xc6, 0x59, 0xc3, 0xcc, 0x38, 0xfb, 0x3f, 0x4e, 0x80, 0x61, - 0xfc, 0x78, 0x0c, 0x23, 0xd9, 0x4b, 0x8c, 0xe4, 0x31, 0x0f, 0xee, 0x86, 0x29, 0x67, 0x50, 0x30, - 0xcf, 0x41, 0x2a, 0x98, 0xe7, 0x56, 0x6e, 0x1c, 0x4f, 0x8e, 0xe5, 0xf9, 0xbe, 0x05, 0x4f, 0xc5, - 0xc8, 0xfd, 0x76, 0xc9, 0x47, 0x2f, 0x4b, 0xaf, 0xc0, 0xac, 0x13, 0x57, 0x93, 0xe3, 0xc6, 0x88, - 0xa4, 0xd0, 0x20, 0x34, 0xf1, 0x62, 0x1f, 0xf4, 0xc2, 0x29, 0x7d, 0xd0, 0x27, 0x4f, 0xf6, 0x41, - 0xb7, 0xff, 0x72, 0x02, 0xae, 0xf4, 0x7f, 0x99, 0x9a, 0x50, 0xc3, 0x5d, 0xf2, 0xbf, 0x0a, 0x73, - 0x91, 0xac, 0x60, 0x6c, 0x0f, 0x3a, 0xfa, 0x72, 0xdb, 0x80, 0x61, 0x02, 0x93, 0xd5, 0x6c, 0x88, - 0xa9, 0x5c, 0x6f, 0xf8, 0x5d, 0x15, 0xc1, 0xa0, 0x6b, 0xae, 0x19, 0x30, 0x4c, 0x60, 0x6a, 0xdf, - 0xd0, 0xc9, 0x33, 0xf7, 0x0d, 0xad, 0xc3, 0x25, 0xe5, 0x0d, 0x77, 0xdd, 0x0f, 0xd6, 0xfc, 0x4e, - 0xb7, 0x4d, 0x65, 0x0c, 0x03, 0x13, 0xf6, 0x8a, 0xac, 0x72, 0x09, 0xb3, 0x90, 0x30, 0xbb, 0xae, - 0xfd, 0xfd, 0x02, 0x5c, 0x88, 0x9b, 0x7d, 0xcd, 0xf7, 0x9a, 0x2e, 0xf7, 0x29, 0x7c, 0x0d, 0x26, - 0xa3, 0xc3, 0xae, 0x6a, 0xec, 0xbf, 0xa1, 0xc4, 0xd9, 0x3e, 0xec, 0xb2, 0xde, 0xbe, 0x9c, 0x51, - 0x85, 0x5b, 0x86, 0x79, 0x25, 0xb2, 0xa9, 0x67, 0x87, 0xe8, 0x81, 0x97, 0x93, 0xa3, 0xf9, 0xe1, - 0x51, 0x39, 0x23, 0xf8, 0x78, 0x45, 0x53, 0x4a, 0x8e, 0x79, 0x72, 0x0f, 0xe6, 0xdb, 0x4e, 0x18, - 0xdd, 0xe9, 0x36, 0x9d, 0x88, 0x6e, 0xbb, 0xd2, 0x43, 0x63, 0xb4, 0xc0, 0x00, 0x7d, 0x95, 0xbd, - 0x99, 0xa0, 0x84, 0x29, 0xca, 0xe4, 0x00, 0x08, 0x2b, 0xd9, 0x0e, 0x1c, 0x2f, 0x14, 0x5f, 0xc5, - 0xf8, 0x8d, 0x1e, 0x88, 0xa0, 0x4f, 0x86, 0x9b, 0x7d, 0xd4, 0x30, 0x83, 0x03, 0x79, 0x0e, 0xa6, - 0x02, 0xea, 0x84, 0xb2, 0x33, 0x4b, 0xf1, 0xfc, 0x47, 0x5e, 0x8a, 0x12, 0x6a, 0x4e, 0xa8, 0xa9, - 0x47, 0x4c, 0xa8, 0x3f, 0xb5, 0x60, 0x3e, 0xee, 0xa6, 0xc7, 0xb0, 0xb3, 0x76, 0x92, 0x3b, 0xeb, - 0x8d, 0xbc, 0x96, 0xc4, 0x01, 0x9b, 0xe9, 0x1f, 0x4c, 0x99, 0xdf, 0xc7, 0x1d, 0xc3, 0xbf, 0x04, - 0x25, 0x35, 0xab, 0x95, 0xca, 0x3a, 0xe6, 0x01, 0x3b, 0xa1, 0xcc, 0x18, 0x01, 0x4d, 0x92, 0x09, - 0xc6, 0xfc, 0xd8, 0x56, 0xde, 0x94, 0xdb, 0xb4, 0x1c, 0xf6, 0x7a, 0x2b, 0x57, 0xdb, 0x77, 0xd6, - 0x56, 0xae, 0xea, 0x90, 0x3b, 0x70, 0xb9, 0x1b, 0xf8, 0x3c, 0x36, 0x79, 0x9d, 0x3a, 0xcd, 0xb6, - 0xeb, 0x51, 0x65, 0xc5, 0x10, 0x9e, 0x14, 0x4f, 0x1d, 0x1f, 0x95, 0x2f, 0xd7, 0xb2, 0x51, 0x70, - 0x50, 0xdd, 0x64, 0x60, 0xd6, 0xe4, 0x10, 0x81, 0x59, 0x7f, 0x5f, 0xdb, 0x0a, 0x69, 0x28, 0xc3, - 0xa3, 0x3e, 0x9f, 0x57, 0x57, 0x66, 0x2c, 0xeb, 0xf1, 0x90, 0xaa, 0x48, 0xa6, 0xa8, 0xd9, 0x0f, - 0x36, 0x48, 0x4d, 0x9d, 0xd2, 0x20, 0x15, 0xfb, 0xd7, 0x4f, 0xff, 0x28, 0xfd, 0xeb, 0x67, 0x3e, - 0x54, 0xfe, 0xf5, 0x1f, 0x14, 0x61, 0x31, 0xad, 0x81, 0x9c, 0x7d, 0xd0, 0xd9, 0x3f, 0xb4, 0x60, - 0x51, 0xcd, 0x1e, 0xc1, 0x93, 0xaa, 0xab, 0x86, 0xcd, 0x9c, 0x26, 0xad, 0xd0, 0xa5, 0x74, 0x34, - 0xfb, 0x76, 0x8a, 0x1b, 0xf6, 0xf1, 0x27, 0xef, 0xc0, 0xac, 0xb6, 0xc8, 0x9f, 0x2a, 0x02, 0x6d, - 0x81, 0x6b, 0x51, 0x31, 0x09, 0x34, 0xe9, 0x91, 0x0f, 0x2c, 0x80, 0x86, 0xda, 0xe6, 0xd4, 0xec, - 0xba, 0x9d, 0xd7, 0xec, 0xd2, 0x1b, 0x68, 0xac, 0x2c, 0xeb, 0xa2, 0x10, 0x0d, 0xc6, 0xe4, 0x17, - 0xb9, 0x2d, 0x5e, 0x6b, 0x77, 0x6c, 0x3e, 0x15, 0xc6, 0xf7, 0x1d, 0x3e, 0x41, 0x31, 0x8d, 0x55, - 0x29, 0x03, 0x14, 0x62, 0x42, 0x08, 0xfb, 0x35, 0xd0, 0xde, 0x9e, 0x6c, 0xd9, 0xe2, 0xfe, 0x9e, - 0x35, 0x27, 0xda, 0x93, 0x43, 0x50, 0x2f, 0x5b, 0xd7, 0x15, 0x00, 0x63, 0x1c, 0xfb, 0x8b, 0x30, - 0xff, 0x46, 0xe0, 0x74, 0xf7, 0x5c, 0x6e, 0xf3, 0x66, 0x67, 0xab, 0x8f, 0xc1, 0xb4, 0xd3, 0x6c, - 0x66, 0xe5, 0x82, 0xa8, 0x88, 0x62, 0x54, 0xf0, 0xa1, 0x8e, 0x51, 0xf6, 0xef, 0x5b, 0x40, 0xe2, - 0x7b, 0x43, 0xd7, 0x6b, 0x6d, 0x39, 0x51, 0x63, 0x8f, 0x9d, 0x8f, 0xf6, 0x78, 0x69, 0xd6, 0xf9, - 0xe8, 0x86, 0x86, 0xa0, 0x81, 0x45, 0xde, 0x83, 0x59, 0xf1, 0xef, 0x6d, 0x6d, 0x21, 0x18, 0x3b, - 0x82, 0x40, 0x6c, 0x28, 0x5c, 0x26, 0x31, 0x0a, 0x6f, 0xc4, 0x1c, 0xd0, 0x64, 0xc7, 0x9a, 0x6a, - 0xc3, 0xdb, 0x6d, 0xf7, 0x1e, 0x34, 0x77, 0xe2, 0xa6, 0xea, 0x06, 0xfe, 0xae, 0xdb, 0xa6, 0xe9, - 0xa6, 0xaa, 0x89, 0x62, 0x54, 0xf0, 0xe1, 0x9a, 0xea, 0x77, 0x2d, 0xb8, 0xb8, 0x11, 0x46, 0xae, - 0xbf, 0x4e, 0xc3, 0x88, 0x6d, 0x2b, 0x6c, 0xf1, 0xe9, 0xb5, 0x87, 0x71, 0xdc, 0x5e, 0x87, 0x45, - 0x79, 0x87, 0xd9, 0xdb, 0x09, 0x69, 0x64, 0xe8, 0xf1, 0x7a, 0x1e, 0xaf, 0xa5, 0xe0, 0xd8, 0x57, - 0x83, 0x51, 0x91, 0x97, 0x99, 0x31, 0x95, 0x42, 0x92, 0x4a, 0x3d, 0x05, 0xc7, 0xbe, 0x1a, 0xf6, - 0xf7, 0x0a, 0x70, 0x81, 0x7f, 0x46, 0x2a, 0xe8, 0xe2, 0x9b, 0x83, 0x82, 0x2e, 0xc6, 0x9c, 0xca, - 0x9c, 0xd7, 0x29, 0x42, 0x2e, 0xfe, 0x81, 0x05, 0x0b, 0xcd, 0x64, 0x4b, 0xe7, 0x63, 0xd3, 0xc9, - 0xea, 0x43, 0xe1, 0xb2, 0x95, 0x2a, 0xc4, 0x34, 0x7f, 0xf2, 0x4b, 0x16, 0x2c, 0x24, 0xc5, 0x54, - 0xab, 0xfb, 0x19, 0x34, 0x92, 0xf6, 0xb1, 0x4e, 0x96, 0x87, 0x98, 0x16, 0xc1, 0xfe, 0xc3, 0x09, - 0xd9, 0xa5, 0x67, 0x11, 0x51, 0x40, 0xee, 0x43, 0x29, 0x6a, 0x87, 0xa2, 0x50, 0x7e, 0xed, 0x98, - 0x27, 0xc2, 0xed, 0xcd, 0xba, 0x70, 0x1f, 0x88, 0x95, 0x36, 0x59, 0xc2, 0x94, 0x4f, 0xc5, 0x8b, - 0x33, 0x6e, 0x74, 0x25, 0xe3, 0x5c, 0x8e, 0xa2, 0xdb, 0x6b, 0xb5, 0x34, 0x63, 0x59, 0xc2, 0x18, - 0x2b, 0x5e, 0xf6, 0xaf, 0x5b, 0x50, 0xba, 0xe9, 0xab, 0x75, 0xe4, 0xa7, 0x72, 0x30, 0xf4, 0x68, - 0x7d, 0x50, 0x5f, 0x53, 0xc6, 0x47, 0x8c, 0xd7, 0x13, 0x66, 0x9e, 0xa7, 0x0d, 0xda, 0x2b, 0x3c, - 0xcf, 0x15, 0x23, 0x75, 0xd3, 0xdf, 0x19, 0x68, 0x7a, 0xfc, 0xe5, 0x22, 0x9c, 0x7b, 0xd3, 0x39, - 0xa4, 0x5e, 0xe4, 0x8c, 0xbe, 0x49, 0xbc, 0x02, 0xb3, 0x4e, 0x97, 0xdf, 0x83, 0x19, 0x3a, 0x7e, - 0x6c, 0x39, 0x89, 0x41, 0x68, 0xe2, 0xc5, 0x0b, 0x9a, 0x48, 0xbb, 0x93, 0xb5, 0x14, 0xad, 0xa5, - 0xe0, 0xd8, 0x57, 0x83, 0xdc, 0x04, 0x22, 0xa3, 0x51, 0x2b, 0x8d, 0x86, 0xdf, 0xf3, 0xc4, 0x92, - 0x26, 0x8c, 0x2a, 0xfa, 0xb0, 0xb9, 0xd5, 0x87, 0x81, 0x19, 0xb5, 0xc8, 0x17, 0x60, 0xa9, 0xc1, - 0x29, 0xcb, 0xa3, 0x87, 0x49, 0x51, 0x1c, 0x3f, 0x75, 0x9c, 0xc0, 0xda, 0x00, 0x3c, 0x1c, 0x48, - 0x81, 0x49, 0x1a, 0x46, 0x7e, 0xe0, 0xb4, 0xa8, 0x49, 0x77, 0x2a, 0x29, 0x69, 0xbd, 0x0f, 0x03, - 0x33, 0x6a, 0x91, 0x2f, 0x43, 0x29, 0xda, 0x0b, 0x68, 0xb8, 0xe7, 0xb7, 0x9b, 0xd2, 0x6f, 0x61, - 0x4c, 0x4b, 0x9b, 0xec, 0xfd, 0x6d, 0x45, 0xd5, 0x18, 0xde, 0xaa, 0x08, 0x63, 0x9e, 0x24, 0x80, - 0xa9, 0xb0, 0xe1, 0x77, 0x69, 0x28, 0x55, 0xf6, 0x9b, 0xb9, 0x70, 0xe7, 0x96, 0x23, 0xc3, 0xc6, - 0xc7, 0x39, 0xa0, 0xe4, 0x64, 0xff, 0xde, 0x04, 0xcc, 0x99, 0x88, 0x43, 0xac, 0x4d, 0x5f, 0xb5, - 0x60, 0xae, 0xe1, 0x7b, 0x51, 0xe0, 0xb7, 0x85, 0xfd, 0x2a, 0x1f, 0x8d, 0x82, 0x91, 0x5a, 0xa7, - 0x91, 0xe3, 0xb6, 0x0d, 0x53, 0x98, 0xc1, 0x06, 0x13, 0x4c, 0xc9, 0x37, 0x2c, 0x58, 0x88, 0xdd, - 0xdc, 0x62, 0x43, 0x5a, 0xae, 0x82, 0xe8, 0xa5, 0xfe, 0x5a, 0x92, 0x13, 0xa6, 0x59, 0xdb, 0x3b, - 0xb0, 0x98, 0xee, 0x6d, 0xd6, 0x94, 0x5d, 0x47, 0xce, 0xf5, 0x42, 0xdc, 0x94, 0x35, 0x27, 0x0c, - 0x91, 0x43, 0xc8, 0x0b, 0x30, 0xd3, 0x71, 0x82, 0x96, 0xeb, 0x39, 0x6d, 0xde, 0x8a, 0x05, 0x63, - 0x41, 0x92, 0xe5, 0xa8, 0x31, 0xec, 0x4f, 0xc0, 0xdc, 0x96, 0xe3, 0xb5, 0x68, 0x53, 0xae, 0xc3, - 0x8f, 0x8e, 0x69, 0xfb, 0xe1, 0x24, 0xcc, 0x1a, 0x67, 0xb3, 0xb3, 0x3f, 0x67, 0x25, 0x52, 0x6a, - 0x14, 0x72, 0x4c, 0xa9, 0xf1, 0x39, 0x80, 0x5d, 0xd7, 0x73, 0xc3, 0xbd, 0x53, 0x26, 0xeb, 0xe0, - 0xf7, 0xba, 0xd7, 0x35, 0x05, 0x34, 0xa8, 0xc5, 0x97, 0x67, 0xc5, 0x13, 0x32, 0x4f, 0x7d, 0x60, - 0x19, 0xdb, 0xcd, 0x54, 0x1e, 0xce, 0x02, 0x46, 0xc7, 0xac, 0xa8, 0xed, 0x47, 0xe4, 0x1a, 0x3a, - 0x69, 0x57, 0xda, 0x86, 0x99, 0x80, 0x86, 0xbd, 0x0e, 0x3b, 0x31, 0x4e, 0x8f, 0xdc, 0x0c, 0xdc, - 0x6d, 0x03, 0x65, 0x7d, 0xd4, 0x94, 0x96, 0x5f, 0x83, 0x73, 0x09, 0x11, 0x46, 0xca, 0x3e, 0xe4, - 0x43, 0xa6, 0x01, 0xe0, 0x34, 0x97, 0x39, 0xac, 0x2f, 0xda, 0x46, 0xb6, 0x0e, 0xdd, 0x17, 0xc2, - 0x39, 0x47, 0xc0, 0xec, 0xbf, 0x9c, 0x02, 0x79, 0xff, 0x3d, 0xc4, 0x72, 0x65, 0xde, 0x7a, 0x4d, - 0x9c, 0xe2, 0xd6, 0xeb, 0x26, 0xcc, 0xb9, 0x9e, 0x1b, 0xb9, 0x4e, 0x9b, 0x1b, 0x77, 0xe4, 0x76, - 0xaa, 0xbc, 0x97, 0xe7, 0x36, 0x0c, 0x58, 0x06, 0x9d, 0x44, 0x5d, 0x72, 0x1b, 0x8a, 0x7c, 0xbf, - 0x91, 0x03, 0x78, 0xf4, 0x4b, 0x7a, 0xee, 0x9f, 0x21, 0x42, 0x9a, 0x04, 0x25, 0x7e, 0xf8, 0x10, - 0xe9, 0x4a, 0xf4, 0xf1, 0x5b, 0x8e, 0xe3, 0xf8, 0xf0, 0x91, 0x82, 0x63, 0x5f, 0x0d, 0x46, 0x65, - 0xd7, 0x71, 0xdb, 0xbd, 0x80, 0xc6, 0x54, 0xa6, 0x92, 0x54, 0xae, 0xa7, 0xe0, 0xd8, 0x57, 0x83, - 0xec, 0xc2, 0x9c, 0x2c, 0x13, 0x2e, 0x57, 0xd3, 0xa7, 0xfc, 0x4a, 0xee, 0x5a, 0x77, 0xdd, 0xa0, - 0x84, 0x09, 0xba, 0xa4, 0x07, 0xe7, 0x5d, 0xaf, 0xe1, 0x7b, 0x8d, 0x76, 0x2f, 0x74, 0x0f, 0x68, - 0x1c, 0x4f, 0x74, 0x1a, 0x66, 0x97, 0x8e, 0x8f, 0xca, 0xe7, 0x37, 0xd2, 0xe4, 0xb0, 0x9f, 0x03, - 0xf9, 0x8a, 0x05, 0x97, 0x1a, 0xbe, 0x17, 0xf2, 0xf8, 0xff, 0x03, 0x7a, 0x2d, 0x08, 0xfc, 0x40, - 0xf0, 0x2e, 0x9d, 0x92, 0x37, 0xb7, 0x29, 0xae, 0x65, 0x91, 0xc4, 0x6c, 0x4e, 0xe4, 0x5d, 0x98, - 0xe9, 0x06, 0xfe, 0x81, 0xdb, 0xa4, 0x81, 0x74, 0xdf, 0xdb, 0xcc, 0x23, 0x1f, 0x49, 0x4d, 0xd2, - 0x8c, 0x97, 0x1e, 0x55, 0x82, 0x9a, 0x9f, 0xfd, 0xbf, 0x67, 0x61, 0x3e, 0x89, 0x4e, 0x7e, 0x16, - 0xa0, 0x1b, 0xf8, 0x1d, 0x1a, 0xed, 0x51, 0x1d, 0x17, 0x72, 0x6b, 0xdc, 0xb4, 0x17, 0x8a, 0x9e, - 0x72, 0x79, 0x61, 0xcb, 0x45, 0x5c, 0x8a, 0x06, 0x47, 0x12, 0xc0, 0xf4, 0xbe, 0xd8, 0x76, 0xa5, - 0x16, 0xf2, 0x66, 0x2e, 0x3a, 0x93, 0xe4, 0xcc, 0x03, 0x1a, 0x64, 0x11, 0x2a, 0x46, 0x64, 0x07, - 0x0a, 0xf7, 0xe9, 0x4e, 0x3e, 0x31, 0xd7, 0x77, 0xa9, 0x3c, 0xcd, 0x54, 0xa7, 0x8f, 0x8f, 0xca, - 0x85, 0xbb, 0x74, 0x07, 0x19, 0x71, 0xf6, 0x5d, 0x4d, 0x71, 0x77, 0x2f, 0x97, 0x8a, 0x31, 0xbf, - 0x2b, 0xe1, 0x08, 0x20, 0xbe, 0x4b, 0x16, 0xa1, 0x62, 0x44, 0xde, 0x85, 0xd2, 0x7d, 0xe7, 0x80, - 0xee, 0x06, 0xbe, 0x17, 0x49, 0x3f, 0xab, 0x31, 0x43, 0x05, 0xee, 0x2a, 0x72, 0x92, 0x2f, 0xdf, - 0xde, 0x75, 0x21, 0xc6, 0xec, 0xc8, 0x01, 0xcc, 0x78, 0xf4, 0x3e, 0xd2, 0xb6, 0xdb, 0xc8, 0xc7, - 0x35, 0xff, 0x96, 0xa4, 0x26, 0x39, 0xf3, 0x7d, 0x4f, 0x95, 0xa1, 0xe6, 0xc5, 0xfa, 0xf2, 0x9e, - 0xbf, 0x23, 0x17, 0xaa, 0x31, 0xfb, 0x52, 0x9f, 0x4c, 0x45, 0x5f, 0xde, 0xf4, 0x77, 0x90, 0x11, - 0x67, 0x73, 0xa4, 0xa1, 0x9d, 0x7c, 0xe4, 0x32, 0x75, 0x2b, 0x5f, 0xe7, 0x26, 0x31, 0x47, 0xe2, - 0x52, 0x34, 0x38, 0xb2, 0xb6, 0x6d, 0x49, 0x63, 0xa5, 0x5c, 0xa8, 0xc6, 0x6c, 0xdb, 0xa4, 0xe9, - 0x53, 0xb4, 0xad, 0x2a, 0x43, 0xcd, 0x8b, 0xf1, 0x75, 0xa5, 0xe5, 0x2f, 0x9f, 0xa5, 0x2a, 0x69, - 0x47, 0x14, 0x7c, 0x55, 0x19, 0x6a, 0x5e, 0xac, 0xbd, 0xc3, 0xfd, 0xc3, 0xfb, 0x4e, 0x7b, 0xdf, - 0xf5, 0x5a, 0x32, 0xce, 0x71, 0xdc, 0xb4, 0xb7, 0xfb, 0x87, 0x77, 0x05, 0x3d, 0xb3, 0xbd, 0xe3, - 0x52, 0x34, 0x38, 0x92, 0x7f, 0x6a, 0xc1, 0x54, 0xb7, 0xdd, 0x6b, 0xb9, 0xde, 0xd2, 0x1c, 0xd7, - 0x13, 0x3f, 0x9b, 0xe7, 0x0a, 0xbd, 0x52, 0xe3, 0xa4, 0x85, 0xa2, 0xf8, 0xe3, 0xda, 0x67, 0x8f, - 0x17, 0xfe, 0xfc, 0x9f, 0x95, 0x97, 0xa8, 0xd7, 0xf0, 0x9b, 0xae, 0xd7, 0x5a, 0xbd, 0x17, 0xfa, - 0xde, 0x0a, 0x3a, 0xf7, 0x95, 0x8e, 0x2e, 0x65, 0x5a, 0xfe, 0x14, 0xcc, 0x1a, 0x24, 0x1e, 0xa5, - 0xe8, 0xcd, 0x99, 0x8a, 0xde, 0xaf, 0x4f, 0xc1, 0x9c, 0x99, 0x51, 0x6f, 0x08, 0xed, 0x4b, 0x9f, - 0x38, 0x26, 0x46, 0x39, 0x71, 0xb0, 0x23, 0xa6, 0x71, 0x7b, 0xa4, 0xcc, 0x5b, 0x1b, 0xb9, 0x29, - 0xdc, 0xf1, 0x11, 0xd3, 0x28, 0x0c, 0x31, 0xc1, 0x74, 0x04, 0x87, 0x12, 0xa6, 0xb6, 0x0a, 0xc5, - 0xae, 0x98, 0x54, 0x5b, 0x13, 0xaa, 0xda, 0x8b, 0x00, 0x71, 0x66, 0x39, 0x79, 0xab, 0xa8, 0xf5, - 0x61, 0x23, 0xe3, 0x9d, 0x81, 0x45, 0x9e, 0x83, 0x29, 0xa6, 0xfa, 0xd0, 0xa6, 0x0c, 0xc3, 0xd6, - 0xe7, 0xf8, 0xeb, 0xbc, 0x14, 0x25, 0x94, 0xbc, 0xca, 0xb4, 0xd4, 0x58, 0x61, 0x91, 0xd1, 0xd5, - 0x17, 0x63, 0x2d, 0x35, 0x86, 0x61, 0x02, 0x93, 0x89, 0x4e, 0x99, 0x7e, 0xc1, 0xd7, 0x06, 0x43, - 0x74, 0xae, 0x74, 0xa0, 0x80, 0x71, 0xbb, 0x52, 0x4a, 0x1f, 0xe1, 0x73, 0xba, 0x68, 0xd8, 0x95, - 0x52, 0x70, 0xec, 0xab, 0xc1, 0x3e, 0x46, 0x5e, 0x88, 0xce, 0x0a, 0x67, 0xdb, 0x01, 0x57, 0x99, - 0x5f, 0x33, 0xcf, 0x5a, 0x39, 0xce, 0x21, 0x31, 0x6a, 0x87, 0x3f, 0x6c, 0x8d, 0x77, 0x2c, 0xfa, - 0x22, 0xcc, 0x27, 0x77, 0xa1, 0xdc, 0x6f, 0x3e, 0xbe, 0x3e, 0x09, 0x17, 0x6e, 0xb5, 0x5c, 0x2f, - 0x9d, 0x2d, 0x2a, 0x2b, 0xd9, 0xb6, 0x35, 0x6a, 0xb2, 0xed, 0x38, 0x9e, 0x4b, 0x66, 0x33, 0xcf, - 0x8e, 0xe7, 0x52, 0xa9, 0xce, 0x93, 0xb8, 0xe4, 0x4f, 0x2d, 0x78, 0xda, 0x69, 0x8a, 0x73, 0x81, - 0xd3, 0x96, 0xa5, 0x46, 0x8e, 0x5b, 0x39, 0xa3, 0xc3, 0x31, 0x77, 0xf9, 0xfe, 0x8f, 0x5f, 0xa9, - 0x9c, 0xc0, 0x55, 0xf4, 0xf8, 0x8f, 0xc9, 0x2f, 0x78, 0xfa, 0x24, 0x54, 0x3c, 0x51, 0x7c, 0xf2, - 0xb7, 0x61, 0x21, 0xf1, 0xc1, 0xd2, 0x12, 0x5e, 0x12, 0x17, 0x16, 0xf5, 0x24, 0x08, 0xd3, 0xb8, - 0xcb, 0x6f, 0xc1, 0x47, 0x1f, 0x29, 0xe7, 0x48, 0x83, 0xed, 0xab, 0x16, 0x94, 0x84, 0x5d, 0x1b, - 0xe9, 0x6e, 0xca, 0x09, 0x33, 0x75, 0xf2, 0xae, 0xd4, 0x36, 0x32, 0x9c, 0x30, 0xd9, 0x5a, 0xbe, - 0xef, 0x7a, 0x4d, 0xd9, 0xcb, 0x7a, 0x2d, 0x7f, 0xd3, 0xf5, 0x9a, 0xc8, 0x21, 0x7a, 0xb5, 0x2f, - 0x0c, 0xb4, 0x37, 0xfd, 0x8a, 0x05, 0xf3, 0x3c, 0x06, 0x36, 0x3e, 0x13, 0xbe, 0xa2, 0x9d, 0x8d, - 0x84, 0x18, 0x57, 0x92, 0xce, 0x46, 0x0f, 0x8f, 0xca, 0xb3, 0x22, 0x6a, 0x36, 0xe9, 0x7b, 0xf4, - 0x79, 0x69, 0x48, 0xe2, 0x2e, 0x51, 0x13, 0x23, 0xdb, 0x39, 0xb4, 0xa1, 0xb5, 0xae, 0x88, 0x60, - 0x4c, 0xcf, 0x7e, 0x0f, 0xe6, 0xcc, 0x60, 0x16, 0xf2, 0x0a, 0xcc, 0x76, 0x5d, 0xaf, 0x95, 0x0c, - 0x7a, 0xd4, 0xc6, 0xf6, 0x5a, 0x0c, 0x42, 0x13, 0x8f, 0x57, 0xf3, 0xe3, 0x6a, 0x29, 0x1b, 0x7d, - 0xcd, 0x37, 0xab, 0xc5, 0x7f, 0x78, 0x8e, 0xed, 0x8c, 0xa0, 0xa9, 0xdc, 0x73, 0x6c, 0x67, 0xf0, - 0xf8, 0xd1, 0xe5, 0xd8, 0xce, 0x12, 0xe6, 0xff, 0xae, 0x1c, 0xdb, 0x3f, 0x09, 0xa3, 0x26, 0x57, - 0x64, 0xfb, 0xdd, 0x7d, 0x33, 0x30, 0x5d, 0xb7, 0xb8, 0x8c, 0x4c, 0x97, 0x50, 0xfb, 0x7d, 0x36, - 0x6d, 0xf4, 0xa1, 0xb6, 0xd2, 0x8b, 0xf6, 0x88, 0x07, 0xc5, 0xd0, 0x6d, 0x1d, 0xbc, 0x9c, 0x93, - 0x4d, 0x9b, 0x91, 0x92, 0x2f, 0x36, 0xc4, 0x01, 0xb1, 0xac, 0x10, 0x05, 0x1b, 0xfb, 0x2f, 0x2c, - 0x58, 0x4c, 0x9f, 0xbc, 0xf3, 0x76, 0x6a, 0x20, 0xdf, 0xb0, 0x60, 0xde, 0xe9, 0x45, 0x7b, 0xd4, - 0x8b, 0xd4, 0xc5, 0x56, 0x2e, 0x6f, 0x7b, 0x24, 0xdb, 0xce, 0x48, 0x61, 0x95, 0xe0, 0x85, 0x29, - 0xde, 0xf6, 0x27, 0x60, 0xc4, 0x0c, 0x91, 0xf6, 0x35, 0x20, 0xe8, 0xb7, 0xdb, 0x3b, 0x4e, 0x63, - 0xff, 0xae, 0xeb, 0x35, 0xfd, 0xfb, 0x7c, 0xf9, 0x58, 0x85, 0x52, 0x20, 0xc3, 0x04, 0x43, 0xd9, - 0xd3, 0x7a, 0xfd, 0x51, 0xf1, 0x83, 0x21, 0xc6, 0x38, 0xf6, 0x1f, 0x4e, 0xc0, 0xb4, 0x8c, 0x69, - 0x7d, 0x0c, 0xee, 0xea, 0xfb, 0x89, 0x7b, 0xcc, 0x8d, 0x5c, 0x42, 0x71, 0x07, 0xfa, 0xaa, 0x87, - 0x29, 0x5f, 0xf5, 0x37, 0xf3, 0x61, 0x77, 0xb2, 0xa3, 0xfa, 0x1f, 0x14, 0x61, 0x21, 0x15, 0x23, - 0xcc, 0x74, 0xc8, 0x3e, 0xff, 0xcc, 0x3b, 0xb9, 0x86, 0x21, 0xeb, 0xf8, 0x8b, 0x93, 0x5d, 0x35, - 0xc3, 0x44, 0x3e, 0xdf, 0xdb, 0xb9, 0x3d, 0x55, 0xf0, 0xd7, 0xa9, 0x7d, 0x47, 0x74, 0x3d, 0x24, - 0xdf, 0xb1, 0xe0, 0x82, 0xd3, 0xff, 0xd6, 0x83, 0xb4, 0x75, 0xdd, 0xce, 0xfd, 0x11, 0x89, 0xea, - 0x53, 0x52, 0xc8, 0xac, 0x27, 0x35, 0x30, 0x4b, 0x14, 0xfb, 0xbf, 0x58, 0xf0, 0xe4, 0xc0, 0x68, - 0x77, 0x9e, 0x2c, 0x29, 0x48, 0x42, 0xe5, 0x9a, 0x91, 0x73, 0x4e, 0x0f, 0x7d, 0xef, 0x99, 0xce, - 0x6f, 0x93, 0x66, 0x4f, 0x5e, 0x86, 0x39, 0xae, 0x57, 0xb1, 0xd5, 0x33, 0xa2, 0x5d, 0x79, 0x6d, - 0xc3, 0x0d, 0xf8, 0x75, 0xa3, 0x1c, 0x13, 0x58, 0xf6, 0x77, 0x2c, 0x58, 0x1a, 0x94, 0x3a, 0x67, - 0x08, 0xa3, 0xc2, 0xdf, 0x4a, 0xb9, 0xfc, 0x97, 0xfb, 0x5c, 0xfe, 0x53, 0x66, 0x05, 0xe5, 0xdd, - 0x6f, 0x9c, 0xe8, 0x0b, 0x8f, 0xf0, 0x68, 0xff, 0xa6, 0x05, 0x97, 0x07, 0x4c, 0xf8, 0xbe, 0xd0, - 0x0f, 0xeb, 0xd4, 0xa1, 0x1f, 0x13, 0xc3, 0x86, 0x7e, 0xd8, 0x7f, 0x54, 0x80, 0x45, 0x29, 0x4f, - 0xac, 0x5c, 0xbf, 0x9a, 0x08, 0x9c, 0xf8, 0xb1, 0x54, 0xe0, 0xc4, 0xc5, 0x34, 0xfe, 0x5f, 0x47, - 0x4d, 0x7c, 0xb8, 0xa2, 0x26, 0xfe, 0x6a, 0x02, 0x2e, 0x65, 0x66, 0xf4, 0x21, 0x5f, 0xcf, 0xd8, - 0xbd, 0xee, 0xe6, 0x9c, 0x3a, 0x68, 0xc8, 0xfd, 0x6b, 0xdc, 0x50, 0x83, 0x5f, 0x32, 0x5d, 0xfc, - 0xc5, 0x6e, 0xb4, 0x7b, 0x06, 0x49, 0x90, 0x46, 0xf4, 0xf6, 0xb7, 0x7f, 0xbe, 0x00, 0xcf, 0x0f, - 0x4b, 0xe8, 0x43, 0x1a, 0x0d, 0x16, 0x26, 0xa2, 0xc1, 0x1e, 0x93, 0x66, 0x71, 0x26, 0x81, 0x61, - 0xff, 0x7c, 0x52, 0x6f, 0x7b, 0xfd, 0xe3, 0x73, 0xa8, 0x3b, 0xfe, 0x69, 0xa6, 0x7d, 0xaa, 0xac, - 0xc0, 0xf1, 0x52, 0x38, 0x5d, 0x17, 0xc5, 0x0f, 0x8f, 0xca, 0xe7, 0xe3, 0xbc, 0x12, 0xb2, 0x10, - 0x55, 0x25, 0xf2, 0x3c, 0xcc, 0x04, 0x02, 0xaa, 0xe2, 0x5f, 0xa4, 0xa3, 0x84, 0x28, 0x43, 0x0d, - 0x25, 0x5f, 0x36, 0xd4, 0xf5, 0xc9, 0xb3, 0x4a, 0x9f, 0x72, 0x92, 0xff, 0xc7, 0x3b, 0x30, 0x13, - 0xaa, 0x8c, 0xbd, 0x42, 0x71, 0x79, 0x69, 0xc8, 0xb0, 0x2a, 0x76, 0x6e, 0x56, 0xe9, 0x7b, 0xc5, - 0xf7, 0xe9, 0xe4, 0xbe, 0x9a, 0x24, 0xb1, 0xf5, 0x91, 0x55, 0xd8, 0xa7, 0xa1, 0xff, 0xb8, 0x4a, - 0x22, 0x98, 0x96, 0x8f, 0x14, 0xca, 0x8b, 0xb3, 0xad, 0x9c, 0x42, 0x28, 0xa4, 0x83, 0x2d, 0xbf, - 0x9e, 0x54, 0xa6, 0x13, 0xc5, 0xca, 0xfe, 0xbe, 0x05, 0xb3, 0x72, 0x8c, 0x3c, 0x86, 0xf8, 0xb2, - 0x7b, 0xc9, 0xf8, 0xb2, 0x6b, 0xb9, 0xac, 0x58, 0x03, 0x82, 0xcb, 0xee, 0xc1, 0x9c, 0x99, 0x4a, - 0x8e, 0x7c, 0xce, 0x58, 0x71, 0xad, 0x71, 0x92, 0x33, 0xa9, 0x35, 0x39, 0x5e, 0x8d, 0xed, 0xdf, - 0x28, 0xe9, 0x56, 0xe4, 0x67, 0x57, 0x73, 0xe4, 0x5b, 0x27, 0x8e, 0x7c, 0x73, 0xe0, 0x4d, 0xe4, - 0x3f, 0xf0, 0x6e, 0xc3, 0x8c, 0x5a, 0x16, 0xa5, 0xf2, 0xf0, 0xac, 0xe9, 0x71, 0xcb, 0x34, 0x10, - 0x46, 0xcc, 0x98, 0x2e, 0xfc, 0x0c, 0xaa, 0xfb, 0x50, 0x2f, 0xd7, 0x9a, 0x0c, 0x79, 0x17, 0x66, - 0xef, 0xfb, 0xc1, 0x7e, 0xdb, 0x77, 0x78, 0xbe, 0x70, 0xc8, 0xe3, 0x92, 0x57, 0xdb, 0x58, 0x45, - 0xd8, 0xc3, 0xdd, 0x98, 0x3e, 0x9a, 0xcc, 0x48, 0x05, 0x16, 0x3a, 0xae, 0x87, 0xd4, 0x69, 0xea, - 0x30, 0xb2, 0x49, 0x91, 0xa2, 0x58, 0xa9, 0xd6, 0x5b, 0x49, 0x30, 0xa6, 0xf1, 0xb9, 0xb5, 0x24, - 0x48, 0x58, 0x1b, 0x64, 0x1e, 0xd2, 0xda, 0xf8, 0x83, 0x31, 0x69, 0xc1, 0x10, 0x7e, 0xff, 0xc9, - 0x72, 0x4c, 0xf1, 0x26, 0x5f, 0x82, 0x99, 0x50, 0xe6, 0x89, 0xcb, 0xc7, 0x3b, 0x40, 0x9f, 0xed, - 0x05, 0xd1, 0xb8, 0x2b, 0x55, 0x09, 0x6a, 0x86, 0x64, 0x13, 0x2e, 0x2a, 0xf3, 0x49, 0xe2, 0x4d, - 0xa7, 0xa9, 0x38, 0xad, 0x10, 0x66, 0xc0, 0x31, 0xb3, 0x16, 0x53, 0xe5, 0x78, 0x8a, 0x46, 0x71, - 0xa9, 0x66, 0xdc, 0x43, 0xf1, 0xf9, 0xd7, 0x44, 0x09, 0x3d, 0x29, 0x4a, 0x72, 0x66, 0x8c, 0x28, - 0xc9, 0x3a, 0x5c, 0x4a, 0x83, 0x78, 0xbe, 0x28, 0x9e, 0xa2, 0xca, 0xd8, 0x42, 0x6b, 0x59, 0x48, - 0x98, 0x5d, 0x97, 0xdc, 0x85, 0x52, 0x40, 0xf9, 0x21, 0xab, 0xa2, 0xfc, 0x91, 0x46, 0xf6, 0xbc, - 0x44, 0x45, 0x00, 0x63, 0x5a, 0xac, 0xdf, 0x9d, 0x64, 0xd2, 0xe0, 0xdb, 0x39, 0x3e, 0x26, 0x2a, - 0xfb, 0x7e, 0x40, 0x1e, 0x37, 0xfb, 0x07, 0xf3, 0x70, 0x2e, 0x61, 0x03, 0x22, 0xcf, 0x42, 0x91, - 0x27, 0xd0, 0xe2, 0xab, 0xd5, 0x4c, 0xbc, 0xa2, 0x8a, 0xc6, 0x11, 0x30, 0xf2, 0x0b, 0x16, 0x2c, - 0x74, 0x13, 0xf7, 0x10, 0x6a, 0x21, 0x1f, 0xd7, 0xd2, 0x98, 0x20, 0x6a, 0xa4, 0xdb, 0x4f, 0x32, - 0xc3, 0x34, 0x77, 0xb6, 0x1e, 0x48, 0xf7, 0xe5, 0x36, 0x0d, 0x38, 0xb6, 0x54, 0xf4, 0x34, 0x89, - 0xb5, 0x24, 0x18, 0xd3, 0xf8, 0xac, 0x87, 0xf9, 0xd7, 0x8d, 0xf3, 0x5c, 0x5d, 0x45, 0x11, 0xc0, - 0x98, 0x16, 0x79, 0x1d, 0xe6, 0x65, 0xae, 0xd8, 0x9a, 0xdf, 0xbc, 0xe1, 0x84, 0x7b, 0xf2, 0x84, - 0xa3, 0x4f, 0x64, 0x6b, 0x09, 0x28, 0xa6, 0xb0, 0xf9, 0xb7, 0xc5, 0x09, 0x79, 0x39, 0x81, 0xa9, - 0xe4, 0x6b, 0x04, 0x6b, 0x49, 0x30, 0xa6, 0xf1, 0xc9, 0x0b, 0xc6, 0x36, 0x24, 0x2e, 0xba, 0xf5, - 0x6a, 0x90, 0xb1, 0x15, 0x55, 0x60, 0xa1, 0xc7, 0x0f, 0x84, 0x4d, 0x05, 0x94, 0xf3, 0x51, 0x33, - 0xbc, 0x93, 0x04, 0x63, 0x1a, 0x9f, 0xbc, 0x06, 0xe7, 0x02, 0xb6, 0xd8, 0x6a, 0x02, 0xe2, 0xf6, - 0x5b, 0x5f, 0x6e, 0xa2, 0x09, 0xc4, 0x24, 0x2e, 0x79, 0x03, 0xce, 0xc7, 0xa9, 0x15, 0x15, 0x01, - 0x71, 0x1d, 0xae, 0xf3, 0x7c, 0x55, 0xd2, 0x08, 0xd8, 0x5f, 0x87, 0xfc, 0x5d, 0x58, 0x34, 0x5a, - 0x62, 0xc3, 0x6b, 0xd2, 0x07, 0x32, 0xfd, 0x1d, 0x7f, 0x66, 0x66, 0x2d, 0x05, 0xc3, 0x3e, 0x6c, - 0xf2, 0x69, 0x98, 0x6f, 0xf8, 0xed, 0x36, 0x5f, 0xe3, 0x44, 0x26, 0x7c, 0x91, 0xe7, 0x4e, 0x64, - 0x04, 0x4c, 0x40, 0x30, 0x85, 0x49, 0x6e, 0x02, 0xf1, 0x77, 0x98, 0x7a, 0x45, 0x9b, 0x6f, 0x88, - 0x77, 0xcb, 0x99, 0xc6, 0x71, 0x2e, 0x19, 0x3c, 0xf1, 0x56, 0x1f, 0x06, 0x66, 0xd4, 0xe2, 0x69, - 0xc2, 0x8c, 0x60, 0xd3, 0xf9, 0x3c, 0x9e, 0x6e, 0x4b, 0x9b, 0x2f, 0x1e, 0x19, 0x69, 0x1a, 0xc0, - 0x94, 0x88, 0x65, 0xc9, 0x27, 0xe1, 0x9d, 0x99, 0x14, 0x3b, 0xde, 0x23, 0x44, 0x29, 0x4a, 0x4e, - 0xe4, 0x67, 0xa1, 0xb4, 0xa3, 0x5e, 0x48, 0xe0, 0x59, 0xee, 0xc6, 0xde, 0x17, 0x53, 0x8f, 0x7d, - 0xc4, 0xc7, 0x73, 0x0d, 0xc0, 0x98, 0x25, 0x79, 0x0e, 0x66, 0x6f, 0xd4, 0x2a, 0x7a, 0x14, 0x9e, - 0xe7, 0xbd, 0x3f, 0xc9, 0xaa, 0xa0, 0x09, 0x60, 0x33, 0x4c, 0xab, 0x6f, 0x24, 0xf9, 0xe8, 0x48, - 0x86, 0x36, 0xc6, 0xb0, 0xf9, 0x95, 0x35, 0xd6, 0x97, 0x2e, 0xa4, 0xb0, 0x65, 0x39, 0x6a, 0x0c, - 0xf2, 0x0e, 0xcc, 0xca, 0xfd, 0x82, 0xaf, 0x4d, 0x17, 0x4f, 0x17, 0xc8, 0x8c, 0x31, 0x09, 0x34, - 0xe9, 0xf1, 0x7b, 0x56, 0x9e, 0x38, 0x9e, 0x5e, 0xef, 0xb5, 0xdb, 0x4b, 0x97, 0xf8, 0xba, 0x19, - 0xdf, 0xb3, 0xc6, 0x20, 0x34, 0xf1, 0xc8, 0x4b, 0xca, 0xf5, 0xe8, 0x23, 0x89, 0x8b, 0x67, 0xed, - 0x7a, 0xa4, 0x95, 0xee, 0x01, 0xb1, 0x0e, 0x97, 0x1f, 0xe1, 0xf3, 0xb3, 0x03, 0xcb, 0x4a, 0xe3, - 0xeb, 0x9f, 0x24, 0x4b, 0x4b, 0x09, 0x53, 0xc9, 0xf2, 0xdd, 0x81, 0x98, 0x78, 0x02, 0x15, 0xb2, - 0x03, 0x05, 0xa7, 0xbd, 0xb3, 0xf4, 0x64, 0x1e, 0xaa, 0x6b, 0x65, 0xb3, 0x2a, 0x47, 0x14, 0xf7, - 0x4f, 0xac, 0x6c, 0x56, 0x91, 0x11, 0xb7, 0xbf, 0x32, 0xa1, 0x6f, 0x4f, 0x74, 0x22, 0xe0, 0xf7, - 0xcc, 0x51, 0x6d, 0xe5, 0xf1, 0xce, 0x76, 0xdf, 0x33, 0x22, 0x62, 0x43, 0xca, 0x1c, 0xd3, 0x5d, - 0x3d, 0x8f, 0x73, 0x49, 0xb1, 0x94, 0x4c, 0x72, 0x2c, 0x8e, 0xb4, 0xc9, 0x59, 0x6c, 0xff, 0x36, - 0x68, 0x4b, 0x5c, 0xca, 0x97, 0x26, 0x80, 0xa2, 0x1b, 0x46, 0xae, 0x9f, 0x63, 0xd0, 0x6d, 0x2a, - 0x3b, 0x30, 0xf7, 0xe9, 0xe7, 0x00, 0x14, 0xac, 0x18, 0x4f, 0xaf, 0xe5, 0x7a, 0x0f, 0xe4, 0xe7, - 0xdf, 0xce, 0xdd, 0x49, 0x46, 0xf0, 0xe4, 0x00, 0x14, 0xac, 0xc8, 0x3d, 0x31, 0xd2, 0xf2, 0x79, - 0x53, 0x7d, 0xb3, 0x9a, 0xe2, 0x97, 0x18, 0x71, 0x8c, 0x57, 0xd8, 0x71, 0xa5, 0x0e, 0x33, 0x26, - 0xaf, 0xfa, 0xd6, 0x46, 0x16, 0xaf, 0xfa, 0xd6, 0x06, 0x32, 0x26, 0xe4, 0x6b, 0x16, 0x80, 0xd3, - 0xd9, 0x71, 0xc2, 0xd0, 0x69, 0x6a, 0x93, 0xc9, 0x98, 0x59, 0xff, 0x2b, 0x9a, 0x5e, 0x8a, 0x35, - 0x77, 0x0b, 0x8d, 0xa1, 0x68, 0x70, 0x26, 0xef, 0xc2, 0xb4, 0x23, 0xde, 0x17, 0x93, 0x1e, 0xce, - 0xf9, 0x3c, 0x9a, 0x97, 0x92, 0x80, 0xdb, 0x4e, 0x24, 0x08, 0x15, 0x43, 0xc6, 0x3b, 0x0a, 0x1c, - 0xba, 0xeb, 0xee, 0x4b, 0x8b, 0x4d, 0x7d, 0xec, 0xc4, 0xff, 0x8c, 0x58, 0x16, 0x6f, 0x09, 0x42, - 0xc5, 0x50, 0xbc, 0xf7, 0xec, 0x78, 0x8e, 0x8e, 0x5b, 0xcb, 0x27, 0xba, 0xd1, 0x8c, 0x84, 0x33, - 0xde, 0x7b, 0x36, 0x19, 0x61, 0x92, 0x2f, 0x39, 0x80, 0x29, 0x87, 0xbf, 0x7c, 0x28, 0xcf, 0x47, - 0x98, 0xc7, 0x2b, 0x8a, 0xa9, 0x36, 0xe0, 0x8b, 0x8b, 0x7c, 0x5f, 0x51, 0x72, 0x23, 0xbf, 0x6a, - 0xc1, 0xb4, 0x70, 0xbe, 0x65, 0x5a, 0x22, 0xfb, 0xf6, 0x2f, 0x9e, 0x41, 0x96, 0x71, 0xe9, 0x18, - 0x2c, 0x5d, 0x69, 0x7e, 0x42, 0x7b, 0x16, 0x8a, 0xd2, 0x13, 0x5d, 0x83, 0x95, 0x74, 0xcb, 0x9f, - 0x86, 0x39, 0x93, 0xca, 0x48, 0xce, 0xc1, 0x7f, 0x5e, 0x00, 0xe0, 0x0d, 0x2d, 0x32, 0x55, 0x74, - 0x78, 0x4a, 0xd4, 0x3d, 0xbf, 0x99, 0xcf, 0x93, 0x95, 0x66, 0xc2, 0x09, 0x90, 0xf9, 0x4f, 0xf7, - 0xfc, 0x26, 0x4a, 0x26, 0xa4, 0x05, 0x93, 0x5d, 0x27, 0xda, 0xcb, 0x3f, 0xbb, 0xc5, 0x8c, 0x08, - 0xd9, 0x8c, 0xf6, 0x90, 0x33, 0x20, 0xef, 0x5b, 0x30, 0x2d, 0xf2, 0x5b, 0xa8, 0xdb, 0x8c, 0xb1, - 0xbd, 0x0a, 0x54, 0x9b, 0xad, 0x88, 0x24, 0x1a, 0xb2, 0x07, 0xb5, 0xe2, 0x21, 0x4b, 0x51, 0xb1, - 0x5d, 0xfe, 0xc0, 0x82, 0x39, 0x13, 0x35, 0xa3, 0x9b, 0x7e, 0xda, 0xec, 0xa6, 0x3c, 0xdb, 0xc3, - 0xec, 0xf1, 0xff, 0x6e, 0x81, 0xf1, 0x06, 0x79, 0xec, 0x03, 0x6d, 0x0d, 0xed, 0x03, 0x3d, 0x31, - 0xa2, 0x0f, 0x74, 0x61, 0x24, 0x1f, 0xe8, 0xc9, 0xd1, 0x7d, 0xa0, 0x8b, 0x83, 0x7d, 0xa0, 0xed, - 0x6f, 0x59, 0x70, 0xbe, 0x6f, 0xb7, 0x61, 0xca, 0x69, 0xe0, 0xfb, 0xd1, 0x00, 0xdf, 0x41, 0x8c, - 0x41, 0x68, 0xe2, 0x91, 0x75, 0x58, 0x94, 0x0f, 0x00, 0xd4, 0xbb, 0x6d, 0x37, 0x33, 0xf3, 0xc8, - 0x76, 0x0a, 0x8e, 0x7d, 0x35, 0xec, 0xdf, 0xb6, 0x60, 0xd6, 0x88, 0x57, 0x66, 0xdf, 0xc1, 0xe3, - 0xba, 0xa5, 0x18, 0xb1, 0xab, 0x17, 0xbf, 0x3d, 0x12, 0x30, 0x71, 0x91, 0xd9, 0x32, 0xd2, 0x43, - 0xc7, 0x17, 0x99, 0xac, 0x14, 0x25, 0x54, 0x24, 0xfe, 0xa5, 0x5d, 0xde, 0xe8, 0x05, 0x33, 0xf1, - 0x2f, 0xed, 0x22, 0x87, 0x70, 0x76, 0x4c, 0x4b, 0x97, 0xee, 0xf1, 0xc6, 0x53, 0x0b, 0x4e, 0x10, - 0xa1, 0x80, 0x91, 0x2b, 0x50, 0xa0, 0x5e, 0x53, 0x9a, 0x14, 0xf4, 0x63, 0x88, 0xd7, 0xbc, 0x26, - 0xb2, 0x72, 0xfb, 0x2d, 0x98, 0xab, 0xd3, 0x46, 0x40, 0xa3, 0x37, 0xe9, 0xe1, 0xd0, 0xaf, 0x2b, - 0xb2, 0xd1, 0x9e, 0x7a, 0x5d, 0x91, 0x55, 0x67, 0xe5, 0xf6, 0xbf, 0xb4, 0x20, 0xf5, 0x1e, 0x88, - 0x71, 0xa9, 0x61, 0x0d, 0xbc, 0xd4, 0x30, 0x0d, 0xe1, 0x13, 0x27, 0x1a, 0xc2, 0x6f, 0x02, 0xe9, - 0xb0, 0xa9, 0x90, 0x78, 0xfd, 0x46, 0x5a, 0x73, 0xe2, 0xec, 0x08, 0x7d, 0x18, 0x98, 0x51, 0xcb, - 0xfe, 0x17, 0x42, 0x58, 0xf3, 0x85, 0x90, 0x47, 0x37, 0x40, 0x0f, 0x8a, 0x9c, 0x94, 0x34, 0x69, - 0x8d, 0x69, 0x0e, 0xee, 0xcf, 0x32, 0x14, 0x77, 0xa4, 0x9c, 0xf2, 0x9c, 0x9b, 0xfd, 0x47, 0x42, - 0x56, 0xe3, 0x09, 0x91, 0x21, 0x64, 0xed, 0x24, 0x65, 0xbd, 0x91, 0xd7, 0x5a, 0x99, 0x2d, 0x23, - 0x59, 0x01, 0xe8, 0xd2, 0xa0, 0x41, 0xbd, 0x48, 0x45, 0x6d, 0x14, 0x65, 0xfc, 0xa0, 0x2e, 0x45, - 0x03, 0xc3, 0xfe, 0x26, 0x9b, 0x40, 0xb1, 0x73, 0x24, 0x79, 0x3e, 0xed, 0xf1, 0x98, 0x9e, 0x1c, - 0xda, 0xe1, 0xd1, 0xf0, 0xe5, 0x9f, 0x78, 0x84, 0x2f, 0xff, 0xc7, 0x60, 0x3a, 0xf0, 0xdb, 0xb4, - 0x12, 0x78, 0x69, 0xaf, 0x13, 0x64, 0xc5, 0x78, 0x0b, 0x15, 0xdc, 0xfe, 0x65, 0x0b, 0x16, 0xd3, - 0xc1, 0x46, 0xb9, 0xbb, 0x61, 0x9a, 0x11, 0xd1, 0x85, 0xd1, 0x23, 0xa2, 0xed, 0xf7, 0x99, 0x90, - 0x91, 0xdb, 0xd8, 0x77, 0x3d, 0x11, 0x44, 0xcc, 0x5a, 0xee, 0x63, 0x30, 0x4d, 0xe5, 0xfb, 0x89, - 0xc2, 0x32, 0xab, 0x85, 0x54, 0xcf, 0x26, 0x2a, 0x38, 0xa9, 0xc0, 0x82, 0xba, 0x8f, 0x52, 0xe6, - 0x74, 0x91, 0xfc, 0x40, 0x9b, 0xef, 0xd6, 0x93, 0x60, 0x4c, 0xe3, 0xdb, 0x5f, 0x86, 0x59, 0x63, - 0x53, 0xe2, 0xeb, 0xf7, 0x03, 0xa7, 0x11, 0xa5, 0xd7, 0xbd, 0x6b, 0xac, 0x10, 0x05, 0x8c, 0x5b, - 0xfd, 0x45, 0x2c, 0x44, 0x6a, 0xdd, 0x93, 0x11, 0x10, 0x12, 0xca, 0x88, 0x05, 0xb4, 0x45, 0x1f, - 0xa8, 0xec, 0xdd, 0x8a, 0x18, 0xb2, 0x42, 0x14, 0x30, 0xfb, 0x05, 0x98, 0x51, 0x29, 0x6a, 0x78, - 0x9e, 0x07, 0x65, 0x91, 0x36, 0xf3, 0x3c, 0xf8, 0x41, 0x84, 0x1c, 0x62, 0xbf, 0x0d, 0x33, 0x2a, - 0x93, 0xce, 0xa3, 0xb1, 0xd9, 0x52, 0x14, 0x7a, 0xee, 0x0d, 0x3f, 0x8c, 0x54, 0xfa, 0x1f, 0x71, - 0x69, 0x76, 0x6b, 0x83, 0x97, 0xa1, 0x86, 0xda, 0x2f, 0xc1, 0x42, 0xea, 0xf2, 0x74, 0x88, 0xa4, - 0x10, 0xbf, 0x57, 0x80, 0x39, 0xf3, 0x0e, 0x6d, 0x88, 0x59, 0x3c, 0xfc, 0xe2, 0x98, 0x71, 0xef, - 0x55, 0x18, 0xf1, 0xde, 0xcb, 0xbc, 0x68, 0x9c, 0x3c, 0xdb, 0x8b, 0xc6, 0x62, 0x3e, 0x17, 0x8d, - 0xc6, 0x85, 0xf8, 0xd4, 0xe3, 0xbb, 0x10, 0xff, 0xad, 0x22, 0xcc, 0x27, 0xb3, 0x0c, 0x0e, 0xd1, - 0x93, 0x2f, 0xf4, 0xf5, 0xe4, 0x88, 0x86, 0xf6, 0xc2, 0xb8, 0x86, 0xf6, 0xc9, 0x71, 0x0d, 0xed, - 0xc5, 0x53, 0x18, 0xda, 0xfb, 0xcd, 0xe4, 0x53, 0x43, 0x9b, 0xc9, 0x3f, 0xa3, 0x5d, 0xe5, 0xa6, - 0x13, 0xbe, 0x25, 0xb1, 0xab, 0x1c, 0x49, 0x76, 0xc3, 0x9a, 0xdf, 0xcc, 0x74, 0x39, 0x9c, 0x79, - 0x84, 0x41, 0x31, 0xc8, 0xf4, 0x6c, 0x1b, 0xfd, 0x2e, 0xef, 0x23, 0x23, 0x78, 0xb5, 0xbd, 0x02, - 0xb3, 0x72, 0x3c, 0x71, 0x15, 0x14, 0x92, 0xea, 0x6b, 0x3d, 0x06, 0xa1, 0x89, 0xc7, 0x1f, 0xa0, - 0x4e, 0xbe, 0xcf, 0xcd, 0xef, 0x2d, 0xcc, 0x07, 0xa8, 0x53, 0xef, 0x79, 0xa7, 0xf1, 0xed, 0x2f, - 0xc1, 0xa5, 0x4c, 0x33, 0x02, 0xb7, 0xab, 0x72, 0xed, 0x88, 0x36, 0x25, 0x82, 0x21, 0x46, 0x2a, - 0x71, 0xfd, 0xf2, 0xdd, 0x81, 0x98, 0x78, 0x02, 0x15, 0xfb, 0x37, 0x0b, 0x30, 0x9f, 0x7c, 0xbd, - 0x90, 0xdc, 0xd7, 0x46, 0xc7, 0x5c, 0xec, 0x9d, 0x82, 0xac, 0x91, 0xb9, 0x6e, 0xe0, 0x0d, 0xc2, - 0x7d, 0x3e, 0xbe, 0x76, 0x74, 0x1a, 0xbd, 0xb3, 0x63, 0x2c, 0x4d, 0xf7, 0x92, 0x1d, 0x7f, 0xa0, - 0x30, 0x0e, 0x72, 0x93, 0xa7, 0xd9, 0xdc, 0xb9, 0xc7, 0x71, 0x67, 0x9a, 0x15, 0x1a, 0x6c, 0xd9, - 0xde, 0x72, 0x40, 0x03, 0x77, 0xd7, 0xd5, 0x2f, 0x2f, 0xf3, 0x95, 0xfb, 0x6d, 0x59, 0x86, 0x1a, - 0x6a, 0xbf, 0x3f, 0x01, 0xf1, 0xab, 0xf4, 0xfc, 0x89, 0xaf, 0xd0, 0x38, 0x39, 0xc8, 0x6e, 0xbb, - 0x39, 0xee, 0x3b, 0x7a, 0x31, 0x45, 0xe9, 0xc6, 0x6c, 0x94, 0x60, 0x82, 0xe3, 0x8f, 0xe0, 0x35, - 0x7a, 0x07, 0x16, 0x52, 0x49, 0x15, 0x72, 0xcf, 0x3c, 0xfa, 0xc3, 0x02, 0x94, 0x74, 0x5a, 0x0a, - 0xf2, 0xa9, 0x84, 0x19, 0xa7, 0x54, 0xfd, 0xa8, 0xf1, 0xfe, 0xcc, 0x9e, 0xdf, 0x7c, 0x78, 0x54, - 0x5e, 0xd0, 0xc8, 0x29, 0x93, 0xcc, 0x15, 0x28, 0xf4, 0x82, 0x76, 0xfa, 0x9c, 0x76, 0x07, 0x37, - 0x91, 0x95, 0x93, 0x07, 0x69, 0x3b, 0xca, 0x56, 0x4e, 0xa9, 0x34, 0xc4, 0x81, 0x66, 0xb0, 0xfd, - 0x84, 0xed, 0x92, 0x3b, 0x7e, 0xf3, 0x30, 0xfd, 0x5e, 0x4d, 0xd5, 0x6f, 0x1e, 0x22, 0x87, 0x90, - 0xd7, 0x61, 0x3e, 0x72, 0x3b, 0xd4, 0xef, 0x45, 0xe6, 0x2b, 0xde, 0x85, 0xf8, 0x46, 0x7c, 0x3b, - 0x01, 0xc5, 0x14, 0x36, 0xdb, 0x65, 0xef, 0x85, 0xbe, 0xc7, 0xf3, 0xc9, 0x4e, 0x25, 0xaf, 0xcf, - 0x6e, 0xd6, 0xdf, 0xba, 0xc5, 0xcd, 0x49, 0x1a, 0x83, 0x61, 0xbb, 0x3c, 0x52, 0x3a, 0xa0, 0xd2, - 0x21, 0x65, 0x31, 0xd6, 0xc7, 0x45, 0x39, 0x6a, 0x0c, 0xb2, 0x2e, 0x68, 0x33, 0x69, 0xf9, 0x8e, - 0x32, 0x57, 0x7d, 0x5e, 0xd1, 0x65, 0x65, 0x0f, 0x8f, 0x4e, 0x30, 0xf4, 0xe9, 0x9a, 0xf6, 0x1d, - 0x58, 0x48, 0x35, 0x98, 0x3a, 0x57, 0x5b, 0xd9, 0xe7, 0xea, 0xe1, 0x9e, 0x98, 0xf9, 0xd7, 0x16, - 0x9c, 0xef, 0x5b, 0x02, 0x86, 0x8d, 0x83, 0x4b, 0x6f, 0x46, 0x13, 0xa7, 0xdf, 0x8c, 0x0a, 0xa3, - 0x6d, 0x46, 0xd5, 0x9d, 0xef, 0xfe, 0xe0, 0xea, 0x13, 0xdf, 0xfb, 0xc1, 0xd5, 0x27, 0xfe, 0xf8, - 0x07, 0x57, 0x9f, 0x78, 0xff, 0xf8, 0xaa, 0xf5, 0xdd, 0xe3, 0xab, 0xd6, 0xf7, 0x8e, 0xaf, 0x5a, - 0x7f, 0x7c, 0x7c, 0xd5, 0xfa, 0xcf, 0xc7, 0x57, 0xad, 0x6f, 0xfd, 0xf0, 0xea, 0x13, 0x9f, 0xfb, - 0x4c, 0x3c, 0x40, 0x57, 0xd5, 0x00, 0xe5, 0x3f, 0x3e, 0xae, 0x86, 0xe3, 0x6a, 0x77, 0xbf, 0xb5, - 0xca, 0x06, 0xe8, 0xaa, 0x2e, 0x51, 0x03, 0xf4, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x73, - 0x4f, 0x0a, 0xe4, 0x9d, 0x00, 0x00, + 0xac, 0xc4, 0x16, 0x82, 0x20, 0x41, 0xa0, 0x18, 0x06, 0xec, 0xc4, 0xfe, 0x25, 0x24, 0xc8, 0x1f, + 0x03, 0x31, 0xe2, 0xd8, 0xd6, 0x8f, 0x38, 0x90, 0x7f, 0x24, 0xb2, 0x03, 0x98, 0x8e, 0xa8, 0xfc, + 0x49, 0x90, 0x40, 0x30, 0xe0, 0x20, 0xf0, 0xfe, 0x08, 0x82, 0xf7, 0xd9, 0xaf, 0x7b, 0x7a, 0xb8, + 0x33, 0x3b, 0xcd, 0xd5, 0x25, 0xd1, 0xbf, 0x99, 0x57, 0xf5, 0xaa, 0xaa, 0xdf, 0x67, 0xbd, 0x7a, + 0x55, 0xf5, 0x60, 0xb3, 0xe5, 0x46, 0x7b, 0xbd, 0x9d, 0x95, 0x86, 0xdf, 0x59, 0x75, 0x82, 0x96, + 0xdf, 0x0d, 0xfc, 0xbb, 0xfc, 0xc7, 0xc7, 0x02, 0xbf, 0xdd, 0xf6, 0x7b, 0x51, 0xb8, 0xda, 0xdd, + 0x6f, 0xad, 0x3a, 0x5d, 0x37, 0x5c, 0xd5, 0x25, 0x07, 0x9f, 0x70, 0xda, 0xdd, 0x3d, 0xe7, 0x13, + 0xab, 0x2d, 0xea, 0xd1, 0xc0, 0x89, 0x68, 0x73, 0xa5, 0x1b, 0xf8, 0x91, 0x4f, 0x3e, 0x1d, 0x53, + 0x5b, 0x51, 0xd4, 0xf8, 0x8f, 0x9f, 0x53, 0x75, 0x57, 0xba, 0xfb, 0xad, 0x15, 0x46, 0x6d, 0x45, + 0x97, 0x28, 0x6a, 0xcb, 0x1f, 0x33, 0x64, 0x69, 0xf9, 0x2d, 0x7f, 0x95, 0x13, 0xdd, 0xe9, 0xed, + 0xf2, 0x7f, 0xfc, 0x0f, 0xff, 0x25, 0x98, 0x2d, 0x3f, 0xbb, 0xff, 0x4a, 0xb8, 0xe2, 0xfa, 0x4c, + 0xb6, 0xd5, 0x1d, 0x27, 0x6a, 0xec, 0xad, 0x1e, 0xf4, 0x49, 0xb4, 0x6c, 0x1b, 0x48, 0x0d, 0x3f, + 0xa0, 0x59, 0x38, 0x2f, 0xc5, 0x38, 0x1d, 0xa7, 0xb1, 0xe7, 0x7a, 0x34, 0x38, 0x8c, 0xbf, 0xba, + 0x43, 0x23, 0x27, 0xab, 0xd6, 0xea, 0xa0, 0x5a, 0x41, 0xcf, 0x8b, 0xdc, 0x0e, 0xed, 0xab, 0xf0, + 0xd7, 0x1f, 0x56, 0x21, 0x6c, 0xec, 0xd1, 0x8e, 0xd3, 0x57, 0xef, 0xc5, 0x41, 0xf5, 0x7a, 0x91, + 0xdb, 0x5e, 0x75, 0xbd, 0x28, 0x8c, 0x82, 0x74, 0x25, 0xfb, 0x47, 0x05, 0x28, 0x55, 0x36, 0xab, + 0xf5, 0xc8, 0x89, 0x7a, 0x21, 0xf9, 0xaa, 0x05, 0x73, 0x6d, 0xdf, 0x69, 0x56, 0x9d, 0xb6, 0xe3, + 0x35, 0x68, 0xb0, 0x64, 0x3d, 0x63, 0x5d, 0x99, 0x7d, 0x61, 0x73, 0x65, 0x9c, 0xfe, 0x5a, 0xa9, + 0xdc, 0x0b, 0x91, 0x86, 0x7e, 0x2f, 0x68, 0x50, 0xa4, 0xbb, 0xd5, 0xf3, 0xdf, 0x3d, 0x2a, 0x3f, + 0x71, 0x7c, 0x54, 0x9e, 0xdb, 0x34, 0x38, 0x61, 0x82, 0x2f, 0xf9, 0x96, 0x05, 0x67, 0x1b, 0x8e, + 0xe7, 0x04, 0x87, 0xdb, 0x4e, 0xd0, 0xa2, 0xd1, 0xeb, 0x81, 0xdf, 0xeb, 0x2e, 0x4d, 0x9c, 0x82, + 0x34, 0x4f, 0x4a, 0x69, 0xce, 0xae, 0xa5, 0xd9, 0x61, 0xbf, 0x04, 0x5c, 0xae, 0x30, 0x72, 0x76, + 0xda, 0xd4, 0x94, 0xab, 0x70, 0x9a, 0x72, 0xd5, 0xd3, 0xec, 0xb0, 0x5f, 0x02, 0xf2, 0x11, 0x98, + 0x76, 0xbd, 0x56, 0x40, 0xc3, 0x70, 0x69, 0xf2, 0x19, 0xeb, 0x4a, 0xa9, 0xba, 0x20, 0xab, 0x4f, + 0x6f, 0x88, 0x62, 0x54, 0x70, 0xfb, 0x3b, 0x05, 0x38, 0x5b, 0xd9, 0xac, 0x6e, 0x07, 0xce, 0xee, + 0xae, 0xdb, 0x40, 0xbf, 0x17, 0xb9, 0x5e, 0xcb, 0x24, 0x60, 0x9d, 0x4c, 0x80, 0xbc, 0x0c, 0xb3, + 0x21, 0x0d, 0x0e, 0xdc, 0x06, 0xad, 0xf9, 0x41, 0xc4, 0x3b, 0xa5, 0x58, 0x3d, 0x27, 0xd1, 0x67, + 0xeb, 0x31, 0x08, 0x4d, 0x3c, 0x56, 0x2d, 0xf0, 0xfd, 0x48, 0xc2, 0x79, 0x9b, 0x95, 0xe2, 0x6a, + 0x18, 0x83, 0xd0, 0xc4, 0x23, 0xeb, 0xb0, 0xe8, 0x78, 0x9e, 0x1f, 0x39, 0x91, 0xeb, 0x7b, 0xb5, + 0x80, 0xee, 0xba, 0xf7, 0xe5, 0x27, 0x2e, 0xc9, 0xba, 0x8b, 0x95, 0x14, 0x1c, 0xfb, 0x6a, 0x90, + 0x6f, 0x5a, 0xb0, 0x18, 0x46, 0x6e, 0x63, 0xdf, 0xf5, 0x68, 0x18, 0xae, 0xf9, 0xde, 0xae, 0xdb, + 0x5a, 0x2a, 0xf2, 0x6e, 0xbb, 0x39, 0x5e, 0xb7, 0xd5, 0x53, 0x54, 0xab, 0xe7, 0x99, 0x48, 0xe9, + 0x52, 0xec, 0xe3, 0x4e, 0x3e, 0x0a, 0x25, 0xd9, 0xa2, 0x34, 0x5c, 0x9a, 0x7a, 0xa6, 0x70, 0xa5, + 0x54, 0x3d, 0x73, 0x7c, 0x54, 0x2e, 0x6d, 0xa8, 0x42, 0x8c, 0xe1, 0xf6, 0x3a, 0x2c, 0x55, 0x3a, + 0x3b, 0x4e, 0x18, 0x3a, 0x4d, 0x3f, 0x48, 0x75, 0xdd, 0x15, 0x98, 0xe9, 0x38, 0xdd, 0xae, 0xeb, + 0xb5, 0x58, 0xdf, 0x31, 0x3a, 0x73, 0xc7, 0x47, 0xe5, 0x99, 0x2d, 0x59, 0x86, 0x1a, 0x6a, 0xff, + 0xe9, 0x04, 0xcc, 0x56, 0x3c, 0xa7, 0x7d, 0x18, 0xba, 0x21, 0xf6, 0x3c, 0xf2, 0x05, 0x98, 0x61, + 0xab, 0x56, 0xd3, 0x89, 0x1c, 0x39, 0xd3, 0x3f, 0xbe, 0x22, 0x16, 0x91, 0x15, 0x73, 0x11, 0x89, + 0x3f, 0x9f, 0x61, 0xaf, 0x1c, 0x7c, 0x62, 0xe5, 0xcd, 0x9d, 0xbb, 0xb4, 0x11, 0x6d, 0xd1, 0xc8, + 0xa9, 0x12, 0xd9, 0x0b, 0x10, 0x97, 0xa1, 0xa6, 0x4a, 0x7c, 0x98, 0x0c, 0xbb, 0xb4, 0x21, 0x67, + 0xee, 0xd6, 0x98, 0x33, 0x24, 0x16, 0xbd, 0xde, 0xa5, 0x8d, 0xea, 0x9c, 0x64, 0x3d, 0xc9, 0xfe, + 0x21, 0x67, 0x44, 0xee, 0xc1, 0x54, 0xc8, 0xd7, 0x32, 0x39, 0x29, 0xdf, 0xcc, 0x8f, 0x25, 0x27, + 0x5b, 0x9d, 0x97, 0x4c, 0xa7, 0xc4, 0x7f, 0x94, 0xec, 0xec, 0xff, 0x64, 0xc1, 0x39, 0x03, 0xbb, + 0x12, 0xb4, 0x7a, 0x1d, 0xea, 0x45, 0xe4, 0x19, 0x98, 0xf4, 0x9c, 0x0e, 0x95, 0xb3, 0x4a, 0x8b, + 0x7c, 0xd3, 0xe9, 0x50, 0xe4, 0x10, 0xf2, 0x2c, 0x14, 0x0f, 0x9c, 0x76, 0x8f, 0xf2, 0x46, 0x2a, + 0x55, 0xcf, 0x48, 0x94, 0xe2, 0x5b, 0xac, 0x10, 0x05, 0x8c, 0xbc, 0x0b, 0x25, 0xfe, 0xe3, 0x5a, + 0xe0, 0x77, 0x72, 0xfa, 0x34, 0x29, 0xe1, 0x5b, 0x8a, 0xac, 0x18, 0x7e, 0xfa, 0x2f, 0xc6, 0x0c, + 0xed, 0x3f, 0xb7, 0x60, 0xc1, 0xf8, 0xb8, 0x4d, 0x37, 0x8c, 0xc8, 0xe7, 0xfb, 0x06, 0xcf, 0xca, + 0x70, 0x83, 0x87, 0xd5, 0xe6, 0x43, 0x67, 0x51, 0x7e, 0xe9, 0x8c, 0x2a, 0x31, 0x06, 0x8e, 0x07, + 0x45, 0x37, 0xa2, 0x9d, 0x70, 0x69, 0xe2, 0x99, 0xc2, 0x95, 0xd9, 0x17, 0x36, 0x72, 0xeb, 0xc6, + 0xb8, 0x7d, 0x37, 0x18, 0x7d, 0x14, 0x6c, 0xec, 0x7f, 0x51, 0x48, 0x74, 0xdf, 0x96, 0x92, 0xe3, + 0x7d, 0x0b, 0xa6, 0xda, 0xce, 0x0e, 0x6d, 0x8b, 0xb9, 0x35, 0xfb, 0xc2, 0xdb, 0xb9, 0x49, 0xa2, + 0x78, 0xac, 0x6c, 0x72, 0xfa, 0x57, 0xbd, 0x28, 0x38, 0x8c, 0x87, 0x97, 0x28, 0x44, 0xc9, 0x9c, + 0xfc, 0x43, 0x0b, 0x66, 0xe3, 0x55, 0x4d, 0x35, 0xcb, 0x4e, 0xfe, 0xc2, 0xc4, 0x8b, 0xa9, 0x94, + 0x48, 0x2f, 0xd1, 0x06, 0x04, 0x4d, 0x59, 0x96, 0x3f, 0x09, 0xb3, 0xc6, 0x27, 0x90, 0x45, 0x28, + 0xec, 0xd3, 0x43, 0x31, 0xe0, 0x91, 0xfd, 0x24, 0xe7, 0x13, 0x23, 0x5c, 0x0e, 0xe9, 0x4f, 0x4d, + 0xbc, 0x62, 0x2d, 0xbf, 0x06, 0x8b, 0x69, 0x86, 0xa3, 0xd4, 0xb7, 0xbf, 0x33, 0x99, 0x18, 0x98, + 0x6c, 0x21, 0x20, 0x3e, 0x4c, 0x77, 0x68, 0x14, 0xb8, 0x0d, 0xd5, 0x65, 0xeb, 0xe3, 0xb5, 0xd2, + 0x16, 0x27, 0x16, 0x6f, 0x88, 0xe2, 0x7f, 0x88, 0x8a, 0x0b, 0xd9, 0x83, 0x49, 0x27, 0x68, 0xa9, + 0x3e, 0xb9, 0x96, 0xcf, 0xb4, 0x8c, 0x97, 0x8a, 0x4a, 0xd0, 0x0a, 0x91, 0x73, 0x20, 0xab, 0x50, + 0x8a, 0x68, 0xd0, 0x71, 0x3d, 0x27, 0x12, 0x3b, 0xe8, 0x4c, 0xf5, 0xac, 0x44, 0x2b, 0x6d, 0x2b, + 0x00, 0xc6, 0x38, 0xa4, 0x0d, 0x53, 0xcd, 0xe0, 0x10, 0x7b, 0xde, 0xd2, 0x64, 0x1e, 0x4d, 0xb1, + 0xce, 0x69, 0xc5, 0x83, 0x54, 0xfc, 0x47, 0xc9, 0x83, 0xfc, 0xba, 0x05, 0xe7, 0x3b, 0xd4, 0x09, + 0x7b, 0x01, 0x65, 0x9f, 0x80, 0x34, 0xa2, 0x1e, 0xeb, 0xd8, 0xa5, 0x22, 0x67, 0x8e, 0xe3, 0xf6, + 0x43, 0x3f, 0xe5, 0xea, 0xd3, 0x52, 0x94, 0xf3, 0x59, 0x50, 0xcc, 0x94, 0xc6, 0xfe, 0xd3, 0x49, + 0x38, 0xdb, 0xb7, 0xb0, 0x93, 0x97, 0xa0, 0xd8, 0xdd, 0x73, 0x42, 0xb5, 0x52, 0x5f, 0x56, 0xcb, + 0x44, 0x8d, 0x15, 0x3e, 0x38, 0x2a, 0x9f, 0x51, 0x55, 0x78, 0x01, 0x0a, 0x64, 0xa6, 0x37, 0x75, + 0x68, 0x18, 0x3a, 0x2d, 0xb5, 0x7c, 0x1b, 0xc3, 0x84, 0x17, 0xa3, 0x82, 0x93, 0xbf, 0x63, 0xc1, + 0x19, 0x31, 0x64, 0x90, 0x86, 0xbd, 0x76, 0xc4, 0xb6, 0x28, 0xd6, 0x2c, 0x37, 0xf2, 0x18, 0x9e, + 0x82, 0x64, 0xf5, 0x82, 0xe4, 0x7e, 0xc6, 0x2c, 0x0d, 0x31, 0xc9, 0x97, 0xdc, 0x81, 0x52, 0x18, + 0x39, 0x41, 0x44, 0x9b, 0x95, 0x88, 0x2b, 0x53, 0xb3, 0x2f, 0xfc, 0xf4, 0x70, 0x6b, 0xf7, 0xb6, + 0xdb, 0xa1, 0x62, 0x9f, 0xa8, 0x2b, 0x02, 0x18, 0xd3, 0x22, 0xef, 0x02, 0x04, 0x3d, 0xaf, 0xde, + 0xeb, 0x74, 0x9c, 0xe0, 0x50, 0xea, 0x57, 0xd7, 0xc7, 0xfb, 0x3c, 0xd4, 0xf4, 0x62, 0x55, 0x23, + 0x2e, 0x43, 0x83, 0x1f, 0xf9, 0xb2, 0x05, 0x67, 0xc4, 0x48, 0x54, 0x12, 0x4c, 0xe5, 0x2c, 0xc1, + 0x59, 0xd6, 0xb4, 0xeb, 0x26, 0x0b, 0x4c, 0x72, 0xb4, 0xff, 0x43, 0x52, 0x0d, 0xa8, 0x47, 0xec, + 0xb0, 0xd5, 0x3a, 0x24, 0x9f, 0x83, 0x27, 0xc3, 0x5e, 0xa3, 0x41, 0xc3, 0x70, 0xb7, 0xd7, 0xc6, + 0x9e, 0x77, 0xdd, 0x0d, 0x23, 0x3f, 0x38, 0xdc, 0x74, 0x3b, 0x6e, 0xc4, 0x47, 0x5c, 0xb1, 0x7a, + 0xe9, 0xf8, 0xa8, 0xfc, 0x64, 0x7d, 0x10, 0x12, 0x0e, 0xae, 0x4f, 0x1c, 0x78, 0xaa, 0xe7, 0x0d, + 0x26, 0x2f, 0x34, 0xf4, 0xf2, 0xf1, 0x51, 0xf9, 0xa9, 0xdb, 0x83, 0xd1, 0xf0, 0x24, 0x1a, 0xf6, + 0x7f, 0xb3, 0xd8, 0x4a, 0x2d, 0xbe, 0x6b, 0x9b, 0x76, 0xba, 0x6d, 0xb6, 0xba, 0x9c, 0xbe, 0xfe, + 0x18, 0x25, 0xf4, 0x47, 0xcc, 0x67, 0xbb, 0x53, 0xf2, 0x0f, 0x52, 0x22, 0xed, 0xff, 0x6a, 0xc1, + 0xf9, 0x34, 0xf2, 0x63, 0xd0, 0x79, 0xc2, 0xa4, 0xce, 0x73, 0x33, 0xdf, 0xaf, 0x1d, 0xa0, 0xf8, + 0x7c, 0x75, 0xb2, 0xff, 0x5b, 0xff, 0x5f, 0xdf, 0x46, 0xe3, 0x5d, 0xb1, 0xf0, 0xe3, 0xdc, 0x15, + 0x27, 0x3f, 0x50, 0xbb, 0xe2, 0x6f, 0x4e, 0xc2, 0x5c, 0xc5, 0x8b, 0xdc, 0xca, 0xee, 0xae, 0xeb, + 0xb9, 0xd1, 0x21, 0xf9, 0xfa, 0x04, 0xac, 0x76, 0x03, 0xba, 0x4b, 0x83, 0x80, 0x36, 0xd7, 0x7b, + 0x81, 0xeb, 0xb5, 0xea, 0x8d, 0x3d, 0xda, 0xec, 0xb5, 0x5d, 0xaf, 0xb5, 0xd1, 0xf2, 0x7c, 0x5d, + 0x7c, 0xf5, 0x3e, 0x6d, 0xf4, 0xf8, 0x27, 0x89, 0x49, 0xd1, 0x19, 0xef, 0x93, 0x6a, 0xa3, 0x31, + 0xad, 0xbe, 0x78, 0x7c, 0x54, 0x5e, 0x1d, 0xb1, 0x12, 0x8e, 0xfa, 0x69, 0xe4, 0x6b, 0x13, 0xb0, + 0x12, 0xd0, 0x9f, 0xef, 0xb9, 0xc3, 0xb7, 0x86, 0x58, 0xb5, 0xda, 0x63, 0x6e, 0x3f, 0x23, 0xf1, + 0xac, 0xbe, 0x70, 0x7c, 0x54, 0x1e, 0xb1, 0x0e, 0x8e, 0xf8, 0x5d, 0x76, 0x0d, 0x66, 0x2b, 0x5d, + 0x37, 0x74, 0xef, 0xa3, 0xdf, 0x8b, 0xe8, 0x10, 0x47, 0xdc, 0x32, 0x14, 0x83, 0x5e, 0x9b, 0x8a, + 0xb9, 0x5d, 0xaa, 0x96, 0xd8, 0x2a, 0x84, 0xac, 0x00, 0x45, 0xb9, 0xfd, 0x8b, 0x6c, 0xc5, 0xe5, + 0x24, 0x53, 0xc6, 0x8d, 0xbb, 0x50, 0x0c, 0x18, 0x13, 0x39, 0xb2, 0xc6, 0x3d, 0x07, 0xc6, 0x52, + 0x4b, 0x21, 0xd8, 0x4f, 0x14, 0x2c, 0xec, 0xdf, 0x9b, 0x80, 0x0b, 0x95, 0x6e, 0x77, 0x8b, 0x86, + 0x7b, 0x29, 0x29, 0x7e, 0xd9, 0x82, 0xf9, 0x03, 0x37, 0x88, 0x7a, 0x4e, 0x5b, 0xd9, 0xaf, 0x84, + 0x3c, 0xf5, 0x71, 0xe5, 0xe1, 0xdc, 0xde, 0x4a, 0x90, 0xae, 0x92, 0xe3, 0xa3, 0xf2, 0x7c, 0xb2, + 0x0c, 0x53, 0xec, 0xc9, 0x3f, 0xb0, 0x60, 0x51, 0x16, 0xdd, 0xf4, 0x9b, 0xd4, 0xb4, 0x8f, 0xde, + 0xce, 0x53, 0x26, 0x4d, 0x5c, 0xd8, 0xb5, 0xd2, 0xa5, 0xd8, 0x27, 0x84, 0xfd, 0x3f, 0x26, 0xe0, + 0xe2, 0x00, 0x1a, 0xe4, 0x37, 0x2c, 0x38, 0x2f, 0x8c, 0xaa, 0x06, 0x08, 0xe9, 0xae, 0x6c, 0xcd, + 0x9f, 0xc9, 0x5b, 0x72, 0x64, 0x53, 0x9c, 0x7a, 0x0d, 0x5a, 0x5d, 0x62, 0xab, 0xe1, 0x5a, 0x06, + 0x6b, 0xcc, 0x14, 0x88, 0x4b, 0x2a, 0xcc, 0xac, 0x29, 0x49, 0x27, 0x1e, 0x8b, 0xa4, 0xf5, 0x0c, + 0xd6, 0x98, 0x29, 0x90, 0xfd, 0xb7, 0xe0, 0xa9, 0x13, 0xc8, 0x3d, 0x7c, 0x72, 0xda, 0x6f, 0xeb, + 0x51, 0x9f, 0x1c, 0x73, 0x43, 0xcc, 0x6b, 0x1b, 0xa6, 0xf8, 0xd4, 0x51, 0x13, 0x1b, 0xd8, 0xf6, + 0xc7, 0xe7, 0x54, 0x88, 0x12, 0x62, 0xff, 0x9e, 0x05, 0x33, 0x23, 0x58, 0xc3, 0xca, 0x49, 0x6b, + 0x58, 0xa9, 0xcf, 0x12, 0x16, 0xf5, 0x5b, 0xc2, 0x5e, 0x1f, 0xaf, 0x37, 0x86, 0xb1, 0x80, 0xfd, + 0xc8, 0x82, 0xb3, 0x7d, 0x16, 0x33, 0xb2, 0x07, 0xe7, 0xbb, 0x7e, 0x53, 0xa9, 0x4d, 0xd7, 0x9d, + 0x70, 0x8f, 0xc3, 0xe4, 0xe7, 0xbd, 0xc4, 0x7a, 0xb2, 0x96, 0x01, 0x7f, 0x70, 0x54, 0x5e, 0xd2, + 0x44, 0x52, 0x08, 0x98, 0x49, 0x91, 0x74, 0x61, 0x66, 0xd7, 0xa5, 0xed, 0x66, 0x3c, 0x04, 0xc7, + 0x54, 0x90, 0xae, 0x49, 0x6a, 0xc2, 0x58, 0xac, 0xfe, 0xa1, 0xe6, 0x62, 0x7f, 0x09, 0xe6, 0x93, + 0x57, 0x12, 0x43, 0x74, 0xde, 0x25, 0x28, 0x38, 0x81, 0x27, 0xbb, 0x6e, 0x56, 0x22, 0x14, 0x2a, + 0x78, 0x13, 0x59, 0x39, 0x79, 0x1e, 0x66, 0x76, 0x7b, 0xed, 0x36, 0xab, 0x20, 0xed, 0xff, 0x5a, + 0x1d, 0xbe, 0x26, 0xcb, 0x51, 0x63, 0xd8, 0x7f, 0x35, 0x09, 0x0b, 0xd5, 0x76, 0x8f, 0xbe, 0x1e, + 0x50, 0xaa, 0x0e, 0xe9, 0x15, 0x58, 0xe8, 0x06, 0xf4, 0xc0, 0xa5, 0xf7, 0xea, 0xb4, 0x4d, 0x1b, + 0x91, 0x1f, 0x48, 0x69, 0x2e, 0x4a, 0x42, 0x0b, 0xb5, 0x24, 0x18, 0xd3, 0xf8, 0xe4, 0x35, 0x98, + 0x77, 0x1a, 0x91, 0x7b, 0x40, 0x35, 0x05, 0x21, 0xee, 0x87, 0x24, 0x85, 0xf9, 0x4a, 0x02, 0x8a, + 0x29, 0x6c, 0xf2, 0x79, 0x58, 0x0a, 0x1b, 0x4e, 0x9b, 0xde, 0xee, 0x4a, 0x56, 0x6b, 0x7b, 0xb4, + 0xb1, 0x5f, 0xf3, 0x5d, 0x2f, 0x92, 0x26, 0x99, 0x67, 0x24, 0xa5, 0xa5, 0xfa, 0x00, 0x3c, 0x1c, + 0x48, 0x81, 0xfc, 0x6b, 0x0b, 0x2e, 0x75, 0x03, 0x5a, 0x0b, 0xfc, 0x8e, 0xcf, 0xf6, 0xda, 0x3e, + 0x3b, 0x85, 0x3c, 0xaf, 0xbf, 0x35, 0xa6, 0x52, 0x21, 0x4a, 0xfa, 0xcd, 0xdb, 0x1f, 0x3e, 0x3e, + 0x2a, 0x5f, 0xaa, 0x9d, 0x24, 0x00, 0x9e, 0x2c, 0x1f, 0xf9, 0x37, 0x16, 0x5c, 0xee, 0xfa, 0x61, + 0x74, 0xc2, 0x27, 0x14, 0x4f, 0xf5, 0x13, 0xec, 0xe3, 0xa3, 0xf2, 0xe5, 0xda, 0x89, 0x12, 0xe0, + 0x43, 0x24, 0xb4, 0x8f, 0x67, 0xe1, 0xac, 0x31, 0xf6, 0xe4, 0x21, 0xfe, 0x55, 0x38, 0xa3, 0x06, + 0x43, 0xac, 0x04, 0x94, 0x62, 0xa3, 0x4b, 0xc5, 0x04, 0x62, 0x12, 0x97, 0x8d, 0x3b, 0x3d, 0x14, + 0x45, 0xed, 0xd4, 0xb8, 0xab, 0x25, 0xa0, 0x98, 0xc2, 0x26, 0x1b, 0x70, 0x4e, 0x96, 0x20, 0xed, + 0xb6, 0xdd, 0x86, 0xb3, 0xe6, 0xf7, 0xe4, 0x90, 0x2b, 0x56, 0x2f, 0x1e, 0x1f, 0x95, 0xcf, 0xd5, + 0xfa, 0xc1, 0x98, 0x55, 0x87, 0x6c, 0xc2, 0x79, 0xa7, 0x17, 0xf9, 0xfa, 0xfb, 0xaf, 0x7a, 0x6c, + 0x5f, 0x69, 0xf2, 0xa1, 0x35, 0x23, 0x36, 0xa0, 0x4a, 0x06, 0x1c, 0x33, 0x6b, 0x91, 0x5a, 0x8a, + 0x5a, 0x9d, 0x36, 0x7c, 0xaf, 0x29, 0x7a, 0xb9, 0x18, 0x1f, 0x45, 0x2a, 0x19, 0x38, 0x98, 0x59, + 0x93, 0xb4, 0x61, 0xbe, 0xe3, 0xdc, 0xbf, 0xed, 0x39, 0x07, 0x8e, 0xdb, 0x66, 0x4c, 0xa4, 0x21, + 0x67, 0xb0, 0x75, 0xa1, 0x17, 0xb9, 0xed, 0x15, 0x71, 0xc5, 0xbd, 0xb2, 0xe1, 0x45, 0x6f, 0x06, + 0xf5, 0x88, 0xa9, 0xac, 0x42, 0x95, 0xda, 0x4a, 0xd0, 0xc2, 0x14, 0x6d, 0xf2, 0x26, 0x5c, 0xe0, + 0xd3, 0x71, 0xdd, 0xbf, 0xe7, 0xad, 0xd3, 0xb6, 0x73, 0xa8, 0x3e, 0x60, 0x9a, 0x7f, 0xc0, 0x93, + 0xc7, 0x47, 0xe5, 0x0b, 0xf5, 0x2c, 0x04, 0xcc, 0xae, 0x47, 0x1c, 0x78, 0x2a, 0x09, 0x40, 0x7a, + 0xe0, 0x86, 0xae, 0xef, 0x09, 0x73, 0xcc, 0x4c, 0x6c, 0x8e, 0xa9, 0x0f, 0x46, 0xc3, 0x93, 0x68, + 0x90, 0x7f, 0x64, 0xc1, 0xf9, 0xac, 0x69, 0xb8, 0x54, 0xca, 0xe3, 0xa2, 0x2d, 0x35, 0xb5, 0xc4, + 0x88, 0xc8, 0x5c, 0x14, 0x32, 0x85, 0x20, 0xef, 0x59, 0x30, 0xe7, 0x18, 0x47, 0xc9, 0x25, 0xe0, + 0x52, 0xdd, 0x18, 0xd7, 0xa0, 0x11, 0x53, 0xac, 0x2e, 0x1e, 0x1f, 0x95, 0x13, 0xc7, 0x55, 0x4c, + 0x70, 0x24, 0xff, 0xc4, 0x82, 0x0b, 0x99, 0x73, 0x7c, 0x69, 0xf6, 0x34, 0x5a, 0x88, 0x0f, 0x92, + 0xec, 0x35, 0x27, 0x5b, 0x0c, 0xf2, 0x4d, 0x4b, 0x6f, 0x65, 0xea, 0xee, 0x65, 0x69, 0x8e, 0x8b, + 0x76, 0x6b, 0xcc, 0xd3, 0x73, 0xac, 0x3e, 0x28, 0xc2, 0xd5, 0x73, 0xc6, 0xce, 0xa8, 0x0a, 0x31, + 0xcd, 0x9e, 0x7c, 0xc3, 0x52, 0x5b, 0xa3, 0x96, 0xe8, 0xcc, 0x69, 0x49, 0x44, 0xe2, 0x9d, 0x56, + 0x0b, 0x94, 0x62, 0x4e, 0x7e, 0x16, 0x96, 0x9d, 0x1d, 0x3f, 0x88, 0x32, 0x27, 0xdf, 0xd2, 0x3c, + 0x9f, 0x46, 0x97, 0x8f, 0x8f, 0xca, 0xcb, 0x95, 0x81, 0x58, 0x78, 0x02, 0x05, 0xfb, 0xb7, 0x8b, + 0x30, 0x27, 0x8e, 0x04, 0x72, 0xeb, 0xfa, 0x1d, 0x0b, 0x9e, 0x6e, 0xf4, 0x82, 0x80, 0x7a, 0x51, + 0x3d, 0xa2, 0xdd, 0xfe, 0x8d, 0xcb, 0x3a, 0xd5, 0x8d, 0xeb, 0x99, 0xe3, 0xa3, 0xf2, 0xd3, 0x6b, + 0x27, 0xf0, 0xc7, 0x13, 0xa5, 0x23, 0xff, 0xde, 0x02, 0x5b, 0x22, 0x54, 0x9d, 0xc6, 0x7e, 0x2b, + 0xf0, 0x7b, 0x5e, 0xb3, 0xff, 0x23, 0x26, 0x4e, 0xf5, 0x23, 0x9e, 0x3b, 0x3e, 0x2a, 0xdb, 0x6b, + 0x0f, 0x95, 0x02, 0x87, 0x90, 0x94, 0xbc, 0x0e, 0x67, 0x25, 0xd6, 0xd5, 0xfb, 0x5d, 0x1a, 0xb8, + 0x4c, 0xf9, 0x96, 0x8a, 0x63, 0xec, 0xb6, 0x93, 0x46, 0xc0, 0xfe, 0x3a, 0x24, 0x84, 0xe9, 0x7b, + 0xd4, 0x6d, 0xed, 0x45, 0x4a, 0x7d, 0x1a, 0xd3, 0x57, 0x47, 0x9a, 0x07, 0xee, 0x08, 0x9a, 0xd5, + 0xd9, 0xe3, 0xa3, 0xf2, 0xb4, 0xfc, 0x83, 0x8a, 0x13, 0xb9, 0x09, 0xf3, 0xe2, 0xc0, 0x56, 0x73, + 0xbd, 0x56, 0xcd, 0xf7, 0x84, 0xc3, 0x49, 0xa9, 0xfa, 0x9c, 0xda, 0xf0, 0xeb, 0x09, 0xe8, 0x83, + 0xa3, 0xf2, 0x9c, 0xfa, 0xbd, 0x7d, 0xd8, 0xa5, 0x98, 0xaa, 0x6d, 0xff, 0xc1, 0x14, 0x80, 0x1a, + 0xae, 0xb4, 0x4b, 0x3e, 0x0a, 0xa5, 0x90, 0x46, 0x82, 0xab, 0xbc, 0x41, 0x10, 0x17, 0x33, 0xaa, + 0x10, 0x63, 0x38, 0xd9, 0x87, 0x62, 0xd7, 0xe9, 0x85, 0x54, 0x76, 0xfe, 0x8d, 0x5c, 0x3a, 0xbf, + 0xc6, 0x28, 0x8a, 0x13, 0x1a, 0xff, 0x89, 0x82, 0x07, 0xf9, 0x8a, 0x05, 0x40, 0x93, 0x1d, 0x36, + 0xb6, 0xa5, 0x44, 0xb2, 0x8c, 0xfb, 0x94, 0xb5, 0x41, 0x75, 0xfe, 0xf8, 0xa8, 0x0c, 0x46, 0xd7, + 0x1b, 0x6c, 0xc9, 0x3d, 0x98, 0x71, 0xd4, 0x9a, 0x3f, 0x79, 0x1a, 0x6b, 0x3e, 0x3f, 0x38, 0xe9, + 0x41, 0xab, 0x99, 0x91, 0xaf, 0x59, 0x30, 0x1f, 0xd2, 0x48, 0x76, 0x15, 0x5b, 0x79, 0xa4, 0xc2, + 0x3b, 0xe6, 0xa0, 0xab, 0x27, 0x68, 0x8a, 0x15, 0x34, 0x59, 0x86, 0x29, 0xbe, 0x4a, 0x94, 0xeb, + 0xd4, 0x69, 0xd2, 0x80, 0x9f, 0xcb, 0xa5, 0x26, 0x35, 0xbe, 0x28, 0x06, 0x4d, 0x2d, 0x8a, 0x51, + 0x86, 0x29, 0xbe, 0x4a, 0x94, 0x2d, 0x37, 0x08, 0x7c, 0x29, 0xca, 0x4c, 0x4e, 0xa2, 0x18, 0x34, + 0xb5, 0x28, 0x46, 0x19, 0xa6, 0xf8, 0xda, 0xdf, 0x3e, 0x03, 0xf3, 0x6a, 0x22, 0xc5, 0x9a, 0xbd, + 0x30, 0x03, 0x0d, 0xd0, 0xec, 0xd7, 0x4c, 0x20, 0x26, 0x71, 0x59, 0x65, 0x31, 0x55, 0x93, 0x8a, + 0xbd, 0xae, 0x5c, 0x37, 0x81, 0x98, 0xc4, 0x25, 0x1d, 0x28, 0x86, 0x11, 0xed, 0xaa, 0xcb, 0xe0, + 0x31, 0xef, 0x2a, 0xe3, 0xf5, 0x21, 0xbe, 0xee, 0x61, 0xff, 0x42, 0x14, 0x5c, 0xb8, 0x25, 0x33, + 0x4a, 0x18, 0x37, 0xe5, 0xe4, 0xc8, 0x67, 0x7e, 0x26, 0xed, 0xa6, 0xa2, 0x37, 0x92, 0x65, 0x98, + 0x62, 0x9f, 0xa1, 0xec, 0x17, 0x4f, 0x51, 0xd9, 0xff, 0x2c, 0xcc, 0x74, 0x9c, 0xfb, 0xf5, 0x5e, + 0xd0, 0x7a, 0xf4, 0x43, 0x85, 0x74, 0xaf, 0x13, 0x54, 0x50, 0xd3, 0x23, 0x5f, 0xb6, 0x8c, 0x25, + 0x67, 0x9a, 0x13, 0xbf, 0x93, 0xef, 0x92, 0xa3, 0xf7, 0xca, 0x81, 0x8b, 0x4f, 0x9f, 0xea, 0x3d, + 0xf3, 0xd8, 0x55, 0x6f, 0xa6, 0x46, 0x8a, 0x09, 0xa2, 0xd5, 0xc8, 0xd2, 0xa9, 0xaa, 0x91, 0x6b, + 0x09, 0x66, 0x98, 0x62, 0xce, 0xe5, 0x11, 0x73, 0x4e, 0xcb, 0x03, 0xa7, 0x2a, 0x4f, 0x3d, 0xc1, + 0x0c, 0x53, 0xcc, 0x07, 0x9f, 0x37, 0x67, 0x4f, 0xe7, 0xbc, 0x39, 0x97, 0xc3, 0x79, 0xf3, 0x64, + 0x55, 0xfc, 0xcc, 0xb8, 0xaa, 0x38, 0xb9, 0x01, 0xa4, 0x79, 0xe8, 0x39, 0x1d, 0xb7, 0x21, 0x17, + 0x4b, 0xbe, 0x6d, 0xce, 0x73, 0x7b, 0xc4, 0xb2, 0x5c, 0xc8, 0xc8, 0x7a, 0x1f, 0x06, 0x66, 0xd4, + 0x22, 0x11, 0xcc, 0x74, 0x95, 0xc6, 0xb5, 0x90, 0xc7, 0xe8, 0x57, 0x1a, 0x98, 0xf0, 0x17, 0x60, + 0x13, 0x4f, 0x95, 0xa0, 0xe6, 0x44, 0x36, 0xe1, 0x7c, 0xc7, 0xf5, 0x6a, 0x7e, 0x33, 0xac, 0xd1, + 0x40, 0x5a, 0x5b, 0xea, 0x34, 0x5a, 0x5a, 0xe4, 0x6d, 0xc3, 0x4f, 0xd0, 0x5b, 0x19, 0x70, 0xcc, + 0xac, 0x65, 0xff, 0x4f, 0x0b, 0x16, 0xd7, 0xda, 0x7e, 0xaf, 0x79, 0xc7, 0x89, 0x1a, 0x7b, 0xe2, + 0xaa, 0x9c, 0xbc, 0x06, 0x33, 0xae, 0x17, 0xd1, 0xe0, 0xc0, 0x69, 0xcb, 0xfd, 0xc9, 0x56, 0xe6, + 0xd3, 0x0d, 0x59, 0xfe, 0xe0, 0xa8, 0x3c, 0xbf, 0xde, 0x0b, 0xb8, 0x3b, 0x9d, 0x58, 0xad, 0x50, + 0xd7, 0x21, 0xdf, 0xb6, 0xe0, 0xac, 0xb8, 0x6c, 0x5f, 0x77, 0x22, 0xe7, 0x56, 0x8f, 0x06, 0x2e, + 0x55, 0xd7, 0xed, 0x63, 0x2e, 0x54, 0x69, 0x59, 0x15, 0x83, 0xc3, 0x58, 0x51, 0xdf, 0x4a, 0x73, + 0xc6, 0x7e, 0x61, 0xec, 0x5f, 0x29, 0xc0, 0x93, 0x03, 0x69, 0x91, 0x65, 0x98, 0x70, 0x9b, 0xf2, + 0xd3, 0x41, 0xd2, 0x9d, 0xd8, 0x68, 0xe2, 0x84, 0xdb, 0x24, 0x2b, 0x5c, 0xe7, 0x0c, 0x68, 0x18, + 0xaa, 0x9b, 0xd7, 0x92, 0x56, 0x0f, 0x65, 0x29, 0x1a, 0x18, 0xa4, 0x0c, 0x45, 0xee, 0x5a, 0x29, + 0xcf, 0x13, 0x5c, 0x8b, 0xe5, 0x5e, 0x8c, 0x28, 0xca, 0xc9, 0x2f, 0x5a, 0x00, 0x42, 0x40, 0x76, + 0x1a, 0x91, 0xbb, 0x24, 0xe6, 0xdb, 0x4c, 0x8c, 0xb2, 0x90, 0x32, 0xfe, 0x8f, 0x06, 0x57, 0xb2, + 0x0d, 0x53, 0x4c, 0xa1, 0xf5, 0x9b, 0x8f, 0xbc, 0x29, 0xf2, 0x2b, 0x99, 0x1a, 0xa7, 0x81, 0x92, + 0x16, 0x6b, 0xab, 0x80, 0x46, 0xbd, 0xc0, 0x63, 0x4d, 0xcb, 0xb7, 0xc1, 0x19, 0x21, 0x05, 0xea, + 0x52, 0x34, 0x30, 0xec, 0x7f, 0x35, 0x01, 0xe7, 0xb3, 0x44, 0x67, 0xbb, 0xcd, 0x94, 0x90, 0x56, + 0x1e, 0x8d, 0x3f, 0x93, 0x7f, 0xfb, 0x48, 0xbf, 0x11, 0xed, 0x5d, 0x21, 0x3d, 0xdb, 0x24, 0x5f, + 0xf2, 0x19, 0xdd, 0x42, 0x13, 0x8f, 0xd8, 0x42, 0x9a, 0x72, 0xaa, 0x95, 0x9e, 0x81, 0xc9, 0x90, + 0xf5, 0x7c, 0x21, 0x79, 0xdd, 0xc1, 0xfb, 0x88, 0x43, 0x18, 0x46, 0xcf, 0x73, 0x23, 0x19, 0x8f, + 0xa0, 0x31, 0x6e, 0x7b, 0x6e, 0x84, 0x1c, 0x62, 0x7f, 0x6b, 0x02, 0x96, 0x07, 0x7f, 0x14, 0xf9, + 0x96, 0x05, 0xd0, 0x64, 0xc7, 0x95, 0x90, 0x3b, 0xf5, 0x0a, 0x3f, 0x1b, 0xe7, 0xb4, 0xda, 0x70, + 0x5d, 0x71, 0x8a, 0x9d, 0xae, 0x74, 0x51, 0x88, 0x86, 0x20, 0xe4, 0x05, 0x35, 0xf4, 0xf9, 0x55, + 0x8d, 0x98, 0x4c, 0xba, 0xce, 0x96, 0x86, 0xa0, 0x81, 0xc5, 0xce, 0xa3, 0x9e, 0xd3, 0xa1, 0x61, + 0xd7, 0xd1, 0xd1, 0x1d, 0xfc, 0x3c, 0x7a, 0x53, 0x15, 0x62, 0x0c, 0xb7, 0xdb, 0xf0, 0xec, 0x10, + 0x72, 0xe6, 0xe4, 0x3c, 0x6f, 0xff, 0x85, 0x05, 0x17, 0xd7, 0xda, 0xbd, 0x30, 0xa2, 0xc1, 0xff, + 0x37, 0x3e, 0x6c, 0xff, 0xcb, 0x82, 0xa7, 0x06, 0x7c, 0xf3, 0x63, 0x70, 0x65, 0x7b, 0x27, 0xe9, + 0xca, 0x76, 0x7b, 0xdc, 0x21, 0x9d, 0xf9, 0x1d, 0x03, 0x3c, 0xda, 0x7e, 0xd3, 0x82, 0x33, 0x6c, + 0xd9, 0x6a, 0xfa, 0xad, 0x9c, 0x36, 0xce, 0x67, 0xa1, 0xf8, 0xf3, 0x6c, 0x03, 0x4a, 0x0f, 0x32, + 0xbe, 0x2b, 0xa1, 0x80, 0xb1, 0x39, 0xe3, 0x74, 0xdd, 0xb7, 0x68, 0xc0, 0x37, 0xa0, 0x42, 0x72, + 0xce, 0x54, 0x34, 0x04, 0x0d, 0x2c, 0xfb, 0xd3, 0x20, 0x9d, 0xc5, 0x52, 0x33, 0xce, 0x1a, 0x66, + 0xc6, 0xd9, 0xff, 0x71, 0x02, 0x0c, 0xe3, 0xc7, 0x63, 0x18, 0xc9, 0x5e, 0x62, 0x24, 0x8f, 0x79, + 0x70, 0x37, 0x4c, 0x39, 0x83, 0x82, 0x79, 0x0e, 0x52, 0xc1, 0x3c, 0x37, 0x73, 0xe3, 0x78, 0x72, + 0x2c, 0xcf, 0xf7, 0x2d, 0x78, 0x2a, 0x46, 0xee, 0xb7, 0x4b, 0x3e, 0x7c, 0x59, 0x7a, 0x19, 0x66, + 0x9d, 0xb8, 0x9a, 0x1c, 0x37, 0x46, 0x24, 0x85, 0x06, 0xa1, 0x89, 0x17, 0xfb, 0xa0, 0x17, 0x1e, + 0xd1, 0x07, 0x7d, 0xf2, 0x64, 0x1f, 0x74, 0xfb, 0x2f, 0x27, 0xe0, 0x52, 0xff, 0x97, 0xa9, 0x09, + 0x35, 0xdc, 0x25, 0xff, 0x2b, 0x30, 0x17, 0xc9, 0x0a, 0xc6, 0xf6, 0xa0, 0xa3, 0x3a, 0xb7, 0x0d, + 0x18, 0x26, 0x30, 0x59, 0xcd, 0x86, 0x98, 0xca, 0xf5, 0x86, 0xdf, 0x55, 0x11, 0x0c, 0xba, 0xe6, + 0x9a, 0x01, 0xc3, 0x04, 0xa6, 0xf6, 0x0d, 0x9d, 0x3c, 0x75, 0xdf, 0xd0, 0x3a, 0x5c, 0x50, 0xde, + 0x70, 0xd7, 0xfc, 0x60, 0xcd, 0xef, 0x74, 0xdb, 0x54, 0xc6, 0x30, 0x30, 0x61, 0x2f, 0xc9, 0x2a, + 0x17, 0x30, 0x0b, 0x09, 0xb3, 0xeb, 0xda, 0xdf, 0x2f, 0xc0, 0xb9, 0xb8, 0xd9, 0xd7, 0x7c, 0xaf, + 0xe9, 0x72, 0x9f, 0xc2, 0x57, 0x61, 0x32, 0x3a, 0xec, 0xaa, 0xc6, 0xfe, 0x6b, 0x4a, 0x9c, 0xed, + 0xc3, 0x2e, 0xeb, 0xed, 0x8b, 0x19, 0x55, 0xb8, 0x65, 0x98, 0x57, 0x22, 0x9b, 0x7a, 0x76, 0x88, + 0x1e, 0x78, 0x29, 0x39, 0x9a, 0x1f, 0x1c, 0x95, 0x33, 0x82, 0x9a, 0x57, 0x34, 0xa5, 0xe4, 0x98, + 0x27, 0x77, 0x61, 0xbe, 0xed, 0x84, 0xd1, 0xed, 0x6e, 0xd3, 0x89, 0xe8, 0xb6, 0x2b, 0x3d, 0x34, + 0x46, 0x0b, 0x0c, 0xd0, 0x57, 0xd9, 0x9b, 0x09, 0x4a, 0x98, 0xa2, 0x4c, 0x0e, 0x80, 0xb0, 0x92, + 0xed, 0xc0, 0xf1, 0x42, 0xf1, 0x55, 0x8c, 0xdf, 0xe8, 0x81, 0x08, 0xfa, 0x64, 0xb8, 0xd9, 0x47, + 0x0d, 0x33, 0x38, 0x90, 0xe7, 0x60, 0x2a, 0xa0, 0x4e, 0x28, 0x3b, 0xb3, 0x14, 0xcf, 0x7f, 0xe4, + 0xa5, 0x28, 0xa1, 0xe6, 0x84, 0x9a, 0x7a, 0xc8, 0x84, 0xfa, 0x33, 0x0b, 0xe6, 0xe3, 0x6e, 0x7a, + 0x0c, 0x3b, 0x6b, 0x27, 0xb9, 0xb3, 0x5e, 0xcf, 0x6b, 0x49, 0x1c, 0xb0, 0x99, 0xfe, 0xe1, 0x94, + 0xf9, 0x7d, 0xdc, 0x31, 0xfc, 0x8b, 0x50, 0x52, 0xb3, 0x5a, 0xa9, 0xac, 0x63, 0x1e, 0xb0, 0x13, + 0xca, 0x8c, 0x11, 0xd0, 0x24, 0x99, 0x60, 0xcc, 0x8f, 0x6d, 0xe5, 0x4d, 0xb9, 0x4d, 0xcb, 0x61, + 0xaf, 0xb7, 0x72, 0xb5, 0x7d, 0x67, 0x6d, 0xe5, 0xaa, 0x0e, 0xb9, 0x0d, 0x17, 0xbb, 0x81, 0xcf, + 0xc3, 0x6a, 0xd7, 0xa9, 0xd3, 0x6c, 0xbb, 0x1e, 0x55, 0x56, 0x0c, 0xe1, 0x49, 0xf1, 0xd4, 0xf1, + 0x51, 0xf9, 0x62, 0x2d, 0x1b, 0x05, 0x07, 0xd5, 0x4d, 0x06, 0x66, 0x4d, 0x0e, 0x11, 0x98, 0xf5, + 0x77, 0xb5, 0xad, 0x90, 0x86, 0x32, 0x3c, 0xea, 0x73, 0x79, 0x75, 0x65, 0xc6, 0xb2, 0x1e, 0x0f, + 0xa9, 0x8a, 0x64, 0x8a, 0x9a, 0xfd, 0x60, 0x83, 0xd4, 0xd4, 0x23, 0x1a, 0xa4, 0x62, 0xff, 0xfa, + 0xe9, 0x1f, 0xa7, 0x7f, 0xfd, 0xcc, 0x07, 0xca, 0xbf, 0xfe, 0xfd, 0x22, 0x2c, 0xa6, 0x35, 0x90, + 0xd3, 0x0f, 0x3a, 0xfb, 0xfb, 0x16, 0x2c, 0xaa, 0xd9, 0x23, 0x78, 0x52, 0x75, 0xd5, 0xb0, 0x99, + 0xd3, 0xa4, 0x15, 0xba, 0x94, 0x8e, 0xc6, 0xdf, 0x4e, 0x71, 0xc3, 0x3e, 0xfe, 0xe4, 0x6d, 0x98, + 0xd5, 0x16, 0xf9, 0x47, 0x8a, 0x40, 0x5b, 0xe0, 0x5a, 0x54, 0x4c, 0x02, 0x4d, 0x7a, 0xe4, 0x7d, + 0x0b, 0xa0, 0xa1, 0xb6, 0x39, 0x35, 0xbb, 0x6e, 0xe5, 0x35, 0xbb, 0xf4, 0x06, 0x1a, 0x2b, 0xcb, + 0xba, 0x28, 0x44, 0x83, 0x31, 0xf9, 0x15, 0x6e, 0x8b, 0xd7, 0xda, 0x9d, 0x88, 0xf2, 0x1f, 0xdb, + 0x77, 0xf8, 0x04, 0xc5, 0x34, 0x56, 0xa5, 0x0c, 0x50, 0x88, 0x09, 0x21, 0xec, 0x57, 0x41, 0x7b, + 0x7b, 0xb2, 0x65, 0x8b, 0xfb, 0x7b, 0xd6, 0x9c, 0x68, 0x4f, 0x0e, 0x41, 0xbd, 0x6c, 0x5d, 0x53, + 0x00, 0x8c, 0x71, 0xec, 0x2f, 0xc0, 0xfc, 0xeb, 0x81, 0xd3, 0xdd, 0x73, 0xb9, 0xcd, 0x9b, 0x9d, + 0xad, 0x3e, 0x02, 0xd3, 0x4e, 0xb3, 0x99, 0x95, 0x38, 0xa2, 0x22, 0x8a, 0x51, 0xc1, 0x87, 0x3a, + 0x46, 0xd9, 0x7f, 0x60, 0x01, 0x89, 0xef, 0x0d, 0x5d, 0xaf, 0xb5, 0xe5, 0x44, 0x8d, 0x3d, 0x76, + 0x3e, 0xda, 0xe3, 0xa5, 0x59, 0xe7, 0xa3, 0xeb, 0x1a, 0x82, 0x06, 0x16, 0x79, 0x17, 0x66, 0xc5, + 0xbf, 0xb7, 0xb4, 0x85, 0x60, 0xec, 0x08, 0x02, 0xb1, 0xa1, 0x70, 0x99, 0xc4, 0x28, 0xbc, 0x1e, + 0x73, 0x40, 0x93, 0x1d, 0x6b, 0xaa, 0x0d, 0x6f, 0xb7, 0xdd, 0xbb, 0xdf, 0xdc, 0x89, 0x9b, 0xaa, + 0x1b, 0xf8, 0xbb, 0x6e, 0x9b, 0xa6, 0x9b, 0xaa, 0x26, 0x8a, 0x51, 0xc1, 0x87, 0x6b, 0xaa, 0x7f, + 0x6b, 0xc1, 0xf9, 0x8d, 0x30, 0x72, 0xfd, 0x75, 0x1a, 0x46, 0x6c, 0x5b, 0x61, 0x8b, 0x4f, 0xaf, + 0x3d, 0x8c, 0xe3, 0xf6, 0x3a, 0x2c, 0xca, 0x3b, 0xcc, 0xde, 0x4e, 0x48, 0x23, 0x43, 0x8f, 0xd7, + 0xf3, 0x78, 0x2d, 0x05, 0xc7, 0xbe, 0x1a, 0x8c, 0x8a, 0xbc, 0xcc, 0x8c, 0xa9, 0x14, 0x92, 0x54, + 0xea, 0x29, 0x38, 0xf6, 0xd5, 0xb0, 0xbf, 0x57, 0x80, 0x73, 0xfc, 0x33, 0x52, 0x41, 0x17, 0xdf, + 0x18, 0x14, 0x74, 0x31, 0xe6, 0x54, 0xe6, 0xbc, 0x1e, 0x21, 0xe4, 0xe2, 0xef, 0x59, 0xb0, 0xd0, + 0x4c, 0xb6, 0x74, 0x3e, 0x36, 0x9d, 0xac, 0x3e, 0x14, 0x2e, 0x5b, 0xa9, 0x42, 0x4c, 0xf3, 0x27, + 0xbf, 0x6a, 0xc1, 0x42, 0x52, 0x4c, 0xb5, 0xba, 0x9f, 0x42, 0x23, 0x69, 0x1f, 0xeb, 0x64, 0x79, + 0x88, 0x69, 0x11, 0xec, 0x3f, 0x9a, 0x90, 0x5d, 0x7a, 0x1a, 0x11, 0x05, 0xe4, 0x1e, 0x94, 0xa2, + 0x76, 0x28, 0x0a, 0xe5, 0xd7, 0x8e, 0x79, 0x22, 0xdc, 0xde, 0xac, 0x0b, 0xf7, 0x81, 0x58, 0x69, + 0x93, 0x25, 0x4c, 0xf9, 0x54, 0xbc, 0x38, 0xe3, 0x46, 0x57, 0x32, 0xce, 0xe5, 0x28, 0xba, 0xbd, + 0x56, 0x4b, 0x33, 0x96, 0x25, 0x8c, 0xb1, 0xe2, 0x65, 0xff, 0x96, 0x05, 0xa5, 0x1b, 0xbe, 0x5a, + 0x47, 0x7e, 0x36, 0x07, 0x43, 0x8f, 0xd6, 0x07, 0xf5, 0x35, 0x65, 0x7c, 0xc4, 0x78, 0x2d, 0x61, + 0xe6, 0x79, 0xda, 0xa0, 0xbd, 0xc2, 0xf3, 0x67, 0x31, 0x52, 0x37, 0xfc, 0x9d, 0x81, 0xa6, 0xc7, + 0x5f, 0x2b, 0xc2, 0x99, 0x37, 0x9c, 0x43, 0xea, 0x45, 0xce, 0xe8, 0x9b, 0xc4, 0xcb, 0x30, 0xeb, + 0x74, 0xf9, 0x3d, 0x98, 0xa1, 0xe3, 0xc7, 0x96, 0x93, 0x18, 0x84, 0x26, 0x5e, 0xbc, 0xa0, 0x89, + 0xec, 0x3a, 0x59, 0x4b, 0xd1, 0x5a, 0x0a, 0x8e, 0x7d, 0x35, 0xc8, 0x0d, 0x20, 0x32, 0x1a, 0xb5, + 0xd2, 0x68, 0xf8, 0x3d, 0x4f, 0x2c, 0x69, 0xc2, 0xa8, 0xa2, 0x0f, 0x9b, 0x5b, 0x7d, 0x18, 0x98, + 0x51, 0x8b, 0x7c, 0x1e, 0x96, 0x1a, 0x9c, 0xb2, 0x3c, 0x7a, 0x98, 0x14, 0xc5, 0xf1, 0x53, 0xc7, + 0x09, 0xac, 0x0d, 0xc0, 0xc3, 0x81, 0x14, 0x98, 0xa4, 0x61, 0xe4, 0x07, 0x4e, 0x8b, 0x9a, 0x74, + 0xa7, 0x92, 0x92, 0xd6, 0xfb, 0x30, 0x30, 0xa3, 0x16, 0xf9, 0x12, 0x94, 0xa2, 0xbd, 0x80, 0x86, + 0x7b, 0x7e, 0xbb, 0x29, 0xfd, 0x16, 0xc6, 0xb4, 0xb4, 0xc9, 0xde, 0xdf, 0x56, 0x54, 0x8d, 0xe1, + 0xad, 0x8a, 0x30, 0xe6, 0x49, 0x02, 0x98, 0x0a, 0x1b, 0x7e, 0x97, 0x86, 0x52, 0x65, 0xbf, 0x91, + 0x0b, 0x77, 0x6e, 0x39, 0x32, 0x6c, 0x7c, 0x9c, 0x03, 0x4a, 0x4e, 0xf6, 0xef, 0x4f, 0xc0, 0x9c, + 0x89, 0x38, 0xc4, 0xda, 0xf4, 0x15, 0x0b, 0xe6, 0x1a, 0xbe, 0x17, 0x05, 0x7e, 0x5b, 0xd8, 0xaf, + 0xf2, 0xd1, 0x28, 0x18, 0xa9, 0x75, 0x1a, 0x39, 0x6e, 0xdb, 0x30, 0x85, 0x19, 0x6c, 0x30, 0xc1, + 0x94, 0x7c, 0xdd, 0x82, 0x85, 0xd8, 0xcd, 0x2d, 0x36, 0xa4, 0xe5, 0x2a, 0x88, 0x5e, 0xea, 0xaf, + 0x26, 0x39, 0x61, 0x9a, 0xb5, 0xbd, 0x03, 0x8b, 0xe9, 0xde, 0x66, 0x4d, 0xd9, 0x75, 0xe4, 0x5c, + 0x2f, 0xc4, 0x4d, 0x59, 0x73, 0xc2, 0x10, 0x39, 0x84, 0x3c, 0x0f, 0x33, 0x1d, 0x27, 0x68, 0xb9, + 0x9e, 0xd3, 0xe6, 0xad, 0x58, 0x30, 0x16, 0x24, 0x59, 0x8e, 0x1a, 0xc3, 0xfe, 0x38, 0xcc, 0x6d, + 0x39, 0x5e, 0x8b, 0x36, 0xe5, 0x3a, 0xfc, 0xf0, 0x98, 0xb6, 0x1f, 0x4e, 0xc2, 0xac, 0x71, 0x36, + 0x3b, 0xfd, 0x73, 0x56, 0x22, 0xa5, 0x46, 0x21, 0xc7, 0x94, 0x1a, 0x9f, 0x05, 0xd8, 0x75, 0x3d, + 0x37, 0xdc, 0x7b, 0xc4, 0x64, 0x1d, 0xfc, 0x5e, 0xf7, 0x9a, 0xa6, 0x80, 0x06, 0xb5, 0xf8, 0xf2, + 0xac, 0x78, 0x42, 0xe6, 0xa9, 0xf7, 0x2d, 0x63, 0xbb, 0x99, 0xca, 0xc3, 0x59, 0xc0, 0xe8, 0x98, + 0x15, 0xb5, 0xfd, 0x88, 0x5c, 0x43, 0x27, 0xed, 0x4a, 0xdb, 0x30, 0x13, 0xd0, 0xb0, 0xd7, 0x61, + 0x27, 0xc6, 0xe9, 0x91, 0x9b, 0x81, 0xbb, 0x6d, 0xa0, 0xac, 0x8f, 0x9a, 0xd2, 0xf2, 0xab, 0x70, + 0x26, 0x21, 0xc2, 0x48, 0xd9, 0x87, 0x7c, 0xc8, 0x34, 0x00, 0x3c, 0xca, 0x65, 0x0e, 0xeb, 0x8b, + 0xb6, 0x91, 0xad, 0x43, 0xf7, 0x85, 0x70, 0xce, 0x11, 0x30, 0xfb, 0x2f, 0xa7, 0x40, 0xde, 0x7f, + 0x0f, 0xb1, 0x5c, 0x99, 0xb7, 0x5e, 0x13, 0x8f, 0x70, 0xeb, 0x75, 0x03, 0xe6, 0x5c, 0xcf, 0x8d, + 0x5c, 0xa7, 0xcd, 0x8d, 0x3b, 0x72, 0x3b, 0x55, 0xde, 0xcb, 0x73, 0x1b, 0x06, 0x2c, 0x83, 0x4e, + 0xa2, 0x2e, 0xb9, 0x05, 0x45, 0xbe, 0xdf, 0xc8, 0x01, 0x3c, 0xfa, 0x25, 0x3d, 0xf7, 0xcf, 0x10, + 0x21, 0x4d, 0x82, 0x12, 0x3f, 0x7c, 0x88, 0x74, 0x25, 0xfa, 0xf8, 0x2d, 0xc7, 0x71, 0x7c, 0xf8, + 0x48, 0xc1, 0xb1, 0xaf, 0x06, 0xa3, 0xb2, 0xeb, 0xb8, 0xed, 0x5e, 0x40, 0x63, 0x2a, 0x53, 0x49, + 0x2a, 0xd7, 0x52, 0x70, 0xec, 0xab, 0x41, 0x76, 0x61, 0x4e, 0x96, 0x09, 0x97, 0xab, 0xe9, 0x47, + 0xfc, 0x4a, 0xee, 0x5a, 0x77, 0xcd, 0xa0, 0x84, 0x09, 0xba, 0xa4, 0x07, 0x67, 0x5d, 0xaf, 0xe1, + 0x7b, 0x8d, 0x76, 0x2f, 0x74, 0x0f, 0x68, 0x1c, 0x4f, 0xf4, 0x28, 0xcc, 0x2e, 0x1c, 0x1f, 0x95, + 0xcf, 0x6e, 0xa4, 0xc9, 0x61, 0x3f, 0x07, 0xf2, 0x65, 0x0b, 0x2e, 0x34, 0x7c, 0x2f, 0xe4, 0xf1, + 0xff, 0x07, 0xf4, 0x6a, 0x10, 0xf8, 0x81, 0xe0, 0x5d, 0x7a, 0x44, 0xde, 0xdc, 0xa6, 0xb8, 0x96, + 0x45, 0x12, 0xb3, 0x39, 0x91, 0x77, 0x60, 0xa6, 0x1b, 0xf8, 0x07, 0x6e, 0x93, 0x06, 0xd2, 0x7d, + 0x6f, 0x33, 0x8f, 0x7c, 0x24, 0x35, 0x49, 0x33, 0x5e, 0x7a, 0x54, 0x09, 0x6a, 0x7e, 0xf6, 0xff, + 0x9e, 0x85, 0xf9, 0x24, 0x3a, 0xf9, 0x05, 0x80, 0x6e, 0xe0, 0x77, 0x68, 0xb4, 0x47, 0x75, 0x5c, + 0xc8, 0xcd, 0x71, 0xd3, 0x5e, 0x28, 0x7a, 0xca, 0xe5, 0x85, 0x2d, 0x17, 0x71, 0x29, 0x1a, 0x1c, + 0x49, 0x00, 0xd3, 0xfb, 0x62, 0xdb, 0x95, 0x5a, 0xc8, 0x1b, 0xb9, 0xe8, 0x4c, 0x92, 0x33, 0x0f, + 0x68, 0x90, 0x45, 0xa8, 0x18, 0x91, 0x1d, 0x28, 0xdc, 0xa3, 0x3b, 0xf9, 0xc4, 0x5c, 0xdf, 0xa1, + 0xf2, 0x34, 0x53, 0x9d, 0x3e, 0x3e, 0x2a, 0x17, 0xee, 0xd0, 0x1d, 0x64, 0xc4, 0xd9, 0x77, 0x35, + 0xc5, 0xdd, 0xbd, 0x5c, 0x2a, 0xc6, 0xfc, 0xae, 0x84, 0x23, 0x80, 0xf8, 0x2e, 0x59, 0x84, 0x8a, + 0x11, 0x79, 0x07, 0x4a, 0xf7, 0x9c, 0x03, 0xba, 0x1b, 0xf8, 0x5e, 0x24, 0xfd, 0xac, 0xc6, 0x0c, + 0x15, 0xb8, 0xa3, 0xc8, 0x49, 0xbe, 0x7c, 0x7b, 0xd7, 0x85, 0x18, 0xb3, 0x23, 0x07, 0x30, 0xe3, + 0xd1, 0x7b, 0x48, 0xdb, 0x6e, 0x23, 0x1f, 0xd7, 0xfc, 0x9b, 0x92, 0x9a, 0xe4, 0xcc, 0xf7, 0x3d, + 0x55, 0x86, 0x9a, 0x17, 0xeb, 0xcb, 0xbb, 0xfe, 0x8e, 0x5c, 0xa8, 0xc6, 0xec, 0x4b, 0x7d, 0x32, + 0x15, 0x7d, 0x79, 0xc3, 0xdf, 0x41, 0x46, 0x9c, 0xcd, 0x91, 0x86, 0x76, 0xf2, 0x91, 0xcb, 0xd4, + 0xcd, 0x7c, 0x9d, 0x9b, 0xc4, 0x1c, 0x89, 0x4b, 0xd1, 0xe0, 0xc8, 0xda, 0xb6, 0x25, 0x8d, 0x95, + 0x72, 0xa1, 0x1a, 0xb3, 0x6d, 0x93, 0xa6, 0x4f, 0xd1, 0xb6, 0xaa, 0x0c, 0x35, 0x2f, 0xc6, 0xd7, + 0x95, 0x96, 0xbf, 0x7c, 0x96, 0xaa, 0xa4, 0x1d, 0x51, 0xf0, 0x55, 0x65, 0xa8, 0x79, 0xb1, 0xf6, + 0x0e, 0xf7, 0x0f, 0xef, 0x39, 0xed, 0x7d, 0xd7, 0x6b, 0xc9, 0x38, 0xc7, 0x71, 0xb3, 0xdb, 0xee, + 0x1f, 0xde, 0x11, 0xf4, 0xcc, 0xf6, 0x8e, 0x4b, 0xd1, 0xe0, 0x48, 0xfe, 0xb1, 0x05, 0x53, 0xdd, + 0x76, 0xaf, 0xe5, 0x7a, 0x4b, 0x73, 0x5c, 0x4f, 0xfc, 0x4c, 0x9e, 0x2b, 0xf4, 0x4a, 0x8d, 0x93, + 0x16, 0x8a, 0xe2, 0x4f, 0x6b, 0x9f, 0x3d, 0x5e, 0xf8, 0x4b, 0x7f, 0x5e, 0x5e, 0xa2, 0x5e, 0xc3, + 0x6f, 0xba, 0x5e, 0x6b, 0xf5, 0x6e, 0xe8, 0x7b, 0x2b, 0xe8, 0xdc, 0x53, 0x3a, 0xba, 0x94, 0x69, + 0xf9, 0x93, 0x30, 0x6b, 0x90, 0x78, 0x98, 0xa2, 0x37, 0x67, 0x2a, 0x7a, 0xbf, 0x35, 0x05, 0x73, + 0x66, 0x46, 0xbd, 0x21, 0xb4, 0x2f, 0x7d, 0xe2, 0x98, 0x18, 0xe5, 0xc4, 0xc1, 0x8e, 0x98, 0xc6, + 0xed, 0x91, 0x32, 0x6f, 0x6d, 0xe4, 0xa6, 0x70, 0xc7, 0x47, 0x4c, 0xa3, 0x30, 0xc4, 0x04, 0xd3, + 0x11, 0x1c, 0x4a, 0x98, 0xda, 0x2a, 0x14, 0xbb, 0x62, 0x52, 0x6d, 0x4d, 0xa8, 0x6a, 0x2f, 0x00, + 0xc4, 0x99, 0xe5, 0xe4, 0xad, 0xa2, 0xd6, 0x87, 0x8d, 0x8c, 0x77, 0x06, 0x16, 0x79, 0x0e, 0xa6, + 0x98, 0xea, 0x43, 0x9b, 0x32, 0x0c, 0x5b, 0x9f, 0xe3, 0xaf, 0xf1, 0x52, 0x94, 0x50, 0xf2, 0x0a, + 0xd3, 0x52, 0x63, 0x85, 0x45, 0x46, 0x57, 0x9f, 0x8f, 0xb5, 0xd4, 0x18, 0x86, 0x09, 0x4c, 0x26, + 0x3a, 0x65, 0xfa, 0x05, 0x5f, 0x1b, 0x0c, 0xd1, 0xb9, 0xd2, 0x81, 0x02, 0xc6, 0xed, 0x4a, 0x29, + 0x7d, 0x84, 0xcf, 0xe9, 0xa2, 0x61, 0x57, 0x4a, 0xc1, 0xb1, 0xaf, 0x06, 0xfb, 0x18, 0x79, 0x21, + 0x3a, 0x2b, 0x9c, 0x6d, 0x07, 0x5c, 0x65, 0x7e, 0xd5, 0x3c, 0x6b, 0xe5, 0x38, 0x87, 0xc4, 0xa8, + 0x1d, 0xfe, 0xb0, 0x35, 0xde, 0xb1, 0xe8, 0x0b, 0x30, 0x9f, 0xdc, 0x85, 0x72, 0xbf, 0xf9, 0xf8, + 0xda, 0x24, 0x9c, 0xbb, 0xd9, 0x72, 0xbd, 0x74, 0xb6, 0xa8, 0xac, 0x64, 0xe1, 0xd6, 0xc8, 0xc9, + 0xc2, 0x75, 0x3c, 0x97, 0x4c, 0xc5, 0x9d, 0x1d, 0xcf, 0xa5, 0xf2, 0xa2, 0x27, 0x71, 0xc9, 0x9f, + 0x59, 0xf0, 0xb4, 0xd3, 0x14, 0xe7, 0x02, 0xa7, 0x2d, 0x4b, 0x8d, 0x1c, 0xb7, 0x72, 0x46, 0x87, + 0x63, 0xee, 0xf2, 0xfd, 0x1f, 0xbf, 0x52, 0x39, 0x81, 0xab, 0xe8, 0xf1, 0x9f, 0x92, 0x5f, 0xf0, + 0xf4, 0x49, 0xa8, 0x78, 0xa2, 0xf8, 0xe4, 0x6f, 0xc2, 0x42, 0xe2, 0x83, 0xa5, 0x25, 0xbc, 0x24, + 0x2e, 0x2c, 0xea, 0x49, 0x10, 0xa6, 0x71, 0x97, 0xdf, 0x84, 0x0f, 0x3f, 0x54, 0xce, 0x91, 0x06, + 0xdb, 0x57, 0x2c, 0x28, 0x09, 0xbb, 0x36, 0xd2, 0xdd, 0x94, 0x13, 0x66, 0xea, 0xe4, 0x5d, 0xa9, + 0x6d, 0x64, 0x38, 0x61, 0xb2, 0xb5, 0x7c, 0xdf, 0xf5, 0x9a, 0xb2, 0x97, 0xf5, 0x5a, 0xfe, 0x86, + 0xeb, 0x35, 0x91, 0x43, 0xf4, 0x6a, 0x5f, 0x18, 0x68, 0x6f, 0xfa, 0x75, 0x0b, 0xe6, 0x79, 0x0c, + 0x6c, 0x7c, 0x26, 0x7c, 0x59, 0x3b, 0x1b, 0x09, 0x31, 0x2e, 0x25, 0x9d, 0x8d, 0x1e, 0x1c, 0x95, + 0x67, 0x45, 0xd4, 0x6c, 0xd2, 0xf7, 0xe8, 0x73, 0xd2, 0x90, 0xc4, 0x5d, 0xa2, 0x26, 0x46, 0xb6, + 0x73, 0x68, 0x43, 0x6b, 0x5d, 0x11, 0xc1, 0x98, 0x9e, 0xfd, 0x2e, 0xcc, 0x99, 0xc1, 0x2c, 0xe4, + 0x65, 0x98, 0xed, 0xba, 0x5e, 0x2b, 0x19, 0xf4, 0xa8, 0x8d, 0xed, 0xb5, 0x18, 0x84, 0x26, 0x1e, + 0xaf, 0xe6, 0xc7, 0xd5, 0x52, 0x36, 0xfa, 0x9a, 0x6f, 0x56, 0x8b, 0xff, 0xf0, 0x1c, 0xdb, 0x19, + 0x41, 0x53, 0xb9, 0xe7, 0xd8, 0xce, 0xe0, 0xf1, 0xe3, 0xcb, 0xb1, 0x9d, 0x25, 0xcc, 0xff, 0x5d, + 0x39, 0xb6, 0x7f, 0x06, 0x46, 0x4d, 0xae, 0xc8, 0xf6, 0xbb, 0x7b, 0x66, 0x60, 0xba, 0x6e, 0x71, + 0x19, 0x99, 0x2e, 0xa1, 0xf6, 0x7b, 0x6c, 0xda, 0xe8, 0x43, 0x6d, 0xa5, 0x17, 0xed, 0x11, 0x0f, + 0x8a, 0xa1, 0xdb, 0x3a, 0x78, 0x29, 0x27, 0x9b, 0x36, 0x23, 0x25, 0x1f, 0x66, 0x88, 0x03, 0x62, + 0x59, 0x21, 0x0a, 0x36, 0xf6, 0x5f, 0x58, 0xb0, 0x98, 0x3e, 0x79, 0xe7, 0xed, 0xd4, 0x40, 0xbe, + 0x6e, 0xc1, 0xbc, 0xd3, 0x8b, 0xf6, 0xa8, 0x17, 0xa9, 0x8b, 0xad, 0x5c, 0xde, 0x0c, 0x49, 0xb6, + 0x9d, 0x91, 0xc2, 0x2a, 0xc1, 0x0b, 0x53, 0xbc, 0xed, 0x8f, 0xc3, 0x88, 0x19, 0x22, 0xed, 0xab, + 0x40, 0xd0, 0x6f, 0xb7, 0x77, 0x9c, 0xc6, 0xfe, 0x1d, 0xd7, 0x6b, 0xfa, 0xf7, 0xf8, 0xf2, 0xb1, + 0x0a, 0xa5, 0x40, 0x86, 0x09, 0x86, 0xb2, 0xa7, 0xf5, 0xfa, 0xa3, 0xe2, 0x07, 0x43, 0x8c, 0x71, + 0xec, 0x3f, 0x9a, 0x80, 0x69, 0x19, 0xd3, 0xfa, 0x18, 0xdc, 0xd5, 0xf7, 0x13, 0xf7, 0x98, 0x1b, + 0xb9, 0x84, 0xe2, 0x0e, 0xf4, 0x55, 0x0f, 0x53, 0xbe, 0xea, 0x6f, 0xe4, 0xc3, 0xee, 0x64, 0x47, + 0xf5, 0x3f, 0x2c, 0xc2, 0x42, 0x2a, 0x46, 0x98, 0xe9, 0x90, 0x7d, 0xfe, 0x99, 0xb7, 0x73, 0x0d, + 0x43, 0xd6, 0xf1, 0x17, 0x27, 0xbb, 0x6a, 0x86, 0x89, 0x7c, 0xbe, 0xb7, 0x72, 0x7b, 0xaa, 0xe0, + 0x27, 0xa9, 0x7d, 0x47, 0x74, 0x3d, 0x24, 0xdf, 0xb6, 0xe0, 0x9c, 0xd3, 0xff, 0xd6, 0x83, 0xb4, + 0x75, 0xdd, 0xca, 0xfd, 0x11, 0x89, 0xea, 0x53, 0x52, 0xc8, 0xac, 0x27, 0x35, 0x30, 0x4b, 0x14, + 0xfb, 0xbf, 0x58, 0xf0, 0xe4, 0xc0, 0x68, 0x77, 0x9e, 0x2c, 0x29, 0x48, 0x42, 0xe5, 0x9a, 0x91, + 0x73, 0x4e, 0x0f, 0x7d, 0xef, 0x99, 0xce, 0x6f, 0x93, 0x66, 0x4f, 0x5e, 0x82, 0x39, 0xae, 0x57, + 0xb1, 0xd5, 0x33, 0xa2, 0x5d, 0x79, 0x6d, 0xc3, 0x0d, 0xf8, 0x75, 0xa3, 0x1c, 0x13, 0x58, 0xf6, + 0xb7, 0x2d, 0x58, 0x1a, 0x94, 0x3a, 0x67, 0x08, 0xa3, 0xc2, 0xdf, 0x48, 0xb9, 0xfc, 0x97, 0xfb, + 0x5c, 0xfe, 0x53, 0x66, 0x05, 0xe5, 0xdd, 0x6f, 0x9c, 0xe8, 0x0b, 0x0f, 0xf1, 0x68, 0xff, 0x86, + 0x05, 0x17, 0x07, 0x4c, 0xf8, 0xbe, 0xd0, 0x0f, 0xeb, 0x91, 0x43, 0x3f, 0x26, 0x86, 0x0d, 0xfd, + 0xb0, 0xff, 0xb8, 0x00, 0x8b, 0x52, 0x9e, 0x58, 0xb9, 0x7e, 0x25, 0x11, 0x38, 0xf1, 0x53, 0xa9, + 0xc0, 0x89, 0xf3, 0x69, 0xfc, 0x9f, 0x44, 0x4d, 0x7c, 0xb0, 0xa2, 0x26, 0xfe, 0x6a, 0x02, 0x2e, + 0x64, 0x66, 0xf4, 0x21, 0x5f, 0xcb, 0xd8, 0xbd, 0xee, 0xe4, 0x9c, 0x3a, 0x68, 0xc8, 0xfd, 0x6b, + 0xdc, 0x50, 0x83, 0x5f, 0x35, 0x5d, 0xfc, 0xc5, 0x6e, 0xb4, 0x7b, 0x0a, 0x49, 0x90, 0x46, 0xf4, + 0xf6, 0xb7, 0x7f, 0xa9, 0x00, 0x57, 0x86, 0x25, 0xf4, 0x01, 0x8d, 0x06, 0x0b, 0x13, 0xd1, 0x60, + 0x8f, 0x49, 0xb3, 0x38, 0x95, 0xc0, 0xb0, 0x7f, 0x3a, 0xa9, 0xb7, 0xbd, 0xfe, 0xf1, 0x39, 0xd4, + 0x1d, 0xff, 0x34, 0xd3, 0x3e, 0x55, 0x56, 0xe0, 0x78, 0x29, 0x9c, 0xae, 0x8b, 0xe2, 0x07, 0x47, + 0xe5, 0xb3, 0x71, 0x5e, 0x09, 0x59, 0x88, 0xaa, 0x12, 0xb9, 0x02, 0x33, 0x81, 0x80, 0xaa, 0xf8, + 0x17, 0xe9, 0x28, 0x21, 0xca, 0x50, 0x43, 0xc9, 0x97, 0x0c, 0x75, 0x7d, 0xf2, 0xb4, 0xd2, 0xa7, + 0x9c, 0xe4, 0xff, 0xf1, 0x36, 0xcc, 0x84, 0x2a, 0x63, 0xaf, 0x50, 0x5c, 0x5e, 0x1c, 0x32, 0xac, + 0x8a, 0x9d, 0x9b, 0x55, 0xfa, 0x5e, 0xf1, 0x7d, 0x3a, 0xb9, 0xaf, 0x26, 0x49, 0x6c, 0x7d, 0x64, + 0x15, 0xf6, 0x69, 0xe8, 0x3f, 0xae, 0x92, 0x08, 0xa6, 0xe5, 0x8b, 0x86, 0xf2, 0xe2, 0x6c, 0x2b, + 0xa7, 0x10, 0x0a, 0xe9, 0x60, 0xcb, 0xaf, 0x27, 0x95, 0xe9, 0x44, 0xb1, 0xb2, 0xbf, 0x6f, 0xc1, + 0xac, 0x1c, 0x23, 0x8f, 0x21, 0xbe, 0xec, 0x6e, 0x32, 0xbe, 0xec, 0x6a, 0x2e, 0x2b, 0xd6, 0x80, + 0xe0, 0xb2, 0xbb, 0x30, 0x67, 0xa6, 0x92, 0x23, 0x9f, 0x35, 0x56, 0x5c, 0x6b, 0x9c, 0xe4, 0x4c, + 0x6a, 0x4d, 0x8e, 0x57, 0x63, 0xfb, 0xb7, 0x4b, 0xba, 0x15, 0xf9, 0xd9, 0xd5, 0x1c, 0xf9, 0xd6, + 0x89, 0x23, 0xdf, 0x1c, 0x78, 0x13, 0xf9, 0x0f, 0xbc, 0x5b, 0x30, 0xa3, 0x96, 0x45, 0xa9, 0x3c, + 0x3c, 0x6b, 0x7a, 0xdc, 0x32, 0x0d, 0x84, 0x11, 0x33, 0xa6, 0x0b, 0x3f, 0x83, 0xea, 0x3e, 0xd4, + 0xcb, 0xb5, 0x26, 0x43, 0xde, 0x81, 0xd9, 0x7b, 0x7e, 0xb0, 0xdf, 0xf6, 0x1d, 0x9e, 0x2f, 0x1c, + 0xf2, 0xb8, 0xe4, 0xd5, 0x36, 0x56, 0x11, 0xf6, 0x70, 0x27, 0xa6, 0x8f, 0x26, 0x33, 0x52, 0x81, + 0x85, 0x8e, 0xeb, 0x21, 0x75, 0x9a, 0x3a, 0x8c, 0x6c, 0x52, 0xa4, 0x28, 0x56, 0xaa, 0xf5, 0x56, + 0x12, 0x8c, 0x69, 0x7c, 0x6e, 0x2d, 0x09, 0x12, 0xd6, 0x06, 0x99, 0x87, 0xb4, 0x36, 0xfe, 0x60, + 0x4c, 0x5a, 0x30, 0x84, 0xdf, 0x7f, 0xb2, 0x1c, 0x53, 0xbc, 0xc9, 0x17, 0x61, 0x26, 0x94, 0x79, + 0xe2, 0xf2, 0xf1, 0x0e, 0xd0, 0x67, 0x7b, 0x41, 0x34, 0xee, 0x4a, 0x55, 0x82, 0x9a, 0x21, 0xd9, + 0x84, 0xf3, 0xca, 0x7c, 0x92, 0x78, 0xd3, 0x69, 0x2a, 0x4e, 0x2b, 0x84, 0x19, 0x70, 0xcc, 0xac, + 0xc5, 0x54, 0x39, 0x9e, 0xa2, 0x51, 0x5c, 0xaa, 0x19, 0xf7, 0x50, 0x7c, 0xfe, 0x35, 0x51, 0x42, + 0x4f, 0x8a, 0x92, 0x9c, 0x19, 0x23, 0x4a, 0xb2, 0x0e, 0x17, 0xd2, 0x20, 0x9e, 0x2f, 0x8a, 0xa7, + 0xa8, 0x32, 0xb6, 0xd0, 0x5a, 0x16, 0x12, 0x66, 0xd7, 0x25, 0x77, 0xa0, 0x14, 0x50, 0x7e, 0xc8, + 0xaa, 0x28, 0x7f, 0xa4, 0x91, 0x3d, 0x2f, 0x51, 0x11, 0xc0, 0x98, 0x16, 0xeb, 0x77, 0x27, 0x99, + 0x34, 0xf8, 0x56, 0x8e, 0x8f, 0x89, 0xca, 0xbe, 0x1f, 0x90, 0xc7, 0xcd, 0xfe, 0x77, 0x0b, 0x70, + 0x26, 0x61, 0x03, 0x22, 0xcf, 0x42, 0x91, 0x27, 0xd0, 0xe2, 0xab, 0xd5, 0x4c, 0xbc, 0xa2, 0x8a, + 0xc6, 0x11, 0x30, 0xf2, 0xcb, 0x16, 0x2c, 0x74, 0x13, 0xf7, 0x10, 0x6a, 0x21, 0x1f, 0xd7, 0xd2, + 0x98, 0x20, 0x6a, 0xa4, 0xdb, 0x4f, 0x32, 0xc3, 0x34, 0x77, 0xb6, 0x1e, 0x48, 0xf7, 0xe5, 0x36, + 0x0d, 0x38, 0xb6, 0x54, 0xf4, 0x34, 0x89, 0xb5, 0x24, 0x18, 0xd3, 0xf8, 0xac, 0x87, 0xf9, 0xd7, + 0x8d, 0xf3, 0x5c, 0x5d, 0x45, 0x11, 0xc0, 0x98, 0x16, 0x79, 0x0d, 0xe6, 0x65, 0xae, 0xd8, 0x9a, + 0xdf, 0xbc, 0xee, 0x84, 0x7b, 0xf2, 0x84, 0xa3, 0x4f, 0x64, 0x6b, 0x09, 0x28, 0xa6, 0xb0, 0xf9, + 0xb7, 0xc5, 0x09, 0x79, 0x39, 0x81, 0xa9, 0xe4, 0x6b, 0x04, 0x6b, 0x49, 0x30, 0xa6, 0xf1, 0xc9, + 0xf3, 0xc6, 0x36, 0x24, 0x2e, 0xba, 0xf5, 0x6a, 0x90, 0xb1, 0x15, 0x55, 0x60, 0xa1, 0xc7, 0x0f, + 0x84, 0x4d, 0x05, 0x94, 0xf3, 0x51, 0x33, 0xbc, 0x9d, 0x04, 0x63, 0x1a, 0x9f, 0xbc, 0x0a, 0x67, + 0x02, 0xb6, 0xd8, 0x6a, 0x02, 0xe2, 0xf6, 0x5b, 0x5f, 0x6e, 0xa2, 0x09, 0xc4, 0x24, 0x2e, 0x79, + 0x1d, 0xce, 0xc6, 0xa9, 0x15, 0x15, 0x01, 0x71, 0x1d, 0xae, 0xf3, 0x7c, 0x55, 0xd2, 0x08, 0xd8, + 0x5f, 0x87, 0xfc, 0x6d, 0x58, 0x34, 0x5a, 0x62, 0xc3, 0x6b, 0xd2, 0xfb, 0x32, 0xfd, 0x1d, 0x7f, + 0x66, 0x66, 0x2d, 0x05, 0xc3, 0x3e, 0x6c, 0xf2, 0x29, 0x98, 0x6f, 0xf8, 0xed, 0x36, 0x5f, 0xe3, + 0x44, 0x26, 0x7c, 0x91, 0xe7, 0x4e, 0x64, 0x04, 0x4c, 0x40, 0x30, 0x85, 0x49, 0x6e, 0x00, 0xf1, + 0x77, 0x98, 0x7a, 0x45, 0x9b, 0xaf, 0x8b, 0xf7, 0xd0, 0x99, 0xc6, 0x71, 0x26, 0x19, 0x3c, 0xf1, + 0x66, 0x1f, 0x06, 0x66, 0xd4, 0xe2, 0x69, 0xc2, 0x8c, 0x60, 0xd3, 0xf9, 0x3c, 0x9e, 0x6e, 0x4b, + 0x9b, 0x2f, 0x1e, 0x1a, 0x69, 0x1a, 0xc0, 0x94, 0x88, 0x65, 0xc9, 0x27, 0xe1, 0x9d, 0x99, 0x14, + 0x3b, 0xde, 0x23, 0x44, 0x29, 0x4a, 0x4e, 0xe4, 0x17, 0xa0, 0xb4, 0xa3, 0x5e, 0x48, 0xe0, 0x59, + 0xee, 0xc6, 0xde, 0x17, 0x53, 0x8f, 0x7d, 0xc4, 0xc7, 0x73, 0x0d, 0xc0, 0x98, 0x25, 0x79, 0x0e, + 0x66, 0xaf, 0xd7, 0x2a, 0x7a, 0x14, 0x9e, 0xe5, 0xbd, 0x3f, 0xc9, 0xaa, 0xa0, 0x09, 0x60, 0x33, + 0x4c, 0xab, 0x6f, 0x24, 0xf9, 0xe8, 0x48, 0x86, 0x36, 0xc6, 0xb0, 0xf9, 0x95, 0x35, 0xd6, 0x97, + 0xce, 0xa5, 0xb0, 0x65, 0x39, 0x6a, 0x0c, 0xf2, 0x36, 0xcc, 0xca, 0xfd, 0x82, 0xaf, 0x4d, 0xe7, + 0x1f, 0x2d, 0x90, 0x19, 0x63, 0x12, 0x68, 0xd2, 0xe3, 0xf7, 0xac, 0x3c, 0x71, 0x3c, 0xbd, 0xd6, + 0x6b, 0xb7, 0x97, 0x2e, 0xf0, 0x75, 0x33, 0xbe, 0x67, 0x8d, 0x41, 0x68, 0xe2, 0x91, 0x17, 0x95, + 0xeb, 0xd1, 0x87, 0x12, 0x17, 0xcf, 0xda, 0xf5, 0x48, 0x2b, 0xdd, 0x03, 0x62, 0x1d, 0x2e, 0x3e, + 0xc4, 0xe7, 0x67, 0x07, 0x96, 0x95, 0xc6, 0xd7, 0x3f, 0x49, 0x96, 0x96, 0x12, 0xa6, 0x92, 0xe5, + 0x3b, 0x03, 0x31, 0xf1, 0x04, 0x2a, 0x64, 0x07, 0x0a, 0x4e, 0x7b, 0x67, 0xe9, 0xc9, 0x3c, 0x54, + 0xd7, 0xca, 0x66, 0x55, 0x8e, 0x28, 0xee, 0x9f, 0x58, 0xd9, 0xac, 0x22, 0x23, 0x4e, 0x5c, 0x98, + 0x74, 0xda, 0x3b, 0xe1, 0xd2, 0x32, 0x9f, 0xb3, 0xb9, 0x31, 0x89, 0x8d, 0x07, 0x9b, 0xd5, 0x10, + 0x39, 0x0b, 0xfb, 0xcb, 0x13, 0xfa, 0xa2, 0x46, 0xe7, 0x1c, 0x7e, 0xd7, 0x9c, 0x40, 0x56, 0x1e, + 0x4f, 0x7a, 0xf7, 0xbd, 0x58, 0x22, 0xf6, 0xbe, 0xcc, 0xe9, 0xd3, 0xd5, 0x4b, 0x46, 0x2e, 0xd9, + 0x9c, 0x92, 0xf9, 0x94, 0xc5, 0xe9, 0x39, 0xb9, 0x60, 0xd8, 0xbf, 0x0b, 0xda, 0xe8, 0x97, 0x72, + 0xdb, 0x09, 0xa0, 0xe8, 0x86, 0x91, 0xeb, 0xe7, 0x18, 0xdf, 0x9b, 0x4a, 0x44, 0xcc, 0xc3, 0x07, + 0x38, 0x00, 0x05, 0x2b, 0xc6, 0xd3, 0x6b, 0xb9, 0xde, 0x7d, 0xf9, 0xf9, 0xb7, 0x72, 0xf7, 0xc7, + 0x11, 0x3c, 0x39, 0x00, 0x05, 0x2b, 0x72, 0x57, 0x0c, 0xea, 0x7c, 0x9e, 0x6f, 0xdf, 0xac, 0xa6, + 0xf8, 0x25, 0x07, 0xf7, 0x5d, 0x28, 0x84, 0x1d, 0x57, 0xaa, 0x4b, 0x63, 0xf2, 0xaa, 0x6f, 0x6d, + 0x64, 0xf1, 0xaa, 0x6f, 0x6d, 0x20, 0x63, 0x42, 0xbe, 0x6a, 0x01, 0x38, 0x9d, 0x1d, 0x27, 0x0c, + 0x9d, 0xa6, 0xb6, 0xce, 0x8c, 0xf9, 0xc0, 0x40, 0x45, 0xd3, 0x4b, 0xb1, 0xe6, 0x1e, 0xa8, 0x31, + 0x14, 0x0d, 0xce, 0xe4, 0x1d, 0x98, 0x76, 0xc4, 0x53, 0x66, 0xd2, 0x99, 0x3a, 0x9f, 0xf7, 0xf9, + 0x52, 0x12, 0x70, 0x33, 0x8d, 0x04, 0xa1, 0x62, 0xc8, 0x78, 0x47, 0x81, 0x43, 0x77, 0xdd, 0x7d, + 0x69, 0x1c, 0xaa, 0x8f, 0xfd, 0xc6, 0x00, 0x23, 0x96, 0xc5, 0x5b, 0x82, 0x50, 0x31, 0x14, 0x4f, + 0x4b, 0x3b, 0x9e, 0xa3, 0x43, 0xe4, 0xf2, 0x09, 0xa4, 0x34, 0x83, 0xee, 0x8c, 0xa7, 0xa5, 0x4d, + 0x46, 0x98, 0xe4, 0x4b, 0x0e, 0x60, 0xca, 0xe1, 0x8f, 0x2c, 0xca, 0xa3, 0x18, 0xe6, 0xf1, 0x60, + 0x63, 0xaa, 0x0d, 0xf8, 0xe2, 0x22, 0x9f, 0x72, 0x94, 0xdc, 0xc8, 0x6f, 0x58, 0x30, 0x2d, 0xfc, + 0x7c, 0x99, 0x42, 0xca, 0xbe, 0xfd, 0x0b, 0xa7, 0x90, 0xd0, 0x5c, 0xfa, 0x20, 0x4b, 0xaf, 0x9d, + 0x8f, 0x6a, 0x27, 0x46, 0x51, 0x7a, 0xa2, 0x17, 0xb2, 0x92, 0x6e, 0xf9, 0x53, 0x30, 0x67, 0x52, + 0x19, 0xc9, 0x0f, 0xf9, 0x47, 0x05, 0x00, 0xde, 0xd0, 0x22, 0x29, 0x46, 0x87, 0x67, 0x5f, 0xdd, + 0xf3, 0x9b, 0xf9, 0xbc, 0x8e, 0x69, 0xe6, 0xb6, 0x00, 0x99, 0x6a, 0x75, 0xcf, 0x6f, 0xa2, 0x64, + 0x42, 0x5a, 0x30, 0xd9, 0x75, 0xa2, 0xbd, 0xfc, 0x13, 0x69, 0xcc, 0x88, 0xe8, 0xd0, 0x68, 0x0f, + 0x39, 0x03, 0xf2, 0x9e, 0x05, 0xd3, 0x22, 0x95, 0x86, 0xba, 0x38, 0x19, 0xdb, 0x81, 0x41, 0xb5, + 0xd9, 0x8a, 0xc8, 0xd7, 0x21, 0x7b, 0x50, 0xeb, 0x38, 0xb2, 0x14, 0x15, 0xdb, 0xe5, 0xf7, 0x2d, + 0x98, 0x33, 0x51, 0x33, 0xba, 0xe9, 0xe7, 0xcc, 0x6e, 0xca, 0xb3, 0x3d, 0xcc, 0x1e, 0xff, 0xef, + 0x16, 0x18, 0xcf, 0x9d, 0xc7, 0xee, 0xd6, 0xd6, 0xd0, 0xee, 0xd6, 0x13, 0x23, 0xba, 0x5b, 0x17, + 0x46, 0x72, 0xb7, 0x9e, 0x1c, 0xdd, 0xdd, 0xba, 0x38, 0xd8, 0xdd, 0xda, 0xfe, 0xa6, 0x05, 0x67, + 0xfb, 0x76, 0x1b, 0xa6, 0x07, 0x07, 0xbe, 0x1f, 0x0d, 0x70, 0x53, 0xc4, 0x18, 0x84, 0x26, 0x1e, + 0x59, 0x87, 0x45, 0xf9, 0xd6, 0x40, 0xbd, 0xdb, 0x76, 0x33, 0x93, 0x9c, 0x6c, 0xa7, 0xe0, 0xd8, + 0x57, 0xc3, 0xfe, 0x5d, 0x0b, 0x66, 0x8d, 0xd0, 0x68, 0xf6, 0x1d, 0x3c, 0x84, 0x5c, 0x8a, 0x11, + 0x7b, 0x95, 0xf1, 0x8b, 0x2a, 0x01, 0x13, 0x77, 0xa6, 0x2d, 0x23, 0x13, 0x75, 0x7c, 0x67, 0xca, + 0x4a, 0x51, 0x42, 0x45, 0x8e, 0x61, 0xda, 0xe5, 0x8d, 0x5e, 0x30, 0x73, 0x0c, 0xd3, 0x2e, 0x72, + 0x08, 0x67, 0xc7, 0x0e, 0x04, 0xd2, 0x13, 0xdf, 0x78, 0xd5, 0xc1, 0x09, 0x22, 0x14, 0x30, 0x72, + 0x09, 0x0a, 0xd4, 0x6b, 0x4a, 0xeb, 0x85, 0x7e, 0x77, 0xf1, 0xaa, 0xd7, 0x44, 0x56, 0x6e, 0xbf, + 0x09, 0x73, 0x75, 0xda, 0x08, 0x68, 0xf4, 0x06, 0x3d, 0x1c, 0xfa, 0x21, 0x47, 0x36, 0xda, 0x53, + 0x0f, 0x39, 0xb2, 0xea, 0xac, 0xdc, 0xfe, 0xe7, 0x16, 0xa4, 0x9e, 0x1e, 0x31, 0xee, 0x4f, 0xac, + 0x81, 0xf7, 0x27, 0xa6, 0xcd, 0x7d, 0xe2, 0x44, 0x9b, 0xfb, 0x0d, 0x20, 0x1d, 0x36, 0x15, 0x12, + 0x0f, 0xed, 0x48, 0xc3, 0x51, 0x9c, 0x88, 0xa1, 0x0f, 0x03, 0x33, 0x6a, 0xd9, 0xff, 0x4c, 0x08, + 0x6b, 0x3e, 0x46, 0xf2, 0xf0, 0x06, 0xe8, 0x41, 0x91, 0x93, 0x92, 0xd6, 0xb3, 0x31, 0x2d, 0xcf, + 0xfd, 0x09, 0x8d, 0xe2, 0x8e, 0x94, 0x53, 0x9e, 0x73, 0xb3, 0xff, 0x58, 0xc8, 0x6a, 0xbc, 0x56, + 0x32, 0x84, 0xac, 0x9d, 0xa4, 0xac, 0xd7, 0xf3, 0x5a, 0x2b, 0xb3, 0x65, 0x24, 0x2b, 0x00, 0x5d, + 0x1a, 0x34, 0xa8, 0x17, 0xa9, 0x00, 0x91, 0xa2, 0x0c, 0x55, 0xd4, 0xa5, 0x68, 0x60, 0xd8, 0xdf, + 0x60, 0x13, 0x28, 0xf6, 0xc3, 0x24, 0x57, 0xd2, 0xce, 0x95, 0xe9, 0xc9, 0xa1, 0x7d, 0x2b, 0x8d, + 0xb0, 0x81, 0x89, 0x87, 0x84, 0x0d, 0x7c, 0x04, 0xa6, 0x03, 0xbf, 0x4d, 0x2b, 0x81, 0x97, 0x76, + 0x70, 0x41, 0x56, 0x8c, 0x37, 0x51, 0xc1, 0xed, 0x5f, 0xb3, 0x60, 0x31, 0x1d, 0xd7, 0x94, 0xbb, + 0xc7, 0xa7, 0x19, 0x7c, 0x5d, 0x18, 0x3d, 0xf8, 0xda, 0x7e, 0x8f, 0x09, 0x19, 0xb9, 0x8d, 0x7d, + 0xd7, 0x13, 0xf1, 0xca, 0xac, 0xe5, 0x3e, 0x02, 0xd3, 0x54, 0x3e, 0xd5, 0x28, 0x8c, 0xc0, 0x5a, + 0x48, 0xf5, 0x42, 0xa3, 0x82, 0x93, 0x0a, 0x2c, 0xa8, 0xab, 0x2f, 0x65, 0xb9, 0x17, 0x79, 0x16, + 0xb4, 0xa5, 0x70, 0x3d, 0x09, 0xc6, 0x34, 0xbe, 0xfd, 0x25, 0x98, 0x35, 0x36, 0x25, 0xbe, 0x7e, + 0xdf, 0x77, 0x1a, 0x51, 0x7a, 0xdd, 0xbb, 0xca, 0x0a, 0x51, 0xc0, 0xf8, 0x05, 0x83, 0x08, 0xbb, + 0x48, 0xad, 0x7b, 0x32, 0xd8, 0x42, 0x42, 0x19, 0xb1, 0x80, 0xb6, 0xe8, 0x7d, 0x95, 0x28, 0x5c, + 0x11, 0x43, 0x56, 0x88, 0x02, 0x66, 0x3f, 0x0f, 0x33, 0x2a, 0x1b, 0x0e, 0x4f, 0x29, 0xa1, 0x8c, + 0xdf, 0x66, 0x4a, 0x09, 0x3f, 0x88, 0x90, 0x43, 0xec, 0xb7, 0x60, 0x46, 0x25, 0xed, 0x79, 0x38, + 0x36, 0x5b, 0x8a, 0x42, 0xcf, 0xbd, 0xee, 0x87, 0x91, 0xca, 0x34, 0x24, 0xee, 0xe7, 0x6e, 0x6e, + 0xf0, 0x32, 0xd4, 0x50, 0xfb, 0x45, 0x58, 0x48, 0xdd, 0xd3, 0x0e, 0x91, 0x7f, 0xe2, 0xf7, 0x0b, + 0x30, 0x67, 0x5e, 0xd7, 0x0d, 0x31, 0x8b, 0x87, 0x5f, 0x1c, 0x33, 0xae, 0xd8, 0x0a, 0x23, 0x5e, + 0xb1, 0x99, 0x77, 0x9a, 0x93, 0xa7, 0x7b, 0xa7, 0x59, 0xcc, 0xe7, 0x4e, 0xd3, 0xb8, 0x7b, 0x9f, + 0x7a, 0x7c, 0x77, 0xef, 0xbf, 0x53, 0x84, 0xf9, 0x64, 0x42, 0xc3, 0x21, 0x7a, 0xf2, 0xf9, 0xbe, + 0x9e, 0x1c, 0xd1, 0xa6, 0x5f, 0x18, 0xd7, 0xa6, 0x3f, 0x39, 0xae, 0x4d, 0xbf, 0xf8, 0x08, 0x36, + 0xfd, 0x7e, 0x8b, 0xfc, 0xd4, 0xd0, 0x16, 0xf9, 0x4f, 0x6b, 0xaf, 0xbc, 0xe9, 0x84, 0x1b, 0x4b, + 0xec, 0x95, 0x47, 0x92, 0xdd, 0xb0, 0xe6, 0x37, 0x33, 0xbd, 0x1b, 0x67, 0x1e, 0x62, 0xbb, 0x0c, + 0x32, 0x9d, 0xe8, 0x46, 0xbf, 0x36, 0xfc, 0xd0, 0x08, 0x0e, 0x74, 0x2f, 0xc3, 0xac, 0x1c, 0x4f, + 0x5c, 0x05, 0x85, 0xa4, 0xfa, 0x5a, 0x8f, 0x41, 0x68, 0xe2, 0xf1, 0xb7, 0xae, 0x93, 0x4f, 0x81, + 0xf3, 0x2b, 0x12, 0xf3, 0xad, 0xeb, 0xd4, 0xd3, 0xe1, 0x69, 0x7c, 0xfb, 0x8b, 0x70, 0x21, 0xd3, + 0x8c, 0xc0, 0x4d, 0xb8, 0x5c, 0x3b, 0xa2, 0x4d, 0x89, 0x60, 0x88, 0x91, 0xca, 0x91, 0xbf, 0x7c, + 0x67, 0x20, 0x26, 0x9e, 0x40, 0xc5, 0xfe, 0x4e, 0x01, 0xe6, 0x93, 0x0f, 0x25, 0x92, 0x7b, 0xda, + 0xe8, 0x98, 0x8b, 0xbd, 0x53, 0x90, 0x35, 0x92, 0xe4, 0x0d, 0xbc, 0xac, 0xb8, 0xc7, 0xc7, 0xd7, + 0x8e, 0xce, 0xd8, 0x77, 0x7a, 0x8c, 0xe5, 0x2d, 0x81, 0x64, 0xc7, 0xdf, 0x42, 0x8c, 0xe3, 0xe9, + 0xe4, 0x69, 0x36, 0x77, 0xee, 0x71, 0x88, 0x9b, 0x66, 0x85, 0x06, 0x5b, 0xb6, 0xb7, 0x1c, 0xd0, + 0xc0, 0xdd, 0x75, 0xf5, 0x23, 0xcf, 0x7c, 0xe5, 0x7e, 0x4b, 0x96, 0xa1, 0x86, 0xda, 0xef, 0x4d, + 0x40, 0xfc, 0x00, 0x3e, 0x7f, 0x4d, 0x2c, 0x34, 0x4e, 0x0e, 0xb2, 0xdb, 0x6e, 0x8c, 0xfb, 0x64, + 0x5f, 0x4c, 0x51, 0x7a, 0x4c, 0x1b, 0x25, 0x98, 0xe0, 0xf8, 0x63, 0x78, 0xf8, 0xde, 0x81, 0x85, + 0x54, 0xfe, 0x86, 0xdc, 0x93, 0x9c, 0xfe, 0xb0, 0x00, 0x25, 0x9d, 0x01, 0x83, 0x7c, 0x32, 0x61, + 0xc6, 0x29, 0x55, 0x3f, 0x6c, 0x3c, 0x75, 0xb3, 0xe7, 0x37, 0x1f, 0x1c, 0x95, 0x17, 0x34, 0x72, + 0xca, 0x24, 0x73, 0x09, 0x0a, 0xbd, 0xa0, 0x9d, 0x3e, 0xa7, 0xdd, 0xc6, 0x4d, 0x64, 0xe5, 0xe4, + 0x7e, 0xda, 0x8e, 0xb2, 0x95, 0x53, 0xd6, 0x0e, 0x71, 0xa0, 0x19, 0x6c, 0x3f, 0x61, 0xbb, 0xe4, + 0x8e, 0xdf, 0x3c, 0x4c, 0x3f, 0x8d, 0x53, 0xf5, 0x9b, 0x87, 0xc8, 0x21, 0xe4, 0x35, 0x98, 0x8f, + 0xdc, 0x0e, 0xf5, 0x7b, 0x91, 0xf9, 0x60, 0x78, 0x21, 0xbe, 0x7c, 0xdf, 0x4e, 0x40, 0x31, 0x85, + 0xcd, 0x76, 0xd9, 0xbb, 0xa1, 0xef, 0xf1, 0xd4, 0xb5, 0x53, 0xc9, 0x9b, 0xba, 0x1b, 0xf5, 0x37, + 0x6f, 0x72, 0x73, 0x92, 0xc6, 0x60, 0xd8, 0x2e, 0x0f, 0xca, 0x0e, 0xa8, 0xf4, 0x7d, 0x59, 0x8c, + 0xf5, 0x71, 0x51, 0x8e, 0x1a, 0x83, 0xac, 0x0b, 0xda, 0x4c, 0x5a, 0xbe, 0xa3, 0xcc, 0x55, 0xaf, + 0x28, 0xba, 0xac, 0xec, 0xc1, 0xd1, 0x09, 0x86, 0x3e, 0x5d, 0xd3, 0xbe, 0x0d, 0x0b, 0xa9, 0x06, + 0x53, 0xe7, 0x6a, 0x2b, 0xfb, 0x5c, 0x3d, 0xdc, 0x6b, 0x36, 0xff, 0xd2, 0x82, 0xb3, 0x7d, 0x4b, + 0xc0, 0xb0, 0x21, 0x77, 0xe9, 0xcd, 0x68, 0xe2, 0xd1, 0x37, 0xa3, 0xc2, 0x68, 0x9b, 0x51, 0x75, + 0xe7, 0xbb, 0x3f, 0xb8, 0xfc, 0xc4, 0xf7, 0x7e, 0x70, 0xf9, 0x89, 0x3f, 0xf9, 0xc1, 0xe5, 0x27, + 0xde, 0x3b, 0xbe, 0x6c, 0x7d, 0xf7, 0xf8, 0xb2, 0xf5, 0xbd, 0xe3, 0xcb, 0xd6, 0x9f, 0x1c, 0x5f, + 0xb6, 0xfe, 0xf3, 0xf1, 0x65, 0xeb, 0x9b, 0x3f, 0xbc, 0xfc, 0xc4, 0x67, 0x3f, 0x1d, 0x0f, 0xd0, + 0x55, 0x35, 0x40, 0xf9, 0x8f, 0x8f, 0xa9, 0xe1, 0xb8, 0xda, 0xdd, 0x6f, 0xad, 0xb2, 0x01, 0xba, + 0xaa, 0x4b, 0xd4, 0x00, 0xfd, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x3c, 0xb1, 0xba, 0xa7, + 0x9e, 0x00, 0x00, } func (m *ALBStatus) Marshal() (dAtA []byte, err error) { @@ -3824,6 +3827,11 @@ func (m *ALBStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + i -= len(m.Ingress) + copy(dAtA[i:], m.Ingress) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Ingress))) + i-- + dAtA[i] = 0x22 { size, err := m.StableTargetGroup.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -3877,6 +3885,15 @@ func (m *ALBTrafficRouting) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Ingresses) > 0 { + for iNdEx := len(m.Ingresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Ingresses[iNdEx]) + copy(dAtA[i:], m.Ingresses[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Ingresses[iNdEx]))) + i-- + dAtA[i] = 0x32 + } + } if m.StickinessConfig != nil { { size, err := m.StickinessConfig.MarshalToSizedBuffer(dAtA[:i]) @@ -8568,6 +8585,22 @@ func (m *RolloutStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ALBs) > 0 { + for iNdEx := len(m.ALBs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ALBs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xd2 + } + } if m.ALB != nil { { size, err := m.ALB.MarshalToSizedBuffer(dAtA[:i]) @@ -10052,6 +10085,8 @@ func (m *ALBStatus) Size() (n int) { n += 1 + l + sovGenerated(uint64(l)) l = m.StableTargetGroup.Size() n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Ingress) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -10072,6 +10107,12 @@ func (m *ALBTrafficRouting) Size() (n int) { l = m.StickinessConfig.Size() n += 1 + l + sovGenerated(uint64(l)) } + if len(m.Ingresses) > 0 { + for _, s := range m.Ingresses { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -11855,6 +11896,12 @@ func (m *RolloutStatus) Size() (n int) { l = m.ALB.Size() n += 2 + l + sovGenerated(uint64(l)) } + if len(m.ALBs) > 0 { + for _, e := range m.ALBs { + l = e.Size() + n += 2 + l + sovGenerated(uint64(l)) + } + } return n } @@ -12352,6 +12399,7 @@ func (this *ALBStatus) String() string { `LoadBalancer:` + strings.Replace(strings.Replace(this.LoadBalancer.String(), "AwsResourceRef", "AwsResourceRef", 1), `&`, ``, 1) + `,`, `CanaryTargetGroup:` + strings.Replace(strings.Replace(this.CanaryTargetGroup.String(), "AwsResourceRef", "AwsResourceRef", 1), `&`, ``, 1) + `,`, `StableTargetGroup:` + strings.Replace(strings.Replace(this.StableTargetGroup.String(), "AwsResourceRef", "AwsResourceRef", 1), `&`, ``, 1) + `,`, + `Ingress:` + fmt.Sprintf("%v", this.Ingress) + `,`, `}`, }, "") return s @@ -12366,6 +12414,7 @@ func (this *ALBTrafficRouting) String() string { `RootService:` + fmt.Sprintf("%v", this.RootService) + `,`, `AnnotationPrefix:` + fmt.Sprintf("%v", this.AnnotationPrefix) + `,`, `StickinessConfig:` + strings.Replace(this.StickinessConfig.String(), "StickinessConfig", "StickinessConfig", 1) + `,`, + `Ingresses:` + fmt.Sprintf("%v", this.Ingresses) + `,`, `}`, }, "") return s @@ -13694,6 +13743,11 @@ func (this *RolloutStatus) String() string { repeatedStringForConditions += strings.Replace(strings.Replace(f.String(), "RolloutCondition", "RolloutCondition", 1), `&`, ``, 1) + "," } repeatedStringForConditions += "}" + repeatedStringForALBs := "[]ALBStatus{" + for _, f := range this.ALBs { + repeatedStringForALBs += strings.Replace(strings.Replace(f.String(), "ALBStatus", "ALBStatus", 1), `&`, ``, 1) + "," + } + repeatedStringForALBs += "}" s := strings.Join([]string{`&RolloutStatus{`, `Abort:` + fmt.Sprintf("%v", this.Abort) + `,`, `PauseConditions:` + repeatedStringForPauseConditions + `,`, @@ -13720,6 +13774,7 @@ func (this *RolloutStatus) String() string { `Message:` + fmt.Sprintf("%v", this.Message) + `,`, `WorkloadObservedGeneration:` + fmt.Sprintf("%v", this.WorkloadObservedGeneration) + `,`, `ALB:` + strings.Replace(this.ALB.String(), "ALBStatus", "ALBStatus", 1) + `,`, + `ALBs:` + repeatedStringForALBs + `,`, `}`, }, "") return s @@ -14230,6 +14285,38 @@ func (m *ALBStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ingress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ingress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -14431,6 +14518,38 @@ func (m *ALBTrafficRouting) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ingresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ingresses = append(m.Ingresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -29690,6 +29809,40 @@ func (m *RolloutStatus) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 26: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ALBs", 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 + } + m.ALBs = append(m.ALBs, ALBStatus{}) + if err := m.ALBs[len(m.ALBs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/rollouts/v1alpha1/generated.proto b/pkg/apis/rollouts/v1alpha1/generated.proto index ab5c65258e..22e777df86 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -37,6 +37,8 @@ message ALBStatus { optional AwsResourceRef canaryTargetGroup = 2; optional AwsResourceRef stableTargetGroup = 3; + + optional string ingress = 4; } // ALBTrafficRouting configuration for ALB ingress controller to control traffic routing @@ -50,13 +52,17 @@ message ALBTrafficRouting { // RootService references the service in the ingress to the controller should add the action to optional string rootService = 3; - // AdditionalForwardConfig allows to specify further settings on the ForwaredConfig + // AnnotationPrefix has to match the configured annotation prefix on the alb ingress controller + // +optional + optional string annotationPrefix = 4; + + // StickinessConfig refers to the duration-based stickiness of the target groups associated with an `Ingress` // +optional optional StickinessConfig stickinessConfig = 5; - // AnnotationPrefix has to match the configured annotation prefix on the alb ingress controller + // Ingresses refers to the name of an `Ingress` resource in the same namespace as the `Rollout` in a multi ingress scenario // +optional - optional string annotationPrefix = 4; + repeated string ingresses = 6; } // AmbassadorTrafficRouting defines the configuration required to use Ambassador as traffic @@ -1480,6 +1486,9 @@ message RolloutStatus { // / ALB keeps information regarding the ALB and TargetGroups optional ALBStatus alb = 25; + + // / ALBs keeps information regarding multiple ALBs and TargetGroups in a multi ingress scenario + repeated ALBStatus albs = 26; } // RolloutStrategy defines strategy to apply during next rollout diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 79251e03a0..fdb24d4d31 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -168,6 +168,12 @@ func schema_pkg_apis_rollouts_v1alpha1_ALBStatus(ref common.ReferenceCallback) c Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AwsResourceRef"), }, }, + "ingress": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, @@ -186,7 +192,6 @@ func schema_pkg_apis_rollouts_v1alpha1_ALBTrafficRouting(ref common.ReferenceCal "ingress": { SchemaProps: spec.SchemaProps{ Description: "Ingress refers to the name of an `Ingress` resource in the same namespace as the `Rollout`", - Default: "", Type: []string{"string"}, Format: "", }, @@ -206,21 +211,36 @@ func schema_pkg_apis_rollouts_v1alpha1_ALBTrafficRouting(ref common.ReferenceCal Format: "", }, }, + "annotationPrefix": { + SchemaProps: spec.SchemaProps{ + Description: "AnnotationPrefix has to match the configured annotation prefix on the alb ingress controller", + Type: []string{"string"}, + Format: "", + }, + }, "stickinessConfig": { SchemaProps: spec.SchemaProps{ - Description: "AdditionalForwardConfig allows to specify further settings on the ForwaredConfig", + Description: "StickinessConfig refers to the duration-based stickiness of the target groups associated with an `Ingress`", Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StickinessConfig"), }, }, - "annotationPrefix": { + "ingresses": { SchemaProps: spec.SchemaProps{ - Description: "AnnotationPrefix has to match the configured annotation prefix on the alb ingress controller", - Type: []string{"string"}, - Format: "", + Description: "Ingresses refers to the name of an `Ingress` resource in the same namespace as the `Rollout` in a multi ingress scenario", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, }, }, }, - Required: []string{"ingress", "servicePort"}, + Required: []string{"servicePort"}, }, }, Dependencies: []string{ @@ -4406,6 +4426,20 @@ func schema_pkg_apis_rollouts_v1alpha1_RolloutStatus(ref common.ReferenceCallbac Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ALBStatus"), }, }, + "albs": { + SchemaProps: spec.SchemaProps{ + Description: "/ ALBs keeps information regarding multiple ALBs and TargetGroups in a multi ingress scenario", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ALBStatus"), + }, + }, + }, + }, + }, }, }, }, diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 6f93585fd9..f2c2dbe7fe 100755 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -335,17 +335,20 @@ type AnalysisRunStrategy struct { // ALBTrafficRouting configuration for ALB ingress controller to control traffic routing type ALBTrafficRouting struct { // Ingress refers to the name of an `Ingress` resource in the same namespace as the `Rollout` - Ingress string `json:"ingress" protobuf:"bytes,1,opt,name=ingress"` + Ingress string `json:"ingress,omitempty" protobuf:"bytes,1,opt,name=ingress"` // ServicePort refers to the port that the Ingress action should route traffic to ServicePort int32 `json:"servicePort" protobuf:"varint,2,opt,name=servicePort"` // RootService references the service in the ingress to the controller should add the action to RootService string `json:"rootService,omitempty" protobuf:"bytes,3,opt,name=rootService"` - // AdditionalForwardConfig allows to specify further settings on the ForwaredConfig - // +optional - StickinessConfig *StickinessConfig `json:"stickinessConfig,omitempty" protobuf:"bytes,5,opt,name=stickinessConfig"` // AnnotationPrefix has to match the configured annotation prefix on the alb ingress controller // +optional AnnotationPrefix string `json:"annotationPrefix,omitempty" protobuf:"bytes,4,opt,name=annotationPrefix"` + // StickinessConfig refers to the duration-based stickiness of the target groups associated with an `Ingress` + // +optional + StickinessConfig *StickinessConfig `json:"stickinessConfig,omitempty" protobuf:"bytes,5,opt,name=stickinessConfig"` + // Ingresses refers to the name of an `Ingress` resource in the same namespace as the `Rollout` in a multi ingress scenario + // +optional + Ingresses []string `json:"ingresses,omitempty" protobuf:"bytes,6,opt,name=ingresses"` } type StickinessConfig struct { @@ -934,6 +937,8 @@ type RolloutStatus struct { WorkloadObservedGeneration string `json:"workloadObservedGeneration,omitempty" protobuf:"bytes,24,opt,name=workloadObservedGeneration"` /// ALB keeps information regarding the ALB and TargetGroups ALB *ALBStatus `json:"alb,omitempty" protobuf:"bytes,25,opt,name=alb"` + /// ALBs keeps information regarding multiple ALBs and TargetGroups in a multi ingress scenario + ALBs []ALBStatus `json:"albs,omitempty" protobuf:"bytes,26,opt,name=albs"` } // BlueGreenStatus status fields that only pertain to the blueGreen rollout @@ -1006,6 +1011,7 @@ type ALBStatus struct { LoadBalancer AwsResourceRef `json:"loadBalancer,omitempty" protobuf:"bytes,1,opt,name=loadBalancer"` CanaryTargetGroup AwsResourceRef `json:"canaryTargetGroup,omitempty" protobuf:"bytes,2,opt,name=canaryTargetGroup"` StableTargetGroup AwsResourceRef `json:"stableTargetGroup,omitempty" protobuf:"bytes,3,opt,name=stableTargetGroup"` + Ingress string `json:"ingress,omitempty" protobuf:"bytes,4,opt,name=ingress"` } type AwsResourceRef struct { diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index ed84121b94..34f84f7659 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -56,6 +56,11 @@ func (in *ALBTrafficRouting) DeepCopyInto(out *ALBTrafficRouting) { *out = new(StickinessConfig) **out = **in } + if in.Ingresses != nil { + in, out := &in.Ingresses, &out.Ingresses + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -2343,6 +2348,11 @@ func (in *RolloutStatus) DeepCopyInto(out *RolloutStatus) { *out = new(ALBStatus) **out = **in } + if in.ALBs != nil { + in, out := &in.ALBs, &out.ALBs + *out = make([]ALBStatus, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/apis/rollouts/validation/validation_references.go b/pkg/apis/rollouts/validation/validation_references.go index 85a92eac7e..85040c71c5 100644 --- a/pkg/apis/rollouts/validation/validation_references.go +++ b/pkg/apis/rollouts/validation/validation_references.go @@ -249,14 +249,27 @@ func validateNginxIngress(canary *v1alpha1.CanaryStrategy, ingress *ingressutil. } func validateAlbIngress(canary *v1alpha1.CanaryStrategy, ingress *ingressutil.Ingress, fldPath *field.Path) field.ErrorList { - fldPath = fldPath.Child("alb").Child("ingress") - ingressName := canary.TrafficRouting.ALB.Ingress - serviceName := canary.StableService - if canary.TrafficRouting.ALB.RootService != "" { - serviceName = canary.TrafficRouting.ALB.RootService + ingresses := canary.TrafficRouting.ALB.Ingresses + allErrs := field.ErrorList{} + // If there are multiple ALB ingresses, and one of them is being validated, + // use that ingress name. + if ingresses != nil && slices.Contains(ingresses, ingress.GetName()) { + fldPath = fldPath.Child("alb").Child("ingresses") + serviceName := canary.StableService + ingressName := ingress.GetName() + if canary.TrafficRouting.ALB.RootService != "" { + serviceName = canary.TrafficRouting.ALB.RootService + } + return reportErrors(ingress, serviceName, ingressName, fldPath, allErrs) + } else { + fldPath = fldPath.Child("alb").Child("ingress") + serviceName := canary.StableService + ingressName := canary.TrafficRouting.ALB.Ingress + if canary.TrafficRouting.ALB.RootService != "" { + serviceName = canary.TrafficRouting.ALB.RootService + } + return reportErrors(ingress, serviceName, ingressName, fldPath, allErrs) } - - return reportErrors(ingress, serviceName, ingressName, fldPath, field.ErrorList{}) } func reportErrors(ingress *ingressutil.Ingress, serviceName, ingressName string, fldPath *field.Path, allErrs field.ErrorList) field.ErrorList { @@ -292,6 +305,31 @@ func ValidateRolloutNginxIngressesConfig(r *v1alpha1.Rollout) error { return err } +// ValidateRolloutAlbIngressesConfig checks that only one or the other of the two fields +// (Ingress, Ingresses) is defined on the ALB struct +func ValidateRolloutAlbIngressesConfig(r *v1alpha1.Rollout) error { + fldPath := field.NewPath("spec", "strategy", "canary", "trafficRouting", "alb") + var err error + + // If the traffic strategy isn't canary -> ALB, no need to validate + // fields on ALB struct. + if r.Spec.Strategy.Canary == nil || + r.Spec.Strategy.Canary.TrafficRouting == nil || + r.Spec.Strategy.Canary.TrafficRouting.ALB == nil { + return nil + } + + // If both Ingress and Ingresses are configured or if neither are configured, + // that's an error. It must be one or the other. + if ingressutil.MultipleAlbIngressesConfigured(r) && ingressutil.SingleAlbIngressConfigured(r) { + err = field.InternalError(fldPath, fmt.Errorf("Either Ingress or Ingresses must be configured. Both are configured.")) + } else if !(ingressutil.MultipleAlbIngressesConfigured(r) || ingressutil.SingleAlbIngressConfigured(r)) { + err = field.InternalError(fldPath, fmt.Errorf("Either Ingress or Ingresses must be configured. Neither are configured.")) + } + + return err +} + // ValidateRolloutVirtualServicesConfig checks either VirtualService or VirtualServices configured // It returns an error if both VirtualService and VirtualServices are configured. // Also, returns an error if both are not configured. diff --git a/pkg/apis/rollouts/validation/validation_references_test.go b/pkg/apis/rollouts/validation/validation_references_test.go index 55468838a1..e0597bf8b1 100644 --- a/pkg/apis/rollouts/validation/validation_references_test.go +++ b/pkg/apis/rollouts/validation/validation_references_test.go @@ -250,7 +250,7 @@ func getAnalysisTemplatesWithType() AnalysisTemplatesWithType { } } -func getAlbRollout() *v1alpha1.Rollout { +func getAlbRollout(ingress string) *v1alpha1.Rollout { return &v1alpha1.Rollout{ Spec: v1alpha1.RolloutSpec{ Strategy: v1alpha1.RolloutStrategy{ @@ -258,7 +258,24 @@ func getAlbRollout() *v1alpha1.Rollout { StableService: "stable-service-name", TrafficRouting: &v1alpha1.RolloutTrafficRouting{ ALB: &v1alpha1.ALBTrafficRouting{ - Ingress: "alb-ingress", + Ingress: ingress, + }, + }, + }, + }, + }, + } +} + +func getAlbRolloutMultiIngress(ingressNames []string) *v1alpha1.Rollout { + return &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + StableService: "stable-service-name", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingresses: ingressNames, }, }, }, @@ -374,7 +391,7 @@ func TestValidateRolloutReferencedResources(t *testing.T) { ServiceWithType: []ServiceWithType{getServiceWithType()}, VirtualServices: nil, } - allErrs := ValidateRolloutReferencedResources(getAlbRollout(), refResources) + allErrs := ValidateRolloutReferencedResources(getAlbRollout("alb-ingress"), refResources) assert.Empty(t, allErrs) } @@ -504,9 +521,139 @@ func TestValidateRolloutReferencedResourcesNginxIngress(t *testing.T) { } } +func TestValidateRolloutReferencedResourcesAlbIngress(t *testing.T) { + stableService := "stable-service" + wrongService := "wrong-stable-service" + stableIngressKey := "spec.strategy.canary.trafficRouting.alb.ingress" + stableIngressesKey := "spec.strategy.canary.trafficRouting.alb.ingresses" + tests := []struct { + name string + multipleIngresses bool + ingresses []string + services []string + expectedErrors [][]string + }{ + { + "Validate single ALB Ingress -- success", + false, + []string{StableIngress}, + []string{stableService}, + [][]string{}, + }, + { + "Validate single ALB Ingress -- failure", + false, + []string{StableIngress}, + []string{wrongService}, + [][]string{{stableIngressKey, StableIngress}}, + }, + { + "Validate multiple ALB Ingresses successfully", + true, + []string{ + StableIngress, + AddStableIngress1, + AddStableIngress2, + }, + []string{ + stableService, + stableService, + stableService, + }, + [][]string{}, + }, + { + "Validate multiple ALB Ingresses -- primary fails", + true, + []string{ + StableIngress, + AddStableIngress1, + AddStableIngress2, + }, + []string{ + wrongService, + stableService, + stableService, + }, + [][]string{{stableIngressesKey, StableIngress}}, + }, + { + "Validate multiple ALB Ingresses -- additional ingress fails", + true, + []string{ + StableIngress, + AddStableIngress1, + AddStableIngress2, + }, + []string{ + stableService, + wrongService, + stableService, + }, + [][]string{{stableIngressesKey, AddStableIngress1}}, + }, + { + "Validate multiple ALB Ingresses -- all ingresses fail fails", + true, + []string{ + StableIngress, + AddStableIngress1, + AddStableIngress2, + }, + []string{ + wrongService, + wrongService, + wrongService, + }, + [][]string{ + {stableIngressesKey, StableIngress}, + {stableIngressesKey, AddStableIngress1}, + {stableIngressesKey, AddStableIngress2}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + var ingresses []ingressutil.Ingress + for i, service := range test.services { + ingress := extensionsIngress(test.ingresses[i], 80, service) + legacyIngress := ingressutil.NewLegacyIngress(ingress) + ingresses = append(ingresses, *legacyIngress) + } + refResources := ReferencedResources{ + AnalysisTemplatesWithType: []AnalysisTemplatesWithType{getAnalysisTemplatesWithType()}, + Ingresses: ingresses, + ServiceWithType: []ServiceWithType{getServiceWithType()}, + VirtualServices: nil, + } + + var allErrs field.ErrorList + if test.multipleIngresses { + allErrs = ValidateRolloutReferencedResources(getAlbRolloutMultiIngress([]string{StableIngress, AddStableIngress1, AddStableIngress2}), refResources) + } else { + allErrs = ValidateRolloutReferencedResources(getAlbRollout(StableIngress), refResources) + } + + if len(test.expectedErrors) > 0 { + assert.Len(t, allErrs, len(test.expectedErrors), "Errors should be present.") + for i, e := range test.expectedErrors { + assert.Equal(t, field.ErrorType("FieldValueInvalid"), allErrs[i].Type, "Should be bad service name for ingress") + assert.Equal(t, e[0], allErrs[i].Field, "Bad service name for ingress") + assert.Equal(t, e[1], allErrs[i].BadValue, "Bad service name for ingress") + } + } else { + assert.Empty(t, allErrs) + } + }) + } +} + func TestValidateAnalysisTemplatesWithType(t *testing.T) { t.Run("failure - invalid argument", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") templates := getAnalysisTemplatesWithType() templates.AnalysisTemplates[0].Spec.Args = append(templates.AnalysisTemplates[0].Spec.Args, v1alpha1.Argument{Name: "invalid"}) allErrs := ValidateAnalysisTemplatesWithType(rollout, templates) @@ -516,7 +663,7 @@ func TestValidateAnalysisTemplatesWithType(t *testing.T) { }) t.Run("success", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") templates := getAnalysisTemplatesWithType() templates.AnalysisTemplates[0].Spec.Args = append(templates.AnalysisTemplates[0].Spec.Args, v1alpha1.Argument{Name: "valid"}) templates.Args = []v1alpha1.AnalysisRunArgument{{Name: "valid", Value: "true"}} @@ -525,7 +672,7 @@ func TestValidateAnalysisTemplatesWithType(t *testing.T) { }) t.Run("failure - duplicate metrics", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") templates := getAnalysisTemplatesWithType() templates.AnalysisTemplates[0].Spec.Args = append(templates.AnalysisTemplates[0].Spec.Args, v1alpha1.Argument{Name: "metric1-name", Value: pointer.StringPtr("true")}) templates.AnalysisTemplates[0].Spec.Args[0] = v1alpha1.Argument{Name: "valid", Value: pointer.StringPtr("true")} @@ -534,7 +681,7 @@ func TestValidateAnalysisTemplatesWithType(t *testing.T) { }) t.Run("failure - duplicate MeasurementRetention", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") rollout.Spec.Strategy.Canary.Steps = append(rollout.Spec.Strategy.Canary.Steps, v1alpha1.CanaryStep{ Analysis: &v1alpha1.RolloutAnalysis{ Templates: []v1alpha1.RolloutAnalysisTemplate{ @@ -570,14 +717,14 @@ func TestValidateAnalysisTemplatesWithType(t *testing.T) { func TestValidateAnalysisTemplateWithType(t *testing.T) { t.Run("validate analysisTemplate - success", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") templates := getAnalysisTemplatesWithType() allErrs := ValidateAnalysisTemplateWithType(rollout, templates.AnalysisTemplates[0], nil, templates.TemplateType, GetAnalysisTemplateWithTypeFieldPath(templates.TemplateType, templates.CanaryStepIndex)) assert.Empty(t, allErrs) }) t.Run("validate inline clusterAnalysisTemplate - failure", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") count := intstr.FromInt(0) template := getAnalysisTemplatesWithType() template.ClusterAnalysisTemplates[0].Spec.Metrics[0].Count = &count @@ -589,7 +736,7 @@ func TestValidateAnalysisTemplateWithType(t *testing.T) { }) t.Run("validate inline analysisTemplate argument - success", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") template := getAnalysisTemplatesWithType() template.AnalysisTemplates[0].Spec.Args = []v1alpha1.Argument{ { @@ -602,7 +749,7 @@ func TestValidateAnalysisTemplateWithType(t *testing.T) { }) t.Run("validate background analysisTemplate - failure", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") template := getAnalysisTemplatesWithType() template.TemplateType = BackgroundAnalysis template.AnalysisTemplates[0].Spec.Args = []v1alpha1.Argument{ @@ -632,7 +779,7 @@ func TestValidateAnalysisTemplateWithType(t *testing.T) { // verify background analysis matches the arguments in rollout spec t.Run("validate background analysisTemplate - success", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") templates := getAnalysisTemplatesWithType() templates.TemplateType = BackgroundAnalysis @@ -671,7 +818,7 @@ func TestValidateAnalysisTemplateWithType(t *testing.T) { // verify background analysis does not care about a metric that runs indefinitely t.Run("validate background analysisTemplate - success", func(t *testing.T) { - rollout := getAlbRollout() + rollout := getAlbRollout("alb-ingress") count := intstr.FromInt(0) templates := getAnalysisTemplatesWithType() templates.TemplateType = BackgroundAnalysis @@ -685,7 +832,7 @@ func TestValidateAnalysisTemplateWithType(t *testing.T) { func TestValidateAlbIngress(t *testing.T) { t.Run("validate alb ingress - success", func(t *testing.T) { ingress := ingressutil.NewLegacyIngress(getIngress()) - allErrs := ValidateIngress(getAlbRollout(), ingress) + allErrs := ValidateIngress(getAlbRollout("alb-ingress"), ingress) assert.Empty(t, allErrs) }) @@ -693,7 +840,7 @@ func TestValidateAlbIngress(t *testing.T) { ingress := getIngress() ingress.Spec.Rules[0].HTTP.Paths[0].Backend.ServiceName = "not-stable-service" i := ingressutil.NewLegacyIngress(ingress) - allErrs := ValidateIngress(getAlbRollout(), i) + allErrs := ValidateIngress(getAlbRollout("alb-ingress"), i) expectedErr := field.Invalid(field.NewPath("spec", "strategy", "canary", "trafficRouting", "alb", "ingress"), ingress.Name, "ingress `alb-ingress` has no rules using service stable-service-name backend") assert.Equal(t, expectedErr.Error(), allErrs[0].Error()) }) @@ -762,17 +909,80 @@ func TestValidateRolloutNginxIngressesConfig(t *testing.T) { } } +func TestValidateRolloutAlbIngressesConfig(t *testing.T) { + var emptyIngress string + var emptyIngresses []string + stableIngress := "stable-ingress" + stableIngresses := []string{"stable-ingress", "additional-stable-ingress"} + fldPath := field.NewPath("spec", "strategy", "canary", "trafficRouting", "alb") + failureCase1 := field.InternalError(fldPath, fmt.Errorf("Either Ingress or Ingresses must be configured. Neither are configured.")) + failureCase2 := field.InternalError(fldPath, fmt.Errorf("Either Ingress or Ingresses must be configured. Both are configured.")) + + tests := []struct { + name string + Ingress string + Ingresses []string + expected error + }{ + { + "No ingress configured", + emptyIngress, + emptyIngresses, + failureCase1, + }, + { + "Both ingresses configured", + stableIngress, + stableIngresses, + failureCase2, + }, + { + "Just Ingress configured", + stableIngress, + emptyIngresses, + nil, + }, + { + "Just Ingresses configured", + emptyIngress, + stableIngresses, + nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ro := &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: test.Ingress, + Ingresses: test.Ingresses, + }, + }, + }, + }, + }, + } + + assert.Equal(t, test.expected, ValidateRolloutAlbIngressesConfig(ro)) + }) + } +} + func TestValidateService(t *testing.T) { t.Run("validate service - success", func(t *testing.T) { svc := getServiceWithType() - allErrs := ValidateService(svc, getAlbRollout()) + allErrs := ValidateService(svc, getAlbRollout("alb-ingress")) assert.Empty(t, allErrs) }) t.Run("validate service - failure", func(t *testing.T) { svc := getServiceWithType() svc.Service.Annotations = map[string]string{v1alpha1.ManagedByRolloutsKey: "not-rollout-name"} - allErrs := ValidateService(svc, getAlbRollout()) + allErrs := ValidateService(svc, getAlbRollout("alb-ingress")) assert.Len(t, allErrs, 1) expectedErr := field.Invalid(GetServiceWithTypeFieldPath(svc.Type), svc.Service.Name, "Service \"stable-service-name\" is managed by another Rollout") assert.Equal(t, expectedErr.Error(), allErrs[0].Error()) @@ -781,7 +991,7 @@ func TestValidateService(t *testing.T) { t.Run("validate service with unmatch label - failure", func(t *testing.T) { svc := getServiceWithType() svc.Service.Spec.Selector = map[string]string{"app": "unmatch-rollout-label"} - allErrs := ValidateService(svc, getAlbRollout()) + allErrs := ValidateService(svc, getAlbRollout("alb-ingress")) assert.Len(t, allErrs, 1) expectedErr := field.Invalid(GetServiceWithTypeFieldPath(svc.Type), svc.Service.Name, "Service \"stable-service-name\" has unmatch label \"app\" in rollout") assert.Equal(t, expectedErr.Error(), allErrs[0].Error()) @@ -790,7 +1000,7 @@ func TestValidateService(t *testing.T) { t.Run("validate service with Rollout label - success", func(t *testing.T) { svc := getServiceWithType() svc.Service.Spec.Selector = map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: "123-456"} - allErrs := ValidateService(svc, getAlbRollout()) + allErrs := ValidateService(svc, getAlbRollout("alb-ingress")) assert.Empty(t, allErrs) }) } @@ -1135,7 +1345,7 @@ spec: refResources := ReferencedResources{ AppMeshResources: []k8sunstructured.Unstructured{*obj}, } - errList := ValidateRolloutReferencedResources(getAlbRollout(), refResources) + errList := ValidateRolloutReferencedResources(getAlbRollout("alb-ingress"), refResources) assert.NotNil(t, errList) assert.Len(t, errList, 1) assert.Equal(t, errList[0].Detail, "Expected object kind to be VirtualRouter but is VirtualService") diff --git a/rollout/controller.go b/rollout/controller.go index c7e51611c2..b5c51914ae 100644 --- a/rollout/controller.go +++ b/rollout/controller.go @@ -512,6 +512,7 @@ func (c *Controller) newRolloutContext(rollout *v1alpha1.Rollout) (*rolloutConte newStatus: v1alpha1.RolloutStatus{ RestartedAt: rollout.Status.RestartedAt, ALB: rollout.Status.ALB, + ALBs: rollout.Status.ALBs, }, pauseContext: &pauseContext{ rollout: rollout, @@ -579,12 +580,18 @@ func (c *rolloutContext) getRolloutReferencedResources() (*validation.Referenced } refResources.AnalysisTemplatesWithType = *analysisTemplates - // // Validate Rollout Nginx Ingress Controller before referencing + // Validate Rollout Nginx Ingress Controller before referencing err = validation.ValidateRolloutNginxIngressesConfig(c.rollout) if err != nil { return nil, err } + // Validate Rollout ALB Ingress Controller before referencing + err = validation.ValidateRolloutAlbIngressesConfig(c.rollout) + if err != nil { + return nil, err + } + ingresses, err := c.getReferencedIngresses() if err != nil { return nil, err @@ -853,14 +860,27 @@ func (c *rolloutContext) getReferencedNginxIngresses(canary *v1alpha1.CanaryStra return &ingresses, nil } -// return nil, field.Invalid(fldPath.Child("alb", "ingress"), canary.TrafficRouting.ALB.Ingress, err.Error()) func (c *rolloutContext) getReferencedALBIngresses(canary *v1alpha1.CanaryStrategy) (*[]ingressutil.Ingress, error) { ingresses := []ingressutil.Ingress{} - ingress, err := c.ingressWrapper.GetCached(c.rollout.Namespace, canary.TrafficRouting.ALB.Ingress) - if err != nil { - return handleCacheError("alb", []string{"ingress"}, canary.TrafficRouting.ALB.Ingress, err) + + // The rollout resource manages more than 1 ingress. + if canary.TrafficRouting.ALB.Ingresses != nil { + for _, ing := range canary.TrafficRouting.ALB.Ingresses { + ingress, err := c.ingressWrapper.GetCached(c.rollout.Namespace, ing) + if err != nil { + return handleCacheError("alb", []string{"ingresses"}, canary.TrafficRouting.ALB.Ingresses, err) + } + ingresses = append(ingresses, *ingress) + } + } else { + // The rollout resource manages only 1 ingress. + ingress, err := c.ingressWrapper.GetCached(c.rollout.Namespace, canary.TrafficRouting.ALB.Ingress) + if err != nil { + return handleCacheError("alb", []string{"ingress"}, canary.TrafficRouting.ALB.Ingress, err) + } + ingresses = append(ingresses, *ingress) } - ingresses = append(ingresses, *ingress) + return &ingresses, nil } diff --git a/rollout/controller_test.go b/rollout/controller_test.go index 00f811617c..d2c78d70c1 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -1705,6 +1705,98 @@ func TestGetReferencedIngressesALB(t *testing.T) { }) } +func TestGetReferencedIngressesALBMultiIngress(t *testing.T) { + primaryIngress := "alb-ingress-name" + addIngress := "alb-ingress-additional" + ingresses := []string{primaryIngress, addIngress} + f := newFixture(t) + defer f.Close() + r := newCanaryRollout("rollout", 1, nil, nil, nil, intstr.FromInt(0), intstr.FromInt(1)) + r.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingresses: ingresses, + }, + } + r.Namespace = metav1.NamespaceDefault + defer f.Close() + + tests := []struct { + name string + ingresses []*ingressutil.Ingress + expectedErr *field.Error + }{ + { + "get referenced ALB ingress - fail first ingress when both missing", + []*ingressutil.Ingress{}, + field.Invalid(field.NewPath("spec", "strategy", "canary", "trafficRouting", "alb", "ingresses"), ingresses, fmt.Sprintf("ingress.extensions \"%s\" not found", primaryIngress)), + }, + { + "get referenced ALB ingress - fail on primary when additional present", + []*ingressutil.Ingress{ + ingressutil.NewLegacyIngress(&extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: addIngress, + Namespace: metav1.NamespaceDefault, + }, + }), + }, + field.Invalid(field.NewPath("spec", "strategy", "canary", "trafficRouting", "alb", "ingresses"), ingresses, fmt.Sprintf("ingress.extensions \"%s\" not found", primaryIngress)), + }, + { + "get referenced ALB ingress - fail on secondary when only secondary missing", + []*ingressutil.Ingress{ + ingressutil.NewLegacyIngress(&extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: primaryIngress, + Namespace: metav1.NamespaceDefault, + }, + }), + }, + field.Invalid(field.NewPath("spec", "strategy", "canary", "trafficRouting", "alb", "ingresses"), ingresses, fmt.Sprintf("ingress.extensions \"%s\" not found", addIngress)), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} + for _, ing := range test.ingresses { + f.ingressLister = append(f.ingressLister, ing) + } + c, _, _ := f.newController(noResyncPeriodFunc) + roCtx, err := c.newRolloutContext(r) + assert.NoError(t, err) + _, err = roCtx.getReferencedIngresses() + assert.Equal(t, test.expectedErr.Error(), err.Error()) + }) + } + + t.Run("get referenced ALB ingress - success", func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} + ingress := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: primaryIngress, + Namespace: metav1.NamespaceDefault, + }, + } + ingressAdditional := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: addIngress, + Namespace: metav1.NamespaceDefault, + }, + } + f.ingressLister = append(f.ingressLister, ingressutil.NewLegacyIngress(ingress)) + f.ingressLister = append(f.ingressLister, ingressutil.NewLegacyIngress(ingressAdditional)) + c, _, _ := f.newController(noResyncPeriodFunc) + roCtx, err := c.newRolloutContext(r) + assert.NoError(t, err) + ingresses, err := roCtx.getReferencedIngresses() + assert.NoError(t, err) + assert.Len(t, *ingresses, 2, "Should find the main ingress and the additional ingress") + }) +} + func TestGetReferencedIngressesNginx(t *testing.T) { primaryIngress := "nginx-ingress-name" f := newFixture(t) diff --git a/rollout/trafficrouting/alb/alb.go b/rollout/trafficrouting/alb/alb.go index abe615638f..b1b9ff0f25 100644 --- a/rollout/trafficrouting/alb/alb.go +++ b/rollout/trafficrouting/alb/alb.go @@ -24,6 +24,7 @@ import ( jsonutil "github.com/argoproj/argo-rollouts/utils/json" logutil "github.com/argoproj/argo-rollouts/utils/log" "github.com/argoproj/argo-rollouts/utils/record" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" ) const ( @@ -75,43 +76,54 @@ func (r *Reconciler) Type() string { // SetWeight modifies ALB Ingress resources to reach desired state func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) error { - ctx := context.TODO() - rollout := r.cfg.Rollout - ingressName := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress - ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) - if err != nil { - return err - } - actionService := rollout.Spec.Strategy.Canary.StableService - if rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService != "" { - actionService = rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService - } - port := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort - if !ingressutil.HasRuleWithService(ingress, actionService) { - return fmt.Errorf("ingress does not have service `%s` in rules", actionService) + if ingresses := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses; ingresses != nil { + return r.SetWeightPerIngress(desiredWeight, ingresses, additionalDestinations...) + } else { + return r.SetWeightPerIngress(desiredWeight, []string{r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress}, additionalDestinations...) } +} - desiredAnnotations, err := getDesiredAnnotations(ingress, rollout, port, desiredWeight, additionalDestinations...) - if err != nil { - return err - } - desiredIngress := ingressutil.NewIngressWithAnnotations(ingress.Mode(), desiredAnnotations) - patch, modified, err := ingressutil.BuildIngressPatch(ingress.Mode(), ingress, desiredIngress, ingressutil.WithAnnotations()) - if err != nil { - return nil - } - if !modified { - r.log.Info("no changes to the ALB Ingress") - return nil - } - r.log.WithField("patch", string(patch)).Debug("applying ALB Ingress patch") - r.log.WithField("desiredWeight", desiredWeight).Info("updating ALB Ingress") - r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: "PatchingALBIngress"}, "Updating Ingress `%s` to desiredWeight '%d'", ingressName, desiredWeight) +// SetWeightPerIngress modifies each ALB Ingress resource to reach desired state in the scenario of a rollout +func (r *Reconciler) SetWeightPerIngress(desiredWeight int32, ingresses []string, additionalDestinations ...v1alpha1.WeightDestination) error { + for _, ingress := range ingresses { + ctx := context.TODO() + rollout := r.cfg.Rollout + ingressName := ingress + ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) + if err != nil { + return err + } + actionService := rollout.Spec.Strategy.Canary.StableService + if rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService != "" { + actionService = rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService + } + port := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort + if !ingressutil.HasRuleWithService(ingress, actionService) { + return fmt.Errorf("ingress does not have service `%s` in rules", actionService) + } - _, err = r.cfg.IngressWrapper.Patch(ctx, ingress.GetNamespace(), ingress.GetName(), types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - r.log.WithField("err", err.Error()).Error("error patching alb ingress") - return fmt.Errorf("error patching alb ingress `%s`: %v", ingressName, err) + desiredAnnotations, err := getDesiredAnnotations(ingress, rollout, port, desiredWeight, additionalDestinations...) + if err != nil { + return err + } + desiredIngress := ingressutil.NewIngressWithAnnotations(ingress.Mode(), desiredAnnotations) + patch, modified, err := ingressutil.BuildIngressPatch(ingress.Mode(), ingress, desiredIngress, ingressutil.WithAnnotations()) + if err != nil { + return nil + } + if !modified { + r.log.Info("no changes to the ALB Ingress") + return nil + } + r.log.WithField("patch", string(patch)).Debug("applying ALB Ingress patch") + r.log.WithField("desiredWeight", desiredWeight).Info("updating ALB Ingress") + r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: "PatchingALBIngress"}, "Updating Ingress `%s` to desiredWeight '%d'", ingressName, desiredWeight) + + _, err = r.cfg.IngressWrapper.Patch(ctx, ingress.GetNamespace(), ingress.GetName(), types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + r.log.WithField("err", err.Error()).Error("error patching alb ingress") + return fmt.Errorf("error patching alb ingress `%s`: %v", ingressName, err) + } } return nil } @@ -120,45 +132,57 @@ func (r *Reconciler) SetHeaderRoute(headerRoute *v1alpha1.SetHeaderRoute) error if headerRoute == nil { return nil } - ctx := context.TODO() - rollout := r.cfg.Rollout - ingressName := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress - action := headerRoute.Name - port := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort - ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) - if err != nil { - return err + if ingresses := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses; ingresses != nil { + return r.SetHeaderRoutePerIngress(headerRoute, ingresses) + } else { + return r.SetHeaderRoutePerIngress(headerRoute, []string{r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress}) } - desiredAnnotations, err := getDesiredHeaderAnnotations(ingress, rollout, port, headerRoute) - if err != nil { - return err - } - desiredIngress := ingressutil.NewIngressWithSpecAndAnnotations(ingress, desiredAnnotations) - hasRule := ingressutil.HasRuleWithService(ingress, action) - if hasRule && headerRoute.Match == nil { - desiredIngress.RemovePathByServiceName(action) - } - if !hasRule && headerRoute.Match != nil { - desiredIngress.CreateAnnotationBasedPath(action) - } - desiredIngress.SortHttpPaths(rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes) - patch, modified, err := ingressutil.BuildIngressPatch(ingress.Mode(), ingress, desiredIngress, ingressutil.WithAnnotations(), ingressutil.WithSpec()) - if err != nil { - return nil - } - if !modified { - r.log.Info("no changes to the ALB Ingress for header routing") - return nil - } - r.log.WithField("patch", string(patch)).Debug("applying ALB Ingress patch") - r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: "PatchingALBIngress"}, "Updating Ingress `%s` to headerRoute '%d'", ingressName, headerRoute) +} - _, err = r.cfg.IngressWrapper.Patch(ctx, ingress.GetNamespace(), ingress.GetName(), types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - r.log.WithField("err", err.Error()).Error("error patching alb ingress") - return fmt.Errorf("error patching alb ingress `%s`: %v", ingressName, err) +func (r *Reconciler) SetHeaderRoutePerIngress(headerRoute *v1alpha1.SetHeaderRoute, ingresses []string) error { + for _, ingress := range ingresses { + ctx := context.TODO() + rollout := r.cfg.Rollout + ingressName := ingress + action := headerRoute.Name + port := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort + + ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) + if err != nil { + return err + } + + desiredAnnotations, err := getDesiredHeaderAnnotations(ingress, rollout, port, headerRoute) + if err != nil { + return err + } + desiredIngress := ingressutil.NewIngressWithSpecAndAnnotations(ingress, desiredAnnotations) + hasRule := ingressutil.HasRuleWithService(ingress, action) + if hasRule && headerRoute.Match == nil { + desiredIngress.RemovePathByServiceName(action) + } + if !hasRule && headerRoute.Match != nil { + desiredIngress.CreateAnnotationBasedPath(action) + } + desiredIngress.SortHttpPaths(rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes) + patch, modified, err := ingressutil.BuildIngressPatch(ingress.Mode(), ingress, desiredIngress, ingressutil.WithAnnotations(), ingressutil.WithSpec()) + if err != nil { + return nil + } + if !modified { + r.log.Info("no changes to the ALB Ingress for header routing") + return nil + } + r.log.WithField("patch", string(patch)).Debug("applying ALB Ingress patch") + r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: "PatchingALBIngress"}, "Updating Ingress `%s` to headerRoute '%d'", ingressName, headerRoute) + + _, err = r.cfg.IngressWrapper.Patch(ctx, ingress.GetNamespace(), ingress.GetName(), types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + r.log.WithField("err", err.Error()).Error("error patching alb ingress") + return fmt.Errorf("error patching alb ingress `%s`: %v", ingressName, err) + } } return nil } @@ -174,6 +198,7 @@ func (r *Reconciler) getShouldVerifyWeightCfg() bool { func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) (*bool, error) { if !r.getShouldVerifyWeightCfg() { r.cfg.Status.ALB = nil + r.cfg.Status.ALBs = nil return nil, nil } @@ -182,7 +207,7 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations .. // installed in the cluster we want to actually run the rest of the function, so we do not return if // r.cfg.Rollout.Status.ALB is nil. However, if we should not verify, and we have already updated the status once // we return early to avoid calling AWS apis. - if r.cfg.Rollout.Status.ALB != nil { + if r.cfg.Rollout.Status.ALBs != nil || r.cfg.Rollout.Status.ALB != nil { return nil, nil } } @@ -190,106 +215,135 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations .. if r.cfg.Status.ALB == nil { r.cfg.Status.ALB = &v1alpha1.ALBStatus{} } - - ctx := context.TODO() - rollout := r.cfg.Rollout - ingressName := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress - ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) - if err != nil { - return pointer.BoolPtr(false), err - } - resourceIDToDest := map[string]v1alpha1.WeightDestination{} - - stableService, canaryService := trafficrouting.GetStableAndCanaryServices(rollout) - canaryResourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.GetName(), canaryService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) - stableResourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.GetName(), stableService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) - - for _, dest := range additionalDestinations { - resourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.GetName(), dest.ServiceName, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) - resourceIDToDest[resourceID] = dest - } - - loadBalancerStatus := ingress.GetLoadBalancerStatus() - if len(loadBalancerStatus.Ingress) == 0 { - r.log.Infof("LoadBalancer not yet allocated") + albsCount := len(r.cfg.Status.ALBs) + if ingresses := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses; len(ingresses) > 0 { + if albsCount == 0 || albsCount != len(ingresses) { + r.cfg.Status.ALBs = make([]v1alpha1.ALBStatus, len(ingresses)) + } + return r.VerifyWeightPerIngress(desiredWeight, ingresses, additionalDestinations...) + } else { + if albsCount == 0 || albsCount != 1 { + r.cfg.Status.ALBs = make([]v1alpha1.ALBStatus, 1) + } + return r.VerifyWeightPerIngress(desiredWeight, []string{r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress}, additionalDestinations...) } +} - numVerifiedWeights := 0 - for _, lbIngress := range loadBalancerStatus.Ingress { - if lbIngress.Hostname == "" { - continue - } - lb, err := r.aws.FindLoadBalancerByDNSName(ctx, lbIngress.Hostname) +func (r *Reconciler) VerifyWeightPerIngress(desiredWeight int32, ingresses []string, additionalDestinations ...v1alpha1.WeightDestination) (*bool, error) { + var numVerifiedWeights int + numVerifiedWeights = 0 + for i, ingress := range ingresses { + ctx := context.TODO() + rollout := r.cfg.Rollout + ingressName := ingress + ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) if err != nil { - r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifyErrorReason}, conditions.TargetGroupVerifyErrorMessage, canaryService, "unknown", err.Error()) - return pointer.BoolPtr(false), err + return pointer.Bool(false), err } - if lb == nil || lb.LoadBalancerArn == nil { - r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.LoadBalancerNotFoundReason}, conditions.LoadBalancerNotFoundMessage, lbIngress.Hostname) - return pointer.BoolPtr(false), nil + resourceIDToDest := map[string]v1alpha1.WeightDestination{} + + stableService, canaryService := trafficrouting.GetStableAndCanaryServices(rollout) + canaryResourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.GetName(), canaryService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) + stableResourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.GetName(), stableService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) + + for _, dest := range additionalDestinations { + resourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.GetName(), dest.ServiceName, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) + resourceIDToDest[resourceID] = dest } - r.cfg.Status.ALB.LoadBalancer.Name = *lb.LoadBalancerName - r.cfg.Status.ALB.LoadBalancer.ARN = *lb.LoadBalancerArn - if lbArnParts := strings.Split(*lb.LoadBalancerArn, "/"); len(lbArnParts) > 2 { - r.cfg.Status.ALB.LoadBalancer.FullName = strings.Join(lbArnParts[2:], "/") - } else { - r.cfg.Status.ALB.LoadBalancer.FullName = "" - r.log.Errorf("error parsing load balancer arn: '%s'", *lb.LoadBalancerArn) + loadBalancerStatus := ingress.GetLoadBalancerStatus() + if len(loadBalancerStatus.Ingress) == 0 { + r.log.Infof("LoadBalancer not yet allocated") } - lbTargetGroups, err := r.aws.GetTargetGroupMetadata(ctx, *lb.LoadBalancerArn) - if err != nil { - r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifyErrorReason}, conditions.TargetGroupVerifyErrorMessage, canaryService, "unknown", err.Error()) - return pointer.BoolPtr(false), err - } - logCtx := r.log.WithField("lb", *lb.LoadBalancerArn) - for _, tg := range lbTargetGroups { - if tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID] == canaryResourceID { - r.cfg.Status.ALB.CanaryTargetGroup.Name = *tg.TargetGroupName - r.cfg.Status.ALB.CanaryTargetGroup.ARN = *tg.TargetGroupArn - if tgArnParts := strings.Split(*tg.TargetGroupArn, "/"); len(tgArnParts) > 1 { - r.cfg.Status.ALB.CanaryTargetGroup.FullName = strings.Join(tgArnParts[1:], "/") - } else { - r.cfg.Status.ALB.CanaryTargetGroup.FullName = "" - r.log.Errorf("error parsing canary target group arn: '%s'", *tg.TargetGroupArn) - } - if tg.Weight != nil { - logCtx := logCtx.WithField("tg", *tg.TargetGroupArn) - logCtx.Infof("canary weight of %s (desired: %d, current: %d)", canaryResourceID, desiredWeight, *tg.Weight) - verified := *tg.Weight == desiredWeight - if verified { - numVerifiedWeights += 1 - r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight) - } else { - r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight, *tg.Weight) - } - } - } else if dest, ok := resourceIDToDest[tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID]]; ok { + for _, lbIngress := range loadBalancerStatus.Ingress { + if lbIngress.Hostname == "" { + continue + } + lb, err := r.aws.FindLoadBalancerByDNSName(ctx, lbIngress.Hostname) + if err != nil { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifyErrorReason}, conditions.TargetGroupVerifyErrorMessage, canaryService, "unknown", err.Error()) + return pointer.Bool(false), err + } + if lb == nil || lb.LoadBalancerArn == nil { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.LoadBalancerNotFoundReason}, conditions.LoadBalancerNotFoundMessage, lbIngress.Hostname) + return pointer.Bool(false), nil + } + + r.cfg.Status.ALBs[i].Ingress = ingressName + r.cfg.Status.ALB.Ingress = ingressName + updateLoadBalancerStatus(&r.cfg.Status.ALBs[i], lb, r.log) + updateLoadBalancerStatus(r.cfg.Status.ALB, lb, r.log) + + lbTargetGroups, err := r.aws.GetTargetGroupMetadata(ctx, *lb.LoadBalancerArn) + if err != nil { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifyErrorReason}, conditions.TargetGroupVerifyErrorMessage, canaryService, "unknown", err.Error()) + return pointer.Bool(false), err + } + logCtx := r.log.WithField("lb", *lb.LoadBalancerArn) + for _, tg := range lbTargetGroups { + updateTargetGroupStatus(&r.cfg.Status.ALBs[i], &tg, canaryResourceID, stableResourceID, r.log) + updateTargetGroupStatus(r.cfg.Status.ALB, &tg, canaryResourceID, stableResourceID, r.log) if tg.Weight != nil { - logCtx := logCtx.WithField("tg", *tg.TargetGroupArn) - logCtx.Infof("%s weight of %s (desired: %d, current: %d)", dest.ServiceName, tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID], dest.Weight, *tg.Weight) - verified := *tg.Weight == dest.Weight - if verified { - numVerifiedWeights += 1 - r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedWeightsMessage, dest.ServiceName, *tg.TargetGroupArn, dest.Weight) - } else { - r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedWeightsMessage, dest.ServiceName, *tg.TargetGroupArn, dest.Weight, *tg.Weight) + if tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID] == canaryResourceID { + logCtx := logCtx.WithField("tg", *tg.TargetGroupArn) + logCtx.Infof("canary weight of %s (desired: %d, current: %d)", canaryResourceID, desiredWeight, *tg.Weight) + verified := *tg.Weight == desiredWeight + if verified { + numVerifiedWeights += 1 + r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight) + } else { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight, *tg.Weight) + } + } else if dest, ok := resourceIDToDest[tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID]]; ok { + logCtx := logCtx.WithField("tg", *tg.TargetGroupArn) + logCtx.Infof("%s weight of %s (desired: %d, current: %d)", dest.ServiceName, tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID], dest.Weight, *tg.Weight) + verified := *tg.Weight == dest.Weight + if verified { + numVerifiedWeights += 1 + r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedWeightsMessage, dest.ServiceName, *tg.TargetGroupArn, dest.Weight) + } else { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedWeightsMessage, dest.ServiceName, *tg.TargetGroupArn, dest.Weight, *tg.Weight) + } } } - } else if tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID] == stableResourceID { - r.cfg.Status.ALB.StableTargetGroup.Name = *tg.TargetGroupName - r.cfg.Status.ALB.StableTargetGroup.ARN = *tg.TargetGroupArn - if tgArnParts := strings.Split(*tg.TargetGroupArn, "/"); len(tgArnParts) > 1 { - r.cfg.Status.ALB.StableTargetGroup.FullName = strings.Join(tgArnParts[1:], "/") - } else { - r.cfg.Status.ALB.StableTargetGroup.FullName = "" - r.log.Errorf("error parsing stable target group arn: '%s'", *tg.TargetGroupArn) - } } } } - return pointer.BoolPtr(numVerifiedWeights == 1+len(additionalDestinations)), nil + return pointer.Bool(numVerifiedWeights == len(ingresses)+len(additionalDestinations)), nil +} + +func updateLoadBalancerStatus(status *v1alpha1.ALBStatus, lb *elbv2types.LoadBalancer, log *logrus.Entry) { + status.LoadBalancer.Name = *lb.LoadBalancerName + status.LoadBalancer.ARN = *lb.LoadBalancerArn + if lbArnParts := strings.Split(*lb.LoadBalancerArn, "/"); len(lbArnParts) > 2 { + status.LoadBalancer.FullName = strings.Join(lbArnParts[2:], "/") + } else { + status.LoadBalancer.FullName = "" + log.Errorf("error parsing load balancer arn: '%s'", *lb.LoadBalancerArn) + } +} + +func updateTargetGroupStatus(status *v1alpha1.ALBStatus, tg *aws.TargetGroupMeta, canaryResourceID string, stableResourceID string, log *logrus.Entry) { + if tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID] == canaryResourceID { + status.CanaryTargetGroup.Name = *tg.TargetGroupName + status.CanaryTargetGroup.ARN = *tg.TargetGroupArn + if tgArnParts := strings.Split(*tg.TargetGroupArn, "/"); len(tgArnParts) > 1 { + status.CanaryTargetGroup.FullName = strings.Join(tgArnParts[1:], "/") + } else { + status.CanaryTargetGroup.FullName = "" + log.Errorf("error parsing canary target group arn: '%s'", *tg.TargetGroupArn) + } + } else if tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID] == stableResourceID { + status.StableTargetGroup.Name = *tg.TargetGroupName + status.StableTargetGroup.ARN = *tg.TargetGroupArn + if tgArnParts := strings.Split(*tg.TargetGroupArn, "/"); len(tgArnParts) > 1 { + status.StableTargetGroup.FullName = strings.Join(tgArnParts[1:], "/") + } else { + status.StableTargetGroup.FullName = "" + log.Errorf("error parsing stable target group arn: '%s'", *tg.TargetGroupArn) + } + } } func getForwardActionString(r *v1alpha1.Rollout, port int32, desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) (string, error) { @@ -490,50 +544,61 @@ func (r *Reconciler) RemoveManagedRoutes() error { if len(r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes) == 0 { return nil } - ctx := context.TODO() - rollout := r.cfg.Rollout - ingressName := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress - ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) - if err != nil { - return err + if ingresses := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses; ingresses != nil { + return r.RemoveManagedRoutesPerIngress(ingresses) + } else { + return r.RemoveManagedRoutesPerIngress([]string{r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress}) } +} - desiredAnnotations := ingress.DeepCopy().GetAnnotations() - var actionKeys []string - for _, managedRoute := range rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes { - actionKey := ingressutil.ALBHeaderBasedActionAnnotationKey(rollout, managedRoute.Name) - conditionKey := ingressutil.ALBHeaderBasedConditionAnnotationKey(rollout, managedRoute.Name) - delete(desiredAnnotations, actionKey) - delete(desiredAnnotations, conditionKey) - actionKeys = append(actionKeys, actionKey, conditionKey) - } - desiredAnnotations, err = modifyManagedAnnotation(desiredAnnotations, rollout.Name, false, actionKeys...) - if err != nil { - return err - } +func (r *Reconciler) RemoveManagedRoutesPerIngress(ingresses []string) error { + for _, ingress := range ingresses { + ctx := context.TODO() + rollout := r.cfg.Rollout + ingressName := ingress - desiredIngress := ingressutil.NewIngressWithSpecAndAnnotations(ingress, desiredAnnotations) + ingress, err := r.cfg.IngressWrapper.GetCached(rollout.Namespace, ingressName) + if err != nil { + return err + } - for _, managedRoute := range rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes { - desiredIngress.RemovePathByServiceName(managedRoute.Name) - } + desiredAnnotations := ingress.DeepCopy().GetAnnotations() + var actionKeys []string + for _, managedRoute := range rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes { + actionKey := ingressutil.ALBHeaderBasedActionAnnotationKey(rollout, managedRoute.Name) + conditionKey := ingressutil.ALBHeaderBasedConditionAnnotationKey(rollout, managedRoute.Name) + delete(desiredAnnotations, actionKey) + delete(desiredAnnotations, conditionKey) + actionKeys = append(actionKeys, actionKey, conditionKey) + } + desiredAnnotations, err = modifyManagedAnnotation(desiredAnnotations, rollout.Name, false, actionKeys...) + if err != nil { + return err + } - patch, modified, err := ingressutil.BuildIngressPatch(ingress.Mode(), ingress, desiredIngress, ingressutil.WithAnnotations(), ingressutil.WithSpec()) - if err != nil { - return nil - } - if !modified { - r.log.Info("no changes to the ALB Ingress for header routing") - return nil - } - r.log.WithField("patch", string(patch)).Debug("applying ALB Ingress patch") - r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: "PatchingALBIngress"}, "Updating Ingress `%s` removing managed routes", ingressName) + desiredIngress := ingressutil.NewIngressWithSpecAndAnnotations(ingress, desiredAnnotations) - _, err = r.cfg.IngressWrapper.Patch(ctx, ingress.GetNamespace(), ingress.GetName(), types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - r.log.WithField("err", err.Error()).Error("error patching alb ingress") - return fmt.Errorf("error patching alb ingress `%s`: %v", ingressName, err) + for _, managedRoute := range rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes { + desiredIngress.RemovePathByServiceName(managedRoute.Name) + } + + patch, modified, err := ingressutil.BuildIngressPatch(ingress.Mode(), ingress, desiredIngress, ingressutil.WithAnnotations(), ingressutil.WithSpec()) + if err != nil { + return nil + } + if !modified { + r.log.Info("no changes to the ALB Ingress for header routing") + return nil + } + r.log.WithField("patch", string(patch)).Debug("applying ALB Ingress patch") + r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: "PatchingALBIngress"}, "Updating Ingress `%s` removing managed routes", ingressName) + + _, err = r.cfg.IngressWrapper.Patch(ctx, ingress.GetNamespace(), ingress.GetName(), types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + r.log.WithField("err", err.Error()).Error("error patching alb ingress") + return fmt.Errorf("error patching alb ingress `%s`: %v", ingressName, err) + } } return nil } diff --git a/rollout/trafficrouting/alb/alb_test.go b/rollout/trafficrouting/alb/alb_test.go index 5bb6619d06..e81b456941 100644 --- a/rollout/trafficrouting/alb/alb_test.go +++ b/rollout/trafficrouting/alb/alb_test.go @@ -57,6 +57,30 @@ func fakeRollout(stableSvc, canarySvc string, pingPong *v1alpha1.PingPongSpec, s } } +func fakeRolloutWithMultiIngress(stableSvc, canarySvc string, pingPong *v1alpha1.PingPongSpec, stableIngresses []string, port int32) *v1alpha1.Rollout { + return &v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rollout", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + StableService: stableSvc, + CanaryService: canarySvc, + PingPong: pingPong, + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingresses: stableIngresses, + ServicePort: port, + }, + }, + }, + }, + }, + } +} + const actionTemplate = `{ "Type":"forward", "ForwardConfig":{ @@ -159,6 +183,19 @@ func TestType(t *testing.T) { assert.NoError(t, err) } +func TestTypeMultiIngress(t *testing.T) { + client := fake.NewSimpleClientset() + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", nil, []string{"stable-ingress", "multi-ingress"}, 443) + r, err := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + }) + assert.Equal(t, Type, r.Type()) + assert.NoError(t, err) +} + func TestAddManagedAnnotation(t *testing.T) { annotations, _ := modifyManagedAnnotation(map[string]string{}, "argo-rollouts", true, "alb.ingress.kubernetes.io/actions.action1", "alb.ingress.kubernetes.io/conditions.action1") assert.Equal(t, annotations[ingressutil.ManagedAnnotations], "{\"argo-rollouts\":[\"alb.ingress.kubernetes.io/actions.action1\",\"alb.ingress.kubernetes.io/conditions.action1\"]}") @@ -186,6 +223,26 @@ func TestIngressNotFound(t *testing.T) { assert.True(t, k8serrors.IsNotFound(err)) } +func TestIngressNotFoundMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress("stable-service", "canary-service", nil, []string{"stable-ingress", "multi-ingress"}, 443) + client := fake.NewSimpleClientset() + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.True(t, k8serrors.IsNotFound(err)) +} + func TestServiceNotFoundInIngress(t *testing.T) { ro := fakeRollout("stable-stable", "canary-service", nil, "ingress", 443) ro.Spec.Strategy.Canary.TrafficRouting.ALB.RootService = "invalid-svc" @@ -209,6 +266,31 @@ func TestServiceNotFoundInIngress(t *testing.T) { assert.Errorf(t, err, "ingress does not use the stable service") } +func TestServiceNotFoundInMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress("stable-service", "canary-service", nil, []string{"stable-ingress", "multi-ingress"}, 443) + ro.Spec.Strategy.Canary.TrafficRouting.ALB.RootService = "invalid-svc" + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 50, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 50, ro.Name, false) + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.Errorf(t, err, "ingress does not use the stable service") +} + func TestNoChanges(t *testing.T) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) @@ -232,6 +314,31 @@ func TestNoChanges(t *testing.T) { assert.Len(t, client.Actions(), 0) } +func TestNoChangesMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.Nil(t, err) + assert.Len(t, client.Actions(), 0) +} + func TestErrorOnInvalidManagedBy(t *testing.T) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) @@ -255,6 +362,31 @@ func TestErrorOnInvalidManagedBy(t *testing.T) { assert.Errorf(t, err, "incorrectly formatted managed actions annotation") } +func TestErrorOnInvalidManagedByMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress("stable-service", "canary-service", nil, []string{"stable-ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + mi.Annotations[ingressutil.ManagedAnnotations] = "test" + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.Errorf(t, err, "incorrectly formatted managed actions annotation") +} + func TestSetInitialDesiredWeight(t *testing.T) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) @@ -279,13 +411,38 @@ func TestSetInitialDesiredWeight(t *testing.T) { assert.Len(t, client.Actions(), 1) } +func TestSetInitialDesiredWeightMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + i.Annotations = map[string]string{} + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.Nil(t, err) + assert.Len(t, client.Actions(), 2) +} + func TestSetWeightPingPong(t *testing.T) { pp := &v1alpha1.PingPongSpec{PingService: PING_SVC, PongService: PONG_SVC} ro := fakeRollout("", "", pp, "ingress", 443) ro.Spec.Strategy.Canary.TrafficRouting.ALB.RootService = "root-service" ro.Status.Canary.StablePingPong = PONG_SVC i := ingress("ingress", PING_SVC, PONG_SVC, "root-service", 443, 10, ro.Name, false) - //i.Spec. i.Annotations = map[string]string{} client := fake.NewSimpleClientset(i) k8sI := kubeinformers.NewSharedInformerFactory(client, 0) @@ -308,6 +465,36 @@ func TestSetWeightPingPong(t *testing.T) { assert.Len(t, actions, 1) } +func TestSetWeightPingPongMultiIngress(t *testing.T) { + pp := &v1alpha1.PingPongSpec{PingService: PING_SVC, PongService: PONG_SVC} + ro := fakeRolloutWithMultiIngress("", "", pp, []string{"ingress", "multi-ingress"}, 443) + ro.Spec.Strategy.Canary.TrafficRouting.ALB.RootService = "root-service" + ro.Status.Canary.StablePingPong = PONG_SVC + i := ingress("ingress", PING_SVC, PONG_SVC, "root-service", 443, 10, ro.Name, false) + mi := ingress("multi-ingress", PING_SVC, PONG_SVC, "root-service", 443, 10, ro.Name, false) + i.Annotations = map[string]string{} + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.Nil(t, err) + actions := client.Actions() + assert.Len(t, actions, 2) +} + func TestUpdateDesiredWeightWithStickyConfig(t *testing.T) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, true) @@ -329,6 +516,29 @@ func TestUpdateDesiredWeightWithStickyConfig(t *testing.T) { assert.Len(t, client.Actions(), 1) } +func TestUpdateDesiredWeightWithStickyConfigMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, true) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, true) + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + assert.Nil(t, err) + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.Nil(t, err) + assert.Len(t, client.Actions(), 2) +} + func TestUpdateDesiredWeight(t *testing.T) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) @@ -352,6 +562,31 @@ func TestUpdateDesiredWeight(t *testing.T) { assert.Len(t, client.Actions(), 1) } +func TestUpdateDesiredWeightMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetWeight(10) + assert.Nil(t, err) + assert.Len(t, client.Actions(), 2) +} + // TestGetForwardActionStringMarshalsZeroCorrectly ensures that the annotation does not omit default value zero when marshalling // the forward action func TestGetForwardActionStringMarshalsZeroCorrectly(t *testing.T) { @@ -435,9 +670,42 @@ func TestErrorPatching(t *testing.T) { assert.Len(t, client.Actions(), 1) } +func TestErrorPatchingMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + client := fake.NewSimpleClientset(i, mi) + client.ReactionChain = nil + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + + errMessage := "some error occurred" + r.cfg.Client.(*fake.Clientset).Fake.AddReactor("patch", "ingresses", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, fmt.Errorf(errMessage) + }) + + err = r.SetWeight(10) + assert.Error(t, err, "some error occurred") + assert.Len(t, client.Actions(), 1) +} + type fakeAWSClient struct { + Ingresses []string targetGroups []aws.TargetGroupMeta - loadBalancer *elbv2types.LoadBalancer + loadBalancers []*elbv2types.LoadBalancer targetHealthDescriptions []elbv2types.TargetHealthDescription } @@ -446,16 +714,21 @@ func (f *fakeAWSClient) GetTargetGroupMetadata(ctx context.Context, loadBalancer } func (f *fakeAWSClient) FindLoadBalancerByDNSName(ctx context.Context, dnsName string) (*elbv2types.LoadBalancer, error) { - return f.loadBalancer, nil + for _, lb := range f.loadBalancers { + if lb.DNSName != nil && *lb.DNSName == dnsName { + return lb, nil + } + } + return nil, nil } func (f *fakeAWSClient) GetTargetGroupHealth(ctx context.Context, targetGroupARN string) ([]elbv2types.TargetHealthDescription, error) { return f.targetHealthDescriptions, nil } -func (f *fakeAWSClient) getAlbStatus() *v1alpha1.ALBStatus { +func (f *fakeAWSClient) getAlbStatus(ingress string) *v1alpha1.ALBStatus { LoadBalancerFullName := "" - if lbArnParts := strings.Split(*f.loadBalancer.LoadBalancerArn, "/"); len(lbArnParts) > 2 { + if lbArnParts := strings.Split(*f.loadBalancers[0].LoadBalancerArn, "/"); len(lbArnParts) > 2 { LoadBalancerFullName = strings.Join(lbArnParts[2:], "/") } CanaryTargetGroupFullName := "" @@ -467,9 +740,10 @@ func (f *fakeAWSClient) getAlbStatus() *v1alpha1.ALBStatus { StableTargetGroupFullName = strings.Join(tgArnParts[1:], "/") } return &v1alpha1.ALBStatus{ + Ingress: ingress, LoadBalancer: v1alpha1.AwsResourceRef{ - Name: *f.loadBalancer.LoadBalancerName, - ARN: *f.loadBalancer.LoadBalancerArn, + Name: *f.loadBalancers[0].LoadBalancerName, + ARN: *f.loadBalancers[0].LoadBalancerArn, FullName: LoadBalancerFullName, }, CanaryTargetGroup: v1alpha1.AwsResourceRef{ @@ -485,6 +759,39 @@ func (f *fakeAWSClient) getAlbStatus() *v1alpha1.ALBStatus { } } +func (f *fakeAWSClient) getAlbStatusMultiIngress(ingress string, lbIdx int32, tgIdx int32) *v1alpha1.ALBStatus { + LoadBalancerFullName := "" + if lbArnParts := strings.Split(*f.loadBalancers[lbIdx].LoadBalancerArn, "/"); len(lbArnParts) > 2 { + LoadBalancerFullName = strings.Join(lbArnParts[2:], "/") + } + CanaryTargetGroupFullName := "" + if tgArnParts := strings.Split(*f.targetGroups[tgIdx].TargetGroupArn, "/"); len(tgArnParts) > 1 { + CanaryTargetGroupFullName = strings.Join(tgArnParts[1:], "/") + } + StableTargetGroupFullName := "" + if tgArnParts := strings.Split(*f.targetGroups[tgIdx+1].TargetGroupArn, "/"); len(tgArnParts) > 1 { + StableTargetGroupFullName = strings.Join(tgArnParts[1:], "/") + } + return &v1alpha1.ALBStatus{ + Ingress: ingress, + LoadBalancer: v1alpha1.AwsResourceRef{ + Name: *f.loadBalancers[lbIdx].LoadBalancerName, + ARN: *f.loadBalancers[lbIdx].LoadBalancerArn, + FullName: LoadBalancerFullName, + }, + CanaryTargetGroup: v1alpha1.AwsResourceRef{ + Name: *f.targetGroups[tgIdx].TargetGroupName, + ARN: *f.targetGroups[tgIdx].TargetGroupArn, + FullName: CanaryTargetGroupFullName, + }, + StableTargetGroup: v1alpha1.AwsResourceRef{ + Name: *f.targetGroups[tgIdx+1].TargetGroupName, + ARN: *f.targetGroups[tgIdx+1].TargetGroupArn, + FullName: StableTargetGroupFullName, + }, + } +} + func TestVerifyWeight(t *testing.T) { newFakeReconciler := func(status *v1alpha1.RolloutStatus) (*Reconciler, *fakeAWSClient) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) @@ -530,9 +837,11 @@ func TestVerifyWeight(t *testing.T) { weightVerified, err := r.VerifyWeight(10) assert.NoError(t, err) assert.False(t, *weightVerified) + assert.NotNil(t, status.ALB) + assert.Len(t, status.ALBs, 1) } - // VeryifyWeight not needed + // VerifyWeight not needed { var status v1alpha1.RolloutStatus r, _ := newFakeReconciler(&status) @@ -541,9 +850,11 @@ func TestVerifyWeight(t *testing.T) { weightVerified, err := r.VerifyWeight(10) assert.NoError(t, err) assert.False(t, *weightVerified) + assert.NotNil(t, status.ALB) + assert.Len(t, status.ALBs, 1) } - // VeryifyWeight that we do not need to verify weight and status.ALB is already set + // VerifyWeight that we do not need to verify weight and status.ALB is already set { var status v1alpha1.RolloutStatus r, _ := newFakeReconciler(&status) @@ -559,10 +870,12 @@ func TestVerifyWeight(t *testing.T) { { var status v1alpha1.RolloutStatus r, fakeClient := newFakeReconciler(&status) - fakeClient.loadBalancer = &elbv2types.LoadBalancer{ - LoadBalancerName: pointer.StringPtr("lb-abc123-name"), - LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), - DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, } fakeClient.targetGroups = []aws.TargetGroupMeta{ { @@ -590,17 +903,20 @@ func TestVerifyWeight(t *testing.T) { weightVerified, err := r.VerifyWeight(10) assert.NoError(t, err) assert.False(t, *weightVerified) - assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus()) + assert.Equal(t, status.ALBs[0], *status.ALB) + assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus("ingress")) } // LoadBalancer found, at weight { var status v1alpha1.RolloutStatus r, fakeClient := newFakeReconciler(&status) - fakeClient.loadBalancer = &elbv2types.LoadBalancer{ - LoadBalancerName: pointer.StringPtr("lb-abc123-name"), - LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), - DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, } fakeClient.targetGroups = []aws.TargetGroupMeta{ { @@ -628,17 +944,20 @@ func TestVerifyWeight(t *testing.T) { weightVerified, err := r.VerifyWeight(10) assert.NoError(t, err) assert.True(t, *weightVerified) - assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus()) + assert.Equal(t, status.ALBs[0], *status.ALB) + assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus("ingress")) } // LoadBalancer found, but ARNs are unparsable { var status v1alpha1.RolloutStatus r, fakeClient := newFakeReconciler(&status) - fakeClient.loadBalancer = &elbv2types.LoadBalancer{ - LoadBalancerName: pointer.StringPtr("lb-abc123-name"), - LoadBalancerArn: pointer.StringPtr("lb-abc123-arn"), - DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("lb-abc123-arn"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, } fakeClient.targetGroups = []aws.TargetGroupMeta{ { @@ -666,19 +985,246 @@ func TestVerifyWeight(t *testing.T) { weightVerified, err := r.VerifyWeight(10) assert.NoError(t, err) assert.True(t, *weightVerified) - albStatus := *fakeClient.getAlbStatus() + albStatus := *fakeClient.getAlbStatus("ingress") assert.Equal(t, albStatus.LoadBalancer.FullName, "") assert.Equal(t, albStatus.CanaryTargetGroup.FullName, "") assert.Equal(t, albStatus.StableTargetGroup.FullName, "") } } -func TestSetWeightWithMultipleBackends(t *testing.T) { - ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) - i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 0, ro.Name, false) - client := fake.NewSimpleClientset(i) - k8sI := kubeinformers.NewSharedInformerFactory(client, 0) - k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) +func TestVerifyWeightMultiIngress(t *testing.T) { + newFakeReconciler := func(status *v1alpha1.RolloutStatus) (*Reconciler, *fakeAWSClient) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + ro.Status.StableRS = "a45fe23" + ro.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }} + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + i.Status.LoadBalancer = corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + { + Hostname: "verify-weight-test-abc-123.us-west-2.elb.amazonaws.com", + }, + }, + } + mi.Status.LoadBalancer = corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + { + Hostname: "verify-weight-multi-ingress.us-west-2.elb.amazonaws.com", + }, + }, + } + + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + VerifyWeight: pointer.BoolPtr(true), + Status: status, + }) + assert.NoError(t, err) + fakeAWS := fakeAWSClient{} + r.aws = &fakeAWS + return r, &fakeAWS + } + + // LoadBalancer not found + { + var status v1alpha1.RolloutStatus + r, _ := newFakeReconciler(&status) + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.False(t, *weightVerified) + assert.NotNil(t, status.ALB) + assert.Len(t, status.ALBs, 2) + } + + // VerifyWeight not needed + { + var status v1alpha1.RolloutStatus + r, _ := newFakeReconciler(&status) + status.StableRS = "" + r.cfg.Rollout.Status.StableRS = "" + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.False(t, *weightVerified) + assert.NotNil(t, status.ALB) + assert.Len(t, status.ALBs, 2) + } + + // VerifyWeight that we do not need to verify weight and status.ALB is already set + { + var status v1alpha1.RolloutStatus + r, _ := newFakeReconciler(&status) + r.cfg.Rollout.Status.ALBs = []v1alpha1.ALBStatus{} + r.cfg.Rollout.Status.CurrentStepIndex = nil + r.cfg.Rollout.Spec.Strategy.Canary.Steps = nil + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.Nil(t, weightVerified) + } + + // status.ALBs already set, len not match + { + var status v1alpha1.RolloutStatus + r, _ := newFakeReconciler(&status) + r.cfg.Status.ALBs = []v1alpha1.ALBStatus{{}} + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.False(t, *weightVerified) + assert.Len(t, status.ALBs, 2) + } + + // LoadBalancer found, not at weight + { + var status v1alpha1.RolloutStatus + r, fakeClient := newFakeReconciler(&status) + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, + { + LoadBalancerName: pointer.StringPtr("lb-multi-ingress-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-multi-ingress-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-multi-ingress.us-west-2.elb.amazonaws.com"), + }, + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(11), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(89), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-stable-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/multi-ingress-canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(11), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/multi-ingress-stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(89), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-stable-svc:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.False(t, *weightVerified) + assert.Equal(t, status.ALBs[0], *fakeClient.getAlbStatusMultiIngress("ingress", 0, 0)) + assert.Equal(t, status.ALBs[1], *fakeClient.getAlbStatusMultiIngress("multi-ingress", 1, 2)) + } + + // LoadBalancer found, at weight + { + var status v1alpha1.RolloutStatus + r, fakeClient := newFakeReconciler(&status) + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, + { + LoadBalancerName: pointer.StringPtr("lb-multi-ingress-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-multi-ingress-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-multi-ingress.us-west-2.elb.amazonaws.com"), + }, + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(90), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-stable-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/multi-ingress-canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/multi-ingress-stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(90), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-stable-svc:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.True(t, *weightVerified) + assert.Equal(t, status.ALBs[0], *fakeClient.getAlbStatusMultiIngress("ingress", 0, 0)) + assert.Equal(t, status.ALBs[1], *fakeClient.getAlbStatusMultiIngress("multi-ingress", 1, 2)) + } +} + +func TestSetWeightWithMultipleBackends(t *testing.T) { + ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 0, ro.Name, false) + client := fake.NewSimpleClientset(i) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) if err != nil { t.Fatal(err) @@ -720,6 +1266,55 @@ func TestSetWeightWithMultipleBackends(t *testing.T) { assert.Equal(t, expectedAction, patchedI.Annotations["alb.ingress.kubernetes.io/actions.stable-svc"]) } +func TestSetWeightWithMultipleBackendsMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 0, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 0, ro.Name, false) + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + + weightDestinations := []v1alpha1.WeightDestination{ + { + ServiceName: "ex-svc-1", + PodTemplateHash: "", + Weight: 2, + }, + { + ServiceName: "ex-svc-2", + PodTemplateHash: "", + Weight: 3, + }, + } + err = r.SetWeight(10, weightDestinations...) + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, client.Actions(), 2) + assert.Equal(t, "patch", actions[0].GetVerb()) + + patchedI := extensionsv1beta1.Ingress{} + err = json.Unmarshal(actions[0].(k8stesting.PatchAction).GetPatch(), &patchedI) + assert.Nil(t, err) + + servicePort := 443 + expectedAction := fmt.Sprintf(actionTemplateWithExperiments, CANARY_SVC, servicePort, 10, weightDestinations[0].ServiceName, servicePort, weightDestinations[0].Weight, weightDestinations[1].ServiceName, servicePort, weightDestinations[1].Weight, STABLE_SVC, servicePort, 85) + assert.Equal(t, expectedAction, patchedI.Annotations["alb.ingress.kubernetes.io/actions.stable-svc"]) +} + func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { weightDestinations := []v1alpha1.WeightDestination{ { @@ -776,10 +1371,12 @@ func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { { var status v1alpha1.RolloutStatus r, fakeClient := newFakeReconciler(&status) - fakeClient.loadBalancer = &elbv2types.LoadBalancer{ - LoadBalancerName: pointer.StringPtr("lb-abc123-name"), - LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), - DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, } fakeClient.targetGroups = []aws.TargetGroupMeta{ { @@ -807,17 +1404,19 @@ func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { weightVerified, err := r.VerifyWeight(10, weightDestinations...) assert.NoError(t, err) assert.False(t, *weightVerified) - assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus()) + assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus("ingress")) } // LoadBalancer found, with incorrect weights { var status v1alpha1.RolloutStatus r, fakeClient := newFakeReconciler(&status) - fakeClient.loadBalancer = &elbv2types.LoadBalancer{ - LoadBalancerName: pointer.StringPtr("lb-abc123-name"), - LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), - DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, } fakeClient.targetGroups = []aws.TargetGroupMeta{ { @@ -865,17 +1464,19 @@ func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { weightVerified, err := r.VerifyWeight(10, weightDestinations...) assert.NoError(t, err) assert.False(t, *weightVerified) - assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus()) + assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus("ingress")) } // LoadBalancer found, with all correct weights { var status v1alpha1.RolloutStatus r, fakeClient := newFakeReconciler(&status) - fakeClient.loadBalancer = &elbv2types.LoadBalancer{ - LoadBalancerName: pointer.StringPtr("lb-abc123-name"), - LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), - DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, } fakeClient.targetGroups = []aws.TargetGroupMeta{ { @@ -923,20 +1524,319 @@ func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { weightVerified, err := r.VerifyWeight(10, weightDestinations...) assert.NoError(t, err) assert.True(t, *weightVerified) - assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus()) + assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus("ingress")) } } -func TestSetHeaderRoute(t *testing.T) { - ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) - ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = []v1alpha1.MangedRoutes{ - {Name: "header-route"}, +func TestVerifyWeightWithAdditionalDestinationsMultiIngress(t *testing.T) { + weightDestinations := []v1alpha1.WeightDestination{ + { + ServiceName: "ex-svc-1", + PodTemplateHash: "", + Weight: 2, + }, + { + ServiceName: "ex-svc-2", + PodTemplateHash: "", + Weight: 3, + }, } - i := ingress("ingress", STABLE_SVC, CANARY_SVC, "action1", 443, 10, ro.Name, false) - client := fake.NewSimpleClientset(i) - k8sI := kubeinformers.NewSharedInformerFactory(client, 0) - k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) - ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + newFakeReconciler := func(status *v1alpha1.RolloutStatus) (*Reconciler, *fakeAWSClient) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + ro.Status.StableRS = "a45fe23" + ro.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }} + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 0, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) + i.Annotations["alb.ingress.kubernetes.io/actions.stable-svc"] = fmt.Sprintf(actionTemplateWithExperiments, CANARY_SVC, 443, 10, weightDestinations[0].ServiceName, 443, weightDestinations[0].Weight, weightDestinations[1].ServiceName, 443, weightDestinations[1].Weight, STABLE_SVC, 443, 85) + mi.Annotations["alb.ingress.kubernetes.io/actions.stable-svc"] = fmt.Sprintf(actionTemplateWithExperiments, CANARY_SVC, 443, 10, weightDestinations[0].ServiceName, 443, weightDestinations[0].Weight, weightDestinations[1].ServiceName, 443, weightDestinations[1].Weight, STABLE_SVC, 443, 85) + + i.Status.LoadBalancer = corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + { + Hostname: "verify-weight-test-abc-123.us-west-2.elb.amazonaws.com", + }, + }, + } + mi.Status.LoadBalancer = corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + { + Hostname: "verify-weight-multi-ingress.us-west-2.elb.amazonaws.com", + }, + }, + } + + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + VerifyWeight: pointer.BoolPtr(true), + Status: status, + }) + assert.NoError(t, err) + fakeAWS := fakeAWSClient{} + r.aws = &fakeAWS + return r, &fakeAWS + } + + // LoadBalancer found, but experiment weights not present + { + var status v1alpha1.RolloutStatus + r, fakeClient := newFakeReconciler(&status) + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, + { + LoadBalancerName: pointer.StringPtr("lb-multi-ingress-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-multi-ingress-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-multi-ingress.us-west-2.elb.amazonaws.com"), + }, + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(90), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-stable-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/multi-ingress-canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/multi-ingress-stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(90), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-stable-svc:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10, weightDestinations...) + assert.NoError(t, err) + assert.False(t, *weightVerified) + assert.Equal(t, status.ALBs[0], *fakeClient.getAlbStatusMultiIngress("ingress", 0, 0)) + } + + // LoadBalancer found, with incorrect weights + { + var status v1alpha1.RolloutStatus + r, fakeClient := newFakeReconciler(&status) + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, + { + LoadBalancerName: pointer.StringPtr("lb-multi-ingress-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-multi-ingress-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-multi-ingress.us-west-2.elb.amazonaws.com"), + }, + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(85), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-stable-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/multi-ingress-canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/app/multi-ingress-stable-tg-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(85), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-stable-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("ex-svc-1-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/ex-svc-1-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(100), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-1:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("ex-svc-2-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/ex-svc-2-tg-abc123-name/123456789012345"), + }, + Weight: pointer.Int32Ptr(100), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-2:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10, weightDestinations...) + assert.NoError(t, err) + assert.False(t, *weightVerified) + assert.Equal(t, status.ALBs[0], *fakeClient.getAlbStatusMultiIngress("ingress", 0, 0)) + assert.Equal(t, status.ALBs[1], *fakeClient.getAlbStatusMultiIngress("multi-ingress", 1, 2)) + } + + // LoadBalancer found, with all correct weights + { + var status v1alpha1.RolloutStatus + r, fakeClient := newFakeReconciler(&status) + fakeClient.loadBalancers = []*elbv2types.LoadBalancer{ + { + LoadBalancerName: pointer.StringPtr("lb-abc123-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-abc123-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + }, + { + LoadBalancerName: pointer.StringPtr("lb-multi-ingress-name"), + LoadBalancerArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/lb-multi-ingress-name/1234567890123456"), + DNSName: pointer.StringPtr("verify-weight-multi-ingress.us-west-2.elb.amazonaws.com"), + }, + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/canary-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/stable-tg-abc123-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(85), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-stable-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-canary-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/app/multi-ingress-canary-tg-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("multi-ingress-stable-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/app/multi-ingress-stable-tg-name/1234567890123456"), + }, + Weight: pointer.Int32Ptr(85), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/multi-ingress-stable-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("ex-svc-1-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/ex-svc-1-tg-abc123-name/1234567890123456"), + }, + Weight: &weightDestinations[0].Weight, + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-1:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupName: pointer.StringPtr("ex-svc-2-tg-abc123-name"), + TargetGroupArn: pointer.StringPtr("arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/ex-svc-2-tg-abc123-name/123456789012345"), + }, + Weight: &weightDestinations[1].Weight, + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-2:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10, weightDestinations...) + assert.NoError(t, err) + assert.True(t, *weightVerified) + assert.Equal(t, status.ALBs[0], *fakeClient.getAlbStatusMultiIngress("ingress", 0, 0)) + } +} + +func TestSetHeaderRoute(t *testing.T) { + ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) + ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = []v1alpha1.MangedRoutes{ + {Name: "header-route"}, + } + i := ingress("ingress", STABLE_SVC, CANARY_SVC, "action1", 443, 10, ro.Name, false) + client := fake.NewSimpleClientset(i) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) if err != nil { t.Fatal(err) } @@ -966,6 +1866,47 @@ func TestSetHeaderRoute(t *testing.T) { assert.Len(t, client.Actions(), 1) } +func TestSetHeaderRouteMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = []v1alpha1.MangedRoutes{ + {Name: "header-route"}, + } + i := ingress("ingress", STABLE_SVC, CANARY_SVC, "action1", 443, 10, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, "action2", 443, 10, ro.Name, false) + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "header-route", + Match: []v1alpha1.HeaderRoutingMatch{{ + HeaderName: "Agent", + HeaderValue: &v1alpha1.StringMatch{ + Prefix: "Chrome", + }, + }}, + }) + assert.Nil(t, err) + assert.Len(t, client.Actions(), 2) + + // no managed routes, no changes expected + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + assert.Len(t, client.Actions(), 2) +} + func TestRemoveManagedRoutes(t *testing.T) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = []v1alpha1.MangedRoutes{ @@ -1040,6 +1981,116 @@ func TestRemoveManagedRoutes(t *testing.T) { assert.Len(t, client.Actions(), 2) } +func TestRemoveManagedRoutesMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = []v1alpha1.MangedRoutes{ + {Name: "header-route"}, + } + i := ingress("ingress", STABLE_SVC, CANARY_SVC, "action1", 443, 10, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, "action1", 443, 10, ro.Name, false) + managedByValue := ingressutil.ManagedALBAnnotations{ + ro.Name: ingressutil.ManagedALBAnnotation{ + "alb.ingress.kubernetes.io/actions.action1", + "alb.ingress.kubernetes.io/actions.header-route", + "alb.ingress.kubernetes.io/conditions.header-route", + }, + } + i.Annotations["alb.ingress.kubernetes.io/actions.header-route"] = "{}" + i.Annotations["alb.ingress.kubernetes.io/conditions.header-route"] = "{}" + i.Annotations[ingressutil.ManagedAnnotations] = managedByValue.String() + i.Spec.Rules = []extensionsv1beta1.IngressRule{ + { + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: "action1", + ServicePort: intstr.Parse("use-annotation"), + }, + }, + }, + }, + }, + }, + { + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: "header-route", + ServicePort: intstr.Parse("use-annotation"), + }, + }, + }, + }, + }, + }, + } + + mi.Annotations["alb.ingress.kubernetes.io/actions.header-route"] = "{}" + mi.Annotations["alb.ingress.kubernetes.io/conditions.header-route"] = "{}" + mi.Annotations[ingressutil.ManagedAnnotations] = managedByValue.String() + mi.Spec.Rules = []extensionsv1beta1.IngressRule{ + { + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: "action1", + ServicePort: intstr.Parse("use-annotation"), + }, + }, + }, + }, + }, + }, + { + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: "header-route", + ServicePort: intstr.Parse("use-annotation"), + }, + }, + }, + }, + }, + }, + } + + client := fake.NewSimpleClientset(i, mi) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + + err = r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "header-route", + }) + assert.Nil(t, err) + assert.Len(t, client.Actions(), 2) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + assert.Len(t, client.Actions(), 4) +} + func TestSetMirrorRoute(t *testing.T) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) @@ -1070,3 +2121,36 @@ func TestSetMirrorRoute(t *testing.T) { assert.Len(t, client.Actions(), 0) } + +func TestSetMirrorRouteMultiIngress(t *testing.T) { + ro := fakeRolloutWithMultiIngress(STABLE_SVC, CANARY_SVC, nil, []string{"ingress", "multi-ingress"}, 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) + mi := ingress("multi-ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) + client := fake.NewSimpleClientset() + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(mi) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetMirrorRoute(&v1alpha1.SetMirrorRoute{ + Name: "mirror-route", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{Exact: "GET"}, + }}, + }) + assert.Nil(t, err) + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + + assert.Len(t, client.Actions(), 0) +} diff --git a/test/e2e/alb/rollout-alb-multi-ingress-experiment-no-setweight.yaml b/test/e2e/alb/rollout-alb-multi-ingress-experiment-no-setweight.yaml new file mode 100644 index 0000000000..413fd8af3a --- /dev/null +++ b/test/e2e/alb/rollout-alb-multi-ingress-experiment-no-setweight.yaml @@ -0,0 +1,123 @@ +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-root +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-canary +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-stable +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-rollout-multi-ingress-1 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + pathType: ImplementationSpecific + backend: + service: + name: alb-rollout-root + port: + name: use-annotation +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-rollout-multi-ingress-2 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + pathType: ImplementationSpecific + backend: + service: + name: alb-rollout-root + port: + name: use-annotation +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: alb-rollout +spec: + selector: + matchLabels: + app: alb-rollout + template: + metadata: + labels: + app: alb-rollout + spec: + containers: + - name: alb-rollout + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m + strategy: + canary: + canaryService: alb-rollout-canary + stableService: alb-rollout-stable + trafficRouting: + alb: + ingresses: + - alb-rollout-multi-ingress-1 + - alb-rollout-multi-ingress-2 + rootService: alb-rollout-root + servicePort: 80 + steps: + - experiment: + duration: 15s + templates: + - name: experiment-alb-canary + specRef: canary + weight: 20 + - name: experiment-alb-stable + specRef: stable + weight: 20 diff --git a/test/e2e/alb/rollout-alb-multi-ingress-experiment.yaml b/test/e2e/alb/rollout-alb-multi-ingress-experiment.yaml new file mode 100644 index 0000000000..14c41e0be6 --- /dev/null +++ b/test/e2e/alb/rollout-alb-multi-ingress-experiment.yaml @@ -0,0 +1,120 @@ +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-root +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-canary +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-stable +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-rollout-multi-ingress-1 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + pathType: ImplementationSpecific + backend: + service: + name: alb-rollout-root + port: + name: use-annotation +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-rollout-multi-ingress-2 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + pathType: ImplementationSpecific + backend: + service: + name: alb-rollout-root + port: + name: use-annotation +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: alb-rollout +spec: + selector: + matchLabels: + app: alb-rollout + template: + metadata: + labels: + app: alb-rollout + spec: + containers: + - name: alb-rollout + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m + strategy: + canary: + canaryService: alb-rollout-canary + stableService: alb-rollout-stable + trafficRouting: + alb: + ingresses: + - alb-rollout-multi-ingress-1 + - alb-rollout-multi-ingress-2 + rootService: alb-rollout-root + servicePort: 80 + steps: + - setWeight: 10 + - experiment: + templates: + - name: experiment-alb + specRef: canary + weight: 20 diff --git a/test/e2e/aws_test.go b/test/e2e/aws_test.go index 1265095797..f8b0553fe2 100644 --- a/test/e2e/aws_test.go +++ b/test/e2e/aws_test.go @@ -46,6 +46,17 @@ func (s *AWSSuite) TestALBCanaryUpdate() { WaitForRolloutStatus("Healthy") } +func (s *AWSSuite) TestALBCanaryUpdateMultiIngress() { + if val, _ := os.LookupEnv(fixtures.EnvVarE2EALBIngressAnnotations); val == "" { + s.T().SkipNow() + } + s.Given(). + HealthyRollout(`@functional/alb-canary-multi-ingress-rollout.yaml`). + When(). + UpdateSpec(). + WaitForRolloutStatus("Healthy") +} + func (s *AWSSuite) TestALBBlueGreenUpdate() { if val, _ := os.LookupEnv(fixtures.EnvVarE2EALBIngressAnnotations); val == "" { s.T().SkipNow() @@ -83,6 +94,32 @@ func (s *AWSSuite) TestALBPingPongUpdate() { Assert(assertWeights(s, "ping-service", "pong-service", 100, 0)) } +func (s *AWSSuite) TestALBPingPongUpdateMultiIngress() { + s.Given(). + RolloutObjects("@functional/alb-pingpong-multi-ingress-rollout.yaml"). + When().ApplyManifests().WaitForRolloutStatus("Healthy"). + Then(). + Assert(assertWeightsMultiIngress(s, "ping-multi-ingress-service", "pong-multi-ingress-service", 100, 0)). + // Update 1. Test the weight switch from ping => pong + When().UpdateSpec(). + WaitForRolloutCanaryStepIndex(1).Sleep(1 * time.Second).Then(). + Assert(assertWeightsMultiIngress(s, "ping-multi-ingress-service", "pong-multi-ingress-service", 75, 25)). + When().PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(1 * time.Second). + Then(). + Assert(assertWeightsMultiIngress(s, "ping-multi-ingress-service", "pong-multi-ingress-service", 0, 100)). + // Update 2. Test the weight switch from pong => ping + When().UpdateSpec(). + WaitForRolloutCanaryStepIndex(1).Sleep(1 * time.Second).Then(). + Assert(assertWeightsMultiIngress(s, "ping-multi-ingress-service", "pong-multi-ingress-service", 25, 75)). + When().PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(1 * time.Second). + Then(). + Assert(assertWeightsMultiIngress(s, "ping-multi-ingress-service", "pong-multi-ingress-service", 100, 0)) +} + func assertWeights(s *AWSSuite, groupA, groupB string, weightA, weightB int64) func(t *fixtures.Then) { return func(t *fixtures.Then) { ingress := t.GetALBIngress() @@ -106,6 +143,31 @@ func assertWeights(s *AWSSuite, groupA, groupB string, weightA, weightB int64) f } } +func assertWeightsMultiIngress(s *AWSSuite, groupA, groupB string, weightA, weightB int64) func(t *fixtures.Then) { + return func(t *fixtures.Then) { + ingresses := t.GetALBIngresses() + for _, ingress := range ingresses { + action, ok := ingress.Annotations["alb.ingress.kubernetes.io/actions.alb-rollout-root"] + assert.True(s.T(), ok) + + var albAction ingress2.ALBAction + if err := json.Unmarshal([]byte(action), &albAction); err != nil { + panic(err) + } + for _, targetGroup := range albAction.ForwardConfig.TargetGroups { + switch targetGroup.ServiceName { + case groupA: + assert.True(s.T(), *targetGroup.Weight == weightA, fmt.Sprintf("Weight doesn't match: %d and %d", *targetGroup.Weight, weightA)) + case groupB: + assert.True(s.T(), *targetGroup.Weight == weightB, fmt.Sprintf("Weight doesn't match: %d and %d", *targetGroup.Weight, weightB)) + default: + assert.True(s.T(), false, "Service is not expected in the target group: "+targetGroup.ServiceName) + } + } + } + } +} + func (s *AWSSuite) TestALBExperimentStep() { s.Given(). RolloutObjects("@alb/rollout-alb-experiment.yaml"). @@ -140,6 +202,42 @@ func (s *AWSSuite) TestALBExperimentStep() { Assert(assertWeights(s, "alb-rollout-canary", "alb-rollout-stable", 0, 100)) } +func (s *AWSSuite) TestALBExperimentStepMultiIngress() { + s.Given(). + RolloutObjects("@alb/rollout-alb-multi-ingress-experiment.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(assertWeightsMultiIngress(s, "alb-rollout-canary", "alb-rollout-stable", 0, 100)). + ExpectExperimentCount(0). + When(). + UpdateSpec(). + WaitForRolloutCanaryStepIndex(1). + Sleep(10 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + ingresses := t.GetALBIngresses() + for _, ingress := range ingresses { + action, ok := ingress.Annotations["alb.ingress.kubernetes.io/actions.alb-rollout-root"] + assert.True(s.T(), ok) + + ex := t.GetRolloutExperiments().Items[0] + exServiceName := ex.Status.TemplateStatuses[0].ServiceName + + port := 80 + expectedAction := fmt.Sprintf(actionTemplateWithExperiment, "alb-rollout-canary", port, 10, exServiceName, port, 20, "alb-rollout-stable", port, 70) + assert.Equal(s.T(), expectedAction, action) + } + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(1 * time.Second). // stable is currently set first, and then changes made to VirtualServices/DestinationRules + Then(). + Assert(assertWeightsMultiIngress(s, "alb-rollout-canary", "alb-rollout-stable", 0, 100)) +} + func (s *AWSSuite) TestALBExperimentStepNoSetWeight() { s.Given(). RolloutObjects("@alb/rollout-alb-experiment-no-setweight.yaml"). @@ -173,6 +271,41 @@ func (s *AWSSuite) TestALBExperimentStepNoSetWeight() { Assert(assertWeights(s, "alb-rollout-canary", "alb-rollout-stable", 0, 100)) } +func (s *AWSSuite) TestALBExperimentStepNoSetWeightMultiIngress() { + s.Given(). + RolloutObjects("@alb/rollout-alb-multi-ingress-experiment-no-setweight.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(assertWeightsMultiIngress(s, "alb-rollout-canary", "alb-rollout-stable", 0, 100)). + ExpectExperimentCount(0). + When(). + UpdateSpec(). + Sleep(10 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + ingresses := t.GetALBIngresses() + for _, ingress := range ingresses { + action, ok := ingress.Annotations["alb.ingress.kubernetes.io/actions.alb-rollout-root"] + assert.True(s.T(), ok) + + experiment := t.GetRolloutExperiments().Items[0] + exService1, exService2 := experiment.Status.TemplateStatuses[0].ServiceName, experiment.Status.TemplateStatuses[1].ServiceName + + port := 80 + expectedAction := fmt.Sprintf(actionTemplateWithExperiments, "alb-rollout-canary", port, 0, exService1, port, 20, exService2, port, 20, "alb-rollout-stable", port, 60) + assert.Equal(s.T(), expectedAction, action) + } + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(2 * time.Second). // stable is currently set first, and then changes made to VirtualServices/DestinationRules + Then(). + Assert(assertWeightsMultiIngress(s, "alb-rollout-canary", "alb-rollout-stable", 0, 100)) +} + func (s *AWSSuite) TestAlbHeaderRoute() { s.Given(). RolloutObjects("@header-routing/alb-header-route.yaml"). @@ -215,6 +348,48 @@ func (s *AWSSuite) TestAlbHeaderRoute() { }) } +func (s *AWSSuite) TestAlbHeaderRouteMultiIngress() { + s.Given(). + RolloutObjects("@header-routing/alb-header-route-multi-ingress.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(func(t *fixtures.Then) { + assertAlbActionDoesNotExistMultiIngress(t, s, "header-route") + assertAlbActionServiceWeightMultiIngress(t, s, "action1", "canary-multi-ingress-service", 0) + assertAlbActionServiceWeightMultiIngress(t, s, "action1", "stable-multi-ingress-service", 100) + }). + When(). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Sleep(5 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + assertAlbActionDoesNotExistMultiIngress(t, s, "header-route") + assertAlbActionServiceWeightMultiIngress(t, s, "action1", "canary-multi-ingress-service", 20) + assertAlbActionServiceWeightMultiIngress(t, s, "action1", "stable-multi-ingress-service", 80) + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Paused"). + Sleep(5 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + assertAlbActionServiceWeightMultiIngress(t, s, "header-route", "canary-multi-ingress-service", 100) + assertAlbActionServiceWeightMultiIngress(t, s, "action1", "canary-multi-ingress-service", 20) + assertAlbActionServiceWeightMultiIngress(t, s, "action1", "stable-multi-ingress-service", 80) + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Paused"). + Sleep(5 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + assertAlbActionDoesNotExistMultiIngress(t, s, "header-route") + }) +} + func assertAlbActionServiceWeight(t *fixtures.Then, s *AWSSuite, actionName, serviceName string, expectedWeight int64) { ingress := t.GetALBIngress() key := "alb.ingress.kubernetes.io/actions." + actionName @@ -237,9 +412,42 @@ func assertAlbActionServiceWeight(t *fixtures.Then, s *AWSSuite, actionName, ser assert.True(s.T(), found, "Service %s was not found", serviceName) } +func assertAlbActionServiceWeightMultiIngress(t *fixtures.Then, s *AWSSuite, actionName, serviceName string, expectedWeight int64) { + ingresses := t.GetALBIngresses() + for _, ingress := range ingresses { + key := "alb.ingress.kubernetes.io/actions." + actionName + actionStr, ok := ingress.Annotations[key] + assert.True(s.T(), ok, "Annotation for action was not found: %s", key) + + var albAction ingress2.ALBAction + err := json.Unmarshal([]byte(actionStr), &albAction) + if err != nil { + panic(err) + } + + found := false + for _, group := range albAction.ForwardConfig.TargetGroups { + if group.ServiceName == serviceName { + assert.Equal(s.T(), pointer.Int64(expectedWeight), group.Weight) + found = true + } + } + assert.True(s.T(), found, "Service %s was not found", serviceName) + } +} + func assertAlbActionDoesNotExist(t *fixtures.Then, s *AWSSuite, actionName string) { ingress := t.GetALBIngress() key := "alb.ingress.kubernetes.io/actions." + actionName _, ok := ingress.Annotations[key] assert.False(s.T(), ok, "Annotation for action should not exist: %s", key) } + +func assertAlbActionDoesNotExistMultiIngress(t *fixtures.Then, s *AWSSuite, actionName string) { + ingresses := t.GetALBIngresses() + for _, ingress := range ingresses { + key := "alb.ingress.kubernetes.io/actions." + actionName + _, ok := ingress.Annotations[key] + assert.False(s.T(), ok, "Annotation for action should not exist: %s", key) + } +} diff --git a/test/e2e/functional/alb-canary-multi-ingress-rollout.yaml b/test/e2e/functional/alb-canary-multi-ingress-rollout.yaml new file mode 100644 index 0000000000..b18d75e606 --- /dev/null +++ b/test/e2e/functional/alb-canary-multi-ingress-rollout.yaml @@ -0,0 +1,118 @@ +apiVersion: v1 +kind: Service +metadata: + name: alb-canary-multi-ingress-root +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-canary-multi-ingress +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-canary-multi-ingress-desired +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-canary-multi-ingress +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-canary-multi-ingress-stable +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-canary-multi-ingress +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-canary-multi-ingress-1 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: alb-canary-multi-ingress-root + port: + name: use-annotation +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-canary-multi-ingress-2 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: alb-canary-multi-ingress-root + port: + name: use-annotation +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: alb-canary-multi-ingress +spec: + selector: + matchLabels: + app: alb-canary-multi-ingress + template: + metadata: + labels: + app: alb-canary-multi-ingress + spec: + containers: + - name: alb-canary-multi-ingress + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m + strategy: + canary: + canaryService: alb-canary-multi-ingress-desired + stableService: alb-canary-multi-ingress-stable + trafficRouting: + alb: + ingresses: + - alb-canary-multi-ingress-1 + - alb-canary-multi-ingress-2 + rootService: alb-canary-root + servicePort: 80 + steps: + - setWeight: 10 + - pause: {duration: 5s} + - setWeight: 20 + - pause: {duration: 5s} diff --git a/test/e2e/functional/alb-canary-rollout.yaml b/test/e2e/functional/alb-canary-rollout.yaml index e05f73d2c2..dd2aa348cb 100644 --- a/test/e2e/functional/alb-canary-rollout.yaml +++ b/test/e2e/functional/alb-canary-rollout.yaml @@ -51,7 +51,7 @@ spec: - http: paths: - path: /* - pathType: Prefix + pathType: ImplementationSpecific backend: service: name: alb-canary-root @@ -95,4 +95,4 @@ spec: - setWeight: 10 - pause: {duration: 5s} - setWeight: 20 - - pause: {duration: 5s} \ No newline at end of file + - pause: {duration: 5s} diff --git a/test/e2e/functional/alb-pingpong-multi-ingress-rollout.yaml b/test/e2e/functional/alb-pingpong-multi-ingress-rollout.yaml new file mode 100644 index 0000000000..6ad8e471c8 --- /dev/null +++ b/test/e2e/functional/alb-pingpong-multi-ingress-rollout.yaml @@ -0,0 +1,106 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: ping-multi-ingress-service +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-canary-multi-ingress +--- +apiVersion: v1 +kind: Service +metadata: + name: pong-multi-ingress-service +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-canary-multi-ingress +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-canary-multi-ingress-1 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + backend: + service: + name: alb-rollout-root + port: + name: use-annotation + pathType: ImplementationSpecific +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-canary-multi-ingress-2 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + backend: + service: + name: alb-rollout-root + port: + name: use-annotation + pathType: ImplementationSpecific +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: alb-canary-multi-ingress +spec: + replicas: 2 + selector: + matchLabels: + app: alb-canary-multi-ingress + template: + metadata: + labels: + app: alb-canary-multi-ingress + spec: + containers: + - name: alb-canary-multi-ingress + image: "argoproj/rollouts-demo:red" + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m + strategy: + canary: + scaleDownDelaySeconds: 2 + pingPong: + pingService: ping-multi-ingress-service + pongService: pong-multi-ingress-service + trafficRouting: + alb: + ingresses: + - alb-canary-multi-ingress-1 + - alb-canary-multi-ingress-2 + rootService: alb-rollout-root + servicePort: 80 + steps: + - setWeight: 25 + - pause: {duration: 5s} diff --git a/test/e2e/header-routing/alb-header-route-multi-ingress.yaml b/test/e2e/header-routing/alb-header-route-multi-ingress.yaml new file mode 100644 index 0000000000..f2bca43442 --- /dev/null +++ b/test/e2e/header-routing/alb-header-route-multi-ingress.yaml @@ -0,0 +1,112 @@ +apiVersion: v1 +kind: Service +metadata: + name: canary-multi-ingress-service +spec: + type: NodePort + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: alb-multi-ingress-rollout +--- +apiVersion: v1 +kind: Service +metadata: + name: stable-multi-ingress-service +spec: + type: NodePort + ports: + - port: 8080 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: alb-multi-ingress-rollout +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-rollout-multi-ingress-1 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + pathType: ImplementationSpecific + backend: + service: + name: action1 + port: + name: use-annotation +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: alb-rollout-multi-ingress-2 + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + pathType: ImplementationSpecific + backend: + service: + name: action1 + port: + name: use-annotation +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollouts-demo +spec: + replicas: 5 + selector: + matchLabels: + app: alb-multi-ingress-rollout + template: + metadata: + labels: + app: alb-multi-ingress-rollout + spec: + containers: + - name: alb-multi-ingress-rollout + image: "argoproj/rollouts-demo:yellow" + ports: + - name: http + containerPort: 8080 + protocol: TCP + strategy: + canary: + scaleDownDelaySeconds: 5 + stableService: stable-multi-ingress-service + canaryService: canary-multi-ingress-service + trafficRouting: + managedRoutes: + - name: header-route + alb: + ingresses: + - alb-rollout-multi-ingress-1 + - alb-rollout-multi-ingress-2 + rootService: action1 + servicePort: 8080 + steps: + - setWeight: 20 + - pause: {} + - setHeaderRoute: + name: header-route + match: + - headerName: Custom-Header + headerValue: + exact: Mozilla* + - pause: {} + - setHeaderRoute: + name: header-route + - pause: {} diff --git a/test/e2e/header-routing/alb-header-route.yaml b/test/e2e/header-routing/alb-header-route.yaml index 71c9e7aa6f..99c3f5d205 100644 --- a/test/e2e/header-routing/alb-header-route.yaml +++ b/test/e2e/header-routing/alb-header-route.yaml @@ -31,21 +31,7 @@ kind: Ingress metadata: name: alb-rollout-ingress annotations: - alb.ingress.kubernetes.io/security-groups: 'iks-intuit-cidr-ingress-tcp-443' - alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-2:795188202216:certificate/27d920c5-a8a6-4210-9f31-bd4a2d439039 - alb.ingress.kubernetes.io/load-balancer-attributes: access_logs.s3.enabled=false - alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-2017-01 - kubernetes.io/ingress.class: aws-alb - alb.ingress.kubernetes.io/load-balancer-name: rollouts-sample - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/healthcheck-protocol: HTTP - alb.ingress.kubernetes.io/healthcheck-port: traffic-port - alb.ingress.kubernetes.io/healthcheck-path: /color - alb.ingress.kubernetes.io/backend-protocol: HTTP - alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS": 443}]' - alb.ingress.kubernetes.io/ssl-redirect: '443' - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/subnets: IngressSubnetAz1, IngressSubnetAz2, IngressSubnetAz3 + kubernetes.io/ingress.class: alb spec: rules: - http: diff --git a/test/fixtures/common.go b/test/fixtures/common.go index c16df649e3..9e060df865 100644 --- a/test/fixtures/common.go +++ b/test/fixtures/common.go @@ -536,6 +536,18 @@ func (c *Common) GetALBIngress() *networkingv1.Ingress { return ingress } +func (c *Common) GetALBIngresses() []*networkingv1.Ingress { + ro := c.Rollout() + names := ro.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses + ingresses := []*networkingv1.Ingress{} + for _, name := range names { + ingress, err := c.kubeClient.NetworkingV1().Ingresses(c.namespace).Get(c.Context, name, metav1.GetOptions{}) + c.CheckError(err) + ingresses = append(ingresses, ingress) + } + return ingresses +} + func (c *Common) GetNginxIngressStable() *networkingv1.Ingress { ro := c.Rollout() name := ro.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress diff --git a/ui/src/models/rollout/generated/api.ts b/ui/src/models/rollout/generated/api.ts index a4064899f4..151eb4b672 100755 --- a/ui/src/models/rollout/generated/api.ts +++ b/ui/src/models/rollout/generated/api.ts @@ -101,6 +101,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBStatus { * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBStatus */ stableTargetGroup?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1AwsResourceRef; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBStatus + */ + ingress?: string; } /** * @@ -126,6 +132,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBTrafficR * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBTrafficRouting */ rootService?: string; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBTrafficRouting + */ + annotationPrefix?: string; /** * * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StickinessConfig} @@ -134,10 +146,10 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBTrafficR stickinessConfig?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StickinessConfig; /** * - * @type {string} + * @type {Array} * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBTrafficRouting */ - annotationPrefix?: string; + ingresses?: Array; } /** * @@ -1534,6 +1546,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutStat * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutStatus */ alb?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ALBStatus; + /** + * + * @type {Array} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutStatus + */ + albs?: Array; } /** * diff --git a/utils/ingress/ingress.go b/utils/ingress/ingress.go index 494245ae8e..69e921beef 100644 --- a/utils/ingress/ingress.go +++ b/utils/ingress/ingress.go @@ -83,6 +83,14 @@ func SingleNginxIngressConfigured(rollout *v1alpha1.Rollout) bool { return rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress != "" } +func MultipleAlbIngressesConfigured(rollout *v1alpha1.Rollout) bool { + return rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses != nil +} + +func SingleAlbIngressConfigured(rollout *v1alpha1.Rollout) bool { + return rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress != "" +} + // GetRolloutIngressKeys returns ingresses keys (namespace/ingressName) which are referenced by specified rollout func GetRolloutIngressKeys(rollout *v1alpha1.Rollout) []string { var ingresses []string @@ -126,6 +134,20 @@ func GetRolloutIngressKeys(rollout *v1alpha1.Rollout) []string { ) } + // Scenario where one rollout is managing multiple ALB ingresses. + if rollout.Spec.Strategy.Canary != nil && + rollout.Spec.Strategy.Canary.TrafficRouting != nil && + rollout.Spec.Strategy.Canary.TrafficRouting.ALB != nil && + rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses != nil { + + for _, ingress := range rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses { + ingresses = append( + ingresses, + fmt.Sprintf("%s/%s", rollout.Namespace, ingress), + ) + } + } + return ingresses } diff --git a/utils/ingress/ingress_test.go b/utils/ingress/ingress_test.go index b0213a3bf9..7d6cecee63 100644 --- a/utils/ingress/ingress_test.go +++ b/utils/ingress/ingress_test.go @@ -77,14 +77,14 @@ func TestGetRolloutIngressKeysForCanaryWithTrafficRoutingMultiIngress(t *testing StableIngresses: []string{"stable-ingress", "stable-ingress-additional"}, }, ALB: &v1alpha1.ALBTrafficRouting{ - Ingress: "alb-ingress", + Ingresses: []string{"alb-ingress", "alb-multi-ingress"}, }, }, }, }, }, }) - assert.ElementsMatch(t, keys, []string{"default/stable-ingress", "default/myrollout-stable-ingress-canary", "default/stable-ingress-additional", "default/myrollout-stable-ingress-additional-canary", "default/alb-ingress"}) + assert.ElementsMatch(t, keys, []string{"default/stable-ingress", "default/myrollout-stable-ingress-canary", "default/stable-ingress-additional", "default/myrollout-stable-ingress-additional-canary", "default/alb-ingress", "default/alb-multi-ingress"}) } func TestGetCanaryIngressName(t *testing.T) { @@ -160,6 +160,79 @@ func TestGetCanaryIngressName(t *testing.T) { }) } +func TestGetCanaryAlbIngressName(t *testing.T) { + singleIngressRollout := &v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myrollout", + Namespace: "default", + }, + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + CanaryService: "canary-service", + StableService: "stable-service", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: "stable-ingress", + }, + }, + }, + }, + }, + } + + multiIngressRollout := &v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myrollout", + Namespace: "default", + }, + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + CanaryService: "canary-service", + StableService: "stable-service", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingresses: []string{"stable-ingress", "stable-ingress-additional"}, + }, + }, + }, + }, + }, + } + + t.Run("Ingress - NoTrim", func(t *testing.T) { + singleIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress = "stable-ingress" + canaryIngress := GetCanaryIngressName(singleIngressRollout.GetName(), singleIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress) + assert.Equal(t, "myrollout-stable-ingress-canary", canaryIngress) + }) + t.Run("Ingress - Trim", func(t *testing.T) { + singleIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress = fmt.Sprintf("stable-ingress%s", strings.Repeat("a", 260)) + canaryIngress := GetCanaryIngressName(singleIngressRollout.GetName(), singleIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress) + assert.Equal(t, 253, len(canaryIngress), "canary ingress truncated to 253") + assert.Equal(t, true, strings.HasSuffix(canaryIngress, "-canary"), "canary ingress has -canary suffix") + }) + t.Run("Ingresses - NoTrim", func(t *testing.T) { + for _, ing := range multiIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses { + canaryIngress := GetCanaryIngressName(multiIngressRollout.GetName(), ing) + assert.Equal(t, fmt.Sprintf("%s-%s-canary", multiIngressRollout.ObjectMeta.Name, ing), canaryIngress) + } + }) + t.Run("Ingresses - Trim", func(t *testing.T) { + multiIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses = []string{fmt.Sprintf("stable-ingress%s", strings.Repeat("a", 260))} + for _, ing := range multiIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingresses { + canaryIngress := GetCanaryIngressName(multiIngressRollout.GetName(), ing) + assert.Equal(t, 253, len(canaryIngress), "canary ingress truncated to 253") + assert.Equal(t, true, strings.HasSuffix(canaryIngress, "-canary"), "canary ingress has -canary suffix") + } + }) + t.Run("NoIngress", func(t *testing.T) { + multiIngressRollout.Spec.Strategy.Canary.TrafficRouting.ALB = nil + canaryIngress := GetCanaryIngressName(multiIngressRollout.GetName(), "") + assert.Equal(t, "", canaryIngress, "canary ingress is empty") + }) +} + func TestHasRuleWithService(t *testing.T) { t.Run("will check rule with legacy ingress", func(t *testing.T) { // given