Skip to content

Commit baad99f

Browse files
authored
Merge pull request kroxylicious#1924 from robobario/refactor-early-resolution
operator: report unresolved ingresses
2 parents 11f8f06 + 68079d1 commit baad99f

39 files changed

+1447
-162
lines changed

kroxylicious-operator/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@
216216
<artifactId>kubernetes-model-apiextensions</artifactId>
217217
<scope>test</scope>
218218
</dependency>
219+
<dependency>
220+
<groupId>org.mockito</groupId>
221+
<artifactId>mockito-junit-jupiter</artifactId>
222+
<scope>test</scope>
223+
</dependency>
219224
</dependencies>
220225
<build>
221226
<plugins>

kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/ClusterCondition.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import java.util.Set;
1212

1313
import io.kroxylicious.kubernetes.api.v1alpha1.virtualkafkaclusterspec.TargetCluster;
14-
import io.kroxylicious.kubernetes.operator.ingress.IngressConflictException;
14+
import io.kroxylicious.kubernetes.operator.model.ingress.IngressConflictException;
1515

1616
import edu.umd.cs.findbugs.annotations.NonNull;
1717
import edu.umd.cs.findbugs.annotations.Nullable;
@@ -36,11 +36,16 @@ static ClusterCondition filterInvalid(String cluster, String filterName, String
3636
String.format("Filter \"%s\" is invalid: %s", filterName, detail));
3737
}
3838

39-
static ClusterCondition filterNotFound(String cluster, String filterName) {
39+
public static ClusterCondition filterNotFound(String cluster, String filterName) {
4040
return new ClusterCondition(cluster, ConditionType.Accepted, Status.FALSE, INVALID,
4141
String.format("Filter \"%s\" does not exist.", filterName));
4242
}
4343

44+
public static ClusterCondition ingressNotFound(String cluster, String ingressName) {
45+
return new ClusterCondition(cluster, ConditionType.Accepted, Status.FALSE, INVALID,
46+
String.format("KafkaProxyIngress \"%s\" does not exist.", ingressName));
47+
}
48+
4449
public static ClusterCondition ingressConflict(String cluster, Set<IngressConflictException> ingressConflictExceptions) {
4550
String ingresses = ingressConflictExceptions.stream()
4651
.map(IngressConflictException::getIngressName)
@@ -49,7 +54,7 @@ public static ClusterCondition ingressConflict(String cluster, Set<IngressConfli
4954
String.format("Ingress(es) [%s] of cluster conflicts with another ingress", ingresses));
5055
}
5156

52-
static ClusterCondition targetClusterRefNotFound(String cluster, TargetCluster targetCluster) {
57+
public static ClusterCondition targetClusterRefNotFound(String cluster, TargetCluster targetCluster) {
5358
return new ClusterCondition(cluster, ConditionType.Accepted, Status.FALSE, INVALID,
5459
String.format("Target Cluster \"%s\" does not exist.", kubeName(targetCluster).orElse("<unknown>")));
5560
}

kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/ClusterService.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66
package io.kroxylicious.kubernetes.operator;
77

8-
import java.util.List;
98
import java.util.Map;
109
import java.util.Objects;
1110
import java.util.Set;
@@ -18,10 +17,10 @@
1817
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
1918

2019
import io.kroxylicious.kubernetes.api.v1alpha1.KafkaProxy;
21-
import io.kroxylicious.kubernetes.api.v1alpha1.KafkaProxyIngress;
2220
import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaCluster;
23-
import io.kroxylicious.kubernetes.operator.ingress.IngressAllocator;
24-
import io.kroxylicious.kubernetes.operator.ingress.ProxyIngressModel;
21+
import io.kroxylicious.kubernetes.operator.model.ProxyModel;
22+
import io.kroxylicious.kubernetes.operator.model.ProxyModelBuilder;
23+
import io.kroxylicious.kubernetes.operator.model.ingress.ProxyIngressModel;
2524

2625
import static io.kroxylicious.kubernetes.operator.ResourcesUtil.toByNameMap;
2726

@@ -51,12 +50,11 @@ static String serviceName(VirtualKafkaCluster cluster) {
5150
public Map<String, Service> desiredResources(
5251
KafkaProxy primary,
5352
Context<KafkaProxy> context) {
54-
List<VirtualKafkaCluster> clusters = ResourcesUtil.clustersInNameOrder(context).toList();
55-
Set<KafkaProxyIngress> ingresses = context.getSecondaryResources(KafkaProxyIngress.class);
56-
ProxyIngressModel ingressModel = IngressAllocator.allocateProxyIngressModel(primary, clusters, ingresses);
57-
Stream<Service> serviceStream = clusters.stream()
53+
ProxyModelBuilder proxyModelBuilder = ProxyModelBuilder.contextBuilder(context);
54+
ProxyModel model = proxyModelBuilder.build(primary, context);
55+
Stream<Service> serviceStream = model.clustersWithValidIngresses().stream()
5856
.filter(cluster -> !SharedKafkaProxyContext.isBroken(context, cluster))
59-
.flatMap(cluster -> ingressModel.clusterIngressModel(cluster).map(ProxyIngressModel.VirtualClusterIngressModel::services).orElse(Stream.empty()));
57+
.flatMap(cluster -> model.ingressModel().clusterIngressModel(cluster).map(ProxyIngressModel.VirtualClusterIngressModel::services).orElse(Stream.empty()));
6058
return serviceStream.collect(toByNameMap());
6159
}
6260

kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/ProxyConfigConfigMap.java

Lines changed: 27 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,35 @@
88
import java.io.IOException;
99
import java.io.UncheckedIOException;
1010
import java.util.ArrayList;
11+
import java.util.Collection;
1112
import java.util.Comparator;
1213
import java.util.HashSet;
1314
import java.util.LinkedHashSet;
1415
import java.util.List;
1516
import java.util.Map;
1617
import java.util.Optional;
1718
import java.util.Set;
18-
import java.util.function.Function;
1919
import java.util.stream.Collectors;
2020

2121
import com.fasterxml.jackson.databind.ObjectMapper;
2222

2323
import io.fabric8.kubernetes.api.model.ConfigMap;
2424
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
25-
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
2625
import io.fabric8.kubernetes.api.model.Volume;
2726
import io.fabric8.kubernetes.api.model.VolumeMount;
2827
import io.javaoperatorsdk.operator.api.reconciler.Context;
2928
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext;
3029
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
3130
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
32-
import io.javaoperatorsdk.operator.processing.event.ResourceID;
3331

3432
import io.kroxylicious.kubernetes.api.v1alpha1.KafkaClusterRef;
3533
import io.kroxylicious.kubernetes.api.v1alpha1.KafkaProxy;
36-
import io.kroxylicious.kubernetes.api.v1alpha1.KafkaProxyIngress;
3734
import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaCluster;
38-
import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterSpec;
3935
import io.kroxylicious.kubernetes.api.v1alpha1.virtualkafkaclusterspec.Filters;
40-
import io.kroxylicious.kubernetes.operator.ingress.IngressAllocator;
41-
import io.kroxylicious.kubernetes.operator.ingress.IngressConflictException;
42-
import io.kroxylicious.kubernetes.operator.ingress.ProxyIngressModel;
36+
import io.kroxylicious.kubernetes.operator.model.ProxyModel;
37+
import io.kroxylicious.kubernetes.operator.model.ProxyModelBuilder;
38+
import io.kroxylicious.kubernetes.operator.model.ingress.ProxyIngressModel;
39+
import io.kroxylicious.kubernetes.operator.resolver.ResolutionResult;
4340
import io.kroxylicious.proxy.config.ConfigParser;
4441
import io.kroxylicious.proxy.config.Configuration;
4542
import io.kroxylicious.proxy.config.NamedFilterDefinition;
@@ -52,7 +49,6 @@
5249
import edu.umd.cs.findbugs.annotations.NonNull;
5350

5451
import static io.kroxylicious.kubernetes.operator.Labels.standardLabels;
55-
import static io.kroxylicious.kubernetes.operator.ResourcesUtil.clusterRefs;
5652
import static io.kroxylicious.kubernetes.operator.ResourcesUtil.namespace;
5753

5854
/**
@@ -132,24 +128,19 @@ protected ConfigMap desired(KafkaProxy primary,
132128

133129
String generateProxyConfig(KafkaProxy primary,
134130
Context<KafkaProxy> context) {
131+
ProxyModelBuilder proxyModelBuilder = ProxyModelBuilder.contextBuilder(context);
132+
ProxyModel model = proxyModelBuilder.build(primary, context);
133+
List<NamedFilterDefinition> allFilterDefinitions = buildFilterDefinitions(context, model);
134+
Map<String, NamedFilterDefinition> namedDefinitions = allFilterDefinitions.stream().collect(Collectors.toMap(NamedFilterDefinition::name, f -> f));
135135

136-
List<VirtualKafkaCluster> virtualKafkaClusters = ResourcesUtil.clustersInNameOrder(context).toList();
137-
Set<KafkaProxyIngress> ingresses = context.getSecondaryResources(KafkaProxyIngress.class);
138-
ProxyIngressModel ingressModel = getProxyIngressModel(primary, context, virtualKafkaClusters, ingresses);
136+
var virtualClusters = buildVirtualClusters(namedDefinitions.keySet(), model);
139137

140-
Map<ResourceID, KafkaClusterRef> clusterRefs = clusterRefs(context);
141-
142-
// TODO fix this double invocation of buildFilterDefinitions which is a workaround for https://github.com/kroxylicious/kroxylicious/issues/1916
143-
// first invocation rejects some virtual clusters with unresolved refs
144-
buildFilterDefinitions(context, virtualKafkaClusters);
145-
146-
var virtualClusters = buildVirtualClusters(context, virtualKafkaClusters, clusterRefs, ingressModel);
147-
148-
// second invocation excludes filters that belong to clusters broken in `buildVirtualClusters`
149-
List<NamedFilterDefinition> filterDefinitions = buildFilterDefinitions(context, virtualKafkaClusters);
138+
List<NamedFilterDefinition> referencedFilters = virtualClusters.stream().flatMap(c -> Optional.ofNullable(c.filters()).stream().flatMap(Collection::stream))
139+
.distinct()
140+
.map(namedDefinitions::get).toList();
150141

151142
Configuration configuration = new Configuration(
152-
new ManagementConfiguration(null, null, new EndpointsConfiguration(new PrometheusMetricsConfig())), filterDefinitions,
143+
new ManagementConfiguration(null, null, new EndpointsConfiguration(new PrometheusMetricsConfig())), referencedFilters,
153144
null, // no defaultFilters <= each of the virtualClusters specifies its own
154145
virtualClusters,
155146
List.of(), false,
@@ -159,60 +150,31 @@ String generateProxyConfig(KafkaProxy primary,
159150
return toYaml(configuration);
160151
}
161152

162-
private static @NonNull ProxyIngressModel getProxyIngressModel(KafkaProxy primary, Context<KafkaProxy> context, List<VirtualKafkaCluster> virtualKafkaClusters,
163-
Set<KafkaProxyIngress> ingresses) {
164-
ProxyIngressModel ingressModel = IngressAllocator.allocateProxyIngressModel(primary, virtualKafkaClusters, ingresses);
165-
for (ProxyIngressModel.VirtualClusterIngressModel virtualClusterIngressModel : ingressModel.clusters()) {
166-
Set<IngressConflictException> exceptions = virtualClusterIngressModel.ingressExceptions();
167-
if (!exceptions.isEmpty()) {
168-
VirtualKafkaCluster cluster = virtualClusterIngressModel.cluster();
169-
SharedKafkaProxyContext.addClusterCondition(context, cluster, ClusterCondition.ingressConflict(ResourcesUtil.name(cluster), exceptions));
170-
}
171-
}
172-
return ingressModel;
173-
}
174-
175153
@NonNull
176-
private static List<VirtualCluster> buildVirtualClusters(Context<KafkaProxy> context,
177-
List<VirtualKafkaCluster> clusters,
178-
Map<ResourceID, KafkaClusterRef> clusterRefs,
179-
ProxyIngressModel ingressModel) {
180-
Map<VirtualKafkaCluster, Optional<KafkaClusterRef>> clusterToRefMap = clusters.stream()
181-
.collect(Collectors.toMap(
182-
Function.identity(),
183-
cluster -> clusterTargetClusterResourceID(cluster).map(clusterRefs::get)));
184-
185-
clusterToRefMap.entrySet().stream()
186-
.filter(e -> e.getValue().isEmpty())
187-
.forEach(e -> {
188-
var cluster = e.getKey();
189-
SharedKafkaProxyContext.addClusterCondition(context, cluster, targetClusterResourceNotFound(cluster).accepted());
190-
});
191-
192-
return clusters.stream()
193-
.filter(cluster -> !SharedKafkaProxyContext.isBroken(context, cluster))
194-
.map(cluster -> getVirtualCluster(cluster, clusterToRefMap.get(cluster).get(), ingressModel))
154+
private static List<VirtualCluster> buildVirtualClusters(Set<String> successfullyBuiltFilterNames, ProxyModel model) {
155+
return model.clustersWithValidIngresses().stream()
156+
.filter(cluster -> Optional.ofNullable(cluster.getSpec().getFilters()).stream().flatMap(Collection::stream).allMatch(
157+
filters -> successfullyBuiltFilterNames.contains(filterDefinitionName(filters))))
158+
.map(cluster -> getVirtualCluster(cluster, model.resolutionResult().kafkaClusterRef(cluster).orElseThrow(), model.ingressModel()))
195159
.toList();
196160
}
197161

198162
@NonNull
199163

200-
private List<NamedFilterDefinition> buildFilterDefinitions(Context<KafkaProxy> context, List<VirtualKafkaCluster> clusters) {
164+
private List<NamedFilterDefinition> buildFilterDefinitions(Context<KafkaProxy> context,
165+
ProxyModel model) {
201166
List<NamedFilterDefinition> filterDefinitions = new ArrayList<>();
202167
Set<NamedFilterDefinition> uniqueValues = new HashSet<>();
203-
for (VirtualKafkaCluster cluster1 : clusters) {
204-
if (SharedKafkaProxyContext.isBroken(context, cluster1)) {
205-
continue;
206-
}
168+
for (VirtualKafkaCluster cluster : model.clustersWithValidIngresses()) {
207169
try {
208-
for (NamedFilterDefinition namedFilterDefinition : filterDefinitions(context, cluster1)) {
170+
for (NamedFilterDefinition namedFilterDefinition : filterDefinitions(context, cluster, model.resolutionResult())) {
209171
if (uniqueValues.add(namedFilterDefinition)) {
210172
filterDefinitions.add(namedFilterDefinition);
211173
}
212174
}
213175
}
214176
catch (InvalidClusterException e) {
215-
SharedKafkaProxyContext.addClusterCondition(context, cluster1, e.accepted());
177+
SharedKafkaProxyContext.addClusterCondition(context, cluster, e.accepted());
216178
}
217179
}
218180
filterDefinitions.sort(Comparator.comparing(NamedFilterDefinition::name));
@@ -233,14 +195,14 @@ private static String filterDefinitionName(Filters filterCrRef) {
233195
}
234196

235197
@NonNull
236-
private List<NamedFilterDefinition> filterDefinitions(Context<KafkaProxy> context, VirtualKafkaCluster cluster)
198+
private List<NamedFilterDefinition> filterDefinitions(Context<KafkaProxy> context, VirtualKafkaCluster cluster, ResolutionResult resolutionResult)
237199
throws InvalidClusterException {
238200

239201
return Optional.ofNullable(cluster.getSpec().getFilters()).orElse(List.of()).stream().map(filterCrRef -> {
240202

241203
String filterDefinitionName = filterDefinitionName(filterCrRef);
242204

243-
var filterCr = filterResourceFromRef(cluster, context, filterCrRef);
205+
var filterCr = resolutionResult.filter(filterCrRef).orElseThrow();
244206
if (filterCr.getAdditionalProperties().get("spec") instanceof Map<?, ?> spec) {
245207
String type = (String) spec.get("type");
246208
SecureConfigInterpolator.InterpolationResult interpolationResult = interpolateConfig(spec);
@@ -266,7 +228,7 @@ private static <T> void putOrMerged(ManagedWorkflowAndDependentResourceContext c
266228
}
267229
}
268230

269-
private @NonNull SecureConfigInterpolator.InterpolationResult interpolateConfig(Map<?, ?> spec) {
231+
private SecureConfigInterpolator.InterpolationResult interpolateConfig(Map<?, ?> spec) {
270232
SecureConfigInterpolator.InterpolationResult result;
271233
Object configTemplate = spec.get("configTemplate");
272234
if (configTemplate != null) {
@@ -278,34 +240,6 @@ private static <T> void putOrMerged(ManagedWorkflowAndDependentResourceContext c
278240
return result;
279241
}
280242

281-
@NonNull
282-
private static InvalidClusterException filterResourceNotFound(VirtualKafkaCluster cluster, Filters filterRef) {
283-
return new InvalidClusterException(ClusterCondition.filterNotFound(ResourcesUtil.name(cluster), filterRef.getName()));
284-
}
285-
286-
@NonNull
287-
private static InvalidClusterException targetClusterResourceNotFound(VirtualKafkaCluster cluster) {
288-
return new InvalidClusterException(ClusterCondition.targetClusterRefNotFound(ResourcesUtil.name(cluster), cluster.getSpec().getTargetCluster()));
289-
}
290-
291-
/**
292-
* Look up a Filter CR from the group, kind and name given in the cluster.
293-
*/
294-
@NonNull
295-
private static GenericKubernetesResource filterResourceFromRef(VirtualKafkaCluster cluster, Context<KafkaProxy> context, Filters filterRef)
296-
throws InvalidClusterException {
297-
return context.getSecondaryResources(GenericKubernetesResource.class).stream()
298-
.filter(filterResource -> {
299-
String apiVersion = filterResource.getApiVersion();
300-
var filterResourceGroup = apiVersion.substring(0, apiVersion.indexOf("/"));
301-
return filterResourceGroup.equals(filterRef.getGroup())
302-
&& filterResource.getKind().equals(filterRef.getKind())
303-
&& ResourcesUtil.name(filterResource).equals(filterRef.getName());
304-
})
305-
.findFirst()
306-
.orElseThrow(() -> filterResourceNotFound(cluster, filterRef));
307-
}
308-
309243
private static VirtualCluster getVirtualCluster(VirtualKafkaCluster cluster,
310244
KafkaClusterRef kafkaClusterRef,
311245
ProxyIngressModel ingressModel) {
@@ -321,10 +255,4 @@ private static VirtualCluster getVirtualCluster(VirtualKafkaCluster cluster,
321255
filterNamesForCluster(cluster));
322256
}
323257

324-
private static Optional<ResourceID> clusterTargetClusterResourceID(VirtualKafkaCluster cluster) {
325-
return Optional.ofNullable(cluster.getSpec())
326-
.map(VirtualKafkaClusterSpec::getTargetCluster)
327-
.map(io.kroxylicious.kubernetes.api.v1alpha1.virtualkafkaclusterspec.TargetCluster::getClusterRef)
328-
.map(r -> new ResourceID(r.getName(), namespace(cluster)));
329-
}
330258
}

0 commit comments

Comments
 (0)