@@ -31,6 +31,17 @@ func nodeWithProviderID(providerID string) corev1.Node {
3131 }
3232}
3333
34+ // nodeWithExternalIP models what a cloud controller manager records for an instance that has a public IP.
35+ func nodeWithExternalIP (providerID string , externalIP string ) corev1.Node {
36+ node := nodeWithProviderID (providerID )
37+ node .Status .Addresses = append (
38+ []corev1.NodeAddress {{Type : corev1 .NodeExternalIP , Address : externalIP }},
39+ node .Status .Addresses ... ,
40+ )
41+
42+ return node
43+ }
44+
3445func TestFindNodeHostnameUsesPublicIPLookup (t * testing.T ) {
3546 t .Parallel ()
3647
@@ -45,7 +56,7 @@ func TestFindNodeHostnameUsesPublicIPLookup(t *testing.T) {
4556 return map [string ]string {testInstanceID : testPublicIP }, nil
4657 }
4758
48- hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
59+ hostname , err := k8s .FindNodeHostnameWithOptionsContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
4960 require .NoError (t , err )
5061
5162 assert .Equal (t , testPublicIP , hostname )
@@ -58,7 +69,7 @@ func TestFindNodeHostnameFallsBackWhenNoLookupConfigured(t *testing.T) {
5869
5970 options := k8s .NewKubectlOptions ("" , "" , "default" )
6071
61- hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
72+ hostname , err := k8s .FindNodeHostnameWithOptionsContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
6273 require .NoError (t , err )
6374
6475 assert .Equal (t , testHostname , hostname , "should fall back to the internal hostname when no lookup is configured" )
@@ -72,7 +83,7 @@ func TestFindNodeHostnameFallsBackWhenInstanceHasNoPublicIP(t *testing.T) {
7283 return map [string ]string {}, nil
7384 }
7485
75- hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
86+ hostname , err := k8s .FindNodeHostnameWithOptionsContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
7687 require .NoError (t , err )
7788
7889 assert .Equal (t , testHostname , hostname )
@@ -88,7 +99,7 @@ func TestFindNodeHostnameSkipsLookupForNonAWSProvider(t *testing.T) {
8899 return nil , nil
89100 }
90101
91- hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), options , nodeWithProviderID ("gce://project/us-central1-a/instance-1" ))
102+ hostname , err := k8s .FindNodeHostnameWithOptionsContextE (t , t .Context (), options , nodeWithProviderID ("gce://project/us-central1-a/instance-1" ))
92103 require .NoError (t , err )
93104
94105 assert .Equal (t , testHostname , hostname )
@@ -104,7 +115,7 @@ func TestFindNodeHostnamePropagatesLookupError(t *testing.T) {
104115 return nil , expectedErr
105116 }
106117
107- _ , err := k8s .FindNodeHostnameContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
118+ _ , err := k8s .FindNodeHostnameWithOptionsContextE (t , t .Context (), options , nodeWithProviderID (testProviderID ))
108119 require .ErrorIs (t , err , expectedErr )
109120}
110121
@@ -144,10 +155,70 @@ func TestFindNodeHostnameRejectsMalformedAwsProviderIDs(t *testing.T) {
144155 // The guard must fire before the region slice, with and without a lookup configured.
145156 for _ , opts := range []* k8s.KubectlOptions {nil , options } {
146157 require .NotPanics (t , func () {
147- _ , err := k8s .FindNodeHostnameContextE (t , t .Context (), opts , node )
158+ _ , err := k8s .FindNodeHostnameWithOptionsContextE (t , t .Context (), opts , node )
148159 require .Error (t , err )
149160 })
150161 }
151162 })
152163 }
153164}
165+
166+ // TestFindNodeHostnamePrefersExternalIPFromNode is the primary path. Cloud controller managers record an
167+ // instance's public IP as an ExternalIP address on the node object, so no cloud API call is needed and no lookup
168+ // has to be configured.
169+ func TestFindNodeHostnamePrefersExternalIPFromNode (t * testing.T ) {
170+ t .Parallel ()
171+
172+ node := nodeWithExternalIP (testProviderID , testPublicIP )
173+
174+ hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), node )
175+ require .NoError (t , err )
176+ assert .Equal (t , testPublicIP , hostname , "the node's ExternalIP must win without any options" )
177+ }
178+
179+ // TestFindNodeHostnameExternalIPSkipsTheLookup pins that the cloud API call is not made when the node already
180+ // advertises an external IP, even if a lookup is configured.
181+ func TestFindNodeHostnameExternalIPSkipsTheLookup (t * testing.T ) {
182+ t .Parallel ()
183+
184+ options := k8s .NewKubectlOptions ("" , "" , "default" )
185+ options .NodePublicIPLookup = func (_ gotesting.TestingT , _ context.Context , _ []string , _ string ) (map [string ]string , error ) {
186+ t .Fatal ("lookup must not be called when the node advertises an ExternalIP" )
187+
188+ return nil , nil
189+ }
190+
191+ hostname , err := k8s .FindNodeHostnameWithOptionsContextE (t , t .Context (), options , nodeWithExternalIP (testProviderID , testPublicIP ))
192+ require .NoError (t , err )
193+ assert .Equal (t , testPublicIP , hostname )
194+ }
195+
196+ // TestFindNodeHostnameExternalIPWorksForNonAwsProviders confirms the preference is provider agnostic, so GKE and
197+ // other clusters that advertise an ExternalIP get it too.
198+ func TestFindNodeHostnameExternalIPWorksForNonAwsProviders (t * testing.T ) {
199+ t .Parallel ()
200+
201+ hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), nodeWithExternalIP ("gce://project/zone/instance" , testPublicIP ))
202+ require .NoError (t , err )
203+ assert .Equal (t , testPublicIP , hostname )
204+ }
205+
206+ // TestFindNodeHostnameWithoutOptionsFallsBackToHostname covers an AWS node with no ExternalIP reached through the
207+ // options-free entry point: there is nothing to query with, so it degrades to the internal hostname.
208+ func TestFindNodeHostnameWithoutOptionsFallsBackToHostname (t * testing.T ) {
209+ t .Parallel ()
210+
211+ hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), nodeWithProviderID (testProviderID ))
212+ require .NoError (t , err )
213+ assert .Equal (t , testHostname , hostname )
214+ }
215+
216+ // TestFindNodeHostnameIgnoresEmptyExternalIP guards against an ExternalIP entry with a blank address shadowing the
217+ // rest of the resolution chain.
218+ func TestFindNodeHostnameIgnoresEmptyExternalIP (t * testing.T ) {
219+ t .Parallel ()
220+
221+ hostname , err := k8s .FindNodeHostnameContextE (t , t .Context (), nodeWithExternalIP (testProviderID , "" ))
222+ require .NoError (t , err )
223+ assert .Equal (t , testHostname , hostname , "a blank ExternalIP must not shadow the hostname" )
224+ }
0 commit comments