Skip to content

Commit fe0368f

Browse files
committed
fix: respect existing hostname
1 parent e3b16c5 commit fe0368f

File tree

4 files changed

+136
-12
lines changed

4 files changed

+136
-12
lines changed

internal/controller/gateway_controller.go

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ func (r *GatewayReconciler) prepareUpstreamGateway(gateway *gatewayv1.Gateway) (
178178
// the defaulting webhook as the UID is not available, but we still want to
179179
// let users create gateways without listeners and receive sane defaults.
180180

181-
gatewayDefaultHostname := ptr.To(gatewayv1.Hostname(r.Config.Gateway.GatewayDNSAddress(gateway)))
181+
gatewayDefaultHostname := ptr.To(gatewayv1.Hostname(r.gatewayCanonicalHostname(gateway)))
182182

183183
defaultHTTPListener := gatewayutil.GetListenerByName(gateway.Spec.Listeners, gatewayutil.DefaultHTTPListenerName)
184184
if defaultHTTPListener != nil && defaultHTTPListener.Hostname == nil {
@@ -221,7 +221,7 @@ func (r *GatewayReconciler) ensureDownstreamGateway(
221221
}
222222
}
223223
} else {
224-
gatewayDNSAddress := r.Config.Gateway.GatewayDNSAddress(upstreamGateway)
224+
gatewayDNSAddress := r.gatewayCanonicalHostname(upstreamGateway)
225225

226226
// targetDomainHostnames are default hostnames that are unique to each gateway, and
227227
// will have DNS records created for them. Any custom hostnames provided in
@@ -756,6 +756,7 @@ func (r *GatewayReconciler) isDatumManagedGatewayHostname(upstreamGateway *gatew
756756
legacyUIDWithoutDashes := strings.ReplaceAll(gatewayUID, "-", "")
757757

758758
managedBaseHostnames := []string{
759+
r.gatewayCanonicalHostname(upstreamGateway),
759760
r.Config.Gateway.GatewayDNSAddress(upstreamGateway),
760761
fmt.Sprintf("%s.%s", legacyUIDWithoutDashes, targetDomain),
761762
fmt.Sprintf("%s.%s", gatewayUID, targetDomain),
@@ -785,7 +786,7 @@ func (r *GatewayReconciler) ensureHostnameVerification(
785786
) ([]string, error) {
786787
logger := log.FromContext(ctx)
787788

788-
gatewayDefaultHostname := r.Config.Gateway.GatewayDNSAddress(upstreamGateway)
789+
gatewayDefaultHostname := r.gatewayCanonicalHostname(upstreamGateway)
789790

790791
// Allow hostnames which have been successfully configured on the downstream
791792
// gateway stay on the gateway, regardless of whether or not there is a
@@ -908,6 +909,43 @@ func (r *GatewayReconciler) ensureHostnameVerification(
908909
return verifiedHostnamesSlice, nil
909910
}
910911

912+
// gatewayCanonicalHostname returns the managed canonical hostname for a gateway.
913+
// It prefers an existing status address in the configured target domain to avoid
914+
// renaming already-programmed gateways during hostname format transitions.
915+
func (r *GatewayReconciler) gatewayCanonicalHostname(upstreamGateway *gatewayv1.Gateway) string {
916+
if existing := managedGatewayHostnameFromStatus(
917+
upstreamGateway.Status.Addresses,
918+
r.Config.Gateway.TargetDomain,
919+
); existing != "" {
920+
return existing
921+
}
922+
return r.Config.Gateway.GatewayDNSAddress(upstreamGateway)
923+
}
924+
925+
// managedGatewayHostnameFromStatus returns the base hostname address (not v4/v6
926+
// variants) in the target domain from gateway status, if present.
927+
func managedGatewayHostnameFromStatus(
928+
addresses []gatewayv1.GatewayStatusAddress,
929+
targetDomain string,
930+
) string {
931+
suffix := "." + targetDomain
932+
933+
for _, addr := range addresses {
934+
if ptr.Deref(addr.Type, "") != gatewayv1.HostnameAddressType {
935+
continue
936+
}
937+
if !(addr.Value == targetDomain || strings.HasSuffix(addr.Value, suffix)) {
938+
continue
939+
}
940+
if strings.HasPrefix(addr.Value, "v4.") || strings.HasPrefix(addr.Value, "v6.") {
941+
continue
942+
}
943+
return addr.Value
944+
}
945+
946+
return ""
947+
}
948+
911949
func (r *GatewayReconciler) ensureDownstreamGatewayDNSEndpoints(
912950
ctx context.Context,
913951
downstreamGateway *gatewayv1.Gateway,

internal/controller/gateway_controller_test.go

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,47 @@ func TestEnsureDownstreamGateway(t *testing.T) {
245245
}
246246
}
247247

248+
func TestPrepareUpstreamGateway_UsesExistingCanonicalHostname(t *testing.T) {
249+
testConfig := config.NetworkServicesOperator{
250+
Gateway: config.GatewayConfig{
251+
TargetDomain: "test-suite.com",
252+
},
253+
}
254+
255+
legacyUID := types.UID("11111111-1111-1111-1111-111111111111")
256+
legacyHostname := "11111111111111111111111111111111.test-suite.com"
257+
gateway := newGateway(testConfig, "test", "test", func(g *gatewayv1.Gateway) {
258+
g.UID = legacyUID
259+
g.Status.Addresses = []gatewayv1.GatewayStatusAddress{
260+
{
261+
Type: ptr.To(gatewayv1.HostnameAddressType),
262+
Value: legacyHostname,
263+
},
264+
}
265+
// Simulate an object where listener hostnames are still nil and need defaulting.
266+
for i := range g.Spec.Listeners {
267+
g.Spec.Listeners[i].Hostname = nil
268+
}
269+
})
270+
271+
reconciler := &GatewayReconciler{
272+
Config: testConfig,
273+
}
274+
275+
needsUpdate := reconciler.prepareUpstreamGateway(gateway)
276+
assert.True(t, needsUpdate, "gateway should be updated to assign listener hostnames")
277+
278+
defaultHTTPListener := gatewayutil.GetListenerByName(gateway.Spec.Listeners, gatewayutil.DefaultHTTPListenerName)
279+
require.NotNil(t, defaultHTTPListener)
280+
require.NotNil(t, defaultHTTPListener.Hostname)
281+
assert.Equal(t, gatewayv1.Hostname(legacyHostname), *defaultHTTPListener.Hostname)
282+
283+
defaultHTTPSListener := gatewayutil.GetListenerByName(gateway.Spec.Listeners, gatewayutil.DefaultHTTPSListenerName)
284+
require.NotNil(t, defaultHTTPSListener)
285+
require.NotNil(t, defaultHTTPSListener.Hostname)
286+
assert.Equal(t, gatewayv1.Hostname(legacyHostname), *defaultHTTPSListener.Hostname)
287+
}
288+
248289
func TestEnsureDownstreamGatewayWildcardCert(t *testing.T) {
249290
testScheme := runtime.NewScheme()
250291
assert.NoError(t, scheme.AddToScheme(testScheme))
@@ -1127,14 +1168,15 @@ func TestEnsureHostnamesClaimed(t *testing.T) {
11271168
)
11281169

11291170
if assert.NoError(t, err, "unexpected error calling ensureHostnameVerification") {
1130-
expectedVerifiedHostnames := append(
1131-
tt.expectedVerifiedHostnames,
1132-
testConfig.Gateway.GatewayDNSAddress(tt.upstreamGateway),
1133-
)
1134-
expectedClaimedHostnames := append(
1135-
tt.expectedClaimedHostnames,
1136-
testConfig.Gateway.GatewayDNSAddress(tt.upstreamGateway),
1137-
)
1171+
canonicalHostname := reconciler.gatewayCanonicalHostname(tt.upstreamGateway)
1172+
expectedVerifiedHostnames := slices.Clone(tt.expectedVerifiedHostnames)
1173+
if !slices.Contains(expectedVerifiedHostnames, canonicalHostname) {
1174+
expectedVerifiedHostnames = append(expectedVerifiedHostnames, canonicalHostname)
1175+
}
1176+
expectedClaimedHostnames := slices.Clone(tt.expectedClaimedHostnames)
1177+
if !slices.Contains(expectedClaimedHostnames, canonicalHostname) {
1178+
expectedClaimedHostnames = append(expectedClaimedHostnames, canonicalHostname)
1179+
}
11381180
slices.Sort(expectedVerifiedHostnames)
11391181
slices.Sort(expectedClaimedHostnames)
11401182
assert.EqualValues(t, expectedVerifiedHostnames, verifiedHostnames, "expected verified hostnames mismatch")

internal/controller/gateway_dns_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (r *GatewayReconciler) ensureDNSRecordSets(
6868
logger := log.FromContext(ctx)
6969
logger.Info("ensuring DNS record sets", "claimed_hostname_count", len(claimedHostnames))
7070

71-
canonicalHostname := r.Config.Gateway.GatewayDNSAddress(upstreamGateway)
71+
canonicalHostname := r.gatewayCanonicalHostname(upstreamGateway)
7272

7373
// List all Domains in the gateway namespace once; we query them per hostname below.
7474
var domainList networkingv1alpha.DomainList

internal/controller/gateway_dns_controller_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import (
1515
apimeta "k8s.io/apimachinery/pkg/api/meta"
1616
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1717
"k8s.io/apimachinery/pkg/runtime"
18+
"k8s.io/apimachinery/pkg/types"
1819
"k8s.io/apimachinery/pkg/util/uuid"
1920
"k8s.io/client-go/kubernetes/scheme"
21+
"k8s.io/utils/ptr"
2022
"sigs.k8s.io/controller-runtime/pkg/client"
2123
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2224
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -538,6 +540,31 @@ func TestEnsureDNSRecordSets(t *testing.T) {
538540
assert.Empty(t, list.Items)
539541
},
540542
},
543+
{
544+
name: "legacy canonical hostname remains canonical when status already set",
545+
claimedHostnames: []string{"11111111111111111111111111111111.gateways.test.local", "api.example.com"},
546+
upstreamObjects: []client.Object{
547+
newVerifiedDNSZoneDomain(ns, "example.com", false),
548+
newDNSZone(ns, "example-com", "example.com"),
549+
},
550+
assertStatuses: func(t *testing.T, statuses []networkingv1alpha.HostnameStatus) {
551+
require.Len(t, statuses, 1)
552+
assert.Equal(t, "api.example.com", statuses[0].Hostname)
553+
c := apimeta.FindStatusCondition(statuses[0].Conditions, networkingv1alpha.HostnameConditionDNSRecordProgrammed)
554+
require.NotNil(t, c)
555+
assert.Equal(t, metav1.ConditionTrue, c.Status)
556+
assert.Equal(t, networkingv1alpha.DNSRecordReasonCreated, c.Reason)
557+
},
558+
assertRecords: func(t *testing.T, cl client.Client) {
559+
var list dnsv1alpha1.DNSRecordSetList
560+
require.NoError(t, cl.List(context.Background(), &list, client.InNamespace(ns)))
561+
require.Len(t, list.Items, 1)
562+
rs := list.Items[0]
563+
require.Len(t, rs.Spec.Records, 1)
564+
require.NotNil(t, rs.Spec.Records[0].CNAME)
565+
assert.Equal(t, "11111111111111111111111111111111.gateways.test.local.", rs.Spec.Records[0].CNAME.Content)
566+
},
567+
},
541568
{
542569
name: "single-label hostname gets NotApplicable",
543570
claimedHostnames: []string{"localhost"},
@@ -580,6 +607,23 @@ func TestEnsureDNSRecordSets(t *testing.T) {
580607
s := newDNSTestScheme(t)
581608

582609
gw := newTestGatewayForDNS(ns, "test-gw")
610+
if tt.name == "legacy canonical hostname remains canonical when status already set" {
611+
gw.UID = types.UID("11111111-1111-1111-1111-111111111111")
612+
gw.Status.Addresses = []gatewayv1.GatewayStatusAddress{
613+
{
614+
Type: ptr.To(gatewayv1.HostnameAddressType),
615+
Value: "11111111111111111111111111111111.gateways.test.local",
616+
},
617+
{
618+
Type: ptr.To(gatewayv1.HostnameAddressType),
619+
Value: "v4.11111111111111111111111111111111.gateways.test.local",
620+
},
621+
{
622+
Type: ptr.To(gatewayv1.HostnameAddressType),
623+
Value: "v6.11111111111111111111111111111111.gateways.test.local",
624+
},
625+
}
626+
}
583627

584628
// Seed the gateway itself so owner reference can be set.
585629
allObjects := append([]client.Object{gw}, tt.upstreamObjects...)

0 commit comments

Comments
 (0)