From 8fc1b2684f91f445df2567b3153a95d07cf8970b Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 18 Jan 2024 19:24:04 -0800 Subject: [PATCH 01/26] Introduce health check extension based on component status reporting --- .chloggen/healthcheck-v2.yaml | 27 + .github/CODEOWNERS | 6 +- cmd/otelcontribcol/components.go | 8 + extension/healthcheckextensionv2/Makefile | 1 + extension/healthcheckextensionv2/README.md | 337 +++++++ extension/healthcheckextensionv2/config.go | 36 + .../healthcheckextensionv2/config_test.go | 91 ++ extension/healthcheckextensionv2/extension.go | 190 ++++ .../healthcheckextensionv2/extension_test.go | 122 +++ extension/healthcheckextensionv2/factory.go | 57 ++ .../healthcheckextensionv2/factory_test.go | 53 ++ extension/healthcheckextensionv2/go.mod | 79 ++ extension/healthcheckextensionv2/go.sum | 639 +++++++++++++ .../internal/grpc/config.go | 11 + .../internal/grpc/grpc.go | 120 +++ .../internal/grpc/grpc_test.go | 438 +++++++++ .../internal/grpc/server.go | 80 ++ .../internal/http/config.go | 23 + .../internal/http/handlers.go | 98 ++ .../internal/http/serialization.go | 155 +++ .../internal/http/server.go | 107 +++ .../internal/http/server_test.go | 890 ++++++++++++++++++ .../internal/http/testdata/config.json | 1 + .../internal/http/testdata/config.yaml | 23 + .../internal/metadata/generated_status.go | 22 + .../internal/status/aggregator.go | 277 ++++++ .../internal/status/aggregator_test.go | 351 +++++++ .../internal/testhelpers/helpers.go | 75 ++ .../healthcheckextensionv2/metadata.yaml | 9 + reports/distributions/contrib.yaml | 1 + 30 files changed, 4324 insertions(+), 3 deletions(-) create mode 100755 .chloggen/healthcheck-v2.yaml create mode 100644 extension/healthcheckextensionv2/Makefile create mode 100644 extension/healthcheckextensionv2/README.md create mode 100644 extension/healthcheckextensionv2/config.go create mode 100644 extension/healthcheckextensionv2/config_test.go create mode 100644 extension/healthcheckextensionv2/extension.go create mode 100644 extension/healthcheckextensionv2/extension_test.go create mode 100644 extension/healthcheckextensionv2/factory.go create mode 100644 extension/healthcheckextensionv2/factory_test.go create mode 100644 extension/healthcheckextensionv2/go.mod create mode 100644 extension/healthcheckextensionv2/go.sum create mode 100644 extension/healthcheckextensionv2/internal/grpc/config.go create mode 100644 extension/healthcheckextensionv2/internal/grpc/grpc.go create mode 100644 extension/healthcheckextensionv2/internal/grpc/grpc_test.go create mode 100644 extension/healthcheckextensionv2/internal/grpc/server.go create mode 100644 extension/healthcheckextensionv2/internal/http/config.go create mode 100644 extension/healthcheckextensionv2/internal/http/handlers.go create mode 100644 extension/healthcheckextensionv2/internal/http/serialization.go create mode 100644 extension/healthcheckextensionv2/internal/http/server.go create mode 100644 extension/healthcheckextensionv2/internal/http/server_test.go create mode 100644 extension/healthcheckextensionv2/internal/http/testdata/config.json create mode 100644 extension/healthcheckextensionv2/internal/http/testdata/config.yaml create mode 100644 extension/healthcheckextensionv2/internal/metadata/generated_status.go create mode 100644 extension/healthcheckextensionv2/internal/status/aggregator.go create mode 100644 extension/healthcheckextensionv2/internal/status/aggregator_test.go create mode 100644 extension/healthcheckextensionv2/internal/testhelpers/helpers.go create mode 100644 extension/healthcheckextensionv2/metadata.yaml diff --git a/.chloggen/healthcheck-v2.yaml b/.chloggen/healthcheck-v2.yaml new file mode 100755 index 0000000000000..4fd7e5a381161 --- /dev/null +++ b/.chloggen/healthcheck-v2.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: 'new_component' + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: healthcheckextensionv2 + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Health Check Extension V2 is meant to be a replacement for the current Health Check Extension. It is based off of component status reporting and provides HTTP and gRPC services health check services. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [26661] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5404b611eebed..b323923b0090a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -285,10 +285,10 @@ testbed/mockdatasenders/mockdatadogagentexporter/ @open-telemetry/collect # List of distribution maintainers for OpenTelemetry Collector Contrib # ##################################################### -reports/distributions/core.yaml @open-telemetry/collector-contrib-approvers -reports/distributions/contrib.yaml @open-telemetry/collector-contrib-approvers +reports/distributions/core.yaml @open-telemetry/collector-contrib-approvers +reports/distributions/contrib.yaml @open-telemetry/collector-contrib-approvers ## UNMAINTAINED components -exporter/skywalkingexporter/ @open-telemetry/collector-contrib-approvers +exporter/skywalkingexporter/ @open-telemetry/collector-contrib-approvers diff --git a/cmd/otelcontribcol/components.go b/cmd/otelcontribcol/components.go index 760cc2346b5b6..6f3096dedfd7c 100644 --- a/cmd/otelcontribcol/components.go +++ b/cmd/otelcontribcol/components.go @@ -85,7 +85,11 @@ import ( googleclientauthextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/googleclientauthextension" headerssetterextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/headerssetterextension" healthcheckextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextension" +<<<<<<< HEAD healthcheckv2extension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension" +======= + healthcheckextensionv2 "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" +>>>>>>> 9e2ab3fdae (Introduce health check extension based on component status reporting) httpforwarderextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/httpforwarderextension" jaegerremotesampling "github.com/open-telemetry/opentelemetry-collector-contrib/extension/jaegerremotesampling" oauth2clientauthextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension" @@ -226,7 +230,11 @@ func components() (otelcol.Factories, error) { googleclientauthextension.NewFactory(), headerssetterextension.NewFactory(), healthcheckextension.NewFactory(), +<<<<<<< HEAD healthcheckv2extension.NewFactory(), +======= + healthcheckextensionv2.NewFactory(), +>>>>>>> 9e2ab3fdae (Introduce health check extension based on component status reporting) httpforwarderextension.NewFactory(), jaegerremotesampling.NewFactory(), oauth2clientauthextension.NewFactory(), diff --git a/extension/healthcheckextensionv2/Makefile b/extension/healthcheckextensionv2/Makefile new file mode 100644 index 0000000000000..ded7a36092dc3 --- /dev/null +++ b/extension/healthcheckextensionv2/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/extension/healthcheckextensionv2/README.md b/extension/healthcheckextensionv2/README.md new file mode 100644 index 0000000000000..792b9c63122f0 --- /dev/null +++ b/extension/healthcheckextensionv2/README.md @@ -0,0 +1,337 @@ +# Health Check Extension - V2 + + +| Status | | +| ------------- |-----------| +| Stability | [development] | +| Distributions | [contrib] | +| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fhealthcheckextensionv2%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fhealthcheckextensionv2) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fhealthcheckextensionv2%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fhealthcheckextensionv2) | +| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@mwear](https://www.github.com/mwear) | + +[development]: https://github.com/open-telemetry/opentelemetry-collector#development +[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib + + +Health Check Extension - V2 provides HTTP and gRPC healthcheck services. The services can be used +separately or together depending on your needs. The source of health for both services is component +status reporting, a collector feature, that allows individual components to report their health via +`StatusEvent`s. The health check extension aggregates the component `StatusEvent`s into overall +collector health and pipeline health and exposes this data through its services. + +Below is a table enumerating component statuses and their meanings. These will be mapped to +appropriate status codes for the protocol. + +| Status | Meaning | +|-------------------|------------------------------------------------------------------------| +| Starting | The component is starting. | +| OK | The component is running without issue. | +| RecoverableError | The component has experienced a transient error and may recover. | +| PermanentError | The component has detected a condition at runtime that will need human intervention to fix. The collector will continue to run in a degraded mode. | +| FatalError | The collector has experienced a fatal runtime error and will shutdown. | +| Stopping | The component is in the process of shutting down. | +| Stopped | The component has completed shutdown. | + +Adoption of status reporting by collector components is still a work in progress. As more components +report status, this extension will become more accurate and useful. + +## Configuration + +Below is sample configuration for both the HTTP and gRPC services. + +```yaml +extensions: + healthcheckv2: + recovery_duration: 1m + http: + endpoint: "127.0.0.1:13133" + status: + detailed: true + enabled: true + path: "/health/status" + config: + enabled: true + path: "/health/config" + grpc: + endpoint: "127.0.0.1:13132" + transport: "tcp" +``` + +### Recovery Duration + +Recovery duration is a setting shared by both the HTTP and gRPC services. Component status reporting +has a `RecoverableError` status to indicate a likely transient failure. The recovery duration is the +time given for a `RecoverableError` to recover before being considered unhealthy by the extension. +During the recovery duration a `RecoverableError` will be considered healthy. If the error does not +recover after the recovery duration has elapsed, it will be considered unhealthy. + +## HTTP Service + +### Status Endpoint + +The HTTP service provides a status endpoint that can be probed for overall collector status and +per-pipeline status. The endpoint is located at `/status` by default, but can be configured using +the `http.status.path` setting. Requests to `/status` will return the overall collector status. To +probe pipeline status, pass the pipeline name as a query parameter, e.g. `/status?pipeline=traces`. +The HTTP status code returned maps to the overall collector or pipeline status, with the mapping +described below. + +#### Mapping of Component Status to HTTP Status + +Component statuses are aggregated into overall collector status and overall pipeline status. In each +case, you can consider the aggregated status to be the sum of its parts. The mapping from component +status to HTTP status is as follows: + +| Status | HTTP Status Code | +|-------------------|----------------------------------------------------------| +| Starting | 503 - Service Unavailable | +| OK | 200 - OK | +| RecoverableError | 200 when elapsed time < recovery duration; 500 otherwise | +| PermanentError | 500 - Internal Server Error | +| FatalError | 500 - Internal Server Error | +| Stopping | 503 - Service Unavailable | +| Stopped | 503 - Service Unavailable | + + +#### Response Body + +The response body contains either a detailed, or non-detailed view into collector or pipeline health +in JSON format. The level of detail applies to the contents of the response body and is controlled +by the `http.status.detailed` configuration option. + + +##### Collector Health + +The detailed response body for collector health will include the overall status for the +collector, the overall status for each pipeline in the collector, and the statuses for the +individual components in each pipeline. The non-detailed response will only contain the overall +collector health. + +###### Detailed Response Example + +Assuming the health check extension is configured with `http.status.endpoint` set to +`127.0.0.1:13133` a request to `http:127.0.0.1:13133/status` will have a +response body such as: + +```json +{ + "start_time": "2024-01-18T17:27:12.570394-08:00", + "healthy": true, + "status": "StatusRecoverableError", + "error": "rpc error: code = ResourceExhausted desc = resource exhausted", + "status_time": "2024-01-18T17:27:32.572301-08:00", + "components": { + "extensions": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.570428-08:00", + "components": { + "extension:healthcheckv2": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.570428-08:00" + } + } + }, + "pipeline:metrics/grpc": { + "healthy": true, + "status": "StatusRecoverableError", + "error": "rpc error: code = ResourceExhausted desc = resource exhausted", + "status_time": "2024-01-18T17:27:32.572301-08:00", + "components": { + "exporter:otlp/staging": { + "healthy": true, + "status": "StatusRecoverableError", + "error": "rpc error: code = ResourceExhausted desc = resource exhausted", + "status_time": "2024-01-18T17:27:32.572301-08:00" + }, + "processor:batch": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571132-08:00" + }, + "receiver:otlp": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571576-08:00" + } + } + }, + "pipeline:traces/http": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571625-08:00", + "components": { + "exporter:otlphttp/staging": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571615-08:00" + }, + "processor:batch": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571621-08:00" + }, + "receiver:otlp": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571625-08:00" + } + } + } + } +} +``` + +Note the following based on this response: +- The overall status is `StatusRecoverableError` but the status healthy because + the recovery duration has not yet passed. +- `pipeline:metrics/grpc` has a matching status, as does `exporter:otlp/staging`. This implicates + the exporter as the root cause for the pipeline and overall collector status. +- `pipeline:traces/http` is completely healthy. + +###### Non-detailed Response example + +If the same request is made to a collector with `http.status.detailed` to `false`, you will only get +the overall status. The pipeline and component level statuses will be omitted. + +``` +{ + "start_time": "2024-01-18T17:39:15.87324-08:00", + "healthy": true, + "status": "StatusRecoverableError", + "error": "rpc error: code = ResourceExhausted desc = resource exhausted", + "status_time": "2024-01-18T17:39:35.875024-08:00" +} +``` + +###### Pipeline Health + +The detailed response body for pipeline health is essentially a zoomed in version of the detailed +collector response. It contains the overall status for the pipeline and the statuses of the +individual components. The non-detailed response body contains only the overall status for the +pipeline. + +###### Detailed Response Example + +Assuming the health check extension is configured with `http.status.endpoint` set to +`127.0.0.1:13133` a request to `http:127.0.0.1:13133/status?pipeline=traces/http` will have a +response body such as: + + +```json +{ + "start_time": "2024-01-18T17:27:12.570394-08:00", + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571625-08:00", + "components": { + "exporter:otlphttp/staging": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571615-08:00" + }, + "processor:batch": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571621-08:00" + }, + "receiver:otlp": { + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:27:12.571625-08:00" + } + } +} +``` + +###### Non-detailed Response example + +If the same request is made to a collector with `http.status.detailed` to `false`, you will only get +the overall status. The component level statuses will be omitted. + +```json +{ + "start_time": "2024-01-18T17:39:15.87324-08:00", + "healthy": true, + "status": "StatusOK", + "status_time": "2024-01-18T17:39:15.874236-08:00" +} +``` + +### Collector Config Endpoint + +The HTTP service optionally exposes an endpoint that provides the collector configuration. Note, +the configuration returned is unfiltered and may contain sensitive information. As such, the +configuration is disabled by default. Enable it using the `http.config.enabled` setting. By +default the path will be `/config`, but it can be changed using the `http.config.path` setting. + +### gRPC Service + +The health check extension provides an implementation of the [grpc_health_v1 service]. The service +was chosen for compatibility with existing gRPC health checks, however, it does not provide the +additional detail available with the HTTP service. Additionally, the gRPC service has a less +nuanced view of the world with only two reportable statuses: `HealthCheckResponse_SERVING` and +`HealthCheckResponse_NOT_SERVING`. + +#### Mapping of ComponentStatus to HealthCheckResponse_ServingStatus + +The HTTP and gRCP services use the same method of component status aggregation to derive +overall collector health and pipeline health from individual status events. The component +statuses map to the following `HealthCheckResponse_ServingStatus`es. + +| Status | HealthCheckResponse_ServingStatus | +|-------------------|--------------------------------------------------------------------| +| Starting | HealthCheckResponse_NOT_SERVING | +| OK | HealthCheckResponse_SERVING | +| RecoverableError | HealthCheckResponse_SERVING when elapsed time < recovery duration; HealthCheckResponse_NOT_SERVING otherwise | +| PermanentError | HealthCheckResponse_NOT_SERVING | +| FatalError | HealthCheckResponse_NOT_SERVING | +| Stopping | HealthCheckResponse_NOT_SERVING | +| Stopped | HealthCheckResponse_NOT_SERVING | + + +#### HealthCheckRequest + +The gRPC service exposes two RPCs: `Check` and `Watch` (more about those below). Each takes a +`HealthCheckRequest` argument. The `HealthCheckRequest` message is defined as: + +```protobuf +message HealthCheckRequest { + string service = 1; +} +``` + +To query for overall collector health, use the empty string `""` as the `service` name. To query for +pipeline health, use the pipeline name as the `service`. + +#### Check RPC + +The `Check` RPC is defined as: + +```protobuf +rpc Check(HealthCheckRequest) returns (HealthCheckResponse) +``` + +If the service is unknown the RPC will return an error with status `NotFound`. Otherwise it will +return a `HealthCheckResponse` with the serving status as mapped in the table above. + +#### Watch Streaming RPC + +The `Watch` RPC is defined as: + +```protobuf +rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) +``` + +The `Watch` RPC will initiate a stream for the given `service`. If the service is known at the time +the RPC is made, its current status will be sent and changes in status will be sent thereafter. If +the service is unknown, a response with a status of `HealthCheckResponse_SERVICE_UNKNOWN`` will be +sent. The stream will remain open, and if and when the service starts reporting, its status will +begin streaming. + +### Future + +There are plans to provide the ability to export status events as OTLP logs adhering to the event +semantic conventions. + +[grpc_health_v1 service]: https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto \ No newline at end of file diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go new file mode 100644 index 0000000000000..1392e3fbf670c --- /dev/null +++ b/extension/healthcheckextensionv2/config.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" + +import ( + "errors" + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" +) + +// Config has the configuration for the extension enabling the health check +// extension, used to report the health status of the service. +type Config struct { + RecoveryDuration time.Duration `mapstructure:"recovery_duration"` + GRPCSettings *grpc.Settings `mapstructure:"grpc"` + HTTPSettings *http.Settings `mapstructure:"http"` +} + +func (c *Config) Validate() error { + if c.GRPCSettings == nil && c.HTTPSettings == nil { + return errors.New("healthcheck extension: must be configured for HTTP or gRPC") + } + + if c.GRPCSettings != nil && c.GRPCSettings.NetAddr.Endpoint == "" { + return errors.New("healthcheck extension: grpc endpoint required") + } + + if c.HTTPSettings != nil && c.HTTPSettings.Endpoint == "" { + return errors.New("healthcheck extension: grpc endpoint required") + } + + return nil +} diff --git a/extension/healthcheckextensionv2/config_test.go b/extension/healthcheckextensionv2/config_test.go new file mode 100644 index 0000000000000..73ff108b6ec5a --- /dev/null +++ b/extension/healthcheckextensionv2/config_test.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/confignet" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" +) + +func TestConfig(t *testing.T) { + grpcSettings := &grpc.Settings{ + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "127.0.0.1:5000", + Transport: "tcp", + }, + }, + } + httpSettings := &http.Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "127.0.0.1:5001", + }, + } + + for _, tc := range []struct { + name string + config *Config + valid bool + }{ + { + name: "Valid GRPC Settings Only", + config: &Config{ + GRPCSettings: grpcSettings, + }, + valid: true, + }, + { + name: "Invalid GRPC Settings Only", + config: &Config{ + GRPCSettings: &grpc.Settings{}, + }, + valid: false, + }, + { + name: "Valid HTTP Settings Only", + config: &Config{ + HTTPSettings: httpSettings, + }, + valid: true, + }, + { + name: "Invalid HTTP Settings Only", + config: &Config{ + HTTPSettings: &http.Settings{}, + }, + valid: false, + }, + { + name: "GRPC and HTTP Settings", + config: &Config{ + GRPCSettings: grpcSettings, + HTTPSettings: httpSettings, + }, + valid: true, + }, + { + name: "GRPC and HTTP Settings both invalid", + config: &Config{ + GRPCSettings: &grpc.Settings{}, + HTTPSettings: &http.Settings{}, + }, + valid: false, + }, + { + name: "Neither GRPC nor HTTP Settings", + config: &Config{}, + valid: false, + }, + } { + err := tc.config.Validate() + assert.Equal(t, tc.valid, err == nil) + } +} diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go new file mode 100644 index 0000000000000..4c5abae9a9e63 --- /dev/null +++ b/extension/healthcheckextensionv2/extension.go @@ -0,0 +1,190 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" + +import ( + "context" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/extension" + "go.uber.org/multierr" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +) + +type eventSourcePair struct { + source *component.InstanceID + event *component.StatusEvent +} + +type healthCheckExtension struct { + config Config + telemetry component.TelemetrySettings + aggregator *status.Aggregator + subcomponents []component.Component + eventCh chan *eventSourcePair + readyCh chan struct{} + doneCh chan struct{} +} + +var _ component.Component = (*healthCheckExtension)(nil) +var _ extension.ConfigWatcher = (*healthCheckExtension)(nil) +var _ extension.PipelineWatcher = (*healthCheckExtension)(nil) + +func newExtension( + ctx context.Context, + config Config, + set extension.CreateSettings, +) *healthCheckExtension { + var comps []component.Component + aggregator := status.NewAggregator() + + if config.GRPCSettings != nil { + srvGRPC := grpc.NewServer( + config.GRPCSettings, + set.TelemetrySettings, + config.RecoveryDuration, + aggregator, + ) + comps = append(comps, srvGRPC) + } + + if config.HTTPSettings != nil { + srvHTTP := http.NewServer( + config.HTTPSettings, + set.TelemetrySettings, + config.RecoveryDuration, + aggregator, + ) + comps = append(comps, srvHTTP) + } + + hc := &healthCheckExtension{ + config: config, + subcomponents: comps, + telemetry: set.TelemetrySettings, + aggregator: aggregator, + eventCh: make(chan *eventSourcePair), + readyCh: make(chan struct{}), + doneCh: make(chan struct{}), + } + + // Start processing events in the background so that our status watcher doesn't + // block others before the extension starts. + go hc.eventLoop(ctx) + + return hc +} + +// Start implements the component.Component interface. +func (hc *healthCheckExtension) Start(ctx context.Context, host component.Host) error { + hc.telemetry.Logger.Info("Starting health check extension V2", zap.Any("config", hc.config)) + + for _, comp := range hc.subcomponents { + if err := comp.Start(ctx, host); err != nil { + return err + } + } + + return nil +} + +// Shutdown implements the component.Component interface. +func (hc *healthCheckExtension) Shutdown(ctx context.Context) error { + // Preemptively send the stopped event, so it can be exported before shutdown + hc.telemetry.ReportStatus(component.NewStatusEvent(component.StatusStopped)) + + close(hc.doneCh) + hc.aggregator.Close() + + var err error + for _, comp := range hc.subcomponents { + err = multierr.Append(err, comp.Shutdown(ctx)) + } + + return err +} + +// ComponentStatusChanged implements the extension.StatusWatcher interface. +func (hc *healthCheckExtension) ComponentStatusChanged( + source *component.InstanceID, + event *component.StatusEvent, +) { + hc.eventCh <- &eventSourcePair{source: source, event: event} +} + +// NotifyConfig implements the extension.ConfigWatcher interface. +func (hc *healthCheckExtension) NotifyConfig(ctx context.Context, conf *confmap.Conf) error { + var err error + for _, comp := range hc.subcomponents { + if cw, ok := comp.(extension.ConfigWatcher); ok { + err = multierr.Append(err, cw.NotifyConfig(ctx, conf)) + } + } + return err +} + +// Ready implements the extension.PipelineWatcher interface. +func (hc *healthCheckExtension) Ready() error { + close(hc.readyCh) + return nil +} + +// NotReady implements the extension.PipelineWatcher interface. +func (hc *healthCheckExtension) NotReady() error { + return nil +} + +func (hc *healthCheckExtension) eventLoop(ctx context.Context) { + // Record events with component.StatusStarting, but queue other events until + // PipelineWatcher.Ready is called. This prevents aggregate statuses from + // flapping between StatusStarting and StatusOK as components are started + // individually by the service. + var eventQueue []*eventSourcePair + + for loop := true; loop; { + select { + case esp := <-hc.eventCh: + if esp.event.Status() != component.StatusStarting { + eventQueue = append(eventQueue, esp) + continue + } + hc.aggregator.RecordStatus(esp.source, esp.event) + case <-hc.readyCh: + for _, esp := range eventQueue { + hc.aggregator.RecordStatus(esp.source, esp.event) + } + eventQueue = nil + loop = false + case <-ctx.Done(): + return + } + } + + // After PipelineWatcher.Ready, record statuses as they are received. + for loop := true; loop; { + select { + case esp := <-hc.eventCh: + hc.aggregator.RecordStatus(esp.source, esp.event) + case <-hc.doneCh: + loop = false + case <-ctx.Done(): + return + } + } + + // After shutdown read late arriving events from channel and discard + for { + select { + case <-hc.eventCh: + hc.telemetry.Logger.Info("healthcheck: discarding event received after shutdown") + case <-ctx.Done(): + return + } + } +} diff --git a/extension/healthcheckextensionv2/extension_test.go b/extension/healthcheckextensionv2/extension_test.go new file mode 100644 index 0000000000000..0c05b353f7c78 --- /dev/null +++ b/extension/healthcheckextensionv2/extension_test.go @@ -0,0 +1,122 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package healthcheckextensionv2 + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/extension/extensiontest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" +) + +func TestComponentStatus(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.HTTPSettings.Endpoint = testutil.GetAvailableLocalAddress(t) + + ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) + + // Status before Start will be StatusNone + assert.Equal(t, ext.aggregator.CollectorStatus().Status(), component.StatusNone) + + require.NoError(t, ext.Start(context.Background(), componenttest.NewNopHost())) + + traces := testhelpers.NewPipelineMetadata("traces") + + // StatusStarting will be sent immediately. + for _, id := range traces.InstanceIDs() { + ext.ComponentStatusChanged(id, component.NewStatusEvent(component.StatusStarting)) + } + + // StatusOK will be queued until the PipelineWatcher Ready method is called. + for _, id := range traces.InstanceIDs() { + ext.ComponentStatusChanged(id, component.NewStatusEvent(component.StatusOK)) + } + + // Note the use of assert.Eventually here and throughout this test is because + // status events are processed asynchronously in the background. + assert.Eventually(t, func() bool { + return ext.aggregator.CollectorStatus().Status() == component.StatusStarting + }, time.Second, 10*time.Millisecond) + + require.NoError(t, ext.Ready()) + + assert.Eventually(t, func() bool { + return ext.aggregator.CollectorStatus().Status() == component.StatusOK + }, time.Second, 10*time.Millisecond) + + // StatusStopping will be sent immediately. + for _, id := range traces.InstanceIDs() { + ext.ComponentStatusChanged(id, component.NewStatusEvent(component.StatusStopping)) + } + + assert.Eventually(t, func() bool { + return ext.aggregator.CollectorStatus().Status() == component.StatusStopping + }, time.Second, 10*time.Millisecond) + + require.NoError(t, ext.NotReady()) + require.NoError(t, ext.Shutdown(context.Background())) + + // Events sent after shutdown will be discarded + for _, id := range traces.InstanceIDs() { + ext.ComponentStatusChanged(id, component.NewStatusEvent(component.StatusStopped)) + } + + assert.Equal(t, component.StatusStopping, ext.aggregator.CollectorStatus().Status()) +} + +func TestNotifyConfig(t *testing.T) { + confMap, err := confmaptest.LoadConf( + filepath.Join("internal", "http", "testdata", "config.yaml"), + ) + require.NoError(t, err) + confJSON, err := os.ReadFile( + filepath.Clean(filepath.Join("internal", "http", "testdata", "config.json")), + ) + require.NoError(t, err) + + endpoint := testutil.GetAvailableLocalAddress(t) + + cfg := createDefaultConfig().(*Config) + cfg.HTTPSettings.Endpoint = endpoint + cfg.HTTPSettings.Config.Enabled = true + cfg.HTTPSettings.Config.Path = "/config" + + ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) + + require.NoError(t, ext.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { require.NoError(t, ext.Shutdown(context.Background())) }) + + client := &http.Client{} + url := fmt.Sprintf("http://%s/config", endpoint) + + var resp *http.Response + + resp, err = client.Get(url) + require.NoError(t, err) + assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode) + + require.NoError(t, ext.NotifyConfig(context.Background(), confMap)) + + resp, err = client.Get(url) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, confJSON, body) +} diff --git a/extension/healthcheckextensionv2/factory.go b/extension/healthcheckextensionv2/factory.go new file mode 100644 index 0000000000000..339bf6d256757 --- /dev/null +++ b/extension/healthcheckextensionv2/factory.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:generate mdatagen metadata.yaml + +package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/extension" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/metadata" +) + +const ( + // Use 0.0.0.0 to make the health check endpoint accessible + // in container orchestration environments like Kubernetes. + defaultEndpoint = "0.0.0.0:13133" +) + +// NewFactory creates a factory for HealthCheck extension. +func NewFactory() extension.Factory { + return extension.NewFactory( + metadata.Type, + createDefaultConfig, + createExtension, + metadata.ExtensionStability, + ) +} + +func createDefaultConfig() component.Config { + return &Config{ + RecoveryDuration: time.Minute, + HTTPSettings: &http.Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultEndpoint, + }, + Status: http.StatusSettings{ + Detailed: true, + PathSettings: http.PathSettings{ + Enabled: true, + Path: "/", + }, + }, + }, + } +} + +func createExtension(ctx context.Context, set extension.CreateSettings, cfg component.Config) (extension.Extension, error) { + config := cfg.(*Config) + return newExtension(ctx, *config, set), nil +} diff --git a/extension/healthcheckextensionv2/factory_test.go b/extension/healthcheckextensionv2/factory_test.go new file mode 100644 index 0000000000000..61c9726828cc4 --- /dev/null +++ b/extension/healthcheckextensionv2/factory_test.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package healthcheckextensionv2 + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/extension/extensiontest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" +) + +func TestCreateDefaultConfig(t *testing.T) { + cfg := createDefaultConfig() + assert.Equal(t, &Config{ + RecoveryDuration: time.Minute, + HTTPSettings: &http.Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultEndpoint, + }, + Status: http.StatusSettings{ + Detailed: true, + PathSettings: http.PathSettings{ + Enabled: true, + Path: "/", + }, + }, + Config: http.PathSettings{}, + }, + }, cfg) + + assert.NoError(t, componenttest.CheckConfigStruct(cfg)) + ext, err := createExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + require.NotNil(t, ext) +} + +func TestCreateExtension(t *testing.T) { + cfg := createDefaultConfig().(*Config) + cfg.HTTPSettings.Endpoint = testutil.GetAvailableLocalAddress(t) + + ext, err := createExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + require.NotNil(t, ext) +} diff --git a/extension/healthcheckextensionv2/go.mod b/extension/healthcheckextensionv2/go.mod new file mode 100644 index 0000000000000..1d2429cec028c --- /dev/null +++ b/extension/healthcheckextensionv2/go.mod @@ -0,0 +1,79 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2 + +go 1.20 + +require ( + github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.92.0 + github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/collector/component v0.92.1-0.20240118172122-8131d31601b8 + go.opentelemetry.io/collector/config/configgrpc v0.92.1-0.20240118172122-8131d31601b8 + go.opentelemetry.io/collector/config/confighttp v0.92.1-0.20240118172122-8131d31601b8 + go.opentelemetry.io/collector/config/confignet v0.92.1-0.20240118172122-8131d31601b8 + go.opentelemetry.io/collector/confmap v0.92.1-0.20240118172122-8131d31601b8 + go.opentelemetry.io/collector/extension v0.92.1-0.20240118172122-8131d31601b8 + go.opentelemetry.io/otel/metric v1.22.0 + go.opentelemetry.io/otel/trace v1.22.0 + go.uber.org/multierr v1.11.0 + go.uber.org/zap v1.26.0 + google.golang.org/grpc v1.60.1 +) + +require ( + cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68 // indirect + contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/mostynb/go-grpc-compression v1.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.46.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/statsd_exporter v0.22.7 // indirect + github.com/rs/cors v1.10.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/collector v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/config/configauth v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/config/configcompression v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/config/configopaque v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/config/configtls v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/config/internal v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/extension/auth v0.92.1-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/featuregate v1.0.2-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector/pdata v1.0.2-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2 // indirect + go.opentelemetry.io/otel/sdk v1.22.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.22.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/common => ../../internal/common diff --git a/extension/healthcheckextensionv2/go.sum b/extension/healthcheckextensionv2/go.sum new file mode 100644 index 0000000000000..a2d6d77b44701 --- /dev/null +++ b/extension/healthcheckextensionv2/go.sum @@ -0,0 +1,639 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68 h1:aRVqY1p2IJaBGStWMsQMpkAa83cPkCDLl80eOj0Rbz4= +cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68/go.mod h1:1a3eRNYX12fs5UABBIXS8HXVvQbX9hRB/RkEBPORpe8= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= +contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/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/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +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/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +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-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/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/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mostynb/go-grpc-compression v1.2.2 h1:XaDbnRvt2+1vgr0b/l0qh4mJAfIxE0bKXtz2Znl3GGI= +github.com/mostynb/go-grpc-compression v1.2.2/go.mod h1:GOCr2KBxXcblCuczg3YdLQlcin1/NfyDA348ckuCH6w= +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/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/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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +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/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= +github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +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/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.6.1/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/collector v0.92.1-0.20240118172122-8131d31601b8 h1:Nn2JQ6bwqgEzzaHQ2QQe08RtlDle2/Ag5BG+c7znYis= +go.opentelemetry.io/collector v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rAYt6LjXy2CxnW+bHlsgOJuuSHVZqcNFTY6CiYWcDjA= +go.opentelemetry.io/collector/component v0.92.1-0.20240118172122-8131d31601b8 h1:FYELfGMDrN3QlQi/umNNDFv+bBTcZ8H58MxDATWnDY0= +go.opentelemetry.io/collector/component v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:Bwg4iVtuCHJmcvhQFQH/KI5dNYwHysTHDivxzVUmWgU= +go.opentelemetry.io/collector/config/configauth v0.92.1-0.20240118172122-8131d31601b8 h1:tSCUQy87rpo+8d4pUvWbqcc9+8hCfU+aaTCI9xbtAOc= +go.opentelemetry.io/collector/config/configauth v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:PCkupWQjjY1+0Ay7pv8CQQyF3WNyHZN4HaDHrcIghOI= +go.opentelemetry.io/collector/config/configcompression v0.92.1-0.20240118172122-8131d31601b8 h1:QgiLlFy+51Z6E4GI1ptQXfaEtbJELsZGIi8lDOC7foQ= +go.opentelemetry.io/collector/config/configcompression v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:fA36AZC/Qcyl+HvMnvFZuV/iUWGQJrchimmk+qYWuMM= +go.opentelemetry.io/collector/config/configgrpc v0.92.1-0.20240118172122-8131d31601b8 h1:VFk66vA+TPRiEZydv0r//P+Y/C3Ne25aLo18WUYad1Y= +go.opentelemetry.io/collector/config/configgrpc v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:NddH5HMcYNwSEyHyKsK2f9NSmQWQA6gfrie6gZBvzN0= +go.opentelemetry.io/collector/config/confighttp v0.92.1-0.20240118172122-8131d31601b8 h1:noMWgXJyUXvjtJpnB5jzJ9VQ+ajBC3tTXUs02ZDFKtk= +go.opentelemetry.io/collector/config/confighttp v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:dE0mjg2eAPqEz285mo+GYkcAlG8gI5c2b7B1lJDmUUA= +go.opentelemetry.io/collector/config/confignet v0.92.1-0.20240118172122-8131d31601b8 h1:omXjwBzy7YQbRhGjARwvn5U30Vhr8Or3Oi9QRb8083g= +go.opentelemetry.io/collector/config/confignet v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rraribsOoZsYZm51+3TXHavmXKJOC5a5/X20vfmNbdw= +go.opentelemetry.io/collector/config/configopaque v0.92.1-0.20240118172122-8131d31601b8 h1:SymD96TeYKaXiigWhdimNWZkbu8ixWKJ/iSeJENkgyk= +go.opentelemetry.io/collector/config/configopaque v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:dQK8eUXjIGKaw1RB7UIg2nqx56AueNxeKFCdB0P1ypg= +go.opentelemetry.io/collector/config/configtelemetry v0.92.1-0.20240118172122-8131d31601b8 h1:jwsti51Wbk5YxFnAT1Is88blqmg7frQeNwdJ7nXweVw= +go.opentelemetry.io/collector/config/configtelemetry v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= +go.opentelemetry.io/collector/config/configtls v0.92.1-0.20240118172122-8131d31601b8 h1:8L2sFdBx4AZKzGKF6WfumqH2xLwoC+BlHIR4waKF90Y= +go.opentelemetry.io/collector/config/configtls v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rL9BH5Hyrkni4t+QOx/opuwD0CHq/ZIFTsh6QLLsbmA= +go.opentelemetry.io/collector/config/internal v0.92.1-0.20240118172122-8131d31601b8 h1:+TSSZgWdJPbc7cvgPbubruZShWN6QF4rs+qOc/eSRak= +go.opentelemetry.io/collector/config/internal v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rPjglfSd4K/kNLfH7TJO8AsstHGMmWTdntOqH7WiFLg= +go.opentelemetry.io/collector/confmap v0.92.1-0.20240118172122-8131d31601b8 h1:TGBvac5l/nTDQB6u4Yasj3GINyupp2BptHHVZntlWy8= +go.opentelemetry.io/collector/confmap v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:+QxYr8qSah4ffcVBUC2KJgwlMsrD2nK1CmcHkLB+D7A= +go.opentelemetry.io/collector/consumer v0.92.0 h1:twa8T0iR9KVglvRbwZ5OPKLXPCC2DO6gVhrgDZ47MPE= +go.opentelemetry.io/collector/extension v0.92.1-0.20240118172122-8131d31601b8 h1:sS33cmZpccGhTNLNXmb9IQU2il/anD7Iu7Nyyz9Lnt8= +go.opentelemetry.io/collector/extension v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:4MG/LMqzVldOP4QZMzImRctujpWdzrd/+VZtZGAJZJU= +go.opentelemetry.io/collector/extension/auth v0.92.1-0.20240118172122-8131d31601b8 h1:bunRKHJ1GcA/QXB7PFJDvA8q3G5GddOQouCzLvdNg5w= +go.opentelemetry.io/collector/extension/auth v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:IRlhFZAFPkEhgJwuZfgVnm5rt1Sgu6ERGCFUMpBkOx4= +go.opentelemetry.io/collector/featuregate v1.0.2-0.20240118172122-8131d31601b8 h1:CjfhuI22zntWF6c+PNhEMO3WNBh0OmKvv3e7tHcuIg4= +go.opentelemetry.io/collector/featuregate v1.0.2-0.20240118172122-8131d31601b8/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= +go.opentelemetry.io/collector/pdata v1.0.2-0.20240118172122-8131d31601b8 h1:ygOOx2cN0zZHrPowRymfUgnRa6aEd5eE0xrv9VAQL9c= +go.opentelemetry.io/collector/pdata v1.0.2-0.20240118172122-8131d31601b8/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2 h1:TnhkxGJ5qPHAMIMI4r+HPT/BbpoHxqn4xONJrok054o= +go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= +go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= +go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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.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.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.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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/extension/healthcheckextensionv2/internal/grpc/config.go b/extension/healthcheckextensionv2/internal/grpc/config.go new file mode 100644 index 0000000000000..aecc17cf28167 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/grpc/config.go @@ -0,0 +1,11 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" + +import "go.opentelemetry.io/collector/config/configgrpc" + +type Settings struct { + configgrpc.GRPCServerSettings `mapstructure:",squash"` + Debug bool `mapstructure:"debug"` +} diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go new file mode 100644 index 0000000000000..fd73511493288 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -0,0 +1,120 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "google.golang.org/grpc/codes" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/status" +) + +func (s *Server) Check( + _ context.Context, + req *healthpb.HealthCheckRequest, +) (*healthpb.HealthCheckResponse, error) { + var err error + var ev *component.StatusEvent + + if req.Service == "" { + ev = s.aggregator.CollectorStatus() + } else { + ev, err = s.aggregator.PipelineStatus(req.Service) + } + + if err != nil { + return nil, status.Error(codes.NotFound, "unknown service") + } + + return &healthpb.HealthCheckResponse{ + Status: s.toServingStatus(ev), + }, nil +} + +func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_WatchServer) error { + sub, err := s.aggregator.Subscribe(req.Service) + if err != nil { + return err + } + defer s.aggregator.Unsubscribe(sub) + + var lastServingStatus healthpb.HealthCheckResponse_ServingStatus = -1 + + failureTicker := time.NewTicker(s.recoveryDuration) + failureTicker.Stop() + + for { + select { + case ev, ok := <-sub: + if !ok { + return status.Error(codes.Canceled, "Server shutting down.") + } + + var sst healthpb.HealthCheckResponse_ServingStatus + + switch { + case ev == nil: + sst = healthpb.HealthCheckResponse_SERVICE_UNKNOWN + case ev.Status() == component.StatusRecoverableError: + failureTicker.Reset(s.recoveryDuration) + sst = lastServingStatus + if lastServingStatus == -1 { + sst = healthpb.HealthCheckResponse_SERVING + } + default: + failureTicker.Stop() + sst = statusToServingStatusMap[ev.Status()] + } + + if lastServingStatus == sst { + continue + } + + lastServingStatus = sst + + err := stream.Send(&healthpb.HealthCheckResponse{Status: sst}) + if err != nil { + return status.Error(codes.Canceled, "Stream has ended.") + } + case <-failureTicker.C: + failureTicker.Stop() + if lastServingStatus == healthpb.HealthCheckResponse_NOT_SERVING { + continue + } + lastServingStatus = healthpb.HealthCheckResponse_NOT_SERVING + err := stream.Send( + &healthpb.HealthCheckResponse{ + Status: healthpb.HealthCheckResponse_NOT_SERVING, + }, + ) + if err != nil { + return status.Error(codes.Canceled, "Stream has ended.") + } + case <-stream.Context().Done(): + return status.Error(codes.Canceled, "Stream has ended.") + } + } +} + +var statusToServingStatusMap = map[component.Status]healthpb.HealthCheckResponse_ServingStatus{ + component.StatusNone: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStarting: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusOK: healthpb.HealthCheckResponse_SERVING, + component.StatusRecoverableError: healthpb.HealthCheckResponse_SERVING, + component.StatusPermanentError: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusFatalError: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStopping: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStopped: healthpb.HealthCheckResponse_NOT_SERVING, +} + +func (s *Server) toServingStatus(ev *component.StatusEvent) healthpb.HealthCheckResponse_ServingStatus { + if ev.Status() == component.StatusRecoverableError && + time.Now().Compare(ev.Timestamp().Add(s.recoveryDuration)) == 1 { + return healthpb.HealthCheckResponse_NOT_SERVING + } + return statusToServingStatusMap[ev.Status()] +} diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go new file mode 100644 index 0000000000000..908f576f889a1 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go @@ -0,0 +1,438 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package grpc + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/confignet" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + grpcstatus "google.golang.org/grpc/status" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" +) + +func TestCheck(t *testing.T) { + addr := testutil.GetAvailableLocalAddress(t) + settings := &Settings{ + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: addr, + Transport: "tcp", + }, + }, + } + server := NewServer( + settings, + componenttest.NewNopTelemetrySettings(), + 10*time.Millisecond, + status.NewAggregator(), + ) + traces := testhelpers.NewPipelineMetadata("traces") + metrics := testhelpers.NewPipelineMetadata("metrics") + + require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { require.NoError(t, server.Shutdown(context.Background())) }) + + cc, err := grpc.Dial( + addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + ) + require.NoError(t, err) + defer func() { + assert.NoError(t, cc.Close()) + }() + + client := healthpb.NewHealthClient(cc) + + // ts is a sequence of test steps + for _, ts := range []struct { + step func() + eventually bool + service string + expectedStatus healthpb.HealthCheckResponse_ServingStatus + expectedErr error + }{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + service: metrics.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + } { + if ts.step != nil { + ts.step() + } + + if ts.eventually { + assert.Eventually(t, func() bool { + resp, err := client.Check( + context.Background(), + &healthpb.HealthCheckRequest{Service: ts.service}, + ) + require.NoError(t, err) + return ts.expectedStatus == resp.Status + }, time.Second, 10*time.Millisecond) + continue + } + + resp, err := client.Check( + context.Background(), + &healthpb.HealthCheckRequest{Service: ts.service}, + ) + require.Equal(t, ts.expectedErr, err) + if ts.expectedErr != nil { + continue + } + assert.Equal(t, ts.expectedStatus, resp.Status) + } +} + +func TestWatch(t *testing.T) { + var err error + addr := testutil.GetAvailableLocalAddress(t) + settings := &Settings{ + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: addr, + Transport: "tcp", + }, + }, + } + server := NewServer( + settings, + componenttest.NewNopTelemetrySettings(), + 10*time.Millisecond, + status.NewAggregator(), + ) + traces := testhelpers.NewPipelineMetadata("traces") + metrics := testhelpers.NewPipelineMetadata("metrics") + + require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { require.NoError(t, server.Shutdown(context.Background())) }) + + cc, err := grpc.Dial( + addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + ) + require.NoError(t, err) + defer func() { + assert.NoError(t, cc.Close()) + }() + + client := healthpb.NewHealthClient(cc) + watchers := make(map[string]healthpb.Health_WatchClient) + + // ts is a sequence of test steps + for _, ts := range []struct { + step func() + service string + expectedStatus healthpb.HealthCheckResponse_ServingStatus + }{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // This will be the last status change for traces (stopping changes to NOT_SERVING) + // Stopped results in the same serving status, and repeat statuses are not streamed. + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // This will be the last status change for metrics (stopping changes to NOT_SERVING) + // Stopped results in the same serving status, and repeat statuses are not streamed. + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + } { + + if ts.step != nil { + ts.step() + } + + watcher, ok := watchers[ts.service] + if !ok { + watcher, err = client.Watch( + context.Background(), + &healthpb.HealthCheckRequest{Service: ts.service}, + ) + require.NoError(t, err) + watchers[ts.service] = watcher + } + + var resp *healthpb.HealthCheckResponse + // Note Recv blocks until there is a new item in the stream + resp, err = watcher.Recv() + require.NoError(t, err) + assert.Equal(t, ts.expectedStatus, resp.Status) + } + + // closing the aggregator will gracefully terminate streams of status events + server.aggregator.Close() + + // Ensure watchers receive the cancelation when streams are closed by the server + for _, watcher := range watchers { + _, err = watcher.Recv() + assert.Equal(t, grpcstatus.Error(codes.Canceled, "Server shutting down."), err) + } +} diff --git a/extension/healthcheckextensionv2/internal/grpc/server.go b/extension/healthcheckextensionv2/internal/grpc/server.go new file mode 100644 index 0000000000000..836709baf0687 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/grpc/server.go @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" + +import ( + "context" + "errors" + "time" + + "go.opentelemetry.io/collector/component" + "google.golang.org/grpc" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/reflection" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +) + +type Server struct { + healthpb.UnimplementedHealthServer + serverGRPC *grpc.Server + aggregator *status.Aggregator + settings *Settings + telemetry component.TelemetrySettings + recoveryDuration time.Duration + doneCh chan struct{} +} + +var _ component.Component = (*Server)(nil) + +func NewServer( + settings *Settings, + telemetry component.TelemetrySettings, + failureDuration time.Duration, + aggregator *status.Aggregator, +) *Server { + return &Server{ + settings: settings, + telemetry: telemetry, + aggregator: aggregator, + recoveryDuration: failureDuration, + doneCh: make(chan struct{}), + } +} + +// Start implements the component.Component interface. +func (s *Server) Start(_ context.Context, host component.Host) error { + var err error + s.serverGRPC, err = s.settings.ToServer(host, s.telemetry) + if err != nil { + return err + } + + healthpb.RegisterHealthServer(s.serverGRPC, s) + if s.settings.Debug { + reflection.Register(s.serverGRPC) + } + + ln, err := s.settings.ToListener() + + go func() { + defer close(s.doneCh) + + if err = s.serverGRPC.Serve(ln); err != nil && !errors.Is(err, grpc.ErrServerStopped) { + s.telemetry.ReportStatus(component.NewPermanentErrorEvent(err)) + } + }() + + return nil +} + +// Shutdown implements the component.Component interface. +func (s *Server) Shutdown(context.Context) error { + if s.serverGRPC == nil { + return nil + } + s.serverGRPC.GracefulStop() + <-s.doneCh + return nil +} diff --git a/extension/healthcheckextensionv2/internal/http/config.go b/extension/healthcheckextensionv2/internal/http/config.go new file mode 100644 index 0000000000000..3a7e94c386724 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/config.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + +import "go.opentelemetry.io/collector/config/confighttp" + +type Settings struct { + confighttp.HTTPServerSettings `mapstructure:",squash"` + + Config PathSettings `mapstructure:"config"` + Status StatusSettings `mapstructure:"status"` +} + +type PathSettings struct { + Enabled bool `mapstructure:"enabled"` + Path string `mapstructure:"path"` +} + +type StatusSettings struct { + PathSettings `mapstructure:",squash"` + Detailed bool `mapstructure:"detailed"` +} diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go new file mode 100644 index 0000000000000..77fec1084a7b1 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -0,0 +1,98 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + +import ( + "encoding/json" + "net/http" + + "go.opentelemetry.io/collector/component" +) + +func (s *Server) statusHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + var sst *serializableStatus + pipeline := r.URL.Query().Get("pipeline") + + if pipeline == "" { + sst = s.collectorSerializableStatus() + } else { + sst, err = s.pipelineSerializableStatus(pipeline) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(s.toHTTPStatus(sst)) + + body, _ := json.Marshal(sst) + _, _ = w.Write(body) + }) +} + +func (s *Server) configHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + conf := s.colconf.Load() + + if conf == nil { + w.WriteHeader(http.StatusServiceUnavailable) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(conf.([]byte)) + }) +} + +func (s *Server) collectorSerializableStatus() *serializableStatus { + if s.settings.Status.Detailed { + details := s.aggregator.CollectorStatusDetailed() + return toCollectorSerializableStatus(details, s.startTimestamp, s.recoveryDuration) + } + + return toSerializableStatus( + s.aggregator.CollectorStatus(), + s.startTimestamp, + s.recoveryDuration, + ) +} + +func (s *Server) pipelineSerializableStatus(pipeline string) (*serializableStatus, error) { + if s.settings.Status.Detailed { + details, err := s.aggregator.PipelineStatusDetailed(pipeline) + if err != nil { + return nil, err + } + return toPipelineSerializableStatus(details, s.startTimestamp, s.recoveryDuration), nil + } + + ev, err := s.aggregator.PipelineStatus(pipeline) + if err != nil { + return nil, err + } + + return toSerializableStatus(ev, s.startTimestamp, s.recoveryDuration), nil +} + +var responseCodes = map[component.Status]int{ + component.StatusNone: http.StatusServiceUnavailable, + component.StatusStarting: http.StatusServiceUnavailable, + component.StatusOK: http.StatusOK, + component.StatusRecoverableError: http.StatusOK, + component.StatusPermanentError: http.StatusInternalServerError, + component.StatusFatalError: http.StatusInternalServerError, + component.StatusStopping: http.StatusServiceUnavailable, + component.StatusStopped: http.StatusServiceUnavailable, +} + +func (s *Server) toHTTPStatus(sst *serializableStatus) int { + if sst.Status() == component.StatusRecoverableError && !sst.Healthy { + return http.StatusInternalServerError + } + return responseCodes[sst.Status()] +} diff --git a/extension/healthcheckextensionv2/internal/http/serialization.go b/extension/healthcheckextensionv2/internal/http/serialization.go new file mode 100644 index 0000000000000..cdb87b863d554 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/serialization.go @@ -0,0 +1,155 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + +import ( + "fmt" + "time" + + "go.opentelemetry.io/collector/component" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +) + +type serializableStatus struct { + StartTimestamp *time.Time `json:"start_time,omitempty"` + *SerializableEvent + ComponentStatuses map[string]*serializableStatus `json:"components,omitempty"` +} + +// SerializableEvent is exported for json.Unmarshal +type SerializableEvent struct { + Healthy bool `json:"healthy"` + StatusString string `json:"status"` + Error string `json:"error,omitempty"` + Timestamp time.Time `json:"status_time"` +} + +var stringToStatusMap = map[string]component.Status{ + "StatusNone": component.StatusNone, + "StatusStarting": component.StatusStarting, + "StatusOK": component.StatusOK, + "StatusRecoverableError": component.StatusRecoverableError, + "StatusPermanentError": component.StatusPermanentError, + "StatusFatalError": component.StatusFatalError, + "StatusStopping": component.StatusStopping, + "StatusStopped": component.StatusStopped, +} + +func (ev *SerializableEvent) Status() component.Status { + if st, ok := stringToStatusMap[ev.StatusString]; ok { + return st + } + return component.StatusNone +} + +func toSerializableEvent( + ev *component.StatusEvent, + now time.Time, + recoveryDuration time.Duration, +) *SerializableEvent { + se := &SerializableEvent{ + Healthy: isHealthy(ev, now, recoveryDuration), + StatusString: ev.Status().String(), + Timestamp: ev.Timestamp(), + } + if ev.Err() != nil { + se.Error = ev.Err().Error() + } + return se +} + +var extsKey = "extensions" + +func toSerializableStatus( + ev *component.StatusEvent, + startTimestamp time.Time, + recoveryDuration time.Duration, +) *serializableStatus { + return &serializableStatus{ + StartTimestamp: &startTimestamp, + SerializableEvent: toSerializableEvent(ev, time.Now(), recoveryDuration), + } +} + +func toCollectorSerializableStatus( + details *status.CollectorStatusDetails, + startTimestamp time.Time, + recoveryDuration time.Duration, +) *serializableStatus { + now := time.Now() + s := &serializableStatus{ + StartTimestamp: &startTimestamp, + SerializableEvent: toSerializableEvent(details.OverallStatus, now, recoveryDuration), + ComponentStatuses: make(map[string]*serializableStatus), + } + + for compID, ev := range details.PipelineStatusMap { + key := compID.String() + if key != extsKey { + key = "pipeline:" + key + } + cs := &serializableStatus{ + SerializableEvent: toSerializableEvent(ev, now, recoveryDuration), + ComponentStatuses: make(map[string]*serializableStatus), + } + s.ComponentStatuses[key] = cs + for instance, ev := range details.ComponentStatusMap[compID] { + key := fmt.Sprintf("%s:%s", kindToString(instance.Kind), instance.ID) + cs.ComponentStatuses[key] = &serializableStatus{ + SerializableEvent: toSerializableEvent(ev, now, recoveryDuration), + } + } + } + + return s +} + +func toPipelineSerializableStatus( + details *status.PipelineStatusDetails, + startTimestamp time.Time, + recoveryDuration time.Duration, +) *serializableStatus { + now := time.Now() + s := &serializableStatus{ + StartTimestamp: &startTimestamp, + SerializableEvent: toSerializableEvent(details.OverallStatus, now, recoveryDuration), + ComponentStatuses: make(map[string]*serializableStatus), + } + + for instance, ev := range details.ComponentStatusMap { + key := fmt.Sprintf("%s:%s", kindToString(instance.Kind), instance.ID) + s.ComponentStatuses[key] = &serializableStatus{ + SerializableEvent: toSerializableEvent(ev, now, recoveryDuration), + } + } + + return s +} + +func isHealthy(ev *component.StatusEvent, now time.Time, recoveryDuration time.Duration) bool { + if ev.Status() == component.StatusRecoverableError && + now.Compare(ev.Timestamp().Add(recoveryDuration)) == -1 { + return true + } + + return !component.StatusIsError(ev.Status()) +} + +// TODO: implemnent Stringer on Kind in core +func kindToString(k component.Kind) string { + switch k { + case component.KindReceiver: + return "receiver" + case component.KindProcessor: + return "processor" + case component.KindExporter: + return "exporter" + case component.KindExtension: + return "extension" + case component.KindConnector: + return "connector" + } + return "" +} diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckextensionv2/internal/http/server.go new file mode 100644 index 0000000000000..185076eb1a985 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/server.go @@ -0,0 +1,107 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "sync/atomic" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/extension" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +) + +type Server struct { + telemetry component.TelemetrySettings + settings *Settings + recoveryDuration time.Duration + mux *http.ServeMux + serverHTTP *http.Server + colconf atomic.Value + aggregator *status.Aggregator + startTimestamp time.Time + done chan struct{} +} + +var _ component.Component = (*Server)(nil) +var _ extension.ConfigWatcher = (*Server)(nil) + +func NewServer( + settings *Settings, + telemetry component.TelemetrySettings, + recoveryDuration time.Duration, + aggregator *status.Aggregator, +) *Server { + srv := &Server{ + telemetry: telemetry, + settings: settings, + aggregator: aggregator, + recoveryDuration: recoveryDuration, + done: make(chan struct{}), + } + + srv.mux = http.NewServeMux() + if settings.Status.Enabled { + srv.mux.Handle(settings.Status.Path, srv.statusHandler()) + } + if settings.Config.Enabled { + srv.mux.Handle(settings.Config.Path, srv.configHandler()) + } + + return srv +} + +// Start implements the component.Component interface. +func (s *Server) Start(_ context.Context, host component.Host) error { + var err error + s.startTimestamp = time.Now() + + s.serverHTTP, err = s.settings.ToServer(host, s.telemetry, s.mux) + if err != nil { + return err + } + + ln, err := s.settings.ToListener() + if err != nil { + return fmt.Errorf("failed to bind to address %s: %w", s.settings.Endpoint, err) + } + + go func() { + defer close(s.done) + if err = s.serverHTTP.Serve(ln); !errors.Is(err, http.ErrServerClosed) && err != nil { + s.telemetry.ReportStatus(component.NewPermanentErrorEvent(err)) + } + }() + + return nil +} + +// Shutdown implements the component.Component interface. +func (s *Server) Shutdown(context.Context) error { + if s.serverHTTP == nil { + return nil + } + s.serverHTTP.Close() + <-s.done + return nil +} + +// NotifyConfig implements the extension.ConfigWatcher interface. +func (s *Server) NotifyConfig(_ context.Context, conf *confmap.Conf) error { + confBytes, err := json.Marshal(conf.ToStringMap()) + if err != nil { + s.telemetry.Logger.Warn("could not marshal config", zap.Error(err)) + return err + } + s.colconf.Store(confBytes) + return nil +} diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go new file mode 100644 index 0000000000000..ab06f4f1666a3 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -0,0 +1,890 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/confmap/confmaptest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" +) + +type componentStatusExpectation struct { + healthy bool + status component.Status + err error + nestedStatus map[string]*componentStatusExpectation +} + +type teststep struct { + step func() + queryParams string + eventually bool + expectedStatusCode int + expectedComponentStatus *componentStatusExpectation +} + +func TestStatus(t *testing.T) { + // server and pipeline are reassigned before each test and are available for + // use in the teststesp + var server *Server + var pipelines map[string]*testhelpers.PipelineMetadata + + tests := []struct { + name string + settings *Settings + pipelines map[string]*testhelpers.PipelineMetadata + teststeps []teststep + }{ + { + name: "Collector Status", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + Detailed: false, + PathSettings: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + eventually: true, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + name: "Collector Status - Detailed", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + Detailed: true, + PathSettings: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + eventually: true, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Pipeline Status", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + Detailed: false, + PathSettings: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + queryParams: "pipeline=traces", + eventually: true, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + name: "Pipeline Status - Detailed", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + Detailed: true, + PathSettings: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + queryParams: "pipeline=traces", + eventually: true, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + name: "Multiple Pipelines", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + Detailed: true, + PathSettings: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + }, + pipelines: testhelpers.NewPipelines("traces", "metrics"), + teststeps: []teststep{ + { + step: func() { + // traces will be StatusOK + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["metrics"].InstanceIDs(), + component.StatusOK, + ) + // metrics and overall status will be PermanentError + server.aggregator.RecordStatus( + pipelines["metrics"].ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + name: "Pipeline Non-existent", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + Detailed: false, + PathSettings: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + }, + queryParams: "pipeline=nonexistent", + expectedStatusCode: http.StatusNotFound, + }, + }, + }, + { + name: "Status Disabled", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + PathSettings: PathSettings{ + Enabled: false, + }, + }, + }, + teststeps: []teststep{ + { + expectedStatusCode: http.StatusNotFound, + }, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + pipelines = tc.pipelines + server = NewServer( + tc.settings, + componenttest.NewNopTelemetrySettings(), + 20*time.Millisecond, + status.NewAggregator(), + ) + + require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) + defer func() { require.NoError(t, server.Shutdown(context.Background())) }() + + client := &http.Client{} + url := fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Status.Path) + + for _, ts := range tc.teststeps { + if ts.step != nil { + ts.step() + } + + stepURL := url + if ts.queryParams != "" { + stepURL = fmt.Sprintf("%s?%s", stepURL, ts.queryParams) + } + + var err error + var resp *http.Response + + if ts.eventually { + assert.Eventually(t, func() bool { + resp, err = client.Get(stepURL) + require.NoError(t, err) + return ts.expectedStatusCode == resp.StatusCode + }, time.Second, 10*time.Millisecond) + } else { + resp, err = client.Get(stepURL) + require.NoError(t, err) + assert.Equal(t, ts.expectedStatusCode, resp.StatusCode) + } + + if ts.expectedComponentStatus != nil { + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + st := &serializableStatus{} + require.NoError(t, json.Unmarshal(body, st)) + + if tc.settings.Status.Detailed { + assertStatusDetailed(t, ts.expectedComponentStatus, st) + continue + } + + assertStatusSimple(t, ts.expectedComponentStatus, st) + } + } + }) + } +} + +func assertStatusDetailed( + t *testing.T, + expected *componentStatusExpectation, + actual *serializableStatus, +) { + assert.Equal(t, expected.healthy, actual.Healthy) + assert.Equal(t, expected.status, actual.Status()) + assert.Equal(t, !component.StatusIsError(expected.status), actual.Healthy) + if expected.err != nil { + assert.Equal(t, expected.err.Error(), actual.Error) + } + assertNestedStatus(t, expected.nestedStatus, actual.ComponentStatuses) +} + +func assertNestedStatus( + t *testing.T, + expected map[string]*componentStatusExpectation, + actual map[string]*serializableStatus, +) { + for k, expectation := range expected { + st, ok := actual[k] + require.True(t, ok, "status for key: %s not found", k) + assert.Equal(t, expectation.healthy, st.Healthy) + assert.Equal(t, expectation.status, st.Status()) + assert.Equal(t, !component.StatusIsError(expectation.status), st.Healthy) + if expectation.err != nil { + assert.Equal(t, expectation.err.Error(), st.Error) + } + assertNestedStatus(t, expectation.nestedStatus, st.ComponentStatuses) + } +} + +func assertStatusSimple( + t *testing.T, + expected *componentStatusExpectation, + actual *serializableStatus, +) { + assert.Equal(t, expected.status, actual.Status()) + assert.Equal(t, !component.StatusIsError(expected.status), actual.Healthy) + if expected.err != nil { + assert.Equal(t, expected.err.Error(), actual.Error) + } + assert.Nil(t, actual.ComponentStatuses) +} + +func TestConfig(t *testing.T) { + var server *Server + confMap, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + confJSON, err := os.ReadFile(filepath.Clean(filepath.Join("testdata", "config.json"))) + require.NoError(t, err) + + for _, tc := range []struct { + name string + settings *Settings + setup func() + expectedStatusCode int + expectedBody []byte + }{ + { + name: "config not notified", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{ + Enabled: true, + Path: "/config", + }, + Status: StatusSettings{ + PathSettings: PathSettings{ + Enabled: false, + }, + }, + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: []byte{}, + }, + { + name: "config notified", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{ + Enabled: true, + Path: "/config", + }, + Status: StatusSettings{ + PathSettings: PathSettings{ + Enabled: false, + }, + }, + }, + setup: func() { + require.NoError(t, server.NotifyConfig(context.Background(), confMap)) + }, + expectedStatusCode: http.StatusOK, + expectedBody: confJSON, + }, + { + name: "config disabled", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{ + Enabled: false, + }, + Status: StatusSettings{ + PathSettings: PathSettings{ + Enabled: false, + }, + }, + }, + expectedStatusCode: http.StatusNotFound, + expectedBody: []byte("404 page not found\n"), + }, + } { + t.Run(tc.name, func(t *testing.T) { + server = NewServer( + tc.settings, + componenttest.NewNopTelemetrySettings(), + 20*time.Millisecond, + status.NewAggregator(), + ) + + require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) + defer func() { require.NoError(t, server.Shutdown(context.Background())) }() + + client := &http.Client{} + url := fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Config.Path) + + if tc.setup != nil { + tc.setup() + } + + resp, err := client.Get(url) + require.NoError(t, err) + assert.Equal(t, tc.expectedStatusCode, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.Equal(t, tc.expectedBody, body) + }) + } + +} diff --git a/extension/healthcheckextensionv2/internal/http/testdata/config.json b/extension/healthcheckextensionv2/internal/http/testdata/config.json new file mode 100644 index 0000000000000..55dc317f7c4da --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/testdata/config.json @@ -0,0 +1 @@ +{"exporters":{"nop":null,"nop/myexporter":null},"extensions":{"nop":null,"nop/myextension":null},"processors":{"nop":null,"nop/myprocessor":null},"receivers":{"nop":null,"nop/myreceiver":null},"service":{"extensions":["nop"],"pipelines":{"traces":{"exporters":["nop"],"processors":["nop"],"receivers":["nop"]}}}} \ No newline at end of file diff --git a/extension/healthcheckextensionv2/internal/http/testdata/config.yaml b/extension/healthcheckextensionv2/internal/http/testdata/config.yaml new file mode 100644 index 0000000000000..38227d7a68bc4 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/testdata/config.yaml @@ -0,0 +1,23 @@ +receivers: + nop: + nop/myreceiver: + +processors: + nop: + nop/myprocessor: + +exporters: + nop: + nop/myexporter: + +extensions: + nop: + nop/myextension: + +service: + extensions: [nop] + pipelines: + traces: + receivers: [nop] + processors: [nop] + exporters: [nop] diff --git a/extension/healthcheckextensionv2/internal/metadata/generated_status.go b/extension/healthcheckextensionv2/internal/metadata/generated_status.go new file mode 100644 index 0000000000000..0a5dc799fff52 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/metadata/generated_status.go @@ -0,0 +1,22 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" +) + +const ( + Type = "healthcheckv2" + ExtensionStability = component.StabilityLevelDevelopment +) + +func Meter(settings component.TelemetrySettings) metric.Meter { + return settings.MeterProvider.Meter("otelcol") +} + +func Tracer(settings component.TelemetrySettings) trace.Tracer { + return settings.TracerProvider.Tracer("otelcol") +} diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go new file mode 100644 index 0000000000000..bf8dc5f9a0f3f --- /dev/null +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -0,0 +1,277 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package status // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + +import ( + "fmt" + "sync" + + "go.opentelemetry.io/collector/component" +) + +// CollectorStatusDetails holds a snapshot of the current overall collector status, the overall +// pipeline statuses, and the statuses of the individual components within the pipelines. +type CollectorStatusDetails struct { + OverallStatus *component.StatusEvent + PipelineStatusMap map[component.ID]*component.StatusEvent + ComponentStatusMap map[component.ID]map[*component.InstanceID]*component.StatusEvent +} + +// PipelineStatusDetails holds a snapshot of the current overall pipeline status, and the statuses +// of the individual components in the pipeline. +type PipelineStatusDetails struct { + OverallStatus *component.StatusEvent + ComponentStatusMap map[*component.InstanceID]*component.StatusEvent +} + +type componentIDCache struct { + mu sync.RWMutex + componentIDMap map[string]component.ID +} + +func (c *componentIDCache) lookup(name string) (component.ID, error) { + compID, ok := func() (component.ID, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + id, ok := c.componentIDMap[name] + return id, ok + }() + + if ok { + return compID, nil + } + + err := compID.UnmarshalText([]byte(name)) + if err == nil { + c.mu.Lock() + c.componentIDMap[name] = compID + c.mu.Unlock() + } + + return compID, err +} + +// Extensions are treated as a pseudo pipeline and extsID is used as a map key +var extsID = component.NewID("extensions") +var extsIDMap = map[component.ID]struct{}{extsID: {}} + +// The empty string is an alias for the overall collector health when subscribing to +// status events. +const emptyStream = "" + +// CollectorID is used as a key in the subscriptions map +var collectorID = component.NewID("__collector__") + +// Aggregator records individual status events for components and aggregates statuses for the +// pipelines they belong to and the collector overall. +type Aggregator struct { + mu sync.RWMutex + componentIDCache *componentIDCache + overallStatus *component.StatusEvent + pipelineStatusMap map[component.ID]*component.StatusEvent + componentStatusMap map[component.ID]map[*component.InstanceID]*component.StatusEvent + subscriptions map[component.ID][]chan *component.StatusEvent +} + +// NewAggregator returns a *status.Aggregator. +func NewAggregator() *Aggregator { + return &Aggregator{ + overallStatus: &component.StatusEvent{}, + pipelineStatusMap: make(map[component.ID]*component.StatusEvent), + componentStatusMap: make(map[component.ID]map[*component.InstanceID]*component.StatusEvent), + componentIDCache: &componentIDCache{ + componentIDMap: make(map[string]component.ID), + }, + subscriptions: make(map[component.ID][]chan *component.StatusEvent), + } +} + +// CollectorStatus returns the overall status for the collector. +func (a *Aggregator) CollectorStatus() *component.StatusEvent { + a.mu.RLock() + defer a.mu.RUnlock() + + return a.overallStatus +} + +// CollectorStatusDetailed returns a snapshot of the current overall collector status, pipeline +// statuses, and individual component statuses. +func (a *Aggregator) CollectorStatusDetailed() *CollectorStatusDetails { + a.mu.RLock() + defer a.mu.RUnlock() + + details := &CollectorStatusDetails{ + OverallStatus: a.overallStatus, + PipelineStatusMap: make(map[component.ID]*component.StatusEvent, len(a.pipelineStatusMap)), + ComponentStatusMap: make( + map[component.ID]map[*component.InstanceID]*component.StatusEvent, + len(a.componentStatusMap), + ), + } + + for compID, ev := range a.pipelineStatusMap { + details.PipelineStatusMap[compID] = ev + } + + for compID, eventMap := range a.componentStatusMap { + details.ComponentStatusMap[compID] = make( + map[*component.InstanceID]*component.StatusEvent, + len(eventMap), + ) + for instID, ev := range eventMap { + details.ComponentStatusMap[compID][instID] = ev + } + } + + return details +} + +// PipelineStatus returns the current overall pipeline status. An error will be returned if the +// pipeline is not found, or if there was an error marshaling the name to a component.ID. +func (a *Aggregator) PipelineStatus(name string) (*component.StatusEvent, error) { + compID, err := a.componentIDCache.lookup(name) + if err != nil { + return nil, err + } + + a.mu.RLock() + defer a.mu.RUnlock() + + ev, ok := a.pipelineStatusMap[compID] + if !ok { + return nil, fmt.Errorf("pipeline not found: %s", name) + } + + return ev, nil +} + +// PipelineStatusDetailed returns the current overall pipeline status and the invidiual statuses of +// the components within the pipeline. An error will be returned if the pipeline is not found, or if +// there was an error marshaling the name to a component.ID. +func (a *Aggregator) PipelineStatusDetailed(name string) (*PipelineStatusDetails, error) { + compID, err := a.componentIDCache.lookup(name) + if err != nil { + return nil, err + } + + a.mu.RLock() + defer a.mu.RUnlock() + + ev, ok := a.pipelineStatusMap[compID] + if !ok { + return nil, fmt.Errorf("pipeline not found: %s", name) + } + + details := &PipelineStatusDetails{ + OverallStatus: ev, + ComponentStatusMap: make( + map[*component.InstanceID]*component.StatusEvent, + len(a.componentStatusMap), + ), + } + + for instanceID, ev := range a.componentStatusMap[compID] { + details.ComponentStatusMap[instanceID] = ev + } + + return details, nil +} + +// RecordStatus stores and aggregates a StatusEvent for the given component instance. +func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component.StatusEvent) { + compIDs := source.PipelineIDs + // extensions are treated as a pseudo-pipeline + if source.Kind == component.KindExtension { + compIDs = extsIDMap + } + + a.mu.Lock() + defer a.mu.Unlock() + + for compID := range compIDs { + var compStatuses map[*component.InstanceID]*component.StatusEvent + compStatuses, ok := a.componentStatusMap[compID] + if !ok { + compStatuses = make(map[*component.InstanceID]*component.StatusEvent) + } + compStatuses[source] = event + a.componentStatusMap[compID] = compStatuses + + pipelineStatus := component.AggregateStatusEvent(compStatuses) + a.pipelineStatusMap[compID] = pipelineStatus + a.notifySubscribers(compID, pipelineStatus) + } + + overallStatus := component.AggregateStatusEvent(a.pipelineStatusMap) + a.overallStatus = overallStatus + a.notifySubscribers(collectorID, overallStatus) +} + +// Subscribe allows you to subscribe to a stream of events for a pipline by passing in the +// pipeline name. The empty string can be used as an alias to subscribe to the collector health +// overall. It is possible to subscribe to a pipeline that has not yet reported. An initial nil +// will be sent on the channel and events will start streaming if and when it starts reporting. +func (a *Aggregator) Subscribe(name string) (<-chan *component.StatusEvent, error) { + a.mu.Lock() + defer a.mu.Unlock() + + var compID component.ID + var ev *component.StatusEvent + + if name == emptyStream { + compID = collectorID + ev = a.overallStatus + } else { + var err error + compID, err = a.componentIDCache.lookup(name) + if err != nil { + return nil, err + } + ev = a.pipelineStatusMap[compID] + } + + eventCh := make(chan *component.StatusEvent, 1) + a.subscriptions[compID] = append(a.subscriptions[compID], eventCh) + eventCh <- ev + + return eventCh, nil +} + +// Unbsubscribe removes a stream from further status updates. +func (a *Aggregator) Unsubscribe(eventCh <-chan *component.StatusEvent) { + a.mu.Lock() + defer a.mu.Unlock() + + for compID, subs := range a.subscriptions { + for i, sub := range subs { + if sub == eventCh { + a.subscriptions[compID] = append(subs[:i], subs[i+1:]...) + return + } + } + } +} + +// Close terminates all existing subscriptions. +func (a *Aggregator) Close() { + a.mu.Lock() + defer a.mu.Unlock() + + for _, subs := range a.subscriptions { + for _, sub := range subs { + close(sub) + } + } +} + +func (a *Aggregator) notifySubscribers(compID component.ID, event *component.StatusEvent) { + for _, sub := range a.subscriptions[compID] { + // clear unread events + select { + case <-sub: + default: + } + sub <- event + } +} diff --git a/extension/healthcheckextensionv2/internal/status/aggregator_test.go b/extension/healthcheckextensionv2/internal/status/aggregator_test.go new file mode 100644 index 0000000000000..a105f2e9c2235 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/status/aggregator_test.go @@ -0,0 +1,351 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package status_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" +) + +func TestCollectorStatus(t *testing.T) { + agg := status.NewAggregator() + traces := testhelpers.NewPipelineMetadata("traces") + + t.Run("zero value", func(t *testing.T) { + assert.Equal(t, component.StatusNone, agg.CollectorStatus().Status()) + }) + + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + + t.Run("pipeline statuses all successful", func(t *testing.T) { + assert.Equal(t, component.StatusOK, agg.CollectorStatus().Status()) + }) + + agg.RecordStatus( + traces.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + + t.Run("pipeline with recoverable error", func(t *testing.T) { + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + agg.CollectorStatus(), + ) + }) + + agg.RecordStatus( + traces.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + + t.Run("pipeline with permanent error", func(t *testing.T) { + assertErrorEventsMatch(t, + component.StatusPermanentError, + assert.AnError, + agg.CollectorStatus(), + ) + }) +} + +func TestCollectorStatusDetailed(t *testing.T) { + agg := status.NewAggregator() + traces := testhelpers.NewPipelineMetadata("traces") + + t.Run("zero value", func(t *testing.T) { + dst := agg.CollectorStatusDetailed() + assertEventsMatch(t, component.StatusNone, agg.CollectorStatus(), dst.OverallStatus) + assert.Empty(t, dst.PipelineStatusMap) + assert.Empty(t, dst.ComponentStatusMap) + }) + + // Seed aggregator with successful statuses for pipeline. + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + + t.Run("pipeline statuses all successful", func(t *testing.T) { + dst := agg.CollectorStatusDetailed() + + // CollectorStatus, OverAllStatus, and PipelineStatus match. + assertEventsMatch(t, + component.StatusOK, + agg.CollectorStatus(), + dst.OverallStatus, + dst.PipelineStatusMap[traces.PipelineID], + ) + + // Component statuses match + assertEventsMatch(t, + component.StatusOK, + collectEvents(dst.ComponentStatusMap[traces.PipelineID], traces.InstanceIDs()...)..., + ) + }) + + // Record an error in the traces exporter + agg.RecordStatus( + traces.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + + t.Run("pipeline with exporter error", func(t *testing.T) { + dst := agg.CollectorStatusDetailed() + + // CollectorStatus, OverAllStatus, and PipelineStatus match. + assertErrorEventsMatch( + t, + component.StatusRecoverableError, + assert.AnError, + agg.CollectorStatus(), + dst.OverallStatus, + dst.PipelineStatusMap[traces.PipelineID], + ) + + // Component statuses match + assertEventsMatch(t, + component.StatusOK, + collectEvents( + dst.ComponentStatusMap[traces.PipelineID], traces.ReceiverID, traces.ProcessorID, + )..., + ) + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + dst.ComponentStatusMap[traces.PipelineID][traces.ExporterID], + ) + }) + +} + +func TestPipelineStatus(t *testing.T) { + agg := status.NewAggregator() + traces := testhelpers.NewPipelineMetadata("traces") + + t.Run("non existent pipeline", func(t *testing.T) { + st, err := agg.PipelineStatus("doesnotexist") + assert.Nil(t, st) + assert.Error(t, err) + }) + + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + + t.Run("pipeline exists / status successful", func(t *testing.T) { + st, err := agg.PipelineStatus(traces.PipelineID.String()) + require.NoError(t, err) + assertEventsMatch(t, component.StatusOK, agg.CollectorStatus(), st) + }) + + agg.RecordStatus( + traces.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + + t.Run("pipeline exists / exporter error", func(t *testing.T) { + st, err := agg.PipelineStatus(traces.PipelineID.String()) + require.NoError(t, err) + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + agg.CollectorStatus(), + st, + ) + }) +} + +func TestPipelineStatusDetailed(t *testing.T) { + agg := status.NewAggregator() + traces := testhelpers.NewPipelineMetadata("traces") + + t.Run("non existent pipeline", func(t *testing.T) { + dst, err := agg.PipelineStatusDetailed("doesnotexist") + assert.Nil(t, dst) + assert.Error(t, err) + }) + + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + + t.Run("pipeline exists / status successful", func(t *testing.T) { + dst, err := agg.PipelineStatusDetailed(traces.PipelineID.String()) + require.NoError(t, err) + + // CollectorStatus, OverAllStatus, match. + assertEventsMatch(t, + component.StatusOK, + agg.CollectorStatus(), + dst.OverallStatus, + ) + + // Component statuses match + assertEventsMatch(t, + component.StatusOK, + collectEvents(dst.ComponentStatusMap, traces.InstanceIDs()...)..., + ) + }) + + agg.RecordStatus( + traces.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + + t.Run("pipeline exists / exporter error", func(t *testing.T) { + dst, err := agg.PipelineStatusDetailed(traces.PipelineID.String()) + require.NoError(t, err) + + // CollectorStatus, OverAllStatus, match. + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + agg.CollectorStatus(), + dst.OverallStatus, + ) + + // Component statuses match + assertEventsMatch(t, + component.StatusOK, + collectEvents(dst.ComponentStatusMap, traces.ReceiverID, traces.ProcessorID)..., + ) + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + dst.ComponentStatusMap[traces.ExporterID], + ) + + }) +} + +func TestStreaming(t *testing.T) { + agg := status.NewAggregator() + defer agg.Close() + + traces := testhelpers.NewPipelineMetadata("traces") + metrics := testhelpers.NewPipelineMetadata("metrics") + + traceEvents, err := agg.Subscribe(traces.PipelineID.String()) + require.NoError(t, err) + metricEvents, err := agg.Subscribe(metrics.PipelineID.String()) + require.NoError(t, err) + allEvents, err := agg.Subscribe("") + require.NoError(t, err) + + assert.Nil(t, <-traceEvents) + assert.Nil(t, <-metricEvents) + assert.NotNil(t, <-allEvents) + + // Start pipelines + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusStarting) + assertEventsRecvdMatch(t, component.StatusStarting, traceEvents, allEvents) + testhelpers.SeedAggregator(agg, metrics.InstanceIDs(), component.StatusStarting) + assertEventsRecvdMatch(t, component.StatusStarting, metricEvents, allEvents) + + // Successful start + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + assertEventsRecvdMatch(t, component.StatusOK, traceEvents) + // All is still in StatusStarting until the metrics pipeline reports OK + assertEventsRecvdMatch(t, component.StatusStarting, allEvents) + testhelpers.SeedAggregator(agg, metrics.InstanceIDs(), component.StatusOK) + assertEventsRecvdMatch(t, component.StatusOK, metricEvents, allEvents) + + // Traces Pipeline RecoverableError + agg.RecordStatus(traces.ExporterID, component.NewRecoverableErrorEvent(assert.AnError)) + assertErrorEventsRecvdMatch(t, + component.StatusRecoverableError, + assert.AnError, + traceEvents, + allEvents, + ) + + // Traces Pipeline Recover + agg.RecordStatus(traces.ExporterID, component.NewStatusEvent(component.StatusOK)) + assertEventsRecvdMatch(t, + component.StatusOK, + traceEvents, + allEvents, + ) + + // Stopping + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusStopping) + assertEventsRecvdMatch(t, component.StatusStopping, traceEvents, allEvents) + testhelpers.SeedAggregator(agg, metrics.InstanceIDs(), component.StatusStopping) + assertEventsRecvdMatch(t, component.StatusStopping, metricEvents, allEvents) + + // Stopped + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusStopped) + // All is not stopped until the metrics pipeline is stopped + assertEventsRecvdMatch(t, component.StatusStopped, traceEvents) + testhelpers.SeedAggregator(agg, metrics.InstanceIDs(), component.StatusStopped) + assertEventsRecvdMatch(t, component.StatusStopped, metricEvents, allEvents) +} + +// assertEventMatches ensures one or more events share the expected status and are +// otherwise equal, ignoring timestamp. +func assertEventsMatch( + t *testing.T, + expectedStatus component.Status, + events ...*component.StatusEvent, +) { + err0 := events[0].Err() + for _, ev := range events { + assert.Equal(t, expectedStatus, ev.Status()) + assert.Equal(t, err0, ev.Err()) + } +} + +// assertErrorEventMatches compares one or more status events with the expected +// status and expected error. +func assertErrorEventsMatch( + t *testing.T, + expectedStatus component.Status, + expectedErr error, + events ...*component.StatusEvent, +) { + assert.True(t, component.StatusIsError(expectedStatus)) + for _, ev := range events { + assert.Equal(t, expectedStatus, ev.Status()) + assert.Equal(t, expectedErr, ev.Err()) + } +} + +// collectEvents returns a slice of events collected from the componentMap using +// the provided instanceIDs +func collectEvents( + componentMap map[*component.InstanceID]*component.StatusEvent, + instanceIDs ...*component.InstanceID, +) (result []*component.StatusEvent) { + for _, id := range instanceIDs { + result = append(result, componentMap[id]) + } + return +} + +func assertEventsRecvdMatch(t *testing.T, + expectedStatus component.Status, + chans ...<-chan *component.StatusEvent, +) { + var err0 error + for i, evCh := range chans { + ev := <-evCh + if i == 0 { + err0 = ev.Err() + } + assert.Equal(t, expectedStatus, ev.Status()) + assert.Equal(t, err0, ev.Err()) + } +} + +func assertErrorEventsRecvdMatch(t *testing.T, + expectedStatus component.Status, + expectedErr error, + chans ...<-chan *component.StatusEvent, +) { + assert.True(t, component.StatusIsError(expectedStatus)) + for _, evCh := range chans { + ev := <-evCh + assert.Equal(t, expectedStatus, ev.Status()) + assert.Equal(t, expectedErr, ev.Err()) + } +} diff --git a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go new file mode 100644 index 0000000000000..a5516244b6b43 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package testhelpers // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + +import ( + "go.opentelemetry.io/collector/component" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +) + +// PipelineMetadata groups together component and instance IDs for a hypothetical pipeline used +// for testing purposes. +type PipelineMetadata struct { + PipelineID component.ID + ReceiverID *component.InstanceID + ProcessorID *component.InstanceID + ExporterID *component.InstanceID +} + +// InstanceIDs returns a slice of instanceIDs for components within the hypothetical pipeline. +func (p *PipelineMetadata) InstanceIDs() []*component.InstanceID { + return []*component.InstanceID{p.ReceiverID, p.ProcessorID, p.ExporterID} +} + +// Returns a metadata for a hypothetical pipeline. +func NewPipelineMetadata(typeVal component.Type) *PipelineMetadata { + pipelineID := component.NewID(typeVal) + return &PipelineMetadata{ + PipelineID: pipelineID, + ReceiverID: &component.InstanceID{ + ID: component.NewIDWithName(typeVal, "in"), + Kind: component.KindReceiver, + PipelineIDs: map[component.ID]struct{}{ + pipelineID: {}, + }, + }, + ProcessorID: &component.InstanceID{ + ID: component.NewID("batch"), + Kind: component.KindProcessor, + PipelineIDs: map[component.ID]struct{}{ + pipelineID: {}, + }, + }, + ExporterID: &component.InstanceID{ + ID: component.NewIDWithName(typeVal, "out"), + Kind: component.KindExporter, + PipelineIDs: map[component.ID]struct{}{ + pipelineID: {}, + }, + }, + } +} + +// Returns a map of hypothetical pipelines identified by their stringified typeVal. +func NewPipelines(typeVals ...component.Type) map[string]*PipelineMetadata { + result := make(map[string]*PipelineMetadata, len(typeVals)) + for _, val := range typeVals { + result[string(val)] = NewPipelineMetadata(val) + } + return result +} + +// SeedAggregator records a status event for each instanceID. +func SeedAggregator( + agg *status.Aggregator, + instanceIDs []*component.InstanceID, + statuses ...component.Status, +) { + for _, st := range statuses { + for _, id := range instanceIDs { + agg.RecordStatus(id, component.NewStatusEvent(st)) + } + } +} diff --git a/extension/healthcheckextensionv2/metadata.yaml b/extension/healthcheckextensionv2/metadata.yaml new file mode 100644 index 0000000000000..075e00471d6aa --- /dev/null +++ b/extension/healthcheckextensionv2/metadata.yaml @@ -0,0 +1,9 @@ +type: healthcheckv2 + +status: + class: extension + stability: + development: [extension] + distributions: [contrib] + codeowners: + active: [mwear] diff --git a/reports/distributions/contrib.yaml b/reports/distributions/contrib.yaml index 1a4b4b57a2256..5303cb71a7c7a 100644 --- a/reports/distributions/contrib.yaml +++ b/reports/distributions/contrib.yaml @@ -67,6 +67,7 @@ components: - file_storage - headers_setter - health_check + - healthcheckv2 - host_observer - jaegerremotesampling - k8s_observer From 3a19a5a5960609135b4e9b8d79fbac0e368ce66c Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Wed, 24 Jan 2024 19:36:50 -0800 Subject: [PATCH 02/26] Refactor aggregator There were previously different types for CollectorStatus and PipelineStatus that have been unified with a newly introduced AggregateStatus type. It cleaned up a lot of special cases. --- .../healthcheckextensionv2/extension_test.go | 21 +- .../internal/grpc/grpc.go | 32 +- .../internal/http/handlers.go | 46 +-- .../internal/http/serialization.go | 70 +--- .../internal/status/aggregator.go | 302 ++++++++---------- .../internal/status/aggregator_test.go | 270 ++++++++++------ 6 files changed, 336 insertions(+), 405 deletions(-) diff --git a/extension/healthcheckextensionv2/extension_test.go b/extension/healthcheckextensionv2/extension_test.go index 0c05b353f7c78..33b0f91592878 100644 --- a/extension/healthcheckextensionv2/extension_test.go +++ b/extension/healthcheckextensionv2/extension_test.go @@ -20,6 +20,7 @@ import ( "go.opentelemetry.io/collector/confmap/confmaptest" "go.opentelemetry.io/collector/extension/extensiontest" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" ) @@ -31,7 +32,9 @@ func TestComponentStatus(t *testing.T) { ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) // Status before Start will be StatusNone - assert.Equal(t, ext.aggregator.CollectorStatus().Status(), component.StatusNone) + st, ok := ext.aggregator.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) + assert.Equal(t, st.StatusEvent.Status(), component.StatusNone) require.NoError(t, ext.Start(context.Background(), componenttest.NewNopHost())) @@ -50,13 +53,17 @@ func TestComponentStatus(t *testing.T) { // Note the use of assert.Eventually here and throughout this test is because // status events are processed asynchronously in the background. assert.Eventually(t, func() bool { - return ext.aggregator.CollectorStatus().Status() == component.StatusStarting + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) + return st.StatusEvent.Status() == component.StatusStarting }, time.Second, 10*time.Millisecond) require.NoError(t, ext.Ready()) assert.Eventually(t, func() bool { - return ext.aggregator.CollectorStatus().Status() == component.StatusOK + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) + return st.StatusEvent.Status() == component.StatusOK }, time.Second, 10*time.Millisecond) // StatusStopping will be sent immediately. @@ -65,7 +72,9 @@ func TestComponentStatus(t *testing.T) { } assert.Eventually(t, func() bool { - return ext.aggregator.CollectorStatus().Status() == component.StatusStopping + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) + return st.StatusEvent.Status() == component.StatusStopping }, time.Second, 10*time.Millisecond) require.NoError(t, ext.NotReady()) @@ -76,7 +85,9 @@ func TestComponentStatus(t *testing.T) { ext.ComponentStatusChanged(id, component.NewStatusEvent(component.StatusStopped)) } - assert.Equal(t, component.StatusStopping, ext.aggregator.CollectorStatus().Status()) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) + assert.Equal(t, component.StatusStopping, st.StatusEvent.Status()) } func TestNotifyConfig(t *testing.T) { diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index fd73511493288..adea37a3ecd2f 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -17,29 +17,18 @@ func (s *Server) Check( _ context.Context, req *healthpb.HealthCheckRequest, ) (*healthpb.HealthCheckResponse, error) { - var err error - var ev *component.StatusEvent - - if req.Service == "" { - ev = s.aggregator.CollectorStatus() - } else { - ev, err = s.aggregator.PipelineStatus(req.Service) - } - - if err != nil { + st, ok := s.aggregator.AggregateStatus(req.Service, false) + if !ok { return nil, status.Error(codes.NotFound, "unknown service") } return &healthpb.HealthCheckResponse{ - Status: s.toServingStatus(ev), + Status: s.toServingStatus(st.StatusEvent), }, nil } func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_WatchServer) error { - sub, err := s.aggregator.Subscribe(req.Service) - if err != nil { - return err - } + sub := s.aggregator.Subscribe(req.Service, false) defer s.aggregator.Unsubscribe(sub) var lastServingStatus healthpb.HealthCheckResponse_ServingStatus = -1 @@ -49,17 +38,16 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ for { select { - case ev, ok := <-sub: + case st, ok := <-sub: if !ok { return status.Error(codes.Canceled, "Server shutting down.") } - var sst healthpb.HealthCheckResponse_ServingStatus switch { - case ev == nil: + case st == nil: sst = healthpb.HealthCheckResponse_SERVICE_UNKNOWN - case ev.Status() == component.StatusRecoverableError: + case st.StatusEvent.Status() == component.StatusRecoverableError: failureTicker.Reset(s.recoveryDuration) sst = lastServingStatus if lastServingStatus == -1 { @@ -67,7 +55,7 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ } default: failureTicker.Stop() - sst = statusToServingStatusMap[ev.Status()] + sst = statusToServingStatusMap[st.StatusEvent.Status()] } if lastServingStatus == sst { @@ -111,7 +99,9 @@ var statusToServingStatusMap = map[component.Status]healthpb.HealthCheckResponse component.StatusStopped: healthpb.HealthCheckResponse_NOT_SERVING, } -func (s *Server) toServingStatus(ev *component.StatusEvent) healthpb.HealthCheckResponse_ServingStatus { +func (s *Server) toServingStatus( + ev *component.StatusEvent, +) healthpb.HealthCheckResponse_ServingStatus { if ev.Status() == component.StatusRecoverableError && time.Now().Compare(ev.Timestamp().Add(s.recoveryDuration)) == 1 { return healthpb.HealthCheckResponse_NOT_SERVING diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go index 77fec1084a7b1..2ad0398c8c074 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -12,20 +12,16 @@ import ( func (s *Server) statusHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var err error - var sst *serializableStatus pipeline := r.URL.Query().Get("pipeline") - if pipeline == "" { - sst = s.collectorSerializableStatus() - } else { - sst, err = s.pipelineSerializableStatus(pipeline) - if err != nil { - w.WriteHeader(http.StatusNotFound) - return - } + st, ok := s.aggregator.AggregateStatus(pipeline, s.settings.Status.Detailed) + if !ok { + w.WriteHeader(http.StatusNotFound) + return } + sst := toSerializableStatus(st, s.startTimestamp, s.recoveryDuration) + w.Header().Set("Content-Type", "application/json") w.WriteHeader(s.toHTTPStatus(sst)) @@ -49,36 +45,6 @@ func (s *Server) configHandler() http.Handler { }) } -func (s *Server) collectorSerializableStatus() *serializableStatus { - if s.settings.Status.Detailed { - details := s.aggregator.CollectorStatusDetailed() - return toCollectorSerializableStatus(details, s.startTimestamp, s.recoveryDuration) - } - - return toSerializableStatus( - s.aggregator.CollectorStatus(), - s.startTimestamp, - s.recoveryDuration, - ) -} - -func (s *Server) pipelineSerializableStatus(pipeline string) (*serializableStatus, error) { - if s.settings.Status.Detailed { - details, err := s.aggregator.PipelineStatusDetailed(pipeline) - if err != nil { - return nil, err - } - return toPipelineSerializableStatus(details, s.startTimestamp, s.recoveryDuration), nil - } - - ev, err := s.aggregator.PipelineStatus(pipeline) - if err != nil { - return nil, err - } - - return toSerializableStatus(ev, s.startTimestamp, s.recoveryDuration), nil -} - var responseCodes = map[component.Status]int{ component.StatusNone: http.StatusServiceUnavailable, component.StatusStarting: http.StatusServiceUnavailable, diff --git a/extension/healthcheckextensionv2/internal/http/serialization.go b/extension/healthcheckextensionv2/internal/http/serialization.go index cdb87b863d554..84a10375cb933 100644 --- a/extension/healthcheckextensionv2/internal/http/serialization.go +++ b/extension/healthcheckextensionv2/internal/http/serialization.go @@ -4,7 +4,6 @@ package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" import ( - "fmt" "time" "go.opentelemetry.io/collector/component" @@ -60,69 +59,37 @@ func toSerializableEvent( return se } -var extsKey = "extensions" - func toSerializableStatus( - ev *component.StatusEvent, - startTimestamp time.Time, - recoveryDuration time.Duration, -) *serializableStatus { - return &serializableStatus{ - StartTimestamp: &startTimestamp, - SerializableEvent: toSerializableEvent(ev, time.Now(), recoveryDuration), - } -} - -func toCollectorSerializableStatus( - details *status.CollectorStatusDetails, + st *status.AggregateStatus, startTimestamp time.Time, recoveryDuration time.Duration, ) *serializableStatus { now := time.Now() s := &serializableStatus{ StartTimestamp: &startTimestamp, - SerializableEvent: toSerializableEvent(details.OverallStatus, now, recoveryDuration), + SerializableEvent: toSerializableEvent(st.StatusEvent, now, recoveryDuration), ComponentStatuses: make(map[string]*serializableStatus), } - for compID, ev := range details.PipelineStatusMap { - key := compID.String() - if key != extsKey { - key = "pipeline:" + key - } - cs := &serializableStatus{ - SerializableEvent: toSerializableEvent(ev, now, recoveryDuration), - ComponentStatuses: make(map[string]*serializableStatus), - } - s.ComponentStatuses[key] = cs - for instance, ev := range details.ComponentStatusMap[compID] { - key := fmt.Sprintf("%s:%s", kindToString(instance.Kind), instance.ID) - cs.ComponentStatuses[key] = &serializableStatus{ - SerializableEvent: toSerializableEvent(ev, now, recoveryDuration), - } - } + for k, cs := range st.ComponentStatusMap { + s.ComponentStatuses[k] = toComponentSerializableStatus(cs, now, recoveryDuration) } return s } -func toPipelineSerializableStatus( - details *status.PipelineStatusDetails, - startTimestamp time.Time, +func toComponentSerializableStatus( + st *status.AggregateStatus, + now time.Time, recoveryDuration time.Duration, ) *serializableStatus { - now := time.Now() s := &serializableStatus{ - StartTimestamp: &startTimestamp, - SerializableEvent: toSerializableEvent(details.OverallStatus, now, recoveryDuration), + SerializableEvent: toSerializableEvent(st.StatusEvent, now, recoveryDuration), ComponentStatuses: make(map[string]*serializableStatus), } - for instance, ev := range details.ComponentStatusMap { - key := fmt.Sprintf("%s:%s", kindToString(instance.Kind), instance.ID) - s.ComponentStatuses[key] = &serializableStatus{ - SerializableEvent: toSerializableEvent(ev, now, recoveryDuration), - } + for k, cs := range st.ComponentStatusMap { + s.ComponentStatuses[k] = toComponentSerializableStatus(cs, now, recoveryDuration) } return s @@ -136,20 +103,3 @@ func isHealthy(ev *component.StatusEvent, now time.Time, recoveryDuration time.D return !component.StatusIsError(ev.Status()) } - -// TODO: implemnent Stringer on Kind in core -func kindToString(k component.Kind) string { - switch k { - case component.KindReceiver: - return "receiver" - case component.KindProcessor: - return "processor" - case component.KindExporter: - return "exporter" - case component.KindExtension: - return "extension" - case component.KindConnector: - return "connector" - } - return "" -} diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go index bf8dc5f9a0f3f..6b84e74ac72d4 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -10,243 +10,169 @@ import ( "go.opentelemetry.io/collector/component" ) -// CollectorStatusDetails holds a snapshot of the current overall collector status, the overall -// pipeline statuses, and the statuses of the individual components within the pipelines. -type CollectorStatusDetails struct { - OverallStatus *component.StatusEvent - PipelineStatusMap map[component.ID]*component.StatusEvent - ComponentStatusMap map[component.ID]map[*component.InstanceID]*component.StatusEvent -} +// Extensions are treated as a pseudo pipeline and extsID is used as a map key +var ( + extsID = component.NewID("extensions") + extsIDMap = map[component.ID]struct{}{extsID: {}} +) -// PipelineStatusDetails holds a snapshot of the current overall pipeline status, and the statuses -// of the individual components in the pipeline. -type PipelineStatusDetails struct { - OverallStatus *component.StatusEvent - ComponentStatusMap map[*component.InstanceID]*component.StatusEvent -} +const ( + ScopeAll = "" + ScopeExtensions = "extensions" + pipelinePrefix = "pipeline:" +) -type componentIDCache struct { - mu sync.RWMutex - componentIDMap map[string]component.ID -} +// AggregateStatus contains a map of child AggregateStatuses and an embedded component.StatusEvent. +// It can be used to represent a single, top-level status when the ComponentStatusMap is empty, +// or a nested structure when map is non-empty. +type AggregateStatus struct { + *component.StatusEvent -func (c *componentIDCache) lookup(name string) (component.ID, error) { - compID, ok := func() (component.ID, bool) { - c.mu.RLock() - defer c.mu.RUnlock() - id, ok := c.componentIDMap[name] - return id, ok - }() + ComponentStatusMap map[string]*AggregateStatus +} - if ok { - return compID, nil +func (a *AggregateStatus) clone(detailed bool) *AggregateStatus { + st := &AggregateStatus{ + StatusEvent: a.StatusEvent, } - err := compID.UnmarshalText([]byte(name)) - if err == nil { - c.mu.Lock() - c.componentIDMap[name] = compID - c.mu.Unlock() + if detailed && len(a.ComponentStatusMap) > 0 { + st.ComponentStatusMap = make(map[string]*AggregateStatus, len(a.ComponentStatusMap)) + for k, cs := range a.ComponentStatusMap { + st.ComponentStatusMap[k] = cs.clone(detailed) + } } - return compID, err + return st } -// Extensions are treated as a pseudo pipeline and extsID is used as a map key -var extsID = component.NewID("extensions") -var extsIDMap = map[component.ID]struct{}{extsID: {}} - -// The empty string is an alias for the overall collector health when subscribing to -// status events. -const emptyStream = "" - -// CollectorID is used as a key in the subscriptions map -var collectorID = component.NewID("__collector__") +type subscription struct { + statusCh chan *AggregateStatus + detailed bool +} // Aggregator records individual status events for components and aggregates statuses for the // pipelines they belong to and the collector overall. type Aggregator struct { - mu sync.RWMutex - componentIDCache *componentIDCache - overallStatus *component.StatusEvent - pipelineStatusMap map[component.ID]*component.StatusEvent - componentStatusMap map[component.ID]map[*component.InstanceID]*component.StatusEvent - subscriptions map[component.ID][]chan *component.StatusEvent + mu sync.RWMutex + aggregateStatus *AggregateStatus + subscriptions map[string][]*subscription } // NewAggregator returns a *status.Aggregator. func NewAggregator() *Aggregator { return &Aggregator{ - overallStatus: &component.StatusEvent{}, - pipelineStatusMap: make(map[component.ID]*component.StatusEvent), - componentStatusMap: make(map[component.ID]map[*component.InstanceID]*component.StatusEvent), - componentIDCache: &componentIDCache{ - componentIDMap: make(map[string]component.ID), + aggregateStatus: &AggregateStatus{ + StatusEvent: &component.StatusEvent{}, + ComponentStatusMap: make(map[string]*AggregateStatus), }, - subscriptions: make(map[component.ID][]chan *component.StatusEvent), + subscriptions: make(map[string][]*subscription), } } -// CollectorStatus returns the overall status for the collector. -func (a *Aggregator) CollectorStatus() *component.StatusEvent { - a.mu.RLock() - defer a.mu.RUnlock() - - return a.overallStatus -} - -// CollectorStatusDetailed returns a snapshot of the current overall collector status, pipeline -// statuses, and individual component statuses. -func (a *Aggregator) CollectorStatusDetailed() *CollectorStatusDetails { - a.mu.RLock() - defer a.mu.RUnlock() - - details := &CollectorStatusDetails{ - OverallStatus: a.overallStatus, - PipelineStatusMap: make(map[component.ID]*component.StatusEvent, len(a.pipelineStatusMap)), - ComponentStatusMap: make( - map[component.ID]map[*component.InstanceID]*component.StatusEvent, - len(a.componentStatusMap), - ), - } - - for compID, ev := range a.pipelineStatusMap { - details.PipelineStatusMap[compID] = ev - } - - for compID, eventMap := range a.componentStatusMap { - details.ComponentStatusMap[compID] = make( - map[*component.InstanceID]*component.StatusEvent, - len(eventMap), - ) - for instID, ev := range eventMap { - details.ComponentStatusMap[compID][instID] = ev - } - } - - return details -} - -// PipelineStatus returns the current overall pipeline status. An error will be returned if the -// pipeline is not found, or if there was an error marshaling the name to a component.ID. -func (a *Aggregator) PipelineStatus(name string) (*component.StatusEvent, error) { - compID, err := a.componentIDCache.lookup(name) - if err != nil { - return nil, err - } - - a.mu.RLock() - defer a.mu.RUnlock() +// AggregateStatus returns an *AggregateStatus for the given scope. The scope can be the collector +// overall (represented the empty string), extensions, or a pipeline by name. If detailed is true, +// the *AggregateStatus will contain child component statuses for the given scope. If it's false, +// only a top level status will be returned. The boolean return value indicates whether or not the +// scope was found. +func (a *Aggregator) AggregateStatus(scope string, detailed bool) (*AggregateStatus, bool) { + a.mu.Lock() + defer a.mu.Unlock() - ev, ok := a.pipelineStatusMap[compID] - if !ok { - return nil, fmt.Errorf("pipeline not found: %s", name) + if scope == ScopeAll { + return a.aggregateStatus.clone(detailed), true } - return ev, nil -} - -// PipelineStatusDetailed returns the current overall pipeline status and the invidiual statuses of -// the components within the pipeline. An error will be returned if the pipeline is not found, or if -// there was an error marshaling the name to a component.ID. -func (a *Aggregator) PipelineStatusDetailed(name string) (*PipelineStatusDetails, error) { - compID, err := a.componentIDCache.lookup(name) - if err != nil { - return nil, err + if scope != ScopeExtensions { + scope = pipelinePrefix + scope } - a.mu.RLock() - defer a.mu.RUnlock() - - ev, ok := a.pipelineStatusMap[compID] + st, ok := a.aggregateStatus.ComponentStatusMap[scope] if !ok { - return nil, fmt.Errorf("pipeline not found: %s", name) + return nil, false } - details := &PipelineStatusDetails{ - OverallStatus: ev, - ComponentStatusMap: make( - map[*component.InstanceID]*component.StatusEvent, - len(a.componentStatusMap), - ), - } - - for instanceID, ev := range a.componentStatusMap[compID] { - details.ComponentStatusMap[instanceID] = ev - } - - return details, nil + return st.clone(detailed), true } // RecordStatus stores and aggregates a StatusEvent for the given component instance. func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component.StatusEvent) { compIDs := source.PipelineIDs + prefix := pipelinePrefix // extensions are treated as a pseudo-pipeline if source.Kind == component.KindExtension { compIDs = extsIDMap + prefix = "" } a.mu.Lock() defer a.mu.Unlock() for compID := range compIDs { - var compStatuses map[*component.InstanceID]*component.StatusEvent - compStatuses, ok := a.componentStatusMap[compID] + var pipelineStatus *AggregateStatus + pipelineKey := prefix + compID.String() + pipelineStatus, ok := a.aggregateStatus.ComponentStatusMap[pipelineKey] if !ok { - compStatuses = make(map[*component.InstanceID]*component.StatusEvent) + pipelineStatus = &AggregateStatus{ + ComponentStatusMap: make(map[string]*AggregateStatus), + } } - compStatuses[source] = event - a.componentStatusMap[compID] = compStatuses - pipelineStatus := component.AggregateStatusEvent(compStatuses) - a.pipelineStatusMap[compID] = pipelineStatus - a.notifySubscribers(compID, pipelineStatus) + componentKey := fmt.Sprintf("%s:%s", kindToString(source.Kind), source.ID.String()) + pipelineStatus.ComponentStatusMap[componentKey] = &AggregateStatus{ + StatusEvent: event, + } + a.aggregateStatus.ComponentStatusMap[pipelineKey] = pipelineStatus + pipelineStatus.StatusEvent = component.AggregateStatusEvent( + toStatusEventMap(pipelineStatus), + ) + a.notifySubscribers(pipelineKey, pipelineStatus) } - overallStatus := component.AggregateStatusEvent(a.pipelineStatusMap) - a.overallStatus = overallStatus - a.notifySubscribers(collectorID, overallStatus) + a.aggregateStatus.StatusEvent = component.AggregateStatusEvent( + toStatusEventMap(a.aggregateStatus), + ) + a.notifySubscribers(ScopeAll, a.aggregateStatus) } -// Subscribe allows you to subscribe to a stream of events for a pipline by passing in the -// pipeline name. The empty string can be used as an alias to subscribe to the collector health -// overall. It is possible to subscribe to a pipeline that has not yet reported. An initial nil -// will be sent on the channel and events will start streaming if and when it starts reporting. -func (a *Aggregator) Subscribe(name string) (<-chan *component.StatusEvent, error) { +// Subscribe allows you to subscribe to a stream of events for the given scope. The scope can be +// the collector overall (represented by the emptry string), extensions, or a pipeline name. +// It is possible to subscribe to a pipeline that has not yet reported. An initial nil +// will be sent on the channel and events will start streaming if and when it starts reporting. If +// detailed is true, the *AggregateStatus will contain child statuses for the given scope. If it's +// false it will contain only a top-level status. +func (a *Aggregator) Subscribe(scope string, detailed bool) <-chan *AggregateStatus { a.mu.Lock() defer a.mu.Unlock() - var compID component.ID - var ev *component.StatusEvent - - if name == emptyStream { - compID = collectorID - ev = a.overallStatus - } else { - var err error - compID, err = a.componentIDCache.lookup(name) - if err != nil { - return nil, err + st := a.aggregateStatus + if scope != ScopeAll { + if scope != ScopeExtensions { + scope = pipelinePrefix + scope } - ev = a.pipelineStatusMap[compID] + st = st.ComponentStatusMap[scope] + } + + sub := &subscription{ + statusCh: make(chan *AggregateStatus, 1), + detailed: detailed, } - eventCh := make(chan *component.StatusEvent, 1) - a.subscriptions[compID] = append(a.subscriptions[compID], eventCh) - eventCh <- ev + a.subscriptions[scope] = append(a.subscriptions[scope], sub) + sub.statusCh <- st - return eventCh, nil + return sub.statusCh } // Unbsubscribe removes a stream from further status updates. -func (a *Aggregator) Unsubscribe(eventCh <-chan *component.StatusEvent) { +func (a *Aggregator) Unsubscribe(statusCh <-chan *AggregateStatus) { a.mu.Lock() defer a.mu.Unlock() - for compID, subs := range a.subscriptions { + for scope, subs := range a.subscriptions { for i, sub := range subs { - if sub == eventCh { - a.subscriptions[compID] = append(subs[:i], subs[i+1:]...) + if sub.statusCh == statusCh { + a.subscriptions[scope] = append(subs[:i], subs[i+1:]...) return } } @@ -260,18 +186,42 @@ func (a *Aggregator) Close() { for _, subs := range a.subscriptions { for _, sub := range subs { - close(sub) + close(sub.statusCh) } } } -func (a *Aggregator) notifySubscribers(compID component.ID, event *component.StatusEvent) { - for _, sub := range a.subscriptions[compID] { +func (a *Aggregator) notifySubscribers(scope string, status *AggregateStatus) { + for _, sub := range a.subscriptions[scope] { // clear unread events select { - case <-sub: + case <-sub.statusCh: default: } - sub <- event + sub.statusCh <- status.clone(sub.detailed) } } + +func toStatusEventMap(aggStatus *AggregateStatus) map[string]*component.StatusEvent { + result := make(map[string]*component.StatusEvent, len(aggStatus.ComponentStatusMap)) + for k, v := range aggStatus.ComponentStatusMap { + result[k] = v.StatusEvent + } + return result +} + +func kindToString(k component.Kind) string { + switch k { + case component.KindReceiver: + return "receiver" + case component.KindProcessor: + return "processor" + case component.KindExporter: + return "exporter" + case component.KindExtension: + return "extension" + case component.KindConnector: + return "connector" + } + return "" +} diff --git a/extension/healthcheckextensionv2/internal/status/aggregator_test.go b/extension/healthcheckextensionv2/internal/status/aggregator_test.go index a105f2e9c2235..a8d35cc547ac3 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator_test.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator_test.go @@ -4,6 +4,7 @@ package status_test import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -14,18 +15,22 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" ) -func TestCollectorStatus(t *testing.T) { +func TestAggregateStatus(t *testing.T) { agg := status.NewAggregator() traces := testhelpers.NewPipelineMetadata("traces") t.Run("zero value", func(t *testing.T) { - assert.Equal(t, component.StatusNone, agg.CollectorStatus().Status()) + st, ok := agg.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) + assert.Equal(t, component.StatusNone, st.Status()) }) testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline statuses all successful", func(t *testing.T) { - assert.Equal(t, component.StatusOK, agg.CollectorStatus().Status()) + st, ok := agg.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) + assert.Equal(t, component.StatusOK, st.Status()) }) agg.RecordStatus( @@ -34,10 +39,12 @@ func TestCollectorStatus(t *testing.T) { ) t.Run("pipeline with recoverable error", func(t *testing.T) { + st, ok := agg.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, - agg.CollectorStatus(), + st, ) }) @@ -47,43 +54,42 @@ func TestCollectorStatus(t *testing.T) { ) t.Run("pipeline with permanent error", func(t *testing.T) { + st, ok := agg.AggregateStatus(status.ScopeAll, false) + require.True(t, ok) assertErrorEventsMatch(t, component.StatusPermanentError, assert.AnError, - agg.CollectorStatus(), + st, ) }) } -func TestCollectorStatusDetailed(t *testing.T) { +func TestAggregateStatusDetailed(t *testing.T) { agg := status.NewAggregator() traces := testhelpers.NewPipelineMetadata("traces") + tracesKey := toPipelineKey(traces.PipelineID) t.Run("zero value", func(t *testing.T) { - dst := agg.CollectorStatusDetailed() - assertEventsMatch(t, component.StatusNone, agg.CollectorStatus(), dst.OverallStatus) - assert.Empty(t, dst.PipelineStatusMap) - assert.Empty(t, dst.ComponentStatusMap) + st, ok := agg.AggregateStatus(status.ScopeAll, true) + require.True(t, ok) + assertEventsMatch(t, component.StatusNone, st) + assert.Empty(t, st.ComponentStatusMap) }) // Seed aggregator with successful statuses for pipeline. testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline statuses all successful", func(t *testing.T) { - dst := agg.CollectorStatusDetailed() + st, ok := agg.AggregateStatus(status.ScopeAll, true) + require.True(t, ok) - // CollectorStatus, OverAllStatus, and PipelineStatus match. - assertEventsMatch(t, - component.StatusOK, - agg.CollectorStatus(), - dst.OverallStatus, - dst.PipelineStatusMap[traces.PipelineID], - ) + // The top-level status and pipeline status match. + assertEventsMatch(t, component.StatusOK, st, st.ComponentStatusMap[tracesKey]) // Component statuses match assertEventsMatch(t, component.StatusOK, - collectEvents(dst.ComponentStatusMap[traces.PipelineID], traces.InstanceIDs()...)..., + collectStatuses(st.ComponentStatusMap[tracesKey], traces.InstanceIDs()...)..., ) }) @@ -94,50 +100,49 @@ func TestCollectorStatusDetailed(t *testing.T) { ) t.Run("pipeline with exporter error", func(t *testing.T) { - dst := agg.CollectorStatusDetailed() - - // CollectorStatus, OverAllStatus, and PipelineStatus match. + st, ok := agg.AggregateStatus(status.ScopeAll, true) + require.True(t, ok) + // The top-level status and pipeline status match. assertErrorEventsMatch( t, component.StatusRecoverableError, assert.AnError, - agg.CollectorStatus(), - dst.OverallStatus, - dst.PipelineStatusMap[traces.PipelineID], + st, + st.ComponentStatusMap[tracesKey], ) // Component statuses match assertEventsMatch(t, component.StatusOK, - collectEvents( - dst.ComponentStatusMap[traces.PipelineID], traces.ReceiverID, traces.ProcessorID, + collectStatuses( + st.ComponentStatusMap[tracesKey], traces.ReceiverID, traces.ProcessorID, )..., ) assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, - dst.ComponentStatusMap[traces.PipelineID][traces.ExporterID], + st.ComponentStatusMap[tracesKey].ComponentStatusMap[toComponentKey(traces.ExporterID)], ) }) } -func TestPipelineStatus(t *testing.T) { +func TestPipelineAggregateStatus(t *testing.T) { agg := status.NewAggregator() traces := testhelpers.NewPipelineMetadata("traces") t.Run("non existent pipeline", func(t *testing.T) { - st, err := agg.PipelineStatus("doesnotexist") - assert.Nil(t, st) - assert.Error(t, err) + st, ok := agg.AggregateStatus("doesnotexist", false) + require.Nil(t, st) + require.False(t, ok) }) testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline exists / status successful", func(t *testing.T) { - st, err := agg.PipelineStatus(traces.PipelineID.String()) - require.NoError(t, err) - assertEventsMatch(t, component.StatusOK, agg.CollectorStatus(), st) + st, ok := agg.AggregateStatus(traces.PipelineID.String(), false) + require.True(t, ok) + assertEventsMatch(t, component.StatusOK, st) }) agg.RecordStatus( @@ -146,75 +151,54 @@ func TestPipelineStatus(t *testing.T) { ) t.Run("pipeline exists / exporter error", func(t *testing.T) { - st, err := agg.PipelineStatus(traces.PipelineID.String()) - require.NoError(t, err) - assertErrorEventsMatch(t, - component.StatusRecoverableError, - assert.AnError, - agg.CollectorStatus(), - st, - ) + st, ok := agg.AggregateStatus(traces.PipelineID.String(), false) + require.True(t, ok) + assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, st) }) } -func TestPipelineStatusDetailed(t *testing.T) { +func TestPipelineAggregateStatusDetailed(t *testing.T) { agg := status.NewAggregator() traces := testhelpers.NewPipelineMetadata("traces") t.Run("non existent pipeline", func(t *testing.T) { - dst, err := agg.PipelineStatusDetailed("doesnotexist") - assert.Nil(t, dst) - assert.Error(t, err) + st, ok := agg.AggregateStatus("doesnotexist", true) + require.Nil(t, st) + require.False(t, ok) }) testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline exists / status successful", func(t *testing.T) { - dst, err := agg.PipelineStatusDetailed(traces.PipelineID.String()) - require.NoError(t, err) + st, ok := agg.AggregateStatus(traces.PipelineID.String(), true) + require.True(t, ok) - // CollectorStatus, OverAllStatus, match. - assertEventsMatch(t, - component.StatusOK, - agg.CollectorStatus(), - dst.OverallStatus, - ) + // Top-level status matches + assertEventsMatch(t, component.StatusOK, st) // Component statuses match - assertEventsMatch(t, - component.StatusOK, - collectEvents(dst.ComponentStatusMap, traces.InstanceIDs()...)..., - ) + assertEventsMatch(t, component.StatusOK, collectStatuses(st, traces.InstanceIDs()...)...) }) - agg.RecordStatus( - traces.ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), - ) + agg.RecordStatus(traces.ExporterID, component.NewRecoverableErrorEvent(assert.AnError)) t.Run("pipeline exists / exporter error", func(t *testing.T) { - dst, err := agg.PipelineStatusDetailed(traces.PipelineID.String()) - require.NoError(t, err) + st, ok := agg.AggregateStatus(traces.PipelineID.String(), true) + require.True(t, ok) - // CollectorStatus, OverAllStatus, match. - assertErrorEventsMatch(t, - component.StatusRecoverableError, - assert.AnError, - agg.CollectorStatus(), - dst.OverallStatus, - ) + // Top-level status matches + assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, st) // Component statuses match assertEventsMatch(t, component.StatusOK, - collectEvents(dst.ComponentStatusMap, traces.ReceiverID, traces.ProcessorID)..., + collectStatuses(st, traces.ReceiverID, traces.ProcessorID)..., ) assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, - dst.ComponentStatusMap[traces.ExporterID], + st.ComponentStatusMap[toComponentKey(traces.ExporterID)], ) - }) } @@ -225,12 +209,9 @@ func TestStreaming(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") - traceEvents, err := agg.Subscribe(traces.PipelineID.String()) - require.NoError(t, err) - metricEvents, err := agg.Subscribe(metrics.PipelineID.String()) - require.NoError(t, err) - allEvents, err := agg.Subscribe("") - require.NoError(t, err) + traceEvents := agg.Subscribe(traces.PipelineID.String(), false) + metricEvents := agg.Subscribe(metrics.PipelineID.String(), false) + allEvents := agg.Subscribe(status.ScopeAll, false) assert.Nil(t, <-traceEvents) assert.Nil(t, <-metricEvents) @@ -261,11 +242,7 @@ func TestStreaming(t *testing.T) { // Traces Pipeline Recover agg.RecordStatus(traces.ExporterID, component.NewStatusEvent(component.StatusOK)) - assertEventsRecvdMatch(t, - component.StatusOK, - traceEvents, - allEvents, - ) + assertEventsRecvdMatch(t, component.StatusOK, traceEvents, allEvents) // Stopping testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusStopping) @@ -281,15 +258,75 @@ func TestStreaming(t *testing.T) { assertEventsRecvdMatch(t, component.StatusStopped, metricEvents, allEvents) } +func TestStreamingDetailed(t *testing.T) { + agg := status.NewAggregator() + defer agg.Close() + + traces := testhelpers.NewPipelineMetadata("traces") + tracesKey := toPipelineKey(traces.PipelineID) + + allEvents := agg.Subscribe(status.ScopeAll, true) + + t.Run("zero value", func(t *testing.T) { + st := <-allEvents + assertEventsMatch(t, component.StatusNone, st) + assert.Empty(t, st.ComponentStatusMap) + }) + + // Seed aggregator with successful statuses for pipeline. + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + + t.Run("pipeline statuses all successful", func(t *testing.T) { + st := <-allEvents + // The top-level status matches the pipeline status. + assertEventsMatch(t, component.StatusOK, st, st.ComponentStatusMap[tracesKey]) + + // Component statuses match + assertEventsMatch(t, + component.StatusOK, + collectStatuses(st.ComponentStatusMap[tracesKey], traces.InstanceIDs()...)..., + ) + }) + + // Record an error in the traces exporter + agg.RecordStatus(traces.ExporterID, component.NewRecoverableErrorEvent(assert.AnError)) + + t.Run("pipeline with exporter error", func(t *testing.T) { + st := <-allEvents + + // The top-level status and pipeline status match. + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + st, + st.ComponentStatusMap[tracesKey], + ) + + // Component statuses match + assertEventsMatch(t, + component.StatusOK, + collectStatuses( + st.ComponentStatusMap[tracesKey], traces.ReceiverID, traces.ProcessorID, + )..., + ) + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + st.ComponentStatusMap[tracesKey].ComponentStatusMap[toComponentKey(traces.ExporterID)], + ) + }) +} + // assertEventMatches ensures one or more events share the expected status and are // otherwise equal, ignoring timestamp. func assertEventsMatch( t *testing.T, expectedStatus component.Status, - events ...*component.StatusEvent, + statuses ...*status.AggregateStatus, ) { - err0 := events[0].Err() - for _, ev := range events { + err0 := statuses[0].StatusEvent.Err() + for _, st := range statuses { + ev := st.StatusEvent assert.Equal(t, expectedStatus, ev.Status()) assert.Equal(t, err0, ev.Err()) } @@ -301,34 +338,35 @@ func assertErrorEventsMatch( t *testing.T, expectedStatus component.Status, expectedErr error, - events ...*component.StatusEvent, + statuses ...*status.AggregateStatus, ) { assert.True(t, component.StatusIsError(expectedStatus)) - for _, ev := range events { + for _, st := range statuses { + ev := st.StatusEvent assert.Equal(t, expectedStatus, ev.Status()) assert.Equal(t, expectedErr, ev.Err()) } } -// collectEvents returns a slice of events collected from the componentMap using -// the provided instanceIDs -func collectEvents( - componentMap map[*component.InstanceID]*component.StatusEvent, +func collectStatuses( + aggregateStatus *status.AggregateStatus, instanceIDs ...*component.InstanceID, -) (result []*component.StatusEvent) { +) (result []*status.AggregateStatus) { for _, id := range instanceIDs { - result = append(result, componentMap[id]) + key := toComponentKey(id) + result = append(result, aggregateStatus.ComponentStatusMap[key]) } return } func assertEventsRecvdMatch(t *testing.T, expectedStatus component.Status, - chans ...<-chan *component.StatusEvent, + chans ...<-chan *status.AggregateStatus, ) { var err0 error - for i, evCh := range chans { - ev := <-evCh + for i, stCh := range chans { + st := <-stCh + ev := st.StatusEvent if i == 0 { err0 = ev.Err() } @@ -340,12 +378,38 @@ func assertEventsRecvdMatch(t *testing.T, func assertErrorEventsRecvdMatch(t *testing.T, expectedStatus component.Status, expectedErr error, - chans ...<-chan *component.StatusEvent, + chans ...<-chan *status.AggregateStatus, ) { assert.True(t, component.StatusIsError(expectedStatus)) - for _, evCh := range chans { - ev := <-evCh + for _, stCh := range chans { + st := <-stCh + ev := st.StatusEvent assert.Equal(t, expectedStatus, ev.Status()) assert.Equal(t, expectedErr, ev.Err()) } } + +// TODO: Implement stringer on Kind in core. +func kindToString(k component.Kind) string { + switch k { + case component.KindReceiver: + return "receiver" + case component.KindProcessor: + return "processor" + case component.KindExporter: + return "exporter" + case component.KindExtension: + return "extension" + case component.KindConnector: + return "connector" + } + return "" +} + +func toComponentKey(id *component.InstanceID) string { + return fmt.Sprintf("%s:%s", kindToString(id.Kind), id.ID.String()) +} + +func toPipelineKey(id component.ID) string { + return fmt.Sprintf("pipeline:%s", id.String()) +} From dd628d15912b2c853bf873152a7563bd4ca34e88 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Wed, 24 Jan 2024 23:17:07 -0800 Subject: [PATCH 03/26] Make gotidy --- extension/healthcheckextensionv2/go.mod | 38 ++++++------- extension/healthcheckextensionv2/go.sum | 74 ++++++++++++------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/extension/healthcheckextensionv2/go.mod b/extension/healthcheckextensionv2/go.mod index 1d2429cec028c..681ff666845ec 100644 --- a/extension/healthcheckextensionv2/go.mod +++ b/extension/healthcheckextensionv2/go.mod @@ -3,14 +3,14 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/extension/healt go 1.20 require ( - github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.92.0 + github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.93.0 github.com/stretchr/testify v1.8.4 - go.opentelemetry.io/collector/component v0.92.1-0.20240118172122-8131d31601b8 - go.opentelemetry.io/collector/config/configgrpc v0.92.1-0.20240118172122-8131d31601b8 - go.opentelemetry.io/collector/config/confighttp v0.92.1-0.20240118172122-8131d31601b8 - go.opentelemetry.io/collector/config/confignet v0.92.1-0.20240118172122-8131d31601b8 - go.opentelemetry.io/collector/confmap v0.92.1-0.20240118172122-8131d31601b8 - go.opentelemetry.io/collector/extension v0.92.1-0.20240118172122-8131d31601b8 + go.opentelemetry.io/collector/component v0.93.0 + go.opentelemetry.io/collector/config/configgrpc v0.93.0 + go.opentelemetry.io/collector/config/confighttp v0.93.0 + go.opentelemetry.io/collector/config/confignet v0.93.0 + go.opentelemetry.io/collector/confmap v0.93.0 + go.opentelemetry.io/collector/extension v0.93.0 go.opentelemetry.io/otel/metric v1.22.0 go.opentelemetry.io/otel/trace v1.22.0 go.uber.org/multierr v1.11.0 @@ -51,20 +51,20 @@ require ( github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/rs/cors v1.10.1 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/collector v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/config/configauth v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/config/configcompression v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/config/configopaque v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/config/configtls v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/config/internal v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/extension/auth v0.92.1-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/featuregate v1.0.2-0.20240118172122-8131d31601b8 // indirect - go.opentelemetry.io/collector/pdata v1.0.2-0.20240118172122-8131d31601b8 // indirect + go.opentelemetry.io/collector v0.93.0 // indirect + go.opentelemetry.io/collector/config/configauth v0.93.0 // indirect + go.opentelemetry.io/collector/config/configcompression v0.93.0 // indirect + go.opentelemetry.io/collector/config/configopaque v0.93.0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.93.0 // indirect + go.opentelemetry.io/collector/config/configtls v0.93.0 // indirect + go.opentelemetry.io/collector/config/internal v0.93.0 // indirect + go.opentelemetry.io/collector/extension/auth v0.93.0 // indirect + go.opentelemetry.io/collector/featuregate v1.0.1 // indirect + go.opentelemetry.io/collector/pdata v1.0.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect go.opentelemetry.io/otel v1.22.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.45.0 // indirect go.opentelemetry.io/otel/sdk v1.22.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.22.0 // indirect golang.org/x/net v0.20.0 // indirect diff --git a/extension/healthcheckextensionv2/go.sum b/extension/healthcheckextensionv2/go.sum index a2d6d77b44701..df3ad86714d5e 100644 --- a/extension/healthcheckextensionv2/go.sum +++ b/extension/healthcheckextensionv2/go.sum @@ -279,47 +279,47 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/collector v0.92.1-0.20240118172122-8131d31601b8 h1:Nn2JQ6bwqgEzzaHQ2QQe08RtlDle2/Ag5BG+c7znYis= -go.opentelemetry.io/collector v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rAYt6LjXy2CxnW+bHlsgOJuuSHVZqcNFTY6CiYWcDjA= -go.opentelemetry.io/collector/component v0.92.1-0.20240118172122-8131d31601b8 h1:FYELfGMDrN3QlQi/umNNDFv+bBTcZ8H58MxDATWnDY0= -go.opentelemetry.io/collector/component v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:Bwg4iVtuCHJmcvhQFQH/KI5dNYwHysTHDivxzVUmWgU= -go.opentelemetry.io/collector/config/configauth v0.92.1-0.20240118172122-8131d31601b8 h1:tSCUQy87rpo+8d4pUvWbqcc9+8hCfU+aaTCI9xbtAOc= -go.opentelemetry.io/collector/config/configauth v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:PCkupWQjjY1+0Ay7pv8CQQyF3WNyHZN4HaDHrcIghOI= -go.opentelemetry.io/collector/config/configcompression v0.92.1-0.20240118172122-8131d31601b8 h1:QgiLlFy+51Z6E4GI1ptQXfaEtbJELsZGIi8lDOC7foQ= -go.opentelemetry.io/collector/config/configcompression v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:fA36AZC/Qcyl+HvMnvFZuV/iUWGQJrchimmk+qYWuMM= -go.opentelemetry.io/collector/config/configgrpc v0.92.1-0.20240118172122-8131d31601b8 h1:VFk66vA+TPRiEZydv0r//P+Y/C3Ne25aLo18WUYad1Y= -go.opentelemetry.io/collector/config/configgrpc v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:NddH5HMcYNwSEyHyKsK2f9NSmQWQA6gfrie6gZBvzN0= -go.opentelemetry.io/collector/config/confighttp v0.92.1-0.20240118172122-8131d31601b8 h1:noMWgXJyUXvjtJpnB5jzJ9VQ+ajBC3tTXUs02ZDFKtk= -go.opentelemetry.io/collector/config/confighttp v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:dE0mjg2eAPqEz285mo+GYkcAlG8gI5c2b7B1lJDmUUA= -go.opentelemetry.io/collector/config/confignet v0.92.1-0.20240118172122-8131d31601b8 h1:omXjwBzy7YQbRhGjARwvn5U30Vhr8Or3Oi9QRb8083g= -go.opentelemetry.io/collector/config/confignet v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rraribsOoZsYZm51+3TXHavmXKJOC5a5/X20vfmNbdw= -go.opentelemetry.io/collector/config/configopaque v0.92.1-0.20240118172122-8131d31601b8 h1:SymD96TeYKaXiigWhdimNWZkbu8ixWKJ/iSeJENkgyk= -go.opentelemetry.io/collector/config/configopaque v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:dQK8eUXjIGKaw1RB7UIg2nqx56AueNxeKFCdB0P1ypg= -go.opentelemetry.io/collector/config/configtelemetry v0.92.1-0.20240118172122-8131d31601b8 h1:jwsti51Wbk5YxFnAT1Is88blqmg7frQeNwdJ7nXweVw= -go.opentelemetry.io/collector/config/configtelemetry v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= -go.opentelemetry.io/collector/config/configtls v0.92.1-0.20240118172122-8131d31601b8 h1:8L2sFdBx4AZKzGKF6WfumqH2xLwoC+BlHIR4waKF90Y= -go.opentelemetry.io/collector/config/configtls v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rL9BH5Hyrkni4t+QOx/opuwD0CHq/ZIFTsh6QLLsbmA= -go.opentelemetry.io/collector/config/internal v0.92.1-0.20240118172122-8131d31601b8 h1:+TSSZgWdJPbc7cvgPbubruZShWN6QF4rs+qOc/eSRak= -go.opentelemetry.io/collector/config/internal v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:rPjglfSd4K/kNLfH7TJO8AsstHGMmWTdntOqH7WiFLg= -go.opentelemetry.io/collector/confmap v0.92.1-0.20240118172122-8131d31601b8 h1:TGBvac5l/nTDQB6u4Yasj3GINyupp2BptHHVZntlWy8= -go.opentelemetry.io/collector/confmap v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:+QxYr8qSah4ffcVBUC2KJgwlMsrD2nK1CmcHkLB+D7A= -go.opentelemetry.io/collector/consumer v0.92.0 h1:twa8T0iR9KVglvRbwZ5OPKLXPCC2DO6gVhrgDZ47MPE= -go.opentelemetry.io/collector/extension v0.92.1-0.20240118172122-8131d31601b8 h1:sS33cmZpccGhTNLNXmb9IQU2il/anD7Iu7Nyyz9Lnt8= -go.opentelemetry.io/collector/extension v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:4MG/LMqzVldOP4QZMzImRctujpWdzrd/+VZtZGAJZJU= -go.opentelemetry.io/collector/extension/auth v0.92.1-0.20240118172122-8131d31601b8 h1:bunRKHJ1GcA/QXB7PFJDvA8q3G5GddOQouCzLvdNg5w= -go.opentelemetry.io/collector/extension/auth v0.92.1-0.20240118172122-8131d31601b8/go.mod h1:IRlhFZAFPkEhgJwuZfgVnm5rt1Sgu6ERGCFUMpBkOx4= -go.opentelemetry.io/collector/featuregate v1.0.2-0.20240118172122-8131d31601b8 h1:CjfhuI22zntWF6c+PNhEMO3WNBh0OmKvv3e7tHcuIg4= -go.opentelemetry.io/collector/featuregate v1.0.2-0.20240118172122-8131d31601b8/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= -go.opentelemetry.io/collector/pdata v1.0.2-0.20240118172122-8131d31601b8 h1:ygOOx2cN0zZHrPowRymfUgnRa6aEd5eE0xrv9VAQL9c= -go.opentelemetry.io/collector/pdata v1.0.2-0.20240118172122-8131d31601b8/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= +go.opentelemetry.io/collector v0.93.0 h1:wnyNd3OjwD7wmTIEVvyZG9cS+dhmfLAhNutWDgE5Vqo= +go.opentelemetry.io/collector v0.93.0/go.mod h1:lorK6TQaQvg0RDRN42HwY0Gqc/d7mvBgCMeJCYGishA= +go.opentelemetry.io/collector/component v0.93.0 h1:FHd86+7hbbBlDxdOFLWRA19HrjxKcXO+6H3UQ0zQz0c= +go.opentelemetry.io/collector/component v0.93.0/go.mod h1:8tglddCwOhrcktA7+EwYqcOL3+7xvbfn8ZwKcxsWch0= +go.opentelemetry.io/collector/config/configauth v0.93.0 h1:el5NR0VZ4naIf7hwcHw5LsxpYoComoMzUDB1HEgZ894= +go.opentelemetry.io/collector/config/configauth v0.93.0/go.mod h1:X0t0TR/DZMzzou1DkG/UdNzFubmOf4WSOidPLeqX54U= +go.opentelemetry.io/collector/config/configcompression v0.93.0 h1:mF9faTs/nU0Uceb+T1kqnUnfrOMqC1hABoFb7nai7Ig= +go.opentelemetry.io/collector/config/configcompression v0.93.0/go.mod h1:fA36AZC/Qcyl+HvMnvFZuV/iUWGQJrchimmk+qYWuMM= +go.opentelemetry.io/collector/config/configgrpc v0.93.0 h1:OSDTuJNL8/EaI4CS1SJF+ea7+b11U+j+HP8dlFVPIBU= +go.opentelemetry.io/collector/config/configgrpc v0.93.0/go.mod h1:gGXBMlaDRyOA9fWK/nb6a/D4yMEIZkLjl5/qtyU3ix8= +go.opentelemetry.io/collector/config/confighttp v0.93.0 h1:H8tiKryH5koh5aHfIdM2kgxQkuHrLtgKi6BL9PysvnA= +go.opentelemetry.io/collector/config/confighttp v0.93.0/go.mod h1:5doQNLIZoUhJJJzpAaMSzGgztAPcvdzTYnynIfJvtkY= +go.opentelemetry.io/collector/config/confignet v0.93.0 h1:UZ3ZGxbf0HBCNAIfxhfmVRal5gjDjocVed4NOs89hNo= +go.opentelemetry.io/collector/config/confignet v0.93.0/go.mod h1:rraribsOoZsYZm51+3TXHavmXKJOC5a5/X20vfmNbdw= +go.opentelemetry.io/collector/config/configopaque v0.93.0 h1:mvJFVhToyL6k0BZMhZTbSAxwYXGHoqibp6JVYY5SZu8= +go.opentelemetry.io/collector/config/configopaque v0.93.0/go.mod h1:dQK8eUXjIGKaw1RB7UIg2nqx56AueNxeKFCdB0P1ypg= +go.opentelemetry.io/collector/config/configtelemetry v0.93.0 h1:s+J/zYXc0zRi346Dz4r5ynTPyI5wRtp+JrSyrSBSiSY= +go.opentelemetry.io/collector/config/configtelemetry v0.93.0/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= +go.opentelemetry.io/collector/config/configtls v0.93.0 h1:m6M4m5EM1e1BmhGwB4kEwm6TBslvjCYDdZViRBvtgW0= +go.opentelemetry.io/collector/config/configtls v0.93.0/go.mod h1:ehXx933OgUTSH7bIWphIf1Kfohy6qmrVHizFCgHtF7U= +go.opentelemetry.io/collector/config/internal v0.93.0 h1:tFGp6z3ieOQMs02Lpkezvr9ZJLRooVdOf3jyJGoTeiM= +go.opentelemetry.io/collector/config/internal v0.93.0/go.mod h1:rPjglfSd4K/kNLfH7TJO8AsstHGMmWTdntOqH7WiFLg= +go.opentelemetry.io/collector/confmap v0.93.0 h1:uYiak0iPuSW4BQIEuN+yihQqvWRwURhoW/qoVs4vLFA= +go.opentelemetry.io/collector/confmap v0.93.0/go.mod h1:+QxYr8qSah4ffcVBUC2KJgwlMsrD2nK1CmcHkLB+D7A= +go.opentelemetry.io/collector/consumer v0.93.0 h1:tt9T8knyamBr/85VqIbESsIHVkFXCkwOD+noFqK3+Vg= +go.opentelemetry.io/collector/extension v0.93.0 h1:HqSiVElfK78/rzSN4e7iyQXuMjsye0YZwZfJfalKKaQ= +go.opentelemetry.io/collector/extension v0.93.0/go.mod h1:KCZuD2WjQSoquIfjjOb18L8TfLrvbGgZW1H445umoMU= +go.opentelemetry.io/collector/extension/auth v0.93.0 h1:NgBT/P3kPJtnrdufIGGX7wbv8WcW3suYC6CcnCbw/Rk= +go.opentelemetry.io/collector/extension/auth v0.93.0/go.mod h1:cc15RY21t3D+cJMubhs2dMIedHS782L/7wfhCdL+pgk= +go.opentelemetry.io/collector/featuregate v1.0.1 h1:ok//hLSXttBbyu4sSV1pTx1nKdr5udSmrWy5sFMIIbM= +go.opentelemetry.io/collector/featuregate v1.0.1/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= +go.opentelemetry.io/collector/pdata v1.0.1 h1:dGX2h7maA6zHbl5D3AsMnF1c3Nn+3EUftbVCLzeyNvA= +go.opentelemetry.io/collector/pdata v1.0.1/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2 h1:TnhkxGJ5qPHAMIMI4r+HPT/BbpoHxqn4xONJrok054o= -go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= +go.opentelemetry.io/otel/exporters/prometheus v0.45.0 h1:BeIK2KGho0oCWa7LxEGSqfDZbs7Fpv/Viz+FS4P8CXE= +go.opentelemetry.io/otel/exporters/prometheus v0.45.0/go.mod h1:UVJZPLnfDSvHj+eJuZE+E1GjIBD267mEMfAAHJdghWg= go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= From 7eb6512aa26f75236976b75ebc0111e228d7c25a Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 25 Jan 2024 14:43:52 -0800 Subject: [PATCH 04/26] Update extension/healthcheckextensionv2/config.go Co-authored-by: Alex Boten --- extension/healthcheckextensionv2/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go index 1392e3fbf670c..5bdd34eff1d42 100644 --- a/extension/healthcheckextensionv2/config.go +++ b/extension/healthcheckextensionv2/config.go @@ -29,7 +29,7 @@ func (c *Config) Validate() error { } if c.HTTPSettings != nil && c.HTTPSettings.Endpoint == "" { - return errors.New("healthcheck extension: grpc endpoint required") + return errors.New("healthcheck extension: http endpoint required") } return nil From 8c0c3f4e1b5cc126b6133eb84d40a14797b3c91d Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 25 Jan 2024 16:10:21 -0800 Subject: [PATCH 05/26] Apply suggestions from code review Co-authored-by: Alex Boten --- extension/healthcheckextensionv2/README.md | 2 +- .../healthcheckextensionv2/internal/testhelpers/helpers.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/healthcheckextensionv2/README.md b/extension/healthcheckextensionv2/README.md index 792b9c63122f0..ed7da0f2466f1 100644 --- a/extension/healthcheckextensionv2/README.md +++ b/extension/healthcheckextensionv2/README.md @@ -194,7 +194,7 @@ Note the following based on this response: If the same request is made to a collector with `http.status.detailed` to `false`, you will only get the overall status. The pipeline and component level statuses will be omitted. -``` +```json { "start_time": "2024-01-18T17:39:15.87324-08:00", "healthy": true, diff --git a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go index a5516244b6b43..92c2cb81c5b2d 100644 --- a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go +++ b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go @@ -23,7 +23,7 @@ func (p *PipelineMetadata) InstanceIDs() []*component.InstanceID { return []*component.InstanceID{p.ReceiverID, p.ProcessorID, p.ExporterID} } -// Returns a metadata for a hypothetical pipeline. +// NewPipelineMetadata returns a metadata for a hypothetical pipeline. func NewPipelineMetadata(typeVal component.Type) *PipelineMetadata { pipelineID := component.NewID(typeVal) return &PipelineMetadata{ @@ -52,7 +52,7 @@ func NewPipelineMetadata(typeVal component.Type) *PipelineMetadata { } } -// Returns a map of hypothetical pipelines identified by their stringified typeVal. +// NewPipelines returns a map of hypothetical pipelines identified by their stringified typeVal. func NewPipelines(typeVals ...component.Type) map[string]*PipelineMetadata { result := make(map[string]*PipelineMetadata, len(typeVals)) for _, val := range typeVals { From 18fe9a7842b136a671ffa1a1c9d377a305194a39 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 25 Jan 2024 17:05:02 -0800 Subject: [PATCH 06/26] Implement suggestions from review --- extension/healthcheckextensionv2/README.md | 3 +- extension/healthcheckextensionv2/config.go | 12 +++- .../healthcheckextensionv2/config_test.go | 15 ++--- extension/healthcheckextensionv2/go.mod | 32 +++++----- extension/healthcheckextensionv2/go.sum | 64 +++++++++---------- .../internal/grpc/grpc.go | 2 +- .../internal/http/serialization.go | 2 +- .../internal/status/aggregator.go | 19 +----- .../internal/status/aggregator_test.go | 20 +----- .../healthcheckextensionv2/metadata.yaml | 2 +- reports/distributions/core.yaml | 1 + 11 files changed, 73 insertions(+), 99 deletions(-) diff --git a/extension/healthcheckextensionv2/README.md b/extension/healthcheckextensionv2/README.md index ed7da0f2466f1..ddd47156c216b 100644 --- a/extension/healthcheckextensionv2/README.md +++ b/extension/healthcheckextensionv2/README.md @@ -4,11 +4,12 @@ | Status | | | ------------- |-----------| | Stability | [development] | -| Distributions | [contrib] | +| Distributions | [core], [contrib] | | Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fhealthcheckextensionv2%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fhealthcheckextensionv2) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fhealthcheckextensionv2%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fhealthcheckextensionv2) | | [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@mwear](https://www.github.com/mwear) | [development]: https://github.com/open-telemetry/opentelemetry-collector#development +[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol [contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go index 5bdd34eff1d42..7b759e2e76f8b 100644 --- a/extension/healthcheckextensionv2/config.go +++ b/extension/healthcheckextensionv2/config.go @@ -11,6 +11,12 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" ) +var ( + errMissingProtocol = errors.New("healthcheck extension: must be configured for HTTP or gRPC") + errGRPCEndpointRequired = errors.New("healthcheck extension: grpc endpoint required") + errHTTPEndpointRequired = errors.New("healthcheck extension: http endpoint required") +) + // Config has the configuration for the extension enabling the health check // extension, used to report the health status of the service. type Config struct { @@ -21,15 +27,15 @@ type Config struct { func (c *Config) Validate() error { if c.GRPCSettings == nil && c.HTTPSettings == nil { - return errors.New("healthcheck extension: must be configured for HTTP or gRPC") + return errMissingProtocol } if c.GRPCSettings != nil && c.GRPCSettings.NetAddr.Endpoint == "" { - return errors.New("healthcheck extension: grpc endpoint required") + return errGRPCEndpointRequired } if c.HTTPSettings != nil && c.HTTPSettings.Endpoint == "" { - return errors.New("healthcheck extension: http endpoint required") + return errHTTPEndpointRequired } return nil diff --git a/extension/healthcheckextensionv2/config_test.go b/extension/healthcheckextensionv2/config_test.go index 73ff108b6ec5a..45017c3ad79aa 100644 --- a/extension/healthcheckextensionv2/config_test.go +++ b/extension/healthcheckextensionv2/config_test.go @@ -33,35 +33,33 @@ func TestConfig(t *testing.T) { for _, tc := range []struct { name string config *Config - valid bool + err error }{ { name: "Valid GRPC Settings Only", config: &Config{ GRPCSettings: grpcSettings, }, - valid: true, }, { name: "Invalid GRPC Settings Only", config: &Config{ GRPCSettings: &grpc.Settings{}, }, - valid: false, + err: errGRPCEndpointRequired, }, { name: "Valid HTTP Settings Only", config: &Config{ HTTPSettings: httpSettings, }, - valid: true, }, { name: "Invalid HTTP Settings Only", config: &Config{ HTTPSettings: &http.Settings{}, }, - valid: false, + err: errHTTPEndpointRequired, }, { name: "GRPC and HTTP Settings", @@ -69,7 +67,6 @@ func TestConfig(t *testing.T) { GRPCSettings: grpcSettings, HTTPSettings: httpSettings, }, - valid: true, }, { name: "GRPC and HTTP Settings both invalid", @@ -77,15 +74,15 @@ func TestConfig(t *testing.T) { GRPCSettings: &grpc.Settings{}, HTTPSettings: &http.Settings{}, }, - valid: false, + err: errGRPCEndpointRequired, }, { name: "Neither GRPC nor HTTP Settings", config: &Config{}, - valid: false, + err: errMissingProtocol, }, } { err := tc.config.Validate() - assert.Equal(t, tc.valid, err == nil) + assert.Equal(t, tc.err, err) } } diff --git a/extension/healthcheckextensionv2/go.mod b/extension/healthcheckextensionv2/go.mod index 681ff666845ec..2be46e13ad22d 100644 --- a/extension/healthcheckextensionv2/go.mod +++ b/extension/healthcheckextensionv2/go.mod @@ -5,12 +5,12 @@ go 1.20 require ( github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.93.0 github.com/stretchr/testify v1.8.4 - go.opentelemetry.io/collector/component v0.93.0 - go.opentelemetry.io/collector/config/configgrpc v0.93.0 - go.opentelemetry.io/collector/config/confighttp v0.93.0 - go.opentelemetry.io/collector/config/confignet v0.93.0 - go.opentelemetry.io/collector/confmap v0.93.0 - go.opentelemetry.io/collector/extension v0.93.0 + go.opentelemetry.io/collector/component v0.93.1-0.20240125183026-3cacd40b27e8 + go.opentelemetry.io/collector/config/configgrpc v0.93.1-0.20240125183026-3cacd40b27e8 + go.opentelemetry.io/collector/config/confighttp v0.93.1-0.20240125183026-3cacd40b27e8 + go.opentelemetry.io/collector/config/confignet v0.93.1-0.20240125183026-3cacd40b27e8 + go.opentelemetry.io/collector/confmap v0.93.1-0.20240125183026-3cacd40b27e8 + go.opentelemetry.io/collector/extension v0.93.1-0.20240125183026-3cacd40b27e8 go.opentelemetry.io/otel/metric v1.22.0 go.opentelemetry.io/otel/trace v1.22.0 go.uber.org/multierr v1.11.0 @@ -51,16 +51,16 @@ require ( github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/rs/cors v1.10.1 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/collector v0.93.0 // indirect - go.opentelemetry.io/collector/config/configauth v0.93.0 // indirect - go.opentelemetry.io/collector/config/configcompression v0.93.0 // indirect - go.opentelemetry.io/collector/config/configopaque v0.93.0 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.93.0 // indirect - go.opentelemetry.io/collector/config/configtls v0.93.0 // indirect - go.opentelemetry.io/collector/config/internal v0.93.0 // indirect - go.opentelemetry.io/collector/extension/auth v0.93.0 // indirect - go.opentelemetry.io/collector/featuregate v1.0.1 // indirect - go.opentelemetry.io/collector/pdata v1.0.1 // indirect + go.opentelemetry.io/collector v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/config/configauth v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/config/configcompression v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/config/configopaque v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/config/configtls v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/config/internal v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/extension/auth v0.93.1-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/featuregate v1.0.2-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector/pdata v1.0.2-0.20240125183026-3cacd40b27e8 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect go.opentelemetry.io/otel v1.22.0 // indirect diff --git a/extension/healthcheckextensionv2/go.sum b/extension/healthcheckextensionv2/go.sum index df3ad86714d5e..bf1db3d9c1e40 100644 --- a/extension/healthcheckextensionv2/go.sum +++ b/extension/healthcheckextensionv2/go.sum @@ -279,39 +279,39 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/collector v0.93.0 h1:wnyNd3OjwD7wmTIEVvyZG9cS+dhmfLAhNutWDgE5Vqo= -go.opentelemetry.io/collector v0.93.0/go.mod h1:lorK6TQaQvg0RDRN42HwY0Gqc/d7mvBgCMeJCYGishA= -go.opentelemetry.io/collector/component v0.93.0 h1:FHd86+7hbbBlDxdOFLWRA19HrjxKcXO+6H3UQ0zQz0c= -go.opentelemetry.io/collector/component v0.93.0/go.mod h1:8tglddCwOhrcktA7+EwYqcOL3+7xvbfn8ZwKcxsWch0= -go.opentelemetry.io/collector/config/configauth v0.93.0 h1:el5NR0VZ4naIf7hwcHw5LsxpYoComoMzUDB1HEgZ894= -go.opentelemetry.io/collector/config/configauth v0.93.0/go.mod h1:X0t0TR/DZMzzou1DkG/UdNzFubmOf4WSOidPLeqX54U= -go.opentelemetry.io/collector/config/configcompression v0.93.0 h1:mF9faTs/nU0Uceb+T1kqnUnfrOMqC1hABoFb7nai7Ig= -go.opentelemetry.io/collector/config/configcompression v0.93.0/go.mod h1:fA36AZC/Qcyl+HvMnvFZuV/iUWGQJrchimmk+qYWuMM= -go.opentelemetry.io/collector/config/configgrpc v0.93.0 h1:OSDTuJNL8/EaI4CS1SJF+ea7+b11U+j+HP8dlFVPIBU= -go.opentelemetry.io/collector/config/configgrpc v0.93.0/go.mod h1:gGXBMlaDRyOA9fWK/nb6a/D4yMEIZkLjl5/qtyU3ix8= -go.opentelemetry.io/collector/config/confighttp v0.93.0 h1:H8tiKryH5koh5aHfIdM2kgxQkuHrLtgKi6BL9PysvnA= -go.opentelemetry.io/collector/config/confighttp v0.93.0/go.mod h1:5doQNLIZoUhJJJzpAaMSzGgztAPcvdzTYnynIfJvtkY= -go.opentelemetry.io/collector/config/confignet v0.93.0 h1:UZ3ZGxbf0HBCNAIfxhfmVRal5gjDjocVed4NOs89hNo= -go.opentelemetry.io/collector/config/confignet v0.93.0/go.mod h1:rraribsOoZsYZm51+3TXHavmXKJOC5a5/X20vfmNbdw= -go.opentelemetry.io/collector/config/configopaque v0.93.0 h1:mvJFVhToyL6k0BZMhZTbSAxwYXGHoqibp6JVYY5SZu8= -go.opentelemetry.io/collector/config/configopaque v0.93.0/go.mod h1:dQK8eUXjIGKaw1RB7UIg2nqx56AueNxeKFCdB0P1ypg= -go.opentelemetry.io/collector/config/configtelemetry v0.93.0 h1:s+J/zYXc0zRi346Dz4r5ynTPyI5wRtp+JrSyrSBSiSY= -go.opentelemetry.io/collector/config/configtelemetry v0.93.0/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= -go.opentelemetry.io/collector/config/configtls v0.93.0 h1:m6M4m5EM1e1BmhGwB4kEwm6TBslvjCYDdZViRBvtgW0= -go.opentelemetry.io/collector/config/configtls v0.93.0/go.mod h1:ehXx933OgUTSH7bIWphIf1Kfohy6qmrVHizFCgHtF7U= -go.opentelemetry.io/collector/config/internal v0.93.0 h1:tFGp6z3ieOQMs02Lpkezvr9ZJLRooVdOf3jyJGoTeiM= -go.opentelemetry.io/collector/config/internal v0.93.0/go.mod h1:rPjglfSd4K/kNLfH7TJO8AsstHGMmWTdntOqH7WiFLg= -go.opentelemetry.io/collector/confmap v0.93.0 h1:uYiak0iPuSW4BQIEuN+yihQqvWRwURhoW/qoVs4vLFA= -go.opentelemetry.io/collector/confmap v0.93.0/go.mod h1:+QxYr8qSah4ffcVBUC2KJgwlMsrD2nK1CmcHkLB+D7A= +go.opentelemetry.io/collector v0.93.1-0.20240125183026-3cacd40b27e8 h1:uezFa4OOVMFLbanA0SvWNMU1ODKXtKRqaWZMASrobrE= +go.opentelemetry.io/collector v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:lorK6TQaQvg0RDRN42HwY0Gqc/d7mvBgCMeJCYGishA= +go.opentelemetry.io/collector/component v0.93.1-0.20240125183026-3cacd40b27e8 h1:4ytX4kyA1gW5wgHJWYSSUeISSpb73YhQMunval73/F8= +go.opentelemetry.io/collector/component v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:e9ZNh6CU+oXz+onYJvkakir9cTkKXGu+aWEF6RqiNeo= +go.opentelemetry.io/collector/config/configauth v0.93.1-0.20240125183026-3cacd40b27e8 h1:I2MsKwmuqNw6M3MBRTc+pEnP0Ljh5kjrkWfdKiat7pA= +go.opentelemetry.io/collector/config/configauth v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:LoZj+yz2p7XmBoNhNIKDGeiTk7oxyLXqpQxZP4hyUA4= +go.opentelemetry.io/collector/config/configcompression v0.93.1-0.20240125183026-3cacd40b27e8 h1:QGvUxASNUQIkeLGWDywomDpuvnSC8F/oEUs9mn8kXrQ= +go.opentelemetry.io/collector/config/configcompression v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:fA36AZC/Qcyl+HvMnvFZuV/iUWGQJrchimmk+qYWuMM= +go.opentelemetry.io/collector/config/configgrpc v0.93.1-0.20240125183026-3cacd40b27e8 h1:jRRnXHuZ2iQN2xeWXNjVCo7HfqsgO3pi9ZNW8J9WoW0= +go.opentelemetry.io/collector/config/configgrpc v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:gGXBMlaDRyOA9fWK/nb6a/D4yMEIZkLjl5/qtyU3ix8= +go.opentelemetry.io/collector/config/confighttp v0.93.1-0.20240125183026-3cacd40b27e8 h1:6fwigpqZSfmAFVX/N4CJ9sFP8s4OyQ3HJWZ6iSycutw= +go.opentelemetry.io/collector/config/confighttp v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:5doQNLIZoUhJJJzpAaMSzGgztAPcvdzTYnynIfJvtkY= +go.opentelemetry.io/collector/config/confignet v0.93.1-0.20240125183026-3cacd40b27e8 h1:WJ8ReVL8JJsMXbN+XLbOYh7W+/pInq/UofqWrps/4mQ= +go.opentelemetry.io/collector/config/confignet v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:rraribsOoZsYZm51+3TXHavmXKJOC5a5/X20vfmNbdw= +go.opentelemetry.io/collector/config/configopaque v0.93.1-0.20240125183026-3cacd40b27e8 h1:QosjKFUFDFJGjgu7F4lNkU/t48ookBoJV52zLI7qrxs= +go.opentelemetry.io/collector/config/configopaque v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:dQK8eUXjIGKaw1RB7UIg2nqx56AueNxeKFCdB0P1ypg= +go.opentelemetry.io/collector/config/configtelemetry v0.93.1-0.20240125183026-3cacd40b27e8 h1:M1YctKlH7yVTxbC0hKg/L1pWewCI54oiYKasrhjDPKE= +go.opentelemetry.io/collector/config/configtelemetry v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= +go.opentelemetry.io/collector/config/configtls v0.93.1-0.20240125183026-3cacd40b27e8 h1:/XR5TOA6RH5ddfF2Hbb/q75sesRK1byxb9TfaypKaog= +go.opentelemetry.io/collector/config/configtls v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:ehXx933OgUTSH7bIWphIf1Kfohy6qmrVHizFCgHtF7U= +go.opentelemetry.io/collector/config/internal v0.93.1-0.20240125183026-3cacd40b27e8 h1:9hYCiXqh/jTuqWOTNVY2Ylyo3c+5u5QMa2NM3fUhEzQ= +go.opentelemetry.io/collector/config/internal v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:Kth7pm10Z91Ui7aDZlrhUJmOvxCCIuogK6bDcHWUXko= +go.opentelemetry.io/collector/confmap v0.93.1-0.20240125183026-3cacd40b27e8 h1:w5c3JCJZCs6PXeJeoN/ECj5u+zjG26pz37GjnfYZph8= +go.opentelemetry.io/collector/confmap v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:KjHrfxKKojaLDc9zDPfVuyp8765AH+XfcoPWMLMiuHU= go.opentelemetry.io/collector/consumer v0.93.0 h1:tt9T8knyamBr/85VqIbESsIHVkFXCkwOD+noFqK3+Vg= -go.opentelemetry.io/collector/extension v0.93.0 h1:HqSiVElfK78/rzSN4e7iyQXuMjsye0YZwZfJfalKKaQ= -go.opentelemetry.io/collector/extension v0.93.0/go.mod h1:KCZuD2WjQSoquIfjjOb18L8TfLrvbGgZW1H445umoMU= -go.opentelemetry.io/collector/extension/auth v0.93.0 h1:NgBT/P3kPJtnrdufIGGX7wbv8WcW3suYC6CcnCbw/Rk= -go.opentelemetry.io/collector/extension/auth v0.93.0/go.mod h1:cc15RY21t3D+cJMubhs2dMIedHS782L/7wfhCdL+pgk= -go.opentelemetry.io/collector/featuregate v1.0.1 h1:ok//hLSXttBbyu4sSV1pTx1nKdr5udSmrWy5sFMIIbM= -go.opentelemetry.io/collector/featuregate v1.0.1/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= -go.opentelemetry.io/collector/pdata v1.0.1 h1:dGX2h7maA6zHbl5D3AsMnF1c3Nn+3EUftbVCLzeyNvA= -go.opentelemetry.io/collector/pdata v1.0.1/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= +go.opentelemetry.io/collector/extension v0.93.1-0.20240125183026-3cacd40b27e8 h1:auNQAdJKXV30Q/0EJGxItCeH7Y9V8rixfVcry3GZs4g= +go.opentelemetry.io/collector/extension v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:mTUTMXug92ewm4hwYMUXPoUQOeBolUTCVcpMUJ/1s70= +go.opentelemetry.io/collector/extension/auth v0.93.1-0.20240125183026-3cacd40b27e8 h1:AGFCgqkC2OEY8+jYMdOFPo/aadJWaq7/qqMHT8/wvTM= +go.opentelemetry.io/collector/extension/auth v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:HLiKtPliU33ubLNmBlDHQBupwHtk4O23H0F0eNKj0n4= +go.opentelemetry.io/collector/featuregate v1.0.2-0.20240125183026-3cacd40b27e8 h1:SIG4rZiTd9uDrcM3vHItCRDHmwh+RVaTLF6OKEOCJNc= +go.opentelemetry.io/collector/featuregate v1.0.2-0.20240125183026-3cacd40b27e8/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= +go.opentelemetry.io/collector/pdata v1.0.2-0.20240125183026-3cacd40b27e8 h1:TCr0cgIqNSQeRjf2HUqKpMIVpb+C/NVNTReeebysW6c= +go.opentelemetry.io/collector/pdata v1.0.2-0.20240125183026-3cacd40b27e8/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index adea37a3ecd2f..d3dde84d69f7b 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -103,7 +103,7 @@ func (s *Server) toServingStatus( ev *component.StatusEvent, ) healthpb.HealthCheckResponse_ServingStatus { if ev.Status() == component.StatusRecoverableError && - time.Now().Compare(ev.Timestamp().Add(s.recoveryDuration)) == 1 { + time.Now().After(ev.Timestamp().Add(s.recoveryDuration)) { return healthpb.HealthCheckResponse_NOT_SERVING } return statusToServingStatusMap[ev.Status()] diff --git a/extension/healthcheckextensionv2/internal/http/serialization.go b/extension/healthcheckextensionv2/internal/http/serialization.go index 84a10375cb933..b7711c3da41f2 100644 --- a/extension/healthcheckextensionv2/internal/http/serialization.go +++ b/extension/healthcheckextensionv2/internal/http/serialization.go @@ -97,7 +97,7 @@ func toComponentSerializableStatus( func isHealthy(ev *component.StatusEvent, now time.Time, recoveryDuration time.Duration) bool { if ev.Status() == component.StatusRecoverableError && - now.Compare(ev.Timestamp().Add(recoveryDuration)) == -1 { + now.Before(ev.Timestamp().Add(recoveryDuration)) { return true } diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go index 6b84e74ac72d4..1ab0be687c73d 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -5,6 +5,7 @@ package status // import "github.com/open-telemetry/opentelemetry-collector-cont import ( "fmt" + "strings" "sync" "go.opentelemetry.io/collector/component" @@ -118,7 +119,7 @@ func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component } } - componentKey := fmt.Sprintf("%s:%s", kindToString(source.Kind), source.ID.String()) + componentKey := fmt.Sprintf("%s:%s", strings.ToLower(source.Kind.String()), source.ID) pipelineStatus.ComponentStatusMap[componentKey] = &AggregateStatus{ StatusEvent: event, } @@ -209,19 +210,3 @@ func toStatusEventMap(aggStatus *AggregateStatus) map[string]*component.StatusEv } return result } - -func kindToString(k component.Kind) string { - switch k { - case component.KindReceiver: - return "receiver" - case component.KindProcessor: - return "processor" - case component.KindExporter: - return "exporter" - case component.KindExtension: - return "extension" - case component.KindConnector: - return "connector" - } - return "" -} diff --git a/extension/healthcheckextensionv2/internal/status/aggregator_test.go b/extension/healthcheckextensionv2/internal/status/aggregator_test.go index a8d35cc547ac3..da15ae5c43078 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator_test.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator_test.go @@ -5,6 +5,7 @@ package status_test import ( "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -389,25 +390,8 @@ func assertErrorEventsRecvdMatch(t *testing.T, } } -// TODO: Implement stringer on Kind in core. -func kindToString(k component.Kind) string { - switch k { - case component.KindReceiver: - return "receiver" - case component.KindProcessor: - return "processor" - case component.KindExporter: - return "exporter" - case component.KindExtension: - return "extension" - case component.KindConnector: - return "connector" - } - return "" -} - func toComponentKey(id *component.InstanceID) string { - return fmt.Sprintf("%s:%s", kindToString(id.Kind), id.ID.String()) + return fmt.Sprintf("%s:%s", strings.ToLower(id.Kind.String()), id.ID) } func toPipelineKey(id component.ID) string { diff --git a/extension/healthcheckextensionv2/metadata.yaml b/extension/healthcheckextensionv2/metadata.yaml index 075e00471d6aa..230e77f7511a3 100644 --- a/extension/healthcheckextensionv2/metadata.yaml +++ b/extension/healthcheckextensionv2/metadata.yaml @@ -4,6 +4,6 @@ status: class: extension stability: development: [extension] - distributions: [contrib] + distributions: [core, contrib] codeowners: active: [mwear] diff --git a/reports/distributions/core.yaml b/reports/distributions/core.yaml index ef8961dc5c1fc..46802e687b2d3 100644 --- a/reports/distributions/core.yaml +++ b/reports/distributions/core.yaml @@ -11,6 +11,7 @@ components: - zipkin extension: - health_check + - healthcheckv2 - pprof processor: - attributes From 449544e194b3014c919f48235156a7ee98873ee1 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Fri, 26 Jan 2024 15:53:24 -0800 Subject: [PATCH 07/26] Add Scope and Detail types to Aggregator --- .../internal/grpc/grpc.go | 18 ++-- .../internal/http/handlers.go | 7 +- .../internal/status/aggregator.go | 86 +++++++++++-------- .../internal/status/aggregator_test.go | 46 ++++++---- 4 files changed, 94 insertions(+), 63 deletions(-) diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index d3dde84d69f7b..c70e7892b18b4 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -10,16 +10,18 @@ import ( "go.opentelemetry.io/collector/component" "google.golang.org/grpc/codes" healthpb "google.golang.org/grpc/health/grpc_health_v1" - "google.golang.org/grpc/status" + grpcstatus "google.golang.org/grpc/status" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) func (s *Server) Check( _ context.Context, req *healthpb.HealthCheckRequest, ) (*healthpb.HealthCheckResponse, error) { - st, ok := s.aggregator.AggregateStatus(req.Service, false) + st, ok := s.aggregator.AggregateStatus(status.Scope(req.Service), status.ExcludeSubtrees) if !ok { - return nil, status.Error(codes.NotFound, "unknown service") + return nil, grpcstatus.Error(codes.NotFound, "unknown service") } return &healthpb.HealthCheckResponse{ @@ -28,7 +30,7 @@ func (s *Server) Check( } func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_WatchServer) error { - sub := s.aggregator.Subscribe(req.Service, false) + sub := s.aggregator.Subscribe(status.Scope(req.Service), status.ExcludeSubtrees) defer s.aggregator.Unsubscribe(sub) var lastServingStatus healthpb.HealthCheckResponse_ServingStatus = -1 @@ -40,7 +42,7 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ select { case st, ok := <-sub: if !ok { - return status.Error(codes.Canceled, "Server shutting down.") + return grpcstatus.Error(codes.Canceled, "Server shutting down.") } var sst healthpb.HealthCheckResponse_ServingStatus @@ -66,7 +68,7 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ err := stream.Send(&healthpb.HealthCheckResponse{Status: sst}) if err != nil { - return status.Error(codes.Canceled, "Stream has ended.") + return grpcstatus.Error(codes.Canceled, "Stream has ended.") } case <-failureTicker.C: failureTicker.Stop() @@ -80,10 +82,10 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ }, ) if err != nil { - return status.Error(codes.Canceled, "Stream has ended.") + return grpcstatus.Error(codes.Canceled, "Stream has ended.") } case <-stream.Context().Done(): - return status.Error(codes.Canceled, "Stream has ended.") + return grpcstatus.Error(codes.Canceled, "Stream has ended.") } } } diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go index 2ad0398c8c074..f1ec780d7ddf3 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -8,13 +8,18 @@ import ( "net/http" "go.opentelemetry.io/collector/component" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) func (s *Server) statusHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pipeline := r.URL.Query().Get("pipeline") - st, ok := s.aggregator.AggregateStatus(pipeline, s.settings.Status.Detailed) + st, ok := s.aggregator.AggregateStatus( + status.Scope(pipeline), + status.Detail(s.settings.Status.Detailed), + ) if !ok { w.WriteHeader(http.StatusNotFound) return diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go index 1ab0be687c73d..fd7635cf784bf 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -17,10 +17,30 @@ var ( extsIDMap = map[component.ID]struct{}{extsID: {}} ) +// Scope refers to a part of an AggregateStatus. The zero-value, aka ScopeAll, +// refers to the entire AggregateStatus. ScopeExtensions refers to the extensions +// subtree, and any other value refers to a pipeline subtree. +type Scope string + +const ( + ScopeAll Scope = "" + ScopeExtensions Scope = "extensions" + pipelinePrefix string = "pipeline:" +) + +func (s Scope) toKey() string { + if s == ScopeAll || s == ScopeExtensions { + return string(s) + } + return pipelinePrefix + string(s) +} + +// Detail specifies whether or not to include subtrees with an AggregateStatus +type Detail bool + const ( - ScopeAll = "" - ScopeExtensions = "extensions" - pipelinePrefix = "pipeline:" + IncludeSubtrees Detail = true + ExcludeSubtrees Detail = false ) // AggregateStatus contains a map of child AggregateStatuses and an embedded component.StatusEvent. @@ -32,15 +52,15 @@ type AggregateStatus struct { ComponentStatusMap map[string]*AggregateStatus } -func (a *AggregateStatus) clone(detailed bool) *AggregateStatus { +func (a *AggregateStatus) clone(detail Detail) *AggregateStatus { st := &AggregateStatus{ StatusEvent: a.StatusEvent, } - if detailed && len(a.ComponentStatusMap) > 0 { + if detail == IncludeSubtrees && len(a.ComponentStatusMap) > 0 { st.ComponentStatusMap = make(map[string]*AggregateStatus, len(a.ComponentStatusMap)) for k, cs := range a.ComponentStatusMap { - st.ComponentStatusMap[k] = cs.clone(detailed) + st.ComponentStatusMap[k] = cs.clone(detail) } } @@ -49,7 +69,7 @@ func (a *AggregateStatus) clone(detailed bool) *AggregateStatus { type subscription struct { statusCh chan *AggregateStatus - detailed bool + detail Detail } // Aggregator records individual status events for components and aggregates statuses for the @@ -72,38 +92,31 @@ func NewAggregator() *Aggregator { } // AggregateStatus returns an *AggregateStatus for the given scope. The scope can be the collector -// overall (represented the empty string), extensions, or a pipeline by name. If detailed is true, -// the *AggregateStatus will contain child component statuses for the given scope. If it's false, -// only a top level status will be returned. The boolean return value indicates whether or not the -// scope was found. -func (a *Aggregator) AggregateStatus(scope string, detailed bool) (*AggregateStatus, bool) { +// overall (ScopeAll), extensions (ScopeExtensions), or a pipeline by name. Detail specifies whether +// or not subtrees should be returned with the *AggregateStatus. The boolean return value indicates +// whether or not the scope was found. +func (a *Aggregator) AggregateStatus(scope Scope, detail Detail) (*AggregateStatus, bool) { a.mu.Lock() defer a.mu.Unlock() if scope == ScopeAll { - return a.aggregateStatus.clone(detailed), true - } - - if scope != ScopeExtensions { - scope = pipelinePrefix + scope + return a.aggregateStatus.clone(detail), true } - st, ok := a.aggregateStatus.ComponentStatusMap[scope] + st, ok := a.aggregateStatus.ComponentStatusMap[scope.toKey()] if !ok { return nil, false } - return st.clone(detailed), true + return st.clone(detail), true } // RecordStatus stores and aggregates a StatusEvent for the given component instance. func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component.StatusEvent) { compIDs := source.PipelineIDs - prefix := pipelinePrefix // extensions are treated as a pseudo-pipeline if source.Kind == component.KindExtension { compIDs = extsIDMap - prefix = "" } a.mu.Lock() @@ -111,7 +124,9 @@ func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component for compID := range compIDs { var pipelineStatus *AggregateStatus - pipelineKey := prefix + compID.String() + pipelineScope := Scope(compID.String()) + pipelineKey := pipelineScope.toKey() + pipelineStatus, ok := a.aggregateStatus.ComponentStatusMap[pipelineKey] if !ok { pipelineStatus = &AggregateStatus{ @@ -127,7 +142,7 @@ func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component pipelineStatus.StatusEvent = component.AggregateStatusEvent( toStatusEventMap(pipelineStatus), ) - a.notifySubscribers(pipelineKey, pipelineStatus) + a.notifySubscribers(pipelineScope, pipelineStatus) } a.aggregateStatus.StatusEvent = component.AggregateStatusEvent( @@ -137,29 +152,26 @@ func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component } // Subscribe allows you to subscribe to a stream of events for the given scope. The scope can be -// the collector overall (represented by the emptry string), extensions, or a pipeline name. +// the collector overall (ScopeAll), extensions (ScopeExtensions), or a pipeline name. // It is possible to subscribe to a pipeline that has not yet reported. An initial nil -// will be sent on the channel and events will start streaming if and when it starts reporting. If -// detailed is true, the *AggregateStatus will contain child statuses for the given scope. If it's -// false it will contain only a top-level status. -func (a *Aggregator) Subscribe(scope string, detailed bool) <-chan *AggregateStatus { +// will be sent on the channel and events will start streaming if and when it starts reporting. +// Detail specifies whether or not subtrees should be returned with the *AggregateStatus. +func (a *Aggregator) Subscribe(scope Scope, detail Detail) <-chan *AggregateStatus { a.mu.Lock() defer a.mu.Unlock() + key := scope.toKey() st := a.aggregateStatus if scope != ScopeAll { - if scope != ScopeExtensions { - scope = pipelinePrefix + scope - } - st = st.ComponentStatusMap[scope] + st = st.ComponentStatusMap[key] } sub := &subscription{ statusCh: make(chan *AggregateStatus, 1), - detailed: detailed, + detail: detail, } - a.subscriptions[scope] = append(a.subscriptions[scope], sub) + a.subscriptions[key] = append(a.subscriptions[key], sub) sub.statusCh <- st return sub.statusCh @@ -192,14 +204,14 @@ func (a *Aggregator) Close() { } } -func (a *Aggregator) notifySubscribers(scope string, status *AggregateStatus) { - for _, sub := range a.subscriptions[scope] { +func (a *Aggregator) notifySubscribers(scope Scope, status *AggregateStatus) { + for _, sub := range a.subscriptions[scope.toKey()] { // clear unread events select { case <-sub.statusCh: default: } - sub.statusCh <- status.clone(sub.detailed) + sub.statusCh <- status.clone(sub.detail) } } diff --git a/extension/healthcheckextensionv2/internal/status/aggregator_test.go b/extension/healthcheckextensionv2/internal/status/aggregator_test.go index da15ae5c43078..b28d00913c1e7 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator_test.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator_test.go @@ -21,7 +21,7 @@ func TestAggregateStatus(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") t.Run("zero value", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, false) + st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) require.True(t, ok) assert.Equal(t, component.StatusNone, st.Status()) }) @@ -29,7 +29,7 @@ func TestAggregateStatus(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline statuses all successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, false) + st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) require.True(t, ok) assert.Equal(t, component.StatusOK, st.Status()) }) @@ -40,7 +40,7 @@ func TestAggregateStatus(t *testing.T) { ) t.Run("pipeline with recoverable error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, false) + st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) require.True(t, ok) assertErrorEventsMatch(t, component.StatusRecoverableError, @@ -55,7 +55,7 @@ func TestAggregateStatus(t *testing.T) { ) t.Run("pipeline with permanent error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, false) + st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) require.True(t, ok) assertErrorEventsMatch(t, component.StatusPermanentError, @@ -71,7 +71,7 @@ func TestAggregateStatusDetailed(t *testing.T) { tracesKey := toPipelineKey(traces.PipelineID) t.Run("zero value", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, true) + st, ok := agg.AggregateStatus(status.ScopeAll, status.IncludeSubtrees) require.True(t, ok) assertEventsMatch(t, component.StatusNone, st) assert.Empty(t, st.ComponentStatusMap) @@ -81,7 +81,7 @@ func TestAggregateStatusDetailed(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline statuses all successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, true) + st, ok := agg.AggregateStatus(status.ScopeAll, status.IncludeSubtrees) require.True(t, ok) // The top-level status and pipeline status match. @@ -101,7 +101,7 @@ func TestAggregateStatusDetailed(t *testing.T) { ) t.Run("pipeline with exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, true) + st, ok := agg.AggregateStatus(status.ScopeAll, status.IncludeSubtrees) require.True(t, ok) // The top-level status and pipeline status match. assertErrorEventsMatch( @@ -133,7 +133,7 @@ func TestPipelineAggregateStatus(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") t.Run("non existent pipeline", func(t *testing.T) { - st, ok := agg.AggregateStatus("doesnotexist", false) + st, ok := agg.AggregateStatus("doesnotexist", status.ExcludeSubtrees) require.Nil(t, st) require.False(t, ok) }) @@ -141,7 +141,10 @@ func TestPipelineAggregateStatus(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline exists / status successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(traces.PipelineID.String(), false) + st, ok := agg.AggregateStatus( + status.Scope(traces.PipelineID.String()), + status.ExcludeSubtrees, + ) require.True(t, ok) assertEventsMatch(t, component.StatusOK, st) }) @@ -152,7 +155,10 @@ func TestPipelineAggregateStatus(t *testing.T) { ) t.Run("pipeline exists / exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus(traces.PipelineID.String(), false) + st, ok := agg.AggregateStatus( + status.Scope(traces.PipelineID.String()), + status.ExcludeSubtrees, + ) require.True(t, ok) assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, st) }) @@ -163,7 +169,7 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") t.Run("non existent pipeline", func(t *testing.T) { - st, ok := agg.AggregateStatus("doesnotexist", true) + st, ok := agg.AggregateStatus("doesnotexist", status.IncludeSubtrees) require.Nil(t, st) require.False(t, ok) }) @@ -171,7 +177,10 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline exists / status successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(traces.PipelineID.String(), true) + st, ok := agg.AggregateStatus( + status.Scope(traces.PipelineID.String()), + status.IncludeSubtrees, + ) require.True(t, ok) // Top-level status matches @@ -184,7 +193,10 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { agg.RecordStatus(traces.ExporterID, component.NewRecoverableErrorEvent(assert.AnError)) t.Run("pipeline exists / exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus(traces.PipelineID.String(), true) + st, ok := agg.AggregateStatus( + status.Scope(traces.PipelineID.String()), + status.IncludeSubtrees, + ) require.True(t, ok) // Top-level status matches @@ -210,9 +222,9 @@ func TestStreaming(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") - traceEvents := agg.Subscribe(traces.PipelineID.String(), false) - metricEvents := agg.Subscribe(metrics.PipelineID.String(), false) - allEvents := agg.Subscribe(status.ScopeAll, false) + traceEvents := agg.Subscribe(status.Scope(traces.PipelineID.String()), status.ExcludeSubtrees) + metricEvents := agg.Subscribe(status.Scope(metrics.PipelineID.String()), status.ExcludeSubtrees) + allEvents := agg.Subscribe(status.ScopeAll, status.ExcludeSubtrees) assert.Nil(t, <-traceEvents) assert.Nil(t, <-metricEvents) @@ -266,7 +278,7 @@ func TestStreamingDetailed(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") tracesKey := toPipelineKey(traces.PipelineID) - allEvents := agg.Subscribe(status.ScopeAll, true) + allEvents := agg.Subscribe(status.ScopeAll, status.IncludeSubtrees) t.Run("zero value", func(t *testing.T) { st := <-allEvents From 04fbd5f53a9b4a3d4783c199a36d33fb6f161d6a Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Mon, 29 Jan 2024 17:26:56 -0800 Subject: [PATCH 08/26] Use pipeline status for availability during start/shutdown --- extension/healthcheckextensionv2/extension.go | 16 +++- .../internal/http/handlers.go | 7 +- .../internal/http/server.go | 40 +++++++++- .../internal/http/server_test.go | 74 ++++++++++++++++++- 4 files changed, 129 insertions(+), 8 deletions(-) diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index 4c5abae9a9e63..86ff6ce2dbe80 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -132,12 +132,24 @@ func (hc *healthCheckExtension) NotifyConfig(ctx context.Context, conf *confmap. // Ready implements the extension.PipelineWatcher interface. func (hc *healthCheckExtension) Ready() error { close(hc.readyCh) - return nil + var err error + for _, comp := range hc.subcomponents { + if pw, ok := comp.(extension.PipelineWatcher); ok { + err = multierr.Append(err, pw.Ready()) + } + } + return err } // NotReady implements the extension.PipelineWatcher interface. func (hc *healthCheckExtension) NotReady() error { - return nil + var err error + for _, comp := range hc.subcomponents { + if pw, ok := comp.(extension.PipelineWatcher); ok { + err = multierr.Append(err, pw.NotReady()) + } + } + return err } func (hc *healthCheckExtension) eventLoop(ctx context.Context) { diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go index f1ec780d7ddf3..bd6755afa7ba3 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -14,12 +14,17 @@ import ( func (s *Server) statusHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - pipeline := r.URL.Query().Get("pipeline") + if !s.isReady() { + w.WriteHeader(http.StatusServiceUnavailable) + return + } + pipeline := r.URL.Query().Get("pipeline") st, ok := s.aggregator.AggregateStatus( status.Scope(pipeline), status.Detail(s.settings.Status.Detailed), ) + if !ok { w.WriteHeader(http.StatusNotFound) return diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckextensionv2/internal/http/server.go index 185076eb1a985..6215612c98350 100644 --- a/extension/healthcheckextensionv2/internal/http/server.go +++ b/extension/healthcheckextensionv2/internal/http/server.go @@ -29,11 +29,14 @@ type Server struct { colconf atomic.Value aggregator *status.Aggregator startTimestamp time.Time - done chan struct{} + readyCh chan struct{} + notReadyCh chan struct{} + doneCh chan struct{} } var _ component.Component = (*Server)(nil) var _ extension.ConfigWatcher = (*Server)(nil) +var _ extension.PipelineWatcher = (*Server)(nil) func NewServer( settings *Settings, @@ -46,7 +49,9 @@ func NewServer( settings: settings, aggregator: aggregator, recoveryDuration: recoveryDuration, - done: make(chan struct{}), + readyCh: make(chan struct{}), + notReadyCh: make(chan struct{}), + doneCh: make(chan struct{}), } srv.mux = http.NewServeMux() @@ -76,7 +81,7 @@ func (s *Server) Start(_ context.Context, host component.Host) error { } go func() { - defer close(s.done) + defer close(s.doneCh) if err = s.serverHTTP.Serve(ln); !errors.Is(err, http.ErrServerClosed) && err != nil { s.telemetry.ReportStatus(component.NewPermanentErrorEvent(err)) } @@ -91,7 +96,7 @@ func (s *Server) Shutdown(context.Context) error { return nil } s.serverHTTP.Close() - <-s.done + <-s.doneCh return nil } @@ -105,3 +110,30 @@ func (s *Server) NotifyConfig(_ context.Context, conf *confmap.Conf) error { s.colconf.Store(confBytes) return nil } + +// Ready implements the extenion.PipelineWatcher interface +func (s *Server) Ready() error { + close(s.readyCh) + return nil +} + +// NotReady implements the extenion.PipelineWatcher interface +func (s *Server) NotReady() error { + close(s.notReadyCh) + return nil +} + +func (s *Server) isReady() bool { + select { + case <-s.notReadyCh: + return false + default: + } + + select { + case <-s.readyCh: + return true + default: + return false + } +} diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go index ab06f4f1666a3..72654b48e9358 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -43,7 +43,7 @@ type teststep struct { func TestStatus(t *testing.T) { // server and pipeline are reassigned before each test and are available for - // use in the teststesp + // use in the teststeps var server *Server var pipelines map[string]*testhelpers.PipelineMetadata @@ -692,6 +692,7 @@ func TestStatus(t *testing.T) { require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) defer func() { require.NoError(t, server.Shutdown(context.Background())) }() + require.NoError(t, server.Ready()) client := &http.Client{} url := fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Status.Path) @@ -740,6 +741,77 @@ func TestStatus(t *testing.T) { } } +func TestPipelineReady(t *testing.T) { + settings := &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: StatusSettings{ + Detailed: false, + PathSettings: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + } + server := NewServer( + settings, + componenttest.NewNopTelemetrySettings(), + 20*time.Millisecond, + status.NewAggregator(), + ) + + require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) + defer func() { require.NoError(t, server.Shutdown(context.Background())) }() + + client := &http.Client{} + url := fmt.Sprintf("http://%s%s", settings.Endpoint, settings.Status.Path) + traces := testhelpers.NewPipelineMetadata("traces") + + for _, ts := range []struct { + step func() + expectedStatusCode int + ready bool + notReady bool + }{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + }, + { + expectedStatusCode: http.StatusOK, + ready: true, + }, + { + expectedStatusCode: http.StatusServiceUnavailable, + notReady: true, + }, + } { + if ts.step != nil { + ts.step() + } + + if ts.ready { + require.NoError(t, server.Ready()) + } + + if ts.notReady { + require.NoError(t, server.NotReady()) + } + + resp, err := client.Get(url) + require.NoError(t, err) + assert.Equal(t, ts.expectedStatusCode, resp.StatusCode) + } +} + func assertStatusDetailed( t *testing.T, expected *componentStatusExpectation, From 0d2126425696fac78ee1e94aa09b62530da19309 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Tue, 6 Feb 2024 19:21:29 -0800 Subject: [PATCH 09/26] Multiple health strategies for http --- extension/healthcheckextensionv2/config.go | 8 +- extension/healthcheckextensionv2/extension.go | 2 +- .../healthcheckextensionv2/extension_test.go | 10 +- extension/healthcheckextensionv2/factory.go | 9 +- .../healthcheckextensionv2/factory_test.go | 9 +- .../internal/common/settings.go | 16 + .../internal/grpc/grpc.go | 4 +- .../internal/http/config.go | 9 +- .../internal/http/handlers.go | 30 +- .../internal/http/health_strategy.go | 74 ++ .../internal/http/serialization.go | 70 +- .../internal/http/server.go | 47 +- .../internal/http/server_test.go | 1027 +++++++++++++---- .../internal/status/aggregator.go | 103 +- .../internal/status/aggregator_test.go | 242 ++-- 15 files changed, 1215 insertions(+), 445 deletions(-) create mode 100644 extension/healthcheckextensionv2/internal/common/settings.go create mode 100644 extension/healthcheckextensionv2/internal/http/health_strategy.go diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go index 7b759e2e76f8b..3e4ca9d956c02 100644 --- a/extension/healthcheckextensionv2/config.go +++ b/extension/healthcheckextensionv2/config.go @@ -7,6 +7,7 @@ import ( "errors" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" ) @@ -20,9 +21,10 @@ var ( // Config has the configuration for the extension enabling the health check // extension, used to report the health status of the service. type Config struct { - RecoveryDuration time.Duration `mapstructure:"recovery_duration"` - GRPCSettings *grpc.Settings `mapstructure:"grpc"` - HTTPSettings *http.Settings `mapstructure:"http"` + ComponentHealthSettings *common.ComponentHealthSettings `mapstructure:"component_health"` + RecoveryDuration time.Duration `mapstructure:"recovery_duration"` + GRPCSettings *grpc.Settings `mapstructure:"grpc"` + HTTPSettings *http.Settings `mapstructure:"http"` } func (c *Config) Validate() error { diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index 86ff6ce2dbe80..3dd1a69cb8cc0 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -57,8 +57,8 @@ func newExtension( if config.HTTPSettings != nil { srvHTTP := http.NewServer( config.HTTPSettings, + config.ComponentHealthSettings, set.TelemetrySettings, - config.RecoveryDuration, aggregator, ) comps = append(comps, srvHTTP) diff --git a/extension/healthcheckextensionv2/extension_test.go b/extension/healthcheckextensionv2/extension_test.go index 33b0f91592878..02ac024ab3969 100644 --- a/extension/healthcheckextensionv2/extension_test.go +++ b/extension/healthcheckextensionv2/extension_test.go @@ -32,7 +32,7 @@ func TestComponentStatus(t *testing.T) { ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) // Status before Start will be StatusNone - st, ok := ext.aggregator.AggregateStatus(status.ScopeAll, false) + st, ok := ext.aggregator.AggregateStatus(status.ScopeAll) require.True(t, ok) assert.Equal(t, st.StatusEvent.Status(), component.StatusNone) @@ -53,7 +53,7 @@ func TestComponentStatus(t *testing.T) { // Note the use of assert.Eventually here and throughout this test is because // status events are processed asynchronously in the background. assert.Eventually(t, func() bool { - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) require.True(t, ok) return st.StatusEvent.Status() == component.StatusStarting }, time.Second, 10*time.Millisecond) @@ -61,7 +61,7 @@ func TestComponentStatus(t *testing.T) { require.NoError(t, ext.Ready()) assert.Eventually(t, func() bool { - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) require.True(t, ok) return st.StatusEvent.Status() == component.StatusOK }, time.Second, 10*time.Millisecond) @@ -72,7 +72,7 @@ func TestComponentStatus(t *testing.T) { } assert.Eventually(t, func() bool { - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) require.True(t, ok) return st.StatusEvent.Status() == component.StatusStopping }, time.Second, 10*time.Millisecond) @@ -85,7 +85,7 @@ func TestComponentStatus(t *testing.T) { ext.ComponentStatusChanged(id, component.NewStatusEvent(component.StatusStopped)) } - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, false) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) require.True(t, ok) assert.Equal(t, component.StatusStopping, st.StatusEvent.Status()) } diff --git a/extension/healthcheckextensionv2/factory.go b/extension/healthcheckextensionv2/factory.go index 339bf6d256757..1b787acc88022 100644 --- a/extension/healthcheckextensionv2/factory.go +++ b/extension/healthcheckextensionv2/factory.go @@ -40,12 +40,9 @@ func createDefaultConfig() component.Config { HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: defaultEndpoint, }, - Status: http.StatusSettings{ - Detailed: true, - PathSettings: http.PathSettings{ - Enabled: true, - Path: "/", - }, + Status: http.PathSettings{ + Enabled: true, + Path: "/", }, }, } diff --git a/extension/healthcheckextensionv2/factory_test.go b/extension/healthcheckextensionv2/factory_test.go index 61c9726828cc4..08bb9c6f1049e 100644 --- a/extension/healthcheckextensionv2/factory_test.go +++ b/extension/healthcheckextensionv2/factory_test.go @@ -26,12 +26,9 @@ func TestCreateDefaultConfig(t *testing.T) { HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: defaultEndpoint, }, - Status: http.StatusSettings{ - Detailed: true, - PathSettings: http.PathSettings{ - Enabled: true, - Path: "/", - }, + Status: http.PathSettings{ + Enabled: true, + Path: "/", }, Config: http.PathSettings{}, }, diff --git a/extension/healthcheckextensionv2/internal/common/settings.go b/extension/healthcheckextensionv2/internal/common/settings.go new file mode 100644 index 0000000000000..aac25d931db24 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/common/settings.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package common // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" + +import "time" + +type ComponentHealthSettings struct { + IncludePermanentErrors bool `mapstructure:"include_permanent_errors"` + IncludeRecoverableErrors bool `mapstructure:"include_recoverable_errors"` + RecoveryDuration time.Duration `mapstructure:"recovery_duration"` +} + +func (c ComponentHealthSettings) Enabled() bool { + return c.IncludePermanentErrors || c.IncludeRecoverableErrors +} diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index c70e7892b18b4..c701689685fc7 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -19,7 +19,7 @@ func (s *Server) Check( _ context.Context, req *healthpb.HealthCheckRequest, ) (*healthpb.HealthCheckResponse, error) { - st, ok := s.aggregator.AggregateStatus(status.Scope(req.Service), status.ExcludeSubtrees) + st, ok := s.aggregator.AggregateStatus(status.Scope(req.Service)) if !ok { return nil, grpcstatus.Error(codes.NotFound, "unknown service") } @@ -30,7 +30,7 @@ func (s *Server) Check( } func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_WatchServer) error { - sub := s.aggregator.Subscribe(status.Scope(req.Service), status.ExcludeSubtrees) + sub := s.aggregator.Subscribe(status.Scope(req.Service)) defer s.aggregator.Unsubscribe(sub) var lastServingStatus healthpb.HealthCheckResponse_ServingStatus = -1 diff --git a/extension/healthcheckextensionv2/internal/http/config.go b/extension/healthcheckextensionv2/internal/http/config.go index 3a7e94c386724..140e200b5c57b 100644 --- a/extension/healthcheckextensionv2/internal/http/config.go +++ b/extension/healthcheckextensionv2/internal/http/config.go @@ -8,16 +8,11 @@ import "go.opentelemetry.io/collector/config/confighttp" type Settings struct { confighttp.HTTPServerSettings `mapstructure:",squash"` - Config PathSettings `mapstructure:"config"` - Status StatusSettings `mapstructure:"status"` + Config PathSettings `mapstructure:"config"` + Status PathSettings `mapstructure:"status"` } type PathSettings struct { Enabled bool `mapstructure:"enabled"` Path string `mapstructure:"path"` } - -type StatusSettings struct { - PathSettings `mapstructure:",squash"` - Detailed bool `mapstructure:"detailed"` -} diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go index bd6755afa7ba3..8653a1e2d1e59 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -7,8 +7,6 @@ import ( "encoding/json" "net/http" - "go.opentelemetry.io/collector/component" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) @@ -20,20 +18,18 @@ func (s *Server) statusHandler() http.Handler { } pipeline := r.URL.Query().Get("pipeline") - st, ok := s.aggregator.AggregateStatus( - status.Scope(pipeline), - status.Detail(s.settings.Status.Detailed), - ) + verbose := r.URL.Query().Has("verbose") + st, ok := s.aggregator.AggregateStatus(status.Scope(pipeline)) if !ok { w.WriteHeader(http.StatusNotFound) return } - sst := toSerializableStatus(st, s.startTimestamp, s.recoveryDuration) + code, sst := s.strategy.toResponse(st, verbose) w.Header().Set("Content-Type", "application/json") - w.WriteHeader(s.toHTTPStatus(sst)) + w.WriteHeader(code) body, _ := json.Marshal(sst) _, _ = w.Write(body) @@ -54,21 +50,3 @@ func (s *Server) configHandler() http.Handler { _, _ = w.Write(conf.([]byte)) }) } - -var responseCodes = map[component.Status]int{ - component.StatusNone: http.StatusServiceUnavailable, - component.StatusStarting: http.StatusServiceUnavailable, - component.StatusOK: http.StatusOK, - component.StatusRecoverableError: http.StatusOK, - component.StatusPermanentError: http.StatusInternalServerError, - component.StatusFatalError: http.StatusInternalServerError, - component.StatusStopping: http.StatusServiceUnavailable, - component.StatusStopped: http.StatusServiceUnavailable, -} - -func (s *Server) toHTTPStatus(sst *serializableStatus) int { - if sst.Status() == component.StatusRecoverableError && !sst.Healthy { - return http.StatusInternalServerError - } - return responseCodes[sst.Status()] -} diff --git a/extension/healthcheckextensionv2/internal/http/health_strategy.go b/extension/healthcheckextensionv2/internal/http/health_strategy.go new file mode 100644 index 0000000000000..1f26f55045e1f --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/health_strategy.go @@ -0,0 +1,74 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + +import ( + "net/http" + "time" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "go.opentelemetry.io/collector/component" +) + +type healthStrategy interface { + toResponse(*status.AggregateStatus, bool) (int, *serializableStatus) +} + +type defaultHealthStrategy struct { + startTimestamp *time.Time +} + +func (d *defaultHealthStrategy) toResponse(st *status.AggregateStatus, verbose bool) (int, *serializableStatus) { + return http.StatusOK, toSerializableStatus(st, &serializationOptions{ + verbose: verbose, + includeStartTime: true, + startTimestamp: d.startTimestamp, + }) +} + +type componentHealthStrategy struct { + settings *common.ComponentHealthSettings + startTimestamp *time.Time +} + +func (c *componentHealthStrategy) toResponse(st *status.AggregateStatus, verbose bool) (int, *serializableStatus) { + now := time.Now() + sst := toSerializableStatus( + st, + &serializationOptions{ + verbose: verbose, + includeStartTime: true, + startTimestamp: c.startTimestamp, + healthyFunc: c.healthyFunc(&now), + }, + ) + + if c.settings.IncludePermanentErrors && st.Status() == component.StatusPermanentError { + return http.StatusInternalServerError, sst + } + + if c.settings.IncludeRecoverableErrors { + recoverable, found := status.ActiveRecoverable(st) + if found && now.After(recoverable.Timestamp().Add(c.settings.RecoveryDuration)) { + return http.StatusInternalServerError, sst + } + } + + return http.StatusOK, sst +} + +func (c *componentHealthStrategy) healthyFunc(now *time.Time) func(*component.StatusEvent) bool { + return func(ev *component.StatusEvent) bool { + if ev.Status() == component.StatusPermanentError { + return !c.settings.IncludePermanentErrors + } + + if ev.Status() == component.StatusRecoverableError && c.settings.IncludeRecoverableErrors { + return now.Before(ev.Timestamp().Add(c.settings.RecoveryDuration)) + } + + return ev.Status() != component.StatusFatalError + } +} diff --git a/extension/healthcheckextensionv2/internal/http/serialization.go b/extension/healthcheckextensionv2/internal/http/serialization.go index b7711c3da41f2..55315461b7861 100644 --- a/extension/healthcheckextensionv2/internal/http/serialization.go +++ b/extension/healthcheckextensionv2/internal/http/serialization.go @@ -11,6 +11,22 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) +type healthyFunc func(*component.StatusEvent) bool + +func (f healthyFunc) isHealthy(ev *component.StatusEvent) bool { + if f != nil { + return f(ev) + } + return true +} + +type serializationOptions struct { + verbose bool + includeStartTime bool + startTimestamp *time.Time + healthyFunc healthyFunc +} + type serializableStatus struct { StartTimestamp *time.Time `json:"start_time,omitempty"` *SerializableEvent @@ -43,13 +59,9 @@ func (ev *SerializableEvent) Status() component.Status { return component.StatusNone } -func toSerializableEvent( - ev *component.StatusEvent, - now time.Time, - recoveryDuration time.Duration, -) *SerializableEvent { +func toSerializableEvent(ev *component.StatusEvent, isHealthy bool) *SerializableEvent { se := &SerializableEvent{ - Healthy: isHealthy(ev, now, recoveryDuration), + Healthy: isHealthy, StatusString: ev.Status().String(), Timestamp: ev.Timestamp(), } @@ -61,45 +73,33 @@ func toSerializableEvent( func toSerializableStatus( st *status.AggregateStatus, - startTimestamp time.Time, - recoveryDuration time.Duration, + opts *serializationOptions, ) *serializableStatus { - now := time.Now() s := &serializableStatus{ - StartTimestamp: &startTimestamp, - SerializableEvent: toSerializableEvent(st.StatusEvent, now, recoveryDuration), + SerializableEvent: toSerializableEvent( + st.StatusEvent, + opts.healthyFunc.isHealthy(st.StatusEvent), + ), ComponentStatuses: make(map[string]*serializableStatus), } - for k, cs := range st.ComponentStatusMap { - s.ComponentStatuses[k] = toComponentSerializableStatus(cs, now, recoveryDuration) - } - - return s -} - -func toComponentSerializableStatus( - st *status.AggregateStatus, - now time.Time, - recoveryDuration time.Duration, -) *serializableStatus { - s := &serializableStatus{ - SerializableEvent: toSerializableEvent(st.StatusEvent, now, recoveryDuration), - ComponentStatuses: make(map[string]*serializableStatus), + if opts.includeStartTime { + s.StartTimestamp = opts.startTimestamp + opts.includeStartTime = false } + childrenHealthy := true for k, cs := range st.ComponentStatusMap { - s.ComponentStatuses[k] = toComponentSerializableStatus(cs, now, recoveryDuration) + sst := toSerializableStatus(cs, opts) + childrenHealthy = childrenHealthy && sst.Healthy + if opts.verbose { + s.ComponentStatuses[k] = sst + } } - return s -} - -func isHealthy(ev *component.StatusEvent, now time.Time, recoveryDuration time.Duration) bool { - if ev.Status() == component.StatusRecoverableError && - now.Before(ev.Timestamp().Add(recoveryDuration)) { - return true + if len(st.ComponentStatusMap) > 0 { + s.Healthy = childrenHealthy } - return !component.StatusIsError(ev.Status()) + return s } diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckextensionv2/internal/http/server.go index 6215612c98350..a7f5d0a2ba432 100644 --- a/extension/healthcheckextensionv2/internal/http/server.go +++ b/extension/healthcheckextensionv2/internal/http/server.go @@ -17,21 +17,22 @@ import ( "go.opentelemetry.io/collector/extension" "go.uber.org/zap" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) type Server struct { - telemetry component.TelemetrySettings - settings *Settings - recoveryDuration time.Duration - mux *http.ServeMux - serverHTTP *http.Server - colconf atomic.Value - aggregator *status.Aggregator - startTimestamp time.Time - readyCh chan struct{} - notReadyCh chan struct{} - doneCh chan struct{} + telemetry component.TelemetrySettings + settings *Settings + strategy healthStrategy + mux *http.ServeMux + serverHTTP *http.Server + colconf atomic.Value + aggregator *status.Aggregator + startTimestamp time.Time + readyCh chan struct{} + notReadyCh chan struct{} + doneCh chan struct{} } var _ component.Component = (*Server)(nil) @@ -40,18 +41,26 @@ var _ extension.PipelineWatcher = (*Server)(nil) func NewServer( settings *Settings, + componentHealthSettings *common.ComponentHealthSettings, telemetry component.TelemetrySettings, - recoveryDuration time.Duration, aggregator *status.Aggregator, ) *Server { + now := time.Now() srv := &Server{ - telemetry: telemetry, - settings: settings, - aggregator: aggregator, - recoveryDuration: recoveryDuration, - readyCh: make(chan struct{}), - notReadyCh: make(chan struct{}), - doneCh: make(chan struct{}), + telemetry: telemetry, + settings: settings, + strategy: &defaultHealthStrategy{startTimestamp: &now}, + aggregator: aggregator, + readyCh: make(chan struct{}), + notReadyCh: make(chan struct{}), + doneCh: make(chan struct{}), + } + + if componentHealthSettings != nil { + srv.strategy = &componentHealthStrategy{ + settings: componentHealthSettings, + startTimestamp: &now, + } } srv.mux = http.NewServeMux() diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go index 72654b48e9358..812cf6843efa6 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "path/filepath" + "strings" "testing" "time" @@ -21,6 +22,7 @@ import ( "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/confmap/confmaptest" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" @@ -48,40 +50,711 @@ func TestStatus(t *testing.T) { var pipelines map[string]*testhelpers.PipelineMetadata tests := []struct { - name string - settings *Settings - pipelines map[string]*testhelpers.PipelineMetadata - teststeps []teststep + name string + settings *Settings + componentHealthSettings *common.ComponentHealthSettings + pipelines map[string]*testhelpers.PipelineMetadata + teststeps []teststep }{ { - name: "Collector Status", + name: "overall status - default strategy", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - Detailed: false, - PathSettings: PathSettings{ - Enabled: true, - Path: "/status", + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + require.NoError(t, server.Ready()) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + }, + }, + }, + { + name: "overall status - default strategy - verbose", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + require.NoError(t, server.Ready()) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + queryParams: "verbose", + eventually: true, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + }, + }, + }, + { + name: "pipeline status - default strategy", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + require.NoError(t, server.Ready()) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + queryParams: "pipeline=traces", + eventually: true, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + }, + }, + }, + { + name: "pipeline status - default strategy - verbose", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + require.NoError(t, server.Ready()) + }, + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + queryParams: "pipeline=traces&verbose", + eventually: true, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + }, + }, + }, + { + name: "overall status - component health stategy", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanentErrors: true, + IncludeRecoverableErrors: true, + RecoveryDuration: 20 * time.Millisecond, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + require.NoError(t, server.Ready()) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + eventually: true, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + }, + }, + }, + { + name: "overall status - component health strategy - verbose", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanentErrors: true, + IncludeRecoverableErrors: true, + RecoveryDuration: 20 * time.Millisecond, + }, + pipelines: testhelpers.NewPipelines("traces"), + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusOK, + ) + require.NoError(t, server.Ready()) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + queryParams: "verbose", + eventually: true, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) }, + expectedStatusCode: http.StatusServiceUnavailable, + }, + }, + }, + { + name: "overall status - component health strategy - exclude permanent", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", }, }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanentErrors: false, + IncludeRecoverableErrors: true, + RecoveryDuration: 20 * time.Millisecond, + }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { step: func() { - testhelpers.SeedAggregator(server.aggregator, + testhelpers.SeedAggregator( + server.aggregator, pipelines["traces"].InstanceIDs(), component.StatusStarting, ) }, expectedStatusCode: http.StatusServiceUnavailable, - expectedComponentStatus: &componentStatusExpectation{ - healthy: true, - status: component.StatusStarting, - }, }, { step: func() { @@ -90,6 +763,7 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) + require.NoError(t, server.Ready()) }, expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ @@ -99,6 +773,10 @@ func TestStatus(t *testing.T) { }, { step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ReceiverID, + component.NewPermanentErrorEvent(assert.AnError), + ) server.aggregator.RecordStatus( pipelines["traces"].ExporterID, component.NewRecoverableErrorEvent(assert.AnError), @@ -108,7 +786,7 @@ func TestStatus(t *testing.T) { expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, - status: component.StatusRecoverableError, + status: component.StatusPermanentError, err: assert.AnError, }, }, @@ -122,26 +800,39 @@ func TestStatus(t *testing.T) { expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusOK, + status: component.StatusPermanentError, + }, + }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) }, + expectedStatusCode: http.StatusServiceUnavailable, }, }, }, { - name: "Collector Status - Detailed", + name: "overall status - component health strategy - exclude permanent - verbose", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - Detailed: true, - PathSettings: PathSettings{ - Enabled: true, - Path: "/status", - }, + Status: PathSettings{ + Enabled: true, + Path: "/status", }, }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanentErrors: false, + IncludeRecoverableErrors: true, + RecoveryDuration: 20 * time.Millisecond, + }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { @@ -152,31 +843,8 @@ func TestStatus(t *testing.T) { component.StatusStarting, ) }, + queryParams: "verbose", expectedStatusCode: http.StatusServiceUnavailable, - expectedComponentStatus: &componentStatusExpectation{ - healthy: true, - status: component.StatusStarting, - nestedStatus: map[string]*componentStatusExpectation{ - "pipeline:traces": { - healthy: true, - status: component.StatusStarting, - nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { - healthy: true, - status: component.StatusStarting, - }, - "processor:batch": { - healthy: true, - status: component.StatusStarting, - }, - "exporter:traces/out": { - healthy: true, - status: component.StatusStarting, - }, - }, - }, - }, - }, }, { step: func() { @@ -185,7 +853,9 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) + require.NoError(t, server.Ready()) }, + queryParams: "verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, @@ -214,26 +884,31 @@ func TestStatus(t *testing.T) { }, { step: func() { + server.aggregator.RecordStatus( + pipelines["traces"].ReceiverID, + component.NewPermanentErrorEvent(assert.AnError), + ) server.aggregator.RecordStatus( pipelines["traces"].ExporterID, component.NewRecoverableErrorEvent(assert.AnError), ) }, + queryParams: "verbose", eventually: true, expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, - status: component.StatusRecoverableError, + status: component.StatusPermanentError, err: assert.AnError, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { healthy: false, - status: component.StatusRecoverableError, + status: component.StatusPermanentError, err: assert.AnError, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, - status: component.StatusOK, + status: component.StatusPermanentError, }, "processor:batch": { healthy: true, @@ -256,18 +931,19 @@ func TestStatus(t *testing.T) { component.NewStatusEvent(component.StatusOK), ) }, + queryParams: "verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusOK, + status: component.StatusPermanentError, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { healthy: true, - status: component.StatusOK, + status: component.StatusPermanentError, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, - status: component.StatusOK, + status: component.StatusPermanentError, }, "processor:batch": { healthy: true, @@ -282,23 +958,36 @@ func TestStatus(t *testing.T) { }, }, }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + }, }, }, { - name: "Pipeline Status", + name: "pipeline status - component health strategy", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - Detailed: false, - PathSettings: PathSettings{ - Enabled: true, - Path: "/status", - }, + Status: PathSettings{ + Enabled: true, + Path: "/status", }, }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanentErrors: true, + IncludeRecoverableErrors: true, + RecoveryDuration: 20 * time.Millisecond, + }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { @@ -311,10 +1000,6 @@ func TestStatus(t *testing.T) { }, queryParams: "pipeline=traces", expectedStatusCode: http.StatusServiceUnavailable, - expectedComponentStatus: &componentStatusExpectation{ - healthy: true, - status: component.StatusStarting, - }, }, { step: func() { @@ -323,6 +1008,7 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) + require.NoError(t, server.Ready()) }, queryParams: "pipeline=traces", expectedStatusCode: http.StatusOK, @@ -361,23 +1047,37 @@ func TestStatus(t *testing.T) { status: component.StatusOK, }, }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + }, }, }, { - name: "Pipeline Status - Detailed", + name: "pipeline status - component health strategy - verbose", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - Detailed: true, - PathSettings: PathSettings{ - Enabled: true, - Path: "/status", - }, + Status: PathSettings{ + Enabled: true, + Path: "/status", }, }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanentErrors: true, + IncludeRecoverableErrors: true, + RecoveryDuration: 20 * time.Millisecond, + }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { @@ -387,26 +1087,8 @@ func TestStatus(t *testing.T) { component.StatusStarting, ) }, - queryParams: "pipeline=traces", + queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusServiceUnavailable, - expectedComponentStatus: &componentStatusExpectation{ - healthy: true, - status: component.StatusStarting, - nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { - healthy: true, - status: component.StatusStarting, - }, - "processor:batch": { - healthy: true, - status: component.StatusStarting, - }, - "exporter:traces/out": { - healthy: true, - status: component.StatusStarting, - }, - }, - }, }, { step: func() { @@ -415,8 +1097,9 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) + require.NoError(t, server.Ready()) }, - queryParams: "pipeline=traces", + queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, @@ -444,7 +1127,7 @@ func TestStatus(t *testing.T) { component.NewRecoverableErrorEvent(assert.AnError), ) }, - queryParams: "pipeline=traces", + queryParams: "pipeline=traces&verbose", eventually: true, expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ @@ -475,7 +1158,7 @@ func TestStatus(t *testing.T) { component.NewStatusEvent(component.StatusOK), ) }, - queryParams: "pipeline=traces", + queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, @@ -496,27 +1179,42 @@ func TestStatus(t *testing.T) { }, }, }, + { + step: func() { + require.NoError(t, server.NotReady()) + testhelpers.SeedAggregator( + server.aggregator, + pipelines["traces"].InstanceIDs(), + component.StatusStopping, + ) + }, + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + }, }, }, { - name: "Multiple Pipelines", + name: "multiple pipelines - component health strategy - verbose", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - Detailed: true, - PathSettings: PathSettings{ - Enabled: true, - Path: "/status", - }, + Status: PathSettings{ + Enabled: true, + Path: "/status", }, }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanentErrors: true, + IncludeRecoverableErrors: true, + RecoveryDuration: 20 * time.Millisecond, + }, pipelines: testhelpers.NewPipelines("traces", "metrics"), teststeps: []teststep{ { step: func() { + require.NoError(t, server.Ready()) // traces will be StatusOK testhelpers.SeedAggregator( server.aggregator, @@ -534,6 +1232,7 @@ func TestStatus(t *testing.T) { component.NewPermanentErrorEvent(assert.AnError), ) }, + queryParams: "verbose", expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, @@ -582,7 +1281,7 @@ func TestStatus(t *testing.T) { }, }, { - queryParams: "pipeline=traces", + queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, @@ -604,7 +1303,7 @@ func TestStatus(t *testing.T) { }, }, { - queryParams: "pipeline=metrics", + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, @@ -630,24 +1329,22 @@ func TestStatus(t *testing.T) { }, }, { - name: "Pipeline Non-existent", + name: "pipeline non-existent", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - Detailed: false, - PathSettings: PathSettings{ - Enabled: true, - Path: "/status", - }, + Status: PathSettings{ + Enabled: true, + Path: "/status", }, }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { step: func() { + require.NoError(t, server.Ready()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -660,16 +1357,14 @@ func TestStatus(t *testing.T) { }, }, { - name: "Status Disabled", + name: "status disabled", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - PathSettings: PathSettings{ - Enabled: false, - }, + Status: PathSettings{ + Enabled: false, }, }, teststeps: []teststep{ @@ -685,14 +1380,13 @@ func TestStatus(t *testing.T) { pipelines = tc.pipelines server = NewServer( tc.settings, + tc.componentHealthSettings, componenttest.NewNopTelemetrySettings(), - 20*time.Millisecond, status.NewAggregator(), ) require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) defer func() { require.NoError(t, server.Shutdown(context.Background())) }() - require.NoError(t, server.Ready()) client := &http.Client{} url := fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Status.Path) @@ -729,7 +1423,7 @@ func TestStatus(t *testing.T) { st := &serializableStatus{} require.NoError(t, json.Unmarshal(body, st)) - if tc.settings.Status.Detailed { + if strings.Contains(ts.queryParams, "verbose") { assertStatusDetailed(t, ts.expectedComponentStatus, st) continue } @@ -741,77 +1435,6 @@ func TestStatus(t *testing.T) { } } -func TestPipelineReady(t *testing.T) { - settings := &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: StatusSettings{ - Detailed: false, - PathSettings: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - } - server := NewServer( - settings, - componenttest.NewNopTelemetrySettings(), - 20*time.Millisecond, - status.NewAggregator(), - ) - - require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) - defer func() { require.NoError(t, server.Shutdown(context.Background())) }() - - client := &http.Client{} - url := fmt.Sprintf("http://%s%s", settings.Endpoint, settings.Status.Path) - traces := testhelpers.NewPipelineMetadata("traces") - - for _, ts := range []struct { - step func() - expectedStatusCode int - ready bool - notReady bool - }{ - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusOK, - ) - }, - expectedStatusCode: http.StatusServiceUnavailable, - }, - { - expectedStatusCode: http.StatusOK, - ready: true, - }, - { - expectedStatusCode: http.StatusServiceUnavailable, - notReady: true, - }, - } { - if ts.step != nil { - ts.step() - } - - if ts.ready { - require.NoError(t, server.Ready()) - } - - if ts.notReady { - require.NoError(t, server.NotReady()) - } - - resp, err := client.Get(url) - require.NoError(t, err) - assert.Equal(t, ts.expectedStatusCode, resp.StatusCode) - } -} - func assertStatusDetailed( t *testing.T, expected *componentStatusExpectation, @@ -819,7 +1442,7 @@ func assertStatusDetailed( ) { assert.Equal(t, expected.healthy, actual.Healthy) assert.Equal(t, expected.status, actual.Status()) - assert.Equal(t, !component.StatusIsError(expected.status), actual.Healthy) + assert.Equal(t, expected.healthy, actual.Healthy) if expected.err != nil { assert.Equal(t, expected.err.Error(), actual.Error) } @@ -836,7 +1459,7 @@ func assertNestedStatus( require.True(t, ok, "status for key: %s not found", k) assert.Equal(t, expectation.healthy, st.Healthy) assert.Equal(t, expectation.status, st.Status()) - assert.Equal(t, !component.StatusIsError(expectation.status), st.Healthy) + assert.Equal(t, expectation.healthy, st.Healthy) if expectation.err != nil { assert.Equal(t, expectation.err.Error(), st.Error) } @@ -850,7 +1473,7 @@ func assertStatusSimple( actual *serializableStatus, ) { assert.Equal(t, expected.status, actual.Status()) - assert.Equal(t, !component.StatusIsError(expected.status), actual.Healthy) + assert.Equal(t, expected.healthy, actual.Healthy) if expected.err != nil { assert.Equal(t, expected.err.Error(), actual.Error) } @@ -881,10 +1504,8 @@ func TestConfig(t *testing.T) { Enabled: true, Path: "/config", }, - Status: StatusSettings{ - PathSettings: PathSettings{ - Enabled: false, - }, + Status: PathSettings{ + Enabled: false, }, }, expectedStatusCode: http.StatusServiceUnavailable, @@ -900,10 +1521,8 @@ func TestConfig(t *testing.T) { Enabled: true, Path: "/config", }, - Status: StatusSettings{ - PathSettings: PathSettings{ - Enabled: false, - }, + Status: PathSettings{ + Enabled: false, }, }, setup: func() { @@ -921,10 +1540,8 @@ func TestConfig(t *testing.T) { Config: PathSettings{ Enabled: false, }, - Status: StatusSettings{ - PathSettings: PathSettings{ - Enabled: false, - }, + Status: PathSettings{ + Enabled: false, }, }, expectedStatusCode: http.StatusNotFound, @@ -934,8 +1551,8 @@ func TestConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { server = NewServer( tc.settings, + &common.ComponentHealthSettings{}, componenttest.NewNopTelemetrySettings(), - 20*time.Millisecond, status.NewAggregator(), ) diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go index fd7635cf784bf..2506f2a899e3f 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -35,14 +35,6 @@ func (s Scope) toKey() string { return pipelinePrefix + string(s) } -// Detail specifies whether or not to include subtrees with an AggregateStatus -type Detail bool - -const ( - IncludeSubtrees Detail = true - ExcludeSubtrees Detail = false -) - // AggregateStatus contains a map of child AggregateStatuses and an embedded component.StatusEvent. // It can be used to represent a single, top-level status when the ComponentStatusMap is empty, // or a nested structure when map is non-empty. @@ -52,24 +44,76 @@ type AggregateStatus struct { ComponentStatusMap map[string]*AggregateStatus } -func (a *AggregateStatus) clone(detail Detail) *AggregateStatus { +// Remove +func (a *AggregateStatus) Ready() bool { + if len(a.ComponentStatusMap) == 0 && + (a.Status() == component.StatusStarting || + a.Status() == component.StatusStopping || + a.Status() == component.StatusStopped || + a.Status() == component.StatusNone) { + return false + } + for _, st := range a.ComponentStatusMap { + if !st.Ready() { + return false + } + } + return true +} + +// TODO: Remove +func (a *AggregateStatus) ActiveRecoverable() (*component.StatusEvent, bool) { + //return early if not permanent, fatal, recoverable + var ev *component.StatusEvent + result := a.activeRecoverable(ev) + return result, result != nil +} + +func (a *AggregateStatus) activeRecoverable(curr *component.StatusEvent) *component.StatusEvent { + if len(a.ComponentStatusMap) == 0 && + a.Status() == component.StatusRecoverableError && + (curr == nil || a.Timestamp().Before(curr.Timestamp())) { + curr = a.StatusEvent + } + for _, st := range a.ComponentStatusMap { + curr = st.activeRecoverable(curr) + } + return curr +} + +func (a *AggregateStatus) clone() *AggregateStatus { st := &AggregateStatus{ StatusEvent: a.StatusEvent, } - if detail == IncludeSubtrees && len(a.ComponentStatusMap) > 0 { + if len(a.ComponentStatusMap) > 0 { st.ComponentStatusMap = make(map[string]*AggregateStatus, len(a.ComponentStatusMap)) for k, cs := range a.ComponentStatusMap { - st.ComponentStatusMap[k] = cs.clone(detail) + st.ComponentStatusMap[k] = cs.clone() } } return st } -type subscription struct { - statusCh chan *AggregateStatus - detail Detail +func ActiveRecoverable(st *AggregateStatus) (*component.StatusEvent, bool) { + if !component.StatusIsError(st.Status()) { + return nil, false + } + result := activeRecoverable(st, nil) + return result, result != nil +} + +func activeRecoverable(st *AggregateStatus, curr *component.StatusEvent) *component.StatusEvent { + if len(st.ComponentStatusMap) == 0 && + st.Status() == component.StatusRecoverableError && + (curr == nil || st.Timestamp().Before(curr.Timestamp())) { + curr = st.StatusEvent + } + for _, cst := range st.ComponentStatusMap { + curr = activeRecoverable(cst, curr) + } + return curr } // Aggregator records individual status events for components and aggregates statuses for the @@ -77,7 +121,7 @@ type subscription struct { type Aggregator struct { mu sync.RWMutex aggregateStatus *AggregateStatus - subscriptions map[string][]*subscription + subscriptions map[string][]chan *AggregateStatus } // NewAggregator returns a *status.Aggregator. @@ -87,7 +131,7 @@ func NewAggregator() *Aggregator { StatusEvent: &component.StatusEvent{}, ComponentStatusMap: make(map[string]*AggregateStatus), }, - subscriptions: make(map[string][]*subscription), + subscriptions: make(map[string][]chan *AggregateStatus), } } @@ -95,12 +139,12 @@ func NewAggregator() *Aggregator { // overall (ScopeAll), extensions (ScopeExtensions), or a pipeline by name. Detail specifies whether // or not subtrees should be returned with the *AggregateStatus. The boolean return value indicates // whether or not the scope was found. -func (a *Aggregator) AggregateStatus(scope Scope, detail Detail) (*AggregateStatus, bool) { +func (a *Aggregator) AggregateStatus(scope Scope) (*AggregateStatus, bool) { a.mu.Lock() defer a.mu.Unlock() if scope == ScopeAll { - return a.aggregateStatus.clone(detail), true + return a.aggregateStatus.clone(), true } st, ok := a.aggregateStatus.ComponentStatusMap[scope.toKey()] @@ -108,7 +152,7 @@ func (a *Aggregator) AggregateStatus(scope Scope, detail Detail) (*AggregateStat return nil, false } - return st.clone(detail), true + return st.clone(), true } // RecordStatus stores and aggregates a StatusEvent for the given component instance. @@ -156,7 +200,7 @@ func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component // It is possible to subscribe to a pipeline that has not yet reported. An initial nil // will be sent on the channel and events will start streaming if and when it starts reporting. // Detail specifies whether or not subtrees should be returned with the *AggregateStatus. -func (a *Aggregator) Subscribe(scope Scope, detail Detail) <-chan *AggregateStatus { +func (a *Aggregator) Subscribe(scope Scope) <-chan *AggregateStatus { a.mu.Lock() defer a.mu.Unlock() @@ -166,15 +210,12 @@ func (a *Aggregator) Subscribe(scope Scope, detail Detail) <-chan *AggregateStat st = st.ComponentStatusMap[key] } - sub := &subscription{ - statusCh: make(chan *AggregateStatus, 1), - detail: detail, - } + statusCh := make(chan *AggregateStatus, 1) - a.subscriptions[key] = append(a.subscriptions[key], sub) - sub.statusCh <- st + a.subscriptions[key] = append(a.subscriptions[key], statusCh) + statusCh <- st - return sub.statusCh + return statusCh } // Unbsubscribe removes a stream from further status updates. @@ -184,7 +225,7 @@ func (a *Aggregator) Unsubscribe(statusCh <-chan *AggregateStatus) { for scope, subs := range a.subscriptions { for i, sub := range subs { - if sub.statusCh == statusCh { + if sub == statusCh { a.subscriptions[scope] = append(subs[:i], subs[i+1:]...) return } @@ -199,7 +240,7 @@ func (a *Aggregator) Close() { for _, subs := range a.subscriptions { for _, sub := range subs { - close(sub.statusCh) + close(sub) } } } @@ -208,10 +249,10 @@ func (a *Aggregator) notifySubscribers(scope Scope, status *AggregateStatus) { for _, sub := range a.subscriptions[scope.toKey()] { // clear unread events select { - case <-sub.statusCh: + case <-sub: default: } - sub.statusCh <- status.clone(sub.detail) + sub <- status.clone() } } diff --git a/extension/healthcheckextensionv2/internal/status/aggregator_test.go b/extension/healthcheckextensionv2/internal/status/aggregator_test.go index b28d00913c1e7..527a8a3f34dcc 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator_test.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator_test.go @@ -4,9 +4,11 @@ package status_test import ( + "errors" "fmt" "strings" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,54 +18,54 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" ) -func TestAggregateStatus(t *testing.T) { - agg := status.NewAggregator() - traces := testhelpers.NewPipelineMetadata("traces") - - t.Run("zero value", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) - require.True(t, ok) - assert.Equal(t, component.StatusNone, st.Status()) - }) - - testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) - - t.Run("pipeline statuses all successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) - require.True(t, ok) - assert.Equal(t, component.StatusOK, st.Status()) - }) - - agg.RecordStatus( - traces.ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), - ) - - t.Run("pipeline with recoverable error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) - require.True(t, ok) - assertErrorEventsMatch(t, - component.StatusRecoverableError, - assert.AnError, - st, - ) - }) - - agg.RecordStatus( - traces.ExporterID, - component.NewPermanentErrorEvent(assert.AnError), - ) - - t.Run("pipeline with permanent error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) - require.True(t, ok) - assertErrorEventsMatch(t, - component.StatusPermanentError, - assert.AnError, - st, - ) - }) -} +// func TestAggregateStatus(t *testing.T) { +// agg := status.NewAggregator() +// traces := testhelpers.NewPipelineMetadata("traces") + +// t.Run("zero value", func(t *testing.T) { +// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) +// require.True(t, ok) +// assert.Equal(t, component.StatusNone, st.Status()) +// }) + +// testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + +// t.Run("pipeline statuses all successful", func(t *testing.T) { +// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) +// require.True(t, ok) +// assert.Equal(t, component.StatusOK, st.Status()) +// }) + +// agg.RecordStatus( +// traces.ExporterID, +// component.NewRecoverableErrorEvent(assert.AnError), +// ) + +// t.Run("pipeline with recoverable error", func(t *testing.T) { +// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) +// require.True(t, ok) +// assertErrorEventsMatch(t, +// component.StatusRecoverableError, +// assert.AnError, +// st, +// ) +// }) + +// agg.RecordStatus( +// traces.ExporterID, +// component.NewPermanentErrorEvent(assert.AnError), +// ) + +// t.Run("pipeline with permanent error", func(t *testing.T) { +// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) +// require.True(t, ok) +// assertErrorEventsMatch(t, +// component.StatusPermanentError, +// assert.AnError, +// st, +// ) +// }) +// } func TestAggregateStatusDetailed(t *testing.T) { agg := status.NewAggregator() @@ -71,7 +73,7 @@ func TestAggregateStatusDetailed(t *testing.T) { tracesKey := toPipelineKey(traces.PipelineID) t.Run("zero value", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, status.IncludeSubtrees) + st, ok := agg.AggregateStatus(status.ScopeAll) require.True(t, ok) assertEventsMatch(t, component.StatusNone, st) assert.Empty(t, st.ComponentStatusMap) @@ -81,7 +83,7 @@ func TestAggregateStatusDetailed(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline statuses all successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, status.IncludeSubtrees) + st, ok := agg.AggregateStatus(status.ScopeAll) require.True(t, ok) // The top-level status and pipeline status match. @@ -101,7 +103,7 @@ func TestAggregateStatusDetailed(t *testing.T) { ) t.Run("pipeline with exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll, status.IncludeSubtrees) + st, ok := agg.AggregateStatus(status.ScopeAll) require.True(t, ok) // The top-level status and pipeline status match. assertErrorEventsMatch( @@ -128,48 +130,48 @@ func TestAggregateStatusDetailed(t *testing.T) { } -func TestPipelineAggregateStatus(t *testing.T) { - agg := status.NewAggregator() - traces := testhelpers.NewPipelineMetadata("traces") - - t.Run("non existent pipeline", func(t *testing.T) { - st, ok := agg.AggregateStatus("doesnotexist", status.ExcludeSubtrees) - require.Nil(t, st) - require.False(t, ok) - }) - - testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) - - t.Run("pipeline exists / status successful", func(t *testing.T) { - st, ok := agg.AggregateStatus( - status.Scope(traces.PipelineID.String()), - status.ExcludeSubtrees, - ) - require.True(t, ok) - assertEventsMatch(t, component.StatusOK, st) - }) - - agg.RecordStatus( - traces.ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), - ) - - t.Run("pipeline exists / exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus( - status.Scope(traces.PipelineID.String()), - status.ExcludeSubtrees, - ) - require.True(t, ok) - assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, st) - }) -} +// func TestPipelineAggregateStatus(t *testing.T) { +// agg := status.NewAggregator() +// traces := testhelpers.NewPipelineMetadata("traces") + +// t.Run("non existent pipeline", func(t *testing.T) { +// st, ok := agg.AggregateStatus("doesnotexist", status.ExcludeSubtrees) +// require.Nil(t, st) +// require.False(t, ok) +// }) + +// testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + +// t.Run("pipeline exists / status successful", func(t *testing.T) { +// st, ok := agg.AggregateStatus( +// status.Scope(traces.PipelineID.String()), +// status.ExcludeSubtrees, +// ) +// require.True(t, ok) +// assertEventsMatch(t, component.StatusOK, st) +// }) + +// agg.RecordStatus( +// traces.ExporterID, +// component.NewRecoverableErrorEvent(assert.AnError), +// ) + +// t.Run("pipeline exists / exporter error", func(t *testing.T) { +// st, ok := agg.AggregateStatus( +// status.Scope(traces.PipelineID.String()), +// status.ExcludeSubtrees, +// ) +// require.True(t, ok) +// assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, st) +// }) +// } func TestPipelineAggregateStatusDetailed(t *testing.T) { agg := status.NewAggregator() traces := testhelpers.NewPipelineMetadata("traces") t.Run("non existent pipeline", func(t *testing.T) { - st, ok := agg.AggregateStatus("doesnotexist", status.IncludeSubtrees) + st, ok := agg.AggregateStatus("doesnotexist") require.Nil(t, st) require.False(t, ok) }) @@ -177,10 +179,7 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline exists / status successful", func(t *testing.T) { - st, ok := agg.AggregateStatus( - status.Scope(traces.PipelineID.String()), - status.IncludeSubtrees, - ) + st, ok := agg.AggregateStatus(status.Scope(traces.PipelineID.String())) require.True(t, ok) // Top-level status matches @@ -193,10 +192,7 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { agg.RecordStatus(traces.ExporterID, component.NewRecoverableErrorEvent(assert.AnError)) t.Run("pipeline exists / exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus( - status.Scope(traces.PipelineID.String()), - status.IncludeSubtrees, - ) + st, ok := agg.AggregateStatus(status.Scope(traces.PipelineID.String())) require.True(t, ok) // Top-level status matches @@ -222,9 +218,9 @@ func TestStreaming(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") - traceEvents := agg.Subscribe(status.Scope(traces.PipelineID.String()), status.ExcludeSubtrees) - metricEvents := agg.Subscribe(status.Scope(metrics.PipelineID.String()), status.ExcludeSubtrees) - allEvents := agg.Subscribe(status.ScopeAll, status.ExcludeSubtrees) + traceEvents := agg.Subscribe(status.Scope(traces.PipelineID.String())) + metricEvents := agg.Subscribe(status.Scope(metrics.PipelineID.String())) + allEvents := agg.Subscribe(status.ScopeAll) assert.Nil(t, <-traceEvents) assert.Nil(t, <-metricEvents) @@ -278,7 +274,7 @@ func TestStreamingDetailed(t *testing.T) { traces := testhelpers.NewPipelineMetadata("traces") tracesKey := toPipelineKey(traces.PipelineID) - allEvents := agg.Subscribe(status.ScopeAll, status.IncludeSubtrees) + allEvents := agg.Subscribe(status.ScopeAll) t.Run("zero value", func(t *testing.T) { st := <-allEvents @@ -409,3 +405,51 @@ func toComponentKey(id *component.InstanceID) string { func toPipelineKey(id component.ID) string { return fmt.Sprintf("pipeline:%s", id.String()) } + +func TestSearch(t *testing.T) { + agg := status.NewAggregator() + defer agg.Close() + + traces := testhelpers.NewPipelineMetadata("traces") + metrics := testhelpers.NewPipelineMetadata("metrics") + + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + testhelpers.SeedAggregator(agg, metrics.InstanceIDs(), component.StatusStarting) + + agg.RecordStatus( + traces.ExporterID, + component.NewPermanentErrorEvent(errors.New("it's perm")), + ) + agg.RecordStatus( + traces.ReceiverID, + component.NewRecoverableErrorEvent(errors.New("first recoverable")), + ) + time.Sleep(20 * time.Millisecond) + agg.RecordStatus( + traces.ProcessorID, + component.NewRecoverableErrorEvent(errors.New("second recoverable")), + ) + + stAll, _ := agg.AggregateStatus(status.ScopeAll) + assert.False(t, stAll.Ready()) + + reAll, found := stAll.ActiveRecoverable() + assert.NotNil(t, reAll) + assert.True(t, found) + assert.Equal(t, reAll.Err().Error(), "first recoverable") + + stTraces, _ := agg.AggregateStatus(status.Scope("traces")) + assert.True(t, stTraces.Ready()) + + reTr, found := stTraces.ActiveRecoverable() + assert.NotNil(t, reTr) + assert.True(t, found) + assert.Equal(t, reTr.Err().Error(), "first recoverable") + + stMetrics, _ := agg.AggregateStatus(status.Scope("metrics")) + assert.False(t, stMetrics.Ready()) + + reMt, found := stMetrics.ActiveRecoverable() + assert.Nil(t, reMt) + assert.False(t, found) +} From 60858823777511defdb7d63a2f781323c900ca0d Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 8 Feb 2024 17:06:34 -0800 Subject: [PATCH 10/26] Multiple health strategies via different aggregation funcs --- extension/healthcheckextensionv2/extension.go | 10 +- .../healthcheckextensionv2/extension_test.go | 20 +- .../internal/common/settings.go | 8 +- .../internal/grpc/grpc.go | 12 +- .../internal/grpc/grpc_test.go | 4 +- .../internal/http/handlers.go | 9 +- .../internal/http/health_strategy.go | 41 +-- .../internal/http/serialization.go | 22 +- .../internal/http/server.go | 32 --- .../internal/http/server_test.go | 74 ++--- .../internal/status/aggregation.go | 150 ++++++++++ .../internal/status/aggregator.go | 141 ++++------ .../internal/status/aggregator_test.go | 264 +++++++----------- .../internal/testhelpers/helpers.go | 8 + 14 files changed, 404 insertions(+), 391 deletions(-) create mode 100644 extension/healthcheckextensionv2/internal/status/aggregation.go diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index 3dd1a69cb8cc0..29e4aba22267f 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -42,7 +42,15 @@ func newExtension( set extension.CreateSettings, ) *healthCheckExtension { var comps []component.Component - aggregator := status.NewAggregator() + + errPriority := status.PriorityPermanent + if config.ComponentHealthSettings != nil && + config.ComponentHealthSettings.IncludeRecoverable && + !config.ComponentHealthSettings.IncludePermanent { + errPriority = status.PriorityRecoverable + } + + aggregator := status.NewAggregator(errPriority) if config.GRPCSettings != nil { srvGRPC := grpc.NewServer( diff --git a/extension/healthcheckextensionv2/extension_test.go b/extension/healthcheckextensionv2/extension_test.go index 02ac024ab3969..5e9e0b43bf601 100644 --- a/extension/healthcheckextensionv2/extension_test.go +++ b/extension/healthcheckextensionv2/extension_test.go @@ -32,9 +32,9 @@ func TestComponentStatus(t *testing.T) { ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) // Status before Start will be StatusNone - st, ok := ext.aggregator.AggregateStatus(status.ScopeAll) + st, ok := ext.aggregator.AggregateStatus(status.ScopeAll, status.Concise) require.True(t, ok) - assert.Equal(t, st.StatusEvent.Status(), component.StatusNone) + assert.Equal(t, st.Status(), component.StatusNone) require.NoError(t, ext.Start(context.Background(), componenttest.NewNopHost())) @@ -53,17 +53,17 @@ func TestComponentStatus(t *testing.T) { // Note the use of assert.Eventually here and throughout this test is because // status events are processed asynchronously in the background. assert.Eventually(t, func() bool { - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, status.Concise) require.True(t, ok) - return st.StatusEvent.Status() == component.StatusStarting + return st.Status() == component.StatusStarting }, time.Second, 10*time.Millisecond) require.NoError(t, ext.Ready()) assert.Eventually(t, func() bool { - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, status.Concise) require.True(t, ok) - return st.StatusEvent.Status() == component.StatusOK + return st.Status() == component.StatusOK }, time.Second, 10*time.Millisecond) // StatusStopping will be sent immediately. @@ -72,9 +72,9 @@ func TestComponentStatus(t *testing.T) { } assert.Eventually(t, func() bool { - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, status.Concise) require.True(t, ok) - return st.StatusEvent.Status() == component.StatusStopping + return st.Status() == component.StatusStopping }, time.Second, 10*time.Millisecond) require.NoError(t, ext.NotReady()) @@ -85,9 +85,9 @@ func TestComponentStatus(t *testing.T) { ext.ComponentStatusChanged(id, component.NewStatusEvent(component.StatusStopped)) } - st, ok = ext.aggregator.AggregateStatus(status.ScopeAll) + st, ok = ext.aggregator.AggregateStatus(status.ScopeAll, status.Concise) require.True(t, ok) - assert.Equal(t, component.StatusStopping, st.StatusEvent.Status()) + assert.Equal(t, component.StatusStopping, st.Status()) } func TestNotifyConfig(t *testing.T) { diff --git a/extension/healthcheckextensionv2/internal/common/settings.go b/extension/healthcheckextensionv2/internal/common/settings.go index aac25d931db24..561e818a53b9d 100644 --- a/extension/healthcheckextensionv2/internal/common/settings.go +++ b/extension/healthcheckextensionv2/internal/common/settings.go @@ -6,11 +6,11 @@ package common // import "github.com/open-telemetry/opentelemetry-collector-cont import "time" type ComponentHealthSettings struct { - IncludePermanentErrors bool `mapstructure:"include_permanent_errors"` - IncludeRecoverableErrors bool `mapstructure:"include_recoverable_errors"` - RecoveryDuration time.Duration `mapstructure:"recovery_duration"` + IncludePermanent bool `mapstructure:"include_permanent_errors"` + IncludeRecoverable bool `mapstructure:"include_recoverable_errors"` + RecoveryDuration time.Duration `mapstructure:"recovery_duration"` } func (c ComponentHealthSettings) Enabled() bool { - return c.IncludePermanentErrors || c.IncludeRecoverableErrors + return c.IncludePermanent || c.IncludeRecoverable } diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index c701689685fc7..8fa87b8e83bbc 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -19,18 +19,18 @@ func (s *Server) Check( _ context.Context, req *healthpb.HealthCheckRequest, ) (*healthpb.HealthCheckResponse, error) { - st, ok := s.aggregator.AggregateStatus(status.Scope(req.Service)) + st, ok := s.aggregator.AggregateStatus(status.Scope(req.Service), status.Concise) if !ok { return nil, grpcstatus.Error(codes.NotFound, "unknown service") } return &healthpb.HealthCheckResponse{ - Status: s.toServingStatus(st.StatusEvent), + Status: s.toServingStatus(st.Event), }, nil } func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_WatchServer) error { - sub := s.aggregator.Subscribe(status.Scope(req.Service)) + sub := s.aggregator.Subscribe(status.Scope(req.Service), status.Concise) defer s.aggregator.Unsubscribe(sub) var lastServingStatus healthpb.HealthCheckResponse_ServingStatus = -1 @@ -49,7 +49,7 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ switch { case st == nil: sst = healthpb.HealthCheckResponse_SERVICE_UNKNOWN - case st.StatusEvent.Status() == component.StatusRecoverableError: + case st.Status() == component.StatusRecoverableError: failureTicker.Reset(s.recoveryDuration) sst = lastServingStatus if lastServingStatus == -1 { @@ -57,7 +57,7 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ } default: failureTicker.Stop() - sst = statusToServingStatusMap[st.StatusEvent.Status()] + sst = statusToServingStatusMap[st.Status()] } if lastServingStatus == sst { @@ -102,7 +102,7 @@ var statusToServingStatusMap = map[component.Status]healthpb.HealthCheckResponse } func (s *Server) toServingStatus( - ev *component.StatusEvent, + ev status.Event, ) healthpb.HealthCheckResponse_ServingStatus { if ev.Status() == component.StatusRecoverableError && time.Now().After(ev.Timestamp().Add(s.recoveryDuration)) { diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go index 908f576f889a1..43c013a9e0eee 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go @@ -39,7 +39,7 @@ func TestCheck(t *testing.T) { settings, componenttest.NewNopTelemetrySettings(), 10*time.Millisecond, - status.NewAggregator(), + status.NewAggregator(status.PriorityPermanent), ) traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") @@ -257,7 +257,7 @@ func TestWatch(t *testing.T) { settings, componenttest.NewNopTelemetrySettings(), 10*time.Millisecond, - status.NewAggregator(), + status.NewAggregator(status.PriorityPermanent), ) traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go index 8653a1e2d1e59..d8c288fb2e136 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -12,21 +12,16 @@ import ( func (s *Server) statusHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !s.isReady() { - w.WriteHeader(http.StatusServiceUnavailable) - return - } - pipeline := r.URL.Query().Get("pipeline") verbose := r.URL.Query().Has("verbose") - st, ok := s.aggregator.AggregateStatus(status.Scope(pipeline)) + st, ok := s.aggregator.AggregateStatus(status.Scope(pipeline), status.Verbosity(verbose)) if !ok { w.WriteHeader(http.StatusNotFound) return } - code, sst := s.strategy.toResponse(st, verbose) + code, sst := s.strategy.toResponse(st) w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) diff --git a/extension/healthcheckextensionv2/internal/http/health_strategy.go b/extension/healthcheckextensionv2/internal/http/health_strategy.go index 1f26f55045e1f..1e996a1fa651d 100644 --- a/extension/healthcheckextensionv2/internal/http/health_strategy.go +++ b/extension/healthcheckextensionv2/internal/http/health_strategy.go @@ -7,22 +7,33 @@ import ( "net/http" "time" + "go.opentelemetry.io/collector/component" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" - "go.opentelemetry.io/collector/component" ) +var responseCodes = map[component.Status]int{ + component.StatusNone: http.StatusServiceUnavailable, + component.StatusStarting: http.StatusServiceUnavailable, + component.StatusOK: http.StatusOK, + component.StatusRecoverableError: http.StatusOK, + component.StatusPermanentError: http.StatusOK, + component.StatusFatalError: http.StatusInternalServerError, + component.StatusStopping: http.StatusServiceUnavailable, + component.StatusStopped: http.StatusServiceUnavailable, +} + type healthStrategy interface { - toResponse(*status.AggregateStatus, bool) (int, *serializableStatus) + toResponse(*status.AggregateStatus) (int, *serializableStatus) } type defaultHealthStrategy struct { startTimestamp *time.Time } -func (d *defaultHealthStrategy) toResponse(st *status.AggregateStatus, verbose bool) (int, *serializableStatus) { - return http.StatusOK, toSerializableStatus(st, &serializationOptions{ - verbose: verbose, +func (d *defaultHealthStrategy) toResponse(st *status.AggregateStatus) (int, *serializableStatus) { + return responseCodes[st.Status()], toSerializableStatus(st, &serializationOptions{ includeStartTime: true, startTimestamp: d.startTimestamp, }) @@ -33,39 +44,37 @@ type componentHealthStrategy struct { startTimestamp *time.Time } -func (c *componentHealthStrategy) toResponse(st *status.AggregateStatus, verbose bool) (int, *serializableStatus) { +func (c *componentHealthStrategy) toResponse(st *status.AggregateStatus) (int, *serializableStatus) { now := time.Now() sst := toSerializableStatus( st, &serializationOptions{ - verbose: verbose, includeStartTime: true, startTimestamp: c.startTimestamp, healthyFunc: c.healthyFunc(&now), }, ) - if c.settings.IncludePermanentErrors && st.Status() == component.StatusPermanentError { + if c.settings.IncludePermanent && st.Status() == component.StatusPermanentError { return http.StatusInternalServerError, sst } - if c.settings.IncludeRecoverableErrors { - recoverable, found := status.ActiveRecoverable(st) - if found && now.After(recoverable.Timestamp().Add(c.settings.RecoveryDuration)) { + if c.settings.IncludeRecoverable && st.Status() == component.StatusRecoverableError { + if now.After(st.Timestamp().Add(c.settings.RecoveryDuration)) { return http.StatusInternalServerError, sst } } - return http.StatusOK, sst + return responseCodes[st.Status()], sst } -func (c *componentHealthStrategy) healthyFunc(now *time.Time) func(*component.StatusEvent) bool { - return func(ev *component.StatusEvent) bool { +func (c *componentHealthStrategy) healthyFunc(now *time.Time) func(status.Event) bool { + return func(ev status.Event) bool { if ev.Status() == component.StatusPermanentError { - return !c.settings.IncludePermanentErrors + return !c.settings.IncludePermanent } - if ev.Status() == component.StatusRecoverableError && c.settings.IncludeRecoverableErrors { + if ev.Status() == component.StatusRecoverableError && c.settings.IncludeRecoverable { return now.Before(ev.Timestamp().Add(c.settings.RecoveryDuration)) } diff --git a/extension/healthcheckextensionv2/internal/http/serialization.go b/extension/healthcheckextensionv2/internal/http/serialization.go index 55315461b7861..ab0ed75f58dbc 100644 --- a/extension/healthcheckextensionv2/internal/http/serialization.go +++ b/extension/healthcheckextensionv2/internal/http/serialization.go @@ -11,9 +11,9 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) -type healthyFunc func(*component.StatusEvent) bool +type healthyFunc func(status.Event) bool -func (f healthyFunc) isHealthy(ev *component.StatusEvent) bool { +func (f healthyFunc) isHealthy(ev status.Event) bool { if f != nil { return f(ev) } @@ -21,7 +21,6 @@ func (f healthyFunc) isHealthy(ev *component.StatusEvent) bool { } type serializationOptions struct { - verbose bool includeStartTime bool startTimestamp *time.Time healthyFunc healthyFunc @@ -59,7 +58,7 @@ func (ev *SerializableEvent) Status() component.Status { return component.StatusNone } -func toSerializableEvent(ev *component.StatusEvent, isHealthy bool) *SerializableEvent { +func toSerializableEvent(ev status.Event, isHealthy bool) *SerializableEvent { se := &SerializableEvent{ Healthy: isHealthy, StatusString: ev.Status().String(), @@ -77,8 +76,8 @@ func toSerializableStatus( ) *serializableStatus { s := &serializableStatus{ SerializableEvent: toSerializableEvent( - st.StatusEvent, - opts.healthyFunc.isHealthy(st.StatusEvent), + st.Event, + opts.healthyFunc.isHealthy(st.Event), ), ComponentStatuses: make(map[string]*serializableStatus), } @@ -88,17 +87,8 @@ func toSerializableStatus( opts.includeStartTime = false } - childrenHealthy := true for k, cs := range st.ComponentStatusMap { - sst := toSerializableStatus(cs, opts) - childrenHealthy = childrenHealthy && sst.Healthy - if opts.verbose { - s.ComponentStatuses[k] = sst - } - } - - if len(st.ComponentStatusMap) > 0 { - s.Healthy = childrenHealthy + s.ComponentStatuses[k] = toSerializableStatus(cs, opts) } return s diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckextensionv2/internal/http/server.go index a7f5d0a2ba432..230ae543567be 100644 --- a/extension/healthcheckextensionv2/internal/http/server.go +++ b/extension/healthcheckextensionv2/internal/http/server.go @@ -30,14 +30,11 @@ type Server struct { colconf atomic.Value aggregator *status.Aggregator startTimestamp time.Time - readyCh chan struct{} - notReadyCh chan struct{} doneCh chan struct{} } var _ component.Component = (*Server)(nil) var _ extension.ConfigWatcher = (*Server)(nil) -var _ extension.PipelineWatcher = (*Server)(nil) func NewServer( settings *Settings, @@ -51,8 +48,6 @@ func NewServer( settings: settings, strategy: &defaultHealthStrategy{startTimestamp: &now}, aggregator: aggregator, - readyCh: make(chan struct{}), - notReadyCh: make(chan struct{}), doneCh: make(chan struct{}), } @@ -119,30 +114,3 @@ func (s *Server) NotifyConfig(_ context.Context, conf *confmap.Conf) error { s.colconf.Store(confBytes) return nil } - -// Ready implements the extenion.PipelineWatcher interface -func (s *Server) Ready() error { - close(s.readyCh) - return nil -} - -// NotReady implements the extenion.PipelineWatcher interface -func (s *Server) NotReady() error { - close(s.notReadyCh) - return nil -} - -func (s *Server) isReady() bool { - select { - case <-s.notReadyCh: - return false - default: - } - - select { - case <-s.readyCh: - return true - default: - return false - } -} diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go index 812cf6843efa6..e54d7000c861d 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -86,7 +86,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ @@ -123,7 +122,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -166,7 +164,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, queryParams: "verbose", expectedStatusCode: http.StatusOK, @@ -269,7 +266,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -312,7 +308,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, queryParams: "pipeline=traces", expectedStatusCode: http.StatusOK, @@ -353,7 +348,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -396,7 +390,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, @@ -480,7 +473,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -505,9 +497,9 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanentErrors: true, - IncludeRecoverableErrors: true, - RecoveryDuration: 20 * time.Millisecond, + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ @@ -527,7 +519,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ @@ -565,7 +556,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -589,9 +579,9 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanentErrors: true, - IncludeRecoverableErrors: true, - RecoveryDuration: 20 * time.Millisecond, + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ @@ -613,7 +603,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, queryParams: "verbose", expectedStatusCode: http.StatusOK, @@ -716,7 +705,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -740,9 +728,9 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanentErrors: false, - IncludeRecoverableErrors: true, - RecoveryDuration: 20 * time.Millisecond, + IncludePermanent: false, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ @@ -763,7 +751,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ @@ -786,7 +773,7 @@ func TestStatus(t *testing.T) { expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, - status: component.StatusPermanentError, + status: component.StatusRecoverableError, err: assert.AnError, }, }, @@ -805,7 +792,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -829,9 +815,9 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanentErrors: false, - IncludeRecoverableErrors: true, - RecoveryDuration: 20 * time.Millisecond, + IncludePermanent: false, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ @@ -853,7 +839,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, queryParams: "verbose", expectedStatusCode: http.StatusOK, @@ -898,12 +883,12 @@ func TestStatus(t *testing.T) { expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, - status: component.StatusPermanentError, + status: component.StatusRecoverableError, err: assert.AnError, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { healthy: false, - status: component.StatusPermanentError, + status: component.StatusRecoverableError, err: assert.AnError, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { @@ -960,7 +945,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -984,9 +968,9 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanentErrors: true, - IncludeRecoverableErrors: true, - RecoveryDuration: 20 * time.Millisecond, + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ @@ -1008,7 +992,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, queryParams: "pipeline=traces", expectedStatusCode: http.StatusOK, @@ -1049,7 +1032,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -1074,9 +1056,9 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanentErrors: true, - IncludeRecoverableErrors: true, - RecoveryDuration: 20 * time.Millisecond, + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ @@ -1097,7 +1079,6 @@ func TestStatus(t *testing.T) { pipelines["traces"].InstanceIDs(), component.StatusOK, ) - require.NoError(t, server.Ready()) }, queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, @@ -1181,7 +1162,6 @@ func TestStatus(t *testing.T) { }, { step: func() { - require.NoError(t, server.NotReady()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -1206,15 +1186,14 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanentErrors: true, - IncludeRecoverableErrors: true, - RecoveryDuration: 20 * time.Millisecond, + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, pipelines: testhelpers.NewPipelines("traces", "metrics"), teststeps: []teststep{ { step: func() { - require.NoError(t, server.Ready()) // traces will be StatusOK testhelpers.SeedAggregator( server.aggregator, @@ -1344,7 +1323,6 @@ func TestStatus(t *testing.T) { teststeps: []teststep{ { step: func() { - require.NoError(t, server.Ready()) testhelpers.SeedAggregator( server.aggregator, pipelines["traces"].InstanceIDs(), @@ -1382,7 +1360,7 @@ func TestStatus(t *testing.T) { tc.settings, tc.componentHealthSettings, componenttest.NewNopTelemetrySettings(), - status.NewAggregator(), + status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthSettings)), ) require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) @@ -1553,7 +1531,7 @@ func TestConfig(t *testing.T) { tc.settings, &common.ComponentHealthSettings{}, componenttest.NewNopTelemetrySettings(), - status.NewAggregator(), + status.NewAggregator(status.PriorityPermanent), ) require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) diff --git a/extension/healthcheckextensionv2/internal/status/aggregation.go b/extension/healthcheckextensionv2/internal/status/aggregation.go new file mode 100644 index 0000000000000..b176d641c5e5c --- /dev/null +++ b/extension/healthcheckextensionv2/internal/status/aggregation.go @@ -0,0 +1,150 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package status // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + +import ( + "time" + + "go.opentelemetry.io/collector/component" +) + +// statusEvent contains a status and timestamp, and can contain an error +type statusEvent struct { + status component.Status + err error + timestamp time.Time +} + +// Status returns the Status (enum) associated with the StatusEvent +func (ev *statusEvent) Status() component.Status { + return ev.status +} + +// Err returns the error associated with the StatusEvent. +func (ev *statusEvent) Err() error { + return ev.err +} + +// Timestamp returns the timestamp associated with the StatusEvent +func (ev *statusEvent) Timestamp() time.Time { + return ev.timestamp +} + +type ErrorPriority int + +const ( + PriorityPermanent ErrorPriority = iota + PriorityRecoverable +) + +type aggregationFunc func(*AggregateStatus) Event + +// The purpose of aggregation is to ensure that the most relevant status bubbles +// upwards in the aggregate status. This aggregation func prioritizes lifecycle +// events (including FatalError) over PermanentError and RecoverableError +// events. The priority argument determines the priority of PermanentError +// events vs RecoverableError events. Lifecycle events will have the timestamp +// of the most recent event and error events will have the timestamp of the +// first occurrence. +func newAggregationFunc(priority ErrorPriority) aggregationFunc { + permanentPriorityFunc := func(seen map[component.Status]struct{}) component.Status { + if _, isPermanent := seen[component.StatusPermanentError]; isPermanent { + return component.StatusPermanentError + } + if _, isRecoverable := seen[component.StatusRecoverableError]; isRecoverable { + return component.StatusRecoverableError + } + return component.StatusNone + } + + recoverablePriorityFunc := func(seen map[component.Status]struct{}) component.Status { + if _, isRecoverable := seen[component.StatusRecoverableError]; isRecoverable { + return component.StatusRecoverableError + } + if _, isPermanent := seen[component.StatusPermanentError]; isPermanent { + return component.StatusPermanentError + } + return component.StatusNone + } + + errPriorityFunc := permanentPriorityFunc + if priority == PriorityRecoverable { + errPriorityFunc = recoverablePriorityFunc + } + + statusFunc := func(st *AggregateStatus) component.Status { + seen := make(map[component.Status]struct{}) + for _, cs := range st.ComponentStatusMap { + seen[cs.Status()] = struct{}{} + } + + // All statuses are the same. Note, this will handle StatusOK and StatusStopped as these two + // cases require all components be in the same state. + if len(seen) == 1 { + for st := range seen { + return st + } + } + + // Handle mixed status cases + if _, isFatal := seen[component.StatusFatalError]; isFatal { + return component.StatusFatalError + } + + if _, isStarting := seen[component.StatusStarting]; isStarting { + return component.StatusStarting + } + + if _, isStopping := seen[component.StatusStopping]; isStopping { + return component.StatusStopping + } + + if _, isStopped := seen[component.StatusStopped]; isStopped { + return component.StatusStopping + } + + return errPriorityFunc(seen) + } + + return func(st *AggregateStatus) Event { + var ev, lastEvent, matchingEvent Event + status := statusFunc(st) + isError := component.StatusIsError(status) + + for _, cs := range st.ComponentStatusMap { + ev = cs.Event + if lastEvent == nil || lastEvent.Timestamp().Before(ev.Timestamp()) { + lastEvent = ev + } + if status == ev.Status() { + switch { + case matchingEvent == nil: + matchingEvent = ev + case isError: + if ev.Timestamp().Before(matchingEvent.Timestamp()) { + matchingEvent = ev + } + case ev.Timestamp().After(matchingEvent.Timestamp()): + matchingEvent = ev + } + } + } + + // the error status will be the first matching event + if isError { + return matchingEvent + } + + // the aggregate status matches an existing event + if lastEvent.Status() == status { + return lastEvent + } + + // the aggregate status requires a synthetic event + return &statusEvent{ + status: status, + timestamp: lastEvent.Timestamp(), + } + } +} diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go index 2506f2a899e3f..e40eaa371125f 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" "sync" + "time" "go.opentelemetry.io/collector/component" ) @@ -17,6 +18,12 @@ var ( extsIDMap = map[component.ID]struct{}{extsID: {}} ) +type Event interface { + Status() component.Status + Err() error + Timestamp() time.Time +} + // Scope refers to a part of an AggregateStatus. The zero-value, aka ScopeAll, // refers to the entire AggregateStatus. ScopeExtensions refers to the extensions // subtree, and any other value refers to a pipeline subtree. @@ -35,85 +42,40 @@ func (s Scope) toKey() string { return pipelinePrefix + string(s) } +type Verbosity bool + +const ( + Verbose Verbosity = true + Concise = false +) + // AggregateStatus contains a map of child AggregateStatuses and an embedded component.StatusEvent. // It can be used to represent a single, top-level status when the ComponentStatusMap is empty, // or a nested structure when map is non-empty. type AggregateStatus struct { - *component.StatusEvent + Event ComponentStatusMap map[string]*AggregateStatus } -// Remove -func (a *AggregateStatus) Ready() bool { - if len(a.ComponentStatusMap) == 0 && - (a.Status() == component.StatusStarting || - a.Status() == component.StatusStopping || - a.Status() == component.StatusStopped || - a.Status() == component.StatusNone) { - return false - } - for _, st := range a.ComponentStatusMap { - if !st.Ready() { - return false - } - } - return true -} - -// TODO: Remove -func (a *AggregateStatus) ActiveRecoverable() (*component.StatusEvent, bool) { - //return early if not permanent, fatal, recoverable - var ev *component.StatusEvent - result := a.activeRecoverable(ev) - return result, result != nil -} - -func (a *AggregateStatus) activeRecoverable(curr *component.StatusEvent) *component.StatusEvent { - if len(a.ComponentStatusMap) == 0 && - a.Status() == component.StatusRecoverableError && - (curr == nil || a.Timestamp().Before(curr.Timestamp())) { - curr = a.StatusEvent - } - for _, st := range a.ComponentStatusMap { - curr = st.activeRecoverable(curr) - } - return curr -} - -func (a *AggregateStatus) clone() *AggregateStatus { +func (a *AggregateStatus) clone(verbosity Verbosity) *AggregateStatus { st := &AggregateStatus{ - StatusEvent: a.StatusEvent, + Event: a.Event, } - if len(a.ComponentStatusMap) > 0 { + if verbosity == Verbose && len(a.ComponentStatusMap) > 0 { st.ComponentStatusMap = make(map[string]*AggregateStatus, len(a.ComponentStatusMap)) for k, cs := range a.ComponentStatusMap { - st.ComponentStatusMap[k] = cs.clone() + st.ComponentStatusMap[k] = cs.clone(verbosity) } } return st } -func ActiveRecoverable(st *AggregateStatus) (*component.StatusEvent, bool) { - if !component.StatusIsError(st.Status()) { - return nil, false - } - result := activeRecoverable(st, nil) - return result, result != nil -} - -func activeRecoverable(st *AggregateStatus, curr *component.StatusEvent) *component.StatusEvent { - if len(st.ComponentStatusMap) == 0 && - st.Status() == component.StatusRecoverableError && - (curr == nil || st.Timestamp().Before(curr.Timestamp())) { - curr = st.StatusEvent - } - for _, cst := range st.ComponentStatusMap { - curr = activeRecoverable(cst, curr) - } - return curr +type subscription struct { + statusCh chan *AggregateStatus + verbosity Verbosity } // Aggregator records individual status events for components and aggregates statuses for the @@ -121,17 +83,19 @@ func activeRecoverable(st *AggregateStatus, curr *component.StatusEvent) *compon type Aggregator struct { mu sync.RWMutex aggregateStatus *AggregateStatus - subscriptions map[string][]chan *AggregateStatus + subscriptions map[string][]*subscription + aggregationFunc aggregationFunc } // NewAggregator returns a *status.Aggregator. -func NewAggregator() *Aggregator { +func NewAggregator(errPriority ErrorPriority) *Aggregator { return &Aggregator{ aggregateStatus: &AggregateStatus{ - StatusEvent: &component.StatusEvent{}, + Event: &component.StatusEvent{}, ComponentStatusMap: make(map[string]*AggregateStatus), }, - subscriptions: make(map[string][]chan *AggregateStatus), + subscriptions: make(map[string][]*subscription), + aggregationFunc: newAggregationFunc(errPriority), } } @@ -139,12 +103,12 @@ func NewAggregator() *Aggregator { // overall (ScopeAll), extensions (ScopeExtensions), or a pipeline by name. Detail specifies whether // or not subtrees should be returned with the *AggregateStatus. The boolean return value indicates // whether or not the scope was found. -func (a *Aggregator) AggregateStatus(scope Scope) (*AggregateStatus, bool) { +func (a *Aggregator) AggregateStatus(scope Scope, verbosity Verbosity) (*AggregateStatus, bool) { a.mu.Lock() defer a.mu.Unlock() if scope == ScopeAll { - return a.aggregateStatus.clone(), true + return a.aggregateStatus.clone(verbosity), true } st, ok := a.aggregateStatus.ComponentStatusMap[scope.toKey()] @@ -152,7 +116,7 @@ func (a *Aggregator) AggregateStatus(scope Scope) (*AggregateStatus, bool) { return nil, false } - return st.clone(), true + return st.clone(verbosity), true } // RecordStatus stores and aggregates a StatusEvent for the given component instance. @@ -180,18 +144,14 @@ func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component componentKey := fmt.Sprintf("%s:%s", strings.ToLower(source.Kind.String()), source.ID) pipelineStatus.ComponentStatusMap[componentKey] = &AggregateStatus{ - StatusEvent: event, + Event: event, } a.aggregateStatus.ComponentStatusMap[pipelineKey] = pipelineStatus - pipelineStatus.StatusEvent = component.AggregateStatusEvent( - toStatusEventMap(pipelineStatus), - ) + pipelineStatus.Event = a.aggregationFunc(pipelineStatus) a.notifySubscribers(pipelineScope, pipelineStatus) } - a.aggregateStatus.StatusEvent = component.AggregateStatusEvent( - toStatusEventMap(a.aggregateStatus), - ) + a.aggregateStatus.Event = a.aggregationFunc(a.aggregateStatus) a.notifySubscribers(ScopeAll, a.aggregateStatus) } @@ -200,7 +160,7 @@ func (a *Aggregator) RecordStatus(source *component.InstanceID, event *component // It is possible to subscribe to a pipeline that has not yet reported. An initial nil // will be sent on the channel and events will start streaming if and when it starts reporting. // Detail specifies whether or not subtrees should be returned with the *AggregateStatus. -func (a *Aggregator) Subscribe(scope Scope) <-chan *AggregateStatus { +func (a *Aggregator) Subscribe(scope Scope, verbosity Verbosity) <-chan *AggregateStatus { a.mu.Lock() defer a.mu.Unlock() @@ -209,13 +169,18 @@ func (a *Aggregator) Subscribe(scope Scope) <-chan *AggregateStatus { if scope != ScopeAll { st = st.ComponentStatusMap[key] } + if st != nil { + st = st.clone(verbosity) + } + sub := &subscription{ + statusCh: make(chan *AggregateStatus, 1), + verbosity: verbosity, + } - statusCh := make(chan *AggregateStatus, 1) - - a.subscriptions[key] = append(a.subscriptions[key], statusCh) - statusCh <- st + a.subscriptions[key] = append(a.subscriptions[key], sub) + sub.statusCh <- st - return statusCh + return sub.statusCh } // Unbsubscribe removes a stream from further status updates. @@ -225,7 +190,7 @@ func (a *Aggregator) Unsubscribe(statusCh <-chan *AggregateStatus) { for scope, subs := range a.subscriptions { for i, sub := range subs { - if sub == statusCh { + if sub.statusCh == statusCh { a.subscriptions[scope] = append(subs[:i], subs[i+1:]...) return } @@ -240,7 +205,7 @@ func (a *Aggregator) Close() { for _, subs := range a.subscriptions { for _, sub := range subs { - close(sub) + close(sub.statusCh) } } } @@ -249,17 +214,9 @@ func (a *Aggregator) notifySubscribers(scope Scope, status *AggregateStatus) { for _, sub := range a.subscriptions[scope.toKey()] { // clear unread events select { - case <-sub: + case <-sub.statusCh: default: } - sub <- status.clone() - } -} - -func toStatusEventMap(aggStatus *AggregateStatus) map[string]*component.StatusEvent { - result := make(map[string]*component.StatusEvent, len(aggStatus.ComponentStatusMap)) - for k, v := range aggStatus.ComponentStatusMap { - result[k] = v.StatusEvent + sub.statusCh <- status.clone(sub.verbosity) } - return result } diff --git a/extension/healthcheckextensionv2/internal/status/aggregator_test.go b/extension/healthcheckextensionv2/internal/status/aggregator_test.go index 527a8a3f34dcc..f97f7bab9a050 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator_test.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator_test.go @@ -4,11 +4,9 @@ package status_test import ( - "errors" "fmt" "strings" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,62 +16,62 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" ) -// func TestAggregateStatus(t *testing.T) { -// agg := status.NewAggregator() -// traces := testhelpers.NewPipelineMetadata("traces") - -// t.Run("zero value", func(t *testing.T) { -// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) -// require.True(t, ok) -// assert.Equal(t, component.StatusNone, st.Status()) -// }) - -// testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) - -// t.Run("pipeline statuses all successful", func(t *testing.T) { -// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) -// require.True(t, ok) -// assert.Equal(t, component.StatusOK, st.Status()) -// }) - -// agg.RecordStatus( -// traces.ExporterID, -// component.NewRecoverableErrorEvent(assert.AnError), -// ) - -// t.Run("pipeline with recoverable error", func(t *testing.T) { -// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) -// require.True(t, ok) -// assertErrorEventsMatch(t, -// component.StatusRecoverableError, -// assert.AnError, -// st, -// ) -// }) - -// agg.RecordStatus( -// traces.ExporterID, -// component.NewPermanentErrorEvent(assert.AnError), -// ) - -// t.Run("pipeline with permanent error", func(t *testing.T) { -// st, ok := agg.AggregateStatus(status.ScopeAll, status.ExcludeSubtrees) -// require.True(t, ok) -// assertErrorEventsMatch(t, -// component.StatusPermanentError, -// assert.AnError, -// st, -// ) -// }) -// } - -func TestAggregateStatusDetailed(t *testing.T) { - agg := status.NewAggregator() +func TestAggregateStatus(t *testing.T) { + agg := status.NewAggregator(status.PriorityPermanent) + traces := testhelpers.NewPipelineMetadata("traces") + + t.Run("zero value", func(t *testing.T) { + st, ok := agg.AggregateStatus(status.ScopeAll, status.Concise) + require.True(t, ok) + assert.Equal(t, component.StatusNone, st.Status()) + }) + + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + + t.Run("pipeline statuses all successful", func(t *testing.T) { + st, ok := agg.AggregateStatus(status.ScopeAll, status.Concise) + require.True(t, ok) + assert.Equal(t, component.StatusOK, st.Status()) + }) + + agg.RecordStatus( + traces.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + + t.Run("pipeline with recoverable error", func(t *testing.T) { + st, ok := agg.AggregateStatus(status.ScopeAll, status.Concise) + require.True(t, ok) + assertErrorEventsMatch(t, + component.StatusRecoverableError, + assert.AnError, + st, + ) + }) + + agg.RecordStatus( + traces.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + + t.Run("pipeline with permanent error", func(t *testing.T) { + st, ok := agg.AggregateStatus(status.ScopeAll, status.Concise) + require.True(t, ok) + assertErrorEventsMatch(t, + component.StatusPermanentError, + assert.AnError, + st, + ) + }) +} + +func TestAggregateStatusVerbose(t *testing.T) { + agg := status.NewAggregator(status.PriorityPermanent) traces := testhelpers.NewPipelineMetadata("traces") tracesKey := toPipelineKey(traces.PipelineID) t.Run("zero value", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll) + st, ok := agg.AggregateStatus(status.ScopeAll, status.Verbose) require.True(t, ok) assertEventsMatch(t, component.StatusNone, st) assert.Empty(t, st.ComponentStatusMap) @@ -83,7 +81,7 @@ func TestAggregateStatusDetailed(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline statuses all successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll) + st, ok := agg.AggregateStatus(status.ScopeAll, status.Verbose) require.True(t, ok) // The top-level status and pipeline status match. @@ -103,7 +101,7 @@ func TestAggregateStatusDetailed(t *testing.T) { ) t.Run("pipeline with exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.ScopeAll) + st, ok := agg.AggregateStatus(status.ScopeAll, status.Verbose) require.True(t, ok) // The top-level status and pipeline status match. assertErrorEventsMatch( @@ -130,48 +128,12 @@ func TestAggregateStatusDetailed(t *testing.T) { } -// func TestPipelineAggregateStatus(t *testing.T) { -// agg := status.NewAggregator() -// traces := testhelpers.NewPipelineMetadata("traces") - -// t.Run("non existent pipeline", func(t *testing.T) { -// st, ok := agg.AggregateStatus("doesnotexist", status.ExcludeSubtrees) -// require.Nil(t, st) -// require.False(t, ok) -// }) - -// testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) - -// t.Run("pipeline exists / status successful", func(t *testing.T) { -// st, ok := agg.AggregateStatus( -// status.Scope(traces.PipelineID.String()), -// status.ExcludeSubtrees, -// ) -// require.True(t, ok) -// assertEventsMatch(t, component.StatusOK, st) -// }) - -// agg.RecordStatus( -// traces.ExporterID, -// component.NewRecoverableErrorEvent(assert.AnError), -// ) - -// t.Run("pipeline exists / exporter error", func(t *testing.T) { -// st, ok := agg.AggregateStatus( -// status.Scope(traces.PipelineID.String()), -// status.ExcludeSubtrees, -// ) -// require.True(t, ok) -// assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, st) -// }) -// } - -func TestPipelineAggregateStatusDetailed(t *testing.T) { - agg := status.NewAggregator() +func TestPipelineAggregateStatus(t *testing.T) { + agg := status.NewAggregator(status.PriorityPermanent) traces := testhelpers.NewPipelineMetadata("traces") t.Run("non existent pipeline", func(t *testing.T) { - st, ok := agg.AggregateStatus("doesnotexist") + st, ok := agg.AggregateStatus("doesnotexist", status.Concise) require.Nil(t, st) require.False(t, ok) }) @@ -179,7 +141,43 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) t.Run("pipeline exists / status successful", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.Scope(traces.PipelineID.String())) + st, ok := agg.AggregateStatus( + status.Scope(traces.PipelineID.String()), + status.Concise, + ) + require.True(t, ok) + assertEventsMatch(t, component.StatusOK, st) + }) + + agg.RecordStatus( + traces.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + + t.Run("pipeline exists / exporter error", func(t *testing.T) { + st, ok := agg.AggregateStatus( + status.Scope(traces.PipelineID.String()), + status.Concise, + ) + require.True(t, ok) + assertErrorEventsMatch(t, component.StatusRecoverableError, assert.AnError, st) + }) +} + +func TestPipelineAggregateStatusVerbose(t *testing.T) { + agg := status.NewAggregator(status.PriorityPermanent) + traces := testhelpers.NewPipelineMetadata("traces") + + t.Run("non existent pipeline", func(t *testing.T) { + st, ok := agg.AggregateStatus("doesnotexist", status.Verbose) + require.Nil(t, st) + require.False(t, ok) + }) + + testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) + + t.Run("pipeline exists / status successful", func(t *testing.T) { + st, ok := agg.AggregateStatus(status.Scope(traces.PipelineID.String()), status.Verbose) require.True(t, ok) // Top-level status matches @@ -192,7 +190,7 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { agg.RecordStatus(traces.ExporterID, component.NewRecoverableErrorEvent(assert.AnError)) t.Run("pipeline exists / exporter error", func(t *testing.T) { - st, ok := agg.AggregateStatus(status.Scope(traces.PipelineID.String())) + st, ok := agg.AggregateStatus(status.Scope(traces.PipelineID.String()), status.Verbose) require.True(t, ok) // Top-level status matches @@ -212,15 +210,15 @@ func TestPipelineAggregateStatusDetailed(t *testing.T) { } func TestStreaming(t *testing.T) { - agg := status.NewAggregator() + agg := status.NewAggregator(status.PriorityPermanent) defer agg.Close() traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") - traceEvents := agg.Subscribe(status.Scope(traces.PipelineID.String())) - metricEvents := agg.Subscribe(status.Scope(metrics.PipelineID.String())) - allEvents := agg.Subscribe(status.ScopeAll) + traceEvents := agg.Subscribe(status.Scope(traces.PipelineID.String()), status.Concise) + metricEvents := agg.Subscribe(status.Scope(metrics.PipelineID.String()), status.Concise) + allEvents := agg.Subscribe(status.ScopeAll, status.Concise) assert.Nil(t, <-traceEvents) assert.Nil(t, <-metricEvents) @@ -267,14 +265,14 @@ func TestStreaming(t *testing.T) { assertEventsRecvdMatch(t, component.StatusStopped, metricEvents, allEvents) } -func TestStreamingDetailed(t *testing.T) { - agg := status.NewAggregator() +func TestStreamingVerbose(t *testing.T) { + agg := status.NewAggregator(status.PriorityPermanent) defer agg.Close() traces := testhelpers.NewPipelineMetadata("traces") tracesKey := toPipelineKey(traces.PipelineID) - allEvents := agg.Subscribe(status.ScopeAll) + allEvents := agg.Subscribe(status.ScopeAll, status.Verbose) t.Run("zero value", func(t *testing.T) { st := <-allEvents @@ -333,9 +331,9 @@ func assertEventsMatch( expectedStatus component.Status, statuses ...*status.AggregateStatus, ) { - err0 := statuses[0].StatusEvent.Err() + err0 := statuses[0].Event.Err() for _, st := range statuses { - ev := st.StatusEvent + ev := st.Event assert.Equal(t, expectedStatus, ev.Status()) assert.Equal(t, err0, ev.Err()) } @@ -351,7 +349,7 @@ func assertErrorEventsMatch( ) { assert.True(t, component.StatusIsError(expectedStatus)) for _, st := range statuses { - ev := st.StatusEvent + ev := st.Event assert.Equal(t, expectedStatus, ev.Status()) assert.Equal(t, expectedErr, ev.Err()) } @@ -375,7 +373,7 @@ func assertEventsRecvdMatch(t *testing.T, var err0 error for i, stCh := range chans { st := <-stCh - ev := st.StatusEvent + ev := st.Event if i == 0 { err0 = ev.Err() } @@ -392,7 +390,7 @@ func assertErrorEventsRecvdMatch(t *testing.T, assert.True(t, component.StatusIsError(expectedStatus)) for _, stCh := range chans { st := <-stCh - ev := st.StatusEvent + ev := st.Event assert.Equal(t, expectedStatus, ev.Status()) assert.Equal(t, expectedErr, ev.Err()) } @@ -405,51 +403,3 @@ func toComponentKey(id *component.InstanceID) string { func toPipelineKey(id component.ID) string { return fmt.Sprintf("pipeline:%s", id.String()) } - -func TestSearch(t *testing.T) { - agg := status.NewAggregator() - defer agg.Close() - - traces := testhelpers.NewPipelineMetadata("traces") - metrics := testhelpers.NewPipelineMetadata("metrics") - - testhelpers.SeedAggregator(agg, traces.InstanceIDs(), component.StatusOK) - testhelpers.SeedAggregator(agg, metrics.InstanceIDs(), component.StatusStarting) - - agg.RecordStatus( - traces.ExporterID, - component.NewPermanentErrorEvent(errors.New("it's perm")), - ) - agg.RecordStatus( - traces.ReceiverID, - component.NewRecoverableErrorEvent(errors.New("first recoverable")), - ) - time.Sleep(20 * time.Millisecond) - agg.RecordStatus( - traces.ProcessorID, - component.NewRecoverableErrorEvent(errors.New("second recoverable")), - ) - - stAll, _ := agg.AggregateStatus(status.ScopeAll) - assert.False(t, stAll.Ready()) - - reAll, found := stAll.ActiveRecoverable() - assert.NotNil(t, reAll) - assert.True(t, found) - assert.Equal(t, reAll.Err().Error(), "first recoverable") - - stTraces, _ := agg.AggregateStatus(status.Scope("traces")) - assert.True(t, stTraces.Ready()) - - reTr, found := stTraces.ActiveRecoverable() - assert.NotNil(t, reTr) - assert.True(t, found) - assert.Equal(t, reTr.Err().Error(), "first recoverable") - - stMetrics, _ := agg.AggregateStatus(status.Scope("metrics")) - assert.False(t, stMetrics.Ready()) - - reMt, found := stMetrics.ActiveRecoverable() - assert.Nil(t, reMt) - assert.False(t, found) -} diff --git a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go index 92c2cb81c5b2d..bf2137140bf5b 100644 --- a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go +++ b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go @@ -6,6 +6,7 @@ package testhelpers // import "github.com/open-telemetry/opentelemetry-collector import ( "go.opentelemetry.io/collector/component" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) @@ -73,3 +74,10 @@ func SeedAggregator( } } } + +func ErrPriority(settings *common.ComponentHealthSettings) status.ErrorPriority { + if settings != nil && settings.IncludeRecoverable && !settings.IncludePermanent { + return status.PriorityRecoverable + } + return status.PriorityPermanent +} From c8f9d561717f2df09c7e1ad965fe7c2b733304d4 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Wed, 21 Feb 2024 11:39:24 -0800 Subject: [PATCH 11/26] Simplify HTTP strategies --- .../internal/http/handlers.go | 2 +- .../internal/http/health_strategy.go | 83 - .../internal/http/responders.go | 81 + .../internal/http/server.go | 9 +- .../internal/http/server_test.go | 3395 ++++++++++++++--- .../internal/status/aggregation_test.go | 124 + 6 files changed, 3040 insertions(+), 654 deletions(-) delete mode 100644 extension/healthcheckextensionv2/internal/http/health_strategy.go create mode 100644 extension/healthcheckextensionv2/internal/http/responders.go create mode 100644 extension/healthcheckextensionv2/internal/status/aggregation_test.go diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go index d8c288fb2e136..6e09d38ac7934 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -21,7 +21,7 @@ func (s *Server) statusHandler() http.Handler { return } - code, sst := s.strategy.toResponse(st) + code, sst := s.responder.response(st) w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) diff --git a/extension/healthcheckextensionv2/internal/http/health_strategy.go b/extension/healthcheckextensionv2/internal/http/health_strategy.go deleted file mode 100644 index 1e996a1fa651d..0000000000000 --- a/extension/healthcheckextensionv2/internal/http/health_strategy.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" - -import ( - "net/http" - "time" - - "go.opentelemetry.io/collector/component" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" -) - -var responseCodes = map[component.Status]int{ - component.StatusNone: http.StatusServiceUnavailable, - component.StatusStarting: http.StatusServiceUnavailable, - component.StatusOK: http.StatusOK, - component.StatusRecoverableError: http.StatusOK, - component.StatusPermanentError: http.StatusOK, - component.StatusFatalError: http.StatusInternalServerError, - component.StatusStopping: http.StatusServiceUnavailable, - component.StatusStopped: http.StatusServiceUnavailable, -} - -type healthStrategy interface { - toResponse(*status.AggregateStatus) (int, *serializableStatus) -} - -type defaultHealthStrategy struct { - startTimestamp *time.Time -} - -func (d *defaultHealthStrategy) toResponse(st *status.AggregateStatus) (int, *serializableStatus) { - return responseCodes[st.Status()], toSerializableStatus(st, &serializationOptions{ - includeStartTime: true, - startTimestamp: d.startTimestamp, - }) -} - -type componentHealthStrategy struct { - settings *common.ComponentHealthSettings - startTimestamp *time.Time -} - -func (c *componentHealthStrategy) toResponse(st *status.AggregateStatus) (int, *serializableStatus) { - now := time.Now() - sst := toSerializableStatus( - st, - &serializationOptions{ - includeStartTime: true, - startTimestamp: c.startTimestamp, - healthyFunc: c.healthyFunc(&now), - }, - ) - - if c.settings.IncludePermanent && st.Status() == component.StatusPermanentError { - return http.StatusInternalServerError, sst - } - - if c.settings.IncludeRecoverable && st.Status() == component.StatusRecoverableError { - if now.After(st.Timestamp().Add(c.settings.RecoveryDuration)) { - return http.StatusInternalServerError, sst - } - } - - return responseCodes[st.Status()], sst -} - -func (c *componentHealthStrategy) healthyFunc(now *time.Time) func(status.Event) bool { - return func(ev status.Event) bool { - if ev.Status() == component.StatusPermanentError { - return !c.settings.IncludePermanent - } - - if ev.Status() == component.StatusRecoverableError && c.settings.IncludeRecoverable { - return now.Before(ev.Timestamp().Add(c.settings.RecoveryDuration)) - } - - return ev.Status() != component.StatusFatalError - } -} diff --git a/extension/healthcheckextensionv2/internal/http/responders.go b/extension/healthcheckextensionv2/internal/http/responders.go new file mode 100644 index 0000000000000..948c8c55a45b3 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/http/responders.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + +import ( + "net/http" + "time" + + "go.opentelemetry.io/collector/component" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +) + +var responseCodes = map[component.Status]int{ + component.StatusNone: http.StatusServiceUnavailable, + component.StatusStarting: http.StatusServiceUnavailable, + component.StatusOK: http.StatusOK, + component.StatusRecoverableError: http.StatusOK, + component.StatusPermanentError: http.StatusOK, + component.StatusFatalError: http.StatusInternalServerError, + component.StatusStopping: http.StatusServiceUnavailable, + component.StatusStopped: http.StatusServiceUnavailable, +} + +type healthResponder interface { + response(*status.AggregateStatus) (int, *serializableStatus) +} + +type responseFunc func(*status.AggregateStatus) (int, *serializableStatus) + +func (f responseFunc) response(st *status.AggregateStatus) (int, *serializableStatus) { + return f(st) +} + +func defaultHealthResponder(startTimestamp *time.Time) responseFunc { + return func(st *status.AggregateStatus) (int, *serializableStatus) { + return responseCodes[st.Status()], toSerializableStatus(st, &serializationOptions{ + includeStartTime: true, + startTimestamp: startTimestamp, + }) + } +} + +func componentHealthResponder( + startTimestamp *time.Time, + settings *common.ComponentHealthSettings, +) responseFunc { + healthyFunc := func(now *time.Time) func(status.Event) bool { + return func(ev status.Event) bool { + if ev.Status() == component.StatusPermanentError { + return !settings.IncludePermanent + } + + if ev.Status() == component.StatusRecoverableError && settings.IncludeRecoverable { + return now.Before(ev.Timestamp().Add(settings.RecoveryDuration)) + } + + return ev.Status() != component.StatusFatalError + } + } + return func(st *status.AggregateStatus) (int, *serializableStatus) { + now := time.Now() + sst := toSerializableStatus( + st, + &serializationOptions{ + includeStartTime: true, + startTimestamp: startTimestamp, + healthyFunc: healthyFunc(&now), + }, + ) + + code := responseCodes[st.Status()] + if !sst.Healthy { + code = http.StatusInternalServerError + } + + return code, sst + } +} diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckextensionv2/internal/http/server.go index 230ae543567be..7beb4e32c2453 100644 --- a/extension/healthcheckextensionv2/internal/http/server.go +++ b/extension/healthcheckextensionv2/internal/http/server.go @@ -24,7 +24,7 @@ import ( type Server struct { telemetry component.TelemetrySettings settings *Settings - strategy healthStrategy + responder healthResponder mux *http.ServeMux serverHTTP *http.Server colconf atomic.Value @@ -46,16 +46,13 @@ func NewServer( srv := &Server{ telemetry: telemetry, settings: settings, - strategy: &defaultHealthStrategy{startTimestamp: &now}, + responder: defaultHealthResponder(&now), aggregator: aggregator, doneCh: make(chan struct{}), } if componentHealthSettings != nil { - srv.strategy = &componentHealthStrategy{ - settings: componentHealthSettings, - startTimestamp: &now, - } + srv.responder = componentHealthResponder(&now, componentHealthSettings) } srv.mux = http.NewServeMux() diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go index e54d7000c861d..79595f730abb9 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -44,10 +44,9 @@ type teststep struct { } func TestStatus(t *testing.T) { - // server and pipeline are reassigned before each test and are available for - // use in the teststeps var server *Server - var pipelines map[string]*testhelpers.PipelineMetadata + traces := testhelpers.NewPipelineMetadata("traces") + metrics := testhelpers.NewPipelineMetadata("metrics") tests := []struct { name string @@ -57,7 +56,7 @@ func TestStatus(t *testing.T) { teststeps []teststep }{ { - name: "overall status - default strategy", + name: "exclude recoverable and permanent errors", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -68,22 +67,59 @@ func TestStatus(t *testing.T) { Path: "/status", }, }, - pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { step: func() { testhelpers.SeedAggregator(server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator(server.aggregator, + metrics.InstanceIDs(), component.StatusStarting, ) }, expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, }, { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + metrics.InstanceIDs(), component.StatusOK, ) }, @@ -93,10 +129,18 @@ func TestStatus(t *testing.T) { status: component.StatusOK, }, }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, + metrics.ExporterID, component.NewRecoverableErrorEvent(assert.AnError), ) }, @@ -107,10 +151,27 @@ func TestStatus(t *testing.T) { err: assert.AnError, }, }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, + metrics.ExporterID, component.NewStatusEvent(component.StatusOK), ) }, @@ -120,20 +181,119 @@ func TestStatus(t *testing.T) { status: component.StatusOK, }, }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), component.StatusStopping, ) }, expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, }, }, }, { - name: "overall status - default strategy - verbose", + name: "exclude recoverable and permanent errors - verbose", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -144,48 +304,57 @@ func TestStatus(t *testing.T) { Path: "/status", }, }, - pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), + testhelpers.SeedAggregator(server.aggregator, + traces.InstanceIDs(), component.StatusStarting, ) - }, - queryParams: "verbose", - expectedStatusCode: http.StatusServiceUnavailable, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusOK, + testhelpers.SeedAggregator(server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, ) }, queryParams: "verbose", - expectedStatusCode: http.StatusOK, + expectedStatusCode: http.StatusServiceUnavailable, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusOK, + status: component.StatusStarting, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { healthy: true, - status: component.StatusOK, + status: component.StatusStarting, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, - status: component.StatusOK, + status: component.StatusStarting, }, "processor:batch": { healthy: true, - status: component.StatusOK, + status: component.StatusStarting, }, "exporter:traces/out": { healthy: true, - status: component.StatusOK, + status: component.StatusStarting, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStarting, }, }, }, @@ -194,23 +363,21 @@ func TestStatus(t *testing.T) { }, { step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, ) }, queryParams: "verbose", - eventually: true, - expectedStatusCode: http.StatusOK, + expectedStatusCode: http.StatusServiceUnavailable, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusRecoverableError, - err: assert.AnError, + status: component.StatusStarting, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { healthy: true, - status: component.StatusRecoverableError, - err: assert.AnError, + status: component.StatusOK, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, @@ -222,19 +389,81 @@ func TestStatus(t *testing.T) { }, "exporter:traces/out": { healthy: true, - status: component.StatusRecoverableError, - err: assert.AnError, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStarting, }, }, }, }, }, }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + }, { step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, ) }, queryParams: "verbose", @@ -261,136 +490,128 @@ func TestStatus(t *testing.T) { }, }, }, - }, - }, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, - ) - }, - expectedStatusCode: http.StatusServiceUnavailable, - }, - }, - }, - { - name: "pipeline status - default strategy", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - pipelines: testhelpers.NewPipelines("traces"), - teststeps: []teststep{ - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStarting, - ) + "pipeline:metrics": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, }, - queryParams: "pipeline=traces", - expectedStatusCode: http.StatusServiceUnavailable, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusOK, - ) - }, - queryParams: "pipeline=traces", + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, }, }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, + metrics.ExporterID, component.NewRecoverableErrorEvent(assert.AnError), ) }, - queryParams: "pipeline=traces", - eventually: true, + queryParams: "verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, status: component.StatusRecoverableError, err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusRecoverableError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, }, }, { - step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), - ) - }, - queryParams: "pipeline=traces", + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusOK, - }, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, - ) - }, - queryParams: "pipeline=traces&verbose", - expectedStatusCode: http.StatusServiceUnavailable, - }, - }, - }, - { - name: "pipeline status - default strategy - verbose", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - pipelines: testhelpers.NewPipelines("traces"), - teststeps: []teststep{ - { - step: func() { - testhelpers.SeedAggregator(server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStarting, - ) + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, }, - queryParams: "pipeline=traces&verbose", - expectedStatusCode: http.StatusServiceUnavailable, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusOK, - ) - }, queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ @@ -415,48 +636,63 @@ func TestStatus(t *testing.T) { { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), ) }, - queryParams: "pipeline=traces&verbose", - eventually: true, + queryParams: "verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusRecoverableError, - err: assert.AnError, + status: component.StatusOK, nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { + "pipeline:traces": { healthy: true, status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, }, - "processor:batch": { + "pipeline:metrics": { healthy: true, status: component.StatusOK, - }, - "exporter:traces/out": { - healthy: true, - status: component.StatusRecoverableError, - err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, }, }, }, }, { - step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), - ) - }, - queryParams: "pipeline=traces&verbose", + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, status: component.StatusOK, nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { + "receiver:metrics/in": { healthy: true, status: component.StatusOK, }, @@ -464,7 +700,7 @@ func TestStatus(t *testing.T) { healthy: true, status: component.StatusOK, }, - "exporter:traces/out": { + "exporter:metrics/out": { healthy: true, status: component.StatusOK, }, @@ -473,135 +709,1664 @@ func TestStatus(t *testing.T) { }, { step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), ) }, - queryParams: "pipeline=traces&verbose", - expectedStatusCode: http.StatusServiceUnavailable, - }, - }, - }, - { - name: "overall status - component health stategy", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanent: true, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusPermanentError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusPermanentError, + }, + }, + }, + }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + }, + }, + { + name: "include recoverable and exclude permanent errors", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: false, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, + }, + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + eventually: true, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + { + name: "include recoverable and exclude permanent errors - verbose", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: false, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, }, - pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { step: func() { testhelpers.SeedAggregator(server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator(server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + eventually: true, + queryParams: "verbose", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: false, + status: component.StatusRecoverableError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusPermanentError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusPermanentError, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + }, + }, + { + name: "include permanent and exclude recoverable errors", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + }, + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + { + name: "include permanent and exclude recoverable errors - verbose", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + }, + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator(server.aggregator, + metrics.InstanceIDs(), component.StatusStarting, ) }, + queryParams: "verbose", expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + }, + }, }, { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), component.StatusOK, ) }, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, }, }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, + metrics.ExporterID, component.NewRecoverableErrorEvent(assert.AnError), ) }, eventually: true, - expectedStatusCode: http.StatusInternalServerError, + queryParams: "verbose", + expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ - healthy: false, + healthy: true, status: component.StatusRecoverableError, err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusRecoverableError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + }, + }, }, }, { - step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), - ) + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, }, + }, + { + queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, }, }, { step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, - ) - }, - expectedStatusCode: http.StatusServiceUnavailable, - }, - }, - }, - { - name: "overall status - component health strategy - verbose", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanent: true, - IncludeRecoverable: true, - RecoveryDuration: 2 * time.Millisecond, - }, - pipelines: testhelpers.NewPipelines("traces"), - teststeps: []teststep{ - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStarting, - ) - }, - queryParams: "verbose", - expectedStatusCode: http.StatusServiceUnavailable, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusOK, + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), ) }, queryParams: "verbose", @@ -628,28 +2393,66 @@ func TestStatus(t *testing.T) { }, }, }, + "pipeline:metrics": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, }, }, }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), ) }, queryParams: "verbose", - eventually: true, expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, - status: component.StatusRecoverableError, + status: component.StatusPermanentError, err: assert.AnError, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { - healthy: false, - status: component.StatusRecoverableError, - err: assert.AnError, + healthy: true, + status: component.StatusOK, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, @@ -660,8 +2463,26 @@ func TestStatus(t *testing.T) { status: component.StatusOK, }, "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: false, + status: component.StatusPermanentError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { healthy: false, - status: component.StatusRecoverableError, + status: component.StatusPermanentError, err: assert.AnError, }, }, @@ -669,34 +2490,183 @@ func TestStatus(t *testing.T) { }, }, }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: false, + status: component.StatusPermanentError, + }, + }, + }, + }, { step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, ) }, queryParams: "verbose", - expectedStatusCode: http.StatusOK, + expectedStatusCode: http.StatusServiceUnavailable, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusOK, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStopped, nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { + "receiver:metrics/in": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "processor:batch": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, - "exporter:traces/out": { + "exporter:metrics/out": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, }, }, @@ -704,19 +2674,53 @@ func TestStatus(t *testing.T) { }, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, - ) + queryParams: "pipeline=traces&verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopped, + }, + }, }, + }, + { + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopped, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopped, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopped, + }, + }, + }, }, }, }, { - name: "overall status - component health strategy - exclude permanent", + name: "include permanent and recoverable errors", settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -728,27 +2732,56 @@ func TestStatus(t *testing.T) { }, }, componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanent: false, + IncludePermanent: true, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, }, - pipelines: testhelpers.NewPipelines("traces"), teststeps: []teststep{ { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), component.StatusStarting, ) }, expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + }, }, { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), component.StatusOK, ) }, @@ -758,14 +2791,26 @@ func TestStatus(t *testing.T) { status: component.StatusOK, }, }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ReceiverID, - component.NewPermanentErrorEvent(assert.AnError), - ) - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, + metrics.ExporterID, component.NewRecoverableErrorEvent(assert.AnError), ) }, @@ -777,66 +2822,231 @@ func TestStatus(t *testing.T) { err: assert.AnError, }, }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, + metrics.ExporterID, component.NewStatusEvent(component.StatusOK), ) }, expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, + status: component.StatusOK, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + }, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, }, }, { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), component.StatusStopping, ) }, - expectedStatusCode: http.StatusServiceUnavailable, - }, - }, - }, - { - name: "overall status - component health strategy - exclude permanent - verbose", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + }, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=traces", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + { + queryParams: "pipeline=metrics", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopped, + }, + }, + }, + }, + { + name: "include permanent and recoverable errors - verbose", + settings: &Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Config: PathSettings{Enabled: false}, + Status: PathSettings{ + Enabled: true, + Path: "/status", + }, + }, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, + }, + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator(server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStarting, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStarting, + }, + "processor:batch": { + healthy: true, + status: component.StatusStarting, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStarting, + }, + }, + }, + }, + }, }, - }, - componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanent: false, - IncludeRecoverable: true, - RecoveryDuration: 2 * time.Millisecond, - }, - pipelines: testhelpers.NewPipelines("traces"), - teststeps: []teststep{ { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStarting, + traces.InstanceIDs(), + component.StatusOK, ) - }, - queryParams: "verbose", - expectedStatusCode: http.StatusServiceUnavailable, - }, - { - step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + metrics.InstanceIDs(), component.StatusOK, ) }, @@ -864,22 +3074,58 @@ func TestStatus(t *testing.T) { }, }, }, + "pipeline:metrics": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, }, }, }, { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ReceiverID, - component.NewPermanentErrorEvent(assert.AnError), - ) - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, + metrics.ExporterID, component.NewRecoverableErrorEvent(assert.AnError), ) }, - queryParams: "verbose", eventually: true, + queryParams: "verbose", expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, @@ -887,56 +3133,39 @@ func TestStatus(t *testing.T) { err: assert.AnError, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { - healthy: false, - status: component.StatusRecoverableError, - err: assert.AnError, + healthy: true, + status: component.StatusOK, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, - status: component.StatusPermanentError, + status: component.StatusOK, }, "processor:batch": { healthy: true, status: component.StatusOK, }, "exporter:traces/out": { - healthy: false, - status: component.StatusRecoverableError, - err: assert.AnError, + healthy: true, + status: component.StatusOK, }, }, }, - }, - }, - }, - { - step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), - ) - }, - queryParams: "verbose", - expectedStatusCode: http.StatusOK, - expectedComponentStatus: &componentStatusExpectation{ - healthy: true, - status: component.StatusPermanentError, - nestedStatus: map[string]*componentStatusExpectation{ - "pipeline:traces": { - healthy: true, - status: component.StatusPermanentError, + "pipeline:metrics": { + healthy: false, + status: component.StatusRecoverableError, nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { + "receiver:metrics/in": { healthy: true, - status: component.StatusPermanentError, + status: component.StatusOK, }, "processor:batch": { healthy: true, status: component.StatusOK, }, - "exporter:traces/out": { - healthy: true, - status: component.StatusOK, + "exporter:metrics/out": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, }, }, }, @@ -944,149 +3173,111 @@ func TestStatus(t *testing.T) { }, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, - ) - }, - expectedStatusCode: http.StatusServiceUnavailable, - }, - }, - }, - { - name: "pipeline status - component health strategy", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanent: true, - IncludeRecoverable: true, - RecoveryDuration: 2 * time.Millisecond, - }, - pipelines: testhelpers.NewPipelines("traces"), - teststeps: []teststep{ - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStarting, - ) - }, - queryParams: "pipeline=traces", - expectedStatusCode: http.StatusServiceUnavailable, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusOK, - ) - }, - queryParams: "pipeline=traces", - expectedStatusCode: http.StatusOK, - expectedComponentStatus: &componentStatusExpectation{ - healthy: true, - status: component.StatusOK, - }, - }, - { - step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), - ) - }, - queryParams: "pipeline=traces", - eventually: true, + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, status: component.StatusRecoverableError, err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: false, + status: component.StatusRecoverableError, + err: assert.AnError, + }, + }, }, }, { - step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), - ) - }, - queryParams: "pipeline=traces", + queryParams: "pipeline=traces&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, }, }, { step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), ) }, - queryParams: "pipeline=traces&verbose", - expectedStatusCode: http.StatusServiceUnavailable, - }, - }, - }, - { - name: "pipeline status - component health strategy - verbose", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanent: true, - IncludeRecoverable: true, - RecoveryDuration: 2 * time.Millisecond, - }, - pipelines: testhelpers.NewPipelines("traces"), - teststeps: []teststep{ - { - step: func() { - testhelpers.SeedAggregator(server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStarting, - ) + queryParams: "verbose", + expectedStatusCode: http.StatusOK, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + }, }, - queryParams: "pipeline=traces&verbose", - expectedStatusCode: http.StatusServiceUnavailable, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusOK, - ) - }, - queryParams: "pipeline=traces&verbose", + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusOK, expectedComponentStatus: &componentStatusExpectation{ healthy: true, status: component.StatusOK, nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { + "receiver:metrics/in": { healthy: true, status: component.StatusOK, }, @@ -1094,7 +3285,7 @@ func TestStatus(t *testing.T) { healthy: true, status: component.StatusOK, }, - "exporter:traces/out": { + "exporter:metrics/out": { healthy: true, status: component.StatusOK, }, @@ -1104,19 +3295,66 @@ func TestStatus(t *testing.T) { { step: func() { server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), ) }, - queryParams: "pipeline=traces&verbose", - eventually: true, + queryParams: "verbose", expectedStatusCode: http.StatusInternalServerError, expectedComponentStatus: &componentStatusExpectation{ healthy: false, - status: component.StatusRecoverableError, + status: component.StatusPermanentError, err: assert.AnError, nestedStatus: map[string]*componentStatusExpectation{ - "receiver:traces/in": { + "pipeline:traces": { + healthy: true, + status: component.StatusOK, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusOK, + }, + }, + }, + "pipeline:metrics": { + healthy: false, + status: component.StatusPermanentError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusOK, + }, + "processor:batch": { + healthy: true, + status: component.StatusOK, + }, + "exporter:metrics/out": { + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + }, + }, + }, + }, + }, + }, + { + queryParams: "pipeline=metrics&verbose", + expectedStatusCode: http.StatusInternalServerError, + expectedComponentStatus: &componentStatusExpectation{ + healthy: false, + status: component.StatusPermanentError, + err: assert.AnError, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { healthy: true, status: component.StatusOK, }, @@ -1124,135 +3362,167 @@ func TestStatus(t *testing.T) { healthy: true, status: component.StatusOK, }, - "exporter:traces/out": { + "exporter:metrics/out": { healthy: false, - status: component.StatusRecoverableError, - err: assert.AnError, + status: component.StatusPermanentError, }, }, }, }, { step: func() { - server.aggregator.RecordStatus( - pipelines["traces"].ExporterID, - component.NewStatusEvent(component.StatusOK), + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, ) }, + queryParams: "verbose", + expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "pipeline:traces": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:traces/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:traces/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + "pipeline:metrics": { + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, + }, + }, + }, + { queryParams: "pipeline=traces&verbose", - expectedStatusCode: http.StatusOK, + expectedStatusCode: http.StatusServiceUnavailable, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusOK, + status: component.StatusStopping, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, - status: component.StatusOK, + status: component.StatusStopping, }, "processor:batch": { healthy: true, - status: component.StatusOK, + status: component.StatusStopping, }, "exporter:traces/out": { healthy: true, - status: component.StatusOK, + status: component.StatusStopping, }, }, }, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusStopping, - ) - }, - queryParams: "pipeline=traces&verbose", + queryParams: "pipeline=metrics&verbose", expectedStatusCode: http.StatusServiceUnavailable, + expectedComponentStatus: &componentStatusExpectation{ + healthy: true, + status: component.StatusStopping, + nestedStatus: map[string]*componentStatusExpectation{ + "receiver:metrics/in": { + healthy: true, + status: component.StatusStopping, + }, + "processor:batch": { + healthy: true, + status: component.StatusStopping, + }, + "exporter:metrics/out": { + healthy: true, + status: component.StatusStopping, + }, + }, + }, }, - }, - }, - { - name: "multiple pipelines - component health strategy - verbose", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: testutil.GetAvailableLocalAddress(t), - }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ - Enabled: true, - Path: "/status", - }, - }, - componentHealthSettings: &common.ComponentHealthSettings{ - IncludePermanent: true, - IncludeRecoverable: true, - RecoveryDuration: 2 * time.Millisecond, - }, - pipelines: testhelpers.NewPipelines("traces", "metrics"), - teststeps: []teststep{ { step: func() { - // traces will be StatusOK testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), - component.StatusOK, + traces.InstanceIDs(), + component.StatusStopped, ) testhelpers.SeedAggregator( server.aggregator, - pipelines["metrics"].InstanceIDs(), - component.StatusOK, - ) - // metrics and overall status will be PermanentError - server.aggregator.RecordStatus( - pipelines["metrics"].ExporterID, - component.NewPermanentErrorEvent(assert.AnError), + metrics.InstanceIDs(), + component.StatusStopped, ) }, queryParams: "verbose", - expectedStatusCode: http.StatusInternalServerError, + expectedStatusCode: http.StatusServiceUnavailable, expectedComponentStatus: &componentStatusExpectation{ - healthy: false, - status: component.StatusPermanentError, - err: assert.AnError, + healthy: true, + status: component.StatusStopped, nestedStatus: map[string]*componentStatusExpectation{ "pipeline:traces": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "processor:batch": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "exporter:traces/out": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, }, }, "pipeline:metrics": { - healthy: false, - status: component.StatusPermanentError, - err: assert.AnError, + healthy: true, + status: component.StatusStopped, nestedStatus: map[string]*componentStatusExpectation{ "receiver:metrics/in": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "processor:batch": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "exporter:metrics/out": { - healthy: false, - status: component.StatusPermanentError, - err: assert.AnError, + healthy: true, + status: component.StatusStopped, }, }, }, @@ -1261,46 +3531,44 @@ func TestStatus(t *testing.T) { }, { queryParams: "pipeline=traces&verbose", - expectedStatusCode: http.StatusOK, + expectedStatusCode: http.StatusServiceUnavailable, expectedComponentStatus: &componentStatusExpectation{ healthy: true, - status: component.StatusOK, + status: component.StatusStopped, nestedStatus: map[string]*componentStatusExpectation{ "receiver:traces/in": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "processor:batch": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "exporter:traces/out": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, }, }, }, { queryParams: "pipeline=metrics&verbose", - expectedStatusCode: http.StatusInternalServerError, + expectedStatusCode: http.StatusServiceUnavailable, expectedComponentStatus: &componentStatusExpectation{ - healthy: false, - status: component.StatusPermanentError, - err: assert.AnError, + healthy: true, + status: component.StatusStopped, nestedStatus: map[string]*componentStatusExpectation{ "receiver:metrics/in": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "processor:batch": { healthy: true, - status: component.StatusOK, + status: component.StatusStopped, }, "exporter:metrics/out": { - healthy: false, - status: component.StatusPermanentError, - err: assert.AnError, + healthy: true, + status: component.StatusStopped, }, }, }, @@ -1325,7 +3593,7 @@ func TestStatus(t *testing.T) { step: func() { testhelpers.SeedAggregator( server.aggregator, - pipelines["traces"].InstanceIDs(), + traces.InstanceIDs(), component.StatusOK, ) }, @@ -1355,7 +3623,6 @@ func TestStatus(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - pipelines = tc.pipelines server = NewServer( tc.settings, tc.componentHealthSettings, @@ -1400,7 +3667,7 @@ func TestStatus(t *testing.T) { st := &serializableStatus{} require.NoError(t, json.Unmarshal(body, st)) - + fmt.Println(string(body)) if strings.Contains(ts.queryParams, "verbose") { assertStatusDetailed(t, ts.expectedComponentStatus, st) continue @@ -1419,8 +3686,8 @@ func assertStatusDetailed( actual *serializableStatus, ) { assert.Equal(t, expected.healthy, actual.Healthy) - assert.Equal(t, expected.status, actual.Status()) - assert.Equal(t, expected.healthy, actual.Healthy) + assert.Equal(t, expected.status, actual.Status(), + "want: %s, got: %s", expected.status, actual.Status()) if expected.err != nil { assert.Equal(t, expected.err.Error(), actual.Error) } @@ -1436,8 +3703,8 @@ func assertNestedStatus( st, ok := actual[k] require.True(t, ok, "status for key: %s not found", k) assert.Equal(t, expectation.healthy, st.Healthy) - assert.Equal(t, expectation.status, st.Status()) - assert.Equal(t, expectation.healthy, st.Healthy) + assert.Equal(t, expectation.status, st.Status(), + "want: %s, got: %s", expectation.status, st.Status()) if expectation.err != nil { assert.Equal(t, expectation.err.Error(), st.Error) } diff --git a/extension/healthcheckextensionv2/internal/status/aggregation_test.go b/extension/healthcheckextensionv2/internal/status/aggregation_test.go new file mode 100644 index 0000000000000..f9c449aa1e370 --- /dev/null +++ b/extension/healthcheckextensionv2/internal/status/aggregation_test.go @@ -0,0 +1,124 @@ +package status + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" +) + +func TestAggregationFuncs(t *testing.T) { + aggRecoverable := newAggregationFunc(PriorityRecoverable) + aggPermanent := newAggregationFunc(PriorityPermanent) + + type statusExpectation struct { + priorityPermanent component.Status + priorityRecoverable component.Status + } + + for _, tc := range []struct { + name string + aggregateStatus *AggregateStatus + expectedStatus *statusExpectation + }{ + { + name: "FatalError takes precedence over all", + aggregateStatus: &AggregateStatus{ + ComponentStatusMap: map[string]*AggregateStatus{ + "c1": { + Event: component.NewStatusEvent(component.StatusFatalError), + }, + "c2": { + Event: component.NewStatusEvent(component.StatusStarting), + }, + "c3": { + Event: component.NewStatusEvent(component.StatusOK), + }, + "c4": { + Event: component.NewStatusEvent(component.StatusRecoverableError), + }, + "c5": { + Event: component.NewStatusEvent(component.StatusPermanentError), + }, + "c6": { + Event: component.NewStatusEvent(component.StatusStopping), + }, + "c7": { + Event: component.NewStatusEvent(component.StatusStopped), + }, + }, + }, + expectedStatus: &statusExpectation{ + priorityPermanent: component.StatusFatalError, + priorityRecoverable: component.StatusFatalError, + }, + }, + { + name: "Lifecycle: Starting takes precedence over non-fatal errors", + aggregateStatus: &AggregateStatus{ + ComponentStatusMap: map[string]*AggregateStatus{ + "c1": { + Event: component.NewStatusEvent(component.StatusStarting), + }, + "c2": { + Event: component.NewStatusEvent(component.StatusRecoverableError), + }, + "c3": { + Event: component.NewStatusEvent(component.StatusPermanentError), + }, + }, + }, + expectedStatus: &statusExpectation{ + priorityPermanent: component.StatusStarting, + priorityRecoverable: component.StatusStarting, + }, + }, + { + name: "Lifecycle: Stopping takes precedence over non-fatal errors", + aggregateStatus: &AggregateStatus{ + ComponentStatusMap: map[string]*AggregateStatus{ + "c1": { + Event: component.NewStatusEvent(component.StatusStopping), + }, + "c2": { + Event: component.NewStatusEvent(component.StatusRecoverableError), + }, + "c3": { + Event: component.NewStatusEvent(component.StatusPermanentError), + }, + }, + }, + expectedStatus: &statusExpectation{ + priorityPermanent: component.StatusStopping, + priorityRecoverable: component.StatusStopping, + }, + }, + { + name: "Prioritized error takes priority over OK", + aggregateStatus: &AggregateStatus{ + ComponentStatusMap: map[string]*AggregateStatus{ + "c1": { + Event: component.NewStatusEvent(component.StatusOK), + }, + "c2": { + Event: component.NewStatusEvent(component.StatusRecoverableError), + }, + "c3": { + Event: component.NewStatusEvent(component.StatusPermanentError), + }, + }, + }, + expectedStatus: &statusExpectation{ + priorityPermanent: component.StatusPermanentError, + priorityRecoverable: component.StatusRecoverableError, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expectedStatus.priorityPermanent, + aggPermanent(tc.aggregateStatus).Status()) + assert.Equal(t, tc.expectedStatus.priorityRecoverable, + aggRecoverable(tc.aggregateStatus).Status()) + }) + } +} From 99afda4b1a18992565e5be4d7287c66b6b94235f Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Wed, 21 Feb 2024 17:50:21 -0800 Subject: [PATCH 12/26] gRPC support for component health options --- extension/healthcheckextensionv2/extension.go | 2 +- .../internal/grpc/grpc.go | 62 +- .../internal/grpc/grpc_test.go | 1805 ++++++++++++++--- .../internal/grpc/server.go | 32 +- 4 files changed, 1543 insertions(+), 358 deletions(-) diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index 29e4aba22267f..d05365b68e007 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -55,8 +55,8 @@ func newExtension( if config.GRPCSettings != nil { srvGRPC := grpc.NewServer( config.GRPCSettings, + config.ComponentHealthSettings, set.TelemetrySettings, - config.RecoveryDuration, aggregator, ) comps = append(comps, srvGRPC) diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index 8fa87b8e83bbc..f6963552da746 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -15,6 +15,17 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) +var statusToServingStatusMap = map[component.Status]healthpb.HealthCheckResponse_ServingStatus{ + component.StatusNone: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStarting: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusOK: healthpb.HealthCheckResponse_SERVING, + component.StatusRecoverableError: healthpb.HealthCheckResponse_SERVING, + component.StatusPermanentError: healthpb.HealthCheckResponse_SERVING, + component.StatusFatalError: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStopping: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStopped: healthpb.HealthCheckResponse_NOT_SERVING, +} + func (s *Server) Check( _ context.Context, req *healthpb.HealthCheckRequest, @@ -34,9 +45,8 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ defer s.aggregator.Unsubscribe(sub) var lastServingStatus healthpb.HealthCheckResponse_ServingStatus = -1 - - failureTicker := time.NewTicker(s.recoveryDuration) - failureTicker.Stop() + var failureTimer *time.Timer + failureCh := make(chan struct{}) for { select { @@ -49,15 +59,27 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ switch { case st == nil: sst = healthpb.HealthCheckResponse_SERVICE_UNKNOWN - case st.Status() == component.StatusRecoverableError: - failureTicker.Reset(s.recoveryDuration) + case s.componentHealthSettings.IncludeRecoverable && + s.componentHealthSettings.RecoveryDuration > 0 && + st.Status() == component.StatusRecoverableError: + if failureTimer == nil { + failureTimer = time.AfterFunc( + s.componentHealthSettings.RecoveryDuration, + func() { failureCh <- struct{}{} }, + ) + } sst = lastServingStatus if lastServingStatus == -1 { sst = healthpb.HealthCheckResponse_SERVING } default: - failureTicker.Stop() - sst = statusToServingStatusMap[st.Status()] + if failureTimer != nil { + if !failureTimer.Stop() { + <-failureTimer.C + } + failureTimer = nil + } + sst = s.toServingStatus(st.Event) } if lastServingStatus == sst { @@ -70,8 +92,9 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ if err != nil { return grpcstatus.Error(codes.Canceled, "Stream has ended.") } - case <-failureTicker.C: - failureTicker.Stop() + case <-failureCh: + failureTimer.Stop() + failureTimer = nil if lastServingStatus == healthpb.HealthCheckResponse_NOT_SERVING { continue } @@ -90,23 +113,18 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ } } -var statusToServingStatusMap = map[component.Status]healthpb.HealthCheckResponse_ServingStatus{ - component.StatusNone: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusStarting: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusOK: healthpb.HealthCheckResponse_SERVING, - component.StatusRecoverableError: healthpb.HealthCheckResponse_SERVING, - component.StatusPermanentError: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusFatalError: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusStopping: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusStopped: healthpb.HealthCheckResponse_NOT_SERVING, -} - func (s *Server) toServingStatus( ev status.Event, ) healthpb.HealthCheckResponse_ServingStatus { - if ev.Status() == component.StatusRecoverableError && - time.Now().After(ev.Timestamp().Add(s.recoveryDuration)) { + if s.componentHealthSettings.IncludeRecoverable && + ev.Status() == component.StatusRecoverableError && + time.Now().After(ev.Timestamp().Add(s.componentHealthSettings.RecoveryDuration)) { + return healthpb.HealthCheckResponse_NOT_SERVING + } + + if s.componentHealthSettings.IncludePermanent && ev.Status() == component.StatusPermanentError { return healthpb.HealthCheckResponse_NOT_SERVING } + return statusToServingStatusMap[ev.Status()] } diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go index 43c013a9e0eee..a1a0151b26ac2 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go @@ -5,6 +5,7 @@ package grpc import ( "context" + "sync" "testing" "time" @@ -20,6 +21,7 @@ import ( healthpb "google.golang.org/grpc/health/grpc_health_v1" grpcstatus "google.golang.org/grpc/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" @@ -35,215 +37,714 @@ func TestCheck(t *testing.T) { }, }, } - server := NewServer( - settings, - componenttest.NewNopTelemetrySettings(), - 10*time.Millisecond, - status.NewAggregator(status.PriorityPermanent), - ) + var server *Server traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") - require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) - t.Cleanup(func() { require.NoError(t, server.Shutdown(context.Background())) }) - - cc, err := grpc.Dial( - addr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock(), - ) - require.NoError(t, err) - defer func() { - assert.NoError(t, cc.Close()) - }() - - client := healthpb.NewHealthClient(cc) - - // ts is a sequence of test steps - for _, ts := range []struct { + type teststep struct { step func() eventually bool service string expectedStatus healthpb.HealthCheckResponse_ServingStatus expectedErr error + } + + tests := []struct { + name string + settings *Settings + componentHealthSettings *common.ComponentHealthSettings + teststeps []teststep }{ { - service: "", - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - service: traces.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), - }, - { - service: metrics.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusStarting, - ) - testhelpers.SeedAggregator( - server.aggregator, - metrics.InstanceIDs(), - component.StatusStarting, - ) + name: "exclude recoverable and permanent errors", + settings: settings, + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + service: metrics.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // errors will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: "", - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, { - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusOK, - ) - testhelpers.SeedAggregator( - server.aggregator, - metrics.InstanceIDs(), - component.StatusOK, - ) + name: "include recoverable and exclude permanent errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: false, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, - service: "", - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - step: func() { - // metrics and overall status will be NOT_SERVING - server.aggregator.RecordStatus( - metrics.ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), - ) + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + service: metrics.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // permament error will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: "", - eventually: true, - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, { - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - step: func() { - // metrics and overall status will recover and resume SERVING - server.aggregator.RecordStatus( - metrics.ExporterID, - component.NewStatusEvent(component.StatusOK), - ) + name: "include permanent and exclude recoverable errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, }, - service: "", - eventually: true, - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusStopping, - ) - testhelpers.SeedAggregator( - server.aggregator, - metrics.InstanceIDs(), - component.StatusStopping, - ) + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + service: metrics.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // recoverable will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // permament error included + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: "", - eventually: true, - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, { - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusStopped, - ) - testhelpers.SeedAggregator( - server.aggregator, - metrics.InstanceIDs(), - component.StatusStopped, - ) + name: "include permanent and recoverable errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, + }, + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + service: metrics.PipelineID.String(), + expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: "", + eventually: true, + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: "", - eventually: true, - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, - } { - if ts.step != nil { - ts.step() - } + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + server = NewServer( + settings, + tc.componentHealthSettings, + componenttest.NewNopTelemetrySettings(), + status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthSettings)), + ) + require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { require.NoError(t, server.Shutdown(context.Background())) }) + + cc, err := grpc.Dial( + addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), + ) + require.NoError(t, err) + defer func() { + assert.NoError(t, cc.Close()) + }() + + client := healthpb.NewHealthClient(cc) + + for _, ts := range tc.teststeps { + if ts.step != nil { + ts.step() + } + + if ts.eventually { + assert.Eventually(t, func() bool { + resp, err := client.Check( + context.Background(), + &healthpb.HealthCheckRequest{Service: ts.service}, + ) + require.NoError(t, err) + return ts.expectedStatus == resp.Status + }, time.Second, 10*time.Millisecond) + continue + } - if ts.eventually { - assert.Eventually(t, func() bool { resp, err := client.Check( context.Background(), &healthpb.HealthCheckRequest{Service: ts.service}, ) - require.NoError(t, err) - return ts.expectedStatus == resp.Status - }, time.Second, 10*time.Millisecond) - continue - } - - resp, err := client.Check( - context.Background(), - &healthpb.HealthCheckRequest{Service: ts.service}, - ) - require.Equal(t, ts.expectedErr, err) - if ts.expectedErr != nil { - continue - } - assert.Equal(t, ts.expectedStatus, resp.Status) + require.Equal(t, ts.expectedErr, err) + if ts.expectedErr != nil { + continue + } + assert.Equal(t, ts.expectedStatus, resp.Status) + } + }) } + } func TestWatch(t *testing.T) { - var err error addr := testutil.GetAvailableLocalAddress(t) settings := &Settings{ GRPCServerSettings: configgrpc.GRPCServerSettings{ @@ -253,186 +754,848 @@ func TestWatch(t *testing.T) { }, }, } - server := NewServer( - settings, - componenttest.NewNopTelemetrySettings(), - 10*time.Millisecond, - status.NewAggregator(status.PriorityPermanent), - ) + var server *Server traces := testhelpers.NewPipelineMetadata("traces") metrics := testhelpers.NewPipelineMetadata("metrics") - require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) - t.Cleanup(func() { require.NoError(t, server.Shutdown(context.Background())) }) - - cc, err := grpc.Dial( - addr, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock(), - ) - require.NoError(t, err) - defer func() { - assert.NoError(t, cc.Close()) - }() - - client := healthpb.NewHealthClient(cc) - watchers := make(map[string]healthpb.Health_WatchClient) + // statusUnchanged is a sentinel value to signal that a step does not result + // in a status change. This is important, because checking for a status + // change is blocking. + var statusUnchanged healthpb.HealthCheckResponse_ServingStatus = -1 - // ts is a sequence of test steps - for _, ts := range []struct { + type teststep struct { step func() service string expectedStatus healthpb.HealthCheckResponse_ServingStatus + } + + tests := []struct { + name string + settings *Settings + componentHealthSettings *common.ComponentHealthSettings + teststeps []teststep }{ { - service: "", - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, - }, - { - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusStarting, - ) + name: "exclude recoverable and permanent errors", + settings: settings, + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // errors will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: statusUnchanged, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: statusUnchanged, + }, + { + step: func() { + // This will be the last status change for traces (stopping changes to NOT_SERVING) + // Stopped results in the same serving status, and repeat statuses are not streamed. + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // This will be the last status change for metrics (stopping changes to NOT_SERVING) + // Stopped results in the same serving status, and repeat statuses are not streamed. + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - metrics.InstanceIDs(), - component.StatusStarting, - ) + name: "include recoverable and exclude permanent errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: false, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusOK, - ) + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // permanent error will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: statusUnchanged, + }, }, - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVING, }, { - step: func() { - testhelpers.SeedAggregator( - server.aggregator, - metrics.InstanceIDs(), - component.StatusOK, - ) + name: "exclude permanent errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: false, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, }, - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - service: "", - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - step: func() { - // metrics and overall status will be NOT_SERVING - server.aggregator.RecordStatus( - metrics.ExporterID, - component.NewRecoverableErrorEvent(assert.AnError), - ) + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // permanent error will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: statusUnchanged, + }, }, - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, { - service: "", - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, - }, - { - step: func() { - // metrics and overall status will recover and resume SERVING - server.aggregator.RecordStatus( - metrics.ExporterID, - component.NewStatusEvent(component.StatusOK), - ) + name: "include recoverable 0s recovery duration", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: false, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, + }, + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // This will be the last status change for traces (stopping changes to NOT_SERVING) + // Stopped results in the same serving status, and repeat statuses are not streamed. + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // This will be the last status change for metrics (stopping changes to NOT_SERVING) + // Stopped results in the same serving status, and repeat statuses are not streamed. + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_SERVING, - }, - { - service: "", - expectedStatus: healthpb.HealthCheckResponse_SERVING, }, { - step: func() { - // This will be the last status change for traces (stopping changes to NOT_SERVING) - // Stopped results in the same serving status, and repeat statuses are not streamed. - testhelpers.SeedAggregator( - server.aggregator, - traces.InstanceIDs(), - component.StatusStopping, - ) + name: "include permanent and exclude recoverable errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + IncludeRecoverable: false, + RecoveryDuration: 2 * time.Millisecond, + }, + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // recoverable will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: statusUnchanged, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // This will be the last status change for traces (stopping changes to NOT_SERVING) + // Stopped results in the same serving status, and repeat statuses are not streamed. + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: traces.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, { - service: "", - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + name: "exclude recoverable errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + IncludeRecoverable: false, + RecoveryDuration: 2 * time.Millisecond, + }, + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // recoverable will be ignored + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: statusUnchanged, + }, + }, }, { - step: func() { - // This will be the last status change for metrics (stopping changes to NOT_SERVING) - // Stopped results in the same serving status, and repeat statuses are not streamed. - testhelpers.SeedAggregator( - server.aggregator, - metrics.InstanceIDs(), - component.StatusStopping, - ) + name: "include recoverable and permanent errors", + settings: settings, + componentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 2 * time.Millisecond, + }, + teststeps: []teststep{ + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVICE_UNKNOWN, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + service: traces.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + step: func() { + // metrics and overall status will recover and resume SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_SERVING, + }, + { + step: func() { + // metrics and overall status will be NOT_SERVING + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + service: metrics.PipelineID.String(), + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, + { + service: "", + expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, + }, }, - service: metrics.PipelineID.String(), - expectedStatus: healthpb.HealthCheckResponse_NOT_SERVING, }, - } { + } - if ts.step != nil { - ts.step() - } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + server = NewServer( + settings, + tc.componentHealthSettings, + componenttest.NewNopTelemetrySettings(), + status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthSettings)), + ) + require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) + t.Cleanup(func() { require.NoError(t, server.Shutdown(context.Background())) }) - watcher, ok := watchers[ts.service] - if !ok { - watcher, err = client.Watch( - context.Background(), - &healthpb.HealthCheckRequest{Service: ts.service}, + cc, err := grpc.Dial( + addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), ) require.NoError(t, err) - watchers[ts.service] = watcher - } + defer func() { + assert.NoError(t, cc.Close()) + }() - var resp *healthpb.HealthCheckResponse - // Note Recv blocks until there is a new item in the stream - resp, err = watcher.Recv() - require.NoError(t, err) - assert.Equal(t, ts.expectedStatus, resp.Status) - } + client := healthpb.NewHealthClient(cc) + watchers := make(map[string]healthpb.Health_WatchClient) + + for _, ts := range tc.teststeps { + if ts.step != nil { + ts.step() + } + + if statusUnchanged == ts.expectedStatus { + continue + } + + watcher, ok := watchers[ts.service] + if !ok { + watcher, err = client.Watch( + context.Background(), + &healthpb.HealthCheckRequest{Service: ts.service}, + ) + require.NoError(t, err) + watchers[ts.service] = watcher + } + + var resp *healthpb.HealthCheckResponse + // Note Recv blocks until there is a new item in the stream + resp, err = watcher.Recv() + require.NoError(t, err) + assert.Equal(t, ts.expectedStatus, resp.Status) + } + + wg := sync.WaitGroup{} + wg.Add(len(watchers)) - // closing the aggregator will gracefully terminate streams of status events - server.aggregator.Close() + for svc, watcher := range watchers { + svc := svc + watcher := watcher + go func() { + resp, err := watcher.Recv() + // Ensure there are not any unread messages + assert.Nil(t, resp, "%s: had unread messages", svc) + // Ensure watchers receive the cancelation when streams are closed by the server + assert.Equal(t, grpcstatus.Error(codes.Canceled, "Server shutting down."), err) + wg.Done() + }() + } - // Ensure watchers receive the cancelation when streams are closed by the server - for _, watcher := range watchers { - _, err = watcher.Recv() - assert.Equal(t, grpcstatus.Error(codes.Canceled, "Server shutting down."), err) + // closing the aggregator will gracefully terminate streams of status events + server.aggregator.Close() + wg.Wait() + }) } } diff --git a/extension/healthcheckextensionv2/internal/grpc/server.go b/extension/healthcheckextensionv2/internal/grpc/server.go index 836709baf0687..8a2a5f37dec70 100644 --- a/extension/healthcheckextensionv2/internal/grpc/server.go +++ b/extension/healthcheckextensionv2/internal/grpc/server.go @@ -6,41 +6,45 @@ package grpc // import "github.com/open-telemetry/opentelemetry-collector-contri import ( "context" "errors" - "time" "go.opentelemetry.io/collector/component" "google.golang.org/grpc" healthpb "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/reflection" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) type Server struct { healthpb.UnimplementedHealthServer - serverGRPC *grpc.Server - aggregator *status.Aggregator - settings *Settings - telemetry component.TelemetrySettings - recoveryDuration time.Duration - doneCh chan struct{} + serverGRPC *grpc.Server + aggregator *status.Aggregator + settings *Settings + componentHealthSettings *common.ComponentHealthSettings + telemetry component.TelemetrySettings + doneCh chan struct{} } var _ component.Component = (*Server)(nil) func NewServer( settings *Settings, + componentHealthSettings *common.ComponentHealthSettings, telemetry component.TelemetrySettings, - failureDuration time.Duration, aggregator *status.Aggregator, ) *Server { - return &Server{ - settings: settings, - telemetry: telemetry, - aggregator: aggregator, - recoveryDuration: failureDuration, - doneCh: make(chan struct{}), + srv := &Server{ + settings: settings, + componentHealthSettings: componentHealthSettings, + telemetry: telemetry, + aggregator: aggregator, + doneCh: make(chan struct{}), } + if srv.componentHealthSettings == nil { + srv.componentHealthSettings = &common.ComponentHealthSettings{} + } + return srv } // Start implements the component.Component interface. From 4075e13e926c82aff9a625d728059baf822627c3 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 29 Feb 2024 15:53:30 -0800 Subject: [PATCH 13/26] Fusion Support config and responses from current healthcheck extension. --- extension/healthcheckextensionv2/config.go | 68 +++- .../healthcheckextensionv2/config_test.go | 199 +++++++++--- extension/healthcheckextensionv2/extension.go | 13 +- .../healthcheckextensionv2/extension_test.go | 3 +- extension/healthcheckextensionv2/factory.go | 30 +- .../healthcheckextensionv2/factory_test.go | 30 +- .../internal/grpc/config.go | 1 - .../internal/grpc/server.go | 17 +- .../internal/http/config.go | 46 +++ .../internal/http/handlers.go | 9 +- .../internal/http/responders.go | 88 +++++- .../internal/http/server.go | 54 ++-- .../internal/http/server_test.go | 296 +++++++++++++++++- .../internal/status/aggregation_test.go | 3 + .../testdata/config.yaml | 48 +++ 15 files changed, 766 insertions(+), 139 deletions(-) create mode 100644 extension/healthcheckextensionv2/testdata/config.yaml diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go index 3e4ca9d956c02..b9ccbfc9cf2d3 100644 --- a/extension/healthcheckextensionv2/config.go +++ b/extension/healthcheckextensionv2/config.go @@ -5,39 +5,95 @@ package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetr import ( "errors" - "time" + "strings" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" ) +const ( + httpSettingsKey = "http" + grpcSettingsKey = "grpc" +) + var ( errMissingProtocol = errors.New("healthcheck extension: must be configured for HTTP or gRPC") errGRPCEndpointRequired = errors.New("healthcheck extension: grpc endpoint required") errHTTPEndpointRequired = errors.New("healthcheck extension: http endpoint required") + errInvalidPath = errors.New("healthcheck extension: path must start with /") ) // Config has the configuration for the extension enabling the health check // extension, used to report the health status of the service. type Config struct { + // LegacySettings contains the settings for the existing healthcheck extension, which we plan + // to deprecate and remove. + http.LegacySettings `mapstructure:",squash"` + + // GRPCSettings are v2 settings for the grpc healthcheck service. + GRPCSettings *grpc.Settings `mapstructure:"grpc"` + + // HTTPSettings are v2 settings for the http healthcheck service. + HTTPSettings *http.Settings `mapstructure:"http"` + + // ComponentHealthSettings are v2 settings shared between http and grpc services ComponentHealthSettings *common.ComponentHealthSettings `mapstructure:"component_health"` - RecoveryDuration time.Duration `mapstructure:"recovery_duration"` - GRPCSettings *grpc.Settings `mapstructure:"grpc"` - HTTPSettings *http.Settings `mapstructure:"http"` } +var _ component.Config = (*Config)(nil) + +// Validate checks if the extension configuration is valid func (c *Config) Validate() error { + if !c.UseV2Settings { + if c.LegacySettings.Endpoint == "" { + return errHTTPEndpointRequired + } + if !strings.HasPrefix(c.LegacySettings.Path, "/") { + return errInvalidPath + } + return nil + } + if c.GRPCSettings == nil && c.HTTPSettings == nil { return errMissingProtocol } + if c.HTTPSettings != nil { + if c.HTTPSettings.Endpoint == "" { + return errHTTPEndpointRequired + } + if c.HTTPSettings.Status.Enabled && !strings.HasPrefix(c.HTTPSettings.Status.Path, "/") { + return errInvalidPath + } + if c.HTTPSettings.Config.Enabled && !strings.HasPrefix(c.HTTPSettings.Config.Path, "/") { + return errInvalidPath + } + } + if c.GRPCSettings != nil && c.GRPCSettings.NetAddr.Endpoint == "" { return errGRPCEndpointRequired } - if c.HTTPSettings != nil && c.HTTPSettings.Endpoint == "" { - return errHTTPEndpointRequired + return nil +} + +// Unmarshal a confmap.Conf into the config struct. +func (c *Config) Unmarshal(conf *confmap.Conf) error { + err := conf.Unmarshal(c) + if err != nil { + return err + } + + if !conf.IsSet(httpSettingsKey) { + c.HTTPSettings = nil + } + + if !conf.IsSet(grpcSettingsKey) { + c.GRPCSettings = nil } return nil diff --git a/extension/healthcheckextensionv2/config_test.go b/extension/healthcheckextensionv2/config_test.go index 45017c3ad79aa..914f61cb6673e 100644 --- a/extension/healthcheckextensionv2/config_test.go +++ b/extension/healthcheckextensionv2/config_test.go @@ -4,85 +4,188 @@ package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" import ( + "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configgrpc" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/confignet" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/confmap/confmaptest" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/metadata" ) -func TestConfig(t *testing.T) { - grpcSettings := &grpc.Settings{ - GRPCServerSettings: configgrpc.GRPCServerSettings{ - NetAddr: confignet.NetAddr{ - Endpoint: "127.0.0.1:5000", - Transport: "tcp", - }, - }, - } - httpSettings := &http.Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: "127.0.0.1:5001", - }, - } +func TestLoadConfig(t *testing.T) { + t.Parallel() - for _, tc := range []struct { - name string - config *Config - err error + tests := []struct { + id component.ID + expected component.Config + expectedErr error }{ { - name: "Valid GRPC Settings Only", - config: &Config{ - GRPCSettings: grpcSettings, + id: component.NewID(metadata.Type), + expected: &Config{ + LegacySettings: http.LegacySettings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + Path: "/", + }, }, }, { - name: "Invalid GRPC Settings Only", - config: &Config{ - GRPCSettings: &grpc.Settings{}, + id: component.NewIDWithName(metadata.Type, "legacysettings"), + expected: &Config{ + LegacySettings: http.LegacySettings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "localhost:13", + TLSSetting: &configtls.TLSServerSetting{ + TLSSetting: configtls.TLSSetting{ + CAFile: "/path/to/ca", + CertFile: "/path/to/cert", + KeyFile: "/path/to/key", + }, + }, + }, + CheckCollectorPipeline: &http.CheckCollectorPipelineSettings{ + Enabled: false, + Interval: "5m", + ExporterFailureThreshold: 5, + }, + Path: "/", + ResponseBody: nil, + }, }, - err: errGRPCEndpointRequired, }, { - name: "Valid HTTP Settings Only", - config: &Config{ - HTTPSettings: httpSettings, - }, + id: component.NewIDWithName(metadata.Type, "missingendpoint"), + expectedErr: errHTTPEndpointRequired, + }, + { + id: component.NewIDWithName(metadata.Type, "invalidpath"), + expectedErr: errInvalidPath, }, { - name: "Invalid HTTP Settings Only", - config: &Config{ - HTTPSettings: &http.Settings{}, + id: component.NewIDWithName(metadata.Type, "v2all"), + expected: &Config{ + LegacySettings: http.LegacySettings{ + UseV2Settings: true, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + Path: "/", + }, + HTTPSettings: &http.Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + Status: http.PathSettings{ + Enabled: true, + Path: "/status", + }, + Config: http.PathSettings{ + Enabled: false, + Path: "/config", + }, + }, + GRPCSettings: &grpc.Settings{ + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCEndpoint, + Transport: "tcp", + }, + }, + }, + ComponentHealthSettings: &common.ComponentHealthSettings{ + IncludePermanent: true, + IncludeRecoverable: true, + RecoveryDuration: 5 * time.Minute, + }, }, - err: errHTTPEndpointRequired, }, { - name: "GRPC and HTTP Settings", - config: &Config{ - GRPCSettings: grpcSettings, - HTTPSettings: httpSettings, + id: component.NewIDWithName(metadata.Type, "v2httpcustomized"), + expected: &Config{ + LegacySettings: http.LegacySettings{ + UseV2Settings: true, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + Path: "/", + }, + HTTPSettings: &http.Settings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: "localhost:13", + }, + Status: http.PathSettings{ + Enabled: true, + Path: "/health", + }, + Config: http.PathSettings{ + Enabled: true, + Path: "/conf", + }, + }, }, }, { - name: "GRPC and HTTP Settings both invalid", - config: &Config{ - GRPCSettings: &grpc.Settings{}, - HTTPSettings: &http.Settings{}, + id: component.NewIDWithName(metadata.Type, "v2httpmissingendpoint"), + expectedErr: errHTTPEndpointRequired, + }, + { + id: component.NewIDWithName(metadata.Type, "v2grpccustomized"), + expected: &Config{ + LegacySettings: http.LegacySettings{ + UseV2Settings: true, + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + Path: "/", + }, + GRPCSettings: &grpc.Settings{ + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: "localhost:13", + Transport: "tcp", + }, + }, + }, }, - err: errGRPCEndpointRequired, }, { - name: "Neither GRPC nor HTTP Settings", - config: &Config{}, - err: errMissingProtocol, + id: component.NewIDWithName(metadata.Type, "v2grpcmissingendpoint"), + expectedErr: errGRPCEndpointRequired, }, - } { - err := tc.config.Validate() - assert.Equal(t, tc.err, err) + { + id: component.NewIDWithName(metadata.Type, "v2noprotocols"), + expectedErr: errMissingProtocol, + }, + } + + for _, tt := range tests { + t.Run(tt.id.String(), func(t *testing.T) { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + sub, err := cm.Sub(tt.id.String()) + require.NoError(t, err) + require.NoError(t, component.UnmarshalConfig(sub, cfg)) + if tt.expectedErr != nil { + assert.ErrorIs(t, component.ValidateConfig(cfg), tt.expectedErr) + return + } + assert.NoError(t, component.ValidateConfig(cfg)) + assert.Equal(t, tt.expected, cfg) + }) } } diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index d05365b68e007..a9842402d007e 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -52,24 +52,25 @@ func newExtension( aggregator := status.NewAggregator(errPriority) - if config.GRPCSettings != nil { - srvGRPC := grpc.NewServer( + if config.UseV2Settings && config.GRPCSettings != nil { + grpcServer := grpc.NewServer( config.GRPCSettings, config.ComponentHealthSettings, set.TelemetrySettings, aggregator, ) - comps = append(comps, srvGRPC) + comps = append(comps, grpcServer) } - if config.HTTPSettings != nil { - srvHTTP := http.NewServer( + if !config.UseV2Settings || config.UseV2Settings && config.HTTPSettings != nil { + httpServer := http.NewServer( config.HTTPSettings, + config.LegacySettings, config.ComponentHealthSettings, set.TelemetrySettings, aggregator, ) - comps = append(comps, srvHTTP) + comps = append(comps, httpServer) } hc := &healthCheckExtension{ diff --git a/extension/healthcheckextensionv2/extension_test.go b/extension/healthcheckextensionv2/extension_test.go index 5e9e0b43bf601..0d8c04b9b9094 100644 --- a/extension/healthcheckextensionv2/extension_test.go +++ b/extension/healthcheckextensionv2/extension_test.go @@ -28,7 +28,7 @@ import ( func TestComponentStatus(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.HTTPSettings.Endpoint = testutil.GetAvailableLocalAddress(t) - + cfg.UseV2Settings = true ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) // Status before Start will be StatusNone @@ -103,6 +103,7 @@ func TestNotifyConfig(t *testing.T) { endpoint := testutil.GetAvailableLocalAddress(t) cfg := createDefaultConfig().(*Config) + cfg.UseV2Settings = true cfg.HTTPSettings.Endpoint = endpoint cfg.HTTPSettings.Config.Enabled = true cfg.HTTPSettings.Config.Path = "/config" diff --git a/extension/healthcheckextensionv2/factory.go b/extension/healthcheckextensionv2/factory.go index 1b787acc88022..707360e1d6393 100644 --- a/extension/healthcheckextensionv2/factory.go +++ b/extension/healthcheckextensionv2/factory.go @@ -7,12 +7,14 @@ package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetr import ( "context" - "time" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/confignet" "go.opentelemetry.io/collector/extension" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/metadata" ) @@ -20,7 +22,8 @@ import ( const ( // Use 0.0.0.0 to make the health check endpoint accessible // in container orchestration environments like Kubernetes. - defaultEndpoint = "0.0.0.0:13133" + defaultGRPCEndpoint = "0.0.0.0:13132" + defaultHTTPEndpoint = "0.0.0.0:13133" ) // NewFactory creates a factory for HealthCheck extension. @@ -35,14 +38,31 @@ func NewFactory() extension.Factory { func createDefaultConfig() component.Config { return &Config{ - RecoveryDuration: time.Minute, + LegacySettings: http.LegacySettings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + Path: "/", + }, HTTPSettings: &http.Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultEndpoint, + Endpoint: defaultHTTPEndpoint, }, Status: http.PathSettings{ Enabled: true, - Path: "/", + Path: "/status", + }, + Config: http.PathSettings{ + Enabled: false, + Path: "/config", + }, + }, + GRPCSettings: &grpc.Settings{ + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCEndpoint, + Transport: "tcp", + }, }, }, } diff --git a/extension/healthcheckextensionv2/factory_test.go b/extension/healthcheckextensionv2/factory_test.go index 08bb9c6f1049e..3ada7e114dbf5 100644 --- a/extension/healthcheckextensionv2/factory_test.go +++ b/extension/healthcheckextensionv2/factory_test.go @@ -6,14 +6,16 @@ package healthcheckextensionv2 import ( "context" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configgrpc" "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/confignet" "go.opentelemetry.io/collector/extension/extensiontest" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" ) @@ -21,16 +23,32 @@ import ( func TestCreateDefaultConfig(t *testing.T) { cfg := createDefaultConfig() assert.Equal(t, &Config{ - RecoveryDuration: time.Minute, + LegacySettings: http.LegacySettings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: defaultHTTPEndpoint, + }, + Path: "/", + }, HTTPSettings: &http.Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultEndpoint, + Endpoint: defaultHTTPEndpoint, }, Status: http.PathSettings{ Enabled: true, - Path: "/", + Path: "/status", + }, + Config: http.PathSettings{ + Enabled: false, + Path: "/config", + }, + }, + GRPCSettings: &grpc.Settings{ + GRPCServerSettings: configgrpc.GRPCServerSettings{ + NetAddr: confignet.NetAddr{ + Endpoint: defaultGRPCEndpoint, + Transport: "tcp", + }, }, - Config: http.PathSettings{}, }, }, cfg) @@ -42,7 +60,7 @@ func TestCreateDefaultConfig(t *testing.T) { func TestCreateExtension(t *testing.T) { cfg := createDefaultConfig().(*Config) - cfg.HTTPSettings.Endpoint = testutil.GetAvailableLocalAddress(t) + cfg.Endpoint = testutil.GetAvailableLocalAddress(t) ext, err := createExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) require.NoError(t, err) diff --git a/extension/healthcheckextensionv2/internal/grpc/config.go b/extension/healthcheckextensionv2/internal/grpc/config.go index aecc17cf28167..fef9837498331 100644 --- a/extension/healthcheckextensionv2/internal/grpc/config.go +++ b/extension/healthcheckextensionv2/internal/grpc/config.go @@ -7,5 +7,4 @@ import "go.opentelemetry.io/collector/config/configgrpc" type Settings struct { configgrpc.GRPCServerSettings `mapstructure:",squash"` - Debug bool `mapstructure:"debug"` } diff --git a/extension/healthcheckextensionv2/internal/grpc/server.go b/extension/healthcheckextensionv2/internal/grpc/server.go index 8a2a5f37dec70..3af32d714d64a 100644 --- a/extension/healthcheckextensionv2/internal/grpc/server.go +++ b/extension/healthcheckextensionv2/internal/grpc/server.go @@ -10,7 +10,6 @@ import ( "go.opentelemetry.io/collector/component" "google.golang.org/grpc" healthpb "google.golang.org/grpc/health/grpc_health_v1" - "google.golang.org/grpc/reflection" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" @@ -18,7 +17,7 @@ import ( type Server struct { healthpb.UnimplementedHealthServer - serverGRPC *grpc.Server + grpcServer *grpc.Server aggregator *status.Aggregator settings *Settings componentHealthSettings *common.ComponentHealthSettings @@ -50,22 +49,18 @@ func NewServer( // Start implements the component.Component interface. func (s *Server) Start(_ context.Context, host component.Host) error { var err error - s.serverGRPC, err = s.settings.ToServer(host, s.telemetry) + s.grpcServer, err = s.settings.ToServer(host, s.telemetry) if err != nil { return err } - healthpb.RegisterHealthServer(s.serverGRPC, s) - if s.settings.Debug { - reflection.Register(s.serverGRPC) - } - + healthpb.RegisterHealthServer(s.grpcServer, s) ln, err := s.settings.ToListener() go func() { defer close(s.doneCh) - if err = s.serverGRPC.Serve(ln); err != nil && !errors.Is(err, grpc.ErrServerStopped) { + if err = s.grpcServer.Serve(ln); err != nil && !errors.Is(err, grpc.ErrServerStopped) { s.telemetry.ReportStatus(component.NewPermanentErrorEvent(err)) } }() @@ -75,10 +70,10 @@ func (s *Server) Start(_ context.Context, host component.Host) error { // Shutdown implements the component.Component interface. func (s *Server) Shutdown(context.Context) error { - if s.serverGRPC == nil { + if s.grpcServer == nil { return nil } - s.serverGRPC.GracefulStop() + s.grpcServer.GracefulStop() <-s.doneCh return nil } diff --git a/extension/healthcheckextensionv2/internal/http/config.go b/extension/healthcheckextensionv2/internal/http/config.go index 140e200b5c57b..514443e56d714 100644 --- a/extension/healthcheckextensionv2/internal/http/config.go +++ b/extension/healthcheckextensionv2/internal/http/config.go @@ -5,6 +5,7 @@ package http // import "github.com/open-telemetry/opentelemetry-collector-contri import "go.opentelemetry.io/collector/config/confighttp" +// Settings contains the v2 settings for the http healthcheck service type Settings struct { confighttp.HTTPServerSettings `mapstructure:",squash"` @@ -16,3 +17,48 @@ type PathSettings struct { Enabled bool `mapstructure:"enabled"` Path string `mapstructure:"path"` } + +// LegacySettings contain the settings for the original healthcheck extension. We plan to migrate +// incrementally towards the v2 settings and behavior. LegacySettings are intentionally handled +// separately here and elsewhere to facilitate their eventual removal. +type LegacySettings struct { + confighttp.HTTPServerSettings `mapstructure:",squash"` + + // Path represents the path the health check service will serve. + // The default path is "/". + Path string `mapstructure:"path"` + + // ResponseBody represents the body of the response returned by the health check service. + // This overrides the default response that it would return. + ResponseBody *ResponseBodySettings `mapstructure:"response_body"` + + // CheckCollectorPipeline contains the list of settings of collector pipeline health check + CheckCollectorPipeline *CheckCollectorPipelineSettings `mapstructure:"check_collector_pipeline"` + + // UseV2Settings is an explicit opt-in to v2 behavior. When true, LegacySettings will be ignored. + UseV2Settings bool `mapstructure:"use_v2_settings"` +} + +// ResponseBodySettings are legacy settings that are currently supported, but can eventually be +// deprecated and removed. +type ResponseBodySettings struct { + // Healthy represents the body of the response returned when the collector is healthy. + // The default value is "" + Healthy string `mapstructure:"healthy"` + + // Unhealthy represents the body of the response returned when the collector is unhealthy. + // The default value is "" + Unhealthy string `mapstructure:"unhealthy"` +} + +// CheckCollectorPipelineSettings are legacy settings that are currently ignored as the +// `check_collector_pipeline` feature in the original healtcheck extension was not working as +// expected. These are here for backwards compatibility. +type CheckCollectorPipelineSettings struct { + // Enabled indicates whether to not enable collector pipeline check. + Enabled bool `mapstructure:"enabled"` + // Interval the time range to check healthy status of collector pipeline + Interval string `mapstructure:"interval"` + // ExporterFailureThreshold is the threshold of exporter failure numbers during the Interval + ExporterFailureThreshold int `mapstructure:"exporter_failure_threshold"` +} diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckextensionv2/internal/http/handlers.go index 6e09d38ac7934..f7ae086455ac7 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckextensionv2/internal/http/handlers.go @@ -4,7 +4,6 @@ package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" import ( - "encoding/json" "net/http" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" @@ -21,13 +20,7 @@ func (s *Server) statusHandler() http.Handler { return } - code, sst := s.responder.response(st) - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - - body, _ := json.Marshal(sst) - _, _ = w.Write(body) + s.responder.respond(st, w) }) } diff --git a/extension/healthcheckextensionv2/internal/http/responders.go b/extension/healthcheckextensionv2/internal/http/responders.go index 948c8c55a45b3..d4b4946266228 100644 --- a/extension/healthcheckextensionv2/internal/http/responders.go +++ b/extension/healthcheckextensionv2/internal/http/responders.go @@ -4,6 +4,8 @@ package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" import ( + "encoding/json" + "fmt" "net/http" "time" @@ -24,29 +26,39 @@ var responseCodes = map[component.Status]int{ component.StatusStopped: http.StatusServiceUnavailable, } -type healthResponder interface { - response(*status.AggregateStatus) (int, *serializableStatus) +type responder interface { + respond(*status.AggregateStatus, http.ResponseWriter) } -type responseFunc func(*status.AggregateStatus) (int, *serializableStatus) +type responderFunc func(*status.AggregateStatus, http.ResponseWriter) -func (f responseFunc) response(st *status.AggregateStatus) (int, *serializableStatus) { - return f(st) +func (f responderFunc) respond(st *status.AggregateStatus, w http.ResponseWriter) { + f(st, w) } -func defaultHealthResponder(startTimestamp *time.Time) responseFunc { - return func(st *status.AggregateStatus) (int, *serializableStatus) { - return responseCodes[st.Status()], toSerializableStatus(st, &serializationOptions{ +func respondWithJSON(code int, content any, w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + + body, _ := json.Marshal(content) + _, _ = w.Write(body) +} + +func defaultResponder(startTimestamp *time.Time) responderFunc { + return func(st *status.AggregateStatus, w http.ResponseWriter) { + code := responseCodes[st.Status()] + sst := toSerializableStatus(st, &serializationOptions{ includeStartTime: true, startTimestamp: startTimestamp, }) + respondWithJSON(code, sst, w) } } func componentHealthResponder( startTimestamp *time.Time, settings *common.ComponentHealthSettings, -) responseFunc { +) responderFunc { healthyFunc := func(now *time.Time) func(status.Event) bool { return func(ev status.Event) bool { if ev.Status() == component.StatusPermanentError { @@ -60,7 +72,7 @@ func componentHealthResponder( return ev.Status() != component.StatusFatalError } } - return func(st *status.AggregateStatus) (int, *serializableStatus) { + return func(st *status.AggregateStatus, w http.ResponseWriter) { now := time.Now() sst := toSerializableStatus( st, @@ -76,6 +88,60 @@ func componentHealthResponder( code = http.StatusInternalServerError } - return code, sst + respondWithJSON(code, sst, w) + } +} + +// Below are responders ported from the original healthcheck extension. We will +// keep them for backwards compatibility, but eventually deprecate and remove +// them. + +// legacyResponseCodes match the current response code mapping with the exception +// of FatalError, which maps to 503 instead of 500. +var legacyResponseCodes = map[component.Status]int{ + component.StatusNone: http.StatusServiceUnavailable, + component.StatusStarting: http.StatusServiceUnavailable, + component.StatusOK: http.StatusOK, + component.StatusRecoverableError: http.StatusOK, + component.StatusPermanentError: http.StatusOK, + component.StatusFatalError: http.StatusServiceUnavailable, + component.StatusStopping: http.StatusServiceUnavailable, + component.StatusStopped: http.StatusServiceUnavailable, +} + +func legacyDefaultResponder(startTimestamp *time.Time) responderFunc { + type healthCheckResponse struct { + StatusMsg string `json:"status"` + UpSince time.Time `json:"upSince"` + Uptime string `json:"uptime"` + } + + codeToMsgMap := map[int]string{ + http.StatusOK: "Server available", + http.StatusServiceUnavailable: "Server not available", + } + + return func(st *status.AggregateStatus, w http.ResponseWriter) { + code := legacyResponseCodes[st.Status()] + resp := healthCheckResponse{ + StatusMsg: codeToMsgMap[code], + } + if code == http.StatusOK { + resp.UpSince = *startTimestamp + resp.Uptime = fmt.Sprintf("%v", time.Since(*startTimestamp)) + } + respondWithJSON(code, resp, w) + } +} + +func legacyCustomResponder(settings *ResponseBodySettings) responderFunc { + codeToMsgMap := map[int][]byte{ + http.StatusOK: []byte(settings.Healthy), + http.StatusServiceUnavailable: []byte(settings.Unhealthy), + } + return func(st *status.AggregateStatus, w http.ResponseWriter) { + code := legacyResponseCodes[st.Status()] + w.WriteHeader(code) + _, _ = w.Write(codeToMsgMap[code]) } } diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckextensionv2/internal/http/server.go index 7beb4e32c2453..17ac05296144c 100644 --- a/extension/healthcheckextensionv2/internal/http/server.go +++ b/extension/healthcheckextensionv2/internal/http/server.go @@ -13,6 +13,7 @@ import ( "time" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/extension" "go.uber.org/zap" @@ -23,10 +24,10 @@ import ( type Server struct { telemetry component.TelemetrySettings - settings *Settings - responder healthResponder + httpSettings confighttp.HTTPServerSettings + httpServer *http.Server mux *http.ServeMux - serverHTTP *http.Server + responder responder colconf atomic.Value aggregator *status.Aggregator startTimestamp time.Time @@ -38,6 +39,7 @@ var _ extension.ConfigWatcher = (*Server)(nil) func NewServer( settings *Settings, + legacySettings LegacySettings, componentHealthSettings *common.ComponentHealthSettings, telemetry component.TelemetrySettings, aggregator *status.Aggregator, @@ -45,22 +47,32 @@ func NewServer( now := time.Now() srv := &Server{ telemetry: telemetry, - settings: settings, - responder: defaultHealthResponder(&now), + mux: http.NewServeMux(), aggregator: aggregator, doneCh: make(chan struct{}), } - if componentHealthSettings != nil { - srv.responder = componentHealthResponder(&now, componentHealthSettings) - } - - srv.mux = http.NewServeMux() - if settings.Status.Enabled { - srv.mux.Handle(settings.Status.Path, srv.statusHandler()) - } - if settings.Config.Enabled { - srv.mux.Handle(settings.Config.Path, srv.configHandler()) + if legacySettings.UseV2Settings { + srv.httpSettings = settings.HTTPServerSettings + if componentHealthSettings != nil { + srv.responder = componentHealthResponder(&now, componentHealthSettings) + } else { + srv.responder = defaultResponder(&now) + } + if settings.Status.Enabled { + srv.mux.Handle(settings.Status.Path, srv.statusHandler()) + } + if settings.Config.Enabled { + srv.mux.Handle(settings.Config.Path, srv.configHandler()) + } + } else { + srv.httpSettings = legacySettings.HTTPServerSettings + if legacySettings.ResponseBody != nil { + srv.responder = legacyCustomResponder(legacySettings.ResponseBody) + } else { + srv.responder = legacyDefaultResponder(&now) + } + srv.mux.Handle(legacySettings.Path, srv.statusHandler()) } return srv @@ -71,19 +83,19 @@ func (s *Server) Start(_ context.Context, host component.Host) error { var err error s.startTimestamp = time.Now() - s.serverHTTP, err = s.settings.ToServer(host, s.telemetry, s.mux) + s.httpServer, err = s.httpSettings.ToServer(host, s.telemetry, s.mux) if err != nil { return err } - ln, err := s.settings.ToListener() + ln, err := s.httpSettings.ToListener() if err != nil { - return fmt.Errorf("failed to bind to address %s: %w", s.settings.Endpoint, err) + return fmt.Errorf("failed to bind to address %s: %w", s.httpSettings.Endpoint, err) } go func() { defer close(s.doneCh) - if err = s.serverHTTP.Serve(ln); !errors.Is(err, http.ErrServerClosed) && err != nil { + if err = s.httpServer.Serve(ln); !errors.Is(err, http.ErrServerClosed) && err != nil { s.telemetry.ReportStatus(component.NewPermanentErrorEvent(err)) } }() @@ -93,10 +105,10 @@ func (s *Server) Start(_ context.Context, host component.Host) error { // Shutdown implements the component.Component interface. func (s *Server) Shutdown(context.Context) error { - if s.serverHTTP == nil { + if s.httpServer == nil { return nil } - s.serverHTTP.Close() + s.httpServer.Close() <-s.doneCh return nil } diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go index 79595f730abb9..2eadb7b6ea553 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -28,6 +28,12 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" ) +// These are used for the legacy test assertions +const ( + expectedBodyNotReady = "{\"status\":\"Server not available\",\"upSince\":" + expectedBodyReady = "{\"status\":\"Server available\",\"upSince\":" +) + type componentStatusExpectation struct { healthy bool status component.Status @@ -40,6 +46,7 @@ type teststep struct { queryParams string eventually bool expectedStatusCode int + expectedBody string expectedComponentStatus *componentStatusExpectation } @@ -51,12 +58,14 @@ func TestStatus(t *testing.T) { tests := []struct { name string settings *Settings + legacySettings LegacySettings componentHealthSettings *common.ComponentHealthSettings pipelines map[string]*testhelpers.PipelineMetadata teststeps []teststep }{ { - name: "exclude recoverable and permanent errors", + name: "exclude recoverable and permanent errors", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -293,7 +302,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "exclude recoverable and permanent errors - verbose", + name: "exclude recoverable and permanent errors - verbose", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -1013,7 +1023,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "include recoverable and exclude permanent errors", + name: "include recoverable and exclude permanent errors", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -1257,7 +1268,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "include recoverable and exclude permanent errors - verbose", + name: "include recoverable and exclude permanent errors - verbose", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -1869,7 +1881,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and exclude recoverable errors", + name: "include permanent and exclude recoverable errors", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -2110,7 +2123,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and exclude recoverable errors - verbose", + name: "include permanent and exclude recoverable errors - verbose", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -2720,7 +2734,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and recoverable errors", + name: "include permanent and recoverable errors", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -2964,7 +2979,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and recoverable errors - verbose", + name: "include permanent and recoverable errors - verbose", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -3576,7 +3592,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "pipeline non-existent", + name: "pipeline non-existent", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -3603,7 +3620,8 @@ func TestStatus(t *testing.T) { }, }, { - name: "status disabled", + name: "status disabled", + legacySettings: LegacySettings{UseV2Settings: true}, settings: &Settings{ HTTPServerSettings: confighttp.HTTPServerSettings{ Endpoint: testutil.GetAvailableLocalAddress(t), @@ -3619,12 +3637,252 @@ func TestStatus(t *testing.T) { }, }, }, + { + name: "legacy - default response", + legacySettings: LegacySettings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Path: "/status", + }, + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator(server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: expectedBodyNotReady, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: expectedBodyNotReady, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: expectedBodyReady, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: expectedBodyReady, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: expectedBodyReady, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: expectedBodyReady, + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewFatalErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: expectedBodyNotReady, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: expectedBodyNotReady, + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: expectedBodyNotReady, + }, + }, + }, + { + name: "legacy - custom response", + legacySettings: LegacySettings{ + HTTPServerSettings: confighttp.HTTPServerSettings{ + Endpoint: testutil.GetAvailableLocalAddress(t), + }, + Path: "/status", + ResponseBody: &ResponseBodySettings{Healthy: "ALL OK", Unhealthy: "NOT OK"}, + }, + teststeps: []teststep{ + { + step: func() { + testhelpers.SeedAggregator(server.aggregator, + traces.InstanceIDs(), + component.StatusStarting, + ) + testhelpers.SeedAggregator(server.aggregator, + metrics.InstanceIDs(), + component.StatusStarting, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: "NOT OK", + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: "NOT OK", + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusOK, + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: "ALL OK", + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewRecoverableErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: "ALL OK", + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewStatusEvent(component.StatusOK), + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: "ALL OK", + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewPermanentErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusOK, + expectedBody: "ALL OK", + }, + { + step: func() { + server.aggregator.RecordStatus( + metrics.ExporterID, + component.NewFatalErrorEvent(assert.AnError), + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: "NOT OK", + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopping, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopping, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: "NOT OK", + }, + { + step: func() { + testhelpers.SeedAggregator( + server.aggregator, + traces.InstanceIDs(), + component.StatusStopped, + ) + testhelpers.SeedAggregator( + server.aggregator, + metrics.InstanceIDs(), + component.StatusStopped, + ) + }, + expectedStatusCode: http.StatusServiceUnavailable, + expectedBody: "NOT OK", + }, + }, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { server = NewServer( tc.settings, + tc.legacySettings, tc.componentHealthSettings, componenttest.NewNopTelemetrySettings(), status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthSettings)), @@ -3633,8 +3891,14 @@ func TestStatus(t *testing.T) { require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) defer func() { require.NoError(t, server.Shutdown(context.Background())) }() + var url string + if tc.legacySettings.UseV2Settings { + url = fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Status.Path) + } else { + url = fmt.Sprintf("http://%s%s", tc.legacySettings.Endpoint, tc.legacySettings.Path) + } + client := &http.Client{} - url := fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Status.Path) for _, ts := range tc.teststeps { if ts.step != nil { @@ -3661,10 +3925,12 @@ func TestStatus(t *testing.T) { assert.Equal(t, ts.expectedStatusCode, resp.StatusCode) } - if ts.expectedComponentStatus != nil { - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + assert.True(t, strings.Contains(string(body), ts.expectedBody)) + + if ts.expectedComponentStatus != nil { st := &serializableStatus{} require.NoError(t, json.Unmarshal(body, st)) fmt.Println(string(body)) @@ -3672,7 +3938,6 @@ func TestStatus(t *testing.T) { assertStatusDetailed(t, ts.expectedComponentStatus, st) continue } - assertStatusSimple(t, ts.expectedComponentStatus, st) } } @@ -3796,6 +4061,7 @@ func TestConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { server = NewServer( tc.settings, + LegacySettings{UseV2Settings: true}, &common.ComponentHealthSettings{}, componenttest.NewNopTelemetrySettings(), status.NewAggregator(status.PriorityPermanent), diff --git a/extension/healthcheckextensionv2/internal/status/aggregation_test.go b/extension/healthcheckextensionv2/internal/status/aggregation_test.go index f9c449aa1e370..9c37768341bd5 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregation_test.go +++ b/extension/healthcheckextensionv2/internal/status/aggregation_test.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package status import ( diff --git a/extension/healthcheckextensionv2/testdata/config.yaml b/extension/healthcheckextensionv2/testdata/config.yaml new file mode 100644 index 0000000000000..b7c0c0f4b7ea8 --- /dev/null +++ b/extension/healthcheckextensionv2/testdata/config.yaml @@ -0,0 +1,48 @@ +healthcheckv2: +healthcheckv2/legacysettings: + endpoint: "localhost:13" + tls: + ca_file: "/path/to/ca" + key_file: "/path/to/key" + cert_file: "/path/to/cert" + check_collector_pipeline: + enabled: false + interval: 5m + exporter_failure_threshold: 5 +healthcheckv2/missingendpoint: + endpoint: "" +healthcheckv2/invalidpath: + endpoint: "localhost:13" + path: "invalid" +healthcheckv2/v2all: + use_v2_settings: true + http: + grpc: + component_health: + include_permanent_errors: true + include_recoverable_errors: true + recovery_duration: 5m +healthcheckv2/v2httpcustomized: + use_v2_settings: true + http: + endpoint: "localhost:13" + status: + enabled: true + path: "/health" + config: + enabled: true + path: "/conf" +healthcheckv2/v2httpmissingendpoint: + use_v2_settings: true + http: + endpoint: "" +healthcheckv2/v2grpccustomized: + use_v2_settings: true + grpc: + endpoint: "localhost:13" +healthcheckv2/v2grpcmissingendpoint: + use_v2_settings: true + grpc: + endpoint: "" +healthcheckv2/v2noprotocols: + use_v2_settings: true From c773103b916e78ad7f39c45037614f4c1dd5e56c Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 29 Feb 2024 18:26:14 -0800 Subject: [PATCH 14/26] Update deps / rename settings -> config --- extension/healthcheckextensionv2/config.go | 47 +- .../healthcheckextensionv2/config_test.go | 69 +- extension/healthcheckextensionv2/extension.go | 20 +- .../healthcheckextensionv2/extension_test.go | 12 +- extension/healthcheckextensionv2/factory.go | 29 +- .../healthcheckextensionv2/factory_test.go | 23 +- extension/healthcheckextensionv2/go.mod | 71 +-- extension/healthcheckextensionv2/go.sum | 598 +++--------------- .../common/{settings.go => config.go} | 4 +- .../internal/grpc/config.go | 4 +- .../internal/grpc/grpc.go | 12 +- .../internal/grpc/grpc_test.go | 82 +-- .../internal/grpc/server.go | 34 +- .../internal/http/config.go | 43 +- .../internal/http/responders.go | 14 +- .../internal/http/server.go | 38 +- .../internal/http/server_test.go | 202 +++--- .../internal/status/aggregation.go | 4 +- .../internal/status/aggregator.go | 3 + .../internal/testhelpers/helpers.go | 4 +- .../testdata/config.yaml | 14 +- 21 files changed, 438 insertions(+), 889 deletions(-) rename extension/healthcheckextensionv2/internal/common/{settings.go => config.go} (85%) diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go index b9ccbfc9cf2d3..b1a4596e4fa62 100644 --- a/extension/healthcheckextensionv2/config.go +++ b/extension/healthcheckextensionv2/config.go @@ -16,8 +16,8 @@ import ( ) const ( - httpSettingsKey = "http" - grpcSettingsKey = "grpc" + httpConfigKey = "http" + grpcConfigKey = "grpc" ) var ( @@ -30,51 +30,50 @@ var ( // Config has the configuration for the extension enabling the health check // extension, used to report the health status of the service. type Config struct { - // LegacySettings contains the settings for the existing healthcheck extension, which we plan - // to deprecate and remove. - http.LegacySettings `mapstructure:",squash"` + // LegacyConfig contains the config for the existing healthcheck extension. + http.LegacyConfig `mapstructure:",squash"` - // GRPCSettings are v2 settings for the grpc healthcheck service. - GRPCSettings *grpc.Settings `mapstructure:"grpc"` + // GRPCConfig is v2 config for the grpc healthcheck service. + GRPCConfig *grpc.Config `mapstructure:"grpc"` - // HTTPSettings are v2 settings for the http healthcheck service. - HTTPSettings *http.Settings `mapstructure:"http"` + // HTTPConfig is v2 config for the http healthcheck service. + HTTPConfig *http.Config `mapstructure:"http"` - // ComponentHealthSettings are v2 settings shared between http and grpc services - ComponentHealthSettings *common.ComponentHealthSettings `mapstructure:"component_health"` + // ComponentHealthConfig is v2 config shared between http and grpc services + ComponentHealthConfig *common.ComponentHealthConfig `mapstructure:"component_health"` } var _ component.Config = (*Config)(nil) // Validate checks if the extension configuration is valid func (c *Config) Validate() error { - if !c.UseV2Settings { - if c.LegacySettings.Endpoint == "" { + if !c.UseV2 { + if c.LegacyConfig.Endpoint == "" { return errHTTPEndpointRequired } - if !strings.HasPrefix(c.LegacySettings.Path, "/") { + if !strings.HasPrefix(c.LegacyConfig.Path, "/") { return errInvalidPath } return nil } - if c.GRPCSettings == nil && c.HTTPSettings == nil { + if c.GRPCConfig == nil && c.HTTPConfig == nil { return errMissingProtocol } - if c.HTTPSettings != nil { - if c.HTTPSettings.Endpoint == "" { + if c.HTTPConfig != nil { + if c.HTTPConfig.Endpoint == "" { return errHTTPEndpointRequired } - if c.HTTPSettings.Status.Enabled && !strings.HasPrefix(c.HTTPSettings.Status.Path, "/") { + if c.HTTPConfig.Status.Enabled && !strings.HasPrefix(c.HTTPConfig.Status.Path, "/") { return errInvalidPath } - if c.HTTPSettings.Config.Enabled && !strings.HasPrefix(c.HTTPSettings.Config.Path, "/") { + if c.HTTPConfig.Config.Enabled && !strings.HasPrefix(c.HTTPConfig.Config.Path, "/") { return errInvalidPath } } - if c.GRPCSettings != nil && c.GRPCSettings.NetAddr.Endpoint == "" { + if c.GRPCConfig != nil && c.GRPCConfig.NetAddr.Endpoint == "" { return errGRPCEndpointRequired } @@ -88,12 +87,12 @@ func (c *Config) Unmarshal(conf *confmap.Conf) error { return err } - if !conf.IsSet(httpSettingsKey) { - c.HTTPSettings = nil + if !conf.IsSet(httpConfigKey) { + c.HTTPConfig = nil } - if !conf.IsSet(grpcSettingsKey) { - c.GRPCSettings = nil + if !conf.IsSet(grpcConfigKey) { + c.GRPCConfig = nil } return nil diff --git a/extension/healthcheckextensionv2/config_test.go b/extension/healthcheckextensionv2/config_test.go index 914f61cb6673e..3d61c28c6bedd 100644 --- a/extension/healthcheckextensionv2/config_test.go +++ b/extension/healthcheckextensionv2/config_test.go @@ -21,6 +21,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/metadata" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/localhostgate" ) func TestLoadConfig(t *testing.T) { @@ -34,19 +35,19 @@ func TestLoadConfig(t *testing.T) { { id: component.NewID(metadata.Type), expected: &Config{ - LegacySettings: http.LegacySettings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + LegacyConfig: http.LegacyConfig{ + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, Path: "/", }, }, }, { - id: component.NewIDWithName(metadata.Type, "legacysettings"), + id: component.NewIDWithName(metadata.Type, "legacyconfig"), expected: &Config{ - LegacySettings: http.LegacySettings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + LegacyConfig: http.LegacyConfig{ + ServerConfig: confighttp.ServerConfig{ Endpoint: "localhost:13", TLSSetting: &configtls.TLSServerSetting{ TLSSetting: configtls.TLSSetting{ @@ -56,7 +57,7 @@ func TestLoadConfig(t *testing.T) { }, }, }, - CheckCollectorPipeline: &http.CheckCollectorPipelineSettings{ + CheckCollectorPipeline: &http.CheckCollectorPipelineConfig{ Enabled: false, Interval: "5m", ExporterFailureThreshold: 5, @@ -77,35 +78,35 @@ func TestLoadConfig(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "v2all"), expected: &Config{ - LegacySettings: http.LegacySettings{ - UseV2Settings: true, - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + LegacyConfig: http.LegacyConfig{ + UseV2: true, + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, Path: "/", }, - HTTPSettings: &http.Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + HTTPConfig: &http.Config{ + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, - Status: http.PathSettings{ + Status: http.PathConfig{ Enabled: true, Path: "/status", }, - Config: http.PathSettings{ + Config: http.PathConfig{ Enabled: false, Path: "/config", }, }, - GRPCSettings: &grpc.Settings{ - GRPCServerSettings: configgrpc.GRPCServerSettings{ + GRPCConfig: &grpc.Config{ + ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.NetAddr{ - Endpoint: defaultGRPCEndpoint, + Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), Transport: "tcp", }, }, }, - ComponentHealthSettings: &common.ComponentHealthSettings{ + ComponentHealthConfig: &common.ComponentHealthConfig{ IncludePermanent: true, IncludeRecoverable: true, RecoveryDuration: 5 * time.Minute, @@ -115,22 +116,22 @@ func TestLoadConfig(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "v2httpcustomized"), expected: &Config{ - LegacySettings: http.LegacySettings{ - UseV2Settings: true, - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + LegacyConfig: http.LegacyConfig{ + UseV2: true, + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, Path: "/", }, - HTTPSettings: &http.Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + HTTPConfig: &http.Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: "localhost:13", }, - Status: http.PathSettings{ + Status: http.PathConfig{ Enabled: true, Path: "/health", }, - Config: http.PathSettings{ + Config: http.PathConfig{ Enabled: true, Path: "/conf", }, @@ -144,15 +145,15 @@ func TestLoadConfig(t *testing.T) { { id: component.NewIDWithName(metadata.Type, "v2grpccustomized"), expected: &Config{ - LegacySettings: http.LegacySettings{ - UseV2Settings: true, - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + LegacyConfig: http.LegacyConfig{ + UseV2: true, + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, Path: "/", }, - GRPCSettings: &grpc.Settings{ - GRPCServerSettings: configgrpc.GRPCServerSettings{ + GRPCConfig: &grpc.Config{ + ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.NetAddr{ Endpoint: "localhost:13", Transport: "tcp", diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index a9842402d007e..69eac77c7f646 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -44,29 +44,29 @@ func newExtension( var comps []component.Component errPriority := status.PriorityPermanent - if config.ComponentHealthSettings != nil && - config.ComponentHealthSettings.IncludeRecoverable && - !config.ComponentHealthSettings.IncludePermanent { + if config.ComponentHealthConfig != nil && + config.ComponentHealthConfig.IncludeRecoverable && + !config.ComponentHealthConfig.IncludePermanent { errPriority = status.PriorityRecoverable } aggregator := status.NewAggregator(errPriority) - if config.UseV2Settings && config.GRPCSettings != nil { + if config.UseV2 && config.GRPCConfig != nil { grpcServer := grpc.NewServer( - config.GRPCSettings, - config.ComponentHealthSettings, + config.GRPCConfig, + config.ComponentHealthConfig, set.TelemetrySettings, aggregator, ) comps = append(comps, grpcServer) } - if !config.UseV2Settings || config.UseV2Settings && config.HTTPSettings != nil { + if !config.UseV2 || config.UseV2 && config.HTTPConfig != nil { httpServer := http.NewServer( - config.HTTPSettings, - config.LegacySettings, - config.ComponentHealthSettings, + config.HTTPConfig, + config.LegacyConfig, + config.ComponentHealthConfig, set.TelemetrySettings, aggregator, ) diff --git a/extension/healthcheckextensionv2/extension_test.go b/extension/healthcheckextensionv2/extension_test.go index 0d8c04b9b9094..14db04497e23b 100644 --- a/extension/healthcheckextensionv2/extension_test.go +++ b/extension/healthcheckextensionv2/extension_test.go @@ -27,8 +27,8 @@ import ( func TestComponentStatus(t *testing.T) { cfg := createDefaultConfig().(*Config) - cfg.HTTPSettings.Endpoint = testutil.GetAvailableLocalAddress(t) - cfg.UseV2Settings = true + cfg.HTTPConfig.Endpoint = testutil.GetAvailableLocalAddress(t) + cfg.UseV2 = true ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) // Status before Start will be StatusNone @@ -103,10 +103,10 @@ func TestNotifyConfig(t *testing.T) { endpoint := testutil.GetAvailableLocalAddress(t) cfg := createDefaultConfig().(*Config) - cfg.UseV2Settings = true - cfg.HTTPSettings.Endpoint = endpoint - cfg.HTTPSettings.Config.Enabled = true - cfg.HTTPSettings.Config.Path = "/config" + cfg.UseV2 = true + cfg.HTTPConfig.Endpoint = endpoint + cfg.HTTPConfig.Config.Enabled = true + cfg.HTTPConfig.Config.Path = "/config" ext := newExtension(context.Background(), *cfg, extensiontest.NewNopCreateSettings()) diff --git a/extension/healthcheckextensionv2/factory.go b/extension/healthcheckextensionv2/factory.go index 707360e1d6393..de902e76f64d6 100644 --- a/extension/healthcheckextensionv2/factory.go +++ b/extension/healthcheckextensionv2/factory.go @@ -17,13 +17,12 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/metadata" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/localhostgate" ) const ( - // Use 0.0.0.0 to make the health check endpoint accessible - // in container orchestration environments like Kubernetes. - defaultGRPCEndpoint = "0.0.0.0:13132" - defaultHTTPEndpoint = "0.0.0.0:13133" + defaultGRPCPort = 13132 + defaultHTTPPort = 13133 ) // NewFactory creates a factory for HealthCheck extension. @@ -38,29 +37,29 @@ func NewFactory() extension.Factory { func createDefaultConfig() component.Config { return &Config{ - LegacySettings: http.LegacySettings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + LegacyConfig: http.LegacyConfig{ + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, Path: "/", }, - HTTPSettings: &http.Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + HTTPConfig: &http.Config{ + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, - Status: http.PathSettings{ + Status: http.PathConfig{ Enabled: true, Path: "/status", }, - Config: http.PathSettings{ + Config: http.PathConfig{ Enabled: false, Path: "/config", }, }, - GRPCSettings: &grpc.Settings{ - GRPCServerSettings: configgrpc.GRPCServerSettings{ + GRPCConfig: &grpc.Config{ + ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.NetAddr{ - Endpoint: defaultGRPCEndpoint, + Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), Transport: "tcp", }, }, diff --git a/extension/healthcheckextensionv2/factory_test.go b/extension/healthcheckextensionv2/factory_test.go index 3ada7e114dbf5..56082f8ad6fde 100644 --- a/extension/healthcheckextensionv2/factory_test.go +++ b/extension/healthcheckextensionv2/factory_test.go @@ -17,35 +17,36 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/localhostgate" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" ) func TestCreateDefaultConfig(t *testing.T) { cfg := createDefaultConfig() assert.Equal(t, &Config{ - LegacySettings: http.LegacySettings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + LegacyConfig: http.LegacyConfig{ + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, Path: "/", }, - HTTPSettings: &http.Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ - Endpoint: defaultHTTPEndpoint, + HTTPConfig: &http.Config{ + ServerConfig: confighttp.ServerConfig{ + Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), }, - Status: http.PathSettings{ + Status: http.PathConfig{ Enabled: true, Path: "/status", }, - Config: http.PathSettings{ + Config: http.PathConfig{ Enabled: false, Path: "/config", }, }, - GRPCSettings: &grpc.Settings{ - GRPCServerSettings: configgrpc.GRPCServerSettings{ + GRPCConfig: &grpc.Config{ + ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.NetAddr{ - Endpoint: defaultGRPCEndpoint, + Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), Transport: "tcp", }, }, diff --git a/extension/healthcheckextensionv2/go.mod b/extension/healthcheckextensionv2/go.mod index 2be46e13ad22d..d1643e0ef80ec 100644 --- a/extension/healthcheckextensionv2/go.mod +++ b/extension/healthcheckextensionv2/go.mod @@ -1,44 +1,43 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2 -go 1.20 +go 1.21 + +toolchain go1.21.4 require ( - github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.93.0 + github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.95.0 github.com/stretchr/testify v1.8.4 - go.opentelemetry.io/collector/component v0.93.1-0.20240125183026-3cacd40b27e8 - go.opentelemetry.io/collector/config/configgrpc v0.93.1-0.20240125183026-3cacd40b27e8 - go.opentelemetry.io/collector/config/confighttp v0.93.1-0.20240125183026-3cacd40b27e8 - go.opentelemetry.io/collector/config/confignet v0.93.1-0.20240125183026-3cacd40b27e8 - go.opentelemetry.io/collector/confmap v0.93.1-0.20240125183026-3cacd40b27e8 - go.opentelemetry.io/collector/extension v0.93.1-0.20240125183026-3cacd40b27e8 - go.opentelemetry.io/otel/metric v1.22.0 - go.opentelemetry.io/otel/trace v1.22.0 + go.opentelemetry.io/collector/component v0.95.0 + go.opentelemetry.io/collector/config/configgrpc v0.95.0 + go.opentelemetry.io/collector/config/confighttp v0.95.0 + go.opentelemetry.io/collector/config/confignet v0.95.0 + go.opentelemetry.io/collector/config/configtls v0.95.0 + go.opentelemetry.io/collector/confmap v0.95.0 + go.opentelemetry.io/collector/extension v0.95.0 + go.opentelemetry.io/otel/metric v1.23.1 + go.opentelemetry.io/otel/trace v1.23.1 go.uber.org/multierr v1.11.0 - go.uber.org/zap v1.26.0 - google.golang.org/grpc v1.60.1 + go.uber.org/zap v1.27.0 + google.golang.org/grpc v1.61.0 ) require ( - cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68 // indirect - contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-kit/log v0.2.1 // indirect - github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.6 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/providers/confmap v0.1.0 // indirect - github.com/knadh/koanf/v2 v2.0.1 // indirect + github.com/knadh/koanf/v2 v2.1.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -48,31 +47,27 @@ require ( github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/rs/cors v1.10.1 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/collector v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/config/configauth v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/config/configcompression v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/config/configopaque v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/config/configtls v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/config/internal v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/extension/auth v0.93.1-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/featuregate v1.0.2-0.20240125183026-3cacd40b27e8 // indirect - go.opentelemetry.io/collector/pdata v1.0.2-0.20240125183026-3cacd40b27e8 // indirect + go.opentelemetry.io/collector v0.95.0 // indirect + go.opentelemetry.io/collector/config/configauth v0.95.0 // indirect + go.opentelemetry.io/collector/config/configcompression v0.95.0 // indirect + go.opentelemetry.io/collector/config/configopaque v1.2.0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.95.0 // indirect + go.opentelemetry.io/collector/config/internal v0.95.0 // indirect + go.opentelemetry.io/collector/extension/auth v0.95.0 // indirect + go.opentelemetry.io/collector/featuregate v1.2.0 // indirect + go.opentelemetry.io/collector/pdata v1.2.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect - go.opentelemetry.io/otel v1.22.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.45.0 // indirect - go.opentelemetry.io/otel/sdk v1.22.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.22.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + go.opentelemetry.io/otel v1.23.1 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.45.2 // indirect + go.opentelemetry.io/otel/sdk v1.23.1 // indirect + go.opentelemetry.io/otel/sdk/metric v1.23.1 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect google.golang.org/protobuf v1.32.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/extension/healthcheckextensionv2/go.sum b/extension/healthcheckextensionv2/go.sum index bf1db3d9c1e40..1c6ae55c069aa 100644 --- a/extension/healthcheckextensionv2/go.sum +++ b/extension/healthcheckextensionv2/go.sum @@ -1,639 +1,187 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68 h1:aRVqY1p2IJaBGStWMsQMpkAa83cPkCDLl80eOj0Rbz4= cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68/go.mod h1:1a3eRNYX12fs5UABBIXS8HXVvQbX9hRB/RkEBPORpe8= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= -contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/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/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -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/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -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-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/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/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= +github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= -github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= -github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= -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/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8= +github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mostynb/go-grpc-compression v1.2.2 h1:XaDbnRvt2+1vgr0b/l0qh4mJAfIxE0bKXtz2Znl3GGI= github.com/mostynb/go-grpc-compression v1.2.2/go.mod h1:GOCr2KBxXcblCuczg3YdLQlcin1/NfyDA348ckuCH6w= -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/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/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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -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/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= -github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -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/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.6.1/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/collector v0.93.1-0.20240125183026-3cacd40b27e8 h1:uezFa4OOVMFLbanA0SvWNMU1ODKXtKRqaWZMASrobrE= -go.opentelemetry.io/collector v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:lorK6TQaQvg0RDRN42HwY0Gqc/d7mvBgCMeJCYGishA= -go.opentelemetry.io/collector/component v0.93.1-0.20240125183026-3cacd40b27e8 h1:4ytX4kyA1gW5wgHJWYSSUeISSpb73YhQMunval73/F8= -go.opentelemetry.io/collector/component v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:e9ZNh6CU+oXz+onYJvkakir9cTkKXGu+aWEF6RqiNeo= -go.opentelemetry.io/collector/config/configauth v0.93.1-0.20240125183026-3cacd40b27e8 h1:I2MsKwmuqNw6M3MBRTc+pEnP0Ljh5kjrkWfdKiat7pA= -go.opentelemetry.io/collector/config/configauth v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:LoZj+yz2p7XmBoNhNIKDGeiTk7oxyLXqpQxZP4hyUA4= -go.opentelemetry.io/collector/config/configcompression v0.93.1-0.20240125183026-3cacd40b27e8 h1:QGvUxASNUQIkeLGWDywomDpuvnSC8F/oEUs9mn8kXrQ= -go.opentelemetry.io/collector/config/configcompression v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:fA36AZC/Qcyl+HvMnvFZuV/iUWGQJrchimmk+qYWuMM= -go.opentelemetry.io/collector/config/configgrpc v0.93.1-0.20240125183026-3cacd40b27e8 h1:jRRnXHuZ2iQN2xeWXNjVCo7HfqsgO3pi9ZNW8J9WoW0= -go.opentelemetry.io/collector/config/configgrpc v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:gGXBMlaDRyOA9fWK/nb6a/D4yMEIZkLjl5/qtyU3ix8= -go.opentelemetry.io/collector/config/confighttp v0.93.1-0.20240125183026-3cacd40b27e8 h1:6fwigpqZSfmAFVX/N4CJ9sFP8s4OyQ3HJWZ6iSycutw= -go.opentelemetry.io/collector/config/confighttp v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:5doQNLIZoUhJJJzpAaMSzGgztAPcvdzTYnynIfJvtkY= -go.opentelemetry.io/collector/config/confignet v0.93.1-0.20240125183026-3cacd40b27e8 h1:WJ8ReVL8JJsMXbN+XLbOYh7W+/pInq/UofqWrps/4mQ= -go.opentelemetry.io/collector/config/confignet v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:rraribsOoZsYZm51+3TXHavmXKJOC5a5/X20vfmNbdw= -go.opentelemetry.io/collector/config/configopaque v0.93.1-0.20240125183026-3cacd40b27e8 h1:QosjKFUFDFJGjgu7F4lNkU/t48ookBoJV52zLI7qrxs= -go.opentelemetry.io/collector/config/configopaque v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:dQK8eUXjIGKaw1RB7UIg2nqx56AueNxeKFCdB0P1ypg= -go.opentelemetry.io/collector/config/configtelemetry v0.93.1-0.20240125183026-3cacd40b27e8 h1:M1YctKlH7yVTxbC0hKg/L1pWewCI54oiYKasrhjDPKE= -go.opentelemetry.io/collector/config/configtelemetry v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= -go.opentelemetry.io/collector/config/configtls v0.93.1-0.20240125183026-3cacd40b27e8 h1:/XR5TOA6RH5ddfF2Hbb/q75sesRK1byxb9TfaypKaog= -go.opentelemetry.io/collector/config/configtls v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:ehXx933OgUTSH7bIWphIf1Kfohy6qmrVHizFCgHtF7U= -go.opentelemetry.io/collector/config/internal v0.93.1-0.20240125183026-3cacd40b27e8 h1:9hYCiXqh/jTuqWOTNVY2Ylyo3c+5u5QMa2NM3fUhEzQ= -go.opentelemetry.io/collector/config/internal v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:Kth7pm10Z91Ui7aDZlrhUJmOvxCCIuogK6bDcHWUXko= -go.opentelemetry.io/collector/confmap v0.93.1-0.20240125183026-3cacd40b27e8 h1:w5c3JCJZCs6PXeJeoN/ECj5u+zjG26pz37GjnfYZph8= -go.opentelemetry.io/collector/confmap v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:KjHrfxKKojaLDc9zDPfVuyp8765AH+XfcoPWMLMiuHU= -go.opentelemetry.io/collector/consumer v0.93.0 h1:tt9T8knyamBr/85VqIbESsIHVkFXCkwOD+noFqK3+Vg= -go.opentelemetry.io/collector/extension v0.93.1-0.20240125183026-3cacd40b27e8 h1:auNQAdJKXV30Q/0EJGxItCeH7Y9V8rixfVcry3GZs4g= -go.opentelemetry.io/collector/extension v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:mTUTMXug92ewm4hwYMUXPoUQOeBolUTCVcpMUJ/1s70= -go.opentelemetry.io/collector/extension/auth v0.93.1-0.20240125183026-3cacd40b27e8 h1:AGFCgqkC2OEY8+jYMdOFPo/aadJWaq7/qqMHT8/wvTM= -go.opentelemetry.io/collector/extension/auth v0.93.1-0.20240125183026-3cacd40b27e8/go.mod h1:HLiKtPliU33ubLNmBlDHQBupwHtk4O23H0F0eNKj0n4= -go.opentelemetry.io/collector/featuregate v1.0.2-0.20240125183026-3cacd40b27e8 h1:SIG4rZiTd9uDrcM3vHItCRDHmwh+RVaTLF6OKEOCJNc= -go.opentelemetry.io/collector/featuregate v1.0.2-0.20240125183026-3cacd40b27e8/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= -go.opentelemetry.io/collector/pdata v1.0.2-0.20240125183026-3cacd40b27e8 h1:TCr0cgIqNSQeRjf2HUqKpMIVpb+C/NVNTReeebysW6c= -go.opentelemetry.io/collector/pdata v1.0.2-0.20240125183026-3cacd40b27e8/go.mod h1:jutXeu0QOXYY8wcZ/hege+YAnSBP3+jpTqYU1+JTI5Y= +go.opentelemetry.io/collector v0.95.0 h1:DFW0BkF2sOocpA3NUPrbMeuPSN3PWxFBrLqs/Cxn3vo= +go.opentelemetry.io/collector v0.95.0/go.mod h1:Lc+VkOkSBppKqR/cAevS5oPrbtUO3JUbYzo91niRkG0= +go.opentelemetry.io/collector/component v0.95.0 h1:68tI7KVy1bfpaR83+LxUvjd9/hjDh78utklGl2t6eVM= +go.opentelemetry.io/collector/component v0.95.0/go.mod h1:SMt7r9zm0OOEKJF/ZUy8agD92OAXq2Xhq1FaTcfIWHw= +go.opentelemetry.io/collector/config/configauth v0.95.0 h1:peGXB3ctZW/B8PomTL7CoIpTy94V5TR3JX6fD8kakmU= +go.opentelemetry.io/collector/config/configauth v0.95.0/go.mod h1:80lAG02Mucz0JYdG+k0NIul/BQF5bUpeRGEndgg07lg= +go.opentelemetry.io/collector/config/configcompression v0.95.0 h1:G1ljjbXmh37JaePSM+2r0bRNlEoCg4l1M1UuhGgnXx0= +go.opentelemetry.io/collector/config/configcompression v0.95.0/go.mod h1:owL6s04LI1fPrNZvXiRm6o4B0jaxb3z/oFEcgrakFK4= +go.opentelemetry.io/collector/config/configgrpc v0.95.0 h1:LBB2wxtCiYhIVDMYYE7JmBZn56pXQmYolAxFGI2Cj3o= +go.opentelemetry.io/collector/config/configgrpc v0.95.0/go.mod h1:zjHif3wbSEtcu+o0qQXdtZZGftMfoMVEGwqxlREDdDk= +go.opentelemetry.io/collector/config/confighttp v0.95.0 h1:x/f3CQhQzei2WPoGkSa9x1D1qXfMkbfnhKoQL7VjGvw= +go.opentelemetry.io/collector/config/confighttp v0.95.0/go.mod h1:77imNR16GOaIEoomda3ysRTyaVzH4cQi27FtDknJrqw= +go.opentelemetry.io/collector/config/confignet v0.95.0 h1:GGAq0E7DFPIcwyq6h0OOPEfLyym/7LUIOB2ij6l3xtE= +go.opentelemetry.io/collector/config/confignet v0.95.0/go.mod h1:BVw5xkQ7TH2wH75cbph+dtOoxq1baWLuhdSYIAvuVu0= +go.opentelemetry.io/collector/config/configopaque v1.2.0 h1:ncnAuq4px3yREsirivGUbwr36xXEKa3K6JTOBNGlbtc= +go.opentelemetry.io/collector/config/configopaque v1.2.0/go.mod h1:6BAnSe6wok2Sg3tiNuapBbLnrduyMwzsBzbfgUSbDnI= +go.opentelemetry.io/collector/config/configtelemetry v0.95.0 h1:HabJZqbOAbNQ52L3v6usXoGXg1UKA1Ofs4Ytp5sGXEo= +go.opentelemetry.io/collector/config/configtelemetry v0.95.0/go.mod h1:tl8sI2RE3LSgJ0HjpadYpIwsKzw/CRA0nZUXLzMAZS0= +go.opentelemetry.io/collector/config/configtls v0.95.0 h1:LB6B5vCXwZV77jWPNhdvsgkyY8CFv8gdsRjfQS2Rioc= +go.opentelemetry.io/collector/config/configtls v0.95.0/go.mod h1:661iHIlhQTKIuUUEkQmuoyek1oq46XlsKjLu3JIzJrE= +go.opentelemetry.io/collector/config/internal v0.95.0 h1:jSifydX2KscDhVuTxGFdYtgWu+7AVKr9/7OCvBmS46Q= +go.opentelemetry.io/collector/config/internal v0.95.0/go.mod h1:KR0lphyXBwuB1DImzQDRVONoRTbwsGnH9degSG5018M= +go.opentelemetry.io/collector/confmap v0.95.0 h1:0oZwSUaeKTDCP7eewFpQSD+9SxXspiaJWjZDQYGGars= +go.opentelemetry.io/collector/confmap v0.95.0/go.mod h1:L3djzwpt+jL06wxnHAuy1jPUFcM+MdKGQAsz3B1d6pk= +go.opentelemetry.io/collector/consumer v0.95.0 h1:M/N5RDx8/6Hz5L1qWUXdtirtdoV8BEjIxCSdt6cCx+I= +go.opentelemetry.io/collector/consumer v0.95.0/go.mod h1:tM5aOolWS1zAByMbne2xVOkmVvZrF3VEKY6TkxhmkOs= +go.opentelemetry.io/collector/extension v0.95.0 h1:amE7zV/lfJRdCmZ4cqWmvBzZB5aQmIFIQqIuXmhaCjI= +go.opentelemetry.io/collector/extension v0.95.0/go.mod h1:IDt4B5GJxh/uJ/mUWxYZ+eHrJ49k4B8s8gJhuI0TRVI= +go.opentelemetry.io/collector/extension/auth v0.95.0 h1:Qu2/I6YXW1yhh+M5PZ6b5izPdzrh+bl8X7ewEeJQWGU= +go.opentelemetry.io/collector/extension/auth v0.95.0/go.mod h1:FSODnSbmqfRT9dQdJgogby7j4bcV8TmrhddTvcAjcjU= +go.opentelemetry.io/collector/featuregate v1.2.0 h1:nF8OGq5PsSNSLeuNwTWlOqThxbLW6v6DOCvSqQMc108= +go.opentelemetry.io/collector/featuregate v1.2.0/go.mod h1:mm8+xyQfgDmqhyegZRNIQmoKsNnDTwWKFLsdMoXAb7A= +go.opentelemetry.io/collector/pdata v1.2.0 h1:N6VdyEFYJyoHIKqHd0F372eNVD5b+AbH0ZQf7Z2jJ9I= +go.opentelemetry.io/collector/pdata v1.2.0/go.mod h1:mKXb6527Syb8PT4P9CZOJNbkuHOHjjGTZNNwSKESJhc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/exporters/prometheus v0.45.0 h1:BeIK2KGho0oCWa7LxEGSqfDZbs7Fpv/Viz+FS4P8CXE= -go.opentelemetry.io/otel/exporters/prometheus v0.45.0/go.mod h1:UVJZPLnfDSvHj+eJuZE+E1GjIBD267mEMfAAHJdghWg= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= -go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/exporters/prometheus v0.45.2 h1:pe2Jqk1K18As0RCw7J08QhgXNqr+6npx0a5W4IgAFA8= +go.opentelemetry.io/otel/exporters/prometheus v0.45.2/go.mod h1:B38pscHKI6bhFS44FDw0eFU3iqG3ASNIvY+fZgR5sAc= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= +go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= +go.opentelemetry.io/otel/sdk/metric v1.23.1 h1:T9/8WsYg+ZqIpMWwdISVVrlGb/N0Jr1OHjR/alpKwzg= +go.opentelemetry.io/otel/sdk/metric v1.23.1/go.mod h1:8WX6WnNtHCgUruJ4TJ+UssQjMtpxkpX0zveQC8JG/E0= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -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/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= 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.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -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.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.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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/extension/healthcheckextensionv2/internal/common/settings.go b/extension/healthcheckextensionv2/internal/common/config.go similarity index 85% rename from extension/healthcheckextensionv2/internal/common/settings.go rename to extension/healthcheckextensionv2/internal/common/config.go index 561e818a53b9d..67f3826c45c8c 100644 --- a/extension/healthcheckextensionv2/internal/common/settings.go +++ b/extension/healthcheckextensionv2/internal/common/config.go @@ -5,12 +5,12 @@ package common // import "github.com/open-telemetry/opentelemetry-collector-cont import "time" -type ComponentHealthSettings struct { +type ComponentHealthConfig struct { IncludePermanent bool `mapstructure:"include_permanent_errors"` IncludeRecoverable bool `mapstructure:"include_recoverable_errors"` RecoveryDuration time.Duration `mapstructure:"recovery_duration"` } -func (c ComponentHealthSettings) Enabled() bool { +func (c ComponentHealthConfig) Enabled() bool { return c.IncludePermanent || c.IncludeRecoverable } diff --git a/extension/healthcheckextensionv2/internal/grpc/config.go b/extension/healthcheckextensionv2/internal/grpc/config.go index fef9837498331..6fa6c017c34ae 100644 --- a/extension/healthcheckextensionv2/internal/grpc/config.go +++ b/extension/healthcheckextensionv2/internal/grpc/config.go @@ -5,6 +5,6 @@ package grpc // import "github.com/open-telemetry/opentelemetry-collector-contri import "go.opentelemetry.io/collector/config/configgrpc" -type Settings struct { - configgrpc.GRPCServerSettings `mapstructure:",squash"` +type Config struct { + configgrpc.ServerConfig `mapstructure:",squash"` } diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index f6963552da746..4fd2f65f6255e 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -59,12 +59,12 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ switch { case st == nil: sst = healthpb.HealthCheckResponse_SERVICE_UNKNOWN - case s.componentHealthSettings.IncludeRecoverable && - s.componentHealthSettings.RecoveryDuration > 0 && + case s.componentHealthConfig.IncludeRecoverable && + s.componentHealthConfig.RecoveryDuration > 0 && st.Status() == component.StatusRecoverableError: if failureTimer == nil { failureTimer = time.AfterFunc( - s.componentHealthSettings.RecoveryDuration, + s.componentHealthConfig.RecoveryDuration, func() { failureCh <- struct{}{} }, ) } @@ -116,13 +116,13 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ func (s *Server) toServingStatus( ev status.Event, ) healthpb.HealthCheckResponse_ServingStatus { - if s.componentHealthSettings.IncludeRecoverable && + if s.componentHealthConfig.IncludeRecoverable && ev.Status() == component.StatusRecoverableError && - time.Now().After(ev.Timestamp().Add(s.componentHealthSettings.RecoveryDuration)) { + time.Now().After(ev.Timestamp().Add(s.componentHealthConfig.RecoveryDuration)) { return healthpb.HealthCheckResponse_NOT_SERVING } - if s.componentHealthSettings.IncludePermanent && ev.Status() == component.StatusPermanentError { + if s.componentHealthConfig.IncludePermanent && ev.Status() == component.StatusPermanentError { return healthpb.HealthCheckResponse_NOT_SERVING } diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go index a1a0151b26ac2..e1c6a3a294a80 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go @@ -29,8 +29,8 @@ import ( func TestCheck(t *testing.T) { addr := testutil.GetAvailableLocalAddress(t) - settings := &Settings{ - GRPCServerSettings: configgrpc.GRPCServerSettings{ + config := &Config{ + ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.NetAddr{ Endpoint: addr, Transport: "tcp", @@ -51,13 +51,13 @@ func TestCheck(t *testing.T) { tests := []struct { name string - settings *Settings - componentHealthSettings *common.ComponentHealthSettings + config *Config + componentHealthSettings *common.ComponentHealthConfig teststeps []teststep }{ { - name: "exclude recoverable and permanent errors", - settings: settings, + name: "exclude recoverable and permanent errors", + config: config, teststeps: []teststep{ { service: "", @@ -203,9 +203,9 @@ func TestCheck(t *testing.T) { }, }, { - name: "include recoverable and exclude permanent errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "include recoverable and exclude permanent errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: false, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -379,9 +379,9 @@ func TestCheck(t *testing.T) { }, }, { - name: "include permanent and exclude recoverable errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "include permanent and exclude recoverable errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: true, }, teststeps: []teststep{ @@ -534,9 +534,9 @@ func TestCheck(t *testing.T) { }, }, { - name: "include permanent and recoverable errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "include permanent and recoverable errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: true, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -692,7 +692,7 @@ func TestCheck(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { server = NewServer( - settings, + config, tc.componentHealthSettings, componenttest.NewNopTelemetrySettings(), status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthSettings)), @@ -746,8 +746,8 @@ func TestCheck(t *testing.T) { func TestWatch(t *testing.T) { addr := testutil.GetAvailableLocalAddress(t) - settings := &Settings{ - GRPCServerSettings: configgrpc.GRPCServerSettings{ + config := &Config{ + ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.NetAddr{ Endpoint: addr, Transport: "tcp", @@ -771,13 +771,13 @@ func TestWatch(t *testing.T) { tests := []struct { name string - settings *Settings - componentHealthSettings *common.ComponentHealthSettings + config *Config + componentHealthSettings *common.ComponentHealthConfig teststeps []teststep }{ { - name: "exclude recoverable and permanent errors", - settings: settings, + name: "exclude recoverable and permanent errors", + config: config, teststeps: []teststep{ { service: "", @@ -893,9 +893,9 @@ func TestWatch(t *testing.T) { }, }, { - name: "include recoverable and exclude permanent errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "include recoverable and exclude permanent errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: false, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -1005,9 +1005,9 @@ func TestWatch(t *testing.T) { }, }, { - name: "exclude permanent errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "exclude permanent errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: false, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -1087,9 +1087,9 @@ func TestWatch(t *testing.T) { }, }, { - name: "include recoverable 0s recovery duration", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "include recoverable 0s recovery duration", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: false, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -1218,9 +1218,9 @@ func TestWatch(t *testing.T) { }, }, { - name: "include permanent and exclude recoverable errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "include permanent and exclude recoverable errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: true, IncludeRecoverable: false, RecoveryDuration: 2 * time.Millisecond, @@ -1328,9 +1328,9 @@ func TestWatch(t *testing.T) { }, }, { - name: "exclude recoverable errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "exclude recoverable errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: true, IncludeRecoverable: false, RecoveryDuration: 2 * time.Millisecond, @@ -1410,9 +1410,9 @@ func TestWatch(t *testing.T) { }, }, { - name: "include recoverable and permanent errors", - settings: settings, - componentHealthSettings: &common.ComponentHealthSettings{ + name: "include recoverable and permanent errors", + config: config, + componentHealthSettings: &common.ComponentHealthConfig{ IncludePermanent: true, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -1530,7 +1530,7 @@ func TestWatch(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { server = NewServer( - settings, + config, tc.componentHealthSettings, componenttest.NewNopTelemetrySettings(), status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthSettings)), diff --git a/extension/healthcheckextensionv2/internal/grpc/server.go b/extension/healthcheckextensionv2/internal/grpc/server.go index 3af32d714d64a..6355b92950a71 100644 --- a/extension/healthcheckextensionv2/internal/grpc/server.go +++ b/extension/healthcheckextensionv2/internal/grpc/server.go @@ -17,31 +17,31 @@ import ( type Server struct { healthpb.UnimplementedHealthServer - grpcServer *grpc.Server - aggregator *status.Aggregator - settings *Settings - componentHealthSettings *common.ComponentHealthSettings - telemetry component.TelemetrySettings - doneCh chan struct{} + grpcServer *grpc.Server + aggregator *status.Aggregator + config *Config + componentHealthConfig *common.ComponentHealthConfig + telemetry component.TelemetrySettings + doneCh chan struct{} } var _ component.Component = (*Server)(nil) func NewServer( - settings *Settings, - componentHealthSettings *common.ComponentHealthSettings, + config *Config, + componentHealthConfig *common.ComponentHealthConfig, telemetry component.TelemetrySettings, aggregator *status.Aggregator, ) *Server { srv := &Server{ - settings: settings, - componentHealthSettings: componentHealthSettings, - telemetry: telemetry, - aggregator: aggregator, - doneCh: make(chan struct{}), + config: config, + componentHealthConfig: componentHealthConfig, + telemetry: telemetry, + aggregator: aggregator, + doneCh: make(chan struct{}), } - if srv.componentHealthSettings == nil { - srv.componentHealthSettings = &common.ComponentHealthSettings{} + if srv.componentHealthConfig == nil { + srv.componentHealthConfig = &common.ComponentHealthConfig{} } return srv } @@ -49,13 +49,13 @@ func NewServer( // Start implements the component.Component interface. func (s *Server) Start(_ context.Context, host component.Host) error { var err error - s.grpcServer, err = s.settings.ToServer(host, s.telemetry) + s.grpcServer, err = s.config.ToServer(host, s.telemetry) if err != nil { return err } healthpb.RegisterHealthServer(s.grpcServer, s) - ln, err := s.settings.ToListener() + ln, err := s.config.ToListenerContext(context.Background()) go func() { defer close(s.doneCh) diff --git a/extension/healthcheckextensionv2/internal/http/config.go b/extension/healthcheckextensionv2/internal/http/config.go index 514443e56d714..6ab8cb184b644 100644 --- a/extension/healthcheckextensionv2/internal/http/config.go +++ b/extension/healthcheckextensionv2/internal/http/config.go @@ -5,24 +5,24 @@ package http // import "github.com/open-telemetry/opentelemetry-collector-contri import "go.opentelemetry.io/collector/config/confighttp" -// Settings contains the v2 settings for the http healthcheck service -type Settings struct { - confighttp.HTTPServerSettings `mapstructure:",squash"` +// Config contains the v2 config for the http healthcheck service +type Config struct { + confighttp.ServerConfig `mapstructure:",squash"` - Config PathSettings `mapstructure:"config"` - Status PathSettings `mapstructure:"status"` + Config PathConfig `mapstructure:"config"` + Status PathConfig `mapstructure:"status"` } -type PathSettings struct { +type PathConfig struct { Enabled bool `mapstructure:"enabled"` Path string `mapstructure:"path"` } -// LegacySettings contain the settings for the original healthcheck extension. We plan to migrate -// incrementally towards the v2 settings and behavior. LegacySettings are intentionally handled -// separately here and elsewhere to facilitate their eventual removal. -type LegacySettings struct { - confighttp.HTTPServerSettings `mapstructure:",squash"` +// LegacyConfig contains the config for the original healthcheck extension. We plan to migrate +// incrementally towards the v2 config and behavior. LegacyConfig is intentionally handled +// separately here and elsewhere to facilitate its eventual removal. +type LegacyConfig struct { + confighttp.ServerConfig `mapstructure:",squash"` // Path represents the path the health check service will serve. // The default path is "/". @@ -30,18 +30,19 @@ type LegacySettings struct { // ResponseBody represents the body of the response returned by the health check service. // This overrides the default response that it would return. - ResponseBody *ResponseBodySettings `mapstructure:"response_body"` + ResponseBody *ResponseBodyConfig `mapstructure:"response_body"` - // CheckCollectorPipeline contains the list of settings of collector pipeline health check - CheckCollectorPipeline *CheckCollectorPipelineSettings `mapstructure:"check_collector_pipeline"` + // CheckCollectorPipeline contains the config for collector pipeline health checks. It is + // retained for backwards compatibility, but is otherwise ignored. + CheckCollectorPipeline *CheckCollectorPipelineConfig `mapstructure:"check_collector_pipeline"` - // UseV2Settings is an explicit opt-in to v2 behavior. When true, LegacySettings will be ignored. - UseV2Settings bool `mapstructure:"use_v2_settings"` + // UseV2 is an explicit opt-in to v2 behavior. When true, LegacyConfig will be ignored. + UseV2 bool `mapstructure:"use_v2"` } -// ResponseBodySettings are legacy settings that are currently supported, but can eventually be +// ResponseBodyConfig is legacy config that is currently supported, but can eventually be // deprecated and removed. -type ResponseBodySettings struct { +type ResponseBodyConfig struct { // Healthy represents the body of the response returned when the collector is healthy. // The default value is "" Healthy string `mapstructure:"healthy"` @@ -51,10 +52,10 @@ type ResponseBodySettings struct { Unhealthy string `mapstructure:"unhealthy"` } -// CheckCollectorPipelineSettings are legacy settings that are currently ignored as the +// CheckCollectorPipelineConfig is legacy config that is currently ignored as the // `check_collector_pipeline` feature in the original healtcheck extension was not working as -// expected. These are here for backwards compatibility. -type CheckCollectorPipelineSettings struct { +// expected. This is here for backwards compatibility. +type CheckCollectorPipelineConfig struct { // Enabled indicates whether to not enable collector pipeline check. Enabled bool `mapstructure:"enabled"` // Interval the time range to check healthy status of collector pipeline diff --git a/extension/healthcheckextensionv2/internal/http/responders.go b/extension/healthcheckextensionv2/internal/http/responders.go index d4b4946266228..5a32b3fdc60fe 100644 --- a/extension/healthcheckextensionv2/internal/http/responders.go +++ b/extension/healthcheckextensionv2/internal/http/responders.go @@ -57,16 +57,16 @@ func defaultResponder(startTimestamp *time.Time) responderFunc { func componentHealthResponder( startTimestamp *time.Time, - settings *common.ComponentHealthSettings, + config *common.ComponentHealthConfig, ) responderFunc { healthyFunc := func(now *time.Time) func(status.Event) bool { return func(ev status.Event) bool { if ev.Status() == component.StatusPermanentError { - return !settings.IncludePermanent + return !config.IncludePermanent } - if ev.Status() == component.StatusRecoverableError && settings.IncludeRecoverable { - return now.Before(ev.Timestamp().Add(settings.RecoveryDuration)) + if ev.Status() == component.StatusRecoverableError && config.IncludeRecoverable { + return now.Before(ev.Timestamp().Add(config.RecoveryDuration)) } return ev.Status() != component.StatusFatalError @@ -134,10 +134,10 @@ func legacyDefaultResponder(startTimestamp *time.Time) responderFunc { } } -func legacyCustomResponder(settings *ResponseBodySettings) responderFunc { +func legacyCustomResponder(config *ResponseBodyConfig) responderFunc { codeToMsgMap := map[int][]byte{ - http.StatusOK: []byte(settings.Healthy), - http.StatusServiceUnavailable: []byte(settings.Unhealthy), + http.StatusOK: []byte(config.Healthy), + http.StatusServiceUnavailable: []byte(config.Unhealthy), } return func(st *status.AggregateStatus, w http.ResponseWriter) { code := legacyResponseCodes[st.Status()] diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckextensionv2/internal/http/server.go index 17ac05296144c..e4c880eb0289c 100644 --- a/extension/healthcheckextensionv2/internal/http/server.go +++ b/extension/healthcheckextensionv2/internal/http/server.go @@ -24,7 +24,7 @@ import ( type Server struct { telemetry component.TelemetrySettings - httpSettings confighttp.HTTPServerSettings + httpConfig confighttp.ServerConfig httpServer *http.Server mux *http.ServeMux responder responder @@ -38,9 +38,9 @@ var _ component.Component = (*Server)(nil) var _ extension.ConfigWatcher = (*Server)(nil) func NewServer( - settings *Settings, - legacySettings LegacySettings, - componentHealthSettings *common.ComponentHealthSettings, + config *Config, + legacyConfig LegacyConfig, + componentHealthConfig *common.ComponentHealthConfig, telemetry component.TelemetrySettings, aggregator *status.Aggregator, ) *Server { @@ -52,27 +52,27 @@ func NewServer( doneCh: make(chan struct{}), } - if legacySettings.UseV2Settings { - srv.httpSettings = settings.HTTPServerSettings - if componentHealthSettings != nil { - srv.responder = componentHealthResponder(&now, componentHealthSettings) + if legacyConfig.UseV2 { + srv.httpConfig = config.ServerConfig + if componentHealthConfig != nil { + srv.responder = componentHealthResponder(&now, componentHealthConfig) } else { srv.responder = defaultResponder(&now) } - if settings.Status.Enabled { - srv.mux.Handle(settings.Status.Path, srv.statusHandler()) + if config.Status.Enabled { + srv.mux.Handle(config.Status.Path, srv.statusHandler()) } - if settings.Config.Enabled { - srv.mux.Handle(settings.Config.Path, srv.configHandler()) + if config.Config.Enabled { + srv.mux.Handle(config.Config.Path, srv.configHandler()) } } else { - srv.httpSettings = legacySettings.HTTPServerSettings - if legacySettings.ResponseBody != nil { - srv.responder = legacyCustomResponder(legacySettings.ResponseBody) + srv.httpConfig = legacyConfig.ServerConfig + if legacyConfig.ResponseBody != nil { + srv.responder = legacyCustomResponder(legacyConfig.ResponseBody) } else { srv.responder = legacyDefaultResponder(&now) } - srv.mux.Handle(legacySettings.Path, srv.statusHandler()) + srv.mux.Handle(legacyConfig.Path, srv.statusHandler()) } return srv @@ -83,14 +83,14 @@ func (s *Server) Start(_ context.Context, host component.Host) error { var err error s.startTimestamp = time.Now() - s.httpServer, err = s.httpSettings.ToServer(host, s.telemetry, s.mux) + s.httpServer, err = s.httpConfig.ToServer(host, s.telemetry, s.mux) if err != nil { return err } - ln, err := s.httpSettings.ToListener() + ln, err := s.httpConfig.ToListener() if err != nil { - return fmt.Errorf("failed to bind to address %s: %w", s.httpSettings.Endpoint, err) + return fmt.Errorf("failed to bind to address %s: %w", s.httpConfig.Endpoint, err) } go func() { diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go index 2eadb7b6ea553..3d0448f1df59d 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -56,22 +56,22 @@ func TestStatus(t *testing.T) { metrics := testhelpers.NewPipelineMetadata("metrics") tests := []struct { - name string - settings *Settings - legacySettings LegacySettings - componentHealthSettings *common.ComponentHealthSettings - pipelines map[string]*testhelpers.PipelineMetadata - teststeps []teststep + name string + config *Config + legacyConfig LegacyConfig + componentHealthConfig *common.ComponentHealthConfig + pipelines map[string]*testhelpers.PipelineMetadata + teststeps []teststep }{ { - name: "exclude recoverable and permanent errors", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "exclude recoverable and permanent errors", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, @@ -302,14 +302,14 @@ func TestStatus(t *testing.T) { }, }, { - name: "exclude recoverable and permanent errors - verbose", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "exclude recoverable and permanent errors - verbose", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, @@ -1023,19 +1023,19 @@ func TestStatus(t *testing.T) { }, }, { - name: "include recoverable and exclude permanent errors", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "include recoverable and exclude permanent errors", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, }, - componentHealthSettings: &common.ComponentHealthSettings{ + componentHealthConfig: &common.ComponentHealthConfig{ IncludePermanent: false, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -1268,19 +1268,19 @@ func TestStatus(t *testing.T) { }, }, { - name: "include recoverable and exclude permanent errors - verbose", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "include recoverable and exclude permanent errors - verbose", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, }, - componentHealthSettings: &common.ComponentHealthSettings{ + componentHealthConfig: &common.ComponentHealthConfig{ IncludePermanent: false, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -1881,19 +1881,19 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and exclude recoverable errors", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "include permanent and exclude recoverable errors", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, }, - componentHealthSettings: &common.ComponentHealthSettings{ + componentHealthConfig: &common.ComponentHealthConfig{ IncludePermanent: true, }, teststeps: []teststep{ @@ -2123,19 +2123,19 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and exclude recoverable errors - verbose", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "include permanent and exclude recoverable errors - verbose", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, }, - componentHealthSettings: &common.ComponentHealthSettings{ + componentHealthConfig: &common.ComponentHealthConfig{ IncludePermanent: true, }, teststeps: []teststep{ @@ -2734,19 +2734,19 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and recoverable errors", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "include permanent and recoverable errors", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, }, - componentHealthSettings: &common.ComponentHealthSettings{ + componentHealthConfig: &common.ComponentHealthConfig{ IncludePermanent: true, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -2979,19 +2979,19 @@ func TestStatus(t *testing.T) { }, }, { - name: "include permanent and recoverable errors - verbose", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "include permanent and recoverable errors - verbose", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, }, - componentHealthSettings: &common.ComponentHealthSettings{ + componentHealthConfig: &common.ComponentHealthConfig{ IncludePermanent: true, IncludeRecoverable: true, RecoveryDuration: 2 * time.Millisecond, @@ -3592,14 +3592,14 @@ func TestStatus(t *testing.T) { }, }, { - name: "pipeline non-existent", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "pipeline non-existent", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: true, Path: "/status", }, @@ -3620,14 +3620,14 @@ func TestStatus(t *testing.T) { }, }, { - name: "status disabled", - legacySettings: LegacySettings{UseV2Settings: true}, - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + name: "status disabled", + legacyConfig: LegacyConfig{UseV2: true}, + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{Enabled: false}, - Status: PathSettings{ + Config: PathConfig{Enabled: false}, + Status: PathConfig{ Enabled: false, }, }, @@ -3639,8 +3639,8 @@ func TestStatus(t *testing.T) { }, { name: "legacy - default response", - legacySettings: LegacySettings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + legacyConfig: LegacyConfig{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Path: "/status", @@ -3758,12 +3758,12 @@ func TestStatus(t *testing.T) { }, { name: "legacy - custom response", - legacySettings: LegacySettings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + legacyConfig: LegacyConfig{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, Path: "/status", - ResponseBody: &ResponseBodySettings{Healthy: "ALL OK", Unhealthy: "NOT OK"}, + ResponseBody: &ResponseBodyConfig{Healthy: "ALL OK", Unhealthy: "NOT OK"}, }, teststeps: []teststep{ { @@ -3881,21 +3881,21 @@ func TestStatus(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { server = NewServer( - tc.settings, - tc.legacySettings, - tc.componentHealthSettings, + tc.config, + tc.legacyConfig, + tc.componentHealthConfig, componenttest.NewNopTelemetrySettings(), - status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthSettings)), + status.NewAggregator(testhelpers.ErrPriority(tc.componentHealthConfig)), ) require.NoError(t, server.Start(context.Background(), componenttest.NewNopHost())) defer func() { require.NoError(t, server.Shutdown(context.Background())) }() var url string - if tc.legacySettings.UseV2Settings { - url = fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Status.Path) + if tc.legacyConfig.UseV2 { + url = fmt.Sprintf("http://%s%s", tc.config.Endpoint, tc.config.Status.Path) } else { - url = fmt.Sprintf("http://%s%s", tc.legacySettings.Endpoint, tc.legacySettings.Path) + url = fmt.Sprintf("http://%s%s", tc.legacyConfig.Endpoint, tc.legacyConfig.Path) } client := &http.Client{} @@ -3999,22 +3999,22 @@ func TestConfig(t *testing.T) { for _, tc := range []struct { name string - settings *Settings + config *Config setup func() expectedStatusCode int expectedBody []byte }{ { name: "config not notified", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{ + Config: PathConfig{ Enabled: true, Path: "/config", }, - Status: PathSettings{ + Status: PathConfig{ Enabled: false, }, }, @@ -4023,15 +4023,15 @@ func TestConfig(t *testing.T) { }, { name: "config notified", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{ + Config: PathConfig{ Enabled: true, Path: "/config", }, - Status: PathSettings{ + Status: PathConfig{ Enabled: false, }, }, @@ -4043,14 +4043,14 @@ func TestConfig(t *testing.T) { }, { name: "config disabled", - settings: &Settings{ - HTTPServerSettings: confighttp.HTTPServerSettings{ + config: &Config{ + ServerConfig: confighttp.ServerConfig{ Endpoint: testutil.GetAvailableLocalAddress(t), }, - Config: PathSettings{ + Config: PathConfig{ Enabled: false, }, - Status: PathSettings{ + Status: PathConfig{ Enabled: false, }, }, @@ -4060,9 +4060,9 @@ func TestConfig(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { server = NewServer( - tc.settings, - LegacySettings{UseV2Settings: true}, - &common.ComponentHealthSettings{}, + tc.config, + LegacyConfig{UseV2: true}, + &common.ComponentHealthConfig{}, componenttest.NewNopTelemetrySettings(), status.NewAggregator(status.PriorityPermanent), ) @@ -4071,7 +4071,7 @@ func TestConfig(t *testing.T) { defer func() { require.NoError(t, server.Shutdown(context.Background())) }() client := &http.Client{} - url := fmt.Sprintf("http://%s%s", tc.settings.Endpoint, tc.settings.Config.Path) + url := fmt.Sprintf("http://%s%s", tc.config.Endpoint, tc.config.Config.Path) if tc.setup != nil { tc.setup() diff --git a/extension/healthcheckextensionv2/internal/status/aggregation.go b/extension/healthcheckextensionv2/internal/status/aggregation.go index b176d641c5e5c..e5ff3b6048286 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregation.go +++ b/extension/healthcheckextensionv2/internal/status/aggregation.go @@ -9,7 +9,9 @@ import ( "go.opentelemetry.io/collector/component" ) -// statusEvent contains a status and timestamp, and can contain an error +// statusEvent contains a status and timestamp, and can contain an error. Note: +// this is duplicated from core because we need to be able to "rewrite" the +// timestamps of some events during aggregation. type statusEvent struct { status component.Status err error diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go index e40eaa371125f..0910dc9e211df 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -18,6 +18,9 @@ var ( extsIDMap = map[component.ID]struct{}{extsID: {}} ) +// Note: this interface had to be introduced because we need to be able to rewrite the +// timestamps of some events during aggregation. The implementation in core doesn't currently +// allow this, but this interface provides a workaround. type Event interface { Status() component.Status Err() error diff --git a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go index bf2137140bf5b..a6c12c1caac92 100644 --- a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go +++ b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go @@ -75,8 +75,8 @@ func SeedAggregator( } } -func ErrPriority(settings *common.ComponentHealthSettings) status.ErrorPriority { - if settings != nil && settings.IncludeRecoverable && !settings.IncludePermanent { +func ErrPriority(config *common.ComponentHealthConfig) status.ErrorPriority { + if config != nil && config.IncludeRecoverable && !config.IncludePermanent { return status.PriorityRecoverable } return status.PriorityPermanent diff --git a/extension/healthcheckextensionv2/testdata/config.yaml b/extension/healthcheckextensionv2/testdata/config.yaml index b7c0c0f4b7ea8..54dee58079797 100644 --- a/extension/healthcheckextensionv2/testdata/config.yaml +++ b/extension/healthcheckextensionv2/testdata/config.yaml @@ -1,5 +1,5 @@ healthcheckv2: -healthcheckv2/legacysettings: +healthcheckv2/legacyconfig: endpoint: "localhost:13" tls: ca_file: "/path/to/ca" @@ -15,7 +15,7 @@ healthcheckv2/invalidpath: endpoint: "localhost:13" path: "invalid" healthcheckv2/v2all: - use_v2_settings: true + use_v2: true http: grpc: component_health: @@ -23,7 +23,7 @@ healthcheckv2/v2all: include_recoverable_errors: true recovery_duration: 5m healthcheckv2/v2httpcustomized: - use_v2_settings: true + use_v2: true http: endpoint: "localhost:13" status: @@ -33,16 +33,16 @@ healthcheckv2/v2httpcustomized: enabled: true path: "/conf" healthcheckv2/v2httpmissingendpoint: - use_v2_settings: true + use_v2: true http: endpoint: "" healthcheckv2/v2grpccustomized: - use_v2_settings: true + use_v2: true grpc: endpoint: "localhost:13" healthcheckv2/v2grpcmissingendpoint: - use_v2_settings: true + use_v2: true grpc: endpoint: "" healthcheckv2/v2noprotocols: - use_v2_settings: true + use_v2: true From 8adc96cf103a159e9d8ee4fe770cd6c1f915e201 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Fri, 1 Mar 2024 16:39:18 -0800 Subject: [PATCH 15/26] Readme fusion --- extension/healthcheckextensionv2/README.md | 178 +++++++++++++----- .../internal/metadata/generated_status.go | 5 +- 2 files changed, 131 insertions(+), 52 deletions(-) diff --git a/extension/healthcheckextensionv2/README.md b/extension/healthcheckextensionv2/README.md index ddd47156c216b..5a1016bab5f85 100644 --- a/extension/healthcheckextensionv2/README.md +++ b/extension/healthcheckextensionv2/README.md @@ -1,4 +1,12 @@ -# Health Check Extension - V2 +# Health Check Extension + +> ⚠️⚠️⚠️ **Warning** ⚠️⚠️⚠️ +> +> The `check_collector_pipeline` feature of this extension was not working as expected and has been +> removed. The config remains for backwards compatibility, but it too will be removed in the future. +> Users wishing to monitor pipeline health should use the v2 functionality described below and +> opt-in to component health as described in +> [component health configuration](#component-health-config). | Status | | @@ -13,6 +21,39 @@ [contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib +## V1 + +Health Check Extension V1 enables an HTTP url that can be probed to check the +status of the OpenTelemetry Collector. This extension can be used as a +liveness and/or readiness probe on Kubernetes. + +The following settings are required: + +- `endpoint` (default = 0.0.0.0:13133): Address to publish the health check status. For full list of `ServerConfig` refer [here](https://github.com/open-telemetry/opentelemetry-collector/tree/main/config/confighttp). The `component.UseLocalHostAsDefaultHost` feature gate changes this to localhost:13133. This will become the default in a future release. +- `path` (default = "/"): Specifies the path to be configured for the health check server. +- `response_body` (default = ""): Specifies a static body that overrides the default response returned by the health check service. +- `check_collector_pipeline:` (deprecated and ignored): Settings of collector pipeline health check + - `enabled` (default = false): Whether enable collector pipeline check or not + - `interval` (default = "5m"): Time interval to check the number of failures + - `exporter_failure_threshold` (default = 5): The failure number threshold to mark + containers as healthy. + +Example: + +```yaml +extensions: + health_check: + health_check/1: + endpoint: "localhost:13" + tls: + ca_file: "/path/to/ca.crt" + cert_file: "/path/to/cert.crt" + key_file: "/path/to/key.key" + path: "/health/status" +``` + +## V2 + Health Check Extension - V2 provides HTTP and gRPC healthcheck services. The services can be used separately or together depending on your needs. The source of health for both services is component status reporting, a collector feature, that allows individual components to report their health via @@ -32,19 +73,25 @@ appropriate status codes for the protocol. | Stopping | The component is in the process of shutting down. | | Stopped | The component has completed shutdown. | -Adoption of status reporting by collector components is still a work in progress. As more components -report status, this extension will become more accurate and useful. +Note: Adoption of status reporting by collector components is still a work in progress. The accuracy +of this extension will improve as more components participate. -## Configuration +### Configuration -Below is sample configuration for both the HTTP and gRPC services. +Below is sample configuration for both the HTTP and gRPC services with component health opt-in. +Note, the `use_v2: true` setting is necessary during the interim while V1 functionality is +incrementally phased out. ```yaml extensions: healthcheckv2: - recovery_duration: 1m + use_v2: true + component_health: + include_permanent_errors: false + include_recoverable_errors: true + recovery_duration: 5m http: - endpoint: "127.0.0.1:13133" + endpoint: "localhost:13133" status: detailed: true enabled: true @@ -53,21 +100,37 @@ extensions: enabled: true path: "/health/config" grpc: - endpoint: "127.0.0.1:13132" + endpoint: "localhost:13132" transport: "tcp" ``` -### Recovery Duration +#### Component Health Config + +By default the Health Check Extension will not consider component error statuses as unhealthy. That +is, an error status will not be reflected in the response code of the health check, but it will be +available in the response body regardless of configuration. This behavior can be changed by opting +in to include recoverable and / or permanent errors. + +##### `include_permanent_errors` -Recovery duration is a setting shared by both the HTTP and gRPC services. Component status reporting -has a `RecoverableError` status to indicate a likely transient failure. The recovery duration is the -time given for a `RecoverableError` to recover before being considered unhealthy by the extension. -During the recovery duration a `RecoverableError` will be considered healthy. If the error does not -recover after the recovery duration has elapsed, it will be considered unhealthy. +To opt-in to permanent errors set `include_permanent_errors: true`. When true, a permanent error +will result in a non-ok return status. By definition, this is a permanent state, and one that will +require human intervention to fix. The collector is running, albeit in a degraded state, and +restarting is unlikely to fix the problem. Thus, caution should be used when enabling this setting +while using the extension as a liveness or readiness probe in k8s. -## HTTP Service +##### `include_recoverable_errors` and `recovery_duration` -### Status Endpoint +To opt-in recoverable errors set `include_recoverable_errors: true`. This setting works in tandem +with the `recovery_duration` option. When true, the Health Check Extension will consider a +recoverable error to be healthy until the recovery duration elapses, and unhealthy afterwards. +During the recovery duration an ok status will be returned. If the collector does not recover in +that time, a non-ok status will be returned. If the collector subsequently recovers, it will resume +reporting an ok status. + +### HTTP Service + +#### Status Endpoint The HTTP service provides a status endpoint that can be probed for overall collector status and per-pipeline status. The endpoint is located at `/status` by default, but can be configured using @@ -76,7 +139,7 @@ probe pipeline status, pass the pipeline name as a query parameter, e.g. `/statu The HTTP status code returned maps to the overall collector or pipeline status, with the mapping described below. -#### Mapping of Component Status to HTTP Status +##### Mapping of Component Status to HTTP Status Component statuses are aggregated into overall collector status and overall pipeline status. In each case, you can consider the aggregated status to be the sum of its parts. The mapping from component @@ -86,31 +149,41 @@ status to HTTP status is as follows: |-------------------|----------------------------------------------------------| | Starting | 503 - Service Unavailable | | OK | 200 - OK | -| RecoverableError | 200 when elapsed time < recovery duration; 500 otherwise | -| PermanentError | 500 - Internal Server Error | +| RecoverableError | 200 - OK1 | +| PermanentError | 200 - OK2 | | FatalError | 500 - Internal Server Error | | Stopping | 503 - Service Unavailable | | Stopped | 503 - Service Unavailable | +1. If `include_recoverable_errors: true`: 200 when elapsed time < recovery duration; 500 otherwise +2. If `include_permanent_errors: true`: 500 - Internal Server Error -#### Response Body +##### Response Body The response body contains either a detailed, or non-detailed view into collector or pipeline health in JSON format. The level of detail applies to the contents of the response body and is controlled -by the `http.status.detailed` configuration option. +by passing `verbose` as a query parameter. +###### Error Precedence -##### Collector Health +The response body contains either a partial or complete aggregate status in JSON format. The +aggregation process functions similar to a priority queue, where the most relevant status bubbles +to the top. By default, FatalError > PermanentError > RecoverableError, however, the priority of +RecoverableError and PermanentError will be reversed if `include_permanent_errors` is `false` and +and `include_recoverable_errors` is `true` as this configuration makes RecoverableErrors more +relevant. + +###### Collector Health The detailed response body for collector health will include the overall status for the collector, the overall status for each pipeline in the collector, and the statuses for the individual components in each pipeline. The non-detailed response will only contain the overall collector health. -###### Detailed Response Example +**Verbose Example** Assuming the health check extension is configured with `http.status.endpoint` set to -`127.0.0.1:13133` a request to `http:127.0.0.1:13133/status` will have a +`localhost:13133` a request to `http://localhost:13133/status?verbose` will have a response body such as: ```json @@ -184,16 +257,16 @@ response body such as: ``` Note the following based on this response: -- The overall status is `StatusRecoverableError` but the status healthy because - the recovery duration has not yet passed. +- The overall status is `StatusRecoverableError` but the status healthy because `include_recoverable_errors` + is set to `false` or it is `true` and the recovery duration has not yet passed. - `pipeline:metrics/grpc` has a matching status, as does `exporter:otlp/staging`. This implicates the exporter as the root cause for the pipeline and overall collector status. - `pipeline:traces/http` is completely healthy. -###### Non-detailed Response example +**Non-verbose Response example** -If the same request is made to a collector with `http.status.detailed` to `false`, you will only get -the overall status. The pipeline and component level statuses will be omitted. +If the same request is made to a collector without setting the verbose flag, only the overall status +will be returned. The pipeline and component level statuses will be omitted. ```json { @@ -212,11 +285,11 @@ collector response. It contains the overall status for the pipeline and the stat individual components. The non-detailed response body contains only the overall status for the pipeline. -###### Detailed Response Example +**Verbose Response Example** Assuming the health check extension is configured with `http.status.endpoint` set to -`127.0.0.1:13133` a request to `http:127.0.0.1:13133/status?pipeline=traces/http` will have a -response body such as: +`localhost:13133` a request to `http://localhost:13133/status?pipeline=traces/http&verbose` will have +a response body such as: ```json @@ -245,10 +318,10 @@ response body such as: } ``` -###### Non-detailed Response example +**Non-detailed Response Example** -If the same request is made to a collector with `http.status.detailed` to `false`, you will only get -the overall status. The component level statuses will be omitted. +If the same request is made without the verbose flag, only the overall pipline status will be +returned. The component level statuses will be omitted. ```json { @@ -259,14 +332,14 @@ the overall status. The component level statuses will be omitted. } ``` -### Collector Config Endpoint +#### Collector Config Endpoint The HTTP service optionally exposes an endpoint that provides the collector configuration. Note, the configuration returned is unfiltered and may contain sensitive information. As such, the configuration is disabled by default. Enable it using the `http.config.enabled` setting. By default the path will be `/config`, but it can be changed using the `http.config.path` setting. -### gRPC Service +#### gRPC Service The health check extension provides an implementation of the [grpc_health_v1 service]. The service was chosen for compatibility with existing gRPC health checks, however, it does not provide the @@ -274,24 +347,27 @@ additional detail available with the HTTP service. Additionally, the gRPC servic nuanced view of the world with only two reportable statuses: `HealthCheckResponse_SERVING` and `HealthCheckResponse_NOT_SERVING`. -#### Mapping of ComponentStatus to HealthCheckResponse_ServingStatus +##### Mapping of ComponentStatus to HealthCheckResponse_ServingStatus The HTTP and gRCP services use the same method of component status aggregation to derive overall collector health and pipeline health from individual status events. The component statuses map to the following `HealthCheckResponse_ServingStatus`es. -| Status | HealthCheckResponse_ServingStatus | -|-------------------|--------------------------------------------------------------------| -| Starting | HealthCheckResponse_NOT_SERVING | -| OK | HealthCheckResponse_SERVING | -| RecoverableError | HealthCheckResponse_SERVING when elapsed time < recovery duration; HealthCheckResponse_NOT_SERVING otherwise | -| PermanentError | HealthCheckResponse_NOT_SERVING | -| FatalError | HealthCheckResponse_NOT_SERVING | -| Stopping | HealthCheckResponse_NOT_SERVING | -| Stopped | HealthCheckResponse_NOT_SERVING | +| Status | HealthCheckResponse_ServingStatus | +|-------------------|------------------------------------------------| +| Starting | NOT_SERVING | +| OK | SERVING | +| RecoverableError | SERVING1 | +| PermanentError | SERVING2 | +| FatalError | NOT_SERVING | +| Stopping | NOT_SERVING | +| Stopped | NOT_SERVING | +1. If `include_recoverable_errors: true`: SERVING when elapsed time < recovery duration; NOT_SERVING + otherwise. +2. If `include_permanent_errors: true`: NOT_SERVING -#### HealthCheckRequest +##### HealthCheckRequest The gRPC service exposes two RPCs: `Check` and `Watch` (more about those below). Each takes a `HealthCheckRequest` argument. The `HealthCheckRequest` message is defined as: @@ -305,7 +381,7 @@ message HealthCheckRequest { To query for overall collector health, use the empty string `""` as the `service` name. To query for pipeline health, use the pipeline name as the `service`. -#### Check RPC +##### Check RPC The `Check` RPC is defined as: @@ -316,7 +392,7 @@ rpc Check(HealthCheckRequest) returns (HealthCheckResponse) If the service is unknown the RPC will return an error with status `NotFound`. Otherwise it will return a `HealthCheckResponse` with the serving status as mapped in the table above. -#### Watch Streaming RPC +##### Watch Streaming RPC The `Watch` RPC is defined as: @@ -330,7 +406,7 @@ the service is unknown, a response with a status of `HealthCheckResponse_SERVICE sent. The stream will remain open, and if and when the service starts reporting, its status will begin streaming. -### Future +#### Future There are plans to provide the ability to export status events as OTLP logs adhering to the event semantic conventions. diff --git a/extension/healthcheckextensionv2/internal/metadata/generated_status.go b/extension/healthcheckextensionv2/internal/metadata/generated_status.go index 0a5dc799fff52..50d64699ab8b0 100644 --- a/extension/healthcheckextensionv2/internal/metadata/generated_status.go +++ b/extension/healthcheckextensionv2/internal/metadata/generated_status.go @@ -8,8 +8,11 @@ import ( "go.opentelemetry.io/otel/trace" ) +var ( + Type = component.MustNewType("healthcheckv2") +) + const ( - Type = "healthcheckv2" ExtensionStability = component.StabilityLevelDevelopment ) From fce6ae1b71cdef249c245b5020f60aff4c3e6748 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Tue, 5 Mar 2024 13:29:31 -0800 Subject: [PATCH 16/26] Update extension/healthcheckextensionv2/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Juraci Paixão Kröhling --- extension/healthcheckextensionv2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/healthcheckextensionv2/README.md b/extension/healthcheckextensionv2/README.md index 5a1016bab5f85..26e8aaaff6002 100644 --- a/extension/healthcheckextensionv2/README.md +++ b/extension/healthcheckextensionv2/README.md @@ -170,7 +170,7 @@ The response body contains either a partial or complete aggregate status in JSON aggregation process functions similar to a priority queue, where the most relevant status bubbles to the top. By default, FatalError > PermanentError > RecoverableError, however, the priority of RecoverableError and PermanentError will be reversed if `include_permanent_errors` is `false` and -and `include_recoverable_errors` is `true` as this configuration makes RecoverableErrors more +`include_recoverable_errors` is `true` as this configuration makes RecoverableErrors more relevant. ###### Collector Health From 2c5670b5b23f684b1ccd6aa20897d12f6ab5eeef Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Tue, 5 Mar 2024 14:44:34 -0800 Subject: [PATCH 17/26] Address code review feedback - Words of caution on the readme - Remove prefix on errors - Log details on discarded events --- extension/healthcheckextensionv2/README.md | 6 ++++++ extension/healthcheckextensionv2/config.go | 8 ++++---- extension/healthcheckextensionv2/extension.go | 8 ++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/extension/healthcheckextensionv2/README.md b/extension/healthcheckextensionv2/README.md index 26e8aaaff6002..71e0f348cbc96 100644 --- a/extension/healthcheckextensionv2/README.md +++ b/extension/healthcheckextensionv2/README.md @@ -139,6 +139,9 @@ probe pipeline status, pass the pipeline name as a query parameter, e.g. `/statu The HTTP status code returned maps to the overall collector or pipeline status, with the mapping described below. +⚠️ Take care not to expose this endpoint on non-localhost ports as it contains the internal state +of the running collector. + ##### Mapping of Component Status to HTTP Status Component statuses are aggregated into overall collector status and overall pipeline status. In each @@ -339,6 +342,9 @@ the configuration returned is unfiltered and may contain sensitive information. configuration is disabled by default. Enable it using the `http.config.enabled` setting. By default the path will be `/config`, but it can be changed using the `http.config.path` setting. +⚠️ Take care not to expose this endpoint on non-localhost ports as it contains the unobfuscated +config of the running collector. + #### gRPC Service The health check extension provides an implementation of the [grpc_health_v1 service]. The service diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go index b1a4596e4fa62..fa2be737d9e98 100644 --- a/extension/healthcheckextensionv2/config.go +++ b/extension/healthcheckextensionv2/config.go @@ -21,10 +21,10 @@ const ( ) var ( - errMissingProtocol = errors.New("healthcheck extension: must be configured for HTTP or gRPC") - errGRPCEndpointRequired = errors.New("healthcheck extension: grpc endpoint required") - errHTTPEndpointRequired = errors.New("healthcheck extension: http endpoint required") - errInvalidPath = errors.New("healthcheck extension: path must start with /") + errMissingProtocol = errors.New("must specify at least one protocol") + errGRPCEndpointRequired = errors.New("grpc endpoint required") + errHTTPEndpointRequired = errors.New("http endpoint required") + errInvalidPath = errors.New("path must start with /") ) // Config has the configuration for the extension enabling the health check diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index 69eac77c7f646..a3842d8c91d75 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -202,8 +202,12 @@ func (hc *healthCheckExtension) eventLoop(ctx context.Context) { // After shutdown read late arriving events from channel and discard for { select { - case <-hc.eventCh: - hc.telemetry.Logger.Info("healthcheck: discarding event received after shutdown") + case esp := <-hc.eventCh: + hc.telemetry.Logger.Info( + "discarding event received after shutdown", + zap.Any("source", esp.source), + zap.Any("event", esp.event), + ) case <-ctx.Done(): return } From 2d23a470862fbc3cd7e515930fffdd8fe22f0cf2 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Tue, 5 Mar 2024 15:24:44 -0800 Subject: [PATCH 18/26] Address more feedback; additional cleanup --- extension/healthcheckextensionv2/extension.go | 18 ++------- .../internal/grpc/grpc.go | 37 +++++++++++-------- .../internal/grpc/grpc_test.go | 16 ++++---- .../internal/grpc/server.go | 2 +- .../internal/http/server_test.go | 1 - 5 files changed, 34 insertions(+), 40 deletions(-) diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go index a3842d8c91d75..c5447b8aad7f9 100644 --- a/extension/healthcheckextensionv2/extension.go +++ b/extension/healthcheckextensionv2/extension.go @@ -92,7 +92,7 @@ func newExtension( // Start implements the component.Component interface. func (hc *healthCheckExtension) Start(ctx context.Context, host component.Host) error { - hc.telemetry.Logger.Info("Starting health check extension V2", zap.Any("config", hc.config)) + hc.telemetry.Logger.Debug("Starting health check extension V2", zap.Any("config", hc.config)) for _, comp := range hc.subcomponents { if err := comp.Start(ctx, host); err != nil { @@ -141,24 +141,12 @@ func (hc *healthCheckExtension) NotifyConfig(ctx context.Context, conf *confmap. // Ready implements the extension.PipelineWatcher interface. func (hc *healthCheckExtension) Ready() error { close(hc.readyCh) - var err error - for _, comp := range hc.subcomponents { - if pw, ok := comp.(extension.PipelineWatcher); ok { - err = multierr.Append(err, pw.Ready()) - } - } - return err + return nil } // NotReady implements the extension.PipelineWatcher interface. func (hc *healthCheckExtension) NotReady() error { - var err error - for _, comp := range hc.subcomponents { - if pw, ok := comp.(extension.PipelineWatcher); ok { - err = multierr.Append(err, pw.NotReady()) - } - } - return err + return nil } func (hc *healthCheckExtension) eventLoop(ctx context.Context) { diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckextensionv2/internal/grpc/grpc.go index 4fd2f65f6255e..7aa611b172c2e 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc.go @@ -15,16 +15,23 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" ) -var statusToServingStatusMap = map[component.Status]healthpb.HealthCheckResponse_ServingStatus{ - component.StatusNone: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusStarting: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusOK: healthpb.HealthCheckResponse_SERVING, - component.StatusRecoverableError: healthpb.HealthCheckResponse_SERVING, - component.StatusPermanentError: healthpb.HealthCheckResponse_SERVING, - component.StatusFatalError: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusStopping: healthpb.HealthCheckResponse_NOT_SERVING, - component.StatusStopped: healthpb.HealthCheckResponse_NOT_SERVING, -} +var ( + errNotFound = grpcstatus.Error(codes.NotFound, "Service not found.") + errShuttingDown = grpcstatus.Error(codes.Canceled, "Server shutting down.") + errStreamSend = grpcstatus.Error(codes.Canceled, "Error sending; stream terminated.") + errStreamEnded = grpcstatus.Error(codes.Canceled, "Stream has ended.") + + statusToServingStatusMap = map[component.Status]healthpb.HealthCheckResponse_ServingStatus{ + component.StatusNone: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStarting: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusOK: healthpb.HealthCheckResponse_SERVING, + component.StatusRecoverableError: healthpb.HealthCheckResponse_SERVING, + component.StatusPermanentError: healthpb.HealthCheckResponse_SERVING, + component.StatusFatalError: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStopping: healthpb.HealthCheckResponse_NOT_SERVING, + component.StatusStopped: healthpb.HealthCheckResponse_NOT_SERVING, + } +) func (s *Server) Check( _ context.Context, @@ -32,7 +39,7 @@ func (s *Server) Check( ) (*healthpb.HealthCheckResponse, error) { st, ok := s.aggregator.AggregateStatus(status.Scope(req.Service), status.Concise) if !ok { - return nil, grpcstatus.Error(codes.NotFound, "unknown service") + return nil, errNotFound } return &healthpb.HealthCheckResponse{ @@ -52,7 +59,7 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ select { case st, ok := <-sub: if !ok { - return grpcstatus.Error(codes.Canceled, "Server shutting down.") + return errShuttingDown } var sst healthpb.HealthCheckResponse_ServingStatus @@ -90,7 +97,7 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ err := stream.Send(&healthpb.HealthCheckResponse{Status: sst}) if err != nil { - return grpcstatus.Error(codes.Canceled, "Stream has ended.") + return errStreamSend } case <-failureCh: failureTimer.Stop() @@ -105,10 +112,10 @@ func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthpb.Health_ }, ) if err != nil { - return grpcstatus.Error(codes.Canceled, "Stream has ended.") + return errStreamSend } case <-stream.Context().Done(): - return grpcstatus.Error(codes.Canceled, "Stream has ended.") + return errStreamEnded } } } diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go index e1c6a3a294a80..fd055fe7cefe3 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go @@ -65,11 +65,11 @@ func TestCheck(t *testing.T) { }, { service: traces.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { service: metrics.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { step: func() { @@ -217,11 +217,11 @@ func TestCheck(t *testing.T) { }, { service: traces.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { service: metrics.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { step: func() { @@ -391,11 +391,11 @@ func TestCheck(t *testing.T) { }, { service: traces.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { service: metrics.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { step: func() { @@ -548,11 +548,11 @@ func TestCheck(t *testing.T) { }, { service: traces.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { service: metrics.PipelineID.String(), - expectedErr: grpcstatus.Error(codes.NotFound, "unknown service"), + expectedErr: grpcstatus.Error(codes.NotFound, "Service not found."), }, { step: func() { diff --git a/extension/healthcheckextensionv2/internal/grpc/server.go b/extension/healthcheckextensionv2/internal/grpc/server.go index 6355b92950a71..185bcc0a4960d 100644 --- a/extension/healthcheckextensionv2/internal/grpc/server.go +++ b/extension/healthcheckextensionv2/internal/grpc/server.go @@ -55,7 +55,7 @@ func (s *Server) Start(_ context.Context, host component.Host) error { } healthpb.RegisterHealthServer(s.grpcServer, s) - ln, err := s.config.ToListenerContext(context.Background()) + ln, err := s.config.NetAddr.Listen(context.Background()) go func() { defer close(s.doneCh) diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckextensionv2/internal/http/server_test.go index 3d0448f1df59d..c69f6585c074f 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckextensionv2/internal/http/server_test.go @@ -3933,7 +3933,6 @@ func TestStatus(t *testing.T) { if ts.expectedComponentStatus != nil { st := &serializableStatus{} require.NoError(t, json.Unmarshal(body, st)) - fmt.Println(string(body)) if strings.Contains(ts.queryParams, "verbose") { assertStatusDetailed(t, ts.expectedComponentStatus, st) continue From 34a00f696827a48bc096f493842962c9fa4e8915 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Wed, 6 Mar 2024 16:57:35 -0800 Subject: [PATCH 19/26] Post rebase fixes --- .../healthcheckextensionv2/config_test.go | 4 +- extension/healthcheckextensionv2/factory.go | 2 +- .../healthcheckextensionv2/factory_test.go | 2 +- .../generated_component_test.go | 42 +++++ extension/healthcheckextensionv2/go.mod | 67 ++++---- extension/healthcheckextensionv2/go.sum | 146 +++++++++--------- .../internal/grpc/grpc_test.go | 4 +- .../internal/metadata/generated_status.go | 4 +- .../internal/status/aggregator.go | 2 +- .../internal/testhelpers/helpers.go | 18 +-- 10 files changed, 165 insertions(+), 126 deletions(-) create mode 100644 extension/healthcheckextensionv2/generated_component_test.go diff --git a/extension/healthcheckextensionv2/config_test.go b/extension/healthcheckextensionv2/config_test.go index 3d61c28c6bedd..a1239f111cfe5 100644 --- a/extension/healthcheckextensionv2/config_test.go +++ b/extension/healthcheckextensionv2/config_test.go @@ -100,7 +100,7 @@ func TestLoadConfig(t *testing.T) { }, GRPCConfig: &grpc.Config{ ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.NetAddr{ + NetAddr: confignet.AddrConfig{ Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), Transport: "tcp", }, @@ -154,7 +154,7 @@ func TestLoadConfig(t *testing.T) { }, GRPCConfig: &grpc.Config{ ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.NetAddr{ + NetAddr: confignet.AddrConfig{ Endpoint: "localhost:13", Transport: "tcp", }, diff --git a/extension/healthcheckextensionv2/factory.go b/extension/healthcheckextensionv2/factory.go index de902e76f64d6..81e914e5763e8 100644 --- a/extension/healthcheckextensionv2/factory.go +++ b/extension/healthcheckextensionv2/factory.go @@ -58,7 +58,7 @@ func createDefaultConfig() component.Config { }, GRPCConfig: &grpc.Config{ ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.NetAddr{ + NetAddr: confignet.AddrConfig{ Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), Transport: "tcp", }, diff --git a/extension/healthcheckextensionv2/factory_test.go b/extension/healthcheckextensionv2/factory_test.go index 56082f8ad6fde..278e11b819422 100644 --- a/extension/healthcheckextensionv2/factory_test.go +++ b/extension/healthcheckextensionv2/factory_test.go @@ -45,7 +45,7 @@ func TestCreateDefaultConfig(t *testing.T) { }, GRPCConfig: &grpc.Config{ ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.NetAddr{ + NetAddr: confignet.AddrConfig{ Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), Transport: "tcp", }, diff --git a/extension/healthcheckextensionv2/generated_component_test.go b/extension/healthcheckextensionv2/generated_component_test.go new file mode 100644 index 0000000000000..727627fe91cf1 --- /dev/null +++ b/extension/healthcheckextensionv2/generated_component_test.go @@ -0,0 +1,42 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package healthcheckextensionv2 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/extension/extensiontest" +) + +func TestComponentLifecycle(t *testing.T) { + factory := NewFactory() + + cm, err := confmaptest.LoadConf("metadata.yaml") + require.NoError(t, err) + cfg := factory.CreateDefaultConfig() + sub, err := cm.Sub("tests::config") + require.NoError(t, err) + require.NoError(t, component.UnmarshalConfig(sub, cfg)) + t.Run("shutdown", func(t *testing.T) { + e, err := factory.CreateExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + err = e.Shutdown(context.Background()) + require.NoError(t, err) + }) + t.Run("lifecycle", func(t *testing.T) { + firstExt, err := factory.CreateExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + require.NoError(t, firstExt.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, firstExt.Shutdown(context.Background())) + + secondExt, err := factory.CreateExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + require.NoError(t, secondExt.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, secondExt.Shutdown(context.Background())) + }) +} diff --git a/extension/healthcheckextensionv2/go.mod b/extension/healthcheckextensionv2/go.mod index d1643e0ef80ec..c8443d4fcb1bf 100644 --- a/extension/healthcheckextensionv2/go.mod +++ b/extension/healthcheckextensionv2/go.mod @@ -6,19 +6,19 @@ toolchain go1.21.4 require ( github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.95.0 - github.com/stretchr/testify v1.8.4 - go.opentelemetry.io/collector/component v0.95.0 - go.opentelemetry.io/collector/config/configgrpc v0.95.0 - go.opentelemetry.io/collector/config/confighttp v0.95.0 - go.opentelemetry.io/collector/config/confignet v0.95.0 - go.opentelemetry.io/collector/config/configtls v0.95.0 - go.opentelemetry.io/collector/confmap v0.95.0 - go.opentelemetry.io/collector/extension v0.95.0 - go.opentelemetry.io/otel/metric v1.23.1 - go.opentelemetry.io/otel/trace v1.23.1 + github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/collector/component v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/collector/config/configgrpc v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/collector/config/confighttp v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/collector/config/confignet v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/collector/config/configtls v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/collector/confmap v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/collector/extension v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/otel/metric v1.24.0 + go.opentelemetry.io/otel/trace v1.24.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.61.0 + google.golang.org/grpc v1.62.1 ) require ( @@ -34,40 +34,39 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/klauspost/compress v1.17.6 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/providers/confmap v0.1.0 // indirect github.com/knadh/koanf/v2 v2.1.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mostynb/go-grpc-compression v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.46.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rs/cors v1.10.1 // indirect - go.opentelemetry.io/collector v0.95.0 // indirect - go.opentelemetry.io/collector/config/configauth v0.95.0 // indirect - go.opentelemetry.io/collector/config/configcompression v0.95.0 // indirect - go.opentelemetry.io/collector/config/configopaque v1.2.0 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.95.0 // indirect - go.opentelemetry.io/collector/config/internal v0.95.0 // indirect - go.opentelemetry.io/collector/extension/auth v0.95.0 // indirect - go.opentelemetry.io/collector/featuregate v1.2.0 // indirect - go.opentelemetry.io/collector/pdata v1.2.0 // indirect + go.opentelemetry.io/collector v0.96.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/config/configauth v0.96.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/config/configcompression v0.96.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/config/configopaque v1.3.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.96.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/config/internal v0.96.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/extension/auth v0.96.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/featuregate v1.3.1-0.20240306115632-b2693620eff6 // indirect + go.opentelemetry.io/collector/pdata v1.3.1-0.20240306115632-b2693620eff6 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect - go.opentelemetry.io/otel v1.23.1 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.45.2 // indirect - go.opentelemetry.io/otel/sdk v1.23.1 // indirect - go.opentelemetry.io/otel/sdk/metric v1.23.1 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.46.0 // indirect + go.opentelemetry.io/otel/sdk v1.24.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.24.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/extension/healthcheckextensionv2/go.sum b/extension/healthcheckextensionv2/go.sum index 1c6ae55c069aa..5b22bf774cfda 100644 --- a/extension/healthcheckextensionv2/go.sum +++ b/extension/healthcheckextensionv2/go.sum @@ -6,12 +6,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -39,8 +39,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= -github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= @@ -53,8 +53,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= -github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -65,72 +63,72 @@ github.com/mostynb/go-grpc-compression v1.2.2 h1:XaDbnRvt2+1vgr0b/l0qh4mJAfIxE0b github.com/mostynb/go-grpc-compression v1.2.2/go.mod h1:GOCr2KBxXcblCuczg3YdLQlcin1/NfyDA348ckuCH6w= 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/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= -github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/collector v0.95.0 h1:DFW0BkF2sOocpA3NUPrbMeuPSN3PWxFBrLqs/Cxn3vo= -go.opentelemetry.io/collector v0.95.0/go.mod h1:Lc+VkOkSBppKqR/cAevS5oPrbtUO3JUbYzo91niRkG0= -go.opentelemetry.io/collector/component v0.95.0 h1:68tI7KVy1bfpaR83+LxUvjd9/hjDh78utklGl2t6eVM= -go.opentelemetry.io/collector/component v0.95.0/go.mod h1:SMt7r9zm0OOEKJF/ZUy8agD92OAXq2Xhq1FaTcfIWHw= -go.opentelemetry.io/collector/config/configauth v0.95.0 h1:peGXB3ctZW/B8PomTL7CoIpTy94V5TR3JX6fD8kakmU= -go.opentelemetry.io/collector/config/configauth v0.95.0/go.mod h1:80lAG02Mucz0JYdG+k0NIul/BQF5bUpeRGEndgg07lg= -go.opentelemetry.io/collector/config/configcompression v0.95.0 h1:G1ljjbXmh37JaePSM+2r0bRNlEoCg4l1M1UuhGgnXx0= -go.opentelemetry.io/collector/config/configcompression v0.95.0/go.mod h1:owL6s04LI1fPrNZvXiRm6o4B0jaxb3z/oFEcgrakFK4= -go.opentelemetry.io/collector/config/configgrpc v0.95.0 h1:LBB2wxtCiYhIVDMYYE7JmBZn56pXQmYolAxFGI2Cj3o= -go.opentelemetry.io/collector/config/configgrpc v0.95.0/go.mod h1:zjHif3wbSEtcu+o0qQXdtZZGftMfoMVEGwqxlREDdDk= -go.opentelemetry.io/collector/config/confighttp v0.95.0 h1:x/f3CQhQzei2WPoGkSa9x1D1qXfMkbfnhKoQL7VjGvw= -go.opentelemetry.io/collector/config/confighttp v0.95.0/go.mod h1:77imNR16GOaIEoomda3ysRTyaVzH4cQi27FtDknJrqw= -go.opentelemetry.io/collector/config/confignet v0.95.0 h1:GGAq0E7DFPIcwyq6h0OOPEfLyym/7LUIOB2ij6l3xtE= -go.opentelemetry.io/collector/config/confignet v0.95.0/go.mod h1:BVw5xkQ7TH2wH75cbph+dtOoxq1baWLuhdSYIAvuVu0= -go.opentelemetry.io/collector/config/configopaque v1.2.0 h1:ncnAuq4px3yREsirivGUbwr36xXEKa3K6JTOBNGlbtc= -go.opentelemetry.io/collector/config/configopaque v1.2.0/go.mod h1:6BAnSe6wok2Sg3tiNuapBbLnrduyMwzsBzbfgUSbDnI= -go.opentelemetry.io/collector/config/configtelemetry v0.95.0 h1:HabJZqbOAbNQ52L3v6usXoGXg1UKA1Ofs4Ytp5sGXEo= -go.opentelemetry.io/collector/config/configtelemetry v0.95.0/go.mod h1:tl8sI2RE3LSgJ0HjpadYpIwsKzw/CRA0nZUXLzMAZS0= -go.opentelemetry.io/collector/config/configtls v0.95.0 h1:LB6B5vCXwZV77jWPNhdvsgkyY8CFv8gdsRjfQS2Rioc= -go.opentelemetry.io/collector/config/configtls v0.95.0/go.mod h1:661iHIlhQTKIuUUEkQmuoyek1oq46XlsKjLu3JIzJrE= -go.opentelemetry.io/collector/config/internal v0.95.0 h1:jSifydX2KscDhVuTxGFdYtgWu+7AVKr9/7OCvBmS46Q= -go.opentelemetry.io/collector/config/internal v0.95.0/go.mod h1:KR0lphyXBwuB1DImzQDRVONoRTbwsGnH9degSG5018M= -go.opentelemetry.io/collector/confmap v0.95.0 h1:0oZwSUaeKTDCP7eewFpQSD+9SxXspiaJWjZDQYGGars= -go.opentelemetry.io/collector/confmap v0.95.0/go.mod h1:L3djzwpt+jL06wxnHAuy1jPUFcM+MdKGQAsz3B1d6pk= -go.opentelemetry.io/collector/consumer v0.95.0 h1:M/N5RDx8/6Hz5L1qWUXdtirtdoV8BEjIxCSdt6cCx+I= -go.opentelemetry.io/collector/consumer v0.95.0/go.mod h1:tM5aOolWS1zAByMbne2xVOkmVvZrF3VEKY6TkxhmkOs= -go.opentelemetry.io/collector/extension v0.95.0 h1:amE7zV/lfJRdCmZ4cqWmvBzZB5aQmIFIQqIuXmhaCjI= -go.opentelemetry.io/collector/extension v0.95.0/go.mod h1:IDt4B5GJxh/uJ/mUWxYZ+eHrJ49k4B8s8gJhuI0TRVI= -go.opentelemetry.io/collector/extension/auth v0.95.0 h1:Qu2/I6YXW1yhh+M5PZ6b5izPdzrh+bl8X7ewEeJQWGU= -go.opentelemetry.io/collector/extension/auth v0.95.0/go.mod h1:FSODnSbmqfRT9dQdJgogby7j4bcV8TmrhddTvcAjcjU= -go.opentelemetry.io/collector/featuregate v1.2.0 h1:nF8OGq5PsSNSLeuNwTWlOqThxbLW6v6DOCvSqQMc108= -go.opentelemetry.io/collector/featuregate v1.2.0/go.mod h1:mm8+xyQfgDmqhyegZRNIQmoKsNnDTwWKFLsdMoXAb7A= -go.opentelemetry.io/collector/pdata v1.2.0 h1:N6VdyEFYJyoHIKqHd0F372eNVD5b+AbH0ZQf7Z2jJ9I= -go.opentelemetry.io/collector/pdata v1.2.0/go.mod h1:mKXb6527Syb8PT4P9CZOJNbkuHOHjjGTZNNwSKESJhc= +go.opentelemetry.io/collector v0.96.1-0.20240306115632-b2693620eff6 h1:yaLqn47nYskiYqvIz+ixF4WgJCNmZYmvYgT1N79q2fc= +go.opentelemetry.io/collector v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:lNnB7h6qHClzeyZKauJdXB6wLhTq5sFvt1G70zTYmTs= +go.opentelemetry.io/collector/component v0.96.1-0.20240306115632-b2693620eff6 h1:hfSeafFTFnO+X0v9oDSnBQA8wKOYz99VfUSP/SE5cwk= +go.opentelemetry.io/collector/component v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:0evn//YPgN/5VmbbD4JS0yH3ikWxwROQN1MKEOM/U3M= +go.opentelemetry.io/collector/config/configauth v0.96.1-0.20240306115632-b2693620eff6 h1:7ObUwKMiL/gCFuTBLdBCwCDPKVx+Awv2kz0XV9PSd7k= +go.opentelemetry.io/collector/config/configauth v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:ivhsOgauQNlgpWLEYSGE7ProeF8hbqTY/mLHhq01VRI= +go.opentelemetry.io/collector/config/configcompression v0.96.1-0.20240306115632-b2693620eff6 h1:AA/eiK/rFUYRhkpqGwYHJLO3LwBy9tw4rOgWxy/KQAU= +go.opentelemetry.io/collector/config/configcompression v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:O0fOPCADyGwGLLIf5lf7N3960NsnIfxsm6dr/mIpL+M= +go.opentelemetry.io/collector/config/configgrpc v0.96.1-0.20240306115632-b2693620eff6 h1:DMOKDOm9pyMxV6YfRuNgFzmwx9Hf+9rt6+0xpA6KFag= +go.opentelemetry.io/collector/config/configgrpc v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:HJxNlsE3XjuxJ3r+Mxvh0vBKHBlUS34CQ1ASuv3IuP8= +go.opentelemetry.io/collector/config/confighttp v0.96.1-0.20240306115632-b2693620eff6 h1:BAxajIrLoPocBZGBRYk4reHz1SyTNzFXmjqDJNhyG4I= +go.opentelemetry.io/collector/config/confighttp v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:IAayU6jxbSsvxLv4o13F5FiXqHWPQYo8trFI8gPMPl8= +go.opentelemetry.io/collector/config/confignet v0.96.1-0.20240306115632-b2693620eff6 h1:m0/XnbEB5f5IIdyTkj838ws6YfFynFxHp5D13k9qPn4= +go.opentelemetry.io/collector/config/confignet v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:3naWoPss70RhDHhYjGACi7xh4NcVRvs9itzIRVWyu1k= +go.opentelemetry.io/collector/config/configopaque v1.3.1-0.20240306115632-b2693620eff6 h1:WKbHuoWXz9Y72MS7OgAMH/NibzFrpHYKQuHpx2EH5VA= +go.opentelemetry.io/collector/config/configopaque v1.3.1-0.20240306115632-b2693620eff6/go.mod h1:xhwF+gytUht4rqIeu60TA+WH7QExqCau9dI5FE6ZaDw= +go.opentelemetry.io/collector/config/configtelemetry v0.96.1-0.20240306115632-b2693620eff6 h1:HxhwEfAK3/8DIt+bjYcPkL9VrFTibOmpmHZOO0/buMs= +go.opentelemetry.io/collector/config/configtelemetry v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:YV5PaOdtnU1xRomPcYqoHmyCr48tnaAREeGO96EZw8o= +go.opentelemetry.io/collector/config/configtls v0.96.1-0.20240306115632-b2693620eff6 h1:6WZVo+mour549zDh0dw9iilTe0HOY75lyndo4IDKNPE= +go.opentelemetry.io/collector/config/configtls v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:4nJgllyzKMVOpcb1KIafRCnciGuuVGkQ8BqRaffupdQ= +go.opentelemetry.io/collector/config/internal v0.96.1-0.20240306115632-b2693620eff6 h1:RNvLuqfx3Nj9ilv8Y1yjv9PU2OsSYE/pJrNV8nhMZd4= +go.opentelemetry.io/collector/config/internal v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:0ZDYwZLmixzsIMp0F7Op9wVwRHrMp3HILhmnk/X6REg= +go.opentelemetry.io/collector/confmap v0.96.1-0.20240306115632-b2693620eff6 h1:nsskiJcF5iL6gYZ/jjkeIShdQZMbJFwOnR5BqctrAiI= +go.opentelemetry.io/collector/confmap v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:AnJmZcZoOLuykSXGiAf3shi11ZZk5ei4tZd9dDTTpWE= +go.opentelemetry.io/collector/consumer v0.96.0 h1:JN4JHelp5EGMGoC2UVelTMG6hyZjgtgdLLt5eZfVynU= +go.opentelemetry.io/collector/consumer v0.96.0/go.mod h1:Vn+qzzKgekDFayCVV8peSH5Btx1xrt/bmzD9gTxgidQ= +go.opentelemetry.io/collector/extension v0.96.1-0.20240306115632-b2693620eff6 h1:GAYQIFEq/Yy5fK1I+Qlv17wRV6By7DdPCiZcaiQEyTA= +go.opentelemetry.io/collector/extension v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:/PxTeHkU+8Ebouf/7Nk5NH+akNVGrSVlnjlw2vXKR6E= +go.opentelemetry.io/collector/extension/auth v0.96.1-0.20240306115632-b2693620eff6 h1:yYfso4crpirLoWtzARCuNnQwJlrfwfUNkDKNpproXp8= +go.opentelemetry.io/collector/extension/auth v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:oSbRWTzHAJm/Lb0VoK8GJ9FBOve/CaCpHnmQZRSkTT4= +go.opentelemetry.io/collector/featuregate v1.3.1-0.20240306115632-b2693620eff6 h1:WPX5pMQgNPvjLrtQ+XoBBsbyhy1m1JtYc1B/rIFhCnQ= +go.opentelemetry.io/collector/featuregate v1.3.1-0.20240306115632-b2693620eff6/go.mod h1:w7nUODKxEi3FLf1HslCiE6YWtMtOOrMnSwsDam8Mg9w= +go.opentelemetry.io/collector/pdata v1.3.1-0.20240306115632-b2693620eff6 h1:YV+WmEZfM0wv4pUtj5uJsLgC1lHgGp8WKhzyNphyX9Y= +go.opentelemetry.io/collector/pdata v1.3.1-0.20240306115632-b2693620eff6/go.mod h1:0Ttp4wQinhV5oJTd9MjyvUegmZBO9O0nrlh/+EDLw+Q= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= -go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= -go.opentelemetry.io/otel/exporters/prometheus v0.45.2 h1:pe2Jqk1K18As0RCw7J08QhgXNqr+6npx0a5W4IgAFA8= -go.opentelemetry.io/otel/exporters/prometheus v0.45.2/go.mod h1:B38pscHKI6bhFS44FDw0eFU3iqG3ASNIvY+fZgR5sAc= -go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= -go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= -go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= -go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= -go.opentelemetry.io/otel/sdk/metric v1.23.1 h1:T9/8WsYg+ZqIpMWwdISVVrlGb/N0Jr1OHjR/alpKwzg= -go.opentelemetry.io/otel/sdk/metric v1.23.1/go.mod h1:8WX6WnNtHCgUruJ4TJ+UssQjMtpxkpX0zveQC8JG/E0= -go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= -go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= +go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= +go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -146,8 +144,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -156,8 +154,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= @@ -172,14 +170,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= 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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go index fd055fe7cefe3..4e46144af0450 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go +++ b/extension/healthcheckextensionv2/internal/grpc/grpc_test.go @@ -31,7 +31,7 @@ func TestCheck(t *testing.T) { addr := testutil.GetAvailableLocalAddress(t) config := &Config{ ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.NetAddr{ + NetAddr: confignet.AddrConfig{ Endpoint: addr, Transport: "tcp", }, @@ -748,7 +748,7 @@ func TestWatch(t *testing.T) { addr := testutil.GetAvailableLocalAddress(t) config := &Config{ ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.NetAddr{ + NetAddr: confignet.AddrConfig{ Endpoint: addr, Transport: "tcp", }, diff --git a/extension/healthcheckextensionv2/internal/metadata/generated_status.go b/extension/healthcheckextensionv2/internal/metadata/generated_status.go index 50d64699ab8b0..7b253fdae7fb0 100644 --- a/extension/healthcheckextensionv2/internal/metadata/generated_status.go +++ b/extension/healthcheckextensionv2/internal/metadata/generated_status.go @@ -17,9 +17,9 @@ const ( ) func Meter(settings component.TelemetrySettings) metric.Meter { - return settings.MeterProvider.Meter("otelcol") + return settings.MeterProvider.Meter("github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2") } func Tracer(settings component.TelemetrySettings) trace.Tracer { - return settings.TracerProvider.Tracer("otelcol") + return settings.TracerProvider.Tracer("github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2") } diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckextensionv2/internal/status/aggregator.go index 0910dc9e211df..03c30816f0bb0 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckextensionv2/internal/status/aggregator.go @@ -14,7 +14,7 @@ import ( // Extensions are treated as a pseudo pipeline and extsID is used as a map key var ( - extsID = component.NewID("extensions") + extsID = component.MustNewID("extensions") extsIDMap = map[component.ID]struct{}{extsID: {}} ) diff --git a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go index a6c12c1caac92..fda59f3be1d3c 100644 --- a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go +++ b/extension/healthcheckextensionv2/internal/testhelpers/helpers.go @@ -25,26 +25,26 @@ func (p *PipelineMetadata) InstanceIDs() []*component.InstanceID { } // NewPipelineMetadata returns a metadata for a hypothetical pipeline. -func NewPipelineMetadata(typeVal component.Type) *PipelineMetadata { - pipelineID := component.NewID(typeVal) +func NewPipelineMetadata(typestr string) *PipelineMetadata { + pipelineID := component.MustNewID(typestr) return &PipelineMetadata{ PipelineID: pipelineID, ReceiverID: &component.InstanceID{ - ID: component.NewIDWithName(typeVal, "in"), + ID: component.NewIDWithName(component.MustNewType(typestr), "in"), Kind: component.KindReceiver, PipelineIDs: map[component.ID]struct{}{ pipelineID: {}, }, }, ProcessorID: &component.InstanceID{ - ID: component.NewID("batch"), + ID: component.MustNewID("batch"), Kind: component.KindProcessor, PipelineIDs: map[component.ID]struct{}{ pipelineID: {}, }, }, ExporterID: &component.InstanceID{ - ID: component.NewIDWithName(typeVal, "out"), + ID: component.NewIDWithName(component.MustNewType(typestr), "out"), Kind: component.KindExporter, PipelineIDs: map[component.ID]struct{}{ pipelineID: {}, @@ -54,10 +54,10 @@ func NewPipelineMetadata(typeVal component.Type) *PipelineMetadata { } // NewPipelines returns a map of hypothetical pipelines identified by their stringified typeVal. -func NewPipelines(typeVals ...component.Type) map[string]*PipelineMetadata { - result := make(map[string]*PipelineMetadata, len(typeVals)) - for _, val := range typeVals { - result[string(val)] = NewPipelineMetadata(val) +func NewPipelines(typestrs ...string) map[string]*PipelineMetadata { + result := make(map[string]*PipelineMetadata, len(typestrs)) + for _, typestr := range typestrs { + result[typestr] = NewPipelineMetadata(typestr) } return result } From cde65077ff12c143a8aa79b2c057d724b045862c Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 28 Mar 2024 11:24:50 -0700 Subject: [PATCH 20/26] Post rebase fixes --- .../healthcheckextensionv2/config_test.go | 4 +- extension/healthcheckextensionv2/go.mod | 35 ++++---- extension/healthcheckextensionv2/go.sum | 86 ++++++++----------- .../internal/grpc/server.go | 4 +- 4 files changed, 60 insertions(+), 69 deletions(-) diff --git a/extension/healthcheckextensionv2/config_test.go b/extension/healthcheckextensionv2/config_test.go index a1239f111cfe5..86d5fbc1f8dd4 100644 --- a/extension/healthcheckextensionv2/config_test.go +++ b/extension/healthcheckextensionv2/config_test.go @@ -49,8 +49,8 @@ func TestLoadConfig(t *testing.T) { LegacyConfig: http.LegacyConfig{ ServerConfig: confighttp.ServerConfig{ Endpoint: "localhost:13", - TLSSetting: &configtls.TLSServerSetting{ - TLSSetting: configtls.TLSSetting{ + TLSSetting: &configtls.ServerConfig{ + Config: configtls.Config{ CAFile: "/path/to/ca", CertFile: "/path/to/cert", KeyFile: "/path/to/key", diff --git a/extension/healthcheckextensionv2/go.mod b/extension/healthcheckextensionv2/go.mod index c8443d4fcb1bf..1c0b8e3ca3f69 100644 --- a/extension/healthcheckextensionv2/go.mod +++ b/extension/healthcheckextensionv2/go.mod @@ -7,13 +7,13 @@ toolchain go1.21.4 require ( github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.95.0 github.com/stretchr/testify v1.9.0 - go.opentelemetry.io/collector/component v0.96.1-0.20240306115632-b2693620eff6 - go.opentelemetry.io/collector/config/configgrpc v0.96.1-0.20240306115632-b2693620eff6 - go.opentelemetry.io/collector/config/confighttp v0.96.1-0.20240306115632-b2693620eff6 - go.opentelemetry.io/collector/config/confignet v0.96.1-0.20240306115632-b2693620eff6 - go.opentelemetry.io/collector/config/configtls v0.96.1-0.20240306115632-b2693620eff6 - go.opentelemetry.io/collector/confmap v0.96.1-0.20240306115632-b2693620eff6 - go.opentelemetry.io/collector/extension v0.96.1-0.20240306115632-b2693620eff6 + go.opentelemetry.io/collector/component v0.97.1-0.20240327181407-1038b67c85a0 + go.opentelemetry.io/collector/config/configgrpc v0.97.1-0.20240327181407-1038b67c85a0 + go.opentelemetry.io/collector/config/confighttp v0.97.1-0.20240327181407-1038b67c85a0 + go.opentelemetry.io/collector/config/confignet v0.97.1-0.20240327181407-1038b67c85a0 + go.opentelemetry.io/collector/config/configtls v0.97.1-0.20240327181407-1038b67c85a0 + go.opentelemetry.io/collector/confmap v0.97.1-0.20240327181407-1038b67c85a0 + go.opentelemetry.io/collector/extension v0.97.1-0.20240327181407-1038b67c85a0 go.opentelemetry.io/otel/metric v1.24.0 go.opentelemetry.io/otel/trace v1.24.0 go.uber.org/multierr v1.11.0 @@ -33,6 +33,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/klauspost/compress v1.17.7 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect @@ -47,16 +48,16 @@ require ( github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rs/cors v1.10.1 // indirect - go.opentelemetry.io/collector v0.96.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/config/configauth v0.96.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/config/configcompression v0.96.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/config/configopaque v1.3.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.96.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/config/internal v0.96.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/extension/auth v0.96.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/featuregate v1.3.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/collector/pdata v1.3.1-0.20240306115632-b2693620eff6 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/collector v0.97.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/config/configauth v0.97.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/config/configcompression v1.4.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/config/configopaque v1.4.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.97.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/config/internal v0.97.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/extension/auth v0.97.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/featuregate v1.4.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/collector/pdata v1.4.1-0.20240327181407-1038b67c85a0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.46.0 // indirect diff --git a/extension/healthcheckextensionv2/go.sum b/extension/healthcheckextensionv2/go.sum index 5b22bf774cfda..e901c28244e93 100644 --- a/extension/healthcheckextensionv2/go.sum +++ b/extension/healthcheckextensionv2/go.sum @@ -1,17 +1,9 @@ -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= -cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68 h1:aRVqY1p2IJaBGStWMsQMpkAa83cPkCDLl80eOj0Rbz4= -cloud.google.com/go/compute/metadata v0.2.4-0.20230617002413-005d2dfb6b68/go.mod h1:1a3eRNYX12fs5UABBIXS8HXVvQbX9hRB/RkEBPORpe8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= -github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -33,6 +25,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -79,42 +73,42 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/collector v0.96.1-0.20240306115632-b2693620eff6 h1:yaLqn47nYskiYqvIz+ixF4WgJCNmZYmvYgT1N79q2fc= -go.opentelemetry.io/collector v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:lNnB7h6qHClzeyZKauJdXB6wLhTq5sFvt1G70zTYmTs= -go.opentelemetry.io/collector/component v0.96.1-0.20240306115632-b2693620eff6 h1:hfSeafFTFnO+X0v9oDSnBQA8wKOYz99VfUSP/SE5cwk= -go.opentelemetry.io/collector/component v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:0evn//YPgN/5VmbbD4JS0yH3ikWxwROQN1MKEOM/U3M= -go.opentelemetry.io/collector/config/configauth v0.96.1-0.20240306115632-b2693620eff6 h1:7ObUwKMiL/gCFuTBLdBCwCDPKVx+Awv2kz0XV9PSd7k= -go.opentelemetry.io/collector/config/configauth v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:ivhsOgauQNlgpWLEYSGE7ProeF8hbqTY/mLHhq01VRI= -go.opentelemetry.io/collector/config/configcompression v0.96.1-0.20240306115632-b2693620eff6 h1:AA/eiK/rFUYRhkpqGwYHJLO3LwBy9tw4rOgWxy/KQAU= -go.opentelemetry.io/collector/config/configcompression v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:O0fOPCADyGwGLLIf5lf7N3960NsnIfxsm6dr/mIpL+M= -go.opentelemetry.io/collector/config/configgrpc v0.96.1-0.20240306115632-b2693620eff6 h1:DMOKDOm9pyMxV6YfRuNgFzmwx9Hf+9rt6+0xpA6KFag= -go.opentelemetry.io/collector/config/configgrpc v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:HJxNlsE3XjuxJ3r+Mxvh0vBKHBlUS34CQ1ASuv3IuP8= -go.opentelemetry.io/collector/config/confighttp v0.96.1-0.20240306115632-b2693620eff6 h1:BAxajIrLoPocBZGBRYk4reHz1SyTNzFXmjqDJNhyG4I= -go.opentelemetry.io/collector/config/confighttp v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:IAayU6jxbSsvxLv4o13F5FiXqHWPQYo8trFI8gPMPl8= -go.opentelemetry.io/collector/config/confignet v0.96.1-0.20240306115632-b2693620eff6 h1:m0/XnbEB5f5IIdyTkj838ws6YfFynFxHp5D13k9qPn4= -go.opentelemetry.io/collector/config/confignet v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:3naWoPss70RhDHhYjGACi7xh4NcVRvs9itzIRVWyu1k= -go.opentelemetry.io/collector/config/configopaque v1.3.1-0.20240306115632-b2693620eff6 h1:WKbHuoWXz9Y72MS7OgAMH/NibzFrpHYKQuHpx2EH5VA= -go.opentelemetry.io/collector/config/configopaque v1.3.1-0.20240306115632-b2693620eff6/go.mod h1:xhwF+gytUht4rqIeu60TA+WH7QExqCau9dI5FE6ZaDw= -go.opentelemetry.io/collector/config/configtelemetry v0.96.1-0.20240306115632-b2693620eff6 h1:HxhwEfAK3/8DIt+bjYcPkL9VrFTibOmpmHZOO0/buMs= -go.opentelemetry.io/collector/config/configtelemetry v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:YV5PaOdtnU1xRomPcYqoHmyCr48tnaAREeGO96EZw8o= -go.opentelemetry.io/collector/config/configtls v0.96.1-0.20240306115632-b2693620eff6 h1:6WZVo+mour549zDh0dw9iilTe0HOY75lyndo4IDKNPE= -go.opentelemetry.io/collector/config/configtls v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:4nJgllyzKMVOpcb1KIafRCnciGuuVGkQ8BqRaffupdQ= -go.opentelemetry.io/collector/config/internal v0.96.1-0.20240306115632-b2693620eff6 h1:RNvLuqfx3Nj9ilv8Y1yjv9PU2OsSYE/pJrNV8nhMZd4= -go.opentelemetry.io/collector/config/internal v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:0ZDYwZLmixzsIMp0F7Op9wVwRHrMp3HILhmnk/X6REg= -go.opentelemetry.io/collector/confmap v0.96.1-0.20240306115632-b2693620eff6 h1:nsskiJcF5iL6gYZ/jjkeIShdQZMbJFwOnR5BqctrAiI= -go.opentelemetry.io/collector/confmap v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:AnJmZcZoOLuykSXGiAf3shi11ZZk5ei4tZd9dDTTpWE= -go.opentelemetry.io/collector/consumer v0.96.0 h1:JN4JHelp5EGMGoC2UVelTMG6hyZjgtgdLLt5eZfVynU= -go.opentelemetry.io/collector/consumer v0.96.0/go.mod h1:Vn+qzzKgekDFayCVV8peSH5Btx1xrt/bmzD9gTxgidQ= -go.opentelemetry.io/collector/extension v0.96.1-0.20240306115632-b2693620eff6 h1:GAYQIFEq/Yy5fK1I+Qlv17wRV6By7DdPCiZcaiQEyTA= -go.opentelemetry.io/collector/extension v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:/PxTeHkU+8Ebouf/7Nk5NH+akNVGrSVlnjlw2vXKR6E= -go.opentelemetry.io/collector/extension/auth v0.96.1-0.20240306115632-b2693620eff6 h1:yYfso4crpirLoWtzARCuNnQwJlrfwfUNkDKNpproXp8= -go.opentelemetry.io/collector/extension/auth v0.96.1-0.20240306115632-b2693620eff6/go.mod h1:oSbRWTzHAJm/Lb0VoK8GJ9FBOve/CaCpHnmQZRSkTT4= -go.opentelemetry.io/collector/featuregate v1.3.1-0.20240306115632-b2693620eff6 h1:WPX5pMQgNPvjLrtQ+XoBBsbyhy1m1JtYc1B/rIFhCnQ= -go.opentelemetry.io/collector/featuregate v1.3.1-0.20240306115632-b2693620eff6/go.mod h1:w7nUODKxEi3FLf1HslCiE6YWtMtOOrMnSwsDam8Mg9w= -go.opentelemetry.io/collector/pdata v1.3.1-0.20240306115632-b2693620eff6 h1:YV+WmEZfM0wv4pUtj5uJsLgC1lHgGp8WKhzyNphyX9Y= -go.opentelemetry.io/collector/pdata v1.3.1-0.20240306115632-b2693620eff6/go.mod h1:0Ttp4wQinhV5oJTd9MjyvUegmZBO9O0nrlh/+EDLw+Q= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/collector v0.97.1-0.20240327181407-1038b67c85a0 h1:dvxsnQ+nBUX99/Sza/bYnW9lLmkb9Cm4fFqFlZk/cNs= +go.opentelemetry.io/collector v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:V6xquYAaO2VHVu4DBK28JYuikRdZajh7DH5Vl/Y8NiA= +go.opentelemetry.io/collector/component v0.97.1-0.20240327181407-1038b67c85a0 h1:OBXZrNlbQtCfpcqfVmKfsiqEKket/cHm61e4l2hfxuo= +go.opentelemetry.io/collector/component v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:F/m3HMlkb16RKI7wJjgbECK1IZkAcmB8bu7yD8XOkwM= +go.opentelemetry.io/collector/config/configauth v0.97.1-0.20240327181407-1038b67c85a0 h1:Me8HrbBgWBnEeBVcD/dI6qZ7LbdOWfs6s23hG30G/gU= +go.opentelemetry.io/collector/config/configauth v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:BkCDatBU7CXXStrRPE1b4woj2VLxaYEMg2WTkb50BlI= +go.opentelemetry.io/collector/config/configcompression v1.4.1-0.20240327181407-1038b67c85a0 h1:SoXZh6osv9ve5a7Wcr5fT/T2iJwpZMudtIuu3KkXkXo= +go.opentelemetry.io/collector/config/configcompression v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:O0fOPCADyGwGLLIf5lf7N3960NsnIfxsm6dr/mIpL+M= +go.opentelemetry.io/collector/config/configgrpc v0.97.1-0.20240327181407-1038b67c85a0 h1:Z6P8/9UBvMbjcdBCcHJmSs4GPUdTdjn78+3z0SeRTLI= +go.opentelemetry.io/collector/config/configgrpc v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:i8OrrxynYldlcZ6wPOUKNoZmmbUCDp3CzryRT+2mN7c= +go.opentelemetry.io/collector/config/confighttp v0.97.1-0.20240327181407-1038b67c85a0 h1:/KSUONuOMD2gYqZ1pV4kCYYxg486TL43CkHKhhCrfqI= +go.opentelemetry.io/collector/config/confighttp v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:wyg4yXvCsk1CsfPBWQ3+rZDThz44Q0d35/1lJBHj5VI= +go.opentelemetry.io/collector/config/confignet v0.97.1-0.20240327181407-1038b67c85a0 h1:EfSh528GosPVHOwHCqYl1ycnTj4rqTb9bVz99Br2wDs= +go.opentelemetry.io/collector/config/confignet v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:3naWoPss70RhDHhYjGACi7xh4NcVRvs9itzIRVWyu1k= +go.opentelemetry.io/collector/config/configopaque v1.4.1-0.20240327181407-1038b67c85a0 h1:nZkYYEdASkZ5L43eclvdWOnM8cB4Ga4FxPv+qLMaLHQ= +go.opentelemetry.io/collector/config/configopaque v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:7Qzo69x7i+FaNELeA9jmZtVvfnR5lE6JYa5YEOCJPFQ= +go.opentelemetry.io/collector/config/configtelemetry v0.97.1-0.20240327181407-1038b67c85a0 h1:n6gNCKxrCs3hD+jafL93JdtPVl05p+C5PecoNE7YUrw= +go.opentelemetry.io/collector/config/configtelemetry v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:YV5PaOdtnU1xRomPcYqoHmyCr48tnaAREeGO96EZw8o= +go.opentelemetry.io/collector/config/configtls v0.97.1-0.20240327181407-1038b67c85a0 h1:xc0wUkz0fFnoUdqtGyyoKMwDO1saOaGj60aunfk8lf0= +go.opentelemetry.io/collector/config/configtls v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:ev/fMI6hm1WTSHHEAEoVjF3RZj0qf38E/XO5itFku7k= +go.opentelemetry.io/collector/config/internal v0.97.1-0.20240327181407-1038b67c85a0 h1:YmVIDMQMC4x+W6fsdO3/6069/8rA1pycAwDydWpO0Lo= +go.opentelemetry.io/collector/config/internal v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:RVGDn9OH/KHT878cclG497/n2qxe54+zW+u/SVsRLNw= +go.opentelemetry.io/collector/confmap v0.97.1-0.20240327181407-1038b67c85a0 h1:Cm5WDKNnmKLZmiAzodv3LLodAN3fAZFl+Q6jek/K6xU= +go.opentelemetry.io/collector/confmap v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:AnJmZcZoOLuykSXGiAf3shi11ZZk5ei4tZd9dDTTpWE= +go.opentelemetry.io/collector/consumer v0.97.0 h1:S0BZQtJQxSHT156S8a5rLt3TeWYP8Rq+jn8QEyWQUYk= +go.opentelemetry.io/collector/consumer v0.97.0/go.mod h1:1D06LURiZ/1KA2OnuKNeSn9bvFmJ5ZWe6L8kLu0osSY= +go.opentelemetry.io/collector/extension v0.97.1-0.20240327181407-1038b67c85a0 h1:/3CkYzkiiAhoy8IGM+FUob4GGWzEb9JfN5J3RGn1ODM= +go.opentelemetry.io/collector/extension v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:jWNG0Npi7AxiqwCclToskDfCQuNKHYHlBPJNnIKHp84= +go.opentelemetry.io/collector/extension/auth v0.97.1-0.20240327181407-1038b67c85a0 h1:ctgHe7ZPXgjjE8s+1L3GndORCLD5Oq/ZVC6p4zzXRPc= +go.opentelemetry.io/collector/extension/auth v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:uElLYtzMPA48mu9baxGIH6lHpOn76NLe4mVHnmV+hEY= +go.opentelemetry.io/collector/featuregate v1.4.1-0.20240327181407-1038b67c85a0 h1:pZlwb1NvnMxlVU3WqSAz3gckK89T6PzfRhMbybtktn8= +go.opentelemetry.io/collector/featuregate v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:w7nUODKxEi3FLf1HslCiE6YWtMtOOrMnSwsDam8Mg9w= +go.opentelemetry.io/collector/pdata v1.4.1-0.20240327181407-1038b67c85a0 h1:ytj0zkWq3pCWCukjLdNgiLZx65lbzJaMIkEhA3w9imw= +go.opentelemetry.io/collector/pdata v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:0Ttp4wQinhV5oJTd9MjyvUegmZBO9O0nrlh/+EDLw+Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= @@ -146,8 +140,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ= -golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= 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= @@ -168,8 +160,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= diff --git a/extension/healthcheckextensionv2/internal/grpc/server.go b/extension/healthcheckextensionv2/internal/grpc/server.go index 185bcc0a4960d..7d5d04070d8c7 100644 --- a/extension/healthcheckextensionv2/internal/grpc/server.go +++ b/extension/healthcheckextensionv2/internal/grpc/server.go @@ -47,9 +47,9 @@ func NewServer( } // Start implements the component.Component interface. -func (s *Server) Start(_ context.Context, host component.Host) error { +func (s *Server) Start(ctx context.Context, host component.Host) error { var err error - s.grpcServer, err = s.config.ToServer(host, s.telemetry) + s.grpcServer, err = s.config.ToServer(ctx, host, s.telemetry) if err != nil { return err } From e52f4d17f086faf64b1854da52a86e1b1dd223e4 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Fri, 29 Mar 2024 08:49:20 -0700 Subject: [PATCH 21/26] Rename healthcheckextensionv2 -> healthcheckv2extension --- .chloggen/healthcheck-v2.yaml | 2 +- cmd/otelcontribcol/components.go | 8 - extension/healthcheckextensionv2/Makefile | 1 - extension/healthcheckextensionv2/README.md | 420 ------------------ extension/healthcheckextensionv2/config.go | 99 ----- .../healthcheckextensionv2/config_test.go | 192 -------- extension/healthcheckextensionv2/extension.go | 203 --------- extension/healthcheckextensionv2/factory.go | 73 --- .../healthcheckextensionv2/factory_test.go | 69 --- .../generated_component_test.go | 42 -- extension/healthcheckextensionv2/go.mod | 74 --- extension/healthcheckextensionv2/go.sum | 175 -------- .../internal/common/config.go | 16 - .../internal/grpc/config.go | 10 - .../internal/http/config.go | 65 --- .../internal/metadata/generated_status.go | 25 -- .../healthcheckextensionv2/metadata.yaml | 9 - .../testdata/config.yaml | 48 -- extension/healthcheckv2extension/extension.go | 177 +++++++- .../extension_test.go | 6 +- .../generated_component_test.go | 8 - .../internal/grpc/grpc.go | 4 +- .../internal/grpc/grpc_test.go | 6 +- .../internal/grpc/server.go | 6 +- .../internal/http/handlers.go | 4 +- .../internal/http/responders.go | 6 +- .../internal/http/serialization.go | 4 +- .../internal/http/server.go | 6 +- .../internal/http/server_test.go | 6 +- .../internal/http/testdata/config.json | 0 .../internal/http/testdata/config.yaml | 0 .../internal/status/aggregation.go | 2 +- .../internal/status/aggregation_test.go | 0 .../internal/status/aggregator.go | 2 +- .../internal/status/aggregator_test.go | 4 +- .../internal/testhelpers/helpers.go | 6 +- 36 files changed, 201 insertions(+), 1577 deletions(-) delete mode 100644 extension/healthcheckextensionv2/Makefile delete mode 100644 extension/healthcheckextensionv2/README.md delete mode 100644 extension/healthcheckextensionv2/config.go delete mode 100644 extension/healthcheckextensionv2/config_test.go delete mode 100644 extension/healthcheckextensionv2/extension.go delete mode 100644 extension/healthcheckextensionv2/factory.go delete mode 100644 extension/healthcheckextensionv2/factory_test.go delete mode 100644 extension/healthcheckextensionv2/generated_component_test.go delete mode 100644 extension/healthcheckextensionv2/go.mod delete mode 100644 extension/healthcheckextensionv2/go.sum delete mode 100644 extension/healthcheckextensionv2/internal/common/config.go delete mode 100644 extension/healthcheckextensionv2/internal/grpc/config.go delete mode 100644 extension/healthcheckextensionv2/internal/http/config.go delete mode 100644 extension/healthcheckextensionv2/internal/metadata/generated_status.go delete mode 100644 extension/healthcheckextensionv2/metadata.yaml delete mode 100644 extension/healthcheckextensionv2/testdata/config.yaml rename extension/{healthcheckextensionv2 => healthcheckv2extension}/extension_test.go (97%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/grpc/grpc.go (97%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/grpc/grpc_test.go (99%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/grpc/server.go (92%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/http/handlers.go (89%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/http/responders.go (96%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/http/serialization.go (95%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/http/server.go (95%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/http/server_test.go (99%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/http/testdata/config.json (100%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/http/testdata/config.yaml (100%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/status/aggregation.go (98%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/status/aggregation_test.go (100%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/status/aggregator.go (99%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/status/aggregator_test.go (99%) rename extension/{healthcheckextensionv2 => healthcheckv2extension}/internal/testhelpers/helpers.go (94%) diff --git a/.chloggen/healthcheck-v2.yaml b/.chloggen/healthcheck-v2.yaml index 4fd7e5a381161..674c89e0e47a2 100755 --- a/.chloggen/healthcheck-v2.yaml +++ b/.chloggen/healthcheck-v2.yaml @@ -4,7 +4,7 @@ change_type: 'new_component' # The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) -component: healthcheckextensionv2 +component: healthcheckv2extension # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: Health Check Extension V2 is meant to be a replacement for the current Health Check Extension. It is based off of component status reporting and provides HTTP and gRPC services health check services. diff --git a/cmd/otelcontribcol/components.go b/cmd/otelcontribcol/components.go index 6f3096dedfd7c..760cc2346b5b6 100644 --- a/cmd/otelcontribcol/components.go +++ b/cmd/otelcontribcol/components.go @@ -85,11 +85,7 @@ import ( googleclientauthextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/googleclientauthextension" headerssetterextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/headerssetterextension" healthcheckextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextension" -<<<<<<< HEAD healthcheckv2extension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension" -======= - healthcheckextensionv2 "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" ->>>>>>> 9e2ab3fdae (Introduce health check extension based on component status reporting) httpforwarderextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/httpforwarderextension" jaegerremotesampling "github.com/open-telemetry/opentelemetry-collector-contrib/extension/jaegerremotesampling" oauth2clientauthextension "github.com/open-telemetry/opentelemetry-collector-contrib/extension/oauth2clientauthextension" @@ -230,11 +226,7 @@ func components() (otelcol.Factories, error) { googleclientauthextension.NewFactory(), headerssetterextension.NewFactory(), healthcheckextension.NewFactory(), -<<<<<<< HEAD healthcheckv2extension.NewFactory(), -======= - healthcheckextensionv2.NewFactory(), ->>>>>>> 9e2ab3fdae (Introduce health check extension based on component status reporting) httpforwarderextension.NewFactory(), jaegerremotesampling.NewFactory(), oauth2clientauthextension.NewFactory(), diff --git a/extension/healthcheckextensionv2/Makefile b/extension/healthcheckextensionv2/Makefile deleted file mode 100644 index ded7a36092dc3..0000000000000 --- a/extension/healthcheckextensionv2/Makefile +++ /dev/null @@ -1 +0,0 @@ -include ../../Makefile.Common diff --git a/extension/healthcheckextensionv2/README.md b/extension/healthcheckextensionv2/README.md deleted file mode 100644 index 71e0f348cbc96..0000000000000 --- a/extension/healthcheckextensionv2/README.md +++ /dev/null @@ -1,420 +0,0 @@ -# Health Check Extension - -> ⚠️⚠️⚠️ **Warning** ⚠️⚠️⚠️ -> -> The `check_collector_pipeline` feature of this extension was not working as expected and has been -> removed. The config remains for backwards compatibility, but it too will be removed in the future. -> Users wishing to monitor pipeline health should use the v2 functionality described below and -> opt-in to component health as described in -> [component health configuration](#component-health-config). - - -| Status | | -| ------------- |-----------| -| Stability | [development] | -| Distributions | [core], [contrib] | -| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fhealthcheckextensionv2%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fhealthcheckextensionv2) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fhealthcheckextensionv2%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fhealthcheckextensionv2) | -| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@mwear](https://www.github.com/mwear) | - -[development]: https://github.com/open-telemetry/opentelemetry-collector#development -[core]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol -[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib - - -## V1 - -Health Check Extension V1 enables an HTTP url that can be probed to check the -status of the OpenTelemetry Collector. This extension can be used as a -liveness and/or readiness probe on Kubernetes. - -The following settings are required: - -- `endpoint` (default = 0.0.0.0:13133): Address to publish the health check status. For full list of `ServerConfig` refer [here](https://github.com/open-telemetry/opentelemetry-collector/tree/main/config/confighttp). The `component.UseLocalHostAsDefaultHost` feature gate changes this to localhost:13133. This will become the default in a future release. -- `path` (default = "/"): Specifies the path to be configured for the health check server. -- `response_body` (default = ""): Specifies a static body that overrides the default response returned by the health check service. -- `check_collector_pipeline:` (deprecated and ignored): Settings of collector pipeline health check - - `enabled` (default = false): Whether enable collector pipeline check or not - - `interval` (default = "5m"): Time interval to check the number of failures - - `exporter_failure_threshold` (default = 5): The failure number threshold to mark - containers as healthy. - -Example: - -```yaml -extensions: - health_check: - health_check/1: - endpoint: "localhost:13" - tls: - ca_file: "/path/to/ca.crt" - cert_file: "/path/to/cert.crt" - key_file: "/path/to/key.key" - path: "/health/status" -``` - -## V2 - -Health Check Extension - V2 provides HTTP and gRPC healthcheck services. The services can be used -separately or together depending on your needs. The source of health for both services is component -status reporting, a collector feature, that allows individual components to report their health via -`StatusEvent`s. The health check extension aggregates the component `StatusEvent`s into overall -collector health and pipeline health and exposes this data through its services. - -Below is a table enumerating component statuses and their meanings. These will be mapped to -appropriate status codes for the protocol. - -| Status | Meaning | -|-------------------|------------------------------------------------------------------------| -| Starting | The component is starting. | -| OK | The component is running without issue. | -| RecoverableError | The component has experienced a transient error and may recover. | -| PermanentError | The component has detected a condition at runtime that will need human intervention to fix. The collector will continue to run in a degraded mode. | -| FatalError | The collector has experienced a fatal runtime error and will shutdown. | -| Stopping | The component is in the process of shutting down. | -| Stopped | The component has completed shutdown. | - -Note: Adoption of status reporting by collector components is still a work in progress. The accuracy -of this extension will improve as more components participate. - -### Configuration - -Below is sample configuration for both the HTTP and gRPC services with component health opt-in. -Note, the `use_v2: true` setting is necessary during the interim while V1 functionality is -incrementally phased out. - -```yaml -extensions: - healthcheckv2: - use_v2: true - component_health: - include_permanent_errors: false - include_recoverable_errors: true - recovery_duration: 5m - http: - endpoint: "localhost:13133" - status: - detailed: true - enabled: true - path: "/health/status" - config: - enabled: true - path: "/health/config" - grpc: - endpoint: "localhost:13132" - transport: "tcp" -``` - -#### Component Health Config - -By default the Health Check Extension will not consider component error statuses as unhealthy. That -is, an error status will not be reflected in the response code of the health check, but it will be -available in the response body regardless of configuration. This behavior can be changed by opting -in to include recoverable and / or permanent errors. - -##### `include_permanent_errors` - -To opt-in to permanent errors set `include_permanent_errors: true`. When true, a permanent error -will result in a non-ok return status. By definition, this is a permanent state, and one that will -require human intervention to fix. The collector is running, albeit in a degraded state, and -restarting is unlikely to fix the problem. Thus, caution should be used when enabling this setting -while using the extension as a liveness or readiness probe in k8s. - -##### `include_recoverable_errors` and `recovery_duration` - -To opt-in recoverable errors set `include_recoverable_errors: true`. This setting works in tandem -with the `recovery_duration` option. When true, the Health Check Extension will consider a -recoverable error to be healthy until the recovery duration elapses, and unhealthy afterwards. -During the recovery duration an ok status will be returned. If the collector does not recover in -that time, a non-ok status will be returned. If the collector subsequently recovers, it will resume -reporting an ok status. - -### HTTP Service - -#### Status Endpoint - -The HTTP service provides a status endpoint that can be probed for overall collector status and -per-pipeline status. The endpoint is located at `/status` by default, but can be configured using -the `http.status.path` setting. Requests to `/status` will return the overall collector status. To -probe pipeline status, pass the pipeline name as a query parameter, e.g. `/status?pipeline=traces`. -The HTTP status code returned maps to the overall collector or pipeline status, with the mapping -described below. - -⚠️ Take care not to expose this endpoint on non-localhost ports as it contains the internal state -of the running collector. - -##### Mapping of Component Status to HTTP Status - -Component statuses are aggregated into overall collector status and overall pipeline status. In each -case, you can consider the aggregated status to be the sum of its parts. The mapping from component -status to HTTP status is as follows: - -| Status | HTTP Status Code | -|-------------------|----------------------------------------------------------| -| Starting | 503 - Service Unavailable | -| OK | 200 - OK | -| RecoverableError | 200 - OK1 | -| PermanentError | 200 - OK2 | -| FatalError | 500 - Internal Server Error | -| Stopping | 503 - Service Unavailable | -| Stopped | 503 - Service Unavailable | - -1. If `include_recoverable_errors: true`: 200 when elapsed time < recovery duration; 500 otherwise -2. If `include_permanent_errors: true`: 500 - Internal Server Error - -##### Response Body - -The response body contains either a detailed, or non-detailed view into collector or pipeline health -in JSON format. The level of detail applies to the contents of the response body and is controlled -by passing `verbose` as a query parameter. - -###### Error Precedence - -The response body contains either a partial or complete aggregate status in JSON format. The -aggregation process functions similar to a priority queue, where the most relevant status bubbles -to the top. By default, FatalError > PermanentError > RecoverableError, however, the priority of -RecoverableError and PermanentError will be reversed if `include_permanent_errors` is `false` and -`include_recoverable_errors` is `true` as this configuration makes RecoverableErrors more -relevant. - -###### Collector Health - -The detailed response body for collector health will include the overall status for the -collector, the overall status for each pipeline in the collector, and the statuses for the -individual components in each pipeline. The non-detailed response will only contain the overall -collector health. - -**Verbose Example** - -Assuming the health check extension is configured with `http.status.endpoint` set to -`localhost:13133` a request to `http://localhost:13133/status?verbose` will have a -response body such as: - -```json -{ - "start_time": "2024-01-18T17:27:12.570394-08:00", - "healthy": true, - "status": "StatusRecoverableError", - "error": "rpc error: code = ResourceExhausted desc = resource exhausted", - "status_time": "2024-01-18T17:27:32.572301-08:00", - "components": { - "extensions": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.570428-08:00", - "components": { - "extension:healthcheckv2": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.570428-08:00" - } - } - }, - "pipeline:metrics/grpc": { - "healthy": true, - "status": "StatusRecoverableError", - "error": "rpc error: code = ResourceExhausted desc = resource exhausted", - "status_time": "2024-01-18T17:27:32.572301-08:00", - "components": { - "exporter:otlp/staging": { - "healthy": true, - "status": "StatusRecoverableError", - "error": "rpc error: code = ResourceExhausted desc = resource exhausted", - "status_time": "2024-01-18T17:27:32.572301-08:00" - }, - "processor:batch": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571132-08:00" - }, - "receiver:otlp": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571576-08:00" - } - } - }, - "pipeline:traces/http": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571625-08:00", - "components": { - "exporter:otlphttp/staging": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571615-08:00" - }, - "processor:batch": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571621-08:00" - }, - "receiver:otlp": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571625-08:00" - } - } - } - } -} -``` - -Note the following based on this response: -- The overall status is `StatusRecoverableError` but the status healthy because `include_recoverable_errors` - is set to `false` or it is `true` and the recovery duration has not yet passed. -- `pipeline:metrics/grpc` has a matching status, as does `exporter:otlp/staging`. This implicates - the exporter as the root cause for the pipeline and overall collector status. -- `pipeline:traces/http` is completely healthy. - -**Non-verbose Response example** - -If the same request is made to a collector without setting the verbose flag, only the overall status -will be returned. The pipeline and component level statuses will be omitted. - -```json -{ - "start_time": "2024-01-18T17:39:15.87324-08:00", - "healthy": true, - "status": "StatusRecoverableError", - "error": "rpc error: code = ResourceExhausted desc = resource exhausted", - "status_time": "2024-01-18T17:39:35.875024-08:00" -} -``` - -###### Pipeline Health - -The detailed response body for pipeline health is essentially a zoomed in version of the detailed -collector response. It contains the overall status for the pipeline and the statuses of the -individual components. The non-detailed response body contains only the overall status for the -pipeline. - -**Verbose Response Example** - -Assuming the health check extension is configured with `http.status.endpoint` set to -`localhost:13133` a request to `http://localhost:13133/status?pipeline=traces/http&verbose` will have -a response body such as: - - -```json -{ - "start_time": "2024-01-18T17:27:12.570394-08:00", - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571625-08:00", - "components": { - "exporter:otlphttp/staging": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571615-08:00" - }, - "processor:batch": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571621-08:00" - }, - "receiver:otlp": { - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:27:12.571625-08:00" - } - } -} -``` - -**Non-detailed Response Example** - -If the same request is made without the verbose flag, only the overall pipline status will be -returned. The component level statuses will be omitted. - -```json -{ - "start_time": "2024-01-18T17:39:15.87324-08:00", - "healthy": true, - "status": "StatusOK", - "status_time": "2024-01-18T17:39:15.874236-08:00" -} -``` - -#### Collector Config Endpoint - -The HTTP service optionally exposes an endpoint that provides the collector configuration. Note, -the configuration returned is unfiltered and may contain sensitive information. As such, the -configuration is disabled by default. Enable it using the `http.config.enabled` setting. By -default the path will be `/config`, but it can be changed using the `http.config.path` setting. - -⚠️ Take care not to expose this endpoint on non-localhost ports as it contains the unobfuscated -config of the running collector. - -#### gRPC Service - -The health check extension provides an implementation of the [grpc_health_v1 service]. The service -was chosen for compatibility with existing gRPC health checks, however, it does not provide the -additional detail available with the HTTP service. Additionally, the gRPC service has a less -nuanced view of the world with only two reportable statuses: `HealthCheckResponse_SERVING` and -`HealthCheckResponse_NOT_SERVING`. - -##### Mapping of ComponentStatus to HealthCheckResponse_ServingStatus - -The HTTP and gRCP services use the same method of component status aggregation to derive -overall collector health and pipeline health from individual status events. The component -statuses map to the following `HealthCheckResponse_ServingStatus`es. - -| Status | HealthCheckResponse_ServingStatus | -|-------------------|------------------------------------------------| -| Starting | NOT_SERVING | -| OK | SERVING | -| RecoverableError | SERVING1 | -| PermanentError | SERVING2 | -| FatalError | NOT_SERVING | -| Stopping | NOT_SERVING | -| Stopped | NOT_SERVING | - -1. If `include_recoverable_errors: true`: SERVING when elapsed time < recovery duration; NOT_SERVING - otherwise. -2. If `include_permanent_errors: true`: NOT_SERVING - -##### HealthCheckRequest - -The gRPC service exposes two RPCs: `Check` and `Watch` (more about those below). Each takes a -`HealthCheckRequest` argument. The `HealthCheckRequest` message is defined as: - -```protobuf -message HealthCheckRequest { - string service = 1; -} -``` - -To query for overall collector health, use the empty string `""` as the `service` name. To query for -pipeline health, use the pipeline name as the `service`. - -##### Check RPC - -The `Check` RPC is defined as: - -```protobuf -rpc Check(HealthCheckRequest) returns (HealthCheckResponse) -``` - -If the service is unknown the RPC will return an error with status `NotFound`. Otherwise it will -return a `HealthCheckResponse` with the serving status as mapped in the table above. - -##### Watch Streaming RPC - -The `Watch` RPC is defined as: - -```protobuf -rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse) -``` - -The `Watch` RPC will initiate a stream for the given `service`. If the service is known at the time -the RPC is made, its current status will be sent and changes in status will be sent thereafter. If -the service is unknown, a response with a status of `HealthCheckResponse_SERVICE_UNKNOWN`` will be -sent. The stream will remain open, and if and when the service starts reporting, its status will -begin streaming. - -#### Future - -There are plans to provide the ability to export status events as OTLP logs adhering to the event -semantic conventions. - -[grpc_health_v1 service]: https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto \ No newline at end of file diff --git a/extension/healthcheckextensionv2/config.go b/extension/healthcheckextensionv2/config.go deleted file mode 100644 index fa2be737d9e98..0000000000000 --- a/extension/healthcheckextensionv2/config.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" - -import ( - "errors" - "strings" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/confmap" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" -) - -const ( - httpConfigKey = "http" - grpcConfigKey = "grpc" -) - -var ( - errMissingProtocol = errors.New("must specify at least one protocol") - errGRPCEndpointRequired = errors.New("grpc endpoint required") - errHTTPEndpointRequired = errors.New("http endpoint required") - errInvalidPath = errors.New("path must start with /") -) - -// Config has the configuration for the extension enabling the health check -// extension, used to report the health status of the service. -type Config struct { - // LegacyConfig contains the config for the existing healthcheck extension. - http.LegacyConfig `mapstructure:",squash"` - - // GRPCConfig is v2 config for the grpc healthcheck service. - GRPCConfig *grpc.Config `mapstructure:"grpc"` - - // HTTPConfig is v2 config for the http healthcheck service. - HTTPConfig *http.Config `mapstructure:"http"` - - // ComponentHealthConfig is v2 config shared between http and grpc services - ComponentHealthConfig *common.ComponentHealthConfig `mapstructure:"component_health"` -} - -var _ component.Config = (*Config)(nil) - -// Validate checks if the extension configuration is valid -func (c *Config) Validate() error { - if !c.UseV2 { - if c.LegacyConfig.Endpoint == "" { - return errHTTPEndpointRequired - } - if !strings.HasPrefix(c.LegacyConfig.Path, "/") { - return errInvalidPath - } - return nil - } - - if c.GRPCConfig == nil && c.HTTPConfig == nil { - return errMissingProtocol - } - - if c.HTTPConfig != nil { - if c.HTTPConfig.Endpoint == "" { - return errHTTPEndpointRequired - } - if c.HTTPConfig.Status.Enabled && !strings.HasPrefix(c.HTTPConfig.Status.Path, "/") { - return errInvalidPath - } - if c.HTTPConfig.Config.Enabled && !strings.HasPrefix(c.HTTPConfig.Config.Path, "/") { - return errInvalidPath - } - } - - if c.GRPCConfig != nil && c.GRPCConfig.NetAddr.Endpoint == "" { - return errGRPCEndpointRequired - } - - return nil -} - -// Unmarshal a confmap.Conf into the config struct. -func (c *Config) Unmarshal(conf *confmap.Conf) error { - err := conf.Unmarshal(c) - if err != nil { - return err - } - - if !conf.IsSet(httpConfigKey) { - c.HTTPConfig = nil - } - - if !conf.IsSet(grpcConfigKey) { - c.GRPCConfig = nil - } - - return nil -} diff --git a/extension/healthcheckextensionv2/config_test.go b/extension/healthcheckextensionv2/config_test.go deleted file mode 100644 index 86d5fbc1f8dd4..0000000000000 --- a/extension/healthcheckextensionv2/config_test.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" - -import ( - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/config/configgrpc" - "go.opentelemetry.io/collector/config/confighttp" - "go.opentelemetry.io/collector/config/confignet" - "go.opentelemetry.io/collector/config/configtls" - "go.opentelemetry.io/collector/confmap/confmaptest" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/metadata" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/localhostgate" -) - -func TestLoadConfig(t *testing.T) { - t.Parallel() - - tests := []struct { - id component.ID - expected component.Config - expectedErr error - }{ - { - id: component.NewID(metadata.Type), - expected: &Config{ - LegacyConfig: http.LegacyConfig{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Path: "/", - }, - }, - }, - { - id: component.NewIDWithName(metadata.Type, "legacyconfig"), - expected: &Config{ - LegacyConfig: http.LegacyConfig{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: "localhost:13", - TLSSetting: &configtls.ServerConfig{ - Config: configtls.Config{ - CAFile: "/path/to/ca", - CertFile: "/path/to/cert", - KeyFile: "/path/to/key", - }, - }, - }, - CheckCollectorPipeline: &http.CheckCollectorPipelineConfig{ - Enabled: false, - Interval: "5m", - ExporterFailureThreshold: 5, - }, - Path: "/", - ResponseBody: nil, - }, - }, - }, - { - id: component.NewIDWithName(metadata.Type, "missingendpoint"), - expectedErr: errHTTPEndpointRequired, - }, - { - id: component.NewIDWithName(metadata.Type, "invalidpath"), - expectedErr: errInvalidPath, - }, - { - id: component.NewIDWithName(metadata.Type, "v2all"), - expected: &Config{ - LegacyConfig: http.LegacyConfig{ - UseV2: true, - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Path: "/", - }, - HTTPConfig: &http.Config{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Status: http.PathConfig{ - Enabled: true, - Path: "/status", - }, - Config: http.PathConfig{ - Enabled: false, - Path: "/config", - }, - }, - GRPCConfig: &grpc.Config{ - ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.AddrConfig{ - Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), - Transport: "tcp", - }, - }, - }, - ComponentHealthConfig: &common.ComponentHealthConfig{ - IncludePermanent: true, - IncludeRecoverable: true, - RecoveryDuration: 5 * time.Minute, - }, - }, - }, - { - id: component.NewIDWithName(metadata.Type, "v2httpcustomized"), - expected: &Config{ - LegacyConfig: http.LegacyConfig{ - UseV2: true, - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Path: "/", - }, - HTTPConfig: &http.Config{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: "localhost:13", - }, - Status: http.PathConfig{ - Enabled: true, - Path: "/health", - }, - Config: http.PathConfig{ - Enabled: true, - Path: "/conf", - }, - }, - }, - }, - { - id: component.NewIDWithName(metadata.Type, "v2httpmissingendpoint"), - expectedErr: errHTTPEndpointRequired, - }, - { - id: component.NewIDWithName(metadata.Type, "v2grpccustomized"), - expected: &Config{ - LegacyConfig: http.LegacyConfig{ - UseV2: true, - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Path: "/", - }, - GRPCConfig: &grpc.Config{ - ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.AddrConfig{ - Endpoint: "localhost:13", - Transport: "tcp", - }, - }, - }, - }, - }, - { - id: component.NewIDWithName(metadata.Type, "v2grpcmissingendpoint"), - expectedErr: errGRPCEndpointRequired, - }, - { - id: component.NewIDWithName(metadata.Type, "v2noprotocols"), - expectedErr: errMissingProtocol, - }, - } - - for _, tt := range tests { - t.Run(tt.id.String(), func(t *testing.T) { - cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) - require.NoError(t, err) - factory := NewFactory() - cfg := factory.CreateDefaultConfig() - sub, err := cm.Sub(tt.id.String()) - require.NoError(t, err) - require.NoError(t, component.UnmarshalConfig(sub, cfg)) - if tt.expectedErr != nil { - assert.ErrorIs(t, component.ValidateConfig(cfg), tt.expectedErr) - return - } - assert.NoError(t, component.ValidateConfig(cfg)) - assert.Equal(t, tt.expected, cfg) - }) - } -} diff --git a/extension/healthcheckextensionv2/extension.go b/extension/healthcheckextensionv2/extension.go deleted file mode 100644 index c5447b8aad7f9..0000000000000 --- a/extension/healthcheckextensionv2/extension.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" - -import ( - "context" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/confmap" - "go.opentelemetry.io/collector/extension" - "go.uber.org/multierr" - "go.uber.org/zap" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" -) - -type eventSourcePair struct { - source *component.InstanceID - event *component.StatusEvent -} - -type healthCheckExtension struct { - config Config - telemetry component.TelemetrySettings - aggregator *status.Aggregator - subcomponents []component.Component - eventCh chan *eventSourcePair - readyCh chan struct{} - doneCh chan struct{} -} - -var _ component.Component = (*healthCheckExtension)(nil) -var _ extension.ConfigWatcher = (*healthCheckExtension)(nil) -var _ extension.PipelineWatcher = (*healthCheckExtension)(nil) - -func newExtension( - ctx context.Context, - config Config, - set extension.CreateSettings, -) *healthCheckExtension { - var comps []component.Component - - errPriority := status.PriorityPermanent - if config.ComponentHealthConfig != nil && - config.ComponentHealthConfig.IncludeRecoverable && - !config.ComponentHealthConfig.IncludePermanent { - errPriority = status.PriorityRecoverable - } - - aggregator := status.NewAggregator(errPriority) - - if config.UseV2 && config.GRPCConfig != nil { - grpcServer := grpc.NewServer( - config.GRPCConfig, - config.ComponentHealthConfig, - set.TelemetrySettings, - aggregator, - ) - comps = append(comps, grpcServer) - } - - if !config.UseV2 || config.UseV2 && config.HTTPConfig != nil { - httpServer := http.NewServer( - config.HTTPConfig, - config.LegacyConfig, - config.ComponentHealthConfig, - set.TelemetrySettings, - aggregator, - ) - comps = append(comps, httpServer) - } - - hc := &healthCheckExtension{ - config: config, - subcomponents: comps, - telemetry: set.TelemetrySettings, - aggregator: aggregator, - eventCh: make(chan *eventSourcePair), - readyCh: make(chan struct{}), - doneCh: make(chan struct{}), - } - - // Start processing events in the background so that our status watcher doesn't - // block others before the extension starts. - go hc.eventLoop(ctx) - - return hc -} - -// Start implements the component.Component interface. -func (hc *healthCheckExtension) Start(ctx context.Context, host component.Host) error { - hc.telemetry.Logger.Debug("Starting health check extension V2", zap.Any("config", hc.config)) - - for _, comp := range hc.subcomponents { - if err := comp.Start(ctx, host); err != nil { - return err - } - } - - return nil -} - -// Shutdown implements the component.Component interface. -func (hc *healthCheckExtension) Shutdown(ctx context.Context) error { - // Preemptively send the stopped event, so it can be exported before shutdown - hc.telemetry.ReportStatus(component.NewStatusEvent(component.StatusStopped)) - - close(hc.doneCh) - hc.aggregator.Close() - - var err error - for _, comp := range hc.subcomponents { - err = multierr.Append(err, comp.Shutdown(ctx)) - } - - return err -} - -// ComponentStatusChanged implements the extension.StatusWatcher interface. -func (hc *healthCheckExtension) ComponentStatusChanged( - source *component.InstanceID, - event *component.StatusEvent, -) { - hc.eventCh <- &eventSourcePair{source: source, event: event} -} - -// NotifyConfig implements the extension.ConfigWatcher interface. -func (hc *healthCheckExtension) NotifyConfig(ctx context.Context, conf *confmap.Conf) error { - var err error - for _, comp := range hc.subcomponents { - if cw, ok := comp.(extension.ConfigWatcher); ok { - err = multierr.Append(err, cw.NotifyConfig(ctx, conf)) - } - } - return err -} - -// Ready implements the extension.PipelineWatcher interface. -func (hc *healthCheckExtension) Ready() error { - close(hc.readyCh) - return nil -} - -// NotReady implements the extension.PipelineWatcher interface. -func (hc *healthCheckExtension) NotReady() error { - return nil -} - -func (hc *healthCheckExtension) eventLoop(ctx context.Context) { - // Record events with component.StatusStarting, but queue other events until - // PipelineWatcher.Ready is called. This prevents aggregate statuses from - // flapping between StatusStarting and StatusOK as components are started - // individually by the service. - var eventQueue []*eventSourcePair - - for loop := true; loop; { - select { - case esp := <-hc.eventCh: - if esp.event.Status() != component.StatusStarting { - eventQueue = append(eventQueue, esp) - continue - } - hc.aggregator.RecordStatus(esp.source, esp.event) - case <-hc.readyCh: - for _, esp := range eventQueue { - hc.aggregator.RecordStatus(esp.source, esp.event) - } - eventQueue = nil - loop = false - case <-ctx.Done(): - return - } - } - - // After PipelineWatcher.Ready, record statuses as they are received. - for loop := true; loop; { - select { - case esp := <-hc.eventCh: - hc.aggregator.RecordStatus(esp.source, esp.event) - case <-hc.doneCh: - loop = false - case <-ctx.Done(): - return - } - } - - // After shutdown read late arriving events from channel and discard - for { - select { - case esp := <-hc.eventCh: - hc.telemetry.Logger.Info( - "discarding event received after shutdown", - zap.Any("source", esp.source), - zap.Any("event", esp.event), - ) - case <-ctx.Done(): - return - } - } -} diff --git a/extension/healthcheckextensionv2/factory.go b/extension/healthcheckextensionv2/factory.go deleted file mode 100644 index 81e914e5763e8..0000000000000 --- a/extension/healthcheckextensionv2/factory.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -//go:generate mdatagen metadata.yaml - -package healthcheckextensionv2 // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2" - -import ( - "context" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/config/configgrpc" - "go.opentelemetry.io/collector/config/confighttp" - "go.opentelemetry.io/collector/config/confignet" - "go.opentelemetry.io/collector/extension" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/metadata" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/localhostgate" -) - -const ( - defaultGRPCPort = 13132 - defaultHTTPPort = 13133 -) - -// NewFactory creates a factory for HealthCheck extension. -func NewFactory() extension.Factory { - return extension.NewFactory( - metadata.Type, - createDefaultConfig, - createExtension, - metadata.ExtensionStability, - ) -} - -func createDefaultConfig() component.Config { - return &Config{ - LegacyConfig: http.LegacyConfig{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Path: "/", - }, - HTTPConfig: &http.Config{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Status: http.PathConfig{ - Enabled: true, - Path: "/status", - }, - Config: http.PathConfig{ - Enabled: false, - Path: "/config", - }, - }, - GRPCConfig: &grpc.Config{ - ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.AddrConfig{ - Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), - Transport: "tcp", - }, - }, - }, - } -} - -func createExtension(ctx context.Context, set extension.CreateSettings, cfg component.Config) (extension.Extension, error) { - config := cfg.(*Config) - return newExtension(ctx, *config, set), nil -} diff --git a/extension/healthcheckextensionv2/factory_test.go b/extension/healthcheckextensionv2/factory_test.go deleted file mode 100644 index 278e11b819422..0000000000000 --- a/extension/healthcheckextensionv2/factory_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package healthcheckextensionv2 - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/component/componenttest" - "go.opentelemetry.io/collector/config/configgrpc" - "go.opentelemetry.io/collector/config/confighttp" - "go.opentelemetry.io/collector/config/confignet" - "go.opentelemetry.io/collector/extension/extensiontest" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/localhostgate" - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" -) - -func TestCreateDefaultConfig(t *testing.T) { - cfg := createDefaultConfig() - assert.Equal(t, &Config{ - LegacyConfig: http.LegacyConfig{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Path: "/", - }, - HTTPConfig: &http.Config{ - ServerConfig: confighttp.ServerConfig{ - Endpoint: localhostgate.EndpointForPort(defaultHTTPPort), - }, - Status: http.PathConfig{ - Enabled: true, - Path: "/status", - }, - Config: http.PathConfig{ - Enabled: false, - Path: "/config", - }, - }, - GRPCConfig: &grpc.Config{ - ServerConfig: configgrpc.ServerConfig{ - NetAddr: confignet.AddrConfig{ - Endpoint: localhostgate.EndpointForPort(defaultGRPCPort), - Transport: "tcp", - }, - }, - }, - }, cfg) - - assert.NoError(t, componenttest.CheckConfigStruct(cfg)) - ext, err := createExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) - require.NoError(t, err) - require.NotNil(t, ext) -} - -func TestCreateExtension(t *testing.T) { - cfg := createDefaultConfig().(*Config) - cfg.Endpoint = testutil.GetAvailableLocalAddress(t) - - ext, err := createExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) - require.NoError(t, err) - require.NotNil(t, ext) -} diff --git a/extension/healthcheckextensionv2/generated_component_test.go b/extension/healthcheckextensionv2/generated_component_test.go deleted file mode 100644 index 727627fe91cf1..0000000000000 --- a/extension/healthcheckextensionv2/generated_component_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Code generated by mdatagen. DO NOT EDIT. - -package healthcheckextensionv2 - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/component/componenttest" - "go.opentelemetry.io/collector/confmap/confmaptest" - "go.opentelemetry.io/collector/extension/extensiontest" -) - -func TestComponentLifecycle(t *testing.T) { - factory := NewFactory() - - cm, err := confmaptest.LoadConf("metadata.yaml") - require.NoError(t, err) - cfg := factory.CreateDefaultConfig() - sub, err := cm.Sub("tests::config") - require.NoError(t, err) - require.NoError(t, component.UnmarshalConfig(sub, cfg)) - t.Run("shutdown", func(t *testing.T) { - e, err := factory.CreateExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) - require.NoError(t, err) - err = e.Shutdown(context.Background()) - require.NoError(t, err) - }) - t.Run("lifecycle", func(t *testing.T) { - firstExt, err := factory.CreateExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) - require.NoError(t, err) - require.NoError(t, firstExt.Start(context.Background(), componenttest.NewNopHost())) - require.NoError(t, firstExt.Shutdown(context.Background())) - - secondExt, err := factory.CreateExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) - require.NoError(t, err) - require.NoError(t, secondExt.Start(context.Background(), componenttest.NewNopHost())) - require.NoError(t, secondExt.Shutdown(context.Background())) - }) -} diff --git a/extension/healthcheckextensionv2/go.mod b/extension/healthcheckextensionv2/go.mod deleted file mode 100644 index 1c0b8e3ca3f69..0000000000000 --- a/extension/healthcheckextensionv2/go.mod +++ /dev/null @@ -1,74 +0,0 @@ -module github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2 - -go 1.21 - -toolchain go1.21.4 - -require ( - github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.95.0 - github.com/stretchr/testify v1.9.0 - go.opentelemetry.io/collector/component v0.97.1-0.20240327181407-1038b67c85a0 - go.opentelemetry.io/collector/config/configgrpc v0.97.1-0.20240327181407-1038b67c85a0 - go.opentelemetry.io/collector/config/confighttp v0.97.1-0.20240327181407-1038b67c85a0 - go.opentelemetry.io/collector/config/confignet v0.97.1-0.20240327181407-1038b67c85a0 - go.opentelemetry.io/collector/config/configtls v0.97.1-0.20240327181407-1038b67c85a0 - go.opentelemetry.io/collector/confmap v0.97.1-0.20240327181407-1038b67c85a0 - go.opentelemetry.io/collector/extension v0.97.1-0.20240327181407-1038b67c85a0 - go.opentelemetry.io/otel/metric v1.24.0 - go.opentelemetry.io/otel/trace v1.24.0 - go.uber.org/multierr v1.11.0 - go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.62.1 -) - -require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/klauspost/compress v1.17.7 // indirect - github.com/knadh/koanf/maps v0.1.1 // indirect - github.com/knadh/koanf/providers/confmap v0.1.0 // indirect - github.com/knadh/koanf/v2 v2.1.0 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/mostynb/go-grpc-compression v1.2.2 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.19.0 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/rs/cors v1.10.1 // indirect - go.opentelemetry.io/collector v0.97.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/config/configauth v0.97.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/config/configcompression v1.4.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/config/configopaque v1.4.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.97.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/config/internal v0.97.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/extension/auth v0.97.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/featuregate v1.4.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/collector/pdata v1.4.1-0.20240327181407-1038b67c85a0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.46.0 // indirect - go.opentelemetry.io/otel/sdk v1.24.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.24.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/common => ../../internal/common diff --git a/extension/healthcheckextensionv2/go.sum b/extension/healthcheckextensionv2/go.sum deleted file mode 100644 index e901c28244e93..0000000000000 --- a/extension/healthcheckextensionv2/go.sum +++ /dev/null @@ -1,175 +0,0 @@ -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -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/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= -github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= -github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= -github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= -github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8= -github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -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/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mostynb/go-grpc-compression v1.2.2 h1:XaDbnRvt2+1vgr0b/l0qh4mJAfIxE0bKXtz2Znl3GGI= -github.com/mostynb/go-grpc-compression v1.2.2/go.mod h1:GOCr2KBxXcblCuczg3YdLQlcin1/NfyDA348ckuCH6w= -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/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= -github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/collector v0.97.1-0.20240327181407-1038b67c85a0 h1:dvxsnQ+nBUX99/Sza/bYnW9lLmkb9Cm4fFqFlZk/cNs= -go.opentelemetry.io/collector v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:V6xquYAaO2VHVu4DBK28JYuikRdZajh7DH5Vl/Y8NiA= -go.opentelemetry.io/collector/component v0.97.1-0.20240327181407-1038b67c85a0 h1:OBXZrNlbQtCfpcqfVmKfsiqEKket/cHm61e4l2hfxuo= -go.opentelemetry.io/collector/component v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:F/m3HMlkb16RKI7wJjgbECK1IZkAcmB8bu7yD8XOkwM= -go.opentelemetry.io/collector/config/configauth v0.97.1-0.20240327181407-1038b67c85a0 h1:Me8HrbBgWBnEeBVcD/dI6qZ7LbdOWfs6s23hG30G/gU= -go.opentelemetry.io/collector/config/configauth v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:BkCDatBU7CXXStrRPE1b4woj2VLxaYEMg2WTkb50BlI= -go.opentelemetry.io/collector/config/configcompression v1.4.1-0.20240327181407-1038b67c85a0 h1:SoXZh6osv9ve5a7Wcr5fT/T2iJwpZMudtIuu3KkXkXo= -go.opentelemetry.io/collector/config/configcompression v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:O0fOPCADyGwGLLIf5lf7N3960NsnIfxsm6dr/mIpL+M= -go.opentelemetry.io/collector/config/configgrpc v0.97.1-0.20240327181407-1038b67c85a0 h1:Z6P8/9UBvMbjcdBCcHJmSs4GPUdTdjn78+3z0SeRTLI= -go.opentelemetry.io/collector/config/configgrpc v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:i8OrrxynYldlcZ6wPOUKNoZmmbUCDp3CzryRT+2mN7c= -go.opentelemetry.io/collector/config/confighttp v0.97.1-0.20240327181407-1038b67c85a0 h1:/KSUONuOMD2gYqZ1pV4kCYYxg486TL43CkHKhhCrfqI= -go.opentelemetry.io/collector/config/confighttp v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:wyg4yXvCsk1CsfPBWQ3+rZDThz44Q0d35/1lJBHj5VI= -go.opentelemetry.io/collector/config/confignet v0.97.1-0.20240327181407-1038b67c85a0 h1:EfSh528GosPVHOwHCqYl1ycnTj4rqTb9bVz99Br2wDs= -go.opentelemetry.io/collector/config/confignet v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:3naWoPss70RhDHhYjGACi7xh4NcVRvs9itzIRVWyu1k= -go.opentelemetry.io/collector/config/configopaque v1.4.1-0.20240327181407-1038b67c85a0 h1:nZkYYEdASkZ5L43eclvdWOnM8cB4Ga4FxPv+qLMaLHQ= -go.opentelemetry.io/collector/config/configopaque v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:7Qzo69x7i+FaNELeA9jmZtVvfnR5lE6JYa5YEOCJPFQ= -go.opentelemetry.io/collector/config/configtelemetry v0.97.1-0.20240327181407-1038b67c85a0 h1:n6gNCKxrCs3hD+jafL93JdtPVl05p+C5PecoNE7YUrw= -go.opentelemetry.io/collector/config/configtelemetry v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:YV5PaOdtnU1xRomPcYqoHmyCr48tnaAREeGO96EZw8o= -go.opentelemetry.io/collector/config/configtls v0.97.1-0.20240327181407-1038b67c85a0 h1:xc0wUkz0fFnoUdqtGyyoKMwDO1saOaGj60aunfk8lf0= -go.opentelemetry.io/collector/config/configtls v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:ev/fMI6hm1WTSHHEAEoVjF3RZj0qf38E/XO5itFku7k= -go.opentelemetry.io/collector/config/internal v0.97.1-0.20240327181407-1038b67c85a0 h1:YmVIDMQMC4x+W6fsdO3/6069/8rA1pycAwDydWpO0Lo= -go.opentelemetry.io/collector/config/internal v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:RVGDn9OH/KHT878cclG497/n2qxe54+zW+u/SVsRLNw= -go.opentelemetry.io/collector/confmap v0.97.1-0.20240327181407-1038b67c85a0 h1:Cm5WDKNnmKLZmiAzodv3LLodAN3fAZFl+Q6jek/K6xU= -go.opentelemetry.io/collector/confmap v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:AnJmZcZoOLuykSXGiAf3shi11ZZk5ei4tZd9dDTTpWE= -go.opentelemetry.io/collector/consumer v0.97.0 h1:S0BZQtJQxSHT156S8a5rLt3TeWYP8Rq+jn8QEyWQUYk= -go.opentelemetry.io/collector/consumer v0.97.0/go.mod h1:1D06LURiZ/1KA2OnuKNeSn9bvFmJ5ZWe6L8kLu0osSY= -go.opentelemetry.io/collector/extension v0.97.1-0.20240327181407-1038b67c85a0 h1:/3CkYzkiiAhoy8IGM+FUob4GGWzEb9JfN5J3RGn1ODM= -go.opentelemetry.io/collector/extension v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:jWNG0Npi7AxiqwCclToskDfCQuNKHYHlBPJNnIKHp84= -go.opentelemetry.io/collector/extension/auth v0.97.1-0.20240327181407-1038b67c85a0 h1:ctgHe7ZPXgjjE8s+1L3GndORCLD5Oq/ZVC6p4zzXRPc= -go.opentelemetry.io/collector/extension/auth v0.97.1-0.20240327181407-1038b67c85a0/go.mod h1:uElLYtzMPA48mu9baxGIH6lHpOn76NLe4mVHnmV+hEY= -go.opentelemetry.io/collector/featuregate v1.4.1-0.20240327181407-1038b67c85a0 h1:pZlwb1NvnMxlVU3WqSAz3gckK89T6PzfRhMbybtktn8= -go.opentelemetry.io/collector/featuregate v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:w7nUODKxEi3FLf1HslCiE6YWtMtOOrMnSwsDam8Mg9w= -go.opentelemetry.io/collector/pdata v1.4.1-0.20240327181407-1038b67c85a0 h1:ytj0zkWq3pCWCukjLdNgiLZx65lbzJaMIkEhA3w9imw= -go.opentelemetry.io/collector/pdata v1.4.1-0.20240327181407-1038b67c85a0/go.mod h1:0Ttp4wQinhV5oJTd9MjyvUegmZBO9O0nrlh/+EDLw+Q= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= -go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= -go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -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/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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extension/healthcheckextensionv2/internal/common/config.go b/extension/healthcheckextensionv2/internal/common/config.go deleted file mode 100644 index 67f3826c45c8c..0000000000000 --- a/extension/healthcheckextensionv2/internal/common/config.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package common // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - -import "time" - -type ComponentHealthConfig struct { - IncludePermanent bool `mapstructure:"include_permanent_errors"` - IncludeRecoverable bool `mapstructure:"include_recoverable_errors"` - RecoveryDuration time.Duration `mapstructure:"recovery_duration"` -} - -func (c ComponentHealthConfig) Enabled() bool { - return c.IncludePermanent || c.IncludeRecoverable -} diff --git a/extension/healthcheckextensionv2/internal/grpc/config.go b/extension/healthcheckextensionv2/internal/grpc/config.go deleted file mode 100644 index 6fa6c017c34ae..0000000000000 --- a/extension/healthcheckextensionv2/internal/grpc/config.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" - -import "go.opentelemetry.io/collector/config/configgrpc" - -type Config struct { - configgrpc.ServerConfig `mapstructure:",squash"` -} diff --git a/extension/healthcheckextensionv2/internal/http/config.go b/extension/healthcheckextensionv2/internal/http/config.go deleted file mode 100644 index 6ab8cb184b644..0000000000000 --- a/extension/healthcheckextensionv2/internal/http/config.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" - -import "go.opentelemetry.io/collector/config/confighttp" - -// Config contains the v2 config for the http healthcheck service -type Config struct { - confighttp.ServerConfig `mapstructure:",squash"` - - Config PathConfig `mapstructure:"config"` - Status PathConfig `mapstructure:"status"` -} - -type PathConfig struct { - Enabled bool `mapstructure:"enabled"` - Path string `mapstructure:"path"` -} - -// LegacyConfig contains the config for the original healthcheck extension. We plan to migrate -// incrementally towards the v2 config and behavior. LegacyConfig is intentionally handled -// separately here and elsewhere to facilitate its eventual removal. -type LegacyConfig struct { - confighttp.ServerConfig `mapstructure:",squash"` - - // Path represents the path the health check service will serve. - // The default path is "/". - Path string `mapstructure:"path"` - - // ResponseBody represents the body of the response returned by the health check service. - // This overrides the default response that it would return. - ResponseBody *ResponseBodyConfig `mapstructure:"response_body"` - - // CheckCollectorPipeline contains the config for collector pipeline health checks. It is - // retained for backwards compatibility, but is otherwise ignored. - CheckCollectorPipeline *CheckCollectorPipelineConfig `mapstructure:"check_collector_pipeline"` - - // UseV2 is an explicit opt-in to v2 behavior. When true, LegacyConfig will be ignored. - UseV2 bool `mapstructure:"use_v2"` -} - -// ResponseBodyConfig is legacy config that is currently supported, but can eventually be -// deprecated and removed. -type ResponseBodyConfig struct { - // Healthy represents the body of the response returned when the collector is healthy. - // The default value is "" - Healthy string `mapstructure:"healthy"` - - // Unhealthy represents the body of the response returned when the collector is unhealthy. - // The default value is "" - Unhealthy string `mapstructure:"unhealthy"` -} - -// CheckCollectorPipelineConfig is legacy config that is currently ignored as the -// `check_collector_pipeline` feature in the original healtcheck extension was not working as -// expected. This is here for backwards compatibility. -type CheckCollectorPipelineConfig struct { - // Enabled indicates whether to not enable collector pipeline check. - Enabled bool `mapstructure:"enabled"` - // Interval the time range to check healthy status of collector pipeline - Interval string `mapstructure:"interval"` - // ExporterFailureThreshold is the threshold of exporter failure numbers during the Interval - ExporterFailureThreshold int `mapstructure:"exporter_failure_threshold"` -} diff --git a/extension/healthcheckextensionv2/internal/metadata/generated_status.go b/extension/healthcheckextensionv2/internal/metadata/generated_status.go deleted file mode 100644 index 7b253fdae7fb0..0000000000000 --- a/extension/healthcheckextensionv2/internal/metadata/generated_status.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by mdatagen. DO NOT EDIT. - -package metadata - -import ( - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/trace" -) - -var ( - Type = component.MustNewType("healthcheckv2") -) - -const ( - ExtensionStability = component.StabilityLevelDevelopment -) - -func Meter(settings component.TelemetrySettings) metric.Meter { - return settings.MeterProvider.Meter("github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2") -} - -func Tracer(settings component.TelemetrySettings) trace.Tracer { - return settings.TracerProvider.Tracer("github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2") -} diff --git a/extension/healthcheckextensionv2/metadata.yaml b/extension/healthcheckextensionv2/metadata.yaml deleted file mode 100644 index 230e77f7511a3..0000000000000 --- a/extension/healthcheckextensionv2/metadata.yaml +++ /dev/null @@ -1,9 +0,0 @@ -type: healthcheckv2 - -status: - class: extension - stability: - development: [extension] - distributions: [core, contrib] - codeowners: - active: [mwear] diff --git a/extension/healthcheckextensionv2/testdata/config.yaml b/extension/healthcheckextensionv2/testdata/config.yaml deleted file mode 100644 index 54dee58079797..0000000000000 --- a/extension/healthcheckextensionv2/testdata/config.yaml +++ /dev/null @@ -1,48 +0,0 @@ -healthcheckv2: -healthcheckv2/legacyconfig: - endpoint: "localhost:13" - tls: - ca_file: "/path/to/ca" - key_file: "/path/to/key" - cert_file: "/path/to/cert" - check_collector_pipeline: - enabled: false - interval: 5m - exporter_failure_threshold: 5 -healthcheckv2/missingendpoint: - endpoint: "" -healthcheckv2/invalidpath: - endpoint: "localhost:13" - path: "invalid" -healthcheckv2/v2all: - use_v2: true - http: - grpc: - component_health: - include_permanent_errors: true - include_recoverable_errors: true - recovery_duration: 5m -healthcheckv2/v2httpcustomized: - use_v2: true - http: - endpoint: "localhost:13" - status: - enabled: true - path: "/health" - config: - enabled: true - path: "/conf" -healthcheckv2/v2httpmissingendpoint: - use_v2: true - http: - endpoint: "" -healthcheckv2/v2grpccustomized: - use_v2: true - grpc: - endpoint: "localhost:13" -healthcheckv2/v2grpcmissingendpoint: - use_v2: true - grpc: - endpoint: "" -healthcheckv2/v2noprotocols: - use_v2: true diff --git a/extension/healthcheckv2extension/extension.go b/extension/healthcheckv2extension/extension.go index cb58a30add883..d8a1c1604d81c 100644 --- a/extension/healthcheckv2extension/extension.go +++ b/extension/healthcheckv2extension/extension.go @@ -7,36 +7,197 @@ import ( "context" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/extension" + "go.uber.org/multierr" "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/grpc" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/http" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) +type eventSourcePair struct { + source *component.InstanceID + event *component.StatusEvent +} + type healthCheckExtension struct { - config Config - telemetry component.TelemetrySettings + config Config + telemetry component.TelemetrySettings + aggregator *status.Aggregator + subcomponents []component.Component + eventCh chan *eventSourcePair + readyCh chan struct{} + doneCh chan struct{} } var _ component.Component = (*healthCheckExtension)(nil) +var _ extension.ConfigWatcher = (*healthCheckExtension)(nil) +var _ extension.PipelineWatcher = (*healthCheckExtension)(nil) func newExtension( - _ context.Context, + ctx context.Context, config Config, set extension.CreateSettings, ) *healthCheckExtension { - return &healthCheckExtension{ - config: config, - telemetry: set.TelemetrySettings, + var comps []component.Component + + errPriority := status.PriorityPermanent + if config.ComponentHealthConfig != nil && + config.ComponentHealthConfig.IncludeRecoverable && + !config.ComponentHealthConfig.IncludePermanent { + errPriority = status.PriorityRecoverable + } + + aggregator := status.NewAggregator(errPriority) + + if config.UseV2 && config.GRPCConfig != nil { + grpcServer := grpc.NewServer( + config.GRPCConfig, + config.ComponentHealthConfig, + set.TelemetrySettings, + aggregator, + ) + comps = append(comps, grpcServer) } + + if !config.UseV2 || config.UseV2 && config.HTTPConfig != nil { + httpServer := http.NewServer( + config.HTTPConfig, + config.LegacyConfig, + config.ComponentHealthConfig, + set.TelemetrySettings, + aggregator, + ) + comps = append(comps, httpServer) + } + + hc := &healthCheckExtension{ + config: config, + subcomponents: comps, + telemetry: set.TelemetrySettings, + aggregator: aggregator, + eventCh: make(chan *eventSourcePair), + readyCh: make(chan struct{}), + doneCh: make(chan struct{}), + } + + // Start processing events in the background so that our status watcher doesn't + // block others before the extension starts. + go hc.eventLoop(ctx) + + return hc } // Start implements the component.Component interface. -func (hc *healthCheckExtension) Start(context.Context, component.Host) error { +func (hc *healthCheckExtension) Start(ctx context.Context, host component.Host) error { hc.telemetry.Logger.Debug("Starting health check extension V2", zap.Any("config", hc.config)) + for _, comp := range hc.subcomponents { + if err := comp.Start(ctx, host); err != nil { + return err + } + } + return nil } // Shutdown implements the component.Component interface. -func (hc *healthCheckExtension) Shutdown(context.Context) error { +func (hc *healthCheckExtension) Shutdown(ctx context.Context) error { + // Preemptively send the stopped event, so it can be exported before shutdown + hc.telemetry.ReportStatus(component.NewStatusEvent(component.StatusStopped)) + + close(hc.doneCh) + hc.aggregator.Close() + + var err error + for _, comp := range hc.subcomponents { + err = multierr.Append(err, comp.Shutdown(ctx)) + } + + return err +} + +// ComponentStatusChanged implements the extension.StatusWatcher interface. +func (hc *healthCheckExtension) ComponentStatusChanged( + source *component.InstanceID, + event *component.StatusEvent, +) { + hc.eventCh <- &eventSourcePair{source: source, event: event} +} + +// NotifyConfig implements the extension.ConfigWatcher interface. +func (hc *healthCheckExtension) NotifyConfig(ctx context.Context, conf *confmap.Conf) error { + var err error + for _, comp := range hc.subcomponents { + if cw, ok := comp.(extension.ConfigWatcher); ok { + err = multierr.Append(err, cw.NotifyConfig(ctx, conf)) + } + } + return err +} + +// Ready implements the extension.PipelineWatcher interface. +func (hc *healthCheckExtension) Ready() error { + close(hc.readyCh) + return nil +} + +// NotReady implements the extension.PipelineWatcher interface. +func (hc *healthCheckExtension) NotReady() error { return nil } + +func (hc *healthCheckExtension) eventLoop(ctx context.Context) { + // Record events with component.StatusStarting, but queue other events until + // PipelineWatcher.Ready is called. This prevents aggregate statuses from + // flapping between StatusStarting and StatusOK as components are started + // individually by the service. + var eventQueue []*eventSourcePair + + for loop := true; loop; { + select { + case esp := <-hc.eventCh: + if esp.event.Status() != component.StatusStarting { + eventQueue = append(eventQueue, esp) + continue + } + hc.aggregator.RecordStatus(esp.source, esp.event) + case <-hc.readyCh: + for _, esp := range eventQueue { + hc.aggregator.RecordStatus(esp.source, esp.event) + } + eventQueue = nil + loop = false + case <-ctx.Done(): + return + } + } + + // After PipelineWatcher.Ready, record statuses as they are received. + for loop := true; loop; { + select { + case esp := <-hc.eventCh: + hc.aggregator.RecordStatus(esp.source, esp.event) + case <-hc.doneCh: + loop = false + case <-ctx.Done(): + return + } + } + + // After shutdown read late arriving events from channel and discard + for { + select { + case esp := <-hc.eventCh: + hc.telemetry.Logger.Info( + "discarding event received after shutdown", + zap.Any("source", esp.source), + zap.Any("event", esp.event), + ) + case <-ctx.Done(): + return + } + } +} diff --git a/extension/healthcheckextensionv2/extension_test.go b/extension/healthcheckv2extension/extension_test.go similarity index 97% rename from extension/healthcheckextensionv2/extension_test.go rename to extension/healthcheckv2extension/extension_test.go index 14db04497e23b..c106ec30c1dbf 100644 --- a/extension/healthcheckextensionv2/extension_test.go +++ b/extension/healthcheckv2extension/extension_test.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package healthcheckextensionv2 +package healthcheckv2extension import ( "context" @@ -20,8 +20,8 @@ import ( "go.opentelemetry.io/collector/confmap/confmaptest" "go.opentelemetry.io/collector/extension/extensiontest" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/testhelpers" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" ) diff --git a/extension/healthcheckv2extension/generated_component_test.go b/extension/healthcheckv2extension/generated_component_test.go index 3ea73d819b85c..708258a4f4d67 100644 --- a/extension/healthcheckv2extension/generated_component_test.go +++ b/extension/healthcheckv2extension/generated_component_test.go @@ -13,14 +13,6 @@ import ( "go.opentelemetry.io/collector/extension/extensiontest" ) -func TestComponentFactoryType(t *testing.T) { - require.Equal(t, "healthcheckv2", NewFactory().Type().String()) -} - -func TestComponentConfigStruct(t *testing.T) { - require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) -} - func TestComponentLifecycle(t *testing.T) { factory := NewFactory() diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc.go b/extension/healthcheckv2extension/internal/grpc/grpc.go similarity index 97% rename from extension/healthcheckextensionv2/internal/grpc/grpc.go rename to extension/healthcheckv2extension/internal/grpc/grpc.go index 7aa611b172c2e..027fab6a220c3 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc.go +++ b/extension/healthcheckv2extension/internal/grpc/grpc.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" +package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/grpc" import ( "context" @@ -12,7 +12,7 @@ import ( healthpb "google.golang.org/grpc/health/grpc_health_v1" grpcstatus "google.golang.org/grpc/status" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) var ( diff --git a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go b/extension/healthcheckv2extension/internal/grpc/grpc_test.go similarity index 99% rename from extension/healthcheckextensionv2/internal/grpc/grpc_test.go rename to extension/healthcheckv2extension/internal/grpc/grpc_test.go index 4e46144af0450..79150d0a56cca 100644 --- a/extension/healthcheckextensionv2/internal/grpc/grpc_test.go +++ b/extension/healthcheckv2extension/internal/grpc/grpc_test.go @@ -21,9 +21,9 @@ import ( healthpb "google.golang.org/grpc/health/grpc_health_v1" grpcstatus "google.golang.org/grpc/status" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/testhelpers" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" ) diff --git a/extension/healthcheckextensionv2/internal/grpc/server.go b/extension/healthcheckv2extension/internal/grpc/server.go similarity index 92% rename from extension/healthcheckextensionv2/internal/grpc/server.go rename to extension/healthcheckv2extension/internal/grpc/server.go index 7d5d04070d8c7..eb0a0f0fd8e7c 100644 --- a/extension/healthcheckextensionv2/internal/grpc/server.go +++ b/extension/healthcheckv2extension/internal/grpc/server.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/grpc" +package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/grpc" import ( "context" @@ -11,8 +11,8 @@ import ( "google.golang.org/grpc" healthpb "google.golang.org/grpc/health/grpc_health_v1" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) type Server struct { diff --git a/extension/healthcheckextensionv2/internal/http/handlers.go b/extension/healthcheckv2extension/internal/http/handlers.go similarity index 89% rename from extension/healthcheckextensionv2/internal/http/handlers.go rename to extension/healthcheckv2extension/internal/http/handlers.go index f7ae086455ac7..1aaf9f713f670 100644 --- a/extension/healthcheckextensionv2/internal/http/handlers.go +++ b/extension/healthcheckv2extension/internal/http/handlers.go @@ -1,12 +1,12 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/http" import ( "net/http" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) func (s *Server) statusHandler() http.Handler { diff --git a/extension/healthcheckextensionv2/internal/http/responders.go b/extension/healthcheckv2extension/internal/http/responders.go similarity index 96% rename from extension/healthcheckextensionv2/internal/http/responders.go rename to extension/healthcheckv2extension/internal/http/responders.go index 5a32b3fdc60fe..4204b5517d972 100644 --- a/extension/healthcheckextensionv2/internal/http/responders.go +++ b/extension/healthcheckv2extension/internal/http/responders.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/http" import ( "encoding/json" @@ -11,8 +11,8 @@ import ( "go.opentelemetry.io/collector/component" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) var responseCodes = map[component.Status]int{ diff --git a/extension/healthcheckextensionv2/internal/http/serialization.go b/extension/healthcheckv2extension/internal/http/serialization.go similarity index 95% rename from extension/healthcheckextensionv2/internal/http/serialization.go rename to extension/healthcheckv2extension/internal/http/serialization.go index ab0ed75f58dbc..6b00933dc9884 100644 --- a/extension/healthcheckextensionv2/internal/http/serialization.go +++ b/extension/healthcheckv2extension/internal/http/serialization.go @@ -1,14 +1,14 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/http" import ( "time" "go.opentelemetry.io/collector/component" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) type healthyFunc func(status.Event) bool diff --git a/extension/healthcheckextensionv2/internal/http/server.go b/extension/healthcheckv2extension/internal/http/server.go similarity index 95% rename from extension/healthcheckextensionv2/internal/http/server.go rename to extension/healthcheckv2extension/internal/http/server.go index e4c880eb0289c..afb74494d839e 100644 --- a/extension/healthcheckextensionv2/internal/http/server.go +++ b/extension/healthcheckv2extension/internal/http/server.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/http" +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/http" import ( "context" @@ -18,8 +18,8 @@ import ( "go.opentelemetry.io/collector/extension" "go.uber.org/zap" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) type Server struct { diff --git a/extension/healthcheckextensionv2/internal/http/server_test.go b/extension/healthcheckv2extension/internal/http/server_test.go similarity index 99% rename from extension/healthcheckextensionv2/internal/http/server_test.go rename to extension/healthcheckv2extension/internal/http/server_test.go index c69f6585c074f..6bd70e69669e3 100644 --- a/extension/healthcheckextensionv2/internal/http/server_test.go +++ b/extension/healthcheckv2extension/internal/http/server_test.go @@ -22,9 +22,9 @@ import ( "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/confmap/confmaptest" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/testhelpers" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil" ) diff --git a/extension/healthcheckextensionv2/internal/http/testdata/config.json b/extension/healthcheckv2extension/internal/http/testdata/config.json similarity index 100% rename from extension/healthcheckextensionv2/internal/http/testdata/config.json rename to extension/healthcheckv2extension/internal/http/testdata/config.json diff --git a/extension/healthcheckextensionv2/internal/http/testdata/config.yaml b/extension/healthcheckv2extension/internal/http/testdata/config.yaml similarity index 100% rename from extension/healthcheckextensionv2/internal/http/testdata/config.yaml rename to extension/healthcheckv2extension/internal/http/testdata/config.yaml diff --git a/extension/healthcheckextensionv2/internal/status/aggregation.go b/extension/healthcheckv2extension/internal/status/aggregation.go similarity index 98% rename from extension/healthcheckextensionv2/internal/status/aggregation.go rename to extension/healthcheckv2extension/internal/status/aggregation.go index e5ff3b6048286..bab7275443193 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregation.go +++ b/extension/healthcheckv2extension/internal/status/aggregation.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package status // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +package status // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" import ( "time" diff --git a/extension/healthcheckextensionv2/internal/status/aggregation_test.go b/extension/healthcheckv2extension/internal/status/aggregation_test.go similarity index 100% rename from extension/healthcheckextensionv2/internal/status/aggregation_test.go rename to extension/healthcheckv2extension/internal/status/aggregation_test.go diff --git a/extension/healthcheckextensionv2/internal/status/aggregator.go b/extension/healthcheckv2extension/internal/status/aggregator.go similarity index 99% rename from extension/healthcheckextensionv2/internal/status/aggregator.go rename to extension/healthcheckv2extension/internal/status/aggregator.go index 03c30816f0bb0..004dbf5447a7d 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator.go +++ b/extension/healthcheckv2extension/internal/status/aggregator.go @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package status // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" +package status // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" import ( "fmt" diff --git a/extension/healthcheckextensionv2/internal/status/aggregator_test.go b/extension/healthcheckv2extension/internal/status/aggregator_test.go similarity index 99% rename from extension/healthcheckextensionv2/internal/status/aggregator_test.go rename to extension/healthcheckv2extension/internal/status/aggregator_test.go index f97f7bab9a050..eb8f6f8b2c22a 100644 --- a/extension/healthcheckextensionv2/internal/status/aggregator_test.go +++ b/extension/healthcheckv2extension/internal/status/aggregator_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/testhelpers" ) func TestAggregateStatus(t *testing.T) { diff --git a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go b/extension/healthcheckv2extension/internal/testhelpers/helpers.go similarity index 94% rename from extension/healthcheckextensionv2/internal/testhelpers/helpers.go rename to extension/healthcheckv2extension/internal/testhelpers/helpers.go index fda59f3be1d3c..be02ca6275380 100644 --- a/extension/healthcheckextensionv2/internal/testhelpers/helpers.go +++ b/extension/healthcheckv2extension/internal/testhelpers/helpers.go @@ -1,13 +1,13 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package testhelpers // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/testhelpers" +package testhelpers // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/testhelpers" import ( "go.opentelemetry.io/collector/component" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/common" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextensionv2/internal/status" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/common" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" ) // PipelineMetadata groups together component and instance IDs for a hypothetical pipeline used From 17edc876583181f58f2d13ba1ddb96979ce1b02c Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Fri, 29 Mar 2024 08:58:03 -0700 Subject: [PATCH 22/26] Update metadata --- reports/distributions/contrib.yaml | 1 - reports/distributions/core.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/reports/distributions/contrib.yaml b/reports/distributions/contrib.yaml index 5303cb71a7c7a..1a4b4b57a2256 100644 --- a/reports/distributions/contrib.yaml +++ b/reports/distributions/contrib.yaml @@ -67,7 +67,6 @@ components: - file_storage - headers_setter - health_check - - healthcheckv2 - host_observer - jaegerremotesampling - k8s_observer diff --git a/reports/distributions/core.yaml b/reports/distributions/core.yaml index 46802e687b2d3..ef8961dc5c1fc 100644 --- a/reports/distributions/core.yaml +++ b/reports/distributions/core.yaml @@ -11,7 +11,6 @@ components: - zipkin extension: - health_check - - healthcheckv2 - pprof processor: - attributes From ac459bebd8731787fdc8b21c4e394a7c23fbc777 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Mon, 15 Apr 2024 16:01:52 -0700 Subject: [PATCH 23/26] Post rebase updates --- .../healthcheckv2extension/generated_component_test.go | 8 ++++++++ extension/healthcheckv2extension/internal/http/server.go | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/extension/healthcheckv2extension/generated_component_test.go b/extension/healthcheckv2extension/generated_component_test.go index 708258a4f4d67..3ea73d819b85c 100644 --- a/extension/healthcheckv2extension/generated_component_test.go +++ b/extension/healthcheckv2extension/generated_component_test.go @@ -13,6 +13,14 @@ import ( "go.opentelemetry.io/collector/extension/extensiontest" ) +func TestComponentFactoryType(t *testing.T) { + require.Equal(t, "healthcheckv2", NewFactory().Type().String()) +} + +func TestComponentConfigStruct(t *testing.T) { + require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) +} + func TestComponentLifecycle(t *testing.T) { factory := NewFactory() diff --git a/extension/healthcheckv2extension/internal/http/server.go b/extension/healthcheckv2extension/internal/http/server.go index afb74494d839e..7ea19534827ef 100644 --- a/extension/healthcheckv2extension/internal/http/server.go +++ b/extension/healthcheckv2extension/internal/http/server.go @@ -79,16 +79,16 @@ func NewServer( } // Start implements the component.Component interface. -func (s *Server) Start(_ context.Context, host component.Host) error { +func (s *Server) Start(ctx context.Context, host component.Host) error { var err error s.startTimestamp = time.Now() - s.httpServer, err = s.httpConfig.ToServer(host, s.telemetry, s.mux) + s.httpServer, err = s.httpConfig.ToServerContext(ctx, host, s.telemetry, s.mux) if err != nil { return err } - ln, err := s.httpConfig.ToListener() + ln, err := s.httpConfig.ToListenerContext(ctx) if err != nil { return fmt.Errorf("failed to bind to address %s: %w", s.httpConfig.Endpoint, err) } From 04cf05bd11f9cad09337f3d0dc64291f9b29af3b Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 18 Apr 2024 15:42:29 -0700 Subject: [PATCH 24/26] Enable goleak --- extension/healthcheckv2extension/extension.go | 45 ++++++++++--------- .../healthcheckv2extension/factory_test.go | 9 ++-- .../internal/grpc/package_test.go | 14 ++++++ .../internal/http/package_test.go | 14 ++++++ .../internal/status/package_test.go | 14 ++++++ 5 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 extension/healthcheckv2extension/internal/grpc/package_test.go create mode 100644 extension/healthcheckv2extension/internal/http/package_test.go create mode 100644 extension/healthcheckv2extension/internal/status/package_test.go diff --git a/extension/healthcheckv2extension/extension.go b/extension/healthcheckv2extension/extension.go index d8a1c1604d81c..18f3f1268b7ad 100644 --- a/extension/healthcheckv2extension/extension.go +++ b/extension/healthcheckv2extension/extension.go @@ -29,7 +29,6 @@ type healthCheckExtension struct { subcomponents []component.Component eventCh chan *eventSourcePair readyCh chan struct{} - doneCh chan struct{} } var _ component.Component = (*healthCheckExtension)(nil) @@ -80,7 +79,6 @@ func newExtension( aggregator: aggregator, eventCh: make(chan *eventSourcePair), readyCh: make(chan struct{}), - doneCh: make(chan struct{}), } // Start processing events in the background so that our status watcher doesn't @@ -108,7 +106,7 @@ func (hc *healthCheckExtension) Shutdown(ctx context.Context) error { // Preemptively send the stopped event, so it can be exported before shutdown hc.telemetry.ReportStatus(component.NewStatusEvent(component.StatusStopped)) - close(hc.doneCh) + close(hc.eventCh) hc.aggregator.Close() var err error @@ -124,6 +122,19 @@ func (hc *healthCheckExtension) ComponentStatusChanged( source *component.InstanceID, event *component.StatusEvent, ) { + // There can be late arriving events after shutdown. We need to close + // the event channel so that this function doesn't block and we release all + // goroutines, but attempting to write to a closed channel will panic; log + // and recover. + defer func() { + if r := recover(); r != nil { + hc.telemetry.Logger.Info( + "discarding event received after shutdown", + zap.Any("source", source), + zap.Any("event", event), + ) + } + }() hc.eventCh <- &eventSourcePair{source: source, event: event} } @@ -158,7 +169,10 @@ func (hc *healthCheckExtension) eventLoop(ctx context.Context) { for loop := true; loop; { select { - case esp := <-hc.eventCh: + case esp, ok := <-hc.eventCh: + if !ok { + return + } if esp.event.Status() != component.StatusStarting { eventQueue = append(eventQueue, esp) continue @@ -176,26 +190,13 @@ func (hc *healthCheckExtension) eventLoop(ctx context.Context) { } // After PipelineWatcher.Ready, record statuses as they are received. - for loop := true; loop; { - select { - case esp := <-hc.eventCh: - hc.aggregator.RecordStatus(esp.source, esp.event) - case <-hc.doneCh: - loop = false - case <-ctx.Done(): - return - } - } - - // After shutdown read late arriving events from channel and discard for { select { - case esp := <-hc.eventCh: - hc.telemetry.Logger.Info( - "discarding event received after shutdown", - zap.Any("source", esp.source), - zap.Any("event", esp.event), - ) + case esp, ok := <-hc.eventCh: + if !ok { + return + } + hc.aggregator.RecordStatus(esp.source, esp.event) case <-ctx.Done(): return } diff --git a/extension/healthcheckv2extension/factory_test.go b/extension/healthcheckv2extension/factory_test.go index 540a0346a00a7..e8fdf2d784393 100644 --- a/extension/healthcheckv2extension/factory_test.go +++ b/extension/healthcheckv2extension/factory_test.go @@ -54,7 +54,9 @@ func TestCreateDefaultConfig(t *testing.T) { }, cfg) assert.NoError(t, componenttest.CheckConfigStruct(cfg)) - ext, err := createExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ext, err := createExtension(ctx, extensiontest.NewNopCreateSettings(), cfg) require.NoError(t, err) require.NotNil(t, ext) } @@ -62,8 +64,9 @@ func TestCreateDefaultConfig(t *testing.T) { func TestCreateExtension(t *testing.T) { cfg := createDefaultConfig().(*Config) cfg.Endpoint = testutil.GetAvailableLocalAddress(t) - - ext, err := createExtension(context.Background(), extensiontest.NewNopCreateSettings(), cfg) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + ext, err := createExtension(ctx, extensiontest.NewNopCreateSettings(), cfg) require.NoError(t, err) require.NotNil(t, ext) } diff --git a/extension/healthcheckv2extension/internal/grpc/package_test.go b/extension/healthcheckv2extension/internal/grpc/package_test.go new file mode 100644 index 0000000000000..0a083f58c642a --- /dev/null +++ b/extension/healthcheckv2extension/internal/grpc/package_test.go @@ -0,0 +1,14 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package grpc // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/grpc" + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/extension/healthcheckv2extension/internal/http/package_test.go b/extension/healthcheckv2extension/internal/http/package_test.go new file mode 100644 index 0000000000000..9f1f2d2d2c7a9 --- /dev/null +++ b/extension/healthcheckv2extension/internal/http/package_test.go @@ -0,0 +1,14 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package http // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/http" + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/extension/healthcheckv2extension/internal/status/package_test.go b/extension/healthcheckv2extension/internal/status/package_test.go new file mode 100644 index 0000000000000..312e32157c05c --- /dev/null +++ b/extension/healthcheckv2extension/internal/status/package_test.go @@ -0,0 +1,14 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package status // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckv2extension/internal/status" + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} From 1a45be54a8ff0af73fce99716324f669ea179d2c Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 18 Apr 2024 15:55:45 -0700 Subject: [PATCH 25/26] Update collector module version --- extension/healthcheckv2extension/internal/http/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/healthcheckv2extension/internal/http/server.go b/extension/healthcheckv2extension/internal/http/server.go index 7ea19534827ef..1ae6663799681 100644 --- a/extension/healthcheckv2extension/internal/http/server.go +++ b/extension/healthcheckv2extension/internal/http/server.go @@ -83,12 +83,12 @@ func (s *Server) Start(ctx context.Context, host component.Host) error { var err error s.startTimestamp = time.Now() - s.httpServer, err = s.httpConfig.ToServerContext(ctx, host, s.telemetry, s.mux) + s.httpServer, err = s.httpConfig.ToServer(ctx, host, s.telemetry, s.mux) if err != nil { return err } - ln, err := s.httpConfig.ToListenerContext(ctx) + ln, err := s.httpConfig.ToListener(ctx) if err != nil { return fmt.Errorf("failed to bind to address %s: %w", s.httpConfig.Endpoint, err) } From 47d2100cf78bcebf6307db9c7632b6526d0ef8f5 Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 25 Apr 2024 09:50:17 -0700 Subject: [PATCH 26/26] Update go.mod --- extension/healthcheckv2extension/go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/healthcheckv2extension/go.mod b/extension/healthcheckv2extension/go.mod index e41ca7fad5915..cb0a38f48d534 100644 --- a/extension/healthcheckv2extension/go.mod +++ b/extension/healthcheckv2extension/go.mod @@ -17,7 +17,9 @@ require ( go.opentelemetry.io/otel/metric v1.25.0 go.opentelemetry.io/otel/trace v1.25.0 go.uber.org/goleak v1.3.0 + go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 + google.golang.org/grpc v1.63.2 ) require ( @@ -61,12 +63,10 @@ require ( go.opentelemetry.io/otel/exporters/prometheus v0.47.0 // indirect go.opentelemetry.io/otel/sdk v1.25.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.25.0 // indirect - go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect - google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect )