Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-public cloud support for Azure Log Analytics scaler. #2834

Merged
merged 2 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
- **General:** Synchronize HPA annotations from ScaledObject ([#2659](https://github.com/kedacore/keda/pull/2659))
- **General:** Updated HTTPClient to be proxy-aware, if available, from environment variables. ([#2577](https://github.com/kedacore/keda/issues/2577))
- **Azure Application Insights Scaler:** Provide support for non-public clouds ([#2735](https://github.com/kedacore/keda/issues/2735))
- **Azure Event Hub Scaler:** Improve logging when blob container not found ([#2363](https://github.com/kedacore/keda/issues/2363)
- **Azure Event Hub Scaler:** Improve logging when blob container not found ([#2363](https://github.com/kedacore/keda/issues/2363))
- **Azure Event Hub Scaler:** Provide support for non-public clouds ([#1915](https://github.com/kedacore/keda/issues/1915))
- **Azure Log Analytics Scaler:** Provide support for non-public clouds ([#1916](https://github.com/kedacore/keda/issues/1916))
- **Azure Monitor Scaler:** Provide support for non-public clouds ([#1917](https://github.com/kedacore/keda/issues/1917))
- **Azure Queue:** Don't call Azure queue GetProperties API unnecessarily ([#2613](https://github.com/kedacore/keda/pull/2613))
- **Datadog Scaler:** Validate query to contain `{` to prevent panic on invalid query ([#2625](https://github.com/kedacore/keda/issues/2625))
Expand Down
67 changes: 51 additions & 16 deletions pkg/scalers/azure_log_analytics_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"sync"
"time"

az "github.com/Azure/go-autorest/autorest/azure"
v2beta2 "k8s.io/api/autoscaling/v2beta2"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -39,13 +40,15 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"

kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
"github.com/kedacore/keda/v2/pkg/scalers/azure"
kedautil "github.com/kedacore/keda/v2/pkg/util"
)

const (
miEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fapi.loganalytics.io%2F"
aadTokenEndpoint = "https://login.microsoftonline.com/%s/oauth2/token"
laQueryEndpoint = "https://api.loganalytics.io/v1/workspaces/%s/query"
miEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=%s"
aadTokenEndpoint = "%s/%s/oauth2/token"
laQueryEndpoint = "%s/v1/workspaces/%s/query"
defaultLogAnalyticsResourceURL = "https://api.loganalytics.io"
)

type azureLogAnalyticsScaler struct {
Expand All @@ -57,15 +60,17 @@ type azureLogAnalyticsScaler struct {
}

type azureLogAnalyticsMetadata struct {
tenantID string
clientID string
clientSecret string
workspaceID string
podIdentity string
query string
threshold int64
metricName string // Custom metric name for trigger
scalerIndex int
tenantID string
clientID string
clientSecret string
workspaceID string
podIdentity string
query string
threshold int64
metricName string // Custom metric name for trigger
scalerIndex int
logAnalyticsResourceURL string
activeDirectoryEndpoint string
}

type sessionCache struct {
Expand Down Expand Up @@ -106,6 +111,12 @@ var tokenCache = struct {

var logAnalyticsLog = logf.Log.WithName("azure_log_analytics_scaler")

var logAnalyticsResourceURLInCloud = map[string]string{
"AZUREPUBLICCLOUD": "https://api.loganalytics.io",
"AZUREUSGOVERNMENTCLOUD": "https://api.loganalytics.us",
"AZURECHINACLOUD": "https://api.loganalytics.azure.cn",
}

// NewAzureLogAnalyticsScaler creates a new Azure Log Analytics Scaler
func NewAzureLogAnalyticsScaler(config *ScalerConfig) (Scaler, error) {
azureLogAnalyticsMetadata, err := parseAzureLogAnalyticsMetadata(config)
Expand Down Expand Up @@ -188,6 +199,30 @@ func parseAzureLogAnalyticsMetadata(config *ScalerConfig) (*azureLogAnalyticsMet

meta.scalerIndex = config.ScalerIndex

meta.logAnalyticsResourceURL = defaultLogAnalyticsResourceURL
if cloud, ok := config.TriggerMetadata["cloud"]; ok {
if strings.EqualFold(cloud, azure.PrivateCloud) {
if resource, ok := config.TriggerMetadata["logAnalyticsResourceURL"]; ok && resource != "" {
meta.logAnalyticsResourceURL = resource
} else {
return nil, fmt.Errorf("logAnalyticsResourceURL must be provided for %s cloud type", azure.PrivateCloud)
}
} else if resource, ok := logAnalyticsResourceURLInCloud[strings.ToUpper(cloud)]; ok {
meta.logAnalyticsResourceURL = resource
} else {
return nil, fmt.Errorf("there is no cloud environment matching the name %s", cloud)
}
}

activeDirectoryEndpointProvider := func(env az.Environment) (string, error) {
return env.ActiveDirectoryEndpoint, nil
}
activeDirectoryEndpoint, err := azure.ParseEnvironmentProperty(config.TriggerMetadata, "activeDirectoryEndpoint", activeDirectoryEndpointProvider)
if err != nil {
return nil, err
}
meta.activeDirectoryEndpoint = activeDirectoryEndpoint

return &meta, nil
}

Expand Down Expand Up @@ -491,7 +526,7 @@ func (s *azureLogAnalyticsScaler) executeLogAnalyticsREST(ctx context.Context, q
return nil, 0, fmt.Errorf("can't construct JSON for request to Log Analytics API. Inner Error: %v", err)
}

request, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf(laQueryEndpoint, s.metadata.workspaceID), bytes.NewBuffer(jsonBytes)) // URL-encoded payload
request, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf(laQueryEndpoint, s.metadata.logAnalyticsResourceURL, s.metadata.workspaceID), bytes.NewBuffer(jsonBytes)) // URL-encoded payload
if err != nil {
return nil, 0, fmt.Errorf("can't construct HTTP request to Log Analytics API. Inner Error: %v", err)
}
Expand All @@ -508,11 +543,11 @@ func (s *azureLogAnalyticsScaler) executeAADApicall(ctx context.Context) ([]byte
"grant_type": {"client_credentials"},
"client_id": {s.metadata.clientID},
"redirect_uri": {"http://"},
"resource": {"https://api.loganalytics.io/"},
"resource": {s.metadata.logAnalyticsResourceURL},
"client_secret": {s.metadata.clientSecret},
}

request, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf(aadTokenEndpoint, s.metadata.tenantID), strings.NewReader(data.Encode())) // URL-encoded payload
request, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf(aadTokenEndpoint, s.metadata.activeDirectoryEndpoint, s.metadata.tenantID), strings.NewReader(data.Encode())) // URL-encoded payload
if err != nil {
return nil, 0, fmt.Errorf("can't construct HTTP request to Azure Active Directory. Inner Error: %v", err)
}
Expand All @@ -524,7 +559,7 @@ func (s *azureLogAnalyticsScaler) executeAADApicall(ctx context.Context) ([]byte
}

func (s *azureLogAnalyticsScaler) executeIMDSApicall(ctx context.Context) ([]byte, int, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, miEndpoint, nil)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(miEndpoint, s.metadata.logAnalyticsResourceURL), nil)
if err != nil {
return nil, 0, fmt.Errorf("can't construct HTTP request to Azure Instance Metadata service. Inner Error: %v", err)
}
Expand Down
22 changes: 16 additions & 6 deletions pkg/scalers/azure_log_analytics_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ import (
)

const (
tenantID = "d248da64-0e1e-4f79-b8c6-72ab7aa055eb"
clientID = "41826dd4-9e0a-4357-a5bd-a88ad771ea7d"
clientSecret = "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs"
workspaceID = "074dd9f8-c368-4220-9400-acb6e80fc325"
tenantID = "d248da64-0e1e-4f79-b8c6-72ab7aa055eb"
clientID = "41826dd4-9e0a-4357-a5bd-a88ad771ea7d"
clientSecret = "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs"
workspaceID = "074dd9f8-c368-4220-9400-acb6e80fc325"
testLogAnalyticsResourceURL = "testLogAnalyticsResourceURL"
testActiveDirectoryEndpoint = "testActiveDirectoryEndpoint"
)

type parseLogAnalyticsMetadataTestData struct {
Expand Down Expand Up @@ -87,8 +89,16 @@ var testLogAnalyticsMetadata = []parseLogAnalyticsMetadataTestData{
{map[string]string{"tenantId": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientId": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecret": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceId": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": ""}, true},
// All parameters set, should succeed
{map[string]string{"tenantId": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientId": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecret": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceId": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": "1900000000"}, false},
// All parameters set, should succeed
{map[string]string{"tenantIdFromEnv": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientIdFromEnv": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecretFromEnv": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceIdFromEnv": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": "1900000000"}, false},
// Known Azure Cloud
{map[string]string{"tenantIdFromEnv": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientIdFromEnv": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecretFromEnv": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceIdFromEnv": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": "1900000000", "cloud": "azurePublicCloud"}, false},
// Private Cloud
{map[string]string{"tenantIdFromEnv": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientIdFromEnv": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecretFromEnv": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceIdFromEnv": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": "1900000000", "cloud": "private", "logAnalyticsResourceURL": testLogAnalyticsResourceURL, "activeDirectoryEndpoint": testActiveDirectoryEndpoint}, false},
// Private Cloud missing log analytics resource url
{map[string]string{"tenantIdFromEnv": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientIdFromEnv": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecretFromEnv": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceIdFromEnv": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": "1900000000", "cloud": "private", "activeDirectoryEndpoint": testActiveDirectoryEndpoint}, true},
// Private Cloud missing active directory endpoint
{map[string]string{"tenantIdFromEnv": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientIdFromEnv": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecretFromEnv": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceIdFromEnv": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": "1900000000", "cloud": "private", "logAnalyticsResourceURL": testLogAnalyticsResourceURL}, true},
// Unsupported cloud
{map[string]string{"tenantIdFromEnv": "d248da64-0e1e-4f79-b8c6-72ab7aa055eb", "clientIdFromEnv": "41826dd4-9e0a-4357-a5bd-a88ad771ea7d", "clientSecretFromEnv": "U6DtAX5r6RPZxd~l12Ri3X8J9urt5Q-xs", "workspaceIdFromEnv": "074dd9f8-c368-4220-9400-acb6e80fc325", "query": query, "threshold": "1900000000", "cloud": "azureGermanCloud"}, true},
}

var LogAnalyticsMetricIdentifiers = []LogAnalyticsMetricIdentifier{
Expand Down
1 change: 0 additions & 1 deletion pkg/scalers/azure_monitor_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (

const (
testAzureResourceManagerEndpoint = "testAzureResourceManagerEndpoint"
testActiveDirectoryEndpoint = "testActiveDirectoryEndpoint"
)

type parseAzMonitorMetadataTestData struct {
Expand Down