Programming

basic programming

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.

“No name matching” and “No subject alternative names present” when Connecting to a Secure Broker on Red Hat AMQ Broker

I had this error when using Artemis to connect to a secure broker on Openshift 4.10. AMQ Broker version is 7.10 and being installed by using Operator.

Caused by: java.security.cert.CertificateException: No name matching enterprise-rhamq-ss-0.enterprise-rhamq-hdls-svc.enterprise-rhamq.svc.cluster.local found
        at java.base/sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:234) [java.base:]
        at java.base/sun.security.util.HostnameChecker.match(HostnameChecker.java:103) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:458) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:418) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:292) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) [java.base:]
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1335) [java.base:]
        ... 28 more

And when accessing with a direct IP, it would give below error

Caused by: java.security.cert.CertificateException: No subject alternative names present
        at java.base/sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:142) [java.base:]
        at java.base/sun.security.util.HostnameChecker.match(HostnameChecker.java:101) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:458) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:432) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:292) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) [java.base:]
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1335) [java.base:]
        ... 28 more

Apparently it is due to Java certificate validation. Workaround is quite easy, just adding below configuration on Artemis URL and it should works,

verifyHost=false

How to Create a Rule Engine with Drools and Quarkus

There are multiple ways of creating a rule engine, some prefer a DMN model and some like to use DRL (Drools) files. On this writing, we are trying to see how can we leverage DRL files to validate a user’s risk profile based on some variables, and run it on top of Quarkus.

So lets start with a 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-drl</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
        <quarkus.platform.version>2.15.3.Final</quarkus.platform.version>
        <kogito.platform.group-id>org.kie.kogito</kogito.platform.group-id>
        <kogito.platform.artifact-id>kogito-quarkus-bom</kogito.platform.artifact-id>
        <kogito.platform.version>1.24.0.Final</kogito.platform.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.antlr</groupId>
                <artifactId>antlr4-runtime</artifactId>
                <version>4.9.2</version>
            </dependency>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>${kogito.platform.group-id}</groupId>
                <artifactId>${kogito.platform.artifact-id}</artifactId>
                <version>${kogito.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.kie.kogito</groupId>
            <artifactId>kogito-quarkus-rules</artifactId>
        </dependency>

        <!-- tests -->
        <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>
    </dependencies>
......
</project>

Next is creating a Java model, this is going to be the base model for variables that are going to contribute on our rule engine.

package com.edw.model;

public class Loan {

    private Integer age;
    private Double salary;

    private String risk;

    public Loan() {
    }

    public Loan(Integer age, Double salary, String risk) {
        this.age = age;
        this.salary = salary;
        this.risk = risk;
    }
	
	// other setter and getter

}

After that, we need to create a Rule Unit class. The purpose of it is to bind JSON request to a Java class, which can be modified by using our Rule Engine.

package com.edw.queries;

import com.edw.model.Loan;
import org.kie.kogito.rules.DataSource;
import org.kie.kogito.rules.DataStore;
import org.kie.kogito.rules.RuleUnitData;

public class LoanUnitData  implements RuleUnitData {
    private DataStore<Loan> loan;

    public LoanUnitData() {
        this(DataSource.createStore());
    }

    public LoanUnitData(DataStore<Loan> loan) {
        this.loan = loan;
    }

    public DataStore<Loan> getLoan() {
        return loan;
    }

    public void setLoan(DataStore<Loan> loan) {
        this.loan = loan;
    }
}

Next is to create our DRL file where we can write our whole Rule Engine there, and please make sure that it needs to be on the same package as our RuleUnit class.

package com.edw.queries;

unit LoanUnitData;

import com.edw.model.Loan;

rule HighRiskCustomerEverythingBelowMinimum when
   $L: /loan[age <= 20, salary <= 1000]
then
   modify($L) { setRisk("High") };
end

rule MediumRiskCustomerAgeBelowMinimum when
   $L: /loan[age <= 20, salary > 1000]
then
   modify($L) { setRisk("Medium") };
end

rule MediumRiskCustomerSalaryBelowMinimum when
   $L: /loan[age > 20, salary <= 1000]
then
   modify($L) { setRisk("Medium") };
end

rule LowRiskCustomerSalaryEverythingAboveMinimum when
   $L: /loan[age > 20, salary > 1000]
then
   modify($L) { setRisk("Low") };
end


query GetRisk
   $L: /loan
end

To validate whether our code works well or not, we can use below Unit Test

package com.edw;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;

@QuarkusTest
public class LoanTest {

    @Test
    public void testHighRisk() {
        given()
                .body("{\"loan\" : [{\"age\":17, \"salary\":900}]}")
                .contentType(ContentType.JSON)
                .log().all()
                .when()
                .post("/get-risk")
                .then()
                .statusCode(200).log().all()
                .body("risk", hasItem("High"));
    }
}

Or we can simply using a CURL to validate this,

$ curl  -X POST http://localhost:8080/get-risk  \
    -H 'content-type: application/json'  \
    -H 'accept: application/json'   \
    -d '{"loan" : [{"age":17, "salary":900}, {"age":39,"salary":1900.0}]}'

[{"age":39,"salary":1900.0,"risk":"Low"},{"age":17,"salary":900.0,"risk":"High"}]    

Code for this article can be found on below Git repository,

https://github.com/edwin/quarkus-drl

Have fun with Drools 🙂

Fail Fast Architecture using Openshift Container Platform

This week ive met an application that are being deployed as Pod in OCP but having a very unique behaviour, it keeps giving below error every one and a while.

[5585.146s][warning][os,thread] Failed to start thread "Unknown thread" 
         - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached.
[5585.147s][warning][os,thread] Failed to start the native thread for java.lang.Thread "HandshakeCompletedNotify-Thread"
[5586.153s][warning][os,thread] Failed to start thread "Unknown thread" 
         - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached.
[5586.154s][warning][os,thread] Failed to start the native thread for java.lang.Thread "HandshakeCompletedNotify-Thread"
[5589.672s][warning][os,thread] Failed to start thread "Unknown thread" 
         - pthread_create failed (EAGAIN) for attributes: stacksize: 1024k, guardsize: 0k, detached.
[5589.673s][warning][os,thread] Failed to start the native thread for java.lang.Thread "pool-4944-thread-1"
06:57:23,949 
         ERROR [io.undertow.request] (default task-34) UT005023: Exception handling request to /actuator/health: java.lang.OutOfMemoryError: 
         unable to create native thread: possibly out of memory or process/resource limits reached	

It seems that once this error happens, Pod will never recover from this condition. So Openshift need to find a way to handle this situation.

One workaround which i found is by utilizing Kubernetes Liveness Probe, which will detect application’s healthness.

      livenessProbe:
        httpGet:
          path: /actuator/health
          port: 8080
          scheme: HTTP
        initialDelaySeconds: 60
        timeoutSeconds: 3
        periodSeconds: 4
        successThreshold: 1
        failureThreshold: 2

For this configuration I am setting a 4 seconds delay between request and will wait for 3 seconds for reply from the corresponding Pod. And if Pod are unable to response to Openshift’s request for two times, Openshift will force terminate the Pod assuming that the Pod is in an unhealthy state.

This strategy makes applications restart quite often in a day, but at least it will be healthy again after being restarted forcefully.

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