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