Skip to content

Commit 4e162b8

Browse files
authored
feat: ResourceIDMapper for external event sources, external dependents and bulk dependents (#3020)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent c407bce commit 4e162b8

File tree

47 files changed

+557
-264
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+557
-264
lines changed

docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -369,19 +369,22 @@ or we can use a matcher based SSA in most of the cases if the resource is manage
369369
### Selecting the target resource
370370

371371
Unfortunately this is not true for external resources. So to make sure we are selecting
372-
the target resources from an event source, we provide a [mechanism](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java#L114-L138) that helps with that logic.
373-
Your POJO representing an external resource can implement [`ExternalResourceIDProvider`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/ExternalDependentIDProvider.java) :
372+
the target resources from an event source, we provide a [mechanism](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java#L133-L147) that helps with that logic.
373+
[`ResourceIDMapper`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceIDMapper.java)
374+
maps the resource to and ID and the ID of desired and actual resource is checked for equality.
375+
376+
Your POJO representing an external resource can implement [`ResourceIDProvider`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceIDProvider.java).
377+
The default `ResourceIDMapper` implementation works on top of resource which implements the `ResourceIDProvider`:
374378

375379
```java
376380

377-
public interface ExternalDependentIDProvider<T> {
381+
public interface ResourceIDProvider<T> {
378382

379-
T externalResourceId();
383+
T resourceId();
380384
}
381385
```
382386

383-
That will provide an ID, what is used to check for equality for desired state and resources from event source caches.
384-
Not that if some reason this mechanism does not suit for you, you can simply
387+
Note that if those approaches does not work for your use case, you can simply
385388
override [`selectTargetSecondaryResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java)
386389
method.
387390

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
title: Migrating from v5.1 to v5.2
3+
description: Migrating from v5.1 to v5.2
4+
---
5+
6+
Version 5.2 brings some breaking changes to certain components. This document provides
7+
a migration guide for these changes. For all the new features, see the release notes.
8+
9+
## Custom ID types across multiple components using ResourceIDMapper and ResourceIDProvider
10+
11+
Working with the id of a resource is needed across various components in the framework.
12+
Until this version, the components provided by the framework assumed that you could easily
13+
convert the id of a resource into a String representation. For example,
14+
[BulkDependentResources](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java#L46)
15+
worked with a `Map<String,R>` of resources, where the id was always of type String.
16+
17+
Mainly because of the need to manage external dependent resources more elegantly,
18+
we introduced a cross-cutting concept: [`ResourceIDMapper`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceIDMapper.java),
19+
which gets the ID of a resource. This is used across various components, see:
20+
21+
- [`ExternalResourceCachingEventSource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/ExternalResourceCachingEventSource.java#L66)
22+
- [`ExternalBulkDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/ExternalBulkDependentResource.java)
23+
- [`AbstractExternalDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java#L39)
24+
and its subclasses.
25+
26+
We also added [`ResourceIDProvider`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceIDProvider.java),
27+
which you can implement in your Pojo representing a resource.
28+
29+
The easiest way to migrate to this new approach is to implement this interface for your (external) resource
30+
and set the ID type generics for the components above. The default implementation of the `ResourceIDMapper`
31+
works with `ResourceIDProvider` (see [related implementation](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ResourceIDMapper.java#L52)).
32+
33+
If you cannot implement `ResourceIDProvider` because, for example, the class that represents the external resource is generated and final,
34+
you can always set a custom `ResourceIDMapper` on the components above.
35+
36+
See also:
37+
- related issue: [link](https://github.com/operator-framework/java-operator-sdk/issues/2972)
38+
- related pull requests:
39+
- [2970](https://github.com/operator-framework/java-operator-sdk/pull/2970)
40+
- [3020](https://github.com/operator-framework/java-operator-sdk/pull/3020)
41+
42+
43+
44+

docs/content/fileList.txt

Whitespace-only changes.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.processing;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
20+
import io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource;
21+
22+
/**
23+
* Provides id for the target resource. This mapper is used across multiple components of the
24+
* framework, like:
25+
*
26+
* <ul>
27+
* <li>{@link io.javaoperatorsdk.operator.processing.dependent.AbstractExternalDependentResource}
28+
* <li>{@link ExternalResourceCachingEventSource}
29+
* <li>{@link io.javaoperatorsdk.operator.processing.dependent.KubernetesBulkDependentResource}
30+
* </ul>
31+
*
32+
* @see ResourceIDProvider<ID>
33+
*/
34+
public interface ResourceIDMapper<R, ID> {
35+
36+
/**
37+
* @return id for the target resource.
38+
*/
39+
ID idFor(R resource);
40+
41+
/**
42+
* Can be used if a polling event source handles only single secondary resource and the id is
43+
* String. See also docs for: {@link ExternalResourceCachingEventSource}; or in {@link
44+
* io.javaoperatorsdk.operator.processing.dependent.AbstractExternalDependentResource} when there
45+
* is always only one secondary resource for that dependent resource. By definition cannot be used
46+
* for a BulkDependent resources.
47+
*
48+
* @return static id mapper, all resources are mapped for same id.
49+
* @param <R> secondary resource type
50+
*/
51+
static <R> ResourceIDMapper<R, String> singleResourceResourceIDMapper() {
52+
// the result could be any string, by definition would work with any value
53+
return r -> "irrelevant";
54+
}
55+
56+
@SuppressWarnings({"rawtypes", "unchecked"})
57+
static <R, ID> ResourceIDMapper<R, ID> resourceIdProviderMapper() {
58+
return r -> {
59+
if (r instanceof ResourceIDProvider resourceIDProvider) {
60+
return (ID) resourceIDProvider.resourceId();
61+
} else {
62+
throw new IllegalStateException(
63+
"Resource does not implement ExternalDependentIDProvider: " + r.getClass());
64+
}
65+
};
66+
}
67+
68+
static <R extends HasMetadata> ResourceIDMapper<R, ResourceID> kubernetesResourceIdMapper() {
69+
return ResourceID::fromResource;
70+
}
71+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.processing;
17+
18+
/**
19+
* Provides the identifier for an object that represents a resource. This ID is used:
20+
*
21+
* <ul>
22+
* <li>to select the target external resource for a dependent resource from the resources returned
23+
* by {@link io.javaoperatorsdk.operator.api.reconciler.Context#getSecondaryResources(Class)},
24+
* <li>used in {@link ResourceIDMapper} for event sources in external resources. But also for bulk
25+
* dependent resource see {@link
26+
* io.javaoperatorsdk.operator.processing.dependent.ExternalBulkDependentResource},
27+
* <li>and external event sources, see {@link
28+
* io.javaoperatorsdk.operator.processing.event.source.ExternalResourceCachingEventSource}
29+
* </ul>
30+
*
31+
* @see ResourceIDMapper
32+
* @param <ID> type of the id
33+
*/
34+
public interface ResourceIDProvider<ID> {
35+
36+
/** ID for the resource POJO that implement this interface. */
37+
ID resourceId();
38+
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ protected AbstractDependentResource(String name) {
6363

6464
dependentResourceReconciler =
6565
this instanceof BulkDependentResource
66-
? new BulkDependentResourceReconciler<>((BulkDependentResource<R, P>) this)
66+
? new BulkDependentResourceReconciler<>((BulkDependentResource<R, P, ?>) this)
6767
: new SingleDependentResourceReconciler<>(this);
6868
this.name = name == null ? DependentResource.defaultNameFor(this.getClass()) : name;
6969
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractExternalDependentResource.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,22 @@
2222
import io.fabric8.kubernetes.api.model.HasMetadata;
2323
import io.javaoperatorsdk.operator.api.reconciler.Context;
2424
import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller;
25+
import io.javaoperatorsdk.operator.processing.ResourceIDMapper;
2526
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever;
2627
import io.javaoperatorsdk.operator.processing.event.ResourceID;
2728
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
2829
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
2930

3031
public abstract class AbstractExternalDependentResource<
31-
R, P extends HasMetadata, T extends EventSource<R, P>>
32+
R, P extends HasMetadata, T extends EventSource<R, P>, ID>
3233
extends AbstractEventSourceHolderDependentResource<R, P, T> {
3334

3435
private final boolean isDependentResourceWithExplicitState =
3536
this instanceof DependentResourceWithExplicitState;
3637
private final boolean isBulkDependentResource = this instanceof BulkDependentResource;
3738

39+
protected ResourceIDMapper<R, ID> resourceIDMapper = ResourceIDMapper.resourceIdProviderMapper();
40+
3841
@SuppressWarnings("rawtypes")
3942
private DependentResourceWithExplicitState dependentResourceWithExplicitState;
4043

@@ -131,24 +134,23 @@ protected Optional<R> selectTargetSecondaryResource(
131134
Set<R> secondaryResources, P primary, Context<P> context) {
132135
R desired = desired(primary, context);
133136
List<R> targetResources;
134-
if (desired instanceof ExternalDependentIDProvider<?> desiredWithId) {
135-
targetResources =
136-
secondaryResources.stream()
137-
.filter(
138-
r ->
139-
((ExternalDependentIDProvider<?>) r)
140-
.externalResourceId()
141-
.equals(desiredWithId.externalResourceId()))
142-
.toList();
143-
} else {
144-
throw new IllegalStateException(
145-
"Either implement ExternalDependentIDProvider or override this "
146-
+ " (selectTargetSecondaryResource) method.");
147-
}
137+
var desiredID = resourceIDMapper.idFor(desired);
138+
targetResources =
139+
secondaryResources.stream()
140+
.filter(r -> resourceIDMapper.idFor(r).equals(desiredID))
141+
.toList();
148142
if (targetResources.size() > 1) {
149143
throw new IllegalStateException(
150144
"More than one secondary resource related to primary: " + targetResources);
151145
}
152146
return targetResources.isEmpty() ? Optional.empty() : Optional.of(targetResources.get(0));
153147
}
148+
149+
public ResourceIDMapper<R, ID> resourceIDMapper() {
150+
return resourceIDMapper;
151+
}
152+
153+
public void setResourceIDMapper(ResourceIDMapper<R, ID> resourceIDMapper) {
154+
this.resourceIDMapper = resourceIDMapper;
155+
}
154156
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResource.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727
* implementing this interface will typically also implement one or more additional interfaces such
2828
* as {@link Creator}, {@link Updater}, {@link Deleter}.
2929
*
30+
* @param <ID> type of the id to distinguish resource
3031
* @param <R> the dependent resource type
3132
* @param <P> the primary resource type
3233
*/
33-
public interface BulkDependentResource<R, P extends HasMetadata> {
34+
public interface BulkDependentResource<R, P extends HasMetadata, ID> {
3435

3536
/**
3637
* Retrieves a map of desired secondary resources associated with the specified primary resource,
@@ -42,7 +43,7 @@ public interface BulkDependentResource<R, P extends HasMetadata> {
4243
* @return a Map associating desired secondary resources with the specified primary via arbitrary
4344
* identifiers
4445
*/
45-
default Map<String, R> desiredResources(P primary, Context<P> context) {
46+
default Map<ID, R> desiredResources(P primary, Context<P> context) {
4647
throw new IllegalStateException(
4748
"Implement desiredResources in case a non read-only bulk dependent resource");
4849
}
@@ -57,7 +58,7 @@ default Map<String, R> desiredResources(P primary, Context<P> context) {
5758
* @return a Map associating actual secondary resources with the specified primary via arbitrary
5859
* identifiers
5960
*/
60-
Map<String, R> getSecondaryResources(P primary, Context<P> context);
61+
Map<ID, R> getSecondaryResources(P primary, Context<P> context);
6162

6263
/**
6364
* Deletes the actual resource identified by the specified key if it's not in the set of desired
@@ -69,7 +70,7 @@ default Map<String, R> desiredResources(P primary, Context<P> context) {
6970
* @param key key of the resource
7071
* @param context actual context
7172
*/
72-
void deleteTargetResource(P primary, R resource, String key, Context<P> context);
73+
void deleteTargetResource(P primary, R resource, ID key, Context<P> context);
7374

7475
/**
7576
* Determines whether the specified secondary resource matches the desired state with target index

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/BulkDependentResourceReconciler.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@
2929
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult;
3030
import io.javaoperatorsdk.operator.processing.dependent.Matcher.Result;
3131

32-
class BulkDependentResourceReconciler<R, P extends HasMetadata>
32+
class BulkDependentResourceReconciler<R, P extends HasMetadata, ID>
3333
implements DependentResourceReconciler<R, P> {
3434

35-
private final BulkDependentResource<R, P> bulkDependentResource;
35+
private final BulkDependentResource<R, P, ID> bulkDependentResource;
3636

37-
BulkDependentResourceReconciler(BulkDependentResource<R, P> bulkDependentResource) {
37+
BulkDependentResourceReconciler(BulkDependentResource<R, P, ID> bulkDependentResource) {
3838
this.bulkDependentResource = bulkDependentResource;
3939
}
4040

4141
@Override
4242
public ReconcileResult<R> reconcile(P primary, Context<P> context) {
4343

44-
Map<String, R> actualResources = bulkDependentResource.getSecondaryResources(primary, context);
44+
Map<ID, R> actualResources = bulkDependentResource.getSecondaryResources(primary, context);
4545
if (!(bulkDependentResource instanceof Creator<?, ?>)
4646
&& !(bulkDependentResource instanceof Deleter<?>)
4747
&& !(bulkDependentResource instanceof Updater<?, ?>)) {
@@ -73,7 +73,7 @@ public void delete(P primary, Context<P> context) {
7373
}
7474

7575
private void deleteExtraResources(
76-
Set<String> expectedKeys, Map<String, R> actualResources, P primary, Context<P> context) {
76+
Set<ID> expectedKeys, Map<ID, R> actualResources, P primary, Context<P> context) {
7777
actualResources.forEach(
7878
(key, value) -> {
7979
if (!expectedKeys.contains(key)) {
@@ -90,13 +90,13 @@ private void deleteExtraResources(
9090
* @param <P>
9191
*/
9292
@Ignore
93-
private static class BulkDependentResourceInstance<R, P extends HasMetadata>
93+
private static class BulkDependentResourceInstance<R, P extends HasMetadata, ID>
9494
extends AbstractDependentResource<R, P> implements Creator<R, P>, Deleter<P>, Updater<R, P> {
95-
private final BulkDependentResource<R, P> bulkDependentResource;
95+
private final BulkDependentResource<R, P, ID> bulkDependentResource;
9696
private final R desired;
9797

9898
private BulkDependentResourceInstance(
99-
BulkDependentResource<R, P> bulkDependentResource, R desired) {
99+
BulkDependentResource<R, P, ID> bulkDependentResource, R desired) {
100100
this.bulkDependentResource = bulkDependentResource;
101101
this.desired = desired;
102102
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818
import io.fabric8.kubernetes.api.model.HasMetadata;
1919
import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter;
2020

21-
public interface CRUDBulkDependentResource<R, P extends HasMetadata>
22-
extends BulkDependentResource<R, P>, Creator<R, P>, BulkUpdater<R, P>, Deleter<P> {}
21+
public interface CRUDBulkDependentResource<R, P extends HasMetadata, ID>
22+
extends BulkDependentResource<R, P, ID>, Creator<R, P>, BulkUpdater<R, P>, Deleter<P> {}

0 commit comments

Comments
 (0)