88import org .springframework .cloud .client .DefaultServiceInstance ;
99import org .springframework .cloud .client .ServiceInstance ;
1010import org .springframework .cloud .client .discovery .DiscoveryClient ;
11- import org .springframework .util .Assert ;
1211
13- import java .util .ArrayList ;
14- import java .util .Collections ;
15- import java .util .List ;
16- import java .util .Optional ;
12+ import java .util .*;
1713
18- public class KubernetesDiscoveryClient implements DiscoveryClient
14+ public class KubernetesDiscoveryClient implements DiscoveryClient , SelectorEnabledDiscoveryClient
1915{
16+ private static final String defaultPortName = "http" ;
17+
2018 private static Logger log = LoggerFactory .getLogger (KubernetesDiscoveryClient .class .getName ());
2119
2220 private KubernetesClient kubeClient ;
@@ -37,45 +35,103 @@ public List<ServiceInstance> getInstances(String serviceId)
3735 {
3836 Service service ;
3937 try {
40- // API calls are automatically namespaced to the client's assigned namespace.
4138 service = kubeClient .services ().withName (serviceId ).get ();
4239 } catch (KubernetesClientException e ) {
43- log .warn ("getInstances: failed to retrieve service '{}': API call failed: {}" , serviceId , e .getMessage ());
44- return Collections .emptyList ();
40+ log .error ("getInstances: failed to retrieve service '{}': API call failed. " +
41+ "Check your K8s client configuration and account permissions." , serviceId );
42+ throw e ;
4543 }
4644
45+ // A get() return value can be null, unlike one from a list() call
4746 if (service == null ) {
4847 log .warn ("getInstances: specified service '{}' doesn't exist" , serviceId );
4948 return Collections .emptyList ();
5049 }
5150
52- if (log .isDebugEnabled ()) {
53- log .debug ("getInstances: service = {}" , service .toString ());
51+ return getInstancesFromService (service );
52+ }
53+
54+ @ Override
55+ public List <ServiceInstance > selectInstances (Map <String , String > match )
56+ {
57+ return selectInstances (match , Collections .emptyMap ());
58+ }
59+
60+ @ Override
61+ public List <ServiceInstance > selectInstances (Map <String , String > match , Map <String , String > doNotMatch )
62+ {
63+ ServiceList serviceList ;
64+ try {
65+ serviceList = kubeClient .services ().withLabels (match ).withoutLabels (doNotMatch ).list ();
66+ } catch (KubernetesClientException e ) {
67+ log .error ("selectInstances: failed to retrieve matching services: API call failed. " +
68+ "Check your K8s client configuration and account permissions." );
69+ throw e ;
5470 }
5571
56- if (service .getSpec ().getPorts ().isEmpty ()) {
57- log .error ("getInstances: service '{}' has no ports" , serviceId );
72+ // serviceList is never supposed to be null, even if the query has no results
73+ if (serviceList == null ) {
74+ log .error ("selectInstances: service list is null" );
5875 return Collections .emptyList ();
5976 }
6077
61- List < ServicePort > servicePorts = service . getSpec (). getPorts ( );
62- ServicePort svcPort = servicePorts . get ( 0 ); // By default, use the first port available
78+ return getInstancesFromServiceList ( serviceList . getItems () );
79+ }
6380
64- // But if a port named "http" is available, use it instead
65- Optional <ServicePort > httpPort = servicePorts .stream ()
66- .filter (s -> s .getName () != null && s .getName ().equals ("http" )).findFirst ();
67- if (httpPort .isPresent ()) {
68- svcPort = httpPort .get ();
81+ private List <ServiceInstance > getInstancesFromService (Service service )
82+ {
83+ return getInstancesFromServiceList (Collections .singletonList (service ));
84+ }
85+
86+ private List <ServiceInstance > getInstancesFromServiceList (List <Service > services )
87+ {
88+ if (log .isDebugEnabled ()) {
89+ log .debug ("getInstancesFromServiceList: services = {}" , services .toString ());
6990 }
7091
7192 List <ServiceInstance > serviceInstances = new ArrayList <>();
72- serviceInstances .add (new DefaultServiceInstance (
73- service .getMetadata ().getName (),
74- service .getSpec ().getClusterIP (),
75- svcPort .getPort (),
76- false ,
77- service .getMetadata ().getLabels ()
78- ));
93+ for (Service service : services ) {
94+ if (service .getSpec () == null ) {
95+ log .error ("skipping service with no spec" );
96+ continue ;
97+ }
98+
99+ String serviceName = service .getMetadata ().getName ();
100+ List <ServicePort > servicePorts = service .getSpec ().getPorts ();
101+
102+ if (servicePorts .isEmpty ()) {
103+ log .error ("service '{}' has no ports" , serviceName );
104+ continue ;
105+ }
106+
107+ ServicePort svcPort ;
108+ if (servicePorts .size () > 1 ) {
109+ Optional <ServicePort > httpPort = servicePorts .stream ()
110+ .filter (s -> s .getName () != null && s .getName ().equals (defaultPortName ))
111+ .findFirst ();
112+
113+ if (httpPort .isPresent ()) {
114+ svcPort = httpPort .get ();
115+ } else {
116+ log .warn ("getInstancesFromServiceList: multiple ports detected in '{}' " +
117+ "and named default port '{}' not found. Falling back to first port available." ,
118+ serviceName , defaultPortName );
119+ svcPort = servicePorts .get (0 );
120+ }
121+ } else {
122+ svcPort = servicePorts .get (0 );
123+ }
124+
125+ assert svcPort != null ;
126+
127+ serviceInstances .add (new DefaultServiceInstance (
128+ service .getMetadata ().getName (),
129+ service .getSpec ().getClusterIP (),
130+ svcPort .getPort (),
131+ false ,
132+ service .getMetadata ().getLabels ()
133+ ));
134+ }
79135 return serviceInstances ;
80136 }
81137
@@ -86,8 +142,9 @@ public List<String> getServices()
86142 try {
87143 serviceList = kubeClient .services ().list ();
88144 } catch (KubernetesClientException e ) {
89- log .warn ("getServices: failed to retrieve the list of services: API call failed: {}" , e .getMessage ());
90- return Collections .emptyList ();
145+ log .error ("getServices: failed to retrieve the list of services: API call failed. " +
146+ "Check your K8s client configuration and account permissions." );
147+ throw e ;
91148 }
92149
93150 List <String > serviceNames = new ArrayList <>();
0 commit comments