Skip to content

Add metric to Solace Scaler #4667

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

Merged
merged 1 commit into from
Jun 21, 2023
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
- **RabbitMQ Scaler**: Add support for `unsafeSsl` in trigger metadata ([#4448](https://github.com/kedacore/keda/issues/4448))
- **Prometheus Metrics**: Add new metric with KEDA build info ([#4647](https://github.com/kedacore/keda/issues/4647))
- **Prometheus Scaler**: Add support for Google Managed Prometheus ([#4675](https://github.com/kedacore/keda/pull/4675))
- **Solace Scaler**: Add new `messageReceiveRateTarget` metric to Solace Scaler ([#4665](https://github.com/kedacore/keda/issues/4665))


### Fixes

Expand Down
66 changes: 63 additions & 3 deletions pkg/scalers/solace_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,47 @@ import (
const (
solaceExtMetricType = "External"
solaceScalerID = "solace"

// REST ENDPOINT String Patterns
solaceSempEndpointURLTemplate = "%s/%s/%s/monitor/msgVpns/%s/%ss/%s"
solaceSempQueryFieldURLSuffix = "?select=msgs,msgSpoolUsage,averageRxMsgRate"
solaceSempEndpointURLTemplate = "%s/%s/%s/monitor/msgVpns/%s/%ss/%s" + solaceSempQueryFieldURLSuffix

// SEMP REST API Context
solaceAPIName = "SEMP"
solaceAPIVersion = "v2"
solaceAPIObjectTypeQueue = "queue"

// Log Message Templates
solaceFoundMetaFalse = "required Field %s NOT FOUND in Solace Metadata"

// YAML Configuration Metadata Field Names
// Broker Identifiers
solaceMetaSempBaseURL = "solaceSempBaseURL"

// Credential Identifiers
solaceMetaUsername = "username"
solaceMetaPassword = "password"
solaceMetaUsernameFromEnv = "usernameFromEnv"
solaceMetaPasswordFromEnv = "passwordFromEnv"

// Target Object Identifiers
solaceMetaMsgVpn = "messageVpn"
solaceMetaQueueName = "queueName"

// Metric Targets
solaceMetaMsgCountTarget = "messageCountTarget"
solaceMetaMsgSpoolUsageTarget = "messageSpoolUsageTarget"
solaceMetaMsgRxRateTarget = "messageReceiveRateTarget"

// Metric Activation Targets
solaceMetaActivationMsgCountTarget = "activationMessageCountTarget"
solaceMetaActivationMsgSpoolUsageTarget = "activationMessageSpoolUsageTarget"
solaceMetaActivationMsgRxRateTarget = "activationMessageReceiveRateTarget"

// Trigger type identifiers
solaceTriggermsgcount = "msgcount"
solaceTriggermsgspoolusage = "msgspoolusage"
solaceTriggermsgrxrate = "msgrcvrate"
)

// Struct for Observed Metric Values
Expand All @@ -54,6 +67,8 @@ type SolaceMetricValues struct {
msgCount int
// Observed Message Spool Usage
msgSpoolUsage int
// Observed Message Received Rate
msgRcvRate int
}

type SolaceScaler struct {
Expand All @@ -67,19 +82,25 @@ type SolaceMetadata struct {
// Full SEMP URL to target queue (CONSTRUCTED IN CODE)
endpointURL string
solaceSempURL string

// Solace Message VPN
messageVpn string
queueName string

// Basic Auth Username
username string
// Basic Auth Password
password string

// Target Message Count
msgCountTarget int64
msgSpoolUsageTarget int64 // Spool Use Target in Megabytes
msgRxRateTarget int64 // Ingress Rate Target per consumer in msgs/second

// Activation Target Message Count
activationMsgCountTarget int
activationMsgSpoolUsageTarget int // Spool Use Target in Megabytes
activationMsgRxRateTarget int // Ingress Rate Target per consumer in msgs/second
// Scaler index
scalerIndex int
}
Expand All @@ -99,6 +120,7 @@ type solaceSEMPCollections struct {
// SEMP API Response Queue Data Struct
type solaceSEMPData struct {
MsgSpoolUsage int `json:"msgSpoolUsage"`
MsgRcvRate int `json:"averageRxMsgRate"`
}

// SEMP API Messages Struct
Expand Down Expand Up @@ -162,6 +184,7 @@ func parseSolaceMetadata(config *ScalerConfig) (*SolaceMetadata, error) {

// GET METRIC TARGET VALUES
// GET msgCountTarget
meta.msgCountTarget = 0
if val, ok := config.TriggerMetadata[solaceMetaMsgCountTarget]; ok && val != "" {
if msgCount, err := strconv.ParseInt(val, 10, 64); err == nil {
meta.msgCountTarget = msgCount
Expand All @@ -170,16 +193,26 @@ func parseSolaceMetadata(config *ScalerConfig) (*SolaceMetadata, error) {
}
}
// GET msgSpoolUsageTarget
meta.msgSpoolUsageTarget = 0
if val, ok := config.TriggerMetadata[solaceMetaMsgSpoolUsageTarget]; ok && val != "" {
if msgSpoolUsage, err := strconv.ParseInt(val, 10, 64); err == nil {
meta.msgSpoolUsageTarget = msgSpoolUsage * 1024 * 1024
} else {
return nil, fmt.Errorf("can't parse [%s], not a valid integer: %w", solaceMetaMsgSpoolUsageTarget, err)
}
}
// GET msgRcvRateTarget
meta.msgRxRateTarget = 0
if val, ok := config.TriggerMetadata[solaceMetaMsgRxRateTarget]; ok && val != "" {
if msgRcvRate, err := strconv.ParseInt(val, 10, 64); err == nil {
meta.msgRxRateTarget = msgRcvRate
} else {
return nil, fmt.Errorf("can't parse [%s], not a valid integer: %w", solaceMetaMsgRxRateTarget, err)
}
}

// Check that we have at least one positive target value for the scaler
if meta.msgCountTarget < 1 && meta.msgSpoolUsageTarget < 1 {
if meta.msgCountTarget < 1 && meta.msgSpoolUsageTarget < 1 && meta.msgRxRateTarget < 1 {
return nil, fmt.Errorf("no target value found in the scaler configuration")
}

Expand All @@ -202,6 +235,14 @@ func parseSolaceMetadata(config *ScalerConfig) (*SolaceMetadata, error) {
return nil, fmt.Errorf("can't parse [%s], not a valid integer: %w", solaceMetaActivationMsgSpoolUsageTarget, err)
}
}
meta.activationMsgRxRateTarget = 0
if val, ok := config.TriggerMetadata[solaceMetaActivationMsgRxRateTarget]; ok && val != "" {
if activationMsgRxRateTarget, err := strconv.Atoi(val); err == nil {
meta.activationMsgRxRateTarget = activationMsgRxRateTarget
} else {
return nil, fmt.Errorf("can't parse [%s], not a valid integer: %w", solaceMetaActivationMsgRxRateTarget, err)
}
}

// Format Solace SEMP Queue Endpoint (REST URL)
meta.endpointURL = fmt.Sprintf(
Expand Down Expand Up @@ -296,6 +337,18 @@ func (s *SolaceScaler) GetMetricSpecForScaling(context.Context) []v2.MetricSpec
metricSpec := v2.MetricSpec{External: externalMetric, Type: solaceExtMetricType}
metricSpecList = append(metricSpecList, metricSpec)
}
// Message Receive Rate Target Spec
if s.metadata.msgRxRateTarget > 0 {
metricName := kedautil.NormalizeString(fmt.Sprintf("solace-%s-%s", s.metadata.queueName, solaceTriggermsgrxrate))
externalMetric := &v2.ExternalMetricSource{
Metric: v2.MetricIdentifier{
Name: GenerateMetricNameWithIndex(s.metadata.scalerIndex, metricName),
},
Target: GetMetricTarget(s.metricType, s.metadata.msgRxRateTarget),
}
metricSpec := v2.MetricSpec{External: externalMetric, Type: solaceExtMetricType}
metricSpecList = append(metricSpecList, metricSpec)
}
return metricSpecList
}

Expand Down Expand Up @@ -341,6 +394,7 @@ func (s *SolaceScaler) getSolaceQueueMetricsFromSEMP(ctx context.Context) (Solac
// Set Return Values
metricValues.msgCount = sempResponse.Collections.Msgs.Count
metricValues.msgSpoolUsage = sempResponse.Data.MsgSpoolUsage
metricValues.msgRcvRate = sempResponse.Data.MsgRcvRate
return metricValues, nil
}

Expand All @@ -363,13 +417,19 @@ func (s *SolaceScaler) GetMetricsAndActivity(ctx context.Context, metricName str
metric = GenerateMetricInMili(metricName, float64(metricValues.msgCount))
case strings.HasSuffix(metricName, solaceTriggermsgspoolusage):
metric = GenerateMetricInMili(metricName, float64(metricValues.msgSpoolUsage))
case strings.HasSuffix(metricName, solaceTriggermsgrxrate):
metric = GenerateMetricInMili(metricName, float64(metricValues.msgRcvRate))
default:
// Should never end up here
err := fmt.Errorf("unidentified metric: %s", metricName)
s.logger.Error(err, "returning error to calling app")
return []external_metrics.ExternalMetricValue{}, false, err
}
return []external_metrics.ExternalMetricValue{metric}, (metricValues.msgCount > s.metadata.activationMsgCountTarget || metricValues.msgSpoolUsage > s.metadata.activationMsgSpoolUsageTarget), nil
return []external_metrics.ExternalMetricValue{metric},
(metricValues.msgCount > s.metadata.activationMsgCountTarget ||
metricValues.msgSpoolUsage > s.metadata.activationMsgSpoolUsageTarget ||
metricValues.msgRcvRate > s.metadata.activationMsgRxRateTarget),
nil
}

// Do Nothing - Satisfies Interface
Expand Down
122 changes: 113 additions & 9 deletions pkg/scalers/solace_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ type testSolaceMetadata struct {
}

var (
soltestValidBaseURL = "http://localhost:8080"
soltestValidUsername = "admin"
soltestValidPassword = "admin"
soltestValidVpn = "dennis_vpn"
soltestValidQueueName = "queue3"
soltestValidMsgCountTarget = "10"
soltestValidMsgSpoolTarget = "20"
soltestEnvUsername = "SOLTEST_USERNAME"
soltestEnvPassword = "SOLTEST_PASSWORD"
soltestValidBaseURL = "http://localhost:8080"
soltestValidUsername = "admin"
soltestValidPassword = "admin"
soltestValidVpn = "dennis_vpn"
soltestValidQueueName = "queue3"
soltestValidMsgCountTarget = "10"
soltestValidMsgSpoolTarget = "20"
soltestValidMsgRxRateTarget = "15"
soltestEnvUsername = "SOLTEST_USERNAME"
soltestEnvPassword = "SOLTEST_PASSWORD"
)

// AUTH RECORD FOR TEST
Expand Down Expand Up @@ -212,6 +213,39 @@ var testParseSolaceMetadata = []testSolaceMetadata{
1,
false,
},
// -Case - msgRxRateTarget non-numeric
{
"#014 - msgRxRateTarget non-numeric",
map[string]string{
solaceMetaSempBaseURL: soltestValidBaseURL,
solaceMetaMsgVpn: soltestValidVpn,
solaceMetaUsernameFromEnv: "",
solaceMetaPasswordFromEnv: "",
solaceMetaUsername: soltestValidUsername,
solaceMetaPassword: soltestValidPassword,
solaceMetaQueueName: soltestValidQueueName,
solaceMetaMsgRxRateTarget: "NOT_AN_INTEGER",
},
1,
true,
},
// -Case - activationMsgRxRateTarget non-numeric
{
"#015 - activationMsgRxRateTarget non-numeric",
map[string]string{
solaceMetaSempBaseURL: soltestValidBaseURL,
solaceMetaMsgVpn: soltestValidVpn,
solaceMetaUsernameFromEnv: "",
solaceMetaPasswordFromEnv: "",
solaceMetaUsername: soltestValidUsername,
solaceMetaPassword: soltestValidPassword,
solaceMetaQueueName: soltestValidQueueName,
solaceMetaMsgRxRateTarget: "10",
solaceMetaActivationMsgRxRateTarget: "NOT_AN_INTEGER",
},
1,
true,
},
}

var testSolaceEnvCreds = []testSolaceMetadata{
Expand Down Expand Up @@ -399,11 +433,81 @@ var testSolaceGetMetricSpecData = []testSolaceMetadata{
1,
false,
},
// Added for 'solaceMetaMsgRxRateTarget'
{
"#410 - Get Metric Spec - msgRxRateTarget",
map[string]string{
solaceMetaSempBaseURL: soltestValidBaseURL,
solaceMetaMsgVpn: soltestValidVpn,
solaceMetaUsernameFromEnv: "",
solaceMetaPasswordFromEnv: "",
solaceMetaUsername: soltestValidUsername,
solaceMetaPassword: soltestValidPassword,
solaceMetaQueueName: soltestValidQueueName,
solaceMetaMsgCountTarget: soltestValidMsgCountTarget,
// solaceMetaMsgSpoolUsageTarget: soltestValidMsgSpoolTarget,
solaceMetaMsgRxRateTarget: soltestValidMsgRxRateTarget,
},
1,
false,
},
{
"#411 - Get Metric Spec - ALL msgSpoolUsage, msgCountTarget, and msgRxRateTarget",
map[string]string{
solaceMetaSempBaseURL: soltestValidBaseURL,
solaceMetaMsgVpn: soltestValidVpn,
solaceMetaUsernameFromEnv: "",
solaceMetaPasswordFromEnv: "",
solaceMetaUsername: soltestValidUsername,
solaceMetaPassword: soltestValidPassword,
solaceMetaQueueName: soltestValidQueueName,
solaceMetaMsgCountTarget: soltestValidMsgCountTarget,
solaceMetaMsgSpoolUsageTarget: soltestValidMsgSpoolTarget,
solaceMetaMsgRxRateTarget: soltestValidMsgRxRateTarget,
},
1,
false,
},
{
"#412 - Get Metric Spec - ALL ZERO",
map[string]string{
solaceMetaSempBaseURL: soltestValidBaseURL,
solaceMetaMsgVpn: soltestValidVpn,
solaceMetaUsernameFromEnv: "",
solaceMetaPasswordFromEnv: "",
solaceMetaUsername: soltestValidUsername,
solaceMetaPassword: soltestValidPassword,
solaceMetaQueueName: soltestValidQueueName,
solaceMetaMsgCountTarget: "0",
solaceMetaMsgSpoolUsageTarget: "0",
solaceMetaMsgRxRateTarget: "0",
},
1,
true,
},
{
"#413 - Get Metric Spec - msgRxRateTarget, OTHERS ZERO",
map[string]string{
solaceMetaSempBaseURL: soltestValidBaseURL,
solaceMetaMsgVpn: soltestValidVpn,
solaceMetaUsernameFromEnv: "",
solaceMetaPasswordFromEnv: "",
solaceMetaUsername: soltestValidUsername,
solaceMetaPassword: soltestValidPassword,
solaceMetaQueueName: soltestValidQueueName,
solaceMetaMsgCountTarget: "0",
solaceMetaMsgSpoolUsageTarget: "0",
solaceMetaMsgRxRateTarget: soltestValidMsgRxRateTarget,
},
1,
false,
},
}

var testSolaceExpectedMetricNames = map[string]string{
"s1-" + solaceScalerID + "-" + soltestValidQueueName + "-" + solaceTriggermsgcount: "",
"s1-" + solaceScalerID + "-" + soltestValidQueueName + "-" + solaceTriggermsgspoolusage: "",
"s1-" + solaceScalerID + "-" + soltestValidQueueName + "-" + solaceTriggermsgrxrate: "",
}

func TestSolaceParseSolaceMetadata(t *testing.T) {
Expand Down
Loading