diff --git a/.gitignore b/.gitignore index db184381d6..1a22b8eef6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ junit.xml rerunreport.txt site/ vendor/ +plugin-bin/ # static server/static/* !server/static/.gitkeep diff --git a/Dockerfile b/Dockerfile index c2809aba6b..0fff9cf749 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,7 +84,7 @@ CMD ["dashboard"] #################################################################################################### # Final image #################################################################################################### -FROM scratch +FROM gcr.io/distroless/static-debian11 COPY --from=argo-rollouts-build /go/src/github.com/argoproj/argo-rollouts/dist/rollouts-controller /bin/ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ diff --git a/Dockerfile.dev b/Dockerfile.dev index 2da9591a0e..93d3320ece 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -FROM scratch +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 diff --git a/Makefile b/Makefile index 6442844ac9..fda72dde50 100644 --- a/Makefile +++ b/Makefile @@ -209,7 +209,7 @@ lint: go-mod-vendor .PHONY: test test: test-kustomize - go test -covermode=count -coverprofile=coverage.out ${TEST_TARGET} + @make test-unit .PHONY: test-kustomize test-kustomize: @@ -225,7 +225,7 @@ test-e2e: install-devtools-local .PHONY: test-unit test-unit: install-devtools-local - ${DIST_DIR}/gotestsum --junitfile=junit.xml --format=testname --packages="./..." -- -covermode=count -coverprofile=coverage.out ./... + ${DIST_DIR}/gotestsum --junitfile=junit.xml --format=testname -- -covermode=count -coverprofile=coverage.out `go list ./... | grep -v ./test/cmd/sample-metrics-plugin` .PHONY: coverage @@ -279,3 +279,10 @@ trivy: .PHONY: checksums checksums: shasum -a 256 ./dist/kubectl-argo-rollouts-* | awk -F './dist/' '{print $$1 $$2}' > ./dist/argo-rollouts-checksums.txt + +# Build sample plugin with debug info +# 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: + go build -gcflags="all=-N -l" -o metric-plugin test/cmd/sample-metrics-plugin/main.go + diff --git a/analysis/analysis.go b/analysis/analysis.go index e5c6fc907d..4e98556794 100644 --- a/analysis/analysis.go +++ b/analysis/analysis.go @@ -327,7 +327,7 @@ func (c *Controller) runMeasurements(run *v1alpha1.AnalysisRun, tasks []metricTa provider, err := c.newProvider(*logger, t.metric) if err != nil { - log.Errorf("Error in getting provider :%v", err) + log.Errorf("Error in getting metric provider :%v", err) return err } if metricResult == nil { diff --git a/analysis/controller.go b/analysis/controller.go index f05c955bb1..505e6f005c 100644 --- a/analysis/controller.go +++ b/analysis/controller.go @@ -5,6 +5,8 @@ import ( "sync" "time" + "github.com/argoproj/argo-rollouts/metric" + unstructuredutil "github.com/argoproj/argo-rollouts/utils/unstructured" log "github.com/sirupsen/logrus" @@ -44,7 +46,7 @@ type Controller struct { metricsServer *metrics.MetricsServer - newProvider func(logCtx log.Entry, metric v1alpha1.Metric) (metricproviders.Provider, error) + newProvider func(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error) // used for unit testing enqueueAnalysis func(obj interface{}) diff --git a/analysis/controller_test.go b/analysis/controller_test.go index a0af638c9e..8d5efe11f9 100644 --- a/analysis/controller_test.go +++ b/analysis/controller_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/argoproj/argo-rollouts/metric" + timeutil "github.com/argoproj/argo-rollouts/utils/time" "github.com/argoproj/argo-rollouts/utils/queue" @@ -25,7 +27,6 @@ import ( "k8s.io/client-go/util/workqueue" "github.com/argoproj/argo-rollouts/controller/metrics" - "github.com/argoproj/argo-rollouts/metricproviders" "github.com/argoproj/argo-rollouts/metricproviders/mocks" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake" @@ -129,7 +130,7 @@ func (f *fixture) newController(resync resyncFunc) (*Controller, informers.Share c.enqueueAnalysis(obj) } f.provider = &mocks.Provider{} - c.newProvider = func(logCtx log.Entry, metric v1alpha1.Metric) (metricproviders.Provider, error) { + c.newProvider = func(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error) { return f.provider, nil } diff --git a/cmd/rollouts-controller/main.go b/cmd/rollouts-controller/main.go index 4aadf9ad82..671cc8681d 100644 --- a/cmd/rollouts-controller/main.go +++ b/cmd/rollouts-controller/main.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/argoproj/pkg/kubeclientmetrics" smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -20,8 +21,6 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" "k8s.io/client-go/tools/clientcmd" - "github.com/argoproj/pkg/kubeclientmetrics" - "github.com/argoproj/argo-rollouts/controller" "github.com/argoproj/argo-rollouts/controller/metrics" jobprovider "github.com/argoproj/argo-rollouts/metricproviders/job" diff --git a/controller/controller.go b/controller/controller.go index 55ef4a45f8..5e5fb3fb2e 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -9,9 +9,15 @@ import ( "sync" "time" - "k8s.io/apimachinery/pkg/util/wait" + "github.com/argoproj/argo-rollouts/utils/plugin" istioutil "github.com/argoproj/argo-rollouts/utils/istio" + + rolloutsConfig "github.com/argoproj/argo-rollouts/utils/config" + goPlugin "github.com/hashicorp/go-plugin" + + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic/dynamicinformer" kubeinformers "k8s.io/client-go/informers" @@ -352,6 +358,16 @@ func NewManager( istioPrimaryDynamicClient: istioPrimaryDynamicClient, } + _, err := rolloutsConfig.InitializeConfig(kubeclientset, defaults.DefaultRolloutsConfigMapName) + if err != nil { + log.Fatalf("Failed to init config: %v", err) + } + + err = plugin.DownloadPlugins(plugin.FileDownloaderImpl{}) + if err != nil { + log.Fatalf("Failed to download plugins: %v", err) + } + return cm } @@ -423,6 +439,7 @@ func (c *Manager) Run(ctx context.Context, rolloutThreadiness, serviceThreadines }) } log.Info("Shutting down workers") + goPlugin.CleanupClients() c.serviceWorkqueue.ShutDownWithDrain() c.ingressWorkqueue.ShutDownWithDrain() diff --git a/docs/analysis/plugins.md b/docs/analysis/plugins.md new file mode 100644 index 0000000000..74fb25452d --- /dev/null +++ b/docs/analysis/plugins.md @@ -0,0 +1,78 @@ +# Metric Plugins + +!!! important Available since v1.5 + +Argo Rollouts supports getting analysis metrics via 3rd party plugin system. This allows users to extend the capabilities of Rollouts +to support metric providers that are not natively supported. Rollout's uses a plugin library called +[go-plugin](https://github.com/hashicorp/go-plugin) to do this. You can find a sample plugin +here: [sample-rollouts-metric-plugin](https://github.com/argoproj-labs/sample-rollouts-metric-plugin) + +## Using a Metric Plugin + +There are two methods of installing and using an argo rollouts plugin. The first method is to mount up the plugin executable +into the rollouts controller container. The second method is to use a HTTP(S) server to host the plugin executable. + +### Mounting the plugin executable into the rollouts controller container + +To use this method, you will need to build or download the plugin executable and then mount it into the rollouts controller container. +The plugin executable must be mounted into the rollouts controller container at the path specified by the `--metric-plugin-location` flag. + +There are a few ways to mount the plugin executable into the rollouts controller container. Some of these will depend on your +particular infrastructure. Here are a few methods: + +* Using an init container to download the plugin executable +* Using a Kubernetes volume mount with a shared volume such as NFS, EBS, etc. +* Building the plugin into the rollouts controller container + +Then you can use the configmap to point to the plugin executable. Example: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: argo-rollouts-config +data: + plugins: |- + metrics: + - name: "prometheus" # name the plugin uses to find this configuration, it must match the name required by the plugin + pluginLocation: "file://./my-custom-plugin" # supports http(s):// urls and file:// +``` + +### Using a HTTP(S) server to host the plugin executable + +Argo Rollouts supports downloading the plugin executable from a HTTP(S) server. To use this method, you will need to +configure the controller via the `argo-rollouts-config` configmap and set `pluginLocation` to a http(s) url. Example: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: argo-rollouts-config +data: + plugins: |- + metrics: + - name: "prometheus" # name the plugin uses to find this configuration, it must match the name required by the plugin + pluginLocation: "https://github.com/argoproj-labs/sample-rollouts-metric-plugin/releases/download/v0.0.3/metric-plugin-linux-amd64" # supports http(s):// urls and file:// + pluginSha256: "08f588b1c799a37bbe8d0fc74cc1b1492dd70b2c" #optional sha256 checksum of the plugin executable +``` + +## Some words of caution + +Depending on which method you use to install and the plugin, there are some things to be aware of. +The rollouts controller will not start if it can not download or find the plugin executable. This means that if you are using +a method of installation that requires a download of the plugin and the server hosting the plugin for some reason is not available and the rollouts +controllers pod got deleted while the server was down or is coming up for the first time, it will not be able to start until +the server hosting the plugin is available again. + +Argo Rollouts will download the plugin at startup only once but if the pod is deleted it will need to download the plugin again on next startup. Running +Argo Rollouts in HA mode can help a little with this situation because each pod will download the plugin at startup. So if a single pod gets +deleted during a server outage, the other pods will still be able to take over because there will already be a plugin executable available to it. However, +it is up to you to define your risk for and decide how you want to install the plugin executable. + +## List of Available Plugins (alphabetical order) + +#### Add Your Plugin Here + * If you have created a plugin, please submit a PR to add it to this list. +#### [sample-rollouts-metric-plugin](https://github.com/argoproj-labs/sample-rollouts-metric-plugin) + * This is just a sample plugin that can be used as a starting point for creating your own plugin. +It is not meant to be used in production. It is based on the built-in prometheus provider. diff --git a/docs/features/kustomize/rollout_cr_schema.json b/docs/features/kustomize/rollout_cr_schema.json index fbff809be6..cf31606c7b 100644 --- a/docs/features/kustomize/rollout_cr_schema.json +++ b/docs/features/kustomize/rollout_cr_schema.json @@ -4165,6 +4165,10 @@ ], "type": "object" }, + "plugin": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, "prometheus": { "properties": { "address": { @@ -8433,6 +8437,10 @@ ], "type": "object" }, + "plugin": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, "prometheus": { "properties": { "address": { @@ -12701,6 +12709,10 @@ ], "type": "object" }, + "plugin": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, "prometheus": { "properties": { "address": { diff --git a/go.mod b/go.mod index 94b40f9c28..5ad397f8fb 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/hashicorp/go-plugin v1.4.8 github.com/influxdata/influxdb-client-go/v2 v2.12.2 github.com/juju/ansiterm v1.0.0 github.com/mitchellh/mapstructure v1.5.0 @@ -92,6 +93,7 @@ require ( github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/fatih/color v1.7.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.2.3 // indirect @@ -114,7 +116,9 @@ require ( github.com/gregdel/pushover v1.1.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v0.14.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect @@ -130,6 +134,7 @@ require ( github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/spdystream v0.2.0 // indirect @@ -139,6 +144,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/oklog/run v1.0.0 // indirect github.com/onsi/ginkgo v1.16.4 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.13 // indirect diff --git a/go.sum b/go.sum index 329beafadd..543383674e 100644 --- a/go.sum +++ b/go.sum @@ -291,6 +291,7 @@ github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htX github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= @@ -518,11 +519,14 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.4.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM= +github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= @@ -539,6 +543,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/heketi/heketi v10.3.0+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= @@ -560,6 +566,7 @@ github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1 github.com/ishidawataru/sctp v0.0.0-20190723014705-7c296d48a2b5/go.mod h1:DM4VvS+hD/kDi1U1QsX2fnZowwBhqD0Dk3bRPKF/Oc8= github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -644,12 +651,14 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -670,6 +679,7 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -718,6 +728,8 @@ github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rR github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= @@ -1157,6 +1169,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/hack/update-mocks.sh b/hack/update-mocks.sh index 453d98d7f9..6f1bcfad4a 100755 --- a/hack/update-mocks.sh +++ b/hack/update-mocks.sh @@ -8,7 +8,7 @@ set -o pipefail PROJECT_ROOT=$(cd $(dirname "$0")/.. ; pwd) mockery \ - --dir "${PROJECT_ROOT}"/metricproviders \ + --dir "${PROJECT_ROOT}"/metric \ --name Provider \ --output "${PROJECT_ROOT}"/metricproviders/mocks diff --git a/manifests/base/argo-rollouts-config.yaml b/manifests/base/argo-rollouts-config.yaml new file mode 100644 index 0000000000..4f679092b9 --- /dev/null +++ b/manifests/base/argo-rollouts-config.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: argo-rollouts-config + labels: + app.kubernetes.io/component: rollouts-controller + app.kubernetes.io/name: argo-rollouts + app.kubernetes.io/part-of: argo-rollouts diff --git a/manifests/base/kustomization.yaml b/manifests/base/kustomization.yaml index 663d7af691..fd6bb3e311 100644 --- a/manifests/base/kustomization.yaml +++ b/manifests/base/kustomization.yaml @@ -7,6 +7,7 @@ resources: - argo-rollouts-aggregate-roles.yaml - argo-rollouts-metrics-service.yaml - argo-rollouts-notification-secret.yaml +- argo-rollouts-config.yaml images: - name: quay.io/argoproj/argo-rollouts newTag: latest diff --git a/manifests/crds/analysis-run-crd.yaml b/manifests/crds/analysis-run-crd.yaml index da21888bde..a58eaaf3ab 100644 --- a/manifests/crds/analysis-run-crd.yaml +++ b/manifests/crds/analysis-run-crd.yaml @@ -2700,6 +2700,9 @@ spec: required: - query type: object + plugin: + type: object + x-kubernetes-preserve-unknown-fields: true prometheus: properties: address: diff --git a/manifests/crds/analysis-template-crd.yaml b/manifests/crds/analysis-template-crd.yaml index 1e37db2dd0..d9c91722b8 100644 --- a/manifests/crds/analysis-template-crd.yaml +++ b/manifests/crds/analysis-template-crd.yaml @@ -2696,6 +2696,9 @@ spec: required: - query type: object + plugin: + type: object + x-kubernetes-preserve-unknown-fields: true prometheus: properties: address: diff --git a/manifests/crds/cluster-analysis-template-crd.yaml b/manifests/crds/cluster-analysis-template-crd.yaml index fb149f2fa3..76bbf2d68b 100644 --- a/manifests/crds/cluster-analysis-template-crd.yaml +++ b/manifests/crds/cluster-analysis-template-crd.yaml @@ -2696,6 +2696,9 @@ spec: required: - query type: object + plugin: + type: object + x-kubernetes-preserve-unknown-fields: true prometheus: properties: address: diff --git a/manifests/install.yaml b/manifests/install.yaml index c90546d8b7..af6c126c67 100755 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -2701,6 +2701,9 @@ spec: required: - query type: object + plugin: + type: object + x-kubernetes-preserve-unknown-fields: true prometheus: properties: address: @@ -5578,6 +5581,9 @@ spec: required: - query type: object + plugin: + type: object + x-kubernetes-preserve-unknown-fields: true prometheus: properties: address: @@ -8341,6 +8347,9 @@ spec: required: - query type: object + plugin: + type: object + x-kubernetes-preserve-unknown-fields: true prometheus: properties: address: @@ -14821,6 +14830,15 @@ subjects: namespace: argo-rollouts --- 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 +--- +apiVersion: v1 kind: Secret metadata: name: argo-rollouts-notification-secret diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 7e883a0d85..1972b411b8 100755 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -332,6 +332,15 @@ subjects: name: argo-rollouts --- 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 +--- +apiVersion: v1 kind: Secret metadata: name: argo-rollouts-notification-secret diff --git a/metric/provider.go b/metric/provider.go new file mode 100644 index 0000000000..98da1acd30 --- /dev/null +++ b/metric/provider.go @@ -0,0 +1,21 @@ +package metric + +import "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + +// Provider methods to query an external systems and generate a measurement +type Provider interface { + // Run start a new external system call for a measurement + // Should be idempotent and do nothing if a call has already been started + Run(*v1alpha1.AnalysisRun, v1alpha1.Metric) v1alpha1.Measurement + // Resume Checks if the external system call is finished and returns the current measurement + Resume(*v1alpha1.AnalysisRun, v1alpha1.Metric, v1alpha1.Measurement) v1alpha1.Measurement + // Terminate will terminate an in-progress measurement + Terminate(*v1alpha1.AnalysisRun, v1alpha1.Metric, v1alpha1.Measurement) v1alpha1.Measurement + // GarbageCollect is used to garbage collect completed measurements to the specified limit + GarbageCollect(*v1alpha1.AnalysisRun, v1alpha1.Metric, int) error + // Type gets the provider type + Type() string + // GetMetadata returns any additional metadata which providers need to store/display as part + // of the metric result. For example, Prometheus uses is to store the final resolved queries. + GetMetadata(metric v1alpha1.Metric) map[string]string +} diff --git a/metricproviders/metricproviders.go b/metricproviders/metricproviders.go index d8a1637acf..7c6be31cb6 100644 --- a/metricproviders/metricproviders.go +++ b/metricproviders/metricproviders.go @@ -3,13 +3,15 @@ package metricproviders import ( "fmt" - "github.com/argoproj/argo-rollouts/metricproviders/influxdb" + "github.com/argoproj/argo-rollouts/metric" "github.com/argoproj/argo-rollouts/metricproviders/cloudwatch" "github.com/argoproj/argo-rollouts/metricproviders/datadog" "github.com/argoproj/argo-rollouts/metricproviders/graphite" + "github.com/argoproj/argo-rollouts/metricproviders/influxdb" "github.com/argoproj/argo-rollouts/metricproviders/kayenta" "github.com/argoproj/argo-rollouts/metricproviders/newrelic" + "github.com/argoproj/argo-rollouts/metricproviders/plugin" "github.com/argoproj/argo-rollouts/metricproviders/wavefront" "github.com/argoproj/argo-rollouts/metricproviders/webmetric" @@ -23,33 +25,15 @@ import ( "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" ) -// Provider methods to query a external systems and generate a measurement -type Provider interface { - // Run start a new external system call for a measurement - // Should be idempotent and do nothing if a call has already been started - Run(*v1alpha1.AnalysisRun, v1alpha1.Metric) v1alpha1.Measurement - // Checks if the external system call is finished and returns the current measurement - Resume(*v1alpha1.AnalysisRun, v1alpha1.Metric, v1alpha1.Measurement) v1alpha1.Measurement - // Terminate will terminate an in-progress measurement - Terminate(*v1alpha1.AnalysisRun, v1alpha1.Metric, v1alpha1.Measurement) v1alpha1.Measurement - // GarbageCollect is used to garbage collect completed measurements to the specified limit - GarbageCollect(*v1alpha1.AnalysisRun, v1alpha1.Metric, int) error - // Type gets the provider type - Type() string - // GetMetadata returns any additional metadata which providers need to store/display as part - // of the metric result. For example, Prometheus uses is to store the final resolved queries. - GetMetadata(metric v1alpha1.Metric) map[string]string -} - type ProviderFactory struct { KubeClient kubernetes.Interface JobLister batchlisters.JobLister } -type ProviderFactoryFunc func(logCtx log.Entry, metric v1alpha1.Metric) (Provider, error) +type ProviderFactoryFunc func(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error) // NewProvider creates the correct provider based on the provider type of the Metric -func (f *ProviderFactory) NewProvider(logCtx log.Entry, metric v1alpha1.Metric) (Provider, error) { +func (f *ProviderFactory) NewProvider(logCtx log.Entry, metric v1alpha1.Metric) (metric.Provider, error) { switch provider := Type(metric); provider { case prometheus.ProviderType: api, err := prometheus.NewPrometheusAPI(metric) @@ -101,6 +85,9 @@ func (f *ProviderFactory) NewProvider(logCtx log.Entry, metric v1alpha1.Metric) return nil, err } return influxdb.NewInfluxdbProvider(client, logCtx), nil + case plugin.ProviderType: + plugin, err := plugin.NewRpcPlugin(metric) + return plugin, err default: return nil, fmt.Errorf("no valid provider in metric '%s'", metric.Name) } @@ -127,6 +114,8 @@ func Type(metric v1alpha1.Metric) string { return graphite.ProviderType } else if metric.Provider.Influxdb != nil { return influxdb.ProviderType + } else if metric.Provider.Plugin != nil { + return plugin.ProviderType } return "Unknown Provider" diff --git a/metricproviders/mocks/Provider.go b/metricproviders/mocks/Provider.go index 1b54e4281c..6944f7b05c 100644 --- a/metricproviders/mocks/Provider.go +++ b/metricproviders/mocks/Provider.go @@ -26,13 +26,13 @@ func (_m *Provider) GarbageCollect(_a0 *v1alpha1.AnalysisRun, _a1 v1alpha1.Metri return r0 } -// GetMetadata provides a mock function with given fields: metric -func (_m *Provider) GetMetadata(metric v1alpha1.Metric) map[string]string { - ret := _m.Called(metric) +// GetMetadata provides a mock function with given fields: _a0 +func (_m *Provider) GetMetadata(_a0 v1alpha1.Metric) map[string]string { + ret := _m.Called(_a0) var r0 map[string]string if rf, ok := ret.Get(0).(func(v1alpha1.Metric) map[string]string); ok { - r0 = rf(metric) + r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(map[string]string) diff --git a/metricproviders/plugin/client/client.go b/metricproviders/plugin/client/client.go new file mode 100644 index 0000000000..3131a53578 --- /dev/null +++ b/metricproviders/plugin/client/client.go @@ -0,0 +1,91 @@ +package client + +import ( + "fmt" + "os/exec" + "sync" + + "github.com/argoproj/argo-rollouts/metricproviders/plugin/rpc" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/utils/plugin" + goPlugin "github.com/hashicorp/go-plugin" +) + +type metricPlugin struct { + pluginClient map[string]*goPlugin.Client + plugin map[string]rpc.MetricsPlugin +} + +var pluginClients *metricPlugin +var once sync.Once + +// GetMetricPlugin returns a singleton plugin client for the given metric plugin. Calling this multiple times +// returns the same plugin client instance for the plugin name defined in the metric. +func GetMetricPlugin(metric v1alpha1.Metric) (rpc.MetricsPlugin, error) { + once.Do(func() { + pluginClients = &metricPlugin{ + pluginClient: make(map[string]*goPlugin.Client), + plugin: make(map[string]rpc.MetricsPlugin), + } + }) + plugin, err := pluginClients.startPluginSystem(metric) + if err != nil { + return nil, fmt.Errorf("unable to start plugin system: %w", err) + } + return plugin, nil +} + +func (m *metricPlugin) startPluginSystem(metric v1alpha1.Metric) (rpc.MetricsPlugin, error) { + var handshakeConfig = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "metrics", + } + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcMetricsPlugin": &rpc.RpcMetricsPlugin{}, + } + + //There should only ever be one plugin defined in metric.Provider.Plugin + for pluginName := range metric.Provider.Plugin { + pluginPath, err := plugin.GetPluginLocation(pluginName) + if err != nil { + return nil, fmt.Errorf("unable to find plugin (%s): %w", pluginName, err) + } + + if m.pluginClient[pluginName] == nil || m.pluginClient[pluginName].Exited() { + m.pluginClient[pluginName] = goPlugin.NewClient(&goPlugin.ClientConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + Cmd: exec.Command(pluginPath), + Managed: true, + }) + + rpcClient, err := m.pluginClient[pluginName].Client() + if err != nil { + return nil, fmt.Errorf("unable to start plugin (%s): %w", pluginName, err) + } + + // Request the plugin + plugin, err := rpcClient.Dispense("RpcMetricsPlugin") + if err != nil { + return nil, fmt.Errorf("unable to dispense plugin (%s): %w", pluginName, err) + } + + pluginType, ok := plugin.(rpc.MetricsPlugin) + if !ok { + return nil, fmt.Errorf("unexpected type from plugin") + } + m.plugin[pluginName] = pluginType + + err = m.plugin[pluginName].NewMetricsPlugin(metric) + if err.Error() != "" { + return nil, fmt.Errorf("unable to initialize plugin via rpc (%s): %w", pluginName, err) + } + } + + return m.plugin[pluginName], nil + } + return nil, fmt.Errorf("no plugin found") +} diff --git a/metricproviders/plugin/plugin.go b/metricproviders/plugin/plugin.go new file mode 100644 index 0000000000..ed8a1aee97 --- /dev/null +++ b/metricproviders/plugin/plugin.go @@ -0,0 +1,35 @@ +package plugin + +import ( + "github.com/argoproj/argo-rollouts/metric" + "github.com/argoproj/argo-rollouts/metricproviders/plugin/client" + "github.com/argoproj/argo-rollouts/metricproviders/plugin/rpc" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +) + +const ProviderType = "RPCPlugin" + +type MetricPlugin struct { + rpc.MetricsPlugin +} + +// NewRpcPlugin returns a new RPC plugin with a singleton client +func NewRpcPlugin(metric v1alpha1.Metric) (metric.Provider, error) { + pluginClient, err := client.GetMetricPlugin(metric) + if err != nil { + return nil, err + } + + return MetricPlugin{ + MetricsPlugin: pluginClient, + }, nil +} + +// GarbageCollect calls the plugins garbage collect method but cast the error back to an "error" type for the internal interface +func (m MetricPlugin) GarbageCollect(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, limit int) error { + err := m.GarbageCollect(run, metric, limit) + if err.Error() != "" { + return err + } + return nil +} diff --git a/metricproviders/plugin/rpc/rpc.go b/metricproviders/plugin/rpc/rpc.go new file mode 100644 index 0000000000..3d085002da --- /dev/null +++ b/metricproviders/plugin/rpc/rpc.go @@ -0,0 +1,254 @@ +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" + metricutil "github.com/argoproj/argo-rollouts/utils/metric" + "github.com/hashicorp/go-plugin" + log "github.com/sirupsen/logrus" +) + +type RunArgs struct { + AnalysisRun *v1alpha1.AnalysisRun + Metric v1alpha1.Metric +} + +type TerminateAndResumeArgs struct { + AnalysisRun *v1alpha1.AnalysisRun + Metric v1alpha1.Metric + Measurement v1alpha1.Measurement +} + +type GarbageCollectArgs struct { + AnalysisRun *v1alpha1.AnalysisRun + Metric v1alpha1.Metric + Limit int +} + +type InitMetricsPluginAndGetMetadataArgs struct { + Metric v1alpha1.Metric +} + +func init() { + gob.RegisterName("RunArgs", new(RunArgs)) + gob.RegisterName("TerminateAndResumeArgs", new(TerminateAndResumeArgs)) + gob.RegisterName("GarbageCollectArgs", new(GarbageCollectArgs)) + gob.RegisterName("InitMetricsPluginAndGetMetadataArgs", new(InitMetricsPluginAndGetMetadataArgs)) + gob.RegisterName("RpcError", new(types.RpcError)) +} + +// MetricsPlugin 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 MetricsPlugin interface { + NewMetricsPlugin(metric v1alpha1.Metric) types.RpcError + types.RpcMetricProvider +} + +// MetricsPluginRPC Here is an implementation that talks over RPC +type MetricsPluginRPC struct{ client *rpc.Client } + +// NewMetricsPlugin is the client side function that is wrapped by a local provider this makes an rpc call to the +// server side function. +func (g *MetricsPluginRPC) NewMetricsPlugin(metric v1alpha1.Metric) types.RpcError { + var resp types.RpcError + var args interface{} = InitMetricsPluginAndGetMetadataArgs{ + Metric: metric, + } + err := g.client.Call("Plugin.NewMetricsPlugin", &args, &resp) + if err != nil { + return types.RpcError{ErrorString: err.Error()} + } + return resp +} + +// Run is the client side function that is wrapped by a local provider this makes an rpc call to the server side function. +func (g *MetricsPluginRPC) Run(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alpha1.Measurement { + var resp v1alpha1.Measurement + var args interface{} = RunArgs{ + AnalysisRun: analysisRun, + Metric: metric, + } + err := g.client.Call("Plugin.Run", &args, &resp) + if err != nil { + return metricutil.MarkMeasurementError(resp, err) + } + return resp +} + +// Resume is the client side function that is wrapped by a local provider this makes an rpc call to the server side function. +func (g *MetricsPluginRPC) Resume(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + var resp v1alpha1.Measurement + var args interface{} = TerminateAndResumeArgs{ + AnalysisRun: analysisRun, + Metric: metric, + Measurement: measurement, + } + err := g.client.Call("Plugin.Resume", &args, &resp) + if err != nil { + return metricutil.MarkMeasurementError(resp, err) + } + return resp +} + +// Terminate is the client side function that is wrapped by a local provider this makes an rpc call to the server side function. +func (g *MetricsPluginRPC) Terminate(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + var resp v1alpha1.Measurement + var args interface{} = TerminateAndResumeArgs{ + AnalysisRun: analysisRun, + Metric: metric, + Measurement: measurement, + } + err := g.client.Call("Plugin.Terminate", &args, &resp) + if err != nil { + return metricutil.MarkMeasurementError(resp, err) + } + return resp +} + +// GarbageCollect is the client side function that is wrapped by a local provider this makes an rpc call to the server side function. +func (g *MetricsPluginRPC) GarbageCollect(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric, limit int) types.RpcError { + var resp types.RpcError + var args interface{} = GarbageCollectArgs{ + AnalysisRun: analysisRun, + Metric: metric, + Limit: limit, + } + err := g.client.Call("Plugin.GarbageCollect", &args, &resp) + if err != nil { + return types.RpcError{ErrorString: err.Error()} + } + return resp +} + +// Type is the client side function that is wrapped by a local provider this makes an rpc call to the server side function. +func (g *MetricsPluginRPC) Type() string { + var resp string + err := g.client.Call("Plugin.Type", new(interface{}), &resp) + if err != nil { + return err.Error() + } + + return resp +} + +// GetMetadata is the client side function that is wrapped by a local provider this makes an rpc call to the server side function. +func (g *MetricsPluginRPC) GetMetadata(metric v1alpha1.Metric) map[string]string { + var resp map[string]string + var args interface{} = InitMetricsPluginAndGetMetadataArgs{ + Metric: metric, + } + err := g.client.Call("Plugin.GetMetadata", &args, &resp) + if err != nil { + log.Errorf("Error calling GetMetadata: %v", err) + return map[string]string{"error": err.Error()} + } + return resp +} + +// MetricsRPCServer Here is the RPC server that MetricsPluginRPC talks to, conforming to +// the requirements of net/rpc +type MetricsRPCServer struct { + // This is the real implementation + Impl MetricsPlugin +} + +// NewMetricsPlugin is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// implementation of the plugin. +func (s *MetricsRPCServer) NewMetricsPlugin(args interface{}, resp *types.RpcError) error { + initArgs, ok := args.(*InitMetricsPluginAndGetMetadataArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.NewMetricsPlugin(initArgs.Metric) + return nil +} + +// Run is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// implementation of the plugin. +func (s *MetricsRPCServer) Run(args interface{}, resp *v1alpha1.Measurement) error { + runArgs, ok := args.(*RunArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.Run(runArgs.AnalysisRun, runArgs.Metric) + return nil +} + +// Resume is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// implementation of the plugin. +func (s *MetricsRPCServer) Resume(args interface{}, resp *v1alpha1.Measurement) error { + resumeArgs, ok := args.(*TerminateAndResumeArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.Resume(resumeArgs.AnalysisRun, resumeArgs.Metric, resumeArgs.Measurement) + return nil +} + +// Terminate is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// implementation of the plugin. +func (s *MetricsRPCServer) Terminate(args interface{}, resp *v1alpha1.Measurement) error { + resumeArgs, ok := args.(*TerminateAndResumeArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.Terminate(resumeArgs.AnalysisRun, resumeArgs.Metric, resumeArgs.Measurement) + return nil +} + +// GarbageCollect is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// implementation of the plugin. +func (s *MetricsRPCServer) GarbageCollect(args interface{}, resp *types.RpcError) error { + gcArgs, ok := args.(*GarbageCollectArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.GarbageCollect(gcArgs.AnalysisRun, gcArgs.Metric, gcArgs.Limit) + return nil +} + +// Type is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// implementation of the plugin. +func (s *MetricsRPCServer) Type(args interface{}, resp *string) error { + *resp = s.Impl.Type() + return nil +} + +// GetMetadata is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// implementation of the plugin. +func (s *MetricsRPCServer) GetMetadata(args interface{}, resp *map[string]string) error { + getMetadataArgs, ok := args.(*InitMetricsPluginAndGetMetadataArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.GetMetadata(getMetadataArgs.Metric) + return nil +} + +// RpcMetricsPlugin 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 MetricsRPCServer for this. +// +// Client must return an implementation of our interface that communicates +// over an RPC client. We return MetricsPluginRPC for this. +// +// Ignore MuxBroker. That is used to create more multiplexed streams on our +// plugin connection and is a more advanced use case. +type RpcMetricsPlugin struct { + // Impl Injection + Impl MetricsPlugin +} + +func (p *RpcMetricsPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return &MetricsRPCServer{Impl: p.Impl}, nil +} + +func (RpcMetricsPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &MetricsPluginRPC{client: c}, nil +} diff --git a/metricproviders/plugin/rpc/rpc_test.go b/metricproviders/plugin/rpc/rpc_test.go new file mode 100644 index 0000000000..2313b7bb0a --- /dev/null +++ b/metricproviders/plugin/rpc/rpc_test.go @@ -0,0 +1,189 @@ +package rpc + +import ( + "context" + "encoding/json" + "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: "metrics", +} + +func pluginClient(t *testing.T) (MetricsPlugin, goPlugin.ClientProtocol, func(), chan struct{}) { + ctx, cancel := context.WithCancel(context.Background()) + + rpcPluginImp := &testRpcPlugin{} + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcMetricsPlugin": &RpcMetricsPlugin{Impl: rpcPluginImp}, + } + + 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("RpcMetricsPlugin") + if err != nil { + t.Fail() + } + + plugin, ok := raw.(MetricsPlugin) + if !ok { + t.Fail() + } + + return plugin, client, cancel, closeCh +} + +func TestPlugin(t *testing.T) { + plugin, _, cancel, closeCh := pluginClient(t) + defer cancel() + + err := plugin.NewMetricsPlugin(v1alpha1.Metric{ + Provider: v1alpha1.MetricProvider{ + Plugin: map[string]json.RawMessage{"prometheus": json.RawMessage(`{"address":"http://prometheus.local", "query":"machine_cpu_cores"}`)}, + }, + }) + if err.Error() != "" { + t.Fail() + } + + runMeasurement := plugin.Run(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}) + assert.Equal(t, "TestCompleted", string(runMeasurement.Phase)) + + runMeasurementErr := plugin.Run(nil, v1alpha1.Metric{}) + assert.Equal(t, "Error", string(runMeasurementErr.Phase)) + assert.Equal(t, "analysisRun is nil", runMeasurementErr.Message) + + resumeMeasurement := plugin.Resume(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{ + Phase: "TestCompletedResume", + Message: "Check to see if we get same phase back", + }) + assert.Equal(t, "TestCompletedResume", string(resumeMeasurement.Phase)) + + terminateMeasurement := plugin.Terminate(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{ + Phase: "TestCompletedTerminate", + Message: "Check to see if we get same phase back", + }) + assert.Equal(t, "TestCompletedTerminate", string(terminateMeasurement.Phase)) + + gcError := plugin.GarbageCollect(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, 0) + assert.Equal(t, "not-implemented", gcError.Error()) + + typeTest := plugin.Type() + assert.Equal(t, "TestRPCPlugin", typeTest) + + metadata := plugin.GetMetadata(v1alpha1.Metric{ + Name: "testMetric", + }) + assert.Equal(t, "testMetric", metadata["metricName"]) + + // 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" + + newMetrics := plugin.NewMetricsPlugin(v1alpha1.Metric{}) + assert.Equal(t, expectedError, newMetrics.Error()) + + measurement := plugin.Terminate(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{}) + assert.Equal(t, expectedError, measurement.Message) + + measurement = plugin.Run(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}) + assert.Equal(t, expectedError, measurement.Message) + + measurement = plugin.Resume(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{}) + assert.Equal(t, expectedError, measurement.Message) + + measurement = plugin.Terminate(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{}) + assert.Equal(t, expectedError, measurement.Message) + + typeStr := plugin.Type() + assert.Equal(t, expectedError, typeStr) + + metadata := plugin.GetMetadata(v1alpha1.Metric{}) + assert.Equal(t, expectedError, metadata["error"]) + + gcError := plugin.GarbageCollect(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, 0) + assert.Equal(t, expectedError, gcError.Error()) + + cancel() + <-closeCh +} + +func TestInvalidArgs(t *testing.T) { + server := MetricsRPCServer{} + badtype := struct { + Args string + }{} + err := server.Run(badtype, &v1alpha1.Measurement{}) + assert.Error(t, err) + + err = server.Resume(badtype, &v1alpha1.Measurement{}) + assert.Error(t, err) + + err = server.Terminate(badtype, &v1alpha1.Measurement{}) + assert.Error(t, err) + + err = server.GarbageCollect(badtype, &types.RpcError{}) + assert.Error(t, err) + + err = server.NewMetricsPlugin(badtype, &types.RpcError{}) + assert.Error(t, err) + + resp := make(map[string]string) + err = server.GetMetadata(badtype, &resp) + assert.Error(t, err) +} diff --git a/metricproviders/plugin/rpc/rpc_test_implementation.go b/metricproviders/plugin/rpc/rpc_test_implementation.go new file mode 100644 index 0000000000..4962f16eb4 --- /dev/null +++ b/metricproviders/plugin/rpc/rpc_test_implementation.go @@ -0,0 +1,71 @@ +package rpc + +import ( + "fmt" + "time" + + "k8s.io/apimachinery/pkg/util/json" + + "github.com/argoproj/argo-rollouts/utils/plugin/types" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + metricutil "github.com/argoproj/argo-rollouts/utils/metric" + timeutil "github.com/argoproj/argo-rollouts/utils/time" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type testRpcPlugin struct{} + +type config struct { + Address string `json:"address"` + Query string `json:"query"` +} + +func (g *testRpcPlugin) NewMetricsPlugin(metric v1alpha1.Metric) types.RpcError { + var c config + err := json.Unmarshal(metric.Provider.Plugin["prometheus"], &c) + if err != nil { + return types.RpcError{ErrorString: err.Error()} + } + return types.RpcError{} +} + +func (g *testRpcPlugin) Run(anaysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alpha1.Measurement { + startTime := timeutil.MetaNow() + finishTime := v1.Time{Time: startTime.Add(10 * time.Second)} + newMeasurement := v1alpha1.Measurement{ + Phase: "TestCompleted", + Message: "Test run completed", + StartedAt: &startTime, + FinishedAt: &finishTime, + Value: "", + Metadata: nil, + ResumeAt: nil, + } + if anaysisRun == nil { + return metricutil.MarkMeasurementError(newMeasurement, fmt.Errorf("analysisRun is nil")) + } + return newMeasurement +} + +func (g *testRpcPlugin) Resume(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + return measurement +} + +func (g *testRpcPlugin) Terminate(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + return measurement +} + +func (g *testRpcPlugin) GarbageCollect(*v1alpha1.AnalysisRun, v1alpha1.Metric, int) types.RpcError { + return types.RpcError{ErrorString: "not-implemented"} +} + +func (g *testRpcPlugin) Type() string { + return "TestRPCPlugin" +} + +func (g *testRpcPlugin) GetMetadata(metric v1alpha1.Metric) map[string]string { + metricsMetadata := make(map[string]string) + metricsMetadata["metricName"] = metric.Name + return metricsMetadata +} diff --git a/mkdocs.yml b/mkdocs.yml index 01972c3c16..5937c17800 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,6 +55,7 @@ nav: - Traefik: features/traffic-management/traefik.md - Analysis: - Overview: features/analysis.md + - Plugins: analysis/plugins.md - Prometheus: analysis/prometheus.md - DataDog: analysis/datadog.md - NewRelic: analysis/newrelic.md diff --git a/pkg/apis/rollouts/v1alpha1/analysis_types.go b/pkg/apis/rollouts/v1alpha1/analysis_types.go index f26fc91585..db72d42bf1 100644 --- a/pkg/apis/rollouts/v1alpha1/analysis_types.go +++ b/pkg/apis/rollouts/v1alpha1/analysis_types.go @@ -173,6 +173,11 @@ type MetricProvider struct { Graphite *GraphiteMetric `json:"graphite,omitempty" protobuf:"bytes,9,opt,name=graphite"` // Influxdb specifies the influxdb metric to query Influxdb *InfluxdbMetric `json:"influxdb,omitempty" protobuf:"bytes,10,opt,name=influxdb"` + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Plugin specifies the hashicorp go-plugin metric to query + Plugin map[string]json.RawMessage `json:"plugin,omitempty" protobuf:"bytes,11,opt,name=plugin"` } // AnalysisPhase is the overall phase of an AnalysisRun, MetricResult, or Measurement diff --git a/pkg/apis/rollouts/v1alpha1/generated.pb.go b/pkg/apis/rollouts/v1alpha1/generated.pb.go index 35baf606ba..b2837b8974 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.pb.go +++ b/pkg/apis/rollouts/v1alpha1/generated.pb.go @@ -20,6 +20,7 @@ limitations under the License. package v1alpha1 import ( + encoding_json "encoding/json" fmt "fmt" io "io" @@ -3111,6 +3112,7 @@ func init() { proto.RegisterType((*MeasurementRetention)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MeasurementRetention") proto.RegisterType((*Metric)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.Metric") proto.RegisterType((*MetricProvider)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MetricProvider") + proto.RegisterMapType((map[string]encoding_json.RawMessage)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MetricProvider.PluginEntry") proto.RegisterType((*MetricResult)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MetricResult") proto.RegisterMapType((map[string]string)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MetricResult.MetadataEntry") proto.RegisterType((*NewRelicMetric)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.NewRelicMetric") @@ -3171,493 +3173,497 @@ func init() { } var fileDescriptor_e0e705f843545fab = []byte{ - // 7773 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x23, 0xd7, - 0x75, 0xb0, 0x87, 0x14, 0x25, 0xf2, 0xe8, 0xff, 0xae, 0x36, 0x2b, 0xcb, 0xde, 0xa5, 0x33, 0x0e, - 0xfc, 0x39, 0xdf, 0xe7, 0x48, 0x89, 0x7f, 0xbe, 0xcf, 0x89, 0x0d, 0x7f, 0x25, 0xa5, 0x5d, 0xaf, - 0xd6, 0xd2, 0x2e, 0xf7, 0x52, 0xbb, 0x9b, 0x38, 0x71, 0x92, 0x11, 0x79, 0x45, 0xcd, 0x8a, 0x9c, - 0x61, 0x66, 0x86, 0xd2, 0xca, 0x31, 0x12, 0x3b, 0x81, 0xdd, 0xb4, 0x48, 0x10, 0xb7, 0x49, 0x50, - 0x14, 0x05, 0x8a, 0xa0, 0x30, 0xd0, 0x9f, 0xe4, 0x29, 0x68, 0xd1, 0x97, 0x00, 0x2d, 0x9a, 0x9f, - 0xa6, 0x0f, 0x29, 0x92, 0x87, 0x36, 0x3f, 0x40, 0xd8, 0x5a, 0xe9, 0x4b, 0x8b, 0x16, 0x41, 0x81, - 0x14, 0x45, 0xf6, 0xa9, 0xb8, 0xbf, 0x73, 0x67, 0x38, 0xd4, 0x52, 0xe2, 0x68, 0x63, 0xb4, 0x79, - 0x23, 0xef, 0x39, 0xf7, 0x9c, 0x73, 0x7f, 0xcf, 0xbd, 0xe7, 0x9e, 0x73, 0x06, 0xd6, 0x1a, 0x76, - 0xb0, 0xdd, 0xd9, 0x5c, 0xac, 0xb9, 0xad, 0x25, 0xcb, 0x6b, 0xb8, 0x6d, 0xcf, 0xbd, 0xc9, 0x7e, - 0xbc, 0xcb, 0x73, 0x9b, 0x4d, 0xb7, 0x13, 0xf8, 0x4b, 0xed, 0x9d, 0xc6, 0x92, 0xd5, 0xb6, 0xfd, - 0x25, 0x55, 0xb2, 0xfb, 0x1e, 0xab, 0xd9, 0xde, 0xb6, 0xde, 0xb3, 0xd4, 0x20, 0x0e, 0xf1, 0xac, - 0x80, 0xd4, 0x17, 0xdb, 0x9e, 0x1b, 0xb8, 0xe8, 0xe9, 0x90, 0xda, 0xa2, 0xa4, 0xc6, 0x7e, 0x7c, - 0x44, 0xd6, 0x5d, 0x6c, 0xef, 0x34, 0x16, 0x29, 0xb5, 0x45, 0x55, 0x22, 0xa9, 0x2d, 0xbc, 0x4b, - 0x93, 0xa5, 0xe1, 0x36, 0xdc, 0x25, 0x46, 0x74, 0xb3, 0xb3, 0xc5, 0xfe, 0xb1, 0x3f, 0xec, 0x17, - 0x67, 0xb6, 0xf0, 0xe0, 0xce, 0x93, 0xfe, 0xa2, 0xed, 0x52, 0xd9, 0x96, 0x36, 0xad, 0xa0, 0xb6, - 0xbd, 0xb4, 0xdb, 0x23, 0xd1, 0x82, 0xa9, 0x21, 0xd5, 0x5c, 0x8f, 0x24, 0xe1, 0x3c, 0x1e, 0xe2, - 0xb4, 0xac, 0xda, 0xb6, 0xed, 0x10, 0x6f, 0x3f, 0x6c, 0x75, 0x8b, 0x04, 0x56, 0x52, 0xad, 0xa5, - 0x7e, 0xb5, 0xbc, 0x8e, 0x13, 0xd8, 0x2d, 0xd2, 0x53, 0xe1, 0xff, 0xde, 0xa9, 0x82, 0x5f, 0xdb, - 0x26, 0x2d, 0xab, 0xa7, 0xde, 0x63, 0xfd, 0xea, 0x75, 0x02, 0xbb, 0xb9, 0x64, 0x3b, 0x81, 0x1f, - 0x78, 0xf1, 0x4a, 0xe6, 0x37, 0xb3, 0x50, 0x28, 0xad, 0x95, 0xab, 0x81, 0x15, 0x74, 0x7c, 0xf4, - 0x9a, 0x01, 0x13, 0x4d, 0xd7, 0xaa, 0x97, 0xad, 0xa6, 0xe5, 0xd4, 0x88, 0x37, 0x6f, 0x3c, 0x60, - 0x3c, 0x3c, 0xfe, 0xe8, 0xda, 0xe2, 0x30, 0xe3, 0xb5, 0x58, 0xda, 0xf3, 0x31, 0xf1, 0xdd, 0x8e, - 0x57, 0x23, 0x98, 0x6c, 0x95, 0xe7, 0xbe, 0xd3, 0x2d, 0xde, 0x73, 0xd0, 0x2d, 0x4e, 0xac, 0x69, - 0x9c, 0x70, 0x84, 0x2f, 0xfa, 0x92, 0x01, 0xb3, 0x35, 0xcb, 0xb1, 0xbc, 0xfd, 0x0d, 0xcb, 0x6b, - 0x90, 0xe0, 0x59, 0xcf, 0xed, 0xb4, 0xe7, 0x33, 0x27, 0x20, 0xcd, 0xbd, 0x42, 0x9a, 0xd9, 0xe5, - 0x38, 0x3b, 0xdc, 0x2b, 0x01, 0x93, 0xcb, 0x0f, 0xac, 0xcd, 0x26, 0xd1, 0xe5, 0xca, 0x9e, 0xa4, - 0x5c, 0xd5, 0x38, 0x3b, 0xdc, 0x2b, 0x81, 0xf9, 0x6a, 0x16, 0x66, 0x4b, 0x6b, 0xe5, 0x0d, 0xcf, - 0xda, 0xda, 0xb2, 0x6b, 0xd8, 0xed, 0x04, 0xb6, 0xd3, 0x40, 0xef, 0x84, 0x31, 0xdb, 0x69, 0x78, - 0xc4, 0xf7, 0xd9, 0x40, 0x16, 0xca, 0xd3, 0x82, 0xe8, 0xd8, 0x2a, 0x2f, 0xc6, 0x12, 0x8e, 0x9e, - 0x80, 0x71, 0x9f, 0x78, 0xbb, 0x76, 0x8d, 0x54, 0x5c, 0x2f, 0x60, 0x3d, 0x9d, 0x2b, 0x9f, 0x12, - 0xe8, 0xe3, 0xd5, 0x10, 0x84, 0x75, 0x3c, 0x5a, 0xcd, 0x73, 0xdd, 0x40, 0xc0, 0x59, 0x47, 0x14, - 0xc2, 0x6a, 0x38, 0x04, 0x61, 0x1d, 0x0f, 0xbd, 0x6e, 0xc0, 0x8c, 0x1f, 0xd8, 0xb5, 0x1d, 0xdb, - 0x21, 0xbe, 0xbf, 0xec, 0x3a, 0x5b, 0x76, 0x63, 0x3e, 0xc7, 0x7a, 0xf1, 0xf2, 0x70, 0xbd, 0x58, - 0x8d, 0x51, 0x2d, 0xcf, 0x1d, 0x74, 0x8b, 0x33, 0xf1, 0x52, 0xdc, 0xc3, 0x1d, 0xad, 0xc0, 0x8c, - 0xe5, 0x38, 0x6e, 0x60, 0x05, 0xb6, 0xeb, 0x54, 0x3c, 0xb2, 0x65, 0xdf, 0x9a, 0x1f, 0x61, 0xcd, - 0x99, 0x17, 0xcd, 0x99, 0x29, 0xc5, 0xe0, 0xb8, 0xa7, 0x86, 0xb9, 0x02, 0xf3, 0xa5, 0xd6, 0xa6, - 0xe5, 0xfb, 0x56, 0xdd, 0xf5, 0x62, 0xa3, 0xf1, 0x30, 0xe4, 0x5b, 0x56, 0xbb, 0x6d, 0x3b, 0x0d, - 0x3a, 0x1c, 0xd9, 0x87, 0x0b, 0xe5, 0x89, 0x83, 0x6e, 0x31, 0xbf, 0x2e, 0xca, 0xb0, 0x82, 0x9a, - 0x3f, 0xca, 0xc0, 0x78, 0xc9, 0xb1, 0x9a, 0xfb, 0xbe, 0xed, 0xe3, 0x8e, 0x83, 0x3e, 0x0a, 0x79, - 0xba, 0xbb, 0xd4, 0xad, 0xc0, 0x12, 0x2b, 0xf2, 0xdd, 0x8b, 0x7c, 0xb1, 0x2f, 0xea, 0x8b, 0x3d, - 0xec, 0x17, 0x8a, 0xbd, 0xb8, 0xfb, 0x9e, 0xc5, 0x2b, 0x9b, 0x37, 0x49, 0x2d, 0x58, 0x27, 0x81, - 0x55, 0x46, 0xa2, 0x15, 0x10, 0x96, 0x61, 0x45, 0x15, 0xb9, 0x30, 0xe2, 0xb7, 0x49, 0x4d, 0xac, - 0xb0, 0xf5, 0x21, 0x67, 0x72, 0x28, 0x7a, 0xb5, 0x4d, 0x6a, 0xe5, 0x09, 0xc1, 0x7a, 0x84, 0xfe, - 0xc3, 0x8c, 0x11, 0xda, 0x83, 0x51, 0x9f, 0xed, 0x39, 0x62, 0xf1, 0x5c, 0x49, 0x8f, 0x25, 0x23, - 0x5b, 0x9e, 0x12, 0x4c, 0x47, 0xf9, 0x7f, 0x2c, 0xd8, 0x99, 0x3f, 0x36, 0xe0, 0x94, 0x86, 0x5d, - 0xf2, 0x1a, 0x9d, 0x16, 0x71, 0x02, 0xf4, 0x00, 0x8c, 0x38, 0x56, 0x8b, 0x88, 0x85, 0xa2, 0x44, - 0xbe, 0x6c, 0xb5, 0x08, 0x66, 0x10, 0xf4, 0x20, 0xe4, 0x76, 0xad, 0x66, 0x87, 0xb0, 0x4e, 0x2a, - 0x94, 0x27, 0x05, 0x4a, 0xee, 0x3a, 0x2d, 0xc4, 0x1c, 0x86, 0x5e, 0x82, 0x02, 0xfb, 0x71, 0xc1, - 0x73, 0x5b, 0x29, 0x35, 0x4d, 0x48, 0x78, 0x5d, 0x92, 0x2d, 0x4f, 0x1e, 0x74, 0x8b, 0x05, 0xf5, - 0x17, 0x87, 0x0c, 0xcd, 0x7f, 0x30, 0x60, 0x5a, 0x6b, 0xdc, 0x9a, 0xed, 0x07, 0xe8, 0x43, 0x3d, - 0x93, 0x67, 0x71, 0xb0, 0xc9, 0x43, 0x6b, 0xb3, 0xa9, 0x33, 0x23, 0x5a, 0x9a, 0x97, 0x25, 0xda, - 0xc4, 0x71, 0x20, 0x67, 0x07, 0xa4, 0xe5, 0xcf, 0x67, 0x1e, 0xc8, 0x3e, 0x3c, 0xfe, 0xe8, 0x6a, - 0x6a, 0xc3, 0x18, 0xf6, 0xef, 0x2a, 0xa5, 0x8f, 0x39, 0x1b, 0xf3, 0x6b, 0x23, 0x91, 0x16, 0xd2, - 0x19, 0x85, 0x5c, 0x18, 0x6b, 0x91, 0xc0, 0xb3, 0x6b, 0x7c, 0x5d, 0x8d, 0x3f, 0xba, 0x32, 0x9c, - 0x14, 0xeb, 0x8c, 0x58, 0xb8, 0x59, 0xf2, 0xff, 0x3e, 0x96, 0x5c, 0xd0, 0x36, 0x8c, 0x58, 0x5e, - 0x43, 0xb6, 0xf9, 0x42, 0x3a, 0xe3, 0x1b, 0xce, 0xb9, 0x92, 0xd7, 0xf0, 0x31, 0xe3, 0x80, 0x96, - 0xa0, 0x10, 0x10, 0xaf, 0x65, 0x3b, 0x56, 0xc0, 0x77, 0xd7, 0x7c, 0x79, 0x56, 0xa0, 0x15, 0x36, - 0x24, 0x00, 0x87, 0x38, 0xa8, 0x09, 0xa3, 0x75, 0x6f, 0x1f, 0x77, 0x9c, 0xf9, 0x91, 0x34, 0xba, - 0x62, 0x85, 0xd1, 0x0a, 0x17, 0x13, 0xff, 0x8f, 0x05, 0x0f, 0xf4, 0x86, 0x01, 0x73, 0x2d, 0x62, - 0xf9, 0x1d, 0x8f, 0xd0, 0x26, 0x60, 0x12, 0x10, 0x87, 0xee, 0x86, 0xf3, 0x39, 0xc6, 0x1c, 0x0f, - 0x3b, 0x0e, 0xbd, 0x94, 0xcb, 0xf7, 0x0b, 0x51, 0xe6, 0x92, 0xa0, 0x38, 0x51, 0x1a, 0xf3, 0x47, - 0x23, 0x30, 0xdb, 0xb3, 0x43, 0xa0, 0xc7, 0x21, 0xd7, 0xde, 0xb6, 0x7c, 0xb9, 0xe4, 0xcf, 0xc9, - 0xf9, 0x56, 0xa1, 0x85, 0xb7, 0xbb, 0xc5, 0x49, 0x59, 0x85, 0x15, 0x60, 0x8e, 0x4c, 0x75, 0x6a, - 0x8b, 0xf8, 0xbe, 0xd5, 0x90, 0xfb, 0x80, 0x36, 0x4d, 0x58, 0x31, 0x96, 0x70, 0xf4, 0xeb, 0x06, - 0x4c, 0xf2, 0x29, 0x83, 0x89, 0xdf, 0x69, 0x06, 0x74, 0xaf, 0xa3, 0xdd, 0x72, 0x29, 0x8d, 0xe9, - 0xc9, 0x49, 0x96, 0x4f, 0x0b, 0xee, 0x93, 0x7a, 0xa9, 0x8f, 0xa3, 0x7c, 0xd1, 0x0d, 0x28, 0xf8, - 0x81, 0xe5, 0x05, 0xa4, 0x5e, 0x0a, 0x98, 0x56, 0x1b, 0x7f, 0xf4, 0x7f, 0x0f, 0xb6, 0x09, 0x6c, - 0xd8, 0x2d, 0xc2, 0x37, 0x9c, 0xaa, 0x24, 0x80, 0x43, 0x5a, 0xe8, 0x25, 0x00, 0xaf, 0xe3, 0x54, - 0x3b, 0xad, 0x96, 0xe5, 0xed, 0x0b, 0x0d, 0x7e, 0x71, 0xb8, 0xe6, 0x61, 0x45, 0x2f, 0xd4, 0x59, - 0x61, 0x19, 0xd6, 0xf8, 0xa1, 0x57, 0x0c, 0x98, 0xe4, 0x33, 0x51, 0x4a, 0x30, 0x9a, 0xb2, 0x04, - 0xb3, 0xb4, 0x6b, 0x57, 0x74, 0x16, 0x38, 0xca, 0xd1, 0xfc, 0xbb, 0xa8, 0x3e, 0xa9, 0x06, 0xf4, - 0x74, 0xdd, 0xd8, 0x47, 0x1f, 0x84, 0x7b, 0xfd, 0x4e, 0xad, 0x46, 0x7c, 0x7f, 0xab, 0xd3, 0xc4, - 0x1d, 0xe7, 0xa2, 0xed, 0x07, 0xae, 0xb7, 0xbf, 0x66, 0xb7, 0xec, 0x80, 0xcd, 0xb8, 0x5c, 0xf9, - 0xec, 0x41, 0xb7, 0x78, 0x6f, 0xb5, 0x1f, 0x12, 0xee, 0x5f, 0x1f, 0x59, 0x70, 0x5f, 0xc7, 0xe9, - 0x4f, 0x9e, 0x9f, 0xde, 0x8a, 0x07, 0xdd, 0xe2, 0x7d, 0xd7, 0xfa, 0xa3, 0xe1, 0xc3, 0x68, 0x98, - 0xff, 0x62, 0xc0, 0x8c, 0x6c, 0xd7, 0x06, 0x69, 0xb5, 0x9b, 0x74, 0x77, 0x39, 0xf9, 0x83, 0x48, - 0x10, 0x39, 0x88, 0xe0, 0x74, 0xd4, 0x89, 0x94, 0xbf, 0xdf, 0x69, 0xc4, 0xfc, 0x67, 0x03, 0xe6, - 0xe2, 0xc8, 0x77, 0x41, 0x79, 0xfa, 0x51, 0xe5, 0x79, 0x39, 0xdd, 0xd6, 0xf6, 0xd1, 0xa0, 0xaf, - 0x8d, 0xf4, 0xb6, 0xf5, 0xbf, 0xbb, 0x1a, 0x0d, 0xb5, 0x62, 0xf6, 0x97, 0xa9, 0x15, 0x47, 0xde, - 0x52, 0x5a, 0xf1, 0x8f, 0x46, 0x60, 0xa2, 0xe4, 0x04, 0x76, 0x69, 0x6b, 0xcb, 0x76, 0xec, 0x60, - 0x1f, 0x7d, 0x36, 0x03, 0x4b, 0x6d, 0x8f, 0x6c, 0x11, 0xcf, 0x23, 0xf5, 0x95, 0x8e, 0x67, 0x3b, - 0x8d, 0x6a, 0x6d, 0x9b, 0xd4, 0x3b, 0x4d, 0xdb, 0x69, 0xac, 0x36, 0x1c, 0x57, 0x15, 0x9f, 0xbf, - 0x45, 0x6a, 0x1d, 0xd6, 0x24, 0xbe, 0x28, 0x5a, 0xc3, 0x35, 0xa9, 0x72, 0x34, 0xa6, 0xe5, 0xc7, - 0x0e, 0xba, 0xc5, 0xa5, 0x23, 0x56, 0xc2, 0x47, 0x6d, 0x1a, 0xfa, 0x4c, 0x06, 0x16, 0x3d, 0xf2, - 0xb1, 0x8e, 0x3d, 0x78, 0x6f, 0xf0, 0x5d, 0xab, 0x39, 0xa4, 0xfa, 0x39, 0x12, 0xcf, 0xf2, 0xa3, - 0x07, 0xdd, 0xe2, 0x11, 0xeb, 0xe0, 0x23, 0xb6, 0xcb, 0xac, 0xc0, 0x78, 0xa9, 0x6d, 0xfb, 0xf6, - 0x2d, 0x7a, 0x97, 0x25, 0x03, 0xdc, 0x95, 0x8a, 0x90, 0xf3, 0x3a, 0x4d, 0xc2, 0xd7, 0x76, 0xa1, - 0x5c, 0xa0, 0xbb, 0x10, 0xa6, 0x05, 0x98, 0x97, 0x9b, 0x9f, 0xa2, 0x3b, 0x2e, 0x23, 0x19, 0xbb, - 0x25, 0xdf, 0x84, 0x9c, 0x47, 0x99, 0x88, 0x99, 0x35, 0xec, 0x85, 0x22, 0x94, 0x5a, 0x08, 0x41, - 0x7f, 0x62, 0xce, 0xc2, 0xfc, 0x46, 0x06, 0x4e, 0x97, 0xda, 0xed, 0x75, 0xe2, 0x6f, 0xc7, 0xa4, - 0xf8, 0xbc, 0x01, 0x53, 0xbb, 0xb6, 0x17, 0x74, 0xac, 0xa6, 0xb4, 0x6d, 0x70, 0x79, 0xaa, 0xc3, - 0xca, 0xc3, 0xb8, 0x5d, 0x8f, 0x90, 0x2e, 0xa3, 0x83, 0x6e, 0x71, 0x2a, 0x5a, 0x86, 0x63, 0xec, - 0xd1, 0xef, 0x18, 0x30, 0x23, 0x8a, 0x2e, 0xbb, 0x75, 0xa2, 0x1b, 0xc4, 0xae, 0xa5, 0x29, 0x93, - 0x22, 0xce, 0x2d, 0x27, 0xf1, 0x52, 0xdc, 0x23, 0x84, 0xf9, 0x6f, 0x19, 0x38, 0xd3, 0x87, 0x06, - 0xfa, 0x43, 0x03, 0xe6, 0xb8, 0x15, 0x4d, 0x03, 0x61, 0xb2, 0x25, 0x7a, 0xf3, 0x03, 0x69, 0x4b, - 0x8e, 0xe9, 0x12, 0x27, 0x4e, 0x8d, 0x94, 0xe7, 0xe9, 0x6e, 0xb8, 0x9c, 0xc0, 0x1a, 0x27, 0x0a, - 0xc4, 0x24, 0xe5, 0x76, 0xb5, 0x98, 0xa4, 0x99, 0xbb, 0x22, 0x69, 0x35, 0x81, 0x35, 0x4e, 0x14, - 0xc8, 0xfc, 0xff, 0x70, 0xdf, 0x21, 0xe4, 0xee, 0xbc, 0x38, 0xcd, 0x17, 0xd4, 0xac, 0x8f, 0xce, - 0xb9, 0x01, 0xd6, 0xb5, 0x09, 0xa3, 0x6c, 0xe9, 0xc8, 0x85, 0x0d, 0x54, 0xfd, 0xb1, 0x35, 0xe5, - 0x63, 0x01, 0x31, 0xbf, 0x61, 0x40, 0xfe, 0x08, 0x66, 0x95, 0x62, 0xd4, 0xac, 0x52, 0xe8, 0x31, - 0xa9, 0x04, 0xbd, 0x26, 0x95, 0x67, 0x87, 0x1b, 0x8d, 0x41, 0x4c, 0x29, 0x3f, 0x33, 0x60, 0xb6, - 0xc7, 0xf4, 0x82, 0xb6, 0x61, 0xae, 0xed, 0xd6, 0xe5, 0xb1, 0xe9, 0xa2, 0xe5, 0x6f, 0x33, 0x98, - 0x68, 0xde, 0xe3, 0x74, 0x24, 0x2b, 0x09, 0xf0, 0xdb, 0xdd, 0xe2, 0xbc, 0x22, 0x12, 0x43, 0xc0, - 0x89, 0x14, 0x51, 0x1b, 0xf2, 0x5b, 0x36, 0x69, 0xd6, 0xc3, 0x29, 0x38, 0xe4, 0x01, 0xe9, 0x82, - 0xa0, 0xc6, 0xad, 0x8e, 0xf2, 0x1f, 0x56, 0x5c, 0xcc, 0xab, 0x30, 0x15, 0xb5, 0x41, 0x0f, 0x30, - 0x78, 0x67, 0x21, 0x6b, 0x79, 0x8e, 0x18, 0xba, 0x71, 0x81, 0x90, 0x2d, 0xe1, 0xcb, 0x98, 0x96, - 0x9b, 0xbf, 0x18, 0x81, 0xe9, 0x72, 0xb3, 0x43, 0x9e, 0xf5, 0x08, 0x91, 0xd7, 0xee, 0x12, 0x4c, - 0xb7, 0x3d, 0xb2, 0x6b, 0x93, 0xbd, 0x2a, 0x69, 0x92, 0x5a, 0xe0, 0x7a, 0x82, 0xfe, 0x19, 0x51, - 0x7d, 0xba, 0x12, 0x05, 0xe3, 0x38, 0x3e, 0x7a, 0x06, 0xa6, 0xac, 0x5a, 0x60, 0xef, 0x12, 0x45, - 0x81, 0x0b, 0xf0, 0x36, 0x41, 0x61, 0xaa, 0x14, 0x81, 0xe2, 0x18, 0x36, 0xfa, 0x10, 0xcc, 0xfb, - 0x35, 0xab, 0x49, 0xae, 0xb5, 0x05, 0xab, 0xe5, 0x6d, 0x52, 0xdb, 0xa9, 0xb8, 0xb6, 0x13, 0x08, - 0x23, 0xcb, 0x03, 0x82, 0xd2, 0x7c, 0xb5, 0x0f, 0x1e, 0xee, 0x4b, 0x01, 0xfd, 0x85, 0x01, 0x67, - 0xdb, 0x1e, 0xa9, 0x78, 0x6e, 0xcb, 0xa5, 0xda, 0xb3, 0xc7, 0xf2, 0x20, 0x6e, 0xe0, 0xd7, 0x87, - 0x3c, 0x26, 0xf0, 0x92, 0x5e, 0xcb, 0xe7, 0xdb, 0x0f, 0xba, 0xc5, 0xb3, 0x95, 0xc3, 0x04, 0xc0, - 0x87, 0xcb, 0x87, 0xfe, 0xca, 0x80, 0x73, 0x6d, 0xd7, 0x0f, 0x0e, 0x69, 0x42, 0xee, 0x44, 0x9b, - 0x60, 0x1e, 0x74, 0x8b, 0xe7, 0x2a, 0x87, 0x4a, 0x80, 0xef, 0x20, 0xa1, 0x79, 0x30, 0x0e, 0xb3, - 0xda, 0xdc, 0x13, 0xd7, 0xf2, 0xa7, 0x60, 0x52, 0x4e, 0x86, 0x50, 0xad, 0x17, 0x42, 0x33, 0x4a, - 0x49, 0x07, 0xe2, 0x28, 0x2e, 0x9d, 0x77, 0x6a, 0x2a, 0xf2, 0xda, 0xb1, 0x79, 0x57, 0x89, 0x40, - 0x71, 0x0c, 0x1b, 0xad, 0xc2, 0x29, 0x51, 0x82, 0x49, 0xbb, 0x69, 0xd7, 0xac, 0x65, 0xb7, 0x23, - 0xa6, 0x5c, 0xae, 0x7c, 0xe6, 0xa0, 0x5b, 0x3c, 0x55, 0xe9, 0x05, 0xe3, 0xa4, 0x3a, 0x68, 0x0d, - 0xe6, 0xac, 0x4e, 0xe0, 0xaa, 0xf6, 0x9f, 0x77, 0xa8, 0xa6, 0xa8, 0xb3, 0xa9, 0x95, 0xe7, 0x2a, - 0xa5, 0x94, 0x00, 0xc7, 0x89, 0xb5, 0x50, 0x25, 0x46, 0xad, 0x4a, 0x6a, 0xae, 0x53, 0xe7, 0xa3, - 0x9c, 0x0b, 0x2f, 0x17, 0xa5, 0x04, 0x1c, 0x9c, 0x58, 0x13, 0x35, 0x61, 0xaa, 0x65, 0xdd, 0xba, - 0xe6, 0x58, 0xbb, 0x96, 0xdd, 0xa4, 0x4c, 0x84, 0x69, 0xa6, 0xbf, 0xbd, 0xa0, 0x13, 0xd8, 0xcd, - 0x45, 0xfe, 0x4a, 0xb9, 0xb8, 0xea, 0x04, 0x57, 0xbc, 0x6a, 0x40, 0x0f, 0xa1, 0xfc, 0x70, 0xb4, - 0x1e, 0xa1, 0x85, 0x63, 0xb4, 0xd1, 0x15, 0x38, 0xcd, 0x96, 0xe3, 0x8a, 0xbb, 0xe7, 0xac, 0x90, - 0xa6, 0xb5, 0x2f, 0x1b, 0x30, 0xc6, 0x1a, 0x70, 0xef, 0x41, 0xb7, 0x78, 0xba, 0x9a, 0x84, 0x80, - 0x93, 0xeb, 0x21, 0x0b, 0xee, 0x8b, 0x02, 0x30, 0xd9, 0xb5, 0x7d, 0xdb, 0x75, 0xb8, 0x81, 0x25, - 0x1f, 0x1a, 0x58, 0xaa, 0xfd, 0xd1, 0xf0, 0x61, 0x34, 0xd0, 0xef, 0x19, 0x30, 0x97, 0xb4, 0x0c, - 0xe7, 0x0b, 0x69, 0xbc, 0xc1, 0xc4, 0x96, 0x16, 0x9f, 0x11, 0x89, 0x9b, 0x42, 0xa2, 0x10, 0xe8, - 0x65, 0x03, 0x26, 0x2c, 0xed, 0x72, 0x38, 0x0f, 0x4c, 0xaa, 0x4b, 0xc3, 0x9a, 0x28, 0x42, 0x8a, - 0xe5, 0x99, 0x83, 0x6e, 0x31, 0x72, 0x01, 0xc5, 0x11, 0x8e, 0xe8, 0xf7, 0x0d, 0x38, 0x9d, 0xb8, - 0xc6, 0xe7, 0xc7, 0x4f, 0xa2, 0x87, 0xd8, 0x24, 0x49, 0xde, 0x73, 0x92, 0xc5, 0x40, 0xaf, 0x1b, - 0x4a, 0x95, 0xad, 0x4b, 0x23, 0xd1, 0x04, 0x13, 0xed, 0xea, 0x90, 0xf7, 0xe1, 0xf0, 0x40, 0x20, - 0x09, 0x97, 0x4f, 0x69, 0x9a, 0x51, 0x16, 0xe2, 0x38, 0x7b, 0xf4, 0x39, 0x43, 0xaa, 0x46, 0x25, - 0xd1, 0xe4, 0x49, 0x49, 0x84, 0x42, 0x4d, 0xab, 0x04, 0x8a, 0x31, 0x47, 0x1f, 0x86, 0x05, 0x6b, - 0xd3, 0xf5, 0x82, 0xc4, 0xc5, 0x37, 0x3f, 0xc5, 0x96, 0xd1, 0xb9, 0x83, 0x6e, 0x71, 0xa1, 0xd4, - 0x17, 0x0b, 0x1f, 0x42, 0xc1, 0xfc, 0x6a, 0x0e, 0x26, 0xf8, 0x21, 0x5f, 0xa8, 0xae, 0xaf, 0x1b, - 0x70, 0x7f, 0xad, 0xe3, 0x79, 0xc4, 0x09, 0xaa, 0x01, 0x69, 0xf7, 0x2a, 0x2e, 0xe3, 0x44, 0x15, - 0xd7, 0x03, 0x07, 0xdd, 0xe2, 0xfd, 0xcb, 0x87, 0xf0, 0xc7, 0x87, 0x4a, 0x87, 0xfe, 0xd6, 0x00, - 0x53, 0x20, 0x94, 0xad, 0xda, 0x4e, 0xc3, 0x73, 0x3b, 0x4e, 0xbd, 0xb7, 0x11, 0x99, 0x13, 0x6d, - 0xc4, 0x43, 0x07, 0xdd, 0xa2, 0xb9, 0x7c, 0x47, 0x29, 0xf0, 0x00, 0x92, 0xa2, 0x67, 0x61, 0x56, - 0x60, 0x9d, 0xbf, 0xd5, 0x26, 0x9e, 0x4d, 0x8f, 0xd3, 0xc2, 0x4d, 0x20, 0xf4, 0xbc, 0x88, 0x23, - 0xe0, 0xde, 0x3a, 0xc8, 0x87, 0xb1, 0x3d, 0x62, 0x37, 0xb6, 0x03, 0x79, 0x7c, 0x1a, 0xd2, 0xdd, - 0x42, 0x5c, 0xf8, 0x6f, 0x70, 0x9a, 0xe5, 0xf1, 0x83, 0x6e, 0x71, 0x4c, 0xfc, 0xc1, 0x92, 0x13, - 0xba, 0x0c, 0x53, 0xfc, 0x0a, 0x56, 0xb1, 0x9d, 0x46, 0xc5, 0x75, 0xb8, 0x93, 0x42, 0xa1, 0xfc, - 0x90, 0x54, 0xf8, 0xd5, 0x08, 0xf4, 0x76, 0xb7, 0x38, 0x21, 0x7f, 0x6f, 0xec, 0xb7, 0x09, 0x8e, - 0xd5, 0x36, 0xbf, 0x3d, 0x0a, 0x20, 0xa7, 0x2b, 0x69, 0xa3, 0xff, 0x03, 0x05, 0x9f, 0x04, 0x9c, - 0xab, 0x78, 0x13, 0xe0, 0x4f, 0x2d, 0xb2, 0x10, 0x87, 0x70, 0xb4, 0x03, 0xb9, 0xb6, 0xd5, 0xf1, - 0x89, 0x18, 0xfc, 0x4b, 0xa9, 0x0c, 0x7e, 0x85, 0x52, 0xe4, 0x77, 0x2e, 0xf6, 0x13, 0x73, 0x1e, - 0xe8, 0xd3, 0x06, 0x00, 0x89, 0x0e, 0xd8, 0xd0, 0xb6, 0x0f, 0xc1, 0x32, 0x1c, 0x53, 0xda, 0x07, - 0xe5, 0xa9, 0x83, 0x6e, 0x11, 0xb4, 0xa1, 0xd7, 0xd8, 0xa2, 0x3d, 0xc8, 0x5b, 0x72, 0xcf, 0x1f, - 0x39, 0x89, 0x3d, 0x9f, 0x5d, 0x85, 0xd4, 0xa4, 0x55, 0xcc, 0xd0, 0x67, 0x0c, 0x98, 0xf2, 0x49, - 0x20, 0x86, 0x8a, 0xee, 0x3c, 0xe2, 0xc0, 0x3b, 0xe4, 0xa4, 0xab, 0x46, 0x68, 0xf2, 0x1d, 0x34, - 0x5a, 0x86, 0x63, 0x7c, 0xa5, 0x28, 0x17, 0x89, 0x55, 0x27, 0x1e, 0xbb, 0x69, 0x8b, 0x93, 0xd4, - 0xf0, 0xa2, 0x68, 0x34, 0x95, 0x28, 0x5a, 0x19, 0x8e, 0xf1, 0x95, 0xa2, 0xac, 0xdb, 0x9e, 0xe7, - 0x0a, 0x51, 0xf2, 0x29, 0x89, 0xa2, 0xd1, 0x54, 0xa2, 0x68, 0x65, 0x38, 0xc6, 0xd7, 0xfc, 0xf2, - 0x24, 0x4c, 0xc9, 0x85, 0x14, 0x9e, 0xec, 0xb9, 0x61, 0xa7, 0xcf, 0xc9, 0x7e, 0x59, 0x07, 0xe2, - 0x28, 0x2e, 0xad, 0xcc, 0x97, 0x6a, 0xf4, 0x60, 0xaf, 0x2a, 0x57, 0x75, 0x20, 0x8e, 0xe2, 0xa2, - 0x16, 0xe4, 0xfc, 0x80, 0xb4, 0xe5, 0xf3, 0xee, 0x90, 0xaf, 0x8f, 0xe1, 0xfe, 0x10, 0x3e, 0xe0, - 0xd0, 0x7f, 0x3e, 0xe6, 0x5c, 0x98, 0x6d, 0x32, 0x88, 0x98, 0x2b, 0xc5, 0xe2, 0x48, 0x67, 0x7d, - 0x46, 0x2d, 0xa1, 0x7c, 0x34, 0xa2, 0x65, 0x38, 0xc6, 0x3e, 0xe1, 0xb0, 0x9f, 0x3b, 0xc1, 0xc3, - 0xfe, 0xf3, 0x90, 0x6f, 0x59, 0xb7, 0xaa, 0x1d, 0xaf, 0x71, 0xfc, 0x4b, 0x85, 0xf0, 0xbc, 0xe2, - 0x54, 0xb0, 0xa2, 0x87, 0x5e, 0x31, 0xb4, 0x2d, 0x67, 0x8c, 0x11, 0xbf, 0x91, 0xee, 0x96, 0xa3, - 0x74, 0x65, 0xdf, 0xcd, 0xa7, 0xe7, 0xe8, 0x9d, 0xbf, 0xeb, 0x47, 0x6f, 0x7a, 0x8c, 0xe4, 0x0b, - 0x44, 0x1d, 0x23, 0x0b, 0x27, 0x7a, 0x8c, 0x5c, 0x8e, 0x30, 0xc3, 0x31, 0xe6, 0x4c, 0x1e, 0xbe, - 0xe6, 0x94, 0x3c, 0x70, 0xa2, 0xf2, 0x54, 0x23, 0xcc, 0x70, 0x8c, 0x79, 0xff, 0xfb, 0xe6, 0xf8, - 0xc9, 0xdc, 0x37, 0x27, 0x52, 0xb8, 0x6f, 0x1e, 0x7e, 0x14, 0x9f, 0x1c, 0xf6, 0x28, 0x8e, 0x2e, - 0x01, 0xaa, 0xef, 0x3b, 0x56, 0xcb, 0xae, 0x89, 0xcd, 0x92, 0xa9, 0xcd, 0x29, 0x66, 0x8f, 0x58, - 0x10, 0x1b, 0x19, 0x5a, 0xe9, 0xc1, 0xc0, 0x09, 0xb5, 0x50, 0x00, 0xf9, 0xb6, 0x3c, 0x71, 0x4d, - 0xa7, 0x31, 0xfb, 0xe5, 0x09, 0x8c, 0x7b, 0x00, 0xd0, 0x85, 0x27, 0x4b, 0xb0, 0xe2, 0x84, 0xd6, - 0x60, 0xae, 0x65, 0x3b, 0x15, 0xb7, 0xee, 0x57, 0x88, 0x27, 0xac, 0x2d, 0x55, 0x12, 0xcc, 0xcf, - 0xb0, 0xbe, 0x61, 0x37, 0xe8, 0xf5, 0x04, 0x38, 0x4e, 0xac, 0x65, 0xfe, 0x87, 0x01, 0x33, 0xcb, - 0x4d, 0xb7, 0x53, 0xbf, 0x61, 0x05, 0xb5, 0x6d, 0xfe, 0xf8, 0x8d, 0x9e, 0x81, 0xbc, 0xed, 0x04, - 0xc4, 0xdb, 0xb5, 0x9a, 0x42, 0x3f, 0x99, 0xd2, 0x3f, 0x60, 0x55, 0x94, 0xdf, 0xee, 0x16, 0xa7, - 0x56, 0x3a, 0x1e, 0xf3, 0x2a, 0xe5, 0xbb, 0x15, 0x56, 0x75, 0xd0, 0x97, 0x0d, 0x98, 0xe5, 0xcf, - 0xe7, 0x2b, 0x56, 0x60, 0x5d, 0xed, 0x10, 0xcf, 0x26, 0xf2, 0x01, 0x7d, 0xc8, 0x8d, 0x2a, 0x2e, - 0xab, 0x64, 0xb0, 0x1f, 0x1e, 0xd4, 0xd7, 0xe3, 0x9c, 0x71, 0xaf, 0x30, 0xe6, 0x17, 0xb2, 0x70, - 0x6f, 0x5f, 0x5a, 0x68, 0x01, 0x32, 0x76, 0x5d, 0x34, 0x1d, 0x04, 0xdd, 0xcc, 0x6a, 0x1d, 0x67, - 0xec, 0x3a, 0x5a, 0x64, 0x67, 0x4e, 0x8f, 0xf8, 0xbe, 0x7c, 0x4b, 0x2d, 0xa8, 0xe3, 0xa1, 0x28, - 0xc5, 0x1a, 0x06, 0x2a, 0x42, 0xae, 0x69, 0x6d, 0x92, 0xa6, 0xb8, 0x4f, 0xb0, 0x53, 0xec, 0x1a, - 0x2d, 0xc0, 0xbc, 0x1c, 0x7d, 0xca, 0x00, 0xe0, 0x02, 0xd2, 0xdb, 0x88, 0xd0, 0x92, 0x38, 0xdd, - 0x6e, 0xa2, 0x94, 0xb9, 0x94, 0xe1, 0x7f, 0xac, 0x71, 0x45, 0x1b, 0x30, 0x4a, 0x0f, 0xb4, 0x6e, - 0xfd, 0xd8, 0x4a, 0x91, 0x3d, 0xb2, 0x54, 0x18, 0x0d, 0x2c, 0x68, 0xd1, 0xbe, 0xf2, 0x48, 0xd0, - 0xf1, 0x1c, 0xda, 0xb5, 0x4c, 0x0d, 0xe6, 0xb9, 0x14, 0x58, 0x95, 0x62, 0x0d, 0xc3, 0xfc, 0xf3, - 0x0c, 0xcc, 0x25, 0x89, 0x4e, 0xb5, 0xcd, 0x28, 0x97, 0x56, 0x5c, 0x8d, 0xdf, 0x9f, 0x7e, 0xff, - 0x08, 0x4f, 0x10, 0xe5, 0x2f, 0x21, 0x7c, 0xd5, 0x04, 0x5f, 0xf4, 0x7e, 0xd5, 0x43, 0x99, 0x63, - 0xf6, 0x90, 0xa2, 0x1c, 0xeb, 0xa5, 0x07, 0x60, 0xc4, 0xa7, 0x23, 0x9f, 0x8d, 0x3e, 0x60, 0xb0, - 0x31, 0x62, 0x10, 0x8a, 0xd1, 0x71, 0xec, 0x40, 0xb8, 0x7a, 0x2b, 0x8c, 0x6b, 0x8e, 0x1d, 0x60, - 0x06, 0x31, 0xbf, 0x94, 0x81, 0x85, 0xfe, 0x8d, 0x42, 0x5f, 0x32, 0x00, 0xea, 0xf4, 0xba, 0x42, - 0xa7, 0xa4, 0xf4, 0x9c, 0xb1, 0x4e, 0xaa, 0x0f, 0x57, 0x24, 0xa7, 0xd0, 0x8d, 0x4a, 0x15, 0xf9, - 0x58, 0x13, 0x04, 0x3d, 0x2a, 0xa7, 0xfe, 0x65, 0xab, 0x25, 0x8f, 0xb3, 0xaa, 0xce, 0xba, 0x82, - 0x60, 0x0d, 0x8b, 0xde, 0x47, 0x1d, 0xab, 0x45, 0xfc, 0xb6, 0xa5, 0x7c, 0xf9, 0xd9, 0x7d, 0xf4, - 0xb2, 0x2c, 0xc4, 0x21, 0xdc, 0x6c, 0xc2, 0x83, 0x03, 0xc8, 0x99, 0x92, 0x5f, 0xb5, 0xf9, 0xef, - 0x06, 0x9c, 0x59, 0x6e, 0x76, 0xfc, 0x80, 0x78, 0xff, 0x63, 0xbc, 0xd2, 0xfe, 0xd3, 0x80, 0xfb, - 0xfa, 0xb4, 0xf9, 0x2e, 0x38, 0xa7, 0xbd, 0x18, 0x75, 0x4e, 0xbb, 0x36, 0xec, 0x94, 0x4e, 0x6c, - 0x47, 0x1f, 0x1f, 0xb5, 0x00, 0x26, 0xe9, 0xae, 0x55, 0x77, 0x1b, 0x29, 0xe9, 0xcd, 0x07, 0x21, - 0xf7, 0x31, 0xaa, 0x7f, 0xe2, 0x73, 0x8c, 0x29, 0x25, 0xcc, 0x61, 0xe6, 0xd3, 0x20, 0x3c, 0xb9, - 0x62, 0x8b, 0xc7, 0x18, 0x64, 0xf1, 0x98, 0x7f, 0x9f, 0x01, 0xcd, 0x8e, 0x71, 0x17, 0x26, 0xa5, - 0x13, 0x99, 0x94, 0x43, 0xde, 0xc1, 0x35, 0xab, 0x4c, 0xbf, 0x90, 0x8d, 0xdd, 0x58, 0xc8, 0xc6, - 0xe5, 0xd4, 0x38, 0x1e, 0x1e, 0xb1, 0xf1, 0x03, 0x03, 0xee, 0x0b, 0x91, 0x7b, 0x4d, 0x8c, 0x77, - 0xde, 0x61, 0x9e, 0x80, 0x71, 0x2b, 0xac, 0x26, 0xe6, 0x80, 0x8a, 0x52, 0xd2, 0x28, 0x62, 0x1d, - 0x2f, 0x74, 0x10, 0xcf, 0x1e, 0xd3, 0x41, 0x7c, 0xe4, 0x70, 0x07, 0x71, 0xf3, 0xe7, 0x19, 0x38, - 0xdb, 0xdb, 0x32, 0xb9, 0x36, 0x06, 0x7b, 0x81, 0x7f, 0x12, 0x26, 0x02, 0x51, 0x41, 0xdb, 0xe9, - 0x55, 0x8c, 0xdd, 0x86, 0x06, 0xc3, 0x11, 0x4c, 0x5a, 0xb3, 0xc6, 0x57, 0x65, 0xb5, 0xe6, 0xb6, - 0x65, 0x78, 0x81, 0xaa, 0xb9, 0xac, 0xc1, 0x70, 0x04, 0x53, 0x39, 0x6e, 0x8e, 0x9c, 0xb8, 0xe3, - 0x66, 0x15, 0x4e, 0x4b, 0x57, 0xb5, 0x0b, 0xae, 0xb7, 0xec, 0xb6, 0xda, 0x4d, 0x22, 0x02, 0x0c, - 0xa8, 0xb0, 0x67, 0x45, 0x95, 0xd3, 0x38, 0x09, 0x09, 0x27, 0xd7, 0x35, 0x7f, 0x90, 0x85, 0x53, - 0x61, 0xb7, 0x2f, 0xbb, 0x4e, 0xdd, 0x66, 0x0e, 0x7f, 0x4f, 0xc1, 0x48, 0xb0, 0xdf, 0x96, 0x9d, - 0xfd, 0xbf, 0xa4, 0x38, 0x1b, 0xfb, 0x6d, 0x3a, 0xda, 0x67, 0x12, 0xaa, 0x30, 0x23, 0x2f, 0xab, - 0x84, 0xd6, 0xd4, 0xea, 0xe0, 0x23, 0xf0, 0x78, 0x74, 0x36, 0xdf, 0xee, 0x16, 0x13, 0x42, 0x4c, - 0x17, 0x15, 0xa5, 0xe8, 0x9c, 0x47, 0x37, 0x61, 0xaa, 0x69, 0xf9, 0xc1, 0xb5, 0x76, 0xdd, 0x0a, - 0xc8, 0x86, 0xdd, 0x22, 0x62, 0xcd, 0x1d, 0xc5, 0x6b, 0x5f, 0xbd, 0x4a, 0xaf, 0x45, 0x28, 0xe1, - 0x18, 0x65, 0xb4, 0x0b, 0x88, 0x96, 0x6c, 0x78, 0x96, 0xe3, 0xf3, 0x56, 0x51, 0x7e, 0x47, 0x8f, - 0x12, 0x50, 0x97, 0xbc, 0xb5, 0x1e, 0x6a, 0x38, 0x81, 0x03, 0x7a, 0x08, 0x46, 0x3d, 0x62, 0xf9, - 0x62, 0x30, 0x0b, 0xe1, 0xfa, 0xc7, 0xac, 0x14, 0x0b, 0xa8, 0xbe, 0xa0, 0x46, 0xef, 0xb0, 0xa0, - 0x7e, 0x62, 0xc0, 0x54, 0x38, 0x4c, 0x77, 0x41, 0x49, 0xb6, 0xa2, 0x4a, 0xf2, 0x62, 0x5a, 0x5b, - 0x62, 0x1f, 0xbd, 0xf8, 0xd7, 0xa3, 0x7a, 0xfb, 0x98, 0xd7, 0xf6, 0xc7, 0xa1, 0x20, 0x57, 0xb5, - 0x3c, 0x7d, 0x0e, 0x79, 0x57, 0x8e, 0x9c, 0x4b, 0xb4, 0x68, 0x23, 0xc1, 0x04, 0x87, 0xfc, 0xa8, - 0x5a, 0xae, 0x0b, 0x95, 0x2b, 0xa6, 0xbd, 0x52, 0xcb, 0x52, 0x15, 0x27, 0xa9, 0x65, 0x59, 0x07, - 0x5d, 0x83, 0x33, 0x6d, 0xcf, 0x65, 0x11, 0xa8, 0x2b, 0xc4, 0xaa, 0x37, 0x6d, 0x87, 0x48, 0x83, - 0x04, 0x77, 0x8a, 0xb8, 0xef, 0xa0, 0x5b, 0x3c, 0x53, 0x49, 0x46, 0xc1, 0xfd, 0xea, 0x46, 0xa3, - 0xa6, 0x46, 0x06, 0x88, 0x9a, 0xfa, 0x0d, 0x65, 0xf6, 0x23, 0xbe, 0x88, 0x5d, 0xfa, 0x60, 0x5a, - 0x43, 0x99, 0xb0, 0xad, 0x87, 0x53, 0xaa, 0x24, 0x98, 0x62, 0xc5, 0xbe, 0xbf, 0x6d, 0x69, 0xf4, - 0x98, 0xb6, 0xa5, 0xd0, 0xf9, 0x7d, 0xec, 0x97, 0xe9, 0xfc, 0x9e, 0x7f, 0x4b, 0x39, 0xbf, 0xbf, - 0x9a, 0x83, 0x99, 0xf8, 0x09, 0xe4, 0xe4, 0x23, 0xc2, 0x7e, 0xdb, 0x80, 0x19, 0xb9, 0x7a, 0x38, - 0x4f, 0x22, 0x5f, 0x0d, 0xd6, 0x52, 0x5a, 0xb4, 0xfc, 0x2c, 0xa5, 0x62, 0x96, 0x37, 0x62, 0xdc, - 0x70, 0x0f, 0x7f, 0xf4, 0x02, 0x8c, 0x2b, 0xe3, 0xfa, 0xb1, 0xc2, 0xc3, 0xa6, 0xd9, 0x29, 0x2a, - 0x24, 0x81, 0x75, 0x7a, 0xe8, 0x55, 0x03, 0xa0, 0x26, 0xd5, 0x9c, 0x5c, 0x5d, 0x57, 0xd3, 0x5a, - 0x5d, 0x4a, 0x81, 0x86, 0x87, 0x65, 0x55, 0xe4, 0x63, 0x8d, 0x31, 0xfa, 0x02, 0x33, 0xab, 0xab, - 0xd3, 0x1d, 0x5d, 0x4f, 0xd9, 0xe1, 0x1d, 0x7b, 0x0f, 0x39, 0x98, 0x86, 0x47, 0x29, 0x0d, 0xe4, - 0xe3, 0x88, 0x10, 0xe6, 0x53, 0xa0, 0x5c, 0x31, 0xe9, 0xb6, 0xc5, 0x9c, 0x31, 0x2b, 0x56, 0xb0, - 0x2d, 0xa6, 0xa0, 0xda, 0xb6, 0x2e, 0x48, 0x00, 0x0e, 0x71, 0xcc, 0x8f, 0xc2, 0xd4, 0xb3, 0x9e, - 0xd5, 0xde, 0xb6, 0x99, 0xf9, 0x9a, 0xde, 0x93, 0xde, 0x09, 0x63, 0x56, 0xbd, 0x9e, 0x14, 0xf1, - 0x5f, 0xe2, 0xc5, 0x58, 0xc2, 0x07, 0xbb, 0x12, 0x7d, 0xdb, 0x00, 0x14, 0x3e, 0x01, 0xda, 0x4e, - 0x63, 0x9d, 0xde, 0xf6, 0xe9, 0xfd, 0x68, 0x9b, 0x95, 0x26, 0xdd, 0x8f, 0x2e, 0x2a, 0x08, 0xd6, - 0xb0, 0xd0, 0x4b, 0x30, 0xce, 0xff, 0x5d, 0x57, 0x97, 0xfd, 0xa1, 0xdd, 0xfb, 0xb9, 0x42, 0x61, - 0x32, 0xf1, 0x59, 0x78, 0x31, 0xe4, 0x80, 0x75, 0x76, 0xb4, 0xab, 0x56, 0x9d, 0xad, 0x66, 0xe7, - 0x56, 0x7d, 0x33, 0xec, 0xaa, 0xb6, 0xe7, 0x6e, 0xd9, 0x4d, 0x12, 0xef, 0xaa, 0x0a, 0x2f, 0xc6, - 0x12, 0x3e, 0x58, 0x57, 0x7d, 0xd3, 0x80, 0xb9, 0x55, 0x3f, 0xb0, 0xdd, 0x15, 0xe2, 0x07, 0x54, - 0xad, 0xd0, 0xcd, 0xa7, 0xd3, 0x1c, 0xc4, 0xab, 0x7a, 0x05, 0x66, 0xc4, 0x73, 0x64, 0x67, 0xd3, - 0x27, 0x81, 0x76, 0x8e, 0x57, 0xeb, 0x78, 0x39, 0x06, 0xc7, 0x3d, 0x35, 0x28, 0x15, 0xf1, 0x2e, - 0x19, 0x52, 0xc9, 0x46, 0xa9, 0x54, 0x63, 0x70, 0xdc, 0x53, 0xc3, 0xfc, 0x5e, 0x16, 0x4e, 0xb1, - 0x66, 0xc4, 0x22, 0x22, 0x3e, 0xd7, 0x2f, 0x22, 0x62, 0xc8, 0xa5, 0xcc, 0x78, 0x1d, 0x23, 0x1e, - 0xe2, 0xb7, 0x0c, 0x98, 0xae, 0x47, 0x7b, 0x3a, 0x1d, 0xf3, 0x4c, 0xd2, 0x18, 0x72, 0xef, 0xab, - 0x58, 0x21, 0x8e, 0xf3, 0x47, 0x5f, 0x34, 0x60, 0x3a, 0x2a, 0xa6, 0xdc, 0xdd, 0x4f, 0xa0, 0x93, - 0x94, 0xbb, 0x74, 0xb4, 0xdc, 0xc7, 0x71, 0x11, 0xcc, 0xef, 0x66, 0xc4, 0x90, 0x9e, 0x84, 0xbb, - 0x3f, 0xda, 0x83, 0x42, 0xd0, 0xf4, 0x79, 0xa1, 0x68, 0xed, 0x90, 0x37, 0xc2, 0x8d, 0xb5, 0x2a, - 0xf7, 0x04, 0x08, 0x0f, 0x6d, 0xa2, 0x84, 0x1e, 0x3e, 0x25, 0x2f, 0xc6, 0xb8, 0xd6, 0x16, 0x8c, - 0x53, 0xb9, 0x8a, 0x6e, 0x2c, 0x57, 0xe2, 0x8c, 0x45, 0x09, 0x65, 0x2c, 0x79, 0x99, 0x5f, 0x31, - 0xa0, 0x70, 0xc9, 0x95, 0xfb, 0xc8, 0x87, 0x53, 0x30, 0xf4, 0xa8, 0xf3, 0xa0, 0x7a, 0x71, 0x0c, - 0xaf, 0x18, 0xcf, 0x44, 0xcc, 0x3c, 0xf7, 0x6b, 0xb4, 0x17, 0x59, 0x36, 0x23, 0x4a, 0xea, 0x92, - 0xbb, 0xd9, 0xd7, 0x8a, 0xf8, 0x07, 0x39, 0x98, 0x7c, 0xce, 0xda, 0x27, 0x4e, 0x60, 0x1d, 0x5d, - 0x49, 0x3c, 0x01, 0xe3, 0x56, 0x9b, 0x3d, 0x69, 0x69, 0x67, 0xfc, 0xd0, 0x72, 0x12, 0x82, 0xb0, - 0x8e, 0x17, 0x6e, 0x68, 0x3c, 0xb9, 0x4a, 0xd2, 0x56, 0xb4, 0x1c, 0x83, 0xe3, 0x9e, 0x1a, 0xe8, - 0x12, 0x20, 0x11, 0x2a, 0x5a, 0xaa, 0xd5, 0xdc, 0x8e, 0xc3, 0xb7, 0x34, 0x6e, 0x54, 0x51, 0x97, - 0xcd, 0xf5, 0x1e, 0x0c, 0x9c, 0x50, 0x0b, 0x7d, 0x08, 0xe6, 0x6b, 0x8c, 0xb2, 0xb8, 0x7a, 0xe8, - 0x14, 0xf9, 0xf5, 0x53, 0xb9, 0xfc, 0x2f, 0xf7, 0xc1, 0xc3, 0x7d, 0x29, 0x50, 0x49, 0xfd, 0xc0, - 0xf5, 0xac, 0x06, 0xd1, 0xe9, 0x8e, 0x46, 0x25, 0xad, 0xf6, 0x60, 0xe0, 0x84, 0x5a, 0xe8, 0x93, - 0x50, 0x08, 0xb6, 0x3d, 0xe2, 0x6f, 0xbb, 0xcd, 0xba, 0x70, 0x41, 0x18, 0xd2, 0xd2, 0x26, 0x46, - 0x7f, 0x43, 0x52, 0xd5, 0xa6, 0xb7, 0x2c, 0xc2, 0x21, 0x4f, 0xe4, 0xc1, 0xa8, 0x5f, 0x73, 0xdb, - 0xc4, 0x17, 0x47, 0xf6, 0x4b, 0xa9, 0x70, 0x67, 0x96, 0x23, 0xcd, 0xc6, 0xc7, 0x38, 0x60, 0xc1, - 0xc9, 0xfc, 0x56, 0x06, 0x26, 0x74, 0xc4, 0x01, 0xf6, 0xa6, 0x4f, 0x1b, 0x30, 0x51, 0x73, 0x9d, - 0xc0, 0x73, 0x9b, 0xdc, 0x7e, 0x95, 0xce, 0x89, 0x82, 0x92, 0x5a, 0x21, 0x81, 0x65, 0x37, 0x35, - 0x53, 0x98, 0xc6, 0x06, 0x47, 0x98, 0xa2, 0xcf, 0x1a, 0x30, 0x1d, 0x7a, 0xac, 0x85, 0x86, 0xb4, - 0x54, 0x05, 0x51, 0x5b, 0xfd, 0xf9, 0x28, 0x27, 0x1c, 0x67, 0x6d, 0x6e, 0xc2, 0x4c, 0x7c, 0xb4, - 0x69, 0x57, 0xb6, 0x2d, 0xb1, 0xd6, 0xb3, 0x61, 0x57, 0x56, 0x2c, 0xdf, 0xc7, 0x0c, 0x82, 0x1e, - 0x81, 0x7c, 0xcb, 0xf2, 0x1a, 0xb6, 0x63, 0x35, 0x59, 0x2f, 0x66, 0xb5, 0x0d, 0x49, 0x94, 0x63, - 0x85, 0x61, 0xbe, 0x1b, 0x26, 0xd6, 0x2d, 0xa7, 0x41, 0xea, 0x62, 0x1f, 0xbe, 0x73, 0xc0, 0xd9, - 0x4f, 0x47, 0x60, 0x5c, 0xbb, 0x9b, 0x9d, 0xfc, 0x3d, 0x2b, 0x92, 0xef, 0x22, 0x9b, 0x62, 0xbe, - 0x8b, 0xe7, 0x01, 0xb6, 0x6c, 0xc7, 0xf6, 0xb7, 0x8f, 0x99, 0x49, 0x83, 0x3d, 0xd1, 0x5e, 0x50, - 0x14, 0xb0, 0x46, 0x2d, 0x7c, 0x07, 0xcb, 0x1d, 0x92, 0x5f, 0xe8, 0x55, 0x43, 0x53, 0x37, 0xa3, - 0x69, 0xbc, 0xfb, 0x6b, 0x03, 0xb3, 0x28, 0xd5, 0xcf, 0x79, 0x27, 0xf0, 0xf6, 0x0f, 0xd5, 0x4a, - 0x1b, 0x90, 0xf7, 0x88, 0xdf, 0x69, 0xd1, 0x1b, 0xe3, 0xd8, 0x91, 0xbb, 0x81, 0x79, 0x60, 0x60, - 0x51, 0x1f, 0x2b, 0x4a, 0x0b, 0x4f, 0xc1, 0x64, 0x44, 0x04, 0x34, 0x03, 0xd9, 0x1d, 0xb2, 0xcf, - 0xe7, 0x09, 0xa6, 0x3f, 0xd1, 0x5c, 0xe4, 0xb5, 0x50, 0x74, 0xcb, 0xfb, 0x32, 0x4f, 0x1a, 0xa6, - 0x0b, 0x89, 0x06, 0x80, 0xe3, 0x3c, 0xe6, 0xd0, 0xb1, 0x68, 0x6a, 0xa9, 0x34, 0xd4, 0x58, 0x70, - 0x3f, 0x1b, 0x0e, 0x33, 0x7f, 0x3e, 0x0a, 0xe2, 0x29, 0x7b, 0x80, 0xed, 0x4a, 0x7f, 0xc1, 0xca, - 0x1c, 0xe3, 0x05, 0xeb, 0x12, 0x4c, 0xd8, 0x8e, 0x1d, 0xd8, 0x56, 0x93, 0x19, 0x77, 0x84, 0x3a, - 0x95, 0x8e, 0xc8, 0x13, 0xab, 0x1a, 0x2c, 0x81, 0x4e, 0xa4, 0x2e, 0xba, 0x0a, 0x39, 0xa6, 0x6f, - 0xc4, 0x04, 0x3e, 0xfa, 0x7b, 0x3b, 0x73, 0xb5, 0xe0, 0xd1, 0x49, 0x9c, 0x12, 0xbb, 0x7c, 0xf0, - 0x5c, 0x22, 0xea, 0xfa, 0x2d, 0xe6, 0x71, 0x78, 0xf9, 0x88, 0xc1, 0x71, 0x4f, 0x0d, 0x4a, 0x65, - 0xcb, 0xb2, 0x9b, 0x1d, 0x8f, 0x84, 0x54, 0x46, 0xa3, 0x54, 0x2e, 0xc4, 0xe0, 0xb8, 0xa7, 0x06, - 0xda, 0x82, 0x09, 0x51, 0xc6, 0xbd, 0xa7, 0xc6, 0x8e, 0xd9, 0x4a, 0xe6, 0x25, 0x77, 0x41, 0xa3, - 0x84, 0x23, 0x74, 0x51, 0x07, 0x66, 0x6d, 0xa7, 0xe6, 0x3a, 0xb5, 0x66, 0xc7, 0xb7, 0x77, 0x49, - 0x18, 0x1a, 0x74, 0x1c, 0x66, 0xa7, 0x0f, 0xba, 0xc5, 0xd9, 0xd5, 0x38, 0x39, 0xdc, 0xcb, 0x01, - 0xbd, 0x62, 0xc0, 0xe9, 0x9a, 0xeb, 0xf8, 0x2c, 0x38, 0x7f, 0x97, 0x9c, 0xf7, 0x3c, 0xd7, 0xe3, - 0xbc, 0x0b, 0xc7, 0xe4, 0xcd, 0x6c, 0x8a, 0xcb, 0x49, 0x24, 0x71, 0x32, 0x27, 0xf4, 0x22, 0xe4, - 0xdb, 0x9e, 0xbb, 0x6b, 0xd7, 0x89, 0x27, 0x3c, 0xf1, 0xd6, 0xd2, 0x48, 0x16, 0x52, 0x11, 0x34, - 0xc3, 0xad, 0x47, 0x96, 0x60, 0xc5, 0xcf, 0x7c, 0xa3, 0x00, 0x53, 0x51, 0x74, 0xf4, 0x09, 0x80, - 0xb6, 0xe7, 0xb6, 0x48, 0xb0, 0x4d, 0x54, 0x88, 0xc7, 0xe5, 0x61, 0x73, 0x52, 0x48, 0x7a, 0xd2, - 0x7b, 0x85, 0x6e, 0x17, 0x61, 0x29, 0xd6, 0x38, 0x22, 0x0f, 0xc6, 0x76, 0xb8, 0xda, 0x15, 0xa7, - 0x90, 0xe7, 0x52, 0x39, 0x33, 0x09, 0xce, 0x2c, 0x36, 0x41, 0x14, 0x61, 0xc9, 0x08, 0x6d, 0x42, - 0x76, 0x8f, 0x6c, 0xa6, 0x13, 0x10, 0x7d, 0x83, 0x88, 0xdb, 0x4c, 0x79, 0xec, 0xa0, 0x5b, 0xcc, - 0xde, 0x20, 0x9b, 0x98, 0x12, 0xa7, 0xed, 0xaa, 0xf3, 0x77, 0x78, 0xb1, 0x55, 0x0c, 0xd9, 0xae, - 0xc8, 0xa3, 0x3e, 0x6f, 0x97, 0x28, 0xc2, 0x92, 0x11, 0x7a, 0x11, 0x0a, 0x7b, 0xd6, 0x2e, 0xd9, - 0xf2, 0x5c, 0x27, 0x10, 0x2e, 0x53, 0x43, 0x7a, 0xfd, 0xdf, 0x90, 0xe4, 0x04, 0x5f, 0xa6, 0xde, - 0x55, 0x21, 0x0e, 0xd9, 0xa1, 0x5d, 0xc8, 0x3b, 0x64, 0x0f, 0x93, 0xa6, 0x5d, 0x4b, 0xc7, 0xcb, - 0xfe, 0xb2, 0xa0, 0x26, 0x38, 0x33, 0xbd, 0x27, 0xcb, 0xb0, 0xe2, 0x45, 0xc7, 0xf2, 0xa6, 0xbb, - 0x29, 0x36, 0xaa, 0x21, 0xc7, 0x52, 0xdd, 0x4c, 0xf9, 0x58, 0x5e, 0x72, 0x37, 0x31, 0x25, 0x4e, - 0xd7, 0x48, 0x4d, 0xf9, 0xeb, 0x88, 0x6d, 0xea, 0x72, 0xba, 0x7e, 0x4a, 0x7c, 0x8d, 0x84, 0xa5, - 0x58, 0xe3, 0x48, 0xfb, 0xb6, 0x21, 0x8c, 0x95, 0x62, 0xa3, 0x1a, 0xb2, 0x6f, 0xa3, 0xa6, 0x4f, - 0xde, 0xb7, 0xb2, 0x0c, 0x2b, 0x5e, 0x94, 0xaf, 0x2d, 0x2c, 0x7f, 0xe9, 0x6c, 0x55, 0x51, 0x3b, - 0x22, 0xe7, 0x2b, 0xcb, 0xb0, 0xe2, 0x65, 0x7e, 0x65, 0x14, 0x26, 0xf4, 0xa4, 0x6c, 0x03, 0x9c, - 0x11, 0xd4, 0xb9, 0x38, 0x73, 0x94, 0x73, 0x31, 0xbd, 0x08, 0x69, 0x6f, 0x1c, 0xd2, 0x08, 0xb3, - 0x9a, 0xda, 0xb1, 0x30, 0xbc, 0x08, 0x69, 0x85, 0x3e, 0x8e, 0x30, 0x3d, 0x82, 0xdb, 0x03, 0x3d, - 0x5c, 0xf1, 0xe3, 0x47, 0x2e, 0x7a, 0xb8, 0x8a, 0x1c, 0x28, 0x1e, 0x05, 0x08, 0x93, 0x93, 0x89, - 0xb7, 0x2f, 0x75, 0x6a, 0xd3, 0x92, 0xa6, 0x69, 0x58, 0xe8, 0x21, 0x18, 0xa5, 0x0a, 0x9a, 0xd4, - 0x45, 0xdc, 0xaf, 0xba, 0x6d, 0x5e, 0x60, 0xa5, 0x58, 0x40, 0xd1, 0x93, 0xf4, 0x2c, 0x15, 0xaa, - 0x55, 0x11, 0xce, 0x3b, 0x17, 0x9e, 0xa5, 0x42, 0x18, 0x8e, 0x60, 0x52, 0xd1, 0x09, 0xd5, 0x82, - 0x6c, 0x06, 0x6b, 0xa2, 0x33, 0xd5, 0x88, 0x39, 0x8c, 0x59, 0x3f, 0x62, 0x5a, 0x93, 0xcd, 0xbc, - 0x9c, 0x66, 0xfd, 0x88, 0xc1, 0x71, 0x4f, 0x0d, 0xda, 0x18, 0xf1, 0x6c, 0x37, 0xce, 0xbd, 0x3b, - 0xfb, 0x3c, 0xb8, 0xbd, 0xa6, 0xdf, 0x08, 0x26, 0xd8, 0xd0, 0xbf, 0x3f, 0xbd, 0x04, 0x83, 0x83, - 0x5f, 0x09, 0x86, 0x3b, 0xbc, 0x7f, 0x14, 0xa6, 0xa2, 0x7b, 0x65, 0xea, 0xf6, 0xf9, 0xbf, 0xc9, - 0xc2, 0xa9, 0xcb, 0x0d, 0xdb, 0x89, 0x27, 0x1c, 0x4a, 0x4a, 0xfc, 0x6b, 0x1c, 0x35, 0xf1, 0x6f, - 0x18, 0x40, 0x24, 0x32, 0x2b, 0x27, 0x07, 0x10, 0xc9, 0xb4, 0xcb, 0x51, 0x5c, 0xf4, 0x13, 0x03, - 0xee, 0xb7, 0xea, 0xfc, 0xf4, 0x6a, 0x35, 0x45, 0x69, 0xc8, 0x54, 0xae, 0x68, 0x7f, 0x48, 0x5d, - 0xd4, 0xdb, 0xf8, 0xc5, 0xd2, 0x21, 0x5c, 0xf9, 0x88, 0xbf, 0x43, 0xb4, 0xe0, 0xfe, 0xc3, 0x50, - 0xf1, 0xa1, 0xe2, 0x2f, 0x5c, 0x81, 0xb7, 0xdf, 0x91, 0xd1, 0x91, 0x66, 0xcb, 0xa7, 0x0d, 0x28, - 0x70, 0xf3, 0x29, 0x26, 0x5b, 0x74, 0xab, 0xb0, 0xda, 0xf6, 0x75, 0xe2, 0xf9, 0x32, 0x23, 0x99, - 0x76, 0xc1, 0x2b, 0x55, 0x56, 0x05, 0x04, 0x6b, 0x58, 0x74, 0x33, 0xde, 0xb1, 0x9d, 0xba, 0x18, - 0x26, 0xb5, 0x19, 0x3f, 0x67, 0x3b, 0x75, 0xcc, 0x20, 0x6a, 0xbb, 0xce, 0xf6, 0x35, 0x6b, 0xbc, - 0x61, 0xc0, 0x14, 0x8b, 0x9a, 0x0c, 0xaf, 0x1e, 0x4f, 0x28, 0x9f, 0x16, 0x2e, 0xc6, 0xd9, 0xa8, - 0x4f, 0xcb, 0xed, 0x6e, 0x71, 0x9c, 0xc7, 0x59, 0x46, 0x5d, 0x5c, 0x3e, 0x28, 0xec, 0x15, 0xcc, - 0xf3, 0x26, 0x73, 0xe4, 0xeb, 0xb4, 0xb2, 0xe7, 0x55, 0x25, 0x11, 0x1c, 0xd2, 0x33, 0x5f, 0x82, - 0x09, 0x3d, 0xfc, 0x01, 0x3d, 0x01, 0xe3, 0x6d, 0xdb, 0x69, 0x44, 0xc3, 0xe4, 0x94, 0x4d, 0xb7, - 0x12, 0x82, 0xb0, 0x8e, 0xc7, 0xaa, 0xb9, 0x61, 0xb5, 0x98, 0x29, 0xb8, 0xe2, 0xea, 0xd5, 0xc2, - 0x3f, 0xe6, 0x9f, 0x66, 0xe1, 0x54, 0x42, 0x98, 0x0d, 0x7a, 0xd5, 0x80, 0x51, 0xe6, 0xa5, 0x2f, - 0xbd, 0x56, 0x5e, 0x48, 0x3d, 0x94, 0x67, 0x91, 0x05, 0x03, 0x88, 0x79, 0xac, 0xb6, 0x4f, 0x5e, - 0x88, 0x05, 0x73, 0xf4, 0xbb, 0x06, 0x8c, 0x5b, 0xda, 0x52, 0xe3, 0x8e, 0x3c, 0x9b, 0xe9, 0x0b, - 0xd3, 0xb3, 0xb2, 0x34, 0x07, 0xc4, 0x70, 0x21, 0xe9, 0xb2, 0x2c, 0xbc, 0x17, 0xc6, 0xb5, 0x26, - 0x1c, 0x65, 0x85, 0x2c, 0x3c, 0x03, 0x33, 0x43, 0xad, 0xb0, 0x0f, 0xc0, 0x51, 0x13, 0xec, 0x51, - 0x85, 0xb5, 0xa7, 0x87, 0x32, 0xab, 0x1e, 0x17, 0xb1, 0xcc, 0x02, 0x6a, 0x6e, 0xc2, 0x4c, 0xfc, - 0x72, 0x95, 0xfa, 0xbb, 0xf5, 0xbb, 0xe1, 0x88, 0x29, 0xf1, 0xcc, 0xf3, 0x80, 0xb0, 0xdb, 0x6c, - 0x6e, 0x5a, 0xb5, 0x9d, 0x1b, 0xb6, 0x53, 0x77, 0xf7, 0xd8, 0x5a, 0x59, 0x82, 0x82, 0x27, 0xa2, - 0xa8, 0x7c, 0xd1, 0x2c, 0xb5, 0xd8, 0x64, 0x78, 0x95, 0x8f, 0x43, 0x1c, 0xf3, 0xbb, 0x19, 0x18, - 0x13, 0x21, 0x7f, 0x77, 0xc1, 0x05, 0x78, 0x27, 0xf2, 0x36, 0xb4, 0x9a, 0x4a, 0xa4, 0x62, 0x5f, - 0xff, 0x5f, 0x3f, 0xe6, 0xff, 0xfb, 0x5c, 0x3a, 0xec, 0x0e, 0x77, 0xfe, 0x7d, 0x63, 0x04, 0xa6, - 0x63, 0x21, 0x94, 0xf4, 0xc4, 0xd3, 0xe3, 0xf3, 0x76, 0x2d, 0xd5, 0x28, 0x4d, 0xe5, 0x9e, 0x7e, - 0xb8, 0xfb, 0x9b, 0x1f, 0x49, 0x60, 0x7a, 0x35, 0xb5, 0xdc, 0xe7, 0xbf, 0xca, 0x65, 0x7a, 0x54, - 0x77, 0xae, 0x7f, 0x32, 0xe0, 0xde, 0xbe, 0x91, 0xb6, 0x2c, 0x51, 0x8b, 0x17, 0x85, 0x8a, 0x05, - 0x99, 0x72, 0x3e, 0x01, 0xf5, 0x50, 0x13, 0xcf, 0xad, 0x11, 0x67, 0x8f, 0x1e, 0x87, 0x09, 0xa6, - 0xa1, 0xe9, 0xd6, 0x14, 0x90, 0xb6, 0xb0, 0x33, 0x33, 0x8b, 0x63, 0x55, 0x2b, 0xc7, 0x11, 0x2c, - 0xf3, 0xcb, 0x06, 0xcc, 0xf7, 0x4b, 0xdb, 0x31, 0xc0, 0xfd, 0xf2, 0xff, 0xc5, 0x7c, 0x94, 0x8b, - 0x3d, 0x3e, 0xca, 0xb1, 0x1b, 0xa6, 0x74, 0x47, 0xd6, 0x2e, 0x77, 0xd9, 0x3b, 0xb8, 0xe0, 0x7e, - 0xce, 0x80, 0x33, 0x7d, 0x56, 0x53, 0x8f, 0xaf, 0xba, 0x71, 0x6c, 0x5f, 0xf5, 0xcc, 0xa0, 0xbe, - 0xea, 0xe6, 0xf7, 0xb3, 0x30, 0x23, 0xe4, 0x09, 0x8f, 0x69, 0x4f, 0x46, 0x3c, 0xbd, 0xdf, 0x11, - 0xf3, 0xf4, 0x9e, 0x8b, 0xe3, 0xff, 0xca, 0xcd, 0xfb, 0xad, 0xe5, 0xe6, 0xfd, 0x8b, 0x0c, 0x9c, - 0x4e, 0xcc, 0x26, 0x82, 0x3e, 0x93, 0xa0, 0x1a, 0x6e, 0xa4, 0x9c, 0xb6, 0x64, 0x40, 0xe5, 0x30, - 0xac, 0x6f, 0xf4, 0x17, 0x75, 0x9f, 0x64, 0xbe, 0xd5, 0x6f, 0x9d, 0x40, 0x02, 0x96, 0x23, 0xba, - 0x27, 0x9b, 0xbf, 0x99, 0x85, 0x87, 0x07, 0x25, 0xf4, 0x16, 0x0d, 0x5f, 0xf1, 0x23, 0xe1, 0x2b, - 0x77, 0x49, 0x6d, 0x9f, 0x48, 0x24, 0xcb, 0x57, 0xb2, 0x4a, 0xed, 0xf5, 0xce, 0xcf, 0x81, 0x1e, - 0x25, 0xc7, 0xe8, 0xd1, 0x4e, 0xe6, 0x18, 0x0d, 0xb7, 0xc2, 0xb1, 0x2a, 0x2f, 0xbe, 0xdd, 0x2d, - 0xce, 0x86, 0x31, 0xed, 0xa2, 0x10, 0xcb, 0x4a, 0xe8, 0x61, 0xc8, 0x7b, 0x1c, 0x2a, 0x1d, 0xf6, - 0xc5, 0xcb, 0x2e, 0x2f, 0xc3, 0x0a, 0x8a, 0x3e, 0xa9, 0x9d, 0x85, 0x47, 0x4e, 0x2a, 0x75, 0xc3, - 0x61, 0x0f, 0xd6, 0x2f, 0x40, 0xde, 0x97, 0xd9, 0x42, 0xf9, 0xab, 0xc2, 0x63, 0x03, 0xc6, 0x81, - 0xd0, 0x1b, 0x98, 0x4c, 0x1d, 0xca, 0xdb, 0xa7, 0x12, 0x8b, 0x2a, 0x92, 0xc8, 0x54, 0x97, 0x1f, - 0x6e, 0xaa, 0x84, 0x84, 0x8b, 0xcf, 0x0f, 0x0c, 0x18, 0x17, 0xa3, 0x75, 0x17, 0x42, 0x53, 0x6e, - 0x46, 0x43, 0x53, 0xce, 0xa7, 0xb2, 0x77, 0xf4, 0x89, 0x4b, 0xb9, 0x09, 0x13, 0x7a, 0x42, 0x29, - 0xf4, 0xbc, 0xb6, 0xf7, 0x19, 0xc3, 0xa4, 0x68, 0x91, 0xbb, 0x63, 0xb8, 0x2f, 0x9a, 0x5f, 0x2d, - 0xa8, 0x5e, 0x64, 0x57, 0x34, 0x7d, 0x0e, 0x1a, 0x87, 0xce, 0x41, 0x7d, 0x0a, 0x64, 0xd2, 0x9f, - 0x02, 0x57, 0x21, 0x2f, 0x37, 0x28, 0xa1, 0xc6, 0x1f, 0xd4, 0x9d, 0xf5, 0xe8, 0x59, 0x80, 0x12, - 0xd3, 0x26, 0x2e, 0xbb, 0x6a, 0xa9, 0x31, 0x54, 0x1b, 0xa7, 0x22, 0x83, 0x5e, 0x84, 0xf1, 0x3d, - 0xd7, 0xdb, 0x69, 0xba, 0x16, 0xcb, 0x03, 0x0c, 0x69, 0xbc, 0x0f, 0x29, 0xbb, 0x19, 0xf7, 0x98, - 0xbe, 0x11, 0xd2, 0xc7, 0x3a, 0x33, 0x54, 0x82, 0xe9, 0x96, 0xed, 0x60, 0x62, 0xd5, 0x55, 0x04, - 0xca, 0x08, 0x4f, 0x54, 0x2a, 0x0f, 0xb9, 0xeb, 0x51, 0x30, 0x8e, 0xe3, 0xa3, 0xcf, 0x1a, 0x30, - 0xe5, 0x45, 0x2e, 0xd5, 0x22, 0x1b, 0x61, 0x65, 0xf8, 0xc9, 0x18, 0xbd, 0xa8, 0x73, 0x97, 0xe1, - 0x68, 0x39, 0x8e, 0xf1, 0x46, 0x1f, 0x87, 0xbc, 0x2f, 0xb2, 0x45, 0xa5, 0xf3, 0xb0, 0xa8, 0xae, - 0xb0, 0x9c, 0x68, 0x38, 0x94, 0xb2, 0x04, 0x2b, 0x86, 0x68, 0x0d, 0xe6, 0xa4, 0x95, 0x20, 0xf2, - 0xad, 0x96, 0xd1, 0x30, 0xb9, 0x08, 0x4e, 0x80, 0xe3, 0xc4, 0x5a, 0xf4, 0x50, 0xc5, 0x12, 0xb5, - 0xf1, 0x97, 0x0e, 0xed, 0x71, 0x80, 0xad, 0xbf, 0x3a, 0x16, 0xd0, 0xc3, 0x02, 0xac, 0xf2, 0x43, - 0x04, 0x58, 0x55, 0xe1, 0x74, 0x1c, 0xc4, 0xb2, 0xc6, 0xb0, 0x44, 0x35, 0x9a, 0x32, 0xab, 0x24, - 0x21, 0xe1, 0xe4, 0xba, 0xe8, 0x06, 0x14, 0x3c, 0xc2, 0xae, 0x3b, 0x25, 0xe9, 0xca, 0x70, 0x64, - 0xa7, 0x2d, 0x2c, 0x09, 0xe0, 0x90, 0x16, 0x1d, 0x77, 0x2b, 0x9a, 0x3a, 0xf4, 0x6a, 0x8a, 0x5f, - 0x9b, 0x13, 0x63, 0xdf, 0x27, 0x9b, 0x93, 0xf9, 0xe6, 0x14, 0x4c, 0x46, 0x4c, 0x1d, 0xe8, 0x41, - 0xc8, 0xb1, 0x34, 0x3a, 0x6c, 0xb7, 0xca, 0x87, 0x3b, 0x2a, 0xef, 0x1c, 0x0e, 0x43, 0x9f, 0x37, - 0x60, 0xba, 0x1d, 0xb1, 0x2d, 0xcb, 0x8d, 0x7c, 0xc8, 0xd7, 0xcb, 0xa8, 0xc1, 0x5a, 0x4b, 0xba, - 0x1d, 0x65, 0x86, 0xe3, 0xdc, 0xe9, 0x7e, 0x20, 0x3c, 0x1f, 0x9b, 0xc4, 0x63, 0xd8, 0xe2, 0xc8, - 0xa5, 0x48, 0x2c, 0x47, 0xc1, 0x38, 0x8e, 0x4f, 0x47, 0x98, 0xb5, 0x6e, 0x98, 0xcf, 0x50, 0x95, - 0x24, 0x01, 0x1c, 0xd2, 0x42, 0xcf, 0xc0, 0x94, 0xc8, 0x18, 0x59, 0x71, 0xeb, 0x17, 0x2d, 0x7f, - 0x5b, 0xdc, 0x35, 0xd4, 0xdd, 0x68, 0x39, 0x02, 0xc5, 0x31, 0x6c, 0xd6, 0xb6, 0x30, 0x2d, 0x27, - 0x23, 0x30, 0x1a, 0xcd, 0x49, 0xbe, 0x1c, 0x05, 0xe3, 0x38, 0x3e, 0x7a, 0x44, 0x53, 0x43, 0xfc, - 0xf5, 0x51, 0xed, 0x06, 0x09, 0xaa, 0xa8, 0x04, 0xd3, 0x1d, 0x76, 0x35, 0xab, 0x4b, 0xa0, 0x58, - 0x8f, 0x8a, 0xe1, 0xb5, 0x28, 0x18, 0xc7, 0xf1, 0xd1, 0x53, 0x30, 0xe9, 0xd1, 0xcd, 0x56, 0x11, - 0xe0, 0x4f, 0x92, 0xea, 0xc5, 0x09, 0xeb, 0x40, 0x1c, 0xc5, 0x45, 0xcf, 0xc2, 0x6c, 0x98, 0x60, - 0x4d, 0x12, 0xe0, 0x6f, 0x94, 0x2a, 0xdb, 0x4f, 0x29, 0x8e, 0x80, 0x7b, 0xeb, 0xa0, 0x5f, 0x83, - 0x19, 0xad, 0x27, 0x56, 0x9d, 0x3a, 0xb9, 0x25, 0x92, 0x60, 0xb1, 0xcf, 0x47, 0x2c, 0xc7, 0x60, - 0xb8, 0x07, 0x1b, 0xbd, 0x0f, 0xa6, 0x6a, 0x6e, 0xb3, 0xc9, 0xf6, 0x38, 0x9e, 0x0f, 0x9b, 0x67, - 0xbb, 0xe2, 0x79, 0xc1, 0x22, 0x10, 0x1c, 0xc3, 0x44, 0x97, 0x00, 0xb9, 0x9b, 0x3e, 0xf1, 0x76, - 0x49, 0xfd, 0x59, 0xfe, 0x61, 0x5b, 0x7a, 0xe2, 0x98, 0x8c, 0xfa, 0x5d, 0x5f, 0xe9, 0xc1, 0xc0, - 0x09, 0xb5, 0x58, 0xb2, 0x20, 0x2d, 0x4e, 0x6d, 0x2a, 0x8d, 0x4f, 0x32, 0xc5, 0x0d, 0x09, 0x77, - 0x0c, 0x52, 0xf3, 0x60, 0x94, 0xbb, 0xc1, 0xa7, 0x93, 0xf6, 0x4a, 0x4f, 0x8d, 0x1b, 0xea, 0x08, - 0x5e, 0x8a, 0x05, 0x27, 0xf4, 0x09, 0x28, 0x6c, 0xca, 0x3c, 0xe9, 0x2c, 0xd7, 0xd5, 0xd0, 0x7a, - 0x31, 0x96, 0xf2, 0x3f, 0xbc, 0x28, 0x2b, 0x00, 0x0e, 0x59, 0xa2, 0x87, 0x60, 0xfc, 0x62, 0xa5, - 0xa4, 0x66, 0xe1, 0x2c, 0x1b, 0xfd, 0x11, 0x5a, 0x05, 0xeb, 0x00, 0xba, 0xc2, 0xd4, 0xf1, 0x0d, - 0xb1, 0x21, 0x0e, 0xf5, 0x6d, 0xef, 0x69, 0x8c, 0x62, 0xb3, 0x47, 0x56, 0x5c, 0x9d, 0x3f, 0x15, - 0xc3, 0x16, 0xe5, 0x58, 0x61, 0xa0, 0x17, 0x60, 0x5c, 0xe8, 0x0b, 0xb6, 0x37, 0xcd, 0x1d, 0x2f, - 0x06, 0x12, 0x87, 0x24, 0xb0, 0x4e, 0x8f, 0xbd, 0x9d, 0xb1, 0xf4, 0xd1, 0xe4, 0x42, 0xa7, 0xd9, - 0x9c, 0x3f, 0xcd, 0xf6, 0xcd, 0xf0, 0xed, 0x2c, 0x04, 0x61, 0x1d, 0x0f, 0x3d, 0x26, 0xfd, 0x41, - 0xde, 0x16, 0x79, 0x4c, 0x54, 0xfe, 0x20, 0xea, 0xd0, 0xdd, 0xc7, 0x4d, 0xfa, 0xcc, 0x1d, 0x1c, - 0x31, 0x36, 0x61, 0x41, 0x9e, 0xf8, 0x7a, 0x17, 0xc9, 0xfc, 0x7c, 0xc4, 0x68, 0xb1, 0x70, 0xa3, - 0x2f, 0x26, 0x3e, 0x84, 0x0a, 0xda, 0x84, 0xac, 0xd5, 0xdc, 0x9c, 0xbf, 0x37, 0x8d, 0xa3, 0xab, - 0xfa, 0x50, 0x35, 0x77, 0x6d, 0x2a, 0xad, 0x95, 0x31, 0x25, 0x6e, 0xbe, 0x92, 0x51, 0x8f, 0x04, - 0x2a, 0x1d, 0xe8, 0x4b, 0xfa, 0xac, 0x36, 0xd2, 0xf8, 0x10, 0x6b, 0xcf, 0xc7, 0x04, 0xb8, 0x42, - 0x4a, 0x9c, 0xd3, 0x6d, 0xb5, 0x8e, 0x53, 0xc9, 0xce, 0x12, 0x4d, 0x75, 0xca, 0x2f, 0x97, 0xd1, - 0x55, 0x6c, 0xfe, 0x38, 0xaf, 0x6c, 0x62, 0x31, 0x07, 0x07, 0x0f, 0x72, 0xb6, 0x1f, 0xd8, 0x6e, - 0x8a, 0xf1, 0x7a, 0xb1, 0x1c, 0xa1, 0xcc, 0x1d, 0x98, 0x01, 0x30, 0x67, 0x45, 0x79, 0x3a, 0x0d, - 0xdb, 0xb9, 0x25, 0x9a, 0x7f, 0x35, 0x75, 0xcf, 0x05, 0xce, 0x93, 0x01, 0x30, 0x67, 0x85, 0x6e, - 0xf2, 0x99, 0x96, 0xce, 0x47, 0x77, 0xe3, 0xdf, 0xd2, 0x8e, 0xce, 0x38, 0xca, 0xcb, 0x6f, 0xd9, - 0xe2, 0x0c, 0x33, 0x24, 0xaf, 0xea, 0xfa, 0x6a, 0x12, 0xaf, 0xea, 0xfa, 0x2a, 0xa6, 0x4c, 0xd0, - 0x6b, 0x06, 0x80, 0xa5, 0x3e, 0x2a, 0x9d, 0xce, 0x97, 0x37, 0xfa, 0x7d, 0xa4, 0x9a, 0x7b, 0xf0, - 0x85, 0x50, 0xac, 0x71, 0x46, 0x2f, 0xc2, 0x98, 0xc5, 0xbf, 0x1b, 0x24, 0x9c, 0x23, 0xd3, 0xf9, - 0x18, 0x56, 0x4c, 0x02, 0xe6, 0x15, 0x2a, 0x40, 0x58, 0x32, 0xa4, 0xbc, 0x03, 0xcf, 0x22, 0x5b, - 0xf6, 0x8e, 0xf0, 0x92, 0xac, 0x0e, 0x9d, 0xfe, 0x9b, 0x12, 0x4b, 0xe2, 0x2d, 0x40, 0x58, 0x32, - 0xe4, 0xdf, 0x71, 0xb5, 0x1c, 0x4b, 0x85, 0xbc, 0xa4, 0x13, 0x18, 0xa5, 0x07, 0xd1, 0x68, 0xdf, - 0x71, 0xd5, 0x19, 0xe1, 0x28, 0x5f, 0xb4, 0x0b, 0xa3, 0x16, 0xfb, 0xa2, 0x99, 0xb8, 0x1f, 0xe1, - 0x34, 0xbe, 0x8e, 0x16, 0xeb, 0x03, 0xb6, 0xb9, 0x88, 0xef, 0xa6, 0x09, 0x6e, 0xe6, 0xcf, 0xb2, - 0x00, 0x4c, 0x04, 0x1e, 0xfe, 0xdd, 0x62, 0x29, 0x03, 0xb7, 0xdd, 0x7a, 0x3a, 0x1f, 0x69, 0xd3, - 0xa3, 0xb8, 0x41, 0xe4, 0x07, 0xdc, 0x76, 0xeb, 0x58, 0x30, 0x41, 0x0d, 0x18, 0x69, 0x5b, 0xc1, - 0x76, 0xfa, 0x21, 0xe3, 0x79, 0x1e, 0x07, 0x15, 0x6c, 0x63, 0xc6, 0x00, 0xbd, 0x6c, 0xc0, 0x18, - 0x0f, 0x1a, 0x97, 0x16, 0xf7, 0xa1, 0x9f, 0x95, 0x65, 0x9f, 0x2d, 0xf2, 0xc8, 0x74, 0xe1, 0xfa, - 0xa1, 0x54, 0xb2, 0x28, 0xc5, 0x92, 0xed, 0xc2, 0xab, 0x06, 0x4c, 0xe8, 0xa8, 0x09, 0x4e, 0x1b, - 0x1f, 0xd1, 0x9d, 0x36, 0xd2, 0xec, 0x0f, 0xdd, 0xff, 0xe3, 0x5f, 0x0d, 0xd0, 0xbe, 0xba, 0x1b, - 0xba, 0x6c, 0x1a, 0x03, 0xbb, 0x6c, 0x66, 0x8e, 0xe8, 0xb2, 0x99, 0x3d, 0x92, 0xcb, 0xe6, 0xc8, - 0xd1, 0x5d, 0x36, 0x73, 0xfd, 0x5d, 0x36, 0xcd, 0xd7, 0x0d, 0x98, 0xed, 0xd9, 0x87, 0xe9, 0xb1, - 0xcd, 0x73, 0xdd, 0xa0, 0x8f, 0xa7, 0x14, 0x0e, 0x41, 0x58, 0xc7, 0x43, 0x2b, 0x30, 0x23, 0x12, - 0x64, 0x57, 0xdb, 0x4d, 0x3b, 0x31, 0x9c, 0x7f, 0x23, 0x06, 0xc7, 0x3d, 0x35, 0xcc, 0xbf, 0x34, - 0x60, 0x5c, 0x0b, 0x02, 0xa4, 0xed, 0x60, 0xc1, 0x92, 0x42, 0x8c, 0x30, 0x37, 0x38, 0x7b, 0xe1, - 0xe0, 0x30, 0xfe, 0xd8, 0xd6, 0xd0, 0xd2, 0xa7, 0x86, 0x8f, 0x6d, 0xb4, 0x14, 0x0b, 0x28, 0x4f, - 0x8c, 0x49, 0xda, 0xac, 0xd3, 0xb3, 0x7a, 0x62, 0x4c, 0xd2, 0xc6, 0x0c, 0xc2, 0xd8, 0xd1, 0xf3, - 0xab, 0xf0, 0xe6, 0xd5, 0x52, 0x91, 0x5b, 0x5e, 0x80, 0x39, 0x0c, 0x9d, 0x85, 0x2c, 0x71, 0xea, - 0xe2, 0xb2, 0xad, 0x3e, 0xff, 0x75, 0xde, 0xa9, 0x63, 0x5a, 0x6e, 0x5e, 0x81, 0x89, 0x2a, 0xa9, - 0x79, 0x24, 0x78, 0x8e, 0xec, 0x0f, 0xfc, 0x3d, 0x31, 0x3a, 0xdb, 0x63, 0xdf, 0x13, 0xa3, 0xd5, - 0x69, 0xb9, 0xf9, 0x27, 0x06, 0xc4, 0xf2, 0xe5, 0x6b, 0x86, 0x77, 0xa3, 0x9f, 0xe1, 0x3d, 0x62, - 0x22, 0xce, 0x1c, 0x6a, 0x22, 0xbe, 0x04, 0xa8, 0x45, 0x97, 0x42, 0xe4, 0xeb, 0x10, 0xc2, 0xce, - 0x11, 0x86, 0x1c, 0xf7, 0x60, 0xe0, 0x84, 0x5a, 0xe6, 0x1f, 0x73, 0x61, 0xf5, 0x0c, 0xfa, 0x77, - 0xee, 0x80, 0x0e, 0xe4, 0x18, 0x29, 0x61, 0xec, 0x19, 0xd2, 0x50, 0xda, 0x9b, 0xba, 0x23, 0x1c, - 0x48, 0xb1, 0xe4, 0x19, 0x37, 0xf3, 0xfb, 0x5c, 0x56, 0x2d, 0xc5, 0xfe, 0x00, 0xb2, 0xb6, 0xa2, - 0xb2, 0x5e, 0x4c, 0x6b, 0xaf, 0x4c, 0x96, 0x11, 0x2d, 0x02, 0xb4, 0x89, 0x57, 0x23, 0x4e, 0x20, - 0x9d, 0xcc, 0x73, 0x22, 0x28, 0x47, 0x95, 0x62, 0x0d, 0xc3, 0x7c, 0xd9, 0x80, 0x99, 0x6a, 0x60, - 0xd7, 0x76, 0x6c, 0x87, 0x07, 0x99, 0x6d, 0xd9, 0x0d, 0x7a, 0x3b, 0x22, 0xe2, 0x53, 0x59, 0xdc, - 0xfc, 0xa6, 0xb6, 0x62, 0xf9, 0x85, 0x2c, 0x09, 0x47, 0x25, 0x98, 0x96, 0x8f, 0x0e, 0xd2, 0x66, - 0xca, 0x83, 0x63, 0x95, 0x8d, 0x66, 0x25, 0x0a, 0xc6, 0x71, 0x7c, 0xf3, 0x93, 0x30, 0xae, 0xed, - 0xaf, 0x6c, 0x2b, 0xba, 0x65, 0xd5, 0x82, 0xf8, 0x12, 0x3e, 0x4f, 0x0b, 0x31, 0x87, 0x31, 0xd3, - 0x2e, 0xf7, 0x42, 0x8e, 0x2d, 0x61, 0xe1, 0x7b, 0x2c, 0xa0, 0x94, 0x98, 0x47, 0x1a, 0xe4, 0x96, - 0x4c, 0xd4, 0x2a, 0x89, 0x61, 0x5a, 0x88, 0x39, 0xcc, 0x7c, 0x04, 0xf2, 0x32, 0x85, 0x01, 0x8b, - 0x03, 0x96, 0x66, 0x47, 0x3d, 0x0e, 0xd8, 0xf5, 0x02, 0xcc, 0x20, 0xe6, 0x75, 0xc8, 0xcb, 0x4c, - 0x0b, 0x77, 0xc6, 0xa6, 0xab, 0xca, 0x77, 0xec, 0x8b, 0xae, 0x1f, 0xc8, 0xf4, 0x10, 0xfc, 0x65, - 0xe4, 0xf2, 0x2a, 0x2b, 0xc3, 0x0a, 0x6a, 0xce, 0xc2, 0xb4, 0x7a, 0xf2, 0x10, 0x6e, 0xa1, 0xdf, - 0xca, 0xc2, 0x44, 0xe4, 0xeb, 0xd3, 0x77, 0x9e, 0x6e, 0x83, 0xaf, 0xe2, 0x84, 0xa7, 0x8b, 0xec, - 0x11, 0x9f, 0x2e, 0xf4, 0xb7, 0xa2, 0x91, 0x93, 0x7d, 0x2b, 0xca, 0xa5, 0xf3, 0x56, 0x14, 0xc0, - 0x98, 0x2f, 0x14, 0xd5, 0x68, 0x1a, 0x46, 0x9c, 0xd8, 0x88, 0xf1, 0xb3, 0xb1, 0xd4, 0x77, 0x92, - 0x95, 0xf9, 0xf5, 0x1c, 0x4c, 0x45, 0x73, 0x4c, 0x0d, 0x30, 0x92, 0x8f, 0xf4, 0x8c, 0xe4, 0x11, - 0x6d, 0xa5, 0xd9, 0x61, 0x6d, 0xa5, 0x23, 0xc3, 0xda, 0x4a, 0x73, 0xc7, 0xb0, 0x95, 0xf6, 0x5a, - 0x3a, 0x47, 0x07, 0xb6, 0x74, 0x3e, 0xad, 0xfc, 0x8e, 0xc6, 0x22, 0x0f, 0xf5, 0xa1, 0xdf, 0x11, - 0x8a, 0x0e, 0xc3, 0xb2, 0x5b, 0x4f, 0xf4, 0xdf, 0xca, 0xdf, 0xc1, 0x26, 0xe4, 0x25, 0xba, 0x09, - 0x1d, 0xfd, 0x39, 0xe6, 0x6d, 0x47, 0x70, 0x11, 0x7a, 0x02, 0xc6, 0xc5, 0x7c, 0x62, 0x67, 0x25, - 0x88, 0x9e, 0xb3, 0xaa, 0x21, 0x08, 0xeb, 0x78, 0xec, 0x4b, 0xa2, 0xd1, 0x4f, 0xa7, 0x32, 0xd3, - 0xb3, 0xfe, 0x25, 0xd1, 0xd8, 0xa7, 0x56, 0xe3, 0xf8, 0xe6, 0xc7, 0xe1, 0x74, 0xe2, 0x4d, 0x90, - 0x99, 0xc6, 0x98, 0x1a, 0x27, 0x75, 0x81, 0xa0, 0x89, 0x11, 0x4b, 0x41, 0xbc, 0x70, 0xa3, 0x2f, - 0x26, 0x3e, 0x84, 0x8a, 0xf9, 0xb5, 0x2c, 0x4c, 0x45, 0x3f, 0x43, 0x85, 0xf6, 0x94, 0xdd, 0x28, - 0x15, 0x93, 0x15, 0x27, 0xab, 0xe5, 0x2d, 0xea, 0x6b, 0x04, 0xde, 0x63, 0xf3, 0x6b, 0x53, 0x25, - 0x51, 0x3a, 0x39, 0xc6, 0xc2, 0xfa, 0x2a, 0xd8, 0xb1, 0x2f, 0x4d, 0x85, 0xc1, 0x23, 0xe2, 0xda, - 0x95, 0x3a, 0xf7, 0x30, 0x1c, 0x44, 0xb1, 0xc2, 0x1a, 0x5b, 0xaa, 0x5b, 0x76, 0x89, 0x67, 0x6f, - 0xd9, 0xea, 0x13, 0x9a, 0x6c, 0xe7, 0xbe, 0x2e, 0xca, 0xb0, 0x82, 0x9a, 0x2f, 0x67, 0x20, 0xfc, - 0x60, 0x30, 0xfb, 0x56, 0x8b, 0xaf, 0x1d, 0x71, 0xc5, 0xb0, 0x5d, 0x1a, 0xf6, 0x83, 0x48, 0x21, - 0x45, 0xe1, 0x13, 0xaa, 0x95, 0xe0, 0x08, 0xc7, 0x5f, 0xc2, 0x87, 0x82, 0x2d, 0x98, 0x8e, 0x85, - 0xd4, 0xa6, 0xee, 0xbf, 0xff, 0xd3, 0x2c, 0x14, 0x54, 0x50, 0x32, 0x7a, 0x6f, 0xc4, 0xde, 0x50, - 0x28, 0xbf, 0x5d, 0xfb, 0x90, 0xc0, 0xb6, 0x5b, 0xbf, 0xdd, 0x2d, 0x4e, 0x2b, 0xe4, 0x98, 0xed, - 0xe0, 0x2c, 0x64, 0x3b, 0x5e, 0x33, 0x7e, 0xa1, 0xb8, 0x86, 0xd7, 0x30, 0x2d, 0x47, 0xb7, 0xe2, - 0x17, 0xfe, 0xf5, 0x94, 0x02, 0xa9, 0xf9, 0xc9, 0xbb, 0xff, 0x45, 0x9f, 0x6a, 0xc9, 0x4d, 0xb7, - 0xbe, 0x1f, 0xff, 0xf0, 0x40, 0xd9, 0xad, 0xef, 0x63, 0x06, 0x41, 0xcf, 0xc0, 0x54, 0x60, 0xb7, - 0x88, 0xdb, 0x09, 0xf4, 0xcf, 0xb1, 0x66, 0xc3, 0x47, 0xcd, 0x8d, 0x08, 0x14, 0xc7, 0xb0, 0xa9, - 0x96, 0xbd, 0xe9, 0xbb, 0x0e, 0xcb, 0x26, 0x38, 0x1a, 0x7d, 0x01, 0xb9, 0x54, 0xbd, 0x72, 0x99, - 0xd9, 0x3d, 0x14, 0x06, 0xc5, 0xb6, 0x59, 0x04, 0xa2, 0x47, 0x84, 0x4f, 0xc1, 0x4c, 0x98, 0x9f, - 0x82, 0x97, 0x63, 0x85, 0x81, 0x56, 0x38, 0x6d, 0x2a, 0x2d, 0xd3, 0x28, 0x13, 0xe5, 0x87, 0x25, - 0x5d, 0x5a, 0x76, 0xbb, 0x5b, 0x9c, 0x27, 0x4e, 0xcd, 0xad, 0xdb, 0x4e, 0x63, 0x89, 0x22, 0x2e, - 0x62, 0x6b, 0x4f, 0xaa, 0x1a, 0x55, 0xd3, 0xbc, 0x06, 0xd3, 0xb1, 0x0e, 0x93, 0x17, 0x40, 0x23, - 0xf9, 0x02, 0x38, 0xd8, 0xb7, 0x02, 0xfe, 0xcc, 0x80, 0xd9, 0x9e, 0x2d, 0x60, 0xd0, 0xf0, 0x94, - 0xb8, 0x32, 0xca, 0x1c, 0x5f, 0x19, 0x65, 0x8f, 0xa6, 0x8c, 0xca, 0x9b, 0xdf, 0x79, 0xf3, 0xdc, - 0x3d, 0xdf, 0x7b, 0xf3, 0xdc, 0x3d, 0x3f, 0x7c, 0xf3, 0xdc, 0x3d, 0x2f, 0x1f, 0x9c, 0x33, 0xbe, - 0x73, 0x70, 0xce, 0xf8, 0xde, 0xc1, 0x39, 0xe3, 0x87, 0x07, 0xe7, 0x8c, 0x7f, 0x3c, 0x38, 0x67, - 0xbc, 0xfe, 0xd3, 0x73, 0xf7, 0x3c, 0xff, 0x74, 0x38, 0x41, 0x97, 0xe4, 0x04, 0x65, 0x3f, 0xde, - 0x25, 0xa7, 0xe3, 0x52, 0x7b, 0xa7, 0xb1, 0x44, 0x27, 0xe8, 0x92, 0x2a, 0x91, 0x13, 0xf4, 0xbf, - 0x02, 0x00, 0x00, 0xff, 0xff, 0x3e, 0xb7, 0xa2, 0x97, 0xc8, 0x95, 0x00, 0x00, + // 7825 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5b, 0x6c, 0x24, 0xd7, + 0x95, 0x98, 0xaa, 0x9b, 0x4d, 0x76, 0x1f, 0xbe, 0xef, 0x70, 0x3c, 0x14, 0xa5, 0x99, 0x96, 0x4b, + 0x86, 0x22, 0x3b, 0x32, 0x69, 0xeb, 0x91, 0xc8, 0x96, 0xa0, 0xa4, 0x9b, 0x9c, 0xd1, 0x70, 0x44, + 0xce, 0xf4, 0xdc, 0xe6, 0xcc, 0xd8, 0xb2, 0x65, 0xbb, 0xd8, 0x7d, 0xd9, 0xac, 0x61, 0x77, 0x55, + 0xbb, 0xaa, 0x9a, 0x1c, 0xca, 0x82, 0x2d, 0xd9, 0x90, 0xe2, 0x18, 0x36, 0xac, 0xc4, 0x36, 0x82, + 0x20, 0x41, 0x60, 0x04, 0x06, 0xf2, 0xb0, 0xbf, 0x8c, 0x04, 0xf9, 0x31, 0x90, 0x20, 0x7e, 0xc4, + 0xf9, 0xf0, 0xc2, 0xfe, 0xd8, 0xf5, 0x03, 0x70, 0x7b, 0x45, 0xef, 0xcf, 0x2e, 0x76, 0x61, 0x2c, + 0xe0, 0xc5, 0xc2, 0xf3, 0xb1, 0x58, 0xdc, 0x67, 0xdd, 0xaa, 0xae, 0x26, 0x9b, 0xec, 0xe2, 0x58, + 0xd8, 0xf5, 0x5f, 0xf7, 0x3d, 0xe7, 0x9e, 0x73, 0xea, 0x3e, 0xcf, 0x3d, 0xf7, 0x9c, 0x73, 0x61, + 0xad, 0x61, 0x07, 0xdb, 0x9d, 0xcd, 0xc5, 0x9a, 0xdb, 0x5a, 0xb2, 0xbc, 0x86, 0xdb, 0xf6, 0xdc, + 0xdb, 0xec, 0xc7, 0x7b, 0x3d, 0xb7, 0xd9, 0x74, 0x3b, 0x81, 0xbf, 0xd4, 0xde, 0x69, 0x2c, 0x59, + 0x6d, 0xdb, 0x5f, 0x52, 0x25, 0xbb, 0xef, 0xb7, 0x9a, 0xed, 0x6d, 0xeb, 0xfd, 0x4b, 0x0d, 0xe2, + 0x10, 0xcf, 0x0a, 0x48, 0x7d, 0xb1, 0xed, 0xb9, 0x81, 0x8b, 0x9e, 0x0d, 0xa9, 0x2d, 0x4a, 0x6a, + 0xec, 0xc7, 0xc7, 0x65, 0xdd, 0xc5, 0xf6, 0x4e, 0x63, 0x91, 0x52, 0x5b, 0x54, 0x25, 0x92, 0xda, + 0xc2, 0x7b, 0x35, 0x59, 0x1a, 0x6e, 0xc3, 0x5d, 0x62, 0x44, 0x37, 0x3b, 0x5b, 0xec, 0x1f, 0xfb, + 0xc3, 0x7e, 0x71, 0x66, 0x0b, 0x0f, 0xef, 0x3c, 0xed, 0x2f, 0xda, 0x2e, 0x95, 0x6d, 0x69, 0xd3, + 0x0a, 0x6a, 0xdb, 0x4b, 0xbb, 0x3d, 0x12, 0x2d, 0x98, 0x1a, 0x52, 0xcd, 0xf5, 0x48, 0x12, 0xce, + 0x93, 0x21, 0x4e, 0xcb, 0xaa, 0x6d, 0xdb, 0x0e, 0xf1, 0xf6, 0xc3, 0xaf, 0x6e, 0x91, 0xc0, 0x4a, + 0xaa, 0xb5, 0xd4, 0xaf, 0x96, 0xd7, 0x71, 0x02, 0xbb, 0x45, 0x7a, 0x2a, 0xfc, 0xb3, 0xa3, 0x2a, + 0xf8, 0xb5, 0x6d, 0xd2, 0xb2, 0x7a, 0xea, 0x3d, 0xd1, 0xaf, 0x5e, 0x27, 0xb0, 0x9b, 0x4b, 0xb6, + 0x13, 0xf8, 0x81, 0x17, 0xaf, 0x64, 0x7e, 0x2f, 0x0b, 0x85, 0xd2, 0x5a, 0xb9, 0x1a, 0x58, 0x41, + 0xc7, 0x47, 0x6f, 0x18, 0x30, 0xd1, 0x74, 0xad, 0x7a, 0xd9, 0x6a, 0x5a, 0x4e, 0x8d, 0x78, 0xf3, + 0xc6, 0x43, 0xc6, 0xa3, 0xe3, 0x8f, 0xaf, 0x2d, 0x0e, 0xd3, 0x5f, 0x8b, 0xa5, 0x3d, 0x1f, 0x13, + 0xdf, 0xed, 0x78, 0x35, 0x82, 0xc9, 0x56, 0x79, 0xee, 0x87, 0xdd, 0xe2, 0x7d, 0x07, 0xdd, 0xe2, + 0xc4, 0x9a, 0xc6, 0x09, 0x47, 0xf8, 0xa2, 0xaf, 0x19, 0x30, 0x5b, 0xb3, 0x1c, 0xcb, 0xdb, 0xdf, + 0xb0, 0xbc, 0x06, 0x09, 0x9e, 0xf7, 0xdc, 0x4e, 0x7b, 0x3e, 0x73, 0x0a, 0xd2, 0xdc, 0x2f, 0xa4, + 0x99, 0x5d, 0x8e, 0xb3, 0xc3, 0xbd, 0x12, 0x30, 0xb9, 0xfc, 0xc0, 0xda, 0x6c, 0x12, 0x5d, 0xae, + 0xec, 0x69, 0xca, 0x55, 0x8d, 0xb3, 0xc3, 0xbd, 0x12, 0x98, 0xaf, 0x67, 0x61, 0xb6, 0xb4, 0x56, + 0xde, 0xf0, 0xac, 0xad, 0x2d, 0xbb, 0x86, 0xdd, 0x4e, 0x60, 0x3b, 0x0d, 0xf4, 0x6e, 0x18, 0xb3, + 0x9d, 0x86, 0x47, 0x7c, 0x9f, 0x75, 0x64, 0xa1, 0x3c, 0x2d, 0x88, 0x8e, 0xad, 0xf2, 0x62, 0x2c, + 0xe1, 0xe8, 0x29, 0x18, 0xf7, 0x89, 0xb7, 0x6b, 0xd7, 0x48, 0xc5, 0xf5, 0x02, 0xd6, 0xd2, 0xb9, + 0xf2, 0x19, 0x81, 0x3e, 0x5e, 0x0d, 0x41, 0x58, 0xc7, 0xa3, 0xd5, 0x3c, 0xd7, 0x0d, 0x04, 0x9c, + 0x35, 0x44, 0x21, 0xac, 0x86, 0x43, 0x10, 0xd6, 0xf1, 0xd0, 0x9b, 0x06, 0xcc, 0xf8, 0x81, 0x5d, + 0xdb, 0xb1, 0x1d, 0xe2, 0xfb, 0xcb, 0xae, 0xb3, 0x65, 0x37, 0xe6, 0x73, 0xac, 0x15, 0xaf, 0x0e, + 0xd7, 0x8a, 0xd5, 0x18, 0xd5, 0xf2, 0xdc, 0x41, 0xb7, 0x38, 0x13, 0x2f, 0xc5, 0x3d, 0xdc, 0xd1, + 0x0a, 0xcc, 0x58, 0x8e, 0xe3, 0x06, 0x56, 0x60, 0xbb, 0x4e, 0xc5, 0x23, 0x5b, 0xf6, 0x9d, 0xf9, + 0x11, 0xf6, 0x39, 0xf3, 0xe2, 0x73, 0x66, 0x4a, 0x31, 0x38, 0xee, 0xa9, 0x61, 0xae, 0xc0, 0x7c, + 0xa9, 0xb5, 0x69, 0xf9, 0xbe, 0x55, 0x77, 0xbd, 0x58, 0x6f, 0x3c, 0x0a, 0xf9, 0x96, 0xd5, 0x6e, + 0xdb, 0x4e, 0x83, 0x76, 0x47, 0xf6, 0xd1, 0x42, 0x79, 0xe2, 0xa0, 0x5b, 0xcc, 0xaf, 0x8b, 0x32, + 0xac, 0xa0, 0xe6, 0xcf, 0x33, 0x30, 0x5e, 0x72, 0xac, 0xe6, 0xbe, 0x6f, 0xfb, 0xb8, 0xe3, 0xa0, + 0x4f, 0x40, 0x9e, 0xae, 0x2e, 0x75, 0x2b, 0xb0, 0xc4, 0x8c, 0x7c, 0xdf, 0x22, 0x9f, 0xec, 0x8b, + 0xfa, 0x64, 0x0f, 0xdb, 0x85, 0x62, 0x2f, 0xee, 0xbe, 0x7f, 0xf1, 0xda, 0xe6, 0x6d, 0x52, 0x0b, + 0xd6, 0x49, 0x60, 0x95, 0x91, 0xf8, 0x0a, 0x08, 0xcb, 0xb0, 0xa2, 0x8a, 0x5c, 0x18, 0xf1, 0xdb, + 0xa4, 0x26, 0x66, 0xd8, 0xfa, 0x90, 0x23, 0x39, 0x14, 0xbd, 0xda, 0x26, 0xb5, 0xf2, 0x84, 0x60, + 0x3d, 0x42, 0xff, 0x61, 0xc6, 0x08, 0xed, 0xc1, 0xa8, 0xcf, 0xd6, 0x1c, 0x31, 0x79, 0xae, 0xa5, + 0xc7, 0x92, 0x91, 0x2d, 0x4f, 0x09, 0xa6, 0xa3, 0xfc, 0x3f, 0x16, 0xec, 0xcc, 0x5f, 0x18, 0x70, + 0x46, 0xc3, 0x2e, 0x79, 0x8d, 0x4e, 0x8b, 0x38, 0x01, 0x7a, 0x08, 0x46, 0x1c, 0xab, 0x45, 0xc4, + 0x44, 0x51, 0x22, 0x5f, 0xb5, 0x5a, 0x04, 0x33, 0x08, 0x7a, 0x18, 0x72, 0xbb, 0x56, 0xb3, 0x43, + 0x58, 0x23, 0x15, 0xca, 0x93, 0x02, 0x25, 0x77, 0x93, 0x16, 0x62, 0x0e, 0x43, 0xaf, 0x40, 0x81, + 0xfd, 0xb8, 0xe4, 0xb9, 0xad, 0x94, 0x3e, 0x4d, 0x48, 0x78, 0x53, 0x92, 0x2d, 0x4f, 0x1e, 0x74, + 0x8b, 0x05, 0xf5, 0x17, 0x87, 0x0c, 0xcd, 0x5f, 0x19, 0x30, 0xad, 0x7d, 0xdc, 0x9a, 0xed, 0x07, + 0xe8, 0xa3, 0x3d, 0x83, 0x67, 0x71, 0xb0, 0xc1, 0x43, 0x6b, 0xb3, 0xa1, 0x33, 0x23, 0xbe, 0x34, + 0x2f, 0x4b, 0xb4, 0x81, 0xe3, 0x40, 0xce, 0x0e, 0x48, 0xcb, 0x9f, 0xcf, 0x3c, 0x94, 0x7d, 0x74, + 0xfc, 0xf1, 0xd5, 0xd4, 0xba, 0x31, 0x6c, 0xdf, 0x55, 0x4a, 0x1f, 0x73, 0x36, 0xe6, 0xb7, 0x47, + 0x22, 0x5f, 0x48, 0x47, 0x14, 0x72, 0x61, 0xac, 0x45, 0x02, 0xcf, 0xae, 0xf1, 0x79, 0x35, 0xfe, + 0xf8, 0xca, 0x70, 0x52, 0xac, 0x33, 0x62, 0xe1, 0x62, 0xc9, 0xff, 0xfb, 0x58, 0x72, 0x41, 0xdb, + 0x30, 0x62, 0x79, 0x0d, 0xf9, 0xcd, 0x97, 0xd2, 0xe9, 0xdf, 0x70, 0xcc, 0x95, 0xbc, 0x86, 0x8f, + 0x19, 0x07, 0xb4, 0x04, 0x85, 0x80, 0x78, 0x2d, 0xdb, 0xb1, 0x02, 0xbe, 0xba, 0xe6, 0xcb, 0xb3, + 0x02, 0xad, 0xb0, 0x21, 0x01, 0x38, 0xc4, 0x41, 0x4d, 0x18, 0xad, 0x7b, 0xfb, 0xb8, 0xe3, 0xcc, + 0x8f, 0xa4, 0xd1, 0x14, 0x2b, 0x8c, 0x56, 0x38, 0x99, 0xf8, 0x7f, 0x2c, 0x78, 0xa0, 0x6f, 0x18, + 0x30, 0xd7, 0x22, 0x96, 0xdf, 0xf1, 0x08, 0xfd, 0x04, 0x4c, 0x02, 0xe2, 0xd0, 0xd5, 0x70, 0x3e, + 0xc7, 0x98, 0xe3, 0x61, 0xfb, 0xa1, 0x97, 0x72, 0xf9, 0x41, 0x21, 0xca, 0x5c, 0x12, 0x14, 0x27, + 0x4a, 0x63, 0xfe, 0x7c, 0x04, 0x66, 0x7b, 0x56, 0x08, 0xf4, 0x24, 0xe4, 0xda, 0xdb, 0x96, 0x2f, + 0xa7, 0xfc, 0x05, 0x39, 0xde, 0x2a, 0xb4, 0xf0, 0x6e, 0xb7, 0x38, 0x29, 0xab, 0xb0, 0x02, 0xcc, + 0x91, 0xe9, 0x9e, 0xda, 0x22, 0xbe, 0x6f, 0x35, 0xe4, 0x3a, 0xa0, 0x0d, 0x13, 0x56, 0x8c, 0x25, + 0x1c, 0xfd, 0x2b, 0x03, 0x26, 0xf9, 0x90, 0xc1, 0xc4, 0xef, 0x34, 0x03, 0xba, 0xd6, 0xd1, 0x66, + 0xb9, 0x92, 0xc6, 0xf0, 0xe4, 0x24, 0xcb, 0x67, 0x05, 0xf7, 0x49, 0xbd, 0xd4, 0xc7, 0x51, 0xbe, + 0xe8, 0x16, 0x14, 0xfc, 0xc0, 0xf2, 0x02, 0x52, 0x2f, 0x05, 0x6c, 0x57, 0x1b, 0x7f, 0xfc, 0x3d, + 0x83, 0x2d, 0x02, 0x1b, 0x76, 0x8b, 0xf0, 0x05, 0xa7, 0x2a, 0x09, 0xe0, 0x90, 0x16, 0x7a, 0x05, + 0xc0, 0xeb, 0x38, 0xd5, 0x4e, 0xab, 0x65, 0x79, 0xfb, 0x62, 0x07, 0xbf, 0x3c, 0xdc, 0xe7, 0x61, + 0x45, 0x2f, 0xdc, 0xb3, 0xc2, 0x32, 0xac, 0xf1, 0x43, 0xaf, 0x19, 0x30, 0xc9, 0x47, 0xa2, 0x94, + 0x60, 0x34, 0x65, 0x09, 0x66, 0x69, 0xd3, 0xae, 0xe8, 0x2c, 0x70, 0x94, 0xa3, 0xf9, 0xc7, 0xd1, + 0xfd, 0xa4, 0x1a, 0x50, 0xed, 0xba, 0xb1, 0x8f, 0x3e, 0x02, 0xf7, 0xfb, 0x9d, 0x5a, 0x8d, 0xf8, + 0xfe, 0x56, 0xa7, 0x89, 0x3b, 0xce, 0x65, 0xdb, 0x0f, 0x5c, 0x6f, 0x7f, 0xcd, 0x6e, 0xd9, 0x01, + 0x1b, 0x71, 0xb9, 0xf2, 0xf9, 0x83, 0x6e, 0xf1, 0xfe, 0x6a, 0x3f, 0x24, 0xdc, 0xbf, 0x3e, 0xb2, + 0xe0, 0x81, 0x8e, 0xd3, 0x9f, 0x3c, 0xd7, 0xde, 0x8a, 0x07, 0xdd, 0xe2, 0x03, 0x37, 0xfa, 0xa3, + 0xe1, 0xc3, 0x68, 0x98, 0x7f, 0x61, 0xc0, 0x8c, 0xfc, 0xae, 0x0d, 0xd2, 0x6a, 0x37, 0xe9, 0xea, + 0x72, 0xfa, 0x8a, 0x48, 0x10, 0x51, 0x44, 0x70, 0x3a, 0xdb, 0x89, 0x94, 0xbf, 0x9f, 0x36, 0x62, + 0xfe, 0xb9, 0x01, 0x73, 0x71, 0xe4, 0x7b, 0xb0, 0x79, 0xfa, 0xd1, 0xcd, 0xf3, 0x6a, 0xba, 0x5f, + 0xdb, 0x67, 0x07, 0x7d, 0x63, 0xa4, 0xf7, 0x5b, 0xff, 0xa1, 0x6f, 0xa3, 0xe1, 0xae, 0x98, 0xfd, + 0x7d, 0xee, 0x8a, 0x23, 0x6f, 0xab, 0x5d, 0xf1, 0xbf, 0x8e, 0xc0, 0x44, 0xc9, 0x09, 0xec, 0xd2, + 0xd6, 0x96, 0xed, 0xd8, 0xc1, 0x3e, 0xfa, 0x62, 0x06, 0x96, 0xda, 0x1e, 0xd9, 0x22, 0x9e, 0x47, + 0xea, 0x2b, 0x1d, 0xcf, 0x76, 0x1a, 0xd5, 0xda, 0x36, 0xa9, 0x77, 0x9a, 0xb6, 0xd3, 0x58, 0x6d, + 0x38, 0xae, 0x2a, 0xbe, 0x78, 0x87, 0xd4, 0x3a, 0xec, 0x93, 0xf8, 0xa4, 0x68, 0x0d, 0xf7, 0x49, + 0x95, 0xe3, 0x31, 0x2d, 0x3f, 0x71, 0xd0, 0x2d, 0x2e, 0x1d, 0xb3, 0x12, 0x3e, 0xee, 0xa7, 0xa1, + 0xcf, 0x67, 0x60, 0xd1, 0x23, 0x9f, 0xec, 0xd8, 0x83, 0xb7, 0x06, 0x5f, 0xb5, 0x9a, 0x43, 0x6e, + 0x3f, 0xc7, 0xe2, 0x59, 0x7e, 0xfc, 0xa0, 0x5b, 0x3c, 0x66, 0x1d, 0x7c, 0xcc, 0xef, 0x32, 0x2b, + 0x30, 0x5e, 0x6a, 0xdb, 0xbe, 0x7d, 0x87, 0x9e, 0x65, 0xc9, 0x00, 0x67, 0xa5, 0x22, 0xe4, 0xbc, + 0x4e, 0x93, 0xf0, 0xb9, 0x5d, 0x28, 0x17, 0xe8, 0x2a, 0x84, 0x69, 0x01, 0xe6, 0xe5, 0xe6, 0x67, + 0xe9, 0x8a, 0xcb, 0x48, 0xc6, 0x4e, 0xc9, 0xb7, 0x21, 0xe7, 0x51, 0x26, 0x62, 0x64, 0x0d, 0x7b, + 0xa0, 0x08, 0xa5, 0x16, 0x42, 0xd0, 0x9f, 0x98, 0xb3, 0x30, 0xbf, 0x9b, 0x81, 0xb3, 0xa5, 0x76, + 0x7b, 0x9d, 0xf8, 0xdb, 0x31, 0x29, 0xbe, 0x6c, 0xc0, 0xd4, 0xae, 0xed, 0x05, 0x1d, 0xab, 0x29, + 0x6d, 0x1b, 0x5c, 0x9e, 0xea, 0xb0, 0xf2, 0x30, 0x6e, 0x37, 0x23, 0xa4, 0xcb, 0xe8, 0xa0, 0x5b, + 0x9c, 0x8a, 0x96, 0xe1, 0x18, 0x7b, 0xf4, 0xef, 0x0c, 0x98, 0x11, 0x45, 0x57, 0xdd, 0x3a, 0xd1, + 0x0d, 0x62, 0x37, 0xd2, 0x94, 0x49, 0x11, 0xe7, 0x96, 0x93, 0x78, 0x29, 0xee, 0x11, 0xc2, 0xfc, + 0xab, 0x0c, 0x9c, 0xeb, 0x43, 0x03, 0xfd, 0x17, 0x03, 0xe6, 0xb8, 0x15, 0x4d, 0x03, 0x61, 0xb2, + 0x25, 0x5a, 0xf3, 0xc3, 0x69, 0x4b, 0x8e, 0xe9, 0x14, 0x27, 0x4e, 0x8d, 0x94, 0xe7, 0xe9, 0x6a, + 0xb8, 0x9c, 0xc0, 0x1a, 0x27, 0x0a, 0xc4, 0x24, 0xe5, 0x76, 0xb5, 0x98, 0xa4, 0x99, 0x7b, 0x22, + 0x69, 0x35, 0x81, 0x35, 0x4e, 0x14, 0xc8, 0xfc, 0x17, 0xf0, 0xc0, 0x21, 0xe4, 0x8e, 0x9e, 0x9c, + 0xe6, 0x4b, 0x6a, 0xd4, 0x47, 0xc7, 0xdc, 0x00, 0xf3, 0xda, 0x84, 0x51, 0x36, 0x75, 0xe4, 0xc4, + 0x06, 0xba, 0xfd, 0xb1, 0x39, 0xe5, 0x63, 0x01, 0x31, 0xbf, 0x6b, 0x40, 0xfe, 0x18, 0x66, 0x95, + 0x62, 0xd4, 0xac, 0x52, 0xe8, 0x31, 0xa9, 0x04, 0xbd, 0x26, 0x95, 0xe7, 0x87, 0xeb, 0x8d, 0x41, + 0x4c, 0x29, 0xbf, 0x31, 0x60, 0xb6, 0xc7, 0xf4, 0x82, 0xb6, 0x61, 0xae, 0xed, 0xd6, 0xa5, 0xda, + 0x74, 0xd9, 0xf2, 0xb7, 0x19, 0x4c, 0x7c, 0xde, 0x93, 0xb4, 0x27, 0x2b, 0x09, 0xf0, 0xbb, 0xdd, + 0xe2, 0xbc, 0x22, 0x12, 0x43, 0xc0, 0x89, 0x14, 0x51, 0x1b, 0xf2, 0x5b, 0x36, 0x69, 0xd6, 0xc3, + 0x21, 0x38, 0xa4, 0x82, 0x74, 0x49, 0x50, 0xe3, 0x56, 0x47, 0xf9, 0x0f, 0x2b, 0x2e, 0xe6, 0x75, + 0x98, 0x8a, 0xda, 0xa0, 0x07, 0xe8, 0xbc, 0xf3, 0x90, 0xb5, 0x3c, 0x47, 0x74, 0xdd, 0xb8, 0x40, + 0xc8, 0x96, 0xf0, 0x55, 0x4c, 0xcb, 0xcd, 0xdf, 0x8d, 0xc0, 0x74, 0xb9, 0xd9, 0x21, 0xcf, 0x7b, + 0x84, 0xc8, 0x63, 0x77, 0x09, 0xa6, 0xdb, 0x1e, 0xd9, 0xb5, 0xc9, 0x5e, 0x95, 0x34, 0x49, 0x2d, + 0x70, 0x3d, 0x41, 0xff, 0x9c, 0xa8, 0x3e, 0x5d, 0x89, 0x82, 0x71, 0x1c, 0x1f, 0x3d, 0x07, 0x53, + 0x56, 0x2d, 0xb0, 0x77, 0x89, 0xa2, 0xc0, 0x05, 0x78, 0x87, 0xa0, 0x30, 0x55, 0x8a, 0x40, 0x71, + 0x0c, 0x1b, 0x7d, 0x14, 0xe6, 0xfd, 0x9a, 0xd5, 0x24, 0x37, 0xda, 0x82, 0xd5, 0xf2, 0x36, 0xa9, + 0xed, 0x54, 0x5c, 0xdb, 0x09, 0x84, 0x91, 0xe5, 0x21, 0x41, 0x69, 0xbe, 0xda, 0x07, 0x0f, 0xf7, + 0xa5, 0x80, 0xfe, 0xb7, 0x01, 0xe7, 0xdb, 0x1e, 0xa9, 0x78, 0x6e, 0xcb, 0xa5, 0xbb, 0x67, 0x8f, + 0xe5, 0x41, 0x9c, 0xc0, 0x6f, 0x0e, 0xa9, 0x26, 0xf0, 0x92, 0x5e, 0xcb, 0xe7, 0x3b, 0x0f, 0xba, + 0xc5, 0xf3, 0x95, 0xc3, 0x04, 0xc0, 0x87, 0xcb, 0x87, 0xfe, 0xaf, 0x01, 0x17, 0xda, 0xae, 0x1f, + 0x1c, 0xf2, 0x09, 0xb9, 0x53, 0xfd, 0x04, 0xf3, 0xa0, 0x5b, 0xbc, 0x50, 0x39, 0x54, 0x02, 0x7c, + 0x84, 0x84, 0xe6, 0xc1, 0x38, 0xcc, 0x6a, 0x63, 0x4f, 0x1c, 0xcb, 0x9f, 0x81, 0x49, 0x39, 0x18, + 0xc2, 0x6d, 0xbd, 0x10, 0x9a, 0x51, 0x4a, 0x3a, 0x10, 0x47, 0x71, 0xe9, 0xb8, 0x53, 0x43, 0x91, + 0xd7, 0x8e, 0x8d, 0xbb, 0x4a, 0x04, 0x8a, 0x63, 0xd8, 0x68, 0x15, 0xce, 0x88, 0x12, 0x4c, 0xda, + 0x4d, 0xbb, 0x66, 0x2d, 0xbb, 0x1d, 0x31, 0xe4, 0x72, 0xe5, 0x73, 0x07, 0xdd, 0xe2, 0x99, 0x4a, + 0x2f, 0x18, 0x27, 0xd5, 0x41, 0x6b, 0x30, 0x67, 0x75, 0x02, 0x57, 0x7d, 0xff, 0x45, 0x87, 0xee, + 0x14, 0x75, 0x36, 0xb4, 0xf2, 0x7c, 0x4b, 0x29, 0x25, 0xc0, 0x71, 0x62, 0x2d, 0x54, 0x89, 0x51, + 0xab, 0x92, 0x9a, 0xeb, 0xd4, 0x79, 0x2f, 0xe7, 0xc2, 0xc3, 0x45, 0x29, 0x01, 0x07, 0x27, 0xd6, + 0x44, 0x4d, 0x98, 0x6a, 0x59, 0x77, 0x6e, 0x38, 0xd6, 0xae, 0x65, 0x37, 0x29, 0x13, 0x61, 0x9a, + 0xe9, 0x6f, 0x2f, 0xe8, 0x04, 0x76, 0x73, 0x91, 0xdf, 0x52, 0x2e, 0xae, 0x3a, 0xc1, 0x35, 0xaf, + 0x1a, 0x50, 0x25, 0x94, 0x2b, 0x47, 0xeb, 0x11, 0x5a, 0x38, 0x46, 0x1b, 0x5d, 0x83, 0xb3, 0x6c, + 0x3a, 0xae, 0xb8, 0x7b, 0xce, 0x0a, 0x69, 0x5a, 0xfb, 0xf2, 0x03, 0xc6, 0xd8, 0x07, 0xdc, 0x7f, + 0xd0, 0x2d, 0x9e, 0xad, 0x26, 0x21, 0xe0, 0xe4, 0x7a, 0xc8, 0x82, 0x07, 0xa2, 0x00, 0x4c, 0x76, + 0x6d, 0xdf, 0x76, 0x1d, 0x6e, 0x60, 0xc9, 0x87, 0x06, 0x96, 0x6a, 0x7f, 0x34, 0x7c, 0x18, 0x0d, + 0xf4, 0x1f, 0x0c, 0x98, 0x4b, 0x9a, 0x86, 0xf3, 0x85, 0x34, 0xee, 0x60, 0x62, 0x53, 0x8b, 0x8f, + 0x88, 0xc4, 0x45, 0x21, 0x51, 0x08, 0xf4, 0xaa, 0x01, 0x13, 0x96, 0x76, 0x38, 0x9c, 0x07, 0x26, + 0xd5, 0x95, 0x61, 0x4d, 0x14, 0x21, 0xc5, 0xf2, 0xcc, 0x41, 0xb7, 0x18, 0x39, 0x80, 0xe2, 0x08, + 0x47, 0xf4, 0x9f, 0x0c, 0x38, 0x9b, 0x38, 0xc7, 0xe7, 0xc7, 0x4f, 0xa3, 0x85, 0xd8, 0x20, 0x49, + 0x5e, 0x73, 0x92, 0xc5, 0x40, 0x6f, 0x1a, 0x6a, 0x2b, 0x5b, 0x97, 0x46, 0xa2, 0x09, 0x26, 0xda, + 0xf5, 0x21, 0xcf, 0xc3, 0xa1, 0x42, 0x20, 0x09, 0x97, 0xcf, 0x68, 0x3b, 0xa3, 0x2c, 0xc4, 0x71, + 0xf6, 0xe8, 0x4b, 0x86, 0xdc, 0x1a, 0x95, 0x44, 0x93, 0xa7, 0x25, 0x11, 0x0a, 0x77, 0x5a, 0x25, + 0x50, 0x8c, 0x39, 0xfa, 0x18, 0x2c, 0x58, 0x9b, 0xae, 0x17, 0x24, 0x4e, 0xbe, 0xf9, 0x29, 0x36, + 0x8d, 0x2e, 0x1c, 0x74, 0x8b, 0x0b, 0xa5, 0xbe, 0x58, 0xf8, 0x10, 0x0a, 0xe6, 0xb7, 0x72, 0x30, + 0xc1, 0x95, 0x7c, 0xb1, 0x75, 0x7d, 0xc7, 0x80, 0x07, 0x6b, 0x1d, 0xcf, 0x23, 0x4e, 0x50, 0x0d, + 0x48, 0xbb, 0x77, 0xe3, 0x32, 0x4e, 0x75, 0xe3, 0x7a, 0xe8, 0xa0, 0x5b, 0x7c, 0x70, 0xf9, 0x10, + 0xfe, 0xf8, 0x50, 0xe9, 0xd0, 0x1f, 0x19, 0x60, 0x0a, 0x84, 0xb2, 0x55, 0xdb, 0x69, 0x78, 0x6e, + 0xc7, 0xa9, 0xf7, 0x7e, 0x44, 0xe6, 0x54, 0x3f, 0xe2, 0x91, 0x83, 0x6e, 0xd1, 0x5c, 0x3e, 0x52, + 0x0a, 0x3c, 0x80, 0xa4, 0xe8, 0x79, 0x98, 0x15, 0x58, 0x17, 0xef, 0xb4, 0x89, 0x67, 0x53, 0x75, + 0x5a, 0xb8, 0x09, 0x84, 0x9e, 0x17, 0x71, 0x04, 0xdc, 0x5b, 0x07, 0xf9, 0x30, 0xb6, 0x47, 0xec, + 0xc6, 0x76, 0x20, 0xd5, 0xa7, 0x21, 0xdd, 0x2d, 0xc4, 0x81, 0xff, 0x16, 0xa7, 0x59, 0x1e, 0x3f, + 0xe8, 0x16, 0xc7, 0xc4, 0x1f, 0x2c, 0x39, 0xa1, 0xab, 0x30, 0xc5, 0x8f, 0x60, 0x15, 0xdb, 0x69, + 0x54, 0x5c, 0x87, 0x3b, 0x29, 0x14, 0xca, 0x8f, 0xc8, 0x0d, 0xbf, 0x1a, 0x81, 0xde, 0xed, 0x16, + 0x27, 0xe4, 0xef, 0x8d, 0xfd, 0x36, 0xc1, 0xb1, 0xda, 0xe6, 0x0f, 0x46, 0x01, 0xe4, 0x70, 0x25, + 0x6d, 0xf4, 0x4f, 0xa1, 0xe0, 0x93, 0x80, 0x73, 0x15, 0x77, 0x02, 0xfc, 0xaa, 0x45, 0x16, 0xe2, + 0x10, 0x8e, 0x76, 0x20, 0xd7, 0xb6, 0x3a, 0x3e, 0x11, 0x9d, 0x7f, 0x25, 0x95, 0xce, 0xaf, 0x50, + 0x8a, 0xfc, 0xcc, 0xc5, 0x7e, 0x62, 0xce, 0x03, 0x7d, 0xce, 0x00, 0x20, 0xd1, 0x0e, 0x1b, 0xda, + 0xf6, 0x21, 0x58, 0x86, 0x7d, 0x4a, 0xdb, 0xa0, 0x3c, 0x75, 0xd0, 0x2d, 0x82, 0xd6, 0xf5, 0x1a, + 0x5b, 0xb4, 0x07, 0x79, 0x4b, 0xae, 0xf9, 0x23, 0xa7, 0xb1, 0xe6, 0xb3, 0xa3, 0x90, 0x1a, 0xb4, + 0x8a, 0x19, 0xfa, 0xbc, 0x01, 0x53, 0x3e, 0x09, 0x44, 0x57, 0xd1, 0x95, 0x47, 0x28, 0xbc, 0x43, + 0x0e, 0xba, 0x6a, 0x84, 0x26, 0x5f, 0x41, 0xa3, 0x65, 0x38, 0xc6, 0x57, 0x8a, 0x72, 0x99, 0x58, + 0x75, 0xe2, 0xb1, 0x93, 0xb6, 0xd0, 0xa4, 0x86, 0x17, 0x45, 0xa3, 0xa9, 0x44, 0xd1, 0xca, 0x70, + 0x8c, 0xaf, 0x14, 0x65, 0xdd, 0xf6, 0x3c, 0x57, 0x88, 0x92, 0x4f, 0x49, 0x14, 0x8d, 0xa6, 0x12, + 0x45, 0x2b, 0xc3, 0x31, 0xbe, 0xe6, 0xd7, 0x27, 0x61, 0x4a, 0x4e, 0xa4, 0x50, 0xb3, 0xe7, 0x86, + 0x9d, 0x3e, 0x9a, 0xfd, 0xb2, 0x0e, 0xc4, 0x51, 0x5c, 0x5a, 0x99, 0x4f, 0xd5, 0xa8, 0x62, 0xaf, + 0x2a, 0x57, 0x75, 0x20, 0x8e, 0xe2, 0xa2, 0x16, 0xe4, 0xfc, 0x80, 0xb4, 0xe5, 0xf5, 0xee, 0x90, + 0xb7, 0x8f, 0xe1, 0xfa, 0x10, 0x5e, 0xe0, 0xd0, 0x7f, 0x3e, 0xe6, 0x5c, 0x98, 0x6d, 0x32, 0x88, + 0x98, 0x2b, 0xc5, 0xe4, 0x48, 0x67, 0x7e, 0x46, 0x2d, 0xa1, 0xbc, 0x37, 0xa2, 0x65, 0x38, 0xc6, + 0x3e, 0x41, 0xd9, 0xcf, 0x9d, 0xa2, 0xb2, 0xff, 0x22, 0xe4, 0x5b, 0xd6, 0x9d, 0x6a, 0xc7, 0x6b, + 0x9c, 0xfc, 0x50, 0x21, 0x3c, 0xaf, 0x38, 0x15, 0xac, 0xe8, 0xa1, 0xd7, 0x0c, 0x6d, 0xc9, 0x19, + 0x63, 0xc4, 0x6f, 0xa5, 0xbb, 0xe4, 0xa8, 0xbd, 0xb2, 0xef, 0xe2, 0xd3, 0xa3, 0x7a, 0xe7, 0xef, + 0xb9, 0xea, 0x4d, 0xd5, 0x48, 0x3e, 0x41, 0x94, 0x1a, 0x59, 0x38, 0x55, 0x35, 0x72, 0x39, 0xc2, + 0x0c, 0xc7, 0x98, 0x33, 0x79, 0xf8, 0x9c, 0x53, 0xf2, 0xc0, 0xa9, 0xca, 0x53, 0x8d, 0x30, 0xc3, + 0x31, 0xe6, 0xfd, 0xcf, 0x9b, 0xe3, 0xa7, 0x73, 0xde, 0x9c, 0x48, 0xe1, 0xbc, 0x79, 0xb8, 0x2a, + 0x3e, 0x39, 0xac, 0x2a, 0x8e, 0xae, 0x00, 0xaa, 0xef, 0x3b, 0x56, 0xcb, 0xae, 0x89, 0xc5, 0x92, + 0x6d, 0x9b, 0x53, 0xcc, 0x1e, 0xb1, 0x20, 0x16, 0x32, 0xb4, 0xd2, 0x83, 0x81, 0x13, 0x6a, 0xa1, + 0x00, 0xf2, 0x6d, 0xa9, 0x71, 0x4d, 0xa7, 0x31, 0xfa, 0xa5, 0x06, 0xc6, 0x3d, 0x00, 0xe8, 0xc4, + 0x93, 0x25, 0x58, 0x71, 0x42, 0x6b, 0x30, 0xd7, 0xb2, 0x9d, 0x8a, 0x5b, 0xf7, 0x2b, 0xc4, 0x13, + 0xd6, 0x96, 0x2a, 0x09, 0xe6, 0x67, 0x58, 0xdb, 0xb0, 0x13, 0xf4, 0x7a, 0x02, 0x1c, 0x27, 0xd6, + 0x32, 0xff, 0xc6, 0x80, 0x99, 0xe5, 0xa6, 0xdb, 0xa9, 0xdf, 0xb2, 0x82, 0xda, 0x36, 0xbf, 0xfc, + 0x46, 0xcf, 0x41, 0xde, 0x76, 0x02, 0xe2, 0xed, 0x5a, 0x4d, 0xb1, 0x3f, 0x99, 0xd2, 0x3f, 0x60, + 0x55, 0x94, 0xdf, 0xed, 0x16, 0xa7, 0x56, 0x3a, 0x1e, 0xf3, 0x2a, 0xe5, 0xab, 0x15, 0x56, 0x75, + 0xd0, 0xd7, 0x0d, 0x98, 0xe5, 0xd7, 0xe7, 0x2b, 0x56, 0x60, 0x5d, 0xef, 0x10, 0xcf, 0x26, 0xf2, + 0x02, 0x7d, 0xc8, 0x85, 0x2a, 0x2e, 0xab, 0x64, 0xb0, 0x1f, 0x2a, 0xea, 0xeb, 0x71, 0xce, 0xb8, + 0x57, 0x18, 0xf3, 0x2b, 0x59, 0xb8, 0xbf, 0x2f, 0x2d, 0xb4, 0x00, 0x19, 0xbb, 0x2e, 0x3e, 0x1d, + 0x04, 0xdd, 0xcc, 0x6a, 0x1d, 0x67, 0xec, 0x3a, 0x5a, 0x64, 0x3a, 0xa7, 0x47, 0x7c, 0x5f, 0xde, + 0xa5, 0x16, 0x94, 0x7a, 0x28, 0x4a, 0xb1, 0x86, 0x81, 0x8a, 0x90, 0x6b, 0x5a, 0x9b, 0xa4, 0x29, + 0xce, 0x13, 0x4c, 0x8b, 0x5d, 0xa3, 0x05, 0x98, 0x97, 0xa3, 0xcf, 0x1a, 0x00, 0x5c, 0x40, 0x7a, + 0x1a, 0x11, 0xbb, 0x24, 0x4e, 0xb7, 0x99, 0x28, 0x65, 0x2e, 0x65, 0xf8, 0x1f, 0x6b, 0x5c, 0xd1, + 0x06, 0x8c, 0x52, 0x85, 0xd6, 0xad, 0x9f, 0x78, 0x53, 0x64, 0x97, 0x2c, 0x15, 0x46, 0x03, 0x0b, + 0x5a, 0xb4, 0xad, 0x3c, 0x12, 0x74, 0x3c, 0x87, 0x36, 0x2d, 0xdb, 0x06, 0xf3, 0x5c, 0x0a, 0xac, + 0x4a, 0xb1, 0x86, 0x61, 0xfe, 0xaf, 0x0c, 0xcc, 0x25, 0x89, 0x4e, 0x77, 0x9b, 0x51, 0x2e, 0xad, + 0x38, 0x1a, 0x7f, 0x28, 0xfd, 0xf6, 0x11, 0x9e, 0x20, 0xca, 0x5f, 0x42, 0xf8, 0xaa, 0x09, 0xbe, + 0xe8, 0x43, 0xaa, 0x85, 0x32, 0x27, 0x6c, 0x21, 0x45, 0x39, 0xd6, 0x4a, 0x0f, 0xc1, 0x88, 0x4f, + 0x7b, 0x3e, 0x1b, 0xbd, 0xc0, 0x60, 0x7d, 0xc4, 0x20, 0x14, 0xa3, 0xe3, 0xd8, 0x81, 0x70, 0xf5, + 0x56, 0x18, 0x37, 0x1c, 0x3b, 0xc0, 0x0c, 0x62, 0x7e, 0x2d, 0x03, 0x0b, 0xfd, 0x3f, 0x0a, 0x7d, + 0xcd, 0x00, 0xa8, 0xd3, 0xe3, 0x0a, 0x1d, 0x92, 0xd2, 0x73, 0xc6, 0x3a, 0xad, 0x36, 0x5c, 0x91, + 0x9c, 0x42, 0x37, 0x2a, 0x55, 0xe4, 0x63, 0x4d, 0x10, 0xf4, 0xb8, 0x1c, 0xfa, 0x57, 0xad, 0x96, + 0x54, 0x67, 0x55, 0x9d, 0x75, 0x05, 0xc1, 0x1a, 0x16, 0x3d, 0x8f, 0x3a, 0x56, 0x8b, 0xf8, 0x6d, + 0x4b, 0xf9, 0xf2, 0xb3, 0xf3, 0xe8, 0x55, 0x59, 0x88, 0x43, 0xb8, 0xd9, 0x84, 0x87, 0x07, 0x90, + 0x33, 0x25, 0xbf, 0x6a, 0xf3, 0xaf, 0x0d, 0x38, 0xb7, 0xdc, 0xec, 0xf8, 0x01, 0xf1, 0xfe, 0xd1, + 0x78, 0xa5, 0xfd, 0xad, 0x01, 0x0f, 0xf4, 0xf9, 0xe6, 0x7b, 0xe0, 0x9c, 0xf6, 0x72, 0xd4, 0x39, + 0xed, 0xc6, 0xb0, 0x43, 0x3a, 0xf1, 0x3b, 0xfa, 0xf8, 0xa8, 0x05, 0x30, 0x49, 0x57, 0xad, 0xba, + 0xdb, 0x48, 0x69, 0xdf, 0x7c, 0x18, 0x72, 0x9f, 0xa4, 0xfb, 0x4f, 0x7c, 0x8c, 0xb1, 0x4d, 0x09, + 0x73, 0x98, 0xf9, 0x2c, 0x08, 0x4f, 0xae, 0xd8, 0xe4, 0x31, 0x06, 0x99, 0x3c, 0xe6, 0x9f, 0x64, + 0x40, 0xb3, 0x63, 0xdc, 0x83, 0x41, 0xe9, 0x44, 0x06, 0xe5, 0x90, 0x67, 0x70, 0xcd, 0x2a, 0xd3, + 0x2f, 0x64, 0x63, 0x37, 0x16, 0xb2, 0x71, 0x35, 0x35, 0x8e, 0x87, 0x47, 0x6c, 0xfc, 0xd4, 0x80, + 0x07, 0x42, 0xe4, 0x5e, 0x13, 0xe3, 0xd1, 0x2b, 0xcc, 0x53, 0x30, 0x6e, 0x85, 0xd5, 0xc4, 0x18, + 0x50, 0x51, 0x4a, 0x1a, 0x45, 0xac, 0xe3, 0x85, 0x0e, 0xe2, 0xd9, 0x13, 0x3a, 0x88, 0x8f, 0x1c, + 0xee, 0x20, 0x6e, 0xfe, 0x36, 0x03, 0xe7, 0x7b, 0xbf, 0x4c, 0xce, 0x8d, 0xc1, 0x6e, 0xe0, 0x9f, + 0x86, 0x89, 0x40, 0x54, 0xd0, 0x56, 0x7a, 0x15, 0x63, 0xb7, 0xa1, 0xc1, 0x70, 0x04, 0x93, 0xd6, + 0xac, 0xf1, 0x59, 0x59, 0xad, 0xb9, 0x6d, 0x19, 0x5e, 0xa0, 0x6a, 0x2e, 0x6b, 0x30, 0x1c, 0xc1, + 0x54, 0x8e, 0x9b, 0x23, 0xa7, 0xee, 0xb8, 0x59, 0x85, 0xb3, 0xd2, 0x55, 0xed, 0x92, 0xeb, 0x2d, + 0xbb, 0xad, 0x76, 0x93, 0x88, 0x00, 0x03, 0x2a, 0xec, 0x79, 0x51, 0xe5, 0x2c, 0x4e, 0x42, 0xc2, + 0xc9, 0x75, 0xcd, 0x9f, 0x66, 0xe1, 0x4c, 0xd8, 0xec, 0xcb, 0xae, 0x53, 0xb7, 0x99, 0xc3, 0xdf, + 0x33, 0x30, 0x12, 0xec, 0xb7, 0x65, 0x63, 0xff, 0x13, 0x29, 0xce, 0xc6, 0x7e, 0x9b, 0xf6, 0xf6, + 0xb9, 0x84, 0x2a, 0xcc, 0xc8, 0xcb, 0x2a, 0xa1, 0x35, 0x35, 0x3b, 0x78, 0x0f, 0x3c, 0x19, 0x1d, + 0xcd, 0x77, 0xbb, 0xc5, 0x84, 0x10, 0xd3, 0x45, 0x45, 0x29, 0x3a, 0xe6, 0xd1, 0x6d, 0x98, 0x6a, + 0x5a, 0x7e, 0x70, 0xa3, 0x5d, 0xb7, 0x02, 0xb2, 0x61, 0xb7, 0x88, 0x98, 0x73, 0xc7, 0xf1, 0xda, + 0x57, 0xb7, 0xd2, 0x6b, 0x11, 0x4a, 0x38, 0x46, 0x19, 0xed, 0x02, 0xa2, 0x25, 0x1b, 0x9e, 0xe5, + 0xf8, 0xfc, 0xab, 0x28, 0xbf, 0xe3, 0x47, 0x09, 0xa8, 0x43, 0xde, 0x5a, 0x0f, 0x35, 0x9c, 0xc0, + 0x01, 0x3d, 0x02, 0xa3, 0x1e, 0xb1, 0x7c, 0xd1, 0x99, 0x85, 0x70, 0xfe, 0x63, 0x56, 0x8a, 0x05, + 0x54, 0x9f, 0x50, 0xa3, 0x47, 0x4c, 0xa8, 0x5f, 0x1a, 0x30, 0x15, 0x76, 0xd3, 0x3d, 0xd8, 0x24, + 0x5b, 0xd1, 0x4d, 0xf2, 0x72, 0x5a, 0x4b, 0x62, 0x9f, 0x7d, 0xf1, 0xff, 0x8d, 0xea, 0xdf, 0xc7, + 0xbc, 0xb6, 0x3f, 0x05, 0x05, 0x39, 0xab, 0xa5, 0xf6, 0x39, 0xe4, 0x59, 0x39, 0xa2, 0x97, 0x68, + 0xd1, 0x46, 0x82, 0x09, 0x0e, 0xf9, 0xd1, 0x6d, 0xb9, 0x2e, 0xb6, 0x5c, 0x31, 0xec, 0xd5, 0xb6, + 0x2c, 0xb7, 0xe2, 0xa4, 0x6d, 0x59, 0xd6, 0x41, 0x37, 0xe0, 0x5c, 0xdb, 0x73, 0x59, 0x04, 0xea, + 0x0a, 0xb1, 0xea, 0x4d, 0xdb, 0x21, 0xd2, 0x20, 0xc1, 0x9d, 0x22, 0x1e, 0x38, 0xe8, 0x16, 0xcf, + 0x55, 0x92, 0x51, 0x70, 0xbf, 0xba, 0xd1, 0xa8, 0xa9, 0x91, 0x01, 0xa2, 0xa6, 0xfe, 0xb5, 0x32, + 0xfb, 0x11, 0x5f, 0xc4, 0x2e, 0x7d, 0x24, 0xad, 0xae, 0x4c, 0x58, 0xd6, 0xc3, 0x21, 0x55, 0x12, + 0x4c, 0xb1, 0x62, 0xdf, 0xdf, 0xb6, 0x34, 0x7a, 0x42, 0xdb, 0x52, 0xe8, 0xfc, 0x3e, 0xf6, 0xfb, + 0x74, 0x7e, 0xcf, 0xbf, 0xad, 0x9c, 0xdf, 0x5f, 0xcf, 0xc1, 0x4c, 0x5c, 0x03, 0x39, 0xfd, 0x88, + 0xb0, 0x7f, 0x6b, 0xc0, 0x8c, 0x9c, 0x3d, 0x9c, 0x27, 0x91, 0xb7, 0x06, 0x6b, 0x29, 0x4d, 0x5a, + 0xae, 0x4b, 0xa9, 0x98, 0xe5, 0x8d, 0x18, 0x37, 0xdc, 0xc3, 0x1f, 0xbd, 0x04, 0xe3, 0xca, 0xb8, + 0x7e, 0xa2, 0xf0, 0xb0, 0x69, 0xa6, 0x45, 0x85, 0x24, 0xb0, 0x4e, 0x0f, 0xbd, 0x6e, 0x00, 0xd4, + 0xe4, 0x36, 0x27, 0x67, 0xd7, 0xf5, 0xb4, 0x66, 0x97, 0xda, 0x40, 0x43, 0x65, 0x59, 0x15, 0xf9, + 0x58, 0x63, 0x8c, 0xbe, 0xc2, 0xcc, 0xea, 0x4a, 0xbb, 0xa3, 0xf3, 0x29, 0x3b, 0xbc, 0x63, 0xef, + 0x21, 0x8a, 0x69, 0xa8, 0x4a, 0x69, 0x20, 0x1f, 0x47, 0x84, 0x30, 0x9f, 0x01, 0xe5, 0x8a, 0x49, + 0x97, 0x2d, 0xe6, 0x8c, 0x59, 0xb1, 0x82, 0x6d, 0x31, 0x04, 0xd5, 0xb2, 0x75, 0x49, 0x02, 0x70, + 0x88, 0x63, 0x7e, 0x02, 0xa6, 0x9e, 0xf7, 0xac, 0xf6, 0xb6, 0xcd, 0xcc, 0xd7, 0xf4, 0x9c, 0xf4, + 0x6e, 0x18, 0xb3, 0xea, 0xf5, 0xa4, 0x88, 0xff, 0x12, 0x2f, 0xc6, 0x12, 0x3e, 0xd8, 0x91, 0xe8, + 0x07, 0x06, 0xa0, 0xf0, 0x0a, 0xd0, 0x76, 0x1a, 0xeb, 0xf4, 0xb4, 0x4f, 0xcf, 0x47, 0xdb, 0xac, + 0x34, 0xe9, 0x7c, 0x74, 0x59, 0x41, 0xb0, 0x86, 0x85, 0x5e, 0x81, 0x71, 0xfe, 0xef, 0xa6, 0x3a, + 0xec, 0x0f, 0xed, 0xde, 0xcf, 0x37, 0x14, 0x26, 0x13, 0x1f, 0x85, 0x97, 0x43, 0x0e, 0x58, 0x67, + 0x47, 0x9b, 0x6a, 0xd5, 0xd9, 0x6a, 0x76, 0xee, 0xd4, 0x37, 0xc3, 0xa6, 0x6a, 0x7b, 0xee, 0x96, + 0xdd, 0x24, 0xf1, 0xa6, 0xaa, 0xf0, 0x62, 0x2c, 0xe1, 0x83, 0x35, 0xd5, 0xf7, 0x0c, 0x98, 0x5b, + 0xf5, 0x03, 0xdb, 0x5d, 0x21, 0x7e, 0x40, 0xb7, 0x15, 0xba, 0xf8, 0x74, 0x9a, 0x83, 0x78, 0x55, + 0xaf, 0xc0, 0x8c, 0xb8, 0x8e, 0xec, 0x6c, 0xfa, 0x24, 0xd0, 0xf4, 0x78, 0x35, 0x8f, 0x97, 0x63, + 0x70, 0xdc, 0x53, 0x83, 0x52, 0x11, 0xf7, 0x92, 0x21, 0x95, 0x6c, 0x94, 0x4a, 0x35, 0x06, 0xc7, + 0x3d, 0x35, 0xcc, 0x1f, 0x67, 0xe1, 0x0c, 0xfb, 0x8c, 0x58, 0x44, 0xc4, 0x97, 0xfa, 0x45, 0x44, + 0x0c, 0x39, 0x95, 0x19, 0xaf, 0x13, 0xc4, 0x43, 0xfc, 0x1b, 0x03, 0xa6, 0xeb, 0xd1, 0x96, 0x4e, + 0xc7, 0x3c, 0x93, 0xd4, 0x87, 0xdc, 0xfb, 0x2a, 0x56, 0x88, 0xe3, 0xfc, 0xd1, 0x57, 0x0d, 0x98, + 0x8e, 0x8a, 0x29, 0x57, 0xf7, 0x53, 0x68, 0x24, 0xe5, 0x2e, 0x1d, 0x2d, 0xf7, 0x71, 0x5c, 0x04, + 0xf3, 0x47, 0x19, 0xd1, 0xa5, 0xa7, 0xe1, 0xee, 0x8f, 0xf6, 0xa0, 0x10, 0x34, 0x7d, 0x5e, 0x28, + 0xbe, 0x76, 0xc8, 0x13, 0xe1, 0xc6, 0x5a, 0x95, 0x7b, 0x02, 0x84, 0x4a, 0x9b, 0x28, 0xa1, 0xca, + 0xa7, 0xe4, 0xc5, 0x18, 0xd7, 0xda, 0x82, 0x71, 0x2a, 0x47, 0xd1, 0x8d, 0xe5, 0x4a, 0x9c, 0xb1, + 0x28, 0xa1, 0x8c, 0x25, 0x2f, 0xf3, 0x9b, 0x06, 0x14, 0xae, 0xb8, 0x72, 0x1d, 0xf9, 0x58, 0x0a, + 0x86, 0x1e, 0xa5, 0x0f, 0xaa, 0x1b, 0xc7, 0xf0, 0x88, 0xf1, 0x5c, 0xc4, 0xcc, 0xf3, 0xa0, 0x46, + 0x7b, 0x91, 0x65, 0x33, 0xa2, 0xa4, 0xae, 0xb8, 0x9b, 0x7d, 0xad, 0x88, 0xff, 0x39, 0x07, 0x93, + 0x2f, 0x58, 0xfb, 0xc4, 0x09, 0xac, 0xe3, 0x6f, 0x12, 0x4f, 0xc1, 0xb8, 0xd5, 0x66, 0x57, 0x5a, + 0x9a, 0x8e, 0x1f, 0x5a, 0x4e, 0x42, 0x10, 0xd6, 0xf1, 0xc2, 0x05, 0x8d, 0x27, 0x57, 0x49, 0x5a, + 0x8a, 0x96, 0x63, 0x70, 0xdc, 0x53, 0x03, 0x5d, 0x01, 0x24, 0x42, 0x45, 0x4b, 0xb5, 0x9a, 0xdb, + 0x71, 0xf8, 0x92, 0xc6, 0x8d, 0x2a, 0xea, 0xb0, 0xb9, 0xde, 0x83, 0x81, 0x13, 0x6a, 0xa1, 0x8f, + 0xc2, 0x7c, 0x8d, 0x51, 0x16, 0x47, 0x0f, 0x9d, 0x22, 0x3f, 0x7e, 0x2a, 0x97, 0xff, 0xe5, 0x3e, + 0x78, 0xb8, 0x2f, 0x05, 0x2a, 0xa9, 0x1f, 0xb8, 0x9e, 0xd5, 0x20, 0x3a, 0xdd, 0xd1, 0xa8, 0xa4, + 0xd5, 0x1e, 0x0c, 0x9c, 0x50, 0x0b, 0x7d, 0x06, 0x0a, 0xc1, 0xb6, 0x47, 0xfc, 0x6d, 0xb7, 0x59, + 0x17, 0x2e, 0x08, 0x43, 0x5a, 0xda, 0x44, 0xef, 0x6f, 0x48, 0xaa, 0xda, 0xf0, 0x96, 0x45, 0x38, + 0xe4, 0x89, 0x3c, 0x18, 0xf5, 0x6b, 0x6e, 0x9b, 0xf8, 0x42, 0x65, 0xbf, 0x92, 0x0a, 0x77, 0x66, + 0x39, 0xd2, 0x6c, 0x7c, 0x8c, 0x03, 0x16, 0x9c, 0xcc, 0xef, 0x67, 0x60, 0x42, 0x47, 0x1c, 0x60, + 0x6d, 0xfa, 0x9c, 0x01, 0x13, 0x35, 0xd7, 0x09, 0x3c, 0xb7, 0xc9, 0xed, 0x57, 0xe9, 0x68, 0x14, + 0x94, 0xd4, 0x0a, 0x09, 0x2c, 0xbb, 0xa9, 0x99, 0xc2, 0x34, 0x36, 0x38, 0xc2, 0x14, 0x7d, 0xd1, + 0x80, 0xe9, 0xd0, 0x63, 0x2d, 0x34, 0xa4, 0xa5, 0x2a, 0x88, 0x5a, 0xea, 0x2f, 0x46, 0x39, 0xe1, + 0x38, 0x6b, 0x73, 0x13, 0x66, 0xe2, 0xbd, 0x4d, 0x9b, 0xb2, 0x6d, 0x89, 0xb9, 0x9e, 0x0d, 0x9b, + 0xb2, 0x62, 0xf9, 0x3e, 0x66, 0x10, 0xf4, 0x18, 0xe4, 0x5b, 0x96, 0xd7, 0xb0, 0x1d, 0xab, 0xc9, + 0x5a, 0x31, 0xab, 0x2d, 0x48, 0xa2, 0x1c, 0x2b, 0x0c, 0xf3, 0x7d, 0x30, 0xb1, 0x6e, 0x39, 0x0d, + 0x52, 0x17, 0xeb, 0xf0, 0xd1, 0x01, 0x67, 0xbf, 0x1e, 0x81, 0x71, 0xed, 0x6c, 0x76, 0xfa, 0xe7, + 0xac, 0x48, 0xbe, 0x8b, 0x6c, 0x8a, 0xf9, 0x2e, 0x5e, 0x04, 0xd8, 0xb2, 0x1d, 0xdb, 0xdf, 0x3e, + 0x61, 0x26, 0x0d, 0x76, 0x45, 0x7b, 0x49, 0x51, 0xc0, 0x1a, 0xb5, 0xf0, 0x1e, 0x2c, 0x77, 0x48, + 0x7e, 0xa1, 0xd7, 0x0d, 0x6d, 0xbb, 0x19, 0x4d, 0xe3, 0xde, 0x5f, 0xeb, 0x98, 0x45, 0xb9, 0xfd, + 0x5c, 0x74, 0x02, 0x6f, 0xff, 0xd0, 0x5d, 0x69, 0x03, 0xf2, 0x1e, 0xf1, 0x3b, 0x2d, 0x7a, 0x62, + 0x1c, 0x3b, 0x76, 0x33, 0x30, 0x0f, 0x0c, 0x2c, 0xea, 0x63, 0x45, 0x69, 0xe1, 0x19, 0x98, 0x8c, + 0x88, 0x80, 0x66, 0x20, 0xbb, 0x43, 0xf6, 0xf9, 0x38, 0xc1, 0xf4, 0x27, 0x9a, 0x8b, 0xdc, 0x16, + 0x8a, 0x66, 0xf9, 0x60, 0xe6, 0x69, 0xc3, 0x74, 0x21, 0xd1, 0x00, 0x70, 0x92, 0xcb, 0x1c, 0xda, + 0x17, 0x4d, 0x2d, 0x95, 0x86, 0xea, 0x0b, 0xee, 0x67, 0xc3, 0x61, 0xe6, 0x6f, 0x47, 0x41, 0x5c, + 0x65, 0x0f, 0xb0, 0x5c, 0xe9, 0x37, 0x58, 0x99, 0x13, 0xdc, 0x60, 0x5d, 0x81, 0x09, 0xdb, 0xb1, + 0x03, 0xdb, 0x6a, 0x32, 0xe3, 0x8e, 0xd8, 0x4e, 0xa5, 0x23, 0xf2, 0xc4, 0xaa, 0x06, 0x4b, 0xa0, + 0x13, 0xa9, 0x8b, 0xae, 0x43, 0x8e, 0xed, 0x37, 0x62, 0x00, 0x1f, 0xff, 0xbe, 0x9d, 0xb9, 0x5a, + 0xf0, 0xe8, 0x24, 0x4e, 0x89, 0x1d, 0x3e, 0x78, 0x2e, 0x11, 0x75, 0xfc, 0x16, 0xe3, 0x38, 0x3c, + 0x7c, 0xc4, 0xe0, 0xb8, 0xa7, 0x06, 0xa5, 0xb2, 0x65, 0xd9, 0xcd, 0x8e, 0x47, 0x42, 0x2a, 0xa3, + 0x51, 0x2a, 0x97, 0x62, 0x70, 0xdc, 0x53, 0x03, 0x6d, 0xc1, 0x84, 0x28, 0xe3, 0xde, 0x53, 0x63, + 0x27, 0xfc, 0x4a, 0xe6, 0x25, 0x77, 0x49, 0xa3, 0x84, 0x23, 0x74, 0x51, 0x07, 0x66, 0x6d, 0xa7, + 0xe6, 0x3a, 0xb5, 0x66, 0xc7, 0xb7, 0x77, 0x49, 0x18, 0x1a, 0x74, 0x12, 0x66, 0x67, 0x0f, 0xba, + 0xc5, 0xd9, 0xd5, 0x38, 0x39, 0xdc, 0xcb, 0x01, 0xbd, 0x66, 0xc0, 0xd9, 0x9a, 0xeb, 0xf8, 0x2c, + 0x38, 0x7f, 0x97, 0x5c, 0xf4, 0x3c, 0xd7, 0xe3, 0xbc, 0x0b, 0x27, 0xe4, 0xcd, 0x6c, 0x8a, 0xcb, + 0x49, 0x24, 0x71, 0x32, 0x27, 0xf4, 0x32, 0xe4, 0xdb, 0x9e, 0xbb, 0x6b, 0xd7, 0x89, 0x27, 0x3c, + 0xf1, 0xd6, 0xd2, 0x48, 0x16, 0x52, 0x11, 0x34, 0xc3, 0xa5, 0x47, 0x96, 0x60, 0xc5, 0xcf, 0xfc, + 0x3b, 0x80, 0xa9, 0x28, 0x3a, 0xfa, 0x34, 0x40, 0xdb, 0x73, 0x5b, 0x24, 0xd8, 0x26, 0x2a, 0xc4, + 0xe3, 0xea, 0xb0, 0x39, 0x29, 0x24, 0x3d, 0xe9, 0xbd, 0x42, 0x97, 0x8b, 0xb0, 0x14, 0x6b, 0x1c, + 0x91, 0x07, 0x63, 0x3b, 0x7c, 0xdb, 0x15, 0x5a, 0xc8, 0x0b, 0xa9, 0xe8, 0x4c, 0x82, 0x33, 0x8b, + 0x4d, 0x10, 0x45, 0x58, 0x32, 0x42, 0x9b, 0x90, 0xdd, 0x23, 0x9b, 0xe9, 0x04, 0x44, 0xdf, 0x22, + 0xe2, 0x34, 0x53, 0x1e, 0x3b, 0xe8, 0x16, 0xb3, 0xb7, 0xc8, 0x26, 0xa6, 0xc4, 0xe9, 0x77, 0xd5, + 0xf9, 0x3d, 0xbc, 0x58, 0x2a, 0x86, 0xfc, 0xae, 0xc8, 0xa5, 0x3e, 0xff, 0x2e, 0x51, 0x84, 0x25, + 0x23, 0xf4, 0x32, 0x14, 0xf6, 0xac, 0x5d, 0xb2, 0xe5, 0xb9, 0x4e, 0x20, 0x5c, 0xa6, 0x86, 0xf4, + 0xfa, 0xbf, 0x25, 0xc9, 0x09, 0xbe, 0x6c, 0x7b, 0x57, 0x85, 0x38, 0x64, 0x87, 0x76, 0x21, 0xef, + 0x90, 0x3d, 0x4c, 0x9a, 0x76, 0x2d, 0x1d, 0x2f, 0xfb, 0xab, 0x82, 0x9a, 0xe0, 0xcc, 0xf6, 0x3d, + 0x59, 0x86, 0x15, 0x2f, 0xda, 0x97, 0xb7, 0xdd, 0x4d, 0xb1, 0x50, 0x0d, 0xd9, 0x97, 0xea, 0x64, + 0xca, 0xfb, 0xf2, 0x8a, 0xbb, 0x89, 0x29, 0x71, 0x3a, 0x47, 0x6a, 0xca, 0x5f, 0x47, 0x2c, 0x53, + 0x57, 0xd3, 0xf5, 0x53, 0xe2, 0x73, 0x24, 0x2c, 0xc5, 0x1a, 0x47, 0xda, 0xb6, 0x0d, 0x61, 0xac, + 0x14, 0x0b, 0xd5, 0x90, 0x6d, 0x1b, 0x35, 0x7d, 0xf2, 0xb6, 0x95, 0x65, 0x58, 0xf1, 0xa2, 0x7c, + 0x6d, 0x61, 0xf9, 0x4b, 0x67, 0xa9, 0x8a, 0xda, 0x11, 0x39, 0x5f, 0x59, 0x86, 0x15, 0x2f, 0xf4, + 0x1f, 0x0d, 0x18, 0x6d, 0x37, 0x3b, 0x0d, 0xdb, 0x99, 0x1f, 0x67, 0x7a, 0xda, 0x87, 0xd2, 0x5c, + 0x21, 0x17, 0x2b, 0x8c, 0x34, 0x57, 0xd4, 0xde, 0xa3, 0xdc, 0xdf, 0x58, 0xe1, 0x17, 0x7e, 0x55, + 0x9c, 0x27, 0x4e, 0xcd, 0xad, 0xdb, 0x4e, 0x63, 0xe9, 0xb6, 0xef, 0x3a, 0x8b, 0xd8, 0xda, 0x93, + 0x3a, 0xb2, 0x90, 0x69, 0xe1, 0x03, 0x30, 0xae, 0x91, 0x38, 0x4a, 0xd1, 0x9a, 0xd0, 0x15, 0xad, + 0x6f, 0x8e, 0xc2, 0x84, 0x9e, 0x6e, 0x6e, 0x00, 0xed, 0x47, 0x69, 0xfc, 0x99, 0xe3, 0x68, 0xfc, + 0xf4, 0x88, 0xa7, 0xdd, 0xde, 0x48, 0xf3, 0xd2, 0x6a, 0x6a, 0x0a, 0x6f, 0x78, 0xc4, 0xd3, 0x0a, + 0x7d, 0x1c, 0x61, 0x7a, 0x0c, 0x87, 0x0e, 0xaa, 0x36, 0x72, 0xc5, 0x2a, 0x17, 0x55, 0x1b, 0x23, + 0xaa, 0xd2, 0xe3, 0x00, 0x61, 0xda, 0x35, 0x71, 0xab, 0xa7, 0xf4, 0x51, 0x2d, 0x1d, 0x9c, 0x86, + 0x85, 0x1e, 0x81, 0x51, 0xaa, 0x7a, 0x90, 0xba, 0x88, 0x68, 0x56, 0xe7, 0xe8, 0x4b, 0xac, 0x14, + 0x0b, 0x28, 0x7a, 0x9a, 0x6a, 0x89, 0xa1, 0xc2, 0x20, 0x02, 0x95, 0xe7, 0x42, 0x2d, 0x31, 0x84, + 0xe1, 0x08, 0x26, 0x15, 0x9d, 0xd0, 0xfd, 0x9d, 0xcd, 0x4d, 0x4d, 0x74, 0xb6, 0xe9, 0x63, 0x0e, + 0x63, 0x76, 0x9d, 0x98, 0x3e, 0xc0, 0xe6, 0x54, 0x4e, 0xb3, 0xeb, 0xc4, 0xe0, 0xb8, 0xa7, 0x06, + 0xfd, 0x18, 0x71, 0x21, 0x39, 0xce, 0xfd, 0x56, 0xfb, 0x5c, 0x25, 0xbe, 0xa1, 0x9f, 0x75, 0x26, + 0xd2, 0x9b, 0x43, 0x7c, 0xd4, 0x0e, 0x7e, 0xd8, 0x19, 0xee, 0x58, 0xf2, 0x09, 0x98, 0x8a, 0xee, + 0x02, 0xa9, 0xdf, 0x3c, 0xfc, 0xff, 0x2c, 0x9c, 0xb9, 0xda, 0xb0, 0x9d, 0x78, 0x2a, 0xa5, 0xa4, + 0x94, 0xc6, 0xc6, 0x71, 0x53, 0x1a, 0x87, 0xa1, 0x51, 0x22, 0x67, 0x74, 0x72, 0x68, 0x94, 0x4c, + 0x28, 0x1d, 0xc5, 0x45, 0xbf, 0x34, 0xe0, 0x41, 0xab, 0xce, 0xf5, 0x72, 0xab, 0x29, 0x4a, 0x43, + 0xa6, 0x72, 0x46, 0xfb, 0x43, 0xee, 0xb2, 0xbd, 0x1f, 0xbf, 0x58, 0x3a, 0x84, 0x2b, 0xef, 0xf1, + 0x77, 0x89, 0x2f, 0x78, 0xf0, 0x30, 0x54, 0x7c, 0xa8, 0xf8, 0x0b, 0xd7, 0xe0, 0x9d, 0x47, 0x32, + 0x3a, 0xd6, 0x68, 0xf9, 0x9c, 0x01, 0x05, 0x6e, 0x18, 0xc6, 0x64, 0x8b, 0x2e, 0x15, 0x56, 0xdb, + 0xbe, 0x49, 0x3c, 0x5f, 0xe6, 0x5a, 0xd3, 0x8e, 0xae, 0xa5, 0xca, 0xaa, 0x80, 0x60, 0x0d, 0x8b, + 0x2e, 0xc6, 0x3b, 0xb6, 0x53, 0x17, 0xdd, 0xa4, 0x16, 0xe3, 0x17, 0x6c, 0xa7, 0x8e, 0x19, 0x44, + 0x2d, 0xd7, 0xd9, 0xbe, 0x06, 0x9b, 0x6f, 0x18, 0x30, 0xc5, 0xe2, 0x41, 0xc3, 0x43, 0xd5, 0x53, + 0xca, 0x5b, 0x87, 0x8b, 0x71, 0x3e, 0xea, 0xad, 0x73, 0xb7, 0x5b, 0x1c, 0xe7, 0x11, 0xa4, 0x51, + 0xe7, 0x9d, 0x8f, 0x08, 0x4b, 0x0c, 0xf3, 0x29, 0xca, 0x1c, 0xdb, 0x50, 0xa0, 0x2c, 0x95, 0x55, + 0x49, 0x04, 0x87, 0xf4, 0xcc, 0x57, 0x60, 0x42, 0x0f, 0xec, 0x40, 0x4f, 0xc1, 0x78, 0xdb, 0x76, + 0x1a, 0xd1, 0x00, 0x40, 0x65, 0xad, 0xae, 0x84, 0x20, 0xac, 0xe3, 0xb1, 0x6a, 0x6e, 0x58, 0x2d, + 0x66, 0xe4, 0xae, 0xb8, 0x7a, 0xb5, 0xf0, 0x8f, 0xf9, 0x3f, 0xb2, 0x70, 0x26, 0x21, 0x80, 0x08, + 0xbd, 0x6e, 0xc0, 0x28, 0x8b, 0x3f, 0x90, 0xfe, 0x38, 0x2f, 0xa5, 0x1e, 0xa4, 0xb4, 0xc8, 0xc2, + 0x1c, 0xc4, 0x38, 0x56, 0xcb, 0x27, 0x2f, 0xc4, 0x82, 0x39, 0xfa, 0xf7, 0x06, 0x8c, 0x5b, 0xda, + 0x54, 0xe3, 0x2e, 0x4a, 0x9b, 0xe9, 0x0b, 0xd3, 0x33, 0xb3, 0x34, 0xd7, 0xca, 0x70, 0x22, 0xe9, + 0xb2, 0x50, 0xed, 0x43, 0xfb, 0x84, 0xe3, 0xcc, 0x90, 0x85, 0xe7, 0x60, 0x66, 0xa8, 0x19, 0xf6, + 0x61, 0x38, 0x6e, 0xea, 0x40, 0xba, 0x61, 0xed, 0xe9, 0x41, 0xda, 0xaa, 0xc5, 0x45, 0x94, 0xb6, + 0x80, 0x9a, 0x9b, 0x30, 0x13, 0x3f, 0x36, 0xa6, 0x7e, 0x23, 0xff, 0x3e, 0x38, 0x66, 0xb2, 0x3f, + 0xf3, 0x22, 0x20, 0xec, 0x36, 0x9b, 0x9b, 0x56, 0x6d, 0xe7, 0x96, 0xed, 0xd4, 0xdd, 0x3d, 0x36, + 0x57, 0x96, 0xa0, 0xe0, 0x89, 0xf8, 0x30, 0x5f, 0x7c, 0x96, 0x9a, 0x6c, 0x32, 0x70, 0xcc, 0xc7, + 0x21, 0x8e, 0xf9, 0xa3, 0x0c, 0x8c, 0x89, 0x60, 0xc6, 0x7b, 0xe0, 0xdc, 0xbc, 0x13, 0xb9, 0xf5, + 0x5a, 0x4d, 0x25, 0x06, 0xb3, 0xaf, 0x67, 0xb3, 0x1f, 0xf3, 0x6c, 0x7e, 0x21, 0x1d, 0x76, 0x87, + 0xbb, 0x35, 0x7f, 0x63, 0x04, 0xa6, 0x63, 0xc1, 0xa1, 0x54, 0xe3, 0xe9, 0xf1, 0xe6, 0xbb, 0x91, + 0x6a, 0xfc, 0xa9, 0x72, 0xbc, 0x3f, 0xdc, 0xb1, 0xcf, 0x8f, 0xa4, 0x66, 0xbd, 0x9e, 0x5a, 0x56, + 0xf7, 0x3f, 0x64, 0x69, 0x3d, 0xae, 0xa3, 0xda, 0x9f, 0x19, 0x70, 0x7f, 0xdf, 0x18, 0x62, 0x96, + 0x82, 0xc6, 0x8b, 0x42, 0xc5, 0x84, 0x4c, 0x39, 0x53, 0x82, 0xba, 0x82, 0x8a, 0x67, 0x0d, 0x89, + 0xb3, 0x47, 0x4f, 0xc2, 0x04, 0xdb, 0xa1, 0xe9, 0xd2, 0x14, 0x90, 0xb6, 0xb0, 0xa0, 0x33, 0x5b, + 0x6a, 0x55, 0x2b, 0xc7, 0x11, 0x2c, 0xf3, 0xeb, 0x06, 0xcc, 0xf7, 0x4b, 0x48, 0x32, 0xc0, 0xf9, + 0xf2, 0x9f, 0xc7, 0xbc, 0xaf, 0x8b, 0x3d, 0xde, 0xd7, 0xb1, 0x13, 0xa6, 0x74, 0xb4, 0xd6, 0x0e, + 0x77, 0xd9, 0x23, 0x9c, 0x8b, 0xbf, 0x64, 0xc0, 0xb9, 0x3e, 0xb3, 0xa9, 0xc7, 0x0b, 0xdf, 0x38, + 0xb1, 0x17, 0x7e, 0x66, 0x50, 0x2f, 0x7c, 0xf3, 0x27, 0x59, 0x98, 0x11, 0xf2, 0x84, 0x6a, 0xda, + 0xd3, 0x11, 0x1f, 0xf6, 0x77, 0xc5, 0x7c, 0xd8, 0xe7, 0xe2, 0xf8, 0x7f, 0x70, 0x60, 0x7f, 0x7b, + 0x39, 0xb0, 0xff, 0x2e, 0x03, 0x67, 0x13, 0xf3, 0xa4, 0xa0, 0xcf, 0x27, 0x6c, 0x0d, 0xb7, 0x52, + 0x4e, 0xc8, 0x32, 0xe0, 0xe6, 0x30, 0xac, 0xd7, 0xf7, 0x57, 0x75, 0x6f, 0x6b, 0xbe, 0xd4, 0x6f, + 0x9d, 0x42, 0x6a, 0x99, 0x63, 0x3a, 0x5e, 0x9b, 0x5f, 0xc8, 0xc2, 0xa3, 0x83, 0x12, 0x7a, 0x9b, + 0x06, 0xe6, 0xf8, 0x91, 0xc0, 0x9c, 0x7b, 0xb4, 0x6d, 0x9f, 0x4a, 0x8c, 0xce, 0x37, 0xb3, 0x6a, + 0xdb, 0xeb, 0x1d, 0x9f, 0x03, 0x5d, 0xb7, 0x8e, 0x51, 0xd5, 0x4e, 0x66, 0x4f, 0x0d, 0x97, 0xc2, + 0xb1, 0x2a, 0x2f, 0xbe, 0xdb, 0x2d, 0xce, 0x86, 0xd1, 0xfa, 0xa2, 0x10, 0xcb, 0x4a, 0xe8, 0x51, + 0xc8, 0x7b, 0x1c, 0x2a, 0x43, 0x11, 0xc4, 0x9d, 0x35, 0x2f, 0xc3, 0x0a, 0x8a, 0x3e, 0xa3, 0xe9, + 0xc2, 0x23, 0xa7, 0x95, 0x94, 0xe2, 0xb0, 0xab, 0xf8, 0x97, 0x20, 0xef, 0xcb, 0x3c, 0xa8, 0xfc, + 0xbe, 0xe4, 0x89, 0x01, 0x23, 0x5c, 0xe8, 0x09, 0x4c, 0x26, 0x45, 0xe5, 0xdf, 0xa7, 0x52, 0xa6, + 0x2a, 0x92, 0xc8, 0x54, 0x87, 0x1f, 0x6e, 0xaa, 0x84, 0x84, 0x83, 0xcf, 0x4f, 0x0d, 0x18, 0x17, + 0xbd, 0x75, 0x0f, 0x82, 0x6e, 0x6e, 0x47, 0x83, 0x6e, 0x2e, 0xa6, 0xb2, 0x76, 0xf4, 0x89, 0xb8, + 0xb9, 0x0d, 0x13, 0x7a, 0xaa, 0x2c, 0xf4, 0xa2, 0xb6, 0xf6, 0x19, 0xc3, 0x24, 0x9f, 0x91, 0xab, + 0x63, 0xb8, 0x2e, 0x9a, 0xdf, 0x2a, 0xa8, 0x56, 0x64, 0x47, 0x34, 0x7d, 0x0c, 0x1a, 0x87, 0x8e, + 0x41, 0x7d, 0x08, 0x64, 0xd2, 0x1f, 0x02, 0xd7, 0x21, 0x2f, 0x17, 0x28, 0xb1, 0x8d, 0x3f, 0xac, + 0xbb, 0x21, 0x52, 0x5d, 0x80, 0x12, 0xd3, 0x06, 0x2e, 0x3b, 0x6a, 0xa9, 0x3e, 0x54, 0x0b, 0xa7, + 0x22, 0x83, 0x5e, 0x86, 0xf1, 0x3d, 0xd7, 0xdb, 0x69, 0xba, 0x16, 0xcb, 0x70, 0x0c, 0x69, 0xdc, + 0x7c, 0x29, 0xbb, 0x19, 0xf7, 0x05, 0xbf, 0x15, 0xd2, 0xc7, 0x3a, 0x33, 0x54, 0x82, 0xe9, 0x96, + 0xed, 0x60, 0x62, 0xd5, 0x55, 0x6c, 0xcd, 0x08, 0x4f, 0xc1, 0x2a, 0x95, 0xdc, 0xf5, 0x28, 0x18, + 0xc7, 0xf1, 0xd1, 0x17, 0x0d, 0x98, 0xf2, 0x22, 0x87, 0x6a, 0x91, 0x67, 0xb1, 0x32, 0xfc, 0x60, + 0x8c, 0x1e, 0xd4, 0xb9, 0x33, 0x74, 0xb4, 0x1c, 0xc7, 0x78, 0xa3, 0x4f, 0x41, 0xde, 0x17, 0x79, + 0xb0, 0xd2, 0xb9, 0x32, 0x55, 0x47, 0x58, 0x4e, 0x34, 0xec, 0x4a, 0x59, 0x82, 0x15, 0x43, 0xb4, + 0x06, 0x73, 0xd2, 0x4a, 0x10, 0x79, 0x85, 0x66, 0x34, 0x4c, 0x9b, 0x82, 0x13, 0xe0, 0x38, 0xb1, + 0x16, 0x55, 0xaa, 0x58, 0x0a, 0x3a, 0x7e, 0xd3, 0xa1, 0x5d, 0x0e, 0xb0, 0xf9, 0x57, 0xc7, 0x02, + 0x7a, 0x58, 0xe8, 0x58, 0x7e, 0x88, 0xd0, 0xb1, 0x2a, 0x9c, 0x8d, 0x83, 0x58, 0x3e, 0x1c, 0x96, + 0x82, 0x47, 0xdb, 0xcc, 0x2a, 0x49, 0x48, 0x38, 0xb9, 0x2e, 0xba, 0x05, 0x05, 0x8f, 0xb0, 0xe3, + 0x4e, 0x49, 0x3a, 0x69, 0x1c, 0xdb, 0x1d, 0x0d, 0x4b, 0x02, 0x38, 0xa4, 0x45, 0xfb, 0xdd, 0x8a, + 0x26, 0x45, 0xbd, 0x9e, 0xe2, 0x3b, 0x7a, 0xa2, 0xef, 0xfb, 0xe4, 0xa9, 0x32, 0xdf, 0x9a, 0x82, + 0xc9, 0x88, 0xa9, 0x03, 0x3d, 0x0c, 0x39, 0x96, 0x20, 0x88, 0xad, 0x56, 0xf9, 0x70, 0x45, 0xe5, + 0x8d, 0xc3, 0x61, 0xe8, 0xcb, 0x06, 0x4c, 0xb7, 0x23, 0xb6, 0x65, 0xb9, 0x90, 0x0f, 0x79, 0x2f, + 0x1b, 0x35, 0x58, 0x6b, 0xe9, 0xc4, 0xa3, 0xcc, 0x70, 0x9c, 0x3b, 0x5d, 0x0f, 0x84, 0x4f, 0x67, + 0x93, 0x78, 0x0c, 0x5b, 0xa8, 0x5c, 0x8a, 0xc4, 0x72, 0x14, 0x8c, 0xe3, 0xf8, 0xb4, 0x87, 0xd9, + 0xd7, 0x0d, 0xf3, 0xc0, 0x56, 0x49, 0x12, 0xc0, 0x21, 0x2d, 0xf4, 0x1c, 0x4c, 0x89, 0x5c, 0x98, + 0x15, 0xb7, 0x7e, 0xd9, 0xf2, 0xb7, 0xc5, 0x59, 0x43, 0x9d, 0x8d, 0x96, 0x23, 0x50, 0x1c, 0xc3, + 0x66, 0xdf, 0x16, 0x26, 0x1c, 0x65, 0x04, 0x46, 0xa3, 0xd9, 0xd6, 0x97, 0xa3, 0x60, 0x1c, 0xc7, + 0x47, 0x8f, 0x69, 0xdb, 0x10, 0xbf, 0x7d, 0x54, 0xab, 0x41, 0xc2, 0x56, 0x54, 0x82, 0xe9, 0x0e, + 0x3b, 0x9a, 0xd5, 0x25, 0x50, 0xcc, 0x47, 0xc5, 0xf0, 0x46, 0x14, 0x8c, 0xe3, 0xf8, 0xe8, 0x19, + 0x98, 0xf4, 0xe8, 0x62, 0xab, 0x08, 0xf0, 0x2b, 0x49, 0x75, 0xe3, 0x84, 0x75, 0x20, 0x8e, 0xe2, + 0xa2, 0xe7, 0x61, 0x36, 0x4c, 0x1d, 0x27, 0x09, 0xf0, 0x3b, 0x4a, 0x95, 0xc7, 0xa8, 0x14, 0x47, + 0xc0, 0xbd, 0x75, 0xd0, 0xbf, 0x84, 0x19, 0xad, 0x25, 0x56, 0x9d, 0x3a, 0xb9, 0x23, 0xd2, 0x7b, + 0xb1, 0x87, 0x31, 0x96, 0x63, 0x30, 0xdc, 0x83, 0x8d, 0x3e, 0x08, 0x53, 0x35, 0xb7, 0xd9, 0x64, + 0x6b, 0x1c, 0xcf, 0xf4, 0xcd, 0xf3, 0x78, 0xf1, 0x8c, 0x67, 0x11, 0x08, 0x8e, 0x61, 0xa2, 0x2b, + 0x80, 0xdc, 0x4d, 0x9f, 0x78, 0xbb, 0xa4, 0xfe, 0x3c, 0x7f, 0xb2, 0x97, 0x6a, 0x1c, 0x93, 0x51, + 0x8f, 0xf2, 0x6b, 0x3d, 0x18, 0x38, 0xa1, 0x16, 0x4b, 0x83, 0xa4, 0x45, 0xe0, 0x4d, 0xa5, 0xf1, + 0xd8, 0x54, 0xdc, 0x90, 0x70, 0x64, 0xf8, 0x9d, 0x07, 0xa3, 0xdc, 0xc1, 0x3f, 0x9d, 0x84, 0x5e, + 0x7a, 0xd2, 0xdf, 0x70, 0x8f, 0xe0, 0xa5, 0x58, 0x70, 0x42, 0x9f, 0x86, 0xc2, 0xa6, 0xcc, 0x00, + 0xcf, 0xb2, 0x78, 0x0d, 0xbd, 0x2f, 0xc6, 0x1e, 0x33, 0x08, 0x0f, 0xca, 0x0a, 0x80, 0x43, 0x96, + 0xe8, 0x11, 0x18, 0xbf, 0x5c, 0x29, 0xa9, 0x51, 0x38, 0xcb, 0x7a, 0x7f, 0x84, 0x56, 0xc1, 0x3a, + 0x80, 0xce, 0x30, 0xa5, 0xbe, 0x21, 0xd6, 0xc5, 0xe1, 0x7e, 0xdb, 0xab, 0x8d, 0x51, 0x6c, 0x76, + 0xc9, 0x8a, 0xab, 0xf3, 0x67, 0x62, 0xd8, 0xa2, 0x1c, 0x2b, 0x0c, 0xf4, 0x12, 0x8c, 0x8b, 0xfd, + 0x82, 0xad, 0x4d, 0x73, 0x27, 0x8b, 0xee, 0xc4, 0x21, 0x09, 0xac, 0xd3, 0x63, 0x77, 0x67, 0x2c, + 0x31, 0x36, 0xb9, 0xd4, 0x69, 0x36, 0xe7, 0xcf, 0xb2, 0x75, 0x33, 0xbc, 0x3b, 0x0b, 0x41, 0x58, + 0xc7, 0x43, 0x4f, 0x48, 0x7f, 0x90, 0x77, 0x44, 0x2e, 0x13, 0x95, 0x3f, 0x88, 0x52, 0xba, 0xfb, + 0x38, 0x80, 0x9f, 0x3b, 0xc2, 0x11, 0x63, 0x13, 0x16, 0xa4, 0xc6, 0xd7, 0x3b, 0x49, 0xe6, 0xe7, + 0x23, 0x46, 0x8b, 0x85, 0x5b, 0x7d, 0x31, 0xf1, 0x21, 0x54, 0xd0, 0x26, 0x64, 0xad, 0xe6, 0xe6, + 0xfc, 0xfd, 0x69, 0xa8, 0xae, 0xea, 0x09, 0x6e, 0xee, 0xb4, 0x55, 0x5a, 0x2b, 0x63, 0x4a, 0xdc, + 0x7c, 0x2d, 0xa3, 0x2e, 0x09, 0x54, 0xa2, 0xd3, 0x57, 0xf4, 0x51, 0x6d, 0xa4, 0xf1, 0xc4, 0x6c, + 0xcf, 0x33, 0x09, 0x7c, 0x43, 0x4a, 0x1c, 0xd3, 0x6d, 0x35, 0x8f, 0x53, 0xc9, 0x3b, 0x13, 0x4d, + 0xe2, 0xca, 0x0f, 0x97, 0xd1, 0x59, 0x6c, 0xfe, 0x22, 0xaf, 0x6c, 0x62, 0x31, 0x07, 0x07, 0x0f, + 0x72, 0xb6, 0x1f, 0xd8, 0x6e, 0x8a, 0x91, 0x88, 0xb1, 0xec, 0xa7, 0xcc, 0xd1, 0x99, 0x01, 0x30, + 0x67, 0x45, 0x79, 0x3a, 0x0d, 0xdb, 0xb9, 0x23, 0x3e, 0xff, 0x7a, 0xea, 0x9e, 0x0b, 0x9c, 0x27, + 0x03, 0x60, 0xce, 0x0a, 0xdd, 0xe6, 0x23, 0x2d, 0x9d, 0xe7, 0x84, 0xe3, 0xaf, 0x84, 0x47, 0x47, + 0x1c, 0xe5, 0xe5, 0xb7, 0x6c, 0xa1, 0xc3, 0x0c, 0xc9, 0xab, 0xba, 0xbe, 0x9a, 0xc4, 0xab, 0xba, + 0xbe, 0x8a, 0x29, 0x13, 0xf4, 0x86, 0x01, 0x60, 0xa9, 0xe7, 0xb2, 0xd3, 0x79, 0x53, 0xa4, 0xdf, + 0xf3, 0xdb, 0xdc, 0x37, 0x31, 0x84, 0x62, 0x8d, 0x33, 0x7a, 0x19, 0xc6, 0x2c, 0xfe, 0x22, 0x92, + 0x70, 0xfb, 0x4c, 0xe7, 0x99, 0xaf, 0x98, 0x04, 0xcc, 0xdf, 0x55, 0x80, 0xb0, 0x64, 0x48, 0x79, + 0x07, 0x9e, 0x45, 0xb6, 0xec, 0x1d, 0xe1, 0xff, 0x59, 0x1d, 0x3a, 0xb1, 0x39, 0x25, 0x96, 0xc4, + 0x5b, 0x80, 0xb0, 0x64, 0xc8, 0x5f, 0xa8, 0xb5, 0x1c, 0x4b, 0x05, 0xf3, 0xa4, 0x13, 0xf2, 0xa5, + 0x87, 0x07, 0x69, 0x2f, 0xd4, 0xea, 0x8c, 0x70, 0x94, 0x2f, 0xda, 0x85, 0x51, 0x8b, 0xbd, 0xd5, + 0x26, 0xce, 0x47, 0x38, 0x8d, 0x77, 0xdf, 0x62, 0x6d, 0xc0, 0x16, 0x17, 0xf1, 0x22, 0x9c, 0xe0, + 0x66, 0xfe, 0x26, 0x0b, 0xc0, 0x44, 0xe0, 0x81, 0xed, 0x2d, 0x96, 0x0c, 0x71, 0xdb, 0xad, 0xa7, + 0xf3, 0xfc, 0x9c, 0x1e, 0x9f, 0x0e, 0x22, 0xf3, 0xe1, 0xb6, 0x5b, 0xc7, 0x82, 0x09, 0x6a, 0xc0, + 0x48, 0xdb, 0x0a, 0xb6, 0xd3, 0x0f, 0x86, 0xcf, 0xf3, 0x08, 0xaf, 0x60, 0x1b, 0x33, 0x06, 0xe8, + 0x55, 0x03, 0xc6, 0x78, 0x38, 0xbc, 0xb4, 0xb8, 0x0f, 0x7d, 0xad, 0x2c, 0xdb, 0x6c, 0x91, 0xc7, + 0xdc, 0x0b, 0xd7, 0x0f, 0xb5, 0x25, 0x8b, 0x52, 0x2c, 0xd9, 0x2e, 0xbc, 0x6e, 0xc0, 0x84, 0x8e, + 0x9a, 0xe0, 0xb4, 0xf1, 0x71, 0xdd, 0x69, 0x23, 0xcd, 0xf6, 0xd0, 0xfd, 0x3f, 0xfe, 0xd2, 0x00, + 0xed, 0x3d, 0xe1, 0xd0, 0x65, 0xd3, 0x18, 0xd8, 0x65, 0x33, 0x73, 0x4c, 0x97, 0xcd, 0xec, 0xb1, + 0x5c, 0x36, 0x47, 0x8e, 0xef, 0xb2, 0x99, 0xeb, 0xef, 0xb2, 0x69, 0xbe, 0x69, 0xc0, 0x6c, 0xcf, + 0x3a, 0x4c, 0xd5, 0x36, 0xcf, 0x75, 0x83, 0x3e, 0x9e, 0x52, 0x38, 0x04, 0x61, 0x1d, 0x0f, 0xad, + 0xc0, 0x8c, 0x48, 0xfd, 0x5d, 0x6d, 0x37, 0xed, 0xc4, 0x44, 0x05, 0x1b, 0x31, 0x38, 0xee, 0xa9, + 0x61, 0xfe, 0x1f, 0x03, 0xc6, 0xb5, 0xf0, 0x46, 0xfa, 0x1d, 0x2c, 0x0c, 0x54, 0x88, 0x11, 0x66, + 0x3d, 0x67, 0x37, 0x1c, 0x1c, 0xc6, 0x2f, 0xdb, 0x1a, 0x5a, 0x62, 0xd8, 0xf0, 0xb2, 0x8d, 0x96, + 0x62, 0x01, 0xe5, 0x29, 0x3f, 0x49, 0x9b, 0x35, 0x7a, 0x56, 0x4f, 0xf9, 0x49, 0xda, 0x98, 0x41, + 0x18, 0x3b, 0xaa, 0xbf, 0x0a, 0x6f, 0x5e, 0x2d, 0xc9, 0xba, 0xe5, 0x05, 0x98, 0xc3, 0xd0, 0x79, + 0xc8, 0x12, 0xa7, 0x2e, 0x0e, 0xdb, 0xea, 0x61, 0xb3, 0x8b, 0x4e, 0x1d, 0xd3, 0x72, 0xf3, 0x1a, + 0x4c, 0x54, 0x49, 0xcd, 0x23, 0xc1, 0x0b, 0x64, 0x7f, 0xe0, 0x97, 0xd2, 0xe8, 0x68, 0x8f, 0xbd, + 0x94, 0x46, 0xab, 0xd3, 0x72, 0xf3, 0xbf, 0x1b, 0x10, 0x7b, 0x09, 0x40, 0x33, 0xbc, 0x1b, 0xfd, + 0x0c, 0xef, 0x11, 0x13, 0x71, 0xe6, 0x50, 0x13, 0xf1, 0x15, 0x40, 0x2d, 0x3a, 0x15, 0x22, 0xef, + 0x5e, 0x08, 0x3b, 0x47, 0x18, 0x4c, 0xdd, 0x83, 0x81, 0x13, 0x6a, 0x99, 0xff, 0x8d, 0x0b, 0xab, + 0xbf, 0x0d, 0x70, 0x74, 0x03, 0x74, 0x20, 0xc7, 0x48, 0x09, 0x63, 0xcf, 0x90, 0x86, 0xd2, 0xde, + 0xa4, 0x24, 0x61, 0x47, 0x8a, 0x29, 0xcf, 0xb8, 0x99, 0x3f, 0xe1, 0xb2, 0x6a, 0x8f, 0x07, 0x0c, + 0x20, 0x6b, 0x2b, 0x2a, 0xeb, 0xe5, 0xb4, 0xd6, 0xca, 0x64, 0x19, 0xd1, 0x22, 0x40, 0x9b, 0x78, + 0x35, 0xe2, 0x04, 0xd2, 0xc9, 0x3c, 0x27, 0xc2, 0x8d, 0x54, 0x29, 0xd6, 0x30, 0xcc, 0x57, 0x0d, + 0x98, 0xa9, 0x06, 0x76, 0x6d, 0xc7, 0x76, 0x78, 0xf8, 0xdc, 0x96, 0xdd, 0xa0, 0xa7, 0x23, 0x22, + 0x1e, 0x01, 0xe3, 0xe6, 0x37, 0xb5, 0x14, 0xcb, 0xb7, 0xbf, 0x24, 0x1c, 0x95, 0x60, 0x5a, 0x5e, + 0x3a, 0x48, 0x9b, 0x29, 0x0f, 0xfb, 0x55, 0x36, 0x9a, 0x95, 0x28, 0x18, 0xc7, 0xf1, 0xcd, 0xcf, + 0xc0, 0xb8, 0xb6, 0xbe, 0xb2, 0xa5, 0xe8, 0x8e, 0x55, 0x0b, 0xe2, 0x53, 0xf8, 0x22, 0x2d, 0xc4, + 0x1c, 0xc6, 0x4c, 0xbb, 0xdc, 0x0b, 0x39, 0x36, 0x85, 0x85, 0xef, 0xb1, 0x80, 0x52, 0x62, 0x1e, + 0x69, 0x90, 0x3b, 0x32, 0x05, 0xad, 0x24, 0x86, 0x69, 0x21, 0xe6, 0x30, 0xf3, 0x31, 0xc8, 0xcb, + 0xe4, 0x0c, 0x2c, 0xc2, 0x59, 0x9a, 0x1d, 0xf5, 0x08, 0x67, 0xd7, 0x0b, 0x30, 0x83, 0x98, 0x37, + 0x21, 0x2f, 0x73, 0x48, 0x1c, 0x8d, 0x4d, 0x67, 0x95, 0xef, 0xd8, 0x97, 0x5d, 0x3f, 0x90, 0x89, + 0x2f, 0xf8, 0xcd, 0xc8, 0xd5, 0x55, 0x56, 0x86, 0x15, 0xd4, 0x9c, 0x85, 0x69, 0x75, 0xe5, 0x21, + 0xdc, 0x42, 0xbf, 0x9f, 0x85, 0x89, 0xc8, 0xbb, 0xda, 0x47, 0x0f, 0xb7, 0xc1, 0x67, 0x71, 0xc2, + 0xd5, 0x45, 0xf6, 0x98, 0x57, 0x17, 0xfa, 0x5d, 0xd1, 0xc8, 0xe9, 0xde, 0x15, 0xe5, 0xd2, 0xb9, + 0x2b, 0x0a, 0x60, 0xcc, 0x17, 0x1b, 0xd5, 0x68, 0x1a, 0x46, 0x9c, 0x58, 0x8f, 0x71, 0xdd, 0x58, + 0xee, 0x77, 0x92, 0x95, 0xf9, 0x9d, 0x1c, 0x4c, 0x45, 0xb3, 0x67, 0x0d, 0xd0, 0x93, 0x8f, 0xf5, + 0xf4, 0xe4, 0x31, 0x6d, 0xa5, 0xd9, 0x61, 0x6d, 0xa5, 0x23, 0xc3, 0xda, 0x4a, 0x73, 0x27, 0xb0, + 0x95, 0xf6, 0x5a, 0x3a, 0x47, 0x07, 0xb6, 0x74, 0x3e, 0xab, 0xfc, 0x8e, 0xc6, 0x22, 0x17, 0xf5, + 0xa1, 0xdf, 0x11, 0x8a, 0x76, 0xc3, 0xb2, 0x5b, 0x4f, 0xf4, 0xdf, 0xca, 0x1f, 0x61, 0x13, 0xf2, + 0x12, 0xdd, 0x84, 0x8e, 0x7f, 0x1d, 0xf3, 0x8e, 0x63, 0xb8, 0x08, 0x3d, 0x05, 0xe3, 0x62, 0x3c, + 0x31, 0x5d, 0x09, 0xa2, 0x7a, 0x56, 0x35, 0x04, 0x61, 0x1d, 0x8f, 0xbd, 0x91, 0x1a, 0x7d, 0x14, + 0x96, 0x99, 0x9e, 0xf5, 0x37, 0x52, 0x63, 0x8f, 0xc8, 0xc6, 0xf1, 0xcd, 0x4f, 0xc1, 0xd9, 0xc4, + 0x93, 0x20, 0x33, 0x8d, 0xb1, 0x6d, 0x9c, 0xd4, 0x05, 0x82, 0x26, 0x46, 0x2c, 0xb9, 0xf2, 0xc2, + 0xad, 0xbe, 0x98, 0xf8, 0x10, 0x2a, 0xe6, 0xb7, 0xb3, 0x30, 0x15, 0x7d, 0x60, 0x0b, 0xed, 0x29, + 0xbb, 0x51, 0x2a, 0x26, 0x2b, 0x4e, 0x56, 0xcb, 0xc8, 0xd4, 0xd7, 0x08, 0xbc, 0xc7, 0xc6, 0xd7, + 0xa6, 0x4a, 0x0f, 0x75, 0x7a, 0x8c, 0x85, 0xf5, 0x55, 0xb0, 0x63, 0x6f, 0x68, 0x85, 0xc1, 0x23, + 0xe2, 0xd8, 0x95, 0x3a, 0xf7, 0x30, 0x1c, 0x44, 0xb1, 0xc2, 0x1a, 0x5b, 0xba, 0xb7, 0xec, 0x12, + 0xcf, 0xde, 0xb2, 0xd5, 0xe3, 0xa0, 0x6c, 0xe5, 0xbe, 0x29, 0xca, 0xb0, 0x82, 0x9a, 0xaf, 0x66, + 0x20, 0x7c, 0x0a, 0x99, 0xbd, 0x42, 0xe3, 0x6b, 0x2a, 0xae, 0xe8, 0xb6, 0x2b, 0xc3, 0x3e, 0xf5, + 0x14, 0x52, 0x14, 0x3e, 0xa1, 0x5a, 0x09, 0x8e, 0x70, 0xfc, 0x3d, 0x3c, 0x81, 0x6c, 0xc1, 0x74, + 0x2c, 0x58, 0x38, 0x75, 0xff, 0xfd, 0x5f, 0x67, 0xa1, 0xa0, 0xc2, 0xad, 0xd1, 0x07, 0x22, 0xf6, + 0x86, 0x42, 0xf9, 0x9d, 0xda, 0x13, 0x09, 0xdb, 0x6e, 0xfd, 0x6e, 0xb7, 0x38, 0xad, 0x90, 0x63, + 0xb6, 0x83, 0xf3, 0x90, 0xed, 0x78, 0xcd, 0xf8, 0x81, 0xe2, 0x06, 0x5e, 0xc3, 0xb4, 0x1c, 0xdd, + 0x89, 0x1f, 0xf8, 0xd7, 0x53, 0x0a, 0x11, 0xe7, 0x9a, 0x77, 0xff, 0x83, 0x3e, 0xdd, 0x25, 0x37, + 0xdd, 0xfa, 0x7e, 0xfc, 0x49, 0x85, 0xb2, 0x5b, 0xdf, 0xc7, 0x0c, 0x82, 0x9e, 0x83, 0xa9, 0xc0, + 0x6e, 0x11, 0xb7, 0x13, 0xe8, 0x0f, 0xcd, 0x66, 0xc3, 0x4b, 0xcd, 0x8d, 0x08, 0x14, 0xc7, 0xb0, + 0xe9, 0x2e, 0x7b, 0xdb, 0x77, 0x1d, 0x96, 0x27, 0x71, 0x34, 0x7a, 0x03, 0x72, 0xa5, 0x7a, 0xed, + 0x2a, 0xb3, 0x7b, 0x28, 0x0c, 0x8a, 0x6d, 0xb3, 0x08, 0x44, 0x8f, 0x08, 0x9f, 0x82, 0x99, 0x30, + 0xf3, 0x06, 0x2f, 0xc7, 0x0a, 0x03, 0xad, 0x70, 0xda, 0x54, 0x5a, 0xb6, 0xa3, 0x4c, 0x94, 0x1f, + 0x95, 0x74, 0x69, 0xd9, 0xdd, 0xee, 0x21, 0xb1, 0xb5, 0xaa, 0xa6, 0x79, 0x03, 0xa6, 0x63, 0x0d, + 0x26, 0x0f, 0x80, 0x46, 0xf2, 0x01, 0x70, 0xb0, 0x57, 0x10, 0xfe, 0xa7, 0x01, 0xb3, 0x3d, 0x4b, + 0xc0, 0xa0, 0xe1, 0x29, 0xf1, 0xcd, 0x28, 0x73, 0xf2, 0xcd, 0x28, 0x7b, 0xbc, 0xcd, 0xa8, 0xbc, + 0xf9, 0xc3, 0xb7, 0x2e, 0xdc, 0xf7, 0xe3, 0xb7, 0x2e, 0xdc, 0xf7, 0xb3, 0xb7, 0x2e, 0xdc, 0xf7, + 0xea, 0xc1, 0x05, 0xe3, 0x87, 0x07, 0x17, 0x8c, 0x1f, 0x1f, 0x5c, 0x30, 0x7e, 0x76, 0x70, 0xc1, + 0xf8, 0xd3, 0x83, 0x0b, 0xc6, 0x9b, 0xbf, 0xbe, 0x70, 0xdf, 0x8b, 0xcf, 0x86, 0x03, 0x74, 0x49, + 0x0e, 0x50, 0xf6, 0xe3, 0xbd, 0x72, 0x38, 0x2e, 0xb5, 0x77, 0x1a, 0x4b, 0x74, 0x80, 0x2e, 0xa9, + 0x12, 0x39, 0x40, 0xff, 0x3e, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x5e, 0x0d, 0x3c, 0xa2, 0x96, 0x00, + 0x00, } func (m *ALBStatus) Marshal() (dAtA []byte, err error) { @@ -6898,6 +6904,32 @@ func (m *MetricProvider) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Plugin) > 0 { + keysForPlugin := make([]string, 0, len(m.Plugin)) + for k := range m.Plugin { + keysForPlugin = append(keysForPlugin, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForPlugin) + for iNdEx := len(keysForPlugin) - 1; iNdEx >= 0; iNdEx-- { + v := m.Plugin[string(keysForPlugin[iNdEx])] + baseI := i + if v != nil { + i -= len(v) + copy(dAtA[i:], v) + i = encodeVarintGenerated(dAtA, i, uint64(len(v))) + i-- + dAtA[i] = 0x12 + } + i -= len(keysForPlugin[iNdEx]) + copy(dAtA[i:], keysForPlugin[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForPlugin[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x5a + } + } if m.Influxdb != nil { { size, err := m.Influxdb.MarshalToSizedBuffer(dAtA[:i]) @@ -10842,6 +10874,18 @@ func (m *MetricProvider) Size() (n int) { l = m.Influxdb.Size() n += 1 + l + sovGenerated(uint64(l)) } + if len(m.Plugin) > 0 { + for k, v := range m.Plugin { + _ = k + _ = v + l = 0 + if v != nil { + l = 1 + len(v) + sovGenerated(uint64(len(v))) + } + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + l + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } return n } @@ -12676,6 +12720,16 @@ func (this *MetricProvider) String() string { if this == nil { return "nil" } + keysForPlugin := make([]string, 0, len(this.Plugin)) + for k := range this.Plugin { + keysForPlugin = append(keysForPlugin, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForPlugin) + mapStringForPlugin := "map[string]encoding_json.RawMessage{" + for _, k := range keysForPlugin { + mapStringForPlugin += fmt.Sprintf("%v: %v,", k, this.Plugin[k]) + } + mapStringForPlugin += "}" s := strings.Join([]string{`&MetricProvider{`, `Prometheus:` + strings.Replace(this.Prometheus.String(), "PrometheusMetric", "PrometheusMetric", 1) + `,`, `Kayenta:` + strings.Replace(this.Kayenta.String(), "KayentaMetric", "KayentaMetric", 1) + `,`, @@ -12687,6 +12741,7 @@ func (this *MetricProvider) String() string { `CloudWatch:` + strings.Replace(this.CloudWatch.String(), "CloudWatchMetric", "CloudWatchMetric", 1) + `,`, `Graphite:` + strings.Replace(this.Graphite.String(), "GraphiteMetric", "GraphiteMetric", 1) + `,`, `Influxdb:` + strings.Replace(this.Influxdb.String(), "InfluxdbMetric", "InfluxdbMetric", 1) + `,`, + `Plugin:` + mapStringForPlugin + `,`, `}`, }, "") return s @@ -23659,6 +23714,134 @@ func (m *MetricProvider) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 11: + 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 = make(map[string]encoding_json.RawMessage) + } + var mapkey string + mapvalue := []byte{} + 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 mapbyteLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapbyteLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intMapbyteLen := int(mapbyteLen) + if intMapbyteLen < 0 { + return ErrInvalidLengthGenerated + } + postbytesIndex := iNdEx + intMapbyteLen + if postbytesIndex < 0 { + return ErrInvalidLengthGenerated + } + if postbytesIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = make([]byte, mapbyteLen) + copy(mapvalue, dAtA[iNdEx:postbytesIndex]) + iNdEx = postbytesIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Plugin[mapkey] = ((encoding_json.RawMessage)(mapvalue)) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/rollouts/v1alpha1/generated.proto b/pkg/apis/rollouts/v1alpha1/generated.proto index df63a7f33c..0666a820cb 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -976,6 +976,12 @@ message MetricProvider { // Influxdb specifies the influxdb metric to query optional InfluxdbMetric influxdb = 10; + + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Plugin specifies the hashicorp go-plugin metric to query + map plugin = 11; } // MetricResult contain a list of the most recent measurements for a single metric along with diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index d124a2acf3..f3634a21b4 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -2925,6 +2925,21 @@ func schema_pkg_apis_rollouts_v1alpha1_MetricProvider(ref common.ReferenceCallba Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.InfluxdbMetric"), }, }, + "plugin": { + SchemaProps: spec.SchemaProps{ + Description: "Plugin specifies the hashicorp go-plugin metric to query", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + }, + }, }, }, }, diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index dad43fb3e0..e5ca988b0e 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -1663,6 +1663,21 @@ func (in *MetricProvider) DeepCopyInto(out *MetricProvider) { *out = new(InfluxdbMetric) **out = **in } + if in.Plugin != nil { + in, out := &in.Plugin, &out.Plugin + *out = make(map[string]json.RawMessage, len(*in)) + for key, val := range *in { + var outVal []byte + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = make(json.RawMessage, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } return } diff --git a/test/cmd/sample-metrics-plugin/internal/plugin/plugin.go b/test/cmd/sample-metrics-plugin/internal/plugin/plugin.go new file mode 100644 index 0000000000..bbe3f7ce28 --- /dev/null +++ b/test/cmd/sample-metrics-plugin/internal/plugin/plugin.go @@ -0,0 +1,185 @@ +package plugin + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" + "os" + "time" + + "github.com/argoproj/argo-rollouts/utils/plugin/types" + + "github.com/argoproj/argo-rollouts/metricproviders/plugin" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/utils/evaluate" + metricutil "github.com/argoproj/argo-rollouts/utils/metric" + timeutil "github.com/argoproj/argo-rollouts/utils/time" + "github.com/prometheus/client_golang/api" + v1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/prometheus/common/model" + log "github.com/sirupsen/logrus" +) + +const EnvVarArgoRolloutsPrometheusAddress string = "ARGO_ROLLOUTS_PROMETHEUS_ADDRESS" + +// Here is a real implementation of MetricsPlugin +type RpcPlugin struct { + LogCtx log.Entry + api v1.API +} + +type Config struct { + // Address is the HTTP address and port of the prometheus server + Address string `json:"address,omitempty" protobuf:"bytes,1,opt,name=address"` + // Query is a raw prometheus query to perform + Query string `json:"query,omitempty" protobuf:"bytes,2,opt,name=query"` +} + +func (g *RpcPlugin) NewMetricsPlugin(metric v1alpha1.Metric) types.RpcError { + config := Config{} + err := json.Unmarshal(metric.Provider.Plugin["prometheus"], &config) + if err != nil { + return types.RpcError{ErrorString: err.Error()} + } + + api, err := newPrometheusAPI(config.Address) + g.api = api + + return types.RpcError{} +} + +func (g *RpcPlugin) Run(anaysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alpha1.Measurement { + startTime := timeutil.MetaNow() + newMeasurement := v1alpha1.Measurement{ + StartedAt: &startTime, + } + + config := Config{} + json.Unmarshal(metric.Provider.Plugin["prometheus"], &config) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + response, warnings, err := g.api.Query(ctx, config.Query, time.Now()) + if err != nil { + return metricutil.MarkMeasurementError(newMeasurement, err) + } + + newValue, newStatus, err := g.processResponse(metric, response) + if err != nil { + return metricutil.MarkMeasurementError(newMeasurement, err) + + } + newMeasurement.Value = newValue + if len(warnings) > 0 { + warningMetadata := "" + for _, warning := range warnings { + warningMetadata = fmt.Sprintf(`%s"%s", `, warningMetadata, warning) + } + warningMetadata = warningMetadata[:len(warningMetadata)-2] + if warningMetadata != "" { + newMeasurement.Metadata = map[string]string{"warnings": warningMetadata} + g.LogCtx.Warnf("Prometheus returned the following warnings: %s", warningMetadata) + } + } + + newMeasurement.Phase = newStatus + finishedTime := timeutil.MetaNow() + newMeasurement.FinishedAt = &finishedTime + return newMeasurement +} + +func (g *RpcPlugin) Resume(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + return measurement +} + +func (g *RpcPlugin) Terminate(analysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + return measurement +} + +func (g *RpcPlugin) GarbageCollect(*v1alpha1.AnalysisRun, v1alpha1.Metric, int) types.RpcError { + return types.RpcError{} +} + +func (g *RpcPlugin) Type() string { + return plugin.ProviderType +} + +func (g *RpcPlugin) GetMetadata(metric v1alpha1.Metric) map[string]string { + metricsMetadata := make(map[string]string) + + config := Config{} + json.Unmarshal(metric.Provider.Plugin["prometheus"], &config) + if config.Query != "" { + metricsMetadata["ResolvedPrometheusQuery"] = config.Query + } + return metricsMetadata +} + +func (g *RpcPlugin) processResponse(metric v1alpha1.Metric, response model.Value) (string, v1alpha1.AnalysisPhase, error) { + switch value := response.(type) { + case *model.Scalar: + valueStr := value.Value.String() + result := float64(value.Value) + newStatus, err := evaluate.EvaluateResult(result, metric, g.LogCtx) + return valueStr, newStatus, err + case model.Vector: + results := make([]float64, 0, len(value)) + valueStr := "[" + for _, s := range value { + if s != nil { + valueStr = valueStr + s.Value.String() + "," + results = append(results, float64(s.Value)) + } + } + // if we appended to the string, we should remove the last comma on the string + if len(valueStr) > 1 { + valueStr = valueStr[:len(valueStr)-1] + } + valueStr = valueStr + "]" + newStatus, err := evaluate.EvaluateResult(results, metric, g.LogCtx) + return valueStr, newStatus, err + default: + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("Prometheus metric type not supported") + } +} + +func newPrometheusAPI(address string) (v1.API, error) { + envValuesByKey := make(map[string]string) + if value, ok := os.LookupEnv(fmt.Sprintf("%s", EnvVarArgoRolloutsPrometheusAddress)); ok { + envValuesByKey[EnvVarArgoRolloutsPrometheusAddress] = value + log.Debugf("ARGO_ROLLOUTS_PROMETHEUS_ADDRESS: %v", envValuesByKey[EnvVarArgoRolloutsPrometheusAddress]) + } + if len(address) != 0 { + if !isUrl(address) { + return nil, errors.New("prometheus address is not is url format") + } + } else if envValuesByKey[EnvVarArgoRolloutsPrometheusAddress] != "" { + if isUrl(envValuesByKey[EnvVarArgoRolloutsPrometheusAddress]) { + address = envValuesByKey[EnvVarArgoRolloutsPrometheusAddress] + } else { + return nil, errors.New("prometheus address is not is url format") + } + } else { + return nil, errors.New("prometheus address is not configured") + } + client, err := api.NewClient(api.Config{ + Address: address, + }) + if err != nil { + log.Errorf("Error in getting prometheus client: %v", err) + return nil, err + } + return v1.NewAPI(client), nil +} + +func isUrl(str string) bool { + u, err := url.Parse(str) + if err != nil { + log.Errorf("Error in parsing url: %v", err) + } + log.Debugf("Parsed url: %v", u) + return err == nil && u.Scheme != "" && u.Host != "" +} diff --git a/test/cmd/sample-metrics-plugin/internal/plugin/plugin_test.go b/test/cmd/sample-metrics-plugin/internal/plugin/plugin_test.go new file mode 100644 index 0000000000..fc66bc0398 --- /dev/null +++ b/test/cmd/sample-metrics-plugin/internal/plugin/plugin_test.go @@ -0,0 +1,108 @@ +package plugin + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/argoproj/argo-rollouts/metricproviders/plugin/rpc" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + log "github.com/sirupsen/logrus" + + goPlugin "github.com/hashicorp/go-plugin" +) + +var testHandshake = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "metrics", +} + +// This is just an example of how to test a plugin. +func TestRunSuccessfully(t *testing.T) { + //Skip test because this is just an example of how to test a plugin. + t.Skip("Skipping test because it requires a running prometheus server") + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + logCtx := *log.WithFields(log.Fields{"plugin-test": "prometheus"}) + + rpcPluginImp := &RpcPlugin{ + LogCtx: logCtx, + } + + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcMetricsPlugin": &rpc.RpcMetricsPlugin{Impl: rpcPluginImp}, + } + + 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) + } + + // Pinging should work + if err := client.Ping(); err != nil { + t.Fatalf("should not err: %s", err) + } + + // Kill which should do nothing + c.Kill() + if err := client.Ping(); err != nil { + t.Fatalf("should not err: %s", err) + } + + // Request the plugin + raw, err := client.Dispense("RpcMetricsPlugin") + if err != nil { + t.Fail() + } + + plugin := raw.(rpc.MetricsPlugin) + + err = plugin.NewMetricsPlugin(v1alpha1.Metric{ + Provider: v1alpha1.MetricProvider{ + Plugin: map[string]json.RawMessage{"prometheus": json.RawMessage(`{"address":"http://prometheus.local", "query":"machine_cpu_cores"}`)}, + }, + }) + if err != nil { + t.Fail() + } + + // Canceling should cause an exit + cancel() + <-closeCh +} diff --git a/test/cmd/sample-metrics-plugin/main.go b/test/cmd/sample-metrics-plugin/main.go new file mode 100644 index 0000000000..ed4a9ec7d5 --- /dev/null +++ b/test/cmd/sample-metrics-plugin/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/argoproj/argo-rollouts/metricproviders/plugin/rpc" + "github.com/argoproj/argo-rollouts/test/cmd/sample-metrics-plugin/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: "metrics", +} + +func main() { + logCtx := *log.WithFields(log.Fields{"plugin": "prometheus"}) + + rpcPluginImp := &plugin.RpcPlugin{ + LogCtx: logCtx, + } + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcMetricsPlugin": &rpc.RpcMetricsPlugin{Impl: rpcPluginImp}, + } + + logCtx.Debug("message from plugin", "foo", "bar") + + goPlugin.Serve(&goPlugin.ServeConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + }) +} diff --git a/utils/analysis/factory.go b/utils/analysis/factory.go index 0fb57030f5..6366c833dd 100644 --- a/utils/analysis/factory.go +++ b/utils/analysis/factory.go @@ -225,6 +225,10 @@ func ValidateMetric(metric v1alpha1.Metric) error { if metric.Provider.Influxdb != nil { numProviders++ } + if metric.Provider.Plugin != nil && len(metric.Provider.Plugin) > 0 { + // We allow exactly one plugin to be specified per analysis run template + numProviders = numProviders + len(metric.Provider.Plugin) + } if numProviders == 0 { return fmt.Errorf("no provider specified") } diff --git a/utils/config/config.go b/utils/config/config.go new file mode 100644 index 0000000000..575f44a1a1 --- /dev/null +++ b/utils/config/config.go @@ -0,0 +1,81 @@ +package config + +import ( + "context" + "fmt" + "sync" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "github.com/argoproj/argo-rollouts/utils/defaults" + "github.com/argoproj/argo-rollouts/utils/plugin/types" + "github.com/ghodss/yaml" + v1 "k8s.io/api/core/v1" + k8errors "k8s.io/apimachinery/pkg/api/errors" +) + +// Config is the in memory representation of the configmap with some additional fields/functions for ease of use. +type Config struct { + configMap *v1.ConfigMap + plugins types.Plugin +} + +var configMemoryCache *Config +var mutex sync.RWMutex + +// InitializeConfig initializes the in memory config and downloads the plugins to the filesystem. Subsequent calls to this +// function will update the configmap in memory. +func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) (*Config, error) { + 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 + // If the configmap is not found, we return + return configMemoryCache, nil + } + return nil, fmt.Errorf("failed to get configmap %s/%s: %w", defaults.Namespace(), configMapName, err) + } + + plugins := types.Plugin{} + if err = yaml.Unmarshal([]byte(configMapCluster.Data["plugins"]), &plugins); err != nil { + return nil, fmt.Errorf("failed to unmarshal plugins while initializing: %w", err) + } + + mutex.Lock() + configMemoryCache = &Config{ + configMap: configMapCluster, + plugins: plugins, + } + mutex.Unlock() + + return configMemoryCache, nil +} + +// GetConfig returns the initialized in memory config object if it exists otherwise errors if InitializeConfig has not been called. +func GetConfig() (*Config, error) { + mutex.RLock() + defer mutex.RUnlock() + if configMemoryCache == nil { + return nil, fmt.Errorf("config not initialized, please initialize before use") + } + return configMemoryCache, nil +} + +// UnInitializeConfig resets the in memory config to nil. This is useful for testing. +func UnInitializeConfig() { + mutex.Lock() + defer mutex.Unlock() + configMemoryCache = nil +} + +// GetMetricPluginsConfig returns the metric plugins configured in the configmap +func (c *Config) GetMetricPluginsConfig() []types.PluginItem { + mutex.RLock() + defer mutex.RUnlock() + var copiedPlugins []types.PluginItem + for _, p := range configMemoryCache.plugins.Metrics { + copiedPlugins = append(copiedPlugins, p) + } + return copiedPlugins +} diff --git a/utils/defaults/defaults.go b/utils/defaults/defaults.go index db605eb99c..b5ce8d03a8 100644 --- a/utils/defaults/defaults.go +++ b/utils/defaults/defaults.go @@ -45,6 +45,10 @@ const ( // DefaultMetricCleanupDelay is the default time to delay metrics removal upon object removal, gives time for metrics // to be collected DefaultMetricCleanupDelay = int32(65) + // DefaultRolloutsConfigMapName is the default name of the ConfigMap that contains the Rollouts controller configuration + DefaultRolloutsConfigMapName = "argo-rollouts-config" + // DefaultRolloutPluginFolder is the default location where plugins will be downloaded and/or moved to. + DefaultRolloutPluginFolder = "plugin-bin" ) const ( diff --git a/utils/plugin/downloader.go b/utils/plugin/downloader.go new file mode 100644 index 0000000000..55e1575de6 --- /dev/null +++ b/utils/plugin/downloader.go @@ -0,0 +1,190 @@ +package plugin + +import ( + "crypto/sha256" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "time" + + "github.com/argoproj/argo-rollouts/utils/config" + + "github.com/argoproj/argo-rollouts/utils/defaults" + + log "github.com/sirupsen/logrus" +) + +// FileDownloader is an interface that allows us to mock the http.Get function +type FileDownloader interface { + Get(url string) (resp *http.Response, err error) +} + +// FileDownloaderImpl is the default/real implementation of the FileDownloader interface +type FileDownloaderImpl struct { +} + +func (fd FileDownloaderImpl) Get(url string) (resp *http.Response, err error) { + return http.Get(url) +} + +// checkPluginExists this function checks if the plugin exists in the configured path if not we panic +func checkPluginExists(pluginLocation string) error { + if pluginLocation != "" { + //Check for plugin executable existence + _, err := os.Stat(pluginLocation) + if err != nil { + return fmt.Errorf("plugin stat file at %s", pluginLocation) + } + } + return nil +} + +func checkShaOfPlugin(pluginLocation string, expectedSha256 string) (bool, error) { + hasher := sha256.New() + fileBytes, err := os.ReadFile(pluginLocation) + if err != nil { + return false, fmt.Errorf("failed to read file %s: %w", pluginLocation, err) + } + fileSha256 := fmt.Sprintf("%x", hasher.Sum(fileBytes)) + match := fileSha256 == expectedSha256 + if !match { + log.Printf("expected sha256: %s, actual sha256: %s, of downloaded metric plugin (%s)", expectedSha256, fileSha256, pluginLocation) + } + return match, nil +} + +func downloadFile(filepath string, url string, downloader FileDownloader) error { + // Get the data + resp, err := downloader.Get(url) + if err != nil { + return fmt.Errorf("failed to download file from %s: %w", url, err) + } + defer resp.Body.Close() + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return fmt.Errorf("failed to create file %s: %w", filepath, err) + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + if err != nil { + return fmt.Errorf("failed to write to file %s: %w", filepath, err) + } + + // Set the file permissions, to allow execution + err = os.Chmod(filepath, 0700) + if err != nil { + return fmt.Errorf("failed to set file permissions on %s: %w", filepath, err) + } + + return err +} + +// DownloadPlugins this function downloads and/or checks that a plugin executable exits on the filesystem +func DownloadPlugins(fd FileDownloader) error { + config, err := config.GetConfig() + if err != nil { + return fmt.Errorf("failed to get config: %w", err) + } + + absoluteFilepath, err := filepath.Abs(defaults.DefaultRolloutPluginFolder) + if err != nil { + return fmt.Errorf("failed to get absolute path of plugin folder: %w", err) + } + + err = os.MkdirAll(absoluteFilepath, 0700) + if err != nil { + return fmt.Errorf("failed to create plugin folder: %w", err) + } + + for _, plugin := range config.GetMetricPluginsConfig() { + urlObj, err := url.ParseRequestURI(plugin.PluginLocation) + if err != nil { + return fmt.Errorf("failed to parse plugin location: %w", err) + } + + finalFileLocation := filepath.Join(absoluteFilepath, plugin.Name) + + switch urlObj.Scheme { + case "http", "https": + log.Infof("Downloading plugin %s from: %s", plugin.Name, plugin.PluginLocation) + startTime := time.Now() + err = downloadFile(finalFileLocation, urlObj.String(), fd) + if err != nil { + return fmt.Errorf("failed to download plugin from %s: %w", plugin.PluginLocation, err) + } + timeTakenToDownload := time.Now().Sub(startTime) + log.Infof("Download complete, it took %s", timeTakenToDownload) + + if plugin.PluginSha256 != "" { + sha256Matched, err := checkShaOfPlugin(finalFileLocation, plugin.PluginSha256) + if err != nil { + return fmt.Errorf("failed to check sha256 of downloaded plugin: %w", err) + } + if !sha256Matched { + return fmt.Errorf("sha256 hash of downloaded plugin (%s) does not match expected hash", plugin.PluginLocation) + } + } + if checkPluginExists(finalFileLocation) != nil { + return fmt.Errorf("failed to find downloaded plugin at location: %s", plugin.PluginLocation) + } + + case "file": + pluginPath, err := filepath.Abs(urlObj.Host + urlObj.Path) + if err != nil { + return fmt.Errorf("failed to get absolute path of plugin: %w", err) + } + + if err := copyFile(pluginPath, finalFileLocation); err != nil { + return fmt.Errorf("failed to copy plugin from %s to %s: %w", pluginPath, finalFileLocation, err) + } + if checkPluginExists(finalFileLocation) != nil { + return fmt.Errorf("failed to find filebased plugin at location: %s", plugin.PluginLocation) + } + // Set the file permissions, to allow execution + err = os.Chmod(finalFileLocation, 0700) + if err != nil { + return fmt.Errorf("failed to set file permissions of plugin (%s): %w", finalFileLocation, err) + } + default: + return fmt.Errorf("plugin location must be of http(s) or file scheme") + } + } + + return nil +} + +// CopyFile copies a file from src to dst. +func copyFile(src, dst string) error { + sourceFileStat, err := os.Stat(src) + if err != nil { + return fmt.Errorf("failed to get file stat of %s: %w", src, err) + } + + if !sourceFileStat.Mode().IsRegular() { + return fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open source file %s: %w", src, err) + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return fmt.Errorf("failed to create destination file %s: %w", dst, err) + } + defer destination.Close() + _, err = io.Copy(destination, source) + if err != nil { + return fmt.Errorf("failed to copy file from %s to %s: %w", src, dst, err) + } + return nil +} diff --git a/utils/plugin/downloader_test.go b/utils/plugin/downloader_test.go new file mode 100644 index 0000000000..0a0296b796 --- /dev/null +++ b/utils/plugin/downloader_test.go @@ -0,0 +1,225 @@ +package plugin + +import ( + "bytes" + "io" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/argoproj/argo-rollouts/utils/config" + + "github.com/argoproj/argo-rollouts/utils/defaults" + "github.com/tj/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +type MockFileDownloader struct { + FileDownloader +} + +func (m MockFileDownloader) Get(url string) (*http.Response, error) { + responseBody := io.NopCloser(bytes.NewReader([]byte(`test`))) + return &http.Response{ + Status: "200", + StatusCode: 200, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: nil, + Body: responseBody, + ContentLength: 4, + }, nil +} + +func TestNotInitialized(t *testing.T) { + _, err := config.GetConfig() + assert.Error(t, err) +} + +func TestInitPlugin(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"plugins": "metrics:\n - name: http\n pluginLocation: https://test/plugin\n - name: http-sha\n pluginLocation: https://test/plugin\n pluginSha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.NoError(t, err) + + filepath.Join(defaults.DefaultRolloutPluginFolder, "http") + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, "http")) + assert.NoError(t, err) + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, "http-sha")) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) +} + +func TestInitPluginBadSha(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaults.DefaultRolloutsConfigMapName, + Namespace: defaults.Namespace(), + }, + Data: map[string]string{"plugins": "metrics:\n - name: http-badsha\n pluginLocation: https://test/plugin\n pluginSha256: badsha352"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName) + assert.NoError(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.Error(t, err) + + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, "http-badsha")) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) +} + +func TestInitPluginConfigNotFound(t *testing.T) { + client := fake.NewSimpleClientset() + + config.UnInitializeConfig() + + cm, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName) + assert.NoError(t, err) + assert.Equal(t, cm, &config.Config{}) + + err = DownloadPlugins(MockFileDownloader{}) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) +} + +func TestFileMove(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaults.DefaultRolloutsConfigMapName, + Namespace: defaults.Namespace(), + }, + Data: map[string]string{"plugins": "metrics:\n - name: file-plugin\n pluginLocation: file://./plugin.go"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName) + assert.NoError(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.NoError(t, err) + + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, "file-plugin")) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) +} + +func TestDoubleInit(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaults.DefaultRolloutsConfigMapName, + Namespace: defaults.Namespace(), + }, + Data: map[string]string{"plugins": "metrics:\n - name: file-plugin\n pluginLocation: file://./plugin.go"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName) + assert.NoError(t, err) + + _, err = config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName) + assert.NoError(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.NoError(t, err) + + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, "file-plugin")) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) +} + +func TestBadConfigMap(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"plugins": "badconfigmap"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.Error(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.Error(t, err) +} + +func TestBadLocation(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"plugins": "metrics:\n - name: http\n pluginLocation: agwegasdlkjf2324"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.Error(t, err) + + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) +} + +func TestCheckPluginExits(t *testing.T) { + err := checkPluginExists("nonexistentplugin") + assert.Error(t, err) + + realfile, err := filepath.Abs("plugin.go") + assert.NoError(t, err) + err = checkPluginExists(realfile) + assert.NoError(t, err) +} + +func TestCheckShaOfPlugin(t *testing.T) { + _, err := checkShaOfPlugin("nonexistentplugin", "") + assert.Error(t, err) + + realfile, err := filepath.Abs("plugin.go") + assert.NoError(t, err) + _, err = checkShaOfPlugin(realfile, "") + assert.NoError(t, err) +} + +func TestDownloadFile(t *testing.T) { + err := downloadFile("error", "", FileDownloaderImpl{}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to download file from") +} diff --git a/utils/plugin/error b/utils/plugin/error new file mode 100755 index 0000000000..30d74d2584 --- /dev/null +++ b/utils/plugin/error @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/utils/plugin/plugin.go b/utils/plugin/plugin.go new file mode 100644 index 0000000000..5555d4db0e --- /dev/null +++ b/utils/plugin/plugin.go @@ -0,0 +1,30 @@ +package plugin + +import ( + "fmt" + "path/filepath" + + "github.com/argoproj/argo-rollouts/utils/defaults" + + "github.com/argoproj/argo-rollouts/utils/config" +) + +// GetPluginLocation returns the location of the plugin on the filesystem via plugin name. If the plugin is not +// configured in the configmap, an error is returned. +func GetPluginLocation(pluginName string) (string, error) { + configMap, err := config.GetConfig() + if err != nil { + return "", fmt.Errorf("failed to get config: %w", err) + } + + for _, item := range configMap.GetMetricPluginsConfig() { + if pluginName == item.Name { + asbFilePath, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, item.Name)) + if err != nil { + return "", fmt.Errorf("failed to get absolute path of plugin folder: %w", err) + } + return asbFilePath, nil + } + } + return "", fmt.Errorf("plugin %s not configured in configmap", pluginName) +} diff --git a/utils/plugin/plugin_test.go b/utils/plugin/plugin_test.go new file mode 100644 index 0000000000..c4d656943f --- /dev/null +++ b/utils/plugin/plugin_test.go @@ -0,0 +1,52 @@ +package plugin + +import ( + "path/filepath" + "testing" + + "github.com/argoproj/argo-rollouts/utils/defaults" + + "github.com/argoproj/argo-rollouts/utils/config" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestGetPluginLocation(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"plugins": "metrics:\n - name: http\n pluginLocation: https://test/plugin\n - name: http-sha\n pluginLocation: https://test/plugin\n pluginSha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + location, err := GetPluginLocation("http") + assert.NoError(t, err) + fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "http")) + assert.NoError(t, err) + assert.Equal(t, fp, location) +} + +func TestGetPluginLocationNoNamedPlugin(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"plugins": "metrics:\n - name: http\n pluginLocation: https://test/plugin\n - name: http-sha\n pluginLocation: https://test/plugin\n pluginSha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + location, err := GetPluginLocation("dose-not-exist") + assert.Error(t, err) + assert.Equal(t, "", location) +} diff --git a/utils/plugin/types/types.go b/utils/plugin/types/types.go new file mode 100644 index 0000000000..ea77d66183 --- /dev/null +++ b/utils/plugin/types/types.go @@ -0,0 +1,38 @@ +package types + +import "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + +type RpcError struct { + ErrorString string +} + +func (e RpcError) Error() string { + return e.ErrorString +} + +type RpcMetricProvider interface { + // Run start a new external system call for a measurement + // Should be idempotent and do nothing if a call has already been started + Run(*v1alpha1.AnalysisRun, v1alpha1.Metric) v1alpha1.Measurement + // Checks if the external system call is finished and returns the current measurement + Resume(*v1alpha1.AnalysisRun, v1alpha1.Metric, v1alpha1.Measurement) v1alpha1.Measurement + // Terminate will terminate an in-progress measurement + Terminate(*v1alpha1.AnalysisRun, v1alpha1.Metric, v1alpha1.Measurement) v1alpha1.Measurement + // GarbageCollect is used to garbage collect completed measurements to the specified limit + GarbageCollect(*v1alpha1.AnalysisRun, v1alpha1.Metric, int) RpcError + // Type gets the provider type + Type() string + // GetMetadata returns any additional metadata which providers need to store/display as part + // of the metric result. For example, Prometheus uses is to store the final resolved queries. + GetMetadata(metric v1alpha1.Metric) map[string]string +} + +type Plugin struct { + Metrics []PluginItem `json:"metrics" yaml:"metrics"` +} + +type PluginItem struct { + Name string `json:"name" yaml:"name"` + PluginLocation string `json:"pluginLocation" yaml:"pluginLocation"` + PluginSha256 string `json:"pluginSha256" yaml:"pluginSha256"` +}