Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore][receiver/sqlserver] Enable receiver to run on non-Windows systems #32542

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions receiver/sqlserverreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@
package sqlserverreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver"

import (
"database/sql"
"errors"
"fmt"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/scraperhelper"

"github.com/open-telemetry/opentelemetry-collector-contrib/internal/sqlquery"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata"
)

var errConfigNotSQLServer = errors.New("config was not a sqlserver receiver config")

// NewFactory creates a factory for SQL Server receiver.
func NewFactory() receiver.Factory {
return receiver.NewFactory(
Expand All @@ -30,8 +36,81 @@ func createDefaultConfig() component.Config {
}
}

func setupQueries(cfg *Config) []string {
var queries []string
// TODO: Only add query if metrics are enabled
queries = append(queries, getSQLServerDatabaseIOQuery(cfg.InstanceName))
return queries
}

func directDBConnectionEnabled(config *Config) bool {
return config.Server != "" &&
config.Username != "" &&
string(config.Password) != ""
}

// Assumes config has all information necessary to directly connect to the database
func getDBConnectionString(config *Config) string {
return fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d", config.Server, config.Username, string(config.Password), config.Port)
}

// SQL Server scraper creation is split out into a separate method for the sake of testing.
func setupSQLServerScrapers(params receiver.CreateSettings, cfg *Config) []*sqlServerScraperHelper {
if !directDBConnectionEnabled(cfg) {
params.Logger.Info("No direct connection will be made to the SQL Server: Configuration doesn't include some options.")
return nil
}

queries := setupQueries(cfg)
if len(queries) == 0 {
params.Logger.Info("No direct connection will be made to the SQL Server: No metrics are enabled requiring it.")
return nil
}

// TODO: Test if this needs to be re-defined for each scraper
// This should be tested when there is more than one query being made.
dbProviderFunc := func() (*sql.DB, error) {
return sql.Open("sqlserver", getDBConnectionString(cfg))
}

var scrapers []*sqlServerScraperHelper
for i, query := range queries {
id := component.NewIDWithName(metadata.Type, fmt.Sprintf("query-%d: %s", i, query))

sqlServerScraper := newSQLServerScraper(id, query,
cfg.InstanceName,
cfg.ControllerConfig,
params.Logger,
sqlquery.TelemetryConfig{},
dbProviderFunc,
sqlquery.NewDbClient,
metadata.NewMetricsBuilder(cfg.MetricsBuilderConfig, params))

scrapers = append(scrapers, sqlServerScraper)
}

return scrapers
}

// Note: This method will fail silently if there is no work to do. This is an acceptable use case
// as this receiver can still get information on Windows from performance counters without a direct
// connection. Messages will be logged at the INFO level in such cases.
func setupScrapers(params receiver.CreateSettings, cfg *Config) ([]scraperhelper.ScraperControllerOption, error) {
sqlServerScrapers := setupSQLServerScrapers(params, cfg)

var opts []scraperhelper.ScraperControllerOption
for _, sqlScraper := range sqlServerScrapers {
scraper, err := scraperhelper.NewScraper(metadata.Type.String(), sqlScraper.Scrape,
scraperhelper.WithStart(sqlScraper.Start),
scraperhelper.WithShutdown(sqlScraper.Shutdown))

if err != nil {
return nil, err
}

opt := scraperhelper.AddScraper(scraper)
opts = append(opts, opt)
}

return opts, nil
}
25 changes: 20 additions & 5 deletions receiver/sqlserverreceiver/factory_others.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,34 @@ package sqlserverreceiver // import "github.com/open-telemetry/opentelemetry-col

import (
"context"
"errors"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/scraperhelper"
)

// createMetricsReceiver creates a metrics receiver based on provided config.
func createMetricsReceiver(
_ context.Context,
_ receiver.CreateSettings,
_ component.Config,
_ consumer.Metrics,
params receiver.CreateSettings,
receiverCfg component.Config,
metricsConsumer consumer.Metrics,
) (receiver.Metrics, error) {
return nil, errors.New("the sqlserver receiver is only supported on Windows")
cfg, ok := receiverCfg.(*Config)
if !ok {
return nil, errConfigNotSQLServer
}

opts, err := setupScrapers(params, cfg)
if err != nil {
return nil, err
}

return scraperhelper.NewScraperControllerReceiver(
&cfg.ControllerConfig,
params,
metricsConsumer,
opts...,
)
}
62 changes: 55 additions & 7 deletions receiver/sqlserverreceiver/factory_others_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,64 @@ import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/receivertest"
)

func TestCreateMetricsReceiver(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
mReceiver, err := factory.CreateMetricsReceiver(context.Background(), receivertest.NewNopCreateSettings(), cfg, consumertest.NewNop())
func TestCreateMetricsReceiverOtherOS(t *testing.T) {
testCases := []struct {
desc string
testFunc func(*testing.T)
}{
{
desc: "Test direct connection with instance name",
testFunc: func(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.Username = "sa"
cfg.Password = "password"
cfg.Server = "0.0.0.0"
cfg.Port = 1433
cfg.InstanceName = "instanceName"
require.NoError(t, cfg.Validate())

assert.EqualError(t, err, "the sqlserver receiver is only supported on Windows")
assert.Nil(t, mReceiver)
require.True(t, directDBConnectionEnabled(cfg))
require.Equal(t, "server=0.0.0.0;user id=sa;password=password;port=1433", getDBConnectionString(cfg))

params := receivertest.NewNopCreateSettings()
scrapers, err := setupScrapers(params, cfg)
require.NoError(t, err)
require.NotEmpty(t, scrapers)

sqlScrapers := setupSQLServerScrapers(params, cfg)
require.NotEmpty(t, sqlScrapers)

databaseIOScraperFound := false
for _, scraper := range sqlScrapers {
if scraper.sqlQuery == getSQLServerDatabaseIOQuery(cfg.InstanceName) {
databaseIOScraperFound = true
break
}
}

require.True(t, databaseIOScraperFound)

r, err := factory.CreateMetricsReceiver(
context.Background(),
receivertest.NewNopCreateSettings(),
cfg,
consumertest.NewNop(),
)
require.NoError(t, err)
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, r.Shutdown(context.Background()))
},
},
}

for _, tc := range testCases {
t.Run(tc.desc, tc.testFunc)
}
}
93 changes: 92 additions & 1 deletion receiver/sqlserverreceiver/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
package sqlserverreceiver

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/receivertest"
"go.opentelemetry.io/collector/receiver/scraperhelper"

"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata"
)

func TestNewFactory(t *testing.T) {
func TestCreateMetricsReceiver(t *testing.T) {
testCases := []struct {
desc string
testFunc func(*testing.T)
Expand Down Expand Up @@ -42,6 +46,93 @@ func TestNewFactory(t *testing.T) {
require.Equal(t, expectedCfg, factory.CreateDefaultConfig())
},
},
{
desc: "creates a new factory and CreateMetricsReceiver returns error with incorrect config",
testFunc: func(t *testing.T) {
factory := NewFactory()
_, err := factory.CreateMetricsReceiver(
context.Background(),
receivertest.NewNopCreateSettings(),
nil,
consumertest.NewNop(),
)
require.ErrorIs(t, err, errConfigNotSQLServer)
},
},
{
desc: "creates a new factory and CreateMetricsReceiver returns no error",
testFunc: func(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
r, err := factory.CreateMetricsReceiver(
context.Background(),
receivertest.NewNopCreateSettings(),
cfg,
consumertest.NewNop(),
)
require.NoError(t, err)
scrapers := setupSQLServerScrapers(receivertest.NewNopCreateSettings(), cfg.(*Config))
require.Empty(t, scrapers)
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, r.Shutdown(context.Background()))
},
},
{
desc: "Test direct connection",
testFunc: func(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig().(*Config)
cfg.Username = "sa"
cfg.Password = "password"
cfg.Server = "0.0.0.0"
cfg.Port = 1433
require.NoError(t, cfg.Validate())

require.True(t, directDBConnectionEnabled(cfg))
require.Equal(t, "server=0.0.0.0;user id=sa;password=password;port=1433", getDBConnectionString(cfg))

params := receivertest.NewNopCreateSettings()
scrapers, err := setupScrapers(params, cfg)
require.NoError(t, err)
require.NotEmpty(t, scrapers)

sqlScrapers := setupSQLServerScrapers(params, cfg)
require.NotEmpty(t, sqlScrapers)

databaseIOScraperFound := false
for _, scraper := range sqlScrapers {
if scraper.sqlQuery == getSQLServerDatabaseIOQuery(cfg.InstanceName) {
databaseIOScraperFound = true
break
}
}

require.True(t, databaseIOScraperFound)
cfg.InstanceName = "instanceName"
sqlScrapers = setupSQLServerScrapers(params, cfg)
require.NotEmpty(t, sqlScrapers)

databaseIOScraperFound = false
for _, scraper := range sqlScrapers {
if scraper.sqlQuery == getSQLServerDatabaseIOQuery(cfg.InstanceName) {
databaseIOScraperFound = true
break
}
}

require.True(t, databaseIOScraperFound)

r, err := factory.CreateMetricsReceiver(
context.Background(),
receivertest.NewNopCreateSettings(),
cfg,
consumertest.NewNop(),
)
require.NoError(t, err)
require.NoError(t, r.Start(context.Background(), componenttest.NewNopHost()))
require.NoError(t, r.Shutdown(context.Background()))
},
},
}

for _, tc := range testCases {
Expand Down
15 changes: 11 additions & 4 deletions receiver/sqlserverreceiver/factory_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package sqlserverreceiver // import "github.com/open-telemetry/opentelemetry-col

import (
"context"
"errors"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
Expand All @@ -17,8 +16,6 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/sqlserverreceiver/internal/metadata"
)

var errConfigNotSQLServer = errors.New("config was not a sqlserver receiver config")

// createMetricsReceiver creates a metrics receiver based on provided config.
func createMetricsReceiver(
_ context.Context,
Expand All @@ -39,7 +36,17 @@ func createMetricsReceiver(
return nil, err
}

var opts []scraperhelper.ScraperControllerOption
opts, err = setupScrapers(params, cfg)
if err != nil {
return nil, err
}
opts = append(opts, scraperhelper.AddScraper(scraper))

return scraperhelper.NewScraperControllerReceiver(
&cfg.ControllerConfig, params, metricsConsumer, scraperhelper.AddScraper(scraper),
&cfg.ControllerConfig,
params,
metricsConsumer,
opts...,
)
}
Loading
Loading