Skip to content
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
66 changes: 65 additions & 1 deletion internal/sqlservermetrics/sqlcollector/dbcentersqlcollector.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ var (
exposedToBroadIPAccessQueryKey = "exposed to broad ip access query key"
exposedToBroadAccess = "exposed_to_broad_ip_access"

signals = []string{auditingEnabledQueryKey, allowUnencryptedConnQueryKey, exposedToBroadIPAccessQueryKey}
notProtectedByAutoFailoverQueryKey = "not protected by automatic failover query key"
notProtectedByAutoFailover = "not_protected_by_auto_failover"

signals = []string{auditingEnabledQueryKey, allowUnencryptedConnQueryKey, exposedToBroadIPAccessQueryKey, notProtectedByAutoFailoverQueryKey}

sqlMetrics = map[string]dbCenterSQLMetricsStruct{
versionQueryKey: {
Expand Down Expand Up @@ -151,6 +154,59 @@ var (
},
resultProcessor: exposedToBroadIPAccess,
},

notProtectedByAutoFailoverQueryKey: {
query: `WITH Protected_AGs AS (
SELECT group_id
FROM sys.availability_replicas
WHERE availability_mode = 1
AND failover_mode = 1
GROUP BY group_id
HAVING COUNT(replica_id) >= 2
),
DBStatuses AS (
SELECT
CASE
WHEN SERVERPROPERTY('IsClustered') = 1 THEN 'PROTECTED_BY_FCI'
WHEN SERVERPROPERTY('IsHadrEnabled') = 1
AND pag.group_id IS NOT NULL
AND ar_local.failover_mode = 1
AND ar_local.availability_mode = 1 THEN 'PROTECTED_BY_AG'
WHEN dm.mirroring_guid IS NOT NULL
AND dm.mirroring_safety_level_desc = 'FULL'
AND dm.mirroring_witness_name IS NOT NULL
AND dm.mirroring_witness_name <> '' THEN 'PROTECTED_BY_MIRRORING'
ELSE 'UNPROTECTED'
END AS FailoverProtectionStatus
FROM master.sys.databases d
LEFT JOIN sys.dm_hadr_database_replica_states drs
ON d.database_id = drs.database_id AND drs.is_local = 1
LEFT JOIN Protected_AGs pag
ON drs.group_id = pag.group_id
LEFT JOIN sys.availability_replicas ar_local
ON drs.replica_id = ar_local.replica_id
LEFT JOIN sys.database_mirroring dm
ON d.database_id = dm.database_id
WHERE d.name NOT IN ('master', 'model', 'msdb', 'tempdb')
AND d.state_desc = 'ONLINE'
)
SELECT
CASE
WHEN SUM(CASE WHEN FailoverProtectionStatus = 'UNPROTECTED' THEN 1 ELSE 0 END) > 0 THEN 1
ELSE 0
END AS not_protected_by_auto_failover
FROM DBStatuses;`,
fields: func(rows [][]any) []map[string]string {
var res []map[string]string
for _, row := range rows {
res = append(res, map[string]string{
notProtectedByAutoFailover: handleNilInt(row[0]),
})
}
return res
},
resultProcessor: notProtectedByAutoFailoverProcessor,
},
}
)

Expand Down Expand Up @@ -225,6 +281,14 @@ func exposedToBroadIPAccess(result, metrics map[string]string) {
}
}

func notProtectedByAutoFailoverProcessor(result, metrics map[string]string) {
if result[notProtectedByAutoFailover] == "1" {
metrics[databasecenter.NotProtectedByAutomaticFailoverKey] = strconv.FormatBool(true)
} else {
metrics[databasecenter.NotProtectedByAutomaticFailoverKey] = strconv.FormatBool(false)
}
}

// PopulateSignals populates the signals for the SQL Server in the metrics map.
func PopulateSignals(ctx context.Context, c *V1, metrics map[string]string) {
for _, signal := range signals {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,11 @@ func TestPopulateSignals(t *testing.T) {
sqlmock.NewRows([]string{auditingEnabled}).AddRow(1),
sqlmock.NewRows([]string{allowUnencryptedConn}).AddRow(0),
sqlmock.NewRows([]string{exposedToBroadAccess}).AddRow(0),
sqlmock.NewRows([]string{notProtectedByAutoFailover}).AddRow(0),
},
delay: 0,
metrics: map[string]string{},
wantMetrics: map[string]string{databasecenter.DatabaseAuditingDisabledKey: "false", databasecenter.UnencryptedConnectionsKey: "false", databasecenter.ExposedToPublicAccessKey: "false"},
wantMetrics: map[string]string{databasecenter.DatabaseAuditingDisabledKey: "false", databasecenter.UnencryptedConnectionsKey: "false", databasecenter.ExposedToPublicAccessKey: "false", databasecenter.NotProtectedByAutomaticFailoverKey: "false"},
},
{
name: "all signals are populated",
Expand All @@ -145,10 +146,11 @@ func TestPopulateSignals(t *testing.T) {
sqlmock.NewRows([]string{auditingEnabled}).AddRow(0),
sqlmock.NewRows([]string{allowUnencryptedConn}).AddRow(1),
sqlmock.NewRows([]string{exposedToBroadAccess}).AddRow(1),
sqlmock.NewRows([]string{notProtectedByAutoFailover}).AddRow(1),
},
delay: 0,
metrics: map[string]string{},
wantMetrics: map[string]string{databasecenter.DatabaseAuditingDisabledKey: "true", databasecenter.UnencryptedConnectionsKey: "true", databasecenter.ExposedToPublicAccessKey: "true"},
wantMetrics: map[string]string{databasecenter.DatabaseAuditingDisabledKey: "true", databasecenter.UnencryptedConnectionsKey: "true", databasecenter.ExposedToPublicAccessKey: "true", databasecenter.NotProtectedByAutomaticFailoverKey: "true"},
},
{
name: "error caught when sql query returns error",
Expand All @@ -166,6 +168,7 @@ func TestPopulateSignals(t *testing.T) {
sqlmock.NewRows(nil),
sqlmock.NewRows(nil),
sqlmock.NewRows(nil),
sqlmock.NewRows(nil),
},
metrics: map[string]string{},
wantMetrics: map[string]string{},
Expand All @@ -178,6 +181,7 @@ func TestPopulateSignals(t *testing.T) {
sqlmock.NewRows([]string{auditingEnabled}).AddRow(1),
sqlmock.NewRows([]string{allowUnencryptedConn}).AddRow(1),
sqlmock.NewRows([]string{exposedToBroadAccess}).AddRow(1),
sqlmock.NewRows([]string{notProtectedByAutoFailover}).AddRow(0),
},
metrics: map[string]string{},
wantMetrics: map[string]string{},
Expand Down Expand Up @@ -214,3 +218,34 @@ func TestPopulateSignals(t *testing.T) {
})
}
}

func TestNotProtectedByAutoFailoverProcessor(t *testing.T) {
tests := []struct {
name string
result map[string]string
metrics map[string]string
wantMetrics map[string]string
}{
{
name: "all databases protected",
result: map[string]string{notProtectedByAutoFailover: "0"},
metrics: map[string]string{},
wantMetrics: map[string]string{databasecenter.NotProtectedByAutomaticFailoverKey: "false"},
},
{
name: "at least one database unprotected raises signal",
result: map[string]string{notProtectedByAutoFailover: "1"},
metrics: map[string]string{},
wantMetrics: map[string]string{databasecenter.NotProtectedByAutomaticFailoverKey: "true"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
notProtectedByAutoFailoverProcessor(tc.result, tc.metrics)
if diff := cmp.Diff(tc.wantMetrics, tc.metrics); diff != "" {
t.Errorf("notProtectedByAutoFailoverProcessor() returned diff (-want +got):\n%s", diff)
}
})
}
}
Loading