Skip to content

Commit 00420a4

Browse files
authored
test: Implemented e2e test framework (#118)
* test: Implemented e2e test framework * test: upgraded tests to v1beta1
1 parent ceb2253 commit 00420a4

File tree

10 files changed

+2082
-1
lines changed

10 files changed

+2082
-1
lines changed

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ vet: ## Vet the code and dependencies
6464

6565
.PHONY: test
6666
test: ## Run tests.
67-
go test ./... -coverprofile coverage.out
67+
go test ./pkg/... -coverprofile coverage.out
6868

6969
.PHONY: toolchain
7070
toolchain: ## Install developer toolchain
@@ -86,3 +86,8 @@ docker-push: ## Push docker image with the manager.
8686
build-deploy: ## Create a deployment file that can be applied with `kubectl apply -f deploy.yaml`
8787
cd config/manager && kustomize edit set image controller=${ECRIMAGES}
8888
kustomize build config/default > deploy.yaml
89+
90+
## Run e2e tests against cluster pointed to by ~/.kube/config
91+
.PHONY: e2etest
92+
e2etest:
93+
cd test && go test -v ./... -count=1 --ginkgo.v

test/go.mod

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
module github.com/aws/aws-application-networking-k8s/test
2+
3+
go 1.20
4+
5+
replace (
6+
github.com/aws/aws-application-networking-k8s => ../
7+
github.com/aws/aws-sdk-go => ../scripts/aws_sdk_model_override/aws-sdk-go
8+
)
9+
10+
require (
11+
github.com/Pallinder/go-randomdata v1.2.0
12+
github.com/aws/aws-application-networking-k8s v0.3.0
13+
github.com/aws/aws-sdk-go v1.42.18
14+
github.com/imdario/mergo v0.3.13
15+
github.com/onsi/ginkgo/v2 v2.9.1
16+
github.com/onsi/gomega v1.27.3
17+
github.com/samber/lo v1.37.0
18+
go.uber.org/zap v1.24.0
19+
k8s.io/api v0.26.2
20+
k8s.io/apimachinery v0.26.2
21+
k8s.io/client-go v0.26.2
22+
sigs.k8s.io/controller-runtime v0.14.5
23+
sigs.k8s.io/gateway-api v0.6.1
24+
sigs.k8s.io/mcs-api v0.1.0
25+
)
26+
27+
require (
28+
github.com/benbjohnson/clock v1.1.0 // indirect
29+
github.com/beorn7/perks v1.0.1 // indirect
30+
github.com/cespare/xxhash/v2 v2.1.2 // indirect
31+
github.com/davecgh/go-spew v1.1.1 // indirect
32+
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
33+
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
34+
github.com/fsnotify/fsnotify v1.6.0 // indirect
35+
github.com/go-logr/logr v1.2.3 // indirect
36+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
37+
github.com/go-openapi/jsonreference v0.20.0 // indirect
38+
github.com/go-openapi/swag v0.19.14 // indirect
39+
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
40+
github.com/gogo/protobuf v1.3.2 // indirect
41+
github.com/golang/glog v1.0.0 // indirect
42+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
43+
github.com/golang/mock v1.6.0 // indirect
44+
github.com/golang/protobuf v1.5.3 // indirect
45+
github.com/google/gnostic v0.5.7-v3refs // indirect
46+
github.com/google/go-cmp v0.5.9 // indirect
47+
github.com/google/gofuzz v1.1.0 // indirect
48+
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
49+
github.com/google/uuid v1.1.2 // indirect
50+
github.com/jmespath/go-jmespath v0.4.0 // indirect
51+
github.com/josharian/intern v1.0.0 // indirect
52+
github.com/json-iterator/go v1.1.12 // indirect
53+
github.com/mailru/easyjson v0.7.6 // indirect
54+
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
55+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
56+
github.com/modern-go/reflect2 v1.0.2 // indirect
57+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
58+
github.com/pkg/errors v0.9.1 // indirect
59+
github.com/prometheus/client_golang v1.14.0 // indirect
60+
github.com/prometheus/client_model v0.3.0 // indirect
61+
github.com/prometheus/common v0.37.0 // indirect
62+
github.com/prometheus/procfs v0.8.0 // indirect
63+
github.com/spf13/pflag v1.0.5 // indirect
64+
go.uber.org/atomic v1.7.0 // indirect
65+
go.uber.org/multierr v1.6.0 // indirect
66+
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
67+
golang.org/x/net v0.8.0 // indirect
68+
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
69+
golang.org/x/sys v0.6.0 // indirect
70+
golang.org/x/term v0.6.0 // indirect
71+
golang.org/x/text v0.8.0 // indirect
72+
golang.org/x/time v0.3.0 // indirect
73+
golang.org/x/tools v0.7.0 // indirect
74+
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
75+
google.golang.org/appengine v1.6.7 // indirect
76+
google.golang.org/protobuf v1.28.1 // indirect
77+
gopkg.in/inf.v0 v0.9.1 // indirect
78+
gopkg.in/yaml.v2 v2.4.0 // indirect
79+
gopkg.in/yaml.v3 v3.0.1 // indirect
80+
k8s.io/apiextensions-apiserver v0.26.1 // indirect
81+
k8s.io/component-base v0.26.1 // indirect
82+
k8s.io/klog/v2 v2.80.1 // indirect
83+
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
84+
k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect
85+
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
86+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
87+
sigs.k8s.io/yaml v1.3.0 // indirect
88+
)

test/go.sum

Lines changed: 1448 additions & 0 deletions
Large diffs are not rendered by default.

test/pkg/test/context.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"go.uber.org/zap"
8+
"go.uber.org/zap/zaptest"
9+
)
10+
11+
type loggerKey struct{}
12+
13+
func NewContext(t *testing.T) context.Context {
14+
ctx := context.Background()
15+
ctx = context.WithValue(ctx, loggerKey{}, zaptest.NewLogger(t, zaptest.WrapOptions(
16+
zap.AddCaller(),
17+
zap.Development(),
18+
)).Sugar())
19+
return ctx
20+
}
21+
22+
func Logger(ctx context.Context) *zap.SugaredLogger {
23+
return ctx.Value(loggerKey{}).(*zap.SugaredLogger)
24+
}

test/pkg/test/framework.go

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package test
2+
3+
import (
4+
"context"
5+
"reflect"
6+
"sync"
7+
"time"
8+
9+
"github.com/aws/aws-application-networking-k8s/pkg/aws/services"
10+
"github.com/aws/aws-application-networking-k8s/pkg/latticestore"
11+
"github.com/aws/aws-sdk-go/aws/session"
12+
"github.com/aws/aws-sdk-go/service/vpclattice"
13+
. "github.com/onsi/ginkgo/v2"
14+
"github.com/onsi/gomega"
15+
. "github.com/onsi/gomega"
16+
"github.com/onsi/gomega/format"
17+
"github.com/onsi/gomega/types"
18+
"github.com/samber/lo"
19+
appsv1 "k8s.io/api/apps/v1"
20+
v1 "k8s.io/api/core/v1"
21+
"k8s.io/apimachinery/pkg/api/errors"
22+
"k8s.io/apimachinery/pkg/api/meta"
23+
"k8s.io/client-go/kubernetes/scheme"
24+
controllerruntime "sigs.k8s.io/controller-runtime"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
"sigs.k8s.io/gateway-api/apis/v1beta1"
27+
"sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
28+
)
29+
30+
func init() {
31+
format.MaxLength = 0
32+
}
33+
34+
var (
35+
CleanupTimeout = 300 * time.Second
36+
CreationTimeout = 120 * time.Second
37+
TestObjects = []struct {
38+
Type client.Object
39+
ListType client.ObjectList
40+
}{
41+
// Must currently be deleted in order to avoid https://github.com/aws/aws-application-networking-k8s/issues/115
42+
{&v1.Service{}, &v1.ServiceList{}},
43+
{&appsv1.Deployment{}, &appsv1.DeploymentList{}},
44+
{&v1beta1.HTTPRoute{}, &v1beta1.HTTPRouteList{}},
45+
{&v1beta1.Gateway{}, &v1beta1.GatewayList{}},
46+
}
47+
)
48+
49+
type Framework struct {
50+
client.Client
51+
LatticeClient services.Lattice
52+
}
53+
54+
func NewFramework(ctx context.Context) *Framework {
55+
var scheme = scheme.Scheme
56+
lo.Must0(v1beta1.Install(scheme))
57+
lo.Must0(v1alpha1.Install(scheme))
58+
framework := &Framework{
59+
Client: lo.Must(client.New(controllerruntime.GetConfigOrDie(), client.Options{Scheme: scheme})),
60+
LatticeClient: services.NewDefaultLattice(session.Must(session.NewSession()), ""), // region is currently hardcoded
61+
}
62+
gomega.Default.SetDefaultEventuallyPollingInterval(time.Second * 1)
63+
BeforeEach(func() { framework.ExpectToBeClean(ctx) })
64+
AfterSuite(func() { framework.ExpectToClean(ctx) })
65+
return framework
66+
}
67+
68+
func (env *Framework) ExpectToBeClean(ctx context.Context) {
69+
Logger(ctx).Info("Expecting the test environment to be clean")
70+
// Kubernetes API Objects
71+
for _, testObject := range TestObjects {
72+
env.EventuallyExpectNoneFound(ctx, testObject.ListType).WithOffset(1).Should(Succeed())
73+
}
74+
// AWS API Objects
75+
Eventually(func(g Gomega) {
76+
g.Expect(env.LatticeClient.ListServicesWithContext(ctx, &vpclattice.ListServicesInput{})).To(HaveField("Items", BeEmpty()))
77+
g.Expect(env.LatticeClient.ListServiceNetworksWithContext(ctx, &vpclattice.ListServiceNetworksInput{})).To(HaveField("Items", BeEmpty()))
78+
g.Expect(env.LatticeClient.ListTargetGroupsWithContext(ctx, &vpclattice.ListTargetGroupsInput{})).To(HaveField("Items", BeEmpty()))
79+
})
80+
}
81+
82+
func (env *Framework) ExpectToClean(ctx context.Context) {
83+
Logger(ctx).Info("Cleaning the test environment")
84+
wg := sync.WaitGroup{}
85+
namespaces := &v1.NamespaceList{}
86+
// Kubernetes API Objects
87+
Expect(env.List(ctx, namespaces)).WithOffset(1).To(Succeed())
88+
for _, namespace := range namespaces.Items {
89+
for _, object := range TestObjects {
90+
wg.Add(1)
91+
go func(object client.Object, objectList client.ObjectList, namespace string) {
92+
defer wg.Done()
93+
defer GinkgoRecover()
94+
env.ExpectDeleteAllToSucceed(ctx, object, namespace)
95+
env.EventuallyExpectNoneFound(ctx, objectList).Should(Succeed())
96+
}(object.Type.DeepCopyObject().(client.Object), object.ListType.DeepCopyObject().(client.ObjectList), namespace.Name)
97+
}
98+
}
99+
wg.Wait()
100+
101+
// AWS API Objects
102+
// Delete Services
103+
listServicesOutput, err := env.LatticeClient.ListServicesWithContext(ctx, &vpclattice.ListServicesInput{})
104+
Expect(err).ToNot(HaveOccurred())
105+
for _, service := range listServicesOutput.Items {
106+
// Delete ServiceNetworkServiceAssociations
107+
listServiceNetworkServiceAssociationsOutput, err := env.LatticeClient.ListServiceNetworkServiceAssociationsWithContext(ctx, &vpclattice.ListServiceNetworkServiceAssociationsInput{ServiceIdentifier: service.Id})
108+
Expect(err).ToNot(HaveOccurred())
109+
for _, serviceNetworkServiceAssociation := range listServiceNetworkServiceAssociationsOutput.Items {
110+
_, err := env.LatticeClient.DeleteServiceNetworkServiceAssociationWithContext(ctx, &vpclattice.DeleteServiceNetworkServiceAssociationInput{ServiceNetworkServiceAssociationIdentifier: serviceNetworkServiceAssociation.Id})
111+
Expect(err).ToNot(HaveOccurred())
112+
}
113+
// Delete Listeners
114+
listListenersOutput, err := env.LatticeClient.ListListenersWithContext(ctx, &vpclattice.ListListenersInput{ServiceIdentifier: service.Id})
115+
Expect(err).ToNot(HaveOccurred())
116+
for _, listener := range listListenersOutput.Items {
117+
_, err = env.LatticeClient.DeleteListenerWithContext(ctx, &vpclattice.DeleteListenerInput{ServiceIdentifier: service.Id, ListenerIdentifier: listener.Id})
118+
Expect(err).ToNot(HaveOccurred())
119+
}
120+
// Delete Service
121+
_, err = env.LatticeClient.DeleteServiceWithContext(ctx, &vpclattice.DeleteServiceInput{ServiceIdentifier: service.Id})
122+
Expect(err).ToNot(HaveOccurred())
123+
}
124+
// Delete TargetGroups
125+
listTargetGroupsOutput, err := env.LatticeClient.ListTargetGroupsWithContext(ctx, &vpclattice.ListTargetGroupsInput{})
126+
Expect(err).ToNot(HaveOccurred())
127+
for _, targetGroup := range listTargetGroupsOutput.Items {
128+
// Delete Targets
129+
listTargetsOutput, err := env.LatticeClient.ListTargetsWithContext(ctx, &vpclattice.ListTargetsInput{TargetGroupIdentifier: targetGroup.Id})
130+
Expect(err).ToNot(HaveOccurred())
131+
if targets := lo.Map(listTargetsOutput.Items, func(target *vpclattice.TargetSummary, _ int) *vpclattice.Target {
132+
return &vpclattice.Target{Id: target.Id}
133+
}); len(targets) > 0 {
134+
_, err = env.LatticeClient.DeregisterTargetsWithContext(ctx, &vpclattice.DeregisterTargetsInput{TargetGroupIdentifier: targetGroup.Id, Targets: targets})
135+
Expect(err).ToNot(HaveOccurred())
136+
}
137+
// Delete TargetGroup
138+
_, err = env.LatticeClient.DeleteTargetGroupWithContext(ctx, &vpclattice.DeleteTargetGroupInput{TargetGroupIdentifier: targetGroup.Id})
139+
Expect(err).ToNot(HaveOccurred())
140+
}
141+
listServiceNetworksOutput, err := env.LatticeClient.ListServiceNetworksWithContext(ctx, &vpclattice.ListServiceNetworksInput{})
142+
Expect(err).ToNot(HaveOccurred())
143+
for _, serviceNetwork := range listServiceNetworksOutput.Items {
144+
_, err := env.LatticeClient.DeleteServiceNetworkWithContext(ctx, &vpclattice.DeleteServiceNetworkInput{ServiceNetworkIdentifier: serviceNetwork.Id})
145+
Expect(err).ToNot(HaveOccurred())
146+
}
147+
148+
// Wait for objects to delete
149+
env.ExpectToBeClean(ctx)
150+
}
151+
152+
func (env *Framework) ExpectCreated(ctx context.Context, objects ...client.Object) {
153+
for _, object := range objects {
154+
Logger(ctx).Infof("Creating %s %s/%s", reflect.TypeOf(object), object.GetNamespace(), object.GetName())
155+
Expect(env.Create(ctx, object)).WithOffset(1).To(Succeed())
156+
}
157+
}
158+
159+
func (env *Framework) ExpectDeleted(ctx context.Context, objects ...client.Object) {
160+
for _, object := range objects {
161+
Logger(ctx).Infof("Deleting %s %s/%s", reflect.TypeOf(object), object.GetNamespace(), object.GetName())
162+
Expect(env.Delete(ctx, object)).WithOffset(1).To(Succeed())
163+
}
164+
}
165+
166+
func (env *Framework) ExpectDeleteAllToSucceed(ctx context.Context, object client.Object, namespace string) {
167+
Expect(env.DeleteAllOf(ctx, object, client.InNamespace(namespace), client.HasLabels([]string{DiscoveryLabel}))).WithOffset(1).To(Succeed())
168+
}
169+
170+
func (env *Framework) EventuallyExpectNotFound(ctx context.Context, objects ...client.Object) types.AsyncAssertion {
171+
return Eventually(func(g Gomega) {
172+
for _, object := range objects {
173+
g.Expect(errors.IsNotFound(env.Get(ctx, client.ObjectKeyFromObject(object), object))).To(BeTrue())
174+
}
175+
}, CleanupTimeout)
176+
}
177+
178+
func (env *Framework) EventuallyExpectNoneFound(ctx context.Context, objectList client.ObjectList) types.AsyncAssertion {
179+
return Eventually(func(g Gomega) {
180+
g.Expect(env.List(ctx, objectList, client.HasLabels([]string{DiscoveryLabel}))).To(Succeed())
181+
g.Expect(meta.ExtractList(objectList)).To(HaveLen(0), "Expected to not find any %q with label %q", reflect.TypeOf(objectList), DiscoveryLabel)
182+
}, CleanupTimeout)
183+
}
184+
185+
func (env *Framework) GetServiceNetwork(ctx context.Context, gateway *v1beta1.Gateway) *vpclattice.ServiceNetworkSummary {
186+
var found *vpclattice.ServiceNetworkSummary
187+
Eventually(func(g Gomega) {
188+
listServiceNetworksOutput, err := env.LatticeClient.ListServiceNetworksWithContext(ctx, &vpclattice.ListServiceNetworksInput{})
189+
g.Expect(err).ToNot(HaveOccurred())
190+
g.Expect(listServiceNetworksOutput.Items).ToNot(BeEmpty())
191+
for _, serviceNetwork := range listServiceNetworksOutput.Items {
192+
if lo.FromPtr(serviceNetwork.Name) == gateway.Name {
193+
found = serviceNetwork
194+
}
195+
}
196+
g.Expect(found).ToNot(BeNil())
197+
}, CreationTimeout).WithOffset(1).Should(Succeed())
198+
return found
199+
}
200+
201+
func (env *Framework) GetService(ctx context.Context, httpRoute *v1beta1.HTTPRoute) *vpclattice.ServiceSummary {
202+
var found *vpclattice.ServiceSummary
203+
Eventually(func(g Gomega) {
204+
listServicesOutput, err := env.LatticeClient.ListServicesWithContext(ctx, &vpclattice.ListServicesInput{})
205+
g.Expect(err).ToNot(HaveOccurred())
206+
g.Expect(listServicesOutput.Items).ToNot(BeEmpty())
207+
for _, service := range listServicesOutput.Items {
208+
if lo.FromPtr(service.Name) == latticestore.AWSServiceName(httpRoute.Name, httpRoute.Namespace) {
209+
found = service
210+
}
211+
}
212+
g.Expect(found).ToNot(BeNil())
213+
g.Expect(found.Status).To(Equal(lo.ToPtr(vpclattice.ServiceStatusActive)))
214+
}, CreationTimeout).WithOffset(1).Should(Succeed())
215+
216+
return found
217+
}
218+
219+
func (env *Framework) GetTargetGroup(ctx context.Context, service *v1.Service) *vpclattice.TargetGroupSummary {
220+
var found *vpclattice.TargetGroupSummary
221+
Eventually(func(g Gomega) {
222+
listTargetGroupsOutput, err := env.LatticeClient.ListTargetGroupsWithContext(ctx, &vpclattice.ListTargetGroupsInput{})
223+
g.Expect(err).ToNot(HaveOccurred())
224+
g.Expect(listTargetGroupsOutput.Items).ToNot(BeEmpty())
225+
for _, targetGroup := range listTargetGroupsOutput.Items {
226+
if lo.FromPtr(targetGroup.Name) == latticestore.TargetGroupName(service.Name, service.Namespace) {
227+
found = targetGroup
228+
}
229+
}
230+
g.Expect(found).ToNot(BeNil())
231+
g.Expect(found.Status).To(Equal(lo.ToPtr(vpclattice.TargetGroupStatusActive)))
232+
}, CreationTimeout).WithOffset(1).Should(Succeed())
233+
return found
234+
}
235+
236+
func (env *Framework) GetTargets(ctx context.Context, targetGroup *vpclattice.TargetGroupSummary, deployment *appsv1.Deployment) []*vpclattice.TargetSummary {
237+
var found []*vpclattice.TargetSummary
238+
Eventually(func(g Gomega) {
239+
podList := &v1.PodList{}
240+
g.Expect(env.List(ctx, podList, client.MatchingLabels(deployment.Spec.Selector.MatchLabels))).To(Succeed())
241+
g.Expect(podList.Items).To(HaveLen(int(*deployment.Spec.Replicas)))
242+
243+
listTargetsOutput, err := env.LatticeClient.ListTargetsWithContext(ctx, &vpclattice.ListTargetsInput{TargetGroupIdentifier: targetGroup.Id})
244+
g.Expect(err).ToNot(HaveOccurred())
245+
g.Expect(listTargetsOutput.Items).To(HaveLen(int(*deployment.Spec.Replicas)))
246+
247+
podIps := lo.Map(podList.Items, func(pod v1.Pod, _ int) string { return pod.Status.PodIP })
248+
targetIps := lo.Filter(listTargetsOutput.Items, func(target *vpclattice.TargetSummary, _ int) bool { return lo.Contains(podIps, *target.Id) })
249+
g.Expect(targetIps).To(HaveLen(int(*deployment.Spec.Replicas)))
250+
251+
found = listTargetsOutput.Items
252+
}, CreationTimeout).WithOffset(1).Should(Succeed())
253+
return found
254+
}

0 commit comments

Comments
 (0)