diff --git a/.chloggen/splunkent-client.yaml b/.chloggen/splunkent-client-refactor.yaml similarity index 63% rename from .chloggen/splunkent-client.yaml rename to .chloggen/splunkent-client-refactor.yaml index e508362cc1bd..9e4e9b459211 100755 --- a/.chloggen/splunkent-client.yaml +++ b/.chloggen/splunkent-client-refactor.yaml @@ -1,20 +1,27 @@ # Use this changelog template to create an entry for release notes. -# If your change doesn't affect end users, such as a test fix or a tooling change, -# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: 'enhancement' # The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) -component: splunkentreceiver +component: splunkentreceiver # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). -note: "adding component logic to splunkenterprise receiver" +note: Refactor Splunkenterprise Receiver component to better leverage existing otel SDK. # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. -issues: [12667] +issues: [27026] # (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: [user] diff --git a/receiver/splunkenterprisereceiver/README.md b/receiver/splunkenterprisereceiver/README.md index 15ab877aef4e..e45319366868 100644 --- a/receiver/splunkenterprisereceiver/README.md +++ b/receiver/splunkenterprisereceiver/README.md @@ -1,5 +1,38 @@ # Splunk Enterprise Receiver ---- -The Splunk Enterprise Receiver is a pull based tool which enables the ingestion of key performance metrics (KPI's) describing the operational status of a user's Splunk Enterprise deployment to be -added to their OpenTelemetry Pipeline. +The Splunk Enterprise Receiver is a pull based tool which enables the ingestion of performance metrics describing the operational status of a user's Splunk Enterprise deployment to an appropriate observability tool. +It is designed to leverage several different data sources to gather these metrics including the [introspection api endpoint](https://docs.splunk.com/Documentation/Splunk/9.1.1/RESTREF/RESTintrospect) and serializing +results from ad-hoc searches. Because of this, care must be taken by users when enabling metrics as running searches can effect your Splunk Enterprise Deployment and introspection may fail to report for Splunk +Cloud deployments. The primary purpose of this receiver is to empower those tasked with the maintenance and care of a Splunk Enterprise deployment to leverage opentelemetry and their observability toolset in their +jobs. + +## Configuration + +The following settings are required, omitting them will either cause your receiver to fail to compile or result in 4/5xx return codes during scraping. + +* `basicauth` (from [basicauthextension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/basicauthextension)): A configured stanza for the basicauthextension. +* `auth` (no default): String name referencing your auth extension. +* `endpoint` (no default): your Splunk Enterprise host's endpoint. + +The following settings are optional: + +* `collection_interval` (default: 10m): The time between scrape attempts. +* `timeout` (default: 60s): The time the scrape function will wait for a response before returning empty. + +Example: + +```yaml +extensions: + basicauth/client: + client_auth: + username: admin + password: securityFirst + +receivers: + splunkenterprise: + auth: basicauth/client + endpoint: "https://localhost:8089" + timeout: 45s +``` + +For a full list of settings exposed by this receiver please look [here](./config.go) with a detailed configuration [here](./testdata/config.yaml). diff --git a/receiver/splunkenterprisereceiver/client.go b/receiver/splunkenterprisereceiver/client.go index 46e4713a14f3..10381be94eea 100644 --- a/receiver/splunkenterprisereceiver/client.go +++ b/receiver/splunkenterprisereceiver/client.go @@ -5,41 +5,31 @@ package splunkenterprisereceiver // import "github.com/open-telemetry/openteleme import ( "context" - "crypto/tls" - "encoding/base64" "fmt" "net/http" "net/url" "strings" + + "go.opentelemetry.io/collector/component" ) type splunkEntClient struct { - endpoint *url.URL - client *http.Client - basicAuth string + client *http.Client + endpoint *url.URL } -func newSplunkEntClient(cfg *Config) splunkEntClient { - // tls party - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, +func newSplunkEntClient(cfg *Config, h component.Host, s component.TelemetrySettings) (*splunkEntClient, error) { + client, err := cfg.HTTPClientSettings.ToClient(h, s) + if err != nil { + return nil, err } - client := &http.Client{Transport: tr} - endpoint, _ := url.Parse(cfg.Endpoint) - // build and encode our auth string. Do this work once to avoid rebuilding the - // auth header every time we make a new request - authString := fmt.Sprintf("%s:%s", cfg.Username, cfg.Password) - auth64 := base64.StdEncoding.EncodeToString([]byte(authString)) - basicAuth := fmt.Sprintf("Basic %s", auth64) - - return splunkEntClient{ - client: client, - endpoint: endpoint, - basicAuth: basicAuth, - } + return &splunkEntClient{ + client: client, + endpoint: endpoint, + }, nil } // For running ad hoc searches only @@ -59,10 +49,6 @@ func (c *splunkEntClient) createRequest(ctx context.Context, sr *searchResponse) return nil, err } - // Required headers - req.Header.Add("Authorization", c.basicAuth) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - return req, nil } path := fmt.Sprintf("/services/search/jobs/%s/results", *sr.Jobid) @@ -73,10 +59,6 @@ func (c *splunkEntClient) createRequest(ctx context.Context, sr *searchResponse) return nil, err } - // Required headers - req.Header.Add("Authorization", c.basicAuth) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - return req, nil } @@ -88,10 +70,6 @@ func (c *splunkEntClient) createAPIRequest(ctx context.Context, apiEndpoint stri return nil, err } - // Required headers - req.Header.Add("Authorization", c.basicAuth) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - return req, nil } diff --git a/receiver/splunkenterprisereceiver/client_test.go b/receiver/splunkenterprisereceiver/client_test.go index be60bc608c8d..c6db8b9553ef 100644 --- a/receiver/splunkenterprisereceiver/client_test.go +++ b/receiver/splunkenterprisereceiver/client_test.go @@ -4,148 +4,179 @@ package splunkenterprisereceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkenterprisereceiver" import ( - "context" - "encoding/base64" - "fmt" - "net/http" "net/url" - "strings" "testing" "time" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configauth" "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/extension/auth" "go.opentelemetry.io/collector/receiver/scraperhelper" ) -func TestClientCreation(t *testing.T) { - // create a client from an example config - client := newSplunkEntClient(&Config{ - Username: "admin", - Password: "securityFirst", - MaxSearchWaitTime: 11 * time.Second, - HTTPClientSettings: confighttp.HTTPClientSettings{ - Endpoint: "https://localhost:8089", - }, - ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ - CollectionInterval: 10 * time.Second, - InitialDelay: 1 * time.Second, - }, - }) - - testEndpoint, _ := url.Parse("https://localhost:8089") - - authString := fmt.Sprintf("%s:%s", "admin", "securityFirst") - auth64 := base64.StdEncoding.EncodeToString([]byte(authString)) - testBasicAuth := fmt.Sprintf("Basic %s", auth64) +// mockHost allows us to create a test host with a no op extension that can be used to satisfy the SDK without having to parse from an +// actual config.yaml. +type mockHost struct { + component.Host + extensions map[component.ID]component.Component +} - require.Equal(t, client.endpoint, testEndpoint) - require.Equal(t, client.basicAuth, testBasicAuth) +func (m *mockHost) GetExtensions() map[component.ID]component.Component { + return m.extensions } -// test functionality of createRequest which is used for building metrics out of -// ad-hoc searches -func TestClientCreateRequest(t *testing.T) { - // create a client from an example config - client := newSplunkEntClient(&Config{ - Username: "admin", - Password: "securityFirst", - MaxSearchWaitTime: 11 * time.Second, +func TestClientCreation(t *testing.T) { + cfg := &Config{ HTTPClientSettings: confighttp.HTTPClientSettings{ Endpoint: "https://localhost:8089", + Auth: &configauth.Authentication{ + AuthenticatorID: component.NewID("basicauth/client"), + }, }, ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ CollectionInterval: 10 * time.Second, InitialDelay: 1 * time.Second, - }, - }) - - testJobID := "123" - - tests := []struct { - desc string - sr *searchResponse - client splunkEntClient - expected *http.Request - }{ - { - desc: "First req, no jobid", - sr: &searchResponse{ - search: "example search", - }, - client: client, - expected: func() *http.Request { - method := "POST" - path := "/services/search/jobs/" - testEndpoint, _ := url.Parse("https://localhost:8089") - url, _ := url.JoinPath(testEndpoint.String(), path) - data := strings.NewReader("example search") - req, _ := http.NewRequest(method, url, data) - req.Header.Add("Authorization", client.basicAuth) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - return req - }(), - }, - { - desc: "Second req, jobID detected", - sr: &searchResponse{ - search: "example search", - Jobid: &testJobID, - }, - client: client, - expected: func() *http.Request { - method := "GET" - path := fmt.Sprintf("/services/search/jobs/%s/results", testJobID) - testEndpoint, _ := url.Parse("https://localhost:8089") - url, _ := url.JoinPath(testEndpoint.String(), path) - req, _ := http.NewRequest(method, url, nil) - req.Header.Add("Authorization", client.basicAuth) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - return req - }(), + Timeout: 11 * time.Second, }, } - ctx := context.Background() - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - req, err := test.client.createRequest(ctx, test.sr) - require.NoError(t, err) - // have to test specific parts since individual fields are pointers - require.Equal(t, test.expected.URL, req.URL) - require.Equal(t, test.expected.Method, req.Method) - require.Equal(t, test.expected.Header, req.Header) - require.Equal(t, test.expected.Body, req.Body) - }) - } -} - -// createAPIRequest creates a request for api calls i.e. to introspection endpoint -func TestAPIRequestCreate(t *testing.T) { - client := newSplunkEntClient(&Config{ - Username: "admin", - Password: "securityFirst", - MaxSearchWaitTime: 11 * time.Second, - HTTPClientSettings: confighttp.HTTPClientSettings{ - Endpoint: "https://localhost:8089", + host := &mockHost{ + extensions: map[component.ID]component.Component{ + component.NewID("basicauth/client"): auth.NewClient(), }, - ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ - CollectionInterval: 10 * time.Second, - InitialDelay: 1 * time.Second, - }, - }) - - ctx := context.Background() - req, err := client.createAPIRequest(ctx, "/test/endpoint") + } + // create a client from an example config + client, err := newSplunkEntClient(cfg, host, componenttest.NewNopTelemetrySettings()) require.NoError(t, err) - expectedURL := client.endpoint.String() + "/test/endpoint" - expected, _ := http.NewRequest(http.MethodGet, expectedURL, nil) - expected.Header.Add("Authorization", client.basicAuth) - expected.Header.Add("Content-Type", "application/x-www-form-urlencoded") + testEndpoint, _ := url.Parse("https://localhost:8089") - require.Equal(t, expected.URL, req.URL) - require.Equal(t, expected.Method, req.Method) - require.Equal(t, expected.Header, req.Header) - require.Equal(t, expected.Body, req.Body) + require.Equal(t, client.endpoint, testEndpoint) } + +// test functionality of createRequest which is used for building metrics out of +// ad-hoc searches +//func TestClientCreateRequest(t *testing.T) { +// cfg := &Config { +// MaxSearchWaitTime: 11 * time.Second, +// HTTPClientSettings: confighttp.HTTPClientSettings{ +// Endpoint: "https://localhost:8089", +// }, +// ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ +// CollectionInterval: 10 * time.Second, +// InitialDelay: 1 * time.Second, +// }, +// } +// +// // create a client from an example config +// client, err := newSplunkEntClient(cfg, componenttest.NewNopHost(), componenttest.NewNopTelemetrySettings()) +// +// +// require.NoError(t, err) +// +// testJobID := "123" +// +// tests := []struct { +// desc string +// sr *searchResponse +// client *splunkEntClient +// expected *http.Request +// }{ +// { +// desc: "First req, no jobid", +// sr: &searchResponse{ +// search: "example search", +// }, +// client: client, +// expected: func() *http.Request { +// method := "POST" +// path := "/services/search/jobs/" +// testEndpoint, _ := url.Parse("https://localhost:8089") +// url, _ := url.JoinPath(testEndpoint.String(), path) +// data := strings.NewReader("example search") +// req, _ := http.NewRequest(method, url, data) +// req.Header.Add("Authorization", client.basicAuth) +// req.Header.Add("Content-Type", "application/x-www-form-urlencoded") +// return req +// }(), +// }, +// { +// desc: "Second req, jobID detected", +// sr: &searchResponse{ +// search: "example search", +// Jobid: &testJobID, +// }, +// client: client, +// expected: func() *http.Request { +// method := "GET" +// path := fmt.Sprintf("/services/search/jobs/%s/results", testJobID) +// testEndpoint, _ := url.Parse("https://localhost:8089") +// url, _ := url.JoinPath(testEndpoint.String(), path) +// req, _ := http.NewRequest(method, url, nil) +// req.Header.Add("Authorization", client.basicAuth) +// req.Header.Add("Content-Type", "application/x-www-form-urlencoded") +// return req +// }(), +// }, +// } +// +// ctx := context.Background() +// for _, test := range tests { +// t.Run(test.desc, func(t *testing.T) { +// req, err := test.client.createRequest(ctx, test.sr) +// require.NoError(t, err) +// // have to test specific parts since individual fields are pointers +// require.Equal(t, test.expected.URL, req.URL) +// require.Equal(t, test.expected.Method, req.Method) +// require.Equal(t, test.expected.Header, req.Header) +// require.Equal(t, test.expected.Body, req.Body) +// }) +// } +//} +// +//// createAPIRequest creates a request for api calls i.e. to introspection endpoint +//func TestAPIRequestCreate(t *testing.T) { +// authExtension := basicauthextension.Config{ +// ClientAuth: &basicauthextension.ClientAuthSettings{ +// Username: "admin", +// Password: configopaque.String("SecurityFirst!"), +// }, +// } +// +// authComponent := basicauthextension.New +// +// cfg := &Config { +// MaxSearchWaitTime: 11 * time.Second, +// HTTPClientSettings: confighttp.HTTPClientSettings{ +// Endpoint: "https://localhost:8089", +// }, +// ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ +// CollectionInterval: 10 * time.Second, +// InitialDelay: 1 * time.Second, +// }, +// } +// +// client, err := newSplunkEntClient(cfg, componenttest.NewNopHost(), componenttest.NewNopTelemetrySettings()) +// require.NoError(t, err) +// +// ctx := context.Background() +// req, err := client.createAPIRequest(ctx, "/test/endpoint") +// require.NoError(t, err) +// +// // build the expected request +// authString := fmt.Sprintf("%s:%s", "admin", "SecurityFirst!") +// auth64 := base64.StdEncoding.EncodeToString([]byte(authString)) +// expectedBasicAuth := fmt.Sprintf("Basic %s", auth64) +// expectedURL := client.endpoint.String() + "/test/endpoint" +// expected, _ := http.NewRequest(http.MethodGet, expectedURL, nil) +// expected.Header.Add("Authorization", expectedBasicAuth) +// expected.Header.Add("Content-Type", "application/x-www-form-urlencoded") +// +// require.Equal(t, expected.URL, req.URL) +// require.Equal(t, expected.Method, req.Method) +// require.Equal(t, expected.Header, req.Header) +// require.Equal(t, expected.Body, req.Body) +//} diff --git a/receiver/splunkenterprisereceiver/config.go b/receiver/splunkenterprisereceiver/config.go index 48546777b71d..62c87d01c5ce 100644 --- a/receiver/splunkenterprisereceiver/config.go +++ b/receiver/splunkenterprisereceiver/config.go @@ -7,7 +7,6 @@ import ( "errors" "net/url" "strings" - "time" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/receiver/scraperhelper" @@ -17,22 +16,15 @@ import ( ) var ( - errBadOrMissingEndpoint = errors.New("Missing a valid endpoint") - errMissingUsername = errors.New("Missing valid username") - errMissingPassword = errors.New("Missing valid password") - errBadScheme = errors.New("Endpoint scheme must be either http or https") + errBadOrMissingEndpoint = errors.New("missing a valid endpoint") + errBadScheme = errors.New("endpoint scheme must be either http or https") + errMissingAuthExtension = errors.New("auth extension missing from config") ) type Config struct { confighttp.HTTPClientSettings `mapstructure:",squash"` scraperhelper.ScraperControllerSettings `mapstructure:",squash"` metadata.MetricsBuilderConfig `mapstructure:",squash"` - // Username and password with associated with an account with - // permission to access the Splunk deployments REST api - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` - // default is 60s - MaxSearchWaitTime time.Duration `mapstructure:"max_search_wait_time"` } func (cfg *Config) Validate() (errors error) { @@ -54,12 +46,8 @@ func (cfg *Config) Validate() (errors error) { } } - if cfg.Username == "" { - errors = multierr.Append(errors, errMissingUsername) - } - - if cfg.Password == "" { - errors = multierr.Append(errors, errMissingPassword) + if cfg.HTTPClientSettings.Auth.AuthenticatorID.Name() == "" { + errors = multierr.Append(errors, errMissingAuthExtension) } return errors diff --git a/receiver/splunkenterprisereceiver/config_test.go b/receiver/splunkenterprisereceiver/config_test.go index 264f9b745f32..8079fc04a3e0 100644 --- a/receiver/splunkenterprisereceiver/config_test.go +++ b/receiver/splunkenterprisereceiver/config_test.go @@ -6,126 +6,22 @@ package splunkenterprisereceiver // import "github.com/open-telemetry/openteleme import ( "path/filepath" "testing" - "time" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/confmap/confmaptest" - "go.opentelemetry.io/collector/receiver/scraperhelper" - "go.uber.org/multierr" "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkenterprisereceiver/internal/metadata" ) -func TestValidateConfig(t *testing.T) { - t.Parallel() - - var multipleErrors error - - multipleErrors = multierr.Combine(multipleErrors, errBadOrMissingEndpoint, errMissingUsername, errMissingPassword) - - tests := []struct { - desc string - expect error - conf Config - }{ - { - desc: "Missing password", - expect: errMissingPassword, - conf: Config{ - Username: "admin", - HTTPClientSettings: confighttp.HTTPClientSettings{ - Endpoint: "https://localhost:8089", - }, - }, - }, - { - desc: "Missing username", - expect: errMissingUsername, - conf: Config{ - Password: "securityFirst", - HTTPClientSettings: confighttp.HTTPClientSettings{ - Endpoint: "https://localhost:8089", - }, - }, - }, - { - desc: "Bad scheme (none http/s)", - expect: errBadScheme, - conf: Config{ - Password: "securityFirst", - Username: "admin", - HTTPClientSettings: confighttp.HTTPClientSettings{ - Endpoint: "localhost:8089", - }, - }, - }, - { - desc: "Missing endpoint", - expect: errBadOrMissingEndpoint, - conf: Config{ - Username: "admin", - Password: "securityFirst", - }, - }, - { - desc: "Missing multiple", - expect: multipleErrors, - conf: Config{}, - }, - } - - for i := range tests { - test := tests[i] - - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - err := test.conf.Validate() - require.Error(t, err) - require.Contains(t, err.Error(), test.expect.Error()) - }) - } -} - +// Since there are no custom fields in config the existing tests for the components should +// cover the testing requirement. func TestLoadConfig(t *testing.T) { t.Parallel() cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) require.NoError(t, err) id := component.NewID(metadata.Type) - cmSub, err := cm.Sub(id.String()) + _, err = cm.Sub(id.String()) require.NoError(t, err) - - testmetrics := metadata.DefaultMetricsBuilderConfig() - testmetrics.Metrics.SplunkLicenseIndexUsage.Enabled = true - testmetrics.Metrics.SplunkIndexerThroughput.Enabled = false - - expected := &Config{ - Username: "admin", - Password: "securityFirst", - MaxSearchWaitTime: 11 * time.Second, - HTTPClientSettings: confighttp.HTTPClientSettings{ - Endpoint: "https://localhost:8089", - }, - ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ - CollectionInterval: 10 * time.Second, - InitialDelay: 1 * time.Second, - }, - MetricsBuilderConfig: testmetrics, - } - - factory := NewFactory() - cfg := factory.CreateDefaultConfig() - - require.NoError(t, component.UnmarshalConfig(cmSub, cfg)) - require.NoError(t, component.ValidateConfig(cfg)) - - diff := cmp.Diff(expected, cfg, cmpopts.IgnoreUnexported(metadata.MetricConfig{})) - if diff != "" { - t.Errorf("config mismatch (-expected / +actual)\n%s", diff) - } } diff --git a/receiver/splunkenterprisereceiver/factory.go b/receiver/splunkenterprisereceiver/factory.go index c956e99b9508..d806bdc806be 100644 --- a/receiver/splunkenterprisereceiver/factory.go +++ b/receiver/splunkenterprisereceiver/factory.go @@ -8,6 +8,8 @@ import ( "time" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" "go.opentelemetry.io/collector/receiver/scraperhelper" @@ -21,13 +23,19 @@ const ( ) func createDefaultConfig() component.Config { + defaultHeaders := map[string]configopaque.String{ + "Content-Type": "application/x-www-form-urlencoded", + } scfg := scraperhelper.NewDefaultScraperControllerSettings(metadata.Type) scfg.CollectionInterval = defaultInterval + scfg.Timeout = defaultMaxSearchWaitTime return &Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Headers: defaultHeaders, + }, ScraperControllerSettings: scfg, MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), - MaxSearchWaitTime: defaultMaxSearchWaitTime, } } diff --git a/receiver/splunkenterprisereceiver/factory_test.go b/receiver/splunkenterprisereceiver/factory_test.go index 4df2a00d471b..526c8c75e76a 100644 --- a/receiver/splunkenterprisereceiver/factory_test.go +++ b/receiver/splunkenterprisereceiver/factory_test.go @@ -9,6 +9,8 @@ import ( "time" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/consumer/consumertest" "go.opentelemetry.io/collector/receiver/receivertest" "go.opentelemetry.io/collector/receiver/scraperhelper" @@ -22,11 +24,18 @@ func TestFactoryCreate(t *testing.T) { } func TestDefaultConfig(t *testing.T) { + defaultHeaders := map[string]configopaque.String{ + "Content-Type": "application/x-www-form-urlencoded", + } + expectedConf := &Config{ - MaxSearchWaitTime: 60 * time.Second, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Headers: defaultHeaders, + }, ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ CollectionInterval: 10 * time.Minute, InitialDelay: 1 * time.Second, + Timeout: 60 * time.Second, }, MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), } diff --git a/receiver/splunkenterprisereceiver/go.mod b/receiver/splunkenterprisereceiver/go.mod index 6000ccbcd0e0..0eac3998677a 100644 --- a/receiver/splunkenterprisereceiver/go.mod +++ b/receiver/splunkenterprisereceiver/go.mod @@ -8,9 +8,12 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.85.0 github.com/stretchr/testify v1.8.4 go.opentelemetry.io/collector/component v0.85.1-0.20230922175119-921b6125f017 + go.opentelemetry.io/collector/config/configauth v0.85.1-0.20230922175119-921b6125f017 go.opentelemetry.io/collector/config/confighttp v0.85.1-0.20230922175119-921b6125f017 + go.opentelemetry.io/collector/config/configopaque v0.85.1-0.20230922175119-921b6125f017 go.opentelemetry.io/collector/confmap v0.85.1-0.20230922175119-921b6125f017 go.opentelemetry.io/collector/consumer v0.85.1-0.20230922175119-921b6125f017 + go.opentelemetry.io/collector/extension/auth v0.85.1-0.20230922175119-921b6125f017 go.opentelemetry.io/collector/pdata v1.0.0-rcv0014.0.20230922175119-921b6125f017 go.opentelemetry.io/collector/receiver v0.85.1-0.20230922175119-921b6125f017 go.uber.org/multierr v1.11.0 @@ -41,15 +44,12 @@ require ( github.com/rs/cors v1.10.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/collector v0.85.1-0.20230922175119-921b6125f017 // indirect - go.opentelemetry.io/collector/config/configauth v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/config/configcompression v0.85.1-0.20230922175119-921b6125f017 // indirect - go.opentelemetry.io/collector/config/configopaque v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/config/configtelemetry v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/config/configtls v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/config/internal v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/exporter v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/extension v0.85.1-0.20230922175119-921b6125f017 // indirect - go.opentelemetry.io/collector/extension/auth v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/featuregate v1.0.0-rcv0014.0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/collector/processor v0.85.1-0.20230922175119-921b6125f017 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 // indirect diff --git a/receiver/splunkenterprisereceiver/scraper.go b/receiver/splunkenterprisereceiver/scraper.go index 90c2d1032898..5eac64f0a5d6 100644 --- a/receiver/splunkenterprisereceiver/scraper.go +++ b/receiver/splunkenterprisereceiver/scraper.go @@ -24,7 +24,7 @@ import ( ) var ( - errMaxSearchWaitTimeExceeded = errors.New("Maximum search wait time exceeded for metric") + errMaxSearchWaitTimeExceeded = errors.New("maximum search wait time exceeded for metric") ) type splunkScraper struct { @@ -43,9 +43,12 @@ func newSplunkMetricsScraper(params receiver.CreateSettings, cfg *Config) splunk } // Create a client instance and add to the splunkScraper -func (s *splunkScraper) start(_ context.Context, _ component.Host) (err error) { - c := newSplunkEntClient(s.conf) - s.splunkClient = &c +func (s *splunkScraper) start(c context.Context, h component.Host) (err error) { + client, err := newSplunkEntClient(s.conf, h, s.settings) + if err != nil { + return err + } + s.splunkClient = client return nil } @@ -97,7 +100,6 @@ func (s *splunkScraper) scrapeLicenseUsageByIndex(ctx context.Context, now pcomm err = unmarshallSearchReq(res, &sr) if err != nil { errs.Add(err) - return } res.Body.Close() @@ -111,7 +113,7 @@ func (s *splunkScraper) scrapeLicenseUsageByIndex(ctx context.Context, now pcomm time.Sleep(2 * time.Second) } - if time.Since(start) > s.conf.MaxSearchWaitTime { + if time.Since(start) > s.conf.ScraperControllerSettings.Timeout { errs.Add(errMaxSearchWaitTimeExceeded) return } diff --git a/receiver/splunkenterprisereceiver/scraper_test.go b/receiver/splunkenterprisereceiver/scraper_test.go index 12eadeee18f3..8ec03ebd982a 100644 --- a/receiver/splunkenterprisereceiver/scraper_test.go +++ b/receiver/splunkenterprisereceiver/scraper_test.go @@ -13,7 +13,11 @@ import ( "time" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configauth" "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/extension/auth" "go.opentelemetry.io/collector/receiver/receivertest" "go.opentelemetry.io/collector/receiver/scraperhelper" @@ -53,24 +57,31 @@ func TestScraper(t *testing.T) { metricsettings.Metrics.SplunkIndexerThroughput.Enabled = true cfg := &Config{ - Username: "admin", - Password: "securityFirst", - MaxSearchWaitTime: 11 * time.Second, HTTPClientSettings: confighttp.HTTPClientSettings{ Endpoint: ts.URL, + Auth: &configauth.Authentication{ + AuthenticatorID: component.NewID("basicauth/client"), + }, }, ScraperControllerSettings: scraperhelper.ScraperControllerSettings{ CollectionInterval: 10 * time.Second, InitialDelay: 1 * time.Second, + Timeout: 11 * time.Second, }, MetricsBuilderConfig: metricsettings, } - require.NoError(t, cfg.Validate()) + host := &mockHost{ + extensions: map[component.ID]component.Component{ + component.NewID("basicauth/client"): auth.NewClient(), + }, + } scraper := newSplunkMetricsScraper(receivertest.NewNopCreateSettings(), cfg) - client := newSplunkEntClient(cfg) - scraper.splunkClient = &client + client, err := newSplunkEntClient(cfg, host, componenttest.NewNopTelemetrySettings()) + require.NoError(t, err) + + scraper.splunkClient = client actualMetrics, err := scraper.scrape(context.Background()) require.NoError(t, err) diff --git a/receiver/splunkenterprisereceiver/testdata/config.yaml b/receiver/splunkenterprisereceiver/testdata/config.yaml index ba27c230bf44..48bf9742b416 100644 --- a/receiver/splunkenterprisereceiver/testdata/config.yaml +++ b/receiver/splunkenterprisereceiver/testdata/config.yaml @@ -1,13 +1,15 @@ # Example config for the Splunk Enterprise Receiver. +basicauth/client: + client_auth: + username: admin + password: securityFirst splunkenterprise: # required settings - username: "admin" - password: "securityFirst" - endpoint: "https://localhost:8089" - # Optional settings + auth: basicauth/client # must use basicauthextension + endpoint: "https://localhost:8089" # Optional settings collection_interval: 10s - max_search_wait_time: 11s + timeout: 11s # Also optional: metric settings metrics: splunk.license.index.usage: