Skip to content

Commit ca76bd8

Browse files
authored
Merge branch 'main' into dym-lb-policy
2 parents 75fece3 + d80fb5b commit ca76bd8

11 files changed

Lines changed: 399 additions & 71 deletions

.github/workflows/release.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ jobs:
118118
run: make image.multiarch.setup image.push.multiarch TAG=${{ env.release_tag }} IMAGE=docker.io/envoyproxy/gateway
119119

120120
- name: Generate Release Artifacts
121+
# rc.0 tags ship no release notes, so generate-artifacts would fail on the missing file.
122+
# These artifacts only feed the GitHub Release upload below, which is also skipped for rc.0.
123+
if: ${{ !contains(github.ref, '-rc.0') }}
121124
run: IMAGE_PULL_POLICY=IfNotPresent make generate-artifacts IMAGE=envoyproxy/gateway TAG=${{ env.release_tag }} OUTPUT_DIR=release-artifacts
122125

123126
- name: Build and Push EG Release Helm Chart
@@ -142,6 +145,7 @@ jobs:
142145
cd release-artifacts && unzip benchmark_report.zip
143146
144147
- name: Package EG multiarch binaries
148+
if: ${{ !contains(github.ref, '-rc.0') }}
145149
run: |
146150
tar -zcvf envoy-gateway_${{ env.release_tag }}_linux_amd64.tar.gz bin/linux/amd64/envoy-gateway
147151
tar -zcvf envoy-gateway_${{ env.release_tag }}_linux_arm64.tar.gz bin/linux/arm64/envoy-gateway

internal/gatewayapi/status/gateway.go

Lines changed: 100 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -62,83 +62,64 @@ type NodeAddresses struct {
6262
// service and deployment or daemonset state.
6363
func UpdateGatewayStatusProgrammedCondition(gw *gwapiv1.Gateway, svc *corev1.Service, envoyObj client.Object, nodeAddresses NodeAddresses) {
6464
var addresses, hostnames []string
65+
var addressNotUsable bool
66+
67+
// When the Service doesn't exist yet but spec.Addresses is set, populate
68+
// status addresses from spec so they are visible as soon as conditions are
69+
// reconciled. The Programmed condition will indicate AddressNotUsable until
70+
// the backing Service is created.
71+
if svc == nil && len(gw.Spec.Addresses) > 0 {
72+
addresses, hostnames = specAddressesToSlices(gw.Spec.Addresses)
73+
gw.Status.Addresses = buildGatewayAddresses(addresses, hostnames)
74+
gw.Status.Conditions = MergeConditions(gw.Status.Conditions,
75+
newCondition(string(gwapiv1.GatewayConditionProgrammed), metav1.ConditionFalse, string(gwapiv1.GatewayReasonAddressNotUsable),
76+
messageAddressNotUsable, gw.Generation))
77+
return
78+
}
79+
6580
// Update the status addresses field.
6681
if svc != nil {
6782
// If the addresses is explicitly set in the Gateway spec by the user, use it
6883
// to populate the Status
6984
if len(gw.Spec.Addresses) > 0 {
70-
// Make sure the addresses have been populated into ExternalIPs/ClusterIPs
71-
// and use that value
7285
if len(svc.Spec.ExternalIPs) > 0 {
7386
addresses = append(addresses, svc.Spec.ExternalIPs...)
74-
} else if len(svc.Spec.ClusterIPs) > 0 {
75-
// Filter out "None" values which represent headless services
76-
for _, ip := range svc.Spec.ClusterIPs {
77-
if ip != "" && ip != "None" {
78-
addresses = append(addresses, ip)
79-
}
80-
}
87+
} else {
88+
addresses = filterNoneClusterIPs(svc.Spec.ClusterIPs)
8189
}
90+
// Hostname addresses in spec cannot be assigned via ExternalIPs/LB;
91+
// report AddressNotUsable if any are present.
92+
addressNotUsable = hasHostnameAddress(gw.Spec.Addresses)
8293
} else {
83-
if svc.Spec.Type == corev1.ServiceTypeLoadBalancer {
84-
for i := range svc.Status.LoadBalancer.Ingress {
85-
switch {
86-
case len(svc.Status.LoadBalancer.Ingress[i].IP) > 0:
87-
addresses = append(addresses, svc.Status.LoadBalancer.Ingress[i].IP)
88-
case len(svc.Status.LoadBalancer.Ingress[i].Hostname) > 0:
89-
// Remove when the following supports the hostname address type:
90-
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.5.0/conformance/utils/kubernetes/helpers.go#L201-L207
91-
if svc.Status.LoadBalancer.Ingress[i].Hostname == "localhost" {
92-
addresses = append(addresses, "127.0.0.1")
93-
}
94-
hostnames = append(hostnames, svc.Status.LoadBalancer.Ingress[i].Hostname)
95-
}
96-
}
97-
}
98-
99-
if svc.Spec.Type == corev1.ServiceTypeClusterIP {
100-
for i := range svc.Spec.ClusterIPs {
101-
// Filter out "None" values which represent headless services
102-
if svc.Spec.ClusterIPs[i] != "" && svc.Spec.ClusterIPs[i] != "None" {
103-
addresses = append(addresses, svc.Spec.ClusterIPs[i])
104-
}
105-
}
106-
}
107-
108-
if svc.Spec.Type == corev1.ServiceTypeNodePort {
109-
var relevantAddresses []string
94+
switch svc.Spec.Type {
95+
case corev1.ServiceTypeLoadBalancer:
96+
addresses, hostnames = collectLoadBalancerAddresses(svc)
97+
case corev1.ServiceTypeClusterIP:
98+
addresses = filterNoneClusterIPs(svc.Spec.ClusterIPs)
99+
case corev1.ServiceTypeNodePort:
110100
if slices.Contains(svc.Spec.IPFamilies, corev1.IPv4Protocol) {
111-
relevantAddresses = append(relevantAddresses, nodeAddresses.IPv4...)
101+
addresses = append(addresses, nodeAddresses.IPv4...)
112102
}
113103
if slices.Contains(svc.Spec.IPFamilies, corev1.IPv6Protocol) {
114-
relevantAddresses = append(relevantAddresses, nodeAddresses.IPv6...)
104+
addresses = append(addresses, nodeAddresses.IPv6...)
115105
}
116-
addresses = relevantAddresses
117106
}
118107
}
119108

120-
gwAddresses := make([]gwapiv1.GatewayStatusAddress, 0, len(addresses)+len(hostnames))
121-
for i := range addresses {
122-
addr := gwapiv1.GatewayStatusAddress{
123-
Type: new(gwapiv1.IPAddressType),
124-
Value: addresses[i],
125-
}
126-
gwAddresses = append(gwAddresses, addr)
127-
}
128-
129-
for i := range hostnames {
130-
addr := gwapiv1.GatewayStatusAddress{
131-
Type: new(gwapiv1.HostnameAddressType),
132-
Value: hostnames[i],
133-
}
134-
gwAddresses = append(gwAddresses, addr)
135-
}
136-
137-
gw.Status.Addresses = gwAddresses
109+
gw.Status.Addresses = buildGatewayAddresses(addresses, hostnames)
138110
} else {
139111
gw.Status.Addresses = nil
140112
}
141113

114+
// If any requested address cannot be used (e.g. hostname addresses),
115+
// report AddressNotUsable before checking deployment readiness.
116+
if addressNotUsable {
117+
gw.Status.Conditions = MergeConditions(gw.Status.Conditions,
118+
newCondition(string(gwapiv1.GatewayConditionProgrammed), metav1.ConditionFalse, string(gwapiv1.GatewayReasonAddressNotUsable),
119+
messageAddressNotUsable, gw.Generation))
120+
return
121+
}
122+
142123
// Update the programmed condition.
143124
updateGatewayProgrammedCondition(gw, envoyObj)
144125
}
@@ -159,6 +140,7 @@ func SetGatewayListenerStatusCondition(gateway *gwapiv1.Gateway, listenerStatusI
159140

160141
const (
161142
messageAddressNotAssigned = "No addresses have been assigned to the Gateway"
143+
messageAddressNotUsable = "One or more Gateway addresses cannot be used"
162144
messageFmtTooManyAddresses = "Too many addresses (%d) have been assigned to the Gateway; only the first 16 are included in the status."
163145
messageNoResources = "Envoy replicas unavailable"
164146
messageFmtProgrammed = "Address assigned to the Gateway, %d/%d envoy replicas available"
@@ -217,3 +199,64 @@ func GetGatewayListenerStatusConditions(gateway *gwapiv1.Gateway, listenerStatus
217199
}
218200
return gateway.Status.Listeners[listenerStatusIdx].Conditions
219201
}
202+
203+
// hasHostnameAddress returns true if any address in the slice uses HostnameAddressType.
204+
func hasHostnameAddress(addrs []gwapiv1.GatewaySpecAddress) bool {
205+
for i := range addrs {
206+
if addrs[i].Type != nil && *addrs[i].Type == gwapiv1.HostnameAddressType {
207+
return true
208+
}
209+
}
210+
return false
211+
}
212+
213+
func specAddressesToSlices(addrs []gwapiv1.GatewaySpecAddress) (ips, hostnames []string) {
214+
for i := range addrs {
215+
if addrs[i].Type != nil && *addrs[i].Type == gwapiv1.HostnameAddressType {
216+
hostnames = append(hostnames, addrs[i].Value)
217+
} else {
218+
ips = append(ips, addrs[i].Value)
219+
}
220+
}
221+
return ips, hostnames
222+
}
223+
224+
func collectLoadBalancerAddresses(svc *corev1.Service) (addresses, hostnames []string) {
225+
for i := range svc.Status.LoadBalancer.Ingress {
226+
switch {
227+
case len(svc.Status.LoadBalancer.Ingress[i].IP) > 0:
228+
addresses = append(addresses, svc.Status.LoadBalancer.Ingress[i].IP)
229+
case len(svc.Status.LoadBalancer.Ingress[i].Hostname) > 0:
230+
hostnames = append(hostnames, svc.Status.LoadBalancer.Ingress[i].Hostname)
231+
}
232+
}
233+
return addresses, hostnames
234+
}
235+
236+
func filterNoneClusterIPs(clusterIPs []string) []string {
237+
var out []string
238+
for _, ip := range clusterIPs {
239+
if ip != "" && ip != "None" {
240+
out = append(out, ip)
241+
}
242+
}
243+
return out
244+
}
245+
246+
// buildGatewayAddresses creates a status address slice from IP and hostname slices.
247+
func buildGatewayAddresses(ips, hostnames []string) []gwapiv1.GatewayStatusAddress {
248+
out := make([]gwapiv1.GatewayStatusAddress, 0, len(ips)+len(hostnames))
249+
for i := range ips {
250+
out = append(out, gwapiv1.GatewayStatusAddress{
251+
Type: new(gwapiv1.IPAddressType),
252+
Value: ips[i],
253+
})
254+
}
255+
for i := range hostnames {
256+
out = append(out, gwapiv1.GatewayStatusAddress{
257+
Type: new(gwapiv1.HostnameAddressType),
258+
Value: hostnames[i],
259+
})
260+
}
261+
return out
262+
}

internal/gatewayapi/status/gateway_test.go

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ func TestUpdateGatewayStatusProgrammedCondition(t *testing.T) {
4141
},
4242
wantAddresses: nil,
4343
},
44+
{
45+
name: "nil svc with spec addresses populates status from spec",
46+
args: args{
47+
gw: &gwapiv1.Gateway{
48+
Spec: gwapiv1.GatewaySpec{
49+
Addresses: []gwapiv1.GatewaySpecAddress{
50+
{
51+
Type: new(gwapiv1.IPAddressType),
52+
Value: "10.0.0.1",
53+
},
54+
},
55+
},
56+
},
57+
svc: nil,
58+
},
59+
wantAddresses: []gwapiv1.GatewayStatusAddress{
60+
{
61+
Type: new(gwapiv1.IPAddressType),
62+
Value: "10.0.0.1",
63+
},
64+
},
65+
},
4466
{
4567
name: "LoadBalancer svc with ingress ip",
4668
args: args{
@@ -99,10 +121,6 @@ func TestUpdateGatewayStatusProgrammedCondition(t *testing.T) {
99121
},
100122
},
101123
wantAddresses: []gwapiv1.GatewayStatusAddress{
102-
{
103-
Type: new(gwapiv1.IPAddressType),
104-
Value: "127.0.0.1",
105-
},
106124
{
107125
Type: new(gwapiv1.HostnameAddressType),
108126
Value: "localhost",
@@ -308,6 +326,37 @@ func TestUpdateGatewayStatusProgrammedCondition(t *testing.T) {
308326
},
309327
wantAddresses: []gwapiv1.GatewayStatusAddress{},
310328
},
329+
{
330+
name: "Spec addresses with hostname triggers AddressNotUsable",
331+
args: args{
332+
gw: &gwapiv1.Gateway{
333+
Spec: gwapiv1.GatewaySpec{
334+
Addresses: []gwapiv1.GatewaySpecAddress{
335+
{
336+
Type: new(gwapiv1.IPAddressType),
337+
Value: "10.0.0.1",
338+
},
339+
{
340+
Type: new(gwapiv1.HostnameAddressType),
341+
Value: "test.example.com",
342+
},
343+
},
344+
},
345+
},
346+
svc: &corev1.Service{
347+
Spec: corev1.ServiceSpec{
348+
ExternalIPs: []string{"10.0.0.1"},
349+
Type: corev1.ServiceTypeLoadBalancer,
350+
},
351+
},
352+
},
353+
wantAddresses: []gwapiv1.GatewayStatusAddress{
354+
{
355+
Type: new(gwapiv1.IPAddressType),
356+
Value: "10.0.0.1",
357+
},
358+
},
359+
},
311360
{
312361
name: "Nodeport svc Ipv6 with dual stack node addresses",
313362
args: args{
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
gateways:
2+
- apiVersion: gateway.networking.k8s.io/v1
3+
kind: Gateway
4+
metadata:
5+
namespace: envoy-gateway
6+
name: gateway-1
7+
spec:
8+
gatewayClassName: envoy-gateway-class
9+
listeners:
10+
- name: http
11+
protocol: HTTP
12+
port: 80
13+
addresses:
14+
- type: IPAddress
15+
value: 1.2.3.4
16+
- type: Hostname
17+
value: foo.bar.com

0 commit comments

Comments
 (0)