Skip to content

Commit db54fb3

Browse files
authored
add e2e test for Ingress (#1502)
1 parent 2d2e3c7 commit db54fb3

File tree

11 files changed

+1141
-2
lines changed

11 files changed

+1141
-2
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ingress
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestIngress(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Ingress Suite")
13+
}
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
package ingress
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/aws/aws-sdk-go/aws"
7+
appsv1 "k8s.io/api/apps/v1"
8+
corev1 "k8s.io/api/core/v1"
9+
networking "k8s.io/api/networking/v1beta1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/util/intstr"
12+
"sigs.k8s.io/aws-load-balancer-controller/pkg/algorithm"
13+
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
14+
"sigs.k8s.io/aws-load-balancer-controller/test/framework"
15+
"sigs.k8s.io/aws-load-balancer-controller/test/framework/utils"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
"sync"
18+
)
19+
20+
type PathConfig struct {
21+
Path string
22+
BackendID string
23+
}
24+
25+
type BackendConfig struct {
26+
Replicas int32
27+
TargetType elbv2model.TargetType
28+
29+
HTTPBody string
30+
}
31+
32+
type MultiPathIngressConfig struct {
33+
GroupName string
34+
GroupOrder int32
35+
PathCFGs []PathConfig
36+
}
37+
38+
type NamespacedResourcesConfig struct {
39+
IngCFGs map[string]MultiPathIngressConfig
40+
BackendCFGs map[string]BackendConfig
41+
}
42+
43+
func NewMultiPathBackendStack(namespacedResourcesCFGs map[string]NamespacedResourcesConfig, enablePodReadinessGate bool) *multiPathBackendStack {
44+
return &multiPathBackendStack{
45+
namespacedResourcesCFGs: namespacedResourcesCFGs,
46+
enablePodReadinessGate: enablePodReadinessGate,
47+
48+
nsByNSID: make(map[string]*corev1.Namespace),
49+
resStackByNSID: make(map[string]*resourceStack),
50+
ingByIngIDByNSID: make(map[string]map[string]*networking.Ingress),
51+
}
52+
}
53+
54+
type multiPathBackendStack struct {
55+
namespacedResourcesCFGs map[string]NamespacedResourcesConfig
56+
enablePodReadinessGate bool
57+
58+
// runtime variables
59+
nsByNSID map[string]*corev1.Namespace
60+
resStackByNSID map[string]*resourceStack
61+
ingByIngIDByNSID map[string]map[string]*networking.Ingress
62+
}
63+
64+
func (s *multiPathBackendStack) Deploy(ctx context.Context, f *framework.Framework) error {
65+
if err := s.allocateNamespaces(ctx, f); err != nil {
66+
return err
67+
}
68+
s.resStackByNSID, s.ingByIngIDByNSID = s.buildResourceStacks(s.namespacedResourcesCFGs, s.nsByNSID)
69+
if err := s.deployResourceStacks(ctx, f); err != nil {
70+
return err
71+
}
72+
return nil
73+
}
74+
75+
func (s *multiPathBackendStack) Cleanup(ctx context.Context, f *framework.Framework) error {
76+
if err := s.cleanupResourceStacks(ctx, f); err != nil {
77+
return err
78+
}
79+
if err := s.cleanupNamespaces(ctx, f); err != nil {
80+
return err
81+
}
82+
return nil
83+
}
84+
85+
func (s *multiPathBackendStack) FindIngress(nsID string, ingID string) *networking.Ingress {
86+
if ingByIngID, ok := s.ingByIngIDByNSID[nsID]; ok {
87+
if ing, ok := ingByIngID[ingID]; ok {
88+
return ing
89+
}
90+
}
91+
return nil
92+
}
93+
94+
func (s *multiPathBackendStack) allocateNamespaces(ctx context.Context, f *framework.Framework) error {
95+
f.Logger.Info("allocate all namespaces")
96+
for nsID := range s.namespacedResourcesCFGs {
97+
f.Logger.Info("allocating namespace", "nsID", nsID)
98+
ns, err := f.NSManager.AllocateNamespace(ctx, "aws-lb-e2e")
99+
if err != nil {
100+
return err
101+
}
102+
f.Logger.Info("allocated namespace", "nsID", nsID, "nsName", ns.Name)
103+
s.nsByNSID[nsID] = ns
104+
}
105+
106+
if s.enablePodReadinessGate {
107+
f.Logger.Info("label all namespaces with podReadinessGate injection")
108+
for _, ns := range s.nsByNSID {
109+
f.Logger.Info("labeling namespace with podReadinessGate injection", "nsName", ns.Name)
110+
oldNS := ns.DeepCopy()
111+
ns.Labels = algorithm.MergeStringMap(map[string]string{
112+
"elbv2.k8s.aws/pod-readiness-gate-inject": "enabled",
113+
}, ns.Labels)
114+
err := f.K8sClient.Patch(ctx, ns, client.MergeFrom(oldNS))
115+
if err != nil {
116+
return err
117+
}
118+
f.Logger.Info("labeled namespace with podReadinessGate injection", "nsName", ns.Name)
119+
}
120+
}
121+
return nil
122+
}
123+
124+
func (s *multiPathBackendStack) cleanupNamespaces(ctx context.Context, f *framework.Framework) error {
125+
f.Logger.Info("cleanup all namespaces")
126+
var cleanupErrs []error
127+
var cleanupErrsMutex sync.Mutex
128+
var wg sync.WaitGroup
129+
for nsID, ns := range s.nsByNSID {
130+
wg.Add(1)
131+
go func(nsID string, ns *corev1.Namespace) {
132+
defer wg.Done()
133+
f.Logger.Info("deleting namespace", "nsID", nsID, "nsName", ns.Name)
134+
if err := f.K8sClient.Delete(ctx, ns); err != nil {
135+
cleanupErrsMutex.Lock()
136+
cleanupErrs = append(cleanupErrs, err)
137+
cleanupErrsMutex.Unlock()
138+
return
139+
}
140+
if err := f.NSManager.WaitUntilNamespaceDeleted(ctx, ns); err != nil {
141+
cleanupErrsMutex.Lock()
142+
cleanupErrs = append(cleanupErrs, err)
143+
cleanupErrsMutex.Unlock()
144+
return
145+
}
146+
f.Logger.Info("deleted namespace", "nsID", nsID, "nsName", ns.Name)
147+
}(nsID, ns)
148+
}
149+
150+
wg.Wait()
151+
if len(cleanupErrs) != 0 {
152+
return utils.NewMultiError(cleanupErrs...)
153+
}
154+
return nil
155+
}
156+
157+
func (s *multiPathBackendStack) deployResourceStacks(ctx context.Context, f *framework.Framework) error {
158+
f.Logger.Info("deploy all resource stacks")
159+
for _, stack := range s.resStackByNSID {
160+
if err := stack.Deploy(ctx, f); err != nil {
161+
return err
162+
}
163+
}
164+
return nil
165+
}
166+
167+
func (s *multiPathBackendStack) cleanupResourceStacks(ctx context.Context, f *framework.Framework) error {
168+
f.Logger.Info("cleanup all resource stacks")
169+
var cleanupErrs []error
170+
var cleanupErrsMutex sync.Mutex
171+
var wg sync.WaitGroup
172+
173+
for nsID, resStack := range s.resStackByNSID {
174+
wg.Add(1)
175+
go func(resStack *resourceStack) {
176+
defer wg.Done()
177+
f.Logger.Info("begin cleanup resource stack", "nsID", nsID)
178+
if err := resStack.Cleanup(ctx, f); err != nil {
179+
cleanupErrsMutex.Lock()
180+
cleanupErrs = append(cleanupErrs, err)
181+
cleanupErrsMutex.Unlock()
182+
return
183+
}
184+
f.Logger.Info("end cleanup resource stack", "nsID", nsID)
185+
}(resStack)
186+
}
187+
188+
wg.Wait()
189+
if len(cleanupErrs) != 0 {
190+
return utils.NewMultiError(cleanupErrs...)
191+
}
192+
return nil
193+
}
194+
195+
func (s *multiPathBackendStack) buildResourceStacks(namespacedResourcesCFGs map[string]NamespacedResourcesConfig, nsByNSID map[string]*corev1.Namespace) (map[string]*resourceStack, map[string]map[string]*networking.Ingress) {
196+
resStackByNSID := make(map[string]*resourceStack, len(namespacedResourcesCFGs))
197+
ingByIngIDByNSID := make(map[string]map[string]*networking.Ingress, len(namespacedResourcesCFGs))
198+
for nsID, resCFG := range namespacedResourcesCFGs {
199+
ns := nsByNSID[nsID]
200+
resStack, ingByIngID := s.buildResourceStack(ns, resCFG)
201+
resStackByNSID[nsID] = resStack
202+
ingByIngIDByNSID[nsID] = ingByIngID
203+
}
204+
return resStackByNSID, ingByIngIDByNSID
205+
}
206+
207+
func (s *multiPathBackendStack) buildResourceStack(ns *corev1.Namespace, resourcesCFG NamespacedResourcesConfig) (*resourceStack, map[string]*networking.Ingress) {
208+
dpByBackendID, svcByBackendID := s.buildBackendResources(ns, resourcesCFG.BackendCFGs)
209+
ingByIngID := s.buildIngressResources(ns, resourcesCFG.IngCFGs, svcByBackendID)
210+
211+
dps := make([]*appsv1.Deployment, 0, len(dpByBackendID))
212+
for _, dp := range dpByBackendID {
213+
dps = append(dps, dp)
214+
}
215+
svcs := make([]*corev1.Service, 0, len(svcByBackendID))
216+
for _, svc := range svcByBackendID {
217+
svcs = append(svcs, svc)
218+
}
219+
ings := make([]*networking.Ingress, 0, len(ingByIngID))
220+
for _, ing := range ingByIngID {
221+
ings = append(ings, ing)
222+
}
223+
return NewResourceStack(dps, svcs, ings), ingByIngID
224+
}
225+
226+
func (s *multiPathBackendStack) buildIngressResources(ns *corev1.Namespace, ingCFGs map[string]MultiPathIngressConfig, svcByBackendID map[string]*corev1.Service) map[string]*networking.Ingress {
227+
ingByIngID := make(map[string]*networking.Ingress, len(ingCFGs))
228+
for ingID, ingCFG := range ingCFGs {
229+
ing := s.buildIngressResource(ns, ingID, ingCFG, svcByBackendID)
230+
ingByIngID[ingID] = ing
231+
}
232+
return ingByIngID
233+
}
234+
235+
func (s *multiPathBackendStack) buildIngressResource(ns *corev1.Namespace, ingID string, ingCFG MultiPathIngressConfig, svcByBackendID map[string]*corev1.Service) *networking.Ingress {
236+
ing := &networking.Ingress{
237+
ObjectMeta: metav1.ObjectMeta{
238+
Namespace: ns.Name,
239+
Name: ingID,
240+
Annotations: map[string]string{
241+
"kubernetes.io/ingress.class": "alb",
242+
"alb.ingress.kubernetes.io/scheme": "internet-facing",
243+
},
244+
},
245+
Spec: networking.IngressSpec{
246+
Rules: []networking.IngressRule{
247+
{
248+
IngressRuleValue: networking.IngressRuleValue{
249+
HTTP: &networking.HTTPIngressRuleValue{},
250+
},
251+
},
252+
},
253+
},
254+
}
255+
for _, pathCFG := range ingCFG.PathCFGs {
256+
backendSVC := svcByBackendID[pathCFG.BackendID]
257+
ing.Spec.Rules[0].HTTP.Paths = append(ing.Spec.Rules[0].HTTP.Paths, networking.HTTPIngressPath{
258+
Path: pathCFG.Path,
259+
Backend: networking.IngressBackend{
260+
ServiceName: backendSVC.Name,
261+
ServicePort: intstr.FromInt(80),
262+
},
263+
})
264+
}
265+
if ingCFG.GroupName != "" {
266+
ing.Annotations["alb.ingress.kubernetes.io/group.name"] = ingCFG.GroupName
267+
if ingCFG.GroupOrder != 0 {
268+
ing.Annotations["alb.ingress.kubernetes.io/group.order"] = fmt.Sprintf("%v", ingCFG.GroupOrder)
269+
}
270+
}
271+
return ing
272+
}
273+
274+
func (s *multiPathBackendStack) buildBackendResources(ns *corev1.Namespace, backendCFGs map[string]BackendConfig) (map[string]*appsv1.Deployment, map[string]*corev1.Service) {
275+
dpByBackendID := make(map[string]*appsv1.Deployment, len(backendCFGs))
276+
svcByBackendID := make(map[string]*corev1.Service, len(backendCFGs))
277+
for backendID, backendCFG := range backendCFGs {
278+
dp, svc := s.buildBackendResource(ns, backendID, backendCFG)
279+
dpByBackendID[backendID] = dp
280+
svcByBackendID[backendID] = svc
281+
}
282+
return dpByBackendID, svcByBackendID
283+
}
284+
285+
func (s *multiPathBackendStack) buildBackendResource(ns *corev1.Namespace, backendID string, backendCFG BackendConfig) (*appsv1.Deployment, *corev1.Service) {
286+
dp := &appsv1.Deployment{
287+
ObjectMeta: metav1.ObjectMeta{
288+
Namespace: ns.Name,
289+
Name: backendID,
290+
},
291+
Spec: appsv1.DeploymentSpec{
292+
Selector: &metav1.LabelSelector{
293+
MatchLabels: map[string]string{
294+
"app.kubernetes.io/name": backendID,
295+
},
296+
},
297+
Replicas: aws.Int32(backendCFG.Replicas),
298+
Template: corev1.PodTemplateSpec{
299+
ObjectMeta: metav1.ObjectMeta{
300+
Labels: map[string]string{
301+
"app.kubernetes.io/name": backendID,
302+
},
303+
},
304+
Spec: corev1.PodSpec{
305+
Containers: []corev1.Container{
306+
{
307+
Name: "app",
308+
Image: "970805265562.dkr.ecr.us-west-2.amazonaws.com/colorteller:latest",
309+
Ports: []corev1.ContainerPort{
310+
{
311+
ContainerPort: 8080,
312+
},
313+
},
314+
Env: []corev1.EnvVar{
315+
{
316+
Name: "SERVER_PORT",
317+
Value: fmt.Sprintf("%d", 8080),
318+
},
319+
{
320+
Name: "COLOR",
321+
Value: backendCFG.HTTPBody,
322+
},
323+
},
324+
},
325+
},
326+
},
327+
},
328+
},
329+
}
330+
svc := &corev1.Service{
331+
ObjectMeta: metav1.ObjectMeta{
332+
Namespace: ns.Name,
333+
Name: backendID,
334+
Annotations: map[string]string{
335+
"alb.ingress.kubernetes.io/target-type": string(backendCFG.TargetType),
336+
},
337+
},
338+
Spec: corev1.ServiceSpec{
339+
Type: corev1.ServiceTypeNodePort,
340+
Selector: map[string]string{
341+
"app.kubernetes.io/name": backendID,
342+
},
343+
Ports: []corev1.ServicePort{
344+
{
345+
Port: 80,
346+
TargetPort: intstr.FromInt(8080),
347+
Protocol: corev1.ProtocolTCP,
348+
},
349+
},
350+
},
351+
}
352+
return dp, svc
353+
}

0 commit comments

Comments
 (0)