Programming

basic programming

Integrating Infinispan, Prometheus, and Grafana

Infinispan 14, or its supported product which is Red Hat DataGrid 8.4, is already having a metrics endpoint API to be parsed and visualized. And on this article, we are trying to integrate those metrics with Prometheus and Grafana, and Generate a dashboard to displayed its statistics in almost real-time update.

The highlevel design perhaps would looks like this,

First we can start by starting 3 different Infinispan instances, we can use multiple ways of doing this such as with docker or podman, but for this scenario im creating 3 different folders which each contains a Red Hat DataGrid instances. Make sure to create a port offset to prevent their port from colliding, and change the servername for an easier maintenance.

$ cd ~/Documents/redhat-datagrid-8.4.6-server-1/bin
$ ./server.sh -c infinispan.xml

$ cd ~/Documents/redhat-datagrid-8.4.6-server-2/bin
$ ./server.sh -c infinispan.xml

$ cd ~/Documents/redhat-datagrid-8.4.6-server-3/bin
$ ./server.sh -c infinispan.xml

Once all those 3 started, we can try to login to one server and see wheter those 3 servers already form a cluster.

we can start by creating replicated or distributed caches on top of our newly created Infinispan cluster.

Next is creating Prometheus instance, and to make this activity easier, we are going to using Podman. Lets start with a prometheus.yaml file first, in here we need to define the location of our Infinispan instances. Im using “host.containers.internal” because Prometheus is running on a container, and going to access Infinspan instances which are running on the host instance.

# my global config
global:
  scrape_interval: 15s 
  evaluation_interval: 15s 
  

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

scrape_configs:  
  - job_name: "ispn01"
    static_configs:
      - targets: ["host.containers.internal:11222"]
  - job_name: "ispn02"
    static_configs:
      - targets: ["host.containers.internal:11223"]
  - job_name: "ispn03"
    static_configs:
      - targets: ["host.containers.internal:11224"]

And run our Prometheus using Podman,

podman run \
           -p 9090:9090 \
           -v /Users/Shared/prometheus.yml:/etc/prometheus/prometheus.yml \
           --network shared  \
		   prom/prometheus

We can validate whether our Prometheus runs well or not by accessing it page and do some queries,

Once successfully started, we can continue by installing our Grafana instance using Podman,

podman run  \
			-p 3000:3000 \ 
			--network shared \  
			grafana/grafana-enterprise

After that, we can access Grafana Dashboard directly

Next is setting-up Prometheus Datasource inside Grafana, where we need to put the name of our datasource, and also its connection URL. For this sample, we are putting Prometheus container’s IP inside.

Make sure we copy the uid of this Datasource (we can see it at the browser’s URL), since we are going to use it in the dashboard.

Next is to create a new Grafana Dashboard for Infinispan, we can use import functionality to import existing dashboard in the form of a json file. For this sample, we can download from below Github repository.

https://github.com/edwin/infinispan-grafana-dashboard/

Dont forget to replace the existing hardcoded datasource uid with our existing Datasource uid

"datasource": {
        "type": "prometheus",
        "uid": "eb756797-79c7-4893-bcc9-c4bfdc7c457d"
      },

Save, and we can see our Grafana Dashboard

Unable to Start Podman on Mac

Just recently had below error when trying to run podman in my mac machine

$ podman machine start
Starting machine "podman-machine-default"
Waiting for VM ...
   Error: qemu exited unexpectedly with exit code 1, 
   stderr: qemu-system-x86_64: cannot create PID file: Cannot lock pid file: Resource temporarily unavailable

Workaround is quite simple, we can run this command

$ ps -edf | grep qemu-system | grep -v grep | awk '{print $2}' | xargs -I{} kill -9 {}; podman machine stop

And run podman again

$ podman machine start
Starting machine "podman-machine-default"
Waiting for VM ...
Mounting volume... /Users:/Users
Mounting volume... /private:/private
Mounting volume... /var/folders:/var/folders

Reference :

https://github.com/containers/podman/issues/16054

Integrate Wiremock and Quarkus in Testing for Mocking API Response

There are multipe ways of testing API connectivity from one service to another, in Integration Testing we can do direct connectivity or using an external API mocking such as Microcks. But for a simple Unit Testing, we can leverage Wiremock to do this.

And in this writings, i will try to integrate Wiremock with Quarkus for Unit Testing. First, lets start with a simple Maven pom file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>quarkus-and-wiremock</artifactId>
    <version>1.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <compiler-plugin.version>3.11.0</compiler-plugin.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <skipITs>true</skipITs>
        <surefire-plugin.version>3.1.2</surefire-plugin.version>

        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>com.redhat.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>3.2.6.SP1-redhat-00001</quarkus.platform.version>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-bom</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>
        </dependency>

        <!-- external call -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client-jackson</artifactId>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.wiremock</groupId>
            <artifactId>wiremock</artifactId>
            <version>3.3.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Lets create a simple Rest API client,

package com.edw.client;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import java.util.HashMap;

@Path("/")
@RegisterRestClient
public interface MockyService {
    @GET
    @Path("/")
    HashMap getDefaultMockData();
}

Its configuration file,

# default
quarkus.http.port=8080
quarkus.log.level=INFO
quarkus.log.category."com.edw".level=DEBUG
quarkus.log.category."org.apache.http".level=INFO

# disable sending anonymous statistics
quarkus.analytics.disabled=true

quarkus.rest-client."com.edw.client.MockyService".url=https://run.mocky.io/v3/99687692-4390-4ca2-816a-35c015fd72d0

And lets call it from our Controller,

package com.edw.controller;

import com.edw.client.MockyService;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.util.HashMap;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/")
public class IndexController {

    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    @Inject
    @RestClient
    MockyService mockyService;

    @GET
    @Path("/call")
    @Produces(MediaType.APPLICATION_JSON)
    public Response callExternalUrl() {
        logger.debug("calling external-service");
        return Response
                .status(200)
                .entity(mockyService.getDefaultMockData())
                .build();
    }
}

We can do some curl test by hitting the endpoint directly thru a curl command,

$ curl -kv http://localhost:8080/call
*   Trying ::1:8080...
* TCP_NODELAY set
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /call HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 17
<
* Connection #0 to host localhost left intact
{"hello":"world"}                                   

Now, lets try to do a unit testing to simulate this external API call. We can start by setting up a Wiremock server that will mocking a specific API endpoint

package com.edw.config;

import com.github.tomakehurst.wiremock.WireMockServer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

import java.util.HashMap;
import java.util.Map;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public class WiremockConfig implements QuarkusTestResourceLifecycleManager {
    private WireMockServer server;

    @Override
    public Map<String, String> start() {
        server = new WireMockServer(8082);
        server.start();
        server.stubFor(
                get(urlEqualTo("/"))
                        .willReturn(aResponse()
                                .withStatus(200)
                                .withHeader("Content-Type", "application/json")
                                .withBody("{\"hello\": \"mock\"}")));

        return new HashMap<>();
    }

    @Override
    public void stop() {
        if (server != null) {
            server.stop();
        }
    }
}

For testing, we create a new properties file which is pointint to our Wiremock server

# default
quarkus.log.level=INFO
quarkus.log.category."com.edw".level=DEBUG
quarkus.log.category."org.apache.http".level=DEBUG

# disable sending anonymous statistics
quarkus.analytics.disabled=true

quarkus.rest-client."com.edw.client.MockyService".url=http://localhost:8082

Lastly, lets create a new test case for this. Simulating a curl call to our endpoint

package com.edw.controller;

import com.edw.config.WiremockConfig;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.isA;

@QuarkusTest
@QuarkusTestResource(WiremockConfig.class)
public class IndexControllerTest {

    @Test
    public void testCall() {
        given()
            .when()
                .get("/call")
                .then()
            .statusCode(200)
                .body("hello", isA(String.class))
                .body("hello", equalTo("mock"))
            .log().all();
    }
}

The whole code for this post can be clone on below repository,

https://github.com/edwin/quarkus-and-wiremock

Deploying a Binary WAR File to Openshift 4 Using s2i

We can leverage s2i to deploy an existing war file into Openshift 4. How to do it is pretty much simple and straight forward, basically just running some oc command and you can have your war file deployed on Openshift.

But before we go far, we need to have a sample application to be deployed and for this sample, i have a hello world apps which is created by using Java Servlet.

https://github.com/edwin/jboss-eap-hello-world

We can clone this application, and build it using a simple maven command.

$ mvn clean package

Next step is create an empty folder where we would run our oc command,

$ mkdir -p jboss-eap/deployments

and we can put our ROOT.war file inside deployment folder,

$ tree .
.
+---- deployments
    +---- ROOT.war

Next is creating a new BuildConfig,

$ oc new-build --name=custom-eap-application \
	--binary=true --image-stream=jboss-eap74-openjdk11-openshift:latest

and start building it by using a start-build command, we can also repeat this step again if there is a new changes to our war file

$ oc start-build custom-eap-application --from-dir . --follow

and finally we can deploy our application directly by using new-app command,

$ oc new-app --name=custom-eap-application \ 
	--image-stream=edwin-ns/custom-eap-application:latest

So deployment can be done easily by using just 3 simple steps.