Fix 1182 (#1266)
This commit is contained in:
@@ -24,8 +24,10 @@ import java.util.function.Predicate;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
import org.springframework.core.log.LogAccessor;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
@@ -39,6 +41,8 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext;
|
||||
*/
|
||||
final class Fabric8DiscoveryServicesAdapter implements Function<KubernetesClient, List<Service>> {
|
||||
|
||||
private static final LogAccessor LOG = new LogAccessor(LogFactory.getLog(Fabric8DiscoveryServicesAdapter.class));
|
||||
|
||||
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
|
||||
|
||||
private static final SimpleEvaluationContext EVALUATION_CONTEXT = SimpleEvaluationContext.forReadOnlyDataBinding()
|
||||
@@ -65,6 +69,8 @@ final class Fabric8DiscoveryServicesAdapter implements Function<KubernetesClient
|
||||
@Override
|
||||
public List<Service> apply(KubernetesClient client) {
|
||||
if (!properties.namespaces().isEmpty()) {
|
||||
LOG.debug(() -> "searching in namespaces : " + properties.namespaces() + " with filter : "
|
||||
+ properties.filter());
|
||||
List<Service> services = new ArrayList<>();
|
||||
properties.namespaces().forEach(namespace -> services.addAll(client.services().inNamespace(namespace)
|
||||
.withLabels(properties.serviceLabels()).list().getItems().stream().filter(filter).toList()));
|
||||
|
||||
@@ -41,7 +41,7 @@ final class Fabric8EndpointsCatalogWatch
|
||||
@Override
|
||||
public List<EndpointNameAndNamespace> apply(Fabric8CatalogWatchContext context) {
|
||||
List<Endpoints> endpoints = endpoints(context.properties(), context.kubernetesClient(),
|
||||
context.namespaceProvider(), "catalog-watcher", null);
|
||||
context.namespaceProvider(), "catalog-watcher", null, x -> true);
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -187,7 +188,8 @@ final class Fabric8KubernetesDiscoveryClientUtils {
|
||||
}
|
||||
|
||||
static List<Endpoints> endpoints(KubernetesDiscoveryProperties properties, KubernetesClient client,
|
||||
KubernetesNamespaceProvider namespaceProvider, String target, @Nullable String serviceName) {
|
||||
KubernetesNamespaceProvider namespaceProvider, String target, @Nullable String serviceName,
|
||||
Predicate<Service> filter) {
|
||||
|
||||
List<Endpoints> endpoints;
|
||||
|
||||
@@ -209,7 +211,35 @@ final class Fabric8KubernetesDiscoveryClientUtils {
|
||||
serviceName);
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
return withFilter(endpoints, properties, client, filter);
|
||||
}
|
||||
|
||||
// see https://github.com/spring-cloud/spring-cloud-kubernetes/issues/1182 on why this
|
||||
// is needed
|
||||
static List<Endpoints> withFilter(List<Endpoints> initial, KubernetesDiscoveryProperties properties,
|
||||
KubernetesClient client, Predicate<Service> filter) {
|
||||
|
||||
if (properties.filter() == null || properties.filter().isBlank()) {
|
||||
LOG.debug(() -> "filter not present");
|
||||
return initial;
|
||||
}
|
||||
|
||||
List<Endpoints> result = new ArrayList<>();
|
||||
// group by namespace in order to make a single API call per namespace when
|
||||
// retrieving services
|
||||
Map<String, List<Endpoints>> byNamespace = initial.stream()
|
||||
.collect(Collectors.groupingBy(x -> x.getMetadata().getNamespace()));
|
||||
|
||||
for (Map.Entry<String, List<Endpoints>> entry : byNamespace.entrySet()) {
|
||||
Set<String> withFilter = client.services().inNamespace(entry.getKey()).list().getItems().stream()
|
||||
.filter(filter).map(service -> service.getMetadata().getName()).collect(Collectors.toSet());
|
||||
|
||||
result.addAll(
|
||||
entry.getValue().stream().filter(x -> withFilter.contains(x.getMetadata().getName())).toList());
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -131,7 +131,7 @@ public class KubernetesDiscoveryClient implements DiscoveryClient, EnvironmentAw
|
||||
}
|
||||
|
||||
public List<Endpoints> getEndPointsList(String serviceId) {
|
||||
return endpoints(properties, client, namespaceProvider, "fabric8-discovery", serviceId);
|
||||
return endpoints(properties, client, namespaceProvider, "fabric8-discovery", serviceId, adapter.filter());
|
||||
}
|
||||
|
||||
private List<ServiceInstance> getNamespaceServiceInstances(EndpointSubsetNS es, String serviceId) {
|
||||
|
||||
@@ -28,6 +28,8 @@ import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.discovery.v1.Endpoint;
|
||||
import io.fabric8.kubernetes.api.model.discovery.v1.EndpointBuilder;
|
||||
import io.fabric8.kubernetes.api.model.discovery.v1.EndpointSlice;
|
||||
@@ -305,6 +307,14 @@ abstract class Fabric8EndpointsAndEndpointSlicesTests {
|
||||
mockClient().endpoints().inNamespace(namespace).resource(endpoints).create();
|
||||
}
|
||||
|
||||
void service(String namespace, Map<String, String> labels, String podName) {
|
||||
|
||||
Service service = new ServiceBuilder()
|
||||
.withMetadata(new ObjectMetaBuilder().withLabels(labels).withName("endpoints-" + podName).build())
|
||||
.build();
|
||||
mockClient().services().inNamespace(namespace).resource(service).create();
|
||||
}
|
||||
|
||||
static void endpointSlice(String namespace, Map<String, String> labels, String podName) {
|
||||
|
||||
Endpoint endpoint = new EndpointBuilder()
|
||||
@@ -320,7 +330,8 @@ abstract class Fabric8EndpointsAndEndpointSlicesTests {
|
||||
static void invokeAndAssert(KubernetesCatalogWatch watch, List<EndpointNameAndNamespace> state) {
|
||||
watch.catalogServicesWatch();
|
||||
|
||||
verify(APPLICATION_EVENT_PUBLISHER).publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture());
|
||||
verify(APPLICATION_EVENT_PUBLISHER, Mockito.atLeastOnce())
|
||||
.publishEvent(HEARTBEAT_EVENT_ARGUMENT_CAPTOR.capture());
|
||||
|
||||
HeartbeatEvent event = HEARTBEAT_EVENT_ARGUMENT_CAPTOR.getValue();
|
||||
assertThat(event.getValue()).isInstanceOf(List.class);
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Set;
|
||||
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.EndpointNameAndNamespace;
|
||||
@@ -38,6 +39,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
|
||||
private static KubernetesClient mockClient;
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
mockClient.endpoints().inAnyNamespace().delete();
|
||||
mockClient.services().inAnyNamespace().delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
void testInSpecificNamespaceWithServiceLabels() {
|
||||
@@ -51,6 +58,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podD");
|
||||
endpoints("namespaceB", Map.of(), "podE");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
|
||||
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA")));
|
||||
}
|
||||
|
||||
@@ -67,6 +80,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podD");
|
||||
endpoints("namespaceB", Map.of(), "podE");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
|
||||
invokeAndAssert(watch,
|
||||
List.of(new EndpointNameAndNamespace("podA", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podB", "namespaceA"),
|
||||
@@ -86,6 +105,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podD");
|
||||
endpoints("namespaceB", Map.of(), "podE");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
|
||||
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podD", "namespaceB")));
|
||||
}
|
||||
@@ -102,6 +127,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podD");
|
||||
endpoints("namespaceB", Map.of(), "podE");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
|
||||
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podA", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podB", "namespaceA"), new EndpointNameAndNamespace("podC", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podD", "namespaceB"),
|
||||
@@ -121,6 +152,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podD");
|
||||
endpoints("namespaceB", Map.of(), "podE");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
|
||||
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podD", "namespaceB")));
|
||||
}
|
||||
@@ -138,6 +175,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podD");
|
||||
endpoints("namespaceB", Map.of(), "podE");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
|
||||
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA")));
|
||||
}
|
||||
|
||||
@@ -154,6 +197,12 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podD");
|
||||
endpoints("namespaceB", Map.of(), "podE");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
|
||||
invokeAndAssert(watch, List.of(new EndpointNameAndNamespace("podB", "namespaceA")));
|
||||
}
|
||||
|
||||
@@ -172,6 +221,14 @@ class Fabric8KubernetesCatalogWatchEndpointsTests extends Fabric8EndpointsAndEnd
|
||||
endpoints("namespaceB", Map.of("color", "blue"), "podF");
|
||||
endpoints("namespaceC", Map.of("color", "blue"), "podO");
|
||||
|
||||
service("namespaceA", Map.of(), "podA");
|
||||
service("namespaceA", Map.of("color", "blue"), "podB");
|
||||
service("namespaceA", Map.of("color", "red"), "podC");
|
||||
service("namespaceB", Map.of("color", "blue"), "podD");
|
||||
service("namespaceB", Map.of(), "podE");
|
||||
service("namespaceB", Map.of("color", "blue"), "podF");
|
||||
service("namespaceC", Map.of("color", "blue"), "podO");
|
||||
|
||||
invokeAndAssert(watch,
|
||||
List.of(new EndpointNameAndNamespace("podB", "namespaceA"),
|
||||
new EndpointNameAndNamespace("podD", "namespaceB"),
|
||||
|
||||
@@ -61,7 +61,8 @@ class Fabric8KubernetesDiscoveryClientTest {
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
mockClient.close();
|
||||
mockClient.endpoints().inAnyNamespace().delete();
|
||||
mockClient.services().inAnyNamespace().delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,8 +129,11 @@ class Fabric8KubernetesDiscoveryClientTest {
|
||||
.withLabels(labels).endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef()
|
||||
.withUid("30").endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP").endSubset()
|
||||
.build();
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
.endMetadata().build();
|
||||
|
||||
mockClient.endpoints().inNamespace("test").resource(endPoint).create();
|
||||
mockClient.services().inNamespace("test").resource(service).create();
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient,
|
||||
KubernetesDiscoveryProperties.DEFAULT, KubernetesClient::services, null,
|
||||
@@ -148,18 +152,24 @@ class Fabric8KubernetesDiscoveryClientTest {
|
||||
|
||||
Endpoints endPoint1 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
|
||||
.endMetadata().build();
|
||||
|
||||
Endpoints endPoint2 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
|
||||
.endMetadata().build();
|
||||
|
||||
Service service1 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
|
||||
.endMetadata().build();
|
||||
Service service2 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
|
||||
.endMetadata().build();
|
||||
|
||||
mockClient.endpoints().inNamespace(namespace1).resource(endPoint1).create();
|
||||
mockClient.endpoints().inNamespace(namespace2).resource(endPoint2).create();
|
||||
mockClient.services().inNamespace(namespace1).resource(service1).create();
|
||||
mockClient.services().inNamespace(namespace2).resource(service2).create();
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, true, Set.of(), true, 60,
|
||||
false, null, Set.of(), Map.of(), null, KubernetesDiscoveryProperties.Metadata.DEFAULT, 0, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(mockClient, properties,
|
||||
KubernetesClient::services, null, new ServicePortSecureResolver(properties));
|
||||
KubernetesClient::services, x -> true, new ServicePortSecureResolver(properties));
|
||||
|
||||
List<Endpoints> result_endpoints = discoveryClient.getEndPointsList("endpoint");
|
||||
|
||||
@@ -180,9 +190,19 @@ class Fabric8KubernetesDiscoveryClientTest {
|
||||
Endpoints endPoint3 = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace3)
|
||||
.endMetadata().build();
|
||||
|
||||
Service service1 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace1)
|
||||
.endMetadata().build();
|
||||
Service service2 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace2)
|
||||
.endMetadata().build();
|
||||
Service service3 = new ServiceBuilder().withNewMetadata().withName("endpoint").withNamespace(namespace3)
|
||||
.endMetadata().build();
|
||||
|
||||
mockClient.endpoints().inNamespace(namespace1).resource(endPoint1).create();
|
||||
mockClient.endpoints().inNamespace(namespace2).resource(endPoint2).create();
|
||||
mockClient.endpoints().inNamespace(namespace3).resource(endPoint3).create();
|
||||
mockClient.services().inNamespace(namespace1).resource(service1).create();
|
||||
mockClient.services().inNamespace(namespace2).resource(service2).create();
|
||||
mockClient.services().inNamespace(namespace3).resource(service3).create();
|
||||
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, false,
|
||||
Set.of(namespace1, namespace3), true, 60, false, null, Set.of(), Map.of(), null,
|
||||
|
||||
@@ -93,6 +93,7 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
void testAllNamespacesSingleEndpointsMatchExactLabels(CapturedOutput output) {
|
||||
|
||||
createEndpoints("default", "blue-service", Map.of("color", "blue"));
|
||||
createService("default", "blue-service", Map.of("color", "blue"));
|
||||
|
||||
boolean allNamespaces = true;
|
||||
Set<String> namespaces = Set.of();
|
||||
@@ -118,6 +119,7 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
void testAllNamespacesSingleEndpointsMatchPartialLabels(CapturedOutput output) {
|
||||
|
||||
createEndpoints("default", "blue-service", Map.of("color", "blue", "shape", "round"));
|
||||
createService("default", "blue-service", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
boolean allNamespaces = true;
|
||||
Set<String> namespaces = Set.of();
|
||||
@@ -170,6 +172,9 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
createEndpoints("default", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createEndpoints("default", "service-two", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
createService("default", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createService("default", "service-two", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
boolean allNamespaces = true;
|
||||
Set<String> namespaces = Set.of();
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
@@ -196,6 +201,9 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
createEndpoints("a", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createEndpoints("b", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
createService("a", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createService("b", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
boolean allNamespaces = true;
|
||||
Set<String> namespaces = Set.of();
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
@@ -244,6 +252,7 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
void testClientNamespaceSingleEndpointsMatchExactLabels(CapturedOutput output) {
|
||||
|
||||
createEndpoints("test", "blue-service", Map.of("color", "blue"));
|
||||
createService("test", "blue-service", Map.of("color", "blue"));
|
||||
|
||||
boolean allNamespaces = false;
|
||||
Set<String> namespaces = Set.of();
|
||||
@@ -269,6 +278,7 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
void testClientNamespaceSingleEndpointsMatchPartialLabels(CapturedOutput output) {
|
||||
|
||||
createEndpoints("test", "blue-service", Map.of("color", "blue", "shape", "round"));
|
||||
createService("test", "blue-service", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
boolean allNamespaces = false;
|
||||
Set<String> namespaces = Set.of();
|
||||
@@ -321,6 +331,9 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
createEndpoints("test", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createEndpoints("test", "service-two", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
createService("test", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createService("test", "service-two", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
boolean allNamespaces = false;
|
||||
Set<String> namespaces = Set.of();
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
@@ -347,6 +360,9 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
createEndpoints("test", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createEndpoints("b", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
createService("test", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
createService("b", "service-one", Map.of("color", "blue", "shape", "round"));
|
||||
|
||||
boolean allNamespaces = false;
|
||||
Set<String> namespaces = Set.of();
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
@@ -395,6 +411,7 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
void testSelectiveNamespacesSingleEndpointsMatchExactLabels(CapturedOutput output) {
|
||||
|
||||
createEndpoints("test", "blue-service", Map.of("color", "blue"));
|
||||
createService("test", "blue-service", Map.of("color", "blue"));
|
||||
|
||||
boolean allNamespaces = false;
|
||||
Set<String> namespaces = Set.of("test");
|
||||
@@ -402,7 +419,8 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
KubernetesDiscoveryProperties properties = new KubernetesDiscoveryProperties(true, allNamespaces, namespaces,
|
||||
true, 60L, false, "", Set.of(), serviceLabels, "", null, 0, false);
|
||||
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, null, null);
|
||||
KubernetesDiscoveryClient discoveryClient = new KubernetesDiscoveryClient(client, properties, null, x -> true,
|
||||
null);
|
||||
List<Endpoints> result = discoveryClient.getEndPointsList("blue-service");
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertTrue(output.getOut().contains("discovering endpoints in namespaces : [test]"));
|
||||
@@ -423,6 +441,9 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
createEndpoints("a", "blue-service", Map.of("color", "blue", "shape", "round"));
|
||||
createEndpoints("b", "blue-service", Map.of("color", "blue", "shape", "rectangle"));
|
||||
|
||||
createService("a", "blue-service", Map.of("color", "blue", "shape", "round"));
|
||||
createService("b", "blue-service", Map.of("color", "blue", "shape", "rectangle"));
|
||||
|
||||
boolean allNamespaces = false;
|
||||
Set<String> namespaces = Set.of("a");
|
||||
Map<String, String> serviceLabels = Map.of("color", "blue");
|
||||
@@ -451,6 +472,9 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
createEndpoints("a", "blue-service", Map.of("color", "blue"));
|
||||
createEndpoints("b", "blue-service", Map.of("color", "blue"));
|
||||
|
||||
createService("a", "blue-service", Map.of("color", "blue"));
|
||||
createService("b", "blue-service", Map.of("color", "blue"));
|
||||
|
||||
boolean allNamespaces = false;
|
||||
Set<String> namespaces = Set.of("a", "b");
|
||||
// so that assertion is correct
|
||||
@@ -564,4 +588,11 @@ class Fabric8KubernetesDiscoveryClientTests {
|
||||
.create();
|
||||
}
|
||||
|
||||
private void createService(String namespace, String name, Map<String, String> labels) {
|
||||
client.services().inNamespace(namespace)
|
||||
.resource(new ServiceBuilder()
|
||||
.withMetadata(new ObjectMetaBuilder().withName(name).withLabels(labels).build()).build())
|
||||
.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.discovery;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.EnableKubernetesMockClient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
|
||||
|
||||
/**
|
||||
* @author wind57
|
||||
*/
|
||||
@EnableKubernetesMockClient(crud = true, https = false)
|
||||
class Fabric8KubernetesDiscoveryClientUtilsFilterTests {
|
||||
|
||||
private static KubernetesClient client;
|
||||
|
||||
private static final KubernetesDiscoveryProperties PROPERTIES = new KubernetesDiscoveryProperties(true, true,
|
||||
Set.of(), false, 60L, false, "some", Set.of(), Map.of(), "", null, 0, false);
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
client.endpoints().inAnyNamespace().delete();
|
||||
client.services().inAnyNamespace().delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void withFilterEmptyInput() {
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(), PROPERTIES, client,
|
||||
x -> true);
|
||||
Assertions.assertEquals(result.size(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - Endpoints with name : "a" and namespace "namespace-a", present
|
||||
* - Service with name "a" and namespace "namespace-not-a" present
|
||||
*
|
||||
* As such, there is no match, empty result.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void withFilterOneEndpointsNoMatchInService() {
|
||||
Endpoints endpoints = createEndpoints("a", "namespace-a");
|
||||
createService("a", "namespace-not-a");
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpoints), PROPERTIES,
|
||||
client, x -> true);
|
||||
Assertions.assertEquals(result.size(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - Endpoints with name : "a" and namespace "namespace-a", present
|
||||
* - Service with name "a" and namespace "namespace-a" present
|
||||
*
|
||||
* As such, there is a match.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void withFilterOneEndpointsMatchInService() {
|
||||
Endpoints endpoints = createEndpoints("a", "namespace-a");
|
||||
createService("a", "namespace-a");
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpoints), PROPERTIES,
|
||||
client, x -> true);
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - Endpoints with name : "a" and namespace "namespace-a", present
|
||||
* - Endpoints with name : "b" and namespace "namespace-b", present
|
||||
* - Service with name "a" and namespace "namespace-a" present
|
||||
*
|
||||
* As such, there is a match, single endpoints as result.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void withFilterTwoEndpointsOneMatchInService() {
|
||||
Endpoints endpointsA = createEndpoints("a", "namespace-a");
|
||||
Endpoints endpointsB = createEndpoints("b", "namespace-b");
|
||||
createService("a", "namespace-a");
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA, endpointsB),
|
||||
PROPERTIES, client, x -> true);
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a");
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "namespace-a");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - Endpoints with name : "a" and namespace "namespace-a", present
|
||||
* - Endpoints with name : "b" and namespace "namespace-b", present
|
||||
* - Service with name "a" and namespace "namespace-a" present
|
||||
* - Service with name "b" and namespace "namespace-b" present
|
||||
* - Service with name "c" and namespace "namespace-c" present
|
||||
*
|
||||
* As such, there are two matches.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void withFilterTwoEndpointsAndThreeServices() {
|
||||
Endpoints endpointsA = createEndpoints("a", "namespace-a");
|
||||
Endpoints endpointsB = createEndpoints("b", "namespace-b");
|
||||
createService("a", "namespace-a");
|
||||
createService("b", "namespace-b");
|
||||
createService("c", "namespace-c");
|
||||
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA, endpointsB),
|
||||
PROPERTIES, client, x -> true);
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
result = result.stream().sorted(Comparator.comparing(x -> x.getMetadata().getName())).toList();
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a");
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "namespace-a");
|
||||
Assertions.assertEquals(result.get(1).getMetadata().getName(), "b");
|
||||
Assertions.assertEquals(result.get(1).getMetadata().getNamespace(), "namespace-b");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - Endpoints with name : "a" and namespace "namespace-a", present
|
||||
* - Endpoints with name : "b" and namespace "namespace-b", present
|
||||
* - Service with name "a" and namespace "namespace-a" present
|
||||
* - Service with name "b" and namespace "namespace-b" present
|
||||
* - Service with name "c" and namespace "namespace-c" present
|
||||
*
|
||||
* As such, there are two matches.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void withFilterSingleEndpointsMatchesFilter() {
|
||||
Endpoints endpointsA = createEndpoints("a", "namespace-a");
|
||||
createService("a", "namespace-a");
|
||||
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA), PROPERTIES,
|
||||
client, x -> x.getMetadata().getNamespace().equals("namespace-a"));
|
||||
Assertions.assertEquals(result.size(), 1);
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a");
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "namespace-a");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - Endpoints with name : "a-1" and namespace "default", present
|
||||
* - Endpoints with name : "b-1" and namespace "default", present
|
||||
* - Endpoints with name : "c-2" and namespace "default", present
|
||||
* - Service with name "a-1" and namespace "default" present
|
||||
* - Service with name "b-1" and namespace "default" present
|
||||
*
|
||||
* As such, there are two matches.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void withFilterTwoEndpointsMatchesFilter() {
|
||||
Endpoints endpointsA = createEndpoints("a-1", "default");
|
||||
Endpoints endpointsB = createEndpoints("b-1", "default");
|
||||
Endpoints endpointsC = createEndpoints("c-2", "default");
|
||||
createService("a-1", "default");
|
||||
createService("b-1", "default");
|
||||
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(
|
||||
List.of(endpointsA, endpointsB, endpointsC), PROPERTIES, client,
|
||||
x -> x.getMetadata().getName().contains("1"));
|
||||
Assertions.assertEquals(result.size(), 2);
|
||||
result = result.stream().sorted(Comparator.comparing(x -> x.getMetadata().getName())).toList();
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getName(), "a-1");
|
||||
Assertions.assertEquals(result.get(0).getMetadata().getNamespace(), "default");
|
||||
Assertions.assertEquals(result.get(1).getMetadata().getName(), "b-1");
|
||||
Assertions.assertEquals(result.get(1).getMetadata().getNamespace(), "default");
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - Endpoints with name : "a" and namespace "default", present
|
||||
* - Service with name "a-1" and namespace "default" present
|
||||
* - Service with name "b-1" and namespace "default" present
|
||||
*
|
||||
* As such, there are two matches.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void withFilterSingleEndpointsNoPredicateMatch() {
|
||||
Endpoints endpointsA = createEndpoints("a", "default");
|
||||
createService("a-1", "default");
|
||||
createService("b-1", "default");
|
||||
|
||||
List<Endpoints> result = Fabric8KubernetesDiscoveryClientUtils.withFilter(List.of(endpointsA), PROPERTIES,
|
||||
client, x -> !x.getMetadata().getName().contains("1"));
|
||||
Assertions.assertEquals(result.size(), 0);
|
||||
}
|
||||
|
||||
private Endpoints createEndpoints(String name, String namespace) {
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName(name).withNamespace(namespace)
|
||||
.endMetadata().build();
|
||||
client.endpoints().inNamespace(namespace).resource(endpoints).create();
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private void createService(String name, String namespace) {
|
||||
Service service = new ServiceBuilder().withNewMetadata().withName(name).withNamespace(namespace).endMetadata()
|
||||
.build();
|
||||
client.services().inNamespace(namespace).resource(service).create();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,14 +25,21 @@ import java.util.stream.Collectors;
|
||||
import io.fabric8.kubernetes.api.model.EndpointAddress;
|
||||
import io.fabric8.kubernetes.api.model.EndpointSubset;
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsList;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ObjectReference;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceList;
|
||||
import io.fabric8.kubernetes.api.model.ServiceListBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.dsl.FilterNested;
|
||||
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
|
||||
import io.fabric8.kubernetes.client.dsl.MixedOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
|
||||
import io.fabric8.kubernetes.client.dsl.Resource;
|
||||
import io.fabric8.kubernetes.client.dsl.ServiceResource;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -92,8 +99,10 @@ class KubernetesCatalogWatchTest {
|
||||
createInSpecificNamespaceWatcher();
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list())
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("api-pod", "other-pod"))
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("other-pod", "api-pod"));
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("test", "api-pod", "other-pod"))
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("test", "other-pod", "api-pod"));
|
||||
mockServicesCall("api-pod", "test");
|
||||
mockServicesCall("other-pod", "test");
|
||||
|
||||
kubernetesCatalogWatch.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -108,8 +117,11 @@ class KubernetesCatalogWatchTest {
|
||||
createInAllNamespaceWatcher();
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list())
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("api-pod", "other-pod"))
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("other-pod", "api-pod"));
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("test", "api-pod", "other-pod"))
|
||||
.thenReturn(createSingleEndpointEndpointListByPodName("test", "other-pod", "api-pod"));
|
||||
|
||||
mockServicesCall("api-pod", "test");
|
||||
mockServicesCall("other-pod", "test");
|
||||
|
||||
kubernetesCatalogWatch.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -124,8 +136,10 @@ class KubernetesCatalogWatchTest {
|
||||
createInSpecificNamespaceWatcher();
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list())
|
||||
.thenReturn(createEndpointsListByServiceName("api-service", "other-service"))
|
||||
.thenReturn(createEndpointsListByServiceName("other-service", "api-service"));
|
||||
.thenReturn(createEndpointsListByServiceName("test", "api-service", "other-service"))
|
||||
.thenReturn(createEndpointsListByServiceName("test", "other-service", "api-service"));
|
||||
mockServicesCall("api-service", "test");
|
||||
mockServicesCall("other-service", "test");
|
||||
|
||||
kubernetesCatalogWatch.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -140,8 +154,10 @@ class KubernetesCatalogWatchTest {
|
||||
createInAllNamespaceWatcher();
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list())
|
||||
.thenReturn(createEndpointsListByServiceName("api-service", "other-service"))
|
||||
.thenReturn(createEndpointsListByServiceName("other-service", "api-service"));
|
||||
.thenReturn(createEndpointsListByServiceName("test", "api-service", "other-service"))
|
||||
.thenReturn(createEndpointsListByServiceName("test", "other-service", "api-service"));
|
||||
mockServicesCall("api-service", "test");
|
||||
mockServicesCall("other-service", "test");
|
||||
|
||||
kubernetesCatalogWatch.catalogServicesWatch();
|
||||
// second execution on shuffleServices
|
||||
@@ -156,7 +172,9 @@ class KubernetesCatalogWatchTest {
|
||||
createInSpecificNamespaceWatcher();
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list())
|
||||
.thenReturn(createSingleEndpointListWithNamespace("default", "api-pod", "other-pod"));
|
||||
.thenReturn(createSingleEndpointListWithNamespace("default", "other-pod", "api-pod", "other-pod"));
|
||||
mockServicesCall("api-pod", "default");
|
||||
mockServicesCall("other-pod", "default");
|
||||
|
||||
kubernetesCatalogWatch.catalogServicesWatch();
|
||||
|
||||
@@ -176,7 +194,9 @@ class KubernetesCatalogWatchTest {
|
||||
createInAllNamespaceWatcher();
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list())
|
||||
.thenReturn(createSingleEndpointListWithNamespace("default", "api-pod", "other-pod"));
|
||||
.thenReturn(createSingleEndpointListWithNamespace("default", "other-pod", "api-pod", "other-pod"));
|
||||
mockServicesCall("api-pod", "default");
|
||||
mockServicesCall("other-pod", "default");
|
||||
|
||||
kubernetesCatalogWatch.catalogServicesWatch();
|
||||
|
||||
@@ -195,8 +215,8 @@ class KubernetesCatalogWatchTest {
|
||||
|
||||
createInSpecificNamespaceWatcher();
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets();
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets("name", "test");
|
||||
mockServicesCall("name", "test");
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
|
||||
|
||||
kubernetesCatalogWatch.catalogServicesWatch();
|
||||
@@ -211,7 +231,8 @@ class KubernetesCatalogWatchTest {
|
||||
|
||||
createInAllNamespaceWatcher();
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets();
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListWithoutSubsets("name", "test");
|
||||
mockServicesCall("name", "test");
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
|
||||
|
||||
@@ -227,7 +248,8 @@ class KubernetesCatalogWatchTest {
|
||||
|
||||
createInSpecificNamespaceWatcher();
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
|
||||
mockServicesCall("api-pod", "test");
|
||||
endpoints.getItems().get(0).getSubsets().get(0).setAddresses(null);
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
|
||||
@@ -244,7 +266,8 @@ class KubernetesCatalogWatchTest {
|
||||
|
||||
createInAllNamespaceWatcher();
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
|
||||
mockServicesCall("api-pod", "test");
|
||||
endpoints.getItems().get(0).getSubsets().get(0).setAddresses(null);
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
|
||||
@@ -261,7 +284,8 @@ class KubernetesCatalogWatchTest {
|
||||
|
||||
createInSpecificNamespaceWatcher();
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
|
||||
mockServicesCall("api-pod", "test");
|
||||
endpoints.getItems().get(0).getSubsets().get(0).getAddresses().get(0).setTargetRef(null);
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
|
||||
@@ -278,7 +302,8 @@ class KubernetesCatalogWatchTest {
|
||||
|
||||
createInAllNamespaceWatcher();
|
||||
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("api-pod");
|
||||
EndpointsList endpoints = createSingleEndpointEndpointListByPodName("test", "api-pod");
|
||||
mockServicesCall("api-pod", "test");
|
||||
endpoints.getItems().get(0).getSubsets().get(0).getAddresses().get(0).setTargetRef(null);
|
||||
|
||||
when(FILTER_WATCH_LIST_DELETABLE.list()).thenReturn(endpoints);
|
||||
@@ -290,34 +315,38 @@ class KubernetesCatalogWatchTest {
|
||||
verify(APPLICATION_EVENT_PUBLISHER).publishEvent(any(HeartbeatEvent.class));
|
||||
}
|
||||
|
||||
private EndpointsList createEndpointsListByServiceName(String... serviceNames) {
|
||||
List<Endpoints> endpoints = stream(serviceNames).map(s -> createEndpointsByPodName(s + "-singlePodUniqueId"))
|
||||
.collect(Collectors.toList());
|
||||
private EndpointsList createEndpointsListByServiceName(String namespace, String... serviceNames) {
|
||||
List<Endpoints> endpoints = stream(serviceNames)
|
||||
.map(s -> createEndpointsByPodName(namespace, s + "-singlePodUniqueId")).collect(Collectors.toList());
|
||||
|
||||
EndpointsList endpointsList = new EndpointsList();
|
||||
endpointsList.setItems(endpoints);
|
||||
return endpointsList;
|
||||
}
|
||||
|
||||
private EndpointsList createSingleEndpointEndpointListWithoutSubsets() {
|
||||
Endpoints endpoints = new Endpoints();
|
||||
private EndpointsList createSingleEndpointEndpointListWithoutSubsets(String name, String namespace) {
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName(name).withNamespace(namespace)
|
||||
.endMetadata().build();
|
||||
|
||||
EndpointsList endpointsList = new EndpointsList();
|
||||
endpointsList.setItems(Collections.singletonList(endpoints));
|
||||
return endpointsList;
|
||||
}
|
||||
|
||||
private EndpointsList createSingleEndpointEndpointListByPodName(String... podNames) {
|
||||
private EndpointsList createSingleEndpointEndpointListByPodName(String namespace, String... podNames) {
|
||||
Endpoints endpoints = new Endpoints();
|
||||
endpoints.setSubsets(createSubsetsByPodName(podNames));
|
||||
endpoints.setMetadata(new ObjectMetaBuilder().withNamespace(namespace).build());
|
||||
|
||||
EndpointsList endpointsList = new EndpointsList();
|
||||
endpointsList.setItems(Collections.singletonList(endpoints));
|
||||
return endpointsList;
|
||||
}
|
||||
|
||||
private EndpointsList createSingleEndpointListWithNamespace(String namespace, String... podNames) {
|
||||
Endpoints endpoints = new Endpoints();
|
||||
private EndpointsList createSingleEndpointListWithNamespace(String namespace, String endpointsName,
|
||||
String... podNames) {
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withNamespace(namespace).withName(endpointsName)
|
||||
.and().build();
|
||||
endpoints.setSubsets(createSubsetsWithNamespace(namespace, podNames));
|
||||
|
||||
EndpointsList endpointsList = new EndpointsList();
|
||||
@@ -325,8 +354,8 @@ class KubernetesCatalogWatchTest {
|
||||
return endpointsList;
|
||||
}
|
||||
|
||||
private Endpoints createEndpointsByPodName(String podName) {
|
||||
Endpoints endpoints = new Endpoints();
|
||||
private Endpoints createEndpointsByPodName(String namespace, String podName) {
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withNamespace(namespace).and().build();
|
||||
endpoints.setSubsets(createSubsetsByPodName(podName));
|
||||
return endpoints;
|
||||
}
|
||||
@@ -401,4 +430,16 @@ class KubernetesCatalogWatchTest {
|
||||
when(FILTER_NESTED.endFilter()).thenReturn(FILTER_WATCH_LIST_DELETABLE);
|
||||
}
|
||||
|
||||
private void mockServicesCall(String name, String namespace) {
|
||||
MixedOperation<Service, ServiceList, ServiceResource<Service>> mixedOperation = Mockito
|
||||
.mock(MixedOperation.class);
|
||||
NonNamespaceOperation<Service, ServiceList, ServiceResource<Service>> nonNamespaceOperation = Mockito
|
||||
.mock(NonNamespaceOperation.class);
|
||||
when(CLIENT.services()).thenReturn(mixedOperation);
|
||||
when(mixedOperation.inNamespace(namespace)).thenReturn(nonNamespaceOperation);
|
||||
when(nonNamespaceOperation.list()).thenReturn(new ServiceListBuilder().withItems(
|
||||
new ServiceBuilder().withNewMetadata().withName(name).withNamespace(namespace).endMetadata().build())
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceList;
|
||||
import io.fabric8.kubernetes.api.model.ServiceListBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServicePort;
|
||||
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ServiceSpecBuilder;
|
||||
@@ -245,9 +246,12 @@ class KubernetesDiscoveryClientFilterMetadataTest {
|
||||
when(serviceResource.get()).thenReturn(service);
|
||||
when(CLIENT.services()).thenReturn(serviceOperation);
|
||||
when(CLIENT.services().inNamespace(anyString())).thenReturn(serviceOperation);
|
||||
when(serviceOperation.list()).thenReturn(new ServiceListBuilder().withItems(new ServiceBuilder()
|
||||
.withNewMetadata().withName(serviceId).withNamespace(namespace).endMetadata().build()).build());
|
||||
|
||||
ObjectMeta objectMeta = new ObjectMeta();
|
||||
objectMeta.setNamespace(namespace);
|
||||
objectMeta.setName(serviceId);
|
||||
|
||||
Endpoints endpoints = new EndpointsBuilder().withMetadata(objectMeta).addNewSubset()
|
||||
.addAllToPorts(getEndpointPorts(ports)).addNewAddress().endAddress().endSubset().build();
|
||||
|
||||
@@ -158,7 +158,8 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services/existing-service")
|
||||
.andReturn(200, services.getItems().get(0)).once();
|
||||
|
||||
Metadata metadata = new Metadata(false, null, false, null, true, "port.");
|
||||
kubernetesServer.expect().get().withPath("/api/v1/namespaces/test/services").andReturn(200, services).once();
|
||||
|
||||
ReactiveDiscoveryClient client = new KubernetesReactiveDiscoveryClient(kubernetesClient,
|
||||
KubernetesDiscoveryProperties.DEFAULT, KubernetesClient::services);
|
||||
Flux<ServiceInstance> instances = client.getInstances("existing-service");
|
||||
@@ -174,7 +175,7 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
|
||||
.once();
|
||||
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("existing-service").withNamespace("test")
|
||||
.endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP").endSubset().build();
|
||||
|
||||
@@ -208,7 +209,7 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
|
||||
.once();
|
||||
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
Endpoints endPoint = new EndpointsBuilder().withNewMetadata().withName("existing-service").withNamespace("test")
|
||||
.endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.addNewPort("https", "https_tcp", 443, "TCP").endSubset().build();
|
||||
@@ -243,9 +244,9 @@ class KubernetesReactiveDiscoveryClientTests {
|
||||
.withSpec(new ServiceSpecBuilder().withType("ExternalName").build()).endItem().build())
|
||||
.once();
|
||||
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName("endpoint").withNamespace("test")
|
||||
.endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef().withUid("uid1")
|
||||
.endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
Endpoints endpoints = new EndpointsBuilder().withNewMetadata().withName("existing-service")
|
||||
.withNamespace("test").endMetadata().addNewSubset().addNewAddress().withIp("ip1").withNewTargetRef()
|
||||
.withUid("uid1").endTargetRef().endAddress().addNewPort("http", "http_tcp", 80, "TCP")
|
||||
.addNewPort("https", "https_tcp", 443, "TCP").endSubset().build();
|
||||
|
||||
EndpointsList endpointsList = new EndpointsList();
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.kubernetes.fabric8.configmap;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.k3s.K3sContainer;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
import org.springframework.cloud.kubernetes.commons.discovery.DefaultKubernetesServiceInstance;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Commons;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.Phase;
|
||||
import org.springframework.cloud.kubernetes.integration.tests.commons.fabric8_client.Util;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
class Fabric8DiscoveryFilterIT {
|
||||
|
||||
private static final String FILTER_BOTH_NAMESPACES = "#root.metadata.namespace matches '^.*uat$'";
|
||||
|
||||
private static final String FILTER_SINGLE_NAMESPACE = "#root.metadata.namespace matches 'a-uat$'";
|
||||
|
||||
private static final String NAMESPACE_A_UAT = "a-uat";
|
||||
|
||||
private static final String NAMESPACE_B_UAT = "b-uat";
|
||||
|
||||
private static final String NAMESPACE = "default";
|
||||
|
||||
private static final String IMAGE_NAME = "spring-cloud-kubernetes-fabric8-client-discovery";
|
||||
|
||||
private static KubernetesClient client;
|
||||
|
||||
private static Util util;
|
||||
|
||||
private static final K3sContainer K3S = Commons.container();
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws Exception {
|
||||
K3S.start();
|
||||
Commons.validateImage(IMAGE_NAME, K3S);
|
||||
Commons.loadSpringCloudKubernetesImage(IMAGE_NAME, K3S);
|
||||
|
||||
util = new Util(K3S);
|
||||
client = util.client();
|
||||
util.setUp(NAMESPACE);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
util.createNamespace(NAMESPACE_A_UAT);
|
||||
util.createNamespace(NAMESPACE_B_UAT);
|
||||
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.CREATE);
|
||||
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.CREATE);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() {
|
||||
util.wiremock(NAMESPACE_A_UAT, "/wiremock", Phase.DELETE);
|
||||
util.wiremock(NAMESPACE_B_UAT, "/wiremock", Phase.DELETE);
|
||||
util.deleteNamespace(NAMESPACE_A_UAT);
|
||||
util.deleteNamespace(NAMESPACE_B_UAT);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void after() throws Exception {
|
||||
Commons.cleanUp(IMAGE_NAME, K3S);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - service "wiremock" is present in namespace "a-uat"
|
||||
* - service "wiremock" is present in namespace "b-uat"
|
||||
*
|
||||
* - we search with a predicate : "#root.metadata.namespace matches '^uat.*$'"
|
||||
*
|
||||
* As such, both services are found via 'getInstances' call.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void filterMatchesBothNamespacesViaThePredicate() {
|
||||
|
||||
manifests(Phase.CREATE, FILTER_BOTH_NAMESPACES);
|
||||
|
||||
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(services.size(), 1);
|
||||
Assertions.assertTrue(services.contains("service-wiremock"));
|
||||
|
||||
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
|
||||
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(serviceInstances.size(), 2);
|
||||
List<DefaultKubernetesServiceInstance> sorted = serviceInstances.stream()
|
||||
.sorted(Comparator.comparing(DefaultKubernetesServiceInstance::getNamespace)).toList();
|
||||
|
||||
DefaultKubernetesServiceInstance first = sorted.get(0);
|
||||
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
|
||||
Assertions.assertNotNull(first.getInstanceId());
|
||||
Assertions.assertEquals(first.getPort(), 8080);
|
||||
Assertions.assertEquals(first.getNamespace(), "a-uat");
|
||||
Assertions.assertEquals(first.getMetadata(),
|
||||
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
|
||||
|
||||
DefaultKubernetesServiceInstance second = sorted.get(1);
|
||||
Assertions.assertEquals(second.getServiceId(), "service-wiremock");
|
||||
Assertions.assertNotNull(second.getInstanceId());
|
||||
Assertions.assertEquals(second.getPort(), 8080);
|
||||
Assertions.assertEquals(second.getNamespace(), "b-uat");
|
||||
Assertions.assertEquals(second.getMetadata(),
|
||||
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "b-uat", "type", "ClusterIP"));
|
||||
|
||||
manifests(Phase.DELETE, FILTER_BOTH_NAMESPACES);
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* - service "wiremock" is present in namespace "a-uat"
|
||||
* - service "wiremock" is present in namespace "b-uat"
|
||||
*
|
||||
* - we search with a predicate : "#root.metadata.namespace matches 'a-uat$'"
|
||||
*
|
||||
* As such, only service from 'a-uat' namespace matches.
|
||||
* </pre>
|
||||
*/
|
||||
@Test
|
||||
void filterMatchesOneNamespaceViaThePredicate() {
|
||||
manifests(Phase.CREATE, FILTER_SINGLE_NAMESPACE);
|
||||
|
||||
WebClient clientServices = builder().baseUrl("http://localhost/services").build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> services = (List<String>) clientServices.method(HttpMethod.GET).retrieve().bodyToMono(List.class)
|
||||
.retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(services.size(), 1);
|
||||
Assertions.assertTrue(services.contains("service-wiremock"));
|
||||
|
||||
WebClient client = builder().baseUrl("http://localhost/service-instances/service-wiremock").build();
|
||||
List<DefaultKubernetesServiceInstance> serviceInstances = client.method(HttpMethod.GET).retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<List<DefaultKubernetesServiceInstance>>() {
|
||||
|
||||
}).retryWhen(retrySpec()).block();
|
||||
|
||||
Assertions.assertEquals(serviceInstances.size(), 1);
|
||||
|
||||
DefaultKubernetesServiceInstance first = serviceInstances.get(0);
|
||||
Assertions.assertEquals(first.getServiceId(), "service-wiremock");
|
||||
Assertions.assertNotNull(first.getInstanceId());
|
||||
Assertions.assertEquals(first.getPort(), 8080);
|
||||
Assertions.assertEquals(first.getNamespace(), "a-uat");
|
||||
Assertions.assertEquals(first.getMetadata(),
|
||||
Map.of("app", "service-wiremock", "port.http", "8080", "k8s_namespace", "a-uat", "type", "ClusterIP"));
|
||||
|
||||
manifests(Phase.DELETE, FILTER_SINGLE_NAMESPACE);
|
||||
}
|
||||
|
||||
private static void manifests(Phase phase, String serviceFilter) {
|
||||
|
||||
InputStream deploymentStream = util.inputStream("fabric8-discovery-deployment.yaml");
|
||||
InputStream serviceStream = util.inputStream("fabric8-discovery-service.yaml");
|
||||
InputStream ingressStream = util.inputStream("fabric8-discovery-ingress.yaml");
|
||||
|
||||
Deployment deployment = client.apps().deployments().load(deploymentStream).get();
|
||||
List<EnvVar> envVars = new ArrayList<>(
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv());
|
||||
EnvVar namespaceAUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_0")
|
||||
.withValue(NAMESPACE_A_UAT).build();
|
||||
EnvVar namespaceBUat = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_NAMESPACES_1")
|
||||
.withValue(NAMESPACE_B_UAT).build();
|
||||
EnvVar filter = new EnvVarBuilder().withName("SPRING_CLOUD_KUBERNETES_DISCOVERY_FILTER")
|
||||
.withValue(serviceFilter).build();
|
||||
EnvVar debug = new EnvVarBuilder()
|
||||
.withName("LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_CLOUD_KUBERNETES_FABRIC8_DISCOVERY").withValue("DEBUG")
|
||||
.build();
|
||||
envVars.add(namespaceAUat);
|
||||
envVars.add(namespaceBUat);
|
||||
envVars.add(filter);
|
||||
envVars.add(debug);
|
||||
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).setEnv(envVars);
|
||||
|
||||
Service service = client.services().load(serviceStream).get();
|
||||
Ingress ingress = client.network().v1().ingresses().load(ingressStream).get();
|
||||
|
||||
if (phase.equals(Phase.CREATE)) {
|
||||
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
|
||||
.create();
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
}
|
||||
else {
|
||||
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
|
||||
.delete();
|
||||
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static InputStream getAdminRole() {
|
||||
return util.inputStream("namespace-filter/fabric8-cluster-admin-serviceaccount-role.yaml");
|
||||
}
|
||||
|
||||
private WebClient.Builder builder() {
|
||||
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create()));
|
||||
}
|
||||
|
||||
private RetryBackoffSpec retrySpec() {
|
||||
return Retry.fixedDelay(15, Duration.ofSeconds(2)).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -137,6 +137,8 @@ class Fabric8DiscoveryNamespaceFilterIT {
|
||||
util.createAndWait(NAMESPACE, null, deployment, service, ingress, true);
|
||||
}
|
||||
else {
|
||||
client.rbac().clusterRoleBindings().resource(client.rbac().clusterRoleBindings().load(getAdminRole()).get())
|
||||
.delete();
|
||||
util.deleteAndWait(NAMESPACE, deployment, service, ingress);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user