Skip to content

Commit

Permalink
Metrics API scaler to support parsing numbers in quotes as well as qu…
Browse files Browse the repository at this point in the history
…antities. (#1668)

* Support parsing numbers in quotes and quantities.
Quantity is returned as a member of ExternalMetricValue so there should be no impact.

Signed-off-by: Joe Shearn <joseph.shearn@sainsburys.co.uk>

* improve error handling when parsing quantities

Signed-off-by: Joe Shearn <joseph.shearn@sainsburys.co.uk>

Co-authored-by: Joe Shearn <joseph.shearn@sainsburys.co.uk>
  • Loading branch information
devjoes and devjoes authored Mar 16, 2021
1 parent 4822272 commit 50b9090
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
### New

- TODO ([#XXX](https://github.com/kedacore/keda/issues/XXX))
- Support Quantities in metrics API scaler ([#1667](https://github.com/kedacore/keda/issues/1667))
- Emit Kubernetes Events on KEDA events ([#1523](https://github.com/kedacore/keda/pull/1523))
- Add Microsoft SQL Server (MSSQL) scaler ([#674](https://github.com/kedacore/keda/issues/674) | [docs](https://keda.sh/docs/2.2/scalers/mssql/))

Expand Down
31 changes: 19 additions & 12 deletions pkg/scalers/metrics_api_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,39 +172,46 @@ func parseMetricsAPIMetadata(config *ScalerConfig) (*metricsAPIScalerMetadata, e
}

// GetValueFromResponse uses provided valueLocation to access the numeric value in provided body
func GetValueFromResponse(body []byte, valueLocation string) (int64, error) {
func GetValueFromResponse(body []byte, valueLocation string) (*resource.Quantity, error) {
r := gjson.GetBytes(body, valueLocation)
errorMsg := "valueLocation must point to value of type number or a string representing a Quanitity got: '%s'"
if r.Type == gjson.String {
q, err := resource.ParseQuantity(r.String())
if err != nil {
return nil, fmt.Errorf(errorMsg, r.String())
}
return &q, nil
}
if r.Type != gjson.Number {
msg := fmt.Sprintf("valueLocation must point to value of type number got: %s", r.Type.String())
return 0, errors.New(msg)
return nil, fmt.Errorf(errorMsg, r.Type.String())
}
return int64(r.Num), nil
return resource.NewQuantity(int64(r.Num), resource.DecimalSI), nil
}

func (s *metricsAPIScaler) getMetricValue() (int64, error) {
func (s *metricsAPIScaler) getMetricValue() (*resource.Quantity, error) {
request, err := getMetricAPIServerRequest(s.metadata)
if err != nil {
return 0, err
return nil, err
}

r, err := s.client.Do(request)
if err != nil {
return 0, err
return nil, err
}

if r.StatusCode != http.StatusOK {
msg := fmt.Sprintf("api returned %d", r.StatusCode)
return 0, errors.New(msg)
return nil, errors.New(msg)
}

defer r.Body.Close()
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return 0, err
return nil, err
}
v, err := GetValueFromResponse(b, s.metadata.valueLocation)
if err != nil {
return 0, err
return nil, err
}
return v, nil
}
Expand All @@ -222,7 +229,7 @@ func (s *metricsAPIScaler) IsActive(ctx context.Context) (bool, error) {
return false, err
}

return v > 0.0, nil
return v.AsApproximateFloat64() > 0.0, nil
}

// GetMetricSpecForScaling returns the MetricSpec for the Horizontal Pod Autoscaler
Expand Down Expand Up @@ -253,7 +260,7 @@ func (s *metricsAPIScaler) GetMetrics(ctx context.Context, metricName string, me

metric := external_metrics.ExternalMetricValue{
MetricName: metricName,
Value: *resource.NewQuantity(v, resource.DecimalSI),
Value: *v,
Timestamp: metav1.Now(),
}

Expand Down
31 changes: 26 additions & 5 deletions pkg/scalers/metrics_api_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,42 @@ func TestParseMetricsAPIMetadata(t *testing.T) {
}

func TestGetValueFromResponse(t *testing.T) {
d := []byte(`{"components":[{"id": "82328e93e", "tasks": 32}],"count":2.43}`)
d := []byte(`{"components":[{"id": "82328e93e", "tasks": 32, "str": "64", "k":"1k","wrong":"NaN"}],"count":2.43}`)
v, err := GetValueFromResponse(d, "components.0.tasks")
if err != nil {
t.Error("Expected success but got error", err)
}
if v != 32 {
t.Errorf("Expected %d got %d", 32, v)
if v.CmpInt64(32) != 0 {
t.Errorf("Expected %d got %d", 32, v.AsDec())
}

v, err = GetValueFromResponse(d, "count")
if err != nil {
t.Error("Expected success but got error", err)
}
if v != 2 {
t.Errorf("Expected %d got %d", 2, v)
if v.CmpInt64(2) != 0 {
t.Errorf("Expected %d got %d", 2, v.AsDec())
}

v, err = GetValueFromResponse(d, "components.0.str")
if err != nil {
t.Error("Expected success but got error", err)
}
if v.CmpInt64(64) != 0 {
t.Errorf("Expected %d got %d", 64, v.AsDec())
}

v, err = GetValueFromResponse(d, "components.0.k")
if err != nil {
t.Error("Expected success but got error", err)
}
if v.CmpInt64(1000) != 0 {
t.Errorf("Expected %d got %d", 1000, v.AsDec())
}

_, err = GetValueFromResponse(d, "components.0.wrong")
if err == nil {
t.Error("Expected error but got success", err)
}
}

Expand Down

0 comments on commit 50b9090

Please sign in to comment.