systemtest/apmservertest/config.go (305 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package apmservertest
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"os"
"sort"
"time"
)
const (
defaultElasticsearchHost = "localhost"
defaultElasticsearchPort = "9200"
defaultElasticsearchUser = "apm_server_user"
defaultElasticsearchPass = "changeme"
defaultKibanaHost = "localhost"
defaultKibanaPort = "5601"
defaultKibanaUser = "apm_user_ro"
defaultKibanaPass = "changeme"
defaultKibanaBasePath = ""
)
// Config holds APM Server configuration.
type Config struct {
Kibana *KibanaConfig `json:"apm-server.kibana,omitempty"`
Sampling *SamplingConfig `json:"apm-server.sampling,omitempty"`
RUM *RUMConfig `json:"apm-server.rum,omitempty"`
DefaultServiceEnvironment string `json:"apm-server.default_service_environment,omitempty"`
AgentConfig *AgentConfig `json:"apm-server.agent.config,omitempty"`
TLS *TLSConfig `json:"apm-server.ssl,omitempty"`
// AgentAuth holds configuration for APM agent authorization.
AgentAuth AgentAuthConfig `json:"apm-server.auth"`
// ResponseHeaders holds headers to add to all APM Server HTTP responses.
ResponseHeaders http.Header `json:"apm-server.response_headers,omitempty"`
// Instrumentation holds configuration for libbeat and apm-server instrumentation.
Instrumentation *InstrumentationConfig `json:"instrumentation,omitempty"`
// Logging holds configuration for libbeat logging.
//
// We always enable JSON output, and log to stderr.
Logging *LoggingConfig `json:"logging,omitempty"`
// Monitoring holds configuration for stack monitoring.
Monitoring *MonitoringConfig `json:"monitoring,omitempty"`
// Output holds configuration for the libbeat output.
Output OutputConfig `json:"output"`
// AggregationConfig holds configuration related to aggregation.
Aggregation *AggregationConfig `json:"apm-server.aggregation,omitempty"`
}
// Args formats cfg as a list of arguments to pass to apm-server,
// in the form ["-E", "k=v", "-E", "k=v", ...]
func (cfg Config) Args() ([]string, error) {
return configArgs(cfg, nil)
}
// TLSConfig holds configuration to TLS encryption of agent/server communication.
type TLSConfig struct {
// ClientAuthentication controls whether TLS client authentication is
// enabled, and optional or required. If this is non-empty, then
// `apm-server.ssl.certificate_authorities` will be set to the server's
// self-signed certificate path.
ClientAuthentication string `json:"client_authentication,omitempty"`
CipherSuites []string `json:"cipher_suites,omitempty"`
SupportedProtocols []string `json:"supported_protocols,omitempty"`
}
// AgentConfig holds configuration related to the Kibana-based or
// Elasticsearch-based implementation of agent configuration.
type AgentConfig struct {
CacheExpiration time.Duration
ElasticsearchUsername string
ElasticsearchPassword string
ElasticsearchAPIKey string
}
func (c *AgentConfig) MarshalJSON() ([]byte, error) {
// time.Duration is encoded as int64.
// Convert time.Durations to durations, to encode as duration strings.
type config struct {
CacheExpiration string `json:"cache.expiration,omitempty"`
ElasticsearchUsername string `json:"elasticsearch.username,omitempty"`
ElasticsearchPassword string `json:"elasticsearch.password,omitempty"`
ElasticsearchAPIKey string `json:"elasticsearch.api_key,omitempty"`
}
return json.Marshal(config{
CacheExpiration: durationString(c.CacheExpiration),
ElasticsearchUsername: c.ElasticsearchUsername,
ElasticsearchPassword: c.ElasticsearchPassword,
ElasticsearchAPIKey: c.ElasticsearchAPIKey,
})
}
// LoggingConfig holds APM Server logging configuration.
type LoggingConfig struct {
Files FileLoggingConfig `json:"files"`
}
// FileLoggingConfig holds APM Server file logging configuration.
type FileLoggingConfig struct {
Path string `json:"path,omitempty"`
}
// KibanaConfig holds APM Server Kibana connection configuration.
type KibanaConfig struct {
Enabled bool `json:"enabled"`
Host string `json:"host,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
// SamplingConfig holds APM Server trace sampling configuration.
type SamplingConfig struct {
Tail *TailSamplingConfig `json:"tail,omitempty"`
}
// TailSamplingConfig holds APM Server tail-based sampling configuration.
type TailSamplingConfig struct {
Enabled bool
Interval time.Duration
Policies []TailSamplingPolicy
}
func (t *TailSamplingConfig) MarshalJSON() ([]byte, error) {
// time.Duration is encoded as int64.
// Convert time.Durations to durations, to encode as duration strings.
type config struct {
Enabled bool `json:"enabled"`
Interval string `json:"interval"`
Policies []TailSamplingPolicy `json:"policies,omitempty"`
}
return json.Marshal(config{
Enabled: t.Enabled,
Interval: durationString(t.Interval),
Policies: t.Policies,
})
}
// TailSamplingPolicy holds an APM Server tail-based sampling policy.
type TailSamplingPolicy struct {
ServiceName string `json:"service.name,omitempty"`
ServiceEnvironment string `json:"service.environment,omitempty"`
TraceName string `json:"trace.name,omitempty"`
TraceOutcome string `json:"trace.outcome,omitempty"`
SampleRate float64 `json:"sample_rate"`
}
// RUMConfig holds APM Server RUM configuration.
type RUMConfig struct {
Enabled bool `json:"enabled"`
// AllowOrigins holds a list of allowed origins for RUM.
AllowOrigins []string `json:"allow_origins,omitempty"`
// AllowHeaders holds a list of Access-Control-Allow-Headers for RUM.
AllowHeaders []string `json:"allow_headers,omitempty"`
// AllowServiceNames holds a list of exclusively allowed service names for
// RUM events.
AllowServiceNames []string `json:"allow_service_names,omitempty"`
// ResponseHeaders holds headers to add to all APM Server RUM HTTP responses.
ResponseHeaders http.Header `json:"response_headers,omitempty"`
Sourcemap *RUMSourcemapConfig `json:"source_mapping,omitempty"`
}
// RUMSourcemapConfig holds APM Server RUM sourcemap configuration.
type RUMSourcemapConfig struct {
Enabled bool `json:"enabled"`
ESConfig *ElasticsearchOutputConfig `json:"elasticsearch"`
}
// APIKeyConfig holds agent auth configuration.
type AgentAuthConfig struct {
SecretToken string `json:"secret_token,omitempty"`
APIKey *APIKeyAuthConfig `json:"api_key,omitempty"`
Anonymous *AnonymousAuthConfig `json:"anonymous,omitempty"`
}
// APIKeyAuthConfig holds API Key agent auth configuration.
type APIKeyAuthConfig struct {
Enabled bool `json:"enabled"`
}
// AnonymousAuthConfig holds anonymous agent auth configuration.
type AnonymousAuthConfig struct {
Enabled bool `json:"enabled"`
AllowAgent []string `json:"allow_agent,omitempty"`
AllowService []string `json:"allow_service,omitempty"`
RateLimit *RateLimitConfig `json:"rate_limit,omitempty"`
}
// RateLimitConfig holds event rate limit configuration.
type RateLimitConfig struct {
IPLimit int `json:"ip_limit,omitempty"`
EventLimit int `json:"event_limit,omitempty"`
}
// InstrumentationConfig holds APM Server instrumentation configuration.
type InstrumentationConfig struct {
Enabled bool `json:"enabled"`
Hosts []string `json:"hosts,omitempty"`
APIKey string `json:"api_key,omitempty"`
SecretToken string `json:"secret_token,omitempty"`
}
// OutputConfig holds APM Server libbeat output configuration.
type OutputConfig struct {
Console *ConsoleOutputConfig `json:"console,omitempty"`
Elasticsearch *ElasticsearchOutputConfig `json:"elasticsearch,omitempty"`
Logstash *LogstashOutputConfig `json:"logstash,omitempty"`
}
// ConsoleOutputConfig holds APM Server libbeat console output configuration.
type ConsoleOutputConfig struct {
Enabled bool `json:"enabled"`
}
// LogstashOutputConfig holds APM Server libbeat logstash output configuration.
type LogstashOutputConfig struct {
Enabled bool `json:"enabled"`
Hosts []string `json:"hosts,omitempty"`
BulkMaxSize int `json:"bulk_max_size,omitempty"`
}
// ElasticsearchOutputConfig holds APM Server libbeat Elasticsearch output configuration.
type ElasticsearchOutputConfig struct {
Enabled bool `json:"enabled"`
Hosts []string `json:"hosts,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
APIKey string `json:"api_key,omitempty"`
FlushBytes string `json:"flush_bytes,omitempty"`
FlushInterval time.Duration `json:"flush_interval,omitempty"`
}
func (c *ElasticsearchOutputConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Enabled bool `json:"enabled"`
Hosts []string `json:"hosts,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
APIKey string `json:"api_key,omitempty"`
FlushBytes string `json:"flush_bytes,omitempty"`
FlushInterval string `json:"flush_interval,omitempty"`
}{
Enabled: c.Enabled,
Hosts: c.Hosts,
Username: c.Username,
Password: c.Password,
APIKey: c.APIKey,
FlushBytes: c.FlushBytes,
FlushInterval: durationString(c.FlushInterval),
})
}
// MonitoringConfig holds APM Server stack monitoring configuration.
type MonitoringConfig struct {
Enabled bool
Elasticsearch *ElasticsearchOutputConfig
MetricsPeriod time.Duration
StatePeriod time.Duration
}
func (m *MonitoringConfig) MarshalJSON() ([]byte, error) {
// time.Duration is encoded as int64.
// Convert time.Durations to durations, to encode as duration strings.
type config struct {
Enabled bool `json:"enabled"`
Elasticsearch *ElasticsearchOutputConfig `json:"elasticsearch,omitempty"`
MetricsPeriod string `json:"elasticsearch.metrics.period,omitempty"`
StatePeriod string `json:"elasticsearch.state.period,omitempty"`
}
return json.Marshal(config{
Enabled: m.Enabled,
Elasticsearch: m.Elasticsearch,
MetricsPeriod: durationString(m.MetricsPeriod),
StatePeriod: durationString(m.StatePeriod),
})
}
func durationString(d time.Duration) string {
if d == 0 {
return ""
}
return d.String()
}
// AggregationConfig holds configuration related to aggregation.
type AggregationConfig struct {
MaxServices int `json:"max_services,omitempty"`
}
func configArgs(cfg Config, extra map[string]interface{}) ([]string, error) {
kv, err := flattenConfig(cfg, extra)
if err != nil {
return nil, err
}
out := make([]string, len(kv)*2)
for i, kv := range kv {
out[2*i] = "-E"
out[2*i+1] = kv
}
return out, nil
}
func flattenConfig(cfg Config, extra map[string]interface{}) ([]string, error) {
data, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}
for k, v := range extra {
m[k] = v
}
var kv []string
flattenMap(m, &kv, "")
sort.Strings(kv)
return kv, nil
}
func flattenMap(m map[string]interface{}, out *[]string, prefix string) {
for k, v := range m {
switch v := v.(type) {
case map[string]interface{}:
flattenMap(v, out, prefix+k+".")
default:
jsonv, err := json.Marshal(v)
if err != nil {
panic(err)
}
*out = append(*out, fmt.Sprintf("%s%s=%s", prefix, k, jsonv))
}
}
}
// DefaultConfig holds the default configuration.
func DefaultConfig() Config {
return Config{
Kibana: &KibanaConfig{
Enabled: true,
Host: (&url.URL{
Scheme: "http",
Host: net.JoinHostPort(
getenvDefault("KIBANA_HOST", defaultKibanaHost),
KibanaPort(),
),
Path: getenvDefault("KIBANA_BASE_PATH", defaultKibanaBasePath),
}).String(),
Username: getenvDefault("KIBANA_USER", defaultKibanaUser),
Password: getenvDefault("KIBANA_PASS", defaultKibanaPass),
},
Output: defaultOutputConfig(),
}
}
// defaultOutputConfig enables overriding the default output, and is used to
// default to console output in tests for apmservertest itself. This is needed
// to avoid interacting with Elasticsearch, which would cause systemtest tests
// to fail as they assume sole access to Elasticsearch.
func defaultOutputConfig() OutputConfig {
var outputConfig OutputConfig
switch v := os.Getenv("APMSERVERTEST_DEFAULT_OUTPUT"); v {
case "console":
outputConfig.Console = &ConsoleOutputConfig{Enabled: true}
case "":
outputConfig.Elasticsearch = &ElasticsearchOutputConfig{
Enabled: true,
Hosts: []string{net.JoinHostPort(
getenvDefault("ES_HOST", defaultElasticsearchHost),
ElasticsearchPort(),
)},
Username: getenvDefault("ES_USER", defaultElasticsearchUser),
Password: getenvDefault("ES_PASS", defaultElasticsearchPass),
// Lower the flush interval to 1ms to avoid delaying the tests.
FlushInterval: time.Millisecond,
}
default:
panic("APMSERVERTEST_DEFAULT_OUTPUT has unexpected value: " + v)
}
return outputConfig
}
// KibanaPort returns the Kibana port, configured using
// KIBANA_PORT, or otherwise returning the default of 5601.
func KibanaPort() string {
return getenvDefault("KIBANA_PORT", defaultKibanaPort)
}
// ElasticsearchPort returns the Elasticsearch REST API port,
// configured using ES_PORT, or otherwise returning the default
// of 9200.
func ElasticsearchPort() string {
return getenvDefault("ES_PORT", defaultElasticsearchPort)
}
func getenvDefault(k, defaultv string) string {
v := os.Getenv(k)
if v == "" {
return defaultv
}
return v
}