Skip to content

Commit 70206c1

Browse files
committed
switching to an informer based implementation
also fully removing the additional operation/context fields
1 parent 7ded399 commit 70206c1

File tree

15 files changed

+202
-545
lines changed

15 files changed

+202
-545
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Fix #3216: made the mock server aware of apiVersions
1616
* Fix #3225: Pod metric does not have corresponding label selector variant
1717
* Fix #3243: pipes provided to exec command are no longer closed on connection close, so that client can fully read the buffer after the command finishes.
18+
* Fix #3271: waitUntilReady and waitUntilCondition should handle resource too old
1819

1920
#### Improvements
2021
* Fix #3078: adding javadocs to further clarify patch, edit, replace, etc. and note the possibility of items being modified.
@@ -54,6 +55,7 @@
5455
##### DSL Changes:
5556
- #3127 `StatusUpdatable.updateStatus` deprecated, please use patchStatus, editStatus, or replaceStatus
5657
- #3239 deprecated methods on SharedInformerFactory directly dealing with the OperationContext, withName, and withNamespace - the Informable interface should be used instead.
58+
- #3271 waitUntilReady and waitUntilCondition with throw a KubernetesClientTimeoutException instead of an IllegalArgumentException on timeout
5759

5860
##### Util Changes:
5961
- #3197 `Utils.waitUntilReady` now accepts a Future, rather than a BlockingQueue

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/KubernetesClientTimeoutException.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ public class KubernetesClientTimeoutException extends KubernetesClientException
3030
private static final String KNOWS_RESOURCES_FORMAT = "Timed out waiting for [%d] milliseconds for multiple resources. %s";
3131

3232
private final List<HasMetadata> resourcesNotReady;
33+
34+
public KubernetesClientTimeoutException(String kind, String name, String namespace, long amount, TimeUnit timeUnit) {
35+
super(String.format(RESOURCE_FORMAT, timeUnit.toMillis(amount), kind, name, namespace));
36+
this.resourcesNotReady = Collections.emptyList();
37+
}
3338

3439
public KubernetesClientTimeoutException(HasMetadata resource, long amount, TimeUnit timeUnit) {
3540
super(String.format(RESOURCE_FORMAT, timeUnit.toMillis(amount), resource.getKind(), resource.getMetadata().getName(), resource.getMetadata().getNamespace()));

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/Waitable.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020

2121
public interface Waitable<T, P> {
2222

23+
@Deprecated
2324
long DEFAULT_INITIAL_BACKOFF_MILLIS = 5L;
25+
@Deprecated
2426
double DEFAULT_BACKOFF_MULTIPLIER = 2d;
2527

2628
T waitUntilReady(long amount, TimeUnit timeUnit) throws InterruptedException;

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/base/BaseOperation.java

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package io.fabric8.kubernetes.client.dsl.base;
1717

1818
import io.fabric8.kubernetes.api.model.ObjectReference;
19-
import io.fabric8.kubernetes.client.WatcherException;
2019
import io.fabric8.kubernetes.client.dsl.WritableOperation;
2120
import io.fabric8.kubernetes.client.utils.CreateOrReplaceHelper;
2221
import org.slf4j.Logger;
@@ -38,6 +37,7 @@
3837
import io.fabric8.kubernetes.client.Config;
3938
import io.fabric8.kubernetes.client.ConfigBuilder;
4039
import io.fabric8.kubernetes.client.KubernetesClientException;
40+
import io.fabric8.kubernetes.client.KubernetesClientTimeoutException;
4141
import io.fabric8.kubernetes.client.OperationInfo;
4242
import io.fabric8.kubernetes.client.ResourceNotFoundException;
4343
import io.fabric8.kubernetes.client.Watch;
@@ -59,7 +59,6 @@
5959
import io.fabric8.kubernetes.client.informers.impl.DefaultSharedIndexInformer;
6060
import io.fabric8.kubernetes.client.internal.readiness.Readiness;
6161
import io.fabric8.kubernetes.client.utils.HttpClientUtils;
62-
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
6362
import io.fabric8.kubernetes.client.utils.URLUtils;
6463
import io.fabric8.kubernetes.client.utils.Utils;
6564
import io.fabric8.kubernetes.client.utils.WatcherToggle;
@@ -73,11 +72,11 @@
7372
import java.net.MalformedURLException;
7473
import java.net.URL;
7574
import java.util.Arrays;
76-
import java.util.Collections;
7775
import java.util.Iterator;
7876
import java.util.List;
7977
import java.util.Map;
8078
import java.util.Objects;
79+
import java.util.concurrent.CompletableFuture;
8180
import java.util.concurrent.ExecutionException;
8281
import java.util.concurrent.TimeUnit;
8382
import java.util.concurrent.TimeoutException;
@@ -89,8 +88,6 @@
8988
import okhttp3.HttpUrl;
9089
import okhttp3.Request;
9190

92-
import static java.util.concurrent.TimeUnit.NANOSECONDS;
93-
9491
public class BaseOperation<T extends HasMetadata, L extends KubernetesResourceList<T>, R extends Resource<T>>
9592
extends OperationSupport
9693
implements
@@ -1123,34 +1120,53 @@ public T waitUntilReady(long amount, TimeUnit timeUnit) throws InterruptedExcept
11231120
@Override
11241121
public T waitUntilCondition(Predicate<T> condition, long amount, TimeUnit timeUnit)
11251122
throws InterruptedException {
1123+
1124+
CompletableFuture<T> future = new CompletableFuture<>();
1125+
// tests the condition, trapping any exceptions
1126+
Consumer<T> tester = obj -> {
1127+
try {
1128+
if (condition.test(obj)) {
1129+
future.complete(obj);
1130+
}
1131+
} catch (Exception e) {
1132+
future.completeExceptionally(e);
1133+
}
1134+
};
1135+
// start an informer that supplies the tester with events and empty list handling
1136+
try (SharedIndexInformer<T> informer = this.createInformer(0, l -> {
1137+
if (l.getItems().isEmpty()) {
1138+
tester.accept(null);
1139+
}
1140+
}, new ResourceEventHandler<T>() {
1141+
1142+
@Override
1143+
public void onAdd(T obj) {
1144+
tester.accept(obj);
1145+
}
11261146

1127-
long remainingNanosToWait = timeUnit.toNanos(amount);
1128-
while (remainingNanosToWait > 0) {
1129-
1130-
T item = fromServer().get();
1131-
if (condition.test(item)) {
1132-
return item;
1147+
@Override
1148+
public void onUpdate(T oldObj, T newObj) {
1149+
tester.accept(newObj);
11331150
}
11341151

1135-
final WaitForConditionWatcher<T> watcher = new WaitForConditionWatcher<>(condition);
1136-
final long startTime = System.nanoTime();
1137-
try (Watch ignored = watch(KubernetesResourceUtil.getResourceVersion(item), watcher)) {
1138-
return watcher.getFuture().get(remainingNanosToWait, NANOSECONDS);
1139-
} catch (ExecutionException e) {
1140-
Throwable cause = e.getCause();
1141-
if (cause instanceof WatcherException && ((WatcherException)cause).isHttpGone()) {
1142-
LOG.debug("Restarting the watch due to http gone");
1143-
remainingNanosToWait -= (System.nanoTime() - startTime);
1144-
continue;
1145-
}
1146-
throw KubernetesClientException.launderThrowable(cause);
1147-
} catch (TimeoutException e) {
1148-
break;
1152+
@Override
1153+
public void onDelete(T obj, boolean deletedFinalStateUnknown) {
1154+
tester.accept(null);
11491155
}
1156+
})) {
1157+
// prevent unnecessary watches
1158+
future.whenComplete((r,t) -> informer.stop());
1159+
informer.run();
1160+
return future.get(amount, timeUnit);
1161+
} catch (ExecutionException e) {
1162+
throw KubernetesClientException.launderThrowable(e.getCause());
1163+
} catch (TimeoutException e) {
1164+
T item = getItem();
1165+
if (item != null) {
1166+
throw new KubernetesClientTimeoutException(item, amount, timeUnit);
1167+
}
1168+
throw new KubernetesClientTimeoutException(getKind(), getName(), getNamespace(), amount, timeUnit);
11501169
}
1151-
1152-
LOG.debug("ran out of time waiting for watcher, wait condition not met");
1153-
throw new IllegalArgumentException(type.getSimpleName() + " with name:[" + name + "] in namespace:[" + namespace + "] matching condition not found!");
11541170
}
11551171

11561172
public void setType(Class<T> type) {
@@ -1179,30 +1195,41 @@ public Informable<T> withIndexers(Map<String, Function<T, List<String>>> indexer
11791195

11801196
@Override
11811197
public SharedIndexInformer<T> inform(ResourceEventHandler<T> handler, long resync) {
1182-
// convert the name into something listable
1183-
FilterWatchListDeletable<T, L> baseOperation =
1184-
getName() == null ? this : withFields(Collections.singletonMap("metadata.name", getName()));
1198+
DefaultSharedIndexInformer<T, L> result = createInformer(resync, null, handler);
1199+
result.run();
1200+
return result;
1201+
}
11851202

1203+
private DefaultSharedIndexInformer<T, L> createInformer(long resync, Consumer<L> onList, ResourceEventHandler<T> handler) {
1204+
T item = getItem();
1205+
String name = (Utils.isNotNullOrEmpty(getName()) || item != null) ? checkName(item) : null;
1206+
11861207
// use the local context / namespace
11871208
DefaultSharedIndexInformer<T, L> informer = new DefaultSharedIndexInformer<>(getType(), new ListerWatcher<T, L>() {
11881209
@Override
11891210
public L list(ListOptions params, String namespace, OperationContext context) {
1190-
return baseOperation.list(params);
1211+
// convert the name into something listable
1212+
if (name != null) {
1213+
params.setFieldSelector("metadata.name="+name);
1214+
}
1215+
L result = BaseOperation.this.list(params);
1216+
if (onList != null) {
1217+
onList.accept(result);
1218+
}
1219+
return result;
11911220
}
11921221

11931222
@Override
11941223
public Watch watch(ListOptions params, String namespace, OperationContext context, Watcher<T> watcher) {
1195-
return baseOperation.watch(params, watcher);
1224+
return BaseOperation.this.watch(params, watcher);
11961225
}
11971226
}, resync, context, Runnable::run); // just run the event notification in the websocket thread
1198-
if (handler != null) {
1199-
informer.addEventHandler(handler);
1200-
}
12011227
if (indexers != null) {
12021228
informer.addIndexers(indexers);
12031229
}
1204-
// synchronous start list/watch must succeed in the calling thread
1205-
informer.run();
1230+
if (handler != null) {
1231+
informer.addEventHandler(handler);
1232+
}
12061233
return informer;
12071234
}
12081235
}

kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/base/WaitForConditionWatcher.java

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)