From db675579a545d075cb1e30a0f286bc71e6a7c925 Mon Sep 17 00:00:00 2001 From: shawkins Date: Thu, 24 Jun 2021 07:35:05 -0400 Subject: [PATCH 1/2] fix #3101 making isWatching better reflect health --- CHANGELOG.md | 1 + .../client/informers/cache/Reflector.java | 18 ++++++++++++--- .../client/informers/cache/ReflectorTest.java | 22 ++++++++++++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e34c3798b4f..ab386cb2b2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * Fix #3186: WebSockets and HTTP connections are closed as soon as possible for Watches. * Fix #2937: Add `SharedInformerFactory#getExistingSharedIndexInformers` method to return list of registered informers * Fix #3239: Add the `Informable` interface for context specific dsl methods to create `SharedIndexInformer`s. +* Fix #3101: making isWatching a health check for the informer #### Dependency Upgrade * Fix #2741: Update Knative Model to v0.23.0 diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/informers/cache/Reflector.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/informers/cache/Reflector.java index ed0b2a60fce..5c8f6d429d6 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/informers/cache/Reflector.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/informers/cache/Reflector.java @@ -81,7 +81,6 @@ private synchronized void stopWatcher() { */ public void listSyncAndWatch() { running = true; - watching = false; final L list = getList(); final String latestResourceVersion = list.getMetadata().getResourceVersion(); lastSyncResourceVersion = latestResourceVersion; @@ -155,12 +154,21 @@ public void eventReceived(Action action, T resource) { @Override public void onClose(WatcherException exception) { - watchStopped(); // this close was triggered by an exception, // not the user, it is expected that the watch retry will handle this log.warn("Watch closing with exception", exception); - if (exception.isHttpGone()) { + boolean restarted = false; + try { + if (exception.isHttpGone()) { listSyncAndWatch(); + restarted = true; + } else { + running = false; // shouldn't happen, but it means the watch won't restart + } + } finally { + if (!restarted) { + watchStopped(); // report the watch as stopped after a problem + } } } @@ -176,5 +184,9 @@ public boolean reconnecting() { } } + + public ReflectorWatcher getWatcher() { + return watcher; + } } diff --git a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReflectorTest.java b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReflectorTest.java index ae72e742cc9..dc15c40dce2 100644 --- a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReflectorTest.java +++ b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReflectorTest.java @@ -21,6 +21,7 @@ import io.fabric8.kubernetes.api.model.PodListBuilder; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; +import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.base.OperationContext; import io.fabric8.kubernetes.client.informers.ListerWatcher; import org.junit.jupiter.api.Test; @@ -29,7 +30,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; class ReflectorTest { @@ -66,5 +66,25 @@ void testStateFlags() { assertFalse(reflector.isWatching()); assertFalse(reflector.isRunning()); } + + @Test + void testNonHttpGone() { + ListerWatcher mock = Mockito.mock(ListerWatcher.class); + PodList list = new PodListBuilder().withNewMetadata().withResourceVersion("1").endMetadata().build(); + Mockito.when(mock.list(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(list); + + Reflector reflector = + new Reflector<>(Pod.class, mock, Mockito.mock(SyncableStore.class), new OperationContext()); + + reflector.listSyncAndWatch(); + + assertTrue(reflector.isWatching()); + assertTrue(reflector.isRunning()); + + reflector.getWatcher().onClose(new WatcherException(null)); + + assertFalse(reflector.isWatching()); + assertFalse(reflector.isRunning()); + } } From 4271ce39e2b5a9e22584695c3330a04681a5907c Mon Sep 17 00:00:00 2001 From: shawkins Date: Fri, 25 Jun 2021 08:00:35 -0400 Subject: [PATCH 2/2] updating the cheatsheet --- doc/CHEATSHEET.md | 65 ++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/doc/CHEATSHEET.md b/doc/CHEATSHEET.md index b9add726e3c..c4cad211c0b 100644 --- a/doc/CHEATSHEET.md +++ b/doc/CHEATSHEET.md @@ -1831,7 +1831,7 @@ Kubernetes Client also provides `SharedInformer` support in order to stay update ```java SharedInformerFactory sharedInformerFactory = client.informers(); ``` -- Create `SharedIndexInformer` for some Kubernetes Resource(requires resource's class and resync period(when to check with server again while watching something). By default it watches in all namespaces.: +- Create `SharedIndexInformer` for some Kubernetes Resource(requires resource's class and resync period (emits a dummy update event on that interval so that the handler can act again). By default it watches in all namespaces.: ```java SharedIndexInformer podInformer = sharedInformerFactory.sharedIndexInformerFor(Pod.class, 30 * 1000L); podInformer.addEventHandler(new ResourceEventHandler() { @@ -1871,15 +1871,20 @@ dummyInformer.addEventHandler(new ResourceEventHandler() { } }); ``` -- Create namespaced `SharedIndexInformer` (informers specific to a particular `Namespace`): +- Start all registered informers: ```java -SharedInformerFactory sharedInformerFactory = client.informers(); -SharedIndexInformer podInformer = sharedInformerFactory.inNamespace("default").sharedIndexInformerFor( - Pod.class, - 30 * 1000L); -logger.info("Informer factory initialized."); +sharedInformerFactory.startAllRegisteredInformers(); +``` +- Stop all registered informers: +```java +sharedInformerFactory.stopAllRegisteredInformers(); +``` -podInformer.addEventHandler(new ResourceEventHandler() { +You are not limited to just creating cluster wide informers, if you want to be informed about a particular context then use the Informable interface and inform methods. + +- Create namespaced `SharedIndexInformer` (informers specific to a particular `Namespace`): +```java +SharedIndexInformer podInformer = client.pods().inNamespace("default").inform(new ResourceEventHandler() { @Override public void onAdd(Pod pod) { logger.info("Pod " + pod.getMetadata().getName() + " got added"); @@ -1894,8 +1899,9 @@ podInformer.addEventHandler(new ResourceEventHandler() { public void onDelete(Pod pod, boolean deletedFinalStateUnknown) { logger.info("Pod " + pod.getMetadata().getName() + " got deleted"); } -}); -} +}, 30 * 1000L); + +logger.info("Informer initialized."); ``` - Create Namespaced Informer for a Custom Resource(**Note:** Your CustomResource POJO must implement `Namespaced` interface like the one used in this example: [Dummy.java](https://github.com/fabric8io/kubernetes-client/blob/master/kubernetes-examples/src/main/java/io/fabric8/kubernetes/examples/crds/Dummy.java)) You should have your CustomResource type POJO annotated with group, version fields with respect to your CRD: @@ -1913,33 +1919,24 @@ public class Dummy extends CustomResource impleme ``` Then you should be able to use it like this: ```java -SharedIndexInformer dummyInformer = sharedInformerFactory.inNamespace("default").sharedIndexInformerForCustomResource(Dummy.class, 60 * 1000L); -dummyInformer.addEventHandler(new ResourceEventHandler() { - @Override - public void onAdd(Dummy dummy) { - System.out.printf("%s dummy added\n", dummy.getMetadata().getName()); - } - - @Override - public void onUpdate(Dummy oldDummy, Dummy newDummy) { - System.out.printf("%s dummy updated\n", oldDummy.getMetadata().getName()); - } +SharedIndexInformer dummyInformer = client.customResources(Dummy.class).inNamespace("default").inform(new ResourceEventHandler() { + @Override + public void onAdd(Dummy dummy) { + System.out.printf("%s dummy added\n", dummy.getMetadata().getName()); + } - @Override - public void onDelete(Dummy dummy, boolean deletedFinalStateUnknown) { - System.out.printf("%s dummy deleted \n", dummy.getMetadata().getName()); - } -}); -``` + @Override + public void onUpdate(Dummy oldDummy, Dummy newDummy) { + System.out.printf("%s dummy updated\n", oldDummy.getMetadata().getName()); + } -- Start all registered informers: -```java -sharedInformerFactory.startAllRegisteredInformers(); -``` -- Stop all registered informers: -```java -sharedInformerFactory.stopAllRegisteredInformers(); + @Override + public void onDelete(Dummy dummy, boolean deletedFinalStateUnknown) { + System.out.printf("%s dummy deleted \n", dummy.getMetadata().getName()); + } +}, 60 * 1000L); ``` +When using the inform methods the informers will already be started/running. ### List Options There are various options provided by Kubernetes Client API when it comes to listing resources. Here are some of the common examples provided: