Skip to content

Commit 464517c

Browse files
committed
wip
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent c3c33bc commit 464517c

File tree

13 files changed

+488
-150
lines changed

13 files changed

+488
-150
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Experimental.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
@Retention(RetentionPolicy.SOURCE)
3030
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.PACKAGE})
3131
public @interface Experimental {
32+
/**
33+
* Message for experimental features that we intend to keep and maintain, but
34+
* the API might change usually, based on user feedback.
35+
* */
36+
String API_MIGHT_CHANGE = "API might change, usually based on feedback";
3237

3338
/**
3439
* Describes why the annotated element is experimental.

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/Expectation.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
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+
*/
116
package io.javaoperatorsdk.operator.processing.expectation;
217

318
import java.util.function.BiPredicate;
419

520
import io.fabric8.kubernetes.api.model.HasMetadata;
621
import io.javaoperatorsdk.operator.api.reconciler.Context;
22+
import io.javaoperatorsdk.operator.api.reconciler.Experimental;
723

24+
@Experimental("based on feedback the API might change")
825
public interface Expectation<P extends HasMetadata> {
926

1027
String UNNAMED = "unnamed";

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/ExpectationManager.java

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
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+
*/
116
package io.javaoperatorsdk.operator.processing.expectation;
217

318
import java.time.Duration;
@@ -7,47 +22,87 @@
722

823
import io.fabric8.kubernetes.api.model.HasMetadata;
924
import io.javaoperatorsdk.operator.api.reconciler.Context;
25+
import io.javaoperatorsdk.operator.api.reconciler.Experimental;
1026
import io.javaoperatorsdk.operator.processing.event.ResourceID;
1127

28+
import static io.javaoperatorsdk.operator.api.reconciler.Experimental.API_MIGHT_CHANGE;
29+
30+
@Experimental(API_MIGHT_CHANGE)
1231
public class ExpectationManager<P extends HasMetadata> {
1332

1433
protected final ConcurrentHashMap<ResourceID, RegisteredExpectation<P>> registeredExpectations =
1534
new ConcurrentHashMap<>();
1635

17-
public void setExpectation(P primary, Expectation<P> expectation, Duration timeout) {
36+
public void setExpectation(P primary, Duration timeout, Expectation<P> expectation) {
1837
registeredExpectations.put(
1938
ResourceID.fromResource(primary),
2039
new RegisteredExpectation<>(LocalDateTime.now(), timeout, expectation));
2140
}
2241

2342
/**
24-
* Checks if provided expectation is fulfilled. Return the expectation result. If the result of
25-
* expectation is fulfilled or timed out, the expectation is automatically removed;
43+
* Checks on expectation with provided name. Return the expectation result. If the result of
44+
* expectation is fulfilled, the expectation is automatically removed;
2645
*/
27-
public Optional<ExpectationResult<P>> checkOnExpectation(P primary, Context<P> context) {
46+
public ExpectationResult<P> checkExpectation(
47+
String expectationName, P primary, Context<P> context) {
2848
var resourceID = ResourceID.fromResource(primary);
29-
var regExp = registeredExpectations.get(ResourceID.fromResource(primary));
30-
if (regExp == null) {
31-
return Optional.empty();
49+
var exp = registeredExpectations.get(ResourceID.fromResource(primary));
50+
if (exp != null && expectationName.equals(exp.expectation().name())) {
51+
return checkExpectation(exp, resourceID, primary, context);
52+
} else {
53+
return checkExpectation(null, resourceID, primary, context);
3254
}
33-
if (regExp.expectation().isFulfilled(primary, context)) {
34-
registeredExpectations.remove(resourceID);
35-
return Optional.of(
36-
new ExpectationResult<>(regExp.expectation(), ExpectationStatus.FULFILLED));
37-
} else if (regExp.isTimedOut()) {
55+
}
56+
57+
/**
58+
* Checks if actual expectation is fulfilled. Return the expectation result. If the result of
59+
* expectation is fulfilled, the expectation is automatically removed;
60+
*/
61+
public ExpectationResult<P> checkExpectation(P primary, Context<P> context) {
62+
var resourceID = ResourceID.fromResource(primary);
63+
var exp = registeredExpectations.get(ResourceID.fromResource(primary));
64+
return checkExpectation(exp, resourceID, primary, context);
65+
}
66+
67+
private ExpectationResult<P> checkExpectation(
68+
RegisteredExpectation<P> exp, ResourceID resourceID, P primary, Context<P> context) {
69+
if (exp == null) {
70+
return new ExpectationResult<>(null, null);
71+
}
72+
if (exp.expectation().isFulfilled(primary, context)) {
3873
registeredExpectations.remove(resourceID);
39-
return Optional.of(
40-
new ExpectationResult<>(regExp.expectation(), ExpectationStatus.TIMED_OUT));
74+
return new ExpectationResult<>(exp.expectation(), ExpectationStatus.FULFILLED);
75+
} else if (exp.isTimedOut()) {
76+
// we don't remove the expectation so user knows about it's state
77+
return new ExpectationResult<>(exp.expectation(), ExpectationStatus.TIMED_OUT);
4178
} else {
42-
return Optional.of(
43-
new ExpectationResult<>(regExp.expectation(), ExpectationStatus.NOT_FULFILLED));
79+
return new ExpectationResult<>(exp.expectation(), ExpectationStatus.NOT_YET_FULFILLED);
80+
}
81+
}
82+
83+
/*
84+
* Returns true if there is an expectation for the primary resource, but it is not yet fulfilled
85+
* neither timed out.
86+
* The intention behind is that you can exit reconciliation early with a simple check
87+
* if true.
88+
* */
89+
public boolean ongoingExpectationPresent(P primary, Context<P> context) {
90+
var exp = registeredExpectations.get(ResourceID.fromResource(primary));
91+
if (exp == null) {
92+
return false;
4493
}
94+
return !exp.isTimedOut() && !exp.expectation().isFulfilled(primary, context);
4595
}
4696

4797
public boolean isExpectationPresent(P primary) {
4898
return registeredExpectations.containsKey(ResourceID.fromResource(primary));
4999
}
50100

101+
public boolean isExpectationPresent(String name, P primary) {
102+
var exp = registeredExpectations.get(ResourceID.fromResource(primary));
103+
return exp != null && name.equals(exp.expectation().name());
104+
}
105+
51106
public Optional<Expectation<P>> getExpectation(P primary) {
52107
var regExp = registeredExpectations.get(ResourceID.fromResource(primary));
53108
return Optional.ofNullable(regExp).map(RegisteredExpectation::expectation);
@@ -57,6 +112,10 @@ public Optional<String> getExpectationName(P primary) {
57112
return getExpectation(primary).map(Expectation::name);
58113
}
59114

115+
public void removeExpectation(P primary) {
116+
registeredExpectations.remove(ResourceID.fromResource(primary));
117+
}
118+
60119
public void cleanup(P primary) {
61120
registeredExpectations.remove(ResourceID.fromResource(primary));
62121
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/ExpectationResult.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
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+
*/
116
package io.javaoperatorsdk.operator.processing.expectation;
217

318
import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -13,6 +28,14 @@ public boolean isTimedOut() {
1328
return status == ExpectationStatus.TIMED_OUT;
1429
}
1530

31+
public boolean isExpectationPresent() {
32+
return expectation != null;
33+
}
34+
35+
public boolean isNotPresentOrFulfilled() {
36+
return !isExpectationPresent() || isFulfilled();
37+
}
38+
1639
public String name() {
1740
return expectation.name();
1841
}
Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
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+
*/
116
package io.javaoperatorsdk.operator.processing.expectation;
217

318
public enum ExpectationStatus {
419
FULFILLED,
5-
NOT_FULFILLED,
20+
NOT_YET_FULFILLED,
621
TIMED_OUT
722
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/PeriodicCleanerExpectationManager.java

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
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+
*/
116
package io.javaoperatorsdk.operator.processing.expectation;
217

318
import java.time.Duration;
4-
import java.time.LocalDateTime;
519
import java.util.concurrent.Executors;
620
import java.util.concurrent.ScheduledExecutorService;
721
import java.util.concurrent.TimeUnit;
822

923
import io.fabric8.kubernetes.api.model.HasMetadata;
24+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
25+
import io.javaoperatorsdk.operator.api.reconciler.Experimental;
1026
import io.javaoperatorsdk.operator.api.reconciler.IndexedResourceCache;
1127

28+
import static io.javaoperatorsdk.operator.api.reconciler.Experimental.API_MIGHT_CHANGE;
29+
30+
/**
31+
* Expectation manager implementation that works without enabling {@link
32+
* ControllerConfiguration#triggerReconcilerOnAllEvent()}. Periodically checks and cleanups'
33+
* expectations for primary resources which are no longer present in the cache.
34+
*/
35+
@Experimental(API_MIGHT_CHANGE)
1236
public class PeriodicCleanerExpectationManager<P extends HasMetadata>
1337
extends ExpectationManager<P> {
1438

39+
public static final Duration DEFAULT_CHECK_PERIOD = Duration.ofMinutes(1);
40+
1541
private final ScheduledExecutorService scheduler =
1642
Executors.newScheduledThreadPool(
1743
1,
@@ -21,41 +47,20 @@ public class PeriodicCleanerExpectationManager<P extends HasMetadata>
2147
return thread;
2248
});
2349

24-
private final Duration cleanupDelayAfterExpiration;
2550
private final IndexedResourceCache<P> primaryCache;
2651

27-
public PeriodicCleanerExpectationManager(Duration period, Duration cleanupDelayAfterExpiration) {
28-
this(period, cleanupDelayAfterExpiration, null);
52+
public PeriodicCleanerExpectationManager(IndexedResourceCache<P> primaryCache) {
53+
this(DEFAULT_CHECK_PERIOD, primaryCache);
2954
}
3055

3156
public PeriodicCleanerExpectationManager(Duration period, IndexedResourceCache<P> primaryCache) {
32-
this(period, null, primaryCache);
33-
}
34-
35-
private PeriodicCleanerExpectationManager(
36-
Duration period, Duration cleanupDelayAfterExpiration, IndexedResourceCache<P> primaryCache) {
37-
this.cleanupDelayAfterExpiration = cleanupDelayAfterExpiration;
3857
this.primaryCache = primaryCache;
3958
scheduler.scheduleWithFixedDelay(
4059
this::clean, period.toMillis(), period.toMillis(), TimeUnit.MICROSECONDS);
4160
}
4261

4362
public void clean() {
44-
registeredExpectations
45-
.entrySet()
46-
.removeIf(
47-
e -> {
48-
if (cleanupDelayAfterExpiration != null) {
49-
return LocalDateTime.now()
50-
.isAfter(
51-
e.getValue()
52-
.registeredAt()
53-
.plus(e.getValue().timeout())
54-
.plus(cleanupDelayAfterExpiration));
55-
} else {
56-
return primaryCache.get(e.getKey()).isEmpty();
57-
}
58-
});
63+
registeredExpectations.entrySet().removeIf(e -> primaryCache.get(e.getKey()).isEmpty());
5964
}
6065

6166
void stop() {

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/expectation/RegisteredExpectation.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
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+
*/
116
package io.javaoperatorsdk.operator.processing.expectation;
217

318
import java.time.Duration;

0 commit comments

Comments
 (0)