3 minute read Published:

Use Java for Helm Tests

Have you heard of this new programming language called java-script? javascript

Recently, I converted a legacy project to Kubernetes. The design of the service was somewhat flaky. It relied on 4 different directory mounts, one of which was a Windows Share mounted as a NFS volume. Have you every tried to resolved permission of a file between Windows and *nix? Don’t. The project is essentially a plain service, allowing for uploading and downloading file base on some specific business logic.

Helm allows you to run test after a Helm deployment and I wanted to see what it would look like to use Java to write that test. A Helm test is simply a pod that runs once and needs to return 0 to indicate success. So why not have it run Java. There are a couple of features, some newer (since version 11), that make the idea less of a stretch. To simplify this task you’ll need to rely on 1) asserts, 2) Java’s new built in HTTP Client, and 3) running a Java file without compiling (see JEP-330).

I could use native OS features, and do something like check to see if the files are mounted, but I wanted to take it a step further and make sure that the service I was running was able to see those files and serve them up. Using Java native HTTP Client, it would look something like:

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create(baseUrl + "/read"))
    .build();

client.send(request, HttpResponse.BodyHandlers.ofString());

Simple enough. In order to have Helm fail the deployment, your test must return something other than 0.

if (response.statusCode() != 200) { 
    System.out.println("failed with error code " + response.statusCode());
    System.exit(1) 
}

However, using assert statement are a clearer way of signaling intent. You won’t be confusing the program logic with the error state and this is pretty much what asserts were designed for.

assert response.statusCode() == 200 : "response should be 200, but found " + response.statusCode();

Now that we have a test, we need to get Helm to run it. And for that we need two things, 1) mount the Java file into a container, and 2) have that container run Java and pass in the service location. To get the Java file into our container we have to create a configmap and mount it as a volume, using the --set-file flag to point Helm at our file. Then Helm simply calls to a java container to run the test:

      command: ["/bin/bash", "-c"]
      args: ["cd /tmp; java -version; cat HelmTest.java; java -ea ./HelmTest.java $SERVICE"]
      env:
        - name: SERVICE
          value: "http://{{ $serviceName }}.{{ .Release.Namespace }}.svc.cluster.local"
      volumeMounts:
        - name: tests
          mountPath: "/tmp"
          readOnly: true

Above you’ll see where the scriptable nature of Java 11 comes in handy. (Note the -ea flag, which enabled assertions feature.) The other important point is the environment variable that we are mounting into the container. This let’s your Java test know where the service is running (ie.baseUrl).

And the helm command.

helm upgrade java-helm-test . --install --wait --debug --namespace default --set-file helmTest=HelmTest.java
helm test java-helm-test

All-in-all I was pretty please with this approach. No tricky classpath building, no 3rd-party dependencies, and the workflow cycle was pretty darn quick. I would tweak the test, run it against a old deployment, repeat. I added a decision to the beginning of my main method to determine if I was running the test locally or on Kubernetes. You can see a full example on github.

https://github.com/Scuilion/java-helm-test