8/25/15

The first releases of the Fabric8 v2 have been using a JAX-RS based Kubernetes client that was using Apache CXF. The client was great, but we always wanted to provide something thinner, with less dependencies (so that its easier to adopt). We also wanted to give it a fecelift and build a DSL around it so that it becomes easier to use and read.

The domain model is a set of objects that represents the data that are exchanged between the client and Kubernetes / Openshift. The raw format of the data is JSON. These JSON objects are quite complex and their structure is pretty strict, so hand crafting them is not a trivial task.

We needed to have a way of manipulating these JSON objects in Java (and being able to take advantage of code completion etc) but also stay as close as possible to the original format. Using a POJO representation of the JSON objects can be used for manipulation, but it doesn't quite feel like JSON and is also not really usable for JSON with deep nesting. So instead, we decided to generate fluent builders on top of those POJOs that used the exact same structure with the original JSON.

Fluent builders are generated by a tiny project called sundrio, which I'll cover in a future post.

Getting an instance of the client
Getting an instance of the default client instance is pretty trivial since an empty constructor is provided. When the empty constructor is used the client will use the default settings which are:

Kubernetes URL

System property "kubernetes.master"

Environment variable "KUBERNETES_MASTER"

From ".kube/config" file inside user home.

Using DNS: "https://kubernetes.default.svc"

Service account path "/var/run/secrets/kubernetes.io/serviceaccount/"

More fine grained configuration can be provided by passing an instance of the Config object.

Here's an example of how to adapt any instance of the client to an instance of the OpenshiftClient:

The code above will work only if /oapi exists in the list of root paths returned by the Kubernetes Client (i.e. the client points to an open shift installation). If not it will throw an IllegalArugementException.

In case the user is writing code that is bound to Openshift he can always directly instantiate an Instance of the default openshift client.

Testing and Mocking

Mocking a client that is talking to an external system is a pretty common case. When the client is flat (doesn't support method chaining) mocking is trivial and there are tons of frameworks out there that can be used for the job. When using a DSL though, things get more complex and require a lot of boilerplate code to wire the pieces together. If the reason is not obvious, let's just say that with mocks you define the behaviour of the mock per method invocation. DSLs tend to have way more methods (with fewer arguments) compared to the equivalent Flat objects. That alone increases the work needed to define the behaviour. Moreover, those methods are chained together by returning intermediate objects, which means that they need to be mocked too, which further increases both the workload and the complexity.

To remove all the boilerplate and make mocking the client pretty trivial to use we combined the DSL of the client, with the DSL of a mocking framework: EasyMock. This means that the entry point to this DSL is the Kubernetes client DSL itself, but the terminal methods have been modified so that they return "Expectation Setters". An example should make this easier to comprehend.

The mocking framework can be easily combined with other Fabric8 components, like the CDI extension. You just have to create @Produces method that returns the mock.