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

Refactor Azure Storage handling. #838

Merged
merged 1 commit into from
May 20, 2020
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,5 @@ config
.vscode

# GO Vendor
vendor
vendor
cover.out
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ all: test build
##################################################
.PHONY: test
test:
go test ./...
go test ./... -covermode=atomic -coverprofile cover.out

.PHONY: e2e-test
e2e-test:
Expand Down
2 changes: 1 addition & 1 deletion cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
"k8s.io/client-go/rest"

"github.com/kedacore/keda/pkg/apis"
"github.com/kedacore/keda/version"
"github.com/kedacore/keda/pkg/controller"
"github.com/kedacore/keda/version"

"github.com/operator-framework/operator-sdk/pkg/k8sutil"
kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics"
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/keda/v1alpha1/scaledobject_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type ScaleTriggers struct {
// +optional
type ScaledObjectStatus struct {
// +optional
LastActiveTime *metav1.Time `json:"lastActiveTime,omitempty"`
LastActiveTime *metav1.Time `json:"lastActiveTime,omitempty"`
// +optional
// +listType
ExternalMetricNames []string `json:"externalMetricNames,omitempty"`
Expand Down
8 changes: 4 additions & 4 deletions pkg/handler/scale_jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ func (h *ScaleHandler) createJobs(scaledObject *kedav1alpha1.ScaledObject, scale
GenerateName: scaledObject.GetName() + "-",
Namespace: scaledObject.GetNamespace(),
Labels: map[string]string{
"app.kubernetes.io/name": scaledObject.GetName(),
"app.kubernetes.io/version": version.Version,
"app.kubernetes.io/part-of": scaledObject.GetName(),
"app.kubernetes.io/name": scaledObject.GetName(),
"app.kubernetes.io/version": version.Version,
"app.kubernetes.io/part-of": scaledObject.GetName(),
"app.kubernetes.io/managed-by": "keda-operator",
"scaledobject": scaledObject.GetName(),
"scaledobject": scaledObject.GetName(),
},
},
Spec: *scaledObject.Spec.JobTargetRef.DeepCopy(),
Expand Down
44 changes: 0 additions & 44 deletions pkg/scalers/AzureStorage.go

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package scalers
package azure

import (
"encoding/json"
Expand All @@ -13,11 +13,11 @@ const (
msiURL = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=%s"
)

func getAzureADPodIdentityToken(uri string) (AADToken, error) {
func GetAzureADPodIdentityToken(audience string) (AADToken, error) {

var token AADToken

resp, err := http.Get(fmt.Sprintf(msiURL, url.QueryEscape(uri)))
resp, err := http.Get(fmt.Sprintf(msiURL, url.QueryEscape(audience)))
if err != nil {
return token, err
}
Expand Down
28 changes: 28 additions & 0 deletions pkg/scalers/azure/azure_blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package azure

import (
"context"
"github.com/Azure/azure-storage-blob-go/azblob"
)

// GetAzureBlobListLength returns the count of the blobs in blob container in int
func GetAzureBlobListLength(ctx context.Context, podIdentity string, connectionString, blobContainerName string, accountName string, blobDelimiter string, blobPrefix string) (int, error) {
credential, endpoint, err := ParseAzureStorageBlobConnection(podIdentity, connectionString, accountName)
if err != nil {
return -1, err
}

listBlobsSegmentOptions := azblob.ListBlobsSegmentOptions{
Prefix: blobPrefix,
}
p := azblob.NewPipeline(credential, azblob.PipelineOptions{})
serviceURL := azblob.NewServiceURL(*endpoint, p)
containerURL := serviceURL.NewContainerURL(blobContainerName)

props, err := containerURL.ListBlobsHierarchySegment(ctx, azblob.Marker{}, blobDelimiter, listBlobsSegmentOptions)
if err != nil {
return -1, err
}

return len(props.Segment.BlobItems), nil
}
36 changes: 36 additions & 0 deletions pkg/scalers/azure/azure_blob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package azure

import (
"context"
"strings"
"testing"
)

func TestGetBlobLength(t *testing.T) {
length, err := GetAzureBlobListLength(context.TODO(), "", "", "blobContainerName", "", "", "")
if length != -1 {
t.Error("Expected length to be -1, but got", length)
}

if err == nil {
t.Error("Expected error for empty connection string, but got nil")
}

if !strings.Contains(err.Error(), "parse storage connection string") {
t.Error("Expected error to contain parsing error message, but got", err.Error())
}

length, err = GetAzureBlobListLength(context.TODO(), "", "DefaultEndpointsProtocol=https;AccountName=name;AccountKey=key==;EndpointSuffix=core.windows.net", "blobContainerName", "", "", "")

if length != -1 {
t.Error("Expected length to be -1, but got", length)
}

if err == nil {
t.Error("Expected error for empty connection string, but got nil")
}

if !strings.Contains(err.Error(), "illegal base64") {
t.Error("Expected error to contain base64 error message, but got", err.Error())
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package scalers
package azure

import (
"bytes"
Expand All @@ -13,11 +13,6 @@ import (

eventhub "github.com/Azure/azure-event-hubs-go"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/Azure/go-autorest/autorest/azure"
)

const (
environmentName = "AzurePublicCloud"
)

type baseCheckpoint struct {
Expand All @@ -43,29 +38,16 @@ type pythonCheckpoint struct {
SequenceNumber int64 `json:"sequence_number"`
}

// GetStorageCredentials returns azure env and storage credentials
func GetStorageCredentials(storageConnection string) (azure.Environment, *azblob.SharedKeyCredential, error) {
_, storageAccountName, storageAccountKey, _, err := ParseAzureStorageConnectionString(storageConnection)
if err != nil {
return azure.Environment{}, &azblob.SharedKeyCredential{}, fmt.Errorf("unable to parse connection string: %s", storageConnection)
}

azureEnv, err := azure.EnvironmentFromName(environmentName)
if err != nil {
return azureEnv, nil, fmt.Errorf("could not get azure.Environment struct: %s", err)
}

cred, err := azblob.NewSharedKeyCredential(storageAccountName, storageAccountKey)
if err != nil {
return azureEnv, nil, fmt.Errorf("could not prepare a blob storage credential: %s", err)
}

return azureEnv, cred, nil
type EventHubInfo struct {
EventHubConnection string
EventHubConsumerGroup string
StorageConnection string
BlobContainer string
}

// GetEventHubClient returns eventhub client
func GetEventHubClient(connectionString string) (*eventhub.Hub, error) {
hub, err := eventhub.NewHubFromConnectionString(connectionString)
func GetEventHubClient(info EventHubInfo) (*eventhub.Hub, error) {
hub, err := eventhub.NewHubFromConnectionString(info.EventHubConnection)
if err != nil {
return nil, fmt.Errorf("failed to create hub client: %s", err)
}
Expand All @@ -74,39 +56,34 @@ func GetEventHubClient(connectionString string) (*eventhub.Hub, error) {
}

// GetCheckpointFromBlobStorage accesses Blob storage and gets checkpoint information of a partition
func GetCheckpointFromBlobStorage(ctx context.Context, partitionID string, eventHubMetadata EventHubMetadata) (Checkpoint, error) {
endpointProtocol, storageAccountName, _, endpointSuffix, err := ParseAzureStorageConnectionString(eventHubMetadata.storageConnection)
func GetCheckpointFromBlobStorage(ctx context.Context, info EventHubInfo, partitionID string) (Checkpoint, error) {

blobCreds, storageEndpoint, err := ParseAzureStorageBlobConnection("none", info.StorageConnection, "")
if err != nil {
return Checkpoint{}, fmt.Errorf("unable to parse storage connection string: %s", err)
return Checkpoint{}, err
}

// Remove trailing spaces from endpointSuffix
endpointSuffix = strings.TrimSpace(endpointSuffix)

eventHubNamespace, eventHubName, err := ParseAzureEventHubConnectionString(eventHubMetadata.eventHubConnection)
eventHubNamespace, eventHubName, err := ParseAzureEventHubConnectionString(info.EventHubConnection)
if err != nil {
return Checkpoint{}, fmt.Errorf("unable to parse event hub connection string: %s", err)
return Checkpoint{}, err
}

// TODO: add more ways to read from different types of storage and read checkpoints/leases written in different JSON formats
var u *url.URL
var baseURL *url.URL
// Checking blob store for C# and Java applications
if eventHubMetadata.blobContainer != "" {
// URL format - <endpointProtocol>://<storageAccountName>.blob.<endpointSuffix>/<blobContainer>/<eventHubConsumerGroup>/<partitionID>
u, _ = url.Parse(fmt.Sprintf("%s://%s.blob.%s/%s/%s/%s", endpointProtocol, storageAccountName, endpointSuffix, eventHubMetadata.blobContainer, eventHubMetadata.eventHubConsumerGroup, partitionID))
if info.BlobContainer != "" {
// URL format - <storageEndpoint>/<blobContainer>/<eventHubConsumerGroup>/<partitionID>
path, _ := url.Parse(fmt.Sprintf("/%s/%s/%s", info.BlobContainer, info.EventHubConsumerGroup, partitionID))
baseURL = storageEndpoint.ResolveReference(path)
} else {
// Checking blob store for Azure functions
// URL format - <endpointProtocol>://<storageAccountName>.blob.<endpointSuffix>/azure-webjobs-eventhub/<eventHubNamespace>/<eventHubName>/<eventHubConsumerGroup>/<partitionID>
u, _ = url.Parse(fmt.Sprintf("%s://%s.blob.%s/azure-webjobs-eventhub/%s/%s/%s/%s", endpointProtocol, storageAccountName, endpointSuffix, eventHubNamespace, eventHubName, eventHubMetadata.eventHubConsumerGroup, partitionID))
}

_, cred, err := GetStorageCredentials(eventHubMetadata.storageConnection)
if err != nil {
return Checkpoint{}, fmt.Errorf("unable to get storage credentials: %s", err)
// URL format - <storageEndpoint>/azure-webjobs-eventhub/<eventHubNamespace>/<eventHubName>/<eventHubConsumerGroup>/<partitionID>
path, _ := url.Parse(fmt.Sprintf("/azure-webjobs-eventhub/%s/%s/%s/%s", eventHubNamespace, eventHubName, info.EventHubConsumerGroup, partitionID))
baseURL = storageEndpoint.ResolveReference(path)
}

// Create a BlockBlobURL object to a blob in the container.
blobURL := azblob.NewBlockBlobURL(*u, azblob.NewPipeline(cred, azblob.PipelineOptions{}))
blobURL := azblob.NewBlockBlobURL(*baseURL, azblob.NewPipeline(blobCreds, azblob.PipelineOptions{}))

get, err := blobURL.Download(ctx, 0, 0, azblob.BlobAccessConditions{}, false)
if err != nil {
Expand Down Expand Up @@ -164,7 +141,7 @@ func ParseAzureEventHubConnectionString(connectionString string) (string, string
}

if eventHubNamespace == "" || eventHubName == "" {
return "", "", errors.New("Can't parse event hub connection string")
return "", "", errors.New("can't parse event hub connection string. Missing eventHubNamespace or eventHubName")
}

return eventHubNamespace, eventHubName, nil
Expand Down
37 changes: 37 additions & 0 deletions pkg/scalers/azure/azure_eventhub_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package azure

import (
"testing"

"github.com/stretchr/testify/assert"
)

const csharpSdkCheckpoint = `{
"Epoch": 123456,
"Offset": "test offset",
"Owner": "test owner",
"PartitionId": "test partitionId",
"SequenceNumber": 12345
}`

const pythonSdkCheckpoint = `{
"epoch": 123456,
"offset": "test offset",
"owner": "test owner",
"partition_id": "test partitionId",
"sequence_number": 12345
}`

func TestGetCheckpoint(t *testing.T) {
cckp, err := getCheckpoint([]byte(csharpSdkCheckpoint))
if err != nil {
t.Error(err)
}

pckp, err := getCheckpoint([]byte(pythonSdkCheckpoint))
if err != nil {
t.Error(err)
}

assert.Equal(t, cckp, pckp)
}
Loading