Skip to content

Commit

Permalink
rollouts: end to end tests (#3879)
Browse files Browse the repository at this point in the history
  • Loading branch information
droot committed Mar 21, 2023
1 parent fd920eb commit ce0ff27
Show file tree
Hide file tree
Showing 5 changed files with 812 additions and 7 deletions.
3 changes: 2 additions & 1 deletion rollouts/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ tidy: ## Run go mod tidy against code.
license: ## Add licenses.
../scripts/update-license.sh

# set KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT=true for controlplane logs
.PHONY: test
test: manifests generate fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" KUBEBUILDER_ATTACH_CONTROL_PLANE_OUTPUT=true go test -v ./... -coverprofile cover.out

.PHONY: run-in-kind
run-in-kind: manifests kustomize ## Run the rollouts-controller-manager in a kind cluster named rollouts-management-cluster
Expand Down
373 changes: 373 additions & 0 deletions rollouts/controllers/rollout_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controllers

import (
"context"
"time"

gitopsv1alpha1 "github.com/GoogleContainerTools/kpt/rollouts/api/v1alpha1"
e2eclusters "github.com/GoogleContainerTools/kpt/rollouts/e2e/clusters"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("Rollout", func() {
var targets []e2eclusters.Config
var targetClusterSetup e2eclusters.ClusterSetup
var RolloutName = "test-rollout"
var RolloutNamespace = "default"

// Define utility constants for object names and testing timeouts/durations and intervals.
const (
timeout = time.Second * 10
duration = time.Second * 10
interval = time.Millisecond * 250
)

Context("A non progressive Rollout", func() {
// setup target clusters
BeforeEach(func() {
var err error
targets = []e2eclusters.Config{
{
Prefix: "e2e-sjc-",
Count: 1,
Labels: map[string]string{
"city": "sjc",
},
},
{
Prefix: "e2e-sfo-",
Count: 1,
Labels: map[string]string{
"city": "sfo",
},
},
}
targetClusterSetup, err = e2eclusters.GetClusterSetup(tt, k8sClient, targets...)
Expect(err).NotTo(HaveOccurred())

Expect(targetClusterSetup.PrepareAndWait(context.TODO(), 5*time.Minute)).To(Succeed())
})

AfterEach(func() {
By("tearing down the target clusters")
Expect(targetClusterSetup.Cleanup(context.TODO())).To(Succeed())
})

It("Should deploy package to only matched target clusters", func() {
By("By creating a new Rollout")
ctx := context.Background()
rollout := &gitopsv1alpha1.Rollout{
TypeMeta: metav1.TypeMeta{
APIVersion: "gitops.kpt.dev/v1alpha1",
Kind: "Rollout",
},
ObjectMeta: metav1.ObjectMeta{
Name: RolloutName,
Namespace: RolloutNamespace,
},
Spec: gitopsv1alpha1.RolloutSpec{
Description: "Test Rollout",
Packages: gitopsv1alpha1.PackagesConfig{
SourceType: gitopsv1alpha1.GitHub,
GitHub: gitopsv1alpha1.GitHubSource{
Selector: gitopsv1alpha1.GitHubSelector{
Org: "droot",
Repo: "store",
Directory: "namespaces",
Revision: "v3",
},
},
},
Clusters: gitopsv1alpha1.ClusterDiscovery{
SourceType: gitopsv1alpha1.KindCluster,
},
Targets: gitopsv1alpha1.ClusterTargetSelector{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"city": "sjc",
},
},
},
PackageToTargetMatcher: gitopsv1alpha1.PackageToClusterMatcher{
Type: gitopsv1alpha1.MatchAllClusters,
},
SyncTemplate: &gitopsv1alpha1.SyncTemplate{
Type: gitopsv1alpha1.TemplateTypeRootSync,
},
Strategy: gitopsv1alpha1.RolloutStrategy{
Type: gitopsv1alpha1.AllAtOnce,
},
},
}
Expect(k8sClient.Create(ctx, rollout)).To(Succeed())
/*
After creating this Rollout, let's check that the Rollout's Spec fields match what we passed in.
Note that, because the k8s apiserver may not have finished creating a Rollout after our `Create()` call from earlier, we will use Gomega’s Eventually() testing function instead of Expect() to give the apiserver an opportunity to finish creating our CronJob.
`Eventually()` will repeatedly run the function provided as an argument every interval seconds until
(a) the function’s output matches what’s expected in the subsequent `Should()` call, or
(b) the number of attempts * interval period exceed the provided timeout value.
In the examples below, timeout and interval are Go Duration values of our choosing.
*/

rolloutLookupKey := types.NamespacedName{Name: RolloutName, Namespace: RolloutNamespace}
createdRollout := &gitopsv1alpha1.Rollout{}

// We'll need to retry getting this newly created Rollout, given that creation may not immediately happen.
Eventually(func() bool {
err := k8sClient.Get(ctx, rolloutLookupKey, createdRollout)
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(createdRollout.Spec.Description).Should(Equal("Test Rollout"))

remoteSyncKey := types.NamespacedName{Name: "github-589324850-namespaces-e2e-sjc-0", Namespace: RolloutNamespace}
remoteSync := &gitopsv1alpha1.RemoteSync{}

// We should eventually have a remotesync object created corresponding to a target cluster
Eventually(func() bool {
err := k8sClient.Get(ctx, remoteSyncKey, remoteSync)
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(remoteSync.Spec.Template.Spec.Git.Repo).To(Equal("https://github.com/droot/store.git"))
Expect(remoteSync.Spec.Template.Spec.Git.Revision).To(Equal("v3"))

// We should eventually have the rollout completed
Eventually(func() bool {
_ = k8sClient.Get(ctx, rolloutLookupKey, createdRollout)
return createdRollout.Status.Overall == "Completed"
}, 1*time.Minute, interval).Should(BeTrue())

Expect(createdRollout.Status.ClusterStatuses).To(HaveLen(1))
Expect(createdRollout.Status.ClusterStatuses).To(ContainElement(gitopsv1alpha1.ClusterStatus{
Name: "e2e-sjc-0",
PackageStatus: gitopsv1alpha1.PackageStatus{
PackageID: "github-589324850-namespaces-e2e-sjc-0",
Status: "Synced",
SyncStatus: "Synced",
},
}))
forground := metav1.DeletePropagationForeground
Expect(k8sClient.Delete(context.TODO(), createdRollout, &client.DeleteOptions{PropagationPolicy: &forground})).To(Succeed())
// We should wait for the rollout to be deleted
Eventually(func() bool {
err := k8sClient.Get(ctx, rolloutLookupKey, createdRollout)
return client.IgnoreNotFound(err) == nil
}, 1*time.Minute, interval).Should(BeTrue())
})
})

Context("A progressive Rollout", func() {
// setup target clusters
BeforeEach(func() {
var err error
targets = []e2eclusters.Config{
{
Prefix: "e2e-sjcc-",
Count: 1,
Labels: map[string]string{
"city": "sjcc",
},
},
{
Prefix: "e2e-sfoo-",
Count: 1,
Labels: map[string]string{
"city": "sfoo",
},
},
}
targetClusterSetup, err = e2eclusters.GetClusterSetup(tt, k8sClient, targets...)
Expect(err).NotTo(HaveOccurred())

Expect(targetClusterSetup.PrepareAndWait(context.TODO(), 5*time.Minute)).To(Succeed())
})

AfterEach(func() {
By("tearing down the target clusters")
Expect(targetClusterSetup.Cleanup(context.TODO())).To(Succeed())
})

It("Should deploy package to matched target clusters", func() {
By("By creating a new Rollout")
ctx := context.Background()
RolloutName = "test-city-rollout"

RolloutStrategyName := "city-wide-rollout"
progressiveRolloutStrategy := &gitopsv1alpha1.ProgressiveRolloutStrategy{
TypeMeta: metav1.TypeMeta{
APIVersion: "gitops.kpt.dev/v1alpha1",
Kind: "ProgressiveRolloutStrategy",
},
ObjectMeta: metav1.ObjectMeta{
Name: RolloutStrategyName,
Namespace: RolloutNamespace,
},
Spec: gitopsv1alpha1.ProgressiveRolloutStrategySpec{
Waves: []gitopsv1alpha1.Wave{
{
Name: "sjc-stores",
Targets: gitopsv1alpha1.ClusterTargetSelector{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"city": "sjcc",
},
},
},
MaxConcurrent: 1,
},
{
Name: "sfo-stores",
Targets: gitopsv1alpha1.ClusterTargetSelector{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"city": "sfoo",
},
},
},
MaxConcurrent: 1,
},
},
},
}

Expect(k8sClient.Create(ctx, progressiveRolloutStrategy)).To(Succeed())
strategyLookupKey := types.NamespacedName{Name: RolloutStrategyName, Namespace: RolloutNamespace}
createdRolloutStrategy := &gitopsv1alpha1.ProgressiveRolloutStrategy{}

Eventually(func() bool {
err := k8sClient.Get(ctx, strategyLookupKey, createdRolloutStrategy)
return err == nil
}, timeout, interval).Should(BeTrue())

rollout := &gitopsv1alpha1.Rollout{
TypeMeta: metav1.TypeMeta{
APIVersion: "gitops.kpt.dev/v1alpha1",
Kind: "Rollout",
},
ObjectMeta: metav1.ObjectMeta{
Name: RolloutName,
Namespace: RolloutNamespace,
},
Spec: gitopsv1alpha1.RolloutSpec{
Description: "Test Rollout",
Packages: gitopsv1alpha1.PackagesConfig{
SourceType: gitopsv1alpha1.GitHub,
GitHub: gitopsv1alpha1.GitHubSource{
Selector: gitopsv1alpha1.GitHubSelector{
Org: "droot",
Repo: "store",
Directory: "namespaces",
Revision: "v3",
},
},
},
Clusters: gitopsv1alpha1.ClusterDiscovery{
SourceType: gitopsv1alpha1.KindCluster,
},
Targets: gitopsv1alpha1.ClusterTargetSelector{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "city",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"sjcc", "sfoo"},
},
},
},
},
PackageToTargetMatcher: gitopsv1alpha1.PackageToClusterMatcher{
Type: gitopsv1alpha1.MatchAllClusters,
},
SyncTemplate: &gitopsv1alpha1.SyncTemplate{
Type: gitopsv1alpha1.TemplateTypeRootSync,
},
Strategy: gitopsv1alpha1.RolloutStrategy{
Type: gitopsv1alpha1.Progressive,
Progressive: &gitopsv1alpha1.StrategyProgressive{
Name: RolloutStrategyName,
Namespace: RolloutNamespace,
PauseAfterWave: gitopsv1alpha1.PauseAfterWave{
WaveName: "sjc-stores", // pause at the first wave
},
},
},
},
}
Expect(k8sClient.Create(ctx, rollout)).To(Succeed())

rolloutLookupKey := types.NamespacedName{Name: RolloutName, Namespace: RolloutNamespace}
createdRollout := &gitopsv1alpha1.Rollout{}

// We'll need to retry getting this newly created Rollout, given that creation may not immediately happen.
Eventually(func() bool {
err := k8sClient.Get(ctx, rolloutLookupKey, createdRollout)
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(createdRollout.Spec.Description).To(Equal("Test Rollout"))

remoteSyncKey := types.NamespacedName{Name: "github-589324850-namespaces-e2e-sjcc-0", Namespace: RolloutNamespace}
remoteSync := &gitopsv1alpha1.RemoteSync{}

// We should eventually have a remotesync object created corresponding to a target cluster
Eventually(func() bool {
err := k8sClient.Get(ctx, remoteSyncKey, remoteSync)
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(remoteSync.Spec.Template.Spec.Git.Repo).To(Equal("https://github.com/droot/store.git"))
Expect(remoteSync.Spec.Template.Spec.Git.Revision).To(Equal("v3"))

// expect rollout to be in waiting state after completion of the first wave
Eventually(func() bool {
_ = k8sClient.Get(ctx, rolloutLookupKey, createdRollout)
return createdRollout.Status.Overall == "Waiting"
}, 2*time.Minute, interval).Should(BeTrue())

// advance the rollout to next wave
createdRollout.Spec.Strategy.Progressive.PauseAfterWave.WaveName = "sfo-stores"
Expect(k8sClient.Update(ctx, createdRollout)).To(Succeed())

// expect rollout to be in completed now
Eventually(func() bool {
_ = k8sClient.Get(ctx, rolloutLookupKey, createdRollout)
return createdRollout.Status.Overall == "Completed"
}, 2*time.Minute, interval).Should(BeTrue())

Expect(createdRollout.Status.ClusterStatuses).To(HaveLen(2))
Expect(createdRollout.Status.ClusterStatuses).To(
ContainElements(
gitopsv1alpha1.ClusterStatus{
Name: "e2e-sjcc-0",
PackageStatus: gitopsv1alpha1.PackageStatus{
PackageID: "github-589324850-namespaces-e2e-sjcc-0",
Status: "Synced",
SyncStatus: "Synced",
},
},
gitopsv1alpha1.ClusterStatus{
Name: "e2e-sfoo-0",
PackageStatus: gitopsv1alpha1.PackageStatus{
PackageID: "github-589324850-namespaces-e2e-sfoo-0",
Status: "Synced",
SyncStatus: "Synced",
},
}))
})
})
})
Loading

0 comments on commit ce0ff27

Please sign in to comment.