diff --git a/Dockerfile.dev b/Dockerfile.dev index 7c3fc3f30c..184d4e33e9 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -12,9 +12,12 @@ FROM gcr.io/distroless/static-debian11 COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY rollouts-controller-linux-amd64 /bin/rollouts-controller +COPY step-plugin-e2e-linux-amd64 /bin/plugin-files/step-plugin-e2e # Use numeric user, allows kubernetes to identify this user as being # non-root when we use a security context with runAsNonRoot: true USER 999 +WORKDIR /home/argo-rollouts + ENTRYPOINT [ "/bin/rollouts-controller" ] diff --git a/Makefile b/Makefile index 55b21af87c..7df22e7cce 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,17 @@ GIT_TREE_STATE=$(shell if [ -z "`git status --porcelain`" ]; then echo "clean" ; GIT_REMOTE_REPO=upstream VERSION=$(shell if [ ! -z "${GIT_TAG}" ] ; then echo "${GIT_TAG}" | sed -e "s/^v//" ; else cat VERSION ; fi) + +TARGET_ARCH?=linux/amd64 + # docker image publishing options -DOCKER_PUSH=false -IMAGE_TAG=latest +DOCKER_PUSH ?= false +IMAGE_TAG ?= latest # build development images DEV_IMAGE ?= false # E2E variables +E2E_K8S_CONTEXT ?= rancher-desktop E2E_INSTANCE_ID ?= argo-rollouts-e2e E2E_TEST_OPTIONS ?= E2E_PARALLEL ?= 1 @@ -149,6 +153,9 @@ gen-crd: install-go-tools-local ## generate crd manifests gen-mocks: install-go-tools-local ## generate mock files ./hack/update-mocks.sh +gen-mocks-fast: + ./hack/update-mocks.sh + # generates openapi_generated.go .PHONY: gen-openapi gen-openapi: $(DIST_DIR)/openapi-gen ## generate openapi files @@ -200,10 +207,11 @@ builder-image: ## build builder image .PHONY: image image: ifeq ($(DEV_IMAGE), true) + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/step-plugin-e2e-linux-amd64 ./test/cmd/step-plugin-e2e CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/rollouts-controller-linux-amd64 ./cmd/rollouts-controller - DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argo-rollouts:$(IMAGE_TAG) -f Dockerfile.dev ${DIST_DIR} + DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argo-rollouts:$(IMAGE_TAG) -f Dockerfile.dev ${DIST_DIR} else - DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)argo-rollouts:$(IMAGE_TAG) . + DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) -t $(IMAGE_PREFIX)argo-rollouts:$(IMAGE_TAG) . endif @if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)argo-rollouts:$(IMAGE_TAG) ; fi @@ -212,15 +220,19 @@ endif # https://www.jetbrains.com/help/go/attach-to-running-go-processes-with-debugger.html .PHONY: build-sample-metric-plugin-debug build-sample-metric-plugin-debug: ## build sample metric plugin with debug info - go build -gcflags="all=-N -l" -o metric-plugin test/cmd/metrics-plugin-sample/main.go + go build -gcflags="all=-N -l" -o plugin-bin/metric-plugin test/cmd/metrics-plugin-sample/main.go .PHONY: build-sample-traffic-plugin-debug build-sample-traffic-plugin-debug: ## build sample traffic plugin with debug info - go build -gcflags="all=-N -l" -o traffic-plugin test/cmd/trafficrouter-plugin-sample/main.go + go build -gcflags="all=-N -l" -o plugin-bin/traffic-plugin test/cmd/trafficrouter-plugin-sample/main.go + +.PHONY: build-sample-step-plugin-debug +build-sample-step-plugin-debug: ## build sample traffic plugin with debug info + go build -gcflags="all=-N -l" -o plugin-bin/step-plugin test/cmd/step-plugin-sample/main.go .PHONY: plugin-image plugin-image: ## build plugin image - DOCKER_BUILDKIT=1 docker build --target kubectl-argo-rollouts -t $(IMAGE_PREFIX)kubectl-argo-rollouts:$(IMAGE_TAG) . + DOCKER_BUILDKIT=1 docker build --platform=$(TARGET_ARCH) --target kubectl-argo-rollouts -t $(IMAGE_PREFIX)kubectl-argo-rollouts:$(IMAGE_TAG) . if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)kubectl-argo-rollouts:$(IMAGE_TAG) ; fi ##@ Test @@ -233,6 +245,12 @@ test: test-kustomize ## run all tests test-kustomize: ## run kustomize tests ./test/kustomize/test.sh +setup-e2e: + @kubectl apply --context='${E2E_K8S_CONTEXT}' -f manifests/crds/rollout-crd.yaml + @kubectl apply --context='${E2E_K8S_CONTEXT}' -n argo-rollouts -f test/e2e/step-plugin/argo-rollouts-config.yaml + @rm -rf plugin-bin + @go build -gcflags="all=-N -l" -o plugin-bin/e2e-step-plugin test/cmd/step-plugin-e2e/main.go + .PHONY: start-e2e start-e2e: ## start e2e test environment mkdir -p coverage-output-e2e diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index b2c74299d6..70ce3bfb22 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,24 +1,27 @@ # Contributing + ## Before You Start + Argo Rollouts is written in Golang. If you do not have a good grounding in Go, try out [the tutorial](https://tour.golang.org/). ## Pre-requisites + Install: -* [docker](https://docs.docker.com/install/#supported-platforms) -* [golang](https://golang.org/) -* [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) -* [kustomize](https://github.com/kubernetes-sigs/kustomize/releases) >= 4.5.5 -* [k3d](https://k3d.io/) recommended +- [docker](https://docs.docker.com/install/#supported-platforms) +- [golang](https://golang.org/) +- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) +- [kustomize](https://github.com/kubernetes-sigs/kustomize/releases) >= 4.5.5 +- [k3d](https://k3d.io/) recommended Kustomize is required for unit tests (`make test` is using it), so you [must install it](https://kubectl.docs.kubernetes.io/installation/kustomize/) locally if you wish to make code contributions to Argo Rollouts. Argo Rollout additionally uses the following tools -* `golangci-lint` to lint the project. -* `protoc` and `swagger-codegen` to generate proto related files -* `yarn` to build the UI +- `golangci-lint` to lint the project. +- `protoc` and `swagger-codegen` to generate proto related files +- `yarn` to build the UI Run the following commands to install them: @@ -56,10 +59,9 @@ cd ~/go/src/github.com/argoproj/argo-rollouts The `make controller` command will build the controller. -* `make install-tools-local` - Runs scripts to install codegen utility CLIs necessary for codegen. - -* `make codegen` - Runs the code generator that creates the informers, client, lister, and deepcopies from the types.go and modifies the open-api spec. +- `make install-tools-local` - Runs scripts to install codegen utility CLIs necessary for codegen. +- `make codegen` - Runs the code generator that creates the informers, client, lister, and deepcopies from the types.go and modifies the open-api spec. ## Running Controller Locally @@ -83,11 +85,7 @@ make test ## Running E2E tests The end-to-end tests need to run against a kubernetes cluster with the Argo Rollouts controller -running. The rollout controller can be started with the command: - -``` -make start-e2e -``` +running. Start and prepare your cluster for e2e tests: @@ -98,6 +96,12 @@ kubectl apply -k manifests/crds kubectl apply -f test/e2e/crds ``` +The rollout controller can be started with the command: + +``` +make start-e2e +``` + Then run the e2e tests: ``` @@ -112,9 +116,10 @@ E2E_TEST_OPTIONS="-run 'TestCanarySuite' -testify.m 'TestCanaryScaleDownOnAbortN ## Running the UI -If you'd like to run the UI locally, you first need a running Rollouts controller. This can be a locally running controller with a k3d cluster, as described above, or a controller running in a remote Kubernetes cluster. +If you'd like to run the UI locally, you first need a running Rollouts controller. This can be a locally running controller with a k3d cluster, as described above, or a controller running in a remote Kubernetes cluster. In order for the local React app to communicate with the controller and Kubernetes API, run the following to open a port forward to the dashboard: + ```bash kubectl argo rollouts dashboard ``` @@ -127,6 +132,7 @@ make plugin ``` In another terminal, run the following to start the UI: + ```bash cd ui yarn install @@ -142,11 +148,11 @@ that handle a specific aspect of Progressive Delivery. The controllers are: -* [Rollout Controller](https://github.com/argoproj/argo-rollouts/blob/master/rollout/controller.go) -* [Service Controller](https://github.com/argoproj/argo-rollouts/blob/master/service/service.go) -* [Ingress Controller](https://github.com/argoproj/argo-rollouts/blob/master/ingress/ingress.go) -* [Experiment Controller](https://github.com/argoproj/argo-rollouts/blob/master/experiments/controller.go) -* [AnalysisRun Controller](https://github.com/argoproj/argo-rollouts/blob/master/analysis/controller.go) +- [Rollout Controller](https://github.com/argoproj/argo-rollouts/blob/master/rollout/controller.go) +- [Service Controller](https://github.com/argoproj/argo-rollouts/blob/master/service/service.go) +- [Ingress Controller](https://github.com/argoproj/argo-rollouts/blob/master/ingress/ingress.go) +- [Experiment Controller](https://github.com/argoproj/argo-rollouts/blob/master/experiments/controller.go) +- [AnalysisRun Controller](https://github.com/argoproj/argo-rollouts/blob/master/analysis/controller.go) ### Tips @@ -158,36 +164,35 @@ KUBECONFIG=~/.kube/minikube make test-e2e ``` 2. To run a specific e2e test, set the `E2E_TEST_OPTIONS` environment variable to specify the test -(or test regex): + (or test regex): ```shell make test-e2e E2E_TEST_OPTIONS="-testify.m ^TestRolloutRestart$" ``` 3. The e2e tests are designed to run as quickly as possible, eliminating readiness and termination -delays. However, it is often desired to artificially slow down the tests for debugging purposes, -as well as to understand what the test is doing. To delay startup and termination of pods, set the -`E2E_POD_DELAY` to an integer value in seconds. This environment variable is often coupled with -`E2E_TEST_OPTIONS` to debug and slow down a specific test. + delays. However, it is often desired to artificially slow down the tests for debugging purposes, + as well as to understand what the test is doing. To delay startup and termination of pods, set the + `E2E_POD_DELAY` to an integer value in seconds. This environment variable is often coupled with + `E2E_TEST_OPTIONS` to debug and slow down a specific test. ```shell make test-e2e E2E_POD_DELAY=10 ``` 4. Increasing the timeout. The E2E tests time out waiting on conditions to be met within 60 seconds. -If debugging the rollout controller, it may be useful to increase this timeout while say sitting -at a debugger breakpoint: + If debugging the rollout controller, it may be useful to increase this timeout while say sitting + at a debugger breakpoint: ```shell make test-e2e E2E_WAIT_TIMEOUT=999999 ``` - 5. The e2e tests leverage a feature of the controller allowing the controller to be sharded with -a user-specific "instance id" label. This allows the tests to operate only on rollouts with the -specified label, and prevents any other controllers (including the system rollout controller), -from also operating on the same set of rollouts. This value can be changed (from the default of -`argo-rollouts-e2e`), using the `E2E_INSTANCE_ID` environment variable: + a user-specific "instance id" label. This allows the tests to operate only on rollouts with the + specified label, and prevents any other controllers (including the system rollout controller), + from also operating on the same set of rollouts. This value can be changed (from the default of + `argo-rollouts-e2e`), using the `E2E_INSTANCE_ID` environment variable: ```shell make start-e2e E2E_INSTANCE_ID=foo @@ -200,7 +205,6 @@ Alternatively, the e2e tests can be run against the system controller (i.e. with make start-e2e E2E_INSTANCE_ID='' ``` - 6. Working on CRDs? While editing them directly works when you are finding the shape of things you want, the final CRDs are autogenerated. Make sure to regenerate them by running `make gen-crd` before submitting PRs. They are controlled by the relevant annotations in the types file: eg: Analysis Templates are controlled by annotations in `pkg/apis/rollouts/v1alpha1/analysis_types.go`. @@ -242,14 +246,16 @@ kubectl -n argo-rollouts apply -f manifests/install.yaml ``` ## Upgrading Kubernetes Libraries + Argo Rollouts has a dependency on the kubernetes/kubernetes repo for some of the functionality that has not been pushed into the other kubernetes repositories yet. In order to import the kubernetes/kubernetes repo, all of the associated repos have to pinned to the correct version specified by the kubernetes/kubernetes release. The `./hack/update-k8s-dependencies.sh` updates all the dependencies to the those correct versions. ## Upgrading Notifications Engine -Argo Rollouts has a dependency on the [argoproj/notifications-engines](https://github.com/argoproj/notifications-engine) repo -for the notifications functionality and related documentation. + +Argo Rollouts has a dependency on the [argoproj/notifications-engines](https://github.com/argoproj/notifications-engine) repo +for the notifications functionality and related documentation. This is updated by upgrading the Go library in `go.mod` by running the commands: diff --git a/go.mod b/go.mod index 45559fc1e6..2a7073409c 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 + github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/hashicorp/go-plugin v1.6.1 github.com/influxdata/influxdb-client-go/v2 v2.13.0 @@ -128,7 +129,6 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect diff --git a/hack/update-mocks.sh b/hack/update-mocks.sh index 6f1bcfad4a..9e4f397db1 100755 --- a/hack/update-mocks.sh +++ b/hack/update-mocks.sh @@ -21,3 +21,13 @@ mockery \ --dir "${PROJECT_ROOT}"/rollout/trafficrouting \ --name TrafficRoutingReconciler \ --output "${PROJECT_ROOT}"/rollout/mocks + +mockery \ + --dir "${PROJECT_ROOT}"/rollout/steps/plugin \ + --name "Resolver|StepPlugin" \ + --output "${PROJECT_ROOT}"/rollout/steps/plugin/mocks + +mockery \ + --dir "${PROJECT_ROOT}"/rollout/steps/plugin/rpc \ + --name "StepPlugin" \ + --output "${PROJECT_ROOT}"/rollout/steps/plugin/rpc/mocks diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index c30ac0fd73..aa06634f9b 100755 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -662,6 +662,16 @@ spec: - type: string x-kubernetes-int-or-string: true type: object + plugin: + properties: + config: + type: object + x-kubernetes-preserve-unknown-fields: true + name: + type: string + required: + - name + type: object setCanaryScale: properties: matchTrafficWeight: @@ -3721,6 +3731,45 @@ spec: type: object stablePingPong: type: string + stepPluginStatuses: + items: + properties: + backoff: + type: string + disabled: + type: boolean + executions: + format: int32 + type: integer + finishedAt: + format: date-time + type: string + index: + format: int32 + type: integer + message: + type: string + name: + type: string + operation: + type: string + phase: + type: string + startedAt: + format: date-time + type: string + status: + type: object + x-kubernetes-preserve-unknown-fields: true + updatedAt: + format: date-time + type: string + required: + - index + - name + - operation + type: object + type: array weights: properties: additional: diff --git a/manifests/install.yaml b/manifests/install.yaml index 95ff89b660..56e71a4e18 100755 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -13176,6 +13176,16 @@ spec: - type: string x-kubernetes-int-or-string: true type: object + plugin: + properties: + config: + type: object + x-kubernetes-preserve-unknown-fields: true + name: + type: string + required: + - name + type: object setCanaryScale: properties: matchTrafficWeight: @@ -16235,6 +16245,45 @@ spec: type: object stablePingPong: type: string + stepPluginStatuses: + items: + properties: + backoff: + type: string + disabled: + type: boolean + executions: + format: int32 + type: integer + finishedAt: + format: date-time + type: string + index: + format: int32 + type: integer + message: + type: string + name: + type: string + operation: + type: string + phase: + type: string + startedAt: + format: date-time + type: string + status: + type: object + x-kubernetes-preserve-unknown-fields: true + updatedAt: + format: date-time + type: string + required: + - index + - name + - operation + type: object + type: array weights: properties: additional: diff --git a/metricproviders/plugin/client/client.go b/metricproviders/plugin/client/client.go index 9fc82ed244..d37e949d8e 100644 --- a/metricproviders/plugin/client/client.go +++ b/metricproviders/plugin/client/client.go @@ -8,6 +8,7 @@ import ( "github.com/argoproj/argo-rollouts/metricproviders/plugin/rpc" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/utils/plugin" + "github.com/argoproj/argo-rollouts/utils/plugin/types" goPlugin "github.com/hashicorp/go-plugin" ) @@ -54,7 +55,7 @@ func (m *metricPlugin) startPluginSystem(metric v1alpha1.Metric) (rpc.MetricProv // There should only ever be one plugin defined in metric.Provider.Plugin per analysis template this gets checked // during validation for pluginName := range metric.Provider.Plugin { - pluginPath, args, err := plugin.GetPluginInfo(pluginName) + pluginPath, args, err := plugin.GetPluginInfo(pluginName, types.PluginTypeMetricProvider) if err != nil { return nil, fmt.Errorf("unable to find plugin (%s): %w", pluginName, err) } diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index e072f7e0da..ab906295f0 100755 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -978,6 +978,13 @@ "stablePingPong": { "type": "string", "title": "StablePingPong For the ping-pong feature holds the current stable service, ping or pong" + }, + "stepPluginStatuses": { + "type": "array", + "items": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StepPluginStatus" + }, + "title": "StepPluginStatuses holds the status of the step plugins executed" } }, "title": "CanaryStatus status fields that only pertain to the canary rollout" @@ -1013,6 +1020,10 @@ "setMirrorRoute": { "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetMirrorRoute", "title": "SetMirrorRoutes Mirrors traffic that matches rules to a particular destination\n+optional" + }, + "plugin": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PluginStep", + "title": "Plugin defines a plugin to execute for a step" } }, "description": "CanaryStep defines a step of a canary deployment." @@ -1761,6 +1772,20 @@ }, "description": "PingPongSpec holds the ping and pong service name." }, + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PluginStep": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name of the hashicorp go-plugin step to query" + }, + "config": { + "type": "string", + "format": "byte", + "title": "+kubebuilder:validation:Schemaless\n+kubebuilder:pruning:PreserveUnknownFields\n+kubebuilder:validation:Type=object\nConfig is the configuration object for the specified plugin" + } + } + }, "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PodTemplateMetadata": { "type": "object", "properties": { @@ -2528,6 +2553,62 @@ } } }, + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StepPluginStatus": { + "type": "object", + "properties": { + "index": { + "type": "integer", + "format": "int32", + "title": "Index is the matching step index of the executed plugin" + }, + "name": { + "type": "string", + "title": "Name is the matching step name of the executed plugin" + }, + "operation": { + "type": "string", + "title": "Operation is the name of the operation that produced this status" + }, + "phase": { + "type": "string", + "title": "Phase is the resulting phase of the operation" + }, + "message": { + "type": "string", + "title": "Message provides details on why the plugin is in its current phase" + }, + "startedAt": { + "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.Time", + "title": "StartedAt indicates when the plugin was first called for the operation" + }, + "updatedAt": { + "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.Time", + "title": "UpdatedAt indicates when the plugin was last called for the operation" + }, + "finishedAt": { + "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.Time", + "title": "FinishedAt indicates when the operation was completed" + }, + "backoff": { + "type": "string", + "title": "Backoff is a duration to wait before trying to execute the operation again if it was not completed" + }, + "executions": { + "type": "integer", + "format": "int32", + "title": "Executions is the number of time the operation was executed" + }, + "disabled": { + "type": "boolean", + "title": "Disabled indicates if the plugin is globally disabled" + }, + "status": { + "type": "string", + "format": "byte", + "title": "+kubebuilder:validation:Schemaless\n+kubebuilder:pruning:PreserveUnknownFields\n+kubebuilder:validation:Type=object\nStatus holds the internal status of the plugin for this operation" + } + } + }, "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StickinessConfig": { "type": "object", "properties": { diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index ea9a241837..c4c0952a74 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -12,6 +12,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,AnalysisTemplateSpec,Templates API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,ApisixRoute,Rules API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,AppMeshVirtualService,Routes +API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,CanaryStatus,StepPluginStatuses API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,CanaryStrategy,Steps API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,CloudWatchMetric,MetricDataQueries API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,CloudWatchMetricStatMetric,Dimensions diff --git a/pkg/apis/rollouts/v1alpha1/generated.pb.go b/pkg/apis/rollouts/v1alpha1/generated.pb.go index 91ef87cc29..b472340b48 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.pb.go +++ b/pkg/apis/rollouts/v1alpha1/generated.pb.go @@ -1979,10 +1979,38 @@ func (m *PingPongSpec) XXX_DiscardUnknown() { var xxx_messageInfo_PingPongSpec proto.InternalMessageInfo +func (m *PluginStep) Reset() { *m = PluginStep{} } +func (*PluginStep) ProtoMessage() {} +func (*PluginStep) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{69} +} +func (m *PluginStep) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PluginStep) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *PluginStep) XXX_Merge(src proto.Message) { + xxx_messageInfo_PluginStep.Merge(m, src) +} +func (m *PluginStep) XXX_Size() int { + return m.Size() +} +func (m *PluginStep) XXX_DiscardUnknown() { + xxx_messageInfo_PluginStep.DiscardUnknown(m) +} + +var xxx_messageInfo_PluginStep proto.InternalMessageInfo + func (m *PodTemplateMetadata) Reset() { *m = PodTemplateMetadata{} } func (*PodTemplateMetadata) ProtoMessage() {} func (*PodTemplateMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{69} + return fileDescriptor_e0e705f843545fab, []int{70} } func (m *PodTemplateMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2012,7 +2040,7 @@ func (m *PreferredDuringSchedulingIgnoredDuringExecution) Reset() { } func (*PreferredDuringSchedulingIgnoredDuringExecution) ProtoMessage() {} func (*PreferredDuringSchedulingIgnoredDuringExecution) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{70} + return fileDescriptor_e0e705f843545fab, []int{71} } func (m *PreferredDuringSchedulingIgnoredDuringExecution) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2040,7 +2068,7 @@ var xxx_messageInfo_PreferredDuringSchedulingIgnoredDuringExecution proto.Intern func (m *PrometheusMetric) Reset() { *m = PrometheusMetric{} } func (*PrometheusMetric) ProtoMessage() {} func (*PrometheusMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{71} + return fileDescriptor_e0e705f843545fab, []int{72} } func (m *PrometheusMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2068,7 +2096,7 @@ var xxx_messageInfo_PrometheusMetric proto.InternalMessageInfo func (m *PrometheusRangeQueryArgs) Reset() { *m = PrometheusRangeQueryArgs{} } func (*PrometheusRangeQueryArgs) ProtoMessage() {} func (*PrometheusRangeQueryArgs) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{72} + return fileDescriptor_e0e705f843545fab, []int{73} } func (m *PrometheusRangeQueryArgs) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2098,7 +2126,7 @@ func (m *RequiredDuringSchedulingIgnoredDuringExecution) Reset() { } func (*RequiredDuringSchedulingIgnoredDuringExecution) ProtoMessage() {} func (*RequiredDuringSchedulingIgnoredDuringExecution) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{73} + return fileDescriptor_e0e705f843545fab, []int{74} } func (m *RequiredDuringSchedulingIgnoredDuringExecution) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2126,7 +2154,7 @@ var xxx_messageInfo_RequiredDuringSchedulingIgnoredDuringExecution proto.Interna func (m *RollbackWindowSpec) Reset() { *m = RollbackWindowSpec{} } func (*RollbackWindowSpec) ProtoMessage() {} func (*RollbackWindowSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{74} + return fileDescriptor_e0e705f843545fab, []int{75} } func (m *RollbackWindowSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2154,7 +2182,7 @@ var xxx_messageInfo_RollbackWindowSpec proto.InternalMessageInfo func (m *Rollout) Reset() { *m = Rollout{} } func (*Rollout) ProtoMessage() {} func (*Rollout) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{75} + return fileDescriptor_e0e705f843545fab, []int{76} } func (m *Rollout) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2182,7 +2210,7 @@ var xxx_messageInfo_Rollout proto.InternalMessageInfo func (m *RolloutAnalysis) Reset() { *m = RolloutAnalysis{} } func (*RolloutAnalysis) ProtoMessage() {} func (*RolloutAnalysis) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{76} + return fileDescriptor_e0e705f843545fab, []int{77} } func (m *RolloutAnalysis) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2210,7 +2238,7 @@ var xxx_messageInfo_RolloutAnalysis proto.InternalMessageInfo func (m *RolloutAnalysisBackground) Reset() { *m = RolloutAnalysisBackground{} } func (*RolloutAnalysisBackground) ProtoMessage() {} func (*RolloutAnalysisBackground) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{77} + return fileDescriptor_e0e705f843545fab, []int{78} } func (m *RolloutAnalysisBackground) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2238,7 +2266,7 @@ var xxx_messageInfo_RolloutAnalysisBackground proto.InternalMessageInfo func (m *RolloutAnalysisRunStatus) Reset() { *m = RolloutAnalysisRunStatus{} } func (*RolloutAnalysisRunStatus) ProtoMessage() {} func (*RolloutAnalysisRunStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{78} + return fileDescriptor_e0e705f843545fab, []int{79} } func (m *RolloutAnalysisRunStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2266,7 +2294,7 @@ var xxx_messageInfo_RolloutAnalysisRunStatus proto.InternalMessageInfo func (m *RolloutCondition) Reset() { *m = RolloutCondition{} } func (*RolloutCondition) ProtoMessage() {} func (*RolloutCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{79} + return fileDescriptor_e0e705f843545fab, []int{80} } func (m *RolloutCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2294,7 +2322,7 @@ var xxx_messageInfo_RolloutCondition proto.InternalMessageInfo func (m *RolloutExperimentStep) Reset() { *m = RolloutExperimentStep{} } func (*RolloutExperimentStep) ProtoMessage() {} func (*RolloutExperimentStep) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{80} + return fileDescriptor_e0e705f843545fab, []int{81} } func (m *RolloutExperimentStep) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2324,7 +2352,7 @@ func (m *RolloutExperimentStepAnalysisTemplateRef) Reset() { } func (*RolloutExperimentStepAnalysisTemplateRef) ProtoMessage() {} func (*RolloutExperimentStepAnalysisTemplateRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{81} + return fileDescriptor_e0e705f843545fab, []int{82} } func (m *RolloutExperimentStepAnalysisTemplateRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2352,7 +2380,7 @@ var xxx_messageInfo_RolloutExperimentStepAnalysisTemplateRef proto.InternalMessa func (m *RolloutExperimentTemplate) Reset() { *m = RolloutExperimentTemplate{} } func (*RolloutExperimentTemplate) ProtoMessage() {} func (*RolloutExperimentTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{82} + return fileDescriptor_e0e705f843545fab, []int{83} } func (m *RolloutExperimentTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2380,7 +2408,7 @@ var xxx_messageInfo_RolloutExperimentTemplate proto.InternalMessageInfo func (m *RolloutList) Reset() { *m = RolloutList{} } func (*RolloutList) ProtoMessage() {} func (*RolloutList) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{83} + return fileDescriptor_e0e705f843545fab, []int{84} } func (m *RolloutList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2408,7 +2436,7 @@ var xxx_messageInfo_RolloutList proto.InternalMessageInfo func (m *RolloutPause) Reset() { *m = RolloutPause{} } func (*RolloutPause) ProtoMessage() {} func (*RolloutPause) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{84} + return fileDescriptor_e0e705f843545fab, []int{85} } func (m *RolloutPause) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2436,7 +2464,7 @@ var xxx_messageInfo_RolloutPause proto.InternalMessageInfo func (m *RolloutSpec) Reset() { *m = RolloutSpec{} } func (*RolloutSpec) ProtoMessage() {} func (*RolloutSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{85} + return fileDescriptor_e0e705f843545fab, []int{86} } func (m *RolloutSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2464,7 +2492,7 @@ var xxx_messageInfo_RolloutSpec proto.InternalMessageInfo func (m *RolloutStatus) Reset() { *m = RolloutStatus{} } func (*RolloutStatus) ProtoMessage() {} func (*RolloutStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{86} + return fileDescriptor_e0e705f843545fab, []int{87} } func (m *RolloutStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2492,7 +2520,7 @@ var xxx_messageInfo_RolloutStatus proto.InternalMessageInfo func (m *RolloutStrategy) Reset() { *m = RolloutStrategy{} } func (*RolloutStrategy) ProtoMessage() {} func (*RolloutStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{87} + return fileDescriptor_e0e705f843545fab, []int{88} } func (m *RolloutStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2520,7 +2548,7 @@ var xxx_messageInfo_RolloutStrategy proto.InternalMessageInfo func (m *RolloutTrafficRouting) Reset() { *m = RolloutTrafficRouting{} } func (*RolloutTrafficRouting) ProtoMessage() {} func (*RolloutTrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{88} + return fileDescriptor_e0e705f843545fab, []int{89} } func (m *RolloutTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2548,7 +2576,7 @@ var xxx_messageInfo_RolloutTrafficRouting proto.InternalMessageInfo func (m *RouteMatch) Reset() { *m = RouteMatch{} } func (*RouteMatch) ProtoMessage() {} func (*RouteMatch) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{89} + return fileDescriptor_e0e705f843545fab, []int{90} } func (m *RouteMatch) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2576,7 +2604,7 @@ var xxx_messageInfo_RouteMatch proto.InternalMessageInfo func (m *RunSummary) Reset() { *m = RunSummary{} } func (*RunSummary) ProtoMessage() {} func (*RunSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{90} + return fileDescriptor_e0e705f843545fab, []int{91} } func (m *RunSummary) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2604,7 +2632,7 @@ var xxx_messageInfo_RunSummary proto.InternalMessageInfo func (m *SMITrafficRouting) Reset() { *m = SMITrafficRouting{} } func (*SMITrafficRouting) ProtoMessage() {} func (*SMITrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{91} + return fileDescriptor_e0e705f843545fab, []int{92} } func (m *SMITrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2632,7 +2660,7 @@ var xxx_messageInfo_SMITrafficRouting proto.InternalMessageInfo func (m *ScopeDetail) Reset() { *m = ScopeDetail{} } func (*ScopeDetail) ProtoMessage() {} func (*ScopeDetail) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{92} + return fileDescriptor_e0e705f843545fab, []int{93} } func (m *ScopeDetail) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2660,7 +2688,7 @@ var xxx_messageInfo_ScopeDetail proto.InternalMessageInfo func (m *SecretKeyRef) Reset() { *m = SecretKeyRef{} } func (*SecretKeyRef) ProtoMessage() {} func (*SecretKeyRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{93} + return fileDescriptor_e0e705f843545fab, []int{94} } func (m *SecretKeyRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2688,7 +2716,7 @@ var xxx_messageInfo_SecretKeyRef proto.InternalMessageInfo func (m *SetCanaryScale) Reset() { *m = SetCanaryScale{} } func (*SetCanaryScale) ProtoMessage() {} func (*SetCanaryScale) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{94} + return fileDescriptor_e0e705f843545fab, []int{95} } func (m *SetCanaryScale) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2716,7 +2744,7 @@ var xxx_messageInfo_SetCanaryScale proto.InternalMessageInfo func (m *SetHeaderRoute) Reset() { *m = SetHeaderRoute{} } func (*SetHeaderRoute) ProtoMessage() {} func (*SetHeaderRoute) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{95} + return fileDescriptor_e0e705f843545fab, []int{96} } func (m *SetHeaderRoute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2744,7 +2772,7 @@ var xxx_messageInfo_SetHeaderRoute proto.InternalMessageInfo func (m *SetMirrorRoute) Reset() { *m = SetMirrorRoute{} } func (*SetMirrorRoute) ProtoMessage() {} func (*SetMirrorRoute) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{96} + return fileDescriptor_e0e705f843545fab, []int{97} } func (m *SetMirrorRoute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2772,7 +2800,7 @@ var xxx_messageInfo_SetMirrorRoute proto.InternalMessageInfo func (m *Sigv4Config) Reset() { *m = Sigv4Config{} } func (*Sigv4Config) ProtoMessage() {} func (*Sigv4Config) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{97} + return fileDescriptor_e0e705f843545fab, []int{98} } func (m *Sigv4Config) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2800,7 +2828,7 @@ var xxx_messageInfo_Sigv4Config proto.InternalMessageInfo func (m *SkyWalkingMetric) Reset() { *m = SkyWalkingMetric{} } func (*SkyWalkingMetric) ProtoMessage() {} func (*SkyWalkingMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{98} + return fileDescriptor_e0e705f843545fab, []int{99} } func (m *SkyWalkingMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2825,10 +2853,38 @@ func (m *SkyWalkingMetric) XXX_DiscardUnknown() { var xxx_messageInfo_SkyWalkingMetric proto.InternalMessageInfo +func (m *StepPluginStatus) Reset() { *m = StepPluginStatus{} } +func (*StepPluginStatus) ProtoMessage() {} +func (*StepPluginStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{100} +} +func (m *StepPluginStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StepPluginStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *StepPluginStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_StepPluginStatus.Merge(m, src) +} +func (m *StepPluginStatus) XXX_Size() int { + return m.Size() +} +func (m *StepPluginStatus) XXX_DiscardUnknown() { + xxx_messageInfo_StepPluginStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_StepPluginStatus proto.InternalMessageInfo + func (m *StickinessConfig) Reset() { *m = StickinessConfig{} } func (*StickinessConfig) ProtoMessage() {} func (*StickinessConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{99} + return fileDescriptor_e0e705f843545fab, []int{101} } func (m *StickinessConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2856,7 +2912,7 @@ var xxx_messageInfo_StickinessConfig proto.InternalMessageInfo func (m *StringMatch) Reset() { *m = StringMatch{} } func (*StringMatch) ProtoMessage() {} func (*StringMatch) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{100} + return fileDescriptor_e0e705f843545fab, []int{102} } func (m *StringMatch) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2884,7 +2940,7 @@ var xxx_messageInfo_StringMatch proto.InternalMessageInfo func (m *TCPRoute) Reset() { *m = TCPRoute{} } func (*TCPRoute) ProtoMessage() {} func (*TCPRoute) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{101} + return fileDescriptor_e0e705f843545fab, []int{103} } func (m *TCPRoute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2912,7 +2968,7 @@ var xxx_messageInfo_TCPRoute proto.InternalMessageInfo func (m *TLSRoute) Reset() { *m = TLSRoute{} } func (*TLSRoute) ProtoMessage() {} func (*TLSRoute) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{102} + return fileDescriptor_e0e705f843545fab, []int{104} } func (m *TLSRoute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2940,7 +2996,7 @@ var xxx_messageInfo_TLSRoute proto.InternalMessageInfo func (m *TTLStrategy) Reset() { *m = TTLStrategy{} } func (*TTLStrategy) ProtoMessage() {} func (*TTLStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{103} + return fileDescriptor_e0e705f843545fab, []int{105} } func (m *TTLStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2968,7 +3024,7 @@ var xxx_messageInfo_TTLStrategy proto.InternalMessageInfo func (m *TemplateService) Reset() { *m = TemplateService{} } func (*TemplateService) ProtoMessage() {} func (*TemplateService) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{104} + return fileDescriptor_e0e705f843545fab, []int{106} } func (m *TemplateService) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2996,7 +3052,7 @@ var xxx_messageInfo_TemplateService proto.InternalMessageInfo func (m *TemplateSpec) Reset() { *m = TemplateSpec{} } func (*TemplateSpec) ProtoMessage() {} func (*TemplateSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{105} + return fileDescriptor_e0e705f843545fab, []int{107} } func (m *TemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3024,7 +3080,7 @@ var xxx_messageInfo_TemplateSpec proto.InternalMessageInfo func (m *TemplateStatus) Reset() { *m = TemplateStatus{} } func (*TemplateStatus) ProtoMessage() {} func (*TemplateStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{106} + return fileDescriptor_e0e705f843545fab, []int{108} } func (m *TemplateStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3052,7 +3108,7 @@ var xxx_messageInfo_TemplateStatus proto.InternalMessageInfo func (m *TraefikTrafficRouting) Reset() { *m = TraefikTrafficRouting{} } func (*TraefikTrafficRouting) ProtoMessage() {} func (*TraefikTrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{107} + return fileDescriptor_e0e705f843545fab, []int{109} } func (m *TraefikTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3080,7 +3136,7 @@ var xxx_messageInfo_TraefikTrafficRouting proto.InternalMessageInfo func (m *TrafficWeights) Reset() { *m = TrafficWeights{} } func (*TrafficWeights) ProtoMessage() {} func (*TrafficWeights) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{108} + return fileDescriptor_e0e705f843545fab, []int{110} } func (m *TrafficWeights) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3108,7 +3164,7 @@ var xxx_messageInfo_TrafficWeights proto.InternalMessageInfo func (m *ValueFrom) Reset() { *m = ValueFrom{} } func (*ValueFrom) ProtoMessage() {} func (*ValueFrom) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{109} + return fileDescriptor_e0e705f843545fab, []int{111} } func (m *ValueFrom) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3136,7 +3192,7 @@ var xxx_messageInfo_ValueFrom proto.InternalMessageInfo func (m *WavefrontMetric) Reset() { *m = WavefrontMetric{} } func (*WavefrontMetric) ProtoMessage() {} func (*WavefrontMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{110} + return fileDescriptor_e0e705f843545fab, []int{112} } func (m *WavefrontMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3164,7 +3220,7 @@ var xxx_messageInfo_WavefrontMetric proto.InternalMessageInfo func (m *WebMetric) Reset() { *m = WebMetric{} } func (*WebMetric) ProtoMessage() {} func (*WebMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{111} + return fileDescriptor_e0e705f843545fab, []int{113} } func (m *WebMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3192,7 +3248,7 @@ var xxx_messageInfo_WebMetric proto.InternalMessageInfo func (m *WebMetricHeader) Reset() { *m = WebMetricHeader{} } func (*WebMetricHeader) ProtoMessage() {} func (*WebMetricHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{112} + return fileDescriptor_e0e705f843545fab, []int{114} } func (m *WebMetricHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3220,7 +3276,7 @@ var xxx_messageInfo_WebMetricHeader proto.InternalMessageInfo func (m *WeightDestination) Reset() { *m = WeightDestination{} } func (*WeightDestination) ProtoMessage() {} func (*WeightDestination) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{113} + return fileDescriptor_e0e705f843545fab, []int{115} } func (m *WeightDestination) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3323,6 +3379,7 @@ func init() { proto.RegisterType((*ObjectRef)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.ObjectRef") proto.RegisterType((*PauseCondition)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PauseCondition") proto.RegisterType((*PingPongSpec)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PingPongSpec") + proto.RegisterType((*PluginStep)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PluginStep") proto.RegisterType((*PodTemplateMetadata)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PodTemplateMetadata") proto.RegisterMapType((map[string]string)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PodTemplateMetadata.AnnotationsEntry") proto.RegisterMapType((map[string]string)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.PodTemplateMetadata.LabelsEntry") @@ -3357,6 +3414,7 @@ func init() { proto.RegisterType((*SetMirrorRoute)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetMirrorRoute") proto.RegisterType((*Sigv4Config)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.Sigv4Config") proto.RegisterType((*SkyWalkingMetric)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SkyWalkingMetric") + proto.RegisterType((*StepPluginStatus)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StepPluginStatus") proto.RegisterType((*StickinessConfig)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StickinessConfig") proto.RegisterType((*StringMatch)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StringMatch") proto.RegisterType((*TCPRoute)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.TCPRoute") @@ -3379,550 +3437,565 @@ func init() { } var fileDescriptor_e0e705f843545fab = []byte{ - // 8675 bytes of a gzipped FileDescriptorProto + // 8914 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6d, 0x6c, 0x64, 0xd7, - 0x75, 0x98, 0x1e, 0x87, 0x43, 0xce, 0x9c, 0xe1, 0x92, 0xdc, 0xbb, 0xbb, 0x12, 0x45, 0x69, 0x77, - 0xd6, 0x4f, 0xa9, 0x2a, 0xc5, 0x32, 0x69, 0xaf, 0xa4, 0x56, 0xb6, 0x5c, 0xb5, 0x33, 0xe4, 0xae, - 0x96, 0x2b, 0x72, 0x97, 0x3a, 0xc3, 0xd5, 0x26, 0xb6, 0x95, 0xf8, 0x71, 0xe6, 0x72, 0xf8, 0x96, - 0x33, 0xef, 0x8d, 0xdf, 0x7b, 0xc3, 0x5d, 0xca, 0x42, 0x2c, 0xdb, 0x50, 0xe2, 0xb8, 0x36, 0xe2, - 0x26, 0x31, 0x8a, 0xa2, 0x45, 0xe1, 0x06, 0x29, 0xd2, 0x36, 0xf9, 0x51, 0x04, 0x29, 0xda, 0x1f, - 0x01, 0x5a, 0xd4, 0x4d, 0xe0, 0x00, 0x75, 0xe1, 0xfc, 0x68, 0x9d, 0x16, 0x08, 0x53, 0x33, 0xfd, - 0xd3, 0xa0, 0x85, 0xd1, 0xc2, 0x45, 0x50, 0xfd, 0x28, 0x8a, 0xfb, 0xf9, 0xee, 0x7b, 0xf3, 0x86, - 0x1f, 0x3b, 0x8f, 0x2b, 0xa5, 0xf5, 0xbf, 0x99, 0x7b, 0xce, 0x3d, 0xe7, 0xbc, 0xfb, 0x79, 0xee, - 0xb9, 0xe7, 0x9c, 0x0b, 0xab, 0x6d, 0x37, 0xda, 0xee, 0x6f, 0x2e, 0x34, 0xfd, 0xee, 0xa2, 0x13, - 0xb4, 0xfd, 0x5e, 0xe0, 0xdf, 0xe5, 0x3f, 0x3e, 0x12, 0xf8, 0x9d, 0x8e, 0xdf, 0x8f, 0xc2, 0xc5, - 0xde, 0x4e, 0x7b, 0xd1, 0xe9, 0xb9, 0xe1, 0xa2, 0x2e, 0xd9, 0xfd, 0x98, 0xd3, 0xe9, 0x6d, 0x3b, - 0x1f, 0x5b, 0x6c, 0x53, 0x8f, 0x06, 0x4e, 0x44, 0x5b, 0x0b, 0xbd, 0xc0, 0x8f, 0x7c, 0xf2, 0xc9, - 0x98, 0xda, 0x82, 0xa2, 0xc6, 0x7f, 0xfc, 0xac, 0xaa, 0xbb, 0xd0, 0xdb, 0x69, 0x2f, 0x30, 0x6a, - 0x0b, 0xba, 0x44, 0x51, 0x9b, 0xff, 0x88, 0x21, 0x4b, 0xdb, 0x6f, 0xfb, 0x8b, 0x9c, 0xe8, 0x66, - 0x7f, 0x8b, 0xff, 0xe3, 0x7f, 0xf8, 0x2f, 0xc1, 0x6c, 0xfe, 0xa9, 0x9d, 0x97, 0xc2, 0x05, 0xd7, - 0x67, 0xb2, 0x2d, 0x6e, 0x3a, 0x51, 0x73, 0x7b, 0x71, 0x77, 0x40, 0xa2, 0x79, 0xdb, 0x40, 0x6a, - 0xfa, 0x01, 0xcd, 0xc2, 0x79, 0x21, 0xc6, 0xe9, 0x3a, 0xcd, 0x6d, 0xd7, 0xa3, 0xc1, 0x5e, 0xfc, - 0xd5, 0x5d, 0x1a, 0x39, 0x59, 0xb5, 0x16, 0x87, 0xd5, 0x0a, 0xfa, 0x5e, 0xe4, 0x76, 0xe9, 0x40, - 0x85, 0xbf, 0x72, 0x54, 0x85, 0xb0, 0xb9, 0x4d, 0xbb, 0xce, 0x40, 0xbd, 0xe7, 0x87, 0xd5, 0xeb, - 0x47, 0x6e, 0x67, 0xd1, 0xf5, 0xa2, 0x30, 0x0a, 0xd2, 0x95, 0xec, 0x1f, 0x16, 0xa0, 0x5c, 0x5b, - 0xad, 0x37, 0x22, 0x27, 0xea, 0x87, 0xe4, 0xe7, 0x2d, 0x98, 0xea, 0xf8, 0x4e, 0xab, 0xee, 0x74, - 0x1c, 0xaf, 0x49, 0x83, 0x39, 0xeb, 0xb2, 0xf5, 0x4c, 0xe5, 0xca, 0xea, 0xc2, 0x28, 0xfd, 0xb5, - 0x50, 0xbb, 0x17, 0x22, 0x0d, 0xfd, 0x7e, 0xd0, 0xa4, 0x48, 0xb7, 0xea, 0xe7, 0xbf, 0xb3, 0x5f, - 0x7d, 0xe4, 0x60, 0xbf, 0x3a, 0xb5, 0x6a, 0x70, 0xc2, 0x04, 0x5f, 0xf2, 0x4d, 0x0b, 0xce, 0x36, - 0x1d, 0xcf, 0x09, 0xf6, 0x36, 0x9c, 0xa0, 0x4d, 0xa3, 0x57, 0x03, 0xbf, 0xdf, 0x9b, 0x1b, 0x3b, - 0x05, 0x69, 0x1e, 0x97, 0xd2, 0x9c, 0x5d, 0x4a, 0xb3, 0xc3, 0x41, 0x09, 0xb8, 0x5c, 0x61, 0xe4, - 0x6c, 0x76, 0xa8, 0x29, 0x57, 0xe1, 0x34, 0xe5, 0x6a, 0xa4, 0xd9, 0xe1, 0xa0, 0x04, 0xe4, 0x59, - 0x98, 0x74, 0xbd, 0x76, 0x40, 0xc3, 0x70, 0x6e, 0xfc, 0xb2, 0xf5, 0x4c, 0xb9, 0x3e, 0x23, 0xab, - 0x4f, 0xae, 0x88, 0x62, 0x54, 0x70, 0xfb, 0xb7, 0x0b, 0x70, 0xb6, 0xb6, 0x5a, 0xdf, 0x08, 0x9c, - 0xad, 0x2d, 0xb7, 0x89, 0x7e, 0x3f, 0x72, 0xbd, 0xb6, 0x49, 0xc0, 0x3a, 0x9c, 0x00, 0x79, 0x11, - 0x2a, 0x21, 0x0d, 0x76, 0xdd, 0x26, 0x5d, 0xf7, 0x83, 0x88, 0x77, 0x4a, 0xb1, 0x7e, 0x4e, 0xa2, - 0x57, 0x1a, 0x31, 0x08, 0x4d, 0x3c, 0x56, 0x2d, 0xf0, 0xfd, 0x48, 0xc2, 0x79, 0x9b, 0x95, 0xe3, - 0x6a, 0x18, 0x83, 0xd0, 0xc4, 0x23, 0xcb, 0x30, 0xeb, 0x78, 0x9e, 0x1f, 0x39, 0x91, 0xeb, 0x7b, - 0xeb, 0x01, 0xdd, 0x72, 0xef, 0xcb, 0x4f, 0x9c, 0x93, 0x75, 0x67, 0x6b, 0x29, 0x38, 0x0e, 0xd4, - 0x20, 0xdf, 0xb0, 0x60, 0x36, 0x8c, 0xdc, 0xe6, 0x8e, 0xeb, 0xd1, 0x30, 0x5c, 0xf2, 0xbd, 0x2d, - 0xb7, 0x3d, 0x57, 0xe4, 0xdd, 0x76, 0x73, 0xb4, 0x6e, 0x6b, 0xa4, 0xa8, 0xd6, 0xcf, 0x33, 0x91, - 0xd2, 0xa5, 0x38, 0xc0, 0x9d, 0x7c, 0x18, 0xca, 0xb2, 0x45, 0x69, 0x38, 0x37, 0x71, 0xb9, 0xf0, - 0x4c, 0xb9, 0x7e, 0xe6, 0x60, 0xbf, 0x5a, 0x5e, 0x51, 0x85, 0x18, 0xc3, 0xed, 0x65, 0x98, 0xab, - 0x75, 0x37, 0x9d, 0x30, 0x74, 0x5a, 0x7e, 0x90, 0xea, 0xba, 0x67, 0xa0, 0xd4, 0x75, 0x7a, 0x3d, - 0xd7, 0x6b, 0xb3, 0xbe, 0x63, 0x74, 0xa6, 0x0e, 0xf6, 0xab, 0xa5, 0x35, 0x59, 0x86, 0x1a, 0x6a, - 0xff, 0xc7, 0x31, 0xa8, 0xd4, 0x3c, 0xa7, 0xb3, 0x17, 0xba, 0x21, 0xf6, 0x3d, 0xf2, 0x59, 0x28, - 0xb1, 0x55, 0xab, 0xe5, 0x44, 0x8e, 0x9c, 0xe9, 0x1f, 0x5d, 0x10, 0x8b, 0xc8, 0x82, 0xb9, 0x88, - 0xc4, 0x9f, 0xcf, 0xb0, 0x17, 0x76, 0x3f, 0xb6, 0x70, 0x6b, 0xf3, 0x2e, 0x6d, 0x46, 0x6b, 0x34, - 0x72, 0xea, 0x44, 0xf6, 0x02, 0xc4, 0x65, 0xa8, 0xa9, 0x12, 0x1f, 0xc6, 0xc3, 0x1e, 0x6d, 0xca, - 0x99, 0xbb, 0x36, 0xe2, 0x0c, 0x89, 0x45, 0x6f, 0xf4, 0x68, 0xb3, 0x3e, 0x25, 0x59, 0x8f, 0xb3, - 0x7f, 0xc8, 0x19, 0x91, 0x7b, 0x30, 0x11, 0xf2, 0xb5, 0x4c, 0x4e, 0xca, 0x5b, 0xf9, 0xb1, 0xe4, - 0x64, 0xeb, 0xd3, 0x92, 0xe9, 0x84, 0xf8, 0x8f, 0x92, 0x9d, 0xfd, 0x9f, 0x2c, 0x38, 0x67, 0x60, - 0xd7, 0x82, 0x76, 0xbf, 0x4b, 0xbd, 0x88, 0x5c, 0x86, 0x71, 0xcf, 0xe9, 0x52, 0x39, 0xab, 0xb4, - 0xc8, 0x37, 0x9d, 0x2e, 0x45, 0x0e, 0x21, 0x4f, 0x41, 0x71, 0xd7, 0xe9, 0xf4, 0x29, 0x6f, 0xa4, - 0x72, 0xfd, 0x8c, 0x44, 0x29, 0xbe, 0xc1, 0x0a, 0x51, 0xc0, 0xc8, 0xdb, 0x50, 0xe6, 0x3f, 0xae, - 0x05, 0x7e, 0x37, 0xa7, 0x4f, 0x93, 0x12, 0xbe, 0xa1, 0xc8, 0x8a, 0xe1, 0xa7, 0xff, 0x62, 0xcc, - 0xd0, 0xfe, 0x13, 0x0b, 0x66, 0x8c, 0x8f, 0x5b, 0x75, 0xc3, 0x88, 0x7c, 0x66, 0x60, 0xf0, 0x2c, - 0x1c, 0x6f, 0xf0, 0xb0, 0xda, 0x7c, 0xe8, 0xcc, 0xca, 0x2f, 0x2d, 0xa9, 0x12, 0x63, 0xe0, 0x78, - 0x50, 0x74, 0x23, 0xda, 0x0d, 0xe7, 0xc6, 0x2e, 0x17, 0x9e, 0xa9, 0x5c, 0x59, 0xc9, 0xad, 0x1b, - 0xe3, 0xf6, 0x5d, 0x61, 0xf4, 0x51, 0xb0, 0xb1, 0x7f, 0xa7, 0x90, 0xe8, 0xbe, 0x35, 0x25, 0xc7, - 0xbb, 0x16, 0x4c, 0x74, 0x9c, 0x4d, 0xda, 0x11, 0x73, 0xab, 0x72, 0xe5, 0xcd, 0xdc, 0x24, 0x51, - 0x3c, 0x16, 0x56, 0x39, 0xfd, 0xab, 0x5e, 0x14, 0xec, 0xc5, 0xc3, 0x4b, 0x14, 0xa2, 0x64, 0x4e, - 0xfe, 0x8e, 0x05, 0x95, 0x78, 0x55, 0x53, 0xcd, 0xb2, 0x99, 0xbf, 0x30, 0xf1, 0x62, 0x2a, 0x25, - 0xd2, 0x4b, 0xb4, 0x01, 0x41, 0x53, 0x96, 0xf9, 0x8f, 0x43, 0xc5, 0xf8, 0x04, 0x32, 0x0b, 0x85, - 0x1d, 0xba, 0x27, 0x06, 0x3c, 0xb2, 0x9f, 0xe4, 0x7c, 0x62, 0x84, 0xcb, 0x21, 0xfd, 0x89, 0xb1, - 0x97, 0xac, 0xf9, 0x57, 0x60, 0x36, 0xcd, 0xf0, 0x24, 0xf5, 0xed, 0x7f, 0x5a, 0x4c, 0x0c, 0x4c, - 0xb6, 0x10, 0x10, 0x1f, 0x26, 0xbb, 0x34, 0x0a, 0xdc, 0xa6, 0xea, 0xb2, 0xe5, 0xd1, 0x5a, 0x69, - 0x8d, 0x13, 0x8b, 0x37, 0x44, 0xf1, 0x3f, 0x44, 0xc5, 0x85, 0x6c, 0xc3, 0xb8, 0x13, 0xb4, 0x55, - 0x9f, 0x5c, 0xcb, 0x67, 0x5a, 0xc6, 0x4b, 0x45, 0x2d, 0x68, 0x87, 0xc8, 0x39, 0x90, 0x45, 0x28, - 0x47, 0x34, 0xe8, 0xba, 0x9e, 0x13, 0x89, 0x1d, 0xb4, 0x54, 0x3f, 0x2b, 0xd1, 0xca, 0x1b, 0x0a, - 0x80, 0x31, 0x0e, 0xe9, 0xc0, 0x44, 0x2b, 0xd8, 0xc3, 0xbe, 0x37, 0x37, 0x9e, 0x47, 0x53, 0x2c, - 0x73, 0x5a, 0xf1, 0x20, 0x15, 0xff, 0x51, 0xf2, 0x20, 0xbf, 0x6e, 0xc1, 0xf9, 0x2e, 0x75, 0xc2, - 0x7e, 0x40, 0xd9, 0x27, 0x20, 0x8d, 0xa8, 0xc7, 0x3a, 0x76, 0xae, 0xc8, 0x99, 0xe3, 0xa8, 0xfd, - 0x30, 0x48, 0xb9, 0xfe, 0xa4, 0x14, 0xe5, 0x7c, 0x16, 0x14, 0x33, 0xa5, 0x21, 0x6f, 0x43, 0x25, - 0x8a, 0x3a, 0x8d, 0x88, 0xe9, 0xc1, 0xed, 0xbd, 0xb9, 0x09, 0xbe, 0x78, 0x8d, 0xb8, 0xc2, 0x6c, - 0x6c, 0xac, 0x2a, 0x82, 0xf5, 0x19, 0x36, 0x5b, 0x8c, 0x02, 0x34, 0xd9, 0xd9, 0xff, 0xa2, 0x08, - 0x67, 0x07, 0xb6, 0x15, 0xf2, 0x02, 0x14, 0x7b, 0xdb, 0x4e, 0xa8, 0xf6, 0x89, 0x4b, 0x6a, 0x91, - 0x5a, 0x67, 0x85, 0xef, 0xed, 0x57, 0xcf, 0xa8, 0x2a, 0xbc, 0x00, 0x05, 0x32, 0xd3, 0xda, 0xba, - 0x34, 0x0c, 0x9d, 0xb6, 0xda, 0x3c, 0x8c, 0x41, 0xca, 0x8b, 0x51, 0xc1, 0xc9, 0x2f, 0x58, 0x70, - 0x46, 0x0c, 0x58, 0xa4, 0x61, 0xbf, 0x13, 0xb1, 0x0d, 0x92, 0x75, 0xca, 0x8d, 0x3c, 0x26, 0x87, - 0x20, 0x59, 0xbf, 0x20, 0xb9, 0x9f, 0x31, 0x4b, 0x43, 0x4c, 0xf2, 0x25, 0x77, 0xa0, 0x1c, 0x46, - 0x4e, 0x10, 0xd1, 0x56, 0x2d, 0xe2, 0xaa, 0x5c, 0xe5, 0xca, 0x4f, 0x1e, 0x6f, 0xe7, 0xd8, 0x70, - 0xbb, 0x54, 0xec, 0x52, 0x0d, 0x45, 0x00, 0x63, 0x5a, 0xe4, 0x6d, 0x80, 0xa0, 0xef, 0x35, 0xfa, - 0xdd, 0xae, 0x13, 0xec, 0x49, 0xed, 0xee, 0xfa, 0x68, 0x9f, 0x87, 0x9a, 0x5e, 0xac, 0xe8, 0xc4, - 0x65, 0x68, 0xf0, 0x23, 0x5f, 0xb4, 0xe0, 0x8c, 0x98, 0x07, 0x4a, 0x82, 0x89, 0x9c, 0x25, 0x38, - 0xcb, 0x9a, 0x76, 0xd9, 0x64, 0x81, 0x49, 0x8e, 0xe4, 0x4d, 0xa8, 0x34, 0xfd, 0x6e, 0xaf, 0x43, - 0x45, 0xe3, 0x4e, 0x9e, 0xb8, 0x71, 0xf9, 0xd0, 0x5d, 0x8a, 0x49, 0xa0, 0x49, 0xcf, 0xfe, 0xf7, - 0x49, 0x1d, 0x47, 0x0d, 0x69, 0xf2, 0x69, 0x78, 0x3c, 0xec, 0x37, 0x9b, 0x34, 0x0c, 0xb7, 0xfa, - 0x1d, 0xec, 0x7b, 0xd7, 0xdd, 0x30, 0xf2, 0x83, 0xbd, 0x55, 0xb7, 0xeb, 0x46, 0x7c, 0x40, 0x17, - 0xeb, 0x17, 0x0f, 0xf6, 0xab, 0x8f, 0x37, 0x86, 0x21, 0xe1, 0xf0, 0xfa, 0xc4, 0x81, 0x27, 0xfa, - 0xde, 0x70, 0xf2, 0xe2, 0xf8, 0x51, 0x3d, 0xd8, 0xaf, 0x3e, 0x71, 0x7b, 0x38, 0x1a, 0x1e, 0x46, - 0xc3, 0xfe, 0x33, 0x8b, 0x6d, 0x43, 0xe2, 0xbb, 0x36, 0x68, 0xb7, 0xd7, 0x61, 0x4b, 0xe7, 0xe9, - 0x2b, 0xc7, 0x51, 0x42, 0x39, 0xc6, 0x7c, 0xf6, 0x72, 0x25, 0xff, 0x30, 0x0d, 0xd9, 0xfe, 0xaf, - 0x16, 0x9c, 0x4f, 0x23, 0x3f, 0x04, 0x85, 0x2e, 0x4c, 0x2a, 0x74, 0x37, 0xf3, 0xfd, 0xda, 0x21, - 0x5a, 0xdd, 0x2f, 0x1a, 0x03, 0x56, 0xa1, 0x22, 0xdd, 0x22, 0x2f, 0xc1, 0x54, 0x24, 0xff, 0xde, - 0x8c, 0x95, 0x73, 0x6d, 0x98, 0xd8, 0x30, 0x60, 0x98, 0xc0, 0x64, 0x35, 0x9b, 0x9d, 0x7e, 0x18, - 0xd1, 0xa0, 0xd1, 0xf4, 0x7b, 0x62, 0xd9, 0x2d, 0xc5, 0x35, 0x97, 0x0c, 0x18, 0x26, 0x30, 0xed, - 0xbf, 0x59, 0x1c, 0x6c, 0xf7, 0xff, 0xd7, 0xf5, 0x95, 0x58, 0xfd, 0x28, 0xbc, 0x9f, 0xea, 0xc7, - 0xf8, 0x07, 0x4a, 0xfd, 0xf8, 0x92, 0xc5, 0xb4, 0x38, 0x31, 0x00, 0x42, 0xa9, 0x1a, 0xbd, 0x9e, - 0xef, 0x74, 0x40, 0xba, 0x65, 0x2a, 0x86, 0x92, 0x17, 0xc6, 0x6c, 0xed, 0x7f, 0x34, 0x0e, 0x53, - 0x35, 0x2f, 0x72, 0x6b, 0x5b, 0x5b, 0xae, 0xe7, 0x46, 0x7b, 0xe4, 0x6b, 0x63, 0xb0, 0xd8, 0x0b, - 0xe8, 0x16, 0x0d, 0x02, 0xda, 0x5a, 0xee, 0x07, 0xae, 0xd7, 0x6e, 0x34, 0xb7, 0x69, 0xab, 0xdf, - 0x71, 0xbd, 0xf6, 0x4a, 0xdb, 0xf3, 0x75, 0xf1, 0xd5, 0xfb, 0xb4, 0xd9, 0xe7, 0xed, 0x2a, 0x56, - 0x89, 0xee, 0x68, 0xb2, 0xaf, 0x9f, 0x8c, 0x69, 0xfd, 0xf9, 0x83, 0xfd, 0xea, 0xe2, 0x09, 0x2b, - 0xe1, 0x49, 0x3f, 0x8d, 0x7c, 0x65, 0x0c, 0x16, 0x02, 0xfa, 0xb9, 0xbe, 0x7b, 0xfc, 0xd6, 0x10, - 0xcb, 0x78, 0x67, 0xc4, 0xed, 0xfe, 0x44, 0x3c, 0xeb, 0x57, 0x0e, 0xf6, 0xab, 0x27, 0xac, 0x83, - 0x27, 0xfc, 0x2e, 0x7b, 0x1d, 0x2a, 0xb5, 0x9e, 0x1b, 0xba, 0xf7, 0xd1, 0xef, 0x47, 0xf4, 0x18, - 0x06, 0x8d, 0x2a, 0x14, 0x83, 0x7e, 0x87, 0x8a, 0x05, 0xa6, 0x5c, 0x2f, 0xb3, 0x65, 0x19, 0x59, - 0x01, 0x8a, 0x72, 0xfb, 0x4b, 0x6c, 0x0b, 0xe2, 0x24, 0x53, 0xa6, 0xac, 0xbb, 0x50, 0x0c, 0x18, - 0x13, 0x39, 0xb2, 0x46, 0x3d, 0xf5, 0xc7, 0x52, 0x4b, 0x21, 0xd8, 0x4f, 0x14, 0x2c, 0xec, 0x6f, - 0x8f, 0xc1, 0x85, 0x5a, 0xaf, 0xb7, 0x46, 0xc3, 0xed, 0x94, 0x14, 0xbf, 0x64, 0xc1, 0xf4, 0xae, - 0x1b, 0x44, 0x7d, 0xa7, 0xa3, 0xac, 0x95, 0x42, 0x9e, 0xc6, 0xa8, 0xf2, 0x70, 0x6e, 0x6f, 0x24, - 0x48, 0xd7, 0xc9, 0xc1, 0x7e, 0x75, 0x3a, 0x59, 0x86, 0x29, 0xf6, 0xe4, 0x6f, 0x5b, 0x30, 0x2b, - 0x8b, 0x6e, 0xfa, 0x2d, 0x6a, 0x5a, 0xc3, 0x6f, 0xe7, 0x29, 0x93, 0x26, 0x2e, 0xac, 0x98, 0xe9, - 0x52, 0x1c, 0x10, 0xc2, 0xfe, 0xef, 0x63, 0xf0, 0xd8, 0x10, 0x1a, 0xe4, 0x37, 0x2c, 0x38, 0x2f, - 0x4c, 0xe8, 0x06, 0x08, 0xe9, 0x96, 0x6c, 0xcd, 0x9f, 0xce, 0x5b, 0x72, 0x64, 0x53, 0x9c, 0x7a, - 0x4d, 0x5a, 0x9f, 0x63, 0x4b, 0xf2, 0x52, 0x06, 0x6b, 0xcc, 0x14, 0x88, 0x4b, 0x2a, 0x8c, 0xea, - 0x29, 0x49, 0xc7, 0x1e, 0x8a, 0xa4, 0x8d, 0x0c, 0xd6, 0x98, 0x29, 0x90, 0xfd, 0xd7, 0xe1, 0x89, - 0x43, 0xc8, 0x1d, 0x3d, 0x39, 0xed, 0x37, 0xf5, 0xa8, 0x4f, 0x8e, 0xb9, 0x63, 0xcc, 0x6b, 0x1b, - 0x26, 0xf8, 0xd4, 0x51, 0x13, 0x1b, 0xd8, 0x1e, 0xcc, 0xe7, 0x54, 0x88, 0x12, 0x62, 0x7f, 0xdb, - 0x82, 0xd2, 0x09, 0x6c, 0x9f, 0xd5, 0xa4, 0xed, 0xb3, 0x3c, 0x60, 0xf7, 0x8c, 0x06, 0xed, 0x9e, - 0xaf, 0x8e, 0xd6, 0x1b, 0xc7, 0xb1, 0x77, 0xfe, 0xd0, 0x82, 0xb3, 0x03, 0xf6, 0x51, 0xb2, 0x0d, - 0xe7, 0x7b, 0x7e, 0x4b, 0x6d, 0xa7, 0xd7, 0x9d, 0x70, 0x9b, 0xc3, 0xe4, 0xe7, 0xbd, 0xc0, 0x7a, - 0x72, 0x3d, 0x03, 0xfe, 0xde, 0x7e, 0x75, 0x4e, 0x13, 0x49, 0x21, 0x60, 0x26, 0x45, 0xd2, 0x83, - 0xd2, 0x96, 0x4b, 0x3b, 0xad, 0x78, 0x08, 0x8e, 0xa8, 0xa5, 0x5d, 0x93, 0xd4, 0xc4, 0xd5, 0x80, - 0xfa, 0x87, 0x9a, 0x8b, 0xfd, 0x23, 0x0b, 0xa6, 0x6b, 0xfd, 0x68, 0x9b, 0xe9, 0x28, 0x4d, 0x6e, - 0x8d, 0x23, 0x1e, 0x14, 0x43, 0xb7, 0xbd, 0xfb, 0x42, 0x3e, 0x8b, 0x71, 0x83, 0x91, 0x92, 0x57, - 0x24, 0x5a, 0x59, 0xe7, 0x85, 0x28, 0xd8, 0x90, 0x00, 0x26, 0x7c, 0xa7, 0x1f, 0x6d, 0x5f, 0x91, - 0x9f, 0x3c, 0xa2, 0x65, 0xe2, 0x16, 0xfb, 0x9c, 0x2b, 0x92, 0xa3, 0x56, 0x19, 0x45, 0x29, 0x4a, - 0x4e, 0xf6, 0x17, 0x60, 0x3a, 0x79, 0xef, 0x76, 0x8c, 0x31, 0x7b, 0x11, 0x0a, 0x4e, 0xe0, 0xc9, - 0x11, 0x5b, 0x91, 0x08, 0x85, 0x1a, 0xde, 0x44, 0x56, 0x4e, 0x9e, 0x83, 0xd2, 0x56, 0xbf, 0xd3, - 0xe1, 0xe7, 0x0a, 0x71, 0xc9, 0xa5, 0x8f, 0x45, 0xd7, 0x64, 0x39, 0x6a, 0x0c, 0xfb, 0x7f, 0x8f, - 0xc3, 0x4c, 0xbd, 0xd3, 0xa7, 0xaf, 0x06, 0x94, 0x2a, 0x5b, 0x50, 0x0d, 0x66, 0x7a, 0x01, 0xdd, - 0x75, 0xe9, 0xbd, 0x06, 0xed, 0xd0, 0x66, 0xe4, 0x07, 0x52, 0x9a, 0xc7, 0x24, 0xa1, 0x99, 0xf5, - 0x24, 0x18, 0xd3, 0xf8, 0xe4, 0x15, 0x98, 0x76, 0x9a, 0x91, 0xbb, 0x4b, 0x35, 0x05, 0x21, 0xee, - 0xa3, 0x92, 0xc2, 0x74, 0x2d, 0x01, 0xc5, 0x14, 0x36, 0xf9, 0x0c, 0xcc, 0x85, 0x4d, 0xa7, 0x43, - 0x6f, 0xf7, 0x24, 0xab, 0xa5, 0x6d, 0xda, 0xdc, 0x59, 0xf7, 0x5d, 0x2f, 0x92, 0x76, 0xc7, 0xcb, - 0x92, 0xd2, 0x5c, 0x63, 0x08, 0x1e, 0x0e, 0xa5, 0x40, 0xfe, 0xa5, 0x05, 0x17, 0x7b, 0x01, 0x5d, - 0x0f, 0xfc, 0xae, 0xcf, 0x86, 0xda, 0x80, 0x39, 0x4c, 0x9a, 0x85, 0xde, 0x18, 0x51, 0x97, 0x12, - 0x25, 0x83, 0x77, 0x38, 0x1f, 0x3a, 0xd8, 0xaf, 0x5e, 0x5c, 0x3f, 0x4c, 0x00, 0x3c, 0x5c, 0x3e, - 0xf2, 0xaf, 0x2d, 0xb8, 0xd4, 0xf3, 0xc3, 0xe8, 0x90, 0x4f, 0x28, 0x9e, 0xea, 0x27, 0xd8, 0x07, - 0xfb, 0xd5, 0x4b, 0xeb, 0x87, 0x4a, 0x80, 0x47, 0x48, 0x68, 0x1f, 0x54, 0xe0, 0xac, 0x31, 0xf6, - 0xa4, 0x31, 0xe7, 0x65, 0x38, 0xa3, 0x06, 0x43, 0xac, 0xfb, 0x94, 0x63, 0xdb, 0x5e, 0xcd, 0x04, - 0x62, 0x12, 0x97, 0x8d, 0x3b, 0x3d, 0x14, 0x45, 0xed, 0xd4, 0xb8, 0x5b, 0x4f, 0x40, 0x31, 0x85, - 0x4d, 0x56, 0xe0, 0x9c, 0x2c, 0x41, 0xda, 0xeb, 0xb8, 0x4d, 0x67, 0xc9, 0xef, 0xcb, 0x21, 0x57, - 0xac, 0x3f, 0x76, 0xb0, 0x5f, 0x3d, 0xb7, 0x3e, 0x08, 0xc6, 0xac, 0x3a, 0x64, 0x15, 0xce, 0x3b, - 0xfd, 0xc8, 0xd7, 0xdf, 0x7f, 0xd5, 0x63, 0xdb, 0x69, 0x8b, 0x0f, 0xad, 0x92, 0xd8, 0x77, 0x6b, - 0x19, 0x70, 0xcc, 0xac, 0x45, 0xd6, 0x53, 0xd4, 0x1a, 0xb4, 0xe9, 0x7b, 0x2d, 0xd1, 0xcb, 0xc5, - 0xf8, 0x18, 0x58, 0xcb, 0xc0, 0xc1, 0xcc, 0x9a, 0xa4, 0x03, 0xd3, 0x5d, 0xe7, 0xfe, 0x6d, 0xcf, - 0xd9, 0x75, 0xdc, 0x0e, 0x63, 0x22, 0xed, 0x85, 0xc3, 0xad, 0x4c, 0xfd, 0xc8, 0xed, 0x2c, 0x08, - 0x3f, 0x8e, 0x85, 0x15, 0x2f, 0xba, 0x15, 0x34, 0x22, 0xa6, 0xa9, 0x0b, 0x0d, 0x72, 0x2d, 0x41, - 0x0b, 0x53, 0xb4, 0xc9, 0x2d, 0xb8, 0xc0, 0xa7, 0xe3, 0xb2, 0x7f, 0xcf, 0x5b, 0xa6, 0x1d, 0x67, - 0x4f, 0x7d, 0xc0, 0x24, 0xff, 0x80, 0xc7, 0x0f, 0xf6, 0xab, 0x17, 0x1a, 0x59, 0x08, 0x98, 0x5d, - 0x8f, 0x38, 0xf0, 0x44, 0x12, 0x80, 0x74, 0xd7, 0x0d, 0x5d, 0xdf, 0x13, 0x66, 0xb9, 0x52, 0x6c, - 0x96, 0x6b, 0x0c, 0x47, 0xc3, 0xc3, 0x68, 0x90, 0xbf, 0x6b, 0xc1, 0xf9, 0xac, 0x69, 0x38, 0x57, - 0xce, 0xe3, 0x36, 0x39, 0x35, 0xb5, 0xc4, 0x88, 0xc8, 0x5c, 0x14, 0x32, 0x85, 0x20, 0xef, 0x58, - 0x30, 0xe5, 0x18, 0x27, 0xe8, 0x39, 0xc8, 0x63, 0xd7, 0x32, 0xcf, 0xe4, 0xf5, 0xd9, 0x83, 0xfd, - 0x6a, 0xe2, 0x94, 0x8e, 0x09, 0x8e, 0xe4, 0xef, 0x5b, 0x70, 0x21, 0x73, 0x8e, 0xcf, 0x55, 0x4e, - 0xa3, 0x85, 0xf8, 0x20, 0xc9, 0x5e, 0x73, 0xb2, 0xc5, 0x20, 0xdf, 0xb0, 0xf4, 0x56, 0xa6, 0x2e, - 0x18, 0xe7, 0xa6, 0xb8, 0x68, 0x23, 0x1a, 0x3c, 0x0c, 0x35, 0x4a, 0x11, 0xae, 0x9f, 0x33, 0x76, - 0x46, 0x55, 0x88, 0x69, 0xf6, 0xe4, 0xeb, 0x96, 0xda, 0x1a, 0xb5, 0x44, 0x67, 0x4e, 0x4b, 0x22, - 0x12, 0xef, 0xb4, 0x5a, 0xa0, 0x14, 0x73, 0xf2, 0x33, 0x30, 0xef, 0x6c, 0xfa, 0x41, 0x94, 0x39, - 0xf9, 0xe6, 0xa6, 0xf9, 0x34, 0xba, 0x74, 0xb0, 0x5f, 0x9d, 0xaf, 0x0d, 0xc5, 0xc2, 0x43, 0x28, - 0xd8, 0xbf, 0x55, 0x84, 0x29, 0x71, 0x12, 0x92, 0x5b, 0xd7, 0xef, 0x5a, 0xf0, 0x64, 0xb3, 0x1f, - 0x04, 0xd4, 0x8b, 0x1a, 0x11, 0xed, 0x0d, 0x6e, 0x5c, 0xd6, 0xa9, 0x6e, 0x5c, 0x97, 0x0f, 0xf6, - 0xab, 0x4f, 0x2e, 0x1d, 0xc2, 0x1f, 0x0f, 0x95, 0x8e, 0xfc, 0x3b, 0x0b, 0x6c, 0x89, 0x50, 0x77, - 0x9a, 0x3b, 0xed, 0xc0, 0xef, 0x7b, 0xad, 0xc1, 0x8f, 0x18, 0x3b, 0xd5, 0x8f, 0x78, 0xfa, 0x60, - 0xbf, 0x6a, 0x2f, 0x1d, 0x29, 0x05, 0x1e, 0x43, 0x52, 0xf2, 0x2a, 0x9c, 0x95, 0x58, 0x57, 0xef, - 0xf7, 0x68, 0xe0, 0xb2, 0x33, 0x87, 0x54, 0x1c, 0x63, 0xdf, 0xb4, 0x34, 0x02, 0x0e, 0xd6, 0x21, - 0x21, 0x4c, 0xde, 0xa3, 0x6e, 0x7b, 0x3b, 0x52, 0xea, 0xd3, 0x88, 0x0e, 0x69, 0xd2, 0x2a, 0x72, - 0x47, 0xd0, 0xac, 0x57, 0x0e, 0xf6, 0xab, 0x93, 0xf2, 0x0f, 0x2a, 0x4e, 0xe4, 0x26, 0x4c, 0x8b, - 0x73, 0xea, 0xba, 0xeb, 0xb5, 0xd7, 0x7d, 0x4f, 0x78, 0x55, 0x95, 0xeb, 0x4f, 0xab, 0x0d, 0xbf, - 0x91, 0x80, 0xbe, 0xb7, 0x5f, 0x9d, 0x52, 0xbf, 0x37, 0xf6, 0x7a, 0x14, 0x53, 0xb5, 0xed, 0xdf, - 0x9f, 0x00, 0x50, 0xc3, 0x95, 0xf6, 0xc8, 0x87, 0xa1, 0x1c, 0xd2, 0x48, 0x70, 0x95, 0x37, 0x49, - 0xe2, 0xfe, 0x4f, 0x15, 0x62, 0x0c, 0x27, 0x3b, 0x50, 0xec, 0x39, 0xfd, 0x90, 0xe6, 0x73, 0x7e, - 0x90, 0x9d, 0xbf, 0xce, 0x28, 0x8a, 0x83, 0x29, 0xff, 0x89, 0x82, 0x07, 0xf9, 0xb2, 0x05, 0x40, - 0x93, 0x1d, 0x36, 0xb2, 0x81, 0x48, 0xb2, 0x8c, 0xfb, 0x94, 0xb5, 0x41, 0x7d, 0xfa, 0x60, 0xbf, - 0x0a, 0x46, 0xd7, 0x1b, 0x6c, 0xc9, 0x3d, 0x28, 0x39, 0x6a, 0xcd, 0x1f, 0x3f, 0x8d, 0x35, 0x9f, - 0x9f, 0x17, 0xf5, 0xa0, 0xd5, 0xcc, 0xc8, 0x57, 0x2c, 0x98, 0x0e, 0x69, 0x24, 0xbb, 0x8a, 0xad, - 0x3c, 0x52, 0xe1, 0x1d, 0x71, 0xd0, 0x35, 0x12, 0x34, 0xc5, 0x0a, 0x9a, 0x2c, 0xc3, 0x14, 0x5f, - 0x25, 0xca, 0x75, 0xea, 0xb4, 0x68, 0xc0, 0xcd, 0x11, 0x52, 0x93, 0x1a, 0x5d, 0x14, 0x83, 0xa6, - 0x16, 0xc5, 0x28, 0xc3, 0x14, 0x5f, 0x25, 0xca, 0x9a, 0x1b, 0x04, 0xbe, 0x14, 0xa5, 0x94, 0x93, - 0x28, 0x06, 0x4d, 0x2d, 0x8a, 0x51, 0x86, 0x29, 0xbe, 0xf6, 0xb7, 0xce, 0xc0, 0xb4, 0x9a, 0x48, - 0xb1, 0x66, 0x2f, 0xac, 0x5f, 0x43, 0x34, 0xfb, 0x25, 0x13, 0x88, 0x49, 0x5c, 0x56, 0x59, 0x4c, - 0xd5, 0xa4, 0x62, 0xaf, 0x2b, 0x37, 0x4c, 0x20, 0x26, 0x71, 0x49, 0x17, 0x8a, 0x61, 0x44, 0x7b, - 0xca, 0xe7, 0x60, 0xc4, 0x2b, 0xf1, 0x78, 0x7d, 0x30, 0x2c, 0x09, 0x8c, 0x3c, 0x0a, 0x2e, 0xdc, - 0x80, 0x1b, 0x25, 0x6c, 0xba, 0x72, 0x72, 0xe4, 0x33, 0x3f, 0x93, 0xe6, 0x62, 0xd1, 0x1b, 0xc9, - 0x32, 0x4c, 0xb1, 0xcf, 0x50, 0xf6, 0x8b, 0xa7, 0xa8, 0xec, 0x7f, 0x0a, 0x4a, 0x5d, 0xe7, 0x7e, - 0xa3, 0x1f, 0xb4, 0x1f, 0xfc, 0x50, 0x21, 0x7d, 0x48, 0x05, 0x15, 0xd4, 0xf4, 0xc8, 0x17, 0x2d, - 0x63, 0xc9, 0x11, 0x0e, 0x06, 0x77, 0xf2, 0x5d, 0x72, 0xf4, 0x5e, 0x39, 0x74, 0xf1, 0x19, 0x50, - 0xbd, 0x4b, 0x0f, 0x5d, 0xf5, 0x66, 0x6a, 0xa4, 0x98, 0x20, 0x5a, 0x8d, 0x2c, 0x9f, 0xaa, 0x1a, - 0xb9, 0x94, 0x60, 0x86, 0x29, 0xe6, 0x5c, 0x1e, 0x31, 0xe7, 0xb4, 0x3c, 0x70, 0xaa, 0xf2, 0x34, - 0x12, 0xcc, 0x30, 0xc5, 0x7c, 0xf8, 0x79, 0xb3, 0x72, 0x3a, 0xe7, 0xcd, 0xa9, 0x1c, 0xce, 0x9b, - 0x87, 0xab, 0xe2, 0x67, 0x46, 0x55, 0xc5, 0xc9, 0x0d, 0x20, 0xad, 0x3d, 0xcf, 0xe9, 0xba, 0x4d, - 0xb9, 0x58, 0xf2, 0x6d, 0x73, 0x9a, 0xdb, 0x23, 0xe6, 0xe5, 0x42, 0x46, 0x96, 0x07, 0x30, 0x30, - 0xa3, 0x16, 0x89, 0xa0, 0xd4, 0x53, 0x1a, 0xd7, 0x4c, 0x1e, 0xa3, 0x5f, 0x69, 0x60, 0xc2, 0x6f, - 0x84, 0x4d, 0x3c, 0x55, 0x82, 0x9a, 0x13, 0x59, 0x85, 0xf3, 0x5d, 0xd7, 0x5b, 0xf7, 0x5b, 0xe1, - 0x3a, 0x0d, 0xa4, 0xb5, 0xa5, 0x41, 0xa3, 0xb9, 0x59, 0xde, 0x36, 0xfc, 0x04, 0xbd, 0x96, 0x01, - 0xc7, 0xcc, 0x5a, 0xf6, 0xff, 0xb2, 0x60, 0x76, 0xa9, 0xe3, 0xf7, 0x5b, 0x77, 0x9c, 0xa8, 0xb9, - 0x2d, 0xdc, 0x14, 0xc8, 0x2b, 0x50, 0x72, 0xbd, 0x88, 0x06, 0xbb, 0x4e, 0x47, 0xee, 0x4f, 0xb6, - 0x32, 0x9f, 0xae, 0xc8, 0xf2, 0xf7, 0xf6, 0xab, 0xd3, 0xcb, 0xfd, 0x80, 0x5b, 0xa9, 0xc5, 0x6a, - 0x85, 0xba, 0x0e, 0xf9, 0x96, 0x05, 0x67, 0x85, 0xa3, 0xc3, 0xb2, 0x13, 0x39, 0xaf, 0xf7, 0x69, - 0xe0, 0x52, 0xe5, 0xea, 0x30, 0xe2, 0x42, 0x95, 0x96, 0x55, 0x31, 0xd8, 0x8b, 0x15, 0xf5, 0xb5, - 0x34, 0x67, 0x1c, 0x14, 0xc6, 0xfe, 0x95, 0x02, 0x3c, 0x3e, 0x94, 0x16, 0x99, 0x87, 0x31, 0xb7, - 0x25, 0x3f, 0x1d, 0x24, 0xdd, 0xb1, 0x95, 0x16, 0x8e, 0xb9, 0x2d, 0xb2, 0xc0, 0x75, 0xce, 0x80, - 0x86, 0xa1, 0xba, 0x70, 0x2e, 0x6b, 0xf5, 0x50, 0x96, 0xa2, 0x81, 0x41, 0xaa, 0x50, 0xe4, 0xfe, - 0xc3, 0xf2, 0x3c, 0xc1, 0xb5, 0x58, 0xee, 0xaa, 0x8b, 0xa2, 0x9c, 0x7c, 0xc9, 0x02, 0x10, 0x02, - 0xb2, 0xd3, 0x88, 0xdc, 0x25, 0x31, 0xdf, 0x66, 0x62, 0x94, 0x85, 0x94, 0xf1, 0x7f, 0x34, 0xb8, - 0x92, 0x0d, 0x98, 0x60, 0x0a, 0xad, 0xdf, 0x7a, 0xe0, 0x4d, 0x91, 0xdf, 0x44, 0xad, 0x73, 0x1a, - 0x28, 0x69, 0xb1, 0xb6, 0x0a, 0x68, 0xd4, 0x0f, 0x3c, 0xd6, 0xb4, 0x7c, 0x1b, 0x2c, 0x09, 0x29, - 0x50, 0x97, 0xa2, 0x81, 0x61, 0xff, 0xf3, 0x31, 0x38, 0x9f, 0x25, 0x3a, 0xdb, 0x6d, 0x26, 0x84, - 0xb4, 0xf2, 0x68, 0xfc, 0x53, 0xf9, 0xb7, 0x8f, 0xf4, 0xd9, 0xd1, 0xd7, 0x14, 0xd2, 0x81, 0x52, - 0xf2, 0x25, 0x3f, 0xa5, 0x5b, 0x68, 0xec, 0x01, 0x5b, 0x48, 0x53, 0x4e, 0xb5, 0xd2, 0x65, 0x18, - 0x0f, 0x59, 0xcf, 0x17, 0x92, 0xd7, 0x1d, 0xbc, 0x8f, 0x38, 0x84, 0x61, 0xf4, 0x3d, 0x37, 0x92, - 0x41, 0x37, 0x1a, 0xe3, 0xb6, 0xe7, 0x46, 0xc8, 0x21, 0xf6, 0x37, 0xc7, 0x60, 0x7e, 0xf8, 0x47, - 0x91, 0x6f, 0x5a, 0x00, 0x2d, 0x76, 0x5c, 0x09, 0xb9, 0xe7, 0xba, 0xf0, 0x71, 0x72, 0x4e, 0xab, - 0x0d, 0x97, 0x15, 0xa7, 0xd8, 0xf9, 0x4e, 0x17, 0x85, 0x68, 0x08, 0x42, 0xae, 0xa8, 0xa1, 0xcf, - 0xaf, 0x6a, 0xc4, 0x64, 0xd2, 0x75, 0xd6, 0x34, 0x04, 0x0d, 0x2c, 0x76, 0x1e, 0xf5, 0x9c, 0x2e, - 0x0d, 0x7b, 0x8e, 0x0e, 0x61, 0xe2, 0xe7, 0xd1, 0x9b, 0xaa, 0x10, 0x63, 0xb8, 0xdd, 0x81, 0xa7, - 0x8e, 0x21, 0x67, 0x4e, 0x11, 0x22, 0xf6, 0xff, 0xb0, 0xe0, 0x31, 0xe9, 0x7e, 0xf6, 0xff, 0x8d, - 0x2f, 0xe3, 0x9f, 0x5b, 0xf0, 0xc4, 0x90, 0x6f, 0x7e, 0x08, 0x2e, 0x8d, 0x6f, 0x25, 0x5d, 0x1a, - 0x6f, 0x8f, 0x3a, 0xa4, 0x33, 0xbf, 0x63, 0x88, 0x67, 0xe3, 0x1f, 0x14, 0xe0, 0x0c, 0x5b, 0xb6, - 0x5a, 0x7e, 0x3b, 0xa7, 0x8d, 0xf3, 0x29, 0x28, 0x7e, 0x8e, 0x6d, 0x40, 0xe9, 0x41, 0xc6, 0x77, - 0x25, 0x14, 0x30, 0xf2, 0x65, 0x0b, 0x26, 0x3f, 0x27, 0xf7, 0x54, 0x71, 0x96, 0x1b, 0x71, 0x31, - 0x4c, 0x7c, 0xc3, 0x82, 0xdc, 0x21, 0x45, 0xe0, 0x89, 0x76, 0x60, 0x54, 0x5b, 0xa9, 0xe2, 0x4c, - 0x9e, 0x85, 0xc9, 0x2d, 0x3f, 0xe8, 0xf6, 0x3b, 0x4e, 0x3a, 0xda, 0xf1, 0x9a, 0x28, 0x46, 0x05, - 0x67, 0x93, 0xdc, 0xe9, 0xb9, 0x6f, 0xd0, 0x20, 0x14, 0x71, 0x08, 0x89, 0x49, 0x5e, 0xd3, 0x10, - 0x34, 0xb0, 0x78, 0x9d, 0x76, 0x3b, 0xa0, 0x6d, 0x27, 0xf2, 0x03, 0xbe, 0x73, 0x98, 0x75, 0x34, - 0x04, 0x0d, 0xac, 0xf9, 0x4f, 0xc0, 0x94, 0x29, 0xfc, 0x89, 0x82, 0x58, 0x3e, 0x09, 0xd2, 0x93, - 0x31, 0xb5, 0x24, 0x59, 0xc7, 0x59, 0x92, 0xec, 0xff, 0x30, 0x06, 0x86, 0x75, 0xe8, 0x21, 0x4c, - 0x75, 0x2f, 0x31, 0xd5, 0x47, 0xb4, 0x6c, 0x18, 0xb6, 0xae, 0x61, 0x21, 0x7d, 0xbb, 0xa9, 0x90, - 0xbe, 0x9b, 0xb9, 0x71, 0x3c, 0x3c, 0xa2, 0xef, 0xfb, 0x16, 0x3c, 0x11, 0x23, 0x0f, 0x1a, 0x6e, - 0x8f, 0x5e, 0xb7, 0x5f, 0x84, 0x8a, 0x13, 0x57, 0x93, 0x13, 0xcb, 0x88, 0xa7, 0xd2, 0x20, 0x34, - 0xf1, 0xe2, 0x58, 0x90, 0xc2, 0x03, 0xc6, 0x82, 0x8c, 0x1f, 0x1e, 0x0b, 0x62, 0xff, 0x68, 0x0c, - 0x2e, 0x0e, 0x7e, 0x99, 0xe9, 0x20, 0x7d, 0xf4, 0xb7, 0xa5, 0x5d, 0xa8, 0xc7, 0x1e, 0xd8, 0x85, - 0xba, 0x70, 0x5c, 0x17, 0x6a, 0xed, 0xb8, 0x3c, 0x7e, 0xea, 0x8e, 0xcb, 0x0d, 0xb8, 0xa0, 0xbc, - 0x24, 0xaf, 0xf9, 0x81, 0x0c, 0x88, 0x50, 0x2b, 0x48, 0xa9, 0x7e, 0x51, 0x56, 0xb9, 0x80, 0x59, - 0x48, 0x98, 0x5d, 0xd7, 0xfe, 0x7e, 0x01, 0xce, 0xc5, 0xcd, 0xbe, 0xe4, 0x7b, 0x2d, 0x97, 0x3b, - 0xda, 0xbc, 0x0c, 0xe3, 0xd1, 0x5e, 0x4f, 0x35, 0xf6, 0x5f, 0x56, 0xe2, 0x6c, 0xec, 0xf5, 0x58, - 0x6f, 0x3f, 0x96, 0x51, 0x85, 0x9b, 0xce, 0x79, 0x25, 0xb2, 0xaa, 0x67, 0x87, 0xe8, 0x81, 0x17, - 0x92, 0xa3, 0xf9, 0xbd, 0xfd, 0x6a, 0x46, 0x6a, 0x83, 0x05, 0x4d, 0x29, 0x39, 0xe6, 0xc9, 0x5d, - 0x98, 0xee, 0x38, 0x61, 0x74, 0xbb, 0xd7, 0x72, 0x22, 0xba, 0xe1, 0x4a, 0x17, 0x96, 0x93, 0xc5, - 0x90, 0xe8, 0xbb, 0xfe, 0xd5, 0x04, 0x25, 0x4c, 0x51, 0x26, 0xbb, 0x40, 0x58, 0xc9, 0x46, 0xe0, - 0x78, 0xa1, 0xf8, 0x2a, 0xc6, 0xef, 0xe4, 0x01, 0x41, 0xfa, 0xe8, 0xbc, 0x3a, 0x40, 0x0d, 0x33, - 0x38, 0x90, 0xa7, 0x61, 0x22, 0xa0, 0x4e, 0xa8, 0xb7, 0x03, 0x3d, 0xff, 0x91, 0x97, 0xa2, 0x84, - 0x9a, 0x13, 0x6a, 0xe2, 0x88, 0x09, 0xf5, 0xc7, 0x16, 0x4c, 0xc7, 0xdd, 0xf4, 0x10, 0x54, 0x8f, - 0x6e, 0x52, 0xf5, 0xb8, 0x9e, 0xd7, 0x92, 0x38, 0x44, 0xdb, 0xf8, 0xb3, 0x49, 0xf3, 0xfb, 0x78, - 0xd4, 0xc2, 0xe7, 0x4d, 0x27, 0x76, 0x2b, 0x8f, 0x50, 0xb2, 0x84, 0xb6, 0x77, 0xa8, 0xf7, 0x3a, - 0xd3, 0x75, 0x5a, 0x52, 0x8f, 0x91, 0xc3, 0x5e, 0xeb, 0x3a, 0x4a, 0xbf, 0xc9, 0xd2, 0x75, 0x54, - 0x1d, 0x72, 0x1b, 0x1e, 0xeb, 0x05, 0x3e, 0x0f, 0xae, 0x5f, 0xa6, 0x4e, 0xab, 0xe3, 0x7a, 0x54, - 0x99, 0x79, 0x84, 0xab, 0xc9, 0x13, 0x07, 0xfb, 0xd5, 0xc7, 0xd6, 0xb3, 0x51, 0x70, 0x58, 0xdd, - 0x64, 0x78, 0xe6, 0xf8, 0x31, 0xc2, 0x33, 0x7f, 0x51, 0x1b, 0x53, 0x75, 0x24, 0xc0, 0xa7, 0xf3, - 0xea, 0xca, 0xac, 0x98, 0x00, 0x3d, 0xa4, 0x6a, 0x92, 0x29, 0x6a, 0xf6, 0xc3, 0x2d, 0x76, 0x13, - 0x0f, 0x68, 0xb1, 0x8b, 0x83, 0x3f, 0x26, 0xdf, 0xcf, 0xe0, 0x8f, 0xd2, 0x07, 0x2a, 0xf8, 0xe3, - 0x5b, 0x16, 0x9c, 0x73, 0x06, 0xc3, 0xae, 0xf3, 0x31, 0x1e, 0x67, 0xc4, 0x73, 0xd7, 0x9f, 0x90, - 0x42, 0x66, 0x45, 0xb7, 0x63, 0x96, 0x28, 0xf6, 0xbb, 0x45, 0x98, 0x4d, 0x2b, 0x49, 0xa7, 0x1f, - 0x9f, 0xfa, 0xcb, 0x16, 0xcc, 0xaa, 0x09, 0x2e, 0x78, 0xea, 0x23, 0xc6, 0x6a, 0x4e, 0xeb, 0x8a, - 0x50, 0xf7, 0x74, 0xda, 0x90, 0x8d, 0x14, 0x37, 0x1c, 0xe0, 0x4f, 0xde, 0x84, 0x8a, 0xbe, 0x55, - 0x79, 0xa0, 0x60, 0x55, 0x1e, 0x4f, 0x59, 0x8b, 0x49, 0xa0, 0x49, 0x8f, 0xbc, 0x6b, 0x01, 0x34, - 0xd5, 0x4e, 0x9c, 0x53, 0x28, 0x50, 0x86, 0xb6, 0x10, 0xeb, 0xf3, 0xba, 0x28, 0x44, 0x83, 0x31, - 0xf9, 0x15, 0x7e, 0x9f, 0xa2, 0x47, 0x82, 0x48, 0x47, 0x32, 0xb2, 0xdb, 0xfb, 0x21, 0xba, 0x73, - 0xac, 0xed, 0x19, 0xa0, 0x10, 0x13, 0x42, 0xd8, 0x2f, 0x83, 0x76, 0x54, 0x66, 0x2b, 0x2b, 0x77, - 0x55, 0x5e, 0x77, 0xa2, 0x6d, 0x39, 0x04, 0xf5, 0xca, 0x7a, 0x4d, 0x01, 0x30, 0xc6, 0xb1, 0x3f, - 0x0b, 0xd3, 0xaf, 0x06, 0x4e, 0x6f, 0xdb, 0xe5, 0xf7, 0x16, 0xec, 0x7c, 0xfc, 0x2c, 0x4c, 0x3a, - 0xad, 0x56, 0x56, 0x86, 0x9b, 0x9a, 0x28, 0x46, 0x05, 0x3f, 0xd6, 0x51, 0xd8, 0xfe, 0x7d, 0x0b, - 0x48, 0x7c, 0xf7, 0xeb, 0x7a, 0xed, 0x35, 0x27, 0x6a, 0x6e, 0xb3, 0x23, 0xdc, 0x36, 0x2f, 0xcd, - 0x3a, 0xc2, 0x5d, 0xd7, 0x10, 0x34, 0xb0, 0xc8, 0xdb, 0x50, 0x11, 0xff, 0xde, 0xd0, 0x07, 0xc4, - 0xd1, 0xfd, 0xad, 0xf9, 0x9e, 0xc7, 0x65, 0x12, 0xa3, 0xf0, 0x7a, 0xcc, 0x01, 0x4d, 0x76, 0xac, - 0xa9, 0x56, 0xbc, 0xad, 0x4e, 0xff, 0x7e, 0x6b, 0x33, 0x6e, 0xaa, 0x5e, 0xe0, 0x6f, 0xb9, 0x1d, - 0x9a, 0x6e, 0xaa, 0x75, 0x51, 0x8c, 0x0a, 0x7e, 0xbc, 0xa6, 0xfa, 0x37, 0x16, 0x9c, 0x5f, 0x09, - 0x23, 0xd7, 0x5f, 0xa6, 0x61, 0xc4, 0x76, 0x3e, 0xb6, 0x3e, 0xf6, 0x3b, 0xc7, 0x89, 0x39, 0x58, - 0x86, 0x59, 0x79, 0x0f, 0xdd, 0xdf, 0x0c, 0x69, 0x64, 0x1c, 0x35, 0xf4, 0x3c, 0x5e, 0x4a, 0xc1, - 0x71, 0xa0, 0x06, 0xa3, 0x22, 0x2f, 0xa4, 0x63, 0x2a, 0x85, 0x24, 0x95, 0x46, 0x0a, 0x8e, 0x03, - 0x35, 0xec, 0xef, 0x15, 0xe0, 0x1c, 0xff, 0x8c, 0x54, 0xbc, 0xd0, 0xd7, 0x87, 0xc5, 0x0b, 0x8d, - 0x38, 0x95, 0x39, 0xaf, 0x07, 0x88, 0x16, 0xfa, 0x5b, 0x16, 0xcc, 0xb4, 0x92, 0x2d, 0x9d, 0x8f, - 0x5d, 0x2e, 0xab, 0x0f, 0x85, 0xdb, 0x5d, 0xaa, 0x10, 0xd3, 0xfc, 0xc9, 0xaf, 0x5a, 0x30, 0x93, - 0x14, 0x53, 0xad, 0xee, 0xa7, 0xd0, 0x48, 0xda, 0x4f, 0x3e, 0x59, 0x1e, 0x62, 0x5a, 0x04, 0xfb, - 0xbb, 0x63, 0xb2, 0x4b, 0x4f, 0x23, 0x18, 0x86, 0xdc, 0x83, 0x72, 0xd4, 0x09, 0x45, 0xa1, 0xfc, - 0xda, 0x11, 0x0f, 0xad, 0x1b, 0xab, 0x0d, 0xe1, 0x02, 0x12, 0xeb, 0x95, 0xb2, 0x84, 0xe9, 0xc7, - 0x8a, 0x17, 0x67, 0xdc, 0xec, 0x49, 0xc6, 0xb9, 0x9c, 0x96, 0x37, 0x96, 0xd6, 0xd3, 0x8c, 0x65, - 0x09, 0x63, 0xac, 0x78, 0xd9, 0xbf, 0x69, 0x41, 0xf9, 0x86, 0xaf, 0xd6, 0x91, 0x9f, 0xc9, 0xc1, - 0x16, 0xa5, 0x55, 0x56, 0xad, 0xb4, 0xc4, 0xa7, 0xa0, 0x57, 0x12, 0x96, 0xa8, 0x27, 0x0d, 0xda, - 0x0b, 0x3c, 0xd1, 0x1f, 0x23, 0x75, 0xc3, 0xdf, 0x1c, 0x6a, 0x3e, 0xfe, 0xb5, 0x22, 0x9c, 0x79, - 0xcd, 0xd9, 0xa3, 0x5e, 0xe4, 0x9c, 0x7c, 0x93, 0x78, 0x11, 0x2a, 0x4e, 0x8f, 0xdf, 0x65, 0x1a, - 0xc7, 0x90, 0xd8, 0xb8, 0x13, 0x83, 0xd0, 0xc4, 0x8b, 0x17, 0x34, 0x11, 0x99, 0x92, 0xb5, 0x14, - 0x2d, 0xa5, 0xe0, 0x38, 0x50, 0x83, 0xdc, 0x00, 0x22, 0xa3, 0xb9, 0x6b, 0xcd, 0xa6, 0xdf, 0xf7, - 0xc4, 0x92, 0x26, 0xec, 0x3e, 0xfa, 0x3c, 0xbc, 0x36, 0x80, 0x81, 0x19, 0xb5, 0xc8, 0x67, 0x60, - 0xae, 0xc9, 0x29, 0xcb, 0xd3, 0x91, 0x49, 0x51, 0x9c, 0x90, 0x75, 0xac, 0xc7, 0xd2, 0x10, 0x3c, - 0x1c, 0x4a, 0x81, 0x49, 0x1a, 0x46, 0x7e, 0xe0, 0xb4, 0xa9, 0x49, 0x77, 0x22, 0x29, 0x69, 0x63, - 0x00, 0x03, 0x33, 0x6a, 0x91, 0x2f, 0x40, 0x39, 0xda, 0x0e, 0x68, 0xb8, 0xed, 0x77, 0x5a, 0xd2, - 0xf7, 0x64, 0x44, 0x63, 0xa0, 0xec, 0xfd, 0x0d, 0x45, 0xd5, 0x18, 0xde, 0xaa, 0x08, 0x63, 0x9e, - 0x24, 0x80, 0x89, 0xb0, 0xe9, 0xf7, 0x68, 0x28, 0x4f, 0x15, 0x37, 0x72, 0xe1, 0xce, 0x8d, 0x5b, - 0x86, 0x19, 0x92, 0x73, 0x40, 0xc9, 0xc9, 0xfe, 0xbd, 0x31, 0x98, 0x32, 0x11, 0x8f, 0xb1, 0x36, - 0x7d, 0xd9, 0x82, 0xa9, 0xa6, 0xef, 0x45, 0x81, 0xdf, 0x89, 0xb3, 0x14, 0x8c, 0xae, 0x51, 0x30, - 0x52, 0xcb, 0x34, 0x72, 0xdc, 0x8e, 0x61, 0xad, 0x33, 0xd8, 0x60, 0x82, 0x29, 0xf9, 0x9a, 0x05, - 0x33, 0xb1, 0xab, 0x62, 0x6c, 0xeb, 0xcb, 0x55, 0x10, 0xbd, 0xd4, 0x5f, 0x4d, 0x72, 0xc2, 0x34, - 0x6b, 0x7b, 0x13, 0x66, 0xd3, 0xbd, 0xcd, 0x9a, 0xb2, 0xe7, 0xc8, 0xb9, 0x5e, 0x88, 0x9b, 0x72, - 0xdd, 0x09, 0x43, 0xe4, 0x10, 0xf2, 0x1c, 0x94, 0xba, 0x4e, 0xd0, 0x76, 0x3d, 0xa7, 0xc3, 0x5b, - 0xb1, 0x60, 0x2c, 0x48, 0xb2, 0x1c, 0x35, 0x86, 0xfd, 0x51, 0x98, 0x5a, 0x73, 0xbc, 0x36, 0x6d, - 0xc9, 0x75, 0xf8, 0xe8, 0x70, 0xcc, 0x3f, 0x1d, 0x87, 0x8a, 0x71, 0x7c, 0x3c, 0xfd, 0x73, 0x56, - 0x22, 0xfb, 0x4e, 0x21, 0xc7, 0xec, 0x3b, 0x9f, 0x02, 0xd8, 0x72, 0x3d, 0x37, 0xdc, 0x7e, 0xc0, - 0xbc, 0x3e, 0xfc, 0x6e, 0xfe, 0x9a, 0xa6, 0x80, 0x06, 0xb5, 0xf8, 0x02, 0xb4, 0x78, 0x48, 0x8a, - 0xbc, 0x77, 0x2d, 0x63, 0xbb, 0x99, 0xc8, 0xc3, 0xe1, 0xc3, 0xe8, 0x98, 0x05, 0xb5, 0xfd, 0x88, - 0xbb, 0xa9, 0xc3, 0x76, 0xa5, 0x0d, 0x28, 0x05, 0x34, 0xec, 0x77, 0xe9, 0x03, 0x65, 0xe0, 0xe1, - 0xae, 0x37, 0x28, 0xeb, 0xa3, 0xa6, 0x34, 0xff, 0x32, 0x9c, 0x49, 0x88, 0x70, 0xa2, 0x1b, 0x26, - 0x1f, 0x32, 0x6d, 0x14, 0x0f, 0x72, 0xdf, 0xc4, 0xfa, 0xa2, 0x63, 0x64, 0xde, 0xd1, 0x7d, 0x21, - 0x1c, 0xac, 0x04, 0xcc, 0xfe, 0xd1, 0x04, 0x48, 0x1f, 0x86, 0x63, 0x2c, 0x57, 0xe6, 0xcd, 0xe5, - 0xd8, 0x03, 0xdc, 0x5c, 0xde, 0x80, 0x29, 0xd7, 0x73, 0x23, 0xd7, 0xe9, 0x70, 0xfb, 0x93, 0xdc, - 0x4e, 0x95, 0x07, 0xfa, 0xd4, 0x8a, 0x01, 0xcb, 0xa0, 0x93, 0xa8, 0x4b, 0x5e, 0x87, 0x22, 0xdf, - 0x6f, 0xe4, 0x00, 0x3e, 0xb9, 0xa3, 0x05, 0xf7, 0xb1, 0x11, 0x61, 0x69, 0x82, 0x12, 0x3f, 0x7c, - 0x88, 0xd4, 0x43, 0xfa, 0xf8, 0x2d, 0xc7, 0x71, 0x7c, 0xf8, 0x48, 0xc1, 0x71, 0xa0, 0x06, 0xa3, - 0xb2, 0xe5, 0xb8, 0x9d, 0x7e, 0x40, 0x63, 0x2a, 0x13, 0x49, 0x2a, 0xd7, 0x52, 0x70, 0x1c, 0xa8, - 0x41, 0xb6, 0x60, 0x4a, 0x96, 0x09, 0xb7, 0xb9, 0xc9, 0x07, 0xfc, 0x4a, 0xee, 0x1e, 0x79, 0xcd, - 0xa0, 0x84, 0x09, 0xba, 0xa4, 0x0f, 0x67, 0x5d, 0xaf, 0xe9, 0x7b, 0xcd, 0x4e, 0x3f, 0x74, 0x77, - 0x69, 0x1c, 0x13, 0xf6, 0x20, 0xcc, 0x2e, 0x1c, 0xec, 0x57, 0xcf, 0xae, 0xa4, 0xc9, 0xe1, 0x20, - 0x07, 0xf2, 0x45, 0x0b, 0x2e, 0x34, 0x7d, 0x2f, 0xe4, 0xa9, 0x2b, 0x76, 0xe9, 0xd5, 0x20, 0xf0, - 0x03, 0xc1, 0xbb, 0xfc, 0x80, 0xbc, 0xb9, 0xd9, 0x73, 0x29, 0x8b, 0x24, 0x66, 0x73, 0x22, 0x6f, - 0x41, 0xa9, 0x17, 0xf8, 0xbb, 0x6e, 0x8b, 0x06, 0xd2, 0x05, 0x73, 0x35, 0x8f, 0x7c, 0x3e, 0xeb, - 0x92, 0x66, 0xbc, 0xf4, 0xa8, 0x12, 0xd4, 0xfc, 0xec, 0xff, 0x53, 0x81, 0xe9, 0x24, 0x3a, 0xf9, - 0x39, 0x80, 0x5e, 0xe0, 0x77, 0x69, 0xb4, 0x4d, 0x75, 0x6c, 0xcf, 0xcd, 0x51, 0x33, 0xb6, 0x28, - 0x7a, 0xca, 0x6d, 0x89, 0x2d, 0x17, 0x71, 0x29, 0x1a, 0x1c, 0x49, 0x00, 0x93, 0x3b, 0x62, 0xdb, - 0x95, 0x5a, 0xc8, 0x6b, 0xb9, 0xe8, 0x4c, 0x92, 0x33, 0x0f, 0x4a, 0x91, 0x45, 0xa8, 0x18, 0x91, - 0x4d, 0x28, 0xdc, 0xa3, 0x9b, 0xf9, 0xa4, 0x0b, 0xb8, 0x43, 0xe5, 0x69, 0xa6, 0x3e, 0x79, 0xb0, - 0x5f, 0x2d, 0xdc, 0xa1, 0x9b, 0xc8, 0x88, 0xb3, 0xef, 0x6a, 0x09, 0xdf, 0x05, 0xb9, 0x54, 0xbc, - 0x96, 0xa3, 0x23, 0x84, 0xf8, 0x2e, 0x59, 0x84, 0x8a, 0x11, 0x79, 0x0b, 0xca, 0xf7, 0x9c, 0x5d, - 0xba, 0x15, 0xf8, 0x5e, 0x24, 0x7d, 0xe5, 0x46, 0x0c, 0xf7, 0xb8, 0xa3, 0xc8, 0x49, 0xbe, 0x7c, - 0x7b, 0xd7, 0x85, 0x18, 0xb3, 0x23, 0xbb, 0x50, 0xf2, 0xe8, 0x3d, 0xa4, 0x1d, 0xb7, 0x99, 0x4f, - 0x78, 0xc5, 0x4d, 0x49, 0x4d, 0x72, 0xe6, 0xfb, 0x9e, 0x2a, 0x43, 0xcd, 0x8b, 0xf5, 0xe5, 0x5d, - 0x7f, 0x53, 0x2e, 0x54, 0x23, 0xf6, 0xa5, 0x3e, 0x99, 0x8a, 0xbe, 0xbc, 0xe1, 0x6f, 0x22, 0x23, - 0xce, 0xe6, 0x48, 0x53, 0x3b, 0x6a, 0xc9, 0x65, 0xea, 0x66, 0xbe, 0x0e, 0x6a, 0x62, 0x8e, 0xc4, - 0xa5, 0x68, 0x70, 0x64, 0x6d, 0xdb, 0x96, 0xc6, 0x4a, 0xb9, 0x50, 0x8d, 0xd8, 0xb6, 0x49, 0xd3, - 0xa7, 0x68, 0x5b, 0x55, 0x86, 0x9a, 0x17, 0xe3, 0xeb, 0x4a, 0xcb, 0x5f, 0x3e, 0x4b, 0x55, 0xd2, - 0x8e, 0x28, 0xf8, 0xaa, 0x32, 0xd4, 0xbc, 0x58, 0x7b, 0x87, 0x3b, 0x7b, 0xf7, 0x9c, 0xce, 0x8e, - 0xeb, 0xb5, 0x65, 0xac, 0xea, 0xa8, 0x69, 0xb8, 0x77, 0xf6, 0xee, 0x08, 0x7a, 0x66, 0x7b, 0xc7, - 0xa5, 0x68, 0x70, 0x24, 0x7f, 0xcf, 0x82, 0x89, 0x5e, 0xa7, 0xdf, 0x76, 0xbd, 0xb9, 0xa9, 0x3c, - 0x9c, 0x98, 0x92, 0x4b, 0xee, 0xc2, 0x3a, 0x27, 0x2d, 0x14, 0xc5, 0x9f, 0xd4, 0x7e, 0x97, 0xbc, - 0xf0, 0xab, 0x7f, 0x52, 0x9d, 0xa3, 0x5e, 0xd3, 0x6f, 0xb9, 0x5e, 0x7b, 0xf1, 0x6e, 0xe8, 0x7b, - 0x0b, 0xe8, 0xdc, 0x53, 0x3a, 0xba, 0x94, 0x69, 0xfe, 0xe3, 0x50, 0x31, 0x48, 0x1c, 0xa5, 0xe8, - 0x4d, 0x99, 0x8a, 0xde, 0x6f, 0x4e, 0xc0, 0x94, 0x99, 0x7c, 0xf3, 0x18, 0xda, 0x97, 0x3e, 0x71, - 0x8c, 0x9d, 0xe4, 0xc4, 0xc1, 0x8e, 0x98, 0xc6, 0x05, 0x97, 0x32, 0x6f, 0xad, 0xe4, 0xa6, 0x70, - 0xc7, 0x47, 0x4c, 0xa3, 0x30, 0xc4, 0x04, 0xd3, 0x13, 0xf8, 0xbc, 0x30, 0xb5, 0x55, 0x28, 0x76, - 0xc5, 0xa4, 0xda, 0x9a, 0x50, 0xd5, 0xae, 0x00, 0xc4, 0x59, 0x22, 0xe5, 0xc5, 0xa7, 0xd6, 0x87, - 0x8d, 0xec, 0x95, 0x06, 0x16, 0x79, 0x1a, 0x26, 0x98, 0xea, 0x43, 0x5b, 0x32, 0x94, 0x5e, 0x9f, - 0xe3, 0xaf, 0xf1, 0x52, 0x94, 0x50, 0xf2, 0x12, 0xd3, 0x52, 0x63, 0x85, 0x45, 0x46, 0xc8, 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, 0x71, 0xa0, 0x06, 0xfb, 0x18, 0x79, 0x67, 0x5b, 0x11, 0x0e, 0xd3, 0x43, 0x6e, 0x5b, 0x7f, - 0xde, 0x3c, 0x6b, 0xe5, 0x38, 0x87, 0xc4, 0xa8, 0x3d, 0xfe, 0x61, 0x6b, 0xb4, 0x63, 0xd1, 0x67, - 0x61, 0x3a, 0xb9, 0x0b, 0xe5, 0x7e, 0xf3, 0xf1, 0x0f, 0x27, 0xe0, 0xdc, 0xcd, 0xb6, 0xeb, 0xa5, - 0x13, 0x9d, 0x65, 0xbd, 0x6a, 0x60, 0x9d, 0xf8, 0x55, 0x03, 0x1d, 0x93, 0x27, 0xdf, 0x0c, 0xc8, - 0x8e, 0xc9, 0x53, 0x0f, 0x38, 0x24, 0x71, 0xc9, 0x1f, 0x5b, 0xf0, 0xa4, 0xd3, 0x12, 0xe7, 0x02, - 0xa7, 0x23, 0x4b, 0x8d, 0x64, 0xdc, 0x72, 0x46, 0x87, 0x23, 0xee, 0xf2, 0x83, 0x1f, 0xbf, 0x50, - 0x3b, 0x84, 0xab, 0xe8, 0xf1, 0x9f, 0x90, 0x5f, 0xf0, 0xe4, 0x61, 0xa8, 0x78, 0xa8, 0xf8, 0xe4, - 0xaf, 0xc1, 0x4c, 0xe2, 0x83, 0xa5, 0x25, 0xbc, 0x2c, 0x2e, 0x2c, 0x1a, 0x49, 0x10, 0xa6, 0x71, - 0xc9, 0x77, 0x2d, 0x98, 0x13, 0x66, 0xd7, 0x8c, 0xa6, 0x11, 0x37, 0xb5, 0x7e, 0xfe, 0x4d, 0xb3, - 0x34, 0x84, 0xa3, 0x68, 0x96, 0xd8, 0x0e, 0x3b, 0x04, 0x0d, 0x87, 0x8a, 0x3c, 0x7f, 0x0b, 0x3e, - 0x74, 0x64, 0xbb, 0x9f, 0x28, 0x75, 0xfb, 0x6b, 0x70, 0xf1, 0x50, 0x69, 0x4f, 0x34, 0x13, 0xbf, - 0x63, 0xc1, 0x94, 0x99, 0xb0, 0x89, 0x3c, 0x07, 0xa5, 0xc8, 0xdf, 0xa1, 0xde, 0xed, 0x40, 0x79, - 0x33, 0xeb, 0x55, 0x60, 0x83, 0x97, 0xe3, 0x2a, 0x6a, 0x0c, 0x86, 0xdd, 0xec, 0xb8, 0xd4, 0x8b, - 0x56, 0x5a, 0x72, 0x0e, 0x68, 0xec, 0x25, 0x51, 0xbe, 0x8c, 0x1a, 0x43, 0x38, 0x20, 0xb2, 0xdf, - 0x0d, 0xda, 0x0c, 0xa8, 0x8a, 0x7d, 0x30, 0x1c, 0x10, 0x63, 0x18, 0x26, 0x30, 0x89, 0xad, 0xed, - 0xbf, 0xe3, 0xf1, 0xa5, 0x4f, 0xca, 0x5e, 0xfb, 0x3b, 0x16, 0x94, 0xc5, 0xfd, 0x05, 0xd2, 0xad, - 0x94, 0xff, 0x71, 0xca, 0xc2, 0x52, 0x5b, 0x5f, 0xc9, 0xf2, 0x3f, 0xbe, 0x0c, 0xe3, 0x3b, 0xae, - 0xa7, 0xbe, 0x44, 0xef, 0xd9, 0xaf, 0xb9, 0x5e, 0x0b, 0x39, 0x44, 0xef, 0xea, 0x85, 0xa1, 0xbb, - 0xfa, 0x22, 0x94, 0xb5, 0x57, 0x8e, 0xdc, 0x1b, 0xb5, 0x69, 0x5b, 0x7b, 0xf1, 0x60, 0x8c, 0x63, - 0xff, 0xba, 0x05, 0xd3, 0x3c, 0xc0, 0x3d, 0x36, 0x16, 0xbc, 0xa8, 0x1d, 0xe5, 0x84, 0xdc, 0x17, - 0x93, 0x8e, 0x72, 0xef, 0xed, 0x57, 0x2b, 0x22, 0x24, 0x3e, 0xe9, 0x37, 0xf7, 0x69, 0x69, 0x61, - 0xe4, 0xee, 0x7c, 0x63, 0x27, 0x36, 0x80, 0xc5, 0x62, 0x2a, 0x22, 0x18, 0xd3, 0xb3, 0xdf, 0x86, - 0x29, 0x33, 0x52, 0x8d, 0xbc, 0x08, 0x95, 0x9e, 0xeb, 0xb5, 0x93, 0x11, 0xcd, 0xfa, 0x16, 0x66, - 0x3d, 0x06, 0xa1, 0x89, 0xc7, 0xab, 0xf9, 0x71, 0xb5, 0xd4, 0xe5, 0xcd, 0xba, 0x6f, 0x56, 0x8b, - 0xff, 0xf0, 0x57, 0x22, 0x32, 0x22, 0x22, 0x73, 0x7f, 0x25, 0x22, 0x83, 0xc7, 0xfb, 0xf7, 0x4a, - 0x44, 0x96, 0x30, 0x7f, 0xb1, 0x5e, 0x89, 0xf8, 0x69, 0x38, 0x69, 0xc2, 0x58, 0xa6, 0x08, 0xdd, - 0x33, 0xb3, 0x4e, 0xe8, 0x16, 0x97, 0x69, 0x27, 0x24, 0xd4, 0xfe, 0x83, 0x71, 0x98, 0x4d, 0xdb, - 0x43, 0xf2, 0x76, 0x35, 0x21, 0x5f, 0xb3, 0x60, 0xda, 0x49, 0x24, 0xe7, 0xcb, 0xe9, 0xc9, 0xa9, - 0x04, 0x4d, 0x23, 0x39, 0x5c, 0xa2, 0x1c, 0x53, 0xbc, 0xc9, 0x5f, 0x82, 0xc9, 0xc8, 0xed, 0x52, - 0xbf, 0x2f, 0xac, 0xa4, 0x05, 0x61, 0xad, 0xd8, 0x10, 0x45, 0xa8, 0x60, 0x6c, 0x51, 0x76, 0xb9, - 0x7a, 0x19, 0x50, 0xe9, 0x36, 0x3d, 0x1b, 0x9b, 0x75, 0x45, 0x39, 0x6a, 0x0c, 0x72, 0x1f, 0x26, - 0x85, 0x53, 0x8a, 0xf2, 0x3e, 0x5a, 0xcb, 0xc9, 0x6e, 0x23, 0xfc, 0x5e, 0xe2, 0x2e, 0x10, 0xff, - 0x43, 0x54, 0xec, 0x98, 0x2e, 0x0b, 0x81, 0xe3, 0xb5, 0x29, 0x6f, 0x73, 0x69, 0x69, 0x78, 0x23, - 0x2f, 0x13, 0x19, 0x6a, 0xca, 0xb5, 0xa0, 0x1d, 0xca, 0x08, 0x44, 0x5d, 0x86, 0x06, 0x67, 0xfb, - 0x97, 0x2d, 0x98, 0x1b, 0x56, 0x91, 0x0d, 0x14, 0xbe, 0x0a, 0xca, 0x11, 0x65, 0x24, 0x3e, 0x70, - 0x82, 0x08, 0x05, 0x8c, 0x5c, 0x84, 0x02, 0xd5, 0x1b, 0x87, 0x4e, 0x4d, 0x78, 0xd5, 0x6b, 0x21, - 0x2b, 0x27, 0x57, 0x60, 0x3c, 0x8c, 0x68, 0x2f, 0x15, 0x57, 0x30, 0xde, 0x88, 0x68, 0x2f, 0xc3, - 0x30, 0xce, 0x71, 0xed, 0x8f, 0xc2, 0x09, 0xf3, 0x0b, 0xdb, 0x57, 0x81, 0xa0, 0xdf, 0xe9, 0x6c, - 0x3a, 0xcd, 0x9d, 0x3b, 0xae, 0xd7, 0xf2, 0xef, 0xf1, 0x85, 0x7a, 0x11, 0xca, 0x81, 0x8c, 0xb6, - 0x0e, 0xe5, 0x9c, 0xd2, 0x2b, 0xbd, 0x0a, 0xc3, 0x0e, 0x31, 0xc6, 0xb1, 0xbf, 0x3b, 0x06, 0x93, - 0x32, 0x35, 0xc0, 0x43, 0x08, 0x6a, 0xd9, 0x49, 0xb8, 0x12, 0xac, 0xe4, 0x92, 0xd1, 0x60, 0x68, - 0x44, 0x4b, 0x98, 0x8a, 0x68, 0x79, 0x2d, 0x1f, 0x76, 0x87, 0x87, 0xb3, 0x7c, 0xbb, 0x08, 0x33, - 0xa9, 0x54, 0x0b, 0xa9, 0x54, 0xe4, 0xd6, 0xfb, 0x92, 0x8a, 0x9c, 0x84, 0x89, 0x74, 0xf4, 0xf9, - 0xb9, 0xc0, 0xfe, 0x38, 0x33, 0x7d, 0x5e, 0xce, 0xc9, 0xc5, 0x0f, 0x8e, 0x73, 0xf2, 0x7f, 0xb1, - 0xe0, 0xf1, 0xa1, 0x09, 0x43, 0x78, 0xbe, 0xb9, 0x20, 0x09, 0x95, 0xeb, 0x45, 0xce, 0x69, 0x91, - 0xb4, 0xdb, 0x41, 0x3a, 0x45, 0x58, 0x9a, 0x3d, 0x79, 0x01, 0xa6, 0xf8, 0xda, 0xcc, 0x56, 0x4e, - 0xb6, 0xf6, 0x8a, 0x5b, 0x53, 0x7e, 0x7f, 0xd6, 0x30, 0xca, 0x31, 0x81, 0x65, 0x7f, 0xcb, 0x82, - 0xb9, 0x61, 0xd9, 0xc7, 0x8e, 0x61, 0xd3, 0xfb, 0xab, 0xa9, 0xa0, 0xa0, 0xea, 0x40, 0x50, 0x50, - 0xca, 0xaa, 0xa7, 0xe2, 0x7f, 0x0c, 0x83, 0x5a, 0xe1, 0x88, 0x98, 0x97, 0x3f, 0x2c, 0xc0, 0xac, - 0x14, 0x31, 0x3e, 0x32, 0xbc, 0x94, 0x08, 0x65, 0xfa, 0x89, 0x54, 0x28, 0xd3, 0xf9, 0x34, 0xfe, - 0x8f, 0xe3, 0x98, 0x3e, 0x58, 0x71, 0x4c, 0x5f, 0x2d, 0xc2, 0x85, 0xcc, 0x24, 0x64, 0xe4, 0x2b, - 0x19, 0x3b, 0xc5, 0x9d, 0x9c, 0xb3, 0x9d, 0xe9, 0x90, 0xe7, 0xd3, 0x0d, 0xfe, 0xf9, 0x55, 0x33, - 0xe8, 0x46, 0xac, 0xfe, 0x5b, 0xa7, 0x90, 0xb7, 0xed, 0xa4, 0xf1, 0x37, 0x0f, 0xf7, 0xa9, 0xb6, - 0xbf, 0x00, 0x4b, 0xfd, 0x57, 0x0b, 0xf0, 0xcc, 0x71, 0x5b, 0xf6, 0x03, 0x1a, 0xb0, 0x1a, 0x26, - 0x02, 0x56, 0x1f, 0x92, 0x6a, 0x73, 0x2a, 0xb1, 0xab, 0xff, 0x60, 0x5c, 0xef, 0xbb, 0x83, 0x13, - 0xf6, 0x58, 0x3e, 0x3e, 0x93, 0x4c, 0xf5, 0x55, 0x09, 0xed, 0xe3, 0xbd, 0x61, 0xb2, 0x21, 0x8a, - 0xdf, 0xdb, 0xaf, 0x9e, 0x8d, 0x73, 0x03, 0xc9, 0x42, 0x54, 0x95, 0xc8, 0x33, 0x50, 0x0a, 0x04, - 0x54, 0x85, 0xe8, 0x49, 0x47, 0x29, 0x51, 0x86, 0x1a, 0x4a, 0xbe, 0x60, 0x9c, 0x15, 0xc6, 0x4f, - 0x2b, 0x05, 0xd6, 0x61, 0xfe, 0x5f, 0x6f, 0x42, 0x29, 0x54, 0x59, 0xd7, 0xc5, 0x74, 0x7a, 0xfe, - 0x98, 0x91, 0x9f, 0xce, 0x26, 0xed, 0xa8, 0x14, 0xec, 0xe2, 0xfb, 0x74, 0x82, 0x76, 0x4d, 0x92, - 0xd8, 0xda, 0x32, 0x21, 0xee, 0xa7, 0x60, 0xd0, 0x2a, 0x41, 0x22, 0x98, 0x94, 0x4f, 0x2f, 0xcb, - 0xe3, 0xec, 0x5a, 0x4e, 0x21, 0x54, 0xd2, 0xc1, 0x9e, 0x1f, 0xf8, 0x95, 0x85, 0x4c, 0xb1, 0xb2, - 0xbf, 0x6f, 0x41, 0x45, 0x8e, 0x91, 0x87, 0x10, 0x02, 0x7b, 0x37, 0x19, 0x02, 0x7b, 0x35, 0x97, - 0x25, 0x7c, 0x48, 0xfc, 0xeb, 0x5d, 0x98, 0x32, 0xd3, 0x81, 0x92, 0x4f, 0x19, 0x5b, 0x90, 0x35, - 0x4a, 0x82, 0x3d, 0xb5, 0x49, 0xc5, 0xdb, 0x93, 0xfd, 0x5b, 0x65, 0xdd, 0x8a, 0xfc, 0xe0, 0x6c, - 0x8e, 0x7c, 0xeb, 0xd0, 0x91, 0x6f, 0x0e, 0xbc, 0xb1, 0xfc, 0x07, 0xde, 0xeb, 0x50, 0x52, 0xcb, - 0xa2, 0xd4, 0xa6, 0x9e, 0x32, 0x3d, 0xee, 0x99, 0x4a, 0xc6, 0x88, 0x19, 0xd3, 0x85, 0x1f, 0x80, - 0x63, 0xbb, 0xbd, 0x5a, 0xae, 0x35, 0x19, 0xf2, 0x16, 0x54, 0xee, 0xf9, 0xc1, 0x4e, 0xc7, 0x77, - 0xf8, 0x53, 0x17, 0x90, 0x87, 0x93, 0x87, 0xb6, 0xbd, 0x8b, 0xb0, 0xa7, 0x3b, 0x31, 0x7d, 0x34, - 0x99, 0x91, 0x1a, 0xcc, 0x74, 0x5d, 0x0f, 0xa9, 0xd3, 0xd2, 0x91, 0xae, 0xe3, 0x22, 0xcd, 0xbc, - 0xd2, 0xed, 0xd7, 0x92, 0x60, 0x4c, 0xe3, 0x73, 0xbb, 0x5c, 0x90, 0x30, 0x75, 0xc8, 0x5c, 0xd2, - 0xeb, 0xa3, 0x0f, 0xc6, 0xa4, 0xf9, 0x44, 0xc4, 0xfd, 0x24, 0xcb, 0x31, 0xc5, 0x9b, 0x7c, 0x1e, - 0x4a, 0xa1, 0x7a, 0xd4, 0xb4, 0x98, 0xe3, 0xa9, 0x47, 0x3f, 0x6c, 0xaa, 0xbb, 0x52, 0xbf, 0x6c, - 0xaa, 0x19, 0x92, 0x55, 0x38, 0xaf, 0x6c, 0x37, 0x89, 0xf7, 0x19, 0x27, 0xe2, 0xd4, 0x70, 0x98, - 0x01, 0xc7, 0xcc, 0x5a, 0x4c, 0xb7, 0xe5, 0x69, 0x76, 0xc5, 0xa5, 0xba, 0x71, 0x0f, 0xcd, 0xe7, - 0x5f, 0x0b, 0x25, 0xf4, 0xb0, 0x40, 0xee, 0xd2, 0x08, 0x81, 0xdc, 0x0d, 0xb8, 0x90, 0x06, 0xf1, - 0x9c, 0x7f, 0x3c, 0xcd, 0xa0, 0xb1, 0x85, 0xae, 0x67, 0x21, 0x61, 0x76, 0x5d, 0x72, 0x07, 0xca, - 0x01, 0xe5, 0xa7, 0xbc, 0x9a, 0xf2, 0x47, 0x3c, 0xb1, 0xe7, 0x35, 0x2a, 0x02, 0x18, 0xd3, 0x62, - 0xfd, 0xee, 0x24, 0x13, 0xbf, 0xbf, 0x9e, 0xe3, 0xab, 0xe7, 0xb2, 0xef, 0x87, 0xe4, 0xe2, 0xb4, - 0xff, 0xed, 0x0c, 0x9c, 0x49, 0x18, 0xa0, 0xc8, 0x53, 0x50, 0xe4, 0x49, 0x10, 0xf9, 0x6a, 0x55, - 0x8a, 0x57, 0x54, 0xd1, 0x38, 0x02, 0x46, 0x7e, 0xc9, 0x82, 0x99, 0x5e, 0xe2, 0xba, 0x49, 0x2d, - 0xe4, 0x23, 0xda, 0xb4, 0x93, 0x77, 0x58, 0xc6, 0x93, 0x29, 0x49, 0x66, 0x98, 0xe6, 0xce, 0xd6, - 0x03, 0x19, 0xbe, 0xd0, 0xa1, 0x01, 0xc7, 0x96, 0x8a, 0x9e, 0x26, 0xb1, 0x94, 0x04, 0x63, 0x1a, - 0x9f, 0xf5, 0x30, 0xff, 0xba, 0x51, 0x5e, 0xb6, 0xad, 0x29, 0x02, 0x18, 0xd3, 0x22, 0xaf, 0xc0, - 0xb4, 0xcc, 0xf7, 0xbd, 0xee, 0xb7, 0xae, 0x3b, 0xe1, 0xb6, 0x3c, 0xf2, 0xe9, 0x23, 0xea, 0x52, - 0x02, 0x8a, 0x29, 0x6c, 0xfe, 0x6d, 0x71, 0x52, 0x75, 0x4e, 0x60, 0x22, 0xf9, 0xa2, 0xcc, 0x52, - 0x12, 0x8c, 0x69, 0x7c, 0xf2, 0x9c, 0xb1, 0x0d, 0x09, 0x47, 0x17, 0xbd, 0x1a, 0x64, 0x6c, 0x45, - 0x35, 0x98, 0xe9, 0xf3, 0x13, 0x72, 0x4b, 0x01, 0xe5, 0x7c, 0xd4, 0x0c, 0x6f, 0x27, 0xc1, 0x98, - 0xc6, 0x27, 0x2f, 0xc3, 0x99, 0x80, 0x2d, 0xb6, 0x9a, 0x80, 0xf0, 0x7e, 0xd1, 0xce, 0x0d, 0x68, - 0x02, 0x31, 0x89, 0x4b, 0x5e, 0x85, 0xb3, 0x71, 0x7a, 0x5c, 0x45, 0x40, 0xb8, 0xc3, 0xe8, 0x5c, - 0x8d, 0xb5, 0x34, 0x02, 0x0e, 0xd6, 0x21, 0x7f, 0x03, 0x66, 0x8d, 0x96, 0x58, 0xf1, 0x5a, 0xf4, - 0xbe, 0x4c, 0x61, 0xca, 0x5f, 0x48, 0x5b, 0x4a, 0xc1, 0x70, 0x00, 0x9b, 0x7c, 0x02, 0xa6, 0x9b, - 0x7e, 0xa7, 0xc3, 0xd7, 0x38, 0xf1, 0x9a, 0x89, 0xc8, 0x55, 0x2a, 0xb2, 0xba, 0x26, 0x20, 0x98, - 0xc2, 0x24, 0x37, 0x80, 0xf8, 0x9b, 0x4c, 0xbd, 0xa2, 0xad, 0x57, 0xa9, 0x47, 0xa5, 0xc6, 0x71, - 0x26, 0x19, 0x3c, 0x75, 0x6b, 0x00, 0x03, 0x33, 0x6a, 0xf1, 0x54, 0x8f, 0x46, 0xb0, 0xf9, 0x74, - 0x1e, 0xcf, 0xb0, 0xa6, 0xed, 0x39, 0x47, 0x46, 0x9a, 0x07, 0x30, 0x21, 0x3c, 0x14, 0xf2, 0x49, - 0x5a, 0x6a, 0x3e, 0x6c, 0x10, 0xef, 0x11, 0xa2, 0x14, 0x25, 0x27, 0xf2, 0x73, 0x50, 0xde, 0x54, - 0xaf, 0xdc, 0xf0, 0x4c, 0xa5, 0x23, 0xef, 0x8b, 0xa9, 0x07, 0x9b, 0x62, 0x7b, 0x85, 0x06, 0x60, - 0xcc, 0x92, 0x3c, 0x0d, 0x95, 0xeb, 0xeb, 0x35, 0x3d, 0x0a, 0xcf, 0xf2, 0xde, 0x1f, 0x67, 0x55, - 0xd0, 0x04, 0xb0, 0x19, 0xa6, 0xd5, 0x37, 0x92, 0x74, 0x62, 0xc8, 0xd0, 0xc6, 0x18, 0x36, 0x77, - 0x59, 0xc1, 0xc6, 0xdc, 0xb9, 0x14, 0xb6, 0x2c, 0x47, 0x8d, 0x41, 0xde, 0x84, 0x8a, 0xdc, 0x2f, - 0xf8, 0xda, 0x74, 0xfe, 0xc1, 0x12, 0x19, 0x60, 0x4c, 0x02, 0x4d, 0x7a, 0xfc, 0x3a, 0x9d, 0x3f, - 0xfe, 0x41, 0xaf, 0xf5, 0x3b, 0x9d, 0xb9, 0x0b, 0x7c, 0xdd, 0x8c, 0xaf, 0xd3, 0x63, 0x10, 0x9a, - 0x78, 0xe4, 0x79, 0xe5, 0x7a, 0xf8, 0x68, 0xc2, 0xbf, 0x40, 0xbb, 0x1e, 0x6a, 0xa5, 0x7b, 0x48, - 0xac, 0xd3, 0x63, 0x47, 0xf8, 0xfc, 0x6d, 0xc2, 0xbc, 0xd2, 0xf8, 0x06, 0x27, 0xc9, 0xdc, 0x5c, - 0xc2, 0x76, 0x34, 0x7f, 0x67, 0x28, 0x26, 0x1e, 0x42, 0x85, 0x6c, 0x42, 0xc1, 0xe9, 0x6c, 0xce, - 0x3d, 0x9e, 0x87, 0xea, 0x5a, 0x5b, 0xad, 0xcb, 0x11, 0xc5, 0xfd, 0x93, 0x6b, 0xab, 0x75, 0x64, - 0xc4, 0x89, 0x0b, 0xe3, 0x4e, 0x67, 0x33, 0x9c, 0x9b, 0xe7, 0x73, 0x36, 0x37, 0x26, 0xb1, 0xf1, - 0x60, 0xb5, 0x1e, 0x22, 0x67, 0x61, 0x7f, 0x71, 0x4c, 0xdf, 0x12, 0xe9, 0xbc, 0xf1, 0x6f, 0x9b, - 0x13, 0x48, 0x1c, 0x77, 0x6e, 0xe5, 0x36, 0x81, 0xa4, 0x7a, 0x71, 0x66, 0xe8, 0xf4, 0xe9, 0xe9, - 0x25, 0x23, 0x97, 0x84, 0x73, 0xc9, 0x9c, 0xf8, 0xe2, 0xf4, 0x9c, 0x5c, 0x30, 0xec, 0x2f, 0x55, - 0xb4, 0x15, 0x34, 0xe5, 0xb6, 0x17, 0x40, 0xd1, 0x0d, 0x23, 0xd7, 0xcf, 0x31, 0xbe, 0x3f, 0x95, - 0x4c, 0x9e, 0x87, 0x0f, 0x71, 0x00, 0x0a, 0x56, 0x8c, 0xa7, 0xd7, 0x76, 0xbd, 0xfb, 0xf2, 0xf3, - 0x5f, 0xcf, 0xdd, 0xe9, 0x4c, 0xf0, 0xe4, 0x00, 0x14, 0xac, 0xc8, 0x5d, 0x31, 0xa8, 0x0b, 0x79, - 0xf4, 0x75, 0x6d, 0xb5, 0x9e, 0xe2, 0x97, 0x1c, 0xdc, 0x77, 0xa1, 0x10, 0x76, 0x5d, 0xa9, 0x2e, - 0x8d, 0xc8, 0xab, 0xb1, 0xb6, 0x92, 0xc5, 0xab, 0xb1, 0xb6, 0x82, 0x8c, 0x09, 0xbf, 0xea, 0x77, - 0xba, 0x9b, 0x4e, 0x18, 0x3a, 0x2d, 0x6d, 0x9d, 0x19, 0xf1, 0xaa, 0xbf, 0xa6, 0xe9, 0xa5, 0x58, - 0xf3, 0xab, 0xfe, 0x18, 0x8a, 0x06, 0x67, 0xf2, 0x16, 0x4c, 0x3a, 0xe2, 0x15, 0x4e, 0x19, 0x4c, - 0x91, 0xcf, 0xd3, 0xb2, 0x29, 0x09, 0xb8, 0x99, 0x46, 0x82, 0x50, 0x31, 0x64, 0xbc, 0xa3, 0xc0, - 0xa1, 0x5b, 0xee, 0x8e, 0x34, 0x0e, 0x35, 0x46, 0x7e, 0x27, 0x86, 0x11, 0xcb, 0xe2, 0x2d, 0x41, - 0xa8, 0x18, 0x92, 0x5f, 0xb0, 0xe0, 0x4c, 0xd7, 0xf1, 0x1c, 0x1d, 0x22, 0x9b, 0x4f, 0x20, 0xb5, - 0x19, 0x74, 0x1b, 0x6b, 0x88, 0x6b, 0x26, 0x23, 0x4c, 0xf2, 0x25, 0xbb, 0x30, 0xe1, 0xf0, 0xf7, - 0x81, 0xe5, 0x51, 0x0c, 0xf3, 0x78, 0x6b, 0x38, 0xd5, 0x06, 0x7c, 0x71, 0x91, 0xaf, 0x10, 0x4b, - 0x6e, 0xe4, 0x37, 0x2c, 0x98, 0x14, 0x7e, 0xfe, 0x4c, 0x21, 0x65, 0xdf, 0xfe, 0xd9, 0x53, 0x78, - 0x94, 0x42, 0xc6, 0x20, 0x48, 0xe7, 0xac, 0x0f, 0x6b, 0x27, 0x66, 0x51, 0x7a, 0x68, 0x14, 0x82, - 0x92, 0x8e, 0xa9, 0xbe, 0x5d, 0xe7, 0x7e, 0xe2, 0x15, 0x20, 0x53, 0xf5, 0x5d, 0x4b, 0xc1, 0x70, - 0x00, 0x7b, 0xfe, 0x13, 0x30, 0x65, 0xca, 0x71, 0xa2, 0x48, 0x86, 0x1f, 0x16, 0x00, 0x78, 0x57, - 0x89, 0xb4, 0x3a, 0x5d, 0x9e, 0x83, 0x7b, 0xdb, 0x6f, 0xe5, 0xf4, 0x1a, 0xa9, 0x91, 0x1d, 0x07, - 0x64, 0xc2, 0xed, 0x6d, 0xbf, 0x85, 0x92, 0x09, 0x69, 0xc3, 0x78, 0xcf, 0x89, 0xb6, 0xf3, 0x4f, - 0xc5, 0x53, 0x12, 0xf1, 0xe5, 0xd1, 0x36, 0x72, 0x06, 0xe4, 0x1d, 0x2b, 0xf6, 0x7b, 0x2a, 0xe4, - 0x91, 0x46, 0x38, 0x6e, 0xb3, 0x05, 0xe9, 0xe9, 0x94, 0xca, 0xa6, 0x9b, 0xf6, 0x7f, 0x9a, 0x7f, - 0xd7, 0x82, 0x29, 0x13, 0x35, 0xa3, 0x9b, 0x7e, 0xd6, 0xec, 0xa6, 0x3c, 0xdb, 0xc3, 0xec, 0xf1, - 0xff, 0x66, 0x01, 0x60, 0xdf, 0x6b, 0xf4, 0xbb, 0x5d, 0xa6, 0xb6, 0xeb, 0x80, 0x0d, 0xeb, 0xd8, - 0x01, 0x1b, 0x63, 0x27, 0x0c, 0xd8, 0x28, 0x9c, 0x28, 0x60, 0x63, 0xfc, 0xe4, 0x01, 0x1b, 0xc5, - 0xe1, 0x01, 0x1b, 0xf6, 0x37, 0x2c, 0x38, 0x3b, 0xb0, 0x5f, 0x31, 0x4d, 0x3a, 0xf0, 0xfd, 0x68, - 0x88, 0x3f, 0x2b, 0xc6, 0x20, 0x34, 0xf1, 0xc8, 0x32, 0xcc, 0xca, 0x17, 0x67, 0x1a, 0xbd, 0x8e, - 0x9b, 0x99, 0x26, 0x69, 0x23, 0x05, 0xc7, 0x81, 0x1a, 0xf6, 0xbf, 0xb2, 0xa0, 0x62, 0x24, 0x57, - 0xe0, 0x3e, 0x67, 0xfc, 0xc6, 0x2b, 0xed, 0x73, 0xc6, 0xaf, 0xba, 0x04, 0x4c, 0x5c, 0x43, 0xb7, - 0x8d, 0xf7, 0x08, 0xe2, 0x6b, 0x68, 0x56, 0x8a, 0x12, 0x2a, 0x32, 0xcd, 0x4b, 0xe7, 0xb3, 0x82, - 0x99, 0x69, 0x9e, 0xf6, 0x84, 0xab, 0x59, 0xec, 0xe2, 0x36, 0x7e, 0xb4, 0x8b, 0x5b, 0x31, 0xdb, - 0xc5, 0xcd, 0xbe, 0x05, 0x53, 0xc2, 0x57, 0xfb, 0x35, 0xba, 0x77, 0xec, 0xe7, 0x7c, 0xd9, 0x68, - 0x4f, 0xf9, 0xcc, 0xb1, 0xea, 0xac, 0xdc, 0xfe, 0x27, 0x16, 0xa4, 0x1e, 0xa0, 0x32, 0x6e, 0x60, - 0xac, 0xa1, 0x37, 0x30, 0xa6, 0xd5, 0x7e, 0xec, 0x50, 0xab, 0xfd, 0x0d, 0x20, 0x5d, 0x36, 0x15, - 0x92, 0x0b, 0x6d, 0x21, 0xf9, 0x2a, 0xc8, 0xda, 0x00, 0x06, 0x66, 0xd4, 0xb2, 0xff, 0xb1, 0x10, - 0xd6, 0x7c, 0x92, 0xea, 0xe8, 0x06, 0xe8, 0x43, 0x91, 0x93, 0x92, 0xf6, 0xb7, 0x11, 0x6d, 0xd7, - 0x83, 0x29, 0xd1, 0xe2, 0x8e, 0x94, 0x53, 0x9e, 0x73, 0xb3, 0xff, 0x50, 0xc8, 0x6a, 0xbc, 0x59, - 0x75, 0x0c, 0x59, 0xbb, 0x49, 0x59, 0xaf, 0xe7, 0xb5, 0x56, 0x66, 0xcb, 0x48, 0x16, 0x00, 0x7a, - 0x34, 0x68, 0x52, 0x2f, 0x52, 0x21, 0x66, 0x45, 0x19, 0xec, 0xac, 0x4b, 0xd1, 0xc0, 0xb0, 0xbf, - 0xce, 0x26, 0x50, 0xfc, 0xd0, 0x35, 0x79, 0x26, 0xed, 0x08, 0x9c, 0x9e, 0x1c, 0xda, 0x0f, 0xd8, - 0x08, 0x3c, 0x1a, 0x3b, 0x22, 0xf0, 0xe8, 0x59, 0x98, 0x0c, 0xfc, 0x0e, 0xad, 0x05, 0x5e, 0xda, - 0x47, 0x07, 0x59, 0x31, 0xde, 0x44, 0x05, 0xb7, 0x7f, 0xcd, 0x82, 0xd9, 0x74, 0x64, 0x64, 0xee, - 0xde, 0xc9, 0x66, 0xfa, 0x86, 0xc2, 0xc9, 0xd3, 0x37, 0xd8, 0xef, 0x30, 0x21, 0x23, 0xb7, 0xb9, - 0xe3, 0x7a, 0x22, 0xe3, 0x01, 0x6b, 0xb9, 0x67, 0x61, 0x92, 0xca, 0x07, 0x7b, 0x85, 0x19, 0x59, - 0x0b, 0xa9, 0xde, 0xe9, 0x55, 0x70, 0x52, 0x83, 0x19, 0x75, 0x79, 0xa6, 0x6c, 0xff, 0x22, 0x53, - 0x8b, 0xb6, 0x35, 0x2e, 0x27, 0xc1, 0x98, 0xc6, 0xb7, 0xbf, 0x00, 0x15, 0x63, 0x53, 0xe2, 0xeb, - 0xf7, 0x7d, 0xa7, 0x39, 0xe0, 0x6b, 0x7b, 0x95, 0x15, 0xa2, 0x80, 0xf1, 0x2b, 0x0a, 0x11, 0xb8, - 0x95, 0x5a, 0xf7, 0x64, 0xb8, 0x96, 0x84, 0x32, 0x62, 0x01, 0x6d, 0xd3, 0xfb, 0xea, 0xb9, 0x08, - 0x45, 0x0c, 0x59, 0x21, 0x0a, 0x98, 0xfd, 0x1c, 0x94, 0x54, 0x3e, 0x2d, 0x9e, 0x94, 0x46, 0x99, - 0xcf, 0xcd, 0xa4, 0x34, 0x7e, 0x10, 0x21, 0x87, 0xd8, 0x6f, 0x40, 0x49, 0xa5, 0xfd, 0x3a, 0x1a, - 0x9b, 0x2d, 0x45, 0xa1, 0xe7, 0x5e, 0xf7, 0xc3, 0x48, 0xe5, 0x2a, 0x13, 0x37, 0x7c, 0x37, 0x57, - 0x78, 0x19, 0x6a, 0xa8, 0xfd, 0xe7, 0x16, 0x54, 0x36, 0x36, 0x56, 0xf5, 0xc1, 0x1f, 0xe1, 0xd1, - 0x50, 0xb4, 0x50, 0x6d, 0x2b, 0xa2, 0xa6, 0x2b, 0x81, 0x58, 0xf8, 0xe6, 0x0f, 0xf6, 0xab, 0x8f, - 0x36, 0x32, 0x31, 0x70, 0x48, 0x4d, 0xb2, 0x02, 0xe7, 0x4c, 0x88, 0xcc, 0x21, 0x21, 0xd7, 0x48, - 0xfe, 0xc2, 0x73, 0x63, 0x10, 0x8c, 0x59, 0x75, 0xd2, 0xa4, 0xe4, 0x76, 0x6f, 0x3e, 0x16, 0xdd, - 0x18, 0x04, 0x63, 0x56, 0x1d, 0xfb, 0x79, 0x98, 0x49, 0xdd, 0x71, 0x1f, 0x23, 0x77, 0xcf, 0xef, - 0x15, 0x60, 0xca, 0xbc, 0xea, 0x3c, 0xc6, 0xfa, 0x75, 0xfc, 0x6d, 0x21, 0xe3, 0x7a, 0xb2, 0x70, - 0xc2, 0xeb, 0x49, 0xf3, 0x3e, 0x78, 0xfc, 0x74, 0xef, 0x83, 0x8b, 0xf9, 0xdc, 0x07, 0x1b, 0x7e, - 0x0b, 0x13, 0x0f, 0xcf, 0x6f, 0xe1, 0x77, 0x8b, 0x30, 0x9d, 0x4c, 0x06, 0x7b, 0x8c, 0x9e, 0x7c, - 0x6e, 0xa0, 0x27, 0x4f, 0x78, 0x1f, 0x52, 0x18, 0xf5, 0x3e, 0x64, 0x7c, 0xd4, 0xfb, 0x90, 0xe2, - 0x03, 0xdc, 0x87, 0x0c, 0xde, 0x66, 0x4c, 0x1c, 0xfb, 0x36, 0xe3, 0x93, 0xda, 0xc5, 0x73, 0x32, - 0xe1, 0x02, 0x14, 0xbb, 0x78, 0x92, 0x64, 0x37, 0x2c, 0xf9, 0xad, 0x4c, 0xd7, 0xd4, 0xd2, 0x11, - 0x76, 0xdf, 0x20, 0xd3, 0x23, 0xf3, 0xe4, 0x57, 0xae, 0x8f, 0x9e, 0xc0, 0x1b, 0xf3, 0x45, 0xa8, - 0xc8, 0xf1, 0xc4, 0x95, 0x6f, 0x48, 0x2a, 0xee, 0x8d, 0x18, 0x84, 0x26, 0x1e, 0x1b, 0x18, 0xbd, - 0x78, 0x82, 0xf0, 0x9b, 0xb9, 0x4a, 0xf2, 0x66, 0x6e, 0x3d, 0x09, 0xc6, 0x34, 0xbe, 0xfd, 0x79, - 0xb8, 0x90, 0x69, 0x82, 0xe1, 0xe6, 0x6f, 0xae, 0x17, 0xd2, 0x96, 0x44, 0x30, 0xc4, 0x48, 0xbd, - 0x11, 0x33, 0x7f, 0x67, 0x28, 0x26, 0x1e, 0x42, 0xc5, 0xfe, 0xed, 0x02, 0x4c, 0x27, 0x1f, 0x0a, - 0x26, 0xf7, 0xb4, 0xc1, 0x36, 0x17, 0x5b, 0xb1, 0x20, 0x6b, 0x24, 0x18, 0x1d, 0x7a, 0xd1, 0x73, - 0x8f, 0x8f, 0xaf, 0x4d, 0x9d, 0xed, 0xf4, 0xf4, 0x18, 0xcb, 0x1b, 0x16, 0xc9, 0x8e, 0xbf, 0x05, - 0x1c, 0xc7, 0x22, 0xcb, 0x73, 0x7c, 0xee, 0xdc, 0xe3, 0xb0, 0x51, 0xcd, 0x0a, 0x0d, 0xb6, 0x6c, - 0x6f, 0xd9, 0xa5, 0x81, 0xbb, 0xe5, 0xd2, 0x96, 0x4c, 0x3e, 0xcf, 0x57, 0xee, 0x37, 0x64, 0x19, - 0x6a, 0xa8, 0xfd, 0xce, 0x18, 0x94, 0x79, 0xea, 0xb4, 0x6b, 0x81, 0xdf, 0xe5, 0xaf, 0x69, 0x86, - 0xc6, 0x99, 0x49, 0x76, 0xdb, 0x8d, 0x51, 0x9f, 0xac, 0x8d, 0x29, 0x4a, 0x77, 0x77, 0xa3, 0x04, - 0x13, 0x1c, 0x49, 0x0f, 0x4a, 0x5b, 0x32, 0xd5, 0xb3, 0xec, 0xbb, 0x11, 0xd3, 0x95, 0xaa, 0xc4, - 0xd1, 0xa2, 0x09, 0xd4, 0x3f, 0xd4, 0x5c, 0x6c, 0x07, 0x66, 0x52, 0xb9, 0x6f, 0x72, 0x4f, 0x10, - 0xfd, 0x3f, 0xc7, 0xa1, 0xac, 0xa3, 0xd0, 0xc8, 0xc7, 0x13, 0x06, 0xac, 0x72, 0xfd, 0x43, 0xc6, - 0x53, 0x6f, 0xdb, 0x7e, 0xeb, 0xbd, 0xfd, 0xea, 0x8c, 0x46, 0x4e, 0x19, 0xa3, 0x2e, 0x42, 0xa1, - 0x1f, 0x74, 0xd2, 0x27, 0xd4, 0xdb, 0xb8, 0x8a, 0xac, 0xdc, 0x8c, 0x9c, 0x2b, 0x3c, 0xdc, 0xc8, - 0xb9, 0xcb, 0x30, 0xbe, 0xe9, 0xb7, 0xf6, 0xd2, 0x4f, 0xc3, 0xd5, 0xfd, 0xd6, 0x1e, 0x72, 0x08, - 0x79, 0x05, 0xa6, 0x65, 0x38, 0xa0, 0x52, 0x62, 0x8a, 0x5c, 0x4f, 0xd5, 0x8e, 0x0b, 0x1b, 0x09, - 0x28, 0xa6, 0xb0, 0xd9, 0x2e, 0x7b, 0x37, 0xf4, 0x3d, 0x9e, 0xf6, 0x7b, 0x22, 0x79, 0xcb, 0x79, - 0xa3, 0x71, 0xeb, 0x26, 0x37, 0xa4, 0x69, 0x8c, 0x44, 0xc4, 0xe1, 0xe4, 0x91, 0x11, 0x87, 0xcb, - 0x82, 0x36, 0x93, 0x96, 0xef, 0x28, 0x53, 0xf5, 0x67, 0x14, 0x5d, 0x56, 0xf6, 0xde, 0xfe, 0x21, - 0x46, 0x52, 0x5d, 0x33, 0x2b, 0x36, 0xb3, 0xfc, 0xfe, 0xc5, 0x66, 0xda, 0xb7, 0x61, 0x26, 0xd5, - 0x7f, 0xca, 0xc0, 0x61, 0x65, 0x1b, 0x38, 0x8e, 0xf7, 0xb8, 0xdc, 0x3f, 0xb3, 0xe0, 0xec, 0xc0, - 0x8a, 0x74, 0xdc, 0x20, 0xd9, 0xf4, 0xde, 0x38, 0xf6, 0xe0, 0x7b, 0x63, 0xe1, 0x64, 0x7b, 0x63, - 0x7d, 0xf3, 0x3b, 0x3f, 0xb8, 0xf4, 0xc8, 0xf7, 0x7e, 0x70, 0xe9, 0x91, 0x3f, 0xfa, 0xc1, 0xa5, - 0x47, 0xde, 0x39, 0xb8, 0x64, 0x7d, 0xe7, 0xe0, 0x92, 0xf5, 0xbd, 0x83, 0x4b, 0xd6, 0x1f, 0x1d, - 0x5c, 0xb2, 0xfe, 0xf3, 0xc1, 0x25, 0xeb, 0x1b, 0x7f, 0x7a, 0xe9, 0x91, 0x4f, 0x7d, 0x32, 0xee, - 0xa9, 0x45, 0xd5, 0x53, 0xfc, 0xc7, 0x47, 0x54, 0xbf, 0x2c, 0xf6, 0x76, 0xda, 0x8b, 0xac, 0xa7, - 0x16, 0x75, 0x89, 0xea, 0xa9, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xa4, 0xe6, 0x16, 0xde, 0x1b, - 0xa9, 0x00, 0x00, + 0x75, 0x98, 0x1e, 0x87, 0x43, 0xce, 0x9c, 0xe1, 0x92, 0xdc, 0xbb, 0xbb, 0x12, 0x45, 0x69, 0x97, + 0xeb, 0xa7, 0x54, 0x5d, 0xc5, 0x32, 0x69, 0xaf, 0xa4, 0x54, 0xb6, 0x5c, 0xb5, 0x33, 0xe4, 0xae, + 0x96, 0x2b, 0x72, 0x97, 0x3a, 0xc3, 0xd5, 0xc6, 0x1f, 0x4a, 0xfc, 0x38, 0x73, 0x39, 0x7c, 0xcb, + 0x99, 0xf7, 0xc6, 0xef, 0xbd, 0xe1, 0x2e, 0x65, 0x21, 0x96, 0x6d, 0x28, 0x71, 0x5c, 0x1b, 0x71, + 0x93, 0x18, 0x45, 0x3f, 0x50, 0xb8, 0x41, 0x8a, 0xb4, 0x4d, 0x7f, 0x14, 0x81, 0x8b, 0xf6, 0x47, + 0x80, 0x16, 0x75, 0x53, 0xd8, 0x40, 0x5d, 0x38, 0x3f, 0x5a, 0xa7, 0x05, 0xc2, 0xd4, 0x4c, 0xff, + 0x34, 0x68, 0x61, 0xa4, 0x70, 0x11, 0x54, 0x3f, 0x8a, 0xe2, 0x7e, 0xbe, 0xfb, 0xde, 0xbc, 0xe1, + 0xd7, 0x3c, 0xae, 0x94, 0xc6, 0xff, 0x66, 0xee, 0x39, 0xf7, 0x9c, 0xf3, 0xee, 0xe7, 0xb9, 0xe7, + 0x9e, 0x73, 0x2e, 0xac, 0xb4, 0xdc, 0x68, 0xab, 0xb7, 0x31, 0xdf, 0xf0, 0x3b, 0x0b, 0x4e, 0xd0, + 0xf2, 0xbb, 0x81, 0x7f, 0x8f, 0xff, 0xf8, 0x50, 0xe0, 0xb7, 0xdb, 0x7e, 0x2f, 0x0a, 0x17, 0xba, + 0xdb, 0xad, 0x05, 0xa7, 0xeb, 0x86, 0x0b, 0xba, 0x64, 0xe7, 0x23, 0x4e, 0xbb, 0xbb, 0xe5, 0x7c, + 0x64, 0xa1, 0x45, 0x3d, 0x1a, 0x38, 0x11, 0x6d, 0xce, 0x77, 0x03, 0x3f, 0xf2, 0xc9, 0xc7, 0x63, + 0x6a, 0xf3, 0x8a, 0x1a, 0xff, 0xf1, 0xf3, 0xaa, 0xee, 0x7c, 0x77, 0xbb, 0x35, 0xcf, 0xa8, 0xcd, + 0xeb, 0x12, 0x45, 0x6d, 0xf6, 0x43, 0x86, 0x2c, 0x2d, 0xbf, 0xe5, 0x2f, 0x70, 0xa2, 0x1b, 0xbd, + 0x4d, 0xfe, 0x8f, 0xff, 0xe1, 0xbf, 0x04, 0xb3, 0xd9, 0xa7, 0xb6, 0x5f, 0x0c, 0xe7, 0x5d, 0x9f, + 0xc9, 0xb6, 0xb0, 0xe1, 0x44, 0x8d, 0xad, 0x85, 0x9d, 0x3e, 0x89, 0x66, 0x6d, 0x03, 0xa9, 0xe1, + 0x07, 0x34, 0x0b, 0xe7, 0xf9, 0x18, 0xa7, 0xe3, 0x34, 0xb6, 0x5c, 0x8f, 0x06, 0xbb, 0xf1, 0x57, + 0x77, 0x68, 0xe4, 0x64, 0xd5, 0x5a, 0x18, 0x54, 0x2b, 0xe8, 0x79, 0x91, 0xdb, 0xa1, 0x7d, 0x15, + 0x7e, 0xe6, 0xb0, 0x0a, 0x61, 0x63, 0x8b, 0x76, 0x9c, 0xbe, 0x7a, 0xcf, 0x0d, 0xaa, 0xd7, 0x8b, + 0xdc, 0xf6, 0x82, 0xeb, 0x45, 0x61, 0x14, 0xa4, 0x2b, 0xd9, 0x3f, 0x2a, 0x40, 0xb9, 0xba, 0x52, + 0xab, 0x47, 0x4e, 0xd4, 0x0b, 0xc9, 0x2f, 0x5a, 0x30, 0xd1, 0xf6, 0x9d, 0x66, 0xcd, 0x69, 0x3b, + 0x5e, 0x83, 0x06, 0x33, 0xd6, 0x65, 0xeb, 0x4a, 0xe5, 0xea, 0xca, 0xfc, 0x30, 0xfd, 0x35, 0x5f, + 0xbd, 0x1f, 0x22, 0x0d, 0xfd, 0x5e, 0xd0, 0xa0, 0x48, 0x37, 0x6b, 0xe7, 0xbf, 0xb3, 0x37, 0xf7, + 0xc8, 0xfe, 0xde, 0xdc, 0xc4, 0x8a, 0xc1, 0x09, 0x13, 0x7c, 0xc9, 0x37, 0x2c, 0x38, 0xdb, 0x70, + 0x3c, 0x27, 0xd8, 0x5d, 0x77, 0x82, 0x16, 0x8d, 0x5e, 0x09, 0xfc, 0x5e, 0x77, 0x66, 0xe4, 0x14, + 0xa4, 0x79, 0x5c, 0x4a, 0x73, 0x76, 0x31, 0xcd, 0x0e, 0xfb, 0x25, 0xe0, 0x72, 0x85, 0x91, 0xb3, + 0xd1, 0xa6, 0xa6, 0x5c, 0x85, 0xd3, 0x94, 0xab, 0x9e, 0x66, 0x87, 0xfd, 0x12, 0x90, 0x67, 0x60, + 0xdc, 0xf5, 0x5a, 0x01, 0x0d, 0xc3, 0x99, 0xd1, 0xcb, 0xd6, 0x95, 0x72, 0x6d, 0x4a, 0x56, 0x1f, + 0x5f, 0x16, 0xc5, 0xa8, 0xe0, 0xf6, 0xef, 0x14, 0xe0, 0x6c, 0x75, 0xa5, 0xb6, 0x1e, 0x38, 0x9b, + 0x9b, 0x6e, 0x03, 0xfd, 0x5e, 0xe4, 0x7a, 0x2d, 0x93, 0x80, 0x75, 0x30, 0x01, 0xf2, 0x02, 0x54, + 0x42, 0x1a, 0xec, 0xb8, 0x0d, 0xba, 0xe6, 0x07, 0x11, 0xef, 0x94, 0x62, 0xed, 0x9c, 0x44, 0xaf, + 0xd4, 0x63, 0x10, 0x9a, 0x78, 0xac, 0x5a, 0xe0, 0xfb, 0x91, 0x84, 0xf3, 0x36, 0x2b, 0xc7, 0xd5, + 0x30, 0x06, 0xa1, 0x89, 0x47, 0x96, 0x60, 0xda, 0xf1, 0x3c, 0x3f, 0x72, 0x22, 0xd7, 0xf7, 0xd6, + 0x02, 0xba, 0xe9, 0x3e, 0x90, 0x9f, 0x38, 0x23, 0xeb, 0x4e, 0x57, 0x53, 0x70, 0xec, 0xab, 0x41, + 0xbe, 0x6e, 0xc1, 0x74, 0x18, 0xb9, 0x8d, 0x6d, 0xd7, 0xa3, 0x61, 0xb8, 0xe8, 0x7b, 0x9b, 0x6e, + 0x6b, 0xa6, 0xc8, 0xbb, 0xed, 0xd6, 0x70, 0xdd, 0x56, 0x4f, 0x51, 0xad, 0x9d, 0x67, 0x22, 0xa5, + 0x4b, 0xb1, 0x8f, 0x3b, 0xf9, 0x20, 0x94, 0x65, 0x8b, 0xd2, 0x70, 0x66, 0xec, 0x72, 0xe1, 0x4a, + 0xb9, 0x76, 0x66, 0x7f, 0x6f, 0xae, 0xbc, 0xac, 0x0a, 0x31, 0x86, 0xdb, 0x4b, 0x30, 0x53, 0xed, + 0x6c, 0x38, 0x61, 0xe8, 0x34, 0xfd, 0x20, 0xd5, 0x75, 0x57, 0xa0, 0xd4, 0x71, 0xba, 0x5d, 0xd7, + 0x6b, 0xb1, 0xbe, 0x63, 0x74, 0x26, 0xf6, 0xf7, 0xe6, 0x4a, 0xab, 0xb2, 0x0c, 0x35, 0xd4, 0xfe, + 0xcf, 0x23, 0x50, 0xa9, 0x7a, 0x4e, 0x7b, 0x37, 0x74, 0x43, 0xec, 0x79, 0xe4, 0x33, 0x50, 0x62, + 0xab, 0x56, 0xd3, 0x89, 0x1c, 0x39, 0xd3, 0x3f, 0x3c, 0x2f, 0x16, 0x91, 0x79, 0x73, 0x11, 0x89, + 0x3f, 0x9f, 0x61, 0xcf, 0xef, 0x7c, 0x64, 0xfe, 0xf6, 0xc6, 0x3d, 0xda, 0x88, 0x56, 0x69, 0xe4, + 0xd4, 0x88, 0xec, 0x05, 0x88, 0xcb, 0x50, 0x53, 0x25, 0x3e, 0x8c, 0x86, 0x5d, 0xda, 0x90, 0x33, + 0x77, 0x75, 0xc8, 0x19, 0x12, 0x8b, 0x5e, 0xef, 0xd2, 0x46, 0x6d, 0x42, 0xb2, 0x1e, 0x65, 0xff, + 0x90, 0x33, 0x22, 0xf7, 0x61, 0x2c, 0xe4, 0x6b, 0x99, 0x9c, 0x94, 0xb7, 0xf3, 0x63, 0xc9, 0xc9, + 0xd6, 0x26, 0x25, 0xd3, 0x31, 0xf1, 0x1f, 0x25, 0x3b, 0xfb, 0xbf, 0x58, 0x70, 0xce, 0xc0, 0xae, + 0x06, 0xad, 0x5e, 0x87, 0x7a, 0x11, 0xb9, 0x0c, 0xa3, 0x9e, 0xd3, 0xa1, 0x72, 0x56, 0x69, 0x91, + 0x6f, 0x39, 0x1d, 0x8a, 0x1c, 0x42, 0x9e, 0x82, 0xe2, 0x8e, 0xd3, 0xee, 0x51, 0xde, 0x48, 0xe5, + 0xda, 0x19, 0x89, 0x52, 0x7c, 0x9d, 0x15, 0xa2, 0x80, 0x91, 0xb7, 0xa0, 0xcc, 0x7f, 0x5c, 0x0f, + 0xfc, 0x4e, 0x4e, 0x9f, 0x26, 0x25, 0x7c, 0x5d, 0x91, 0x15, 0xc3, 0x4f, 0xff, 0xc5, 0x98, 0xa1, + 0xfd, 0x47, 0x16, 0x4c, 0x19, 0x1f, 0xb7, 0xe2, 0x86, 0x11, 0xf9, 0x74, 0xdf, 0xe0, 0x99, 0x3f, + 0xda, 0xe0, 0x61, 0xb5, 0xf9, 0xd0, 0x99, 0x96, 0x5f, 0x5a, 0x52, 0x25, 0xc6, 0xc0, 0xf1, 0xa0, + 0xe8, 0x46, 0xb4, 0x13, 0xce, 0x8c, 0x5c, 0x2e, 0x5c, 0xa9, 0x5c, 0x5d, 0xce, 0xad, 0x1b, 0xe3, + 0xf6, 0x5d, 0x66, 0xf4, 0x51, 0xb0, 0xb1, 0xbf, 0x55, 0x48, 0x74, 0xdf, 0xaa, 0x92, 0xe3, 0x1d, + 0x0b, 0xc6, 0xda, 0xce, 0x06, 0x6d, 0x8b, 0xb9, 0x55, 0xb9, 0xfa, 0x46, 0x6e, 0x92, 0x28, 0x1e, + 0xf3, 0x2b, 0x9c, 0xfe, 0x35, 0x2f, 0x0a, 0x76, 0xe3, 0xe1, 0x25, 0x0a, 0x51, 0x32, 0x27, 0x7f, + 0xdb, 0x82, 0x4a, 0xbc, 0xaa, 0xa9, 0x66, 0xd9, 0xc8, 0x5f, 0x98, 0x78, 0x31, 0x95, 0x12, 0xe9, + 0x25, 0xda, 0x80, 0xa0, 0x29, 0xcb, 0xec, 0x47, 0xa1, 0x62, 0x7c, 0x02, 0x99, 0x86, 0xc2, 0x36, + 0xdd, 0x15, 0x03, 0x1e, 0xd9, 0x4f, 0x72, 0x3e, 0x31, 0xc2, 0xe5, 0x90, 0xfe, 0xd8, 0xc8, 0x8b, + 0xd6, 0xec, 0xcb, 0x30, 0x9d, 0x66, 0x78, 0x9c, 0xfa, 0xf6, 0x3f, 0x2b, 0x26, 0x06, 0x26, 0x5b, + 0x08, 0x88, 0x0f, 0xe3, 0x1d, 0x1a, 0x05, 0x6e, 0x43, 0x75, 0xd9, 0xd2, 0x70, 0xad, 0xb4, 0xca, + 0x89, 0xc5, 0x1b, 0xa2, 0xf8, 0x1f, 0xa2, 0xe2, 0x42, 0xb6, 0x60, 0xd4, 0x09, 0x5a, 0xaa, 0x4f, + 0xae, 0xe7, 0x33, 0x2d, 0xe3, 0xa5, 0xa2, 0x1a, 0xb4, 0x42, 0xe4, 0x1c, 0xc8, 0x02, 0x94, 0x23, + 0x1a, 0x74, 0x5c, 0xcf, 0x89, 0xc4, 0x0e, 0x5a, 0xaa, 0x9d, 0x95, 0x68, 0xe5, 0x75, 0x05, 0xc0, + 0x18, 0x87, 0xb4, 0x61, 0xac, 0x19, 0xec, 0x62, 0xcf, 0x9b, 0x19, 0xcd, 0xa3, 0x29, 0x96, 0x38, + 0xad, 0x78, 0x90, 0x8a, 0xff, 0x28, 0x79, 0x90, 0xdf, 0xb4, 0xe0, 0x7c, 0x87, 0x3a, 0x61, 0x2f, + 0xa0, 0xec, 0x13, 0x90, 0x46, 0xd4, 0x63, 0x1d, 0x3b, 0x53, 0xe4, 0xcc, 0x71, 0xd8, 0x7e, 0xe8, + 0xa7, 0x5c, 0x7b, 0x52, 0x8a, 0x72, 0x3e, 0x0b, 0x8a, 0x99, 0xd2, 0x90, 0xb7, 0xa0, 0x12, 0x45, + 0xed, 0x7a, 0xc4, 0xf4, 0xe0, 0xd6, 0xee, 0xcc, 0x18, 0x5f, 0xbc, 0x86, 0x5c, 0x61, 0xd6, 0xd7, + 0x57, 0x14, 0xc1, 0xda, 0x14, 0x9b, 0x2d, 0x46, 0x01, 0x9a, 0xec, 0xec, 0x7f, 0x59, 0x84, 0xb3, + 0x7d, 0xdb, 0x0a, 0x79, 0x1e, 0x8a, 0xdd, 0x2d, 0x27, 0x54, 0xfb, 0xc4, 0x25, 0xb5, 0x48, 0xad, + 0xb1, 0xc2, 0x77, 0xf7, 0xe6, 0xce, 0xa8, 0x2a, 0xbc, 0x00, 0x05, 0x32, 0xd3, 0xda, 0x3a, 0x34, + 0x0c, 0x9d, 0x96, 0xda, 0x3c, 0x8c, 0x41, 0xca, 0x8b, 0x51, 0xc1, 0xc9, 0x2f, 0x59, 0x70, 0x46, + 0x0c, 0x58, 0xa4, 0x61, 0xaf, 0x1d, 0xb1, 0x0d, 0x92, 0x75, 0xca, 0xcd, 0x3c, 0x26, 0x87, 0x20, + 0x59, 0xbb, 0x20, 0xb9, 0x9f, 0x31, 0x4b, 0x43, 0x4c, 0xf2, 0x25, 0x77, 0xa1, 0x1c, 0x46, 0x4e, + 0x10, 0xd1, 0x66, 0x35, 0xe2, 0xaa, 0x5c, 0xe5, 0xea, 0x4f, 0x1f, 0x6d, 0xe7, 0x58, 0x77, 0x3b, + 0x54, 0xec, 0x52, 0x75, 0x45, 0x00, 0x63, 0x5a, 0xe4, 0x2d, 0x80, 0xa0, 0xe7, 0xd5, 0x7b, 0x9d, + 0x8e, 0x13, 0xec, 0x4a, 0xed, 0xee, 0xc6, 0x70, 0x9f, 0x87, 0x9a, 0x5e, 0xac, 0xe8, 0xc4, 0x65, + 0x68, 0xf0, 0x23, 0x5f, 0xb0, 0xe0, 0x8c, 0x98, 0x07, 0x4a, 0x82, 0xb1, 0x9c, 0x25, 0x38, 0xcb, + 0x9a, 0x76, 0xc9, 0x64, 0x81, 0x49, 0x8e, 0xe4, 0x0d, 0xa8, 0x34, 0xfc, 0x4e, 0xb7, 0x4d, 0x45, + 0xe3, 0x8e, 0x1f, 0xbb, 0x71, 0xf9, 0xd0, 0x5d, 0x8c, 0x49, 0xa0, 0x49, 0xcf, 0xfe, 0x8f, 0x49, + 0x1d, 0x47, 0x0d, 0x69, 0xf2, 0x29, 0x78, 0x3c, 0xec, 0x35, 0x1a, 0x34, 0x0c, 0x37, 0x7b, 0x6d, + 0xec, 0x79, 0x37, 0xdc, 0x30, 0xf2, 0x83, 0xdd, 0x15, 0xb7, 0xe3, 0x46, 0x7c, 0x40, 0x17, 0x6b, + 0x17, 0xf7, 0xf7, 0xe6, 0x1e, 0xaf, 0x0f, 0x42, 0xc2, 0xc1, 0xf5, 0x89, 0x03, 0x4f, 0xf4, 0xbc, + 0xc1, 0xe4, 0xc5, 0xf1, 0x63, 0x6e, 0x7f, 0x6f, 0xee, 0x89, 0x3b, 0x83, 0xd1, 0xf0, 0x20, 0x1a, + 0xf6, 0x9f, 0x58, 0x6c, 0x1b, 0x12, 0xdf, 0xb5, 0x4e, 0x3b, 0xdd, 0x36, 0x5b, 0x3a, 0x4f, 0x5f, + 0x39, 0x8e, 0x12, 0xca, 0x31, 0xe6, 0xb3, 0x97, 0x2b, 0xf9, 0x07, 0x69, 0xc8, 0xf6, 0x7f, 0xb7, + 0xe0, 0x7c, 0x1a, 0xf9, 0x21, 0x28, 0x74, 0x61, 0x52, 0xa1, 0xbb, 0x95, 0xef, 0xd7, 0x0e, 0xd0, + 0xea, 0x7e, 0xd9, 0x18, 0xb0, 0x0a, 0x15, 0xe9, 0x26, 0x79, 0x11, 0x26, 0x22, 0xf9, 0xf7, 0x56, + 0xac, 0x9c, 0x6b, 0xc3, 0xc4, 0xba, 0x01, 0xc3, 0x04, 0x26, 0xab, 0xd9, 0x68, 0xf7, 0xc2, 0x88, + 0x06, 0xf5, 0x86, 0xdf, 0x15, 0xcb, 0x6e, 0x29, 0xae, 0xb9, 0x68, 0xc0, 0x30, 0x81, 0x69, 0xff, + 0x8d, 0x62, 0x7f, 0xbb, 0xff, 0xff, 0xae, 0xaf, 0xc4, 0xea, 0x47, 0xe1, 0xbd, 0x54, 0x3f, 0x46, + 0xdf, 0x57, 0xea, 0xc7, 0x17, 0x2d, 0xa6, 0xc5, 0x89, 0x01, 0x10, 0x4a, 0xd5, 0xe8, 0xb5, 0x7c, + 0xa7, 0x03, 0xd2, 0x4d, 0x53, 0x31, 0x94, 0xbc, 0x30, 0x66, 0x6b, 0xff, 0xa3, 0x51, 0x98, 0xa8, + 0x7a, 0x91, 0x5b, 0xdd, 0xdc, 0x74, 0x3d, 0x37, 0xda, 0x25, 0x5f, 0x1d, 0x81, 0x85, 0x6e, 0x40, + 0x37, 0x69, 0x10, 0xd0, 0xe6, 0x52, 0x2f, 0x70, 0xbd, 0x56, 0xbd, 0xb1, 0x45, 0x9b, 0xbd, 0xb6, + 0xeb, 0xb5, 0x96, 0x5b, 0x9e, 0xaf, 0x8b, 0xaf, 0x3d, 0xa0, 0x8d, 0x1e, 0x6f, 0x57, 0xb1, 0x4a, + 0x74, 0x86, 0x93, 0x7d, 0xed, 0x78, 0x4c, 0x6b, 0xcf, 0xed, 0xef, 0xcd, 0x2d, 0x1c, 0xb3, 0x12, + 0x1e, 0xf7, 0xd3, 0xc8, 0x97, 0x47, 0x60, 0x3e, 0xa0, 0x9f, 0xed, 0xb9, 0x47, 0x6f, 0x0d, 0xb1, + 0x8c, 0xb7, 0x87, 0xdc, 0xee, 0x8f, 0xc5, 0xb3, 0x76, 0x75, 0x7f, 0x6f, 0xee, 0x98, 0x75, 0xf0, + 0x98, 0xdf, 0x65, 0xaf, 0x41, 0xa5, 0xda, 0x75, 0x43, 0xf7, 0x01, 0xfa, 0xbd, 0x88, 0x1e, 0xc1, + 0xa0, 0x31, 0x07, 0xc5, 0xa0, 0xd7, 0xa6, 0x62, 0x81, 0x29, 0xd7, 0xca, 0x6c, 0x59, 0x46, 0x56, + 0x80, 0xa2, 0xdc, 0xfe, 0x22, 0xdb, 0x82, 0x38, 0xc9, 0x94, 0x29, 0xeb, 0x1e, 0x14, 0x03, 0xc6, + 0x44, 0x8e, 0xac, 0x61, 0x4f, 0xfd, 0xb1, 0xd4, 0x52, 0x08, 0xf6, 0x13, 0x05, 0x0b, 0xfb, 0xdb, + 0x23, 0x70, 0xa1, 0xda, 0xed, 0xae, 0xd2, 0x70, 0x2b, 0x25, 0xc5, 0xaf, 0x58, 0x30, 0xb9, 0xe3, + 0x06, 0x51, 0xcf, 0x69, 0x2b, 0x6b, 0xa5, 0x90, 0xa7, 0x3e, 0xac, 0x3c, 0x9c, 0xdb, 0xeb, 0x09, + 0xd2, 0x35, 0xb2, 0xbf, 0x37, 0x37, 0x99, 0x2c, 0xc3, 0x14, 0x7b, 0xf2, 0xb7, 0x2c, 0x98, 0x96, + 0x45, 0xb7, 0xfc, 0x26, 0x35, 0xad, 0xe1, 0x77, 0xf2, 0x94, 0x49, 0x13, 0x17, 0x56, 0xcc, 0x74, + 0x29, 0xf6, 0x09, 0x61, 0xff, 0xcf, 0x11, 0x78, 0x6c, 0x00, 0x0d, 0xf2, 0x5b, 0x16, 0x9c, 0x17, + 0x26, 0x74, 0x03, 0x84, 0x74, 0x53, 0xb6, 0xe6, 0x27, 0xf2, 0x96, 0x1c, 0xd9, 0x14, 0xa7, 0x5e, + 0x83, 0xd6, 0x66, 0xd8, 0x92, 0xbc, 0x98, 0xc1, 0x1a, 0x33, 0x05, 0xe2, 0x92, 0x0a, 0xa3, 0x7a, + 0x4a, 0xd2, 0x91, 0x87, 0x22, 0x69, 0x3d, 0x83, 0x35, 0x66, 0x0a, 0x64, 0xff, 0x35, 0x78, 0xe2, + 0x00, 0x72, 0x87, 0x4f, 0x4e, 0xfb, 0x0d, 0x3d, 0xea, 0x93, 0x63, 0xee, 0x08, 0xf3, 0xda, 0x86, + 0x31, 0x3e, 0x75, 0xd4, 0xc4, 0x06, 0xb6, 0x07, 0xf3, 0x39, 0x15, 0xa2, 0x84, 0xd8, 0xdf, 0xb6, + 0xa0, 0x74, 0x0c, 0xdb, 0xe7, 0x5c, 0xd2, 0xf6, 0x59, 0xee, 0xb3, 0x7b, 0x46, 0xfd, 0x76, 0xcf, + 0x57, 0x86, 0xeb, 0x8d, 0xa3, 0xd8, 0x3b, 0x7f, 0x64, 0xc1, 0xd9, 0x3e, 0xfb, 0x28, 0xd9, 0x82, + 0xf3, 0x5d, 0xbf, 0xa9, 0xb6, 0xd3, 0x1b, 0x4e, 0xb8, 0xc5, 0x61, 0xf2, 0xf3, 0x9e, 0x67, 0x3d, + 0xb9, 0x96, 0x01, 0x7f, 0x77, 0x6f, 0x6e, 0x46, 0x13, 0x49, 0x21, 0x60, 0x26, 0x45, 0xd2, 0x85, + 0xd2, 0xa6, 0x4b, 0xdb, 0xcd, 0x78, 0x08, 0x0e, 0xa9, 0xa5, 0x5d, 0x97, 0xd4, 0xc4, 0xd5, 0x80, + 0xfa, 0x87, 0x9a, 0x8b, 0xfd, 0x63, 0x0b, 0x26, 0xab, 0xbd, 0x68, 0x8b, 0xe9, 0x28, 0x0d, 0x6e, + 0x8d, 0x23, 0x1e, 0x14, 0x43, 0xb7, 0xb5, 0xf3, 0x7c, 0x3e, 0x8b, 0x71, 0x9d, 0x91, 0x92, 0x57, + 0x24, 0x5a, 0x59, 0xe7, 0x85, 0x28, 0xd8, 0x90, 0x00, 0xc6, 0x7c, 0xa7, 0x17, 0x6d, 0x5d, 0x95, + 0x9f, 0x3c, 0xa4, 0x65, 0xe2, 0x36, 0xfb, 0x9c, 0xab, 0x92, 0xa3, 0x56, 0x19, 0x45, 0x29, 0x4a, + 0x4e, 0xf6, 0xe7, 0x61, 0x32, 0x79, 0xef, 0x76, 0x84, 0x31, 0x7b, 0x11, 0x0a, 0x4e, 0xe0, 0xc9, + 0x11, 0x5b, 0x91, 0x08, 0x85, 0x2a, 0xde, 0x42, 0x56, 0x4e, 0x9e, 0x85, 0xd2, 0x66, 0xaf, 0xdd, + 0xe6, 0xe7, 0x0a, 0x71, 0xc9, 0xa5, 0x8f, 0x45, 0xd7, 0x65, 0x39, 0x6a, 0x0c, 0xfb, 0xff, 0x8c, + 0xc2, 0x54, 0xad, 0xdd, 0xa3, 0xaf, 0x04, 0x94, 0x2a, 0x5b, 0x50, 0x15, 0xa6, 0xba, 0x01, 0xdd, + 0x71, 0xe9, 0xfd, 0x3a, 0x6d, 0xd3, 0x46, 0xe4, 0x07, 0x52, 0x9a, 0xc7, 0x24, 0xa1, 0xa9, 0xb5, + 0x24, 0x18, 0xd3, 0xf8, 0xe4, 0x65, 0x98, 0x74, 0x1a, 0x91, 0xbb, 0x43, 0x35, 0x05, 0x21, 0xee, + 0xa3, 0x92, 0xc2, 0x64, 0x35, 0x01, 0xc5, 0x14, 0x36, 0xf9, 0x34, 0xcc, 0x84, 0x0d, 0xa7, 0x4d, + 0xef, 0x74, 0x25, 0xab, 0xc5, 0x2d, 0xda, 0xd8, 0x5e, 0xf3, 0x5d, 0x2f, 0x92, 0x76, 0xc7, 0xcb, + 0x92, 0xd2, 0x4c, 0x7d, 0x00, 0x1e, 0x0e, 0xa4, 0x40, 0xfe, 0x95, 0x05, 0x17, 0xbb, 0x01, 0x5d, + 0x0b, 0xfc, 0x8e, 0xcf, 0x86, 0x5a, 0x9f, 0x39, 0x4c, 0x9a, 0x85, 0x5e, 0x1f, 0x52, 0x97, 0x12, + 0x25, 0xfd, 0x77, 0x38, 0x1f, 0xd8, 0xdf, 0x9b, 0xbb, 0xb8, 0x76, 0x90, 0x00, 0x78, 0xb0, 0x7c, + 0xe4, 0xdf, 0x58, 0x70, 0xa9, 0xeb, 0x87, 0xd1, 0x01, 0x9f, 0x50, 0x3c, 0xd5, 0x4f, 0xb0, 0xf7, + 0xf7, 0xe6, 0x2e, 0xad, 0x1d, 0x28, 0x01, 0x1e, 0x22, 0xa1, 0xbd, 0x5f, 0x81, 0xb3, 0xc6, 0xd8, + 0x93, 0xc6, 0x9c, 0x97, 0xe0, 0x8c, 0x1a, 0x0c, 0xb1, 0xee, 0x53, 0x8e, 0x6d, 0x7b, 0x55, 0x13, + 0x88, 0x49, 0x5c, 0x36, 0xee, 0xf4, 0x50, 0x14, 0xb5, 0x53, 0xe3, 0x6e, 0x2d, 0x01, 0xc5, 0x14, + 0x36, 0x59, 0x86, 0x73, 0xb2, 0x04, 0x69, 0xb7, 0xed, 0x36, 0x9c, 0x45, 0xbf, 0x27, 0x87, 0x5c, + 0xb1, 0xf6, 0xd8, 0xfe, 0xde, 0xdc, 0xb9, 0xb5, 0x7e, 0x30, 0x66, 0xd5, 0x21, 0x2b, 0x70, 0xde, + 0xe9, 0x45, 0xbe, 0xfe, 0xfe, 0x6b, 0x1e, 0xdb, 0x4e, 0x9b, 0x7c, 0x68, 0x95, 0xc4, 0xbe, 0x5b, + 0xcd, 0x80, 0x63, 0x66, 0x2d, 0xb2, 0x96, 0xa2, 0x56, 0xa7, 0x0d, 0xdf, 0x6b, 0x8a, 0x5e, 0x2e, + 0xc6, 0xc7, 0xc0, 0x6a, 0x06, 0x0e, 0x66, 0xd6, 0x24, 0x6d, 0x98, 0xec, 0x38, 0x0f, 0xee, 0x78, + 0xce, 0x8e, 0xe3, 0xb6, 0x19, 0x13, 0x69, 0x2f, 0x1c, 0x6c, 0x65, 0xea, 0x45, 0x6e, 0x7b, 0x5e, + 0xf8, 0x71, 0xcc, 0x2f, 0x7b, 0xd1, 0xed, 0xa0, 0x1e, 0x31, 0x4d, 0x5d, 0x68, 0x90, 0xab, 0x09, + 0x5a, 0x98, 0xa2, 0x4d, 0x6e, 0xc3, 0x05, 0x3e, 0x1d, 0x97, 0xfc, 0xfb, 0xde, 0x12, 0x6d, 0x3b, + 0xbb, 0xea, 0x03, 0xc6, 0xf9, 0x07, 0x3c, 0xbe, 0xbf, 0x37, 0x77, 0xa1, 0x9e, 0x85, 0x80, 0xd9, + 0xf5, 0x88, 0x03, 0x4f, 0x24, 0x01, 0x48, 0x77, 0xdc, 0xd0, 0xf5, 0x3d, 0x61, 0x96, 0x2b, 0xc5, + 0x66, 0xb9, 0xfa, 0x60, 0x34, 0x3c, 0x88, 0x06, 0xf9, 0xbb, 0x16, 0x9c, 0xcf, 0x9a, 0x86, 0x33, + 0xe5, 0x3c, 0x6e, 0x93, 0x53, 0x53, 0x4b, 0x8c, 0x88, 0xcc, 0x45, 0x21, 0x53, 0x08, 0xf2, 0xb6, + 0x05, 0x13, 0x8e, 0x71, 0x82, 0x9e, 0x81, 0x3c, 0x76, 0x2d, 0xf3, 0x4c, 0x5e, 0x9b, 0xde, 0xdf, + 0x9b, 0x4b, 0x9c, 0xd2, 0x31, 0xc1, 0x91, 0xfc, 0x7d, 0x0b, 0x2e, 0x64, 0xce, 0xf1, 0x99, 0xca, + 0x69, 0xb4, 0x10, 0x1f, 0x24, 0xd9, 0x6b, 0x4e, 0xb6, 0x18, 0xe4, 0xeb, 0x96, 0xde, 0xca, 0xd4, + 0x05, 0xe3, 0xcc, 0x04, 0x17, 0x6d, 0x48, 0x83, 0x87, 0xa1, 0x46, 0x29, 0xc2, 0xb5, 0x73, 0xc6, + 0xce, 0xa8, 0x0a, 0x31, 0xcd, 0x9e, 0x7c, 0xcd, 0x52, 0x5b, 0xa3, 0x96, 0xe8, 0xcc, 0x69, 0x49, + 0x44, 0xe2, 0x9d, 0x56, 0x0b, 0x94, 0x62, 0x4e, 0x7e, 0x0e, 0x66, 0x9d, 0x0d, 0x3f, 0x88, 0x32, + 0x27, 0xdf, 0xcc, 0x24, 0x9f, 0x46, 0x97, 0xf6, 0xf7, 0xe6, 0x66, 0xab, 0x03, 0xb1, 0xf0, 0x00, + 0x0a, 0xf6, 0x77, 0xc7, 0x60, 0x42, 0x9c, 0x84, 0xe4, 0xd6, 0xf5, 0xbb, 0x16, 0x3c, 0xd9, 0xe8, + 0x05, 0x01, 0xf5, 0xa2, 0x7a, 0x44, 0xbb, 0xfd, 0x1b, 0x97, 0x75, 0xaa, 0x1b, 0xd7, 0xe5, 0xfd, + 0xbd, 0xb9, 0x27, 0x17, 0x0f, 0xe0, 0x8f, 0x07, 0x4a, 0x47, 0xfe, 0x83, 0x05, 0xb6, 0x44, 0xa8, + 0x39, 0x8d, 0xed, 0x56, 0xe0, 0xf7, 0xbc, 0x66, 0xff, 0x47, 0x8c, 0x9c, 0xea, 0x47, 0x3c, 0xbd, + 0xbf, 0x37, 0x67, 0x2f, 0x1e, 0x2a, 0x05, 0x1e, 0x41, 0x52, 0xf2, 0x0a, 0x9c, 0x95, 0x58, 0xd7, + 0x1e, 0x74, 0x69, 0xe0, 0xb2, 0x33, 0x87, 0x54, 0x1c, 0x63, 0xdf, 0xb4, 0x34, 0x02, 0xf6, 0xd7, + 0x21, 0x21, 0x8c, 0xdf, 0xa7, 0x6e, 0x6b, 0x2b, 0x52, 0xea, 0xd3, 0x90, 0x0e, 0x69, 0xd2, 0x2a, + 0x72, 0x57, 0xd0, 0xac, 0x55, 0xf6, 0xf7, 0xe6, 0xc6, 0xe5, 0x1f, 0x54, 0x9c, 0xc8, 0x2d, 0x98, + 0x14, 0xe7, 0xd4, 0x35, 0xd7, 0x6b, 0xad, 0xf9, 0x9e, 0xf0, 0xaa, 0x2a, 0xd7, 0x9e, 0x56, 0x1b, + 0x7e, 0x3d, 0x01, 0x7d, 0x77, 0x6f, 0x6e, 0x42, 0xfd, 0x5e, 0xdf, 0xed, 0x52, 0x4c, 0xd5, 0x26, + 0x7f, 0xc7, 0x02, 0x12, 0x46, 0xb4, 0xbb, 0xd6, 0xee, 0xb5, 0x5c, 0xd9, 0x44, 0xd2, 0x3f, 0x2a, + 0x07, 0x57, 0xad, 0x24, 0xdd, 0xda, 0xac, 0x14, 0x92, 0xd4, 0xfb, 0x38, 0x62, 0x86, 0x14, 0xf6, + 0xb7, 0xc6, 0x01, 0xd4, 0x5c, 0xa2, 0x5d, 0xf2, 0x41, 0x28, 0x87, 0x34, 0x12, 0x4d, 0x22, 0xaf, + 0xb9, 0xc4, 0xe5, 0xa4, 0x2a, 0xc4, 0x18, 0x4e, 0xb6, 0xa1, 0xd8, 0x75, 0x7a, 0x21, 0xcd, 0xe7, + 0x70, 0x23, 0x47, 0xe6, 0x1a, 0xa3, 0x28, 0x4e, 0xcd, 0xfc, 0x27, 0x0a, 0x1e, 0xe4, 0x4b, 0x16, + 0x00, 0x4d, 0x8e, 0xa6, 0xa1, 0xad, 0x57, 0x92, 0x65, 0x3c, 0xe0, 0x58, 0x1b, 0xd4, 0x26, 0xf7, + 0xf7, 0xe6, 0xc0, 0x18, 0x97, 0x06, 0x5b, 0x72, 0x1f, 0x4a, 0x8e, 0xda, 0x90, 0x46, 0x4f, 0x63, + 0x43, 0xe2, 0x87, 0x59, 0x3d, 0xa3, 0x34, 0x33, 0xf2, 0x65, 0x0b, 0x26, 0x43, 0x1a, 0xc9, 0xae, + 0x62, 0xcb, 0xa2, 0xd4, 0xc6, 0x87, 0x9c, 0x11, 0xf5, 0x04, 0x4d, 0xb1, 0xbc, 0x27, 0xcb, 0x30, + 0xc5, 0x57, 0x89, 0x72, 0x83, 0x3a, 0x4d, 0x1a, 0x70, 0x5b, 0x89, 0x54, 0xf3, 0x86, 0x17, 0xc5, + 0xa0, 0xa9, 0x45, 0x31, 0xca, 0x30, 0xc5, 0x57, 0x89, 0xb2, 0xea, 0x06, 0x81, 0x2f, 0x45, 0x29, + 0xe5, 0x24, 0x8a, 0x41, 0x53, 0x8b, 0x62, 0x94, 0x61, 0x8a, 0x2f, 0x69, 0xc3, 0x58, 0x97, 0x4f, + 0x2d, 0xa9, 0xca, 0x0d, 0x79, 0x47, 0xae, 0xa6, 0x29, 0xed, 0x0a, 0x9b, 0x94, 0xf8, 0x8f, 0x92, + 0x87, 0xfd, 0xcd, 0x33, 0x30, 0xa9, 0xa6, 0x6d, 0x7c, 0xc8, 0x11, 0x86, 0xc0, 0x01, 0x87, 0x9c, + 0x45, 0x13, 0x88, 0x49, 0x5c, 0x56, 0x59, 0xac, 0x5a, 0xc9, 0x33, 0x8e, 0xae, 0x5c, 0x37, 0x81, + 0x98, 0xc4, 0x25, 0x1d, 0x28, 0xb2, 0x95, 0x45, 0xb9, 0x5f, 0x0c, 0xf9, 0xe5, 0xf1, 0x6a, 0x64, + 0x18, 0x55, 0x18, 0x79, 0x14, 0x5c, 0xb8, 0x2d, 0x3b, 0x4a, 0x98, 0xb7, 0xe5, 0x54, 0xcc, 0x67, + 0x35, 0x48, 0x5a, 0xce, 0x45, 0xdf, 0x27, 0xcb, 0x30, 0xc5, 0x3e, 0xe3, 0xdc, 0x53, 0x3c, 0xc5, + 0x73, 0xcf, 0x27, 0xa1, 0xd4, 0x71, 0x1e, 0xd4, 0x7b, 0x41, 0xeb, 0xe4, 0xe7, 0x2b, 0xe9, 0x4e, + 0x2b, 0xa8, 0xa0, 0xa6, 0x47, 0xbe, 0x60, 0x19, 0x0b, 0x9c, 0xf0, 0xb5, 0xb8, 0x9b, 0xef, 0x02, + 0xa7, 0xd5, 0x86, 0x81, 0x4b, 0x5d, 0xdf, 0x29, 0xa4, 0xf4, 0xd0, 0x4f, 0x21, 0x4c, 0xa3, 0x16, + 0x13, 0x44, 0x6b, 0xd4, 0xe5, 0x53, 0xd5, 0xa8, 0x17, 0x13, 0xcc, 0x30, 0xc5, 0x9c, 0xcb, 0x23, + 0xe6, 0x9c, 0x96, 0x07, 0x4e, 0x55, 0x9e, 0x7a, 0x82, 0x19, 0xa6, 0x98, 0x0f, 0x3e, 0x7a, 0x57, + 0x4e, 0xe7, 0xe8, 0x3d, 0x91, 0xc3, 0xd1, 0xfb, 0xe0, 0x53, 0xc9, 0x99, 0x61, 0x4f, 0x25, 0xe4, + 0x26, 0x90, 0xe6, 0xae, 0xe7, 0x74, 0xdc, 0x86, 0x5c, 0x2c, 0xf9, 0x26, 0x3d, 0xc9, 0x4d, 0x33, + 0x5a, 0x2b, 0x5b, 0xea, 0xc3, 0xc0, 0x8c, 0x5a, 0x24, 0x82, 0x52, 0x57, 0x29, 0x9f, 0x53, 0x79, + 0x8c, 0x7e, 0xa5, 0x8c, 0x0a, 0x17, 0x1a, 0x36, 0xf1, 0x54, 0x09, 0x6a, 0x4e, 0x64, 0x05, 0xce, + 0x77, 0x5c, 0x6f, 0xcd, 0x6f, 0x86, 0x6b, 0x34, 0x90, 0x86, 0xa7, 0x3a, 0x8d, 0x66, 0xa6, 0x79, + 0xdb, 0x70, 0x63, 0xc2, 0x6a, 0x06, 0x1c, 0x33, 0x6b, 0xd9, 0xff, 0xdb, 0x82, 0xe9, 0xc5, 0xb6, + 0xdf, 0x6b, 0xde, 0x75, 0xa2, 0xc6, 0x96, 0xf0, 0xd8, 0x20, 0x2f, 0x43, 0xc9, 0xf5, 0x22, 0x1a, + 0xec, 0x38, 0x6d, 0xb9, 0x3f, 0xd9, 0xca, 0x92, 0xbc, 0x2c, 0xcb, 0xdf, 0xdd, 0x9b, 0x9b, 0x5c, + 0xea, 0x05, 0xdc, 0x60, 0x2f, 0x56, 0x2b, 0xd4, 0x75, 0xc8, 0x37, 0x2d, 0x38, 0x2b, 0x7c, 0x3e, + 0x96, 0x9c, 0xc8, 0x79, 0xad, 0x47, 0x03, 0x97, 0x2a, 0xaf, 0x8f, 0x21, 0x17, 0xaa, 0xb4, 0xac, + 0x8a, 0xc1, 0x6e, 0x7c, 0x66, 0x59, 0x4d, 0x73, 0xc6, 0x7e, 0x61, 0xec, 0x5f, 0x2b, 0xc0, 0xe3, + 0x03, 0x69, 0x91, 0x59, 0x18, 0x71, 0x9b, 0xf2, 0xd3, 0x41, 0xd2, 0x1d, 0x59, 0x6e, 0xe2, 0x88, + 0xdb, 0x24, 0xf3, 0x5c, 0xc3, 0x0d, 0x68, 0x18, 0xaa, 0xbb, 0xf7, 0xb2, 0x56, 0x46, 0x65, 0x29, + 0x1a, 0x18, 0x64, 0x0e, 0x8a, 0xdc, 0x95, 0x5a, 0x1e, 0xad, 0xb8, 0xce, 0xcc, 0xbd, 0x96, 0x51, + 0x94, 0x93, 0x2f, 0x5a, 0x00, 0x42, 0x40, 0xa6, 0xef, 0xcb, 0x5d, 0x12, 0xf3, 0x6d, 0x26, 0x46, + 0x59, 0x48, 0x19, 0xff, 0x47, 0x83, 0x2b, 0x59, 0x87, 0x31, 0xa6, 0x3e, 0xfb, 0xcd, 0x13, 0x6f, + 0x8a, 0x42, 0x01, 0xe2, 0x34, 0x50, 0xd2, 0x62, 0x6d, 0x15, 0xd0, 0xa8, 0x17, 0x78, 0xac, 0x69, + 0xf9, 0x36, 0x58, 0x12, 0x52, 0xa0, 0x2e, 0x45, 0x03, 0xc3, 0xfe, 0x17, 0x23, 0x70, 0x3e, 0x4b, + 0x74, 0xb6, 0xdb, 0x8c, 0x09, 0x69, 0xa5, 0x95, 0xe0, 0x67, 0xf3, 0x6f, 0x1f, 0xe9, 0xbe, 0xa4, + 0x6f, 0x6c, 0xa4, 0x2f, 0xa9, 0xe4, 0x4b, 0x7e, 0x56, 0xb7, 0xd0, 0xc8, 0x09, 0x5b, 0x48, 0x53, + 0x4e, 0xb5, 0xd2, 0x65, 0x18, 0x0d, 0x59, 0xcf, 0x17, 0x92, 0x37, 0x3f, 0xbc, 0x8f, 0x38, 0x84, + 0x61, 0xf4, 0x3c, 0x37, 0x92, 0xf1, 0x47, 0x1a, 0xe3, 0x8e, 0xe7, 0x46, 0xc8, 0x21, 0xf6, 0x37, + 0x46, 0x60, 0x76, 0xf0, 0x47, 0x91, 0x6f, 0x58, 0x00, 0x4d, 0x76, 0x38, 0x0a, 0xb9, 0x13, 0xbf, + 0x70, 0xf7, 0x72, 0x4e, 0xab, 0x0d, 0x97, 0x14, 0xa7, 0xd8, 0x0f, 0x51, 0x17, 0x85, 0x68, 0x08, + 0x42, 0xae, 0xaa, 0xa1, 0xcf, 0x6f, 0xad, 0xc4, 0x64, 0xd2, 0x75, 0x56, 0x35, 0x04, 0x0d, 0x2c, + 0x76, 0xfa, 0xf5, 0x9c, 0x0e, 0x0d, 0xbb, 0x8e, 0x8e, 0xe6, 0xe2, 0xa7, 0xdf, 0x5b, 0xaa, 0x10, + 0x63, 0xb8, 0xdd, 0x86, 0xa7, 0x8e, 0x20, 0x67, 0x4e, 0xc1, 0x32, 0xf6, 0x9f, 0x5a, 0xf0, 0x98, + 0xf4, 0xc4, 0xfb, 0x0b, 0xe3, 0xd6, 0xf9, 0x67, 0x16, 0x3c, 0x31, 0xe0, 0x9b, 0x1f, 0x82, 0x77, + 0xe7, 0x9b, 0x49, 0xef, 0xce, 0x3b, 0xc3, 0x0e, 0xe9, 0xcc, 0xef, 0x18, 0xe0, 0xe4, 0xf9, 0xdd, + 0x02, 0x9c, 0x61, 0xcb, 0x56, 0xd3, 0x6f, 0xe5, 0xb4, 0x71, 0x3e, 0x05, 0xc5, 0xcf, 0xb2, 0x0d, + 0x28, 0x3d, 0xc8, 0xf8, 0xae, 0x84, 0x02, 0x46, 0xbe, 0x64, 0xc1, 0xf8, 0x67, 0xe5, 0x9e, 0x2a, + 0xce, 0x72, 0x43, 0x2e, 0x86, 0x89, 0x6f, 0x98, 0x97, 0x3b, 0xa4, 0x88, 0xc1, 0xd1, 0xbe, 0x9c, + 0x6a, 0x2b, 0x55, 0x9c, 0xc9, 0x33, 0x30, 0xbe, 0xe9, 0x07, 0x9d, 0x5e, 0xdb, 0x49, 0x07, 0x7e, + 0x5e, 0x17, 0xc5, 0xa8, 0xe0, 0x6c, 0x92, 0x3b, 0x5d, 0xf7, 0x75, 0x1a, 0x84, 0x22, 0x24, 0x23, + 0x31, 0xc9, 0xab, 0x1a, 0x82, 0x06, 0x16, 0xaf, 0xd3, 0x6a, 0x05, 0xb4, 0xe5, 0x44, 0x7e, 0xc0, + 0x77, 0x0e, 0xb3, 0x8e, 0x86, 0xa0, 0x81, 0x35, 0xfb, 0x31, 0x98, 0x30, 0x85, 0x3f, 0x56, 0x3c, + 0xcf, 0xc7, 0x41, 0x3a, 0x75, 0xa6, 0x96, 0x24, 0xeb, 0x28, 0x4b, 0x92, 0xfd, 0x9f, 0x46, 0xc0, + 0xb0, 0x45, 0x3d, 0x84, 0xa9, 0xee, 0x25, 0xa6, 0xfa, 0x90, 0x76, 0x14, 0xc3, 0xb2, 0x36, 0x28, + 0xba, 0x71, 0x27, 0x15, 0xdd, 0x78, 0x2b, 0x37, 0x8e, 0x07, 0x07, 0x37, 0xfe, 0xc0, 0x82, 0x27, + 0x62, 0xe4, 0x7e, 0x1b, 0xf6, 0xe1, 0xeb, 0xf6, 0x0b, 0x50, 0x71, 0xe2, 0x6a, 0x72, 0x62, 0x19, + 0xa1, 0x65, 0x1a, 0x84, 0x26, 0x5e, 0x1c, 0x16, 0x53, 0x38, 0x61, 0x58, 0xcc, 0xe8, 0xc1, 0x61, + 0x31, 0xf6, 0x8f, 0x47, 0xe0, 0x62, 0xff, 0x97, 0x99, 0xbe, 0xe2, 0x87, 0x7f, 0x5b, 0xda, 0x9b, + 0x7c, 0xe4, 0xc4, 0xde, 0xe4, 0x85, 0xa3, 0x7a, 0x93, 0x6b, 0x1f, 0xee, 0xd1, 0x53, 0xf7, 0xe1, + 0xae, 0xc3, 0x05, 0xe5, 0x30, 0x7a, 0xdd, 0x0f, 0x64, 0x6c, 0x88, 0x5a, 0x41, 0x4a, 0xb5, 0x8b, + 0xb2, 0xca, 0x05, 0xcc, 0x42, 0xc2, 0xec, 0xba, 0xf6, 0x0f, 0x0a, 0x70, 0x2e, 0x6e, 0xf6, 0x45, + 0xdf, 0x6b, 0xba, 0xdc, 0xe7, 0xe8, 0x25, 0x18, 0x8d, 0x76, 0xbb, 0xaa, 0xb1, 0xff, 0xb2, 0x12, + 0x67, 0x7d, 0xb7, 0xcb, 0x7a, 0xfb, 0xb1, 0x8c, 0x2a, 0xfc, 0x16, 0x81, 0x57, 0x22, 0x2b, 0x7a, + 0x76, 0x88, 0x1e, 0x78, 0x3e, 0x39, 0x9a, 0xdf, 0xdd, 0x9b, 0xcb, 0xc8, 0xf2, 0x30, 0xaf, 0x29, + 0x25, 0xc7, 0x3c, 0xb9, 0x07, 0x93, 0x6d, 0x27, 0x8c, 0xee, 0x74, 0x9b, 0x4e, 0x44, 0xd7, 0x5d, + 0xe9, 0xcd, 0x73, 0xbc, 0x70, 0x1a, 0xed, 0xf6, 0xb0, 0x92, 0xa0, 0x84, 0x29, 0xca, 0x64, 0x07, + 0x08, 0x2b, 0x59, 0x0f, 0x1c, 0x2f, 0x14, 0x5f, 0xc5, 0xf8, 0x1d, 0x3f, 0x36, 0x4a, 0x1f, 0x9d, + 0x57, 0xfa, 0xa8, 0x61, 0x06, 0x07, 0xf2, 0x34, 0x8c, 0x05, 0xd4, 0x09, 0xf5, 0x76, 0xa0, 0xe7, + 0x3f, 0xf2, 0x52, 0x94, 0x50, 0x73, 0x42, 0x8d, 0x1d, 0x32, 0xa1, 0xfe, 0xd0, 0x82, 0xc9, 0xb8, + 0x9b, 0x1e, 0x82, 0xea, 0xd1, 0x49, 0xaa, 0x1e, 0x37, 0xf2, 0x5a, 0x12, 0x07, 0x68, 0x1b, 0x7f, + 0x32, 0x6e, 0x7e, 0x1f, 0x0f, 0xe0, 0xf8, 0x9c, 0xe9, 0xcf, 0x6f, 0xe5, 0x11, 0x55, 0x97, 0xd0, + 0xf6, 0x0e, 0x74, 0xe4, 0x67, 0xba, 0x4e, 0x53, 0xea, 0x31, 0x72, 0xd8, 0x6b, 0x5d, 0x47, 0xe9, + 0x37, 0x59, 0xba, 0x8e, 0xaa, 0x43, 0xee, 0xc0, 0x63, 0xdd, 0xc0, 0xe7, 0x79, 0x06, 0x96, 0xa8, + 0xd3, 0x6c, 0xbb, 0x1e, 0x55, 0x66, 0x1e, 0xe1, 0x75, 0xf3, 0xc4, 0xfe, 0xde, 0xdc, 0x63, 0x6b, + 0xd9, 0x28, 0x38, 0xa8, 0x6e, 0x32, 0x52, 0x75, 0xf4, 0x08, 0x91, 0xaa, 0xbf, 0xac, 0x8d, 0xa9, + 0x3a, 0x28, 0xe2, 0x53, 0x79, 0x75, 0x65, 0x56, 0x78, 0x84, 0x1e, 0x52, 0x55, 0xc9, 0x14, 0x35, + 0xfb, 0xc1, 0x16, 0xbb, 0xb1, 0x13, 0x5a, 0xec, 0xe2, 0x38, 0x98, 0xf1, 0xf7, 0x32, 0x0e, 0xa6, + 0xf4, 0xbe, 0x8a, 0x83, 0xf9, 0xa6, 0x05, 0xe7, 0x9c, 0xfe, 0x08, 0xf4, 0x7c, 0x8c, 0xc7, 0x19, + 0xa1, 0xed, 0xb5, 0x27, 0xa4, 0x90, 0x59, 0x81, 0xfe, 0x98, 0x25, 0x8a, 0xfd, 0x4e, 0x11, 0xa6, + 0xd3, 0x4a, 0xd2, 0xe9, 0x87, 0xea, 0xfe, 0xaa, 0x05, 0xd3, 0x6a, 0x82, 0xeb, 0x1b, 0x70, 0x71, + 0xc4, 0x58, 0xc9, 0x69, 0x5d, 0x11, 0xea, 0x9e, 0xce, 0xa0, 0xb2, 0x9e, 0xe2, 0x86, 0x7d, 0xfc, + 0xc9, 0x1b, 0x50, 0xd1, 0xb7, 0x2a, 0x27, 0x8a, 0xdb, 0xe5, 0xa1, 0xa5, 0xd5, 0x98, 0x04, 0x9a, + 0xf4, 0xc8, 0x3b, 0x16, 0x40, 0x43, 0xed, 0xc4, 0x39, 0x45, 0x45, 0x65, 0x68, 0x0b, 0xb1, 0x3e, + 0xaf, 0x8b, 0x42, 0x34, 0x18, 0x93, 0x5f, 0xe3, 0xf7, 0x29, 0x7a, 0x24, 0x28, 0xcf, 0x83, 0x4f, + 0xe4, 0xbd, 0x14, 0xc5, 0xbe, 0x24, 0x5a, 0xdb, 0x33, 0x40, 0x21, 0x26, 0x84, 0xb0, 0x5f, 0x02, + 0xed, 0xb3, 0xcd, 0x56, 0x56, 0xee, 0xb5, 0xbd, 0xe6, 0x44, 0x5b, 0x72, 0x08, 0xea, 0x95, 0xf5, + 0xba, 0x02, 0x60, 0x8c, 0x63, 0x7f, 0x06, 0x26, 0x5f, 0x09, 0x9c, 0xee, 0x96, 0xcb, 0xef, 0x2d, + 0xd8, 0xf9, 0xf8, 0x19, 0x18, 0x77, 0x9a, 0xcd, 0xac, 0x64, 0x3f, 0x55, 0x51, 0x8c, 0x0a, 0x7e, + 0xa4, 0xa3, 0xb0, 0xfd, 0xef, 0x2c, 0x20, 0xf1, 0x4d, 0xb3, 0xeb, 0xb5, 0x56, 0x9d, 0xa8, 0xb1, + 0xc5, 0x8e, 0x70, 0x5b, 0xbc, 0x34, 0xeb, 0x08, 0x77, 0x43, 0x43, 0xd0, 0xc0, 0x22, 0x6f, 0x41, + 0x45, 0xfc, 0x7b, 0x5d, 0x1f, 0x10, 0x87, 0x77, 0x3d, 0xe7, 0x7b, 0x1e, 0x97, 0x49, 0x8c, 0xc2, + 0x1b, 0x31, 0x07, 0x34, 0xd9, 0xb1, 0xa6, 0x5a, 0xf6, 0x36, 0xdb, 0xbd, 0x07, 0xcd, 0x8d, 0xb8, + 0xa9, 0xba, 0x81, 0xbf, 0xe9, 0xb6, 0x69, 0xba, 0xa9, 0xd6, 0x44, 0x31, 0x2a, 0xf8, 0xd1, 0x9a, + 0xea, 0xdf, 0x5a, 0x70, 0x7e, 0x39, 0x8c, 0x5c, 0x7f, 0x89, 0x86, 0x11, 0xdb, 0xf9, 0xd8, 0xfa, + 0xd8, 0x6b, 0x1f, 0x25, 0xfc, 0x62, 0x09, 0xa6, 0xe5, 0x3d, 0x74, 0x6f, 0x23, 0xa4, 0x91, 0x71, + 0xd4, 0xd0, 0xf3, 0x78, 0x31, 0x05, 0xc7, 0xbe, 0x1a, 0x8c, 0x8a, 0xbc, 0x90, 0x8e, 0xa9, 0x14, + 0x92, 0x54, 0xea, 0x29, 0x38, 0xf6, 0xd5, 0xb0, 0xbf, 0x5f, 0x80, 0x73, 0xfc, 0x33, 0x52, 0xa1, + 0x53, 0x5f, 0x1b, 0x14, 0x3a, 0x35, 0xe4, 0x54, 0xe6, 0xbc, 0x4e, 0x10, 0x38, 0xf5, 0x37, 0x2d, + 0x98, 0x6a, 0x26, 0x5b, 0x3a, 0x1f, 0xbb, 0x5c, 0x56, 0x1f, 0x0a, 0x0f, 0xc4, 0x54, 0x21, 0xa6, + 0xf9, 0x93, 0x5f, 0xb7, 0x60, 0x2a, 0x29, 0xa6, 0x5a, 0xdd, 0x4f, 0xa1, 0x91, 0x74, 0xc8, 0x40, + 0xb2, 0x3c, 0xc4, 0xb4, 0x08, 0xf6, 0xf7, 0x46, 0x64, 0x97, 0x9e, 0x46, 0x5c, 0x10, 0xb9, 0x0f, + 0xe5, 0xa8, 0x1d, 0x8a, 0x42, 0xf9, 0xb5, 0x43, 0x1e, 0x5a, 0xd7, 0x57, 0xea, 0xc2, 0xe1, 0x24, + 0xd6, 0x2b, 0x65, 0x09, 0xd3, 0x8f, 0x15, 0x2f, 0xce, 0xb8, 0xd1, 0x95, 0x8c, 0x73, 0x39, 0x2d, + 0xaf, 0x2f, 0xae, 0xa5, 0x19, 0xcb, 0x12, 0xc6, 0x58, 0xf1, 0xb2, 0x7f, 0xdb, 0x82, 0xf2, 0x4d, + 0x5f, 0xad, 0x23, 0x3f, 0x97, 0x83, 0x2d, 0x4a, 0xab, 0xac, 0x5a, 0x69, 0x89, 0x4f, 0x41, 0x2f, + 0x27, 0x2c, 0x51, 0x4f, 0x1a, 0xb4, 0xe7, 0x79, 0xce, 0x43, 0x46, 0xea, 0xa6, 0xbf, 0x31, 0xd0, + 0x7c, 0xfc, 0x1b, 0x45, 0x38, 0xf3, 0xaa, 0xb3, 0x4b, 0xbd, 0xc8, 0x39, 0xfe, 0x26, 0xf1, 0x02, + 0x54, 0x9c, 0x2e, 0xbf, 0xcb, 0x34, 0x8e, 0x21, 0xb1, 0x71, 0x27, 0x06, 0xa1, 0x89, 0x17, 0x2f, + 0x68, 0x22, 0x48, 0x27, 0x6b, 0x29, 0x5a, 0x4c, 0xc1, 0xb1, 0xaf, 0x06, 0xb9, 0x09, 0x44, 0x06, + 0xb6, 0x57, 0x1b, 0x0d, 0xbf, 0xe7, 0x89, 0x25, 0x4d, 0xd8, 0x7d, 0xf4, 0x79, 0x78, 0xb5, 0x0f, + 0x03, 0x33, 0x6a, 0x91, 0x4f, 0xc3, 0x4c, 0x83, 0x53, 0x96, 0xa7, 0x23, 0x93, 0xa2, 0x38, 0x21, + 0xeb, 0xb0, 0x97, 0xc5, 0x01, 0x78, 0x38, 0x90, 0x02, 0x93, 0x34, 0x8c, 0xfc, 0xc0, 0x69, 0x51, + 0x93, 0xee, 0x58, 0x52, 0xd2, 0x7a, 0x1f, 0x06, 0x66, 0xd4, 0x22, 0x9f, 0x87, 0x72, 0xb4, 0x15, + 0xd0, 0x70, 0xcb, 0x6f, 0x37, 0xa5, 0xef, 0xc9, 0x90, 0xc6, 0x40, 0xd9, 0xfb, 0xeb, 0x8a, 0xaa, + 0x31, 0xbc, 0x55, 0x11, 0xc6, 0x3c, 0x49, 0x00, 0x63, 0x61, 0xc3, 0xef, 0xd2, 0x50, 0x9e, 0x2a, + 0x6e, 0xe6, 0xc2, 0x9d, 0x1b, 0xb7, 0x0c, 0x33, 0x24, 0xe7, 0x80, 0x92, 0x93, 0xfd, 0x7b, 0x23, + 0x30, 0x61, 0x22, 0x1e, 0x61, 0x6d, 0xfa, 0x92, 0x05, 0x13, 0x0d, 0xdf, 0x8b, 0x02, 0xbf, 0x1d, + 0x27, 0x6c, 0x18, 0x5e, 0xa3, 0x60, 0xa4, 0x96, 0x68, 0xe4, 0xb8, 0x6d, 0xc3, 0x5a, 0x67, 0xb0, + 0xc1, 0x04, 0x53, 0xf2, 0x55, 0x0b, 0xa6, 0x62, 0xc7, 0xc8, 0xd8, 0xd6, 0x97, 0xab, 0x20, 0x7a, + 0xa9, 0xbf, 0x96, 0xe4, 0x84, 0x69, 0xd6, 0xf6, 0x06, 0x4c, 0xa7, 0x7b, 0x9b, 0x35, 0x65, 0xd7, + 0x91, 0x73, 0xbd, 0x10, 0x37, 0xe5, 0x9a, 0x13, 0x86, 0xc8, 0x21, 0xe4, 0x59, 0x28, 0x75, 0x9c, + 0xa0, 0xe5, 0x7a, 0x4e, 0x9b, 0xb7, 0x62, 0xc1, 0x58, 0x90, 0x64, 0x39, 0x6a, 0x0c, 0xfb, 0xc3, + 0x30, 0xb1, 0xea, 0x78, 0x2d, 0xda, 0x94, 0xeb, 0xf0, 0xe1, 0x91, 0xa9, 0x7f, 0x3c, 0x0a, 0x15, + 0xe3, 0xf8, 0x78, 0xfa, 0xe7, 0xac, 0x44, 0x22, 0xa2, 0x42, 0x8e, 0x89, 0x88, 0x3e, 0x09, 0xb0, + 0xe9, 0x7a, 0x6e, 0xb8, 0x75, 0xc2, 0x14, 0x47, 0xfc, 0x6e, 0xfe, 0xba, 0xa6, 0x80, 0x06, 0xb5, + 0xf8, 0x02, 0xb4, 0x78, 0x40, 0xb6, 0xc0, 0x77, 0x2c, 0x63, 0xbb, 0x19, 0xcb, 0xc3, 0xe1, 0xc3, + 0xe8, 0x98, 0x79, 0xb5, 0xfd, 0x88, 0xbb, 0xa9, 0x83, 0x76, 0xa5, 0x75, 0x28, 0x05, 0x34, 0xec, + 0x75, 0xe8, 0x89, 0x92, 0x11, 0x71, 0xd7, 0x1b, 0x94, 0xf5, 0x51, 0x53, 0x9a, 0x7d, 0x09, 0xce, + 0x24, 0x44, 0x38, 0xd6, 0x0d, 0x93, 0x0f, 0x99, 0x36, 0x8a, 0x93, 0xdc, 0x37, 0xb1, 0xbe, 0x68, + 0x1b, 0x49, 0x88, 0x74, 0x5f, 0x08, 0x07, 0x2b, 0x01, 0xb3, 0x7f, 0x3c, 0x06, 0xd2, 0x87, 0xe1, + 0x08, 0xcb, 0x95, 0x79, 0x73, 0x39, 0x72, 0x82, 0x9b, 0xcb, 0x9b, 0x30, 0xe1, 0x7a, 0x6e, 0xe4, + 0x3a, 0x6d, 0x6e, 0x7f, 0x92, 0xdb, 0xa9, 0x72, 0xc6, 0x9f, 0x58, 0x36, 0x60, 0x19, 0x74, 0x12, + 0x75, 0xc9, 0x6b, 0x50, 0xe4, 0xfb, 0x8d, 0x1c, 0xc0, 0xc7, 0x77, 0xb4, 0xe0, 0x3e, 0x36, 0x22, + 0x42, 0x4f, 0x50, 0xe2, 0x87, 0x0f, 0x91, 0x85, 0x49, 0x1f, 0xbf, 0xe5, 0x38, 0x8e, 0x0f, 0x1f, + 0x29, 0x38, 0xf6, 0xd5, 0x60, 0x54, 0x36, 0x1d, 0xb7, 0xdd, 0x0b, 0x68, 0x4c, 0x65, 0x2c, 0x49, + 0xe5, 0x7a, 0x0a, 0x8e, 0x7d, 0x35, 0xc8, 0x26, 0x4c, 0xc8, 0x32, 0xe1, 0x36, 0x37, 0x7e, 0xc2, + 0xaf, 0xe4, 0xee, 0x91, 0xd7, 0x0d, 0x4a, 0x98, 0xa0, 0x4b, 0x7a, 0x70, 0xd6, 0xf5, 0x1a, 0xbe, + 0xd7, 0x68, 0xf7, 0x42, 0x77, 0x87, 0xc6, 0xe1, 0x71, 0x27, 0x61, 0x76, 0x61, 0x7f, 0x6f, 0xee, + 0xec, 0x72, 0x9a, 0x1c, 0xf6, 0x73, 0x20, 0x5f, 0xb0, 0xe0, 0x42, 0xc3, 0xf7, 0x42, 0x9e, 0xc5, + 0x63, 0x87, 0x5e, 0x0b, 0x02, 0x3f, 0x10, 0xbc, 0xcb, 0x27, 0xe4, 0xcd, 0xcd, 0x9e, 0x8b, 0x59, + 0x24, 0x31, 0x9b, 0x13, 0x79, 0x13, 0x4a, 0xdd, 0xc0, 0xdf, 0x71, 0x9b, 0x34, 0x90, 0x2e, 0x98, + 0x2b, 0x79, 0xa4, 0x36, 0x5a, 0x93, 0x34, 0xe3, 0xa5, 0x47, 0x95, 0xa0, 0xe6, 0x67, 0xff, 0xdf, + 0x0a, 0x4c, 0x26, 0xd1, 0xc9, 0x2f, 0x00, 0x74, 0x03, 0xbf, 0x43, 0xa3, 0x2d, 0xaa, 0xc3, 0x9c, + 0x6e, 0x0d, 0x9b, 0xbc, 0x46, 0xd1, 0x53, 0x6e, 0x4b, 0x6c, 0xb9, 0x88, 0x4b, 0xd1, 0xe0, 0x48, + 0x02, 0x18, 0xdf, 0x16, 0xdb, 0xae, 0xd4, 0x42, 0x5e, 0xcd, 0x45, 0x67, 0x92, 0x9c, 0x79, 0x7c, + 0x8e, 0x2c, 0x42, 0xc5, 0x88, 0x6c, 0x40, 0xe1, 0x3e, 0xdd, 0xc8, 0x27, 0x73, 0xc2, 0x5d, 0x2a, + 0x4f, 0x33, 0xb5, 0xf1, 0xfd, 0xbd, 0xb9, 0xc2, 0x5d, 0xba, 0x81, 0x8c, 0x38, 0xfb, 0xae, 0xa6, + 0xf0, 0x5d, 0x90, 0x4b, 0xc5, 0xab, 0x39, 0x3a, 0x42, 0x88, 0xef, 0x92, 0x45, 0xa8, 0x18, 0x91, + 0x37, 0xa1, 0x7c, 0xdf, 0xd9, 0xa1, 0x9b, 0x81, 0xef, 0x45, 0xd2, 0x57, 0x6e, 0xc8, 0xe0, 0x92, + 0xbb, 0x8a, 0x9c, 0xe4, 0xcb, 0xb7, 0x77, 0x5d, 0x88, 0x31, 0x3b, 0xb2, 0x03, 0x25, 0x8f, 0xde, + 0x47, 0xda, 0x76, 0x1b, 0xf9, 0x04, 0x73, 0xdc, 0x92, 0xd4, 0x24, 0x67, 0xbe, 0xef, 0xa9, 0x32, + 0xd4, 0xbc, 0x58, 0x5f, 0xde, 0xf3, 0x37, 0xe4, 0x42, 0x35, 0x64, 0x5f, 0xea, 0x93, 0xa9, 0xe8, + 0xcb, 0x9b, 0xfe, 0x06, 0x32, 0xe2, 0x6c, 0x8e, 0x34, 0xb4, 0xa3, 0x96, 0x5c, 0xa6, 0x6e, 0xe5, + 0xeb, 0xa0, 0x26, 0xe6, 0x48, 0x5c, 0x8a, 0x06, 0x47, 0xd6, 0xb6, 0x2d, 0x69, 0xac, 0x94, 0x0b, + 0xd5, 0x90, 0x6d, 0x9b, 0x34, 0x7d, 0x8a, 0xb6, 0x55, 0x65, 0xa8, 0x79, 0x31, 0xbe, 0xae, 0xb4, + 0xfc, 0xe5, 0xb3, 0x54, 0x25, 0xed, 0x88, 0x82, 0xaf, 0x2a, 0x43, 0xcd, 0x8b, 0xb5, 0x77, 0xb8, + 0xbd, 0x7b, 0xdf, 0x69, 0x6f, 0xbb, 0x5e, 0x4b, 0x86, 0xed, 0x0e, 0x1b, 0xe6, 0xb6, 0xbd, 0x7b, + 0x57, 0xd0, 0x33, 0xdb, 0x3b, 0x2e, 0x45, 0x83, 0x23, 0xf9, 0x7b, 0x96, 0x0e, 0xc5, 0x99, 0xc8, + 0xc3, 0x89, 0x29, 0xb9, 0xe4, 0xca, 0xc8, 0x1c, 0xa1, 0x28, 0xfe, 0xb4, 0xf6, 0xbb, 0xe4, 0x85, + 0x5f, 0xf9, 0xa3, 0xb9, 0x19, 0xea, 0x35, 0xfc, 0xa6, 0xeb, 0xb5, 0x16, 0xee, 0x85, 0xbe, 0x37, + 0x8f, 0xce, 0x7d, 0xa5, 0xa3, 0x4b, 0x99, 0x66, 0x3f, 0x0a, 0x15, 0x83, 0xc4, 0x61, 0x8a, 0xde, + 0x84, 0xa9, 0xe8, 0xfd, 0xf6, 0x18, 0x4c, 0x98, 0x79, 0x48, 0x8f, 0xa0, 0x7d, 0xe9, 0x13, 0xc7, + 0xc8, 0x71, 0x4e, 0x1c, 0xec, 0x88, 0x69, 0x5c, 0x70, 0x29, 0xf3, 0xd6, 0x72, 0x6e, 0x0a, 0x77, + 0x7c, 0xc4, 0x34, 0x0a, 0x43, 0x4c, 0x30, 0x3d, 0x86, 0xcf, 0x0b, 0x53, 0x5b, 0x85, 0x62, 0x57, + 0x4c, 0xaa, 0xad, 0x09, 0x55, 0xed, 0x2a, 0x40, 0x9c, 0x30, 0x53, 0x5e, 0x7c, 0x6a, 0x7d, 0xd8, + 0x48, 0xe4, 0x69, 0x60, 0x91, 0xa7, 0x61, 0x8c, 0xa9, 0x3e, 0xb4, 0x29, 0xb3, 0x0a, 0xe8, 0x73, + 0xfc, 0x75, 0x5e, 0x8a, 0x12, 0x4a, 0x5e, 0x64, 0x5a, 0x6a, 0xac, 0xb0, 0xc8, 0x64, 0x01, 0xe7, + 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, 0xde, 0xd9, 0x56, 0x84, 0xc3, 0xf4, 0x80, 0xdb, 0xd6, 0x5f, + 0x34, 0xcf, 0x5a, 0x39, 0xce, 0x21, 0x31, 0x6a, 0x8f, 0x7e, 0xd8, 0x1a, 0xee, 0x58, 0xf4, 0x19, + 0x98, 0x4c, 0xee, 0x42, 0xb9, 0xdf, 0x7c, 0xfc, 0xc3, 0x31, 0x38, 0x77, 0xab, 0xe5, 0x7a, 0xe9, + 0x9c, 0x6f, 0x59, 0x0f, 0x3c, 0x58, 0xc7, 0x7e, 0xe0, 0x41, 0xc7, 0xe4, 0xc9, 0xe7, 0x13, 0xb2, + 0x63, 0xf2, 0xd4, 0x5b, 0x16, 0x49, 0x5c, 0xf2, 0x87, 0x16, 0x3c, 0xe9, 0x34, 0xc5, 0xb9, 0xc0, + 0x69, 0xcb, 0x52, 0x23, 0x2f, 0xb9, 0x9c, 0xd1, 0xe1, 0x90, 0xbb, 0x7c, 0xff, 0xc7, 0xcf, 0x57, + 0x0f, 0xe0, 0x2a, 0x7a, 0xfc, 0xa7, 0xe4, 0x17, 0x3c, 0x79, 0x10, 0x2a, 0x1e, 0x28, 0x3e, 0xf9, + 0xab, 0x30, 0x95, 0xf8, 0x60, 0x69, 0x09, 0x2f, 0x8b, 0x0b, 0x8b, 0x7a, 0x12, 0x84, 0x69, 0x5c, + 0xf2, 0x3d, 0x0b, 0x66, 0x84, 0xd9, 0x35, 0xa3, 0x69, 0xc4, 0x4d, 0xad, 0x9f, 0x7f, 0xd3, 0x2c, + 0x0e, 0xe0, 0x28, 0x9a, 0x25, 0xb6, 0xc3, 0x0e, 0x40, 0xc3, 0x81, 0x22, 0xcf, 0xde, 0x86, 0x0f, + 0x1c, 0xda, 0xee, 0xc7, 0xca, 0x62, 0xff, 0x2a, 0x5c, 0x3c, 0x50, 0xda, 0x63, 0xcd, 0xc4, 0xef, + 0x58, 0x30, 0x61, 0xe6, 0xae, 0x22, 0xcf, 0x42, 0x29, 0xf2, 0xb7, 0xa9, 0x77, 0x27, 0x50, 0xde, + 0xcc, 0x7a, 0x15, 0x58, 0xe7, 0xe5, 0xb8, 0x82, 0x1a, 0x83, 0x61, 0x37, 0xda, 0x2e, 0xf5, 0xa2, + 0xe5, 0xa6, 0x9c, 0x03, 0x1a, 0x7b, 0x51, 0x94, 0x2f, 0xa1, 0xc6, 0x10, 0x0e, 0x88, 0xec, 0x77, + 0x9d, 0x36, 0x02, 0xaa, 0x62, 0x1f, 0x0c, 0x07, 0xc4, 0x18, 0x86, 0x09, 0x4c, 0x62, 0x6b, 0xfb, + 0xef, 0x68, 0x7c, 0xe9, 0x93, 0xb2, 0xd7, 0x7e, 0xcb, 0x82, 0xb2, 0xb8, 0xbf, 0x40, 0xba, 0x99, + 0xf2, 0x3f, 0x4e, 0x59, 0x58, 0xaa, 0x6b, 0xcb, 0x59, 0xfe, 0xc7, 0x97, 0x61, 0x74, 0xdb, 0xf5, + 0xd4, 0x97, 0xe8, 0x3d, 0xfb, 0x55, 0xd7, 0x6b, 0x22, 0x87, 0xe8, 0x5d, 0xbd, 0x30, 0x70, 0x57, + 0x5f, 0x80, 0xb2, 0xf6, 0xca, 0x91, 0x7b, 0xa3, 0x36, 0x6d, 0x6b, 0x2f, 0x1e, 0x8c, 0x71, 0xec, + 0xdf, 0xb4, 0x60, 0x92, 0x87, 0xd3, 0xc7, 0xc6, 0x82, 0x17, 0xb4, 0xa3, 0x9c, 0x90, 0xfb, 0x62, + 0xd2, 0x51, 0xee, 0xdd, 0xbd, 0xb9, 0x8a, 0x08, 0xc0, 0x4f, 0xfa, 0xcd, 0x7d, 0x4a, 0x5a, 0x18, + 0xb9, 0x3b, 0xdf, 0xc8, 0xb1, 0x0d, 0x60, 0xb1, 0x98, 0x8a, 0x08, 0xc6, 0xf4, 0xec, 0xb7, 0x60, + 0xc2, 0x8c, 0x54, 0x23, 0x2f, 0x40, 0xa5, 0xeb, 0x7a, 0xad, 0x64, 0x44, 0xb3, 0xbe, 0x85, 0x59, + 0x8b, 0x41, 0x68, 0xe2, 0xf1, 0x6a, 0x7e, 0x5c, 0x2d, 0x75, 0x79, 0xb3, 0xe6, 0x9b, 0xd5, 0xe2, + 0x3f, 0xb6, 0x07, 0x10, 0x87, 0x5d, 0x1f, 0xc9, 0xb2, 0x35, 0x26, 0x2e, 0x46, 0x84, 0xa6, 0xc6, + 0x53, 0x68, 0x8c, 0x89, 0x11, 0xfe, 0xee, 0xde, 0x41, 0x9a, 0xa0, 0xa8, 0xc5, 0x1f, 0xe8, 0xc8, + 0x88, 0xc0, 0xcc, 0xfd, 0x81, 0x8e, 0x0c, 0x1e, 0xef, 0xdd, 0x03, 0x1d, 0x59, 0xc2, 0xfc, 0xf9, + 0x7a, 0xa0, 0xe3, 0x13, 0x70, 0xdc, 0x5c, 0xbd, 0x4c, 0xf1, 0xba, 0x6f, 0xe6, 0xd4, 0xd0, 0x2d, + 0x2e, 0x93, 0x6a, 0x48, 0xa8, 0xfd, 0xdd, 0x51, 0x98, 0x4e, 0xdb, 0x5f, 0xf2, 0x76, 0x6d, 0x21, + 0x5f, 0xb5, 0x60, 0xd2, 0x49, 0xe4, 0x45, 0xcc, 0xe9, 0xb5, 0xaf, 0x04, 0x4d, 0x23, 0x2f, 0x5f, + 0xa2, 0x1c, 0x53, 0xbc, 0xc9, 0x5f, 0x82, 0xf1, 0xc8, 0xed, 0x50, 0xbf, 0x27, 0xac, 0xb2, 0x05, + 0x61, 0x1d, 0x59, 0x17, 0x45, 0xa8, 0x60, 0x6c, 0x13, 0x70, 0xb9, 0x3a, 0x1b, 0x50, 0xe9, 0xa6, + 0x3d, 0x1d, 0x9b, 0x91, 0x45, 0x39, 0x6a, 0x0c, 0xf2, 0x00, 0xc6, 0x85, 0x13, 0x8c, 0xf2, 0x76, + 0x5a, 0xcd, 0xc9, 0x4e, 0x24, 0xfc, 0x6c, 0xe2, 0x2e, 0x10, 0xff, 0x43, 0x54, 0xec, 0x98, 0xee, + 0x0c, 0x81, 0xe3, 0xb5, 0x28, 0x6f, 0x73, 0x69, 0xd9, 0x78, 0x3d, 0x2f, 0x93, 0x1c, 0x6a, 0xca, + 0xd5, 0xa0, 0x15, 0xca, 0x88, 0x47, 0x5d, 0x86, 0x06, 0x67, 0xfb, 0x57, 0x2d, 0x98, 0x19, 0x54, + 0x91, 0x0d, 0x14, 0xbe, 0xea, 0xca, 0x11, 0x65, 0x24, 0x5a, 0x70, 0x82, 0x08, 0x05, 0x8c, 0x5c, + 0x84, 0x02, 0xd5, 0x1b, 0x95, 0xce, 0x0a, 0x79, 0xcd, 0x6b, 0x22, 0x2b, 0x27, 0x57, 0x61, 0x34, + 0x8c, 0x68, 0x37, 0x15, 0xc7, 0x30, 0xca, 0x16, 0xcf, 0x0c, 0x43, 0x3c, 0xc7, 0xb5, 0x3f, 0x0c, + 0xc7, 0x4c, 0xed, 0x6c, 0x5f, 0x03, 0x82, 0x7e, 0xbb, 0xbd, 0xe1, 0x34, 0xb6, 0xef, 0xba, 0x5e, + 0xd3, 0xbf, 0xcf, 0x37, 0x86, 0x05, 0x28, 0x07, 0x32, 0xba, 0x3b, 0x94, 0x73, 0x4a, 0xef, 0x2c, + 0x2a, 0xec, 0x3b, 0xc4, 0x18, 0xc7, 0xfe, 0xde, 0x08, 0x8c, 0xcb, 0x54, 0x04, 0x0f, 0x21, 0x88, + 0x66, 0x3b, 0xe1, 0xba, 0xb0, 0x9c, 0x4b, 0x06, 0x85, 0x81, 0x11, 0x34, 0x61, 0x2a, 0x82, 0xe6, + 0xd5, 0x7c, 0xd8, 0x1d, 0x1c, 0x3e, 0xf3, 0xed, 0x22, 0x4c, 0xa5, 0x52, 0x3b, 0xa4, 0xb2, 0xc0, + 0x5b, 0xef, 0x49, 0x16, 0x78, 0x12, 0x26, 0x5e, 0x02, 0xc8, 0xcf, 0xe5, 0xf6, 0x27, 0x8f, 0x02, + 0xe4, 0xe5, 0x0c, 0x5d, 0x7c, 0xff, 0x38, 0x43, 0xff, 0x37, 0x0b, 0x1e, 0x1f, 0x98, 0xa0, 0x84, + 0xa7, 0xfa, 0x0b, 0x92, 0x50, 0xb9, 0x5e, 0xe4, 0x9c, 0xf4, 0x49, 0xbb, 0x39, 0xa4, 0xb3, 0xb3, + 0xa5, 0xd9, 0x93, 0xe7, 0x61, 0x82, 0xaf, 0xcd, 0x6c, 0xe5, 0x64, 0x6b, 0xaf, 0xb8, 0xa5, 0xe5, + 0xf7, 0x75, 0x75, 0xa3, 0x1c, 0x13, 0x58, 0xf6, 0x37, 0x2d, 0x98, 0x19, 0x94, 0xf8, 0xed, 0x08, + 0x7a, 0xee, 0x5f, 0x49, 0x05, 0x21, 0xcd, 0xf5, 0x05, 0x21, 0xa5, 0xac, 0x88, 0x2a, 0xde, 0xc8, + 0x30, 0xe0, 0x15, 0x0e, 0x89, 0xb1, 0xf9, 0xfd, 0x02, 0x4c, 0x4b, 0x11, 0xe3, 0x23, 0xca, 0x8b, + 0x89, 0xd0, 0xa9, 0x9f, 0x4a, 0x85, 0x4e, 0x9d, 0x4f, 0xe3, 0xff, 0x24, 0x6e, 0xea, 0xfd, 0x15, + 0x37, 0xf5, 0x95, 0x22, 0x5c, 0xc8, 0x4c, 0xb1, 0x46, 0xbe, 0x9c, 0xb1, 0x53, 0xdc, 0xcd, 0x39, + 0x97, 0x9b, 0x0e, 0xb1, 0x3e, 0xdd, 0x60, 0xa3, 0x5f, 0x37, 0x83, 0x7c, 0xc4, 0xea, 0xbf, 0x79, + 0x0a, 0x59, 0xe9, 0x8e, 0x1b, 0xef, 0xf3, 0x70, 0x5f, 0xc9, 0xfb, 0x73, 0xb0, 0xd4, 0x7f, 0xa5, + 0x00, 0x57, 0x8e, 0xda, 0xb2, 0xef, 0xd3, 0x00, 0xd9, 0x30, 0x11, 0x20, 0xfb, 0x90, 0x54, 0x9b, + 0x53, 0x89, 0x95, 0xfd, 0x07, 0xa3, 0x7a, 0xdf, 0xed, 0x9f, 0xb0, 0x47, 0xb2, 0xbc, 0x8c, 0x33, + 0xd5, 0x57, 0xbd, 0x25, 0x10, 0xef, 0x0d, 0xe3, 0x75, 0x51, 0xfc, 0xee, 0xde, 0xdc, 0xd9, 0x38, + 0x17, 0x91, 0x2c, 0x44, 0x55, 0x89, 0x5c, 0x81, 0x52, 0x20, 0xa0, 0x2a, 0x24, 0x50, 0x3a, 0x66, + 0x89, 0x32, 0xd4, 0x50, 0xf2, 0x79, 0xe3, 0xac, 0x30, 0x7a, 0x5a, 0x29, 0xb7, 0x0e, 0xf2, 0x37, + 0x7b, 0x03, 0x4a, 0xa1, 0x4a, 0x78, 0x2f, 0xa6, 0xd3, 0x73, 0x47, 0x8c, 0x34, 0x75, 0x36, 0x68, + 0x5b, 0x65, 0xbf, 0x17, 0xdf, 0xa7, 0x73, 0xe3, 0x6b, 0x92, 0xc4, 0xd6, 0x96, 0x09, 0x71, 0x1f, + 0x06, 0xfd, 0x56, 0x09, 0x12, 0xc1, 0xb8, 0x7c, 0xf5, 0x5a, 0x1e, 0x67, 0x57, 0x73, 0x0a, 0xd9, + 0x92, 0x0e, 0xfd, 0xfc, 0xc0, 0xaf, 0x2c, 0x72, 0x8a, 0x95, 0xfd, 0x03, 0x0b, 0x2a, 0x72, 0x8c, + 0x3c, 0x84, 0x90, 0xdb, 0x7b, 0xc9, 0x90, 0xdb, 0x6b, 0xb9, 0x2c, 0xe1, 0x03, 0xe2, 0x6d, 0xef, + 0xc1, 0x84, 0x99, 0xec, 0x94, 0x7c, 0xd2, 0xd8, 0x82, 0xac, 0x61, 0x12, 0xfa, 0xa9, 0x4d, 0x2a, + 0xde, 0x9e, 0xec, 0x7f, 0x5a, 0xd6, 0xad, 0xc8, 0x0f, 0xce, 0xe6, 0xc8, 0xb7, 0x0e, 0x1c, 0xf9, + 0xe6, 0xc0, 0x1b, 0xc9, 0x7f, 0xe0, 0xbd, 0x06, 0x25, 0xb5, 0x2c, 0x4a, 0x6d, 0xea, 0x29, 0xd3, + 0xc3, 0x9f, 0xa9, 0x64, 0x8c, 0x98, 0x31, 0x5d, 0xf8, 0x01, 0x38, 0xbe, 0x27, 0x50, 0xcb, 0xb5, + 0x26, 0x43, 0xde, 0x84, 0xca, 0x7d, 0x3f, 0xd8, 0x6e, 0xfb, 0x0e, 0x7f, 0x65, 0x04, 0xf2, 0x70, + 0x2a, 0xd1, 0xb6, 0x7e, 0x11, 0x66, 0x75, 0x37, 0xa6, 0x8f, 0x26, 0x33, 0x52, 0x85, 0xa9, 0x8e, + 0xeb, 0x21, 0x75, 0x9a, 0x3a, 0xb2, 0x76, 0x54, 0x64, 0xf8, 0x57, 0xba, 0xfd, 0x6a, 0x12, 0x8c, + 0x69, 0x7c, 0x6e, 0x97, 0x0b, 0x12, 0xa6, 0x0e, 0x99, 0xc6, 0x7b, 0x6d, 0xf8, 0xc1, 0x98, 0x34, + 0x9f, 0x88, 0x38, 0xa3, 0x64, 0x39, 0xa6, 0x78, 0x93, 0xcf, 0x41, 0x29, 0x54, 0xef, 0xc9, 0x16, + 0x73, 0x3c, 0xf5, 0xe8, 0x37, 0x65, 0x75, 0x57, 0xea, 0x47, 0x65, 0x35, 0x43, 0xb2, 0x02, 0xe7, + 0x95, 0xed, 0x26, 0xf1, 0x34, 0xe6, 0x58, 0x9c, 0x8a, 0x0e, 0x33, 0xe0, 0x98, 0x59, 0x8b, 0xe9, + 0xb6, 0x3c, 0x89, 0xb0, 0xb8, 0xc4, 0x37, 0xee, 0xbd, 0xf9, 0xfc, 0x6b, 0xa2, 0x84, 0x1e, 0x14, + 0x38, 0x5e, 0x1a, 0x22, 0x70, 0xbc, 0x0e, 0x17, 0xd2, 0x20, 0x9e, 0x63, 0x90, 0xa7, 0x35, 0x34, + 0xb6, 0xd0, 0xb5, 0x2c, 0x24, 0xcc, 0xae, 0x4b, 0xee, 0x42, 0x39, 0xa0, 0xfc, 0x94, 0x57, 0x55, + 0xfe, 0x8f, 0xc7, 0xf6, 0xf4, 0x46, 0x45, 0x00, 0x63, 0x5a, 0xac, 0xdf, 0x9d, 0x64, 0xce, 0xfd, + 0xd7, 0x72, 0x7c, 0x70, 0x5e, 0xf6, 0xfd, 0x80, 0xdc, 0x9f, 0xf6, 0xbf, 0x9f, 0x82, 0x33, 0x09, + 0x03, 0x14, 0x79, 0x0a, 0x8a, 0x3c, 0xe9, 0x22, 0x5f, 0xad, 0x4a, 0xf1, 0x8a, 0x2a, 0x1a, 0x47, + 0xc0, 0xc8, 0xaf, 0x58, 0x30, 0xd5, 0x4d, 0x5c, 0x6f, 0xa9, 0x85, 0x7c, 0x48, 0x9b, 0x76, 0xf2, + 0xce, 0xcc, 0x78, 0xad, 0x26, 0xc9, 0x0c, 0xd3, 0xdc, 0xd9, 0x7a, 0x20, 0xc3, 0x25, 0xda, 0x34, + 0xe0, 0xd8, 0x52, 0xd1, 0xd3, 0x24, 0x16, 0x93, 0x60, 0x4c, 0xe3, 0xb3, 0x1e, 0xe6, 0x5f, 0x37, + 0xcc, 0xa3, 0xc2, 0x55, 0x45, 0x00, 0x63, 0x5a, 0xe4, 0x65, 0x98, 0x94, 0xa9, 0xd6, 0xd7, 0xfc, + 0xe6, 0x0d, 0x27, 0xdc, 0x92, 0x47, 0x3e, 0x7d, 0x44, 0x5d, 0x4c, 0x40, 0x31, 0x85, 0xcd, 0xbf, + 0x2d, 0xce, 0x67, 0xcf, 0x09, 0x8c, 0x25, 0x1f, 0xf3, 0x59, 0x4c, 0x82, 0x31, 0x8d, 0x4f, 0x9e, + 0x35, 0xb6, 0x21, 0xe1, 0x58, 0xa3, 0x57, 0x83, 0x8c, 0xad, 0xa8, 0x0a, 0x53, 0x3d, 0x7e, 0x42, + 0x6e, 0x2a, 0xa0, 0x9c, 0x8f, 0x9a, 0xe1, 0x9d, 0x24, 0x18, 0xd3, 0xf8, 0xe4, 0x25, 0x38, 0x13, + 0xb0, 0xc5, 0x56, 0x13, 0x10, 0xde, 0x36, 0xda, 0x99, 0x02, 0x4d, 0x20, 0x26, 0x71, 0xc9, 0x2b, + 0x70, 0x36, 0x4e, 0xc7, 0xab, 0x08, 0x08, 0xf7, 0x1b, 0x9d, 0x1b, 0xb2, 0x9a, 0x46, 0xc0, 0xfe, + 0x3a, 0xe4, 0xaf, 0xc3, 0xb4, 0xd1, 0x12, 0xcb, 0x5e, 0x93, 0x3e, 0x90, 0x29, 0x53, 0xf9, 0xe3, + 0x74, 0x8b, 0x29, 0x18, 0xf6, 0x61, 0x93, 0x8f, 0xc1, 0x64, 0xc3, 0x6f, 0xb7, 0xf9, 0x1a, 0x27, + 0x1e, 0x92, 0x11, 0xb9, 0x51, 0x45, 0x16, 0xd9, 0x04, 0x04, 0x53, 0x98, 0xe4, 0x26, 0x10, 0x7f, + 0x83, 0xa9, 0x57, 0xb4, 0xf9, 0x0a, 0xf5, 0xa8, 0xd4, 0x38, 0xce, 0x24, 0x83, 0xb5, 0x6e, 0xf7, + 0x61, 0x60, 0x46, 0x2d, 0x9e, 0x5a, 0xd2, 0x08, 0x6e, 0x9f, 0xcc, 0x23, 0x99, 0x7d, 0xda, 0x9e, + 0x73, 0x68, 0x64, 0x7b, 0x00, 0x63, 0xc2, 0x23, 0x22, 0x9f, 0x24, 0xa9, 0xe6, 0x9b, 0x12, 0xf1, + 0x1e, 0x21, 0x4a, 0x51, 0x72, 0x22, 0xbf, 0x00, 0xe5, 0x0d, 0xf5, 0xc0, 0x10, 0xcf, 0x8c, 0x3a, + 0xf4, 0xbe, 0x98, 0x7a, 0x2b, 0x2b, 0xb6, 0x57, 0x68, 0x00, 0xc6, 0x2c, 0xc9, 0xd3, 0x50, 0xb9, + 0xb1, 0x56, 0xd5, 0xa3, 0xf0, 0x2c, 0xef, 0xfd, 0x51, 0x56, 0x05, 0x4d, 0x00, 0x9b, 0x61, 0x5a, + 0x7d, 0x23, 0x49, 0xa7, 0x89, 0x0c, 0x6d, 0x8c, 0x61, 0x73, 0x17, 0x19, 0xac, 0xcf, 0x9c, 0x4b, + 0x61, 0xcb, 0x72, 0xd4, 0x18, 0xe4, 0x0d, 0xa8, 0xc8, 0xfd, 0x82, 0xaf, 0x4d, 0xe7, 0x4f, 0x96, + 0x38, 0x01, 0x63, 0x12, 0x68, 0xd2, 0xe3, 0xd7, 0xf7, 0xfc, 0xdd, 0x15, 0x7a, 0xbd, 0xd7, 0x6e, + 0xcf, 0x5c, 0xe0, 0xeb, 0x66, 0x7c, 0x7d, 0x1f, 0x83, 0xd0, 0xc4, 0x23, 0xcf, 0x29, 0x57, 0xc7, + 0x47, 0x13, 0xfe, 0x0c, 0xda, 0xd5, 0x51, 0x2b, 0xdd, 0x03, 0x62, 0xab, 0x1e, 0x3b, 0xc4, 0xc7, + 0x70, 0x03, 0x66, 0x95, 0xc6, 0xd7, 0x3f, 0x49, 0x66, 0x66, 0x12, 0xb6, 0xa3, 0xd9, 0xbb, 0x03, + 0x31, 0xf1, 0x00, 0x2a, 0x64, 0x03, 0x0a, 0x4e, 0x7b, 0x63, 0xe6, 0xf1, 0x3c, 0x54, 0xd7, 0xea, + 0x4a, 0x4d, 0x8e, 0x28, 0xee, 0x0f, 0x5d, 0x5d, 0xa9, 0x21, 0x23, 0x4e, 0x5c, 0x18, 0x75, 0xda, + 0x1b, 0xe1, 0xcc, 0x2c, 0x9f, 0xb3, 0xb9, 0x31, 0x89, 0x8d, 0x07, 0x2b, 0xb5, 0x10, 0x39, 0x0b, + 0xfb, 0x0b, 0x23, 0xfa, 0x96, 0x48, 0xe7, 0xa9, 0x7f, 0xcb, 0x9c, 0x40, 0xe2, 0xb8, 0x73, 0x3b, + 0xb7, 0x09, 0x24, 0xd5, 0x8b, 0x33, 0x03, 0xa7, 0x4f, 0x57, 0x2f, 0x19, 0xb9, 0x24, 0xb8, 0x4b, + 0xe6, 0xe0, 0x17, 0xa7, 0xe7, 0xe4, 0x82, 0x61, 0x7f, 0xb1, 0xa2, 0xad, 0xa0, 0x29, 0x37, 0xc1, + 0x00, 0x8a, 0x6e, 0x18, 0xb9, 0x7e, 0x8e, 0xf9, 0x04, 0x52, 0xc9, 0xeb, 0x79, 0xb8, 0x12, 0x07, + 0xa0, 0x60, 0xc5, 0x78, 0x7a, 0x2d, 0xd7, 0x7b, 0x20, 0x3f, 0xff, 0xb5, 0xdc, 0x9d, 0xdc, 0x04, + 0x4f, 0x0e, 0x40, 0xc1, 0x8a, 0xdc, 0x13, 0x83, 0xba, 0x90, 0x47, 0x5f, 0x57, 0x57, 0x6a, 0x29, + 0x7e, 0xc9, 0xc1, 0x7d, 0x0f, 0x0a, 0x61, 0xc7, 0x95, 0xea, 0xd2, 0x90, 0xbc, 0xea, 0xab, 0xcb, + 0x59, 0xbc, 0xea, 0xab, 0xcb, 0xc8, 0x98, 0xf0, 0xab, 0x7e, 0xa7, 0xb3, 0xe1, 0x84, 0xa1, 0xd3, + 0xd4, 0xd6, 0x99, 0x21, 0xaf, 0xfa, 0xab, 0x9a, 0x5e, 0x8a, 0x35, 0xbf, 0xea, 0x8f, 0xa1, 0x68, + 0x70, 0x26, 0x6f, 0xc2, 0xb8, 0x23, 0x1e, 0x40, 0x95, 0xc1, 0x1b, 0xf9, 0xbc, 0xea, 0x9b, 0x92, + 0x80, 0x9b, 0x69, 0x24, 0x08, 0x15, 0x43, 0xc6, 0x3b, 0x0a, 0x1c, 0xba, 0xe9, 0x6e, 0x4b, 0xe3, + 0x50, 0x7d, 0xe8, 0x27, 0x7a, 0x18, 0xb1, 0x2c, 0xde, 0x12, 0x84, 0x8a, 0x21, 0xf9, 0x25, 0x0b, + 0xce, 0x74, 0x1c, 0xcf, 0xd1, 0x21, 0xb9, 0xf9, 0x04, 0x6e, 0x9b, 0x41, 0xbe, 0xb1, 0x86, 0xb8, + 0x6a, 0x32, 0xc2, 0x24, 0x5f, 0xb2, 0x03, 0x63, 0x0e, 0x7f, 0x9a, 0x59, 0x1e, 0xc5, 0x30, 0x8f, + 0x67, 0x9e, 0x53, 0x6d, 0xc0, 0x17, 0x17, 0xf9, 0x00, 0xb4, 0xe4, 0x46, 0x7e, 0xcb, 0x82, 0x71, + 0x11, 0x57, 0xc0, 0x14, 0x52, 0xf6, 0xed, 0x9f, 0x39, 0x85, 0x47, 0x30, 0x64, 0xcc, 0x83, 0x74, + 0xce, 0xfa, 0xa0, 0x76, 0x9a, 0x16, 0xa5, 0x07, 0x46, 0x3d, 0x28, 0xe9, 0x98, 0xea, 0xdb, 0x71, + 0x1e, 0x24, 0x1e, 0x60, 0x32, 0x55, 0xdf, 0xd5, 0x14, 0x0c, 0xfb, 0xb0, 0x67, 0x3f, 0x06, 0x13, + 0xa6, 0x1c, 0xc7, 0x8a, 0x9c, 0xf8, 0x51, 0x01, 0x80, 0x77, 0x95, 0x48, 0xe3, 0xd3, 0xe1, 0x39, + 0xbf, 0xb7, 0xfc, 0x66, 0x4e, 0x0f, 0xc1, 0x1a, 0xd9, 0x78, 0x40, 0x26, 0xf8, 0xde, 0xf2, 0x9b, + 0x28, 0x99, 0x90, 0x16, 0x8c, 0x76, 0x9d, 0x68, 0x2b, 0xff, 0xd4, 0x3f, 0x25, 0x11, 0xcf, 0x1e, + 0x6d, 0x21, 0x67, 0x40, 0xde, 0xb6, 0x62, 0xbf, 0xa7, 0x42, 0x1e, 0x69, 0x8b, 0xe3, 0x36, 0x9b, + 0x97, 0x9e, 0x4e, 0xa9, 0xec, 0xbd, 0x69, 0xff, 0xa7, 0xd9, 0x77, 0x2c, 0x98, 0x30, 0x51, 0x33, + 0xba, 0xe9, 0xe7, 0xcd, 0x6e, 0xca, 0xb3, 0x3d, 0xcc, 0x1e, 0xff, 0x1f, 0x16, 0x00, 0xf6, 0xbc, + 0x7a, 0xaf, 0xd3, 0x61, 0x6a, 0xbb, 0x0e, 0x10, 0xb1, 0x8e, 0x1c, 0x20, 0x32, 0x72, 0xcc, 0x00, + 0x91, 0xc2, 0xb1, 0x02, 0x44, 0x46, 0x8f, 0x1f, 0x20, 0x52, 0x1c, 0x1c, 0x20, 0x62, 0x7f, 0xdd, + 0x82, 0xb3, 0x7d, 0xfb, 0x15, 0xd3, 0xa4, 0x03, 0xdf, 0x8f, 0x06, 0xf8, 0xcf, 0x62, 0x0c, 0x42, + 0x13, 0x8f, 0x2c, 0xc1, 0xb4, 0x7c, 0xe1, 0xa6, 0xde, 0x6d, 0xbb, 0x99, 0x69, 0x99, 0xd6, 0x53, + 0x70, 0xec, 0xab, 0x61, 0xff, 0x6b, 0x0b, 0x2a, 0x46, 0x32, 0x07, 0xee, 0x73, 0xc6, 0x6f, 0xbc, + 0xd2, 0x3e, 0x67, 0xfc, 0xaa, 0x4b, 0xc0, 0xc4, 0x35, 0x74, 0xcb, 0x78, 0xff, 0x20, 0xbe, 0x86, + 0x66, 0xa5, 0x28, 0xa1, 0x22, 0xb3, 0xbd, 0x74, 0x3e, 0x2b, 0x98, 0x99, 0xed, 0x69, 0x57, 0xb8, + 0x9a, 0xc5, 0x2e, 0x6e, 0xa3, 0x87, 0xbb, 0xb8, 0x15, 0xb3, 0x5d, 0xdc, 0xec, 0xdb, 0x30, 0x21, + 0x7c, 0xc3, 0x5f, 0xa5, 0xbb, 0x47, 0x7e, 0x49, 0x99, 0x8d, 0xf6, 0x94, 0xcf, 0x1c, 0xab, 0xce, + 0xca, 0xed, 0x7f, 0x62, 0x41, 0xea, 0x79, 0x2d, 0xe3, 0x06, 0xc6, 0x1a, 0x78, 0x03, 0x63, 0x5a, + 0xed, 0x47, 0x0e, 0xb4, 0xda, 0xdf, 0x04, 0xd2, 0x61, 0x53, 0x21, 0xb9, 0xd0, 0x16, 0x92, 0xaf, + 0x90, 0xac, 0xf6, 0x61, 0x60, 0x46, 0x2d, 0xfb, 0x1f, 0x0b, 0x61, 0xcd, 0x07, 0xb7, 0x0e, 0x6f, + 0x80, 0x1e, 0x14, 0x39, 0x29, 0x69, 0x7f, 0x1b, 0xd2, 0x76, 0xdd, 0x9f, 0x82, 0x2d, 0xee, 0x48, + 0x39, 0xe5, 0x39, 0x37, 0xfb, 0xf7, 0x85, 0xac, 0xe6, 0x8b, 0x5c, 0x87, 0xcb, 0xda, 0x49, 0xca, + 0x7a, 0x23, 0xaf, 0xb5, 0x32, 0x5b, 0x46, 0x32, 0x0f, 0xd0, 0xa5, 0x41, 0x83, 0x7a, 0x91, 0x0a, + 0x69, 0x2b, 0xca, 0xe0, 0x6a, 0x5d, 0x8a, 0x06, 0x86, 0xfd, 0x35, 0x36, 0x81, 0xe2, 0x37, 0xc6, + 0xc9, 0x95, 0xb4, 0x23, 0x70, 0x7a, 0x72, 0x68, 0x3f, 0x60, 0x23, 0xd0, 0x69, 0xe4, 0x90, 0x40, + 0xa7, 0x67, 0x60, 0x3c, 0xf0, 0xdb, 0xb4, 0x1a, 0x78, 0x69, 0x1f, 0x1d, 0x64, 0xc5, 0x78, 0x0b, + 0x15, 0xdc, 0xfe, 0x0d, 0x0b, 0xa6, 0xd3, 0x91, 0x98, 0xb9, 0x7b, 0x27, 0x9b, 0xe9, 0x22, 0x0a, + 0xc7, 0x4f, 0x17, 0x61, 0xff, 0x69, 0x11, 0xa6, 0xd3, 0x6f, 0x1f, 0x32, 0xce, 0x2e, 0x37, 0xb6, + 0xa5, 0x56, 0x7f, 0x61, 0x65, 0x13, 0x30, 0x3d, 0x5e, 0x46, 0x06, 0x8e, 0x97, 0xeb, 0x50, 0xf6, + 0xbb, 0xea, 0xc0, 0x2f, 0x84, 0xbb, 0xa2, 0x8c, 0x35, 0xb7, 0x15, 0xe0, 0xdd, 0xbd, 0xb9, 0x73, + 0xb1, 0x00, 0xba, 0x18, 0xe3, 0xaa, 0xe4, 0x67, 0x94, 0xa5, 0x62, 0x34, 0x91, 0x80, 0x49, 0x5b, + 0x2a, 0xa6, 0xe2, 0xfa, 0x83, 0x8c, 0x15, 0xc5, 0xe3, 0x24, 0x82, 0x19, 0xcb, 0x31, 0x11, 0xcc, + 0x5d, 0x28, 0x4b, 0xdb, 0xea, 0x89, 0x12, 0xa0, 0x70, 0xc2, 0x77, 0x14, 0x01, 0x8c, 0x69, 0xa5, + 0x32, 0xcc, 0x94, 0x72, 0xcd, 0x30, 0xf3, 0x12, 0x8c, 0x6f, 0x38, 0x8d, 0x6d, 0x7f, 0x73, 0x93, + 0xeb, 0xe7, 0xe5, 0xda, 0x07, 0x54, 0xc3, 0xd5, 0x44, 0x71, 0xc6, 0x90, 0x52, 0x35, 0x98, 0x56, + 0x40, 0x95, 0x3b, 0xb2, 0x32, 0xfb, 0x6a, 0xad, 0x40, 0x3b, 0x2a, 0x87, 0x68, 0x60, 0x91, 0x67, + 0xa1, 0xd4, 0x74, 0x43, 0xf1, 0x3a, 0x77, 0x25, 0xe9, 0xad, 0xbe, 0x24, 0xcb, 0x51, 0x63, 0x90, + 0x97, 0xb5, 0xb7, 0xda, 0x44, 0x1c, 0x48, 0xa2, 0x3d, 0xd5, 0x0e, 0x08, 0x24, 0x91, 0xce, 0xb8, + 0x6f, 0xb3, 0x89, 0x19, 0xb9, 0x8d, 0x6d, 0xd7, 0x13, 0x59, 0x45, 0xd8, 0x6a, 0xf1, 0x0c, 0x8c, + 0x53, 0xf9, 0x3e, 0xb8, 0xb8, 0x3a, 0xd1, 0x83, 0x45, 0x3d, 0x0b, 0xae, 0xe0, 0xa4, 0x0a, 0x53, + 0xea, 0xc2, 0x58, 0xdd, 0x77, 0x89, 0x6c, 0x48, 0xda, 0xbe, 0xbe, 0x94, 0x04, 0x63, 0x1a, 0xdf, + 0xfe, 0x3c, 0x54, 0x0c, 0x45, 0x8c, 0xeb, 0x2c, 0x0f, 0x9c, 0x46, 0x9f, 0x7f, 0xf9, 0x35, 0x56, + 0x88, 0x02, 0xc6, 0xaf, 0xe5, 0x44, 0x70, 0x64, 0x6a, 0xaf, 0x97, 0x21, 0x91, 0x12, 0xca, 0x88, + 0x05, 0xb4, 0x45, 0x1f, 0xa8, 0x27, 0x59, 0x14, 0x31, 0x64, 0x85, 0x28, 0x60, 0xf6, 0xb3, 0x50, + 0x52, 0x39, 0xeb, 0x78, 0xe2, 0x27, 0x75, 0x65, 0x64, 0x26, 0x7e, 0xf2, 0x83, 0x08, 0x39, 0xc4, + 0x7e, 0x1d, 0x4a, 0x2a, 0xb5, 0xde, 0xe1, 0xd8, 0x6c, 0xfb, 0x0d, 0x3d, 0xf7, 0x86, 0x1f, 0x46, + 0x2a, 0x1f, 0xa0, 0xb8, 0xd5, 0xbe, 0xb5, 0xcc, 0xcb, 0x50, 0x43, 0xed, 0x3f, 0xb3, 0xa0, 0xb2, + 0xbe, 0xbe, 0xa2, 0x8d, 0x5d, 0x08, 0x8f, 0x86, 0xa2, 0x85, 0xaa, 0x9b, 0x11, 0x35, 0xdd, 0x67, + 0xc4, 0x4a, 0x34, 0xbb, 0xbf, 0x37, 0xf7, 0x68, 0x3d, 0x13, 0x03, 0x07, 0xd4, 0x24, 0xcb, 0x70, + 0xce, 0x84, 0xc8, 0x3c, 0x2d, 0x52, 0x2f, 0xe0, 0x0f, 0xca, 0xd7, 0xfb, 0xc1, 0x98, 0x55, 0x27, + 0x4d, 0x4a, 0xaa, 0xb8, 0xe6, 0xdb, 0xf4, 0xf5, 0x7e, 0x30, 0x66, 0xd5, 0xb1, 0x9f, 0x83, 0xa9, + 0x94, 0x5f, 0xc7, 0x11, 0xf2, 0x63, 0xfd, 0x5e, 0x01, 0x26, 0xcc, 0xeb, 0xfd, 0x23, 0xec, 0xd9, + 0x47, 0x57, 0x85, 0x32, 0xae, 0xe4, 0x0b, 0xc7, 0xbc, 0x92, 0x37, 0x7d, 0x20, 0x46, 0x4f, 0xd7, + 0x07, 0xa2, 0x98, 0x8f, 0x0f, 0x84, 0xe1, 0xab, 0x33, 0xf6, 0xf0, 0x7c, 0x75, 0x7e, 0xb7, 0x08, + 0x93, 0xc9, 0x84, 0xcb, 0x47, 0xe8, 0xc9, 0x67, 0xfb, 0x7a, 0xf2, 0x98, 0x77, 0x80, 0x85, 0x61, + 0xef, 0x00, 0x47, 0x87, 0xbd, 0x03, 0x2c, 0x9e, 0xe0, 0x0e, 0xb0, 0xff, 0x06, 0x6f, 0xec, 0xc8, + 0x37, 0x78, 0x1f, 0xd7, 0x1b, 0xc5, 0x78, 0xc2, 0xed, 0x2d, 0xde, 0x2c, 0x48, 0xb2, 0x1b, 0x16, + 0xfd, 0x66, 0xa6, 0x3b, 0x76, 0xe9, 0x10, 0xf5, 0x21, 0xc8, 0xf4, 0x42, 0x3e, 0xbe, 0x9b, 0xc1, + 0xa3, 0xc7, 0xf0, 0x40, 0x7e, 0x01, 0x2a, 0x72, 0x3c, 0xf1, 0x03, 0x27, 0x24, 0x0f, 0xab, 0xf5, + 0x18, 0x84, 0x26, 0x1e, 0x1b, 0x18, 0xdd, 0x78, 0x82, 0xf0, 0xdb, 0xe8, 0x4a, 0xf2, 0x36, 0x7a, + 0x2d, 0x09, 0xc6, 0x34, 0xbe, 0xfd, 0x39, 0xb8, 0x90, 0x69, 0x76, 0xe4, 0x57, 0x3e, 0xfc, 0x2c, + 0x44, 0x9b, 0x12, 0xc1, 0x10, 0x23, 0xf5, 0x0e, 0xd3, 0xec, 0xdd, 0x81, 0x98, 0x78, 0x00, 0x15, + 0xfb, 0x77, 0x0a, 0x30, 0x99, 0x7c, 0x97, 0x9c, 0xdc, 0xd7, 0x97, 0x14, 0xb9, 0xdc, 0x8f, 0x08, + 0xb2, 0x46, 0x12, 0xdf, 0x81, 0x97, 0x9b, 0xf7, 0xf9, 0xf8, 0xda, 0xd0, 0x19, 0x85, 0x4f, 0x8f, + 0xb1, 0xbc, 0x55, 0x94, 0xec, 0xf8, 0xeb, 0xde, 0x71, 0xbc, 0xbf, 0xb4, 0x5d, 0xe5, 0xce, 0x3d, + 0x0e, 0xcd, 0xd6, 0xac, 0xd0, 0x60, 0xcb, 0xf6, 0x96, 0x1d, 0x1a, 0xb8, 0x9b, 0x2e, 0x6d, 0xca, + 0x07, 0x1e, 0xf8, 0xca, 0xfd, 0xba, 0x2c, 0x43, 0x0d, 0xb5, 0xdf, 0x1e, 0x81, 0x32, 0x4f, 0x4f, + 0x78, 0x3d, 0xf0, 0x3b, 0xfc, 0xc5, 0xda, 0xd0, 0xb0, 0x13, 0xc8, 0x6e, 0xbb, 0x39, 0xec, 0x23, + 0xd4, 0x31, 0x45, 0x19, 0xe2, 0x61, 0x94, 0x60, 0x82, 0x23, 0xe9, 0x42, 0x69, 0x53, 0xa6, 0x53, + 0x97, 0x7d, 0x37, 0x64, 0x4a, 0x60, 0x95, 0x9c, 0x5d, 0x34, 0x81, 0xfa, 0x87, 0x9a, 0x8b, 0xed, + 0xc0, 0x54, 0x2a, 0xbf, 0x54, 0xee, 0x49, 0xd8, 0xff, 0xd7, 0x28, 0x94, 0x75, 0xe4, 0x25, 0xf9, + 0x68, 0xc2, 0x68, 0x1b, 0xeb, 0xf0, 0xd2, 0xda, 0xca, 0xce, 0x4d, 0x1a, 0x39, 0x65, 0x80, 0xbd, + 0x08, 0x85, 0x5e, 0xd0, 0x4e, 0x5b, 0x65, 0xee, 0xe0, 0x0a, 0xb2, 0x72, 0x33, 0x5a, 0xb4, 0xf0, + 0x70, 0xa3, 0x45, 0x2f, 0xc3, 0xe8, 0x86, 0xdf, 0xdc, 0x4d, 0x3f, 0xbf, 0x58, 0xf3, 0x9b, 0xbb, + 0xc8, 0x21, 0xe4, 0x65, 0x98, 0x94, 0x21, 0xb0, 0x4a, 0x89, 0x29, 0x72, 0x3d, 0x55, 0x3b, 0xeb, + 0xac, 0x27, 0xa0, 0x98, 0xc2, 0x66, 0xbb, 0x2c, 0x3b, 0x36, 0xf0, 0xd4, 0xfa, 0x63, 0xc9, 0x9b, + 0xfd, 0x9b, 0xf5, 0xdb, 0xb7, 0xb8, 0xf1, 0x58, 0x63, 0x24, 0xa2, 0x6c, 0xc7, 0x0f, 0x8d, 0xb2, + 0x5d, 0x12, 0xb4, 0x99, 0xb4, 0x7c, 0x47, 0x99, 0xa8, 0x5d, 0x51, 0x74, 0x59, 0xd9, 0x81, 0x67, + 0x17, 0x5d, 0x33, 0x2b, 0x1e, 0xb9, 0xfc, 0xde, 0xc5, 0x23, 0xdb, 0x77, 0x60, 0x2a, 0xd5, 0x7f, + 0xca, 0xa8, 0x67, 0x65, 0x1b, 0xf5, 0x8e, 0xf6, 0x80, 0xe3, 0x3f, 0xb7, 0xe0, 0x6c, 0xdf, 0x8a, + 0x74, 0xd4, 0xc0, 0xf0, 0xf4, 0xde, 0x38, 0x72, 0xf2, 0xbd, 0xb1, 0x70, 0xbc, 0xbd, 0xb1, 0xb6, + 0xf1, 0x9d, 0x1f, 0x5e, 0x7a, 0xe4, 0xfb, 0x3f, 0xbc, 0xf4, 0xc8, 0x1f, 0xfc, 0xf0, 0xd2, 0x23, + 0x6f, 0xef, 0x5f, 0xb2, 0xbe, 0xb3, 0x7f, 0xc9, 0xfa, 0xfe, 0xfe, 0x25, 0xeb, 0x0f, 0xf6, 0x2f, + 0x59, 0xff, 0x75, 0xff, 0x92, 0xf5, 0xf5, 0x3f, 0xbe, 0xf4, 0xc8, 0x27, 0x3f, 0x1e, 0xf7, 0xd4, + 0x82, 0xea, 0x29, 0xfe, 0xe3, 0x43, 0xaa, 0x5f, 0x16, 0xba, 0xdb, 0xad, 0x05, 0xd6, 0x53, 0x0b, + 0xba, 0x44, 0xf5, 0xd4, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x4f, 0x69, 0xab, 0x4b, 0x8a, 0xad, + 0x00, 0x00, } func (m *ALBStatus) Marshal() (dAtA []byte, err error) { @@ -5408,6 +5481,20 @@ func (m *CanaryStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.StepPluginStatuses) > 0 { + for iNdEx := len(m.StepPluginStatuses) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.StepPluginStatuses[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } i -= len(m.StablePingPong) copy(dAtA[i:], m.StablePingPong) i = encodeVarintGenerated(dAtA, i, uint64(len(m.StablePingPong))) @@ -5477,6 +5564,18 @@ func (m *CanaryStep) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Plugin != nil { + { + size, err := m.Plugin.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } if m.SetMirrorRoute != nil { { size, err := m.SetMirrorRoute.MarshalToSizedBuffer(dAtA[:i]) @@ -7966,6 +8065,41 @@ func (m *PingPongSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PluginStep) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PluginStep) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PluginStep) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Config != nil { + i -= len(m.Config) + copy(dAtA[i:], m.Config) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Config))) + i-- + dAtA[i] = 0x12 + } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *PodTemplateMetadata) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -9827,7 +9961,7 @@ func (m *SkyWalkingMetric) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *StickinessConfig) Marshal() (dAtA []byte, err error) { +func (m *StepPluginStatus) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -9837,31 +9971,102 @@ func (m *StickinessConfig) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *StickinessConfig) MarshalTo(dAtA []byte) (int, error) { +func (m *StepPluginStatus) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *StickinessConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *StepPluginStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i = encodeVarintGenerated(dAtA, i, uint64(m.DurationSeconds)) - i-- - dAtA[i] = 0x10 + if m.Status != nil { + i -= len(m.Status) + copy(dAtA[i:], m.Status) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Status))) + i-- + dAtA[i] = 0x62 + } i-- - if m.Enabled { + if m.Disabled { dAtA[i] = 1 } else { dAtA[i] = 0 } i-- + dAtA[i] = 0x58 + i = encodeVarintGenerated(dAtA, i, uint64(m.Executions)) + i-- + dAtA[i] = 0x50 + i -= len(m.Backoff) + copy(dAtA[i:], m.Backoff) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Backoff))) + i-- + dAtA[i] = 0x4a + if m.FinishedAt != nil { + { + size, err := m.FinishedAt.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + if m.UpdatedAt != nil { + { + size, err := m.UpdatedAt.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + if m.StartedAt != nil { + { + size, err := m.StartedAt.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x2a + i -= len(m.Phase) + copy(dAtA[i:], m.Phase) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Phase))) + i-- + dAtA[i] = 0x22 + i -= len(m.Operation) + copy(dAtA[i:], m.Operation) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Operation))) + i-- + dAtA[i] = 0x1a + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0x12 + i = encodeVarintGenerated(dAtA, i, uint64(m.Index)) + i-- dAtA[i] = 0x8 return len(dAtA) - i, nil } -func (m *StringMatch) Marshal() (dAtA []byte, err error) { +func (m *StickinessConfig) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -9871,28 +10076,62 @@ func (m *StringMatch) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *StringMatch) MarshalTo(dAtA []byte) (int, error) { +func (m *StickinessConfig) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *StringMatch) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *StickinessConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - i -= len(m.Regex) - copy(dAtA[i:], m.Regex) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Regex))) + i = encodeVarintGenerated(dAtA, i, uint64(m.DurationSeconds)) i-- - dAtA[i] = 0x1a - i -= len(m.Prefix) - copy(dAtA[i:], m.Prefix) - i = encodeVarintGenerated(dAtA, i, uint64(len(m.Prefix))) + dAtA[i] = 0x10 i-- - dAtA[i] = 0x12 - i -= len(m.Exact) - copy(dAtA[i:], m.Exact) + if m.Enabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func (m *StringMatch) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *StringMatch) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StringMatch) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Regex) + copy(dAtA[i:], m.Regex) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Regex))) + i-- + dAtA[i] = 0x1a + i -= len(m.Prefix) + copy(dAtA[i:], m.Prefix) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Prefix))) + i-- + dAtA[i] = 0x12 + i -= len(m.Exact) + copy(dAtA[i:], m.Exact) i = encodeVarintGenerated(dAtA, i, uint64(len(m.Exact))) i-- dAtA[i] = 0xa @@ -11082,6 +11321,12 @@ func (m *CanaryStatus) Size() (n int) { } l = len(m.StablePingPong) n += 1 + l + sovGenerated(uint64(l)) + if len(m.StepPluginStatuses) > 0 { + for _, e := range m.StepPluginStatuses { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -11118,6 +11363,10 @@ func (m *CanaryStep) Size() (n int) { l = m.SetMirrorRoute.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.Plugin != nil { + l = m.Plugin.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -12016,6 +12265,21 @@ func (m *PingPongSpec) Size() (n int) { return n } +func (m *PluginStep) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + if m.Config != nil { + l = len(m.Config) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *PodTemplateMetadata) Size() (n int) { if m == nil { return 0 @@ -12689,6 +12953,44 @@ func (m *SkyWalkingMetric) Size() (n int) { return n } +func (m *StepPluginStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovGenerated(uint64(m.Index)) + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Operation) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Phase) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Message) + n += 1 + l + sovGenerated(uint64(l)) + if m.StartedAt != nil { + l = m.StartedAt.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.UpdatedAt != nil { + l = m.UpdatedAt.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.FinishedAt != nil { + l = m.FinishedAt.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + l = len(m.Backoff) + n += 1 + l + sovGenerated(uint64(l)) + n += 1 + sovGenerated(uint64(m.Executions)) + n += 2 + if m.Status != nil { + l = len(m.Status) + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *StickinessConfig) Size() (n int) { if m == nil { return 0 @@ -13367,12 +13669,18 @@ func (this *CanaryStatus) String() string { if this == nil { return "nil" } + repeatedStringForStepPluginStatuses := "[]StepPluginStatus{" + for _, f := range this.StepPluginStatuses { + repeatedStringForStepPluginStatuses += strings.Replace(strings.Replace(f.String(), "StepPluginStatus", "StepPluginStatus", 1), `&`, ``, 1) + "," + } + repeatedStringForStepPluginStatuses += "}" s := strings.Join([]string{`&CanaryStatus{`, `CurrentStepAnalysisRunStatus:` + strings.Replace(this.CurrentStepAnalysisRunStatus.String(), "RolloutAnalysisRunStatus", "RolloutAnalysisRunStatus", 1) + `,`, `CurrentBackgroundAnalysisRunStatus:` + strings.Replace(this.CurrentBackgroundAnalysisRunStatus.String(), "RolloutAnalysisRunStatus", "RolloutAnalysisRunStatus", 1) + `,`, `CurrentExperiment:` + fmt.Sprintf("%v", this.CurrentExperiment) + `,`, `Weights:` + strings.Replace(this.Weights.String(), "TrafficWeights", "TrafficWeights", 1) + `,`, `StablePingPong:` + fmt.Sprintf("%v", this.StablePingPong) + `,`, + `StepPluginStatuses:` + repeatedStringForStepPluginStatuses + `,`, `}`, }, "") return s @@ -13389,6 +13697,7 @@ func (this *CanaryStep) String() string { `SetCanaryScale:` + strings.Replace(this.SetCanaryScale.String(), "SetCanaryScale", "SetCanaryScale", 1) + `,`, `SetHeaderRoute:` + strings.Replace(this.SetHeaderRoute.String(), "SetHeaderRoute", "SetHeaderRoute", 1) + `,`, `SetMirrorRoute:` + strings.Replace(this.SetMirrorRoute.String(), "SetMirrorRoute", "SetMirrorRoute", 1) + `,`, + `Plugin:` + strings.Replace(this.Plugin.String(), "PluginStep", "PluginStep", 1) + `,`, `}`, }, "") return s @@ -14077,6 +14386,17 @@ func (this *PingPongSpec) String() string { }, "") return s } +func (this *PluginStep) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&PluginStep{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Config:` + valueToStringGenerated(this.Config) + `,`, + `}`, + }, "") + return s +} func (this *PodTemplateMetadata) String() string { if this == nil { return "nil" @@ -14603,6 +14923,27 @@ func (this *SkyWalkingMetric) String() string { }, "") return s } +func (this *StepPluginStatus) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&StepPluginStatus{`, + `Index:` + fmt.Sprintf("%v", this.Index) + `,`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Operation:` + fmt.Sprintf("%v", this.Operation) + `,`, + `Phase:` + fmt.Sprintf("%v", this.Phase) + `,`, + `Message:` + fmt.Sprintf("%v", this.Message) + `,`, + `StartedAt:` + strings.Replace(fmt.Sprintf("%v", this.StartedAt), "Time", "v1.Time", 1) + `,`, + `UpdatedAt:` + strings.Replace(fmt.Sprintf("%v", this.UpdatedAt), "Time", "v1.Time", 1) + `,`, + `FinishedAt:` + strings.Replace(fmt.Sprintf("%v", this.FinishedAt), "Time", "v1.Time", 1) + `,`, + `Backoff:` + fmt.Sprintf("%v", this.Backoff) + `,`, + `Executions:` + fmt.Sprintf("%v", this.Executions) + `,`, + `Disabled:` + fmt.Sprintf("%v", this.Disabled) + `,`, + `Status:` + valueToStringGenerated(this.Status) + `,`, + `}`, + }, "") + return s +} func (this *StickinessConfig) String() string { if this == nil { return "nil" @@ -19351,6 +19692,40 @@ func (m *CanaryStatus) Unmarshal(dAtA []byte) error { } m.StablePingPong = PingPongType(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StepPluginStatuses", 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.StepPluginStatuses = append(m.StepPluginStatuses, StepPluginStatus{}) + if err := m.StepPluginStatuses[len(m.StepPluginStatuses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -19637,6 +20012,42 @@ func (m *CanaryStep) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Plugin", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Plugin == nil { + m.Plugin = &PluginStep{} + } + if err := m.Plugin.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -27723,7 +28134,7 @@ func (m *PingPongSpec) Unmarshal(dAtA []byte) error { } return nil } -func (m *PodTemplateMetadata) Unmarshal(dAtA []byte) error { +func (m *PluginStep) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -27746,17 +28157,17 @@ func (m *PodTemplateMetadata) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: PodTemplateMetadata: wiretype end group for non-group") + return fmt.Errorf("proto: PluginStep: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: PodTemplateMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: PluginStep: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGenerated @@ -27766,82 +28177,198 @@ func (m *PodTemplateMetadata) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGenerated } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLengthGenerated } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Labels == nil { - m.Labels = make(map[string]string) + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) } - var mapkey string - var mapvalue string - for iNdEx < postIndex { - entryPreIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated } - fieldNum := int32(wire >> 3) - if fieldNum == 1 { - var stringLenmapkey uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLenmapkey |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLenmapkey := int(stringLenmapkey) - if intStringLenmapkey < 0 { - return ErrInvalidLengthGenerated - } - postStringIndexmapkey := iNdEx + intStringLenmapkey - if postStringIndexmapkey < 0 { - return ErrInvalidLengthGenerated - } - if postStringIndexmapkey > l { - return io.ErrUnexpectedEOF - } - mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) - iNdEx = postStringIndexmapkey - } else if fieldNum == 2 { - var stringLenmapvalue uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGenerated - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Config = append(m.Config[:0], dAtA[iNdEx:postIndex]...) + if m.Config == nil { + m.Config = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PodTemplateMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PodTemplateMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PodTemplateMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Labels == nil { + m.Labels = make(map[string]string) + } + var mapkey string + var mapvalue string + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var stringLenmapvalue uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } b := dAtA[iNdEx] iNdEx++ stringLenmapvalue |= uint64(b&0x7F) << shift @@ -33655,6 +34182,416 @@ func (m *SkyWalkingMetric) Unmarshal(dAtA []byte) error { } return nil } +func (m *StepPluginStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StepPluginStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StepPluginStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) + } + m.Index = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Index |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", 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.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", 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.Operation = StepPluginOperation(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Phase", 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.Phase = StepPluginPhase(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", 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.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StartedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.StartedAt == nil { + m.StartedAt = &v1.Time{} + } + if err := m.StartedAt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpdatedAt == nil { + m.UpdatedAt = &v1.Time{} + } + if err := m.UpdatedAt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FinishedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.FinishedAt == nil { + m.FinishedAt = &v1.Time{} + } + if err := m.FinishedAt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Backoff", 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.Backoff = DurationString(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Executions", wireType) + } + m.Executions = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Executions |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Disabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Disabled = bool(v != 0) + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Status = append(m.Status[:0], dAtA[iNdEx:postIndex]...) + if m.Status == nil { + m.Status = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *StickinessConfig) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/pkg/apis/rollouts/v1alpha1/generated.proto b/pkg/apis/rollouts/v1alpha1/generated.proto index 4a384b789f..624c985c21 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -448,6 +448,9 @@ message CanaryStatus { // StablePingPong For the ping-pong feature holds the current stable service, ping or pong optional string stablePingPong = 5; + + // StepPluginStatuses holds the status of the step plugins executed + repeated StepPluginStatus stepPluginStatuses = 6; } // CanaryStep defines a step of a canary deployment. @@ -477,6 +480,9 @@ message CanaryStep { // SetMirrorRoutes Mirrors traffic that matches rules to a particular destination // +optional optional SetMirrorRoute setMirrorRoute = 8; + + // Plugin defines a plugin to execute for a step + optional PluginStep plugin = 9; } // CanaryStrategy defines parameters for a Replica Based Canary @@ -1180,6 +1186,17 @@ message PingPongSpec { optional string pongService = 2; } +message PluginStep { + // Name of the hashicorp go-plugin step to query + optional string name = 1; + + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Config is the configuration object for the specified plugin + optional bytes config = 2; +} + // PodTemplateMetadata extra labels to add to the template message PodTemplateMetadata { // Labels Additional labels to add to the experiment @@ -1755,6 +1772,47 @@ message SkyWalkingMetric { optional string interval = 3; } +message StepPluginStatus { + // Index is the matching step index of the executed plugin + optional int32 index = 1; + + // Name is the matching step name of the executed plugin + optional string name = 2; + + // Operation is the name of the operation that produced this status + optional string operation = 3; + + // Phase is the resulting phase of the operation + optional string phase = 4; + + // Message provides details on why the plugin is in its current phase + optional string message = 5; + + // StartedAt indicates when the plugin was first called for the operation + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time startedAt = 6; + + // UpdatedAt indicates when the plugin was last called for the operation + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time updatedAt = 7; + + // FinishedAt indicates when the operation was completed + optional k8s.io.apimachinery.pkg.apis.meta.v1.Time finishedAt = 8; + + // Backoff is a duration to wait before trying to execute the operation again if it was not completed + optional string backoff = 9; + + // Executions is the number of time the operation was executed + optional int32 executions = 10; + + // Disabled indicates if the plugin is globally disabled + optional bool disabled = 11; + + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Status holds the internal status of the plugin for this operation + optional bytes status = 12; +} + message StickinessConfig { optional bool enabled = 1; diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 7945b82df4..5ba707c6ac 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -97,6 +97,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ObjectRef": schema_pkg_apis_rollouts_v1alpha1_ObjectRef(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PauseCondition": schema_pkg_apis_rollouts_v1alpha1_PauseCondition(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PingPongSpec": schema_pkg_apis_rollouts_v1alpha1_PingPongSpec(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PluginStep": schema_pkg_apis_rollouts_v1alpha1_PluginStep(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PodTemplateMetadata": schema_pkg_apis_rollouts_v1alpha1_PodTemplateMetadata(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PreferredDuringSchedulingIgnoredDuringExecution": schema_pkg_apis_rollouts_v1alpha1_PreferredDuringSchedulingIgnoredDuringExecution(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PrometheusMetric": schema_pkg_apis_rollouts_v1alpha1_PrometheusMetric(ref), @@ -127,6 +128,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetMirrorRoute": schema_pkg_apis_rollouts_v1alpha1_SetMirrorRoute(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.Sigv4Config": schema_pkg_apis_rollouts_v1alpha1_Sigv4Config(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SkyWalkingMetric": schema_pkg_apis_rollouts_v1alpha1_SkyWalkingMetric(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StepPluginStatus": schema_pkg_apis_rollouts_v1alpha1_StepPluginStatus(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StickinessConfig": schema_pkg_apis_rollouts_v1alpha1_StickinessConfig(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StringMatch": schema_pkg_apis_rollouts_v1alpha1_StringMatch(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TCPRoute": schema_pkg_apis_rollouts_v1alpha1_TCPRoute(ref), @@ -1418,11 +1420,25 @@ func schema_pkg_apis_rollouts_v1alpha1_CanaryStatus(ref common.ReferenceCallback Format: "", }, }, + "stepPluginStatuses": { + SchemaProps: spec.SchemaProps{ + Description: "StepPluginStatuses holds the status of the step plugins executed", + 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.StepPluginStatus"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutAnalysisRunStatus", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TrafficWeights"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutAnalysisRunStatus", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StepPluginStatus", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TrafficWeights"}, } } @@ -1476,11 +1492,17 @@ func schema_pkg_apis_rollouts_v1alpha1_CanaryStep(ref common.ReferenceCallback) Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetMirrorRoute"), }, }, + "plugin": { + SchemaProps: spec.SchemaProps{ + Description: "Plugin defines a plugin to execute for a step", + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PluginStep"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutAnalysis", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutExperimentStep", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutPause", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetCanaryScale", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRoute", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetMirrorRoute"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PluginStep", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutAnalysis", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutExperimentStep", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutPause", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetCanaryScale", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRoute", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetMirrorRoute"}, } } @@ -3525,6 +3547,34 @@ func schema_pkg_apis_rollouts_v1alpha1_PingPongSpec(ref common.ReferenceCallback } } +func schema_pkg_apis_rollouts_v1alpha1_PluginStep(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the hashicorp go-plugin step to query", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "config": { + SchemaProps: spec.SchemaProps{ + Description: "Config is the configuration object for the specified plugin", + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + func schema_pkg_apis_rollouts_v1alpha1_PodTemplateMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -5182,6 +5232,105 @@ func schema_pkg_apis_rollouts_v1alpha1_SkyWalkingMetric(ref common.ReferenceCall } } +func schema_pkg_apis_rollouts_v1alpha1_StepPluginStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "index": { + SchemaProps: spec.SchemaProps{ + Description: "Index is the matching step index of the executed plugin", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the matching step name of the executed plugin", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "operation": { + SchemaProps: spec.SchemaProps{ + Description: "Operation is the name of the operation that produced this status", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "Phase is the resulting phase of the operation", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message provides details on why the plugin is in its current phase", + Type: []string{"string"}, + Format: "", + }, + }, + "startedAt": { + SchemaProps: spec.SchemaProps{ + Description: "StartedAt indicates when the plugin was first called for the operation", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "updatedAt": { + SchemaProps: spec.SchemaProps{ + Description: "UpdatedAt indicates when the plugin was last called for the operation", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "finishedAt": { + SchemaProps: spec.SchemaProps{ + Description: "FinishedAt indicates when the operation was completed", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "backoff": { + SchemaProps: spec.SchemaProps{ + Description: "Backoff is a duration to wait before trying to execute the operation again if it was not completed", + Type: []string{"string"}, + Format: "", + }, + }, + "executions": { + SchemaProps: spec.SchemaProps{ + Description: "Executions is the number of time the operation was executed", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "disabled": { + SchemaProps: spec.SchemaProps{ + Description: "Disabled indicates if the plugin is globally disabled", + Type: []string{"boolean"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status holds the internal status of the plugin for this operation", + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + Required: []string{"index", "name", "operation"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + func schema_pkg_apis_rollouts_v1alpha1_StickinessConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 47bc6a0f4b..8886e292be 100755 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -2,6 +2,7 @@ package v1alpha1 import ( "encoding/json" + fmt "fmt" "strconv" "time" @@ -638,6 +639,19 @@ type CanaryStep struct { // SetMirrorRoutes Mirrors traffic that matches rules to a particular destination // +optional SetMirrorRoute *SetMirrorRoute `json:"setMirrorRoute,omitempty" protobuf:"bytes,8,opt,name=setMirrorRoute"` + // Plugin defines a plugin to execute for a step + Plugin *PluginStep `json:"plugin,omitempty" protobuf:"bytes,9,opt,name=plugin"` +} + +type PluginStep struct { + // Name of the hashicorp go-plugin step to query + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Config is the configuration object for the specified plugin + Config json.RawMessage `json:"config,omitempty" protobuf:"bytes,2,opt,name=config"` } type SetMirrorRoute struct { @@ -986,6 +1000,8 @@ type CanaryStatus struct { Weights *TrafficWeights `json:"weights,omitempty" protobuf:"bytes,4,opt,name=weights"` // StablePingPong For the ping-pong feature holds the current stable service, ping or pong StablePingPong PingPongType `json:"stablePingPong,omitempty" protobuf:"bytes,5,opt,name=stablePingPong"` + // StepPluginStatuses holds the status of the step plugins executed + StepPluginStatuses []StepPluginStatus `json:"stepPluginStatuses,omitempty" protobuf:"bytes,6,rep,name=stepPluginStatuses"` } type PingPongType string @@ -1023,6 +1039,78 @@ type RolloutAnalysisRunStatus struct { Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"` } +type StepPluginStatus struct { + // Index is the matching step index of the executed plugin + Index int32 `json:"index" protobuf:"bytes,1,name=index"` + // Name is the matching step name of the executed plugin + Name string `json:"name" protobuf:"bytes,2,name=name"` + // Operation is the name of the operation that produced this status + Operation StepPluginOperation `json:"operation" protobuf:"bytes,3,name=operation,casttype=StepPluginOperation"` + // Phase is the resulting phase of the operation + Phase StepPluginPhase `json:"phase,omitempty" protobuf:"bytes,4,opt,name=phase,casttype=StepPluginPhase"` + // Message provides details on why the plugin is in its current phase + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` + // StartedAt indicates when the plugin was first called for the operation + StartedAt *metav1.Time `json:"startedAt,omitempty" protobuf:"bytes,6,name=startedAt"` + // UpdatedAt indicates when the plugin was last called for the operation + UpdatedAt *metav1.Time `json:"updatedAt,omitempty" protobuf:"bytes,7,opt,name=updatedAt"` + // FinishedAt indicates when the operation was completed + FinishedAt *metav1.Time `json:"finishedAt,omitempty" protobuf:"bytes,8,opt,name=finishedAt"` + // Backoff is a duration to wait before trying to execute the operation again if it was not completed + Backoff DurationString `json:"backoff,omitempty" protobuf:"bytes,9,opt,name=backoff,casttype=DurationString"` + // Executions is the number of time the operation was executed + Executions int32 `json:"executions,omitempty" protobuf:"varint,10,opt,name=executions"` + // Disabled indicates if the plugin is globally disabled + Disabled bool `json:"disabled,omitempty" protobuf:"bytes,11,opt,name=disabled"` + + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Status holds the internal status of the plugin for this operation + Status json.RawMessage `json:"status,omitempty" protobuf:"bytes,12,opt,name=status"` +} + +// StepPluginPhase is the overall phase of a StepPlugin +type StepPluginPhase string + +// Possible StepPluginPhase values +const ( + // StepPluginPhaseRunning is the phase of a step plugin when it has not completed its execution + StepPluginPhaseRunning StepPluginPhase = "Running" + // StepPluginPhaseSuccessful is the phase of a step plugin when the operation completed successfully + StepPluginPhaseSuccessful StepPluginPhase = "Successful" + // StepPluginPhaseFailed is the phase of a step plugin when the operation completed unsuccessfully + StepPluginPhaseFailed StepPluginPhase = "Failed" + // StepPluginPhaseError is the phase of a step plugin when an unexpected error prevented the completion of the operation + StepPluginPhaseError StepPluginPhase = "Error" +) + +// Validate that the object is a valid phase +func (p StepPluginPhase) Validate() error { + switch p { + case StepPluginPhaseRunning: + case StepPluginPhaseSuccessful: + case StepPluginPhaseFailed: + case StepPluginPhaseError: + default: + return fmt.Errorf("phase '%s' is not valid", p) + } + return nil +} + +// StepPluginOperation is the operation executed by a step plugin +type StepPluginOperation string + +// Possible StepPluginOperation values +const ( + // StepPluginOperationRun is the value for the Run operation + StepPluginOperationRun StepPluginOperation = "Run" + // StepPluginOperationRun is the value for the Terminate operation + StepPluginOperationTerminate StepPluginOperation = "Terminate" + // StepPluginOperationRun is the value for the Abort operation + StepPluginOperationAbort StepPluginOperation = "Abort" +) + 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"` diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index 2816852447..f2497499fd 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -794,6 +794,13 @@ func (in *CanaryStatus) DeepCopyInto(out *CanaryStatus) { *out = new(TrafficWeights) (*in).DeepCopyInto(*out) } + if in.StepPluginStatuses != nil { + in, out := &in.StepPluginStatuses, &out.StepPluginStatuses + *out = make([]StepPluginStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -845,6 +852,11 @@ func (in *CanaryStep) DeepCopyInto(out *CanaryStep) { *out = new(SetMirrorRoute) (*in).DeepCopyInto(*out) } + if in.Plugin != nil { + in, out := &in.Plugin, &out.Plugin + *out = new(PluginStep) + (*in).DeepCopyInto(*out) + } return } @@ -1938,6 +1950,27 @@ func (in *PingPongSpec) DeepCopy() *PingPongSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PluginStep) DeepCopyInto(out *PluginStep) { + *out = *in + if in.Config != nil { + in, out := &in.Config, &out.Config + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginStep. +func (in *PluginStep) DeepCopy() *PluginStep { + if in == nil { + return nil + } + out := new(PluginStep) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodTemplateMetadata) DeepCopyInto(out *PodTemplateMetadata) { *out = *in @@ -2767,6 +2800,39 @@ func (in *SkyWalkingMetric) DeepCopy() *SkyWalkingMetric { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StepPluginStatus) DeepCopyInto(out *StepPluginStatus) { + *out = *in + if in.StartedAt != nil { + in, out := &in.StartedAt, &out.StartedAt + *out = (*in).DeepCopy() + } + if in.UpdatedAt != nil { + in, out := &in.UpdatedAt, &out.UpdatedAt + *out = (*in).DeepCopy() + } + if in.FinishedAt != nil { + in, out := &in.FinishedAt, &out.FinishedAt + *out = (*in).DeepCopy() + } + if in.Status != nil { + in, out := &in.Status, &out.Status + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StepPluginStatus. +func (in *StepPluginStatus) DeepCopy() *StepPluginStatus { + if in == nil { + return nil + } + out := new(StepPluginStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StickinessConfig) DeepCopyInto(out *StickinessConfig) { *out = *in diff --git a/pkg/apis/rollouts/validation/validation.go b/pkg/apis/rollouts/validation/validation.go index 8eb7cdac19..6b4ddb45e2 100644 --- a/pkg/apis/rollouts/validation/validation.go +++ b/pkg/apis/rollouts/validation/validation.go @@ -48,8 +48,8 @@ const ( InvalidDurationMessage = "Duration needs to be greater than 0" // InvalidMaxSurgeMaxUnavailable indicates both maxSurge and MaxUnavailable can not be set to zero InvalidMaxSurgeMaxUnavailable = "MaxSurge and MaxUnavailable both can not be zero" - // InvalidStepMessage indicates that a step must have either setWeight or pause set - InvalidStepMessage = "Step must have one of the following set: experiment, setWeight, setCanaryScale or pause" + // InvalidStepMessage indicates that a step must have either experiment, setWeight, setCanaryScale, plugin or pause + InvalidStepMessage = "Step must have one of the following set: experiment, setWeight, setCanaryScale, plugin or pause" // InvalidStrategyMessage indicates that multiple strategies can not be listed InvalidStrategyMessage = "Multiple Strategies can not be listed" // DuplicatedServicesBlueGreenMessage the message to indicate that the rollout uses the same service for the active and preview services @@ -308,9 +308,9 @@ func ValidateRolloutStrategyCanary(rollout *v1alpha1.Rollout, fldPath *field.Pat stepFldPath := fldPath.Child("steps").Index(i) allErrs = append(allErrs, hasMultipleStepsType(step, stepFldPath)...) if step.Experiment == nil && step.Pause == nil && step.SetWeight == nil && step.Analysis == nil && step.SetCanaryScale == nil && - step.SetHeaderRoute == nil && step.SetMirrorRoute == nil { - errVal := fmt.Sprintf("step.Experiment: %t step.Pause: %t step.SetWeight: %t step.Analysis: %t step.SetCanaryScale: %t step.SetHeaderRoute: %t step.SetMirrorRoutes: %t", - step.Experiment == nil, step.Pause == nil, step.SetWeight == nil, step.Analysis == nil, step.SetCanaryScale == nil, step.SetHeaderRoute == nil, step.SetMirrorRoute == nil) + step.SetHeaderRoute == nil && step.SetMirrorRoute == nil && step.Plugin == nil { + errVal := fmt.Sprintf("step.Experiment: %t step.Pause: %t step.SetWeight: %t step.Analysis: %t step.SetCanaryScale: %t step.SetHeaderRoute: %t step.SetMirrorRoute: %t step.Plugin: %t", + step.Experiment == nil, step.Pause == nil, step.SetWeight == nil, step.Analysis == nil, step.SetCanaryScale == nil, step.SetHeaderRoute == nil, step.SetMirrorRoute == nil, step.Plugin == nil) allErrs = append(allErrs, field.Invalid(stepFldPath, errVal, InvalidStepMessage)) } diff --git a/rollout/canary.go b/rollout/canary.go index a3033fea02..422ed3644d 100644 --- a/rollout/canary.go +++ b/rollout/canary.go @@ -87,6 +87,11 @@ func (c *rolloutContext) rolloutCanary() error { return c.syncRolloutStatusCanary() } + err = c.stepPluginContext.reconcile(c) + if err != nil { + return err + } + return c.syncRolloutStatusCanary() } @@ -315,7 +320,7 @@ func (c *rolloutContext) completedCurrentCanaryStep() bool { if c.rollout.Spec.Paused { return false } - currentStep, _ := replicasetutil.GetCurrentCanaryStep(c.rollout) + currentStep, currentStepIndex := replicasetutil.GetCurrentCanaryStep(c.rollout) if currentStep == nil { return false } @@ -344,6 +349,8 @@ func (c *rolloutContext) completedCurrentCanaryStep() bool { return true case currentStep.SetMirrorRoute != nil: return true + case currentStep.Plugin != nil: + return c.stepPluginContext.isStepPluginCompleted(*currentStepIndex, currentStep.Plugin) } return false } @@ -354,6 +361,8 @@ func (c *rolloutContext) syncRolloutStatusCanary() error { newStatus.HPAReplicas = replicasetutil.GetActualReplicaCountForReplicaSets(c.allRSs) newStatus.Selector = metav1.FormatLabelSelector(c.rollout.Spec.Selector) newStatus.Canary.StablePingPong = c.rollout.Status.Canary.StablePingPong + newStatus.Canary.StepPluginStatuses = c.rollout.Status.Canary.StepPluginStatuses + c.stepPluginContext.updateStatus(&newStatus) currentStep, currentStepIndex := replicasetutil.GetCurrentCanaryStep(c.rollout) newStatus.StableRS = c.rollout.Status.StableRS diff --git a/rollout/context.go b/rollout/context.go index f8fa5b5f03..c595a5718b 100644 --- a/rollout/context.go +++ b/rollout/context.go @@ -39,8 +39,9 @@ type rolloutContext struct { currentEx *v1alpha1.Experiment otherExs []*v1alpha1.Experiment - newStatus v1alpha1.RolloutStatus - pauseContext *pauseContext + newStatus v1alpha1.RolloutStatus + pauseContext *pauseContext + stepPluginContext *stepPluginContext // targetsVerified indicates if the pods targets have been verified with underlying LoadBalancer. // This is used in pod-aware flat networks where LoadBalancers target Pods and not Nodes. diff --git a/rollout/controller.go b/rollout/controller.go index 23f7f340dd..8c5c905ef1 100644 --- a/rollout/controller.go +++ b/rollout/controller.go @@ -49,6 +49,7 @@ import ( clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" informers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1" listers "github.com/argoproj/argo-rollouts/pkg/client/listers/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin" "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/ambassador" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/appmesh" @@ -527,6 +528,10 @@ func (c *Controller) newRolloutContext(rollout *v1alpha1.Rollout) (*rolloutConte rollout: rollout, log: logCtx, }, + stepPluginContext: &stepPluginContext{ + resolver: plugin.NewResolver(), + log: logCtx, + }, reconcilerBase: c.reconcilerBase, } if rolloututil.IsFullyPromoted(rollout) && roCtx.pauseContext.IsAborted() { diff --git a/rollout/stepplugin.go b/rollout/stepplugin.go new file mode 100644 index 0000000000..eb777f6c70 --- /dev/null +++ b/rollout/stepplugin.go @@ -0,0 +1,280 @@ +package rollout + +import ( + "fmt" + "time" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin" + "github.com/argoproj/argo-rollouts/utils/conditions" + "github.com/argoproj/argo-rollouts/utils/record" + replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" + rolloututil "github.com/argoproj/argo-rollouts/utils/rollout" + log "github.com/sirupsen/logrus" +) + +var ( + defaultControllerErrorBackoff = time.Second * 30 + defaultBackoffDelay = time.Second * 5 +) + +type stepPluginContext struct { + resolver plugin.Resolver + log *log.Entry + + stepPluginStatuses []v1alpha1.StepPluginStatus + hasError bool +} + +func (spc *stepPluginContext) reconcile(c *rolloutContext) error { + rollout := c.rollout.DeepCopy() + spc.stepPluginStatuses = rollout.Status.Canary.StepPluginStatuses + + //On abort, we need to abort all successful previous steps + if c.pauseContext.IsAborted() { + for i := len(spc.stepPluginStatuses) - 1; i >= 0; i-- { + pluginStatus := spc.stepPluginStatuses[i] + if pluginStatus.Operation != v1alpha1.StepPluginOperationRun { + // Only call abort for Run operation. + continue + } + pluginStep := rollout.Spec.Strategy.Canary.Steps[pluginStatus.Index] + if pluginStep.Plugin == nil { + continue + } + + stepPlugin, err := spc.resolver.Resolve(pluginStatus.Index, *pluginStep.Plugin, c.log) + if err != nil { + return spc.handleError(c, fmt.Errorf("could not create step plugin at index %d : %w", pluginStatus.Index, err)) + } + status, err := stepPlugin.Abort(rollout) + if err != nil { + return spc.handleError(c, fmt.Errorf("failed to abort plugin: %w", err)) + } + phaseTransition := spc.updateStepPluginStatus(status) + if phaseTransition { + spc.recordPhase(c, status) + } + } + return nil + } + + // On full promotion, we want to Terminate only the last step still in Running, if any + if rollout.Status.PromoteFull || rolloututil.IsFullyPromoted(rollout) { + stepIndex := spc.getStepToTerminate(rollout) + if stepIndex == nil { + return nil + } + + pluginStep := rollout.Spec.Strategy.Canary.Steps[*stepIndex] + if pluginStep.Plugin == nil { + return nil + } + + stepPlugin, err := spc.resolver.Resolve(*stepIndex, *pluginStep.Plugin, c.log) + if err != nil { + return spc.handleError(c, fmt.Errorf("could not create step plugin at index %d : %w", *stepIndex, err)) + } + + status, err := stepPlugin.Terminate(rollout) + if err != nil { + return spc.handleError(c, fmt.Errorf("failed to terminate plugin: %w", err)) + } + + phaseTransition := spc.updateStepPluginStatus(status) + if phaseTransition { + spc.recordPhase(c, status) + } + return nil + } + + // If we retry an aborted rollout, we need to have a clean status + spc.cleanStatusForRetry(rollout) + + // Normal execution flow of a step plugin + currentStep, currentStepIndex := replicasetutil.GetCurrentCanaryStep(rollout) + if currentStep == nil || currentStep.Plugin == nil { + return nil + } + + if rollout.Status.Phase != v1alpha1.RolloutPhaseProgressing { + spc.log.Debug("Not reconciling step plugin because it is not progressing") + return nil + } + + stepPlugin, err := spc.resolver.Resolve(*currentStepIndex, *currentStep.Plugin, c.log) + if err != nil { + return spc.handleError(c, fmt.Errorf("could not create step plugin at index %d : %w", *currentStepIndex, err)) + } + status, err := stepPlugin.Run(rollout) + if err != nil { + return spc.handleError(c, fmt.Errorf("failed to run plugin: %w", err)) + } + + phaseTransition := spc.updateStepPluginStatus(status) + if phaseTransition { + spc.recordPhase(c, status) + } + + if status == nil || status.Disabled { + return nil + } + + if status.Phase == v1alpha1.StepPluginPhaseRunning || status.Phase == v1alpha1.StepPluginPhaseError { + backoff, err := status.Backoff.Duration() + if err != nil { + return spc.handleError(c, fmt.Errorf("failed to parse backoff duration: %w", err)) + } + + // Get the remaining time until the backoff + a little buffer + remaining := time.Until(status.UpdatedAt.Add(backoff)) + defaultBackoffDelay + c.log.Debugf("queueing up rollout in %s because step plugin phase is %s", remaining, status.Phase) + c.enqueueRolloutAfter(rollout, remaining) + return nil + } + + if status.Phase == v1alpha1.StepPluginPhaseFailed { + c.pauseContext.AddAbort(fmt.Sprintf("Step Plugin %d (%s) failed: %s", status.Index+1, status.Name, status.Message)) + } + + return nil +} + +// handleError handles any error that should not cause the rollout reconciliation to fail +func (spc *stepPluginContext) handleError(c *rolloutContext, e error) error { + spc.hasError = true + + msg := fmt.Sprintf(conditions.RolloutReconciliationErrorMessage, e.Error()) + c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.RolloutReconciliationErrorReason}, msg) + + c.log.Debugf("queueing up rollout in %s because of transient error", defaultControllerErrorBackoff) + c.enqueueRolloutAfter(c.rollout, defaultControllerErrorBackoff) + + return nil +} + +func (spc *stepPluginContext) recordPhase(c *rolloutContext, status *v1alpha1.StepPluginStatus) { + if status.Disabled || status.Operation == v1alpha1.StepPluginOperationRun && status.Phase == v1alpha1.StepPluginPhaseSuccessful { + // If the run status is successful, do not record event because the controller will record the RolloutStepCompleted + return + } + + msg := fmt.Sprintf(conditions.StepPluginTransitionRunMessage, status.Name, status.Index+1, status.Phase) + if status.Operation == v1alpha1.StepPluginOperationAbort { + msg = fmt.Sprintf(conditions.StepPluginTransitionAbortMessage, status.Name, status.Index+1, status.Phase) + } else if status.Operation == v1alpha1.StepPluginOperationTerminate { + msg = fmt.Sprintf(conditions.StepPluginTransitionTerminateMessage, status.Name, status.Index+1, status.Phase) + } + + if status.Phase == v1alpha1.StepPluginPhaseError || status.Phase == v1alpha1.StepPluginPhaseFailed { + c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.StepPluginTransitionReason}, msg) + } else { + c.recorder.Eventf(c.rollout, record.EventOptions{EventReason: conditions.StepPluginTransitionReason}, msg) + } +} + +func (spc *stepPluginContext) updateStatus(status *v1alpha1.RolloutStatus) { + if spc.stepPluginStatuses != nil { + status.Canary.StepPluginStatuses = spc.stepPluginStatuses + } +} + +func (spc *stepPluginContext) isStepPluginCompleted(stepIndex int32, _ *v1alpha1.PluginStep) bool { + if spc.hasError { + // If there was a transient error during the reconcile, we should retry + return false + } + + runStatus := spc.findCurrentStepStatus(stepIndex, v1alpha1.StepPluginOperationRun) + if runStatus != nil && runStatus.Disabled { + return true + } + + isRunning := runStatus != nil && runStatus.Phase == v1alpha1.StepPluginPhaseRunning + if isRunning { + terminateStatus := spc.findCurrentStepStatus(stepIndex, v1alpha1.StepPluginOperationTerminate) + abortStatus := spc.findCurrentStepStatus(stepIndex, v1alpha1.StepPluginOperationAbort) + isRunning = terminateStatus == nil && abortStatus == nil + } + return runStatus != nil && + ((!isRunning && runStatus.Phase == v1alpha1.StepPluginPhaseRunning) || + runStatus.Phase == v1alpha1.StepPluginPhaseFailed || + runStatus.Phase == v1alpha1.StepPluginPhaseSuccessful) +} + +func (spc *stepPluginContext) findCurrentStepStatus(stepIndex int32, operation v1alpha1.StepPluginOperation) *v1alpha1.StepPluginStatus { + for _, s := range spc.stepPluginStatuses { + if s.Index == stepIndex && s.Operation == operation { + return &s + } + } + return nil +} + +func (spc *stepPluginContext) updateStepPluginStatus(status *v1alpha1.StepPluginStatus) bool { + phaseChanged := false + if status == nil { + return phaseChanged + } + + // Update new status and preserve order + statusUpdated := false + for i, s := range spc.stepPluginStatuses { + if !statusUpdated && s.Index == status.Index && s.Operation == status.Operation { + spc.stepPluginStatuses[i] = *status + statusUpdated = true + phaseChanged = s.Phase != status.Phase + break + } + } + if !statusUpdated { + spc.stepPluginStatuses = append(spc.stepPluginStatuses, *status) + phaseChanged = true + } + + return phaseChanged +} + +func (spc *stepPluginContext) getStepToTerminate(rollout *v1alpha1.Rollout) *int32 { + for i := len(rollout.Status.Canary.StepPluginStatuses) - 1; i >= 0; i-- { + pluginStep := rollout.Status.Canary.StepPluginStatuses[i] + + if pluginStep.Operation == v1alpha1.StepPluginOperationTerminate && pluginStep.Phase == v1alpha1.StepPluginPhaseSuccessful { + // last running step is already terminated + return nil + } + + if pluginStep.Operation == v1alpha1.StepPluginOperationRun && pluginStep.Phase == v1alpha1.StepPluginPhaseRunning { + // found the last running step + return &pluginStep.Index + } + } + return nil +} + +// cleanStatusForRetry is expected to be called on a non-aborted rollout. +// It validates that stepPluginStatuses does not contain outdated results, and remove them +// if it does. +func (spc *stepPluginContext) cleanStatusForRetry(rollout *v1alpha1.Rollout) { + if len(spc.stepPluginStatuses) == 0 || rollout.Status.Abort { + return + } + + currentStep, currentStepIndex := replicasetutil.GetCurrentCanaryStep(rollout) + if currentStep == nil || int(*currentStepIndex) > 0 { + // Nothing to clean if rollout steps are completed or in progress + return + } + + // if we are at step 0, it could be either that we haven't started, or we are progressing. + // if step 0 is a plugin, check if it is completed. In that case, we know that we should be at step > 0 + // abd we are retrying + shouldCleanCurrentStatus := true + if currentStep.Plugin != nil { + shouldCleanCurrentStatus = spc.isStepPluginCompleted(*currentStepIndex, currentStep.Plugin) + } + + if shouldCleanCurrentStatus { + spc.stepPluginStatuses = []v1alpha1.StepPluginStatus{} + } +} diff --git a/rollout/stepplugin_test.go b/rollout/stepplugin_test.go new file mode 100644 index 0000000000..b3959b05e1 --- /dev/null +++ b/rollout/stepplugin_test.go @@ -0,0 +1,896 @@ +package rollout + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/mocks" + logutil "github.com/argoproj/argo-rollouts/utils/log" + "github.com/argoproj/argo-rollouts/utils/record" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" +) + +func newStepPluginRollout() *v1alpha1.Rollout { + steps := []v1alpha1.CanaryStep{ + { + Plugin: &v1alpha1.PluginStep{ + Name: "test-plugin", + }, + }, + } + return newCanaryRollout("foo", 3, nil, steps, ptr.To(int32(0)), intstr.FromInt(1), intstr.FromInt(0)) +} + +func newStepPluginStatus(operation v1alpha1.StepPluginOperation, phase v1alpha1.StepPluginPhase) *v1alpha1.StepPluginStatus { + now := metav1.Now() + status := &v1alpha1.StepPluginStatus{ + Index: 0, + Name: "test-plugin", + Message: "this is a default message", + Status: json.RawMessage("value"), + Operation: operation, + Phase: phase, + StartedAt: &now, + UpdatedAt: &now, + Executions: 1, + Backoff: "0s", + } + return status +} + +func Test_stepPluginContext_reconcile_ReconciliationError(t *testing.T) { + stepPluginResolver := mocks.NewResolver(t) + r := newStepPluginRollout() + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logutil.WithRollout(r), + reconcilerBase: reconcilerBase{ + enqueueRollout: func(obj any) { t.Error("enqueueRollout should not be called") }, + enqueueRolloutAfter: func(obj any, duration time.Duration) { t.Error("enqueueRolloutAfter should not be called") }, + recorder: record.NewFakeEventRecorder(), + }, + pauseContext: &pauseContext{ + rollout: r, + log: logCtx, + }, + stepPluginContext: &stepPluginContext{ + resolver: stepPluginResolver, + log: logCtx, + }, + } + + stepPluginResolver.On("Resolve", mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("test error")) + + var requeuedAfter time.Duration + roCtx.enqueueRolloutAfter = func(obj any, duration time.Duration) { + requeuedAfter = duration + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + assert.Equal(t, roCtx.rollout.Status.Canary.StepPluginStatuses, roCtx.stepPluginContext.stepPluginStatuses) + assert.Equal(t, defaultControllerErrorBackoff, requeuedAfter) + assert.True(t, roCtx.stepPluginContext.hasError) + +} + +func Test_stepPluginContext_reconcile_SuccessfulReconciliation(t *testing.T) { + setup := func(t *testing.T) (*rolloutContext, *v1alpha1.StepPluginStatus) { + stepPluginResolver := mocks.NewResolver(t) + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", int32(0), mock.Anything, mock.Anything).Return(stepPluginMock, nil) + + r := newStepPluginRollout() + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logutil.WithRollout(r), + reconcilerBase: reconcilerBase{ + enqueueRollout: func(obj any) { t.Error("enqueueRollout should not be called") }, + enqueueRolloutAfter: func(obj any, duration time.Duration) { t.Error("enqueueRolloutAfter should not be called") }, + recorder: record.NewFakeEventRecorder(), + }, + pauseContext: &pauseContext{ + rollout: r, + log: logCtx, + }, + stepPluginContext: &stepPluginContext{ + resolver: stepPluginResolver, + log: logCtx, + }, + } + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationRun, v1alpha1.StepPluginPhaseSuccessful) + stepPluginMock.On("Run", r).Return(runStatus, nil) + return roCtx, runStatus + } + + t.Run("Status is added when not present", func(t *testing.T) { + roCtx, runStatus := setup(t) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[0], *runStatus) + }) + t.Run("Status is updated when existing", func(t *testing.T) { + roCtx, runStatus := setup(t) + + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Message: "this is the existing status", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + }, + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[0], *runStatus) + }) + t.Run("Status order is preserved when updating", func(t *testing.T) { + roCtx, runStatus := setup(t) + + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 123, + Name: runStatus.Name, + Operation: v1alpha1.StepPluginOperationRun, + }, + { + Index: runStatus.Index, + Name: runStatus.Name, + Operation: v1alpha1.StepPluginOperationRun, + }, + { + Index: 456, + Name: "other", + Operation: v1alpha1.StepPluginOperationRun, + }, + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 3) + assert.Equal(t, int32(123), roCtx.stepPluginContext.stepPluginStatuses[0].Index) + assert.Equal(t, runStatus.Index, roCtx.stepPluginContext.stepPluginStatuses[1].Index) + assert.Equal(t, int32(456), roCtx.stepPluginContext.stepPluginStatuses[2].Index) + }) +} + +func Test_stepPluginContext_reconcile_RunningReconciliation(t *testing.T) { + setup := func(t *testing.T, phase v1alpha1.StepPluginPhase, backoff *time.Duration) (*rolloutContext, *v1alpha1.StepPluginStatus) { + stepPluginResolver := mocks.NewResolver(t) + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", int32(0), mock.Anything, mock.Anything).Return(stepPluginMock, nil) + + r := newStepPluginRollout() + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logutil.WithRollout(r), + reconcilerBase: reconcilerBase{ + enqueueRollout: func(obj any) { t.Error("enqueueRollout should not be called") }, + enqueueRolloutAfter: func(obj any, duration time.Duration) {}, + recorder: record.NewFakeEventRecorder(), + }, + pauseContext: &pauseContext{ + rollout: r, + log: logCtx, + }, + stepPluginContext: &stepPluginContext{ + resolver: stepPluginResolver, + log: logCtx, + }, + } + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationRun, phase) + if backoff != nil { + runStatus.Backoff = v1alpha1.DurationString(backoff.String()) + } + stepPluginMock.On("Run", r).Return(runStatus, nil) + return roCtx, runStatus + } + + t.Run("Status is added when not present", func(t *testing.T) { + roCtx, runStatus := setup(t, v1alpha1.StepPluginPhaseRunning, nil) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[0], *runStatus) + }) + t.Run("Status is updated when existing", func(t *testing.T) { + roCtx, runStatus := setup(t, v1alpha1.StepPluginPhaseRunning, nil) + + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Message: "this is the existing status", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + }, + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[0], *runStatus) + }) + t.Run("Rollout is added to the queue", func(t *testing.T) { + expectedRequeueAfter := 123 * time.Second + roCtx, _ := setup(t, v1alpha1.StepPluginPhaseRunning, &expectedRequeueAfter) + + var requeuedAfter time.Duration + roCtx.enqueueRolloutAfter = func(obj any, duration time.Duration) { + requeuedAfter = duration + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + assert.GreaterOrEqual(t, requeuedAfter, expectedRequeueAfter) + assert.LessOrEqual(t, requeuedAfter, expectedRequeueAfter+defaultBackoffDelay) + }) +} + +func Test_stepPluginContext_reconcile_FailedReconciliation(t *testing.T) { + setup := func(t *testing.T, phase v1alpha1.StepPluginPhase) (*rolloutContext, *v1alpha1.StepPluginStatus) { + stepPluginResolver := mocks.NewResolver(t) + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", int32(0), mock.Anything, mock.Anything).Return(stepPluginMock, nil) + + r := newStepPluginRollout() + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logCtx, + reconcilerBase: reconcilerBase{ + enqueueRollout: func(obj any) { t.Error("enqueueRollout should not be called") }, + enqueueRolloutAfter: func(obj any, duration time.Duration) { t.Error("enqueueRolloutAfter should not be called") }, + recorder: record.NewFakeEventRecorder(), + }, + pauseContext: &pauseContext{ + rollout: r, + log: logCtx, + }, + stepPluginContext: &stepPluginContext{ + resolver: stepPluginResolver, + log: logCtx, + }, + } + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationRun, phase) + stepPluginMock.On("Run", r).Return(runStatus, nil) + return roCtx, runStatus + } + + t.Run("Status is added when not present", func(t *testing.T) { + roCtx, runStatus := setup(t, v1alpha1.StepPluginPhaseFailed) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[0], *runStatus) + }) + t.Run("Status is updated when existing", func(t *testing.T) { + roCtx, runStatus := setup(t, v1alpha1.StepPluginPhaseFailed) + + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Message: "this is the existing status", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + }, + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[0], *runStatus) + }) + t.Run("Rollout is aborted", func(t *testing.T) { + roCtx, failedStatus := setup(t, v1alpha1.StepPluginPhaseFailed) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + assert.True(t, roCtx.pauseContext.IsAborted()) + assert.Contains(t, roCtx.pauseContext.abortMessage, failedStatus.Message) + }) +} + +func Test_stepPluginContext_reconcile_FullyPromoted(t *testing.T) { + newRolloutContext := func(t *testing.T) *rolloutContext { + r := newStepPluginRollout() + + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logCtx, + reconcilerBase: reconcilerBase{ + enqueueRollout: func(obj any) { t.Error("enqueueRollout should not be called") }, + enqueueRolloutAfter: func(obj any, duration time.Duration) { t.Error("enqueueRolloutAfter should not be called") }, + recorder: record.NewFakeEventRecorder(), + }, + pauseContext: &pauseContext{ + rollout: r, + log: logCtx, + }, + stepPluginContext: &stepPluginContext{ + log: logCtx, + }, + } + return roCtx + } + setup := func(t *testing.T) (*rolloutContext, *mocks.StepPlugin) { + stepPluginResolver := mocks.NewResolver(t) + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", int32(0), mock.Anything, mock.Anything).Return(stepPluginMock, nil) + + roCtx := newRolloutContext(t) + roCtx.stepPluginContext.resolver = stepPluginResolver + + return roCtx, stepPluginMock + } + + t.Run("Rollout is Terminated on full promotion", func(t *testing.T) { + roCtx, stepPluginMock := setup(t) + roCtx.rollout.Status.PromoteFull = true + roCtx.rollout.Status.StableRS = "stable-value" + roCtx.rollout.Status.CurrentPodHash = "current-value" + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationTerminate, v1alpha1.StepPluginPhaseSuccessful) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + }, + } + + stepPluginMock.On("Terminate", mock.Anything).Return(runStatus, nil) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 2) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[1], *runStatus) + }) + + t.Run("Rollout is Terminated on fully promoted", func(t *testing.T) { + roCtx, stepPluginMock := setup(t) + roCtx.rollout.Status.PromoteFull = false + roCtx.rollout.Status.StableRS = "stable-value" + roCtx.rollout.Status.CurrentPodHash = "stable-value" + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationTerminate, v1alpha1.StepPluginPhaseSuccessful) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + }, + } + + stepPluginMock.On("Terminate", mock.Anything).Return(runStatus, nil) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 2) + assert.EqualExportedValues(t, roCtx.stepPluginContext.stepPluginStatuses[1], *runStatus) + }) + + t.Run("Rollout is Reconciled when already terminated", func(t *testing.T) { + roCtx := newRolloutContext(t) + roCtx.rollout.Status.PromoteFull = false + roCtx.rollout.Status.StableRS = "stable-value" + roCtx.rollout.Status.CurrentPodHash = "stable-value" + roCtx.rollout.Status.CurrentStepIndex = ptr.To(int32(1)) + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationTerminate, v1alpha1.StepPluginPhaseSuccessful) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + }, + { + Index: runStatus.Index, + Name: runStatus.Name, + Phase: v1alpha1.StepPluginPhaseSuccessful, + Operation: v1alpha1.StepPluginOperationTerminate, + }, + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 2) + assert.EqualExportedValues(t, roCtx.rollout.Status.Canary.StepPluginStatuses[1], roCtx.stepPluginContext.stepPluginStatuses[1]) + }) + + t.Run("Reconciliation error", func(t *testing.T) { + roCtx, stepPluginMock := setup(t) + roCtx.rollout.Status.PromoteFull = true + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationTerminate, v1alpha1.StepPluginPhaseSuccessful) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + }, + } + + var requeuedAfter time.Duration + roCtx.enqueueRolloutAfter = func(obj any, duration time.Duration) { + requeuedAfter = duration + } + + stepPluginMock.On("Terminate", mock.Anything).Return(nil, fmt.Errorf("error")) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.Equal(t, roCtx.rollout.Status.Canary.StepPluginStatuses, roCtx.stepPluginContext.stepPluginStatuses) + assert.Equal(t, defaultControllerErrorBackoff, requeuedAfter) + assert.True(t, roCtx.stepPluginContext.hasError) + }) +} + +func Test_stepPluginContext_reconcile_Aborted(t *testing.T) { + setup := func(t *testing.T) (*rolloutContext, *mocks.Resolver) { + stepPluginResolver := mocks.NewResolver(t) + + r := newStepPluginRollout() + r.Status.Abort = true + + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logCtx, + reconcilerBase: reconcilerBase{ + enqueueRollout: func(obj any) { t.Error("enqueueRollout should not be called") }, + enqueueRolloutAfter: func(obj any, duration time.Duration) { t.Error("enqueueRolloutAfter should not be called") }, + recorder: record.NewFakeEventRecorder(), + }, + pauseContext: &pauseContext{ + rollout: r, + log: logCtx, + }, + stepPluginContext: &stepPluginContext{ + resolver: stepPluginResolver, + log: logCtx, + }, + } + + return roCtx, stepPluginResolver + } + t.Run("Abort called on each plugin step", func(t *testing.T) { + roCtx, stepPluginResolver := setup(t) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + { + Index: 2, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + }, + } + roCtx.rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{ + { + Plugin: &v1alpha1.PluginStep{ + Name: "test-plugin", + }, + }, + { + Pause: &v1alpha1.RolloutPause{}, // Not a step plugin + }, + { + Plugin: &v1alpha1.PluginStep{ + Name: "test-plugin", + }, + }, + } + roCtx.rollout.Status.CurrentStepIndex = int32Ptr(0) + + expectedAbortStatus := []*v1alpha1.StepPluginStatus{} + for _, stepIndex := range []int32{0, 2} { + abortStatus := &v1alpha1.StepPluginStatus{ + Index: stepIndex, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationAbort, + Phase: v1alpha1.StepPluginPhaseSuccessful, + } + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", stepIndex, mock.Anything, mock.Anything).Return(stepPluginMock, nil) + stepPluginMock.On("Abort", mock.Anything).Return(abortStatus, nil) + expectedAbortStatus = append(expectedAbortStatus, abortStatus) + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 4) + assert.EqualExportedValues(t, roCtx.rollout.Status.Canary.StepPluginStatuses[0], roCtx.stepPluginContext.stepPluginStatuses[0]) + assert.EqualExportedValues(t, roCtx.rollout.Status.Canary.StepPluginStatuses[1], roCtx.stepPluginContext.stepPluginStatuses[1]) + assert.EqualExportedValues(t, *expectedAbortStatus[1], roCtx.stepPluginContext.stepPluginStatuses[2]) + assert.EqualExportedValues(t, *expectedAbortStatus[0], roCtx.stepPluginContext.stepPluginStatuses[3]) + }) + t.Run("Rollout is reconciled when already aborted", func(t *testing.T) { + roCtx, stepPluginResolver := setup(t) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + { + Index: 2, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + }, + { + Index: 2, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationAbort, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + { + Index: 0, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationAbort, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + } + roCtx.rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{ + { + Plugin: &v1alpha1.PluginStep{ + Name: "test-plugin", + }, + }, + { + Pause: &v1alpha1.RolloutPause{}, // Not a step plugin + }, + { + Plugin: &v1alpha1.PluginStep{ + Name: "test-plugin", + }, + }, + } + roCtx.rollout.Status.CurrentStepIndex = int32Ptr(0) + + for _, stepIndex := range []int32{0, 2} { + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", stepIndex, mock.Anything, mock.Anything).Return(stepPluginMock, nil) + stepPluginMock.On("Abort", mock.Anything).Return(nil, nil) + } + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 4) + assert.Equal(t, roCtx.rollout.Status.Canary.StepPluginStatuses, roCtx.stepPluginContext.stepPluginStatuses) + }) + t.Run("Reconciliation error", func(t *testing.T) { + roCtx, stepPluginResolver := setup(t) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + } + roCtx.rollout.Status.CurrentStepIndex = int32Ptr(0) + + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationTerminate, v1alpha1.StepPluginPhaseSuccessful) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: runStatus.Index, + Name: runStatus.Name, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + }, + } + + var requeuedAfter time.Duration + roCtx.enqueueRolloutAfter = func(obj any, duration time.Duration) { + requeuedAfter = duration + } + + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", int32(0), mock.Anything, mock.Anything).Return(stepPluginMock, nil) + stepPluginMock.On("Abort", mock.Anything).Return(nil, fmt.Errorf("error")) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.Equal(t, roCtx.rollout.Status.Canary.StepPluginStatuses, roCtx.stepPluginContext.stepPluginStatuses) + assert.Equal(t, defaultControllerErrorBackoff, requeuedAfter) + assert.True(t, roCtx.stepPluginContext.hasError) + }) +} + +func Test_stepPluginContext_reconcile_Retry_After_Abort(t *testing.T) { + setup := func(t *testing.T) (*rolloutContext, *mocks.Resolver) { + stepPluginResolver := mocks.NewResolver(t) + + r := newStepPluginRollout() + r.Status.Abort = true + + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logCtx, + reconcilerBase: reconcilerBase{ + enqueueRollout: func(obj any) { t.Error("enqueueRollout should not be called") }, + enqueueRolloutAfter: func(obj any, duration time.Duration) { t.Error("enqueueRolloutAfter should not be called") }, + recorder: record.NewFakeEventRecorder(), + }, + pauseContext: &pauseContext{ + rollout: r, + log: logCtx, + }, + stepPluginContext: &stepPluginContext{ + resolver: stepPluginResolver, + log: logCtx, + }, + } + + return roCtx, stepPluginResolver + } + + t.Run("Rollout is retried when already aborted", func(t *testing.T) { + roCtx, stepPluginResolver := setup(t) + roCtx.rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + { + Index: 2, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + }, + { + Index: 2, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationAbort, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + { + Index: 0, + Name: "test-plugin", + Operation: v1alpha1.StepPluginOperationAbort, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + } + roCtx.rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{ + { + Plugin: &v1alpha1.PluginStep{ + Name: "test-plugin", + }, + }, + { + Pause: &v1alpha1.RolloutPause{}, + }, + { + Plugin: &v1alpha1.PluginStep{ + Name: "test-plugin", + }, + }, + } + roCtx.rollout.Status.CurrentStepIndex = int32Ptr(0) + roCtx.rollout.Status.Abort = false + roCtx.rollout.Status.AbortedAt = nil + + stepPluginMock := mocks.NewStepPlugin(t) + stepPluginResolver.On("Resolve", int32(0), mock.Anything, mock.Anything).Return(stepPluginMock, nil) + runStatus := newStepPluginStatus(v1alpha1.StepPluginOperationRun, v1alpha1.StepPluginPhaseSuccessful) + stepPluginMock.On("Run", roCtx.rollout).Return(runStatus, nil) + + err := roCtx.stepPluginContext.reconcile(roCtx) + + require.NoError(t, err) + require.Len(t, roCtx.stepPluginContext.stepPluginStatuses, 1) + assert.Equal(t, *runStatus, roCtx.stepPluginContext.stepPluginStatuses[0]) + }) +} + +func Test_stepPluginContext_isStepPluginCompleted(t *testing.T) { + newRolloutContext := func(statuses []*v1alpha1.StepPluginStatus, hasError bool) *rolloutContext { + r := newStepPluginRollout() + logCtx := logutil.WithRollout(r) + roCtx := &rolloutContext{ + rollout: r, + log: logCtx, + stepPluginContext: &stepPluginContext{ + log: logCtx, + }, + } + + for _, s := range statuses { + roCtx.stepPluginContext.stepPluginStatuses = append(roCtx.stepPluginContext.stepPluginStatuses, *s) + } + roCtx.stepPluginContext.hasError = hasError + return roCtx + } + + tests := []struct { + name string + statuses []*v1alpha1.StepPluginStatus + index int32 + hasError bool + want bool + }{ + { + name: "Status is not set", + statuses: nil, + index: 0, + want: false, + }, + { + name: "Phase is successful", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseSuccessful}, + }, + index: 0, + want: true, + }, + { + name: "With transient error", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseSuccessful}, + }, + index: 0, + hasError: true, + want: false, + }, + { + name: "Phase is failed", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseFailed}, + }, + index: 0, + want: true, + }, + { + name: "Phase is error", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseError}, + }, + index: 0, + want: false, + }, + { + name: "Phase is running", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseRunning}, + }, + index: 0, + want: false, + }, + { + name: "Phase is running, but terminated", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseRunning}, + {Index: 0, Operation: v1alpha1.StepPluginOperationTerminate}, + }, + index: 0, + want: true, + }, + { + name: "Phase is running, but aborted", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseRunning}, + {Index: 0, Operation: v1alpha1.StepPluginOperationTerminate}, + }, + index: 0, + want: true, + }, + { + name: "status for index is missing", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 1, Operation: v1alpha1.StepPluginOperationRun, Phase: v1alpha1.StepPluginPhaseSuccessful}, + }, + index: 0, + want: false, + }, + { + name: "status is disabled", + statuses: []*v1alpha1.StepPluginStatus{ + {Index: 0, Operation: v1alpha1.StepPluginOperationRun, Disabled: true}, + }, + index: 0, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := newRolloutContext(tt.statuses, tt.hasError) + if got := c.stepPluginContext.isStepPluginCompleted(tt.index, c.rollout.Spec.Strategy.Canary.Steps[tt.index].Plugin); got != tt.want { + t.Errorf("rolloutContext.isStepPluginCompleted() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_stepPluginContext_updateStatus(t *testing.T) { + + t.Run("status is not", func(t *testing.T) { + stepPluginContext := stepPluginContext{ + stepPluginStatuses: nil, + } + + expected := []v1alpha1.StepPluginStatus{} + status := &v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: expected, + }, + } + + stepPluginContext.updateStatus(status) + + assert.Equal(t, expected, status.Canary.StepPluginStatuses) + }) + t.Run("status is set", func(t *testing.T) { + + expected := []v1alpha1.StepPluginStatus{{}} + + stepPluginContext := stepPluginContext{ + stepPluginStatuses: expected, + } + + status := &v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{}, + }, + } + + stepPluginContext.updateStatus(status) + + assert.Equal(t, expected, status.Canary.StepPluginStatuses) + }) +} diff --git a/rollout/steps/plugin/client/client.go b/rollout/steps/plugin/client/client.go new file mode 100644 index 0000000000..584f9bbd19 --- /dev/null +++ b/rollout/steps/plugin/client/client.go @@ -0,0 +1,105 @@ +package client + +import ( + "fmt" + "os/exec" + "sync" + + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/plugin" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + goPlugin "github.com/hashicorp/go-plugin" +) + +type stepPlugin struct { + client map[string]*goPlugin.Client + plugin map[string]rpc.StepPlugin +} + +var pluginClients *stepPlugin +var once sync.Once +var mutex sync.Mutex + +var handshakeConfig = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "step", +} + +// pluginMap is the map of plugins we can dispense. +var pluginMap = map[string]goPlugin.Plugin{ + "RpcStepPlugin": &rpc.RpcStepPlugin{}, +} + +// GetPlugin returns a singleton plugin client for the given plugin. Calling this multiple times +// returns the same plugin client instance for the plugin name defined in the rollout object. +func GetPlugin(pluginName string) (rpc.StepPlugin, error) { + once.Do(func() { + pluginClients = &stepPlugin{ + client: make(map[string]*goPlugin.Client), + plugin: make(map[string]rpc.StepPlugin), + } + }) + plugin, err := pluginClients.startPlugin(pluginName) + if err != nil { + return nil, fmt.Errorf("unable to start plugin system: %w", err) + } + return plugin, nil +} + +func (t *stepPlugin) startPlugin(pluginName string) (rpc.StepPlugin, error) { + mutex.Lock() + defer mutex.Unlock() + + if t.client[pluginName] == nil || t.client[pluginName].Exited() { + + pluginPath, args, err := plugin.GetPluginInfo(pluginName, types.PluginTypeStep) + if err != nil { + return nil, fmt.Errorf("unable to find plugin (%s): %w", pluginName, err) + } + + t.client[pluginName] = goPlugin.NewClient(&goPlugin.ClientConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + Cmd: exec.Command(pluginPath, args...), + Managed: true, + }) + + rpcClient, err := t.client[pluginName].Client() + if err != nil { + return nil, fmt.Errorf("unable to get plugin client (%s): %w", pluginName, err) + } + + // Request the plugin + plugin, err := rpcClient.Dispense("RpcStepPlugin") + if err != nil { + return nil, fmt.Errorf("unable to dispense plugin (%s): %w", pluginName, err) + } + + pluginType, ok := plugin.(rpc.StepPlugin) + if !ok { + return nil, fmt.Errorf("unexpected type from plugin") + } + t.plugin[pluginName] = pluginType + + resp := t.plugin[pluginName].InitPlugin() + if resp.HasError() { + return nil, fmt.Errorf("unable to initialize plugin via rpc (%s): %w", pluginName, resp) + } + } + + client, err := t.client[pluginName].Client() + if err != nil { + // If we are not able to create the client, something is utterly wrong + // we should try to re-download the plugin and restart because the file + // can be corrupted + return nil, fmt.Errorf("unable to get plugin client (%s) for ping: %w", pluginName, err) + } + if err := client.Ping(); err != nil { + t.client[pluginName].Kill() + t.client[pluginName] = nil + return nil, fmt.Errorf("could not ping plugin will cleanup process so we can restart it next reconcile (%w)", err) + } + + return t.plugin[pluginName], nil +} diff --git a/rollout/steps/plugin/mocks/Resolver.go b/rollout/steps/plugin/mocks/Resolver.go new file mode 100644 index 0000000000..356b69e0b5 --- /dev/null +++ b/rollout/steps/plugin/mocks/Resolver.go @@ -0,0 +1,61 @@ +// Code generated by mockery v2.42.2. DO NOT EDIT. + +package mocks + +import ( + logrus "github.com/sirupsen/logrus" + mock "github.com/stretchr/testify/mock" + + plugin "github.com/argoproj/argo-rollouts/rollout/steps/plugin" + + v1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +) + +// Resolver is an autogenerated mock type for the Resolver type +type Resolver struct { + mock.Mock +} + +// Resolve provides a mock function with given fields: index, _a1, log +func (_m *Resolver) Resolve(index int32, _a1 v1alpha1.PluginStep, log *logrus.Entry) (plugin.StepPlugin, error) { + ret := _m.Called(index, _a1, log) + + if len(ret) == 0 { + panic("no return value specified for Resolve") + } + + var r0 plugin.StepPlugin + var r1 error + if rf, ok := ret.Get(0).(func(int32, v1alpha1.PluginStep, *logrus.Entry) (plugin.StepPlugin, error)); ok { + return rf(index, _a1, log) + } + if rf, ok := ret.Get(0).(func(int32, v1alpha1.PluginStep, *logrus.Entry) plugin.StepPlugin); ok { + r0 = rf(index, _a1, log) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(plugin.StepPlugin) + } + } + + if rf, ok := ret.Get(1).(func(int32, v1alpha1.PluginStep, *logrus.Entry) error); ok { + r1 = rf(index, _a1, log) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewResolver creates a new instance of Resolver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewResolver(t interface { + mock.TestingT + Cleanup(func()) +}) *Resolver { + mock := &Resolver{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/rollout/steps/plugin/mocks/StepPlugin.go b/rollout/steps/plugin/mocks/StepPlugin.go new file mode 100644 index 0000000000..2a5b7cb679 --- /dev/null +++ b/rollout/steps/plugin/mocks/StepPlugin.go @@ -0,0 +1,118 @@ +// Code generated by mockery v2.42.2. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + v1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +) + +// StepPlugin is an autogenerated mock type for the StepPlugin type +type StepPlugin struct { + mock.Mock +} + +// Abort provides a mock function with given fields: _a0 +func (_m *StepPlugin) Abort(_a0 *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Abort") + } + + var r0 *v1alpha1.StepPluginStatus + var r1 error + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout) *v1alpha1.StepPluginStatus); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1alpha1.StepPluginStatus) + } + } + + if rf, ok := ret.Get(1).(func(*v1alpha1.Rollout) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Run provides a mock function with given fields: _a0 +func (_m *StepPlugin) Run(_a0 *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Run") + } + + var r0 *v1alpha1.StepPluginStatus + var r1 error + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout) *v1alpha1.StepPluginStatus); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1alpha1.StepPluginStatus) + } + } + + if rf, ok := ret.Get(1).(func(*v1alpha1.Rollout) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Terminate provides a mock function with given fields: _a0 +func (_m *StepPlugin) Terminate(_a0 *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Terminate") + } + + var r0 *v1alpha1.StepPluginStatus + var r1 error + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout) *v1alpha1.StepPluginStatus); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*v1alpha1.StepPluginStatus) + } + } + + if rf, ok := ret.Get(1).(func(*v1alpha1.Rollout) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewStepPlugin creates a new instance of StepPlugin. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStepPlugin(t interface { + mock.TestingT + Cleanup(func()) +}) *StepPlugin { + mock := &StepPlugin{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/rollout/steps/plugin/plugin.go b/rollout/steps/plugin/plugin.go new file mode 100644 index 0000000000..dd9ebe04ad --- /dev/null +++ b/rollout/steps/plugin/plugin.go @@ -0,0 +1,255 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + metatime "github.com/argoproj/argo-rollouts/utils/time" + log "github.com/sirupsen/logrus" +) + +type stepPlugin struct { + rpc rpc.StepPlugin + index int32 + name string + config json.RawMessage + log *log.Entry +} + +type disabledStepPlugin struct { + index int32 + name string +} + +// StepPlugin allows to execute different operation for a step plugin specifc to a rollout +type StepPlugin interface { + // Run executes the run operation for of a step plugin and returns a Run operation status + Run(*v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) + // Terminate cancels an ongoing running Run operation and returns a Terminate status if it did + Terminate(*v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) + // Abort reverts a completed Run operation and returns a Terminate status if it did + Abort(*v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) +} + +var ( + minRequeueDuration = time.Second * 10 + defaultRequeuDuration = time.Second * 30 + defaultErrorBackoff = time.Second * 30 +) + +func (p *stepPlugin) Run(rollout *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + stepStatus := p.getStepStatus(rollout, v1alpha1.StepPluginOperationRun) + if stepStatus == nil || stepStatus.Disabled { + now := metatime.MetaNow() + stepStatus = &v1alpha1.StepPluginStatus{ + Index: p.index, + Name: p.name, + StartedAt: &now, + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + } + } + + if stepStatus.Phase == v1alpha1.StepPluginPhaseSuccessful || stepStatus.Phase == v1alpha1.StepPluginPhaseFailed { + // Already completed + return nil, nil + } + + if stepStatus.Executions > 0 { + // If status existed, check the backoff to know if we are ready to retry. + // If we are not, return the status without modifying it. + backoff, err := stepStatus.Backoff.Duration() + if err != nil { + return nil, fmt.Errorf("could not parse backoff duration: %w", err) + } + if stepStatus.UpdatedAt.Add(backoff).After(metatime.Now()) { + p.log.Debug("skipping plugin Run due to backoff") + return stepStatus, nil + } + } + + p.log.Debug("calling RPC Run") + resp, err := p.rpc.Run(rollout.DeepCopy(), p.getStepContext(stepStatus)) + finishedAt := metatime.MetaNow() + stepStatus.Backoff = "" + stepStatus.UpdatedAt = &finishedAt + stepStatus.Executions++ + if err.HasError() { + p.log.Errorf("error during plugin execution") + stepStatus.Phase = v1alpha1.StepPluginPhaseError + stepStatus.Message = err.Error() + stepStatus.Backoff = v1alpha1.DurationString(defaultErrorBackoff.String()) + return stepStatus, nil + } + + stepStatus.Message = resp.Message + if resp.Phase != "" { + stepStatus.Phase = v1alpha1.StepPluginPhase(resp.Phase) + if err := stepStatus.Phase.Validate(); err != nil { + return nil, fmt.Errorf("could not validate rpc phase: %w", err) + } + } + + if stepStatus.Phase == v1alpha1.StepPluginPhaseSuccessful || stepStatus.Phase == v1alpha1.StepPluginPhaseFailed { + stepStatus.FinishedAt = &finishedAt + } + + if stepStatus.Phase != v1alpha1.StepPluginPhaseError { + // do not update status on error because it can be invalid and we want to retry later on current status + stepStatus.Status = resp.Status + } + + if stepStatus.Phase == v1alpha1.StepPluginPhaseRunning { + backoff := defaultRequeuDuration + if resp.RequeueAfter > minRequeueDuration { + backoff = resp.RequeueAfter + } + stepStatus.Backoff = v1alpha1.DurationString(backoff.String()) + } + + return stepStatus, nil +} + +func (p *stepPlugin) Terminate(rollout *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + terminateStatus := p.getStepStatus(rollout, v1alpha1.StepPluginOperationTerminate) + if terminateStatus != nil { + // Already terminated + return nil, nil + } + + stepStatus := p.getStepStatus(rollout, v1alpha1.StepPluginOperationRun) + if stepStatus == nil || stepStatus.Disabled || stepStatus.Phase != v1alpha1.StepPluginPhaseRunning { + // Step is not running, no need to call terminate + return nil, nil + } + + now := metatime.MetaNow() + terminateStatus = &v1alpha1.StepPluginStatus{ + Index: stepStatus.Index, + Name: stepStatus.Name, + StartedAt: &now, + Operation: v1alpha1.StepPluginOperationTerminate, + Phase: v1alpha1.StepPluginPhaseSuccessful, + } + + p.log.Debug("calling RPC Terminate") + resp, err := p.rpc.Terminate(rollout.DeepCopy(), p.getStepContext(stepStatus)) + finishedAt := metatime.MetaNow() + stepStatus.UpdatedAt = &finishedAt + if err.HasError() { + terminateStatus.Phase = v1alpha1.StepPluginPhaseError + terminateStatus.Message = err.Error() + terminateStatus.FinishedAt = &finishedAt + return terminateStatus, nil + } + + if resp.Phase != "" { + terminateStatus.Phase = v1alpha1.StepPluginPhase(resp.Phase) + if err := terminateStatus.Phase.Validate(); err != nil { + return nil, fmt.Errorf("could not validate rpc phase: %w", err) + } + } + + if terminateStatus.Phase == v1alpha1.StepPluginPhaseRunning { + p.log.Warnf("terminate cannot run asynchronously. Overriding status phase to %s.", v1alpha1.StepPluginPhaseFailed) + terminateStatus.Phase = v1alpha1.StepPluginPhaseFailed + } + + terminateStatus.Message = resp.Message + terminateStatus.FinishedAt = &finishedAt + return terminateStatus, nil +} + +func (p *stepPlugin) Abort(rollout *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + abortStatus := p.getStepStatus(rollout, v1alpha1.StepPluginOperationAbort) + if abortStatus != nil { + // Already aborted + return nil, nil + } + + stepStatus := p.getStepStatus(rollout, v1alpha1.StepPluginOperationRun) + if stepStatus == nil || stepStatus.Disabled || (stepStatus.Phase != v1alpha1.StepPluginPhaseRunning && stepStatus.Phase != v1alpha1.StepPluginPhaseSuccessful) { + // Step plugin isn't in a phase where it needs to be aborted + return nil, nil + } + + now := metatime.MetaNow() + abortStatus = &v1alpha1.StepPluginStatus{ + Index: stepStatus.Index, + Name: stepStatus.Name, + StartedAt: &now, + Operation: v1alpha1.StepPluginOperationAbort, + Phase: v1alpha1.StepPluginPhaseSuccessful, + } + + p.log.Debug("calling RPC Abort") + resp, err := p.rpc.Abort(rollout.DeepCopy(), p.getStepContext(stepStatus)) + finishedAt := metatime.MetaNow() + stepStatus.UpdatedAt = &finishedAt + if err.HasError() { + abortStatus.Phase = v1alpha1.StepPluginPhaseError + abortStatus.Message = err.Error() + abortStatus.FinishedAt = &finishedAt + return abortStatus, nil + } + + if resp.Phase != "" { + abortStatus.Phase = v1alpha1.StepPluginPhase(resp.Phase) + if err := abortStatus.Phase.Validate(); err != nil { + return nil, fmt.Errorf("could not validate rpc phase: %w", err) + } + } + + if abortStatus.Phase == v1alpha1.StepPluginPhaseRunning { + p.log.Warnf("abort cannot run asynchronously. Overriding status phase to %s.", v1alpha1.StepPluginPhaseFailed) + abortStatus.Phase = v1alpha1.StepPluginPhaseFailed + } + + abortStatus.Message = resp.Message + abortStatus.FinishedAt = &finishedAt + return abortStatus, nil +} + +func (p *disabledStepPlugin) Run(_ *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + return &v1alpha1.StepPluginStatus{ + Index: p.index, + Name: p.name, + Operation: v1alpha1.StepPluginOperationRun, + Disabled: true, + }, nil +} + +func (p *disabledStepPlugin) Terminate(_ *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + return nil, nil +} + +func (p *disabledStepPlugin) Abort(_ *v1alpha1.Rollout) (*v1alpha1.StepPluginStatus, error) { + return nil, nil +} + +// getStepStatus returns the existing status for the current operation +func (p *stepPlugin) getStepStatus(rollout *v1alpha1.Rollout, operation v1alpha1.StepPluginOperation) *v1alpha1.StepPluginStatus { + for _, s := range rollout.Status.Canary.StepPluginStatuses { + if s.Index == p.index && s.Name == p.name && s.Operation == operation { + return s.DeepCopy() + } + } + return nil +} + +// getStepContext returns the current step configuration with the from a previous operation, if any +func (p *stepPlugin) getStepContext(stepStatus *v1alpha1.StepPluginStatus) *types.RpcStepContext { + var status json.RawMessage = nil + if stepStatus != nil { + status = stepStatus.Status + } + return &types.RpcStepContext{ + PluginName: p.name, + Config: p.config, + Status: status, + } +} diff --git a/rollout/steps/plugin/plugin_test.go b/rollout/steps/plugin/plugin_test.go new file mode 100644 index 0000000000..dc041fa015 --- /dev/null +++ b/rollout/steps/plugin/plugin_test.go @@ -0,0 +1,769 @@ +package plugin + +import ( + "encoding/json" + "testing" + "time" + + log "github.com/sirupsen/logrus" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc/mocks" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_stepPlugin_Run(t *testing.T) { + setup := func(t *testing.T) (*stepPlugin, *mocks.StepPlugin) { + plugin := &stepPlugin{ + name: "test-plugin", + index: 0, + config: json.RawMessage("value"), + log: log.WithFields(log.Fields{}), + } + rpcPluginMock := mocks.NewStepPlugin(t) + plugin.rpc = rpcPluginMock + return plugin, rpcPluginMock + } + + t.Run("Argument set without status", func(t *testing.T) { + p, rpcMock := setup(t) + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: p.name, + Status: json.RawMessage("step status value"), + Operation: v1alpha1.StepPluginOperationRun, + }, + }, + }, + }, + } + validateArguments := func(args mock.Arguments) { + rollout, ok0 := args.Get(0).(*v1alpha1.Rollout) + context, ok1 := args.Get(1).(*types.RpcStepContext) + require.Truef(t, ok0, "Argument 0 is of the wrong type") + require.Truef(t, ok1, "Argument 1 is of the wrong type") + + assert.NotNil(t, rollout) + assert.NotSame(t, r, rollout) + assert.NotNil(t, context) + assert.Equal(t, p.name, context.PluginName) + assert.Equal(t, p.config, context.Config) + assert.Nil(t, context.Status) + } + + rpcMock.On("Run", mock.Anything, mock.Anything).Run(validateArguments).Return(types.RpcStepResult{}, types.RpcError{}).Once() + + p.index = 1 + _, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + }) + t.Run("Argument set with status", func(t *testing.T) { + p, rpcMock := setup(t) + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: p.name, + Status: json.RawMessage("step status value"), + Operation: v1alpha1.StepPluginOperationRun, + }, + }, + }, + }, + } + validateArguments := func(args mock.Arguments) { + rollout, ok0 := args.Get(0).(*v1alpha1.Rollout) + context, ok1 := args.Get(1).(*types.RpcStepContext) + require.Truef(t, ok0, "Argument 0 is of the wrong type") + require.Truef(t, ok1, "Argument 1 is of the wrong type") + + assert.NotNil(t, rollout) + assert.NotSame(t, r, rollout) + assert.NotNil(t, context) + assert.Equal(t, p.name, context.PluginName) + assert.Equal(t, p.config, context.Config) + assert.Equal(t, r.Status.Canary.StepPluginStatuses[0].Status, context.Status) + } + + rpcMock.On("Run", mock.Anything, mock.Anything).Run(validateArguments).Return(types.RpcStepResult{}, types.RpcError{}).Once() + + p.index = 0 + _, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + }) + t.Run("use existing state", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := &v1alpha1.StepPluginStatus{ + Index: 0, + Name: p.name, + Status: json.RawMessage("step status value"), + StartedAt: &v1.Time{Time: time.Now().Add(30 * time.Minute * -1)}, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + } + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + *currentStatus, + }, + }, + }, + } + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Run", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}).Once() + + status, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.Equal(t, currentStatus.StartedAt, status.StartedAt) + assert.NotEqual(t, currentStatus.UpdatedAt, status.UpdatedAt) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Equal(t, rpcResult.Status, status.Status) + assert.NotNil(t, status.FinishedAt) + assert.Empty(t, status.Backoff) + assert.False(t, status.Disabled) + }) + t.Run("Successful status", func(t *testing.T) { + p, rpcMock := setup(t) + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{}, + }, + }, + } + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Run", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}).Once() + + status, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.StartedAt) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Equal(t, rpcResult.Status, status.Status) + assert.EqualValues(t, "", status.Backoff) + assert.False(t, status.Disabled) + }) + t.Run("Running status", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := &v1alpha1.StepPluginStatus{ + Index: 0, + Name: p.name, + Status: json.RawMessage("step status value"), + StartedAt: &v1.Time{Time: time.Now().Add(30 * time.Minute * -1)}, + Operation: v1alpha1.StepPluginOperationRun, + } + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + *currentStatus, + }, + }, + }, + } + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseRunning, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Run", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}).Once() + + status, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.StartedAt) + assert.Nil(t, status.FinishedAt) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Equal(t, rpcResult.Status, status.Status) + assert.EqualValues(t, rpcResult.RequeueAfter.String(), status.Backoff) + assert.False(t, status.Disabled) + }) + t.Run("Running status without requeue", func(t *testing.T) { + p, rpcMock := setup(t) + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{}, + }, + }, + } + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseRunning, + Message: "Good message", + Status: json.RawMessage("status"), + } + rpcMock.On("Run", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}).Once() + + status, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.StartedAt) + assert.Nil(t, status.FinishedAt) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Equal(t, rpcResult.Status, status.Status) + assert.EqualValues(t, defaultRequeuDuration.String(), status.Backoff) + assert.False(t, status.Disabled) + }) + t.Run("Running status with requeue too fast", func(t *testing.T) { + p, rpcMock := setup(t) + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{}, + }, + }, + } + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseRunning, + Message: "Good message", + RequeueAfter: 1 * time.Second, + Status: json.RawMessage("status"), + } + rpcMock.On("Run", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}).Once() + + status, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.StartedAt) + assert.Nil(t, status.FinishedAt) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Equal(t, rpcResult.Status, status.Status) + assert.EqualValues(t, defaultRequeuDuration.String(), status.Backoff) + assert.False(t, status.Disabled) + }) + t.Run("Failed status", func(t *testing.T) { + p, rpcMock := setup(t) + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{}, + }, + }, + } + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Run", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}).Once() + + status, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.StartedAt) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Equal(t, rpcResult.Status, status.Status) + assert.Empty(t, status.Backoff) + assert.False(t, status.Disabled) + }) + t.Run("Error status", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := &v1alpha1.StepPluginStatus{ + Index: 0, + Name: p.name, + Status: json.RawMessage("step status value"), + StartedAt: &v1.Time{Time: time.Now().Add(30 * time.Minute * -1)}, + Operation: v1alpha1.StepPluginOperationRun, + } + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + *currentStatus, + }, + }, + }, + } + + invalidResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "This message should not be used", + RequeueAfter: time.Hour, + Status: json.RawMessage("invalid status"), + } + expectedError := types.RpcError{ + ErrorString: "This is an error", + } + rpcMock.On("Run", mock.Anything, mock.Anything).Return(invalidResult, expectedError).Once() + + status, err := p.Run(r) + + require.NoError(t, err) + rpcMock.AssertExpectations(t) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.Equal(t, currentStatus.StartedAt, status.StartedAt) + assert.NotEqual(t, currentStatus.UpdatedAt, status.UpdatedAt) + assert.Equal(t, v1alpha1.StepPluginPhaseError, status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.Equal(t, expectedError.Error(), status.Message) + assert.Equal(t, currentStatus.Status, status.Status) + assert.EqualValues(t, "30s", status.Backoff) + assert.False(t, status.Disabled) + }) +} + +func Test_stepPlugin_Terminate(t *testing.T) { + setup := func(t *testing.T) (*stepPlugin, *mocks.StepPlugin) { + plugin := &stepPlugin{ + name: "test-plugin", + index: 0, + config: json.RawMessage("value"), + log: log.WithFields(log.Fields{}), + } + rpcPluginMock := mocks.NewStepPlugin(t) + plugin.rpc = rpcPluginMock + return plugin, rpcPluginMock + } + newRunningStatus := func() *v1alpha1.StepPluginStatus { + return &v1alpha1.StepPluginStatus{ + Index: 0, + Name: "test-plugin", + Status: json.RawMessage("step status value"), + StartedAt: &v1.Time{Time: time.Now().Add(30 * time.Minute * -1)}, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + } + } + newRollout := func(s *v1alpha1.StepPluginStatus) *v1alpha1.Rollout { + return &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + *s, + }, + }, + }, + } + } + + t.Run("Return nil status if not running", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + currentStatus.Phase = v1alpha1.StepPluginPhaseSuccessful + currentStatus.Operation = v1alpha1.StepPluginOperationRun + r := newRollout(currentStatus) + + rpcMock.On("Terminate", mock.Anything, mock.Anything).Maybe().Panic("Terminate should not be called when plugin is not running") + + status, err := p.Terminate(r) + + require.NoError(t, err) + assert.Nil(t, status) + }) + + t.Run("Return nil status if already terminated", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + currentStatus.Phase = v1alpha1.StepPluginPhaseSuccessful + currentStatus.Operation = v1alpha1.StepPluginOperationTerminate + r := newRollout(currentStatus) + + rpcMock.On("Terminate", mock.Anything, mock.Anything).Maybe().Panic("Terminate should not be called when plugin is not running") + + status, err := p.Terminate(r) + + require.NoError(t, err) + assert.Nil(t, status) + }) + t.Run("Running phase overridden to failed if running", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + r := newRollout(currentStatus) + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseRunning, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Terminate", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}) + + status, err := p.Terminate(r) + + require.NoError(t, err) + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginOperationTerminate, status.Operation) + assert.Equal(t, v1alpha1.StepPluginPhaseFailed, status.Phase) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Nil(t, status.Status) + assert.False(t, status.Disabled) + }) + t.Run("Completes successfully", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + r := newRollout(currentStatus) + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Terminate", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}) + + status, err := p.Terminate(r) + + require.NoError(t, err) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationTerminate, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Nil(t, status.Status) + assert.False(t, status.Disabled) + }) + + t.Run("Error status", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + r := newRollout(currentStatus) + + invalidResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "This message should not be used", + RequeueAfter: time.Hour, + Status: json.RawMessage("invalid status"), + } + expectedError := types.RpcError{ + ErrorString: "This is an error", + } + rpcMock.On("Terminate", mock.Anything, mock.Anything).Return(invalidResult, expectedError) + + status, err := p.Terminate(r) + + require.NoError(t, err) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginOperationTerminate, status.Operation) + assert.Equal(t, v1alpha1.StepPluginPhaseError, status.Phase) + assert.Contains(t, status.Message, expectedError.Error()) + assert.Nil(t, status.Status) + assert.False(t, status.Disabled) + }) +} + +func Test_stepPlugin_Abort(t *testing.T) { + setup := func(t *testing.T) (*stepPlugin, *mocks.StepPlugin) { + plugin := &stepPlugin{ + name: "test-plugin", + index: 0, + config: json.RawMessage("value"), + log: log.WithFields(log.Fields{}), + } + rpcPluginMock := mocks.NewStepPlugin(t) + plugin.rpc = rpcPluginMock + return plugin, rpcPluginMock + } + newRunningStatus := func() *v1alpha1.StepPluginStatus { + return &v1alpha1.StepPluginStatus{ + Index: 0, + Name: "test-plugin", + Status: json.RawMessage("step status value"), + StartedAt: &v1.Time{Time: time.Now().Add(30 * time.Minute * -1)}, + Phase: v1alpha1.StepPluginPhaseRunning, + Operation: v1alpha1.StepPluginOperationRun, + } + } + newRollout := func(s *v1alpha1.StepPluginStatus) *v1alpha1.Rollout { + return &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + *s, + }, + }, + }, + } + } + + t.Run("Return nil status if run is in error", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + currentStatus.Phase = v1alpha1.StepPluginPhaseError + currentStatus.Operation = v1alpha1.StepPluginOperationRun + r := newRollout(currentStatus) + + rpcMock.On("Abort", mock.Anything, mock.Anything).Maybe().Panic("Abort should not be called when plugin is not running or copmpleted") + + status, err := p.Abort(r) + + require.NoError(t, err) + assert.Nil(t, status) + }) + + t.Run("Return nil status if run has failed", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + currentStatus.Phase = v1alpha1.StepPluginPhaseFailed + currentStatus.Operation = v1alpha1.StepPluginOperationRun + r := newRollout(currentStatus) + + rpcMock.On("Abort", mock.Anything, mock.Anything).Maybe().Panic("Abort should not be called when plugin is not running or completed") + + status, err := p.Abort(r) + + require.NoError(t, err) + assert.Nil(t, status) + }) + + t.Run("Return nil status if already aborted", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + currentStatus.Phase = v1alpha1.StepPluginPhaseSuccessful + currentStatus.Operation = v1alpha1.StepPluginOperationAbort + r := newRollout(currentStatus) + + rpcMock.On("Abort", mock.Anything, mock.Anything).Maybe().Panic("Abort should not be called when plugin is not running") + + status, err := p.Abort(r) + + require.NoError(t, err) + assert.Nil(t, status) + }) + t.Run("Running phase overridden to failed if running", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + r := newRollout(currentStatus) + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseRunning, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Abort", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}) + + status, err := p.Abort(r) + + require.NoError(t, err) + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginOperationAbort, status.Operation) + assert.Equal(t, v1alpha1.StepPluginPhaseFailed, status.Phase) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Nil(t, status.Status) + assert.False(t, status.Disabled) + }) + t.Run("Completes successfully", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + r := newRollout(currentStatus) + + rpcResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "Good message", + RequeueAfter: time.Hour, + Status: json.RawMessage("status"), + } + rpcMock.On("Abort", mock.Anything, mock.Anything).Return(rpcResult, types.RpcError{}) + + status, err := p.Abort(r) + + require.NoError(t, err) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginPhase(rpcResult.Phase), status.Phase) + assert.Equal(t, v1alpha1.StepPluginOperationAbort, status.Operation) + assert.Equal(t, rpcResult.Message, status.Message) + assert.Nil(t, status.Status) + assert.False(t, status.Disabled) + }) + + t.Run("Error status", func(t *testing.T) { + p, rpcMock := setup(t) + currentStatus := newRunningStatus() + r := newRollout(currentStatus) + + invalidResult := types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "This message should not be used", + RequeueAfter: time.Hour, + Status: json.RawMessage("invalid status"), + } + expectedError := types.RpcError{ + ErrorString: "This is an error", + } + rpcMock.On("Abort", mock.Anything, mock.Anything).Return(invalidResult, expectedError) + + status, err := p.Abort(r) + + require.NoError(t, err) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.NotNil(t, status.FinishedAt) + assert.Greater(t, status.FinishedAt.Time, status.StartedAt.Time) + assert.Equal(t, v1alpha1.StepPluginOperationAbort, status.Operation) + assert.Equal(t, v1alpha1.StepPluginPhaseError, status.Phase) + assert.Contains(t, status.Message, expectedError.Error()) + assert.Nil(t, status.Status) + assert.False(t, status.Disabled) + }) +} + +func Test_disabledStepPlugin(t *testing.T) { + t.Run("Run always return disabled status", func(t *testing.T) { + p := disabledStepPlugin{ + index: 0, + name: "test", + } + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{}, + }, + }, + } + + status, err := p.Run(r) + + require.NoError(t, err) + + assert.Equal(t, p.name, status.Name) + assert.Equal(t, p.index, status.Index) + assert.Equal(t, v1alpha1.StepPluginOperationRun, status.Operation) + assert.True(t, status.Disabled) + }) + t.Run("Abort has no effect", func(t *testing.T) { + p := disabledStepPlugin{ + index: 0, + name: "test", + } + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseSuccessful, + }, + }, + }, + }, + } + + status, err := p.Abort(r) + + require.NoError(t, err) + assert.Nil(t, status) + }) + t.Run("Terminate has no effect", func(t *testing.T) { + p := disabledStepPlugin{ + index: 0, + name: "test", + } + r := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{ + StepPluginStatuses: []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test", + Operation: v1alpha1.StepPluginOperationRun, + Phase: v1alpha1.StepPluginPhaseRunning, + }, + }, + }, + }, + } + + status, err := p.Terminate(r) + + require.NoError(t, err) + assert.Nil(t, status) + }) +} diff --git a/rollout/steps/plugin/resolver.go b/rollout/steps/plugin/resolver.go new file mode 100644 index 0000000000..f2233526ec --- /dev/null +++ b/rollout/steps/plugin/resolver.go @@ -0,0 +1,53 @@ +package plugin + +import ( + "fmt" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/client" + "github.com/argoproj/argo-rollouts/utils/config" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" +) + +type resolver struct { +} + +// Resolver allows to resolve a StepPlugin object +type Resolver interface { + // Resolve is a factory to create the correct StepPlugin based on the current step and global configurations + Resolve(index int32, plugin v1alpha1.PluginStep, log *log.Entry) (StepPlugin, error) +} + +// NewResolver creaates a new Resolver +func NewResolver() Resolver { + return &resolver{} +} + +func (r *resolver) Resolve(index int32, plugin v1alpha1.PluginStep, log *log.Entry) (StepPlugin, error) { + if config, err := config.GetConfig(); err != nil { + return nil, fmt.Errorf("could not get config: %w", err) + } else { + plugin := config.GetPlugin(plugin.Name, types.PluginTypeStep) + if plugin != nil && plugin.Disabled { + return &disabledStepPlugin{ + index: index, + name: plugin.Name, + }, nil + } + } + + pluginClient, err := client.GetPlugin(plugin.Name) + if err != nil { + return nil, fmt.Errorf("failed to get step plugin %s: %w", plugin.Name, err) + } + + return &stepPlugin{ + rpc: pluginClient, + index: index, + name: plugin.Name, + config: plugin.Config, + log: log.WithFields(logrus.Fields{"stepplugin": plugin.Name, "stepindex": index}), + }, nil +} diff --git a/rollout/steps/plugin/rpc/mocks/StepPlugin.go b/rollout/steps/plugin/rpc/mocks/StepPlugin.go new file mode 100644 index 0000000000..c858f4fa58 --- /dev/null +++ b/rollout/steps/plugin/rpc/mocks/StepPlugin.go @@ -0,0 +1,150 @@ +// Code generated by mockery v2.42.2. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/argoproj/argo-rollouts/utils/plugin/types" + + v1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +) + +// StepPlugin is an autogenerated mock type for the StepPlugin type +type StepPlugin struct { + mock.Mock +} + +// Abort provides a mock function with given fields: _a0, _a1 +func (_m *StepPlugin) Abort(_a0 *v1alpha1.Rollout, _a1 *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Abort") + } + + var r0 types.RpcStepResult + var r1 types.RpcError + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout, *types.RpcStepContext) (types.RpcStepResult, types.RpcError)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout, *types.RpcStepContext) types.RpcStepResult); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(types.RpcStepResult) + } + + if rf, ok := ret.Get(1).(func(*v1alpha1.Rollout, *types.RpcStepContext) types.RpcError); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Get(1).(types.RpcError) + } + + return r0, r1 +} + +// InitPlugin provides a mock function with given fields: +func (_m *StepPlugin) InitPlugin() types.RpcError { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for InitPlugin") + } + + var r0 types.RpcError + if rf, ok := ret.Get(0).(func() types.RpcError); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(types.RpcError) + } + + return r0 +} + +// Run provides a mock function with given fields: _a0, _a1 +func (_m *StepPlugin) Run(_a0 *v1alpha1.Rollout, _a1 *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Run") + } + + var r0 types.RpcStepResult + var r1 types.RpcError + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout, *types.RpcStepContext) (types.RpcStepResult, types.RpcError)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout, *types.RpcStepContext) types.RpcStepResult); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(types.RpcStepResult) + } + + if rf, ok := ret.Get(1).(func(*v1alpha1.Rollout, *types.RpcStepContext) types.RpcError); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Get(1).(types.RpcError) + } + + return r0, r1 +} + +// Terminate provides a mock function with given fields: _a0, _a1 +func (_m *StepPlugin) Terminate(_a0 *v1alpha1.Rollout, _a1 *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Terminate") + } + + var r0 types.RpcStepResult + var r1 types.RpcError + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout, *types.RpcStepContext) (types.RpcStepResult, types.RpcError)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*v1alpha1.Rollout, *types.RpcStepContext) types.RpcStepResult); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(types.RpcStepResult) + } + + if rf, ok := ret.Get(1).(func(*v1alpha1.Rollout, *types.RpcStepContext) types.RpcError); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Get(1).(types.RpcError) + } + + return r0, r1 +} + +// Type provides a mock function with given fields: +func (_m *StepPlugin) Type() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Type") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// NewStepPlugin creates a new instance of StepPlugin. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStepPlugin(t interface { + mock.TestingT + Cleanup(func()) +}) *StepPlugin { + mock := &StepPlugin{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/rollout/steps/plugin/rpc/rpc.go b/rollout/steps/plugin/rpc/rpc.go new file mode 100644 index 0000000000..f460361f39 --- /dev/null +++ b/rollout/steps/plugin/rpc/rpc.go @@ -0,0 +1,197 @@ +package rpc + +import ( + "encoding/gob" + "fmt" + "net/rpc" + + "github.com/argoproj/argo-rollouts/utils/plugin/types" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/hashicorp/go-plugin" +) + +type RunArgs struct { + Rollout *v1alpha1.Rollout + Context *types.RpcStepContext +} + +type TerminateArgs struct { + Rollout *v1alpha1.Rollout + Context *types.RpcStepContext +} + +type AbortArgs struct { + Rollout *v1alpha1.Rollout + Context *types.RpcStepContext +} + +type Response struct { + Result types.RpcStepResult + Error types.RpcError +} + +func init() { + gob.RegisterName("step.RunArgs", new(RunArgs)) + gob.RegisterName("step.TerminateArgs", new(TerminateArgs)) + gob.RegisterName("step.AbortArgs", new(AbortArgs)) +} + +// StepPlugin is the interface that we're exposing as a plugin. It needs to match metricproviders.Providers but we can +// not import that package because it would create a circular dependency. +type StepPlugin interface { + InitPlugin() types.RpcError + types.RpcStep +} + +// StepPluginRPC Here is an implementation that talks over RPC +type StepPluginRPC struct{ client *rpc.Client } + +// InitPlugin is the client aka the controller side function that calls the server side rpc (plugin) +// this gets called once during startup of the plugin and can be used to set up informers, k8s clients, etc. +func (g *StepPluginRPC) InitPlugin() types.RpcError { + var resp types.RpcError + err := g.client.Call("Plugin.InitPlugin", new(any), &resp) + if err != nil { + return types.RpcError{ErrorString: fmt.Sprintf("InitPlugin rpc call error: %s", err)} + } + return resp +} + +// Run executes the step +func (g *StepPluginRPC) Run(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + var resp Response + var args any = RunArgs{ + Rollout: rollout, + Context: context, + } + err := g.client.Call("Plugin.Run", &args, &resp) + if err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Sprintf("Run rpc call error: %s", err)} + } + return resp.Result, resp.Error +} + +// Terminate stops the execution of a running step and exits early +func (g *StepPluginRPC) Terminate(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + var resp Response + var args any = TerminateArgs{ + Rollout: rollout, + Context: context, + } + err := g.client.Call("Plugin.Terminate", &args, &resp) + if err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Sprintf("Terminate rpc call error: %s", err)} + } + return resp.Result, resp.Error +} + +// Abort reverts previous operation executed by the step if necessary +func (g *StepPluginRPC) Abort(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + var resp Response + var args any = AbortArgs{ + Rollout: rollout, + Context: context, + } + err := g.client.Call("Plugin.Abort", &args, &resp) + if err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Sprintf("Abort rpc call error: %s", err)} + } + return resp.Result, resp.Error +} + +// Type returns the type of the traffic routing reconciler +func (g *StepPluginRPC) Type() string { + var resp string + err := g.client.Call("Plugin.Type", new(any), &resp) + if err != nil { + return fmt.Sprintf("Type rpc call error: %s", err) + } + + return resp +} + +// StepRPCServer Here is the RPC server that MetricsPluginRPC talks to, conforming to +// the requirements of net/rpc +type StepRPCServer struct { + // This is the real implementation + Impl StepPlugin +} + +// InitPlugin this is the server aka the controller side function that receives calls from the client side rpc (controller) +// this gets called once during startup of the plugin and can be used to set up informers or k8s clients etc. +func (s *StepRPCServer) InitPlugin(args any, resp *types.RpcError) error { + *resp = s.Impl.InitPlugin() + return nil +} + +// Run executes the step +func (s *StepRPCServer) Run(args any, resp *Response) error { + runArgs, ok := args.(*RunArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + result, err := s.Impl.Run(runArgs.Rollout, runArgs.Context) + *resp = Response{ + Result: result, + Error: err, + } + return nil +} + +// Terminate stops the execution of a running step and exits early +func (s *StepRPCServer) Terminate(args any, resp *Response) error { + runArgs, ok := args.(*TerminateArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + result, err := s.Impl.Terminate(runArgs.Rollout, runArgs.Context) + *resp = Response{ + Result: result, + Error: err, + } + return nil +} + +// Abort reverts previous operation executed by the step if necessary +func (s *StepRPCServer) Abort(args any, resp *Response) error { + runArgs, ok := args.(*AbortArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + result, err := s.Impl.Abort(runArgs.Rollout, runArgs.Context) + *resp = Response{ + Result: result, + Error: err, + } + return nil +} + +// Type returns the type of the traffic routing reconciler +func (s *StepRPCServer) Type(args any, resp *string) error { + *resp = s.Impl.Type() + return nil +} + +// RpcStepPlugin This is the implementation of plugin.Plugin so we can serve/consume +// +// This has two methods: Server must return an RPC server for this plugin +// type. We construct a StepRPCServer for this. +// +// Client must return an implementation of our interface that communicates +// over an RPC client. We return StepPluginRPC for this. +// +// Ignore MuxBroker. That is used to create more multiplexed streams on our +// plugin connection and is a more advanced use case. +type RpcStepPlugin struct { + // Impl Injection + Impl StepPlugin +} + +func (p *RpcStepPlugin) Server(*plugin.MuxBroker) (any, error) { + return &StepRPCServer{Impl: p.Impl}, nil +} + +func (RpcStepPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (any, error) { + return &StepPluginRPC{client: c}, nil +} diff --git a/rollout/steps/plugin/rpc/rpc_test.go b/rollout/steps/plugin/rpc/rpc_test.go new file mode 100644 index 0000000000..b5bac5e3f6 --- /dev/null +++ b/rollout/steps/plugin/rpc/rpc_test.go @@ -0,0 +1,149 @@ +package rpc + +import ( + "context" + "testing" + "time" + + "github.com/argoproj/argo-rollouts/utils/plugin/types" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + goPlugin "github.com/hashicorp/go-plugin" + "github.com/tj/assert" +) + +var testHandshake = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "step", +} + +func pluginClient(t *testing.T) (StepPlugin, goPlugin.ClientProtocol, func(), chan struct{}) { + ctx, cancel := context.WithCancel(context.Background()) + + pluginImpl := &testRpcPlugin{} + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcStepPlugin": &RpcStepPlugin{Impl: pluginImpl}, + } + + ch := make(chan *goPlugin.ReattachConfig, 1) + closeCh := make(chan struct{}) + go goPlugin.Serve(&goPlugin.ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: pluginMap, + Test: &goPlugin.ServeTestConfig{ + Context: ctx, + ReattachConfigCh: ch, + CloseCh: closeCh, + }, + }) + + // We should get a config + var config *goPlugin.ReattachConfig + select { + case config = <-ch: + case <-time.After(2000 * time.Millisecond): + t.Fatal("should've received reattach") + } + if config == nil { + t.Fatal("config should not be nil") + } + + // Connect! + c := goPlugin.NewClient(&goPlugin.ClientConfig{ + Cmd: nil, + HandshakeConfig: testHandshake, + Plugins: pluginMap, + Reattach: config, + }) + client, err := c.Client() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Request the plugin + raw, err := client.Dispense("RpcStepPlugin") + if err != nil { + t.Fail() + } + + plugin, ok := raw.(StepPlugin) + if !ok { + t.Fail() + } + + return plugin, client, cancel, closeCh +} + +func TestPlugin(t *testing.T) { + plugin, _, cancel, closeCh := pluginClient(t) + defer cancel() + + err := plugin.InitPlugin() + if err.Error() != "" { + t.Fail() + } + + ro := v1alpha1.Rollout{} + + _, err = plugin.Run(&ro, &types.RpcStepContext{}) + assert.Equal(t, "", err.Error()) + + _, err = plugin.Terminate(&ro, &types.RpcStepContext{}) + assert.Equal(t, "", err.Error()) + + _, err = plugin.Abort(&ro, &types.RpcStepContext{}) + assert.Equal(t, "", err.Error()) + + typeString := plugin.Type() + assert.Equal(t, "StepPlugin Test", typeString) + + // Canceling should cause an exit + cancel() + <-closeCh +} + +func TestPluginClosedConnection(t *testing.T) { + plugin, client, cancel, closeCh := pluginClient(t) + defer cancel() + + client.Close() + time.Sleep(100 * time.Millisecond) + + const expectedError = "connection is shut down" + + err := plugin.InitPlugin() + assert.Contains(t, err.Error(), expectedError) + + _, err = plugin.Run(&v1alpha1.Rollout{}, &types.RpcStepContext{}) + assert.Contains(t, err.Error(), expectedError) + + _, err = plugin.Terminate(&v1alpha1.Rollout{}, &types.RpcStepContext{}) + assert.Contains(t, err.Error(), expectedError) + + _, err = plugin.Abort(&v1alpha1.Rollout{}, &types.RpcStepContext{}) + assert.Contains(t, err.Error(), expectedError) + + cancel() + <-closeCh +} + +func TestInvalidArgs(t *testing.T) { + server := StepRPCServer{} + badtype := struct { + Args string + }{} + + var resp Response + err := server.Run(badtype, &resp) + assert.Error(t, err) + + err = server.Terminate(badtype, &resp) + assert.Error(t, err) + + err = server.Abort(badtype, &resp) + assert.Error(t, err) + +} diff --git a/rollout/steps/plugin/rpc/rpc_test_implementation.go b/rollout/steps/plugin/rpc/rpc_test_implementation.go new file mode 100644 index 0000000000..db577edf94 --- /dev/null +++ b/rollout/steps/plugin/rpc/rpc_test_implementation.go @@ -0,0 +1,29 @@ +package rpc + +import ( + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/utils/plugin/types" +) + +type testRpcPlugin struct{} + +func (p *testRpcPlugin) InitPlugin() types.RpcError { + return types.RpcError{} +} + +func (p *testRpcPlugin) Run(_ *v1alpha1.Rollout, _ *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + return types.RpcStepResult{}, types.RpcError{} +} + +func (p *testRpcPlugin) Terminate(_ *v1alpha1.Rollout, _ *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + return types.RpcStepResult{}, types.RpcError{} +} + +func (p *testRpcPlugin) Abort(_ *v1alpha1.Rollout, _ *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + return types.RpcStepResult{}, types.RpcError{} +} + +// Type returns the type of the step plugin +func (p *testRpcPlugin) Type() string { + return "StepPlugin Test" +} diff --git a/rollout/sync.go b/rollout/sync.go index 6d656e2d5a..a2d4a0d1e7 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -659,7 +659,7 @@ func (c *rolloutContext) calculateRolloutConditions(newStatus v1alpha1.RolloutSt // everything but lastTransitionTime. SetRolloutCondition already does that but // it also is not updating conditions when the reason of the new condition is the // same as the old. The Progressing condition is a special case because we want to - // update with the same reason and change just lastUpdateTime iff we notice any + // update with the same reason and change just lastUpdateTime if we notice any // progress. That's why we handle it here. if currentCond != nil { if currentCond.Status == corev1.ConditionTrue { @@ -895,6 +895,7 @@ func (c *rolloutContext) resetRolloutStatus(newStatus *v1alpha1.RolloutStatus) { newStatus.BlueGreen.ScaleUpPreviewCheckPoint = false newStatus.Canary.CurrentStepAnalysisRunStatus = nil newStatus.Canary.CurrentBackgroundAnalysisRunStatus = nil + newStatus.Canary.StepPluginStatuses = nil newStatus.CurrentStepIndex = replicasetutil.ResetCurrentStepIndex(c.rollout) } diff --git a/rollout/trafficrouting/plugin/client/client.go b/rollout/trafficrouting/plugin/client/client.go index a9a8b0b713..05ec1bc169 100644 --- a/rollout/trafficrouting/plugin/client/client.go +++ b/rollout/trafficrouting/plugin/client/client.go @@ -7,6 +7,7 @@ import ( "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" "github.com/argoproj/argo-rollouts/utils/plugin" + "github.com/argoproj/argo-rollouts/utils/plugin/types" goPlugin "github.com/hashicorp/go-plugin" ) @@ -52,7 +53,7 @@ func (t *trafficPlugin) startPlugin(pluginName string) (rpc.TrafficRouterPlugin, if t.pluginClient[pluginName] == nil || t.pluginClient[pluginName].Exited() { - pluginPath, args, err := plugin.GetPluginInfo(pluginName) + pluginPath, args, err := plugin.GetPluginInfo(pluginName, types.PluginTypeTrafficRouter) if err != nil { return nil, fmt.Errorf("unable to find plugin (%s): %w", pluginName, err) } diff --git a/test/cmd/step-plugin-e2e/internal/plugin/plugin.go b/test/cmd/step-plugin-e2e/internal/plugin/plugin.go new file mode 100644 index 0000000000..f46d9bfa78 --- /dev/null +++ b/test/cmd/step-plugin-e2e/internal/plugin/plugin.go @@ -0,0 +1,172 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +type Config struct { + Return string + Requeue string + + Abort Abort + Terminate Terminate +} + +type Abort struct { + Return string +} + +type Terminate struct { + Return string +} + +type State struct { + Data string + Count int +} + +type rpcPlugin struct { + LogCtx *log.Entry +} + +func New(logCtx *log.Entry) rpc.StepPlugin { + return &rpcPlugin{ + LogCtx: logCtx, + } +} + +func (p *rpcPlugin) InitPlugin() types.RpcError { + p.LogCtx.Infof("InitPlugin") + return types.RpcError{} +} + +func (p *rpcPlugin) Run(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + p.LogCtx.Infof("Run plugin for rollout %s/%s", rollout.Namespace, rollout.Name) + + // Get configs + var config Config + var state State + if context != nil { + if context.Config != nil { + if err := json.Unmarshal(context.Config, &config); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal config: %w", err).Error()} + } + p.LogCtx.Infof("Using config: %+v", config) + } + if context.Status != nil { + if err := json.Unmarshal(context.Status, &state); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal status: %w", err).Error()} + } + p.LogCtx.Infof("Using status: %+v", state) + } + } + + if state.Data == "" { + state.Data = uuid.New().String() + } + state.Count = state.Count + 1 + + var requeue time.Duration + if config.Requeue != "" { + v, err := time.ParseDuration(config.Requeue) + if err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not parse requeue duration: %w", err).Error()} + } + requeue = v + } + + phase := types.PhaseSuccessful + if config.Return != "" { + phase = types.StepPhase(config.Return) + if err := phase.Validate(); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not parse phase: %w", err).Error()} + } + } + + return Result(state, phase, requeue) +} + +func (p *rpcPlugin) Terminate(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + p.LogCtx.Infof("Terminate plugin for rollout %s/%s", rollout.Namespace, rollout.Name) + + // Get configs + var config Config + var state State + if context != nil { + if context.Config != nil { + if err := json.Unmarshal(context.Config, &config); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal config: %w", err).Error()} + } + } + if context.Status != nil { + if err := json.Unmarshal(context.Status, &state); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal status: %w", err).Error()} + } + } + } + + phase := types.PhaseSuccessful + if config.Terminate.Return != "" { + phase = types.StepPhase(config.Terminate.Return) + if err := phase.Validate(); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not parse phase: %w", err).Error()} + } + } + + return Result(state, phase, 0) +} + +func (p *rpcPlugin) Abort(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + p.LogCtx.Infof("Abort plugin for rollout %s/%s", rollout.Namespace, rollout.Name) + + // Get configs + var config Config + var state State + if context != nil { + if context.Config != nil { + if err := json.Unmarshal(context.Config, &config); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal config: %w", err).Error()} + } + } + if context.Status != nil { + if err := json.Unmarshal(context.Status, &state); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal status: %w", err).Error()} + } + } + } + + phase := types.PhaseSuccessful + if config.Abort.Return != "" { + phase = types.StepPhase(config.Abort.Return) + if err := phase.Validate(); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not parse phase: %w", err).Error()} + } + } + + return Result(state, phase, 0) +} + +func (p *rpcPlugin) Type() string { + return "e2e-plugin" +} + +func Result(state State, phase types.StepPhase, requeue time.Duration) (types.RpcStepResult, types.RpcError) { + stateRaw, err := json.Marshal(state) + if err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Sprintf("Could not marshal state: %v", err)} + } + + return types.RpcStepResult{ + Phase: phase, + RequeueAfter: requeue, + Status: stateRaw, + }, types.RpcError{} +} diff --git a/test/cmd/step-plugin-e2e/main.go b/test/cmd/step-plugin-e2e/main.go new file mode 100644 index 0000000000..995b0fe690 --- /dev/null +++ b/test/cmd/step-plugin-e2e/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "strings" + + rolloutsPlugin "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + "github.com/argoproj/argo-rollouts/test/cmd/step-plugin-e2e/internal/plugin" + goPlugin "github.com/hashicorp/go-plugin" + log "github.com/sirupsen/logrus" +) + +var handshakeConfig = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "step", +} + +func main() { + logCtx := log.WithFields(log.Fields{"plugin": "e2e"}) + + setLogLevel("debug") + log.SetFormatter(createFormatter("text")) + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcStepPlugin": &rolloutsPlugin.RpcStepPlugin{Impl: plugin.New(logCtx)}, + } + + goPlugin.Serve(&goPlugin.ServeConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + }) +} + +func createFormatter(logFormat string) log.Formatter { + var formatType log.Formatter + switch strings.ToLower(logFormat) { + case "json": + formatType = &log.JSONFormatter{} + case "text": + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + default: + log.Infof("Unknown format: %s. Using text logformat", logFormat) + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + } + + return formatType +} + +func setLogLevel(logLevel string) { + level, err := log.ParseLevel(logLevel) + if err != nil { + log.Fatal(err) + } + log.SetLevel(level) +} diff --git a/test/cmd/step-plugin-sample/internal/plugin/plugin.go b/test/cmd/step-plugin-sample/internal/plugin/plugin.go new file mode 100644 index 0000000000..de2341dfd1 --- /dev/null +++ b/test/cmd/step-plugin-sample/internal/plugin/plugin.go @@ -0,0 +1,263 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "math/rand" + "strconv" + "sync" + "time" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" +) + +type Config struct { + Async bool + Aggregate bool +} + +type State struct { + Id string + SharedId string + + Value string +} + +type Result struct { + Value int + Id string +} + +type rpcPlugin struct { + LogCtx *log.Entry + Seed int64 + + lock *sync.RWMutex + generator *rand.Rand + randomMap map[string]*Result +} + +func New(logCtx *log.Entry, seed int64) rpc.StepPlugin { + return &rpcPlugin{ + LogCtx: logCtx, + Seed: seed, + lock: &sync.RWMutex{}, + } +} + +func (p *rpcPlugin) InitPlugin() types.RpcError { + p.lock.Lock() + defer p.lock.Unlock() + + p.LogCtx.Infof("InitPlugin with seed %d", p.Seed) + p.generator = rand.New(rand.NewSource(p.Seed)) + p.randomMap = map[string]*Result{} + return types.RpcError{} +} + +func (p *rpcPlugin) Run(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + // if !p.validate(rollout) { + + // } + + // Get configs + var config Config + var state State + if context != nil { + if context.Config != nil { + if err := json.Unmarshal(context.Config, &config); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal config: %w", err).Error()} + } + } + if context.Status != nil { + if err := json.Unmarshal(context.Status, &state); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal status: %w", err).Error()} + } + } + } + + // Already completed + if state.Value != "" { + return CompletedResult(state) + } + + // If Aggregate, look for previous steps. We want to re-use the value in memory for that step id + if config.Aggregate && state.SharedId == "" { + lastStep := getLastStep(rollout, context.PluginName) + if lastStep != nil { + var lastState State + if err := json.Unmarshal(lastStep.Status, &lastState); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: "could not unmarshal last step status"} + } + if lastState.SharedId != "" { + // consecutive aggregate steps all use the same id + state.SharedId = lastState.SharedId + } else { + // Most likely the last step was not an aggregate, so we restart a sequence with this id + state.SharedId = lastState.Id + } + } + } + + // Already started, look if it is completed + if state.Id != "" { + p.lock.RLock() + v, ok := p.randomMap[state.getId()] + p.lock.RUnlock() + if ok { + if v.Id == state.Id { + // Make sure the current step is the one that updated the value + state.Value = strconv.Itoa(v.Value) + return CompletedResult(state) + } + } + return RunningResult(state) + } + + state.Id = uuid.New().String() + if config.Async { + go func(state State) { + time.Sleep(60 * time.Second) + p.generate(state) + }(state) + return RunningResult(state) + } else { + state.Value = strconv.Itoa(p.generate(state).Value) + } + + return CompletedResult(state) + +} + +func (p *rpcPlugin) Terminate(rollout *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + // Get configs + var config Config + var state State + if context != nil { + if context.Config != nil { + if err := json.Unmarshal(context.Config, &config); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal config: %w", err).Error()} + } + } + if context.Status != nil { + if err := json.Unmarshal(context.Status, &state); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal status: %w", err).Error()} + } + } + } + + log.Infof("Ignoring future value for '%s'", state.Id) + state.Value = "0" + return CompletedResult(state) + +} + +func (p *rpcPlugin) Abort(_ *v1alpha1.Rollout, context *types.RpcStepContext) (types.RpcStepResult, types.RpcError) { + + // Get configs + var config Config + var state State + if context != nil { + if context.Config != nil { + if err := json.Unmarshal(context.Config, &config); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal config: %w", err).Error()} + } + } + if context.Status != nil { + if err := json.Unmarshal(context.Status, &state); err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Errorf("could not unmarshal status: %w", err).Error()} + } + } + } + + p.lock.Lock() + defer p.lock.Unlock() + + log.Infof("deleting entry for id '%s'", state.getId()) + delete(p.randomMap, state.getId()) + + return CompletedResult(state) +} + +func (p *rpcPlugin) Type() string { + return "plugin-example" +} + +// func (p *RpcPlugin) validate(rollout *v1alpha1.Rollout) bool { +// if rollout == nil || rollout.Status == nil || rollout.Status.Canary == nil {} +// } + +func (p *rpcPlugin) generate(state State) *Result { + p.lock.Lock() + defer p.lock.Unlock() + + base := 0 + if state.SharedId != "" { + v, ok := p.randomMap[state.SharedId] + if ok { + log.Infof("Using base '%d' for aggregate %s", v.Value, state.SharedId) + base = v.Value + } + } + + result := &Result{ + Value: p.generator.Intn(100) + base, + Id: state.Id, + } + p.randomMap[state.getId()] = result + log.Infof("Set '%d' for id %s", result.Value, state.getId()) + + return result +} + +func (s State) getId() string { + if s.SharedId != "" { + return s.SharedId + } + return s.Id +} + +func getLastStep(rollout *v1alpha1.Rollout, name string) *v1alpha1.StepPluginStatus { + var last *v1alpha1.StepPluginStatus + currentStepIndex := *rollout.Status.CurrentStepIndex + for _, status := range rollout.Status.Canary.StepPluginStatuses { + if status.Name != name || status.Index == currentStepIndex { + continue + } + + if last == nil || status.Index > last.Index { + last = &status + } + } + return last +} + +func CompletedResult(state State) (types.RpcStepResult, types.RpcError) { + stateRaw, err := json.Marshal(state) + if err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Sprintf("Could not marshal state: %v", err)} + } + + return types.RpcStepResult{ + Phase: types.PhaseSuccessful, + Message: "Operation completed", + Status: stateRaw, + }, types.RpcError{} +} + +func RunningResult(state State) (types.RpcStepResult, types.RpcError) { + stateRaw, err := json.Marshal(state) + if err != nil { + return types.RpcStepResult{}, types.RpcError{ErrorString: fmt.Sprintf("Could not marshal state: %v", err)} + } + + return types.RpcStepResult{ + Phase: types.PhaseRunning, + RequeueAfter: 15 * time.Second, + Status: stateRaw, + }, types.RpcError{} +} diff --git a/test/cmd/step-plugin-sample/internal/plugin/plugin_test.go b/test/cmd/step-plugin-sample/internal/plugin/plugin_test.go new file mode 100644 index 0000000000..97897217c0 --- /dev/null +++ b/test/cmd/step-plugin-sample/internal/plugin/plugin_test.go @@ -0,0 +1,368 @@ +package plugin + +import ( + "encoding/json" + "sync" + "testing" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/utils/ptr" +) + +func new(logCtx *log.Entry, seed int64) *rpcPlugin { + return &rpcPlugin{ + LogCtx: logCtx, + Seed: seed, + lock: &sync.RWMutex{}, + } +} + +func newRollout() *v1alpha1.Rollout { + return &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{}, + }, + }, + Status: v1alpha1.RolloutStatus{ + Canary: v1alpha1.CanaryStatus{}, + }, + } +} + +func getStateFromResult(t *testing.T, result types.RpcStepResult) *State { + require.NotNil(t, result) + + var state *State + if err := json.Unmarshal(result.Status, &state); err != nil { + require.NotNil(t, err) + } + return state +} + +func toRaw(obj any) json.RawMessage { + raw, err := json.Marshal(obj) + if err != nil { + panic(err) + } + return raw +} + +func Test_rpcPlugin_Run(t *testing.T) { + t.Run("Return value with nil context", func(t *testing.T) { + p := New(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + + rollout := newRollout() + + got, err := p.Run(rollout, nil) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseSuccessful, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "74", state.Value) + + }) + t.Run("Return value with empty context", func(t *testing.T) { + p := New(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + + rollout := newRollout() + context := &types.RpcStepContext{ + PluginName: "test", + Config: nil, + Status: nil, + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseSuccessful, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "74", state.Value) + + }) + + t.Run("Return value with existing state", func(t *testing.T) { + p := New(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + + rollout := newRollout() + context := &types.RpcStepContext{ + PluginName: "test", + Config: nil, + Status: toRaw(&State{ + Id: "fake-id", + Value: "123", + }), + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseSuccessful, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "123", state.Value) + }) + + t.Run("Running status with async config", func(t *testing.T) { + p := New(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + + rollout := newRollout() + context := &types.RpcStepContext{ + PluginName: "test", + Config: toRaw(&Config{ + Async: true, + }), + Status: nil, + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseRunning, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "", state.Value) + }) + + t.Run("Return running with async config and existing state not completed", func(t *testing.T) { + p := new(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + + rollout := newRollout() + context := &types.RpcStepContext{ + PluginName: "test", + Config: toRaw(&Config{ + Async: true, + }), + Status: toRaw(&State{ + Id: "fake-id", + }), + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseRunning, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "", state.Value) + }) + + t.Run("Return value with async config and existing state", func(t *testing.T) { + p := new(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + p.randomMap["fake-id"] = &Result{ + Value: 44, + Id: "fake-id", + } + + rollout := newRollout() + context := &types.RpcStepContext{ + PluginName: "test", + Config: toRaw(&Config{ + Async: true, + }), + Status: toRaw(&State{ + Id: "fake-id", + }), + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseSuccessful, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "44", state.Value) + }) + + t.Run("Return value with aggregate and no previous step", func(t *testing.T) { + p := new(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + p.randomMap["foo"] = &Result{ + Value: 6, + Id: "foo", + } + + rollout := newRollout() + rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{ + { + Plugin: &v1alpha1.PluginStep{ + Name: "test", + }, + }, + { + Plugin: &v1alpha1.PluginStep{ + Name: "test", + }, + }, + } + rollout.Status.CurrentStepIndex = ptr.To(int32(0)) + rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test", + Status: toRaw(&State{ + Id: "foo", + Value: "99", + }), + }, + } + context := &types.RpcStepContext{ + PluginName: "test", + Config: toRaw(&Config{ + Aggregate: true, + }), + Status: nil, + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseSuccessful, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "74", state.Value) + }) + + t.Run("Return value with aggregate", func(t *testing.T) { + p := new(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + p.randomMap["foo"] = &Result{ + Value: 6, + Id: "foo", + } + + rollout := newRollout() + rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{ + { + Plugin: &v1alpha1.PluginStep{ + Name: "test", + }, + }, + { + Plugin: &v1alpha1.PluginStep{ + Name: "test", + }, + }, + } + rollout.Status.CurrentStepIndex = ptr.To(int32(1)) + rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test", + Status: toRaw(&State{ + Id: "foo", + Value: "6", + }), + }, + } + context := &types.RpcStepContext{ + PluginName: "test", + Config: toRaw(&Config{ + Aggregate: true, + }), + Status: nil, + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseSuccessful, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "80", state.Value) + }) + + t.Run("Return value with aggregate of an aggregate", func(t *testing.T) { + p := new(log.WithFields(log.Fields{}), 0) + err := p.InitPlugin() + if err.HasError() { + t.Fatalf("Got error during init: %s", err.Error()) + } + p.randomMap["foo"] = &Result{ + Value: 80, + Id: "bar", + } + + rollout := newRollout() + rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{ + { + Plugin: &v1alpha1.PluginStep{ + Name: "test", + }, + }, + { + Plugin: &v1alpha1.PluginStep{ + Name: "test", + }, + }, + { + Plugin: &v1alpha1.PluginStep{ + Name: "test", + }, + }, + } + rollout.Status.CurrentStepIndex = ptr.To(int32(2)) + rollout.Status.Canary.StepPluginStatuses = []v1alpha1.StepPluginStatus{ + { + Index: 0, + Name: "test", + Status: toRaw(&State{ + Id: "foo", + Value: "6", + }), + }, + { + Index: 1, + Name: "test", + Status: toRaw(&State{ + Id: "bar", + SharedId: "foo", + Value: "80", + }), + }, + } + context := &types.RpcStepContext{ + PluginName: "test", + Config: toRaw(&Config{ + Aggregate: true, + }), + Status: nil, + } + + got, err := p.Run(rollout, context) + + require.False(t, err.HasError(), "Error: %s", err.Error()) + assert.Equal(t, types.PhaseSuccessful, got.Phase) + state := getStateFromResult(t, got) + assert.Equal(t, "154", state.Value) + }) + +} diff --git a/test/cmd/step-plugin-sample/main.go b/test/cmd/step-plugin-sample/main.go new file mode 100644 index 0000000000..f70d4d25eb --- /dev/null +++ b/test/cmd/step-plugin-sample/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "os" + "strconv" + "strings" + "time" + + rolloutsPlugin "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + "github.com/argoproj/argo-rollouts/test/cmd/step-plugin-sample/internal/plugin" + goPlugin "github.com/hashicorp/go-plugin" + log "github.com/sirupsen/logrus" +) + +// handshakeConfigs are used to just do a basic handshake between +// a plugin and host. If the handshake fails, a user friendly error is shown. +// This prevents users from executing bad plugins or executing a plugin +// directory. It is a UX feature, not a security feature. +var handshakeConfig = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "step", +} + +func main() { + logCtx := log.WithFields(log.Fields{"plugin": "step"}) + + setLogLevel("debug") + log.SetFormatter(createFormatter("text")) + + seed := time.Now().UnixNano() + + args := os.Args[1:] + if len(args) > 0 { + if args[0] == "--seed" { + if len(args) >= 2 { + n, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + log.Fatal(err) + } + seed = n + } else { + log.Fatal("No value for --seed argument") + } + } + } + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcStepPlugin": &rolloutsPlugin.RpcStepPlugin{Impl: plugin.New(logCtx, seed)}, + } + + logCtx.Debug("message from plugin", "foo", "bar") + + goPlugin.Serve(&goPlugin.ServeConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + }) +} + +func createFormatter(logFormat string) log.Formatter { + var formatType log.Formatter + switch strings.ToLower(logFormat) { + case "json": + formatType = &log.JSONFormatter{} + case "text": + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + default: + log.Infof("Unknown format: %s. Using text logformat", logFormat) + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + } + + return formatType +} + +func setLogLevel(logLevel string) { + level, err := log.ParseLevel(logLevel) + if err != nil { + log.Fatal(err) + } + log.SetLevel(level) +} diff --git a/test/cmd/step-plugin-sample/main_test.go b/test/cmd/step-plugin-sample/main_test.go new file mode 100644 index 0000000000..17bc5730b7 --- /dev/null +++ b/test/cmd/step-plugin-sample/main_test.go @@ -0,0 +1,107 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + rolloutsPlugin "github.com/argoproj/argo-rollouts/rollout/steps/plugin/rpc" + "github.com/argoproj/argo-rollouts/test/cmd/step-plugin-sample/internal/plugin" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + goPlugin "github.com/hashicorp/go-plugin" + log "github.com/sirupsen/logrus" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/tj/assert" +) + +var testHandshake = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "step", +} + +func pluginClient(t *testing.T) (rpc.StepPlugin, goPlugin.ClientProtocol, func(), chan struct{}) { + ctx, cancel := context.WithCancel(context.Background()) + + pluginImpl := plugin.New(log.WithFields(log.Fields{}), 0) + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcStepPlugin": &rolloutsPlugin.RpcStepPlugin{Impl: pluginImpl}, + } + + ch := make(chan *goPlugin.ReattachConfig, 1) + closeCh := make(chan struct{}) + go goPlugin.Serve(&goPlugin.ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: pluginMap, + Test: &goPlugin.ServeTestConfig{ + Context: ctx, + ReattachConfigCh: ch, + CloseCh: closeCh, + }, + }) + + // We should get a config + var config *goPlugin.ReattachConfig + select { + case config = <-ch: + case <-time.After(2000 * time.Millisecond): + t.Fatal("should've received reattach") + } + if config == nil { + t.Fatal("config should not be nil") + } + + // Connect! + c := goPlugin.NewClient(&goPlugin.ClientConfig{ + Cmd: nil, + HandshakeConfig: testHandshake, + Plugins: pluginMap, + Reattach: config, + }) + client, err := c.Client() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Request the plugin + raw, err := client.Dispense("RpcStepPlugin") + if err != nil { + t.Fail() + } + + plugin, ok := raw.(rpc.StepPlugin) + if !ok { + t.Fail() + } + + return plugin, client, cancel, closeCh +} + +func TestPlugin(t *testing.T) { + plugin, _, cancel, closeCh := pluginClient(t) + defer cancel() + + err := plugin.InitPlugin() + if err.Error() != "" { + t.Fail() + } + + ro := v1alpha1.Rollout{} + rpcCtx := &types.RpcStepContext{ + PluginName: "test-1", + Config: nil, + Status: nil, + } + + result, err := plugin.Run(&ro, rpcCtx) + assert.Equal(t, "", err.Error()) + assert.NotNil(t, result) + + // Canceling should cause an exit + cancel() + <-closeCh +} diff --git a/test/e2e/canary_test.go b/test/e2e/canary_test.go index 8656aa5c4d..7bf3af22c8 100644 --- a/test/e2e/canary_test.go +++ b/test/e2e/canary_test.go @@ -47,7 +47,7 @@ func (s *CanarySuite) TestCanarySetCanaryScale() { - pause: {duration: 5s} ` s.Given(). - RolloutTemplate("@functional/nginx-template.yaml", "set-canary-scale"). + RolloutTemplate("@functional/nginx-template.yaml", map[string]string{"REPLACEME": "set-canary-scale"}). SetSteps(canarySteps). When(). ApplyManifests(). diff --git a/test/e2e/step-plugin/argo-rollouts-config.yaml b/test/e2e/step-plugin/argo-rollouts-config.yaml new file mode 100644 index 0000000000..281d851d7a --- /dev/null +++ b/test/e2e/step-plugin/argo-rollouts-config.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/component: rollouts-controller + app.kubernetes.io/name: argo-rollouts + app.kubernetes.io/part-of: argo-rollouts + name: argo-rollouts-config + namespace: argo-rollouts +data: + stepPlugins: |- + - name: "step/e2e-test" # name of the plugin, it must match the name required by the plugin so it can find it's configuration + location: "file://plugin-bin/e2e-step-plugin" # supports http(s):// urls and file:// + disabled: false + - name: "step/e2e-test-disabled" # name of the plugin, it must match the name required by the plugin so it can find it's configuration + location: "file://plugin-bin/e2e-step-plugin" # supports http(s):// urls and file:// + disabled: true diff --git a/test/e2e/step-plugin/disabled-rollout.yaml b/test/e2e/step-plugin/disabled-rollout.yaml new file mode 100644 index 0000000000..3ebc15c679 --- /dev/null +++ b/test/e2e/step-plugin/disabled-rollout.yaml @@ -0,0 +1,31 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: disabled-step-plugin +spec: + selector: + matchLabels: + app: disabled-step-plugin + strategy: + canary: + steps: + - plugin: + name: $$disabled_plugin$$ + config: + return: Successful + - plugin: + name: step/e2e-test + config: + return: Successful + template: + metadata: + labels: + app: disabled-step-plugin + spec: + containers: + - name: basic + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 1m diff --git a/test/e2e/step-plugin/invalid-rollout.yaml b/test/e2e/step-plugin/invalid-rollout.yaml new file mode 100644 index 0000000000..df75248db1 --- /dev/null +++ b/test/e2e/step-plugin/invalid-rollout.yaml @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: invalid-step-plugin +spec: + progressDeadlineSeconds: 15 + selector: + matchLabels: + app: invalid-step-plugin + strategy: + canary: + steps: + - plugin: + name: step/e2e-test-invalid-name + template: + metadata: + labels: + app: invalid-step-plugin + spec: + containers: + - name: basic + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 1m diff --git a/test/e2e/step-plugin/template-rollout.yaml b/test/e2e/step-plugin/template-rollout.yaml new file mode 100644 index 0000000000..1d4d064662 --- /dev/null +++ b/test/e2e/step-plugin/template-rollout.yaml @@ -0,0 +1,32 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: step-plugin-e2e-$$name$$ +spec: + selector: + matchLabels: + app: step-plugin-e2e-$$name$$ + strategy: + canary: + steps: + - plugin: + name: step/e2e-test + config: + return: Successful + - plugin: + name: step/e2e-test + config: + return: $$phase$$ + requeue: 17s + template: + metadata: + labels: + app: step-plugin-e2e-$$name$$ + spec: + containers: + - name: basic + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 1m diff --git a/test/e2e/step_plugin_test.go b/test/e2e/step_plugin_test.go new file mode 100644 index 0000000000..5a938f3f51 --- /dev/null +++ b/test/e2e/step_plugin_test.go @@ -0,0 +1,338 @@ +//go:build e2e +// +build e2e + +package e2e + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/tj/assert" + "gopkg.in/yaml.v2" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/test/fixtures" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + corev1 "k8s.io/api/core/v1" +) + +const E2EStepPluginName = "step/e2e-test" +const E2EStepPluginNameDisabled = "step/e2e-test-disabled" + +type StepPluginSuite struct { + fixtures.E2ESuite +} + +func TestStepPluginSuite(t *testing.T) { + suite.Run(t, new(StepPluginSuite)) +} + +func (s *StepPluginSuite) SetupSuite() { + s.E2ESuite.SetupSuite() + if !IsStepPluginConfigured(&s.Common, s.GetControllerConfig()) { + s.T().SkipNow() + } +} + +// getControllerConfiguredPlugin look at the controller default config map to find the list of plugins +// This is a best effort because it does not mean the controller have these plugins configured in-memory +func IsStepPluginConfigured(c *fixtures.Common, config *corev1.ConfigMap) bool { + if config == nil { + return false + } + + var stepPlugins []types.PluginItem + if err := yaml.Unmarshal([]byte(config.Data["stepPlugins"]), &stepPlugins); err != nil { + c.CheckError(err) + } + + hasPlugin := false + hasPluginDisabled := false + for _, p := range stepPlugins { + if p.Name == E2EStepPluginName { + hasPlugin = true + } + if p.Name == E2EStepPluginNameDisabled { + hasPluginDisabled = true + } + } + return hasPlugin && hasPluginDisabled +} + +// test step plugin ignored when disabled +// test rollout error when step plugin not configured + +func (s *StepPluginSuite) TestRolloutCompletesWhenStepSuccessful() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "successful", "$$phase$$": string(types.PhaseSuccessful)}). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Healthy"). + Then(). + ExpectStableRevision("2"). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.EqualValues(s.T(), 2, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseSuccessful, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + }) +} + +func (s *StepPluginSuite) TestRolloutAbortWhenStepFails() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "failed", "$$phase$$": string(types.PhaseFailed)}). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Degraded"). + Then(). + ExpectStableRevision("1"). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.True(s.T(), rollout.Status.Abort) + assert.EqualValues(s.T(), 3, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseFailed, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + + stepStatus = rollout.Status.Canary.StepPluginStatuses[2] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 0, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseSuccessful, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationAbort, stepStatus.Operation) + }) +} + +func (s *StepPluginSuite) TestRolloutAbortStepsWhenAborted() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "aborted", "$$phase$$": string(types.PhaseRunning)}). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Progressing"). + WaitForRolloutCanaryStepIndex(1). + WaitForRolloutStepPluginRunning(). + AbortRollout().WaitForRolloutStatus("Degraded"). + Then(). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.True(s.T(), rollout.Status.Abort) + assert.EqualValues(s.T(), 4, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseRunning, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + + stepStatus = rollout.Status.Canary.StepPluginStatuses[2] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseSuccessful, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationAbort, stepStatus.Operation) + + stepStatus = rollout.Status.Canary.StepPluginStatuses[3] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 0, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseSuccessful, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationAbort, stepStatus.Operation) + }) +} + +func (s *StepPluginSuite) TestRolloutRetryAbortedRollout() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "aborted", "$$phase$$": string(types.PhaseRunning)}). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Progressing"). + WaitForRolloutCanaryStepIndex(1). + WaitForRolloutStepPluginRunning(). + AbortRollout().WaitForRolloutStatus("Degraded"). + RetryRollout().WaitForRolloutStepPluginRunning(). + Then(). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.False(s.T(), rollout.Status.Abort) + assert.EqualValues(s.T(), 2, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseRunning, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + }) +} + +func (s *StepPluginSuite) TestRolloutCompletesWhenPromotedAndStepRunning() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "full-promotion", "$$phase$$": string(types.PhaseRunning)}). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Progressing"). + WaitForRolloutCanaryStepIndex(1). + WaitForRolloutStepPluginRunning(). + PromoteRolloutFull().WaitForRolloutStatus("Healthy"). + Then(). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.EqualValues(s.T(), 3, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseRunning, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + + stepStatus = rollout.Status.Canary.StepPluginStatuses[2] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseSuccessful, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationTerminate, stepStatus.Operation) + }) +} + +func (s *StepPluginSuite) TestRolloutRetriesUntilDeadlineWhenRunning() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "running", "$$phase$$": string(types.PhaseRunning)}). + When().ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec(` +spec: + progressDeadlineSeconds: 45`). + UpdateSpec().WaitForRolloutStatus("Progressing"). + WaitForRolloutCanaryStepIndex(1). + WaitForRolloutStepPluginRunning(). + Wait(50 * time.Second). + WaitForRolloutStatus("Degraded"). + Then(). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.EqualValues(s.T(), 1, *rollout.Status.CurrentStepIndex) + assert.EqualValues(s.T(), 2, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseRunning, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + assert.NotEmpty(s.T(), stepStatus.Status) + + // Performance test validating that the plugin is not constantly executed + assert.EqualValues(s.T(), "17s", stepStatus.Backoff) + assert.EqualValues(s.T(), 3, stepStatus.Executions) + + }) +} + +func (s *StepPluginSuite) TestRolloutRetriesUntilDeadlineWhenError() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "error", "$$phase$$": "invalidPhaseCausingPluginError"}). + When().ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec(` +spec: + progressDeadlineSeconds: 45`). + UpdateSpec().WaitForRolloutStatus("Progressing"). + Wait(50 * time.Second). + WaitForRolloutStatus("Degraded"). + Then(). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.EqualValues(s.T(), 1, *rollout.Status.CurrentStepIndex) + assert.EqualValues(s.T(), 2, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseError, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + assert.Contains(s.T(), stepStatus.Message, "phase 'invalidPhaseCausingPluginError' is not valid") + assert.Empty(s.T(), stepStatus.Status) + + // Performance test validating that the plugin is not constantly executed + assert.EqualValues(s.T(), "30s", stepStatus.Backoff) + assert.EqualValues(s.T(), 2, stepStatus.Executions) + + }) +} + +func (s *StepPluginSuite) TestRolloutStatusIsNotUsedOnNewRollout() { + s.Given(). + RolloutTemplate("@step-plugin/template-rollout.yaml", map[string]string{"$$name$$": "run-again", "$$phase$$": string(types.PhaseSuccessful)}). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Healthy").Then().ExpectStableRevision("2").When(). + UpdateSpec(` +spec: + strategy: + canary: + steps: + - plugin: + name: step/e2e-test + config: + return: Successful`). + UpdateSpec().WaitForRolloutStatus("Healthy"). + Then(). + ExpectStableRevision("3"). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.EqualValues(s.T(), 1, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[0] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 0, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseSuccessful, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + }) +} + +func (s *StepPluginSuite) TestRolloutErrorWhenStepPluginNotConfigured() { + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + s.Given(). + StartEventWatch(ctx). + RolloutObjects("@step-plugin/invalid-rollout.yaml"). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Degraded"). + Then(). + ExpectStableRevision("1"). + ExpectRolloutEventsContains([]string{"ReconciliationError"}). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.EqualValues(s.T(), 0, *rollout.Status.CurrentStepIndex) + assert.EqualValues(s.T(), 0, len(rollout.Status.Canary.StepPluginStatuses)) + }) +} + +func (s *StepPluginSuite) TestRolloutSkipPluginWhenDisabled() { + s.Given(). + RolloutTemplate("@step-plugin/disabled-rollout.yaml", map[string]string{"$$disabled_plugin$$": E2EStepPluginNameDisabled}). + When(). + ApplyManifests().WaitForRolloutStatus("Healthy"). + UpdateSpec().WaitForRolloutStatus("Healthy"). + Then(). + ExpectStableRevision("2"). + Assert(func(t *fixtures.Then) { + rollout := t.GetRollout() + assert.EqualValues(s.T(), 2, len(rollout.Status.Canary.StepPluginStatuses)) + + stepStatus := rollout.Status.Canary.StepPluginStatuses[0] + assert.EqualValues(s.T(), E2EStepPluginNameDisabled, stepStatus.Name) + assert.EqualValues(s.T(), 0, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + assert.EqualValues(s.T(), true, stepStatus.Disabled) + + stepStatus = rollout.Status.Canary.StepPluginStatuses[1] + assert.EqualValues(s.T(), E2EStepPluginName, stepStatus.Name) + assert.EqualValues(s.T(), 1, stepStatus.Index) + assert.EqualValues(s.T(), v1alpha1.StepPluginPhaseSuccessful, stepStatus.Phase) + assert.EqualValues(s.T(), v1alpha1.StepPluginOperationRun, stepStatus.Operation) + assert.EqualValues(s.T(), false, stepStatus.Disabled) + }) +} diff --git a/test/fixtures/common.go b/test/fixtures/common.go index 670d2dd2f4..b5c5dae7b0 100644 --- a/test/fixtures/common.go +++ b/test/fixtures/common.go @@ -13,6 +13,7 @@ import ( "time" a6util "github.com/argoproj/argo-rollouts/utils/apisix" + k8errors "k8s.io/apimachinery/pkg/api/errors" smiv1alpha1 "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/split/v1alpha1" smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" @@ -681,6 +682,17 @@ func (c *Common) GetRolloutEventReasons() []string { return reasons } +func (c *Common) GetControllerConfig() *corev1.ConfigMap { + configMap, err := c.kubeClient.CoreV1().ConfigMaps(c.namespace).Get(c.Context, "argo-rollouts-config", metav1.GetOptions{}) + if err != nil { + if k8errors.IsNotFound(err) { + return nil + } + c.CheckError(err) + } + return configMap +} + // PrintRolloutEvents prints all Kubernetes events associated with the given rollout. func (c *Common) PrintRolloutEvents(ro *v1alpha1.Rollout) { opts := metav1.ListOptions{FieldSelector: fields.ParseSelectorOrDie(fmt.Sprintf("involvedObject.uid=%s", ro.UID)).String()} diff --git a/test/fixtures/given.go b/test/fixtures/given.go index 57e17251c6..a6e332d69a 100644 --- a/test/fixtures/given.go +++ b/test/fixtures/given.go @@ -34,9 +34,12 @@ func (g *Given) RolloutObjects(text string) *Given { return g } -func (g *Given) RolloutTemplate(text, name string) *Given { +func (g *Given) RolloutTemplate(text string, values map[string]string) *Given { yamlBytes := g.yamlBytes(text) - newText := strings.ReplaceAll(string(yamlBytes), "REPLACEME", name) + newText := string(yamlBytes) + for k, v := range values { + newText = strings.ReplaceAll(newText, k, v) + } return g.RolloutObjects(newText) } diff --git a/test/fixtures/then.go b/test/fixtures/then.go index 8183cc2adc..d39e054d34 100644 --- a/test/fixtures/then.go +++ b/test/fixtures/then.go @@ -494,6 +494,15 @@ func (t *Then) ExpectRolloutEvents(reasons []string) *Then { return t } +func (t *Then) ExpectRolloutEventsContains(reasons []string) *Then { + t.t.Helper() + eventReasons := t.GetRolloutEventReasons() + for _, r := range reasons { + assert.Contains(t.Common.t, eventReasons, r) + } + return t +} + func (t *Then) When() *When { return &When{ Common: t.Common, diff --git a/test/fixtures/when.go b/test/fixtures/when.go index d9696a7761..a5806befb9 100644 --- a/test/fixtures/when.go +++ b/test/fixtures/when.go @@ -415,6 +415,18 @@ func (w *When) WaitForActiveRevision(revision string, timeout ...time.Duration) return w.WaitForRolloutCondition(checkStatus, fmt.Sprintf("active revision=%s", revision), timeout...) } +func (w *When) WaitForRolloutStepPluginRunning(timeout ...time.Duration) *When { + checkStatus := func(ro *rov1.Rollout) bool { + for _, s := range ro.Status.Canary.StepPluginStatuses { + if s.Index == *ro.Status.CurrentStepIndex && s.Operation == rov1.StepPluginOperationRun && s.Phase == v1alpha1.StepPluginPhaseRunning { + return true + } + } + return false + } + return w.WaitForRolloutCondition(checkStatus, fmt.Sprintf("stepPluginStatus[currentIndex].phase=Running"), timeout...) +} + func (w *When) WaitForRolloutCondition(test func(ro *rov1.Rollout) bool, condition string, timeouts ...time.Duration) *When { start := time.Now() w.log.Infof("Waiting for condition: %s", condition) diff --git a/ui/src/app/components/rollout/rollout.scss b/ui/src/app/components/rollout/rollout.scss index 6ee3b71099..b93f9ed8ff 100644 --- a/ui/src/app/components/rollout/rollout.scss +++ b/ui/src/app/components/rollout/rollout.scss @@ -97,6 +97,10 @@ padding-bottom: 4px; font-size: 14px; } + + &-value { + white-space: pre-wrap; + } } } diff --git a/ui/src/app/components/rollout/rollout.tsx b/ui/src/app/components/rollout/rollout.tsx index dfecde7e84..f0cf0037bf 100644 --- a/ui/src/app/components/rollout/rollout.tsx +++ b/ui/src/app/components/rollout/rollout.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import * as YAML from 'yaml'; import {Helmet} from 'react-helmet'; import {useParams} from 'react-router-dom'; import { @@ -6,6 +7,7 @@ import { GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1HeaderRoutingMatch, GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutExperimentTemplate, GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute, + GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep, RolloutReplicaSetInfo, RolloutRolloutInfo, RolloutServiceApi, @@ -316,6 +318,7 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1 const [openAnalysis, setOpenAnalysis] = React.useState(false); const [openHeader, setOpenHeader] = React.useState(false); const [openMirror, setOpenMirror] = React.useState(false); + const [openPlugin, setOpenPlugin] = React.useState(false); let icon: string; let content = ''; @@ -359,6 +362,11 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1 } } + if (props.step.plugin) { + content = `${props.step.plugin.name}`; + icon = 'fa-puzzle-piece'; + } + return (
@@ -368,7 +376,8 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1 (props.step.setCanaryScale && openCanary) || (props.step.analysis && openAnalysis) || (props.step.setHeaderRoute && openHeader) || - (props.step.setMirrorRoute && openMirror) + (props.step.setMirrorRoute && openMirror) || + (props.step.plugin && openPlugin) ? 'steps__step-title--experiment' : '' }`} @@ -395,6 +404,11 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1
)} + {props.step.plugin && props.step.plugin.config && ( +
setOpenPlugin(!openPlugin)}> + +
+ )} {props.step.experiment?.templates && (
@@ -419,6 +433,7 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1
)} {props.step?.setCanaryScale && openCanary && } + {props.step?.plugin && openPlugin && } {props.step?.setHeaderRoute && openHeader && } {props.step?.setMirrorRoute && openMirror && } @@ -563,3 +578,13 @@ const WidgetItemSetHeader = ({values}: {values: GithubComArgoprojArgoRolloutsPkg ); }; + +const WidgetItemPlugin = ({values}: {values: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep}) => { + if (!values.config) return null; + return ( +
+
CONFIG
+
{YAML.stringify(values.config)}
+
+ ); +}; diff --git a/ui/src/models/rollout/generated/api.ts b/ui/src/models/rollout/generated/api.ts index a2cd53858e..9159d1afd3 100755 --- a/ui/src/models/rollout/generated/api.ts +++ b/ui/src/models/rollout/generated/api.ts @@ -711,6 +711,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStatu * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStatus */ stablePingPong?: string; + /** + * + * @type {Array} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStatus + */ + stepPluginStatuses?: Array; } /** * CanaryStep defines a step of a canary deployment. @@ -760,6 +766,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep */ setMirrorRoute?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute; + /** + * + * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep + */ + plugin?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep; } /** * @@ -1788,6 +1800,25 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PingPongSpe */ pongService?: string; } +/** + * + * @export + * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep + */ +export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep { + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep + */ + name?: string; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1PluginStep + */ + config?: string; +} /** * * @export @@ -2795,6 +2826,85 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SkyWalkingM */ interval?: string; } +/** + * + * @export + * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ +export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus { + /** + * + * @type {number} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + index?: number; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + name?: string; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + operation?: string; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + phase?: string; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + message?: string; + /** + * + * @type {K8sIoApimachineryPkgApisMetaV1Time} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + startedAt?: K8sIoApimachineryPkgApisMetaV1Time; + /** + * + * @type {K8sIoApimachineryPkgApisMetaV1Time} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + updatedAt?: K8sIoApimachineryPkgApisMetaV1Time; + /** + * + * @type {K8sIoApimachineryPkgApisMetaV1Time} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + finishedAt?: K8sIoApimachineryPkgApisMetaV1Time; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + backoff?: string; + /** + * + * @type {number} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + executions?: number; + /** + * + * @type {boolean} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + disabled?: boolean; + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StepPluginStatus + */ + status?: string; +} /** * * @export diff --git a/utils/conditions/conditions.go b/utils/conditions/conditions.go index 77cd401c63..2e62cb80fe 100644 --- a/utils/conditions/conditions.go +++ b/utils/conditions/conditions.go @@ -133,6 +133,11 @@ const ( // RolloutExperimentFailedMessage is added in a rollout when the experiment owned by a rollout fails to show any progress RolloutExperimentFailedMessage = "Experiment '%s' owned by the Rollout '%q' has timed out." + // RolloutReconciliationErrorReason is added in a rollout when the reconciliation returns an error preventing progress + RolloutReconciliationErrorReason = "ReconciliationError" + // RolloutReconciliationErrorMessage is added in a rollout when the reconciliation returns an error preventing progress + RolloutReconciliationErrorMessage = "Reconciliation failed with error: %v" + // TimedOutReason is added in a rollout when its newest replica set fails to show any progress // within the given deadline (progressDeadlineSeconds). TimedOutReason = "ProgressDeadlineExceeded" @@ -157,6 +162,12 @@ const ( // ServiceReferencingManagedService is added in a rollout when the multiple rollouts reference a Rollout ServiceReferencingManagedService = "Service %q is managed by another Rollout" + // StepPluginTransitionReason is added to a Rollout when a step plugin transition to an unsuccessful phase + StepPluginTransitionReason = "StepPluginTransition" + StepPluginTransitionRunMessage = "Step plugin %s (step %d) transitioned to %s" + StepPluginTransitionAbortMessage = "Step plugin %s (step %d) aborted (%s)" + StepPluginTransitionTerminateMessage = "Step plugin %s (step %d) terminated (%s)" + // TargetGroupHealthyReason is emitted when target group has been verified TargetGroupVerifiedReason = "TargetGroupVerified" TargetGroupVerifiedRegistrationMessage = "Service %s (TargetGroup %s) verified: %d endpoints registered" diff --git a/utils/config/config.go b/utils/config/config.go index 7989374e8e..3eb9a1f86f 100644 --- a/utils/config/config.go +++ b/utils/config/config.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "regexp" + "slices" "sync" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,10 +21,11 @@ import ( type Config struct { configMap *v1.ConfigMap plugins []types.PluginItem + lock *sync.RWMutex } var configMemoryCache *Config -var mutex sync.RWMutex +var mutex = &sync.RWMutex{} // Regex to match plugin names, this matches github username and repo limits var re = regexp.MustCompile(`^([a-zA-Z0-9\-]+)\/{1}([a-zA-Z0-9_\-.]+)$`) @@ -34,7 +36,9 @@ func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) ( configMapCluster, err := k8sClientset.CoreV1().ConfigMaps(defaults.Namespace()).Get(context.Background(), configMapName, metav1.GetOptions{}) if err != nil { if k8errors.IsNotFound(err) { - configMemoryCache = &Config{} // We create an empty config so that we don't try to initialize again + configMemoryCache = &Config{ + lock: &sync.RWMutex{}, + } // We create an empty config so that we don't try to initialize again // If the configmap is not found, we return return configMemoryCache, nil } @@ -45,16 +49,31 @@ func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) ( if err = yaml.Unmarshal([]byte(configMapCluster.Data["trafficRouterPlugins"]), &trafficRouterPlugins); err != nil { return nil, fmt.Errorf("failed to unmarshal traffic router plugins while initializing: %w", err) } + for i := range trafficRouterPlugins { + trafficRouterPlugins[i].Type = types.PluginTypeTrafficRouter + } var metricProviderPlugins []types.PluginItem if err = yaml.Unmarshal([]byte(configMapCluster.Data["metricProviderPlugins"]), &metricProviderPlugins); err != nil { return nil, fmt.Errorf("failed to unmarshal metric provider plugins while initializing: %w", err) } + for i := range metricProviderPlugins { + metricProviderPlugins[i].Type = types.PluginTypeMetricProvider + } + + var stepPlugins []types.PluginItem + if err = yaml.Unmarshal([]byte(configMapCluster.Data["stepPlugins"]), &stepPlugins); err != nil { + return nil, fmt.Errorf("failed to unmarshal step plugins while initializing: %w", err) + } + for i := range stepPlugins { + stepPlugins[i].Type = types.PluginTypeStep + } mutex.Lock() configMemoryCache = &Config{ configMap: configMapCluster, - plugins: append(trafficRouterPlugins, metricProviderPlugins...), + plugins: slices.Concat(trafficRouterPlugins, metricProviderPlugins, stepPlugins), + lock: &sync.RWMutex{}, } mutex.Unlock() @@ -70,6 +89,7 @@ func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) ( func GetConfig() (*Config, error) { mutex.RLock() defer mutex.RUnlock() + if configMemoryCache == nil { return nil, fmt.Errorf("config not initialized, please initialize before use") } @@ -85,12 +105,30 @@ func UnInitializeConfig() { // GetAllPlugins returns a flattened list of plugin items. This is useful for iterating over all plugins. func (c *Config) GetAllPlugins() []types.PluginItem { - mutex.RLock() - defer mutex.RUnlock() + c.lock.RLock() + defer c.lock.RUnlock() + // Return a copy of the slice + return append([]types.PluginItem{}, c.plugins...) +} - var copiedPlugins []types.PluginItem - copiedPlugins = append(copiedPlugins, configMemoryCache.plugins...) - return copiedPlugins +// GetPlugin returns the plugin item by name and type if it exists +func (c *Config) GetPlugin(name string, pluginType types.PluginType) *types.PluginItem { + for _, plugin := range c.GetAllPlugins() { + if plugin.Name == name && plugin.Type == pluginType { + return &plugin + } + } + return nil +} + +func (c *Config) ValidateConfig() error { + for _, pluginItem := range c.GetAllPlugins() { + matches := re.FindAllStringSubmatch(pluginItem.Name, -1) + if len(matches) != 1 || len(matches[0]) != 3 { + return fmt.Errorf("plugin repository (%s) must be in the format of /", pluginItem.Name) + } + } + return nil } // GetPluginDirectoryAndFilename this functions return the directory and file name from a given pluginName such as @@ -105,16 +143,3 @@ func GetPluginDirectoryAndFilename(pluginName string) (directory string, filenam return namespace, plugin, nil } - -func (c *Config) ValidateConfig() error { - mutex.RLock() - defer mutex.RUnlock() - - for _, pluginItem := range c.GetAllPlugins() { - matches := re.FindAllStringSubmatch(pluginItem.Name, -1) - if len(matches) != 1 || len(matches[0]) != 3 { - return fmt.Errorf("plugin repository (%s) must be in the format of /", pluginItem.Name) - } - } - return nil -} diff --git a/utils/plugin/downloader_test.go b/utils/plugin/downloader_test.go index ab14be3385..f01e1d0e5f 100644 --- a/utils/plugin/downloader_test.go +++ b/utils/plugin/downloader_test.go @@ -107,7 +107,7 @@ func TestPlugin(t *testing.T) { cm, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName) assert.NoError(t, err) - assert.Equal(t, cm, &config.Config{}) + assert.Equal(t, 0, len(cm.GetAllPlugins())) err = DownloadPlugins(MockFileDownloader{}) assert.NoError(t, err) diff --git a/utils/plugin/plugin.go b/utils/plugin/plugin.go index 3fd7a013a0..a76ed5e942 100644 --- a/utils/plugin/plugin.go +++ b/utils/plugin/plugin.go @@ -5,30 +5,32 @@ import ( "path/filepath" "github.com/argoproj/argo-rollouts/utils/defaults" + "github.com/argoproj/argo-rollouts/utils/plugin/types" "github.com/argoproj/argo-rollouts/utils/config" ) // GetPluginInfo returns the location & command arguments of the plugin on the filesystem via plugin name. If the plugin is not // configured in the configmap, an error is returned. -func GetPluginInfo(pluginName string) (string, []string, error) { +func GetPluginInfo(pluginName string, pluginType types.PluginType) (string, []string, error) { configMap, err := config.GetConfig() if err != nil { return "", nil, fmt.Errorf("failed to get config: %w", err) } - for _, item := range configMap.GetAllPlugins() { - if pluginName == item.Name { - dir, filename, err := config.GetPluginDirectoryAndFilename(item.Name) - if err != nil { - return "", nil, err - } - absFilePath, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) - if err != nil { - return "", nil, fmt.Errorf("failed to get absolute path of plugin folder: %w", err) - } - return absFilePath, item.Args, nil - } + plugin := configMap.GetPlugin(pluginName, pluginType) + if plugin == nil { + return "", nil, fmt.Errorf("plugin %s not configured in configmap", pluginName) } - return "", nil, fmt.Errorf("plugin %s not configured in configmap", pluginName) + + dir, filename, err := config.GetPluginDirectoryAndFilename(plugin.Name) + if err != nil { + return "", nil, err + } + absFilePath, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) + if err != nil { + return "", nil, fmt.Errorf("failed to get absolute path of plugin folder: %w", err) + } + return absFilePath, plugin.Args, nil + } diff --git a/utils/plugin/plugin_test.go b/utils/plugin/plugin_test.go index 7226d2d312..0e5d994a6c 100644 --- a/utils/plugin/plugin_test.go +++ b/utils/plugin/plugin_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/argoproj/argo-rollouts/utils/defaults" + "github.com/argoproj/argo-rollouts/utils/plugin/types" "github.com/argoproj/argo-rollouts/utils/config" "github.com/stretchr/testify/assert" @@ -29,14 +30,14 @@ func TestGetPluginInfo(t *testing.T) { _, err := config.InitializeConfig(client, "argo-rollouts-config") assert.NoError(t, err) - location, args, err := GetPluginInfo("argoproj-labs/http") + location, args, err := GetPluginInfo("argoproj-labs/http", types.PluginTypeMetricProvider) assert.NoError(t, err) fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/http")) assert.NoError(t, err) assert.Equal(t, fp, location) assert.Equal(t, args, cmdArgs) - _, args, _ = GetPluginInfo("argoproj-labs/http-sha") + _, args, _ = GetPluginInfo("argoproj-labs/http-sha", types.PluginTypeMetricProvider) assert.Equal(t, len(args), 0) }) @@ -54,14 +55,39 @@ func TestGetPluginInfo(t *testing.T) { _, err := config.InitializeConfig(client, "argo-rollouts-config") assert.NoError(t, err) - location, args, err := GetPluginInfo("argoproj-labs/router") + location, args, err := GetPluginInfo("argoproj-labs/router", types.PluginTypeTrafficRouter) assert.NoError(t, err) fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/router")) assert.NoError(t, err) assert.Equal(t, fp, location) assert.Equal(t, args, cmdArgs) - _, args, _ = GetPluginInfo("argoproj-labs/router-sha") + _, args, _ = GetPluginInfo("argoproj-labs/router-sha", types.PluginTypeTrafficRouter) + assert.Equal(t, len(args), 0) + }) + + t.Run("tests getting plugin location of step plugins", func(t *testing.T) { + + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"stepPlugins": "\n - name: argoproj-labs/steps\n location: https://test/plugin\n args: [\"-l 2\"]\n - name: argoproj-labs/steps-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + location, args, err := GetPluginInfo("argoproj-labs/steps", types.PluginTypeStep) + assert.NoError(t, err) + fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/steps")) + assert.NoError(t, err) + assert.Equal(t, fp, location) + assert.Equal(t, args, cmdArgs) + + _, args, _ = GetPluginInfo("argoproj-labs/step-sha", types.PluginTypeStep) assert.Equal(t, len(args), 0) }) @@ -78,10 +104,30 @@ func TestGetPluginInfo(t *testing.T) { _, err := config.InitializeConfig(client, "argo-rollouts-config") assert.NoError(t, err) - location, args, err := GetPluginInfo("does-not-exist") + location, args, err := GetPluginInfo("does-not-exist", types.PluginTypeMetricProvider) assert.Error(t, err) assert.Equal(t, "plugin does-not-exist not configured in configmap", err.Error()) assert.Equal(t, "", location) assert.Equal(t, len(args), 0) }) + + t.Run("test getting plugin location from a plugin of a different type", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: https://test/plugin\n - name: argoproj-labs/http-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + location, args, err := GetPluginInfo("argoproj-labs/http", types.PluginTypeStep) + assert.Error(t, err) + assert.Equal(t, "plugin argoproj-labs/http not configured in configmap", err.Error()) + assert.Equal(t, "", location) + assert.Equal(t, len(args), 0) + }) } diff --git a/utils/plugin/types/steps.go b/utils/plugin/types/steps.go new file mode 100644 index 0000000000..f54d7ec00b --- /dev/null +++ b/utils/plugin/types/steps.go @@ -0,0 +1,55 @@ +package types + +import ( + "encoding/json" + "fmt" + "time" +) + +// StepPhase is the type of phase of step plugin result +type StepPhase string + +const ( + // PhaseRunning is the Running phase of a step plugin + PhaseRunning StepPhase = "Running" + // PhaseRunning is the Successful phase of a step plugin + PhaseSuccessful StepPhase = "Successful" + // PhaseRunning is the Failed phase of a step plugin + PhaseFailed StepPhase = "Failed" + // PhaseRunning is the Error phase of a step plugin + PhaseError StepPhase = "Error" +) + +// RpcStepContext is the context of the step plugin operation +type RpcStepContext struct { + // PluginName is the name of the plugin as defined by the user + PluginName string + // Config holds the user specified configuration in the Rollout object for this plugin step + Config json.RawMessage + // Status holds a previous execution status related to the operation + Status json.RawMessage +} + +type RpcStepResult struct { + // Phase of the operation to idicate if it has completed or not + Phase StepPhase + // Message contains information about the execution + Message string + // RequeueAfter is the duration to wait before executing the operation again when it does not return a completed phase + RequeueAfter time.Duration + // Status hold the execution status of this plugin step. It can be used to persist a state between executions + Status json.RawMessage +} + +// Validate the phase of a step plugin +func (p StepPhase) Validate() error { + switch p { + case PhaseRunning: + case PhaseSuccessful: + case PhaseFailed: + case PhaseError: + default: + return fmt.Errorf("phase '%s' is not valid", p) + } + return nil +} diff --git a/utils/plugin/types/types.go b/utils/plugin/types/types.go index e89ba40f2a..11cd8be5e4 100644 --- a/utils/plugin/types/types.go +++ b/utils/plugin/types/types.go @@ -85,24 +85,55 @@ type RpcTrafficRoutingReconciler interface { Type() string } -//type Plugin struct { -// MetricProviders []PluginItem `json:"metricProviders" yaml:"metricProviders"` -// TrafficRouters []PluginItem `json:"trafficRouters" yaml:"trafficRouters"` -//} +type RpcStep interface { + // Run executes a step plugin for the RpcStepContext and returns the result to the controller or an RpcError for unexpeted failures + Run(*v1alpha1.Rollout, *RpcStepContext) (RpcStepResult, RpcError) + // Terminate stops an uncompleted operation started by the Run operation + Terminate(*v1alpha1.Rollout, *RpcStepContext) (RpcStepResult, RpcError) + // Abort reverts the actions performed during the Run operation if necessary + Abort(*v1alpha1.Rollout, *RpcStepContext) (RpcStepResult, RpcError) + // Type returns the type of the step plugin + Type() string +} type TrafficRouterPlugins struct { + // TrafficRouters is the list of plugin that implements a RpcTrafficRoutingReconciler TrafficRouters []PluginItem `json:"trafficRouterPlugins" yaml:"trafficRouterPlugins"` } type MetricProviderPlugins struct { + // MetricProviders is the list of plugin that implements a RpcMetricProvider MetricProviders []PluginItem `json:"metricProviderPlugins" yaml:"metricProviderPlugins"` } +type StepPlugins struct { + // Steps is the list of plugin that implements a RpcStep + Steps []PluginItem `json:"stepPlugins" yaml:"stepPlugins"` +} + +// PluginType is a type of plugin +type PluginType string + +const ( + // PluginTypeMetricProvider is the type for a MetricProvider plugin + PluginTypeMetricProvider PluginType = "MetricProvider" + // PluginTypeTrafficRouter is the type for a TrafficRouter plugin + PluginTypeTrafficRouter PluginType = "TrafficRouter" + // PluginTypeStep is the type for a Step plugin + PluginTypeStep PluginType = "Step" +) + type PluginItem struct { - Name string `json:"name" yaml:"name"` + // Name of the plugin to use in the Rollout custom resources + Name string `json:"name" yaml:"name"` + // Location of the plugin. Supports http(s):// urls and file:// prefix Location string `json:"location" yaml:"location"` - Sha256 string `json:"sha256" yaml:"sha256"` - - // Args holds command line arguments + // Sha256 is the checksum of the file specified at the provided Location + Sha256 string `json:"sha256" yaml:"sha256"` + // Type of the plugin + Type PluginType + // Disabled indicates if the plugin should be ignored when referenced in Rollout custom resources. Only valid for a plugin of type Step. + Disabled bool `json:"disabled" yaml:"disabled"` + // Args holds command line arguments to initialize the plugin Args []string `json:"args" yaml:"args"` } diff --git a/utils/rollout/rolloututil.go b/utils/rollout/rolloututil.go index 5411f3f58c..35a3bca9c3 100644 --- a/utils/rollout/rolloututil.go +++ b/utils/rollout/rolloututil.go @@ -181,6 +181,9 @@ func CanaryStepString(c v1alpha1.CanaryStep) string { return fmt.Sprintf("setCanaryScale{replicas: %d}", *c.SetCanaryScale.Replicas) } } + if c.Plugin != nil { + return fmt.Sprintf("plugin: %s", c.Plugin.Name) + } return "invalid" } diff --git a/utils/rollout/rolloututil_test.go b/utils/rollout/rolloututil_test.go index d88f080f64..f85a53faf6 100644 --- a/utils/rollout/rolloututil_test.go +++ b/utils/rollout/rolloututil_test.go @@ -389,6 +389,10 @@ func TestCanaryStepString(t *testing.T) { step: v1alpha1.CanaryStep{SetCanaryScale: &v1alpha1.SetCanaryScale{Replicas: pointer.Int32Ptr(5)}}, expectedString: "setCanaryScale{replicas: 5}", }, + { + step: v1alpha1.CanaryStep{Plugin: &v1alpha1.PluginStep{Name: "foo"}}, + expectedString: "plugin: foo", + }, } for _, test := range tests { assert.Equal(t, test.expectedString, CanaryStepString(test.step))