Skip to content

Commit

Permalink
Add operator mode configuration (#1658)
Browse files Browse the repository at this point in the history
* Add OperatorMode config value and use it from main

It's specified as AZURE_OPERATOR_MODE, with possible values `webhooks`, `watchers` and `watchers-and-webhooks`. Use the setting from main() to decide whether watchers and webhooks should be started.

* Move reconciler and webhook registration out of main

Move it to controllers.RegisterReconcilers and controllers.RegisterWebhooks so that it can be shared between main and the controller tests.

* Test the watchers/webhooks behaviour of different operator modes

* Include operator logging when TEST_EMIT_ASO_LOGS is set

This can be very useful when trying to understand why a test is failing, but it's far too noisy to include all the time.

* Add tests for OperatorMode

* Add make targets and pipeline jobs for webhooks + watchers modes
  • Loading branch information
babbageclunk authored Aug 17, 2021
1 parent e190674 commit 1def7c5
Show file tree
Hide file tree
Showing 16 changed files with 1,239 additions and 1,695 deletions.
18 changes: 18 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ test-no-target-namespaces: generate fmt vet manifests
-run TestTargetNamespaces \
./controllers/...

# Check that we do the right thing in webhooks-only mode.
.PHONY: test-webhooks-only-mode
test-webhooks-only-mode: generate fmt vet manifests
TEST_RESOURCE_PREFIX=$(TEST_RESOURCE_PREFIX) TEST_USE_EXISTING_CLUSTER=false REQUEUE_AFTER=20 \
AZURE_OPERATOR_MODE=webhooks \
go test -v -tags "$(BUILD_TAGS)" -coverprofile=reports/webhooks-only-coverage-output.txt -coverpkg=./... -covermode count -parallel 4 -timeout 45m \
-run TestOperatorMode \
./controllers

# Check that when there are no target namespaces all namespaces are watched
.PHONY: test-watchers-only-mode
test-watchers-only-mode: generate fmt vet manifests
TEST_RESOURCE_PREFIX=$(TEST_RESOURCE_PREFIX) TEST_USE_EXISTING_CLUSTER=false REQUEUE_AFTER=20 \
AZURE_OPERATOR_MODE=watchers \
go test -v -tags "$(BUILD_TAGS)" -coverprofile=reports/watchers-only-coverage-output.txt -coverpkg=./... -covermode count -parallel 4 -timeout 45m \
-run TestOperatorMode \
./controllers

# Run subset of tests with v1 secret naming enabled to ensure no regression in old secret naming
.PHONY: test-v1-secret-naming
test-v1-secret-naming: generate fmt vet manifests
Expand Down
40 changes: 40 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,46 @@ jobs:
BUILD_ID: $(Build.BuildId)
workingDirectory: '$(System.DefaultWorkingDirectory)'
# TODO: There is no way to run steps in parallel in Azure pipelines but ideally this step would run in parallel
# TODO: with the above testing step to reduce overall runtime
- script: |
set -e
export PATH=$PATH:$(go env GOPATH)/bin:$(go env GOPATH)/kubebuilder/bin
export KUBEBUILDER_ASSETS=$(go env GOPATH)/kubebuilder/bin
make test-webhooks-only-mode
displayName: Run webhooks-only mode tests
condition: or(eq(variables['check_changes.SOURCE_CODE_CHANGED'], 'true'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
continueOnError: 'false'
env:
GO111MODULE: on
AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
REQUEUE_AFTER: $(REQUEUE_AFTER)
BUILD_ID: $(Build.BuildId)
workingDirectory: '$(System.DefaultWorkingDirectory)'
# TODO: There is no way to run steps in parallel in Azure pipelines but ideally this step would run in parallel
# TODO: with the above testing step to reduce overall runtime
- script: |
set -e
export PATH=$PATH:$(go env GOPATH)/bin:$(go env GOPATH)/kubebuilder/bin
export KUBEBUILDER_ASSETS=$(go env GOPATH)/kubebuilder/bin
make test-watchers-only-mode
displayName: Run watchers-only mode tests
condition: or(eq(variables['check_changes.SOURCE_CODE_CHANGED'], 'true'), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
continueOnError: 'false'
env:
GO111MODULE: on
AZURE_SUBSCRIPTION_ID: $(AZURE_SUBSCRIPTION_ID)
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
AZURE_CLIENT_SECRET: $(AZURE_CLIENT_SECRET)
REQUEUE_AFTER: $(REQUEUE_AFTER)
BUILD_ID: $(Build.BuildId)
workingDirectory: '$(System.DefaultWorkingDirectory)'
- script: |
set -e
export PATH=$PATH:$(go env GOPATH)/bin
Expand Down
134 changes: 134 additions & 0 deletions controllers/operatormode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// +build all

package controllers

import (
"context"
"testing"
"time"

"github.com/gobuffalo/envy"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

"github.com/Azure/azure-service-operator/api/v1alpha1"
"github.com/Azure/azure-service-operator/pkg/helpers"
"github.com/Azure/azure-service-operator/pkg/resourcemanager/config"
"github.com/Azure/go-autorest/autorest/to"
)

func TestOperatorModeWebhooks(t *testing.T) {
t.Parallel()
defer PanicRecover(t)
ctx := context.Background()
require := require.New(t)

operatorMode, err := config.ParseOperatorMode(
envy.Get("AZURE_OPERATOR_MODE", config.OperatorModeBoth.String()))
require.Equal(nil, err)

rgName := tc.resourceGroupName
sqlServerName := GenerateTestResourceNameWithRandom("sqlserver", 10)
userName := GenerateTestResourceNameWithRandom("sqluser", 5)

database := v1alpha1.AzureSQLUser{
ObjectMeta: metav1.ObjectMeta{
Name: userName,
Namespace: "default",
},
Spec: v1alpha1.AzureSQLUserSpec{
Server: sqlServerName,
DbName: "master",
ResourceGroup: rgName,
Roles: []string{"db_datareader", "db_datawriter"},
},
}

err = tc.k8sClient.Create(ctx, &database)
require.NotNil(err)
if operatorMode.IncludesWebhooks() {
// If we're running in a mode that enables webhooks, a user
// being created in the master database should be forbidden.
require.Contains(err.Error(), "'master' is a reserved database name and cannot be used")
} else {
// Otherwise we should fail because the webhook isn't
// registered (in a real multi-operator deployment it would be
// routed to a different operator running in webhook-only
// mode).
require.Contains(err.Error(), `failed calling webhook "vazuresqluser.kb.io"`)
}
}

func TestOperatorModeWatchers(t *testing.T) {
t.Parallel()
defer PanicRecover(t)
ctx := context.Background()
require := require.New(t)

rgName := tc.resourceGroupName
rgLocation := tc.resourceGroupLocation
acctName := "storageacct" + helpers.RandomString(6)
instance := v1alpha1.StorageAccount{
ObjectMeta: metav1.ObjectMeta{
Name: acctName,
Namespace: "default",
},
Spec: v1alpha1.StorageAccountSpec{
Kind: "BlobStorage",
Location: rgLocation,
ResourceGroup: rgName,
Sku: v1alpha1.StorageAccountSku{
Name: "Standard_LRS",
},
AccessTier: "Hot",
EnableHTTPSTrafficOnly: to.BoolPtr(true),
},
}

err := tc.k8sClient.Create(ctx, &instance)
require.Equal(nil, err)
defer EnsureDelete(ctx, t, tc, &instance)

operatorMode, err := config.ParseOperatorMode(
envy.Get("AZURE_OPERATOR_MODE", config.OperatorModeBoth.String()))
require.Equal(nil, err)

names := types.NamespacedName{
Namespace: "default",
Name: acctName,
}
gotFinalizer := func() bool {
var instance v1alpha1.StorageAccount
err := tc.k8sClient.Get(ctx, names, &instance)
require.Equal(nil, err)
res, err := meta.Accessor(&instance)
require.Equal(nil, err)
return HasFinalizer(res, finalizerName)
}

if operatorMode.IncludesWatchers() {
// The operator should see this account and start reconciling it.
require.Eventually(
gotFinalizer,
tc.timeoutFast,
tc.retry,
"instance never got finalizer even though operator mode is %q",
operatorMode,
)
} else {
// The operator shouldn't have registered a watcher, so there
// shouldn't be a finalizer.
require.Never(
gotFinalizer,
20*time.Second,
time.Second,
"instance got finalizer when operator mode is %q",
operatorMode,
)
}
}
Loading

0 comments on commit 1def7c5

Please sign in to comment.