From 583d06f388b89a3556e15ddbd5883c405ba20d0f Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Mon, 27 Feb 2023 16:11:05 -0500 Subject: [PATCH 01/17] Add OpAMP supervisor skeleton --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/feature_request.yaml | 1 + .github/ISSUE_TEMPLATE/other.yaml | 1 + cmd/opampsupervisor/.gitignore | 2 + cmd/opampsupervisor/Makefile | 1 + cmd/opampsupervisor/README.md | 24 + cmd/opampsupervisor/go.mod | 28 + cmd/opampsupervisor/go.sum | 414 +++++++++++ cmd/opampsupervisor/main.go | 43 ++ .../supervisor/commander/commander.go | 192 +++++ .../supervisor/config/config.go | 29 + .../supervisor/healthchecker/healthchecker.go | 54 ++ cmd/opampsupervisor/supervisor/opamplogger.go | 38 + cmd/opampsupervisor/supervisor/supervisor.go | 695 ++++++++++++++++++ .../testdata/supervisor_darwin.yaml | 5 + .../testdata/supervisor_linux.yaml | 5 + .../testdata/supervisor_windows.yaml | 5 + 18 files changed, 1539 insertions(+) create mode 100644 cmd/opampsupervisor/.gitignore create mode 100644 cmd/opampsupervisor/Makefile create mode 100644 cmd/opampsupervisor/README.md create mode 100644 cmd/opampsupervisor/go.mod create mode 100644 cmd/opampsupervisor/go.sum create mode 100644 cmd/opampsupervisor/main.go create mode 100644 cmd/opampsupervisor/supervisor/commander/commander.go create mode 100644 cmd/opampsupervisor/supervisor/config/config.go create mode 100644 cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go create mode 100644 cmd/opampsupervisor/supervisor/opamplogger.go create mode 100644 cmd/opampsupervisor/supervisor/supervisor.go create mode 100644 cmd/opampsupervisor/testdata/supervisor_darwin.yaml create mode 100644 cmd/opampsupervisor/testdata/supervisor_linux.yaml create mode 100644 cmd/opampsupervisor/testdata/supervisor_windows.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e3d9fbc9a183..f37f267c7bdd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -22,6 +22,7 @@ cmd/configschema/ @open-telemetry/collector-contrib-approvers @mx-psi @dmitryax @pmcollins cmd/mdatagen/ @open-telemetry/collector-contrib-approvers @dmitryax +cmd/opampsupervisor/ @open-telemetry/collector-contrib-approvers @evan-bradley @atoulme @tigrannajaryan cmd/otelcontribcol/ @open-telemetry/collector-contrib-approvers cmd/oteltestbedcol/ @open-telemetry/collector-contrib-approvers cmd/telemetrygen/ @open-telemetry/collector-contrib-approvers @mx-psi @codeboten diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 4107accc67e5..68316bc0df41 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -20,6 +20,7 @@ body: # Start Collector components list - cmd/configschema - cmd/mdatagen + - cmd/opampsupervisor - cmd/otelcontribcol - cmd/oteltestbedcol - cmd/telemetrygen diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index 9680ffa19da9..1c9d4773bc55 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -14,6 +14,7 @@ body: # Start Collector components list - cmd/configschema - cmd/mdatagen + - cmd/opampsupervisor - cmd/otelcontribcol - cmd/oteltestbedcol - cmd/telemetrygen diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml index 1746a9f0c376..3c6069627cc0 100644 --- a/.github/ISSUE_TEMPLATE/other.yaml +++ b/.github/ISSUE_TEMPLATE/other.yaml @@ -14,6 +14,7 @@ body: # Start Collector components list - cmd/configschema - cmd/mdatagen + - cmd/opampsupervisor - cmd/otelcontribcol - cmd/oteltestbedcol - cmd/telemetrygen diff --git a/cmd/opampsupervisor/.gitignore b/cmd/opampsupervisor/.gitignore new file mode 100644 index 000000000000..fbaf96e91182 --- /dev/null +++ b/cmd/opampsupervisor/.gitignore @@ -0,0 +1,2 @@ +effective.yaml +agent.log diff --git a/cmd/opampsupervisor/Makefile b/cmd/opampsupervisor/Makefile new file mode 100644 index 000000000000..ded7a36092dc --- /dev/null +++ b/cmd/opampsupervisor/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/cmd/opampsupervisor/README.md b/cmd/opampsupervisor/README.md new file mode 100644 index 000000000000..2c8bdbafaece --- /dev/null +++ b/cmd/opampsupervisor/README.md @@ -0,0 +1,24 @@ +# OpAMP Supervisor for the OpenTelemetry Collector + +This is an implementation of an OpAMP Supervisor that runs a Collector instance using configuration provided from an OpAMP server. This implementation +is following a design specified in this [Google Doc](https://docs.google.com/document/d/1KtH5atZQUs9Achbce6LiOaJxLbksNJenvgvyKLsJrkc/edit). +The design is still undergoing changes, and as such this implementation may change as well. + +## Experimenting with the supervisor + +The supervisor is currently undergoing heavy development and is not ready for any serious use. However, if you would like to test it, you can follow the steps below: + +1. Download the [opamp-go](https://github.com/open-telemetry/opamp-go) repository, and run the OpAMP example server in the `internal/examples/server` directory. +2. From the Collector contrib repository root, build the Collector: + + ```shell + make otelcontribcol + ``` + +3. Run the supervisor, substituting `` for your platform: + + ```shell + go run . --config testdata/supervisor_.yaml + ``` + +4. The supervisor should connect to the OpAMP server and start a Collector instance. \ No newline at end of file diff --git a/cmd/opampsupervisor/go.mod b/cmd/opampsupervisor/go.mod new file mode 100644 index 000000000000..5f2043add89f --- /dev/null +++ b/cmd/opampsupervisor/go.mod @@ -0,0 +1,28 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor + +go 1.19 + +require ( + github.com/cenkalti/backoff/v4 v4.2.0 + github.com/knadh/koanf v1.5.0 + github.com/oklog/ulid/v2 v2.1.0 + github.com/open-telemetry/opamp-go v0.6.0 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.13 + go.uber.org/atomic v1.7.0 + go.uber.org/zap v1.17.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.2 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/cmd/opampsupervisor/go.sum b/cmd/opampsupervisor/go.sum new file mode 100644 index 000000000000..a13b33da6055 --- /dev/null +++ b/cmd/opampsupervisor/go.sum @@ -0,0 +1,414 @@ +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= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +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/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= +github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= +github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= +github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= +github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +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/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +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/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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +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/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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-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/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.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.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.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/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.5.0/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +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-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +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.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-rootcerts v1.0.2/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/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.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +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/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs= +github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +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.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +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.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= +github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs= +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/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.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +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.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/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.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/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/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/open-telemetry/opamp-go v0.6.0 h1:pTnVvIp9FT3qEQ8P+evENhvaQGkF8i6vu73gawBPRLQ= +github.com/open-telemetry/opamp-go v0.6.0/go.mod h1:IMdeuHGVc5CjKSu5/oNV0o+UmiXuahoHvoZ4GOmAI9M= +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/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +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/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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +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.1/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/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/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/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/scaleway/scaleway-sdk-go v1.0.0-beta.13 h1:n5J2K6g/kl/iT6mODjCoSoRBGQVmIG3aMtYbofi9kxc= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.13/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/yuin/goldmark v1.1.27/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.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +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 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +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/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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/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.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-20181114220301-adae6a3d119a/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-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-20190923162816-aa69164e4478/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-20200625001655-4c5254603344/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/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-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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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/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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/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.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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +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.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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/cmd/opampsupervisor/main.go b/cmd/opampsupervisor/main.go new file mode 100644 index 000000000000..697ab8fe1a8b --- /dev/null +++ b/cmd/opampsupervisor/main.go @@ -0,0 +1,43 @@ +// 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 main + +import ( + "flag" + "os" + "os/signal" + + "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor" + "go.uber.org/zap" +) + +func main() { + configFlag := flag.String("config", "", "Path to a supervisor configuration file") + flag.Parse() + + logger, _ := zap.NewDevelopment() + + supervisor, err := supervisor.NewSupervisor(logger, *configFlag) + if err != nil { + logger.Error(err.Error()) + os.Exit(-1) + return + } + + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + <-interrupt + supervisor.Shutdown() +} diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go new file mode 100644 index 000000000000..a41768afbc5c --- /dev/null +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -0,0 +1,192 @@ +// 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 commander + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "syscall" + "time" + + "go.uber.org/atomic" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor/config" +) + +// Commander can start/stop/restart the Agent executable and also watch for a signal +// for the Agent process to finish. +type Commander struct { + logger *zap.Logger + cfg *config.Agent + args []string + cmd *exec.Cmd + doneCh chan struct{} + waitCh chan struct{} + running *atomic.Int64 +} + +func NewCommander(logger *zap.Logger, cfg *config.Agent, args ...string) (*Commander, error) { + if cfg.Executable == "" { + return nil, errors.New("agent.executable config option must be specified") + } + + return &Commander{ + logger: logger, + cfg: cfg, + args: args, + running: atomic.NewInt64(0), + }, nil +} + +// Start the Agent and begin watching the process. +// Agent's stdout and stderr are written to a file. +func (c *Commander) Start(ctx context.Context) error { + c.logger.Debug("Starting agent", zap.String("agent", c.cfg.Executable)) + + logFilePath := "agent.log" + logFile, err := os.Create(logFilePath) + if err != nil { + return fmt.Errorf("cannot create %s: %w", logFilePath, err) + } + + c.cmd = exec.CommandContext(ctx, c.cfg.Executable, c.args...) // #nosec G204 + + // Capture standard output and standard error. + c.cmd.Stdout = logFile + c.cmd.Stderr = logFile + + c.doneCh = make(chan struct{}, 1) + c.waitCh = make(chan struct{}) + + if err := c.cmd.Start(); err != nil { + return err + } + + c.logger.Debug("Agent process started", zap.Int("pid", c.cmd.Process.Pid)) + c.running.Store(1) + + go c.watch() + + return nil +} + +func (c *Commander) Restart(ctx context.Context) error { + if err := c.Stop(ctx); err != nil { + return err + } + if err := c.Start(ctx); err != nil { + return err + } + return nil +} + +func (c *Commander) watch() { + err := c.cmd.Wait() + + // cmd.Wait returns an exec.ExitError when the Collector exits unsuccessfully or stops + // after receiving a signal. The Commander caller will handle these cases, so we filter + // them out here. + if _, ok := err.(*exec.ExitError); err != nil && !ok { + c.logger.Error("An error occurred while watching the agent process", zap.Error(err)) + } + + c.doneCh <- struct{}{} + c.running.Store(0) + close(c.waitCh) +} + +// Done returns a channel that will send a signal when the Agent process is finished. +func (c *Commander) Done() <-chan struct{} { + return c.doneCh +} + +// Pid returns Agent process PID if it is started or 0 if it is not. +func (c *Commander) Pid() int { + if c.cmd == nil || c.cmd.Process == nil { + return 0 + } + return c.cmd.Process.Pid +} + +// ExitCode returns Agent process exit code if it exited or 0 if it is not. +func (c *Commander) ExitCode() int { + if c.cmd == nil || c.cmd.ProcessState == nil { + return 0 + } + return c.cmd.ProcessState.ExitCode() +} + +func (c *Commander) IsRunning() bool { + return c.running.Load() != 0 +} + +// Stop the Agent process. Sends SIGTERM to the process and wait for up 10 seconds +// and if the process does not finish kills it forcedly by sending SIGKILL. +// Returns after the process is terminated. +func (c *Commander) Stop(ctx context.Context) error { + if c.cmd == nil || c.cmd.Process == nil { + // Not started, nothing to do. + return nil + } + + c.logger.Debug("Stopping agent process", zap.Int("pid", c.cmd.Process.Pid)) + + // Gracefully signal process to stop. + if err := c.cmd.Process.Signal(syscall.SIGTERM); err != nil { + return err + } + + finished := make(chan struct{}) + + // Setup a goroutine to wait a while for process to finish and send kill signal + // to the process if it doesn't finish. + var innerErr error + go func() { + // Wait 10 seconds. + t := time.After(10 * time.Second) + select { + case <-ctx.Done(): + break + case <-t: + break + case <-finished: + // Process is successfully finished. + c.logger.Debug("Agent process successfully stopped.", zap.Int("pid", c.cmd.Process.Pid)) + return + } + + // Time is out. Kill the process. + c.logger.Debug( + "Agent process is not responding to SIGTERM. Sending SIGKILL to kill forcedly.", + zap.Int("pid", c.cmd.Process.Pid)) + if innerErr = c.cmd.Process.Signal(syscall.SIGKILL); innerErr != nil { + return + } + }() + + // Wait for process to terminate + <-c.waitCh + + c.running.Store(0) + + // Let goroutine know process is finished. + close(finished) + + return innerErr +} diff --git a/cmd/opampsupervisor/supervisor/config/config.go b/cmd/opampsupervisor/supervisor/config/config.go new file mode 100644 index 000000000000..dd3ad391375d --- /dev/null +++ b/cmd/opampsupervisor/supervisor/config/config.go @@ -0,0 +1,29 @@ +// 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 + +// Supervisor is the Supervisor config file format. +type Supervisor struct { + Server *OpAMPServer + Agent *Agent +} + +type OpAMPServer struct { + Endpoint string +} + +type Agent struct { + Executable string +} diff --git a/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go b/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go new file mode 100644 index 000000000000..3e50baf3b2ef --- /dev/null +++ b/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go @@ -0,0 +1,54 @@ +// 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 healthchecker + +import ( + "context" + "fmt" + "net/http" + "time" +) + +type HTTPHealthChecker struct { + endpoint string +} + +func NewHTTPHealthChecker(endpoint string) *HTTPHealthChecker { + return &HTTPHealthChecker{ + endpoint: endpoint, + } +} + +func (h *HTTPHealthChecker) Check(ctx context.Context) error { + req, err := http.NewRequestWithContext(ctx, "GET", h.endpoint, nil) + if err != nil { + return err + } + + client := http.Client{ + Timeout: time.Second * 10, + } + + resp, err := client.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("health check on %s returned %d", h.endpoint, resp.StatusCode) + } + + return nil +} diff --git a/cmd/opampsupervisor/supervisor/opamplogger.go b/cmd/opampsupervisor/supervisor/opamplogger.go new file mode 100644 index 000000000000..00f8c1c9fd63 --- /dev/null +++ b/cmd/opampsupervisor/supervisor/opamplogger.go @@ -0,0 +1,38 @@ +// 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 supervisor + +import ( + "fmt" + + "github.com/open-telemetry/opamp-go/client/types" + "go.uber.org/zap" +) + +// opAMPLogger adapts the Supervisor's zap.Logger instance so it +// can be used by the OpAMP client. +type opAMPLogger struct { + logger *zap.Logger +} + +func (l opAMPLogger) Debugf(format string, v ...interface{}) { + l.logger.Debug(fmt.Sprintf(format, v...)) +} + +func (l opAMPLogger) Errorf(format string, v ...interface{}) { + l.logger.Error(fmt.Sprintf(format, v...)) +} + +var _ types.Logger = opAMPLogger{} diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go new file mode 100644 index 000000000000..050ebaf3dc49 --- /dev/null +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -0,0 +1,695 @@ +// 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 supervisor + +import ( + "context" + "errors" + "fmt" + "math/rand" + "net" + "os" + "runtime" + "sort" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/knadh/koanf" + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/providers/rawbytes" + "github.com/oklog/ulid/v2" + "github.com/open-telemetry/opamp-go/client" + "github.com/open-telemetry/opamp-go/client/types" + "github.com/open-telemetry/opamp-go/protobufs" + "go.uber.org/atomic" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor/commander" + "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor/healthchecker" +) + +// This Supervisor is developed specifically for the OpenTelemetry Collector. +const agentType = "io.opentelemetry.collector" + +// Supervisor implements supervising of OpenTelemetry Collector and uses OpAMPClient +// to work with an OpAMP Server. +type Supervisor struct { + logger *zap.Logger + + // Commander that starts/stops the Agent process. + commander *commander.Commander + + startedAt time.Time + + healthCheckTicker *backoff.Ticker + healthChecker *healthchecker.HTTPHealthChecker + lastHealthCheckErr error + + // Supervisor's own config. + config config.Supervisor + + // Agent's instance id. + instanceID ulid.ULID + + // The version of the agent. + agentVersion string + + // A config section to be added to the Collector's config to fetch its own metrics. + // TODO: store this persistently so that when starting we can compose the effective + // config correctly. + agentConfigOwnMetricsSection atomic.Value + + // agentHealthCheckEndpoint is the endpoint the Collector's health check extension + // will listen on for health check requests from the Supervisor. + agentHealthCheckEndpoint string + + // Final effective config of the Collector. + effectiveConfig atomic.Value + + // Location of the effective config file. + effectiveConfigFilePath string + + // Last received remote config. + remoteConfig *protobufs.AgentRemoteConfig + + // A channel to indicate there is a new config to apply. + hasNewConfig chan struct{} + + // The OpAMP client to connect to the OpAMP Server. + opampClient client.OpAMPClient +} + +func NewSupervisor(logger *zap.Logger, configFile string) (*Supervisor, error) { + s := &Supervisor{ + logger: logger, + hasNewConfig: make(chan struct{}, 1), + effectiveConfigFilePath: "effective.yaml", + } + + if err := s.loadConfig(configFile); err != nil { + return nil, fmt.Errorf("error loading config: %w", err) + } + + if err := s.getBootstrapInfo(); err != nil { + s.logger.Error("Couldn't get agent version", zap.Error(err)) + } + + port, err := s.findRandomPort() + + if err != nil { + return nil, fmt.Errorf("could not find port for health check: %w", err) + } + + s.agentHealthCheckEndpoint = fmt.Sprintf("localhost:%d", port) + + s.createInstanceID() + logger.Debug("Supervisor starting", + zap.String("id", s.instanceID.String()), zap.String("type", agentType), zap.String("version", s.agentVersion)) + + s.loadAgentEffectiveConfig() + + if err := s.startOpAMP(); err != nil { + return nil, fmt.Errorf("cannot start OpAMP client: %w", err) + } + + s.commander, err = commander.NewCommander( + s.logger, + s.config.Agent, + "--config", s.effectiveConfigFilePath, + ) + if err != nil { + return nil, err + } + + s.startHealthCheckTicker() + go s.runAgentProcess() + + return s, nil +} + +func (s *Supervisor) loadConfig(configFile string) error { + if configFile == "" { + return errors.New("path to config file cannot be empty") + } + + k := koanf.New("::") + if err := k.Load(file.Provider(configFile), yaml.Parser()); err != nil { + return err + } + + if err := k.Unmarshal("", &s.config); err != nil { + return fmt.Errorf("cannot parse %v: %w", configFile, err) + } + + return nil +} + +func (s *Supervisor) getBootstrapInfo() (err error) { + s.agentVersion = "1.0.0" + + return nil +} + +func (s *Supervisor) startOpAMP() error { + s.opampClient = client.NewWebSocket(opAMPLogger{logger: s.logger}) + + settings := types.StartSettings{ + OpAMPServerURL: s.config.Server.Endpoint, + InstanceUid: s.instanceID.String(), + Callbacks: types.CallbacksStruct{ + OnConnectFunc: func() { + s.logger.Debug("Connected to the server.") + }, + OnConnectFailedFunc: func(err error) { + s.logger.Error("Failed to connect to the server", zap.Error(err)) + }, + OnErrorFunc: func(err *protobufs.ServerErrorResponse) { + s.logger.Error("Server returned an error response", zap.String("message", err.ErrorMessage)) + }, + GetEffectiveConfigFunc: func(ctx context.Context) (*protobufs.EffectiveConfig, error) { + return s.createEffectiveConfigMsg(), nil + }, + OnMessageFunc: s.onMessage, + }, + // TODO: Make capabilities configurable + Capabilities: protobufs.AgentCapabilities_AgentCapabilities_AcceptsRemoteConfig | + protobufs.AgentCapabilities_AgentCapabilities_ReportsRemoteConfig | + protobufs.AgentCapabilities_AgentCapabilities_ReportsEffectiveConfig | + protobufs.AgentCapabilities_AgentCapabilities_ReportsOwnMetrics | + protobufs.AgentCapabilities_AgentCapabilities_ReportsHealth, + } + err := s.opampClient.SetAgentDescription(s.createAgentDescription()) + if err != nil { + return err + } + + err = s.opampClient.SetHealth(&protobufs.AgentHealth{Healthy: false}) + if err != nil { + return err + } + + s.logger.Debug("Starting OpAMP client...") + + err = s.opampClient.Start(context.Background(), settings) + if err != nil { + return err + } + + s.logger.Debug("OpAMP Client started.") + + return nil +} + +func (s *Supervisor) createInstanceID() { + // Generate instance id. + entropy := ulid.Monotonic(rand.New(rand.NewSource(0)), 0) + s.instanceID = ulid.MustNew(ulid.Timestamp(time.Now()), entropy) + + // TODO: Persist instance ID. +} + +func keyVal(key, val string) *protobufs.KeyValue { + return &protobufs.KeyValue{ + Key: key, + Value: &protobufs.AnyValue{ + Value: &protobufs.AnyValue_StringValue{StringValue: val}, + }, + } +} + +func (s *Supervisor) createAgentDescription() *protobufs.AgentDescription { + hostname, _ := os.Hostname() + + // Create Agent description. + return &protobufs.AgentDescription{ + IdentifyingAttributes: []*protobufs.KeyValue{ + keyVal("service.name", agentType), + keyVal("service.version", s.agentVersion), + }, + NonIdentifyingAttributes: []*protobufs.KeyValue{ + keyVal("os.family", runtime.GOOS), + keyVal("host.name", hostname), + }, + } +} + +func (s *Supervisor) composeExtraLocalConfig() string { + return fmt.Sprintf(` +service: + telemetry: + logs: + # Enables JSON log output for the Agent. + encoding: json + resource: + # Set resource attributes required by OpAMP spec. + # See https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#agentdescriptionidentifying_attributes + service.name: %s + service.version: %s + service.instance.id: %s + + # Enable extension to allow the Supervisor to check health. + extensions: [health_check] + +extensions: + health_check: + endpoint: %s +`, + agentType, + s.agentVersion, + s.instanceID.String(), + s.agentHealthCheckEndpoint, + ) +} + +func (s *Supervisor) loadAgentEffectiveConfig() { + var effectiveConfigBytes []byte + + effFromFile, err := os.ReadFile(s.effectiveConfigFilePath) + if err == nil { + // We have an effective config file. + effectiveConfigBytes = effFromFile + } else { + // No effective config file, just use the initial config. + effectiveConfigBytes = []byte(s.composeExtraLocalConfig()) + } + + s.effectiveConfig.Store(string(effectiveConfigBytes)) +} + +// createEffectiveConfigMsg create an EffectiveConfig with the content of the +// current effective config. +func (s *Supervisor) createEffectiveConfigMsg() *protobufs.EffectiveConfig { + cfgStr, ok := s.effectiveConfig.Load().(string) + if !ok { + cfgStr = "" + } + + cfg := &protobufs.EffectiveConfig{ + ConfigMap: &protobufs.AgentConfigMap{ + ConfigMap: map[string]*protobufs.AgentConfigFile{ + "": {Body: []byte(cfgStr)}, + }, + }, + } + + return cfg +} + +func (s *Supervisor) setupOwnMetrics(_ context.Context, settings *protobufs.TelemetryConnectionSettings) (configChanged bool) { + var cfg string + if settings.DestinationEndpoint == "" { + // No destination. Disable metric collection. + s.logger.Debug("Disabling own metrics pipeline in the config") + cfg = "" + } else { + s.logger.Debug("Enabling own metrics pipeline in the config") + + port, err := s.findRandomPort() + + if err != nil { + s.logger.Error("Could not setup own metrics", zap.Error(err)) + return + } + + cfg = fmt.Sprintf( + ` +receivers: + # Collect own metrics + prometheus/own_metrics: + config: + scrape_configs: + - job_name: 'otel-collector' + scrape_interval: 10s + static_configs: + - targets: ['0.0.0.0:%d'] +exporters: + otlphttp/own_metrics: + metrics_endpoint: %s + +service: + telemetry: + metrics: + address: :%d + pipelines: + metrics/own_metrics: + receivers: [prometheus/own_metrics] + exporters: [otlphttp/own_metrics] +`, + port, + settings.DestinationEndpoint, + port, + ) + } + + s.agentConfigOwnMetricsSection.Store(cfg) + + // Need to recalculate the Agent config so that the metric config is included in it. + configChanged, err := s.recalcEffectiveConfig() + if err != nil { + return + } + + return configChanged +} + +// composeEffectiveConfig composes the effective config from multiple sources: +// 1) the remote config from OpAMP Server +// 2) the own metrics config section +// 3) the local override config that is hard-coded in the Supervisor. +func (s *Supervisor) composeEffectiveConfig(config *protobufs.AgentRemoteConfig) (configChanged bool, err error) { + var k = koanf.New(".") + + // Begin with empty config. We will merge received configs on top of it. + if err = k.Load(rawbytes.Provider([]byte{}), yaml.Parser()); err != nil { + return false, err + } + + // Sort to make sure the order of merging is stable. + var names []string + for name := range config.Config.ConfigMap { + if name == "" { + // skip instance config + continue + } + names = append(names, name) + } + + sort.Strings(names) + + // Append instance config as the last item. + names = append(names, "") + + // Merge received configs. + for _, name := range names { + item := config.Config.ConfigMap[name] + var k2 = koanf.New(".") + err = k2.Load(rawbytes.Provider(item.Body), yaml.Parser()) + if err != nil { + return false, fmt.Errorf("cannot parse config named %s: %w", name, err) + } + err = k.Merge(k2) + if err != nil { + return false, fmt.Errorf("cannot merge config named %s: %w", name, err) + } + } + + // Merge own metrics config. + ownMetricsCfg, ok := s.agentConfigOwnMetricsSection.Load().(string) + if ok { + if err = k.Load(rawbytes.Provider([]byte(ownMetricsCfg)), yaml.Parser()); err != nil { + return false, err + } + } + + // Merge local config last since it has the highest precedence. + if err = k.Load(rawbytes.Provider([]byte(s.composeExtraLocalConfig())), yaml.Parser()); err != nil { + return false, err + } + + // The merged final result is our effective config. + effectiveConfigBytes, err := k.Marshal(yaml.Parser()) + if err != nil { + return false, err + } + + // Check if effective config is changed. + newEffectiveConfig := string(effectiveConfigBytes) + configChanged = false + if s.effectiveConfig.Load().(string) != newEffectiveConfig { + s.logger.Debug("Effective config changed.") + s.effectiveConfig.Store(newEffectiveConfig) + configChanged = true + } + + return configChanged, nil +} + +// Recalculate the Agent's effective config and if the config changes, signal to the +// background goroutine that the config needs to be applied to the Agent. +func (s *Supervisor) recalcEffectiveConfig() (configChanged bool, err error) { + configChanged, err = s.composeEffectiveConfig(s.remoteConfig) + if err != nil { + s.logger.Error("Error composing effective config. Ignoring received config", zap.Error(err)) + return configChanged, err + } + + return configChanged, nil +} + +func (s *Supervisor) startAgent() { + err := s.commander.Start(context.Background()) + if err != nil { + s.logger.Error("Cannot start the agent", zap.Error(err)) + err := s.opampClient.SetHealth(&protobufs.AgentHealth{Healthy: false, LastError: fmt.Sprintf("Cannot start the agent: %v", err)}) + + if err != nil { + s.logger.Error("Failed to report OpAMP client health", zap.Error(err)) + } + + return + } + s.startedAt = time.Now() + s.startHealthCheckTicker() + + s.healthChecker = healthchecker.NewHTTPHealthChecker(fmt.Sprintf("http://%s", s.agentHealthCheckEndpoint)) +} + +func (s *Supervisor) startHealthCheckTicker() { + // Prepare health checker + healthCheckBackoff := backoff.NewExponentialBackOff() + healthCheckBackoff.MaxInterval = 60 * time.Second + healthCheckBackoff.MaxElapsedTime = 0 // Never stop + if s.healthCheckTicker != nil { + s.healthCheckTicker.Stop() + } + s.healthCheckTicker = backoff.NewTicker(healthCheckBackoff) +} + +func (s *Supervisor) healthCheck() { + if !s.commander.IsRunning() { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + + err := s.healthChecker.Check(ctx) + cancel() + + if errors.Is(err, s.lastHealthCheckErr) { + // No difference from last check. Nothing new to report. + return + } + + // Prepare OpAMP health report. + health := &protobufs.AgentHealth{ + StartTimeUnixNano: uint64(s.startedAt.UnixNano()), + } + + if err != nil { + health.Healthy = false + health.LastError = err.Error() + s.logger.Error("Agent is not healthy", zap.Error(err)) + } else { + health.Healthy = true + s.logger.Debug("Agent is healthy.") + } + + // Report via OpAMP. + if err2 := s.opampClient.SetHealth(health); err2 != nil { + s.logger.Error("Could not report health to OpAMP server", zap.Error(err2)) + return + } + + s.lastHealthCheckErr = err +} + +func (s *Supervisor) runAgentProcess() { + if _, err := os.Stat(s.effectiveConfigFilePath); err == nil { + // We have an effective config file saved previously. Use it to start the agent. + s.startAgent() + } + + restartTimer := time.NewTimer(0) + restartTimer.Stop() + + for { + select { + case <-s.hasNewConfig: + restartTimer.Stop() + s.stopAgentApplyConfig() + s.startAgent() + + case <-s.commander.Done(): + s.logger.Debug("Agent process exited unexpectedly. Will restart in a bit...", zap.Int("pid", s.commander.Pid()), zap.Int("exit_code", s.commander.ExitCode())) + errMsg := fmt.Sprintf( + "Agent process PID=%d exited unexpectedly, exit code=%d. Will restart in a bit...", + s.commander.Pid(), s.commander.ExitCode(), + ) + err := s.opampClient.SetHealth(&protobufs.AgentHealth{Healthy: false, LastError: errMsg}) + + if err != nil { + s.logger.Error("Could not report health to OpAMP server", zap.Error(err)) + } + + // TODO: decide why the agent stopped. If it was due to bad config, report it to server. + + // Wait 5 seconds before starting again. + restartTimer.Stop() + restartTimer.Reset(5 * time.Second) + + case <-restartTimer.C: + s.startAgent() + + case <-s.healthCheckTicker.C: + s.healthCheck() + } + } +} + +func (s *Supervisor) stopAgentApplyConfig() { + s.logger.Debug("Stopping the agent to apply new config") + cfg := s.effectiveConfig.Load().(string) + err := s.commander.Stop(context.Background()) + + if err != nil { + s.logger.Error("Could not stop agent process", zap.Error(err)) + } + + s.writeEffectiveConfigToFile(cfg, s.effectiveConfigFilePath) +} + +func (s *Supervisor) writeEffectiveConfigToFile(cfg string, filePath string) { + f, err := os.Create(filePath) + if err != nil { + s.logger.Error("Cannot create effective config file", zap.Error(err)) + } + defer f.Close() + + _, err = f.WriteString(cfg) + + if err != nil { + s.logger.Error("Cannot write effective config file", zap.Error(err)) + } +} + +func (s *Supervisor) Shutdown() { + s.logger.Debug("Supervisor shutting down...") + if s.commander != nil { + err := s.commander.Stop(context.Background()) + + if err != nil { + s.logger.Error("Could not stop agent process", zap.Error(err)) + } + } + if s.opampClient != nil { + err := s.opampClient.SetHealth( + &protobufs.AgentHealth{ + Healthy: false, LastError: "Supervisor is shutdown", + }, + ) + + if err != nil { + s.logger.Error("Could not report health to OpAMP server", zap.Error(err)) + } + + err = s.opampClient.Stop(context.Background()) + + if err != nil { + s.logger.Error("Could not stop the OpAMP client", zap.Error(err)) + } + } +} + +func (s *Supervisor) onMessage(ctx context.Context, msg *types.MessageData) { + configChanged := false + if msg.RemoteConfig != nil { + s.remoteConfig = msg.RemoteConfig + s.logger.Debug("Received remote config from server", zap.String("hash", fmt.Sprintf("%x", s.remoteConfig.ConfigHash))) + + var err error + configChanged, err = s.recalcEffectiveConfig() + if err != nil { + err = s.opampClient.SetRemoteConfigStatus(&protobufs.RemoteConfigStatus{ + LastRemoteConfigHash: msg.RemoteConfig.ConfigHash, + Status: protobufs.RemoteConfigStatuses_RemoteConfigStatuses_FAILED, + ErrorMessage: err.Error(), + }) + if err != nil { + s.logger.Error("Could not report failed OpAMP remote config status", zap.Error(err)) + } + } else { + err = s.opampClient.SetRemoteConfigStatus(&protobufs.RemoteConfigStatus{ + LastRemoteConfigHash: msg.RemoteConfig.ConfigHash, + Status: protobufs.RemoteConfigStatuses_RemoteConfigStatuses_APPLIED, + }) + if err != nil { + s.logger.Error("Could not report applied OpAMP remote config status", zap.Error(err)) + } + } + } + + if msg.OwnMetricsConnSettings != nil { + configChanged = s.setupOwnMetrics(ctx, msg.OwnMetricsConnSettings) || configChanged + } + + if msg.AgentIdentification != nil { + newInstanceID, err := ulid.Parse(msg.AgentIdentification.NewInstanceUid) + if err != nil { + s.logger.Error("Failed to parse instance ULID", zap.Error(err)) + } + + s.logger.Debug("Agent identity is changing", + zap.String("old_id", s.instanceID.String()), + zap.String("new_id", newInstanceID.String())) + s.instanceID = newInstanceID + s.opampClient.SetAgentDescription(s.createAgentDescription()) + + configChanged = true + } + + if configChanged { + err := s.opampClient.UpdateEffectiveConfig(ctx) + if err != nil { + s.logger.Error("The OpAMP client failed to update the effective config", zap.Error(err)) + } + + s.logger.Debug("Config is changed. Signal to restart the agent") + // Signal that there is a new config. + select { + case s.hasNewConfig <- struct{}{}: + default: + } + } +} + +func (s *Supervisor) findRandomPort() (int, error) { + l, err := net.Listen("tcp", ":0") + + if err != nil { + return 0, err + } + + port := l.Addr().(*net.TCPAddr).Port + + err = l.Close() + + if err != nil { + return 0, err + } + + return port, nil +} diff --git a/cmd/opampsupervisor/testdata/supervisor_darwin.yaml b/cmd/opampsupervisor/testdata/supervisor_darwin.yaml new file mode 100644 index 000000000000..9b278fedbcd8 --- /dev/null +++ b/cmd/opampsupervisor/testdata/supervisor_darwin.yaml @@ -0,0 +1,5 @@ +server: + endpoint: ws://127.0.0.1:4320/v1/opamp + +agent: + executable: ../../bin/otelcontribcol_darwin_amd64 diff --git a/cmd/opampsupervisor/testdata/supervisor_linux.yaml b/cmd/opampsupervisor/testdata/supervisor_linux.yaml new file mode 100644 index 000000000000..9cb491ff2436 --- /dev/null +++ b/cmd/opampsupervisor/testdata/supervisor_linux.yaml @@ -0,0 +1,5 @@ +server: + endpoint: ws://127.0.0.1:4320/v1/opamp + +agent: + executable: ../../bin/otelcontribcol_linux_amd64 diff --git a/cmd/opampsupervisor/testdata/supervisor_windows.yaml b/cmd/opampsupervisor/testdata/supervisor_windows.yaml new file mode 100644 index 000000000000..0aefc0c2b727 --- /dev/null +++ b/cmd/opampsupervisor/testdata/supervisor_windows.yaml @@ -0,0 +1,5 @@ +server: + endpoint: ws://127.0.0.1:4320/v1/opamp + +agent: + executable: ../../bin/otelcontribcol_windows_amd64.exe From 63a9488c8c29fd2a9893b996a20d9d2c2102cb59 Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Thu, 13 Apr 2023 13:55:25 -0400 Subject: [PATCH 02/17] Add specification document converted from Markdown --- cmd/opampsupervisor/README.md | 4 +- cmd/opampsupervisor/specification/README.md | 468 ++++++++++++++++++ .../specification/extension-diagram.png | Bin 0 -> 12635 bytes .../specification/supervisor-diagram.png | Bin 0 -> 29815 bytes 4 files changed, 470 insertions(+), 2 deletions(-) create mode 100644 cmd/opampsupervisor/specification/README.md create mode 100644 cmd/opampsupervisor/specification/extension-diagram.png create mode 100644 cmd/opampsupervisor/specification/supervisor-diagram.png diff --git a/cmd/opampsupervisor/README.md b/cmd/opampsupervisor/README.md index 2c8bdbafaece..3601a0732bcd 100644 --- a/cmd/opampsupervisor/README.md +++ b/cmd/opampsupervisor/README.md @@ -1,7 +1,7 @@ # OpAMP Supervisor for the OpenTelemetry Collector This is an implementation of an OpAMP Supervisor that runs a Collector instance using configuration provided from an OpAMP server. This implementation -is following a design specified in this [Google Doc](https://docs.google.com/document/d/1KtH5atZQUs9Achbce6LiOaJxLbksNJenvgvyKLsJrkc/edit). +is following a design specified [here](./specification/README.md). The design is still undergoing changes, and as such this implementation may change as well. ## Experimenting with the supervisor @@ -21,4 +21,4 @@ The supervisor is currently undergoing heavy development and is not ready for an go run . --config testdata/supervisor_.yaml ``` -4. The supervisor should connect to the OpAMP server and start a Collector instance. \ No newline at end of file +4. The supervisor should connect to the OpAMP server and start a Collector instance. diff --git a/cmd/opampsupervisor/specification/README.md b/cmd/opampsupervisor/specification/README.md new file mode 100644 index 000000000000..0d1b766f8976 --- /dev/null +++ b/cmd/opampsupervisor/specification/README.md @@ -0,0 +1,468 @@ +# OpAMP for OpenTelemetry Collector + +Author: Tigran Najaryan + +## Introduction + +[OpAMP](https://github.com/open-telemetry/opamp-spec) has been one of +the most requested capabilities at Kubecon 2022. We can implement OpAMP +for the Collector in 2 different ways: + +- As a Collector extension, with limited functionality, +- As an external Supervisor, that implements all or most of OpAMP + capabilities. + +In discussions with users and Collector contributors we found that both +of these approaches are wanted. This document describes how to implement +both while minimizing duplicate work. + +The main idea is to implement a Collector extension with a limited set +of OpAMP capabilities, where the extension can be used on its own, then +additionally create an external Supervisor that uses the exact same +extension as a helper and implements the remaining OpAMP capabilities on +top of what the extension implements. This way most of OpAMP-related +functionality is implemented once only, there is virtually no code +duplication. + +## Supervised Operation + +This section describes a specialized Supervisor made for OpenTelemetry +Collector. The Supervisor will have the benefit of knowing for example +how to inject desirable values into the Collector's configuration file. + +Here is how a Supervisor-based management works: + +![Supervisor architecture diagram](supervisor-diagram.png) + +The Supervisor process does the following: + +- Implements the client-side of OpAMP protocol and communicates with + the OpAMP Backend. +- Starts/stops the Collector process as necessary. +- Receives configuration from the OpAMP Backend and pushes it to the + Collector, using the Collector config.yaml file as an intermediary, + restarting the Collector process as necessary. +- Serves as a watchdog, restarts the Collector process if the + Collector crashes. +- Accepts an OpAMP connection from Collectors' [*opamp + extension*](#collectors-opamp-extension), receives the Collector's + AgentDescription, HealthStatus and EffectiveConfig messages and + forwards them to the OpAMP Backend. +- Optionally: downloads Collector executable packages offered by the + Backend and performs the Collector updates. +- Optionally: configures Collector to collect Collector's own metrics + and report the metrics to the OTLP telemetry backend requested by + OpAMP Backend. +- Optionally: collects Collector logs and sends them to the Telemetry + Backend via OTLP. + +Supervisor is implemented as a Go library that may be customized and +rebuilt by vendors with useful default configurations, such as the OpAMP +Backend endpoint to connect to, in order to minimize the manual +configuration required. + +*Important: the Supervisor needs to be highly stable, so we need to keep +its complexity and functionality to minimum. The features listed in this +section need a critical review and may be removed (responsibility moved +elsewhere, e.g. to the Collector itself).* + +### Supervisor Configuration + +The Supervisor is configured via a yaml config file: + +```yaml +# OpAMP backend server settings. +server: + # endpoint is a URL and is mandatory. + # ws,wss,http,https schemes are supported. + # Other connection settings, e.g. TLS cert, etc. + endpoint: wss://example.com/opamp + +# Keys with boolean true/false values that enable a particular +# OpAMP capability. +# The Supervisor will accept remote configuration from the Server. +# If enabled the Supervisor will also report RemoteConfig status +# to the Server. +capabilities: + AcceptsRemoteConfig: # false if unspecified + + # The Supervisor will report EffectiveConfig to the Server. + ReportsEffectiveConfig: # true if unspecified + + # The Supervisor can accept Collector executable package updates. + # If enabled the Supervisor will also report package status to the + # Server. + AcceptsPackages: # false if unspecified + + # The Collector will report own metrics to the destination specified by + # the Server. + ReportsOwnMetrics: # true if unspecified + + # The Collector will report own logs to the destination specified by + # the Server. + ReportsOwnLogs: # true if unspecified + + # The Collector will accept connections settings for exporters + # from the Server. + AcceptsOtherConnectionSettings: # false if unspecified + + # The Supervisor will accept restart requests. + AcceptsRestartCommand: # true if unspecified + + # The Collector will report Health. + ReportsHealth: # true if unspecified + +storage: + # A writable directory where the Supervisor can store data + # (e.g. cached remote config). + # defaults to /var/lib/otelcol/supervisor on posix systems + # and %ProgramData%/Otelcol/Supervisor on Windows. + directory: /path/to/dir + +collector: + # Path to Collector executable. Required. + executable: /opt/otelcol/bin/otelcol + + # extra command line flags to pass to the Collector executable. + args: + + # extra environment variables to set when executing the Collector + # Optional user name to drop the privileges to when running the + # Collector process. + env: + run_as: myuser + # Path to optional local Collector config file to be merged with the + # config provided by the OpAMP server. + config_file: /etc/otelcol/config.yaml + # Optional directories that are allowed to be read/written by the + # Collector. + # If unspecified then NO access to the filesystem is allowed. + access_dirs: + read: + allow: \[/var/log\] + deny: \[/var/log/secret_logs\] + write: + allow: \[/var/otelcol\] +``` + +### Executing Collector + +The Supervisor starts and stops the Collector process as necessary. When +run_as setting is provided, the Supervisor will execute the Collector +process as the specified user. This is highly recommended in situations +when the Supervisor itself is running as root and it is desirable to +drop the root privileges and run the Collector as a more restricted +user. + +#### Stopping Collector + +To stop the Collector the Supervisor will issue a SIGTERM signal first, +wait for the process to exit, then force exit via SIGKILL if necessary. + +### Collector Config + +The Supervisor creates a Collector config file in a temp directory and +passes to the Collector via --config command line option. The config +file is created by merging the local config file (optional) and the +remote config received from OpAMP backend (also optional). The +configuration file merging rules match the merging rules that are +already in place in the Collector codebase (key-by-key, recursively). + +#### Remote Configuration + +Note: this capability must be manually enabled by the user via a +AcceptsRemoteConfig setting in the supervisor config file and is +disabled by default. + +The Supervisor receives [*Remote +Configuration*](https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#configuration) +from the OpAMP Backend, merges it with an optional local config file and +writes it to the Collector's config file, then restarts the Collector. + +In the future once config file watching is implemented the Collector can +reload the config without the need for the Supervisor to restart the +Collector process. + +The Supervisor will report to the OpAMP Backend the status of all these +operations via RemoteConfigStatus message. + +#### Sanitizing Configuration + +The Supervisor will sanitize the configuration of the components that +access the local filesystem according to the access_dirs config setting +to only allow specified directories and their subdirectories. This +applies for example to \`include\` setting of the \`filelog\` receiver +or to \`directory\` setting of the \`file_storage\` extension. + +The Supervisor will locate all such entries while building the Collector +config file and will delete the ones which are prohibited by the access +control settings. + +*Open Question: if after sanitizing the component's directory setting +the configuration becomes invalid what do we do?* + +*The sanitizing logic is hard-coded in the Supervisor and works for +specific components only. In the future we will consider implementing a +more generic safety mechanism that does not depend on the knowledge +about specific component behavior.* + +#### Bootstrapping + +In order to obtain the remote configuration from the OpAMP Backend the +Supervisor must send an AgentDescription to the Backend. Initially the +Supervisor doesn't have this information. The AgentDescription becomes +available only after the Collector process is started and the +AgentDescription is sent from the opamp extension to the Supervisor. +However, it is impossible to start the Collector without a +configuration. + +To overcome this problem the Supervisor starts the Collector with an +"noop" configuration that collects nothing but allows the opamp +extension to be started. The "noop" configuration is a single pipeline +with a filelog receiver that points to a non-existing file and a logging +exporter and the opamp extension. The purpose of the "noop" +configuration is to make sure the Collector starts and the opamp +extension communicates with the Supervisor. + +Once the initial Collector launch is successful and the remote +configuration is received by the Supervisor the Supervisor restarts the +Collector with the new config. The new config is also cached by the +Supervisor in a local file, so that subsequent restarts no longer need +to start the Collector using the "noop" configuration. Caching of the +last config also allows the Supervisor to subsequently start the +Collector without waiting for the OpAMP Backend to provide the remote +config and mitigates OpAMP Backend unavailability. + +#### Reverting + +One of the challenges when the Supervisor updates the configuration is +knowing whether the configuration is successfully applied or not. There +may not exist a definite signal that indicates that all of the +configuration is good and applied by the Collector. One possible +approach is to set a certain period after which the Collector is +expected to be up and running in "healthy" status and if that does not +happen (i.e. the Collector crashes or "healthy" status is not seen) then +the configuration is reverted to the last one. + +The reverting likely needs to be an optional feature that the user can +enable. TODO: add a Supervisor config setting for this option. + +### Watchdog + +The Supervisor will monitor the Collector process it started. If the +Collector process terminates unexpectedly the Supervisor will restart +the Collector (with a backoff). The Supervisor can also report restart +or crash-related metrics along with other telemetry it reports for the +Collector. + +### Collector Instance UID + +The Supervisor maintains a Collector instance_uid (a +[ULID](https://github.com/open-telemetry/opamp-spec/blob/main/specification.md#agenttoserverinstance_uid)). +The instance_uid is generated by the Supervisor on the first run or +during the Supervisor installation and remains unchanged thereafter. The +instance_uid will be used in OpAMP communication. + +The value is injected into the Collector config in [opamp +extension's](#collectors-opamp-extension) instance_uid setting and as +service.instance.id attribute under the service.telemetry.resource +setting. + +Note: if [Make generated service.instance.id available to +extensions](https://github.com/open-telemetry/opentelemetry-collector/issues/6599) +is implemented then the instance_uid setting in the opamp extension is +no longer necessary. + +### Collector's Own Telemetry + +#### Own Metrics + +Supervisor will configure Collector to report its own metrics to the +OTLP destination requested by the OpAMP Backend. See how it is done in +[this example +here](https://github.com/open-telemetry/opamp-go/blob/efddaa260895e7ebb2431deeefef3a3380d8d902/internal/examples/supervisor/supervisor/supervisor.go#L277). + +Eventually, when the Collector's internal metrics are migrated to Otel +SDK we should be able to configure the SDK to send to this destination +without configuring a Collector pipeline. + +#### Own Logs + +The Supervisor will collect Collector's logs (the stdout and stderr) and +send them to the log collection endpoint offered by the OpAMP Backend. + +The Supervisor will configure the Collector output logs in json format +(e.g. [like +this](https://github.com/open-telemetry/opamp-go/blob/efddaa260895e7ebb2431deeefef3a3380d8d902/internal/examples/supervisor/supervisor/supervisor.go#LL220C21-L220C21)) +and will parse them, then send to the OTLP destination requested by the +OpAMP Backend. The Supervisor will enrich the logs using the attributes +it received previously in AgentDescription message from the [opamp +extension](#collectors-opamp-extension). + +Note that for logs we are not using the same approach as we do for +Collector's own metrics. The reason is that collecting logs may produce +logs and that may result in catastrophic amplification of generated +logs. To safeguard from that we collect logs using the Supervisor. + +The additional benefit of using Supervisor for log collection is that if +the Collector crashes the Supervisor will still be able to collect all +log output up until the process termination, which can be crucial for +understanding the cause of crash (using Collector to collect its logs +won't guarantee this). + +The Supervisor will also write the Collector's log to a local log file. +The path to the Collector log files will be printed in the Supervisor +output. + +*Open Question: instead of writing to a local log file do we want to +pipe Collector logs to Supervisor's log output?* + +### Collector Executable Updates + +Note: this capability must be manually enabled by the user via the +AcceptsPackages setting in the supervisor config file and is disabled by +default. + +This capability should be only implemented after we introduce the code +signing process for the Collector and all released Collector executables +are signed, the Supervisor is capable of verifying the signature of +executables and other OpAMP security recommendations are followed. + +The Supervisor will download Collector package updates when offered so +by the Backend. The Supervisor will verify the integrity of the packages +and will install them. This requires stopping the Collector, overwriting +the Collector executable file with the newly downloaded version and +starting the Collector. Before overwriting the executable the Supervisor +will save it in case it is necessary for reverting. + +If after the restart the Collector does not become healthy the +Supervisor will revert the update, by stopping the Collector, reverting +the Collector executable file and starting the Collector again. The +failed update attempt will be reported to the Backend and the failed +Collector package version will be marked as "bad" to avoid trying it +again even if offered by the Backend. + +Note: cached local config must be invalidated after executable updates +to make sure a fresh AgentDescription is obtained by the Supervisor on +the next Collector start (at the minimum the version number to be +included in AgentDescription is expected to change after the executable +is updated). + +### Addons Management + +The Collector currently does not have a concept of addons so this OpAMP +capability is not implemented by the Supervisor. + +### Exporter Connection Settings + +The Supervisor will populate the connection settings for exporters in +the Collector configuration file based on the "other_connections" named +settings it receives from the OpAMP Backend. Each named connection +setting corresponds to the exporter with the same name. The Supervisor +will populate exporter settings from OpAMP ConnectionSettings message +the following way: + +| **ConnectionSettings** | **Exporter setting** | +|---------------------------|----------------------| +| destination_endpoint | endpoint | +| headers | headers | +| certificate.public_key | tls.cert_file | +| certificate.private_key | tls.key_file | +| certificate.ca_public_key | tls.ca_file | + +The received certificate will be written to local files and the paths to +the files containing the keys will be populated in the corresponding +sections under the \`tls\` setting. + +## Collector's opamp Extension + +The Supervisor automatically injects the opamp extension in the +Collector's configuration. The opamp extension implements an OpAMP +client with a small subset of OpAMP agent capabilities: + +- ReportsStatus. The extension reports agent description and status. + This is the first message from the client to the server in the OpAMP + protocol that is essential for beginning OpAMP message exchange. +- ReportsEffectiveConfig. The extension reports the Collector's + effective config on startup and any time the config changes. In + order to do this the opamp extension needs [access to the effective + config](https://github.com/open-telemetry/opentelemetry-collector/issues/6596). +- ReportsHealth. The extension reports Collector's health on startup + and any time the health changes. In order to do this the opamp + extension needs access to the health of the Collector. The very + basic health capability can be replicated by mirroring the + functionality of the healthcheck extension, a more advanced + capability depends on the [component status + reporting](https://github.com/open-telemetry/opentelemetry-collector/pull/6560). + +The messages received from the opamp extension are forwarded by the +Supervisor to the destination OpAMP Backend and replies to these +messages that the Supervisor receives from the OpAMP Backend are +forwarded in the opposite direction to the opamp extension. The +Supervisor also peeks into AgentDescription and keeps it for its own use +as necessary (e.g. to use as [log attributes](#own-logs)). + +The opamp extension in the Collector will accept the following +configuration: + +```yaml +extensions: + opamp: + # OpAMP server URL. Supports WS or plain http transport, + # based on the scheme of the URL (ws,wss,http,https). + # Any other settings defined in HTTPClientSettings, squashed. This + # includes ability to specify an "auth" setting that refers + # to an extension that implements the Authentication interface. + endpoint: + + # ULID formatted as a 26 character string in canonical + # representation. Auto-generated on start if missing. + # Injected by Supervisor. + # Note: can be deprecated after Collector issue #6599 + # is implemented. + instance_uid: +``` + +The extension uses an OpAMP connection to the Supervisor when used with +the Supervisor model. + +The extensions' configuration cannot be overridden by the remote +configuration. + +The same extension can be used to connect directly to the OpAMP Server, +without the Supervisor: + +![OpAMP extension architecture diagram](extension-diagram.png) + +This is the simplified operation model where only status reporting is +required. Only the 3 capabilities that the extension implements work. No +other OpAMP capabilities are available in this operation model. + +When the opamp extension is used without the Supervisor it will need to +know what service.instance.id the Collector's telemetry is using. There +is an [open +issue](https://github.com/open-telemetry/opentelemetry-collector/issues/6599) +to allow this. + +*Open Question: when used with Supervisor do we want the Supervisor to +actively periodically query the health of the Collector or we can rely +on opamp extension to report the health when it changes?* + +## Future Work + +- Decide if we want to have Supervisor-less AcceptsRemoteConfig + capability in the Collector. This currently can't be done by using + just an extension. At the minimum it requires a config Provider. +- Consider extending the Supervisor to be able to manage multiple + Collector instances. + +## References + +- OpAMP Specification: + [https://github.com/open-telemetry/opamp-spec/blob/main/specification.md](https://github.com/open-telemetry/opamp-spec/blob/main/specification.md) +- OpAMP client and server implementation in Go: + [https://github.com/open-telemetry/opamp-go](https://github.com/open-telemetry/opamp-go) +- Example Supervisor implementation: + [https://github.com/open-telemetry/opamp-go/tree/main/internal/examples/supervisor](https://github.com/open-telemetry/opamp-go/tree/main/internal/examples/supervisor) +- OpAMP Milestone in the Collector: + [https://github.com/open-telemetry/opentelemetry-collector/milestone/29](https://github.com/open-telemetry/opentelemetry-collector/milestone/29) diff --git a/cmd/opampsupervisor/specification/extension-diagram.png b/cmd/opampsupervisor/specification/extension-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..9f754812a8a6afd19ba9d979c1f5c49755d1cb85 GIT binary patch literal 12635 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 literal 0 HcmV?d00001 diff --git a/cmd/opampsupervisor/specification/supervisor-diagram.png b/cmd/opampsupervisor/specification/supervisor-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..cb10dd2eb0d8adc460ab71328a5f570491bb24bf GIT binary patch literal 29815 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 literal 0 HcmV?d00001 From 61ef95c21086b260bae2f6779a2bda124d094f9d Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Wed, 19 Apr 2023 15:52:30 -0400 Subject: [PATCH 03/17] Address PR feedback and add issues --- cmd/opampsupervisor/go.mod | 1 - cmd/opampsupervisor/go.sum | 2 -- .../supervisor/commander/commander.go | 1 + cmd/opampsupervisor/supervisor/supervisor.go | 30 ++++++++++++++++--- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/cmd/opampsupervisor/go.mod b/cmd/opampsupervisor/go.mod index 5f2043add89f..587f9d82e2a3 100644 --- a/cmd/opampsupervisor/go.mod +++ b/cmd/opampsupervisor/go.mod @@ -7,7 +7,6 @@ require ( github.com/knadh/koanf v1.5.0 github.com/oklog/ulid/v2 v2.1.0 github.com/open-telemetry/opamp-go v0.6.0 - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.13 go.uber.org/atomic v1.7.0 go.uber.org/zap v1.17.0 ) diff --git a/cmd/opampsupervisor/go.sum b/cmd/opampsupervisor/go.sum index a13b33da6055..85fe87f978a4 100644 --- a/cmd/opampsupervisor/go.sum +++ b/cmd/opampsupervisor/go.sum @@ -228,8 +228,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L 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/scaleway/scaleway-sdk-go v1.0.0-beta.13 h1:n5J2K6g/kl/iT6mODjCoSoRBGQVmIG3aMtYbofi9kxc= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.13/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index a41768afbc5c..fbdf732b918b 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -68,6 +68,7 @@ func (c *Commander) Start(ctx context.Context) error { c.cmd = exec.CommandContext(ctx, c.cfg.Executable, c.args...) // #nosec G204 // Capture standard output and standard error. + // https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21072 c.cmd.Stdout = logFile c.cmd.Stderr = logFile diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go index 050ebaf3dc49..4f7bc9c96265 100644 --- a/cmd/opampsupervisor/supervisor/supervisor.go +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -71,6 +71,7 @@ type Supervisor struct { // A config section to be added to the Collector's config to fetch its own metrics. // TODO: store this persistently so that when starting we can compose the effective // config correctly. + // https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21078 agentConfigOwnMetricsSection atomic.Value // agentHealthCheckEndpoint is the endpoint the Collector's health check extension @@ -158,6 +159,7 @@ func (s *Supervisor) loadConfig(configFile string) error { return nil } +// TODO: Implement bootstrapping https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21071 func (s *Supervisor) getBootstrapInfo() (err error) { s.agentVersion = "1.0.0" @@ -180,10 +182,30 @@ func (s *Supervisor) startOpAMP() error { OnErrorFunc: func(err *protobufs.ServerErrorResponse) { s.logger.Error("Server returned an error response", zap.String("message", err.ErrorMessage)) }, + OnMessageFunc: s.onMessage, + OnOpampConnectionSettingsFunc: func(ctx context.Context, settings *protobufs.OpAMPConnectionSettings) error { + // TODO: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21043 + s.logger.Debug("Received ConnectionSettings request") + return nil + }, + OnOpampConnectionSettingsAcceptedFunc: func(settings *protobufs.OpAMPConnectionSettings) { + // TODO: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21043 + s.logger.Debug("ConnectionSettings accepted") + }, + OnCommandFunc: func(command *protobufs.ServerToAgentCommand) error { + cmdType := command.GetType() + if *cmdType.Enum() == protobufs.CommandType_CommandType_Restart { + // TODO: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21077 + s.logger.Debug("Received restart command") + } + return nil + }, + SaveRemoteConfigStatusFunc: func(ctx context.Context, status *protobufs.RemoteConfigStatus) { + // TODO: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21079 + }, GetEffectiveConfigFunc: func(ctx context.Context) (*protobufs.EffectiveConfig, error) { return s.createEffectiveConfigMsg(), nil }, - OnMessageFunc: s.onMessage, }, // TODO: Make capabilities configurable Capabilities: protobufs.AgentCapabilities_AgentCapabilities_AcceptsRemoteConfig | @@ -215,11 +237,10 @@ func (s *Supervisor) startOpAMP() error { } func (s *Supervisor) createInstanceID() { - // Generate instance id. entropy := ulid.Monotonic(rand.New(rand.NewSource(0)), 0) s.instanceID = ulid.MustNew(ulid.Timestamp(time.Now()), entropy) - // TODO: Persist instance ID. + // TODO: Persist instance ID. https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21073 } func keyVal(key, val string) *protobufs.KeyValue { @@ -234,11 +255,11 @@ func keyVal(key, val string) *protobufs.KeyValue { func (s *Supervisor) createAgentDescription() *protobufs.AgentDescription { hostname, _ := os.Hostname() - // Create Agent description. return &protobufs.AgentDescription{ IdentifyingAttributes: []*protobufs.KeyValue{ keyVal("service.name", agentType), keyVal("service.version", s.agentVersion), + keyVal("service.instance.id", s.instanceID.String()), }, NonIdentifyingAttributes: []*protobufs.KeyValue{ keyVal("os.family", runtime.GOOS), @@ -546,6 +567,7 @@ func (s *Supervisor) runAgentProcess() { } // TODO: decide why the agent stopped. If it was due to bad config, report it to server. + // https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21079 // Wait 5 seconds before starting again. restartTimer.Stop() From f759c9b38991c28a270ebf63b3d0bd21d6cf5609 Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Fri, 21 Apr 2023 09:30:13 -0400 Subject: [PATCH 04/17] Fix lint issues --- .github/dependabot.yml | 5 ++++ cmd/opampsupervisor/go.mod | 4 +-- cmd/opampsupervisor/go.sum | 6 +++-- cmd/opampsupervisor/main.go | 3 ++- .../supervisor/commander/commander.go | 6 ++--- cmd/opampsupervisor/supervisor/supervisor.go | 26 ++++++++++++------- versions.yaml | 1 + 7 files changed, 33 insertions(+), 18 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3d76b7033704..4b16be9fd7f9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,6 +17,11 @@ updates: schedule: interval: "weekly" day: "wednesday" + - package-ecosystem: "gomod" + directory: "/cmd/opampsupervisor" + schedule: + interval: "weekly" + day: "wednesday" - package-ecosystem: "gomod" directory: "/cmd/otelcontribcol" schedule: diff --git a/cmd/opampsupervisor/go.mod b/cmd/opampsupervisor/go.mod index 587f9d82e2a3..f904fe846433 100644 --- a/cmd/opampsupervisor/go.mod +++ b/cmd/opampsupervisor/go.mod @@ -7,8 +7,7 @@ require ( github.com/knadh/koanf v1.5.0 github.com/oklog/ulid/v2 v2.1.0 github.com/open-telemetry/opamp-go v0.6.0 - go.uber.org/atomic v1.7.0 - go.uber.org/zap v1.17.0 + go.uber.org/zap v1.24.0 ) require ( @@ -20,6 +19,7 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/testify v1.8.2 // indirect + go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/sys v0.5.0 // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/cmd/opampsupervisor/go.sum b/cmd/opampsupervisor/go.sum index 85fe87f978a4..dd758b3fcf0c 100644 --- a/cmd/opampsupervisor/go.sum +++ b/cmd/opampsupervisor/go.sum @@ -21,6 +21,7 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72H github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 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= @@ -255,10 +256,12 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 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 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= @@ -401,7 +404,6 @@ 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/cmd/opampsupervisor/main.go b/cmd/opampsupervisor/main.go index 697ab8fe1a8b..62ffb6361026 100644 --- a/cmd/opampsupervisor/main.go +++ b/cmd/opampsupervisor/main.go @@ -19,8 +19,9 @@ import ( "os" "os/signal" - "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor" "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor" ) func main() { diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index fbdf732b918b..3fdee44661eb 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -20,10 +20,10 @@ import ( "fmt" "os" "os/exec" + "sync/atomic" "syscall" "time" - "go.uber.org/atomic" "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor/config" @@ -50,7 +50,7 @@ func NewCommander(logger *zap.Logger, cfg *config.Agent, args ...string) (*Comma logger: logger, cfg: cfg, args: args, - running: atomic.NewInt64(0), + running: &atomic.Int64{}, }, nil } @@ -103,7 +103,7 @@ func (c *Commander) watch() { // cmd.Wait returns an exec.ExitError when the Collector exits unsuccessfully or stops // after receiving a signal. The Commander caller will handle these cases, so we filter // them out here. - if _, ok := err.(*exec.ExitError); err != nil && !ok { + if ok := errors.Is(err, &exec.ExitError{}); err != nil && !ok { c.logger.Error("An error occurred while watching the agent process", zap.Error(err)) } diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go index 4f7bc9c96265..db817015782c 100644 --- a/cmd/opampsupervisor/supervisor/supervisor.go +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -23,6 +23,7 @@ import ( "os" "runtime" "sort" + "sync/atomic" "time" "github.com/cenkalti/backoff/v4" @@ -34,7 +35,6 @@ import ( "github.com/open-telemetry/opamp-go/client" "github.com/open-telemetry/opamp-go/client/types" "github.com/open-telemetry/opamp-go/protobufs" - "go.uber.org/atomic" "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor/commander" @@ -72,14 +72,14 @@ type Supervisor struct { // TODO: store this persistently so that when starting we can compose the effective // config correctly. // https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21078 - agentConfigOwnMetricsSection atomic.Value + agentConfigOwnMetricsSection *atomic.Value // agentHealthCheckEndpoint is the endpoint the Collector's health check extension // will listen on for health check requests from the Supervisor. agentHealthCheckEndpoint string // Final effective config of the Collector. - effectiveConfig atomic.Value + effectiveConfig *atomic.Value // Location of the effective config file. effectiveConfigFilePath string @@ -96,9 +96,11 @@ type Supervisor struct { func NewSupervisor(logger *zap.Logger, configFile string) (*Supervisor, error) { s := &Supervisor{ - logger: logger, - hasNewConfig: make(chan struct{}, 1), - effectiveConfigFilePath: "effective.yaml", + logger: logger, + hasNewConfig: make(chan struct{}, 1), + effectiveConfigFilePath: "effective.yaml", + agentConfigOwnMetricsSection: &atomic.Value{}, + effectiveConfig: &atomic.Value{}, } if err := s.loadConfig(configFile); err != nil { @@ -123,7 +125,7 @@ func NewSupervisor(logger *zap.Logger, configFile string) (*Supervisor, error) { s.loadAgentEffectiveConfig() - if err := s.startOpAMP(); err != nil { + if err = s.startOpAMP(); err != nil { return nil, fmt.Errorf("cannot start OpAMP client: %w", err) } @@ -160,6 +162,7 @@ func (s *Supervisor) loadConfig(configFile string) error { } // TODO: Implement bootstrapping https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21071 +// nolint: unparam func (s *Supervisor) getBootstrapInfo() (err error) { s.agentVersion = "1.0.0" @@ -475,7 +478,7 @@ func (s *Supervisor) startAgent() { err := s.commander.Start(context.Background()) if err != nil { s.logger.Error("Cannot start the agent", zap.Error(err)) - err := s.opampClient.SetHealth(&protobufs.AgentHealth{Healthy: false, LastError: fmt.Sprintf("Cannot start the agent: %v", err)}) + err = s.opampClient.SetHealth(&protobufs.AgentHealth{Healthy: false, LastError: fmt.Sprintf("Cannot start the agent: %v", err)}) if err != nil { s.logger.Error("Failed to report OpAMP client health", zap.Error(err)) @@ -678,7 +681,10 @@ func (s *Supervisor) onMessage(ctx context.Context, msg *types.MessageData) { zap.String("old_id", s.instanceID.String()), zap.String("new_id", newInstanceID.String())) s.instanceID = newInstanceID - s.opampClient.SetAgentDescription(s.createAgentDescription()) + err = s.opampClient.SetAgentDescription(s.createAgentDescription()) + if err != nil { + s.logger.Error("Failed to send agent description to OpAMP server") + } configChanged = true } @@ -699,7 +705,7 @@ func (s *Supervisor) onMessage(ctx context.Context, msg *types.MessageData) { } func (s *Supervisor) findRandomPort() (int, error) { - l, err := net.Listen("tcp", ":0") + l, err := net.Listen("tcp", "localhost:0") if err != nil { return 0, err diff --git a/versions.yaml b/versions.yaml index 1b3f9a824697..3e10a94d55db 100644 --- a/versions.yaml +++ b/versions.yaml @@ -8,6 +8,7 @@ module-sets: - github.com/open-telemetry/opentelemetry-collector-contrib - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/configschema - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/mdatagen + - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor - github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen - github.com/open-telemetry/opentelemetry-collector-contrib/confmap/provider/s3provider - github.com/open-telemetry/opentelemetry-collector-contrib/connector/countconnector From d5cddf6530fc4aee7336fcc24a80bcd753020e23 Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Tue, 30 May 2023 13:49:30 -0400 Subject: [PATCH 05/17] Fix error handling --- cmd/opampsupervisor/supervisor/commander/commander.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index 3fdee44661eb..5c8c9297475a 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -103,7 +103,8 @@ func (c *Commander) watch() { // cmd.Wait returns an exec.ExitError when the Collector exits unsuccessfully or stops // after receiving a signal. The Commander caller will handle these cases, so we filter // them out here. - if ok := errors.Is(err, &exec.ExitError{}); err != nil && !ok { + var exitError *exec.ExitError + if ok := errors.As(err, &exitError); err != nil && !ok { c.logger.Error("An error occurred while watching the agent process", zap.Error(err)) } From 1db1af9ee56e132ff2cac8c3e1dadb0cd150ac0a Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Tue, 30 May 2023 13:49:56 -0400 Subject: [PATCH 06/17] Remove unnecessary channel --- .../supervisor/commander/commander.go | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index 5c8c9297475a..56f24139b700 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -154,21 +154,15 @@ func (c *Commander) Stop(ctx context.Context) error { return err } - finished := make(chan struct{}) + waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second) // Setup a goroutine to wait a while for process to finish and send kill signal // to the process if it doesn't finish. var innerErr error go func() { - // Wait 10 seconds. - t := time.After(10 * time.Second) - select { - case <-ctx.Done(): - break - case <-t: - break - case <-finished: - // Process is successfully finished. + <-waitCtx.Done() + + if waitCtx.Err() != context.DeadlineExceeded { c.logger.Debug("Agent process successfully stopped.", zap.Int("pid", c.cmd.Process.Pid)) return } @@ -188,7 +182,7 @@ func (c *Commander) Stop(ctx context.Context) error { c.running.Store(0) // Let goroutine know process is finished. - close(finished) + cancel() return innerErr } From 67ec60ceb1b164db3fb7ea38ca34535864c395d2 Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Tue, 30 May 2023 13:50:35 -0400 Subject: [PATCH 07/17] Get rid of process startup/shutdown timing errors --- cmd/opampsupervisor/supervisor/supervisor.go | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go index db817015782c..3e4f57b8a6f4 100644 --- a/cmd/opampsupervisor/supervisor/supervisor.go +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -92,6 +92,11 @@ type Supervisor struct { // The OpAMP client to connect to the OpAMP Server. opampClient client.OpAMPClient + + shuttingDown bool + + agentHasStarted bool + agentStartHealthCheckAttempts int } func NewSupervisor(logger *zap.Logger, configFile string) (*Supervisor, error) { @@ -486,6 +491,9 @@ func (s *Supervisor) startAgent() { return } + + s.agentHasStarted = false + s.agentStartHealthCheckAttempts = 0 s.startedAt = time.Now() s.startHealthCheckTicker() @@ -525,9 +533,15 @@ func (s *Supervisor) healthCheck() { if err != nil { health.Healthy = false - health.LastError = err.Error() - s.logger.Error("Agent is not healthy", zap.Error(err)) + if !s.agentHasStarted && s.agentStartHealthCheckAttempts < 10 { + health.LastError = "Agent is starting" + s.agentStartHealthCheckAttempts += 1 + } else { + health.LastError = err.Error() + s.logger.Error("Agent is not healthy", zap.Error(err)) + } } else { + s.agentHasStarted = true health.Healthy = true s.logger.Debug("Agent is healthy.") } @@ -558,6 +572,10 @@ func (s *Supervisor) runAgentProcess() { s.startAgent() case <-s.commander.Done(): + if s.shuttingDown { + break + } + s.logger.Debug("Agent process exited unexpectedly. Will restart in a bit...", zap.Int("pid", s.commander.Pid()), zap.Int("exit_code", s.commander.ExitCode())) errMsg := fmt.Sprintf( "Agent process PID=%d exited unexpectedly, exit code=%d. Will restart in a bit...", @@ -613,6 +631,7 @@ func (s *Supervisor) writeEffectiveConfigToFile(cfg string, filePath string) { func (s *Supervisor) Shutdown() { s.logger.Debug("Supervisor shutting down...") + s.shuttingDown = true if s.commander != nil { err := s.commander.Stop(context.Background()) @@ -620,6 +639,7 @@ func (s *Supervisor) Shutdown() { s.logger.Error("Could not stop agent process", zap.Error(err)) } } + if s.opampClient != nil { err := s.opampClient.SetHealth( &protobufs.AgentHealth{ From fda423714b315143e95f3819791045909d181c5b Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Tue, 30 May 2023 13:54:19 -0400 Subject: [PATCH 08/17] Remove waitCh --- cmd/opampsupervisor/supervisor/commander/commander.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index 56f24139b700..e5beef5a6e9e 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -37,7 +37,6 @@ type Commander struct { args []string cmd *exec.Cmd doneCh chan struct{} - waitCh chan struct{} running *atomic.Int64 } @@ -72,8 +71,7 @@ func (c *Commander) Start(ctx context.Context) error { c.cmd.Stdout = logFile c.cmd.Stderr = logFile - c.doneCh = make(chan struct{}, 1) - c.waitCh = make(chan struct{}) + c.doneCh = make(chan struct{}) if err := c.cmd.Start(); err != nil { return err @@ -108,9 +106,8 @@ func (c *Commander) watch() { c.logger.Error("An error occurred while watching the agent process", zap.Error(err)) } - c.doneCh <- struct{}{} c.running.Store(0) - close(c.waitCh) + close(c.doneCh) } // Done returns a channel that will send a signal when the Agent process is finished. @@ -177,7 +174,7 @@ func (c *Commander) Stop(ctx context.Context) error { }() // Wait for process to terminate - <-c.waitCh + <-c.doneCh c.running.Store(0) From e7b84a402465f6531b9b03454476783603eb5e20 Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Thu, 1 Jun 2023 08:36:13 -0400 Subject: [PATCH 09/17] Update cmd/opampsupervisor/specification/README.md Co-authored-by: Matej Gera <38492574+matej-g@users.noreply.github.com> --- cmd/opampsupervisor/specification/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/opampsupervisor/specification/README.md b/cmd/opampsupervisor/specification/README.md index 0d1b766f8976..4a644a12c214 100644 --- a/cmd/opampsupervisor/specification/README.md +++ b/cmd/opampsupervisor/specification/README.md @@ -126,10 +126,11 @@ collector: # extra command line flags to pass to the Collector executable. args: - # extra environment variables to set when executing the Collector + # Extra environment variables to set when executing the Collector. + env: + # Optional user name to drop the privileges to when running the # Collector process. - env: run_as: myuser # Path to optional local Collector config file to be merged with the # config provided by the OpAMP server. From fb9dd4f42f36b73ee1672792306da0554fb72027 Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Thu, 1 Jun 2023 08:36:25 -0400 Subject: [PATCH 10/17] Update cmd/opampsupervisor/specification/README.md Co-authored-by: Matej Gera <38492574+matej-g@users.noreply.github.com> --- cmd/opampsupervisor/specification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/opampsupervisor/specification/README.md b/cmd/opampsupervisor/specification/README.md index 4a644a12c214..5ca8f9f62889 100644 --- a/cmd/opampsupervisor/specification/README.md +++ b/cmd/opampsupervisor/specification/README.md @@ -123,7 +123,7 @@ collector: # Path to Collector executable. Required. executable: /opt/otelcol/bin/otelcol - # extra command line flags to pass to the Collector executable. + # Extra command line flags to pass to the Collector executable. args: # Extra environment variables to set when executing the Collector. From 0e4e033e1109f53306d36932758474230c84950b Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Thu, 1 Jun 2023 09:18:38 -0400 Subject: [PATCH 11/17] Address PR feedback --- cmd/opampsupervisor/supervisor/commander/commander.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index e5beef5a6e9e..f2fc27fa6628 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -55,7 +55,14 @@ func NewCommander(logger *zap.Logger, cfg *config.Agent, args ...string) (*Comma // Start the Agent and begin watching the process. // Agent's stdout and stderr are written to a file. +// Calling this method when a command is already running +// is a no-op. func (c *Commander) Start(ctx context.Context) error { + if c.running.Load() == 1 { + // Already started, nothing to do + return nil + } + c.logger.Debug("Starting agent", zap.String("agent", c.cfg.Executable)) logFilePath := "agent.log" @@ -139,7 +146,7 @@ func (c *Commander) IsRunning() bool { // and if the process does not finish kills it forcedly by sending SIGKILL. // Returns after the process is terminated. func (c *Commander) Stop(ctx context.Context) error { - if c.cmd == nil || c.cmd.Process == nil { + if c.running.Load() == 0 { // Not started, nothing to do. return nil } From 5a24be9b59d3c32128a35067d1d489e13a10f3da Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Thu, 1 Jun 2023 14:20:31 -0400 Subject: [PATCH 12/17] Fix lint --- cmd/opampsupervisor/main.go | 13 +------------ .../supervisor/commander/commander.go | 13 +------------ cmd/opampsupervisor/supervisor/config/config.go | 13 +------------ .../supervisor/healthchecker/healthchecker.go | 13 +------------ cmd/opampsupervisor/supervisor/opamplogger.go | 13 +------------ cmd/opampsupervisor/supervisor/supervisor.go | 13 +------------ 6 files changed, 6 insertions(+), 72 deletions(-) diff --git a/cmd/opampsupervisor/main.go b/cmd/opampsupervisor/main.go index 62ffb6361026..b93579842c09 100644 --- a/cmd/opampsupervisor/main.go +++ b/cmd/opampsupervisor/main.go @@ -1,16 +1,5 @@ // 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. +// SPDX-License-Identifier: Apache-2.0 package main diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index f2fc27fa6628..ec556d366f6b 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -1,16 +1,5 @@ // 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. +// SPDX-License-Identifier: Apache-2.0 package commander diff --git a/cmd/opampsupervisor/supervisor/config/config.go b/cmd/opampsupervisor/supervisor/config/config.go index dd3ad391375d..31edfce10f2e 100644 --- a/cmd/opampsupervisor/supervisor/config/config.go +++ b/cmd/opampsupervisor/supervisor/config/config.go @@ -1,16 +1,5 @@ // 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. +// SPDX-License-Identifier: Apache-2.0 package config diff --git a/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go b/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go index 3e50baf3b2ef..045236fd843b 100644 --- a/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go +++ b/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go @@ -1,16 +1,5 @@ // 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. +// SPDX-License-Identifier: Apache-2.0 package healthchecker diff --git a/cmd/opampsupervisor/supervisor/opamplogger.go b/cmd/opampsupervisor/supervisor/opamplogger.go index 00f8c1c9fd63..82101f07232a 100644 --- a/cmd/opampsupervisor/supervisor/opamplogger.go +++ b/cmd/opampsupervisor/supervisor/opamplogger.go @@ -1,16 +1,5 @@ // 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. +// SPDX-License-Identifier: Apache-2.0 package supervisor diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go index 3e4f57b8a6f4..8b6991f7834b 100644 --- a/cmd/opampsupervisor/supervisor/supervisor.go +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -1,16 +1,5 @@ // 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. +// SPDX-License-Identifier: Apache-2.0 package supervisor From 007abef0001fdbe384298bb75f209c6008457933 Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Thu, 1 Jun 2023 15:23:43 -0400 Subject: [PATCH 13/17] Fix lint --- cmd/opampsupervisor/supervisor/commander/commander.go | 2 +- cmd/opampsupervisor/supervisor/supervisor.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index ec556d366f6b..6a937dcd752a 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -155,7 +155,7 @@ func (c *Commander) Stop(ctx context.Context) error { go func() { <-waitCtx.Done() - if waitCtx.Err() != context.DeadlineExceeded { + if !errors.Is(waitCtx.Err(), context.DeadlineExceeded) { c.logger.Debug("Agent process successfully stopped.", zap.Int("pid", c.cmd.Process.Pid)) return } diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go index 8b6991f7834b..8c1da51d3385 100644 --- a/cmd/opampsupervisor/supervisor/supervisor.go +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -524,7 +524,7 @@ func (s *Supervisor) healthCheck() { health.Healthy = false if !s.agentHasStarted && s.agentStartHealthCheckAttempts < 10 { health.LastError = "Agent is starting" - s.agentStartHealthCheckAttempts += 1 + s.agentStartHealthCheckAttempts++ } else { health.LastError = err.Error() s.logger.Error("Agent is not healthy", zap.Error(err)) From 082bed1476d8efa41289044242b249f40f7bf37f Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Thu, 1 Jun 2023 15:55:15 -0400 Subject: [PATCH 14/17] Fix lint --- cmd/opampsupervisor/supervisor/commander/commander.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/opampsupervisor/supervisor/commander/commander.go b/cmd/opampsupervisor/supervisor/commander/commander.go index 6a937dcd752a..23ef147343b6 100644 --- a/cmd/opampsupervisor/supervisor/commander/commander.go +++ b/cmd/opampsupervisor/supervisor/commander/commander.go @@ -85,10 +85,8 @@ func (c *Commander) Restart(ctx context.Context) error { if err := c.Stop(ctx); err != nil { return err } - if err := c.Start(ctx); err != nil { - return err - } - return nil + + return c.Start(ctx) } func (c *Commander) watch() { From 4fbeefc901d42ef70b1312cb835c39ef5b15ac7f Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Tue, 13 Jun 2023 13:07:33 -0400 Subject: [PATCH 15/17] Address PR feedback --- .../supervisor/healthchecker/healthchecker.go | 2 ++ cmd/opampsupervisor/supervisor/supervisor.go | 21 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go b/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go index 045236fd843b..e941284daef3 100644 --- a/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go +++ b/cmd/opampsupervisor/supervisor/healthchecker/healthchecker.go @@ -10,6 +10,8 @@ import ( "time" ) +// TODO: Support more settings +// https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/23344 type HTTPHealthChecker struct { endpoint string } diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go index 8c1da51d3385..014e8f822827 100644 --- a/cmd/opampsupervisor/supervisor/supervisor.go +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -113,7 +113,14 @@ func NewSupervisor(logger *zap.Logger, configFile string) (*Supervisor, error) { s.agentHealthCheckEndpoint = fmt.Sprintf("localhost:%d", port) - s.createInstanceID() + id, err := s.createInstanceID() + + if err != nil { + return nil, err + } + + s.instanceID = id + logger.Debug("Supervisor starting", zap.String("id", s.instanceID.String()), zap.String("type", agentType), zap.String("version", s.agentVersion)) @@ -233,11 +240,17 @@ func (s *Supervisor) startOpAMP() error { return nil } -func (s *Supervisor) createInstanceID() { +// TODO: Persist instance ID. https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21073 +func (s *Supervisor) createInstanceID() (ulid.ULID, error) { entropy := ulid.Monotonic(rand.New(rand.NewSource(0)), 0) - s.instanceID = ulid.MustNew(ulid.Timestamp(time.Now()), entropy) + id, err := ulid.New(ulid.Timestamp(time.Now()), entropy) + + if err != nil { + return ulid.ULID{}, err + } + + return id, nil - // TODO: Persist instance ID. https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/21073 } func keyVal(key, val string) *protobufs.KeyValue { From 95329e14158456f8d69bac3f08284e7dbcaa0c35 Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Tue, 13 Jun 2023 13:18:19 -0400 Subject: [PATCH 16/17] Switch to use zap.SugaredLogger for the OpAMP client's logger --- cmd/opampsupervisor/supervisor/opamplogger.go | 27 ------------------- cmd/opampsupervisor/supervisor/supervisor.go | 2 +- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 cmd/opampsupervisor/supervisor/opamplogger.go diff --git a/cmd/opampsupervisor/supervisor/opamplogger.go b/cmd/opampsupervisor/supervisor/opamplogger.go deleted file mode 100644 index 82101f07232a..000000000000 --- a/cmd/opampsupervisor/supervisor/opamplogger.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package supervisor - -import ( - "fmt" - - "github.com/open-telemetry/opamp-go/client/types" - "go.uber.org/zap" -) - -// opAMPLogger adapts the Supervisor's zap.Logger instance so it -// can be used by the OpAMP client. -type opAMPLogger struct { - logger *zap.Logger -} - -func (l opAMPLogger) Debugf(format string, v ...interface{}) { - l.logger.Debug(fmt.Sprintf(format, v...)) -} - -func (l opAMPLogger) Errorf(format string, v ...interface{}) { - l.logger.Error(fmt.Sprintf(format, v...)) -} - -var _ types.Logger = opAMPLogger{} diff --git a/cmd/opampsupervisor/supervisor/supervisor.go b/cmd/opampsupervisor/supervisor/supervisor.go index 014e8f822827..4287e2c88c23 100644 --- a/cmd/opampsupervisor/supervisor/supervisor.go +++ b/cmd/opampsupervisor/supervisor/supervisor.go @@ -171,7 +171,7 @@ func (s *Supervisor) getBootstrapInfo() (err error) { } func (s *Supervisor) startOpAMP() error { - s.opampClient = client.NewWebSocket(opAMPLogger{logger: s.logger}) + s.opampClient = client.NewWebSocket(s.logger.Sugar()) settings := types.StartSettings{ OpAMPServerURL: s.config.Server.Endpoint, From ea0ed54465c8e881f18db849a50b7be2e80bbfc1 Mon Sep 17 00:00:00 2001 From: Evan Bradley Date: Tue, 13 Jun 2023 19:05:25 -0400 Subject: [PATCH 17/17] make gendependabot --- .github/dependabot.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4b16be9fd7f9..472a1cb63107 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1102,8 +1102,3 @@ updates: schedule: interval: "weekly" day: "wednesday" - - package-ecosystem: "gomod" - directory: "/receiver/tcplogreceiver" - schedule: - interval: "weekly" - day: "wednesday"