Skip to content

Commit 020b7b5

Browse files
authored
Cortex Exporter Setup Pipeline and Configuration (#205)
* Add Config struct definition, test files, and testing data * Add tests for Validate method * Implement Validate method * Add Config utility files and testing data * Add helper function to create YAML files for testing * Add NewConfig function, Options interface, and tests * Add Viper tags to Config struct and two With functions * Add test for WithFilepath * Add WithClient and test for WithClient * Remove default for http client and adjust tests * Add check for conflicting authentication types and update tests * Update README.md * Run make precommit * Add example Config struct * Add NewRawExporter for creating an Exporter and TestNewRawExporter * Add NewExportPipeline for creating a controller and TestNewExportPipeline * Add InstallNewPipeline and TestInstallNewPipeline * Update README and run make precommit
1 parent df49040 commit 020b7b5

File tree

13 files changed

+1310
-1
lines changed

13 files changed

+1310
-1
lines changed

exporters/metric/cortex/README.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,111 @@
22

33
Work in progress exporter to send data from the OpenTelemetry Go SDK to Cortex using the Prometheus
44
Remote Write API.
5+
6+
## Setting up an Exporter
7+
8+
Users can setup the Exporter with the `InstallNewPipeline` function. It requires a
9+
`Config` struct and returns a push Controller that will periodically collect and push
10+
data.
11+
12+
Example:
13+
```go
14+
pusher, err := cortex.InstallNewPipeline(config)
15+
if err != nil {
16+
return err
17+
}
18+
19+
// Make instruments and record data
20+
```
21+
22+
## Configuration
23+
24+
The Exporter needs certain information, such as the endpoint URL and push interval
25+
duration, to function properly. This information is stored in a `Config` struct, which is
26+
passed into the Exporter during the setup pipeline.
27+
28+
### Creating the Config struct
29+
30+
Users can either create the struct manually or use a `utils` submodule in the package to
31+
read settings from a YAML file into a new Config struct using `Viper`. Here are the
32+
supported YAML properties as well as the Config struct that they map to.
33+
34+
```yaml
35+
# The URL of the endpoint to send samples to.
36+
url: <string>
37+
38+
# Timeout for requests to the remote write endpoint.
39+
[ remote_timeout: <duration> | default = 30s ]
40+
41+
# Name of the remote write config, which if specified must be unique among remote write configs. The name will be used in metrics and logging in place of a generated value to help users distinguish between remote write configs.
42+
[ name: <string>]
43+
44+
# Sets the `Authorization` header on every remote write request with the
45+
# configured username and password.
46+
# password and password_file are mutually exclusive.
47+
basic_auth:
48+
[ username: <string>]
49+
[ password: <string>]
50+
[ password_file: <string> ]
51+
52+
# Sets the `Authorization` header on every remote write request with
53+
# the configured bearer token. It is mutually exclusive with `bearer_token_file`.
54+
[ bearer_token: <string> ]
55+
56+
# Sets the `Authorization` header on every remote write request with the bearer token
57+
# read from the configured file. It is mutually exclusive with `bearer_token`.
58+
[ bearer_token_file: /path/to/bearer/token/file ]
59+
60+
# Configures the remote write request's TLS settings.
61+
tls_config:
62+
# CA certificate to validate API server certificate with.
63+
[ ca_file: <filename>]
64+
65+
# Certificate and key files for client cert authentication to the server.
66+
[ cert_file: <filename> ]
67+
[ key_file: <filename> ]
68+
69+
# ServerName extension to indicate the name of the server.
70+
# https://tools.ietf.org/html/rfc4366#section-3.1
71+
[ server_name: <string> ]
72+
73+
# Disable validation of the server certificate.
74+
[ insecure_skip_verify: <boolean> ]
75+
76+
# Optional proxy URL.
77+
[ proxy_url: <string>]
78+
```
79+
80+
```go
81+
type Config struct {
82+
Endpoint string `mapstructure:"url"`
83+
RemoteTimeout time.Duration `mapstructure:"remote_timeout"`
84+
Name string `mapstructure:"name"`
85+
BasicAuth map[string]string `mapstructure:"basic_auth"`
86+
BearerToken string `mapstructure:"bearer_token"`
87+
BearerTokenFile string `mapstructure:"bearer_token_file"`
88+
TLSConfig map[string]string `mapstructure:"tls_config"`
89+
ProxyURL string `mapstructure:"proxy_url"`
90+
PushInterval time.Duration `mapstructure:"push_interval"`
91+
Headers map[string]string `mapstructure:"headers"`
92+
Client *http.Client
93+
}
94+
```
95+
96+
The struct is used during the setup pipeline:
97+
98+
```go
99+
// Create Config struct using utils module.
100+
config, err := utils.NewConfig("config.yml")
101+
if err != nil {
102+
return err
103+
}
104+
105+
// Setup the exporter.
106+
pusher, err := cortex.InstallNewPipeline(config)
107+
if err != nil {
108+
return err
109+
}
110+
111+
// Add instruments and start collecting data.
112+
```

exporters/metric/cortex/config.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cortex
16+
17+
import (
18+
"fmt"
19+
"net/http"
20+
"time"
21+
)
22+
23+
var (
24+
// ErrTwoPasswords occurs when the YAML file contains both `password` and
25+
// `password_file`.
26+
ErrTwoPasswords = fmt.Errorf("Cannot have two passwords in the YAML file")
27+
28+
// ErrTwoBearerTokens occurs when the YAML file contains both `bearer_token` and
29+
// `bearer_token_file`.
30+
ErrTwoBearerTokens = fmt.Errorf("Cannot have two bearer tokens in the YAML file")
31+
32+
// ErrConflictingAuthorization occurs when the YAML file contains both BasicAuth and
33+
// bearer token authorization
34+
ErrConflictingAuthorization = fmt.Errorf("Cannot have both basic auth and bearer token authorization")
35+
)
36+
37+
// Config contains properties the Exporter uses to export metrics data to Cortex.
38+
type Config struct {
39+
Endpoint string `mapstructure:"url"`
40+
RemoteTimeout time.Duration `mapstructure:"remote_timeout"`
41+
Name string `mapstructure:"name"`
42+
BasicAuth map[string]string `mapstructure:"basic_auth"`
43+
BearerToken string `mapstructure:"bearer_token"`
44+
BearerTokenFile string `mapstructure:"bearer_token_file"`
45+
TLSConfig map[string]string `mapstructure:"tls_config"`
46+
ProxyURL string `mapstructure:"proxy_url"`
47+
PushInterval time.Duration `mapstructure:"push_interval"`
48+
Headers map[string]string `mapstructure:"headers"`
49+
Client *http.Client
50+
}
51+
52+
// Validate checks a Config struct for missing required properties and property conflicts.
53+
// Additionally, it adds default values to missing properties when there is a default.
54+
func (c *Config) Validate() error {
55+
// Check for mutually exclusive properties.
56+
if c.BasicAuth != nil {
57+
if c.BearerToken != "" || c.BearerTokenFile != "" {
58+
return ErrConflictingAuthorization
59+
}
60+
if c.BasicAuth["password"] != "" && c.BasicAuth["password_file"] != "" {
61+
return ErrTwoPasswords
62+
}
63+
}
64+
if c.BearerToken != "" && c.BearerTokenFile != "" {
65+
return ErrTwoBearerTokens
66+
}
67+
68+
// Add default values for missing properties.
69+
if c.Endpoint == "" {
70+
c.Endpoint = "/api/prom/push"
71+
}
72+
if c.RemoteTimeout == 0 {
73+
c.RemoteTimeout = 30 * time.Second
74+
}
75+
// Default time interval between pushes for the push controller is 10s.
76+
if c.PushInterval == 0 {
77+
c.PushInterval = 10 * time.Second
78+
}
79+
80+
return nil
81+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cortex_test
16+
17+
import (
18+
"time"
19+
20+
"go.opentelemetry.io/contrib/exporters/metric/cortex"
21+
)
22+
23+
// Config struct with default values. This is used to verify the output of Validate().
24+
var validatedStandardConfig = cortex.Config{
25+
Endpoint: "/api/prom/push",
26+
Name: "Config",
27+
RemoteTimeout: 30 * time.Second,
28+
PushInterval: 10 * time.Second,
29+
}
30+
31+
// Config struct with default values other than the remote timeout. This is used to verify
32+
// the output of Validate().
33+
var validatedCustomTimeoutConfig = cortex.Config{
34+
Endpoint: "/api/prom/push",
35+
Name: "Config",
36+
RemoteTimeout: 10 * time.Second,
37+
PushInterval: 10 * time.Second,
38+
}
39+
40+
// Example Config struct with a custom remote timeout.
41+
var exampleRemoteTimeoutConfig = cortex.Config{
42+
Endpoint: "/api/prom/push",
43+
Name: "Config",
44+
PushInterval: 10 * time.Second,
45+
RemoteTimeout: 10 * time.Second,
46+
}
47+
48+
// Example Config struct without a remote timeout.
49+
var exampleNoRemoteTimeoutConfig = cortex.Config{
50+
Endpoint: "/api/prom/push",
51+
Name: "Config",
52+
PushInterval: 10 * time.Second,
53+
}
54+
55+
// Example Config struct without a push interval.
56+
var exampleNoPushIntervalConfig = cortex.Config{
57+
Endpoint: "/api/prom/push",
58+
Name: "Config",
59+
RemoteTimeout: 30 * time.Second,
60+
}
61+
62+
// Example Config struct without a http client.
63+
var exampleNoClientConfig = cortex.Config{
64+
Endpoint: "/api/prom/push",
65+
Name: "Config",
66+
RemoteTimeout: 30 * time.Second,
67+
PushInterval: 10 * time.Second,
68+
}
69+
70+
// Example Config struct without an endpoint.
71+
var exampleNoEndpointConfig = cortex.Config{
72+
Name: "Config",
73+
RemoteTimeout: 30 * time.Second,
74+
PushInterval: 10 * time.Second,
75+
}
76+
77+
// Example Config struct with two bearer tokens.
78+
var exampleTwoBearerTokenConfig = cortex.Config{
79+
Endpoint: "/api/prom/push",
80+
Name: "Config",
81+
RemoteTimeout: 30 * time.Second,
82+
PushInterval: 10 * time.Second,
83+
BearerToken: "bearer_token",
84+
BearerTokenFile: "bearer_token_file",
85+
}
86+
87+
// Example Config struct with two passwords.
88+
var exampleTwoPasswordConfig = cortex.Config{
89+
Endpoint: "/api/prom/push",
90+
Name: "Config",
91+
RemoteTimeout: 30 * time.Second,
92+
PushInterval: 10 * time.Second,
93+
BasicAuth: map[string]string{
94+
"username": "user",
95+
"password": "password",
96+
"password_file": "passwordFile",
97+
},
98+
}
99+
100+
// Example Config struct with both basic auth and bearer token authentication.
101+
var exampleTwoAuthConfig = cortex.Config{
102+
Endpoint: "/api/prom/push",
103+
Name: "Config",
104+
RemoteTimeout: 30 * time.Second,
105+
PushInterval: 10 * time.Second,
106+
BasicAuth: map[string]string{
107+
"username": "user",
108+
"password": "password",
109+
"password_file": "passwordFile",
110+
},
111+
BearerToken: "bearer_token",
112+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cortex_test
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/require"
21+
22+
"go.opentelemetry.io/contrib/exporters/metric/cortex"
23+
)
24+
25+
// TestValidate checks whether Validate() returns the correct error and sets the correct
26+
// default values.
27+
func TestValidate(t *testing.T) {
28+
tests := []struct {
29+
testName string
30+
config *cortex.Config
31+
expectedConfig *cortex.Config
32+
expectedError error
33+
}{
34+
{
35+
testName: "Config with Conflicting Bearer Tokens",
36+
config: &exampleTwoBearerTokenConfig,
37+
expectedConfig: nil,
38+
expectedError: cortex.ErrTwoBearerTokens,
39+
},
40+
{
41+
testName: "Config with Conflicting Passwords",
42+
config: &exampleTwoPasswordConfig,
43+
expectedConfig: nil,
44+
expectedError: cortex.ErrTwoPasswords,
45+
},
46+
{
47+
testName: "Config with Custom Timeout",
48+
config: &exampleRemoteTimeoutConfig,
49+
expectedConfig: &validatedCustomTimeoutConfig,
50+
expectedError: nil,
51+
},
52+
{
53+
testName: "Config with no Endpoint",
54+
config: &exampleNoEndpointConfig,
55+
expectedConfig: &validatedStandardConfig,
56+
expectedError: nil,
57+
},
58+
{
59+
testName: "Config with no Remote Timeout",
60+
config: &exampleNoRemoteTimeoutConfig,
61+
expectedConfig: &validatedStandardConfig,
62+
expectedError: nil,
63+
},
64+
{
65+
testName: "Config with no Push Interval",
66+
config: &exampleNoPushIntervalConfig,
67+
expectedConfig: &validatedStandardConfig,
68+
expectedError: nil,
69+
},
70+
{
71+
testName: "Config with no Client",
72+
config: &exampleNoClientConfig,
73+
expectedConfig: &validatedStandardConfig,
74+
expectedError: nil,
75+
},
76+
{
77+
testName: "Config with both BasicAuth and BearerTokens",
78+
config: &exampleTwoAuthConfig,
79+
expectedConfig: nil,
80+
expectedError: cortex.ErrConflictingAuthorization,
81+
},
82+
}
83+
for _, test := range tests {
84+
t.Run(test.testName, func(t *testing.T) {
85+
err := test.config.Validate()
86+
require.Equal(t, err, test.expectedError)
87+
if err == nil {
88+
require.Equal(t, test.config, test.expectedConfig)
89+
}
90+
})
91+
}
92+
}

0 commit comments

Comments
 (0)