Java

java

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.

How to Clear User Cache in Keycloak

Keycloak provides a very convenient method of reducing workload to either database or active directory, and that is by using cache mechanism. But sometimes we want to trigger clearing cache manually, for example when there is some changes into the user data which coming from external applications.

We can do that by going to Keycloak menu, click on Realm Settings, and go to Cache tabs. We can clear cache by clicking on the User Cache Clear button.

But becareful since it will clear the whole user-cache, and not a specific user only.

Connecting Spring Boot to an Infinispan Cluster

Infinispan, or its supported product which is Red Hat DataGrid, is a very strong in-memory data grid product which offers flexible deployment options and robust capabilities for storing, managing, and processing data. And to maintain high availability and fault tolerance, Infinispan provides a clustering mechanism which have a multiple members.

For this article, im trying to create a cluster which consist of 3 Infinispan instances and all instances are being installed by using docker images. First we need to pull a specific Infinispan image,

$ docker pull infinispan/server:14.0.2.Final

And run 3 different instances of Infinispan,

$ docker run -p 11222:11222 -e USER=admin -e PASS=password \
        --add-host=HOST:192.168.56.1 \ 
        infinispan/server:14.0.2.Final
		
$ docker run -p 11223:11222 -e USER=admin -e PASS=password \
        --add-host=HOST:192.168.56.1 \ 
        infinispan/server:14.0.2.Final		

$ docker run -p 11224:11222 -e USER=admin -e PASS=password \
        --add-host=HOST:192.168.56.1 \ 
        infinispan/server:14.0.2.Final

Next is login to one of Infinispan instances which is located in localhost:11222, and login with credential of “admin” and “password”. A successfully cluster will give this display,

Once every Infinispan instances are started, we can now focus on our Java project. Lets start with pom.xml

<?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-and-clustered-infinispan</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.7.Final</version.infinispan>
        <version.protostream>4.6.2.Final</version.protostream>
        <version.spring.boot3>3.0.4</version.spring.boot3>
    </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-boot3-starter-remote</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-query</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-remote-query-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.infinispan.protostream</groupId>
            <artifactId>protostream-processor</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-client-hotrod</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

And an application.properties, in here we can define how many instances of Infinispan that we are connecting to. For this sample, im putting 3 instances which are each having their own ip.

### server port
server.port=8080
spring.application.name=Spring Boot and Clustered Infinispan

## logging
logging.level.root=INFO
logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss} %magenta([%thread]) %highlight(%-5level) %logger.%M - %msg%n

# infinispan
infinispan.remote.server-list=172.17.0.2:11222;172.17.0.3:11222;172.17.0.4:11222
infinispan.remote.auth-username=admin
infinispan.remote.auth-password=password
infinispan.remote.marshaller=org.infinispan.commons.marshall.ProtoStreamMarshaller

And create several Spring Boot’s Java classes, such as main class, controllers, beans, and configs.

@EnableCaching
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}
@RestController
public class IndexController {

    @Autowired
    private RemoteCacheManager cacheManager;

    @GetMapping(path = "/")
    public HashMap index() {
        return new HashMap(){{
            put("hello", "world");
        }};
    }

    @GetMapping(path = "/get-user")
    public User getUsers(@RequestParam String name) {
        return (User) cacheManager.getCache("user-cache").getOrDefault(name, new User());
    }

    @GetMapping(path = "/add-user")
    public User addUsers(@RequestParam String name, @RequestParam Integer age, @RequestParam String address) {
        cacheManager.getCache("user-cache").put(name, new User(name, age, address));
        return (User) cacheManager.getCache("user-cache").getOrDefault(name, new User());
    }

}
public class User implements Serializable {
    private String name;

    private Integer age;

    private String address;

    public User() {
    }

    public User(String name, Integer age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    @ProtoField(number = 1, required = true)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @ProtoField(number = 2)
    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @ProtoField(number = 3)
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
@Configuration
public class InfinispanConfiguration {
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public InfinispanRemoteCacheCustomizer remoteCacheCustomizer() {
        return b -> {
            b.remoteCache("user-cache").marshaller(ProtoStreamMarshaller.class);
        };
    }
}
@Component
public class InfinispanInitializer implements CommandLineRunner {

    @Autowired
    private RemoteCacheManager cacheManager;

    @Override
    public void run(String...args) throws Exception {
        SerializationContext ctx = MarshallerUtil.getSerializationContext(cacheManager);
        RemoteCache<String, String> protoMetadataCache = cacheManager.getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME);

        String msgSchemaFile = null;
        try {
            ProtoSchemaBuilder protoSchemaBuilder = new ProtoSchemaBuilder();
            msgSchemaFile = protoSchemaBuilder.fileName("user.proto").packageName("user").addClass(User.class).build(ctx);
            protoMetadataCache.put("user.proto", msgSchemaFile);
        } catch (Exception e) {
            throw new RuntimeException("Failed to build protobuf definition from 'User class'", e);
        }

        String errors = protoMetadataCache.get(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX);
        if (errors != null) {
            throw new IllegalStateException("Some Protobuf schema files contain errors: " + errors + "\nSchema :\n" + msgSchemaFile);
        }
    }
}

Run the code and try do some curl to add and retrieve data from cache,

$ curl -kv http://localhost:8080/add-user?name=lele&age=14&address=Jogja
{"name":"lele","age":14,"address":"Jogja"} 

$ curl -kv http://localhost:8080/get-user?name=lele
{"name":"lele","age":14,"address":"Jogja"} 

And we can check the content of our cache from our dashboard,

Have fun with Infinispan.

https://github.com/edwin/spring-boot-and-clustered-infinispan

Java, SecureRandom, and Openshift

Had one unique case where generating a SecureRandom in Java is very very slow, the thing is that this never happens on a regular VM deployment, and only happens in a Pod deployment in Openshift 4.

This is original sample code that is slow,

    @GetMapping(path = "/secure-random")
    public HashMap secureRandom() {
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.setSeed(secureRandom.generateSeed(24));
        return new HashMap(){{
            put("random-value", String.format("%06d", secureRandom.nextInt(1000000)));
        }};
    }

The reason why it is slow is because SecureRandom is relying on the OS random generator which is relying on noise. And it seems that for our case, we are lacking of noise to make a good entropy therefore we arent able to generate a SecureRandom at all.

Our workaround for this is by using a Pseudo Random Number Generator (PRNG) from Java, and not relying on the OS at all. For this example, im using “SHA1PRNG”.

    @GetMapping(path = "/secure-random-new")
    public HashMap secureRandomNew() throws Exception {
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        secureRandom.setSeed(secureRandom.generateSeed(24));
        return new HashMap(){{
            put("random-value", String.format("%06d", secureRandom.nextInt(1000000)));
        }};
    }

Hope it helps.

Direcly Deploy Jar File to Openshift

Openshift provides a convenient method for deploying binary Java applications. Other than deploying application’s source code, it can also deploy Jar file directly. For this example, im trying to deploy a Spring Boot and Red Hat Fuse middleware which is located on below repository.

https://github.com/edwin/hello-world-fuse-on-ocp

After we clone it, we can build the repo into Jar file.

$ mvn clean package

It will later on create a Jar file with the name of hello-world-fuse-on-ocp-1.0-SNAPSHOT.jar, which we can deploy to Openshift later on.

Along the way, we can create Openshift BuildConfig by using below command, it will create an Application template using Java8 on UBI8 base image.

$ oc new-build --name=hello-world-fuse-on-ocp \
		--binary=true \ 
		--image-stream=openshift/ubi8-openjdk-8:1.10  \ 
		--strategy=source

Next is we can deploy our Jar file using below command,

$ oc start-build hello-world-fuse-on-ocp \ 
		--from-file=hello-world-fuse-on-ocp-1.0-SNAPSHOT.jar \ 
		--follow

And publish it,

$ oc new-app hello-world-fuse-on-ocp

$ oc create route edge \
		--service=hello-world-fuse-on-ocp

Lets say we have some code changes and we want to build and redeploy the Application, we can just rerun the start-build command, and deploying the latest jar file into Openshift.

$ oc start-build hello-world-fuse-on-ocp \ 
		--from-file=hello-world-fuse-on-ocp-1.1-LATEST-JAR.jar \ 
		--follow