Skip to content

Commit aa3768f

Browse files
author
Doyoon Kim
committed
Address PR comments and add E2E test
1 parent b2cef56 commit aa3768f

File tree

4 files changed

+134
-58
lines changed

4 files changed

+134
-58
lines changed

pkg/deploy/lattice/targets_manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (s *defaultTargetsManager) findStaleTargets(
9292
TargetIP: aws.StringValue(target.Id),
9393
Port: aws.Int64Value(target.Port),
9494
}
95-
if !modelSet.Contains(ipPort) {
95+
if aws.StringValue(target.Status) != vpclattice.TargetStatusDraining && !modelSet.Contains(ipPort) {
9696
staleTargets = append(staleTargets, ipPort)
9797
}
9898
}

pkg/deploy/lattice/targets_synthesizer.go

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import (
88
model "github.com/aws/aws-application-networking-k8s/pkg/model/lattice"
99
"github.com/aws/aws-application-networking-k8s/pkg/utils"
1010
"github.com/aws/aws-application-networking-k8s/pkg/utils/gwlog"
11+
"github.com/aws/aws-application-networking-k8s/pkg/webhook"
1112
"github.com/aws/aws-sdk-go/aws"
1213
"github.com/aws/aws-sdk-go/service/vpclattice"
1314
corev1 "k8s.io/api/core/v1"
1415
"sigs.k8s.io/controller-runtime/pkg/client"
1516
)
1617

1718
const (
18-
// TODO: use the constant on webhook side instead.
19-
LatticeReadinessGateConditionType = "aws-application-networking-k8s/pod-readiness-gate"
19+
LatticeReadinessGateConditionType = webhook.PodReadinessGateConditionType
2020

2121
ReadinessReasonHealthy = "Healthy"
2222
ReadinessReasonUnhealthy = "Unhealthy"
@@ -125,66 +125,67 @@ func (t *targetsSynthesizer) syncStatus(ctx context.Context, modelTargets []mode
125125

126126
var requeue bool
127127
for _, target := range modelTargets {
128-
// Step 0: Check if the endpoint is not ready yet.
129-
if !target.Ready && target.TargetRef.Name != "" {
130-
pod := &corev1.Pod{}
131-
t.client.Get(ctx, target.TargetRef, pod)
132-
133-
// Step 1: Check if the pod has the readiness gate spec.
134-
if !utils.PodHasReadinessGate(pod, LatticeReadinessGateConditionType) {
135-
continue
136-
}
128+
// Step 0: Check if the endpoint has a valid target, and is not ready yet.
129+
if target.Ready || target.TargetRef.Name == "" {
130+
continue
131+
}
137132

138-
// Step 2: Check if the pod readiness condition exists with specific condition type.
139-
// The condition is considered false when it does not exist.
140-
cond := utils.FindPodStatusCondition(pod.Status.Conditions, LatticeReadinessGateConditionType)
141-
if cond != nil && cond.Status == corev1.ConditionTrue {
142-
continue
143-
}
133+
// Step 1: Check if the pod has the readiness gate spec.
134+
pod := &corev1.Pod{}
135+
t.client.Get(ctx, target.TargetRef, pod)
136+
if !utils.PodHasReadinessGate(pod, LatticeReadinessGateConditionType) {
137+
continue
138+
}
144139

145-
// Step 3: Check if the Lattice target is healthy.
146-
newCond := corev1.PodCondition{
147-
Type: LatticeReadinessGateConditionType,
148-
Status: corev1.ConditionFalse,
149-
}
150-
targetIpPort := model.Target{
151-
TargetIP: target.TargetIP,
152-
Port: target.Port,
153-
}
154-
// syncStatus is called at post synthesis, so we can assume:
155-
// 1. Target for the pod (eventually) exists. If the target doesn't exist, we can simply requeue.
156-
// 2. Target group will be always in use, except for ServiceExport TGs.
157-
if latticeTarget, ok := latticeTargetMap[targetIpPort]; ok {
158-
switch status := aws.StringValue(latticeTarget.Status); status {
159-
case vpclattice.TargetStatusHealthy:
160-
newCond.Status = corev1.ConditionTrue
161-
newCond.Reason = ReadinessReasonHealthy
162-
case vpclattice.TargetStatusUnavailable:
163-
// Lattice HC not turned on. Readiness is designed to work only with HC but do not block deployment on this case.
164-
newCond.Status = corev1.ConditionTrue
165-
newCond.Reason = ReadinessReasonHealthCheckUnavailable
166-
case vpclattice.TargetStatusUnused:
167-
// Since this logic is called after HTTPRoute is wired, this only happens for ServiceExport TGs.
168-
// In this case we do not have to evaluate them as Healthy, but we also do not have to requeue.
169-
newCond.Reason = ReadinessReasonUnused
170-
case vpclattice.TargetStatusInitial:
171-
requeue = true
172-
newCond.Reason = ReadinessReasonInitial
173-
default:
174-
requeue = true
175-
newCond.Reason = ReadinessReasonUnhealthy
176-
newCond.Message = fmt.Sprintf("Target health check status: %s", status)
177-
}
178-
} else {
140+
// Step 2: Check if the pod readiness condition exists with specific condition type.
141+
// The condition is considered false when it does not exist.
142+
cond := utils.FindPodStatusCondition(pod.Status.Conditions, LatticeReadinessGateConditionType)
143+
if cond != nil && cond.Status == corev1.ConditionTrue {
144+
continue
145+
}
146+
147+
// Step 3: Check if the Lattice target is healthy.
148+
newCond := corev1.PodCondition{
149+
Type: LatticeReadinessGateConditionType,
150+
Status: corev1.ConditionFalse,
151+
}
152+
targetIpPort := model.Target{
153+
TargetIP: target.TargetIP,
154+
Port: target.Port,
155+
}
156+
// syncStatus is called at post synthesis, so we can assume:
157+
// 1. Target for the pod (eventually) exists. If the target doesn't exist, we can simply requeue.
158+
// 2. Target group will be always in use, except for ServiceExport TGs.
159+
if latticeTarget, ok := latticeTargetMap[targetIpPort]; ok {
160+
switch status := aws.StringValue(latticeTarget.Status); status {
161+
case vpclattice.TargetStatusHealthy:
162+
newCond.Status = corev1.ConditionTrue
163+
newCond.Reason = ReadinessReasonHealthy
164+
case vpclattice.TargetStatusUnavailable:
165+
// Lattice HC not turned on. Readiness is designed to work only with HC but do not block deployment on this case.
166+
newCond.Status = corev1.ConditionTrue
167+
newCond.Reason = ReadinessReasonHealthCheckUnavailable
168+
case vpclattice.TargetStatusUnused:
169+
// Since this logic is called after HTTPRoute is wired, this only happens for ServiceExport TGs.
170+
// In this case we do not have to evaluate them as Healthy, but we also do not have to requeue.
171+
newCond.Reason = ReadinessReasonUnused
172+
case vpclattice.TargetStatusInitial:
179173
requeue = true
180-
newCond.Reason = ReadinessReasonTargetNotFound
174+
newCond.Reason = ReadinessReasonInitial
175+
default:
176+
requeue = true
177+
newCond.Reason = ReadinessReasonUnhealthy
178+
newCond.Message = fmt.Sprintf("Target health check status: %s", status)
181179
}
180+
} else {
181+
requeue = true
182+
newCond.Reason = ReadinessReasonTargetNotFound
183+
}
182184

183-
// Step 4: Update status.
184-
utils.SetPodStatusCondition(&pod.Status.Conditions, newCond)
185-
if err := t.client.Status().Update(ctx, pod); err != nil {
186-
return requeue, err
187-
}
185+
// Step 4: Update status.
186+
utils.SetPodStatusCondition(&pod.Status.Conditions, newCond)
187+
if err := t.client.Status().Update(ctx, pod); err != nil {
188+
return requeue, err
188189
}
189190
}
190191
return requeue, nil

test/pkg/test/nginxapp.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package test
22

33
import (
4+
"github.com/aws/aws-application-networking-k8s/pkg/deploy/lattice"
45
"github.com/samber/lo"
56
appsv1 "k8s.io/api/apps/v1"
67
v1 "k8s.io/api/core/v1"
@@ -18,6 +19,7 @@ type ElasticSearchOptions struct {
1819
TargetPort2 int
1920
MergeFromDeployment []*appsv1.Deployment
2021
MergeFromService []*v1.Service
22+
ReadinessGate bool
2123
}
2224

2325
func (env *Framework) NewNginxApp(options ElasticSearchOptions) (*appsv1.Deployment, *v1.Service) {
@@ -33,6 +35,12 @@ func (env *Framework) NewNginxApp(options ElasticSearchOptions) (*appsv1.Deploym
3335
if options.TargetPort2 == 0 {
3436
options.TargetPort2 = 9114
3537
}
38+
var readinessGates []v1.PodReadinessGate
39+
if options.ReadinessGate {
40+
readinessGates = append(readinessGates, v1.PodReadinessGate{
41+
ConditionType: lattice.LatticeReadinessGateConditionType,
42+
})
43+
}
3644
deployment := New(&appsv1.Deployment{
3745
ObjectMeta: metav1.ObjectMeta{
3846
Namespace: options.Namespace,
@@ -53,6 +61,7 @@ func (env *Framework) NewNginxApp(options ElasticSearchOptions) (*appsv1.Deploym
5361
},
5462
},
5563
Spec: v1.PodSpec{
64+
ReadinessGates: readinessGates,
5665
Containers: []v1.Container{
5766
{
5867
Name: options.Name,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package integration
2+
3+
import (
4+
"github.com/aws/aws-application-networking-k8s/pkg/deploy/lattice"
5+
"github.com/aws/aws-application-networking-k8s/pkg/k8s"
6+
"github.com/aws/aws-application-networking-k8s/pkg/utils"
7+
"github.com/aws/aws-application-networking-k8s/test/pkg/test"
8+
. "github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega"
10+
"github.com/samber/lo"
11+
appsv1 "k8s.io/api/apps/v1"
12+
v1 "k8s.io/api/core/v1"
13+
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
14+
)
15+
16+
var _ = Describe("Pod Readiness Gate", Ordered, func() {
17+
var (
18+
deployment *appsv1.Deployment
19+
service *v1.Service
20+
route *gwv1.HTTPRoute
21+
)
22+
23+
BeforeAll(func() {
24+
deployment, service = testFramework.NewNginxApp(test.ElasticSearchOptions{
25+
Name: "pod-test",
26+
Namespace: k8snamespace,
27+
ReadinessGate: true,
28+
})
29+
route = testFramework.NewHttpRoute(testGateway, service, service.Kind)
30+
testFramework.ExpectCreated(ctx, deployment, service, route)
31+
})
32+
33+
It("updates condition when injected", func() {
34+
Eventually(func(g Gomega) {
35+
pods := testFramework.GetPodsByDeploymentName(deployment.Name, deployment.Namespace)
36+
for _, pod := range pods {
37+
cond := utils.FindPodStatusCondition(pod.Status.Conditions, lattice.LatticeReadinessGateConditionType)
38+
g.Expect(cond).To(Not(BeNil()))
39+
g.Expect(cond.Status).To(Equal(v1.ConditionTrue))
40+
}
41+
}).Should(Succeed())
42+
})
43+
44+
It("updates condition when a new pod is added", func() {
45+
testFramework.Get(ctx, k8s.NamespacedName(deployment), deployment)
46+
deployment.Spec.Replicas = lo.ToPtr(int32(3))
47+
testFramework.ExpectUpdated(ctx, deployment)
48+
49+
Eventually(func(g Gomega) {
50+
pods := testFramework.GetPodsByDeploymentName(deployment.Name, deployment.Namespace)
51+
for _, pod := range pods {
52+
cond := utils.FindPodStatusCondition(pod.Status.Conditions, lattice.LatticeReadinessGateConditionType)
53+
g.Expect(cond).To(Not(BeNil()))
54+
g.Expect(cond.Status).To(Equal(v1.ConditionTrue))
55+
}
56+
}).Should(Succeed())
57+
})
58+
59+
AfterAll(func() {
60+
testFramework.ExpectDeletedThenNotFound(ctx,
61+
deployment,
62+
service,
63+
route,
64+
)
65+
})
66+
})

0 commit comments

Comments
 (0)