From b787cc536d73f0da32c675fe07924497da914433 Mon Sep 17 00:00:00 2001 From: Zach Aller Date: Thu, 2 Mar 2023 13:25:40 -0600 Subject: [PATCH] feat: add support for traffic router plugins (#2573) * feat: add support for traffic router plugins Signed-off-by: zachaller * finish up refactor Signed-off-by: zachaller * codegen Signed-off-by: zachaller * update docs Signed-off-by: zachaller * rename config field Signed-off-by: zachaller * refactor tests Signed-off-by: zachaller * add godocs Signed-off-by: zachaller * add docs on creating plugins Signed-off-by: zachaller * rename config fields Signed-off-by: zachaller * Change New function to Init for tr Signed-off-by: zachaller * Change New function to Init for metrics Signed-off-by: zachaller * docs update Signed-off-by: zachaller * docs update Signed-off-by: zachaller * codegen Signed-off-by: zachaller * change repo name Signed-off-by: zachaller * small docs changes Signed-off-by: zachaller * fix bad merge comments Signed-off-by: zachaller * remove metric passing from metrics plugin on Init method Signed-off-by: zachaller * fix mutex Signed-off-by: zachaller * wrap errors Signed-off-by: zachaller * rename Signed-off-by: zachaller * docs change Signed-off-by: zachaller * codegen Signed-off-by: zachaller * some updates to docs Signed-off-by: zachaller * change plugin to plugins for tr Signed-off-by: zachaller * change plugin to plugins for tr Signed-off-by: zachaller * refactor naming for metric plugins Signed-off-by: zachaller * lint Signed-off-by: zachaller * change handshake Signed-off-by: zachaller * more renames Signed-off-by: zachaller * change handshake Signed-off-by: zachaller * add err context Signed-off-by: zachaller * lint Signed-off-by: zachaller * small docs change Signed-off-by: zachaller * docs update from pr review Signed-off-by: zachaller * updates from review Signed-off-by: zachaller * change config map format Signed-off-by: zachaller * update docs Signed-off-by: zachaller * add context to error Signed-off-by: zachaller * add context to error Signed-off-by: zachaller * add context to errors ans well as wrap the *bool returned by verifiy weight Signed-off-by: zachaller * update docs for new interface type Signed-off-by: zachaller * change error wraping for init Signed-off-by: zachaller --------- Signed-off-by: zachaller --- Makefile | 4 + docs/analysis/plugins.md | 31 +- docs/features/traffic-management/plugins.md | 73 + docs/plugins.md | 155 +++ manifests/crds/rollout-crd.yaml | 3 + manifests/install.yaml | 3 + metricproviders/metricproviders.go | 5 +- metricproviders/plugin/client/client.go | 57 +- metricproviders/plugin/plugin.go | 14 +- metricproviders/plugin/rpc/rpc.go | 77 +- metricproviders/plugin/rpc/rpc_test.go | 40 +- .../plugin/rpc/rpc_test_implementation.go | 14 +- mkdocs.yml | 2 + pkg/apiclient/rollout/rollout.swagger.json | 10 +- pkg/apis/rollouts/v1alpha1/generated.pb.go | 1169 ++++++++++------- pkg/apis/rollouts/v1alpha1/generated.proto | 8 +- .../rollouts/v1alpha1/openapi_generated.go | 17 +- pkg/apis/rollouts/v1alpha1/types.go | 7 +- .../v1alpha1/zz_generated.deepcopy.go | 15 + rollout/trafficrouting.go | 17 + .../trafficrouting/plugin/client/client.go | 101 ++ rollout/trafficrouting/plugin/plugin.go | 97 ++ rollout/trafficrouting/plugin/rpc/rpc.go | 280 ++++ rollout/trafficrouting/plugin/rpc/rpc_test.go | 177 +++ .../plugin/rpc/rpc_test_implementation.go | 42 + .../internal/plugin/plugin.go | 32 +- .../internal/plugin/plugin_test.go | 14 +- test/cmd/sample-metrics-plugin/main.go | 4 +- .../internal/plugin/plugin.go | 351 +++++ test/cmd/sample-trafficrouter-plugin/main.go | 69 + ui/src/models/rollout/generated/api.ts | 8 +- utils/config/config.go | 59 +- utils/plugin/downloader.go | 44 +- utils/plugin/downloader_test.go | 382 +++--- utils/plugin/error | 1 - utils/plugin/plugin.go | 10 +- utils/plugin/plugin_test.go | 93 +- utils/plugin/types/types.go | 81 +- 38 files changed, 2673 insertions(+), 893 deletions(-) create mode 100644 docs/features/traffic-management/plugins.md create mode 100644 docs/plugins.md create mode 100644 rollout/trafficrouting/plugin/client/client.go create mode 100644 rollout/trafficrouting/plugin/plugin.go create mode 100644 rollout/trafficrouting/plugin/rpc/rpc.go create mode 100644 rollout/trafficrouting/plugin/rpc/rpc_test.go create mode 100644 rollout/trafficrouting/plugin/rpc/rpc_test_implementation.go create mode 100644 test/cmd/sample-trafficrouter-plugin/internal/plugin/plugin.go create mode 100644 test/cmd/sample-trafficrouter-plugin/main.go delete mode 100755 utils/plugin/error diff --git a/Makefile b/Makefile index fda72dde50..7133f0f9b4 100644 --- a/Makefile +++ b/Makefile @@ -286,3 +286,7 @@ checksums: build-sample-metric-plugin-debug: go build -gcflags="all=-N -l" -o metric-plugin test/cmd/sample-metrics-plugin/main.go +.PHONY: build-sample-traffic-plugin-debug +build-sample-traffic-plugin-debug: + go build -gcflags="all=-N -l" -o traffic-plugin test/cmd/sample-trafficrouter-plugin/main.go + diff --git a/docs/analysis/plugins.md b/docs/analysis/plugins.md index 74fb25452d..8b718bf136 100644 --- a/docs/analysis/plugins.md +++ b/docs/analysis/plugins.md @@ -1,11 +1,11 @@ # Metric Plugins -!!! important Available since v1.5 +!!! important Available since v1.5 - Status: Alpha 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) +here: [rollouts-sample_prometheus-metric-plugin](https://github.com/argoproj-labs/rollouts-sample_prometheus-metric-plugin) ## Using a Metric Plugin @@ -14,9 +14,6 @@ into the rollouts controller container. The second method is to use a HTTP(S) se ### 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: @@ -24,7 +21,7 @@ particular infrastructure. Here are a few methods: * 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: +Then you can use the configmap to point to the plugin executable file location. Example: ```yaml apiVersion: v1 @@ -32,10 +29,9 @@ 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:// + metricProviderPlugins: |- + - name: "argoproj-labs/sample-prometheus" # name of the plugin, it must match the name required by the plugin so it can find it's configuration + location: "file://./my-custom-plugin" # supports http(s):// urls and file:// ``` ### Using a HTTP(S) server to host the plugin executable @@ -49,11 +45,10 @@ 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 + metricProviderPlugins: |- + - name: "argoproj-labs/sample-prometheus" # name of the plugin, it must match the name required by the plugin so it can find it's configuration + location: "https://github.com/argoproj-labs/rollouts-sample_prometheus-metric-plugin/releases/download/v0.0.4/metric-plugin-linux-amd64" # supports http(s):// urls and file:// + sha256: "dac10cbf57633c9832a17f8c27d2ca34aa97dd3d" #optional sha256 checksum of the plugin executable ``` ## Some words of caution @@ -66,13 +61,13 @@ 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. +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. It is the +responsibility of the Argo Rollouts administrator to define the plugin installation method considering the risks of each approach. ## 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) +#### [rollouts-sample_prometheus-metric-plugin](https://github.com/argoproj-labs/rollouts-sample_prometheus-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/traffic-management/plugins.md b/docs/features/traffic-management/plugins.md new file mode 100644 index 0000000000..b211ca75d0 --- /dev/null +++ b/docs/features/traffic-management/plugins.md @@ -0,0 +1,73 @@ +# Traffic Router Plugins + +!!! important Available since v1.5 - Status: Alpha + +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: [rollouts-sample_nginx-trafficrouter-plugin](https://github.com/argoproj-labs/rollouts-sample_nginx-trafficrouter-plugin) + +## Using a Traffic Router 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 + +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 file location. Example: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: argo-rollouts-config +data: + trafficRouterPlugins: |- + - name: "argoproj-labs/sample-nginx" # name of the plugin, it must match the name required by the plugin so it can find it's configuration + location: "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: + trafficRouterPlugins: |- + - name: "argoproj-labs/sample-nginx" # name of the plugin, it must match the name required by the plugin so it can find it's configuration + location: "https://github.com/argoproj-labs/rollouts-sample_nginx-trafficrouter-plugin/releases/download/v0.0.1/metric-plugin-linux-amd64" # supports http(s):// urls and file:// + sha256: "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. It is the +responsibility of the Argo Rollouts administrator to define the plugin installation method considering the risks of each approach. + +## 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. +#### [rollouts-sample_nginx-trafficrouter-plugin](https://github.com/argoproj-labs/rollouts-sample_nginx-trafficrouter-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/plugins.md b/docs/plugins.md new file mode 100644 index 0000000000..794ec8157f --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,155 @@ +# Creating an Argo Rollouts Plugin + +## High Level Overview + +Argo Rollouts plugins depend on hashicorp's [go-plugin](https://github.com/hashicorp/go-plugin) library. This library +provides a way for a plugin to be compiled as a standalone executable and then loaded by the rollouts controller at runtime. +This works by having the plugin executable act as a rpc server and the rollouts controller act as a client. The plugin executable +is started by the rollouts controller and is a long-lived process and that the rollouts controller connects to over a unix socket. +The communication protocol uses golang built in net/rpc library so plugins have to be written in golang. + +## Plugin Repository + +In order to get plugins listed in the main argo rollouts documentation we ask that the plugin repository be created under +the [argoproj-labs](https://github.com/argoproj-labs) organization. Please open an issue under argo-rollouts requesting a +repo which you would be granted admin access on. + +There is also a standard naming convention for plugin names used for configmap registration, as well as what the plugin +uses for locating its specific configuration on rollout or analysis resources. The name needs to be in the form of +`/` and both and have a regular expression check that matches Github's requirements +for `username/org` and `repository name`. This requirement is in place to help with allowing multiple creators of the same plugin +types to exist such as `/nginx` and `/nginx`. These names could be based of the repo name such +as `argoproj-labs/rollouts-sample_prometheus-metric-plugin` but it is not a requirement. + +There will also be a standard for naming repositories under argoproj-labs in the form of `rollouts---plugin` +where `` is say `metric`, or `trafficrouter` and `` is the software the plugin is for say nginx. + +## Plugin Name + +So now that we have an idea on plugin naming and repository standards let's pick a name to use for the rest of this +documentation and call our plugin `argoproj-labs/nginx`. + +This name will be used in a few different spots the first is the config map that your plugin users will need to configure. +It looks like this below. + +```yaml +kind: ConfigMap +metadata: + name: argo-rollouts-config +data: + metricProviderPlugins: |- + - name: "argoproj-labs/metrics" + location: "file:///tmp/argo-rollouts/metric-plugin" + trafficRouterPlugins: |- + - name: "argoproj-labs/nginx" + location: "file:///tmp/argo-rollouts/traffic-plugin" +``` + +As you can see there is a field called `name:` under both `metrics` or `trafficrouters` this is the first place where your +end users will need to configure the name of the plugin. The second location is either in the rollout object or the analysis +template which you can see the examples below. + +#### AnalysisTemplate Example +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: AnalysisTemplate +metadata: + name: success-rate +spec: + metrics: + - name: success-rate + ... + provider: + plugin: + argoproj-labs/metrics: + address: http://prometheus.local +``` + +#### Traffic Router Example +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: example-plugin-ro +spec: + strategy: + canary: + canaryService: example-plugin-ro-canary-analysis + stableService: example-plugin-ro-stable-analysis + trafficRouting: + plugins: + argoproj-labs/nginx: + stableIngress: canary-demo +``` + +You can see that we use the plugin name under `spec.metrics[].provider.plugin` for analysis template and `spec.strategy.canary.trafficRouting.plugins` +for traffic routers. You as a plugin author can then put any configuration you need under `argoproj-labs/nginx` and you will be able to +look up that config in your plugin via the plugin name key. You will also want to document what configuration options your plugin supports. + +## Plugin Interfaces + +Argo Rollouts currently supports two plugin systems as a plugin author your end goal is to implement these interfaces as +a hashicorp go-plugin. The two interfaces are `MetricsPlugin` and `TrafficRouterPlugin` for each of the respective plugins: + +```go +type MetricProviderPlugin interface { + // InitPlugin initializes the traffic router plugin this gets called once when the plugin is loaded. + InitPlugin() RpcError + // 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) 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 TrafficRouterPlugin interface { + // InitPlugin initializes the traffic router plugin this gets called once when the plugin is loaded. + InitPlugin() RpcError + // UpdateHash informs a traffic routing reconciler about new canary, stable, and additionalDestination(s) pod hashes + UpdateHash(rollout *v1alpha1.Rollout, canaryHash, stableHash string, additionalDestinations []v1alpha1.WeightDestination) RpcError + // SetWeight sets the canary weight to the desired weight + SetWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) RpcError + // SetHeaderRoute sets the header routing step + SetHeaderRoute(rollout *v1alpha1.Rollout, setHeaderRoute *v1alpha1.SetHeaderRoute) RpcError + // SetMirrorRoute sets up the traffic router to mirror traffic to a service + SetMirrorRoute(rollout *v1alpha1.Rollout, setMirrorRoute *v1alpha1.SetMirrorRoute) RpcError + // VerifyWeight returns true if the canary is at the desired weight and additionalDestinations are at the weights specified + // Returns nil if weight verification is not supported or not applicable + VerifyWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) (RpcVerified, RpcError) + // RemoveManagedRoutes Removes all routes that are managed by rollouts by looking at spec.strategy.canary.trafficRouting.managedRoutes + RemoveManagedRoutes(ro *v1alpha1.Rollout) RpcError + // Type returns the type of the traffic routing reconciler + Type() string +} +``` + +## Plugin Init Function + +Each plugin interface has a `InitPlugin` function, this function is called when the plugin is first started up and is only called +once per startup. The `InitPlugin` function is used as a means to initialize the plugin it gives you the plugin author the ability +to either set up a client for a specific metrics provider or in the case of a traffic router construct a client or informer +for kubernetes api. The one thing to note about this though is because these calls happen over RPC the plugin author should +not depend on state being stored in the plugin struct as it will not be persisted between calls. + +## Kubernetes RBAC + +The plugin runs as a child process of the rollouts controller and as such it will inherit the same RBAC permissions as the +controller. This means that the service account for the rollouts controller will need the correct permissions for the plugin +to function. This might mean instructing users to create a role and role binding to the standard rollouts service account +for the plugin to use. This will probably affect traffic router plugins more than metrics plugins. + +## Sample Plugins + +There are two sample plugins within the argo-rollouts repo that you can use as a reference for creating your own plugin. + +* [Sample Metrics Plugin](https://github.com/argoproj/argo-rollouts/tree/master/test/cmd/sample-metrics-plugin) +* [Sample Traffic Router Plugin](https://github.com/argoproj/argo-rollouts/tree/master/test/cmd/sample-trafficrouter-plugin) \ No newline at end of file diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index 39fe9adc16..dc59d54fed 100755 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -862,6 +862,9 @@ spec: required: - stableIngress type: object + plugins: + type: object + x-kubernetes-preserve-unknown-fields: true smi: properties: rootService: diff --git a/manifests/install.yaml b/manifests/install.yaml index a0122414c9..4117df7452 100755 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -11908,6 +11908,9 @@ spec: required: - stableIngress type: object + plugins: + type: object + x-kubernetes-preserve-unknown-fields: true smi: properties: rootService: diff --git a/metricproviders/metricproviders.go b/metricproviders/metricproviders.go index e2803ffc09..ed544a7f04 100644 --- a/metricproviders/metricproviders.go +++ b/metricproviders/metricproviders.go @@ -94,7 +94,10 @@ func (f *ProviderFactory) NewProvider(logCtx log.Entry, metric v1alpha1.Metric) return skywalking.NewSkyWalkingProvider(client, logCtx), nil case plugin.ProviderType: plugin, err := plugin.NewRpcPlugin(metric) - return plugin, err + if err != nil { + return nil, fmt.Errorf("failed to create plugin: %v", err) + } + return plugin, nil default: return nil, fmt.Errorf("no valid provider in metric '%s'", metric.Name) } diff --git a/metricproviders/plugin/client/client.go b/metricproviders/plugin/client/client.go index 3131a53578..97e76e0b0f 100644 --- a/metricproviders/plugin/client/client.go +++ b/metricproviders/plugin/client/client.go @@ -13,19 +13,31 @@ import ( type metricPlugin struct { pluginClient map[string]*goPlugin.Client - plugin map[string]rpc.MetricsPlugin + plugin map[string]rpc.MetricProviderPlugin } var pluginClients *metricPlugin var once sync.Once +var mutex sync.Mutex + +var handshakeConfig = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "metricprovider", +} + +// pluginMap is the map of plugins we can dispense. +var pluginMap = map[string]goPlugin.Plugin{ + "RpcMetricProviderPlugin": &rpc.RpcMetricProviderPlugin{}, +} // 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) { +func GetMetricPlugin(metric v1alpha1.Metric) (rpc.MetricProviderPlugin, error) { once.Do(func() { pluginClients = &metricPlugin{ pluginClient: make(map[string]*goPlugin.Client), - plugin: make(map[string]rpc.MetricsPlugin), + plugin: make(map[string]rpc.MetricProviderPlugin), } }) plugin, err := pluginClients.startPluginSystem(metric) @@ -35,19 +47,12 @@ func GetMetricPlugin(metric v1alpha1.Metric) (rpc.MetricsPlugin, error) { 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{}, - } +func (m *metricPlugin) startPluginSystem(metric v1alpha1.Metric) (rpc.MetricProviderPlugin, error) { + mutex.Lock() + defer mutex.Unlock() - //There should only ever be one plugin defined in metric.Provider.Plugin + // There should only ever be one plugin defined in metric.Provider.Plugin per analysis template this gets checked + // during validation for pluginName := range metric.Provider.Plugin { pluginPath, err := plugin.GetPluginLocation(pluginName) if err != nil { @@ -55,6 +60,7 @@ func (m *metricPlugin) startPluginSystem(metric v1alpha1.Metric) (rpc.MetricsPlu } if m.pluginClient[pluginName] == nil || m.pluginClient[pluginName].Exited() { + m.pluginClient[pluginName] = goPlugin.NewClient(&goPlugin.ClientConfig{ HandshakeConfig: handshakeConfig, Plugins: pluginMap, @@ -64,28 +70,39 @@ func (m *metricPlugin) startPluginSystem(metric v1alpha1.Metric) (rpc.MetricsPlu rpcClient, err := m.pluginClient[pluginName].Client() if err != nil { - return nil, fmt.Errorf("unable to start plugin (%s): %w", pluginName, err) + return nil, fmt.Errorf("unable to get plugin client (%s): %w", pluginName, err) } // Request the plugin - plugin, err := rpcClient.Dispense("RpcMetricsPlugin") + plugin, err := rpcClient.Dispense("RpcMetricProviderPlugin") if err != nil { return nil, fmt.Errorf("unable to dispense plugin (%s): %w", pluginName, err) } - pluginType, ok := plugin.(rpc.MetricsPlugin) + pluginType, ok := plugin.(rpc.MetricProviderPlugin) if !ok { return nil, fmt.Errorf("unexpected type from plugin") } m.plugin[pluginName] = pluginType - err = m.plugin[pluginName].NewMetricsPlugin(metric) - if err.Error() != "" { + resp := m.plugin[pluginName].InitPlugin() + if resp.HasError() { return nil, fmt.Errorf("unable to initialize plugin via rpc (%s): %w", pluginName, err) } } + client, err := m.pluginClient[pluginName].Client() + if err != nil { + return nil, fmt.Errorf("unable to get plugin client (%s) for ping: %w", pluginName, err) + } + if err := client.Ping(); err != nil { + m.pluginClient[pluginName].Kill() + m.pluginClient[pluginName] = nil + return nil, fmt.Errorf("could not ping plugin will cleanup process so we can restart it next reconcile (%w)", 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 index ed8a1aee97..4755952a32 100644 --- a/metricproviders/plugin/plugin.go +++ b/metricproviders/plugin/plugin.go @@ -1,6 +1,8 @@ package plugin import ( + "fmt" + "github.com/argoproj/argo-rollouts/metric" "github.com/argoproj/argo-rollouts/metricproviders/plugin/client" "github.com/argoproj/argo-rollouts/metricproviders/plugin/rpc" @@ -10,26 +12,26 @@ import ( const ProviderType = "RPCPlugin" type MetricPlugin struct { - rpc.MetricsPlugin + rpc.MetricProviderPlugin } // 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 nil, fmt.Errorf("unable to get metric plugin: %w", err) } return MetricPlugin{ - MetricsPlugin: pluginClient, + MetricProviderPlugin: 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 + resp := m.MetricProviderPlugin.GarbageCollect(run, metric, limit) + if resp.HasError() { + return fmt.Errorf("failed to garbage collect via plugin: %w", resp) } return nil } diff --git a/metricproviders/plugin/rpc/rpc.go b/metricproviders/plugin/rpc/rpc.go index 3d085002da..7f703b5ec9 100644 --- a/metricproviders/plugin/rpc/rpc.go +++ b/metricproviders/plugin/rpc/rpc.go @@ -10,7 +10,6 @@ import ( "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 { @@ -30,7 +29,7 @@ type GarbageCollectArgs struct { Limit int } -type InitMetricsPluginAndGetMetadataArgs struct { +type GetMetadataArgs struct { Metric v1alpha1.Metric } @@ -38,30 +37,28 @@ 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)) + gob.RegisterName("GetMetadataArgs", new(GetMetadataArgs)) } -// MetricsPlugin is the interface that we're exposing as a plugin. It needs to match metricproviders.Providers but we can +var _ types.RpcMetricProvider = &MetricsPluginRPC{} + +// MetricProviderPlugin 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 +type MetricProviderPlugin interface { + InitPlugin() 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 +// InitPlugin is the client side function that is wrapped by a local provider this makes a rpc call to the // server side function. -func (g *MetricsPluginRPC) NewMetricsPlugin(metric v1alpha1.Metric) types.RpcError { +func (g *MetricsPluginRPC) InitPlugin() types.RpcError { var resp types.RpcError - var args interface{} = InitMetricsPluginAndGetMetadataArgs{ - Metric: metric, - } - err := g.client.Call("Plugin.NewMetricsPlugin", &args, &resp) + err := g.client.Call("Plugin.InitPlugin", new(interface{}), &resp) if err != nil { - return types.RpcError{ErrorString: err.Error()} + return types.RpcError{ErrorString: fmt.Sprintf("InitPlugin rpc call error: %s", err)} } return resp } @@ -75,7 +72,10 @@ func (g *MetricsPluginRPC) Run(analysisRun *v1alpha1.AnalysisRun, metric v1alpha } err := g.client.Call("Plugin.Run", &args, &resp) if err != nil { - return metricutil.MarkMeasurementError(resp, err) + return metricutil.MarkMeasurementError(resp, fmt.Errorf("Run rpc call error: %s", err)) + } + if resp.Phase == v1alpha1.AnalysisPhaseError { + resp.Message = fmt.Sprintf("failed to run via plugin: %s", resp.Message) } return resp } @@ -90,7 +90,10 @@ func (g *MetricsPluginRPC) Resume(analysisRun *v1alpha1.AnalysisRun, metric v1al } err := g.client.Call("Plugin.Resume", &args, &resp) if err != nil { - return metricutil.MarkMeasurementError(resp, err) + return metricutil.MarkMeasurementError(resp, fmt.Errorf("Resume rpc call error: %s", err)) + } + if resp.Phase == v1alpha1.AnalysisPhaseError { + resp.Message = fmt.Sprintf("failed to resume via plugin: %s", resp.Message) } return resp } @@ -105,7 +108,10 @@ func (g *MetricsPluginRPC) Terminate(analysisRun *v1alpha1.AnalysisRun, metric v } err := g.client.Call("Plugin.Terminate", &args, &resp) if err != nil { - return metricutil.MarkMeasurementError(resp, err) + return metricutil.MarkMeasurementError(resp, fmt.Errorf("Terminate rpc call error: %s", err)) + } + if resp.Phase == v1alpha1.AnalysisPhaseError { + resp.Message = fmt.Sprintf("failed to terminate via plugin: %s", resp.Message) } return resp } @@ -120,7 +126,7 @@ func (g *MetricsPluginRPC) GarbageCollect(analysisRun *v1alpha1.AnalysisRun, met } err := g.client.Call("Plugin.GarbageCollect", &args, &resp) if err != nil { - return types.RpcError{ErrorString: err.Error()} + return types.RpcError{ErrorString: fmt.Sprintf("GarbageCollect rpc call error: %s", err)} } return resp } @@ -130,22 +136,23 @@ func (g *MetricsPluginRPC) Type() string { var resp string err := g.client.Call("Plugin.Type", new(interface{}), &resp) if err != nil { - return err.Error() + return fmt.Sprintf("Type rpc call error: %s", err) } - 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{ + var args interface{} = GetMetadataArgs{ 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 map[string]string{"error": fmt.Sprintf("GetMetadata rpc call error: %s", err)} + } + if resp != nil && resp["error"] != "" { + resp["error"] = fmt.Sprintf("failed to get metadata via plugin: %s", resp["error"]) } return resp } @@ -154,17 +161,13 @@ func (g *MetricsPluginRPC) GetMetadata(metric v1alpha1.Metric) map[string]string // the requirements of net/rpc type MetricsRPCServer struct { // This is the real implementation - Impl MetricsPlugin + Impl MetricProviderPlugin } -// NewMetricsPlugin is the receiving end of the RPC call running in the plugin executable process (the server), and it calls the +// InitPlugin 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) +func (s *MetricsRPCServer) InitPlugin(args interface{}, resp *types.RpcError) error { + *resp = s.Impl.InitPlugin() return nil } @@ -222,7 +225,7 @@ func (s *MetricsRPCServer) Type(args interface{}, resp *string) error { // 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) + getMetadataArgs, ok := args.(*GetMetadataArgs) if !ok { return fmt.Errorf("invalid args %s", args) } @@ -230,7 +233,7 @@ func (s *MetricsRPCServer) GetMetadata(args interface{}, resp *map[string]string return nil } -// RpcMetricsPlugin This is the implementation of plugin.Plugin so we can serve/consume +// RpcMetricProviderPlugin 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. @@ -240,15 +243,15 @@ func (s *MetricsRPCServer) GetMetadata(args interface{}, resp *map[string]string // // Ignore MuxBroker. That is used to create more multiplexed streams on our // plugin connection and is a more advanced use case. -type RpcMetricsPlugin struct { +type RpcMetricProviderPlugin struct { // Impl Injection - Impl MetricsPlugin + Impl MetricProviderPlugin } -func (p *RpcMetricsPlugin) Server(*plugin.MuxBroker) (interface{}, error) { +func (p *RpcMetricProviderPlugin) Server(*plugin.MuxBroker) (interface{}, error) { return &MetricsRPCServer{Impl: p.Impl}, nil } -func (RpcMetricsPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { +func (RpcMetricProviderPlugin) 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 index 2313b7bb0a..ad351149c3 100644 --- a/metricproviders/plugin/rpc/rpc_test.go +++ b/metricproviders/plugin/rpc/rpc_test.go @@ -2,7 +2,6 @@ package rpc import ( "context" - "encoding/json" "testing" "time" @@ -16,17 +15,17 @@ import ( var testHandshake = goPlugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", - MagicCookieValue: "metrics", + MagicCookieValue: "metricprovider", } -func pluginClient(t *testing.T) (MetricsPlugin, goPlugin.ClientProtocol, func(), chan struct{}) { +func pluginClient(t *testing.T) (MetricProviderPlugin, 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}, + "RpcMetricProviderPlugin": &RpcMetricProviderPlugin{Impl: rpcPluginImp}, } ch := make(chan *goPlugin.ReattachConfig, 1) @@ -65,12 +64,12 @@ func pluginClient(t *testing.T) (MetricsPlugin, goPlugin.ClientProtocol, func(), } // Request the plugin - raw, err := client.Dispense("RpcMetricsPlugin") + raw, err := client.Dispense("RpcMetricProviderPlugin") if err != nil { t.Fail() } - plugin, ok := raw.(MetricsPlugin) + plugin, ok := raw.(MetricProviderPlugin) if !ok { t.Fail() } @@ -82,11 +81,7 @@ 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"}`)}, - }, - }) + err := plugin.InitPlugin() if err.Error() != "" { t.Fail() } @@ -96,7 +91,7 @@ func TestPlugin(t *testing.T) { runMeasurementErr := plugin.Run(nil, v1alpha1.Metric{}) assert.Equal(t, "Error", string(runMeasurementErr.Phase)) - assert.Equal(t, "analysisRun is nil", runMeasurementErr.Message) + assert.Contains(t, runMeasurementErr.Message, "analysisRun is nil") resumeMeasurement := plugin.Resume(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{ Phase: "TestCompletedResume", @@ -135,29 +130,29 @@ func TestPluginClosedConnection(t *testing.T) { const expectedError = "connection is shut down" - newMetrics := plugin.NewMetricsPlugin(v1alpha1.Metric{}) - assert.Equal(t, expectedError, newMetrics.Error()) + newMetrics := plugin.InitPlugin() + assert.Contains(t, newMetrics.Error(), expectedError) measurement := plugin.Terminate(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{}) - assert.Equal(t, expectedError, measurement.Message) + assert.Contains(t, measurement.Message, expectedError) measurement = plugin.Run(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}) - assert.Equal(t, expectedError, measurement.Message) + assert.Contains(t, measurement.Message, expectedError) measurement = plugin.Resume(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{}) - assert.Equal(t, expectedError, measurement.Message) + assert.Contains(t, measurement.Message, expectedError) measurement = plugin.Terminate(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, v1alpha1.Measurement{}) - assert.Equal(t, expectedError, measurement.Message) + assert.Contains(t, measurement.Message, expectedError) typeStr := plugin.Type() - assert.Equal(t, expectedError, typeStr) + assert.Contains(t, typeStr, expectedError) metadata := plugin.GetMetadata(v1alpha1.Metric{}) - assert.Equal(t, expectedError, metadata["error"]) + assert.Contains(t, metadata["error"], expectedError) gcError := plugin.GarbageCollect(&v1alpha1.AnalysisRun{}, v1alpha1.Metric{}, 0) - assert.Equal(t, expectedError, gcError.Error()) + assert.Contains(t, gcError.Error(), expectedError) cancel() <-closeCh @@ -180,9 +175,6 @@ func TestInvalidArgs(t *testing.T) { 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 index 4962f16eb4..2dd18d6434 100644 --- a/metricproviders/plugin/rpc/rpc_test_implementation.go +++ b/metricproviders/plugin/rpc/rpc_test_implementation.go @@ -4,8 +4,6 @@ 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" @@ -16,17 +14,7 @@ import ( 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()} - } +func (g *testRpcPlugin) InitPlugin() types.RpcError { return types.RpcError{} } diff --git a/mkdocs.yml b/mkdocs.yml index 2b6073be4b..f8159bd5d9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,6 +51,7 @@ nav: - AWS ALB: features/traffic-management/alb.md - Istio: features/traffic-management/istio.md - NGINX: features/traffic-management/nginx.md + - Plugins: traffic-management/plugins.md - SMI: features/traffic-management/smi.md - Traefik: features/traffic-management/traefik.md - Analysis: @@ -134,6 +135,7 @@ nav: - Contribution Guide: CONTRIBUTING.md - Environment Setup: getting-started/setup/index.md - Releasing: releasing.md + - Plugins: plugins.md - Releases ⧉: https://github.com/argoproj/argo-rollouts/releases - Roadmap ⧉: https://github.com/argoproj/argo-rollouts/milestones - Blog ⧉: https://blog.argoproj.io/ diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index ef21ac88f4..0f4f6e3f51 100755 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -1618,11 +1618,19 @@ "items": { "$ref": "#/definitions/github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MangedRoutes" }, - "description": "A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream\ntraffic router." + "description": "ManagedRoutes A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream\ntraffic router." }, "apisix": { "$ref": "#/definitions/github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.ApisixTrafficRouting", "title": "Apisix holds specific configuration to use Apisix to route traffic" + }, + "plugins": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "byte" + }, + "title": "+kubebuilder:validation:Schemaless\n+kubebuilder:pruning:PreserveUnknownFields\n+kubebuilder:validation:Type=object\nPlugins holds specific configuration that traffic router plugins can use for routing traffic" } }, "title": "RolloutTrafficRouting hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing" diff --git a/pkg/apis/rollouts/v1alpha1/generated.pb.go b/pkg/apis/rollouts/v1alpha1/generated.pb.go index c424ad1f28..6845833229 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.pb.go +++ b/pkg/apis/rollouts/v1alpha1/generated.pb.go @@ -3171,6 +3171,7 @@ func init() { proto.RegisterType((*RolloutStatus)(nil), "github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RolloutStatus") proto.RegisterType((*RolloutStrategy)(nil), "github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RolloutStrategy") proto.RegisterType((*RolloutTrafficRouting)(nil), "github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RolloutTrafficRouting") + proto.RegisterMapType((map[string]encoding_json.RawMessage)(nil), "github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RolloutTrafficRouting.PluginsEntry") proto.RegisterType((*RouteMatch)(nil), "github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RouteMatch") proto.RegisterMapType((map[string]StringMatch)(nil), "github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RouteMatch.HeadersEntry") proto.RegisterType((*RunSummary)(nil), "github.conef.uk.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RunSummary") @@ -3202,500 +3203,503 @@ func init() { } var fileDescriptor_e0e705f843545fab = []byte{ - // 7887 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5b, 0x6c, 0x24, 0xd9, - 0x75, 0xd8, 0x56, 0x37, 0x9b, 0xec, 0x3e, 0x7c, 0xdf, 0xe1, 0x68, 0xb8, 0xdc, 0x9d, 0xe9, 0x55, - 0xad, 0xb1, 0x59, 0x3b, 0x2b, 0x52, 0x5a, 0xed, 0x26, 0x6b, 0xaf, 0xb0, 0x49, 0x37, 0x39, 0xb3, - 0xc3, 0x59, 0x72, 0xa6, 0xe7, 0x36, 0x67, 0x46, 0x96, 0xb4, 0xb6, 0x8a, 0xdd, 0x97, 0xcd, 0x1a, - 0x56, 0x57, 0xb5, 0xab, 0xaa, 0xc9, 0xe1, 0x6a, 0x61, 0xad, 0x2c, 0xec, 0x46, 0x31, 0x24, 0x78, - 0x13, 0x5b, 0x08, 0x82, 0x04, 0x81, 0x60, 0x08, 0xc8, 0x43, 0xfe, 0x32, 0x12, 0xe4, 0xc7, 0x40, - 0x82, 0xf8, 0x11, 0xe5, 0xc3, 0x81, 0xfc, 0x91, 0xc8, 0x36, 0xe0, 0x76, 0x96, 0xca, 0x4f, 0x82, - 0x04, 0x46, 0x00, 0x07, 0x81, 0xe7, 0x23, 0x08, 0xee, 0xb3, 0x6e, 0x55, 0x57, 0x93, 0xdd, 0xec, - 0xe2, 0x68, 0x11, 0xfb, 0xaf, 0xfb, 0x9e, 0x73, 0xcf, 0x39, 0x75, 0x9f, 0xe7, 0x9e, 0x7b, 0xce, - 0xb9, 0xb0, 0xd5, 0xb2, 0xc3, 0xfd, 0xee, 0xee, 0x6a, 0xc3, 0x6b, 0xaf, 0x59, 0x7e, 0xcb, 0xeb, - 0xf8, 0xde, 0x43, 0xf6, 0xe3, 0x53, 0xbe, 0xe7, 0x38, 0x5e, 0x37, 0x0c, 0xd6, 0x3a, 0x07, 0xad, - 0x35, 0xab, 0x63, 0x07, 0x6b, 0xaa, 0xe4, 0xf0, 0x33, 0x96, 0xd3, 0xd9, 0xb7, 0x3e, 0xb3, 0xd6, - 0x22, 0x2e, 0xf1, 0xad, 0x90, 0x34, 0x57, 0x3b, 0xbe, 0x17, 0x7a, 0xe8, 0x73, 0x11, 0xb5, 0x55, - 0x49, 0x8d, 0xfd, 0xf8, 0x79, 0x59, 0x77, 0xb5, 0x73, 0xd0, 0x5a, 0xa5, 0xd4, 0x56, 0x55, 0x89, - 0xa4, 0xb6, 0xf2, 0x29, 0x4d, 0x96, 0x96, 0xd7, 0xf2, 0xd6, 0x18, 0xd1, 0xdd, 0xee, 0x1e, 0xfb, - 0xc7, 0xfe, 0xb0, 0x5f, 0x9c, 0xd9, 0xca, 0xf3, 0x07, 0xaf, 0x05, 0xab, 0xb6, 0x47, 0x65, 0x5b, - 0xdb, 0xb5, 0xc2, 0xc6, 0xfe, 0xda, 0x61, 0x9f, 0x44, 0x2b, 0xa6, 0x86, 0xd4, 0xf0, 0x7c, 0x92, - 0x86, 0xf3, 0x4a, 0x84, 0xd3, 0xb6, 0x1a, 0xfb, 0xb6, 0x4b, 0xfc, 0xe3, 0xe8, 0xab, 0xdb, 0x24, - 0xb4, 0xd2, 0x6a, 0xad, 0x0d, 0xaa, 0xe5, 0x77, 0xdd, 0xd0, 0x6e, 0x93, 0xbe, 0x0a, 0x7f, 0xe3, - 0xac, 0x0a, 0x41, 0x63, 0x9f, 0xb4, 0xad, 0xbe, 0x7a, 0x9f, 0x1d, 0x54, 0xaf, 0x1b, 0xda, 0xce, - 0x9a, 0xed, 0x86, 0x41, 0xe8, 0x27, 0x2b, 0x99, 0xbf, 0x93, 0x87, 0x52, 0x65, 0xab, 0x5a, 0x0f, - 0xad, 0xb0, 0x1b, 0xa0, 0x0f, 0x0c, 0x98, 0x71, 0x3c, 0xab, 0x59, 0xb5, 0x1c, 0xcb, 0x6d, 0x10, - 0x7f, 0xd9, 0x78, 0xce, 0x78, 0x71, 0xfa, 0xe5, 0xad, 0xd5, 0x71, 0xfa, 0x6b, 0xb5, 0x72, 0x14, - 0x60, 0x12, 0x78, 0x5d, 0xbf, 0x41, 0x30, 0xd9, 0xab, 0x2e, 0x7d, 0xbf, 0x57, 0x7e, 0xea, 0xa4, - 0x57, 0x9e, 0xd9, 0xd2, 0x38, 0xe1, 0x18, 0x5f, 0xf4, 0x6d, 0x03, 0x16, 0x1b, 0x96, 0x6b, 0xf9, - 0xc7, 0x3b, 0x96, 0xdf, 0x22, 0xe1, 0x9b, 0xbe, 0xd7, 0xed, 0x2c, 0xe7, 0x2e, 0x40, 0x9a, 0xa7, - 0x85, 0x34, 0x8b, 0xeb, 0x49, 0x76, 0xb8, 0x5f, 0x02, 0x26, 0x57, 0x10, 0x5a, 0xbb, 0x0e, 0xd1, - 0xe5, 0xca, 0x5f, 0xa4, 0x5c, 0xf5, 0x24, 0x3b, 0xdc, 0x2f, 0x81, 0xf9, 0x7e, 0x1e, 0x16, 0x2b, - 0x5b, 0xd5, 0x1d, 0xdf, 0xda, 0xdb, 0xb3, 0x1b, 0xd8, 0xeb, 0x86, 0xb6, 0xdb, 0x42, 0x3f, 0x09, - 0x53, 0xb6, 0xdb, 0xf2, 0x49, 0x10, 0xb0, 0x8e, 0x2c, 0x55, 0xe7, 0x05, 0xd1, 0xa9, 0x4d, 0x5e, - 0x8c, 0x25, 0x1c, 0xbd, 0x0a, 0xd3, 0x01, 0xf1, 0x0f, 0xed, 0x06, 0xa9, 0x79, 0x7e, 0xc8, 0x5a, - 0xba, 0x50, 0xbd, 0x24, 0xd0, 0xa7, 0xeb, 0x11, 0x08, 0xeb, 0x78, 0xb4, 0x9a, 0xef, 0x79, 0xa1, - 0x80, 0xb3, 0x86, 0x28, 0x45, 0xd5, 0x70, 0x04, 0xc2, 0x3a, 0x1e, 0xfa, 0xd0, 0x80, 0x85, 0x20, - 0xb4, 0x1b, 0x07, 0xb6, 0x4b, 0x82, 0x60, 0xdd, 0x73, 0xf7, 0xec, 0xd6, 0x72, 0x81, 0xb5, 0xe2, - 0xed, 0xf1, 0x5a, 0xb1, 0x9e, 0xa0, 0x5a, 0x5d, 0x3a, 0xe9, 0x95, 0x17, 0x92, 0xa5, 0xb8, 0x8f, - 0x3b, 0xda, 0x80, 0x05, 0xcb, 0x75, 0xbd, 0xd0, 0x0a, 0x6d, 0xcf, 0xad, 0xf9, 0x64, 0xcf, 0x7e, - 0xb4, 0x3c, 0xc1, 0x3e, 0x67, 0x59, 0x7c, 0xce, 0x42, 0x25, 0x01, 0xc7, 0x7d, 0x35, 0xcc, 0x0d, - 0x58, 0xae, 0xb4, 0x77, 0xad, 0x20, 0xb0, 0x9a, 0x9e, 0x9f, 0xe8, 0x8d, 0x17, 0xa1, 0xd8, 0xb6, - 0x3a, 0x1d, 0xdb, 0x6d, 0xd1, 0xee, 0xc8, 0xbf, 0x58, 0xaa, 0xce, 0x9c, 0xf4, 0xca, 0xc5, 0x6d, - 0x51, 0x86, 0x15, 0xd4, 0xfc, 0xa3, 0x1c, 0x4c, 0x57, 0x5c, 0xcb, 0x39, 0x0e, 0xec, 0x00, 0x77, - 0x5d, 0xf4, 0x65, 0x28, 0xd2, 0xd5, 0xa5, 0x69, 0x85, 0x96, 0x98, 0x91, 0x9f, 0x5e, 0xe5, 0x93, - 0x7d, 0x55, 0x9f, 0xec, 0x51, 0xbb, 0x50, 0xec, 0xd5, 0xc3, 0xcf, 0xac, 0xde, 0xd9, 0x7d, 0x48, - 0x1a, 0xe1, 0x36, 0x09, 0xad, 0x2a, 0x12, 0x5f, 0x01, 0x51, 0x19, 0x56, 0x54, 0x91, 0x07, 0x13, - 0x41, 0x87, 0x34, 0xc4, 0x0c, 0xdb, 0x1e, 0x73, 0x24, 0x47, 0xa2, 0xd7, 0x3b, 0xa4, 0x51, 0x9d, - 0x11, 0xac, 0x27, 0xe8, 0x3f, 0xcc, 0x18, 0xa1, 0x23, 0x98, 0x0c, 0xd8, 0x9a, 0x23, 0x26, 0xcf, - 0x9d, 0xec, 0x58, 0x32, 0xb2, 0xd5, 0x39, 0xc1, 0x74, 0x92, 0xff, 0xc7, 0x82, 0x9d, 0xf9, 0xc7, - 0x06, 0x5c, 0xd2, 0xb0, 0x2b, 0x7e, 0xab, 0xdb, 0x26, 0x6e, 0x88, 0x9e, 0x83, 0x09, 0xd7, 0x6a, - 0x13, 0x31, 0x51, 0x94, 0xc8, 0xb7, 0xad, 0x36, 0xc1, 0x0c, 0x82, 0x9e, 0x87, 0xc2, 0xa1, 0xe5, - 0x74, 0x09, 0x6b, 0xa4, 0x52, 0x75, 0x56, 0xa0, 0x14, 0xee, 0xd3, 0x42, 0xcc, 0x61, 0xe8, 0x5d, - 0x28, 0xb1, 0x1f, 0x37, 0x7c, 0xaf, 0x9d, 0xd1, 0xa7, 0x09, 0x09, 0xef, 0x4b, 0xb2, 0xd5, 0xd9, - 0x93, 0x5e, 0xb9, 0xa4, 0xfe, 0xe2, 0x88, 0xa1, 0xf9, 0xa7, 0x06, 0xcc, 0x6b, 0x1f, 0xb7, 0x65, - 0x07, 0x21, 0xfa, 0x52, 0xdf, 0xe0, 0x59, 0x1d, 0x6e, 0xf0, 0xd0, 0xda, 0x6c, 0xe8, 0x2c, 0x88, - 0x2f, 0x2d, 0xca, 0x12, 0x6d, 0xe0, 0xb8, 0x50, 0xb0, 0x43, 0xd2, 0x0e, 0x96, 0x73, 0xcf, 0xe5, - 0x5f, 0x9c, 0x7e, 0x79, 0x33, 0xb3, 0x6e, 0x8c, 0xda, 0x77, 0x93, 0xd2, 0xc7, 0x9c, 0x8d, 0xf9, - 0x9b, 0x13, 0xb1, 0x2f, 0xa4, 0x23, 0x0a, 0x79, 0x30, 0xd5, 0x26, 0xa1, 0x6f, 0x37, 0xf8, 0xbc, - 0x9a, 0x7e, 0x79, 0x63, 0x3c, 0x29, 0xb6, 0x19, 0xb1, 0x68, 0xb1, 0xe4, 0xff, 0x03, 0x2c, 0xb9, - 0xa0, 0x7d, 0x98, 0xb0, 0xfc, 0x96, 0xfc, 0xe6, 0x1b, 0xd9, 0xf4, 0x6f, 0x34, 0xe6, 0x2a, 0x7e, - 0x2b, 0xc0, 0x8c, 0x03, 0x5a, 0x83, 0x52, 0x48, 0xfc, 0xb6, 0xed, 0x5a, 0x21, 0x5f, 0x5d, 0x8b, - 0xd5, 0x45, 0x81, 0x56, 0xda, 0x91, 0x00, 0x1c, 0xe1, 0x20, 0x07, 0x26, 0x9b, 0xfe, 0x31, 0xee, - 0xba, 0xcb, 0x13, 0x59, 0x34, 0xc5, 0x06, 0xa3, 0x15, 0x4d, 0x26, 0xfe, 0x1f, 0x0b, 0x1e, 0xe8, - 0xbb, 0x06, 0x2c, 0xb5, 0x89, 0x15, 0x74, 0x7d, 0x42, 0x3f, 0x01, 0x93, 0x90, 0xb8, 0x74, 0x35, - 0x5c, 0x2e, 0x30, 0xe6, 0x78, 0xdc, 0x7e, 0xe8, 0xa7, 0x5c, 0x7d, 0x56, 0x88, 0xb2, 0x94, 0x06, - 0xc5, 0xa9, 0xd2, 0x98, 0x7f, 0x34, 0x01, 0x8b, 0x7d, 0x2b, 0x04, 0x7a, 0x05, 0x0a, 0x9d, 0x7d, - 0x2b, 0x90, 0x53, 0xfe, 0x9a, 0x1c, 0x6f, 0x35, 0x5a, 0xf8, 0xb8, 0x57, 0x9e, 0x95, 0x55, 0x58, - 0x01, 0xe6, 0xc8, 0x74, 0x4f, 0x6d, 0x93, 0x20, 0xb0, 0x5a, 0x72, 0x1d, 0xd0, 0x86, 0x09, 0x2b, - 0xc6, 0x12, 0x8e, 0xfe, 0x8e, 0x01, 0xb3, 0x7c, 0xc8, 0x60, 0x12, 0x74, 0x9d, 0x90, 0xae, 0x75, - 0xb4, 0x59, 0x6e, 0x65, 0x31, 0x3c, 0x39, 0xc9, 0xea, 0x65, 0xc1, 0x7d, 0x56, 0x2f, 0x0d, 0x70, - 0x9c, 0x2f, 0x7a, 0x00, 0xa5, 0x20, 0xb4, 0xfc, 0x90, 0x34, 0x2b, 0x21, 0xdb, 0xd5, 0xa6, 0x5f, - 0xfe, 0xa9, 0xe1, 0x16, 0x81, 0x1d, 0xbb, 0x4d, 0xf8, 0x82, 0x53, 0x97, 0x04, 0x70, 0x44, 0x0b, - 0xbd, 0x0b, 0xe0, 0x77, 0xdd, 0x7a, 0xb7, 0xdd, 0xb6, 0xfc, 0x63, 0xb1, 0x83, 0xdf, 0x1c, 0xef, - 0xf3, 0xb0, 0xa2, 0x17, 0xed, 0x59, 0x51, 0x19, 0xd6, 0xf8, 0xa1, 0xaf, 0x19, 0x30, 0xcb, 0x47, - 0xa2, 0x94, 0x60, 0x32, 0x63, 0x09, 0x16, 0x69, 0xd3, 0x6e, 0xe8, 0x2c, 0x70, 0x9c, 0xa3, 0xf9, - 0x9f, 0xe2, 0xfb, 0x49, 0x3d, 0xa4, 0xda, 0x75, 0xeb, 0x18, 0x7d, 0x11, 0x9e, 0x0e, 0xba, 0x8d, - 0x06, 0x09, 0x82, 0xbd, 0xae, 0x83, 0xbb, 0xee, 0x4d, 0x3b, 0x08, 0x3d, 0xff, 0x78, 0xcb, 0x6e, - 0xdb, 0x21, 0x1b, 0x71, 0x85, 0xea, 0xd5, 0x93, 0x5e, 0xf9, 0xe9, 0xfa, 0x20, 0x24, 0x3c, 0xb8, - 0x3e, 0xb2, 0xe0, 0x99, 0xae, 0x3b, 0x98, 0x3c, 0xd7, 0xde, 0xca, 0x27, 0xbd, 0xf2, 0x33, 0xf7, - 0x06, 0xa3, 0xe1, 0xd3, 0x68, 0x98, 0xff, 0xdd, 0x80, 0x05, 0xf9, 0x5d, 0x3b, 0xa4, 0xdd, 0x71, - 0xe8, 0xea, 0x72, 0xf1, 0x8a, 0x48, 0x18, 0x53, 0x44, 0x70, 0x36, 0xdb, 0x89, 0x94, 0x7f, 0x90, - 0x36, 0x62, 0xfe, 0x37, 0x03, 0x96, 0x92, 0xc8, 0x4f, 0x60, 0xf3, 0x0c, 0xe2, 0x9b, 0xe7, 0xed, - 0x6c, 0xbf, 0x76, 0xc0, 0x0e, 0xfa, 0xc1, 0x44, 0xff, 0xb7, 0xfe, 0xff, 0xbe, 0x8d, 0x46, 0xbb, - 0x62, 0xfe, 0xc7, 0xb9, 0x2b, 0x4e, 0x7c, 0xac, 0x76, 0xc5, 0x7f, 0x36, 0x01, 0x33, 0x15, 0x37, - 0xb4, 0x2b, 0x7b, 0x7b, 0xb6, 0x6b, 0x87, 0xc7, 0xe8, 0x9b, 0x39, 0x58, 0xeb, 0xf8, 0x64, 0x8f, - 0xf8, 0x3e, 0x69, 0x6e, 0x74, 0x7d, 0xdb, 0x6d, 0xd5, 0x1b, 0xfb, 0xa4, 0xd9, 0x75, 0x6c, 0xb7, - 0xb5, 0xd9, 0x72, 0x3d, 0x55, 0x7c, 0xfd, 0x11, 0x69, 0x74, 0xd9, 0x27, 0xf1, 0x49, 0xd1, 0x1e, - 0xef, 0x93, 0x6a, 0xa3, 0x31, 0xad, 0x7e, 0xf6, 0xa4, 0x57, 0x5e, 0x1b, 0xb1, 0x12, 0x1e, 0xf5, - 0xd3, 0xd0, 0x37, 0x72, 0xb0, 0xea, 0x93, 0x5f, 0xe8, 0xda, 0xc3, 0xb7, 0x06, 0x5f, 0xb5, 0x9c, - 0x31, 0xb7, 0x9f, 0x91, 0x78, 0x56, 0x5f, 0x3e, 0xe9, 0x95, 0x47, 0xac, 0x83, 0x47, 0xfc, 0x2e, - 0xb3, 0x06, 0xd3, 0x95, 0x8e, 0x1d, 0xd8, 0x8f, 0xe8, 0x59, 0x96, 0x0c, 0x71, 0x56, 0x2a, 0x43, - 0xc1, 0xef, 0x3a, 0x84, 0xcf, 0xed, 0x52, 0xb5, 0x44, 0x57, 0x21, 0x4c, 0x0b, 0x30, 0x2f, 0x37, - 0x7f, 0x89, 0xae, 0xb8, 0x8c, 0x64, 0xe2, 0x94, 0xfc, 0x10, 0x0a, 0x3e, 0x65, 0x22, 0x46, 0xd6, - 0xb8, 0x07, 0x8a, 0x48, 0x6a, 0x21, 0x04, 0xfd, 0x89, 0x39, 0x0b, 0xf3, 0xb7, 0x73, 0x70, 0xb9, - 0xd2, 0xe9, 0x6c, 0x93, 0x60, 0x3f, 0x21, 0xc5, 0xaf, 0x18, 0x30, 0x77, 0x68, 0xfb, 0x61, 0xd7, - 0x72, 0xa4, 0x6d, 0x83, 0xcb, 0x53, 0x1f, 0x57, 0x1e, 0xc6, 0xed, 0x7e, 0x8c, 0x74, 0x15, 0x9d, - 0xf4, 0xca, 0x73, 0xf1, 0x32, 0x9c, 0x60, 0x8f, 0xfe, 0x81, 0x01, 0x0b, 0xa2, 0xe8, 0xb6, 0xd7, - 0x24, 0xba, 0x41, 0xec, 0x5e, 0x96, 0x32, 0x29, 0xe2, 0xdc, 0x72, 0x92, 0x2c, 0xc5, 0x7d, 0x42, - 0x98, 0xff, 0x33, 0x07, 0x57, 0x06, 0xd0, 0x40, 0xff, 0xd4, 0x80, 0x25, 0x6e, 0x45, 0xd3, 0x40, - 0x98, 0xec, 0x89, 0xd6, 0xfc, 0xd9, 0xac, 0x25, 0xc7, 0x74, 0x8a, 0x13, 0xb7, 0x41, 0xaa, 0xcb, - 0x74, 0x35, 0x5c, 0x4f, 0x61, 0x8d, 0x53, 0x05, 0x62, 0x92, 0x72, 0xbb, 0x5a, 0x42, 0xd2, 0xdc, - 0x13, 0x91, 0xb4, 0x9e, 0xc2, 0x1a, 0xa7, 0x0a, 0x64, 0xfe, 0x2d, 0x78, 0xe6, 0x14, 0x72, 0x67, - 0x4f, 0x4e, 0xf3, 0x6d, 0x35, 0xea, 0xe3, 0x63, 0x6e, 0x88, 0x79, 0x6d, 0xc2, 0x24, 0x9b, 0x3a, - 0x72, 0x62, 0x03, 0xdd, 0xfe, 0xd8, 0x9c, 0x0a, 0xb0, 0x80, 0x98, 0xbf, 0x6d, 0x40, 0x71, 0x04, - 0xb3, 0x4a, 0x39, 0x6e, 0x56, 0x29, 0xf5, 0x99, 0x54, 0xc2, 0x7e, 0x93, 0xca, 0x9b, 0xe3, 0xf5, - 0xc6, 0x30, 0xa6, 0x94, 0x3f, 0x33, 0x60, 0xb1, 0xcf, 0xf4, 0x82, 0xf6, 0x61, 0xa9, 0xe3, 0x35, - 0xa5, 0xda, 0x74, 0xd3, 0x0a, 0xf6, 0x19, 0x4c, 0x7c, 0xde, 0x2b, 0xb4, 0x27, 0x6b, 0x29, 0xf0, - 0xc7, 0xbd, 0xf2, 0xb2, 0x22, 0x92, 0x40, 0xc0, 0xa9, 0x14, 0x51, 0x07, 0x8a, 0x7b, 0x36, 0x71, - 0x9a, 0xd1, 0x10, 0x1c, 0x53, 0x41, 0xba, 0x21, 0xa8, 0x71, 0xab, 0xa3, 0xfc, 0x87, 0x15, 0x17, - 0xf3, 0xab, 0x30, 0x17, 0xb7, 0x41, 0x0f, 0xd1, 0x79, 0x57, 0x21, 0x6f, 0xf9, 0xae, 0xe8, 0xba, - 0x69, 0x81, 0x90, 0xaf, 0xe0, 0xdb, 0x98, 0x96, 0xa3, 0x97, 0xa0, 0xb8, 0xd7, 0x75, 0x1c, 0x5a, - 0x41, 0xd8, 0x86, 0x95, 0x3a, 0x7c, 0x43, 0x94, 0x63, 0x85, 0x61, 0xfe, 0xc5, 0x04, 0xcc, 0x57, - 0x9d, 0x2e, 0x79, 0xd3, 0x27, 0x44, 0x1e, 0xd2, 0x2b, 0x30, 0xdf, 0xf1, 0xc9, 0xa1, 0x4d, 0x8e, - 0xea, 0xc4, 0x21, 0x8d, 0xd0, 0xf3, 0x85, 0x34, 0x57, 0x04, 0xa1, 0xf9, 0x5a, 0x1c, 0x8c, 0x93, - 0xf8, 0xe8, 0x0d, 0x98, 0xb3, 0x1a, 0xa1, 0x7d, 0x48, 0x14, 0x05, 0x2e, 0xee, 0x27, 0x04, 0x85, - 0xb9, 0x4a, 0x0c, 0x8a, 0x13, 0xd8, 0xe8, 0x4b, 0xb0, 0x1c, 0x34, 0x2c, 0x87, 0xdc, 0xeb, 0x08, - 0x56, 0xeb, 0xfb, 0xa4, 0x71, 0x50, 0xf3, 0x6c, 0x37, 0x14, 0x26, 0x99, 0xe7, 0x04, 0xa5, 0xe5, - 0xfa, 0x00, 0x3c, 0x3c, 0x90, 0x02, 0xfa, 0x37, 0x06, 0x5c, 0xed, 0xf8, 0xa4, 0xe6, 0x7b, 0x6d, - 0x8f, 0xee, 0xb5, 0x7d, 0x76, 0x0a, 0x71, 0x5e, 0xbf, 0x3f, 0xa6, 0x52, 0xc1, 0x4b, 0xfa, 0xed, - 0xa4, 0x9f, 0x3c, 0xe9, 0x95, 0xaf, 0xd6, 0x4e, 0x13, 0x00, 0x9f, 0x2e, 0x1f, 0xfa, 0x77, 0x06, - 0x5c, 0xeb, 0x78, 0x41, 0x78, 0xca, 0x27, 0x14, 0x2e, 0xf4, 0x13, 0xcc, 0x93, 0x5e, 0xf9, 0x5a, - 0xed, 0x54, 0x09, 0xf0, 0x19, 0x12, 0x9a, 0x27, 0xd3, 0xb0, 0xa8, 0x8d, 0x3d, 0x71, 0x88, 0x7f, - 0x1d, 0x66, 0xe5, 0x60, 0x88, 0x94, 0x80, 0x52, 0x64, 0x74, 0xa9, 0xe8, 0x40, 0x1c, 0xc7, 0xa5, - 0xe3, 0x4e, 0x0d, 0x45, 0x5e, 0x3b, 0x31, 0xee, 0x6a, 0x31, 0x28, 0x4e, 0x60, 0xa3, 0x4d, 0xb8, - 0x24, 0x4a, 0x30, 0xe9, 0x38, 0x76, 0xc3, 0x5a, 0xf7, 0xba, 0x62, 0xc8, 0x15, 0xaa, 0x57, 0x4e, - 0x7a, 0xe5, 0x4b, 0xb5, 0x7e, 0x30, 0x4e, 0xab, 0x83, 0xb6, 0x60, 0xc9, 0xea, 0x86, 0x9e, 0xfa, - 0xfe, 0xeb, 0x2e, 0xdd, 0x57, 0x9a, 0x6c, 0x68, 0x15, 0xf9, 0x06, 0x54, 0x49, 0x81, 0xe3, 0xd4, - 0x5a, 0xa8, 0x96, 0xa0, 0x56, 0x27, 0x0d, 0xcf, 0x6d, 0xf2, 0x5e, 0x2e, 0x44, 0x47, 0x91, 0x4a, - 0x0a, 0x0e, 0x4e, 0xad, 0x89, 0x1c, 0x98, 0x6b, 0x5b, 0x8f, 0xee, 0xb9, 0xd6, 0xa1, 0x65, 0x3b, - 0x94, 0x89, 0x30, 0xe4, 0x0c, 0xb6, 0x2e, 0x74, 0x43, 0xdb, 0x59, 0xe5, 0x77, 0x9a, 0xab, 0x9b, - 0x6e, 0x78, 0xc7, 0xaf, 0x87, 0x54, 0x65, 0xe5, 0xaa, 0xd4, 0x76, 0x8c, 0x16, 0x4e, 0xd0, 0x46, - 0x77, 0xe0, 0x32, 0x9b, 0x8e, 0x1b, 0xde, 0x91, 0xbb, 0x41, 0x1c, 0xeb, 0x58, 0x7e, 0xc0, 0x14, - 0xfb, 0x80, 0xa7, 0x4f, 0x7a, 0xe5, 0xcb, 0xf5, 0x34, 0x04, 0x9c, 0x5e, 0x0f, 0x59, 0xf0, 0x4c, - 0x1c, 0x80, 0xc9, 0xa1, 0x1d, 0xd8, 0x9e, 0xcb, 0xcd, 0x31, 0xc5, 0xc8, 0x1c, 0x53, 0x1f, 0x8c, - 0x86, 0x4f, 0xa3, 0x81, 0xfe, 0x91, 0x01, 0x4b, 0x69, 0xd3, 0x70, 0xb9, 0x94, 0xc5, 0x8d, 0x4d, - 0x62, 0x6a, 0xf1, 0x11, 0x91, 0xba, 0x28, 0xa4, 0x0a, 0x81, 0xde, 0x33, 0x60, 0xc6, 0xd2, 0x8e, - 0x92, 0xcb, 0xc0, 0xa4, 0xba, 0x35, 0xae, 0x41, 0x23, 0xa2, 0x58, 0x5d, 0x38, 0xe9, 0x95, 0x63, - 0xc7, 0x55, 0x1c, 0xe3, 0x88, 0xfe, 0x89, 0x01, 0x97, 0x53, 0xe7, 0xf8, 0xf2, 0xf4, 0x45, 0xb4, - 0x10, 0x1b, 0x24, 0xe9, 0x6b, 0x4e, 0xba, 0x18, 0xe8, 0x43, 0x43, 0x6d, 0x65, 0xdb, 0xd2, 0xa4, - 0x34, 0xc3, 0x44, 0xbb, 0x3b, 0xe6, 0xe9, 0x39, 0x52, 0x1f, 0x24, 0xe1, 0xea, 0x25, 0x6d, 0x67, - 0x94, 0x85, 0x38, 0xc9, 0x1e, 0x7d, 0xcb, 0x90, 0x5b, 0xa3, 0x92, 0x68, 0xf6, 0xa2, 0x24, 0x42, - 0xd1, 0x4e, 0xab, 0x04, 0x4a, 0x30, 0x47, 0x3f, 0x07, 0x2b, 0xd6, 0xae, 0xe7, 0x87, 0xa9, 0x93, - 0x6f, 0x79, 0x8e, 0x4d, 0xa3, 0x6b, 0x27, 0xbd, 0xf2, 0x4a, 0x65, 0x20, 0x16, 0x3e, 0x85, 0x82, - 0xf9, 0x1b, 0x05, 0x98, 0xe1, 0x47, 0x02, 0xb1, 0x75, 0xfd, 0x96, 0x01, 0xcf, 0x36, 0xba, 0xbe, - 0x4f, 0xdc, 0xb0, 0x1e, 0x92, 0x4e, 0xff, 0xc6, 0x65, 0x5c, 0xe8, 0xc6, 0xf5, 0xdc, 0x49, 0xaf, - 0xfc, 0xec, 0xfa, 0x29, 0xfc, 0xf1, 0xa9, 0xd2, 0xa1, 0xff, 0x68, 0x80, 0x29, 0x10, 0xaa, 0x56, - 0xe3, 0xa0, 0xe5, 0x7b, 0x5d, 0xb7, 0xd9, 0xff, 0x11, 0xb9, 0x0b, 0xfd, 0x88, 0x17, 0x4e, 0x7a, - 0x65, 0x73, 0xfd, 0x4c, 0x29, 0xf0, 0x10, 0x92, 0xa2, 0x37, 0x61, 0x51, 0x60, 0x5d, 0x7f, 0xd4, - 0x21, 0xbe, 0x4d, 0x95, 0x6f, 0xa1, 0x38, 0x46, 0x7e, 0x1a, 0x49, 0x04, 0xdc, 0x5f, 0x07, 0x05, - 0x30, 0x75, 0x44, 0xec, 0xd6, 0x7e, 0x28, 0xd5, 0xa7, 0x31, 0x9d, 0x33, 0x84, 0x79, 0xe0, 0x01, - 0xa7, 0x59, 0x9d, 0x3e, 0xe9, 0x95, 0xa7, 0xc4, 0x1f, 0x2c, 0x39, 0xa1, 0xdb, 0x30, 0xc7, 0x0f, - 0x6c, 0x35, 0xdb, 0x6d, 0xd5, 0x3c, 0x97, 0xbb, 0x34, 0x94, 0xaa, 0x2f, 0xc8, 0x0d, 0xbf, 0x1e, - 0x83, 0x3e, 0xee, 0x95, 0x67, 0xe4, 0xef, 0x9d, 0xe3, 0x0e, 0xc1, 0x89, 0xda, 0xe6, 0xef, 0x4d, - 0x02, 0xc8, 0xe1, 0x4a, 0x3a, 0xe8, 0xaf, 0x43, 0x29, 0x20, 0x21, 0xe7, 0x2a, 0x6e, 0x10, 0xf8, - 0xc5, 0x8c, 0x2c, 0xc4, 0x11, 0x1c, 0x1d, 0x40, 0xa1, 0x63, 0x75, 0x03, 0x22, 0x3a, 0xff, 0x56, - 0x26, 0x9d, 0x5f, 0xa3, 0x14, 0xf9, 0x09, 0x8d, 0xfd, 0xc4, 0x9c, 0x07, 0xfa, 0xba, 0x01, 0x40, - 0xe2, 0x1d, 0x36, 0xb6, 0xa5, 0x44, 0xb0, 0x8c, 0xfa, 0x94, 0xb6, 0x41, 0x75, 0xee, 0xa4, 0x57, - 0x06, 0xad, 0xeb, 0x35, 0xb6, 0xe8, 0x08, 0x8a, 0x96, 0x5c, 0xf3, 0x27, 0x2e, 0x62, 0xcd, 0x67, - 0x07, 0x27, 0x35, 0x68, 0x15, 0x33, 0xf4, 0x0d, 0x03, 0xe6, 0x02, 0x12, 0x8a, 0xae, 0xa2, 0x2b, - 0x8f, 0x50, 0x78, 0xc7, 0x1c, 0x74, 0xf5, 0x18, 0x4d, 0xbe, 0x82, 0xc6, 0xcb, 0x70, 0x82, 0xaf, - 0x14, 0xe5, 0x26, 0xb1, 0x9a, 0xc4, 0x67, 0xe7, 0x72, 0xa1, 0x49, 0x8d, 0x2f, 0x8a, 0x46, 0x53, - 0x89, 0xa2, 0x95, 0xe1, 0x04, 0x5f, 0x29, 0xca, 0xb6, 0xed, 0xfb, 0x9e, 0x10, 0xa5, 0x98, 0x91, - 0x28, 0x1a, 0x4d, 0x25, 0x8a, 0x56, 0x86, 0x13, 0x7c, 0xcd, 0xef, 0xcc, 0xc2, 0x9c, 0x9c, 0x48, - 0x91, 0x66, 0xcf, 0xcd, 0x40, 0x03, 0x34, 0xfb, 0x75, 0x1d, 0x88, 0xe3, 0xb8, 0xb4, 0x32, 0x9f, - 0xaa, 0x71, 0xc5, 0x5e, 0x55, 0xae, 0xeb, 0x40, 0x1c, 0xc7, 0x45, 0x6d, 0x28, 0x04, 0x21, 0xe9, - 0xc8, 0xcb, 0xe0, 0x31, 0xef, 0x2a, 0xa3, 0xf5, 0x21, 0xba, 0xee, 0xa1, 0xff, 0x02, 0xcc, 0xb9, - 0x30, 0x4b, 0x66, 0x18, 0x33, 0x6e, 0x8a, 0xc9, 0x91, 0xcd, 0xfc, 0x8c, 0xdb, 0x4d, 0x79, 0x6f, - 0xc4, 0xcb, 0x70, 0x82, 0x7d, 0x8a, 0xb2, 0x5f, 0xb8, 0x40, 0x65, 0xff, 0x0b, 0x50, 0x6c, 0x5b, - 0x8f, 0xea, 0x5d, 0xbf, 0x75, 0xfe, 0x43, 0x85, 0xf0, 0xd3, 0xe2, 0x54, 0xb0, 0xa2, 0x87, 0xbe, - 0x66, 0x68, 0x4b, 0xce, 0x14, 0x23, 0xfe, 0x20, 0xdb, 0x25, 0x47, 0xed, 0x95, 0x03, 0x17, 0x9f, - 0x3e, 0xd5, 0xbb, 0xf8, 0xc4, 0x55, 0x6f, 0xaa, 0x46, 0xf2, 0x09, 0xa2, 0xd4, 0xc8, 0xd2, 0x85, - 0xaa, 0x91, 0xeb, 0x31, 0x66, 0x38, 0xc1, 0x9c, 0xc9, 0xc3, 0xe7, 0x9c, 0x92, 0x07, 0x2e, 0x54, - 0x9e, 0x7a, 0x8c, 0x19, 0x4e, 0x30, 0x1f, 0x7c, 0xde, 0x9c, 0xbe, 0x98, 0xf3, 0xe6, 0x4c, 0x06, - 0xe7, 0xcd, 0xd3, 0x55, 0xf1, 0xd9, 0x71, 0x55, 0x71, 0x74, 0x0b, 0x50, 0xf3, 0xd8, 0xb5, 0xda, - 0x76, 0x43, 0x2c, 0x96, 0x6c, 0xdb, 0x9c, 0x63, 0xf6, 0x88, 0x15, 0xb1, 0x90, 0xa1, 0x8d, 0x3e, - 0x0c, 0x9c, 0x52, 0x0b, 0x85, 0x50, 0xec, 0x48, 0x8d, 0x6b, 0x3e, 0x8b, 0xd1, 0x2f, 0x35, 0x30, - 0xee, 0x2f, 0x40, 0x27, 0x9e, 0x2c, 0xc1, 0x8a, 0x13, 0xda, 0x82, 0xa5, 0xb6, 0xed, 0xd6, 0xbc, - 0x66, 0x50, 0x23, 0xbe, 0xb0, 0xb6, 0xd4, 0x49, 0xb8, 0xbc, 0xc0, 0xda, 0x86, 0x9d, 0xa0, 0xb7, - 0x53, 0xe0, 0x38, 0xb5, 0x96, 0xf9, 0xbf, 0x0d, 0x58, 0x58, 0x77, 0xbc, 0x6e, 0xf3, 0x81, 0x15, - 0x36, 0xf6, 0xf9, 0x55, 0x39, 0x7a, 0x03, 0x8a, 0xb6, 0x1b, 0x12, 0xff, 0xd0, 0x72, 0xc4, 0xfe, - 0x64, 0x4a, 0xf3, 0xe9, 0xa6, 0x28, 0x7f, 0xdc, 0x2b, 0xcf, 0x6d, 0x74, 0x7d, 0xe6, 0x83, 0xca, - 0x57, 0x2b, 0xac, 0xea, 0xa0, 0xef, 0x18, 0xb0, 0xc8, 0x2f, 0xdb, 0x37, 0xac, 0xd0, 0xba, 0xdb, - 0x25, 0xbe, 0x4d, 0xe4, 0x75, 0xfb, 0x98, 0x0b, 0x55, 0x52, 0x56, 0xc9, 0xe0, 0x38, 0x52, 0xd4, - 0xb7, 0x93, 0x9c, 0x71, 0xbf, 0x30, 0xe6, 0xaf, 0xe6, 0xe1, 0xe9, 0x81, 0xb4, 0xd0, 0x0a, 0xe4, - 0xec, 0xa6, 0xf8, 0x74, 0x10, 0x74, 0x73, 0x9b, 0x4d, 0x9c, 0xb3, 0x9b, 0x68, 0x95, 0xe9, 0x9c, - 0x3e, 0x09, 0x02, 0x79, 0xf3, 0x5a, 0x52, 0xea, 0xa1, 0x28, 0xc5, 0x1a, 0x06, 0x2a, 0x43, 0xc1, - 0xb1, 0x76, 0x89, 0x23, 0xce, 0x13, 0x4c, 0x8b, 0xdd, 0xa2, 0x05, 0x98, 0x97, 0xa3, 0x5f, 0x32, - 0x00, 0xb8, 0x80, 0xf4, 0x34, 0x22, 0x76, 0x49, 0x9c, 0x6d, 0x33, 0x51, 0xca, 0x5c, 0xca, 0xe8, - 0x3f, 0xd6, 0xb8, 0xa2, 0x1d, 0x98, 0xa4, 0x0a, 0xad, 0xd7, 0x3c, 0xf7, 0xa6, 0xc8, 0xae, 0x64, - 0x6a, 0x8c, 0x06, 0x16, 0xb4, 0x68, 0x5b, 0xf9, 0x24, 0xec, 0xfa, 0x2e, 0x6d, 0x5a, 0xb6, 0x0d, - 0x16, 0xb9, 0x14, 0x58, 0x95, 0x62, 0x0d, 0xc3, 0xfc, 0xd7, 0x39, 0x58, 0x4a, 0x13, 0x9d, 0xee, - 0x36, 0x93, 0x5c, 0x5a, 0x71, 0x34, 0xfe, 0x7c, 0xf6, 0xed, 0x23, 0xfc, 0x46, 0x94, 0x77, 0x85, - 0xf0, 0x6c, 0x13, 0x7c, 0xd1, 0xe7, 0x55, 0x0b, 0xe5, 0xce, 0xd9, 0x42, 0x8a, 0x72, 0xa2, 0x95, - 0x9e, 0x83, 0x89, 0x80, 0xf6, 0x7c, 0x3e, 0x7e, 0xdd, 0xc1, 0xfa, 0x88, 0x41, 0x28, 0x46, 0xd7, - 0xb5, 0x43, 0xe1, 0x18, 0xae, 0x30, 0xee, 0xb9, 0x76, 0x88, 0x19, 0xc4, 0xfc, 0x76, 0x0e, 0x56, - 0x06, 0x7f, 0x14, 0xfa, 0xb6, 0x01, 0xd0, 0xa4, 0xc7, 0x15, 0x3a, 0x24, 0xa5, 0x9f, 0x8d, 0x75, - 0x51, 0x6d, 0xb8, 0x21, 0x39, 0x45, 0x4e, 0x57, 0xaa, 0x28, 0xc0, 0x9a, 0x20, 0xe8, 0x65, 0x39, - 0xf4, 0xd9, 0x55, 0x0d, 0x9f, 0x4c, 0xaa, 0xce, 0xb6, 0x82, 0x60, 0x0d, 0x8b, 0x9e, 0x47, 0x5d, - 0xab, 0x4d, 0x82, 0x8e, 0xa5, 0x3c, 0xff, 0xd9, 0x79, 0xf4, 0xb6, 0x2c, 0xc4, 0x11, 0xdc, 0x74, - 0xe0, 0xf9, 0x21, 0xe4, 0xcc, 0xc8, 0x0b, 0xdb, 0xfc, 0x5f, 0x06, 0x5c, 0x59, 0x77, 0xba, 0x41, - 0x48, 0xfc, 0xbf, 0x34, 0x3e, 0x6c, 0xff, 0xc7, 0x80, 0x67, 0x06, 0x7c, 0xf3, 0x13, 0x70, 0x65, - 0x7b, 0x27, 0xee, 0xca, 0x76, 0x6f, 0xdc, 0x21, 0x9d, 0xfa, 0x1d, 0x03, 0x3c, 0xda, 0x42, 0x98, - 0xa5, 0xab, 0x56, 0xd3, 0x6b, 0x65, 0xb4, 0x6f, 0x3e, 0x0f, 0x85, 0x5f, 0xa0, 0xfb, 0x4f, 0x72, - 0x8c, 0xb1, 0x4d, 0x09, 0x73, 0x98, 0xf9, 0x39, 0x10, 0x7e, 0x5f, 0x89, 0xc9, 0x63, 0x0c, 0x33, - 0x79, 0xcc, 0xff, 0x9c, 0x03, 0xcd, 0x8e, 0xf1, 0x04, 0x06, 0xa5, 0x1b, 0x1b, 0x94, 0x63, 0x9e, - 0xc1, 0x35, 0xab, 0xcc, 0xa0, 0x00, 0x8f, 0xc3, 0x44, 0x80, 0xc7, 0xed, 0xcc, 0x38, 0x9e, 0x1e, - 0xdf, 0xf1, 0x43, 0x03, 0x9e, 0x89, 0x90, 0xfb, 0x4d, 0x8c, 0x67, 0xaf, 0x30, 0xaf, 0xc2, 0xb4, - 0x15, 0x55, 0x13, 0x63, 0x40, 0xc5, 0x34, 0x69, 0x14, 0xb1, 0x8e, 0x17, 0xb9, 0x93, 0xe7, 0xcf, - 0xe9, 0x4e, 0x3e, 0x71, 0xba, 0x3b, 0xb9, 0xf9, 0xe7, 0x39, 0xb8, 0xda, 0xff, 0x65, 0x72, 0x6e, - 0x0c, 0x77, 0x5f, 0xff, 0x1a, 0xcc, 0x84, 0xa2, 0x82, 0xb6, 0xd2, 0xab, 0x88, 0xbc, 0x1d, 0x0d, - 0x86, 0x63, 0x98, 0xb4, 0x66, 0x83, 0xcf, 0xca, 0x7a, 0xc3, 0xeb, 0xc8, 0x60, 0x04, 0x55, 0x73, - 0x5d, 0x83, 0xe1, 0x18, 0xa6, 0x72, 0xf3, 0x9c, 0xb8, 0x70, 0x37, 0xcf, 0x3a, 0x5c, 0x96, 0x8e, - 0x6d, 0x37, 0x3c, 0x7f, 0xdd, 0x6b, 0x77, 0x1c, 0x22, 0xc2, 0x11, 0xa8, 0xb0, 0x57, 0x45, 0x95, - 0xcb, 0x38, 0x0d, 0x09, 0xa7, 0xd7, 0x35, 0x7f, 0x98, 0x87, 0x4b, 0x51, 0xb3, 0xaf, 0x7b, 0x6e, - 0xd3, 0x66, 0xee, 0x81, 0xaf, 0xc3, 0x44, 0x78, 0xdc, 0x91, 0x8d, 0xfd, 0xd7, 0xa4, 0x38, 0x3b, - 0xc7, 0x1d, 0xda, 0xdb, 0x57, 0x52, 0xaa, 0x30, 0x23, 0x2f, 0xab, 0x84, 0xb6, 0xd4, 0xec, 0xe0, - 0x3d, 0xf0, 0x4a, 0x7c, 0x34, 0x3f, 0xee, 0x95, 0x53, 0x02, 0x52, 0x57, 0x15, 0xa5, 0xf8, 0x98, - 0x47, 0x0f, 0x61, 0xce, 0xb1, 0x82, 0xf0, 0x5e, 0xa7, 0x69, 0x85, 0x64, 0xc7, 0x16, 0xce, 0x16, - 0xa3, 0xf9, 0xf8, 0xab, 0x5b, 0xe9, 0xad, 0x18, 0x25, 0x9c, 0xa0, 0x8c, 0x0e, 0x01, 0xd1, 0x92, - 0x1d, 0xdf, 0x72, 0x03, 0xfe, 0x55, 0x94, 0xdf, 0xe8, 0x31, 0x05, 0xea, 0x90, 0xb7, 0xd5, 0x47, - 0x0d, 0xa7, 0x70, 0x40, 0x2f, 0xc0, 0xa4, 0x4f, 0xac, 0x40, 0x74, 0x66, 0x29, 0x9a, 0xff, 0x98, - 0x95, 0x62, 0x01, 0xd5, 0x27, 0xd4, 0xe4, 0x19, 0x13, 0xea, 0x4f, 0x0c, 0x98, 0x8b, 0xba, 0xe9, - 0x09, 0x6c, 0x92, 0xed, 0xf8, 0x26, 0x79, 0x33, 0xab, 0x25, 0x71, 0xc0, 0xbe, 0xf8, 0xef, 0x27, - 0xf5, 0xef, 0x63, 0x3e, 0xde, 0x5f, 0x81, 0x92, 0x9c, 0xd5, 0x52, 0xfb, 0x1c, 0xf3, 0xac, 0x1c, - 0xd3, 0x4b, 0xb4, 0xd8, 0x24, 0xc1, 0x04, 0x47, 0xfc, 0xe8, 0xb6, 0xdc, 0x14, 0x5b, 0xae, 0x18, - 0xf6, 0x6a, 0x5b, 0x96, 0x5b, 0x71, 0xda, 0xb6, 0x2c, 0xeb, 0xa0, 0x7b, 0x70, 0xa5, 0xe3, 0x7b, - 0x2c, 0x5e, 0x75, 0x83, 0x58, 0x4d, 0xc7, 0x76, 0x89, 0x34, 0x48, 0x70, 0xa7, 0x88, 0x67, 0x4e, - 0x7a, 0xe5, 0x2b, 0xb5, 0x74, 0x14, 0x3c, 0xa8, 0x6e, 0x3c, 0xc6, 0x6a, 0x62, 0x88, 0x18, 0xab, - 0xbf, 0xab, 0xcc, 0x7e, 0x24, 0x10, 0x91, 0x4e, 0x5f, 0xcc, 0xaa, 0x2b, 0x53, 0x96, 0xf5, 0x68, - 0x48, 0x55, 0x04, 0x53, 0xac, 0xd8, 0x0f, 0xb6, 0x2d, 0x4d, 0x9e, 0xd3, 0xb6, 0x14, 0xb9, 0xca, - 0x4f, 0xfd, 0x38, 0x5d, 0xe5, 0x8b, 0x1f, 0x2b, 0x57, 0xf9, 0xf7, 0x0b, 0xb0, 0x90, 0xd4, 0x40, - 0x2e, 0x3e, 0x7e, 0xec, 0xef, 0x1b, 0xb0, 0x20, 0x67, 0x0f, 0xe7, 0x49, 0xe4, 0xad, 0xc1, 0x56, - 0x46, 0x93, 0x96, 0xeb, 0x52, 0x2a, 0xc2, 0x79, 0x27, 0xc1, 0x0d, 0xf7, 0xf1, 0x47, 0x6f, 0xc3, - 0xb4, 0x32, 0xae, 0x9f, 0x2b, 0x98, 0x6c, 0x9e, 0x69, 0x51, 0x11, 0x09, 0xac, 0xd3, 0x43, 0xef, - 0x1b, 0x00, 0x0d, 0xb9, 0xcd, 0xc9, 0xd9, 0x75, 0x37, 0xab, 0xd9, 0xa5, 0x36, 0xd0, 0x48, 0x59, - 0x56, 0x45, 0x01, 0xd6, 0x18, 0xa3, 0x5f, 0x65, 0x66, 0x75, 0xa5, 0xdd, 0xd1, 0xf9, 0x94, 0x1f, - 0xdf, 0x0d, 0xf8, 0x14, 0xc5, 0x34, 0x52, 0xa5, 0x34, 0x50, 0x80, 0x63, 0x42, 0x98, 0xaf, 0x83, - 0x72, 0xdc, 0xa4, 0xcb, 0x16, 0x73, 0xdd, 0xac, 0x59, 0xe1, 0xbe, 0x18, 0x82, 0x6a, 0xd9, 0xba, - 0x21, 0x01, 0x38, 0xc2, 0x31, 0xbf, 0x0c, 0x73, 0x6f, 0xfa, 0x56, 0x67, 0xdf, 0x66, 0xe6, 0x6b, - 0x7a, 0x4e, 0xfa, 0x49, 0x98, 0xb2, 0x9a, 0xcd, 0xb4, 0xfc, 0x00, 0x15, 0x5e, 0x8c, 0x25, 0x7c, - 0xb8, 0x23, 0xd1, 0xef, 0x19, 0x80, 0xa2, 0x2b, 0x40, 0xdb, 0x6d, 0x6d, 0xd3, 0xd3, 0x3e, 0x3d, - 0x1f, 0xed, 0xb3, 0xd2, 0xb4, 0xf3, 0xd1, 0x4d, 0x05, 0xc1, 0x1a, 0x16, 0x7a, 0x17, 0xa6, 0xf9, - 0xbf, 0xfb, 0xea, 0xb0, 0x3f, 0x76, 0x30, 0x00, 0xdf, 0x50, 0x98, 0x4c, 0x7c, 0x14, 0xde, 0x8c, - 0x38, 0x60, 0x9d, 0x1d, 0x6d, 0xaa, 0x4d, 0x77, 0xcf, 0xe9, 0x3e, 0x6a, 0xee, 0x46, 0x4d, 0xd5, - 0xf1, 0xbd, 0x3d, 0xdb, 0x21, 0xc9, 0xa6, 0xaa, 0xf1, 0x62, 0x2c, 0xe1, 0xc3, 0x35, 0xd5, 0xef, - 0x18, 0xb0, 0xb4, 0x19, 0x84, 0xb6, 0xb7, 0x41, 0x82, 0x90, 0x6e, 0x2b, 0x74, 0xf1, 0xe9, 0x3a, - 0xc3, 0xf8, 0x60, 0x6f, 0xc0, 0x82, 0xb8, 0x8e, 0xec, 0xee, 0x06, 0x24, 0xd4, 0xf4, 0x78, 0x35, - 0x8f, 0xd7, 0x13, 0x70, 0xdc, 0x57, 0x83, 0x52, 0x11, 0xf7, 0x92, 0x11, 0x95, 0x7c, 0x9c, 0x4a, - 0x3d, 0x01, 0xc7, 0x7d, 0x35, 0xcc, 0x1f, 0xe4, 0xe1, 0x12, 0xfb, 0x8c, 0x44, 0xfc, 0xc4, 0xb7, - 0x06, 0xc5, 0x4f, 0x8c, 0x39, 0x95, 0x19, 0xaf, 0x73, 0x44, 0x4f, 0xfc, 0x3d, 0x03, 0xe6, 0x9b, - 0xf1, 0x96, 0xce, 0xc6, 0x3c, 0x93, 0xd6, 0x87, 0xdc, 0xfb, 0x2a, 0x51, 0x88, 0x93, 0xfc, 0xd1, - 0xaf, 0x19, 0x30, 0x1f, 0x17, 0x53, 0xae, 0xee, 0x17, 0xd0, 0x48, 0xca, 0x5d, 0x3a, 0x5e, 0x1e, - 0xe0, 0xa4, 0x08, 0xe6, 0xef, 0xe7, 0x44, 0x97, 0x5e, 0x44, 0x70, 0x00, 0x3a, 0x82, 0x52, 0xe8, - 0x04, 0xbc, 0x50, 0x7c, 0xed, 0x98, 0x27, 0xc2, 0x9d, 0xad, 0x3a, 0xf7, 0x04, 0x88, 0x94, 0x36, - 0x51, 0x42, 0x95, 0x4f, 0xc9, 0x8b, 0x31, 0x6e, 0x74, 0x04, 0xe3, 0x4c, 0x8e, 0xa2, 0x3b, 0xeb, - 0xb5, 0x24, 0x63, 0x51, 0x42, 0x19, 0x4b, 0x5e, 0xe6, 0xf7, 0x0c, 0x28, 0xdd, 0xf2, 0xe4, 0x3a, - 0xf2, 0x73, 0x19, 0x18, 0x7a, 0x94, 0x3e, 0xa8, 0x6e, 0x1c, 0xa3, 0x23, 0xc6, 0x1b, 0x31, 0x33, - 0xcf, 0xb3, 0x1a, 0xed, 0x55, 0x96, 0xfb, 0x88, 0x92, 0xba, 0xe5, 0xed, 0x0e, 0xb4, 0x22, 0xfe, - 0x7a, 0x01, 0x66, 0xdf, 0xb2, 0x8e, 0x89, 0x1b, 0x5a, 0xa3, 0x6f, 0x12, 0xaf, 0xc2, 0xb4, 0xd5, - 0x61, 0x57, 0x5a, 0x9a, 0x8e, 0x1f, 0x59, 0x4e, 0x22, 0x10, 0xd6, 0xf1, 0xa2, 0x05, 0x8d, 0xa7, - 0x62, 0x49, 0x5b, 0x8a, 0xd6, 0x13, 0x70, 0xdc, 0x57, 0x03, 0xdd, 0x02, 0x24, 0x02, 0x4b, 0x2b, - 0x8d, 0x86, 0xd7, 0x75, 0xf9, 0x92, 0xc6, 0x8d, 0x2a, 0xea, 0xb0, 0xb9, 0xdd, 0x87, 0x81, 0x53, - 0x6a, 0xa1, 0x2f, 0xc1, 0x72, 0x83, 0x51, 0x16, 0x47, 0x0f, 0x9d, 0x22, 0x3f, 0x7e, 0x2a, 0x97, - 0xff, 0xf5, 0x01, 0x78, 0x78, 0x20, 0x05, 0x2a, 0x69, 0x10, 0x7a, 0xbe, 0xd5, 0x22, 0x3a, 0xdd, - 0xc9, 0xb8, 0xa4, 0xf5, 0x3e, 0x0c, 0x9c, 0x52, 0x0b, 0x7d, 0x15, 0x4a, 0xe1, 0xbe, 0x4f, 0x82, - 0x7d, 0xcf, 0x69, 0x0a, 0x17, 0x84, 0x31, 0x2d, 0x6d, 0xa2, 0xf7, 0x77, 0x24, 0x55, 0x6d, 0x78, - 0xcb, 0x22, 0x1c, 0xf1, 0x44, 0x3e, 0x4c, 0x06, 0x0d, 0xaf, 0x43, 0x02, 0xa1, 0xb2, 0xdf, 0xca, - 0x84, 0x3b, 0xb3, 0x1c, 0x69, 0x36, 0x3e, 0xc6, 0x01, 0x0b, 0x4e, 0xe6, 0xef, 0xe6, 0x60, 0x46, - 0x47, 0x1c, 0x62, 0x6d, 0xfa, 0xba, 0x01, 0x33, 0x0d, 0xcf, 0x0d, 0x7d, 0xcf, 0xe1, 0xf6, 0xab, - 0x6c, 0x34, 0x0a, 0x4a, 0x6a, 0x83, 0x84, 0x96, 0xed, 0x68, 0xa6, 0x30, 0x8d, 0x0d, 0x8e, 0x31, - 0x45, 0xdf, 0x34, 0x60, 0x3e, 0xf2, 0x58, 0x8b, 0x0c, 0x69, 0x99, 0x0a, 0xa2, 0x96, 0xfa, 0xeb, - 0x71, 0x4e, 0x38, 0xc9, 0xda, 0xdc, 0x85, 0x85, 0x64, 0x6f, 0xd3, 0xa6, 0xec, 0x58, 0x62, 0xae, - 0xe7, 0xa3, 0xa6, 0xac, 0x59, 0x41, 0x80, 0x19, 0x04, 0xbd, 0x04, 0xc5, 0xb6, 0xe5, 0xb7, 0x6c, - 0xd7, 0x72, 0x58, 0x2b, 0xe6, 0xb5, 0x05, 0x49, 0x94, 0x63, 0x85, 0x61, 0x7e, 0x1a, 0x66, 0xb6, - 0x2d, 0xb7, 0x45, 0x9a, 0x62, 0x1d, 0x3e, 0x3b, 0x3c, 0xed, 0x47, 0x13, 0x30, 0xad, 0x9d, 0xcd, - 0x2e, 0xfe, 0x9c, 0x15, 0xcb, 0x8e, 0x91, 0xcf, 0x30, 0x3b, 0xc6, 0x17, 0x00, 0xf6, 0x6c, 0xd7, - 0x0e, 0xf6, 0xcf, 0x99, 0x77, 0x83, 0x5d, 0xd1, 0xde, 0x50, 0x14, 0xb0, 0x46, 0x2d, 0xba, 0x07, - 0x2b, 0x9c, 0x92, 0x8d, 0xe8, 0x7d, 0x43, 0xdb, 0x6e, 0x26, 0xb3, 0xb8, 0xf7, 0xd7, 0x3a, 0x66, - 0x55, 0x6e, 0x3f, 0xd7, 0xdd, 0xd0, 0x3f, 0x3e, 0x75, 0x57, 0xda, 0x81, 0xa2, 0x4f, 0x82, 0x6e, - 0x9b, 0x9e, 0x18, 0xa7, 0x46, 0x6e, 0x06, 0xe6, 0x81, 0x81, 0x45, 0x7d, 0xac, 0x28, 0xad, 0xbc, - 0x0e, 0xb3, 0x31, 0x11, 0xd0, 0x02, 0xe4, 0x0f, 0xc8, 0x31, 0x1f, 0x27, 0x98, 0xfe, 0x44, 0x4b, - 0xb1, 0xdb, 0x42, 0xd1, 0x2c, 0x3f, 0x93, 0x7b, 0xcd, 0x30, 0x3d, 0x48, 0x35, 0x00, 0x9c, 0xe7, - 0x32, 0x87, 0xf6, 0x85, 0xa3, 0x25, 0xde, 0x50, 0x7d, 0xc1, 0xfd, 0x6c, 0x38, 0xcc, 0xfc, 0xf3, - 0x49, 0x10, 0x57, 0xd9, 0x43, 0x2c, 0x57, 0xfa, 0x0d, 0x56, 0xee, 0x1c, 0x37, 0x58, 0xb7, 0x60, - 0xc6, 0x76, 0xed, 0xd0, 0xb6, 0x1c, 0x66, 0xdc, 0x11, 0xdb, 0xa9, 0x74, 0x44, 0x9e, 0xd9, 0xd4, - 0x60, 0x29, 0x74, 0x62, 0x75, 0xd1, 0x5d, 0x28, 0xb0, 0xfd, 0x46, 0x0c, 0xe0, 0xd1, 0xef, 0xdb, - 0x99, 0xab, 0x05, 0x8f, 0x4e, 0xe2, 0x94, 0xd8, 0xe1, 0x83, 0x67, 0x1e, 0x51, 0xc7, 0x6f, 0x31, - 0x8e, 0xa3, 0xc3, 0x47, 0x02, 0x8e, 0xfb, 0x6a, 0x50, 0x2a, 0x7b, 0x96, 0xed, 0x74, 0x7d, 0x12, - 0x51, 0x99, 0x8c, 0x53, 0xb9, 0x91, 0x80, 0xe3, 0xbe, 0x1a, 0x68, 0x0f, 0x66, 0x44, 0x19, 0xf7, - 0x9e, 0x9a, 0x3a, 0xe7, 0x57, 0x32, 0x2f, 0xb9, 0x1b, 0x1a, 0x25, 0x1c, 0xa3, 0x8b, 0xba, 0xb0, - 0x68, 0xbb, 0x0d, 0xcf, 0x6d, 0x38, 0xdd, 0xc0, 0x3e, 0x24, 0x51, 0x68, 0xd0, 0x79, 0x98, 0x5d, - 0x3e, 0xe9, 0x95, 0x17, 0x37, 0x93, 0xe4, 0x70, 0x3f, 0x07, 0xf4, 0x35, 0x03, 0x2e, 0x37, 0x3c, - 0x37, 0x60, 0xa1, 0xfc, 0x87, 0xe4, 0xba, 0xef, 0x7b, 0x3e, 0xe7, 0x5d, 0x3a, 0x27, 0x6f, 0x66, - 0x53, 0x5c, 0x4f, 0x23, 0x89, 0xd3, 0x39, 0xa1, 0x77, 0xa0, 0xd8, 0xf1, 0xbd, 0x43, 0xbb, 0x49, - 0x7c, 0xe1, 0x89, 0xb7, 0x95, 0x45, 0x6a, 0x91, 0x9a, 0xa0, 0x19, 0x2d, 0x3d, 0xb2, 0x04, 0x2b, - 0x7e, 0xe6, 0xff, 0x9d, 0x86, 0xb9, 0x38, 0x3a, 0xfa, 0x45, 0x80, 0x8e, 0xef, 0xb5, 0x49, 0xb8, - 0x4f, 0x54, 0x88, 0xc7, 0xed, 0x71, 0x33, 0x58, 0x48, 0x7a, 0xd2, 0x7b, 0x85, 0x2e, 0x17, 0x51, - 0x29, 0xd6, 0x38, 0x22, 0x1f, 0xa6, 0x0e, 0xf8, 0xb6, 0x2b, 0xb4, 0x90, 0xb7, 0x32, 0xd1, 0x99, - 0x04, 0x67, 0x16, 0x9b, 0x20, 0x8a, 0xb0, 0x64, 0x84, 0x76, 0x21, 0x7f, 0x44, 0x76, 0xb3, 0x09, - 0x9f, 0x7e, 0x40, 0xc4, 0x69, 0xa6, 0x3a, 0x75, 0xd2, 0x2b, 0xe7, 0x1f, 0x90, 0x5d, 0x4c, 0x89, - 0xd3, 0xef, 0x6a, 0xf2, 0x7b, 0x78, 0xb1, 0x54, 0x8c, 0xf9, 0x5d, 0xb1, 0x4b, 0x7d, 0xfe, 0x5d, - 0xa2, 0x08, 0x4b, 0x46, 0xe8, 0x1d, 0x28, 0x1d, 0x59, 0x87, 0x64, 0xcf, 0xf7, 0xdc, 0x50, 0xb8, - 0x4c, 0x8d, 0xe9, 0xf5, 0xff, 0x40, 0x92, 0x13, 0x7c, 0xd9, 0xf6, 0xae, 0x0a, 0x71, 0xc4, 0x0e, - 0x1d, 0x42, 0xd1, 0x25, 0x47, 0x98, 0x38, 0x76, 0x23, 0x1b, 0x2f, 0xfb, 0xdb, 0x82, 0x9a, 0xe0, - 0xcc, 0xf6, 0x3d, 0x59, 0x86, 0x15, 0x2f, 0xda, 0x97, 0x0f, 0xbd, 0x5d, 0xb1, 0x50, 0x8d, 0xd9, - 0x97, 0xea, 0x64, 0xca, 0xfb, 0xf2, 0x96, 0xb7, 0x8b, 0x29, 0x71, 0x3a, 0x47, 0x1a, 0xca, 0x5f, - 0x47, 0x2c, 0x53, 0xb7, 0xb3, 0xf5, 0x53, 0xe2, 0x73, 0x24, 0x2a, 0xc5, 0x1a, 0x47, 0xda, 0xb6, - 0x2d, 0x61, 0xac, 0x14, 0x0b, 0xd5, 0x98, 0x6d, 0x1b, 0x37, 0x7d, 0xf2, 0xb6, 0x95, 0x65, 0x58, - 0xf1, 0xa2, 0x7c, 0x6d, 0x61, 0xf9, 0xcb, 0x66, 0xa9, 0x8a, 0xdb, 0x11, 0x39, 0x5f, 0x59, 0x86, - 0x15, 0x2f, 0xda, 0xde, 0xc1, 0xc1, 0xf1, 0x91, 0xe5, 0x1c, 0xd8, 0x6e, 0x4b, 0x84, 0x2c, 0x8e, - 0x9b, 0x0a, 0xf5, 0xe0, 0xf8, 0x01, 0xa7, 0xa7, 0xb7, 0x77, 0x54, 0x8a, 0x35, 0x8e, 0xe8, 0x1f, - 0x1b, 0x30, 0xd9, 0x71, 0xba, 0x2d, 0xdb, 0x5d, 0x9e, 0x61, 0x7a, 0xe2, 0xe7, 0xb3, 0x5c, 0xa1, - 0x57, 0x6b, 0x8c, 0x34, 0x57, 0x14, 0x7f, 0x4a, 0xb9, 0xdf, 0xb1, 0xc2, 0x5f, 0xfe, 0xd3, 0xf2, - 0x32, 0x71, 0x1b, 0x5e, 0xd3, 0x76, 0x5b, 0x6b, 0x0f, 0x03, 0xcf, 0x5d, 0xc5, 0xd6, 0x91, 0xd4, - 0xd1, 0x85, 0x4c, 0x2b, 0x3f, 0x0d, 0xd3, 0x1a, 0x89, 0xb3, 0x14, 0xbd, 0x19, 0x5d, 0xd1, 0xfb, - 0xde, 0x24, 0xcc, 0xe8, 0xc9, 0xf1, 0x86, 0xd0, 0xbe, 0xd4, 0x89, 0x23, 0x37, 0xca, 0x89, 0x83, - 0x1e, 0x31, 0xb5, 0xdb, 0x23, 0x69, 0xde, 0xda, 0xcc, 0x4c, 0xe1, 0x8e, 0x8e, 0x98, 0x5a, 0x61, - 0x80, 0x63, 0x4c, 0x47, 0x70, 0x28, 0xa1, 0x6a, 0x2b, 0x57, 0xec, 0x0a, 0x71, 0xb5, 0x35, 0xa6, - 0xaa, 0xbd, 0x0c, 0x10, 0x25, 0x89, 0x13, 0xb7, 0x8a, 0x4a, 0x1f, 0xd6, 0x92, 0xd7, 0x69, 0x58, - 0xe8, 0x05, 0x98, 0xa4, 0xaa, 0x0f, 0x69, 0x8a, 0x88, 0x6a, 0x75, 0x8e, 0xbf, 0xc1, 0x4a, 0xb1, - 0x80, 0xa2, 0xd7, 0xa8, 0x96, 0x1a, 0x29, 0x2c, 0x22, 0x50, 0x7a, 0x29, 0xd2, 0x52, 0x23, 0x18, - 0x8e, 0x61, 0x52, 0xd1, 0x09, 0xd5, 0x2f, 0xd8, 0xda, 0xa0, 0x89, 0xce, 0x94, 0x0e, 0xcc, 0x61, - 0xcc, 0xae, 0x94, 0xd0, 0x47, 0xd8, 0x9c, 0x2e, 0x68, 0x76, 0xa5, 0x04, 0x1c, 0xf7, 0xd5, 0xa0, - 0x1f, 0x23, 0x2e, 0x44, 0xa7, 0xb9, 0xdf, 0xec, 0x80, 0xab, 0xcc, 0x0f, 0xf4, 0xb3, 0x56, 0x86, - 0x73, 0x88, 0x8f, 0xda, 0xe1, 0x0f, 0x5b, 0xe3, 0x1d, 0x8b, 0xbe, 0x0c, 0x73, 0xf1, 0x5d, 0x28, - 0xf3, 0x9b, 0x8f, 0xff, 0x90, 0x87, 0x4b, 0xb7, 0x5b, 0xb6, 0x9b, 0x4c, 0xfc, 0x94, 0x96, 0x80, - 0xd9, 0x18, 0x35, 0x01, 0x73, 0x14, 0x9a, 0x25, 0x32, 0x5c, 0xa7, 0x87, 0x66, 0xc9, 0xf4, 0xd7, - 0x71, 0x5c, 0xf4, 0x27, 0x06, 0x3c, 0x6b, 0x35, 0xf9, 0xb9, 0xc0, 0x72, 0x44, 0x69, 0xc4, 0x54, - 0xce, 0xe8, 0x60, 0xcc, 0x5d, 0xbe, 0xff, 0xe3, 0x57, 0x2b, 0xa7, 0x70, 0xe5, 0x3d, 0xfe, 0x13, - 0xe2, 0x0b, 0x9e, 0x3d, 0x0d, 0x15, 0x9f, 0x2a, 0xfe, 0xca, 0x1d, 0xf8, 0xe4, 0x99, 0x8c, 0x46, - 0x1a, 0x2d, 0x5f, 0x37, 0xa0, 0xc4, 0x0d, 0xd3, 0x98, 0xec, 0xd1, 0xa5, 0xc2, 0xea, 0xd8, 0xf7, - 0x89, 0x1f, 0xc8, 0xcc, 0x70, 0xda, 0xd1, 0xb9, 0x52, 0xdb, 0x14, 0x10, 0xac, 0x61, 0xd1, 0xc5, - 0xf8, 0xc0, 0x76, 0x9b, 0xa2, 0x9b, 0xd4, 0x62, 0xfc, 0x96, 0xed, 0x36, 0x31, 0x83, 0xa8, 0xe5, - 0x3a, 0x3f, 0xd0, 0x60, 0xf4, 0x5d, 0x03, 0xe6, 0x58, 0x3c, 0x6a, 0x74, 0xa8, 0x7b, 0x55, 0x79, - 0x0b, 0x71, 0x31, 0xae, 0xc6, 0xbd, 0x85, 0x1e, 0xf7, 0xca, 0xd3, 0x3c, 0x82, 0x35, 0xee, 0x3c, - 0xf4, 0x45, 0x61, 0x09, 0x62, 0x3e, 0x4d, 0xb9, 0x91, 0x0d, 0x15, 0xca, 0x52, 0x5a, 0x97, 0x44, - 0x70, 0x44, 0xcf, 0x7c, 0x17, 0x66, 0xf4, 0xc0, 0x12, 0xf4, 0x2a, 0x4c, 0x77, 0x6c, 0xb7, 0x15, - 0x0f, 0x40, 0x54, 0xd6, 0xf2, 0x5a, 0x04, 0xc2, 0x3a, 0x1e, 0xab, 0xe6, 0x45, 0xd5, 0x12, 0x46, - 0xf6, 0x9a, 0xa7, 0x57, 0x8b, 0xfe, 0x98, 0xff, 0x32, 0x0f, 0x97, 0x52, 0x02, 0x98, 0xd0, 0xfb, - 0x06, 0x4c, 0xb2, 0xf8, 0x07, 0xe9, 0x0f, 0xf4, 0x76, 0xe6, 0x41, 0x52, 0xab, 0x2c, 0xcc, 0x42, - 0x8c, 0x63, 0xb5, 0x7c, 0xf2, 0x42, 0x2c, 0x98, 0xa3, 0x7f, 0x68, 0xc0, 0xb4, 0xa5, 0x4d, 0x35, - 0xee, 0x22, 0xb5, 0x9b, 0xbd, 0x30, 0x7d, 0x33, 0x4b, 0x73, 0xed, 0x8c, 0x26, 0x92, 0x2e, 0x0b, - 0xd5, 0x3e, 0xb4, 0x4f, 0x18, 0x65, 0x86, 0xac, 0xbc, 0x01, 0x0b, 0x63, 0xcd, 0xb0, 0x9f, 0x85, - 0x51, 0x13, 0x1d, 0xd2, 0x0d, 0xeb, 0x48, 0x0f, 0x12, 0x57, 0x2d, 0x2e, 0xa2, 0xc4, 0x05, 0xd4, - 0xdc, 0x85, 0x85, 0xe4, 0xb1, 0x35, 0x73, 0x8f, 0x80, 0x4f, 0xc3, 0x88, 0xa9, 0x09, 0xcd, 0xeb, - 0x80, 0xb0, 0xe7, 0x38, 0xbb, 0x56, 0xe3, 0xe0, 0x81, 0xed, 0x36, 0xbd, 0x23, 0x36, 0x57, 0xd6, - 0xa0, 0xe4, 0x8b, 0xf8, 0xb4, 0x40, 0x7c, 0x96, 0x9a, 0x6c, 0x32, 0x70, 0x2d, 0xc0, 0x11, 0x8e, - 0xf9, 0xfb, 0x39, 0x98, 0x12, 0xc1, 0x94, 0x4f, 0xc0, 0xb9, 0xfa, 0x20, 0x76, 0xeb, 0xb6, 0x99, - 0x49, 0x0c, 0xe8, 0x40, 0xcf, 0xea, 0x20, 0xe1, 0x59, 0xfd, 0x56, 0x36, 0xec, 0x4e, 0x77, 0xab, - 0xfe, 0xee, 0x04, 0xcc, 0x27, 0x82, 0x53, 0xa9, 0xc6, 0xd3, 0xe7, 0x4d, 0x78, 0x2f, 0xd3, 0xf8, - 0x57, 0xe5, 0xf8, 0x7f, 0xba, 0x63, 0x61, 0x10, 0x4b, 0x24, 0x7b, 0x37, 0xb3, 0x1c, 0xf4, 0x7f, - 0x95, 0x53, 0x76, 0x54, 0x47, 0xb9, 0xff, 0x6a, 0xc0, 0xd3, 0x03, 0x63, 0x98, 0x59, 0x0a, 0x1c, - 0x3f, 0x0e, 0x15, 0x13, 0x32, 0xe3, 0x4c, 0x0d, 0xea, 0x0a, 0x2c, 0x99, 0xb5, 0x24, 0xc9, 0x1e, - 0xbd, 0x02, 0x33, 0x6c, 0x87, 0xa6, 0x4b, 0x53, 0x48, 0x3a, 0xc2, 0x82, 0xcf, 0x6c, 0xb9, 0x75, - 0xad, 0x1c, 0xc7, 0xb0, 0xcc, 0xef, 0x18, 0xb0, 0x3c, 0x28, 0x21, 0xca, 0x10, 0xe7, 0xcb, 0xbf, - 0x99, 0xf0, 0xfe, 0x2e, 0xf7, 0x79, 0x7f, 0x27, 0x4e, 0x98, 0xd2, 0xd1, 0x5b, 0x3b, 0xdc, 0xe5, - 0xcf, 0x70, 0x6e, 0xfe, 0x96, 0x01, 0x57, 0x06, 0xcc, 0xa6, 0xbe, 0x28, 0x00, 0xe3, 0xdc, 0x51, - 0x00, 0xb9, 0x61, 0xa3, 0x00, 0xcc, 0x3f, 0xc8, 0xc3, 0x82, 0x90, 0x27, 0x52, 0xd3, 0x5e, 0x8b, - 0xf9, 0xd0, 0xff, 0x44, 0xc2, 0x87, 0x7e, 0x29, 0x89, 0xff, 0x57, 0x0e, 0xf4, 0x1f, 0x2f, 0x07, - 0xfa, 0xbf, 0xc8, 0xc1, 0xe5, 0xd4, 0x3c, 0x2d, 0xe8, 0x1b, 0x29, 0x5b, 0xc3, 0x83, 0x8c, 0x13, - 0xc2, 0x0c, 0xb9, 0x39, 0x8c, 0xeb, 0x75, 0xfe, 0x6b, 0xba, 0xb7, 0x37, 0x5f, 0xea, 0xf7, 0x2e, - 0x20, 0xb5, 0xcd, 0x88, 0x8e, 0xdf, 0xe6, 0x2f, 0xe7, 0xe1, 0xc5, 0x61, 0x09, 0x7d, 0x4c, 0x03, - 0x83, 0x82, 0x58, 0x60, 0xd0, 0x13, 0xda, 0xb6, 0x2f, 0x24, 0x46, 0xe8, 0x7b, 0x79, 0xb5, 0xed, - 0xf5, 0x8f, 0xcf, 0xa1, 0xae, 0x7b, 0xa7, 0xa8, 0x6a, 0x27, 0x73, 0xbd, 0x46, 0x4b, 0xe1, 0x54, - 0x9d, 0x17, 0x3f, 0xee, 0x95, 0x17, 0xa3, 0x6c, 0x01, 0xa2, 0x10, 0xcb, 0x4a, 0xe8, 0x45, 0x28, - 0xfa, 0x1c, 0x2a, 0x43, 0x21, 0xc4, 0x9d, 0x39, 0x2f, 0xc3, 0x0a, 0x8a, 0xbe, 0xaa, 0xe9, 0xc2, - 0x13, 0x17, 0x95, 0x14, 0xe3, 0x34, 0x57, 0x80, 0xb7, 0xa1, 0x18, 0xc8, 0x3c, 0xac, 0xfc, 0xbe, - 0xe6, 0xb3, 0x43, 0x46, 0xd8, 0xd0, 0x13, 0x98, 0x4c, 0xca, 0xca, 0xbf, 0x4f, 0xa5, 0x6c, 0x55, - 0x24, 0x91, 0xa9, 0x0e, 0x3f, 0xdc, 0x54, 0x09, 0x29, 0x07, 0x9f, 0x1f, 0x1a, 0x30, 0x2d, 0x7a, - 0xeb, 0x09, 0x04, 0xfd, 0x3c, 0x8c, 0x07, 0xfd, 0x5c, 0xcf, 0x64, 0xed, 0x18, 0x10, 0xf1, 0xf3, - 0x10, 0x66, 0xf4, 0x54, 0x5d, 0xe8, 0x0b, 0xda, 0xda, 0x67, 0x8c, 0x93, 0xfc, 0x46, 0xae, 0x8e, - 0xd1, 0xba, 0x68, 0xfe, 0x46, 0x49, 0xb5, 0x22, 0x3b, 0xa2, 0xe9, 0x63, 0xd0, 0x38, 0x75, 0x0c, - 0xea, 0x43, 0x20, 0x97, 0xfd, 0x10, 0xb8, 0x0b, 0x45, 0xb9, 0x40, 0x89, 0x6d, 0xfc, 0x79, 0xdd, - 0x0d, 0x92, 0xea, 0x02, 0x94, 0x98, 0x36, 0x70, 0xd9, 0x51, 0x4b, 0xf5, 0xa1, 0x5a, 0x38, 0x15, - 0x19, 0xf4, 0x0e, 0x4c, 0x1f, 0x79, 0xfe, 0x81, 0xe3, 0x59, 0x2c, 0x1f, 0x33, 0x64, 0x71, 0xf3, - 0xa6, 0xec, 0x66, 0xdc, 0x17, 0xfd, 0x41, 0x44, 0x1f, 0xeb, 0xcc, 0x50, 0x05, 0xe6, 0xdb, 0xb6, - 0x8b, 0x89, 0xd5, 0x54, 0xb1, 0x3d, 0x13, 0x3c, 0x05, 0xac, 0x54, 0x72, 0xb7, 0xe3, 0x60, 0x9c, - 0xc4, 0x47, 0xdf, 0x34, 0x60, 0xce, 0x8f, 0x1d, 0xaa, 0x45, 0x9e, 0xc7, 0xda, 0xf8, 0x83, 0x31, - 0x7e, 0x50, 0xe7, 0xce, 0xd8, 0xf1, 0x72, 0x9c, 0xe0, 0x8d, 0xbe, 0x02, 0xc5, 0x40, 0xe4, 0xe1, - 0xca, 0xe6, 0xca, 0x56, 0x1d, 0x61, 0x39, 0xd1, 0xa8, 0x2b, 0x65, 0x09, 0x56, 0x0c, 0xd1, 0x16, - 0x2c, 0x49, 0x2b, 0x41, 0xec, 0xcd, 0x9c, 0xc9, 0x28, 0x6d, 0x0b, 0x4e, 0x81, 0xe3, 0xd4, 0x5a, - 0x54, 0xa9, 0x62, 0x29, 0xf0, 0xf8, 0x4d, 0x87, 0x76, 0x39, 0xc0, 0xe6, 0x5f, 0x13, 0x0b, 0xe8, - 0x69, 0xa1, 0x6b, 0xc5, 0x31, 0x42, 0xd7, 0xea, 0x70, 0x39, 0x09, 0x62, 0xf9, 0x78, 0x58, 0x0a, - 0x20, 0x6d, 0x33, 0xab, 0xa5, 0x21, 0xe1, 0xf4, 0xba, 0xe8, 0x01, 0x94, 0x7c, 0xc2, 0x8e, 0x3b, - 0x15, 0xe9, 0x24, 0x32, 0xb2, 0x3b, 0x1c, 0x96, 0x04, 0x70, 0x44, 0x8b, 0xf6, 0xbb, 0x15, 0x4f, - 0xca, 0x7a, 0x37, 0xc3, 0x57, 0xff, 0x44, 0xdf, 0x0f, 0xc8, 0x93, 0x65, 0x7e, 0x34, 0x07, 0xb3, - 0x31, 0x53, 0x07, 0x7a, 0x1e, 0x0a, 0x2c, 0x41, 0x11, 0x5b, 0xad, 0x8a, 0xd1, 0x8a, 0xca, 0x1b, - 0x87, 0xc3, 0xd0, 0xaf, 0x18, 0x30, 0xdf, 0x89, 0xd9, 0x96, 0xe5, 0x42, 0x3e, 0xe6, 0xbd, 0x70, - 0xdc, 0x60, 0xad, 0xa5, 0x33, 0x8f, 0x33, 0xc3, 0x49, 0xee, 0x74, 0x3d, 0x10, 0x3e, 0xa5, 0x0e, - 0xf1, 0x19, 0xb6, 0x50, 0xb9, 0x14, 0x89, 0xf5, 0x38, 0x18, 0x27, 0xf1, 0x69, 0x0f, 0xb3, 0xaf, - 0x1b, 0xe7, 0x39, 0xb0, 0x8a, 0x24, 0x80, 0x23, 0x5a, 0xe8, 0x0d, 0x98, 0x13, 0xb9, 0x38, 0x6b, - 0x5e, 0xf3, 0xa6, 0x15, 0xec, 0x8b, 0xb3, 0x86, 0x3a, 0x1b, 0xad, 0xc7, 0xa0, 0x38, 0x81, 0xcd, - 0xbe, 0x2d, 0x4a, 0x78, 0xca, 0x08, 0x4c, 0xc6, 0xb3, 0xbd, 0xaf, 0xc7, 0xc1, 0x38, 0x89, 0x8f, - 0x5e, 0xd2, 0xb6, 0x21, 0x7e, 0xfb, 0xa8, 0x56, 0x83, 0x94, 0xad, 0xa8, 0x02, 0xf3, 0x5d, 0x76, - 0x34, 0x6b, 0x4a, 0xa0, 0x98, 0x8f, 0x8a, 0xe1, 0xbd, 0x38, 0x18, 0x27, 0xf1, 0xd1, 0xeb, 0x30, - 0xeb, 0xd3, 0xc5, 0x56, 0x11, 0xe0, 0x57, 0x92, 0xea, 0xc6, 0x09, 0xeb, 0x40, 0x1c, 0xc7, 0x45, - 0x6f, 0xc2, 0x62, 0x94, 0xba, 0x4e, 0x12, 0xe0, 0x77, 0x94, 0x2a, 0x8f, 0x52, 0x25, 0x89, 0x80, - 0xfb, 0xeb, 0xa0, 0xbf, 0x0d, 0x0b, 0x5a, 0x4b, 0x6c, 0xba, 0x4d, 0xf2, 0x48, 0xa4, 0x17, 0x63, - 0xcf, 0x78, 0xac, 0x27, 0x60, 0xb8, 0x0f, 0x1b, 0xfd, 0x0c, 0xcc, 0x35, 0x3c, 0xc7, 0x61, 0x6b, - 0x1c, 0xcf, 0x34, 0xce, 0xf3, 0x88, 0xf1, 0x8c, 0x6b, 0x31, 0x08, 0x4e, 0x60, 0xa2, 0x5b, 0x80, - 0xbc, 0xdd, 0x80, 0xf8, 0x87, 0xa4, 0xf9, 0x26, 0x7f, 0x60, 0x98, 0x6a, 0x1c, 0xb3, 0x71, 0x8f, - 0xf6, 0x3b, 0x7d, 0x18, 0x38, 0xa5, 0x16, 0x4b, 0xc3, 0xa4, 0x45, 0x00, 0xce, 0x65, 0xf1, 0x34, - 0x56, 0xd2, 0x90, 0x70, 0x66, 0xf8, 0x9f, 0x0f, 0x93, 0x3c, 0xc0, 0x20, 0x9b, 0x84, 0x62, 0x7a, - 0xd2, 0xe1, 0x68, 0x8f, 0xe0, 0xa5, 0x58, 0x70, 0x42, 0xbf, 0x08, 0xa5, 0x5d, 0x99, 0x81, 0x9e, - 0x65, 0x11, 0x1b, 0x7b, 0x5f, 0x4c, 0x3c, 0xa6, 0x10, 0x1d, 0x94, 0x15, 0x00, 0x47, 0x2c, 0xd1, - 0x0b, 0x30, 0x7d, 0xb3, 0x56, 0x51, 0xa3, 0x70, 0x91, 0xf5, 0xfe, 0x04, 0xad, 0x82, 0x75, 0x00, - 0x9d, 0x61, 0x4a, 0x7d, 0x43, 0xf1, 0x47, 0x1d, 0x52, 0xb4, 0x31, 0x8a, 0xcd, 0x2e, 0x59, 0x71, - 0x7d, 0xf9, 0x52, 0x02, 0x5b, 0x94, 0x63, 0x85, 0x81, 0xde, 0x86, 0x69, 0xb1, 0x5f, 0xb0, 0xb5, - 0x69, 0xe9, 0x7c, 0xd1, 0xa5, 0x38, 0x22, 0x81, 0x75, 0x7a, 0xec, 0xee, 0x8c, 0x25, 0xe6, 0x26, - 0x37, 0xba, 0x8e, 0xb3, 0x7c, 0x99, 0xad, 0x9b, 0xd1, 0xdd, 0x59, 0x04, 0xc2, 0x3a, 0x1e, 0xfa, - 0xac, 0xf4, 0x07, 0xf9, 0x44, 0xec, 0x32, 0x51, 0xf9, 0x83, 0x28, 0xa5, 0x7b, 0x80, 0x03, 0xfa, - 0x95, 0x33, 0x1c, 0x31, 0x76, 0x61, 0x45, 0x6a, 0x7c, 0xfd, 0x93, 0x64, 0x79, 0x39, 0x66, 0xb4, - 0x58, 0x79, 0x30, 0x10, 0x13, 0x9f, 0x42, 0x05, 0xed, 0x42, 0xde, 0x72, 0x76, 0x97, 0x9f, 0xce, - 0x42, 0x75, 0x55, 0x0f, 0x86, 0x73, 0xa7, 0xb1, 0xca, 0x56, 0x15, 0x53, 0xe2, 0xe6, 0xd7, 0x72, - 0xea, 0x92, 0x40, 0x25, 0x5a, 0x7d, 0x57, 0x1f, 0xd5, 0x46, 0x16, 0x0f, 0xe2, 0xf6, 0x3d, 0xd3, - 0xc0, 0x37, 0xa4, 0xd4, 0x31, 0xdd, 0x51, 0xf3, 0x38, 0x93, 0xbc, 0x37, 0xf1, 0x24, 0xb2, 0xfc, - 0x70, 0x19, 0x9f, 0xc5, 0xe6, 0x1f, 0x17, 0x95, 0x4d, 0x2c, 0xe1, 0xe0, 0xe0, 0x43, 0xc1, 0x0e, - 0x42, 0xdb, 0xcb, 0x30, 0x12, 0x32, 0x91, 0x7d, 0x95, 0x39, 0x5a, 0x33, 0x00, 0xe6, 0xac, 0x28, - 0x4f, 0xb7, 0x65, 0xbb, 0x8f, 0xc4, 0xe7, 0xdf, 0xcd, 0xdc, 0x73, 0x81, 0xf3, 0x64, 0x00, 0xcc, - 0x59, 0xa1, 0x87, 0x7c, 0xa4, 0x65, 0xf3, 0xf8, 0x71, 0xf2, 0x4d, 0xf3, 0xf8, 0x88, 0xa3, 0xbc, - 0x82, 0xb6, 0x2d, 0x74, 0x98, 0x31, 0x79, 0xd5, 0xb7, 0x37, 0xd3, 0x78, 0xd5, 0xb7, 0x37, 0x31, - 0x65, 0x82, 0x3e, 0x30, 0x00, 0x2c, 0xf5, 0xb8, 0x77, 0x36, 0x6f, 0x9a, 0x0c, 0x7a, 0x2c, 0x9c, - 0xfb, 0xea, 0x45, 0x50, 0xac, 0x71, 0x46, 0xef, 0xc0, 0x94, 0xc5, 0xdf, 0x6f, 0x12, 0x6e, 0xa7, - 0xd9, 0x3c, 0x4a, 0x96, 0x90, 0x80, 0xf9, 0xdb, 0x0a, 0x10, 0x96, 0x0c, 0x29, 0xef, 0xd0, 0xb7, - 0xc8, 0x9e, 0x7d, 0x20, 0xfc, 0x4f, 0xeb, 0x63, 0x27, 0x56, 0xa7, 0xc4, 0xd2, 0x78, 0x0b, 0x10, - 0x96, 0x0c, 0xf9, 0x7b, 0xba, 0x96, 0x6b, 0xa9, 0x60, 0xa2, 0x6c, 0x42, 0xce, 0xf4, 0xf0, 0x24, - 0xed, 0x3d, 0x5d, 0x9d, 0x11, 0x8e, 0xf3, 0x45, 0x87, 0x30, 0x69, 0xb1, 0x97, 0xe5, 0xc4, 0xf9, - 0x08, 0x67, 0xf1, 0x4a, 0x5d, 0xa2, 0x0d, 0xd8, 0xe2, 0x22, 0xde, 0xaf, 0x13, 0xdc, 0xcc, 0x3f, - 0xcb, 0x03, 0x30, 0x11, 0x78, 0x60, 0x7d, 0x9b, 0x25, 0x63, 0xdc, 0xf7, 0x9a, 0xd9, 0x3c, 0x96, - 0xa7, 0xc7, 0xc7, 0x83, 0xc8, 0xbc, 0xb8, 0xef, 0x35, 0xb1, 0x60, 0x82, 0x5a, 0x30, 0xd1, 0xb1, - 0xc2, 0xfd, 0xec, 0x83, 0xf1, 0x8b, 0x3c, 0xc2, 0x2c, 0xdc, 0xc7, 0x8c, 0x01, 0x7a, 0xcf, 0x80, - 0x29, 0x1e, 0x8e, 0x2f, 0x2d, 0xee, 0x63, 0x5f, 0x2b, 0xcb, 0x36, 0x5b, 0xe5, 0x31, 0xff, 0xc2, - 0xf5, 0x43, 0x6d, 0xc9, 0xa2, 0x14, 0x4b, 0xb6, 0x2b, 0xef, 0x1b, 0x30, 0xa3, 0xa3, 0xa6, 0x38, - 0x6d, 0xfc, 0xbc, 0xee, 0xb4, 0x91, 0x65, 0x7b, 0xe8, 0xfe, 0x1f, 0xff, 0xc3, 0x00, 0xed, 0xf5, - 0xe3, 0xc8, 0x65, 0xd3, 0x18, 0xda, 0x65, 0x33, 0x37, 0xa2, 0xcb, 0x66, 0x7e, 0x24, 0x97, 0xcd, - 0x89, 0xd1, 0x5d, 0x36, 0x0b, 0x83, 0x5d, 0x36, 0xcd, 0x0f, 0x0d, 0x58, 0xec, 0x5b, 0x87, 0xa9, - 0xda, 0xe6, 0x7b, 0x5e, 0x38, 0xc0, 0x53, 0x0a, 0x47, 0x20, 0xac, 0xe3, 0xa1, 0x0d, 0x58, 0x10, - 0xa9, 0xc7, 0xeb, 0x1d, 0xc7, 0x4e, 0x4d, 0x94, 0xb0, 0x93, 0x80, 0xe3, 0xbe, 0x1a, 0xe6, 0xbf, - 0x35, 0x60, 0x5a, 0x0b, 0xaf, 0xa4, 0xdf, 0xc1, 0xc2, 0x50, 0x85, 0x18, 0x51, 0xd6, 0x75, 0x76, - 0xc3, 0xc1, 0x61, 0xfc, 0xb2, 0xad, 0xa5, 0x25, 0xa6, 0x8d, 0x2e, 0xdb, 0x68, 0x29, 0x16, 0x50, - 0x9e, 0x72, 0x94, 0x74, 0x58, 0xa3, 0xe7, 0xf5, 0x94, 0xa3, 0xa4, 0x83, 0x19, 0x84, 0xb1, 0xa3, - 0xfa, 0xab, 0xf0, 0xe6, 0xd5, 0x92, 0xbc, 0x5b, 0x7e, 0x88, 0x39, 0x0c, 0x5d, 0x85, 0x3c, 0x71, - 0x9b, 0xe2, 0xb0, 0xad, 0x9e, 0x61, 0xbb, 0xee, 0x36, 0x31, 0x2d, 0x37, 0xef, 0xc0, 0x4c, 0x9d, - 0x34, 0x7c, 0x12, 0xbe, 0x45, 0x8e, 0x87, 0x7e, 0xd7, 0x8d, 0x8e, 0xf6, 0xc4, 0xbb, 0x6e, 0xb4, - 0x3a, 0x2d, 0x37, 0xff, 0x85, 0x01, 0x89, 0x97, 0x08, 0x34, 0xc3, 0xbb, 0x31, 0xc8, 0xf0, 0x1e, - 0x33, 0x11, 0xe7, 0x4e, 0x35, 0x11, 0xdf, 0x02, 0xd4, 0xa6, 0x53, 0x21, 0xf6, 0xee, 0x86, 0xb0, - 0x73, 0x44, 0xc1, 0xdc, 0x7d, 0x18, 0x38, 0xa5, 0x96, 0xf9, 0xcf, 0xb9, 0xb0, 0xfa, 0xdb, 0x04, - 0x67, 0x37, 0x40, 0x17, 0x0a, 0x8c, 0x94, 0x30, 0xf6, 0x8c, 0x69, 0x28, 0xed, 0x4f, 0x8a, 0x12, - 0x75, 0xa4, 0x98, 0xf2, 0x8c, 0x9b, 0xf9, 0x07, 0x5c, 0x56, 0xed, 0xf1, 0x82, 0x21, 0x64, 0x6d, - 0xc7, 0x65, 0xbd, 0x99, 0xd5, 0x5a, 0x99, 0x2e, 0x23, 0x5a, 0x05, 0xe8, 0x10, 0xbf, 0x41, 0xdc, - 0x50, 0x3a, 0x99, 0x17, 0x44, 0xb8, 0x93, 0x2a, 0xc5, 0x1a, 0x86, 0xf9, 0xeb, 0x06, 0x2c, 0x24, - 0x63, 0x11, 0xb2, 0x76, 0x34, 0x8b, 0x05, 0x4c, 0xe6, 0x47, 0x0f, 0x98, 0x34, 0xdf, 0xa3, 0x42, - 0x86, 0x76, 0xe3, 0xc0, 0x76, 0x79, 0x8c, 0xe1, 0x9e, 0xdd, 0xa2, 0x42, 0x12, 0xf1, 0x52, 0x1a, - 0xb7, 0x11, 0x2a, 0x21, 0xe5, 0x03, 0x69, 0x12, 0x8e, 0x2a, 0x30, 0x2f, 0x6f, 0x46, 0xa4, 0x61, - 0x97, 0xc7, 0x46, 0x2b, 0x43, 0xd2, 0x46, 0x1c, 0x8c, 0x93, 0xf8, 0xe6, 0x57, 0x61, 0x5a, 0xdb, - 0x04, 0xd8, 0x7a, 0xf9, 0xc8, 0x6a, 0x84, 0xc9, 0x75, 0xe6, 0x3a, 0x2d, 0xc4, 0x1c, 0xc6, 0xec, - 0xcf, 0xdc, 0x55, 0x3a, 0xb1, 0xce, 0x08, 0x07, 0x69, 0x01, 0xa5, 0xc4, 0x7c, 0xd2, 0x22, 0x8f, - 0x64, 0x9e, 0x5e, 0x49, 0x0c, 0xd3, 0x42, 0xcc, 0x61, 0xe6, 0x4b, 0x50, 0x94, 0x19, 0x2c, 0x58, - 0x18, 0xb8, 0xb4, 0x8d, 0xea, 0x61, 0xe0, 0x9e, 0x1f, 0x62, 0x06, 0x31, 0xef, 0x43, 0x51, 0x26, - 0xda, 0x38, 0x1b, 0x9b, 0x4e, 0xfd, 0xc0, 0xb5, 0x6f, 0x7a, 0x41, 0x28, 0xb3, 0x83, 0xf0, 0xeb, - 0x9b, 0xdb, 0x9b, 0xac, 0x0c, 0x2b, 0xa8, 0xb9, 0x08, 0xf3, 0xea, 0x5e, 0x46, 0xf8, 0xae, 0xfe, - 0x6e, 0x1e, 0x66, 0x62, 0x4f, 0x95, 0x9f, 0x3d, 0x27, 0x86, 0x5f, 0x6a, 0x52, 0xee, 0x57, 0xf2, - 0x23, 0xde, 0xaf, 0xe8, 0x17, 0x5a, 0x13, 0x17, 0x7b, 0xa1, 0x55, 0xc8, 0xe6, 0x42, 0x2b, 0x84, - 0xa9, 0x40, 0xec, 0xa6, 0x93, 0x59, 0x58, 0x9a, 0x12, 0x3d, 0xc6, 0x15, 0x78, 0xb9, 0x29, 0x4b, - 0x56, 0xe6, 0x6f, 0x15, 0x60, 0x2e, 0x9e, 0x62, 0x6c, 0x88, 0x9e, 0x7c, 0xa9, 0xaf, 0x27, 0x47, - 0x34, 0xe8, 0xe6, 0xc7, 0x35, 0xe8, 0x4e, 0x8c, 0x6b, 0xd0, 0x2d, 0x9c, 0xc3, 0xa0, 0xdb, 0x6f, - 0x8e, 0x9d, 0x1c, 0xda, 0x1c, 0xfb, 0x39, 0xe5, 0x1c, 0x35, 0x15, 0xf3, 0x26, 0x88, 0x9c, 0xa3, - 0x50, 0xbc, 0x1b, 0xd6, 0xbd, 0x66, 0xaa, 0x93, 0x59, 0xf1, 0x0c, 0xc3, 0x95, 0x9f, 0xea, 0xcb, - 0x34, 0xfa, 0x9d, 0xd1, 0x27, 0x46, 0xf0, 0x63, 0x7a, 0x15, 0xa6, 0xc5, 0x78, 0x62, 0x0a, 0x1d, - 0xc4, 0x95, 0xc1, 0x7a, 0x04, 0xc2, 0x3a, 0x1e, 0x7b, 0x48, 0x36, 0xfe, 0xce, 0x2e, 0xb3, 0x8f, - 0xeb, 0x0f, 0xc9, 0x26, 0xde, 0xe5, 0x4d, 0xe2, 0x9b, 0x5f, 0x81, 0xcb, 0xa9, 0xc7, 0x55, 0x66, - 0xbf, 0x63, 0xba, 0x06, 0x69, 0x0a, 0x04, 0x4d, 0x8c, 0x44, 0x06, 0xea, 0x95, 0x07, 0x03, 0x31, - 0xf1, 0x29, 0x54, 0xcc, 0xdf, 0xcc, 0xc3, 0x5c, 0xfc, 0x15, 0x32, 0x74, 0xa4, 0x8c, 0x5b, 0x99, - 0xd8, 0xd5, 0x38, 0x59, 0x2d, 0x6d, 0xd5, 0x40, 0x4b, 0xf5, 0x11, 0x1b, 0x5f, 0xbb, 0x2a, 0x87, - 0xd6, 0xc5, 0x31, 0x16, 0x26, 0x62, 0xc1, 0x8e, 0x3d, 0x34, 0x16, 0x45, 0xb8, 0x88, 0xb3, 0x61, - 0xe6, 0xdc, 0xa3, 0x98, 0x15, 0xc5, 0x0a, 0x6b, 0x6c, 0xe9, 0xde, 0x72, 0x48, 0x7c, 0x7b, 0xcf, - 0x56, 0x2f, 0xa8, 0xb2, 0x95, 0xfb, 0xbe, 0x28, 0xc3, 0x0a, 0x6a, 0xbe, 0x97, 0x83, 0xe8, 0x75, - 0x69, 0xf6, 0x54, 0x4f, 0xa0, 0xe9, 0xe1, 0xa2, 0xdb, 0x6e, 0x8d, 0xfb, 0x1e, 0x56, 0x44, 0x51, - 0x38, 0xae, 0x6a, 0x25, 0x38, 0xc6, 0xf1, 0xc7, 0xf0, 0xaa, 0xb4, 0x05, 0xf3, 0x89, 0x88, 0xea, - 0xcc, 0x83, 0x0c, 0x7e, 0x94, 0x87, 0x92, 0x8a, 0x49, 0x47, 0x3f, 0x1d, 0x33, 0x8a, 0x94, 0xaa, - 0x9f, 0xd4, 0xde, 0x91, 0xd8, 0xf7, 0x9a, 0x8f, 0x7b, 0xe5, 0x79, 0x85, 0x9c, 0x30, 0x70, 0x5c, - 0x85, 0x7c, 0xd7, 0x77, 0x92, 0xa7, 0x9e, 0x7b, 0x78, 0x0b, 0xd3, 0x72, 0xf4, 0x28, 0x69, 0x95, - 0xd8, 0xce, 0x28, 0x8e, 0x9e, 0x1f, 0x0f, 0x06, 0x5b, 0x23, 0xe8, 0x2e, 0xb9, 0xeb, 0x35, 0x8f, - 0x93, 0xef, 0x4e, 0x54, 0xbd, 0xe6, 0x31, 0x66, 0x10, 0xf4, 0x06, 0xcc, 0x85, 0x76, 0x9b, 0x78, - 0xdd, 0x50, 0x7f, 0x8d, 0x37, 0x1f, 0xdd, 0xbc, 0xee, 0xc4, 0xa0, 0x38, 0x81, 0x4d, 0x77, 0xd9, - 0x87, 0x81, 0xe7, 0xb2, 0x64, 0x92, 0x93, 0xf1, 0x6b, 0x9a, 0x5b, 0xf5, 0x3b, 0xb7, 0x99, 0x71, - 0x46, 0x61, 0x50, 0x6c, 0x9b, 0x85, 0x49, 0xfa, 0x44, 0x38, 0x3e, 0x2c, 0x44, 0xda, 0x36, 0x2f, - 0xc7, 0x0a, 0x03, 0x6d, 0x70, 0xda, 0x54, 0x5a, 0xb6, 0xa3, 0xcc, 0x54, 0x5f, 0x94, 0x74, 0x69, - 0xd9, 0xe3, 0xde, 0x29, 0x01, 0xc0, 0xaa, 0xa6, 0x79, 0x0f, 0xe6, 0x13, 0x0d, 0x26, 0x4f, 0xa9, - 0x46, 0xfa, 0x29, 0x75, 0xb8, 0xa7, 0x22, 0xfe, 0x95, 0x01, 0x8b, 0x7d, 0x4b, 0xc0, 0xb0, 0x31, - 0x34, 0xc9, 0xcd, 0x28, 0x77, 0xfe, 0xcd, 0x28, 0x3f, 0xda, 0x66, 0x54, 0xdd, 0xfd, 0xfe, 0x47, - 0xd7, 0x9e, 0xfa, 0xc1, 0x47, 0xd7, 0x9e, 0xfa, 0xc3, 0x8f, 0xae, 0x3d, 0xf5, 0xde, 0xc9, 0x35, - 0xe3, 0xfb, 0x27, 0xd7, 0x8c, 0x1f, 0x9c, 0x5c, 0x33, 0xfe, 0xf0, 0xe4, 0x9a, 0xf1, 0x5f, 0x4e, - 0xae, 0x19, 0x1f, 0xfe, 0xe8, 0xda, 0x53, 0x5f, 0xf8, 0x5c, 0x34, 0x40, 0xd7, 0xe4, 0x00, 0x65, - 0x3f, 0x3e, 0x25, 0x87, 0xe3, 0x5a, 0xe7, 0xa0, 0xb5, 0x46, 0x07, 0xe8, 0x9a, 0x2a, 0x91, 0x03, - 0xf4, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x04, 0xa3, 0x99, 0x8b, 0xf5, 0x97, 0x00, 0x00, + // 7927 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x6d, 0x6c, 0x24, 0xc9, + 0x75, 0xd8, 0xf5, 0x0c, 0x87, 0x9c, 0x79, 0xfc, 0xae, 0xe5, 0x6a, 0x79, 0xbc, 0xdb, 0x9d, 0x53, + 0x9f, 0x71, 0x39, 0xdb, 0x27, 0x52, 0x3a, 0xdd, 0x25, 0x67, 0x9d, 0x70, 0xc9, 0x0c, 0xb9, 0x7b, + 0xcb, 0x3d, 0x72, 0x77, 0xb6, 0x86, 0xbb, 0x2b, 0x4b, 0x3a, 0x5b, 0xcd, 0x99, 0xe2, 0xb0, 0x97, + 0x3d, 0xdd, 0xe3, 0xee, 0x1e, 0x72, 0x79, 0x3a, 0x58, 0x27, 0x0b, 0xa7, 0x28, 0x86, 0x04, 0x5f, + 0x62, 0x0b, 0x41, 0x90, 0x20, 0x10, 0x0c, 0x01, 0x4e, 0x22, 0xff, 0x32, 0x12, 0xe4, 0x8f, 0x81, + 0x18, 0xf1, 0x47, 0x94, 0x1f, 0x0e, 0xe4, 0x1f, 0x89, 0xec, 0x00, 0x1e, 0xe7, 0xa8, 0xfc, 0x49, + 0x90, 0xc0, 0x08, 0xe0, 0x20, 0xf0, 0xfe, 0x08, 0x82, 0xfa, 0xec, 0xea, 0x9e, 0x1e, 0x72, 0x86, + 0xd3, 0x5c, 0x1d, 0x62, 0xff, 0x9b, 0xa9, 0xf7, 0xea, 0xbd, 0xd7, 0xf5, 0xf9, 0xea, 0xd5, 0x7b, + 0xaf, 0x60, 0xab, 0x65, 0x87, 0xfb, 0xdd, 0xdd, 0xd5, 0x86, 0xd7, 0x5e, 0xb3, 0xfc, 0x96, 0xd7, + 0xf1, 0xbd, 0x87, 0xec, 0xc7, 0x27, 0x7c, 0xcf, 0x71, 0xbc, 0x6e, 0x18, 0xac, 0x75, 0x0e, 0x5a, + 0x6b, 0x56, 0xc7, 0x0e, 0xd6, 0x54, 0xc9, 0xe1, 0xa7, 0x2c, 0xa7, 0xb3, 0x6f, 0x7d, 0x6a, 0xad, + 0x45, 0x5c, 0xe2, 0x5b, 0x21, 0x69, 0xae, 0x76, 0x7c, 0x2f, 0xf4, 0xd0, 0x67, 0x23, 0x6a, 0xab, + 0x92, 0x1a, 0xfb, 0xf1, 0xf3, 0xb2, 0xee, 0x6a, 0xe7, 0xa0, 0xb5, 0x4a, 0xa9, 0xad, 0xaa, 0x12, + 0x49, 0x6d, 0xe5, 0x13, 0x9a, 0x2c, 0x2d, 0xaf, 0xe5, 0xad, 0x31, 0xa2, 0xbb, 0xdd, 0x3d, 0xf6, + 0x8f, 0xfd, 0x61, 0xbf, 0x38, 0xb3, 0x95, 0xe7, 0x0f, 0x5e, 0x0b, 0x56, 0x6d, 0x8f, 0xca, 0xb6, + 0xb6, 0x6b, 0x85, 0x8d, 0xfd, 0xb5, 0xc3, 0x3e, 0x89, 0x56, 0x4c, 0x0d, 0xa9, 0xe1, 0xf9, 0x24, + 0x0d, 0xe7, 0x95, 0x08, 0xa7, 0x6d, 0x35, 0xf6, 0x6d, 0x97, 0xf8, 0xc7, 0xd1, 0x57, 0xb7, 0x49, + 0x68, 0xa5, 0xd5, 0x5a, 0x1b, 0x54, 0xcb, 0xef, 0xba, 0xa1, 0xdd, 0x26, 0x7d, 0x15, 0xfe, 0xe6, + 0x59, 0x15, 0x82, 0xc6, 0x3e, 0x69, 0x5b, 0x7d, 0xf5, 0x3e, 0x3d, 0xa8, 0x5e, 0x37, 0xb4, 0x9d, + 0x35, 0xdb, 0x0d, 0x83, 0xd0, 0x4f, 0x56, 0x32, 0x7f, 0x2f, 0x0f, 0xa5, 0xca, 0x56, 0xb5, 0x1e, + 0x5a, 0x61, 0x37, 0x40, 0x5f, 0x37, 0x60, 0xc6, 0xf1, 0xac, 0x66, 0xd5, 0x72, 0x2c, 0xb7, 0x41, + 0xfc, 0x65, 0xe3, 0x39, 0xe3, 0xc5, 0xe9, 0x97, 0xb7, 0x56, 0xc7, 0xe9, 0xaf, 0xd5, 0xca, 0x51, + 0x80, 0x49, 0xe0, 0x75, 0xfd, 0x06, 0xc1, 0x64, 0xaf, 0xba, 0xf4, 0xfd, 0x5e, 0xf9, 0xa9, 0x93, + 0x5e, 0x79, 0x66, 0x4b, 0xe3, 0x84, 0x63, 0x7c, 0xd1, 0xb7, 0x0d, 0x58, 0x6c, 0x58, 0xae, 0xe5, + 0x1f, 0xef, 0x58, 0x7e, 0x8b, 0x84, 0x6f, 0xfa, 0x5e, 0xb7, 0xb3, 0x9c, 0xbb, 0x00, 0x69, 0x9e, + 0x16, 0xd2, 0x2c, 0xae, 0x27, 0xd9, 0xe1, 0x7e, 0x09, 0x98, 0x5c, 0x41, 0x68, 0xed, 0x3a, 0x44, + 0x97, 0x2b, 0x7f, 0x91, 0x72, 0xd5, 0x93, 0xec, 0x70, 0xbf, 0x04, 0xe6, 0xfb, 0x79, 0x58, 0xac, + 0x6c, 0x55, 0x77, 0x7c, 0x6b, 0x6f, 0xcf, 0x6e, 0x60, 0xaf, 0x1b, 0xda, 0x6e, 0x0b, 0xfd, 0x24, + 0x4c, 0xd9, 0x6e, 0xcb, 0x27, 0x41, 0xc0, 0x3a, 0xb2, 0x54, 0x9d, 0x17, 0x44, 0xa7, 0x36, 0x79, + 0x31, 0x96, 0x70, 0xf4, 0x2a, 0x4c, 0x07, 0xc4, 0x3f, 0xb4, 0x1b, 0xa4, 0xe6, 0xf9, 0x21, 0x6b, + 0xe9, 0x42, 0xf5, 0x92, 0x40, 0x9f, 0xae, 0x47, 0x20, 0xac, 0xe3, 0xd1, 0x6a, 0xbe, 0xe7, 0x85, + 0x02, 0xce, 0x1a, 0xa2, 0x14, 0x55, 0xc3, 0x11, 0x08, 0xeb, 0x78, 0xe8, 0x03, 0x03, 0x16, 0x82, + 0xd0, 0x6e, 0x1c, 0xd8, 0x2e, 0x09, 0x82, 0x75, 0xcf, 0xdd, 0xb3, 0x5b, 0xcb, 0x05, 0xd6, 0x8a, + 0xb7, 0xc7, 0x6b, 0xc5, 0x7a, 0x82, 0x6a, 0x75, 0xe9, 0xa4, 0x57, 0x5e, 0x48, 0x96, 0xe2, 0x3e, + 0xee, 0x68, 0x03, 0x16, 0x2c, 0xd7, 0xf5, 0x42, 0x2b, 0xb4, 0x3d, 0xb7, 0xe6, 0x93, 0x3d, 0xfb, + 0xd1, 0xf2, 0x04, 0xfb, 0x9c, 0x65, 0xf1, 0x39, 0x0b, 0x95, 0x04, 0x1c, 0xf7, 0xd5, 0x30, 0x37, + 0x60, 0xb9, 0xd2, 0xde, 0xb5, 0x82, 0xc0, 0x6a, 0x7a, 0x7e, 0xa2, 0x37, 0x5e, 0x84, 0x62, 0xdb, + 0xea, 0x74, 0x6c, 0xb7, 0x45, 0xbb, 0x23, 0xff, 0x62, 0xa9, 0x3a, 0x73, 0xd2, 0x2b, 0x17, 0xb7, + 0x45, 0x19, 0x56, 0x50, 0xf3, 0x4f, 0x72, 0x30, 0x5d, 0x71, 0x2d, 0xe7, 0x38, 0xb0, 0x03, 0xdc, + 0x75, 0xd1, 0x97, 0xa0, 0x48, 0x57, 0x97, 0xa6, 0x15, 0x5a, 0x62, 0x46, 0x7e, 0x72, 0x95, 0x4f, + 0xf6, 0x55, 0x7d, 0xb2, 0x47, 0xed, 0x42, 0xb1, 0x57, 0x0f, 0x3f, 0xb5, 0x7a, 0x67, 0xf7, 0x21, + 0x69, 0x84, 0xdb, 0x24, 0xb4, 0xaa, 0x48, 0x7c, 0x05, 0x44, 0x65, 0x58, 0x51, 0x45, 0x1e, 0x4c, + 0x04, 0x1d, 0xd2, 0x10, 0x33, 0x6c, 0x7b, 0xcc, 0x91, 0x1c, 0x89, 0x5e, 0xef, 0x90, 0x46, 0x75, + 0x46, 0xb0, 0x9e, 0xa0, 0xff, 0x30, 0x63, 0x84, 0x8e, 0x60, 0x32, 0x60, 0x6b, 0x8e, 0x98, 0x3c, + 0x77, 0xb2, 0x63, 0xc9, 0xc8, 0x56, 0xe7, 0x04, 0xd3, 0x49, 0xfe, 0x1f, 0x0b, 0x76, 0xe6, 0x7f, + 0x36, 0xe0, 0x92, 0x86, 0x5d, 0xf1, 0x5b, 0xdd, 0x36, 0x71, 0x43, 0xf4, 0x1c, 0x4c, 0xb8, 0x56, + 0x9b, 0x88, 0x89, 0xa2, 0x44, 0xbe, 0x6d, 0xb5, 0x09, 0x66, 0x10, 0xf4, 0x3c, 0x14, 0x0e, 0x2d, + 0xa7, 0x4b, 0x58, 0x23, 0x95, 0xaa, 0xb3, 0x02, 0xa5, 0x70, 0x9f, 0x16, 0x62, 0x0e, 0x43, 0xef, + 0x42, 0x89, 0xfd, 0xb8, 0xe1, 0x7b, 0xed, 0x8c, 0x3e, 0x4d, 0x48, 0x78, 0x5f, 0x92, 0xad, 0xce, + 0x9e, 0xf4, 0xca, 0x25, 0xf5, 0x17, 0x47, 0x0c, 0xcd, 0x3f, 0x33, 0x60, 0x5e, 0xfb, 0xb8, 0x2d, + 0x3b, 0x08, 0xd1, 0x17, 0xfb, 0x06, 0xcf, 0xea, 0x70, 0x83, 0x87, 0xd6, 0x66, 0x43, 0x67, 0x41, + 0x7c, 0x69, 0x51, 0x96, 0x68, 0x03, 0xc7, 0x85, 0x82, 0x1d, 0x92, 0x76, 0xb0, 0x9c, 0x7b, 0x2e, + 0xff, 0xe2, 0xf4, 0xcb, 0x9b, 0x99, 0x75, 0x63, 0xd4, 0xbe, 0x9b, 0x94, 0x3e, 0xe6, 0x6c, 0xcc, + 0xdf, 0x9a, 0x88, 0x7d, 0x21, 0x1d, 0x51, 0xc8, 0x83, 0xa9, 0x36, 0x09, 0x7d, 0xbb, 0xc1, 0xe7, + 0xd5, 0xf4, 0xcb, 0x1b, 0xe3, 0x49, 0xb1, 0xcd, 0x88, 0x45, 0x8b, 0x25, 0xff, 0x1f, 0x60, 0xc9, + 0x05, 0xed, 0xc3, 0x84, 0xe5, 0xb7, 0xe4, 0x37, 0xdf, 0xc8, 0xa6, 0x7f, 0xa3, 0x31, 0x57, 0xf1, + 0x5b, 0x01, 0x66, 0x1c, 0xd0, 0x1a, 0x94, 0x42, 0xe2, 0xb7, 0x6d, 0xd7, 0x0a, 0xf9, 0xea, 0x5a, + 0xac, 0x2e, 0x0a, 0xb4, 0xd2, 0x8e, 0x04, 0xe0, 0x08, 0x07, 0x39, 0x30, 0xd9, 0xf4, 0x8f, 0x71, + 0xd7, 0x5d, 0x9e, 0xc8, 0xa2, 0x29, 0x36, 0x18, 0xad, 0x68, 0x32, 0xf1, 0xff, 0x58, 0xf0, 0x40, + 0xdf, 0x35, 0x60, 0xa9, 0x4d, 0xac, 0xa0, 0xeb, 0x13, 0xfa, 0x09, 0x98, 0x84, 0xc4, 0xa5, 0xab, + 0xe1, 0x72, 0x81, 0x31, 0xc7, 0xe3, 0xf6, 0x43, 0x3f, 0xe5, 0xea, 0xb3, 0x42, 0x94, 0xa5, 0x34, + 0x28, 0x4e, 0x95, 0xc6, 0xfc, 0x93, 0x09, 0x58, 0xec, 0x5b, 0x21, 0xd0, 0x2b, 0x50, 0xe8, 0xec, + 0x5b, 0x81, 0x9c, 0xf2, 0xd7, 0xe4, 0x78, 0xab, 0xd1, 0xc2, 0xc7, 0xbd, 0xf2, 0xac, 0xac, 0xc2, + 0x0a, 0x30, 0x47, 0xa6, 0x7b, 0x6a, 0x9b, 0x04, 0x81, 0xd5, 0x92, 0xeb, 0x80, 0x36, 0x4c, 0x58, + 0x31, 0x96, 0x70, 0xf4, 0x77, 0x0d, 0x98, 0xe5, 0x43, 0x06, 0x93, 0xa0, 0xeb, 0x84, 0x74, 0xad, + 0xa3, 0xcd, 0x72, 0x2b, 0x8b, 0xe1, 0xc9, 0x49, 0x56, 0x2f, 0x0b, 0xee, 0xb3, 0x7a, 0x69, 0x80, + 0xe3, 0x7c, 0xd1, 0x03, 0x28, 0x05, 0xa1, 0xe5, 0x87, 0xa4, 0x59, 0x09, 0xd9, 0xae, 0x36, 0xfd, + 0xf2, 0x4f, 0x0d, 0xb7, 0x08, 0xec, 0xd8, 0x6d, 0xc2, 0x17, 0x9c, 0xba, 0x24, 0x80, 0x23, 0x5a, + 0xe8, 0x5d, 0x00, 0xbf, 0xeb, 0xd6, 0xbb, 0xed, 0xb6, 0xe5, 0x1f, 0x8b, 0x1d, 0xfc, 0xe6, 0x78, + 0x9f, 0x87, 0x15, 0xbd, 0x68, 0xcf, 0x8a, 0xca, 0xb0, 0xc6, 0x0f, 0x7d, 0xd5, 0x80, 0x59, 0x3e, + 0x12, 0xa5, 0x04, 0x93, 0x19, 0x4b, 0xb0, 0x48, 0x9b, 0x76, 0x43, 0x67, 0x81, 0xe3, 0x1c, 0xcd, + 0xff, 0x18, 0xdf, 0x4f, 0xea, 0x21, 0xd5, 0xae, 0x5b, 0xc7, 0xe8, 0x0b, 0xf0, 0x74, 0xd0, 0x6d, + 0x34, 0x48, 0x10, 0xec, 0x75, 0x1d, 0xdc, 0x75, 0x6f, 0xda, 0x41, 0xe8, 0xf9, 0xc7, 0x5b, 0x76, + 0xdb, 0x0e, 0xd9, 0x88, 0x2b, 0x54, 0xaf, 0x9e, 0xf4, 0xca, 0x4f, 0xd7, 0x07, 0x21, 0xe1, 0xc1, + 0xf5, 0x91, 0x05, 0xcf, 0x74, 0xdd, 0xc1, 0xe4, 0xb9, 0xf6, 0x56, 0x3e, 0xe9, 0x95, 0x9f, 0xb9, + 0x37, 0x18, 0x0d, 0x9f, 0x46, 0xc3, 0xfc, 0xef, 0x06, 0x2c, 0xc8, 0xef, 0xda, 0x21, 0xed, 0x8e, + 0x43, 0x57, 0x97, 0x8b, 0x57, 0x44, 0xc2, 0x98, 0x22, 0x82, 0xb3, 0xd9, 0x4e, 0xa4, 0xfc, 0x83, + 0xb4, 0x11, 0xf3, 0xbf, 0x19, 0xb0, 0x94, 0x44, 0x7e, 0x02, 0x9b, 0x67, 0x10, 0xdf, 0x3c, 0x6f, + 0x67, 0xfb, 0xb5, 0x03, 0x76, 0xd0, 0xaf, 0x4f, 0xf4, 0x7f, 0xeb, 0xff, 0xef, 0xdb, 0x68, 0xb4, + 0x2b, 0xe6, 0x7f, 0x9c, 0xbb, 0xe2, 0xc4, 0x47, 0x6a, 0x57, 0xfc, 0x67, 0x13, 0x30, 0x53, 0x71, + 0x43, 0xbb, 0xb2, 0xb7, 0x67, 0xbb, 0x76, 0x78, 0x8c, 0xbe, 0x99, 0x83, 0xb5, 0x8e, 0x4f, 0xf6, + 0x88, 0xef, 0x93, 0xe6, 0x46, 0xd7, 0xb7, 0xdd, 0x56, 0xbd, 0xb1, 0x4f, 0x9a, 0x5d, 0xc7, 0x76, + 0x5b, 0x9b, 0x2d, 0xd7, 0x53, 0xc5, 0xd7, 0x1f, 0x91, 0x46, 0x97, 0x7d, 0x12, 0x9f, 0x14, 0xed, + 0xf1, 0x3e, 0xa9, 0x36, 0x1a, 0xd3, 0xea, 0xa7, 0x4f, 0x7a, 0xe5, 0xb5, 0x11, 0x2b, 0xe1, 0x51, + 0x3f, 0x0d, 0x7d, 0x23, 0x07, 0xab, 0x3e, 0xf9, 0x85, 0xae, 0x3d, 0x7c, 0x6b, 0xf0, 0x55, 0xcb, + 0x19, 0x73, 0xfb, 0x19, 0x89, 0x67, 0xf5, 0xe5, 0x93, 0x5e, 0x79, 0xc4, 0x3a, 0x78, 0xc4, 0xef, + 0x32, 0x6b, 0x30, 0x5d, 0xe9, 0xd8, 0x81, 0xfd, 0x88, 0x9e, 0x65, 0xc9, 0x10, 0x67, 0xa5, 0x32, + 0x14, 0xfc, 0xae, 0x43, 0xf8, 0xdc, 0x2e, 0x55, 0x4b, 0x74, 0x15, 0xc2, 0xb4, 0x00, 0xf3, 0x72, + 0xf3, 0x97, 0xe8, 0x8a, 0xcb, 0x48, 0x26, 0x4e, 0xc9, 0x0f, 0xa1, 0xe0, 0x53, 0x26, 0x62, 0x64, + 0x8d, 0x7b, 0xa0, 0x88, 0xa4, 0x16, 0x42, 0xd0, 0x9f, 0x98, 0xb3, 0x30, 0x7f, 0x37, 0x07, 0x97, + 0x2b, 0x9d, 0xce, 0x36, 0x09, 0xf6, 0x13, 0x52, 0xfc, 0x8a, 0x01, 0x73, 0x87, 0xb6, 0x1f, 0x76, + 0x2d, 0x47, 0xda, 0x36, 0xb8, 0x3c, 0xf5, 0x71, 0xe5, 0x61, 0xdc, 0xee, 0xc7, 0x48, 0x57, 0xd1, + 0x49, 0xaf, 0x3c, 0x17, 0x2f, 0xc3, 0x09, 0xf6, 0xe8, 0x1f, 0x1a, 0xb0, 0x20, 0x8a, 0x6e, 0x7b, + 0x4d, 0xa2, 0x1b, 0xc4, 0xee, 0x65, 0x29, 0x93, 0x22, 0xce, 0x2d, 0x27, 0xc9, 0x52, 0xdc, 0x27, + 0x84, 0xf9, 0x3f, 0x73, 0x70, 0x65, 0x00, 0x0d, 0xf4, 0x1b, 0x06, 0x2c, 0x71, 0x2b, 0x9a, 0x06, + 0xc2, 0x64, 0x4f, 0xb4, 0xe6, 0xcf, 0x66, 0x2d, 0x39, 0xa6, 0x53, 0x9c, 0xb8, 0x0d, 0x52, 0x5d, + 0xa6, 0xab, 0xe1, 0x7a, 0x0a, 0x6b, 0x9c, 0x2a, 0x10, 0x93, 0x94, 0xdb, 0xd5, 0x12, 0x92, 0xe6, + 0x9e, 0x88, 0xa4, 0xf5, 0x14, 0xd6, 0x38, 0x55, 0x20, 0xf3, 0x6f, 0xc3, 0x33, 0xa7, 0x90, 0x3b, + 0x7b, 0x72, 0x9a, 0x6f, 0xab, 0x51, 0x1f, 0x1f, 0x73, 0x43, 0xcc, 0x6b, 0x13, 0x26, 0xd9, 0xd4, + 0x91, 0x13, 0x1b, 0xe8, 0xf6, 0xc7, 0xe6, 0x54, 0x80, 0x05, 0xc4, 0xfc, 0x5d, 0x03, 0x8a, 0x23, + 0x98, 0x55, 0xca, 0x71, 0xb3, 0x4a, 0xa9, 0xcf, 0xa4, 0x12, 0xf6, 0x9b, 0x54, 0xde, 0x1c, 0xaf, + 0x37, 0x86, 0x31, 0xa5, 0xfc, 0xb9, 0x01, 0x8b, 0x7d, 0xa6, 0x17, 0xb4, 0x0f, 0x4b, 0x1d, 0xaf, + 0x29, 0xd5, 0xa6, 0x9b, 0x56, 0xb0, 0xcf, 0x60, 0xe2, 0xf3, 0x5e, 0xa1, 0x3d, 0x59, 0x4b, 0x81, + 0x3f, 0xee, 0x95, 0x97, 0x15, 0x91, 0x04, 0x02, 0x4e, 0xa5, 0x88, 0x3a, 0x50, 0xdc, 0xb3, 0x89, + 0xd3, 0x8c, 0x86, 0xe0, 0x98, 0x0a, 0xd2, 0x0d, 0x41, 0x8d, 0x5b, 0x1d, 0xe5, 0x3f, 0xac, 0xb8, + 0x98, 0x5f, 0x81, 0xb9, 0xb8, 0x0d, 0x7a, 0x88, 0xce, 0xbb, 0x0a, 0x79, 0xcb, 0x77, 0x45, 0xd7, + 0x4d, 0x0b, 0x84, 0x7c, 0x05, 0xdf, 0xc6, 0xb4, 0x1c, 0xbd, 0x04, 0xc5, 0xbd, 0xae, 0xe3, 0xd0, + 0x0a, 0xc2, 0x36, 0xac, 0xd4, 0xe1, 0x1b, 0xa2, 0x1c, 0x2b, 0x0c, 0xf3, 0x2f, 0x27, 0x60, 0xbe, + 0xea, 0x74, 0xc9, 0x9b, 0x3e, 0x21, 0xf2, 0x90, 0x5e, 0x81, 0xf9, 0x8e, 0x4f, 0x0e, 0x6d, 0x72, + 0x54, 0x27, 0x0e, 0x69, 0x84, 0x9e, 0x2f, 0xa4, 0xb9, 0x22, 0x08, 0xcd, 0xd7, 0xe2, 0x60, 0x9c, + 0xc4, 0x47, 0x6f, 0xc0, 0x9c, 0xd5, 0x08, 0xed, 0x43, 0xa2, 0x28, 0x70, 0x71, 0x3f, 0x26, 0x28, + 0xcc, 0x55, 0x62, 0x50, 0x9c, 0xc0, 0x46, 0x5f, 0x84, 0xe5, 0xa0, 0x61, 0x39, 0xe4, 0x5e, 0x47, + 0xb0, 0x5a, 0xdf, 0x27, 0x8d, 0x83, 0x9a, 0x67, 0xbb, 0xa1, 0x30, 0xc9, 0x3c, 0x27, 0x28, 0x2d, + 0xd7, 0x07, 0xe0, 0xe1, 0x81, 0x14, 0xd0, 0xbf, 0x31, 0xe0, 0x6a, 0xc7, 0x27, 0x35, 0xdf, 0x6b, + 0x7b, 0x74, 0xaf, 0xed, 0xb3, 0x53, 0x88, 0xf3, 0xfa, 0xfd, 0x31, 0x95, 0x0a, 0x5e, 0xd2, 0x6f, + 0x27, 0xfd, 0xf8, 0x49, 0xaf, 0x7c, 0xb5, 0x76, 0x9a, 0x00, 0xf8, 0x74, 0xf9, 0xd0, 0xbf, 0x35, + 0xe0, 0x5a, 0xc7, 0x0b, 0xc2, 0x53, 0x3e, 0xa1, 0x70, 0xa1, 0x9f, 0x60, 0x9e, 0xf4, 0xca, 0xd7, + 0x6a, 0xa7, 0x4a, 0x80, 0xcf, 0x90, 0xd0, 0x3c, 0x99, 0x86, 0x45, 0x6d, 0xec, 0x89, 0x43, 0xfc, + 0xeb, 0x30, 0x2b, 0x07, 0x43, 0xa4, 0x04, 0x94, 0x22, 0xa3, 0x4b, 0x45, 0x07, 0xe2, 0x38, 0x2e, + 0x1d, 0x77, 0x6a, 0x28, 0xf2, 0xda, 0x89, 0x71, 0x57, 0x8b, 0x41, 0x71, 0x02, 0x1b, 0x6d, 0xc2, + 0x25, 0x51, 0x82, 0x49, 0xc7, 0xb1, 0x1b, 0xd6, 0xba, 0xd7, 0x15, 0x43, 0xae, 0x50, 0xbd, 0x72, + 0xd2, 0x2b, 0x5f, 0xaa, 0xf5, 0x83, 0x71, 0x5a, 0x1d, 0xb4, 0x05, 0x4b, 0x56, 0x37, 0xf4, 0xd4, + 0xf7, 0x5f, 0x77, 0xe9, 0xbe, 0xd2, 0x64, 0x43, 0xab, 0xc8, 0x37, 0xa0, 0x4a, 0x0a, 0x1c, 0xa7, + 0xd6, 0x42, 0xb5, 0x04, 0xb5, 0x3a, 0x69, 0x78, 0x6e, 0x93, 0xf7, 0x72, 0x21, 0x3a, 0x8a, 0x54, + 0x52, 0x70, 0x70, 0x6a, 0x4d, 0xe4, 0xc0, 0x5c, 0xdb, 0x7a, 0x74, 0xcf, 0xb5, 0x0e, 0x2d, 0xdb, + 0xa1, 0x4c, 0x84, 0x21, 0x67, 0xb0, 0x75, 0xa1, 0x1b, 0xda, 0xce, 0x2a, 0xbf, 0xd3, 0x5c, 0xdd, + 0x74, 0xc3, 0x3b, 0x7e, 0x3d, 0xa4, 0x2a, 0x2b, 0x57, 0xa5, 0xb6, 0x63, 0xb4, 0x70, 0x82, 0x36, + 0xba, 0x03, 0x97, 0xd9, 0x74, 0xdc, 0xf0, 0x8e, 0xdc, 0x0d, 0xe2, 0x58, 0xc7, 0xf2, 0x03, 0xa6, + 0xd8, 0x07, 0x3c, 0x7d, 0xd2, 0x2b, 0x5f, 0xae, 0xa7, 0x21, 0xe0, 0xf4, 0x7a, 0xc8, 0x82, 0x67, + 0xe2, 0x00, 0x4c, 0x0e, 0xed, 0xc0, 0xf6, 0x5c, 0x6e, 0x8e, 0x29, 0x46, 0xe6, 0x98, 0xfa, 0x60, + 0x34, 0x7c, 0x1a, 0x0d, 0xf4, 0x8f, 0x0d, 0x58, 0x4a, 0x9b, 0x86, 0xcb, 0xa5, 0x2c, 0x6e, 0x6c, + 0x12, 0x53, 0x8b, 0x8f, 0x88, 0xd4, 0x45, 0x21, 0x55, 0x08, 0xf4, 0x9e, 0x01, 0x33, 0x96, 0x76, + 0x94, 0x5c, 0x06, 0x26, 0xd5, 0xad, 0x71, 0x0d, 0x1a, 0x11, 0xc5, 0xea, 0xc2, 0x49, 0xaf, 0x1c, + 0x3b, 0xae, 0xe2, 0x18, 0x47, 0xf4, 0x4f, 0x0d, 0xb8, 0x9c, 0x3a, 0xc7, 0x97, 0xa7, 0x2f, 0xa2, + 0x85, 0xd8, 0x20, 0x49, 0x5f, 0x73, 0xd2, 0xc5, 0x40, 0x1f, 0x18, 0x6a, 0x2b, 0xdb, 0x96, 0x26, + 0xa5, 0x19, 0x26, 0xda, 0xdd, 0x31, 0x4f, 0xcf, 0x91, 0xfa, 0x20, 0x09, 0x57, 0x2f, 0x69, 0x3b, + 0xa3, 0x2c, 0xc4, 0x49, 0xf6, 0xe8, 0x5b, 0x86, 0xdc, 0x1a, 0x95, 0x44, 0xb3, 0x17, 0x25, 0x11, + 0x8a, 0x76, 0x5a, 0x25, 0x50, 0x82, 0x39, 0xfa, 0x39, 0x58, 0xb1, 0x76, 0x3d, 0x3f, 0x4c, 0x9d, + 0x7c, 0xcb, 0x73, 0x6c, 0x1a, 0x5d, 0x3b, 0xe9, 0x95, 0x57, 0x2a, 0x03, 0xb1, 0xf0, 0x29, 0x14, + 0xcc, 0xdf, 0x2c, 0xc0, 0x0c, 0x3f, 0x12, 0x88, 0xad, 0xeb, 0xb7, 0x0d, 0x78, 0xb6, 0xd1, 0xf5, + 0x7d, 0xe2, 0x86, 0xf5, 0x90, 0x74, 0xfa, 0x37, 0x2e, 0xe3, 0x42, 0x37, 0xae, 0xe7, 0x4e, 0x7a, + 0xe5, 0x67, 0xd7, 0x4f, 0xe1, 0x8f, 0x4f, 0x95, 0x0e, 0xfd, 0x07, 0x03, 0x4c, 0x81, 0x50, 0xb5, + 0x1a, 0x07, 0x2d, 0xdf, 0xeb, 0xba, 0xcd, 0xfe, 0x8f, 0xc8, 0x5d, 0xe8, 0x47, 0xbc, 0x70, 0xd2, + 0x2b, 0x9b, 0xeb, 0x67, 0x4a, 0x81, 0x87, 0x90, 0x14, 0xbd, 0x09, 0x8b, 0x02, 0xeb, 0xfa, 0xa3, + 0x0e, 0xf1, 0x6d, 0xaa, 0x7c, 0x0b, 0xc5, 0x31, 0xf2, 0xd3, 0x48, 0x22, 0xe0, 0xfe, 0x3a, 0x28, + 0x80, 0xa9, 0x23, 0x62, 0xb7, 0xf6, 0x43, 0xa9, 0x3e, 0x8d, 0xe9, 0x9c, 0x21, 0xcc, 0x03, 0x0f, + 0x38, 0xcd, 0xea, 0xf4, 0x49, 0xaf, 0x3c, 0x25, 0xfe, 0x60, 0xc9, 0x09, 0xdd, 0x86, 0x39, 0x7e, + 0x60, 0xab, 0xd9, 0x6e, 0xab, 0xe6, 0xb9, 0xdc, 0xa5, 0xa1, 0x54, 0x7d, 0x41, 0x6e, 0xf8, 0xf5, + 0x18, 0xf4, 0x71, 0xaf, 0x3c, 0x23, 0x7f, 0xef, 0x1c, 0x77, 0x08, 0x4e, 0xd4, 0x36, 0xff, 0x60, + 0x12, 0x40, 0x0e, 0x57, 0xd2, 0x41, 0x3f, 0x0d, 0xa5, 0x80, 0x84, 0x9c, 0xab, 0xb8, 0x41, 0xe0, + 0x17, 0x33, 0xb2, 0x10, 0x47, 0x70, 0x74, 0x00, 0x85, 0x8e, 0xd5, 0x0d, 0x88, 0xe8, 0xfc, 0x5b, + 0x99, 0x74, 0x7e, 0x8d, 0x52, 0xe4, 0x27, 0x34, 0xf6, 0x13, 0x73, 0x1e, 0xe8, 0x6b, 0x06, 0x00, + 0x89, 0x77, 0xd8, 0xd8, 0x96, 0x12, 0xc1, 0x32, 0xea, 0x53, 0xda, 0x06, 0xd5, 0xb9, 0x93, 0x5e, + 0x19, 0xb4, 0xae, 0xd7, 0xd8, 0xa2, 0x23, 0x28, 0x5a, 0x72, 0xcd, 0x9f, 0xb8, 0x88, 0x35, 0x9f, + 0x1d, 0x9c, 0xd4, 0xa0, 0x55, 0xcc, 0xd0, 0x37, 0x0c, 0x98, 0x0b, 0x48, 0x28, 0xba, 0x8a, 0xae, + 0x3c, 0x42, 0xe1, 0x1d, 0x73, 0xd0, 0xd5, 0x63, 0x34, 0xf9, 0x0a, 0x1a, 0x2f, 0xc3, 0x09, 0xbe, + 0x52, 0x94, 0x9b, 0xc4, 0x6a, 0x12, 0x9f, 0x9d, 0xcb, 0x85, 0x26, 0x35, 0xbe, 0x28, 0x1a, 0x4d, + 0x25, 0x8a, 0x56, 0x86, 0x13, 0x7c, 0xa5, 0x28, 0xdb, 0xb6, 0xef, 0x7b, 0x42, 0x94, 0x62, 0x46, + 0xa2, 0x68, 0x34, 0x95, 0x28, 0x5a, 0x19, 0x4e, 0xf0, 0x35, 0xbf, 0x33, 0x0b, 0x73, 0x72, 0x22, + 0x45, 0x9a, 0x3d, 0x37, 0x03, 0x0d, 0xd0, 0xec, 0xd7, 0x75, 0x20, 0x8e, 0xe3, 0xd2, 0xca, 0x7c, + 0xaa, 0xc6, 0x15, 0x7b, 0x55, 0xb9, 0xae, 0x03, 0x71, 0x1c, 0x17, 0xb5, 0xa1, 0x10, 0x84, 0xa4, + 0x23, 0x2f, 0x83, 0xc7, 0xbc, 0xab, 0x8c, 0xd6, 0x87, 0xe8, 0xba, 0x87, 0xfe, 0x0b, 0x30, 0xe7, + 0xc2, 0x2c, 0x99, 0x61, 0xcc, 0xb8, 0x29, 0x26, 0x47, 0x36, 0xf3, 0x33, 0x6e, 0x37, 0xe5, 0xbd, + 0x11, 0x2f, 0xc3, 0x09, 0xf6, 0x29, 0xca, 0x7e, 0xe1, 0x02, 0x95, 0xfd, 0xcf, 0x43, 0xb1, 0x6d, + 0x3d, 0xaa, 0x77, 0xfd, 0xd6, 0xf9, 0x0f, 0x15, 0xc2, 0x4f, 0x8b, 0x53, 0xc1, 0x8a, 0x1e, 0xfa, + 0xaa, 0xa1, 0x2d, 0x39, 0x53, 0x8c, 0xf8, 0x83, 0x6c, 0x97, 0x1c, 0xb5, 0x57, 0x0e, 0x5c, 0x7c, + 0xfa, 0x54, 0xef, 0xe2, 0x13, 0x57, 0xbd, 0xa9, 0x1a, 0xc9, 0x27, 0x88, 0x52, 0x23, 0x4b, 0x17, + 0xaa, 0x46, 0xae, 0xc7, 0x98, 0xe1, 0x04, 0x73, 0x26, 0x0f, 0x9f, 0x73, 0x4a, 0x1e, 0xb8, 0x50, + 0x79, 0xea, 0x31, 0x66, 0x38, 0xc1, 0x7c, 0xf0, 0x79, 0x73, 0xfa, 0x62, 0xce, 0x9b, 0x33, 0x19, + 0x9c, 0x37, 0x4f, 0x57, 0xc5, 0x67, 0xc7, 0x55, 0xc5, 0xd1, 0x2d, 0x40, 0xcd, 0x63, 0xd7, 0x6a, + 0xdb, 0x0d, 0xb1, 0x58, 0xb2, 0x6d, 0x73, 0x8e, 0xd9, 0x23, 0x56, 0xc4, 0x42, 0x86, 0x36, 0xfa, + 0x30, 0x70, 0x4a, 0x2d, 0x14, 0x42, 0xb1, 0x23, 0x35, 0xae, 0xf9, 0x2c, 0x46, 0xbf, 0xd4, 0xc0, + 0xb8, 0xbf, 0x00, 0x9d, 0x78, 0xb2, 0x04, 0x2b, 0x4e, 0x68, 0x0b, 0x96, 0xda, 0xb6, 0x5b, 0xf3, + 0x9a, 0x41, 0x8d, 0xf8, 0xc2, 0xda, 0x52, 0x27, 0xe1, 0xf2, 0x02, 0x6b, 0x1b, 0x76, 0x82, 0xde, + 0x4e, 0x81, 0xe3, 0xd4, 0x5a, 0xe6, 0xff, 0x36, 0x60, 0x61, 0xdd, 0xf1, 0xba, 0xcd, 0x07, 0x56, + 0xd8, 0xd8, 0xe7, 0x57, 0xe5, 0xe8, 0x0d, 0x28, 0xda, 0x6e, 0x48, 0xfc, 0x43, 0xcb, 0x11, 0xfb, + 0x93, 0x29, 0xcd, 0xa7, 0x9b, 0xa2, 0xfc, 0x71, 0xaf, 0x3c, 0xb7, 0xd1, 0xf5, 0x99, 0x0f, 0x2a, + 0x5f, 0xad, 0xb0, 0xaa, 0x83, 0xbe, 0x63, 0xc0, 0x22, 0xbf, 0x6c, 0xdf, 0xb0, 0x42, 0xeb, 0x6e, + 0x97, 0xf8, 0x36, 0x91, 0xd7, 0xed, 0x63, 0x2e, 0x54, 0x49, 0x59, 0x25, 0x83, 0xe3, 0x48, 0x51, + 0xdf, 0x4e, 0x72, 0xc6, 0xfd, 0xc2, 0x98, 0xbf, 0x9a, 0x87, 0xa7, 0x07, 0xd2, 0x42, 0x2b, 0x90, + 0xb3, 0x9b, 0xe2, 0xd3, 0x41, 0xd0, 0xcd, 0x6d, 0x36, 0x71, 0xce, 0x6e, 0xa2, 0x55, 0xa6, 0x73, + 0xfa, 0x24, 0x08, 0xe4, 0xcd, 0x6b, 0x49, 0xa9, 0x87, 0xa2, 0x14, 0x6b, 0x18, 0xa8, 0x0c, 0x05, + 0xc7, 0xda, 0x25, 0x8e, 0x38, 0x4f, 0x30, 0x2d, 0x76, 0x8b, 0x16, 0x60, 0x5e, 0x8e, 0x7e, 0xc9, + 0x00, 0xe0, 0x02, 0xd2, 0xd3, 0x88, 0xd8, 0x25, 0x71, 0xb6, 0xcd, 0x44, 0x29, 0x73, 0x29, 0xa3, + 0xff, 0x58, 0xe3, 0x8a, 0x76, 0x60, 0x92, 0x2a, 0xb4, 0x5e, 0xf3, 0xdc, 0x9b, 0x22, 0xbb, 0x92, + 0xa9, 0x31, 0x1a, 0x58, 0xd0, 0xa2, 0x6d, 0xe5, 0x93, 0xb0, 0xeb, 0xbb, 0xb4, 0x69, 0xd9, 0x36, + 0x58, 0xe4, 0x52, 0x60, 0x55, 0x8a, 0x35, 0x0c, 0xf3, 0x5f, 0xe7, 0x60, 0x29, 0x4d, 0x74, 0xba, + 0xdb, 0x4c, 0x72, 0x69, 0xc5, 0xd1, 0xf8, 0x73, 0xd9, 0xb7, 0x8f, 0xf0, 0x1b, 0x51, 0xde, 0x15, + 0xc2, 0xb3, 0x4d, 0xf0, 0x45, 0x9f, 0x53, 0x2d, 0x94, 0x3b, 0x67, 0x0b, 0x29, 0xca, 0x89, 0x56, + 0x7a, 0x0e, 0x26, 0x02, 0xda, 0xf3, 0xf9, 0xf8, 0x75, 0x07, 0xeb, 0x23, 0x06, 0xa1, 0x18, 0x5d, + 0xd7, 0x0e, 0x85, 0x63, 0xb8, 0xc2, 0xb8, 0xe7, 0xda, 0x21, 0x66, 0x10, 0xf3, 0xdb, 0x39, 0x58, + 0x19, 0xfc, 0x51, 0xe8, 0xdb, 0x06, 0x40, 0x93, 0x1e, 0x57, 0xe8, 0x90, 0x94, 0x7e, 0x36, 0xd6, + 0x45, 0xb5, 0xe1, 0x86, 0xe4, 0x14, 0x39, 0x5d, 0xa9, 0xa2, 0x00, 0x6b, 0x82, 0xa0, 0x97, 0xe5, + 0xd0, 0x67, 0x57, 0x35, 0x7c, 0x32, 0xa9, 0x3a, 0xdb, 0x0a, 0x82, 0x35, 0x2c, 0x7a, 0x1e, 0x75, + 0xad, 0x36, 0x09, 0x3a, 0x96, 0xf2, 0xfc, 0x67, 0xe7, 0xd1, 0xdb, 0xb2, 0x10, 0x47, 0x70, 0xd3, + 0x81, 0xe7, 0x87, 0x90, 0x33, 0x23, 0x2f, 0x6c, 0xf3, 0x7f, 0x19, 0x70, 0x65, 0xdd, 0xe9, 0x06, + 0x21, 0xf1, 0xff, 0xca, 0xf8, 0xb0, 0xfd, 0x1f, 0x03, 0x9e, 0x19, 0xf0, 0xcd, 0x4f, 0xc0, 0x95, + 0xed, 0x9d, 0xb8, 0x2b, 0xdb, 0xbd, 0x71, 0x87, 0x74, 0xea, 0x77, 0x0c, 0xf0, 0x68, 0x0b, 0x61, + 0x96, 0xae, 0x5a, 0x4d, 0xaf, 0x95, 0xd1, 0xbe, 0xf9, 0x3c, 0x14, 0x7e, 0x81, 0xee, 0x3f, 0xc9, + 0x31, 0xc6, 0x36, 0x25, 0xcc, 0x61, 0xe6, 0x67, 0x41, 0xf8, 0x7d, 0x25, 0x26, 0x8f, 0x31, 0xcc, + 0xe4, 0x31, 0xff, 0x53, 0x0e, 0x34, 0x3b, 0xc6, 0x13, 0x18, 0x94, 0x6e, 0x6c, 0x50, 0x8e, 0x79, + 0x06, 0xd7, 0xac, 0x32, 0x83, 0x02, 0x3c, 0x0e, 0x13, 0x01, 0x1e, 0xb7, 0x33, 0xe3, 0x78, 0x7a, + 0x7c, 0xc7, 0x0f, 0x0d, 0x78, 0x26, 0x42, 0xee, 0x37, 0x31, 0x9e, 0xbd, 0xc2, 0xbc, 0x0a, 0xd3, + 0x56, 0x54, 0x4d, 0x8c, 0x01, 0x15, 0xd3, 0xa4, 0x51, 0xc4, 0x3a, 0x5e, 0xe4, 0x4e, 0x9e, 0x3f, + 0xa7, 0x3b, 0xf9, 0xc4, 0xe9, 0xee, 0xe4, 0xe6, 0x5f, 0xe4, 0xe0, 0x6a, 0xff, 0x97, 0xc9, 0xb9, + 0x31, 0xdc, 0x7d, 0xfd, 0x6b, 0x30, 0x13, 0x8a, 0x0a, 0xda, 0x4a, 0xaf, 0x22, 0xf2, 0x76, 0x34, + 0x18, 0x8e, 0x61, 0xd2, 0x9a, 0x0d, 0x3e, 0x2b, 0xeb, 0x0d, 0xaf, 0x23, 0x83, 0x11, 0x54, 0xcd, + 0x75, 0x0d, 0x86, 0x63, 0x98, 0xca, 0xcd, 0x73, 0xe2, 0xc2, 0xdd, 0x3c, 0xeb, 0x70, 0x59, 0x3a, + 0xb6, 0xdd, 0xf0, 0xfc, 0x75, 0xaf, 0xdd, 0x71, 0x88, 0x08, 0x47, 0xa0, 0xc2, 0x5e, 0x15, 0x55, + 0x2e, 0xe3, 0x34, 0x24, 0x9c, 0x5e, 0xd7, 0xfc, 0x61, 0x1e, 0x2e, 0x45, 0xcd, 0xbe, 0xee, 0xb9, + 0x4d, 0x9b, 0xb9, 0x07, 0xbe, 0x0e, 0x13, 0xe1, 0x71, 0x47, 0x36, 0xf6, 0xdf, 0x90, 0xe2, 0xec, + 0x1c, 0x77, 0x68, 0x6f, 0x5f, 0x49, 0xa9, 0xc2, 0x8c, 0xbc, 0xac, 0x12, 0xda, 0x52, 0xb3, 0x83, + 0xf7, 0xc0, 0x2b, 0xf1, 0xd1, 0xfc, 0xb8, 0x57, 0x4e, 0x09, 0x48, 0x5d, 0x55, 0x94, 0xe2, 0x63, + 0x1e, 0x3d, 0x84, 0x39, 0xc7, 0x0a, 0xc2, 0x7b, 0x9d, 0xa6, 0x15, 0x92, 0x1d, 0x5b, 0x38, 0x5b, + 0x8c, 0xe6, 0xe3, 0xaf, 0x6e, 0xa5, 0xb7, 0x62, 0x94, 0x70, 0x82, 0x32, 0x3a, 0x04, 0x44, 0x4b, + 0x76, 0x7c, 0xcb, 0x0d, 0xf8, 0x57, 0x51, 0x7e, 0xa3, 0xc7, 0x14, 0xa8, 0x43, 0xde, 0x56, 0x1f, + 0x35, 0x9c, 0xc2, 0x01, 0xbd, 0x00, 0x93, 0x3e, 0xb1, 0x02, 0xd1, 0x99, 0xa5, 0x68, 0xfe, 0x63, + 0x56, 0x8a, 0x05, 0x54, 0x9f, 0x50, 0x93, 0x67, 0x4c, 0xa8, 0x3f, 0x35, 0x60, 0x2e, 0xea, 0xa6, + 0x27, 0xb0, 0x49, 0xb6, 0xe3, 0x9b, 0xe4, 0xcd, 0xac, 0x96, 0xc4, 0x01, 0xfb, 0xe2, 0xbf, 0x9b, + 0xd4, 0xbf, 0x8f, 0xf9, 0x78, 0x7f, 0x19, 0x4a, 0x72, 0x56, 0x4b, 0xed, 0x73, 0xcc, 0xb3, 0x72, + 0x4c, 0x2f, 0xd1, 0x62, 0x93, 0x04, 0x13, 0x1c, 0xf1, 0xa3, 0xdb, 0x72, 0x53, 0x6c, 0xb9, 0x62, + 0xd8, 0xab, 0x6d, 0x59, 0x6e, 0xc5, 0x69, 0xdb, 0xb2, 0xac, 0x83, 0xee, 0xc1, 0x95, 0x8e, 0xef, + 0xb1, 0x78, 0xd5, 0x0d, 0x62, 0x35, 0x1d, 0xdb, 0x25, 0xd2, 0x20, 0xc1, 0x9d, 0x22, 0x9e, 0x39, + 0xe9, 0x95, 0xaf, 0xd4, 0xd2, 0x51, 0xf0, 0xa0, 0xba, 0xf1, 0x18, 0xab, 0x89, 0x21, 0x62, 0xac, + 0xfe, 0x9e, 0x32, 0xfb, 0x91, 0x40, 0x44, 0x3a, 0x7d, 0x21, 0xab, 0xae, 0x4c, 0x59, 0xd6, 0xa3, + 0x21, 0x55, 0x11, 0x4c, 0xb1, 0x62, 0x3f, 0xd8, 0xb6, 0x34, 0x79, 0x4e, 0xdb, 0x52, 0xe4, 0x2a, + 0x3f, 0xf5, 0xe3, 0x74, 0x95, 0x2f, 0x7e, 0xa4, 0x5c, 0xe5, 0xdf, 0x2f, 0xc0, 0x42, 0x52, 0x03, + 0xb9, 0xf8, 0xf8, 0xb1, 0x7f, 0x60, 0xc0, 0x82, 0x9c, 0x3d, 0x9c, 0x27, 0x91, 0xb7, 0x06, 0x5b, + 0x19, 0x4d, 0x5a, 0xae, 0x4b, 0xa9, 0x08, 0xe7, 0x9d, 0x04, 0x37, 0xdc, 0xc7, 0x1f, 0xbd, 0x0d, + 0xd3, 0xca, 0xb8, 0x7e, 0xae, 0x60, 0xb2, 0x79, 0xa6, 0x45, 0x45, 0x24, 0xb0, 0x4e, 0x0f, 0xbd, + 0x6f, 0x00, 0x34, 0xe4, 0x36, 0x27, 0x67, 0xd7, 0xdd, 0xac, 0x66, 0x97, 0xda, 0x40, 0x23, 0x65, + 0x59, 0x15, 0x05, 0x58, 0x63, 0x8c, 0x7e, 0x95, 0x99, 0xd5, 0x95, 0x76, 0x47, 0xe7, 0x53, 0x7e, + 0x7c, 0x37, 0xe0, 0x53, 0x14, 0xd3, 0x48, 0x95, 0xd2, 0x40, 0x01, 0x8e, 0x09, 0x61, 0xbe, 0x0e, + 0xca, 0x71, 0x93, 0x2e, 0x5b, 0xcc, 0x75, 0xb3, 0x66, 0x85, 0xfb, 0x62, 0x08, 0xaa, 0x65, 0xeb, + 0x86, 0x04, 0xe0, 0x08, 0xc7, 0xfc, 0x12, 0xcc, 0xbd, 0xe9, 0x5b, 0x9d, 0x7d, 0x9b, 0x99, 0xaf, + 0xe9, 0x39, 0xe9, 0x27, 0x61, 0xca, 0x6a, 0x36, 0xd3, 0xf2, 0x03, 0x54, 0x78, 0x31, 0x96, 0xf0, + 0xe1, 0x8e, 0x44, 0x7f, 0x60, 0x00, 0x8a, 0xae, 0x00, 0x6d, 0xb7, 0xb5, 0x4d, 0x4f, 0xfb, 0xf4, + 0x7c, 0xb4, 0xcf, 0x4a, 0xd3, 0xce, 0x47, 0x37, 0x15, 0x04, 0x6b, 0x58, 0xe8, 0x5d, 0x98, 0xe6, + 0xff, 0xee, 0xab, 0xc3, 0xfe, 0xd8, 0xc1, 0x00, 0x7c, 0x43, 0x61, 0x32, 0xf1, 0x51, 0x78, 0x33, + 0xe2, 0x80, 0x75, 0x76, 0xb4, 0xa9, 0x36, 0xdd, 0x3d, 0xa7, 0xfb, 0xa8, 0xb9, 0x1b, 0x35, 0x55, + 0xc7, 0xf7, 0xf6, 0x6c, 0x87, 0x24, 0x9b, 0xaa, 0xc6, 0x8b, 0xb1, 0x84, 0x0f, 0xd7, 0x54, 0xbf, + 0x67, 0xc0, 0xd2, 0x66, 0x10, 0xda, 0xde, 0x06, 0x09, 0x42, 0xba, 0xad, 0xd0, 0xc5, 0xa7, 0xeb, + 0x0c, 0xe3, 0x83, 0xbd, 0x01, 0x0b, 0xe2, 0x3a, 0xb2, 0xbb, 0x1b, 0x90, 0x50, 0xd3, 0xe3, 0xd5, + 0x3c, 0x5e, 0x4f, 0xc0, 0x71, 0x5f, 0x0d, 0x4a, 0x45, 0xdc, 0x4b, 0x46, 0x54, 0xf2, 0x71, 0x2a, + 0xf5, 0x04, 0x1c, 0xf7, 0xd5, 0x30, 0x7f, 0x90, 0x87, 0x4b, 0xec, 0x33, 0x12, 0xf1, 0x13, 0xdf, + 0x1a, 0x14, 0x3f, 0x31, 0xe6, 0x54, 0x66, 0xbc, 0xce, 0x11, 0x3d, 0xf1, 0xf7, 0x0d, 0x98, 0x6f, + 0xc6, 0x5b, 0x3a, 0x1b, 0xf3, 0x4c, 0x5a, 0x1f, 0x72, 0xef, 0xab, 0x44, 0x21, 0x4e, 0xf2, 0x47, + 0xbf, 0x66, 0xc0, 0x7c, 0x5c, 0x4c, 0xb9, 0xba, 0x5f, 0x40, 0x23, 0x29, 0x77, 0xe9, 0x78, 0x79, + 0x80, 0x93, 0x22, 0x98, 0x7f, 0x98, 0x13, 0x5d, 0x7a, 0x11, 0xc1, 0x01, 0xe8, 0x08, 0x4a, 0xa1, + 0x13, 0xf0, 0x42, 0xf1, 0xb5, 0x63, 0x9e, 0x08, 0x77, 0xb6, 0xea, 0xdc, 0x13, 0x20, 0x52, 0xda, + 0x44, 0x09, 0x55, 0x3e, 0x25, 0x2f, 0xc6, 0xb8, 0xd1, 0x11, 0x8c, 0x33, 0x39, 0x8a, 0xee, 0xac, + 0xd7, 0x92, 0x8c, 0x45, 0x09, 0x65, 0x2c, 0x79, 0x99, 0xdf, 0x33, 0xa0, 0x74, 0xcb, 0x93, 0xeb, + 0xc8, 0xcf, 0x65, 0x60, 0xe8, 0x51, 0xfa, 0xa0, 0xba, 0x71, 0x8c, 0x8e, 0x18, 0x6f, 0xc4, 0xcc, + 0x3c, 0xcf, 0x6a, 0xb4, 0x57, 0x59, 0xee, 0x23, 0x4a, 0xea, 0x96, 0xb7, 0x3b, 0xd0, 0x8a, 0xf8, + 0xeb, 0x05, 0x98, 0x7d, 0xcb, 0x3a, 0x26, 0x6e, 0x68, 0x8d, 0xbe, 0x49, 0xbc, 0x0a, 0xd3, 0x56, + 0x87, 0x5d, 0x69, 0x69, 0x3a, 0x7e, 0x64, 0x39, 0x89, 0x40, 0x58, 0xc7, 0x8b, 0x16, 0x34, 0x9e, + 0x8a, 0x25, 0x6d, 0x29, 0x5a, 0x4f, 0xc0, 0x71, 0x5f, 0x0d, 0x74, 0x0b, 0x90, 0x08, 0x2c, 0xad, + 0x34, 0x1a, 0x5e, 0xd7, 0xe5, 0x4b, 0x1a, 0x37, 0xaa, 0xa8, 0xc3, 0xe6, 0x76, 0x1f, 0x06, 0x4e, + 0xa9, 0x85, 0xbe, 0x08, 0xcb, 0x0d, 0x46, 0x59, 0x1c, 0x3d, 0x74, 0x8a, 0xfc, 0xf8, 0xa9, 0x5c, + 0xfe, 0xd7, 0x07, 0xe0, 0xe1, 0x81, 0x14, 0xa8, 0xa4, 0x41, 0xe8, 0xf9, 0x56, 0x8b, 0xe8, 0x74, + 0x27, 0xe3, 0x92, 0xd6, 0xfb, 0x30, 0x70, 0x4a, 0x2d, 0xf4, 0x15, 0x28, 0x85, 0xfb, 0x3e, 0x09, + 0xf6, 0x3d, 0xa7, 0x29, 0x5c, 0x10, 0xc6, 0xb4, 0xb4, 0x89, 0xde, 0xdf, 0x91, 0x54, 0xb5, 0xe1, + 0x2d, 0x8b, 0x70, 0xc4, 0x13, 0xf9, 0x30, 0x19, 0x34, 0xbc, 0x0e, 0x09, 0x84, 0xca, 0x7e, 0x2b, + 0x13, 0xee, 0xcc, 0x72, 0xa4, 0xd9, 0xf8, 0x18, 0x07, 0x2c, 0x38, 0x99, 0xbf, 0x9f, 0x83, 0x19, + 0x1d, 0x71, 0x88, 0xb5, 0xe9, 0x6b, 0x06, 0xcc, 0x34, 0x3c, 0x37, 0xf4, 0x3d, 0x87, 0xdb, 0xaf, + 0xb2, 0xd1, 0x28, 0x28, 0xa9, 0x0d, 0x12, 0x5a, 0xb6, 0xa3, 0x99, 0xc2, 0x34, 0x36, 0x38, 0xc6, + 0x14, 0x7d, 0xd3, 0x80, 0xf9, 0xc8, 0x63, 0x2d, 0x32, 0xa4, 0x65, 0x2a, 0x88, 0x5a, 0xea, 0xaf, + 0xc7, 0x39, 0xe1, 0x24, 0x6b, 0x73, 0x17, 0x16, 0x92, 0xbd, 0x4d, 0x9b, 0xb2, 0x63, 0x89, 0xb9, + 0x9e, 0x8f, 0x9a, 0xb2, 0x66, 0x05, 0x01, 0x66, 0x10, 0xf4, 0x12, 0x14, 0xdb, 0x96, 0xdf, 0xb2, + 0x5d, 0xcb, 0x61, 0xad, 0x98, 0xd7, 0x16, 0x24, 0x51, 0x8e, 0x15, 0x86, 0xf9, 0x49, 0x98, 0xd9, + 0xb6, 0xdc, 0x16, 0x69, 0x8a, 0x75, 0xf8, 0xec, 0xf0, 0xb4, 0x1f, 0x4d, 0xc0, 0xb4, 0x76, 0x36, + 0xbb, 0xf8, 0x73, 0x56, 0x2c, 0x3b, 0x46, 0x3e, 0xc3, 0xec, 0x18, 0x9f, 0x07, 0xd8, 0xb3, 0x5d, + 0x3b, 0xd8, 0x3f, 0x67, 0xde, 0x0d, 0x76, 0x45, 0x7b, 0x43, 0x51, 0xc0, 0x1a, 0xb5, 0xe8, 0x1e, + 0xac, 0x70, 0x4a, 0x36, 0xa2, 0xf7, 0x0d, 0x6d, 0xbb, 0x99, 0xcc, 0xe2, 0xde, 0x5f, 0xeb, 0x98, + 0x55, 0xb9, 0xfd, 0x5c, 0x77, 0x43, 0xff, 0xf8, 0xd4, 0x5d, 0x69, 0x07, 0x8a, 0x3e, 0x09, 0xba, + 0x6d, 0x7a, 0x62, 0x9c, 0x1a, 0xb9, 0x19, 0x98, 0x07, 0x06, 0x16, 0xf5, 0xb1, 0xa2, 0xb4, 0xf2, + 0x3a, 0xcc, 0xc6, 0x44, 0x40, 0x0b, 0x90, 0x3f, 0x20, 0xc7, 0x7c, 0x9c, 0x60, 0xfa, 0x13, 0x2d, + 0xc5, 0x6e, 0x0b, 0x45, 0xb3, 0x7c, 0x26, 0xf7, 0x9a, 0x61, 0x7a, 0x90, 0x6a, 0x00, 0x38, 0xcf, + 0x65, 0x0e, 0xed, 0x0b, 0x47, 0x4b, 0xbc, 0xa1, 0xfa, 0x82, 0xfb, 0xd9, 0x70, 0x98, 0xf9, 0x17, + 0x93, 0x20, 0xae, 0xb2, 0x87, 0x58, 0xae, 0xf4, 0x1b, 0xac, 0xdc, 0x39, 0x6e, 0xb0, 0x6e, 0xc1, + 0x8c, 0xed, 0xda, 0xa1, 0x6d, 0x39, 0xcc, 0xb8, 0x23, 0xb6, 0x53, 0xe9, 0x88, 0x3c, 0xb3, 0xa9, + 0xc1, 0x52, 0xe8, 0xc4, 0xea, 0xa2, 0xbb, 0x50, 0x60, 0xfb, 0x8d, 0x18, 0xc0, 0xa3, 0xdf, 0xb7, + 0x33, 0x57, 0x0b, 0x1e, 0x9d, 0xc4, 0x29, 0xb1, 0xc3, 0x07, 0xcf, 0x3c, 0xa2, 0x8e, 0xdf, 0x62, + 0x1c, 0x47, 0x87, 0x8f, 0x04, 0x1c, 0xf7, 0xd5, 0xa0, 0x54, 0xf6, 0x2c, 0xdb, 0xe9, 0xfa, 0x24, + 0xa2, 0x32, 0x19, 0xa7, 0x72, 0x23, 0x01, 0xc7, 0x7d, 0x35, 0xd0, 0x1e, 0xcc, 0x88, 0x32, 0xee, + 0x3d, 0x35, 0x75, 0xce, 0xaf, 0x64, 0x5e, 0x72, 0x37, 0x34, 0x4a, 0x38, 0x46, 0x17, 0x75, 0x61, + 0xd1, 0x76, 0x1b, 0x9e, 0xdb, 0x70, 0xba, 0x81, 0x7d, 0x48, 0xa2, 0xd0, 0xa0, 0xf3, 0x30, 0xbb, + 0x7c, 0xd2, 0x2b, 0x2f, 0x6e, 0x26, 0xc9, 0xe1, 0x7e, 0x0e, 0xe8, 0xab, 0x06, 0x5c, 0x6e, 0x78, + 0x6e, 0xc0, 0x42, 0xf9, 0x0f, 0xc9, 0x75, 0xdf, 0xf7, 0x7c, 0xce, 0xbb, 0x74, 0x4e, 0xde, 0xcc, + 0xa6, 0xb8, 0x9e, 0x46, 0x12, 0xa7, 0x73, 0x42, 0xef, 0x40, 0xb1, 0xe3, 0x7b, 0x87, 0x76, 0x93, + 0xf8, 0xc2, 0x13, 0x6f, 0x2b, 0x8b, 0xd4, 0x22, 0x35, 0x41, 0x33, 0x5a, 0x7a, 0x64, 0x09, 0x56, + 0xfc, 0xcc, 0xff, 0x3b, 0x0d, 0x73, 0x71, 0x74, 0xf4, 0x8b, 0x00, 0x1d, 0xdf, 0x6b, 0x93, 0x70, + 0x9f, 0xa8, 0x10, 0x8f, 0xdb, 0xe3, 0x66, 0xb0, 0x90, 0xf4, 0xa4, 0xf7, 0x0a, 0x5d, 0x2e, 0xa2, + 0x52, 0xac, 0x71, 0x44, 0x3e, 0x4c, 0x1d, 0xf0, 0x6d, 0x57, 0x68, 0x21, 0x6f, 0x65, 0xa2, 0x33, + 0x09, 0xce, 0x2c, 0x36, 0x41, 0x14, 0x61, 0xc9, 0x08, 0xed, 0x42, 0xfe, 0x88, 0xec, 0x66, 0x13, + 0x3e, 0xfd, 0x80, 0x88, 0xd3, 0x4c, 0x75, 0xea, 0xa4, 0x57, 0xce, 0x3f, 0x20, 0xbb, 0x98, 0x12, + 0xa7, 0xdf, 0xd5, 0xe4, 0xf7, 0xf0, 0x62, 0xa9, 0x18, 0xf3, 0xbb, 0x62, 0x97, 0xfa, 0xfc, 0xbb, + 0x44, 0x11, 0x96, 0x8c, 0xd0, 0x3b, 0x50, 0x3a, 0xb2, 0x0e, 0xc9, 0x9e, 0xef, 0xb9, 0xa1, 0x70, + 0x99, 0x1a, 0xd3, 0xeb, 0xff, 0x81, 0x24, 0x27, 0xf8, 0xb2, 0xed, 0x5d, 0x15, 0xe2, 0x88, 0x1d, + 0x3a, 0x84, 0xa2, 0x4b, 0x8e, 0x30, 0x71, 0xec, 0x46, 0x36, 0x5e, 0xf6, 0xb7, 0x05, 0x35, 0xc1, + 0x99, 0xed, 0x7b, 0xb2, 0x0c, 0x2b, 0x5e, 0xb4, 0x2f, 0x1f, 0x7a, 0xbb, 0x62, 0xa1, 0x1a, 0xb3, + 0x2f, 0xd5, 0xc9, 0x94, 0xf7, 0xe5, 0x2d, 0x6f, 0x17, 0x53, 0xe2, 0x74, 0x8e, 0x34, 0x94, 0xbf, + 0x8e, 0x58, 0xa6, 0x6e, 0x67, 0xeb, 0xa7, 0xc4, 0xe7, 0x48, 0x54, 0x8a, 0x35, 0x8e, 0xb4, 0x6d, + 0x5b, 0xc2, 0x58, 0x29, 0x16, 0xaa, 0x31, 0xdb, 0x36, 0x6e, 0xfa, 0xe4, 0x6d, 0x2b, 0xcb, 0xb0, + 0xe2, 0x45, 0xf9, 0xda, 0xc2, 0xf2, 0x97, 0xcd, 0x52, 0x15, 0xb7, 0x23, 0x72, 0xbe, 0xb2, 0x0c, + 0x2b, 0x5e, 0xb4, 0xbd, 0x83, 0x83, 0xe3, 0x23, 0xcb, 0x39, 0xb0, 0xdd, 0x96, 0x08, 0x59, 0x1c, + 0x37, 0x15, 0xea, 0xc1, 0xf1, 0x03, 0x4e, 0x4f, 0x6f, 0xef, 0xa8, 0x14, 0x6b, 0x1c, 0xd1, 0x3f, + 0x31, 0x60, 0xb2, 0xe3, 0x74, 0x5b, 0xb6, 0xbb, 0x3c, 0xc3, 0xf4, 0xc4, 0xcf, 0x65, 0xb9, 0x42, + 0xaf, 0xd6, 0x18, 0x69, 0xae, 0x28, 0xfe, 0x94, 0x72, 0xbf, 0x63, 0x85, 0xbf, 0xfc, 0x67, 0xe5, + 0x65, 0xe2, 0x36, 0xbc, 0xa6, 0xed, 0xb6, 0xd6, 0x1e, 0x06, 0x9e, 0xbb, 0x8a, 0xad, 0x23, 0xa9, + 0xa3, 0x0b, 0x99, 0x56, 0x7e, 0x06, 0xa6, 0x35, 0x12, 0x67, 0x29, 0x7a, 0x33, 0xba, 0xa2, 0xf7, + 0xbd, 0x49, 0x98, 0xd1, 0x93, 0xe3, 0x0d, 0xa1, 0x7d, 0xa9, 0x13, 0x47, 0x6e, 0x94, 0x13, 0x07, + 0x3d, 0x62, 0x6a, 0xb7, 0x47, 0xd2, 0xbc, 0xb5, 0x99, 0x99, 0xc2, 0x1d, 0x1d, 0x31, 0xb5, 0xc2, + 0x00, 0xc7, 0x98, 0x8e, 0xe0, 0x50, 0x42, 0xd5, 0x56, 0xae, 0xd8, 0x15, 0xe2, 0x6a, 0x6b, 0x4c, + 0x55, 0x7b, 0x19, 0x20, 0x4a, 0x12, 0x27, 0x6e, 0x15, 0x95, 0x3e, 0xac, 0x25, 0xaf, 0xd3, 0xb0, + 0xd0, 0x0b, 0x30, 0x49, 0x55, 0x1f, 0xd2, 0x14, 0x11, 0xd5, 0xea, 0x1c, 0x7f, 0x83, 0x95, 0x62, + 0x01, 0x45, 0xaf, 0x51, 0x2d, 0x35, 0x52, 0x58, 0x44, 0xa0, 0xf4, 0x52, 0xa4, 0xa5, 0x46, 0x30, + 0x1c, 0xc3, 0xa4, 0xa2, 0x13, 0xaa, 0x5f, 0xb0, 0xb5, 0x41, 0x13, 0x9d, 0x29, 0x1d, 0x98, 0xc3, + 0x98, 0x5d, 0x29, 0xa1, 0x8f, 0xb0, 0x39, 0x5d, 0xd0, 0xec, 0x4a, 0x09, 0x38, 0xee, 0xab, 0x41, + 0x3f, 0x46, 0x5c, 0x88, 0x4e, 0x73, 0xbf, 0xd9, 0x01, 0x57, 0x99, 0x5f, 0xd7, 0xcf, 0x5a, 0x19, + 0xce, 0x21, 0x3e, 0x6a, 0x87, 0x3f, 0x6c, 0x8d, 0x77, 0x2c, 0xfa, 0x12, 0xcc, 0xc5, 0x77, 0xa1, + 0xcc, 0x6f, 0x3e, 0xfe, 0x7d, 0x1e, 0x2e, 0xdd, 0x6e, 0xd9, 0x6e, 0x32, 0xf1, 0x53, 0x5a, 0x02, + 0x66, 0x63, 0xd4, 0x04, 0xcc, 0x51, 0x68, 0x96, 0xc8, 0x70, 0x9d, 0x1e, 0x9a, 0x25, 0xd3, 0x5f, + 0xc7, 0x71, 0xd1, 0x9f, 0x1a, 0xf0, 0xac, 0xd5, 0xe4, 0xe7, 0x02, 0xcb, 0x11, 0xa5, 0x11, 0x53, + 0x39, 0xa3, 0x83, 0x31, 0x77, 0xf9, 0xfe, 0x8f, 0x5f, 0xad, 0x9c, 0xc2, 0x95, 0xf7, 0xf8, 0x4f, + 0x88, 0x2f, 0x78, 0xf6, 0x34, 0x54, 0x7c, 0xaa, 0xf8, 0x2b, 0x77, 0xe0, 0xe3, 0x67, 0x32, 0x1a, + 0x69, 0xb4, 0x7c, 0xcd, 0x80, 0x12, 0x37, 0x4c, 0x63, 0xb2, 0x47, 0x97, 0x0a, 0xab, 0x63, 0xdf, + 0x27, 0x7e, 0x20, 0x33, 0xc3, 0x69, 0x47, 0xe7, 0x4a, 0x6d, 0x53, 0x40, 0xb0, 0x86, 0x45, 0x17, + 0xe3, 0x03, 0xdb, 0x6d, 0x8a, 0x6e, 0x52, 0x8b, 0xf1, 0x5b, 0xb6, 0xdb, 0xc4, 0x0c, 0xa2, 0x96, + 0xeb, 0xfc, 0x40, 0x83, 0xd1, 0x77, 0x0d, 0x98, 0x63, 0xf1, 0xa8, 0xd1, 0xa1, 0xee, 0x55, 0xe5, + 0x2d, 0xc4, 0xc5, 0xb8, 0x1a, 0xf7, 0x16, 0x7a, 0xdc, 0x2b, 0x4f, 0xf3, 0x08, 0xd6, 0xb8, 0xf3, + 0xd0, 0x17, 0x84, 0x25, 0x88, 0xf9, 0x34, 0xe5, 0x46, 0x36, 0x54, 0x28, 0x4b, 0x69, 0x5d, 0x12, + 0xc1, 0x11, 0x3d, 0xf3, 0x5d, 0x98, 0xd1, 0x03, 0x4b, 0xd0, 0xab, 0x30, 0xdd, 0xb1, 0xdd, 0x56, + 0x3c, 0x00, 0x51, 0x59, 0xcb, 0x6b, 0x11, 0x08, 0xeb, 0x78, 0xac, 0x9a, 0x17, 0x55, 0x4b, 0x18, + 0xd9, 0x6b, 0x9e, 0x5e, 0x2d, 0xfa, 0x63, 0xfe, 0xcb, 0x3c, 0x5c, 0x4a, 0x09, 0x60, 0x42, 0xef, + 0x1b, 0x30, 0xc9, 0xe2, 0x1f, 0xa4, 0x3f, 0xd0, 0xdb, 0x99, 0x07, 0x49, 0xad, 0xb2, 0x30, 0x0b, + 0x31, 0x8e, 0xd5, 0xf2, 0xc9, 0x0b, 0xb1, 0x60, 0x8e, 0xfe, 0x91, 0x01, 0xd3, 0x96, 0x36, 0xd5, + 0xb8, 0x8b, 0xd4, 0x6e, 0xf6, 0xc2, 0xf4, 0xcd, 0x2c, 0xcd, 0xb5, 0x33, 0x9a, 0x48, 0xba, 0x2c, + 0x54, 0xfb, 0xd0, 0x3e, 0x61, 0x94, 0x19, 0xb2, 0xf2, 0x06, 0x2c, 0x8c, 0x35, 0xc3, 0x7e, 0x16, + 0x46, 0x4d, 0x74, 0x48, 0x37, 0xac, 0x23, 0x3d, 0x48, 0x5c, 0xb5, 0xb8, 0x88, 0x12, 0x17, 0x50, + 0x73, 0x17, 0x16, 0x92, 0xc7, 0xd6, 0xcc, 0x3d, 0x02, 0x3e, 0x09, 0x23, 0xa6, 0x26, 0x34, 0xaf, + 0x03, 0xc2, 0x9e, 0xe3, 0xec, 0x5a, 0x8d, 0x83, 0x07, 0xb6, 0xdb, 0xf4, 0x8e, 0xd8, 0x5c, 0x59, + 0x83, 0x92, 0x2f, 0xe2, 0xd3, 0x02, 0xf1, 0x59, 0x6a, 0xb2, 0xc9, 0xc0, 0xb5, 0x00, 0x47, 0x38, + 0xe6, 0x1f, 0xe6, 0x60, 0x4a, 0x04, 0x53, 0x3e, 0x01, 0xe7, 0xea, 0x83, 0xd8, 0xad, 0xdb, 0x66, + 0x26, 0x31, 0xa0, 0x03, 0x3d, 0xab, 0x83, 0x84, 0x67, 0xf5, 0x5b, 0xd9, 0xb0, 0x3b, 0xdd, 0xad, + 0xfa, 0xbb, 0x13, 0x30, 0x9f, 0x08, 0x4e, 0xa5, 0x1a, 0x4f, 0x9f, 0x37, 0xe1, 0xbd, 0x4c, 0xe3, + 0x5f, 0x95, 0xe3, 0xff, 0xe9, 0x8e, 0x85, 0x41, 0x2c, 0x91, 0xec, 0xdd, 0xcc, 0x72, 0xd0, 0xff, + 0x75, 0x4e, 0xd9, 0x51, 0x1d, 0xe5, 0xfe, 0xab, 0x01, 0x4f, 0x0f, 0x8c, 0x61, 0x66, 0x29, 0x70, + 0xfc, 0x38, 0x54, 0x4c, 0xc8, 0x8c, 0x33, 0x35, 0xa8, 0x2b, 0xb0, 0x64, 0xd6, 0x92, 0x24, 0x7b, + 0xf4, 0x0a, 0xcc, 0xb0, 0x1d, 0x9a, 0x2e, 0x4d, 0x21, 0xe9, 0x08, 0x0b, 0x3e, 0xb3, 0xe5, 0xd6, + 0xb5, 0x72, 0x1c, 0xc3, 0x32, 0xbf, 0x63, 0xc0, 0xf2, 0xa0, 0x84, 0x28, 0x43, 0x9c, 0x2f, 0xff, + 0x56, 0xc2, 0xfb, 0xbb, 0xdc, 0xe7, 0xfd, 0x9d, 0x38, 0x61, 0x4a, 0x47, 0x6f, 0xed, 0x70, 0x97, + 0x3f, 0xc3, 0xb9, 0xf9, 0x5b, 0x06, 0x5c, 0x19, 0x30, 0x9b, 0xfa, 0xa2, 0x00, 0x8c, 0x73, 0x47, + 0x01, 0xe4, 0x86, 0x8d, 0x02, 0x30, 0xff, 0x28, 0x0f, 0x0b, 0x42, 0x9e, 0x48, 0x4d, 0x7b, 0x2d, + 0xe6, 0x43, 0xff, 0x13, 0x09, 0x1f, 0xfa, 0xa5, 0x24, 0xfe, 0x5f, 0x3b, 0xd0, 0x7f, 0xb4, 0x1c, + 0xe8, 0xff, 0x32, 0x07, 0x97, 0x53, 0xf3, 0xb4, 0xa0, 0x6f, 0xa4, 0x6c, 0x0d, 0x0f, 0x32, 0x4e, + 0x08, 0x33, 0xe4, 0xe6, 0x30, 0xae, 0xd7, 0xf9, 0xaf, 0xe9, 0xde, 0xde, 0x7c, 0xa9, 0xdf, 0xbb, + 0x80, 0xd4, 0x36, 0x23, 0x3a, 0x7e, 0x9b, 0xbf, 0x9c, 0x87, 0x17, 0x87, 0x25, 0xf4, 0x11, 0x0d, + 0x0c, 0x0a, 0x62, 0x81, 0x41, 0x4f, 0x68, 0xdb, 0xbe, 0x90, 0x18, 0xa1, 0xef, 0xe5, 0xd5, 0xb6, + 0xd7, 0x3f, 0x3e, 0x87, 0xba, 0xee, 0x9d, 0xa2, 0xaa, 0x9d, 0xcc, 0xf5, 0x1a, 0x2d, 0x85, 0x53, + 0x75, 0x5e, 0xfc, 0xb8, 0x57, 0x5e, 0x8c, 0xb2, 0x05, 0x88, 0x42, 0x2c, 0x2b, 0xa1, 0x17, 0xa1, + 0xe8, 0x73, 0xa8, 0x0c, 0x85, 0x10, 0x77, 0xe6, 0xbc, 0x0c, 0x2b, 0x28, 0xfa, 0x8a, 0xa6, 0x0b, + 0x4f, 0x5c, 0x54, 0x52, 0x8c, 0xd3, 0x5c, 0x01, 0xde, 0x86, 0x62, 0x20, 0xf3, 0xb0, 0xf2, 0xfb, + 0x9a, 0x4f, 0x0f, 0x19, 0x61, 0x43, 0x4f, 0x60, 0x32, 0x29, 0x2b, 0xff, 0x3e, 0x95, 0xb2, 0x55, + 0x91, 0x44, 0xa6, 0x3a, 0xfc, 0x70, 0x53, 0x25, 0xa4, 0x1c, 0x7c, 0x7e, 0x68, 0xc0, 0xb4, 0xe8, + 0xad, 0x27, 0x10, 0xf4, 0xf3, 0x30, 0x1e, 0xf4, 0x73, 0x3d, 0x93, 0xb5, 0x63, 0x40, 0xc4, 0xcf, + 0x43, 0x98, 0xd1, 0x53, 0x75, 0xa1, 0xcf, 0x6b, 0x6b, 0x9f, 0x31, 0x4e, 0xf2, 0x1b, 0xb9, 0x3a, + 0x46, 0xeb, 0xa2, 0xf9, 0x9b, 0x25, 0xd5, 0x8a, 0xec, 0x88, 0xa6, 0x8f, 0x41, 0xe3, 0xd4, 0x31, + 0xa8, 0x0f, 0x81, 0x5c, 0xf6, 0x43, 0xe0, 0x2e, 0x14, 0xe5, 0x02, 0x25, 0xb6, 0xf1, 0xe7, 0x75, + 0x37, 0x48, 0xaa, 0x0b, 0x50, 0x62, 0xda, 0xc0, 0x65, 0x47, 0x2d, 0xd5, 0x87, 0x6a, 0xe1, 0x54, + 0x64, 0xd0, 0x3b, 0x30, 0x7d, 0xe4, 0xf9, 0x07, 0x8e, 0x67, 0xb1, 0x7c, 0xcc, 0x90, 0xc5, 0xcd, + 0x9b, 0xb2, 0x9b, 0x71, 0x5f, 0xf4, 0x07, 0x11, 0x7d, 0xac, 0x33, 0x43, 0x15, 0x98, 0x6f, 0xdb, + 0x2e, 0x26, 0x56, 0x53, 0xc5, 0xf6, 0x4c, 0xf0, 0x14, 0xb0, 0x52, 0xc9, 0xdd, 0x8e, 0x83, 0x71, + 0x12, 0x1f, 0x7d, 0xd3, 0x80, 0x39, 0x3f, 0x76, 0xa8, 0x16, 0x79, 0x1e, 0x6b, 0xe3, 0x0f, 0xc6, + 0xf8, 0x41, 0x9d, 0x3b, 0x63, 0xc7, 0xcb, 0x71, 0x82, 0x37, 0xfa, 0x32, 0x14, 0x03, 0x91, 0x87, + 0x2b, 0x9b, 0x2b, 0x5b, 0x75, 0x84, 0xe5, 0x44, 0xa3, 0xae, 0x94, 0x25, 0x58, 0x31, 0x44, 0x5b, + 0xb0, 0x24, 0xad, 0x04, 0xb1, 0x37, 0x73, 0x26, 0xa3, 0xb4, 0x2d, 0x38, 0x05, 0x8e, 0x53, 0x6b, + 0x51, 0xa5, 0x8a, 0xa5, 0xc0, 0xe3, 0x37, 0x1d, 0xda, 0xe5, 0x00, 0x9b, 0x7f, 0x4d, 0x2c, 0xa0, + 0xa7, 0x85, 0xae, 0x15, 0xc7, 0x08, 0x5d, 0xab, 0xc3, 0xe5, 0x24, 0x88, 0xe5, 0xe3, 0x61, 0x29, + 0x80, 0xb4, 0xcd, 0xac, 0x96, 0x86, 0x84, 0xd3, 0xeb, 0xa2, 0x07, 0x50, 0xf2, 0x09, 0x3b, 0xee, + 0x54, 0xa4, 0x93, 0xc8, 0xc8, 0xee, 0x70, 0x58, 0x12, 0xc0, 0x11, 0x2d, 0xda, 0xef, 0x56, 0x3c, + 0x29, 0xeb, 0xdd, 0x0c, 0x5f, 0xfd, 0x13, 0x7d, 0x3f, 0x20, 0x4f, 0x96, 0xf9, 0xe1, 0x1c, 0xcc, + 0xc6, 0x4c, 0x1d, 0xe8, 0x79, 0x28, 0xb0, 0x04, 0x45, 0x6c, 0xb5, 0x2a, 0x46, 0x2b, 0x2a, 0x6f, + 0x1c, 0x0e, 0x43, 0xbf, 0x62, 0xc0, 0x7c, 0x27, 0x66, 0x5b, 0x96, 0x0b, 0xf9, 0x98, 0xf7, 0xc2, + 0x71, 0x83, 0xb5, 0x96, 0xce, 0x3c, 0xce, 0x0c, 0x27, 0xb9, 0xd3, 0xf5, 0x40, 0xf8, 0x94, 0x3a, + 0xc4, 0x67, 0xd8, 0x42, 0xe5, 0x52, 0x24, 0xd6, 0xe3, 0x60, 0x9c, 0xc4, 0xa7, 0x3d, 0xcc, 0xbe, + 0x6e, 0x9c, 0xe7, 0xc0, 0x2a, 0x92, 0x00, 0x8e, 0x68, 0xa1, 0x37, 0x60, 0x4e, 0xe4, 0xe2, 0xac, + 0x79, 0xcd, 0x9b, 0x56, 0xb0, 0x2f, 0xce, 0x1a, 0xea, 0x6c, 0xb4, 0x1e, 0x83, 0xe2, 0x04, 0x36, + 0xfb, 0xb6, 0x28, 0xe1, 0x29, 0x23, 0x30, 0x19, 0xcf, 0xf6, 0xbe, 0x1e, 0x07, 0xe3, 0x24, 0x3e, + 0x7a, 0x49, 0xdb, 0x86, 0xf8, 0xed, 0xa3, 0x5a, 0x0d, 0x52, 0xb6, 0xa2, 0x0a, 0xcc, 0x77, 0xd9, + 0xd1, 0xac, 0x29, 0x81, 0x62, 0x3e, 0x2a, 0x86, 0xf7, 0xe2, 0x60, 0x9c, 0xc4, 0x47, 0xaf, 0xc3, + 0xac, 0x4f, 0x17, 0x5b, 0x45, 0x80, 0x5f, 0x49, 0xaa, 0x1b, 0x27, 0xac, 0x03, 0x71, 0x1c, 0x17, + 0xbd, 0x09, 0x8b, 0x51, 0xea, 0x3a, 0x49, 0x80, 0xdf, 0x51, 0xaa, 0x3c, 0x4a, 0x95, 0x24, 0x02, + 0xee, 0xaf, 0x83, 0xfe, 0x0e, 0x2c, 0x68, 0x2d, 0xb1, 0xe9, 0x36, 0xc9, 0x23, 0x91, 0x5e, 0x8c, + 0x3d, 0xe3, 0xb1, 0x9e, 0x80, 0xe1, 0x3e, 0x6c, 0xf4, 0x19, 0x98, 0x6b, 0x78, 0x8e, 0xc3, 0xd6, + 0x38, 0x9e, 0x69, 0x9c, 0xe7, 0x11, 0xe3, 0x19, 0xd7, 0x62, 0x10, 0x9c, 0xc0, 0x44, 0xb7, 0x00, + 0x79, 0xbb, 0x01, 0xf1, 0x0f, 0x49, 0xf3, 0x4d, 0xfe, 0xc0, 0x30, 0xd5, 0x38, 0x66, 0xe3, 0x1e, + 0xed, 0x77, 0xfa, 0x30, 0x70, 0x4a, 0x2d, 0x96, 0x86, 0x49, 0x8b, 0x00, 0x9c, 0xcb, 0xe2, 0x69, + 0xac, 0xa4, 0x21, 0xe1, 0xcc, 0xf0, 0x3f, 0x1f, 0x26, 0x79, 0x80, 0x41, 0x36, 0x09, 0xc5, 0xf4, + 0xa4, 0xc3, 0xd1, 0x1e, 0xc1, 0x4b, 0xb1, 0xe0, 0x84, 0x7e, 0x11, 0x4a, 0xbb, 0x32, 0x03, 0x3d, + 0xcb, 0x22, 0x36, 0xf6, 0xbe, 0x98, 0x78, 0x4c, 0x21, 0x3a, 0x28, 0x2b, 0x00, 0x8e, 0x58, 0xa2, + 0x17, 0x60, 0xfa, 0x66, 0xad, 0xa2, 0x46, 0xe1, 0x22, 0xeb, 0xfd, 0x09, 0x5a, 0x05, 0xeb, 0x00, + 0x3a, 0xc3, 0x94, 0xfa, 0x86, 0xe2, 0x8f, 0x3a, 0xa4, 0x68, 0x63, 0x14, 0x9b, 0x5d, 0xb2, 0xe2, + 0xfa, 0xf2, 0xa5, 0x04, 0xb6, 0x28, 0xc7, 0x0a, 0x03, 0xbd, 0x0d, 0xd3, 0x62, 0xbf, 0x60, 0x6b, + 0xd3, 0xd2, 0xf9, 0xa2, 0x4b, 0x71, 0x44, 0x02, 0xeb, 0xf4, 0xd8, 0xdd, 0x19, 0x4b, 0xcc, 0x4d, + 0x6e, 0x74, 0x1d, 0x67, 0xf9, 0x32, 0x5b, 0x37, 0xa3, 0xbb, 0xb3, 0x08, 0x84, 0x75, 0x3c, 0xf4, + 0x69, 0xe9, 0x0f, 0xf2, 0xb1, 0xd8, 0x65, 0xa2, 0xf2, 0x07, 0x51, 0x4a, 0xf7, 0x00, 0x07, 0xf4, + 0x2b, 0x67, 0x38, 0x62, 0xec, 0xc2, 0x8a, 0xd4, 0xf8, 0xfa, 0x27, 0xc9, 0xf2, 0x72, 0xcc, 0x68, + 0xb1, 0xf2, 0x60, 0x20, 0x26, 0x3e, 0x85, 0x0a, 0xda, 0x85, 0xbc, 0xe5, 0xec, 0x2e, 0x3f, 0x9d, + 0x85, 0xea, 0xaa, 0x1e, 0x0c, 0xe7, 0x4e, 0x63, 0x95, 0xad, 0x2a, 0xa6, 0xc4, 0xcd, 0xaf, 0xe6, + 0xd4, 0x25, 0x81, 0x4a, 0xb4, 0xfa, 0xae, 0x3e, 0xaa, 0x8d, 0x2c, 0x1e, 0xc4, 0xed, 0x7b, 0xa6, + 0x81, 0x6f, 0x48, 0xa9, 0x63, 0xba, 0xa3, 0xe6, 0x71, 0x26, 0x79, 0x6f, 0xe2, 0x49, 0x64, 0xf9, + 0xe1, 0x32, 0x3e, 0x8b, 0xcd, 0xdf, 0x01, 0x65, 0x13, 0x4b, 0x38, 0x38, 0xf8, 0x50, 0xb0, 0x83, + 0xd0, 0xf6, 0x32, 0x8c, 0x84, 0x4c, 0x64, 0x5f, 0x65, 0x8e, 0xd6, 0x0c, 0x80, 0x39, 0x2b, 0xca, + 0xd3, 0x6d, 0xd9, 0xee, 0x23, 0xf1, 0xf9, 0x77, 0x33, 0xf7, 0x5c, 0xe0, 0x3c, 0x19, 0x00, 0x73, + 0x56, 0xe8, 0x21, 0x1f, 0x69, 0xd9, 0x3c, 0x7e, 0x9c, 0x7c, 0xd3, 0x3c, 0x3e, 0xe2, 0x28, 0xaf, + 0xa0, 0x6d, 0x0b, 0x1d, 0x66, 0x4c, 0x5e, 0xf5, 0xed, 0xcd, 0x34, 0x5e, 0xf5, 0xed, 0x4d, 0x4c, + 0x99, 0xa0, 0xaf, 0x1b, 0x00, 0x96, 0x7a, 0xdc, 0x3b, 0x9b, 0x37, 0x4d, 0x06, 0x3d, 0x16, 0xce, + 0x7d, 0xf5, 0x22, 0x28, 0xd6, 0x38, 0xa3, 0x77, 0x60, 0xca, 0xe2, 0xef, 0x37, 0x09, 0xb7, 0xd3, + 0x6c, 0x1e, 0x25, 0x4b, 0x48, 0xc0, 0xfc, 0x6d, 0x05, 0x08, 0x4b, 0x86, 0x94, 0x77, 0xe8, 0x5b, + 0x64, 0xcf, 0x3e, 0x10, 0xfe, 0xa7, 0xf5, 0xb1, 0x13, 0xab, 0x53, 0x62, 0x69, 0xbc, 0x05, 0x08, + 0x4b, 0x86, 0xfc, 0x3d, 0x5d, 0xcb, 0xb5, 0x54, 0x30, 0x51, 0x36, 0x21, 0x67, 0x7a, 0x78, 0x92, + 0xf6, 0x9e, 0xae, 0xce, 0x08, 0xc7, 0xf9, 0xa2, 0x43, 0x98, 0xb4, 0xd8, 0xcb, 0x72, 0xe2, 0x7c, + 0x84, 0xb3, 0x78, 0xa5, 0x2e, 0xd1, 0x06, 0x6c, 0x71, 0x11, 0xef, 0xd7, 0x09, 0x6e, 0xe8, 0x37, + 0x0c, 0x98, 0xe2, 0x1e, 0x91, 0x54, 0x4b, 0xa4, 0xdf, 0xfe, 0xa5, 0x0b, 0xc8, 0xe2, 0x2c, 0xbc, + 0x35, 0x85, 0x7b, 0xc4, 0x4f, 0x2b, 0x77, 0x2f, 0x5e, 0x7a, 0xaa, 0xbf, 0xa6, 0x94, 0x6e, 0xe5, + 0x33, 0x30, 0xa3, 0x53, 0x19, 0xc9, 0x63, 0xf3, 0xcf, 0xf3, 0x00, 0xac, 0xa1, 0x79, 0xfa, 0x80, + 0x36, 0x4b, 0x39, 0xb9, 0xef, 0x35, 0xb3, 0x79, 0x12, 0x50, 0xcf, 0x02, 0x00, 0x22, 0xbf, 0xe4, + 0xbe, 0xd7, 0xc4, 0x82, 0x09, 0x6a, 0xc1, 0x44, 0xc7, 0x0a, 0xf7, 0xb3, 0x4f, 0x39, 0x50, 0xe4, + 0x71, 0x74, 0xe1, 0x3e, 0x66, 0x0c, 0xd0, 0x7b, 0x06, 0x4c, 0xf1, 0xa4, 0x03, 0xf2, 0x5e, 0x61, + 0xec, 0xcb, 0x73, 0xd9, 0x66, 0xab, 0x3c, 0xb3, 0x81, 0xe8, 0x41, 0xa5, 0x78, 0x88, 0x52, 0x2c, + 0xd9, 0xae, 0xbc, 0x6f, 0xc0, 0x8c, 0x8e, 0x9a, 0xd2, 0x4d, 0x3f, 0xaf, 0x77, 0x53, 0x96, 0xed, + 0xa1, 0xf7, 0xf8, 0xff, 0x30, 0x40, 0x7b, 0xe3, 0x39, 0x72, 0x4c, 0x35, 0x86, 0x76, 0x4c, 0xcd, + 0x8d, 0xe8, 0x98, 0x9a, 0x1f, 0xc9, 0x31, 0x75, 0x62, 0x74, 0xc7, 0xd4, 0xc2, 0x60, 0xc7, 0x54, + 0xf3, 0x03, 0x03, 0x16, 0xfb, 0x76, 0x1b, 0xaa, 0x9c, 0xfa, 0x9e, 0x17, 0x0e, 0xf0, 0x07, 0xc3, + 0x11, 0x08, 0xeb, 0x78, 0x68, 0x03, 0x16, 0x44, 0x82, 0xf5, 0x7a, 0xc7, 0xb1, 0x53, 0xd3, 0x41, + 0xec, 0x24, 0xe0, 0xb8, 0xaf, 0x86, 0xf9, 0x3b, 0x06, 0x4c, 0x6b, 0x41, 0xa4, 0xf4, 0x3b, 0x58, + 0xb0, 0xad, 0x10, 0x23, 0xca, 0x2d, 0xcf, 0xee, 0x71, 0x38, 0x8c, 0x5f, 0x29, 0xb6, 0xb4, 0xf4, + 0xbb, 0xd1, 0x95, 0x22, 0x2d, 0xc5, 0x02, 0xca, 0x13, 0xab, 0x92, 0x0e, 0x6b, 0xf4, 0xbc, 0x9e, + 0x58, 0x95, 0x74, 0x30, 0x83, 0x30, 0x76, 0x54, 0x4b, 0x17, 0x3e, 0xcb, 0x5a, 0x2a, 0x7b, 0xcb, + 0x0f, 0x31, 0x87, 0xa1, 0xab, 0x90, 0x27, 0x6e, 0x53, 0x98, 0x14, 0xd4, 0x63, 0x73, 0xd7, 0xdd, + 0x26, 0xa6, 0xe5, 0xe6, 0x1d, 0x98, 0xa9, 0x93, 0x86, 0x4f, 0xc2, 0xb7, 0xc8, 0xf1, 0xd0, 0xaf, + 0xd7, 0xd1, 0xd1, 0x9e, 0x78, 0xbd, 0x8e, 0x56, 0xa7, 0xe5, 0xe6, 0xbf, 0x30, 0x20, 0xf1, 0xde, + 0x82, 0x76, 0xbd, 0x60, 0x0c, 0xba, 0x5e, 0x88, 0x19, 0xc2, 0x73, 0xa7, 0x1a, 0xc2, 0x6f, 0x01, + 0x6a, 0xd3, 0xa9, 0x10, 0x7b, 0x5d, 0x44, 0x58, 0x73, 0xa2, 0x90, 0xf5, 0x3e, 0x0c, 0x9c, 0x52, + 0xcb, 0xfc, 0xe7, 0x5c, 0x58, 0xfd, 0x05, 0x86, 0xb3, 0x1b, 0xa0, 0x0b, 0x05, 0x46, 0x4a, 0x98, + 0xb4, 0xc6, 0x34, 0x07, 0xf7, 0xa7, 0x7e, 0x89, 0x3a, 0x52, 0x4c, 0x79, 0xc6, 0xcd, 0xfc, 0x23, + 0x2e, 0xab, 0xf6, 0x44, 0xc3, 0x10, 0xb2, 0xb6, 0xe3, 0xb2, 0xde, 0xcc, 0x6a, 0xad, 0x4c, 0x97, + 0x11, 0xad, 0x02, 0x74, 0x88, 0xdf, 0x20, 0x6e, 0x28, 0x5d, 0xe9, 0x0b, 0x22, 0xa8, 0x4b, 0x95, + 0x62, 0x0d, 0xc3, 0xfc, 0x75, 0x03, 0x16, 0x92, 0x11, 0x17, 0x59, 0xbb, 0xd3, 0xc5, 0xc2, 0x42, + 0xf3, 0xa3, 0x87, 0x85, 0x9a, 0xef, 0x51, 0x21, 0x43, 0xbb, 0x71, 0x60, 0xbb, 0x3c, 0x92, 0x72, + 0xcf, 0x6e, 0x51, 0x21, 0x89, 0x78, 0x0f, 0x8e, 0x5b, 0x42, 0x95, 0x90, 0xf2, 0x19, 0x38, 0x09, + 0x47, 0x15, 0x98, 0x97, 0xf7, 0x3f, 0xd2, 0x7c, 0xcd, 0x23, 0xc0, 0x95, 0xb9, 0x6c, 0x23, 0x0e, + 0xc6, 0x49, 0x7c, 0xf3, 0x2b, 0x30, 0xad, 0x6d, 0x02, 0x6c, 0xbd, 0x7c, 0x64, 0x35, 0xc2, 0xe4, + 0x3a, 0x73, 0x9d, 0x16, 0x62, 0x0e, 0x63, 0x56, 0x76, 0xee, 0x10, 0x9e, 0x58, 0x67, 0x84, 0x1b, + 0xb8, 0x80, 0x52, 0x62, 0x3e, 0x69, 0x91, 0x47, 0x32, 0x1b, 0xb1, 0x24, 0x86, 0x69, 0x21, 0xe6, + 0x30, 0xf3, 0x25, 0x28, 0xca, 0x3c, 0x1d, 0x2c, 0xd8, 0x5d, 0x5a, 0x80, 0xf5, 0x60, 0x77, 0xcf, + 0x0f, 0x31, 0x83, 0x98, 0xf7, 0xa1, 0x28, 0xd3, 0x89, 0x9c, 0x8d, 0x4d, 0xa7, 0x7e, 0xe0, 0xda, + 0x37, 0xbd, 0x20, 0x94, 0x39, 0x50, 0xf8, 0x25, 0xd5, 0xed, 0x4d, 0x56, 0x86, 0x15, 0xd4, 0x5c, + 0x84, 0x79, 0x75, 0xfb, 0x24, 0x3c, 0x74, 0x7f, 0x3f, 0x0f, 0x33, 0xb1, 0x07, 0xd9, 0xcf, 0x9e, + 0x13, 0xc3, 0x2f, 0x35, 0x29, 0xb7, 0x48, 0xf9, 0x11, 0x6f, 0x91, 0xf4, 0x6b, 0xbb, 0x89, 0x8b, + 0xbd, 0xb6, 0x2b, 0x64, 0x73, 0x6d, 0x17, 0xc2, 0x54, 0x20, 0x76, 0xd3, 0xc9, 0x2c, 0xec, 0x69, + 0x89, 0x1e, 0xe3, 0xc7, 0x14, 0xb9, 0x29, 0x4b, 0x56, 0xe6, 0x6f, 0x17, 0x60, 0x2e, 0x9e, 0x48, + 0x6d, 0x88, 0x9e, 0x7c, 0xa9, 0xaf, 0x27, 0x47, 0x34, 0x5b, 0xe7, 0xc7, 0x35, 0x5b, 0x4f, 0x8c, + 0x6b, 0xb6, 0x2e, 0x9c, 0xc3, 0x6c, 0xdd, 0x6f, 0x74, 0x9e, 0x1c, 0xda, 0xe8, 0xfc, 0x59, 0xe5, + 0x02, 0x36, 0x15, 0xf3, 0x99, 0x88, 0x5c, 0xc0, 0x50, 0xbc, 0x1b, 0xd6, 0xbd, 0x66, 0xaa, 0x2b, + 0x5d, 0xf1, 0x0c, 0xf3, 0x9c, 0x9f, 0xea, 0xb1, 0x35, 0xfa, 0xcd, 0xd8, 0xc7, 0x46, 0xf0, 0xd6, + 0x7a, 0x15, 0xa6, 0xc5, 0x78, 0x62, 0x0a, 0x1d, 0xc4, 0x95, 0xc1, 0x7a, 0x04, 0xc2, 0x3a, 0x1e, + 0x7b, 0x2e, 0x37, 0xfe, 0x9a, 0x30, 0xbb, 0x05, 0xd0, 0x9f, 0xcb, 0x4d, 0xbc, 0x3e, 0x9c, 0xc4, + 0x37, 0xbf, 0x0c, 0x97, 0x53, 0x0f, 0xe5, 0xcc, 0x4a, 0xc9, 0x74, 0x0d, 0xd2, 0x14, 0x08, 0x9a, + 0x18, 0x89, 0x3c, 0xdb, 0x2b, 0x0f, 0x06, 0x62, 0xe2, 0x53, 0xa8, 0x98, 0xbf, 0x95, 0x87, 0xb9, + 0xf8, 0x5b, 0x6b, 0xe8, 0x48, 0x99, 0xf0, 0x32, 0xb1, 0x1e, 0x72, 0xb2, 0x5a, 0x72, 0xae, 0x81, + 0xf6, 0xf8, 0x23, 0x36, 0xbe, 0x76, 0x55, 0xa6, 0xb0, 0x8b, 0x63, 0x2c, 0x0c, 0xe1, 0x82, 0x1d, + 0x7b, 0x4e, 0x2d, 0x8a, 0xe3, 0x11, 0x67, 0xc3, 0xcc, 0xb9, 0x47, 0x91, 0x39, 0x8a, 0x15, 0xd6, + 0xd8, 0xd2, 0xbd, 0xe5, 0x90, 0xf8, 0xf6, 0x9e, 0xad, 0xde, 0x89, 0x65, 0x2b, 0xf7, 0x7d, 0x51, + 0x86, 0x15, 0xd4, 0x7c, 0x2f, 0x07, 0xd1, 0x1b, 0xda, 0xec, 0x41, 0xa2, 0x40, 0xd3, 0xc3, 0x45, + 0xb7, 0xdd, 0x1a, 0xf7, 0xd5, 0xaf, 0x88, 0xa2, 0x70, 0xcf, 0xd5, 0x4a, 0x70, 0x8c, 0xe3, 0x8f, + 0xe1, 0xed, 0x6c, 0x0b, 0xe6, 0x13, 0x71, 0xe3, 0x99, 0x87, 0x52, 0xfc, 0x28, 0x0f, 0x25, 0x15, + 0x79, 0x8f, 0x7e, 0x26, 0x66, 0x14, 0x29, 0x55, 0x3f, 0xae, 0xbd, 0x96, 0xb1, 0xef, 0x35, 0x1f, + 0xf7, 0xca, 0xf3, 0x0a, 0x39, 0x61, 0xe0, 0xb8, 0x0a, 0xf9, 0xae, 0xef, 0x24, 0x4f, 0x3d, 0xf7, + 0xf0, 0x16, 0xa6, 0xe5, 0xe8, 0x51, 0xd2, 0x2a, 0xb1, 0x9d, 0x51, 0xb6, 0x00, 0x7e, 0x3c, 0x18, + 0x6c, 0x8d, 0xa0, 0xbb, 0xe4, 0xae, 0xd7, 0x3c, 0x4e, 0xbe, 0xae, 0x51, 0xf5, 0x9a, 0xc7, 0x98, + 0x41, 0xd0, 0x1b, 0x30, 0x17, 0xda, 0x6d, 0xe2, 0x75, 0x43, 0xfd, 0xcd, 0xe1, 0x7c, 0x74, 0xbf, + 0xbc, 0x13, 0x83, 0xe2, 0x04, 0x36, 0xdd, 0x65, 0x1f, 0x06, 0x9e, 0xcb, 0x52, 0x66, 0x4e, 0xc6, + 0x2f, 0xa3, 0x6e, 0xd5, 0xef, 0xdc, 0x66, 0xc6, 0x19, 0x85, 0x41, 0xb1, 0x6d, 0x16, 0x0c, 0xea, + 0x13, 0xe1, 0xde, 0xb1, 0x10, 0x69, 0xdb, 0xbc, 0x1c, 0x2b, 0x0c, 0xb4, 0xc1, 0x69, 0x53, 0x69, + 0xd9, 0x8e, 0x32, 0x53, 0x7d, 0x51, 0xd2, 0xa5, 0x65, 0x8f, 0x7b, 0xa7, 0x98, 0xcd, 0x54, 0x4d, + 0xf3, 0x1e, 0xcc, 0x27, 0x1a, 0x4c, 0x9e, 0x52, 0x8d, 0xf4, 0x53, 0xea, 0x70, 0x0f, 0x62, 0xfc, + 0x2b, 0x03, 0x16, 0xfb, 0x96, 0x80, 0x61, 0x23, 0x85, 0x92, 0x9b, 0x51, 0xee, 0xfc, 0x9b, 0x51, + 0x7e, 0xb4, 0xcd, 0xa8, 0xba, 0xfb, 0xfd, 0x0f, 0xaf, 0x3d, 0xf5, 0x83, 0x0f, 0xaf, 0x3d, 0xf5, + 0xc7, 0x1f, 0x5e, 0x7b, 0xea, 0xbd, 0x93, 0x6b, 0xc6, 0xf7, 0x4f, 0xae, 0x19, 0x3f, 0x38, 0xb9, + 0x66, 0xfc, 0xf1, 0xc9, 0x35, 0xe3, 0xbf, 0x9c, 0x5c, 0x33, 0x3e, 0xf8, 0xd1, 0xb5, 0xa7, 0x3e, + 0xff, 0xd9, 0x68, 0x80, 0xae, 0xc9, 0x01, 0xca, 0x7e, 0x7c, 0x42, 0x0e, 0xc7, 0xb5, 0xce, 0x41, + 0x6b, 0x8d, 0x0e, 0xd0, 0x35, 0x55, 0x22, 0x07, 0xe8, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x2a, + 0x3c, 0x20, 0x9b, 0xdb, 0x98, 0x00, 0x00, } func (m *ALBStatus) Marshal() (dAtA []byte, err error) { @@ -8575,6 +8579,32 @@ func (m *RolloutTrafficRouting) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.Plugins) > 0 { + keysForPlugins := make([]string, 0, len(m.Plugins)) + for k := range m.Plugins { + keysForPlugins = append(keysForPlugins, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForPlugins) + for iNdEx := len(keysForPlugins) - 1; iNdEx >= 0; iNdEx-- { + v := m.Plugins[string(keysForPlugins[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(keysForPlugins[iNdEx]) + copy(dAtA[i:], keysForPlugins[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForPlugins[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x52 + } + } if m.Apisix != nil { { size, err := m.Apisix.MarshalToSizedBuffer(dAtA[:i]) @@ -11542,6 +11572,18 @@ func (m *RolloutTrafficRouting) Size() (n int) { l = m.Apisix.Size() n += 1 + l + sovGenerated(uint64(l)) } + if len(m.Plugins) > 0 { + for k, v := range m.Plugins { + _ = 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 } @@ -13292,6 +13334,16 @@ func (this *RolloutTrafficRouting) String() string { repeatedStringForManagedRoutes += strings.Replace(strings.Replace(f.String(), "MangedRoutes", "MangedRoutes", 1), `&`, ``, 1) + "," } repeatedStringForManagedRoutes += "}" + keysForPlugins := make([]string, 0, len(this.Plugins)) + for k := range this.Plugins { + keysForPlugins = append(keysForPlugins, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForPlugins) + mapStringForPlugins := "map[string]encoding_json.RawMessage{" + for _, k := range keysForPlugins { + mapStringForPlugins += fmt.Sprintf("%v: %v,", k, this.Plugins[k]) + } + mapStringForPlugins += "}" s := strings.Join([]string{`&RolloutTrafficRouting{`, `Istio:` + strings.Replace(this.Istio.String(), "IstioTrafficRouting", "IstioTrafficRouting", 1) + `,`, `Nginx:` + strings.Replace(this.Nginx.String(), "NginxTrafficRouting", "NginxTrafficRouting", 1) + `,`, @@ -13302,6 +13354,7 @@ func (this *RolloutTrafficRouting) String() string { `Traefik:` + strings.Replace(this.Traefik.String(), "TraefikTrafficRouting", "TraefikTrafficRouting", 1) + `,`, `ManagedRoutes:` + repeatedStringForManagedRoutes + `,`, `Apisix:` + strings.Replace(this.Apisix.String(), "ApisixTrafficRouting", "ApisixTrafficRouting", 1) + `,`, + `Plugins:` + mapStringForPlugins + `,`, `}`, }, "") return s @@ -29155,6 +29208,134 @@ func (m *RolloutTrafficRouting) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Plugins", 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.Plugins == nil { + m.Plugins = 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.Plugins[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 0cf6389d2a..5bccf25611 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -1475,12 +1475,18 @@ message RolloutTrafficRouting { // Traefik holds specific configuration to use Traefik to route traffic optional TraefikTrafficRouting traefik = 7; - // A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream + // ManagedRoutes A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream // traffic router. repeated MangedRoutes managedRoutes = 8; // Apisix holds specific configuration to use Apisix to route traffic optional ApisixTrafficRouting apisix = 9; + + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Plugins holds specific configuration that traffic router plugins can use for routing traffic + map plugins = 10; } message RouteMatch { diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 2786436e54..8d97ea6481 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -4359,7 +4359,7 @@ func schema_pkg_apis_rollouts_v1alpha1_RolloutTrafficRouting(ref common.Referenc }, "managedRoutes": { SchemaProps: spec.SchemaProps{ - Description: "A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream traffic router.", + Description: "ManagedRoutes A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream traffic router.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ @@ -4377,6 +4377,21 @@ func schema_pkg_apis_rollouts_v1alpha1_RolloutTrafficRouting(ref common.Referenc Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ApisixTrafficRouting"), }, }, + "plugins": { + SchemaProps: spec.SchemaProps{ + Description: "Plugins holds specific configuration that traffic router plugins can use for routing traffic", + 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/types.go b/pkg/apis/rollouts/v1alpha1/types.go index a8a3592e36..0839d1b82c 100755 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -369,11 +369,16 @@ type RolloutTrafficRouting struct { AppMesh *AppMeshTrafficRouting `json:"appMesh,omitempty" protobuf:"bytes,6,opt,name=appMesh"` // Traefik holds specific configuration to use Traefik to route traffic Traefik *TraefikTrafficRouting `json:"traefik,omitempty" protobuf:"bytes,7,opt,name=traefik"` - // A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream + // ManagedRoutes A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream // traffic router. ManagedRoutes []MangedRoutes `json:"managedRoutes,omitempty" protobuf:"bytes,8,rep,name=managedRoutes"` // Apisix holds specific configuration to use Apisix to route traffic Apisix *ApisixTrafficRouting `json:"apisix,omitempty" protobuf:"bytes,9,opt,name=apisix"` + // +kubebuilder:validation:Schemaless + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Type=object + // Plugins holds specific configuration that traffic router plugins can use for routing traffic + Plugins map[string]json.RawMessage `json:"plugins,omitempty" protobuf:"bytes,10,opt,name=plugins"` } type MangedRoutes struct { diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index 066977cab8..0957d4f725 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -2371,6 +2371,21 @@ func (in *RolloutTrafficRouting) DeepCopyInto(out *RolloutTrafficRouting) { *out = new(ApisixTrafficRouting) (*in).DeepCopyInto(*out) } + if in.Plugins != nil { + in, out := &in.Plugins, &out.Plugins + *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/rollout/trafficrouting.go b/rollout/trafficrouting.go index 6064b60dad..c00eb66e1b 100644 --- a/rollout/trafficrouting.go +++ b/rollout/trafficrouting.go @@ -6,6 +6,8 @@ import ( "strconv" "strings" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/alb" @@ -105,6 +107,21 @@ func (c *Controller) NewTrafficRoutingReconciler(roCtx *rolloutContext) ([]traff })) } + if rollout.Spec.Strategy.Canary.TrafficRouting.Plugins != nil { + for pluginName := range rollout.Spec.Strategy.Canary.TrafficRouting.Plugins { + pluginReconciler, err := plugin.NewReconciler(&plugin.ReconcilerConfig{ + Rollout: rollout, + Client: c.kubeclientset, + Recorder: c.recorder, + PluginName: pluginName, + }) + if err != nil { + return trafficReconcilers, err + } + trafficReconcilers = append(trafficReconcilers, pluginReconciler) + } + } + // ensure that the trafficReconcilers is a healthy list and its not empty if len(trafficReconcilers) > 0 { return trafficReconcilers, nil diff --git a/rollout/trafficrouting/plugin/client/client.go b/rollout/trafficrouting/plugin/client/client.go new file mode 100644 index 0000000000..6350c2c578 --- /dev/null +++ b/rollout/trafficrouting/plugin/client/client.go @@ -0,0 +1,101 @@ +package client + +import ( + "fmt" + "os/exec" + "sync" + + "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/plugin" + goPlugin "github.com/hashicorp/go-plugin" +) + +type trafficPlugin struct { + pluginClient map[string]*goPlugin.Client + plugin map[string]rpc.TrafficRouterPlugin +} + +var pluginClients *trafficPlugin +var once sync.Once +var mutex sync.Mutex + +var handshakeConfig = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "trafficrouter", +} + +// pluginMap is the map of plugins we can dispense. +var pluginMap = map[string]goPlugin.Plugin{ + "RpcTrafficRouterPlugin": &rpc.RpcTrafficRouterPlugin{}, +} + +// GetTrafficPlugin returns a singleton plugin client for the given traffic router plugin. Calling this multiple times +// returns the same plugin client instance for the plugin name defined in the rollout object. +func GetTrafficPlugin(pluginName string) (rpc.TrafficRouterPlugin, error) { + once.Do(func() { + pluginClients = &trafficPlugin{ + pluginClient: make(map[string]*goPlugin.Client), + plugin: make(map[string]rpc.TrafficRouterPlugin), + } + }) + plugin, err := pluginClients.startPlugin(pluginName) + if err != nil { + return nil, fmt.Errorf("unable to start plugin system: %w", err) + } + return plugin, nil +} + +func (t *trafficPlugin) startPlugin(pluginName string) (rpc.TrafficRouterPlugin, error) { + mutex.Lock() + defer mutex.Unlock() + + if t.pluginClient[pluginName] == nil || t.pluginClient[pluginName].Exited() { + + pluginPath, err := plugin.GetPluginLocation(pluginName) + if err != nil { + return nil, fmt.Errorf("unable to find plugin (%s): %w", pluginName, err) + } + + t.pluginClient[pluginName] = goPlugin.NewClient(&goPlugin.ClientConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + Cmd: exec.Command(pluginPath), + Managed: true, + }) + + rpcClient, err := t.pluginClient[pluginName].Client() + if err != nil { + return nil, fmt.Errorf("unable to get plugin client (%s): %w", pluginName, err) + } + + // Request the plugin + plugin, err := rpcClient.Dispense("RpcTrafficRouterPlugin") + if err != nil { + return nil, fmt.Errorf("unable to dispense plugin (%s): %w", pluginName, err) + } + + pluginType, ok := plugin.(rpc.TrafficRouterPlugin) + if !ok { + return nil, fmt.Errorf("unexpected type from plugin") + } + t.plugin[pluginName] = pluginType + + resp := t.plugin[pluginName].InitPlugin() + if resp.HasError() { + return nil, fmt.Errorf("unable to initialize plugin via rpc (%s): %w", pluginName, err) + } + } + + client, err := t.pluginClient[pluginName].Client() + if err != nil { + return nil, fmt.Errorf("unable to get plugin client (%s) for ping: %w", pluginName, err) + } + if err := client.Ping(); err != nil { + t.pluginClient[pluginName].Kill() + t.pluginClient[pluginName] = nil + return nil, fmt.Errorf("could not ping plugin will cleanup process so we can restart it next reconcile (%w)", err) + } + + return t.plugin[pluginName], nil +} diff --git a/rollout/trafficrouting/plugin/plugin.go b/rollout/trafficrouting/plugin/plugin.go new file mode 100644 index 0000000000..7ded9ad2f1 --- /dev/null +++ b/rollout/trafficrouting/plugin/plugin.go @@ -0,0 +1,97 @@ +package plugin + +import ( + "fmt" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/client" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/record" + "k8s.io/client-go/kubernetes" +) + +type ReconcilerConfig struct { + Rollout *v1alpha1.Rollout + PluginName string + Client kubernetes.Interface + Recorder record.EventRecorder +} + +type Reconciler struct { + Rollout *v1alpha1.Rollout + PluginName string + Client kubernetes.Interface + Recorder record.EventRecorder + rpc.TrafficRouterPlugin +} + +func NewReconciler(cfg *ReconcilerConfig) (*Reconciler, error) { + pluginClient, err := client.GetTrafficPlugin(cfg.PluginName) + if err != nil { + return nil, fmt.Errorf("failed to get traffic router plugin %s: %w", cfg.PluginName, err) + } + + reconciler := &Reconciler{ + Rollout: cfg.Rollout, + Client: cfg.Client, + Recorder: cfg.Recorder, + PluginName: cfg.PluginName, + TrafficRouterPlugin: pluginClient, + } + return reconciler, nil +} + +// UpdateHash informs a traffic routing reconciler about new canary, stable, and additionalDestination(s) pod hashes +func (r *Reconciler) UpdateHash(canaryHash, stableHash string, additionalDestinations ...v1alpha1.WeightDestination) error { + resp := r.TrafficRouterPlugin.UpdateHash(r.Rollout, canaryHash, stableHash, additionalDestinations) + if resp.HasError() { + return fmt.Errorf("failed to update hash via plugin: %w", resp) + } + return nil +} + +// SetWeight sets the canary weight to the desired weight +func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) error { + resp := r.TrafficRouterPlugin.SetWeight(r.Rollout, desiredWeight, additionalDestinations) + if resp.HasError() { + return fmt.Errorf("failed to set weight via plugin: %w", resp) + } + return nil +} + +// SetHeaderRoute sets the header routing step +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { + resp := r.TrafficRouterPlugin.SetHeaderRoute(r.Rollout, headerRouting) + if resp.HasError() { + return fmt.Errorf("failed to set header route via plugin: %w", resp) + } + return nil +} + +// VerifyWeight returns true if the canary is at the desired weight and additionalDestinations are at the weights specified +// Returns nil if weight verification is not supported or not applicable +func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) (*bool, error) { + verified, errResp := r.TrafficRouterPlugin.VerifyWeight(r.Rollout, desiredWeight, additionalDestinations) + if errResp.HasError() { + return verified.IsVerified(), fmt.Errorf("failed to verify weight via plugin: %w", errResp) + } + return verified.IsVerified(), nil +} + +// SetMirrorRoute sets up the traffic router to mirror traffic to a service +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + resp := r.TrafficRouterPlugin.SetMirrorRoute(r.Rollout, setMirrorRoute) + if resp.HasError() { + return fmt.Errorf("failed to set mirror route via plugin: %w", resp) + } + return nil +} + +// RemoveManagedRoutes Removes all routes that are managed by rollouts by looking at spec.strategy.canary.trafficRouting.managedRoutes +func (r *Reconciler) RemoveManagedRoutes() error { + resp := r.TrafficRouterPlugin.RemoveManagedRoutes(r.Rollout) + if resp.HasError() { + return fmt.Errorf("failed to remove managed routes via plugin: %w", resp) + } + return nil +} diff --git a/rollout/trafficrouting/plugin/rpc/rpc.go b/rollout/trafficrouting/plugin/rpc/rpc.go new file mode 100644 index 0000000000..78cb4103d9 --- /dev/null +++ b/rollout/trafficrouting/plugin/rpc/rpc.go @@ -0,0 +1,280 @@ +package rpc + +import ( + "encoding/gob" + "fmt" + "net/rpc" + + "github.com/argoproj/argo-rollouts/utils/plugin/types" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/hashicorp/go-plugin" +) + +type UpdateHashArgs struct { + Rollout v1alpha1.Rollout + CanaryHash string + StableHash string + AdditionalDestinations []v1alpha1.WeightDestination +} + +type SetWeightAndVerifyWeightArgs struct { + Rollout v1alpha1.Rollout + DesiredWeight int32 + AdditionalDestinations []v1alpha1.WeightDestination +} + +type SetHeaderArgs struct { + Rollout v1alpha1.Rollout + SetHeaderRoute v1alpha1.SetHeaderRoute +} + +type SetMirrorArgs struct { + Rollout v1alpha1.Rollout + SetMirrorRoute v1alpha1.SetMirrorRoute +} + +type RemoveManagedRoutesArgs struct { + Rollout v1alpha1.Rollout +} + +type VerifyWeightResponse struct { + Verified types.RpcVerified + Err types.RpcError +} + +func init() { + gob.RegisterName("UpdateHashArgs", new(UpdateHashArgs)) + gob.RegisterName("SetWeightAndVerifyWeightArgs", new(SetWeightAndVerifyWeightArgs)) + gob.RegisterName("SetHeaderArgs", new(SetHeaderArgs)) + gob.RegisterName("SetMirrorArgs", new(SetMirrorArgs)) + gob.RegisterName("RemoveManagedRoutesArgs", new(RemoveManagedRoutesArgs)) +} + +// TrafficRouterPlugin 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 TrafficRouterPlugin interface { + InitPlugin() types.RpcError + types.RpcTrafficRoutingReconciler +} + +// TrafficRouterPluginRPC Here is an implementation that talks over RPC +type TrafficRouterPluginRPC struct{ client *rpc.Client } + +// NewTrafficRouterPlugin this is the client aka the controller side function that calls the server side rpc (plugin) +// this gets called once during startup of the plugin and can be used to set up informers or k8s clients etc. +func (g *TrafficRouterPluginRPC) InitPlugin() types.RpcError { + var resp types.RpcError + err := g.client.Call("Plugin.InitPlugin", new(interface{}), &resp) + if err != nil { + return types.RpcError{ErrorString: fmt.Sprintf("InitPlugin rpc call error: %s", err)} + } + return resp +} + +// UpdateHash informs a traffic routing reconciler about new canary, stable, and additionalDestination(s) pod hashes +func (g *TrafficRouterPluginRPC) UpdateHash(rollout *v1alpha1.Rollout, canaryHash string, stableHash string, additionalDestinations []v1alpha1.WeightDestination) types.RpcError { + var resp types.RpcError + var args interface{} = UpdateHashArgs{ + Rollout: *rollout, + CanaryHash: canaryHash, + StableHash: stableHash, + AdditionalDestinations: additionalDestinations, + } + err := g.client.Call("Plugin.UpdateHash", &args, &resp) + if err != nil { + return types.RpcError{ErrorString: fmt.Sprintf("UpdateHash rpc call error: %s", err)} + } + return resp +} + +// SetWeight sets the canary weight to the desired weight +func (g *TrafficRouterPluginRPC) SetWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) types.RpcError { + var resp types.RpcError + var args interface{} = SetWeightAndVerifyWeightArgs{ + Rollout: *rollout, + DesiredWeight: desiredWeight, + AdditionalDestinations: additionalDestinations, + } + err := g.client.Call("Plugin.SetWeight", &args, &resp) + if err != nil { + return types.RpcError{ErrorString: fmt.Sprintf("SetWeight rpc call error: %s", err)} + } + return resp +} + +// SetHeaderRoute sets the header routing step +func (g *TrafficRouterPluginRPC) SetHeaderRoute(rollout *v1alpha1.Rollout, setHeaderRoute *v1alpha1.SetHeaderRoute) types.RpcError { + var resp types.RpcError + var args interface{} = SetHeaderArgs{ + Rollout: *rollout, + SetHeaderRoute: *setHeaderRoute, + } + err := g.client.Call("Plugin.SetHeaderRoute", &args, &resp) + if err != nil { + return types.RpcError{ErrorString: fmt.Sprintf("SetHeaderRoute rpc call error: %s", err)} + } + return resp +} + +// SetMirrorRoute sets up the traffic router to mirror traffic to a service +func (g *TrafficRouterPluginRPC) SetMirrorRoute(rollout *v1alpha1.Rollout, setMirrorRoute *v1alpha1.SetMirrorRoute) types.RpcError { + var resp types.RpcError + var args interface{} = SetMirrorArgs{ + Rollout: *rollout, + SetMirrorRoute: *setMirrorRoute, + } + err := g.client.Call("Plugin.SetMirrorRoute", &args, &resp) + if err != nil { + return types.RpcError{ErrorString: fmt.Sprintf("SetMirrorRoute rpc call error: %s", err)} + } + return resp +} + +// Type returns the type of the traffic routing reconciler +func (g *TrafficRouterPluginRPC) Type() string { + var resp string + err := g.client.Call("Plugin.Type", new(interface{}), &resp) + if err != nil { + return fmt.Sprintf("Type rpc call error: %s", err) + } + + return resp +} + +// VerifyWeight returns true if the canary is at the desired weight and additionalDestinations are at the weights specified +// Returns nil if weight verification is not supported or not applicable +func (g *TrafficRouterPluginRPC) VerifyWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) (types.RpcVerified, types.RpcError) { + var resp VerifyWeightResponse + var args interface{} = SetWeightAndVerifyWeightArgs{ + Rollout: *rollout, + DesiredWeight: desiredWeight, + AdditionalDestinations: additionalDestinations, + } + err := g.client.Call("Plugin.VerifyWeight", &args, &resp) + if err != nil { + return types.NotVerified, types.RpcError{ErrorString: fmt.Sprintf("VerifyWeight rpc call error: %s", err)} + } + return resp.Verified, resp.Err +} + +// RemoveAllRoutes Removes all routes that are managed by rollouts by looking at spec.strategy.canary.trafficRouting.managedRoutes +func (g *TrafficRouterPluginRPC) RemoveManagedRoutes(rollout *v1alpha1.Rollout) types.RpcError { + var resp types.RpcError + var args interface{} = RemoveManagedRoutesArgs{ + Rollout: *rollout, + } + err := g.client.Call("Plugin.RemoveManagedRoutes", &args, &resp) + if err != nil { + return types.RpcError{ErrorString: fmt.Sprintf("RemoveManagedRoutes rpc call error: %s", err)} + } + return resp +} + +// TrafficRouterRPCServer Here is the RPC server that MetricsPluginRPC talks to, conforming to +// the requirements of net/rpc +type TrafficRouterRPCServer struct { + // This is the real implementation + Impl TrafficRouterPlugin +} + +// InitPlugin this is the server aka the controller side function that receives calls from the client side rpc (controller) +// this gets called once during startup of the plugin and can be used to set up informers or k8s clients etc. +func (s *TrafficRouterRPCServer) InitPlugin(args interface{}, resp *types.RpcError) error { + *resp = s.Impl.InitPlugin() + return nil +} + +// UpdateHash informs a traffic routing reconciler about new canary, stable, and additionalDestination(s) pod hashes +func (s *TrafficRouterRPCServer) UpdateHash(args interface{}, resp *types.RpcError) error { + runArgs, ok := args.(*UpdateHashArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.UpdateHash(&runArgs.Rollout, runArgs.CanaryHash, runArgs.StableHash, runArgs.AdditionalDestinations) + return nil +} + +// SetWeight sets the canary weight to the desired weight +func (s *TrafficRouterRPCServer) SetWeight(args interface{}, resp *types.RpcError) error { + setWeigthArgs, ok := args.(*SetWeightAndVerifyWeightArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.SetWeight(&setWeigthArgs.Rollout, setWeigthArgs.DesiredWeight, setWeigthArgs.AdditionalDestinations) + return nil +} + +// SetHeaderRoute sets the header routing step +func (s *TrafficRouterRPCServer) SetHeaderRoute(args interface{}, resp *types.RpcError) error { + setHeaderArgs, ok := args.(*SetHeaderArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.SetHeaderRoute(&setHeaderArgs.Rollout, &setHeaderArgs.SetHeaderRoute) + return nil +} + +// SetMirrorRoute sets up the traffic router to mirror traffic to a service +func (s *TrafficRouterRPCServer) SetMirrorRoute(args interface{}, resp *types.RpcError) error { + setMirrorArgs, ok := args.(*SetMirrorArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.SetMirrorRoute(&setMirrorArgs.Rollout, &setMirrorArgs.SetMirrorRoute) + return nil +} + +// Type returns the type of the traffic routing reconciler +func (s *TrafficRouterRPCServer) Type(args interface{}, resp *string) error { + *resp = s.Impl.Type() + return nil +} + +// VerifyWeight returns true if the canary is at the desired weight and additionalDestinations are at the weights specified +// Returns nil if weight verification is not supported or not applicable +func (s *TrafficRouterRPCServer) VerifyWeight(args interface{}, resp *VerifyWeightResponse) error { + verifyWeightArgs, ok := args.(*SetWeightAndVerifyWeightArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + verified, err := s.Impl.VerifyWeight(&verifyWeightArgs.Rollout, verifyWeightArgs.DesiredWeight, verifyWeightArgs.AdditionalDestinations) + *resp = VerifyWeightResponse{ + Verified: verified, + Err: err, + } + return nil +} + +// RemoveAllRoutes Removes all routes that are managed by rollouts by looking at spec.strategy.canary.trafficRouting.managedRoutes +func (s *TrafficRouterRPCServer) RemoveManagedRoutes(args interface{}, resp *types.RpcError) error { + removeManagedRoutesArgs, ok := args.(*RemoveManagedRoutesArgs) + if !ok { + return fmt.Errorf("invalid args %s", args) + } + *resp = s.Impl.RemoveManagedRoutes(&removeManagedRoutesArgs.Rollout) + return nil +} + +// RpcTrafficRouterPlugin 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 RpcTrafficRouterPlugin struct { + // Impl Injection + Impl TrafficRouterPlugin +} + +func (p *RpcTrafficRouterPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return &TrafficRouterRPCServer{Impl: p.Impl}, nil +} + +func (RpcTrafficRouterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &TrafficRouterPluginRPC{client: c}, nil +} diff --git a/rollout/trafficrouting/plugin/rpc/rpc_test.go b/rollout/trafficrouting/plugin/rpc/rpc_test.go new file mode 100644 index 0000000000..5ca189e186 --- /dev/null +++ b/rollout/trafficrouting/plugin/rpc/rpc_test.go @@ -0,0 +1,177 @@ +package rpc + +import ( + "context" + "testing" + "time" + + "github.com/argoproj/argo-rollouts/utils/plugin/types" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + goPlugin "github.com/hashicorp/go-plugin" + "github.com/tj/assert" +) + +var testHandshake = goPlugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", + MagicCookieValue: "trafficrouter", +} + +func pluginClient(t *testing.T) (TrafficRouterPlugin, 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{ + "RpcTrafficRouterPlugin": &RpcTrafficRouterPlugin{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("RpcTrafficRouterPlugin") + if err != nil { + t.Fail() + } + + plugin, ok := raw.(TrafficRouterPlugin) + if !ok { + t.Fail() + } + + return plugin, client, cancel, closeCh +} + +func TestPlugin(t *testing.T) { + plugin, _, cancel, closeCh := pluginClient(t) + defer cancel() + + err := plugin.InitPlugin() + if err.Error() != "" { + t.Fail() + } + + ro := v1alpha1.Rollout{} + + err = plugin.RemoveManagedRoutes(&ro) + assert.Equal(t, "", err.Error()) + + err = plugin.SetMirrorRoute(&ro, &v1alpha1.SetMirrorRoute{}) + assert.Equal(t, "", err.Error()) + + err = plugin.SetHeaderRoute(&ro, &v1alpha1.SetHeaderRoute{}) + assert.Equal(t, "", err.Error()) + + err = plugin.SetWeight(&ro, 0, []v1alpha1.WeightDestination{}) + assert.Equal(t, "", err.Error()) + + b, err := plugin.VerifyWeight(&ro, 0, []v1alpha1.WeightDestination{}) + assert.Equal(t, "", err.Error()) + assert.Equal(t, true, *b.IsVerified()) + + err = plugin.UpdateHash(&ro, "canary-hash", "stable-hash", []v1alpha1.WeightDestination{}) + assert.Equal(t, "", err.Error()) + + typeString := plugin.Type() + assert.Equal(t, "TestRPCPlugin", typeString) + + // Canceling should cause an exit + cancel() + <-closeCh +} + +func TestPluginClosedConnection(t *testing.T) { + plugin, client, cancel, closeCh := pluginClient(t) + defer cancel() + + client.Close() + time.Sleep(100 * time.Millisecond) + + const expectedError = "connection is shut down" + + err := plugin.InitPlugin() + assert.Contains(t, err.Error(), expectedError) + + err = plugin.RemoveManagedRoutes(&v1alpha1.Rollout{}) + assert.Contains(t, err.Error(), expectedError) + + err = plugin.SetMirrorRoute(&v1alpha1.Rollout{}, &v1alpha1.SetMirrorRoute{}) + assert.Contains(t, err.Error(), expectedError) + + err = plugin.SetHeaderRoute(&v1alpha1.Rollout{}, &v1alpha1.SetHeaderRoute{}) + assert.Contains(t, err.Error(), expectedError) + + err = plugin.SetWeight(&v1alpha1.Rollout{}, 0, []v1alpha1.WeightDestination{}) + assert.Contains(t, err.Error(), expectedError) + + _, err = plugin.VerifyWeight(&v1alpha1.Rollout{}, 0, []v1alpha1.WeightDestination{}) + assert.Contains(t, err.Error(), expectedError) + + cancel() + <-closeCh +} + +func TestInvalidArgs(t *testing.T) { + server := TrafficRouterRPCServer{} + badtype := struct { + Args string + }{} + + var errRpc types.RpcError + err := server.SetMirrorRoute(badtype, &errRpc) + assert.Error(t, err) + + err = server.RemoveManagedRoutes(badtype, &errRpc) + assert.Error(t, err) + + var vw VerifyWeightResponse + err = server.VerifyWeight(badtype, &vw) + assert.Error(t, err) + + err = server.SetMirrorRoute(badtype, &errRpc) + assert.Error(t, err) + + err = server.SetHeaderRoute(badtype, &errRpc) + assert.Error(t, err) + + err = server.SetWeight(badtype, &errRpc) + assert.Error(t, err) + + err = server.UpdateHash(badtype, &errRpc) + assert.Error(t, err) +} diff --git a/rollout/trafficrouting/plugin/rpc/rpc_test_implementation.go b/rollout/trafficrouting/plugin/rpc/rpc_test_implementation.go new file mode 100644 index 0000000000..5e3a462bab --- /dev/null +++ b/rollout/trafficrouting/plugin/rpc/rpc_test_implementation.go @@ -0,0 +1,42 @@ +package rpc + +import ( + "github.com/argoproj/argo-rollouts/utils/plugin/types" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +) + +type testRpcPlugin struct{} + +func (p *testRpcPlugin) InitPlugin() types.RpcError { + return types.RpcError{} +} + +func (r *testRpcPlugin) SetWeight(ro *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) types.RpcError { + return types.RpcError{} +} + +func (r *testRpcPlugin) SetHeaderRoute(ro *v1alpha1.Rollout, headerRouting *v1alpha1.SetHeaderRoute) types.RpcError { + return types.RpcError{} +} + +func (r *testRpcPlugin) VerifyWeight(ro *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) (types.RpcVerified, types.RpcError) { + return types.Verified, types.RpcError{} +} + +// UpdateHash informs a traffic routing reconciler about new canary/stable pod hashes +func (r *testRpcPlugin) UpdateHash(ro *v1alpha1.Rollout, canaryHash, stableHash string, additionalDestinations []v1alpha1.WeightDestination) types.RpcError { + return types.RpcError{} +} + +func (r *testRpcPlugin) SetMirrorRoute(ro *v1alpha1.Rollout, setMirrorRoute *v1alpha1.SetMirrorRoute) types.RpcError { + return types.RpcError{} +} + +func (r *testRpcPlugin) RemoveManagedRoutes(ro *v1alpha1.Rollout) types.RpcError { + return types.RpcError{} +} + +func (r *testRpcPlugin) Type() string { + return "TestRPCPlugin" +} diff --git a/test/cmd/sample-metrics-plugin/internal/plugin/plugin.go b/test/cmd/sample-metrics-plugin/internal/plugin/plugin.go index bbe3f7ce28..69d3763469 100644 --- a/test/cmd/sample-metrics-plugin/internal/plugin/plugin.go +++ b/test/cmd/sample-metrics-plugin/internal/plugin/plugin.go @@ -9,6 +9,8 @@ import ( "os" "time" + "github.com/argoproj/argo-rollouts/metricproviders/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/plugin/types" "github.com/argoproj/argo-rollouts/metricproviders/plugin" @@ -24,10 +26,11 @@ import ( const EnvVarArgoRolloutsPrometheusAddress string = "ARGO_ROLLOUTS_PROMETHEUS_ADDRESS" -// Here is a real implementation of MetricsPlugin +var _ rpc.MetricProviderPlugin = &RpcPlugin{} + +// Here is a real implementation of MetricProviderPlugin type RpcPlugin struct { LogCtx log.Entry - api v1.API } type Config struct { @@ -37,16 +40,7 @@ type Config struct { 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 - +func (g *RpcPlugin) InitPlugin() types.RpcError { return types.RpcError{} } @@ -57,12 +51,20 @@ func (g *RpcPlugin) Run(anaysisRun *v1alpha1.AnalysisRun, metric v1alpha1.Metric } config := Config{} - json.Unmarshal(metric.Provider.Plugin["prometheus"], &config) + err := json.Unmarshal(metric.Provider.Plugin["argoproj/sample-prometheus"], &config) + if err != nil { + return metricutil.MarkMeasurementError(newMeasurement, err) + } + + api, err := newPrometheusAPI(config.Address) + if err != nil { + return metricutil.MarkMeasurementError(newMeasurement, err) + } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - response, warnings, err := g.api.Query(ctx, config.Query, time.Now()) + response, warnings, err := api.Query(ctx, config.Query, time.Now()) if err != nil { return metricutil.MarkMeasurementError(newMeasurement, err) } @@ -111,7 +113,7 @@ func (g *RpcPlugin) GetMetadata(metric v1alpha1.Metric) map[string]string { metricsMetadata := make(map[string]string) config := Config{} - json.Unmarshal(metric.Provider.Plugin["prometheus"], &config) + json.Unmarshal(metric.Provider.Plugin["argoproj/sample-prometheus"], &config) if config.Query != "" { metricsMetadata["ResolvedPrometheusQuery"] = config.Query } diff --git a/test/cmd/sample-metrics-plugin/internal/plugin/plugin_test.go b/test/cmd/sample-metrics-plugin/internal/plugin/plugin_test.go index fc66bc0398..9447019449 100644 --- a/test/cmd/sample-metrics-plugin/internal/plugin/plugin_test.go +++ b/test/cmd/sample-metrics-plugin/internal/plugin/plugin_test.go @@ -2,13 +2,11 @@ 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" @@ -36,7 +34,7 @@ func TestRunSuccessfully(t *testing.T) { // pluginMap is the map of plugins we can dispense. var pluginMap = map[string]goPlugin.Plugin{ - "RpcMetricsPlugin": &rpc.RpcMetricsPlugin{Impl: rpcPluginImp}, + "RpcMetricProviderPlugin": &rpc.RpcMetricProviderPlugin{Impl: rpcPluginImp}, } ch := make(chan *goPlugin.ReattachConfig, 1) @@ -86,18 +84,14 @@ func TestRunSuccessfully(t *testing.T) { } // Request the plugin - raw, err := client.Dispense("RpcMetricsPlugin") + raw, err := client.Dispense("RpcMetricProviderPlugin") if err != nil { t.Fail() } - plugin := raw.(rpc.MetricsPlugin) + plugin := raw.(rpc.MetricProviderPlugin) - err = plugin.NewMetricsPlugin(v1alpha1.Metric{ - Provider: v1alpha1.MetricProvider{ - Plugin: map[string]json.RawMessage{"prometheus": json.RawMessage(`{"address":"http://prometheus.local", "query":"machine_cpu_cores"}`)}, - }, - }) + err = plugin.InitPlugin() if err != nil { t.Fail() } diff --git a/test/cmd/sample-metrics-plugin/main.go b/test/cmd/sample-metrics-plugin/main.go index ed4a9ec7d5..703d823e22 100644 --- a/test/cmd/sample-metrics-plugin/main.go +++ b/test/cmd/sample-metrics-plugin/main.go @@ -14,7 +14,7 @@ import ( var handshakeConfig = goPlugin.HandshakeConfig{ ProtocolVersion: 1, MagicCookieKey: "ARGO_ROLLOUTS_RPC_PLUGIN", - MagicCookieValue: "metrics", + MagicCookieValue: "metricprovider", } func main() { @@ -25,7 +25,7 @@ func main() { } // pluginMap is the map of plugins we can dispense. var pluginMap = map[string]goPlugin.Plugin{ - "RpcMetricsPlugin": &rpc.RpcMetricsPlugin{Impl: rpcPluginImp}, + "RpcMetricProviderPlugin": &rpc.RpcMetricProviderPlugin{Impl: rpcPluginImp}, } logCtx.Debug("message from plugin", "foo", "bar") diff --git a/test/cmd/sample-trafficrouter-plugin/internal/plugin/plugin.go b/test/cmd/sample-trafficrouter-plugin/internal/plugin/plugin.go new file mode 100644 index 0000000000..0d18c5d069 --- /dev/null +++ b/test/cmd/sample-trafficrouter-plugin/internal/plugin/plugin.go @@ -0,0 +1,351 @@ +package plugin + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" + "github.com/argoproj/argo-rollouts/utils/defaults" + ingressutil "github.com/argoproj/argo-rollouts/utils/ingress" + pluginTypes "github.com/argoproj/argo-rollouts/utils/plugin/types" + "github.com/sirupsen/logrus" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + networkingv1 "k8s.io/api/networking/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + kubeinformers "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" +) + +var _ rpc.TrafficRouterPlugin = &RpcPlugin{} + +type RpcPlugin struct { + LogCtx *logrus.Entry + ingressWrapper *ingressutil.IngressWrap +} + +func (p *RpcPlugin) InitPlugin() pluginTypes.RpcError { + p.LogCtx.Info("InitPlugin") + + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + // if you want to change the loading rules (which files in which order), you can do so here + configOverrides := &clientcmd.ConfigOverrides{} + // if you want to change override values or bind them to flags, there are methods to help you + kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides) + config, err := kubeConfig.ClientConfig() + if err != nil { + return pluginTypes.RpcError{ErrorString: err.Error()} + } + kubeClient, _ := kubernetes.NewForConfig(config) + + kubeInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions( + kubeClient, + 5*time.Minute, + kubeinformers.WithNamespace(metav1.NamespaceAll)) + + mode, _ := ingressutil.DetermineIngressMode("", kubeClient.DiscoveryClient) + ingressWrapper, _ := ingressutil.NewIngressWrapper(mode, kubeClient, kubeInformerFactory) + p.ingressWrapper = ingressWrapper + go p.ingressWrapper.Informer().Run(context.Background().Done()) + cache.WaitForCacheSync(context.Background().Done(), p.ingressWrapper.Informer().HasSynced) + return pluginTypes.RpcError{} +} + +func (r *RpcPlugin) buildCanaryIngress(ro *v1alpha1.Rollout, stableIngress *networkingv1.Ingress, name string, desiredWeight int32) (*ingressutil.Ingress, error) { + stableIngressName := "canary-demo" + stableServiceName := ro.Spec.Strategy.Canary.StableService + canaryServiceName := ro.Spec.Strategy.Canary.CanaryService + annotationPrefix := defaults.GetCanaryIngressAnnotationPrefixOrDefault(ro) + + // Set up canary ingress resource, we do *not* have to duplicate `spec.tls` in a canary, only + // `spec.rules` + desiredCanaryIngress := &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: map[string]string{}, + }, + Spec: networkingv1.IngressSpec{ + Rules: make([]networkingv1.IngressRule, 0), // We have no way of knowing yet how many rules there will be + }, + } + + // Preserve ingressClassName from stable ingress + if stableIngress.Spec.IngressClassName != nil { + desiredCanaryIngress.Spec.IngressClassName = stableIngress.Spec.IngressClassName + } + + // Must preserve ingress.class on canary ingress, no other annotations matter + // See: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary + if val, ok := stableIngress.Annotations["kubernetes.io/ingress.class"]; ok { + desiredCanaryIngress.Annotations["kubernetes.io/ingress.class"] = val + } + + // Ensure canaryIngress is owned by this Rollout for cleanup + desiredCanaryIngress.SetOwnerReferences([]metav1.OwnerReference{*metav1.NewControllerRef(ro, v1alpha1.SchemeGroupVersion.WithKind("Rollout"))}) + + // Copy only the rules which reference the stableService from the stableIngress to the canaryIngress + // and change service backend to canaryService. Rules **not** referencing the stableIngress will be ignored. + for ir := 0; ir < len(stableIngress.Spec.Rules); ir++ { + var hasStableServiceBackendRule bool + ingressRule := stableIngress.Spec.Rules[ir].DeepCopy() + + // Update all backends pointing to the stableService to point to the canaryService now + for ip := 0; ip < len(ingressRule.HTTP.Paths); ip++ { + if ingressRule.HTTP.Paths[ip].Backend.Service.Name == stableServiceName { + hasStableServiceBackendRule = true + ingressRule.HTTP.Paths[ip].Backend.Service.Name = canaryServiceName + } + } + + // If this rule was using the specified stableService backend, append it to the canary Ingress spec + if hasStableServiceBackendRule { + desiredCanaryIngress.Spec.Rules = append(desiredCanaryIngress.Spec.Rules, *ingressRule) + } + } + + if len(desiredCanaryIngress.Spec.Rules) == 0 { + return nil, fmt.Errorf("ingress `%s` has no rules using service %s backend", stableIngressName, stableServiceName) + } + + // Process additional annotations, would commonly be things like `canary-by-header` or `load-balance` + //for k, v := range ro.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalIngressAnnotations { + // if !strings.HasPrefix(k, annotationPrefix) { + // k = fmt.Sprintf("%s/%s", annotationPrefix, k) + // } + // desiredCanaryIngress.Annotations[k] = v + //} + // Always set `canary` and `canary-weight` - `canary-by-header` and `canary-by-cookie`, if set, will always take precedence + desiredCanaryIngress.Annotations[fmt.Sprintf("%s/canary", annotationPrefix)] = "true" + desiredCanaryIngress.Annotations[fmt.Sprintf("%s/canary-weight", annotationPrefix)] = fmt.Sprintf("%d", desiredWeight) + + return ingressutil.NewIngress(desiredCanaryIngress), nil +} + +func (r *RpcPlugin) buildLegacyCanaryIngress(ro *v1alpha1.Rollout, stableIngress *extensionsv1beta1.Ingress, name string, desiredWeight int32) (*ingressutil.Ingress, error) { + stableIngressName := "canary-demo" + stableServiceName := ro.Spec.Strategy.Canary.StableService + canaryServiceName := ro.Spec.Strategy.Canary.CanaryService + annotationPrefix := defaults.GetCanaryIngressAnnotationPrefixOrDefault(ro) + + // Set up canary ingress resource, we do *not* have to duplicate `spec.tls` in a canary, only + // `spec.rules` + desiredCanaryIngress := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: map[string]string{}, + }, + Spec: extensionsv1beta1.IngressSpec{ + Rules: make([]extensionsv1beta1.IngressRule, 0), // We have no way of knowing yet how many rules there will be + }, + } + + // Preserve ingressClassName from stable ingress + if stableIngress.Spec.IngressClassName != nil { + desiredCanaryIngress.Spec.IngressClassName = stableIngress.Spec.IngressClassName + } + + // Must preserve ingress.class on canary ingress, no other annotations matter + // See: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary + if val, ok := stableIngress.Annotations["kubernetes.io/ingress.class"]; ok { + desiredCanaryIngress.Annotations["kubernetes.io/ingress.class"] = val + } + + // Ensure canaryIngress is owned by this Rollout for cleanup + desiredCanaryIngress.SetOwnerReferences([]metav1.OwnerReference{*metav1.NewControllerRef(ro, v1alpha1.SchemeGroupVersion.WithKind("Rollout"))}) + + // Copy only the rules which reference the stableService from the stableIngress to the canaryIngress + // and change service backend to canaryService. Rules **not** referencing the stableIngress will be ignored. + for ir := 0; ir < len(stableIngress.Spec.Rules); ir++ { + var hasStableServiceBackendRule bool + ingressRule := stableIngress.Spec.Rules[ir].DeepCopy() + + // Update all backends pointing to the stableService to point to the canaryService now + for ip := 0; ip < len(ingressRule.HTTP.Paths); ip++ { + if ingressRule.HTTP.Paths[ip].Backend.ServiceName == stableServiceName { + hasStableServiceBackendRule = true + ingressRule.HTTP.Paths[ip].Backend.ServiceName = canaryServiceName + } + } + + // If this rule was using the specified stableService backend, append it to the canary Ingress spec + if hasStableServiceBackendRule { + desiredCanaryIngress.Spec.Rules = append(desiredCanaryIngress.Spec.Rules, *ingressRule) + } + } + + if len(desiredCanaryIngress.Spec.Rules) == 0 { + return nil, fmt.Errorf("ingress `%s` has no rules using service %s backend", stableIngressName, stableServiceName) + } + + // Process additional annotations, would commonly be things like `canary-by-header` or `load-balance` + for k, v := range ro.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalIngressAnnotations { + if !strings.HasPrefix(k, annotationPrefix) { + k = fmt.Sprintf("%s/%s", annotationPrefix, k) + } + desiredCanaryIngress.Annotations[k] = v + } + // Always set `canary` and `canary-weight` - `canary-by-header` and `canary-by-cookie`, if set, will always take precedence + desiredCanaryIngress.Annotations[fmt.Sprintf("%s/canary", annotationPrefix)] = "true" + desiredCanaryIngress.Annotations[fmt.Sprintf("%s/canary-weight", annotationPrefix)] = fmt.Sprintf("%d", desiredWeight) + + return ingressutil.NewLegacyIngress(desiredCanaryIngress), nil +} + +// canaryIngress returns the desired state of the canary ingress +func (r *RpcPlugin) canaryIngress(ro *v1alpha1.Rollout, stableIngress *ingressutil.Ingress, name string, desiredWeight int32) (*ingressutil.Ingress, error) { + switch stableIngress.Mode() { + case ingressutil.IngressModeNetworking: + networkingIngress, err := stableIngress.GetNetworkingIngress() + if err != nil { + return nil, err + } + return r.buildCanaryIngress(ro, networkingIngress, name, desiredWeight) + case ingressutil.IngressModeExtensions: + extensionsIngress, err := stableIngress.GetExtensionsIngress() + if err != nil { + return nil, err + } + return r.buildLegacyCanaryIngress(ro, extensionsIngress, name, desiredWeight) + default: + return nil, errors.New("undefined ingress mode") + } +} + +// SetWeight modifies Nginx Ingress resources to reach desired state +func (r *RpcPlugin) SetWeight(ro *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) pluginTypes.RpcError { + ctx := context.TODO() + + s := v1alpha1.NginxTrafficRouting{} + err := json.Unmarshal(ro.Spec.Strategy.Canary.TrafficRouting.Plugins["argoproj/sample-nginx"], &s) + if err != nil { + return pluginTypes.RpcError{ErrorString: "could not unmarshal nginx config"} + } + + stableIngressName := s.StableIngress + canaryIngressName := getCanaryIngressName(ro, stableIngressName) + + // Check if stable ingress exists (from lister, which has a cache), error if it does not + stableIngress, err := r.ingressWrapper.GetCached(ro.Namespace, stableIngressName) + if err != nil { + return pluginTypes.RpcError{ErrorString: fmt.Sprintf("error retrieving stableIngress `%s` from cache: %v", stableIngressName, err)} + } + // Check if canary ingress exists (from lister which has a cache), determines whether we later call Create() or Update() + canaryIngress, err := r.ingressWrapper.GetCached(ro.Namespace, canaryIngressName) + + canaryIngressExists := true + if err != nil { + if !k8serrors.IsNotFound(err) { + // An error other than "not found" occurred + return pluginTypes.RpcError{ErrorString: fmt.Sprintf("error retrieving canary ingress `%s` from cache: %v", canaryIngressName, err)} + } + canaryIngressExists = false + } + + // Construct the desired canary Ingress resource + desiredCanaryIngress, err := r.canaryIngress(ro, stableIngress, canaryIngressName, desiredWeight) + if err != nil { + return pluginTypes.RpcError{ErrorString: err.Error()} + } + + if !canaryIngressExists { + _, err = r.ingressWrapper.Create(ctx, ro.Namespace, desiredCanaryIngress, metav1.CreateOptions{}) + if err == nil { + return pluginTypes.RpcError{} + } + if !k8serrors.IsAlreadyExists(err) { + //r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error creating canary ingress") + return pluginTypes.RpcError{ErrorString: fmt.Sprintf("error creating canary ingress `%s`: %v", canaryIngressName, err)} + } + // Canary ingress was created by a different reconcile call before this one could complete (race) + // This means we just read it from the API now (instead of cache) and continue with the normal + // flow we take when the canary already existed. + canaryIngress, err = r.ingressWrapper.Get(ctx, ro.Namespace, canaryIngressName, metav1.GetOptions{}) + if err != nil { + //r.log.WithField(logutil.IngressKey, canaryIngressName).Error(err.Error()) + return pluginTypes.RpcError{ErrorString: fmt.Sprintf("error retrieving canary ingress `%s` from api: %v", canaryIngressName, err)} + } + } + + // Canary Ingress already exists, apply a patch if needed + + // Only modify canaryIngress if it is controlled by this Rollout + if !metav1.IsControlledBy(canaryIngress.GetObjectMeta(), ro) { + //r.log.WithField(logutil.IngressKey, canaryIngressName).Error("canary ingress controlled by different object") + return pluginTypes.RpcError{ErrorString: fmt.Sprintf("canary ingress `%s` controlled by different object", canaryIngressName)} + } + + // Make patches + patch, modified, err := ingressutil.BuildIngressPatch(canaryIngress.Mode(), canaryIngress, + desiredCanaryIngress, ingressutil.WithAnnotations(), ingressutil.WithLabels(), ingressutil.WithSpec()) + + if err != nil { + //r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error constructing canary ingress patch") + return pluginTypes.RpcError{ErrorString: fmt.Sprintf("error constructing canary ingress patch for `%s`: %v", canaryIngressName, err)} + } + if !modified { + //r.log.WithField(logutil.IngressKey, canaryIngressName).Info("No changes to canary ingress - skipping patch") + return pluginTypes.RpcError{} + } + + //r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("patch", string(patch)).Debug("applying canary Ingress patch") + //r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("desiredWeight", desiredWeight).Info("updating canary Ingress") + //r.cfg.Recorder.Eventf(r.cfg.Rollout, record.EventOptions{EventReason: "PatchingCanaryIngress"}, "Updating Ingress `%s` to desiredWeight '%d'", canaryIngressName, desiredWeight) + + _, err = r.ingressWrapper.Patch(ctx, ro.Namespace, canaryIngressName, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + //r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error patching canary ingress") + return pluginTypes.RpcError{ErrorString: fmt.Sprintf("error patching canary ingress `%s`: %v", canaryIngressName, err)} + } + + return pluginTypes.RpcError{} +} + +func (r *RpcPlugin) SetHeaderRoute(ro *v1alpha1.Rollout, headerRouting *v1alpha1.SetHeaderRoute) pluginTypes.RpcError { + return pluginTypes.RpcError{} +} + +func (r *RpcPlugin) VerifyWeight(ro *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) (pluginTypes.RpcVerified, pluginTypes.RpcError) { + return pluginTypes.NotImplemented, pluginTypes.RpcError{} +} + +// UpdateHash informs a traffic routing reconciler about new canary/stable pod hashes +func (r *RpcPlugin) UpdateHash(ro *v1alpha1.Rollout, canaryHash, stableHash string, additionalDestinations []v1alpha1.WeightDestination) pluginTypes.RpcError { + return pluginTypes.RpcError{} +} + +func (r *RpcPlugin) SetMirrorRoute(ro *v1alpha1.Rollout, setMirrorRoute *v1alpha1.SetMirrorRoute) pluginTypes.RpcError { + return pluginTypes.RpcError{} +} + +func (r *RpcPlugin) RemoveManagedRoutes(ro *v1alpha1.Rollout) pluginTypes.RpcError { + return pluginTypes.RpcError{} +} + +func (r *RpcPlugin) Type() string { + return "plugin-nginx" +} + +func getCanaryIngressName(rollout *v1alpha1.Rollout, stableIngress string) string { + // names limited to 253 characters + if rollout.Spec.Strategy.Canary != nil && + rollout.Spec.Strategy.Canary.TrafficRouting != nil && + rollout.Spec.Strategy.Canary.TrafficRouting.Plugins != nil && + rollout.Spec.Strategy.Canary.TrafficRouting.Plugins["argoproj/sample-nginx"] != nil { + + prefix := fmt.Sprintf("%s-%s", rollout.GetName(), stableIngress) + if len(prefix) > 253-len("-canary") { + // trim prefix + prefix = prefix[0 : 253-len("-canary")] + } + return fmt.Sprintf("%s%s", prefix, "-canary") + } + return "" +} diff --git a/test/cmd/sample-trafficrouter-plugin/main.go b/test/cmd/sample-trafficrouter-plugin/main.go new file mode 100644 index 0000000000..37efcc839b --- /dev/null +++ b/test/cmd/sample-trafficrouter-plugin/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "strings" + + rolloutsPlugin "github.com/argoproj/argo-rollouts/rollout/trafficrouting/plugin/rpc" + "github.com/argoproj/argo-rollouts/test/cmd/sample-trafficrouter-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: "trafficrouter", +} + +func main() { + logCtx := log.WithFields(log.Fields{"plugin": "trafficrouter"}) + + setLogLevel("debug") + log.SetFormatter(createFormatter("text")) + + rpcPluginImp := &plugin.RpcPlugin{ + LogCtx: logCtx, + } + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]goPlugin.Plugin{ + "RpcTrafficRouterPlugin": &rolloutsPlugin.RpcTrafficRouterPlugin{Impl: rpcPluginImp}, + } + + logCtx.Debug("message from plugin", "foo", "bar") + + goPlugin.Serve(&goPlugin.ServeConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + }) +} + +func createFormatter(logFormat string) log.Formatter { + var formatType log.Formatter + switch strings.ToLower(logFormat) { + case "json": + formatType = &log.JSONFormatter{} + case "text": + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + default: + log.Infof("Unknown format: %s. Using text logformat", logFormat) + formatType = &log.TextFormatter{ + FullTimestamp: true, + } + } + + return formatType +} + +func setLogLevel(logLevel string) { + level, err := log.ParseLevel(logLevel) + if err != nil { + log.Fatal(err) + } + log.SetLevel(level) +} diff --git a/ui/src/models/rollout/generated/api.ts b/ui/src/models/rollout/generated/api.ts index 0a5cd66368..37f73731d7 100755 --- a/ui/src/models/rollout/generated/api.ts +++ b/ui/src/models/rollout/generated/api.ts @@ -1566,7 +1566,7 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTraf */ traefik?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1TraefikTrafficRouting; /** - * A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream traffic router. + * ManagedRoutes A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream traffic router. * @type {Array} * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTrafficRouting */ @@ -1577,6 +1577,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTraf * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTrafficRouting */ apisix?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1ApisixTrafficRouting; + /** + * + * @type {{ [key: string]: string; }} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTrafficRouting + */ + plugins?: { [key: string]: string; }; } /** * diff --git a/utils/config/config.go b/utils/config/config.go index 575f44a1a1..a7338cf2ab 100644 --- a/utils/config/config.go +++ b/utils/config/config.go @@ -3,6 +3,7 @@ package config import ( "context" "fmt" + "regexp" "sync" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,12 +19,15 @@ import ( // 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 + plugins []types.PluginItem } var configMemoryCache *Config var mutex sync.RWMutex +// Regex to match plugin names, this matches github username and repo limits +var re = regexp.MustCompile(`^([a-zA-Z0-9\-]+)\/{1}([a-zA-Z0-9_\-.]+)$`) + // 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) { @@ -37,18 +41,28 @@ func InitializeConfig(k8sClientset kubernetes.Interface, configMapName string) ( 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) + var trafficRouterPlugins []types.PluginItem + if err = yaml.Unmarshal([]byte(configMapCluster.Data["trafficRouterPlugins"]), &trafficRouterPlugins); err != nil { + return nil, fmt.Errorf("failed to unmarshal traffic router plugins while initializing: %w", err) + } + + var metricProviderPlugins []types.PluginItem + if err = yaml.Unmarshal([]byte(configMapCluster.Data["metricProviderPlugins"]), &metricProviderPlugins); err != nil { + return nil, fmt.Errorf("failed to unmarshal metric provider plugins while initializing: %w", err) } mutex.Lock() configMemoryCache = &Config{ configMap: configMapCluster, - plugins: plugins, + plugins: append(trafficRouterPlugins, metricProviderPlugins...), } mutex.Unlock() + err = configMemoryCache.ValidateConfig() + if err != nil { + return nil, fmt.Errorf("validation of config due to (%w)", err) + } + return configMemoryCache, nil } @@ -69,13 +83,38 @@ func UnInitializeConfig() { configMemoryCache = nil } -// GetMetricPluginsConfig returns the metric plugins configured in the configmap -func (c *Config) GetMetricPluginsConfig() []types.PluginItem { +// GetAllPlugins returns a flattened list of plugin items. This is useful for iterating over all plugins. +func (c *Config) GetAllPlugins() []types.PluginItem { mutex.RLock() defer mutex.RUnlock() + var copiedPlugins []types.PluginItem - for _, p := range configMemoryCache.plugins.Metrics { - copiedPlugins = append(copiedPlugins, p) - } + copiedPlugins = append(copiedPlugins, configMemoryCache.plugins...) return copiedPlugins } + +// GetPluginDirectoryAndFilename this functions return the directory and file name from a given pluginName such as +// argoproj-labs/sample-plugin +func GetPluginDirectoryAndFilename(pluginName string) (directory string, filename string, err error) { + matches := re.FindAllStringSubmatch(pluginName, -1) + if len(matches) != 1 || len(matches[0]) != 3 { + return "", "", fmt.Errorf("plugin repository (%s) must be in the format of /", pluginName) + } + namespace := matches[0][1] + plugin := matches[0][2] + + return namespace, plugin, nil +} + +func (c *Config) ValidateConfig() error { + mutex.RLock() + defer mutex.RUnlock() + + for _, pluginItem := range c.GetAllPlugins() { + matches := re.FindAllStringSubmatch(pluginItem.Name, -1) + if len(matches) != 1 || len(matches[0]) != 3 { + return fmt.Errorf("plugin repository (%s) must be in the format of /", pluginItem.Name) + } + } + return nil +} diff --git a/utils/plugin/downloader.go b/utils/plugin/downloader.go index 55e1575de6..b8b1ad6263 100644 --- a/utils/plugin/downloader.go +++ b/utils/plugin/downloader.go @@ -10,7 +10,7 @@ import ( "path/filepath" "time" - "github.com/argoproj/argo-rollouts/utils/config" + argoConfig "github.com/argoproj/argo-rollouts/utils/config" "github.com/argoproj/argo-rollouts/utils/defaults" @@ -30,7 +30,7 @@ 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 +// checkPluginExists this function checks if the plugin exists in the configured path on the filesystem func checkPluginExists(pluginLocation string) error { if pluginLocation != "" { //Check for plugin executable existence @@ -88,7 +88,7 @@ func downloadFile(filepath string, url string, downloader FileDownloader) error // DownloadPlugins this function downloads and/or checks that a plugin executable exits on the filesystem func DownloadPlugins(fd FileDownloader) error { - config, err := config.GetConfig() + config, err := argoConfig.GetConfig() if err != nil { return fmt.Errorf("failed to get config: %w", err) } @@ -98,41 +98,47 @@ func DownloadPlugins(fd FileDownloader) error { 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) + for _, plugin := range config.GetAllPlugins() { + urlObj, err := url.ParseRequestURI(plugin.Location) if err != nil { return fmt.Errorf("failed to parse plugin location: %w", err) } - finalFileLocation := filepath.Join(absoluteFilepath, plugin.Name) + dir, pluginFile, err := argoConfig.GetPluginDirectoryAndFilename(plugin.Name) + if err != nil { + return fmt.Errorf("failed to convert plugin name (%s) to directory and filename: (%w)", plugin.Name, err) + } + + finalFolderLocation := filepath.Join(absoluteFilepath, dir) + err = os.MkdirAll(finalFolderLocation, 0700) + if err != nil { + return fmt.Errorf("failed to create plugin folder for plugin (%s): (%w)", plugin.Name, err) + } + + finalFileLocation := filepath.Join(finalFolderLocation, pluginFile) switch urlObj.Scheme { case "http", "https": - log.Infof("Downloading plugin %s from: %s", plugin.Name, plugin.PluginLocation) + log.Infof("Downloading plugin %s from: %s", plugin.Name, plugin.Location) 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) + return fmt.Errorf("failed to download plugin from %s: %w", plugin.Location, err) } timeTakenToDownload := time.Now().Sub(startTime) log.Infof("Download complete, it took %s", timeTakenToDownload) - if plugin.PluginSha256 != "" { - sha256Matched, err := checkShaOfPlugin(finalFileLocation, plugin.PluginSha256) + if plugin.Sha256 != "" { + sha256Matched, err := checkShaOfPlugin(finalFileLocation, plugin.Sha256) 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) + return fmt.Errorf("sha256 hash of downloaded plugin (%s) does not match expected hash", plugin.Location) } } if checkPluginExists(finalFileLocation) != nil { - return fmt.Errorf("failed to find downloaded plugin at location: %s", plugin.PluginLocation) + return fmt.Errorf("failed to find downloaded plugin at location: %s", plugin.Location) } case "file": @@ -144,8 +150,10 @@ func DownloadPlugins(fd FileDownloader) error { if err := copyFile(pluginPath, finalFileLocation); err != nil { return fmt.Errorf("failed to copy plugin from %s to %s: %w", pluginPath, finalFileLocation, err) } + + log.Infof("Copied plugin from %s to %s", pluginPath, finalFileLocation) if checkPluginExists(finalFileLocation) != nil { - return fmt.Errorf("failed to find filebased plugin at location: %s", plugin.PluginLocation) + return fmt.Errorf("failed to find filebased plugin at location: %s", plugin.Location) } // Set the file permissions, to allow execution err = os.Chmod(finalFileLocation, 0700) diff --git a/utils/plugin/downloader_test.go b/utils/plugin/downloader_test.go index 0a0296b796..855b0a6510 100644 --- a/utils/plugin/downloader_test.go +++ b/utils/plugin/downloader_test.go @@ -35,187 +35,219 @@ func (m MockFileDownloader) Get(url string) (*http.Response, error) { }, 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 TestPlugin(t *testing.T) { + t.Run("try to get config without being initialized", func(t *testing.T) { + _, err := config.GetConfig() + assert.Error(t, err) + }) + + t.Run("test initializing and downloading plugins successfully", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: https://test/plugin\n - name: argoproj-labs/http-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.NoError(t, err) + + dir, filename, err := config.GetPluginDirectoryAndFilename("argoproj-labs/http") + assert.NoError(t, err) + + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) + assert.NoError(t, err) + + dir, filename, err = config.GetPluginDirectoryAndFilename("argoproj-labs/http-sha") + assert.NoError(t, err) + + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) + }) + + t.Run("test bad sha", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaults.DefaultRolloutsConfigMapName, + Namespace: defaults.Namespace(), + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http-badsha\n location: https://test/plugin\n sha256: badsha352"}, + } + client := fake.NewSimpleClientset(cm) + + config.UnInitializeConfig() + + _, err := config.InitializeConfig(client, defaults.DefaultRolloutsConfigMapName) + assert.NoError(t, err) + + err = DownloadPlugins(MockFileDownloader{}) + assert.Error(t, err) + + dir, filename, err := config.GetPluginDirectoryAndFilename("argoproj-labs/http-badsha") + assert.NoError(t, err) + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) + }) + + t.Run("test plugin initialization with no configmap found", func(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) + }) + + t.Run("test moving file to plugin location", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaults.DefaultRolloutsConfigMapName, + Namespace: defaults.Namespace(), + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/file-plugin\n location: 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) + + dir, filename, err := config.GetPluginDirectoryAndFilename("argoproj-labs/file-plugin") + assert.NoError(t, err) + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) + }) + + t.Run("test initialzing the config system twice", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: defaults.DefaultRolloutsConfigMapName, + Namespace: defaults.Namespace(), + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: namespace/file-plugin\n location: 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) + + dir, filename, err := config.GetPluginDirectoryAndFilename("namespace/file-plugin") + assert.NoError(t, err) + + err = os.Remove(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) + assert.NoError(t, err) + err = os.RemoveAll(defaults.DefaultRolloutPluginFolder) + assert.NoError(t, err) + }) + + t.Run("test a maformed configmap", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"trafficRouterPlugins": "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) + }) + + t.Run("test malformed pluginLocation", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: 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) + t.Run("test that non existing files on the fs return error", func(t *testing.T) { + err := checkPluginExists("nonexistentplugin") + assert.Error(t, err) + }) + + t.Run("test that if a file exists on the fs we dont error", func(t *testing.T) { + 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) + t.Run("test sha of non existing file", func(t *testing.T) { + _, err := checkShaOfPlugin("nonexistentplugin", "") + assert.Error(t, err) + }) + + t.Run("test sha of real file", func(t *testing.T) { + os.WriteFile("test-sha", []byte("test"), 0700) + realfile, err := filepath.Abs("test-sha") + assert.NoError(t, err) + + shaNotValid, err := checkShaOfPlugin(realfile, "") + assert.NoError(t, err) + assert.Equal(t, false, shaNotValid) + + shaValid, err := checkShaOfPlugin(realfile, "74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + assert.NoError(t, err) + assert.Equal(t, true, shaValid) + + os.Remove("test-sha") + }) } func TestDownloadFile(t *testing.T) { diff --git a/utils/plugin/error b/utils/plugin/error deleted file mode 100755 index 30d74d2584..0000000000 --- a/utils/plugin/error +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/utils/plugin/plugin.go b/utils/plugin/plugin.go index 5555d4db0e..a57481adae 100644 --- a/utils/plugin/plugin.go +++ b/utils/plugin/plugin.go @@ -17,13 +17,17 @@ func GetPluginLocation(pluginName string) (string, error) { return "", fmt.Errorf("failed to get config: %w", err) } - for _, item := range configMap.GetMetricPluginsConfig() { + for _, item := range configMap.GetAllPlugins() { if pluginName == item.Name { - asbFilePath, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, item.Name)) + dir, filename, err := config.GetPluginDirectoryAndFilename(item.Name) + if err != nil { + return "", err + } + absFilePath, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, dir, filename)) if err != nil { return "", fmt.Errorf("failed to get absolute path of plugin folder: %w", err) } - return asbFilePath, nil + return absFilePath, 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 index c4d656943f..63e39285ab 100644 --- a/utils/plugin/plugin_test.go +++ b/utils/plugin/plugin_test.go @@ -14,39 +14,64 @@ import ( ) 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) -} + t.Run("tests getting plugin location of metric provider plugins", func(t *testing.T) { + + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: https://test/plugin\n - name: argoproj-labs/http-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + location, err := GetPluginLocation("argoproj-labs/http") + assert.NoError(t, err) + fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/http")) + assert.NoError(t, err) + assert.Equal(t, fp, location) + }) + + t.Run("tests getting plugin location of traffic router plugins", func(t *testing.T) { + + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"trafficRouterPlugins": "\n - name: argoproj-labs/router\n location: https://test/plugin\n - name: argoproj-labs/router-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) + + location, err := GetPluginLocation("argoproj-labs/router") + assert.NoError(t, err) + fp, err := filepath.Abs(filepath.Join(defaults.DefaultRolloutPluginFolder, "argoproj-labs/router")) + assert.NoError(t, err) + assert.Equal(t, fp, location) + }) + + t.Run("test getting plugin location of a plugin that does not exists", func(t *testing.T) { + cm := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "argo-rollouts-config", + Namespace: "argo-rollouts", + }, + Data: map[string]string{"metricProviderPlugins": "\n - name: argoproj-labs/http\n location: https://test/plugin\n - name: argoproj-labs/http-sha\n location: https://test/plugin\n sha256: 74657374e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + } + client := fake.NewSimpleClientset(cm) + + _, err := config.InitializeConfig(client, "argo-rollouts-config") + assert.NoError(t, err) -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) + location, err := GetPluginLocation("does-not-exist") + assert.Error(t, err) + assert.Equal(t, "plugin does-not-exist not configured in configmap", err.Error()) + assert.Equal(t, "", location) + }) } diff --git a/utils/plugin/types/types.go b/utils/plugin/types/types.go index ea77d66183..102af1490a 100644 --- a/utils/plugin/types/types.go +++ b/utils/plugin/types/types.go @@ -1,7 +1,17 @@ package types -import "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +import ( + "encoding/gob" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" +) + +func init() { + gob.RegisterName("RpcError", new(RpcError)) +} + +// RpcError is a wrapper around the error type to allow for usage with net/rpc +// and empty ErrorString == "" is considered no error type RpcError struct { ErrorString string } @@ -10,11 +20,41 @@ func (e RpcError) Error() string { return e.ErrorString } +// HasError returns true if there is an error +func (e RpcError) HasError() bool { + return e.ErrorString != "" +} + +// RpcVerified is a wrapper around the *bool as used in VerifyWeight for traffic routers. This is needed because +// net/rpc does not support pointers. +type RpcVerified int32 + +const ( + NotVerified RpcVerified = iota + Verified + NotImplemented +) + +func (v *RpcVerified) IsVerified() *bool { + verified := true + notVerified := false + switch *v { + case Verified: + return &verified + case NotVerified: + return ¬Verified + case NotImplemented: + return nil + default: + return ¬Verified + } +} + 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 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 @@ -27,12 +67,39 @@ type RpcMetricProvider interface { GetMetadata(metric v1alpha1.Metric) map[string]string } -type Plugin struct { - Metrics []PluginItem `json:"metrics" yaml:"metrics"` +type RpcTrafficRoutingReconciler interface { + // UpdateHash informs a traffic routing reconciler about new canary, stable, and additionalDestination(s) pod hashes + UpdateHash(rollout *v1alpha1.Rollout, canaryHash, stableHash string, additionalDestinations []v1alpha1.WeightDestination) RpcError + // SetWeight sets the canary weight to the desired weight + SetWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) RpcError + // SetHeaderRoute sets the header routing step + SetHeaderRoute(rollout *v1alpha1.Rollout, setHeaderRoute *v1alpha1.SetHeaderRoute) RpcError + // SetMirrorRoute sets up the traffic router to mirror traffic to a service + SetMirrorRoute(rollout *v1alpha1.Rollout, setMirrorRoute *v1alpha1.SetMirrorRoute) RpcError + // VerifyWeight returns true if the canary is at the desired weight and additionalDestinations are at the weights specified + // Returns nil if weight verification is not supported or not applicable + VerifyWeight(rollout *v1alpha1.Rollout, desiredWeight int32, additionalDestinations []v1alpha1.WeightDestination) (RpcVerified, RpcError) + // RemoveManagedRoutes Removes all routes that are managed by rollouts by looking at spec.strategy.canary.trafficRouting.managedRoutes + RemoveManagedRoutes(ro *v1alpha1.Rollout) RpcError + // Type returns the type of the traffic routing reconciler + Type() string +} + +//type Plugin struct { +// MetricProviders []PluginItem `json:"metricProviders" yaml:"metricProviders"` +// TrafficRouters []PluginItem `json:"trafficRouters" yaml:"trafficRouters"` +//} + +type TrafficRouterPlugins struct { + TrafficRouters []PluginItem `json:"trafficRouterPlugins" yaml:"trafficRouterPlugins"` +} + +type MetricProviderPlugins struct { + MetricProviders []PluginItem `json:"metricProviderPlugins" yaml:"metricProviderPlugins"` } type PluginItem struct { - Name string `json:"name" yaml:"name"` - PluginLocation string `json:"pluginLocation" yaml:"pluginLocation"` - PluginSha256 string `json:"pluginSha256" yaml:"pluginSha256"` + Name string `json:"name" yaml:"name"` + Location string `json:"location" yaml:"location"` + Sha256 string `json:"sha256" yaml:"sha256"` }