From 2c6c0c2f8b61f6f021e75f60a591d283e7b55db6 Mon Sep 17 00:00:00 2001 From: Przemek Maciolek Date: Wed, 4 Nov 2020 18:34:15 +0100 Subject: [PATCH] Merge Sumo Logic updates to OTC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge Sumo tail-sampling extensions Update source processor to new API Fix imports order Update deps for OTC v0.14.0 Expose tailsamplingprocessor metrics Config update (#289) CircleCI configuration update and linter fixes Tail sampling processor - update to v0.16 Source processor - update to v0.16 Tailsampling processor order fix Introduce cascading filter processor (#359) Introduce cascading filter processor Switch to AWS ECR (#409) Switch to AWS ECR Investigate issue with image tagging (#410) Move envs into $BASH_ENV AWS CLI orb Update README.md Clarify which span attributes are updated/added and `probablistic_filtering_ratio` behavior Provide invert (like grep -v) match capability (#509) Bump OTC-Sumo version Add Sumo examples (#681) Update README.md Update base dependency to v0.19.0-sumo Remove uneccesary return values per linter Switch master -> main Address linter errors in sourceprocessor Use cloud.namespace on AWS resourcedetection Update ec2.go TRACING-1684 | Change cloud.namespace tag from "ec2" to "aws/ec2". TRACING-1684 | Test fix Switch examples to use OTLP/HTTP (#864) Bump SumoLogic OTC base version Fix cascadingfilterprocessor unit test Introduce telegrafreceiver (#935) Increase testbed memory limits AWS OTel Collector templates (#788) Add AWS OTel Collector config templates Update core version to v0.22.0 AWS Distro for Opentelemetry collector configuration file (#983) Add AWS Distro Collector config Change endpoint, add insecure flag Update readme Update Telegraf for changes in core v0.22.0 Rebase on v0.24.0 upstream changes Add opentelemetry-collector-builder with Makefile and Github Action to build on PR (#1300) Add telegrafreceiver to opentelemetry-collector-builder config Add tracing-tests into pipeline (#1302) Install fluentbit (#1312) Add publish-check for all tags and branches Fix CI to actually produce artifacts for tags Update config.yml (#1412) Update aws-otel-config-file.yaml (#1537) Add resourcedetection Add additional receivers Sumo Logic Syslog Processor (#1313) add sumologicsyslogprocessor Signed-off-by: Dominik Rosiek Co-authored-by: Patryk Małek <69143962+pmalek-sumo@users.noreply.github.com> Update k8sprocessor with recent changes from sumologic-otel-collector --- .circleci/config.yml | 389 ++++++--- .../opentelemetry-collector-builder.yaml | 49 ++ .gitignore | 4 + Makefile | 2 +- README.md | 124 +-- Vagrantfile | 17 + examples/README.md | 71 ++ ...-distro-collector-lambda-layer-config.yaml | 16 + .../custom-values-cascading-filter.yaml | 73 ++ examples/kubernetes/custom-values.yaml | 16 + .../agent-configuration-template.yaml | 70 ++ .../non-kubernetes/aws-otel-config-file.yaml | 42 + .../aws-otel-ec2-deployment.yaml | 242 ++++++ .../aws-otel-ecs-ec2-deployment.yaml | 127 +++ .../aws-otel-ecs-fargate-deployment.yaml | 153 ++++ ...ration-template-with-cascading-filter.yaml | 104 +++ .../gateway-configuration-template.yaml | 62 ++ go.mod | 13 +- internal/components/components.go | 6 + otelcolbuilder/.gitignore | 1 + otelcolbuilder/.otelcol-builder.yaml | 39 + otelcolbuilder/Makefile | 8 + processor/cascadingfilterprocessor/Makefile | 1 + processor/cascadingfilterprocessor/README.md | 131 +++ .../big_endian_converter.go | 47 + .../big_endian_converter_test.go | 67 ++ .../cascading_test.go | 154 ++++ .../cascadingfilterprocessor/config/config.go | 89 ++ .../cascadingfilterprocessor/config_test.go | 100 +++ processor/cascadingfilterprocessor/factory.go | 77 ++ .../cascadingfilterprocessor/factory_test.go | 51 ++ processor/cascadingfilterprocessor/go.mod | 12 + processor/cascadingfilterprocessor/go.sum | 804 ++++++++++++++++++ .../idbatcher/id_batcher.go | 141 +++ .../idbatcher/id_batcher_test.go | 161 ++++ processor/cascadingfilterprocessor/metrics.go | 150 ++++ .../cascadingfilterprocessor/processor.go | 633 ++++++++++++++ .../processor_test.go | 575 +++++++++++++ .../sampling/always_sample_test.go | 46 + .../cascadingfilterprocessor/sampling/doc.go | 17 + .../sampling/empty_test.go | 15 + .../sampling/numeric_tag_filter_test.go | 111 +++ .../sampling/policy.go | 76 ++ .../sampling/policy_factory.go | 131 +++ .../sampling/policy_filter.go | 225 +++++ .../sampling/rate_limiting_test.go | 64 ++ .../sampling/span_properties_filter_test.go | 168 ++++ .../sampling/string_tag_filter_test.go | 109 +++ .../testdata/cascading_filter_config.yaml | 61 ++ processor/k8sprocessor/README.md | 319 ++++++- processor/k8sprocessor/client_test.go | 34 +- processor/k8sprocessor/config.go | 84 +- processor/k8sprocessor/config_test.go | 43 +- processor/k8sprocessor/doc.go | 26 - processor/k8sprocessor/factory.go | 61 +- processor/k8sprocessor/factory_test.go | 3 +- processor/k8sprocessor/go.mod | 56 +- processor/k8sprocessor/go.sum | 10 +- processor/k8sprocessor/kube/client.go | 268 +++--- processor/k8sprocessor/kube/client_test.go | 462 +++++----- processor/k8sprocessor/kube/fake_informer.go | 75 -- processor/k8sprocessor/kube/fake_owner.go | 90 ++ processor/k8sprocessor/kube/informer.go | 33 - processor/k8sprocessor/kube/informer_test.go | 39 - processor/k8sprocessor/kube/kube.go | 135 ++- processor/k8sprocessor/kube/owner.go | 334 ++++++++ .../observability/observability.go | 97 +-- .../observability/observability_test.go | 52 +- processor/k8sprocessor/options.go | 182 ++-- processor/k8sprocessor/options_test.go | 114 +-- processor/k8sprocessor/pod_association.go | 81 +- processor/k8sprocessor/processor.go | 52 +- processor/k8sprocessor/processor_test.go | 45 +- processor/k8sprocessor/testdata/config.yaml | 55 +- .../internal/aws/ec2/ec2.go | 1 + .../internal/aws/ec2/ec2_test.go | 3 +- .../internal/aws/ecs/ecs.go | 1 + .../internal/aws/ecs/ecs_test.go | 6 +- .../aws/elasticbeanstalk/elasticbeanstalk.go | 1 + .../elasticbeanstalk/elasticbeanstalk_test.go | 3 +- processor/sourceprocessor/Makefile | 1 + processor/sourceprocessor/README.md | 73 ++ processor/sourceprocessor/attribute_filler.go | 123 +++ processor/sourceprocessor/config.go | 46 + processor/sourceprocessor/config_test.go | 73 ++ processor/sourceprocessor/doc.go | 17 + processor/sourceprocessor/factory.go | 137 +++ processor/sourceprocessor/go.mod | 10 + processor/sourceprocessor/go.sum | 792 +++++++++++++++++ .../observability/empty_test.go | 15 + .../observability/observability.go | 88 ++ processor/sourceprocessor/source_processor.go | 344 ++++++++ .../sourceprocessor/source_processor_test.go | 326 +++++++ .../sourceprocessor/testdata/config.yaml | 39 + processor/sumologicsyslogprocessor/Makefile | 1 + processor/sumologicsyslogprocessor/README.md | 30 + processor/sumologicsyslogprocessor/config.go | 31 + .../sumologicsyslogprocessor/config_test.go | 44 + processor/sumologicsyslogprocessor/factory.go | 66 ++ .../sumologicsyslogprocessor/factory_test.go | 44 + processor/sumologicsyslogprocessor/go.mod | 10 + processor/sumologicsyslogprocessor/go.sum | 803 +++++++++++++++++ .../sumologicsyslogprocessor/processor.go | 121 +++ .../processor_test.go | 66 ++ .../testdata/sumologic_syslog_config.yaml | 16 + vagrant/build.sh | 8 + vagrant/provision.sh | 18 + 107 files changed, 10533 insertions(+), 1418 deletions(-) create mode 100644 .github/workflows/opentelemetry-collector-builder.yaml create mode 100644 Vagrantfile create mode 100644 examples/README.md create mode 100644 examples/aws_lambda/aws-distro-collector-lambda-layer-config.yaml create mode 100644 examples/kubernetes/custom-values-cascading-filter.yaml create mode 100644 examples/kubernetes/custom-values.yaml create mode 100644 examples/non-kubernetes/agent-configuration-template.yaml create mode 100644 examples/non-kubernetes/aws-otel-config-file.yaml create mode 100644 examples/non-kubernetes/aws-otel-ec2-deployment.yaml create mode 100644 examples/non-kubernetes/aws-otel-ecs-ec2-deployment.yaml create mode 100644 examples/non-kubernetes/aws-otel-ecs-fargate-deployment.yaml create mode 100644 examples/non-kubernetes/gateway-configuration-template-with-cascading-filter.yaml create mode 100644 examples/non-kubernetes/gateway-configuration-template.yaml create mode 100644 otelcolbuilder/.gitignore create mode 100644 otelcolbuilder/.otelcol-builder.yaml create mode 100644 otelcolbuilder/Makefile create mode 100644 processor/cascadingfilterprocessor/Makefile create mode 100644 processor/cascadingfilterprocessor/README.md create mode 100644 processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter.go create mode 100644 processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter_test.go create mode 100644 processor/cascadingfilterprocessor/cascading_test.go create mode 100644 processor/cascadingfilterprocessor/config/config.go create mode 100644 processor/cascadingfilterprocessor/config_test.go create mode 100644 processor/cascadingfilterprocessor/factory.go create mode 100644 processor/cascadingfilterprocessor/factory_test.go create mode 100644 processor/cascadingfilterprocessor/go.mod create mode 100644 processor/cascadingfilterprocessor/go.sum create mode 100644 processor/cascadingfilterprocessor/idbatcher/id_batcher.go create mode 100644 processor/cascadingfilterprocessor/idbatcher/id_batcher_test.go create mode 100644 processor/cascadingfilterprocessor/metrics.go create mode 100644 processor/cascadingfilterprocessor/processor.go create mode 100644 processor/cascadingfilterprocessor/processor_test.go create mode 100644 processor/cascadingfilterprocessor/sampling/always_sample_test.go create mode 100644 processor/cascadingfilterprocessor/sampling/doc.go create mode 100644 processor/cascadingfilterprocessor/sampling/empty_test.go create mode 100644 processor/cascadingfilterprocessor/sampling/numeric_tag_filter_test.go create mode 100644 processor/cascadingfilterprocessor/sampling/policy.go create mode 100644 processor/cascadingfilterprocessor/sampling/policy_factory.go create mode 100644 processor/cascadingfilterprocessor/sampling/policy_filter.go create mode 100644 processor/cascadingfilterprocessor/sampling/rate_limiting_test.go create mode 100644 processor/cascadingfilterprocessor/sampling/span_properties_filter_test.go create mode 100644 processor/cascadingfilterprocessor/sampling/string_tag_filter_test.go create mode 100644 processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml create mode 100644 processor/k8sprocessor/kube/fake_owner.go create mode 100644 processor/k8sprocessor/kube/owner.go create mode 100644 processor/sourceprocessor/Makefile create mode 100644 processor/sourceprocessor/README.md create mode 100644 processor/sourceprocessor/attribute_filler.go create mode 100644 processor/sourceprocessor/config.go create mode 100644 processor/sourceprocessor/config_test.go create mode 100644 processor/sourceprocessor/doc.go create mode 100644 processor/sourceprocessor/factory.go create mode 100644 processor/sourceprocessor/go.mod create mode 100644 processor/sourceprocessor/go.sum create mode 100644 processor/sourceprocessor/observability/empty_test.go create mode 100644 processor/sourceprocessor/observability/observability.go create mode 100644 processor/sourceprocessor/source_processor.go create mode 100644 processor/sourceprocessor/source_processor_test.go create mode 100644 processor/sourceprocessor/testdata/config.yaml create mode 100644 processor/sumologicsyslogprocessor/Makefile create mode 100644 processor/sumologicsyslogprocessor/README.md create mode 100644 processor/sumologicsyslogprocessor/config.go create mode 100644 processor/sumologicsyslogprocessor/config_test.go create mode 100644 processor/sumologicsyslogprocessor/factory.go create mode 100644 processor/sumologicsyslogprocessor/factory_test.go create mode 100644 processor/sumologicsyslogprocessor/go.mod create mode 100644 processor/sumologicsyslogprocessor/go.sum create mode 100644 processor/sumologicsyslogprocessor/processor.go create mode 100644 processor/sumologicsyslogprocessor/processor_test.go create mode 100644 processor/sumologicsyslogprocessor/testdata/sumologic_syslog_config.yaml create mode 100755 vagrant/build.sh create mode 100755 vagrant/provision.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index f55d3007a3aa..03cce7a0ec55 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,26 +1,3 @@ -# Using Contexts: -# some jobs depend on secrets like API tokens to work correctly such as publishing to docker hub -# or reporting issues to GitHub. All such tokens are stored in CircleCI contexts (https://circleci.com/docs/2.0/contexts). -# -# All tokens stored in a contexts are injected into a job as environment variables IF the pipeline that runs the job -# explicitly enables the context for the job. -# -# Contexts are protected with security groups. Jobs that use contexts will not run for commits from people who are not -# part of the approved security groups for the given context. This means that contributors who are not part of the -# OpenTelemetry GitHub organisation will not be able to run jobs that depend on contexts. As a result, PR pipelines -# should never depend on any contexts and never use any tokens/secrets. -# -# This CI pipeline uses two contexts: -# - github-release-and-issues-api-token -# This context makes GITHUB_TOKEN available to jobs. Jobs can use the token to authenticate with the GitHub API. -# We use this to report failures as issues back to the GitHub project. -# Any member of the OpenTelemetry GitHub organisation can run jobs that require this context e.g, loadtest-with-github-reports. -# -# - dockerhub-token -# This contexts makes DOCKER_HUB_USERNAME and DOCKER_HUB_PASSWORD environment variables available to the jobs. -# This is used to publish docker images to Docker Hub. -# Only project approvers and maintainers can run jobs that depend on this context such e.g, publish-stable. - version: 2.1 parameters: @@ -36,11 +13,13 @@ parameters: orbs: win: circleci/windows@2.4.0 + aws-cli: circleci/aws-cli@1.3.1 + kubernetes: circleci/kubernetes@0.11.2 executors: golang: docker: - - image: cimg/go:1.17 + - image: cimg/go:1.14 machine: machine: image: ubuntu-1604:201903-01 @@ -51,7 +30,6 @@ commands: files: type: string default: | - bin/otelcontribcol_darwin_arm64 bin/otelcontribcol_darwin_amd64 bin/otelcontribcol_linux_arm64 bin/otelcontribcol_linux_amd64 @@ -63,7 +41,7 @@ commands: dist/otel-contrib-collector-*amd64.msi steps: - run: - name: Check if files exist + name: Check if distribution files exist command: | files="<< parameters.files >>" for f in $files; do @@ -74,6 +52,7 @@ commands: fi done + setup: steps: - checkout @@ -84,15 +63,18 @@ commands: - run: name: Install tools command: make install-tools + - run: + name: Install testbed tools + command: make -C testbed install-tools - save_module_cache setup_go: steps: - run: - name: Install Go 1.17 + name: Install Go 1.14 command: | sudo rm -rf /usr/local/go - curl -L https://golang.org/dl/go1.17.linux-amd64.tar.gz | sudo tar xz -C /usr/local + curl -L https://dl.google.com/go/go1.14.4.linux-amd64.tar.gz | sudo tar xz -C /usr/local - run: name: Add ~/go/bin to PATH command: | @@ -111,7 +93,7 @@ commands: save_module_cache: steps: - save_cache: - key: cimg-go-pkg-mod-{{ arch }}-{{ checksum "go.sum" }}-{{ checksum "internal/tools/go.sum" }} + key: cimg-go-pkg-mod-{{ arch }}-{{ checksum "go.sum" }}-v4 paths: - "/home/circleci/go/pkg/mod" @@ -122,7 +104,7 @@ commands: command: mkdir -p ~/go/pkg/mod - restore_cache: # restores saved cache if no changes are detected since last run keys: - - cimg-go-pkg-mod-{{ arch }}-{{ checksum "go.sum" }}-{{ checksum "internal/tools/go.sum" }} + - cimg-go-pkg-mod-{{ arch }}-{{ checksum "go.sum" }}-v4 install_fluentbit: steps: @@ -150,43 +132,28 @@ commands: tag: type: string steps: + - run: + name: Setup Environment Variables + command: | + echo "export REGISTRY=public.ecr.aws/sumologic" >> $BASH_ENV + echo "export TAG_URL=public.ecr.aws/sumologic/<< parameters.repo >>:<< parameters.tag >>" >> $BASH_ENV + echo "export LATEST_URL=public.ecr.aws/sumologic/<< parameters.repo >>:latest" >> $BASH_ENV - run: name: Build image command: | make docker-otelcontribcol - docker tag otelcontribcol:latest otel/<< parameters.repo >>:<< parameters.tag >> - docker tag otelcontribcol:latest otel/<< parameters.repo >>:latest + docker tag otelcontribcol:latest ${TAG_URL} + docker tag otelcontribcol:latest ${LATEST_URL} + - aws-cli/install - run: - name: Login to Docker Hub - command: docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD + name: Login to AWS ECR + command: aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${REGISTRY} - run: name: Push image command: | - docker push otel/<< parameters.repo >>:<< parameters.tag >> - docker push otel/<< parameters.repo >>:latest + docker push ${TAG_URL} + docker push ${LATEST_URL} - github_issue_generator: - steps: - - when: - condition: - equal: [main, << pipeline.git.branch >>] - steps: - - run: - name: Generate GitHub Issue - command: issuegenerator ${TEST_RESULTS} - when: on_fail - - run_loadtest: - steps: - - restore_workspace - - install_fluentbit - - run: - name: Loadtest - command: TEST_ARGS="-test.run=$(make -C testbed -s list-loadtest-tests | circleci tests split|xargs echo|sed 's/ /|/g')" make e2e-test - - store_artifacts: - path: testbed/tests/results - - store_test_results: - path: testbed/tests/results/junit workflows: version: 2 @@ -201,92 +168,94 @@ workflows: requires: - setup - run-stability-tests: - context: - - github-release-and-issues-api-token requires: - - cross-compile + - cross-compile - publish-dev: - context: - - dockerhub-token requires: - run-stability-tests + filters: + branches: + only: /.*/ + tags: + only: /.*/ + - run-tracing-tests: + repo: opentelemetry-collector-dev + tag: ${CIRCLE_SHA1} + requires: + - publish-dev + filters: + branches: + only: /.*/ + tags: + only: /.*/ + build-publish: when: << pipeline.parameters.run-build-publish >> jobs: + - windows-test: + filters: + tags: + only: /.*/ - setup: filters: tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ - - build-examples: + only: /.*/ + - lint: requires: - setup filters: tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ - - cross-compile: + only: /.*/ + - build-examples: requires: - setup filters: tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ - - loadtest-with-github-reports: - context: - - github-release-and-issues-api-token + only: /.*/ + - cross-compile: requires: - - cross-compile + - setup filters: - branches: - only: main tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ + only: /.*/ - loadtest: requires: - - cross-compile + - cross-compile filters: - branches: - ignore: main - - windows-msi: + tags: + only: /.*/ + - unit-tests: requires: - - cross-compile + - setup filters: tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ - # this publish-check step only runs on the main branch. - # it is identical to the other publish-check step in all ways except - # it runs loadtest-with-github-reports instead of loadtest. - # This is because these jobs can access the GITHUB_TOKEN secret which is not available to PR builds. - - publish-check: + only: /.*/ + - windows-msi: requires: - cross-compile - - loadtest-with-github-reports - - windows-msi - - deb-package - - rpm-package filters: - branches: - only: main - # this publish-check step run for PR builds (all branches except main). - # it runs the same jobs as the previous public-check step but - # it uses the versions that do not need access to the - # GITHUB_TOKEN secret. + tags: + only: /.*/ - publish-check: requires: + - lint + - unit-tests + - integration-tests - cross-compile - loadtest + - windows-test - windows-msi - deb-package - rpm-package - filters: - branches: - ignore: main - publish-stable: - context: - - github-release-and-issues-api-token - - dockerhub-token requires: + - lint + - unit-tests + - integration-tests - cross-compile - - loadtest-with-github-reports + - loadtest + - windows-test - windows-msi - deb-package - rpm-package @@ -297,13 +266,21 @@ workflows: only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ - spawn-stability-tests-job: requires: - - loadtest-with-github-reports + - lint + - unit-tests + - loadtest + - windows-test + - integration-tests - cross-compile filters: branches: - only: /main|release\/.+/ + only: /main|release|tracing\/.+/ tags: - ignore: /.*/ + only: /.*/ + - integration-tests: + filters: + tags: + only: /.*/ - build-package: name: deb-package package_type: deb @@ -311,7 +288,7 @@ workflows: - cross-compile filters: tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ + only: /.*/ - build-package: name: rpm-package package_type: rpm @@ -319,7 +296,8 @@ workflows: - cross-compile filters: tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ + only: /.*/ + jobs: setup: @@ -331,10 +309,20 @@ jobs: paths: - project - go/bin + lint: + executor: golang + steps: + - restore_workspace + - run: + name: Lint + command: make -j2 for-all-target TARGET=lint + - run: + name: Checks + command: make -j4 checklicense impi misspell build-examples: docker: - - image: cimg/go:1.17 + - image: cimg/go:1.14 steps: - restore_workspace - setup_remote_docker @@ -350,31 +338,55 @@ jobs: - run: name: Build collector for all archs command: grep ^otelcontribcol-all-sys Makefile|fmt -w 1|tail -n +2|circleci tests split|xargs make - - run: - name: Log checksums to console - command: shasum -a 256 bin/* - persist_to_workspace: root: ~/ paths: project/bin - loadtest-with-github-reports: + unit-tests: executor: golang - parallelism: 6 - resource_class: medium+ - environment: - TEST_RESULTS: testbed/tests/results/junit/results.xml steps: - - run_loadtest - - github_issue_generator + - restore_workspace + - run: + name: Unit test coverage + command: make unit-tests-with-cover + - run: + name: Upload unit test coverage + command: bash <(curl -s https://codecov.io/bash) -F unit loadtest: executor: golang - parallelism: 6 resource_class: medium+ environment: TEST_RESULTS: testbed/tests/results/junit/results.xml steps: - - run_loadtest + - restore_workspace + - install_fluentbit + - run: + name: Loadtest + command: make e2e-test + - store_artifacts: + path: testbed/tests/results + - store_test_results: + path: testbed/tests/results/junit + + windows-test: + executor: + name: win/default + shell: powershell.exe + environment: + GOPATH=~/go + steps: + - checkout + - restore_module_cache + - run: + name: Upgrade golang + command: | + choco upgrade golang --version=1.15 + refreshenv + - run: + name: Unit tests + command: (Get-Childitem -Include go.mod -Recurse) | ForEach-Object { cd (Split-Path $_ -Parent); go test ./...; if ($LastExitCode -gt 0) { exit $LastExitCode } } + - save_module_cache windows-msi: executor: @@ -401,7 +413,7 @@ jobs: publish-check: docker: - - image: cimg/go:1.17 + - image: cimg/go:1.14 steps: - attach_to_workspace - setup_remote_docker @@ -415,17 +427,15 @@ jobs: command: echo "publish check failed. This means release CI jobs will likely fail as well" when: on_fail - # any pipeline using this job must enable "github-release-and-issues-api-token" - # and "dockerhub-token" contexts publish-stable: docker: - - image: cimg/go:1.17 + - image: cimg/go:1.14 steps: - restore_workspace - verify_dist_files_exist - setup_remote_docker - publish_docker_images: - repo: opentelemetry-collector-contrib + repo: opentelemetry-collector tag: ${CIRCLE_TAG:1} - run: name: Prepare release artifacts @@ -438,8 +448,6 @@ jobs: name: Create Github release and upload artifacts command: ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME --replace $CIRCLE_TAG dist/ - # any pipeline using this job must enable "github-release-and-issues-api-token" - # and "dockerhub-token" contexts publish-dev: executor: golang steps: @@ -452,7 +460,7 @@ jobs: bin/otelcontribcol_windows_amd64.exe - setup_remote_docker - publish_docker_images: - repo: opentelemetry-collector-contrib-dev + repo: opentelemetry-collector-dev tag: ${CIRCLE_SHA1} spawn-stability-tests-job: @@ -461,10 +469,15 @@ jobs: - run: name: Trigger stability tests job command: | - curl -f -X POST "https://circleci.com/api/v2/project/github/open-telemetry/${CIRCLE_PROJECT_REPONAME}/pipeline?circle-token=${CIRCLE_API_TOKEN}" \ + PARAM='"branch": "'"${CIRCLE_BRANCH}"'"' + if [ -z "$CIRCLE_BRANCH" ]; then + PARAM='"tag": "'"${CIRCLE_TAG}"'"' + fi + curl -f -X POST "https://circleci.com/api/v2/project/github/SumoLogic/${CIRCLE_PROJECT_REPONAME}/pipeline" \ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ - -d '{"parameters": {"run-build-publish": false, "run-stability-tests": true, "collector-sha": "'"${CIRCLE_SHA1}"'"}, "branch": "'"${CIRCLE_BRANCH}"'"}' + -H "Circle-Token: ${CIRCLE_API_TOKEN}" \ + -d '{"parameters": {"run-build-publish": false, "run-stability-tests": true, "collector-sha": "'"${CIRCLE_SHA1}"'"}, '"${PARAM}"'}' checkout-commit: executor: golang @@ -476,14 +489,12 @@ jobs: git checkout << pipeline.parameters.collector-sha >> git status - # this jobs reports failures as github issues and as a result, any pipeline using this job - # must enable "github-release-and-issues-api-token" context run-stability-tests: parameters: # Number of runners must be always in sync with number of stability tests, # so every node runs exactly one stability test. runners-number: - type: integer + type: integer default: 9 executor: golang resource_class: medium+ @@ -508,7 +519,51 @@ jobs: path: testbed/stabilitytests/results - store_test_results: path: testbed/stabilitytests/results/junit - - github_issue_generator + - run: + name: Run on fail status + command: | + curl --request POST \ + --url https://api.github.com/repos/SumoLogic/opentelemetry-collector-contrib/issues \ + --header "authorization: Bearer ${GITHUB_TOKEN}" \ + --header "content-type: application/json" \ + --data '{ + "title": "Stability tests failed in branch '"${CIRCLE_BRANCH}"' for commit << pipeline.parameters.collector-sha >>", + "body": "Link to failed job: '"${CIRCLE_BUILD_URL}"'." + }' + when: on_fail + + integration-tests: + executor: machine + environment: + GOPATH: /home/circleci/go + steps: + - setup_go + - setup + - run: + name: Integration tests with coverage + command: | + mkdir -p test-results/junit + trap "go-junit-report -set-exit-code < test-results/go-integration-tests.out > test-results/junit/results.xml" EXIT + make integration-tests-with-cover | tee test-results/go-integration-tests.out + - run: + name: Upload integration test coverage + command: bash <(curl -s https://codecov.io/bash) -F integration + - store_test_results: + path: test-results/junit + - store_artifacts: + path: test-results + - run: + name: Run on fail status + command: | + curl --request POST \ + --url https://api.github.com/repos/SumoLogic/opentelemetry-collector-contrib/issues \ + --header "authorization: Bearer ${GITHUB_TOKEN}" \ + --header "content-type: application/json" \ + --data '{ + "title": "Stability tests failed in branch '"${CIRCLE_BRANCH}"' for commit << pipeline.parameters.collector-sha >>", + "body": "Link to failed job: '"${CIRCLE_BUILD_URL}"'." + }' + when: on_fail build-package: machine: @@ -543,3 +598,67 @@ jobs: - persist_to_workspace: root: ~/ paths: project/dist/*.<< parameters.package_type >> + + run-tracing-tests: + parameters: + repo: + type: string + tag: + type: string + docker: + - image: ${INFRASTRUCTURE_ACCOUNT_ID}.dkr.ecr.us-west-2.amazonaws.com/tracing-tests/tests:latest + aws_auth: + aws_access_key_id: ${TRACING_TESTS_AWS_ACCESS_KEY_ID} + aws_secret_access_key: ${TRACING_TESTS_AWS_SECRET_ACCESS_KEY} + steps: + - run: + name: "Configure environment variables" + command: | + echo "export SUMO_API_ENDPOINT=${TRACING_TESTS_SUMO_API_ENDPOINT}" >> $BASH_ENV + echo "export OTELCOL_HEALTHCHECK_URL=${TRACING_TESTS_OTELCOL_URL}" >> $BASH_ENV + echo "export SUMO_ACCESS_ID=${TRACING_TESTS_SUMO_ACCESS_ID}" >> $BASH_ENV + echo "export SUMO_ACCESS_KEY=${TRACING_TESTS_SUMO_ACCESS_KEY}" >> $BASH_ENV + echo "export PYTHONWARNINGS=ignore:Unverified HTTPS request" >> $BASH_ENV + echo "export AWS_ACCESS_KEY_ID=${TRACING_TESTS_CLUSTER_AWS_ACCESS_ID}" >> $BASH_ENV + echo "export AWS_SECRET_ACCESS_KEY=${TRACING_TESTS_CLUSTER_AWS_ACCESS_KEY}" >> $BASH_ENV + - kubernetes/install-kubeconfig: + kubeconfig: TRACING_TESTS_CLUSTER_KUBECONFIG_DATA + - kubernetes/install-kubectl + - aws-cli/install + - run: + name: "Clean up environment" + command: /opt/tracing-tests/deployment_scripts/clean-up-env.sh + - run: + name: "Deploy Sumologic OpenTelemetry Collector" + command: /opt/tracing-tests/deployment_scripts/deploy-otelcol.sh << parameters.repo >> << parameters.tag >> + - run: + name: "Wait for Sumologic OpenTelemetry Collector to be available" + command: kubectl -n java-app wait --for=condition=ready --timeout=120s pod -l app=otelcol + - run: + name: "Deploy ECR Registry Secret" + command: /opt/tracing-tests/deployment_scripts/deploy-ecr-registry-secret.sh + - run: + name: "Deploy Kubernetes Metadata Provider application" + command: /opt/tracing-tests/deployment_scripts/deploy-k8sfeeder.sh + - run: + name: "Wait for Kubernetes Metadata Provider" + command: kubectl -n java-app wait --for=condition=ready --timeout=60s pod -l app=k8s-feeder + - run: + name: "Get Kubernetes Metadata provider URL" + command: echo "export KUBERNETES_METADATA_URL=$(kubectl -n java-app get svc k8s-feeder-svc-pub -o json | jq .status.loadBalancer.ingress[0].hostname)" >> $BASH_ENV + - run: + name: "Deploy Java App application" + command: /opt/tracing-tests/deployment_scripts/deploy-test-applications.sh + - run: + name: "Wait for Kubernetes Metadata Provider" + command: kubectl -n java-app wait --for=condition=ready --timeout=60s pod -l app=server + - run: + name: "Wait for data..." + command: sleep 180 + - run: + name: "Execute Tracing Tests" + command: "pytest --rootdir=/opt/tracing-tests --junitxml=/opt/tracing-tests/test-results/junit.xml --html=/opt/tracing-tests/test-results/report.html --self-contained-html -vvv /opt/tracing-tests/tests" + - store_test_results: + path: /opt/tracing-tests/test-results + - store_artifacts: + path: /opt/tracing-tests/test-results diff --git a/.github/workflows/opentelemetry-collector-builder.yaml b/.github/workflows/opentelemetry-collector-builder.yaml new file mode 100644 index 000000000000..d7a4618e34e2 --- /dev/null +++ b/.github/workflows/opentelemetry-collector-builder.yaml @@ -0,0 +1,49 @@ +name: OpenTelemetry Collector Builder + +on: + pull_request: + branches: + - main + +jobs: + # otelcolbuilder_post_go1_16: + # runs-on: ubuntu-20.04 + # strategy: + # matrix: + # go: [ '1.16' ] + # steps: + # - uses: actions/checkout@v2 + # - name: Setup go + # uses: actions/setup-go@v2 + # with: + # go-version: ${{ matrix.go }} + # - name: Print go version + # run: go version + # - name: Build OpenTelemetry distro + # working-directory: ./otelcolbuilder/ + # run: | + # go install github.com/open-telemetry/opentelemetry-collector-builder@v0.24.0 + # make build + + # Just build on 1.15 for now because of a weird issue: + # https://github.com/actions/setup-go/issues/107 + otelcolbuilder_pre_go1_16: + runs-on: ubuntu-20.04 + strategy: + matrix: + go: [ '1.15' ] + steps: + - uses: actions/checkout@v2 + - name: Setup go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - name: Print go version + run: go version + - name: Print go env + run: go env + - name: Build OpenTelemetry distro + working-directory: ./otelcolbuilder/ + run: | + make install-prego1.16 + make build diff --git a/.gitignore b/.gitignore index c55af97cdace..0774532d4860 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,10 @@ integration-coverage.txt coverage.html integration-coverage.html +# vagrant +.vagrant +ubuntu-bionic-18.04-cloudimg-console.log + # Wix *.wixobj *.wixpdb diff --git a/Makefile b/Makefile index 0dc7c0e45af1..b17501acc54d 100644 --- a/Makefile +++ b/Makefile @@ -171,7 +171,7 @@ run: docker-component: check-component GOOS=linux GOARCH=amd64 $(MAKE) $(COMPONENT) cp ./bin/$(COMPONENT)_linux_amd64 ./cmd/$(COMPONENT)/$(COMPONENT) - docker build -t $(COMPONENT) ./cmd/$(COMPONENT)/ + docker build -t $(COMPONENT) -f ./cmd/$(COMPONENT)/Dockerfile . rm ./cmd/$(COMPONENT)/$(COMPONENT) .PHONY: check-component diff --git a/README.md b/README.md index 7d1606c5fa68..96863a10e863 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,28 @@ ---- +# SumoLogic / OpenTelemetry Collector Contrib +This is a repository for OpenTelemetry Collector Contrib with additional Sumo Logic extensions. It is based +on the [opentelemetry-collector-contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib) and +[SumoLogic flavor](https://github.com/SumoLogic/opentelemetry-collector) of +[core distribution of the Collector](https://github.com/open-telemetry/opentelemetry-collector). -

- - Getting Started -   •   - Getting Involved -   •   - Getting In Touch - -

-

- - Go Report Card - - - Build Status - - - Codecov Status - - - GitHub release (latest by date including pre-releases) - - Beta -

+## Docker Images +Docker images for all releases are published at https://hub.docker.com/r/sumologic/opentelemetry-collector -

- - Vision -   •   - Design -   •   - Monitoring -   •   - Performance -   •   - Security -   •   - Roadmap - -

+### Building docker locally ---- +``` +docker build -f cmd/otelcontribcol/Dockerfile -t otelcontribcol . +``` -# OpenTelemetry Collector Contrib +## Differences from the core release -This is a repository for OpenTelemetry Collector contributions that are not -part of the [core -distribution](https://github.com/open-telemetry/opentelemetry-collector) of the -Collector. Typically, these contributions are vendor specific -receivers/exporters and/or components that are only useful to a relatively -small number of users. +SumoLogic version of OpenTelemetry Collector introduces a number of additions over the plain version: -> Please note that this repository and its releases are a superset of the core repository. - -## Contributing - -See [CONTRIBUTING.md](CONTRIBUTING.md). - -Triagers ([@open-telemetry/collector-contrib-triagers](https://github.com/orgs/open-telemetry/teams/collector-contrib-triagers)) -- [Alolita Sharma](https://github.com/alolita), AWS -- [Punya Biswal](https://github.com/punya), Google -- [Steve Flanders](https://github.com/flands), Splunk - -Approvers ([@open-telemetry/collector-contrib-approvers](https://github.com/orgs/open-telemetry/teams/collector-contrib-approvers)): - -- [Alex Boten](https://github.com/codeboten), Lightstep -- [Anthony Mirabella](https://github.com/Aneurysm9), AWS -- [Anuraag Agrawal](https://github.com/anuraaga), AWS -- [Daniel Jaglowski](https://github.com/djaglowski), observIQ -- [Dmitrii Anoshin](https://github.com/dmitryax), Splunk -- [Juraci Paixão Kröhling](https://github.com/jpkrohling), Red Hat -- [Kevin Brockhoff](https://github.com/kbrockhoff), Daugherty Business Solutions -- [Pablo Baeyens](https://github.com/mx-psi), DataDog -- [Owais Lone](https://github.com/owais), Splunk - -Maintainers ([@open-telemetry/collector-contrib-maintainer](https://github.com/orgs/open-telemetry/teams/collector-contrib-maintainer)): - -- [Bogdan Drutu](https://github.com/BogdanDrutu), Splunk -- [Tigran Najaryan](https://github.com/tigrannajaryan), Splunk - -Learn more about roles in the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md). - -## PRs and Reviews - -When creating a PR please following the process [described -here](https://github.com/open-telemetry/opentelemetry-collector/blob/main/CONTRIBUTING.md#how-to-structure-prs-to-get-expedient-reviews). - -News PRs will be automatically associated with the reviewers based on -[CODEOWNERS](.github/CODEOWNERS). PRs will be also automatically assigned to one of the -maintainers or approvers for facilitation. - -The facilitator is responsible for helping the PR author and reviewers to make progress -or if progress cannot be made for closing the PR. - -If the reviewers do not have approval rights the facilitator is also responsible -for the official approval that is required for the PR to be merged and if the facilitator -is a maintainer they are responsible for merging the PR as well. - -The facilitator is not required to perform a thorough review, but they are encouraged to -enforce Collector best practices and consistency across the codebase and component -behavior. The facilitators will typically rely on codeowner's detailed review of the code -when making the final approval decision. - -We recommend maintainers and approvers to keep an eye on the -[project board](https://github.com/orgs/open-telemetry/projects/3). All newly created -PRs are automatically added to this board. (If you don't see the PR on the board you -may need to add it manually by setting the Project field in the PR view). +* Extensions to [k8sprocessor](https://github.com/SumoLogic/opentelemetry-collector-contrib/tree/master/processor/k8sprocessor) + which include more tags being collected and field extraction enhancements +* A [sourceprocessor](https://github.com/SumoLogic/opentelemetry-collector-contrib/tree/master/processor/sourceprocessor) that + adds additional tags (mostly relevant to K8s environment) and provides some data filtering rules +* [Cascading filter processor](https://github.com/pmm-sumo/opentelemetry-collector-contrib/tree/remote-conf-poc/processor/cascadingfilterprocessor) + extensions, which include *cascading* policy with two-pass rules for determining span budget for each of the defined rules +* Support for spans in [filtering processor](https://github.com/SumoLogic/opentelemetry-collector/tree/master/processor/filterprocessor) +* Additional release schedule, which allows to quickly introduce bugfixes and extensions diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000000..ae4b149e9fda --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,17 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure('2') do |config| + config.vm.box = 'ubuntu/bionic64' + config.vm.box_check_update = false + config.vm.host_name = 'opentelemetry-collector-contrib' + config.vm.network :private_network, ip: "192.168.33.33" + + config.vm.provider 'virtualbox' do |vb| + vb.gui = false + vb.memory = 4096 + vb.name = 'opentelemetry-collector-contrib' + end + config.vm.provision 'file', source: 'vagrant', destination: 'vagrant' + config.vm.provision 'shell', path: 'vagrant/provision.sh' +end diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000000..9d0804faeb64 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,71 @@ +# Examples +## Kubernetes configuration + +### Helm chart values template +[kubernetes/custom-values.yaml](./kubernetes/custom-values.yaml) contains +an example template for Sumologic Kubernetes Collection Helm chart, which +installs OpenTelemetry Collector in Agent and Gateway configuration, as described +in the [documentation](https://help.sumologic.com/Traces/Getting_Started_with_Transaction_Tracing/Set_up_traces_collection_for_Kubernetes_environments). + +After filling the template values, you can install it following +[Sumologic Kubernetes Collection installation instructions](https://github.com/SumoLogic/sumologic-kubernetes-collection/blob/release-v2.0/deploy/docs/Installation_with_Helm.md) +For example, by running following commands: +```shell +helm repo add sumologic https://sumologic.github.io/sumologic-kubernetes-collection +kubectl create namespace sumologic +helm upgrade --install my-release -n sumologic sumologic/sumologic -f custom-values.yaml +``` + +### Helm chart values template with cascading filter enabled + +Additionally, [kubernetes/custom-values-cascading-filter.yaml](./kubernetes/custom-values-cascading-filter.yaml) +includes an alternative example template that enables cascading filter, +as described in [trace filtering documentation](https://help.sumologic.com/Traces/Getting_Started_with_Transaction_Tracing/What_if_I_don't_want_to_send_all_the_tracing_data_to_Sumo_Logic%3F). +Note that cascading filter is currently supported only for single-instance +OpenTelemetry Collector deployments. + +## Non-kubernetes configuration + +### Agent configuration (should be run on each host/node) +[non-kubernetes/agent-configuration-template.yaml](non-kubernetes/agent-configuration-template.yaml) contains +an OpenTelemetry Collector YAML file which includes configuration +for OpenTelemetry Collector running in Agent mode. It should be +deployed on each host/node within the system. + +### Gateway configuration (should be run per each cluster/data-center/etc.) +[non-kubernetes/gateway-configuration-template.yaml](non-kubernetes/gateway-configuration-template.yaml) contains +an OpenTelemetry Collector YAML file which includes configuration +for OpenTelemetry Collector running in Gateway mode. + +Additionally, for [non-kubernetes/gateway-configuration-template-with-cascading-filter.yaml](non-kubernetes/gateway-configuration-template-with-cascading-filter.yaml) +the configuration also includes cascading filter config, +which is described in more detail in [trace filtering documentation](https://help.sumologic.com/Traces/Getting_Started_with_Transaction_Tracing/What_if_I_don't_want_to_send_all_the_tracing_data_to_Sumo_Logic%3F). + +Please refer to [relevant documentation](https://help.sumologic.com/Traces/Getting_Started_with_Transaction_Tracing/Set_up_traces_collection_for_other_environments) +for more details. + +### AWS OTel Collector configuration file +[non-kubernetes/aws-otel-config.yaml](non-kubernetes/aws-otel-config.yaml) contains +an AWS OpenTelemetry Collector distrubtion YAML file which includes configuration +for OpenTelemetry Collector. Should be deployed on the AWS environments. + +### AWS OTel Collector for ECS in EC2 mode template +[non-kubernetes/aws-otel-ecs-ec2-deployment.yaml](non-kubernetes/aws-otel-ecs-ec2-deployment.yaml) contains +an AWS OpenTelemetry Collector distribution YAML file which includes +CloudFormation template. It should be deployed on the AWS ECS EC2 +environment. + +### AWS OTel Collector for ECS in Fargate mode template +[non-kubernetes/aws-otel-ecs-fargate-deployment.yaml](non-kubernetes/aws-otel-ecs-fargate-deployment.yaml) contains +an AWS OpenTelemetry Collector distribution YAML file which includes +CloudFormation template. It should be deployed on the AWS ECS Fargate +environment. + +### AWS OTel Collector for EC2 deployment template +[non-kubernetes/aws-otel-ec2-deployment.yaml](non-kubernetes/aws-otel-ec2-deployment.yaml) contains +an AWS OpenTelemetry Collector distribution YAML file which includes +CloudFormation template. It should be deployed on the AWS EC2. + +### AWS Distro for OpenTelemetry configuration +[aws_lambda/aws-distro-collector-lambda-layer-config.yaml](aws_lambda/aws-distro-collector-lambda-layer-config.yaml) contains +an [AWS Distro for Opentelemetry Collector](https://github.com/aws-observability/aws-otel-lambda/tree/main/extensions/aoc-extension) YAML file which includes configuration for collector installed in a Lambda Layer. Collector requires *SUMOLOGIC_HTTP_TRACES_ENDPOINT_URL* environment variable to be set. diff --git a/examples/aws_lambda/aws-distro-collector-lambda-layer-config.yaml b/examples/aws_lambda/aws-distro-collector-lambda-layer-config.yaml new file mode 100644 index 000000000000..9b0f7fdfcc02 --- /dev/null +++ b/examples/aws_lambda/aws-distro-collector-lambda-layer-config.yaml @@ -0,0 +1,16 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: "localhost:55680" + +exporters: + otlphttp: + traces_endpoint: $SUMOLOGIC_HTTP_TRACES_ENDPOINT_URL + insecure: true + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [otlphttp] diff --git a/examples/kubernetes/custom-values-cascading-filter.yaml b/examples/kubernetes/custom-values-cascading-filter.yaml new file mode 100644 index 000000000000..c242554ead7b --- /dev/null +++ b/examples/kubernetes/custom-values-cascading-filter.yaml @@ -0,0 +1,73 @@ +sumologic: + accessId: + accessKey: + clusterName: + traces: + enabled: true +## Following enables OpenTelemetry Agent which runs on each node as a DaemonSet +otelagent: + enabled: + true +## Following configures OpenTelemetry Collector (gateway) +## Note that if cascading_filter is used, deployment must include only a single instance +otelcol: + metrics: + ## This enables exposing OpenTelemetry Collector metrics. Note that they will consume your DPM + ## hence by default they are disabled + enabled: + true + config: + processors: + ## Following enables a smart cascading filtering rules with preset limits. + cascading_filter: + ## (default = 30s): Wait time since the first span of a trace before making + ## a filtering decision + decision_wait: 30s + ## (default = 50000): Number of traces kept in memory + num_traces: 50000 + ## (default = 0): Expected number of new traces (helps in allocating data structures) + expected_new_traces_per_sec: 100 + ## (default = 0): defines maximum number of spans per second + spans_per_second: 1600 + ## (default = 0.2): Ratio of spans that are always probabilistically filtered + ## (hence might be used for metrics calculation). + probabilistic_filtering_ratio: 0.2 + ## (no default): Policies used to make a sampling decision + policies: + - name: sampling-priority, + ## string_attribute: allows to specify conditions that need to be met + string_attribute: { + key: sampling.priority, values: [ "1" ] + }, + ## Spans_per_second: max number of emitted spans per second by this policy. + spans_per_second: 500 + - name: everything-else + ## This selects all traces, up the to the global limit + spans_per_second: -1 + ## Following are some examples of other rules that could be used + # - name: extended-duration + # ## Spans_per_second: max number of emitted spans per second by this policy. + # spans_per_second: 500 + # properties: + # ## Selects the span if the duration is greater or equal the given + # ## value (use s or ms as the suffix to indicate unit). + # min_duration: 5s + # - name: "status_code_condition", + # ## Spans_per_second: max number of emitted spans per second by this policy. + # spans_per_second: 500, + # ## numeric_attribute: provides a list of conditions that need to be met + # numeric_attribute: { + # key: "http.status_code", min_value: 400, max_value: 999 + # } + # - name: everything-that-is-not-healthcheck + # ## This selects all traces where there is NO span starting with `health` operation name + # ## If employed, "everything-else" rule must be replaced with it + # properties: + # name_pattern: "^(healthcheck|otherhealthcheck).*" + # invert_match: true + # spans_per_second: -1 + service: + pipelines: + traces: + ## This is required to enable cascading_filter + processors: [memory_limiter, k8s_tagger, source, resource, cascading_filter, batch] diff --git a/examples/kubernetes/custom-values.yaml b/examples/kubernetes/custom-values.yaml new file mode 100644 index 000000000000..801f704aec21 --- /dev/null +++ b/examples/kubernetes/custom-values.yaml @@ -0,0 +1,16 @@ +sumologic: + accessId: + accessKey: + clusterName: + traces: + enabled: true +otelcol: + ## This enables exposing OpenTelemetry Collector metrics. Note that they will consume your DPM + ## hence by default they are disabled + metrics: + enabled: + true +## Following enables OpenTelemetry Agent which runs on each node as a DaemonSet +otelagent: + enabled: + true \ No newline at end of file diff --git a/examples/non-kubernetes/agent-configuration-template.yaml b/examples/non-kubernetes/agent-configuration-template.yaml new file mode 100644 index 000000000000..51de27ff427c --- /dev/null +++ b/examples/non-kubernetes/agent-configuration-template.yaml @@ -0,0 +1,70 @@ +receivers: + jaeger: + protocols: + thrift_compact: + endpoint: "0.0.0.0:6831" + thrift_binary: + endpoint: "0.0.0.0:6832" + grpc: + endpoint: "0.0.0.0:14250" + thrift_http: + endpoint: "0.0.0.0:14268" + opencensus: + endpoint: "0.0.0.0:55678" + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:55681" + zipkin: + endpoint: "0.0.0.0:9411" +processors: + ## The memory_limiter processor is used to prevent out of memory situations on the collector. + memory_limiter: + ## check_interval is the time between measurements of memory usage for the + ## purposes of avoiding going over the limits. Defaults to zero, so no + ## checks will be performed. Values below 1 second are not recommended since + ## it can result in unnecessary CPU consumption. + check_interval: 5s + + ## Maximum amount of memory, in MiB, targeted to be allocated by the process heap. + ## Note that typically the total memory usage of process will be about 50MiB higher + ## than this value. + limit_mib: 500 + + ## Please enable/disable accordingly if on AWS, GCE, ECS, elastic_beanstalk or neither + resourcedetection: + detectors: [ ec2, gce, ecs, elastic_beanstalk ] + timeout: 5s + override: false + + ## The batch processor accepts spans and places them into batches grouped by node and resource + batch: + ## Number of spans after which a batch will be sent regardless of time + send_batch_size: 256 + ## Never more than this many spans are being sent in a batch + send_batch_max_size: 512 + ## Time duration after which a batch will be sent regardless of size + timeout: 5s + +extensions: + health_check: {} +exporters: + otlp: + ## Please enter OpenTelemetry Collector Gateway address here + endpoint: HOSTNAME + insecure: true + ## Following generates verbose logs with span content, useful to verify what + ## metadata is being tagged. To enable, uncomment and add "logging" to exporters below. + ## There are two levels that could be used: `debug` and `info` with the former + ## being much more verbose and including (sampled) spans content + # logging: + # loglevel: debug +service: + extensions: [health_check] + pipelines: + traces: + receivers: [jaeger, opencensus, otlp, zipkin] + processors: [memory_limiter, resourcedetection, batch] + exporters: [otlp] diff --git a/examples/non-kubernetes/aws-otel-config-file.yaml b/examples/non-kubernetes/aws-otel-config-file.yaml new file mode 100644 index 000000000000..75722e5fe8f6 --- /dev/null +++ b/examples/non-kubernetes/aws-otel-config-file.yaml @@ -0,0 +1,42 @@ +extensions: + health_check: +receivers: + awsxray: + endpoint: 0.0.0.0:2000 + transport: udp + jaeger: + protocols: + thrift_compact: + endpoint: 0.0.0.0:6831 + thrift_binary: + endpoint: 0.0.0.0:6832 + grpc: + endpoint: 0.0.0.0:14250 + thrift_http: + endpoint: 0.0.0.0:14268 + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:55681 + zipkin: + endpoint: 0.0.0.0:9411 +processors: + batch/traces: + timeout: 5s + send_batch_size: 256 + resourcedetection: + detectors: [env, ec2, ecs] + timeout: 5s + override: true +exporters: + otlphttp: + endpoint: $SUMO_HTTP_TRACES_URL +service: + extensions: [health_check] + pipelines: + traces: + receivers: [awsxray,jaeger,otlp,zipkin] + processors: [resourcedetection,batch/traces] + exporters: [otlphttp] diff --git a/examples/non-kubernetes/aws-otel-ec2-deployment.yaml b/examples/non-kubernetes/aws-otel-ec2-deployment.yaml new file mode 100644 index 000000000000..6cfef8fcec26 --- /dev/null +++ b/examples/non-kubernetes/aws-otel-ec2-deployment.yaml @@ -0,0 +1,242 @@ +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Template to install AWS OTel Collector on EC2 - Amazon Linux.' +Parameters: + SSHKeyName: + Description: Name of an existing EC2 KeyPair to enable SSH access to the instance + Type: AWS::EC2::KeyPair::KeyName + ConstraintDescription: must be the name of an existing EC2 KeyPair. + InstanceType: + Description: EC2 instance type + Type: String + Default: m4.2xlarge + ConstraintDescription: must be a valid EC2 instance type. + InstanceAMI: + Description: Managed AMI ID for EC2 Instance + Type : String + IAMRole: + Description: EC2 attached IAM role + Type: String + Default: SumologicAWSOTelColRoleEC2 + ConstraintDescription: must be an existing IAM role which will be attached to EC2 instance. + IAMPolicy: + Description: IAM Role attached IAM Managed Policy + Type: String + Default: SumologicAWSOTelColPolicyEC2 + ConstraintDescription: Must be an existing IAM Managed Policy which will be attached to IAM Role. + IAMInstanceProfileName: + Description: IAM Role attached IAM Instance Profile + Type: String + Default: SumologicAWSOTelColRoleEC2 + ConstraintDescription: Must be an existing IAM Instance Profile which will be attached to IAM Role. + SumoHttpTracesURL: + Type: String + Description: Enther the Sumologic HTTP Traces Endpoint URL +Resources: + EC2Instance: + Type: AWS::EC2::Instance + Metadata: + AWS::CloudFormation::Init: + configSets: + default: + - 01_setupCfnHup + - 02_config-aws-otel-collector + - 03_restart-aws-otel-collector + UpdateEnvironment: + - 02_config-aws-otel-collector + - 03_restart-aws-otel-collector + # Definition of YAML configuration of aws-otel-collector, you can change the configuration below. + 02_config-aws-otel-collector: + files: + '/opt/aws/aws-otel-collector/etc/config.yaml': + content: !Sub + - | + extensions: + health_check: + receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:55681 + awsxray: + endpoint: 0.0.0.0:2000 + transport: udp + processors: + batch/traces: + timeout: 1s + send_batch_size: 50 + batch/metrics: + timeout: 60s + exporters: + otlphttp: + endpoint: ${sumo_http_traces_url} + service: + extensions: [health_check] + pipelines: + traces: + receivers: [otlp,awsxray] + processors: [batch/traces] + exporters: [otlphttp] + metrics: + receivers: [otlp] + processors: [batch/metrics] + exporters: [otlphttp] + - sumo_http_traces_url: !Ref SumoHttpTracesURL + # Invoke aws-otel-collector-ctl to restart aws-otel-collector. + 03_restart-aws-otel-collector: + commands: + 01_stop_service: + command: sudo /opt/aws/aws-otel-collector/bin/aws-otel-collector-ctl -a stop + 02_start_service: + command: sudo /opt/aws/aws-otel-collector/bin/aws-otel-collector-ctl -a start + # Cfn-hup setting, it is to monitor the change of metadata. + # When there is change in the contents of json file in the metadata section, cfn-hup will call cfn-init to restart aws-otel-collector. + 01_setupCfnHup: + files: + '/etc/cfn/cfn-hup.conf': + content: !Sub | + [main] + stack=${AWS::StackId} + region=${AWS::Region} + interval=1 + mode: '000400' + owner: root + group: root + '/etc/cfn/hooks.d/aws-otel-collector-auto-reloader.conf': + content: !Sub | + [cfn-auto-reloader-hook] + triggers=post.update + path=Resources.EC2Instance.Metadata.AWS::CloudFormation::Init.02_config-aws-otel-collector + action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource EC2Instance --region ${AWS::Region} --configsets UpdateEnvironment + runas=root + mode: '000400' + owner: root + group: root + "/lib/systemd/system/cfn-hup.service": + content: !Sub | + [Unit] + Description=cfn-hup daemon + [Service] + Type=simple + ExecStart=/opt/aws/bin/cfn-hup + Restart=always + [Install] + WantedBy=multi-user.target + commands: + 01enable_cfn_hup: + command: !Sub | + systemctl enable cfn-hup.service + 02start_cfn_hup: + command: !Sub | + systemctl start cfn-hup.service + + Properties: + InstanceType: + Ref: InstanceType + IamInstanceProfile: + Ref: IAMRole + KeyName: + Ref: SSHKeyName + ImageId: + Ref: InstanceAMI + SecurityGroups: + - Ref: InstanceSecurityGroup + Tags: + - Key: Name + Value: sumologic-aws-otel-col-ec2 + UserData: + # This script below is to install aws-otel-collector, restart aws-otel-collector and tell the result to cloudformation. + Fn::Base64: !Sub + - | + #!/bin/bash + + # Download AWS OTel Collector RPM + sudo rpm -Uvh https://aws-otel-collector.s3.amazonaws.com/amazon_linux/amd64/latest/aws-otel-collector.rpm + + # Setup Sumologic HTTP Traces URL ENV + echo "export SUMO_HTTP_TRACES_URL=${sumo_http_traces_url}" > /etc/profile.d/setSumoVar.sh + + /opt/aws/bin/cfn-init -v --stack ${AWS::StackId} --resource EC2Instance --region ${AWS::Region} --configsets default + /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource EC2Instance --region ${AWS::Region} + - sumo_http_traces_url: !Ref SumoHttpTracesURL + DependsOn: + - EC2Role + - IAMInstanceProfile + - InstanceSecurityGroup + + IAMInstanceProfile: + Type: 'AWS::IAM::InstanceProfile' + Properties: + InstanceProfileName: !Ref IAMInstanceProfileName + Path: / + Roles: + - !Ref IAMRole + DependsOn: EC2Role + + EC2Role: + Type: 'AWS::IAM::Role' + Properties: + Description: Allows EC2 to call AWS services on your behalf. + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: ec2.amazonaws.com + Action: 'sts:AssumeRole' + ManagedPolicyArns: + - !Sub 'arn:aws:iam::${AWS::AccountId}:policy/${IAMPolicy}' + RoleName: !Ref IAMRole + DependsOn: EC2Policy + + EC2Policy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: Allows EC2 to call AWS services on your behalf. + Path: / + ManagedPolicyName: !Ref IAMPolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - logs:PutLogEvents + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:DescribeLogStreams + - logs:DescribeLogGroups + - xray:PutTraceSegments + - xray:PutTelemetryRecords + - xray:GetSamplingRules + - xray:GetSamplingTargets + - xray:GetSamplingStatisticSummaries + - ssm:GetParameters + Resource: '*' + + InstanceSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Enable SSH access via port 22 + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 22 + ToPort: 22 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 4317 + ToPort: 4317 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 55680 + ToPort: 55680 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 55681 + ToPort: 55681 + CidrIp: 0.0.0.0/0 + - IpProtocol: udp + FromPort: 2000 + ToPort: 2000 + CidrIp: 0.0.0.0/0 diff --git a/examples/non-kubernetes/aws-otel-ecs-ec2-deployment.yaml b/examples/non-kubernetes/aws-otel-ecs-ec2-deployment.yaml new file mode 100644 index 000000000000..31e593fa76c9 --- /dev/null +++ b/examples/non-kubernetes/aws-otel-ecs-ec2-deployment.yaml @@ -0,0 +1,127 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: 'Template to install AWS OTel Collector on ECS in EC2 mode' +Parameters: + IAMTaskRole: + Description: Task attached IAM role + Type: String + Default: SumologicAWSOTelColTaskRoleECSEC2 + ConstraintDescription: must be an existing IAM role which will be attached to EC2 instance. + IAMExecutionRole: + Description: Task Execution attached IAM role + Type: String + Default: SumologicAWSOTelColExecutionRoleECSEC2 + ConstraintDescription: must be an existing IAM role which will be attached to EC2 instance. + IAMPolicy: + Description: IAM Role attached IAM Policy + Type: String + Default: SumologicAWSOTelColPolicyECSEC2 + ConstraintDescription: Must be an existing IAM Managed Policy which will be attached to IAM Role. + ClusterName: + Type: String + Description: Enter the name of your ECS cluster from which you want to collect telemetry data + SumoHttpTracesURL: + Type: String + Description: Enther the Sumologic HTTP Traces Endpoint URL + SumoAWSOTelColConfig: + Type: AWS::SSM::Parameter::Value + Default: sumologic-otel-col-config + Description: AWS SSM Parameter which contains OTel Collector config file +Resources: + ECSTaskDefinition: + Type: 'AWS::ECS::TaskDefinition' + Properties: + Family: sumologic-aws-otel-collector-ec2 + TaskRoleArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${IAMTaskRole}' + ExecutionRoleArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${IAMExecutionRole}' + ContainerDefinitions: + - logConfiguration: + logDriver: awslogs + options: + awslogs-create-group: 'True' + awslogs-group: /ecs/aws-otel-collector + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: ecs + portMappings: + - hostPort: 2000 + protocol: udp + containerPort: 2000 + - hostPort: 4317 + protocol: tcp + containerPort: 4317 + - hostPort: 55681 + protocol: tcp + containerPort: 55681 + environment: + - name: SUMO_HTTP_TRACES_URL + value: !Ref SumoHttpTracesURL + - name: AOT_CONFIG_CONTENT + value: !Ref SumoAWSOTelColConfig + image: amazon/aws-otel-collector:latest + name: sumologic-aws-otel-collector + RequiresCompatibilities: + - EC2 + Cpu: 1024 + Memory: 2048 + DependsOn: + - ECSTaskRole + - ECSExecutionRole + ECSReplicaService: + Type: 'AWS::ECS::Service' + Properties: + TaskDefinition: !Ref ECSTaskDefinition + Cluster: !Ref ClusterName + LaunchType: EC2 + SchedulingStrategy: REPLICA + DesiredCount: 1 + ServiceName: sumologic-aws-otel-col-svc-ecs-ec2 + ECSTaskRole: + Type: 'AWS::IAM::Role' + Properties: + Description: Allows ECS tasks to call AWS services on your behalf. + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Sid: '' + Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: 'sts:AssumeRole' + Policies: + - PolicyName: !Ref IAMPolicy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'logs:PutLogEvents' + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:DescribeLogStreams' + - 'logs:DescribeLogGroups' + - 'xray:PutTraceSegments' + - 'xray:PutTelemetryRecords' + - 'xray:GetSamplingRules' + - 'xray:GetSamplingTargets' + - 'xray:GetSamplingStatisticSummaries' + - 'ssm:GetParameters' + Resource: '*' + RoleName: !Ref IAMTaskRole + ECSExecutionRole: + Type: 'AWS::IAM::Role' + Properties: + Description: >- + Allows ECS container agent makes calls to the Amazon ECS API on your + behalf. + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Sid: '' + Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: 'sts:AssumeRole' + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy' + - 'arn:aws:iam::aws:policy/CloudWatchLogsFullAccess' + - 'arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess' + RoleName: !Ref IAMExecutionRole diff --git a/examples/non-kubernetes/aws-otel-ecs-fargate-deployment.yaml b/examples/non-kubernetes/aws-otel-ecs-fargate-deployment.yaml new file mode 100644 index 000000000000..a656912ea0dc --- /dev/null +++ b/examples/non-kubernetes/aws-otel-ecs-fargate-deployment.yaml @@ -0,0 +1,153 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: 'Template to install AWS OTel Collector on ECS in Fargate mode' +Parameters: + IAMTaskRole: + Description: Task attached IAM role + Type: String + Default: SumologicAWSOTelColTaskRoleECSFargate + ConstraintDescription: must be an existing IAM role which will be attached to EC2 instance. + IAMExecutionRole: + Description: Task Execution attached IAM role + Type: String + Default: SumologicAWSOTelColExecutionRoleECSFargate + ConstraintDescription: must be an existing IAM role which will be attached to EC2 instance. + IAMPolicy: + Description: IAM Role attached IAM Policy + Type: String + Default: SumologicAWSOTelColPolicyECSFargate + ConstraintDescription: Must be an existing IAM Managed Policy which will be attached to IAM Role. + ClusterName: + Type: String + Description: Enter the name of your ECS cluster from which you want to collect telemetry data + SecurityGroups: + Type: CommaDelimitedList + Description: The list of SecurityGroupIds in your Virtual Private Cloud (VPC) + Subnets: + Type: CommaDelimitedList + Description: The list of Subnets in your Virtual Private Cloud (VPC) + SumoHttpTracesURL: + Type: String + Description: Enther the Sumologic HTTP Traces Endpoint URL + SumoAWSOTelColConfig: + Type: AWS::SSM::Parameter::Value + Default: sumologic-otel-col-config + Description: AWS SSM Parameter which contains OTel Collector config file +Resources: + ECSTaskDefinition: + Type: 'AWS::ECS::TaskDefinition' + Properties: + Family: sumologic-aws-otel-collector-fargate + TaskRoleArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${IAMTaskRole}' + ExecutionRoleArn: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${IAMExecutionRole}' + NetworkMode: awsvpc + ContainerDefinitions: + - LogConfiguration: + LogDriver: awslogs + Options: + awslogs-create-group: 'True' + awslogs-group: /ecs/aws-otel-collector + awslogs-region: !Ref 'AWS::Region' + awslogs-stream-prefix: ecs + portMappings: + - hostPort: 2000 + protocol: udp + containerPort: 2000 + - hostPort: 4317 + protocol: tcp + containerPort: 4317 + - hostPort: 6831 + protocol: udp + containerPort: 6831 + - hostPort: 6832 + protocol: udp + containerPort: 6832 + - hostPort: 9411 + protocol: tcp + containerPort: 9411 + - hostPort: 14250 + protocol: tcp + containerPort: 14250 + - hostPort: 14268 + protocol: tcp + containerPort: 14268 + - hostPort: 55681 + protocol: tcp + containerPort: 55681 + environment: + - name: SUMO_HTTP_TRACES_URL + value: !Ref SumoHttpTracesURL + - name: AOT_CONFIG_CONTENT + value: !Ref SumoAWSOTelColConfig + image: amazon/aws-otel-collector:latest + name: sumologic-aws-otel-collector + RequiresCompatibilities: + - FARGATE + Cpu: 1024 + Memory: 2048 + DependsOn: + - ECSTaskRole + - ECSExecutionRole + ECSReplicaService: + Type: 'AWS::ECS::Service' + Properties: + TaskDefinition: !Ref ECSTaskDefinition + Cluster: !Ref ClusterName + LaunchType: FARGATE + SchedulingStrategy: REPLICA + DesiredCount: 1 + ServiceName: sumologic-aws-otel-col-svc-ecs-fargate + NetworkConfiguration: + AwsvpcConfiguration: + AssignPublicIp: ENABLED + SecurityGroups: !Ref SecurityGroups + Subnets: !Ref Subnets + ECSTaskRole: + Type: 'AWS::IAM::Role' + Properties: + Description: Allows ECS tasks to call AWS services on your behalf. + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: 'sts:AssumeRole' + Policies: + - PolicyName: !Ref IAMPolicy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'logs:PutLogEvents' + - 'logs:CreateLogGroup' + - 'logs:CreateLogStream' + - 'logs:DescribeLogStreams' + - 'logs:DescribeLogGroups' + - 'xray:PutTraceSegments' + - 'xray:PutTelemetryRecords' + - 'xray:GetSamplingRules' + - 'xray:GetSamplingTargets' + - 'xray:GetSamplingStatisticSummaries' + - 'ssm:GetParameters' + Resource: '*' + RoleName: !Ref IAMTaskRole + ECSExecutionRole: + Type: 'AWS::IAM::Role' + Properties: + Description: >- + Allows ECS container agent makes calls to the Amazon ECS API on your + behalf. + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Sid: '' + Effect: Allow + Principal: + Service: ecs-tasks.amazonaws.com + Action: 'sts:AssumeRole' + ManagedPolicyArns: + - 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy' + - 'arn:aws:iam::aws:policy/CloudWatchLogsFullAccess' + - 'arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess' + RoleName: !Ref IAMExecutionRole diff --git a/examples/non-kubernetes/gateway-configuration-template-with-cascading-filter.yaml b/examples/non-kubernetes/gateway-configuration-template-with-cascading-filter.yaml new file mode 100644 index 000000000000..8b4b0aae26d7 --- /dev/null +++ b/examples/non-kubernetes/gateway-configuration-template-with-cascading-filter.yaml @@ -0,0 +1,104 @@ +receivers: + jaeger: + protocols: + thrift_compact: + endpoint: "0.0.0.0:6831" + thrift_binary: + endpoint: "0.0.0.0:6832" + grpc: + endpoint: "0.0.0.0:14250" + thrift_http: + endpoint: "0.0.0.0:14268" + opencensus: + endpoint: "0.0.0.0:55678" + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:55681" + zipkin: + endpoint: "0.0.0.0:9411" +processors: + ## The memory_limiter processor is used to prevent out of memory situations on the collector. + memory_limiter: + ## check_interval is the time between measurements of memory usage for the + ## purposes of avoiding going over the limits. Defaults to zero, so no + ## checks will be performed. Values below 1 second are not recommended since + ## it can result in unnecessary CPU consumption. + check_interval: 5s + + ## Maximum amount of memory, in MiB, targeted to be allocated by the process heap. + ## Note that typically the total memory usage of process will be about 50MiB higher + ## than this value. + limit_mib: 1900 + + ## Smart cascading filtering rules with preset limits. + cascading_filter: + ## (default = 30s): Wait time since the first span of a trace arrived before making + ## a filtering decision + decision_wait: 30s + ## (default = 50000): Maximum number of traces kept in memory + num_traces: 100000 + ## (default = 0): Expected number of new traces (helps in allocating data structures) + expected_new_traces_per_sec: 1000 + ## (default = 0): defines the global limit of maximum number of spans per second + ## that are going to be emitted + spans_per_second: 1660 + ## (default = 0.2): Ratio of spans that are always probabilistically filtered + ## (hence might be used for metrics calculation). + probabilistic_filtering_ratio: 0.2 + ## (no default): Policies used to make a sampling decision + policies: + - name: sampling-priority, + ## string_attribute: allows to specify conditions that need to be met + string_attribute: { + key: sampling.priority, values: [ "1" ] + }, + ## Spans_per_second: max number of emitted spans per second by this policy. + spans_per_second: 500 + - name: extended-duration + ## Spans_per_second: max number of emitted spans per second by this policy. + spans_per_second: 500 + properties: + ## Selects the span if the duration is greater or equal the given + ## value (use s or ms as the suffix to indicate unit). + min_duration: 5s + - name: "status_code_condition", + ## Spans_per_second: max number of emitted spans per second by this policy. + spans_per_second: 500, + ## numeric_attribute: provides a list of conditions that need to be met + numeric_attribute: { + key: "http.status_code", min_value: 400, max_value: 999 + } + - name: everything-else + ## This selects all traces, up the to the global limit + spans_per_second: -1 + + ## The batch processor accepts spans and places them into batches grouped by node and resource + batch: + ## Number of spans after which a batch will be sent regardless of time + send_batch_size: 256 + ## Never more than this many spans are being sent in a batch + send_batch_max_size: 512 + ## Time duration after which a batch will be sent regardless of size + timeout: 5s + +extensions: + health_check: {} +exporters: + otlphttp: + traces_endpoint: ENDPOINT_URL + ## Following generates verbose logs with span content, useful to verify what + ## metadata is being tagged. To enable, uncomment and add "logging" to exporters below. + ## There are two levels that could be used: `debug` and `info` with the former + ## being much more verbose and including (sampled) spans content + # logging: + # loglevel: debug +service: + extensions: [health_check] + pipelines: + traces: + receivers: [jaeger, opencensus, otlp, zipkin] + processors: [memory_limiter, cascading_filter, batch] + exporters: [otlphttp] diff --git a/examples/non-kubernetes/gateway-configuration-template.yaml b/examples/non-kubernetes/gateway-configuration-template.yaml new file mode 100644 index 000000000000..b15e832b80be --- /dev/null +++ b/examples/non-kubernetes/gateway-configuration-template.yaml @@ -0,0 +1,62 @@ +receivers: + jaeger: + protocols: + thrift_compact: + endpoint: "0.0.0.0:6831" + thrift_binary: + endpoint: "0.0.0.0:6832" + grpc: + endpoint: "0.0.0.0:14250" + thrift_http: + endpoint: "0.0.0.0:14268" + opencensus: + endpoint: "0.0.0.0:55678" + otlp: + protocols: + grpc: + endpoint: "0.0.0.0:4317" + http: + endpoint: "0.0.0.0:55681" + zipkin: + endpoint: "0.0.0.0:9411" +processors: + ## The memory_limiter processor is used to prevent out of memory situations on the collector. + memory_limiter: + ## check_interval is the time between measurements of memory usage for the + ## purposes of avoiding going over the limits. Defaults to zero, so no + ## checks will be performed. Values below 1 second are not recommended since + ## it can result in unnecessary CPU consumption. + check_interval: 5s + + ## Maximum amount of memory, in MiB, targeted to be allocated by the process heap. + ## Note that typically the total memory usage of process will be about 50MiB higher + ## than this value. + limit_mib: 1900 + + ## The batch processor accepts spans and places them into batches grouped by node and resource + batch: + ## Number of spans after which a batch will be sent regardless of time + send_batch_size: 256 + ## Never more than this many spans are being sent in a batch + send_batch_max_size: 512 + ## Time duration after which a batch will be sent regardless of size + timeout: 5s + +extensions: + health_check: {} +exporters: + otlphttp: + traces_endpoint: ENDPOINT_URL + ## Following generates verbose logs with span content, useful to verify what + ## metadata is being tagged. To enable, uncomment and add "logging" to exporters below. + ## There are two levels that could be used: `debug` and `info` with the former + ## being much more verbose and including (sampled) spans content + # logging: + # loglevel: debug +service: + extensions: [health_check] + pipelines: + traces: + receivers: [jaeger, opencensus, otlp, zipkin] + processors: [memory_limiter, batch] + exporters: [otlphttp] diff --git a/go.mod b/go.mod index edca43257968..e42d98fef4ab 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor v0.35.0 + github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/cumulativetodeltaprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatorateprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor v0.35.0 @@ -60,8 +61,10 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/routingprocessor v0.35.0 + github.com/open-telemetry/opentelemetry-collector-contrib/processor/sourceprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanmetricsprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanprocessor v0.35.0 + github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicsyslogprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsecscontainermetricsreceiver v0.35.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsxrayreceiver v0.35.0 @@ -432,6 +435,8 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) +replace github.com/influxdata/telegraf => github.com/sumologic/telegraf v1.17.3-sumo + // Replace references to modules that are in this repository with their relateive paths // so that we always build with current (latest) version of the source code. @@ -631,14 +636,20 @@ replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/filt replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor => ./processor/groupbyattrsprocessor -replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbytraceprocessor => ./processor/groupbytraceprocessor +replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor => ./processor/cascadingfilterprocessor/ + +replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbytraceprocessor => ./processor/groupbytraceprocessor/ replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor => ./processor/k8sprocessor/ +replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/sourceprocessor => ./processor/sourceprocessor/ + replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor => ./processor/resourcedetectionprocessor/ replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor => ./processor/resourceprocessor/ +replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicsyslogprocessor => ./processor/sumologicsyslogprocessor/ + replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricstransformprocessor => ./processor/metricstransformprocessor/ replace github.com/open-telemetry/opentelemetry-collector-contrib/processor/metricsgenerationprocessor => ./processor/metricsgenerationprocessor/ diff --git a/internal/components/components.go b/internal/components/components.go index 3e46fb204520..a103215a9eb3 100644 --- a/internal/components/components.go +++ b/internal/components/components.go @@ -62,6 +62,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage/filestorage" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cumulativetodeltaprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatorateprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/filterprocessor" @@ -74,8 +75,10 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourceprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/routingprocessor" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sourceprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanmetricsprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/spanprocessor" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicsyslogprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/tailsamplingprocessor" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsecscontainermetricsreceiver" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsxrayreceiver" @@ -238,6 +241,8 @@ func Components() (component.Factories, error) { attributesprocessor.NewFactory(), filterprocessor.NewFactory(), groupbyattrsprocessor.NewFactory(), + cascadingfilterprocessor.NewFactory(), + sourceprocessor.NewFactory(), groupbytraceprocessor.NewFactory(), k8sprocessor.NewFactory(), metricstransformprocessor.NewFactory(), @@ -251,6 +256,7 @@ func Components() (component.Factories, error) { spanprocessor.NewFactory(), cumulativetodeltaprocessor.NewFactory(), deltatorateprocessor.NewFactory(), + sumologicsyslogprocessor.NewFactory(), } for _, pr := range factories.Processors { processors = append(processors, pr) diff --git a/otelcolbuilder/.gitignore b/otelcolbuilder/.gitignore new file mode 100644 index 000000000000..b3087b09750d --- /dev/null +++ b/otelcolbuilder/.gitignore @@ -0,0 +1 @@ +cmd/ diff --git a/otelcolbuilder/.otelcol-builder.yaml b/otelcolbuilder/.otelcol-builder.yaml new file mode 100644 index 000000000000..611ac2c32093 --- /dev/null +++ b/otelcolbuilder/.otelcol-builder.yaml @@ -0,0 +1,39 @@ +dist: + name: otelcol-sumo + description: Sumo Logic OpenTelemetry Collector distribution + + # the module name for the new distribution, following Go mod conventions. Optional, but recommended. + module: github.com/SumoLogic/opentelemetry-collector-builder + + otelcol_version: 0.24.0 # the OpenTelemetry Collector version to use as base for the distribution. + output_path: ./cmd/ # the path to write the output (sources and binary). + +exporters: + - gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sumologicexporter v0.24.0" + +processors: + - gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor v0.24.0" + - gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor v0.24.0" + - gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sourceprocessor v0.24.0" + +receivers: + - gomod: "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/telegrafreceiver v0.24.0" + +# Replacement paths are relative to the output_path (location of source files) +replaces: + # Customized processors + - github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor => ./../../processor/cascadingfilterprocessor + # - + - github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor => ./../../processor/k8sprocessor + - github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig => ./../../internal/k8sconfig + # - + - github.com/open-telemetry/opentelemetry-collector-contrib/processor/sourceprocessor => ./../../processor/sourceprocessor + + # ---------------------------------------------------------------------------- + # Customized receivers + - github.com/open-telemetry/opentelemetry-collector-contrib/receiver/telegrafreceiver => ./../../receiver/telegrafreceiver + - github.com/influxdata/telegraf => github.com/sumologic/telegraf v1.17.3-sumo + + # ---------------------------------------------------------------------------- + # Customized core + - go.opentelemetry.io/collector => github.com/SumoLogic/opentelemetry-collector v0.24.0-sumo diff --git a/otelcolbuilder/Makefile b/otelcolbuilder/Makefile new file mode 100644 index 000000000000..3f4b7cdbd717 --- /dev/null +++ b/otelcolbuilder/Makefile @@ -0,0 +1,8 @@ +install: + go install github.com/open-telemetry/opentelemetry-collector-builder@v0.24.0 + +install-prego1.16: + GO111MODULE=on go get github.com/open-telemetry/opentelemetry-collector-builder@v0.24.0 + +build: + opentelemetry-collector-builder --config .otelcol-builder.yaml diff --git a/processor/cascadingfilterprocessor/Makefile b/processor/cascadingfilterprocessor/Makefile new file mode 100644 index 000000000000..c1496226e590 --- /dev/null +++ b/processor/cascadingfilterprocessor/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common \ No newline at end of file diff --git a/processor/cascadingfilterprocessor/README.md b/processor/cascadingfilterprocessor/README.md new file mode 100644 index 000000000000..adfed6db7e22 --- /dev/null +++ b/processor/cascadingfilterprocessor/README.md @@ -0,0 +1,131 @@ +# Cascading Filter Processor + +Supported pipeline types: traces + +The Cascading Filter processor is a fork of +[tailsamplingprocessor][tailsamplingprocessor] which allows for defining smart +cascading filtering rules with preset limits. + +[tailsamplingprocessor]:https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor + +## Processor configuration + +The following configuration options should be configured as desired: +- `policies` (no default): Policies used to make a sampling decision +- `spans_per_second` (default = 1500): Maximum total number of emitted spans per second +- `probabilistic_filtering_ratio` (default = 0.2): Ratio of spans that are always probabilistically filtered +(hence might be used for metrics calculation). The ratio is specified as portion of output spans (defined by +`spans_per_second`) rather than input spans. So the default filtering rate of `0.2` and default max span rate of +`1500` produces at most `300` probabilistically sampled spans per second. + +The following configuration options can also be modified: +- `decision_wait` (default = 30s): Wait time since the first span of a trace before making a filtering decision +- `num_traces` (default = 50000): Number of traces kept in memory +- `expected_new_traces_per_sec` (default = 0): Expected number of new traces (helps in allocating data structures) + +## Updated span attributes + +The processor modifies each span attributes, by setting following two attributes: +- `sampling.rule`: describing if `probabilistic` or `filtered` policy was applied +- `sampling.probability`: describing the effective sampling rate in case of `probabilistic` rule. E.g. if there were `5000` +spans evaluated in a given second, with `1500` max total spans per second and `0.2` filtering ratio, at most `300` spans +would be selected by such rule. This would effect in having `sampling.probability=0.06` (`300/5000=0.6`). If such value is already +set by head-based (or other) sampling, it's multiplied by the calculated value. + +## Policy configuration + +Each defined policy is evaluated with order as specified in config. There are several properties: +- `name` (required): identifies the policy +- `spans_per_second` (default = 0): defines maximum number of spans per second that could be handled by this policy. When set to `-1`, +it selects the traces only if the global limit is not exceeded by other policies (however, without further limitations) + +Additionally, each of the policy might have any of the following filtering criteria defined. They are evaluated for +each of the trace spans. If at least one span matching all defined criteria is found, the trace is selected: +- `numeric_attribute: {key: , min_value: , max_value: }`: selects span by matching numeric +attribute (either at resource of span level) +- `string_attribute: {key: , values: [, ]}`: selects span by matching string attribute that is one +of the provided values (either at resource of span level) +- `properties: { min_number_of_spans: }`: selects the trace if it has at least provided number of spans +- `properties: { min_duration: }`: selects the span if the duration is greater or equal the given value +(use `s` or `ms` as the suffix to indicate unit) +- `properties: { name_pattern: `}: selects the span if its operation name matches the provided regular expression + +To invert the decision (which is still a subject to rate limiting), additional property can be configured: +- `invert_match: ` (default=`false`): when set to `true`, the opposite decision is selected for the trace. E.g. +if trace matches a given string attribute and `invert_match=true`, then the trace is not selected + +## Limiting the number of spans + +There are two `spans_per_second` settings. The global one and the policy-one. + +While evaluating traces, the limit is evaluated first on the policy level and then on the global level. The sum +of all `spans_per_second` rates might be actually higher than the global limit, but the latter will never be +exceeded (so some of the traces will not be included). + +For example, we have 3 policies: `A, B, C`. Each of them has limit of `300` spans per second and the global limit +is `500` spans per second. Now, lets say, that there for each of the policies there were 5 distinct traces, each +having `100` spans and matching policy criteria (lets call them `A1, A2, ... B1, B2...` and so forth: + +`Policy A`: `A1, A2, A3` +`Policy B`: `B1, B2, B3` +`Policy C`: `C1, C2, C3` + +However, in total, this is `900` spans, which is more than the global limit of `500` spans/second. The processor +will take care of that and randomly select only the spans up to the global limit. So eventually, it might +for example send further only following traces: `A1, A2, B1, C2, C5` and filter out the others. + +## Example + +```yaml +processors: + cascading_filter: + decision_wait: 10s + num_traces: 100 + expected_new_traces_per_sec: 10 + spans_per_second: 1000 + probabilistic_filtering_ratio: 0.1 + policies: + [ + { + name: test-policy-1, + }, + { + name: test-policy-2, + numeric_attribute: { key: key1, min_value: 50, max_value: 100 } + }, + { + name: test-policy-3, + string_attribute: { key: key2, values: [ value1, value2 ] } + }, + { + name: test-policy-4, + spans_per_second: 35, + }, + { + name: test-policy-5, + spans_per_second: 123, + numeric_attribute: { key: key1, min_value: 50, max_value: 100 }, + invert_match: true + }, + { + name: test-policy-6, + spans_per_second: 50, + properties: { min_duration: 9s } + }, + { + name: test-policy-7, + properties: { + name_pattern: "foo.*", + min_number_of_spans: 10, + min_duration: 9s + } + }, + { + name: everything_else, + spans_per_second: -1 + }, + ] +``` + +Refer to [cascading_filter_config.yaml](./testdata/cascading_filter_config.yaml) for detailed +examples on using the processor. diff --git a/processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter.go b/processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter.go new file mode 100644 index 000000000000..4f8cc2661474 --- /dev/null +++ b/processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry 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 bigendianconverter + +import ( + "encoding/binary" + + "go.opentelemetry.io/collector/model/pdata" +) + +// NOTE: +// This code was copied over from: +// https://github.com/open-telemetry/opentelemetry-collector/blob/v0.28.0/internal/idutils/big_endian_converter.go +// to allow processor tests to still run as they used to. + +// UInt64ToTraceID converts the pair of uint64 representation of a TraceID to pdata.TraceID. +func UInt64ToTraceID(high, low uint64) pdata.TraceID { + traceID := [16]byte{} + binary.BigEndian.PutUint64(traceID[:8], high) + binary.BigEndian.PutUint64(traceID[8:], low) + return pdata.NewTraceID(traceID) +} + +// SpanIDToUInt64 converts the pdata.SpanID to uint64 representation. +func SpanIDToUInt64(spanID pdata.SpanID) uint64 { + bytes := spanID.Bytes() + return binary.BigEndian.Uint64(bytes[:]) +} + +// UInt64ToSpanID converts the uint64 representation of a SpanID to pdata.SpanID. +func UInt64ToSpanID(id uint64) pdata.SpanID { + spanID := [8]byte{} + binary.BigEndian.PutUint64(spanID[:], id) + return pdata.NewSpanID(spanID) +} diff --git a/processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter_test.go b/processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter_test.go new file mode 100644 index 000000000000..c601a15899f0 --- /dev/null +++ b/processor/cascadingfilterprocessor/bigendianconverter/big_endian_converter_test.go @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry 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 bigendianconverter + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/model/pdata" +) + +func TestUInt64ToTraceIDConversion(t *testing.T) { + assert.Equal(t, + pdata.NewTraceID([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), + UInt64ToTraceID(0, 0), + "Failed 0 conversion:") + assert.Equal(t, + pdata.NewTraceID([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}), + UInt64ToTraceID(256*256+256+1, 256+1), + "Failed simple conversion:") + assert.Equal(t, + pdata.NewTraceID([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05}), + UInt64ToTraceID(0, 5), + "Failed to convert 0 high:") + assert.Equal(t, + UInt64ToTraceID(5, 0), + pdata.NewTraceID([16]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), + UInt64ToTraceID(5, 0), + "Failed to convert 0 low:") + assert.Equal(t, + pdata.NewTraceID([16]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05}), + UInt64ToTraceID(math.MaxUint64, 5), + "Failed to convert MaxUint64:") +} + +func TestUInt64ToSpanIDConversion(t *testing.T) { + assert.Equal(t, + pdata.NewSpanID([8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), + UInt64ToSpanID(0), + "Failed 0 conversion:") + assert.Equal(t, + pdata.NewSpanID([8]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01}), + UInt64ToSpanID(256*256+256+1), + "Failed simple conversion:") + assert.Equal(t, + pdata.NewSpanID([8]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}), + UInt64ToSpanID(math.MaxUint64), + "Failed to convert MaxUint64:") +} + +func TestSpanIdUInt64RoundTrip(t *testing.T) { + w := uint64(0x0001020304050607) + assert.Equal(t, w, SpanIDToUInt64(UInt64ToSpanID(w))) +} diff --git a/processor/cascadingfilterprocessor/cascading_test.go b/processor/cascadingfilterprocessor/cascading_test.go new file mode 100644 index 000000000000..6b68c007e2e4 --- /dev/null +++ b/processor/cascadingfilterprocessor/cascading_test.go @@ -0,0 +1,154 @@ +// Copyright The OpenTelemetry 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 cascadingfilterprocessor + +import ( + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" + + cfconfig "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/sampling" +) + +var testValue = 10 * time.Millisecond +var cfg = cfconfig.Config{ + ProcessorSettings: &config.ProcessorSettings{}, + DecisionWait: 2 * time.Second, + NumTraces: 100, + ExpectedNewTracesPerSec: 100, + SpansPerSecond: 1000, + PolicyCfgs: []cfconfig.PolicyCfg{ + { + Name: "duration", + SpansPerSecond: 10, + PropertiesCfg: cfconfig.PropertiesCfg{ + MinDuration: &testValue, + }, + }, + { + Name: "everything else", + SpansPerSecond: -1, + }, + }, +} + +func fillSpan(span *pdata.Span, durationMicros int64) { + nowTs := time.Now().UnixNano() + startTime := nowTs - durationMicros*1000 + + span.Attributes().InsertInt("foo", 55) + span.SetStartTimestamp(pdata.Timestamp(startTime)) + span.SetEndTimestamp(pdata.Timestamp(nowTs)) +} + +func createTrace(fsp *cascadingFilterSpanProcessor, numSpans int, durationMicros int64) *sampling.TraceData { + var traceBatches []pdata.Traces + + traces := pdata.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + ils := rs.InstrumentationLibrarySpans().AppendEmpty() + + spans := ils.Spans() + spans.EnsureCapacity(numSpans) + + for i := 0; i < numSpans; i++ { + span := spans.AppendEmpty() + + fillSpan(&span, durationMicros) + } + + traceBatches = append(traceBatches, traces) + + return &sampling.TraceData{ + Mutex: sync.Mutex{}, + Decisions: make([]sampling.Decision, len(fsp.policies)), + ArrivalTime: time.Time{}, + DecisionTime: time.Time{}, + SpanCount: int64(numSpans), + ReceivedBatches: traceBatches, + } +} + +func createCascadingEvaluator(t *testing.T) *cascadingFilterSpanProcessor { + cascading, err := newCascadingFilterSpanProcessor(zap.NewNop(), nil, cfg) + assert.NoError(t, err) + return cascading +} + +func TestSampling(t *testing.T) { + cascading := createCascadingEvaluator(t) + + decision, policy := cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{0}), createTrace(cascading, 8, 1000000)) + require.NotNil(t, policy) + require.Equal(t, sampling.Sampled, decision) + + decision, _ = cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{1}), createTrace(cascading, 1000, 1000)) + require.Equal(t, sampling.SecondChance, decision) +} + +func TestSecondChanceEvaluation(t *testing.T) { + cascading := createCascadingEvaluator(t) + + decision, _ := cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{0}), createTrace(cascading, 8, 1000)) + require.Equal(t, sampling.SecondChance, decision) + + decision, _ = cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{1}), createTrace(cascading, 8, 1000)) + require.Equal(t, sampling.SecondChance, decision) + + // TODO: This could me optimized to make a decision within cascadingfilter processor, as such span would never fit anyway + //decision, _ = cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{1}), createTrace(8000, 1000), metrics) + //require.Equal(t, sampling.NotSampled, decision) +} + +func TestProbabilisticFilter(t *testing.T) { + ratio := float32(0.5) + cfg.ProbabilisticFilteringRatio = &ratio + cascading := createCascadingEvaluator(t) + + trace1 := createTrace(cascading, 8, 1000000) + decision, _ := cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{0}), trace1) + require.Equal(t, sampling.Sampled, decision) + require.True(t, trace1.SelectedByProbabilisticFilter) + + trace2 := createTrace(cascading, 800, 1000000) + decision, _ = cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{1}), trace2) + require.Equal(t, sampling.SecondChance, decision) + require.False(t, trace2.SelectedByProbabilisticFilter) + + ratio = float32(0.0) + cfg.ProbabilisticFilteringRatio = &ratio +} + +//func TestSecondChanceReevaluation(t *testing.T) { +// cascading := createCascadingEvaluator() +// +// decision, _ := cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{1}), createTrace(100, 1000), metrics) +// require.Equal(t, sampling.Sampled, decision) +// +// // Too much +// decision, _ = cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{1}), createTrace(1000, 1000), metrics) +// require.Equal(t, sampling.NotSampled, decision) +// +// // Just right +// decision, _ = cascading.makeProvisionalDecision(pdata.NewTraceID([16]byte{1}), createTrace(900, 1000), metrics) +// require.Equal(t, sampling.Sampled, decision) +//} diff --git a/processor/cascadingfilterprocessor/config/config.go b/processor/cascadingfilterprocessor/config/config.go new file mode 100644 index 000000000000..30137a9748d0 --- /dev/null +++ b/processor/cascadingfilterprocessor/config/config.go @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry 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 config + +import ( + "time" + + "go.opentelemetry.io/collector/config" +) + +// PolicyCfg holds the common configuration to all sampling policies. +type PolicyCfg struct { + // Name given to the instance of the policy to make easy to identify it in metrics and logs. + Name string `mapstructure:"name"` + // Configs for numeric attribute filter sampling policy evaluator. + NumericAttributeCfg *NumericAttributeCfg `mapstructure:"numeric_attribute"` + // Configs for string attribute filter sampling policy evaluator. + StringAttributeCfg *StringAttributeCfg `mapstructure:"string_attribute"` + // Configs for properties sampling policy evaluator. + PropertiesCfg PropertiesCfg `mapstructure:"properties"` + // SpansPerSecond specifies the rule budget that should never be exceeded for it + SpansPerSecond int64 `mapstructure:"spans_per_second"` + // InvertMatch specifies if the match should be inverted. Default: false + InvertMatch bool `mapstructure:"invert_match"` +} + +// PropertiesCfg holds the configurable settings to create a duration filter +type PropertiesCfg struct { + // NamePattern (optional) describes a regular expression that must be met by any span operation name. + NamePattern *string `mapstructure:"name_pattern"` + // MinDuration (optional) is the minimum duration of trace to be considered a match. + MinDuration *time.Duration `mapstructure:"min_duration"` + // MinNumberOfSpans (optional) is the minimum number spans that must be present in a matching trace. + MinNumberOfSpans *int `mapstructure:"min_number_of_spans"` +} + +// NumericAttributeCfg holds the configurable settings to create a numeric attribute filter +// sampling policy evaluator. +type NumericAttributeCfg struct { + // Tag that the filter is going to be matching against. + Key string `mapstructure:"key"` + // MinValue is the minimum value of the attribute to be considered a match. + MinValue int64 `mapstructure:"min_value"` + // MaxValue is the maximum value of the attribute to be considered a match. + MaxValue int64 `mapstructure:"max_value"` +} + +// StringAttributeCfg holds the configurable settings to create a string attribute filter +// sampling policy evaluator. +type StringAttributeCfg struct { + // Tag that the filter is going to be matching against. + Key string `mapstructure:"key"` + // Values is the set of attribute values that if any is equal to the actual attribute value to be considered a match. + Values []string `mapstructure:"values"` +} + +// Config holds the configuration for cascading-filter-based sampling. +type Config struct { + *config.ProcessorSettings `mapstructure:"-"` + // DecisionWait is the desired wait time from the arrival of the first span of + // trace until the decision about sampling it or not is evaluated. + DecisionWait time.Duration `mapstructure:"decision_wait"` + // SpansPerSecond specifies the total budget that should never be exceeded + SpansPerSecond int64 `mapstructure:"spans_per_second"` + // ProbabilisticFilteringRatio describes which part (0.0-1.0) of the SpansPerSecond budget + // is exclusively allocated for probabilistically selected spans + ProbabilisticFilteringRatio *float32 `mapstructure:"probabilistic_filtering_ratio"` + // NumTraces is the number of traces kept on memory. Typically most of the data + // of a trace is released after a sampling decision is taken. + NumTraces uint64 `mapstructure:"num_traces"` + // ExpectedNewTracesPerSec sets the expected number of new traces sending to the Cascading Filter processor + // per second. This helps with allocating data structures with closer to actual usage size. + ExpectedNewTracesPerSec uint64 `mapstructure:"expected_new_traces_per_sec"` + // PolicyCfgs sets the cascading-filter-based sampling policy which makes a sampling decision + // for a given trace when requested. + PolicyCfgs []PolicyCfg `mapstructure:"policies"` +} diff --git a/processor/cascadingfilterprocessor/config_test.go b/processor/cascadingfilterprocessor/config_test.go new file mode 100644 index 000000000000..5e0d86a577ed --- /dev/null +++ b/processor/cascadingfilterprocessor/config_test.go @@ -0,0 +1,100 @@ +// Copyright The OpenTelemetry 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 cascadingfilterprocessor + +import ( + "path" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configtest" + + cfconfig "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/config" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.NopFactories() + assert.NoError(t, err) + + factory := NewFactory() + factories.Processors[factory.Type()] = factory + + cfg, err := configtest.LoadConfig(path.Join(".", "testdata", "cascading_filter_config.yaml"), factories) + require.NoError(t, err) + require.NotNil(t, cfg) + + minDurationValue := 9 * time.Second + minSpansValue := 10 + probFilteringRatio := float32(0.1) + namePatternValue := "foo.*" + + id := config.NewID("cascading_filter") + ps := config.NewProcessorSettings(id) + assert.Equal(t, cfg.Processors[id], + &cfconfig.Config{ + ProcessorSettings: &ps, + DecisionWait: 10 * time.Second, + NumTraces: 100, + ExpectedNewTracesPerSec: 10, + SpansPerSecond: 1000, + ProbabilisticFilteringRatio: &probFilteringRatio, + PolicyCfgs: []cfconfig.PolicyCfg{ + { + Name: "test-policy-1", + }, + { + Name: "test-policy-2", + NumericAttributeCfg: &cfconfig.NumericAttributeCfg{Key: "key1", MinValue: 50, MaxValue: 100}, + }, + { + Name: "test-policy-3", + StringAttributeCfg: &cfconfig.StringAttributeCfg{Key: "key2", Values: []string{"value1", "value2"}}, + }, + { + Name: "test-policy-4", + SpansPerSecond: 35, + }, + { + Name: "test-policy-5", + SpansPerSecond: 123, + NumericAttributeCfg: &cfconfig.NumericAttributeCfg{ + Key: "key1", MinValue: 50, MaxValue: 100}, + InvertMatch: true, + }, + { + Name: "test-policy-6", + SpansPerSecond: 50, + + PropertiesCfg: cfconfig.PropertiesCfg{MinDuration: &minDurationValue}, + }, + { + Name: "test-policy-7", + PropertiesCfg: cfconfig.PropertiesCfg{ + NamePattern: &namePatternValue, + MinDuration: &minDurationValue, + MinNumberOfSpans: &minSpansValue, + }, + }, + { + Name: "everything_else", + SpansPerSecond: -1, + }, + }, + }) +} diff --git a/processor/cascadingfilterprocessor/factory.go b/processor/cascadingfilterprocessor/factory.go new file mode 100644 index 000000000000..8752a81c2044 --- /dev/null +++ b/processor/cascadingfilterprocessor/factory.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry 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 cascadingfilterprocessor + +import ( + "context" + "time" + + "go.opencensus.io/stats/view" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" + + cfconfig "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/config" +) + +const ( + // The value of "type" Cascading Filter in configuration. + typeStr = "cascading_filter" +) + +var ( + defaultProbabilisticFilteringRatio = float32(0.2) +) + +func init() { + // TODO: this is hardcoding the metrics level + err := view.Register(CascadingFilterMetricViews(configtelemetry.LevelNormal)...) + if err != nil { + panic("failed to register cascadingfilterprocessor: " + err.Error()) + } +} + +// NewFactory returns a new factory for the Cascading Filter processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor)) +} + +func createDefaultConfig() config.Processor { + id := config.NewID("cascading_filter") + ps := config.NewProcessorSettings(id) + + return &cfconfig.Config{ + ProcessorSettings: &ps, + DecisionWait: 30 * time.Second, + NumTraces: 50000, + SpansPerSecond: 1500, + ProbabilisticFilteringRatio: &defaultProbabilisticFilteringRatio, + } +} + +func createTraceProcessor( + _ context.Context, + params component.ProcessorCreateSettings, + cfg config.Processor, + nextConsumer consumer.Traces, +) (component.TracesProcessor, error) { + tCfg := cfg.(*cfconfig.Config) + return newTraceProcessor(params.Logger, nextConsumer, *tCfg) +} diff --git a/processor/cascadingfilterprocessor/factory_test.go b/processor/cascadingfilterprocessor/factory_test.go new file mode 100644 index 000000000000..14ff9bcef797 --- /dev/null +++ b/processor/cascadingfilterprocessor/factory_test.go @@ -0,0 +1,51 @@ +// Copyright The OpenTelemetry 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 cascadingfilterprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/consumer/consumertest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/config" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestCreateProcessor(t *testing.T) { + factory := NewFactory() + + cfg := factory.CreateDefaultConfig().(*config.Config) + // Manually set required fields + cfg.ExpectedNewTracesPerSec = 64 + cfg.PolicyCfgs = []config.PolicyCfg{ + { + Name: "test-policy", + }, + } + + params := component.ProcessorCreateSettings{} + tp, err := factory.CreateTracesProcessor(context.Background(), params, cfg, consumertest.NewNop()) + assert.NotNil(t, tp) + assert.NoError(t, err, "cannot create trace processor") +} diff --git a/processor/cascadingfilterprocessor/go.mod b/processor/cascadingfilterprocessor/go.mod new file mode 100644 index 000000000000..9cda0afe1b6a --- /dev/null +++ b/processor/cascadingfilterprocessor/go.mod @@ -0,0 +1,12 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor + +go 1.16 + +require ( + github.com/google/uuid v1.3.0 + github.com/stretchr/testify v1.7.0 + go.opencensus.io v0.23.0 + go.opentelemetry.io/collector v0.35.0 + go.opentelemetry.io/collector/model v0.35.0 + go.uber.org/zap v1.19.0 +) diff --git a/processor/cascadingfilterprocessor/go.sum b/processor/cascadingfilterprocessor/go.sum new file mode 100644 index 000000000000..6ab4048da6ec --- /dev/null +++ b/processor/cascadingfilterprocessor/go.sum @@ -0,0 +1,804 @@ +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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0QmaQNhGnptITnPQB+z1+qeFB0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +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-cleanhttp v0.5.1/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.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +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.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/koanf v1.2.2 h1:CydaDM/2mtza/ytVVnj4iQSVPjDq+pSV2vWMFDiQS08= +github.com/knadh/koanf v1.2.2/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +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/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface 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/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ= +github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= +github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v3.21.8+incompatible h1:sh0foI8tMRlCidUJR+KzqWYWxrkuuPIGiO6Vp+KXdCU= +github.com/shirou/gopsutil v3.21.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/collector v0.35.0 h1:e7kutiBxbyk9Gc0DkFDYxnWrmW9sAPpXbgFoQmof/kE= +go.opentelemetry.io/collector v0.35.0/go.mod h1:mMW4VJHhy8CwoTenV75axteKd0aZhWHDZjcZ59V53kc= +go.opentelemetry.io/collector/model v0.35.0 h1:NpKjghiqlei4ecwjOYOMhD6tj4gY8yiWHPJmbFs/ArI= +go.opentelemetry.io/collector/model v0.35.0/go.mod h1:+7YCSjJG+MqiIFjauzt7oM2qkqBsaJWh5hcsO4fwsAc= +go.opentelemetry.io/contrib v0.22.0 h1:0F7gDEjgb1WGn4ODIjaCAg75hmqF+UN0LiVgwxsCodc= +go.opentelemetry.io/contrib v0.22.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0 h1:TjqELdtCtlOJQrTnXd2y+RP6wXKZUnnJer0HR0CSo18= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0/go.mod h1:KjqwX4uJNaj479ZjFpADOMJKOM4rBXq4kN7nbeuGKrY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.22.0 h1:WHjZguqT+3UjTgFum33hWZYybDVnx8u9q5/kQDfaGTs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.22.0/go.mod h1:o3MuU25bYroYnc2TOKe8mTk8f9X1oPFO6C5RCoPKtSU= +go.opentelemetry.io/contrib/zpages v0.22.0/go.mod h1:pO7VUk5qoCiekzXk0XCuQcKQsKBHyjx9KFIW1Vlc8dw= +go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= +go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM= +go.opentelemetry.io/otel v1.0.0-RC3 h1:kvwiyEkiUT/JaadXzVLI/R1wDO934A7r3Bs2wEe6wqA= +go.opentelemetry.io/otel v1.0.0-RC3/go.mod h1:Ka5j3ua8tZs4Rkq4Ex3hwgBgOchyPVq5S6P2lz//nKQ= +go.opentelemetry.io/otel/internal/metric v0.22.0 h1:Q9bS02XRykSRIbggaU4hVF9oWOP9PyILu26zJWoKmk0= +go.opentelemetry.io/otel/internal/metric v0.22.0/go.mod h1:7qVuMihW/ktMonEfOvBXuh6tfMvvEyoIDgeJNRloYbQ= +go.opentelemetry.io/otel/metric v0.22.0 h1:/qv10BzznqEifrXBwsTT370OCN1PRgt+mnjzMwxJKrQ= +go.opentelemetry.io/otel/metric v0.22.0/go.mod h1:KcsUkBiYGW003DJ+ugd2aqIRIfjabD9jeOUXqsAtrq0= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= +go.opentelemetry.io/otel/oteltest v1.0.0-RC2/go.mod h1:kiQ4tw5tAL4JLTbcOYwK1CWI1HkT5aiLzHovgOVnz/A= +go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw= +go.opentelemetry.io/otel/sdk v1.0.0-RC3/go.mod h1:78H6hyg2fka0NYT9fqGuFLvly2yCxiBXDJAgLKo/2Us= +go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= +go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4= +go.opentelemetry.io/otel/trace v1.0.0-RC3 h1:9F0ayEvlxv8BmNmPbU005WK7hC+7KbOazCPZjNa1yME= +go.opentelemetry.io/otel/trace v1.0.0-RC3/go.mod h1:VUt2TUYd8S2/ZRX09ZDFZQwn2RqfMB5MzO17jBojGxo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-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-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190312151545-0bb0c0a6e846/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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/processor/cascadingfilterprocessor/idbatcher/id_batcher.go b/processor/cascadingfilterprocessor/idbatcher/id_batcher.go new file mode 100644 index 000000000000..29e2632b7653 --- /dev/null +++ b/processor/cascadingfilterprocessor/idbatcher/id_batcher.go @@ -0,0 +1,141 @@ +// Copyright The OpenTelemetry 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 idbatcher defines a pipeline of fixed size in which the +// elements are batches of ids. +package idbatcher + +import ( + "errors" + "sync" + + "go.opentelemetry.io/collector/model/pdata" +) + +var ( + // ErrInvalidNumBatches occurs when an invalid number of batches is specified. + ErrInvalidNumBatches = errors.New("invalid number of batches, it must be greater than zero") + // ErrInvalidBatchChannelSize occurs when an invalid batch channel size is specified. + ErrInvalidBatchChannelSize = errors.New("invalid batch channel size, it must be greater than zero") +) + +// Batch is the type of batches held by the Batcher. +type Batch []pdata.TraceID + +// Batcher behaves like a pipeline of batches that has a fixed number of batches in the pipe +// and a new batch being built outside of the pipe. Items can be concurrently added to the batch +// currently being built. When the batch being built is closed, the oldest batch in the pipe +// is pushed out so the one just closed can be put on the end of the pipe (this is done as an +// atomic operation). The caller is in control of when a batch is completed and a new one should +// be started. +type Batcher interface { + // AddToCurrentBatch puts the given id on the batch being currently built. The client is in charge + // of limiting the growth of the current batch if appropriate for its scenario. It can + // either call CloseCurrentAndTakeFirstBatch earlier or stop adding new items depending on what is + // required by the scenario. + AddToCurrentBatch(id pdata.TraceID) + // CloseCurrentAndTakeFirstBatch takes the batch at the front of the pipe, and moves the current + // batch to the end of the pipe, creating a new batch to receive new items. This operation should + // be atomic. + // It returns the batch that was in front of the pipe and a boolean that if true indicates that + // there are more batches to be retrieved. + CloseCurrentAndTakeFirstBatch() (Batch, bool) + // Stop informs that no more items are going to be batched and the pipeline can be read until it + // is empty. After this method is called attempts to enqueue new items will panic. + Stop() +} + +var _ Batcher = (*batcher)(nil) + +type batcher struct { + pendingIds chan pdata.TraceID // Channel for the ids to be added to the next batch. + batches chan Batch // Channel with already captured batches. + + // cbMutex protects the currentBatch storing ids. + cbMutex sync.Mutex + currentBatch Batch + + newBatchesInitialCapacity uint64 + stopchan chan bool + stopped bool +} + +// New creates a Batcher that will hold numBatches in its pipeline, having a channel with +// batchChannelSize to receive new items. New batches will be created with capacity set to +// newBatchesInitialCapacity. +func New(numBatches, newBatchesInitialCapacity, batchChannelSize uint64) (Batcher, error) { + if numBatches < 1 { + return nil, ErrInvalidNumBatches + } + if batchChannelSize < 1 { + return nil, ErrInvalidBatchChannelSize + } + + batches := make(chan Batch, numBatches) + // First numBatches batches will be empty in order to simplify clients that are running + // CloseCurrentAndTakeFirstBatch on a timer and want to delay the processing of the first + // batch with actual data. This way there is no need for accounting on the client side and + // a single timer can be started immediately. + for i := uint64(0); i < numBatches; i++ { + batches <- nil + } + + batcher := &batcher{ + pendingIds: make(chan pdata.TraceID, batchChannelSize), + batches: batches, + currentBatch: make(Batch, 0, newBatchesInitialCapacity), + newBatchesInitialCapacity: newBatchesInitialCapacity, + stopchan: make(chan bool), + } + + // Single goroutine that keeps filling the current batch, contention is expected only + // when the current batch is being switched. + go func() { + for id := range batcher.pendingIds { + batcher.cbMutex.Lock() + batcher.currentBatch = append(batcher.currentBatch, id) + batcher.cbMutex.Unlock() + } + batcher.stopchan <- true + }() + + return batcher, nil +} + +func (b *batcher) AddToCurrentBatch(id pdata.TraceID) { + b.pendingIds <- id +} + +func (b *batcher) CloseCurrentAndTakeFirstBatch() (Batch, bool) { + if readBatch, ok := <-b.batches; ok { + if !b.stopped { + nextBatch := make(Batch, 0, b.newBatchesInitialCapacity) + b.cbMutex.Lock() + b.batches <- b.currentBatch + b.currentBatch = nextBatch + b.cbMutex.Unlock() + } + return readBatch, true + } + + readBatch := b.currentBatch + b.currentBatch = nil + return readBatch, false +} + +func (b *batcher) Stop() { + close(b.pendingIds) + b.stopped = <-b.stopchan + close(b.batches) +} diff --git a/processor/cascadingfilterprocessor/idbatcher/id_batcher_test.go b/processor/cascadingfilterprocessor/idbatcher/id_batcher_test.go new file mode 100644 index 000000000000..ed86da91589d --- /dev/null +++ b/processor/cascadingfilterprocessor/idbatcher/id_batcher_test.go @@ -0,0 +1,161 @@ +// Copyright The OpenTelemetry 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 idbatcher + +import ( + "runtime" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/model/pdata" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/bigendianconverter" +) + +func TestBatcherNew(t *testing.T) { + tests := []struct { + name string + numBatches uint64 + newBatchesInitialCapacity uint64 + batchChannelSize uint64 + wantErr error + }{ + {"invalid numBatches", 0, 0, 1, ErrInvalidNumBatches}, + {"invalid batchChannelSize", 1, 0, 0, ErrInvalidBatchChannelSize}, + {"valid", 1, 0, 1, nil}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := New(tt.numBatches, tt.newBatchesInitialCapacity, tt.batchChannelSize) + if err != tt.wantErr { + t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != nil { + got.Stop() + } + }) + } +} + +func TestTypicalConfig(t *testing.T) { + concurrencyTest(t, 10, 100, uint64(4*runtime.NumCPU())) +} + +func TestMinBufferedChannels(t *testing.T) { + concurrencyTest(t, 1, 0, 1) +} + +func BenchmarkConcurrentEnqueue(b *testing.B) { + ids := generateSequentialIds(1) + batcher, err := New(10, 100, uint64(4*runtime.NumCPU())) + defer batcher.Stop() + if err != nil { + b.Fatalf("Failed to create Batcher: %v", err) + } + + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + var ticked int32 + var received int32 + go func() { + for range ticker.C { + batch, _ := batcher.CloseCurrentAndTakeFirstBatch() + atomic.AddInt32(&ticked, 1) + atomic.AddInt32(&received, int32(len(batch))) + } + }() + + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + batcher.AddToCurrentBatch(ids[0]) + } + }) +} + +func concurrencyTest(t *testing.T, numBatches, newBatchesInitialCapacity, batchChannelSize uint64) { + batcher, err := New(numBatches, newBatchesInitialCapacity, batchChannelSize) + require.NoError(t, err, "Failed to create Batcher: %v", err) + + ticker := time.NewTicker(100 * time.Millisecond) + stopTicker := make(chan bool) + var got Batch + go func() { + var completedDequeues uint64 + outer: + for { + select { + case <-ticker.C: + g, _ := batcher.CloseCurrentAndTakeFirstBatch() + completedDequeues++ + if completedDequeues <= numBatches && len(g) != 0 { + t.Error("Some of the first batches were not empty") + return + } + got = append(got, g...) + case <-stopTicker: + break outer + } + } + }() + + ids := generateSequentialIds(10000) + wg := &sync.WaitGroup{} + for i := 0; i < len(ids); i++ { + wg.Add(1) + go func(id pdata.TraceID) { + batcher.AddToCurrentBatch(id) + wg.Done() + }(ids[i]) + } + + wg.Wait() + stopTicker <- true + ticker.Stop() + batcher.Stop() + + // Get all ids added to the batcher + for { + batch, ok := batcher.CloseCurrentAndTakeFirstBatch() + got = append(got, batch...) + if !ok { + break + } + } + + require.Equal(t, len(ids), len(got), "Batcher got incorrect count of traces from batches") + + idSeen := make(map[[16]byte]bool, len(ids)) + for _, id := range got { + idSeen[id.Bytes()] = true + } + + for i := 0; i < len(ids); i++ { + require.True(t, idSeen[ids[i].Bytes()], "want id %v but id was not seen", ids[i]) + } +} + +func generateSequentialIds(numIds uint64) []pdata.TraceID { + ids := make([]pdata.TraceID, numIds) + for i := uint64(0); i < numIds; i++ { + ids[i] = bigendianconverter.UInt64ToTraceID(0, i) + } + return ids +} diff --git a/processor/cascadingfilterprocessor/metrics.go b/processor/cascadingfilterprocessor/metrics.go new file mode 100644 index 000000000000..ff02509cf44c --- /dev/null +++ b/processor/cascadingfilterprocessor/metrics.go @@ -0,0 +1,150 @@ +// Copyright The OpenTelemetry 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 cascadingfilterprocessor + +import ( + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" + "go.opentelemetry.io/collector/config/configtelemetry" +) + +// Variables related to metrics specific to Cascading Filter. +var ( + statusSampled = "Sampled" + statusNotSampled = "NotSampled" + statusExceededKey = "RateExceeded" + statusSecondChance = "SecondChance" + statusSecondChanceSampled = "SecondChanceSampled" + statusSecondChanceExceeded = "SecondChanceRateExceeded" + + tagPolicyKey, _ = tag.NewKey("policy") + tagCascadingFilterDecisionKey, _ = tag.NewKey("cascading_filter_decision") + tagPolicyDecisionKey, _ = tag.NewKey("policy_decision") + + statDecisionLatencyMicroSec = stats.Int64("policy_decision_latency", "Latency (in microseconds) of a given filtering policy", "µs") + statOverallDecisionLatencyus = stats.Int64("cascading_filtering_batch_processing_latency", "Latency (in microseconds) of each run of the cascading filter timer", "µs") + + statTraceRemovalAgeSec = stats.Int64("cascading_trace_removal_age", "Time (in seconds) from arrival of a new trace until its removal from memory", "s") + statLateSpanArrivalAfterDecision = stats.Int64("cascadind_late_span_age", "Time (in seconds) from the cascading filter decision was taken and the arrival of a late span", "s") + + statPolicyEvaluationErrorCount = stats.Int64("cascading_policy_evaluation_error", "Count of cascading policy evaluation errors", stats.UnitDimensionless) + + statCascadingFilterDecision = stats.Int64("count_final_decision", "Count of traces that were filtered or not", stats.UnitDimensionless) + statPolicyDecision = stats.Int64("count_policy_decision", "Count of provisional (policy) decisions if traces were filtered or not", stats.UnitDimensionless) + + statDroppedTooEarlyCount = stats.Int64("casdading_trace_dropped_too_early", "Count of traces that needed to be dropped the configured wait time", stats.UnitDimensionless) + statNewTraceIDReceivedCount = stats.Int64("cascading_new_trace_id_received", "Counts the arrival of new traces", stats.UnitDimensionless) + statTracesOnMemoryGauge = stats.Int64("cascading_traces_on_memory", "Tracks the number of traces current on memory", stats.UnitDimensionless) +) + +// CascadingFilterMetricViews return the metrics views according to given telemetry level. +func CascadingFilterMetricViews(level configtelemetry.Level) []*view.View { + if level == configtelemetry.LevelNone { + return nil + } + + latencyDistributionAggregation := view.Distribution(1, 2, 5, 10, 25, 50, 75, 100, 150, 200, 300, 400, 500, 750, 1000, 2000, 3000, 4000, 5000, 10000, 20000, 30000, 50000) + ageDistributionAggregation := view.Distribution(1, 2, 5, 10, 20, 30, 40, 50, 60, 90, 120, 180, 300, 600, 1800, 3600, 7200) + + overallDecisionLatencyView := &view.View{ + Name: statOverallDecisionLatencyus.Name(), + Measure: statOverallDecisionLatencyus, + Description: statOverallDecisionLatencyus.Description(), + Aggregation: latencyDistributionAggregation, + } + + traceRemovalAgeView := &view.View{ + Name: statTraceRemovalAgeSec.Name(), + Measure: statTraceRemovalAgeSec, + Description: statTraceRemovalAgeSec.Description(), + Aggregation: ageDistributionAggregation, + } + + lateSpanArrivalView := &view.View{ + Name: statLateSpanArrivalAfterDecision.Name(), + Measure: statLateSpanArrivalAfterDecision, + Description: statLateSpanArrivalAfterDecision.Description(), + Aggregation: ageDistributionAggregation, + } + + countPolicyEvaluationErrorView := &view.View{ + Name: statPolicyEvaluationErrorCount.Name(), + Measure: statPolicyEvaluationErrorCount, + Description: statPolicyEvaluationErrorCount.Description(), + Aggregation: view.Sum(), + } + + countFinalDecisionView := &view.View{ + Name: statCascadingFilterDecision.Name(), + Measure: statCascadingFilterDecision, + Description: statCascadingFilterDecision.Description(), + TagKeys: []tag.Key{tagPolicyKey, tagCascadingFilterDecisionKey}, + Aggregation: view.Sum(), + } + + countPolicyDecisionsView := &view.View{ + Name: statPolicyDecision.Name(), + Measure: statPolicyDecision, + Description: statPolicyDecision.Description(), + TagKeys: []tag.Key{tagPolicyKey, tagPolicyDecisionKey}, + Aggregation: view.Sum(), + } + + policyLatencyView := &view.View{ + Name: statDecisionLatencyMicroSec.Name(), + Measure: statDecisionLatencyMicroSec, + Description: statDecisionLatencyMicroSec.Description(), + TagKeys: []tag.Key{tagPolicyKey}, + Aggregation: view.Sum(), + } + + countTraceDroppedTooEarlyView := &view.View{ + Name: statDroppedTooEarlyCount.Name(), + Measure: statDroppedTooEarlyCount, + Description: statDroppedTooEarlyCount.Description(), + Aggregation: view.Sum(), + } + countTraceIDArrivalView := &view.View{ + Name: statNewTraceIDReceivedCount.Name(), + Measure: statNewTraceIDReceivedCount, + Description: statNewTraceIDReceivedCount.Description(), + Aggregation: view.Sum(), + } + trackTracesOnMemorylView := &view.View{ + Name: statTracesOnMemoryGauge.Name(), + Measure: statTracesOnMemoryGauge, + Description: statTracesOnMemoryGauge.Description(), + Aggregation: view.LastValue(), + } + + legacyViews := []*view.View{ + overallDecisionLatencyView, + traceRemovalAgeView, + lateSpanArrivalView, + + countPolicyDecisionsView, + policyLatencyView, + countFinalDecisionView, + + countPolicyEvaluationErrorView, + countTraceDroppedTooEarlyView, + countTraceIDArrivalView, + trackTracesOnMemorylView, + } + + // return obsreport.ProcessorMetricViews(typeStr, legacyViews) + return legacyViews +} diff --git a/processor/cascadingfilterprocessor/processor.go b/processor/cascadingfilterprocessor/processor.go new file mode 100644 index 000000000000..c177b67447c1 --- /dev/null +++ b/processor/cascadingfilterprocessor/processor.go @@ -0,0 +1,633 @@ +// Copyright The OpenTelemetry 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 cascadingfilterprocessor + +import ( + "context" + "runtime" + "sync" + "sync/atomic" + "time" + + "go.opencensus.io/stats" + "go.opencensus.io/tag" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenterror" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/idbatcher" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/sampling" +) + +// Policy combines a sampling policy evaluator with the destinations to be +// used for that policy. +type Policy struct { + // Name used to identify this policy instance. + Name string + // Evaluator that decides if a trace is sampled or not by this policy instance. + Evaluator sampling.PolicyEvaluator + // ctx used to carry metric tags of each policy. + ctx context.Context + // probabilisticFilter determines whether `sampling.probability` field must be calculated and added + probabilisticFilter bool +} + +// traceKey is defined since sync.Map requires a comparable type, isolating it on its own +// type to help track usage. +type traceKey [16]byte + +// cascadingFilterSpanProcessor handles the incoming trace data and uses the given sampling +// policy to sample traces. +type cascadingFilterSpanProcessor struct { + ctx context.Context + nextConsumer consumer.Traces + start sync.Once + maxNumTraces uint64 + policies []*Policy + logger *zap.Logger + idToTrace sync.Map + policyTicker tTicker + decisionBatcher idbatcher.Batcher + deleteChan chan traceKey + numTracesOnMap uint64 + + currentSecond int64 + maxSpansPerSecond int64 + spansInCurrentSecond int64 +} + +const ( + probabilisticFilterPolicyName = "probabilistic_filter" + probabilisticRuleVale = "probabilistic" + filteredRuleValue = "filtered" + AttributeSamplingRule = "sampling.rule" + + AttributeSamplingProbability = "sampling.probability" +) + +// newTraceProcessor returns a processor.TraceProcessor that will perform Cascading Filter according to the given +// configuration. +func newTraceProcessor(logger *zap.Logger, nextConsumer consumer.Traces, cfg config.Config) (component.TracesProcessor, error) { + if nextConsumer == nil { + return nil, componenterror.ErrNilNextConsumer + } + + return newCascadingFilterSpanProcessor(logger, nextConsumer, cfg) +} + +func newCascadingFilterSpanProcessor(logger *zap.Logger, nextConsumer consumer.Traces, cfg config.Config) (*cascadingFilterSpanProcessor, error) { + numDecisionBatches := uint64(cfg.DecisionWait.Seconds()) + inBatcher, err := idbatcher.New(numDecisionBatches, cfg.ExpectedNewTracesPerSec, uint64(2*runtime.NumCPU())) + if err != nil { + return nil, err + } + + ctx := context.Background() + var policies []*Policy + + // This must be always first as it must select traces independently of other policies + if cfg.ProbabilisticFilteringRatio != nil && *cfg.ProbabilisticFilteringRatio > 0.0 { + policyCtx, err := tag.New(ctx, tag.Upsert(tagPolicyKey, probabilisticFilterPolicyName)) + if err != nil { + return nil, err + } + eval, err := getProbabilisticFilterEvaluator(logger, int64(float32(cfg.SpansPerSecond)**cfg.ProbabilisticFilteringRatio)) + if err != nil { + return nil, err + } + policy := &Policy{ + Name: probabilisticFilterPolicyName, + Evaluator: eval, + ctx: policyCtx, + probabilisticFilter: true, + } + policies = append(policies, policy) + } + + for i := range cfg.PolicyCfgs { + policyCfg := &cfg.PolicyCfgs[i] + policyCtx, err := tag.New(ctx, tag.Upsert(tagPolicyKey, policyCfg.Name)) + if err != nil { + return nil, err + } + eval, err := getPolicyEvaluator(logger, policyCfg) + if err != nil { + return nil, err + } + policy := &Policy{ + Name: policyCfg.Name, + Evaluator: eval, + ctx: policyCtx, + probabilisticFilter: false, + } + policies = append(policies, policy) + } + + cfsp := &cascadingFilterSpanProcessor{ + ctx: ctx, + nextConsumer: nextConsumer, + maxNumTraces: cfg.NumTraces, + maxSpansPerSecond: cfg.SpansPerSecond, + logger: logger, + decisionBatcher: inBatcher, + policies: policies, + } + + cfsp.policyTicker = &policyTicker{onTick: cfsp.samplingPolicyOnTick} + cfsp.deleteChan = make(chan traceKey, cfg.NumTraces) + + return cfsp, nil +} + +func getPolicyEvaluator(logger *zap.Logger, cfg *config.PolicyCfg) (sampling.PolicyEvaluator, error) { + return sampling.NewFilter(logger, cfg) +} + +func getProbabilisticFilterEvaluator(logger *zap.Logger, maxSpanRate int64) (sampling.PolicyEvaluator, error) { + return sampling.NewProbabilisticFilter(logger, maxSpanRate) +} + +type policyMetrics struct { + idNotFoundOnMapCount, evaluateErrorCount, decisionSampled, decisionNotSampled int64 +} + +func (cfsp *cascadingFilterSpanProcessor) updateRate(currSecond int64, numSpans int64) sampling.Decision { + if cfsp.currentSecond != currSecond { + cfsp.currentSecond = currSecond + cfsp.spansInCurrentSecond = 0 + } + + spansInSecondIfSampled := cfsp.spansInCurrentSecond + numSpans + if spansInSecondIfSampled <= cfsp.maxSpansPerSecond { + cfsp.spansInCurrentSecond = spansInSecondIfSampled + return sampling.Sampled + } + + return sampling.NotSampled +} + +func (cfsp *cascadingFilterSpanProcessor) samplingPolicyOnTick() { + metrics := policyMetrics{} + + startTime := time.Now() + batch, _ := cfsp.decisionBatcher.CloseCurrentAndTakeFirstBatch() + batchLen := len(batch) + cfsp.logger.Debug("Sampling Policy Evaluation ticked") + + currSecond := time.Now().Unix() + + totalSpans := int64(0) + selectedByProbabilisticFilterSpans := int64(0) + + // The first run applies decisions to batches, executing each policy separately + for _, id := range batch { + d, ok := cfsp.idToTrace.Load(traceKey(id.Bytes())) + if !ok { + metrics.idNotFoundOnMapCount++ + continue + } + trace := d.(*sampling.TraceData) + trace.DecisionTime = time.Now() + totalSpans += trace.SpanCount + + provisionalDecision, _ := cfsp.makeProvisionalDecision(id, trace) + if provisionalDecision == sampling.Sampled { + trace.FinalDecision = cfsp.updateRate(currSecond, trace.SpanCount) + if trace.FinalDecision == sampling.Sampled { + if trace.SelectedByProbabilisticFilter { + selectedByProbabilisticFilterSpans += trace.SpanCount + } + err := stats.RecordWithTags( + cfsp.ctx, + []tag.Mutator{tag.Insert(tagCascadingFilterDecisionKey, statusSampled)}, + statCascadingFilterDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Sampling Policy Evaluation error on first run tick", zap.Error(err)) + } + } else { + err := stats.RecordWithTags( + cfsp.ctx, + []tag.Mutator{tag.Insert(tagCascadingFilterDecisionKey, statusExceededKey)}, + statCascadingFilterDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Sampling Policy Evaluation error on first run tick", zap.Error(err)) + } + } + } else if provisionalDecision == sampling.SecondChance { + trace.FinalDecision = sampling.SecondChance + } else { + trace.FinalDecision = provisionalDecision + err := stats.RecordWithTags( + cfsp.ctx, + []tag.Mutator{tag.Insert(tagCascadingFilterDecisionKey, statusNotSampled)}, + statCascadingFilterDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Sampling Policy Evaluation error on first run tick", zap.Error(err)) + } + } + } + + // The second run executes the decisions and makes "SecondChance" decisions in the meantime + for _, id := range batch { + d, ok := cfsp.idToTrace.Load(traceKey(id.Bytes())) + if !ok { + continue + } + trace := d.(*sampling.TraceData) + if trace.FinalDecision == sampling.SecondChance { + trace.FinalDecision = cfsp.updateRate(currSecond, trace.SpanCount) + if trace.FinalDecision == sampling.Sampled { + err := stats.RecordWithTags( + cfsp.ctx, + []tag.Mutator{tag.Insert(tagCascadingFilterDecisionKey, statusSecondChanceSampled)}, + statCascadingFilterDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Sampling Policy Evaluation error on second run tick", zap.Error(err)) + } + } else { + err := stats.RecordWithTags( + cfsp.ctx, + []tag.Mutator{tag.Insert(tagCascadingFilterDecisionKey, statusSecondChanceExceeded)}, + statCascadingFilterDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Sampling Policy Evaluation error on second run tick", zap.Error(err)) + } + } + } + + // Sampled or not, remove the batches + trace.Lock() + traceBatches := trace.ReceivedBatches + trace.ReceivedBatches = nil + trace.Unlock() + + if trace.FinalDecision == sampling.Sampled { + metrics.decisionSampled++ + + // Combine all individual batches into a single batch so + // consumers may operate on the entire trace + allSpans := pdata.NewTraces() + for j := 0; j < len(traceBatches); j++ { + batch := traceBatches[j] + batch.ResourceSpans().MoveAndAppendTo(allSpans.ResourceSpans()) + } + + if trace.SelectedByProbabilisticFilter { + updateProbabilisticRateTag(allSpans, selectedByProbabilisticFilterSpans, totalSpans) + } else { + updateFilteringTag(allSpans) + } + + err := cfsp.nextConsumer.ConsumeTraces(cfsp.ctx, allSpans) + if err != nil { + cfsp.logger.Error("Sampling Policy Evaluation error on consuming traces", zap.Error(err)) + } + } else { + metrics.decisionNotSampled++ + } + } + + stats.Record(cfsp.ctx, + statOverallDecisionLatencyus.M(int64(time.Since(startTime)/time.Microsecond)), + statDroppedTooEarlyCount.M(metrics.idNotFoundOnMapCount), + statPolicyEvaluationErrorCount.M(metrics.evaluateErrorCount), + statTracesOnMemoryGauge.M(int64(atomic.LoadUint64(&cfsp.numTracesOnMap)))) + + cfsp.logger.Debug("Sampling policy evaluation completed", + zap.Int("batch.len", batchLen), + zap.Int64("sampled", metrics.decisionSampled), + zap.Int64("notSampled", metrics.decisionNotSampled), + zap.Int64("droppedPriorToEvaluation", metrics.idNotFoundOnMapCount), + zap.Int64("policyEvaluationErrors", metrics.evaluateErrorCount), + ) +} + +func updateProbabilisticRateTag(traces pdata.Traces, probabilisticSpans int64, allSpans int64) { + ratio := float64(probabilisticSpans) / float64(allSpans) + + rs := traces.ResourceSpans() + + for i := 0; i < rs.Len(); i++ { + ils := rs.At(i).InstrumentationLibrarySpans() + for j := 0; j < ils.Len(); j++ { + spans := ils.At(j).Spans() + for k := 0; k < spans.Len(); k++ { + attrs := spans.At(k).Attributes() + av, found := attrs.Get(AttributeSamplingProbability) + if found && av.Type() == pdata.AttributeValueTypeDouble { + av.SetDoubleVal(av.DoubleVal() * ratio) + } else { + attrs.UpsertDouble(AttributeSamplingProbability, ratio) + } + attrs.UpsertString(AttributeSamplingRule, probabilisticRuleVale) + } + } + } +} + +func updateFilteringTag(traces pdata.Traces) { + rs := traces.ResourceSpans() + + for i := 0; i < rs.Len(); i++ { + ils := rs.At(i).InstrumentationLibrarySpans() + for j := 0; j < ils.Len(); j++ { + spans := ils.At(j).Spans() + for k := 0; k < spans.Len(); k++ { + attrs := spans.At(k).Attributes() + attrs.UpsertString(AttributeSamplingRule, filteredRuleValue) + } + } + } +} + +func (cfsp *cascadingFilterSpanProcessor) makeProvisionalDecision(id pdata.TraceID, trace *sampling.TraceData) (sampling.Decision, *Policy) { + provisionalDecision := sampling.Unspecified + var matchingPolicy *Policy + + for i, policy := range cfsp.policies { + policyEvaluateStartTime := time.Now() + decision := policy.Evaluator.Evaluate(id, trace) + stats.Record( + policy.ctx, + statDecisionLatencyMicroSec.M(int64(time.Since(policyEvaluateStartTime)/time.Microsecond))) + + trace.Decisions[i] = decision + + switch decision { + case sampling.Sampled: + // any single policy that decides to sample will cause the decision to be sampled + // the nextConsumer will get the context from the first matching policy + provisionalDecision = sampling.Sampled + if matchingPolicy == nil { + matchingPolicy = policy + } + + if policy.probabilisticFilter { + trace.SelectedByProbabilisticFilter = true + } + + err := stats.RecordWithTags( + policy.ctx, + []tag.Mutator{tag.Insert(tagPolicyDecisionKey, statusSampled)}, + statPolicyDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Making provisional decision error", zap.Error(err)) + } + case sampling.NotSampled: + if provisionalDecision == sampling.Unspecified { + provisionalDecision = sampling.NotSampled + } + err := stats.RecordWithTags( + policy.ctx, + []tag.Mutator{tag.Insert(tagPolicyDecisionKey, statusNotSampled)}, + statPolicyDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Making provisional decision error", zap.Error(err)) + } + case sampling.SecondChance: + if provisionalDecision != sampling.Sampled { + provisionalDecision = sampling.SecondChance + } + + err := stats.RecordWithTags( + policy.ctx, + []tag.Mutator{tag.Insert(tagPolicyDecisionKey, statusSecondChance)}, + statPolicyDecision.M(int64(1)), + ) + if err != nil { + cfsp.logger.Error("Making provisional decision error", zap.Error(err)) + } + } + } + + return provisionalDecision, matchingPolicy +} + +// ConsumeTraceData is required by the SpanProcessor interface. +func (cfsp *cascadingFilterSpanProcessor) ConsumeTraces(ctx context.Context, td pdata.Traces) error { + cfsp.start.Do(func() { + cfsp.logger.Info("First trace data arrived, starting cascading_filter timers") + cfsp.policyTicker.Start(1 * time.Second) + }) + resourceSpans := td.ResourceSpans() + for i := 0; i < resourceSpans.Len(); i++ { + resourceSpan := resourceSpans.At(i) + cfsp.processTraces(resourceSpan) + } + return nil +} + +func (cfsp *cascadingFilterSpanProcessor) groupSpansByTraceKey(resourceSpans pdata.ResourceSpans) map[traceKey][]*pdata.Span { + idToSpans := make(map[traceKey][]*pdata.Span) + ilss := resourceSpans.InstrumentationLibrarySpans() + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + spansLen := ils.Spans().Len() + for k := 0; k < spansLen; k++ { + span := ils.Spans().At(k) + tk := traceKey(span.TraceID().Bytes()) + if len(tk) != 16 { + cfsp.logger.Warn("Span without valid TraceId") + } + idToSpans[tk] = append(idToSpans[tk], &span) + } + } + return idToSpans +} + +func (cfsp *cascadingFilterSpanProcessor) processTraces(resourceSpans pdata.ResourceSpans) { + // Group spans per their traceId to minimize contention on idToTrace + idToSpans := cfsp.groupSpansByTraceKey(resourceSpans) + var newTraceIDs int64 + for id, spans := range idToSpans { + lenSpans := int64(len(spans)) + lenPolicies := len(cfsp.policies) + initialDecisions := make([]sampling.Decision, lenPolicies) + for i := 0; i < lenPolicies; i++ { + initialDecisions[i] = sampling.Pending + } + initialTraceData := &sampling.TraceData{ + Decisions: initialDecisions, + ArrivalTime: time.Now(), + SpanCount: lenSpans, + } + d, loaded := cfsp.idToTrace.LoadOrStore(id, initialTraceData) + + actualData := d.(*sampling.TraceData) + if loaded { + // PMM: why actualData is not updated with new trace? + atomic.AddInt64(&actualData.SpanCount, lenSpans) + } else { + newTraceIDs++ + cfsp.decisionBatcher.AddToCurrentBatch(pdata.NewTraceID(id)) + atomic.AddUint64(&cfsp.numTracesOnMap, 1) + postDeletion := false + currTime := time.Now() + + for !postDeletion { + select { + case cfsp.deleteChan <- id: + postDeletion = true + default: + // Note this is a buffered channel, so this will only delete excessive traces (if they exist) + traceKeyToDrop := <-cfsp.deleteChan + cfsp.dropTrace(traceKeyToDrop, currTime) + } + } + } + + for i, policy := range cfsp.policies { + var traceTd pdata.Traces + actualData.Lock() + actualDecision := actualData.Decisions[i] + // If decision is pending, we want to add the new spans still under the lock, so the decision doesn't happen + // in between the transition from pending. + if actualDecision == sampling.Pending { + // Add the spans to the trace, but only once for all policy, otherwise same spans will + // be duplicated in the final trace. + traceTd = prepareTraceBatch(resourceSpans, spans) + actualData.ReceivedBatches = append(actualData.ReceivedBatches, traceTd) + actualData.Unlock() + break + } + actualData.Unlock() + + // This section is run in case the decision was already applied earlier + switch actualDecision { + case sampling.Pending: + // All process for pending done above, keep the case so it doesn't go to default. + case sampling.SecondChance: + // It shouldn't normally get here, keep the case so it doesn't go to default, like above. + case sampling.Sampled: + // Forward the spans to the policy destinations + traceTd := prepareTraceBatch(resourceSpans, spans) + if err := cfsp.nextConsumer.ConsumeTraces(policy.ctx, traceTd); err != nil { + cfsp.logger.Warn("Error sending late arrived spans to destination", + zap.String("policy", policy.Name), + zap.Error(err)) + } + fallthrough // so OnLateArrivingSpans is also called for decision Sampled. + case sampling.NotSampled: + err := policy.Evaluator.OnLateArrivingSpans(actualDecision, spans) + // TODO: + // Check this error + _ = err + + stats.Record(cfsp.ctx, statLateSpanArrivalAfterDecision.M(int64(time.Since(actualData.DecisionTime)/time.Second))) + + default: + cfsp.logger.Warn("Encountered unexpected sampling decision", + zap.String("policy", policy.Name), + zap.Int("decision", int(actualDecision))) + } + + // At this point the late arrival has been passed to nextConsumer. Need to break out of the policy loop + // so that it isn't sent to nextConsumer more than once when multiple policies chose to sample + if actualDecision == sampling.Sampled { + break + } + } + } + + stats.Record(cfsp.ctx, statNewTraceIDReceivedCount.M(newTraceIDs)) +} + +// func (cfsp *cascadingFilterSpanProcessor) GetCapabilities() component.ProcessorCapabilities { +// return component.ProcessorCapabilities{MutatesConsumedData: false} +// } + +func (cfsp *cascadingFilterSpanProcessor) Capabilities() consumer.Capabilities { + return consumer.Capabilities{MutatesData: false} +} + +// Start is invoked during service startup. +func (cfsp *cascadingFilterSpanProcessor) Start(context.Context, component.Host) error { + return nil +} + +// Shutdown is invoked during service shutdown. +func (cfsp *cascadingFilterSpanProcessor) Shutdown(context.Context) error { + return nil +} + +func (cfsp *cascadingFilterSpanProcessor) dropTrace(traceID traceKey, deletionTime time.Time) { + var trace *sampling.TraceData + if d, ok := cfsp.idToTrace.Load(traceID); ok { + trace = d.(*sampling.TraceData) + cfsp.idToTrace.Delete(traceID) + // Subtract one from numTracesOnMap per https://godoc.org/sync/atomic#AddUint64 + atomic.AddUint64(&cfsp.numTracesOnMap, ^uint64(0)) + } + if trace == nil { + cfsp.logger.Error("Attempt to delete traceID not on table") + return + } + + stats.Record(cfsp.ctx, statTraceRemovalAgeSec.M(int64(deletionTime.Sub(trace.ArrivalTime)/time.Second))) +} + +func prepareTraceBatch(rss pdata.ResourceSpans, spans []*pdata.Span) pdata.Traces { + traceTd := pdata.NewTraces() + rs := traceTd.ResourceSpans().AppendEmpty() + rss.Resource().CopyTo(rs.Resource()) + ils := rs.InstrumentationLibrarySpans().AppendEmpty() + ilsSpans := ils.Spans() + for _, span := range spans { + span.CopyTo(ilsSpans.AppendEmpty()) + } + return traceTd +} + +// tTicker interface allows easier testing of ticker related functionality used by cascadingfilterprocessor +type tTicker interface { + // Start sets the frequency of the ticker and starts the periodic calls to OnTick. + Start(d time.Duration) + // OnTick is called when the ticker fires. + OnTick() + // Stops firing the ticker. + Stop() +} + +type policyTicker struct { + ticker *time.Ticker + onTick func() +} + +func (pt *policyTicker) Start(d time.Duration) { + pt.ticker = time.NewTicker(d) + go func() { + for range pt.ticker.C { + pt.OnTick() + } + }() +} +func (pt *policyTicker) OnTick() { + pt.onTick() +} +func (pt *policyTicker) Stop() { + pt.ticker.Stop() +} + +var _ tTicker = (*policyTicker)(nil) diff --git a/processor/cascadingfilterprocessor/processor_test.go b/processor/cascadingfilterprocessor/processor_test.go new file mode 100644 index 000000000000..e7825fb9cfd7 --- /dev/null +++ b/processor/cascadingfilterprocessor/processor_test.go @@ -0,0 +1,575 @@ +// Copyright The OpenTelemetry 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 cascadingfilterprocessor + +import ( + "context" + "errors" + "sort" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/bigendianconverter" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/idbatcher" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/sampling" +) + +const ( + defaultTestDecisionWait = 30 * time.Second +) + +//nolint:unused +var testPolicy = []config.PolicyCfg{{ + Name: "test-policy", + SpansPerSecond: 1000, +}} + +func TestSequentialTraceArrival(t *testing.T) { + traceIds, batches := generateIdsAndBatches(128) + cfg := config.Config{ + DecisionWait: defaultTestDecisionWait, + NumTraces: uint64(2 * len(traceIds)), + ExpectedNewTracesPerSec: 64, + PolicyCfgs: testPolicy, + } + sp, err := newTraceProcessor(zap.NewNop(), consumertest.NewNop(), cfg) + require.NoError(t, err) + tsp := sp.(*cascadingFilterSpanProcessor) + for _, batch := range batches { + assert.NoError(t, tsp.ConsumeTraces(context.Background(), batch)) + } + + for i := range traceIds { + d, ok := tsp.idToTrace.Load(traceKey(traceIds[i].Bytes())) + require.True(t, ok, "Missing expected traceId") + v := d.(*sampling.TraceData) + require.Equal(t, int64(i+1), v.SpanCount, "Incorrect number of spans for entry %d", i) + } +} + +func TestConcurrentTraceArrival(t *testing.T) { + traceIds, batches := generateIdsAndBatches(128) + + var wg sync.WaitGroup + cfg := config.Config{ + DecisionWait: defaultTestDecisionWait, + NumTraces: uint64(2 * len(traceIds)), + ExpectedNewTracesPerSec: 64, + PolicyCfgs: testPolicy, + } + sp, err := newTraceProcessor(zap.NewNop(), consumertest.NewNop(), cfg) + require.NoError(t, err) + tsp := sp.(*cascadingFilterSpanProcessor) + for _, batch := range batches { + // Add the same traceId twice. + wg.Add(2) + go func(td pdata.Traces) { + if err := tsp.ConsumeTraces(context.Background(), td); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + wg.Done() + }(batch) + go func(td pdata.Traces) { + if err := tsp.ConsumeTraces(context.Background(), td); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + wg.Done() + }(batch) + } + + wg.Wait() + + for i := range traceIds { + d, ok := tsp.idToTrace.Load(traceKey(traceIds[i].Bytes())) + require.True(t, ok, "Missing expected traceId") + v := d.(*sampling.TraceData) + require.Equal(t, int64(i+1)*2, v.SpanCount, "Incorrect number of spans for entry %d", i) + } +} + +func TestSequentialTraceMapSize(t *testing.T) { + traceIds, batches := generateIdsAndBatches(210) + const maxSize = 100 + cfg := config.Config{ + DecisionWait: defaultTestDecisionWait, + NumTraces: uint64(maxSize), + ExpectedNewTracesPerSec: 64, + PolicyCfgs: testPolicy, + } + sp, err := newTraceProcessor(zap.NewNop(), consumertest.NewNop(), cfg) + require.NoError(t, err) + tsp := sp.(*cascadingFilterSpanProcessor) + for _, batch := range batches { + if err := tsp.ConsumeTraces(context.Background(), batch); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + } + + // On sequential insertion it is possible to know exactly which traces should be still on the map. + for i := 0; i < len(traceIds)-maxSize; i++ { + _, ok := tsp.idToTrace.Load(traceKey(traceIds[i].Bytes())) + require.False(t, ok, "Found unexpected traceId[%d] still on map (id: %v)", i, traceIds[i]) + } +} + +func TestConcurrentTraceMapSize(t *testing.T) { + _, batches := generateIdsAndBatches(210) + const maxSize = 100 + var wg sync.WaitGroup + cfg := config.Config{ + DecisionWait: defaultTestDecisionWait, + NumTraces: uint64(maxSize), + ExpectedNewTracesPerSec: 64, + PolicyCfgs: testPolicy, + } + sp, err := newTraceProcessor(zap.NewNop(), consumertest.NewNop(), cfg) + require.NoError(t, err) + tsp := sp.(*cascadingFilterSpanProcessor) + for _, batch := range batches { + wg.Add(1) + go func(td pdata.Traces) { + if err := tsp.ConsumeTraces(context.Background(), td); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + wg.Done() + }(batch) + } + + wg.Wait() + + // Since we can't guarantee the order of insertion the only thing that can be checked is + // if the number of traces on the map matches the expected value. + cnt := 0 + tsp.idToTrace.Range(func(_ interface{}, _ interface{}) bool { + cnt++ + return true + }) + require.Equal(t, maxSize, cnt, "Incorrect traces count on idToTrace") +} + +func TestSamplingPolicyTypicalPath(t *testing.T) { + const maxSize = 100 + const decisionWaitSeconds = 5 + // For this test explicitly control the timer calls and batcher, and set a mock + // sampling policy evaluator. + msp := new(consumertest.TracesSink) + mpe := &mockPolicyEvaluator{} + mtt := &manualTTicker{} + tsp := &cascadingFilterSpanProcessor{ + ctx: context.Background(), + nextConsumer: msp, + maxNumTraces: maxSize, + logger: zap.NewNop(), + decisionBatcher: newSyncIDBatcher(decisionWaitSeconds), + policies: []*Policy{{Name: "mock-policy", Evaluator: mpe, ctx: context.TODO()}}, + deleteChan: make(chan traceKey, maxSize), + policyTicker: mtt, + maxSpansPerSecond: 10000, + } + + _, batches := generateIdsAndBatches(210) + currItem := 0 + numSpansPerBatchWindow := 10 + // First evaluations shouldn't have anything to evaluate, until decision wait time passed. + for evalNum := 0; evalNum < decisionWaitSeconds; evalNum++ { + for ; currItem < numSpansPerBatchWindow*(evalNum+1); currItem++ { + if err := tsp.ConsumeTraces(context.Background(), batches[currItem]); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + require.True(t, mtt.Started, "Time ticker was expected to have started") + } + tsp.samplingPolicyOnTick() + require.False( + t, + msp.SpanCount() != 0 || mpe.EvaluationCount != 0, + "policy for initial items was evaluated before decision wait period", + ) + } + + // Now the first batch that waited the decision period. + mpe.NextDecision = sampling.Sampled + tsp.samplingPolicyOnTick() + require.False( + t, + msp.SpanCount() == 0 || mpe.EvaluationCount == 0, + "policy should have been evaluated totalspans == %d and evaluationcount == %d", + msp.SpanCount(), + mpe.EvaluationCount, + ) + + require.Equal(t, numSpansPerBatchWindow, msp.SpanCount(), "not all spans of first window were accounted for") + + // Late span of a sampled trace should be sent directly down the pipeline exporter + if err := tsp.ConsumeTraces(context.Background(), batches[0]); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + expectedNumWithLateSpan := numSpansPerBatchWindow + 1 + require.Equal(t, expectedNumWithLateSpan, msp.SpanCount(), "late span was not accounted for") + require.Equal(t, 1, mpe.LateArrivingSpanCount, "policy was not notified of the late span") +} + +func TestSamplingMultiplePolicies(t *testing.T) { + const maxSize = 100 + const decisionWaitSeconds = 5 + // For this test explicitly control the timer calls and batcher, and set a mock + // sampling policy evaluator. + msp := new(consumertest.TracesSink) + mpe1 := &mockPolicyEvaluator{} + mpe2 := &mockPolicyEvaluator{} + mtt := &manualTTicker{} + tsp := &cascadingFilterSpanProcessor{ + ctx: context.Background(), + nextConsumer: msp, + maxNumTraces: maxSize, + logger: zap.NewNop(), + decisionBatcher: newSyncIDBatcher(decisionWaitSeconds), + policies: []*Policy{ + { + Name: "policy-1", Evaluator: mpe1, ctx: context.TODO(), + }, + { + Name: "policy-2", Evaluator: mpe2, ctx: context.TODO(), + }}, + deleteChan: make(chan traceKey, maxSize), + policyTicker: mtt, + maxSpansPerSecond: 10000, + } + + _, batches := generateIdsAndBatches(210) + currItem := 0 + numSpansPerBatchWindow := 10 + // First evaluations shouldn't have anything to evaluate, until decision wait time passed. + for evalNum := 0; evalNum < decisionWaitSeconds; evalNum++ { + for ; currItem < numSpansPerBatchWindow*(evalNum+1); currItem++ { + if err := tsp.ConsumeTraces(context.Background(), batches[currItem]); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + + require.True(t, mtt.Started, "Time ticker was expected to have started") + } + tsp.samplingPolicyOnTick() + require.False( + t, + msp.SpanCount() != 0 || mpe1.EvaluationCount != 0 || mpe2.EvaluationCount != 0, + "policy for initial items was evaluated before decision wait period", + ) + } + + // Both policies will decide to sample + mpe1.NextDecision = sampling.Sampled + mpe2.NextDecision = sampling.Sampled + tsp.samplingPolicyOnTick() + require.False( + t, + msp.SpanCount() == 0 || mpe1.EvaluationCount == 0 || mpe2.EvaluationCount == 0, + "policy should have been evaluated totalspans == %d and evaluationcount(1) == %d and evaluationcount(2) == %d", + msp.SpanCount(), + mpe1.EvaluationCount, + mpe2.EvaluationCount, + ) + + require.Equal(t, numSpansPerBatchWindow, msp.SpanCount(), "nextConsumer should've been called with exactly 1 batch of spans") + + // Late span of a sampled trace should be sent directly down the pipeline exporter + if err := tsp.ConsumeTraces(context.Background(), batches[0]); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + + expectedNumWithLateSpan := numSpansPerBatchWindow + 1 + require.Equal(t, expectedNumWithLateSpan, msp.SpanCount(), "late span was not accounted for") + require.Equal(t, 1, mpe1.LateArrivingSpanCount, "1st policy was not notified of the late span") + require.Equal(t, 0, mpe2.LateArrivingSpanCount, "2nd policy should not have been notified of the late span") +} + +func TestSamplingPolicyDecisionNotSampled(t *testing.T) { + const maxSize = 100 + const decisionWaitSeconds = 5 + // For this test explicitly control the timer calls and batcher, and set a mock + // sampling policy evaluator. + msp := new(consumertest.TracesSink) + mpe := &mockPolicyEvaluator{} + mtt := &manualTTicker{} + tsp := &cascadingFilterSpanProcessor{ + ctx: context.Background(), + nextConsumer: msp, + maxNumTraces: maxSize, + logger: zap.NewNop(), + decisionBatcher: newSyncIDBatcher(decisionWaitSeconds), + policies: []*Policy{{Name: "mock-policy", Evaluator: mpe, ctx: context.TODO()}}, + deleteChan: make(chan traceKey, maxSize), + policyTicker: mtt, + maxSpansPerSecond: 10000, + } + + _, batches := generateIdsAndBatches(210) + currItem := 0 + numSpansPerBatchWindow := 10 + // First evaluations shouldn't have anything to evaluate, until decision wait time passed. + for evalNum := 0; evalNum < decisionWaitSeconds; evalNum++ { + for ; currItem < numSpansPerBatchWindow*(evalNum+1); currItem++ { + if err := tsp.ConsumeTraces(context.Background(), batches[currItem]); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + require.True(t, mtt.Started, "Time ticker was expected to have started") + } + tsp.samplingPolicyOnTick() + require.False( + t, + msp.SpanCount() != 0 || mpe.EvaluationCount != 0, + "policy for initial items was evaluated before decision wait period", + ) + } + + // Now the first batch that waited the decision period. + mpe.NextDecision = sampling.NotSampled + tsp.samplingPolicyOnTick() + require.EqualValues(t, 0, msp.SpanCount(), "exporter should have received zero spans") + require.EqualValues(t, 4, mpe.EvaluationCount, "policy should have been evaluated 4 times") + + // Late span of a non-sampled trace should be ignored + + if err := tsp.ConsumeTraces(context.Background(), batches[0]); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + require.Equal(t, 0, msp.SpanCount()) + require.Equal(t, 1, mpe.LateArrivingSpanCount, "policy was not notified of the late span") + + mpe.NextDecision = sampling.Unspecified + mpe.NextError = errors.New("mock policy error") + tsp.samplingPolicyOnTick() + require.EqualValues(t, 0, msp.SpanCount(), "exporter should have received zero spans") + require.EqualValues(t, 6, mpe.EvaluationCount, "policy should have been evaluated 6 times") + + // Late span of a non-sampled trace should be ignored + if err := tsp.ConsumeTraces(context.Background(), batches[0]); err != nil { + t.Errorf("Failed consuming traces: %v", err) + } + require.Equal(t, 0, msp.SpanCount()) + require.Equal(t, 2, mpe.LateArrivingSpanCount, "policy was not notified of the late span") +} + +func TestMultipleBatchesAreCombinedIntoOne(t *testing.T) { + const maxSize = 100 + const decisionWaitSeconds = 1 + // For this test explicitly control the timer calls and batcher, and set a mock + // sampling policy evaluator. + msp := new(consumertest.TracesSink) + mpe := &mockPolicyEvaluator{} + mtt := &manualTTicker{} + tsp := &cascadingFilterSpanProcessor{ + ctx: context.Background(), + nextConsumer: msp, + maxNumTraces: maxSize, + logger: zap.NewNop(), + decisionBatcher: newSyncIDBatcher(decisionWaitSeconds), + policies: []*Policy{{Name: "mock-policy", Evaluator: mpe, ctx: context.TODO()}}, + deleteChan: make(chan traceKey, maxSize), + policyTicker: mtt, + maxSpansPerSecond: 10000, + } + + mpe.NextDecision = sampling.Sampled + + traceIds, batches := generateIdsAndBatches(3) + for _, batch := range batches { + require.NoError(t, tsp.ConsumeTraces(context.Background(), batch)) + } + + tsp.samplingPolicyOnTick() + tsp.samplingPolicyOnTick() + + require.EqualValues(t, 3, len(msp.AllTraces()), "There should be three batches, one for each trace") + + expectedSpanIds := make(map[int][]pdata.SpanID) + expectedSpanIds[0] = []pdata.SpanID{ + bigendianconverter.UInt64ToSpanID(uint64(1)), + } + expectedSpanIds[1] = []pdata.SpanID{ + bigendianconverter.UInt64ToSpanID(uint64(2)), + bigendianconverter.UInt64ToSpanID(uint64(3)), + } + expectedSpanIds[2] = []pdata.SpanID{ + bigendianconverter.UInt64ToSpanID(uint64(4)), + bigendianconverter.UInt64ToSpanID(uint64(5)), + bigendianconverter.UInt64ToSpanID(uint64(6)), + } + + receivedTraces := msp.AllTraces() + for i, traceID := range traceIds { + trace := findTrace(receivedTraces, traceID) + require.NotNil(t, trace, "Trace was not received. TraceId %s", traceID.HexString()) + require.EqualValues(t, i+1, trace.SpanCount(), "The trace should have all of its spans in a single batch") + + expected := expectedSpanIds[i] + got := collectSpanIds(trace) + + // might have received out of order, sort for comparison + sort.Slice(got, func(i, j int) bool { + a := bigendianconverter.SpanIDToUInt64(got[i]) + b := bigendianconverter.SpanIDToUInt64(got[j]) + return a < b + }) + + require.EqualValues(t, expected, got) + } +} + +//nolint:unused +func collectSpanIds(trace *pdata.Traces) []pdata.SpanID { + spanIDs := make([]pdata.SpanID, 0) + + for i := 0; i < trace.ResourceSpans().Len(); i++ { + ilss := trace.ResourceSpans().At(i).InstrumentationLibrarySpans() + + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + + for k := 0; k < ils.Spans().Len(); k++ { + span := ils.Spans().At(k) + spanIDs = append(spanIDs, span.SpanID()) + } + } + } + + return spanIDs +} + +//nolint:unused +func findTrace(a []pdata.Traces, traceID pdata.TraceID) *pdata.Traces { + for _, batch := range a { + id := batch.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0).TraceID() + if traceID.Bytes() == id.Bytes() { + return &batch + } + } + return nil +} + +func generateIdsAndBatches(numIds int) ([]pdata.TraceID, []pdata.Traces) { + traceIds := make([]pdata.TraceID, numIds) + spanID := 0 + var tds []pdata.Traces + for i := 0; i < numIds; i++ { + traceIds[i] = bigendianconverter.UInt64ToTraceID(1, uint64(i+1)) + // Send each span in a separate batch + for j := 0; j <= i; j++ { + td := simpleTraces() + span := td.ResourceSpans().At(0).InstrumentationLibrarySpans().At(0).Spans().At(0) + span.SetTraceID(traceIds[i]) + + spanID++ + span.SetSpanID(bigendianconverter.UInt64ToSpanID(uint64(spanID))) + tds = append(tds, td) + } + } + + return traceIds, tds +} + +//nolint:unused +func simpleTraces() pdata.Traces { + return simpleTracesWithID(pdata.NewTraceID([16]byte{1, 2, 3, 4})) +} + +//nolint:unused +func simpleTracesWithID(traceID pdata.TraceID) pdata.Traces { + traces := pdata.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + + ils := rs.InstrumentationLibrarySpans().AppendEmpty() + span := ils.Spans().AppendEmpty() + span.SetTraceID(traceID) + + return traces +} + +type mockPolicyEvaluator struct { + NextDecision sampling.Decision + NextError error + EvaluationCount int + LateArrivingSpanCount int + OnDroppedSpanCount int +} + +var _ sampling.PolicyEvaluator = (*mockPolicyEvaluator)(nil) + +func (m *mockPolicyEvaluator) OnLateArrivingSpans(sampling.Decision, []*pdata.Span) error { + m.LateArrivingSpanCount++ + return m.NextError +} +func (m *mockPolicyEvaluator) Evaluate(_ pdata.TraceID, _ *sampling.TraceData) sampling.Decision { + m.EvaluationCount++ + return m.NextDecision +} + +type manualTTicker struct { + Started bool +} + +var _ tTicker = (*manualTTicker)(nil) + +func (t *manualTTicker) Start(time.Duration) { + t.Started = true +} + +func (t *manualTTicker) OnTick() { +} + +func (t *manualTTicker) Stop() { +} + +type syncIDBatcher struct { + sync.Mutex + openBatch idbatcher.Batch + batchPipe chan idbatcher.Batch +} + +var _ idbatcher.Batcher = (*syncIDBatcher)(nil) + +func newSyncIDBatcher(numBatches uint64) idbatcher.Batcher { + batches := make(chan idbatcher.Batch, numBatches) + for i := uint64(0); i < numBatches; i++ { + batches <- nil + } + return &syncIDBatcher{ + batchPipe: batches, + } +} + +func (s *syncIDBatcher) AddToCurrentBatch(id pdata.TraceID) { + s.Lock() + s.openBatch = append(s.openBatch, id) + s.Unlock() +} + +func (s *syncIDBatcher) CloseCurrentAndTakeFirstBatch() (idbatcher.Batch, bool) { + s.Lock() + defer s.Unlock() + firstBatch := <-s.batchPipe + s.batchPipe <- s.openBatch + s.openBatch = nil + return firstBatch, true +} + +func (s *syncIDBatcher) Stop() { +} diff --git a/processor/cascadingfilterprocessor/sampling/always_sample_test.go b/processor/cascadingfilterprocessor/sampling/always_sample_test.go new file mode 100644 index 000000000000..a2a15b498f69 --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/always_sample_test.go @@ -0,0 +1,46 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" +) + +func newAlwaysSample() *policyEvaluator { + return &policyEvaluator{ + logger: zap.NewNop(), + maxSpansPerSecond: math.MaxInt64, + } +} + +func TestEvaluate_AlwaysSample(t *testing.T) { + filter := newAlwaysSample() + decision := filter.Evaluate( + pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + newTraceStringAttrs(map[string]pdata.AttributeValue{}, "example", "value"), + ) + assert.Equal(t, decision, Sampled) +} + +func TestOnLateArrivingSpans_AlwaysSample(t *testing.T) { + filter := newAlwaysSample() + err := filter.OnLateArrivingSpans(NotSampled, nil) + assert.Nil(t, err) +} diff --git a/processor/cascadingfilterprocessor/sampling/doc.go b/processor/cascadingfilterprocessor/sampling/doc.go new file mode 100644 index 000000000000..c41ee2577889 --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry 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 sampling contains the interfaces and data types used to implement +// the various sampling policies. +package sampling diff --git a/processor/cascadingfilterprocessor/sampling/empty_test.go b/processor/cascadingfilterprocessor/sampling/empty_test.go new file mode 100644 index 000000000000..28080c37f33c --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/empty_test.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry 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 sampling diff --git a/processor/cascadingfilterprocessor/sampling/numeric_tag_filter_test.go b/processor/cascadingfilterprocessor/sampling/numeric_tag_filter_test.go new file mode 100644 index 000000000000..cccb27cde85d --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/numeric_tag_filter_test.go @@ -0,0 +1,111 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "math" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" +) + +func newNumericAttributeFilter(minValue int64, maxValue int64) *policyEvaluator { + return &policyEvaluator{ + logger: zap.NewNop(), + numericAttr: &numericAttributeFilter{ + key: "example", + minValue: minValue, + maxValue: maxValue, + }, + maxSpansPerSecond: math.MaxInt64, + } +} + +func TestNumericTagFilter(t *testing.T) { + var empty = map[string]pdata.AttributeValue{} + filter := newNumericAttributeFilter(math.MinInt32, math.MaxInt32) + + resAttr := map[string]pdata.AttributeValue{} + resAttr["example"] = pdata.NewAttributeValueInt(8) + + cases := []struct { + Desc string + Trace *TraceData + Decision Decision + }{ + { + Desc: "nonmatching span attribute", + Trace: newTraceIntAttrs(empty, "non_matching", math.MinInt32), + Decision: NotSampled, + }, + { + Desc: "span attribute with lower limit", + Trace: newTraceIntAttrs(empty, "example", math.MinInt32), + Decision: Sampled, + }, + { + Desc: "span attribute with upper limit", + Trace: newTraceIntAttrs(empty, "example", math.MaxInt32), + Decision: Sampled, + }, + { + Desc: "span attribute below min limit", + Trace: newTraceIntAttrs(empty, "example", math.MinInt32-1), + Decision: NotSampled, + }, + { + Desc: "span attribute above max limit", + Trace: newTraceIntAttrs(empty, "example", math.MaxInt32+1), + Decision: NotSampled, + }, + } + + for _, c := range cases { + t.Run(c.Desc, func(t *testing.T) { + u, err := uuid.NewRandom() + require.NoError(t, err) + decision := filter.Evaluate(pdata.NewTraceID(u), c.Trace) + assert.Equal(t, decision, c.Decision) + }) + } +} + +func TestOnLateArrivingSpans_NumericTagFilter(t *testing.T) { + filter := newNumericAttributeFilter(math.MinInt32, math.MaxInt32) + err := filter.OnLateArrivingSpans(NotSampled, nil) + assert.Nil(t, err) +} + +func newTraceIntAttrs(nodeAttrs map[string]pdata.AttributeValue, spanAttrKey string, spanAttrValue int64) *TraceData { + var traceBatches []pdata.Traces + traces := pdata.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + rs.Resource().Attributes().InitFromMap(nodeAttrs) + ils := rs.InstrumentationLibrarySpans().AppendEmpty() + span := ils.Spans().AppendEmpty() + span.SetTraceID(pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})) + span.SetSpanID(pdata.NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + attributes := make(map[string]pdata.AttributeValue) + attributes[spanAttrKey] = pdata.NewAttributeValueInt(spanAttrValue) + span.Attributes().InitFromMap(attributes) + traceBatches = append(traceBatches, traces) + return &TraceData{ + ReceivedBatches: traceBatches, + } +} diff --git a/processor/cascadingfilterprocessor/sampling/policy.go b/processor/cascadingfilterprocessor/sampling/policy.go new file mode 100644 index 000000000000..f50f48d767ba --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/policy.go @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "sync" + "time" + + "go.opentelemetry.io/collector/model/pdata" +) + +// TraceData stores the sampling related trace data. +type TraceData struct { + sync.Mutex + // Decisions gives the current status of the sampling decision for each policy. + Decisions []Decision + // FinalDecision describes the ultimate fate of the trace + FinalDecision Decision + // SelectedByProbabilisticFilter determines if this trace was selected by probabilistic filter + SelectedByProbabilisticFilter bool + // Arrival time the first span for the trace was received. + ArrivalTime time.Time + // Decisiontime time when sampling decision was taken. + DecisionTime time.Time + // SpanCount track the number of spans on the trace. + SpanCount int64 + // ReceivedBatches stores all the batches received for the trace. + ReceivedBatches []pdata.Traces +} + +// Decision gives the status of sampling decision. +type Decision int32 + +const ( + // Unspecified indicates that the status of the decision was not set yet. + Unspecified Decision = iota + // Pending indicates that the policy was not evaluated yet. + Pending + // Sampled is used to indicate that the decision was already taken + // to sample the data. + Sampled + // SecondChance is a special category that allows to make a final decision + // after all batches are processed. It should be converted to Sampled or NotSampled + SecondChance + // NotSampled is used to indicate that the decision was already taken + // to not sample the data. + NotSampled + // Dropped is used when data needs to be purged before the sampling policy + // had a chance to evaluate it. + Dropped +) + +// PolicyEvaluator implements a cascading policy evaluator, +// which makes a sampling decision for a given trace when requested. +type PolicyEvaluator interface { + // OnLateArrivingSpans notifies the evaluator that the given list of spans arrived + // after the sampling decision was already taken for the trace. + // This gives the evaluator a chance to log any message/metrics and/or update any + // related internal state. + OnLateArrivingSpans(earlyDecision Decision, spans []*pdata.Span) error + + // Evaluate looks at the trace data and returns a corresponding SamplingDecision. + Evaluate(traceID pdata.TraceID, trace *TraceData) Decision +} diff --git a/processor/cascadingfilterprocessor/sampling/policy_factory.go b/processor/cascadingfilterprocessor/sampling/policy_factory.go new file mode 100644 index 000000000000..2d88993ad2a6 --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/policy_factory.go @@ -0,0 +1,131 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "errors" + "regexp" + "time" + + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/cascadingfilterprocessor/config" +) + +type numericAttributeFilter struct { + key string + minValue, maxValue int64 +} + +type stringAttributeFilter struct { + key string + values map[string]struct{} +} + +type policyEvaluator struct { + numericAttr *numericAttributeFilter + stringAttr *stringAttributeFilter + + operationRe *regexp.Regexp + minDuration *time.Duration + minNumberOfSpans *int + + currentSecond int64 + maxSpansPerSecond int64 + spansInCurrentSecond int64 + + invertMatch bool + + logger *zap.Logger +} + +var _ PolicyEvaluator = (*policyEvaluator)(nil) + +func createNumericAttributeFilter(cfg *config.NumericAttributeCfg) *numericAttributeFilter { + if cfg == nil { + return nil + } + + return &numericAttributeFilter{ + key: cfg.Key, + minValue: cfg.MinValue, + maxValue: cfg.MaxValue, + } +} + +func createStringAttributeFilter(cfg *config.StringAttributeCfg) *stringAttributeFilter { + if cfg == nil { + return nil + } + + valuesMap := make(map[string]struct{}) + for _, value := range cfg.Values { + if value != "" { + valuesMap[value] = struct{}{} + } + } + + return &stringAttributeFilter{ + key: cfg.Key, + values: valuesMap, + } +} + +// NewProbabilisticFilter creates a policy evaluator intended for selecting samples probabilistically +func NewProbabilisticFilter(logger *zap.Logger, maxSpanRate int64) (PolicyEvaluator, error) { + return &policyEvaluator{ + logger: logger, + currentSecond: 0, + spansInCurrentSecond: 0, + maxSpansPerSecond: maxSpanRate, + }, nil +} + +// NewFilter creates a policy evaluator that samples all traces with the specified criteria +func NewFilter(logger *zap.Logger, cfg *config.PolicyCfg) (PolicyEvaluator, error) { + numericAttrFilter := createNumericAttributeFilter(cfg.NumericAttributeCfg) + stringAttrFilter := createStringAttributeFilter(cfg.StringAttributeCfg) + + var operationRe *regexp.Regexp + var err error + + if cfg.PropertiesCfg.NamePattern != nil { + operationRe, err = regexp.Compile(*cfg.PropertiesCfg.NamePattern) + if err != nil { + return nil, err + } + } + + if cfg.PropertiesCfg.MinDuration != nil && *cfg.PropertiesCfg.MinDuration < 0*time.Second { + return nil, errors.New("minimum span duration must be a non-negative number") + } + + if cfg.PropertiesCfg.MinNumberOfSpans != nil && *cfg.PropertiesCfg.MinNumberOfSpans < 1 { + return nil, errors.New("minimum number of spans must be a positive number") + } + + return &policyEvaluator{ + stringAttr: stringAttrFilter, + numericAttr: numericAttrFilter, + operationRe: operationRe, + minDuration: cfg.PropertiesCfg.MinDuration, + minNumberOfSpans: cfg.PropertiesCfg.MinNumberOfSpans, + logger: logger, + currentSecond: 0, + spansInCurrentSecond: 0, + maxSpansPerSecond: cfg.SpansPerSecond, + invertMatch: cfg.InvertMatch, + }, nil +} diff --git a/processor/cascadingfilterprocessor/sampling/policy_filter.go b/processor/cascadingfilterprocessor/sampling/policy_filter.go new file mode 100644 index 000000000000..848d84e7d496 --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/policy_filter.go @@ -0,0 +1,225 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "time" + + "go.opentelemetry.io/collector/model/pdata" +) + +// OnLateArrivingSpans notifies the evaluator that the given list of spans arrived +// after the sampling decision was already taken for the trace. +// This gives the evaluator a chance to log any message/metrics and/or update any +// related internal state. +func (pe *policyEvaluator) OnLateArrivingSpans(earlyDecision Decision, spans []*pdata.Span) error { + return nil +} + +func tsToMicros(ts pdata.Timestamp) int64 { + return int64(ts / 1000) +} + +func checkIfNumericAttrFound(attrs pdata.AttributeMap, filter *numericAttributeFilter) bool { + if v, ok := attrs.Get(filter.key); ok { + value := v.IntVal() + if value >= filter.minValue && value <= filter.maxValue { + return true + } + } + return false +} + +func checkIfStringAttrFound(attrs pdata.AttributeMap, filter *stringAttributeFilter) bool { + if v, ok := attrs.Get(filter.key); ok { + truncableStr := v.StringVal() + if len(truncableStr) > 0 { + if _, ok := filter.values[truncableStr]; ok { + return true + } + } + } + return false +} + +// evaluateRules goes through the defined properties and checks if they are matched +func (pe *policyEvaluator) evaluateRules(_ pdata.TraceID, trace *TraceData) Decision { + trace.Lock() + batches := trace.ReceivedBatches + trace.Unlock() + + matchingOperationFound := false + matchingStringAttrFound := false + matchingNumericAttrFound := false + spanCount := 0 + minStartTime := int64(0) + maxEndTime := int64(0) + + for _, batch := range batches { + rs := batch.ResourceSpans() + + for i := 0; i < rs.Len(); i++ { + if pe.stringAttr != nil || pe.numericAttr != nil { + res := rs.At(i).Resource() + if !matchingStringAttrFound && pe.stringAttr != nil { + matchingStringAttrFound = checkIfStringAttrFound(res.Attributes(), pe.stringAttr) + } + if !matchingNumericAttrFound && pe.numericAttr != nil { + matchingNumericAttrFound = checkIfNumericAttrFound(res.Attributes(), pe.numericAttr) + } + } + + ils := rs.At(i).InstrumentationLibrarySpans() + for j := 0; j < ils.Len(); j++ { + spans := ils.At(j).Spans() + spanCount += spans.Len() + for k := 0; k < spans.Len(); k++ { + span := spans.At(k) + + if pe.stringAttr != nil || pe.numericAttr != nil { + if !matchingStringAttrFound && pe.stringAttr != nil { + matchingStringAttrFound = checkIfStringAttrFound(span.Attributes(), pe.stringAttr) + } + if !matchingNumericAttrFound && pe.numericAttr != nil { + matchingNumericAttrFound = checkIfNumericAttrFound(span.Attributes(), pe.numericAttr) + } + } + + if pe.operationRe != nil && !matchingOperationFound { + if pe.operationRe.MatchString(span.Name()) { + matchingOperationFound = true + } + } + + if pe.minDuration != nil { + startTs := tsToMicros(span.StartTimestamp()) + endTs := tsToMicros(span.EndTimestamp()) + + if minStartTime == 0 { + minStartTime = startTs + maxEndTime = endTs + } else { + if startTs < minStartTime { + minStartTime = startTs + } + if endTs > maxEndTime { + maxEndTime = endTs + } + } + } + + } + } + } + } + + conditionMet := struct { + operationName, minDuration, minSpanCount, stringAttr, numericAttr bool + }{ + operationName: true, + minDuration: true, + minSpanCount: true, + stringAttr: true, + numericAttr: true, + } + + if pe.operationRe != nil { + conditionMet.operationName = matchingOperationFound + } + if pe.minNumberOfSpans != nil { + conditionMet.minSpanCount = spanCount >= *pe.minNumberOfSpans + } + if pe.minDuration != nil { + conditionMet.minDuration = maxEndTime > minStartTime && maxEndTime-minStartTime >= pe.minDuration.Microseconds() + } + if pe.numericAttr != nil { + conditionMet.numericAttr = matchingNumericAttrFound + } + if pe.stringAttr != nil { + conditionMet.stringAttr = matchingStringAttrFound + } + + if conditionMet.minSpanCount && + conditionMet.minDuration && + conditionMet.operationName && + conditionMet.numericAttr && + conditionMet.stringAttr { + if pe.invertMatch { + return NotSampled + } + return Sampled + } + + if pe.invertMatch { + return Sampled + } + return NotSampled +} + +func (pe *policyEvaluator) shouldConsider(currSecond int64, trace *TraceData) bool { + if pe.maxSpansPerSecond < 0 { + // This emits "second chance" traces + return true + } else if trace.SpanCount > pe.maxSpansPerSecond { + // This trace will never fit, there are more spans than max limit + return false + } else if pe.currentSecond == currSecond && trace.SpanCount > pe.maxSpansPerSecond-pe.spansInCurrentSecond { + // This trace will not fit in this second, no way + return false + } else { + // This has some chances + return true + } +} + +func (pe *policyEvaluator) emitsSecondChance() bool { + return pe.maxSpansPerSecond < 0 +} + +func (pe *policyEvaluator) updateRate(currSecond int64, numSpans int64) Decision { + if pe.currentSecond != currSecond { + pe.currentSecond = currSecond + pe.spansInCurrentSecond = 0 + } + + spansInSecondIfSampled := pe.spansInCurrentSecond + numSpans + if spansInSecondIfSampled <= pe.maxSpansPerSecond { + pe.spansInCurrentSecond = spansInSecondIfSampled + return Sampled + } + + return NotSampled +} + +// Evaluate looks at the trace data and returns a corresponding SamplingDecision. Also takes into account +// the usage of sampling rate budget +func (pe *policyEvaluator) Evaluate(traceID pdata.TraceID, trace *TraceData) Decision { + currSecond := time.Now().Unix() + + if !pe.shouldConsider(currSecond, trace) { + return NotSampled + } + + decision := pe.evaluateRules(traceID, trace) + if decision != Sampled { + return decision + } + + if pe.emitsSecondChance() { + return SecondChance + } + + return pe.updateRate(currSecond, trace.SpanCount) +} diff --git a/processor/cascadingfilterprocessor/sampling/rate_limiting_test.go b/processor/cascadingfilterprocessor/sampling/rate_limiting_test.go new file mode 100644 index 000000000000..27e1e90cbfea --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/rate_limiting_test.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" +) + +func newRateLimiterFilter(maxRate int64) *policyEvaluator { + return &policyEvaluator{ + logger: zap.NewNop(), + maxSpansPerSecond: maxRate, + } +} + +func TestRateLimiter(t *testing.T) { + var empty = map[string]pdata.AttributeValue{} + + trace := newTraceStringAttrs(empty, "example", "value") + traceID := pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) + rateLimiter := newRateLimiterFilter(3) + + // Trace span count greater than spans per second + trace.SpanCount = 10 + decision := rateLimiter.Evaluate(traceID, trace) + assert.Equal(t, decision, NotSampled) + + // Trace span count just above to spans per second + trace.SpanCount = 4 + decision = rateLimiter.Evaluate(traceID, trace) + assert.Equal(t, decision, NotSampled) + + // Trace span count equal spans per second + trace.SpanCount = 3 + decision = rateLimiter.Evaluate(traceID, trace) + assert.Equal(t, decision, Sampled) + + // Trace span count less than spans per second + trace.SpanCount = 0 + decision = rateLimiter.Evaluate(traceID, trace) + assert.Equal(t, decision, Sampled) +} + +func TestOnLateArrivingSpans_RateLimiter(t *testing.T) { + rateLimiter := newRateLimiterFilter(3) + err := rateLimiter.OnLateArrivingSpans(NotSampled, nil) + assert.Nil(t, err) +} diff --git a/processor/cascadingfilterprocessor/sampling/span_properties_filter_test.go b/processor/cascadingfilterprocessor/sampling/span_properties_filter_test.go new file mode 100644 index 000000000000..38bd27b201fe --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/span_properties_filter_test.go @@ -0,0 +1,168 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "math" + "regexp" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" +) + +var ( + operationNamePattern = "foo.*" + minDuration = 500 * time.Microsecond + minNumberOfSpans = 2 +) + +func newSpanPropertiesFilter(t *testing.T, operationNamePattern *string, minDuration *time.Duration, minNumberOfSpans *int) policyEvaluator { + var operationRe *regexp.Regexp + var err error + if operationNamePattern != nil { + operationRe, err = regexp.Compile(*operationNamePattern) + require.NoError(t, err) + } + return policyEvaluator{ + logger: zap.NewNop(), + operationRe: operationRe, + minNumberOfSpans: minNumberOfSpans, + minDuration: minDuration, + maxSpansPerSecond: math.MaxInt64, + } +} + +func evaluate(t *testing.T, evaluator policyEvaluator, traces *TraceData, expectedDecision Decision) { + u, err := uuid.NewRandom() + require.NoError(t, err) + decision := evaluator.Evaluate(pdata.NewTraceID(u), traces) + assert.Equal(t, expectedDecision, decision) +} + +func TestPartialSpanPropertiesFilter(t *testing.T) { + opFilter := newSpanPropertiesFilter(t, &operationNamePattern, nil, nil) + durationFilter := newSpanPropertiesFilter(t, nil, &minDuration, nil) + spansFilter := newSpanPropertiesFilter(t, nil, nil, &minNumberOfSpans) + + cases := []struct { + Desc string + Evaluator policyEvaluator + }{ + { + Desc: "operation name filter", + Evaluator: opFilter, + }, + { + Desc: "duration filter", + Evaluator: durationFilter, + }, + { + Desc: "spans filter", + Evaluator: spansFilter, + }, + } + + matchingTraces := newTraceAttrs("foobar", 1000*time.Microsecond, 100) + nonMatchingTraces := newTraceAttrs("bar", 100*time.Microsecond, 1) + + for _, c := range cases { + t.Run(c.Desc, func(t *testing.T) { + c.Evaluator.invertMatch = false + evaluate(t, c.Evaluator, matchingTraces, Sampled) + evaluate(t, c.Evaluator, nonMatchingTraces, NotSampled) + + c.Evaluator.invertMatch = true + evaluate(t, c.Evaluator, matchingTraces, NotSampled) + evaluate(t, c.Evaluator, nonMatchingTraces, Sampled) + }) + } +} + +func TestSpanPropertiesFilter(t *testing.T) { + cases := []struct { + Desc string + Trace *TraceData + Decision Decision + }{ + { + Desc: "fully matching", + Trace: newTraceAttrs("foobar", 1000*time.Microsecond, 100), + Decision: Sampled, + }, + { + Desc: "nonmatching operation name", + Trace: newTraceAttrs("non_matching", 1000*time.Microsecond, 100), + Decision: NotSampled, + }, + { + Desc: "nonmatching duration", + Trace: newTraceAttrs("foobar", 100*time.Microsecond, 100), + Decision: NotSampled, + }, + { + Desc: "nonmatching number of spans", + Trace: newTraceAttrs("foobar", 1000*time.Microsecond, 1), + Decision: NotSampled, + }, + } + + for _, c := range cases { + t.Run(c.Desc, func(t *testing.T) { + // Regular match + filter := newSpanPropertiesFilter(t, &operationNamePattern, &minDuration, &minNumberOfSpans) + evaluate(t, filter, c.Trace, c.Decision) + + // Invert match + filter.invertMatch = true + invertDecision := Sampled + if c.Decision == Sampled { + invertDecision = NotSampled + } + evaluate(t, filter, c.Trace, invertDecision) + }) + } +} + +func newTraceAttrs(operationName string, duration time.Duration, numberOfSpans int) *TraceData { + endTs := time.Now().UnixNano() + startTs := endTs - duration.Nanoseconds() + + var traceBatches []pdata.Traces + + traces := pdata.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + ils := rs.InstrumentationLibrarySpans().AppendEmpty() + + spans := ils.Spans() + spans.EnsureCapacity(numberOfSpans) + + for i := 0; i < numberOfSpans; i++ { + span := spans.AppendEmpty() + span.SetName(operationName) + span.SetStartTimestamp(pdata.Timestamp(startTs)) + span.SetEndTimestamp(pdata.Timestamp(endTs)) + } + + traceBatches = append(traceBatches, traces) + + return &TraceData{ + ReceivedBatches: traceBatches, + } +} diff --git a/processor/cascadingfilterprocessor/sampling/string_tag_filter_test.go b/processor/cascadingfilterprocessor/sampling/string_tag_filter_test.go new file mode 100644 index 000000000000..3212a22ff040 --- /dev/null +++ b/processor/cascadingfilterprocessor/sampling/string_tag_filter_test.go @@ -0,0 +1,109 @@ +// Copyright The OpenTelemetry 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 sampling + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/model/pdata" + "go.uber.org/zap" +) + +func newStringAttributeFilter() *policyEvaluator { + return &policyEvaluator{ + logger: zap.NewNop(), + stringAttr: &stringAttributeFilter{ + key: "example", + values: map[string]struct{}{"value": {}}, + }, + maxSpansPerSecond: math.MaxInt64, + } +} + +func TestStringTagFilter(t *testing.T) { + + var empty = map[string]pdata.AttributeValue{} + filter := newStringAttributeFilter() + + cases := []struct { + Desc string + Trace *TraceData + Decision Decision + }{ + { + Desc: "nonmatching node attribute key", + Trace: newTraceStringAttrs(map[string]pdata.AttributeValue{"non_matching": pdata.NewAttributeValueString("value")}, "", ""), + Decision: NotSampled, + }, + { + Desc: "nonmatching node attribute value", + Trace: newTraceStringAttrs(map[string]pdata.AttributeValue{"example": pdata.NewAttributeValueString("non_matching")}, "", ""), + Decision: NotSampled, + }, + { + Desc: "matching node attribute", + Trace: newTraceStringAttrs(map[string]pdata.AttributeValue{"example": pdata.NewAttributeValueString("value")}, "", ""), + Decision: Sampled, + }, + { + Desc: "nonmatching span attribute key", + Trace: newTraceStringAttrs(empty, "nonmatching", "value"), + Decision: NotSampled, + }, + { + Desc: "nonmatching span attribute value", + Trace: newTraceStringAttrs(empty, "example", "nonmatching"), + Decision: NotSampled, + }, + { + Desc: "matching span attribute", + Trace: newTraceStringAttrs(empty, "example", "value"), + Decision: Sampled, + }, + } + + for _, c := range cases { + t.Run(c.Desc, func(t *testing.T) { + decision := filter.Evaluate(pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), c.Trace) + assert.Equal(t, decision, c.Decision) + }) + } +} + +func newTraceStringAttrs(nodeAttrs map[string]pdata.AttributeValue, spanAttrKey string, spanAttrValue string) *TraceData { + var traceBatches []pdata.Traces + traces := pdata.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + rs.Resource().Attributes().InitFromMap(nodeAttrs) + ils := rs.InstrumentationLibrarySpans().AppendEmpty() + span := ils.Spans().AppendEmpty() + span.SetTraceID(pdata.NewTraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})) + span.SetSpanID(pdata.NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + attributes := make(map[string]pdata.AttributeValue) + attributes[spanAttrKey] = pdata.NewAttributeValueString(spanAttrValue) + span.Attributes().InitFromMap(attributes) + traceBatches = append(traceBatches, traces) + return &TraceData{ + ReceivedBatches: traceBatches, + } +} + +func TestOnLateArrivingSpans_StringAttribute(t *testing.T) { + filter := newStringAttributeFilter() + err := filter.OnLateArrivingSpans(NotSampled, nil) + assert.Nil(t, err) +} diff --git a/processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml b/processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml new file mode 100644 index 000000000000..166f9203b487 --- /dev/null +++ b/processor/cascadingfilterprocessor/testdata/cascading_filter_config.yaml @@ -0,0 +1,61 @@ +receivers: + nop: + +exporters: + nop: + +processors: + cascading_filter: + decision_wait: 10s + num_traces: 100 + expected_new_traces_per_sec: 10 + spans_per_second: 1000 + probabilistic_filtering_ratio: 0.1 + policies: + [ + { + name: test-policy-1, + }, + { + name: test-policy-2, + numeric_attribute: {key: key1, min_value: 50, max_value: 100} + }, + { + name: test-policy-3, + string_attribute: {key: key2, values: [value1, value2]} + }, + { + name: test-policy-4, + spans_per_second: 35, + }, + { + name: test-policy-5, + spans_per_second: 123, + numeric_attribute: {key: key1, min_value: 50, max_value: 100}, + invert_match: true + }, + { + name: test-policy-6, + spans_per_second: 50, + properties: {min_duration: 9s } + }, + { + name: test-policy-7, + properties: { + name_pattern: "foo.*", + min_number_of_spans: 10, + min_duration: 9s + } + }, + { + name: everything_else, + spans_per_second: -1 + }, + ] + +service: + pipelines: + traces: + receivers: [nop] + processors: [cascading_filter] + exporters: [nop] diff --git a/processor/k8sprocessor/README.md b/processor/k8sprocessor/README.md index 78d0cc8763a4..6e84eaf4ea00 100644 --- a/processor/k8sprocessor/README.md +++ b/processor/k8sprocessor/README.md @@ -1 +1,318 @@ -Documentation is published to [pkg.go.dev](https://pkg.go.dev/github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor?tab=doc) +## Kubernetes Processor + +The `k8sprocessor` allow automatic tagging of spans with k8s metadata. + +It automatically discovers k8s resources (pods), extracts metadata from them and adds theextracted +metadata to the relevant spans. The processor use the kubernetes API to discover all pods running +in a cluster, keeps a record of their IP addresses and interesting metadata. Upon receiving spans, +the processor tries to identify the source IP address of the service that sent the spans and matches +it with the in memory data. If a match is found, the cached metadata is added to the spans as attributes. + +### Config + +There are several top level sections of the processor config: + +- `passthrough` (default = false): when set to true, only annotates resources with the pod IP and +does not try to extract any other metadata. It does not need access to the K8S cluster API. +Agent/Collector must receive spans directly from services to be able to correctly detect the pod IPs. +- `owner_lookup_enabled` (default = false): when set to true, fields such as `daemonSetName`, +`replicaSetName`, `service`, etc. can be extracted, though it requires fetching additional data to traverse +the `owner` relationship. See the [list of fields](#k8sprocessor-extract) for more information over +which tags require the flag to be enabled. +- `extract`: the section (see [below](#k8sprocessor-extract)) allows specifying extraction rules +- `filter`: the section (see [below](#k8sprocessor-filter)) allows specifying filters when matching pods + +#### Extract section + +Allows specifying extraction rules to extract data from k8s pod specs. + +- `metadata` (default = empty): specifies a list of strings that denote extracted fields. Following fields +can be extracted: + - `containerId` + - `containerName` + - `containerImage` + - `clusterName` + - `daemonSetName` _(`owner_lookup_enabled` must be set to `true`)_ + - `deploymentName` + - `hostName` + - `namespace` + - `nodeName` + - `podId` + - `podName` + - `replicaSetName` _(`owner_lookup_enabled` must be set to `true`)_ + - `serviceName` _(`owner_lookup_enabled` must be set to `true`)_ - in case more than one service is assigned + to the pod, they are comma-separated + - `startTime` + - `statefulSetName` _(`owner_lookup_enabled` must be set to `true`)_ + + Also, see [example config](#k8sprocessor-example). +- `tags`: specifies an optional map of custom tag names to be used. By default, following names are being assigned: + - `clusterName `: `k8s.cluster.name` + - `containerID `: `k8s.container.id` + - `containerImage `: `k8s.container.image` + - `containerName `: `k8s.container.name` + - `daemonSetName `: `k8s.daemonset.name` + - `deploymentName `: `k8s.deployment.name` + - `hostName `: `k8s.pod.hostname` + - `namespaceName `: `k8s.namespace.name` + - `nodeName `: `k8s.node.name` + - `podID `: `k8s.pod.id` + - `podName `: `k8s.pod.name` + - `replicaSetName `: `k8s.replicaset.name` + - `serviceName `: `k8s.service.name` + - `statefulSetName`: `k8s.statefulset.name` + - `startTime `: `k8s.pod.startTime` + + When custom value is specified, specified fields use provided names when being tagged, e.g.: + ```yaml + tags: + containerId: my-custom-tag-for-container-id + nodeName: node_name + ``` + - `annotations` (default = empty): a list of rules for extraction and recording annotation data. +See [field extract config](#k8sprocessor-field-extract) for an example on how to use it. +- `labels` (default = empty): a list of rules for extraction and recording label data. +See [field extract config](#k8sprocessor-field-extract) for an example on how to use it. +- `namespace_labels` (default = empty): a list of rules for extraction and recording namespace label data. +See [field extract config](#k8sprocessor-field-extract) for an example on how to use it. + +#### Field Extract Config + +Allows specifying an extraction rule to extract a value from exactly one field. + +The field accepts a list of maps accepting three keys: `tag-name`, `key` and `regex` + +- `tag-name`: represents the name of the tag that will be added to the span. When not specified +a default tag name will be used of the format: `k8s..` For example, if +`tag-name` is not specified and the key is `git_sha`, then the span name will be `k8s.annotation.deployment.git_sha` + +- `key`: represents the annotation name. This must exactly match an annotation name. To capture +all keys, `*` can be used + +- `regex`: is an optional field used to extract a sub-string from a complex field value. +The supplied regular expression must contain one named parameter with the string "value" +as the name. For example, if your pod spec contains the following annotation, +`kubernetes.io/change-cause: 2019-08-28T18:34:33Z APP_NAME=my-app GIT_SHA=58a1e39 CI_BUILD=4120` +and you'd like to extract the GIT_SHA and the CI_BUILD values as tags, then you must specify +the following two extraction rules: + + ```yaml + procesors: + k8s-tagger: + annotations: + - tag_name: git.sha + key: kubernetes.io/change-cause + regex: GIT_SHA=(?P\w+) + - tag_name: ci.build + key: kubernetes.io/change-cause + regex: JENKINS=(?P[\w]+) + ``` + + this will add the `git.sha` and `ci.build` tags to the spans. It is also possible to generically fetch + all keys and fill them into a template. To substitute the original name, use `%s`. For example: + + ```yaml + procesors: + k8s-tagger: + annotations: + - tag_name: k8s.annotation/%s + key: * + ``` + +#### Filter section + +FilterConfig section allows specifying filters to filter pods by labels, fields, namespaces, nodes, etc. + +- `node` (default = ""): represents a k8s node or host. If specified, any pods not running on the specified +node will be ignored by the tagger. +- `node_from_env_var` (default = ""): can be used to extract the node name from an environment variable. +The value must be the name of the environment variable. This is useful when the node a Otel agent will +run on cannot be predicted. In such cases, the Kubernetes downward API can be used to add the node name +to each pod as an environment variable. K8s tagger can then read this value and filter pods by it. +For example, node name can be passed to each agent with the downward API as follows + + ```yaml + env: + - name: K8S_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + ``` + + Then the NodeFromEnv field can be set to `K8S_NODE_NAME` to filter all pods by the node that the agent + is running on. More on downward API here: + https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/ +- `namespace` (default = ""): filters all pods by the provided namespace. All other pods are ignored. +- `fields` (default = empty): a list of maps accepting three keys: `key`, `value`, `op`. Allows to filter +pods by generic k8s fields. Only the following operations (`op`) are supported: `equals`, `not-equals`. +For example, to match pods having `key1=value1` and `key2<>value2` condition met for fields, one can specify: + + ```yaml + fields: + - key: key1 # `op` defaults to "equals" when not specified + value: value1 + - key: key2 + value: value2 + op: not-equals + ``` + +- `labels` (default = empty): a list of maps accepting three keys: `key`, `value`, `op`. Allows to filter +pods by generic k8s pod labels. Only the following operations (`op`) are supported: `equals`, `not-equals`, +`exists`, `not-exists`. For example, to match pods where `label1` exists, one can specify + + ```yaml + fields: + - key: label1 + op: exists + ``` + +#### Example config: + +```yaml +processors: + k8s_tagger: + passthrough: false + owner_lookup_enabled: true # To enable fetching additional metadata using `owner` relationship + extract: + metadata: + # extract the following well-known metadata fields + - containerId + - containerName + - containerImage + - clusterName + - daemonSetName + - deploymentName + - hostName + - namespace + - nodeName + - podId + - podName + - replicaSetName + - serviceName + - startTime + - statefulSetName + tags: + # It is possible to provide your custom key names for each of the extracted metadata fields, + # e.g. to store podName as "pod_name" rather than the default "k8s.pod.name", use following: + podName: pod_name + + annotations: + # Extract all annotations using a template + - tag_name: k8s.annotation.%s + key: "*" + labels: + - tag_name: l1 # extracts value of label with key `label1` and inserts it as a tag with key `l1` + key: label1 + - tag_name: l2 # extracts value of label with key `label1` with regexp and inserts it as a tag with key `l2` + key: label2 + regex: field=(?P.+) + + filter: + namespace: ns2 # only look for pods running in ns2 namespace + node: ip-111.us-west-2.compute.internal # only look for pods running on this node/host + node_from_env_var: K8S_NODE # only look for pods running on the node/host specified by the K8S_NODE environment variable + labels: # only consider pods that match the following labels + - key: key1 # match pods that have a label `key1=value1`. `op` defaults to "equals" when not specified + value: value1 + - key: key2 # ignore pods that have a label `key2=value2`. + value: value2 + op: not-equals + fields: # works the same way as labels but for fields instead (like annotations) + - key: key1 + value: value1 + - key: key2 + value: value2 + op: not-equals +``` + +### RBAC + +TODO: mention the required RBAC rules. + +### Deployment scenarios + +The processor supports running both in agent and collector mode. + +#### As an agent + +When running as an agent, the processor detects IP addresses of pods sending spans to the agent and uses this +information to extract metadata from pods and add to spans. When running as an agent, it is important to apply +a discovery filter so that the processor only discovers pods from the same host that it is running on. Not using +such a filter can result in unnecessary resource usage especially on very large clusters. Once the fitler is applied, +each processor will only query the k8s API for pods running on it's own node. + +Node filter can be applied by setting the `filter.node` config option to the name of a k8s node. While this works +as expected, it cannot be used to automatically filter pods by the same node that the processor is running on in +most cases as it is not know before hand which node a pod will be scheduled on. Luckily, kubernetes has a solution +for this called the downward API. To automatically filter pods by the node the processor is running on, you'll need +to complete the following steps: + +1. Use the downward API to inject the node name as an environment variable. +Add the following snippet under the pod env section of the OpenTelemetry container. + + ```yaml + env: + - name: KUBE_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + ``` + + This will inject a new environment variable to the OpenTelemetry container with the value as the + name of the node the pod was scheduled to run on. + +2. Set "filter.node_from_env_var" to the name of the environment variable holding the node name. + + ```yaml + k8s_tagger: + filter: + node_from_env_var: KUBE_NODE_NAME # this should be same as the var name used in previous step + ``` + + This will restrict each OpenTelemetry agent to query pods running on the same node only dramatically reducing + resource requirements for very large clusters. + +#### As a collector + +The processor can be deployed both as an agent or as a collector. + +When running as a collector, the processor cannot correctly detect the IP address of the pods generating +the spans when it receives the spans from an agent instead of receiving them directly from the pods. To +workaround this issue, agents deployed with the k8s_tagger processor can be configured to detect +the IP addresses and forward them along with the span resources. Collector can then match this IP address +with k8s pods and enrich the spans with the metadata. In order to set this up, you'll need to complete the +following steps: + +1. Setup agents in passthrough mode +Configure the agents' k8s_tagger processors to run in passthrough mode. + + ```yaml + # k8s_tagger config for agent + k8s_tagger: + passthrough: true + ``` + This will ensure that the agents detect the IP address as add it as an attribute to all span resources. + Agents will not make any k8s API calls, do any discovery of pods or extract any metadata. + +2. Configure the collector as usual +No special configuration changes are needed to be made on the collector. It'll automatically detect +the IP address of spans sent by the agents as well as directly by other services/pods. + + +### Caveats + +There are some edge-cases and scenarios where k8s_tagger will not work properly. + + +#### Host networking mode + +The processor cannot correct identify pods running in the host network mode and +enriching spans generated by such pods is not supported at the moment. + +#### As a sidecar + +The processor does not support detecting containers from the same pods when running +as a sidecar. While this can be done, we think it is simpler to just use the kubernetes +downward API to inject environment variables into the pods and directly use their values +as tags. diff --git a/processor/k8sprocessor/client_test.go b/processor/k8sprocessor/client_test.go index 1cce74b9e43a..6b1337a1d920 100644 --- a/processor/k8sprocessor/client_test.go +++ b/processor/k8sprocessor/client_test.go @@ -27,14 +27,12 @@ import ( // fakeClient is used as a replacement for WatchClient in test cases. type fakeClient struct { - Pods map[kube.PodIdentifier]*kube.Pod - Rules kube.ExtractionRules - Filters kube.Filters - Associations []kube.Association - Informer cache.SharedInformer - NamespaceInformer cache.SharedInformer - Namespaces map[string]*kube.Namespace - StopCh chan struct{} + Pods map[kube.PodIdentifier]*kube.Pod + Rules kube.ExtractionRules + Filters kube.Filters + Associations []kube.Association + Informer cache.SharedInformer + StopCh chan struct{} } func selectors() (labels.Selector, fields.Selector) { @@ -43,18 +41,17 @@ func selectors() (labels.Selector, fields.Selector) { } // newFakeClient instantiates a new FakeClient object and satisfies the ClientProvider type -func newFakeClient(_ *zap.Logger, apiCfg k8sconfig.APIConfig, rules kube.ExtractionRules, filters kube.Filters, associations []kube.Association, exclude kube.Excludes, _ kube.APIClientsetProvider, _ kube.InformerProvider, _ kube.InformerProviderNamespace) (kube.Client, error) { +func newFakeClient(_ *zap.Logger, apiCfg k8sconfig.APIConfig, rules kube.ExtractionRules, filters kube.Filters, associations []kube.Association, _ kube.APIClientsetProvider, _ kube.InformerProvider, _ kube.OwnerProvider) (kube.Client, error) { cs := fake.NewSimpleClientset() ls, fs := selectors() return &fakeClient{ - Pods: map[kube.PodIdentifier]*kube.Pod{}, - Rules: rules, - Filters: filters, - Associations: associations, - Informer: kube.NewFakeInformer(cs, "", ls, fs), - NamespaceInformer: kube.NewFakeInformer(cs, "", ls, fs), - StopCh: make(chan struct{}), + Pods: map[kube.PodIdentifier]*kube.Pod{}, + Rules: rules, + Filters: filters, + Associations: associations, + Informer: kube.NewFakeInformer(cs, "", ls, fs), + StopCh: make(chan struct{}), }, nil } @@ -65,11 +62,6 @@ func (f *fakeClient) GetPod(identifier kube.PodIdentifier) (*kube.Pod, bool) { return p, ok } -func (f *fakeClient) GetNamespace(namespace string) (*kube.Namespace, bool) { - ns, ok := f.Namespaces[namespace] - return ns, ok -} - // Start is a noop for FakeClient. func (f *fakeClient) Start() { if f.Informer != nil { diff --git a/processor/k8sprocessor/config.go b/processor/k8sprocessor/config.go index 0585ff20f848..1396678fbaf2 100644 --- a/processor/k8sprocessor/config.go +++ b/processor/k8sprocessor/config.go @@ -22,7 +22,7 @@ import ( // Config defines configuration for k8s attributes processor. type Config struct { - config.ProcessorSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct + config.ProcessorSettings `mapstructure:"-"` k8sconfig.APIConfig `mapstructure:",squash"` @@ -32,6 +32,10 @@ type Config struct { // directly from services to be able to correctly detect the pod IPs. Passthrough bool `mapstructure:"passthrough"` + // OwnerLookupEnabled enables pulling owner data, which triggers + // additional calls to Kubernetes API + OwnerLookupEnabled bool `mapstructure:"owner_lookup_enabled"` + // Extract section allows specifying extraction rules to extract // data from k8s pod specs Extract ExtractConfig `mapstructure:"extract"` @@ -43,10 +47,6 @@ type Config struct { // Association section allows to define rules for tagging spans, metrics, // and logs with Pod metadata. Association []PodAssociationConfig `mapstructure:"pod_association"` - - // Exclude section allows to define names of pod that should be - // ignored while tagging. - Exclude ExcludeConfig `mapstructure:"exclude"` } func (cfg *Config) Validate() error { @@ -56,31 +56,41 @@ func (cfg *Config) Validate() error { // ExtractConfig section allows specifying extraction rules to extract // data from k8s pod specs. type ExtractConfig struct { - // Metadata allows to extract pod/namespace metadata from a list of metadata fields. + // Metadata allows to extract pod metadata from a list of metadata fields. // The field accepts a list of strings. // // Metadata fields supported right now are, - // k8s.pod.name, k8s.pod.uid, k8s.deployment.name, k8s.cluster.name, - // k8s.node.name, k8s.namespace.name and k8s.pod.start_time + // namespace, podName, podUID, deployment, cluster, node and startTime // // Specifying anything other than these values will result in an error. // By default all of the fields are extracted and added to spans and metrics. Metadata []string `mapstructure:"metadata"` + // Tags allow to specify output name used for each of the kubernetes tags + // The field accepts a map of string->string. It is optional and if no values + // are provided, defaults will be used + Tags map[string]string `mapstructure:"tags"` + // Annotations allows extracting data from pod annotations and record it // as resource attributes. // It is a list of FieldExtractConfig type. See FieldExtractConfig // documentation for more details. Annotations []FieldExtractConfig `mapstructure:"annotations"` - // Annotations allows extracting data from pod labels and record it + // Labels allows extracting data from pod labels and record it // as resource attributes. // It is a list of FieldExtractConfig type. See FieldExtractConfig // documentation for more details. Labels []FieldExtractConfig `mapstructure:"labels"` + + // NamespaceLabels allows extracting data from namespace labels and record it + // as resource attributes. + // It is a list of FieldExtractConfig type. See FieldExtractConfig + // documentation for more details. + NamespaceLabels []FieldExtractConfig `mapstructure:"namespace_labels"` } -// FieldExtractConfig allows specifying an extraction rule to extract a value from exactly one field. +//FieldExtractConfig allows specifying an extraction rule to extract a value from exactly one field. // // The field accepts a list FilterExtractConfig map. The map accepts three keys // tag_name, key and regex @@ -92,35 +102,43 @@ type ExtractConfig struct { // For example, if tag_name is not specified and the key is git_sha, // then the attribute name will be `k8s.pod.annotations.git_sha`. // -// - key represents the annotation name. This must exactly match an annotation name. +//- key represents the annotation name. This must exactly match an annotation name. +// To capture all keys, `*` can be used // -// - regex is an optional field used to extract a sub-string from a complex field value. -// The supplied regular expression must contain one named parameter with the string "value" -// as the name. For example, if your pod spec contains the following annotation, +//- regex is an optional field used to extract a sub-string from a complex field value. +// The supplied regular expression must contain one named parameter with the string "value" +// as the name. For example, if your pod spec contains the following annotation, // // kubernetes.io/change-cause: 2019-08-28T18:34:33Z APP_NAME=my-app GIT_SHA=58a1e39 CI_BUILD=4120 // -// and you'd like to extract the GIT_SHA and the CI_BUILD values as tags, then you must -// specify the following two extraction rules: +// and you'd like to extract the GIT_SHA and the CI_BUILD values as tags, then you must +// specify the following two extraction rules: // -// procesors: -// k8s-tagger: -// annotations: -// - name: git.sha -// key: kubernetes.io/change-cause -// regex: GIT_SHA=(?P\w+) -// - name: ci.build +// procesors: +// k8s-tagger: +// annotations: +// - tag_name: git.sha +// key: kubernetes.io/change-cause +// regex: GIT_SHA=(?P\w+) +// - tag_name: ci.build // key: kubernetes.io/change-cause -// regex: JENKINS=(?P[\w]+) +// regex: JENKINS=(?P[\w]+) +// +// this will add the `git.sha` and `ci.build` tags to the spans. // -// this will add the `git.sha` and `ci.build` tags to the spans or metrics. +// It is also possible to generically fetch all keys and fill them into a template. +// To substitute the original name, use `%s`. For example: +// +// procesors: +// k8s-tagger: +// annotations: +// - tag_name: k8s.annotation/%s +// key: * + type FieldExtractConfig struct { TagName string `mapstructure:"tag_name"` Key string `mapstructure:"key"` Regex string `mapstructure:"regex"` - // From represents the source of the labels/annotations. - // Allowed values are "pod" and "namespace". The default is pod. - From string `mapstructure:"from"` } // FilterConfig section allows specifying filters to filter @@ -201,13 +219,3 @@ type PodAssociationConfig struct { // e.g. ip, pod_uid, k8s.pod.ip Name string `mapstructure:"name"` } - -// ExcludeConfig represent a list of Pods to exclude -type ExcludeConfig struct { - Pods []ExcludePodConfig `mapstructure:"pods"` -} - -// ExcludePodConfig represent a Pod name to ignore -type ExcludePodConfig struct { - Name string `mapstructure:"name"` -} diff --git a/processor/k8sprocessor/config_test.go b/processor/k8sprocessor/config_test.go index 210d38040de5..bbbfabf2cb2f 100644 --- a/processor/k8sprocessor/config_test.go +++ b/processor/k8sprocessor/config_test.go @@ -26,47 +26,58 @@ import ( "go.opentelemetry.io/collector/config/configtest" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/kube" ) func TestLoadConfig(t *testing.T) { factories, err := componenttest.NopFactories() require.NoError(t, err) factory := NewFactory() - factories.Processors[typeStr] = factory + factories.Processors[config.Type(typeStr)] = factory require.NoError(t, err) err = configcheck.ValidateConfig(factory.CreateDefaultConfig()) require.NoError(t, err) - cfg, err := configtest.LoadConfigAndValidate(path.Join(".", "testdata", "config.yaml"), factories) + cfg, err := configtest.LoadConfigAndValidate( + path.Join(".", "testdata", "config.yaml"), + factories) require.Nil(t, err) require.NotNil(t, cfg) - p0 := cfg.Processors[config.NewID(typeStr)] + p0 := cfg.Processors[config.NewID("k8s_tagger")] assert.Equal(t, p0, &Config{ ProcessorSettings: config.NewProcessorSettings(config.NewID(typeStr)), APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeServiceAccount}, - Exclude: ExcludeConfig{Pods: []ExcludePodConfig{{Name: "jaeger-agent"}, {Name: "jaeger-collector"}}}, }) p1 := cfg.Processors[config.NewIDWithName(typeStr, "2")] assert.Equal(t, p1, &Config{ - ProcessorSettings: config.NewProcessorSettings(config.NewIDWithName(typeStr, "2")), - APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeKubeConfig}, - Passthrough: false, + ProcessorSettings: config.NewProcessorSettings(config.NewIDWithName(typeStr, "2")), + APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeKubeConfig}, + Passthrough: false, + OwnerLookupEnabled: true, Extract: ExtractConfig{ - Metadata: []string{"k8s.pod.name", "k8s.pod.uid", "k8s.deployment.name", "k8s.cluster.name", "k8s.namespace.name", "k8s.node.name", "k8s.pod.start_time"}, + Metadata: []string{ + "containerId", "containerName", "containerImage", "clusterName", "daemonSetName", + "deploymentName", "hostName", "namespace", "nodeName", "podId", "podName", + "replicaSetName", "serviceName", "startTime", "statefulSetName", + }, + Tags: map[string]string{ + "containerId": "my.namespace.containerId", + }, Annotations: []FieldExtractConfig{ - {TagName: "a1", Key: "annotation-one", From: "pod"}, - {TagName: "a2", Key: "annotation-two", Regex: "field=(?P.+)", From: kube.MetadataFromPod}, + {TagName: "a1", Key: "annotation-one"}, + {TagName: "a2", Key: "annotation-two", Regex: "field=(?P.+)"}, }, Labels: []FieldExtractConfig{ - {TagName: "l1", Key: "label1", From: "pod"}, - {TagName: "l2", Key: "label2", Regex: "field=(?P.+)", From: kube.MetadataFromPod}, + {TagName: "l1", Key: "label1"}, + {TagName: "l2", Key: "label2", Regex: "field=(?P.+)"}, + }, + NamespaceLabels: []FieldExtractConfig{ + {TagName: "namespace_labels_%s", Key: "*"}, }, }, Filter: FilterConfig{ @@ -104,11 +115,5 @@ func TestLoadConfig(t *testing.T) { Name: "k8s.pod.uid", }, }, - Exclude: ExcludeConfig{ - Pods: []ExcludePodConfig{ - {Name: "jaeger-agent"}, - {Name: "jaeger-collector"}, - }, - }, }) } diff --git a/processor/k8sprocessor/doc.go b/processor/k8sprocessor/doc.go index 9340f6014709..3e9c2aa0f43a 100644 --- a/processor/k8sprocessor/doc.go +++ b/processor/k8sprocessor/doc.go @@ -40,32 +40,6 @@ // // If Pod association rules are not configured resources are associated with metadata only by connection's IP Address. // -// -//The k8sprocessor can be used for automatic tagging of spans, metrics and logs with k8s labels and annotations from pods and namespaces. -//The config for associating the data passing through the processor (spans, metrics and logs) with specific Pod/Namespace annotations/labels is configured via "annotations" and "labels" keys. -//This config represents a list of annotations/labels that are extracted from pods/namespaces and added to spans, metrics and logs. -//Each item is specified as a config of tag_name (representing the tag name to tag the spans with), -//key (representing the key used to extract value) and from (representing the kubernetes object used to extract the value). -//The "from" field has only two possible values "pod" and "namespace" and defaults to "pod" if none is specified. -// -//A few examples to use this config are as follows: -//annotations: -// - tag_name: a1 # extracts value of annotation from pods with key `annotation-one` and inserts it as a tag with key `a1` -// key: annotation-one -// from: pod -// - tag_name: a2 # extracts value of annotation from namespaces with key `annotation-two` with regexp and inserts it as a tag with key `a2` -// key: annotation-two -// regex: field=(?P.+) -// from: namespace -//labels: -// - tag_name: l1 # extracts value of label from namespaces with key `label1` and inserts it as a tag with key `l1` -// key: label1 -// from: namespace -// - tag_name: l2 # extracts value of label from pods with key `label1` with regexp and inserts it as a tag with key `l2` -// key: label2 -// regex: field=(?P.+) -// from: pod - // RBAC // // TODO: mention the required RBAC rules. diff --git a/processor/k8sprocessor/factory.go b/processor/k8sprocessor/factory.go index 150569a67e05..06a8ce09e600 100644 --- a/processor/k8sprocessor/factory.go +++ b/processor/k8sprocessor/factory.go @@ -16,14 +16,11 @@ package k8sprocessor import ( "context" - "fmt" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config" "go.opentelemetry.io/collector/consumer" - conventions "go.opentelemetry.io/collector/model/semconv/v1.5.0" "go.opentelemetry.io/collector/processor/processorhelper" - "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/kube" @@ -36,14 +33,13 @@ const ( var kubeClientProvider = kube.ClientProvider(nil) var consumerCapabilities = consumer.Capabilities{MutatesData: true} -var defaultExcludes = ExcludeConfig{Pods: []ExcludePodConfig{{Name: "jaeger-agent"}, {Name: "jaeger-collector"}}} // NewFactory returns a new factory for the k8s processor. func NewFactory() component.ProcessorFactory { return processorhelper.NewFactory( typeStr, createDefaultConfig, - processorhelper.WithTraces(createTracesProcessor), + processorhelper.WithTraces(createTraceProcessor), processorhelper.WithMetrics(createMetricsProcessor), processorhelper.WithLogs(createLogsProcessor), ) @@ -53,17 +49,16 @@ func createDefaultConfig() config.Processor { return &Config{ ProcessorSettings: config.NewProcessorSettings(config.NewID(typeStr)), APIConfig: k8sconfig.APIConfig{AuthType: k8sconfig.AuthTypeServiceAccount}, - Exclude: defaultExcludes, } } -func createTracesProcessor( +func createTraceProcessor( ctx context.Context, params component.ProcessorCreateSettings, cfg config.Processor, next consumer.Traces, ) (component.TracesProcessor, error) { - return createTracesProcessorWithOptions(ctx, params, cfg, next) + return createTraceProcessorWithOptions(ctx, params, cfg, next) } func createLogsProcessor( @@ -84,7 +79,7 @@ func createMetricsProcessor( return createMetricsProcessorWithOptions(ctx, params, cfg, nextMetricsConsumer) } -func createTracesProcessorWithOptions( +func createTraceProcessorWithOptions( _ context.Context, params component.ProcessorCreateSettings, cfg config.Processor, @@ -99,7 +94,7 @@ func createTracesProcessorWithOptions( return processorhelper.NewTracesProcessor( cfg, next, - kp.processTraces, + kp.ProcessTraces, processorhelper.WithCapabilities(consumerCapabilities), processorhelper.WithStart(kp.Start), processorhelper.WithShutdown(kp.Shutdown)) @@ -120,7 +115,7 @@ func createMetricsProcessorWithOptions( return processorhelper.NewMetricsProcessor( cfg, nextMetricsConsumer, - kp.processMetrics, + kp.ProcessMetrics, processorhelper.WithCapabilities(consumerCapabilities), processorhelper.WithStart(kp.Start), processorhelper.WithShutdown(kp.Shutdown)) @@ -141,7 +136,7 @@ func createLogsProcessorWithOptions( return processorhelper.NewLogsProcessor( cfg, nextLogsConsumer, - kp.processLogs, + kp.ProcessLogs, processorhelper.WithCapabilities(consumerCapabilities), processorhelper.WithStart(kp.Start), processorhelper.WithShutdown(kp.Shutdown)) @@ -154,8 +149,6 @@ func createKubernetesProcessor( ) (*kubernetesprocessor, error) { kp := &kubernetesprocessor{logger: params.Logger} - warnDeprecatedMetadataConfig(kp.logger, cfg) - allOptions := append(createProcessorOpts(cfg), options...) for _, opt := range allOptions { @@ -186,6 +179,11 @@ func createProcessorOpts(cfg config.Processor) []Option { opts = append(opts, WithExtractMetadata(oCfg.Extract.Metadata...)) opts = append(opts, WithExtractLabels(oCfg.Extract.Labels...)) opts = append(opts, WithExtractAnnotations(oCfg.Extract.Annotations...)) + opts = append(opts, WithExtractTags(oCfg.Extract.Tags)) + + if oCfg.OwnerLookupEnabled { + opts = append(opts, WithOwnerLookupEnabled()) + } // filters opts = append(opts, WithFilterNode(oCfg.Filter.Node, oCfg.Filter.NodeFromEnvVar)) @@ -196,40 +194,5 @@ func createProcessorOpts(cfg config.Processor) []Option { opts = append(opts, WithExtractPodAssociations(oCfg.Association...)) - opts = append(opts, WithExcludes(oCfg.Exclude)) - return opts } - -func warnDeprecatedMetadataConfig(logger *zap.Logger, cfg config.Processor) { - oCfg := cfg.(*Config) - for _, field := range oCfg.Extract.Metadata { - var oldName, newName string - switch field { - case metdataNamespace: - oldName = metdataNamespace - newName = conventions.AttributeK8SNamespaceName - case metadataPodName: - oldName = metadataPodName - newName = conventions.AttributeK8SPodName - case metadataPodUID: - oldName = metadataPodUID - newName = conventions.AttributeK8SPodUID - case metadataStartTime: - oldName = metadataStartTime - newName = metadataPodStartTime - case metadataDeployment: - oldName = metadataDeployment - newName = conventions.AttributeK8SDeploymentName - case metadataCluster: - oldName = metadataCluster - newName = conventions.AttributeK8SClusterName - case metadataNode: - oldName = metadataNode - newName = conventions.AttributeK8SNodeName - } - if oldName != "" { - logger.Warn(fmt.Sprintf("%s has been deprecated in favor of %s for k8s-tagger processor", oldName, newName)) - } - } -} diff --git a/processor/k8sprocessor/factory_test.go b/processor/k8sprocessor/factory_test.go index 817d45dd4221..ba22e404ea61 100644 --- a/processor/k8sprocessor/factory_test.go +++ b/processor/k8sprocessor/factory_test.go @@ -18,8 +18,9 @@ import ( "context" "testing" - "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/component/componenttest" + + "github.com/stretchr/testify/assert" "go.opentelemetry.io/collector/config/configcheck" "go.opentelemetry.io/collector/consumer/consumertest" ) diff --git a/processor/k8sprocessor/go.mod b/processor/k8sprocessor/go.mod index eff34fdfe7f8..1ad053d87da0 100644 --- a/processor/k8sprocessor/go.mod +++ b/processor/k8sprocessor/go.mod @@ -1,71 +1,19 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor -go 1.17 +go 1.14 require ( github.com/onsi/ginkgo v1.14.1 // indirect github.com/onsi/gomega v1.10.2 // indirect - github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig v0.35.0 + github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig v0.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.7.0 go.opencensus.io v0.23.0 go.opentelemetry.io/collector v0.35.0 go.opentelemetry.io/collector/model v0.35.0 go.uber.org/zap v1.19.0 - golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.3 // indirect k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 k8s.io/client-go v0.22.1 ) -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/evanphx/json-patch v4.11.0+incompatible // indirect - github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/go-logr/logr v0.4.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.6 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect - github.com/imdario/mergo v0.3.11 // indirect - github.com/json-iterator/go v1.1.11 // indirect - github.com/knadh/koanf v1.2.2 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/openshift/api v0.0.0-20210521075222-e273a339932a // indirect - github.com/openshift/client-go v0.0.0-20210521082421-73d9475a9142 // indirect - github.com/pelletier/go-toml v1.9.3 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.6.1 // indirect - github.com/spf13/cast v1.4.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect - go.opentelemetry.io/otel v1.0.0-RC3 // indirect - go.opentelemetry.io/otel/trace v1.0.0-RC3 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect - golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect - golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect - golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect - google.golang.org/grpc v1.40.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/klog/v2 v2.9.0 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect - k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect -) - replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig => ./../../internal/k8sconfig diff --git a/processor/k8sprocessor/go.sum b/processor/k8sprocessor/go.sum index 24ac2c32d0d5..8505a338351a 100644 --- a/processor/k8sprocessor/go.sum +++ b/processor/k8sprocessor/go.sum @@ -17,6 +17,7 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -485,8 +486,10 @@ go.opentelemetry.io/otel/internal/metric v0.22.0/go.mod h1:7qVuMihW/ktMonEfOvBXu go.opentelemetry.io/otel/metric v0.22.0 h1:/qv10BzznqEifrXBwsTT370OCN1PRgt+mnjzMwxJKrQ= go.opentelemetry.io/otel/metric v0.22.0/go.mod h1:KcsUkBiYGW003DJ+ugd2aqIRIfjabD9jeOUXqsAtrq0= go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= +go.opentelemetry.io/otel/oteltest v1.0.0-RC2 h1:xNKqMhlZYkASSyvF4JwObZFMq0jhFN3c3SP+2rCzVPk= go.opentelemetry.io/otel/oteltest v1.0.0-RC2/go.mod h1:kiQ4tw5tAL4JLTbcOYwK1CWI1HkT5aiLzHovgOVnz/A= go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw= +go.opentelemetry.io/otel/sdk v1.0.0-RC3 h1:iRMkET+EmJUn5mW0hJzygBraXRmrUwzbOtNvTCh/oKs= go.opentelemetry.io/otel/sdk v1.0.0-RC3/go.mod h1:78H6hyg2fka0NYT9fqGuFLvly2yCxiBXDJAgLKo/2Us= go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4= @@ -550,6 +553,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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= @@ -702,9 +706,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -764,9 +767,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/processor/k8sprocessor/kube/client.go b/processor/k8sprocessor/kube/client.go index 420b31eaaabb..2fd403cc5e4f 100644 --- a/processor/k8sprocessor/kube/client.go +++ b/processor/k8sprocessor/kube/client.go @@ -21,7 +21,6 @@ import ( "sync" "time" - conventions "go.opentelemetry.io/collector/model/semconv/v1.5.0" "go.uber.org/zap" api_v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" @@ -36,15 +35,15 @@ import ( // WatchClient is the main interface provided by this package to a kubernetes cluster. type WatchClient struct { - m sync.RWMutex - deleteMut sync.Mutex - logger *zap.Logger - kc kubernetes.Interface - informer cache.SharedInformer - namespaceInformer cache.SharedInformer - deploymentRegex *regexp.Regexp - deleteQueue []deleteRequest - stopCh chan struct{} + m sync.RWMutex + deleteMut sync.Mutex + logger *zap.Logger + kc kubernetes.Interface + informer cache.SharedInformer + deploymentRegex *regexp.Regexp + deleteQueue []deleteRequest + stopCh chan struct{} + op OwnerAPI // A map containing Pod related data, used to associate them with resources. // Key can be either an IP address or Pod UID @@ -52,11 +51,6 @@ type WatchClient struct { Rules ExtractionRules Filters Filters Associations []Association - Exclude Excludes - - // A map containing Namespace related data, used to associate them with resources. - // Key is namespace name - Namespaces map[string]*Namespace } // Extract deployment name from the pod name. Pod name is created using @@ -64,20 +58,18 @@ type WatchClient struct { var dRegex = regexp.MustCompile(`^(.*)-[0-9a-zA-Z]*-[0-9a-zA-Z]*$`) // New initializes a new k8s Client. -func New(logger *zap.Logger, apiCfg k8sconfig.APIConfig, rules ExtractionRules, filters Filters, associations []Association, exclude Excludes, newClientSet APIClientsetProvider, newInformer InformerProvider, newNamespaceInformer InformerProviderNamespace) (Client, error) { +func New(logger *zap.Logger, apiCfg k8sconfig.APIConfig, rules ExtractionRules, filters Filters, associations []Association, newClientSet APIClientsetProvider, newInformer InformerProvider, newOwnerProviderFunc OwnerProvider) (Client, error) { c := &WatchClient{ logger: logger, Rules: rules, Filters: filters, Associations: associations, - Exclude: exclude, deploymentRegex: dRegex, stopCh: make(chan struct{}), } go c.deleteLoop(time.Second*30, defaultPodDeleteGracePeriod) c.Pods = map[PodIdentifier]*Pod{} - c.Namespaces = map[string]*Namespace{} if newClientSet == nil { newClientSet = k8sconfig.MakeClient } @@ -92,6 +84,18 @@ func New(logger *zap.Logger, apiCfg k8sconfig.APIConfig, rules ExtractionRules, if err != nil { return nil, err } + + if c.Rules.OwnerLookupEnabled { + if newOwnerProviderFunc == nil { + newOwnerProviderFunc = newOwnerProvider + } + + c.op, err = newOwnerProviderFunc(logger, c.kc, labelSelector, fieldSelector, c.Filters.Namespace) + if err != nil { + return nil, err + } + } + logger.Info( "k8s filtering", zap.String("labelSelector", labelSelector.String()), @@ -101,38 +105,31 @@ func New(logger *zap.Logger, apiCfg k8sconfig.APIConfig, rules ExtractionRules, newInformer = newSharedInformer } - if newNamespaceInformer == nil { - newNamespaceInformer = newNamespaceSharedInformer - } - c.informer = newInformer(c.kc, c.Filters.Namespace, labelSelector, fieldSelector) - if c.extractNamespaceLabelsAnnotations() { - c.namespaceInformer = newNamespaceInformer(c.kc) - } else { - c.namespaceInformer = NewNoOpInformer(c.kc) - } return c, err } // Start registers pod event handlers and starts watching the kubernetes cluster for pod changes. func (c *WatchClient) Start() { + if c.op != nil { + c.op.Start() + } + c.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.handlePodAdd, UpdateFunc: c.handlePodUpdate, DeleteFunc: c.handlePodDelete, }) - go c.informer.Run(c.stopCh) - c.namespaceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: c.handleNamespaceAdd, - UpdateFunc: c.handleNamespaceUpdate, - DeleteFunc: c.handleNamespaceDelete, - }) - go c.namespaceInformer.Run(c.stopCh) + c.informer.Run(c.stopCh) } // Stop signals the the k8s watcher/informer to stop watching for new events. func (c *WatchClient) Stop() { close(c.stopCh) + + if c.op != nil { + c.op.Stop() + } } func (c *WatchClient) handlePodAdd(obj interface{}) { @@ -169,40 +166,6 @@ func (c *WatchClient) handlePodDelete(obj interface{}) { observability.RecordPodTableSize(int64(podTableSize)) } -func (c *WatchClient) handleNamespaceAdd(obj interface{}) { - observability.RecordNamespaceAdded() - if namespace, ok := obj.(*api_v1.Namespace); ok { - c.addOrUpdateNamespace(namespace) - } else { - c.logger.Error("object received was not of type api_v1.Namespace", zap.Any("received", obj)) - } -} - -func (c *WatchClient) handleNamespaceUpdate(old, new interface{}) { - observability.RecordNamespaceUpdated() - if namespace, ok := new.(*api_v1.Namespace); ok { - c.addOrUpdateNamespace(namespace) - } else { - c.logger.Error("object received was not of type api_v1.Namespace", zap.Any("received", new)) - } -} - -func (c *WatchClient) handleNamespaceDelete(obj interface{}) { - observability.RecordNamespaceDeleted() - if namespace, ok := obj.(*api_v1.Namespace); ok { - c.m.Lock() - if ns, ok := c.Namespaces[namespace.Name]; ok { - // When a namespace is deleted all the pods(and other k8s objects in that namespace) in that namespace are deleted before it. - // So we wont have any spans that might need namespace annotations and labels. - // Thats why we dont need an implementation for deleteQueue and gracePeriod for namespaces. - delete(c.Namespaces, ns.Name) - } - c.m.Unlock() - } else { - c.logger.Error("object received was not of type api_v1.Namespace", zap.Any("received", obj)) - } -} - func (c *WatchClient) deleteLoop(interval time.Duration, gracePeriod time.Duration) { // This loop runs after N seconds and deletes pods from cache. // It iterates over the delete queue and deletes all that aren't @@ -258,99 +221,142 @@ func (c *WatchClient) GetPod(identifier PodIdentifier) (*Pod, bool) { return nil, false } -// GetNamespace takes a namespace and returns the namespace object the namespace is associated with. -func (c *WatchClient) GetNamespace(namespace string) (*Namespace, bool) { - c.m.RLock() - ns, ok := c.Namespaces[namespace] - c.m.RUnlock() - if ok { - return ns, ok - } - return nil, false -} - func (c *WatchClient) extractPodAttributes(pod *api_v1.Pod) map[string]string { tags := map[string]string{} if c.Rules.PodName { - tags[conventions.AttributeK8SPodName] = pod.Name + tags[c.Rules.Tags.PodName] = pod.Name } if c.Rules.Namespace { - tags[conventions.AttributeK8SNamespaceName] = pod.GetNamespace() + tags[c.Rules.Tags.Namespace] = pod.GetNamespace() } if c.Rules.StartTime { ts := pod.GetCreationTimestamp() if !ts.IsZero() { - tags[tagStartTime] = ts.String() + tags[c.Rules.Tags.StartTime] = ts.String() } } if c.Rules.PodUID { uid := pod.GetUID() - tags[conventions.AttributeK8SPodUID] = string(uid) + tags[c.Rules.Tags.PodUID] = string(uid) } - if c.Rules.Deployment { + if c.Rules.DeploymentName { // format: [deployment-name]-[Random-String-For-ReplicaSet]-[Random-String-For-Pod] parts := c.deploymentRegex.FindStringSubmatch(pod.Name) if len(parts) == 2 { - tags[conventions.AttributeK8SDeploymentName] = parts[1] + tags[c.Rules.Tags.DeploymentName] = parts[1] } } - if c.Rules.Node { - tags[tagNodeName] = pod.Spec.NodeName + if c.Rules.NodeName { + tags[c.Rules.Tags.NodeName] = pod.Spec.NodeName + } + + if c.Rules.HostName { + // Basing on v1.17 Kubernetes docs, when a hostname is specified, it takes precedence over + // the associated metadata name, see: + // https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-hostname-and-subdomain-fields + if pod.Spec.Hostname == "" { + tags[c.Rules.Tags.HostName] = pod.Name + } else { + tags[c.Rules.Tags.HostName] = pod.Spec.Hostname + } } - if c.Rules.Cluster { + if c.Rules.ClusterName { clusterName := pod.GetClusterName() if clusterName != "" { - tags[conventions.AttributeK8SClusterName] = clusterName + tags[c.Rules.Tags.ClusterName] = clusterName } } - for _, r := range c.Rules.Labels { - // By default if the From field is not set for labels and annotations we want to extract them from pod - if r.From == MetadataFromPod || r.From == "" { - if v, ok := pod.Labels[r.Key]; ok { - tags[r.Name] = c.extractField(v, r) + if c.Rules.OwnerLookupEnabled { + owners := c.op.GetOwners(pod) + + for _, owner := range owners { + switch owner.kind { + case "DaemonSet": + if c.Rules.DaemonSetName { + tags[c.Rules.Tags.DaemonSetName] = owner.name + } + case "DeploymentName": + // This should be already set earlier + case "ReplicaSet": + if c.Rules.ReplicaSetName { + tags[c.Rules.Tags.ReplicaSetName] = owner.name + } + case "StatefulSet": + if c.Rules.StatefulSetName { + tags[c.Rules.Tags.StatefulSetName] = owner.name + } + default: + // Do nothing } } + + if c.Rules.ServiceName { + tags[c.Rules.Tags.ServiceName] = strings.Join(c.op.GetServices(pod), ", ") + } + } - for _, r := range c.Rules.Annotations { - // By default if the From field is not set for labels and annotations we want to extract them from pod - if r.From == MetadataFromPod || r.From == "" { - if v, ok := pod.Annotations[r.Key]; ok { - tags[r.Name] = c.extractField(v, r) - } + if len(pod.Status.ContainerStatuses) > 0 { + cs := pod.Status.ContainerStatuses[0] + if c.Rules.ContainerID { + tags[c.Rules.Tags.ContainerID] = cs.ContainerID } } - return tags -} -func (c *WatchClient) extractNamespaceAttributes(namespace *api_v1.Namespace) map[string]string { - tags := map[string]string{} + if len(pod.Spec.Containers) > 0 { + container := pod.Spec.Containers[0] + + if c.Rules.ContainerName { + tags[c.Rules.Tags.ContainerName] = container.Name + } + if c.Rules.ContainerImage { + tags[c.Rules.Tags.ContainerImage] = container.Image + } + } + + if c.Rules.PodUID { + tags[c.Rules.Tags.PodUID] = string(pod.UID) + } for _, r := range c.Rules.Labels { - if r.From == MetadataFromNamespace { - if v, ok := namespace.Labels[r.Key]; ok { - tags[r.Name] = c.extractField(v, r) + c.extractLabelsIntoTags(r, pod.Labels, tags) + } + + if len(c.Rules.NamespaceLabels) > 0 && c.Rules.OwnerLookupEnabled { + namespace := c.op.GetNamespace(pod) + if namespace != nil { + for _, r := range c.Rules.NamespaceLabels { + c.extractLabelsIntoTags(r, namespace.Labels, tags) } } } for _, r := range c.Rules.Annotations { - if r.From == MetadataFromNamespace { - if v, ok := namespace.Annotations[r.Key]; ok { - tags[r.Name] = c.extractField(v, r) - } - } + c.extractLabelsIntoTags(r, pod.Annotations, tags) } return tags } +func (c *WatchClient) extractLabelsIntoTags(r FieldExtractionRule, labels map[string]string, tags map[string]string) { + if r.Key == "*" { + // Special case, extract everything + for label, value := range labels { + tags[fmt.Sprintf(r.Name, label)] = c.extractField(value, r) + } + } else { + if v, ok := labels[r.Key]; ok { + tags[r.Name] = c.extractField(v, r) + } + } +} + func (c *WatchClient) extractField(v string, r FieldExtractionRule) string { // Check if a subset of the field should be extracted with a regular expression // instead of the whole field. @@ -368,7 +374,6 @@ func (c *WatchClient) extractField(v string, r FieldExtractionRule) string { func (c *WatchClient) addOrUpdatePod(pod *api_v1.Pod) { newPod := &Pod{ Name: pod.Name, - Namespace: pod.GetNamespace(), Address: pod.Status.PodIP, PodUID: string(pod.UID), StartTime: pod.Status.StartTime, @@ -445,9 +450,9 @@ func (c *WatchClient) shouldIgnorePod(pod *api_v1.Pod) bool { } } - // Check if user requested the pod to be ignored through configuration - for _, excludedPod := range c.Exclude.Pods { - if excludedPod.Name.MatchString(pod.Name) { + // Check well known names that should be ignored + for _, rexp := range podNameIgnorePatterns { + if rexp.MatchString(pod.Name) { return true } } @@ -482,34 +487,3 @@ func selectorsFromFilters(filters Filters) (labels.Selector, fields.Selector, er } return labelSelector, fields.AndSelectors(selectors...), nil } - -func (c *WatchClient) addOrUpdateNamespace(namespace *api_v1.Namespace) { - newNamespace := &Namespace{ - Name: namespace.Name, - NamespaceUID: string(namespace.UID), - StartTime: namespace.GetCreationTimestamp(), - } - newNamespace.Attributes = c.extractNamespaceAttributes(namespace) - - c.m.Lock() - if namespace.Name != "" { - c.Namespaces[namespace.Name] = newNamespace - } - c.m.Unlock() -} - -func (c *WatchClient) extractNamespaceLabelsAnnotations() bool { - for _, r := range c.Rules.Labels { - if r.From == MetadataFromNamespace { - return true - } - } - - for _, r := range c.Rules.Annotations { - if r.From == MetadataFromNamespace { - return true - } - } - - return false -} diff --git a/processor/k8sprocessor/kube/client_test.go b/processor/k8sprocessor/kube/client_test.go index c8e1ebe5f654..4ad7495b9c39 100644 --- a/processor/k8sprocessor/kube/client_test.go +++ b/processor/k8sprocessor/kube/client_test.go @@ -86,38 +86,13 @@ func podAddAndUpdateTest(t *testing.T, c *WatchClient, handler func(obj interfac } -func namespaceAddAndUpdateTest(t *testing.T, c *WatchClient, handler func(obj interface{})) { - assert.Equal(t, len(c.Namespaces), 0) - - namespace := &api_v1.Namespace{} - handler(namespace) - assert.Equal(t, len(c.Namespaces), 0) - - namespace = &api_v1.Namespace{} - namespace.Name = "namespaceA" - handler(namespace) - assert.Equal(t, len(c.Namespaces), 1) - got := c.Namespaces["namespaceA"] - assert.Equal(t, got.Name, "namespaceA") - assert.Equal(t, got.NamespaceUID, "") - - namespace = &api_v1.Namespace{} - namespace.Name = "namespaceB" - namespace.UID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" - handler(namespace) - assert.Equal(t, len(c.Namespaces), 2) - got = c.Namespaces["namespaceB"] - assert.Equal(t, got.Name, "namespaceB") - assert.Equal(t, got.NamespaceUID, "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee") -} - func TestDefaultClientset(t *testing.T) { - c, err := New(zap.NewNop(), k8sconfig.APIConfig{}, ExtractionRules{}, Filters{}, []Association{}, Excludes{}, nil, nil, nil) + c, err := New(zap.NewNop(), k8sconfig.APIConfig{}, ExtractionRules{}, Filters{}, []Association{}, nil, nil, nil) assert.Error(t, err) assert.Equal(t, "invalid authType for kubernetes: ", err.Error()) assert.Nil(t, c) - c, err = New(zap.NewNop(), k8sconfig.APIConfig{}, ExtractionRules{}, Filters{}, []Association{}, Excludes{}, newFakeAPIClientset, nil, nil) + c, err = New(zap.NewNop(), k8sconfig.APIConfig{}, ExtractionRules{}, Filters{}, []Association{}, newFakeAPIClientset, nil, nil) assert.NoError(t, err) assert.NotNil(t, c) } @@ -129,10 +104,9 @@ func TestBadFilters(t *testing.T) { ExtractionRules{}, Filters{Fields: []FieldFilter{{Op: selection.Exists}}}, []Association{}, - Excludes{}, newFakeAPIClientset, NewFakeInformer, - NewFakeNamespaceInformer, + newFakeOwnerProvider, ) assert.Error(t, err) assert.Nil(t, c) @@ -153,7 +127,6 @@ func TestClientStartStop(t *testing.T) { }() c.Stop() <-done - time.Sleep(time.Second) assert.True(t, fctr.HasStopped()) } @@ -169,7 +142,7 @@ func TestConstructorErrors(t *testing.T) { gotAPIConfig = c return nil, fmt.Errorf("error creating k8s client") } - c, err := New(zap.NewNop(), apiCfg, er, ff, []Association{}, Excludes{}, clientProvider, NewFakeInformer, NewFakeNamespaceInformer) + c, err := New(zap.NewNop(), apiCfg, er, ff, []Association{}, clientProvider, NewFakeInformer, newFakeOwnerProvider) assert.Nil(t, c) assert.Error(t, err) assert.Equal(t, err.Error(), "error creating k8s client") @@ -182,11 +155,6 @@ func TestPodAdd(t *testing.T) { podAddAndUpdateTest(t, c, c.handlePodAdd) } -func TestNamespaceAdd(t *testing.T) { - c, _ := newTestClient(t) - namespaceAddAndUpdateTest(t, c, c.handleNamespaceAdd) -} - func TestPodHostNetwork(t *testing.T) { c, _ := newTestClient(t) assert.Equal(t, 0, len(c.Pods)) @@ -238,14 +206,6 @@ func TestPodUpdate(t *testing.T) { }) } -func TestNamespaceUpdate(t *testing.T) { - c, _ := newTestClient(t) - namespaceAddAndUpdateTest(t, c, func(obj interface{}) { - // first argument (old namespace) is not used right now - c.handleNamespaceUpdate(&api_v1.Namespace{}, obj) - }) -} - func TestPodDelete(t *testing.T) { c, _ := newTestClient(t) podAddAndUpdateTest(t, c, c.handlePodAdd) @@ -307,24 +267,6 @@ func TestPodDelete(t *testing.T) { assert.False(t, deleteRequest.ts.After(time.Now())) } -func TestNamespaceDelete(t *testing.T) { - c, _ := newTestClient(t) - namespaceAddAndUpdateTest(t, c, c.handleNamespaceAdd) - assert.Equal(t, len(c.Namespaces), 2) - assert.Equal(t, c.Namespaces["namespaceA"].Name, "namespaceA") - - // delete empty namespace - c.handleNamespaceDelete(&api_v1.Namespace{}) - - // delete non-existent namespace - namespace := &api_v1.Namespace{} - namespace.Name = "namespaceC" - c.handleNamespaceDelete(namespace) - assert.Equal(t, len(c.Namespaces), 2) - got := c.Namespaces["namespaceA"] - assert.Equal(t, got.Name, "namespaceA") -} - func TestDeleteQueue(t *testing.T) { c, _ := newTestClient(t) podAddAndUpdateTest(t, c, c.handlePodAdd) @@ -400,14 +342,40 @@ func TestHandlerWrongType(t *testing.T) { } } -func TestExtractionRules(t *testing.T) { +func TestNoHostnameExtractionRules(t *testing.T) { c, _ := newTestClientWithRulesAndFilters(t, ExtractionRules{}, Filters{}) + podName := "auth-service-abc12-xyz3" + + pod := &api_v1.Pod{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: podName, + }, + Spec: api_v1.PodSpec{}, + Status: api_v1.PodStatus{ + PodIP: "1.1.1.1", + }, + } + + c.Rules = ExtractionRules{ + HostName: true, + Tags: NewExtractionFieldTags(), + } + + c.handlePodAdd(pod) + p, _ := c.GetPod(PodIdentifier(pod.Status.PodIP)) + assert.Equal(t, p.Attributes["k8s.pod.hostname"], podName) +} + +func TestExtractionRules(t *testing.T) { + // OwnerLookupEnabled is set to true so the newOwnerProviderFunc can be called in the initializer + c, _ := newTestClientWithRulesAndFilters(t, ExtractionRules{OwnerLookupEnabled: true}, Filters{}) + pod := &api_v1.Pod{ ObjectMeta: meta_v1.ObjectMeta{ Name: "auth-service-abc12-xyz3", - UID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", Namespace: "ns1", + UID: "33333", CreationTimestamp: meta_v1.Now(), ClusterName: "cluster1", Labels: map[string]string{ @@ -417,12 +385,31 @@ func TestExtractionRules(t *testing.T) { Annotations: map[string]string{ "annotation1": "av1", }, + OwnerReferences: []meta_v1.OwnerReference{ + { + Kind: "ReplicaSet", + Name: "foo-bar-rs", + UID: "1a1658f9-7818-11e9-90f1-02324f7e0d1e", + }, + }, }, Spec: api_v1.PodSpec{ NodeName: "node1", + Hostname: "auth-hostname3", + Containers: []api_v1.Container{ + { + Image: "auth-service-image", + Name: "auth-service-container-name", + }, + }, }, Status: api_v1.PodStatus{ PodIP: "1.1.1.1", + ContainerStatuses: []api_v1.ContainerStatus{ + { + ContainerID: "111-222-333", + }, + }, }, } @@ -437,7 +424,8 @@ func TestExtractionRules(t *testing.T) { }, { name: "deployment", rules: ExtractionRules{ - Deployment: true, + DeploymentName: true, + Tags: NewExtractionFieldTags(), }, attributes: map[string]string{ "k8s.deployment.name": "auth-service", @@ -445,52 +433,70 @@ func TestExtractionRules(t *testing.T) { }, { name: "metadata", rules: ExtractionRules{ - Deployment: true, - Namespace: true, - PodName: true, - PodUID: true, - Node: true, - Cluster: true, - StartTime: true, + ClusterName: true, + ContainerID: true, + ContainerImage: true, + ContainerName: true, + DaemonSetName: true, + DeploymentName: true, + HostName: true, + PodUID: true, + PodName: true, + ReplicaSetName: true, + ServiceName: true, + StatefulSetName: true, + StartTime: true, + Namespace: true, + NodeName: true, + OwnerLookupEnabled: true, + Tags: NewExtractionFieldTags(), }, attributes: map[string]string{ + "k8s.cluster.name": "cluster1", + "k8s.container.id": "111-222-333", + "k8s.container.image": "auth-service-image", + "k8s.container.name": "auth-service-container-name", "k8s.deployment.name": "auth-service", + "k8s.pod.hostname": "auth-hostname3", + "k8s.pod.id": "33333", + "k8s.pod.name": "auth-service-abc12-xyz3", + "k8s.pod.startTime": pod.GetCreationTimestamp().String(), + "k8s.replicaset.name": "SomeReplicaSet", + "k8s.service.name": "foo, bar", "k8s.namespace.name": "ns1", - "k8s.cluster.name": "cluster1", "k8s.node.name": "node1", - "k8s.pod.name": "auth-service-abc12-xyz3", - "k8s.pod.uid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", - "k8s.pod.start_time": pod.GetCreationTimestamp().String(), }, }, { - name: "labels", + name: "non-default tags", rules: ExtractionRules{ - Annotations: []FieldExtractionRule{{ - Name: "a1", - Key: "annotation1", - From: MetadataFromPod, - }, - }, - Labels: []FieldExtractionRule{{ - Name: "l1", - Key: "label1", - From: MetadataFromPod, - }, { - Name: "l2", - Key: "label2", - Regex: regexp.MustCompile(`k5=(?P[^\s]+)`), - From: MetadataFromPod, - }, + ClusterName: true, + ContainerID: true, + ContainerImage: false, + ContainerName: true, + DaemonSetName: false, + DeploymentName: false, + HostName: false, + PodUID: false, + PodName: false, + ReplicaSetName: false, + ServiceName: false, + StatefulSetName: false, + StartTime: false, + Namespace: false, + NodeName: false, + Tags: ExtractionFieldTags{ + ClusterName: "cc", + ContainerID: "cid", + ContainerName: "cn", }, }, attributes: map[string]string{ - "l1": "lv1", - "l2": "v5", - "a1": "av1", + "cc": "cluster1", + "cid": "111-222-333", + "cn": "auth-service-container-name", }, }, { - // By default if the From field is not set for labels and annotations we want to extract them from pod - name: "labels-annotations-default-pod", + name: "labels", rules: ExtractionRules{ Annotations: []FieldExtractionRule{{ Name: "a1", @@ -513,76 +519,40 @@ func TestExtractionRules(t *testing.T) { "a1": "av1", }, }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - c.Rules = tc.rules - c.handlePodAdd(pod) - p, ok := c.GetPod(PodIdentifier(pod.Status.PodIP)) - require.True(t, ok) - - assert.Equal(t, len(tc.attributes), len(p.Attributes)) - for k, v := range tc.attributes { - got, ok := p.Attributes[k] - assert.True(t, ok) - assert.Equal(t, v, got) - } - }) - } -} - -func TestNamespaceExtractionRules(t *testing.T) { - c, _ := newTestClientWithRulesAndFilters(t, ExtractionRules{}, Filters{}) - - namespace := &api_v1.Namespace{ - ObjectMeta: meta_v1.ObjectMeta{ - Name: "auth-service-namespace-abc12-xyz3", - UID: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", - CreationTimestamp: meta_v1.Now(), - Labels: map[string]string{ - "label1": "lv1", - }, - Annotations: map[string]string{ - "annotation1": "av1", - }, - }, - } - - testCases := []struct { - name string - rules ExtractionRules - attributes map[string]string - }{{ - name: "no-rules", - rules: ExtractionRules{}, - attributes: nil, - }, { - name: "labels", - rules: ExtractionRules{ - Annotations: []FieldExtractionRule{{ - Name: "a1", - Key: "annotation1", - From: MetadataFromNamespace, - }, - }, - Labels: []FieldExtractionRule{{ - Name: "l1", - Key: "label1", - From: MetadataFromNamespace, + { + name: "generic-labels", + rules: ExtractionRules{ + OwnerLookupEnabled: true, + Tags: NewExtractionFieldTags(), + Annotations: []FieldExtractionRule{{ + Name: "k8s.pod.annotation.%s", + Key: "*", + }, + }, + Labels: []FieldExtractionRule{{ + Name: "k8s.pod.label.%s", + Key: "*", + }, + }, + NamespaceLabels: []FieldExtractionRule{{ + Name: "namespace_labels_%s", + Key: "*", + }, + }, }, + attributes: map[string]string{ + "k8s.pod.label.label1": "lv1", + "k8s.pod.label.label2": "k1=v1 k5=v5 extra!", + "k8s.pod.annotation.annotation1": "av1", + "namespace_labels_label": "namespace_label_value", }, }, - attributes: map[string]string{ - "l1": "lv1", - "a1": "av1", - }, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c.Rules = tc.rules - c.handleNamespaceAdd(namespace) - p, ok := c.GetNamespace(namespace.Name) + c.handlePodAdd(pod) + p, ok := c.GetPod(PodIdentifier(pod.Status.PodIP)) require.True(t, ok) assert.Equal(t, len(tc.attributes), len(p.Attributes)) @@ -724,20 +694,6 @@ func TestPodIgnorePatterns(t *testing.T) { Name: "jaeger-collector", }, }, - }, { - ignore: true, - pod: api_v1.Pod{ - ObjectMeta: meta_v1.ObjectMeta{ - Name: "jaeger-agent-b2zdv", - }, - }, - }, { - ignore: false, - pod: api_v1.Pod{ - ObjectMeta: meta_v1.ObjectMeta{ - Name: "test-pod-name", - }, - }, }, } @@ -836,75 +792,10 @@ func Test_selectorsFromFilters(t *testing.T) { } } -func TestExtractNamespaceLabelsAnnotations(t *testing.T) { - c, _ := newTestClientWithRulesAndFilters(t, ExtractionRules{}, Filters{}) - testCases := []struct { - name string - shouldExtractNamespace bool - rules ExtractionRules - }{{ - name: "empty-rules", - shouldExtractNamespace: false, - rules: ExtractionRules{}, - }, { - name: "pod-rules", - shouldExtractNamespace: false, - rules: ExtractionRules{ - Annotations: []FieldExtractionRule{{ - Name: "a1", - Key: "annotation1", - From: MetadataFromPod, - }, - }, - Labels: []FieldExtractionRule{{ - Name: "l1", - Key: "label1", - From: MetadataFromPod, - }, - }, - }, - }, { - name: "namespace-rules-only-annotations", - shouldExtractNamespace: true, - rules: ExtractionRules{ - Annotations: []FieldExtractionRule{{ - Name: "a1", - Key: "annotation1", - From: MetadataFromNamespace, - }, - }, - }, - }, { - name: "namespace-rules-only-labels", - shouldExtractNamespace: true, - rules: ExtractionRules{ - Labels: []FieldExtractionRule{{ - Name: "l1", - Key: "label1", - From: MetadataFromNamespace, - }, - }, - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - c.Rules = tc.rules - assert.Equal(t, tc.shouldExtractNamespace, c.extractNamespaceLabelsAnnotations()) - }) - } -} - func newTestClientWithRulesAndFilters(t *testing.T, e ExtractionRules, f Filters) (*WatchClient, *observer.ObservedLogs) { observedLogger, logs := observer.New(zapcore.WarnLevel) logger := zap.New(observedLogger) - exclude := Excludes{ - Pods: []ExcludePods{ - {Name: regexp.MustCompile(`jaeger-agent`)}, - {Name: regexp.MustCompile(`jaeger-collector`)}, - }, - } - c, err := New(logger, k8sconfig.APIConfig{}, e, f, []Association{}, exclude, newFakeAPIClientset, NewFakeInformer, NewFakeNamespaceInformer) + c, err := New(logger, k8sconfig.APIConfig{}, e, f, []Association{}, newFakeAPIClientset, NewFakeInformer, newFakeOwnerProvider) require.NoError(t, err) return c.(*WatchClient), logs } @@ -912,3 +803,92 @@ func newTestClientWithRulesAndFilters(t *testing.T, e ExtractionRules, f Filters func newTestClient(t *testing.T) (*WatchClient, *observer.ObservedLogs) { return newTestClientWithRulesAndFilters(t, ExtractionRules{}, Filters{}) } + +//func newBenchmarkClient(b *testing.B) *WatchClient { +// e := ExtractionRules{ +// ClusterName: true, +// ContainerID: true, +// ContainerImage: true, +// ContainerName: true, +// DaemonSetName: true, +// DeploymentName: true, +// HostName: true, +// PodUID: true, +// PodName: true, +// ReplicaSetName: true, +// ServiceName: true, +// StatefulSetName: true, +// StartTime: true, +// Namespace: true, +// NodeName: true, +// Tags: NewExtractionFieldTags(), +// } +// f := Filters{} +// +// c, _ := New(zap.NewNop(), e, f, newFakeAPIClientset, newFakeInformer, newFakeOwnerProvider, newFakeOwnerProvider) +// return c.(*WatchClient) +//} +// +//// benchmark actually checks what's the impact of adding new Pod, which is mostly impacted by duration of API call +//func benchmark(b *testing.B, podsPerUniqueOwner int) { +// c := newBenchmarkClient(b) +// +// b.ResetTimer() +// for i := 0; i < b.N; i++ { +// pod := &api_v1.Pod{ +// ObjectMeta: meta_v1.ObjectMeta{ +// Name: fmt.Sprintf("pod-number-%d", i), +// Namespace: "ns1", +// UID: types.UID(fmt.Sprintf("33333-%d", i)), +// CreationTimestamp: meta_v1.Now(), +// ClusterName: "cluster1", +// Labels: map[string]string{ +// "label1": fmt.Sprintf("lv1-%d", i), +// "label2": "k1=v1 k5=v5 extra!", +// }, +// Annotations: map[string]string{ +// "annotation1": fmt.Sprintf("av%d", i), +// }, +// OwnerReferences: []meta_v1.OwnerReference{ +// { +// Kind: "ReplicaSet", +// Name: "foo-bar-rs", +// UID: types.UID(fmt.Sprintf("1a1658f9-7818-11e9-90f1-02324f7e0d1e-%d", i/podsPerUniqueOwner)), +// }, +// }, +// }, +// Spec: api_v1.PodSpec{ +// NodeName: "node1", +// Hostname: "auth-hostname3", +// Containers: []api_v1.Container{ +// { +// Image: "auth-service-image", +// Name: "auth-service-container-name", +// }, +// }, +// }, +// Status: api_v1.PodStatus{ +// PodIP: fmt.Sprintf("%d.%d.%d.%d", (i>>24)%256, (i>>16)%256, (i>>8)%256, i%256), +// ContainerStatuses: []api_v1.ContainerStatus{ +// { +// ContainerID: fmt.Sprintf("111-222-333-%d", i), +// }, +// }, +// }, +// } +// +// c.handlePodAdd(pod) +// _, ok := c.GetPodByIP(pod.Status.PodIP) +// require.True(b, ok) +// +// } +// +//} +// +//func BenchmarkManyPodsPerOwner(b *testing.B) { +// benchmark(b, 100000) +//} +// +//func BenchmarkFewPodsPerOwner(b *testing.B) { +// benchmark(b, 10) +//} diff --git a/processor/k8sprocessor/kube/fake_informer.go b/processor/k8sprocessor/kube/fake_informer.go index d071b1df9018..0a5c03fdc322 100644 --- a/processor/k8sprocessor/kube/fake_informer.go +++ b/processor/k8sprocessor/kube/fake_informer.go @@ -59,31 +59,6 @@ func (f *FakeInformer) GetController() cache.Controller { return f.FakeController } -type FakeNamespaceInformer struct { - *FakeController -} - -func NewFakeNamespaceInformer( - _ kubernetes.Interface, -) cache.SharedInformer { - return &FakeInformer{ - FakeController: &FakeController{}, - } -} - -func (f *FakeNamespaceInformer) AddEventHandler(handler cache.ResourceEventHandler) {} - -func (f *FakeNamespaceInformer) AddEventHandlerWithResyncPeriod(handler cache.ResourceEventHandler, period time.Duration) { -} - -func (f *FakeNamespaceInformer) GetStore() cache.Store { - return cache.NewStore(func(obj interface{}) (string, error) { return "", nil }) -} - -func (f *FakeNamespaceInformer) GetController() cache.Controller { - return f.FakeController -} - type FakeController struct { sync.Mutex stopped bool @@ -113,53 +88,3 @@ func (c *FakeController) LastSyncResourceVersion() string { func (f *FakeInformer) SetWatchErrorHandler(cache.WatchErrorHandler) error { return nil } - -type NoOpInformer struct { - *NoOpController -} - -func NewNoOpInformer( - _ kubernetes.Interface, -) cache.SharedInformer { - return &NoOpInformer{ - NoOpController: &NoOpController{}, - } -} - -func (f *NoOpInformer) AddEventHandler(handler cache.ResourceEventHandler) {} - -func (f *NoOpInformer) AddEventHandlerWithResyncPeriod(handler cache.ResourceEventHandler, period time.Duration) { -} - -func (f *NoOpInformer) GetStore() cache.Store { - return cache.NewStore(func(obj interface{}) (string, error) { return "", nil }) -} - -func (f *NoOpInformer) GetController() cache.Controller { - return f.NoOpController -} - -type NoOpController struct { - hasStopped bool -} - -func (c *NoOpController) Run(stopCh <-chan struct{}) { - go func() { - <-stopCh - c.hasStopped = true - }() -} -func (c *NoOpController) HasStopped() bool { - return c.hasStopped -} -func (c *NoOpController) HasSynced() bool { - return true -} - -func (c *NoOpController) LastSyncResourceVersion() string { - return "" -} - -func (c *NoOpController) SetWatchErrorHandler(cache.WatchErrorHandler) error { - return nil -} diff --git a/processor/k8sprocessor/kube/fake_owner.go b/processor/k8sprocessor/kube/fake_owner.go new file mode 100644 index 000000000000..c757dfddd5c9 --- /dev/null +++ b/processor/k8sprocessor/kube/fake_owner.go @@ -0,0 +1,90 @@ +// Copyright 2019 Omnition 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 kube + +import ( + "go.uber.org/zap" + api_v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" +) + +// fakeOwnerCache is a simple structure which aids querying for owners +type fakeOwnerCache struct { + logger *zap.Logger + objectOwners map[string]*ObjectOwner +} + +// NewOwnerProvider creates new instance of the owners api +func newFakeOwnerProvider(logger *zap.Logger, + client kubernetes.Interface, + labelSelector labels.Selector, + fieldSelector fields.Selector, + namespace string) (OwnerAPI, error) { + ownerCache := fakeOwnerCache{} + ownerCache.objectOwners = map[string]*ObjectOwner{} + ownerCache.logger = logger + + oo := ObjectOwner{ + UID: "1a1658f9-7818-11e9-90f1-02324f7e0d1e", + namespace: "kube-system", + ownerUIDs: []types.UID{}, + kind: "ReplicaSet", + name: "SomeReplicaSet", + } + ownerCache.objectOwners[string(oo.UID)] = &oo + + return &ownerCache, nil +} + +// Start +func (op *fakeOwnerCache) Start() {} + +// Stop +func (op *fakeOwnerCache) Stop() {} + +// GetServices fetches list of services for a given pod +func (op *fakeOwnerCache) GetServices(pod *api_v1.Pod) []string { + return []string{"foo", "bar"} +} + +// GetNamespace returns a namespace +func (op *fakeOwnerCache) GetNamespace(pod *api_v1.Pod) *api_v1.Namespace { + namespace := api_v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.Namespace, + Labels: map[string]string{"label": "namespace_label_value"}, + }, + } + return &namespace +} + +// GetOwners fetches deep tree of owners for a given pod +func (op *fakeOwnerCache) GetOwners(pod *api_v1.Pod) []*ObjectOwner { + objectOwners := []*ObjectOwner{} + + // Make sure the tree is cached/traversed first + for _, or := range pod.OwnerReferences { + oo, found := op.objectOwners[string(or.UID)] + if found { + objectOwners = append(objectOwners, oo) + } + } + + return objectOwners +} diff --git a/processor/k8sprocessor/kube/informer.go b/processor/k8sprocessor/kube/informer.go index 8352d65a65f4..1ecfcd652653 100644 --- a/processor/k8sprocessor/kube/informer.go +++ b/processor/k8sprocessor/kube/informer.go @@ -36,12 +36,6 @@ type InformerProvider func( fieldSelector fields.Selector, ) cache.SharedInformer -// InformerProviderNamespace defines a function type that returns a new SharedInformer. It is used to -// allow passing custom shared informers to the watch client for fetching namespace objects. -type InformerProviderNamespace func( - client kubernetes.Interface, -) cache.SharedInformer - func newSharedInformer( client kubernetes.Interface, namespace string, @@ -75,30 +69,3 @@ func informerWatchFuncWithSelectors(client kubernetes.Interface, namespace strin return client.CoreV1().Pods(namespace).Watch(context.Background(), opts) } } - -func newNamespaceSharedInformer( - client kubernetes.Interface, -) cache.SharedInformer { - informer := cache.NewSharedInformer( - &cache.ListWatch{ - ListFunc: namespaceInformerListFunc(client), - WatchFunc: namespaceInformerWatchFunc(client), - }, - &api_v1.Namespace{}, - watchSyncPeriod, - ) - return informer -} - -func namespaceInformerListFunc(client kubernetes.Interface) cache.ListFunc { - return func(opts metav1.ListOptions) (runtime.Object, error) { - return client.CoreV1().Namespaces().List(context.Background(), opts) - } - -} - -func namespaceInformerWatchFunc(client kubernetes.Interface) cache.WatchFunc { - return func(opts metav1.ListOptions) (watch.Interface, error) { - return client.CoreV1().Namespaces().Watch(context.Background(), opts) - } -} diff --git a/processor/k8sprocessor/kube/informer_test.go b/processor/k8sprocessor/kube/informer_test.go index 7920ca3c21ad..d4272d919b9a 100644 --- a/processor/k8sprocessor/kube/informer_test.go +++ b/processor/k8sprocessor/kube/informer_test.go @@ -37,13 +37,6 @@ func Test_newSharedInformer(t *testing.T) { assert.NotNil(t, informer) } -func Test_newSharedNamespaceInformer(t *testing.T) { - client, err := newFakeAPIClientset(k8sconfig.APIConfig{}) - require.NoError(t, err) - informer := newNamespaceSharedInformer(client) - assert.NotNil(t, informer) -} - func Test_informerListFuncWithSelectors(t *testing.T) { ls, fs, err := selectorsFromFilters(Filters{ Fields: []FieldFilter{ @@ -71,16 +64,6 @@ func Test_informerListFuncWithSelectors(t *testing.T) { assert.NotNil(t, obj) } -func Test_namespaceInformerListFunc(t *testing.T) { - c, err := newFakeAPIClientset(k8sconfig.APIConfig{}) - assert.NoError(t, err) - listFunc := namespaceInformerListFunc(c) - opts := metav1.ListOptions{} - obj, err := listFunc(opts) - assert.NoError(t, err) - assert.NotNil(t, obj) -} - func Test_informerWatchFuncWithSelectors(t *testing.T) { ls, fs, err := selectorsFromFilters(Filters{ Fields: []FieldFilter{ @@ -108,16 +91,6 @@ func Test_informerWatchFuncWithSelectors(t *testing.T) { assert.NotNil(t, obj) } -func Test_namespaceInformerWatchFunc(t *testing.T) { - c, err := newFakeAPIClientset(k8sconfig.APIConfig{}) - assert.NoError(t, err) - watchFunc := namespaceInformerWatchFunc(c) - opts := metav1.ListOptions{} - obj, err := watchFunc(opts) - assert.NoError(t, err) - assert.NotNil(t, obj) -} - func Test_fakeInformer(t *testing.T) { // nothing real to test here. just to make coverage happy c, err := newFakeAPIClientset(k8sconfig.APIConfig{}) @@ -129,15 +102,3 @@ func Test_fakeInformer(t *testing.T) { store := i.GetStore() store.Add(api_v1.Pod{}) } - -func Test_fakeNamespaceInformer(t *testing.T) { - // nothing real to test here. just to make coverage happy - c, err := newFakeAPIClientset(k8sconfig.APIConfig{}) - assert.NoError(t, err) - i := NewFakeNamespaceInformer(c) - i.AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{}, time.Second) - i.HasSynced() - i.LastSyncResourceVersion() - store := i.GetStore() - store.Add(api_v1.Namespace{}) -} diff --git a/processor/k8sprocessor/kube/kube.go b/processor/k8sprocessor/kube/kube.go index dd981746e1ef..74d63e7d266b 100644 --- a/processor/k8sprocessor/kube/kube.go +++ b/processor/k8sprocessor/kube/kube.go @@ -18,6 +18,7 @@ import ( "regexp" "time" + conventions "go.opentelemetry.io/collector/model/semconv/v1.5.0" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/selection" @@ -29,12 +30,18 @@ import ( const ( podNodeField = "spec.nodeName" ignoreAnnotation string = "opentelemetry.io/k8s-processor/ignore" - tagNodeName = "k8s.node.name" - tagStartTime = "k8s.pod.start_time" - // MetadataFromPod is used to specify to extract metadata/labels/annotations from pod - MetadataFromPod = "pod" - // MetadataFromNamespace is used to specify to extract metadata/labels/annotations from namespace - MetadataFromNamespace = "namespace" + + defaultTagContainerID = "k8s.container.id" + defaultTagContainerImage = "k8s.container.image" + defaultTagContainerName = "k8s.container.name" + defaultTagDaemonSetName = "k8s.daemonset.name" + defaultTagHostName = "k8s.pod.hostname" + defaultTagNodeName = "k8s.node.name" + defaultTagPodUID = "k8s.pod.id" + defaultTagReplicaSetName = "k8s.replicaset.name" + defaultTagServiceName = "k8s.service.name" + defaultTagStatefulSetName = "k8s.statefulset.name" + defaultTagStartTime = "k8s.pod.startTime" ) // PodIdentifier is a custom type to represent IP Address or Pod UID @@ -42,6 +49,13 @@ type PodIdentifier string var ( // TODO: move these to config with default values + podNameIgnorePatterns = []*regexp.Regexp{ + regexp.MustCompile(`jaeger-agent`), + regexp.MustCompile(`jaeger-collector`), + regexp.MustCompile(`otel-collector`), + regexp.MustCompile(`otel-agent`), + regexp.MustCompile(`collection-sumologic-otelcol`), + } defaultPodDeleteGracePeriod = time.Second * 120 watchSyncPeriod = time.Minute * 5 ) @@ -49,13 +63,12 @@ var ( // Client defines the main interface that allows querying pods by metadata. type Client interface { GetPod(PodIdentifier) (*Pod, bool) - GetNamespace(string) (*Namespace, bool) Start() Stop() } // ClientProvider defines a func type that returns a new Client. -type ClientProvider func(*zap.Logger, k8sconfig.APIConfig, ExtractionRules, Filters, []Association, Excludes, APIClientsetProvider, InformerProvider, InformerProviderNamespace) (Client, error) +type ClientProvider func(*zap.Logger, k8sconfig.APIConfig, ExtractionRules, Filters, []Association, APIClientsetProvider, InformerProvider, OwnerProvider) (Client, error) // APIClientsetProvider defines a func type that initializes and return a new kubernetes // Clientset object. @@ -69,20 +82,10 @@ type Pod struct { Attributes map[string]string StartTime *metav1.Time Ignore bool - Namespace string DeletedAt time.Time } -// Namespace represents a kubernetes namespace. -type Namespace struct { - Name string - NamespaceUID string - Attributes map[string]string - StartTime metav1.Time - DeletedAt time.Time -} - type deleteRequest struct { // id is identifier (IP address or Pod UID) of pod to remove from pods map id PodIdentifier @@ -96,10 +99,11 @@ type deleteRequest struct { // for performance reasons. We can support adding additional custom filters // in future if there is a real need. type Filters struct { - Node string - Namespace string - Fields []FieldFilter - Labels []FieldFilter + Node string + Namespace string + Fields []FieldFilter + Labels []FieldFilter + NamespaceLabels []FieldFilter } // FieldFilter represents exactly one filter by field rule. @@ -118,16 +122,68 @@ type FieldFilter struct { // ExtractionRules is used to specify the information that needs to be extracted // from pods and added to the spans as tags. type ExtractionRules struct { - Deployment bool - Namespace bool - PodName bool - PodUID bool - Node bool - Cluster bool - StartTime bool - - Annotations []FieldExtractionRule - Labels []FieldExtractionRule + ClusterName bool + ContainerID bool + ContainerImage bool + ContainerName bool + DaemonSetName bool + DeploymentName bool + HostName bool + PodUID bool + PodName bool + ReplicaSetName bool + ServiceName bool + StatefulSetName bool + StartTime bool + Namespace bool + NodeName bool + + OwnerLookupEnabled bool + + Tags ExtractionFieldTags + Annotations []FieldExtractionRule + Labels []FieldExtractionRule + NamespaceLabels []FieldExtractionRule +} + +// ExtractionFieldTags is used to describe selected exported key names for the extracted data +type ExtractionFieldTags struct { + ClusterName string + ContainerID string + ContainerImage string + ContainerName string + DaemonSetName string + DeploymentName string + HostName string + PodUID string + PodName string + Namespace string + NodeName string + ReplicaSetName string + ServiceName string + StartTime string + StatefulSetName string +} + +// NewExtractionFieldTags builds a new instance of tags with default values +func NewExtractionFieldTags() ExtractionFieldTags { + tags := ExtractionFieldTags{} + tags.ClusterName = conventions.AttributeK8SClusterName + tags.ContainerID = defaultTagContainerID + tags.ContainerImage = defaultTagContainerImage + tags.ContainerName = defaultTagContainerName + tags.DaemonSetName = defaultTagDaemonSetName + tags.DeploymentName = conventions.AttributeK8SDeploymentName + tags.HostName = defaultTagHostName + tags.PodUID = defaultTagPodUID + tags.PodName = conventions.AttributeK8SPodName + tags.Namespace = conventions.AttributeK8SNamespaceName + tags.NodeName = defaultTagNodeName + tags.ReplicaSetName = defaultTagReplicaSetName + tags.ServiceName = defaultTagServiceName + tags.StartTime = defaultTagStartTime + tags.StatefulSetName = defaultTagStatefulSetName + return tags } // FieldExtractionRule is used to specify which fields to extract from pod fields @@ -140,11 +196,6 @@ type FieldExtractionRule struct { // Regex is a regular expression used to extract a sub-part of a field value. // Full value is extracted when no regexp is provided. Regex *regexp.Regexp - // From determines the kubernetes object the field should be retrieved from. - // Currently only two values are supported, - // - pod - // - namespace - From string } // Associations represent a list of rules for Pod metadata associations with resources @@ -157,13 +208,3 @@ type Association struct { From string Name string } - -// Excludes represent a list of Pods to ignore -type Excludes struct { - Pods []ExcludePods -} - -// ExcludePods represent a Pod name to ignore -type ExcludePods struct { - Name *regexp.Regexp -} diff --git a/processor/k8sprocessor/kube/owner.go b/processor/k8sprocessor/kube/owner.go new file mode 100644 index 000000000000..25480a151c08 --- /dev/null +++ b/processor/k8sprocessor/kube/owner.go @@ -0,0 +1,334 @@ +// Copyright 2019 OpenTelemetry 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 kube + +import ( + "sort" + "sync" + + "go.uber.org/zap" + api_v1 "k8s.io/api/core/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/observability" +) + +// OwnerProvider allows to dynamically assign constructor +type OwnerProvider func( + logger *zap.Logger, + client kubernetes.Interface, + labelSelector labels.Selector, + fieldSelector fields.Selector, + namespace string, +) (OwnerAPI, error) + +// ObjectOwner keeps single entry +type ObjectOwner struct { + UID types.UID + ownerUIDs []types.UID + namespace string + kind string + name string +} + +// OwnerAPI describes functions that could allow retrieving owner info +type OwnerAPI interface { + GetOwners(pod *api_v1.Pod) []*ObjectOwner + GetNamespace(pod *api_v1.Pod) *api_v1.Namespace + GetServices(pod *api_v1.Pod) []string + Start() + Stop() +} + +// OwnerCache is a simple structure which aids querying for owners +type OwnerCache struct { + objectOwners map[string]*ObjectOwner + podServices map[string][]string + namespaces map[string]*api_v1.Namespace + cacheMutex sync.RWMutex + + client kubernetes.Interface + logger *zap.Logger + + stopCh chan struct{} + informers []cache.SharedIndexInformer +} + +// Start runs the informers +func (op *OwnerCache) Start() { + op.logger.Info("Staring K8S resource informers", zap.Int("#infomers", len(op.informers))) + for _, informer := range op.informers { + go informer.Run(op.stopCh) + } +} + +// Stop shutdowns the informers +func (op *OwnerCache) Stop() { + close(op.stopCh) +} + +func newOwnerProvider( + logger *zap.Logger, + client kubernetes.Interface, + labelSelector labels.Selector, + fieldSelector fields.Selector, + namespace string) (OwnerAPI, error) { + ownerCache := OwnerCache{} + ownerCache.objectOwners = map[string]*ObjectOwner{} + ownerCache.podServices = map[string][]string{} + ownerCache.namespaces = map[string]*api_v1.Namespace{} + ownerCache.cacheMutex = sync.RWMutex{} + + ownerCache.client = client + ownerCache.logger = logger + + factory := informers.NewSharedInformerFactoryWithOptions(client, watchSyncPeriod, + informers.WithNamespace(namespace), + informers.WithTweakListOptions(func(opts *meta_v1.ListOptions) { + opts.LabelSelector = labelSelector.String() + opts.FieldSelector = fieldSelector.String() + })) + + ownerCache.addNamespaceInformer(factory) + + ownerCache.addOwnerInformer("ReplicaSet", + factory.Apps().V1().ReplicaSets().Informer(), + ownerCache.cacheObject, + ownerCache.deleteObject) + + ownerCache.addOwnerInformer("Deployment", + factory.Apps().V1().Deployments().Informer(), + ownerCache.cacheObject, + ownerCache.deleteObject) + + ownerCache.addOwnerInformer("StatefulSet", + factory.Apps().V1().StatefulSets().Informer(), + ownerCache.cacheObject, + ownerCache.deleteObject) + + ownerCache.addOwnerInformer("Endpoint", + factory.Core().V1().Endpoints().Informer(), + ownerCache.cacheEndpoint, + ownerCache.deleteEndpoint) + + return &ownerCache, nil +} + +func (op *OwnerCache) upsertNamespace(obj interface{}) { + namespace := obj.(*api_v1.Namespace) + op.cacheMutex.Lock() + defer op.cacheMutex.Unlock() + op.namespaces[namespace.Name] = namespace +} + +func (op *OwnerCache) deleteNamespace(obj interface{}) { + namespace := obj.(*api_v1.Namespace) + op.cacheMutex.Lock() + defer op.cacheMutex.Unlock() + delete(op.namespaces, namespace.Name) +} + +func (op *OwnerCache) addNamespaceInformer(factory informers.SharedInformerFactory) { + informer := factory.Core().V1().Namespaces().Informer() + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + observability.RecordOtherAdded() + op.upsertNamespace(obj) + }, + UpdateFunc: func(_, obj interface{}) { + observability.RecordOtherUpdated() + op.upsertNamespace(obj) + }, + DeleteFunc: func(obj interface{}) { + observability.RecordOtherDeleted() + op.deleteNamespace(obj) + }, + }) + + op.informers = append(op.informers, informer) +} + +func (op *OwnerCache) addOwnerInformer( + kind string, + informer cache.SharedIndexInformer, + cacheFunc func(kind string, obj interface{}), + deleteFunc func(obj interface{})) { + informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + observability.RecordOtherAdded() + cacheFunc(kind, obj) + }, + UpdateFunc: func(_, obj interface{}) { + observability.RecordOtherUpdated() + cacheFunc(kind, obj) + }, + DeleteFunc: func(obj interface{}) { + observability.RecordOtherDeleted() + deleteFunc(obj) + }, + }) + + op.informers = append(op.informers, informer) +} + +func (op *OwnerCache) deleteObject(obj interface{}) { + op.cacheMutex.Lock() + defer op.cacheMutex.Unlock() + delete(op.objectOwners, string(obj.(meta_v1.Object).GetUID())) +} + +func (op *OwnerCache) cacheObject(kind string, obj interface{}) { + meta := obj.(meta_v1.Object) + + oo := ObjectOwner{ + UID: meta.GetUID(), + namespace: meta.GetNamespace(), + ownerUIDs: []types.UID{}, + kind: kind, + name: meta.GetName(), + } + for _, or := range meta.GetOwnerReferences() { + oo.ownerUIDs = append(oo.ownerUIDs, or.UID) + } + + op.cacheMutex.Lock() + defer op.cacheMutex.Unlock() + op.objectOwners[string(oo.UID)] = &oo +} + +func (op *OwnerCache) addEndpointToPod(pod string, endpoint string) { + op.cacheMutex.RLock() + services := op.podServices[pod] + op.cacheMutex.RUnlock() + + for _, it := range services { + if it == endpoint { + return + } + } + + services = append(services, endpoint) + sort.Strings(services) + + op.cacheMutex.Lock() + defer op.cacheMutex.Unlock() + op.podServices[pod] = services +} + +func (op *OwnerCache) deleteEndpointFromPod(pod string, endpoint string) { + op.cacheMutex.RLock() + services := op.podServices[pod] + op.cacheMutex.RUnlock() + + newServices := []string{} + + for _, it := range services { + if it != endpoint { + newServices = append(newServices, it) + } + } + + op.cacheMutex.Lock() + defer op.cacheMutex.Unlock() + op.podServices[pod] = newServices +} + +func (op *OwnerCache) genericEndpointOp(obj interface{}, endpointFunc func(pod string, endpoint string)) { + ep := obj.(*api_v1.Endpoints) + + for _, it := range ep.Subsets { + for _, addr := range it.Addresses { + if addr.TargetRef != nil && addr.TargetRef.Kind == "Pod" { + endpointFunc(addr.TargetRef.Name, ep.Name) + } + } + for _, addr := range it.NotReadyAddresses { + if addr.TargetRef != nil && addr.TargetRef.Kind == "Pod" { + endpointFunc(addr.TargetRef.Name, ep.Name) + } + } + } +} + +func (op *OwnerCache) deleteEndpoint(obj interface{}) { + op.genericEndpointOp(obj, op.deleteEndpointFromPod) +} + +func (op *OwnerCache) cacheEndpoint(kind string, obj interface{}) { + op.genericEndpointOp(obj, op.addEndpointToPod) +} + +// GetNamespaces returns a cached namespace object (if one is found) or nil otherwise +func (op *OwnerCache) GetNamespace(pod *api_v1.Pod) *api_v1.Namespace { + namespace, found := op.namespaces[pod.Namespace] + if found { + return namespace + } + return nil +} + +// GetServices returns a slice with matched services - in case no services are found, it returns an empty slice +func (op *OwnerCache) GetServices(pod *api_v1.Pod) []string { + op.cacheMutex.RLock() + oo, found := op.podServices[pod.Name] + op.cacheMutex.RUnlock() + + if found { + return oo + } + return []string{} +} + +// GetOwners goes through the cached data and assigns relevant metadata for pod +func (op *OwnerCache) GetOwners(pod *api_v1.Pod) []*ObjectOwner { + objectOwners := []*ObjectOwner{} + + visited := map[types.UID]bool{} + queue := []types.UID{} + + for _, or := range pod.OwnerReferences { + if _, uidVisited := visited[or.UID]; !uidVisited { + queue = append(queue, or.UID) + visited[or.UID] = true + } + } + + for len(queue) > 0 { + uid := queue[0] + queue = queue[1:] + + op.cacheMutex.RLock() + oo, found := op.objectOwners[string(uid)] + if found { + objectOwners = append(objectOwners, oo) + + for _, ownerUID := range oo.ownerUIDs { + if _, uidVisited := visited[ownerUID]; !uidVisited { + queue = append(queue, ownerUID) + visited[ownerUID] = true + } + } + } + op.cacheMutex.RUnlock() + } + + return objectOwners +} diff --git a/processor/k8sprocessor/observability/observability.go b/processor/k8sprocessor/observability/observability.go index efda52eb7317..9298cf09de65 100644 --- a/processor/k8sprocessor/observability/observability.go +++ b/processor/k8sprocessor/observability/observability.go @@ -29,23 +29,25 @@ func init() { viewPodsUpdated, viewPodsAdded, viewPodsDeleted, + viewOtherUpdated, + viewOtherAdded, + viewOtherDeleted, viewIPLookupMiss, viewPodTableSize, - viewNamespacesAdded, - viewNamespacesUpdated, - viewNamespacesDeleted, ) } var ( - mPodsUpdated = stats.Int64("otelsvc/k8s/pod_updated", "Number of pod update events received", "1") - mPodsAdded = stats.Int64("otelsvc/k8s/pod_added", "Number of pod add events received", "1") - mPodsDeleted = stats.Int64("otelsvc/k8s/pod_deleted", "Number of pod delete events received", "1") - mPodTableSize = stats.Int64("otelsvc/k8s/pod_table_size", "Size of table containing pod info", "1") - mIPLookupMiss = stats.Int64("otelsvc/k8s/ip_lookup_miss", "Number of times pod by IP lookup failed.", "1") - mNamespacesUpdated = stats.Int64("otelsvc/k8s/namespace_updated", "Number of namespace update events received", "1") - mNamespacesAdded = stats.Int64("otelsvc/k8s/namespace_added", "Number of namespace add events received", "1") - mNamespacesDeleted = stats.Int64("otelsvc/k8s/namespace_deleted", "Number of namespace delete events received", "1") + mPodsUpdated = stats.Int64("otelsvc/k8s/pod_updated", "Number of pod update events received", "1") + mPodsAdded = stats.Int64("otelsvc/k8s/pod_added", "Number of pod add events received", "1") + mPodsDeleted = stats.Int64("otelsvc/k8s/pod_deleted", "Number of pod delete events received", "1") + mPodTableSize = stats.Int64("otelsvc/k8s/pod_table_size", "Size of table containing pod info", "1") + + mOtherUpdated = stats.Int64("otelsvc/k8s/other_updated", "Number of other update events received", "1") + mOtherAdded = stats.Int64("otelsvc/k8s/other_added", "Number of other add events received", "1") + mOtherDeleted = stats.Int64("otelsvc/k8s/other_deleted", "Number of other delete events received", "1") + + mIPLookupMiss = stats.Int64("otelsvc/k8s/ip_lookup_miss", "Number of times pod by IP lookup failed.", "1") ) var viewPodsUpdated = &view.View{ @@ -69,13 +71,33 @@ var viewPodsDeleted = &view.View{ Aggregation: view.Sum(), } +var viewOtherUpdated = &view.View{ + Name: mOtherUpdated.Name(), + Description: mOtherUpdated.Description(), + Measure: mOtherUpdated, + Aggregation: view.Sum(), +} + +var viewOtherAdded = &view.View{ + Name: mOtherAdded.Name(), + Description: mOtherAdded.Description(), + Measure: mOtherAdded, + Aggregation: view.Sum(), +} + +var viewOtherDeleted = &view.View{ + Name: mOtherDeleted.Name(), + Description: mOtherDeleted.Description(), + Measure: mOtherDeleted, + Aggregation: view.Sum(), +} + var viewIPLookupMiss = &view.View{ Name: mIPLookupMiss.Name(), Description: mIPLookupMiss.Description(), Measure: mIPLookupMiss, Aggregation: view.Sum(), } - var viewPodTableSize = &view.View{ Name: mPodTableSize.Name(), Description: mPodTableSize.Description(), @@ -83,27 +105,6 @@ var viewPodTableSize = &view.View{ Aggregation: view.LastValue(), } -var viewNamespacesUpdated = &view.View{ - Name: mNamespacesUpdated.Name(), - Description: mNamespacesUpdated.Description(), - Measure: mNamespacesUpdated, - Aggregation: view.Sum(), -} - -var viewNamespacesAdded = &view.View{ - Name: mNamespacesAdded.Name(), - Description: mNamespacesAdded.Description(), - Measure: mNamespacesAdded, - Aggregation: view.Sum(), -} - -var viewNamespacesDeleted = &view.View{ - Name: mNamespacesDeleted.Name(), - Description: mNamespacesDeleted.Description(), - Measure: mNamespacesDeleted, - Aggregation: view.Sum(), -} - // RecordPodUpdated increments the metric that records pod update events received. func RecordPodUpdated() { stats.Record(context.Background(), mPodsUpdated.M(int64(1))) @@ -119,27 +120,27 @@ func RecordPodDeleted() { stats.Record(context.Background(), mPodsDeleted.M(int64(1))) } -// RecordIPLookupMiss increments the metric that records Pod lookup by IP misses. -func RecordIPLookupMiss() { - stats.Record(context.Background(), mIPLookupMiss.M(int64(1))) +// RecordOtherUpdated increments the metric that records other update events received. +func RecordOtherUpdated() { + stats.Record(context.Background(), mOtherUpdated.M(int64(1))) } -// RecordPodTableSize store size of pod table field in WatchClient -func RecordPodTableSize(podTableSize int64) { - stats.Record(context.Background(), mPodTableSize.M(podTableSize)) +// RecordOtherAdded increments the metric that records other add events receiver. +func RecordOtherAdded() { + stats.Record(context.Background(), mOtherAdded.M(int64(1))) } -// RecordNamespaceUpdated increments the metric that records namespace update events received. -func RecordNamespaceUpdated() { - stats.Record(context.Background(), mNamespacesUpdated.M(int64(1))) +// RecordOtherDeleted increments the metric that records other events deleted. +func RecordOtherDeleted() { + stats.Record(context.Background(), mOtherDeleted.M(int64(1))) } -// RecordNamespaceAdded increments the metric that records namespace add events receiver. -func RecordNamespaceAdded() { - stats.Record(context.Background(), mNamespacesAdded.M(int64(1))) +// RecordIPLookupMiss increments the metric that records Pod lookup by IP misses. +func RecordIPLookupMiss() { + stats.Record(context.Background(), mIPLookupMiss.M(int64(1))) } -// RecordNamespaceDeleted increments the metric that records namespace events deleted. -func RecordNamespaceDeleted() { - stats.Record(context.Background(), mNamespacesDeleted.M(int64(1))) +// RecordPodTableSize store size of pod table field in WatchClient +func RecordPodTableSize(podTableSize int64) { + stats.Record(context.Background(), mPodTableSize.M(podTableSize)) } diff --git a/processor/k8sprocessor/observability/observability_test.go b/processor/k8sprocessor/observability/observability_test.go index 7e56c06aab96..ddb2bc21f1fe 100644 --- a/processor/k8sprocessor/observability/observability_test.go +++ b/processor/k8sprocessor/observability/observability_test.go @@ -56,9 +56,7 @@ func (e *exporter) ExportMetrics(ctx context.Context, data []*metricdata.Metric) return nil } -// NOTE: -// This test can only be run with -count 1 because of static -// metricproducer.GlobalManager() used in metricexport.NewReader(). +// TODO: FIXME: this is one flaky test func TestMetrics(t *testing.T) { type testCase struct { name string @@ -81,57 +79,20 @@ func TestMetrics(t *testing.T) { "otelsvc/k8s/ip_lookup_miss", RecordIPLookupMiss, }, - { - "otelsvc/k8s/namespace_added", - RecordNamespaceAdded, - }, - { - "otelsvc/k8s/namespace_updated", - RecordNamespaceUpdated, - }, - { - "otelsvc/k8s/namespace_deleted", - RecordNamespaceDeleted, - }, } - var ( - fail = make(chan struct{}) - chData = make(chan []*metricdata.Metric) - ) - - go func() { - reader := metricexport.NewReader() - e := newExporter() - ch := e.ReturnAfter(len(tests)) - - // Add a manual retry mechanism in case there's a hiccup reading the - // metrics from producers in ReadAndExport(): we can wait for the metrics - // to come instead of failing because they didn't come right away. - for i := 0; i < 10; i++ { - go reader.ReadAndExport(e) - - select { - case <-time.After(500 * time.Millisecond): - - case data := <-ch: - chData <- data - return - } - } - - fail <- struct{}{} - }() - + e := newExporter() + metricReader := metricexport.NewReader() for _, tt := range tests { tt.recordFunc() } + go metricReader.ReadAndExport(e) var data []*metricdata.Metric select { - case <-fail: + case <-time.After(time.Second * 10): t.Fatalf("timedout waiting for metrics to arrive") - case data = <-chData: + case data = <-e.ReturnAfter(len(tests)): } sort.Slice(tests, func(i, j int) bool { @@ -142,6 +103,7 @@ func TestMetrics(t *testing.T) { return data[i].Descriptor.Name < data[j].Descriptor.Name }) + // TODO: FIXME: this is one flaky test for i, tt := range tests { require.Len(t, data, len(tests)) d := data[i] diff --git a/processor/k8sprocessor/options.go b/processor/k8sprocessor/options.go index ae1167add0fd..58cc2c1f704d 100644 --- a/processor/k8sprocessor/options.go +++ b/processor/k8sprocessor/options.go @@ -18,8 +18,8 @@ import ( "fmt" "os" "regexp" + "strings" - conventions "go.opentelemetry.io/collector/model/semconv/v1.5.0" "k8s.io/apimachinery/pkg/selection" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" @@ -31,16 +31,22 @@ const ( filterOPNotEquals = "not-equals" filterOPExists = "exists" filterOPDoesNotExist = "does-not-exist" - // Used for maintaining backward compatibility - metdataNamespace = "namespace" - metadataPodName = "podName" - metadataPodUID = "podUID" - metadataStartTime = "startTime" - metadataDeployment = "deployment" - metadataCluster = "cluster" - metadataNode = "node" - // Will be removed when new fields get merged to https://github.com/open-telemetry/opentelemetry-collector/blob/main/model/semconv/opentelemetry.go - metadataPodStartTime = "k8s.pod.start_time" + + metadataContainerID = "containerId" + metadataContainerName = "containerName" + metadataContainerImage = "containerImage" + metadataClusterName = "clusterName" + metadataDaemonSetName = "daemonSetName" + metadataDeploymentName = "deploymentName" + metadataHostName = "hostName" + metadataNamespace = "namespace" + metadataNodeName = "nodeName" + metadataPodID = "podId" + metadataPodName = "podName" + metadataReplicaSetName = "replicaSetName" + metadataServiceName = "serviceName" + metadataStartTime = "startTime" + metadataStatefulSetName = "statefulSetName" ) // Option represents a configuration option that can be passes. @@ -65,40 +71,69 @@ func WithPassthrough() Option { } } +// WithOwnerLookupEnabled makes the processor pull additional owner data from K8S API +func WithOwnerLookupEnabled() Option { + return func(p *kubernetesprocessor) error { + p.rules.OwnerLookupEnabled = true + return nil + } +} + // WithExtractMetadata allows specifying options to control extraction of pod metadata. // If no fields explicitly provided, all metadata extracted by default. func WithExtractMetadata(fields ...string) Option { return func(p *kubernetesprocessor) error { if len(fields) == 0 { fields = []string{ - conventions.AttributeK8SNamespaceName, - conventions.AttributeK8SPodName, - conventions.AttributeK8SPodUID, - metadataPodStartTime, - conventions.AttributeK8SDeploymentName, - conventions.AttributeK8SClusterName, - conventions.AttributeK8SNodeName, + metadataClusterName, + metadataContainerID, + metadataContainerImage, + metadataContainerName, + metadataDaemonSetName, + metadataDeploymentName, + metadataHostName, + metadataNamespace, + metadataNodeName, + metadataPodName, + metadataPodID, + metadataReplicaSetName, + metadataServiceName, + metadataStartTime, + metadataStatefulSetName, } } for _, field := range fields { switch field { - // Old conventions handled by the cases metdataNamespace, metadataPodName, metadataPodUID, - // metadataStartTime, metadataDeployment, metadataCluster, metadataNode are being supported for backward compatibility. - // These will be removed when new conventions get merged to https://github.com/open-telemetry/opentelemetry-collector/blob/main/model/semconv/opentelemetry.go - case metdataNamespace, conventions.AttributeK8SNamespaceName: + case metadataClusterName: + p.rules.ClusterName = true + case metadataContainerID: + p.rules.ContainerID = true + case metadataContainerImage: + p.rules.ContainerImage = true + case metadataContainerName: + p.rules.ContainerName = true + case metadataDaemonSetName: + p.rules.DaemonSetName = true + case metadataDeploymentName: + p.rules.DeploymentName = true + case metadataHostName: + p.rules.HostName = true + case metadataNamespace: p.rules.Namespace = true - case metadataPodName, conventions.AttributeK8SPodName: - p.rules.PodName = true - case metadataPodUID, conventions.AttributeK8SPodUID: + case metadataNodeName: + p.rules.NodeName = true + case metadataPodID: p.rules.PodUID = true - case metadataStartTime, metadataPodStartTime: + case metadataPodName: + p.rules.PodName = true + case metadataReplicaSetName: + p.rules.ReplicaSetName = true + case metadataServiceName: + p.rules.ServiceName = true + case metadataStartTime: p.rules.StartTime = true - case metadataDeployment, conventions.AttributeK8SDeploymentName: - p.rules.Deployment = true - case metadataCluster, conventions.AttributeK8SClusterName: - p.rules.Cluster = true - case metadataNode, conventions.AttributeK8SNodeName: - p.rules.Node = true + case metadataStatefulSetName: + p.rules.StatefulSetName = true default: return fmt.Errorf("\"%s\" is not a supported metadata field", field) } @@ -107,6 +142,51 @@ func WithExtractMetadata(fields ...string) Option { } } +// WithExtractTags allows specifying custom tag names +func WithExtractTags(tagsMap map[string]string) Option { + return func(p *kubernetesprocessor) error { + var tags = kube.NewExtractionFieldTags() + for field, tag := range tagsMap { + switch field { + case strings.ToLower(metadataClusterName): + tags.ClusterName = tag + case strings.ToLower(metadataContainerID): + tags.ContainerID = tag + case strings.ToLower(metadataContainerName): + tags.ContainerName = tag + case strings.ToLower(metadataContainerImage): + tags.ContainerImage = tag + case strings.ToLower(metadataDaemonSetName): + tags.DaemonSetName = tag + case strings.ToLower(metadataDeploymentName): + tags.DeploymentName = tag + case strings.ToLower(metadataHostName): + tags.HostName = tag + case strings.ToLower(metadataNamespace): + tags.Namespace = tag + case strings.ToLower(metadataNodeName): + tags.NodeName = tag + case strings.ToLower(metadataPodID): + tags.PodUID = tag + case strings.ToLower(metadataPodName): + tags.PodName = tag + case strings.ToLower(metadataReplicaSetName): + tags.ReplicaSetName = tag + case strings.ToLower(metadataServiceName): + tags.ServiceName = tag + case strings.ToLower(metadataStartTime): + tags.StartTime = tag + case strings.ToLower(metadataStatefulSetName): + tags.StatefulSetName = tag + default: + return fmt.Errorf("\"%s\" is not a supported metadata field", field) + } + } + p.rules.Tags = tags + return nil + } +} + // WithExtractLabels allows specifying options to control extraction of pod labels. func WithExtractLabels(labels ...FieldExtractConfig) Option { return func(p *kubernetesprocessor) error { @@ -135,22 +215,11 @@ func extractFieldRules(fieldType string, fields ...FieldExtractConfig) ([]kube.F rules := []kube.FieldExtractionRule{} for _, a := range fields { name := a.TagName - - switch a.From { - // By default if the From field is not set for labels and annotations we want to extract them from pod - case "", kube.MetadataFromPod: - a.From = kube.MetadataFromPod - case kube.MetadataFromNamespace: - a.From = kube.MetadataFromNamespace - default: - return rules, fmt.Errorf("%s is not a valid choice for From. Must be one of: pod, namespace", a.From) - } - if name == "" { - if a.From == kube.MetadataFromPod { - name = fmt.Sprintf("k8s.pod.%s.%s", fieldType, a.Key) - } else if a.From == kube.MetadataFromNamespace { - name = fmt.Sprintf("k8s.namespace.%s.%s", fieldType, a.Key) + if a.Key == "*" { + name = fmt.Sprintf("k8s.%s.%%s", fieldType) + } else { + name = fmt.Sprintf("k8s.%s.%s", fieldType, a.Key) } } @@ -168,7 +237,7 @@ func extractFieldRules(fieldType string, fields ...FieldExtractConfig) ([]kube.F } rules = append(rules, kube.FieldExtractionRule{ - Name: name, Key: a.Key, Regex: r, From: a.From, + Name: name, Key: a.Key, Regex: r, }) } return rules, nil @@ -270,20 +339,3 @@ func WithExtractPodAssociations(podAssociations ...PodAssociationConfig) Option return nil } } - -// WithExcludes allows specifying pods to exclude -func WithExcludes(podExclude ExcludeConfig) Option { - return func(p *kubernetesprocessor) error { - ignoredNames := kube.Excludes{} - names := podExclude.Pods - - if len(names) == 0 { - names = []ExcludePodConfig{{Name: "jaeger-agent"}, {Name: "jaeger-collector"}} - } - for _, name := range names { - ignoredNames.Pods = append(ignoredNames.Pods, kube.ExcludePods{Name: regexp.MustCompile(name.Name)}) - } - p.podIgnore = ignoredNames - return nil - } -} diff --git a/processor/k8sprocessor/options_test.go b/processor/k8sprocessor/options_test.go index 00101845340f..edbbebb674ec 100644 --- a/processor/k8sprocessor/options_test.go +++ b/processor/k8sprocessor/options_test.go @@ -21,7 +21,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - conventions "go.opentelemetry.io/collector/model/semconv/v1.5.0" "k8s.io/apimachinery/pkg/selection" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" @@ -90,7 +89,6 @@ func TestWithExtractAnnotations(t *testing.T) { TagName: "tag1", Key: "key1", Regex: "[", - From: kube.MetadataFromPod, }, }, []kube.FieldExtractionRule{}, @@ -103,7 +101,6 @@ func TestWithExtractAnnotations(t *testing.T) { TagName: "tag1", Key: "key1", Regex: "field=(?P.+)", - From: kube.MetadataFromPod, }, }, []kube.FieldExtractionRule{ @@ -111,25 +108,6 @@ func TestWithExtractAnnotations(t *testing.T) { Name: "tag1", Key: "key1", Regex: regexp.MustCompile(`field=(?P.+)`), - From: kube.MetadataFromPod, - }, - }, - "", - }, - { - "basic-namespace", - []FieldExtractConfig{ - { - TagName: "tag1", - Key: "key1", - From: kube.MetadataFromNamespace, - }, - }, - []kube.FieldExtractionRule{ - { - Name: "tag1", - Key: "key1", - From: kube.MetadataFromNamespace, }, }, "", @@ -174,7 +152,6 @@ func TestWithExtractLabels(t *testing.T) { TagName: "t1", Key: "k1", Regex: "[", - From: kube.MetadataFromPod, }}, []kube.FieldExtractionRule{}, "error parsing regexp: missing closing ]: `[`", @@ -186,7 +163,6 @@ func TestWithExtractLabels(t *testing.T) { TagName: "tag1", Key: "key1", Regex: "field=(?P.+)", - From: kube.MetadataFromPod, }, }, []kube.FieldExtractionRule{ @@ -194,25 +170,6 @@ func TestWithExtractLabels(t *testing.T) { Name: "tag1", Key: "key1", Regex: regexp.MustCompile(`field=(?P.+)`), - From: kube.MetadataFromPod, - }, - }, - "", - }, - { - "basic-namespace", - []FieldExtractConfig{ - { - TagName: "tag1", - Key: "key1", - From: kube.MetadataFromNamespace, - }, - }, - []kube.FieldExtractionRule{ - { - Name: "tag1", - Key: "key1", - From: kube.MetadataFromNamespace, }, }, "", @@ -245,24 +202,23 @@ func TestWithExtractMetadata(t *testing.T) { assert.True(t, p.rules.PodName) assert.True(t, p.rules.PodUID) assert.True(t, p.rules.StartTime) - assert.True(t, p.rules.Deployment) - assert.True(t, p.rules.Cluster) - assert.True(t, p.rules.Node) + assert.True(t, p.rules.DeploymentName) + assert.True(t, p.rules.ClusterName) + assert.True(t, p.rules.NodeName) p = &kubernetesprocessor{} err := WithExtractMetadata("randomfield")(p) assert.Error(t, err) assert.Equal(t, err.Error(), `"randomfield" is not a supported metadata field`) - p = &kubernetesprocessor{} - assert.NoError(t, WithExtractMetadata(conventions.AttributeK8SNamespaceName, conventions.AttributeK8SPodName, conventions.AttributeK8SPodUID)(p)) + assert.NoError(t, WithExtractMetadata("namespace", "clusterName")(p)) assert.True(t, p.rules.Namespace) - assert.False(t, p.rules.Cluster) - assert.True(t, p.rules.PodName) - assert.True(t, p.rules.PodUID) + assert.True(t, p.rules.ClusterName) + assert.False(t, p.rules.PodName) + assert.False(t, p.rules.PodUID) assert.False(t, p.rules.StartTime) - assert.False(t, p.rules.Deployment) - assert.False(t, p.rules.Node) + assert.False(t, p.rules.DeploymentName) + assert.False(t, p.rules.NodeName) } func TestWithFilterLabels(t *testing.T) { @@ -543,15 +499,13 @@ func Test_extractFieldRules(t *testing.T) { "default", args{"labels", []FieldExtractConfig{ { - Key: "key", - From: kube.MetadataFromPod, + Key: "key", }, }}, []kube.FieldExtractionRule{ { - Name: "k8s.pod.labels.key", + Name: "k8s.labels.key", Key: "key", - From: kube.MetadataFromPod, }, }, false, @@ -562,14 +516,12 @@ func Test_extractFieldRules(t *testing.T) { { TagName: "name", Key: "key", - From: kube.MetadataFromPod, }, }}, []kube.FieldExtractionRule{ { Name: "name", Key: "key", - From: kube.MetadataFromPod, }, }, false, @@ -581,7 +533,6 @@ func Test_extractFieldRules(t *testing.T) { TagName: "name", Key: "key", Regex: "^h$", - From: kube.MetadataFromPod, }, }}, []kube.FieldExtractionRule{}, @@ -594,7 +545,6 @@ func Test_extractFieldRules(t *testing.T) { TagName: "name", Key: "key", Regex: "[", - From: kube.MetadataFromPod, }, }}, []kube.FieldExtractionRule{}, @@ -651,45 +601,3 @@ func TestWithExtractPodAssociation(t *testing.T) { }) } } - -func TestWithExcludes(t *testing.T) { - tests := []struct { - name string - args ExcludeConfig - want kube.Excludes - }{ - { - "default", - ExcludeConfig{}, - kube.Excludes{ - Pods: []kube.ExcludePods{ - {Name: regexp.MustCompile(`jaeger-agent`)}, - {Name: regexp.MustCompile(`jaeger-collector`)}, - }, - }, - }, - { - "configured", - ExcludeConfig{ - Pods: []ExcludePodConfig{ - {Name: "ignore_pod1"}, - {Name: "ignore_pod2"}, - }, - }, - kube.Excludes{ - Pods: []kube.ExcludePods{ - {Name: regexp.MustCompile(`ignore_pod1`)}, - {Name: regexp.MustCompile(`ignore_pod2`)}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - p := &kubernetesprocessor{} - option := WithExcludes(tt.args) - option(p) - assert.Equal(t, tt.want, p.podIgnore) - }) - } -} diff --git a/processor/k8sprocessor/pod_association.go b/processor/k8sprocessor/pod_association.go index 632980895831..f8a21b4584b5 100644 --- a/processor/k8sprocessor/pod_association.go +++ b/processor/k8sprocessor/pod_association.go @@ -28,69 +28,62 @@ import ( // extractPodIds extracts IP and pod UID from attributes or request context. // It returns a value pair containing configured label and IP Address and/or Pod UID. // If empty value in return it means that attributes does not contains configured label to match resources for Pod. -func extractPodID(ctx context.Context, attrs pdata.AttributeMap, associations []kube.Association) (string, kube.PodIdentifier) { +func extractPodID(ctx context.Context, attrs pdata.AttributeMap, associations []kube.Association) (podIdentifierKey string, podIdentifierValue kube.PodIdentifier) { + hostname := stringAttributeFromMap(attrs, conventions.AttributeHostName) + var connectionIP kube.PodIdentifier + if c, ok := client.FromContext(ctx); ok { + connectionIP = kube.PodIdentifier(c.IP) + } // If pod association is not set if len(associations) == 0 { - return extractPodIDNoAssociations(ctx, attrs) + var podIP, labelIP kube.PodIdentifier + podIP = kube.PodIdentifier(stringAttributeFromMap(attrs, k8sIPLabelName)) + labelIP = kube.PodIdentifier(stringAttributeFromMap(attrs, clientIPLabelName)) + podIdentifierKey = k8sIPLabelName + if podIP != "" { + podIdentifierValue = podIP + return + } else if labelIP != "" { + podIdentifierValue = labelIP + return + } else if connectionIP != "" { + podIdentifierValue = connectionIP + return + } else if net.ParseIP(hostname) != nil { + podIdentifierValue = kube.PodIdentifier(hostname) + return + } + podIdentifierKey = "" + return } - connectionIP := getConnectionIP(ctx) - hostname := stringAttributeFromMap(attrs, conventions.AttributeHostName) for _, asso := range associations { // If association configured to take IP address from connection - switch { - case asso.From == "connection" && connectionIP != "": - return k8sIPLabelName, connectionIP - case asso.From == "resource_attribute": - // If association configured by resource_attribute + if asso.From == "connection" && connectionIP != "" { + podIdentifierKey = k8sIPLabelName + podIdentifierValue = connectionIP + return + } else if asso.From == "resource_attribute" { // If association configured by resource_attribute // In k8s environment, host.name label set to a pod IP address. // If the value doesn't represent an IP address, we skip it. if asso.Name == conventions.AttributeHostName { if net.ParseIP(hostname) != nil { - return k8sIPLabelName, kube.PodIdentifier(hostname) + podIdentifierKey = k8sIPLabelName + podIdentifierValue = kube.PodIdentifier(hostname) + return } } else { // Extract values based on configured resource_attribute. attributeValue := stringAttributeFromMap(attrs, asso.Name) if attributeValue != "" { - return asso.Name, kube.PodIdentifier(attributeValue) + podIdentifierKey = asso.Name + podIdentifierValue = kube.PodIdentifier(attributeValue) + return } } } } - return "", "" -} - -func extractPodIDNoAssociations(ctx context.Context, attrs pdata.AttributeMap) (string, kube.PodIdentifier) { - var podIP, labelIP kube.PodIdentifier - podIP = kube.PodIdentifier(stringAttributeFromMap(attrs, k8sIPLabelName)) - if podIP != "" { - return k8sIPLabelName, podIP - } - - labelIP = kube.PodIdentifier(stringAttributeFromMap(attrs, clientIPLabelName)) - if labelIP != "" { - return k8sIPLabelName, labelIP - } - - connectionIP := getConnectionIP(ctx) - if connectionIP != "" { - return k8sIPLabelName, connectionIP - } - - hostname := stringAttributeFromMap(attrs, conventions.AttributeHostName) - if net.ParseIP(hostname) != nil { - return k8sIPLabelName, kube.PodIdentifier(hostname) - } - - return "", "" -} - -func getConnectionIP(ctx context.Context) kube.PodIdentifier { - if c, ok := client.FromContext(ctx); ok { - return kube.PodIdentifier(c.IP) - } - return "" + return "", kube.PodIdentifier("") } func stringAttributeFromMap(attrs pdata.AttributeMap, key string) string { diff --git a/processor/k8sprocessor/processor.go b/processor/k8sprocessor/processor.go index 439925c02afc..fb3779d6139b 100644 --- a/processor/k8sprocessor/processor.go +++ b/processor/k8sprocessor/processor.go @@ -19,7 +19,6 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/model/pdata" - conventions "go.opentelemetry.io/collector/model/semconv/v1.5.0" "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/k8sconfig" @@ -39,7 +38,6 @@ type kubernetesprocessor struct { rules kube.ExtractionRules filters kube.Filters podAssociations []kube.Association - podIgnore kube.Excludes } func (kp *kubernetesprocessor) initKubeClient(logger *zap.Logger, kubeClient kube.ClientProvider) error { @@ -47,7 +45,7 @@ func (kp *kubernetesprocessor) initKubeClient(logger *zap.Logger, kubeClient kub kubeClient = kube.New } if !kp.passthroughMode { - kc, err := kubeClient(logger, kp.apiConfig, kp.rules, kp.filters, kp.podAssociations, kp.podIgnore, nil, nil, nil) + kc, err := kubeClient(logger, kp.apiConfig, kp.rules, kp.filters, kp.podAssociations, nil, nil, nil) if err != nil { return err } @@ -70,8 +68,8 @@ func (kp *kubernetesprocessor) Shutdown(context.Context) error { return nil } -// processTraces process traces and add k8s metadata using resource IP or incoming IP as pod origin. -func (kp *kubernetesprocessor) processTraces(ctx context.Context, td pdata.Traces) (pdata.Traces, error) { +// ProcessTraces process traces and add k8s metadata using resource IP or incoming IP as pod origin. +func (kp *kubernetesprocessor) ProcessTraces(ctx context.Context, td pdata.Traces) (pdata.Traces, error) { rss := td.ResourceSpans() for i := 0; i < rss.Len(); i++ { kp.processResource(ctx, rss.At(i).Resource()) @@ -80,8 +78,8 @@ func (kp *kubernetesprocessor) processTraces(ctx context.Context, td pdata.Trace return td, nil } -// processMetrics process metrics and add k8s metadata using resource IP, hostname or incoming IP as pod origin. -func (kp *kubernetesprocessor) processMetrics(ctx context.Context, md pdata.Metrics) (pdata.Metrics, error) { +// ProcessMetrics process metrics and add k8s metadata using resource IP, hostname or incoming IP as pod origin. +func (kp *kubernetesprocessor) ProcessMetrics(ctx context.Context, md pdata.Metrics) (pdata.Metrics, error) { rm := md.ResourceMetrics() for i := 0; i < rm.Len(); i++ { kp.processResource(ctx, rm.At(i).Resource()) @@ -90,8 +88,8 @@ func (kp *kubernetesprocessor) processMetrics(ctx context.Context, md pdata.Metr return md, nil } -// processLogs process logs and add k8s metadata using resource IP, hostname or incoming IP as pod origin. -func (kp *kubernetesprocessor) processLogs(ctx context.Context, ld pdata.Logs) (pdata.Logs, error) { +// ProcessLogs process logs and add k8s metadata using resource IP, hostname or incoming IP as pod origin. +func (kp *kubernetesprocessor) ProcessLogs(ctx context.Context, ld pdata.Logs) (pdata.Logs, error) { rl := ld.ResourceLogs() for i := 0; i < rl.Len(); i++ { kp.processResource(ctx, rl.At(i).Resource()) @@ -102,32 +100,18 @@ func (kp *kubernetesprocessor) processLogs(ctx context.Context, ld pdata.Logs) ( // processResource adds Pod metadata tags to resource based on pod association configuration func (kp *kubernetesprocessor) processResource(ctx context.Context, resource pdata.Resource) { - podIdentifierKey, podIdentifierValue := extractPodID(ctx, resource.Attributes(), kp.podAssociations) - if podIdentifierKey != "" { - resource.Attributes().InsertString(podIdentifierKey, string(podIdentifierValue)) - } - namespace := stringAttributeFromMap(resource.Attributes(), conventions.AttributeK8SNamespaceName) - if namespace != "" { - resource.Attributes().InsertString(conventions.AttributeK8SNamespaceName, namespace) + podIdentifierKey, podIdentifierValue := extractPodID(ctx, resource.Attributes(), kp.podAssociations) + if podIdentifierKey == "" { + return } - + resource.Attributes().InsertString(podIdentifierKey, string(podIdentifierValue)) if kp.passthroughMode { return } - - if podIdentifierKey != "" { - attrsToAdd := kp.getAttributesForPod(podIdentifierValue) - for key, val := range attrsToAdd { - resource.Attributes().InsertString(key, val) - } - } - - if namespace != "" { - attrsToAdd := kp.getAttributesForPodsNamespace(namespace) - for key, val := range attrsToAdd { - resource.Attributes().InsertString(key, val) - } + attrsToAdd := kp.getAttributesForPod(podIdentifierValue) + for key, val := range attrsToAdd { + resource.Attributes().InsertString(key, val) } } @@ -138,11 +122,3 @@ func (kp *kubernetesprocessor) getAttributesForPod(identifier kube.PodIdentifier } return pod.Attributes } - -func (kp *kubernetesprocessor) getAttributesForPodsNamespace(namespace string) map[string]string { - ns, ok := kp.kc.GetNamespace(namespace) - if !ok { - return nil - } - return ns.Attributes -} diff --git a/processor/k8sprocessor/processor_test.go b/processor/k8sprocessor/processor_test.go index 613aa28786b8..631bc89000dc 100644 --- a/processor/k8sprocessor/processor_test.go +++ b/processor/k8sprocessor/processor_test.go @@ -36,9 +36,9 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sprocessor/kube" ) -func newTracesProcessor(cfg config.Processor, next consumer.Traces, options ...Option) (component.TracesProcessor, error) { +func newTraceProcessor(cfg config.Processor, next consumer.Traces, options ...Option) (component.TracesProcessor, error) { opts := append(options, withKubeClientProvider(newFakeClient)) - return createTracesProcessorWithOptions( + return createTraceProcessorWithOptions( context.Background(), componenttest.NewNopProcessorCreateSettings(), cfg, @@ -113,7 +113,7 @@ func newMultiTest( nextLogs: new(consumertest.LogsSink), } - tp, err := newTracesProcessor(cfg, m.nextTrace, append(options, withExtractKubernetesProcessorInto(&m.kpTrace))...) + tp, err := newTraceProcessor(cfg, m.nextTrace, append(options, withExtractKubernetesProcessorInto(&m.kpTrace))...) if errFunc == nil { assert.NotNil(t, tp) require.NoError(t, err) @@ -217,7 +217,7 @@ func TestProcessorBadConfig(t *testing.T) { } func TestProcessorBadClientProvider(t *testing.T) { - clientProvider := func(_ *zap.Logger, _ k8sconfig.APIConfig, _ kube.ExtractionRules, _ kube.Filters, _ []kube.Association, _ kube.Excludes, _ kube.APIClientsetProvider, _ kube.InformerProvider, _ kube.InformerProviderNamespace) (kube.Client, error) { + clientProvider := func(_ *zap.Logger, _ k8sconfig.APIConfig, _ kube.ExtractionRules, _ kube.Filters, _ []kube.Association, _ kube.APIClientsetProvider, _ kube.InformerProvider, _ kube.OwnerProvider) (kube.Client, error) { return nil, fmt.Errorf("bad client error") } @@ -323,7 +323,7 @@ func TestProcessorNoAttrs(t *testing.T) { t, NewFactory().CreateDefaultConfig(), nil, - WithExtractMetadata(conventions.AttributeK8SPodName), + WithExtractMetadata(metadataPodName), ) ctx := client.NewContext(context.Background(), &client.Client{IP: "1.1.1.1"}) @@ -701,7 +701,7 @@ func TestMetricsProcessorHostname(t *testing.T) { p, err := newMetricsProcessor( NewFactory().CreateDefaultConfig(), next, - WithExtractMetadata(conventions.AttributeK8SPodName), + WithExtractMetadata(metadataPodName), withExtractKubernetesProcessorInto(&kp), ) require.NoError(t, err) @@ -771,7 +771,7 @@ func TestMetricsProcessorHostnameWithPodAssociation(t *testing.T) { p, err := newMetricsProcessor( NewFactory().CreateDefaultConfig(), next, - WithExtractMetadata(conventions.AttributeK8SPodName), + WithExtractMetadata(metadataPodName), withExtractKubernetesProcessorInto(&kp), ) require.NoError(t, err) @@ -845,7 +845,7 @@ func TestPassthroughStart(t *testing.T) { next := new(consumertest.TracesSink) opts := []Option{WithPassthrough()} - p, err := newTracesProcessor( + p, err := newTraceProcessor( NewFactory().CreateDefaultConfig(), next, opts..., @@ -871,7 +871,7 @@ func TestRealClient(t *testing.T) { } func TestCapabilities(t *testing.T) { - p, err := newTracesProcessor( + p, err := newTraceProcessor( NewFactory().CreateDefaultConfig(), consumertest.NewNop(), ) @@ -882,7 +882,7 @@ func TestCapabilities(t *testing.T) { func TestStartStop(t *testing.T) { var kp *kubernetesprocessor - p, err := newTracesProcessor( + p, err := newTraceProcessor( NewFactory().CreateDefaultConfig(), consumertest.NewNop(), withExtractKubernetesProcessorInto(&kp), @@ -908,3 +908,28 @@ func assertResourceHasStringAttribute(t *testing.T, r pdata.Resource, k, v strin assert.EqualValues(t, pdata.AttributeValueTypeString, got.Type(), "attribute %s is not of type string", k) assert.EqualValues(t, v, got.StringVal(), "attribute %s is not equal to %s", k, v) } + +//func BenchmarkConsumingTraceData(b *testing.B) { +// next := &testConsumer{} +// p, _ := NewTraceProcessor( +// zap.NewNop(), +// next, +// kube.NewFakeClient, +// ) +// +// kp, _ := p.(*kubernetesprocessor) +// kc, _ := kp.kc.(*kube.FakeClient) +// +// b.ResetTimer() +// for i := 0; i < b.N; i++ { +// ip := "1.1.1.1" +// attrs := map[string]string{ +// "pod": "test-2323", +// "ns": "default", +// "another tag": "value", +// } +// kc.Pods[ip] = &kube.Pod{Attributes: attrs} +// ctx := client.NewContext(context.Background(), &client.Client{IP: ip}) +// p.ConsumeTraceData(ctx, consumerdata.TraceData{}) +// } +//} diff --git a/processor/k8sprocessor/testdata/config.yaml b/processor/k8sprocessor/testdata/config.yaml index 9c4f5c6242d2..2ce564611572 100644 --- a/processor/k8sprocessor/testdata/config.yaml +++ b/processor/k8sprocessor/testdata/config.yaml @@ -5,42 +5,51 @@ processors: k8s_tagger: k8s_tagger/2: passthrough: false + owner_lookup_enabled: true auth_type: "kubeConfig" extract: metadata: - # the following metadata fields configuration options are deprecated - # - podName - # - podUID - # - deployment - # - cluster - # - namespace - # - node - # - startTime - # extract the following well-known metadata fields from pods and namespaces - - k8s.pod.name - - k8s.pod.uid - - k8s.deployment.name - - k8s.cluster.name - - k8s.namespace.name - - k8s.node.name - - k8s.pod.start_time + # extract the following well-known metadata fields + - containerId + - containerName + - containerImage + - clusterName + - daemonSetName + - deploymentName + - hostName + - namespace + - nodeName + - podId + - podName + - replicaSetName + - serviceName + - startTime + - statefulSetName + tags: + # It is possible to provide your custom key names for each of the extracted metadata: + containerId: my.namespace.containerId annotations: - tag_name: a1 # extracts value of annotation with key `annotation-one` and inserts it as a tag with key `a1` key: annotation-one - from: pod - tag_name: a2 # extracts value of annotation with key `annotation-two` with regexp and inserts it as a tag with key `a2` key: annotation-two regex: field=(?P.+) - from: pod + # You can also extract all annotations, e.g.: + # - tag_name: k8s.annotation.%s + # key: "*" labels: - tag_name: l1 # extracts value of label with key `label1` and inserts it as a tag with key `l1` key: label1 - from: pod - tag_name: l2 # extracts value of label with key `label1` with regexp and inserts it as a tag with key `l2` key: label2 regex: field=(?P.+) - from: pod + # You can also extract all labels, e.g.: + # - tag_name: k8s.label.%s + # key: "*" + namespace_labels: + - tag_name: "namespace_labels_%s" + key: "*" filter: namespace: ns2 # only look for pods running in ns2 namespace @@ -70,12 +79,6 @@ processors: name: ip - from: resource_attribute name: k8s.pod.uid - - exclude: - pods: - - name: jaeger-agent - - name: jaeger-collector - exporters: nop: diff --git a/processor/resourcedetectionprocessor/internal/aws/ec2/ec2.go b/processor/resourcedetectionprocessor/internal/aws/ec2/ec2.go index b8ddfccbb828..340131f5b781 100644 --- a/processor/resourcedetectionprocessor/internal/aws/ec2/ec2.go +++ b/processor/resourcedetectionprocessor/internal/aws/ec2/ec2.go @@ -75,6 +75,7 @@ func (d *Detector) Detect(ctx context.Context) (resource pdata.Resource, schemaU attr := res.Attributes() attr.InsertString(conventions.AttributeCloudProvider, conventions.AttributeCloudProviderAWS) attr.InsertString(conventions.AttributeCloudPlatform, conventions.AttributeCloudPlatformAWSEC2) + attr.InsertString("cloud.namespace", "aws/ec2") attr.InsertString(conventions.AttributeCloudRegion, meta.Region) attr.InsertString(conventions.AttributeCloudAccountID, meta.AccountID) attr.InsertString(conventions.AttributeCloudAvailabilityZone, meta.AvailabilityZone) diff --git a/processor/resourcedetectionprocessor/internal/aws/ec2/ec2_test.go b/processor/resourcedetectionprocessor/internal/aws/ec2/ec2_test.go index 03fa46dd18a8..57b2090bdf17 100644 --- a/processor/resourcedetectionprocessor/internal/aws/ec2/ec2_test.go +++ b/processor/resourcedetectionprocessor/internal/aws/ec2/ec2_test.go @@ -134,7 +134,8 @@ func TestDetector_Detect(t *testing.T) { attr := res.Attributes() attr.InsertString("cloud.account.id", "account1234") attr.InsertString("cloud.provider", "aws") - attr.InsertString("cloud.platform", "aws_ec2") + attr.InsertString("cloud.infrastructure_service", "aws_ec2") + attr.InsertString("cloud.namespace", "aws/ec2") attr.InsertString("cloud.region", "us-west-2") attr.InsertString("cloud.availability_zone", "us-west-2a") attr.InsertString("host.id", "i-abcd1234") diff --git a/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go b/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go index ebd861902465..ed0bab49a940 100644 --- a/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go +++ b/processor/resourcedetectionprocessor/internal/aws/ecs/ecs.go @@ -68,6 +68,7 @@ func (d *Detector) Detect(context.Context) (resource pdata.Resource, schemaURL s attr := res.Attributes() attr.InsertString(conventions.AttributeCloudProvider, conventions.AttributeCloudProviderAWS) attr.InsertString(conventions.AttributeCloudPlatform, conventions.AttributeCloudPlatformAWSECS) + attr.InsertString("cloud.namespace", "ecs") attr.InsertString(conventions.AttributeAWSECSTaskARN, tmdeResp.TaskARN) attr.InsertString(conventions.AttributeAWSECSTaskFamily, tmdeResp.Family) attr.InsertString(conventions.AttributeAWSECSTaskRevision, tmdeResp.Revision) diff --git a/processor/resourcedetectionprocessor/internal/aws/ecs/ecs_test.go b/processor/resourcedetectionprocessor/internal/aws/ecs/ecs_test.go index e697dac05076..d0330d6b8cf0 100644 --- a/processor/resourcedetectionprocessor/internal/aws/ecs/ecs_test.go +++ b/processor/resourcedetectionprocessor/internal/aws/ecs/ecs_test.go @@ -114,7 +114,8 @@ func Test_ecsDetectV4(t *testing.T) { want := pdata.NewResource() attr := want.Attributes() attr.InsertString("cloud.provider", "aws") - attr.InsertString("cloud.platform", "aws_ecs") + attr.InsertString("cloud.infrastructure_service", "aws_ecs") + attr.InsertString("cloud.namespace", "ecs") attr.InsertString("aws.ecs.cluster.arn", "arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster") attr.InsertString("aws.ecs.task.arn", "arn:aws:ecs:us-west-2:123456789123:task/123") attr.InsertString("aws.ecs.task.family", "family") @@ -150,7 +151,8 @@ func Test_ecsDetectV3(t *testing.T) { want := pdata.NewResource() attr := want.Attributes() attr.InsertString("cloud.provider", "aws") - attr.InsertString("cloud.platform", "aws_ecs") + attr.InsertString("cloud.infrastructure_service", "aws_ecs") + attr.InsertString("cloud.namespace", "ecs") attr.InsertString("aws.ecs.cluster.arn", "arn:aws:ecs:us-west-2:123456789123:cluster/my-cluster") attr.InsertString("aws.ecs.task.arn", "arn:aws:ecs:us-west-2:123456789123:task/123") attr.InsertString("aws.ecs.task.family", "family") diff --git a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk.go b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk.go index 80956679267c..a2bd02d84c22 100644 --- a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk.go +++ b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk.go @@ -79,6 +79,7 @@ func (d Detector) Detect(context.Context) (resource pdata.Resource, schemaURL st attr := res.Attributes() attr.InsertString(conventions.AttributeCloudProvider, conventions.AttributeCloudProviderAWS) attr.InsertString(conventions.AttributeCloudPlatform, conventions.AttributeCloudPlatformAWSElasticBeanstalk) + attr.InsertString("cloud.namespace", "ElasticBeanstalk") attr.InsertString(conventions.AttributeServiceInstanceID, strconv.Itoa(ebmd.DeploymentID)) attr.InsertString(conventions.AttributeDeploymentEnvironment, ebmd.EnvironmentName) attr.InsertString(conventions.AttributeServiceVersion, ebmd.VersionLabel) diff --git a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk_test.go b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk_test.go index 9bb819f5f4d6..06fd48f3ebf8 100644 --- a/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk_test.go +++ b/processor/resourcedetectionprocessor/internal/aws/elasticbeanstalk/elasticbeanstalk_test.go @@ -98,7 +98,8 @@ func Test_AttributesDetectedSuccessfully(t *testing.T) { want := pdata.NewResource() attr := want.Attributes() attr.InsertString("cloud.provider", "aws") - attr.InsertString("cloud.platform", "aws_elastic_beanstalk") + attr.InsertString("cloud.infrastructure_service", "aws_elastic_beanstalk") + attr.InsertString("cloud.namespace", "ElasticBeanstalk") attr.InsertString("deployment.environment", "BETA") attr.InsertString("service.instance.id", "23") attr.InsertString("service.version", "env-version-1234") diff --git a/processor/sourceprocessor/Makefile b/processor/sourceprocessor/Makefile new file mode 100644 index 000000000000..c1496226e590 --- /dev/null +++ b/processor/sourceprocessor/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common \ No newline at end of file diff --git a/processor/sourceprocessor/README.md b/processor/sourceprocessor/README.md new file mode 100644 index 000000000000..e542631b9c25 --- /dev/null +++ b/processor/sourceprocessor/README.md @@ -0,0 +1,73 @@ +# Source Processor + +The `sourceprocessor` adds `_source` and other tags related to Sumo Logic metadata taxonomy. + +It leverages data tagged by `k8sprocessor` and must be after it in the processing chain. +It has certain expectations on the label names used by `k8sprocessor` which might be configured below. + +## Config + +- `collector` (default = ""): name of the collector, put in `collector` tag +- `source` (default = "traces"): name of the source, put in `_source` tag +- `source_name` (default = "%{namespace}.%{pod}.%{container}"): `_sourceName` template +- `source_category` (default = "%{namespace}/%{pod_name}"): `_sourceCategory` template +- `source_category_prefix` (default = "kubernetes/"): prefix added before each `_sourceCategory` value +- `source_category_replace_dash` (default = "/"): character which all dashes (`-`) are being replaced to + +### Filtering section + +**NOTE**: The filtering is done on the resource level attributes. + +- `exclude` (default = empty): a mapping of field names to exclusion regexes + for those particular fields. Whenever a value under particular field matches + a corresponding regex, the processed entry is dropped. + + **NOTE**: + + When systemd related filtering is taking place (`exclude` contains + an entry for `_SYSTEMD_UNIT`) then whenever the processed record contains + `_HOSTNAME` attribute it will be added to the resulting record under `host` key. + +### Keys section (must match `k8sprocessor` config) + +- `annotation_prefix` (default = "pod_annotation_"): prefix which allows to find given annotation; +it is used for including/excluding pods, among other attributes +- `pod_template_hash_key` (default = "pod_labels_pod-template-hash"): attribute where pod template +hash is found (used for `pod` extraction) +- `pod_name_key` (default = "pod_name"): attribute where name portion of the pod is stored +during enrichment +- `namespace_key` (default = "namespace"): attribute where namespace name is found +- `pod_key` (default = "pod"): attribute where pod full name is found +- `container_key` (default = "container"): attribute where container name is found +- `source_host_key` (default = "source_host"): attribute where source host is found + +### Name translation and template keys + +The key names provided as `namespace`, `pod`, `pod_name`, `container` in templates for `source_category` +or `source_name`are replaced with the key name provided in `namespace_key`, `pod_key`, +`pod_name_key`, `container_key` respectively. + +For example, when default template for `source_category` is being used (`%{namespace}/%{pod_name}`) and +`namespace_key=k8s.namespace.name`, the resource has attributes: + +```yaml +k8s.namespace.name: my-namespace +pod_name: some-name +``` + +Then the `_source_category` will contain: `my-namespace/some-name` + +### Example config + +```yaml +processors: + source: + collector: "mycollector" + source_name: "%{namespace}.%{pod}.%{container}" + source_category: "%{namespace}/%{pod_name}" + source_category_prefix: "kubernetes/" + source_category_replace_dash: "/" + exclude: + namespace: "kube-system" + pod: "custom-pod-.*" +``` diff --git a/processor/sourceprocessor/attribute_filler.go b/processor/sourceprocessor/attribute_filler.go new file mode 100644 index 000000000000..d7ddecee4233 --- /dev/null +++ b/processor/sourceprocessor/attribute_filler.go @@ -0,0 +1,123 @@ +// Copyright 2021 OpenTelemetry 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 sourceprocessor + +import ( + "fmt" + "regexp" + "strings" + + "go.opentelemetry.io/collector/model/pdata" +) + +var ( + formatRegex *regexp.Regexp +) + +func init() { + var err error + formatRegex, err = regexp.Compile(`\%\{(\w+)\}`) + if err != nil { + panic("failed to parse regex: " + err.Error()) + } +} + +type attributeFiller struct { + name string + compiledFormat string + dashReplacement string + prefix string + labels []string +} + +func createSourceHostFiller() attributeFiller { + return attributeFiller{ + name: sourceHostKey, + compiledFormat: "", + dashReplacement: "", + labels: make([]string, 0), + prefix: "", + } +} + +func extractFormat(format string, name string, keys sourceKeys) attributeFiller { + labels := make([]string, 0) + matches := formatRegex.FindAllStringSubmatch(format, -1) + for _, matchset := range matches { + labels = append(labels, keys.convertKey(matchset[1])) + } + template := formatRegex.ReplaceAllString(format, "%s") + + return attributeFiller{ + name: name, + compiledFormat: template, + dashReplacement: "", + labels: labels, + prefix: "", + } +} + +func createSourceNameFiller(cfg *Config, keys sourceKeys) attributeFiller { + filler := extractFormat(cfg.SourceName, sourceNameKey, keys) + return filler +} + +func createSourceCategoryFiller(cfg *Config, keys sourceKeys) attributeFiller { + filler := extractFormat(cfg.SourceCategory, sourceCategoryKey, keys) + filler.compiledFormat = cfg.SourceCategoryPrefix + filler.compiledFormat + filler.dashReplacement = cfg.SourceCategoryReplaceDash + filler.prefix = cfg.SourceCategoryPrefix + return filler +} + +func (f *attributeFiller) fillResourceOrUseAnnotation(atts *pdata.AttributeMap, annotationKey string, keys sourceKeys) bool { + val, found := atts.Get(annotationKey) + if found { + annotationFiller := extractFormat(val.StringVal(), f.name, keys) + annotationFiller.dashReplacement = f.dashReplacement + annotationFiller.compiledFormat = f.prefix + annotationFiller.compiledFormat + return annotationFiller.fillAttributes(atts) + } + return f.fillAttributes(atts) +} + +func (f *attributeFiller) fillAttributes(atts *pdata.AttributeMap) bool { + if len(f.compiledFormat) == 0 { + return false + } + + labelValues := f.resourceLabelValues(atts) + if labelValues != nil { + str := fmt.Sprintf(f.compiledFormat, labelValues...) + if f.dashReplacement != "" { + str = strings.ReplaceAll(str, "-", f.dashReplacement) + } + atts.UpsertString(f.name, str) + return true + } + return false +} + +func (f *attributeFiller) resourceLabelValues(atts *pdata.AttributeMap) []interface{} { + arr := make([]interface{}, 0) + for _, label := range f.labels { + value, ok := atts.Get(label) + if !ok { + return nil + } + arr = append(arr, value.StringVal()) + } + return arr +} diff --git a/processor/sourceprocessor/config.go b/processor/sourceprocessor/config.go new file mode 100644 index 000000000000..34efe74271eb --- /dev/null +++ b/processor/sourceprocessor/config.go @@ -0,0 +1,46 @@ +// Copyright 2019 OpenTelemetry 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 sourceprocessor + +import ( + "go.opentelemetry.io/collector/config" +) + +// Config defines configuration for Source processor. +type Config struct { + *config.ProcessorSettings `mapstructure:"-"` + + Collector string `mapstructure:"collector"` + Source string `mapstructure:"source"` + SourceName string `mapstructure:"source_name"` + SourceCategory string `mapstructure:"source_category"` + SourceCategoryPrefix string `mapstructure:"source_category_prefix"` + SourceCategoryReplaceDash string `mapstructure:"source_category_replace_dash"` + + // Exclude is a mapping of field names to exclusion regexes for those + // particular fields. + // Whenever a value for a particular field matches a corresponding regex, + // the processed entry is dropped. + Exclude map[string]string `mapstructure:"exclude"` + + AnnotationPrefix string `mapstructure:"annotation_prefix"` + ContainerKey string `mapstructure:"container_key"` + NamespaceKey string `mapstructure:"namespace_key"` + PodKey string `mapstructure:"pod_key"` + PodIDKey string `mapstructure:"pod_id_key"` + PodNameKey string `mapstructure:"pod_name_key"` + PodTemplateHashKey string `mapstructure:"pod_template_hash_key"` + SourceHostKey string `mapstructure:"source_host_key"` +} diff --git a/processor/sourceprocessor/config_test.go b/processor/sourceprocessor/config_test.go new file mode 100644 index 000000000000..109db6349353 --- /dev/null +++ b/processor/sourceprocessor/config_test.go @@ -0,0 +1,73 @@ +// Copyright 2019 OpenTelemetry 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 sourceprocessor + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.NopFactories() + assert.NoError(t, err) + + factory := NewFactory() + factories.Processors[typeStr] = factory + + cfgPath := path.Join(".", "testdata", "config.yaml") + cfg, err := configtest.LoadConfig(cfgPath, factories) + assert.NoError(t, err) + assert.NotNil(t, cfg) + + id1 := config.NewID("source") + p1 := cfg.Processors[id1] + assert.Equal(t, p1, factory.CreateDefaultConfig()) + + id2 := config.NewIDWithName("source", "2") + p2, ok := cfg.Processors[id2] + assert.True(t, ok) + + ps2 := config.NewProcessorSettings(id2) + assert.Equal(t, p2, &Config{ + ProcessorSettings: &ps2, + Collector: "somecollector", + Source: "tracesource", + SourceName: "%{namespace}.%{pod}.%{container}/foo", + SourceCategory: "%{namespace}/%{pod_name}/bar", + SourceCategoryPrefix: "kubernetes/", + SourceCategoryReplaceDash: "/", + Exclude: map[string]string{ + "container": "excluded_container_regex", + "host": "excluded_host_regex", + "namespace": "excluded_namespace_regex", + "pod": "excluded_pod_regex", + "_SYSTEMD_UNIT": "excluded_systemd_unit_regex", + }, + + AnnotationPrefix: "pod_annotation_", + ContainerKey: "container", + NamespaceKey: "namespace", + PodKey: "pod", + PodIDKey: "pod_id", + PodNameKey: "pod_name", + PodTemplateHashKey: "pod_labels_pod-template-hash", + SourceHostKey: "source_host", + }) +} diff --git a/processor/sourceprocessor/doc.go b/processor/sourceprocessor/doc.go new file mode 100644 index 000000000000..c7de42f563bc --- /dev/null +++ b/processor/sourceprocessor/doc.go @@ -0,0 +1,17 @@ +// Copyright 2019 OpenTelemetry 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 sourceprocessor implements a processor for tagging +// _source* fields +package sourceprocessor diff --git a/processor/sourceprocessor/factory.go b/processor/sourceprocessor/factory.go new file mode 100644 index 000000000000..0483aab305ad --- /dev/null +++ b/processor/sourceprocessor/factory.go @@ -0,0 +1,137 @@ +// Copyright 2019 OpenTelemetry 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 sourceprocessor + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "source" + + defaultSource = "traces" + defaultCollector = "" + + defaultSourceName = "%{namespace}.%{pod}.%{container}" + defaultSourceCategory = "%{namespace}/%{pod_name}" + defaultSourceCategoryPrefix = "kubernetes/" + defaultSourceCategoryReplaceDash = "/" + + defaultAnnotationPrefix = "pod_annotation_" + defaultContainerKey = "container" + defaultNamespaceKey = "namespace" + defaultPodIDKey = "pod_id" + defaultPodKey = "pod" + defaultPodNameKey = "pod_name" + defaultPodTemplateHashKey = "pod_labels_pod-template-hash" + defaultSourceHostKey = "source_host" +) + +var processorCapabilities = consumer.Capabilities{MutatesData: true} + +// NewFactory returns a new factory for the Span processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithTraces(createTraceProcessor), + processorhelper.WithMetrics(createMetricsProcessor), + processorhelper.WithLogs(createLogsProcessor), + ) +} + +// createDefaultConfig creates the default configuration for processor. +func createDefaultConfig() config.Processor { + ps := config.NewProcessorSettings(config.NewID(typeStr)) + return &Config{ + ProcessorSettings: &ps, + Source: defaultSource, + Collector: defaultCollector, + SourceName: defaultSourceName, + SourceCategory: defaultSourceCategory, + SourceCategoryPrefix: defaultSourceCategoryPrefix, + SourceCategoryReplaceDash: defaultSourceCategoryReplaceDash, + + AnnotationPrefix: defaultAnnotationPrefix, + ContainerKey: defaultContainerKey, + NamespaceKey: defaultNamespaceKey, + PodKey: defaultPodKey, + PodIDKey: defaultPodIDKey, + PodNameKey: defaultPodNameKey, + PodTemplateHashKey: defaultPodTemplateHashKey, + SourceHostKey: defaultSourceHostKey, + } +} + +// CreateTraceProcessor creates a trace processor based on this config. +func createTraceProcessor( + _ context.Context, + params component.ProcessorCreateSettings, + cfg config.Processor, + next consumer.Traces) (component.TracesProcessor, error) { + + oCfg := cfg.(*Config) + + sp := newSourceProcessor(oCfg) + + return processorhelper.NewTracesProcessor( + cfg, + next, + sp.ProcessTraces, + processorhelper.WithCapabilities(processorCapabilities), + ) +} + +// createMetricsProcessor creates a metrics processor based on this config +func createMetricsProcessor( + _ context.Context, + params component.ProcessorCreateSettings, + cfg config.Processor, + next consumer.Metrics, +) (component.MetricsProcessor, error) { + oCfg := cfg.(*Config) + + sp := newSourceProcessor(oCfg) + return processorhelper.NewMetricsProcessor( + cfg, + next, + sp.ProcessMetrics, + processorhelper.WithCapabilities(processorCapabilities), + ) +} + +// createLogsProcessor creates a logs processor based on this config +func createLogsProcessor( + _ context.Context, + params component.ProcessorCreateSettings, + cfg config.Processor, + next consumer.Logs, +) (component.LogsProcessor, error) { + oCfg := cfg.(*Config) + + sp := newSourceProcessor(oCfg) + return processorhelper.NewLogsProcessor( + cfg, + next, + sp.ProcessLogs, + processorhelper.WithCapabilities(processorCapabilities), + ) +} diff --git a/processor/sourceprocessor/go.mod b/processor/sourceprocessor/go.mod new file mode 100644 index 000000000000..e7b6a0dbc710 --- /dev/null +++ b/processor/sourceprocessor/go.mod @@ -0,0 +1,10 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/processor/sourceprocessor + +go 1.16 + +require ( + github.com/stretchr/testify v1.7.0 + go.opencensus.io v0.23.0 + go.opentelemetry.io/collector v0.35.0 + go.opentelemetry.io/collector/model v0.35.0 +) diff --git a/processor/sourceprocessor/go.sum b/processor/sourceprocessor/go.sum new file mode 100644 index 000000000000..79f2854715d0 --- /dev/null +++ b/processor/sourceprocessor/go.sum @@ -0,0 +1,792 @@ +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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0QmaQNhGnptITnPQB+z1+qeFB0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +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-cleanhttp v0.5.1/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.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +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.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/koanf v1.2.2 h1:CydaDM/2mtza/ytVVnj4iQSVPjDq+pSV2vWMFDiQS08= +github.com/knadh/koanf v1.2.2/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +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/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface 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/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ= +github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v3.21.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/collector v0.35.0 h1:e7kutiBxbyk9Gc0DkFDYxnWrmW9sAPpXbgFoQmof/kE= +go.opentelemetry.io/collector v0.35.0/go.mod h1:mMW4VJHhy8CwoTenV75axteKd0aZhWHDZjcZ59V53kc= +go.opentelemetry.io/collector/model v0.35.0 h1:NpKjghiqlei4ecwjOYOMhD6tj4gY8yiWHPJmbFs/ArI= +go.opentelemetry.io/collector/model v0.35.0/go.mod h1:+7YCSjJG+MqiIFjauzt7oM2qkqBsaJWh5hcsO4fwsAc= +go.opentelemetry.io/contrib v0.22.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0/go.mod h1:KjqwX4uJNaj479ZjFpADOMJKOM4rBXq4kN7nbeuGKrY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.22.0/go.mod h1:o3MuU25bYroYnc2TOKe8mTk8f9X1oPFO6C5RCoPKtSU= +go.opentelemetry.io/contrib/zpages v0.22.0/go.mod h1:pO7VUk5qoCiekzXk0XCuQcKQsKBHyjx9KFIW1Vlc8dw= +go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= +go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM= +go.opentelemetry.io/otel v1.0.0-RC3 h1:kvwiyEkiUT/JaadXzVLI/R1wDO934A7r3Bs2wEe6wqA= +go.opentelemetry.io/otel v1.0.0-RC3/go.mod h1:Ka5j3ua8tZs4Rkq4Ex3hwgBgOchyPVq5S6P2lz//nKQ= +go.opentelemetry.io/otel/internal/metric v0.22.0/go.mod h1:7qVuMihW/ktMonEfOvBXuh6tfMvvEyoIDgeJNRloYbQ= +go.opentelemetry.io/otel/metric v0.22.0/go.mod h1:KcsUkBiYGW003DJ+ugd2aqIRIfjabD9jeOUXqsAtrq0= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= +go.opentelemetry.io/otel/oteltest v1.0.0-RC2/go.mod h1:kiQ4tw5tAL4JLTbcOYwK1CWI1HkT5aiLzHovgOVnz/A= +go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw= +go.opentelemetry.io/otel/sdk v1.0.0-RC3/go.mod h1:78H6hyg2fka0NYT9fqGuFLvly2yCxiBXDJAgLKo/2Us= +go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= +go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4= +go.opentelemetry.io/otel/trace v1.0.0-RC3 h1:9F0ayEvlxv8BmNmPbU005WK7hC+7KbOazCPZjNa1yME= +go.opentelemetry.io/otel/trace v1.0.0-RC3/go.mod h1:VUt2TUYd8S2/ZRX09ZDFZQwn2RqfMB5MzO17jBojGxo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-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-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190312151545-0bb0c0a6e846/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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/processor/sourceprocessor/observability/empty_test.go b/processor/sourceprocessor/observability/empty_test.go new file mode 100644 index 000000000000..653b1a205f81 --- /dev/null +++ b/processor/sourceprocessor/observability/empty_test.go @@ -0,0 +1,15 @@ +// Copyright 2019 OpenTelemetry 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 observability diff --git a/processor/sourceprocessor/observability/observability.go b/processor/sourceprocessor/observability/observability.go new file mode 100644 index 000000000000..95f3cf0f6079 --- /dev/null +++ b/processor/sourceprocessor/observability/observability.go @@ -0,0 +1,88 @@ +// Copyright 2019 Omnition 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 observability + +import ( + "context" + "fmt" + "os" + + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" +) + +func init() { + err := view.Register( + viewResourceSpansProcessed, + viewRecordsFilteredOut, + viewRecordsFilteredIn, + ) + if err != nil { + fmt.Printf("Error registering source processor's views: %v\n", err) + os.Exit(1) + } +} + +var ( + mResouceSpansProcessed = stats.Int64("otelsvc/sumo/resource_spans_processed", "Number of record span packages processed", "1") + mRecordsFilteredOut = stats.Int64("otelsvc/sumo/records_filtered_out", "Number of records filtered out", "1") + mRecordsFilteredIn = stats.Int64("otelsvc/sumo/records_filtered_in", "Number of records filtered in", "1") +) + +var viewResourceSpansProcessed = &view.View{ + Name: mResouceSpansProcessed.Name(), + Description: mResouceSpansProcessed.Description(), + Measure: mResouceSpansProcessed, + Aggregation: view.Sum(), +} + +var viewRecordsFilteredOut = &view.View{ + Name: mRecordsFilteredOut.Name(), + Description: mRecordsFilteredOut.Description(), + Measure: mRecordsFilteredOut, + Aggregation: view.Sum(), +} + +var viewRecordsFilteredIn = &view.View{ + Name: mRecordsFilteredIn.Name(), + Description: mRecordsFilteredIn.Description(), + Measure: mRecordsFilteredIn, + Aggregation: view.Sum(), +} + +// RecordResourceSpansProcessed increments the metric that resource spans package was processed +func RecordResourceSpansProcessed() { + stats.Record(context.Background(), mResouceSpansProcessed.M(int64(1))) +} + +// RecordFilteredOut increments the metric that records record filtered out +func RecordFilteredOut() { + stats.Record(context.Background(), mRecordsFilteredOut.M(int64(1))) +} + +// RecordFilteredOutN increments the metric that records record filtered out +func RecordFilteredOutN(n int) { + stats.Record(context.Background(), mRecordsFilteredOut.M(int64(n))) +} + +// RecordFilteredIn increments the metric that records record filtered in +func RecordFilteredIn() { + stats.Record(context.Background(), mRecordsFilteredIn.M(int64(1))) +} + +// RecordFilteredInN increments the metric that records record filtered in +func RecordFilteredInN(n int) { + stats.Record(context.Background(), mRecordsFilteredIn.M(int64(n))) +} diff --git a/processor/sourceprocessor/source_processor.go b/processor/sourceprocessor/source_processor.go new file mode 100644 index 000000000000..4c8a5565d26a --- /dev/null +++ b/processor/sourceprocessor/source_processor.go @@ -0,0 +1,344 @@ +// Copyright 2019 OpenTelemetry 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 sourceprocessor + +import ( + "context" + "log" + "regexp" + "strings" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/model/pdata" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/sourceprocessor/observability" +) + +type sourceKeys struct { + annotationPrefix string + containerKey string + namespaceKey string + podKey string + podIDKey string + podNameKey string + podTemplateHashKey string + sourceHostKey string +} + +func (stk sourceKeys) convertKey(key string) string { + switch key { + case "container": + return stk.containerKey + case "namespace": + return stk.namespaceKey + case "pod": + return stk.podKey + case "pod_id": + return stk.podIDKey + case "pod_name": + return stk.podNameKey + case "source_host": + return stk.sourceHostKey + default: + return key + } +} + +type sourceProcessor struct { + collector string + source string + sourceCategoryFiller attributeFiller + sourceNameFiller attributeFiller + sourceHostFiller attributeFiller + + systemdFiltering bool + exclude map[string]*regexp.Regexp + keys sourceKeys +} + +const ( + alphanums = "bcdfghjklmnpqrstvwxz2456789" + + sourceHostSpecialAnnotation = "sumologic.com/sourceHost" + sourceNameSpecialAnnotation = "sumologic.com/sourceName" + sourceCategorySpecialAnnotation = "sumologic.com/sourceCategory" + + includeAnnotation = "sumologic.com/include" + excludeAnnotation = "sumologic.com/exclude" + + collectorKey = "_collector" + sourceCategoryKey = "_sourceCategory" + sourceHostKey = "_sourceHost" + sourceNameKey = "_sourceName" +) + +func compileRegex(regex string) *regexp.Regexp { + if regex == "" { + return nil + } + + re, err := regexp.Compile(regex) + if err != nil { + log.Fatalf("Cannot compile regular expression: %s Error: %v\n", regex, err) + } + + return re +} + +func newSourceProcessor(cfg *Config) *sourceProcessor { + keys := sourceKeys{ + annotationPrefix: cfg.AnnotationPrefix, + containerKey: cfg.ContainerKey, + namespaceKey: cfg.NamespaceKey, + podIDKey: cfg.PodIDKey, + podKey: cfg.PodKey, + podNameKey: cfg.PodNameKey, + podTemplateHashKey: cfg.PodTemplateHashKey, + sourceHostKey: cfg.SourceHostKey, + } + + var ( + exclude = make(map[string]*regexp.Regexp) + systemdFiltering bool + ) + for field, regexStr := range cfg.Exclude { + if field == "_SYSTEMD_UNIT" { + systemdFiltering = true + } + if r := compileRegex(regexStr); r != nil { + exclude[field] = r + } + } + + return &sourceProcessor{ + collector: cfg.Collector, + keys: keys, + source: cfg.Source, + sourceHostFiller: createSourceHostFiller(), + sourceCategoryFiller: createSourceCategoryFiller(cfg, keys), + sourceNameFiller: createSourceNameFiller(cfg, keys), + exclude: exclude, + systemdFiltering: systemdFiltering, + } +} + +func (sp *sourceProcessor) fillOtherMeta(atts pdata.AttributeMap) { + if sp.collector != "" { + atts.UpsertString(collectorKey, sp.collector) + } +} + +func (sp *sourceProcessor) isFilteredOut(atts pdata.AttributeMap) bool { + // TODO: This is quite inefficient when done for each package (ore even more so, span) separately. + // It should be moved to K8S Meta Processor and done once per new pod/changed pod + + if value, found := atts.Get(sp.annotationAttribute(excludeAnnotation)); found { + if value.Type() == pdata.AttributeValueTypeString && value.StringVal() == "true" { + return true + } else if value.Type() == pdata.AttributeValueTypeBool && value.BoolVal() { + return true + } + } + + if value, found := atts.Get(sp.annotationAttribute(includeAnnotation)); found { + if value.Type() == pdata.AttributeValueTypeString && value.StringVal() == "true" { + return false + } else if value.Type() == pdata.AttributeValueTypeBool && value.BoolVal() { + return false + } + } + + // Check fields by matching them against field exclusion regexes + for field, r := range sp.exclude { + v, ok := matchFieldByRegex(atts, field, r) + if ok { + // If we're filtering/processing systemd entries then set the hostname + // based on the _HOSTNAME attribute coming from systemd. + if sp.systemdFiltering && field == "_HOSTNAME" && v != "" { + atts.UpsertString("host", v) + } + + return true + } + } + + return false +} + +func (sp *sourceProcessor) annotationAttribute(annotationKey string) string { + return sp.keys.annotationPrefix + annotationKey +} + +// ProcessTraces processes traces +func (sp *sourceProcessor) ProcessTraces(ctx context.Context, td pdata.Traces) (pdata.Traces, error) { + rss := td.ResourceSpans() + + for i := 0; i < rss.Len(); i++ { + observability.RecordResourceSpansProcessed() + + rs := rss.At(i) + res := sp.processResource(rs.Resource()) + atts := res.Attributes() + + ilss := rs.InstrumentationLibrarySpans() + totalSpans := 0 + for j := 0; j < ilss.Len(); j++ { + ils := ilss.At(j) + totalSpans += ils.Spans().Len() + } + + if sp.isFilteredOut(atts) { + rs.InstrumentationLibrarySpans().RemoveIf(func(pdata.InstrumentationLibrarySpans) bool { return true }) + observability.RecordFilteredOutN(totalSpans) + } else { + observability.RecordFilteredInN(totalSpans) + } + } + + return td, nil +} + +// ProcessMetrics processes metrics +func (sp *sourceProcessor) ProcessMetrics(ctx context.Context, md pdata.Metrics) (pdata.Metrics, error) { + rss := md.ResourceMetrics() + + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + res := sp.processResource(rs.Resource()) + atts := res.Attributes() + + if sp.isFilteredOut(atts) { + rs.InstrumentationLibraryMetrics().RemoveIf(func(pdata.InstrumentationLibraryMetrics) bool { return true }) + } + } + + return md, nil +} + +// ProcessLogs processes logs +func (sp *sourceProcessor) ProcessLogs(ctx context.Context, md pdata.Logs) (pdata.Logs, error) { + rss := md.ResourceLogs() + + for i := 0; i < rss.Len(); i++ { + rs := rss.At(i) + res := sp.processResource(rs.Resource()) + atts := res.Attributes() + + if sp.isFilteredOut(atts) { + rs.InstrumentationLibraryLogs().RemoveIf(func(pdata.InstrumentationLibraryLogs) bool { return true }) + } + } + + return md, nil +} + +// processResource performs multiple actions on resource: +// - enrich pod name, so it can be used in templates +// - fills source attributes based on config or annotations +// - set metadata (collector name) +func (sp *sourceProcessor) processResource(res pdata.Resource) pdata.Resource { + atts := res.Attributes() + + sp.enrichPodName(&atts) + sp.fillOtherMeta(atts) + + sp.sourceHostFiller.fillResourceOrUseAnnotation(&atts, + sp.annotationAttribute(sourceHostSpecialAnnotation), + sp.keys, + ) + sp.sourceCategoryFiller.fillResourceOrUseAnnotation(&atts, + sp.annotationAttribute(sourceCategorySpecialAnnotation), + sp.keys, + ) + sp.sourceNameFiller.fillResourceOrUseAnnotation(&atts, + sp.annotationAttribute(sourceNameSpecialAnnotation), + sp.keys, + ) + + return res +} + +// Start is invoked during service startup. +func (*sourceProcessor) Start(_context context.Context, _host component.Host) error { + return nil +} + +// Shutdown is invoked during service shutdown. +func (*sourceProcessor) Shutdown(_context context.Context) error { + return nil +} + +// Convert the pod_template_hash to an alphanumeric string using the same logic Kubernetes +// uses at https://github.com/kubernetes/apimachinery/blob/18a5ff3097b4b189511742e39151a153ee16988b/pkg/util/rand/rand.go#L119 +func SafeEncodeString(s string) string { + r := make([]byte, len(s)) + for i, b := range []rune(s) { + r[i] = alphanums[(int(b) % len(alphanums))] + } + return string(r) +} + +func (sp *sourceProcessor) enrichPodName(atts *pdata.AttributeMap) { + // This replicates sanitize_pod_name function + // Strip out dynamic bits from pod name. + // NOTE: Kubernetes deployments append a template hash. + // At the moment this can be in 3 different forms: + // 1) pre-1.8: numeric in pod_template_hash and pod_parts[-2] + // 2) 1.8-1.11: numeric in pod_template_hash, hash in pod_parts[-2] + // 3) post-1.11: hash in pod_template_hash and pod_parts[-2] + + if atts == nil { + return + } + pod, found := atts.Get(sp.keys.podKey) + if !found { + return + } + + podParts := strings.Split(pod.StringVal(), "-") + if len(podParts) < 2 { + // This is unexpected, fallback + return + } + + podTemplateHashAttr, found := atts.Get(sp.keys.podTemplateHashKey) + + if found && len(podParts) > 2 { + podTemplateHash := podTemplateHashAttr.StringVal() + if podTemplateHash == podParts[len(podParts)-2] || SafeEncodeString(podTemplateHash) == podParts[len(podParts)-2] { + atts.UpsertString(sp.keys.podNameKey, strings.Join(podParts[:len(podParts)-2], "-")) + return + } + } + atts.UpsertString(sp.keys.podNameKey, strings.Join(podParts[:len(podParts)-1], "-")) +} + +// matchFieldByRegex searches the provided attribute map for a particular field +// and matches is with the provided regex. +// It returns the string value of found elements and a boolean flag whether the +// value matched the provided regex. +func matchFieldByRegex(atts pdata.AttributeMap, field string, r *regexp.Regexp) (string, bool) { + att, ok := atts.Get(field) + if !ok { + return "", false + } + + if att.Type() != pdata.AttributeValueTypeString { + return "", false + } + + v := att.StringVal() + return v, r.MatchString(v) +} diff --git a/processor/sourceprocessor/source_processor_test.go b/processor/sourceprocessor/source_processor_test.go new file mode 100644 index 000000000000..8227975f6499 --- /dev/null +++ b/processor/sourceprocessor/source_processor_test.go @@ -0,0 +1,326 @@ +// Copyright 2019 OpenTelemetry 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 sourceprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/model/pdata" +) + +func createConfig() *Config { + factory := NewFactory() + config := factory.CreateDefaultConfig().(*Config) + config.Collector = "foocollector" + config.SourceCategoryPrefix = "prefix/" + config.SourceCategoryReplaceDash = "#" + return config +} + +var ( + cfg = createConfig() + + k8sLabels = map[string]string{ + "namespace": "namespace-1", + "pod_id": "pod-1234", + "pod": "pod-5db86d8867-sdqlj", + "pod_labels_pod-template-hash": "5db86d8867", + "container": "container-1", + } + + mergedK8sLabels = map[string]string{ + "container": "container-1", + "namespace": "namespace-1", + "pod_id": "pod-1234", + "pod": "pod-5db86d8867-sdqlj", + "pod_name": "pod", + "pod_labels_pod-template-hash": "5db86d8867", + "_sourceName": "namespace-1.pod-5db86d8867-sdqlj.container-1", + "_sourceCategory": "prefix/namespace#1/pod", + } + + mergedK8sLabelsWithMeta = map[string]string{ + "container": "container-1", + "namespace": "namespace-1", + "pod_id": "pod-1234", + "pod": "pod-5db86d8867-sdqlj", + "pod_name": "pod", + "pod_labels_pod-template-hash": "5db86d8867", + "_collector": "foocollector", + "_sourceName": "namespace-1.pod-5db86d8867-sdqlj.container-1", + "_sourceCategory": "prefix/namespace#1/pod", + } + + k8sNewLabels = map[string]string{ + "k8s.namespace.name": "namespace-1", + "k8s.pod.id": "pod-1234", + "k8s.pod.name": "pod-5db86d8867-sdqlj", + "k8s.pod.labels.pod-template-hash": "5db86d8867", + "k8s.container.name": "container-1", + } + + mergedK8sNewLabelsWithMeta = map[string]string{ + "k8s.namespace.name": "namespace-1", + "k8s.pod.id": "pod-1234", + "k8s.pod.name": "pod-5db86d8867-sdqlj", + "k8s.pod.labels.pod-template-hash": "5db86d8867", + "k8s.container.name": "container-1", + "k8s.pod.pod_name": "pod", + "_sourceName": "namespace-1.pod-5db86d8867-sdqlj.container-1", + "_sourceCategory": "prefix/namespace#1/pod", + "_collector": "foocollector", + } + + limitedLabels = map[string]string{ + "pod_id": "pod-1234", + } + + limitedLabelsWithMeta = map[string]string{ + "pod_id": "pod-1234", + "_collector": "foocollector", + } +) + +func newTraceData(labels map[string]string) pdata.Traces { + td := pdata.NewTraces() + rs := td.ResourceSpans().AppendEmpty() + attrs := rs.Resource().Attributes() + for k, v := range labels { + attrs.UpsertString(k, v) + } + return td +} + +func newTraceDataWithSpans(_resourceLabels map[string]string, _spanLabels map[string]string) pdata.Traces { + // This will be very small attribute set, the actual data will be at span level + td := newTraceData(_resourceLabels) + ils := td.ResourceSpans().At(0).InstrumentationLibrarySpans().AppendEmpty() + span := ils.Spans().AppendEmpty() + span.SetName("foo") + spanAttrs := span.Attributes() + for k, v := range _spanLabels { + spanAttrs.UpsertString(k, v) + } + return td +} + +func prepareAttributesForAssert(t pdata.Traces) { + for i := 0; i < t.ResourceSpans().Len(); i++ { + rss := t.ResourceSpans().At(i) + rss.Resource().Attributes().Sort() + for j := 0; j < rss.InstrumentationLibrarySpans().Len(); j++ { + ss := rss.InstrumentationLibrarySpans().At(j).Spans() + for k := 0; k < ss.Len(); k++ { + ss.At(k).Attributes().Sort() + } + } + } +} + +func assertTracesEqual(t *testing.T, t1 pdata.Traces, t2 pdata.Traces) { + prepareAttributesForAssert(t1) + prepareAttributesForAssert(t2) + assert.Equal(t, t1, t2) +} + +func assertSpansEqual(t *testing.T, t1 pdata.Traces, t2 pdata.Traces) { + prepareAttributesForAssert(t1) + prepareAttributesForAssert(t2) + assert.Equal(t, t1.ResourceSpans().Len(), t2.ResourceSpans().Len()) + for i := 0; i < t1.ResourceSpans().Len(); i++ { + rss1 := t1.ResourceSpans().At(i) + rss2 := t2.ResourceSpans().At(i) + assert.Equal(t, rss1.InstrumentationLibrarySpans(), rss2.InstrumentationLibrarySpans()) + } +} + +func TestTraceSourceProcessor(t *testing.T) { + want := newTraceData(mergedK8sLabelsWithMeta) + test := newTraceData(k8sLabels) + + rtp := newSourceProcessor(cfg) + + td, err := rtp.ProcessTraces(context.Background(), test) + assert.NoError(t, err) + + assertTracesEqual(t, td, want) +} + +func TestTraceSourceProcessorNewTaxonomy(t *testing.T) { + want := newTraceData(mergedK8sNewLabelsWithMeta) + test := newTraceData(k8sNewLabels) + + config := createConfig() + config.NamespaceKey = "k8s.namespace.name" + config.PodIDKey = "k8s.pod.id" + config.PodNameKey = "k8s.pod.pod_name" + config.PodKey = "k8s.pod.name" + config.PodTemplateHashKey = "k8s.pod.labels.pod-template-hash" + config.ContainerKey = "k8s.container.name" + + rtp := newSourceProcessor(config) + + td, err := rtp.ProcessTraces(context.Background(), test) + assert.NoError(t, err) + + assertTracesEqual(t, td, want) +} + +func TestTraceSourceProcessorEmpty(t *testing.T) { + want := newTraceData(limitedLabelsWithMeta) + test := newTraceData(limitedLabels) + + rtp := newSourceProcessor(cfg) + + td, err := rtp.ProcessTraces(context.Background(), test) + assert.NoError(t, err) + assertTracesEqual(t, td, want) +} + +func TestTraceSourceFilteringOutByRegex(t *testing.T) { + testcases := []struct { + name string + cfg *Config + want pdata.Traces + }{ + { + name: "pod exclude regex", + cfg: func() *Config { + cfg := createConfig() + cfg.Exclude = map[string]string{ + "pod": ".*", + } + return cfg + }(), + want: func() pdata.Traces { + want := newTraceDataWithSpans(mergedK8sLabelsWithMeta, k8sLabels) + want.ResourceSpans().At(0).InstrumentationLibrarySpans(). + RemoveIf(func(pdata.InstrumentationLibrarySpans) bool { return true }) + return want + }(), + }, + { + name: "container exclude regex", + cfg: func() *Config { + cfg := createConfig() + cfg.Exclude = map[string]string{ + "container": ".*", + } + return cfg + }(), + want: func() pdata.Traces { + want := newTraceDataWithSpans(mergedK8sLabelsWithMeta, k8sLabels) + want.ResourceSpans().At(0).InstrumentationLibrarySpans(). + RemoveIf(func(pdata.InstrumentationLibrarySpans) bool { return true }) + return want + }(), + }, + { + name: "namespace exclude regex", + cfg: func() *Config { + cfg := createConfig() + cfg.Exclude = map[string]string{ + "namespace": ".*", + } + return cfg + }(), + want: func() pdata.Traces { + want := newTraceDataWithSpans(mergedK8sLabelsWithMeta, k8sLabels) + want.ResourceSpans().At(0).InstrumentationLibrarySpans(). + RemoveIf(func(pdata.InstrumentationLibrarySpans) bool { return true }) + return want + }(), + }, + { + name: "no exclude regex", + cfg: func() *Config { + return createConfig() + }(), + want: func() pdata.Traces { + return newTraceDataWithSpans(mergedK8sLabelsWithMeta, k8sLabels) + }(), + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + test := newTraceDataWithSpans(mergedK8sLabels, k8sLabels) + + rtp := newSourceProcessor(tc.cfg) + + td, err := rtp.ProcessTraces(context.Background(), test) + assert.NoError(t, err) + + assertTracesEqual(t, td, tc.want) + }) + } +} + +func TestTraceSourceFilteringOutByExclude(t *testing.T) { + test := newTraceDataWithSpans(k8sLabels, k8sLabels) + test.ResourceSpans().At(0).Resource().Attributes(). + UpsertString("pod_annotation_sumologic.com/exclude", "true") + + want := newTraceDataWithSpans(limitedLabelsWithMeta, mergedK8sLabels) + want.ResourceSpans().At(0).InstrumentationLibrarySpans(). + RemoveIf(func(pdata.InstrumentationLibrarySpans) bool { return true }) + + rtp := newSourceProcessor(cfg) + + td, err := rtp.ProcessTraces(context.Background(), test) + assert.NoError(t, err) + + assertSpansEqual(t, td, want) +} + +func TestTraceSourceIncludePrecedence(t *testing.T) { + test := newTraceDataWithSpans(limitedLabels, k8sLabels) + test.ResourceSpans().At(0).Resource().Attributes().UpsertString("pod_annotation_sumologic.com/include", "true") + + want := newTraceDataWithSpans(limitedLabelsWithMeta, k8sLabels) + want.ResourceSpans().At(0).Resource().Attributes().UpsertString("pod_annotation_sumologic.com/include", "true") + + cfg1 := createConfig() + cfg1.Exclude = map[string]string{ + "pod": ".*", + } + rtp := newSourceProcessor(cfg) + + td, err := rtp.ProcessTraces(context.Background(), test) + assert.NoError(t, err) + + assertTracesEqual(t, td, want) +} + +func TestTraceSourceProcessorAnnotations(t *testing.T) { + k8sLabels["pod_annotation_sumologic.com/sourceHost"] = "sh:%{pod_id}" + k8sLabels["pod_annotation_sumologic.com/sourceCategory"] = "sc:%{pod_id}" + test := newTraceData(k8sLabels) + + mergedK8sLabelsWithMeta["pod_annotation_sumologic.com/sourceHost"] = "sh:%{pod_id}" + mergedK8sLabelsWithMeta["pod_annotation_sumologic.com/sourceCategory"] = "sc:%{pod_id}" + mergedK8sLabelsWithMeta["_sourceHost"] = "sh:pod-1234" + mergedK8sLabelsWithMeta["_sourceCategory"] = "prefix/sc:pod#1234" + want := newTraceData(mergedK8sLabelsWithMeta) + + rtp := newSourceProcessor(cfg) + + td, err := rtp.ProcessTraces(context.Background(), test) + assert.NoError(t, err) + + assertTracesEqual(t, td, want) +} diff --git a/processor/sourceprocessor/testdata/config.yaml b/processor/sourceprocessor/testdata/config.yaml new file mode 100644 index 000000000000..470e4af74487 --- /dev/null +++ b/processor/sourceprocessor/testdata/config.yaml @@ -0,0 +1,39 @@ +receivers: + nop: + +processors: + # The following specifies an empty source - it will have no effect on trace or metrics data. + source: + # The following specifies a non-trivial source + source/2: + collector: "somecollector" + source: "tracesource" + source_name: "%{namespace}.%{pod}.%{container}/foo" + source_category: "%{namespace}/%{pod_name}/bar" + source_category_prefix: "kubernetes/" + source_category_replace_dash: "/" + exclude: + namespace: "excluded_namespace_regex" + pod: "excluded_pod_regex" + container: "excluded_container_regex" + host: "excluded_host_regex" + _SYSTEMD_UNIT: "excluded_systemd_unit_regex" + + annotation_prefix: "pod_annotation_" + pod_template_hash_key: "pod_labels_pod-template-hash" + pod_name_key: "pod_name" + namespace_key: "namespace" + pod_key: "pod" + container_key: "container" + source_host_key: "source_host" + +exporters: + nop: + +service: + pipelines: + traces: + receivers: [nop] + processors: [source/2] + exporters: [nop] + diff --git a/processor/sumologicsyslogprocessor/Makefile b/processor/sumologicsyslogprocessor/Makefile new file mode 100644 index 000000000000..c1496226e590 --- /dev/null +++ b/processor/sumologicsyslogprocessor/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common \ No newline at end of file diff --git a/processor/sumologicsyslogprocessor/README.md b/processor/sumologicsyslogprocessor/README.md new file mode 100644 index 000000000000..76cc06c5f79f --- /dev/null +++ b/processor/sumologicsyslogprocessor/README.md @@ -0,0 +1,30 @@ +# Sumo Logic Syslog Processor + +Supported pipeline types: logs + +The Sumo Logic Syslog processor can be used to create attribute with facility name +based on facility code. Default facility name is `syslog`. + +## Configuration + +| Field | Default | Description | +|---------------|----------|--------------------------------------------------------------------| +| facility_attr | facility | The attribute name in which a facility name is going to be written | + +## Examples + +Following table shows example facility names which are extracted from log line + +| log | facility | +|---------------------------|---------------------| +| <13> Example log | user-level messages | +| <334> Another example log | syslog | +| Plain text log | syslog | + +## Configuration Example + +```yaml +processors: + sumologic_syslog: + facility_attr: testAttrName +``` diff --git a/processor/sumologicsyslogprocessor/config.go b/processor/sumologicsyslogprocessor/config.go new file mode 100644 index 000000000000..72980a08dea5 --- /dev/null +++ b/processor/sumologicsyslogprocessor/config.go @@ -0,0 +1,31 @@ +// Copyright The OpenTelemetry 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 sumologicsyslogprocessor + +import ( + "go.opentelemetry.io/collector/config" +) + +// Config holds the configuration for tail-based sampling. +type Config struct { + config.ProcessorSettings `mapstructure:"-"` + + // FacilityAttr is the name of the attribute the facility name should be placed into. + FacilityAttr string `mapstructure:"facility_attr"` +} + +const ( + defaultFacilityAttr = "facility" +) diff --git a/processor/sumologicsyslogprocessor/config_test.go b/processor/sumologicsyslogprocessor/config_test.go new file mode 100644 index 000000000000..960732f2a0d3 --- /dev/null +++ b/processor/sumologicsyslogprocessor/config_test.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry 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 sumologicsyslogprocessor + +import ( + "path" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/config/configtest" +) + +func TestLoadConfig(t *testing.T) { + factories, err := componenttest.NopFactories() + require.NoError(t, err) + + factory := NewFactory() + factories.Processors[factory.Type()] = factory + + cfg, err := configtest.LoadConfig(path.Join(".", "testdata", "sumologic_syslog_config.yaml"), factories) + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, cfg.Processors[config.NewID("sumologic_syslog")], + &Config{ + ProcessorSettings: config.NewProcessorSettings(config.NewID("sumologic_syslog")), + FacilityAttr: "testAttrName", + }) +} diff --git a/processor/sumologicsyslogprocessor/factory.go b/processor/sumologicsyslogprocessor/factory.go new file mode 100644 index 000000000000..2a5ffdec13ff --- /dev/null +++ b/processor/sumologicsyslogprocessor/factory.go @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry 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 sumologicsyslogprocessor + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/processor/processorhelper" +) + +const ( + // The value of "type" Tail Sampling in configuration. + typeStr = "sumologic_syslog" +) + +var processorCapabilities = consumer.Capabilities{MutatesData: true} + +// NewFactory returns a new factory for the Tail Sampling processor. +func NewFactory() component.ProcessorFactory { + return processorhelper.NewFactory( + typeStr, + createDefaultConfig, + processorhelper.WithLogs(createLogProcessor)) +} + +func createDefaultConfig() config.Processor { + return &Config{ + ProcessorSettings: config.NewProcessorSettings(config.NewID(typeStr)), + FacilityAttr: defaultFacilityAttr, + } +} + +func createLogProcessor( + _ context.Context, + params component.ProcessorCreateSettings, + cfg config.Processor, + nextConsumer consumer.Logs, +) (component.LogsProcessor, error) { + tCfg := cfg.(*Config) + + ssp, err := newSumologicSyslogProcessor(tCfg) + if err != nil { + return nil, err + } + + return processorhelper.NewLogsProcessor( + cfg, + nextConsumer, + ssp.ProcessLogs, + processorhelper.WithCapabilities(processorCapabilities)) +} diff --git a/processor/sumologicsyslogprocessor/factory_test.go b/processor/sumologicsyslogprocessor/factory_test.go new file mode 100644 index 000000000000..7dfc993c0e67 --- /dev/null +++ b/processor/sumologicsyslogprocessor/factory_test.go @@ -0,0 +1,44 @@ +// Copyright The OpenTelemetry 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 sumologicsyslogprocessor + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configcheck" + "go.opentelemetry.io/collector/consumer/consumertest" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, configcheck.ValidateConfig(cfg)) +} + +func TestLogProcessor(t *testing.T) { + factory := NewFactory() + + cfg := factory.CreateDefaultConfig().(*Config) + // Manually set required fields + cfg.FacilityAttr = "testAttrName" + + params := component.ProcessorCreateSettings{} + lp, err := factory.CreateLogsProcessor(context.Background(), params, cfg, consumertest.NewNop()) + assert.NotNil(t, lp) + assert.NoError(t, err, "cannot create log processor") +} diff --git a/processor/sumologicsyslogprocessor/go.mod b/processor/sumologicsyslogprocessor/go.mod new file mode 100644 index 000000000000..c561f47a2750 --- /dev/null +++ b/processor/sumologicsyslogprocessor/go.mod @@ -0,0 +1,10 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/processor/sumologicsyslogprocessor + +go 1.16 + +require ( + github.com/stretchr/testify v1.7.0 + go.opentelemetry.io/collector v0.35.0 + go.opentelemetry.io/collector/model v0.35.0 + go.uber.org/zap v1.19.0 +) diff --git a/processor/sumologicsyslogprocessor/go.sum b/processor/sumologicsyslogprocessor/go.sum new file mode 100644 index 000000000000..e4f43e22ab6b --- /dev/null +++ b/processor/sumologicsyslogprocessor/go.sum @@ -0,0 +1,803 @@ +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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/prometheus v0.4.0/go.mod h1:o7cosnyfuPVK0tB8q0QmaQNhGnptITnPQB+z1+qeFB0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= +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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +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-cleanhttp v0.5.1/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.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +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.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= +github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/koanf v1.2.2 h1:CydaDM/2mtza/ytVVnj4iQSVPjDq+pSV2vWMFDiQS08= +github.com/knadh/koanf v1.2.2/go.mod h1:xpPTwMhsA/aaQLAilyCCqfpEiY1gpa160AiCuWHJUjY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +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/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface 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/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3MLCHmSHelCh9hSGYNLTQ= +github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= +github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v3.21.8+incompatible h1:sh0foI8tMRlCidUJR+KzqWYWxrkuuPIGiO6Vp+KXdCU= +github.com/shirou/gopsutil v3.21.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/collector v0.35.0 h1:e7kutiBxbyk9Gc0DkFDYxnWrmW9sAPpXbgFoQmof/kE= +go.opentelemetry.io/collector v0.35.0/go.mod h1:mMW4VJHhy8CwoTenV75axteKd0aZhWHDZjcZ59V53kc= +go.opentelemetry.io/collector/model v0.35.0 h1:NpKjghiqlei4ecwjOYOMhD6tj4gY8yiWHPJmbFs/ArI= +go.opentelemetry.io/collector/model v0.35.0/go.mod h1:+7YCSjJG+MqiIFjauzt7oM2qkqBsaJWh5hcsO4fwsAc= +go.opentelemetry.io/contrib v0.22.0 h1:0F7gDEjgb1WGn4ODIjaCAg75hmqF+UN0LiVgwxsCodc= +go.opentelemetry.io/contrib v0.22.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0 h1:TjqELdtCtlOJQrTnXd2y+RP6wXKZUnnJer0HR0CSo18= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.22.0/go.mod h1:KjqwX4uJNaj479ZjFpADOMJKOM4rBXq4kN7nbeuGKrY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.22.0 h1:WHjZguqT+3UjTgFum33hWZYybDVnx8u9q5/kQDfaGTs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.22.0/go.mod h1:o3MuU25bYroYnc2TOKe8mTk8f9X1oPFO6C5RCoPKtSU= +go.opentelemetry.io/contrib/zpages v0.22.0/go.mod h1:pO7VUk5qoCiekzXk0XCuQcKQsKBHyjx9KFIW1Vlc8dw= +go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= +go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM= +go.opentelemetry.io/otel v1.0.0-RC3 h1:kvwiyEkiUT/JaadXzVLI/R1wDO934A7r3Bs2wEe6wqA= +go.opentelemetry.io/otel v1.0.0-RC3/go.mod h1:Ka5j3ua8tZs4Rkq4Ex3hwgBgOchyPVq5S6P2lz//nKQ= +go.opentelemetry.io/otel/internal/metric v0.22.0 h1:Q9bS02XRykSRIbggaU4hVF9oWOP9PyILu26zJWoKmk0= +go.opentelemetry.io/otel/internal/metric v0.22.0/go.mod h1:7qVuMihW/ktMonEfOvBXuh6tfMvvEyoIDgeJNRloYbQ= +go.opentelemetry.io/otel/metric v0.22.0 h1:/qv10BzznqEifrXBwsTT370OCN1PRgt+mnjzMwxJKrQ= +go.opentelemetry.io/otel/metric v0.22.0/go.mod h1:KcsUkBiYGW003DJ+ugd2aqIRIfjabD9jeOUXqsAtrq0= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= +go.opentelemetry.io/otel/oteltest v1.0.0-RC2/go.mod h1:kiQ4tw5tAL4JLTbcOYwK1CWI1HkT5aiLzHovgOVnz/A= +go.opentelemetry.io/otel/sdk v1.0.0-RC2/go.mod h1:fgwHyiDn4e5k40TD9VX243rOxXR+jzsWBZYA2P5jpEw= +go.opentelemetry.io/otel/sdk v1.0.0-RC3/go.mod h1:78H6hyg2fka0NYT9fqGuFLvly2yCxiBXDJAgLKo/2Us= +go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= +go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4= +go.opentelemetry.io/otel/trace v1.0.0-RC3 h1:9F0ayEvlxv8BmNmPbU005WK7hC+7KbOazCPZjNa1yME= +go.opentelemetry.io/otel/trace v1.0.0-RC3/go.mod h1:VUt2TUYd8S2/ZRX09ZDFZQwn2RqfMB5MzO17jBojGxo= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +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/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-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-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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20190312151545-0bb0c0a6e846/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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/processor/sumologicsyslogprocessor/processor.go b/processor/sumologicsyslogprocessor/processor.go new file mode 100644 index 000000000000..d2d5aa8f0e6a --- /dev/null +++ b/processor/sumologicsyslogprocessor/processor.go @@ -0,0 +1,121 @@ +// Copyright The OpenTelemetry 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 sumologicsyslogprocessor + +import ( + "context" + "fmt" + "regexp" + "strconv" + + "go.opentelemetry.io/collector/model/pdata" +) + +// tailSamplingSpanProcessor handles the incoming trace data and uses the given sampling +// policy to sample traces. +type sumologicSyslogProcessor struct { + syslogFacilityAttrName string + syslogFacilityRegex *regexp.Regexp +} + +const ( + syslogSource = "syslog" + facilityRegexp = `^<(?P\d+)>` +) + +var ( + facilities = map[int]string{ + 0: "kernel messages", + 1: "user-level messages", + 2: "mail system", + 3: "system daemons", + 4: "security/authorization messages", + 5: "messages generated internally by syslogd", + 6: "line printer subsystem", + 7: "network news subsystem", + 8: "UUCP subsystem", + 9: "clock daemon", + 10: "security/authorization messages", + 11: "FTP daemon", + 12: "NTP subsystem", + 13: "log audit", + 14: "log alert", + 15: "clock daemon", + 16: "local use 0 (local0)", + 17: "local use 1 (local1)", + 18: "local use 2 (local2)", + 19: "local use 3 (local3)", + 20: "local use 4 (local4)", + 21: "local use 5 (local5)", + 22: "local use 6 (local6)", + 23: "local use 7 (local7)", + } +) + +func newSumologicSyslogProcessor(cfg *Config) (*sumologicSyslogProcessor, error) { + r, err := regexp.Compile(facilityRegexp) + if err != nil { + return nil, err + } + + return &sumologicSyslogProcessor{ + syslogFacilityAttrName: cfg.FacilityAttr, + syslogFacilityRegex: r, + }, nil +} + +// ProcessLogs tries to extract facility number from log syslog line and maps it to facility name. +// Facility is taken as $number/8 rounded down, where log looks like `^<$number> .*` +func (ssp *sumologicSyslogProcessor) ProcessLogs(ctx context.Context, ld pdata.Logs) (pdata.Logs, error) { + // Iterate over ResourceLogs + rls := ld.ResourceLogs() + for i := 0; i < rls.Len(); i++ { + rl := rls.At(i) + + ills := rl.InstrumentationLibraryLogs() + // iterate over InstrumentationLibraryLogs + for j := 0; j < ills.Len(); j++ { + ill := ills.At(j) + + // iterate over Logs + logs := ill.Logs() + for k := 0; k < logs.Len(); k++ { + var ( + value string = syslogSource + ok bool + ) + + log := logs.At(k) + match := ssp.syslogFacilityRegex.FindStringSubmatch(log.Body().StringVal()) + + if match != nil { + facility, err := strconv.Atoi(match[1]) + if err != nil { + return ld, fmt.Errorf("failed to parse: %s, err: %w", match[1], err) + } + facility = facility / 8 + + value, ok = facilities[facility] + if !ok { + value = syslogSource + } + } + log.Attributes().UpsertString(ssp.syslogFacilityAttrName, value) + } + } + } + + return ld, nil +} diff --git a/processor/sumologicsyslogprocessor/processor_test.go b/processor/sumologicsyslogprocessor/processor_test.go new file mode 100644 index 000000000000..ba2db8fd78c9 --- /dev/null +++ b/processor/sumologicsyslogprocessor/processor_test.go @@ -0,0 +1,66 @@ +// Copyright The OpenTelemetry 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 sumologicsyslogprocessor + +import ( + "context" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/model/pdata" +) + +func TestProcessLogs(t *testing.T) { + lines := []string{ + `<13> Example log`, + `<334> Another example log`, + `Plain text`, + } + + facilities := []string{ + `user-level messages`, + `syslog`, + `syslog`, + } + + logs := pdata.NewLogs() + rls := logs.ResourceLogs().AppendEmpty() + rls.InstrumentationLibraryLogs().EnsureCapacity(len(lines)) + ills := rls.InstrumentationLibraryLogs().AppendEmpty() + + for _, line := range lines { + lr := ills.Logs().AppendEmpty() + lr.Body().SetStringVal(line) + } + ills.Logs().At(1).Attributes().InsertString("facility_name", "pre filled facility") + + ctx := context.Background() + processor := &sumologicSyslogProcessor{ + syslogFacilityAttrName: "facility_name", + syslogFacilityRegex: regexp.MustCompile(`^<(?P\d+)>`), + } + + result, err := processor.ProcessLogs(ctx, logs) + require.NoError(t, err) + + for i, line := range facilities { + attrs := result.ResourceLogs().At(0).InstrumentationLibraryLogs().At(0).Logs().At(i).Attributes() + attr, ok := attrs.Get("facility_name") + require.True(t, ok) + assert.Equal(t, line, attr.StringVal()) + } +} diff --git a/processor/sumologicsyslogprocessor/testdata/sumologic_syslog_config.yaml b/processor/sumologicsyslogprocessor/testdata/sumologic_syslog_config.yaml new file mode 100644 index 000000000000..186a4f0d26ff --- /dev/null +++ b/processor/sumologicsyslogprocessor/testdata/sumologic_syslog_config.yaml @@ -0,0 +1,16 @@ +receivers: + nop: + +exporters: + nop: + +processors: + sumologic_syslog: + facility_attr: testAttrName + +service: + pipelines: + logs: + receivers: [nop] + processors: [sumologic_syslog] + exporters: [nop] diff --git a/vagrant/build.sh b/vagrant/build.sh new file mode 100755 index 000000000000..a73df3ea99bf --- /dev/null +++ b/vagrant/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +export PATH=/home/vagrant/go/bin:$PATH + +cd /vagrant +make install-tools +make common +make docker-otelcontribcol \ No newline at end of file diff --git a/vagrant/provision.sh b/vagrant/provision.sh new file mode 100755 index 000000000000..44242ff0b981 --- /dev/null +++ b/vagrant/provision.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -x + +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get -y upgrade +apt-get -y install apt-transport-https +apt-get install --yes docker.io docker-compose + +add-apt-repository ppa:longsleep/golang-backports +apt update +apt install -y golang-go + +usermod -aG docker vagrant +systemctl start docker + +su vagrant -c '/vagrant/vagrant/build.sh' \ No newline at end of file