From b745a30be301a9af95e1a74a4654e0d09af2a640 Mon Sep 17 00:00:00 2001 From: Kyle DeFrancia Date: Fri, 13 Sep 2019 10:18:13 -0700 Subject: [PATCH] Initial commit --- .dockerignore | 8 + .github/ISSUE_TEMPLATE/bug_report.md | 28 + .gitignore | 2 + CHANGELOG.md | 14 + CODE_OF_CONDUCT.md | 43 + CONTRIBUTING.md | 69 + CORPORATE_CLA.md | 43 + DEVELOPMENT.md | 128 + Dockerfile | 47 + INDIVIDUAL_CLA.md | 41 + LICENSE.md | 201 + Makefile | 46 + README.md | 174 + STYLE.md | 11 + THIRD_PARTY_NOTICES.md | 3714 +++++++++++++++++ VERSION | 1 + bin/bump_version.sh | 85 + bin/mixer_codegen.sh | 217 + bin/protoc.sh | 31 + cmd/main.go | 145 + config/adapter.newrelic.config.pb.html | 167 + config/config.pb.go | 973 +++++ config/config.proto | 100 + config/config.proto_descriptor | Bin 0 -> 65517 bytes config/newrelic.yaml | 15 + convert/value.go | 84 + convert/value_test.go | 112 + go.mod | 13 + go.sum | 463 ++ handler.go | 41 + helm-charts/Chart.yaml | 26 + helm-charts/README.md | 39 + helm-charts/templates/_helpers.tpl | 64 + helm-charts/templates/adapter.yaml | 35 + helm-charts/templates/attributes.yaml | 28 + helm-charts/templates/deployment.yaml | 88 + helm-charts/templates/handler.yaml | 39 + helm-charts/templates/metric-template.yaml | 31 + helm-charts/templates/metrics.yaml | 34 + helm-charts/templates/rules.yaml | 36 + helm-charts/templates/secret.yaml | 30 + helm-charts/templates/service.yaml | 35 + helm-charts/templates/traces.yaml | 32 + helm-charts/templates/tracespan-template.yaml | 31 + helm-charts/values.yaml | 682 +++ integration_test.go | 382 ++ integration_test_cfg.yaml | 191 + internal/nrsdk/cumulative/cumulative.go | 100 + internal/nrsdk/cumulative/cumulative_test.go | 228 + internal/nrsdk/instrumentation/aggregator.go | 116 + .../nrsdk/instrumentation/aggregator_test.go | 149 + .../nrsdk/instrumentation/benchmark_test.go | 20 + internal/nrsdk/instrumentation/count.go | 53 + internal/nrsdk/instrumentation/count_test.go | 43 + internal/nrsdk/instrumentation/doc.go | 3 + .../nrsdk/instrumentation/example_test.go | 62 + internal/nrsdk/instrumentation/gauge.go | 54 + internal/nrsdk/instrumentation/gauge_test.go | 87 + internal/nrsdk/instrumentation/summary.go | 65 + .../nrsdk/instrumentation/summary_test.go | 57 + internal/nrsdk/internal/attributes.go | 103 + internal/nrsdk/internal/attributes_test.go | 123 + internal/nrsdk/internal/compress.go | 32 + internal/nrsdk/internal/compress_test.go | 18 + internal/nrsdk/internal/json_writer.go | 72 + internal/nrsdk/internal/jsonx/encode.go | 174 + internal/nrsdk/internal/jsonx/encode_test.go | 179 + internal/nrsdk/internal/version.go | 17 + internal/nrsdk/telemetry/attributes.go | 43 + internal/nrsdk/telemetry/config.go | 139 + internal/nrsdk/telemetry/config_test.go | 75 + internal/nrsdk/telemetry/doc.go | 10 + internal/nrsdk/telemetry/example_test.go | 72 + internal/nrsdk/telemetry/harvester.go | 379 ++ internal/nrsdk/telemetry/harvester_test.go | 615 +++ internal/nrsdk/telemetry/metrics.go | 320 ++ .../nrsdk/telemetry/metrics_batch_test.go | 398 ++ internal/nrsdk/telemetry/metrics_test.go | 104 + internal/nrsdk/telemetry/request.go | 55 + internal/nrsdk/telemetry/request_test.go | 79 + internal/nrsdk/telemetry/spans.go | 175 + internal/nrsdk/telemetry/spans_batch_test.go | 208 + internal/nrsdk/telemetry/spans_test.go | 155 + internal/nrsdk/telemetry/utilities.go | 25 + internal/nrsdk/telemetry/utilities_test.go | 52 + metric/builder.go | 127 + metric/builder_test.go | 128 + metric/handler.go | 74 + metric/handler_test.go | 214 + sample_newrelic_dashboard.json | 113 + sample_operator_cfg.yaml | 400 ++ server.go | 210 + trace/builder.go | 31 + trace/handler.go | 101 + trace/handler_test.go | 135 + 95 files changed, 15011 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 CORPORATE_CLA.md create mode 100644 DEVELOPMENT.md create mode 100644 Dockerfile create mode 100644 INDIVIDUAL_CLA.md create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 STYLE.md create mode 100644 THIRD_PARTY_NOTICES.md create mode 100644 VERSION create mode 100755 bin/bump_version.sh create mode 100755 bin/mixer_codegen.sh create mode 100755 bin/protoc.sh create mode 100644 cmd/main.go create mode 100644 config/adapter.newrelic.config.pb.html create mode 100644 config/config.pb.go create mode 100644 config/config.proto create mode 100644 config/config.proto_descriptor create mode 100644 config/newrelic.yaml create mode 100644 convert/value.go create mode 100644 convert/value_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 handler.go create mode 100644 helm-charts/Chart.yaml create mode 100644 helm-charts/README.md create mode 100644 helm-charts/templates/_helpers.tpl create mode 100644 helm-charts/templates/adapter.yaml create mode 100644 helm-charts/templates/attributes.yaml create mode 100644 helm-charts/templates/deployment.yaml create mode 100644 helm-charts/templates/handler.yaml create mode 100644 helm-charts/templates/metric-template.yaml create mode 100644 helm-charts/templates/metrics.yaml create mode 100644 helm-charts/templates/rules.yaml create mode 100644 helm-charts/templates/secret.yaml create mode 100644 helm-charts/templates/service.yaml create mode 100644 helm-charts/templates/traces.yaml create mode 100644 helm-charts/templates/tracespan-template.yaml create mode 100644 helm-charts/values.yaml create mode 100644 integration_test.go create mode 100644 integration_test_cfg.yaml create mode 100644 internal/nrsdk/cumulative/cumulative.go create mode 100644 internal/nrsdk/cumulative/cumulative_test.go create mode 100644 internal/nrsdk/instrumentation/aggregator.go create mode 100644 internal/nrsdk/instrumentation/aggregator_test.go create mode 100644 internal/nrsdk/instrumentation/benchmark_test.go create mode 100644 internal/nrsdk/instrumentation/count.go create mode 100644 internal/nrsdk/instrumentation/count_test.go create mode 100644 internal/nrsdk/instrumentation/doc.go create mode 100644 internal/nrsdk/instrumentation/example_test.go create mode 100644 internal/nrsdk/instrumentation/gauge.go create mode 100644 internal/nrsdk/instrumentation/gauge_test.go create mode 100644 internal/nrsdk/instrumentation/summary.go create mode 100644 internal/nrsdk/instrumentation/summary_test.go create mode 100644 internal/nrsdk/internal/attributes.go create mode 100644 internal/nrsdk/internal/attributes_test.go create mode 100644 internal/nrsdk/internal/compress.go create mode 100644 internal/nrsdk/internal/compress_test.go create mode 100644 internal/nrsdk/internal/json_writer.go create mode 100644 internal/nrsdk/internal/jsonx/encode.go create mode 100644 internal/nrsdk/internal/jsonx/encode_test.go create mode 100644 internal/nrsdk/internal/version.go create mode 100644 internal/nrsdk/telemetry/attributes.go create mode 100644 internal/nrsdk/telemetry/config.go create mode 100644 internal/nrsdk/telemetry/config_test.go create mode 100644 internal/nrsdk/telemetry/doc.go create mode 100644 internal/nrsdk/telemetry/example_test.go create mode 100644 internal/nrsdk/telemetry/harvester.go create mode 100644 internal/nrsdk/telemetry/harvester_test.go create mode 100644 internal/nrsdk/telemetry/metrics.go create mode 100644 internal/nrsdk/telemetry/metrics_batch_test.go create mode 100644 internal/nrsdk/telemetry/metrics_test.go create mode 100644 internal/nrsdk/telemetry/request.go create mode 100644 internal/nrsdk/telemetry/request_test.go create mode 100644 internal/nrsdk/telemetry/spans.go create mode 100644 internal/nrsdk/telemetry/spans_batch_test.go create mode 100644 internal/nrsdk/telemetry/spans_test.go create mode 100644 internal/nrsdk/telemetry/utilities.go create mode 100644 internal/nrsdk/telemetry/utilities_test.go create mode 100644 metric/builder.go create mode 100644 metric/builder_test.go create mode 100644 metric/handler.go create mode 100644 metric/handler_test.go create mode 100644 sample_newrelic_dashboard.json create mode 100644 sample_operator_cfg.yaml create mode 100644 server.go create mode 100644 trace/builder.go create mode 100644 trace/handler.go create mode 100644 trace/handler_test.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9cf393f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +# Ignore Docker build configuration. +.dockerignore +Dockerfile + +# Ignore git project releated files. +.git +.gitignore +.github diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f9b613b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Report a bug to help us improve the New Relic Istio Adapter +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. ... +2. ... +3. ... + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Version information (please complete the following information):** + - newrelic-istio-adapter: [e.g. 0.0.1] + - Kubernetes: [e.g. v1.15] + - Istio: [e.g. 1.2] + +**Additional context** +Add any other context about the problem here. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a04e1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +kube/00-secret.yaml +vendor* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d8a287e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## Unreleased + +## 1.0.0 + +### Added + +* Initial `newrelic-istio-adapter` application code. +* Documentation, user guides, and project metadata. +* Build configuration files. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..6de22df --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +# CONTRIBUTOR COVENANT CODE OF CONDUCT + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others’ private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at open-source@newrelic.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5b0de25 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,69 @@ +# Contributing to newrelic-istio-adapter + +Welcome! +We gladly accept contributions from the community. + +If you wish to contribute code and you have not signed our [Individual Contributor License Agreement](INDIVIDUAL_CLA.md) or our [Corporate Contributor License Agreement](CORPORATE_CLA.md), please do so in order to contribute. + +## How to contribute + +We use GitHub Pull Requests to incorporate code changes from external +contributors. +Typical contribution flow steps are: + +* Sign the [Individual Contributor License Agreement](INDIVIDUAL_CLA.md) or our [Corporate Contributor License Agreement](CORPORATE_CLA.md). +* Fork the `newrelic-istio-adapter` repository. +* Clone the forked repository locally and configure the upstream repository. +* Open an Issue describing what you propose to do (unless the change is so + trivial that an issue is not needed). + Issues should be submitted with a clear description, steps to reproduce the issue, Istio version used, and Kubernetes version. +* Once you know which steps to take in your intended contribution, make changes + in a topic branch and commit. + (Don't forget to add or modify tests!). +* Consult the [style guide](STYLE.md) and ensure your changes adhere to the project style. +* Fetch changes from upstream, rebase with master and resolve any merge + conflicts so that your topic branch is up-to-date. +* Build and test the project locally. + (See [DEVELOPMENT.md](DEVELOPMENT.md) for more details.) +* Push all commits to the topic branch in your forked repository. +* Submit a Pull Request to merge topic branch commits to upstream master. + Be sure to reference your Issue when creating the PR unless it is a trivial change. + +If this process sounds unfamiliar, have a look at the excellent overview of [collaboration via Pull Requests on GitHub](https://help.github.com/categories/collaborating-with-issues-and-pull-requests/) +for more information. + +## Adding dependencies + +Please avoid adding new dependencies to the project. If a change requires a new dependency, we encourage you to choose dependencies licensed under BSD, MIT, or Apache licenses. + +## Do you have questions or are you experiencing unexpected behaviors after modifying this Open Source Software? + +Please engage with the “Build on New Relic” space in the [Explorers Hub](https://discuss.newrelic.com/c/build-on-new-relic/Integrations), New Relic’s Forum. +Posts are publicly viewable by anyone, please do not include PII or sensitive information in your forum post. + +## Contributor License Agreement ("CLA") + +We'd love to get your contributions to improve `newrelic-istio-adapter`! +Keep in mind when you submit your pull request, you'll need to sign the [CLA](INDIVIDUAL_CLA.md) via the click-through using CLA-Assistant. +You only have to sign the CLA one time per project. + +To execute our corporate CLA, which is required if your contribution is on behalf of a company, or if you have any questions, please drop us an email at open-source@newrelic.com. + +## Filing Issues & Bug Reports + +We use GitHub issues to track public issues and bugs. +Issues should be submitted with a clear description, steps to reproduce the issue, Istio version used, and Kubernetes version. +If possible, please provide a link to an example app or gist that reproduces the issue. +Be aware that GitHub issues are publicly viewable by anyone, so please do not include personal information in your GitHub issue or in any of your contributions, except as minimally necessary for the purpose of supporting your issue. +New Relic will process any personal data you submit through GitHub issues in compliance with the [New Relic Privacy Notice](https://newrelic.com/termsandconditions/privacy). + +## A note about vulnerabilities + +New Relic is committed to the privacy and security of our customers and their data. +We believe that providing coordinated disclosure by security researchers and engaging with the security community are important means to achieve our security goals. +If you believe you have found a security vulnerability in this project or any of New Relic's products or websites, we welcome and greatly appreciate you reporting it to New Relic through [HackerOne](https://hackerone.com/newrelic). + +## License + +By contributing to `newrelic-istio-adapter`, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree. + diff --git a/CORPORATE_CLA.md b/CORPORATE_CLA.md new file mode 100644 index 0000000..ed86eaa --- /dev/null +++ b/CORPORATE_CLA.md @@ -0,0 +1,43 @@ +# NEW RELIC, INC. + +## CORPORATE CONTRIBUTOR LICENSE AGREEMENT + +Thank you for your interest in contributing to the open source projects of New Relic, Inc. (“New Relic”). +In order to clarify the intellectual property license granted with Contributions from any person or entity, New Relic must have a Contributor License Agreement ("Agreement") on file that has been signed by each Contributor, indicating agreement to the license terms below. +This Agreement is for your protection as a Contributor as well as the protection of New Relic; it does not change your rights to use your own Contributions for any other purpose. + +This version of the Agreement allows an entity (the "Corporation") to submit Contributions to New Relic, to authorize Contributions submitted by its designated employees to New Relic, and to grant copyright and patent licenses thereto. + +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to New Relic. +Except for the licenses granted herein to New Relic and recipients of software distributed by New Relic, You reserve all right, title, and interest in and to Your Contributions. + +1) Definitions + + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is entering into this Agreement with New Relic. + For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. + For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean the code, documentation or other original works of authorship initially identified by You, as well as any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to New Relic for inclusion in, or documentation of, any of the products managed or maintained by New Relic (the "Work"). + For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to New Relic or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, New Relic for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2) Grant of Copyright License. +Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3) Grant of Patent License. +Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. +If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4) You represent that You are legally entitled to grant the above licenses. +You represent further that each employee of the Corporation designated by You (now or in the future) is authorized to submit Contributions on behalf of the Corporation. + +5) You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). +You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which You are aware and which are associated with any part of Your Contributions. + +6) You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. +You may provide support for free, for a fee, or not at all. +Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7) Should You wish to submit work that is not Your original creation, You may submit it to New Relic separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". + +8) It is Your responsibility to notify New Relic when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's point of contact with New Relic. +You agree to notify New Relic of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..9790629 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,128 @@ +# Development Guide + +This document is intended to aid contributors of this project get up and running. + +## Developing Locally + +To begin developing locally, you will need the following things installed, configured, and verified to be working. + +* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Docker](https://docs.docker.com/install/) +* [Go](https://golang.org/doc/install#install) (>=1.13) +* [protoc](https://github.com/protocolbuffers/protobuf) + * For example on MacOS: + ```shell + brew install protobuf + ``` + * Or change the `protoc` variable in `bin/mixer_codegen.sh` to run a Docker container instead +* A local copy of [Mixer](https://github.com/istio/istio/tree/master/mixer) + * For example: + ```shell + mkdir -p $GOPATH/src/istio.io/ && \ + git clone https://github.com/istio/istio $GOPATH/src/istio.io/istio + ``` +* A New Relic Insights Insert API Key. + +Once you have these things in place, use the following steps to get a Mixer server running and test your build of the adapter with it. + +### 1) Setup Istio Development Environment Variables + +```shell +export MIXER_REPO=$GOPATH/src/istio.io/istio/mixer +export ISTIO=$GOPATH/src/istio.io +``` + +### 2) Build the Mixer Server, Client, and Toolset + +```shell +pushd $ISTIO/istio && make mixs mixc mixgen && popd +``` + +### 3) Make a development copy of `config/` into `testdata/` + +```shell +mkdir testdata +cp sample_operator_cfg.yaml \ + config/newrelic.yaml \ + $MIXER_REPO/testdata/config/attributes.yaml \ + $MIXER_REPO/template/metric/template.yaml \ + $MIXER_REPO/template/tracespan/tracespan.yaml \ + testdata +``` + +### 4) Start the newrelic-istio-adapter + +Be sure to replace `"$NEW_RELIC_API_KEY"` with your valid New Relic Insights Insert API Key. + +Another option is to export an environment variable (`NEW_RELIC_API_KEY`) prior to starting the `newrelic-istio-adapter`. + +```shell +go run cmd/main.go \ + --cluster-name "Local Testing" \ + --debug \ + "$NEW_RELIC_API_KEY" +``` + +### 5) Start the Mixer Server + +The location of the `mixs` binary depends on what OS you are on. + +* Linux: + ```shell + export MIXS="$GOPATH/out/linux_amd64/release/mixs" + ``` +* MacOS: + ```shell + export MIXS="$GOPATH/out/darwin_amd64/release/mixs" + ``` + +Start the Istio Mixer server with `configStoreURL` pointed to the `testdata` directory previously created. + +```shell +$MIXS server --configStoreURL=fs://$(pwd)/testdata +``` + +Mixer should begin printing logs to `STDOUT`. +It is important to stop here and troubleshoot any errors reported by the Mixer server related to connecting to the locally running adapter. + +### 6) Send Test Instances + +The location of the `mixc` binary depends on what OS you are on. + +* Linux + ```shell + export MIXC="$GOPATH/out/linux_amd64/release/mixc" + ``` +* MacOS + ```shell + export MIXC="$GOPATH/out/darwin_amd64/release/mixc" + ``` + +Send test events to the Mixer server using the Mixer client. + +```shell +$MIXC report \ + --string_attributes "context.protocol=http,destination.principal=service-account-bar,destination.service.host=bar.test.svc.cluster.local,destination.service.name=bar,destination.service.namespace=test,destination.workload.name=bar,destination.workload.namespace=test,source.principal=service-account-foo,source.service.host=foo.test.svc.cluster.local,source.service.name=foo,source.service.namespace=test,source.workload.name=foo,source.workload.namespace=test" \ + --stringmap_attributes "request.headers=x-forward-proto:https;source:foo,destination.labels=app:bar;version:v1,source.labels=app:foo" \ + --int64_attributes response.duration=2003,response.size=1024,response.code=200 \ + --timestamp_attributes "request.time="2017-07-04T00:01:10Z,response.time="2017-07-04T00:01:11Z" \ + --bytes_attributes source.ip=c0:0:0:2 +``` + +The `newrelic-istio-adapter` should output activity to `STDOUT` signifying that metrics have been captured and sent to New Relic. + +## Release Process + +1. Bump the project version. + ```shell + ./bin/bump_version.sh + ``` + The `` needs to be an appropriately incremented [semver](https://semver.org/) format version. + * This means that any backwards-incompatible API changes being included in the release require the major version to be incremented. + * Any backwards-compatible changes that add functionality means the minor version must be incremented. + * If the release only contains bug fixes and patches, just the patch version will need to be incremented. +2. Review the [CHANGELOG](./CHANGELOG.md) and ensure all relevant changes included in the release are captured there. +3. Commit changes and open an appropriately titled (i.e. `Release X.X.X`) PR in GitHub. + Have another developer approve your PR and then merge your changes to `master`. +4. Create a GitHub release for the merged changes. + Be sure to tag the commit of the release with the `` value (do not include a `v` prefix) and have the release description body include all the changes from the [CHANGELOG](./CHANGELOG.md). diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..aba3df9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ARG GRPC_HEALTH_PROBE_VERSION=v0.2.0 + +############################################################################# +# STEP 1: build static binary +############################################################################# +FROM golang:1.13-stretch as builder + +WORKDIR ${GOPATH}/src/github.com/newrelic/newrelic-istio-adapter +COPY . . + +ENV GOOS=linux +ENV GOARCH=amd64 +ENV CGO_ENABLED=0 +# Run full test suite and build the static binary +RUN make test TAGS=integration \ + && make build GOBIN=/go/bin + +############################################################################# +# STEP 2: create a small image +############################################################################# +FROM alpine:3.10 +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* +COPY --from=builder /go/bin/newrelic-istio-adapter /go/bin/newrelic-istio-adapter +COPY THIRD_PARTY_NOTICES.md ./THIRD_PARTY_NOTICES +COPY LICENSE.md ./LICENSE + +# gRPC health check utility +ARG GRPC_HEALTH_PROBE_VERSION=v0.2.0 +RUN wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 \ + && chmod +x /bin/grpc_health_probe + +EXPOSE 55912 +ENTRYPOINT ["/go/bin/newrelic-istio-adapter"] diff --git a/INDIVIDUAL_CLA.md b/INDIVIDUAL_CLA.md new file mode 100644 index 0000000..a840535 --- /dev/null +++ b/INDIVIDUAL_CLA.md @@ -0,0 +1,41 @@ +# NEW RELIC, INC. + +## INDIVIDUAL CONTRIBUTOR LICENSE AGREEMENT + +Thank you for your interest in contributing to the open source projects of New Relic, Inc. (“New Relic”). +In order to clarify the intellectual property license granted with Contributions from any person or entity, New Relic must have a Contributor License Agreement ("Agreement") on file that has been signed by each Contributor, indicating agreement to the license terms below. +This Agreement is for your protection as a Contributor as well as the protection of New Relic; it does not change your rights to use your own Contributions for any other purpose. + +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to New Relic. +Except for the licenses granted herein to New Relic and recipients of software distributed by New Relic, You reserve all right, title, and interest in and to Your Contributions. + +1) Definitions + + "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is entering into this Agreement with New Relic. + For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. + For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to New Relic for inclusion in, or documentation of, any of the products managed or maintained by New Relic (the "Work"). + For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to New Relic or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, New Relic for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2) Grant of Copyright License. +Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3) Grant of Patent License. +Subject to the terms and conditions of this Agreement, You hereby grant to New Relic and to recipients of software distributed by New Relic a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contributions alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. +If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that Your Contribution, or the Work to which You have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4) You represent that You are legally entitled to grant the above licenses. +If Your employer(s) has rights to intellectual property that You create that includes Your Contributions, You represent that You have received permission to make Contributions on behalf of that employer, that Your employer has waived such rights for Your Contributions to New Relic, or that Your employer has executed a separate Agreement with New Relic. + +5) You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). + +You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which You are personally aware and which are associated with any part of Your Contributions. + +6) You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. +You may provide support for free, for a fee, or not at all. +Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7) Should You wish to submit work that is not Your original creation, You may submit it to New Relic separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which You are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". + +8) You agree to notify New Relic of any facts or circumstances of which You become aware that would make these representations inaccurate in any respect. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..77b306c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 New Relic, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..91b79da --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +# +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Name of binary to create. +BINARY = newrelic-istio-adapter + +# Semver of application. +VERSION := $(shell cat ./VERSION) + +# Go related variables. +GOPATH ?= $(shell pwd) +GOBIN ?= $(GOPATH)/bin +GO = GOPATH=$(GOPATH) GOBIN=$(GOBIN) go + +# While statically linking we want to inject version related information into the binary. +LDFLAGS = -ldflags="-extldflags '-static' -X main.Version=$(VERSION)" + + +all: generate test build + +generate: +# The generate script requires local copies of dependencies. + @$(GO) mod vendor + @$(GO) generate ./... + +TAGS ?= "" +TEST_TAGS := --tags=$(TAGS) +test: + @$(GO) test -v $(TEST_TAGS) ./... + +build: + @$(GO) build $(LDFLAGS) -o $(GOBIN)/$(BINARY) cmd/main.go + +.PHONY: generate test build diff --git a/README.md b/README.md new file mode 100644 index 0000000..325fd09 --- /dev/null +++ b/README.md @@ -0,0 +1,174 @@ +# New Relic Istio Adapter + +*An Istio Mixer adapter to send telemetry data to New Relic.* + +For more information on how Istio Mixer telemetry is created and collected, please see this [Mixer Overview](https://istio.io/docs/reference/config/policy-and-telemetry/mixer-overview/). + +For more information about out-of-process Istio Mixer adapters, please see the [Mixer Out of Process Adapter Walkthrough](https://github.com/istio/istio/wiki/Mixer-Out-Of-Process-Adapter-Walkthrough) + +## Quotas + +**Metrics and Spans exported from this adapter to New Relic will be rate limited!** + +Currently (2019-08-30) the following quotas apply to APM Professional accounts: + +* 500,000 Metrics / minute + * 250,000 unique Metric timeseries + * 50 attributes per Metric +* 5,000 Spans / minute + +You may request a quota increase for Metrics and/or Span by contacting your New Relic account representative. + +## Quickstart + +The `newrelic-istio-adapter` should be run alongside an installed/configured Istio Mixer server. + +For Kubernetes installations, Helm deployment charts have been provided in the `helm-charts` directory. +These charts are intended to provide a simple installation and customization method for users. + +See the [Helm installation docs](https://helm.sh/docs/using_helm/#install-helm) for installation/configuration of Helm. + +### Prerequisites + +* A Kubernetes cluster +* A working `kubectl` installation +* A working `helm` installation +* A Healthy Istio deployment +* A New Relic Insights Insert API Key. + +### Deploy Helm Template + +The `newrelic-istio-adapter` should be deployed to an independent namespace. +This provides isolation and customizable access control. + +The examples in this guide install the adapter to the `newrelic-istio-adapter` namespace. +This namespace is not managed by this installation process and will need to be created manually. +I.e. + +```shell +kubectl create namespace newrelic-istio-adapter +``` + +Additionally, several components of the `newrelic-istio-adapter` are required to be deployed into the Istio namespace (i.e. `istio-system`). +Make sure you have privileges to deploy to this namespace. + +Once you have ensured all of these things, generate Kubernetes manifests with Helm (be sure to replace `` with your New Relic Insights API key) and deploy the components using `kubectl`. + +```shell +cd helm-charts +helm template . \ + -f values.yaml \ + --name newrelic-istio-adapter \ + --namespace newrelic-istio-adapter \ + --set authentication.apiKey= \ + > newrelic-istio-adapter.yaml +kubectl apply -f newrelic-istio-adapter.yaml +``` + +### Validate + +Verify that the `newrelic-istio-adapter` deployment and pod are healthy within the `newrelic-istio-adapter` namespace + +``` +$ kubectl -n newrelic-istio-adapter get deploy newrelic-istio-adapter + +NAME READY UP-TO-DATE AVAILABLE AGE +newrelic-istio-adapter 1/1 1 1 10s + +$ kubectl -n newrelic-istio-adapter get po -l app.kubernetes.io/name=newrelic-istio-adapter + +NAME READY STATUS RESTARTS AGE +newrelic-istio-adapter-6d9c4f9b88-r5gn7 1/1 Running 1 8s +``` + +Verify that the `newrelic-istio-adapter` handler, rules, adapter, and instances are present within the `istio-system` namespace + +``` +$ kubectl -n istio-system get handler -l app.kubernetes.io/name=newrelic-istio-adapter + +NAME AGE +newrelic-istio-adapter 10s + +$ kubectl -n istio-system get rules -l app.kubernetes.io/name=newrelic-istio-adapter + +NAME AGE +newrelic-http-connection 10s +newrelic-tcp-connection 10s +newrelic-tcp-connection-closed 10s +newrelic-tcp-connection-open 10s + +$ kubectl -n istio-system get adapter -l app.kubernetes.io/name=newrelic-istio-adapter + +NAME AGE +newrelic 10s + +$ kubectl -n istio-system get instances -l app.kubernetes.io/name=newrelic-istio-adapter + +NAME AGE +newrelic-bytes-received 10s +newrelic-bytes-sent 10s +newrelic-connections-closed 10s +newrelic-connections-opened 10s +newrelic-request-count 10s +newrelic-request-duration 10s +newrelic-request-size 10s +newrelic-response-size 10s +newrelic-span 10s +``` + +You should start to see metrics sent to [Insights](https://insights.newrelic.com) a few minutes after the deployment. +As an example, this Insights query will display a timeseries graph of total Istio requests: + +``` +From Metric SELECT sum(istio.request.total) TIMESERIES +``` + +The logs of Mixer and the `newrelic-istio-adapter` in Kubernetes should show activity or errors. + +```shell +kubectl -n newrelic-istio-adapter logs -l app.kubernetes.io/name=newrelic-istio-adapter +``` + +To get started visualizing your data try the [sample dashboard template](#new-relic-dashboard-template). + +### Clean Up + +If you want to remove the `newrelic-istio-adapter` you can do so by deleting the resources defined in the manifest you deployed. + +``` +kubectl delete -f newrelic-istio-adapter.yaml +``` + +## Distributed Tracing + +The `newrelic-istio-adapter` is able to send [trace spans from services within the Istio service mesh](https://istio.io/docs/tasks/telemetry/distributed-tracing/overview/) to New Relic. +This functionality is disabled by default, but it can be enabled by adding the following `telemetry.rules` value when deploying the `newrelic-istio-adapter` Helm [chart](./helm-charts/README.md#configuration). + +``` +... +newrelic-tracing: + match: (context.protocol == "http" || context.protocol == "grpc") && destination.workload.name != "istio-telemetry" && destination.workload.name != "istio-pilot" && ((request.headers["x-b3-sampled"] | "0") == "1") + instances: + - newrelic-span +``` + +Adding this rule means that Mixer will send the adapter all `HTTP`/`gRPC` spans for services that propagate appropriate Zipkin (B3) headers in their requests. + +Note that the match condition for this rule configures Mixer to only send spans that have been sampled (i.e. `x-b3-sampled: 1`). +It is up the services themselves to appropriately sample traces. + +This sampling is important to keep in mind when enabling this functionality. +Without sampling you can quickly exceed the [quota](#quotas) associated with your account for the number of spans-per-minute you are allowed. +Additionally, the cost of sending spans to New Relic needs to be understood **before** you enable this. + +## New Relic Dashboard Template + +A [dashboard template](sample_newrelic_dashboard.json) is provided to chart some Istio metrics the default configuration produces. The template is designed to be imported with the [Insights Dashboard API](https://docs.newrelic.com/docs/insights/insights-api/manage-dashboards/insights-dashboard-api) and can be created straight from the [API Explorer](https://rpm.newrelic.com/api/explore/dashboards/create). + +The sample dashboard can be filtered by `cluster.name`, `destination.service.name`, and `source.app`. + +## Versioning + +This project follows [semver](http://semver.org/). + +See the [CHANGELOG](./CHANGELOG.md) for a detailed description of changes between versions. diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..6512be5 --- /dev/null +++ b/STYLE.md @@ -0,0 +1,11 @@ +# Code Style + +To keep source files organized the `newrelic-istio-adapter` project follows the style guides in this document. + +## Golang + +For code use the standard formatting as emitted by `go fmt`. See [Code Review Comments on the Golang wiki](https://github.com/golang/go/wiki/CodeReviewComments) for more information. + +## Markdown + +For documentation use the [Google Markdown style guide](https://google.github.io/styleguide/docguide/style). diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..0af3090 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,3714 @@ +# Third Party Notices + +This project uses source code from third-party libraries which carry their own copyright notices and license terms. +These notices are provided below. + +In the event that a required notice is missing or incorrect, please notify us by e-mailing [open-source@newrelic.com](mailto:open-source@newrelic.com). + +For any licenses that require the disclosure of source code, the source code can be found [here](https://github.com/newrelic/newrelic-istio-adapter). + +Dependency | Version | SPDX ID | License +---------- | ------- | ------- | ------- +[cloud.google.com/go](#cloudgooglecomgo) | v0.37.4 | Apache-2.0 | Apache License 2.0 +[github.com/alecthomas/template](#githubcomalecthomastemplate) | v0.0.0-20160405071501-a0175ee3bccc | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[github.com/alecthomas/units](#githubcomalecthomasunits) | v0.0.0-20151022065526-2efee857e7cf | MIT | MIT License +[github.com/antlr/antlr4](#githubcomantlrantlr4) | v0.0.0-20190223165740-dade65a895c2 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[github.com/fsnotify/fsnotify](#githubcomfsnotifyfsnotify) | v1.4.7 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[github.com/ghodss/yaml](#githubcomghodssyaml) | v1.0.0 | MIT | MIT License +[github.com/gogo/googleapis](#githubcomgogogoogleapis) | v1.2.0 | Apache-2.0 | Apache License 2.0 +[github.com/gogo/protobuf](#githubcomgogoprotobuf) | v1.2.1 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[github.com/golang/protobuf](#githubcomgolangprotobuf) | v1.3.0 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[github.com/google/cel-go](#githubcomgooglecel-go) | v0.2.0 | Apache-2.0 | Apache License 2.0 +[github.com/hashicorp/errwrap](#githubcomhashicorperrwrap) | v1.0.0 | MPL-2.0 | Mozilla Public License 2.0 +[github.com/hashicorp/go-multierror](#githubcomhashicorpgo-multierror) | v1.0.0 | MPL-2.0 | Mozilla Public License 2.0 +[github.com/natefinch/lumberjack](#githubcomnatefinchlumberjack) | v2.0.0 | MIT | MIT License +[github.com/pkg/errors](#githubcompkgerrors) | v0.8.1 | BSD-2-Clause | BSD 2-Clause "Simplified" License +[github.com/spf13/cobra](#githubcomspf13cobra) | v0.0.3 | Apache-2.0 | Apache License 2.0 +[github.com/spf13/pflag](#githubcomspf13pflag) | v1.0.3 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[go.opencensus.io](#goopencensusio) | v0.21.0 | Apache-2.0 | Apache License 2.0 +[go.uber.org/atomic](#gouberorgatomic) | v1.4.0 | MIT | MIT License +[go.uber.org/multierr](#gouberorgmultierr) | v1.1.0 | MIT | MIT License +[go.uber.org/zap](#gouberorgzap) | v1.10.0 | MIT | MIT License +[golang.org/x/net](#golangorgxnet) | v0.0.0-20190503192946-f4e77d36d62c | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[golang.org/x/oauth2](#golangorgxoauth2) | v0.0.0-20190226205417-e64efc72b421 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[golang.org/x/sys](#golangorgxsys) | v0.0.0-20190508220229-2d0786266e9c | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[golang.org/x/text](#golangorgxtext) | v0.3.2 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[golang.org/x/tools](#golangorgxtools) | v0.0.0-20190524140312-2c0ae7006135 | BSD-3-Clause | BSD 3-Clause "New" or "Revised" License +[google.golang.org/genproto](#googlegolangorggenproto) | v0.0.0-20190404172233-64821d5d2107 | Apache-2.0 | Apache License 2.0 +[google.golang.org/grpc](#googlegolangorggrpc) | v1.22.1 | Apache-2.0 | Apache License 2.0 +[gopkg.in/alecthomas/kingpin.v2](#gopkginalecthomaskingpinv2) | v2.2.6 | MIT | MIT License +[gopkg.in/yaml.v2](#gopkginyamlv2) | v2.2.2 | Apache-2.0 | Apache License 2.0 +[istio.io/api](#istioioapi) | v0.0.0-20190718213450-0a0442bf8664 | Apache-2.0 | Apache License 2.0 +[istio.io/istio](#istioioistio) | v0.0.0-20190726191302-76f15793c4f9 | Apache-2.0 | Apache License 2.0 +[istio.io/pkg](#istioiopkg) | v0.0.0-20190726080000-e5d6de6b352b | Apache-2.0 | Apache License 2.0 +[k8s.io/apimachinery](#k8sioapimachinery) | v0.0.0-20190221213512-86fb29eff628 | Apache-2.0 | Apache License 2.0 + +## [cloud.google.com/go](https://github.com/googleapis/google-cloud-go/blob/v0.37.4/LICENSE) + +* License: Apache License 2.0 + +``` +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. +``` + + + +## [github.com/alecthomas/template](https://github.com/alecthomas/template/blob/a0175ee3bccc567396460bf5acd36800cb10c49c/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [github.com/alecthomas/units](https://github.com/alecthomas/units/blob/2efee857e7cfd4f3d0138cc3cbb1b4966962b93a/COPYING) + +* License: MIT License + +``` +Copyright (C) 2014 Alec Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + + +## [github.com/antlr/antlr4](https://github.com/antlr/antlr4/blob/dade65a895c25c47d865d426603eaacce6e11e0b/LICENSE.txt) + +* License: BSD 3-Clause "New" or "Revised" License + +``` + +[The "BSD 3-clause license"] +Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +===== + +MIT License for codepointat.js from https://git.io/codepointat +MIT License for fromcodepoint.js from https://git.io/vDW1m + +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + + +## [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify/blob/c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2012 fsnotify Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [github.com/ghodss/yaml](https://github.com/ghodss/yaml/blob/0ca9ea5df5451ffdf184b4428c902747c2c11cd7/LICENSE) + +* License: MIT License + +``` +The MIT License (MIT) + +Copyright (c) 2014 Sam Ghods + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## [github.com/gogo/googleapis](https://github.com/gogo/googleapis/blob/v1.2.0/LICENSE) + +* License: Apache License 2.0 + +``` +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015, Google Inc + Copyright 2018, GoGo Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [github.com/gogo/protobuf](https://github.com/gogo/protobuf/blob/v1.2.1/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2013, The GoGo Authors. All rights reserved. + +Protocol Buffers for Go with Gadgets + +Go support for Protocol Buffers - Google's data interchange format + +Copyright 2010 The Go Authors. All rights reserved. +https://github.com/golang/protobuf + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## [github.com/golang/protobuf](https://github.com/golang/protobuf/blob/v1.3.0/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright 2010 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [github.com/google/cel-go](https://github.com/google/cel-go/blob/v0.2.0/LICENSE) + +* License: Apache License 2.0 + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [github.com/hashicorp/errwrap](https://github.com/hashicorp/errwrap/blob/v1.0.0/LICENSE) + +* License: Mozilla Public License 2.0 + +``` +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. +``` + + +## [github.com/hashicorp/go-multierror](https://github.com/hashicorp/go-multierror/blob/v1.0.0/LICENSE) + +* License: Mozilla Public License 2.0 + +``` +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. +``` + + +## [github.com/natefinch/lumberjack](https://github.com/natefinch/lumberjack/blob/v2.0/LICENSE) + +* License: MIT License + +``` +The MIT License (MIT) + +Copyright (c) 2014 Nate Finch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## [github.com/pkg/errors](https://github.com/pkg/errors/blob/v0.8.1/LICENSE) + +* License: BSD 2-Clause "Simplified" License + +``` +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [github.com/spf13/cobra](https://github.com/spf13/cobra/blob/v0.0.3/LICENSE.txt) + +* License: Apache License 2.0 + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. +``` + + +## [github.com/spf13/pflag](https://github.com/spf13/pflag/blob/v1.0.3/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2012 Alex Ogier. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [go.opencensus.io](https://github.com/census-instrumentation/opencensus-go/blob/v0.21.0/LICENSE) + +* License: Apache License 2.0 + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [go.uber.org/atomic](https://github.com/uber-go/atomic/blob/v1.4.0/LICENSE.txt) + +* License: MIT License + +``` +Copyright (c) 2016 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` + + +## [go.uber.org/multierr](https://github.com/uber-go/multierr/blob/v1.1.0/LICENSE.txt) + +* License: MIT License + +``` +Copyright (c) 2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` + + +## [go.uber.org/zap](https://github.com/uber-go/zap/blob/v1.10.0/LICENSE.txt) + +* License: MIT License + +``` +Copyright (c) 2016-2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` + + +## [golang.org/x/net](https://github.com/golang/net/blob/f4e77d36d62c17c2336347bb2670ddbd02d092b7/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [golang.org/x/oauth2](https://github.com/golang/oauth2/blob/e64efc72b421e893cbf63f17ba2221e7d6d0b0f3/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [golang.org/x/sys](https://github.com/golang/sys/blob/2d0786266e9cd132da844109aa05016f11f3df28/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [golang.org/x/text](https://github.com/golang/text/blob/v0.3.2/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [golang.org/x/tools](https://github.com/golang/tools/blob/2c0ae70061356820330c96810d9483beb9a6da8e/LICENSE) + +* License: BSD 3-Clause "New" or "Revised" License + +``` +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + +## [google.golang.org/genproto](https://github.com/googleapis/go-genproto/blob/64821d5d210748c883cd2b809589555ae4654203/LICENSE) + +* License: Apache License 2.0 + +``` +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. +``` + + +## [google.golang.org/grpc](https://github.com/grpc/grpc-go/blob/v1.22.1/LICENSE) + +* License: Apache License 2.0 + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [gopkg.in/alecthomas/kingpin.v2](https://github.com/alecthomas/kingpin/blob/v2.2.6/COPYING) + +* License: MIT License + +``` +Copyright (C) 2014 Alec Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + + +## [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml/blob/v2.2.2/LICENSE) + +* License: Apache License 2.0 + +``` + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [istio.io/api](https://github.com/istio/api/blob/0a0442bf866448e56062e950e630e7d893a84a19/LICENSE) + +* License: Apache License 2.0 + +``` + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016-2019 Istio Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [istio.io/istio](https://github.com/istio/istio/blob/76f15793c4f9b259c075c2c9a99d13f64f09599f/LICENSE) + +* License: Apache License 2.0 + +``` + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016-2019 Istio Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [istio.io/pkg](https://github.com/istio/pkg/blob/d61a610098d9935710f61bd3dc192e1324d7ce7e/LICENSE) + +* License: Apache License 2.0 + +``` + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016-2019 Istio Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + +## [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery/blob/master/LICENSE) + +* License: Apache License 2.0 + +``` + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/bin/bump_version.sh b/bin/bump_version.sh new file mode 100755 index 0000000..433b566 --- /dev/null +++ b/bin/bump_version.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Bump project version in preperation for release. + +set -e + +readonly BIN="$( basename "$0" )" +readonly BIN_DIR="$( cd "$(dirname "$0")" ; pwd -P )" +readonly VERSION_FILE="${BIN_DIR}/../VERSION" +readonly CHANGELOG_FILE="${BIN_DIR}/../CHANGELOG.md" +readonly HELM_CHART_MANIFEST="${BIN_DIR}/../helm-charts/Chart.yaml" + +# Matches the expected semver spec: https://semver.org/ +readonly SEMVER_RE="^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(\\-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$" + +# Display CLI help dialogue. +usage() { + cat <&2 + exit 1 + fi + + if [ $# -gt 1 ] + then + echo "unknown arguments: ${*:2}" + usage >&2 + exit 1 + fi + + version="$1" + validate_version "$version" + + # Add version release section to changelog. + sed -i.bkup -e "/^## Unreleased.*/a\\ +\\ +## $version\\ +" "$CHANGELOG_FILE" && rm "${CHANGELOG_FILE}.bkup" + + # Update app version in helm chart + sed -i.bkup -e "/^appVersion.*/s/.*/appVersion: \"$version\"/" "$HELM_CHART_MANIFEST" \ + && rm "${HELM_CHART_MANIFEST}.bkup" + + # Update the project version file. + echo "$version" > "$VERSION_FILE" +} + +main "$@" diff --git a/bin/mixer_codegen.sh b/bin/mixer_codegen.sh new file mode 100755 index 0000000..7298c64 --- /dev/null +++ b/bin/mixer_codegen.sh @@ -0,0 +1,217 @@ +#!/bin/bash + +# Copyright 2019 New Relic Corporation +# Copyright 2018 Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Modifications Copyright (C) 2019 New Relic Corporation + +die () { + echo "ERROR: $*. Aborting." >&2 + exit 1 +} + +SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOTDIR="$(dirname "$SCRIPTPATH")" + +set -e + +outdir=$ROOTDIR +file=$ROOTDIR +## To run `go generate` locally, utilize the `bin/protoc.sh` script +## this script runs a docker container, some CICD platforms may +## object to Docker in Docker. +## Only one (1) protoc variable should be set. +# protoc="$ROOTDIR/bin/protoc.sh" +protoc="/usr/bin/protoc" + +optimport=$ROOTDIR +template=$ROOTDIR + +optproto=false +optadapter=false +opttemplate=false +gendoc=true +# extra flags are arguments that are passed to the underlying tool verbatim +# Its value depend on the context of the main generation flag. +# * for parent flag `-a`, the `-x` flag can provide additional options required by tool mixer/tool/mixgen adapter --help +extraflags="" + +while getopts ':f:o:p:i:t:a:d:x:' flag; do + case "${flag}" in + f) $opttemplate && $optadapter && die "Cannot use proto file option (-f) with template file option (-t) or adapter option (-a)" + optproto=true + file+="/${OPTARG}" + ;; + a) $opttemplate && $optproto && die "Cannot use proto adapter option (-a) with template file option (-t) or file option (-f)" + optadapter=true + file+="/${OPTARG}" + ;; + o) outdir="${OPTARG}" ;; + p) protoc="${OPTARG}" ;; + x) extraflags="${OPTARG}" ;; + i) optimport+=/"${OPTARG}" ;; + t) $optproto && $optadapter && die "Cannot use template file option (-t) with proto file option (-f) or adapter option (-a)" + opttemplate=true + template+="/${OPTARG}" + ;; + d) gendoc="${OPTARG}" ;; + *) die "Unexpected option ${flag}" ;; + esac +done + +# echo "outdir: ${outdir}" + +IMPORTS=( + "--proto_path=${ROOTDIR}" + "--proto_path=${ROOTDIR}/vendor/istio.io/api" + "--proto_path=${ROOTDIR}/vendor/github.com/gogo/protobuf" + "--proto_path=${ROOTDIR}/vendor/github.com/gogo/googleapis" + "--proto_path=$optimport" +) + +mappings=( + "gogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto" + "google/protobuf/any.proto=github.com/gogo/protobuf/types" + "google/protobuf/duration.proto=github.com/gogo/protobuf/types" + "google/protobuf/timestamp.proto=github.com/gogo/protobuf/types" + "google/protobuf/struct.proto=github.com/gogo/protobuf/types" + "google/rpc/status.proto=github.com/gogo/googleapis/google/rpc" + "google/rpc/code.proto=github.com/gogo/googleapis/google/rpc" + "google/rpc/error_details.proto=github.com/gogo/googleapis/google/rpc" +) + +MAPPINGS="" + +for i in "${mappings[@]}" +do + MAPPINGS+="M$i," +done + +PLUGIN="--gogoslick_out=plugins=grpc,$MAPPINGS:$outdir" + +GENDOCS_PLUGIN="--docs_out=warnings=true,mode=html_fragment_with_front_matter:" +GENDOCS_PLUGIN_FILE=$GENDOCS_PLUGIN$(dirname "${file}") +GENDOCS_PLUGIN_TEMPLATE=$GENDOCS_PLUGIN$(dirname "${template}") + +# handle template code generation +if [ "$opttemplate" = true ]; then + + template_mappings=( + "google/protobuf/any.proto:github.com/gogo/protobuf/types" + "gogoproto/gogo.proto:github.com/gogo/protobuf/gogoproto" + "google/protobuf/duration.proto:github.com/gogo/protobuf/types" + "google/protobuf/timestamp.proto:github.com/gogo/protobuf/types" + "google/rpc/status.proto:github.com/gogo/googleapis/google/rpc" + "google/protobuf/struct.proto:github.com/gogo/protobuf/types" + ) + + TMPL_GEN_MAP=() + TMPL_PROTOC_MAPPING="" + + for i in "${template_mappings[@]}" + do + TMPL_GEN_MAP+=("-m" "$i") + TMPL_PROTOC_MAPPING+="M${i/:/=}," + done + + TMPL_PLUGIN="--gogoslick_out=plugins=grpc,$TMPL_PROTOC_MAPPING:$outdir" + + descriptor_set="_proto.descriptor_set" + handler_gen_go="_handler.gen.go" + handler_service="_handler_service.proto" + pb_go=".pb.go" + + templateDS=${template/.proto/$descriptor_set} + templateHG=${template/.proto/$handler_gen_go} + templateHSP=${template/.proto/$handler_service} + templatePG=${template/.proto/$pb_go} + # generate the descriptor set for the intermediate artifacts + DESCRIPTOR=( + "--include_imports" + "--include_source_info" + "--descriptor_set_out=$templateDS" + ) + if [ "$gendoc" = true ]; then + err=$($protoc "${DESCRIPTOR[@]}" "${IMPORTS[@]}" "$PLUGIN" "$GENDOCS_PLUGIN_TEMPLATE" "$template") + else + err=$($protoc "${DESCRIPTOR[@]}" "${IMPORTS[@]}" "$PLUGIN" "$template") + fi + if [ -n "$err" ]; then + die "template generation failure: $err"; + fi + + go run "$GOPATH/src/istio.io/istio/mixer/tools/mixgen/main.go" api -t "$templateDS" --go_out "$templateHG" --proto_out "$templateHSP" "${TMPL_GEN_MAP[@]}" + + err=$($protoc "${IMPORTS[@]}" "$TMPL_PLUGIN" "$templateHSP") + if [ -n "$err" ]; then + die "template generation failure: $err"; + fi + + templateSDS=${template/.proto/_handler_service.descriptor_set} + SDESCRIPTOR=( + "--include_imports" + "--include_source_info" + "--descriptor_set_out=$templateSDS" + ) + err=$($protoc "${SDESCRIPTOR[@]}" "${IMPORTS[@]}" "$PLUGIN" "$templateHSP") + if [ -n "$err" ]; then + die "template generation failure: $err"; + fi + + templateYaml=${template/.proto/.yaml} + go run "$GOPATH/src/istio.io/istio/mixer/tools/mixgen/main.go" template -d "$templateSDS" -o "$templateYaml" -n "$(basename "$(dirname "${template}")")" + + rm "$templatePG" + + exit 0 +fi + +# handle adapter code generation +if [ "$optadapter" = true ]; then + if [ "$gendoc" = true ]; then + err=$($protoc "${IMPORTS[@]}" "$PLUGIN" "$GENDOCS_PLUGIN_FILE" "$file") + else + err=$($protoc "${IMPORTS[@]}" "$PLUGIN" "$file") + fi + if [ -n "$err" ]; then + die "generation failure: $err"; + fi + + adapteCfdDS=${file}_descriptor + err=$($protoc "${IMPORTS[@]}" "$PLUGIN" --include_imports --include_source_info --descriptor_set_out="${adapteCfdDS}" "$file") + if [ -n "$err" ]; then + die "config generation failure: $err"; + fi + + IFS=" " read -r -a extraflags_array <<< "$extraflags" + go run "$GOPATH/src/istio.io/istio/mixer/tools/mixgen/main.go" adapter -c "$adapteCfdDS" -o "$(dirname "${file}")" "${extraflags_array[@]}" + + exit 0 +fi + +# handle simple protoc-based generation +if [ "$gendoc" = true ]; then + err=$($protoc "${IMPORTS[@]}" "$PLUGIN" "$GENDOCS_PLUGIN_FILE" "$file") +else + err=$($protoc "${IMPORTS[@]}" "$PLUGIN" "$file") +fi +if [ -n "$err" ]; then + die "generation failure: $err"; +fi + +err=$($protoc "${IMPORTS[@]}" "$PLUGIN" --include_imports --include_source_info --descriptor_set_out="${file}_descriptor" "$file") +if [ -n "$err" ]; then +die "config generation failure: $err"; +fi diff --git a/bin/protoc.sh b/bin/protoc.sh new file mode 100755 index 0000000..9b8cb59 --- /dev/null +++ b/bin/protoc.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# Copyright 2019 New Relic Corporation +# Copyright 2018 Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Modifications Copyright (C) 2019 New Relic Corporation + +if [[ $# -le 0 ]]; then + echo Require more than one argument to protoc. + exit 1 +fi + +SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ROOTDIR="$(dirname "$SCRIPTPATH")" + +gen_img=gcr.io/istio-testing/protoc:2019-03-29 + +docker run -i --volume /var/run/docker.sock:/var/run/docker.sock \ + --rm --entrypoint /usr/bin/protoc -v "$ROOTDIR:$ROOTDIR" -w "$(pwd)" $gen_img "$@" diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..399a15b --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,145 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + "os/signal" + "syscall" + + newrelic "github.com/newrelic/newrelic-istio-adapter" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/instrumentation" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "gopkg.in/alecthomas/kingpin.v2" +) + +// Version is the semver set during build with an ldflag arg. +// E.g. go build -ldflags "-X main.Version=0.1.0" ... +var Version = "undefined" + +var ( + portPtr = kingpin.Flag("port", "port gRPC server listens on").Default("55912").OverrideDefaultFromEnvar("NEW_RELIC_PORT").Short('p').Int32() + clusterNamePtr = kingpin.Flag("cluster-name", "Name of cluster where metrics come from").OverrideDefaultFromEnvar("NEW_RELIC_CLUSTER_NAME").String() + debugPtr = kingpin.Flag("debug", "enable debug logging").OverrideDefaultFromEnvar("NEW_RELIC_DEBUG").Short('d').Bool() + harvestPeriodPtr = kingpin.Flag("harvest-period", "rate data is reported to New Relic").Default("5s").OverrideDefaultFromEnvar("NEW_RELIC_HARVEST_PERIOD").Duration() + metricsHostPtr = kingpin.Flag("metrics-host", "Endpoint to send metrics (used for debugging)").OverrideDefaultFromEnvar("NEW_RELIC_METRICS_HOST").String() + spansHostPtr = kingpin.Flag("spans-host", "Endpoint to send spans (used for debugging)").OverrideDefaultFromEnvar("NEW_RELIC_SPANS_HOST").String() + mtlsCertPtr = kingpin.Flag("cert", "mTLS certificate for gRPC server").OverrideDefaultFromEnvar("NEW_RELIC_MTLS_CERT").ExistingFile() + mtlsKeyPtr = kingpin.Flag("key", "mTLS key for gRPC server").OverrideDefaultFromEnvar("NEW_RELIC_MTLS_KEY").ExistingFile() + mtlsCAPtr = kingpin.Flag("ca", "mTLS CA certificate for gRPC server").OverrideDefaultFromEnvar("NEW_RELIC_MTLS_CA").ExistingFile() + apiKeyPtr = kingpin.Arg("api-key", "New Relic API key").Envar("NEW_RELIC_API_KEY").Required().String() +) + +func getServerTLSOption(cert, key, ca string) (grpc.ServerOption, error) { + certificate, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + return nil, fmt.Errorf("failed to load key cert pair: %v", err) + } + tlsConfig := &tls.Config{Certificates: []tls.Certificate{certificate}} + + if ca != "" { + certPool := x509.NewCertPool() + bs, err := ioutil.ReadFile(ca) + if err != nil { + return nil, fmt.Errorf("failed to read client ca cert %q: %v", ca, err) + } + + if ok := certPool.AppendCertsFromPEM(bs); !ok { + return nil, fmt.Errorf("failed to append client certs") + } + + tlsConfig.ClientCAs = certPool + } + + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + + return grpc.Creds(credentials.NewTLS(tlsConfig)), nil +} + +func main() { + kingpin.Version(Version) + kingpin.Parse() + + var commonAttrs map[string]interface{} + if clusterNamePtr != nil && *clusterNamePtr != "" { + commonAttrs = map[string]interface{}{ + "cluster.name": *clusterNamePtr, + } + } + + debugLogFile := ioutil.Discard + if *debugPtr { + debugLogFile = os.Stderr + } + + agg := instrumentation.NewMetricAggregator() + h := telemetry.NewHarvester( + telemetry.ConfigAPIKey(*apiKeyPtr), + telemetry.ConfigCommonAttributes(commonAttrs), + telemetry.ConfigBasicErrorLogger(os.Stderr), + telemetry.ConfigHarvestPeriod(*harvestPeriodPtr), + telemetry.ConfigBasicDebugLogger(debugLogFile), + agg.BeforeHarvest, + func(cfg *telemetry.Config) { + cfg.MetricsURLOverride = *metricsHostPtr + cfg.SpansURLOverride = *spansHostPtr + }, + ) + + address := fmt.Sprintf(":%d", *portPtr) + + var err error + var s *newrelic.Server + if *mtlsCertPtr != "" && *mtlsKeyPtr != "" { + so, err := getServerTLSOption(*mtlsCertPtr, *mtlsKeyPtr, *mtlsCAPtr) + if err != nil { + fmt.Printf("Unable to configure gRPC server TLS: %v\n", err) + os.Exit(-1) + } + s, err = newrelic.NewServer(address, agg, h, so) + } else { + s, err = newrelic.NewServer(address, agg, h) + } + if err != nil { + fmt.Printf("Unable to start server: %v\n", err) + os.Exit(-1) + } + + // Termination handler. + term := make(chan os.Signal, 1) + signal.Notify(term, os.Interrupt, syscall.SIGTERM) + go func() { + select { + case <-term: + fmt.Println("Received SIGTERM, exiting gracefully...") + if err := s.Close(); err != nil { + fmt.Printf("%v\n", err) + } + } + }() + + s.Run() + if err := s.Wait(); err != nil { + fmt.Printf("%v\n", err) + os.Exit(1) + } +} diff --git a/config/adapter.newrelic.config.pb.html b/config/adapter.newrelic.config.pb.html new file mode 100644 index 0000000..2f4626c --- /dev/null +++ b/config/adapter.newrelic.config.pb.html @@ -0,0 +1,167 @@ +--- +title: adapter.newrelic.config +layout: protoc-gen-docs +generator: protoc-gen-docs +number_of_entries: 3 +--- +

An Istio Mixer adapter to send telemetry data to New Relic.

+ +

Params

+
+

Configuration format for the newrelic adapter.

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
namespacestring +

Optional. The namespace is used as a prefix for metric names in New Relic. +An example: for a metric named requestSize with a namespace of istio, +the full metric name in New Relic becomes istio.requestSize.

+ +
metricsmap<string, Params.MetricInfo> +

Map of Istio metric instance names and the corresponding New Relic +MetricInfo specification. This identifies what to send New Relic and +in what form it should be sent.

+ +

Any metric instances Istio sends to the adapter but not specified here +will be dropped and not exported to New Relic.

+ +
+
+

Params.MetricInfo

+
+

Describes how to represent an Istio metric instance in New Relic.

+ + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
namestring +

Recommended. The name of the metric (scoped by namespaces) in New Relic.

+ +

The name must not be empty and the fully qualified name (prefixed +with the namespace) must contain 255 16-bit code units (UTF-16) or +less. Otherwise, an error will be logged and no metric will be sent +to New Relic.

+ +
typeParams.MetricInfo.Type +

Required. New Relic metric type to interpret the Istio instance as.

+ +
+
+

Params.MetricInfo.Type

+
+

New Relic Metric types.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
UNSPECIFIED +

Default and invalid unspecified type.

+ +

An error will be logged and the metric dropped if unspecified.

+ +
GAUGE +

A New Relic Gauge type.

+ +

This metric type represents the instantaneous state of something +or process that can both increase and decrease in value.

+ +

For example, this metric type would be used to record:

+ +
    +
  • the network throughput of a service
  • +
  • the storage capacity used on a server
  • +
  • the size of a queue
  • +
+ +
COUNT +

A New Relic Count type.

+ +

This metric type represents the number of occurrences for an event +within a time window. It is important to note that this is not the +cumulative tally of occurrences since the beginning of +measurements. Rather, this metric type represents the change in the +cumulative tally of events within a time window.

+ +

For example, this metric type would be used to record:

+ +
    +
  • the number of requests to a service
  • +
  • the number of tasks submitted to a processor
  • +
  • the number of errors produced
  • +
+ +
SUMMARY +

New Relic Summary type.

+ +

This metric type reports aggregated information about discrete +events. The information is recorded as a count of events, average +event values, sum of event values, and the minimum and maximum +event values observed within a time window.

+ +

For example, this metric type would be used to record:

+ +
    +
  • the duration and count of requests to service
  • +
  • the duration and count of database transactions
  • +
  • the time each message spent in a queue
  • +
+ +
+
diff --git a/config/config.pb.go b/config/config.pb.go new file mode 100644 index 0000000..5f2b4ee --- /dev/null +++ b/config/config.pb.go @@ -0,0 +1,973 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: config/config.proto + +// An Istio Mixer adapter to send telemetry data to New Relic. + +package config + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_sortkeys "github.com/gogo/protobuf/sortkeys" + io "io" + math "math" + reflect "reflect" + strconv "strconv" + strings "strings" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +// New Relic Metric types. +type Params_MetricInfo_Type int32 + +const ( + // Default and invalid unspecified type. + // + // An error will be logged and the metric dropped if unspecified. + UNSPECIFIED Params_MetricInfo_Type = 0 + // A New Relic `Gauge` type. + // + // This metric type represents the instantaneous state of something + // or process that can both increase and decrease in value. + // + // For example, this metric type would be used to record: + // + // * the network throughput of a service + // * the storage capacity used on a server + // * the size of a queue + GAUGE Params_MetricInfo_Type = 1 + // A New Relic `Count` type. + // + // This metric type represents the number of occurrences for an event + // within a time window. It is important to note that this is not the + // cumulative tally of occurrences since the beginning of + // measurements. Rather, this metric type represents the change in the + // cumulative tally of events within a time window. + // + // For example, this metric type would be used to record: + // + // * the number of requests to a service + // * the number of tasks submitted to a processor + // * the number of errors produced + COUNT Params_MetricInfo_Type = 2 + // New Relic `Summary` type. + // + // This metric type reports aggregated information about discrete + // events. The information is recorded as a count of events, average + // event values, sum of event values, and the minimum and maximum + // event values observed within a time window. + // + // For example, this metric type would be used to record: + // + // * the duration and count of requests to service + // * the duration and count of database transactions + // * the time each message spent in a queue + SUMMARY Params_MetricInfo_Type = 3 +) + +var Params_MetricInfo_Type_name = map[int32]string{ + 0: "UNSPECIFIED", + 1: "GAUGE", + 2: "COUNT", + 3: "SUMMARY", +} + +var Params_MetricInfo_Type_value = map[string]int32{ + "UNSPECIFIED": 0, + "GAUGE": 1, + "COUNT": 2, + "SUMMARY": 3, +} + +func (Params_MetricInfo_Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_cc332a44e926b360, []int{0, 0, 0} +} + +// Configuration format for the `newrelic` adapter. +type Params struct { + // Optional. The namespace is used as a prefix for metric names in New Relic. + // An example: for a metric named `requestSize` with a namespace of `istio`, + // the full metric name in New Relic becomes `istio.requestSize`. + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + // Map of Istio metric instance names and the corresponding New Relic + // MetricInfo specification. This identifies what to send New Relic and + // in what form it should be sent. + // + // Any metric instances Istio sends to the adapter but not specified here + // will be dropped and not exported to New Relic. + Metrics map[string]*Params_MetricInfo `protobuf:"bytes,2,rep,name=metrics,proto3" json:"metrics,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (m *Params) Reset() { *m = Params{} } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_cc332a44e926b360, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + +func (m *Params) GetMetrics() map[string]*Params_MetricInfo { + if m != nil { + return m.Metrics + } + return nil +} + +// Describes how to represent an Istio metric instance in New Relic. +type Params_MetricInfo struct { + // Recommended. The name of the metric (scoped by namespaces) in New Relic. + // + // The name must not be empty and the fully qualified name (prefixed + // with the namespace) must contain 255 16-bit code units (UTF-16) or + // less. Otherwise, an error will be logged and no metric will be sent + // to New Relic. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Required. New Relic metric type to interpret the Istio instance as. + Type Params_MetricInfo_Type `protobuf:"varint,2,opt,name=type,proto3,enum=adapter.newrelic.config.Params_MetricInfo_Type" json:"type,omitempty"` +} + +func (m *Params_MetricInfo) Reset() { *m = Params_MetricInfo{} } +func (*Params_MetricInfo) ProtoMessage() {} +func (*Params_MetricInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_cc332a44e926b360, []int{0, 0} +} +func (m *Params_MetricInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params_MetricInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params_MetricInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params_MetricInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params_MetricInfo.Merge(m, src) +} +func (m *Params_MetricInfo) XXX_Size() int { + return m.Size() +} +func (m *Params_MetricInfo) XXX_DiscardUnknown() { + xxx_messageInfo_Params_MetricInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_Params_MetricInfo proto.InternalMessageInfo + +func (m *Params_MetricInfo) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Params_MetricInfo) GetType() Params_MetricInfo_Type { + if m != nil { + return m.Type + } + return UNSPECIFIED +} + +func init() { + proto.RegisterEnum("adapter.newrelic.config.Params_MetricInfo_Type", Params_MetricInfo_Type_name, Params_MetricInfo_Type_value) + proto.RegisterType((*Params)(nil), "adapter.newrelic.config.Params") + proto.RegisterMapType((map[string]*Params_MetricInfo)(nil), "adapter.newrelic.config.Params.MetricsEntry") + proto.RegisterType((*Params_MetricInfo)(nil), "adapter.newrelic.config.Params.MetricInfo") +} + +func init() { proto.RegisterFile("config/config.proto", fileDescriptor_cc332a44e926b360) } + +var fileDescriptor_cc332a44e926b360 = []byte{ + // 356 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x51, 0xbf, 0x4f, 0xea, 0x40, + 0x1c, 0xbf, 0x2b, 0xbf, 0xc2, 0xb7, 0x2f, 0xef, 0x35, 0xf7, 0x5e, 0xf2, 0x08, 0x31, 0x17, 0xc2, + 0x44, 0x8c, 0x39, 0x12, 0x5c, 0x0c, 0x71, 0x10, 0xb1, 0x10, 0x06, 0x90, 0x14, 0x3a, 0xe8, 0x76, + 0xd6, 0x83, 0x10, 0xa1, 0x6d, 0x4a, 0xd5, 0x74, 0xf3, 0x4f, 0xf0, 0x5f, 0x70, 0xf3, 0x4f, 0x71, + 0x64, 0x64, 0x94, 0x63, 0x71, 0x64, 0x76, 0x32, 0xed, 0x61, 0x74, 0x31, 0x61, 0xba, 0xcf, 0xdd, + 0xe7, 0x67, 0x72, 0xf0, 0xd7, 0xf1, 0xdc, 0xd1, 0x64, 0x5c, 0x55, 0x07, 0xf3, 0x03, 0x2f, 0xf4, + 0xc8, 0x7f, 0x7e, 0xcd, 0xfd, 0x50, 0x04, 0xcc, 0x15, 0xf7, 0x81, 0x98, 0x4e, 0x1c, 0xa6, 0xe8, + 0xe2, 0xbf, 0xb1, 0x37, 0xf6, 0x12, 0x4d, 0x35, 0x46, 0x4a, 0x5e, 0x7e, 0xd7, 0x20, 0xdb, 0xe7, + 0x01, 0x9f, 0xcd, 0xc9, 0x1e, 0xe4, 0x5d, 0x3e, 0x13, 0x73, 0x9f, 0x3b, 0xa2, 0x80, 0x4b, 0xb8, + 0x92, 0xb7, 0xbe, 0x1e, 0x48, 0x0b, 0x72, 0x33, 0x11, 0x06, 0x13, 0x67, 0x5e, 0xd0, 0x4a, 0xa9, + 0x8a, 0x5e, 0x3b, 0x60, 0x3f, 0x34, 0x31, 0x95, 0xc7, 0xba, 0x4a, 0x6e, 0xba, 0x61, 0x10, 0x59, + 0x9f, 0xe6, 0xe2, 0x13, 0x06, 0x50, 0x4c, 0xc7, 0x1d, 0x79, 0x84, 0x40, 0x3a, 0xee, 0xd8, 0xf6, + 0x25, 0x98, 0x34, 0x21, 0x1d, 0x46, 0xbe, 0x28, 0x68, 0x25, 0x5c, 0xf9, 0x5d, 0xab, 0xee, 0xd6, + 0x13, 0xa7, 0xb1, 0x61, 0xe4, 0x0b, 0x2b, 0x31, 0x97, 0xeb, 0x90, 0x8e, 0x6f, 0xe4, 0x0f, 0xe8, + 0x76, 0x6f, 0xd0, 0x37, 0x9b, 0x9d, 0x56, 0xc7, 0x3c, 0x33, 0x10, 0xc9, 0x43, 0xa6, 0xdd, 0xb0, + 0xdb, 0xa6, 0x81, 0x63, 0xd8, 0x3c, 0xb7, 0x7b, 0x43, 0x43, 0x23, 0x3a, 0xe4, 0x06, 0x76, 0xb7, + 0xdb, 0xb0, 0x2e, 0x8c, 0x54, 0x71, 0x04, 0xbf, 0xbe, 0x8f, 0x27, 0x06, 0xa4, 0x6e, 0x44, 0xb4, + 0xdd, 0x18, 0x43, 0x72, 0x02, 0x99, 0x3b, 0x3e, 0xbd, 0x55, 0x1b, 0xf5, 0xda, 0xfe, 0xee, 0x1b, + 0x2d, 0x65, 0xac, 0x6b, 0x47, 0xf8, 0xf4, 0x78, 0xb1, 0xa2, 0x68, 0xb9, 0xa2, 0x68, 0xb3, 0xa2, + 0xf8, 0x41, 0x52, 0xfc, 0x2c, 0x29, 0x7e, 0x91, 0x14, 0x2f, 0x24, 0xc5, 0xaf, 0x92, 0xe2, 0x37, + 0x49, 0xd1, 0x46, 0x52, 0xfc, 0xb8, 0xa6, 0x68, 0xb1, 0xa6, 0x68, 0xb9, 0xa6, 0xe8, 0x32, 0xab, + 0xa2, 0xaf, 0xb2, 0xc9, 0x0f, 0x1e, 0x7e, 0x04, 0x00, 0x00, 0xff, 0xff, 0xbf, 0xc2, 0xac, 0xdf, + 0x07, 0x02, 0x00, 0x00, +} + +func (x Params_MetricInfo_Type) String() string { + s, ok := Params_MetricInfo_Type_name[int32(x)] + if ok { + return s + } + return strconv.Itoa(int(x)) +} +func (this *Params) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Params) + if !ok { + that2, ok := that.(Params) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Namespace != that1.Namespace { + return false + } + if len(this.Metrics) != len(that1.Metrics) { + return false + } + for i := range this.Metrics { + if !this.Metrics[i].Equal(that1.Metrics[i]) { + return false + } + } + return true +} +func (this *Params_MetricInfo) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Params_MetricInfo) + if !ok { + that2, ok := that.(Params_MetricInfo) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Name != that1.Name { + return false + } + if this.Type != that1.Type { + return false + } + return true +} +func (this *Params) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 6) + s = append(s, "&config.Params{") + s = append(s, "Namespace: "+fmt.Sprintf("%#v", this.Namespace)+",\n") + keysForMetrics := make([]string, 0, len(this.Metrics)) + for k, _ := range this.Metrics { + keysForMetrics = append(keysForMetrics, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForMetrics) + mapStringForMetrics := "map[string]*Params_MetricInfo{" + for _, k := range keysForMetrics { + mapStringForMetrics += fmt.Sprintf("%#v: %#v,", k, this.Metrics[k]) + } + mapStringForMetrics += "}" + if this.Metrics != nil { + s = append(s, "Metrics: "+mapStringForMetrics+",\n") + } + s = append(s, "}") + return strings.Join(s, "") +} +func (this *Params_MetricInfo) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 6) + s = append(s, "&config.Params_MetricInfo{") + s = append(s, "Name: "+fmt.Sprintf("%#v", this.Name)+",\n") + s = append(s, "Type: "+fmt.Sprintf("%#v", this.Type)+",\n") + s = append(s, "}") + return strings.Join(s, "") +} +func valueToGoStringConfig(v interface{}, typ string) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv) +} +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Namespace) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintConfig(dAtA, i, uint64(len(m.Namespace))) + i += copy(dAtA[i:], m.Namespace) + } + if len(m.Metrics) > 0 { + for k, _ := range m.Metrics { + dAtA[i] = 0x12 + i++ + v := m.Metrics[k] + msgSize := 0 + if v != nil { + msgSize = v.Size() + msgSize += 1 + sovConfig(uint64(msgSize)) + } + mapSize := 1 + len(k) + sovConfig(uint64(len(k))) + msgSize + i = encodeVarintConfig(dAtA, i, uint64(mapSize)) + dAtA[i] = 0xa + i++ + i = encodeVarintConfig(dAtA, i, uint64(len(k))) + i += copy(dAtA[i:], k) + if v != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintConfig(dAtA, i, uint64(v.Size())) + n1, err1 := v.MarshalTo(dAtA[i:]) + if err1 != nil { + return 0, err1 + } + i += n1 + } + } + } + return i, nil +} + +func (m *Params_MetricInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params_MetricInfo) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Name) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintConfig(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + } + if m.Type != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintConfig(dAtA, i, uint64(m.Type)) + } + return i, nil +} + +func encodeVarintConfig(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Namespace) + if l > 0 { + n += 1 + l + sovConfig(uint64(l)) + } + if len(m.Metrics) > 0 { + for k, v := range m.Metrics { + _ = k + _ = v + l = 0 + if v != nil { + l = v.Size() + l += 1 + sovConfig(uint64(l)) + } + mapEntrySize := 1 + len(k) + sovConfig(uint64(len(k))) + l + n += mapEntrySize + 1 + sovConfig(uint64(mapEntrySize)) + } + } + return n +} + +func (m *Params_MetricInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + if l > 0 { + n += 1 + l + sovConfig(uint64(l)) + } + if m.Type != 0 { + n += 1 + sovConfig(uint64(m.Type)) + } + return n +} + +func sovConfig(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozConfig(x uint64) (n int) { + return sovConfig(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *Params) String() string { + if this == nil { + return "nil" + } + keysForMetrics := make([]string, 0, len(this.Metrics)) + for k, _ := range this.Metrics { + keysForMetrics = append(keysForMetrics, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForMetrics) + mapStringForMetrics := "map[string]*Params_MetricInfo{" + for _, k := range keysForMetrics { + mapStringForMetrics += fmt.Sprintf("%v: %v,", k, this.Metrics[k]) + } + mapStringForMetrics += "}" + s := strings.Join([]string{`&Params{`, + `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, + `Metrics:` + mapStringForMetrics + `,`, + `}`, + }, "") + return s +} +func (this *Params_MetricInfo) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&Params_MetricInfo{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Type:` + fmt.Sprintf("%v", this.Type) + `,`, + `}`, + }, "") + return s +} +func valueToStringConfig(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConfig + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConfig + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Namespace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metrics", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthConfig + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthConfig + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metrics == nil { + m.Metrics = make(map[string]*Params_MetricInfo) + } + var mapkey string + var mapvalue *Params_MetricInfo + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + 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 ErrIntOverflowConfig + } + 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 ErrInvalidLengthConfig + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthConfig + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthConfig + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthConfig + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &Params_MetricInfo{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthConfig + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Metrics[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthConfig + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Params_MetricInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MetricInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MetricInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConfig + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConfig + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= Params_MetricInfo_Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthConfig + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipConfig(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthConfig + } + iNdEx += length + if iNdEx < 0 { + return 0, ErrInvalidLengthConfig + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipConfig(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + if iNdEx < 0 { + return 0, ErrInvalidLengthConfig + } + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthConfig = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowConfig = fmt.Errorf("proto: integer overflow") +) diff --git a/config/config.proto b/config/config.proto new file mode 100644 index 0000000..f18a434 --- /dev/null +++ b/config/config.proto @@ -0,0 +1,100 @@ +// Copyright 2019 New Relic Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +// An Istio Mixer adapter to send telemetry data to New Relic. +package adapter.newrelic.config; + +import "gogoproto/gogo.proto"; + +option go_package="config"; + +// Configuration format for the `newrelic` adapter. +message Params { + // Optional. The namespace is used as a prefix for metric names in New Relic. + // An example: for a metric named `requestSize` with a namespace of `istio`, + // the full metric name in New Relic becomes `istio.requestSize`. + string namespace = 1; + + // Describes how to represent an Istio metric instance in New Relic. + message MetricInfo { + // Recommended. The name of the metric (scoped by namespaces) in New Relic. + // + // The name must not be empty and the fully qualified name (prefixed + // with the namespace) must contain 255 16-bit code units (UTF-16) or + // less. Otherwise, an error will be logged and no metric will be sent + // to New Relic. + string name = 1; + + // New Relic Metric types. + enum Type { + // Default and invalid unspecified type. + // + // An error will be logged and the metric dropped if unspecified. + UNSPECIFIED = 0; + + // A New Relic `Gauge` type. + // + // This metric type represents the instantaneous state of something + // or process that can both increase and decrease in value. + // + // For example, this metric type would be used to record: + // + // * the network throughput of a service + // * the storage capacity used on a server + // * the size of a queue + GAUGE = 1; + + // A New Relic `Count` type. + // + // This metric type represents the number of occurrences for an event + // within a time window. It is important to note that this is not the + // cumulative tally of occurrences since the beginning of + // measurements. Rather, this metric type represents the change in the + // cumulative tally of events within a time window. + // + // For example, this metric type would be used to record: + // + // * the number of requests to a service + // * the number of tasks submitted to a processor + // * the number of errors produced + COUNT = 2; + + // New Relic `Summary` type. + // + // This metric type reports aggregated information about discrete + // events. The information is recorded as a count of events, average + // event values, sum of event values, and the minimum and maximum + // event values observed within a time window. + // + // For example, this metric type would be used to record: + // + // * the duration and count of requests to service + // * the duration and count of database transactions + // * the time each message spent in a queue + SUMMARY = 3; + } + // Required. New Relic metric type to interpret the Istio instance as. + Type type = 2; + } + + // Map of Istio metric instance names and the corresponding New Relic + // MetricInfo specification. This identifies what to send New Relic and + // in what form it should be sent. + // + // Any metric instances Istio sends to the adapter but not specified here + // will be dropped and not exported to New Relic. + map metrics = 2; +} diff --git a/config/config.proto_descriptor b/config/config.proto_descriptor new file mode 100644 index 0000000000000000000000000000000000000000..9de44bab515eba3fc97cdf7f0a7494540cfc2459 GIT binary patch literal 65517 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

15bEzh8~P*&$^$t^cyJ1w(t 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 milliseconds as float64 +// string, bool, all number types -> unchanged +// everything else -> string +func ValueToAttribute(in *policy.Value) interface{} { + switch t := in.GetValue().(type) { + case *policy.Value_StringValue: + return t.StringValue + case *policy.Value_Int64Value: + return t.Int64Value + case *policy.Value_DoubleValue: + return t.DoubleValue + case *policy.Value_BoolValue: + return t.BoolValue + case *policy.Value_DurationValue: + return float64(t.DurationValue.Value.Seconds) * 1000.0 // milliseconds NR preferred unit + case *policy.Value_TimestampValue: + return float64(t.TimestampValue.Value.Seconds) * 1000.0 + case *policy.Value_IpAddressValue: + return adapter.Stringify(t.IpAddressValue.Value) + case *policy.Value_EmailAddressValue: + return adapter.Stringify(t.EmailAddressValue.Value) + case *policy.Value_DnsNameValue: + return adapter.Stringify(t.DnsNameValue.Value) + case *policy.Value_UriValue: + return adapter.Stringify(t.UriValue.Value) + case *policy.Value_StringMapValue: + return adapter.Stringify(t.StringMapValue.Value) + default: + return fmt.Sprintf("%v", in) + } + panic("fell through attribute value parsing") +} diff --git a/convert/value_test.go b/convert/value_test.go new file mode 100644 index 0000000..d7a8653 --- /dev/null +++ b/convert/value_test.go @@ -0,0 +1,112 @@ +// Copyright 2019 New Relic Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "net" + "testing" + "time" + + "github.com/gogo/protobuf/types" + policy "istio.io/api/policy/v1beta1" +) + +func TestValueToFloat64(t *testing.T) { + testCases := []struct { + isValid bool + value *policy.Value + expected float64 + }{ + {true, &policy.Value{Value: &policy.Value_StringValue{StringValue: "1"}}, float64(1.0)}, + {false, &policy.Value{Value: &policy.Value_StringValue{StringValue: "love"}}, float64(0.0)}, + {true, &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(2)}}, float64(2.0)}, + {true, &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: float64(3.33)}}, float64(3.33)}, + {true, &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(-2)}}, float64(-2.0)}, + {true, &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: float64(-123.456)}}, float64(-123.456)}, + {true, &policy.Value{Value: &policy.Value_DurationValue{DurationValue: &policy.Duration{ + Value: types.DurationProto(time.Second), + }}}, float64(1000.0)}, + {false, &policy.Value{Value: &policy.Value_BoolValue{BoolValue: true}}, float64(0.0)}, + } + + for _, tc := range testCases { + result, err := ValueToFloat64(tc.value) + if err != nil && tc.isValid { + t.Errorf("decodeMetricValue() error: %v", err) + } + + if result != tc.expected && tc.isValid { + t.Errorf("failed to decode valid value %v: expected %v got %v.", tc.value, tc.expected, result) + continue + } + if result != 0.0 && !tc.isValid { + t.Errorf("decoded an invalid value: expected 0.0 got value %v.", result) + } + } +} + +func TestValueToAttribute(t *testing.T) { + testTime, err := types.TimestampProto(time.Unix(1582230020, 0)) + if err != nil { + t.Fatalf("failed to parse timestamp test data: %v", err) + } + + testCases := []struct { + isValid bool + value *policy.Value + expected interface{} + }{ + {true, &policy.Value{Value: &policy.Value_StringValue{StringValue: "1"}}, "1"}, + {true, &policy.Value{Value: &policy.Value_StringValue{StringValue: "love"}}, "love"}, + {true, &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(2)}}, int64(2)}, + {true, &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: float64(3.33)}}, float64(3.33)}, + {true, &policy.Value{Value: &policy.Value_DurationValue{DurationValue: &policy.Duration{ + Value: types.DurationProto(time.Second), + }}}, float64(1000.0)}, + {true, &policy.Value{Value: &policy.Value_BoolValue{BoolValue: true}}, true}, + {true, &policy.Value{Value: &policy.Value_TimestampValue{TimestampValue: &policy.TimeStamp{ + Value: testTime, + }}}, float64(1582230020000)}, + {true, &policy.Value{Value: &policy.Value_IpAddressValue{IpAddressValue: &policy.IPAddress{ + Value: net.ParseIP("127.0.0.1"), + }}}, "127.0.0.1"}, + {true, &policy.Value{Value: &policy.Value_EmailAddressValue{EmailAddressValue: &policy.EmailAddress{ + Value: "new@rel.ic", + }}}, "new@rel.ic"}, + {true, &policy.Value{Value: &policy.Value_DnsNameValue{DnsNameValue: &policy.DNSName{ + Value: "newrelic.ninja", + }}}, "newrelic.ninja"}, + {true, &policy.Value{Value: &policy.Value_UriValue{UriValue: &policy.Uri{ + Value: "file:///etc/shadow", + }}}, "file:///etc/shadow"}, + {true, &policy.Value{Value: &policy.Value_StringMapValue{StringMapValue: &policy.StringMap{ + Value: map[string]string{"hello": "aloha", "goodbye": "aloha"}, + }}}, "goodbye=aloha&hello=aloha"}, + } + + for _, tc := range testCases { + result := ValueToAttribute(tc.value) + + if result != tc.expected && tc.isValid { + t.Errorf("failed to decode valid value %v: expected %v (%T) got %v (%T).", + tc.value, tc.expected, tc.expected, result, result) + continue + } + if result == tc.expected && !tc.isValid { + t.Errorf("decoded an unknown attribute type %v {%T) : got %v (%T)", + tc.value, tc.value, result, result) + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..296a536 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/newrelic/newrelic-istio-adapter + +go 1.13 + +require ( + github.com/gogo/googleapis v1.2.0 // indirect + github.com/gogo/protobuf v1.2.1 + google.golang.org/grpc v1.22.1 + gopkg.in/alecthomas/kingpin.v2 v2.2.6 + istio.io/api v0.0.0-20190718213450-0a0442bf8664 + istio.io/istio v0.0.0-20190726191302-76f15793c4f9 + istio.io/pkg v0.0.0-20190726080000-e5d6de6b352b +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3a59397 --- /dev/null +++ b/go.sum @@ -0,0 +1,463 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +contrib.go.opencensus.io/exporter/prometheus v0.1.0 h1:SByaIoWwNgMdPSgl5sMqM2KDE5H/ukPWBRo314xiDvg= +contrib.go.opencensus.io/exporter/prometheus v0.1.0/go.mod h1:cGFniUXGZlKRjzOyuZJ6mgB+PgBcCIa79kEKR8YCW+A= +contrib.go.opencensus.io/exporter/stackdriver v0.6.0/go.mod h1:QeFzMJDAw8TXt5+aRaSuE8l5BwaMIOIlaVkBOPRuMuw= +contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= +fortio.org/fortio v1.3.0/go.mod h1:Go0fRqoPJ1xy5JOWcS23jyF58byVZxFyEePYsGmCR0k= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-autorest v11.1.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= +github.com/Masterminds/semver v1.4.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.14.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/SAP/go-hdb v0.14.1/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo= +github.com/SermoDigital/jose v0.9.1/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis v0.0.0-20180201100744-9d52b1fc8da9/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/antlr/antlr4 v0.0.0-20190223165740-dade65a895c2 h1:Q1TGw0wvj6lqZQ4/CMfZykGQDnkslNcvuDID+AfNiQE= +github.com/antlr/antlr4 v0.0.0-20190223165740-dade65a895c2/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.13.24/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/cactus/go-statsd-client v3.1.1+incompatible/go.mod h1:cMRcwZDklk7hXp+Law83urTHUiHMzCev/r4JMYr/zU0= +github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY= +github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= +github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/siphash v1.1.0/go.mod h1:q+IRvb2gOSrUnYoPqHiyHXS0FOBBOdl6tONBlVnOnt4= +github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20170912183627-bc6354cbbc29/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dropbox/godropbox v0.0.0-20190501155911-5749d3b71cbe/go.mod h1:glr97hP/JuXb+WMYCizc4PIFuzw1lCR97mwbe1VVXhQ= +github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/emicklei/go-restful v2.6.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.8.2 h1:HsOE6hYsY5/UYEjZ/rNezsZUBiMxJdBNxgsDQZbxIaU= +github.com/envoyproxy/go-control-plane v0.8.2/go.mod h1:EWRTAFN6uuDZIa6KOuUfrOMJ7ySgXZ44rVKiTWjKe34= +github.com/envoyproxy/protoc-gen-validate v0.0.0-20190405222122-d6164de49109 h1:FNgqGzbOm637YKRbYGKb9cqGo8i50++w/LWvMau7jrw= +github.com/envoyproxy/protoc-gen-validate v0.0.0-20190405222122-d6164de49109/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v3.0.0+incompatible h1:l91aby7TzBXBdmF8heZqjskeH9f3g7ZOL8/sSe+vTlU= +github.com/evanphx/json-patch v3.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/stackerr v0.0.0-20150612192056-c2fcf88613f4/go.mod h1:SBHk9aNQtiw4R4bEuzHjVmZikkUKCnO1v3lPQ21HZGk= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fluent/fluent-logger-golang v1.3.0/go.mod h1:2/HCT/jTy78yGyeNGQLGQsjF3zzzAuy6Xlk6FCMV5eU= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-ini/ini v1.33.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-redis/redis v6.10.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gocql/gocql v0.0.0-20190423091413-b99afaf3b163/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.2.0 h1:Z0v3OJDotX9ZBpdz2V+AI7F4fITSZhVE5mg6GQppwMM= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/status v1.0.3 h1:WkVBY59mw7qUNTr/bLwO7J2vesJ0rQ2C3tMXrTd3w5M= +github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRsugc= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20180203143532-66deaeb636df h1:Sf/EWTqecLGj5mn9KFu3L4Cc4O/6kGnbtbDtXrjzv5A= +github.com/golang/groupcache v0.0.0-20180203143532-66deaeb636df/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f h1:kSqKc8ouCLIBHqdj9a9xxhtxlZhNqbePClixA4HoM44= +github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:YCHYtYb9c8Q7XgYVYjmJBPtFPKx5QvOcPxHZWjldabE= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.2.0 h1:1xQjGc4NQ0Kk0308Om1gfSt7Tkk4hwgVMGEpEEYwf9g= +github.com/google/cel-go v0.2.0/go.mod h1:fTCVOuSN/Vn6d49zvRpr3fDAKFyfpLViE0gU+9Vtm7g= +github.com/google/cel-spec v0.2.0/go.mod h1:MjQm800JAGhOZXI7vatnVpmIaFTR6L8FHcKk+piiKpI= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-github v15.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.0.0-20180327194212-2daf3049f2a9/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20180323085839-aed189ae50cf/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20160910222444-6b7015e65d36/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20171214222146-0e7658f8ee99 h1:bTRV2bQrg85E7ZeeyQfHX3GyfidLrNzVoyq7epx0bTw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20171214222146-0e7658f8ee99/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul v1.3.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.0.1/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= +github.com/hashicorp/vault v0.10.0/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/howeyc/fsnotify v0.9.0 h1:0gtV5JmOKH4A8SsFxG2BczSeXWWPvcMT0euZt5gDAxY= +github.com/howeyc/fsnotify v0.9.0/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jefferai/jsonx v1.0.0/go.mod h1:OGmqmi2tTeI/PS+qQfBDToLHHJIy/RMp24fPo8vFvoQ= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/json-iterator/go v0.0.0-20180914014843-2433035e5132 h1:iRdNmFu7aSZLOiWsUJwpdZuiti7CcGRELZbSz5r+tHI= +github.com/json-iterator/go v0.0.0-20180914014843-2433035e5132/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20190207033735-e65537c515d7/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20190212223446-d976af380377/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/keybase/go-crypto v0.0.0-20190416182011-b785b22cc757/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lukechampine/freeze v0.0.0-20160818180733-f514e08ae5a0/go.mod h1:kHf6qlhSQAjGo6pMSDgbSc78BG+K/cNjj/Pqw5bT2oQ= +github.com/lyft/protoc-gen-star v0.4.10/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/open-policy-agent/opa v0.8.2/go.mod h1:rlfeSeHuZmMEpmrcGla42AjkOUjP4rGIpS96H12un3o= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/openshift/api v0.0.0-20190322043348-8741ff068a47/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= +github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20180306154005-525d0eb5f91d/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJDADN2ufcGik7W992pyps0wZ888b/y9GXcLTU= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/prom2json v1.1.0 h1:/fEL2DK7EEyHVeGMG4TV+gSS9Sw53yYKt//QRL0IIYE= +github.com/prometheus/prom2json v1.1.0/go.mod h1:v7OY1795b9fEUZgq4UU2+15YjRv0LfpxKejIQCy3L7o= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/ryanuber/go-glob v0.0.0-20160226084822-572520ed46db/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sethgrid/pester v0.0.0-20180227223404-ed9870dad317/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= +github.com/signalfx/com_signalfx_metrics_protobuf v0.0.0-20170330202426-93e507b42f43/go.mod h1:muYA2clvwCdj7nzAJ5vJIXYpJsUumhAl4Uu1wUNpWzA= +github.com/signalfx/gohistogram v0.0.0-20160107210732-1ccfd2ff5083/go.mod h1:adPDS6s7WaajdFBV9mQ7i0dKfQ8xiDnF9ZNETVPpp7c= +github.com/signalfx/golib v1.1.6/go.mod h1:nWYefOwlUKWm/SpN/LgVSBnyH1T9NpT1ANlmgRIi1Cs= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/uber/jaeger-client-go v0.0.0-20190228190846-ecf2d03a9e80 h1:nWzkj/LnVGFeaeXAAXq7AyH4BQP3678Lqj3y2fIaieg= +github.com/uber/jaeger-client-go v0.0.0-20190228190846-ecf2d03a9e80/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.0.0+incompatible h1:iMSCV0rmXEogjNWPh2D0xk9YVKvrtGoHJNe9ebLu/pw= +github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yashtewari/glob-intersection v0.0.0-20180206001645-7af743e8ec84/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= +github.com/yl2chen/cidranger v0.0.0-20180214081945-928b519e5268/go.mod h1:mq0zhomp/G6rRTb0dvHWXRHr/2+Qgeq5hMXfJ670+i4= +github.com/yuin/gopher-lua v0.0.0-20180316054350-84ea3a3c79b3/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c h1:hDn6jm7snBX2O7+EeTk6Q4WXJfKt7MWgtiCCRi1rBoY= +golang.org/x/sys v0.0.0-20190508220229-2d0786266e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190227213309-4f5b463f9597/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107 h1:xtNn7qFlagY2mQNFHMSRPjT2RkOV4OXM7P5TVy9xATo= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.22.1 h1:/7cs52RnTJmD43s3uxzlq2U7nqVTd/37viQwMrMNlOM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0= +gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/logfmt.v0 v0.3.0/go.mod h1:mRLMcMLrml5h2Ux/H+4zccFOlVCiRvOvndsolsJoU8Q= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= +gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/stack.v1 v1.7.0/go.mod h1:QtWz4C5wbvhA63ngux3942W/ppRxtyYjHvvhz02s7+M= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +istio.io/api v0.0.0-20190515205759-982e5c3888c6/go.mod h1:hhLFQmpHia8zgaM37vb2ml9iS5NfNfqZGRt1pS9aVEo= +istio.io/api v0.0.0-20190718213450-0a0442bf8664 h1:6qd2tnoFRDkqIcuf7rBOAIoZ9F62nuT/k7xGBUMHfQ8= +istio.io/api v0.0.0-20190718213450-0a0442bf8664/go.mod h1:hhLFQmpHia8zgaM37vb2ml9iS5NfNfqZGRt1pS9aVEo= +istio.io/gogo-genproto v0.0.0-20190614210408-e88dc8b0e4db/go.mod h1:eIDJ6jNk/IeJz6ODSksHl5Aiczy5JUq6vFhJWI5OtiI= +istio.io/istio v0.0.0-20190726191302-76f15793c4f9 h1:N305u1rfea+jL9Koqig93maL6zLQgV28b3UP78y7yjM= +istio.io/istio v0.0.0-20190726191302-76f15793c4f9/go.mod h1:j9gC1CCpE36TIBG9o7l2ecRxqlyQErc0wPh9al+fkzk= +istio.io/pkg v0.0.0-20190710182420-c26792dead42 h1:2GOb5IEH8Q4tbv4sw6OabHV/xcRK4qQD5lLieiYe64s= +istio.io/pkg v0.0.0-20190710182420-c26792dead42/go.mod h1:QuX5yn89LMkQdwuz0OyikOs3DBN45poAUNBAlOmFIvQ= +istio.io/pkg v0.0.0-20190726080000-e5d6de6b352b h1:sfevP1Gj1H8bdo4Cp0kDCoOvxrKkwrFgIDWnTP6+6kQ= +istio.io/pkg v0.0.0-20190726080000-e5d6de6b352b/go.mod h1:QuX5yn89LMkQdwuz0OyikOs3DBN45poAUNBAlOmFIvQ= +k8s.io/api v0.0.0-20190222213804-5cb15d344471 h1:MzQGt8qWQCR+39kbYRd0uQqsvSidpYqJLFeWiJ9l4OE= +k8s.io/api v0.0.0-20190222213804-5cb15d344471/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apiextensions-apiserver v0.0.0-20190221221350-bfb440be4b87/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= +k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628 h1:UYfHH+KEF88OTg+GojQUwFTNxbxwmoktLwutUzR0GPg= +k8s.io/apimachinery v0.0.0-20190221213512-86fb29eff628/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/cli-runtime v0.0.0-20190221101700-11047e25a94a/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= +k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34= +k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/helm v2.9.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= +k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/kube-openapi v0.0.0-20180216212618-50ae88d24ede h1:YOWlONzJUq456SnNYPcK/org5asA+LU6AzNBm+l/04o= +k8s.io/kube-openapi v0.0.0-20180216212618-50ae88d24ede/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..365ffe1 --- /dev/null +++ b/handler.go @@ -0,0 +1,41 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package newrelic + +import ( + "context" + + nrmetric "github.com/newrelic/newrelic-istio-adapter/metric" + "github.com/newrelic/newrelic-istio-adapter/trace" + "istio.io/istio/mixer/template/metric" + "istio.io/istio/mixer/template/tracespan" +) + +// Handler represents a processor that can handle instances from Istio and transmit them to New Relic. +type Handler struct { + m *nrmetric.Handler + t *trace.Handler +} + +// HandleMetric implements the HandleMetricServiceServer interface. +func (h *Handler) HandleMetric(ctx context.Context, values []*metric.InstanceMsg) error { + return h.m.HandleMetric(ctx, values) +} + +// HandleTraceSpan implements the HandleTraceSpanServiceServer interface. +func (h *Handler) HandleTraceSpan(ctx context.Context, values []*tracespan.InstanceMsg) error { + return h.t.HandleTraceSpan(ctx, values) +} diff --git a/helm-charts/Chart.yaml b/helm-charts/Chart.yaml new file mode 100644 index 0000000..0108b8e --- /dev/null +++ b/helm-charts/Chart.yaml @@ -0,0 +1,26 @@ +# Copyright 2019 New Relic Corporation +# Copyright The Helm Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +apiVersion: v1 +name: newrelic-istio-adapter +description: A Helm chart for New Relic's Adapter for Istio +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - istio + - mixer + - adapter +sources: + - https://github.com/newrelic/newrelic-istio-adapter diff --git a/helm-charts/README.md b/helm-charts/README.md new file mode 100644 index 0000000..3f09ffa --- /dev/null +++ b/helm-charts/README.md @@ -0,0 +1,39 @@ +# New Relic Istio Adapter Helm Chart + +This [Helm](https://helm.sh/) chart enables you to deploy the `newrelic-istio-adapter` to your Kubernetes cluster. + +## Configuration + +This chart is designed to be configured to support your environment. +The following table lists the configurable parameters of the `newrelic-istio-adapter` chart and their default values. + +| Parameter | Description | Default | +|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------| +| `image.repository` | Repository for container image. | `newrelic/newrelic-istio-adapter`| +| `image.tag` | Image tag. | `latest` | +| `image.pullPolicy` | Image pull policy. | `IfNotPresent` | +| `nameOverride` | Override the Chart name. Also used as the label `app.kubernetes.io/name` value for all resources. | `""` | +| `fullnameOverride` | Override the naming of `newrelic-istio-adapter` namespace resources. Enables multiple `newrelic-istio-adapter` versions to be deployed simultaneously. | `""` | +| `istioNamespace` | Namespace of the Istio control plane resources. | `istio-system` | +| `clusterName` | Name used by `newrelic-istio-adapter` as a unique Kubernetes cluster identifier for metrics sent to New Relic. | `istio-cluster` | +| `authentication.manageSecret` | Create a Kubernetes Secret to manage `authentication.apiKey` securely. Setting this to `false` mean you will manually handle secrets. | `true` | +| `authentication.apiKey` | New Relic API Key, deployed as a Kubernetes Secret. **Required if `authentication.manageSecret` is `true`.** | `""` | +| `authentication.secretNameOverride` | Name of the Kubernetes Secret providing your API key (stored in the `NEW_RELIC_API_KEY` field). **If set, `authentication.manageSecret` must be `false`.** | `""` | +| `service.type` | The `newrelic-istio-adapter` Kubernetes Service type. | `ClusterIP` | +| `service.port` | The `newrelic-istio-adapter` Kubernetes Service port. | 80 | +| `replicaCount` | Kubernetes Deployment relica count definition. | `1` | +| `resources` | Kubernetes Pod resource requests & limits resource definition. | `{}` | +| `nodeSelector` | Kubernetes Deployment nodeSelector definition. | `{}` | +| `tolerations` | Kubernetes Deployment tolerations definition. | `[]` | +| `affinity` | Kubernetes Deployment affinity definition. | `{}` | +| `telemetry.namespace` | Prefixed name for all metrics sent from `newrelic-istio-adapter` to New Relic. | `istio` | +| `telemetry.attributes` | **Advanced** Envoy to Mixer attribute mapping. See the [Istio Attribute Vocabulary](https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/). | *See [values.yaml](values.yaml)* | +| `telemetry.traces` | **Advanced** Istio [Tracespan](https://istio.io/docs/reference/config/policy-and-telemetry/templates/tracespan/) mapping. | *See [values.yaml](values.yaml)* | +| `telemetry.metrics` | **Advanced** Istio [Metric](https://istio.io/docs/reference/config/policy-and-telemetry/templates/metric/) mapping. | *See [values.yaml](values.yaml)* | +| `telemetry.rules` | **Advanced** Istio [Policy and Telemetry Rules](https://istio.io/docs/reference/config/policy-and-telemetry/istio.policy.v1beta1/) | *See [values.yaml](values.yaml)* | + + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm template`. + +Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. +> **Tip**: You can use the default [values.yaml](values.yaml) diff --git a/helm-charts/templates/_helpers.tpl b/helm-charts/templates/_helpers.tpl new file mode 100644 index 0000000..c7a79d1 --- /dev/null +++ b/helm-charts/templates/_helpers.tpl @@ -0,0 +1,64 @@ +{{/* vim: set filetype=mustache: */}} +{{/* + + Copyright 2019 New Relic Corporation + Copyright The Helm Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/}}{{/* +Expand the name of the chart. +*/}} +{{- define "newrelic-istio-adapter.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "newrelic-istio-adapter.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "newrelic-istio-adapter.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return the name for the Kubernetes Secret resouce. + +The default value will be the same as full "newrelic-istio-adapter.fullname" +unless `.Values.authentication.secretNameOverride` is provided, in which +case that will be returned. +*/}} +{{- define "newrelic-istio-adapter.secretName" -}} +{{- if .Values.authentication.secretNameOverride -}} +{{- .Values.authentication.secretNameOverride -}} +{{- else -}} +{{ include "newrelic-istio-adapter.fullname" . }} +{{- end -}} +{{- end -}} diff --git a/helm-charts/templates/adapter.yaml b/helm-charts/templates/adapter.yaml new file mode 100644 index 0000000..03f1b9a --- /dev/null +++ b/helm-charts/templates/adapter.yaml @@ -0,0 +1,35 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +# this config is created through command +# mixgen adapter -c $GOPATH/src/github.com/newrelic/newrelic-istio-adapter/./config/config.proto_descriptor -o $GOPATH/src/github.com/newrelic/newrelic-istio-adapter/./config -s=false -n newrelic -t metric -t tracespan +apiVersion: "config.istio.io/v1alpha2" +kind: adapter +metadata: + name: newrelic + namespace: {{ .Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +spec: + description: + session_based: false + templates: + - metric + - tracespan + config: CvD6AgogZ29vZ2xlL3Byb3RvYnVmL2Rlc2NyaXB0b3IucHJvdG8SD2dvb2dsZS5wcm90b2J1ZiJNChFGaWxlRGVzY3JpcHRvclNldBI4CgRmaWxlGAEgAygLMiQuZ29vZ2xlLnByb3RvYnVmLkZpbGVEZXNjcmlwdG9yUHJvdG9SBGZpbGUi5AQKE0ZpbGVEZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRIYCgdwYWNrYWdlGAIgASgJUgdwYWNrYWdlEh4KCmRlcGVuZGVuY3kYAyADKAlSCmRlcGVuZGVuY3kSKwoRcHVibGljX2RlcGVuZGVuY3kYCiADKAVSEHB1YmxpY0RlcGVuZGVuY3kSJwoPd2Vha19kZXBlbmRlbmN5GAsgAygFUg53ZWFrRGVwZW5kZW5jeRJDCgxtZXNzYWdlX3R5cGUYBCADKAsyIC5nb29nbGUucHJvdG9idWYuRGVzY3JpcHRvclByb3RvUgttZXNzYWdlVHlwZRJBCgllbnVtX3R5cGUYBSADKAsyJC5nb29nbGUucHJvdG9idWYuRW51bURlc2NyaXB0b3JQcm90b1IIZW51bVR5cGUSQQoHc2VydmljZRgGIAMoCzInLmdvb2dsZS5wcm90b2J1Zi5TZXJ2aWNlRGVzY3JpcHRvclByb3RvUgdzZXJ2aWNlEkMKCWV4dGVuc2lvbhgHIAMoCzIlLmdvb2dsZS5wcm90b2J1Zi5GaWVsZERlc2NyaXB0b3JQcm90b1IJZXh0ZW5zaW9uEjYKB29wdGlvbnMYCCABKAsyHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnNSB29wdGlvbnMSSQoQc291cmNlX2NvZGVfaW5mbxgJIAEoCzIfLmdvb2dsZS5wcm90b2J1Zi5Tb3VyY2VDb2RlSW5mb1IOc291cmNlQ29kZUluZm8SFgoGc3ludGF4GAwgASgJUgZzeW50YXgiuQYKD0Rlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEjsKBWZpZWxkGAIgAygLMiUuZ29vZ2xlLnByb3RvYnVmLkZpZWxkRGVzY3JpcHRvclByb3RvUgVmaWVsZBJDCglleHRlbnNpb24YBiADKAsyJS5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG9SCWV4dGVuc2lvbhJBCgtuZXN0ZWRfdHlwZRgDIAMoCzIgLmdvb2dsZS5wcm90b2J1Zi5EZXNjcmlwdG9yUHJvdG9SCm5lc3RlZFR5cGUSQQoJZW51bV90eXBlGAQgAygLMiQuZ29vZ2xlLnByb3RvYnVmLkVudW1EZXNjcmlwdG9yUHJvdG9SCGVudW1UeXBlElgKD2V4dGVuc2lvbl9yYW5nZRgFIAMoCzIvLmdvb2dsZS5wcm90b2J1Zi5EZXNjcmlwdG9yUHJvdG8uRXh0ZW5zaW9uUmFuZ2VSDmV4dGVuc2lvblJhbmdlEkQKCm9uZW9mX2RlY2wYCCADKAsyJS5nb29nbGUucHJvdG9idWYuT25lb2ZEZXNjcmlwdG9yUHJvdG9SCW9uZW9mRGVjbBI5CgdvcHRpb25zGAcgASgLMh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zUgdvcHRpb25zElUKDnJlc2VydmVkX3JhbmdlGAkgAygLMi4uZ29vZ2xlLnByb3RvYnVmLkRlc2NyaXB0b3JQcm90by5SZXNlcnZlZFJhbmdlUg1yZXNlcnZlZFJhbmdlEiMKDXJlc2VydmVkX25hbWUYCiADKAlSDHJlc2VydmVkTmFtZRp6Cg5FeHRlbnNpb25SYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQSQAoHb3B0aW9ucxgDIAEoCzImLmdvb2dsZS5wcm90b2J1Zi5FeHRlbnNpb25SYW5nZU9wdGlvbnNSB29wdGlvbnMaNwoNUmVzZXJ2ZWRSYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQifAoVRXh0ZW5zaW9uUmFuZ2VPcHRpb25zElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAIimAYKFEZpZWxkRGVzY3JpcHRvclByb3RvEhIKBG5hbWUYASABKAlSBG5hbWUSFgoGbnVtYmVyGAMgASgFUgZudW1iZXISQQoFbGFiZWwYBCABKA4yKy5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG8uTGFiZWxSBWxhYmVsEj4KBHR5cGUYBSABKA4yKi5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG8uVHlwZVIEdHlwZRIbCgl0eXBlX25hbWUYBiABKAlSCHR5cGVOYW1lEhoKCGV4dGVuZGVlGAIgASgJUghleHRlbmRlZRIjCg1kZWZhdWx0X3ZhbHVlGAcgASgJUgxkZWZhdWx0VmFsdWUSHwoLb25lb2ZfaW5kZXgYCSABKAVSCm9uZW9mSW5kZXgSGwoJanNvbl9uYW1lGAogASgJUghqc29uTmFtZRI3CgdvcHRpb25zGAggASgLMh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9uc1IHb3B0aW9ucyK2AgoEVHlwZRIPCgtUWVBFX0RPVUJMRRABEg4KClRZUEVfRkxPQVQQAhIOCgpUWVBFX0lOVDY0EAMSDwoLVFlQRV9VSU5UNjQQBBIOCgpUWVBFX0lOVDMyEAUSEAoMVFlQRV9GSVhFRDY0EAYSEAoMVFlQRV9GSVhFRDMyEAcSDQoJVFlQRV9CT09MEAgSDwoLVFlQRV9TVFJJTkcQCRIOCgpUWVBFX0dST1VQEAoSEAoMVFlQRV9NRVNTQUdFEAsSDgoKVFlQRV9CWVRFUxAMEg8KC1RZUEVfVUlOVDMyEA0SDQoJVFlQRV9FTlVNEA4SEQoNVFlQRV9TRklYRUQzMhAPEhEKDVRZUEVfU0ZJWEVENjQQEBIPCgtUWVBFX1NJTlQzMhAREg8KC1RZUEVfU0lOVDY0EBIiQwoFTGFiZWwSEgoOTEFCRUxfT1BUSU9OQUwQARISCg5MQUJFTF9SRVFVSVJFRBACEhIKDkxBQkVMX1JFUEVBVEVEEAMiYwoUT25lb2ZEZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRI3CgdvcHRpb25zGAIgASgLMh0uZ29vZ2xlLnByb3RvYnVmLk9uZW9mT3B0aW9uc1IHb3B0aW9ucyLjAgoTRW51bURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEj8KBXZhbHVlGAIgAygLMikuZ29vZ2xlLnByb3RvYnVmLkVudW1WYWx1ZURlc2NyaXB0b3JQcm90b1IFdmFsdWUSNgoHb3B0aW9ucxgDIAEoCzIcLmdvb2dsZS5wcm90b2J1Zi5FbnVtT3B0aW9uc1IHb3B0aW9ucxJdCg5yZXNlcnZlZF9yYW5nZRgEIAMoCzI2Lmdvb2dsZS5wcm90b2J1Zi5FbnVtRGVzY3JpcHRvclByb3RvLkVudW1SZXNlcnZlZFJhbmdlUg1yZXNlcnZlZFJhbmdlEiMKDXJlc2VydmVkX25hbWUYBSADKAlSDHJlc2VydmVkTmFtZRo7ChFFbnVtUmVzZXJ2ZWRSYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQigwEKGEVudW1WYWx1ZURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEhYKBm51bWJlchgCIAEoBVIGbnVtYmVyEjsKB29wdGlvbnMYAyABKAsyIS5nb29nbGUucHJvdG9idWYuRW51bVZhbHVlT3B0aW9uc1IHb3B0aW9ucyKnAQoWU2VydmljZURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEj4KBm1ldGhvZBgCIAMoCzImLmdvb2dsZS5wcm90b2J1Zi5NZXRob2REZXNjcmlwdG9yUHJvdG9SBm1ldGhvZBI5CgdvcHRpb25zGAMgASgLMh8uZ29vZ2xlLnByb3RvYnVmLlNlcnZpY2VPcHRpb25zUgdvcHRpb25zIokCChVNZXRob2REZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRIdCgppbnB1dF90eXBlGAIgASgJUglpbnB1dFR5cGUSHwoLb3V0cHV0X3R5cGUYAyABKAlSCm91dHB1dFR5cGUSOAoHb3B0aW9ucxgEIAEoCzIeLmdvb2dsZS5wcm90b2J1Zi5NZXRob2RPcHRpb25zUgdvcHRpb25zEjAKEGNsaWVudF9zdHJlYW1pbmcYBSABKAg6BWZhbHNlUg9jbGllbnRTdHJlYW1pbmcSMAoQc2VydmVyX3N0cmVhbWluZxgGIAEoCDoFZmFsc2VSD3NlcnZlclN0cmVhbWluZyK5CAoLRmlsZU9wdGlvbnMSIQoMamF2YV9wYWNrYWdlGAEgASgJUgtqYXZhUGFja2FnZRIwChRqYXZhX291dGVyX2NsYXNzbmFtZRgIIAEoCVISamF2YU91dGVyQ2xhc3NuYW1lEjUKE2phdmFfbXVsdGlwbGVfZmlsZXMYCiABKAg6BWZhbHNlUhFqYXZhTXVsdGlwbGVGaWxlcxJECh1qYXZhX2dlbmVyYXRlX2VxdWFsc19hbmRfaGFzaBgUIAEoCEICGAFSGWphdmFHZW5lcmF0ZUVxdWFsc0FuZEhhc2gSOgoWamF2YV9zdHJpbmdfY2hlY2tfdXRmOBgbIAEoCDoFZmFsc2VSE2phdmFTdHJpbmdDaGVja1V0ZjgSUwoMb3B0aW1pemVfZm9yGAkgASgOMikuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zLk9wdGltaXplTW9kZToFU1BFRURSC29wdGltaXplRm9yEh0KCmdvX3BhY2thZ2UYCyABKAlSCWdvUGFja2FnZRI1ChNjY19nZW5lcmljX3NlcnZpY2VzGBAgASgIOgVmYWxzZVIRY2NHZW5lcmljU2VydmljZXMSOQoVamF2YV9nZW5lcmljX3NlcnZpY2VzGBEgASgIOgVmYWxzZVITamF2YUdlbmVyaWNTZXJ2aWNlcxI1ChNweV9nZW5lcmljX3NlcnZpY2VzGBIgASgIOgVmYWxzZVIRcHlHZW5lcmljU2VydmljZXMSNwoUcGhwX2dlbmVyaWNfc2VydmljZXMYKiABKAg6BWZhbHNlUhJwaHBHZW5lcmljU2VydmljZXMSJQoKZGVwcmVjYXRlZBgXIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSLwoQY2NfZW5hYmxlX2FyZW5hcxgfIAEoCDoFZmFsc2VSDmNjRW5hYmxlQXJlbmFzEioKEW9iamNfY2xhc3NfcHJlZml4GCQgASgJUg9vYmpjQ2xhc3NQcmVmaXgSKQoQY3NoYXJwX25hbWVzcGFjZRglIAEoCVIPY3NoYXJwTmFtZXNwYWNlEiEKDHN3aWZ0X3ByZWZpeBgnIAEoCVILc3dpZnRQcmVmaXgSKAoQcGhwX2NsYXNzX3ByZWZpeBgoIAEoCVIOcGhwQ2xhc3NQcmVmaXgSIwoNcGhwX25hbWVzcGFjZRgpIAEoCVIMcGhwTmFtZXNwYWNlElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uIjoKDE9wdGltaXplTW9kZRIJCgVTUEVFRBABEg0KCUNPREVfU0laRRACEhAKDExJVEVfUlVOVElNRRADKgkI6AcQgICAgAJKBAgmECci0QIKDk1lc3NhZ2VPcHRpb25zEjwKF21lc3NhZ2Vfc2V0X3dpcmVfZm9ybWF0GAEgASgIOgVmYWxzZVIUbWVzc2FnZVNldFdpcmVGb3JtYXQSTAofbm9fc3RhbmRhcmRfZGVzY3JpcHRvcl9hY2Nlc3NvchgCIAEoCDoFZmFsc2VSHG5vU3RhbmRhcmREZXNjcmlwdG9yQWNjZXNzb3ISJQoKZGVwcmVjYXRlZBgDIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSGwoJbWFwX2VudHJ5GAcgASgIUghtYXBFbnRyeRJYChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvblITdW5pbnRlcnByZXRlZE9wdGlvbioJCOgHEICAgIACSgQICBAJSgQICRAKIuIDCgxGaWVsZE9wdGlvbnMSQQoFY3R5cGUYASABKA4yIy5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zLkNUeXBlOgZTVFJJTkdSBWN0eXBlEhYKBnBhY2tlZBgCIAEoCFIGcGFja2VkEkcKBmpzdHlwZRgGIAEoDjIkLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMuSlNUeXBlOglKU19OT1JNQUxSBmpzdHlwZRIZCgRsYXp5GAUgASgIOgVmYWxzZVIEbGF6eRIlCgpkZXByZWNhdGVkGAMgASgIOgVmYWxzZVIKZGVwcmVjYXRlZBIZCgR3ZWFrGAogASgIOgVmYWxzZVIEd2VhaxJYChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvblITdW5pbnRlcnByZXRlZE9wdGlvbiIvCgVDVHlwZRIKCgZTVFJJTkcQABIICgRDT1JEEAESEAoMU1RSSU5HX1BJRUNFEAIiNQoGSlNUeXBlEg0KCUpTX05PUk1BTBAAEg0KCUpTX1NUUklORxABEg0KCUpTX05VTUJFUhACKgkI6AcQgICAgAJKBAgEEAUicwoMT25lb2ZPcHRpb25zElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAIiwAEKC0VudW1PcHRpb25zEh8KC2FsbG93X2FsaWFzGAIgASgIUgphbGxvd0FsaWFzEiUKCmRlcHJlY2F0ZWQYAyABKAg6BWZhbHNlUgpkZXByZWNhdGVkElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAJKBAgFEAYingEKEEVudW1WYWx1ZU9wdGlvbnMSJQoKZGVwcmVjYXRlZBgBIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24qCQjoBxCAgICAAiKcAQoOU2VydmljZU9wdGlvbnMSJQoKZGVwcmVjYXRlZBghIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24qCQjoBxCAgICAAiLgAgoNTWV0aG9kT3B0aW9ucxIlCgpkZXByZWNhdGVkGCEgASgIOgVmYWxzZVIKZGVwcmVjYXRlZBJxChFpZGVtcG90ZW5jeV9sZXZlbBgiIAEoDjIvLmdvb2dsZS5wcm90b2J1Zi5NZXRob2RPcHRpb25zLklkZW1wb3RlbmN5TGV2ZWw6E0lERU1QT1RFTkNZX1VOS05PV05SEGlkZW1wb3RlbmN5TGV2ZWwSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24iUAoQSWRlbXBvdGVuY3lMZXZlbBIXChNJREVNUE9URU5DWV9VTktOT1dOEAASEwoPTk9fU0lERV9FRkZFQ1RTEAESDgoKSURFTVBPVEVOVBACKgkI6AcQgICAgAIimgMKE1VuaW50ZXJwcmV0ZWRPcHRpb24SQQoEbmFtZRgCIAMoCzItLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uLk5hbWVQYXJ0UgRuYW1lEikKEGlkZW50aWZpZXJfdmFsdWUYAyABKAlSD2lkZW50aWZpZXJWYWx1ZRIsChJwb3NpdGl2ZV9pbnRfdmFsdWUYBCABKARSEHBvc2l0aXZlSW50VmFsdWUSLAoSbmVnYXRpdmVfaW50X3ZhbHVlGAUgASgDUhBuZWdhdGl2ZUludFZhbHVlEiEKDGRvdWJsZV92YWx1ZRgGIAEoAVILZG91YmxlVmFsdWUSIQoMc3RyaW5nX3ZhbHVlGAcgASgMUgtzdHJpbmdWYWx1ZRInCg9hZ2dyZWdhdGVfdmFsdWUYCCABKAlSDmFnZ3JlZ2F0ZVZhbHVlGkoKCE5hbWVQYXJ0EhsKCW5hbWVfcGFydBgBIAIoCVIIbmFtZVBhcnQSIQoMaXNfZXh0ZW5zaW9uGAIgAigIUgtpc0V4dGVuc2lvbiKnAgoOU291cmNlQ29kZUluZm8SRAoIbG9jYXRpb24YASADKAsyKC5nb29nbGUucHJvdG9idWYuU291cmNlQ29kZUluZm8uTG9jYXRpb25SCGxvY2F0aW9uGs4BCghMb2NhdGlvbhIWCgRwYXRoGAEgAygFQgIQAVIEcGF0aBIWCgRzcGFuGAIgAygFQgIQAVIEc3BhbhIpChBsZWFkaW5nX2NvbW1lbnRzGAMgASgJUg9sZWFkaW5nQ29tbWVudHMSKwoRdHJhaWxpbmdfY29tbWVudHMYBCABKAlSEHRyYWlsaW5nQ29tbWVudHMSOgoZbGVhZGluZ19kZXRhY2hlZF9jb21tZW50cxgGIAMoCVIXbGVhZGluZ0RldGFjaGVkQ29tbWVudHMi0QEKEUdlbmVyYXRlZENvZGVJbmZvEk0KCmFubm90YXRpb24YASADKAsyLS5nb29nbGUucHJvdG9idWYuR2VuZXJhdGVkQ29kZUluZm8uQW5ub3RhdGlvblIKYW5ub3RhdGlvbhptCgpBbm5vdGF0aW9uEhYKBHBhdGgYASADKAVCAhABUgRwYXRoEh8KC3NvdXJjZV9maWxlGAIgASgJUgpzb3VyY2VGaWxlEhQKBWJlZ2luGAMgASgFUgViZWdpbhIQCgNlbmQYBCABKAVSA2VuZEKPAQoTY29tLmdvb2dsZS5wcm90b2J1ZkIQRGVzY3JpcHRvclByb3Rvc0gBWj5naXRodWIuY29tL2dvbGFuZy9wcm90b2J1Zi9wcm90b2MtZ2VuLWdvL2Rlc2NyaXB0b3I7ZGVzY3JpcHRvcvgBAaICA0dQQqoCGkdvb2dsZS5Qcm90b2J1Zi5SZWZsZWN0aW9uSqrAAgoHEgUnAOcGAQqqDwoBDBIDJwASMsEMIFByb3RvY29sIEJ1ZmZlcnMgLSBHb29nbGUncyBkYXRhIGludGVyY2hhbmdlIGZvcm1hdAogQ29weXJpZ2h0IDIwMDggR29vZ2xlIEluYy4gIEFsbCByaWdodHMgcmVzZXJ2ZWQuCiBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wcm90b2NvbC1idWZmZXJzLwoKIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAogbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZQogbWV0OgoKICAgICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0CiBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlCiBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyCiBpbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlCiBkaXN0cmlidXRpb24uCiAgICAgKiBOZWl0aGVyIHRoZSBuYW1lIG9mIEdvb2dsZSBJbmMuIG5vciB0aGUgbmFtZXMgb2YgaXRzCiBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbQogdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi4KCiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTCiAiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVAogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SCiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVAogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsCiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwKIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWQogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAogKElOQ0xVRElORyBORUdMSUdF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bm5vdAogc3RvcmUgdGhlIHZhbHVlcyBpbiB0aGVtLiAgSW5zdGVhZCBpdCBzdG9yZXMgdGhlbSBpbiBhIGZpZWxkIGluIHRoZSAqT3B0aW9ucwogbWVzc2FnZSBjYWxsZWQgdW5pbnRlcnByZXRlZF9vcHRpb24uIFRoaXMgZmllbGQgbXVzdCBoYXZlIHRoZSBzYW1lIG5hbWUKIGFjcm9zcyBhbGwgKk9wdGlvbnMgbWVzc2FnZXMuIFdlIHRoZW4gdXNlIHRoaXMgZmllbGQgdG8gcG9wdWxhdGUgdGhlCiBleHRlbnNpb25zIHdoZW4gd2UgYnVpbGQgYSBkZXNjcmlwdG9yLCBhdCB3aGljaCBwb2ludCBhbGwgcHJvdG9zIGhhdmUgYmVlbgogcGFyc2VkIGFuZCBzbyBhbGwgZXh0ZW5zaW9ucyBhcmUga25vd24uCgogRXh0ZW5zaW9uIG51bWJlcnMgZm9yIGN1c3RvbSBvcHRpb25zIG1heSBiZSBjaG9zZW4gYXMgZm9sbG93czoKICogRm9yIG9wdGlvbnMgd2hpY2ggd2lsbCBvbmx5IGJlIHVzZWQgd2l0aGluIGEgc2luZ2xlIGFwcGxpY2F0aW9uIG9yCiAgIG9yZ2FuaXphdGlvbiwgb3IgZm9yIGV4cGVyaW1lbnRhbCBvcHRpb25zLCB1c2UgZmllbGQgbnVtYmVycyA1MDAwMAogICB0aHJvdWdoIDk5OTk5LiAgSXQgaXMgdXAgdG8geW91IHRvIGVuc3VyZSB0aGF0IHlvdSBkbyBub3QgdXNlIHRoZQogICBzYW1lIG51bWJlciBmb3IgbXVsdGlwbGUgb3B0aW9ucy4KICogRm9yIG9wdGlvbnMgd2hpY2ggd2lsbCBiZSBwdWJsaXNoZWQgYW5kIHVzZWQgcHVibGljbHkgYnkgbXVsdGlwbGUKICAgaW5kZXBlbmRlbnQgZW50aXRpZXMsIGUtbWFpbCBwcm90b2J1Zi1nbG9iYWwtZXh0ZW5zaW9uLXJlZ2lzdHJ5QGdvb2dsZS5jb20KICAgdG8gcmVzZXJ2ZSBleHRlbnNpb24gbnVtYmVycy4gU2ltcGx5IHByb3ZpZGUgeW91ciBwcm9qZWN0IG5hbWUgKGUuZy4KICAgT2JqZWN0aXZlLUMgcGx1Z2luKSBhbmQgeW91ciBwcm9qZWN0IHdlYnNpdGUgKGlmIGF2YWlsYWJsZSkgLS0gdGhlcmUncyBubwogICBuZWVkIHRvIGV4cGxhaW4gaG93IHlvdSBpbnRlbmQgdG8gdXNlIHRoZW0uIFVzdWFsbHkgeW91IG9ubHkgbmVlZCBvbmUKICAgZXh0ZW5zaW9uIG51bWJlci4gWW91IGNhbiBkZWNsYXJlIG11bHRpcGxlIG9wdGlvbnMgd2l0aCBvbmx5IG9uZSBleHRlbnNpb24KICAgbnVtYmVyIGJ5IHB1dHRpbmcgdGhlbSBpbiBhIHN1Yi1tZXNzYWdlLiBTZWUgdGhlIEN1c3RvbSBPcHRpb25zIHNlY3Rpb24gb2YKICAgdGhlIGRvY3MgZm9yIGV4YW1wbGVzOgogICBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wcm90b2NvbC1idWZmZXJzL2RvY3MvcHJvdG8jb3B0aW9ucwogICBJZiB0aGlzIHR1cm5zIG91dCB0byBiZSBwb3B1bGFyLCBhIHdlYiBzZXJ2aWNlIHdpbGwgYmUgc2V0IHVwCiAgIHRvIGF1dG9tYXRpY2FsbHkgYXNzaWduIG9wdGlvbiBudW1iZXJzLgoKCwoDBAoBEgS9AggTCvQBCgQECgIAEgTDAgIjGuUBIFNldHMgdGhlIEphdmEgcGFja2FnZSB3aGVyZSBjbGFzc2VzIGdlbmVyYXRlZCBmcm9tIHRoaXMgLnByb3RvIHdpbGwgYmUKIHBsYWNlZC4gIEJ5IGRlZmF1bHQsIHRoZSBwcm90byBwYWNrYWdlIGlzIHVzZWQsIGJ1dCB0aGlzIGlzIG9mdGVuCiBpbmFwcHJvcHJpYXRlIGJlY2F1c2UgcHJvdG8gcGFja2FnZXMgZG8gbm90IG5vcm1hbGx5IHN0YXJ0IHdpdGggYmFja3dhcmRzCiBkb21haW4gbmFtZXMuCgoNCgUECgIABBIEwwICCgoNCgUECgIABRIEwwILEQoNCgUECgIAARIEwwISHgoNCgUECgIAAxIEwwIhIgq/AgoEBAoCARIEywICKxqwAiBJZiBzZXQsIGFsbCB0aGUgY2xhc3NlcyBmcm9tIHRoZSAucHJvdG8gZmlsZSBhcmUgd3JhcHBlZCBpbiBhIHNpbmdsZQogb3V0ZXIgY2xh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CgUAEgSpAw0YCg0KBQQKBQABEgSpAw0RCg0KBQQKBQACEgSpAxUYCgsKAwQKCRIEqwMLDgoMCgQECgkAEgSrAwsNCg0KBQQKCQABEgSrAwsNCg0KBQQKCQACEgSrAwsNCgwKAgQLEgauAwDtAwEKCwoDBAsBEgSuAwgWCtgFCgQECwIAEgTBAwI8GskFIFNldCB0cnVlIHRvIHVzZSB0aGUgb2xkIHByb3RvMSBNZXNzYWdlU2V0IHdpcmUgZm9ybWF0IGZvciBleHRlbnNpb25zLgogVGhpcyBpcyBwcm92aWRlZCBmb3IgYmFja3dhcmRzLWNvbXBhdGliaWxpdHkgd2l0aCB0aGUgTWVzc2FnZVNldCB3aXJlCiBmb3JtYXQuICBZb3Ugc2hvdWxkIG5vdCB1c2UgdGhpcyBmb3IgYW55IG90aGVyIHJlYXNvbjogIEl0J3MgbGVzcwogZWZmaWNpZW50LCBoYXMgZmV3ZXIgZmVhdHVyZXMsIGFuZCBpcyBtb3JlIGNvbXBsaWNhdGVkLgoKIFRoZSBtZXNzYWdlIG11c3QgYmUgZGVmaW5lZCBleGFjdGx5IGFzIGZvbGxvd3M6CiAgIG1lc3NhZ2UgRm9vIHsKICAgICBvcHRpb24gbWVzc2FnZV9zZXRfd2lyZV9mb3JtYXQgPSB0cnVlOwogICAgIGV4dGVuc2lvbnMgNCB0byBtYXg7CiAgIH0KIE5vdGUgdGhhdCB0aGUgbWVzc2FnZSBjYW5ub3QgaGF2ZSBhbnkgZGVmaW5lZCBmaWVsZHM7IE1lc3NhZ2VTZXRzIG9ubHkKIGhhdmUgZXh0ZW5zaW9ucy4KCiBBbGwgZXh0ZW5zaW9ucyBvZiB5b3VyIHR5cGUgbXVzdCBiZSBzaW5ndWxhciBtZXNzYWdlczsgZS5nLiB0aGV5IGNhbm5vdAogYmUgaW50MzJzLCBlbnVtcywgb3IgcmVwZWF0ZWQgbWVzc2FnZXMuCgogQmVjYXVzZSB0aGlzIGlzIGFuIG9wdGlvbiwgdGhlIGFib3ZlIHR3byByZXN0cmljdGlvbnMgYXJlIG5vdCBlbmZvcmNlZCBieQogdGhlIHByb3RvY29sIGNvbXBpbGVyLgoKDQoFBAsCAAQSBMEDAgoKDQoFBAsCAAUSBMEDCw8KDQoFBAsCAAESBMEDECcK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CiBhIG1vcmUgZWZmaWNpZW50IHJlcHJlc2VudGF0aW9uIG9uIHRoZSB3aXJlLiBSYXRoZXIgdGhhbiByZXBlYXRlZGx5CiB3cml0aW5nIHRoZSB0YWcgYW5kIHR5cGUgZm9yIGVhY2ggZWxlbWVudCwgdGhlIGVudGlyZSBhcnJheSBpcyBlbmNvZGVkIGFzCiBhIHNpbmdsZSBsZW5ndGgtZGVsaW1pdGVkIGJsb2IuIEluIHByb3RvMywgb25seSBleHBsaWNpdCBzZXR0aW5nIGl0IHRvCiBmYWxzZSB3aWxsIGF2b2lkIHVzaW5nIHBhY2tlZCBlbmNvZGluZy4KCg0KBQQMAgEEEgSCBAIKCg0KBQQMAgEFEgSCBAsPCg0KBQQMAgEBEgSCBBAWCg0KBQQMAgEDEgSCBBkaCpoFCgQEDAICEgSPBAIzGosFIFRoZSBqc3R5cGUgb3B0aW9uIGRldGVybWluZXMgdGhlIEphdmFTY3JpcHQgdHlwZSB1c2VkIGZvciB2YWx1ZXMgb2YgdGhlCiBmaWVsZC4gIFRoZSBvcHRpb24gaXMgcGVybWl0dGVkIG9ubHkgZm9yIDY0IGJpdCBpbnRlZ3JhbCBhbmQgZml4ZWQgdHlwZXMKIChpbnQ2NCwgdWludDY0LCBzaW50NjQsIGZpeGVkNjQsIHNmaXhlZDY0KS4gIEEgZmllbGQgd2l0aCBqc3R5cGUgSlNfU1RSSU5HCiBpcyByZXByZXNlbnRlZCBhcyBKYXZhU2NyaXB0IHN0cmluZywgd2hpY2ggYXZvaWRzIGxvc3Mgb2YgcHJlY2lzaW9uIHRoYXQKIGNhbiBoYXBwZW4gd2hlbiBhIGxhcmdlIHZhbHVlIGlzIGNvbnZlcnRlZCB0byBhIGZsb2F0aW5nIHBvaW50IEphdmFTY3JpcHQuCiBTcGVjaWZ5aW5nIEpTX05VTUJFUiBmb3IgdGhlIGpzdHlwZSBjYXVzZXMgdGhlIGdlbmVyYXRlZCBKYXZhU2NyaXB0IGNvZGUgdG8KIHVzZSB0aGUgSmF2YVNjcmlwdCAibnVtYmVyIiB0eXBlLiAgVGhlIGJlaGF2aW9yIG9mIHRoZSBkZWZhdWx0IG9wdGlvbgogSlNfTk9STUFMIGlzIGltcGxlbWVudGF0aW9uIGRlcGVuZGVudC4KCiBUaGlzIG9wdGlvbiBpcyBhbiBlbnVtIHRvIHBlcm1pdCBhZGRpdGlvbmFsIHR5cGVzIHRvIGJlIGFkZGVkLCBlLmcuCiBnb29nLm1hdGguSW50ZWdlci4KCg0KBQQMAgIEEgSPBAIKCg0KBQQMAgIGEgSPBAsRCg0KBQQMAgIBEgSPBBIYCg0KBQQMAgIDEgSPBBscCg0KBQQMAgIIEgSPBB0yCg0KBQQMAgIHEgSPBCgxCg4KBAQMBAESBpAEApkEAwoNCgUEDAQBARIEkAQHDQonCgYEDAQBAgASBJIEBBIaFyBVc2UgdGhlIGRlZmF1bHQgdHlwZS4KCg8KBwQMBAECAAESBJIEBA0KDwoHBAwEAQIAAhIEkgQQEQopCgYEDAQBAgESBJUEBBIaGSBVc2UgSmF2YVNjcmlwdCBzdHJpbmdzLgoKDwoHBAwEAQIBARIElQQEDQoPCgcEDAQBAgECEgSVBBARCikKBgQMBAECAhIEmAQEEhoZIFVzZSBKYXZhU2NyaXB0IG51bWJlcnMuCgoPCgcEDAQBAgIBEgSYBAQNCg8KBwQMBAECAgISBJgEEBEK7wwKBAQMAgMSBLcEAika4AwgU2hvdWxkIHRoaXMgZmllbGQgYmUgcGFyc2VkIGxhemlseT8gIExhenkgYXBwbGllcyBvbmx5IHRvIG1lc3NhZ2UtdHlwZQogZmllbGRzLiAgSXQgbWVhbnMgdGhhdCB3aGVuIHRoZSBvdXRlciBtZXNzYWdlIGlzIGluaXRpYWxseSBwYXJzZWQsIHRoZQogaW5uZXIgbWVzc2FnZSdzIGNvbnRlbnRzIHdpbGwgbm90IGJlIHBhcnNlZCBidXQgaW5zdGVhZCBzdG9yZWQgaW4gZW5jb2RlZAogZm9ybS4gIFRoZSBpbm5lciBtZXNzYWdlIHdpbGwgYWN0dWFsbHkgYmUgcGFyc2VkIHdoZW4gaXQgaXMgZmlyc3QgYWNjZXNzZWQuCgogVGhpcyBpcyBvbmx5IGEgaGludC4gIEltcGxlbWVudGF0aW9ucyBhcmUgZnJlZSB0byBjaG9vc2Ugd2hldGhlciB0byB1c2UKIGVhZ2VyIG9yIGxhenkgcGFyc2luZyByZWdhcmRsZXNzIG9mIHRoZSB2YWx1ZSBvZiB0aGlzIG9wdGlvbi4gIEhvd2V2ZXIsCiBzZXR0aW5nIHRoaXMgb3B0aW9uIHRydWUgc3VnZ2VzdHMgdGhhdCB0aGUgcHJvdG9jb2wgYXV0aG9yIGJlbGlldmVzIHRoYXQKIHVzaW5nIGxhenkgcGFyc2luZyBvbiB0aGlzIGZpZWxkIGlzIHdvcnRoIHRoZSBhZGRpdGlvbmFsIGJvb2trZWVwaW5nCiBvdmVyaGVhZCB0eXBpY2FsbHkgbmVlZGVkIHRvIGltcGxlbWVudCBpdC4KCiBUaGlzIG9wdGlvbiBkb2VzIG5vdCBhZmZlY3QgdGhlIHB1YmxpYyBpbnRlcmZhY2Ugb2YgYW55IGdlbmVyYXRlZCBjb2RlOwogYWxsIG1ldGhvZCBzaWduYXR1cmVzIHJlbWFpbiB0aGUgc2FtZS4gIEZ1cnRoZXJtb3JlLCB0aHJlYWQtc2FmZXR5IG9mIHRoZQogaW50ZXJmYWNlIGlzIG5vdCBhZmZlY3RlZCBieSB0aGlzIG9wdGlvbjsgY29uc3QgbWV0aG9kcyByZW1haW4gc2FmZSB0bwogY2FsbCBmcm9tIG11bHRpcGxlIHRocmVhZHMgY29uY3VycmVudGx5LCB3aGlsZSBub24tY29uc3QgbWV0aG9kcyBjb250aW51ZQogdG8gcmVxdWlyZSBleGNsdXNpdmUgYWNjZXNzLgoKCiBOb3RlIHRoYXQgaW1wbGVtZW50YXRpb25zIG1heSBjaG9vc2Ugbm90IHRvIGNoZWNrIHJlcXVpcmVkIGZpZWxkcyB3aXRoaW4KIGEgbGF6eSBzdWItbWVzc2FnZS4gIFRoYXQgaXMsIGNhbGxpbmcgSXNJbml0aWFsaXplZCgpIG9uIHRoZSBvdXRlciBtZXNzYWdlCiBtYXkgcmV0dXJuIHRydWUgZXZlbiBpZiB0aGUgaW5uZXIgbWVzc2FnZSBoYXMgbWlzc2luZyByZXF1aXJlZCBmaWVsZHMuCiBUaGlzIGlzIG5lY2Vzc2FyeSBiZWNhdXNlIG90aGVyd2lzZSB0aGUgaW5uZXIgbWVzc2FnZSB3b3VsZCBoYXZlIHRvIGJlCiBwYXJzZWQgaW4gb3JkZXIgdG8gcGVyZm9ybSB0aGUgY2hlY2ssIGRlZmVhdGluZyB0aGUgcHVycG9zZSBvZiBsYXp5CiBwYXJzaW5nLiAgQW4gaW1wbGVtZW50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dCByZWNvZ25pemUuIFRoaXMgb25seQogYXBwZWFycyBpbiBvcHRpb25zIHByb3RvcyBjcmVhdGVkIGJ5IHRoZSBjb21waWxlcjo6UGFyc2VyIGNsYXNzLgogRGVzY3JpcHRvclBvb2wgcmVzb2x2ZXMgdGhlc2Ugd2hlbiBidWlsZGluZyBEZXNjcmlwdG9yIG9iamVjdHMuIFRoZXJlZm9yZSwKIG9wdGlvbnMgcHJvdG9zIGluIGRlc2NyaXB0b3Igb2JqZWN0cyAoZS5nLiByZXR1cm5lZCBieSBEZXNjcmlwdG9yOjpvcHRpb25zKCksCiBvciBwcm9kdWNlZCBieSBEZXNjcmlwdG9yOjpDb3B5VG8oKSkgd2lsbCBuZXZlciBoYXZlIFVuaW50ZXJwcmV0ZWRPcHRpb25zCiBpbiB0aGVtLgoKCwoDBBIBEgSxBQgbCssCCgQEEgMAEga3BQK6BQMaugIgVGhlIG5hbWUgb2YgdGhlIHVuaW50ZXJwcmV0ZWQgb3B0aW9uLiAgRWFjaCBzdHJpbmcgcmVwcmVzZW50cyBhIHNlZ21lbnQgaW4KIGEgZG90LXNlcGFyYXRlZCBuYW1lLiAgaXNfZXh0ZW5zaW9uIGlzIHRydWUgaWZmIGEgc2VnbWVudCByZXByZXNlbnRzIGFuCiBleHRlbnNpb24gKGRlbm90ZWQgd2l0aCBwYXJlbnRoZXNlcyBpbiBvcHRpb25zIHNwZWNzIGluIC5wcm90byBmaWxlcykuCiBFLmcuLHsgWyJmb28iLCBmYWxzZV0sIFsiYmFyLmJheiIsIHRydWVdLCBbInF1eCIsIGZhbHNlXSB9IHJlcHJlc2VudHMKICJmb28uKGJhci5iYXopLnF1eCIuCgoNCgUEEgMAARIEtwUKEgoOCgYEEgMAAgASBLgFBCIKDwoHBBIDAAIABBIEuAUEDAoPCgcEEgMAAgAFEgS4BQ0TCg8KBwQSAwACAAESBLgFFB0KDwoHBBIDAAIAAxIEuAUgIQoOCgYEEgMAAgESBLkFBCMKDwoHBBIDAAIBBBIEuQUEDAoPCgcEEgMAAgEFEgS5BQ0RCg8KBwQSAwACAQESBLkFEh4KDwoHBBIDAAIBAxIEuQUhIgoMCgQEEgIAEgS7BQIdCg0KBQQSAgAEEgS7BQIKCg0KBQQSAgAGEgS7BQsTCg0KBQQSAgABEgS7BRQYCg0KBQQSAgADEgS7BRscCpwBCgQEEgIBEgS/BQInGo0BIFRoZSB2YWx1ZSBvZiB0aGUgdW5pbnRlcnByZXRlZCBvcHRpb24sIGluIHdoYXRldmVyIHR5cGUgdGhlIHRva2VuaXplcgogaWRlbnRpZmllZCBpdCBhcyBkdXJpbmcgcGFyc2luZy4gRXhhY3RseSBvbmUgb2YgdGhlc2Ugc2hvdWxkIGJlIHNldC4KCg0KBQQSAgEEEgS/BQIKCg0KBQQSAgEFEgS/BQsRCg0KBQQSAgEBEgS/BRIiCg0KBQQSAgEDEgS/BSUmCgwKBAQSAgISBMAFAikKDQoFBBICAgQSBMAFAgoKDQoFBBICAgUSBMAFCxEKDQoFBBICAgESBMAFEiQKDQoFBBICAgMSBMAFJygKDAoEBBICAxIEwQUCKAoNCgUEEgIDBBIEwQUCCgoNCgUEEgIDBRIEwQULEAoNCgUEEgIDARIEwQURIwoNCgUEEgIDAxIEwQUmJwoMCgQEEgIEEgTCBQIjCg0KBQQSAgQEEgTCBQIKCg0KBQQSAgQFEgTCBQsRCg0KBQQSAgQBEgTCBRIeCg0KBQQSAgQDEgTCBSEiCgwKBAQSAgUSBMMFAiIKDQoFBBICBQQSBMMFAgoKDQoFBBICBQUSBMMFCxAKDQoFBBICBQESBMMFER0KDQoFBBICBQMSBMMFICEKDAoEBBICBhIExAUCJgoNCgUEEgIGBBIExAUCCgoNCgUEEgIGBRIExAULEQoNCgUEEgIGARIExAUSIQoNCgUEEgIGAxIExAUkJQraAQoCBBMSBswFAM0GARpqIEVuY2Fwc3VsYXRlcyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgb3JpZ2luYWwgc291cmNlIGZpbGUgZnJvbSB3aGljaCBhCiBGaWxlRGVzY3JpcHRvclByb3RvIHdhcyBnZW5lcmF0ZWQuCjJgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIE9wdGlvbmFsIHNvdXJjZSBjb2RlIGluZm8KCgsKAwQTARIEzAUIFgqCEQoEBBMCABIE+AUCIRrzECBBIExvY2F0aW9uIGlkZW50aWZpZXMgYSBwaWVjZSBvZiBzb3VyY2UgY29kZSBpbiBhIC5wcm90byBmaWxlIHdoaWNoCiBjb3JyZXNwb25kcyB0byBhIHBhcnRpY3VsYXIgZGVmaW5pdGlvbi4gIFRoaXMgaW5mb3JtYXRpb24gaXMgaW50ZW5kZWQKIHRvIGJlIHVzZWZ1bCB0byBJREVzLCBjb2RlIGluZGV4ZXJzLCBkb2N1bWVudGF0aW9uIGdlbmVyYXRvcnMsIGFuZCBzaW1pbGFyCiB0b29scy4KCiBGb3IgZXhhbXBsZSwgc2F5IHdlIGhhdmUgYSBmaWxlIGxpa2U6CiAgIG1lc3NhZ2UgRm9vIHsKICAgICBvcHRpb25hbCBzdHJpbmcgZm9vID0gMTsKICAgfQogTGV0J3MgbG9vayBhdCBqdXN0IHRoZSBmaWVsZCBkZWZpbml0aW9uOgogICBvcHRpb25hbCBzdHJpbmcgZm9vID0gMTsKICAgXiAgICAgICBeXiAgICAgXl4gIF4gIF5eXgogICBhICAgICAgIGJjICAgICBkZSAgZiAgZ2hpCiBXZSBoYXZlIHRoZSBmb2xsb3dpbmcgbG9jYXRpb25zOgogICBzcGFuICAgcGF0aCAgICAgICAgICAgICAgIHJlcHJlc2VudHMKICAgW2EsaSkgIFsgNCwgMCwgMiwgMCBdICAgICBUaGUgd2hvbGUgZmllbGQgZGVmaW5pdGlvbi4KICAgW2EsYikgIFsgNCwgMCwgMiwgMCwgNCBdICBUaGUgbGFiZWwgKG9wdGlvbmFsKS4KICAgW2MsZCkgIFsgNCwgMCwgMiwgMCwgNSBdICBUaGUgdHlwZSAoc3RyaW5nKS4KICAgW2UsZikgIFsgNCwgMCwgMiwgMCwgMSBdICBUaGUgbmFtZSAoZm9vKS4KICAgW2csaCkgIFsgNCwgMCwgMiwgMCwgMyBdICBUaGUgbnVtYmVyICgxKS4KCiBOb3RlczoKIC0gQSBsb2NhdGlvbiBtYXkgcmVmZXIgdG8gYSByZXBlYXRlZCBmaWVsZCBpdHNlbGYgKGkuZS4gbm90IHRvIGFueQogICBwYXJ0aWN1bGFyIGluZGV4IHdpdGhpbiBpdCkuICBUaGlzIGlzIHVzZWQgd2hlbmV2ZXIgYSBzZXQgb2YgZWxlbWVudHMgYXJlCiAgIGxvZ2ljYWxseSBlbmNsb3NlZCBpbiBhIHNpbmdsZSBjb2RlIHNlZ21lbnQuICBGb3IgZXhhbXBsZSwgYW4gZW50aXJlCiAgIGV4dGVuZCBibG9jayAocG9zc2libHkgY29udGFpbmluZyBtdWx0aXBsZSBleHRlbnNpb24gZGVmaW5pdGlvbnMpIHdpbGwKICAgaGF2ZSBhbiBvdXRlciBsb2NhdGlvbiB3aG9zZSBwYXRoIHJlZmVycyB0byB0aGUgImV4dGVuc2lvbnMiIHJlcGVhdGVkCiAgIGZpZWxkIHdpdGhvdXQgYW4gaW5kZXguCiAtIE11bHRpcGxlIGxvY2F0aW9ucyBtYXkgaGF2ZSB0aGUgc2FtZSBwYXRoLiAgVGhpcyBoYXBwZW5zIHdoZW4gYSBzaW5nbGUKICAgbG9naWNhbCBkZWNsYXJhdGlvbiBpcyBzcHJlYWQgb3V0IGFjcm9zcyBtdWx0aXBsZSBwbGFjZXMuICBUaGUgbW9zdAogICBvYnZpb3VzIGV4YW1wbGUgaXMgdGhlICJleHRlbmQiIGJsb2NrIGFnYWluIC0tIHRoZXJlIG1heSBiZSBtdWx0aXBsZQogICBleHRlbmQgYmxvY2tzIGluIHRoZSBzYW1lIHNjb3BlLCBlYWNoIG9mIHdoaWNoIHdpbGwgaGF2ZSB0aGUgc2FtZSBwYXRoLgogLSBBIGxvY2F0aW9uJ3Mgc3BhbiBpcyBub3QgYWx3YXlzIGEgc3Vic2V0IG9mIGl0cyBwYXJlbnQncyBzcGFuLiAgRm9yCiAgIGV4YW1wbGUsIHRoZSAiZXh0ZW5kZWUiIG9mIGFuIGV4dGVuc2lvbiBkZWNsYXJhdGlvbiBhcHBlYXJzIGF0IHRoZQogICBiZWdpbm5pbmcgb2YgdGhlICJleHRlbmQiIGJsb2NrIGFuZCBpcyBzaGFyZWQgYnkgYWxsIGV4dGVuc2lvbnMgd2l0aGluCiAgIHRoZSBibG9jay4KIC0gSnVzdCBiZWNhdXNlIGEgbG9jYXRpb24ncyBzcGFuIGlzIGEgc3Vic2V0IG9mIHNvbWUgb3RoZXIgbG9jYXRpb24ncyBzcGFuCiAgIGRvZXMgbm90IG1lYW4gdGhhdCBpdCBpcyBhIGRlc2NlbmRlbnQuICBGb3IgZXhhbXBsZSwgYSAiZ3JvdXAiIGRlZmluZXMKICAgYm90aCBhIHR5cGUgYW5kIGEgZmllbGQgaW4gYSBzaW5nbGUgZGVjbGFyYXRpb24uICBUaHVzLCB0aGUgbG9jYXRpb25zCiAgIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHR5cGUgYW5kIGZpZWxkIGFuZCB0aGVpciBjb21wb25lbnRzIHdpbGwgb3ZlcmxhcC4KIC0gQ29kZSB3aGljaCB0cmllcyB0byBpbnRlcnByZXQgbG9jYXRpb25zIHNob3VsZCBwcm9iYWJseSBiZSBkZXNpZ25lZCB0bwogICBpZ25vcmUgdGhvc2UgdGhhdCBpdCBkb2Vzbid0IHVuZGVyc3RhbmQsIGFzIG1vcmUgdHlwZXMgb2YgbG9jYXRpb25zIGNvdWxkCiAgIGJlIHJlY29yZGVkIGluIHRoZSBmdXR1cmUuCgoNCgUEEwIABBIE+AUCCgoNCgUEEwIABhIE+AULEwoNCgUEEwIAARIE+AUUHAoNCgUEEwIAAxIE+AUfIAoOCgQEEwMAEgb5BQLMBgMKDQoFBBMDAAESBPkFChIKgwcKBgQTAwACABIEkQYEKhryBiBJZGVudGlmaWVzIHdoaWNoIHBhcnQgb2YgdGhlIEZpbGVEZXNjcmlwdG9yUHJvdG8gd2FzIGRlZmluZWQgYXQgdGhpcwogbG9jYXRpb24uCgogRWFjaCBlbGVtZW50IGlzIGEgZmllbGQgbnVtYmVyIG9yIGFuIGluZGV4LiAgVGhleSBmb3JtIGEgcGF0aCBmcm9tCiB0aGUgcm9vdCBGaWxlRGVzY3JpcHRvclByb3RvIHRvIHRoZSBwbGFjZSB3aGVyZSB0aGUgZGVmaW5pdGlvbi4gIEZvcgogZXhhbXBsZSwgdGhpcyBwYXRoOgogICBbIDQsIDMsIDIsIDcsIDEgXQogcmVmZXJzIHRvOgogICBmaWxlLm1lc3NhZ2VfdHlwZSgzKSAgLy8gNCwgMwogICAgICAgLmZpZWxkKDcpICAgICAgICAgLy8gMiwgNwogICAgICAgLm5hbWUoKSAgICAgICAgICAgLy8gMQogVGhpcyBpcyBiZWNhdXNlIEZpbGVEZXNjcmlwdG9yUHJvdG8ubWVzc2FnZV90eXBlIGhhcyBmaWVsZCBudW1iZXIgNDoKICAgcmVwZWF0ZWQgRGVzY3JpcHRvclByb3RvIG1lc3NhZ2VfdHlwZSA9IDQ7CiBhbmQgRGVzY3JpcHRvclByb3RvLmZpZWxkIGhhcyBmaWVsZCBudW1iZXIgMjoKICAgcmVwZWF0ZWQgRmllbGREZXNjcmlwdG9yUHJvdG8gZmllbGQgPSAyOwogYW5kIEZpZWxkRGVzY3JpcHRvclByb3RvLm5hbWUgaGFzIGZpZWxkIG51bWJlciAxOgogICBvcHRpb25hbCBzdHJpbmcgbmFtZSA9IDE7CgogVGh1cywgdGhlIGFib3ZlIHBhdGggZ2l2ZXMgdGhlIGxvY2F0aW9uIG9mIGEgZmllbGQgbmFtZS4gIElmIHdlIHJlbW92ZWQKIHRoZSBsYXN0IGVsZW1lbnQ6CiAgIFsgNCwgMywgMiwgNyBdCiB0aGlzIHBhdGggcmVmZXJzIHRvIHRoZSB3aG9sZSBmaWVsZCBkZWNsYXJhdGlvbiAoZnJvbSB0aGUgYmVnaW5uaW5nCiBvZiB0aGUgbGFiZWwgdG8gdGhlIHRlcm1pbmF0aW5nIHNlbWljb2xvbikuCgoPCgcEEwMAAgAEEgSRBgQMCg8KBwQTAwACAAUSBJEGDRIKDwoHBBMDAAIAARIEkQYTFwoPCgcEEwMAAgADEgSRBhobCg8KBwQTAwACAAgSBJEGHCkKEgoKBBMDAAIACOcHABIEkQYdKAoTCgsEEwMAAgAI5wcAAhIEkQYdIwoUCgwEEwMAAgAI5wcAAgASBJEGHSMKFQoNBBMDAAIACOcHAAIAARIEkQYdIwoTCgsEEwMAAgAI5wcAAxIEkQYkKArSAgoGBBMDAAIBEgSYBgQqGsECIEFsd2F5cyBoYXMgZXhhY3RseSB0aHJlZSBvciBmb3VyIGVsZW1lbnRzOiBzdGFydCBsaW5lLCBzdGFydCBjb2x1bW4sCiBlbmQgbGluZSAob3B0aW9uYWwsIG90aGVyd2lzZSBhc3N1bWVkIHNhbWUgYXMgc3RhcnQgbGluZSksIGVuZCBjb2x1bW4uCiBUaGVzZSBhcmUgcGFja2VkIGludG8gYSBzaW5nbGUgZmllbGQgZm9yIGVmZmljaWVuY3kuICBOb3RlIHRoYXQgbGluZQogYW5kIGNvbHVtbiBudW1iZXJzIGFyZSB6ZXJvLWJhc2VkIC0tIHR5cGljYWxseSB5b3Ugd2lsbCB3YW50IHRvIGFkZAogMSB0byBlYWNoIGJlZm9yZSBkaXNwbGF5aW5nIHRvIGEgdXNlci4KCg8KBwQTAwACAQQSBJgGBAwKDwoHBBMDAAIBBRIEmAYNEgoPCgcEEwMAAgEBEgSYBhMXCg8KBwQTAwACAQMSBJgGGhsKDwoHBBMDAAIBCBIEmAYcKQoSCgoEEwMAAgEI5wcAEgSYBh0oChMKCwQTAwACAQjnBwACEgSYBh0jChQKDAQTAwACAQjnBwACABIEmAYdIwoVCg0EEwMAAgEI5wcAAgABEgSYBh0jChMKCwQTAwACAQjnBwADEgSYBiQoCqUMCgYEEwMAAgISBMkGBCkalAwgSWYgdGhpcyBTb3VyY2VDb2RlSW5mbyByZXByZXNlbnRzIGEgY29tcGxldGUgZGVjbGFyYXRpb24sIHRoZXNlIGFyZSBhbnkKIGNvbW1lbnRzIGFwcGVhcmluZyBiZWZvcmUgYW5kIGFmdGVyIHRoZSBkZWNsYXJhdGlvbiB3aGljaCBhcHBlYXIgdG8gYmUKIGF0dGFjaGVkIHRvIHRoZSBkZWNsYXJhdGlvbi4KCiBBIHNlcmllcyBvZiBsaW5lIGNvbW1lbnRzIGFwcGVhcmluZyBvbiBjb25zZWN1dGl2ZSBsaW5lcywgd2l0aCBubyBvdGhlcgogdG9rZW5zIGFwcGVhcmluZyBvbiB0aG9zZSBsaW5lcywgd2lsbCBiZSB0cmVhdGVkIGFzIGEgc2luZ2xlIGNvbW1lbnQuCgogbGVhZGluZ19kZXRhY2hlZF9jb21tZW50cyB3aWxsIGtlZXAgcGFyYWdyYXBocyBvZiBjb21tZW50cyB0aGF0IGFwcGVhcgogYmVmb3JlIChidXQgbm90IGNvbm5lY3RlZCB0bykgdGhlIGN1cnJlbnQgZWxlbWVudC4gRWFjaCBwYXJhZ3JhcGgsCiBzZXBhcmF0ZWQgYnkgZW1wdHkgbGluZXMsIHdpbGwgYmUgb25lIGNvbW1lbnQgZWxlbWVudCBpbiB0aGUgcmVwZWF0ZWQKIGZpZWxkLgoKIE9ubHkgdGhlIGNvbW1lbnQgY29udGVudCBpcyBwcm92aWRlZDsgY29tbWVudCBtYXJrZXJzIChlLmcuIC8vKSBhcmUKIHN0cmlwcGVkIG91dC4gIEZvciBibG9jayBjb21tZW50cywgbGVhZGluZyB3aGl0ZXNwYWNlIGFuZCBhbiBhc3Rlcmlzawogd2lsbCBiZSBzdHJpcHBlZCBmcm9tIHRoZSBiZWdpbm5pbmcgb2YgZWFjaCBsaW5lIG90aGVyIHRoYW4gdGhlIGZpcnN0LgogTmV3bGluZXMgYXJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQuCgogRXhhbXBsZXM6CgogICBvcHRpb25hbCBpbnQzMiBmb28gPSAxOyAgLy8gQ29tbWVudCBhdHRhY2hlZCB0byBmb28uCiAgIC8vIENvbW1lbnQgYXR0YWNoZWQgdG8gYmFyLgogICBvcHRpb25hbCBpbnQzMiBiYXIgPSAyOwoKICAgb3B0aW9uYWwgc3RyaW5nIGJheiA9IDM7CiAgIC8vIENvbW1lbnQgYXR0YWNoZWQgdG8gYmF6LgogICAvLyBBbm90aGVyIGxpbmUgYXR0YWNoZWQgdG8gYmF6LgoKICAgLy8gQ29tbWVudCBhdHRhY2hlZCB0byBxdXguCiAgIC8vCiAgIC8vIEFub3RoZXIgbGluZSBhdHRhY2hlZCB0byBxdXguCiAgIG9wdGlvbmFsIGRvdWJsZSBxdXggPSA0OwoKICAgLy8gRGV0YWNoZWQgY29tbWVudCBmb3IgY29yZ2UuIFRoaXMgaXMgbm90IGxlYWRpbmcgb3IgdHJhaWxpbmcgY29tbWVudHMKICAgLy8gdG8gcXV4IG9yIGNvcmdlIGJlY2F1c2UgdGhlcmUgYXJlIGJsYW5rIGxpbmVzIHNlcGFyYXRpbmcgaXQgZnJvbQogICAvLyBib3RoLgoKICAgLy8gRGV0YWNoZWQgY29tbWVudCBmb3IgY29yZ2UgcGFyYWdyYXBoIDIuCgogICBvcHRpb25hbCBzdHJpbmcgY29yZ2UgPSA1OwogICAvKiBCbG9jayBjb21tZW50IGF0dGFjaGVkCiAgICAqIHRvIGNvcmdlLiAgTGVhZGluZyBhc3Rlcmlza3MKICAgICogd2lsbCBiZSByZW1vdmVkLiAqLwogICAvKiBCbG9jayBjb21tZW50IGF0dGFjaGVkIHRvCiAgICAqIGdyYXVsdC4gKi8KICAgb3B0aW9uYWwgaW50MzIgZ3JhdWx0ID0gNjsKCiAg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bGUucHJvdG9idWYuRW51bU9wdGlvbnMYseQDIAEoCFIRZ29wcm90b0VudW1QcmVmaXg6UgoVZ29wcm90b19lbnVtX3N0cmluZ2VyEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMXkAyABKAhSE2dvcHJvdG9FbnVtU3RyaW5nZXI6QwoNZW51bV9zdHJpbmdlchIcLmdvb2dsZS5wcm90b2J1Zi5FbnVtT3B0aW9ucxjG5AMgASgIUgxlbnVtU3RyaW5nZXI6RwoPZW51bV9jdXN0b21uYW1lEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMfkAyABKAlSDmVudW1DdXN0b21uYW1lOjoKCGVudW1kZWNsEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMjkAyABKAhSCGVudW1kZWNsOlYKFGVudW12YWx1ZV9jdXN0b21uYW1lEiEuZ29vZ2xlLnByb3RvYnVmLkVudW1WYWx1ZU9wdGlvbnMY0YMEIAEoCVITZW51bXZhbHVlQ3VzdG9tbmFtZTpOChNnb3Byb3RvX2dldHRlcnNfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJnsAyABKAhSEWdvcHJvdG9HZXR0ZXJzQWxsOlUKF2dvcHJvdG9fZW51bV9wcmVmaXhfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJrsAyABKAhSFGdvcHJvdG9FbnVtUHJlZml4QWxsOlAKFGdvcHJvdG9fc3RyaW5nZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJvsAyABKAhSEmdvcHJvdG9TdHJpbmdlckFsbDpKChF2ZXJib3NlX2VxdWFsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxic7AMgASgIUg92ZXJib3NlRXF1YWxBbGw6OQoIZmFjZV9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYnewDIAEoCFIHZmFjZUFsbDpBCgxnb3N0cmluZ19hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYnuwDIAEoCFILZ29zdHJpbmdBbGw6QQoMcG9wdWxhdGVfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJ/sAyABKAhSC3BvcHVsYXRlQWxsOkEKDHN0cmluZ2VyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxig7AMgASgIUgtzdHJpbmdlckFsbDo/Cgtvbmx5b25lX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxih7AMgASgIUgpvbmx5b25lQWxsOjsKCWVxdWFsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxil7AMgASgIUghlcXVhbEFsbDpHCg9kZXNjcmlwdGlvbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYpuwDIAEoCFIOZGVzY3JpcHRpb25BbGw6PwoLdGVzdGdlbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYp+wDIAEoCFIKdGVzdGdlbkFsbDpBCgxiZW5jaGdlbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYqOwDIAEoCFILYmVuY2hnZW5BbGw6QwoNbWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxip7AMgASgIUgxtYXJzaGFsZXJBbGw6RwoPdW5tYXJzaGFsZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGKrsAyABKAhSDnVubWFyc2hhbGVyQWxsOlAKFHN0YWJsZV9tYXJzaGFsZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGKvsAyABKAhSEnN0YWJsZU1hcnNoYWxlckFsbDo7CglzaXplcl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYrOwDIAEoCFIIc2l6ZXJBbGw6WQoZZ29wcm90b19lbnVtX3N0cmluZ2VyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxit7AMgASgIUhZnb3Byb3RvRW51bVN0cmluZ2VyQWxsOkoKEWVudW1fc3RyaW5nZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGK7sAyABKAhSD2VudW1TdHJpbmdlckFsbDpQChR1bnNhZmVfbWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxiv7AMgASgIUhJ1bnNhZmVNYXJzaGFsZXJBbGw6VAoWdW5zYWZlX3VubWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxiw7AMgASgIUhR1bnNhZmVVbm1hcnNoYWxlckFsbDpbChpnb3Byb3RvX2V4dGVuc2lvbnNfbWFwX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxix7AMgASgIUhdnb3Byb3RvRXh0ZW5zaW9uc01hcEFsbDpYChhnb3Byb3RvX3VucmVjb2duaXplZF9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYsuwDIAEoCFIWZ29wcm90b1VucmVjb2duaXplZEFsbDpJChBnb2dvcHJvdG9faW1wb3J0EhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLPsAyABKAhSD2dvZ29wcm90b0ltcG9ydDpFCg5wcm90b3NpemVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi07AMgASgIUg1wcm90b3NpemVyQWxsOj8KC2NvbXBhcmVfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLXsAyABKAhSCmNvbXBhcmVBbGw6QQoMdHlwZWRlY2xfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLbsAyABKAhSC3R5cGVkZWNsQWxsOkEKDGVudW1kZWNsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi37AMgASgIUgtlbnVtZGVjbEFsbDpRChRnb3Byb3RvX3JlZ2lzdHJhdGlvbhIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi47AMgASgIUhNnb3Byb3RvUmVnaXN0cmF0aW9uOkcKD21lc3NhZ2VuYW1lX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi57AMgASgIUg5tZXNzYWdlbmFtZUFsbDpSChVnb3Byb3RvX3NpemVjYWNoZV9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYuuwDIAEoCFITZ29wcm90b1NpemVjYWNoZUFsbDpOChNnb3Byb3RvX3Vua2V5ZWRfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLvsAyABKAhSEWdvcHJvdG9VbmtleWVkQWxsOkoKD2dvcHJvdG9fZ2V0dGVycxIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiB9AMgASgIUg5nb3Byb3RvR2V0dGVyczpMChBnb3Byb3RvX3N0cmluZ2VyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGIP0AyABKAhSD2dvcHJvdG9TdHJpbmdlcjpGCg12ZXJib3NlX2VxdWFsEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGIT0AyABKAhSDHZlcmJvc2VFcXVhbDo1CgRmYWNlEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGIX0AyABKAhSBGZhY2U6PQoIZ29zdHJpbmcSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYhvQDIAEoCFIIZ29zdHJpbmc6PQoIcG9wdWxhdGUSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYh/QDIAEoCFIIcG9wdWxhdGU6PQoIc3RyaW5nZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYwIsEIAEoCFIIc3RyaW5nZXI6OwoHb25seW9uZRIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiJ9AMgASgIUgdvbmx5b25lOjcKBWVxdWFsEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGI30AyABKAhSBWVxdWFsOkMKC2Rlc2NyaXB0aW9uEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGI70AyABKAhSC2Rlc2NyaXB0aW9uOjsKB3Rlc3RnZW4SHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYj/QDIAEoCFIHdGVzdGdlbjo9CghiZW5jaGdlbhIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiQ9AMgASgIUghiZW5jaGdlbjo/CgltYXJzaGFsZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYkfQDIAEoCFIJbWFyc2hhbGVyOkMKC3VubWFyc2hhbGVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJL0AyABKAhSC3VubWFyc2hhbGVyOkwKEHN0YWJsZV9tYXJzaGFsZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYk/QDIAEoCFIPc3RhYmxlTWFyc2hhbGVyOjcKBXNpemVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJT0AyABKAhSBXNpemVyOkwKEHVuc2FmZV9tYXJzaGFsZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYl/QDIAEoCFIPdW5zYWZlTWFyc2hhbGVyOlAKEnVuc2FmZV91bm1hcnNoYWxlchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiY9AMgASgIUhF1bnNhZmVVbm1hcnNoYWxlcjpXChZnb3Byb3RvX2V4dGVuc2lvbnNfbWFwEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJn0AyABKAhSFGdvcHJvdG9FeHRlbnNpb25zTWFwOlQKFGdvcHJvdG9fdW5yZWNvZ25pemVkEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJr0AyABKAhSE2dvcHJvdG9VbnJlY29nbml6ZWQ6QQoKcHJvdG9zaXplchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxic9AMgASgIUgpwcm90b3NpemVyOjsKB2NvbXBhcmUSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYnfQDIAEoCFIHY29tcGFyZTo9Cgh0eXBlZGVjbBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxie9AMgASgIUgh0eXBlZGVjbDpDCgttZXNzYWdlbmFtZRIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxih9AMgASgIUgttZXNzYWdlbmFtZTpOChFnb3Byb3RvX3NpemVjYWNoZRIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxii9AMgASgIUhBnb3Byb3RvU2l6ZWNhY2hlOkoKD2dvcHJvdG9fdW5rZXllZBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxij9AMgASgIUg5nb3Byb3RvVW5rZXllZDo7CghudWxsYWJsZRIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY6fsDIAEoCFIIbnVsbGFibGU6NQoFZW1iZWQSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGOr7AyABKAhSBWVtYmVkOj8KCmN1c3RvbXR5cGUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGOv7AyABKAlSCmN1c3RvbXR5cGU6PwoKY3VzdG9tbmFtZRIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY7PsDIAEoCVIKY3VzdG9tbmFtZTo5Cgdqc29udGFnEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjt+wMgASgJUgdqc29udGFnOjsKCG1vcmV0YWdzEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxju+wMgASgJUghtb3JldGFnczo7CghjYXN0dHlwZRIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY7/sDIAEoCVIIY2FzdHR5cGU6OQoHY2FzdGtleRIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY8PsDIAEoCVIHY2FzdGtleTo9CgljYXN0dmFsdWUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGPH7AyABKAlSCWNhc3R2YWx1ZTo5CgdzdGR0aW1lEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjy+wMgASgIUgdzdGR0aW1lOkEKC3N0ZGR1cmF0aW9uEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjz+wMgASgIUgtzdGRkdXJhdGlvbjo/Cgp3a3Rwb2ludGVyEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxj0+wMgASgIUgp3a3Rwb2ludGVyQkUKE2NvbS5nb29nbGUucHJvdG9idWZCCkdvR29Qcm90b3NaImdpdGh1Yi5jb20vZ29nby9wcm90b2J1Zi9nb2dvcHJvdG9KqDgKBxIFHACPAQEK/AoKAQwSAxwAEjLxCiBQcm90b2NvbCBCdWZmZXJzIGZvciBHbyB3aXRoIEdhZGdldHMKCiBDb3B5cmlnaHQgKGMpIDIwMTMsIFRoZSBHb0dvIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuCiBodHRwOi8vZ2l0aHViLmNvbS9nb2dvL3Byb3RvYnVmCgogUmVkaXN0cmlidXRpb24gYW5kIHVzZSBpbiBzb3VyY2UgYW5kIGJpbmFyeSBmb3Jtcywgd2l0aCBvciB3aXRob3V0CiBtb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlCiBtZXQ6CgogICAgICogUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQKIG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KICAgICAqIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUKIGNvcHlyaWdodCBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIKIGluIHRoZSBkb2N1bWVudGF0aW9uIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUKIGRpc3RyaWJ1dGlvbi4KCiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTCiAiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVAogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SCiBBIFBBUlRJQ1VMQVIgUFVSUE9T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BoABAI8BAQoKCgIHQBIEgQEIJwoLCgMHQAISBIABByMKCwoDB0AEEgSBAQgQCgsKAwdABRIEgQERFQoLCgMHQAESBIEBFh4KCwoDB0ADEgSBASEmCgoKAgdBEgSCAQgkCgsKAwdBAhIEgAEHIwoLCgMHQQQSBIIBCBAKCwoDB0EFEgSCAREVCgsKAwdBARIEggEWGwoLCgMHQQMSBIIBHiMKCgoCB0ISBIMBCCsKCwoDB0ICEgSAAQcjCgsKAwdCBBIEgwEIEAoLCgMHQgUSBIMBERcKCwoDB0IBEgSDARgiCgsKAwdCAxIEgwElKgoKCgIHQxIEhAEIKwoLCgMHQwISBIABByMKCwoDB0MEEgSEAQgQCgsKAwdDBRIEhAERFwoLCgMHQwESBIQBGCIKCwoDB0MDEgSEASUqCgoKAgdEEgSFAQgoCgsKAwdEAhIEgAEHIwoLCgMHRAQSBIUBCBAKCwoDB0QFEgSFAREXCgsKAwdEARIEhQEYHwoLCgMHRAMSBIUBIicKCgoCB0USBIYBCCkKCwoDB0UCEgSAAQcjCgsKAwdFBBIEhgEIEAoLCgMHRQUSBIYBERcKCwoDB0UBEgSGARggCgsKAwdFAxIEhgEjKAoKCgIHRhIEhwEIKQoLCgMHRgISBIABByMKCwoDB0YEEgSHAQgQCgsKAwdGBRIEhwERFwoLCgMHRgESBIcBGCAKCwoDB0YDEgSHASMoCgoKAgdHEgSIAQgoCgsKAwdHAhIEgAEHIwoLCgMHRwQSBIgBCBAKCwoDB0cFEgSIAREXCgsKAwdHARIEiAEYHwoLCgMHRwMSBIgBIicKCgoCB0gSBIkBCCoKCwoDB0gCEgSAAQcjCgsKAwdIBBIEiQEIEAoLCgMHSAUSBIkBERcKCwoDB0gBEgSJARghCgsKAwdIAxIEiQEkKQoKCgIHSRIEiwEIJgoLCgMHSQISBIABByMKCwoDB0kEEgSLAQgQCgsKAwdJBRIEiwERFQoLCgMHSQESBIsBFh0KCwoDB0kDEgSLASAlCgoKAgdKEgSMAQgqCgsKAwdKAhIEgAEHIwoLCgMHSgQSBIwBCBAKCwoDB0oFEgSMAREVCgsKAwdKARIEjAEWIQoLCgMHSgMSBIwBJCkKCgoCB0sSBI0BCCkKCwoDB0sCEgSAAQcjCgsKAwdLBBIEjQEIEAoLCgMHSwUSBI0BERUKCwoDB0sBEgSNARYgCgsKAwdLAxIEjQEjKAq7IQoTY29uZmlnL2NvbmZpZy5wcm90bxIXYWRhcHRlci5uZXdyZWxpYy5jb25maWcaFGdvZ29wcm90by9nb2dvLnByb3RvIvoCCgZQYXJhbXMSHAoJbmFtZXNwYWNlGAEgASgJUgluYW1lc3BhY2USRgoHbWV0cmljcxgCIAMoCzIsLmFkYXB0ZXIubmV3cmVsaWMuY29uZmlnLlBhcmFtcy5NZXRyaWNzRW50cnlSB21ldHJpY3MaoQEKCk1ldHJpY0luZm8SEgoEbmFtZRgBIAEoCVIEbmFtZRJDCgR0eXBlGAIgASgOMi8uYWRhcHRlci5uZXdyZWxpYy5jb25maWcuUGFyYW1zLk1ldHJpY0luZm8uVHlwZVIEdHlwZSI6CgRUeXBlEg8KC1VOU1BFQ0lGSUVEEAASCQoFR0FVR0UQARIJCgVDT1VOVBACEgsKB1NVTU1BUlkQAxpmCgxNZXRyaWNzRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSQAoFdmFsdWUYAiABKAsyKi5hZGFwdGVyLm5ld3JlbGljLmNvbmZpZy5QYXJhbXMuTWV0cmljSW5mb1IFdmFsdWU6AjgBQghaBmNvbmZpZ0rlHQoGEgQOAGMBCscECgEMEgMOABIyvAQgQ29weXJpZ2h0IDIwMTkgTmV3IFJlbGljIENvcnBvcmF0aW9uCgogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKCiAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCgogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4g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EgNIBhAaiAQgQSBOZXcgUmVsaWMgYENvdW50YCB0eXBlLgoKIFRoaXMgbWV0cmljIHR5cGUgcmVwcmVzZW50cyB0aGUgbnVtYmVyIG9mIG9jY3VycmVuY2VzIGZvciBhbiBldmVudAogd2l0aGluIGEgdGltZSB3aW5kb3cuIEl0IGlzIGltcG9ydGFudCB0byBub3RlIHRoYXQgdGhpcyBpcyBub3QgdGhlCiBjdW11bGF0aXZlIHRhbGx5IG9mIG9jY3VycmVuY2VzIHNpbmNlIHRoZSBiZWdpbm5pbmcgb2YKIG1lYXN1cmVtZW50cy4gUmF0aGVyLCB0aGlzIG1ldHJpYyB0eXBlIHJlcHJlc2VudHMgdGhlIGNoYW5nZSBpbiB0aGUKIGN1bXVsYXRpdmUgdGFsbHkgb2YgZXZlbnRzIHdpdGhpbiBhIHRpbWUgd2luZG93LgoKIEZvciBleGFtcGxlLCB0aGlzIG1ldHJpYyB0eXBlIHdvdWxkIGJlIHVzZWQgdG8gcmVjb3JkOgoKICAqIHRoZSBudW1iZXIgb2YgcmVxdWVzdHMgdG8gYSBzZXJ2aWNlCiAgKiB0aGUgbnVtYmVyIG9mIHRhc2tzIHN1Ym1pdHRlZCB0byBhIHByb2Nlc3NvcgogICogdGhlIG51bWJlciBvZiBlcnJvcnMgcHJvZHVjZWQKChAKCQQAAwAEAAICARIDSAYLChAKCQQAAwAEAAICAhIDSA4PCukDCggEAAMABAACAxIDVgYSGtcDIE5ldyBSZWxpYyBgU3VtbWFyeWAgdHlwZS4KCiBUaGlzIG1ldHJpYyB0eXBlIHJlcG9ydHMgYWdncmVnYXRlZCBpbmZvcm1hdGlvbiBhYm91dCBkaXNjcmV0ZQogZXZlbnRzLiBUaGUgaW5mb3JtYXRpb24gaXMgcmVjb3JkZWQgYXMgYSBjb3VudCBvZiBldmVudHMsIGF2ZXJhZ2UKIGV2ZW50IHZhbHVlcywgc3VtIG9mIGV2ZW50IHZhbHVlcywgYW5kIHRoZSBtaW5pbXVtIGFuZCBtYXhpbXVtCiBldmVudCB2YWx1ZXMgb2JzZXJ2ZWQgd2l0aGluIGEgdGltZSB3aW5kb3cuCgogRm9yIGV4YW1wbGUsIHRoaXMgbWV0cmljIHR5cGUgd291bGQgYmUgdXNlZCB0byByZWNvcmQ6CgogICogdGhlIGR1cmF0aW9uIGFuZCBjb3VudCBvZiByZXF1ZXN0cyB0byBzZXJ2aWNlCiAgKiB0aGUgZHVyYXRpb24gYW5kIGNvdW50IG9mIGRhdGFiYXNlIHRyYW5zYWN0aW9ucwogICogdGhlIHRpbWUgZWFjaCBtZXNzYWdlIHNwZW50IGluIGEgcXVldWUKChAKCQQAAwAEAAIDARIDVgYNChAKCQQAAwAEAAIDAhIDVhARClQKBgQAAwACARIDWQQSGkUgUmVxdWlyZWQuIE5ldyBSZWxpYyBtZXRyaWMgdHlwZSB0byBpbnRlcnByZXQgdGhlIElzdGlvIGluc3RhbmNlIGFzLgoKDwoHBAADAAIBBBIEWQRXBQoOCgcEAAMAAgEGEgNZBAgKDgoHBAADAAIBARIDWQkNCg4KBwQAAwACAQMSA1kQEQqyAgoEBAACARIDYgImGqQCIE1hcCBvZiBJc3RpbyBtZXRyaWMgaW5zdGFuY2UgbmFtZXMgYW5kIHRoZSBjb3JyZXNwb25kaW5nIE5ldyBSZWxpYwogTWV0cmljSW5mbyBzcGVjaWZpY2F0aW9uLiBUaGlzIGlkZW50aWZpZXMgd2hhdCB0byBzZW5kIE5ldyBSZWxpYyBhbmQKIGluIHdoYXQgZm9ybSBpdCBzaG91bGQgYmUgc2VudC4KCiBBbnkgbWV0cmljIGluc3RhbmNlcyBJc3RpbyBzZW5kcyB0byB0aGUgYWRhcHRlciBidXQgbm90IHNwZWNpZmllZCBoZXJlCiB3aWxsIGJlIGRyb3BwZWQgYW5kIG5vdCBleHBvcnRlZCB0byBOZXcgUmVsaWMuCgoNCgUEAAIBBBIEYgJaAwoMCgUEAAIBBhIDYgIZCgwKBQQAAgEBEgNiGiEKDAoFBAACAQMSA2IkJWIGcHJvdG8z +--- diff --git a/helm-charts/templates/attributes.yaml b/helm-charts/templates/attributes.yaml new file mode 100644 index 0000000..641d4e5 --- /dev/null +++ b/helm-charts/templates/attributes.yaml @@ -0,0 +1,28 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +apiVersion: "config.istio.io/v1alpha2" +kind: attributemanifest +metadata: + name: {{ include "newrelic-istio-adapter.fullname" . }} + namespace: {{ .Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +spec: + attributes: + {{- toYaml .Values.telemetry.attributes | nindent 4 }} diff --git a/helm-charts/templates/deployment.yaml b/helm-charts/templates/deployment.yaml new file mode 100644 index 0000000..4ecb472 --- /dev/null +++ b/helm-charts/templates/deployment.yaml @@ -0,0 +1,88 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "newrelic-istio-adapter.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + annotations: + sidecar.istio.io/inject: "false" + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: grpc + containerPort: 55912 + env: + - name: NEW_RELIC_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "newrelic-istio-adapter.secretName" . }} + key: NEW_RELIC_API_KEY + args: + - --cluster-name + - {{ .Values.clusterName }} + {{- with .Values.metricsHost }} + - --metrics-host + - {{ . }} + {{- end }} + {{- with .Values.spansHost }} + - --spans-host + - {{ . }} + {{- end }} + - $(NEW_RELIC_API_KEY) + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:55912"] + initialDelaySeconds: 5 + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:55912"] + initialDelaySeconds: 10 + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm-charts/templates/handler.yaml b/helm-charts/templates/handler.yaml new file mode 100644 index 0000000..4480d2d --- /dev/null +++ b/helm-charts/templates/handler.yaml @@ -0,0 +1,39 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +apiVersion: "config.istio.io/v1alpha2" +kind: handler +metadata: + name: {{ include "newrelic-istio-adapter.fullname" . }} + namespace: {{ .Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +spec: + adapter: newrelic + connection: + address: {{ include "newrelic-istio-adapter.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:80 + params: + {{- with .Values.telemetry.namespace }} + namespace: {{ . }} + {{- end }} + metrics: + {{- range $k, $v := .Values.telemetry.metrics }} + {{ $k }}.instance.{{ $.Values.istioNamespace }}: + name: {{ $v.name | quote }} + type: {{ $v.type | quote }} + {{- end }} diff --git a/helm-charts/templates/metric-template.yaml b/helm-charts/templates/metric-template.yaml new file mode 100644 index 0000000..c544b85 --- /dev/null +++ b/helm-charts/templates/metric-template.yaml @@ -0,0 +1,31 @@ +# Copyright 2019 New Relic Corporation +# Copyright 2018 Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +# this config is created through command +# mixgen template -d $GOPATH/src/istio.io/istio/mixer/template/metric/template_handler_service.descriptor_set -o $GOPATH/src/istio.io/istio/mixer/template/metric/template.yaml -n metric +apiVersion: "config.istio.io/v1alpha2" +kind: template +metadata: + name: metric + namespace: {{ .Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +spec: + descriptor: "CvD6AgogZ29vZ2xlL3Byb3RvYnVmL2Rlc2NyaXB0b3IucHJvdG8SD2dvb2dsZS5wcm90b2J1ZiJNChFGaWxlRGVzY3JpcHRvclNldBI4CgRmaWxlGAEgAygLMiQuZ29vZ2xlLnByb3RvYnVmLkZpbGVEZXNjcmlwdG9yUHJvdG9SBGZpbGUi5AQKE0ZpbGVEZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRIYCgdwYWNrYWdlGAIgASgJUgdwYWNrYWdlEh4KCmRlcGVuZGVuY3kYAyADKAlSCmRlcGVuZGVuY3kSKwoRcHVibGljX2RlcGVuZGVuY3kYCiADKAVSEHB1YmxpY0RlcGVuZGVuY3kSJwoPd2Vha19kZXBlbmRlbmN5GAsgAygFUg53ZWFrRGVwZW5kZW5jeRJDCgxtZXNzYWdlX3R5cGUYBCADKAsyIC5nb29nbGUucHJvdG9idWYuRGVzY3JpcHRvclByb3RvUgttZXNzYWdlVHlwZRJBCgllbnVtX3R5cGUYBSADKAsyJC5nb29nbGUucHJvdG9idWYuRW51bURlc2NyaXB0b3JQcm90b1IIZW51bVR5cGUSQQoHc2VydmljZRgGIAMoCzInLmdvb2dsZS5wcm90b2J1Zi5TZXJ2aWNlRGVzY3JpcHRvclByb3RvUgdzZXJ2aWNlEkMKCWV4dGVuc2lvbhgHIAMoCzIlLmdvb2dsZS5wcm90b2J1Zi5GaWVsZERlc2NyaXB0b3JQcm90b1IJZXh0ZW5zaW9uEjYKB29wdGlvbnMYCCABKAsyHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnNSB29wdGlvbnMSSQoQc291cmNlX2NvZGVfaW5mbxgJIAEoCzIfLmdvb2dsZS5wcm90b2J1Zi5Tb3VyY2VDb2RlSW5mb1IOc291cmNlQ29kZUluZm8SFgoGc3ludGF4GAwgASgJUgZzeW50YXgiuQYKD0Rlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEjsKBWZpZWxkGAIgAygLMiUuZ29vZ2xlLnByb3RvYnVmLkZpZWxkRGVzY3JpcHRvclByb3RvUgVmaWVsZBJDCglleHRlbnNpb24YBiADKAsyJS5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG9SCWV4dGVuc2lvbhJBCgtuZXN0ZWRfdHlwZRgDIAMoCzIgLmdvb2dsZS5wcm90b2J1Zi5EZXNjcmlwdG9yUHJvdG9SCm5lc3RlZFR5cGUSQQoJZW51bV90eXBlGAQgAygLMiQuZ29vZ2xlLnByb3RvYnVmLkVudW1EZXNjcmlwdG9yUHJvdG9SCGVudW1UeXBlElgKD2V4dGVuc2lvbl9yYW5nZRgFIAMoCzIvLmdvb2dsZS5wcm90b2J1Zi5EZXNjcmlwdG9yUHJvdG8uRXh0ZW5zaW9uUmFuZ2VSDmV4dGVuc2lvblJhbmdlEkQKCm9uZW9mX2RlY2wYCCADKAsyJS5nb29nbGUucHJvdG9idWYuT25lb2ZEZXNjcmlwdG9yUHJvdG9SCW9uZW9mRGVjbBI5CgdvcHRpb25zGAcgASgLMh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zUgdvcHRpb25zElUKDnJlc2VydmVkX3JhbmdlGAkgAygLMi4uZ29vZ2xlLnByb3RvYnVmLkRlc2NyaXB0b3JQcm90by5SZXNlcnZlZFJhbmdlUg1yZXNlcnZlZFJhbmdlEiMKDXJlc2VydmVkX25hbWUYCiADKAlSDHJlc2VydmVkTmFtZRp6Cg5FeHRlbnNpb25SYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQSQAoHb3B0aW9ucxgDIAEoCzImLmdvb2dsZS5wcm90b2J1Zi5FeHRlbnNpb25SYW5nZU9wdGlvbnNSB29wdGlvbnMaNwoNUmVzZXJ2ZWRSYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQifAoVRXh0ZW5zaW9uUmFuZ2VPcHRpb25zElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAIimAYKFEZpZWxkRGVzY3JpcHRvclByb3RvEhIKBG5hbWUYASABKAlSBG5hbWUSFgoGbnVtYmVyGAMgASgFUgZudW1iZXISQQoFbGFiZWwYBCABKA4yKy5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG8uTGFiZWxSBWxhYmVsEj4KBHR5cGUYBSABKA4yKi5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG8uVHlwZVIEdHlwZRIbCgl0eXBlX25hbWUYBiABKAlSCHR5cGVOYW1lEhoKCGV4dGVuZGVlGAIgASgJUghleHRlbmRlZRIjCg1kZWZhdWx0X3ZhbHVlGAcgASgJUgxkZWZhdWx0VmFsdWUSHwoLb25lb2ZfaW5kZXgYCSABKAVSCm9uZW9mSW5kZXgSGwoJanNvbl9uYW1lGAogASgJUghqc29uTmFtZRI3CgdvcHRpb25zGAggASgLMh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9uc1IHb3B0aW9ucyK2AgoEVHlwZRIPCgtUWVBFX0RPVUJMRRABEg4KClRZUEVfRkxPQVQQAhIOCgpUWVBFX0lOVDY0EAMSDwoLVFlQRV9VSU5UNjQQBBIOCgpUWVBFX0lOVDMyEAUSEAoMVFlQRV9GSVhFRDY0EAYSEAoMVFlQRV9GSVhFRDMyEAcSDQoJVFlQRV9CT09MEAgSDwoLVFlQRV9TVFJJTkcQCRIOCgpUWVBFX0dST1VQEAoSEAoMVFlQRV9NRVNTQUdFEAsSDgoKVFlQRV9CWVRFUxAMEg8KC1RZUEVfVUlOVDMyEA0SDQoJVFlQRV9FTlVNEA4SEQoNVFlQRV9TRklYRUQzMhAPEhEKDVRZUEVfU0ZJWEVENjQQEBIPCgtUWVBFX1NJTlQzMhAREg8KC1RZUEVfU0lOVDY0EBIiQwoFTGFiZWwSEgoOTEFCRUxfT1BUSU9OQUwQARISCg5MQUJFTF9SRVFVSVJFRBACEhIKDkxBQkVMX1JFUEVBVEVEEAMiYwoUT25lb2ZEZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRI3CgdvcHRpb25zGAIgASgLMh0uZ29vZ2xlLnByb3RvYnVmLk9uZW9mT3B0aW9uc1IHb3B0aW9ucyLjAgoTRW51bURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEj8KBXZhbHVlGAIgAygLMikuZ29vZ2xlLnByb3RvYnVmLkVudW1WYWx1ZURlc2NyaXB0b3JQcm90b1IFdmFsdWUSNgoHb3B0aW9ucxgDIAEoCzIcLmdvb2dsZS5wcm90b2J1Zi5FbnVtT3B0aW9uc1IHb3B0aW9ucxJdCg5yZXNlcnZlZF9yYW5nZRgEIAMoCzI2Lmdvb2dsZS5wcm90b2J1Zi5FbnVtRGVzY3JpcHRvclByb3RvLkVudW1SZXNlcnZlZFJhbmdlUg1yZXNlcnZlZFJhbmdlEiMKDXJlc2VydmVkX25hbWUYBSADKAlSDHJlc2VydmVkTmFtZRo7ChFFbnVtUmVzZXJ2ZWRSYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQigwEKGEVudW1WYWx1ZURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEhYKBm51bWJlchgCIAEoBVIGbnVtYmVyEjsKB29wdGlvbnMYAyABKAsyIS5nb29nbGUucHJvdG9idWYuRW51bVZhbHVlT3B0aW9uc1IHb3B0aW9ucyKnAQoWU2VydmljZURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEj4KBm1ldGhvZBgCIAMoCzImLmdvb2dsZS5wcm90b2J1Zi5NZXRob2REZXNjcmlwdG9yUHJvdG9SBm1ldGhvZBI5CgdvcHRpb25zGAMgASgLMh8uZ29vZ2xlLnByb3RvYnVmLlNlcnZpY2VPcHRpb25zUgdvcHRpb25zIokCChVNZXRob2REZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRIdCgppbnB1dF90eXBlGAIgASgJUglpbnB1dFR5cGUSHwoLb3V0cHV0X3R5cGUYAyABKAlSCm91dHB1dFR5cGUSOAoHb3B0aW9ucxgEIAEoCzIeLmdvb2dsZS5wcm90b2J1Zi5NZXRob2RPcHRpb25zUgdvcHRpb25zEjAKEGNsaWVudF9zdHJlYW1pbmcYBSABKAg6BWZhbHNlUg9jbGllbnRTdHJlYW1pbmcSMAoQc2VydmVyX3N0cmVhbWluZxgGIAEoCDoFZmFsc2VSD3NlcnZlclN0cmVhbWluZyK5CAoLRmlsZU9wdGlvbnMSIQoMamF2YV9wYWNrYWdlGAEgASgJUgtqYXZhUGFja2FnZRIwChRqYXZhX291dGVyX2NsYXNzbmFtZRgIIAEoCVISamF2YU91dGVyQ2xhc3NuYW1lEjUKE2phdmFfbXVsdGlwbGVfZmlsZXMYCiABKAg6BWZhbHNlUhFqYXZhTXVsdGlwbGVGaWxlcxJECh1qYXZhX2dlbmVyYXRlX2VxdWFsc19hbmRfaGFzaBgUIAEoCEICGAFSGWphdmFHZW5lcmF0ZUVxdWFsc0FuZEhhc2gSOgoWamF2YV9zdHJpbmdfY2hlY2tfdXRmOBgbIAEoCDoFZmFsc2VSE2phdmFTdHJpbmdDaGVja1V0ZjgSUwoMb3B0aW1pemVfZm9yGAkgASgOMikuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zLk9wdGltaXplTW9kZToFU1BFRURSC29wdGltaXplRm9yEh0KCmdvX3BhY2thZ2UYCyABKAlSCWdvUGFja2FnZRI1ChNjY19nZW5lcmljX3NlcnZpY2VzGBAgASgIOgVmYWxzZVIRY2NHZW5lcmljU2VydmljZXMSOQoVamF2YV9nZW5lcmljX3NlcnZpY2VzGBEgASgIOgVmYWxzZVITamF2YUdlbmVyaWNTZXJ2aWNlcxI1ChNweV9nZW5lcmljX3NlcnZpY2VzGBIgASgIOgVmYWxzZVIRcHlHZW5lcmljU2VydmljZXMSNwoUcGhwX2dlbmVyaWNfc2VydmljZXMYKiABKAg6BWZhbHNlUhJwaHBHZW5lcmljU2VydmljZXMSJQoKZGVwcmVjYXRlZBgXIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSLwoQY2NfZW5hYmxlX2FyZW5hcxgfIAEoCDoFZmFsc2VSDmNjRW5hYmxlQXJlbmFzEioKEW9iamNfY2xhc3NfcHJlZml4GCQgASgJUg9vYmpjQ2xhc3NQcmVmaXgSKQoQY3NoYXJwX25hbWVzcGFjZRglIAEoCVIPY3NoYXJwTmFtZXNwYWNlEiEKDHN3aWZ0X3ByZWZpeBgnIAEoCVILc3dpZnRQcmVmaXgSKAoQcGhwX2NsYXNzX3ByZWZpeBgoIAEoCVIOcGhwQ2xhc3NQcmVmaXgSIwoNcGhwX25hbWVzcGFjZRgpIAEoCVIMcGhwTmFtZXNwYWNlElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uIjoKDE9wdGltaXplTW9kZRIJCgVTUEVFRBABEg0KCUNPREVfU0laRRACEhAKDExJVEVfUlVOVElNRRADKgkI6AcQgICAgAJKBAgmECci0QIKDk1lc3NhZ2VPcHRpb25zEjwKF21lc3NhZ2Vfc2V0X3dpcmVfZm9ybWF0GAEgASgIOgVmYWxzZVIUbWVzc2FnZVNldFdpcmVGb3JtYXQSTAofbm9fc3RhbmRhcmRfZGVzY3JpcHRvcl9hY2Nlc3NvchgCIAEoCDoFZmFsc2VSHG5vU3RhbmRhcmREZXNjcmlwdG9yQWNjZXNzb3ISJQoKZGVwcmVjYXRlZBgDIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSGwoJbWFwX2VudHJ5GAcgASgIUghtYXBFbnRyeRJYChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvblITdW5pbnRlcnByZXRlZE9wdGlvbioJCOgHEICAgIACSgQICBAJSgQICRAKIuIDCgxGaWVsZE9wdGlvbnMSQQoFY3R5cGUYASABKA4yIy5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zLkNUeXBlOgZTVFJJTkdSBWN0eXBlEhYKBnBhY2tlZBgCIAEoCFIGcGFja2VkEkcKBmpzdHlwZRgGIAEoDjIkLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMuSlNUeXBlOglKU19OT1JNQUxSBmpzdHlwZRIZCgRsYXp5GAUgASgIOgVmYWxzZVIEbGF6eRIlCgpkZXByZWNhdGVkGAMgASgIOgVmYWxzZVIKZGVwcmVjYXRlZBIZCgR3ZWFrGAogASgIOgVmYWxzZVIEd2VhaxJYChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvblITdW5pbnRlcnByZXRlZE9wdGlvbiIvCgVDVHlwZRIKCgZTVFJJTkcQABIICgRDT1JEEAESEAoMU1RSSU5HX1BJRUNFEAIiNQoGSlNUeXBlEg0KCUpTX05PUk1BTBAAEg0KCUpTX1NUUklORxABEg0KCUpTX05VTUJFUhACKgkI6AcQgICAgAJKBAgEEAUicwoMT25lb2ZPcHRpb25zElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAIiwAEKC0VudW1PcHRpb25zEh8KC2FsbG93X2FsaWFzGAIgASgIUgphbGxvd0FsaWFzEiUKCmRlcHJlY2F0ZWQYAyABKAg6BWZhbHNlUgpkZXByZWNhdGVkElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAJKBAgFEAYingEKEEVudW1WYWx1ZU9wdGlvbnMSJQoKZGVwcmVjYXRlZBgBIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24qCQjoBxCAgICAAiKcAQoOU2VydmljZU9wdGlvbnMSJQoKZGVwcmVjYXRlZBghIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24qCQjoBxCAgICAAiLgAgoNTWV0aG9kT3B0aW9ucxIlCgpkZXByZWNhdGVkGCEgASgIOgVmYWxzZVIKZGVwcmVjYXRlZBJxChFpZGVtcG90ZW5jeV9sZXZlbBgiIAEoDjIvLmdvb2dsZS5wcm90b2J1Zi5NZXRob2RPcHRpb25zLklkZW1wb3RlbmN5TGV2ZWw6E0lERU1QT1RFTkNZX1VOS05PV05SEGlkZW1wb3RlbmN5TGV2ZWwSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24iUAoQSWRlbXBvdGVuY3lMZXZlbBIXChNJREVNUE9URU5DWV9VTktOT1dOEAASEwoPTk9fU0lERV9FRkZFQ1RTEAESDgoKSURFTVBPVEVOVBACKgkI6AcQgICAgAIimgMKE1VuaW50ZXJwcmV0ZWRPcHRpb24SQQoEbmFtZRgCIAMoCzItLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uLk5hbWVQYXJ0UgRuYW1lEikKEGlkZW50aWZpZXJfdmFsdWUYAyABKAlSD2lkZW50aWZpZXJWYWx1ZRIsChJwb3NpdGl2ZV9pbnRfdmFsdWUYBCABKARSEHBvc2l0aXZlSW50VmFsdWUSLAoSbmVnYXRpdmVfaW50X3ZhbHVlGAUgASgDUhBuZWdhdGl2ZUludFZhbHVlEiEKDGRvdWJsZV92YWx1ZRgGIAEoAVILZG91YmxlVmFsdWUSIQoMc3RyaW5nX3ZhbHVlGAcgASgMUgtzdHJpbmdWYWx1ZRInCg9hZ2dyZWdhdGVfdmFsdWUYCCABKAlSDmFnZ3JlZ2F0ZVZhbHVlGkoKCE5hbWVQYXJ0EhsKCW5hbWVfcGFydBgBIAIoCVIIbmFtZVBhcnQSIQoMaXNfZXh0ZW5zaW9uGAIgAigIUgtpc0V4dGVuc2lvbiKnAgoOU291cmNlQ29kZUluZm8SRAoIbG9jYXRpb24YASADKAsyKC5nb29nbGUucHJvdG9idWYuU291cmNlQ29kZUluZm8uTG9jYXRpb25SCGxvY2F0aW9uGs4BCghMb2NhdGlvbhIWCgRwYXRoGAEgAygFQgIQAVIEcGF0aBIWCgRzcGFuGAIgAygFQgIQAVIEc3BhbhIpChBsZWFkaW5nX2NvbW1lbnRzGAMgASgJUg9sZWFkaW5nQ29tbWVudHMSKwoRdHJhaWxpbmdfY29tbWVudHMYBCABKAlSEHRyYWlsaW5nQ29tbWVudHMSOgoZbGVhZGluZ19kZXRhY2hlZF9jb21tZW50cxgGIAMoCVIXbGVhZGluZ0RldGFjaGVkQ29tbWVudHMi0QEKEUdlbmVyYXRlZENvZGVJbmZvEk0KCmFubm90YXRpb24YASADKAsyLS5nb29nbGUucHJvdG9idWYuR2VuZXJhdGVkQ29kZUluZm8uQW5ub3RhdGlvblIKYW5ub3RhdGlvbhptCgpBbm5vdGF0aW9uEhYKBHBhdGgYASADKAVCAhABUgRwYXRoEh8KC3NvdXJjZV9maWxlGAIgASgJUgpzb3VyY2VGaWxlEhQKBWJlZ2luGAMgASgFUgViZWdpbhIQCgNlbmQYBCABKAVSA2VuZEKPAQoTY29tLmdvb2dsZS5wcm90b2J1ZkIQRGVzY3JpcHRvclByb3Rvc0gBWj5naXRodWIuY29tL2dvbGFuZy9wcm90b2J1Zi9wcm90b2MtZ2VuLWdvL2Rlc2NyaXB0b3I7ZGVzY3JpcHRvcvgBAaICA0dQQqoCGkdvb2dsZS5Qcm90b2J1Zi5SZWZsZWN0aW9uSqrAAgoHEgUnAOcGAQqqDwoBDBIDJwASMsEMIFByb3RvY29sIEJ1ZmZlcnMgLSBHb29nbGUncyBkYXRhIGludGVyY2hhbmdlIGZvcm1hdAogQ29weXJpZ2h0IDIwMDggR29vZ2xlIEluYy4gIEFsbCByaWdodHMgcmVzZXJ2ZWQuCiBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wcm90b2NvbC1idWZmZXJzLwoKIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAogbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZQogbWV0OgoKICAgICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0CiBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlCiBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyCiBpbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlCiBkaXN0cmlidXRpb24uCiAgICAgKiBOZWl0aGVyIHRoZSBuYW1lIG9mIEdvb2dsZSBJbmMuIG5vciB0aGUgbmFtZXMgb2YgaXRzCiBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbQogdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi4KCiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTCiAiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVAogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SCiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVAogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsCiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwKIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWQogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAogKElOQ0xVRElORyBORUdMSUdF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bm5vdAogc3RvcmUgdGhlIHZhbHVlcyBpbiB0aGVtLiAgSW5zdGVhZCBpdCBzdG9yZXMgdGhlbSBpbiBhIGZpZWxkIGluIHRoZSAqT3B0aW9ucwogbWVzc2FnZSBjYWxsZWQgdW5pbnRlcnByZXRlZF9vcHRpb24uIFRoaXMgZmllbGQgbXVzdCBoYXZlIHRoZSBzYW1lIG5hbWUKIGFjcm9zcyBhbGwgKk9wdGlvbnMgbWVzc2FnZXMuIFdlIHRoZW4gdXNlIHRoaXMgZmllbGQgdG8gcG9wdWxhdGUgdGhlCiBleHRlbnNpb25zIHdoZW4gd2UgYnVpbGQgYSBkZXNjcmlwdG9yLCBhdCB3aGljaCBwb2ludCBhbGwgcHJvdG9zIGhhdmUgYmVlbgogcGFyc2VkIGFuZCBzbyBhbGwgZXh0ZW5zaW9ucyBhcmUga25vd24uCgogRXh0ZW5zaW9uIG51bWJlcnMgZm9yIGN1c3RvbSBvcHRpb25zIG1heSBiZSBjaG9zZW4gYXMgZm9sbG93czoKICogRm9yIG9wdGlvbnMgd2hpY2ggd2lsbCBvbmx5IGJlIHVzZWQgd2l0aGluIGEgc2luZ2xlIGFwcGxpY2F0aW9uIG9yCiAgIG9yZ2FuaXphdGlvbiwgb3IgZm9yIGV4cGVyaW1lbnRhbCBvcHRpb25zLCB1c2UgZmllbGQgbnVtYmVycyA1MDAwMAogICB0aHJvdWdoIDk5OTk5LiAgSXQgaXMgdXAgdG8geW91IHRvIGVuc3VyZSB0aGF0IHlvdSBkbyBub3QgdXNlIHRoZQogICBzYW1lIG51bWJlciBmb3IgbXVsdGlwbGUgb3B0aW9ucy4KICogRm9yIG9wdGlvbnMgd2hpY2ggd2lsbCBiZSBwdWJsaXNoZWQgYW5kIHVzZWQgcHVibGljbHkgYnkgbXVsdGlwbGUKICAgaW5kZXBlbmRlbnQgZW50aXRpZXMsIGUtbWFpbCBwcm90b2J1Zi1nbG9iYWwtZXh0ZW5zaW9uLXJlZ2lzdHJ5QGdvb2dsZS5jb20KICAgdG8gcmVzZXJ2ZSBleHRlbnNpb24gbnVtYmVycy4gU2ltcGx5IHByb3ZpZGUgeW91ciBwcm9qZWN0IG5hbWUgKGUuZy4KICAgT2JqZWN0aXZlLUMgcGx1Z2luKSBhbmQgeW91ciBwcm9qZWN0IHdlYnNpdGUgKGlmIGF2YWlsYWJsZSkgLS0gdGhlcmUncyBubwogICBuZWVkIHRvIGV4cGxhaW4gaG93IHlvdSBpbnRlbmQgdG8gdXNlIHRoZW0uIFVzdWFsbHkgeW91IG9ubHkgbmVlZCBvbmUKICAgZXh0ZW5zaW9uIG51bWJlci4gWW91IGNhbiBkZWNsYXJlIG11bHRpcGxlIG9wdGlvbnMgd2l0aCBvbmx5IG9uZSBleHRlbnNpb24KICAgbnVtYmVyIGJ5IHB1dHRpbmcgdGhlbSBpbiBhIHN1Yi1tZXNzYWdlLiBTZWUgdGhlIEN1c3RvbSBPcHRpb25zIHNlY3Rpb24gb2YKICAgdGhlIGRvY3MgZm9yIGV4YW1wbGVzOgogICBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wcm90b2NvbC1idWZmZXJzL2RvY3MvcHJvdG8jb3B0aW9ucwogICBJZiB0aGlzIHR1cm5zIG91dCB0byBiZSBwb3B1bGFyLCBhIHdlYiBzZXJ2aWNlIHdpbGwgYmUgc2V0IHVwCiAgIHRvIGF1dG9tYXRpY2FsbHkgYXNzaWduIG9wdGlvbiBudW1iZXJzLgoKCwoDBAoBEgS9AggTCvQBCgQECgIAEgTDAgIjGuUBIFNldHMgdGhlIEphdmEgcGFja2FnZSB3aGVyZSBjbGFzc2VzIGdlbmVyYXRlZCBmcm9tIHRoaXMgLnByb3RvIHdpbGwgYmUKIHBsYWNlZC4gIEJ5IGRlZmF1bHQsIHRoZSBwcm90byBwYWNrYWdlIGlzIHVzZWQsIGJ1dCB0aGlzIGlzIG9mdGVuCiBpbmFwcHJvcHJpYXRlIGJlY2F1c2UgcHJvdG8gcGFja2FnZXMgZG8gbm90IG5vcm1hbGx5IHN0YXJ0IHdpdGggYmFja3dhcmRzCiBkb21haW4gbmFtZXMuCgoNCgUECgIABBIEwwICCgoNCgUECgIABRIEwwILEQoNCgUECgIAARIEwwISHgoNCgUECgIAAxIEwwIhIgq/AgoEBAoCARIEywICKxqwAiBJZiBzZXQsIGFsbCB0aGUgY2xhc3NlcyBmcm9tIHRoZSAucHJvdG8gZmlsZSBhcmUgd3JhcHBlZCBpbiBhIHNpbmdsZQogb3V0ZXIgY2xhc3Mgd2l0aCB0aGUgZ2l2ZW4gbmFtZS4gIFRoaXMgYXBwbGllcyB0byBib3RoIFByb3RvMQogKGVxdWl2YWxlbnQgdG8gdGhlIG9sZCAiLS1vbmVfamF2YV9maWxlIiBvcHRpb24pIGFuZCBQcm90bzIgKHdoZXJlCiBhIC5wcm90byBhbHdheXMgdHJhbnNsYXRlcyB0byBhIHNpbmdsZSBjbGFzcywgYnV0IHlvdSBtYXkgd2FudCB0bwogZXhwbGljaXRseSBjaG9vc2UgdGhlIGNsYXNzIG5hbWUpLgoKDQoFBAoCAQQSBMsCAgoKDQoFBAoCAQUSBMsCCxEKDQoFBAoCAQESBMsCEiYKDQoFBAoCAQMSBMsCKSoKowMKBAQKAgISBNMCAjkalAMgSWYgc2V0IHRydWUsIHRoZW4gdGhlIEphdmEgY29kZSBnZW5lcmF0b3Igd2lsbCBnZW5lcmF0ZSBhIHNlcGFyYXRlIC5qYXZhCiBmaWxlIGZvciBlYWNoIHRvcC1sZXZlbCBtZXNzYWdlLCBlbnVtLCBhbmQgc2VydmljZSBkZWZpbmVkIGluIHRoZSAucHJvdG8KIGZpbGUuICBUaHVzLCB0aGVzZSB0eXBlcyB3aWxsICpub3QqIGJlIG5lc3RlZCBpbnNpZGUgdGhlIG91dGVyIGNsYXNzCiBuYW1lZCBieSBqYXZhX291dGVyX2NsYXNzbmFtZS4gIEhvd2V2ZXIsIHRoZSBvdXRlciBjbGFzcyB3aWxsIHN0aWxsIGJlCiBnZW5lcmF0ZWQgdG8gY29udGFpbiB0aGUgZmlsZSdzIGdldERlc2NyaXB0b3IoKSBtZXRob2QgYXMgd2VsbCBhcyBhbnkKIHRvcC1sZXZlbCBleHRlbnNpb25zIGRlZmluZWQgaW4gdGhlIGZpbGUuCgoNCgUECgICBBIE0wICCgoNCgUECgICBRIE0wILDwoNCgUECgICARIE0wIQIwoNCgUECgICAxIE0wImKAoNCgUECgICCBIE0wIpOAoNCgUECgICBxIE0wIyNwopCgQECgIDEgTWAgJFGhsgVGhpcyBvcHRpb24gZG9lcyBub3RoaW5nLgoKDQoFBAoCAwQSBNYCAgoKDQoFBAoCAwUSBNYCCw8KDQoFBAoCAwESBNYCEC0KDQoFBAoCAwMSBNYCMDIKDQoFBAoCAwgSBNYCM0QKEAoIBAoCAwjnBwASBNYCNEMKEQoJBAoCAwjnBwACEgTWAjQ+ChIKCgQKAgMI5wcAAgASBNYCND4KEwoLBAoCAwjnBwACAAESBNYCND4KEQoJBAoCAwjnBwADEgTWAj9DCuYCCgQECgIEEgTeAgI8GtcCIElmIHNldCB0cnVlLCB0aGVuIHRoZSBKYXZhMiBjb2RlIGdlbmVyYXRvciB3aWxsIGdlbmVyYXRlIGNvZGUgdGhhdAogdGhyb3dzIGFuIGV4Y2VwdGlvbiB3aGVuZXZlciBhbiBhdHRlbXB0IGlzIG1hZGUgdG8gYXNzaWduIGEgbm9uLVVURi04CiBieXRlIHNlcXVlbmNlIHRvIGEgc3RyaW5nIGZpZWxkLgogTWVzc2FnZSByZWZsZWN0aW9uIHdpbGwgZG8gdGhlIHNhbWUuCiBIb3dldmVyLCBhbiBleHRlbnNpb24gZmllbGQgc3RpbGwgYWNjZXB0cyBub24tVVRGLTggYnl0ZSBzZXF1ZW5jZXMuCiBUaGlzIG9wdGlvbiBoYXMgbm8gZWZmZWN0IG9uIHdoZW4gdXNlZCB3aXRoIHRoZSBsaXRlIHJ1bnRpbWUuCgoNCgUECgIEBBIE3gICCgoNCgUECgIEBRIE3gILDwoNCgUECgIEARIE3gIQJgoNCgUECgIEAxIE3gIpKwoNCgUECgIECBIE3gIsOwoNCgUECgIEBxIE3gI1OgpMCgQECgQAEgbiAgLnAgMaPCBHZW5lcmF0ZWQgY2xhc3NlcyBjYW4gYmUgb3B0aW1pemVkIGZvciBzcGVlZCBvciBjb2RlIHNpemUuCgoNCgUECgQAARIE4gIHEwpECgYECgQAAgASBOMCBA4iNCBHZW5lcmF0ZSBjb21wbGV0ZSBjb2RlIGZvciBwYXJzaW5nLCBzZXJpYWxpemF0aW9uLAoKDwoHBAoEAAIAARIE4wIECQoPCgcECgQAAgACEgTjAgwNCkcKBgQKBAACARIE5QIEEhoGIGV0Yy4KIi8gVXNlIFJlZmxlY3Rpb25PcHMgdG8gaW1wbGVtZW50IHRoZXNlIG1ldGhvZHMuCgoPCgcECgQAAgEBEgTlAgQNCg8KBwQKBAACAQISBOUCEBEKRwoGBAoEAAICEgTmAgQVIjcgR2VuZXJhdGUgY29kZSB1c2luZyBNZXNzYWdlTGl0ZSBhbmQgdGhlIGxpdGUgcnVudGltZS4KCg8KBwQKBAACAgESBOYCBBAKDwoHBAoEAAICAhIE5gITFAoMCgQECgIFEgToAgI5Cg0KBQQKAgUEEgToAgIKCg0KBQQKAgUGEgToAgsXCg0KBQQKAgUBEgToAhgkCg0KBQQKAgUDEgToAicoCg0KBQQKAgUIEgToAik4Cg0KBQQKAgUHEgToAjI3CuICCgQECgIGEgTvAgIiGtMCIFNldHMgdGhlIEdvIHBhY2thZ2Ugd2hlcmUgc3RydWN0cyBnZW5lcmF0ZWQgZnJvbSB0aGlzIC5wcm90byB3aWxsIGJlCiBwbGFjZWQuIElmIG9taXR0ZWQsIHRoZSBHbyBwYWNrYWdlIHdpbGwgYmUgZGVyaXZlZCBmcm9tIHRoZSBmb2xsb3dpbmc6CiAgIC0gVGhlIGJhc2VuYW1lIG9mIHRoZSBwYWNrYWdlIGltcG9ydCBwYXRoLCBpZiBwcm92aWRlZC4KICAgLSBPdGhlcndpc2UsIHRoZSBwYWNrYWdlIHN0YXRlbWVudCBpbiB0aGUgLnByb3RvIGZpbGUsIGlmIHByZXNlbnQuCiAgIC0gT3RoZXJ3aXNlLCB0aGUgYmFzZW5hbWUgb2YgdGhlIC5wcm90byBmaWxlLCB3aXRob3V0IGV4dGVuc2lvbi4KCg0KBQQKAgYEEgTvAgIKCg0KBQQKAgYFEgTvAgsRCg0KBQQKAgYBEgTvAhIcCg0KBQQKAgYDEgTvAh8hCtQECgQECgIHEgT9AgI5GsUEIFNob3VsZCBnZW5lcmljIHNlcnZpY2VzIGJlIGdlbmVyYXRlZCBpbiBlYWNoIGxhbmd1YWdlPyAgIkdlbmVyaWMiIHNlcnZpY2VzCiBhcmUgbm90IHNwZWNpZmljIHRvIGFueSBwYXJ0aWN1bGFyIFJQQyBzeXN0ZW0uICBUaGV5IGFyZSBnZW5lcmF0ZWQgYnkgdGhlCiBtYWluIGNvZGUgZ2VuZXJhdG9ycyBpbiBlYWNoIGxhbmd1YWdlICh3aXRob3V0IGFkZGl0aW9uYWwgcGx1Z2lucykuCiBHZW5lcmljIHNlcnZpY2VzIHdlcmUgdGhlIG9ubHkga2luZCBvZiBzZXJ2aWNlIGdlbmVyYXRpb24gc3VwcG9ydGVkIGJ5CiBlYXJseSB2ZXJzaW9ucyBvZiBnb29nbGUucHJvdG9idWYuCgogR2VuZXJpYyBzZXJ2aWNlcyBhcmUgbm93IGNvbnNpZGVyZWQgZGVwcmVjYXRlZCBpbiBmYXZvciBvZiB1c2luZyBwbHVnaW5zCiB0aGF0IGdlbmVyYXRlIGNvZGUgc3BlY2lmaWMgdG8geW91ciBwYXJ0aWN1bGFyIFJQQyBzeXN0ZW0uICBUaGVyZWZvcmUsCiB0aGVzZSBkZWZhdWx0IHRvIGZhbHNlLiAgT2xkIGNvZGUgd2hpY2ggZGVwZW5kcyBvbiBnZW5lcmljIHNlcnZpY2VzIHNob3VsZAogZXhwbGljaXRseSBzZXQgdGhlbSB0byB0cnVlLgoKDQoFBAoCBwQSBP0CAgoKDQoFBAoCBwUSBP0CCw8KDQoFBAoCBwESBP0CECMKDQoFBAoCBwMSBP0CJigKDQoFBAoCBwgSBP0CKTgKDQoFBAoCBwcSBP0CMjcKDAoEBAoCCBIE/gICOwoNCgUECgIIBBIE/gICCgoNCgUECgIIBRIE/gILDwoNCgUECgIIARIE/gIQJQoNCgUECgIIAxIE/gIoKgoNCgUECgIICBIE/gIrOgoNCgUECgIIBxIE/gI0OQoMCgQECgIJEgT/AgI5Cg0KBQQKAgkEEgT/AgIKCg0KBQQKAgkFEgT/AgsPCg0KBQQKAgkBEgT/AhAjCg0KBQQKAgkDEgT/AiYoCg0KBQQKAgkIEgT/Aik4Cg0KBQQKAgkHEgT/AjI3CgwKBAQKAgoSBIADAjoKDQoFBAoCCgQSBIADAgoKDQoFBAoCCgUSBIADCw8KDQoFBAoCCgESBIADECQKDQoFBAoCCgMSBIADJykKDQoFBAoCCggSBIADKjkKDQoFBAoCCgcSBIADMzgK8wEKBAQKAgsSBIYDAjAa5AEgSXMgdGhpcyBmaWxlIGRlcHJlY2F0ZWQ/CiBEZXBlbmRpbmcgb24gdGhlIHRhcmdldCBwbGF0Zm9ybSwgdGhpcyBjYW4gZW1pdCBEZXByZWNhdGVkIGFubm90YXRpb25zCiBmb3IgZXZlcnl0aGluZyBpbiB0aGUgZmlsZSwgb3IgaXQgd2lsbCBiZSBjb21wbGV0ZWx5IGlnbm9yZWQ7IGluIHRoZSB2ZXJ5CiBsZWFzdCwgdGhpcyBpcyBhIGZvcm1hbGl6YXRpb24gZm9yIGRlcHJlY2F0aW5nIGZpbGVzLgoKDQoFBAoCCwQSBIYDAgoKDQoFBAoCCwUSBIYDCw8KDQoFBAoCCwESBIYDEBoKDQoFBAoCCwMSBIYDHR8KDQoFBAoCCwgSBIYDIC8KDQoFBAoCCwcSBIYDKS4KfwoEBAoCDBIEigMCNhpxIEVuYWJsZXMgdGhlIHVzZSBvZiBhcmVuYXMgZm9yIHRoZSBwcm90byBtZXNzYWdlcyBpbiB0aGlzIGZpbGUuIFRoaXMgYXBwbGllcwogb25seSB0byBnZW5lcmF0ZWQgY2xhc3NlcyBmb3IgQysrLgoKDQoFBAoCDAQSBIoDAgoKDQoFBAoCDAUSBIoDCw8KDQoFBAoCDAESBIoDECAKDQoFBAoCDAMSBIoDIyUKDQoFBAoCDAgSBIoDJjUKDQoFBAoCDAcSBIoDLzQKkgEKBAQKAg0SBI8DAikagwEgU2V0cyB0aGUgb2JqZWN0aXZlIGMgY2xhc3MgcHJlZml4IHdoaWNoIGlzIHByZXBlbmRlZCB0byBhbGwgb2JqZWN0aXZlIGMKIGdlbmVyYXRlZCBjbGFzc2VzIGZyb20gdGhpcyAucHJvdG8uIFRoZXJlIGlzIG5vIGRlZmF1bHQuCgoNCgUECgINBBIEjwMCCgoNCgUECgINBRIEjwMLEQoNCgUECgINARIEjwMSIwoNCgUECgINAxIEjwMmKApJCgQECgIOEgSSAwIoGjsgTmFtZXNwYWNlIGZvciBnZW5lcmF0ZWQgY2xhc3NlczsgZGVmYXVsdHMgdG8gdGhlIHBhY2thZ2UuCgoNCgUECgIOBBIEkgMCCgoNCgUECgIOBRIEkgMLEQoNCgUECgIOARIEkgMSIgoNCgUECgIOAxIEkgMlJwqRAgoEBAoCDxIEmAMCJBqCAiBCeSBkZWZhdWx0IFN3aWZ0IGdlbmVyYXRvcnMgd2lsbCB0YWtlIHRoZSBwcm90byBwYWNrYWdlIGFuZCBDYW1lbENhc2UgaXQKIHJlcGxhY2luZyAnLicgd2l0aCB1bmRlcnNjb3JlIGFuZCB1c2UgdGhhdCB0byBwcmVmaXggdGhlIHR5cGVzL3N5bWJvbHMKIGRlZmluZWQuIFdoZW4gdGhpcyBvcHRpb25zIGlzIHByb3ZpZGVkLCB0aGV5IHdpbGwgdXNlIHRoaXMgdmFsdWUgaW5zdGVhZAogdG8gcHJlZml4IHRoZSB0eXBlcy9zeW1ib2xzIGRlZmluZWQuCgoNCgUECgIPBBIEmAMCCgoNCgUECgIPBRIEmAMLEQoNCgUECgIPARIEmAMSHgoNCgUECgIPAxIEmAMhIwp+CgQECgIQEgScAwIoGnAgU2V0cyB0aGUgcGhwIGNsYXNzIHByZWZpeCB3aGljaCBpcyBwcmVwZW5kZWQgdG8gYWxsIHBocCBnZW5lcmF0ZWQgY2xhc3NlcwogZnJvbSB0aGlzIC5wcm90by4gRGVmYXVsdCBpcyBlbXB0eS4KCg0KBQQKAhAEEgScAwIKCg0KBQQKAhAFEgScAwsRCg0KBQQKAhABEgScAxIiCg0KBQQKAhADEgScAyUnCr4BCgQECgIREgShAwIlGq8BIFVzZSB0aGlzIG9wdGlvbiB0byBjaGFuZ2UgdGhlIG5hbWVzcGFjZSBvZiBwaHAgZ2VuZXJhdGVkIGNsYXNzZXMuIERlZmF1bHQKIGlzIGVtcHR5LiBXaGVuIHRoaXMgb3B0aW9uIGlzIGVtcHR5LCB0aGUgcGFja2FnZSBuYW1lIHdpbGwgYmUgdXNlZCBmb3IKIGRldGVybWluaW5nIHRoZSBuYW1lc3BhY2UuCgoNCgUECgIRBBIEoQMCCgoNCgUECgIRBRIEoQMLEQoNCgUECgIRARIEoQMSHwoNCgUECgIRAxIEoQMiJAp8CgQECgISEgSlAwI6Gm4gVGhlIHBhcnNlciBzdG9yZXMgb3B0aW9ucyBpdCBkb2Vzbid0IHJlY29nbml6ZSBoZXJlLgogU2VlIHRoZSBkb2N1bWVudGF0aW9uIGZvciB0aGUgIk9wdGlvbnMiIHNlY3Rpb24gYWJvdmUuCgoNCgUECgISBBIEpQMCCgoNCgUECgISBhIEpQMLHgoNCgUECgISARIEpQMfMwoNCgUECgISAxIEpQM2OQqHAQoDBAoFEgSpAwIZGnogQ2xpZW50cyBjYW4gZGVmaW5lIGN1c3RvbSBvcHRpb25zIGluIGV4dGVuc2lvbnMgb2YgdGhpcyBtZXNzYWdlLgogU2VlIHRoZSBkb2N1bWVudGF0aW9uIGZvciB0aGUgIk9wdGlvbnMiIHNlY3Rpb24gYWJvdmUuCgoMCgQECgUAEgSpAw0YCg0KBQQKBQABEgSpAw0RCg0KBQQKBQACEgSpAxUYCgsKAwQKCRIEqwMLDgoMCgQECgkAEgSrAwsNCg0KBQQKCQABEgSrAwsNCg0KBQQKCQACEgSrAwsNCgwKAgQLEgauAwDtAwEKCwoDBAsBEgSuAwgWCtgFCgQECwIAEgTBAwI8GskFIFNldCB0cnVlIHRvIHVzZSB0aGUgb2xkIHByb3RvMSBNZXNzYWdlU2V0IHdpcmUgZm9ybWF0IGZvciBleHRlbnNpb25zLgogVGhpcyBpcyBwcm92aWRlZCBmb3IgYmFja3dhcmRzLWNvbXBhdGliaWxpdHkgd2l0aCB0aGUgTWVzc2FnZVNldCB3aXJlCiBmb3JtYXQuICBZb3Ugc2hvdWxkIG5vdCB1c2UgdGhpcyBmb3IgYW55IG90aGVyIHJlYXNvbjogIEl0J3MgbGVzcwogZWZmaWNpZW50LCBoYXMgZmV3ZXIgZmVhdHVyZXMsIGFuZCBpcyBtb3JlIGNvbXBsaWNhdGVkLgoKIFRoZSBtZXNzYWdlIG11c3QgYmUgZGVmaW5lZCBleGFjdGx5IGFzIGZvbGxvd3M6CiAgIG1lc3NhZ2UgRm9vIHsKICAgICBvcHRpb24gbWVzc2FnZV9zZXRfd2lyZV9mb3JtYXQgPSB0cnVlOwogICAgIGV4dGVuc2lvbnMgNCB0byBtYXg7CiAgIH0KIE5vdGUgdGhhdCB0aGUgbWVzc2FnZSBjYW5ub3QgaGF2ZSBhbnkgZGVmaW5lZCBmaWVsZHM7IE1lc3NhZ2VTZXRzIG9ubHkKIGhhdmUgZXh0ZW5zaW9ucy4KCiBBbGwgZXh0ZW5zaW9ucyBvZiB5b3VyIHR5cGUgbXVzdCBiZSBzaW5ndWxhciBtZXNzYWdlczsgZS5nLiB0aGV5IGNhbm5vdAogYmUgaW50MzJzLCBlbnVtcywgb3IgcmVwZWF0ZWQgbWVzc2FnZXMuCgogQmVjYXVzZSB0aGlzIGlzIGFuIG9wdGlvbiwgdGhlIGFib3ZlIHR3byByZXN0cmljdGlvbnMgYXJlIG5vdCBlbmZvcmNlZCBieQogdGhlIHByb3RvY29sIGNvbXBpbGVyLgoKDQoFBAsCAAQSBMEDAgoKDQoFBAsCAAUSBMEDCw8KDQoFBAsCAAESBMEDECcK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CiBhIG1vcmUgZWZmaWNpZW50IHJlcHJlc2VudGF0aW9uIG9uIHRoZSB3aXJlLiBSYXRoZXIgdGhhbiByZXBlYXRlZGx5CiB3cml0aW5nIHRoZSB0YWcgYW5kIHR5cGUgZm9yIGVhY2ggZWxlbWVudCwgdGhlIGVudGlyZSBhcnJheSBpcyBlbmNvZGVkIGFzCiBhIHNpbmdsZSBsZW5ndGgtZGVsaW1pdGVkIGJsb2IuIEluIHByb3RvMywgb25seSBleHBsaWNpdCBzZXR0aW5nIGl0IHRvCiBmYWxzZSB3aWxsIGF2b2lkIHVzaW5nIHBhY2tlZCBlbmNvZGluZy4KCg0KBQQMAgEEEgSCBAIKCg0KBQQMAgEFEgSCBAsPCg0KBQQMAgEBEgSCBBAWCg0KBQQMAgEDEgSCBBkaCpoFCgQEDAICEgSPBAIzGosFIFRoZSBqc3R5cGUgb3B0aW9uIGRldGVybWluZXMgdGhlIEphdmFTY3JpcHQgdHlwZSB1c2VkIGZvciB2YWx1ZXMgb2YgdGhlCiBmaWVsZC4gIFRoZSBvcHRpb24gaXMgcGVybWl0dGVkIG9ubHkgZm9yIDY0IGJpdCBpbnRlZ3JhbCBhbmQgZml4ZWQgdHlwZXMKIChpbnQ2NCwgdWludDY0LCBzaW50NjQsIGZpeGVkNjQsIHNmaXhlZDY0KS4gIEEgZmllbGQgd2l0aCBqc3R5cGUgSlNfU1RSSU5HCiBpcyByZXByZXNlbnRlZCBhcyBKYXZhU2NyaXB0IHN0cmluZywgd2hpY2ggYXZvaWRzIGxvc3Mgb2YgcHJlY2lzaW9uIHRoYXQKIGNhbiBoYXBwZW4gd2hlbiBhIGxhcmdlIHZhbHVlIGlzIGNvbnZlcnRlZCB0byBhIGZsb2F0aW5nIHBvaW50IEphdmFTY3JpcHQuCiBTcGVjaWZ5aW5nIEpTX05VTUJFUiBmb3IgdGhlIGpzdHlwZSBjYXVzZXMgdGhlIGdlbmVyYXRlZCBKYXZhU2NyaXB0IGNvZGUgdG8KIHVzZSB0aGUgSmF2YVNjcmlwdCAibnVtYmVyIiB0eXBlLiAgVGhlIGJlaGF2aW9yIG9mIHRoZSBkZWZhdWx0IG9wdGlvbgogSlNfTk9STUFMIGlzIGltcGxlbWVudGF0aW9uIGRlcGVuZGVudC4KCiBUaGlzIG9wdGlvbiBpcyBhbiBlbnVtIHRvIHBlcm1pdCBhZGRpdGlvbmFsIHR5cGVzIHRvIGJlIGFkZGVkLCBlLmcuCiBnb29nLm1hdGguSW50ZWdlci4KCg0KBQQMAgIEEgSPBAIKCg0KBQQMAgIGEgSPBAsRCg0KBQQMAgIBEgSPBBIYCg0KBQQMAgIDEgSPBBscCg0KBQQMAgIIEgSPBB0yCg0KBQQMAgIHEgSPBCgxCg4KBAQMBAESBpAEApkEAwoNCgUEDAQBARIEkAQHDQonCgYEDAQBAgASBJIEBBIaFyBVc2UgdGhlIGRlZmF1bHQgdHlwZS4KCg8KBwQMBAECAAESBJIEBA0KDwoHBAwEAQIAAhIEkgQQEQopCgYEDAQBAgESBJUEBBIaGSBVc2UgSmF2YVNjcmlwdCBzdHJpbmdzLgoKDwoHBAwEAQIBARIElQQEDQoPCgcEDAQBAgECEgSVBBARCikKBgQMBAECAhIEmAQEEhoZIFVzZSBKYXZhU2NyaXB0IG51bWJlcnMuCgoPCgcEDAQBAgIBEgSYBAQNCg8KBwQMBAECAgISBJgEEBEK7wwKBAQMAgMSBLcEAika4AwgU2hvdWxkIHRoaXMgZmllbGQgYmUgcGFyc2VkIGxhemlseT8gIExhenkgYXBwbGllcyBvbmx5IHRvIG1lc3NhZ2UtdHlwZQogZmllbGRzLiAgSXQgbWVhbnMgdGhhdCB3aGVuIHRoZSBvdXRlciBtZXNzYWdlIGlzIGluaXRpYWxseSBwYXJzZWQsIHRoZQogaW5uZXIgbWVzc2FnZSdzIGNvbnRlbnRzIHdpbGwgbm90IGJlIHBhcnNlZCBidXQgaW5zdGVhZCBzdG9yZWQgaW4gZW5jb2RlZAogZm9ybS4gIFRoZSBpbm5lciBtZXNzYWdlIHdpbGwgYWN0dWFsbHkgYmUgcGFyc2VkIHdoZW4gaXQgaXMgZmlyc3QgYWNjZXNzZWQuCgogVGhpcyBpcyBvbmx5IGEgaGludC4gIEltcGxlbWVudGF0aW9ucyBhcmUgZnJlZSB0byBjaG9vc2Ugd2hldGhlciB0byB1c2UKIGVhZ2VyIG9yIGxhenkgcGFyc2luZyByZWdhcmRsZXNzIG9mIHRoZSB2YWx1ZSBvZiB0aGlzIG9wdGlvbi4gIEhvd2V2ZXIsCiBzZXR0aW5nIHRoaXMgb3B0aW9uIHRydWUgc3VnZ2VzdHMgdGhhdCB0aGUgcHJvdG9jb2wgYXV0aG9yIGJlbGlldmVzIHRoYXQKIHVzaW5nIGxhenkgcGFyc2luZyBvbiB0aGlzIGZpZWxkIGlzIHdvcnRoIHRoZSBhZGRpdGlvbmFsIGJvb2trZWVwaW5nCiBvdmVyaGVhZCB0eXBpY2FsbHkgbmVlZGVkIHRvIGltcGxlbWVudCBpdC4KCiBUaGlzIG9wdGlvbiBkb2VzIG5vdCBhZmZlY3QgdGhlIHB1YmxpYyBpbnRlcmZhY2Ugb2YgYW55IGdlbmVyYXRlZCBjb2RlOwogYWxsIG1ldGhvZCBzaWduYXR1cmVzIHJlbWFpbiB0aGUgc2FtZS4gIEZ1cnRoZXJtb3JlLCB0aHJlYWQtc2FmZXR5IG9mIHRoZQogaW50ZXJmYWNlIGlzIG5vdCBhZmZlY3RlZCBieSB0aGlzIG9wdGlvbjsgY29uc3QgbWV0aG9kcyByZW1haW4gc2FmZSB0bwogY2FsbCBmcm9tIG11bHRpcGxlIHRocmVhZHMgY29uY3VycmVudGx5LCB3aGlsZSBub24tY29uc3QgbWV0aG9kcyBjb250aW51ZQogdG8gcmVxdWlyZSBleGNsdXNpdmUgYWNjZXNzLgoKCiBOb3RlIHRoYXQgaW1wbGVtZW50YXRpb25zIG1heSBjaG9vc2Ugbm90IHRvIGNoZWNrIHJlcXVpcmVkIGZpZWxkcyB3aXRoaW4KIGEgbGF6eSBzdWItbWVzc2FnZS4gIFRoYXQgaXMsIGNhbGxpbmcgSXNJbml0aWFsaXplZCgpIG9uIHRoZSBvdXRlciBtZXNzYWdlCiBtYXkgcmV0dXJuIHRydWUgZXZlbiBpZiB0aGUgaW5uZXIgbWVzc2FnZSBoYXMgbWlzc2luZyByZXF1aXJlZCBmaWVsZHMuCiBUaGlzIGlzIG5lY2Vzc2FyeSBiZWNhdXNlIG90aGVyd2lzZSB0aGUgaW5uZXIgbWVzc2FnZSB3b3VsZCBoYXZlIHRvIGJlCiBwYXJzZWQgaW4gb3JkZXIgdG8gcGVyZm9ybSB0aGUgY2hlY2ssIGRlZmVhdGluZyB0aGUgcHVycG9zZSBvZiBsYXp5CiBwYXJzaW5nLiAgQW4gaW1wbGVtZW50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dCByZWNvZ25pemUuIFRoaXMgb25seQogYXBwZWFycyBpbiBvcHRpb25zIHByb3RvcyBjcmVhdGVkIGJ5IHRoZSBjb21waWxlcjo6UGFyc2VyIGNsYXNzLgogRGVzY3JpcHRvclBvb2wgcmVzb2x2ZXMgdGhlc2Ugd2hlbiBidWlsZGluZyBEZXNjcmlwdG9yIG9iamVjdHMuIFRoZXJlZm9yZSwKIG9wdGlvbnMgcHJvdG9zIGluIGRlc2NyaXB0b3Igb2JqZWN0cyAoZS5nLiByZXR1cm5lZCBieSBEZXNjcmlwdG9yOjpvcHRpb25zKCksCiBvciBwcm9kdWNlZCBieSBEZXNjcmlwdG9yOjpDb3B5VG8oKSkgd2lsbCBuZXZlciBoYXZlIFVuaW50ZXJwcmV0ZWRPcHRpb25zCiBpbiB0aGVtLgoKCwoDBBIBEgSxBQgbCssCCgQEEgMAEga3BQK6BQMaugIgVGhlIG5hbWUgb2YgdGhlIHVuaW50ZXJwcmV0ZWQgb3B0aW9uLiAgRWFjaCBzdHJpbmcgcmVwcmVzZW50cyBhIHNlZ21lbnQgaW4KIGEgZG90LXNlcGFyYXRlZCBuYW1lLiAgaXNfZXh0ZW5zaW9uIGlzIHRydWUgaWZmIGEgc2VnbWVudCByZXByZXNlbnRzIGFuCiBleHRlbnNpb24gKGRlbm90ZWQgd2l0aCBwYXJlbnRoZXNlcyBpbiBvcHRpb25zIHNwZWNzIGluIC5wcm90byBmaWxlcykuCiBFLmcuLHsgWyJmb28iLCBmYWxzZV0sIFsiYmFyLmJheiIsIHRydWVdLCBbInF1eCIsIGZhbHNlXSB9IHJlcHJlc2VudHMKICJmb28uKGJhci5iYXopLnF1eCIuCgoNCgUEEgMAARIEtwUKEgoOCgYEEgMAAgASBLgFBCIKDwoHBBIDAAIABBIEuAUEDAoPCgcEEgMAAgAFEgS4BQ0TCg8KBwQSAwACAAESBLgFFB0KDwoHBBIDAAIAAxIEuAUgIQoOCgYEEgMAAgESBLkFBCMKDwoHBBIDAAIBBBIEuQUEDAoPCgcEEgMAAgEFEgS5BQ0RCg8KBwQSAwACAQESBLkFEh4KDwoHBBIDAAIBAxIEuQUhIgoMCgQEEgIAEgS7BQIdCg0KBQQSAgAEEgS7BQIKCg0KBQQSAgAGEgS7BQsTCg0KBQQSAgABEgS7BRQYCg0KBQQSAgADEgS7BRscCpwBCgQEEgIBEgS/BQInGo0BIFRoZSB2YWx1ZSBvZiB0aGUgdW5pbnRlcnByZXRlZCBvcHRpb24sIGluIHdoYXRldmVyIHR5cGUgdGhlIHRva2VuaXplcgogaWRlbnRpZmllZCBpdCBhcyBkdXJpbmcgcGFyc2luZy4gRXhhY3RseSBvbmUgb2YgdGhlc2Ugc2hvdWxkIGJlIHNldC4KCg0KBQQSAgEEEgS/BQIKCg0KBQQSAgEFEgS/BQsRCg0KBQQSAgEBEgS/BRIiCg0KBQQSAgEDEgS/BSUmCgwKBAQSAgISBMAFAikKDQoFBBICAgQSBMAFAgoKDQoFBBICAgUSBMAFCxEKDQoFBBICAgESBMAFEiQKDQoFBBICAgMSBMAFJygKDAoEBBICAxIEwQUCKAoNCgUEEgIDBBIEwQUCCgoNCgUEEgIDBRIEwQULEAoNCgUEEgIDARIEwQURIwoNCgUEEgIDAxIEwQUmJwoMCgQEEgIEEgTCBQIjCg0KBQQSAgQEEgTCBQIKCg0KBQQSAgQFEgTCBQsRCg0KBQQSAgQBEgTCBRIeCg0KBQQSAgQDEgTCBSEiCgwKBAQSAgUSBMMFAiIKDQoFBBICBQQSBMMFAgoKDQoFBBICBQUSBMMFCxAKDQoFBBICBQESBMMFER0KDQoFBBICBQMSBMMFICEKDAoEBBICBhIExAUCJgoNCgUEEgIGBBIExAUCCgoNCgUEEgIGBRIExAULEQoNCgUEEgIGARIExAUSIQoNCgUEEgIGAxIExAUkJQraAQoCBBMSBswFAM0GARpqIEVuY2Fwc3VsYXRlcyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgb3JpZ2luYWwgc291cmNlIGZpbGUgZnJvbSB3aGljaCBhCiBGaWxlRGVzY3JpcHRvclByb3RvIHdhcyBnZW5lcmF0ZWQuCjJgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIE9wdGlvbmFsIHNvdXJjZSBjb2RlIGluZm8KCgsKAwQTARIEzAUIFgqCEQoEBBMCABIE+AUCIRrzECBBIExvY2F0aW9uIGlkZW50aWZpZXMgYSBwaWVjZSBvZiBzb3VyY2UgY29kZSBpbiBhIC5wcm90byBmaWxlIHdoaWNoCiBjb3JyZXNwb25kcyB0byBhIHBhcnRpY3VsYXIgZGVmaW5pdGlvbi4gIFRoaXMgaW5mb3JtYXRpb24gaXMgaW50ZW5kZWQKIHRvIGJlIHVzZWZ1bCB0byBJREVzLCBjb2RlIGluZGV4ZXJzLCBkb2N1bWVudGF0aW9uIGdlbmVyYXRvcnMsIGFuZCBzaW1pbGFyCiB0b29scy4KCiBGb3IgZXhhbXBsZSwgc2F5IHdlIGhhdmUgYSBmaWxlIGxpa2U6CiAgIG1lc3NhZ2UgRm9vIHsKICAgICBvcHRpb25hbCBzdHJpbmcgZm9vID0gMTsKICAgfQogTGV0J3MgbG9vayBhdCBqdXN0IHRoZSBmaWVsZCBkZWZpbml0aW9uOgogICBvcHRpb25hbCBzdHJpbmcgZm9vID0gMTsKICAgXiAgICAgICBeXiAgICAgXl4gIF4gIF5eXgogICBhICAgICAgIGJjICAgICBkZSAgZiAgZ2hpCiBXZSBoYXZlIHRoZSBmb2xsb3dpbmcgbG9jYXRpb25zOgogICBzcGFuICAgcGF0aCAgICAgICAgICAgICAgIHJlcHJlc2VudHMKICAgW2EsaSkgIFsgNCwgMCwgMiwgMCBdICAgICBUaGUgd2hvbGUgZmllbGQgZGVmaW5pdGlvbi4KICAgW2EsYikgIFsgNCwgMCwgMiwgMCwgNCBdICBUaGUgbGFiZWwgKG9wdGlvbmFsKS4KICAgW2MsZCkgIFsgNCwgMCwgMiwgMCwgNSBdICBUaGUgdHlwZSAoc3RyaW5nKS4KICAgW2UsZikgIFsgNCwgMCwgMiwgMCwgMSBdICBUaGUgbmFtZSAoZm9vKS4KICAgW2csaCkgIFsgNCwgMCwgMiwgMCwgMyBdICBUaGUgbnVtYmVyICgxKS4KCiBOb3RlczoKIC0gQSBsb2NhdGlvbiBtYXkgcmVmZXIgdG8gYSByZXBlYXRlZCBmaWVsZCBpdHNlbGYgKGkuZS4gbm90IHRvIGFueQogICBwYXJ0aWN1bGFyIGluZGV4IHdpdGhpbiBpdCkuICBUaGlzIGlzIHVzZWQgd2hlbmV2ZXIgYSBzZXQgb2YgZWxlbWVudHMgYXJlCiAgIGxvZ2ljYWxseSBlbmNsb3NlZCBpbiBhIHNpbmdsZSBjb2RlIHNlZ21lbnQuICBGb3IgZXhhbXBsZSwgYW4gZW50aXJlCiAgIGV4dGVuZCBibG9jayAocG9zc2libHkgY29udGFpbmluZyBtdWx0aXBsZSBleHRlbnNpb24gZGVmaW5pdGlvbnMpIHdpbGwKICAgaGF2ZSBhbiBvdXRlciBsb2NhdGlvbiB3aG9zZSBwYXRoIHJlZmVycyB0byB0aGUgImV4dGVuc2lvbnMiIHJlcGVhdGVkCiAgIGZpZWxkIHdpdGhvdXQgYW4gaW5kZXguCiAtIE11bHRpcGxlIGxvY2F0aW9ucyBtYXkgaGF2ZSB0aGUgc2FtZSBwYXRoLiAgVGhpcyBoYXBwZW5zIHdoZW4gYSBzaW5nbGUKICAgbG9naWNhbCBkZWNsYXJhdGlvbiBpcyBzcHJlYWQgb3V0IGFjcm9zcyBtdWx0aXBsZSBwbGFjZXMuICBUaGUgbW9zdAogICBvYnZpb3VzIGV4YW1wbGUgaXMgdGhlICJleHRlbmQiIGJsb2NrIGFnYWluIC0tIHRoZXJlIG1heSBiZSBtdWx0aXBsZQogICBleHRlbmQgYmxvY2tzIGluIHRoZSBzYW1lIHNjb3BlLCBlYWNoIG9mIHdoaWNoIHdpbGwgaGF2ZSB0aGUgc2FtZSBwYXRoLgogLSBBIGxvY2F0aW9uJ3Mgc3BhbiBpcyBub3QgYWx3YXlzIGEgc3Vic2V0IG9mIGl0cyBwYXJlbnQncyBzcGFuLiAgRm9yCiAgIGV4YW1wbGUsIHRoZSAiZXh0ZW5kZWUiIG9mIGFuIGV4dGVuc2lvbiBkZWNsYXJhdGlvbiBhcHBlYXJzIGF0IHRoZQogICBiZWdpbm5pbmcgb2YgdGhlICJleHRlbmQiIGJsb2NrIGFuZCBpcyBzaGFyZWQgYnkgYWxsIGV4dGVuc2lvbnMgd2l0aGluCiAgIHRoZSBibG9jay4KIC0gSnVzdCBiZWNhdXNlIGEgbG9jYXRpb24ncyBzcGFuIGlzIGEgc3Vic2V0IG9mIHNvbWUgb3RoZXIgbG9jYXRpb24ncyBzcGFuCiAgIGRvZXMgbm90IG1lYW4gdGhhdCBpdCBpcyBhIGRlc2NlbmRlbnQuICBGb3IgZXhhbXBsZSwgYSAiZ3JvdXAiIGRlZmluZXMKICAgYm90aCBhIHR5cGUgYW5kIGEgZmllbGQgaW4gYSBzaW5nbGUgZGVjbGFyYXRpb24uICBUaHVzLCB0aGUgbG9jYXRpb25zCiAgIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHR5cGUgYW5kIGZpZWxkIGFuZCB0aGVpciBjb21wb25lbnRzIHdpbGwgb3ZlcmxhcC4KIC0gQ29kZSB3aGljaCB0cmllcyB0byBpbnRlcnByZXQgbG9jYXRpb25zIHNob3VsZCBwcm9iYWJseSBiZSBkZXNpZ25lZCB0bwogICBpZ25vcmUgdGhvc2UgdGhhdCBpdCBkb2Vzbid0IHVuZGVyc3RhbmQsIGFzIG1vcmUgdHlwZXMgb2YgbG9jYXRpb25zIGNvdWxkCiAgIGJlIHJlY29yZGVkIGluIHRoZSBmdXR1cmUuCgoNCgUEEwIABBIE+AUCCgoNCgUEEwIABhIE+AULEwoNCgUEEwIAARIE+AUUHAoNCgUEEwIAAxIE+AUfIAoOCgQEEwMAEgb5BQLMBgMKDQoFBBMDAAESBPkFChIKgwcKBgQTAwACABIEkQYEKhryBiBJZGVudGlmaWVzIHdoaWNoIHBhcnQgb2YgdGhlIEZpbGVEZXNjcmlwdG9yUHJvdG8gd2FzIGRlZmluZWQgYXQgdGhpcwogbG9jYXRpb24uCgogRWFjaCBlbGVtZW50IGlzIGEgZmllbGQgbnVtYmVyIG9yIGFuIGluZGV4LiAgVGhleSBmb3JtIGEgcGF0aCBmcm9tCiB0aGUgcm9vdCBGaWxlRGVzY3JpcHRvclByb3RvIHRvIHRoZSBwbGFjZSB3aGVyZSB0aGUgZGVmaW5pdGlvbi4gIEZvcgogZXhhbXBsZSwgdGhpcyBwYXRoOgogICBbIDQsIDMsIDIsIDcsIDEgXQogcmVmZXJzIHRvOgogICBmaWxlLm1lc3NhZ2VfdHlwZSgzKSAgLy8gNCwgMwogICAgICAgLmZpZWxkKDcpICAgICAgICAgLy8gMiwgNwogICAgICAgLm5hbWUoKSAgICAgICAgICAgLy8gMQogVGhpcyBpcyBiZWNhdXNlIEZpbGVEZXNjcmlwdG9yUHJvdG8ubWVzc2FnZV90eXBlIGhhcyBmaWVsZCBudW1iZXIgNDoKICAgcmVwZWF0ZWQgRGVzY3JpcHRvclByb3RvIG1lc3NhZ2VfdHlwZSA9IDQ7CiBhbmQgRGVzY3JpcHRvclByb3RvLmZpZWxkIGhhcyBmaWVsZCBudW1iZXIgMjoKICAgcmVwZWF0ZWQgRmllbGREZXNjcmlwdG9yUHJvdG8gZmllbGQgPSAyOwogYW5kIEZpZWxkRGVzY3JpcHRvclByb3RvLm5hbWUgaGFzIGZpZWxkIG51bWJlciAxOgogICBvcHRpb25hbCBzdHJpbmcgbmFtZSA9IDE7CgogVGh1cywgdGhlIGFib3ZlIHBhdGggZ2l2ZXMgdGhlIGxvY2F0aW9uIG9mIGEgZmllbGQgbmFtZS4gIElmIHdlIHJlbW92ZWQKIHRoZSBsYXN0IGVsZW1lbnQ6CiAgIFsgNCwgMywgMiwgNyBdCiB0aGlzIHBhdGggcmVmZXJzIHRvIHRoZSB3aG9sZSBmaWVsZCBkZWNsYXJhdGlvbiAoZnJvbSB0aGUgYmVnaW5uaW5nCiBvZiB0aGUgbGFiZWwgdG8gdGhlIHRlcm1pbmF0aW5nIHNlbWljb2xvbikuCgoPCgcEEwMAAgAEEgSRBgQMCg8KBwQTAwACAAUSBJEGDRIKDwoHBBMDAAIAARIEkQYTFwoPCgcEEwMAAgADEgSRBhobCg8KBwQTAwACAAgSBJEGHCkKEgoKBBMDAAIACOcHABIEkQYdKAoTCgsEEwMAAgAI5wcAAhIEkQYdIwoUCgwEEwMAAgAI5wcAAgASBJEGHSMKFQoNBBMDAAIACOcHAAIAARIEkQYdIwoTCgsEEwMAAgAI5wcAAxIEkQYkKArSAgoGBBMDAAIBEgSYBgQqGsECIEFsd2F5cyBoYXMgZXhhY3RseSB0aHJlZSBvciBmb3VyIGVsZW1lbnRzOiBzdGFydCBsaW5lLCBzdGFydCBjb2x1bW4sCiBlbmQgbGluZSAob3B0aW9uYWwsIG90aGVyd2lzZSBhc3N1bWVkIHNhbWUgYXMgc3RhcnQgbGluZSksIGVuZCBjb2x1bW4uCiBUaGVzZSBhcmUgcGFja2VkIGludG8gYSBzaW5nbGUgZmllbGQgZm9yIGVmZmljaWVuY3kuICBOb3RlIHRoYXQgbGluZQogYW5kIGNvbHVtbiBudW1iZXJzIGFyZSB6ZXJvLWJhc2VkIC0tIHR5cGljYWxseSB5b3Ugd2lsbCB3YW50IHRvIGFkZAogMSB0byBlYWNoIGJlZm9yZSBkaXNwbGF5aW5nIHRvIGEgdXNlci4KCg8KBwQTAwACAQQSBJgGBAwKDwoHBBMDAAIBBRIEmAYNEgoPCgcEEwMAAgEBEgSYBhMXCg8KBwQTAwACAQMSBJgGGhsKDwoHBBMDAAIBCBIEmAYcKQoSCgoEEwMAAgEI5wcAEgSYBh0oChMKCwQTAwACAQjnBwACEgSYBh0jChQKDAQTAwACAQjnBwACABIEmAYdIwoVCg0EEwMAAgEI5wcAAgABEgSYBh0jChMKCwQTAwACAQjnBwADEgSYBiQoCqUMCgYEEwMAAgISBMkGBCkalAwgSWYgdGhpcyBTb3VyY2VDb2RlSW5mbyByZXByZXNlbnRzIGEgY29tcGxldGUgZGVjbGFyYXRpb24sIHRoZXNlIGFyZSBhbnkKIGNvbW1lbnRzIGFwcGVhcmluZyBiZWZvcmUgYW5kIGFmdGVyIHRoZSBkZWNsYXJhdGlvbiB3aGljaCBhcHBlYXIgdG8gYmUKIGF0dGFjaGVkIHRvIHRoZSBkZWNsYXJhdGlvbi4KCiBBIHNlcmllcyBvZiBsaW5lIGNvbW1lbnRzIGFwcGVhcmluZyBvbiBjb25zZWN1dGl2ZSBsaW5lcywgd2l0aCBubyBvdGhlcgogdG9rZW5zIGFwcGVhcmluZyBvbiB0aG9zZSBsaW5lcywgd2lsbCBiZSB0cmVhdGVkIGFzIGEgc2luZ2xlIGNvbW1lbnQuCgogbGVhZGluZ19kZXRhY2hlZF9jb21tZW50cyB3aWxsIGtlZXAgcGFyYWdyYXBocyBvZiBjb21tZW50cyB0aGF0IGFwcGVhcgogYmVmb3JlIChidXQgbm90IGNvbm5lY3RlZCB0bykgdGhlIGN1cnJlbnQgZWxlbWVudC4gRWFjaCBwYXJhZ3JhcGgsCiBzZXBhcmF0ZWQgYnkgZW1wdHkgbGluZXMsIHdpbGwgYmUgb25lIGNvbW1lbnQgZWxlbWVudCBpbiB0aGUgcmVwZWF0ZWQKIGZpZWxkLgoKIE9ubHkgdGhlIGNvbW1lbnQgY29udGVudCBpcyBwcm92aWRlZDsgY29tbWVudCBtYXJrZXJzIChlLmcuIC8vKSBhcmUKIHN0cmlwcGVkIG91dC4gIEZvciBibG9jayBjb21tZW50cywgbGVhZGluZyB3aGl0ZXNwYWNlIGFuZCBhbiBhc3Rlcmlzawogd2lsbCBiZSBzdHJpcHBlZCBmcm9tIHRoZSBiZWdpbm5pbmcgb2YgZWFjaCBsaW5lIG90aGVyIHRoYW4gdGhlIGZpcnN0LgogTmV3bGluZXMgYXJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQuCgogRXhhbXBsZXM6CgogICBvcHRpb25hbCBpbnQzMiBmb28gPSAxOyAgLy8gQ29tbWVudCBhdHRhY2hlZCB0byBmb28uCiAgIC8vIENvbW1lbnQgYXR0YWNoZWQgdG8gYmFyLgogICBvcHRpb25hbCBpbnQzMiBiYXIgPSAyOwoKICAgb3B0aW9uYWwgc3RyaW5nIGJheiA9IDM7CiAgIC8vIENvbW1lbnQgYXR0YWNoZWQgdG8gYmF6LgogICAvLyBBbm90aGVyIGxpbmUgYXR0YWNoZWQgdG8gYmF6LgoKICAgLy8gQ29tbWVudCBhdHRhY2hlZCB0byBxdXguCiAgIC8vCiAgIC8vIEFub3RoZXIgbGluZSBhdHRhY2hlZCB0byBxdXguCiAgIG9wdGlvbmFsIGRvdWJsZSBxdXggPSA0OwoKICAgLy8gRGV0YWNoZWQgY29tbWVudCBmb3IgY29yZ2UuIFRoaXMgaXMgbm90IGxlYWRpbmcgb3IgdHJhaWxpbmcgY29tbWVudHMKICAgLy8gdG8gcXV4IG9yIGNvcmdlIGJlY2F1c2UgdGhlcmUgYXJlIGJsYW5rIGxpbmVzIHNlcGFyYXRpbmcgaXQgZnJvbQogICAvLyBib3RoLgoKICAgLy8gRGV0YWNoZWQgY29tbWVudCBmb3IgY29yZ2UgcGFyYWdyYXBoIDIuCgogICBvcHRpb25hbCBzdHJpbmcgY29yZ2UgPSA1OwogICAvKiBCbG9jayBjb21tZW50IGF0dGFjaGVkCiAgICAqIHRvIGNvcmdlLiAgTGVhZGluZyBhc3Rlcmlza3MKICAgICogd2lsbCBiZSByZW1vdmVkLiAqLwogICAvKiBCbG9jayBjb21tZW50IGF0dGFjaGVkIHRvCiAgICAqIGdyYXVsdC4gKi8KICAgb3B0aW9uYWwgaW50MzIgZ3JhdWx0ID0gNjsKCiAg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bGUucHJvdG9idWYuRW51bU9wdGlvbnMYseQDIAEoCFIRZ29wcm90b0VudW1QcmVmaXg6UgoVZ29wcm90b19lbnVtX3N0cmluZ2VyEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMXkAyABKAhSE2dvcHJvdG9FbnVtU3RyaW5nZXI6QwoNZW51bV9zdHJpbmdlchIcLmdvb2dsZS5wcm90b2J1Zi5FbnVtT3B0aW9ucxjG5AMgASgIUgxlbnVtU3RyaW5nZXI6RwoPZW51bV9jdXN0b21uYW1lEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMfkAyABKAlSDmVudW1DdXN0b21uYW1lOjoKCGVudW1kZWNsEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMjkAyABKAhSCGVudW1kZWNsOlYKFGVudW12YWx1ZV9jdXN0b21uYW1lEiEuZ29vZ2xlLnByb3RvYnVmLkVudW1WYWx1ZU9wdGlvbnMY0YMEIAEoCVITZW51bXZhbHVlQ3VzdG9tbmFtZTpOChNnb3Byb3RvX2dldHRlcnNfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJnsAyABKAhSEWdvcHJvdG9HZXR0ZXJzQWxsOlUKF2dvcHJvdG9fZW51bV9wcmVmaXhfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJrsAyABKAhSFGdvcHJvdG9FbnVtUHJlZml4QWxsOlAKFGdvcHJvdG9fc3RyaW5nZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJvsAyABKAhSEmdvcHJvdG9TdHJpbmdlckFsbDpKChF2ZXJib3NlX2VxdWFsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxic7AMgASgIUg92ZXJib3NlRXF1YWxBbGw6OQoIZmFjZV9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYnewDIAEoCFIHZmFjZUFsbDpBCgxnb3N0cmluZ19hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYnuwDIAEoCFILZ29zdHJpbmdBbGw6QQoMcG9wdWxhdGVfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJ/sAyABKAhSC3BvcHVsYXRlQWxsOkEKDHN0cmluZ2VyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxig7AMgASgIUgtzdHJpbmdlckFsbDo/Cgtvbmx5b25lX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxih7AMgASgIUgpvbmx5b25lQWxsOjsKCWVxdWFsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxil7AMgASgIUghlcXVhbEFsbDpHCg9kZXNjcmlwdGlvbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYpuwDIAEoCFIOZGVzY3JpcHRpb25BbGw6PwoLdGVzdGdlbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYp+wDIAEoCFIKdGVzdGdlbkFsbDpBCgxiZW5jaGdlbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYqOwDIAEoCFILYmVuY2hnZW5BbGw6QwoNbWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxip7AMgASgIUgxtYXJzaGFsZXJBbGw6RwoPdW5tYXJzaGFsZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGKrsAyABKAhSDnVubWFyc2hhbGVyQWxsOlAKFHN0YWJsZV9tYXJzaGFsZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGKvsAyABKAhSEnN0YWJsZU1hcnNoYWxlckFsbDo7CglzaXplcl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYrOwDIAEoCFIIc2l6ZXJBbGw6WQoZZ29wcm90b19lbnVtX3N0cmluZ2VyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxit7AMgASgIUhZnb3Byb3RvRW51bVN0cmluZ2VyQWxsOkoKEWVudW1fc3RyaW5nZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGK7sAyABKAhSD2VudW1TdHJpbmdlckFsbDpQChR1bnNhZmVfbWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxiv7AMgASgIUhJ1bnNhZmVNYXJzaGFsZXJBbGw6VAoWdW5zYWZlX3VubWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxiw7AMgASgIUhR1bnNhZmVVbm1hcnNoYWxlckFsbDpbChpnb3Byb3RvX2V4dGVuc2lvbnNfbWFwX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxix7AMgASgIUhdnb3Byb3RvRXh0ZW5zaW9uc01hcEFsbDpYChhnb3Byb3RvX3VucmVjb2duaXplZF9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYsuwDIAEoCFIWZ29wcm90b1VucmVjb2duaXplZEFsbDpJChBnb2dvcHJvdG9faW1wb3J0EhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLPsAyABKAhSD2dvZ29wcm90b0ltcG9ydDpFCg5wcm90b3NpemVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi07AMgASgIUg1wcm90b3NpemVyQWxsOj8KC2NvbXBhcmVfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLXsAyABKAhSCmNvbXBhcmVBbGw6QQoMdHlwZWRlY2xfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLbsAyABKAhSC3R5cGVkZWNsQWxsOkEKDGVudW1kZWNsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi37AMgASgIUgtlbnVtZGVjbEFsbDpRChRnb3Byb3RvX3JlZ2lzdHJhdGlvbhIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi47AMgASgIUhNnb3Byb3RvUmVnaXN0cmF0aW9uOkcKD21lc3NhZ2VuYW1lX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi57AMgASgIUg5tZXNzYWdlbmFtZUFsbDpKCg9nb3Byb3RvX2dldHRlcnMSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYgfQDIAEoCFIOZ29wcm90b0dldHRlcnM6TAoQZ29wcm90b19zdHJpbmdlchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiD9AMgASgIUg9nb3Byb3RvU3RyaW5nZXI6RgoNdmVyYm9zZV9lcXVhbBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiE9AMgASgIUgx2ZXJib3NlRXF1YWw6NQoEZmFjZRIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiF9AMgASgIUgRmYWNlOj0KCGdvc3RyaW5nEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGIb0AyABKAhSCGdvc3RyaW5nOj0KCHBvcHVsYXRlEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGIf0AyABKAhSCHBvcHVsYXRlOj0KCHN0cmluZ2VyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGMCLBCABKAhSCHN0cmluZ2VyOjsKB29ubHlvbmUSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYifQDIAEoCFIHb25seW9uZTo3CgVlcXVhbBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiN9AMgASgIUgVlcXVhbDpDCgtkZXNjcmlwdGlvbhIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiO9AMgASgIUgtkZXNjcmlwdGlvbjo7Cgd0ZXN0Z2VuEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGI/0AyABKAhSB3Rlc3RnZW46PQoIYmVuY2hnZW4SHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYkPQDIAEoCFIIYmVuY2hnZW46PwoJbWFyc2hhbGVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJH0AyABKAhSCW1hcnNoYWxlcjpDCgt1bm1hcnNoYWxlchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiS9AMgASgIUgt1bm1hcnNoYWxlcjpMChBzdGFibGVfbWFyc2hhbGVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJP0AyABKAhSD3N0YWJsZU1hcnNoYWxlcjo3CgVzaXplchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiU9AMgASgIUgVzaXplcjpMChB1bnNhZmVfbWFyc2hhbGVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJf0AyABKAhSD3Vuc2FmZU1hcnNoYWxlcjpQChJ1bnNhZmVfdW5tYXJzaGFsZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYmPQDIAEoCFIRdW5zYWZlVW5tYXJzaGFsZXI6VwoWZ29wcm90b19leHRlbnNpb25zX21hcBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiZ9AMgASgIUhRnb3Byb3RvRXh0ZW5zaW9uc01hcDpUChRnb3Byb3RvX3VucmVjb2duaXplZBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxia9AMgASgIUhNnb3Byb3RvVW5yZWNvZ25pemVkOkEKCnByb3Rvc2l6ZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYnPQDIAEoCFIKcHJvdG9zaXplcjo7Cgdjb21wYXJlEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJ30AyABKAhSB2NvbXBhcmU6PQoIdHlwZWRlY2wSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYnvQDIAEoCFIIdHlwZWRlY2w6QwoLbWVzc2FnZW5hbWUSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYofQDIAEoCFILbWVzc2FnZW5hbWU6OwoIbnVsbGFibGUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGOn7AyABKAhSCG51bGxhYmxlOjUKBWVtYmVkEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjq+wMgASgIUgVlbWJlZDo/CgpjdXN0b210eXBlEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjr+wMgASgJUgpjdXN0b210eXBlOj8KCmN1c3RvbW5hbWUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGOz7AyABKAlSCmN1c3RvbW5hbWU6OQoHanNvbnRhZxIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY7fsDIAEoCVIHanNvbnRhZzo7Cghtb3JldGFncxIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY7vsDIAEoCVIIbW9yZXRhZ3M6OwoIY2FzdHR5cGUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGO/7AyABKAlSCGNhc3R0eXBlOjkKB2Nhc3RrZXkSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGPD7AyABKAlSB2Nhc3RrZXk6PQoJY2FzdHZhbHVlEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjx+wMgASgJUgljYXN0dmFsdWU6OQoHc3RkdGltZRIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY8vsDIAEoCFIHc3RkdGltZTpBCgtzdGRkdXJhdGlvbhIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY8/sDIAEoCFILc3RkZHVyYXRpb25CRQoTY29tLmdvb2dsZS5wcm90b2J1ZkIKR29Hb1Byb3Rvc1oiZ2l0aHViLmNvbS9nb2dvL3Byb3RvYnVmL2dvZ29wcm90b0qaNQoHEgUcAIcBAQr8CgoBDBIDHAASMvEKIFByb3RvY29sIEJ1ZmZlcnMgZm9yIEdvIHdpdGggR2FkZ2V0cwoKIENvcHlyaWdodCAoYykgMjAxMywgVGhlIEdvR28gQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KIGh0dHA6Ly9naXRodWIuY29tL2dvZ28vcHJvdG9idWYKCiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUKIG1ldDoKCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodAogbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgogICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZQogY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lcgogaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZQogZGlzdHJpYnV0aW9uLgoKIFRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORCBDT05UUklCVVRPUlMKICJBUyBJUyIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IKIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFSRSBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUCiBPV05FUiBPUiBDT05UUklCVVRPUlMgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwKIFNQRUNJQUwsIEVYRU1QTEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJTkNMVURJTkcsIEJVVCBOT1QKIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLAogREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZCiBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklM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B0USBIUBCCYKCgoDB0UCEgN6ByMKCwoDB0UEEgSFAQgQCgsKAwdFBRIEhQERFQoLCgMHRQESBIUBFh0KCwoDB0UDEgSFASAlCgoKAgdGEgSGAQgqCgoKAwdGAhIDegcjCgsKAwdGBBIEhgEIEAoLCgMHRgUSBIYBERUKCwoDB0YBEgSGARYhCgsKAwdGAxIEhgEkKQqMFwosbWl4ZXIvYWRhcHRlci9tb2RlbC92MWJldGExL2V4dGVuc2lvbnMucHJvdG8SIWlzdGlvLm1peGVyLmFkYXB0ZXIubW9kZWwudjFiZXRhMRogZ29vZ2xlL3Byb3RvYnVmL2Rlc2NyaXB0b3IucHJvdG8quAEKD1RlbXBsYXRlVmFyaWV0eRIaChZURU1QTEFURV9WQVJJRVRZX0NIRUNLEAASGwoXVEVNUExBVEVfVkFSSUVUWV9SRVBPUlQQARIaChZURU1QTEFURV9WQVJJRVRZX1FVT1RBEAISKAokVEVNUExBVEVfVkFSSUVUWV9BVFRSSUJVVEVfR0VORVJBVE9SEAMSJgoiVEVNUExBVEVfVkFSSUVUWV9DSEVDS19XSVRIX09VVFBVVBAEOn4KEHRlbXBsYXRlX3ZhcmlldHkSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYr8q8IiABKA4yMi5pc3Rpby5taXhlci5hZGFwdGVyLm1vZGVsLnYxYmV0YTEuVGVtcGxhdGVWYXJpZXR5Ug90ZW1wbGF0ZVZhcmlldHk6RAoNdGVtcGxhdGVfbmFtZRIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxjQy7wiIAEoCVIMdGVtcGxhdGVOYW1lQipaKGlzdGlvLmlvL2FwaS9taXhlci9hZGFwdGVyL21vZGVsL3YxYmV0YTFK4RIKBhIEDgAyAQq/BAoBDBIDDgASMrQEIENvcHlyaWdodCAyMDE4IElzdGlvIEF1dGhvcnMKCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAIKQoICgEIEgMSAD0KCwoECOcHABIDEgA9CgwKBQjnBwACEgMSBxEKDQoGCOcHAAIAEgMSBxEKDgoHCOcHAAIAARIDEgcRCgwKBQjnBwAHEgMSEjwKCQoCAwASAxQHKQp5CgIFABIEGAAoARptIFRoZSBhdmFpbGFibGUgdmFyaWV0aWVzIG9mIHRlbXBsYXRlcywgY29udHJvbGxpbmcgdGhlIHNlbWFudGljcyBvZiB3aGF0IGFuIGFkYXB0ZXIgZG9lcyB3aXRoIGVhY2ggaW5zdGFuY2UuCgoKCgMFAAESAxgFFArHAQoEBQACABIDGwQfGrkBIE1ha2VzIHRoZSB0ZW1wbGF0ZSBhcHBsaWNhYmxlIGZvciBNaXhlcidzIGNoZWNrIGNhbGxzLiBJbnN0YW5jZXMgb2Ygc3VjaCB0ZW1wbGF0ZSBhcmUgY3JlYXRlZCBkdXJpbmcKIGNoZWNrIGNhbGxzIGluIE1peGVyIGFuZCBwYXNzZWQgdG8gdGhlIGhhbmRsZXJzIGJhc2VkIG9uIHRoZSBydWxlIGNvbmZpZ3VyYXRpb25zLgoKDAoFBQACAAESAxsEGgoMCgUFAAIAAhIDGx0eCskBCgQFAAIBEgMeBCAauwEgTWFrZXMgdGhlIHRlbXBsYXRlIGFwcGxpY2FibGUgZm9yIE1peGVyJ3MgcmVwb3J0IGNhbGxzLiBJbnN0YW5jZXMgb2Ygc3VjaCB0ZW1wbGF0ZSBhcmUgY3JlYXRlZCBkdXJpbmcKIHJlcG9ydCBjYWxscyBpbiBNaXhlciBhbmQgcGFzc2VkIHRvIHRoZSBoYW5kbGVycyBiYXNlZCBvbiB0aGUgcnVsZSBjb25maWd1cmF0aW9ucy4KCgwKBQUAAgEBEgMeBBsKDAoFBQACAQISAx4eHwrNAQoEBQACAhIDIQQfGr8BIE1ha2VzIHRoZSB0ZW1wbGF0ZSBhcHBsaWNhYmxlIGZvciBNaXhlcidzIHF1b3RhIGNhbGxzLiBJbnN0YW5jZXMgb2Ygc3VjaCB0ZW1wbGF0ZSBhcmUgY3JlYXRlZCBkdXJpbmcKIHF1b3RhIGNoZWNrIGNhbGxzIGluIE1peGVyIGFuZCBwYXNzZWQgdG8gdGhlIGhhbmRsZXJzIGJhc2VkIG9uIHRoZSBydWxlIGNvbmZpZ3VyYXRpb25zLgoKDAoFBQACAgESAyEEGgoMCgUFAAICAhIDIR0eCusBCgQFAAIDEgMkBC0a3QEgTWFrZXMgdGhlIHRlbXBsYXRlIGFwcGxpY2FibGUgZm9yIE1peGVyJ3MgYXR0cmlidXRlIGdlbmVyYXRpb24gcGhhc2UuIEluc3RhbmNlcyBvZiBzdWNoIHRlbXBsYXRlIGFyZSBjcmVhdGVkIGR1cmluZwogcHJlLXByb2Nlc3NpbmcgYXR0cmlidXRlIGdlbmVyYXRpb24gcGhhc2UgYW5kIHBhc3NlZCB0byB0aGUgaGFuZGxlcnMgYmFzZWQgb24gdGhlIHJ1bGUgY29uZmlndXJhdGlvbnMuCgoMCgUFAAIDARIDJAQoCgwKBQUAAgMCEgMkKywKugEKBAUAAgQSAycEKxqsASBNYWtlcyB0aGUgdGVtcGxhdGUgYXBwbGljYWJsZSBmb3IgTWl4ZXIncyBjaGVjayBjYWxscy4gSW5zdGFuY2VzIG9mIHN1Y2ggdGVtcGxhdGUgYXJlIGNyZWF0ZWQgZHVyaW5nCiBjaGVjayBjYWxscyBpbiBNaXhlciBhbmQgcGFzc2VkIHRvIHRoZSBoYW5kbGVycyB0aGF0IHByb2R1Y2UgdmFsdWVzLgoKDAoFBQACBAESAycEJgoMCgUFAAIEAhIDJykqCjEKAQcSBCsAMgEaJiBGaWxlIGxldmVsIG9wdGlvbnMgZm9yIHRoZSB0ZW1wbGF0ZS4KCjYKAgcAEgMtBDAaKyBSZXF1aXJlZDogb3B0aW9uIGZvciB0aGUgVGVtcGxhdGVWYXJpZXR5LgoKCgoDBwACEgMrByIKCwoDBwAEEgQtBCskCgoKAwcABhIDLQQTCgoKAwcAARIDLRQkCgoKAwcAAxIDLScvCqQBCgIHARIDMQQkGpgBIE9wdGlvbmFsOiBvcHRpb24gZm9yIHRoZSB0ZW1wbGF0ZSBuYW1lLgogSWYgbm90IHNwZWNpZmllZCwgdGhlIGxhc3Qgc2VnbWVudCBvZiB0aGUgdGVtcGxhdGUgcHJvdG8ncyBwYWNrYWdlIG5hbWUgaXMgdXNlZCB0bwogZGVyaXZlIHRoZSB0ZW1wbGF0ZSBuYW1lLgoKCgoDBwECEgMrByIKCwoDBwEEEgQxBC0wCgoKAwcBBRIDMQQKCgoKAwcBARIDMQsYCgoKAwcBAxIDMRsjYgZwcm90bzMK3CwKGWdvb2dsZS9wcm90b2J1Zi9hbnkucHJvdG8SD2dvb2dsZS5wcm90b2J1ZiI2CgNBbnkSGQoIdHlwZV91cmwYASABKAlSB3R5cGVVcmwSFAoFdmFsdWUYAiABKAxSBXZhbHVlQm8KE2NvbS5nb29nbGUucHJvdG9idWZCCEFueVByb3RvUAFaJWdpdGh1Yi5jb20vZ29sYW5nL3Byb3RvYnVmL3B0eXBlcy9hbnmiAgNHUEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlwZXNK/CoKBxIFHgCUAQEKzAwKAQwSAx4AEjLBDCBQcm90b2NvbCBCdWZmZXJzIC0gR29vZ2xlJ3MgZGF0YSBpbnRlcmNoYW5nZSBmb3JtYXQKIENvcHlyaWdodCAyMDA4IEdvb2dsZSBJbmMuICBBbGwgcmlnaHRzIHJlc2VydmVkLgogaHR0cHM6Ly9kZXZlbG9wZXJzLmdvb2dsZS5jb20vcHJvdG9jb2wtYnVmZmVycy8KCiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUKIG1ldDoKCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodAogbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgogICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZQogY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lcgogaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZQogZGlzdHJpYnV0aW9uLgogICAgICogTmVpdGhlciB0aGUgbmFtZSBvZiBHb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0cwogY29udHJpYnV0b3JzIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20KIHRoaXMgc29mdHdhcmUgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgogVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwogIkFTIElTIiBBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1QKIExJTUlURUQgVE8sIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUgogQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQKIE9XTkVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFO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cGFjayBhIG1lc3NhZ2UgaW4gR28KCiAgICAgIGZvbyA6PSAmcGIuRm9vey4uLn0KICAgICAgYW55LCBlcnIgOj0gcHR5cGVzLk1hcnNoYWxBbnkoZm9vKQogICAgICAuLi4KICAgICAgZm9vIDo9ICZwYi5Gb297fQogICAgICBpZiBlcnIgOj0gcHR5cGVzLlVubWFyc2hhbEFueShhbnksIGZvbyk7IGVyciAhPSBuaWwgewogICAgICAgIC4uLgogICAgICB9CgogVGhlIHBhY2sgbWV0aG9kcyBwcm92aWRlZCBieSBwcm90b2J1ZiBsaWJyYXJ5IHdpbGwgYnkgZGVmYXVsdCB1c2UKICd0eXBlLmdvb2dsZWFwaXMuY29tL2Z1bGwudHlwZS5uYW1lJyBhcyB0aGUgdHlwZSBVUkwgYW5kIHRoZSB1bnBhY2sKIG1ldGhvZHMgb25seSB1c2UgdGhlIGZ1bGx5IHF1YWxpZmllZCB0eXBlIG5hbWUgYWZ0ZXIgdGhlIGxhc3QgJy8nCiBpbiB0aGUgdHlwZSBVUkwsIGZvciBleGFtcGxlICJmb28uYmFyLmNvbS94L3kueiIgd2lsbCB5aWVsZCB0eXBlCiBuYW1lICJ5LnoiLgoKCiBKU09OCiA9PT09CiBUaGUgSlNPTiByZXByZXNlbnRhdGlvbiBvZiBhbiBgQW55YCB2YWx1ZSB1c2VzIHRoZSByZWd1bGFyCiByZXByZXNlbnRhdGlvbiBvZiB0aGUgZGVzZXJpYWxpemVkLCBlbWJlZGRlZCBtZXNzYWdlLCB3aXRoIGFuCiBhZGRpdGlvbmFsIGZpZWxkIGBAdHlwZWAgd2hpY2ggY29udGFpbnMgdGhlIHR5cGUgVVJMLiBFeGFtcGxlOgoKICAgICBwYWNrYWdlIGdvb2dsZS5wcm9maWxlOwogICAgIG1lc3NhZ2UgUGVyc29uIHsKICAgICAgIHN0cmluZyBmaXJzdF9uYW1lID0gMTsKICAgICAgIHN0cmluZyBsYXN0X25hbWUgPSAyOwogICAgIH0KCiAgICAgewogICAgICAgIkB0eXBlIjogInR5cGUuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLnByb2ZpbGUuUGVyc29uIiwKICAgICAgICJmaXJzdE5hbWUiOiA8c3RyaW5nPiwKICAgICAgICJsYXN0TmFtZSI6IDxzdHJpbmc+CiAgICAgfQoKIElmIHRoZSBlbWJlZGRlZCBtZXNzYWdlIHR5cGUgaXMgd2VsbC1rbm93biBhbmQgaGFzIGEgY3VzdG9tIEpTT04KIHJlcHJlc2VudGF0aW9uLCB0aGF0IHJlcHJlc2VudGF0aW9uIHdpbGwgYmUgZW1iZWRkZWQgYWRkaW5nIGEgZmllbGQKIGB2YWx1ZWAgd2hpY2ggaG9sZHMgdGhlIGN1c3RvbSBKU09OIGluIGFkZGl0aW9uIHRvIHRoZSBgQHR5cGVgCiBmaWVsZC4gRXhhbXBsZSAoZm9yIG1lc3NhZ2UgW2dvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbl1bXSk6CgogICAgIHsKICAgICAgICJAdHlwZSI6ICJ0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbiIsCiAgICAgICAidmFsdWUiOiAiMS4yMTJzIgogICAgIH0KCgoKCgMEAAESA3kICwrkBwoEBAACABIEkAECFhrVByBBIFVSTC9yZXNvdXJjZSBuYW1lIHdob3NlIGNvbnRlbnQgZGVzY3JpYmVzIHRoZSB0eXBlIG9mIHRoZQogc2VyaWFsaXplZCBwcm90b2NvbCBidWZmZXIgbWVzc2FnZS4KCiBGb3IgVVJMcyB3aGljaCB1c2UgdGhlIHNjaGVtZSBgaHR0cGAsIGBodHRwc2AsIG9yIG5vIHNjaGVtZSwgdGhlCiBmb2xsb3dpbmcgcmVzdHJpY3Rpb25zIGFuZCBpbnRlcnByZXRhdGlvbnMgYXBwbHk6CgogKiBJZiBubyBzY2hlbWUgaXMgcHJvdmlkZWQsIGBodHRwc2AgaXMgYXNzdW1lZC4KICogVGhlIGxhc3Qgc2VnbWVudCBvZiB0aGUgVVJMJ3MgcGF0aCBtdXN0IHJlcHJlc2VudCB0aGUgZnVsbHkKICAgcXVhbGlmaWVkIG5hbWUgb2YgdGhlIHR5cGUgKGFzIGluIGBwYXRoL2dvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbmApLgogICBUaGUgbmFtZSBzaG91bGQgYmUgaW4gYSBjYW5vbmljYWwgZm9ybSAoZS5nLiwgbGVhZGluZyAiLiIgaXMKICAgbm90IGFjY2VwdGVkKS4KICogQW4gSFRUUCBHRVQgb24gdGhlIFVSTCBtdXN0IHlpZWxkIGEgW2dvb2dsZS5wcm90b2J1Zi5UeXBlXVtdCiAgIHZhbHVlIGluIGJpbmFyeSBmb3JtYXQsIG9yIHByb2R1Y2UgYW4gZXJyb3IuCiAqIEFwcGxpY2F0aW9ucyBhcmUgYWxsb3dlZCB0byBjYWNoZSBsb29rdXAgcmVzdWx0cyBiYXNlZCBvbiB0aGUKICAgVVJMLCBvciBoYXZlIHRoZW0gcHJlY29tcGlsZWQgaW50byBhIGJpbmFyeSB0byBhdm9pZCBhbnkKICAgbG9va3VwLiBUaGVyZWZvcmUsIGJpbmFyeSBjb21wYXRpYmlsaXR5IG5lZWRzIHRvIGJlIHByZXNlcnZlZAogICBvbiBjaGFuZ2VzIHRvIHR5cGVzLiAoVXNlIHZlcnNpb25lZCB0eXBlIG5hbWVzIHRvIG1hbmFnZQogICBicmVha2luZyBjaGFuZ2VzLikKCiBTY2hlbWVzIG90aGVyIHRoYW4gYGh0dHBgLCBgaHR0cHNgIChvciB0aGUgZW1wdHkgc2NoZW1lKSBtaWdodCBiZQogdXNlZCB3aXRoIGltcGxlbWVudGF0aW9uIHNwZWNpZmljIHNlbWFudGljcy4KCgoOCgUEAAIABBIFkAECeQ0KDQoFBAACAAUSBJABAggKDQoFBAACAAESBJABCREKDQoFBAACAAMSBJABFBUKVwoEBAACARIEkwECEhpJIE11c3QgYmUgYSB2YWxpZCBzZXJpYWxpemVkIHByb3RvY29sIGJ1ZmZlciBvZiB0aGUgYWJvdmUgc3BlY2lmaWVkIHR5cGUuCgoPCgUEAAIBBBIGkwECkAEWCg0KBQQAAgEFEgSTAQIHCg0KBQQAAgEBEgSTAQgNCg0KBQQAAgEDEgSTARARYgZwcm90bzMKngkKKG1peGVyL2FkYXB0ZXIvbW9kZWwvdjFiZXRhMS9yZXBvcnQucHJvdG8SIWlzdGlvLm1peGVyLmFkYXB0ZXIubW9kZWwudjFiZXRhMRoUZ29nb3Byb3RvL2dvZ28ucHJvdG8iDgoMUmVwb3J0UmVzdWx0QjZaKGlzdGlvLmlvL2FwaS9taXhlci9hZGFwdGVyL21vZGVsL3YxYmV0YTHI4R4AqOIeAPDhHgBK6AcKBhIEDgAbFwq/BAoBDBIDDgASMrQEIENvcHlyaWdodCAyMDE4IElzdGlvIEF1dGhvcnMKCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAIKQoICgEIEgMSAD0KCwoECOcHABIDEgA9CgwKBQjnBwACEgMSBxEKDQoGCOcHAAIAEgMSBxEKDgoHCOcHAAIAARIDEgcRCgwKBQjnBwAHEgMSEjwKCQoCAwASAxQHHQoICgEIEgMWAC8KCwoECOcHARIDFgAvCgwKBQjnBwECEgMWByYKDQoGCOcHAQIAEgMWByYKDgoHCOcHAQIAARIDFgglCgwKBQjnBwEDEgMWKS4KCAoBCBIDFwAlCgsKBAjnBwISAxcAJQoMCgUI5wcCAhIDFwccCg0KBgjnBwICABIDFwccCg4KBwjnBwICAAESAxcIGwoMCgUI5wcCAxIDFx8kCggKAQgSAxgAKAoLCgQI5wcDEgMYACgKDAoFCOcHAwISAxgHHwoNCgYI5wcDAgASAxgHHwoOCgcI5wcDAgABEgMYCB4KDAoFCOcHAwMSAxgiJwozCgIEABIDGwAXGiggRXhwcmVzc2VzIHRoZSByZXN1bHQgb2YgYSByZXBvcnQgY2FsbC4KCgoKAwQAARIDGwgUYgZwcm90bzMK1BAKH3BvbGljeS92MWJldGExL3ZhbHVlX3R5cGUucHJvdG8SFGlzdGlvLnBvbGljeS52MWJldGExKrsBCglWYWx1ZVR5cGUSGgoWVkFMVUVfVFlQRV9VTlNQRUNJRklFRBAAEgoKBlNUUklORxABEgkKBUlOVDY0EAISCgoGRE9VQkxFEAMSCAoEQk9PTBAEEg0KCVRJTUVTVEFNUBAFEg4KCklQX0FERFJFU1MQBhIRCg1FTUFJTF9BRERSRVNTEAcSBwoDVVJJEAgSDAoIRE5TX05BTUUQCRIMCghEVVJBVElPThAKEg4KClNUUklOR19NQVAQC0IdWhtpc3Rpby5pby9hcGkvcG9saWN5L3YxYmV0YTFKtQ4KBhIEDgA8AQq/BAoBDBIDDgASMrQEIENvcHlyaWdodCAyMDE4IElzdGlvIEF1dGhvcnMKCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vu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cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAogbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZQogbWV0OgoKICAgICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0CiBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlCiBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyCiBpbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlCiBkaXN0cmlidXRpb24uCiAgICAgKiBOZWl0aGVyIHRoZSBuYW1lIG9mIEdvb2dsZSBJbmMuIG5vciB0aGUgbmFtZXMgb2YgaXRzCiBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbQogdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi4KCiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTCiAiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVAogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SCiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVAogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsCiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwKIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWQogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAogKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFCiBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLgoKCAoBAhIDIAgXCggKAQgSAyIAOwoLCgQI5wcAEgMiADsKDAoFCOcHAAISAyIHFwoNCgYI5wcAAgASAyIHFwoOCgcI5wcAAgABEgMiBxcKDAoFCOcHAAcSAyIaOgoICgEIEgMjAB8KCwoECOcHARIDIwAfCgwKBQjnBwECEgMjBxcKDQoGCOcHAQIAEgMjBxcKDgoHCOcHAQIAARIDIwcXCgwKBQjnBwEDEgMjGh4KCAoBCBIDJABBCgsKBAjnBwISAyQAQQoMCgUI5wcCAhIDJAcRCg0KBgjnBwICABIDJAcRCg4KBwjnBwICAAESAyQHEQoMCgUI5wcCBxIDJBRACggKAQgSAyUALAoLCgQI5wcDEgMlACwKDAoFCOcHAwISAyUHEwoNCgYI5wcDAgASAyUHEwoOCgcI5wcDAgABEgMlBxMKDAoFCOcHAwcSAyUWKwoICgEIEgMmAC4KCwoECOcHBBIDJgAuCgwKBQjnBwQCEgMmBxsKDQoGCOcHBAIAEgMmBxsKDgoHCOcHBAIAARIDJgcbCgwKBQjnBwQHEgMmHi0KCAoBCBIDJwAiCgsKBAjnBwUSAycAIgoMCgUI5wcFAhIDJwcaCg0KBgjnBwUCABIDJwcaCg4KBwjnBwUCAAESAycHGgoMCgUI5wcFAxIDJx0hCggKAQgSAygAIQoLCgQI5wcGEgMoACEKDAoFCOcHBgISAygHGAoNCgYI5wcGAgASAygHGAoOCgcI5wcGAgABEgMoBxgKDAoFCOcHBgcSAygbIAqfEAoCBAASBGYAdAEakhAgQSBEdXJhdGlvbiByZXByZXNlbnRzIGEgc2lnbmVkLCBmaXhlZC1sZW5ndGggc3BhbiBvZiB0aW1lIHJlcHJlc2VudGVkCiBhcyBhIGNvdW50IG9mIHNlY29uZHMgYW5kIGZyYWN0aW9ucyBvZiBzZWNvbmRzIGF0IG5hbm9zZWNvbmQKIHJlc29sdXRpb24uIEl0IGlzIGluZGVwZW5kZW50IG9mIGFueSBjYWxlbmRhciBhbmQgY29uY2VwdHMgbGlrZSAiZGF5Igogb3IgIm1vbnRoIi4gSXQgaXMgcmVsYXRlZCB0byBUaW1lc3RhbXAgaW4gdGhhdCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuCiB0d28gVGltZXN0YW1wIHZhbHVlcyBpcyBhIER1cmF0aW9uIGFuZCBpdCBjYW4gYmUgYWRkZWQgb3Igc3VidHJhY3RlZAogZnJvbSBhIFRpbWVzdGFtcC4gUmFuZ2UgaXMgYXBwcm94aW1hdGVseSArLTEwLDAwMCB5ZWFycy4KCiAjIEV4YW1wbGVzCgogRXhhbXBsZSAxOiBDb21wdXRlIER1cmF0aW9uIGZyb20gdHdvIFRpbWVzdGFtcHMgaW4gcHNldWRvIGNvZGUuCgogICAgIFRpbWVzdGFtcCBzdGFydCA9IC4uLjsKICAgICBUaW1lc3RhbXAgZW5kID0gLi4uOwogICAgIER1cmF0aW9uIGR1cmF0aW9uID0gLi4uOwoKICAgICBkdXJhdGlvbi5zZWNvbmRzID0gZW5kLnNlY29uZHMgLSBzdGFydC5zZWNvbmRzOwogICAgIGR1cmF0aW9uLm5hbm9zID0gZW5kLm5hbm9zIC0gc3RhcnQubmFub3M7CgogICAgIGlmIChkdXJhdGlvbi5zZWNvbmRzIDwgMCAmJiBkdXJhdGlvbi5uYW5vcyA+IDApIHsKICAgICAgIGR1cmF0aW9uLnNlY29uZHMgKz0gMTsKICAgICAgIGR1cmF0aW9uLm5hbm9zIC09IDEwMDAwMDAwMDA7CiAgICAgfSBlbHNlIGlmIChkdXJhdGlvbnMuc2Vjb25kcyA+IDAgJiYgZHVyYXRpb24ubmFub3MgPCAwKSB7CiAgICAgICBkdXJhdGlvbi5zZWNvbmRzIC09IDE7CiAgICAgICBkdXJhdGlvbi5uYW5vcyArPSAxMDAwMDAwMDAwOwogICAgIH0KCiBFeGFtcGxlIDI6IENvbXB1dGUgVGltZXN0YW1wIGZyb20gVGltZXN0YW1wICsgRHVyYXRpb24gaW4gcHNldWRvIGNvZGUuCgogICAgIFRpbWVzdGFtcCBzdGFydCA9IC4uLjsKICAgICBEdXJhdGlvbiBkdXJhdGlvbiA9IC4uLjsKICAgICBUaW1lc3RhbXAgZW5kID0gLi4uOwoKICAgICBlbmQuc2Vjb25kcyA9IHN0YXJ0LnNlY29uZHMgKyBkdXJhdGlvbi5zZWNvbmRzOwogICAgIGVuZC5uYW5vcyA9IHN0YXJ0Lm5hbm9zICsgZHVyYXRpb24ubmFub3M7CgogICAgIGlmIChlbmQubmFub3MgPCAwKSB7CiAgICAgICBlbmQuc2Vjb25kcyAtPSAxOwogICAgICAgZW5kLm5hbm9zICs9IDEwMDAwMDAwMDA7CiAgICAgfSBlbHNlIGlmIChlbmQubmFub3MgPj0gMTAwMDAwMDAwMCkgewogICAgICAgZW5kLnNlY29uZHMgKz0gMTsKICAgICAgIGVuZC5uYW5vcyAtPSAxMDAwMDAwMDAwOwogICAgIH0KCiBFeGFtcGxlIDM6IENvbXB1dGUgRHVyYXRpb24gZnJvbSBkYXRldGltZS50aW1lZGVsdGEgaW4gUHl0aG9uLgoKICAgICB0ZCA9IGRhdGV0aW1lLnRpbWVkZWx0YShkYXlzPTMsIG1pbnV0ZXM9MTApCiAgICAgZHVyYXRpb24gPSBEdXJhdGlvbigpCiAgICAgZHVyYXRpb24uRnJvbVRpbWVkZWx0YSh0ZCkKCiAjIEpTT04gTWFwcGluZwoKIEluIEpTT04gZm9ybWF0LCB0aGUgRHVyYXRpb24gdHlwZSBpcyBlbmNvZGVkIGFzIGEgc3RyaW5nIHJhdGhlciB0aGFuIGFuCiBvYmplY3QsIHdoZXJlIHRoZSBzdHJpbmcgZW5kcyBpbiB0aGUgc3VmZml4ICJzIiAoaW5kaWNhdGluZyBzZWNvbmRzKSBhbmQKIGlzIHByZWNlZGVkIGJ5IHRoZSBudW1iZXIgb2Ygc2Vjb25kcywgd2l0aCBuYW5vc2Vjb25kcyBleHByZXNzZWQgYXMKIGZyYWN0aW9uYWwgc2Vjb25kcy4gRm9yIGV4YW1wbGUsIDMgc2Vjb25kcyB3aXRoIDAgbmFub3NlY29uZHMgc2hvdWxkIGJlCiBlbmNvZGVkIGluIEpTT04gZm9ybWF0IGFzICIzcyIsIHdoaWxlIDMgc2Vjb25kcyBhbmQgMSBuYW5vc2Vjb25kIHNob3VsZAogYmUgZXhwcmVzc2VkIGluIEpTT04gZm9ybWF0IGFzICIzLjAwMDAwMDAwMXMiLCBhbmQgMyBzZWNvbmRzIGFuZCAxCiBtaWNyb3NlY29uZCBzaG91bGQgYmUgZXhwcmVzc2VkIGluIEpTT04gZm9ybWF0IGFzICIzLjAwMDAwMXMiLgoKCgoKCgMEAAESA2YIEArcAQoEBAACABIDawIUGs4BIFNpZ25lZCBzZWNvbmRzIG9mIHRoZSBzcGFuIG9mIHRpbWUuIE11c3QgYmUgZnJvbSAtMzE1LDU3NiwwMDAsMDAwCiB0byArMzE1LDU3NiwwMDAsMDAwIGluY2x1c2l2ZS4gTm90ZTogdGhlc2UgYm91bmRzIGFyZSBjb21wdXRlZCBmcm9tOgogNjAgc2VjL21pbiAqIDYwIG1pbi9ociAqIDI0IGhyL2RheSAqIDM2NS4yNSBkYXlzL3llYXIgKiAxMDAwMCB5ZWFycwoKDQoFBAACAAQSBGsCZhIKDAoFBAACAAUSA2sCBwoMCgUEAAIAARIDawgPCgwKBQQAAgADEgNrEhMKgwMKBAQAAgESA3MCEhr1AiBTaWduZWQgZnJhY3Rpb25zIG9mIGEgc2Vjb25kIGF0IG5hbm9zZWNvbmQgcmVzb2x1dGlvbiBvZiB0aGUgc3Bhbgogb2YgdGltZS4gRHVyYXRpb25zIGxlc3MgdGhhbiBvbmUgc2Vjb25kIGFyZSByZXByZXNlbnRlZCB3aXRoIGEgMAogYHNlY29uZHNgIGZpZWxkIGFuZCBhIHBvc2l0aXZlIG9yIG5lZ2F0aXZlIGBuYW5vc2AgZmllbGQuIEZvciBkdXJhdGlvbnMKIG9mIG9uZSBzZWNvbmQgb3IgbW9yZSwgYSBub24temVybyB2YWx1ZSBmb3IgdGhlIGBuYW5vc2AgZmllbGQgbXVzdCBiZQogb2YgdGhlIHNhbWUgc2lnbiBhcyB0aGUgYHNlY29uZHNgIGZpZWxkLiBNdXN0IGJlIGZyb20gLTk5OSw5OTksOTk5CiB0byArOTk5LDk5OSw5OTkgaW5jbHVzaXZlLgoKDQoFBAACAQQSBHMCaxQKDAoFBAACAQUSA3MCBwoMCgUEAAIBARIDcwgNCgwKBQQAAgEDEgNzEBFiBnByb3RvMwrDMQofZ29vZ2xlL3Byb3RvYnVmL3RpbWVzdGFtcC5wcm90bxIPZ29vZ2xlLnByb3RvYnVmIjsKCVRpbWVzdGFtcBIYCgdzZWNvbmRzGAEgASgDUgdzZWNvbmRzEhQKBW5hbm9zGAIgASgFUgVuYW5vc0J+ChNjb20uZ29vZ2xlLnByb3RvYnVmQg5UaW1lc3RhbXBQcm90b1ABWitnaXRodWIuY29tL2dvbGFuZy9wcm90b2J1Zi9wdHlwZXMvdGltZXN0YW1w+AEBogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93blR5cGVzSskvCgcSBR4AhAEBCswMCgEMEgMeABIywQwgUHJvdG9jb2wgQnVmZmVycyAtIEdvb2dsZSdzIGRhdGEgaW50ZXJjaGFuZ2UgZm9ybWF0CiBDb3B5cmlnaHQgMjAwOCBHb29nbGUgSW5jLiAgQWxsIHJpZ2h0cyByZXNlcnZlZC4KIGh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL3Byb3RvY29sLWJ1ZmZlcnMvCgogUmVkaXN0cmlidXRpb24gYW5kIHVzZSBpbiBzb3VyY2UgYW5kIGJpbmFyeSBmb3Jtcywgd2l0aCBvciB3aXRob3V0CiBtb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlCiBtZXQ6CgogICAgICogUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHQKIG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci4KICAgICAqIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUKIGNvcHlyaWdodCBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIKIGluIHRoZSBkb2N1bWVudGF0aW9uIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWQgd2l0aCB0aGUKIGRpc3RyaWJ1dGlvbi4KICAgICAqIE5laXRoZXIgdGhlIG5hbWUgb2YgR29vZ2xlIEluYy4gbm9yIHRoZSBuYW1lcyBvZiBpdHMKIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0byBlbmRvcnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZCBmcm9tCiB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKIFRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORCBDT05UUklCVVRPUlMKICJBUyBJUyIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IKIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFSRSBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUCiBPV05FUiBPUiBDT05UUklCVVRPUlMgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwKIFNQRUNJQUwsIEVYRU1QTEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJTkNMVURJTkcsIEJVVCBOT1QKIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLAogREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZCiBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUCiAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRIRSBVU0UKIE9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCgoICgECEgMgCBcKCAoBCBIDIgA7CgsKBAjnBwASAyIAOwoMCgUI5wcAAhIDIgcXCg0KBgjnBwACABIDIgcXCg4KBwjnBwACAAESAyIHFwoMCgUI5wcABxIDIho6CggKAQgSAyMAHwoLCgQI5wcBEgMjAB8KDAoFCOcHAQISAyMHFwoNCgYI5wcBAgASAyMHFwoOCgcI5wcBAgABEgMjBxcKDAoFCOcHAQMSAyMaHgoICgEIEgMkAEIKCwoECOcHAhIDJABCCgwKBQjnBwICEgMkBxEKDQoGCOcHAgIAEgMkBxEKDgoHCOcHAgIAARIDJAcRCgwKBQjnBwIHEgMkFEEKCAoBCBIDJQAsCgsKBAjnBwMSAyUALAoMCgUI5wcDAhIDJQcTCg0KBgjnBwMCABIDJQcTCg4KBwjnBwMCAAESAyUHEwoMCgUI5wcDBxIDJRYrCggKAQgSAyYALwoLCgQI5wcEEgMmAC8KDAoFCOcHBAISAyYHGwoNCgYI5wcEAgASAyYHGwoOCgcI5wcEAgABEgMmBxsKDAoFCOcHBAcSAyYeLgoICgEIEgMnACIKCwoECOcHBRIDJwAiCgwKBQjnBwUCEgMnBxoKDQoGCOcHBQIAEgMnBxoKDgoHCOcHBQIAARIDJwcaCgwKBQjnBwUDEgMnHSEKCAoBCBIDKAAhCgsKBAjnBwYSAygAIQoMCgUI5wcGAhIDKAcYCg0KBgjnBwYCABIDKAcYCg4KBwjnBwYCAAESAygHGAoMCgUI5wcGBxIDKBsgCp0aCgIEABIFeACEAQEajxogQSBUaW1lc3RhbXAgcmVwcmVzZW50cyBhIHBvaW50IGluIHRpbWUgaW5kZXBlbmRlbnQgb2YgYW55IHRpbWUgem9uZQogb3IgY2FsZW5kYXIsIHJlcHJlc2VudGVkIGFzIHNlY29uZHMgYW5kIGZyYWN0aW9ucyBvZiBzZWNvbmRzIGF0CiBuYW5vc2Vjb25kIHJlc29sdXRpb24gaW4gVVRDIEVwb2NoIHRpbWUuIEl0IGlzIGVuY29kZWQgdXNpbmcgdGhlCiBQcm9sZXB0aWMgR3JlZ29yaWFuIENhbGVuZGFyIHdoaWNoIGV4dGVuZHMgdGhlIEdyZWdvcmlhbiBjYWxlbmRhcgogYmFja3dhcmRzIHRvIHllYXIgb25lLiBJdCBpcyBlbmNvZGVkIGFzc3VtaW5nIGFsbCBtaW51dGVzIGFyZSA2MAogc2Vjb25kcyBsb25nLCBpLmUuIGxlYXAgc2Vjb25kcyBhcmUgInNtZWFyZWQiIHNvIHRoYXQgbm8gbGVhcCBzZWNvbmQKIHRhYmxlIGlzIG5lZWRlZCBmb3IgaW50ZXJwcmV0YXRpb24uIFJhbmdlIGlzIGZyb20KIDAwMDEtMDEtMDFUMDA6MDA6MDBaIHRvIDk5OTktMTItMzFUMjM6NTk6NTkuOTk5OTk5OTk5Wi4KIEJ5IHJlc3RyaWN0aW5nIHRvIHRoYXQgcmFuZ2UsIHdlIGVuc3VyZSB0aGF0IHdlIGNhbiBjb252ZXJ0IHRvCiBhbmQgZnJvbSAgUkZDIDMzMzkgZGF0ZSBzdHJpbmdzLgogU2VlIFtodHRwczovL3d3dy5pZXRmLm9yZy9yZmMvcmZjMzMzOS50eHRdKGh0dHBzOi8vd3d3LmlldGYub3JnL3JmYy9yZmMzMzM5LnR4dCkuCgogIyBFeGFtcGxlcwoKIEV4YW1wbGUgMTogQ29tcHV0ZSBUaW1lc3RhbXAgZnJvbSBQT1NJWCBgdGltZSgpYC4KCiAgICAgVGltZXN0YW1wIHRpbWVzdGFtcDsKICAgICB0aW1lc3RhbXAuc2V0X3NlY29uZHModGltZShOVUxMKSk7CiAgICAgdGltZXN0YW1wLnNldF9uYW5vcygwKTsKCiBFeGFtcGxlIDI6IENvbXB1dGUgVGltZXN0YW1wIGZyb20gUE9TSVggYGdldHRpbWVvZmRheSgpYC4KCiAgICAgc3RydWN0IHRpbWV2YWwgdHY7CiAgICAgZ2V0dGltZW9mZGF5KCZ0diwgTlVMTCk7CgogICAgIFRpbWVzdGFtcCB0aW1lc3RhbXA7CiAgICAgdGltZXN0YW1wLnNldF9zZWNvbmRzKHR2LnR2X3NlYyk7CiAgICAgdGltZXN0YW1wLnNldF9uYW5vcyh0di50dl91c2VjICogMTAwMCk7CgogRXhhbXBsZSAzOiBDb21wdXRlIFRpbWVzdGFtcCBmcm9tIFdpbjMyIGBHZXRTeXN0ZW1UaW1lQXNGaWxlVGltZSgpYC4KCiAgICAgRklMRVRJTUUgZnQ7CiAgICAgR2V0U3lzdGVtVGltZUFzRmlsZVRpbWUoJmZ0KTsKICAgICBVSU5UNjQgdGlja3MgPSAoKChVSU5UNjQpZnQuZHdIaWdoRGF0ZVRpbWUpIDw8IDMyKSB8IGZ0LmR3TG93RGF0ZVRpbWU7CgogICAgIC8vIEEgV2luZG93cyB0aWNrIGlzIDEwMCBuYW5vc2Vjb25kcy4gV2luZG93cyBlcG9jaCAxNjAxLTAxLTAxVDAwOjAwOjAwWgogICAgIC8vIGlzIDExNjQ0NDczNjAwIHNlY29uZHMgYmVmb3JlIFVuaXggZXBvY2ggMTk3MC0wMS0wMVQwMDowMDowMFouCiAgICAgVGltZXN0YW1wIHRpbWVzdGFtcDsKICAgICB0aW1lc3RhbXAuc2V0X3NlY29uZHMoKElOVDY0KSAoKHRpY2tzIC8gMTAwMDAwMDApIC0gMTE2NDQ0NzM2MDBMTCkpOwogICAgIHRpbWVzdGFtcC5zZXRfbmFub3MoKElOVDMyKSAoKHRpY2tzICUgMTAwMDAwMDApICogMTAwKSk7CgogRXhhbXBsZSA0OiBDb21wdXRlIFRpbWVzdGFtcCBmcm9tIEphdmEgYFN5c3RlbS5jdXJyZW50VGltZU1pbGxpcygpYC4KCiAgICAgbG9uZyBtaWxsaXMgPSBTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKTsKCiAgICAgVGltZXN0YW1wIHRpbWVzdGFtcCA9IFRpbWVzdGFtcC5uZXdCdWlsZGVyKCkuc2V0U2Vjb25kcyhtaWxsaXMgLyAxMDAwKQogICAgICAgICAuc2V0TmFub3MoKGludCkgKChtaWxsaXMgJSAxMDAwKSAqIDEwMDAwMDApKS5idWlsZCgpOwoKCiBFeGFtcGxlIDU6IENvbXB1dGUgVGltZXN0YW1wIGZyb20gY3VycmVudCB0aW1lIGluIFB5dGhvbi4KCiAgICAgdGltZXN0YW1wID0gVGltZXN0YW1wKCkKICAgICB0aW1lc3RhbXAuR2V0Q3VycmVudFRpbWUoKQoKICMgSlNPTiBNYXBwaW5nCgogSW4gSlNPTiBmb3JtYXQsIHRoZSBUaW1lc3RhbXAgdHlwZSBpcyBlbmNvZGVkIGFzIGEgc3RyaW5nIGluIHRoZQogW1JGQyAzMzM5XShodHRwczovL3d3dy5pZXRmLm9yZy9yZmMvcmZjMzMzOS50eHQpIGZvcm1hdC4gVGhhdCBpcywgdGhlCiBmb3JtYXQgaXMgInt5ZWFyfS17bW9udGh9LXtkYXl9VHtob3VyfTp7bWlufTp7c2VjfVsue2ZyYWNfc2VjfV1aIgogd2hlcmUge3llYXJ9IGlzIGFsd2F5cyBleHByZXNzZWQgdXNpbmcgZm91ciBkaWdpdHMgd2hpbGUge21vbnRofSwge2RheX0sCiB7aG91cn0sIHttaW59LCBhbmQge3NlY30gYXJlIHplcm8tcGFkZGVkIHRvIHR3byBkaWdpdHMgZWFjaC4gVGhlIGZyYWN0aW9uYWwKIHNlY29uZHMsIHdoaWNoIGNhbiBnbyB1cCB0byA5IGRpZ2l0cyAoaS5lLiB1cCB0byAxIG5hbm9zZWNvbmQgcmVzb2x1dGlvbiksCiBhcmUgb3B0aW9uYWwuIFRoZSAiWiIgc3VmZml4IGluZGljYXRlcyB0aGUgdGltZXpvbmUgKCJVVEMiKTsgdGhlIHRpbWV6b25lCiBpcyByZXF1aXJlZCwgdGhvdWdoIG9ubHkgVVRDIChhcyBpbmRpY2F0ZWQgYnkgIloiKSBpcyBwcmVzZW50bHkgc3VwcG9ydGVkLgoKIEZvciBleGFtcGxlLCAiMjAxNy0wMS0xNVQwMTozMDoxNS4wMVoiIGVuY29kZXMgMTUuMDEgc2Vjb25kcyBwYXN0CiAwMTozMCBVVEMgb24gSmFudWFyeSAxNSwgMjAxNy4KCiBJbiBKYXZhU2NyaXB0LCBvbmUgY2FuIGNvbnZlcnQgYSBEYXRlIG9iamVjdCB0byB0aGlzIGZvcm1hdCB1c2luZyB0aGUKIHN0YW5kYXJkIFt0b0lTT1N0cmluZygpXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9KYXZhU2NyaXB0L1JlZmVyZW5jZS9HbG9iYWxfT2JqZWN0cy9EYXRlL3RvSVNPU3RyaW5nXQogbWV0aG9kLiBJbiBQeXRob24sIGEgc3RhbmRhcmQgYGRhdGV0aW1lLmRhdGV0aW1lYCBvYmplY3QgY2FuIGJlIGNvbnZlcnRlZAogdG8gdGhpcyBmb3JtYXQgdXNpbmcgW2BzdHJmdGltZWBdKGh0dHBzOi8vZG9jcy5weXRob24ub3JnLzIvbGlicmFyeS90aW1lLmh0bWwjdGltZS5zdHJmdGltZSkKIHdpdGggdGhlIHRpbWUgZm9ybWF0IHNwZWMgJyVZLSVtLSVkVCVIOiVNOiVTLiVmWicuIExpa2V3aXNlLCBpbiBKYXZhLCBvbmUKIGNhbiB1c2UgdGhlIEpvZGEgVGltZSdzIFtgSVNPRGF0ZVRpbWVGb3JtYXQuZGF0ZVRpbWUoKWBdKAogaHR0cDovL3d3dy5qb2RhLm9yZy9qb2RhLXRpbWUvYXBpZG9jcy9vcmcvam9kYS90aW1lL2Zvcm1hdC9JU09EYXRlVGltZUZvcm1hdC5odG1sI2RhdGVUaW1lLS0pCiB0byBvYnRhaW4gYSBmb3JtYXR0ZXIgY2FwYWJsZSBvZiBnZW5lcmF0aW5nIHRpbWVzdGFtcHMgaW4gdGhpcyBmb3JtYXQuCgoKCgoKAwQAARIDeAgRCpwBCgQEAAIAEgN9AhQajgEgUmVwcmVzZW50cyBzZWNvbmRzIG9mIFVUQyB0aW1lIHNpbmNlIFVuaXggZXBvY2gKIDE5NzAtMDEtMDFUMDA6MDA6MDBaLiBNdXN0IGJlIGZyb20gMDAwMS0wMS0wMVQwMDowMDowMFogdG8KIDk5OTktMTItMzFUMjM6NTk6NTlaIGluY2x1c2l2ZS4KCg0KBQQAAgAEEgR9AngTCgwKBQQAAgAFEgN9AgcKDAoFBAACAAESA30IDwoMCgUEAAIAAxIDfRITCuUBCgQEAAIBEgSDAQISGtYBIE5vbi1uZWdhdGl2ZSBmcmFjdGlvbnMgb2YgYSBzZWNvbmQgYXQgbmFub3NlY29uZCByZXNvbHV0aW9uLiBOZWdhdGl2ZQogc2Vjb25kIHZhbHVlcyB3aXRoIGZyYWN0aW9ucyBtdXN0IHN0aWxsIGhhdmUgbm9uLW5lZ2F0aXZlIG5hbm9zIHZhbHVlcwogdGhhdCBjb3VudCBmb3J3YXJkIGluIHRpbWUuIE11c3QgYmUgZnJvbSAwIHRvIDk5OSw5OTksOTk5CiBpbmNsdXNpdmUuCgoOCgUEAAIBBBIFgwECfRQKDQoFBAACAQUSBIMBAgcKDQoFBAACAQESBIMBCA0KDQoFBAACAQMSBIMBEBFiBnByb3RvMwrSMQoZcG9saWN5L3YxYmV0YTEvdHlwZS5wcm90bxIUaXN0aW8ucG9saWN5LnYxYmV0YTEaHmdvb2dsZS9wcm90b2J1Zi9kdXJhdGlvbi5wcm90bxofZ29vZ2xlL3Byb3RvYnVmL3RpbWVzdGFtcC5wcm90byLXBAoFVmFsdWUSIwoMc3RyaW5nX3ZhbHVlGAEgASgJSABSC3N0cmluZ1ZhbHVlEiEKC2ludDY0X3ZhbHVlGAIgASgDSABSCmludDY0VmFsdWUSIwoMZG91YmxlX3ZhbHVlGAMgASgBSABSC2RvdWJsZVZhbHVlEh8KCmJvb2xfdmFsdWUYBCABKAhIAFIJYm9vbFZhbHVlEksKEGlwX2FkZHJlc3NfdmFsdWUYBSABKAsyHy5pc3Rpby5wb2xpY3kudjFiZXRhMS5JUEFkZHJlc3NIAFIOaXBBZGRyZXNzVmFsdWUSSgoPdGltZXN0YW1wX3ZhbHVlGAYgASgLMh8uaXN0aW8ucG9saWN5LnYxYmV0YTEuVGltZVN0YW1wSABSDnRpbWVzdGFtcFZhbHVlEkcKDmR1cmF0aW9uX3ZhbHVlGAcgASgLMh4uaXN0aW8ucG9saWN5LnYxYmV0YTEuRHVyYXRpb25IAFINZHVyYXRpb25WYWx1ZRJUChNlbWFpbF9hZGRyZXNzX3ZhbHVlGAggASgLMiIuaXN0aW8ucG9saWN5LnYxYmV0YTEuRW1haWxBZGRyZXNzSABSEWVtYWlsQWRkcmVzc1ZhbHVlEkUKDmRuc19uYW1lX3ZhbHVlGAkgASgLMh0uaXN0aW8ucG9saWN5LnYxYmV0YTEuRE5TTmFtZUgAUgxkbnNOYW1lVmFsdWUSOAoJdXJpX3ZhbHVlGAogASgLMhkuaXN0aW8ucG9saWN5LnYxYmV0YTEuVXJpSABSCHVyaVZhbHVlQgcKBXZhbHVlIiEKCUlQQWRkcmVzcxIUCgV2YWx1ZRgBIAEoDFIFdmFsdWUiOwoIRHVyYXRpb24SLwoFdmFsdWUYASABKAsyGS5nb29nbGUucHJvdG9idWYuRHVyYXRpb25SBXZhbHVlIj0KCVRpbWVTdGFtcBIwCgV2YWx1ZRgBIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBSBXZhbHVlIh8KB0ROU05hbWUSFAoFdmFsdWUYASABKAlSBXZhbHVlIiQKDEVtYWlsQWRkcmVzcxIUCgV2YWx1ZRgBIAEoCVIFdmFsdWUiGwoDVXJpEhQKBXZhbHVlGAEgASgJUgV2YWx1ZUIdWhtpc3Rpby5pby9hcGkvcG9saWN5L3YxYmV0YTFK2SkKBxIFDgCDAQEKvwQKAQwSAw4AEjK0BCBDb3B5cmlnaHQgMjAxOCBJc3RpbyBBdXRob3JzCgogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKCiAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCgogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLgogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgqoAgoBAhIDFQgcGk4gRGVzY3JpYmVzIHRoZSBydWxlcyB1c2VkIHRvIGNvbmZpZ3VyZSBNaXhlcidzIHBvbGljeSBhbmQgdGVsZW1ldHJ5IGZlYXR1cmVzLgoyzQEgJHRpdGxlOiBSdWxlcwogJGRlc2NyaXB0aW9uOiBEZXNjcmliZXMgdGhlIHJ1bGVzIHVzZWQgdG8gY29uZmlndXJlIE1peGVyJ3MgcG9saWN5IGFuZCB0ZWxlbWV0cnkgZmVhdHVyZXMuCiAkbG9jYXRpb246IGh0dHBzOi8vaXN0aW8uaW8vZG9jcy9yZWZlcmVuY2UvY29uZmlnL3BvbGljeS1hbmQtdGVsZW1ldHJ5L2lzdGlvLnBvbGljeS52MWJldGExLmh0bWwKCggKAQgSAxcAMAoLCgQI5wcAEgMXADAKDAoFCOcHAAISAxcHEQoNCgYI5wcAAgASAxcHEQoOCgcI5wcAAgABEgMXBxEKDAoFCOcHAAcSAxcSLwoJCgIDABIDGQcnCgkKAgMBEgMaBygKyAYKAgQAEgQlAEUBGrsGIEFuIGluc3RhbmNlIGZpZWxkIG9mIHR5cGUgVmFsdWUgZGVub3RlcyB0aGF0IHRoZSBleHByZXNzaW9uIGZvciB0aGUgZmllbGQgaXMgb2YgZHluYW1pYyB0eXBlIGFuZCBjYW4gZXZhbHVhdGUgdG8gYW55CiBbVmFsdWVUeXBlXVtpc3Rpby5wb2xpY3kudjFiZXRhMS5WYWx1ZVR5cGVdIGVudW0gdmFsdWVzLiBGb3IgZXhhbXBsZSwgd2hlbgogYXV0aG9yaW5nIGFuIGluc3RhbmNlIGNvbmZpZ3VyYXRpb24gZm9yIGEgdGVtcGxhdGUgdGhhdCBoYXMgYSBmaWVsZCBgZGF0YWAgb2YgdHlwZSBgaXN0aW8ucG9saWN5LnYxYmV0YTEuVmFsdWVgLAog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b2RlZCBhcyBieXRlcy4KCg0KBQQBAgAEEgROBEwTCgwKBQQBAgAFEgNOBAkKDAoFBAECAAESA04KDwoMCgUEAQIAAxIDThITCqQCCgIEAhIEVgBZARqXAiBBbiBpbnN0YW5jZSBmaWVsZCBvZiB0eXBlIER1cmF0aW9uIGRlbm90ZXMgdGhhdCB0aGUgZXhwcmVzc2lvbiBmb3IgdGhlIGZpZWxkIG11c3QgZXZhbHVhdGUgdG8KIFtWYWx1ZVR5cGUuRFVSQVRJT05dW2lzdGlvLnBvbGljeS52MWJldGExLlZhbHVlVHlwZS5EVVJBVElPTl0KCiBPYmplY3RzIG9mIHR5cGUgRHVyYXRpb24gYXJlIGFsc28gcGFzc2VkIHRvIHRoZSBhZGFwdGVycyBkdXJpbmcgcmVxdWVzdC10aW1lIGZvciB0aGUgaW5zdGFuY2UgZmllbGRzIG9mCiB0eXBlIER1cmF0aW9uCgoKCgMEAgESA1YIEAo8CgQEAgIAEgNYBCcaLyBEdXJhdGlvbiBlbmNvZGVkIGFzIGdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbi4KCg0KBQQCAgAEEgRYBFYSCgwKBQQCAgAGEgNYBBwKDAoFBAICAAESA1gdIgoMCgUEAgIAAxIDWCUmCqkCCgIEAxIEYABjARqcAiBBbiBpbnN0YW5jZSBmaWVsZCBvZiB0eXBlIFRpbWVTdGFtcCBkZW5vdGVzIHRoYXQgdGhlIGV4cHJlc3Npb24gZm9yIHRoZSBmaWVsZCBtdXN0IGV2YWx1YXRlIHRvCiBbVmFsdWVUeXBlLlRJTUVTVEFNUF1baXN0aW8ucG9saWN5LnYxYmV0YTEuVmFsdWVUeXBlLlRJTUVTVEFNUF0KCiBPYmplY3RzIG9mIHR5cGUgVGltZVN0YW1wIGFyZSBhbHNvIHBhc3NlZCB0byB0aGUgYWRhcHRlcnMgZHVyaW5nIHJlcXVlc3QtdGltZSBmb3IgdGhlIGluc3RhbmNlIGZpZWxkcyBvZgogdHlwZSBUaW1lU3RhbXAKCgoKAwQDARIDYAgRCj4KBAQDAgASA2IEKBoxIFRpbWVTdGFtcCBlbmNvZGVkIGFzIGdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAuCgoNCgUEAwIABBIEYgRgEwoMCgUEAwIABhIDYgQdCgwKBQQDAgABEgNiHiMKDAoFBAMCAAMSA2ImJwqhAgoCBAQSBGoAbQEalAIgQW4gaW5zdGFuY2UgZmllbGQgb2YgdHlwZSBETlNOYW1lIGRlbm90ZXMgdGhhdCB0aGUgZXhwcmVzc2lvbiBmb3IgdGhlIGZpZWxkIG11c3QgZXZhbHVhdGUgdG8KIFtWYWx1ZVR5cGUuRE5TX05BTUVdW2lzdGlvLnBvbGljeS52MWJldGExLlZhbHVlVHlwZS5ETlNfTkFNRV0KCiBPYmplY3RzIG9mIHR5cGUgRE5TTmFtZSBhcmUgYWxzbyBwYXNzZWQgdG8gdGhlIGFkYXB0ZXJzIGR1cmluZyByZXF1ZXN0LXRpbWUgZm9yIHRoZSBpbnN0YW5jZSBmaWVsZHMgb2YKIHR5cGUgRE5TTmFtZQoKCgoDBAQBEgNqCA8KKQoEBAQCABIDbAQVGhwgRE5TTmFtZSBlbmNvZGVkIGFzIHN0cmluZy4KCg0KBQQEAgAEEgRsBGoRCgwKBQQEAgAFEgNsBAoKDAoFBAQCAAESA2wLEAoMCgUEBAIAAxIDbBMUCtsCCgIEBRIEdQB4ARrOAiBETyBOT1QgVVNFICEhIFVuZGVyIERldmVsb3BtZW50CiBBbiBpbnN0YW5jZSBmaWVsZCBvZiB0eXBlIEVtYWlsQWRkcmVzcyBkZW5vdGVzIHRoYXQgdGhlIGV4cHJlc3Npb24gZm9yIHRoZSBmaWVsZCBtdXN0IGV2YWx1YXRlIHRvCiBbVmFsdWVUeXBlLkVNQUlMX0FERFJFU1NdW2lzdGlvLnBvbGljeS52MWJldGExLlZhbHVlVHlwZS5FTUFJTF9BRERSRVNTXQoKIE9iamVjdHMgb2YgdHlwZSBFbWFpbEFkZHJlc3MgYXJlIGFsc28gcGFzc2VkIHRvIHRoZSBhZGFwdGVycyBkdXJpbmcgcmVxdWVzdC10aW1lIGZvciB0aGUgaW5zdGFuY2UgZmllbGRzIG9mCiB0eXBlIEVtYWlsQWRkcmVzcwoKCgoDBAUBEgN1CBQKLgoEBAUCABIDdwQVGiEgRW1haWxBZGRyZXNzIGVuY29kZWQgYXMgc3RyaW5nLgoKDQoFBAUCAAQSBHcEdRYKDAoFBAUCAAUSA3cECgoMCgUEBQIAARIDdwsQCgwKBQQFAgADEgN3ExQKrgIKAgQGEgaAAQCDAQEanwIgRE8gTk9UIFVTRSAhISBVbmRlciBEZXZlbG9wbWVudAogQW4gaW5zdGFuY2UgZmllbGQgb2YgdHlwZSBVcmkgZGVub3RlcyB0aGF0IHRoZSBleHByZXNzaW9uIGZvciB0aGUgZmllbGQgbXVzdCBldmFsdWF0ZSB0bwogW1ZhbHVlVHlwZS5VUkldW2lzdGlvLnBvbGljeS52MWJldGExLlZhbHVlVHlwZS5VUkldCgogT2JqZWN0cyBvZiB0eXBlIFVyaSBhcmUgYWxzbyBwYXNzZWQgdG8gdGhlIGFkYXB0ZXJzIGR1cmluZyByZXF1ZXN0LXRpbWUgZm9yIHRoZSBpbnN0YW5jZSBmaWVsZHMgb2YKIHR5cGUgVXJpCgoLCgMEBgESBIABCAsKJgoEBAYCABIEggEEFRoYIFVyaSBlbmNvZGVkIGFzIHN0cmluZy4KCg8KBQQGAgAEEgaCAQSAAQ0KDQoFBAYCAAUSBIIBBAoKDQoFBAYCAAESBIIBCxAKDQoFBAYCAAMSBIIBExRiBnByb3RvMwreQgo0bWl4ZXIvdGVtcGxhdGUvbWV0cmljL3RlbXBsYXRlX2hhbmRsZXJfc2VydmljZS5wcm90bxIGbWV0cmljGhRnb2dvcHJvdG8vZ29nby5wcm90bxosbWl4ZXIvYWRhcHRlci9tb2RlbC92MWJldGExL2V4dGVuc2lvbnMucHJvdG8aGWdvb2dsZS9wcm90b2J1Zi9hbnkucHJvdG8aKG1peGVyL2FkYXB0ZXIvbW9kZWwvdjFiZXRhMS9yZXBvcnQucHJvdG8aH3BvbGljeS92MWJldGExL3ZhbHVlX3R5cGUucHJvdG8aGXBvbGljeS92MWJldGExL3R5cGUucHJvdG8ioAEKE0hhbmRsZU1ldHJpY1JlcXVlc3QSMQoJaW5zdGFuY2VzGAEgAygLMhMubWV0cmljLkluc3RhbmNlTXNnUglpbnN0YW5jZXMSOwoOYWRhcHRlcl9jb25maWcYAiABKAsyFC5nb29nbGUucHJvdG9idWYuQW55Ug1hZGFwdGVyQ29uZmlnEhkKCGRlZHVwX2lkGAMgASgJUgdkZWR1cElkIpcECgtJbnN0YW5jZU1zZxIVCgRuYW1lGK/KvCIgASgJUgRuYW1lEjEKBXZhbHVlGAEgASgLMhsuaXN0aW8ucG9saWN5LnYxYmV0YTEuVmFsdWVSBXZhbHVlEkMKCmRpbWVuc2lvbnMYAiADKAsyIy5tZXRyaWMuSW5zdGFuY2VNc2cuRGltZW5zaW9uc0VudHJ5UgpkaW1lbnNpb25zEjYKF21vbml0b3JlZF9yZXNvdXJjZV90eXBlGAMgASgJUhVtb25pdG9yZWRSZXNvdXJjZVR5cGUSeAodbW9uaXRvcmVkX3Jlc291cmNlX2RpbWVuc2lvbnMYBCADKAsyNC5tZXRyaWMuSW5zdGFuY2VNc2cuTW9uaXRvcmVkUmVzb3VyY2VEaW1lbnNpb25zRW50cnlSG21vbml0b3JlZFJlc291cmNlRGltZW5zaW9ucxpaCg9EaW1lbnNpb25zRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSMQoFdmFsdWUYAiABKAsyGy5pc3Rpby5wb2xpY3kudjFiZXRhMS5WYWx1ZVIFdmFsdWU6AjgBGmsKIE1vbml0b3JlZFJlc291cmNlRGltZW5zaW9uc0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EjEKBXZhbHVlGAIgASgLMhsuaXN0aW8ucG9saWN5LnYxYmV0YTEuVmFsdWVSBXZhbHVlOgI4ASK/AwoEVHlwZRI1CgV2YWx1ZRgBIAEoDjIfLmlzdGlvLnBvbGljeS52MWJldGExLlZhbHVlVHlwZVIFdmFsdWUSPAoKZGltZW5zaW9ucxgCIAMoCzIcLm1ldHJpYy5UeXBlLkRpbWVuc2lvbnNFbnRyeVIKZGltZW5zaW9ucxJxCh1tb25pdG9yZWRfcmVzb3VyY2VfZGltZW5zaW9ucxgEIAMoCzItLm1ldHJpYy5UeXBlLk1vbml0b3JlZFJlc291cmNlRGltZW5zaW9uc0VudHJ5Uhttb25pdG9yZWRSZXNvdXJjZURpbWVuc2lvbnMaXgoPRGltZW5zaW9uc0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EjUKBXZhbHVlGAIgASgOMh8uaXN0aW8ucG9saWN5LnYxYmV0YTEuVmFsdWVUeXBlUgV2YWx1ZToCOAEabwogTW9uaXRvcmVkUmVzb3VyY2VEaW1lbnNpb25zRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSNQoFdmFsdWUYAiABKA4yHy5pc3Rpby5wb2xpY3kudjFiZXRhMS5WYWx1ZVR5cGVSBXZhbHVlOgI4ASKvAwoNSW5zdGFuY2VQYXJhbRIUCgV2YWx1ZRgBIAEoCVIFdmFsdWUSRQoKZGltZW5zaW9ucxgCIAMoCzIlLm1ldHJpYy5JbnN0YW5jZVBhcmFtLkRpbWVuc2lvbnNFbnRyeVIKZGltZW5zaW9ucxI2Chdtb25pdG9yZWRfcmVzb3VyY2VfdHlwZRgDIAEoCVIVbW9uaXRvcmVkUmVzb3VyY2VUeXBlEnoKHW1vbml0b3JlZF9yZXNvdXJjZV9kaW1lbnNpb25zGAQgAygLMjYubWV0cmljLkluc3RhbmNlUGFyYW0uTW9uaXRvcmVkUmVzb3VyY2VEaW1lbnNpb25zRW50cnlSG21vbml0b3JlZFJlc291cmNlRGltZW5zaW9ucxo9Cg9EaW1lbnNpb25zRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbHVlOgI4ARpOCiBNb25pdG9yZWRSZXNvdXJjZURpbWVuc2lvbnNFbnRyeRIQCgNrZXkYASABKAlSA2tleRIUCgV2YWx1ZRgCIAEoCVIFdmFsdWU6AjgBMnMKE0hhbmRsZU1ldHJpY1NlcnZpY2USXAoMSGFuZGxlTWV0cmljEhsubWV0cmljLkhhbmRsZU1ldHJpY1JlcXVlc3QaLy5pc3Rpby5taXhlci5hZGFwdGVyLm1vZGVsLnYxYmV0YTEuUmVwb3J0UmVzdWx0Qh740uSTAgGC3eSTAgZtZXRyaWPI4R4AqOIeAPDhHgBKijMKBxIFEACZAQEK8gQKAQwSAxAAEjK0BCBDb3B5cmlnaHQgMjAxNyBJc3RpbyBBdXRob3JzCgogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKCiAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCgogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLgogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCjIxIFRISVMgRklMRSBJUyBBVVRPTUFUSUNBTExZIEdFTkVSQVRFRCBCWSBNSVhHRU4uCgroCAoBAhIDLQgOGt0IIFRoZSBgbWV0cmljYCB0ZW1wbGF0ZSBpcyBkZXNpZ25lZCB0byBsZXQgeW91IGRlc2NyaWJlIHJ1bnRpbWUgbWV0cmljIHRvIGRpc3BhdGNoIHRvCiBtb25pdG9yaW5nIGJhY2tlbmRzLgogCiBFeGFtcGxlIGNvbmZpZzoKIAogYGBgeWFtbAogYXBpVmVyc2lvbjogImNvbmZpZy5pc3Rpby5pby92MWFscGhhMiIKIGtpbmQ6IG1ldHJpYwogbWV0YWRhdGE6CiAgIG5hbWU6IHJlcXVlc3RzaXplCiAgIG5hbWVzcGFjZTogaXN0aW8tc3lzdGVtCiBzcGVjOgogICB2YWx1ZTogcmVxdWVzdC5zaXplIHwgMAogICBkaW1lbnNpb25zOgogICAgIHNvdXJjZV92ZXJzaW9uOiBzb3VyY2UubGFiZWxzWyJ2ZXJzaW9uIl0gfCAidW5rbm93biIKICAgICBkZXN0aW5hdGlvbl9zZXJ2aWNlOiBkZXN0aW5hdGlvbi5zZXJ2aWNlLmhvc3QgfCAidW5rbm93biIKICAgICBkZXN0aW5hdGlvbl92ZXJzaW9uOiBkZXN0aW5hdGlvbi5sYWJlbHNbInZlcnNpb24iXSB8ICJ1bmtub3duIgogICAgIHJlc3BvbnNlX2NvZGU6IHJlc3BvbnNlLmNv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dXJpbmcgc2Vzc2lvbiBjcmVhdGlvbiB0aHJvdWdoIFtJbmZyYXN0cnVjdHVyZUJhY2tlbmQuQ3JlYXRlU2Vzc2lvbl1bVE9ETzogTGluayB0byB0aGlzIGZyYWdtZW50XQogY2FsbC4gSW4gdGhhdCBjYXNlLCBhZGFwdGVyX2NvbmZpZyB3aWxsIGhhdmUgdHlwZV91cmwgYXMgJ2dvb2dsZS5wcm90b2J1Zi5BbnkudHlwZV91cmwnIGFuZCB3b3VsZCBjb250YWluIHN0cmluZwogdmFsdWUgb2Ygc2Vzc2lvbl9pZCAocmV0dXJuZWQgZnJvbSBJbmZyYXN0cnVjdHVyZUJhY2tlbmQuQ3JlYXRlU2Vzc2lvbikuCgoNCgUEAAIBBBIEUQRJJwoMCgUEAAIBBhIDUQQXCgwKBQQAAgEBEgNRGCYKDAoFBAACAQMSA1EpKgo6CgQEAAICEgNUBBgaLSBJZCB0byBkZWR1cGUgaWRlbnRpY2FsIHJlcXVlc3RzIGZyb20gTWl4ZXIuCgoNCgUEAAICBBIEVARRKwoMCgUEAAICBRIDVAQKCgwKBQQAAgIBEgNUCxMKDAoFBAACAgMSA1QWFwqoAQoCBAESBF0AcgEamwEgQ29udGFpbnMgaW5zdGFuY2UgcGF5bG9hZCBmb3IgJ21ldHJpYycgdGVtcGxhdGUuIFRoaXMgaXMgcGFzc2VkIHRvIGluZnJhc3RydWN0dXJlIGJhY2tlbmRzIGR1cmluZyByZXF1ZXN0LXRpbWUKIHRocm91Z2ggSGFuZGxlTWV0cmljU2VydmljZS5IYW5kbGVNZXRyaWMuCgoKCgMEAQESA10IEwpCCgQEAQIAEgNgBBsaNSBOYW1lIG9mIHRoZSBpbnN0YW5jZSBhcyBzcGVjaWZpZWQgaW4gY29uZmlndXJhdGlvbi4KCg0KBQQBAgAEEgRgBF0VCgwKBQQBAgAFEgNgBAoKDAoFBAECAAESA2ALDwoMCgUEAQIAAxIDYBIaCigKBAQBAgESA2MEKRobIFRoZSB2YWx1ZSBiZWluZyByZXBvcnRlZC4KCg0KBQQBAgEEEgRjBGAbCgwKBQQBAgEGEgNjBB4KDAoFBAECAQESA2MfJAoMCgUEAQIBAxIDYycoCkYKBAQBAgISA2YEOxo5IFRoZSB1bmlxdWUgaWRlbnRpdHkgb2YgdGhlIHBhcnRpY3VsYXIgbWV0cmljIHRvIHJlcG9ydC4KCg0KBQQBAgIEEgRmBGMpCgwKBQQBAgIGEgNmBCsKDAoFBAECAgESA2YsNgoMCgUEAQICAxIDZjk6CpUCCgQEAQIDEgNrBCcahwIgT3B0aW9uYWwuIEFuIGV4cHJlc3Npb24gdG8gY29tcHV0ZSB0aGUgdHlwZSBvZiB0aGUgbW9uaXRvcmVkIHJlc291cmNlIHRoaXMgbWV0cmljIGlzIGJlaW5nIHJlcG9ydGVkIG9uLgogSWYgdGhlIG1ldHJpYyBiYWNrZW5kIHN1cHBvcnRzIG1vbml0b3JlZCByZXNvdXJjZXMsIHRoZXNlIGZpZWxkcyBhcmUgdXNlZCB0byBwb3B1bGF0ZSB0aGF0IHJlc291cmNlLiBPdGhlcndpc2UKIHRoZXNlIGZpZWxkcyB3aWxsIGJlIGlnbm9yZWQgYnkgdGhlIGFkYXB0ZXIuCgoNCgUEAQIDBBIEawRmOwoMCgUEAQIDBRIDawQKCgwKBQQBAgMBEgNrCyIKDAoFBAECAwMSA2slJgqmAgoEBAECBBIDcAROGpgCIE9wdGlvbmFsLiBBIHNldCBvZiBleHByZXNzaW9ucyB0aGF0IHdpbGwgZm9ybSB0aGUgZGltZW5zaW9ucyBvZiB0aGUgbW9uaXRvcmVkIHJlc291cmNlIHRoaXMgbWV0cmljIGlzIGJlaW5nIHJlcG9ydGVkIG9uLgogSWYgdGhlIG1ldHJpYyBiYWNrZW5kIHN1cHBvcnRzIG1vbml0b3JlZCByZXNvdXJjZXMsIHRoZXNlIGZpZWxkcyBhcmUgdXNlZCB0byBwb3B1bGF0ZSB0aGF0IHJlc291cmNlLiBPdGhlcndpc2UKIHRoZXNlIGZpZWxkcyB3aWxsIGJlIGlnbm9yZWQgYnkgdGhlIGFkYXB0ZXIuCgoNCgUEAQIEBBIEcARrJwoMCgUEAQIEBhIDcAQrCgwKBQQBAgQBEgNwLEkKDAoFBAECBAMSA3BMTQrxAQoCBAISBXYAgwEBGuMBIENvbnRhaW5zIGluZmVycmVkIHR5cGUgaW5mb3JtYXRpb24gYWJvdXQgc3BlY2lmaWMgaW5zdGFuY2Ugb2YgJ21ldHJpYycgdGVtcGxhdGUuIFRoaXMgaXMgcGFzc2VkIHRvCiBpbmZyYXN0cnVjdHVyZSBiYWNrZW5kcyBkdXJpbmcgY29uZmlndXJhdGlvbi10aW1lIHRocm91Z2ggW0luZnJhc3RydWN0dXJlQmFja2VuZC5DcmVhdGVTZXNzaW9uXVtUT0RPOiBMaW5rIHRvIHRoaXMgZnJhZ21lbnRdLgoKCgoDBAIBEgN2CAwKKAoEBAICABIDeQQtGhsgVGhlIHZhbHVlIGJlaW5nIHJlcG9ydGVkLgoKDQoFBAICAAQSBHkEdg4KDAoFBAICAAYSA3kEIgoMCgUEAgIAARIDeSMoCgwKBQQCAgADEgN5KywKRgoEBAICARIDfAQ/GjkgVGhlIHVuaXF1ZSBpZGVudGl0eSBvZiB0aGUgcGFydGljdWxhciBtZXRyaWMgdG8gcmVwb3J0LgoKDQoFBAICAQQSBHwEeS0KDAoFBAICAQYSA3wELwoMCgUEAgIBARIDfDA6CgwKBQQCAgEDEgN8PT4KpwIKBAQCAgISBIEBBFIamAIgT3B0aW9uYWwuIEEgc2V0IG9mIGV4cHJlc3Npb25zIHRoYXQgd2lsbCBmb3JtIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBtb25pdG9yZWQgcmVzb3VyY2UgdGhpcyBtZXRyaWMgaXMgYmVpbmcgcmVwb3J0ZWQgb24uCiBJZiB0aGUgbWV0cmljIGJhY2tlbmQgc3VwcG9ydHMgbW9uaXRvcmVkIHJlc291cmNlcywgdGhlc2UgZmllbGRzIGFyZSB1c2VkIHRvIHBvcHVsYXRlIHRoYXQgcmVzb3VyY2UuIE90aGVyd2lzZQogdGhlc2UgZmllbGRzIHdpbGwgYmUgaWdub3JlZCBieSB0aGUgYWRhcHRlci4KCg4KBQQCAgIEEgWBAQR8PwoNCgUEAgICBhIEgQEELwoNCgUEAgICARIEgQEwTQoNCgUEAgICAxIEgQFQUQpPCgIEAxIGhwEAmQEBGkEgUmVwcmVzZW50cyBpbnN0YW5jZSBjb25maWd1cmF0aW9uIHNjaGVtYSBmb3IgJ21ldHJpYycgdGVtcGxhdGUuCgoLCgMEAwESBIcBCBUKKQoEBAMCABIEigEEFRobIFRoZSB2YWx1ZSBiZWluZyByZXBvcnRlZC4KCg8KBQQDAgAEEgaKAQSHARcKDQoFBAMCAAUSBIoBBAoKDQoFBAMCAAESBIoBCxAKDQoFBAMCAAMSBIoBExQKRwoEBAMCARIEjQEEJxo5IFRoZSB1bmlxdWUgaWRlbnRpdHkgb2YgdGhlIHBhcnRpY3VsYXIgbWV0cmljIHRvIHJlcG9ydC4KCg8KBQQDAgEEEgaNAQSKARUKDQoFBAMCAQYSBI0BBBcKDQoFBAMCAQESBI0BGCIKDQoFBAMCAQMSBI0BJSYKlgIKBAQDAgISBJIBBCcahwIgT3B0aW9uYWwuIEFuIGV4cHJlc3Npb24gdG8gY29tcHV0ZSB0aGUgdHlwZSBvZiB0aGUgbW9uaXRvcmVkIHJlc291cmNlIHRoaXMgbWV0cmljIGlzIGJlaW5nIHJlcG9ydGVkIG9uLgogSWYgdGhlIG1ldHJpYyBiYWNrZW5kIHN1cHBvcnRzIG1vbml0b3JlZCByZXNvdXJjZXMsIHRoZXNlIGZpZWxkcyBhcmUgdXNlZCB0byBwb3B1bGF0ZSB0aGF0IHJlc291cmNlLiBPdGhlcndpc2UKIHRoZXNlIGZpZWxkcyB3aWxsIGJlIGlnbm9yZWQgYnkgdGhlIGFkYXB0ZXIuCgoPCgUEAwICBBIGkgEEjQEnCg0KBQQDAgIFEgSSAQQKCg0KBQQDAgIBEgSSAQsiCg0KBQQDAgIDEgSSASUmCqcCCgQEAwIDEgSXAQQ6GpgCIE9wdGlvbmFsLiBBIHNldCBvZiBleHByZXNzaW9ucyB0aGF0IHdpbGwgZm9ybSB0aGUgZGltZW5zaW9ucyBvZiB0aGUgbW9uaXRvcmVkIHJlc291cmNlIHRoaXMgbWV0cmljIGlzIGJlaW5nIHJlcG9ydGVkIG9uLgogSWYgdGhlIG1ldHJpYyBiYWNrZW5kIHN1cHBvcnRzIG1vbml0b3JlZCByZXNvdXJjZXMsIHRoZXNlIGZpZWxkcyBhcmUgdXNlZCB0byBwb3B1bGF0ZSB0aGF0IHJlc291cmNlLiBPdGhlcndpc2UKIHRoZXNlIGZpZWxkcyB3aWxsIGJlIGlnbm9yZWQgYnkgdGhlIGFkYXB0ZXIuCgoPCgUEAwIDBBIGlwEEkgEnCg0KBQQDAgMGEgSXAQQXCg0KBQQDAgMBEgSXARg1Cg0KBQQDAgMDEgSXATg5YgZwcm90bzM=" +--- diff --git a/helm-charts/templates/metrics.yaml b/helm-charts/templates/metrics.yaml new file mode 100644 index 0000000..1324ce9 --- /dev/null +++ b/helm-charts/templates/metrics.yaml @@ -0,0 +1,34 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +{{- range $k, $v := .Values.telemetry.metrics }} +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: {{ $k | quote }} + namespace: {{ $.Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" $ }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" $ }} + app.kubernetes.io/instance: {{ $.Release.Name }} + app.kubernetes.io/managed-by: {{ $.Release.Service }} + app.kubernetes.io/version: {{ $.Chart.AppVersion }} +spec: + template: metric + params: + value: {{ $v.value | quote }} + dimensions: + {{- toYaml $v.dimensions | nindent 6 }} +--- +{{- end }} diff --git a/helm-charts/templates/rules.yaml b/helm-charts/templates/rules.yaml new file mode 100644 index 0000000..e4d65e5 --- /dev/null +++ b/helm-charts/templates/rules.yaml @@ -0,0 +1,36 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +{{- range $k, $v := .Values.telemetry.rules }} +apiVersion: "config.istio.io/v1alpha2" +kind: rule +metadata: + name: {{ $k | quote }} + namespace: {{ $.Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" $ }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" $ }} + app.kubernetes.io/instance: {{ $.Release.Name }} + app.kubernetes.io/managed-by: {{ $.Release.Service }} + app.kubernetes.io/version: {{ $.Chart.AppVersion }} +spec: + match: {{ $v.match }} + actions: + - handler: {{ include "newrelic-istio-adapter.fullname" $ }} + instances: + {{- range $v.instances }} + - {{ . | quote }} + {{- end }} +--- +{{- end }} diff --git a/helm-charts/templates/secret.yaml b/helm-charts/templates/secret.yaml new file mode 100644 index 0000000..4452e66 --- /dev/null +++ b/helm-charts/templates/secret.yaml @@ -0,0 +1,30 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +{{- if .Values.authentication.manageSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "newrelic-istio-adapter.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +type: Opaque +data: + NEW_RELIC_API_KEY: {{ required "A valid .Values.authentication.apiKey entry required" .Values.authentication.apiKey | b64enc | quote }} +{{- end -}} diff --git a/helm-charts/templates/service.yaml b/helm-charts/templates/service.yaml new file mode 100644 index 0000000..f42e90b --- /dev/null +++ b/helm-charts/templates/service.yaml @@ -0,0 +1,35 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "newrelic-istio-adapter.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: grpc + protocol: TCP + name: grpc + selector: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/helm-charts/templates/traces.yaml b/helm-charts/templates/traces.yaml new file mode 100644 index 0000000..a0b33e2 --- /dev/null +++ b/helm-charts/templates/traces.yaml @@ -0,0 +1,32 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +{{- range $k, $v := .Values.telemetry.traces }} +apiVersion: config.istio.io/v1alpha2 +kind: instance +metadata: + name: {{ $k | quote }} + namespace: {{ $.Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" $ }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" $ }} + app.kubernetes.io/instance: {{ $.Release.Name }} + app.kubernetes.io/managed-by: {{ $.Release.Service }} + app.kubernetes.io/version: {{ $.Chart.AppVersion }} +spec: + template: tracespan + params: + {{- toYaml $v | nindent 4 }} +--- +{{- end }} diff --git a/helm-charts/templates/tracespan-template.yaml b/helm-charts/templates/tracespan-template.yaml new file mode 100644 index 0000000..b7e8b11 --- /dev/null +++ b/helm-charts/templates/tracespan-template.yaml @@ -0,0 +1,31 @@ +# Copyright 2019 New Relic Corporation +# Copyright 2018 Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +# this config is created through command +# mixgen template -d $GOPATH/src/istio.io/istio/mixer/template/tracespan/tracespan_handler_service.descriptor_set -o $GOPATH/src/istio.io/istio/mixer/template/tracespan/tracespan.yaml -n tracespan +apiVersion: "config.istio.io/v1alpha2" +kind: template +metadata: + name: tracespan + namespace: {{ .Values.istioNamespace }} + labels: + app.kubernetes.io/name: {{ include "newrelic-istio-adapter.name" . }} + helm.sh/chart: {{ include "newrelic-istio-adapter.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} +spec: + descriptor: "CvD6AgogZ29vZ2xlL3Byb3RvYnVmL2Rlc2NyaXB0b3IucHJvdG8SD2dvb2dsZS5wcm90b2J1ZiJNChFGaWxlRGVzY3JpcHRvclNldBI4CgRmaWxlGAEgAygLMiQuZ29vZ2xlLnByb3RvYnVmLkZpbGVEZXNjcmlwdG9yUHJvdG9SBGZpbGUi5AQKE0ZpbGVEZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRIYCgdwYWNrYWdlGAIgASgJUgdwYWNrYWdlEh4KCmRlcGVuZGVuY3kYAyADKAlSCmRlcGVuZGVuY3kSKwoRcHVibGljX2RlcGVuZGVuY3kYCiADKAVSEHB1YmxpY0RlcGVuZGVuY3kSJwoPd2Vha19kZXBlbmRlbmN5GAsgAygFUg53ZWFrRGVwZW5kZW5jeRJDCgxtZXNzYWdlX3R5cGUYBCADKAsyIC5nb29nbGUucHJvdG9idWYuRGVzY3JpcHRvclByb3RvUgttZXNzYWdlVHlwZRJBCgllbnVtX3R5cGUYBSADKAsyJC5nb29nbGUucHJvdG9idWYuRW51bURlc2NyaXB0b3JQcm90b1IIZW51bVR5cGUSQQoHc2VydmljZRgGIAMoCzInLmdvb2dsZS5wcm90b2J1Zi5TZXJ2aWNlRGVzY3JpcHRvclByb3RvUgdzZXJ2aWNlEkMKCWV4dGVuc2lvbhgHIAMoCzIlLmdvb2dsZS5wcm90b2J1Zi5GaWVsZERlc2NyaXB0b3JQcm90b1IJZXh0ZW5zaW9uEjYKB29wdGlvbnMYCCABKAsyHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnNSB29wdGlvbnMSSQoQc291cmNlX2NvZGVfaW5mbxgJIAEoCzIfLmdvb2dsZS5wcm90b2J1Zi5Tb3VyY2VDb2RlSW5mb1IOc291cmNlQ29kZUluZm8SFgoGc3ludGF4GAwgASgJUgZzeW50YXgiuQYKD0Rlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEjsKBWZpZWxkGAIgAygLMiUuZ29vZ2xlLnByb3RvYnVmLkZpZWxkRGVzY3JpcHRvclByb3RvUgVmaWVsZBJDCglleHRlbnNpb24YBiADKAsyJS5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG9SCWV4dGVuc2lvbhJBCgtuZXN0ZWRfdHlwZRgDIAMoCzIgLmdvb2dsZS5wcm90b2J1Zi5EZXNjcmlwdG9yUHJvdG9SCm5lc3RlZFR5cGUSQQoJZW51bV90eXBlGAQgAygLMiQuZ29vZ2xlLnByb3RvYnVmLkVudW1EZXNjcmlwdG9yUHJvdG9SCGVudW1UeXBlElgKD2V4dGVuc2lvbl9yYW5nZRgFIAMoCzIvLmdvb2dsZS5wcm90b2J1Zi5EZXNjcmlwdG9yUHJvdG8uRXh0ZW5zaW9uUmFuZ2VSDmV4dGVuc2lvblJhbmdlEkQKCm9uZW9mX2RlY2wYCCADKAsyJS5nb29nbGUucHJvdG9idWYuT25lb2ZEZXNjcmlwdG9yUHJvdG9SCW9uZW9mRGVjbBI5CgdvcHRpb25zGAcgASgLMh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zUgdvcHRpb25zElUKDnJlc2VydmVkX3JhbmdlGAkgAygLMi4uZ29vZ2xlLnByb3RvYnVmLkRlc2NyaXB0b3JQcm90by5SZXNlcnZlZFJhbmdlUg1yZXNlcnZlZFJhbmdlEiMKDXJlc2VydmVkX25hbWUYCiADKAlSDHJlc2VydmVkTmFtZRp6Cg5FeHRlbnNpb25SYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQSQAoHb3B0aW9ucxgDIAEoCzImLmdvb2dsZS5wcm90b2J1Zi5FeHRlbnNpb25SYW5nZU9wdGlvbnNSB29wdGlvbnMaNwoNUmVzZXJ2ZWRSYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQifAoVRXh0ZW5zaW9uUmFuZ2VPcHRpb25zElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAIimAYKFEZpZWxkRGVzY3JpcHRvclByb3RvEhIKBG5hbWUYASABKAlSBG5hbWUSFgoGbnVtYmVyGAMgASgFUgZudW1iZXISQQoFbGFiZWwYBCABKA4yKy5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG8uTGFiZWxSBWxhYmVsEj4KBHR5cGUYBSABKA4yKi5nb29nbGUucHJvdG9idWYuRmllbGREZXNjcmlwdG9yUHJvdG8uVHlwZVIEdHlwZRIbCgl0eXBlX25hbWUYBiABKAlSCHR5cGVOYW1lEhoKCGV4dGVuZGVlGAIgASgJUghleHRlbmRlZRIjCg1kZWZhdWx0X3ZhbHVlGAcgASgJUgxkZWZhdWx0VmFsdWUSHwoLb25lb2ZfaW5kZXgYCSABKAVSCm9uZW9mSW5kZXgSGwoJanNvbl9uYW1lGAogASgJUghqc29uTmFtZRI3CgdvcHRpb25zGAggASgLMh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9uc1IHb3B0aW9ucyK2AgoEVHlwZRIPCgtUWVBFX0RPVUJMRRABEg4KClRZUEVfRkxPQVQQAhIOCgpUWVBFX0lOVDY0EAMSDwoLVFlQRV9VSU5UNjQQBBIOCgpUWVBFX0lOVDMyEAUSEAoMVFlQRV9GSVhFRDY0EAYSEAoMVFlQRV9GSVhFRDMyEAcSDQoJVFlQRV9CT09MEAgSDwoLVFlQRV9TVFJJTkcQCRIOCgpUWVBFX0dST1VQEAoSEAoMVFlQRV9NRVNTQUdFEAsSDgoKVFlQRV9CWVRFUxAMEg8KC1RZUEVfVUlOVDMyEA0SDQoJVFlQRV9FTlVNEA4SEQoNVFlQRV9TRklYRUQzMhAPEhEKDVRZUEVfU0ZJWEVENjQQEBIPCgtUWVBFX1NJTlQzMhAREg8KC1RZUEVfU0lOVDY0EBIiQwoFTGFiZWwSEgoOTEFCRUxfT1BUSU9OQUwQARISCg5MQUJFTF9SRVFVSVJFRBACEhIKDkxBQkVMX1JFUEVBVEVEEAMiYwoUT25lb2ZEZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRI3CgdvcHRpb25zGAIgASgLMh0uZ29vZ2xlLnByb3RvYnVmLk9uZW9mT3B0aW9uc1IHb3B0aW9ucyLjAgoTRW51bURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEj8KBXZhbHVlGAIgAygLMikuZ29vZ2xlLnByb3RvYnVmLkVudW1WYWx1ZURlc2NyaXB0b3JQcm90b1IFdmFsdWUSNgoHb3B0aW9ucxgDIAEoCzIcLmdvb2dsZS5wcm90b2J1Zi5FbnVtT3B0aW9uc1IHb3B0aW9ucxJdCg5yZXNlcnZlZF9yYW5nZRgEIAMoCzI2Lmdvb2dsZS5wcm90b2J1Zi5FbnVtRGVzY3JpcHRvclByb3RvLkVudW1SZXNlcnZlZFJhbmdlUg1yZXNlcnZlZFJhbmdlEiMKDXJlc2VydmVkX25hbWUYBSADKAlSDHJlc2VydmVkTmFtZRo7ChFFbnVtUmVzZXJ2ZWRSYW5nZRIUCgVzdGFydBgBIAEoBVIFc3RhcnQSEAoDZW5kGAIgASgFUgNlbmQigwEKGEVudW1WYWx1ZURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEhYKBm51bWJlchgCIAEoBVIGbnVtYmVyEjsKB29wdGlvbnMYAyABKAsyIS5nb29nbGUucHJvdG9idWYuRW51bVZhbHVlT3B0aW9uc1IHb3B0aW9ucyKnAQoWU2VydmljZURlc2NyaXB0b3JQcm90bxISCgRuYW1lGAEgASgJUgRuYW1lEj4KBm1ldGhvZBgCIAMoCzImLmdvb2dsZS5wcm90b2J1Zi5NZXRob2REZXNjcmlwdG9yUHJvdG9SBm1ldGhvZBI5CgdvcHRpb25zGAMgASgLMh8uZ29vZ2xlLnByb3RvYnVmLlNlcnZpY2VPcHRpb25zUgdvcHRpb25zIokCChVNZXRob2REZXNjcmlwdG9yUHJvdG8SEgoEbmFtZRgBIAEoCVIEbmFtZRIdCgppbnB1dF90eXBlGAIgASgJUglpbnB1dFR5cGUSHwoLb3V0cHV0X3R5cGUYAyABKAlSCm91dHB1dFR5cGUSOAoHb3B0aW9ucxgEIAEoCzIeLmdvb2dsZS5wcm90b2J1Zi5NZXRob2RPcHRpb25zUgdvcHRpb25zEjAKEGNsaWVudF9zdHJlYW1pbmcYBSABKAg6BWZhbHNlUg9jbGllbnRTdHJlYW1pbmcSMAoQc2VydmVyX3N0cmVhbWluZxgGIAEoCDoFZmFsc2VSD3NlcnZlclN0cmVhbWluZyK5CAoLRmlsZU9wdGlvbnMSIQoMamF2YV9wYWNrYWdlGAEgASgJUgtqYXZhUGFja2FnZRIwChRqYXZhX291dGVyX2NsYXNzbmFtZRgIIAEoCVISamF2YU91dGVyQ2xhc3NuYW1lEjUKE2phdmFfbXVsdGlwbGVfZmlsZXMYCiABKAg6BWZhbHNlUhFqYXZhTXVsdGlwbGVGaWxlcxJECh1qYXZhX2dlbmVyYXRlX2VxdWFsc19hbmRfaGFzaBgUIAEoCEICGAFSGWphdmFHZW5lcmF0ZUVxdWFsc0FuZEhhc2gSOgoWamF2YV9zdHJpbmdfY2hlY2tfdXRmOBgbIAEoCDoFZmFsc2VSE2phdmFTdHJpbmdDaGVja1V0ZjgSUwoMb3B0aW1pemVfZm9yGAkgASgOMikuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zLk9wdGltaXplTW9kZToFU1BFRURSC29wdGltaXplRm9yEh0KCmdvX3BhY2thZ2UYCyABKAlSCWdvUGFja2FnZRI1ChNjY19nZW5lcmljX3NlcnZpY2VzGBAgASgIOgVmYWxzZVIRY2NHZW5lcmljU2VydmljZXMSOQoVamF2YV9nZW5lcmljX3NlcnZpY2VzGBEgASgIOgVmYWxzZVITamF2YUdlbmVyaWNTZXJ2aWNlcxI1ChNweV9nZW5lcmljX3NlcnZpY2VzGBIgASgIOgVmYWxzZVIRcHlHZW5lcmljU2VydmljZXMSNwoUcGhwX2dlbmVyaWNfc2VydmljZXMYKiABKAg6BWZhbHNlUhJwaHBHZW5lcmljU2VydmljZXMSJQoKZGVwcmVjYXRlZBgXIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSLwoQY2NfZW5hYmxlX2FyZW5hcxgfIAEoCDoFZmFsc2VSDmNjRW5hYmxlQXJlbmFzEioKEW9iamNfY2xhc3NfcHJlZml4GCQgASgJUg9vYmpjQ2xhc3NQcmVmaXgSKQoQY3NoYXJwX25hbWVzcGFjZRglIAEoCVIPY3NoYXJwTmFtZXNwYWNlEiEKDHN3aWZ0X3ByZWZpeBgnIAEoCVILc3dpZnRQcmVmaXgSKAoQcGhwX2NsYXNzX3ByZWZpeBgoIAEoCVIOcGhwQ2xhc3NQcmVmaXgSIwoNcGhwX25hbWVzcGFjZRgpIAEoCVIMcGhwTmFtZXNwYWNlElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uIjoKDE9wdGltaXplTW9kZRIJCgVTUEVFRBABEg0KCUNPREVfU0laRRACEhAKDExJVEVfUlVOVElNRRADKgkI6AcQgICAgAJKBAgmECci0QIKDk1lc3NhZ2VPcHRpb25zEjwKF21lc3NhZ2Vfc2V0X3dpcmVfZm9ybWF0GAEgASgIOgVmYWxzZVIUbWVzc2FnZVNldFdpcmVGb3JtYXQSTAofbm9fc3RhbmRhcmRfZGVzY3JpcHRvcl9hY2Nlc3NvchgCIAEoCDoFZmFsc2VSHG5vU3RhbmRhcmREZXNjcmlwdG9yQWNjZXNzb3ISJQoKZGVwcmVjYXRlZBgDIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSGwoJbWFwX2VudHJ5GAcgASgIUghtYXBFbnRyeRJYChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvblITdW5pbnRlcnByZXRlZE9wdGlvbioJCOgHEICAgIACSgQICBAJSgQICRAKIuIDCgxGaWVsZE9wdGlvbnMSQQoFY3R5cGUYASABKA4yIy5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zLkNUeXBlOgZTVFJJTkdSBWN0eXBlEhYKBnBhY2tlZBgCIAEoCFIGcGFja2VkEkcKBmpzdHlwZRgGIAEoDjIkLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMuSlNUeXBlOglKU19OT1JNQUxSBmpzdHlwZRIZCgRsYXp5GAUgASgIOgVmYWxzZVIEbGF6eRIlCgpkZXByZWNhdGVkGAMgASgIOgVmYWxzZVIKZGVwcmVjYXRlZBIZCgR3ZWFrGAogASgIOgVmYWxzZVIEd2VhaxJYChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvblITdW5pbnRlcnByZXRlZE9wdGlvbiIvCgVDVHlwZRIKCgZTVFJJTkcQABIICgRDT1JEEAESEAoMU1RSSU5HX1BJRUNFEAIiNQoGSlNUeXBlEg0KCUpTX05PUk1BTBAAEg0KCUpTX1NUUklORxABEg0KCUpTX05VTUJFUhACKgkI6AcQgICAgAJKBAgEEAUicwoMT25lb2ZPcHRpb25zElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAIiwAEKC0VudW1PcHRpb25zEh8KC2FsbG93X2FsaWFzGAIgASgIUgphbGxvd0FsaWFzEiUKCmRlcHJlY2F0ZWQYAyABKAg6BWZhbHNlUgpkZXByZWNhdGVkElgKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uUhN1bmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAJKBAgFEAYingEKEEVudW1WYWx1ZU9wdGlvbnMSJQoKZGVwcmVjYXRlZBgBIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24qCQjoBxCAgICAAiKcAQoOU2VydmljZU9wdGlvbnMSJQoKZGVwcmVjYXRlZBghIAEoCDoFZmFsc2VSCmRlcHJlY2F0ZWQSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24qCQjoBxCAgICAAiLgAgoNTWV0aG9kT3B0aW9ucxIlCgpkZXByZWNhdGVkGCEgASgIOgVmYWxzZVIKZGVwcmVjYXRlZBJxChFpZGVtcG90ZW5jeV9sZXZlbBgiIAEoDjIvLmdvb2dsZS5wcm90b2J1Zi5NZXRob2RPcHRpb25zLklkZW1wb3RlbmN5TGV2ZWw6E0lERU1QT1RFTkNZX1VOS05PV05SEGlkZW1wb3RlbmN5TGV2ZWwSWAoUdW5pbnRlcnByZXRlZF9vcHRpb24Y5wcgAygLMiQuZ29vZ2xlLnByb3RvYnVmLlVuaW50ZXJwcmV0ZWRPcHRpb25SE3VuaW50ZXJwcmV0ZWRPcHRpb24iUAoQSWRlbXBvdGVuY3lMZXZlbBIXChNJREVNUE9URU5DWV9VTktOT1dOEAASEwoPTk9fU0lERV9FRkZFQ1RTEAESDgoKSURFTVBPVEVOVBACKgkI6AcQgICAgAIimgMKE1VuaW50ZXJwcmV0ZWRPcHRpb24SQQoEbmFtZRgCIAMoCzItLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uLk5hbWVQYXJ0UgRuYW1lEikKEGlkZW50aWZpZXJfdmFsdWUYAyABKAlSD2lkZW50aWZpZXJWYWx1ZRIsChJwb3NpdGl2ZV9pbnRfdmFsdWUYBCABKARSEHBvc2l0aXZlSW50VmFsdWUSLAoSbmVnYXRpdmVfaW50X3ZhbHVlGAUgASgDUhBuZWdhdGl2ZUludFZhbHVlEiEKDGRvdWJsZV92YWx1ZRgGIAEoAVILZG91YmxlVmFsdWUSIQoMc3RyaW5nX3ZhbHVlGAcgASgMUgtzdHJpbmdWYWx1ZRInCg9hZ2dyZWdhdGVfdmFsdWUYCCABKAlSDmFnZ3JlZ2F0ZVZhbHVlGkoKCE5hbWVQYXJ0EhsKCW5hbWVfcGFydBgBIAIoCVIIbmFtZVBhcnQSIQoMaXNfZXh0ZW5zaW9uGAIgAigIUgtpc0V4dGVuc2lvbiKnAgoOU291cmNlQ29kZUluZm8SRAoIbG9jYXRpb24YASADKAsyKC5nb29nbGUucHJvdG9idWYuU291cmNlQ29kZUluZm8uTG9jYXRpb25SCGxvY2F0aW9uGs4BCghMb2NhdGlvbhIWCgRwYXRoGAEgAygFQgIQAVIEcGF0aBIWCgRzcGFuGAIgAygFQgIQAVIEc3BhbhIpChBsZWFkaW5nX2NvbW1lbnRzGAMgASgJUg9sZWFkaW5nQ29tbWVudHMSKwoRdHJhaWxpbmdfY29tbWVudHMYBCABKAlSEHRyYWlsaW5nQ29tbWVudHMSOgoZbGVhZGluZ19kZXRhY2hlZF9jb21tZW50cxgGIAMoCVIXbGVhZGluZ0RldGFjaGVkQ29tbWVudHMi0QEKEUdlbmVyYXRlZENvZGVJbmZvEk0KCmFubm90YXRpb24YASADKAsyLS5nb29nbGUucHJvdG9idWYuR2VuZXJhdGVkQ29kZUluZm8uQW5ub3RhdGlvblIKYW5ub3RhdGlvbhptCgpBbm5vdGF0aW9uEhYKBHBhdGgYASADKAVCAhABUgRwYXRoEh8KC3NvdXJjZV9maWxlGAIgASgJUgpzb3VyY2VGaWxlEhQKBWJlZ2luGAMgASgFUgViZWdpbhIQCgNlbmQYBCABKAVSA2VuZEKPAQoTY29tLmdvb2dsZS5wcm90b2J1ZkIQRGVzY3JpcHRvclByb3Rvc0gBWj5naXRodWIuY29tL2dvbGFuZy9wcm90b2J1Zi9wcm90b2MtZ2VuLWdvL2Rlc2NyaXB0b3I7ZGVzY3JpcHRvcvgBAaICA0dQQqoCGkdvb2dsZS5Qcm90b2J1Zi5SZWZsZWN0aW9uSqrAAgoHEgUnAOcGAQqqDwoBDBIDJwASMsEMIFByb3RvY29sIEJ1ZmZlcnMgLSBHb29nbGUncyBkYXRhIGludGVyY2hhbmdlIGZvcm1hdAogQ29weXJpZ2h0IDIwMDggR29vZ2xlIEluYy4gIEFsbCByaWdodHMgcmVzZXJ2ZWQuCiBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wcm90b2NvbC1idWZmZXJzLwoKIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAogbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZQogbWV0OgoKICAgICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0CiBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlCiBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyCiBpbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlCiBkaXN0cmlidXRpb24uCiAgICAgKiBOZWl0aGVyIHRoZSBuYW1lIG9mIEdvb2dsZSBJbmMuIG5vciB0aGUgbmFtZXMgb2YgaXRzCiBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbQogdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi4KCiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTCiAiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVAogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SCiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVAogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsCiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwKIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWQogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAogKElOQ0xVRElORyBORUdMSUdF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bm5vdAogc3RvcmUgdGhlIHZhbHVlcyBpbiB0aGVtLiAgSW5zdGVhZCBpdCBzdG9yZXMgdGhlbSBpbiBhIGZpZWxkIGluIHRoZSAqT3B0aW9ucwogbWVzc2FnZSBjYWxsZWQgdW5pbnRlcnByZXRlZF9vcHRpb24uIFRoaXMgZmllbGQgbXVzdCBoYXZlIHRoZSBzYW1lIG5hbWUKIGFjcm9zcyBhbGwgKk9wdGlvbnMgbWVzc2FnZXMuIFdlIHRoZW4gdXNlIHRoaXMgZmllbGQgdG8gcG9wdWxhdGUgdGhlCiBleHRlbnNpb25zIHdoZW4gd2UgYnVpbGQgYSBkZXNjcmlwdG9yLCBhdCB3aGljaCBwb2ludCBhbGwgcHJvdG9zIGhhdmUgYmVlbgogcGFyc2VkIGFuZCBzbyBhbGwgZXh0ZW5zaW9ucyBhcmUga25vd24uCgogRXh0ZW5zaW9uIG51bWJlcnMgZm9yIGN1c3RvbSBvcHRpb25zIG1heSBiZSBjaG9zZW4gYXMgZm9sbG93czoKICogRm9yIG9wdGlvbnMgd2hpY2ggd2lsbCBvbmx5IGJlIHVzZWQgd2l0aGluIGEgc2luZ2xlIGFwcGxpY2F0aW9uIG9yCiAgIG9yZ2FuaXphdGlvbiwgb3IgZm9yIGV4cGVyaW1lbnRhbCBvcHRpb25zLCB1c2UgZmllbGQgbnVtYmVycyA1MDAwMAogICB0aHJvdWdoIDk5OTk5LiAgSXQgaXMgdXAgdG8geW91IHRvIGVuc3VyZSB0aGF0IHlvdSBkbyBub3QgdXNlIHRoZQogICBzYW1lIG51bWJlciBmb3IgbXVsdGlwbGUgb3B0aW9ucy4KICogRm9yIG9wdGlvbnMgd2hpY2ggd2lsbCBiZSBwdWJsaXNoZWQgYW5kIHVzZWQgcHVibGljbHkgYnkgbXVsdGlwbGUKICAgaW5kZXBlbmRlbnQgZW50aXRpZXMsIGUtbWFpbCBwcm90b2J1Zi1nbG9iYWwtZXh0ZW5zaW9uLXJlZ2lzdHJ5QGdvb2dsZS5jb20KICAgdG8gcmVzZXJ2ZSBleHRlbnNpb24gbnVtYmVycy4gU2ltcGx5IHByb3ZpZGUgeW91ciBwcm9qZWN0IG5hbWUgKGUuZy4KICAgT2JqZWN0aXZlLUMgcGx1Z2luKSBhbmQgeW91ciBwcm9qZWN0IHdlYnNpdGUgKGlmIGF2YWlsYWJsZSkgLS0gdGhlcmUncyBubwogICBuZWVkIHRvIGV4cGxhaW4gaG93IHlvdSBpbnRlbmQgdG8gdXNlIHRoZW0uIFVzdWFsbHkgeW91IG9ubHkgbmVlZCBvbmUKICAgZXh0ZW5zaW9uIG51bWJlci4gWW91IGNhbiBkZWNsYXJlIG11bHRpcGxlIG9wdGlvbnMgd2l0aCBvbmx5IG9uZSBleHRlbnNpb24KICAgbnVtYmVyIGJ5IHB1dHRpbmcgdGhlbSBpbiBhIHN1Yi1tZXNzYWdlLiBTZWUgdGhlIEN1c3RvbSBPcHRpb25zIHNlY3Rpb24gb2YKICAgdGhlIGRvY3MgZm9yIGV4YW1wbGVzOgogICBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wcm90b2NvbC1idWZmZXJzL2RvY3MvcHJvdG8jb3B0aW9ucwogICBJZiB0aGlzIHR1cm5zIG91dCB0byBiZSBwb3B1bGFyLCBhIHdlYiBzZXJ2aWNlIHdpbGwgYmUgc2V0IHVwCiAgIHRvIGF1dG9tYXRpY2FsbHkgYXNzaWduIG9wdGlvbiBudW1iZXJzLgoKCwoDBAoBEgS9AggTCvQBCgQECgIAEgTDAgIjGuUBIFNldHMgdGhlIEphdmEgcGFja2FnZSB3aGVyZSBjbGFzc2VzIGdlbmVyYXRlZCBmcm9tIHRoaXMgLnByb3RvIHdpbGwgYmUKIHBsYWNlZC4gIEJ5IGRlZmF1bHQsIHRoZSBwcm90byBwYWNrYWdlIGlzIHVzZWQsIGJ1dCB0aGlzIGlzIG9mdGVuCiBpbmFwcHJvcHJpYXRlIGJlY2F1c2UgcHJvdG8gcGFja2FnZXMgZG8gbm90IG5vcm1hbGx5IHN0YXJ0IHdpdGggYmFja3dhcmRzCiBkb21haW4gbmFtZXMuCgoNCgUECgIABBIEwwICCgoNCgUECgIABRIEwwILEQoNCgUECgIAARIEwwISHgoNCgUECgIAAxIEwwIhIgq/AgoEBAoCARIEywICKxqwAiBJZiBzZXQsIGFsbCB0aGUgY2xhc3NlcyBmcm9tIHRoZSAucHJvdG8gZmlsZSBhcmUgd3JhcHBlZCBpbiBhIHNpbmdsZQogb3V0ZXIgY2xh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CgUAEgSpAw0YCg0KBQQKBQABEgSpAw0RCg0KBQQKBQACEgSpAxUYCgsKAwQKCRIEqwMLDgoMCgQECgkAEgSrAwsNCg0KBQQKCQABEgSrAwsNCg0KBQQKCQACEgSrAwsNCgwKAgQLEgauAwDtAwEKCwoDBAsBEgSuAwgWCtgFCgQECwIAEgTBAwI8GskFIFNldCB0cnVlIHRvIHVzZSB0aGUgb2xkIHByb3RvMSBNZXNzYWdlU2V0IHdpcmUgZm9ybWF0IGZvciBleHRlbnNpb25zLgogVGhpcyBpcyBwcm92aWRlZCBmb3IgYmFja3dhcmRzLWNvbXBhdGliaWxpdHkgd2l0aCB0aGUgTWVzc2FnZVNldCB3aXJlCiBmb3JtYXQuICBZb3Ugc2hvdWxkIG5vdCB1c2UgdGhpcyBmb3IgYW55IG90aGVyIHJlYXNvbjogIEl0J3MgbGVzcwogZWZmaWNpZW50LCBoYXMgZmV3ZXIgZmVhdHVyZXMsIGFuZCBpcyBtb3JlIGNvbXBsaWNhdGVkLgoKIFRoZSBtZXNzYWdlIG11c3QgYmUgZGVmaW5lZCBleGFjdGx5IGFzIGZvbGxvd3M6CiAgIG1lc3NhZ2UgRm9vIHsKICAgICBvcHRpb24gbWVzc2FnZV9zZXRfd2lyZV9mb3JtYXQgPSB0cnVlOwogICAgIGV4dGVuc2lvbnMgNCB0byBtYXg7CiAgIH0KIE5vdGUgdGhhdCB0aGUgbWVzc2FnZSBjYW5ub3QgaGF2ZSBhbnkgZGVmaW5lZCBmaWVsZHM7IE1lc3NhZ2VTZXRzIG9ubHkKIGhhdmUgZXh0ZW5zaW9ucy4KCiBBbGwgZXh0ZW5zaW9ucyBvZiB5b3VyIHR5cGUgbXVzdCBiZSBzaW5ndWxhciBtZXNzYWdlczsgZS5nLiB0aGV5IGNhbm5vdAogYmUgaW50MzJzLCBlbnVtcywgb3IgcmVwZWF0ZWQgbWVzc2FnZXMuCgogQmVjYXVzZSB0aGlzIGlzIGFuIG9wdGlvbiwgdGhlIGFib3ZlIHR3byByZXN0cmljdGlvbnMgYXJlIG5vdCBlbmZvcmNlZCBieQogdGhlIHByb3RvY29sIGNvbXBpbGVyLgoKDQoFBAsCAAQSBMEDAgoKDQoFBAsCAAUSBMEDCw8KDQoFBAsCAAESBMEDECcK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CiBhIG1vcmUgZWZmaWNpZW50IHJlcHJlc2VudGF0aW9uIG9uIHRoZSB3aXJlLiBSYXRoZXIgdGhhbiByZXBlYXRlZGx5CiB3cml0aW5nIHRoZSB0YWcgYW5kIHR5cGUgZm9yIGVhY2ggZWxlbWVudCwgdGhlIGVudGlyZSBhcnJheSBpcyBlbmNvZGVkIGFzCiBhIHNpbmdsZSBsZW5ndGgtZGVsaW1pdGVkIGJsb2IuIEluIHByb3RvMywgb25seSBleHBsaWNpdCBzZXR0aW5nIGl0IHRvCiBmYWxzZSB3aWxsIGF2b2lkIHVzaW5nIHBhY2tlZCBlbmNvZGluZy4KCg0KBQQMAgEEEgSCBAIKCg0KBQQMAgEFEgSCBAsPCg0KBQQMAgEBEgSCBBAWCg0KBQQMAgEDEgSCBBkaCpoFCgQEDAICEgSPBAIzGosFIFRoZSBqc3R5cGUgb3B0aW9uIGRldGVybWluZXMgdGhlIEphdmFTY3JpcHQgdHlwZSB1c2VkIGZvciB2YWx1ZXMgb2YgdGhlCiBmaWVsZC4gIFRoZSBvcHRpb24gaXMgcGVybWl0dGVkIG9ubHkgZm9yIDY0IGJpdCBpbnRlZ3JhbCBhbmQgZml4ZWQgdHlwZXMKIChpbnQ2NCwgdWludDY0LCBzaW50NjQsIGZpeGVkNjQsIHNmaXhlZDY0KS4gIEEgZmllbGQgd2l0aCBqc3R5cGUgSlNfU1RSSU5HCiBpcyByZXByZXNlbnRlZCBhcyBKYXZhU2NyaXB0IHN0cmluZywgd2hpY2ggYXZvaWRzIGxvc3Mgb2YgcHJlY2lzaW9uIHRoYXQKIGNhbiBoYXBwZW4gd2hlbiBhIGxhcmdlIHZhbHVlIGlzIGNvbnZlcnRlZCB0byBhIGZsb2F0aW5nIHBvaW50IEphdmFTY3JpcHQuCiBTcGVjaWZ5aW5nIEpTX05VTUJFUiBmb3IgdGhlIGpzdHlwZSBjYXVzZXMgdGhlIGdlbmVyYXRlZCBKYXZhU2NyaXB0IGNvZGUgdG8KIHVzZSB0aGUgSmF2YVNjcmlwdCAibnVtYmVyIiB0eXBlLiAgVGhlIGJlaGF2aW9yIG9mIHRoZSBkZWZhdWx0IG9wdGlvbgogSlNfTk9STUFMIGlzIGltcGxlbWVudGF0aW9uIGRlcGVuZGVudC4KCiBUaGlzIG9wdGlvbiBpcyBhbiBlbnVtIHRvIHBlcm1pdCBhZGRpdGlvbmFsIHR5cGVzIHRvIGJlIGFkZGVkLCBlLmcuCiBnb29nLm1hdGguSW50ZWdlci4KCg0KBQQMAgIEEgSPBAIKCg0KBQQMAgIGEgSPBAsRCg0KBQQMAgIBEgSPBBIYCg0KBQQMAgIDEgSPBBscCg0KBQQMAgIIEgSPBB0yCg0KBQQMAgIHEgSPBCgxCg4KBAQMBAESBpAEApkEAwoNCgUEDAQBARIEkAQHDQonCgYEDAQBAgASBJIEBBIaFyBVc2UgdGhlIGRlZmF1bHQgdHlwZS4KCg8KBwQMBAECAAESBJIEBA0KDwoHBAwEAQIAAhIEkgQQEQopCgYEDAQBAgESBJUEBBIaGSBVc2UgSmF2YVNjcmlwdCBzdHJpbmdzLgoKDwoHBAwEAQIBARIElQQEDQoPCgcEDAQBAgECEgSVBBARCikKBgQMBAECAhIEmAQEEhoZIFVzZSBKYXZhU2NyaXB0IG51bWJlcnMuCgoPCgcEDAQBAgIBEgSYBAQNCg8KBwQMBAECAgISBJgEEBEK7wwKBAQMAgMSBLcEAika4AwgU2hvdWxkIHRoaXMgZmllbGQgYmUgcGFyc2VkIGxhemlseT8gIExhenkgYXBwbGllcyBvbmx5IHRvIG1lc3NhZ2UtdHlwZQogZmllbGRzLiAgSXQgbWVhbnMgdGhhdCB3aGVuIHRoZSBvdXRlciBtZXNzYWdlIGlzIGluaXRpYWxseSBwYXJzZWQsIHRoZQogaW5uZXIgbWVzc2FnZSdzIGNvbnRlbnRzIHdpbGwgbm90IGJlIHBhcnNlZCBidXQgaW5zdGVhZCBzdG9yZWQgaW4gZW5jb2RlZAogZm9ybS4gIFRoZSBpbm5lciBtZXNzYWdlIHdpbGwgYWN0dWFsbHkgYmUgcGFyc2VkIHdoZW4gaXQgaXMgZmlyc3QgYWNjZXNzZWQuCgogVGhpcyBpcyBvbmx5IGEgaGludC4gIEltcGxlbWVudGF0aW9ucyBhcmUgZnJlZSB0byBjaG9vc2Ugd2hldGhlciB0byB1c2UKIGVhZ2VyIG9yIGxhenkgcGFyc2luZyByZWdhcmRsZXNzIG9mIHRoZSB2YWx1ZSBvZiB0aGlzIG9wdGlvbi4gIEhvd2V2ZXIsCiBzZXR0aW5nIHRoaXMgb3B0aW9uIHRydWUgc3VnZ2VzdHMgdGhhdCB0aGUgcHJvdG9jb2wgYXV0aG9yIGJlbGlldmVzIHRoYXQKIHVzaW5nIGxhenkgcGFyc2luZyBvbiB0aGlzIGZpZWxkIGlzIHdvcnRoIHRoZSBhZGRpdGlvbmFsIGJvb2trZWVwaW5nCiBvdmVyaGVhZCB0eXBpY2FsbHkgbmVlZGVkIHRvIGltcGxlbWVudCBpdC4KCiBUaGlzIG9wdGlvbiBkb2VzIG5vdCBhZmZlY3QgdGhlIHB1YmxpYyBpbnRlcmZhY2Ugb2YgYW55IGdlbmVyYXRlZCBjb2RlOwogYWxsIG1ldGhvZCBzaWduYXR1cmVzIHJlbWFpbiB0aGUgc2FtZS4gIEZ1cnRoZXJtb3JlLCB0aHJlYWQtc2FmZXR5IG9mIHRoZQogaW50ZXJmYWNlIGlzIG5vdCBhZmZlY3RlZCBieSB0aGlzIG9wdGlvbjsgY29uc3QgbWV0aG9kcyByZW1haW4gc2FmZSB0bwogY2FsbCBmcm9tIG11bHRpcGxlIHRocmVhZHMgY29uY3VycmVudGx5LCB3aGlsZSBub24tY29uc3QgbWV0aG9kcyBjb250aW51ZQogdG8gcmVxdWlyZSBleGNsdXNpdmUgYWNjZXNzLgoKCiBOb3RlIHRoYXQgaW1wbGVtZW50YXRpb25zIG1heSBjaG9vc2Ugbm90IHRvIGNoZWNrIHJlcXVpcmVkIGZpZWxkcyB3aXRoaW4KIGEgbGF6eSBzdWItbWVzc2FnZS4gIFRoYXQgaXMsIGNhbGxpbmcgSXNJbml0aWFsaXplZCgpIG9uIHRoZSBvdXRlciBtZXNzYWdlCiBtYXkgcmV0dXJuIHRydWUgZXZlbiBpZiB0aGUgaW5uZXIgbWVzc2FnZSBoYXMgbWlzc2luZyByZXF1aXJlZCBmaWVsZHMuCiBUaGlzIGlzIG5lY2Vzc2FyeSBiZWNhdXNlIG90aGVyd2lzZSB0aGUgaW5uZXIgbWVzc2FnZSB3b3VsZCBoYXZlIHRvIGJlCiBwYXJzZWQgaW4gb3JkZXIgdG8gcGVyZm9ybSB0aGUgY2hlY2ssIGRlZmVhdGluZyB0aGUgcHVycG9zZSBvZiBsYXp5CiBwYXJzaW5nLiAgQW4gaW1wbGVtZW50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dCByZWNvZ25pemUuIFRoaXMgb25seQogYXBwZWFycyBpbiBvcHRpb25zIHByb3RvcyBjcmVhdGVkIGJ5IHRoZSBjb21waWxlcjo6UGFyc2VyIGNsYXNzLgogRGVzY3JpcHRvclBvb2wgcmVzb2x2ZXMgdGhlc2Ugd2hlbiBidWlsZGluZyBEZXNjcmlwdG9yIG9iamVjdHMuIFRoZXJlZm9yZSwKIG9wdGlvbnMgcHJvdG9zIGluIGRlc2NyaXB0b3Igb2JqZWN0cyAoZS5nLiByZXR1cm5lZCBieSBEZXNjcmlwdG9yOjpvcHRpb25zKCksCiBvciBwcm9kdWNlZCBieSBEZXNjcmlwdG9yOjpDb3B5VG8oKSkgd2lsbCBuZXZlciBoYXZlIFVuaW50ZXJwcmV0ZWRPcHRpb25zCiBpbiB0aGVtLgoKCwoDBBIBEgSxBQgbCssCCgQEEgMAEga3BQK6BQMaugIgVGhlIG5hbWUgb2YgdGhlIHVuaW50ZXJwcmV0ZWQgb3B0aW9uLiAgRWFjaCBzdHJpbmcgcmVwcmVzZW50cyBhIHNlZ21lbnQgaW4KIGEgZG90LXNlcGFyYXRlZCBuYW1lLiAgaXNfZXh0ZW5zaW9uIGlzIHRydWUgaWZmIGEgc2VnbWVudCByZXByZXNlbnRzIGFuCiBleHRlbnNpb24gKGRlbm90ZWQgd2l0aCBwYXJlbnRoZXNlcyBpbiBvcHRpb25zIHNwZWNzIGluIC5wcm90byBmaWxlcykuCiBFLmcuLHsgWyJmb28iLCBmYWxzZV0sIFsiYmFyLmJheiIsIHRydWVdLCBbInF1eCIsIGZhbHNlXSB9IHJlcHJlc2VudHMKICJmb28uKGJhci5iYXopLnF1eCIuCgoNCgUEEgMAARIEtwUKEgoOCgYEEgMAAgASBLgFBCIKDwoHBBIDAAIABBIEuAUEDAoPCgcEEgMAAgAFEgS4BQ0TCg8KBwQSAwACAAESBLgFFB0KDwoHBBIDAAIAAxIEuAUgIQoOCgYEEgMAAgESBLkFBCMKDwoHBBIDAAIBBBIEuQUEDAoPCgcEEgMAAgEFEgS5BQ0RCg8KBwQSAwACAQESBLkFEh4KDwoHBBIDAAIBAxIEuQUhIgoMCgQEEgIAEgS7BQIdCg0KBQQSAgAEEgS7BQIKCg0KBQQSAgAGEgS7BQsTCg0KBQQSAgABEgS7BRQYCg0KBQQSAgADEgS7BRscCpwBCgQEEgIBEgS/BQInGo0BIFRoZSB2YWx1ZSBvZiB0aGUgdW5pbnRlcnByZXRlZCBvcHRpb24sIGluIHdoYXRldmVyIHR5cGUgdGhlIHRva2VuaXplcgogaWRlbnRpZmllZCBpdCBhcyBkdXJpbmcgcGFyc2luZy4gRXhhY3RseSBvbmUgb2YgdGhlc2Ugc2hvdWxkIGJlIHNldC4KCg0KBQQSAgEEEgS/BQIKCg0KBQQSAgEFEgS/BQsRCg0KBQQSAgEBEgS/BRIiCg0KBQQSAgEDEgS/BSUmCgwKBAQSAgISBMAFAikKDQoFBBICAgQSBMAFAgoKDQoFBBICAgUSBMAFCxEKDQoFBBICAgESBMAFEiQKDQoFBBICAgMSBMAFJygKDAoEBBICAxIEwQUCKAoNCgUEEgIDBBIEwQUCCgoNCgUEEgIDBRIEwQULEAoNCgUEEgIDARIEwQURIwoNCgUEEgIDAxIEwQUmJwoMCgQEEgIEEgTCBQIjCg0KBQQSAgQEEgTCBQIKCg0KBQQSAgQFEgTCBQsRCg0KBQQSAgQBEgTCBRIeCg0KBQQSAgQDEgTCBSEiCgwKBAQSAgUSBMMFAiIKDQoFBBICBQQSBMMFAgoKDQoFBBICBQUSBMMFCxAKDQoFBBICBQESBMMFER0KDQoFBBICBQMSBMMFICEKDAoEBBICBhIExAUCJgoNCgUEEgIGBBIExAUCCgoNCgUEEgIGBRIExAULEQoNCgUEEgIGARIExAUSIQoNCgUEEgIGAxIExAUkJQraAQoCBBMSBswFAM0GARpqIEVuY2Fwc3VsYXRlcyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgb3JpZ2luYWwgc291cmNlIGZpbGUgZnJvbSB3aGljaCBhCiBGaWxlRGVzY3JpcHRvclByb3RvIHdhcyBnZW5lcmF0ZWQuCjJgID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIE9wdGlvbmFsIHNvdXJjZSBjb2RlIGluZm8KCgsKAwQTARIEzAUIFgqCEQoEBBMCABIE+AUCIRrzECBBIExvY2F0aW9uIGlkZW50aWZpZXMgYSBwaWVjZSBvZiBzb3VyY2UgY29kZSBpbiBhIC5wcm90byBmaWxlIHdoaWNoCiBjb3JyZXNwb25kcyB0byBhIHBhcnRpY3VsYXIgZGVmaW5pdGlvbi4gIFRoaXMgaW5mb3JtYXRpb24gaXMgaW50ZW5kZWQKIHRvIGJlIHVzZWZ1bCB0byBJREVzLCBjb2RlIGluZGV4ZXJzLCBkb2N1bWVudGF0aW9uIGdlbmVyYXRvcnMsIGFuZCBzaW1pbGFyCiB0b29scy4KCiBGb3IgZXhhbXBsZSwgc2F5IHdlIGhhdmUgYSBmaWxlIGxpa2U6CiAgIG1lc3NhZ2UgRm9vIHsKICAgICBvcHRpb25hbCBzdHJpbmcgZm9vID0gMTsKICAgfQogTGV0J3MgbG9vayBhdCBqdXN0IHRoZSBmaWVsZCBkZWZpbml0aW9uOgogICBvcHRpb25hbCBzdHJpbmcgZm9vID0gMTsKICAgXiAgICAgICBeXiAgICAgXl4gIF4gIF5eXgogICBhICAgICAgIGJjICAgICBkZSAgZiAgZ2hpCiBXZSBoYXZlIHRoZSBmb2xsb3dpbmcgbG9jYXRpb25zOgogICBzcGFuICAgcGF0aCAgICAgICAgICAgICAgIHJlcHJlc2VudHMKICAgW2EsaSkgIFsgNCwgMCwgMiwgMCBdICAgICBUaGUgd2hvbGUgZmllbGQgZGVmaW5pdGlvbi4KICAgW2EsYikgIFsgNCwgMCwgMiwgMCwgNCBdICBUaGUgbGFiZWwgKG9wdGlvbmFsKS4KICAgW2MsZCkgIFsgNCwgMCwgMiwgMCwgNSBdICBUaGUgdHlwZSAoc3RyaW5nKS4KICAgW2UsZikgIFsgNCwgMCwgMiwgMCwgMSBdICBUaGUgbmFtZSAoZm9vKS4KICAgW2csaCkgIFsgNCwgMCwgMiwgMCwgMyBdICBUaGUgbnVtYmVyICgxKS4KCiBOb3RlczoKIC0gQSBsb2NhdGlvbiBtYXkgcmVmZXIgdG8gYSByZXBlYXRlZCBmaWVsZCBpdHNlbGYgKGkuZS4gbm90IHRvIGFueQogICBwYXJ0aWN1bGFyIGluZGV4IHdpdGhpbiBpdCkuICBUaGlzIGlzIHVzZWQgd2hlbmV2ZXIgYSBzZXQgb2YgZWxlbWVudHMgYXJlCiAgIGxvZ2ljYWxseSBlbmNsb3NlZCBpbiBhIHNpbmdsZSBjb2RlIHNlZ21lbnQuICBGb3IgZXhhbXBsZSwgYW4gZW50aXJlCiAgIGV4dGVuZCBibG9jayAocG9zc2libHkgY29udGFpbmluZyBtdWx0aXBsZSBleHRlbnNpb24gZGVmaW5pdGlvbnMpIHdpbGwKICAgaGF2ZSBhbiBvdXRlciBsb2NhdGlvbiB3aG9zZSBwYXRoIHJlZmVycyB0byB0aGUgImV4dGVuc2lvbnMiIHJlcGVhdGVkCiAgIGZpZWxkIHdpdGhvdXQgYW4gaW5kZXguCiAtIE11bHRpcGxlIGxvY2F0aW9ucyBtYXkgaGF2ZSB0aGUgc2FtZSBwYXRoLiAgVGhpcyBoYXBwZW5zIHdoZW4gYSBzaW5nbGUKICAgbG9naWNhbCBkZWNsYXJhdGlvbiBpcyBzcHJlYWQgb3V0IGFjcm9zcyBtdWx0aXBsZSBwbGFjZXMuICBUaGUgbW9zdAogICBvYnZpb3VzIGV4YW1wbGUgaXMgdGhlICJleHRlbmQiIGJsb2NrIGFnYWluIC0tIHRoZXJlIG1heSBiZSBtdWx0aXBsZQogICBleHRlbmQgYmxvY2tzIGluIHRoZSBzYW1lIHNjb3BlLCBlYWNoIG9mIHdoaWNoIHdpbGwgaGF2ZSB0aGUgc2FtZSBwYXRoLgogLSBBIGxvY2F0aW9uJ3Mgc3BhbiBpcyBub3QgYWx3YXlzIGEgc3Vic2V0IG9mIGl0cyBwYXJlbnQncyBzcGFuLiAgRm9yCiAgIGV4YW1wbGUsIHRoZSAiZXh0ZW5kZWUiIG9mIGFuIGV4dGVuc2lvbiBkZWNsYXJhdGlvbiBhcHBlYXJzIGF0IHRoZQogICBiZWdpbm5pbmcgb2YgdGhlICJleHRlbmQiIGJsb2NrIGFuZCBpcyBzaGFyZWQgYnkgYWxsIGV4dGVuc2lvbnMgd2l0aGluCiAgIHRoZSBibG9jay4KIC0gSnVzdCBiZWNhdXNlIGEgbG9jYXRpb24ncyBzcGFuIGlzIGEgc3Vic2V0IG9mIHNvbWUgb3RoZXIgbG9jYXRpb24ncyBzcGFuCiAgIGRvZXMgbm90IG1lYW4gdGhhdCBpdCBpcyBhIGRlc2NlbmRlbnQuICBGb3IgZXhhbXBsZSwgYSAiZ3JvdXAiIGRlZmluZXMKICAgYm90aCBhIHR5cGUgYW5kIGEgZmllbGQgaW4gYSBzaW5nbGUgZGVjbGFyYXRpb24uICBUaHVzLCB0aGUgbG9jYXRpb25zCiAgIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHR5cGUgYW5kIGZpZWxkIGFuZCB0aGVpciBjb21wb25lbnRzIHdpbGwgb3ZlcmxhcC4KIC0gQ29kZSB3aGljaCB0cmllcyB0byBpbnRlcnByZXQgbG9jYXRpb25zIHNob3VsZCBwcm9iYWJseSBiZSBkZXNpZ25lZCB0bwogICBpZ25vcmUgdGhvc2UgdGhhdCBpdCBkb2Vzbid0IHVuZGVyc3RhbmQsIGFzIG1vcmUgdHlwZXMgb2YgbG9jYXRpb25zIGNvdWxkCiAgIGJlIHJlY29yZGVkIGluIHRoZSBmdXR1cmUuCgoNCgUEEwIABBIE+AUCCgoNCgUEEwIABhIE+AULEwoNCgUEEwIAARIE+AUUHAoNCgUEEwIAAxIE+AUfIAoOCgQEEwMAEgb5BQLMBgMKDQoFBBMDAAESBPkFChIKgwcKBgQTAwACABIEkQYEKhryBiBJZGVudGlmaWVzIHdoaWNoIHBhcnQgb2YgdGhlIEZpbGVEZXNjcmlwdG9yUHJvdG8gd2FzIGRlZmluZWQgYXQgdGhpcwogbG9jYXRpb24uCgogRWFjaCBlbGVtZW50IGlzIGEgZmllbGQgbnVtYmVyIG9yIGFuIGluZGV4LiAgVGhleSBmb3JtIGEgcGF0aCBmcm9tCiB0aGUgcm9vdCBGaWxlRGVzY3JpcHRvclByb3RvIHRvIHRoZSBwbGFjZSB3aGVyZSB0aGUgZGVmaW5pdGlvbi4gIEZvcgogZXhhbXBsZSwgdGhpcyBwYXRoOgogICBbIDQsIDMsIDIsIDcsIDEgXQogcmVmZXJzIHRvOgogICBmaWxlLm1lc3NhZ2VfdHlwZSgzKSAgLy8gNCwgMwogICAgICAgLmZpZWxkKDcpICAgICAgICAgLy8gMiwgNwogICAgICAgLm5hbWUoKSAgICAgICAgICAgLy8gMQogVGhpcyBpcyBiZWNhdXNlIEZpbGVEZXNjcmlwdG9yUHJvdG8ubWVzc2FnZV90eXBlIGhhcyBmaWVsZCBudW1iZXIgNDoKICAgcmVwZWF0ZWQgRGVzY3JpcHRvclByb3RvIG1lc3NhZ2VfdHlwZSA9IDQ7CiBhbmQgRGVzY3JpcHRvclByb3RvLmZpZWxkIGhhcyBmaWVsZCBudW1iZXIgMjoKICAgcmVwZWF0ZWQgRmllbGREZXNjcmlwdG9yUHJvdG8gZmllbGQgPSAyOwogYW5kIEZpZWxkRGVzY3JpcHRvclByb3RvLm5hbWUgaGFzIGZpZWxkIG51bWJlciAxOgogICBvcHRpb25hbCBzdHJpbmcgbmFtZSA9IDE7CgogVGh1cywgdGhlIGFib3ZlIHBhdGggZ2l2ZXMgdGhlIGxvY2F0aW9uIG9mIGEgZmllbGQgbmFtZS4gIElmIHdlIHJlbW92ZWQKIHRoZSBsYXN0IGVsZW1lbnQ6CiAgIFsgNCwgMywgMiwgNyBdCiB0aGlzIHBhdGggcmVmZXJzIHRvIHRoZSB3aG9sZSBmaWVsZCBkZWNsYXJhdGlvbiAoZnJvbSB0aGUgYmVnaW5uaW5nCiBvZiB0aGUgbGFiZWwgdG8gdGhlIHRlcm1pbmF0aW5nIHNlbWljb2xvbikuCgoPCgcEEwMAAgAEEgSRBgQMCg8KBwQTAwACAAUSBJEGDRIKDwoHBBMDAAIAARIEkQYTFwoPCgcEEwMAAgADEgSRBhobCg8KBwQTAwACAAgSBJEGHCkKEgoKBBMDAAIACOcHABIEkQYdKAoTCgsEEwMAAgAI5wcAAhIEkQYdIwoUCgwEEwMAAgAI5wcAAgASBJEGHSMKFQoNBBMDAAIACOcHAAIAARIEkQYdIwoTCgsEEwMAAgAI5wcAAxIEkQYkKArSAgoGBBMDAAIBEgSYBgQqGsECIEFsd2F5cyBoYXMgZXhhY3RseSB0aHJlZSBvciBmb3VyIGVsZW1lbnRzOiBzdGFydCBsaW5lLCBzdGFydCBjb2x1bW4sCiBlbmQgbGluZSAob3B0aW9uYWwsIG90aGVyd2lzZSBhc3N1bWVkIHNhbWUgYXMgc3RhcnQgbGluZSksIGVuZCBjb2x1bW4uCiBUaGVzZSBhcmUgcGFja2VkIGludG8gYSBzaW5nbGUgZmllbGQgZm9yIGVmZmljaWVuY3kuICBOb3RlIHRoYXQgbGluZQogYW5kIGNvbHVtbiBudW1iZXJzIGFyZSB6ZXJvLWJhc2VkIC0tIHR5cGljYWxseSB5b3Ugd2lsbCB3YW50IHRvIGFkZAogMSB0byBlYWNoIGJlZm9yZSBkaXNwbGF5aW5nIHRvIGEgdXNlci4KCg8KBwQTAwACAQQSBJgGBAwKDwoHBBMDAAIBBRIEmAYNEgoPCgcEEwMAAgEBEgSYBhMXCg8KBwQTAwACAQMSBJgGGhsKDwoHBBMDAAIBCBIEmAYcKQoSCgoEEwMAAgEI5wcAEgSYBh0oChMKCwQTAwACAQjnBwACEgSYBh0jChQKDAQTAwACAQjnBwACABIEmAYdIwoVCg0EEwMAAgEI5wcAAgABEgSYBh0jChMKCwQTAwACAQjnBwADEgSYBiQoCqUMCgYEEwMAAgISBMkGBCkalAwgSWYgdGhpcyBTb3VyY2VDb2RlSW5mbyByZXByZXNlbnRzIGEgY29tcGxldGUgZGVjbGFyYXRpb24sIHRoZXNlIGFyZSBhbnkKIGNvbW1lbnRzIGFwcGVhcmluZyBiZWZvcmUgYW5kIGFmdGVyIHRoZSBkZWNsYXJhdGlvbiB3aGljaCBhcHBlYXIgdG8gYmUKIGF0dGFjaGVkIHRvIHRoZSBkZWNsYXJhdGlvbi4KCiBBIHNlcmllcyBvZiBsaW5lIGNvbW1lbnRzIGFwcGVhcmluZyBvbiBjb25zZWN1dGl2ZSBsaW5lcywgd2l0aCBubyBvdGhlcgogdG9rZW5zIGFwcGVhcmluZyBvbiB0aG9zZSBsaW5lcywgd2lsbCBiZSB0cmVhdGVkIGFzIGEgc2luZ2xlIGNvbW1lbnQuCgogbGVhZGluZ19kZXRhY2hlZF9jb21tZW50cyB3aWxsIGtlZXAgcGFyYWdyYXBocyBvZiBjb21tZW50cyB0aGF0IGFwcGVhcgogYmVmb3JlIChidXQgbm90IGNvbm5lY3RlZCB0bykgdGhlIGN1cnJlbnQgZWxlbWVudC4gRWFjaCBwYXJhZ3JhcGgsCiBzZXBhcmF0ZWQgYnkgZW1wdHkgbGluZXMsIHdpbGwgYmUgb25lIGNvbW1lbnQgZWxlbWVudCBpbiB0aGUgcmVwZWF0ZWQKIGZpZWxkLgoKIE9ubHkgdGhlIGNvbW1lbnQgY29udGVudCBpcyBwcm92aWRlZDsgY29tbWVudCBtYXJrZXJzIChlLmcuIC8vKSBhcmUKIHN0cmlwcGVkIG91dC4gIEZvciBibG9jayBjb21tZW50cywgbGVhZGluZyB3aGl0ZXNwYWNlIGFuZCBhbiBhc3Rlcmlzawogd2lsbCBiZSBzdHJpcHBlZCBmcm9tIHRoZSBiZWdpbm5pbmcgb2YgZWFjaCBsaW5lIG90aGVyIHRoYW4gdGhlIGZpcnN0LgogTmV3bGluZXMgYXJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQuCgogRXhhbXBsZXM6CgogICBvcHRpb25hbCBpbnQzMiBmb28gPSAxOyAgLy8gQ29tbWVudCBhdHRhY2hlZCB0byBmb28uCiAgIC8vIENvbW1lbnQgYXR0YWNoZWQgdG8gYmFyLgogICBvcHRpb25hbCBpbnQzMiBiYXIgPSAyOwoKICAgb3B0aW9uYWwgc3RyaW5nIGJheiA9IDM7CiAgIC8vIENvbW1lbnQgYXR0YWNoZWQgdG8gYmF6LgogICAvLyBBbm90aGVyIGxpbmUgYXR0YWNoZWQgdG8gYmF6LgoKICAgLy8gQ29tbWVudCBhdHRhY2hlZCB0byBxdXguCiAgIC8vCiAgIC8vIEFub3RoZXIgbGluZSBhdHRhY2hlZCB0byBxdXguCiAgIG9wdGlvbmFsIGRvdWJsZSBxdXggPSA0OwoKICAgLy8gRGV0YWNoZWQgY29tbWVudCBmb3IgY29yZ2UuIFRoaXMgaXMgbm90IGxlYWRpbmcgb3IgdHJhaWxpbmcgY29tbWVudHMKICAgLy8gdG8gcXV4IG9yIGNvcmdlIGJlY2F1c2UgdGhlcmUgYXJlIGJsYW5rIGxpbmVzIHNlcGFyYXRpbmcgaXQgZnJvbQogICAvLyBib3RoLgoKICAgLy8gRGV0YWNoZWQgY29tbWVudCBmb3IgY29yZ2UgcGFyYWdyYXBoIDIuCgogICBvcHRpb25hbCBzdHJpbmcgY29yZ2UgPSA1OwogICAvKiBCbG9jayBjb21tZW50IGF0dGFjaGVkCiAgICAqIHRvIGNvcmdlLiAgTGVhZGluZyBhc3Rlcmlza3MKICAgICogd2lsbCBiZSByZW1vdmVkLiAqLwogICAvKiBCbG9jayBjb21tZW50IGF0dGFjaGVkIHRvCiAgICAqIGdyYXVsdC4gKi8KICAgb3B0aW9uYWwgaW50MzIgZ3JhdWx0ID0gNjsKCiAg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bGUucHJvdG9idWYuRW51bU9wdGlvbnMYseQDIAEoCFIRZ29wcm90b0VudW1QcmVmaXg6UgoVZ29wcm90b19lbnVtX3N0cmluZ2VyEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMXkAyABKAhSE2dvcHJvdG9FbnVtU3RyaW5nZXI6QwoNZW51bV9zdHJpbmdlchIcLmdvb2dsZS5wcm90b2J1Zi5FbnVtT3B0aW9ucxjG5AMgASgIUgxlbnVtU3RyaW5nZXI6RwoPZW51bV9jdXN0b21uYW1lEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMfkAyABKAlSDmVudW1DdXN0b21uYW1lOjoKCGVudW1kZWNsEhwuZ29vZ2xlLnByb3RvYnVmLkVudW1PcHRpb25zGMjkAyABKAhSCGVudW1kZWNsOlYKFGVudW12YWx1ZV9jdXN0b21uYW1lEiEuZ29vZ2xlLnByb3RvYnVmLkVudW1WYWx1ZU9wdGlvbnMY0YMEIAEoCVITZW51bXZhbHVlQ3VzdG9tbmFtZTpOChNnb3Byb3RvX2dldHRlcnNfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJnsAyABKAhSEWdvcHJvdG9HZXR0ZXJzQWxsOlUKF2dvcHJvdG9fZW51bV9wcmVmaXhfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJrsAyABKAhSFGdvcHJvdG9FbnVtUHJlZml4QWxsOlAKFGdvcHJvdG9fc3RyaW5nZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJvsAyABKAhSEmdvcHJvdG9TdHJpbmdlckFsbDpKChF2ZXJib3NlX2VxdWFsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxic7AMgASgIUg92ZXJib3NlRXF1YWxBbGw6OQoIZmFjZV9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYnewDIAEoCFIHZmFjZUFsbDpBCgxnb3N0cmluZ19hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYnuwDIAEoCFILZ29zdHJpbmdBbGw6QQoMcG9wdWxhdGVfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGJ/sAyABKAhSC3BvcHVsYXRlQWxsOkEKDHN0cmluZ2VyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxig7AMgASgIUgtzdHJpbmdlckFsbDo/Cgtvbmx5b25lX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxih7AMgASgIUgpvbmx5b25lQWxsOjsKCWVxdWFsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxil7AMgASgIUghlcXVhbEFsbDpHCg9kZXNjcmlwdGlvbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYpuwDIAEoCFIOZGVzY3JpcHRpb25BbGw6PwoLdGVzdGdlbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYp+wDIAEoCFIKdGVzdGdlbkFsbDpBCgxiZW5jaGdlbl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYqOwDIAEoCFILYmVuY2hnZW5BbGw6QwoNbWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxip7AMgASgIUgxtYXJzaGFsZXJBbGw6RwoPdW5tYXJzaGFsZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGKrsAyABKAhSDnVubWFyc2hhbGVyQWxsOlAKFHN0YWJsZV9tYXJzaGFsZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGKvsAyABKAhSEnN0YWJsZU1hcnNoYWxlckFsbDo7CglzaXplcl9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYrOwDIAEoCFIIc2l6ZXJBbGw6WQoZZ29wcm90b19lbnVtX3N0cmluZ2VyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxit7AMgASgIUhZnb3Byb3RvRW51bVN0cmluZ2VyQWxsOkoKEWVudW1fc3RyaW5nZXJfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGK7sAyABKAhSD2VudW1TdHJpbmdlckFsbDpQChR1bnNhZmVfbWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxiv7AMgASgIUhJ1bnNhZmVNYXJzaGFsZXJBbGw6VAoWdW5zYWZlX3VubWFyc2hhbGVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxiw7AMgASgIUhR1bnNhZmVVbm1hcnNoYWxlckFsbDpbChpnb3Byb3RvX2V4dGVuc2lvbnNfbWFwX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxix7AMgASgIUhdnb3Byb3RvRXh0ZW5zaW9uc01hcEFsbDpYChhnb3Byb3RvX3VucmVjb2duaXplZF9hbGwSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYsuwDIAEoCFIWZ29wcm90b1VucmVjb2duaXplZEFsbDpJChBnb2dvcHJvdG9faW1wb3J0EhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLPsAyABKAhSD2dvZ29wcm90b0ltcG9ydDpFCg5wcm90b3NpemVyX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi07AMgASgIUg1wcm90b3NpemVyQWxsOj8KC2NvbXBhcmVfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLXsAyABKAhSCmNvbXBhcmVBbGw6QQoMdHlwZWRlY2xfYWxsEhwuZ29vZ2xlLnByb3RvYnVmLkZpbGVPcHRpb25zGLbsAyABKAhSC3R5cGVkZWNsQWxsOkEKDGVudW1kZWNsX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi37AMgASgIUgtlbnVtZGVjbEFsbDpRChRnb3Byb3RvX3JlZ2lzdHJhdGlvbhIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi47AMgASgIUhNnb3Byb3RvUmVnaXN0cmF0aW9uOkcKD21lc3NhZ2VuYW1lX2FsbBIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxi57AMgASgIUg5tZXNzYWdlbmFtZUFsbDpKCg9nb3Byb3RvX2dldHRlcnMSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYgfQDIAEoCFIOZ29wcm90b0dldHRlcnM6TAoQZ29wcm90b19zdHJpbmdlchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiD9AMgASgIUg9nb3Byb3RvU3RyaW5nZXI6RgoNdmVyYm9zZV9lcXVhbBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiE9AMgASgIUgx2ZXJib3NlRXF1YWw6NQoEZmFjZRIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiF9AMgASgIUgRmYWNlOj0KCGdvc3RyaW5nEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGIb0AyABKAhSCGdvc3RyaW5nOj0KCHBvcHVsYXRlEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGIf0AyABKAhSCHBvcHVsYXRlOj0KCHN0cmluZ2VyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGMCLBCABKAhSCHN0cmluZ2VyOjsKB29ubHlvbmUSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYifQDIAEoCFIHb25seW9uZTo3CgVlcXVhbBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiN9AMgASgIUgVlcXVhbDpDCgtkZXNjcmlwdGlvbhIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiO9AMgASgIUgtkZXNjcmlwdGlvbjo7Cgd0ZXN0Z2VuEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGI/0AyABKAhSB3Rlc3RnZW46PQoIYmVuY2hnZW4SHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYkPQDIAEoCFIIYmVuY2hnZW46PwoJbWFyc2hhbGVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJH0AyABKAhSCW1hcnNoYWxlcjpDCgt1bm1hcnNoYWxlchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiS9AMgASgIUgt1bm1hcnNoYWxlcjpMChBzdGFibGVfbWFyc2hhbGVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJP0AyABKAhSD3N0YWJsZU1hcnNoYWxlcjo3CgVzaXplchIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiU9AMgASgIUgVzaXplcjpMChB1bnNhZmVfbWFyc2hhbGVyEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJf0AyABKAhSD3Vuc2FmZU1hcnNoYWxlcjpQChJ1bnNhZmVfdW5tYXJzaGFsZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYmPQDIAEoCFIRdW5zYWZlVW5tYXJzaGFsZXI6VwoWZ29wcm90b19leHRlbnNpb25zX21hcBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxiZ9AMgASgIUhRnb3Byb3RvRXh0ZW5zaW9uc01hcDpUChRnb3Byb3RvX3VucmVjb2duaXplZBIfLmdvb2dsZS5wcm90b2J1Zi5NZXNzYWdlT3B0aW9ucxia9AMgASgIUhNnb3Byb3RvVW5yZWNvZ25pemVkOkEKCnByb3Rvc2l6ZXISHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYnPQDIAEoCFIKcHJvdG9zaXplcjo7Cgdjb21wYXJlEh8uZ29vZ2xlLnByb3RvYnVmLk1lc3NhZ2VPcHRpb25zGJ30AyABKAhSB2NvbXBhcmU6PQoIdHlwZWRlY2wSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYnvQDIAEoCFIIdHlwZWRlY2w6QwoLbWVzc2FnZW5hbWUSHy5nb29nbGUucHJvdG9idWYuTWVzc2FnZU9wdGlvbnMYofQDIAEoCFILbWVzc2FnZW5hbWU6OwoIbnVsbGFibGUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGOn7AyABKAhSCG51bGxhYmxlOjUKBWVtYmVkEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjq+wMgASgIUgVlbWJlZDo/CgpjdXN0b210eXBlEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjr+wMgASgJUgpjdXN0b210eXBlOj8KCmN1c3RvbW5hbWUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGOz7AyABKAlSCmN1c3RvbW5hbWU6OQoHanNvbnRhZxIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY7fsDIAEoCVIHanNvbnRhZzo7Cghtb3JldGFncxIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY7vsDIAEoCVIIbW9yZXRhZ3M6OwoIY2FzdHR5cGUSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGO/7AyABKAlSCGNhc3R0eXBlOjkKB2Nhc3RrZXkSHS5nb29nbGUucHJvdG9idWYuRmllbGRPcHRpb25zGPD7AyABKAlSB2Nhc3RrZXk6PQoJY2FzdHZhbHVlEh0uZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucxjx+wMgASgJUgljYXN0dmFsdWU6OQoHc3RkdGltZRIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY8vsDIAEoCFIHc3RkdGltZTpBCgtzdGRkdXJhdGlvbhIdLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE9wdGlvbnMY8/sDIAEoCFILc3RkZHVyYXRpb25CRQoTY29tLmdvb2dsZS5wcm90b2J1ZkIKR29Hb1Byb3Rvc1oiZ2l0aHViLmNvbS9nb2dvL3Byb3RvYnVmL2dvZ29wcm90b0qaNQoHEgUcAIcBAQr8CgoBDBIDHAASMvEKIFByb3RvY29sIEJ1ZmZlcnMgZm9yIEdvIHdpdGggR2FkZ2V0cwoKIENvcHlyaWdodCAoYykgMjAxMywgVGhlIEdvR28gQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC4KIGh0dHA6Ly9naXRodWIuY29tL2dvZ28vcHJvdG9idWYKCiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUKIG1ldDoKCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodAogbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgogICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZQogY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lcgogaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZQogZGlzdHJpYnV0aW9uLgoKIFRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORCBDT05UUklCVVRPUlMKICJBUyBJUyIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1IKIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFSRSBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUCiBPV05FUiBPUiBDT05UUklCVVRPUlMgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCwKIFNQRUNJQUwsIEVYRU1QTEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJTkNMVURJTkcsIEJVVCBOT1QKIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLAogREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZCiBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklM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B0USBIUBCCYKCgoDB0UCEgN6ByMKCwoDB0UEEgSFAQgQCgsKAwdFBRIEhQERFQoLCgMHRQESBIUBFh0KCwoDB0UDEgSFASAlCgoKAgdGEgSGAQgqCgoKAwdGAhIDegcjCgsKAwdGBBIEhgEIEAoLCgMHRgUSBIYBERUKCwoDB0YBEgSGARYhCgsKAwdGAxIEhgEkKQqMFwosbWl4ZXIvYWRhcHRlci9tb2RlbC92MWJldGExL2V4dGVuc2lvbnMucHJvdG8SIWlzdGlvLm1peGVyLmFkYXB0ZXIubW9kZWwudjFiZXRhMRogZ29vZ2xlL3Byb3RvYnVmL2Rlc2NyaXB0b3IucHJvdG8quAEKD1RlbXBsYXRlVmFyaWV0eRIaChZURU1QTEFURV9WQVJJRVRZX0NIRUNLEAASGwoXVEVNUExBVEVfVkFSSUVUWV9SRVBPUlQQARIaChZURU1QTEFURV9WQVJJRVRZX1FVT1RBEAISKAokVEVNUExBVEVfVkFSSUVUWV9BVFRSSUJVVEVfR0VORVJBVE9SEAMSJgoiVEVNUExBVEVfVkFSSUVUWV9DSEVDS19XSVRIX09VVFBVVBAEOn4KEHRlbXBsYXRlX3ZhcmlldHkSHC5nb29nbGUucHJvdG9idWYuRmlsZU9wdGlvbnMYr8q8IiABKA4yMi5pc3Rpby5taXhlci5hZGFwdGVyLm1vZGVsLnYxYmV0YTEuVGVtcGxhdGVWYXJpZXR5Ug90ZW1wbGF0ZVZhcmlldHk6RAoNdGVtcGxhdGVfbmFtZRIcLmdvb2dsZS5wcm90b2J1Zi5GaWxlT3B0aW9ucxjQy7wiIAEoCVIMdGVtcGxhdGVOYW1lQipaKGlzdGlvLmlvL2FwaS9taXhlci9hZGFwdGVyL21vZGVsL3YxYmV0YTFK4RIKBhIEDgAyAQq/BAoBDBIDDgASMrQEIENvcHlyaWdodCAyMDE4IElzdGlvIEF1dGhvcnMKCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAIKQoICgEIEgMSAD0KCwoECOcHABIDEgA9CgwKBQjnBwACEgMSBxEKDQoGCOcHAAIAEgMSBxEKDgoHCOcHAAIAARIDEgcRCgwKBQjnBwAHEgMSEjwKCQoCAwASAxQHKQp5CgIFABIEGAAoARptIFRoZSBhdmFpbGFibGUgdmFyaWV0aWVzIG9mIHRlbXBsYXRlcywgY29udHJvbGxpbmcgdGhlIHNlbWFudGljcyBvZiB3aGF0IGFuIGFkYXB0ZXIgZG9lcyB3aXRoIGVhY2ggaW5zdGFuY2UuCgoKCgMFAAESAxgFFArHAQoEBQACABIDGwQfGrkBIE1ha2VzIHRoZSB0ZW1wbGF0ZSBhcHBsaWNhYmxlIGZvciBNaXhlcidzIGNoZWNrIGNhbGxzLiBJbnN0YW5jZXMgb2Ygc3VjaCB0ZW1wbGF0ZSBhcmUgY3JlYXRlZCBkdXJpbmcKIGNoZWNrIGNhbGxzIGluIE1peGVyIGFuZCBwYXNzZWQgdG8gdGhlIGhhbmRsZXJzIGJhc2VkIG9uIHRoZSBydWxlIGNvbmZpZ3VyYXRpb25zLgoKDAoFBQACAAESAxsEGgoMCgUFAAIAAhIDGx0eCskBCgQFAAIBEgMeBCAauwEgTWFrZXMgdGhlIHRlbXBsYXRlIGFwcGxpY2FibGUgZm9yIE1peGVyJ3MgcmVwb3J0IGNhbGxzLiBJbnN0YW5jZXMgb2Ygc3VjaCB0ZW1wbGF0ZSBhcmUgY3JlYXRlZCBkdXJpbmcKIHJlcG9ydCBjYWxscyBpbiBNaXhlciBhbmQgcGFzc2VkIHRvIHRoZSBoYW5kbGVycyBiYXNlZCBvbiB0aGUgcnVsZSBjb25maWd1cmF0aW9ucy4KCgwKBQUAAgEBEgMeBBsKDAoFBQACAQISAx4eHwrNAQoEBQACAhIDIQQfGr8BIE1ha2VzIHRoZSB0ZW1wbGF0ZSBhcHBsaWNhYmxlIGZvciBNaXhlcidzIHF1b3RhIGNhbGxzLiBJbnN0YW5jZXMgb2Ygc3VjaCB0ZW1wbGF0ZSBhcmUgY3JlYXRlZCBkdXJpbmcKIHF1b3RhIGNoZWNrIGNhbGxzIGluIE1peGVyIGFuZCBwYXNzZWQgdG8gdGhlIGhhbmRsZXJzIGJhc2VkIG9uIHRoZSBydWxlIGNvbmZpZ3VyYXRpb25zLgoKDAoFBQACAgESAyEEGgoMCgUFAAICAhIDIR0eCusBCgQFAAIDEgMkBC0a3QEgTWFrZXMgdGhlIHRlbXBsYXRlIGFwcGxpY2FibGUgZm9yIE1peGVyJ3MgYXR0cmlidXRlIGdlbmVyYXRpb24gcGhhc2UuIEluc3RhbmNlcyBvZiBzdWNoIHRlbXBsYXRlIGFyZSBjcmVhdGVkIGR1cmluZwogcHJlLXByb2Nlc3NpbmcgYXR0cmlidXRlIGdlbmVyYXRpb24gcGhhc2UgYW5kIHBhc3NlZCB0byB0aGUgaGFuZGxlcnMgYmFzZWQgb24gdGhlIHJ1bGUgY29uZmlndXJhdGlvbnMuCgoMCgUFAAIDARIDJAQoCgwKBQUAAgMCEgMkKywKugEKBAUAAgQSAycEKxqsASBNYWtlcyB0aGUgdGVtcGxhdGUgYXBwbGljYWJsZSBmb3IgTWl4ZXIncyBjaGVjayBjYWxscy4gSW5zdGFuY2VzIG9mIHN1Y2ggdGVtcGxhdGUgYXJlIGNyZWF0ZWQgZHVyaW5nCiBjaGVjayBjYWxscyBpbiBNaXhlciBhbmQgcGFzc2VkIHRvIHRoZSBoYW5kbGVycyB0aGF0IHByb2R1Y2UgdmFsdWVzLgoKDAoFBQACBAESAycEJgoMCgUFAAIEAhIDJykqCjEKAQcSBCsAMgEaJiBGaWxlIGxldmVsIG9wdGlvbnMgZm9yIHRoZSB0ZW1wbGF0ZS4KCjYKAgcAEgMtBDAaKyBSZXF1aXJlZDogb3B0aW9uIGZvciB0aGUgVGVtcGxhdGVWYXJpZXR5LgoKCgoDBwACEgMrByIKCwoDBwAEEgQtBCskCgoKAwcABhIDLQQTCgoKAwcAARIDLRQkCgoKAwcAAxIDLScvCqQBCgIHARIDMQQkGpgBIE9wdGlvbmFsOiBvcHRpb24gZm9yIHRoZSB0ZW1wbGF0ZSBuYW1lLgogSWYgbm90IHNwZWNpZmllZCwgdGhlIGxhc3Qgc2VnbWVudCBvZiB0aGUgdGVtcGxhdGUgcHJvdG8ncyBwYWNrYWdlIG5hbWUgaXMgdXNlZCB0bwogZGVyaXZlIHRoZSB0ZW1wbGF0ZSBuYW1lLgoKCgoDBwECEgMrByIKCwoDBwEEEgQxBC0wCgoKAwcBBRIDMQQKCgoKAwcBARIDMQsYCgoKAwcBAxIDMRsjYgZwcm90bzMK3CwKGWdvb2dsZS9wcm90b2J1Zi9hbnkucHJvdG8SD2dvb2dsZS5wcm90b2J1ZiI2CgNBbnkSGQoIdHlwZV91cmwYASABKAlSB3R5cGVVcmwSFAoFdmFsdWUYAiABKAxSBXZhbHVlQm8KE2NvbS5nb29nbGUucHJvdG9idWZCCEFueVByb3RvUAFaJWdpdGh1Yi5jb20vZ29sYW5nL3Byb3RvYnVmL3B0eXBlcy9hbnmiAgNHUEKqAh5Hb29nbGUuUHJvdG9idWYuV2VsbEtub3duVHlwZXNK/CoKBxIFHgCUAQEKzAwKAQwSAx4AEjLBDCBQcm90b2NvbCBCdWZmZXJzIC0gR29vZ2xlJ3MgZGF0YSBpbnRlcmNoYW5nZSBmb3JtYXQKIENvcHlyaWdodCAyMDA4IEdvb2dsZSBJbmMuICBBbGwgcmlnaHRzIHJlc2VydmVkLgogaHR0cHM6Ly9kZXZlbG9wZXJzLmdvb2dsZS5jb20vcHJvdG9jb2wtYnVmZmVycy8KCiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUKIG1ldDoKCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodAogbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgogICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZQogY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lcgogaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZQogZGlzdHJpYnV0aW9uLgogICAgICogTmVpdGhlciB0aGUgbmFtZSBvZiBHb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0cwogY29udHJpYnV0b3JzIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20KIHRoaXMgc29mdHdhcmUgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgogVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwogIkFTIElTIiBBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1QKIExJTUlURUQgVE8sIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUgogQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQKIE9XTkVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFO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cGFjayBhIG1lc3NhZ2UgaW4gR28KCiAgICAgIGZvbyA6PSAmcGIuRm9vey4uLn0KICAgICAgYW55LCBlcnIgOj0gcHR5cGVzLk1hcnNoYWxBbnkoZm9vKQogICAgICAuLi4KICAgICAgZm9vIDo9ICZwYi5Gb297fQogICAgICBpZiBlcnIgOj0gcHR5cGVzLlVubWFyc2hhbEFueShhbnksIGZvbyk7IGVyciAhPSBuaWwgewogICAgICAgIC4uLgogICAgICB9CgogVGhlIHBhY2sgbWV0aG9kcyBwcm92aWRlZCBieSBwcm90b2J1ZiBsaWJyYXJ5IHdpbGwgYnkgZGVmYXVsdCB1c2UKICd0eXBlLmdvb2dsZWFwaXMuY29tL2Z1bGwudHlwZS5uYW1lJyBhcyB0aGUgdHlwZSBVUkwgYW5kIHRoZSB1bnBhY2sKIG1ldGhvZHMgb25seSB1c2UgdGhlIGZ1bGx5IHF1YWxpZmllZCB0eXBlIG5hbWUgYWZ0ZXIgdGhlIGxhc3QgJy8nCiBpbiB0aGUgdHlwZSBVUkwsIGZvciBleGFtcGxlICJmb28uYmFyLmNvbS94L3kueiIgd2lsbCB5aWVsZCB0eXBlCiBuYW1lICJ5LnoiLgoKCiBKU09OCiA9PT09CiBUaGUgSlNPTiByZXByZXNlbnRhdGlvbiBvZiBhbiBgQW55YCB2YWx1ZSB1c2VzIHRoZSByZWd1bGFyCiByZXByZXNlbnRhdGlvbiBvZiB0aGUgZGVzZXJpYWxpemVkLCBlbWJlZGRlZCBtZXNzYWdlLCB3aXRoIGFuCiBhZGRpdGlvbmFsIGZpZWxkIGBAdHlwZWAgd2hpY2ggY29udGFpbnMgdGhlIHR5cGUgVVJMLiBFeGFtcGxlOgoKICAgICBwYWNrYWdlIGdvb2dsZS5wcm9maWxlOwogICAgIG1lc3NhZ2UgUGVyc29uIHsKICAgICAgIHN0cmluZyBmaXJzdF9uYW1lID0gMTsKICAgICAgIHN0cmluZyBsYXN0X25hbWUgPSAyOwogICAgIH0KCiAgICAgewogICAgICAgIkB0eXBlIjogInR5cGUuZ29vZ2xlYXBpcy5jb20vZ29vZ2xlLnByb2ZpbGUuUGVyc29uIiwKICAgICAgICJmaXJzdE5hbWUiOiA8c3RyaW5nPiwKICAgICAgICJsYXN0TmFtZSI6IDxzdHJpbmc+CiAgICAgfQoKIElmIHRoZSBlbWJlZGRlZCBtZXNzYWdlIHR5cGUgaXMgd2VsbC1rbm93biBhbmQgaGFzIGEgY3VzdG9tIEpTT04KIHJlcHJlc2VudGF0aW9uLCB0aGF0IHJlcHJlc2VudGF0aW9uIHdpbGwgYmUgZW1iZWRkZWQgYWRkaW5nIGEgZmllbGQKIGB2YWx1ZWAgd2hpY2ggaG9sZHMgdGhlIGN1c3RvbSBKU09OIGluIGFkZGl0aW9uIHRvIHRoZSBgQHR5cGVgCiBmaWVsZC4gRXhhbXBsZSAoZm9yIG1lc3NhZ2UgW2dvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbl1bXSk6CgogICAgIHsKICAgICAgICJAdHlwZSI6ICJ0eXBlLmdvb2dsZWFwaXMuY29tL2dvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbiIsCiAgICAgICAidmFsdWUiOiAiMS4yMTJzIgogICAgIH0KCgoKCgMEAAESA3kICwrkBwoEBAACABIEkAECFhrVByBBIFVSTC9yZXNvdXJjZSBuYW1lIHdob3NlIGNvbnRlbnQgZGVzY3JpYmVzIHRoZSB0eXBlIG9mIHRoZQogc2VyaWFsaXplZCBwcm90b2NvbCBidWZmZXIgbWVzc2FnZS4KCiBGb3IgVVJMcyB3aGljaCB1c2UgdGhlIHNjaGVtZSBgaHR0cGAsIGBodHRwc2AsIG9yIG5vIHNjaGVtZSwgdGhlCiBmb2xsb3dpbmcgcmVzdHJpY3Rpb25zIGFuZCBpbnRlcnByZXRhdGlvbnMgYXBwbHk6CgogKiBJZiBubyBzY2hlbWUgaXMgcHJvdmlkZWQsIGBodHRwc2AgaXMgYXNzdW1lZC4KICogVGhlIGxhc3Qgc2VnbWVudCBvZiB0aGUgVVJMJ3MgcGF0aCBtdXN0IHJlcHJlc2VudCB0aGUgZnVsbHkKICAgcXVhbGlmaWVkIG5hbWUgb2YgdGhlIHR5cGUgKGFzIGluIGBwYXRoL2dvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbmApLgogICBUaGUgbmFtZSBzaG91bGQgYmUgaW4gYSBjYW5vbmljYWwgZm9ybSAoZS5nLiwgbGVhZGluZyAiLiIgaXMKICAgbm90IGFjY2VwdGVkKS4KICogQW4gSFRUUCBHRVQgb24gdGhlIFVSTCBtdXN0IHlpZWxkIGEgW2dvb2dsZS5wcm90b2J1Zi5UeXBlXVtdCiAgIHZhbHVlIGluIGJpbmFyeSBmb3JtYXQsIG9yIHByb2R1Y2UgYW4gZXJyb3IuCiAqIEFwcGxpY2F0aW9ucyBhcmUgYWxsb3dlZCB0byBjYWNoZSBsb29rdXAgcmVzdWx0cyBiYXNlZCBvbiB0aGUKICAgVVJMLCBvciBoYXZlIHRoZW0gcHJlY29tcGlsZWQgaW50byBhIGJpbmFyeSB0byBhdm9pZCBhbnkKICAgbG9va3VwLiBUaGVyZWZvcmUsIGJpbmFyeSBjb21wYXRpYmlsaXR5IG5lZWRzIHRvIGJlIHByZXNlcnZlZAogICBvbiBjaGFuZ2VzIHRvIHR5cGVzLiAoVXNlIHZlcnNpb25lZCB0eXBlIG5hbWVzIHRvIG1hbmFnZQogICBicmVha2luZyBjaGFuZ2VzLikKCiBTY2hlbWVzIG90aGVyIHRoYW4gYGh0dHBgLCBgaHR0cHNgIChvciB0aGUgZW1wdHkgc2NoZW1lKSBtaWdodCBiZQogdXNlZCB3aXRoIGltcGxlbWVudGF0aW9uIHNwZWNpZmljIHNlbWFudGljcy4KCgoOCgUEAAIABBIFkAECeQ0KDQoFBAACAAUSBJABAggKDQoFBAACAAESBJABCREKDQoFBAACAAMSBJABFBUKVwoEBAACARIEkwECEhpJIE11c3QgYmUgYSB2YWxpZCBzZXJpYWxpemVkIHByb3RvY29sIGJ1ZmZlciBvZiB0aGUgYWJvdmUgc3BlY2lmaWVkIHR5cGUuCgoPCgUEAAIBBBIGkwECkAEWCg0KBQQAAgEFEgSTAQIHCg0KBQQAAgEBEgSTAQgNCg0KBQQAAgEDEgSTARARYgZwcm90bzMKngkKKG1peGVyL2FkYXB0ZXIvbW9kZWwvdjFiZXRhMS9yZXBvcnQucHJvdG8SIWlzdGlvLm1peGVyLmFkYXB0ZXIubW9kZWwudjFiZXRhMRoUZ29nb3Byb3RvL2dvZ28ucHJvdG8iDgoMUmVwb3J0UmVzdWx0QjZaKGlzdGlvLmlvL2FwaS9taXhlci9hZGFwdGVyL21vZGVsL3YxYmV0YTHI4R4AqOIeAPDhHgBK6AcKBhIEDgAbFwq/BAoBDBIDDgASMrQEIENvcHlyaWdodCAyMDE4IElzdGlvIEF1dGhvcnMKCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAIKQoICgEIEgMSAD0KCwoECOcHABIDEgA9CgwKBQjnBwACEgMSBxEKDQoGCOcHAAIAEgMSBxEKDgoHCOcHAAIAARIDEgcRCgwKBQjnBwAHEgMSEjwKCQoCAwASAxQHHQoICgEIEgMWAC8KCwoECOcHARIDFgAvCgwKBQjnBwECEgMWByYKDQoGCOcHAQIAEgMWByYKDgoHCOcHAQIAARIDFgglCgwKBQjnBwEDEgMWKS4KCAoBCBIDFwAlCgsKBAjnBwISAxcAJQoMCgUI5wcCAhIDFwccCg0KBgjnBwICABIDFwccCg4KBwjnBwICAAESAxcIGwoMCgUI5wcCAxIDFx8kCggKAQgSAxgAKAoLCgQI5wcDEgMYACgKDAoFCOcHAwISAxgHHwoNCgYI5wcDAgASAxgHHwoOCgcI5wcDAgABEgMYCB4KDAoFCOcHAwMSAxgiJwozCgIEABIDGwAXGiggRXhwcmVzc2VzIHRoZSByZXN1bHQgb2YgYSByZXBvcnQgY2FsbC4KCgoKAwQAARIDGwgUYgZwcm90bzMKmikKHmdvb2dsZS9wcm90b2J1Zi9kdXJhdGlvbi5wcm90bxIPZ29vZ2xlLnByb3RvYnVmIjoKCER1cmF0aW9uEhgKB3NlY29uZHMYASABKANSB3NlY29uZHMSFAoFbmFub3MYAiABKAVSBW5hbm9zQnwKE2NvbS5nb29nbGUucHJvdG9idWZCDUR1cmF0aW9uUHJvdG9QAVoqZ2l0aHViLmNvbS9nb2xhbmcvcHJvdG9idWYvcHR5cGVzL2R1cmF0aW9u+AEBogIDR1BCqgIeR29vZ2xlLlByb3RvYnVmLldlbGxLbm93blR5cGVzSqQnCgYSBB4AdAEKzAwKAQwSAx4AEjLBDCBQcm90b2NvbCBCdWZmZXJzIC0gR29vZ2xlJ3MgZGF0YSBpbnRlcmNoYW5nZSBmb3JtYXQKIENvcHlyaWdodCAyMDA4IEdvb2dsZSBJbmMuICBBbGwgcmlnaHRzIHJlc2VydmVkLgogaHR0cHM6Ly9kZXZlbG9wZXJzLmdvb2dsZS5jb20vcHJvdG9jb2wtYnVmZmVycy8KCiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmUKIG1ldDoKCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodAogbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLgogICAgICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZQogY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lcgogaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZCB3aXRoIHRoZQogZGlzdHJpYnV0aW9uLgogICAgICogTmVpdGhlciB0aGUgbmFtZSBvZiBHb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0cwogY29udHJpYnV0b3JzIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkIGZyb20KIHRoaXMgc29mdHdhcmUgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uCgogVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwogIkFTIElTIiBBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1QKIExJTUlURUQgVE8sIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUgogQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFQKIE9XTkVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVOVEFMLAogU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVAogTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsCiBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTiBBTlkKIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksIE9SIFRPUlQKIChJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRQogT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KCggKAQISAyAIFwoICgEIEgMiADsKCwoECOcHABIDIgA7CgwKBQjnBwACEgMiBxcKDQoGCOcHAAIAEgMiBxcKDgoHCOcHAAIAARIDIgcXCgwKBQjnBwAHEgMiGjoKCAoBCBIDIwAfCgsKBAjnBwESAyMAHwoMCgUI5wcBAhIDIwcXCg0KBgjnBwECABIDIwcXCg4KBwjnBwECAAESAyMHFwoMCgUI5wcBAxIDIxoeCggKAQgSAyQAQQoLCgQI5wcCEgMkAEEKDAoFCOcHAgISAyQHEQoNCgYI5wcCAgASAyQHEQoOCgcI5wcCAgABEgMkBxEKDAoFCOcHAgcSAyQUQAoICgEIEgMlACwKCwoECOcHAxIDJQAsCgwKBQjnBwMCEgMlBxMKDQoGCOcHAwIAEgMlBxMKDgoHCOcHAwIAARIDJQcTCgwKBQjnBwMHEgMlFisKCAoBCBIDJgAuCgsKBAjnBwQSAyYALgoMCgUI5wcEAhIDJgcbCg0KBgjnBwQCABIDJgcbCg4KBwjnBwQCAAESAyYHGwoMCgUI5wcEBxIDJh4tCggKAQgSAycAIgoLCgQI5wcFEgMnACIKDAoFCOcHBQISAycHGgoNCgYI5wcFAgASAycHGgoOCgcI5wcFAgABEgMnBxoKDAoFCOcHBQMSAycdIQoICgEIEgMoACEKCwoECOcHBhIDKAAhCgwKBQjnBwYCEgMoBxgKDQoGCOcHBgIAEgMoBxgKDgoHCOcHBgIAARIDKAcYCgwKBQjnBwYHEgMoGyAKnxAKAgQAEgRmAHQBGpIQIEEgRHVyYXRpb24gcmVwcmVzZW50cyBhIHNpZ25lZCwgZml4ZWQtbGVuZ3RoIHNwYW4gb2YgdGltZSByZXByZXNlbnRlZAogYXMgYSBjb3VudCBvZiBzZWNvbmRzIGFuZCBmcmFjdGlvbnMgb2Ygc2Vjb25kcyBhdCBuYW5vc2Vjb25kCiByZXNvbHV0aW9uLiBJdCBpcyBpbmRlcGVuZGVudCBvZiBhbnkgY2FsZW5kYXIgYW5kIGNvbmNlcHRzIGxpa2UgImRheSIKIG9yICJtb250aCIuIEl0IGlzIHJlbGF0ZWQgdG8gVGltZXN0YW1wIGluIHRoYXQgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbgogdHdvIFRpbWVzdGFtcCB2YWx1ZXMgaXMgYSBEdXJhdGlvbiBhbmQgaXQgY2FuIGJlIGFkZGVkIG9yIHN1YnRyYWN0ZWQKIGZyb20gYSBUaW1lc3RhbXAuIFJhbmdlIGlzIGFwcHJveGltYXRlbHkgKy0xMCwwMDAgeWVhcnMuCgogIyBFeGFtcGxlcwoKIEV4YW1wbGUgMTogQ29tcHV0ZSBEdXJhdGlvbiBmcm9tIHR3byBUaW1lc3RhbXBzIGluIHBzZXVkbyBjb2RlLgoKICAgICBUaW1lc3RhbXAgc3RhcnQgPSAuLi47CiAgICAgVGltZXN0YW1wIGVuZCA9IC4uLjsKICAgICBEdXJhdGlvbiBkdXJhdGlvbiA9IC4uLjsKCiAgICAgZHVyYXRpb24uc2Vjb25kcyA9IGVuZC5zZWNvbmRzIC0gc3RhcnQuc2Vjb25kczsKICAgICBkdXJhdGlvbi5uYW5vcyA9IGVuZC5uYW5vcyAtIHN0YXJ0Lm5hbm9zOwoKICAgICBpZiAoZHVyYXRpb24uc2Vjb25kcyA8IDAgJiYgZHVyYXRpb24ubmFub3MgPiAwKSB7CiAgICAgICBkdXJhdGlvbi5zZWNvbmRzICs9IDE7CiAgICAgICBkdXJhdGlvbi5uYW5vcyAtPSAxMDAwMDAwMDAwOwogICAgIH0gZWxzZSBpZiAoZHVyYXRpb25zLnNlY29uZHMgPiAwICYmIGR1cmF0aW9uLm5hbm9zIDwgMCkgewogICAgICAgZHVyYXRpb24uc2Vjb25kcyAtPSAxOwogICAgICAgZHVyYXRpb24ubmFub3MgKz0gMTAwMDAwMDAwMDsKICAgICB9CgogRXhhbXBsZSAyOiBDb21wdXRlIFRpbWVzdGFtcCBmcm9tIFRpbWVzdGFtcCArIER1cmF0aW9uIGluIHBzZXVkbyBjb2RlLgoKICAgICBUaW1lc3RhbXAgc3RhcnQgPSAuLi47CiAgICAgRHVyYXRpb24gZHVyYXRpb24gPSAuLi47CiAgICAgVGltZXN0YW1wIGVuZCA9IC4uLjsKCiAgICAgZW5kLnNlY29uZHMgPSBzdGFydC5zZWNvbmRzICsgZHVyYXRpb24uc2Vjb25kczsKICAgICBlbmQubmFub3MgPSBzdGFydC5uYW5vcyArIGR1cmF0aW9uLm5hbm9zOwoKICAgICBpZiAoZW5kLm5hbm9zIDwgMCkgewogICAgICAgZW5kLnNlY29uZHMgLT0gMTsKICAgICAgIGVuZC5uYW5vcyArPSAxMDAwMDAwMDAwOwogICAgIH0gZWxzZSBpZiAoZW5kLm5hbm9zID49IDEwMDAwMDAwMDApIHsKICAgICAgIGVuZC5zZWNvbmRzICs9IDE7CiAgICAgICBlbmQubmFub3MgLT0gMTAwMDAwMDAwMDsKICAgICB9CgogRXhhbXBsZSAzOiBDb21wdXRlIER1cmF0aW9uIGZyb20gZGF0ZXRpbWUudGltZWRlbHRhIGluIFB5dGhvbi4KCiAgICAgdGQgPSBkYXRldGltZS50aW1lZGVsdGEoZGF5cz0zLCBtaW51dGVzPTEwKQogICAgIGR1cmF0aW9uID0gRHVyYXRpb24oKQogICAgIGR1cmF0aW9uLkZyb21UaW1lZGVsdGEodGQpCgogIyBKU09OIE1hcHBpbmcKCiBJbiBKU09OIGZvcm1hdCwgdGhlIER1cmF0aW9uIHR5cGUgaXMgZW5jb2RlZCBhcyBhIHN0cmluZyByYXRoZXIgdGhhbiBhbgogb2JqZWN0LCB3aGVyZSB0aGUgc3RyaW5nIGVuZHMgaW4gdGhlIHN1ZmZpeCAicyIgKGluZGljYXRpbmcgc2Vjb25kcykgYW5kCiBpcyBwcmVjZWRlZCBieSB0aGUgbnVtYmVyIG9mIHNlY29uZHMsIHdpdGggbmFub3NlY29uZHMgZXhwcmVzc2VkIGFzCiBmcmFjdGlvbmFsIHNlY29uZHMuIEZvciBleGFtcGxlLCAzIHNlY29uZHMgd2l0aCAwIG5hbm9zZWNvbmRzIHNob3VsZCBiZQogZW5jb2RlZCBpbiBKU09OIGZvcm1hdCBhcyAiM3MiLCB3aGlsZSAzIHNlY29uZHMgYW5kIDEgbmFub3NlY29uZCBzaG91bGQKIGJlIGV4cHJlc3NlZCBpbiBKU09OIGZvcm1hdCBhcyAiMy4wMDAwMDAwMDFzIiwgYW5kIDMgc2Vjb25kcyBhbmQgMQogbWljcm9zZWNvbmQgc2hvdWxkIGJlIGV4cHJlc3NlZCBpbiBKU09OIGZvcm1hdCBhcyAiMy4wMDAwMDFzIi4KCgoKCgoDBAABEgNmCBAK3AEKBAQAAgASA2sCFBrOASBTaWduZWQgc2Vjb25kcyBvZiB0aGUgc3BhbiBvZiB0aW1lLiBNdXN0IGJlIGZyb20gLTMxNSw1NzYsMDAwLDAwMAogdG8gKzMxNSw1NzYsMDAwLDAwMCBpbmNsdXNpdmUuIE5vdGU6IHRoZXNlIGJvdW5kcyBhcmUgY29tcHV0ZWQgZnJvbToKIDYwIHNlYy9taW4gKiA2MCBtaW4vaHIgKiAyNCBoci9kYXkgKiAzNjUuMjUgZGF5cy95ZWFyICogMTAwMDAgeWVhcnMKCg0KBQQAAgAEEgRrAmYSCgwKBQQAAgAFEgNrAgcKDAoFBAACAAESA2sIDwoMCgUEAAIAAxIDaxITCoMDCgQEAAIBEgNzAhIa9QIgU2lnbmVkIGZyYWN0aW9ucyBvZiBhIHNlY29uZCBhdCBuYW5vc2Vjb25kIHJlc29sdXRpb24gb2YgdGhlIHNwYW4KIG9mIHRpbWUuIER1cmF0aW9ucyBsZXNzIHRoYW4gb25lIHNlY29uZCBhcmUgcmVwcmVzZW50ZWQgd2l0aCBhIDAKIGBzZWNvbmRzYCBmaWVsZCBhbmQgYSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSBgbmFub3NgIGZpZWxkLiBGb3IgZHVyYXRpb25zCiBvZiBvbmUgc2Vjb25kIG9yIG1vcmUsIGEgbm9uLXplcm8gdmFsdWUgZm9yIHRoZSBgbmFub3NgIGZpZWxkIG11c3QgYmUKIG9mIHRoZSBzYW1lIHNpZ24gYXMgdGhlIGBzZWNvbmRzYCBmaWVsZC4gTXVzdCBiZSBmcm9tIC05OTksOTk5LDk5OQogdG8gKzk5OSw5OTksOTk5IGluY2x1c2l2ZS4KCg0KBQQAAgEEEgRzAmsUCgwKBQQAAgEFEgNzAgcKDAoFBAACAQESA3MIDQoMCgUEAAIBAxIDcxARYgZwcm90bzMKwzEKH2dvb2dsZS9wcm90b2J1Zi90aW1lc3RhbXAucHJvdG8SD2dvb2dsZS5wcm90b2J1ZiI7CglUaW1lc3RhbXASGAoHc2Vjb25kcxgBIAEoA1IHc2Vjb25kcxIUCgVuYW5vcxgCIAEoBVIFbmFub3NCfgoTY29tLmdvb2dsZS5wcm90b2J1ZkIOVGltZXN0YW1wUHJvdG9QAVorZ2l0aHViLmNvbS9nb2xhbmcvcHJvdG9idWYvcHR5cGVzL3RpbWVzdGFtcPgBAaICA0dQQqoCHkdvb2dsZS5Qcm90b2J1Zi5XZWxsS25vd25UeXBlc0rJLwoHEgUeAIQBAQrMDAoBDBIDHgASMsEMIFByb3RvY29sIEJ1ZmZlcnMgLSBHb29nbGUncyBkYXRhIGludGVyY2hhbmdlIGZvcm1hdAogQ29weXJpZ2h0IDIwMDggR29vZ2xlIEluYy4gIEFsbCByaWdodHMgcmVzZXJ2ZWQuCiBodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9wcm90b2NvbC1idWZmZXJzLwoKIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dAogbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZQogbWV0OgoKICAgICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0CiBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCiAgICAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlCiBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyCiBpbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlCiBkaXN0cmlidXRpb24uCiAgICAgKiBOZWl0aGVyIHRoZSBuYW1lIG9mIEdvb2dsZSBJbmMuIG5vciB0aGUgbmFtZXMgb2YgaXRzCiBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQgZnJvbQogdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi4KCiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTCiAiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVAogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SCiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVAogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsCiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UCiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSwKIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWQogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVAogKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFCiBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLgoKCAoBAhIDIAgXCggKAQgSAyIAOwoLCgQI5wcAEgMiADsKDAoFCOcHAAISAyIHFwoNCgYI5wcAAgASAyIHFwoOCgcI5wcAAgABEgMiBxcKDAoFCOcHAAcSAyIaOgoICgEIEgMjAB8KCwoECOcHARIDIwAfCgwKBQjnBwECEgMjBxcKDQoGCOcHAQIAEgMjBxcKDgoHCOcHAQIAARIDIwcXCgwKBQjnBwEDEgMjGh4KCAoBCBIDJABCCgsKBAjnBwISAyQAQgoMCgUI5wcCAhIDJAcRCg0KBgjnBwICABIDJAcRCg4KBwjnBwICAAESAyQHEQoMCgUI5wcCBxIDJBRBCggKAQgSAyUALAoLCgQI5wcDEgMlACwKDAoFCOcHAwISAyUHEwoNCgYI5wcDAgASAyUHEwoOCgcI5wcDAgABEgMlBxMKDAoFCOcHAwcSAyUWKwoICgEIEgMmAC8KCwoECOcHBBIDJgAvCgwKBQjnBwQCEgMmBxsKDQoGCOcHBAIAEgMmBxsKDgoHCOcHBAIAARIDJgcbCgwKBQjnBwQHEgMmHi4KCAoBCBIDJwAiCgsKBAjnBwUSAycAIgoMCgUI5wcFAhIDJwcaCg0KBgjnBwUCABIDJwcaCg4KBwjnBwUCAAESAycHGgoMCgUI5wcFAxIDJx0hCggKAQgSAygAIQoLCgQI5wcGEgMoACEKDAoFCOcHBgISAygHGAoNCgYI5wcGAgASAygHGAoOCgcI5wcGAgABEgMoBxgKDAoFCOcHBgcSAygbIAqdGgoCBAASBXgAhAEBGo8aIEEgVGltZXN0YW1wIHJlcHJlc2VudHMgYSBwb2ludCBpbiB0aW1lIGluZGVwZW5kZW50IG9mIGFueSB0aW1lIHpvbmUKIG9yIGNhbGVuZGFyLCByZXByZXNlbnRlZCBhcyBzZWNvbmRzIGFuZCBmcmFjdGlvbnMgb2Ygc2Vjb25kcyBhdAogbmFub3NlY29uZCByZXNvbHV0aW9uIGluIFVUQyBFcG9jaCB0aW1lLiBJdCBpcyBlbmNvZGVkIHVzaW5nIHRoZQogUHJvbGVwdGljIEdyZWdvcmlhbiBDYWxlbmRhciB3aGljaCBleHRlbmRzIHRoZSBHcmVnb3JpYW4gY2FsZW5kYXIKIGJhY2t3YXJkcyB0byB5ZWFyIG9uZS4gSXQgaXMgZW5jb2RlZCBhc3N1bWluZyBhbGwgbWludXRlcyBhcmUgNjAKIHNlY29uZHMgbG9uZywgaS5lLiBsZWFwIHNlY29uZHMgYXJlICJzbWVhcmVkIiBzbyB0aGF0IG5vIGxlYXAgc2Vjb25kCiB0YWJsZSBpcyBuZWVkZWQgZm9yIGludGVycHJldGF0aW9uLiBSYW5nZSBpcyBmcm9tCiAwMDAxLTAxLTAxVDAwOjAwOjAwWiB0byA5OTk5LTEyLTMxVDIzOjU5OjU5Ljk5OTk5OTk5OVouCiBCeSByZXN0cmljdGluZyB0byB0aGF0IHJhbmdlLCB3ZSBlbnN1cmUgdGhhdCB3ZSBjYW4gY29udmVydCB0bwogYW5kIGZyb20gIFJGQyAzMzM5IGRhdGUgc3RyaW5ncy4KIFNlZSBbaHR0cHM6Ly93d3cuaWV0Zi5vcmcvcmZjL3JmYzMzMzkudHh0XShodHRwczovL3d3dy5pZXRmLm9yZy9yZmMvcmZjMzMzOS50eHQpLgoKICMgRXhhbXBsZXMKCiBFeGFtcGxlIDE6IENvbXB1dGUgVGltZXN0YW1wIGZyb20gUE9TSVggYHRpbWUoKWAuCgogICAgIFRpbWVzdGFtcCB0aW1lc3RhbXA7CiAgICAgdGltZXN0YW1wLnNldF9zZWNvbmRzKHRpbWUoTlVMTCkpOwogICAgIHRpbWVzdGFtcC5zZXRfbmFub3MoMCk7CgogRXhhbXBsZSAyOiBDb21wdXRlIFRpbWVzdGFtcCBmcm9tIFBPU0lYIGBnZXR0aW1lb2ZkYXkoKWAuCgogICAgIHN0cnVjdCB0aW1ldmFsIHR2OwogICAgIGdldHRpbWVvZmRheSgmdHYsIE5VTEwpOwoKICAgICBUaW1lc3RhbXAgdGltZXN0YW1wOwogICAgIHRpbWVzdGFtcC5zZXRfc2Vjb25kcyh0di50dl9zZWMpOwogICAgIHRpbWVzdGFtcC5zZXRfbmFub3ModHYudHZfdXNlYyAqIDEwMDApOwoKIEV4YW1wbGUgMzogQ29tcHV0ZSBUaW1lc3RhbXAgZnJvbSBXaW4zMiBgR2V0U3lzdGVtVGltZUFzRmlsZVRpbWUoKWAuCgogICAgIEZJTEVUSU1FIGZ0OwogICAgIEdldFN5c3RlbVRpbWVBc0ZpbGVUaW1lKCZmdCk7CiAgICAgVUlOVDY0IHRpY2tzID0gKCgoVUlOVDY0KWZ0LmR3SGlnaERhdGVUaW1lKSA8PCAzMikgfCBmdC5kd0xvd0RhdGVUaW1lOwoKICAgICAvLyBBIFdpbmRvd3MgdGljayBpcyAxMDAgbmFub3NlY29uZHMuIFdpbmRvd3MgZXBvY2ggMTYwMS0wMS0wMVQwMDowMDowMFoKICAgICAvLyBpcyAxMTY0NDQ3MzYwMCBzZWNvbmRzIGJlZm9yZSBVbml4IGVwb2NoIDE5NzAtMDEtMDFUMDA6MDA6MDBaLgogICAgIFRpbWVzdGFtcCB0aW1lc3RhbXA7CiAgICAgdGltZXN0YW1wLnNldF9zZWNvbmRzKChJTlQ2NCkgKCh0aWNrcyAvIDEwMDAwMDAwKSAtIDExNjQ0NDczNjAwTEwpKTsKICAgICB0aW1lc3RhbXAuc2V0X25hbm9zKChJTlQzMikgKCh0aWNrcyAlIDEwMDAwMDAwKSAqIDEwMCkpOwoKIEV4YW1wbGUgNDogQ29tcHV0ZSBUaW1lc3RhbXAgZnJvbSBKYXZhIGBTeXN0ZW0uY3VycmVudFRpbWVNaWxsaXMoKWAuCgogICAgIGxvbmcgbWlsbGlzID0gU3lzdGVtLmN1cnJlbnRUaW1lTWlsbGlzKCk7CgogICAgIFRpbWVzdGFtcCB0aW1lc3RhbXAgPSBUaW1lc3RhbXAubmV3QnVpbGRlcigpLnNldFNlY29uZHMobWlsbGlzIC8gMTAwMCkKICAgICAgICAgLnNldE5hbm9zKChpbnQpICgobWlsbGlzICUgMTAwMCkgKiAxMDAwMDAwKSkuYnVpbGQoKTsKCgogRXhhbXBsZSA1OiBDb21wdXRlIFRpbWVzdGFtcCBmcm9tIGN1cnJlbnQgdGltZSBpbiBQeXRob24uCgogICAgIHRpbWVzdGFtcCA9IFRpbWVzdGFtcCgpCiAgICAgdGltZXN0YW1wLkdldEN1cnJlbnRUaW1lKCkKCiAjIEpTT04gTWFwcGluZwoKIEluIEpTT04gZm9ybWF0LCB0aGUgVGltZXN0YW1wIHR5cGUgaXMgZW5jb2RlZCBhcyBhIHN0cmluZyBpbiB0aGUKIFtSRkMgMzMzOV0oaHR0cHM6Ly93d3cuaWV0Zi5vcmcvcmZjL3JmYzMzMzkudHh0KSBmb3JtYXQuIFRoYXQgaXMsIHRoZQogZm9ybWF0IGlzICJ7eWVhcn0te21vbnRofS17ZGF5fVR7aG91cn06e21pbn06e3NlY31bLntmcmFjX3NlY31dWiIKIHdoZXJlIHt5ZWFyfSBpcyBhbHdheXMgZXhwcmVzc2VkIHVzaW5nIGZvdXIgZGlnaXRzIHdoaWxlIHttb250aH0sIHtkYXl9LAoge2hvdXJ9LCB7bWlufSwgYW5kIHtzZWN9IGFyZSB6ZXJvLXBhZGRlZCB0byB0d28gZGlnaXRzIGVhY2guIFRoZSBmcmFjdGlvbmFsCiBzZWNvbmRzLCB3aGljaCBjYW4gZ28gdXAgdG8gOSBkaWdpdHMgKGkuZS4gdXAgdG8gMSBuYW5vc2Vjb25kIHJlc29sdXRpb24pLAogYXJlIG9wdGlvbmFsLiBUaGUgIloiIHN1ZmZpeCBpbmRpY2F0ZXMgdGhlIHRpbWV6b25lICgiVVRDIik7IHRoZSB0aW1lem9uZQogaXMgcmVxdWlyZWQsIHRob3VnaCBvbmx5IFVUQyAoYXMgaW5kaWNhdGVkIGJ5ICJaIikgaXMgcHJlc2VudGx5IHN1cHBvcnRlZC4KCiBGb3IgZXhhbXBsZSwgIjIwMTctMDEtMTVUMDE6MzA6MTUuMDFaIiBlbmNvZGVzIDE1LjAxIHNlY29uZHMgcGFzdAogMDE6MzAgVVRDIG9uIEphbnVhcnkgMTUsIDIwMTcuCgogSW4gSmF2YVNjcmlwdCwgb25lIGNhbiBjb252ZXJ0IGEgRGF0ZSBvYmplY3QgdG8gdGhpcyBmb3JtYXQgdXNpbmcgdGhlCiBzdGFuZGFyZCBbdG9JU09TdHJpbmcoKV0oaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvSmF2YVNjcmlwdC9SZWZlcmVuY2UvR2xvYmFsX09iamVjdHMvRGF0ZS90b0lTT1N0cmluZ10KIG1ldGhvZC4gSW4gUHl0aG9uLCBhIHN0YW5kYXJkIGBkYXRldGltZS5kYXRldGltZWAgb2JqZWN0IGNhbiBiZSBjb252ZXJ0ZWQKIHRvIHRoaXMgZm9ybWF0IHVzaW5nIFtgc3RyZnRpbWVgXShodHRwczovL2RvY3MucHl0aG9uLm9yZy8yL2xpYnJhcnkvdGltZS5odG1sI3RpbWUuc3RyZnRpbWUpCiB3aXRoIHRoZSB0aW1lIGZvcm1hdCBzcGVjICclWS0lbS0lZFQlSDolTTolUy4lZlonLiBMaWtld2lzZSwgaW4gSmF2YSwgb25lCiBjYW4gdXNlIHRoZSBKb2RhIFRpbWUncyBbYElTT0RhdGVUaW1lRm9ybWF0LmRhdGVUaW1lKClgXSgKIGh0dHA6Ly93d3cuam9kYS5vcmcvam9kYS10aW1lL2FwaWRvY3Mvb3JnL2pvZGEvdGltZS9mb3JtYXQvSVNPRGF0ZVRpbWVGb3JtYXQuaHRtbCNkYXRlVGltZS0tKQogdG8gb2J0YWluIGEgZm9ybWF0dGVyIGNhcGFibGUgb2YgZ2VuZXJhdGluZyB0aW1lc3RhbXBzIGluIHRoaXMgZm9ybWF0LgoKCgoKCgMEAAESA3gIEQqcAQoEBAACABIDfQIUGo4BIFJlcHJlc2VudHMgc2Vjb25kcyBvZiBVVEMgdGltZSBzaW5jZSBVbml4IGVwb2NoCiAxOTcwLTAxLTAxVDAwOjAwOjAwWi4gTXVzdCBiZSBmcm9tIDAwMDEtMDEtMDFUMDA6MDA6MDBaIHRvCiA5OTk5LTEyLTMxVDIzOjU5OjU5WiBpbmNsdXNpdmUuCgoNCgUEAAIABBIEfQJ4EwoMCgUEAAIABRIDfQIHCgwKBQQAAgABEgN9CA8KDAoFBAACAAMSA30SEwrlAQoEBAACARIEgwECEhrWASBOb24tbmVnYXRpdmUgZnJhY3Rpb25zIG9mIGEgc2Vjb25kIGF0IG5hbm9zZWNvbmQgcmVzb2x1dGlvbi4gTmVnYXRpdmUKIHNlY29uZCB2YWx1ZXMgd2l0aCBmcmFjdGlvbnMgbXVzdCBzdGlsbCBoYXZlIG5vbi1uZWdhdGl2ZSBuYW5vcyB2YWx1ZXMKIHRoYXQgY291bnQgZm9yd2FyZCBpbiB0aW1lLiBNdXN0IGJlIGZyb20gMCB0byA5OTksOTk5LDk5OQogaW5jbHVzaXZlLgoKDgoFBAACAQQSBYMBAn0UCg0KBQQAAgEFEgSDAQIHCg0KBQQAAgEBEgSDAQgNCg0KBQQAAgEDEgSDARARYgZwcm90bzMK0jEKGXBvbGljeS92MWJldGExL3R5cGUucHJvdG8SFGlzdGlvLnBvbGljeS52MWJldGExGh5nb29nbGUvcHJvdG9idWYvZHVyYXRpb24ucHJvdG8aH2dvb2dsZS9wcm90b2J1Zi90aW1lc3RhbXAucHJvdG8i1wQKBVZhbHVlEiMKDHN0cmluZ192YWx1ZRgBIAEoCUgAUgtzdHJpbmdWYWx1ZRIhCgtpbnQ2NF92YWx1ZRgCIAEoA0gAUgppbnQ2NFZhbHVlEiMKDGRvdWJsZV92YWx1ZRgDIAEoAUgAUgtkb3VibGVWYWx1ZRIfCgpib29sX3ZhbHVlGAQgASgISABSCWJvb2xWYWx1ZRJLChBpcF9hZGRyZXNzX3ZhbHVlGAUgASgLMh8uaXN0aW8ucG9saWN5LnYxYmV0YTEuSVBBZGRyZXNzSABSDmlwQWRkcmVzc1ZhbHVlEkoKD3RpbWVzdGFtcF92YWx1ZRgGIAEoCzIfLmlzdGlvLnBvbGljeS52MWJldGExLlRpbWVTdGFtcEgAUg50aW1lc3RhbXBWYWx1ZRJHCg5kdXJhdGlvbl92YWx1ZRgHIAEoCzIeLmlzdGlvLnBvbGljeS52MWJldGExLkR1cmF0aW9uSABSDWR1cmF0aW9uVmFsdWUSVAoTZW1haWxfYWRkcmVzc192YWx1ZRgIIAEoCzIiLmlzdGlvLnBvbGljeS52MWJldGExLkVtYWlsQWRkcmVzc0gAUhFlbWFpbEFkZHJlc3NWYWx1ZRJFCg5kbnNfbmFtZV92YWx1ZRgJIAEoCzIdLmlzdGlvLnBvbGljeS52MWJldGExLkROU05hbWVIAFIMZG5zTmFtZVZhbHVlEjgKCXVyaV92YWx1ZRgKIAEoCzIZLmlzdGlvLnBvbGljeS52MWJldGExLlVyaUgAUgh1cmlWYWx1ZUIHCgV2YWx1ZSIhCglJUEFkZHJlc3MSFAoFdmFsdWUYASABKAxSBXZhbHVlIjsKCER1cmF0aW9uEi8KBXZhbHVlGAEgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uUgV2YWx1ZSI9CglUaW1lU3RhbXASMAoFdmFsdWUYASABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wUgV2YWx1ZSIfCgdETlNOYW1lEhQKBXZhbHVlGAEgASgJUgV2YWx1ZSIkCgxFbWFpbEFkZHJlc3MSFAoFdmFsdWUYASABKAlSBXZhbHVlIhsKA1VyaRIUCgV2YWx1ZRgBIAEoCVIFdmFsdWVCHVobaXN0aW8uaW8vYXBpL3BvbGljeS92MWJldGExStkpCgcSBQ4AgwEBCr8ECgEMEgMOABIytAQgQ29weXJpZ2h0IDIwMTggSXN0aW8gQXV0aG9ycwoKIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CgogICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAoKIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUKIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoKqAIKAQISAxUIHBpOIERlc2NyaWJlcyB0aGUgcnVsZXMgdXNlZCB0byBjb25maWd1cmUgTWl4ZXIncyBwb2xpY3kgYW5kIHRlbGVtZXRyeSBmZWF0dXJlcy4KMs0BICR0aXRsZTogUnVsZXMKICRkZXNjcmlwdGlvbjogRGVzY3JpYmVzIHRoZSBydWxlcyB1c2VkIHRvIGNvbmZpZ3VyZSBNaXhlcidzIHBvbGljeSBhbmQgdGVsZW1ldHJ5IGZlYXR1cmVzLgogJGxvY2F0aW9uOiBodHRwczovL2lzdGlvLmlvL2RvY3MvcmVmZXJlbmNlL2NvbmZpZy9wb2xpY3ktYW5kLXRlbGVtZXRyeS9pc3Rpby5wb2xpY3kudjFiZXRhMS5odG1sCgoICgEIEgMXADAKCwoECOcHABIDFwAwCgwKBQjnBwACEgMXBxEKDQoGCOcHAAIAEgMXBxEKDgoHCOcHAAIAARIDFwcRCgwKBQjnBwAHEgMXEi8KCQoCAwASAxkHJwoJCgIDARIDGgcoCsgGCgIEABIEJQBFARq7BiBBbiBpbnN0YW5jZSBmaWVsZCBvZiB0eXBlIFZhbHVlIGRlbm90ZXMgdGhhdCB0aGUgZXhwcmVzc2lvbiBmb3IgdGhlIGZpZWxkIGlzIG9mIGR5bmFtaWMgdHlwZSBhbmQgY2FuIGV2YWx1YXRlIHRvIGFueQogW1ZhbHVlVHlwZV1baXN0aW8ucG9saWN5LnYxYmV0YTEuVmFsdWVUeXBlXSBlbnVtIHZhbHVlcy4gRm9yIGV4YW1wbGUsIHdoZW4KIGF1dGhvcmluZyBhbiBpbnN0YW5jZSBjb25maWd1cmF0aW9uIGZvciBhIHRlbXBsYXRlIHRoYXQgaGFzIGEgZmllbGQgYGRhdGFgIG9mIHR5cGUgYGlzdGlvLnBvbGljeS52MWJldGExLlZhbHVlYCwKIGJvdGggb2YgdGhlIGZvbGxvd2luZyBleHByZXNzaW9ucyBhcmUgdmFsaWQgYGRhdGE6IHNvdXJjZS5pcCB8IGlwKCIwLjAuMC4wIilgLCBgZGF0YTogcmVxdWVzdC5pZCB8ICIiYDsKIHRoZSByZXN1bHRpbmcgdHlwZSBpcyBlaXRoZXIgVmFsdWVUeXBlLklQX0FERFJFU1Mgb3IgVmFsdWVUeXBlLlNUUklORyBmb3IgdGhlIHR3byBjYXNlcyByZXNwZWN0aXZlbHkuCgogT2JqZWN0cyBvZiB0eXBlIFZhbHVlIGFyZSBhbHNvIHBhc3NlZCB0byB0aGUgYWRhcHRlcnMgZHVyaW5nIHJlcXVlc3QtdGltZS4gVGhlcmUgaXMgYSAxOjEgbWFwcGluZyBiZXR3ZWVuCiBvbmVvZiBmaWVsZHMgaW4gYFZh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c3RhbmNlIGZpZWxkcyBvZgogdHlwZSBEdXJhdGlvbgoKCgoDBAIBEgNWCBAKPAoEBAICABIDWAQnGi8gRHVyYXRpb24gZW5jb2RlZCBhcyBnb29nbGUucHJvdG9idWYuRHVyYXRpb24uCgoNCgUEAgIABBIEWARWEgoMCgUEAgIABhIDWAQcCgwKBQQCAgABEgNYHSIKDAoFBAICAAMSA1glJgqpAgoCBAMSBGAAYwEanAIgQW4gaW5zdGFuY2UgZmllbGQgb2YgdHlwZSBUaW1lU3RhbXAgZGVub3RlcyB0aGF0IHRoZSBleHByZXNzaW9uIGZvciB0aGUgZmllbGQgbXVzdCBldmFsdWF0ZSB0bwogW1ZhbHVlVHlwZS5USU1FU1RBTVBdW2lzdGlvLnBvbGljeS52MWJldGExLlZhbHVlVHlwZS5USU1FU1RBTVBdCgogT2JqZWN0cyBvZiB0eXBlIFRpbWVTdGFtcCBhcmUgYWxzbyBwYXNzZWQgdG8gdGhlIGFkYXB0ZXJzIGR1cmluZyByZXF1ZXN0LXRpbWUgZm9yIHRoZSBpbnN0YW5jZSBmaWVsZHMgb2YKIHR5cGUgVGltZVN0YW1wCgoKCgMEAwESA2AIEQo+CgQEAwIAEgNiBCgaMSBUaW1lU3RhbXAgZW5jb2RlZCBhcyBnb29nbGUucHJvdG9idWYuVGltZXN0YW1wLgoKDQoFBAMCAAQSBGIEYBMKDAoFBAMCAAYSA2IEHQoMCgUEAwIAARIDYh4jCgwKBQQDAgADEgNiJicKoQIKAgQEEgRqAG0BGpQCIEFuIGluc3RhbmNlIGZpZWxkIG9mIHR5cGUgRE5TTmFtZSBkZW5vdGVzIHRoYXQgdGhlIGV4cHJlc3Npb24gZm9yIHRoZSBmaWVsZCBtdXN0IGV2YWx1YXRlIHRvCiBbVmFsdWVUeXBlLkROU19OQU1FXVtpc3Rpby5wb2xpY3kudjFiZXRhMS5WYWx1ZVR5cGUuRE5TX05BTUVdCgogT2JqZWN0cyBvZiB0eXBlIEROU05hbWUgYXJlIGFsc28gcGFzc2VkIHRvIHRoZSBhZGFwdGVycyBkdXJpbmcgcmVxdWVzdC10aW1lIGZvciB0aGUgaW5zdGFuY2UgZmllbGRzIG9mCiB0eXBlIEROU05hbWUKCgoKAwQEARIDaggPCikKBAQEAgASA2wEFRocIEROU05hbWUgZW5jb2RlZCBhcyBzdHJpbmcuCgoNCgUEBAIABBIEbARqEQoMCgUEBAIABRIDbAQKCgwKBQQEAgABEgNsCxAKDAoFBAQCAAMSA2wTFArbAgoCBAUSBHUAeAEazgIgRE8gTk9UIFVTRSAhISBVbmRlciBEZXZlbG9wbWVudAogQW4gaW5zdGFuY2UgZmllbGQgb2YgdHlwZSBFbWFpbEFkZHJlc3MgZGVub3RlcyB0aGF0IHRoZSBleHByZXNzaW9uIGZvciB0aGUgZmllbGQgbXVzdCBldmFsdWF0ZSB0bwogW1ZhbHVlVHlwZS5FTUFJTF9BRERSRVNTXVtpc3Rpby5wb2xpY3kudjFiZXRhMS5WYWx1ZVR5cGUuRU1BSUxfQUREUkVTU10KCiBPYmplY3RzIG9mIHR5cGUgRW1haWxBZGRyZXNzIGFyZSBhbHNvIHBhc3NlZCB0byB0aGUgYWRhcHRlcnMgZHVyaW5nIHJlcXVlc3QtdGltZSBmb3IgdGhlIGluc3RhbmNlIGZpZWxkcyBvZgogdHlwZSBFbWFpbEFkZHJlc3MKCgoKAwQFARIDdQgUCi4KBAQFAgASA3cEFRohIEVtYWlsQWRkcmVzcyBlbmNvZGVkIGFzIHN0cmluZy4KCg0KBQQFAgAEEgR3BHUWCgwKBQQFAgAFEgN3BAoKDAoFBAUCAAESA3cLEAoMCgUEBQIAAxIDdxMUCq4CCgIEBhIGgAEAgwEBGp8CIERPIE5PVCBVU0UgISEgVW5kZXIgRGV2ZWxvcG1lbnQKIEFuIGluc3RhbmNlIGZpZWxkIG9mIHR5cGUgVXJpIGRlbm90ZXMgdGhhdCB0aGUgZXhwcmVzc2lvbiBmb3IgdGhlIGZpZWxkIG11c3QgZXZhbHVhdGUgdG8KIFtWYWx1ZVR5cGUuVVJJXVtpc3Rpby5wb2xpY3kudjFiZXRhMS5WYWx1ZVR5cGUuVVJJXQoKIE9iamVjdHMgb2YgdHlwZSBVcmkgYXJlIGFsc28gcGFzc2VkIHRvIHRoZSBhZGFwdGVycyBkdXJpbmcgcmVxdWVzdC10aW1lIGZvciB0aGUgaW5zdGFuY2UgZmllbGRzIG9mCiB0eXBlIFVyaQoKCwoDBAYBEgSAAQgLCiYKBAQGAgASBIIBBBUaGCBVcmkgZW5jb2RlZCBhcyBzdHJpbmcuCgoPCgUEBgIABBIGggEEgAENCg0KBQQGAgAFEgSCAQQKCg0KBQQGAgABEgSCAQsQCg0KBQQGAgADEgSCARMUYgZwcm90bzMK1BAKH3BvbGljeS92MWJldGExL3ZhbHVlX3R5cGUucHJvdG8SFGlzdGlvLnBvbGljeS52MWJldGExKrsBCglWYWx1ZVR5cGUSGgoWVkFMVUVfVFlQRV9VTlNQRUNJRklFRBAAEgoKBlNUUklORxABEgkKBUlOVDY0EAISCgoGRE9VQkxFEAMSCAoEQk9PTBAEEg0KCVRJTUVTVEFNUBAFEg4KCklQX0FERFJFU1MQBhIRCg1FTUFJTF9BRERSRVNTEAcSBwoDVVJJEAgSDAoIRE5TX05BTUUQCRIMCghEVVJBVElPThAKEg4KClNUUklOR19NQVAQC0IdWhtpc3Rpby5pby9hcGkvcG9saWN5L3YxYmV0YTFKtQ4KBhIEDgA8AQq/BAoBDBIDDgASMrQEIENvcHlyaWdodCAyMDE4IElzdGlvIEF1dGhvcnMKCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAIHAoICgEIEgMSADAKCwoECOcHABIDEgAwCgwKBQjnBwACEgMSBxEKDQoGCOcHAAIAEgMSBxEKDgoHCOcHAAIAARIDEgcRCgwKBQjnBwAHEgMSEi8KlgIKAgUAEgQYADwBGokCIFZhbHVlVHlwZSBkZXNjcmliZXMgdGhlIHR5cGVzIHRoYXQgdmFsdWVzIGluIHRoZSBJc3RpbyBzeXN0ZW0gY2FuIHRha2UuIFRoZXNlCiBhcmUgdXNlZCB0byBkZXNjcmliZSB0aGUgdHlwZSBvZiBBdHRyaWJ1dGVzIGF0IHJ1biB0aW1lLCBkZXNjcmliZSB0aGUgdHlwZSBvZgogdGhlIHJlc3VsdCBvZiBldmFsdWF0aW5nIGFuIGV4cHJlc3Npb24sIGFuZCB0byBkZXNjcmliZSB0aGUgcnVudGltZSB0eXBlIG9mCiBmaWVsZHMgb2Ygb3RoZXIgZGVzY3JpcHRvcnMuCgoKCgMFAAESAxgFDgomCgQFAAIAEgMaBB8aGSBJbnZhbGlkLCBkZWZhdWx0IHZhbHVlLgoKDAoFBQACAAESAxoEGgoMCgUFAAIAAhIDGh0eCjkKBAUAAgESAx0EDxosIEFuIHVuZGlzY3JpbWluYXRlZCB2YXJpYWJsZS1sZW5ndGggc3RyaW5nLgoKDAoFBQACAQESAx0ECgoMCgUFAAIBAhIDHQ0OCjgKBAUAAgISAyAEDhorIEFuIHVuZGlzY3JpbWluYXRlZCA2NC1iaXQgc2lnbmVkIGludGVnZXIuCgoMCgUFAAICARIDIAQJCgwKBQUAAgICEgMgDA0KPgoEBQACAxIDIwQPGjEgQW4gdW5kaXNjcmltaW5hdGVkIDY0LWJpdCBmbG9hdGluZy1wb2ludCB2YWx1ZS4KCgwKBQUAAgMBEgMjBAoKDAoFBQACAwISAyMNDgowCgQFAAIEEgMmBA0aIyBBbiB1bmRpc2NyaW1pbmF0ZWQgYm9vbGVhbiB2YWx1ZS4KCgwKBQUAAgQBEgMmBAgKDAoFBQACBAISAyYLDAofCgQFAAIFEgMpBBIaEiBBIHBvaW50IGluIHRpbWUuCgoMCgUFAAIFARIDKQQNCgwKBQUAAgUCEgMpEBEKHQoEBQACBhIDLAQTGhAgQW4gSVAgYWRkcmVzcy4KCgwKBQUAAgYBEgMsBA4KDAoFBQACBgISAywREgogCgQFAAIHEgMvBBYaEyBBbiBlbWFpbCBhZGRyZXNzLgoKDAoFBQACBwESAy8EEQoMCgUFAAIHAhIDLxQVChUKBAUAAggSAzIEDBoIIEEgVVJJLgoKDAoFBQACCAESAzIEBwoMCgUFAAIIAhIDMgoLChoKBAUAAgkSAzUEERoNIEEgRE5TIG5hbWUuCgoMCgUFAAIJARIDNQQMCgwKBQUAAgkCEgM1DxAKMQoEBQACChIDOAQSGiQgQSBzcGFuIGJldHdlZW4gdHdvIHBvaW50cyBpbiB0aW1lLgoKDAoFBQACCgESAzgEDAoMCgUFAAIKAhIDOA8RCkEKBAUAAgsSAzsEFBo0IEEgbWFwIHN0cmluZyAtPiBzdHJpbmcsIHR5cGljYWxseSB1c2VkIGJ5IGhlYWRlcnMuCgoMCgUFAAILARIDOwQOCgwKBQUAAgsCEgM7ERNiBnByb3RvMwqGeQo4bWl4ZXIvdGVtcGxhdGUvdHJhY2VzcGFuL3RyYWNlc3Bhbl9oYW5kbGVyX3NlcnZpY2UucHJvdG8SCXRyYWNlc3BhbhoUZ29nb3Byb3RvL2dvZ28ucHJvdG8aLG1peGVyL2FkYXB0ZXIvbW9kZWwvdjFiZXRhMS9leHRlbnNpb25zLnByb3RvGhlnb29nbGUvcHJvdG9idWYvYW55LnByb3RvGihtaXhlci9hZGFwdGVyL21vZGVsL3YxYmV0YTEvcmVwb3J0LnByb3RvGhlwb2xpY3kvdjFiZXRhMS90eXBlLnByb3RvGh9wb2xpY3kvdjFiZXRhMS92YWx1ZV90eXBlLnByb3RvIqYBChZIYW5kbGVUcmFjZVNwYW5SZXF1ZXN0EjQKCWluc3RhbmNlcxgBIAMoCzIWLnRyYWNlc3Bhbi5JbnN0YW5jZU1zZ1IJaW5zdGFuY2VzEjsKDmFkYXB0ZXJfY29uZmlnGAIgASgLMhQuZ29vZ2xlLnByb3RvYnVmLkFueVINYWRhcHRlckNvbmZpZxIZCghkZWR1cF9pZBgDIAEoCVIHZGVkdXBJZCLNBwoLSW5zdGFuY2VNc2cSFQoEbmFtZRivyrwiIAEoCVIEbmFtZRIZCgh0cmFjZV9pZBgBIAEoCVIHdHJhY2VJZBIXCgdzcGFuX2lkGAIgASgJUgZzcGFuSWQSJAoOcGFyZW50X3NwYW5faWQYAyABKAlSDHBhcmVudFNwYW5JZBIbCglzcGFuX25hbWUYBCABKAlSCHNwYW5OYW1lEj4KCnN0YXJ0X3RpbWUYBSABKAsyHy5pc3Rpby5wb2xpY3kudjFiZXRhMS5UaW1lU3RhbXBSCXN0YXJ0VGltZRI6CghlbmRfdGltZRgGIAEoCzIfLmlzdGlvLnBvbGljeS52MWJldGExLlRpbWVTdGFtcFIHZW5kVGltZRJBCglzcGFuX3RhZ3MYByADKAsyJC50cmFjZXNwYW4uSW5zdGFuY2VNc2cuU3BhblRhZ3NFbnRyeVIIc3BhblRhZ3MSJgoOaHR0cFN0YXR1c0NvZGUYCCABKANSDmh0dHBTdGF0dXNDb2RlEh8KC2NsaWVudF9zcGFuGAkgASgIUgpjbGllbnRTcGFuEjMKFnJld3JpdGVfY2xpZW50X3NwYW5faWQYCiABKAhSE3Jld3JpdGVDbGllbnRTcGFuSWQSHwoLc291cmNlX25hbWUYCyABKAlSCnNvdXJjZU5hbWUSPAoJc291cmNlX2lwGAwgASgLMh8uaXN0aW8ucG9saWN5LnYxYmV0YTEuSVBBZGRyZXNzUghzb3VyY2VJcBIpChBkZXN0aW5hdGlvbl9uYW1lGA0gASgJUg9kZXN0aW5hdGlvbk5hbWUSRgoOZGVzdGluYXRpb25faXAYDiABKAsyHy5pc3Rpby5wb2xpY3kudjFiZXRhMS5JUEFkZHJlc3NSDWRlc3RpbmF0aW9uSXASIQoMcmVxdWVzdF9zaXplGA8gASgDUgtyZXF1ZXN0U2l6ZRIsChJyZXF1ZXN0X3RvdGFsX3NpemUYECABKANSEHJlcXVlc3RUb3RhbFNpemUSIwoNcmVzcG9uc2Vfc2l6ZRgRIAEoA1IMcmVzcG9uc2VTaXplEi4KE3Jlc3BvbnNlX3RvdGFsX3NpemUYEiABKANSEXJlc3BvbnNlVG90YWxTaXplEiEKDGFwaV9wcm90b2NvbBgTIAEoCVILYXBpUHJvdG9jb2waWAoNU3BhblRhZ3NFbnRyeRIQCgNrZXkYASABKAlSA2tleRIxCgV2YWx1ZRgCIAEoCzIbLmlzdGlvLnBvbGljeS52MWJldGExLlZhbHVlUgV2YWx1ZToCOAEioAEKBFR5cGUSOgoJc3Bhbl90YWdzGAcgAygLMh0udHJhY2VzcGFuLlR5cGUuU3BhblRhZ3NFbnRyeVIIc3BhblRhZ3MaXAoNU3BhblRhZ3NFbnRyeRIQCgNrZXkYASABKAlSA2tleRI1CgV2YWx1ZRgCIAEoDjIfLmlzdGlvLnBvbGljeS52MWJldGExLlZhbHVlVHlwZVIFdmFsdWU6AjgBIpkGCg1JbnN0YW5jZVBhcmFtEhkKCHRyYWNlX2lkGAEgASgJUgd0cmFjZUlkEhcKB3NwYW5faWQYAiABKAlSBnNwYW5JZBIkCg5wYXJlbnRfc3Bhbl9pZBgDIAEoCVIMcGFyZW50U3BhbklkEhsKCXNwYW5fbmFtZRgEIAEoCVIIc3Bhbk5hbWUSHQoKc3RhcnRfdGltZRgFIAEoCVIJc3RhcnRUaW1lEhkKCGVuZF90aW1lGAYgASgJUgdlbmRUaW1lEkMKCXNwYW5fdGFncxgHIAMoCzImLnRyYWNlc3Bhbi5JbnN0YW5jZVBhcmFtLlNwYW5UYWdzRW50cnlSCHNwYW5UYWdzEiYKDmh0dHBTdGF0dXNDb2RlGAggASgJUg5odHRwU3RhdHVzQ29kZRIfCgtjbGllbnRfc3BhbhgJIAEoCVIKY2xpZW50U3BhbhIzChZyZXdyaXRlX2NsaWVudF9zcGFuX2lkGAogASgJUhNyZXdyaXRlQ2xpZW50U3BhbklkEh8KC3NvdXJjZV9uYW1lGAsgASgJUgpzb3VyY2VOYW1lEhsKCXNvdXJjZV9pcBgMIAEoCVIIc291cmNlSXASKQoQZGVzdGluYXRpb25fbmFtZRgNIAEoCVIPZGVzdGluYXRpb25OYW1lEiUKDmRlc3RpbmF0aW9uX2lwGA4gASgJUg1kZXN0aW5hdGlvbklwEiEKDHJlcXVlc3Rfc2l6ZRgPIAEoCVILcmVxdWVzdFNpemUSLAoScmVxdWVzdF90b3RhbF9zaXplGBAgASgJUhByZXF1ZXN0VG90YWxTaXplEiMKDXJlc3BvbnNlX3NpemUYESABKAlSDHJlc3BvbnNlU2l6ZRIuChNyZXNwb25zZV90b3RhbF9zaXplGBIgASgJUhFyZXNwb25zZVRvdGFsU2l6ZRIhCgxhcGlfcHJvdG9jb2wYEyABKAlSC2FwaVByb3RvY29sGjsKDVNwYW5UYWdzRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBXZhbHVlOgI4ATJ/ChZIYW5kbGVUcmFjZVNwYW5TZXJ2aWNlEmUKD0hhbmRsZVRyYWNlU3BhbhIhLnRyYWNlc3Bhbi5IYW5kbGVUcmFjZVNwYW5SZXF1ZXN0Gi8uaXN0aW8ubWl4ZXIuYWRhcHRlci5tb2RlbC52MWJldGExLlJlcG9ydFJlc3VsdEIh+NLkkwIBgt3kkwIJdHJhY2VzcGFuyOEeAKjiHgDw4R4ASpVlCgcSBRAA5QIBCvIECgEMEgMQABIytAQgQ29weXJpZ2h0IDIwMTcgSXN0aW8gQXV0aG9ycwoKIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLgogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0CgogICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAoKIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUKIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsCiBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQKIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLgoyMSBUSElTIEZJTEUgSVMgQVVUT01BVElDQUxMWSBHRU5FUkFURUQgQlkgTUlYR0VOLgoKkQwKAQISAzgIERqGDCBUaGUgYHRyYWNlc3BhbmAgdGVtcGxhdGUgcmVwcmVzZW50cyBhbiBpbmRpdmlkdWFsIHNwYW4gd2l0aGluIGEgZGlzdHJpYnV0ZWQgdHJhY2UuCiAKIEV4YW1wbGUgY29uZmlnOgogCiBgYGB5YW1sCiBhcGlWZXJzaW9uOiAiY29uZmlnLmlzdGlvLmlvL3YxYWxwaGEyIgoga2luZDogdHJhY2VzcGFuCiBtZXRhZGF0YToKICAgbmFtZTogZGVmYXVsdAogICBuYW1lc3BhY2U6IGlzdGlvLXN5c3RlbQogc3BlYzoKICAgdHJhY2VJZDogcmVxdWVzdC5oZWFkZXJzWyJ4LWIzLXRyYWNlaWQiXQogICBzcGFuSWQ6IHJlcXVlc3QuaGVhZGVyc1sieC1iMy1zcGFuaWQiXSB8ICIiCiAgIHBhcmVudFNwYW5JZDogcmVxdWVzdC5oZWFkZXJzWyJ4LWIzLXBhcmVudHNwYW5pZCJdIHwgIiIKICAgc3Bhbk5hbWU6IHJlcXVlc3QucGF0aCB8ICIvIgogICBzdGFydFRpbWU6IHJlcXVlc3QudGltZQogICBlbmRUaW1lOiByZXNwb25zZS50aW1lCiAgIGNsaWVudFNwYW46IChjb250ZXh0LnJlcG9ydGVyLmtpbmQgfCAiaW5ib3VuZCIpID09ICJpbmJvdW5kIgogICByZXdyaXRlQ2xpZW50U3BhbklkOiBmYWxzZQogICBzcGFuVGFnczoKICAgICBodHRwLm1ldGhvZDogcmVxdWVzdC5tZXRob2QgfCAiIgogICAgIGh0dHAuc3RhdHVzX2NvZGU6IHJlc3BvbnNlLmNvZGUgfCAyMDAKICAgICBodHRwLnVybDogcmVxdWVzdC5wYXRoIHwgIiIKICAgICByZXF1ZXN0LnNpemU6IHJlcXVlc3Quc2l6ZSB8IDAKICAgICByZXNwb25zZS5zaXplOiByZXNwb25zZS5zaXplIHwgMAogICAgIHNvdXJjZS5wcmluY2lwYWw6IHNvdXJjZS5wcmluY2lwYWwgfCAiIgogICAgIHNvdXJjZS52ZXJzaW9uOiBzb3VyY2UubGFiZWxzWyJ2ZXJzaW9uIl0gfCAiIgogYGBgCiAKIFNlZSBhbHNvOiBbRGlzdHJpYnV0ZWQgVHJhY2luZ10oaHR0cHM6Ly9pc3Rpby5pby9kb2NzL3Rhc2tzL3RlbGVtZXRyeS9kaXN0cmlidXRlZC10cmFjaW5nLykKIGZvciBpbmZvcm1hdGlvbiBvbiB0cmFjaW5nIHdpdGhpbiBJc3Rpby4KCiBUcmFjZVNwYW4gcmVwcmVzZW50cyBhbiBpbmRpdmlkdWFsIHNwYW4gd2l0aGluIGEgZGlzdHJpYnV0ZWQgdHJhY2UuCiAKIFdoZW4gd3JpdGluZyB0aGUgY29uZmlndXJhdGlvbiwgdGhlIHZhbHVlIGZvciB0aGUgZmllbGRzIGFzc29jaWF0ZWQgd2l0aCB0aGlzIHRlbXBsYXRlIGNhbiBlaXRoZXIgYmUgYQogbGl0ZXJhbCBvciBhbiBbZXhwcmVzc2lvbl0oaHR0cHM6Ly9pc3Rpby5pby9kb2NzL3JlZmVyZW5jZS8vY29uZmlnL3BvbGljeS1hbmQtdGVsZW1ldHJ5L2V4cHJlc3Npb24tbGFuZ3VhZ2UvKS4gUGxlYXNlIG5vdGUgdGhhdCBpZiB0aGUgZGF0YXR5cGUgb2YgYSBmaWVsZCBpcyBub3QgaXN0aW8ucG9saWN5LnYxYmV0YTEuVmFsdWUsCiB0aGVuIHRoZSBleHByZXNzaW9uJ3MgW2luZmVycmVkIHR5cGVdKGh0dHBzOi8vaXN0aW8uaW8vZG9jcy9yZWZlcmVuY2UvL2NvbmZpZy9wb2xpY3ktYW5kLXRlbGVtZXRyeS9leHByZXNzaW9uLWxhbmd1YWdlLyN0eXBlLWNoZWNraW5nKSBtdXN0IG1hdGNoIHRoZSBkYXRhdHlwZSBvZiB0aGUgZmllbGQuCgoJCgIDABIDOwcdCgkKAgMBEgM8BzUKCQoCAwISAz0HIgoJCgIDAxIDPgcxCgkKAgMEEgM/ByIKCQoCAwUSA0AHKAoICgEIEgNCAFYKCwoECOcHABIDQgBWCgwKBQjnBwACEgNCBzsKDQoGCOcHAAIAEgNCBzsKDgoHCOcHAAIAARIDQgg6CgwKBQjnBwADEgNCPlUKCAoBCBIDQwBHCgsKBAjnBwESA0MARwoMCgUI5wcBAhIDQwc4Cg0KBgjnBwECABIDQwc4Cg4KBwjnBwECAAESA0MINwoMCgUI5wcBBxIDQztGCggKAQgSA0UALwoLCgQI5wcCEgNFAC8KDAoFCOcHAgISA0UHJgoNCgYI5wcCAgASA0UHJgoOCgcI5wcCAgABEgNFCCUKDAoFCOcHAgMSA0UpLgoICgEIEgNGACUKCwoECOcHAxIDRgAlCgwKBQjnBwMCEgNGBxwKDQoGCOcHAwIAEgNGBxwKDgoHCOcHAwIAARIDRggbCgwKBQjnBwMDEgNGHyQKCAoBCBIDRwAoCgsKBAjnBwQSA0cAKAoMCgUI5wcEAhIDRwcfCg0KBgjnBwQCABIDRwcfCg4KBwjnBwQCAAESA0cIHgoMCgUI5wcEAxIDRyInCngKAgYAEgRKAE4BGmwgSGFuZGxlVHJhY2VTcGFuU2VydmljZSBpcyBpbXBsZW1lbnRlZCBieSBiYWNrZW5kcyB0aGF0IHdhbnRzIHRvIGhhbmRsZSByZXF1ZXN0LXRpbWUgJ3RyYWNlc3BhbicgaW5zdGFuY2VzLgoKCgoDBgABEgNKCB4KcgoEBgACABIDTARpGmUgSGFuZGxlVHJhY2VTcGFuIGlzIGNhbGxlZCBieSBNaXhlciBhdCByZXF1ZXN0LXRpbWUgdG8gZGVsaXZlciAndHJhY2VzcGFuJyBpbnN0YW5jZXMgdG8gdGhlIGJhY2tlbmQuCgoMCgUGAAIAARIDTAgXCgwKBQYAAgACEgNMGC4KDAoFBgACAAMSA0w5Zwo5CgIEABIEUQBgARotIFJlcXVlc3QgbWVzc2FnZSBmb3IgSGFuZGxlVHJhY2VTcGFuIG1ldGhvZC4KCgoKAwQAARIDUQgeCiUKBAQAAgASA1QEJxoYICd0cmFjZXNwYW4nIGluc3RhbmNlcy4KCgwKBQQAAgAEEgNUBAwKDAoFBAACAAYSA1QNGAoMCgUEAAIAARIDVBkiCgwKBQQAAgADEgNUJSYKuQQKBAQAAgESA1wEKxqrBCBBZGFwdGVyIHNwZWNpZmljIGhhbmRsZXIgY29uZmlndXJhdGlvbi4KCiBOb3RlOiBCYWNrZW5kcyBjYW4gYWxzbyBpbXBsZW1lbnQgW0luZnJhc3RydWN0dXJlQmFja2VuZF1baHR0cHM6Ly9pc3Rpby5pby9kb2NzL3JlZmVyZW5jZS9jb25maWcvbWl4ZXIvaXN0aW8ubWl4ZXIuYWRhcHRlci5tb2RlbC52MWJldGExLmh0bWwjSW5mcmFzdHJ1Y3R1cmVCYWNrZW5kXQogc2VydmljZSBhbmQgdGhlcmVmb3JlIG9wdCB0byByZWNlaXZlIGhhbmRsZXIgY29uZmlndXJhdGlvbiBkdXJpbmcgc2Vzc2lvbiBjcmVhdGlvbiB0aHJvdWdoIFtJbmZyYXN0cnVjdHVyZUJhY2tlbmQuQ3JlYXRlU2Vzc2lvbl1bVE9ETzogTGluayB0byB0aGlzIGZyYWdtZW50XQogY2FsbC4gSW4gdGhhdCBjYXNlLCBhZGFwdGVyX2NvbmZpZyB3aWxsIGhhdmUgdHlwZV91cmwgYXMgJ2dvb2dsZS5wcm90b2J1Zi5BbnkudHlwZV91cmwnIGFuZCB3b3VsZCBjb250YWluIHN0cmluZwogdmFsdWUgb2Ygc2Vzc2lvbl9pZCAocmV0dXJuZWQgZnJvbSBJbmZyYXN0cnVjdHVyZUJhY2tlbmQuQ3JlYXRlU2Vzc2lvbikuCgoNCgUEAAIBBBIEXARUJwoMCgUEAAIBBhIDXAQXCgwKBQQAAgEBEgNcGCYKDAoFBAACAQMSA1wpKgo6CgQEAAICEgNfBBgaLSBJZCB0byBkZWR1cGUgaWRlbnRpY2FsIHJlcXVlc3RzIGZyb20gTWl4ZXIuCgoNCgUEAAICBBIEXwRcKwoMCgUEAAICBRIDXwQKCgwKBQQAAgIBEgNfCxMKDAoFBAACAgMSA18WFwqyAQoCBAESBWgA4AEBGqQBIENvbnRhaW5zIGluc3RhbmNlIHBheWxvYWQgZm9yICd0cmFjZXNwYW4nIHRlbXBsYXRlLiBUaGlzIGlzIHBhc3NlZCB0byBpbmZyYXN0cnVjdHVyZSBiYWNrZW5kcyBkdXJpbmcgcmVxdWVzdC10aW1lCiB0aHJvdWdoIEhhbmRsZVRyYWNlU3BhblNlcnZpY2UuSGFuZGxlVHJhY2VTcGFuLgoKCgoDBAEBEgNoCBMKQgoEBAECABIDawQbGjUgTmFtZSBvZiB0aGUgaW5zdGFuY2UgYXMgc3BlY2lmaWVkIGluIGNvbmZpZ3VyYXRpb24uCgoNCgUEAQIABBIEawRoFQoMCgUEAQIABRIDawQKCgwKBQQBAgABEgNrCw8KDAoFBAECAAMSA2sSGgqCAQoEBAECARIDcQQYGnUgVHJhY2UgSUQgaXMgdGhlIHVuaXF1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bCBpbnN0ZWFkIG9mIGVudW0gaXMgYSB0ZW1wb3JhcnkKIHdvcmsgYXJvdW5kIHNpbmNlIG1peGVyIGV4cHJlc3Npb24gbGFuZ3VhZ2UgZG9lcyBub3QgeWV0IHN1cHBvcnQgZW51bQogdHlwZS4KIAogT3B0aW9uYWwKCg8KBQQBAgkEEgajAQSbAR0KDQoFBAECCQUSBKMBBAgKDQoFBAECCQESBKMBCRQKDQoFBAECCQMSBKMBFxgKjAMKBAQBAgoSBKwBBCUa/QIgcmV3cml0ZV9jbGllbnRfc3Bhbl9pZCBpcyB1c2VkIHRvIGluZGljYXRlIHdoZXRoZXIgdG8gY3JlYXRlIGEgbmV3IGNsaWVudAogc3BhbiBpZCB0byBhY2NvbW1vZGF0ZSBaaXBraW4gc2hhcmVkIHNwYW4gbW9kZWwuIFNvbWUgdHJhY2luZyBzeXN0ZW1zIGxpa2UKIFN0YWNrZHJpdmVyIHNlcGFyYXRlcyBhIFJQQyBpbnRvIGNsaWVudCBzcGFuIGFuZCBzZXJ2ZXIgc3Bhbi4gVG8gc29sdmUgdGhpcwogaW5jb21wYXRpYmlsaXR5LCBkZXRlcm1pbmlzdGljYWxseSByZXdyaXRpbmcgYm90aCBzcGFuIGlkIG9mIGNsaWVudCBzcGFuIGFuZAogcGFyZW50IHNwYW4gaWQgb2Ygc2VydmVyIHNwYW4gdG8gdGhlIHNhbWUgbmV3bHkgZ2VuZXJhdGVkIGlkLgogCiBPcHRpb25hbAoKDwoFBAECCgQSBqwBBKMBGQoNCgUEAQIKBRIErAEECAoNCgUEAQIKARIErAEJHwoNCgUEAQIKAxIErAEiJAqAAQoEBAECCxIEsgEEHBpyIElkZW50aWZpZXMgdGhlIHNvdXJjZSAoY2xpZW50IHNpZGUpIG9mIHRoaXMgc3Bhbi4KIFNob3VsZCB1c3VhbGx5IGJlIHNldCB0byBgc291cmNlLndvcmtsb2FkLm5hbWVgLgogCiBPcHRpb25hbC4KCg8KBQQBAgsEEgayAQSsASUKDQoFBAECCwUSBLIBBAoKDQoFBAECCwESBLIBCxYKDQoFBAECCwMSBLIBGRsKVQoEBAECDBIEtwEEMhpHIENsaWVudCBJUCBhZGRyZXNzLiBTaG91bGQgdXN1YWxseSBiZSBzZXQgdG8g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BBIGoAIEnAImCg0KBQQDAgcFEgSgAgQKCg0KBQQDAgcBEgSgAgsZCg0KBQQDAgcDEgSgAhwdCvwBCgQEAwIIEgSoAgQbGu0BIGNsaWVudF9zcGFuIGluZGljYXRlcyB0aGUgc3BhbiBraW5kLiBUcnVlIGZvciBjbGllbnQgc3BhbnMgYW5kIEZhbHNlIG9yCiBub3QgcHJvdmlkZWQgZm9yIHNlcnZlciBzcGFucy4gVXNpbmcgYm9vbCBpbnN0ZWFkIG9mIGVudW0gaXMgYSB0ZW1wb3JhcnkKIHdvcmsgYXJvdW5kIHNpbmNlIG1peGVyIGV4cHJlc3Npb24gbGFuZ3VhZ2UgZG9lcyBub3QgeWV0IHN1cHBvcnQgZW51bQogdHlwZS4KIAogT3B0aW9uYWwKCg8KBQQDAggEEgaoAgSgAh4KDQoFBAMCCAUSBKgCBAoKDQoFBAMCCAESBKgCCxYKDQoFBAMCCAMSBKgCGRoKjAMKBAQDAgkSBLECBCca/QIgcmV3cml0ZV9jbGllbnRfc3Bhbl9pZCBpcyB1c2VkIHRvIGluZGljYXRlIHdoZXRoZXIgdG8gY3JlYXRlIGEgbmV3IGNsaWVudAogc3BhbiBpZCB0byBhY2NvbW1vZGF0ZSBaaXBraW4gc2hhcmVkIHNwYW4gbW9kZWwuIFNvbWUgdHJhY2luZyBzeXN0ZW1zIGxpa2UKIFN0YWNrZHJpdmVyIHNlcGFyYXRlcyBhIFJQQyBpbnRvIGNsaWVudCBzcGFuIGFuZCBzZXJ2ZXIgc3Bhbi4gVG8gc29sdmUgdGhpcwogaW5jb21wYXRpYmlsaXR5LCBkZXRlcm1pbmlzdGljYWxseSByZXdyaXRpbmcgYm90aCBzcGFuIGlkIG9mIGNsaWVudCBzcGFuIGFuZAogcGFyZW50IHNwYW4gaWQgb2Ygc2VydmVyIHNwYW4gdG8gdGhlIHNhbWUgbmV3bHkgZ2VuZXJhdGVkIGlkLgogCiBPcHRpb25hbAoKDwoFBAMCCQQSBrECBKgCGwoNCgUEAwIJBRIEsQIECgoNCgUEAwIJARIEsQILIQoNCgUEAwIJAxIEsQIkJgqAAQoEBAMCChIEtwIEHBpyIElkZW50aWZpZXMgdGhlIHNvdXJjZSAoY2xpZW50IHNpZGUpIG9mIHRoaXMgc3Bhbi4K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" +--- diff --git a/helm-charts/values.yaml b/helm-charts/values.yaml new file mode 100644 index 0000000..11adb5e --- /dev/null +++ b/helm-charts/values.yaml @@ -0,0 +1,682 @@ +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- + +# Default values for newrelic-istio-adapter. + +# Override the Chart name. +# Also used as the label `app.kubernetes.io/name` value for all resources. +nameOverride: "" + +# Override the naming of `newrelic-istio-adapter` namespace resources. +# Enables multiple `newrelic-istio-adapter` versions to be deployed simultaneously. +fullnameOverride: "" + +# Namespace of the Istio control plane resources. +istioNamespace: istio-system + +# Name used by `newrelic-istio-adapter`, a unique Kubernetes cluster identifier for metrics. +# Corresponds to the cluster.name attribute of metrics. +clusterName: istio-cluster + +# Override the New Relic Metrics API endpoint (used for debugging). +#metricsHost: "" + +# Override the New Relic Trace API endpoint (used for debugging). +#spansHost: "" + +authentication: + # Specify if this Chart will manage the secret containing the + # authentication credentials to New Relic that the adapter uses. + # Setting this to `false` mean you will manually handle secrets. + manageSecret: true + + # If `manageSecret` is true the following must to be provided. + # + # Plaintext New Relic Insights Insert API key. + apiKey: "" + + # Kubernetes Secret resource name. + # + # This is used to override the default name of the authentication + # credential containing Opaque secret. + # + # This is useful if `manageSecret` is `false` and you plan to use a secret + # that does not match the name of the expected secret (defaults to the + # full name of this release). Additionally, if you want to use a different + # scheme for secret naming this provides that functionality. + # + # The Kubernetes Secret must contain keys: + # NEW_RELIC_API_KEY + secretNameOverride: "" + +image: + # Repository for container image. + repository: newrelic/newrelic-istio-adapter + # Image tag. + tag: latest + # Image pull policy. + pullPolicy: IfNotPresent + +service: + # The `newrelic-istio-adapter` Kubernetes Service type. + type: ClusterIP + # The `newrelic-istio-adapter` Kubernetes Service port. + port: 80 + +# Kubernetes Deployment relica count definition. +replicaCount: 1 + +# Kubernetes Pod resource requests & limits resource definition. +resources: {} + +# Kubernetes Deployment nodeSelector definition. +nodeSelector: {} + +# Kubernetes Deployment tolerations definition. +tolerations: [] + +# Kubernetes Deployment affinity definition. +affinity: {} + +# Istio telemetry configuration +telemetry: + + # Prefixed namespace for all metrics sent to New Relic. + namespace: istio + + # Envoy to Mixer attribute value mapping + # https://istio.io/docs/reference/config/policy-and-telemetry/attribute-vocabulary/ + attributes: + source.ip: + valueType: IP_ADDRESS + source.labels: + valueType: STRING_MAP + source.name: + valueType: STRING + source.namespace: + valueType: STRING + source.owner: + valueType: STRING + source.serviceAccount: + valueType: STRING + source.services: + valueType: STRING + source.workload.uid: + valueType: STRING + source.workload.name: + valueType: STRING + source.workload.namespace: + valueType: STRING + destination.ip: + valueType: IP_ADDRESS + destination.labels: + valueType: STRING_MAP + destination.metadata: + valueType: STRING_MAP + destination.name: + valueType: STRING + destination.namespace: + valueType: STRING + destination.owner: + valueType: STRING + destination.service.uid: + valueType: STRING + destination.service.name: + valueType: STRING + destination.service.namespace: + valueType: STRING + destination.service.host: + valueType: STRING + destination.serviceAccount: + valueType: STRING + destination.workload.uid: + valueType: STRING + destination.workload.name: + valueType: STRING + destination.workload.namespace: + valueType: STRING + destination.container.name: + valueType: STRING + origin.ip: + valueType: IP_ADDRESS + origin.uid: + valueType: STRING + origin.user: + valueType: STRING + request.headers: + valueType: STRING_MAP + request.id: + valueType: STRING + request.host: + valueType: STRING + request.method: + valueType: STRING + request.path: + valueType: STRING + request.url_path: + valueType: STRING + request.query_params: + valueType: STRING_MAP + request.reason: + valueType: STRING + request.referer: + valueType: STRING + request.scheme: + valueType: STRING + request.total_size: + valueType: INT64 + request.size: + valueType: INT64 + request.time: + valueType: TIMESTAMP + request.useragent: + valueType: STRING + response.code: + valueType: INT64 + response.duration: + valueType: DURATION + response.headers: + valueType: STRING_MAP + response.total_size: + valueType: INT64 + response.size: + valueType: INT64 + response.time: + valueType: TIMESTAMP + response.grpc_status: + valueType: STRING + response.grpc_message: + valueType: STRING + source.uid: + valueType: STRING + source.user: # DEPRECATED + valueType: STRING + source.principal: + valueType: STRING + destination.uid: + valueType: STRING + destination.principal: + valueType: STRING + destination.port: + valueType: INT64 + connection.event: + valueType: STRING + connection.id: + valueType: STRING + connection.received.bytes: + valueType: INT64 + connection.received.bytes_total: + valueType: INT64 + connection.sent.bytes: + valueType: INT64 + connection.sent.bytes_total: + valueType: INT64 + connection.duration: + valueType: DURATION + connection.mtls: + valueType: BOOL + connection.requested_server_name: + valueType: STRING + context.protocol: + valueType: STRING + context.proxy_error_code: + valueType: STRING + context.timestamp: + valueType: TIMESTAMP + context.time: + valueType: TIMESTAMP + # Deprecated, kept for compatibility + context.reporter.local: + valueType: BOOL + context.reporter.kind: + valueType: STRING + context.reporter.uid: + valueType: STRING + api.service: + valueType: STRING + api.version: + valueType: STRING + api.operation: + valueType: STRING + api.protocol: + valueType: STRING + request.auth.principal: + valueType: STRING + request.auth.audiences: + valueType: STRING + request.auth.presenter: + valueType: STRING + request.auth.claims: + valueType: STRING_MAP + request.auth.raw_claims: + valueType: STRING + request.api_key: + valueType: STRING + rbac.permissive.response_code: + valueType: STRING + rbac.permissive.effective_policy_id: + valueType: STRING + check.error_code: + valueType: INT64 + check.error_message: + valueType: STRING + check.cache_hit: + valueType: BOOL + quota.cache_hit: + valueType: BOOL + + # https://istio.io/docs/reference/config/policy-and-telemetry/templates/tracespan/ + # # The key name is the name of the Istio instance using the tracespan template. + # INSTANCE_NAME: + # # Trace ID is the unique identifier for a trace. All spans from the + # # same trace share the same Trace ID. + # # + # # Required. + # traceId string + # + # # Span ID is the unique identifier for a span within a trace. It is + # # assigned when the span is created. + # # + # # Optional. + # spanId string + # + # # Parent Span ID is the unique identifier for a parent span of this + # # span instance. If this is a root span, then this field MUST be + # # empty. + # # + # # Optional. + # parentSpanId string + # + # # Span name is a description of the span’s operation. + # # + # # For example, the name can be a qualified method name or a file name + # # and a line number where the operation is called. A best practice is + # # to use the same display name within an application and at the same + # # call point. This makes it easier to correlate spans in different + # # traces. + # # + # # Required. + # spanName string + # + # # The start time of the span. + # # + # # Required. + # startTime istio.policy.v1beta1.TimeStamp + # + # # The end time of the span. + # # + # # Required. + # endTime istio.policy.v1beta1.TimeStamp + # + # # Span tags are a set of < key, value > pairs that provide metadata + # # for the entire span. The values can be specified in the form of + # # expressions. + # # + # # Optional. + # spanTags map + # + # # HTTP status code used to set the span status. If unset or set to 0, + # # the span status will be assumed to be successful. + # # + # # Optional. + # httpStatusCode int64 + # + # # client_span indicates the span kind. True for client spans and False + # # or not provided for server spans. Using bool instead of enum is a + # # temporary work around since mixer expression language does not yet + # # support enum type. + # # + # # Optional. + # clientSpan bool + # + # # rewriteclientspan_id is used to indicate whether to create a new + # # client span id to accommodate Zipkin shared span model. Some tracing + # # systems like Stackdriver separates a RPC into client span and server + # # span. To solve this incompatibility, deterministically rewriting + # # both span id of client span and parent span id of server span to the + # # same newly generated id. + # # + # # Optional. + # rewriteClientSpanId bool + # + # # Identifies the source (client side) of this span. Should usually be + # # set to source.workload.name. + # # + # # Optional. + # sourceName string + # + # # Client IP address. Should usually be set to source.ip. + # # + # # Optional. + # sourceIp istio.policy.v1beta1.IPAddress + # + # # Identifies the destination (server side) of this span. Should + # # usually be set to destination.workload.name. + # # + # # Optional. + # destinationName string + # + # # Server IP address. Should usually be set to destination.ip. + # # + # # Optional. + # destinationIp istio.policy.v1beta1.IPAddress + # + # # Request body size. Should usually be set to request.size. + # # + # # Optional. + # requestSize int64 + # + # # Total request size (headers and body). Should usually be set to + # # request.total_size. + # # + # # Optional. + # requestTotalSize int64 + # + # # Response body size. Should usually be set to response.size. + # # + # # Optional. + # responseSize int64 + # + # # Response total size (headers and body). Should usually be set to + # # response.total_size. + # # + # # Optional. + # responseTotalSize int64 + # + # # One of “http”, “https”, or “grpc” or any other value of the + # # api.protocol attribute. Should usually be set to api.protocol. + # # + # # Optional. + # apiProtocol string + traces: + + newrelic-span: + traceId: request.headers["x-b3-traceid"] | "" + spanId: request.headers["x-b3-spanid"] | "" + parentSpanId: request.headers["x-b3-parentspanid"] | "" + spanName: destination.workload.name | destination.service.name | "unknown" + startTime: request.time + endTime: response.time + httpStatusCode: response.code | 0 + clientSpan: (context.reporter.kind | "inbound") == "outbound" + rewriteClientSpanId: "false" + sourceName: source.workload.name | "unknown" + sourceIp: source.ip | ip("0.0.0.0") + destinationName: destination.workload.name | "unknown" + destinationIp: destination.ip | ip("0.0.0.0") + requestSize: request.size | 0 + requestTotalSize: request.total_size | 0 + responseSize: response.size | 0 + responseTotalSize: response.total_size | 0 + apiProtocol: api.protocol | "" + spanTags: + api.name: api.service | "unknown" + api.version: api.version | "unknown" + destination.owner: destination.owner | "unknown" + destination.port: destination.port | 0 + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + destination.workload.name: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + request.path: request.path | "" + request.operation: conditional((context.protocol | "unknown") == "grpc", request.path | "unknown", request.method | "unknown") + request.protocol: context.protocol | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + source.owner: source.owner | "unknown" + source.workload.name: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + + # https://istio.io/docs/reference/config/policy-and-telemetry/templates/metric/ + # # The key name is the name of the Istio instance using the metric template. + # INSTANCE_NAME: + # # The name field is the name passed to New Relic for the metric + # name: NEW_RELIC_NAME + # # The type of Dimensional Metric in New Relic the metric is converted to. + # type: NEW_RELIC_TYPE + # # The interpreted instance value + # value: INSTANCE_VALUE + # # New Relic metric attributes and instance dimensions. + # dimensions: + # # Interpreted instance dimensions and New Relic metric attributes. + # key: value + # + metrics: + + newrelic-request-count: + name: "request.total" + type: COUNT + value: "1" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + + newrelic-request-size: + name: "request.bytes" + type: GAUGE + value: request.size | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + + newrelic-request-duration: + name: "request.duration.milliseconds" + type: SUMMARY + value: response.duration | "0ms" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + + newrelic-response-size: + name: "response.bytes" + type: GAUGE + value: response.size | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + + newrelic-bytes-sent: + name: "tcp.sent.bytes" + type: COUNT + value: connection.sent.bytes | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" + + newrelic-bytes-received: + name: "tcp.received.bytes" + type: COUNT + value: connection.received.bytes | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" + + newrelic-connections-opened: + name: "tcp.connections.opened" + type: COUNT + value: "1" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.name | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" + + newrelic-connections-closed: + name: "tcp.connections.closed" + type: COUNT + value: "1" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.name | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" + + # https://istio.io/docs/reference/config/policy-and-telemetry/istio.policy.v1beta1/ + rules: + + newrelic-http-connection: + match: (context.protocol == "http" || context.protocol == "grpc") && (match((request.useragent | "-"), "kube-probe*") == false) + instances: + - newrelic-request-count + - newrelic-request-size + - newrelic-request-duration + - newrelic-response-size + + newrelic-tcp-connection: + match: context.protocol == "tcp" + instances: + - newrelic-bytes-sent + - newrelic-bytes-received + + newrelic-tcp-connection-open: + match: context.protocol == "tcp" && ((connection.event | "na") == "open") + instances: + - newrelic-connections-opened + + newrelic-tcp-connection-closed: + match: context.protocol == "tcp" && ((connection.event | "na") == "close") + instances: + - newrelic-connections-closed + + ## Span instances from Istio are not sent to the adapter by default. + ## The following can be uncommented if spans for inter-service communication + ## should be sent to New Relic. + #newrelic-tracing: + # match: (context.protocol == "http" || context.protocol == "grpc") && destination.workload.name != "istio-telemetry" && destination.workload.name != "istio-pilot" && ((request.headers["x-b3-sampled"] | "0") == "1") + # instances: + # - newrelic-span diff --git a/integration_test.go b/integration_test.go new file mode 100644 index 0000000..a2b7f00 --- /dev/null +++ b/integration_test.go @@ -0,0 +1,382 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build integration + +package newrelic + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "sort" + "testing" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/instrumentation" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" + integration "istio.io/istio/mixer/pkg/adapter/test" +) + +const ( + adapterConfigPath = "config/newrelic.yaml" + operatorConfigPath = "integration_test_cfg.yaml" +) + +func TestReport(t *testing.T) { + adapterCfg, err := ioutil.ReadFile(adapterConfigPath) + if err != nil { + t.Fatalf("failed to read adapter config file: %v", err) + } + + operatorCfg, err := ioutil.ReadFile(operatorConfigPath) + if err != nil { + t.Fatalf("failed to read operator config file: %v", err) + } + + var commonAttrs = map[string]interface{}{ + "cluster.name": "hotdog-stand", + "foodHandlerPermit": "revoked", + } + mockt := newMockTransport() + agg, harvester := mockHarvester(mockt, commonAttrs) + + scenario := integration.Scenario{ + Setup: func() (ctx interface{}, err error) { + s, err := NewServer(":0", agg, harvester) + if err != nil { + return nil, err + } + + // start the adapter + go func() { + s.Run() + }() + + return s, nil + }, + + Teardown: func(ctx interface{}) { + s := ctx.(*Server) + s.Close() + }, + + ParallelCalls: []integration.Call{ + { + CallKind: integration.REPORT, + Attrs: map[string]interface{}{ + "context.protocol": "http", + "destination.workload.name": "cat", + "source.labels": map[string]string{"app": "grocery"}, + "response.code": int64(418), + "request.time": time.Unix(1582230020, 0), + "source.ip": []byte(net.ParseIP("1.2.3.4")), + "connection.duration": 127 * time.Hour, + "connection.mtls": true, + }, + }, + }, + + GetState: func(ctx interface{}) (interface{}, error) { + // send metrics immediately + harvester.HarvestNow(context.Background()) + + // verify a single request was issued with the correct metric data + if len(mockt.requests) != 1 { + t.Fatalf("expected a single request to New Relic metric api, %d requests reported", + len(mockt.requests)) + } + + common, metrics, err := readTelemetryRequestJson(mockt.requests[0]) + if err != nil { + t.Fatalf("failed to unmarshal the telemetry request json: %v", err) + } + sort.Slice(metrics, func(i, j int) bool { return metrics[i].Name < metrics[j].Name }) + + return map[string]interface{}{ + "common": common, + "metrics": metrics, + }, nil + }, + + GetConfig: func(ctx interface{}) ([]string, error) { + // This supplies the adapter config and some operator config for instances and rules + // Metric template config is required and automatically added by the test framework + s := ctx.(*Server) + return []string{ + string(adapterCfg), + fmt.Sprintf(string(operatorCfg), s.listener.Addr().String()), + }, nil + }, + + // Want is the expected serialized json for the Result struct. + // Result.AdapterState is what the callback function GetState returns. + // Returns is going be full of empty values as this is a Report call not a Check call + Want: ` + { + "AdapterState": { + "common": { + "attributes": { + "cluster.name": "hotdog-stand", + "foodHandlerPermit": "revoked" + } + }, + "metrics": [ + { + "attributes": { + "connection.duration": 457200000, + "connection.mtls": true, + "connection.securityPolicy": "mutual_tls", + "destination.app": "unknown", + "destination.principal": "unknown", + "destination.service": "unknown", + "destination.service.name": "unknown", + "destination.service.namespace": "unknown", + "destination.version": "unknown", + "destination.workload": "cat", + "destination.workload.namespace": "unknown", + "reporter": "destination", + "request.protocol": "http", + "request.time": 1582230020000, + "response.code": 418, + "response.flags": "-", + "service.name": "cat", + "source.app": "grocery", + "source.ip": "1.2.3.4", + "source.principal": "unknown", + "source.version": "unknown", + "source.workload": "unknown", + "source.workload.namespace": "unknown" + }, + "name": "istio.request.bytes", + "type": "gauge", + "value": 0 + }, + { + "attributes": { + "connection.duration": 457200000, + "connection.mtls": true, + "connection.securityPolicy": "mutual_tls", + "destination.app": "unknown", + "destination.principal": "unknown", + "destination.service": "unknown", + "destination.service.name": "unknown", + "destination.service.namespace": "unknown", + "destination.version": "unknown", + "destination.workload": "cat", + "destination.workload.namespace": "unknown", + "reporter": "destination", + "request.protocol": "http", + "request.time": 1582230020000, + "response.code": 418, + "response.flags": "-", + "service.name": "cat", + "source.app": "grocery", + "source.ip": "1.2.3.4", + "source.principal": "unknown", + "source.version": "unknown", + "source.workload": "unknown", + "source.workload.namespace": "unknown" + }, + "name": "istio.request.duration.seconds", + "type": "summary", + "value": { + "count": 1, + "max": 0, + "min": 0, + "sum": 0 + } + }, + { + "attributes": { + "connection.duration": 457200000, + "connection.mtls": true, + "connection.securityPolicy": "mutual_tls", + "destination.app": "unknown", + "destination.principal": "unknown", + "destination.service": "unknown", + "destination.service.name": "unknown", + "destination.service.namespace": "unknown", + "destination.version": "unknown", + "destination.workload": "cat", + "destination.workload.namespace": "unknown", + "reporter": "destination", + "request.protocol": "http", + "request.time": 1582230020000, + "response.code": 418, + "response.flags": "-", + "service.name": "cat", + "source.app": "grocery", + "source.ip": "1.2.3.4", + "source.principal": "unknown", + "source.version": "unknown", + "source.workload": "unknown", + "source.workload.namespace": "unknown" + }, + "name": "istio.request.total", + "type": "count", + "value": 1 + }, + { + "attributes": { + "connection.duration": 457200000, + "connection.mtls": true, + "connection.securityPolicy": "mutual_tls", + "destination.app": "unknown", + "destination.principal": "unknown", + "destination.service": "unknown", + "destination.service.name": "unknown", + "destination.service.namespace": "unknown", + "destination.version": "unknown", + "destination.workload": "cat", + "destination.workload.namespace": "unknown", + "reporter": "destination", + "request.protocol": "http", + "request.time": 1582230020000, + "response.code": 418, + "response.flags": "-", + "service.name": "cat", + "source.app": "grocery", + "source.ip": "1.2.3.4", + "source.principal": "unknown", + "source.version": "unknown", + "source.workload": "unknown", + "source.workload.namespace": "unknown" + }, + "name": "istio.response.bytes", + "type": "gauge", + "value": 0 + } + ] + }, + "Returns": [ + { + "Check": { + "Status": {}, + "ValidDuration": 0, + "ValidUseCount": 0, + "RouteDirective": null + }, + "Quota": null, + "Error": {} + } + ] + }`, + } + + integration.RunTest(t, nil, scenario) +} + +// MockTransport caches decompressed request bodies +type MockTransport struct { + requests [][]byte +} + +func (c *MockTransport) RoundTrip(r *http.Request) (*http.Response, error) { + // telemetry sdk gzip compresses json payloads + gz, err := gzip.NewReader(r.Body) + if err != nil { + return nil, err + } + defer gz.Close() + + contents, err := ioutil.ReadAll(gz) + if err != nil { + return nil, err + } + + if !json.Valid(contents) { + return nil, errors.New("error validating request body json") + } + c.requests = append(c.requests, contents) + + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(&bytes.Buffer{}), + }, nil +} + +func newMockTransport() *MockTransport { + return &MockTransport{ + requests: make([][]byte, 0), + } +} + +func mockHarvester(mt *MockTransport, common map[string]interface{}) (*instrumentation.MetricAggregator, *telemetry.Harvester) { + agg := instrumentation.NewMetricAggregator() + return agg, telemetry.NewHarvester( + telemetry.ConfigAPIKey("8675309"), + telemetry.ConfigCommonAttributes(common), + telemetry.ConfigHarvestPeriod(time.Duration(5*time.Second)), + telemetry.ConfigBasicErrorLogger(os.Stderr), + telemetry.ConfigBasicDebugLogger(os.Stderr), + agg.BeforeHarvest, + func(cfg *telemetry.Config) { + cfg.MetricsURLOverride = "localhost" + cfg.SpansURLOverride = "localhost" + cfg.Client.Transport = mt + }, + ) +} + +type CommonAttributes struct { + timestamp interface{} `json:"-"` + interval interface{} `json:"-"` + Attributes map[string]string `json:"attributes"` +} + +type Metric struct { + Name string `json:"name"` + Typo string `json:"type"` + Value interface{} `json:"value"` + timestamp interface{} `json:"-"` + Attributes map[string]interface{} `json:"attributes"` +} + +func readTelemetryRequestJson(data []byte) (CommonAttributes, []Metric, error) { + // Expected format: + // [{ + // "common": CommonAttributes{}, + // "metrics": [Metric{}], + // }] + var objs []*json.RawMessage + if err := json.Unmarshal(data, &objs); err != nil { + return CommonAttributes{}, nil, err + } + + var objmap map[string]*json.RawMessage + if err := json.Unmarshal(*objs[0], &objmap); err != nil { + return CommonAttributes{}, nil, err + } + + var common CommonAttributes + if err := json.Unmarshal(*objmap["common"], &common); err != nil { + return CommonAttributes{}, nil, err + } + + var metrics []Metric + if err := json.Unmarshal(*objmap["metrics"], &metrics); err != nil { + return CommonAttributes{}, nil, err + } + + return common, metrics, nil +} diff --git a/integration_test_cfg.yaml b/integration_test_cfg.yaml new file mode 100644 index 0000000..8c00ac2 --- /dev/null +++ b/integration_test_cfg.yaml @@ -0,0 +1,191 @@ +# +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: requestcount + namespace: istio-system +spec: + template: metric + params: + value: "1" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + source.ip: source.ip | ip("0.0.0.0") + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + request.time: request.time | timestamp("2001-01-01T00:00:01+00:00") + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + connection.mtls: connection.mtls | false + connection.duration: connection.duration | "1s" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: requestsize + namespace: istio-system +spec: + template: metric + params: + value: request.size | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + source.ip: source.ip | ip("0.0.0.0") + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + request.time: request.time | timestamp("2001-01-01T00:00:01+00:00") + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + connection.mtls: connection.mtls | false + connection.duration: connection.duration | "1s" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: requestduration + namespace: istio-system +spec: + template: metric + params: + value: response.duration | "0ms" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + source.ip: source.ip | ip("0.0.0.0") + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + request.time: request.time | timestamp("2001-01-01T00:00:01+00:00") + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + connection.mtls: connection.mtls | false + connection.duration: connection.duration | "1s" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: responsesize + namespace: istio-system +spec: + template: metric + params: + value: response.size | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + source.ip: source.ip | ip("0.0.0.0") + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + request.time: request.time | timestamp("2001-01-01T00:00:01+00:00") + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + connection.mtls: connection.mtls | false + connection.duration: connection.duration | "1s" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: rule +metadata: + name: newrelic-http + namespace: istio-system +spec: + match: (context.protocol == "http" || context.protocol == "grpc") && (match((request.useragent | "-"), "kube-probe*") == false) + actions: + - handler: newrelic + instances: + - requestcount + - requestsize + - requestduration + - responsesize +--- +apiVersion: "config.istio.io/v1alpha2" +kind: handler +metadata: + name: newrelic + namespace: istio-system +spec: + adapter: newrelic + connection: + address: "%s" + params: + namespace: istio + metrics: + requestduration.instance.istio-system: + name: request.duration.seconds + type: SUMMARY + requestsize.instance.istio-system: + name: request.bytes + type: GAUGE + requestcount.instance.istio-system: + name: request.total + type: COUNT + responsesize.instance.istio-system: + name: response.bytes + type: GAUGE +--- diff --git a/internal/nrsdk/cumulative/cumulative.go b/internal/nrsdk/cumulative/cumulative.go new file mode 100644 index 0000000..a191357 --- /dev/null +++ b/internal/nrsdk/cumulative/cumulative.go @@ -0,0 +1,100 @@ +// Package cumulative creates Count metrics from cumulative values. +package cumulative + +import ( + "sync" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +type metricIdentity struct { + name string + attributesJSON string +} + +type lastValue struct { + when time.Time + value float64 +} + +// DeltaCalculator is used to create Count metrics from cumulative values. +type DeltaCalculator struct { + lock sync.Mutex + datapoints map[metricIdentity]lastValue + lastClean time.Time + expirationCheckInterval time.Duration + expirationAge time.Duration +} + +// NewDeltaCalculator creates a new DeltaCalculator. A single DeltaCalculator +// stores all cumulative values seen in order to compute deltas. +func NewDeltaCalculator() *DeltaCalculator { + return &DeltaCalculator{ + datapoints: make(map[metricIdentity]lastValue), + // These defaults are described in the Set method doc comments. + expirationCheckInterval: 20 * time.Minute, + expirationAge: 20 * time.Minute, + } +} + +// SetExpirationAge configures how old entries must be for expiration. The +// default is twenty minutes. +func (dc *DeltaCalculator) SetExpirationAge(age time.Duration) *DeltaCalculator { + dc.lock.Lock() + defer dc.lock.Unlock() + dc.expirationAge = age + return dc +} + +// SetExpirationCheckInterval configures how often to check for expired entries. +// The default is twenty minutes. +func (dc *DeltaCalculator) SetExpirationCheckInterval(interval time.Duration) *DeltaCalculator { + dc.lock.Lock() + defer dc.lock.Unlock() + dc.expirationCheckInterval = interval + return dc +} + +// CountMetric creates a count metric from the difference between the values and +// timestamps of multiple calls. If this is the first time the name/attributes +// combination has been seen then the `valid` return value will be false. +func (dc *DeltaCalculator) CountMetric(name string, attributes map[string]interface{}, val float64, now time.Time) (count telemetry.Count, valid bool) { + var attributesJSON []byte + if nil != attributes { + attributesJSON = internal.MarshalOrderedAttributes(attributes) + } + dc.lock.Lock() + defer dc.lock.Unlock() + + if now.Sub(dc.lastClean) > dc.expirationCheckInterval { + cutoff := now.Add(-dc.expirationAge) + for k, v := range dc.datapoints { + if v.when.Before(cutoff) { + delete(dc.datapoints, k) + } + } + dc.lastClean = now + } + + id := metricIdentity{name: name, attributesJSON: string(attributesJSON)} + var timestampsOrdered bool + last, ok := dc.datapoints[id] + if ok { + delta := val - last.value + timestampsOrdered = now.After(last.when) + if timestampsOrdered && delta >= 0 { + count.Name = name + count.AttributesJSON = attributesJSON + count.Value = delta + count.Timestamp = last.when + count.Interval = now.Sub(last.when) + valid = true + } + } + if !ok || timestampsOrdered { + dc.datapoints[id] = lastValue{value: val, when: now} + } + return +} diff --git a/internal/nrsdk/cumulative/cumulative_test.go b/internal/nrsdk/cumulative/cumulative_test.go new file mode 100644 index 0000000..56bc86f --- /dev/null +++ b/internal/nrsdk/cumulative/cumulative_test.go @@ -0,0 +1,228 @@ +package cumulative + +import ( + "encoding/json" + "os" + "reflect" + "strconv" + "testing" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +func Example() { + h := telemetry.NewHarvester( + telemetry.ConfigAPIKey(os.Getenv("NEW_RELIC_API_KEY")), + ) + dc := NewDeltaCalculator() + + attributes := map[string]interface{}{ + "id": 123, + "zip": "zap", + } + for { + cumulativeValue := float64(time.Now().Unix()) + if m, ok := dc.CountMetric("secondsElapsed", attributes, cumulativeValue, time.Now()); ok { + h.RecordMetric(m) + } + time.Sleep(5 * time.Second) + } +} + +func TestCountMetricBasicUse(t *testing.T) { + // Test expected usage of CountMetric. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + dc := NewDeltaCalculator() + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 100.0, now); ok { + t.Error(ok) + } + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": 1}, 200.0, now); ok { + t.Error(ok) + } + if _, ok := dc.CountMetric("m2", map[string]interface{}{"zip": "zap"}, 300.0, now); ok { + t.Error(ok) + } + m, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 105.0, now.Add(1*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m1", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 5.0, + Timestamp: now, + Interval: 1 * time.Minute, + }) { + t.Error(ok, m) + } + m, ok = dc.CountMetric("m1", map[string]interface{}{"zip": 1}, 206.0, now.Add(1*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m1", + AttributesJSON: json.RawMessage(`{"zip":1}`), + Value: 6.0, + Timestamp: now, + Interval: 1 * time.Minute, + }) { + t.Error(ok, m) + } + m, ok = dc.CountMetric("m2", map[string]interface{}{"zip": "zap"}, 307.0, now.Add(1*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m2", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 7.0, + Timestamp: now, + Interval: 1 * time.Minute, + }) { + t.Error(ok, m) + } +} + +func TestCountZeroDelta(t *testing.T) { + // Test that adding the same value twice results in a Count with a zero + // value. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + ats := map[string]interface{}{"zip": "zap"} + dc := NewDeltaCalculator() + if _, ok := dc.CountMetric("m1", ats, 5.0, now); ok { + t.Error(ok) + } + m, ok := dc.CountMetric("m1", ats, 5.0, now.Add(1*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m1", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 0.0, + Timestamp: now, + Interval: 1 * time.Minute, + }) { + t.Error(ok, m) + } +} + +func TestCountMetricNegativeDeltaReset(t *testing.T) { + // Test that CountMetric does not return a count metric with a negative + // value. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + dc := NewDeltaCalculator() + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 5.0, now); ok { + t.Error(ok) + } + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 4.0, now.Add(1*time.Minute)); ok { + t.Error(ok) + } + m, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 7.0, now.Add(2*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m1", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 3.0, + Timestamp: now.Add(1 * time.Minute), + Interval: 1 * time.Minute, + }) { + t.Error(ok, m) + } +} + +func TestTimestampOrder(t *testing.T) { + // Test that CountMetric does not return a count metric when the + // timestamp values are not in increasing order. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + dc := NewDeltaCalculator() + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 5.0, now); ok { + t.Error(ok) + } + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 6.0, now.Add(-1*time.Minute)); ok { + t.Error(ok) + } + m, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 7.0, now.Add(1*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m1", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 2.0, + Timestamp: now, + Interval: 1 * time.Minute, + }) { + t.Error(ok, m) + } +} + +func TestCountMetricNoAttributes(t *testing.T) { + // Test that CountMetric works when no attributes are provided. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + dc := NewDeltaCalculator() + if _, ok := dc.CountMetric("m1", nil, 5.0, now); ok { + t.Error(ok) + } + m, ok := dc.CountMetric("m1", nil, 10.0, now.Add(1*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m1", + Value: 5.0, + Timestamp: now, + Interval: 1 * time.Minute, + }) { + t.Error(ok, m) + } +} + +func TestExpirationDefaults(t *testing.T) { + // Test that expiration happens with the default settings. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + dc := NewDeltaCalculator() + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 5.0, now); ok { + t.Error(ok) + } + if _, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 10.0, now.Add(21*time.Minute)); ok { + t.Error(ok) + } + m, ok := dc.CountMetric("m1", map[string]interface{}{"zip": "zap"}, 12.0, now.Add(40*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m1", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 2.0, + Timestamp: now.Add(21 * time.Minute), + Interval: 19 * time.Minute, + }) { + t.Error(ok, m) + } +} + +func TestExpirationCustomSettings(t *testing.T) { + // Test that expiration happens with custom settings. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + dc := NewDeltaCalculator(). + SetExpirationAge(5 * time.Minute). + SetExpirationCheckInterval(10 * time.Minute) + + if _, ok := dc.CountMetric("m1", nil, 5.0, now); ok { + t.Error(ok) + } + if _, ok := dc.CountMetric("m2", nil, 5.0, now.Add(9*time.Minute)); ok { + t.Error(ok) + } + if _, ok := dc.CountMetric("m1", nil, 10.0, now.Add(11*time.Minute)); ok { + t.Error(ok) + } + m, ok := dc.CountMetric("m2", nil, 10.0, now.Add(11*time.Minute)) + if !ok || !reflect.DeepEqual(m, telemetry.Count{ + Name: "m2", + Value: 5.0, + Timestamp: now.Add(9 * time.Minute), + Interval: 2 * time.Minute, + }) { + t.Error(ok, m) + } +} + +func TestManyAttributes(t *testing.T) { + // Test that attributes are turned into JSON in a fixed order. Note + // that if JSON attribute order is random this test may still + // occasionally pass. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + dc := NewDeltaCalculator() + + attributes := make(map[string]interface{}) + for i := 0; i < 100; i++ { + attributes[strconv.Itoa(i)] = i + } + dc.CountMetric("myMetric", attributes, 5.0, now) + _, ok := dc.CountMetric("myMetric", attributes, 6.0, now.Add(1*time.Minute)) + if !ok { + t.Error(ok) + } +} diff --git a/internal/nrsdk/instrumentation/aggregator.go b/internal/nrsdk/instrumentation/aggregator.go new file mode 100644 index 0000000..ab175de --- /dev/null +++ b/internal/nrsdk/instrumentation/aggregator.go @@ -0,0 +1,116 @@ +package instrumentation + +import ( + "sync" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +type metricIdentity struct { + // Note that the type is not a field here since a single 'metric' type + // may contain a count, gauge, and summary. + Name string + attributesJSON string +} + +type metric struct { + s *telemetry.Summary + c *telemetry.Count + g *telemetry.Gauge +} + +type metricHandle struct { + metricIdentity + aggregator *MetricAggregator +} + +func newMetricHandle(ag *MetricAggregator, name string, attributes map[string]interface{}) metricHandle { + return metricHandle{ + aggregator: ag, + metricIdentity: metricIdentity{ + attributesJSON: string(internal.MarshalOrderedAttributes(attributes)), + Name: name, + }, + } +} + +// MetricAggregator combines individual datapoints into metrics. +type MetricAggregator struct { + lock sync.Mutex + metrics map[metricIdentity]*metric +} + +// NewMetricAggregator creates a new MetricAggregator. +func NewMetricAggregator() *MetricAggregator { + return &MetricAggregator{ + metrics: make(map[metricIdentity]*metric), + } +} + +// findOrCreateMetric finds or creates the metric associated with the given +// identity. This function assumes the Harvester is locked. +func (ag *MetricAggregator) findOrCreateMetric(identity metricIdentity) *metric { + m := ag.metrics[identity] + if nil == m { + // this happens the first time we update the value, + // or after a harvest when the metric is removed. + m = &metric{} + ag.metrics[identity] = m + } + return m +} + +// NewCount creates a new Count metric. +func (ag *MetricAggregator) NewCount(name string, attributes map[string]interface{}) *Count { + return &Count{metricHandle: newMetricHandle(ag, name, attributes)} +} + +// NewGauge creates a new Gauge metric. +func (ag *MetricAggregator) NewGauge(name string, attributes map[string]interface{}) *Gauge { + return &Gauge{metricHandle: newMetricHandle(ag, name, attributes)} +} + +// NewSummary creates a new Summary metric. +func (ag *MetricAggregator) NewSummary(name string, attributes map[string]interface{}) *Summary { + return &Summary{metricHandle: newMetricHandle(ag, name, attributes)} +} + +// Metrics returns the metrics that have been added to the aggregator since the +// last call to Metrics. Once those metrics are returned, the aggregator is +// reset and metric aggregation will begin anew. +func (ag *MetricAggregator) Metrics() []telemetry.Metric { + ag.lock.Lock() + mts := ag.metrics + ag.metrics = make(map[metricIdentity]*metric, len(mts)) + ag.lock.Unlock() + + var ms []telemetry.Metric + for _, m := range mts { + if nil != m.c { + ms = append(ms, m.c) + } + if nil != m.s { + ms = append(ms, m.s) + } + if nil != m.g { + ms = append(ms, m.g) + } + } + return ms +} + +// BeforeHarvest registers the aggregator with the harvester so that the +// aggregated metrics will be harvested on the harvester's interval. This is +// the preferred way to send metrics from a MetricAggregator to New Relic. +func (ag *MetricAggregator) BeforeHarvest(cfg *telemetry.Config) { + previous := cfg.BeforeHarvestFunc + cfg.BeforeHarvestFunc = func(h *telemetry.Harvester) { + if nil != previous { + previous(h) + } + for _, m := range ag.Metrics() { + h.RecordMetric(m) + } + } +} diff --git a/internal/nrsdk/instrumentation/aggregator_test.go b/internal/nrsdk/instrumentation/aggregator_test.go new file mode 100644 index 0000000..bfe5227 --- /dev/null +++ b/internal/nrsdk/instrumentation/aggregator_test.go @@ -0,0 +1,149 @@ +package instrumentation + +import ( + "bytes" + "context" + "encoding/json" + "io/ioutil" + "net/http" + "strconv" + "testing" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +func TestDifferentAttributes(t *testing.T) { + // Test that attributes contribute to identity, ie, metrics with the + // same name but different attributes should generate different metrics. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + ag := NewMetricAggregator() + ag.NewGauge("myGauge", map[string]interface{}{"zip": "zap"}).valueNow(1.0, now) + ag.NewGauge("myGauge", map[string]interface{}{"zip": "zup"}).valueNow(2.0, now) + expect := `[ + {"Name":"myGauge","Attributes":null,"AttributesJSON":{"zip":"zap"},"Value":1,"Timestamp":"2014-11-28T01:01:00Z"}, + {"Name":"myGauge","Attributes":null,"AttributesJSON":{"zip":"zup"},"Value":2,"Timestamp":"2014-11-28T01:01:00Z"} + ]` + testMetrics(t, ag, expect) +} + +func TestSameNameDifferentTypes(t *testing.T) { + // Test that type contributes to identity, ie, metrics with the same + // name and same attributes of different types should generate different + // metrics. + ag := NewMetricAggregator() + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + ag.NewGauge("metric", map[string]interface{}{"zip": "zap"}).valueNow(1.0, now) + ag.NewCount("metric", map[string]interface{}{"zip": "zap"}).Increment() + ag.NewSummary("metric", map[string]interface{}{"zip": "zap"}).Record(1.0) + expect := `[ + {"Name":"metric","Attributes":null,"AttributesJSON":{"zip":"zap"},"Count":1,"Sum":1,"Min":1,"Max":1,"Timestamp":"0001-01-01T00:00:00Z","Interval":0}, + {"Name":"metric","Attributes":null,"AttributesJSON":{"zip":"zap"},"Value":1,"Timestamp":"0001-01-01T00:00:00Z","Interval":0}, + {"Name":"metric","Attributes":null,"AttributesJSON":{"zip":"zap"},"Value":1,"Timestamp":"2014-11-28T01:01:00Z"} + ]` + testMetrics(t, ag, expect) +} + +func TestManyAttributes(t *testing.T) { + // Test adding the same metric with many attributes to ensure that + // attributes are serialized into JSON in a fixed order. Note that if + // JSON attribute order is random this test may still occasionally pass. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + ag := NewMetricAggregator() + attributes := make(map[string]interface{}) + for i := 0; i < 100; i++ { + attributes[strconv.Itoa(i)] = i + } + ag.NewGauge("myGauge", attributes).valueNow(1.0, now) + ag.NewGauge("myGauge", attributes).valueNow(2.0, now) + if len(ag.metrics) != 1 { + t.Fatal(len(ag.metrics)) + } +} + +func TestBeforeHarvestFunc(t *testing.T) { + ag := NewMetricAggregator() + + cfg := &telemetry.Config{} + ag.BeforeHarvest(cfg) + if nil == cfg.BeforeHarvestFunc { + t.Error("BeforeHarvestFunc not set") + } + + var calls int + cfg = &telemetry.Config{ + BeforeHarvestFunc: func(h *telemetry.Harvester) { + calls++ + }, + } + ag.BeforeHarvest(cfg) + if nil == cfg.BeforeHarvestFunc { + t.Error("BeforeHarvestFunc not set") + } + cfg.BeforeHarvestFunc(nil) + if 1 != calls { + t.Error("original BeforeHarvestFunc not called") + } +} + +type roundTripperFunc func(*http.Request) (*http.Response, error) + +func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return fn(req) +} + +// optional interface required for go1.4 and go1.5 +func (fn roundTripperFunc) CancelRequest(*http.Request) {} + +func emptyResponse(status int) *http.Response { + return &http.Response{ + StatusCode: status, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + } +} + +func TestBeforeHarvestFuncRecordsMetrics(t *testing.T) { + ag := NewMetricAggregator() + ag.NewCount("mycount", nil).Increment() + + var posts int + rt := roundTripperFunc(func(req *http.Request) (*http.Response, error) { + posts++ + body, err := ioutil.ReadAll(req.Body) + if nil != err { + t.Fatal(err) + } + defer req.Body.Close() + uncompressed, err := internal.Uncompress(body) + if err != nil { + t.Fatal(err) + } + var helper []struct { + Metrics json.RawMessage `json:"metrics"` + } + if err := json.Unmarshal(uncompressed, &helper); err != nil { + t.Fatal("unable to unmarshal metrics for sorting", err) + } + + expected := `[{"name":"mycount","type":"count","value":1,"attributes":{}}]` + if string(helper[0].Metrics) != expected { + t.Error("incorrect metrics found", helper[0].Metrics) + } + + return emptyResponse(200), nil + }) + + h := telemetry.NewHarvester( + ag.BeforeHarvest, + func(cfg *telemetry.Config) { + cfg.APIKey = "api-key" + cfg.HarvestPeriod = 0 + cfg.Client.Transport = rt + }, + ) + h.HarvestNow(context.Background()) + if 1 != posts { + t.Error("no metric data posted") + } +} diff --git a/internal/nrsdk/instrumentation/benchmark_test.go b/internal/nrsdk/instrumentation/benchmark_test.go new file mode 100644 index 0000000..230e2ce --- /dev/null +++ b/internal/nrsdk/instrumentation/benchmark_test.go @@ -0,0 +1,20 @@ +package instrumentation + +import "testing" + +func BenchmarkAddMetric(b *testing.B) { + // This benchmark tests creating and aggregating a summary. + ag := NewMetricAggregator() + attributes := map[string]interface{}{"zip": "zap", "zop": 123} + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + summary := ag.NewSummary("mySummary", attributes) + summary.Record(12.3) + if nil == summary { + b.Fatal("nil summary") + } + } +} diff --git a/internal/nrsdk/instrumentation/count.go b/internal/nrsdk/instrumentation/count.go new file mode 100644 index 0000000..ee75d32 --- /dev/null +++ b/internal/nrsdk/instrumentation/count.go @@ -0,0 +1,53 @@ +package instrumentation + +import ( + "encoding/json" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +// Count is the metric type that counts the number of times an event occurred. +// This counter is reset every time the data is reported, meaning the value +// reported represents the difference in count over the reporting time window. +// +// Example possible uses: +// +// * the number of messages put on a topic +// * the number of HTTP requests +// * the number of errors thrown +// * the number of support tickets answered +// +type Count struct{ metricHandle } + +// Increment increases the Count value by one. +func (c *Count) Increment() { + c.Increase(1) +} + +// Increase increases the Count value by the number given. The value must be +// non-negative. +func (c *Count) Increase(val float64) { + if nil == c { + return + } + ag := c.aggregator + if nil == ag { + return + } + + if val < 0 { + return + } + + ag.lock.Lock() + defer ag.lock.Unlock() + + m := ag.findOrCreateMetric(c.metricIdentity) + if nil == m.c { + m.c = &telemetry.Count{ + Name: c.Name, + AttributesJSON: json.RawMessage(c.attributesJSON), + } + } + m.c.Value += val +} diff --git a/internal/nrsdk/instrumentation/count_test.go b/internal/nrsdk/instrumentation/count_test.go new file mode 100644 index 0000000..e86ef2a --- /dev/null +++ b/internal/nrsdk/instrumentation/count_test.go @@ -0,0 +1,43 @@ +package instrumentation + +import ( + "testing" +) + +func TestCount(t *testing.T) { + ag := NewMetricAggregator() + count := ag.NewCount("myCount", map[string]interface{}{"zip": "zap"}) + count.Increase(22.5) + count.Increment() + + expect := `[{"Name":"myCount","Attributes":null,"AttributesJSON":{"zip":"zap"},"Value":23.5,"Timestamp":"0001-01-01T00:00:00Z","Interval":0}]` + testMetrics(t, ag, expect) +} + +func TestCountNegative(t *testing.T) { + ag := NewMetricAggregator() + count := ag.NewCount("myCount", map[string]interface{}{"zip": "zap"}) + count.Increase(-123) + if ms := ag.Metrics(); len(ms) != 0 { + t.Fatal(ms) + } +} + +func TestNilAggregatorCounts(t *testing.T) { + var ag *MetricAggregator + count := ag.NewCount("count", map[string]interface{}{}) + count.Increment() + count.Increase(5) +} + +func TestNilCountMethods(t *testing.T) { + var count *Count + count.Increment() + count.Increase(5) +} + +func TestCountNilAggregator(t *testing.T) { + c := Count{} + c.Increment() + c.Increase(1) +} diff --git a/internal/nrsdk/instrumentation/doc.go b/internal/nrsdk/instrumentation/doc.go new file mode 100644 index 0000000..e40029d --- /dev/null +++ b/internal/nrsdk/instrumentation/doc.go @@ -0,0 +1,3 @@ +// Package instrumentation aggregates individual data points into metrics. It +// is designed to be used as a supplement to the telemetry package. +package instrumentation diff --git a/internal/nrsdk/instrumentation/example_test.go b/internal/nrsdk/instrumentation/example_test.go new file mode 100644 index 0000000..a4593a2 --- /dev/null +++ b/internal/nrsdk/instrumentation/example_test.go @@ -0,0 +1,62 @@ +package instrumentation + +import ( + "os" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +func Example() { + // Create a MetricAggregator: + agg := NewMetricAggregator() + // Then create a telemetry.NewHarvester. Be sure to register the + // aggregator with the harvester by using the aggregator's + // BeforeHarvest method. + telemetry.NewHarvester( + telemetry.ConfigAPIKey(os.Getenv("NEW_RELIC_API_KEY")), + agg.BeforeHarvest, + ) + + // Now use the aggregator to create metrics. You can create metrics in + // a single line: + agg.NewGauge("temperature", map[string]interface{}{"zip": "zap"}).Value(23.4) + + // Or use a reference to the metric's identity to add data-points with + // to the same metric with minimal overhead: + count := agg.NewCount("iterations", map[string]interface{}{"zip": "zap"}) + for { + count.Increment() + } + // When the harvester harvests (by default every 5 seconds), it will + // collect metrics from the MetricAggregator and send them to New Relic. +} + +func ExampleMetricAggregator_NewCount() { + ag := NewMetricAggregator() + count := ag.NewCount("myCount", map[string]interface{}{"zip": "zap"}) + count.Increment() +} + +func ExampleMetricAggregator_NewGauge() { + ag := NewMetricAggregator() + gauge := ag.NewGauge("temperature", map[string]interface{}{"zip": "zap"}) + gauge.Value(23.4) +} + +func ExampleMetricAggregator_NewSummary() { + ag := NewMetricAggregator() + summary := ag.NewSummary("mySummary", map[string]interface{}{"zip": "zap"}) + summary.RecordDuration(3 * time.Second) +} + +func ExampleMetricAggregator_BeforeHarvest() { + // Create a MetricAggregator + agg := NewMetricAggregator() + // Register this aggregator with a Harvester. This is necessary if you want + // to harvest metrics on a schedule. + telemetry.NewHarvester( + telemetry.ConfigAPIKey(os.Getenv("NEW_RELIC_API_KEY")), + agg.BeforeHarvest, + ) +} diff --git a/internal/nrsdk/instrumentation/gauge.go b/internal/nrsdk/instrumentation/gauge.go new file mode 100644 index 0000000..afa3a8e --- /dev/null +++ b/internal/nrsdk/instrumentation/gauge.go @@ -0,0 +1,54 @@ +package instrumentation + +import ( + "encoding/json" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +// Gauge is the metric type that records a value that can increase or decrease. +// It generally represents the value for something at a particular moment in +// time. One typically records a Gauge value on a set interval. +// +// Only the most recent Gauge metric value is reported over a given harvest +// period, all others are dropped. +// +// Example possible uses: +// +// * the temperature in a room +// * the amount of memory currently in use for a process +// * the bytes per second flowing into Kafka at this exact moment in time +// * the current speed of your car +// +type Gauge struct{ metricHandle } + +// valueNow facilitates testing. +func (g *Gauge) valueNow(val float64, now time.Time) { + if nil == g { + return + } + ag := g.aggregator + if nil == ag { + return + } + + ag.lock.Lock() + defer ag.lock.Unlock() + + m := ag.findOrCreateMetric(g.metricIdentity) + if nil == m.g { + m.g = &telemetry.Gauge{ + Name: g.Name, + AttributesJSON: json.RawMessage(g.attributesJSON), + Value: val, + } + } + m.g.Value = val + m.g.Timestamp = now +} + +// Value records the value given. +func (g *Gauge) Value(val float64) { + g.valueNow(val, time.Now()) +} diff --git a/internal/nrsdk/instrumentation/gauge_test.go b/internal/nrsdk/instrumentation/gauge_test.go new file mode 100644 index 0000000..15a217a --- /dev/null +++ b/internal/nrsdk/instrumentation/gauge_test.go @@ -0,0 +1,87 @@ +package instrumentation + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + "testing" + "time" +) + +// compactJSONString removes the whitespace from a JSON string. This function +// will panic if the string provided is not valid JSON. +func compactJSONString(js string) string { + buf := new(bytes.Buffer) + if err := json.Compact(buf, []byte(js)); err != nil { + panic(fmt.Errorf("unable to compact JSON: %v", err)) + } + return buf.String() +} + +// sortedMetricsHelper is used to sort metrics for JSON comparison. +type sortedMetricsHelper []json.RawMessage + +func (h sortedMetricsHelper) Len() int { + return len(h) +} +func (h sortedMetricsHelper) Less(i, j int) bool { + return string(h[i]) < string(h[j]) +} +func (h sortedMetricsHelper) Swap(i, j int) { + h[i], h[j] = h[j], h[i] +} + +func testMetrics(t testing.TB, ag *MetricAggregator, expect string) { + js, err := json.Marshal(ag.Metrics()) + if err != nil { + t.Fatal(err) + } + var helper sortedMetricsHelper + if err := json.Unmarshal(js, &helper); err != nil { + t.Fatal("unable to unmarshal metrics for sorting", err) + return + } + sort.Sort(helper) + js, err = json.Marshal(helper) + if nil != err { + t.Fatal("unable to marshal metrics", err) + return + } + actual := string(js) + + if th, ok := t.(interface{ Helper() }); ok { + th.Helper() + } + compactExpect := compactJSONString(expect) + if compactExpect != actual { + t.Errorf("\nexpect=%s\nactual=%s\n", compactExpect, actual) + } +} + +func TestGauge(t *testing.T) { + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + ag := NewMetricAggregator() + ag.NewGauge("myGauge", map[string]interface{}{"zip": "zap"}).valueNow(123.4, now) + + expect := `[{"Name":"myGauge","Attributes":null,"AttributesJSON":{"zip":"zap"},"Value":123.4,"Timestamp":"2014-11-28T01:01:00Z"}]` + testMetrics(t, ag, expect) +} + +func TestNilAggregatorGauges(t *testing.T) { + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + var ag *MetricAggregator + gauge := ag.NewGauge("gauge", map[string]interface{}{}) + gauge.valueNow(5.5, now) +} + +func TestNilGaugeMethods(t *testing.T) { + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + var gauge *Gauge + gauge.valueNow(5.5, now) +} + +func TestGaugeNilAggregator(t *testing.T) { + g := Gauge{} + g.Value(10) +} diff --git a/internal/nrsdk/instrumentation/summary.go b/internal/nrsdk/instrumentation/summary.go new file mode 100644 index 0000000..78452bb --- /dev/null +++ b/internal/nrsdk/instrumentation/summary.go @@ -0,0 +1,65 @@ +package instrumentation + +import ( + "encoding/json" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" +) + +// Summary is the metric type used for reporting aggregated information about +// discrete events. It provides the count, average, sum, min and max values +// over time. All fields are reset to 0 every reporting interval. +// +// The final metric reported at the end of a harvest period is an aggregation. +// Values reported are the count of the number of metrics recorded, sum of +// all their values, minimum value recorded, and maximum value recorded. +// +// Example possible uses: +// +// * the duration and count of spans +// * the duration and count of transactions +// * the time each message spent in a queue +// +type Summary struct{ metricHandle } + +// Record adds an observation to a summary. +func (s *Summary) Record(val float64) { + if nil == s { + return + } + ag := s.aggregator + if nil == ag { + return + } + + ag.lock.Lock() + defer ag.lock.Unlock() + + m := ag.findOrCreateMetric(s.metricIdentity) + if nil == m.s { + m.s = &telemetry.Summary{ + Name: s.Name, + AttributesJSON: json.RawMessage(s.attributesJSON), + Count: 1, + Sum: val, + Min: val, + Max: val, + } + return + } + m.s.Sum += val + m.s.Count++ + if val < m.s.Min { + m.s.Min = val + } + if val > m.s.Max { + m.s.Max = val + } +} + +// RecordDuration adds a duration observation to a summary. It records the +// value in milliseconds, New Relic's recommended duration units. +func (s *Summary) RecordDuration(val time.Duration) { + s.Record(val.Seconds() * 1000.0) +} diff --git a/internal/nrsdk/instrumentation/summary_test.go b/internal/nrsdk/instrumentation/summary_test.go new file mode 100644 index 0000000..0993d32 --- /dev/null +++ b/internal/nrsdk/instrumentation/summary_test.go @@ -0,0 +1,57 @@ +package instrumentation + +import ( + "testing" + "time" +) + +func TestSummary(t *testing.T) { + ag := NewMetricAggregator() + summary := ag.NewSummary("mySummary", map[string]interface{}{"zip": "zap"}) + summary.Record(3) + summary.Record(4) + summary.Record(5) + + expect := `[{"Name":"mySummary","Attributes":null,"AttributesJSON":{"zip":"zap"},"Count":3,"Sum":12,"Min":3,"Max":5,"Timestamp":"0001-01-01T00:00:00Z","Interval":0}]` + testMetrics(t, ag, expect) +} + +func TestSummaryDuration(t *testing.T) { + ag := NewMetricAggregator() + summary := ag.NewSummary("mySummary", map[string]interface{}{"zip": "zap"}) + summary.RecordDuration(3 * time.Second) + summary.RecordDuration(4 * time.Second) + summary.RecordDuration(5 * time.Second) + + expect := `[{"Name":"mySummary","Attributes":null,"AttributesJSON":{"zip":"zap"},"Count":3,"Sum":12000,"Min":3000,"Max":5000,"Timestamp":"0001-01-01T00:00:00Z","Interval":0}]` + testMetrics(t, ag, expect) +} + +func TestNilAggregatorSummaries(t *testing.T) { + var ag *MetricAggregator + summary := ag.NewSummary("summary", map[string]interface{}{}) + summary.Record(1) + summary.RecordDuration(time.Second) +} + +func TestNilSummaryMethods(t *testing.T) { + var summary *Summary + summary.Record(1) + summary.RecordDuration(time.Second) +} + +func TestSummaryNilAggregator(t *testing.T) { + s := Summary{} + s.Record(10) + s.RecordDuration(time.Second) +} + +func TestSummaryMinMax(t *testing.T) { + ag := NewMetricAggregator() + s := ag.NewSummary("sum", nil) + s.Record(2) + s.Record(1) + s.Record(3) + expect := `[{"Name":"sum","Attributes":null,"AttributesJSON":{},"Count":3,"Sum":6,"Min":1,"Max":3,"Timestamp":"0001-01-01T00:00:00Z","Interval":0}]` + testMetrics(t, ag, expect) +} diff --git a/internal/nrsdk/internal/attributes.go b/internal/nrsdk/internal/attributes.go new file mode 100644 index 0000000..5435597 --- /dev/null +++ b/internal/nrsdk/internal/attributes.go @@ -0,0 +1,103 @@ +package internal + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" +) + +// MarshalAttributes turns attributes into JSON. +func MarshalAttributes(ats map[string]interface{}) []byte { + attrs := Attributes(ats) + buf := &bytes.Buffer{} + attrs.WriteJSON(buf) + return buf.Bytes() +} + +// Attributes is used for marshalling attributes to JSON. +type Attributes map[string]interface{} + +// WriteJSON writes the attributes in JSON. +func (attrs Attributes) WriteJSON(buf *bytes.Buffer) { + w := JSONFieldsWriter{Buf: buf} + w.Buf.WriteByte('{') + AddAttributes(&w, attrs) + w.Buf.WriteByte('}') +} + +// AddAttributes writes the attributes to the fields writer. +func AddAttributes(w *JSONFieldsWriter, attrs map[string]interface{}) { + for key, val := range attrs { + writeAttribute(w, key, val) + } +} + +// MarshalOrderedAttributes marshals the given attributes into JSON in +// alphabetical order. +func MarshalOrderedAttributes(attrs map[string]interface{}) []byte { + buf := &bytes.Buffer{} + OrderedAttributes(attrs).WriteJSON(buf) + return buf.Bytes() +} + +// OrderedAttributes turns attributes into JSON in a fixed order. +type OrderedAttributes map[string]interface{} + +// WriteJSON writes the attributes in JSON in a fixed order. +func (attrs OrderedAttributes) WriteJSON(buf *bytes.Buffer) { + keys := make([]string, 0, len(attrs)) + for k := range attrs { + keys = append(keys, k) + } + sort.Strings(keys) + w := JSONFieldsWriter{Buf: buf} + w.Buf.WriteByte('{') + for _, k := range keys { + writeAttribute(&w, k, attrs[k]) + } + w.Buf.WriteByte('}') +} + +func writeAttribute(w *JSONFieldsWriter, key string, val interface{}) { + switch v := val.(type) { + case string: + w.StringField(key, v) + case bool: + if v { + w.RawField(key, json.RawMessage(`true`)) + } else { + w.RawField(key, json.RawMessage(`false`)) + } + case uint8: + w.IntField(key, int64(v)) + case uint16: + w.IntField(key, int64(v)) + case uint32: + w.IntField(key, int64(v)) + case uint64: + w.IntField(key, int64(v)) + case uint: + w.IntField(key, int64(v)) + case uintptr: + w.IntField(key, int64(v)) + case int8: + w.IntField(key, int64(v)) + case int16: + w.IntField(key, int64(v)) + case int32: + w.IntField(key, int64(v)) + case int64: + w.IntField(key, v) + case int: + w.IntField(key, int64(v)) + case float32: + w.FloatField(key, float64(v)) + case float64: + w.FloatField(key, v) + case nil: + // nil gets dropped. + default: + w.StringField(key, fmt.Sprintf("%T", v)) + } +} diff --git a/internal/nrsdk/internal/attributes_test.go b/internal/nrsdk/internal/attributes_test.go new file mode 100644 index 0000000..2e25533 --- /dev/null +++ b/internal/nrsdk/internal/attributes_test.go @@ -0,0 +1,123 @@ +package internal + +import ( + "bytes" + "strconv" + "testing" +) + +func TestAttributesWriteJSON(t *testing.T) { + tests := []struct { + key string + val interface{} + expect string + }{ + {"string", "string", `{"string":"string"}`}, + {"true", true, `{"true":true}`}, + {"false", false, `{"false":false}`}, + {"uint8", uint8(1), `{"uint8":1}`}, + {"uint16", uint16(1), `{"uint16":1}`}, + {"uint32", uint32(1), `{"uint32":1}`}, + {"uint64", uint64(1), `{"uint64":1}`}, + {"uint", uint(1), `{"uint":1}`}, + {"uintptr", uintptr(1), `{"uintptr":1}`}, + {"int8", int8(1), `{"int8":1}`}, + {"int16", int16(1), `{"int16":1}`}, + {"int32", int32(1), `{"int32":1}`}, + {"int64", int64(1), `{"int64":1}`}, + {"int", int(1), `{"int":1}`}, + {"float32", float32(1), `{"float32":1}`}, + {"float64", float64(1), `{"float64":1}`}, + {"default", func() {}, `{"default":"func()"}`}, + } + + for _, test := range tests { + buf := &bytes.Buffer{} + ats := Attributes(map[string]interface{}{ + test.key: test.val, + }) + ats.WriteJSON(buf) + got := string(buf.Bytes()) + if got != test.expect { + t.Errorf("key='%s' val=%v expect='%s' got='%s'", + test.key, test.val, test.expect, got) + } + } +} + +func TestEmptyAttributesWriteJSON(t *testing.T) { + var ats Attributes + buf := &bytes.Buffer{} + ats.WriteJSON(buf) + got := string(buf.Bytes()) + if got != `{}` { + t.Error(got) + } +} + +func TestOrderedAttributesWriteJSON(t *testing.T) { + ats := map[string]interface{}{ + "z": 123, + "b": "hello", + "a": true, + "x": 13579, + "m": "zap", + "c": "zip", + } + got := string(MarshalOrderedAttributes(ats)) + if got != `{"a":true,"b":"hello","c":"zip","m":"zap","x":13579,"z":123}` { + t.Error(got) + } +} + +func TestEmptyOrderedAttributesWriteJSON(t *testing.T) { + got := string(MarshalOrderedAttributes(nil)) + if got != `{}` { + t.Error(got) + } +} + +func sampleAttributes(num int) map[string]interface{} { + attributes := make(map[string]interface{}) + for i := 0; i < num; i++ { + istr := strconv.Itoa(i) + // Mix string and integer attributes: + if i%2 == 0 { + attributes[istr] = istr + } else { + attributes[istr] = i + } + } + return attributes +} + +func BenchmarkAttributes(b *testing.B) { + attributes := Attributes(sampleAttributes(1000)) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + // Compare this to: `js, err := json.Marshal(attributes)` + buf := &bytes.Buffer{} + attributes.WriteJSON(buf) + if 0 == buf.Len() { + b.Fatal(buf.Len()) + } + } +} + +func BenchmarkOrderedAttributes(b *testing.B) { + attributes := sampleAttributes(1000) + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + // Compare this to: `js, err := json.Marshal(attributes)` + js := MarshalOrderedAttributes(attributes) + if len(js) == 0 { + b.Fatal(string(js)) + } + } +} diff --git a/internal/nrsdk/internal/compress.go b/internal/nrsdk/internal/compress.go new file mode 100644 index 0000000..065b61a --- /dev/null +++ b/internal/nrsdk/internal/compress.go @@ -0,0 +1,32 @@ +package internal + +import ( + "bytes" + "compress/gzip" + "io/ioutil" +) + +// Compress gzips the given input. +func Compress(b []byte) (*bytes.Buffer, error) { + var buf bytes.Buffer + w := gzip.NewWriter(&buf) + _, err := w.Write(b) + w.Close() + + if nil != err { + return nil, err + } + + return &buf, nil +} + +// Uncompress un-gzips the given input. +func Uncompress(b []byte) ([]byte, error) { + buf := bytes.NewBuffer(b) + gz, err := gzip.NewReader(buf) + if nil != err { + return nil, err + } + defer gz.Close() + return ioutil.ReadAll(gz) +} diff --git a/internal/nrsdk/internal/compress_test.go b/internal/nrsdk/internal/compress_test.go new file mode 100644 index 0000000..044454f --- /dev/null +++ b/internal/nrsdk/internal/compress_test.go @@ -0,0 +1,18 @@ +package internal + +import "testing" + +func TestCompress(t *testing.T) { + input := "this is the input string that needs to be compressed" + buf, err := Compress([]byte(input)) + if err != nil { + t.Fatal(err) + } + back, err := Uncompress(buf.Bytes()) + if err != nil { + t.Fatal(err) + } + if string(back) != input { + t.Error(string(back)) + } +} diff --git a/internal/nrsdk/internal/json_writer.go b/internal/nrsdk/internal/json_writer.go new file mode 100644 index 0000000..2000c32 --- /dev/null +++ b/internal/nrsdk/internal/json_writer.go @@ -0,0 +1,72 @@ +package internal + +import ( + "bytes" + "encoding/json" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal/jsonx" +) + +// JSONWriter is something that can write JSON to a buffer. +type JSONWriter interface { + WriteJSON(buf *bytes.Buffer) +} + +// JSONFieldsWriter helps write JSON objects to a buffer. +type JSONFieldsWriter struct { + Buf *bytes.Buffer + needsComma bool +} + +// AddKey adds the key for a new object field. A comma is prefixed if another +// field has previously been added. +func (w *JSONFieldsWriter) AddKey(key string) { + if w.needsComma { + w.Buf.WriteByte(',') + } else { + w.needsComma = true + } + // defensively assume that the key needs escaping: + jsonx.AppendString(w.Buf, key) + w.Buf.WriteByte(':') +} + +// StringField adds a string field to the object. +func (w *JSONFieldsWriter) StringField(key string, val string) { + w.AddKey(key) + jsonx.AppendString(w.Buf, val) +} + +// IntField adds an int field to the object. +func (w *JSONFieldsWriter) IntField(key string, val int64) { + w.AddKey(key) + jsonx.AppendInt(w.Buf, val) +} + +// FloatField adds a float field to the object. +func (w *JSONFieldsWriter) FloatField(key string, val float64) { + w.AddKey(key) + jsonx.AppendFloat(w.Buf, val) +} + +// BoolField adds a bool field to the object. +func (w *JSONFieldsWriter) BoolField(key string, val bool) { + w.AddKey(key) + if val { + w.Buf.WriteString("true") + } else { + w.Buf.WriteString("false") + } +} + +// RawField adds a raw JSON field to the object. +func (w *JSONFieldsWriter) RawField(key string, val json.RawMessage) { + w.AddKey(key) + w.Buf.Write(val) +} + +// WriterField adds a JSONWriter field to the object. +func (w *JSONFieldsWriter) WriterField(key string, val JSONWriter) { + w.AddKey(key) + val.WriteJSON(w.Buf) +} diff --git a/internal/nrsdk/internal/jsonx/encode.go b/internal/nrsdk/internal/jsonx/encode.go new file mode 100644 index 0000000..6495829 --- /dev/null +++ b/internal/nrsdk/internal/jsonx/encode.go @@ -0,0 +1,174 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package jsonx extends the encoding/json package to encode JSON +// incrementally and without requiring reflection. +package jsonx + +import ( + "bytes" + "encoding/json" + "math" + "reflect" + "strconv" + "unicode/utf8" +) + +var hex = "0123456789abcdef" + +// AppendString escapes s appends it to buf. +func AppendString(buf *bytes.Buffer, s string) { + buf.WriteByte('"') + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { + i++ + continue + } + if start < i { + buf.WriteString(s[start:i]) + } + switch b { + case '\\', '"': + buf.WriteByte('\\') + buf.WriteByte(b) + case '\n': + buf.WriteByte('\\') + buf.WriteByte('n') + case '\r': + buf.WriteByte('\\') + buf.WriteByte('r') + case '\t': + buf.WriteByte('\\') + buf.WriteByte('t') + default: + // This encodes bytes < 0x20 except for \n and \r, + // as well as <, > and &. The latter are escaped because they + // can lead to security holes when user-controlled strings + // are rendered into JSON and served to some browsers. + buf.WriteString(`\u00`) + buf.WriteByte(hex[b>>4]) + buf.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRuneInString(s[i:]) + if c == utf8.RuneError && size == 1 { + if start < i { + buf.WriteString(s[start:i]) + } + buf.WriteString(`\ufffd`) + i += size + start = i + continue + } + // U+2028 is LINE SEPARATOR. + // U+2029 is PARAGRAPH SEPARATOR. + // They are both technically valid characters in JSON strings, + // but don't work in JSONP, which has to be evaluated as JavaScript, + // and can lead to security holes there. It is valid JSON to + // escape them, so we do so unconditionally. + // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. + if c == '\u2028' || c == '\u2029' { + if start < i { + buf.WriteString(s[start:i]) + } + buf.WriteString(`\u202`) + buf.WriteByte(hex[c&0xF]) + i += size + start = i + continue + } + i += size + } + if start < len(s) { + buf.WriteString(s[start:]) + } + buf.WriteByte('"') +} + +// AppendStringArray appends an array of string literals to buf. +func AppendStringArray(buf *bytes.Buffer, a ...string) { + buf.WriteByte('[') + for i, s := range a { + if i > 0 { + buf.WriteByte(',') + } + AppendString(buf, s) + } + buf.WriteByte(']') +} + +// AppendFloat appends a numeric literal representing the value to buf. +func AppendFloat(buf *bytes.Buffer, x float64) error { + var scratch [64]byte + + if math.IsInf(x, 0) || math.IsNaN(x) { + return &json.UnsupportedValueError{ + Value: reflect.ValueOf(x), + Str: strconv.FormatFloat(x, 'g', -1, 64), + } + } + + buf.Write(strconv.AppendFloat(scratch[:0], x, 'g', -1, 64)) + return nil +} + +// AppendFloatArray appends an array of numeric literals to buf. +func AppendFloatArray(buf *bytes.Buffer, a ...float64) error { + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteByte(',') + } + if err := AppendFloat(buf, x); err != nil { + return err + } + } + buf.WriteByte(']') + return nil +} + +// AppendInt appends a numeric literal representing the value to buf. +func AppendInt(buf *bytes.Buffer, x int64) { + var scratch [64]byte + buf.Write(strconv.AppendInt(scratch[:0], x, 10)) +} + +// AppendIntArray appends an array of numeric literals to buf. +func AppendIntArray(buf *bytes.Buffer, a ...int64) { + var scratch [64]byte + + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteByte(',') + } + buf.Write(strconv.AppendInt(scratch[:0], x, 10)) + } + buf.WriteByte(']') +} + +// AppendUint appends a numeric literal representing the value to buf. +func AppendUint(buf *bytes.Buffer, x uint64) { + var scratch [64]byte + buf.Write(strconv.AppendUint(scratch[:0], x, 10)) +} + +// AppendUintArray appends an array of numeric literals to buf. +func AppendUintArray(buf *bytes.Buffer, a ...uint64) { + var scratch [64]byte + + buf.WriteByte('[') + for i, x := range a { + if i > 0 { + buf.WriteByte(',') + } + buf.Write(strconv.AppendUint(scratch[:0], x, 10)) + } + buf.WriteByte(']') +} diff --git a/internal/nrsdk/internal/jsonx/encode_test.go b/internal/nrsdk/internal/jsonx/encode_test.go new file mode 100644 index 0000000..2b97c5f --- /dev/null +++ b/internal/nrsdk/internal/jsonx/encode_test.go @@ -0,0 +1,179 @@ +package jsonx + +import ( + "bytes" + "math" + "testing" +) + +func TestAppendFloat(t *testing.T) { + buf := &bytes.Buffer{} + + err := AppendFloat(buf, math.NaN()) + if err == nil { + t.Error("AppendFloat(NaN) should return an error") + } + + err = AppendFloat(buf, math.Inf(1)) + if err == nil { + t.Error("AppendFloat(+Inf) should return an error") + } + + err = AppendFloat(buf, math.Inf(-1)) + if err == nil { + t.Error("AppendFloat(-Inf) should return an error") + } +} + +func TestAppendFloats(t *testing.T) { + buf := &bytes.Buffer{} + + AppendFloatArray(buf) + if want, got := "[]", buf.String(); want != got { + t.Errorf("AppendFloatArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendFloatArray(buf, 3.14) + if want, got := "[3.14]", buf.String(); want != got { + t.Errorf("AppendFloatArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendFloatArray(buf, 1, 2) + if want, got := "[1,2]", buf.String(); want != got { + t.Errorf("AppendFloatArray(buf)=%q want=%q", got, want) + } +} + +func TestAppendInt(t *testing.T) { + buf := &bytes.Buffer{} + + AppendInt(buf, 42) + if got := buf.String(); got != "42" { + t.Errorf("AppendUint(42) = %#q want %#q", got, "42") + } + + buf.Reset() + AppendInt(buf, -42) + if got := buf.String(); got != "-42" { + t.Errorf("AppendUint(-42) = %#q want %#q", got, "-42") + } +} + +func TestAppendIntArray(t *testing.T) { + buf := &bytes.Buffer{} + + AppendIntArray(buf) + if want, got := "[]", buf.String(); want != got { + t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendIntArray(buf, 42) + if want, got := "[42]", buf.String(); want != got { + t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendIntArray(buf, 1, -2) + if want, got := "[1,-2]", buf.String(); want != got { + t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendIntArray(buf, 1, -2, 0) + if want, got := "[1,-2,0]", buf.String(); want != got { + t.Errorf("AppendIntArray(buf)=%q want=%q", got, want) + } +} + +func TestAppendUint(t *testing.T) { + buf := &bytes.Buffer{} + + AppendUint(buf, 42) + if got := buf.String(); got != "42" { + t.Errorf("AppendUint(42) = %#q want %#q", got, "42") + } +} + +func TestAppendUintArray(t *testing.T) { + buf := &bytes.Buffer{} + + AppendUintArray(buf) + if want, got := "[]", buf.String(); want != got { + t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendUintArray(buf, 42) + if want, got := "[42]", buf.String(); want != got { + t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendUintArray(buf, 1, 2) + if want, got := "[1,2]", buf.String(); want != got { + t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) + } + + buf.Reset() + AppendUintArray(buf, 1, 2, 3) + if want, got := "[1,2,3]", buf.String(); want != got { + t.Errorf("AppendUintArray(buf)=%q want=%q", got, want) + } +} + +var encodeStringTests = []struct { + in string + out string +}{ + {"\x00", `"\u0000"`}, + {"\x01", `"\u0001"`}, + {"\x02", `"\u0002"`}, + {"\x03", `"\u0003"`}, + {"\x04", `"\u0004"`}, + {"\x05", `"\u0005"`}, + {"\x06", `"\u0006"`}, + {"\x07", `"\u0007"`}, + {"\x08", `"\u0008"`}, + {"\x09", `"\t"`}, + {"\x0a", `"\n"`}, + {"\x0b", `"\u000b"`}, + {"\x0c", `"\u000c"`}, + {"\x0d", `"\r"`}, + {"\x0e", `"\u000e"`}, + {"\x0f", `"\u000f"`}, + {"\x10", `"\u0010"`}, + {"\x11", `"\u0011"`}, + {"\x12", `"\u0012"`}, + {"\x13", `"\u0013"`}, + {"\x14", `"\u0014"`}, + {"\x15", `"\u0015"`}, + {"\x16", `"\u0016"`}, + {"\x17", `"\u0017"`}, + {"\x18", `"\u0018"`}, + {"\x19", `"\u0019"`}, + {"\x1a", `"\u001a"`}, + {"\x1b", `"\u001b"`}, + {"\x1c", `"\u001c"`}, + {"\x1d", `"\u001d"`}, + {"\x1e", `"\u001e"`}, + {"\x1f", `"\u001f"`}, + {"\\", `"\\"`}, + {`"`, `"\""`}, + {"the\u2028quick\t\nbrown\u2029fox", `"the\u2028quick\t\nbrown\u2029fox"`}, +} + +func TestAppendString(t *testing.T) { + buf := &bytes.Buffer{} + + for _, tt := range encodeStringTests { + buf.Reset() + + AppendString(buf, tt.in) + if got := buf.String(); got != tt.out { + t.Errorf("AppendString(%q) = %#q, want %#q", tt.in, got, tt.out) + } + } +} diff --git a/internal/nrsdk/internal/version.go b/internal/nrsdk/internal/version.go new file mode 100644 index 0000000..6290c1d --- /dev/null +++ b/internal/nrsdk/internal/version.go @@ -0,0 +1,17 @@ +package internal + +import "net/http" + +const ( + major = "0" + minor = "1" + patch = "0" + + // Version is the full string version of this SDK. + Version = major + "." + minor + "." + patch +) + +// AddUserAgentHeader adds a User-Agent header with the SDK's version. +func AddUserAgentHeader(h http.Header) { + h.Add("User-Agent", "NewRelic-Go-TelemetrySDK/"+Version) +} diff --git a/internal/nrsdk/telemetry/attributes.go b/internal/nrsdk/telemetry/attributes.go new file mode 100644 index 0000000..0c94bb5 --- /dev/null +++ b/internal/nrsdk/telemetry/attributes.go @@ -0,0 +1,43 @@ +package telemetry + +import ( + "fmt" +) + +func attributeValueValid(val interface{}) bool { + switch val.(type) { + case string, bool, uint8, uint16, uint32, uint64, int8, int16, + int32, int64, float32, float64, uint, int, uintptr: + return true + default: + return false + } +} + +// vetAttributes returns the attributes that are valid. vetAttributes does not +// modify remove any elements from its parameter. +func vetAttributes(attributes map[string]interface{}, errorLogger func(map[string]interface{})) map[string]interface{} { + valid := true + for _, val := range attributes { + if !attributeValueValid(val) { + valid = false + break + } + } + if valid { + return attributes + } + // Note that the map is only copied if elements are to be removed to + // improve performance. + validAttributes := make(map[string]interface{}, len(attributes)) + for key, val := range attributes { + if attributeValueValid(val) { + validAttributes[key] = val + } else if nil != errorLogger { + errorLogger(map[string]interface{}{ + "err": fmt.Sprintf(`attribute "%s" has invalid type %T`, key, val), + }) + } + } + return validAttributes +} diff --git a/internal/nrsdk/telemetry/config.go b/internal/nrsdk/telemetry/config.go new file mode 100644 index 0000000..2a4b019 --- /dev/null +++ b/internal/nrsdk/telemetry/config.go @@ -0,0 +1,139 @@ +package telemetry + +import ( + "encoding/json" + "io" + "log" + "net/http" + "time" +) + +// Config customizes the behavior of a Harvester. +type Config struct { + // Client is the http.Client used for making requests. + Client *http.Client + // HarvestTimeout is the total amount of time including retries that the + // Harvester may use trying to harvest data. By default, HarvestTimeout + // is set to 15 seconds. + HarvestTimeout time.Duration + // RetryBackoff is the amount of time to wait between attempts to send a + // payload. By default, RetryBackoff is set to 3 seconds. + RetryBackoff time.Duration + // APIKey is required. + APIKey string + // CommonAttributes are the attributes to be applied to all metrics that + // use this Config. They are not applied to spans. + CommonAttributes map[string]interface{} + // HarvestPeriod controls how frequently data will be sent to New Relic. + // If HarvestPeriod is zero then NewHarvester will not spawn a goroutine + // to send data and it is incumbent on the consumer to call + // Harvester.HarvestNow when data should be sent. By default, HarvestPeriod + // is set to 5 seconds. + HarvestPeriod time.Duration + // ErrorLogger receives errors that occur in this sdk. + ErrorLogger func(map[string]interface{}) + // DebugLogger receives structured debug log messages. + DebugLogger func(map[string]interface{}) + // AuditLogger receives structured log messages that include the + // uncompressed data sent to New Relic. Use this to log all data sent. + AuditLogger func(map[string]interface{}) + // MetricsURLOverride overrides the metrics endpoint if not not empty. + MetricsURLOverride string + // SpansURLOverride overrides the spans endpoint if not not empty. + SpansURLOverride string + // BeforeHarvestFunc is a callback function that will be called before a + // harvest occurs. + BeforeHarvestFunc func(*Harvester) +} + +// ConfigAPIKey sets the Config's APIKey which is required. +func ConfigAPIKey(key string) func(*Config) { + return func(cfg *Config) { + cfg.APIKey = key + } +} + +// ConfigCommonAttributes adds the given attributes to the Config's +// CommonAttributes. +func ConfigCommonAttributes(attributes map[string]interface{}) func(*Config) { + return func(cfg *Config) { + cfg.CommonAttributes = attributes + } +} + +// ConfigHarvestPeriod sets the Config's HarvestPeriod field which controls the +// rate data is reported to New Relic. If it is set to zero then the Harvester +// will never report data unless HarvestNow is called. +func ConfigHarvestPeriod(period time.Duration) func(*Config) { + return func(cfg *Config) { + cfg.HarvestPeriod = period + } +} + +func newBasicLogger(w io.Writer) func(map[string]interface{}) { + flags := log.Ldate | log.Ltime | log.Lmicroseconds + lg := log.New(w, "", flags) + return func(fields map[string]interface{}) { + if js, err := json.Marshal(fields); nil != err { + lg.Println(err.Error()) + } else { + lg.Println(string(js)) + } + } +} + +// ConfigBasicErrorLogger sets the error logger to a simple logger that logs +// to the writer provided. +func ConfigBasicErrorLogger(w io.Writer) func(*Config) { + return func(cfg *Config) { + cfg.ErrorLogger = newBasicLogger(w) + } +} + +// ConfigBasicDebugLogger sets the debug logger to a simple logger that logs +// to the writer provided. +func ConfigBasicDebugLogger(w io.Writer) func(*Config) { + return func(cfg *Config) { + cfg.DebugLogger = newBasicLogger(w) + } +} + +// ConfigBasicAuditLogger sets the audit logger to a simple logger that logs +// to the writer provided. +func ConfigBasicAuditLogger(w io.Writer) func(*Config) { + return func(cfg *Config) { + cfg.AuditLogger = newBasicLogger(w) + } +} + +// configTesting is the config function to be used when testing. It sets the +// APIKey but disables the harvest goroutine. +func configTesting(cfg *Config) { + cfg.APIKey = "api-key" + cfg.HarvestPeriod = 0 +} + +func (cfg *Config) logError(fields map[string]interface{}) { + if nil == cfg.ErrorLogger { + return + } + cfg.ErrorLogger(fields) +} + +func (cfg *Config) logDebug(fields map[string]interface{}) { + if nil == cfg.DebugLogger { + return + } + cfg.DebugLogger(fields) +} + +func (cfg *Config) auditLogEnabled() bool { + return cfg.AuditLogger != nil +} + +func (cfg *Config) logAudit(fields map[string]interface{}) { + if nil == cfg.AuditLogger { + return + } + cfg.AuditLogger(fields) +} diff --git a/internal/nrsdk/telemetry/config_test.go b/internal/nrsdk/telemetry/config_test.go new file mode 100644 index 0000000..9d9802b --- /dev/null +++ b/internal/nrsdk/telemetry/config_test.go @@ -0,0 +1,75 @@ +package telemetry + +import ( + "bytes" + "strings" + "testing" +) + +func TestConfigAPIKey(t *testing.T) { + apikey := "apikey" + h := NewHarvester(ConfigAPIKey(apikey)) + if h.config.APIKey != apikey { + t.Error("config func does not set APIKey correctly") + } +} + +func TestConfigHarvestPeriod(t *testing.T) { + h := NewHarvester(ConfigHarvestPeriod(0)) + if 0 != h.config.HarvestPeriod { + t.Error("config func does not set harvest period correctly") + } +} + +func TestConfigBasicErrorLogger(t *testing.T) { + buf := new(bytes.Buffer) + h := NewHarvester(ConfigBasicErrorLogger(buf)) + + buf.Reset() + h.config.logError(map[string]interface{}{"zip": "zap"}) + if log := buf.String(); !strings.Contains(log, "{\"zip\":\"zap\"}") { + t.Error("message not logged correctly", log) + } + + buf.Reset() + h.config.logError(map[string]interface{}{"zip": func() {}}) + if log := buf.String(); !strings.Contains(log, "json: unsupported type: func()") { + t.Error("message not logged correctly", log) + } +} + +func TestConfigBasicDebugLogger(t *testing.T) { + buf := new(bytes.Buffer) + h := NewHarvester(ConfigBasicDebugLogger(buf)) + + buf.Reset() + h.config.logDebug(map[string]interface{}{"zip": "zap"}) + if log := buf.String(); !strings.Contains(log, "{\"zip\":\"zap\"}") { + t.Error("message not logged correctly", log) + } + + buf.Reset() + h.config.logDebug(map[string]interface{}{"zip": func() {}}) + if log := buf.String(); !strings.Contains(log, "json: unsupported type: func()") { + t.Error("message not logged correctly", log) + } +} + +func TestConfigAuditLogger(t *testing.T) { + h := NewHarvester(configTesting) + if enabled := h.config.auditLogEnabled(); enabled { + t.Error("audit logging should not be enabled", enabled) + } + // This should not panic. + h.config.logAudit(map[string]interface{}{"zip": "zap"}) + + buf := new(bytes.Buffer) + h = NewHarvester(configTesting, ConfigBasicAuditLogger(buf)) + if enabled := h.config.auditLogEnabled(); !enabled { + t.Error("audit logging should be enabled", enabled) + } + h.config.logAudit(map[string]interface{}{"zip": "zap"}) + if lg := buf.String(); !strings.Contains(lg, `{"zip":"zap"}`) { + t.Error("audit message not logged correctly", lg) + } +} diff --git a/internal/nrsdk/telemetry/doc.go b/internal/nrsdk/telemetry/doc.go new file mode 100644 index 0000000..5d815ce --- /dev/null +++ b/internal/nrsdk/telemetry/doc.go @@ -0,0 +1,10 @@ +// Package telemetry is the recommended way of interacting with the New +// Relic Metrics and Spans HTTP APIs. +// +// This package provides basic interaction with the New Relic Metrics and Spans +// HTTP APIs, automatic batch harvesting on a given schedule, and handling of +// errors from the API response. +// +// To aggregate metrics between harvests, use the instrumentation package. +// +package telemetry diff --git a/internal/nrsdk/telemetry/example_test.go b/internal/nrsdk/telemetry/example_test.go new file mode 100644 index 0000000..6f8190d --- /dev/null +++ b/internal/nrsdk/telemetry/example_test.go @@ -0,0 +1,72 @@ +package telemetry + +import ( + "encoding/json" + "os" + "time" +) + +func Example() { + h := NewHarvester( + ConfigAPIKey(os.Getenv("NEW_RELIC_API_KEY")), + ConfigCommonAttributes(map[string]interface{}{ + "app.name": "myApplication", + }), + ConfigBasicErrorLogger(os.Stderr), + ConfigBasicDebugLogger(os.Stdout), + ) + + start := time.Now() + duration := time.Second + + // raw metric + h.RecordMetric(Gauge{ + Timestamp: time.Now(), + Value: 1, + Name: "myMetric", + Attributes: map[string]interface{}{ + "color": "purple", + }, + }) + + // span + h.RecordSpan(Span{ + ID: "12345", + TraceID: "67890", + Name: "purple-span", + Timestamp: start, + Duration: duration, + ServiceName: "ExampleApplication", + Attributes: map[string]interface{}{ + "color": "purple", + }, + }) +} + +func ExampleNewHarvester() { + h := NewHarvester( + ConfigAPIKey(os.Getenv("NEW_RELIC_API_KEY")), + ) + h.RecordMetric(Gauge{ + Timestamp: time.Now(), + Value: 1, + Name: "myMetric", + Attributes: map[string]interface{}{ + "color": "purple", + }, + }) +} + +func ExampleHarvester_RecordMetric() { + h := NewHarvester( + ConfigAPIKey(os.Getenv("NEW_RELIC_API_KEY")), + ) + start := time.Now() + h.RecordMetric(Count{ + Name: "myCount", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 123, + Timestamp: start, + Interval: 5 * time.Second, + }) +} diff --git a/internal/nrsdk/telemetry/harvester.go b/internal/nrsdk/telemetry/harvester.go new file mode 100644 index 0000000..6987d4b --- /dev/null +++ b/internal/nrsdk/telemetry/harvester.go @@ -0,0 +1,379 @@ +package telemetry + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "sync" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" +) + +// Harvester aggregates and reports metrics and spans. +type Harvester struct { + // These fields are not modified after Harvester creation. They may be + // safely accessed without locking. + config Config + commonAttributesJSON json.RawMessage + + // lock protects the mutable fields below. + lock sync.Mutex + lastHarvest time.Time + rawMetrics []Metric + spans []Span +} + +const ( + // NOTE: These constant values are used in Config field doc comments. + defaultHarvestPeriod = 5 * time.Second + defaultRetryBackoff = 3 * time.Second + defaultHarvestTimeout = 15 * time.Second +) + +// NewHarvester creates a new harvester. +func NewHarvester(options ...func(*Config)) *Harvester { + cfg := Config{ + Client: &http.Client{}, + HarvestPeriod: defaultHarvestPeriod, + HarvestTimeout: defaultHarvestTimeout, + RetryBackoff: defaultRetryBackoff, + } + for _, opt := range options { + opt(&cfg) + } + + h := &Harvester{ + config: cfg, + lastHarvest: time.Now(), + } + + // Marshal the common attributes to JSON here to avoid doing it on every + // harvest. This also has the benefit that it avoids race conditions if + // the consumer modifies the CommonAttributes map after calling + // NewHarvester. + if nil != h.config.CommonAttributes { + attrs := vetAttributes(h.config.CommonAttributes, h.config.logError) + attributesJSON, err := json.Marshal(attrs) + if err != nil { + h.config.logError(map[string]interface{}{ + "err": err.Error(), + "message": "error marshaling common attributes", + }) + } else { + h.commonAttributesJSON = attributesJSON + } + h.config.CommonAttributes = nil + } + + spawnHarvester := h.needsHarvestThread() + + h.config.logDebug(map[string]interface{}{ + "event": "harvester created", + "api-key": h.config.APIKey, + "harvest-period-seconds": h.config.HarvestPeriod.Seconds(), + "spawn-harvest-goroutine": spawnHarvester, + "metrics-url-override": h.config.MetricsURLOverride, + "spans-url-override": h.config.SpansURLOverride, + "collect-metrics": h.collectMetrics(), + "collect-spans": h.collectSpans(), + "version": internal.Version, + }) + + if spawnHarvester { + go h.harvest() + } + + return h +} + +func (h *Harvester) needsHarvestThread() bool { + if 0 == h.config.HarvestPeriod { + return false + } + if !h.collectMetrics() && !h.collectSpans() { + return false + } + return true +} + +func (h *Harvester) collectMetrics() bool { + if nil == h { + return false + } + if "" == h.config.APIKey { + return false + } + return true +} + +func (h *Harvester) collectSpans() bool { + if nil == h { + return false + } + if "" == h.config.APIKey { + return false + } + return true +} + +var ( + errSpanIDUnset = errors.New("span id must be set") + errTraceIDUnset = errors.New("trace id must be set") + errSpansDisabled = errors.New("spans are not enabled: APIKey unset") +) + +// RecordSpan records the given span. +func (h *Harvester) RecordSpan(s Span) error { + if nil == h { + return nil + } + if "" == h.config.APIKey { + return errSpansDisabled + } + if "" == s.TraceID { + return errTraceIDUnset + } + if "" == s.ID { + return errSpanIDUnset + } + if s.Timestamp.IsZero() { + s.Timestamp = time.Now() + } + + // TODO: Remove Attributes which collide with the recommended + // attributes. + + h.lock.Lock() + defer h.lock.Unlock() + + h.spans = append(h.spans, s) + return nil +} + +// RecordMetric adds a fully formed metric. This metric is not aggregated with +// any other metrics and is never dropped. The Timestamp field must be +// specified on Gauge metrics. The Timestamp/Interval fields on Count and +// Summary are optional and will be assumed to be the harvester batch times if +// unset. +func (h *Harvester) RecordMetric(m Metric) { + if !h.collectMetrics() { + return + } + h.lock.Lock() + defer h.lock.Unlock() + + h.rawMetrics = append(h.rawMetrics, m) +} + +type response struct { + statusCode int + body []byte + err error + retryAfter string +} + +func (r response) needsRetry(cfg *Config) (bool, time.Duration) { + switch r.statusCode { + case 202, 200: + // success + return false, 0 + case 400, 403, 404, 405, 411, 413: + // errors that should not retry + return false, 0 + case 429: + // special retry backoff time + if "" != r.retryAfter { + // Honor Retry-After header value in seconds + if d, err := time.ParseDuration(r.retryAfter + "s"); nil == err { + if d > cfg.RetryBackoff { + return true, d + } + } + } + return true, cfg.RetryBackoff + default: + // all other errors should retry + return true, cfg.RetryBackoff + } +} + +func postData(req *http.Request, client *http.Client) response { + resp, err := client.Do(req) + if nil != err { + return response{err: fmt.Errorf("error posting data: %v", err)} + } + defer resp.Body.Close() + + r := response{ + statusCode: resp.StatusCode, + retryAfter: resp.Header.Get("Retry-After"), + } + + // On success, metrics ingest returns 202, span ingest returns 200. + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusAccepted { + r.body, _ = ioutil.ReadAll(resp.Body) + } else { + r.err = fmt.Errorf("unexpected post response code: %d: %s", + resp.StatusCode, http.StatusText(resp.StatusCode)) + } + + return r +} + +func (h *Harvester) swapOutMetrics(now time.Time) []request { + if !h.collectMetrics() { + return nil + } + + h.lock.Lock() + lastHarvest := h.lastHarvest + h.lastHarvest = now + rawMetrics := h.rawMetrics + h.rawMetrics = nil + h.lock.Unlock() + + if 0 == len(rawMetrics) { + return nil + } + + batch := &metricBatch{ + Timestamp: lastHarvest, + Interval: now.Sub(lastHarvest), + AttributesJSON: h.commonAttributesJSON, + Metrics: rawMetrics, + } + reqs, err := batch.NewRequests(h.config.APIKey, h.config.MetricsURLOverride) + if nil != err { + h.config.logError(map[string]interface{}{ + "err": err.Error(), + "message": "error creating requests for metrics", + }) + return nil + } + return reqs +} + +func (h *Harvester) swapOutSpans() []request { + if !h.collectSpans() { + return nil + } + + h.lock.Lock() + sps := h.spans + h.spans = nil + h.lock.Unlock() + + if nil == sps { + return nil + } + batch := &spanBatch{ + AttributesJSON: h.commonAttributesJSON, + Spans: sps, + } + reqs, err := batch.NewRequests(h.config.APIKey, h.config.SpansURLOverride) + if nil != err { + h.config.logError(map[string]interface{}{ + "err": err.Error(), + "message": "error creating requests for spans", + }) + return nil + } + return reqs +} + +func harvestRequest(req request, cfg *Config) { + for { + cfg.logDebug(map[string]interface{}{ + "event": "data post", + "url": req.Request.URL.String(), + "body-length": req.compressedBodyLength, + }) + // Check if the audit log is enabled to prevent unnecessarily + // copying UncompressedBody. + if cfg.auditLogEnabled() { + cfg.logAudit(map[string]interface{}{ + "event": "uncompressed request body", + "url": req.Request.URL.String(), + "data": jsonString(req.UncompressedBody), + }) + } + + resp := postData(req.Request, cfg.Client) + + if nil != resp.err { + cfg.logError(map[string]interface{}{ + "err": resp.err.Error(), + }) + } else { + cfg.logDebug(map[string]interface{}{ + "event": "data post response", + "status": resp.statusCode, + "body": jsonOrString(resp.body), + }) + } + retry, backoff := resp.needsRetry(cfg) + if !retry { + return + } + + tmr := time.NewTimer(backoff) + select { + case <-tmr.C: + continue + case <-req.Request.Context().Done(): + tmr.Stop() + return + } + } +} + +// HarvestNow sends metric and span data to New Relic. This method blocks until +// all data has been sent successfully or the Config.HarvestTimeout timeout has +// elapsed. This method can be used with a zero Config.HarvestPeriod value to +// control exactly when data is sent to New Relic servers. +func (h *Harvester) HarvestNow(ct context.Context) { + if nil == h { + return + } + + ctx, cancel := context.WithTimeout(ct, h.config.HarvestTimeout) + defer cancel() + + if nil != h.config.BeforeHarvestFunc { + h.config.BeforeHarvestFunc(h) + } + + var reqs []request + reqs = append(reqs, h.swapOutMetrics(time.Now())...) + reqs = append(reqs, h.swapOutSpans()...) + + for _, req := range reqs { + req.Request = req.Request.WithContext(ctx) + harvestRequest(req, &h.config) + if err := ctx.Err(); err != nil { + // NOTE: It is possible that the context was + // cancelled/timedout right after the request + // successfully finished. In that case, we will + // erroneously log a message. I (will) don't think + // that's worth trying to engineer around. + h.config.logError(map[string]interface{}{ + "event": "harvest cancelled or timed out", + "message": "dropping data", + "context-error": err.Error(), + }) + return + } + } +} + +// harvest concurrently harvests telemetry data +func (h *Harvester) harvest() { + ticker := time.NewTicker(h.config.HarvestPeriod) + for range ticker.C { + go h.HarvestNow(context.Background()) + } +} diff --git a/internal/nrsdk/telemetry/harvester_test.go b/internal/nrsdk/telemetry/harvester_test.go new file mode 100644 index 0000000..9f9d280 --- /dev/null +++ b/internal/nrsdk/telemetry/harvester_test.go @@ -0,0 +1,615 @@ +package telemetry + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "sort" + "strings" + "testing" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" +) + +// compactJSONString removes the whitespace from a JSON string. This function +// will panic if the string provided is not valid JSON. +func compactJSONString(js string) string { + buf := new(bytes.Buffer) + if err := json.Compact(buf, []byte(js)); err != nil { + panic(fmt.Errorf("unable to compact JSON: %v", err)) + } + return buf.String() +} + +func TestNilHarvestNow(t *testing.T) { + var h *Harvester + h.HarvestNow(context.Background()) +} + +func TestNilHarvesterRecordSpan(t *testing.T) { + var h *Harvester + h.RecordSpan(Span{ + ID: "id", + TraceID: "traceId", + Name: "myspan", + ParentID: "parentId", + Timestamp: time.Now(), + Duration: time.Second, + ServiceName: "span", + Attributes: map[string]interface{}{ + "attr": 1, + }, + }) +} + +func TestHarvestErrorLogger(t *testing.T) { + err := map[string]interface{}{} + + harvestMissingErrorLogger := NewHarvester() + harvestMissingErrorLogger.config.logError(err) + + var savedErrors []map[string]interface{} + h := NewHarvester(func(cfg *Config) { + cfg.ErrorLogger = func(e map[string]interface{}) { + savedErrors = append(savedErrors, e) + } + }) + h.config.logError(err) + if len(savedErrors) != 1 { + t.Error("incorrect errors found", savedErrors) + } +} + +func TestHarvestDebugLogger(t *testing.T) { + fields := map[string]interface{}{ + "something": "happened", + } + + emptyHarvest := NewHarvester() + emptyHarvest.config.logDebug(fields) + + var savedFields map[string]interface{} + h := NewHarvester(func(cfg *Config) { + cfg.DebugLogger = func(f map[string]interface{}) { + savedFields = f + } + }) + h.config.logDebug(fields) + if !reflect.DeepEqual(fields, savedFields) { + t.Error(fields, savedFields) + } +} + +func TestVetCommonAttributes(t *testing.T) { + attributes := map[string]interface{}{ + "bool": true, + "bad": struct{}{}, + "int": 123, + "remove-me": t, + "nil-is-invalid": nil, + } + var savedErrors []map[string]interface{} + NewHarvester( + ConfigCommonAttributes(attributes), + func(cfg *Config) { + cfg.ErrorLogger = func(e map[string]interface{}) { + savedErrors = append(savedErrors, e) + } + }, + ) + if len(savedErrors) != 3 { + t.Fatal(savedErrors) + } +} + +func TestNeedsHarvestThread(t *testing.T) { + testcases := []struct { + cfgfn func(cfg *Config) + needsHarvestThread bool + }{ + { + cfgfn: func(cfg *Config) {}, + needsHarvestThread: false, + }, + { + cfgfn: func(cfg *Config) { + cfg.HarvestPeriod = 5 * time.Second + cfg.APIKey = "APIKey" + }, + needsHarvestThread: true, + }, + { + cfgfn: func(cfg *Config) { + cfg.HarvestPeriod = 0 + cfg.APIKey = "APIKey" + }, + needsHarvestThread: false, + }, + { + cfgfn: func(cfg *Config) { + cfg.HarvestPeriod = 5 * time.Second + cfg.APIKey = "" + }, + needsHarvestThread: false, + }, + { + cfgfn: func(cfg *Config) { + cfg.HarvestPeriod = 0 + cfg.APIKey = "" + }, + needsHarvestThread: false, + }, + } + for idx, tc := range testcases { + h := NewHarvester(tc.cfgfn) + got := h.needsHarvestThread() + if got != tc.needsHarvestThread { + t.Error(idx, got, tc.needsHarvestThread) + } + } +} + +func TestHarvestCancelled(t *testing.T) { + var errs int + var posts int + rt := roundTripperFunc(func(r *http.Request) (*http.Response, error) { + // Test that the context with the deadline is added to the + // harvest request. + <-r.Context().Done() + posts++ + return emptyResponse(500), nil + }) + h := NewHarvester(func(cfg *Config) { + cfg.ErrorLogger = func(e map[string]interface{}) { + errs++ + } + cfg.HarvestPeriod = 0 + cfg.Client.Transport = rt + cfg.APIKey = "key" + cfg.RetryBackoff = time.Hour + }) + h.RecordSpan(Span{TraceID: "id", ID: "id"}) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + h.HarvestNow(ctx) + + if posts != 1 { + t.Error("incorrect number of tries tried", posts) + } + if errs != 2 { + t.Error("incorrect number of errors logged", errs) + } +} + +func TestNewRequestHeaders(t *testing.T) { + h := NewHarvester(configTesting) + h.RecordSpan(Span{TraceID: "id", ID: "id"}) + h.RecordMetric(Gauge{}) + + reqs := h.swapOutSpans() + if len(reqs) != 1 { + t.Fatal(reqs) + } + req := reqs[0] + if h := req.Request.Header.Get("Content-Encoding"); "gzip" != h { + t.Error("incorrect Content-Encoding header", req.Request.Header) + } + if h := req.Request.Header.Get("User-Agent"); "" == h { + t.Error("User-Agent header not found", req.Request.Header) + } + + reqs = h.swapOutMetrics(time.Now()) + if len(reqs) != 1 { + t.Fatal(reqs) + } + req = reqs[0] + if h := req.Request.Header.Get("Content-Type"); "application/json" != h { + t.Error("incorrect Content-Type", h) + } + if h := req.Request.Header.Get("Api-Key"); "api-key" != h { + t.Error("incorrect Api-Key", h) + } + if h := req.Request.Header.Get("Content-Encoding"); "gzip" != h { + t.Error("incorrect Content-Encoding header", h) + } + if h := req.Request.Header.Get("User-Agent"); "" == h { + t.Error("User-Agent header not found", h) + } +} + +type roundTripperFunc func(*http.Request) (*http.Response, error) + +func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return fn(req) +} + +// optional interface required for go1.4 and go1.5 +func (fn roundTripperFunc) CancelRequest(*http.Request) {} + +func emptyResponse(status int) *http.Response { + return &http.Response{ + StatusCode: status, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + } +} + +func uncompressBody(req *http.Request) (string, error) { + body, err := ioutil.ReadAll(req.Body) + defer req.Body.Close() + + if err != nil { + return "", fmt.Errorf("unable to read body: %v", err) + } + uncompressed, err := internal.Uncompress(body) + if err != nil { + return "", fmt.Errorf("unable to uncompress body: %v", err) + } + return string(uncompressed), nil +} + +// sortedMetricsHelper is used to sort metrics for JSON comparison. +type sortedMetricsHelper []json.RawMessage + +func (h sortedMetricsHelper) Len() int { + return len(h) +} +func (h sortedMetricsHelper) Less(i, j int) bool { + return string(h[i]) < string(h[j]) +} +func (h sortedMetricsHelper) Swap(i, j int) { + h[i], h[j] = h[j], h[i] +} + +func testHarvesterMetrics(t testing.TB, h *Harvester, expect string) { + reqs := h.swapOutMetrics(time.Now()) + if len(reqs) != 1 { + t.Fatal(reqs) + } + js := reqs[0].UncompressedBody + var helper []struct { + Metrics sortedMetricsHelper `json:"metrics"` + } + if err := json.Unmarshal(js, &helper); err != nil { + t.Fatal("unable to unmarshal metrics for sorting", err) + return + } + sort.Sort(helper[0].Metrics) + js, err := json.Marshal(helper[0].Metrics) + if nil != err { + t.Fatal("unable to marshal metrics", err) + return + } + actual := string(js) + + if th, ok := t.(interface{ Helper() }); ok { + th.Helper() + } + compactExpect := compactJSONString(expect) + if compactExpect != actual { + t.Errorf("\nexpect=%s\nactual=%s\n", compactExpect, actual) + } +} + +func TestRecordMetric(t *testing.T) { + start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + h := NewHarvester(configTesting) + h.RecordMetric(Count{ + Name: "myCount", + AttributesJSON: json.RawMessage(`{"zip":"zap"}`), + Value: 123, + Timestamp: start, + Interval: 5 * time.Second, + }) + h.RecordMetric(Gauge{ + Name: "myGauge", + Attributes: map[string]interface{}{"zippity": "zappity"}, + Value: 246, + Timestamp: start, + }) + h.RecordMetric(Summary{ + Name: "mySummary", + Attributes: map[string]interface{}{"zup": "zop"}, + Count: 3, + Sum: 15, + Min: 4, + Max: 6, + Timestamp: start, + Interval: 5 * time.Second, + }) + expect := `[ + {"name":"myCount","type":"count","value":123,"timestamp":1417136460000,"interval.ms":5000,"attributes":{"zip":"zap"}}, + {"name":"myGauge","type":"gauge","value":246,"timestamp":1417136460000,"attributes":{"zippity":"zappity"}}, + {"name":"mySummary","type":"summary","value":{"sum":15,"count":3,"min":4,"max":6},"timestamp":1417136460000,"interval.ms":5000,"attributes":{"zup":"zop"}} + ]` + testHarvesterMetrics(t, h, expect) +} + +func TestReturnCodes(t *testing.T) { + // tests which return codes should retry and which should not + testcases := []struct { + returnCode int + shouldRetry bool + }{ + {200, false}, + {202, false}, + {400, false}, + {403, false}, + {404, false}, + {405, false}, + {411, false}, + {413, false}, + {429, true}, + {500, true}, + {503, true}, + } + + var posts int + sp := Span{TraceID: "id", ID: "id", Name: "span1", Timestamp: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC)} + + rtFunc := func(code int) roundTripperFunc { + return roundTripperFunc(func(req *http.Request) (*http.Response, error) { + posts++ + if posts > 1 { + return emptyResponse(202), nil + } + return emptyResponse(code), nil + }) + } + + for _, test := range testcases { + posts = 0 + h := NewHarvester(configTesting, func(cfg *Config) { + cfg.Client.Transport = rtFunc(test.returnCode) + cfg.RetryBackoff = 0 + }) + h.RecordSpan(sp) + h.HarvestNow(context.Background()) + if (test.shouldRetry && 2 != posts) || (!test.shouldRetry && 1 != posts) { + t.Error("incorrect number of posts", posts) + } + } +} + +func Test429RetryAfterUsesConfig(t *testing.T) { + // Test when resp code is 429, retry backoff uses value from config if: + // * Retry-After header not set + // * Retry-After header not parsable + // * Retry-After header delay is less than config retry backoff + var posts int + var start time.Time + tm := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + span := Span{TraceID: "id", ID: "id", Name: "span1", Timestamp: tm} + + roundTripper := func(retryHeader string) roundTripperFunc { + return roundTripperFunc(func(req *http.Request) (*http.Response, error) { + posts++ + if posts > 1 { + if since := time.Since(start); since > time.Second { + t.Errorf("incorrect retry backoff used, since=%v", since) + } + return emptyResponse(200), nil + } + start = time.Now() + resp := emptyResponse(429) + resp.Header = http.Header{} + resp.Header.Add("Retry-After", retryHeader) + return resp, nil + }) + } + + h := NewHarvester(func(cfg *Config) { + cfg.Client.Transport = roundTripper("") + cfg.APIKey = "key" + cfg.RetryBackoff = 0 + }) + h.RecordSpan(span) + h.HarvestNow(context.Background()) + if posts != 2 { + t.Error("incorrect number of posts", posts) + } + + posts = 0 + h = NewHarvester(func(cfg *Config) { + cfg.Client.Transport = roundTripper("hello world!") + cfg.APIKey = "key" + cfg.RetryBackoff = 0 + }) + h.RecordSpan(span) + h.HarvestNow(context.Background()) + if posts != 2 { + t.Error("incorrect number of posts", posts) + } + + posts = 0 + h = NewHarvester(func(cfg *Config) { + cfg.Client.Transport = roundTripper("0") + cfg.APIKey = "key" + cfg.RetryBackoff = 2 + }) + h.RecordSpan(span) + h.HarvestNow(context.Background()) + if posts != 2 { + t.Error("incorrect number of posts", posts) + } +} + +func TestResponseNeedsRetry(t *testing.T) { + testcases := []struct { + headerRetry string + respCode int + expectRetry bool + expectBackoff time.Duration + }{ + { + headerRetry: "2", + respCode: 202, + expectRetry: false, + expectBackoff: 0, + }, + { + headerRetry: "2", + respCode: 200, + expectRetry: false, + expectBackoff: 0, + }, + { + headerRetry: "2", + respCode: 413, + expectRetry: false, + expectBackoff: 0, + }, + { + headerRetry: "", + respCode: 429, + expectRetry: true, + expectBackoff: time.Second, + }, + { + headerRetry: "hello", + respCode: 429, + expectRetry: true, + expectBackoff: time.Second, + }, + { + headerRetry: "0.5", + respCode: 429, + expectRetry: true, + expectBackoff: time.Second, + }, + { + headerRetry: "2", + respCode: 429, + expectRetry: true, + expectBackoff: 2 * time.Second, + }, + } + + h := NewHarvester(configTesting, func(cfg *Config) { + cfg.RetryBackoff = time.Second + }) + for _, test := range testcases { + resp := response{ + statusCode: test.respCode, + retryAfter: test.headerRetry, + } + actualRetry, actualBackoff := resp.needsRetry(&h.config) + if actualRetry != test.expectRetry { + t.Errorf("incorrect retry value found, actualRetry=%t, expectRetry=%t", actualRetry, test.expectRetry) + } + if actualBackoff != test.expectBackoff { + t.Errorf("incorrect retry value found, actualBackoff=%v, expectBackoff=%v", actualBackoff, test.expectBackoff) + } + } +} + +func TestNoDataNoHarvest(t *testing.T) { + roundTripper := roundTripperFunc(func(req *http.Request) (*http.Response, error) { + t.Error("harvest should not have been run") + return emptyResponse(200), nil + }) + + h := NewHarvester(func(cfg *Config) { + cfg.HarvestPeriod = 0 + cfg.Client.Transport = roundTripper + cfg.APIKey = "APIKey" + cfg.RetryBackoff = 0 + }) + h.HarvestNow(context.Background()) +} + +func TestNewRequestErrorNoPost(t *testing.T) { + // Test that when newRequest returns an error, no post is made + roundTripper := roundTripperFunc(func(req *http.Request) (*http.Response, error) { + t.Error("no post should not have been run") + return emptyResponse(200), nil + }) + + h := NewHarvester(func(cfg *Config) { + cfg.HarvestPeriod = 0 + cfg.Client.Transport = roundTripper + cfg.APIKey = "APIKey" + cfg.RetryBackoff = 0 + cfg.MetricsURLOverride = "t h i s i s n o t a h o s t%" + }) + h.RecordMetric(Count{}) + h.HarvestNow(context.Background()) +} + +func TestRecordMetricNil(t *testing.T) { + var h *Harvester + h.RecordMetric(Count{}) +} + +func TestRecordMetricDisabled(t *testing.T) { + h := NewHarvester(func(cfg *Config) { + cfg.APIKey = "" + cfg.HarvestPeriod = 0 + }) + h.RecordMetric(Count{}) + if 0 != len(h.rawMetrics) { + t.Error(h.rawMetrics) + } +} + +func TestBeforeHarvestFunc(t *testing.T) { + var calls int + h := NewHarvester(configTesting, func(cfg *Config) { + cfg.BeforeHarvestFunc = func(h *Harvester) { + calls++ + } + }) + h.HarvestNow(context.Background()) + if 1 != calls { + t.Error("BeforeHarvestFunc not called") + } +} + +func TestRecordSpanZeroTimestamp(t *testing.T) { + h := NewHarvester(func(cfg *Config) { + cfg.HarvestPeriod = 0 + cfg.APIKey = "APIKey" + }) + if err := h.RecordSpan(Span{ + ID: "id", + TraceID: "traceid", + }); err != nil { + t.Fatal(err) + } + if s := h.spans[0]; s.Timestamp.IsZero() { + t.Fatal(s.Timestamp) + } +} + +func TestHarvestAuditLog(t *testing.T) { + roundTripper := roundTripperFunc(func(req *http.Request) (*http.Response, error) { + return emptyResponse(200), nil + }) + + var audit map[string]interface{} + + h := NewHarvester(func(cfg *Config) { + cfg.HarvestPeriod = 0 + cfg.APIKey = "APIKey" + cfg.Client.Transport = roundTripper + cfg.AuditLogger = func(fields map[string]interface{}) { + audit = fields + } + }) + h.RecordMetric(Count{}) + h.HarvestNow(context.Background()) + if u := audit["url"]; u != "https://metric-api.newrelic.com/metric/v1" { + t.Fatal(u) + } + // We can't test "data" against a fixed string because of the dynamic + // timestamp. + if d := audit["data"]; !strings.Contains(string(d.(jsonString)), `"metrics":[{"name":"","type":"count","value":0}]`) { + t.Fatal(d) + } +} diff --git a/internal/nrsdk/telemetry/metrics.go b/internal/nrsdk/telemetry/metrics.go new file mode 100644 index 0000000..b06dc8f --- /dev/null +++ b/internal/nrsdk/telemetry/metrics.go @@ -0,0 +1,320 @@ +package telemetry + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" +) + +// Count is the metric type that counts the number of times an event occurred. +// This counter should be reset every time the data is reported, meaning the +// value reported represents the difference in count over the reporting time +// window. +// +// Example possible uses: +// +// * the number of messages put on a topic +// * the number of HTTP requests +// * the number of errors thrown +// * the number of support tickets answered +// +type Count struct { + // Name is the name of this metric. + Name string + // Attributes is a map of attributes for this metric. + Attributes map[string]interface{} + // AttributesJSON is a json.RawMessage of attributes for this metric. It + // will only be sent if Attributes is nil. + AttributesJSON json.RawMessage + // Value is the value of this metric. + Value float64 + // Timestamp is the start time of this metric's interval. If Timestamp + // is unset then the Harvester's period start will be used. + Timestamp time.Time + // Interval is the length of time for this metric. If Interval is unset + // then the time between Harvester harvests will be used. + Interval time.Duration +} + +// Metric is implemented by Count, Gauge, and Summary. +type Metric interface { + writeJSON(buf *bytes.Buffer) +} + +func writeTimestampInterval(w *internal.JSONFieldsWriter, timestamp time.Time, interval time.Duration) { + if !timestamp.IsZero() { + w.IntField("timestamp", timestamp.UnixNano()/(1000*1000)) + } + if interval != 0 { + w.IntField("interval.ms", interval.Nanoseconds()/(1000*1000)) + } +} + +func (m Count) writeJSON(buf *bytes.Buffer) { + w := internal.JSONFieldsWriter{Buf: buf} + w.Buf.WriteByte('{') + w.StringField("name", m.Name) + w.StringField("type", "count") + w.FloatField("value", m.Value) + writeTimestampInterval(&w, m.Timestamp, m.Interval) + if nil != m.Attributes { + w.WriterField("attributes", internal.Attributes(m.Attributes)) + } else if nil != m.AttributesJSON { + w.RawField("attributes", m.AttributesJSON) + } + w.Buf.WriteByte('}') +} + +// Summary is the metric type used for reporting aggregated information about +// discrete events. It provides the count, average, sum, min and max values +// over time. All fields should be reset to 0 every reporting interval. +// +// Example possible uses: +// +// * the duration and count of spans +// * the duration and count of transactions +// * the time each message spent in a queue +// +type Summary struct { + // Name is the name of this metric. + Name string + // Attributes is a map of attributes for this metric. + Attributes map[string]interface{} + // AttributesJSON is a json.RawMessage of attributes for this metric. It + // will only be sent if Attributes is nil. + AttributesJSON json.RawMessage + // Count is the count of occurrences of this metric for this time period. + Count float64 + // Sum is the sum of all occurrences of this metric for this time period. + Sum float64 + // Min is the smallest value recorded of this metric for this time period. + Min float64 + // Max is the largest value recorded of this metric for this time period. + Max float64 + // Timestamp is the start time of this metric's interval. If Timestamp + // is unset then the Harvester's period start will be used. + Timestamp time.Time + // Interval is the length of time for this metric. If Interval is unset + // then the time between Harvester harvests will be used. + Interval time.Duration +} + +func (m Summary) writeJSON(buf *bytes.Buffer) { + w := internal.JSONFieldsWriter{Buf: buf} + buf.WriteByte('{') + + w.StringField("name", m.Name) + w.StringField("type", "summary") + + w.AddKey("value") + buf.WriteByte('{') + vw := internal.JSONFieldsWriter{Buf: buf} + vw.FloatField("sum", m.Sum) + vw.FloatField("count", m.Count) + vw.FloatField("min", m.Min) + vw.FloatField("max", m.Max) + buf.WriteByte('}') + + writeTimestampInterval(&w, m.Timestamp, m.Interval) + if nil != m.Attributes { + w.WriterField("attributes", internal.Attributes(m.Attributes)) + } else if nil != m.AttributesJSON { + w.RawField("attributes", m.AttributesJSON) + } + buf.WriteByte('}') +} + +// Gauge is the metric type that records a value that can increase or decrease. +// It generally represents the value for something at a particular moment in +// time. One typically records a Gauge value on a set interval. +// +// Example possible uses: +// +// * the temperature in a room +// * the amount of memory currently in use for a process +// * the bytes per second flowing into Kafka at this exact moment in time +// * the current speed of your car +// +type Gauge struct { + // Name is the name of this metric. + Name string + // Attributes is a map of attributes for this metric. + Attributes map[string]interface{} + // AttributesJSON is a json.RawMessage of attributes for this metric. It + // will only be sent if Attributes is nil. + AttributesJSON json.RawMessage + // Value is the value of this metric. + Value float64 + // Timestamp is the time at which this metric was gathered. If + // Timestamp is unset then the Harvester's period start will be used. + Timestamp time.Time +} + +func (m Gauge) writeJSON(buf *bytes.Buffer) { + w := internal.JSONFieldsWriter{Buf: buf} + buf.WriteByte('{') + w.StringField("name", m.Name) + w.StringField("type", "gauge") + w.FloatField("value", m.Value) + writeTimestampInterval(&w, m.Timestamp, 0) + if nil != m.Attributes { + w.WriterField("attributes", internal.Attributes(m.Attributes)) + } else if nil != m.AttributesJSON { + w.RawField("attributes", m.AttributesJSON) + } + buf.WriteByte('}') +} + +// metricBatch represents a single batch of metrics to report to New Relic. +// +// Timestamp/Interval are optional and can be used to represent the start and +// duration of the batch as a whole. Individual Count and Summary metrics may +// provide Timestamp/Interval fields which will take priority over the batch +// Timestamp/Interval. This is not the case for Gauge metrics which each require +// a Timestamp. +// +// Attributes are any attributes that should be applied to all metrics in this +// batch. Each metric type also accepts an Attributes field. +type metricBatch struct { + // Timestamp is the start time of all metrics in this metricBatch. This value + // can be overridden by setting Timestamp on any particular metric. + // Timestamp must be set here or on all metrics. + Timestamp time.Time + // Interval is the length of time for all metrics in this metricBatch. This + // value can be overriden by setting Interval on any particular Count or + // Summary metric. Interval must be set to a non-zero value here or on + // all Count and Summary metrics. + Interval time.Duration + // Attributes is a map of attributes to apply to all metrics in this metricBatch. + // They are included in addition to any attributes set on any particular + // metric. + Attributes map[string]interface{} + // AttributesJSON is a json.RawMessage of attributes to apply to all + // metrics in this metricBatch. It will only be sent if the Attributes field on + // this metricBatch is nil. These attributes are included in addition to any + // attributes on any particular metric. + AttributesJSON json.RawMessage + // Metrics is the slice of metrics to send with this metricBatch. + Metrics []Metric +} + +// AddMetric adds a Count, Gauge, or Summary metric to a metricBatch. +func (batch *metricBatch) AddMetric(metric Metric) { + batch.Metrics = append(batch.Metrics, metric) +} + +type metricsArray []Metric + +func (ma metricsArray) WriteJSON(buf *bytes.Buffer) { + buf.WriteByte('[') + for idx, m := range ma { + if idx > 0 { + buf.WriteByte(',') + } + m.writeJSON(buf) + } + buf.WriteByte(']') +} + +type commonAttributes metricBatch + +func (c commonAttributes) WriteJSON(buf *bytes.Buffer) { + buf.WriteByte('{') + w := internal.JSONFieldsWriter{Buf: buf} + writeTimestampInterval(&w, c.Timestamp, c.Interval) + if nil != c.Attributes { + w.WriterField("attributes", internal.Attributes(c.Attributes)) + } else if nil != c.AttributesJSON { + w.RawField("attributes", c.AttributesJSON) + } + buf.WriteByte('}') +} + +func (batch *metricBatch) writeJSON(buf *bytes.Buffer) { + buf.WriteByte('[') + buf.WriteByte('{') + w := internal.JSONFieldsWriter{Buf: buf} + w.WriterField("common", commonAttributes(*batch)) + w.WriterField("metrics", metricsArray(batch.Metrics)) + buf.WriteByte('}') + buf.WriteByte(']') +} + +// split will split the metricBatch into 2 equal parts, returning a slice of metricBatches. +// If the number of metrics in the original is 0 or 1 then nil is returned. +func (batch *metricBatch) split() []requestsBuilder { + if len(batch.Metrics) < 2 { + return nil + } + + half := len(batch.Metrics) / 2 + mb1 := *batch + mb1.Metrics = batch.Metrics[:half] + mb2 := *batch + mb2.Metrics = batch.Metrics[half:] + + return []requestsBuilder{ + requestsBuilder(&mb1), + requestsBuilder(&mb2), + } +} + +const ( + defaultMetricURL = "https://metric-api.newrelic.com/metric/v1" +) + +// NewRequests creates new Requests from the metricBatch. The requests can be +// sent with an http.Client. +// +// NewRequest returns the requests or an error if there was one. Each Request +// has an UncompressedBody field that is useful in debugging or testing. +// +// Possible response codes to be expected when sending the request to New +// Relic: +// +// 202 for success +// 403 for an auth failure +// 404 for a bad path +// 405 for anything but POST +// 411 if the Content-Length header is not included +// 413 for a payload that is too large +// 400 for a generally invalid request +// 429 Too Many Requests +// +func (batch *metricBatch) NewRequests(apiKey string, urlOverride string) ([]request, error) { + return newRequests(batch, apiKey, urlOverride, maxCompressedSizeBytes) +} + +func newRequest(defaultURL string, urlOverride string, apiKey string, uncompressed []byte) (request, error) { + u := defaultURL + if "" != urlOverride { + u = urlOverride + } + compressed, err := internal.Compress(uncompressed) + if nil != err { + return request{}, fmt.Errorf("error compressing data: %v", err) + } + req, err := http.NewRequest("POST", u, compressed) + if nil != err { + return request{}, fmt.Errorf("error creating request: %v", err) + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Api-Key", apiKey) + req.Header.Add("Content-Encoding", "gzip") + internal.AddUserAgentHeader(req.Header) + return request{ + Request: req, + UncompressedBody: uncompressed, + compressedBodyLength: compressed.Len(), + }, nil +} + +func (batch *metricBatch) newRequest(apiKey, urlOverride string) (request, error) { + buf := &bytes.Buffer{} + batch.writeJSON(buf) + return newRequest(defaultMetricURL, urlOverride, apiKey, buf.Bytes()) +} diff --git a/internal/nrsdk/telemetry/metrics_batch_test.go b/internal/nrsdk/telemetry/metrics_batch_test.go new file mode 100644 index 0000000..bb1c559 --- /dev/null +++ b/internal/nrsdk/telemetry/metrics_batch_test.go @@ -0,0 +1,398 @@ +package telemetry + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "testing" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" +) + +func TestMetrics(t *testing.T) { + metrics := &metricBatch{} + start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + metrics.AddMetric(Summary{ + Name: "mySummary", + Attributes: map[string]interface{}{ + "attribute": "string", + }, + Count: 3, + Sum: 15, + Min: 4, + Max: 6, + Timestamp: start, + Interval: 5 * time.Second, + }) + metrics.AddMetric(Gauge{ + Name: "myGauge", + Attributes: map[string]interface{}{ + "attribute": true, + }, + Value: 12.3, + Timestamp: start, + }) + metrics.AddMetric(Count{ + Name: "myCount", + Attributes: map[string]interface{}{ + "attribute": 123, + }, + Value: 100, + Timestamp: start, + Interval: 5 * time.Second, + }) + metrics.Attributes = map[string]interface{}{ + "zip": "zap", + } + + expect := compactJSONString(`[{ + "common":{ + "attributes":{"zip":"zap"} + }, + "metrics":[ + { + "name":"mySummary", + "type":"summary", + "value":{"sum":15,"count":3,"min":4,"max":6}, + "timestamp":1417136460000, + "interval.ms":5000, + "attributes":{"attribute":"string"} + }, + { + "name":"myGauge", + "type":"gauge", + "value":12.3, + "timestamp":1417136460000, + "attributes":{"attribute":true} + }, + { + "name":"myCount", + "type":"count", + "value":100, + "timestamp":1417136460000, + "interval.ms":5000, + "attributes":{"attribute":123} + } + ] + }]`) + + reqs, err := metrics.NewRequests("my-api-key", "") + if err != nil { + t.Error("error creating request", err) + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + req := reqs[0] + data := reqs[0].UncompressedBody + if string(data) != expect { + t.Error("metrics JSON mismatch", string(data), expect) + } + body, err := ioutil.ReadAll(req.Request.Body) + req.Request.Body.Close() + if err != nil { + t.Fatal("unable to read body", err) + } + if len(body) != req.compressedBodyLength { + t.Error("compressed body length mismatch", len(body), req.compressedBodyLength) + } + uncompressed, err := internal.Uncompress(body) + if err != nil { + t.Fatal("unable to uncompress body", err) + } + if string(uncompressed) != expect { + t.Error("metrics JSON mismatch", string(uncompressed), expect) + } +} + +func testBatchJSON(t testing.TB, batch *metricBatch, expect string) { + if th, ok := t.(interface{ Helper() }); ok { + th.Helper() + } + reqs, err := batch.NewRequests("apiKey", "") + if nil != err { + t.Fatal(err) + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + js := reqs[0].UncompressedBody + actual := string(js) + compactExpect := compactJSONString(expect) + if actual != compactExpect { + t.Errorf("\nexpect=%s\nactual=%s\n", compactExpect, actual) + } +} + +func TestSplit(t *testing.T) { + // test len 0 + batch := &metricBatch{} + split := batch.split() + if split != nil { + t.Error(split) + } + + // test len 1 + batch = &metricBatch{Metrics: []Metric{Count{}}} + split = batch.split() + if split != nil { + t.Error(split) + } + + // test len 2 + batch = &metricBatch{Metrics: []Metric{Count{Name: "c1"}, Count{Name: "c2"}}} + split = batch.split() + if len(split) != 2 { + t.Error("split into incorrect number of slices", len(split)) + } + testBatchJSON(t, split[0].(*metricBatch), `[{"common":{},"metrics":[{"name":"c1","type":"count","value":0}]}]`) + testBatchJSON(t, split[1].(*metricBatch), `[{"common":{},"metrics":[{"name":"c2","type":"count","value":0}]}]`) + + // test len 3 + batch = &metricBatch{Metrics: []Metric{Count{Name: "c1"}, Count{Name: "c2"}, Count{Name: "c3"}}} + split = batch.split() + if len(split) != 2 { + t.Error("split into incorrect number of slices", len(split)) + } + testBatchJSON(t, split[0].(*metricBatch), `[{"common":{},"metrics":[{"name":"c1","type":"count","value":0}]}]`) + testBatchJSON(t, split[1].(*metricBatch), `[{"common":{},"metrics":[{"name":"c2","type":"count","value":0},{"name":"c3","type":"count","value":0}]}]`) +} + +func BenchmarkMetricsJSON(b *testing.B) { + // This benchmark tests the overhead of turning metrics into JSON. + batch := &metricBatch{ + Attributes: map[string]interface{}{"zip": "zap"}, + } + numMetrics := 10 * 1000 + start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + + for i := 0; i < numMetrics/3; i++ { + batch.AddMetric(Summary{ + Name: "mySummary", + Attributes: map[string]interface{}{"attribute": "string"}, + Count: 3, + Sum: 15, + Min: 4, + Max: 6, + Timestamp: start, + Interval: 5 * time.Second, + }) + batch.AddMetric(Gauge{ + Name: "myGauge", + Attributes: map[string]interface{}{"attribute": true}, + Value: 12.3, + Timestamp: start, + }) + batch.AddMetric(Count{ + Name: "myCount", + Attributes: map[string]interface{}{"attribute": 123}, + Value: 100, + Timestamp: start, + Interval: 5 * time.Second, + }) + } + + b.ResetTimer() + b.ReportAllocs() + + estimate := len(batch.Metrics) * 256 + for i := 0; i < b.N; i++ { + buf := bytes.NewBuffer(make([]byte, 0, estimate)) + batch.writeJSON(buf) + bts := buf.Bytes() + if len(bts) == 0 { + b.Fatal(string(bts)) + } + } +} + +func TestMetricAttributesJSON(t *testing.T) { + tests := []struct { + key string + val interface{} + expect string + }{ + {"string", "string", `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"string":"string"}}]}]`}, + {"true", true, `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"true":true}}]}]`}, + {"false", false, `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"false":false}}]}]`}, + {"uint8", uint8(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"uint8":1}}]}]`}, + {"uint16", uint16(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"uint16":1}}]}]`}, + {"uint32", uint32(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"uint32":1}}]}]`}, + {"uint64", uint64(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"uint64":1}}]}]`}, + {"uint", uint(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"uint":1}}]}]`}, + {"uintptr", uintptr(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"uintptr":1}}]}]`}, + {"int8", int8(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"int8":1}}]}]`}, + {"int16", int16(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"int16":1}}]}]`}, + {"int32", int32(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"int32":1}}]}]`}, + {"int64", int64(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"int64":1}}]}]`}, + {"int", int(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"int":1}}]}]`}, + {"float32", float32(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"float32":1}}]}]`}, + {"float64", float64(1), `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"float64":1}}]}]`}, + {"default", func() {}, `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"default":"func()"}}]}]`}, + } + + for _, test := range tests { + batch := &metricBatch{} + batch.AddMetric(Count{ + Attributes: map[string]interface{}{ + test.key: test.val, + }, + }) + testBatchJSON(t, batch, test.expect) + } +} + +func TestCountAttributesJSON(t *testing.T) { + batch := &metricBatch{} + batch.AddMetric(Count{ + Attributes: map[string]interface{}{ + "zip": "zap", + }, + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + }) + testBatchJSON(t, batch, `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"zip":"zap"}}]}]`) + + batch = &metricBatch{} + batch.AddMetric(Count{ + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + }) + testBatchJSON(t, batch, `[{"common":{},"metrics":[{"name":"","type":"count","value":0,"attributes":{"zing":"zang"}}]}]`) +} + +func TestGaugeAttributesJSON(t *testing.T) { + start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + + batch := &metricBatch{} + batch.AddMetric(Gauge{ + Attributes: map[string]interface{}{ + "zip": "zap", + }, + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + Timestamp: start, + }) + testBatchJSON(t, batch, `[{"common":{},"metrics":[{"name":"","type":"gauge","value":0,"timestamp":1417136460000,"attributes":{"zip":"zap"}}]}]`) + + batch = &metricBatch{} + batch.AddMetric(Gauge{ + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + Timestamp: start, + }) + testBatchJSON(t, batch, `[{"common":{},"metrics":[{"name":"","type":"gauge","value":0,"timestamp":1417136460000,"attributes":{"zing":"zang"}}]}]`) +} + +func TestSummaryAttributesJSON(t *testing.T) { + batch := &metricBatch{} + batch.AddMetric(Summary{ + Attributes: map[string]interface{}{ + "zip": "zap", + }, + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + }) + testBatchJSON(t, batch, `[{"common":{},"metrics":[{"name":"","type":"summary","value":{"sum":0,"count":0,"min":0,"max":0},"attributes":{"zip":"zap"}}]}]`) + + batch = &metricBatch{} + batch.AddMetric(Summary{ + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + }) + testBatchJSON(t, batch, `[{"common":{},"metrics":[{"name":"","type":"summary","value":{"sum":0,"count":0,"min":0,"max":0},"attributes":{"zing":"zang"}}]}]`) +} + +func TestBatchAttributesJSON(t *testing.T) { + batch := &metricBatch{ + Attributes: map[string]interface{}{ + "zip": "zap", + }, + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + } + testBatchJSON(t, batch, `[{"common":{"attributes":{"zip":"zap"}},"metrics":[]}]`) + + batch = &metricBatch{ + AttributesJSON: json.RawMessage(`{"zing":"zang"}`), + } + testBatchJSON(t, batch, `[{"common":{"attributes":{"zing":"zang"}},"metrics":[]}]`) +} + +func TestBatchStartEndTimesJSON(t *testing.T) { + start := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + + batch := &metricBatch{} + testBatchJSON(t, batch, `[{"common":{},"metrics":[]}]`) + + batch = &metricBatch{ + Timestamp: start, + } + testBatchJSON(t, batch, `[{"common":{"timestamp":1417136460000},"metrics":[]}]`) + + batch = &metricBatch{ + Interval: 5 * time.Second, + } + testBatchJSON(t, batch, `[{"common":{"interval.ms":5000},"metrics":[]}]`) + + batch = &metricBatch{ + Timestamp: start, + Interval: 5 * time.Second, + } + testBatchJSON(t, batch, `[{"common":{"timestamp":1417136460000,"interval.ms":5000},"metrics":[]}]`) +} + +func TestCommonAttributes(t *testing.T) { + // Tests when the "common" key is included in the metrics payload + type testStruct struct { + start time.Time + interval time.Duration + attributes map[string]interface{} + attributesJSON json.RawMessage + expect string + } + sometime := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + testcases := []testStruct{ + {expect: `[{"common":{},"metrics":[]}]`}, + {start: sometime, expect: `[{"common":{"timestamp":1417136460000},"metrics":[]}]`}, + {interval: 5 * time.Second, expect: `[{"common":{"interval.ms":5000},"metrics":[]}]`}, + {start: sometime, interval: 5 * time.Second, + expect: `[{"common":{"timestamp":1417136460000,"interval.ms":5000},"metrics":[]}]`}, + {attributes: map[string]interface{}{"zip": "zap"}, + expect: `[{"common":{"attributes":{"zip":"zap"}},"metrics":[]}]`}, + {attributesJSON: json.RawMessage(`{"zip":"zap"}`), + expect: `[{"common":{"attributes":{"zip":"zap"}},"metrics":[]}]`}, + } + + for _, test := range testcases { + batch := &metricBatch{ + Timestamp: test.start, + Interval: test.interval, + Attributes: test.attributes, + AttributesJSON: test.attributesJSON, + } + testBatchJSON(t, batch, test.expect) + } +} + +func TestMetricRequestURL(t *testing.T) { + batch := &metricBatch{Metrics: []Metric{Count{}}} + // Default URL + reqs, err := batch.NewRequests("apiKey", "") + if err != nil { + t.Error(err) + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + req := reqs[0] + if req.Request.URL.String() != "https://metric-api.newrelic.com/metric/v1" { + t.Error(req.Request.URL.String()) + } + // Override URL + reqs, err = batch.NewRequests("apiKey", "https://override.host.com/metric/v1") + if err != nil { + t.Error(err) + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + req = reqs[0] + if req.Request.URL.String() != "https://override.host.com/metric/v1" { + t.Error(req.Request.URL.String()) + } +} diff --git a/internal/nrsdk/telemetry/metrics_test.go b/internal/nrsdk/telemetry/metrics_test.go new file mode 100644 index 0000000..a617f3f --- /dev/null +++ b/internal/nrsdk/telemetry/metrics_test.go @@ -0,0 +1,104 @@ +package telemetry + +import ( + "testing" + "time" +) + +func TestMetricPayload(t *testing.T) { + // Test that a metric payload with timestamp, duration, and common + // attributes correctly marshals into JSON. + now := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + h := NewHarvester(ConfigCommonAttributes(map[string]interface{}{"zop": "zup"}), configTesting) + // Use a single metric to avoid sorting. + h.RecordMetric(Gauge{ + Name: "metric", + Attributes: map[string]interface{}{"zip": "zap"}, + Timestamp: now, + Value: 1.0, + }) + h.lastHarvest = now + end := h.lastHarvest.Add(5 * time.Second) + reqs := h.swapOutMetrics(end) + if len(reqs) != 1 { + t.Fatal(reqs) + } + js := reqs[0].UncompressedBody + actual := string(js) + expect := `[{ + "common":{ + "timestamp":1417136460000, + "interval.ms":5000, + "attributes":{"zop":"zup"} + }, + "metrics":[ + {"name":"metric","type":"gauge","value":1,"timestamp":1417136460000,"attributes":{"zip":"zap"}} + ] + }]` + compactExpect := compactJSONString(expect) + if compactExpect != actual { + t.Errorf("\nexpect=%s\nactual=%s\n", compactExpect, actual) + } +} + +func TestVetAttributes(t *testing.T) { + testcases := []struct { + Input interface{} + Valid bool + }{ + // Valid attribute types. + {Input: "string value", Valid: true}, + {Input: true, Valid: true}, + {Input: uint8(0), Valid: true}, + {Input: uint16(0), Valid: true}, + {Input: uint32(0), Valid: true}, + {Input: uint64(0), Valid: true}, + {Input: int8(0), Valid: true}, + {Input: int16(0), Valid: true}, + {Input: int32(0), Valid: true}, + {Input: int64(0), Valid: true}, + {Input: float32(0), Valid: true}, + {Input: float64(0), Valid: true}, + {Input: uint(0), Valid: true}, + {Input: int(0), Valid: true}, + {Input: uintptr(0), Valid: true}, + // Invalid attribute types. + {Input: nil, Valid: false}, + {Input: struct{}{}, Valid: false}, + {Input: &struct{}{}, Valid: false}, + {Input: []int{1, 2, 3}, Valid: false}, + } + + for idx, tc := range testcases { + key := "input" + input := map[string]interface{}{ + key: tc.Input, + } + var errorLogged map[string]interface{} + output := vetAttributes(input, func(e map[string]interface{}) { + errorLogged = e + }) + // Test the the input map has not been modified. + if len(input) != 1 { + t.Error("input map modified", input) + } + if tc.Valid { + if len(output) != 1 { + t.Error(idx, tc.Input, output) + } + if _, ok := output[key]; !ok { + t.Error(idx, tc.Input, output) + } + if errorLogged != nil { + t.Error(idx, "unexpected error present") + } + } else { + if errorLogged == nil { + t.Error(idx, "expected error missing") + } + if len(output) != 0 { + t.Error(idx, tc.Input, output) + } + } + } +} diff --git a/internal/nrsdk/telemetry/request.go b/internal/nrsdk/telemetry/request.go new file mode 100644 index 0000000..ce8822b --- /dev/null +++ b/internal/nrsdk/telemetry/request.go @@ -0,0 +1,55 @@ +package telemetry + +import ( + "encoding/json" + "fmt" + "net/http" +) + +const ( + maxCompressedSizeBytes = 1 << 20 +) + +// request contains an http.Request and the UncompressedBody which is provided +// for logging. +type request struct { + Request *http.Request + UncompressedBody json.RawMessage + + compressedBodyLength int +} + +type requestsBuilder interface { + newRequest(key, urlOverride string) (request, error) + split() []requestsBuilder +} + +var ( + errUnableToSplit = fmt.Errorf("unable to split large payload further") +) + +func newRequests(batch requestsBuilder, key, urlOverride string, maxCompressedSize int) ([]request, error) { + req, err := batch.newRequest(key, urlOverride) + if nil != err { + return nil, err + } + + if req.compressedBodyLength <= maxCompressedSize { + return []request{req}, nil + } + + var reqs []request + batches := batch.split() + if nil == batches { + return nil, errUnableToSplit + } + + for _, b := range batches { + rs, err := newRequests(b, key, urlOverride, maxCompressedSize) + if nil != err { + return nil, err + } + reqs = append(reqs, rs...) + } + return reqs, nil +} diff --git a/internal/nrsdk/telemetry/request_test.go b/internal/nrsdk/telemetry/request_test.go new file mode 100644 index 0000000..e87d686 --- /dev/null +++ b/internal/nrsdk/telemetry/request_test.go @@ -0,0 +1,79 @@ +package telemetry + +import ( + "errors" + "testing" +) + +type testRequestBuilder struct { + requests []func() (request, error) +} + +func (ts testRequestBuilder) newRequest(licenseKey, urlOverride string) (request, error) { + return ts.requests[0]() +} + +func (ts testRequestBuilder) split() []requestsBuilder { + reqs := ts.requests[1:] + if len(reqs) == 0 { + return nil + } + return []requestsBuilder{ + testRequestBuilder{requests: reqs}, + testRequestBuilder{requests: reqs}, + } +} + +func TestNewRequestsSplitSuccess(t *testing.T) { + ts := testRequestBuilder{ + requests: []func() (request, error){ + func() (request, error) { return request{compressedBodyLength: 20}, nil }, + func() (request, error) { return request{compressedBodyLength: 15}, nil }, + func() (request, error) { return request{compressedBodyLength: 11}, nil }, + func() (request, error) { return request{compressedBodyLength: 9}, nil }, + }, + } + reqs, err := newRequests(ts, "", "", 10) + if err != nil { + t.Error(err) + } + if len(reqs) != 8 { + t.Error(len(reqs)) + } +} + +func TestNewRequestsCantMakeRequest(t *testing.T) { + reqError := errors.New("cant make request") + ts := testRequestBuilder{ + requests: []func() (request, error){ + func() (request, error) { return request{compressedBodyLength: 20}, nil }, + func() (request, error) { return request{compressedBodyLength: 15}, nil }, + func() (request, error) { return request{compressedBodyLength: 11}, nil }, + func() (request, error) { return request{}, reqError }, + }, + } + reqs, err := newRequests(ts, "", "", 10) + if err != reqError { + t.Error(err) + } + if len(reqs) != 0 { + t.Error(len(reqs)) + } +} + +func TestNewRequestsCantSplit(t *testing.T) { + ts := testRequestBuilder{ + requests: []func() (request, error){ + func() (request, error) { return request{compressedBodyLength: 20}, nil }, + func() (request, error) { return request{compressedBodyLength: 15}, nil }, + func() (request, error) { return request{compressedBodyLength: 11}, nil }, + }, + } + reqs, err := newRequests(ts, "", "", 10) + if err != errUnableToSplit { + t.Error(err) + } + if len(reqs) != 0 { + t.Error(len(reqs)) + } +} diff --git a/internal/nrsdk/telemetry/spans.go b/internal/nrsdk/telemetry/spans.go new file mode 100644 index 0000000..5ef5f4f --- /dev/null +++ b/internal/nrsdk/telemetry/spans.go @@ -0,0 +1,175 @@ +package telemetry + +import ( + "bytes" + "encoding/json" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" +) + +// Span is a distributed tracing span. +type Span struct { + // Required Fields: + // + // ID is a unique identifier for this span. + ID string + // TraceID is a unique identifier shared by all spans within a single + // trace. + TraceID string + // Timestamp is when this span started. If Timestamp is not set, it + // will be assigned to time.Now() in Harvester.RecordSpan. + Timestamp time.Time + + // Recommended Fields: + // + // Name is the name of this span. + Name string + // ParentID is the span id of the previous caller of this span. This + // can be empty if this is the first span. + ParentID string + // Duration is the duration of this span. This field will be reported + // in milliseconds. + Duration time.Duration + // ServiceName is the name of the service that created this span. + ServiceName string + + // Additional Fields: + // + // Attributes is a map of user specified tags on this span. The map + // values can be any of bool, number, or string. + Attributes map[string]interface{} + // AttributesJSON is a json.RawMessage of attributes for this metric. It + // will only be sent if Attributes is nil. + AttributesJSON json.RawMessage +} + +func (s *Span) writeJSON(buf *bytes.Buffer) { + w := internal.JSONFieldsWriter{Buf: buf} + buf.WriteByte('{') + + w.StringField("id", s.ID) + w.StringField("trace.id", s.TraceID) + w.IntField("timestamp", s.Timestamp.UnixNano()/(1000*1000)) + + w.AddKey("attributes") + buf.WriteByte('{') + ww := internal.JSONFieldsWriter{Buf: buf} + + if "" != s.Name { + ww.StringField("name", s.Name) + } + if "" != s.ParentID { + ww.StringField("parent.id", s.ParentID) + } + if 0 != s.Duration { + ww.FloatField("duration.ms", s.Duration.Seconds()*1000.0) + } + if "" != s.ServiceName { + ww.StringField("service.name", s.ServiceName) + } + + internal.AddAttributes(&ww, s.Attributes) + + buf.WriteByte('}') + buf.WriteByte('}') +} + +// spanBatch represents a single batch of spans to report to New Relic. +type spanBatch struct { + // Attributes is a map of attributes to apply to all spans in this + // spanBatch. They are included in addition to any attributes set on + // any particular span. + Attributes map[string]interface{} + // AttributesJSON is a json.RawMessage of attributes to apply to all + // spans in this spanBatch. It will only be sent if the Attributes field + // on this spanBatch is nil. These attributes are included in addition + // to any attributes on any particular span. + AttributesJSON json.RawMessage + // TODO: Add ID and TraceID here. + Spans []Span +} + +// AddSpan appends a span to the spanBatch. +func (batch *spanBatch) AddSpan(s Span) { + batch.Spans = append(batch.Spans, s) +} + +// split will split the spanBatch into 2 equally sized batches. +// If the number of spans in the original is 0 or 1 then nil is returned. +func (batch *spanBatch) split() []requestsBuilder { + if len(batch.Spans) < 2 { + return nil + } + + half := len(batch.Spans) / 2 + b1 := *batch + b1.Spans = batch.Spans[:half] + b2 := *batch + b2.Spans = batch.Spans[half:] + + return []requestsBuilder{ + requestsBuilder(&b1), + requestsBuilder(&b2), + } +} + +func (batch *spanBatch) writeJSON(buf *bytes.Buffer) { + buf.WriteByte('[') + buf.WriteByte('{') + w := internal.JSONFieldsWriter{Buf: buf} + + w.AddKey("common") + buf.WriteByte('{') + ww := internal.JSONFieldsWriter{Buf: buf} + if nil != batch.Attributes { + ww.WriterField("attributes", internal.Attributes(batch.Attributes)) + } else if nil != batch.AttributesJSON { + ww.RawField("attributes", batch.AttributesJSON) + } + buf.WriteByte('}') + + w.AddKey("spans") + buf.WriteByte('[') + for idx, s := range batch.Spans { + if idx > 0 { + buf.WriteByte(',') + } + s.writeJSON(buf) + } + buf.WriteByte(']') + buf.WriteByte('}') + buf.WriteByte(']') +} + +const ( + defaultSpanURL = "https://trace-api.newrelic.com/trace/v1" +) + +// NewRequests creates new requests from the spanBatch. The request can be +// sent with an http.Client. +// +// NewRequest returns requests or an error if there was one. Each Request +// has an UncompressedBody field that is useful in debugging or testing. +// +// Possible response codes to be expected when sending the request to New +// Relic: +// +// 202 for success +// 403 for an auth failure +// 404 for a bad path +// 405 for anything but POST +// 411 if the Content-Length header is not included +// 413 for a payload that is too large +// 400 for a generally invalid request +// 429 Too Many Requests +// +func (batch *spanBatch) NewRequests(apiKey, urlOverride string) ([]request, error) { + return newRequests(batch, apiKey, urlOverride, maxCompressedSizeBytes) +} + +func (batch *spanBatch) newRequest(apiKey, urlOverride string) (request, error) { + buf := &bytes.Buffer{} + batch.writeJSON(buf) + return newRequest(defaultSpanURL, urlOverride, apiKey, buf.Bytes()) +} diff --git a/internal/nrsdk/telemetry/spans_batch_test.go b/internal/nrsdk/telemetry/spans_batch_test.go new file mode 100644 index 0000000..b699449 --- /dev/null +++ b/internal/nrsdk/telemetry/spans_batch_test.go @@ -0,0 +1,208 @@ +package telemetry + +import ( + "encoding/json" + "io/ioutil" + "testing" + "time" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/internal" +) + +func testSpanBatchJSON(t testing.TB, batch *spanBatch, expect string) { + if th, ok := t.(interface{ Helper() }); ok { + th.Helper() + } + reqs, err := batch.NewRequests("licensKey", "") + if nil != err { + t.Fatal(err) + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + req := reqs[0] + actual := string(req.UncompressedBody) + compact := compactJSONString(expect) + if actual != compact { + t.Errorf("\nexpect=%s\nactual=%s\n", compact, actual) + } + + body, err := ioutil.ReadAll(req.Request.Body) + req.Request.Body.Close() + if err != nil { + t.Fatal("unable to read body", err) + } + if len(body) != req.compressedBodyLength { + t.Error("compressed body length mismatch", + len(body), req.compressedBodyLength) + } + uncompressed, err := internal.Uncompress(body) + if err != nil { + t.Fatal("unable to uncompress body", err) + } + if string(uncompressed) != string(req.UncompressedBody) { + t.Error("request JSON mismatch", string(uncompressed), string(req.UncompressedBody)) + } +} + +func TestSpansPayloadSplit(t *testing.T) { + // test len 0 + sp := &spanBatch{} + split := sp.split() + if split != nil { + t.Error(split) + } + + // test len 1 + sp = &spanBatch{Spans: []Span{{Name: "a"}}} + split = sp.split() + if split != nil { + t.Error(split) + } + + // test len 2 + sp = &spanBatch{Spans: []Span{{Name: "a"}, {Name: "b"}}} + split = sp.split() + if len(split) != 2 { + t.Error("split into incorrect number of slices", len(split)) + } + testSpanBatchJSON(t, split[0].(*spanBatch), `[{"common":{},"spans":[{"id":"","trace.id":"","timestamp":-6795364578871,"attributes":{"name":"a"}}]}]`) + testSpanBatchJSON(t, split[1].(*spanBatch), `[{"common":{},"spans":[{"id":"","trace.id":"","timestamp":-6795364578871,"attributes":{"name":"b"}}]}]`) + + // test len 3 + sp = &spanBatch{Spans: []Span{{Name: "a"}, {Name: "b"}, {Name: "c"}}} + split = sp.split() + if len(split) != 2 { + t.Error("split into incorrect number of slices", len(split)) + } + testSpanBatchJSON(t, split[0].(*spanBatch), `[{"common":{},"spans":[{"id":"","trace.id":"","timestamp":-6795364578871,"attributes":{"name":"a"}}]}]`) + testSpanBatchJSON(t, split[1].(*spanBatch), `[{"common":{},"spans":[{"id":"","trace.id":"","timestamp":-6795364578871,"attributes":{"name":"b"}},{"id":"","trace.id":"","timestamp":-6795364578871,"attributes":{"name":"c"}}]}]`) +} + +func TestSpansJSON(t *testing.T) { + batch := &spanBatch{Spans: []Span{ + {}, // Empty span + { // Span with everything + ID: "myid", + TraceID: "mytraceid", + Name: "myname", + ParentID: "myparentid", + Timestamp: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), + Duration: 2 * time.Second, + ServiceName: "myentity", + Attributes: map[string]interface{}{"zip": "zap"}, + }, + }} + testSpanBatchJSON(t, batch, `[{"common":{},"spans":[ + { + "id":"", + "trace.id":"", + "timestamp":-6795364578871, + "attributes": { + } + }, + { + "id":"myid", + "trace.id":"mytraceid", + "timestamp":1417136460000, + "attributes": { + "name":"myname", + "parent.id":"myparentid", + "duration.ms":2000, + "service.name":"myentity", + "zip":"zap" + } + } + ]}]`) +} + +func TestSpansJSONWithCommonAttributes(t *testing.T) { + batch := &spanBatch{ + Attributes: map[string]interface{}{"zup": "wup"}, + Spans: []Span{ + { + ID: "myid", + TraceID: "mytraceid", + Name: "myname", + ParentID: "myparentid", + Timestamp: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), + Duration: 2 * time.Second, + ServiceName: "myentity", + Attributes: map[string]interface{}{"zip": "zap"}, + }, + }} + testSpanBatchJSON(t, batch, `[{"common":{"attributes":{"zup":"wup"}},"spans":[ + { + "id":"myid", + "trace.id":"mytraceid", + "timestamp":1417136460000, + "attributes": { + "name":"myname", + "parent.id":"myparentid", + "duration.ms":2000, + "service.name":"myentity", + "zip":"zap" + } + } + ]}]`) +} + +func TestSpansJSONWithCommonAttributesJSON(t *testing.T) { + batch := &spanBatch{ + AttributesJSON: json.RawMessage(`{"zup":"wup"}`), + Spans: []Span{ + { + ID: "myid", + TraceID: "mytraceid", + Name: "myname", + ParentID: "myparentid", + Timestamp: time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC), + Duration: 2 * time.Second, + ServiceName: "myentity", + Attributes: map[string]interface{}{"zip": "zap"}, + }, + }} + testSpanBatchJSON(t, batch, `[{"common":{"attributes":{"zup":"wup"}},"spans":[ + { + "id":"myid", + "trace.id":"mytraceid", + "timestamp":1417136460000, + "attributes": { + "name":"myname", + "parent.id":"myparentid", + "duration.ms":2000, + "service.name":"myentity", + "zip":"zap" + } + } + ]}]`) +} + +func TestSpanRequestURL(t *testing.T) { + batch := &spanBatch{Spans: []Span{{}}} + + // Default URL + reqs, err := batch.NewRequests("key", "") + if err != nil { + t.Error(err) + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + req := reqs[0] + if req.Request.URL.String() != "https://trace-api.newrelic.com/trace/v1" { + t.Error(req.Request.URL.String()) + } + // Override URL + reqs, err = batch.NewRequests("key", "https://override.host.com/path") + if err != nil { + t.Error(err) + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + req = reqs[0] + if req.Request.URL.String() != "https://override.host.com/path" { + t.Error(req.Request.URL.String()) + } +} diff --git a/internal/nrsdk/telemetry/spans_test.go b/internal/nrsdk/telemetry/spans_test.go new file mode 100644 index 0000000..9fd1464 --- /dev/null +++ b/internal/nrsdk/telemetry/spans_test.go @@ -0,0 +1,155 @@ +package telemetry + +import ( + "bytes" + "testing" + "time" +) + +func BenchmarkSpansJSON(b *testing.B) { + // This benchmark tests the overhead of turning spans into JSON. + batch := &spanBatch{} + numSpans := 10 * 1000 + tm := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + + for i := 0; i < numSpans; i++ { + batch.AddSpan(Span{ + ID: "myid", + TraceID: "mytraceid", + Name: "myname", + ParentID: "myparent", + Timestamp: tm, + Duration: 2 * time.Second, + ServiceName: "myentity", + Attributes: map[string]interface{}{ + "zip": "zap", + "zop": 123, + }, + }) + } + + if len(batch.Spans) != numSpans { + b.Fatal(len(batch.Spans)) + } + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + buf := &bytes.Buffer{} + batch.writeJSON(buf) + if bts := buf.Bytes(); nil == bts || len(bts) == 0 { + b.Fatal(string(bts)) + } + } +} + +func testHarvesterSpans(t testing.TB, h *Harvester, expect string) { + reqs := h.swapOutSpans() + if nil == reqs { + if expect != "null" { + t.Error("nil spans", expect) + } + return + } + if len(reqs) != 1 { + t.Fatal(reqs) + } + js := reqs[0].UncompressedBody + actual := string(js) + if th, ok := t.(interface{ Helper() }); ok { + th.Helper() + } + compactExpect := compactJSONString(expect) + if compactExpect != actual { + t.Errorf("\nexpect=%s\nactual=%s\n", compactExpect, actual) + } +} + +func TestSpan(t *testing.T) { + tm := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + h := NewHarvester(configTesting) + h.RecordSpan(Span{ + ID: "myid", + TraceID: "mytraceid", + Name: "myname", + ParentID: "myparent", + Timestamp: tm, + Duration: 2 * time.Second, + ServiceName: "myentity", + Attributes: map[string]interface{}{ + "zip": "zap", + }, + }) + expect := `[{"common":{},"spans":[{ + "id":"myid", + "trace.id":"mytraceid", + "timestamp":1417136460000, + "attributes": { + "name":"myname", + "parent.id":"myparent", + "duration.ms":2000, + "service.name":"myentity", + "zip":"zap" + } + }]}]` + testHarvesterSpans(t, h, expect) +} + +func TestSpanInvalidAttribute(t *testing.T) { + tm := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + h := NewHarvester(configTesting) + h.RecordSpan(Span{ + ID: "myid", + TraceID: "mytraceid", + Name: "myname", + ParentID: "myparent", + Timestamp: tm, + Duration: 2 * time.Second, + ServiceName: "myentity", + Attributes: map[string]interface{}{ + "weird-things-get-turned-to-strings": struct{}{}, + "nil-gets-removed": nil, + }, + }) + expect := `[{"common":{},"spans":[{ + "id":"myid", + "trace.id":"mytraceid", + "timestamp":1417136460000, + "attributes": { + "name":"myname", + "parent.id":"myparent", + "duration.ms":2000, + "service.name":"myentity", + "weird-things-get-turned-to-strings":"struct {}" + } + }]}]` + testHarvesterSpans(t, h, expect) +} + +func TestNoAPIKeyNoSpan(t *testing.T) { + tm := time.Date(2014, time.November, 28, 1, 1, 0, 0, time.UTC) + h := NewHarvester() + err := h.RecordSpan(Span{ + ID: "myid", + TraceID: "mytraceid", + Name: "myname", + ParentID: "myparent", + Timestamp: tm, + Duration: 2 * time.Second, + ServiceName: "myentity", + Attributes: map[string]interface{}{ + "zip": "zap", + "zop": 123, + }, + }) + if err != errSpansDisabled { + t.Error(err) + } + if 0 != len(h.spans) { + t.Error("spans were recorded", h.spans) + } + + expect := "null" + testHarvesterSpans(t, h, expect) +} diff --git a/internal/nrsdk/telemetry/utilities.go b/internal/nrsdk/telemetry/utilities.go new file mode 100644 index 0000000..5c371a0 --- /dev/null +++ b/internal/nrsdk/telemetry/utilities.go @@ -0,0 +1,25 @@ +package telemetry + +import "encoding/json" + +// jsonOrString returns its input as a jsonString if it is valid JSON, and as a +// string otherwise. +func jsonOrString(d []byte) interface{} { + var js json.RawMessage + if err := json.Unmarshal(d, &js); err == nil { + return jsonString(d) + } + return string(d) +} + +// jsonString assists in debug logging: The debug map could be marshalled as +// JSON or just printed directly. +type jsonString string + +// MarshalJSON returns the JSONString unmodified without any escaping. +func (js jsonString) MarshalJSON() ([]byte, error) { + if "" == js { + return []byte("null"), nil + } + return []byte(js), nil +} diff --git a/internal/nrsdk/telemetry/utilities_test.go b/internal/nrsdk/telemetry/utilities_test.go new file mode 100644 index 0000000..060d316 --- /dev/null +++ b/internal/nrsdk/telemetry/utilities_test.go @@ -0,0 +1,52 @@ +package telemetry + +import ( + "encoding/json" + "fmt" + "testing" +) + +func TestJSONString(t *testing.T) { + // Test that the jsonString type has the intended behavior. + var emptySlice []byte + jstr := jsonString(emptySlice) + if js, _ := json.Marshal(jstr); string(js) != `null` { + t.Error(string(js)) + } + if s := fmt.Sprintf("%v", jstr); s != "" { + t.Error(s) + } + jstr = jsonString(`{"zip":"zap"}`) + if js, _ := json.Marshal(jstr); string(js) != `{"zip":"zap"}` { + t.Error(string(js)) + } + if s := fmt.Sprintf("%v", jstr); s != `{"zip":"zap"}` { + t.Error(s) + } +} + +func TestJSONOrString(t *testing.T) { + // Test that jsonOrString has the intended behavior. + + out := jsonOrString(nil) + if js, _ := json.Marshal(out); string(js) != `""` { + t.Error(string(js)) + } + if s := fmt.Sprintf("%v", out); s != "" { + t.Error(s) + } + out = jsonOrString([]byte("this is not json")) + if js, _ := json.Marshal(out); string(js) != `"this is not json"` { + t.Error(string(js)) + } + if s := fmt.Sprintf("%v", out); s != "this is not json" { + t.Error(s) + } + out = jsonOrString([]byte(`{"this is":"json"}`)) + if js, _ := json.Marshal(out); string(js) != `{"this is":"json"}` { + t.Error(string(js)) + } + if s := fmt.Sprintf("%v", out); s != `{"this is":"json"}` { + t.Error(s) + } +} diff --git a/metric/builder.go b/metric/builder.go new file mode 100644 index 0000000..2f7d578 --- /dev/null +++ b/metric/builder.go @@ -0,0 +1,127 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "fmt" + "strings" + "unicode/utf16" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/instrumentation" + "github.com/newrelic/newrelic-istio-adapter/config" + "istio.io/istio/mixer/pkg/adapter" +) + +const ( + invalidNonEmptyErrMsg = "must be non-empty" + invalidTooLongErrMsg = "must be <= 255 16-bit code units" + maxCodeUnitsCount = 255 +) + +type metricConfig struct { + namespace string + metrics map[string]info +} + +// BuildHandler returns a metric Handler with valid configuration. +func BuildHandler(params *config.Params, agg *instrumentation.MetricAggregator, env adapter.Env) (*Handler, error) { + cfg, err := buildConfig(params) + if err != nil { + return nil, err + } + env.Logger().Infof("Built metrics: %#v", cfg.metrics) + + h := &Handler{ + logger: env.Logger(), + agg: agg, + metrics: cfg.metrics, + } + return h, nil +} + +// buildConfig returns a valid metricConfig. Most importantly, it iterates through +// the metrics from config.Params and validates them. If any of the metrics are +// invalid, it returns nil instead. +func buildConfig(params *config.Params) (cfg *metricConfig, errs *adapter.ConfigErrors) { + namespace := params.GetNamespace() + handlerMetrics := make(map[string]info, len(params.GetMetrics())) + cfg = &metricConfig{ + namespace: namespace, + metrics: handlerMetrics, + } + + for instName, mInfo := range params.GetMetrics() { + if mInfo.GetType() == config.UNSPECIFIED { + errs = errs.Appendf("MetricInfo.type", "type is invalid: UNSPECIFIED") + cfg = nil + } + + metricName := mInfo.GetName() + if metricName == "" { + errs = errs.Appendf("MetricInfo.name", "must be non-empty") + cfg = nil + continue + } + + fullName := buildName(namespace, metricName) + err := validateName(fullName) + if err != nil { + if namespace == "" { + errs = errs.Appendf("MetricInfo.name", "name is invalid: %v", err) + } else { + errs = errs.Appendf("MetricInfo.name", "namespace + name is invalid: %v", err) + } + cfg = nil + continue + } + + // Add valid metric name, so long as we haven't seen any validation errors yet. + if cfg != nil { + cfg.metrics[instName] = info{ + name: fullName, + mtype: mInfo.GetType(), + } + } + } + return cfg, errs +} + +func countUtf16CodeUnits(s string) int { + codePoints := []rune(s) + codeUnits := utf16.Encode(codePoints) + return len(codeUnits) +} + +func validateName(name string) error { + // Must be non-empty. + if name == "" { + return fmt.Errorf(invalidNonEmptyErrMsg) + } + // Must be less than or equal to 255 16-bit code units (UTF-16). + if countUtf16CodeUnits(name) > maxCodeUnitsCount { + return fmt.Errorf("%q %s", name, invalidTooLongErrMsg) + } + return nil +} + +// buildName creates the full name by combining namespace and MetricInfo.name. +func buildName(namespace, metricName string) string { + name := metricName + if namespace != "" { + name = strings.Join([]string{namespace, metricName}, ".") + } + return name +} diff --git a/metric/builder_test.go b/metric/builder_test.go new file mode 100644 index 0000000..a2cc9a0 --- /dev/null +++ b/metric/builder_test.go @@ -0,0 +1,128 @@ +// Copyright 2019 New Relic Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "strings" + "testing" + + adapterTest "istio.io/istio/mixer/pkg/adapter/test" + + "github.com/newrelic/newrelic-istio-adapter/config" +) + +func TestBuildConfig(t *testing.T) { + testCases := []struct { + isValid bool + namespace string + metricName string + expectedErrMsg string + }{ + {true, "", "request.bytes", ""}, + {true, "istio", "request.bytes", ""}, + {true, "istio.", "istio..request.bytes", ""}, + {true, "istio", "123", ""}, + {false, "istio", "", invalidNonEmptyErrMsg}, + {false, "", "", invalidNonEmptyErrMsg}, + // these test cases check that names are <= 255 UTF-16 code units; each code unit is 2 bytes + {false, "", strings.Repeat("z", 256), invalidTooLongErrMsg}, + // U+61 a is encoded to 1 UTF-16 code unit, so this string is 255 runes and 255 code units + {true, "", strings.Repeat("a", 254) + string('\U00000061'), ""}, + // U+13189 Egyptian Hieroglyph turtle is encoded to 2 UTF-16 code units, so this string is 255 runes and 256 code units + {false, "", strings.Repeat("a", 254) + string('\U00013189'), invalidTooLongErrMsg}, + } + + for i, tc := range testCases { + pmi := &config.Params_MetricInfo{ + Name: tc.metricName, + Type: config.GAUGE, + } + + params := &config.Params{ + Namespace: tc.namespace, + Metrics: map[string]*config.Params_MetricInfo{"example": pmi}, + } + + errString := "" + _, err := buildConfig(params) + + if err != nil { + errString = err.Error() + } + if tc.isValid && err != nil { + t.Errorf("index %d: Did not expect error, got %s", i, errString) + } + if !tc.isValid && !strings.Contains(errString, tc.expectedErrMsg) { + t.Errorf("index %d: Expected error to contain '%s', got %s", i, tc.expectedErrMsg, errString) + } + } +} + +func TestBuildHandler(t *testing.T) { + mixerMetricName := "requestsize.instance.istio-system" + + testCases := []struct { + isValid bool + namespace string + metricName string + expectedName string + }{ + {true, "", "request.bytes", "request.bytes"}, + {true, "istio", "request.bytes", "istio.request.bytes"}, + {true, "istio.", "request.bytes", "istio..request.bytes"}, + {true, "istio", "123", "istio.123"}, + {false, "istio", "", ""}, + } + + for _, tc := range testCases { + pmi := &config.Params_MetricInfo{ + Name: tc.metricName, + Type: config.GAUGE, + } + + params := &config.Params{ + Namespace: tc.namespace, + Metrics: map[string]*config.Params_MetricInfo{mixerMetricName: pmi}, + } + + env := adapterTest.NewEnv(t) + h, err := BuildHandler(params, nil, env) + + // Invalid name, but we saw expected error, so it's all good. + if !tc.isValid && err != nil { + continue + } + + // Invalid name, but we didn't see expected error. + if !tc.isValid && err == nil { + t.Errorf("expected error for invalid metric '%s', but did not get one.", tc.metricName) + continue + } + + // Valid name, but we got an error building the handler. + if tc.isValid && err != nil { + t.Errorf("metric '%s' is valid, but got error building handler: '%v'", tc.metricName, err) + continue + } + + // Valid name, so make sure we see it in metrics map. + if tc.isValid && err == nil { + _, ok := h.metrics[mixerMetricName] + if !ok { + t.Errorf("metric '%s' is valid, but not found in metrics map: '%#v'.", tc.metricName, h.metrics) + } + } + } +} diff --git a/metric/handler.go b/metric/handler.go new file mode 100644 index 0000000..303cac7 --- /dev/null +++ b/metric/handler.go @@ -0,0 +1,74 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "context" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/instrumentation" + "github.com/newrelic/newrelic-istio-adapter/config" + "github.com/newrelic/newrelic-istio-adapter/convert" + "istio.io/istio/mixer/pkg/adapter" + "istio.io/istio/mixer/template/metric" +) + +// Handler represents a processor that can handle metrics from Istio and transmit them to New Relic. +type Handler struct { + logger adapter.Logger + agg *instrumentation.MetricAggregator + metrics map[string]info +} + +type info struct { + name string + mtype config.Params_MetricInfo_Type +} + +// HandleMetric transforms metric template instances into New Relic metrics and +// sends them to New Relic. +func (h *Handler) HandleMetric(_ context.Context, msgs []*metric.InstanceMsg) error { + for _, i := range msgs { + v, err := convert.ValueToFloat64(i.Value) + if err != nil { + h.logger.Warningf("Failed to parse metric value '%v' for '%s'", i.Value, i.Name) + continue + } + attrs := convert.DimensionsToAttributes(i.Dimensions) + + minfo, found := h.metrics[i.Name] + if !found { + h.logger.Warningf("no metric info found for %s, skipping metric", i.Name) + continue + } + + switch minfo.mtype { + case config.GAUGE: + h.agg.NewGauge(minfo.name, attrs).Value(v) + case config.COUNT: + if v < 0.0 { + h.logger.Warningf("invalid count value for %s (skipping): must be a positive value for a count (got %f)", i.Name, v) + continue + } + h.agg.NewCount(minfo.name, attrs).Increase(v) + case config.SUMMARY: + h.agg.NewSummary(minfo.name, attrs).Record(v) + default: + h.logger.Warningf("unknown metric type for %s: %v", i.Name, minfo.mtype) + } + } + + return nil +} diff --git a/metric/handler_test.go b/metric/handler_test.go new file mode 100644 index 0000000..3c12a70 --- /dev/null +++ b/metric/handler_test.go @@ -0,0 +1,214 @@ +// Copyright 2019 New Relic Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "context" + "testing" + "time" + + "github.com/gogo/protobuf/types" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/instrumentation" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" + policy "istio.io/api/policy/v1beta1" + adapterTest "istio.io/istio/mixer/pkg/adapter/test" + "istio.io/istio/mixer/template/metric" + + "github.com/newrelic/newrelic-istio-adapter/config" +) + +func TestHandleMetric(t *testing.T) { + testCases := []struct { + isValid bool + metricName string + value *policy.Value + expectedName string + expectedValue float64 + }{ + { + true, + "gaugeExample.instance.istio-system", + &policy.Value{Value: &policy.Value_StringValue{StringValue: "1"}}, + "gaugeExample", + float64(1.0), + }, + { + true, + "gaugeExample.instance.istio-system", + &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(2)}}, + "gaugeExample", + float64(2.0), + }, + { + true, + "gaugeExample.instance.istio-system", + &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: float64(3.33)}}, + "gaugeExample", + float64(3.33), + }, + { + true, + "gaugeExample.instance.istio-system", + &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(-2)}}, + "gaugeExample", + float64(-2.0), + }, + { + true, + "countExample.instance.istio-system", + &policy.Value{Value: &policy.Value_StringValue{StringValue: "1"}}, + "countExample", + float64(1.0), + }, + { + true, + "countExample.instance.istio-system", + &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(2)}}, + "countExample", + float64(2.0), + }, + { + true, + "countExample.instance.istio-system", + &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: float64(3.33)}}, + "countExample", + float64(3.33), + }, + { + false, + "countExample.instance.istio-system", + &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(-2)}}, + "countExample", + float64(0), + }, + { + true, + "summaryExample.instance.istio-system", + &policy.Value{Value: &policy.Value_StringValue{StringValue: "1"}}, + "summaryExample", + float64(1.0), + }, + { + true, + "summaryExample.instance.istio-system", + &policy.Value{Value: &policy.Value_Int64Value{Int64Value: int64(2)}}, + "summaryExample", + float64(2.0), + }, + { + true, + "summaryExample.instance.istio-system", + &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: float64(3.33)}}, + "summaryExample", + float64(3.33), + }, + { + true, + "summaryExample.instance.istio-system", + &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: float64(-123.456)}}, + "summaryExample", + float64(-123.456), + }, + { + false, + "summaryExample.instance.istio-system", + &policy.Value{Value: &policy.Value_StringValue{StringValue: "123ms"}}, + "summaryExample", + float64(123.0), + }, + { + true, + "summaryExample.instance.istio-system", + &policy.Value{Value: &policy.Value_DurationValue{DurationValue: &policy.Duration{Value: types.DurationProto(time.Second)}}}, + "summaryExample", + float64(1000.0), + }, + } + + handlerMetrics := make(map[string]info, 3) + handlerMetrics["gaugeExample.instance.istio-system"] = info{ + name: "gaugeExample", + mtype: config.GAUGE, + } + handlerMetrics["countExample.instance.istio-system"] = info{ + name: "countExample", + mtype: config.COUNT, + } + handlerMetrics["summaryExample.instance.istio-system"] = info{ + name: "summaryExample", + mtype: config.SUMMARY, + } + + metricHandler := &Handler{ + logger: adapterTest.NewEnv(t).Logger(), + agg: instrumentation.NewMetricAggregator(), + metrics: handlerMetrics, + } + + for _, tc := range testCases { + instances := make([]*metric.InstanceMsg, 1) + instances[0] = &metric.InstanceMsg{ + Name: tc.metricName, + Value: tc.value, + Dimensions: make(map[string]*policy.Value, 0), + } + err := metricHandler.HandleMetric(context.Background(), instances) + if err != nil { + t.Errorf("HandleMetric() error: %v", err) + } + + metrics := metricHandler.agg.Metrics() + if len(metrics) == 0 && !tc.isValid { + // We correctly skipped invalid metric, so nothing left to test. + continue + } + if len(metrics) == 0 && tc.isValid { + t.Errorf("failed to record valid metric %q with value %v.", tc.metricName, tc.value) + continue + } + if len(metrics) > 1 { + t.Errorf("expected to record only one metric %q, got %v", tc.metricName, metrics) + continue + } + metric := metrics[0] + + switch m := metric.(type) { + case *telemetry.Gauge: + if m.Name != tc.expectedName { + t.Errorf("expected name %s, got %s", tc.expectedName, m.Name) + } + if m.Value != tc.expectedValue { + t.Errorf("expected value %f, got %f", tc.expectedValue, m.Value) + } + case *telemetry.Count: + if m.Name != tc.expectedName { + t.Errorf("expected name %s, got %s", tc.expectedName, m.Name) + } + if m.Value != tc.expectedValue { + t.Errorf("expected value %f, got %f", tc.expectedValue, m.Value) + } + case *telemetry.Summary: + if m.Name != tc.expectedName { + t.Errorf("expected name %s, got %s", tc.expectedName, m.Name) + } + if m.Sum != tc.expectedValue { + t.Errorf("expected value %f, got %f", tc.expectedValue, m.Sum) + } + default: + t.Errorf("unknown metric type %T for %s", m, tc.metricName) + } + + } +} diff --git a/sample_newrelic_dashboard.json b/sample_newrelic_dashboard.json new file mode 100644 index 0000000..abcc975 --- /dev/null +++ b/sample_newrelic_dashboard.json @@ -0,0 +1,113 @@ +{ + "dashboard": { + "title": "New Relic Istio Adapter Template", + "icon": "line-chart", + "visibility": "all", + "editable": "editable_by_all", + "metadata": { + "version": 1 + }, + "filter": { + "event_types": [ + "Metric" + ], + "attributes": [ + "cluster.name", + "destination.service.name", + "source.app" + ] + }, + "widgets": [ + { + "visualization": "faceted_area_chart", + "layout": { + "width": 1, + "height": 2, + "row": 1, + "column": 1 + }, + "data": [ + { + "nrql": "FROM Metric SELECT average(istio.request.total) WHERE destination.service.name != 'istio-telemetry' TIMESERIES max FACET source.app,destination.service.name LIMIT 100" + } + ], + "presentation": { + "title": "Request Volume", + "notes": null + } + }, + { + "visualization": "faceted_area_chart", + "layout": { + "width": 1, + "height": 1, + "row": 1, + "column": 2 + }, + "data": [ + { + "nrql": "FROM Metric SELECT sum(istio.request.total) WHERE destination.service.name != 'istio-telemetry' FACET response_code TIMESERIES max" + } + ], + "presentation": { + "title": "Request Response Code", + "notes": null + } + }, + { + "visualization": "faceted_line_chart", + "layout": { + "width": 1, + "height": 1, + "row": 1, + "column": 3 + }, + "data": [ + { + "nrql": "FROM Metric SELECT average(istio.request.duration.milliseconds) WHERE destination.service.name != 'istio-telemetry' TIMESERIES max FACET source.app,destination.service.name" + } + ], + "presentation": { + "title": "Request Duration [milliseconds]", + "notes": null + } + }, + { + "visualization": "faceted_area_chart", + "layout": { + "width": 1, + "height": 1, + "row": 2, + "column": 2 + }, + "data": [ + { + "nrql": "FROM Metric SELECT average(istio.request.bytes) WHERE destination.service.name != 'istio-telemetry' TIMESERIES max FACET source.app,destination.service.name" + } + ], + "presentation": { + "title": "Request Throughput [Bytes]", + "notes": null + } + }, + { + "visualization": "faceted_area_chart", + "layout": { + "width": 1, + "height": 1, + "row": 2, + "column": 3 + }, + "data": [ + { + "nrql": "FROM Metric SELECT average(istio.response.bytes) WHERE destination.service.name != 'istio-telemetry' TIMESERIES max FACET source.app,destination.service.name" + } + ], + "presentation": { + "title": "Response Throughput [Bytes]", + "notes": null + } + } + ] + } +} diff --git a/sample_operator_cfg.yaml b/sample_operator_cfg.yaml new file mode 100644 index 0000000..d9cddbc --- /dev/null +++ b/sample_operator_cfg.yaml @@ -0,0 +1,400 @@ +# +# Copyright 2019 New Relic Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +--- +apiVersion: "config.istio.io/v1alpha2" +kind: handler +metadata: + name: newrelic-handler + namespace: istio-system +spec: + adapter: newrelic + connection: + address: ":55912" + # address: "newrelic-istio-adapter.newrelic-istio-adapter.svc.cluster.local:80" + # authentication: + # mutual: + # private_key: "/tmp/grpc-test-key-cert/mixer.key" + # client_certificate: "/tmp/grpc-test-key-cert/mixer.crt" + # ca_certificates: "/tmp/grpc-test-key-cert/ca.pem" + params: + namespace: istio + metrics: + requestduration.instance.istio-system: + name: request.duration.seconds + type: SUMMARY + requestsize.instance.istio-system: + name: request.bytes + type: GAUGE + requestcount.instance.istio-system: + name: request.total + type: COUNT + responsesize.instance.istio-system: + name: response.bytes + type: GAUGE + tcpbytesent.instance.istio-system: + name: tcp.sent.bytes + type: COUNT + tcpbytereceived.instance.istio-system: + name: tcp.received.bytes + type: COUNT + tcpconnectionsopened.instance.istio-system: + name: tcp.connections.opened + type: COUNT + tcpconnectionsclosed.instance.istio-system: + name: tcp.connections.closed + type: COUNT +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: requestcount + namespace: istio-system +spec: + template: metric + params: + value: "1" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: requestsize + namespace: istio-system +spec: + template: metric + params: + value: request.size | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: requestduration + namespace: istio-system +spec: + template: metric + params: + value: response.duration | "0ms" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: responsesize + namespace: istio-system +spec: + template: metric + params: + value: response.size | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + request.protocol: api.protocol | context.protocol | "unknown" + response.code: response.code | 200 + response.flags: context.proxy_error_code | "-" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: tcpbytesent + namespace: istio-system +spec: + template: metric + params: + value: connection.sent.bytes | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: tcpbytereceived + namespace: istio-system +spec: + template: metric + params: + value: connection.received.bytes | 0 + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.host | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: tcpconnectionsopened + namespace: istio-system +spec: + template: metric + params: + value: "1" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.name | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: instance +metadata: + name: tcpconnectionsclosed + namespace: istio-system +spec: + template: metric + params: + value: "1" + dimensions: + reporter: conditional((context.reporter.kind | "inbound") == "outbound", "source", "destination") + service.name: conditional((context.reporter.kind | "inbound") == "outbound", source.workload.name | "unknown", destination.workload.name | "unknown") + source.workload: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + source.principal: source.principal | "unknown" + source.app: source.labels["app"] | "unknown" + source.version: source.labels["version"] | "unknown" + destination.workload: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + destination.principal: destination.principal | "unknown" + destination.app: destination.labels["app"] | "unknown" + destination.version: destination.labels["version"] | "unknown" + destination.service: destination.service.name | "unknown" + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + response.flags: context.proxy_error_code | "-" +--- +apiVersion: config.istio.io/v1alpha2 +kind: instance +metadata: + name: "newrelic-span" + namespace: istio-system +spec: + template: tracespan + params: + apiProtocol: api.protocol | "" + clientSpan: (context.reporter.kind | "inbound") == "outbound" + destinationIp: destination.ip | ip("0.0.0.0") + destinationName: destination.workload.name | "unknown" + endTime: response.time + httpStatusCode: response.code | 0 + parentSpanId: request.headers["x-b3-parentspanid"] | "" + requestSize: request.size | 0 + requestTotalSize: request.total_size | 0 + responseSize: response.size | 0 + responseTotalSize: response.total_size | 0 + rewriteClientSpanId: "false" + sourceIp: source.ip | ip("0.0.0.0") + sourceName: source.workload.name | "unknown" + spanId: request.headers["x-b3-spanid"] | "" + spanName: destination.workload.name | destination.service.name | "unknown" + spanTags: + api.name: api.service | "unknown" + api.version: api.version | "unknown" + destination.owner: destination.owner | "unknown" + destination.port: destination.port | 0 + destination.service.name: destination.service.name | "unknown" + destination.service.namespace: destination.service.namespace | "unknown" + destination.workload.name: destination.workload.name | "unknown" + destination.workload.namespace: destination.workload.namespace | "unknown" + request.path: request.path | "" + request.operation: conditional((context.protocol | "unknown") == "grpc", request.path + | "unknown", request.method | "unknown") + request.protocol: context.protocol | "unknown" + connection.securityPolicy: conditional((context.reporter.kind | "inbound") == + "outbound", "unknown", conditional(connection.mtls | false, "mutual_tls", "none")) + source.owner: source.owner | "unknown" + source.workload.name: source.workload.name | "unknown" + source.workload.namespace: source.workload.namespace | "unknown" + startTime: request.time + traceId: request.headers["x-b3-traceid"] | "" +--- +apiVersion: "config.istio.io/v1alpha2" +kind: rule +metadata: + name: newrelic-http + namespace: istio-system +spec: + match: (context.protocol == "http" || context.protocol == "grpc") && (match((request.useragent | "-"), "kube-probe*") == false) + actions: + - handler: newrelic-handler + instances: + - requestcount + - requestsize + - requestduration + - responsesize +--- +apiVersion: "config.istio.io/v1alpha2" +kind: rule +metadata: + name: newrelic-tcp + namespace: istio-system +spec: + match: context.protocol == "tcp" + actions: + - handler: newrelic-handler + instances: + - tcpbytesent + - tcpbytereceived +--- +apiVersion: "config.istio.io/v1alpha2" +kind: rule +metadata: + name: newrelic-tcpconnectionopen + namespace: istio-system +spec: + match: context.protocol == "tcp" && ((connection.event | "na") == "open") + actions: + - handler: newrelic-handler + instances: + - tcpconnectionsopened +--- +apiVersion: "config.istio.io/v1alpha2" +kind: rule +metadata: + name: newrelic-tcpconnectionclosed + namespace: istio-system +spec: + match: context.protocol == "tcp" && ((connection.event | "na") == "close") + actions: + - handler: newrelic-handler + instances: + - tcpconnectionsclosed +--- +# apiVersion: "config.istio.io/v1alpha2" +# kind: rule +# metadata: +# name: "newrelic-tracing" +# namespace: istio-system +# spec: +# match: (context.protocol == "http" || context.protocol == "grpc") && destination.workload.name != "istio-telemetry" && destination.workload.name != "istio-pilot" && ((request.headers["x-b3-traceid"] | "0") == "1") +# actions: +# - handler: newrelic-handler +# instances: +# - "newrelic-span" +# --- diff --git a/server.go b/server.go new file mode 100644 index 0000000..2928fff --- /dev/null +++ b/server.go @@ -0,0 +1,210 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// nolint:lll +//go:generate ./bin/mixer_codegen.sh -a ./config/config.proto -x "-s=false -n newrelic -t metric -t tracespan" + +package newrelic + +import ( + "bytes" + "context" + "fmt" + "net" + "sync" + + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/instrumentation" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" + "github.com/newrelic/newrelic-istio-adapter/config" + nrmetric "github.com/newrelic/newrelic-istio-adapter/metric" + "github.com/newrelic/newrelic-istio-adapter/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + adptModel "istio.io/api/mixer/adapter/model/v1beta1" + "istio.io/istio/mixer/pkg/adapter" + rtHandler "istio.io/istio/mixer/pkg/runtime/handler" + "istio.io/istio/mixer/template/metric" + "istio.io/istio/mixer/template/tracespan" + "istio.io/pkg/log" + "istio.io/pkg/pool" +) + +// Server listens for Mixer metrics and sends them to New Relic. +type Server struct { + listener net.Listener + healthServer *health.Server + server *grpc.Server + shutdown chan error + + rawcfg []byte + env adapter.Env + builderLock sync.RWMutex + + handler *Handler + + harvester *telemetry.Harvester + agg *instrumentation.MetricAggregator +} + +// Compile time assertion Server implement what it is expected to. +var ( + _ metric.HandleMetricServiceServer = &Server{} + _ tracespan.HandleTraceSpanServiceServer = &Server{} +) + +// NewServer creates a new Mixer metric handling server. +func NewServer(addr string, agg *instrumentation.MetricAggregator, h *telemetry.Harvester, grpcOpt ...grpc.ServerOption) (*Server, error) { + gp := pool.NewGoroutinePool(5, false) + s := &Server{ + env: rtHandler.NewEnv(0, "newrelic", gp), + rawcfg: []byte{0xff, 0xff}, + healthServer: health.NewServer(), + server: grpc.NewServer(grpcOpt...), + harvester: h, + agg: agg, + } + + var err error + if s.listener, err = net.Listen("tcp", addr); err != nil { + return nil, fmt.Errorf("unable to listen on %q: %v", addr, err) + } + log.Infof("listening on %q", s.listener.Addr().String()) + + metric.RegisterHandleMetricServiceServer(s.server, s) + tracespan.RegisterHandleTraceSpanServiceServer(s.server, s) + if _, err = s.getHandler(nil); err != nil { + return nil, err + } + + s.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) + healthpb.RegisterHealthServer(s.server, s.healthServer) + + return s, nil +} + +// getHandler returns the handler for rawcfg if it already exists otherwise it builds it. +func (s *Server) getHandler(rawcfg []byte) (*Handler, error) { + s.builderLock.RLock() + if bytes.Equal(rawcfg, s.rawcfg) { + h := s.handler + s.builderLock.RUnlock() + return h, nil + } + s.builderLock.RUnlock() + + // build a handler for the rawcfg and establish session. + cfg := &config.Params{} + if err := cfg.Unmarshal(rawcfg); err != nil { + return nil, err + } + + s.builderLock.Lock() + defer s.builderLock.Unlock() + + // check again if someone else beat you to this. + if bytes.Equal(rawcfg, s.rawcfg) { + return s.handler, nil + } + + mh, err := nrmetric.BuildHandler(cfg, s.agg, s.env) + if err != nil { + return nil, err + } + + th, err := trace.BuildHandler(cfg, s.harvester, s.env) + if err != nil { + return nil, err + } + + s.rawcfg = rawcfg + s.handler = &Handler{m: mh, t: th} + + return s.handler, nil +} + +// Run starts the Server. +func (s *Server) Run() { + s.shutdown = make(chan error, 1) + go func() { + err := s.server.Serve(s.listener) + + // notify closer we're done + s.shutdown <- err + }() +} + +// Wait waits for Server to stop. +func (s *Server) Wait() error { + if s.shutdown == nil { + return fmt.Errorf("server not running") + } + + err := <-s.shutdown + s.shutdown = nil + return err +} + +// Close gracefully shuts down Server. +func (s *Server) Close() error { + var results error + if s.shutdown != nil { + s.healthServer.Shutdown() + s.server.GracefulStop() + if err := s.Wait(); err != nil { + results = err + } + } + + if s.listener != nil { + if err := s.listener.Close(); err != nil { + if results != nil { + results = fmt.Errorf("%v: %w", err, results) + } else { + results = err + } + } + } + + return results +} + +// HandleTraceSpan implements tracespan.HandleMetricServiceServer. +func (s *Server) HandleTraceSpan(ctx context.Context, r *tracespan.HandleTraceSpanRequest) (*adptModel.ReportResult, error) { + h, err := s.getHandler(r.AdapterConfig.Value) + if err != nil { + return nil, err + } + + if err = h.HandleTraceSpan(ctx, r.Instances); err != nil { + return nil, s.env.Logger().Errorf("could not process: %v", err) + } + + return &adptModel.ReportResult{}, nil +} + +// HandleMetric implements metric.HandleMetricServiceServer. +func (s *Server) HandleMetric(ctx context.Context, r *metric.HandleMetricRequest) (*adptModel.ReportResult, error) { + h, err := s.getHandler(r.AdapterConfig.Value) + if err != nil { + return nil, err + } + + if err = h.HandleMetric(ctx, r.Instances); err != nil { + return nil, s.env.Logger().Errorf("could not process: %v", err) + } + + return &adptModel.ReportResult{}, nil +} diff --git a/trace/builder.go b/trace/builder.go new file mode 100644 index 0000000..d2bc273 --- /dev/null +++ b/trace/builder.go @@ -0,0 +1,31 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" + "github.com/newrelic/newrelic-istio-adapter/config" + "istio.io/istio/mixer/pkg/adapter" +) + +// BuildHandler returns a trace Handler with valid configuration. +func BuildHandler(_ *config.Params, h *telemetry.Harvester, env adapter.Env) (*Handler, error) { + traceHandler := &Handler{ + logger: env.Logger(), + harvester: h, + } + return traceHandler, nil +} diff --git a/trace/handler.go b/trace/handler.go new file mode 100644 index 0000000..e7abea5 --- /dev/null +++ b/trace/handler.go @@ -0,0 +1,101 @@ +// Copyright 2019 New Relic Corporation +// Copyright 2018 Istio Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + "errors" + + "github.com/gogo/protobuf/types" + "github.com/newrelic/newrelic-istio-adapter/convert" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" + "istio.io/istio/mixer/pkg/adapter" + "istio.io/istio/mixer/template/tracespan" +) + +// Handler represents a processor that can handle tracespans from Istio and transmit them to New Relic. +type Handler struct { + logger adapter.Logger + harvester *telemetry.Harvester +} + +// HandleTraceSpan transforms tracespan template instances into New Relic spans and +// sends them to New Relic. +func (h *Handler) HandleTraceSpan(_ context.Context, msgs []*tracespan.InstanceMsg) error { + for _, i := range msgs { + span, err := convertTraceSpan(i) + if err != nil { + h.logger.Warningf("Error converting tracespan: %v", err) + continue + } + + h.harvester.RecordSpan(*span) + } + return nil +} + +// convertTraceSpan will convert a tracespan.InstanceMsg into a telemetry.Span. +func convertTraceSpan(i *tracespan.InstanceMsg) (*telemetry.Span, error) { + startTime, err := types.TimestampFromProto(i.StartTime.GetValue()) + if err != nil { + return nil, err + } + endTime, err := types.TimestampFromProto(i.EndTime.GetValue()) + if err != nil { + return nil, err + } + + if i.TraceId == "" { + return nil, errors.New("no trace ID") + } + + if i.SpanId == "" { + return nil, errors.New("no span ID") + } + + attributes := convert.DimensionsToAttributes(i.SpanTags) + attributes["response.code"] = i.HttpStatusCode + attributes["clientSpan"] = i.ClientSpan + attributes["rewriteClientSpanId"] = i.RewriteClientSpanId + attributes["source.name"] = i.SourceName + attributes["source.ip"] = adapter.Stringify(i.SourceIp.GetValue()) + attributes["destination.name"] = i.DestinationName + attributes["destination.ip"] = adapter.Stringify(i.DestinationIp.GetValue()) + attributes["request.size"] = i.RequestSize + attributes["request.totalSize"] = i.RequestTotalSize + attributes["response.size"] = i.ResponseSize + attributes["response.totalSize"] = i.ResponseTotalSize + attributes["api.protocol"] = i.ApiProtocol + + span := &telemetry.Span{ + ID: i.SpanId, + TraceID: i.TraceId, + Name: i.SpanName, + ParentID: i.ParentSpanId, + Timestamp: startTime, + Duration: endTime.Sub(startTime), + // Default to assuming this was a server span. + ServiceName: i.DestinationName, + Attributes: attributes, + } + + if i.ClientSpan { + // For explicit client spans, assign to the source. + span.ServiceName = i.SourceName + } + + return span, nil +} diff --git a/trace/handler_test.go b/trace/handler_test.go new file mode 100644 index 0000000..c63c608 --- /dev/null +++ b/trace/handler_test.go @@ -0,0 +1,135 @@ +// Copyright 2019 New Relic Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "reflect" + "testing" + "time" + + "github.com/gogo/protobuf/types" + "github.com/newrelic/newrelic-istio-adapter/internal/nrsdk/telemetry" + policy "istio.io/api/policy/v1beta1" + "istio.io/istio/mixer/template/tracespan" +) + +func TestConvertTraceSpan(t *testing.T) { + startTime := time.Date(2020, time.August, 10, 11, 12, 30, 0, time.UTC) + startTimestamp, err := types.TimestampProto(startTime) + if err != nil { + t.Errorf("error creating startTimestamp: %v", err) + } + + endTime := time.Date(2020, time.August, 10, 11, 12, 31, 0, time.UTC) + endTimestamp, err := types.TimestampProto(endTime) + if err != nil { + t.Errorf("error creating endTimestamp: %v", err) + } + + spanTags := map[string]*policy.Value{ + "zip": &policy.Value{Value: &policy.Value_StringValue{StringValue: "zap"}}, + "int": &policy.Value{Value: &policy.Value_Int64Value{Int64Value: 123}}, + "float": &policy.Value{Value: &policy.Value_DoubleValue{DoubleValue: 4.56}}, + } + + tSpan := &tracespan.InstanceMsg{ + SpanId: "some-guid-value", + TraceId: "123", + Name: "name", + ParentSpanId: "456", + StartTime: &policy.TimeStamp{Value: startTimestamp}, + EndTime: &policy.TimeStamp{Value: endTimestamp}, + SpanName: "span-name", + HttpStatusCode: 200, + ClientSpan: true, + RewriteClientSpanId: true, + SourceName: "source-service", + SourceIp: &policy.IPAddress{Value: []byte{192, 168, 2, 2}}, + DestinationName: "destination-service", + DestinationIp: &policy.IPAddress{Value: []byte{192, 168, 3, 3}}, + RequestSize: int64(99), + RequestTotalSize: int64(199), + ResponseSize: int64(299), + ResponseTotalSize: int64(399), + ApiProtocol: "http", + SpanTags: spanTags, + } + + expectedAttrs := map[string]interface{}{ + "response.code": int64(200), + "clientSpan": true, + "rewriteClientSpanId": true, + "source.name": "source-service", + "source.ip": "192.168.2.2", + "destination.name": "destination-service", + "destination.ip": "192.168.3.3", + "request.size": int64(99), + "request.totalSize": int64(199), + "response.size": int64(299), + "response.totalSize": int64(399), + "api.protocol": "http", + "zip": "zap", + "int": int64(123), + "float": float64(4.56), + } + + expected := &telemetry.Span{ + ID: "some-guid-value", + TraceID: "123", + Name: "span-name", + ParentID: "456", + Timestamp: startTime, + Duration: time.Second, + ServiceName: "source-service", + Attributes: expectedAttrs, + } + + actual, err := convertTraceSpan(tSpan) + if err != nil { + t.Errorf("failed to convert tracespan: %v", err) + } + + if actual.ID != expected.ID { + t.Errorf("expected GUID '%s', got '%s'", expected.ID, actual.ID) + } + + if actual.TraceID != expected.TraceID { + t.Errorf("expected TraceID '%s', got '%s'", expected.TraceID, actual.TraceID) + } + + if actual.Name != expected.Name { + t.Errorf("expected Name '%s', got '%s'", expected.Name, actual.Name) + } + + if actual.ParentID != expected.ParentID { + t.Errorf("expected ParentID '%s', got '%s'", expected.ParentID, actual.ParentID) + } + + if actual.Timestamp != expected.Timestamp { + t.Errorf("expected Timestamp '%s', got '%s'", expected.Timestamp, actual.Timestamp) + } + + if actual.Duration != expected.Duration { + t.Errorf("expected Duration '%s', got '%s'", expected.Duration, actual.Duration) + } + + if actual.ServiceName != expected.ServiceName { + t.Errorf("expected ServiceName '%s', got '%s'", expected.ServiceName, actual.ServiceName) + } + + if !reflect.DeepEqual(actual.Attributes, expected.Attributes) { + t.Errorf("expected attributes '%#v', got '%#v'", expected.Attributes, actual.Attributes) + } +}