diff --git a/_meta/beat.reference.yml b/_meta/beat.reference.yml index 6fc2f17bc83..f0d2fbcc3f0 100644 --- a/_meta/beat.reference.yml +++ b/_meta/beat.reference.yml @@ -70,6 +70,14 @@ apm-server: # a matching index pattern needs to be specified here. #index_pattern: "apm-*" + # golang expvar support - https://golang.org/pkg/expvar/ + #expvar: + # Set to true to Expose expvar + #enabled: false + + # Url to expose expvar + #url: "/debug/vars" + #================================ General ====================================== # Internal queue configuration for buffering events to be published. diff --git a/apm-server.reference.yml b/apm-server.reference.yml index 6fc2f17bc83..f0d2fbcc3f0 100644 --- a/apm-server.reference.yml +++ b/apm-server.reference.yml @@ -70,6 +70,14 @@ apm-server: # a matching index pattern needs to be specified here. #index_pattern: "apm-*" + # golang expvar support - https://golang.org/pkg/expvar/ + #expvar: + # Set to true to Expose expvar + #enabled: false + + # Url to expose expvar + #url: "/debug/vars" + #================================ General ====================================== # Internal queue configuration for buffering events to be published. diff --git a/beater/beater_test.go b/beater/beater_test.go index 68fe4b4241d..554dfc85f25 100644 --- a/beater/beater_test.go +++ b/beater/beater_test.go @@ -49,6 +49,10 @@ func TestBeatConfig(t *testing.T) { "certificate": "1234cert", }, "concurrent_requests": 15, + "expvar": map[string]interface{}{ + "enabled": true, + "url": "/debug/vars", + }, "frontend": map[string]interface{}{ "enabled": true, "rate_limit": 1000, @@ -72,6 +76,10 @@ func TestBeatConfig(t *testing.T) { ShutdownTimeout: 9000000000, SecretToken: "1234random", SSL: &SSLConfig{Enabled: &truthy, PrivateKey: "1234key", Cert: "1234cert"}, + Expvar: &ExpvarConfig{ + Enabled: &truthy, + Url: "/debug/vars", + }, Frontend: &FrontendConfig{ Enabled: &truthy, RateLimit: 1000, @@ -95,6 +103,10 @@ func TestBeatConfig(t *testing.T) { "ssl": map[string]interface{}{ "enabled": true, }, + "expvar": map[string]interface{}{ + "enabled": true, + "url": "/debug/vars", + }, "frontend": map[string]interface{}{ "enabled": true, "source_mapping": map[string]interface{}{ @@ -113,6 +125,10 @@ func TestBeatConfig(t *testing.T) { ShutdownTimeout: 5000000000, SecretToken: "1234random", SSL: &SSLConfig{Enabled: &truthy, PrivateKey: "", Cert: ""}, + Expvar: &ExpvarConfig{ + Enabled: &truthy, + Url: "/debug/vars", + }, Frontend: &FrontendConfig{ Enabled: &truthy, RateLimit: 10, diff --git a/beater/config.go b/beater/config.go index f2c8d84e1bb..e0ad20c8483 100644 --- a/beater/config.go +++ b/beater/config.go @@ -17,9 +17,15 @@ type Config struct { SecretToken string `config:"secret_token"` SSL *SSLConfig `config:"ssl"` ConcurrentRequests int `config:"concurrent_requests" validate:"min=1"` + Expvar *ExpvarConfig `config:"expvar"` Frontend *FrontendConfig `config:"frontend"` } +type ExpvarConfig struct { + Enabled *bool `config:"enabled"` + Url string `config:"url"` +} + type FrontendConfig struct { Enabled *bool `config:"enabled"` RateLimit int `config:"rate_limit"` @@ -57,6 +63,10 @@ func (c *SSLConfig) isEnabled() bool { return c != nil && (c.Enabled == nil || *c.Enabled) } +func (c *ExpvarConfig) isEnabled() bool { + return c != nil && (c.Enabled == nil || *c.Enabled) +} + func (c *FrontendConfig) isEnabled() bool { return c != nil && (c.Enabled == nil || *c.Enabled) } @@ -109,5 +119,9 @@ func defaultConfig() *Config { LibraryPattern: "node_modules|bower_components|~", ExcludeFromGrouping: "^/webpack", }, + Expvar: &ExpvarConfig{ + Enabled: new(bool), + Url: "/debug/vars", + }, } } diff --git a/beater/handlers.go b/beater/handlers.go index bebc3f3db9f..f6ffd79aa34 100644 --- a/beater/handlers.go +++ b/beater/handlers.go @@ -6,6 +6,7 @@ import ( "context" "crypto/subtle" "encoding/json" + "expvar" "fmt" "net" "net/http" @@ -81,6 +82,11 @@ func newMuxer(config *Config, report reporter) *http.ServeMux { mux.Handle(path, mapping.ProcessorHandler(mapping.ProcessorFactory, config, report)) } + if config.Expvar.isEnabled() { + path := config.Expvar.Url + logp.Info("Path %s added to request handler", path) + mux.Handle(path, expvar.Handler()) + } return mux } diff --git a/tests/system/apmserver.py b/tests/system/apmserver.py index 0ffd8bfdcf5..282e47b0cf3 100644 --- a/tests/system/apmserver.py +++ b/tests/system/apmserver.py @@ -1,7 +1,8 @@ -import sys -import os import json +import os import shutil +import sys + sys.path.append('../../_beats/libbeat/tests/system') from beat.beat import TestCase from elasticsearch import Elasticsearch @@ -45,9 +46,9 @@ def get_error_payload(self): class ServerSetUpBaseTest(BaseTest): - transactions_url = 'http://localhost:8200/v1/transactions' errors_url = 'http://localhost:8200/v1/errors' + expvar_url = 'http://localhost:8200/debug/vars' def config(self): return {"ssl_enabled": "false", @@ -300,3 +301,15 @@ def config(self): cfg = super(SmapCacheBaseTest, self).config() cfg.update({"smap_cache_expiration": "1"}) return cfg + + +class ExpvarBaseTest(ServerBaseTest): + config_overrides = {} + + def config(self): + cfg = super(ServerBaseTest, self).config() + cfg.update(self.config_overrides) + return cfg + + def get_debug_vars(self): + return requests.get(self.expvar_url) diff --git a/tests/system/config/apm-server.yml.j2 b/tests/system/config/apm-server.yml.j2 index 99974a71d63..5d413bd3add 100644 --- a/tests/system/config/apm-server.yml.j2 +++ b/tests/system/config/apm-server.yml.j2 @@ -17,6 +17,12 @@ apm-server: frontend.source_mapping.index_pattern: {{ smap_index_pattern}} frontend.source_mapping.cache.expiration: {{ smap_cache_expiration}} {% endif %} + {% if expvar_enabled %} + expvar.enabled: {{ expvar_enabled }} + {% endif %} + {% if expvar_url %} + expvar.url: {{ expvar_url }} + {% endif %} ############################# Setup ########################################## {% if index_name %} diff --git a/tests/system/test_integration.py b/tests/system/test_integration.py index f52794f12a0..d5cce2d05a0 100644 --- a/tests/system/test_integration.py +++ b/tests/system/test_integration.py @@ -1,10 +1,8 @@ -from apmserver import ElasticTest, ClientSideBaseTest, SmapCacheBaseTest -from beat.beat import INTEGRATION_TESTS import os -import json -import requests import unittest -import time + +from apmserver import ElasticTest, ExpvarBaseTest, ClientSideBaseTest, SmapCacheBaseTest +from beat.beat import INTEGRATION_TESTS class Test(ElasticTest): @@ -311,3 +309,34 @@ def test_sourcemap_cache_expiration(self): 'error', 1) self.check_frontend_error_sourcemap(False, expected_err="No Sourcemap available for") + + +class ExpvarDisabledIntegrationTest(ExpvarBaseTest): + config_overrides = {"expvar_enabled": "false"} + + @unittest.skipUnless(INTEGRATION_TESTS, "integration test") + def test_expvar_exists(self): + """expvar disabled, should 404""" + r = self.get_debug_vars() + assert r.status_code == 404, r.status_code + + +class ExpvarEnabledIntegrationTest(ExpvarBaseTest): + config_overrides = {"expvar_enabled": "true"} + + @unittest.skipUnless(INTEGRATION_TESTS, "integration test") + def test_expvar_exists(self): + """expvar enabled, should 200""" + r = self.get_debug_vars() + assert r.status_code == 200, r.status_code + + +class ExpvarCustomUrlIntegrationTest(ExpvarBaseTest): + config_overrides = {"expvar_enabled": "true", "expvar_url": "/foo"} + expvar_url = ExpvarBaseTest.expvar_url.replace("/debug/vars", "/foo") + + @unittest.skipUnless(INTEGRATION_TESTS, "integration test") + def test_expvar_exists(self): + """expvar enabled, should 200""" + r = self.get_debug_vars() + assert r.status_code == 200, r.status_code diff --git a/tests/system/test_requests.py b/tests/system/test_requests.py index f2a99376f1b..9111ee1c92f 100644 --- a/tests/system/test_requests.py +++ b/tests/system/test_requests.py @@ -112,6 +112,11 @@ def test_deflate_error(self): headers={'Content-Encoding': 'deflate', 'Content-Type': 'application/json'}) assert r.status_code == 400, r.status_code + def test_expvar_default(self): + """expvar should not be exposed by default""" + r = requests.get(self.expvar_url) + assert r.status_code == 404, r.status_code + class SecureTest(SecureServerBaseTest):