fabric8

Fabric8 failed to watch custom resource in the junit test


I am trying to learn how to test custom resource watcher in the Fabric8, I follow the example from this link https://github.com/r0haaaan/kubernetes-mockserver-demo/blob/master/src/test/java/io/fabric8/demo/kubernetes/mockserver/CustomResourceMockTest.java

My custom resource is "UserACL", I am using Java junit5, this is my fabric8 version. implementation group: 'io.fabric8', name: 'kubernetes-client', version: '5.9.0' implementation group: 'io.fabric8', name: 'kubernetes-api', version: '3.0.12' testImplementation group: 'io.fabric8', name: 'kubernetes-server-mock', version: '5.9.0'

This test case failed, seem there is no any WatchEvent emitted, so countLatch never count down.

Could anybody help take a look and point out what's wrong here? Anything is missing in the following code. I appreciate it in advanced.

import io.fabric8.kubernetes.api.model.Condition;
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.WatchEvent;
import io.fabric8.kubernetes.api.model.WatchEventBuilder;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.WatcherException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import io.fabric8.kubernetes.internal.KubernetesDeserializer;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Kind;
import io.fabric8.kubernetes.model.annotation.Version;
import io.vertx.junit5.VertxExtension;
import java.net.HttpURLConnection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;

public class TestKafkaHelperUtilFourth {

  @Rule
  public KubernetesServer server = new KubernetesServer(true, true);

  @Test
  @DisplayName("Should watch all custom resources")
  public void testWatch() throws InterruptedException {
    // Given
    server.expect().withPath("/apis/custom.example.com/v1/namespaces/default/useracls?watch=true")
        .andUpgradeToWebSocket()
        .open()
        .waitFor(10L)
        .andEmit(new WatchEvent(getUserACL("test-resource"), "ADDED"))
        .waitFor(10L)
        .andEmit(new WatchEventBuilder()
            .withNewStatusObject()
            .withMessage("410 - the event requested is outdated")
            .withCode(HttpURLConnection.HTTP_GONE)
            .endStatusObject()
            .build()).done().always();
    KubernetesClient client = server.getClient();
    MixedOperation<
        UserACL,
            KubernetesResourceList<UserACL>,
            Resource<UserACL>>
        userAclClient = client.resources(UserACL.class);

    // When
    CountDownLatch eventRecieved = new CountDownLatch(1);
    KubernetesDeserializer.registerCustomKind("custom.example.com/v1", "UserACL", UserACL.class);
    Watch watch = userAclClient.inNamespace("default").watch(new Watcher<UserACL>() {
      @Override
      public void eventReceived(Action action, UserACL userAcl) {
        if (action.name().contains("ADDED"))
          eventRecieved.countDown();
      }

      @Override
      public void onClose(WatcherException e) { }
    });

    // Then
    eventRecieved.await(20, TimeUnit.SECONDS);
    Assertions.assertEquals(0, eventRecieved.getCount());
    watch.close();
  }

  private UserACL getUserACL(String resourceName) {
    UserACLSpec spec = new UserACLSpec();
    spec.setUserName("test-user-name");

    UserACL createdUserACL = new UserACL();
    createdUserACL.setMetadata(
        new ObjectMetaBuilder().withName(resourceName).build());
    createdUserACL.setSpec(spec);

    Condition condition = new Condition();
    condition.setMessage("Last reconciliation succeeded");
    condition.setReason("Successful");
    condition.setStatus("True");
    condition.setType("Successful");
    UserACLStatus status = new UserACLStatus();
    status.setCondition(new Condition[]{condition});
    createdUserACL.setStatus(status);

    return createdUserACL;
  }

  @Group("custom.example.com")
  @Version("v1")
  @Kind("UserACL")
  public static final class UserACL
      extends CustomResource<UserACLSpec, UserACLStatus> {

  }
  public static final class UserACLSpec {
    private String userName;

    public UserACLSpec() {}

    public String getUserName() {
      return userName;
    }

    public void setUserName(String userName) {
      this.userName = userName;
    }
  }
  public static final class UserACLStatus {
    Condition[] condition;

    public UserACLStatus() {};

    public Condition[] getCondition() {
      return condition;
    }

    public void setCondition(Condition[] condition) {
      this.condition = condition;
    }
  }
}


Solution

  • I checked your code; after resolving these problems I was able to get test working:

    1. You're setting MockServer expectations using server.expect() for mocking watch call. But you've initialized KubernetesMockServer to enable CRUD mode (this doesn't require any expectations). You need to do this instead (see false being passed as second argument which disables CRUD mode):
      @Rule
      public KubernetesServer server = new KubernetesServer(true, false);
    
    1. In Watch expectations path I can see that UserACL resource is a namespaced one. All Namespaced scope resources in KubernetesClient must implement io.fabric8.kubernetes.api.model.Namespaced interface:
      @Group("custom.example.com")
      @Version("v1")
      @Kind("UserACL")
      public static final class UserACL extends CustomResource<UserACLSpec, UserACLStatus> implements Namespaced { }
    
    1. (Optional) I'm not sure whether you're using JUnit4 or JUnit5, I also had to update org.junit.Test to org.junit.jupiter.api.Test and also add @EnableRuleMigrationSupport annotation in order to run this test on my JUnit5 project:
    @EnableRuleMigrationSupport
    public class TestKafkaHelperUtilFourth { 
    

    After making these changes, test seemed to run okay for me:

    $ mvn -Dtest=io.fabric8.demo.kubernetes.mockserver.TestKafkaHelperUtilFourth test
    
    
    [INFO] -------------------------------------------------------
    [INFO]  T E S T S
    [INFO] -------------------------------------------------------
    [INFO] Running io.fabric8.demo.kubernetes.mockserver.TestKafkaHelperUtilFourth
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    जुल॰ 08, 2022 10:29:31 अपराह्न okhttp3.mockwebserver.MockWebServer$2 execute
    INFO: MockWebServer[49697] starting to accept connections
    जुल॰ 08, 2022 10:29:31 अपराह्न okhttp3.mockwebserver.MockWebServer$3 processOneRequest
    INFO: MockWebServer[49697] received request: GET /apis/custom.example.com/v1/namespaces/default/useracls?watch=true HTTP/1.1 and responded: HTTP/1.1 101 Switching Protocols
    जुल॰ 08, 2022 10:29:31 अपराह्न okhttp3.mockwebserver.MockWebServer$2 acceptConnections
    INFO: MockWebServer[49697] done accepting connections: Socket closed
    [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.779 s - in io.fabric8.demo.kubernetes.mockserver.TestKafkaHelperUtilFourth