From 55330656b9610cf7c87f9597dd8093386c7895b5 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Tue, 21 Jan 2020 20:40:17 -0500 Subject: [PATCH 01/25] Just KEDA portion of scaler, possibly working --- pkg/handler/scale_handler.go | 2 + pkg/scalers/azure_monitor_scaler.go | 286 ++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 pkg/scalers/azure_monitor_scaler.go diff --git a/pkg/handler/scale_handler.go b/pkg/handler/scale_handler.go index 3a1347b7823..4b9e3bd6cb4 100644 --- a/pkg/handler/scale_handler.go +++ b/pkg/handler/scale_handler.go @@ -306,6 +306,8 @@ func (h *ScaleHandler) getScaler(name, namespace, triggerType string, resolvedEn return scalers.NewPostgresScaler(resolvedEnv, triggerMetadata, authParams) case "mysql": return scalers.NewMySQLScaler(resolvedEnv, triggerMetadata, authParams) + case "azure-monitor": + return scalers.NewAzureMonitorScaler(resolvedEnv, triggerMetadata, authParams) default: return nil, fmt.Errorf("no scaler found for type: %s", triggerType) } diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go new file mode 100644 index 00000000000..a60a6ff14e2 --- /dev/null +++ b/pkg/scalers/azure_monitor_scaler.go @@ -0,0 +1,286 @@ +package scalers + +import ( + "context" + "fmt" + + v2beta1 "k8s.io/api/autoscaling/v2beta1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/metrics/pkg/apis/external_metrics" + logf "sigs.k8s.io/controller-runtime/pkg/log" +) + +const ( + defaultTargetMetricValue = 5 + azureMonitorMetricName = "metricName" +) + +type azureMonitorScaler struct { + metadata *azureMonitorMetadata +} + +type azureMonitorMetadata struct { + resourceURI string + tentantID string + subscriptionID string + resourceGroupName string + name string + filter string + aggregationInterval string + aggregationType string + servicePrincipalID string + servicePrincipalPass string + targetMetricValue int +} + +var azureMonitorLog = logf.Log.WithName("azure_monitor_scaler") + +// NewAzureMonitorScaler stuff +func NewAzureMonitorScaler(resolvedEnv, metadata, authParams map[string]string) (Scaler, error) { + meta, err := parseAzureMonitorMetadata(metadata, resolvedEnv, authParams) + if err != nil { + return nil, fmt.Errorf("error parsing azure monitor metadata: %s", err) + } + + return &azureMonitorScaler{ + metadata: meta, + }, nil +} + +func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]string) (*azureMonitorMetadata, error) { + meta := azureMonitorMetadata{} + meta.targetMetricValue = defaultTargetMetricValue + + /*if val, ok := metadata[metricResourceURI]; ok { + Length, err := strconv.Atoi(val) + if err != nil { + azureMonitorLog.Error(err, "Error parsing azure queue metadata", "queueLengthMet ricName", LengthMetricName) + return nil, "", fmt.Errorf("Error parsing azure queue metadata %s: %s", LengthMetricName, err.Error()) + } + + meta.targetLength = Length + }*/ + + if val, ok := metadata["resourceURI"]; ok && val != "" { + meta.resourceURI = val + } else { + return nil, fmt.Errorf("no resourceURI given") + } + + if val, ok := metadata["resourceTenantId"]; ok && val != "" { + meta.tentantID = val + } else { + return nil, fmt.Errorf("no resourceTenantId given") + } + + if val, ok := metadata["resourceSubscriptionId"]; ok && val != "" { + meta.subscriptionID = val + } else { + return nil, fmt.Errorf("no resourceSubscriptionId given") + } + + if val, ok := metadata["resourceGroupName"]; ok && val != "" { + meta.resourceGroupName = val + } else { + return nil, fmt.Errorf("no resourceGroupName given") + } + + if val, ok := metadata[azureMonitorMetricName]; ok && val != "" { + meta.name = val + } else { + return nil, fmt.Errorf("no metricName given") + } + + if val, ok := metadata["metricFilter"]; ok { + if val != "" { + meta.filter = val + } + } + + if val, ok := metadata["metricAggregationInterval"]; ok { + if val != "" { + meta.aggregationInterval = val + } + } + + if val, ok := metadata["metricAggregationType"]; ok && val != "" { + meta.subscriptionID = val + } else { + return nil, fmt.Errorf("no metricAggregationType given") + } + + if val, ok := metadata["adServicePrincipleId"]; ok && val != "" { + meta.servicePrincipalID = val + } else { + return nil, fmt.Errorf("no adServicePrincipleId given") + } + + if val, ok := metadata["adServicePrinciplePassword"]; ok { + meta.servicePrincipalPass = val + } else { + return nil, fmt.Errorf("no adServicePrinciplePassword given") + } + + return &meta, nil +} + +// needs to interact with azure monitor +func (s *azureMonitorScaler) IsActive(ctx context.Context) (bool, error) { + val, err := s.GetAzureMetric() + if err != nil { + azureMonitorLog.Error(err, "error getting azure monitor metric") + return false, err + } + + return val > 0, nil +} + +func (s *azureMonitorScaler) Close() error { + return nil +} + +func (s *azureMonitorScaler) GetMetricSpecForScaling() []v2beta1.MetricSpec { + targetMetricVal := resource.NewQuantity(int64(s.metadata.targetMetricValue), resource.DecimalSI) + externalMetric := &v2beta1.ExternalMetricSource{MetricName: azureMonitorMetricName, TargetAverageValue: targetMetricVal} + metricSpec := v2beta1.MetricSpec{External: externalMetric, Type: externalMetricType} + return []v2beta1.MetricSpec{metricSpec} +} + +// GetMetrics returns value for a supported metric and an error if there is a problem getting the metric +func (s *azureMonitorScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) { + val, err := s.GetAzureMetric() + if err != nil { + azureMonitorLog.Error(err, "error getting azure monitor metric") + return []external_metrics.ExternalMetricValue{}, err + } + + metric := external_metrics.ExternalMetricValue{ + MetricName: metricName, + Value: *resource.NewQuantity(int64(val), resource.DecimalSI), + Timestamp: metav1.Now(), + } + + return append([]external_metrics.ExternalMetricValue{}, metric), nil +} + +// GetAzureMetric does things +func (s *azureMonitorScaler) GetAzureMetric() (int, error) { + return defaultTargetMetricValue, nil +} + +/* +package externalmetrics + +import ( + "context" + "fmt" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" + "github.com/Azure/go-autorest/autorest/azure/auth" + "k8s.io/klog" +) + +type insightsmonitorClient interface { + List(ctx context.Context, resourceURI string, timespan string, interval *string, metricnames string, aggregation string, top *int32, orderby string, filter string, resultType insights.ResultType, metricnamespace string) (result insights.Response, err error) +} + +type monitorClient struct { + client insightsmonitorClient + DefaultSubscriptionID string +} + +func NewMonitorClient(defaultsubscriptionID string) AzureExternalMetricClient { + client := insights.NewMetricsClient(defaultsubscriptionID) + authorizer, err := auth.NewAuthorizerFromEnvironment() + if err == nil { + client.Authorizer = authorizer + } + + return &monitorClient{ + client: client, + DefaultSubscriptionID: defaultsubscriptionID, + } +} + +func newMonitorClient(defaultsubscriptionID string, client insightsmonitorClient) monitorClient { + return monitorClient{ + client: client, + DefaultSubscriptionID: defaultsubscriptionID, + } +} + +// GetAzureMetric calls Azure Monitor endpoint and returns a metric +func (c *monitorClient) GetAzureMetric(azMetricRequest AzureExternalMetricRequest) (AzureExternalMetricResponse, error) { + err := azMetricRequest.Validate() + if err != nil { + return AzureExternalMetricResponse{}, err + } + + metricResourceURI := azMetricRequest.MetricResourceURI() + klog.V(2).Infof("resource uri: %s", metricResourceURI) + + metricResult, err := c.client.List(context.Background(), metricResourceURI, + azMetricRequest.Timespan, nil, + azMetricRequest.MetricName, azMetricRequest.Aggregation, nil, + "", azMetricRequest.Filter, "", "") + if err != nil { + return AzureExternalMetricResponse{}, err + } + + value, err := extractValue(azMetricRequest, metricResult) + + return AzureExternalMetricResponse{ + Value: value, + }, err +} + +func extractValue(azMetricRequest AzureExternalMetricRequest, metricResult insights.Response) (float64, error) { + metricVals := *metricResult.Value + + if len(metricVals) == 0 { + err := fmt.Errorf("Got an empty response for metric %s/%s and aggregate type %s", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + return 0, err + } + + timeseries := *metricVals[0].Timeseries + if timeseries == nil { + err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without timeseries", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + return 0, err + } + + data := *timeseries[0].Data + if data == nil { + err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without any metric values", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + return 0, err + } + + var valuePtr *float64 + if strings.EqualFold(string(insights.Average), azMetricRequest.Aggregation) && data[len(data)-1].Average != nil { + valuePtr = data[len(data)-1].Average + } else if strings.EqualFold(string(insights.Total), azMetricRequest.Aggregation) && data[len(data)-1].Total != nil { + valuePtr = data[len(data)-1].Total + } else if strings.EqualFold(string(insights.Maximum), azMetricRequest.Aggregation) && data[len(data)-1].Maximum != nil { + valuePtr = data[len(data)-1].Maximum + } else if strings.EqualFold(string(insights.Minimum), azMetricRequest.Aggregation) && data[len(data)-1].Minimum != nil { + valuePtr = data[len(data)-1].Minimum + } else if strings.EqualFold(string(insights.Count), azMetricRequest.Aggregation) && data[len(data)-1].Count != nil { + fValue := float64(*data[len(data)-1].Count) + valuePtr = &fValue + } else { + err := fmt.Errorf("Unsupported aggregation type %s specified in metric %s/%s", insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation)), azMetricRequest.Namespace, azMetricRequest.MetricName) + return 0, err + } + + if valuePtr == nil { + err := fmt.Errorf("Unable to get value for metric %s/%s with aggregation %s. No value returned by the Azure Monitor", azMetricRequest.Namespace, azMetricRequest.MetricName, azMetricRequest.Aggregation) + return 0, err + } + + klog.V(2).Infof("metric type: %s %f", azMetricRequest.Aggregation, *valuePtr) + + return *valuePtr, nil +} +*/ From c2135a188e6eeae296f193c13dc868132a6ca6cf Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 22 Jan 2020 17:11:19 -0500 Subject: [PATCH 02/25] Hard code value until get azure monitor working --- pkg/scalers/azure_monitor_scaler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index a60a6ff14e2..7d7a50892e0 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -167,7 +167,7 @@ func (s *azureMonitorScaler) GetMetrics(ctx context.Context, metricName string, // GetAzureMetric does things func (s *azureMonitorScaler) GetAzureMetric() (int, error) { - return defaultTargetMetricValue, nil + return 16, nil } /* From 126028cd35d3752f6949d0c7d5bfa209b1269c7e Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 22 Jan 2020 17:27:26 -0500 Subject: [PATCH 03/25] Add metricThreshold to metadata --- pkg/scalers/azure_monitor_scaler.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 7d7a50892e0..995640d87ce 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -3,6 +3,7 @@ package scalers import ( "context" "fmt" + "strconv" v2beta1 "k8s.io/api/autoscaling/v2beta1" "k8s.io/apimachinery/pkg/api/resource" @@ -13,8 +14,8 @@ import ( ) const ( - defaultTargetMetricValue = 5 - azureMonitorMetricName = "metricName" + azureMonitorMetricName = "metricName" + metricThresholdName = "metricThreshold" ) type azureMonitorScaler struct { @@ -32,7 +33,7 @@ type azureMonitorMetadata struct { aggregationType string servicePrincipalID string servicePrincipalPass string - targetMetricValue int + metricThreshold int } var azureMonitorLog = logf.Log.WithName("azure_monitor_scaler") @@ -51,17 +52,16 @@ func NewAzureMonitorScaler(resolvedEnv, metadata, authParams map[string]string) func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]string) (*azureMonitorMetadata, error) { meta := azureMonitorMetadata{} - meta.targetMetricValue = defaultTargetMetricValue - /*if val, ok := metadata[metricResourceURI]; ok { - Length, err := strconv.Atoi(val) + if val, ok := metadata[metricThresholdName]; ok && val != "" { + metricThreshold, err := strconv.Atoi(val) if err != nil { - azureMonitorLog.Error(err, "Error parsing azure queue metadata", "queueLengthMet ricName", LengthMetricName) - return nil, "", fmt.Errorf("Error parsing azure queue metadata %s: %s", LengthMetricName, err.Error()) + azureMonitorLog.Error(err, "Error parsing azure monitor metadata", "metricThreshold", metricThresholdName) + return nil, fmt.Errorf("Error parsing azure monitor metadata %s: %s", metricThresholdName, err.Error()) } - meta.targetLength = Length - }*/ + meta.metricThreshold = metricThreshold + } if val, ok := metadata["resourceURI"]; ok && val != "" { meta.resourceURI = val @@ -142,7 +142,7 @@ func (s *azureMonitorScaler) Close() error { } func (s *azureMonitorScaler) GetMetricSpecForScaling() []v2beta1.MetricSpec { - targetMetricVal := resource.NewQuantity(int64(s.metadata.targetMetricValue), resource.DecimalSI) + targetMetricVal := resource.NewQuantity(int64(s.metadata.metricThreshold), resource.DecimalSI) externalMetric := &v2beta1.ExternalMetricSource{MetricName: azureMonitorMetricName, TargetAverageValue: targetMetricVal} metricSpec := v2beta1.MetricSpec{External: externalMetric, Type: externalMetricType} return []v2beta1.MetricSpec{metricSpec} From 06810042cab4449644b3f6cec975dc612b6a53be Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Mon, 27 Jan 2020 14:18:44 -0500 Subject: [PATCH 04/25] Create metrics client and get azure metric. Still need to create metric request --- pkg/scalers/azure_monitor.go | 163 ++++++++++++++++++++++++++++ pkg/scalers/azure_monitor_scaler.go | 148 +++---------------------- 2 files changed, 177 insertions(+), 134 deletions(-) create mode 100644 pkg/scalers/azure_monitor.go diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go new file mode 100644 index 00000000000..e2f385efcb6 --- /dev/null +++ b/pkg/scalers/azure_monitor.go @@ -0,0 +1,163 @@ +package scalers + +import ( + "context" + "fmt" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" + "github.com/Azure/go-autorest/autorest/azure/auth" + "k8s.io/klog" +) + +type azureExternalMetricRequest struct { + MetricName string + SubscriptionID string + Type string + ResourceName string + ResourceProviderNamespace string + ResourceType string + Aggregation string + Timespan string + Filter string + ResourceGroup string + Namespace string + Topic string + Subscription string +} + +type azureExternalMetricResponse struct { + Value float64 +} + +type azureExternalMetricClient interface { + getAzureMetric(azMetricRequest azureExternalMetricRequest) (azureExternalMetricResponse, error) +} + +type insightsmonitorClient interface { + List(ctx context.Context, resourceURI string, timespan string, interval *string, metricnames string, aggregation string, top *int32, orderby string, filter string, resultType insights.ResultType, metricnamespace string) (result insights.Response, err error) +} + +type monitorClient struct { + client insightsmonitorClient +} + +// GetAzureMetricValue is a func +func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetadata) (int32, error) { + metricsClient := newMonitorClient(metricMetadata) + + metricResponse, err := metricsClient.getAzureMetric(metricRequest) + if err != nil { + azureMonitorLog.Error(err, "error getting azure monitor metric") + } + + return 2, nil +} + +func newMonitorClient(metadata *azureMonitorMetadata) azureExternalMetricClient { + client := insights.NewMetricsClient(metadata.subscriptionID) + config := auth.NewClientCredentialsConfig(metadata.servicePrincipalID, metadata.servicePrincipalPass, metadata.tentantID) + + authorizer, err := config.Authorizer() + if err == nil { + client.Authorizer = authorizer + } + + return &monitorClient{ + client: client, + } +} + +func (c *monitorClient) getAzureMetric(azMetricRequest azureExternalMetricRequest) (azureExternalMetricResponse, error) { + err := azMetricRequest.validate() + if err != nil { + return azureExternalMetricResponse{}, err + } + + metricResourceURI := azMetricRequest.metricResourceURI() + klog.V(2).Infof("resource uri: %s", metricResourceURI) + + metricResult, err := c.client.List(context.Background(), metricResourceURI, + azMetricRequest.Timespan, nil, + azMetricRequest.MetricName, azMetricRequest.Aggregation, nil, + "", azMetricRequest.Filter, "", "") + if err != nil { + return azureExternalMetricResponse{}, err + } + + value, err := extractValue(azMetricRequest, metricResult) + + return azureExternalMetricResponse{ + Value: value, + }, err +} + +func extractValue(azMetricRequest azureExternalMetricRequest, metricResult insights.Response) (float64, error) { + metricVals := *metricResult.Value + + if len(metricVals) == 0 { + err := fmt.Errorf("Got an empty response for metric %s/%s and aggregate type %s", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + return 0, err + } + + timeseries := *metricVals[0].Timeseries + if timeseries == nil { + err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without timeseries", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + return 0, err + } + + data := *timeseries[0].Data + if data == nil { + err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without any metric values", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + return 0, err + } + + var valuePtr *float64 + if strings.EqualFold(string(insights.Average), azMetricRequest.Aggregation) && data[len(data)-1].Average != nil { + valuePtr = data[len(data)-1].Average + } else if strings.EqualFold(string(insights.Total), azMetricRequest.Aggregation) && data[len(data)-1].Total != nil { + valuePtr = data[len(data)-1].Total + } else if strings.EqualFold(string(insights.Maximum), azMetricRequest.Aggregation) && data[len(data)-1].Maximum != nil { + valuePtr = data[len(data)-1].Maximum + } else if strings.EqualFold(string(insights.Minimum), azMetricRequest.Aggregation) && data[len(data)-1].Minimum != nil { + valuePtr = data[len(data)-1].Minimum + } else if strings.EqualFold(string(insights.Count), azMetricRequest.Aggregation) && data[len(data)-1].Count != nil { + fValue := float64(*data[len(data)-1].Count) + valuePtr = &fValue + } else { + err := fmt.Errorf("Unsupported aggregation type %s specified in metric %s/%s", insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation)), azMetricRequest.Namespace, azMetricRequest.MetricName) + return 0, err + } + + if valuePtr == nil { + err := fmt.Errorf("Unable to get value for metric %s/%s with aggregation %s. No value returned by the Azure Monitor", azMetricRequest.Namespace, azMetricRequest.MetricName, azMetricRequest.Aggregation) + return 0, err + } + + klog.V(2).Infof("metric type: %s %f", azMetricRequest.Aggregation, *valuePtr) + + return *valuePtr, nil +} + +func (amr azureExternalMetricRequest) validate() error { + // Shared + if amr.MetricName == "" { + return fmt.Errorf("metricName is required") + } + if amr.ResourceGroup == "" { + return fmt.Errorf("resourceGroup is required") + } + if amr.SubscriptionID == "" { + return fmt.Errorf("subscriptionID is required. set a default or pass via label selectors") + } + return nil +} + +func (amr azureExternalMetricRequest) metricResourceURI() string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s", + amr.SubscriptionID, + amr.ResourceGroup, + amr.ResourceProviderNamespace, + amr.ResourceType, + amr.ResourceName) +} \ No newline at end of file diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 995640d87ce..401b3bbf269 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -15,7 +15,7 @@ import ( const ( azureMonitorMetricName = "metricName" - metricThresholdName = "metricThreshold" + targetValueName = "targetValue" ) type azureMonitorScaler struct { @@ -33,7 +33,7 @@ type azureMonitorMetadata struct { aggregationType string servicePrincipalID string servicePrincipalPass string - metricThreshold int + targetValue int } var azureMonitorLog = logf.Log.WithName("azure_monitor_scaler") @@ -53,14 +53,14 @@ func NewAzureMonitorScaler(resolvedEnv, metadata, authParams map[string]string) func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]string) (*azureMonitorMetadata, error) { meta := azureMonitorMetadata{} - if val, ok := metadata[metricThresholdName]; ok && val != "" { - metricThreshold, err := strconv.Atoi(val) + if val, ok := metadata[targetValueName]; ok && val != "" { + targetValue, err := strconv.Atoi(val) if err != nil { - azureMonitorLog.Error(err, "Error parsing azure monitor metadata", "metricThreshold", metricThresholdName) - return nil, fmt.Errorf("Error parsing azure monitor metadata %s: %s", metricThresholdName, err.Error()) + azureMonitorLog.Error(err, "Error parsing azure monitor metadata", "targetValue", targetValueName) + return nil, fmt.Errorf("Error parsing azure monitor metadata %s: %s", targetValueName, err.Error()) } - meta.metricThreshold = metricThreshold + meta.targetValue = targetValue } if val, ok := metadata["resourceURI"]; ok && val != "" { @@ -69,16 +69,16 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri return nil, fmt.Errorf("no resourceURI given") } - if val, ok := metadata["resourceTenantId"]; ok && val != "" { + if val, ok := metadata["tenantId"]; ok && val != "" { meta.tentantID = val } else { - return nil, fmt.Errorf("no resourceTenantId given") + return nil, fmt.Errorf("no tenantId given") } - if val, ok := metadata["resourceSubscriptionId"]; ok && val != "" { + if val, ok := metadata["subscriptionId"]; ok && val != "" { meta.subscriptionID = val } else { - return nil, fmt.Errorf("no resourceSubscriptionId given") + return nil, fmt.Errorf("no subscriptionId given") } if val, ok := metadata["resourceGroupName"]; ok && val != "" { @@ -128,7 +128,7 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri // needs to interact with azure monitor func (s *azureMonitorScaler) IsActive(ctx context.Context) (bool, error) { - val, err := s.GetAzureMetric() + val, err := GetAzureMetricValue(ctx, s.metadata) if err != nil { azureMonitorLog.Error(err, "error getting azure monitor metric") return false, err @@ -142,7 +142,7 @@ func (s *azureMonitorScaler) Close() error { } func (s *azureMonitorScaler) GetMetricSpecForScaling() []v2beta1.MetricSpec { - targetMetricVal := resource.NewQuantity(int64(s.metadata.metricThreshold), resource.DecimalSI) + targetMetricVal := resource.NewQuantity(int64(s.metadata.targetValue), resource.DecimalSI) externalMetric := &v2beta1.ExternalMetricSource{MetricName: azureMonitorMetricName, TargetAverageValue: targetMetricVal} metricSpec := v2beta1.MetricSpec{External: externalMetric, Type: externalMetricType} return []v2beta1.MetricSpec{metricSpec} @@ -150,7 +150,7 @@ func (s *azureMonitorScaler) GetMetricSpecForScaling() []v2beta1.MetricSpec { // GetMetrics returns value for a supported metric and an error if there is a problem getting the metric func (s *azureMonitorScaler) GetMetrics(ctx context.Context, metricName string, metricSelector labels.Selector) ([]external_metrics.ExternalMetricValue, error) { - val, err := s.GetAzureMetric() + val, err := GetAzureMetricValue(ctx, s.metadata) if err != nil { azureMonitorLog.Error(err, "error getting azure monitor metric") return []external_metrics.ExternalMetricValue{}, err @@ -164,123 +164,3 @@ func (s *azureMonitorScaler) GetMetrics(ctx context.Context, metricName string, return append([]external_metrics.ExternalMetricValue{}, metric), nil } - -// GetAzureMetric does things -func (s *azureMonitorScaler) GetAzureMetric() (int, error) { - return 16, nil -} - -/* -package externalmetrics - -import ( - "context" - "fmt" - "strings" - - "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" - "github.com/Azure/go-autorest/autorest/azure/auth" - "k8s.io/klog" -) - -type insightsmonitorClient interface { - List(ctx context.Context, resourceURI string, timespan string, interval *string, metricnames string, aggregation string, top *int32, orderby string, filter string, resultType insights.ResultType, metricnamespace string) (result insights.Response, err error) -} - -type monitorClient struct { - client insightsmonitorClient - DefaultSubscriptionID string -} - -func NewMonitorClient(defaultsubscriptionID string) AzureExternalMetricClient { - client := insights.NewMetricsClient(defaultsubscriptionID) - authorizer, err := auth.NewAuthorizerFromEnvironment() - if err == nil { - client.Authorizer = authorizer - } - - return &monitorClient{ - client: client, - DefaultSubscriptionID: defaultsubscriptionID, - } -} - -func newMonitorClient(defaultsubscriptionID string, client insightsmonitorClient) monitorClient { - return monitorClient{ - client: client, - DefaultSubscriptionID: defaultsubscriptionID, - } -} - -// GetAzureMetric calls Azure Monitor endpoint and returns a metric -func (c *monitorClient) GetAzureMetric(azMetricRequest AzureExternalMetricRequest) (AzureExternalMetricResponse, error) { - err := azMetricRequest.Validate() - if err != nil { - return AzureExternalMetricResponse{}, err - } - - metricResourceURI := azMetricRequest.MetricResourceURI() - klog.V(2).Infof("resource uri: %s", metricResourceURI) - - metricResult, err := c.client.List(context.Background(), metricResourceURI, - azMetricRequest.Timespan, nil, - azMetricRequest.MetricName, azMetricRequest.Aggregation, nil, - "", azMetricRequest.Filter, "", "") - if err != nil { - return AzureExternalMetricResponse{}, err - } - - value, err := extractValue(azMetricRequest, metricResult) - - return AzureExternalMetricResponse{ - Value: value, - }, err -} - -func extractValue(azMetricRequest AzureExternalMetricRequest, metricResult insights.Response) (float64, error) { - metricVals := *metricResult.Value - - if len(metricVals) == 0 { - err := fmt.Errorf("Got an empty response for metric %s/%s and aggregate type %s", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) - return 0, err - } - - timeseries := *metricVals[0].Timeseries - if timeseries == nil { - err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without timeseries", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) - return 0, err - } - - data := *timeseries[0].Data - if data == nil { - err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without any metric values", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) - return 0, err - } - - var valuePtr *float64 - if strings.EqualFold(string(insights.Average), azMetricRequest.Aggregation) && data[len(data)-1].Average != nil { - valuePtr = data[len(data)-1].Average - } else if strings.EqualFold(string(insights.Total), azMetricRequest.Aggregation) && data[len(data)-1].Total != nil { - valuePtr = data[len(data)-1].Total - } else if strings.EqualFold(string(insights.Maximum), azMetricRequest.Aggregation) && data[len(data)-1].Maximum != nil { - valuePtr = data[len(data)-1].Maximum - } else if strings.EqualFold(string(insights.Minimum), azMetricRequest.Aggregation) && data[len(data)-1].Minimum != nil { - valuePtr = data[len(data)-1].Minimum - } else if strings.EqualFold(string(insights.Count), azMetricRequest.Aggregation) && data[len(data)-1].Count != nil { - fValue := float64(*data[len(data)-1].Count) - valuePtr = &fValue - } else { - err := fmt.Errorf("Unsupported aggregation type %s specified in metric %s/%s", insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation)), azMetricRequest.Namespace, azMetricRequest.MetricName) - return 0, err - } - - if valuePtr == nil { - err := fmt.Errorf("Unable to get value for metric %s/%s with aggregation %s. No value returned by the Azure Monitor", azMetricRequest.Namespace, azMetricRequest.MetricName, azMetricRequest.Aggregation) - return 0, err - } - - klog.V(2).Infof("metric type: %s %f", azMetricRequest.Aggregation, *valuePtr) - - return *valuePtr, nil -} -*/ From cadf1ca0d4434e2845f5fbe464ed6d708c92d558 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Mon, 27 Jan 2020 16:30:09 -0500 Subject: [PATCH 05/25] possibly working? --- pkg/scalers/azure_monitor.go | 48 +++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index e2f385efcb6..8b20249c13a 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -2,8 +2,10 @@ package scalers import ( "context" - "fmt" - "strings" + "fmt" + "math" + "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" "github.com/Azure/go-autorest/autorest/azure/auth" @@ -14,9 +16,7 @@ type azureExternalMetricRequest struct { MetricName string SubscriptionID string Type string - ResourceName string - ResourceProviderNamespace string - ResourceType string + ResourceURI string Aggregation string Timespan string Filter string @@ -46,12 +46,36 @@ type monitorClient struct { func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetadata) (int32, error) { metricsClient := newMonitorClient(metricMetadata) + metricRequest := azureExternalMetricRequest{ + Timespan: timeSpan(), + SubscriptionID: metricMetadata.subscriptionID, + } + + metricRequest.MetricName = metricMetadata.name + metricRequest.ResourceGroup = metricMetadata.resourceGroupName + metricRequest.ResourceURI = metricMetadata.resourceURI + metricRequest.Aggregation = metricMetadata.aggregationType + + filter := metricMetadata.filter + if filter != "" { + metricRequest.Filter = filter + } + + aggregationInterval := metricMetadata.aggregationInterval + if aggregationInterval != "" { + metricRequest.Timespan = aggregationInterval + } + metricResponse, err := metricsClient.getAzureMetric(metricRequest) if err != nil { azureMonitorLog.Error(err, "error getting azure monitor metric") + return -1, fmt.Errorf("Error getting azure monitor metric %s: %s", metricRequest.MetricName, err.Error()) } - return 2, nil + // casting drops everything after decimal, so round first + metricValue := int32(math.Round(metricResponse.Value)) + + return metricValue, nil } func newMonitorClient(metadata *azureMonitorMetadata) azureExternalMetricClient { @@ -157,7 +181,13 @@ func (amr azureExternalMetricRequest) metricResourceURI() string { return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s", amr.SubscriptionID, amr.ResourceGroup, - amr.ResourceProviderNamespace, - amr.ResourceType, - amr.ResourceName) + amr.ResourceURI) +} + +func timeSpan() string { + // defaults to last five minutes. + // TODO support configuration via config + endtime := time.Now().UTC().Format(time.RFC3339) + starttime := time.Now().Add(-(5 * time.Minute)).UTC().Format(time.RFC3339) + return fmt.Sprintf("%s/%s", starttime, endtime) } \ No newline at end of file From c090ef5e7808d8eb752bec7de6730c193f4e69b0 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Mon, 27 Jan 2020 18:28:25 -0500 Subject: [PATCH 06/25] Fix copy/paste error --- pkg/scalers/azure_monitor_scaler.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 401b3bbf269..c33453655ab 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -105,12 +105,6 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri } } - if val, ok := metadata["metricAggregationType"]; ok && val != "" { - meta.subscriptionID = val - } else { - return nil, fmt.Errorf("no metricAggregationType given") - } - if val, ok := metadata["adServicePrincipleId"]; ok && val != "" { meta.servicePrincipalID = val } else { From 352af5f0182e1372bc8b7799371c8c5edb985f11 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Tue, 28 Jan 2020 16:13:58 -0500 Subject: [PATCH 07/25] Set aggregationType, ResourceProviderNamespace, ResourceType, and ResourceName --- pkg/scalers/azure_monitor.go | 101 +++++++++++++++------------- pkg/scalers/azure_monitor_scaler.go | 6 ++ 2 files changed, 61 insertions(+), 46 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index 8b20249c13a..8ca94beaeba 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -2,10 +2,10 @@ package scalers import ( "context" - "fmt" - "math" - "strings" - "time" + "fmt" + "math" + "strings" + "time" "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" "github.com/Azure/go-autorest/autorest/azure/auth" @@ -16,12 +16,13 @@ type azureExternalMetricRequest struct { MetricName string SubscriptionID string Type string - ResourceURI string + ResourceName string + ResourceProviderNamespace string + ResourceType string Aggregation string Timespan string Filter string ResourceGroup string - Namespace string Topic string Subscription string } @@ -44,44 +45,50 @@ type monitorClient struct { // GetAzureMetricValue is a func func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetadata) (int32, error) { - metricsClient := newMonitorClient(metricMetadata) + metricsClient := newMonitorClient(metricMetadata) - metricRequest := azureExternalMetricRequest{ + metricRequest := azureExternalMetricRequest{ Timespan: timeSpan(), SubscriptionID: metricMetadata.subscriptionID, - } - - metricRequest.MetricName = metricMetadata.name - metricRequest.ResourceGroup = metricMetadata.resourceGroupName - metricRequest.ResourceURI = metricMetadata.resourceURI - metricRequest.Aggregation = metricMetadata.aggregationType - - filter := metricMetadata.filter - if filter != "" { - metricRequest.Filter = filter - } - - aggregationInterval := metricMetadata.aggregationInterval - if aggregationInterval != "" { - metricRequest.Timespan = aggregationInterval - } - - metricResponse, err := metricsClient.getAzureMetric(metricRequest) - if err != nil { - azureMonitorLog.Error(err, "error getting azure monitor metric") - return -1, fmt.Errorf("Error getting azure monitor metric %s: %s", metricRequest.MetricName, err.Error()) - } - - // casting drops everything after decimal, so round first - metricValue := int32(math.Round(metricResponse.Value)) - - return metricValue, nil + } + + metricRequest.MetricName = metricMetadata.name + metricRequest.ResourceGroup = metricMetadata.resourceGroupName + resourceInfo := strings.Split(metricMetadata.resourceURI, "/") + metricRequest.ResourceProviderNamespace = resourceInfo[0] + metricRequest.ResourceType = resourceInfo[1] + metricRequest.ResourceName = resourceInfo[2] + // do empty checking + + metricRequest.Aggregation = metricMetadata.aggregationType + + filter := metricMetadata.filter + if filter != "" { + metricRequest.Filter = filter + } + + aggregationInterval := metricMetadata.aggregationInterval + if aggregationInterval != "" { + metricRequest.Timespan = aggregationInterval + } + + metricResponse, err := metricsClient.getAzureMetric(metricRequest) + if err != nil { + azureMonitorLog.Error(err, "error getting azure monitor metric") + return -1, fmt.Errorf("MetricName %s: , ResourceGroup: %s, Namespace: %s, ResourceType: %s, ResourceName: %s, Aggregation: %s, Timespan: %s", metricRequest.MetricName, metricRequest.ResourceGroup, metricRequest.ResourceProviderNamespace, metricRequest.ResourceType, metricRequest.ResourceName, metricRequest.Aggregation, metricRequest.Timespan) + //return -1, fmt.Errorf("Error getting azure monitor metric %s: %s", metricRequest.MetricName, err.Error()) + } + + // casting drops everything after decimal, so round first + metricValue := int32(math.Round(metricResponse.Value)) + + return metricValue, nil } func newMonitorClient(metadata *azureMonitorMetadata) azureExternalMetricClient { - client := insights.NewMetricsClient(metadata.subscriptionID) - config := auth.NewClientCredentialsConfig(metadata.servicePrincipalID, metadata.servicePrincipalPass, metadata.tentantID) - + client := insights.NewMetricsClient(metadata.subscriptionID) + config := auth.NewClientCredentialsConfig(metadata.servicePrincipalID, metadata.servicePrincipalPass, metadata.tentantID) + authorizer, err := config.Authorizer() if err == nil { client.Authorizer = authorizer @@ -120,19 +127,19 @@ func extractValue(azMetricRequest azureExternalMetricRequest, metricResult insig metricVals := *metricResult.Value if len(metricVals) == 0 { - err := fmt.Errorf("Got an empty response for metric %s/%s and aggregate type %s", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + err := fmt.Errorf("Got an empty response for metric %s/%s and aggregate type %s", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) return 0, err } timeseries := *metricVals[0].Timeseries if timeseries == nil { - err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without timeseries", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without timeseries", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) return 0, err } data := *timeseries[0].Data if data == nil { - err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without any metric values", azMetricRequest.Namespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) + err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without any metric values", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) return 0, err } @@ -149,12 +156,12 @@ func extractValue(azMetricRequest azureExternalMetricRequest, metricResult insig fValue := float64(*data[len(data)-1].Count) valuePtr = &fValue } else { - err := fmt.Errorf("Unsupported aggregation type %s specified in metric %s/%s", insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation)), azMetricRequest.Namespace, azMetricRequest.MetricName) + err := fmt.Errorf("Unsupported aggregation type %s specified in metric %s/%s", insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation)), azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName) return 0, err } if valuePtr == nil { - err := fmt.Errorf("Unable to get value for metric %s/%s with aggregation %s. No value returned by the Azure Monitor", azMetricRequest.Namespace, azMetricRequest.MetricName, azMetricRequest.Aggregation) + err := fmt.Errorf("Unable to get value for metric %s/%s with aggregation %s. No value returned by the Azure Monitor", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, azMetricRequest.Aggregation) return 0, err } @@ -178,10 +185,12 @@ func (amr azureExternalMetricRequest) validate() error { } func (amr azureExternalMetricRequest) metricResourceURI() string { - return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s", + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s/%s", amr.SubscriptionID, amr.ResourceGroup, - amr.ResourceURI) + amr.ResourceProviderNamespace, + amr.ResourceType, + amr.ResourceName) } func timeSpan() string { @@ -190,4 +199,4 @@ func timeSpan() string { endtime := time.Now().UTC().Format(time.RFC3339) starttime := time.Now().Add(-(5 * time.Minute)).UTC().Format(time.RFC3339) return fmt.Sprintf("%s/%s", starttime, endtime) -} \ No newline at end of file +} diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index c33453655ab..717a3517b78 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -93,6 +93,12 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri return nil, fmt.Errorf("no metricName given") } + if val, ok := metadata["metricAggregationType"]; ok && val != "" { + meta.aggregationType = val + } else { + return nil, fmt.Errorf("no metricAggregationType given") + } + if val, ok := metadata["metricFilter"]; ok { if val != "" { meta.filter = val From 2e878bbff620407c6fe7969d4ae221eac8d7b28f Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Tue, 28 Jan 2020 16:24:59 -0500 Subject: [PATCH 08/25] Make sure that resourceURI includes resource namespace, resource type, and resource name --- pkg/scalers/azure_monitor.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index 8ca94beaeba..95183008309 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -55,10 +55,14 @@ func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetada metricRequest.MetricName = metricMetadata.name metricRequest.ResourceGroup = metricMetadata.resourceGroupName resourceInfo := strings.Split(metricMetadata.resourceURI, "/") + + if len(resourceInfo) != 3 { + return -1, fmt.Errorf("resourceURI is missing resource namespace, resource type, or resource name") + } + metricRequest.ResourceProviderNamespace = resourceInfo[0] metricRequest.ResourceType = resourceInfo[1] metricRequest.ResourceName = resourceInfo[2] - // do empty checking metricRequest.Aggregation = metricMetadata.aggregationType @@ -75,8 +79,8 @@ func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetada metricResponse, err := metricsClient.getAzureMetric(metricRequest) if err != nil { azureMonitorLog.Error(err, "error getting azure monitor metric") - return -1, fmt.Errorf("MetricName %s: , ResourceGroup: %s, Namespace: %s, ResourceType: %s, ResourceName: %s, Aggregation: %s, Timespan: %s", metricRequest.MetricName, metricRequest.ResourceGroup, metricRequest.ResourceProviderNamespace, metricRequest.ResourceType, metricRequest.ResourceName, metricRequest.Aggregation, metricRequest.Timespan) - //return -1, fmt.Errorf("Error getting azure monitor metric %s: %s", metricRequest.MetricName, err.Error()) + //return -1, fmt.Errorf("MetricName %s: , ResourceGroup: %s, Namespace: %s, ResourceType: %s, ResourceName: %s, Aggregation: %s, Timespan: %s", metricRequest.MetricName, metricRequest.ResourceGroup, metricRequest.ResourceProviderNamespace, metricRequest.ResourceType, metricRequest.ResourceName, metricRequest.Aggregation, metricRequest.Timespan) + return -1, fmt.Errorf("Error getting azure monitor metric %s: %s", metricRequest.MetricName, err.Error()) } // casting drops everything after decimal, so round first From 4ae421af1f2e46a3af1b8edf5eeaf0483080036a Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 29 Jan 2020 19:32:26 -0500 Subject: [PATCH 09/25] Check that aggregation interval is provided in the correct format --- pkg/scalers/azure_monitor_scaler.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 717a3517b78..f558ca02b16 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -3,7 +3,8 @@ package scalers import ( "context" "fmt" - "strconv" + "strconv" + "strings" v2beta1 "k8s.io/api/autoscaling/v2beta1" "k8s.io/apimachinery/pkg/api/resource" @@ -107,6 +108,11 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri if val, ok := metadata["metricAggregationInterval"]; ok { if val != "" { + aggregationInterval := strings.Split(val, ":") + if len(aggregationInterval) != 3 { + return nil, fmt.Errorf("metricAggregationInterval not in the correct format. Should be hh:mm:ss") + } + meta.aggregationInterval = val } } From a4dc5ce695f9be930ad0bbadec63caf8e7b97a45 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Thu, 30 Jan 2020 12:53:36 -0500 Subject: [PATCH 10/25] Change servicePrinciple to client and ad to activeDirectory. Make clientID required --- pkg/scalers/azure_monitor.go | 2 +- pkg/scalers/azure_monitor_scaler.go | 46 ++++++++++++++--------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index 95183008309..96760b2799c 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -91,7 +91,7 @@ func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetada func newMonitorClient(metadata *azureMonitorMetadata) azureExternalMetricClient { client := insights.NewMetricsClient(metadata.subscriptionID) - config := auth.NewClientCredentialsConfig(metadata.servicePrincipalID, metadata.servicePrincipalPass, metadata.tentantID) + config := auth.NewClientCredentialsConfig(metadata.clientID, metadata.clientPassword, metadata.tentantID) authorizer, err := config.Authorizer() if err == nil { diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index f558ca02b16..0627529a3a1 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -3,8 +3,8 @@ package scalers import ( "context" "fmt" - "strconv" - "strings" + "strconv" + "strings" v2beta1 "k8s.io/api/autoscaling/v2beta1" "k8s.io/apimachinery/pkg/api/resource" @@ -24,17 +24,17 @@ type azureMonitorScaler struct { } type azureMonitorMetadata struct { - resourceURI string - tentantID string - subscriptionID string - resourceGroupName string - name string - filter string - aggregationInterval string - aggregationType string - servicePrincipalID string - servicePrincipalPass string - targetValue int + resourceURI string + tentantID string + subscriptionID string + resourceGroupName string + name string + filter string + aggregationInterval string + aggregationType string + clientID string + clientPassword string + targetValue int } var azureMonitorLog = logf.Log.WithName("azure_monitor_scaler") @@ -108,25 +108,25 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri if val, ok := metadata["metricAggregationInterval"]; ok { if val != "" { - aggregationInterval := strings.Split(val, ":") - if len(aggregationInterval) != 3 { - return nil, fmt.Errorf("metricAggregationInterval not in the correct format. Should be hh:mm:ss") - } + aggregationInterval := strings.Split(val, ":") + if len(aggregationInterval) != 3 { + return nil, fmt.Errorf("metricAggregationInterval not in the correct format. Should be hh:mm:ss") + } meta.aggregationInterval = val } } - if val, ok := metadata["adServicePrincipleId"]; ok && val != "" { - meta.servicePrincipalID = val + if val, ok := metadata["activeDirectoryClientId"]; ok && val != "" { + meta.clientID = val } else { - return nil, fmt.Errorf("no adServicePrincipleId given") + return nil, fmt.Errorf("no activeDirectoryClientId given") } - if val, ok := metadata["adServicePrinciplePassword"]; ok { - meta.servicePrincipalPass = val + if val, ok := metadata["activeDirectoryClientPassword"]; ok && val != "" { + meta.clientPassword = val } else { - return nil, fmt.Errorf("no adServicePrinciplePassword given") + return nil, fmt.Errorf("no activeDirectoryClientPassword given") } return &meta, nil From 866c2e938c14fe4d39d303eb47cf9c72cd0248d6 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Thu, 30 Jan 2020 17:03:32 -0500 Subject: [PATCH 11/25] Add support for custom aggregation interval. Clean up code and remove unused parts from struct --- pkg/scalers/azure_monitor.go | 59 ++++++++++++++--------------- pkg/scalers/azure_monitor_scaler.go | 1 - 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index 96760b2799c..fbfd6e146df 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -3,8 +3,9 @@ package scalers import ( "context" "fmt" - "math" - "strings" + "math" + "strconv" + "strings" "time" "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" @@ -15,7 +16,6 @@ import ( type azureExternalMetricRequest struct { MetricName string SubscriptionID string - Type string ResourceName string ResourceProviderNamespace string ResourceType string @@ -23,8 +23,6 @@ type azureExternalMetricRequest struct { Timespan string Filter string ResourceGroup string - Topic string - Subscription string } type azureExternalMetricResponse struct { @@ -48,33 +46,25 @@ func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetada metricsClient := newMonitorClient(metricMetadata) metricRequest := azureExternalMetricRequest{ - Timespan: timeSpan(), + MetricName: metricMetadata.name, SubscriptionID: metricMetadata.subscriptionID, + Aggregation: metricMetadata.aggregationType, + Filter: metricMetadata.filter, + ResourceGroup: metricMetadata.resourceGroupName, } - metricRequest.MetricName = metricMetadata.name - metricRequest.ResourceGroup = metricMetadata.resourceGroupName resourceInfo := strings.Split(metricMetadata.resourceURI, "/") - - if len(resourceInfo) != 3 { - return -1, fmt.Errorf("resourceURI is missing resource namespace, resource type, or resource name") - } - metricRequest.ResourceProviderNamespace = resourceInfo[0] metricRequest.ResourceType = resourceInfo[1] metricRequest.ResourceName = resourceInfo[2] - metricRequest.Aggregation = metricMetadata.aggregationType + // if no timespan is provided, defaults to 5 minutes + timespan, err := formatTimeSpan(metricMetadata.aggregationInterval) + if err != nil { + return -1, err + } - filter := metricMetadata.filter - if filter != "" { - metricRequest.Filter = filter - } - - aggregationInterval := metricMetadata.aggregationInterval - if aggregationInterval != "" { - metricRequest.Timespan = aggregationInterval - } + metricRequest.Timespan = timespan metricResponse, err := metricsClient.getAzureMetric(metricRequest) if err != nil { @@ -93,10 +83,8 @@ func newMonitorClient(metadata *azureMonitorMetadata) azureExternalMetricClient client := insights.NewMetricsClient(metadata.subscriptionID) config := auth.NewClientCredentialsConfig(metadata.clientID, metadata.clientPassword, metadata.tentantID) - authorizer, err := config.Authorizer() - if err == nil { - client.Authorizer = authorizer - } + authorizer, _ := config.Authorizer() + client.Authorizer = authorizer return &monitorClient{ client: client, @@ -197,10 +185,21 @@ func (amr azureExternalMetricRequest) metricResourceURI() string { amr.ResourceName) } -func timeSpan() string { +func formatTimeSpan(timeSpan string) (string, error) { // defaults to last five minutes. - // TODO support configuration via config endtime := time.Now().UTC().Format(time.RFC3339) starttime := time.Now().Add(-(5 * time.Minute)).UTC().Format(time.RFC3339) - return fmt.Sprintf("%s/%s", starttime, endtime) + if timeSpan != "" { + aggregationInterval := strings.Split(timeSpan, ":") + hours, herr := strconv.Atoi(aggregationInterval[0]) + minutes, merr := strconv.Atoi(aggregationInterval[1]) + seconds, serr := strconv.Atoi(aggregationInterval[2]) + + if herr != nil || merr != nil || serr != nil { + return "", fmt.Errorf("Errors parsing metricAggregationInterval: %v, %v, %v", herr, merr, serr) + } + + starttime = time.Now().Add(-(time.Duration(hours)*time.Hour + time.Duration(minutes)*time.Minute + time.Duration(seconds)*time.Second)).UTC().Format(time.RFC3339) + } + return fmt.Sprintf("%s/%s", starttime, endtime), nil } diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 0627529a3a1..0d84c01f6e3 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -112,7 +112,6 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri if len(aggregationInterval) != 3 { return nil, fmt.Errorf("metricAggregationInterval not in the correct format. Should be hh:mm:ss") } - meta.aggregationInterval = val } } From 616744851ea96b4f0b72f3c49d002584ec577f98 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Thu, 30 Jan 2020 19:00:09 -0500 Subject: [PATCH 12/25] Add authentication using resolvedEnv or authParams --- pkg/scalers/azure_monitor_scaler.go | 102 +++++++++++++++++++--------- 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 0d84c01f6e3..8fe77de387d 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -15,8 +15,12 @@ import ( ) const ( - azureMonitorMetricName = "metricName" - targetValueName = "targetValue" + azureMonitorMetricName = "metricName" + targetValueName = "targetValue" + defaultSubscriptionIDSetting = "xxx" + defaultTenantIDSetting = "yyy" + defaultClientIDSetting = "zzz" + defaultClientPasswordSetting = "qqq" ) type azureMonitorScaler struct { @@ -60,7 +64,6 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri azureMonitorLog.Error(err, "Error parsing azure monitor metadata", "targetValue", targetValueName) return nil, fmt.Errorf("Error parsing azure monitor metadata %s: %s", targetValueName, err.Error()) } - meta.targetValue = targetValue } @@ -70,18 +73,6 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri return nil, fmt.Errorf("no resourceURI given") } - if val, ok := metadata["tenantId"]; ok && val != "" { - meta.tentantID = val - } else { - return nil, fmt.Errorf("no tenantId given") - } - - if val, ok := metadata["subscriptionId"]; ok && val != "" { - meta.subscriptionID = val - } else { - return nil, fmt.Errorf("no subscriptionId given") - } - if val, ok := metadata["resourceGroupName"]; ok && val != "" { meta.resourceGroupName = val } else { @@ -100,32 +91,81 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri return nil, fmt.Errorf("no metricAggregationType given") } - if val, ok := metadata["metricFilter"]; ok { - if val != "" { - meta.filter = val + if val, ok := metadata["metricFilter"]; ok && val != "" { + meta.filter = val + } + + if val, ok := metadata["metricAggregationInterval"]; ok && val != "" { + aggregationInterval := strings.Split(val, ":") + if len(aggregationInterval) != 3 { + return nil, fmt.Errorf("metricAggregationInterval not in the correct format. Should be hh:mm:ss") } } - if val, ok := metadata["metricAggregationInterval"]; ok { - if val != "" { - aggregationInterval := strings.Split(val, ":") - if len(aggregationInterval) != 3 { - return nil, fmt.Errorf("metricAggregationInterval not in the correct format. Should be hh:mm:ss") - } - meta.aggregationInterval = val + // Required authentication parameters below + + subscriptionID := authParams["subscriptionId"] + if subscriptionID != "" { + meta.subscriptionID = subscriptionID + } else { + subscriptionIDSetting := defaultSubscriptionIDSetting + if val, ok := metadata["subscriptionId"]; ok && val != "" { + subscriptionIDSetting = val + } + + if val, ok := resolvedEnv[subscriptionIDSetting]; ok { + meta.subscriptionID = val + } else { + return nil, fmt.Errorf("no subscriptionId given") } } - if val, ok := metadata["activeDirectoryClientId"]; ok && val != "" { - meta.clientID = val + tentantID := authParams["tenantId"] + if tentantID != "" { + meta.tentantID = tentantID } else { - return nil, fmt.Errorf("no activeDirectoryClientId given") + tenantIDSetting := defaultTenantIDSetting + if val, ok := metadata["tenantId"]; ok && val != "" { + tenantIDSetting = val + } + + if val, ok := resolvedEnv[tenantIDSetting]; ok { + meta.tentantID = val + } else { + return nil, fmt.Errorf("no tenantId given") + } + } + + clientID := authParams["activeDirectoryClientId"] + if clientID != "" { + meta.clientID = clientID + } else { + clientIDSetting := defaultClientIDSetting + if val, ok := metadata["activeDirectoryClientId"]; ok && val != "" { + clientIDSetting = val + } + + if val, ok := resolvedEnv[clientIDSetting]; ok { + meta.clientID = val + } else { + return nil, fmt.Errorf("no activeDirectoryClientId given") + } } - if val, ok := metadata["activeDirectoryClientPassword"]; ok && val != "" { - meta.clientPassword = val + clientPassword := authParams["activeDirectoryClientPassword"] + if clientPassword != "" { + meta.clientPassword = clientPassword } else { - return nil, fmt.Errorf("no activeDirectoryClientPassword given") + clientPasswordSetting := defaultClientPasswordSetting + if val, ok := metadata["activeDirectoryClientPassword"]; ok && val != "" { + clientPasswordSetting = val + } + + if val, ok := resolvedEnv[clientPasswordSetting]; ok { + meta.clientPassword = val + } else { + return nil, fmt.Errorf("no activeDirectoryClientPassword given") + } } return &meta, nil From 561f060979062a81bbdaf2fbaf2f6fd8a37a1f37 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Thu, 30 Jan 2020 20:36:47 -0500 Subject: [PATCH 13/25] Move aggregation type validation to separate function. Return -1 on error. Delete debugging comment --- pkg/scalers/azure_monitor.go | 49 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index fbfd6e146df..ea7f6361965 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -120,41 +120,24 @@ func extractValue(azMetricRequest azureExternalMetricRequest, metricResult insig if len(metricVals) == 0 { err := fmt.Errorf("Got an empty response for metric %s/%s and aggregate type %s", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) - return 0, err + return -1, err } timeseries := *metricVals[0].Timeseries if timeseries == nil { err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without timeseries", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) - return 0, err + return -1, err } data := *timeseries[0].Data if data == nil { err := fmt.Errorf("Got metric result for %s/%s and aggregate type %s without any metric values", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation))) - return 0, err - } - - var valuePtr *float64 - if strings.EqualFold(string(insights.Average), azMetricRequest.Aggregation) && data[len(data)-1].Average != nil { - valuePtr = data[len(data)-1].Average - } else if strings.EqualFold(string(insights.Total), azMetricRequest.Aggregation) && data[len(data)-1].Total != nil { - valuePtr = data[len(data)-1].Total - } else if strings.EqualFold(string(insights.Maximum), azMetricRequest.Aggregation) && data[len(data)-1].Maximum != nil { - valuePtr = data[len(data)-1].Maximum - } else if strings.EqualFold(string(insights.Minimum), azMetricRequest.Aggregation) && data[len(data)-1].Minimum != nil { - valuePtr = data[len(data)-1].Minimum - } else if strings.EqualFold(string(insights.Count), azMetricRequest.Aggregation) && data[len(data)-1].Count != nil { - fValue := float64(*data[len(data)-1].Count) - valuePtr = &fValue - } else { - err := fmt.Errorf("Unsupported aggregation type %s specified in metric %s/%s", insights.AggregationType(strings.ToTitle(azMetricRequest.Aggregation)), azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName) - return 0, err + return -1, err } - if valuePtr == nil { - err := fmt.Errorf("Unable to get value for metric %s/%s with aggregation %s. No value returned by the Azure Monitor", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, azMetricRequest.Aggregation) - return 0, err + valuePtr, err := verifyAggregationTypeIsSupported(azMetricRequest.Aggregation, data) + if err != nil { + return -1, fmt.Errorf("Unable to get value for metric %s/%s with aggregation %s. No value returned by Azure Monitor", azMetricRequest.ResourceProviderNamespace, azMetricRequest.MetricName, azMetricRequest.Aggregation) } klog.V(2).Infof("metric type: %s %f", azMetricRequest.Aggregation, *valuePtr) @@ -203,3 +186,23 @@ func formatTimeSpan(timeSpan string) (string, error) { } return fmt.Sprintf("%s/%s", starttime, endtime), nil } + +func verifyAggregationTypeIsSupported(aggregationType string, data []insights.MetricValue) (*float64, error) { + var valuePtr *float64 + if strings.EqualFold(string(insights.Average), aggregationType) && data[len(data)-1].Average != nil { + valuePtr = data[len(data)-1].Average + } else if strings.EqualFold(string(insights.Total), aggregationType) && data[len(data)-1].Total != nil { + valuePtr = data[len(data)-1].Total + } else if strings.EqualFold(string(insights.Maximum), aggregationType) && data[len(data)-1].Maximum != nil { + valuePtr = data[len(data)-1].Maximum + } else if strings.EqualFold(string(insights.Minimum), aggregationType) && data[len(data)-1].Minimum != nil { + valuePtr = data[len(data)-1].Minimum + } else if strings.EqualFold(string(insights.Count), aggregationType) && data[len(data)-1].Count != nil { + fValue := float64(*data[len(data)-1].Count) + valuePtr = &fValue + } else { + err := fmt.Errorf("Unsupported aggregation type %s", insights.AggregationType(strings.ToTitle(aggregationType))) + return nil, err + } + return valuePtr, nil +} From daf76e709d1141d279131532235a0667546516fa Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 5 Feb 2020 14:08:42 -0500 Subject: [PATCH 14/25] Replace spaces with tabs --- pkg/scalers/azure_monitor.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index ea7f6361965..99123bec793 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -3,9 +3,9 @@ package scalers import ( "context" "fmt" - "math" - "strconv" - "strings" + "math" + "strconv" + "strings" "time" "github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights" @@ -62,7 +62,7 @@ func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetada timespan, err := formatTimeSpan(metricMetadata.aggregationInterval) if err != nil { return -1, err - } + } metricRequest.Timespan = timespan From c420b74bd34f776ebf6dc2957db48d61495701f2 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 5 Feb 2020 15:00:11 -0500 Subject: [PATCH 15/25] Remove unnecessary types and and interfaces. Refactor code to split getAzureMetricValue into three smaller functions --- pkg/scalers/azure_monitor.go | 82 ++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index 99123bec793..e05fb50f268 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -25,94 +25,86 @@ type azureExternalMetricRequest struct { ResourceGroup string } -type azureExternalMetricResponse struct { - Value float64 -} +// GetAzureMetricValue is a func +func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetadata) (int32, error) { + client := createMetricsClient(metricMetadata) -type azureExternalMetricClient interface { - getAzureMetric(azMetricRequest azureExternalMetricRequest) (azureExternalMetricResponse, error) -} + requestPtr, err := createMetricsRequest(metricMetadata) + if err != nil { + return -1, err + } -type insightsmonitorClient interface { - List(ctx context.Context, resourceURI string, timespan string, interval *string, metricnames string, aggregation string, top *int32, orderby string, filter string, resultType insights.ResultType, metricnamespace string) (result insights.Response, err error) + return executeRequest(client, requestPtr) } -type monitorClient struct { - client insightsmonitorClient -} +func createMetricsClient(metadata *azureMonitorMetadata) insights.MetricsClient { + client := insights.NewMetricsClient(metadata.subscriptionID) + config := auth.NewClientCredentialsConfig(metadata.clientID, metadata.clientPassword, metadata.tentantID) -// GetAzureMetricValue is a func -func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetadata) (int32, error) { - metricsClient := newMonitorClient(metricMetadata) + authorizer, _ := config.Authorizer() + client.Authorizer = authorizer + + return client +} +func createMetricsRequest(metadata *azureMonitorMetadata) (*azureExternalMetricRequest, error) { metricRequest := azureExternalMetricRequest{ - MetricName: metricMetadata.name, - SubscriptionID: metricMetadata.subscriptionID, - Aggregation: metricMetadata.aggregationType, - Filter: metricMetadata.filter, - ResourceGroup: metricMetadata.resourceGroupName, + MetricName: metadata.name, + SubscriptionID: metadata.subscriptionID, + Aggregation: metadata.aggregationType, + Filter: metadata.filter, + ResourceGroup: metadata.resourceGroupName, } - resourceInfo := strings.Split(metricMetadata.resourceURI, "/") + resourceInfo := strings.Split(metadata.resourceURI, "/") metricRequest.ResourceProviderNamespace = resourceInfo[0] metricRequest.ResourceType = resourceInfo[1] metricRequest.ResourceName = resourceInfo[2] // if no timespan is provided, defaults to 5 minutes - timespan, err := formatTimeSpan(metricMetadata.aggregationInterval) + timespan, err := formatTimeSpan(metadata.aggregationInterval) if err != nil { - return -1, err + return nil, err } metricRequest.Timespan = timespan - metricResponse, err := metricsClient.getAzureMetric(metricRequest) + return &metricRequest, nil +} + +func executeRequest(client insights.MetricsClient, request *azureExternalMetricRequest) (int32, error) { + metricResponse, err := getAzureMetric(client, *request) if err != nil { azureMonitorLog.Error(err, "error getting azure monitor metric") - //return -1, fmt.Errorf("MetricName %s: , ResourceGroup: %s, Namespace: %s, ResourceType: %s, ResourceName: %s, Aggregation: %s, Timespan: %s", metricRequest.MetricName, metricRequest.ResourceGroup, metricRequest.ResourceProviderNamespace, metricRequest.ResourceType, metricRequest.ResourceName, metricRequest.Aggregation, metricRequest.Timespan) - return -1, fmt.Errorf("Error getting azure monitor metric %s: %s", metricRequest.MetricName, err.Error()) + return -1, fmt.Errorf("Error getting azure monitor metric %s: %s", request.MetricName, err.Error()) } // casting drops everything after decimal, so round first - metricValue := int32(math.Round(metricResponse.Value)) + metricValue := int32(math.Round(metricResponse)) return metricValue, nil } -func newMonitorClient(metadata *azureMonitorMetadata) azureExternalMetricClient { - client := insights.NewMetricsClient(metadata.subscriptionID) - config := auth.NewClientCredentialsConfig(metadata.clientID, metadata.clientPassword, metadata.tentantID) - - authorizer, _ := config.Authorizer() - client.Authorizer = authorizer - - return &monitorClient{ - client: client, - } -} - -func (c *monitorClient) getAzureMetric(azMetricRequest azureExternalMetricRequest) (azureExternalMetricResponse, error) { +func getAzureMetric(client insights.MetricsClient, azMetricRequest azureExternalMetricRequest) (float64, error) { err := azMetricRequest.validate() if err != nil { - return azureExternalMetricResponse{}, err + return -1, err } metricResourceURI := azMetricRequest.metricResourceURI() klog.V(2).Infof("resource uri: %s", metricResourceURI) - metricResult, err := c.client.List(context.Background(), metricResourceURI, + metricResult, err := client.List(context.Background(), metricResourceURI, azMetricRequest.Timespan, nil, azMetricRequest.MetricName, azMetricRequest.Aggregation, nil, "", azMetricRequest.Filter, "", "") if err != nil { - return azureExternalMetricResponse{}, err + return -1, err } value, err := extractValue(azMetricRequest, metricResult) - return azureExternalMetricResponse{ - Value: value, - }, err + return value, err } func extractValue(azMetricRequest azureExternalMetricRequest, metricResult insights.Response) (float64, error) { From c6fc2deab7e930a93a449b76d6be83a44339bd6a Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 5 Feb 2020 18:17:51 -0500 Subject: [PATCH 16/25] Fix typo: tentant -> tenant --- pkg/scalers/azure_monitor.go | 2 +- pkg/scalers/azure_monitor_scaler.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index e05fb50f268..0e5efaddf92 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -39,7 +39,7 @@ func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetada func createMetricsClient(metadata *azureMonitorMetadata) insights.MetricsClient { client := insights.NewMetricsClient(metadata.subscriptionID) - config := auth.NewClientCredentialsConfig(metadata.clientID, metadata.clientPassword, metadata.tentantID) + config := auth.NewClientCredentialsConfig(metadata.clientID, metadata.clientPassword, metadata.tenantID) authorizer, _ := config.Authorizer() client.Authorizer = authorizer diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 8fe77de387d..1d615081770 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -29,7 +29,7 @@ type azureMonitorScaler struct { type azureMonitorMetadata struct { resourceURI string - tentantID string + tenantID string subscriptionID string resourceGroupName string name string @@ -120,9 +120,9 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri } } - tentantID := authParams["tenantId"] - if tentantID != "" { - meta.tentantID = tentantID + tenantID := authParams["tenantId"] + if tenantID != "" { + meta.tenantID = tenantID } else { tenantIDSetting := defaultTenantIDSetting if val, ok := metadata["tenantId"]; ok && val != "" { @@ -130,7 +130,7 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri } if val, ok := resolvedEnv[tenantIDSetting]; ok { - meta.tentantID = val + meta.tenantID = val } else { return nil, fmt.Errorf("no tenantId given") } From 71bf0f842d8f4f61514401a0a4ee16c843dc029a Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Mon, 10 Feb 2020 19:33:06 -0500 Subject: [PATCH 17/25] Get rid of resolvedEnv usage since was not using it correctly --- pkg/scalers/azure_monitor_scaler.go | 48 ++++++----------------------- 1 file changed, 10 insertions(+), 38 deletions(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 1d615081770..9b3ff27ad3e 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -15,12 +15,8 @@ import ( ) const ( - azureMonitorMetricName = "metricName" - targetValueName = "targetValue" - defaultSubscriptionIDSetting = "xxx" - defaultTenantIDSetting = "yyy" - defaultClientIDSetting = "zzz" - defaultClientPasswordSetting = "qqq" + azureMonitorMetricName = "metricName" + targetValueName = "targetValue" ) type azureMonitorScaler struct { @@ -104,64 +100,40 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri // Required authentication parameters below - subscriptionID := authParams["subscriptionId"] - if subscriptionID != "" { - meta.subscriptionID = subscriptionID + if val, ok := authParams["subscriptionId"]; ok && val != "" { + meta.subscriptionID = val } else { - subscriptionIDSetting := defaultSubscriptionIDSetting if val, ok := metadata["subscriptionId"]; ok && val != "" { - subscriptionIDSetting = val - } - - if val, ok := resolvedEnv[subscriptionIDSetting]; ok { meta.subscriptionID = val } else { return nil, fmt.Errorf("no subscriptionId given") } } - tenantID := authParams["tenantId"] - if tenantID != "" { - meta.tenantID = tenantID + if val, ok := authParams["tenantId"]; ok && val != "" { + meta.tenantID = val } else { - tenantIDSetting := defaultTenantIDSetting if val, ok := metadata["tenantId"]; ok && val != "" { - tenantIDSetting = val - } - - if val, ok := resolvedEnv[tenantIDSetting]; ok { meta.tenantID = val } else { return nil, fmt.Errorf("no tenantId given") } } - clientID := authParams["activeDirectoryClientId"] - if clientID != "" { - meta.clientID = clientID + if val, ok := authParams["activeDirectoryClientId"]; ok && val != "" { + meta.clientID = val } else { - clientIDSetting := defaultClientIDSetting if val, ok := metadata["activeDirectoryClientId"]; ok && val != "" { - clientIDSetting = val - } - - if val, ok := resolvedEnv[clientIDSetting]; ok { meta.clientID = val } else { return nil, fmt.Errorf("no activeDirectoryClientId given") } } - clientPassword := authParams["activeDirectoryClientPassword"] - if clientPassword != "" { - meta.clientPassword = clientPassword + if val, ok := authParams["activeDirectoryClientPassword"]; ok && val != "" { + meta.clientPassword = val } else { - clientPasswordSetting := defaultClientPasswordSetting if val, ok := metadata["activeDirectoryClientPassword"]; ok && val != "" { - clientPasswordSetting = val - } - - if val, ok := resolvedEnv[clientPasswordSetting]; ok { meta.clientPassword = val } else { return nil, fmt.Errorf("no activeDirectoryClientPassword given") From fb97e5f225faaa9149394245ca21cb03b5964388 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Mon, 10 Feb 2020 19:49:04 -0500 Subject: [PATCH 18/25] Add test for parsing metadata --- pkg/scalers/azure_monitor_test.go | 55 +++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 pkg/scalers/azure_monitor_test.go diff --git a/pkg/scalers/azure_monitor_test.go b/pkg/scalers/azure_monitor_test.go new file mode 100644 index 00000000000..eaefb4eee3c --- /dev/null +++ b/pkg/scalers/azure_monitor_test.go @@ -0,0 +1,55 @@ +package scalers + +import ( + "testing" +) + +type parseAzMonitorMetadataTestData struct { + metadata map[string]string + isError bool + resolvedEnv map[string]string + authParams map[string]string +} + +var testParseAzMonitorMetadata = []parseAzMonitorMetadataTestData{ + // nothing passed + {map[string]string{}, true, map[string]string{}, map[string]string{}}, + // properly formed + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, false, map[string]string{}, map[string]string{}}, + // no optional parameters + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, false, map[string]string{}, map[string]string{}}, + // incorrectly formatted resourceURI + {map[string]string{"resourceURI": "bad/format", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // improperly formatted aggregationInterval + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:1", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // missing resourceURI + {map[string]string{"tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // missing tenantId + {map[string]string{"resourceURI": "test/resource/uri", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // missing subscriptionId + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // missing resourceGroupName + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // missing metricName + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // filter included + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricFilter": "namespace eq 'default'", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, false, map[string]string{}, map[string]string{}}, + // missing activeDirectoryClientId + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // missing activeDirectoryClientPassword + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + // missing targetValue + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234"}, true, map[string]string{}, map[string]string{}}, +} + +func TestAzMonitorParseMetadata(t *testing.T) { + for _, testData := range testParseAzMonitorMetadata { + _, err := parseAzureMonitorMetadata(testData.metadata, testData.resolvedEnv, testData.authParams) + if err != nil && !testData.isError { + t.Error("Expected success but got error", err) + } + if testData.isError && err == nil { + t.Errorf("Expected error but got success. testData: %v", testData) + } + } +} From 75bbd7281142a49433513b7675a5ebe94e7e667b Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Mon, 10 Feb 2020 19:55:18 -0500 Subject: [PATCH 19/25] Check that resourceURI is the correct format --- pkg/scalers/azure_monitor_scaler.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 9b3ff27ad3e..957db5e320c 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -64,6 +64,10 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri } if val, ok := metadata["resourceURI"]; ok && val != "" { + resourceURI := strings.Split(val, "/") + if len(resourceURI) != 3 { + return nil, fmt.Errorf("resourceURI not in the correct format. Should be namespace/resource_type/resource_name") + } meta.resourceURI = val } else { return nil, fmt.Errorf("no resourceURI given") From c695e7ee89da9e7d8cc1d837b859ead75ad2dd1f Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Mon, 10 Feb 2020 19:48:13 -0500 Subject: [PATCH 20/25] Return error if targetValue is not provided --- pkg/scalers/azure_monitor_scaler.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 957db5e320c..1a339bf8656 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -61,6 +61,8 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri return nil, fmt.Errorf("Error parsing azure monitor metadata %s: %s", targetValueName, err.Error()) } meta.targetValue = targetValue + } else { + return nil, fmt.Errorf("no targetValue given") } if val, ok := metadata["resourceURI"]; ok && val != "" { From 06f293905357af680e8d25cb8ace379adf9ba3c7 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Tue, 11 Feb 2020 16:27:38 -0500 Subject: [PATCH 21/25] Set aggregationInterval if provided and in the proper format --- pkg/scalers/azure_monitor_scaler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 1a339bf8656..6410b4a066c 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -102,6 +102,7 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri if len(aggregationInterval) != 3 { return nil, fmt.Errorf("metricAggregationInterval not in the correct format. Should be hh:mm:ss") } + meta.aggregationInterval = val } // Required authentication parameters below From e754344d0df6e84aaa0835e2500b5732a0578a59 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 12 Feb 2020 17:39:45 -0500 Subject: [PATCH 22/25] Clean up function comments, add ref to Azure K8s metrics adapter --- pkg/scalers/azure_monitor.go | 8 +++++--- pkg/scalers/azure_monitor_scaler.go | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/scalers/azure_monitor.go b/pkg/scalers/azure_monitor.go index 0e5efaddf92..2753c1ead3d 100644 --- a/pkg/scalers/azure_monitor.go +++ b/pkg/scalers/azure_monitor.go @@ -13,6 +13,9 @@ import ( "k8s.io/klog" ) +// Much of the code in this file is taken from the Azure Kubernetes Metrics Adapter +// https://github.com/Azure/azure-k8s-metrics-adapter/tree/master/pkg/azure/externalmetrics + type azureExternalMetricRequest struct { MetricName string SubscriptionID string @@ -25,7 +28,7 @@ type azureExternalMetricRequest struct { ResourceGroup string } -// GetAzureMetricValue is a func +// GetAzureMetricValue returns the value of an Azure Monitor metric, rounded to the nearest int func GetAzureMetricValue(ctx context.Context, metricMetadata *azureMonitorMetadata) (int32, error) { client := createMetricsClient(metricMetadata) @@ -138,7 +141,6 @@ func extractValue(azMetricRequest azureExternalMetricRequest, metricResult insig } func (amr azureExternalMetricRequest) validate() error { - // Shared if amr.MetricName == "" { return fmt.Errorf("metricName is required") } @@ -160,8 +162,8 @@ func (amr azureExternalMetricRequest) metricResourceURI() string { amr.ResourceName) } +// formatTimeSpan defaults to a 5 minute timespan if the user does not provide one func formatTimeSpan(timeSpan string) (string, error) { - // defaults to last five minutes. endtime := time.Now().UTC().Format(time.RFC3339) starttime := time.Now().Add(-(5 * time.Minute)).UTC().Format(time.RFC3339) if timeSpan != "" { diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 6410b4a066c..5460095b008 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -39,7 +39,7 @@ type azureMonitorMetadata struct { var azureMonitorLog = logf.Log.WithName("azure_monitor_scaler") -// NewAzureMonitorScaler stuff +// NewAzureMonitorScaler creates a new AzureMonitorScaler func NewAzureMonitorScaler(resolvedEnv, metadata, authParams map[string]string) (Scaler, error) { meta, err := parseAzureMonitorMetadata(metadata, resolvedEnv, authParams) if err != nil { @@ -150,7 +150,7 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri return &meta, nil } -// needs to interact with azure monitor +// Returns true if the Azure Monitor metric value is greater than zero func (s *azureMonitorScaler) IsActive(ctx context.Context) (bool, error) { val, err := GetAzureMetricValue(ctx, s.metadata) if err != nil { From 1199841691cf0f2516c73adef0434444277d9a99 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 12 Feb 2020 19:13:35 -0500 Subject: [PATCH 23/25] Remove subscriptionId and tenantId from authParams --- pkg/scalers/azure_monitor_scaler.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 5460095b008..74399b34ab8 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -107,24 +107,16 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri // Required authentication parameters below - if val, ok := authParams["subscriptionId"]; ok && val != "" { + if val, ok := metadata["subscriptionId"]; ok && val != "" { meta.subscriptionID = val } else { - if val, ok := metadata["subscriptionId"]; ok && val != "" { - meta.subscriptionID = val - } else { - return nil, fmt.Errorf("no subscriptionId given") - } + return nil, fmt.Errorf("no subscriptionId given") } - if val, ok := authParams["tenantId"]; ok && val != "" { + if val, ok := metadata["tenantId"]; ok && val != "" { meta.tenantID = val } else { - if val, ok := metadata["tenantId"]; ok && val != "" { - meta.tenantID = val - } else { - return nil, fmt.Errorf("no tenantId given") - } + return nil, fmt.Errorf("no tenantId given") } if val, ok := authParams["activeDirectoryClientId"]; ok && val != "" { From 71ad924d9144a4e1e15905bcda948cbe1bb6fbb7 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 12 Feb 2020 19:28:55 -0500 Subject: [PATCH 24/25] Support resolvedEnv for aadClientID and aadClientPassword --- pkg/scalers/azure_monitor_scaler.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/scalers/azure_monitor_scaler.go b/pkg/scalers/azure_monitor_scaler.go index 74399b34ab8..a27d83a34a7 100644 --- a/pkg/scalers/azure_monitor_scaler.go +++ b/pkg/scalers/azure_monitor_scaler.go @@ -15,8 +15,10 @@ import ( ) const ( - azureMonitorMetricName = "metricName" - targetValueName = "targetValue" + azureMonitorMetricName = "metricName" + targetValueName = "targetValue" + defaultClientIDSetting = "" + defaultClientPasswordSetting = "" ) type azureMonitorScaler struct { @@ -122,7 +124,12 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri if val, ok := authParams["activeDirectoryClientId"]; ok && val != "" { meta.clientID = val } else { + clientIDSetting := defaultClientIDSetting if val, ok := metadata["activeDirectoryClientId"]; ok && val != "" { + clientIDSetting = val + } + + if val, ok := resolvedEnv[clientIDSetting]; ok { meta.clientID = val } else { return nil, fmt.Errorf("no activeDirectoryClientId given") @@ -132,7 +139,12 @@ func parseAzureMonitorMetadata(metadata, resolvedEnv, authParams map[string]stri if val, ok := authParams["activeDirectoryClientPassword"]; ok && val != "" { meta.clientPassword = val } else { + clientPasswordSetting := defaultClientPasswordSetting if val, ok := metadata["activeDirectoryClientPassword"]; ok && val != "" { + clientPasswordSetting = val + } + + if val, ok := resolvedEnv[clientPasswordSetting]; ok { meta.clientPassword = val } else { return nil, fmt.Errorf("no activeDirectoryClientPassword given") From 8d945b77d68a1c75879535f43ed0518972308711 Mon Sep 17 00:00:00 2001 From: Mel Cone Date: Wed, 12 Feb 2020 20:51:24 -0500 Subject: [PATCH 25/25] Add resolvedEnv to test and add test for authParams --- pkg/scalers/azure_monitor_test.go | 35 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/pkg/scalers/azure_monitor_test.go b/pkg/scalers/azure_monitor_test.go index eaefb4eee3c..883be47df33 100644 --- a/pkg/scalers/azure_monitor_test.go +++ b/pkg/scalers/azure_monitor_test.go @@ -11,35 +11,44 @@ type parseAzMonitorMetadataTestData struct { authParams map[string]string } +var testAzMonitorResolvedEnv = map[string]string{ + "CLIENT_ID": "xxx", + "CLIENT_PASSWORD": "yyy", +} + var testParseAzMonitorMetadata = []parseAzMonitorMetadataTestData{ // nothing passed {map[string]string{}, true, map[string]string{}, map[string]string{}}, // properly formed - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, false, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, false, testAzMonitorResolvedEnv, map[string]string{}}, // no optional parameters - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, false, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, false, testAzMonitorResolvedEnv, map[string]string{}}, // incorrectly formatted resourceURI - {map[string]string{"resourceURI": "bad/format", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "bad/format", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // improperly formatted aggregationInterval - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:1", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:1", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // missing resourceURI - {map[string]string{"tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // missing tenantId - {map[string]string{"resourceURI": "test/resource/uri", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // missing subscriptionId - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // missing resourceGroupName - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // missing metricName - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, + // missing metricAggregationType + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // filter included - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricFilter": "namespace eq 'default'", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, false, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricFilter": "namespace eq 'default'", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, false, testAzMonitorResolvedEnv, map[string]string{}}, // missing activeDirectoryClientId - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientPassword": "1234", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientPassword": "CLIENT_PASSWORD", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // missing activeDirectoryClientPassword - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "targetValue": "5"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "targetValue": "5"}, true, testAzMonitorResolvedEnv, map[string]string{}}, // missing targetValue - {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "789", "activeDirectoryClientPassword": "1234"}, true, map[string]string{}, map[string]string{}}, + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "activeDirectoryClientId": "CLIENT_ID", "activeDirectoryClientPassword": "CLIENT_PASSWORD"}, true, testAzMonitorResolvedEnv, map[string]string{}}, + // connection from authParams + {map[string]string{"resourceURI": "test/resource/uri", "tenantId": "123", "subscriptionId": "456", "resourceGroupName": "test", "metricName": "metric", "metricAggregationInterval": "0:15:0", "metricAggregationType": "Average", "targetValue": "5"}, false, map[string]string{}, map[string]string{"activeDirectoryClientId": "zzz", "activeDirectoryClientPassword": "password"}}, } func TestAzMonitorParseMetadata(t *testing.T) {