Skip to content

Commit f45ed4f

Browse files
authored
fix #3615: always requesting bookmarks (#3617)
* fix #3615: always requesting bookmarks * updating the tests and adding a changelog * updating the changelog for possible mock uri changes * removing errerant changes
1 parent c1505ba commit f45ed4f

File tree

11 files changed

+64
-51
lines changed

11 files changed

+64
-51
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* Fix #3636: ensure proper handling of LogWatch closure wrt its streams
1414

1515
#### Improvements
16+
* Fix #3615: opt into bookmarks by default
1617
* Fix #3600: add owner references support to HasMetadata
1718

1819
#### Dependency Upgrade
@@ -23,6 +24,7 @@
2324
* Fix #3593: Add support for Istio extension
2425

2526
#### _**Note**_: Breaking changes in the API
27+
* If you do not wish to receive bookmarks, then set ListOptions.allowWatchBookmarks=false - otherwise all Watches will default to requesting bookmarks. If supported by the api-server, bookmarks will avoid 410 exceptions and keep the watch alive longer. If you are using the mock framework with explicit uris, you may need to update your expected watch endpoints to include the parameter allowWatchBookmarks=true
2628
* Refactoring #3547: due to an abstraction layer added over okHttp, the following api changes were made:
2729
* OperationContext withOkHttpClient was removed, it should be needed to be directly called
2830
* PatchType.getMediaType was replaced with PatchType.getContentType

Diff for: kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/AbstractWatchManager.java

+11
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ public abstract class AbstractWatchManager<T extends HasMetadata> implements Wat
6767
private URL requestUrl;
6868

6969
private final AtomicBoolean reconnectPending = new AtomicBoolean(false);
70+
71+
private final boolean receiveBookmarks;
7072

7173
AbstractWatchManager(
7274
Watcher<T> watcher, BaseOperation<T, ?, ?> baseOperation, ListOptions listOptions, int reconnectLimit, int reconnectInterval, int maxIntervalExponent, Supplier<HttpClient> clientSupplier
@@ -77,6 +79,11 @@ public abstract class AbstractWatchManager<T extends HasMetadata> implements Wat
7779
this.resourceVersion = new AtomicReference<>(listOptions.getResourceVersion());
7880
this.currentReconnectAttempt = new AtomicInteger(0);
7981
this.forceClosed = new AtomicBoolean();
82+
this.receiveBookmarks = Boolean.TRUE.equals(listOptions.getAllowWatchBookmarks());
83+
// opt into bookmarks by default
84+
if (listOptions.getAllowWatchBookmarks() == null) {
85+
listOptions.setAllowWatchBookmarks(true);
86+
}
8087
this.baseOperation = baseOperation;
8188
this.requestUrl = baseOperation.getNamespacedUrl();
8289
this.listOptions = listOptions;
@@ -184,6 +191,10 @@ boolean isForceClosed() {
184191
}
185192

186193
void eventReceived(Watcher.Action action, HasMetadata resource) {
194+
if (!receiveBookmarks && action == Action.BOOKMARK) {
195+
// the user didn't ask for bookmarks, just filter them
196+
return;
197+
}
187198
// the WatchEvent deserialization is not specifically typed
188199
// modify the type here if needed
189200
if (resource != null && !baseOperation.getType().isAssignableFrom(resource.getClass())) {

Diff for: kubernetes-client/src/test/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImplTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ public void onClose(WatcherException cause) {
827827
// Then
828828
assertThat(eventReceived).hasValue("{\"kind\":\"Hello\",\"metadata\":{\"name\":\"test\"}}");
829829
assertThat(result).isNotNull();
830-
verify(builder, Mockito.times(1)).uri(URI.create("https://localhost:8443/apis/test.fabric8.io/v1alpha1/hellos?watch=true"));
830+
verify(builder, Mockito.times(1)).uri(URI.create("https://localhost:8443/apis/test.fabric8.io/v1alpha1/hellos?allowWatchBookmarks=true&watch=true"));
831831
}
832832

833833
private void mockCallWithResponse(int code) throws IOException {

Diff for: kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceTest.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ void testStatusUpdate() throws IOException {
312312
@DisplayName("Should be able to watch some resource in a namespace with null name, labelSelector and ListOptions")
313313
void testWatchAllResource() throws IOException, InterruptedException {
314314
// Given
315-
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?watch=true")
315+
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?allowWatchBookmarks=true&watch=true")
316316
.andUpgradeToWebSocket()
317317
.open()
318318
.waitFor(WATCH_EVENT_PERIOD)
@@ -340,7 +340,7 @@ public void onClose(WatcherException cause) { }
340340
@DisplayName("Should be able to watch some resource in a namespace")
341341
void testWatchAllResourceInNamespace() throws IOException, InterruptedException {
342342
// Given
343-
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?watch=true")
343+
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?allowWatchBookmarks=true&watch=true")
344344
.andUpgradeToWebSocket()
345345
.open()
346346
.waitFor(WATCH_EVENT_PERIOD)
@@ -369,7 +369,7 @@ public void onClose(WatcherException cause) { }
369369
@DisplayName("Should be able to watch a single resource with some name")
370370
void testWatchSingleResource() throws IOException, InterruptedException {
371371
// Given
372-
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos"+ "?fieldSelector=" + Utils.toUrlEncoded("metadata.name=example-hello")+"&watch=true")
372+
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos"+ "?fieldSelector=" + Utils.toUrlEncoded("metadata.name=example-hello")+"&allowWatchBookmarks=true&watch=true")
373373
.andUpgradeToWebSocket()
374374
.open()
375375
.waitFor(WATCH_EVENT_PERIOD)
@@ -397,7 +397,7 @@ public void onClose(WatcherException cause) { }
397397
@DisplayName("Should be able to watch with labelSelectors")
398398
void testWatchWithLabels() throws IOException, InterruptedException {
399399
// Given
400-
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?labelSelector="+ Utils.toUrlEncoded("foo=bar")+ "&watch=true")
400+
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?labelSelector="+ Utils.toUrlEncoded("foo=bar")+ "&allowWatchBookmarks=true&watch=true")
401401
.andUpgradeToWebSocket()
402402
.open()
403403
.waitFor(WATCH_EVENT_PERIOD)
@@ -426,7 +426,7 @@ public void onClose(WatcherException cause) { }
426426
void testWatchSomeResourceVersion() throws IOException, InterruptedException {
427427
// Given
428428
String watchResourceVersion = "1001";
429-
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?resourceVersion=" + watchResourceVersion + "&watch=true")
429+
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?resourceVersion=" + watchResourceVersion + "&allowWatchBookmarks=true&watch=true")
430430
.andUpgradeToWebSocket()
431431
.open()
432432
.waitFor(WATCH_EVENT_PERIOD)
@@ -456,7 +456,7 @@ public void onClose(WatcherException cause) { }
456456
void testWatchNamespaceAndSomeResourceVersion() throws IOException, InterruptedException {
457457
// Given
458458
String watchResourceVersion = "1001";
459-
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?resourceVersion=" + watchResourceVersion + "&watch=true")
459+
server.expect().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos?resourceVersion=" + watchResourceVersion + "&allowWatchBookmarks=true&watch=true")
460460
.andUpgradeToWebSocket()
461461
.open()
462462
.waitFor(WATCH_EVENT_PERIOD)

Diff for: kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/DefaultSharedIndexInformerTest.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ void testNamespacedPodInformer() throws InterruptedException {
117117

118118
server.expect().withPath("/api/v1/namespaces/test/pods")
119119
.andReturn(200, getList(startResourceVersion, Pod.class)).once();
120-
server.expect().withPath("/api/v1/namespaces/test/pods?resourceVersion=" + startResourceVersion + "&watch=true")
120+
server.expect().withPath("/api/v1/namespaces/test/pods?resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true")
121121
.andUpgradeToWebSocket()
122122
.open()
123123
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -160,7 +160,7 @@ void testInformerWithNamespaceAndNameConfigured() throws InterruptedException {
160160

161161
server.expect().withPath("/api/v1/namespaces/test/pods?fieldSelector=" + Utils.toUrlEncoded("metadata.name=pod1"))
162162
.andReturn(200, getList(startResourceVersion, Pod.class)).once();
163-
server.expect().withPath("/api/v1/namespaces/test/pods?fieldSelector=" + Utils.toUrlEncoded("metadata.name=pod1") + "&resourceVersion=" + startResourceVersion + "&watch=true")
163+
server.expect().withPath("/api/v1/namespaces/test/pods?fieldSelector=" + Utils.toUrlEncoded("metadata.name=pod1") + "&resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true")
164164
.andUpgradeToWebSocket()
165165
.open()
166166
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -204,7 +204,7 @@ void testAllNamespacedInformer() throws InterruptedException {
204204

205205
server.expect().withPath("/api/v1/pods")
206206
.andReturn(200, new PodListBuilder().withNewMetadata().withResourceVersion(startResourceVersion).endMetadata().withItems(Collections.emptyList()).build()).once();
207-
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&watch=true")
207+
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true")
208208
.andUpgradeToWebSocket()
209209
.open()
210210
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -248,7 +248,7 @@ void shouldReconnectInCaseOf410() throws InterruptedException {
248248

249249
server.expect().withPath("/api/v1/pods")
250250
.andReturn(200, new PodListBuilder().withNewMetadata().withResourceVersion(startResourceVersion).endMetadata().withItems(Collections.emptyList()).build()).once();
251-
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&watch=true")
251+
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true")
252252
.andUpgradeToWebSocket()
253253
.open()
254254
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -259,7 +259,7 @@ void shouldReconnectInCaseOf410() throws InterruptedException {
259259
server.expect().withPath("/api/v1/pods")
260260
.andReturn(200, new PodListBuilder().withNewMetadata().withResourceVersion(mid2ResourceVersion).endMetadata().withItems(
261261
new PodBuilder().withNewMetadata().withNamespace("test").withName("pod1").withResourceVersion(endResourceVersion).endMetadata().build()).build()).times(2);
262-
server.expect().withPath("/api/v1/pods?resourceVersion=" + mid2ResourceVersion + "&watch=true")
262+
server.expect().withPath("/api/v1/pods?resourceVersion=" + mid2ResourceVersion + "&allowWatchBookmarks=true&watch=true")
263263
.andUpgradeToWebSocket()
264264
.open()
265265
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -302,7 +302,7 @@ void shouldDeleteIfMissingOnResync() throws InterruptedException {
302302

303303
server.expect().withPath("/api/v1/pods")
304304
.andReturn(200, new PodListBuilder().withNewMetadata().withResourceVersion(startResourceVersion).endMetadata().withItems(Collections.emptyList()).build()).once();
305-
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&watch=true")
305+
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true")
306306
.andUpgradeToWebSocket()
307307
.open()
308308
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -348,7 +348,7 @@ void testHasSynced() {
348348
String startResourceVersion = "1000", endResourceVersion = "1001";
349349
server.expect().withPath("/api/v1/namespaces/test/pods")
350350
.andReturn(200, new PodListBuilder().withNewMetadata().withResourceVersion(startResourceVersion).endMetadata().withItems(Collections.emptyList()).build()).once();
351-
server.expect().withPath("/api/v1/namespaces/test/pods?resourceVersion=" + startResourceVersion + "&watch=true")
351+
server.expect().withPath("/api/v1/namespaces/test/pods?resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true")
352352
.andUpgradeToWebSocket()
353353
.open()
354354
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -834,7 +834,7 @@ void testReconnectAfterOnCloseException() throws InterruptedException {
834834
.andReturn(200, new PodListBuilder().withNewMetadata().withResourceVersion(startResourceVersion).endMetadata().withItems(Collections.emptyList()).build()).once();
835835

836836
// initial watch - terminates with an exception
837-
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&watch=true")
837+
server.expect().withPath("/api/v1/pods?resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true")
838838
.andUpgradeToWebSocket()
839839
.open()
840840
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -844,7 +844,7 @@ void testReconnectAfterOnCloseException() throws InterruptedException {
844844
.done().always();
845845

846846
// should pick this up after the termination
847-
server.expect().withPath("/api/v1/pods?resourceVersion=" + midResourceVersion + "&watch=true")
847+
server.expect().withPath("/api/v1/pods?resourceVersion=" + midResourceVersion + "&allowWatchBookmarks=true&watch=true")
848848
.andUpgradeToWebSocket()
849849
.open()
850850
.waitFor(WATCH_EVENT_EMIT_TIME)
@@ -991,7 +991,7 @@ private <T extends HasMetadata> void setupMockServerExpectationsWithVersion(Clas
991991
watchUrl += "?";
992992
}
993993

994-
watchUrl += "resourceVersion=" + startResourceVersion + "&watch=true";
994+
watchUrl += "resourceVersion=" + startResourceVersion + "&allowWatchBookmarks=true&watch=true";
995995
server.expect().withPath(watchUrl)
996996
.andUpgradeToWebSocket()
997997
.open()

Diff for: kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/EventTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ void watch() throws InterruptedException {
5656
.build();
5757

5858
server.expect()
59-
.withPath("/api/v1/namespaces/ns1/events?watch=true")
59+
.withPath("/api/v1/namespaces/ns1/events?allowWatchBookmarks=true&watch=true")
6060
.andUpgradeToWebSocket().open().waitFor(50)
6161
.andEmit(new WatchEvent(testEvent, "ADDED"))
6262
.done().once();

Diff for: kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/InformTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void testInformPodWithLabel() throws InterruptedException {
6161
.once();
6262

6363
server.expect()
64-
.withPath("/api/v1/namespaces/test/pods?labelSelector=my-label&resourceVersion=1&watch=true")
64+
.withPath("/api/v1/namespaces/test/pods?labelSelector=my-label&resourceVersion=1&allowWatchBookmarks=true&watch=true")
6565
.andUpgradeToWebSocket()
6666
.open()
6767
.waitFor(EVENT_WAIT_PERIOD_MS)
@@ -114,7 +114,7 @@ void testInformGeneric() throws InterruptedException {
114114
.once();
115115

116116
server.expect()
117-
.withPath("/apis/demos.fabric8.io/v1/namespaces/test/dummies?labelSelector=my-label&resourceVersion=1&watch=true")
117+
.withPath("/apis/demos.fabric8.io/v1/namespaces/test/dummies?labelSelector=my-label&resourceVersion=1&allowWatchBookmarks=true&watch=true")
118118
.andUpgradeToWebSocket()
119119
.open()
120120
.waitFor(EVENT_WAIT_PERIOD_MS)
@@ -176,7 +176,7 @@ void testGenericWithKnownType() throws InterruptedException {
176176
.once();
177177

178178
server.expect()
179-
.withPath("/api/v1/namespaces/test/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&watch=true")
179+
.withPath("/api/v1/namespaces/test/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&allowWatchBookmarks=true&watch=true")
180180
.andUpgradeToWebSocket()
181181
.open()
182182
.waitFor(EVENT_WAIT_PERIOD_MS)
@@ -235,7 +235,7 @@ void testRunnableInformer() throws InterruptedException {
235235
.once();
236236

237237
server.expect()
238-
.withPath("/api/v1/namespaces/test/pods?labelSelector=my-label&resourceVersion=1&watch=true")
238+
.withPath("/api/v1/namespaces/test/pods?labelSelector=my-label&resourceVersion=1&allowWatchBookmarks=true&watch=true")
239239
.andUpgradeToWebSocket()
240240
.open()
241241
.waitFor(EVENT_WAIT_PERIOD_MS)

Diff for: kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/PodTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ void testWatch() throws InterruptedException {
362362
.addToItems(pod1)
363363
.build()
364364
).once();
365-
server.expect().withPath("/api/v1/namespaces/test/pods?fieldSelector=metadata.name%3Dpod1&watch=true")
365+
server.expect().withPath("/api/v1/namespaces/test/pods?fieldSelector=metadata.name%3Dpod1&allowWatchBookmarks=true&watch=true")
366366
.andUpgradeToWebSocket()
367367
.open()
368368
.waitFor(50).andEmit(new WatchEvent(pod1, "DELETED"))
@@ -436,7 +436,7 @@ void testWait() throws InterruptedException {
436436

437437
server.expect().get().withPath("/api/v1/namespaces/test/pods?fieldSelector=metadata.name%3Dpod1").andReturn(200, notReady).once();
438438

439-
server.expect().get().withPath("/api/v1/namespaces/test/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&watch=true").andUpgradeToWebSocket()
439+
server.expect().get().withPath("/api/v1/namespaces/test/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&allowWatchBookmarks=true&watch=true").andUpgradeToWebSocket()
440440
.open()
441441
.waitFor(50).andEmit(new WatchEvent(ready, "MODIFIED"))
442442
.done()

Diff for: kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/ResourceListTest.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,13 @@ void testSuccessfulWaitUntilCondition() throws InterruptedException {
188188
ResourceTest.list(server, noReady1);
189189
ResourceTest.list(server, noReady2);
190190

191-
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&watch=true").andUpgradeToWebSocket()
191+
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&allowWatchBookmarks=true&watch=true").andUpgradeToWebSocket()
192192
.open()
193193
.waitFor(500).andEmit(new WatchEvent(ready1, "MODIFIED"))
194194
.done()
195195
.once();
196196

197-
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod2&resourceVersion=1&watch=true").andUpgradeToWebSocket()
197+
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod2&resourceVersion=1&allowWatchBookmarks=true&watch=true").andUpgradeToWebSocket()
198198
.open()
199199
.waitFor(500).andEmit(new WatchEvent(ready2, "MODIFIED"))
200200
.done()
@@ -234,15 +234,15 @@ void testPartialSuccessfulWaitUntilCondition() {
234234
.build();
235235

236236
// This pod has a non-retryable error.
237-
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&watch=true")
237+
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&allowWatchBookmarks=true&watch=true")
238238
.andUpgradeToWebSocket()
239239
.open()
240240
.waitFor(500).andEmit(new WatchEvent(gone, "ERROR"))
241241
.done()
242242
.once();
243243

244244
// This pod succeeds.
245-
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod2&resourceVersion=1&watch=true")
245+
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod2&resourceVersion=1&allowWatchBookmarks=true&watch=true")
246246
.andUpgradeToWebSocket()
247247
.open()
248248
.waitFor(500).andEmit(new WatchEvent(ready2, "MODIFIED"))
@@ -284,14 +284,14 @@ void testAllFailedWaitUntilCondition() {
284284
.build();
285285

286286
// Both pods have a non-retryable error.
287-
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&watch=true")
287+
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod1&resourceVersion=1&allowWatchBookmarks=true&watch=true")
288288
.andUpgradeToWebSocket()
289289
.open()
290290
.waitFor(500).andEmit(new WatchEvent(gone, "ERROR"))
291291
.done()
292292
.once();
293293

294-
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod2&resourceVersion=1&watch=true")
294+
server.expect().get().withPath("/api/v1/namespaces/ns1/pods?fieldSelector=metadata.name%3Dpod2&resourceVersion=1&allowWatchBookmarks=true&watch=true")
295295
.andUpgradeToWebSocket()
296296
.open()
297297
.waitFor(500).andEmit(new WatchEvent(gone, "ERROR"))

0 commit comments

Comments
 (0)