33import java .io .IOException ;
44import java .net .InetSocketAddress ;
55import java .net .SocketAddress ;
6+ import java .util .ArrayList ;
67import java .util .List ;
8+ import java .util .Map ;
79import java .util .Optional ;
810import java .util .Set ;
11+ import java .util .concurrent .ConcurrentHashMap ;
912import java .util .concurrent .Executor ;
1013import java .util .concurrent .ExecutorService ;
1114import java .util .concurrent .Executors ;
1215import java .util .concurrent .Semaphore ;
1316import java .util .logging .Level ;
1417import java .util .logging .Logger ;
18+ import java .util .stream .Collectors ;
1519
1620import io .github .lothar1998 .kuberesolver .kubernetes .EndpointSliceWatcher ;
1721import io .github .lothar1998 .kuberesolver .kubernetes .InClusterEndpointSliceWatcher ;
@@ -116,7 +120,7 @@ public KubernetesNameResolver(Executor executor, ResolverTarget params) throws I
116120 @ Override
117121 public void start (Listener listener ) {
118122 this .listener = listener ;
119- resolve ();
123+ refresh ();
120124 }
121125
122126 /**
@@ -143,6 +147,8 @@ private void resolve() {
143147 */
144148 private void watch () {
145149 watcher .watch (params .service (), new EndpointSliceWatcher .Subscriber () {
150+ private final Map <String , List <Set <SocketAddress >>> endpoints = new ConcurrentHashMap <>();
151+
146152 @ Override
147153 public void onEvent (Event event ) {
148154 // watch event occurred
@@ -152,21 +158,48 @@ public void onEvent(Event event) {
152158 return ;
153159 }
154160
155- if (event .type ().equals (EventType .DELETED )) {
156- LOGGER .log (Level .FINE , "EndpointSlice {0} was deleted" ,
157- new Object []{event .endpointSlice ().metadata ().name ()});
161+ if (event .endpointSlice () == null ) {
162+ LOGGER .log (Level .FINE , "No EndpointSlice found in watch event" );
158163 return ;
159164 }
160165
161- if (event .endpointSlice () == null ) {
162- LOGGER .log (Level .FINE , "No EndpointSlice found in watch event" );
166+ if (event .endpointSlice ().metadata () == null || event .endpointSlice ().metadata ().name () == null ) {
167+ LOGGER .log (Level .FINE , "No EndpointSlice name found in watch event metadata" );
168+ return ;
169+ }
170+
171+ if (event .type ().equals (EventType .DELETED )) {
172+ LOGGER .log (Level .FINE , "EndpointSlice {0} was deleted" ,
173+ new Object []{event .endpointSlice ().metadata ().name ()});
174+ endpoints .remove (event .endpointSlice ().metadata ().name ());
163175 return ;
164176 }
165177
166178 LOGGER .log (Level .FINER , "Resolving addresses for service {0}" , new Object []{params .service ()});
167- buildAddresses (event .endpointSlice ()).ifPresentOrElse (a -> listener .onAddresses (a , Attributes .EMPTY ),
168- () -> LOGGER .log (Level .FINE , "No usable addresses found for Kubernetes service {0}" ,
169- new Object []{params .service ()}));
179+ var endpointSliceAddresses = buildAddresses (event .endpointSlice ());
180+ if (endpointSliceAddresses .isEmpty ()) {
181+ LOGGER .log (Level .FINE , "No usable addresses found for service {0} in EndpointSlice {1}" ,
182+ new Object []{params .service (), event .endpointSlice ().metadata ().name ()});
183+ } else {
184+ LOGGER .log (Level .FINEST ,
185+ () -> String .format (
186+ "Resolved addresses for service %s from EndpointSlice %s: %s" ,
187+ params .service (),
188+ event .endpointSlice ().metadata ().name (),
189+ addressGroupsToString (endpointSliceAddresses .get ())
190+ ));
191+ endpoints .put (event .endpointSlice ().metadata ().name (), endpointSliceAddresses .get ());
192+
193+ var allAddresses = endpoints .values ().stream ()
194+ .flatMap (List ::stream )
195+ .distinct ()
196+ .toList ();
197+
198+ LOGGER .log (Level .FINEST , () -> String .format (
199+ "All resolved addresses for service %s: %s" ,
200+ params .service (), addressGroupsToString (allAddresses )));
201+ listener .onAddresses (toEquivalentAddressGroups (allAddresses ), Attributes .EMPTY );
202+ }
170203 }
171204
172205 @ Override
@@ -208,17 +241,28 @@ public String getServiceAuthority() {
208241 }
209242
210243 /**
211- * Builds a list of gRPC {@link EquivalentAddressGroup} from the given
212- * {@link EndpointSlice}.
244+ * Extracts and processes network addresses from a Kubernetes {@link EndpointSlice}.
245+ * <p>
246+ * This method performs several key steps in the address resolution process:
247+ * <ol>
248+ * <li>Finds the appropriate port to use from the EndpointSlice</li>
249+ * <li>Filters for endpoints that are in the "ready" condition</li>
250+ * <li>Maps each endpoint's IP addresses to socket addresses using the resolved port</li>
251+ * </ol>
252+ * <p>
253+ * If no suitable port can be found or if the EndpointSlice contains no ready endpoints,
254+ * an empty Optional will be returned.
213255 *
214- * @param endpointSlice the EndpointSlice to process
215- * @return an optional list of resolved addresses
256+ * @param endpointSlice the Kubernetes EndpointSlice containing endpoint information
257+ * @return an Optional containing a list of socket address sets for ready endpoints,
258+ * or an empty Optional if no addresses could be resolved
216259 */
217- private Optional <List <EquivalentAddressGroup >> buildAddresses (EndpointSlice endpointSlice ) {
260+ private Optional <List <Set < SocketAddress > >> buildAddresses (EndpointSlice endpointSlice ) {
218261 return findPort (endpointSlice .ports ())
219262 .map (port -> endpointSlice .endpoints ().stream ()
220263 .filter (endpoint -> endpoint .conditions ().isReady ())
221264 .map (endpoint -> buildAddressGroup (endpoint .addresses (), port ))
265+ .filter (group -> !group .isEmpty ())
222266 .toList ());
223267 }
224268
@@ -246,17 +290,77 @@ private Optional<Integer> findPort(List<EndpointPort> ports) {
246290 }
247291
248292 /**
249- * Builds a gRPC {@link EquivalentAddressGroup} from the given addresses and
250- * port.
293+ * Builds a set of socket addresses from a list of IP addresses and a port number.
294+ * This method converts each IP address into an {@link InetSocketAddress} using the given port,
295+ * which represents one endpoint in a Kubernetes EndpointSlice.
296+ * <p>
297+ * The resulting set of addresses is used in the name resolution process to provide
298+ * gRPC clients with possible connection endpoints for the target service.
251299 *
252- * @param addresses the list of addresses
253- * @param port the port number
254- * @return an {@link EquivalentAddressGroup} containing the resolved addresses
300+ * @param addresses the list of IP addresses from a Kubernetes endpoint
301+ * @param port the port number to use for all addresses
302+ * @return a set of {@link SocketAddress} objects representing the endpoint addresses
255303 */
256- private EquivalentAddressGroup buildAddressGroup (List <String > addresses , int port ) {
257- var socketAddresses = addresses .stream ()
304+ private Set < SocketAddress > buildAddressGroup (List <String > addresses , int port ) {
305+ return addresses .stream ()
258306 .map (address -> (SocketAddress ) new InetSocketAddress (address , port ))
307+ .collect (Collectors .toSet ());
308+ }
309+
310+ /**
311+ * Converts a list of socket address sets into a list of {@link EquivalentAddressGroup} objects.
312+ * Each set of socket addresses is transformed into a single {@link EquivalentAddressGroup},
313+ * which gRPC uses to represent a group of equivalent addresses for load balancing.
314+ *
315+ * @param addressGroups the list of socket address sets to convert
316+ * @return a list of {@link EquivalentAddressGroup} objects, each representing one set of addresses
317+ */
318+ private List <EquivalentAddressGroup > toEquivalentAddressGroups (List <Set <SocketAddress >> addressGroups ) {
319+ return addressGroups .stream ()
320+ .map (group -> new EquivalentAddressGroup (new ArrayList <>(group )))
259321 .toList ();
260- return new EquivalentAddressGroup (socketAddresses , Attributes .EMPTY );
322+ }
323+
324+ /**
325+ * Converts a list of socket address sets into a human-readable string representation.
326+ * The format is a nested structure like: [(addr1, addr2), (addr3), (addr4, addr5)]
327+ * where each set of addresses is represented as a group in parentheses.
328+ *
329+ * @param addressGroups the list of socket address sets to convert to string
330+ * @return a string representation of the address groups
331+ */
332+ private String addressGroupsToString (List <Set <SocketAddress >> addressGroups ) {
333+ if (addressGroups == null || addressGroups .isEmpty ()) {
334+ return "[]" ;
335+ }
336+
337+ var result = new StringBuilder ("[" );
338+
339+ result .append ("(" );
340+ boolean firstAddr = true ;
341+ for (SocketAddress address : addressGroups .get (0 )) {
342+ if (!firstAddr ) {
343+ result .append (", " );
344+ }
345+ result .append (address );
346+ firstAddr = false ;
347+ }
348+ result .append (")" );
349+
350+ for (int i = 1 ; i < addressGroups .size (); i ++) {
351+ result .append (", (" );
352+ firstAddr = true ;
353+ for (SocketAddress address : addressGroups .get (i )) {
354+ if (!firstAddr ) {
355+ result .append (", " );
356+ }
357+ result .append (address );
358+ firstAddr = false ;
359+ }
360+ result .append (")" );
361+ }
362+
363+ result .append ("]" );
364+ return result .toString ();
261365 }
262366}
0 commit comments