Skip to content

Commit b48a2e5

Browse files
committed
Refactor MSSQL scaler
Signed-off-by: Rick Brouwer <[email protected]>
1 parent a368eee commit b48a2e5

File tree

3 files changed

+127
-247
lines changed

3 files changed

+127
-247
lines changed

pkg/scalers/mssql_scaler.go

+64-160
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ package scalers
33
import (
44
"context"
55
"database/sql"
6-
"errors"
76
"fmt"
87
"net"
98
"net/url"
10-
"strconv"
119

1210
// mssql driver required for this scaler
1311
_ "github.com/denisenkom/go-mssqldb"
@@ -18,59 +16,35 @@ import (
1816
"github.com/kedacore/keda/v2/pkg/scalers/scalersconfig"
1917
)
2018

21-
var (
22-
// ErrMsSQLNoQuery is returned when "query" is missing from the config.
23-
ErrMsSQLNoQuery = errors.New("no query given")
24-
25-
// ErrMsSQLNoTargetValue is returned when "targetValue" is missing from the config.
26-
ErrMsSQLNoTargetValue = errors.New("no targetValue given")
27-
)
28-
29-
// mssqlScaler exposes a data pointer to mssqlMetadata and sql.DB connection
3019
type mssqlScaler struct {
3120
metricType v2.MetricTargetType
32-
metadata *mssqlMetadata
21+
metadata mssqlMetadata
3322
connection *sql.DB
3423
logger logr.Logger
3524
}
3625

37-
// mssqlMetadata defines metadata used by KEDA to query a Microsoft SQL database
3826
type mssqlMetadata struct {
39-
// The connection string used to connect to the MSSQL database.
40-
// Both URL syntax (sqlserver://host?database=dbName) and OLEDB syntax is supported.
41-
// +optional
42-
connectionString string
43-
// The username credential for connecting to the MSSQL instance, if not specified in the connection string.
44-
// +optional
45-
username string
46-
// The password credential for connecting to the MSSQL instance, if not specified in the connection string.
47-
// +optional
48-
password string
49-
// The hostname of the MSSQL instance endpoint, if not specified in the connection string.
50-
// +optional
51-
host string
52-
// The port number of the MSSQL instance endpoint, if not specified in the connection string.
53-
// +optional
54-
port int
55-
// The name of the database to query, if not specified in the connection string.
56-
// +optional
57-
database string
58-
// The T-SQL query to run against the target database - e.g. SELECT COUNT(*) FROM table.
59-
// +required
60-
query string
61-
// The threshold that is used as targetAverageValue in the Horizontal Pod Autoscaler.
62-
// +required
63-
targetValue float64
64-
// The threshold that is used in activation phase
65-
// +optional
66-
activationTargetValue float64
67-
// The index of the scaler inside the ScaledObject
68-
// +internal
69-
triggerIndex int
27+
ConnectionString string `keda:"name=connectionString, order=authParams;resolvedEnv;triggerMetadata, optional"`
28+
Username string `keda:"name=username, order=authParams;triggerMetadata, optional"`
29+
Password string `keda:"name=password, order=authParams;resolvedEnv;triggerMetadata, optional"`
30+
Host string `keda:"name=host, order=authParams;triggerMetadata, optional"`
31+
Port int `keda:"name=port, order=authParams;triggerMetadata, optional"`
32+
Database string `keda:"name=database, order=authParams;triggerMetadata, optional"`
33+
Query string `keda:"name=query, order=triggerMetadata"`
34+
TargetValue float64 `keda:"name=targetValue, order=triggerMetadata"`
35+
ActivationTargetValue float64 `keda:"name=activationTargetValue, order=triggerMetadata, default=0"`
36+
37+
TriggerIndex int
7038
}
7139

72-
// NewMSSQLScaler creates a new mssql scaler
73-
func NewMSSQLScaler(config *scalersconfig.ScalerConfig) (Scaler, error) {
40+
func (m *mssqlMetadata) Validate() error {
41+
if m.ConnectionString == "" && m.Host == "" {
42+
return fmt.Errorf("must provide either connectionstring or host")
43+
}
44+
return nil
45+
}
46+
47+
func NewMSSQLScaler(ctx context.Context, config *scalersconfig.ScalerConfig) (Scaler, error) {
7448
metricType, err := GetMetricTargetType(config)
7549
if err != nil {
7650
return nil, fmt.Errorf("error getting scaler metric type: %w", err)
@@ -83,155 +57,87 @@ func NewMSSQLScaler(config *scalersconfig.ScalerConfig) (Scaler, error) {
8357
return nil, fmt.Errorf("error parsing mssql metadata: %w", err)
8458
}
8559

86-
conn, err := newMSSQLConnection(meta, logger)
87-
if err != nil {
88-
return nil, fmt.Errorf("error establishing mssql connection: %w", err)
89-
}
90-
91-
return &mssqlScaler{
60+
scaler := &mssqlScaler{
9261
metricType: metricType,
9362
metadata: meta,
94-
connection: conn,
9563
logger: logger,
96-
}, nil
97-
}
98-
99-
// parseMSSQLMetadata takes a ScalerConfig and returns a mssqlMetadata or an error if the config is invalid
100-
func parseMSSQLMetadata(config *scalersconfig.ScalerConfig) (*mssqlMetadata, error) {
101-
meta := mssqlMetadata{}
102-
103-
// Query
104-
if val, ok := config.TriggerMetadata["query"]; ok {
105-
meta.query = val
106-
} else {
107-
return nil, ErrMsSQLNoQuery
10864
}
10965

110-
// Target query value
111-
if val, ok := config.TriggerMetadata["targetValue"]; ok {
112-
targetValue, err := strconv.ParseFloat(val, 64)
113-
if err != nil {
114-
return nil, fmt.Errorf("targetValue parsing error %w", err)
115-
}
116-
meta.targetValue = targetValue
117-
} else {
118-
if config.AsMetricSource {
119-
meta.targetValue = 0
120-
} else {
121-
return nil, ErrMsSQLNoTargetValue
122-
}
66+
conn, err := newMSSQLConnection(scaler)
67+
if err != nil {
68+
return nil, fmt.Errorf("error establishing mssql connection: %w", err)
12369
}
12470

125-
// Activation target value
126-
meta.activationTargetValue = 0
127-
if val, ok := config.TriggerMetadata["activationTargetValue"]; ok {
128-
activationTargetValue, err := strconv.ParseFloat(val, 64)
129-
if err != nil {
130-
return nil, fmt.Errorf("activationTargetValue parsing error %w", err)
131-
}
132-
meta.activationTargetValue = activationTargetValue
133-
}
71+
scaler.connection = conn
13472

135-
// Connection string, which can either be provided explicitly or via the helper fields
136-
switch {
137-
case config.AuthParams["connectionString"] != "":
138-
meta.connectionString = config.AuthParams["connectionString"]
139-
case config.TriggerMetadata["connectionStringFromEnv"] != "":
140-
meta.connectionString = config.ResolvedEnv[config.TriggerMetadata["connectionStringFromEnv"]]
141-
default:
142-
meta.connectionString = ""
143-
var err error
144-
145-
host, err := GetFromAuthOrMeta(config, "host")
146-
if err != nil {
147-
return nil, err
148-
}
149-
meta.host = host
150-
151-
var paramPort string
152-
paramPort, _ = GetFromAuthOrMeta(config, "port")
153-
if paramPort != "" {
154-
port, err := strconv.Atoi(paramPort)
155-
if err != nil {
156-
return nil, fmt.Errorf("port parsing error %w", err)
157-
}
158-
meta.port = port
159-
}
73+
return scaler, nil
74+
}
16075

161-
meta.username, _ = GetFromAuthOrMeta(config, "username")
76+
func parseMSSQLMetadata(config *scalersconfig.ScalerConfig) (mssqlMetadata, error) {
77+
meta := mssqlMetadata{}
78+
err := config.TypedConfig(&meta)
79+
if err != nil {
80+
return meta, err
81+
}
16282

163-
// database is optional in SQL s
164-
meta.database, _ = GetFromAuthOrMeta(config, "database")
83+
meta.TriggerIndex = config.TriggerIndex
16584

166-
if config.AuthParams["password"] != "" {
167-
meta.password = config.AuthParams["password"]
168-
} else if config.TriggerMetadata["passwordFromEnv"] != "" {
169-
meta.password = config.ResolvedEnv[config.TriggerMetadata["passwordFromEnv"]]
170-
}
171-
}
172-
meta.triggerIndex = config.TriggerIndex
173-
return &meta, nil
85+
return meta, nil
17486
}
17587

176-
// newMSSQLConnection returns a new, opened SQL connection for the provided mssqlMetadata
177-
func newMSSQLConnection(meta *mssqlMetadata, logger logr.Logger) (*sql.DB, error) {
178-
connStr := getMSSQLConnectionString(meta)
88+
func newMSSQLConnection(s *mssqlScaler) (*sql.DB, error) {
89+
connStr := getMSSQLConnectionString(s)
17990

18091
db, err := sql.Open("sqlserver", connStr)
18192
if err != nil {
182-
logger.Error(err, fmt.Sprintf("Found error opening mssql: %s", err))
93+
s.logger.Error(err, "Found error opening mssql")
18394
return nil, err
18495
}
18596

18697
err = db.Ping()
18798
if err != nil {
188-
logger.Error(err, fmt.Sprintf("Found error pinging mssql: %s", err))
99+
s.logger.Error(err, "Found error pinging mssql")
189100
return nil, err
190101
}
191102

192103
return db, nil
193104
}
194105

195-
// getMSSQLConnectionString returns a connection string from a mssqlMetadata
196-
func getMSSQLConnectionString(meta *mssqlMetadata) string {
197-
var connStr string
198-
199-
if meta.connectionString != "" {
200-
connStr = meta.connectionString
201-
} else {
202-
query := url.Values{}
203-
if meta.database != "" {
204-
query.Add("database", meta.database)
205-
}
106+
func getMSSQLConnectionString(s *mssqlScaler) string {
107+
meta := s.metadata
108+
if meta.ConnectionString != "" {
109+
return meta.ConnectionString
110+
}
206111

207-
connectionURL := &url.URL{Scheme: "sqlserver", RawQuery: query.Encode()}
208-
if meta.username != "" {
209-
if meta.password != "" {
210-
connectionURL.User = url.UserPassword(meta.username, meta.password)
211-
} else {
212-
connectionURL.User = url.User(meta.username)
213-
}
214-
}
112+
query := url.Values{}
113+
if meta.Database != "" {
114+
query.Add("database", meta.Database)
115+
}
215116

216-
if meta.port > 0 {
217-
connectionURL.Host = net.JoinHostPort(meta.host, fmt.Sprintf("%d", meta.port))
117+
connectionURL := &url.URL{Scheme: "sqlserver", RawQuery: query.Encode()}
118+
if meta.Username != "" {
119+
if meta.Password != "" {
120+
connectionURL.User = url.UserPassword(meta.Username, meta.Password)
218121
} else {
219-
connectionURL.Host = meta.host
122+
connectionURL.User = url.User(meta.Username)
220123
}
124+
}
221125

222-
connStr = connectionURL.String()
126+
if meta.Port > 0 {
127+
connectionURL.Host = net.JoinHostPort(meta.Host, fmt.Sprintf("%d", meta.Port))
128+
} else {
129+
connectionURL.Host = meta.Host
223130
}
224131

225-
return connStr
132+
return connectionURL.String()
226133
}
227134

228-
// GetMetricSpecForScaling returns the MetricSpec for the Horizontal Pod Autoscaler
229135
func (s *mssqlScaler) GetMetricSpecForScaling(context.Context) []v2.MetricSpec {
230136
externalMetric := &v2.ExternalMetricSource{
231137
Metric: v2.MetricIdentifier{
232-
Name: GenerateMetricNameWithIndex(s.metadata.triggerIndex, "mssql"),
138+
Name: GenerateMetricNameWithIndex(s.metadata.TriggerIndex, "mssql"),
233139
},
234-
Target: GetMetricTargetMili(s.metricType, s.metadata.targetValue),
140+
Target: GetMetricTargetMili(s.metricType, s.metadata.TargetValue),
235141
}
236142

237143
metricSpec := v2.MetricSpec{
@@ -241,7 +147,6 @@ func (s *mssqlScaler) GetMetricSpecForScaling(context.Context) []v2.MetricSpec {
241147
return []v2.MetricSpec{metricSpec}
242148
}
243149

244-
// GetMetricsAndActivity returns a value for a supported metric or an error if there is a problem getting the metric
245150
func (s *mssqlScaler) GetMetricsAndActivity(ctx context.Context, metricName string) ([]external_metrics.ExternalMetricValue, bool, error) {
246151
num, err := s.getQueryResult(ctx)
247152
if err != nil {
@@ -250,13 +155,13 @@ func (s *mssqlScaler) GetMetricsAndActivity(ctx context.Context, metricName stri
250155

251156
metric := GenerateMetricInMili(metricName, num)
252157

253-
return []external_metrics.ExternalMetricValue{metric}, num > s.metadata.activationTargetValue, nil
158+
return []external_metrics.ExternalMetricValue{metric}, num > s.metadata.ActivationTargetValue, nil
254159
}
255160

256-
// getQueryResult returns the result of the scaler query
257161
func (s *mssqlScaler) getQueryResult(ctx context.Context) (float64, error) {
258162
var value float64
259-
err := s.connection.QueryRowContext(ctx, s.metadata.query).Scan(&value)
163+
164+
err := s.connection.QueryRowContext(ctx, s.metadata.Query).Scan(&value)
260165
switch {
261166
case err == sql.ErrNoRows:
262167
value = 0
@@ -268,7 +173,6 @@ func (s *mssqlScaler) getQueryResult(ctx context.Context) (float64, error) {
268173
return value, nil
269174
}
270175

271-
// Close closes the mssql database connections
272176
func (s *mssqlScaler) Close(context.Context) error {
273177
err := s.connection.Close()
274178
if err != nil {

0 commit comments

Comments
 (0)