Deploying Red Hat DataGrid 8 using Operator to Openshift 4 and Setting Up a Persistent Cache

Red Hat Data Grid, or its Open Source version which is Infinispan, is a good distributed cache and key-value NoSQL data store software developed by Red Hat which can be used as an embedded library or as a standalone server. It even can be deployed easily to a container management platform like Openshift by using Operator or Helm chart. And for this example, we are trying to deploy Red Hat Data Grid 8.4 to Openshift by using Operator.

First lets start by creating a namespace dedicated for Data Grid

$ oc new-project datagrid-ns

And create two new YAML file, one is datagrid-operator.yaml

apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
 name: datagrid
 namespace: datagrid-ns

and another one is datagrid-subscription.yaml

apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
 name: datagrid-operator
 namespace: datagrid-ns
spec:
 channel: 8.4.x
 installPlanApproval: Manual
 name: datagrid
 source: redhat-operators
 sourceNamespace: openshift-marketplace

apply them,

$ oc apply -f datagrid-operator.yaml
$ oc apply -f datagrid-subscription.yaml

it would generate a new datagrid item on “Installed Operators” page,

select “Upgrade” and “Approve” to install Data Grid Operator

a successful installation is going to looks like this,

next is creating a new Infinispan cluster, this is for sample purpose only therefore we disable TLS certificate to connect with a very minimum CPU and memory

kind: Infinispan
apiVersion: infinispan.org/v1
metadata:
  name: datagrid-cluster
  namespace: datagrid-ns
spec:
  replicas: 1
  security:
    endpointEncryption:
      type: None
  container:
    cpu: "500m:100m"
    memory: "1Gi:500Mi"

a successful configuration it would generate pods like this,

next is to expose its endpoint so it can be accessible from external

$ oc create route edge --service datagrid-cluster --hostname=datagrid.apps-crc.testing

the result is looks like this,

we can login by using credentials which is stored in Openshift’s Secret. If we are able to successfully login, we can see below Data Grid dashboard

Now lets focus on the Java part. For this, we are using Spring Boot and Java 17 which is defined in our pom.xml 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>spring-boot-with-datagrid-operator</artifactId>
    <version>1.0-SNAPSHOT</version>

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

        <version.infinispan>14.0.2.Final</version.infinispan>
        <version.protostream>4.6.2.Final</version.protostream>
        <version.spring.boot3>3.0.4</version.spring.boot3>

        <start-class>com.edw.Main</start-class>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${version.infinispan}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${version.spring.boot3}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- infinispan -->
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter-remote</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-jboss-marshalling</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-remote-query-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-api</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter-embedded</artifactId>
        </dependency>

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${version.spring.boot3}</version>
                <configuration>
                    <layout>JAR</layout>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

a Java file for configuration,

package com.edw.configuration;

import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.jboss.marshalling.commons.GenericJBossMarshaller;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InfinispanConfiguration {
    @Bean
    public RemoteCacheManager remoteCacheManager() {
        return new RemoteCacheManager(
                new ConfigurationBuilder()
                        .addServers("datagrid-cluster.datagrid-ns.svc.cluster.local:11222")
                        .security().authentication().username("developer").password("password")
                        .clientIntelligence(ClientIntelligence.HASH_DISTRIBUTION_AWARE)
                        .marshaller(new GenericJBossMarshaller())
                        .addJavaSerialWhiteList(".*")
                        .build());
    }
}

And this is perhaps most important class where we can define our caches. For this example, we are trying to create two different caches where one cache is persistent and another one is not.

package com.edw.helper;

import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.commons.configuration.XMLStringConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.UUID;

@Service
public class CacheHelper {

    private RemoteCacheManager cacheManager;

    @Autowired
    public CacheHelper (RemoteCacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

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

    public void populate() {
        // build cache without persistence
        RemoteCache cacheWithoutPersistence = cacheManager.administration().getOrCreateCache("cache-without-persistence",
                new XMLStringConfiguration("<distributed-cache name=\"cache-without-persistence\" mode=\"ASYNC\">\n" +
                        "\t<encoding media-type=\"application/x-jboss-marshalling\" />\n" +
                        "</distributed-cache>")
        );
        for (int i = 0; i < 50; i++) {
            cacheWithoutPersistence.put("key"+i, UUID.randomUUID().toString());
        }

        // build cache with persistence
        RemoteCache cacheWithPersistence = cacheManager.administration().getOrCreateCache("cache-with-persistence",
                new XMLStringConfiguration("<distributed-cache name=\"cache-with-persistence\" mode=\"ASYNC\">\n" +
                        "\t<encoding media-type=\"application/x-jboss-marshalling\" />\n" +
                        "\t<persistence passivation=\"false\">\n" +
                        "\t\t<file-store>\n" +
                        "\t\t  <index path=\"/opt/infinispan/server/data\" />\n" +
                        "\t\t  <data path=\"/opt/infinispan/server/data\" />\n" +
                        "\t\t</file-store>\n" +
                        "\t</persistence>\n" +
                        "</distributed-cache>")
        );
        for (int i = 0; i < 50; i++) {
            cacheWithPersistence.put("key"+i, UUID.randomUUID().toString());
        }
    }

    public HashMap getCacheWithoutPersistence() {
        RemoteCache<String, String> cache = cacheManager.getCache("cache-without-persistence");

        HashMap hashMap = new HashMap();
        for (Object key : cache.keySet()) {
            hashMap.put(key, cache.get(key));
        }
        return hashMap;
    }

    public HashMap getCacheWithPersistence() {
        RemoteCache<String, String> cache = cacheManager.getCache("cache-with-persistence");

        HashMap hashMap = new HashMap();
        for (Object key : cache.keySet()) {
            hashMap.put(key, cache.get(key));
        }
        return hashMap;
    }
}

with one controller file,

package com.edw.controller;

import com.edw.helper.CacheHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class IndexController {

    private CacheHelper cacheHelper;

    @Autowired
    public IndexController (CacheHelper cacheHelper) {
        this.cacheHelper = cacheHelper;
    }

    @GetMapping(path = "/cache-without-persistence")
    public HashMap cacheWithoutPersistence() {
        return cacheHelper.getCacheWithoutPersistence();
    }

    @GetMapping(path = "/cache-with-persistence")
    public HashMap cacheWithPersistence() {
        return cacheHelper.getCacheWithPersistence();
    }

    @GetMapping(path = "/populate")
    public HashMap populate() {
        cacheHelper.populate();
        return new HashMap() {{
            put("status", "success");
        }};
    }
}

After that we can build and deploy our Java application to Openshift,

Trigger populate endpoint from our Spring Boot to generate Caches and its contents,

$ curl -kv http://localhost:8080/populate
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /populate HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.76.1
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 14 Sep 2024 17:22:03 GMT
< 
* Connection #0 to host localhost left intact
{"status":"success"}

We can check the content of each Caches stores,

Now lets try to delete Data Grid pod and see whether all the cache data is gone or not,

$ oc delete po --grace-period=0 --force datagrid-cluster-0

We can see that cache-with-persistence Cache Store still having its data

while cache-without-persistence Cache Store is not having any data at all

Source code for this tutorial can be found here,

https://github.com/edwin/spring-boot-with-datagrid-operator

Leave a Comment

Your email address will not be published.