467 lines
14 KiB
Go
467 lines
14 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package otlploggrpc
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry"
|
|
)
|
|
|
|
const (
|
|
weakCertificate = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBhzCCASygAwIBAgIRANHpHgAWeTnLZpTSxCKs0ggwCgYIKoZIzj0EAwIwEjEQ
|
|
MA4GA1UEChMHb3RlbC1nbzAeFw0yMTA0MDExMzU5MDNaFw0yMTA0MDExNDU5MDNa
|
|
MBIxEDAOBgNVBAoTB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS9
|
|
nWSkmPCxShxnp43F+PrOtbGV7sNfkbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0Z
|
|
sJCLHGogQsYnWJBXUZOVo2MwYTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
|
|
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAsBgNVHREEJTAjgglsb2NhbGhvc3SHEAAA
|
|
AAAAAAAAAAAAAAAAAAGHBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhANwZVVKvfvQ/
|
|
1HXsTvgH+xTQswOwSSKYJ1cVHQhqK7ZbAiEAus8NxpTRnp5DiTMuyVmhVNPB+bVH
|
|
Lhnm4N/QDk5rek0=
|
|
-----END CERTIFICATE-----
|
|
`
|
|
weakPrivateKey = `
|
|
-----BEGIN PRIVATE KEY-----
|
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN8HEXiXhvByrJ1zK
|
|
SFT6Y2l2KqDWwWzKf+t4CyWrNKehRANCAAS9nWSkmPCxShxnp43F+PrOtbGV7sNf
|
|
kbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0ZsJCLHGogQsYnWJBXUZOV
|
|
-----END PRIVATE KEY-----
|
|
`
|
|
)
|
|
|
|
func newTLSConf(cert, key []byte) (*tls.Config, error) {
|
|
cp := x509.NewCertPool()
|
|
if ok := cp.AppendCertsFromPEM(cert); !ok {
|
|
return nil, errors.New("failed to append certificate to the cert pool")
|
|
}
|
|
crt, err := tls.X509KeyPair(cert, key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
crts := []tls.Certificate{crt}
|
|
return &tls.Config{RootCAs: cp, Certificates: crts}, nil
|
|
}
|
|
|
|
func TestNewConfig(t *testing.T) {
|
|
orig := readFile
|
|
readFile = func() func(name string) ([]byte, error) {
|
|
index := map[string][]byte{
|
|
"cert_path": []byte(weakCertificate),
|
|
"key_path": []byte(weakPrivateKey),
|
|
"invalid_cert": []byte("invalid certificate file."),
|
|
"invalid_key": []byte("invalid key file."),
|
|
}
|
|
return func(name string) ([]byte, error) {
|
|
b, ok := index[name]
|
|
if !ok {
|
|
err := fmt.Errorf("file does not exist: %s", name)
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
}()
|
|
t.Cleanup(func() { readFile = orig })
|
|
|
|
tlsCfg, err := newTLSConf([]byte(weakCertificate), []byte(weakPrivateKey))
|
|
require.NoError(t, err, "testing TLS config")
|
|
|
|
headers := map[string]string{"a": "A"}
|
|
rc := retry.Config{}
|
|
|
|
dialOptions := []grpc.DialOption{grpc.WithUserAgent("test-agent")}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
options []Option
|
|
envars map[string]string
|
|
want config
|
|
errs []string
|
|
}{
|
|
{
|
|
name: "Defaults",
|
|
want: config{
|
|
endpoint: newSetting(defaultEndpoint),
|
|
timeout: newSetting(defaultTimeout),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "Options",
|
|
options: []Option{
|
|
WithInsecure(),
|
|
WithEndpoint("test"),
|
|
WithEndpointURL("http://test:8080/path"),
|
|
WithReconnectionPeriod(time.Second),
|
|
WithCompressor("gzip"),
|
|
WithHeaders(headers),
|
|
WithTLSCredentials(credentials.NewTLS(tlsCfg)),
|
|
WithServiceConfig("{}"),
|
|
WithDialOption(dialOptions...),
|
|
WithGRPCConn(&grpc.ClientConn{}),
|
|
WithTimeout(2 * time.Second),
|
|
WithRetry(RetryConfig(rc)),
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("test:8080"),
|
|
insecure: newSetting(true),
|
|
headers: newSetting(headers),
|
|
compression: newSetting(GzipCompression),
|
|
timeout: newSetting(2 * time.Second),
|
|
retryCfg: newSetting(rc),
|
|
gRPCCredentials: newSetting(credentials.NewTLS(tlsCfg)),
|
|
serviceConfig: newSetting("{}"),
|
|
reconnectionPeriod: newSetting(time.Second),
|
|
gRPCConn: newSetting(&grpc.ClientConn{}),
|
|
dialOptions: newSetting(dialOptions),
|
|
},
|
|
},
|
|
{
|
|
name: "WithEndpointURL",
|
|
options: []Option{
|
|
WithEndpointURL("http://test:8080/path"),
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("test:8080"),
|
|
insecure: newSetting(true),
|
|
timeout: newSetting(defaultTimeout),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "EndpointPrecedence",
|
|
options: []Option{
|
|
WithEndpointURL("https://test:8080/path"),
|
|
WithEndpoint("not-test:9090"),
|
|
WithInsecure(),
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("not-test:9090"),
|
|
insecure: newSetting(true),
|
|
timeout: newSetting(defaultTimeout),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "EndpointURLPrecedence",
|
|
options: []Option{
|
|
WithEndpoint("not-test:9090"),
|
|
WithInsecure(),
|
|
WithEndpointURL("https://test:8080/path"),
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("test:8080"),
|
|
insecure: newSetting(false),
|
|
timeout: newSetting(defaultTimeout),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "LogEnvironmentVariables",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/prefix",
|
|
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "a=A",
|
|
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
|
|
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
insecure: newSetting(false),
|
|
tlsCfg: newSetting(tlsCfg),
|
|
headers: newSetting(headers),
|
|
compression: newSetting(GzipCompression),
|
|
timeout: newSetting(15 * time.Second),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "LogEndpointEnvironmentVariablesDefaultPath",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "http://env.endpoint",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint"),
|
|
insecure: newSetting(true),
|
|
timeout: newSetting(defaultTimeout),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "OTLPEnvironmentVariables",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env.endpoint:8080/prefix",
|
|
"OTEL_EXPORTER_OTLP_HEADERS": "a=A",
|
|
"OTEL_EXPORTER_OTLP_COMPRESSION": "none",
|
|
"OTEL_EXPORTER_OTLP_TIMEOUT": "15000",
|
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_CLIENT_KEY": "key_path",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
insecure: newSetting(true),
|
|
tlsCfg: newSetting(tlsCfg),
|
|
headers: newSetting(headers),
|
|
compression: newSetting(NoCompression),
|
|
timeout: newSetting(15 * time.Second),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "OTLPEndpointEnvironmentVariablesDefaultPath",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://env.endpoint",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint"),
|
|
insecure: newSetting(true),
|
|
timeout: newSetting(defaultTimeout),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "EnvironmentVariablesPrecedence",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://ignored:9090/alt",
|
|
"OTEL_EXPORTER_OTLP_HEADERS": "b=B",
|
|
"OTEL_EXPORTER_OTLP_COMPRESSION": "none",
|
|
"OTEL_EXPORTER_OTLP_TIMEOUT": "30000",
|
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "invalid_cert",
|
|
"OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE": "invalid_cert",
|
|
"OTEL_EXPORTER_OTLP_CLIENT_KEY": "invalid_key",
|
|
|
|
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/path",
|
|
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "a=A",
|
|
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
|
|
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
insecure: newSetting(false),
|
|
tlsCfg: newSetting(tlsCfg),
|
|
headers: newSetting(headers),
|
|
compression: newSetting(GzipCompression),
|
|
timeout: newSetting(15 * time.Second),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
},
|
|
{
|
|
name: "OptionsPrecedence",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://ignored:9090/alt",
|
|
"OTEL_EXPORTER_OTLP_HEADERS": "b=B",
|
|
"OTEL_EXPORTER_OTLP_COMPRESSION": "none",
|
|
"OTEL_EXPORTER_OTLP_TIMEOUT": "30000",
|
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "invalid_cert",
|
|
"OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE": "invalid_cert",
|
|
"OTEL_EXPORTER_OTLP_CLIENT_KEY": "invalid_key",
|
|
|
|
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://env.endpoint:8080/prefix",
|
|
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "a=A",
|
|
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "gzip",
|
|
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "15000",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "cert_path",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "key_path",
|
|
},
|
|
options: []Option{
|
|
WithEndpoint("foo"),
|
|
WithEndpointURL("https://test/path"),
|
|
WithInsecure(),
|
|
WithTLSCredentials(credentials.NewTLS(tlsCfg)),
|
|
WithCompressor("gzip"),
|
|
WithHeaders(headers),
|
|
WithTimeout(time.Second),
|
|
WithRetry(RetryConfig(rc)),
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("test"),
|
|
insecure: newSetting(true),
|
|
tlsCfg: newSetting(tlsCfg),
|
|
headers: newSetting(headers),
|
|
compression: newSetting(GzipCompression),
|
|
timeout: newSetting(time.Second),
|
|
retryCfg: newSetting(rc),
|
|
gRPCCredentials: newSetting(credentials.NewTLS(tlsCfg)),
|
|
},
|
|
},
|
|
{
|
|
name: "InvalidEnvironmentVariables",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "%invalid",
|
|
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "a,%ZZ=valid,key=%ZZ",
|
|
"OTEL_EXPORTER_OTLP_LOGS_COMPRESSION": "xz",
|
|
"OTEL_EXPORTER_OTLP_LOGS_TIMEOUT": "100 seconds",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE": "invalid_cert",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE": "invalid_cert",
|
|
"OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY": "invalid_key",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting(defaultEndpoint),
|
|
timeout: newSetting(defaultTimeout),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
},
|
|
errs: []string{
|
|
`invalid OTEL_EXPORTER_OTLP_LOGS_ENDPOINT value %invalid: parse "%invalid": invalid URL escape "%in"`,
|
|
`failed to load TLS:`,
|
|
`certificate not added`,
|
|
`tls: failed to find any PEM data in certificate input`,
|
|
`invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value a,%ZZ=valid,key=%ZZ:`,
|
|
`invalid header: a`,
|
|
`invalid header key: %ZZ`,
|
|
`invalid header value: %ZZ`,
|
|
`invalid OTEL_EXPORTER_OTLP_LOGS_COMPRESSION value xz: unknown compression: xz`,
|
|
`invalid OTEL_EXPORTER_OTLP_LOGS_TIMEOUT value 100 seconds: strconv.Atoi: parsing "100 seconds": invalid syntax`,
|
|
},
|
|
},
|
|
{
|
|
name: "OptionEndpointURLWithoutScheme",
|
|
options: []Option{
|
|
WithEndpointURL("//env.endpoint:8080/prefix"),
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
timeout: newSetting(defaultTimeout),
|
|
},
|
|
},
|
|
{
|
|
name: "EnvEndpointWithoutScheme",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "//env.endpoint:8080/prefix",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
timeout: newSetting(defaultTimeout),
|
|
},
|
|
},
|
|
{
|
|
name: "DefaultEndpointWithEnvInsecure",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting(defaultEndpoint),
|
|
insecure: newSetting(true),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
timeout: newSetting(defaultTimeout),
|
|
},
|
|
},
|
|
{
|
|
name: "EnvEndpointWithoutSchemeWithEnvInsecure",
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "//env.endpoint:8080/prefix",
|
|
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
insecure: newSetting(true),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
timeout: newSetting(defaultTimeout),
|
|
},
|
|
},
|
|
{
|
|
name: "OptionEndpointURLWithoutSchemeWithEnvInsecure",
|
|
options: []Option{
|
|
WithEndpointURL("//env.endpoint:8080/prefix"),
|
|
},
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
insecure: newSetting(true),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
timeout: newSetting(defaultTimeout),
|
|
},
|
|
},
|
|
{
|
|
name: "OptionEndpointWithEnvInsecure",
|
|
options: []Option{
|
|
WithEndpoint("env.endpoint:8080"),
|
|
},
|
|
envars: map[string]string{
|
|
"OTEL_EXPORTER_OTLP_LOGS_INSECURE": "true",
|
|
},
|
|
want: config{
|
|
endpoint: newSetting("env.endpoint:8080"),
|
|
insecure: newSetting(true),
|
|
retryCfg: newSetting(defaultRetryCfg),
|
|
timeout: newSetting(defaultTimeout),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
for key, value := range tc.envars {
|
|
t.Setenv(key, value)
|
|
}
|
|
|
|
var err error
|
|
t.Cleanup(func(orig otel.ErrorHandler) func() {
|
|
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(e error) {
|
|
err = errors.Join(err, e)
|
|
}))
|
|
return func() { otel.SetErrorHandler(orig) }
|
|
}(otel.GetErrorHandler()))
|
|
c := newConfig(tc.options)
|
|
|
|
// Do not compare pointer values.
|
|
assertTLSConfig(t, tc.want.tlsCfg, c.tlsCfg)
|
|
var emptyTLS setting[*tls.Config]
|
|
c.tlsCfg, tc.want.tlsCfg = emptyTLS, emptyTLS
|
|
|
|
assert.Equal(t, tc.want, c)
|
|
|
|
for _, errMsg := range tc.errs {
|
|
assert.ErrorContains(t, err, errMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertTLSConfig(t *testing.T, want, got setting[*tls.Config]) {
|
|
t.Helper()
|
|
|
|
assert.Equal(t, want.Set, got.Set, "setting Set")
|
|
if !want.Set {
|
|
return
|
|
}
|
|
|
|
if want.Value == nil {
|
|
assert.Nil(t, got.Value, "*tls.Config")
|
|
return
|
|
}
|
|
require.NotNil(t, got.Value, "*tls.Config")
|
|
|
|
if want.Value.RootCAs == nil {
|
|
assert.Nil(t, got.Value.RootCAs, "*tls.Config.RootCAs")
|
|
} else {
|
|
if assert.NotNil(t, got.Value.RootCAs, "RootCAs") {
|
|
assert.True(t, want.Value.RootCAs.Equal(got.Value.RootCAs), "RootCAs equal")
|
|
}
|
|
}
|
|
assert.Equal(t, want.Value.Certificates, got.Value.Certificates, "Certificates")
|
|
}
|