Cloud-oriented messaging on the Red Hat OpenShift platform using Quarkus and AMQ Online

Hello everyone! Here it is – our final post in the series about Quarkus! (By the way, check out our webinar "This is Quarkus – a Kubernetes-native Java framework". We will show how to get started "from scratch" or migrate existing solutions)

Cloud-oriented messaging on the Red Hat OpenShift platform using Quarkus and AMQ Online

In In the previous post, we discussed the relevant tools that can be used to quantitatively assess the improvements gained from modernizing Java applications.

Starting from version 0.17.0, Quarkus supports the use of the Advanced Message Queuing Protocol (AMQP), which is an open standard for business messaging between applications or organizations.

Red Hat AMQ Online is a service built on the open-source project EnMasse and implements a messaging mechanism on the Red Hat OpenShiftplatform. For more details on how it works, see here (EN). Today, we will show how to combine AMQ Online and Quarkus to build a modern messaging system based on OpenShift using two new technologies related to message processing.

It is assumed that you have already deployed AMQ Online on the OpenShift platform (if not, see the installation guide).

. To begin, we will create a Quarkus application that will represent a simple order processing system using reactive messaging. This application will include an order generator that sends orders to a message queue at fixed intervals, as well as an order processor that will handle messages from the queue and generate confirmations available for viewing in a browser.

After creating the application, we will show how to integrate messaging system configuration into it and utilize AMQ Online to initialize the required resources on this system.

Quarkus Application

Our Quarkus application runs on OpenShift and is a modified version of the amqp-quickstart. A complete example of the client part can be found here.

Order Generator

The generator monotonously sends incrementing order IDs to the address "orders" every 5 seconds.

@ApplicationScoped
public class OrderGenerator {
 
    private int orderId = 1;
 
    @Outgoing("orders")
    public Flowable generate() {
        return Flowable.interval(5, TimeUnit.SECONDS)
        .map(tick -> orderId++);
    }
}

Order Processor

The order handler is even simpler; it just returns the confirmation ID to the "confirmations" address.

@ApplicationScoped
public class OrderProcessor {
    @Incoming("orders")
    @Outgoing("confirmations")
    public Integer process(Integer order) {
        // The confirmation ID is equal to double the order ID <img draggable="false" class="emoji" alt=":-)" src="https://s.w.org/images/core/emoji/11.2.0/svg/1f642.svg">
        return order * 2;
    }
}

Confirmation Resources

A confirmation resource is an HTTP endpoint for listing confirmations generated as a result of our application's work.

@Path("/confirmations")
public class ConfirmationResource {
 
    @Inject
    @Stream("confirmations") Publisher orders;
 
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }
 
 
    @GET
    @Path("/stream")
    @Produces(MediaType.SERVER_SENT_EVENTS)
    public Publisher stream() {
        return orders;
    }
}

Settings

To connect to AMQ Online, our application will need some configuration data, specifically: Quarkus connector configuration, AMQP endpoint information, and client credentials. It's best to keep all configuration data in one place, but we'll intentionally separate them to show possible configuration options for the Quarkus application.

Connectors

Connector configuration can be provided at compile time using the application's properties file:

mp.messaging.outgoing.orders.connector=smallrye-amqp
mp.messaging.incoming.orders.connector=smallrye-amqp

To keep things simple, we'll use a message queue only for the "orders" address. The "confirmations" address in our application will use an in-memory queue.

AMQP Endpoint

At compile time, the host name and port number for the AMQP endpoint are unknown, so they need to be injected. The endpoint can be specified in the configmap created by AMQ Online; thus, we will define them through environment variables in the application manifest:

spec:
  template:
    spec:
      containers:
      - env:
        - name: AMQP_HOST
          valueFrom:
            configMapKeyRef:
              name: quarkus-config
              key: service.host
        - name: AMQP_PORT
          valueFrom:
            configMapKeyRef:
              name: quarkus-config
              key: service.port.amqp

Credentials

A service account token can be used to authenticate our application in OpenShift. To do this, we first need to create a custom ConfigSource that will read the authentication token from the pod's filesystem:

public class MessagingCredentialsConfigSource implements ConfigSource {
    private static final Set propertyNames;
 
    static {
        propertyNames = new HashSet();
        propertyNames.add("amqp-username");
        propertyNames.add("amqp-password");
    }
 
    @Override
    public Set getPropertyNames() {
        return propertyNames;
    }
 
    @Override
    public Map getProperties() {
        try {
            Map properties = new HashMap();
            properties.put("amqp-username", "@@serviceaccount@@");
            properties.put("amqp-password", readTokenFromFile());
            return properties;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
 
    @Override
    public String getValue(String key) {
        if ("amqp-username".equals(key)) {
            return "@@serviceaccount@@";
        }
        if ("amqp-password".equals(key)) {
            try {
                return readTokenFromFile();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }
        return null;
    }
 
    @Override
    public String getName() {
        return "messaging-credentials-config";
    }
 
    private static String readTokenFromFile() throws IOException {
        return new String(Files.readAllBytes(Paths.get("/var/run/secrets/kubernetes.io/serviceaccount/token")), StandardCharsets.UTF_8);
    }
}

Building and Deploying the Application

Since the application needs to be compiled into an executable file, a GraalVM virtual machine will be required. For more details on setting up the environment for this, see the relevant instructions in Quarkus Guide.

Then, following the instructions provided there, download the source code, build it, and deploy our application:

git clone https://github.com/EnMasseProject/enmasse-example-clients
cd enmasse-example-clients/quarkus-example-client
oc new-project myapp
mvn -Pnative -Dfabric8.mode=openshift -Dfabric8.build.strategy=docker package fabric8:build fabric8:resource fabric8:apply

After these commands, the application will be deployed, but it won't run until we set up the necessary messaging resources in AMQ Online.

Configuring the Messaging System

Now we need to specify the resources in the messaging system that our application requires. To do this, we need to create: 1) an address space to initialize the endpoint for the messaging system; 2) an address to configure the addresses we use in our application; 3) a messaging system user to set up client credentials.

Address Space

The AddressSpace object in AMQ Online is a group of addresses that share connection endpoints, as well as authentication and authorization policies. When creating an address space, you can specify how the messaging system endpoints will be provided:

apiVersion: enmasse.io/v1beta1
kind: AddressSpace
metadata:
  name: quarkus-example
spec:
  type: brokered
  plan: brokered-single-broker
  endpoints:
  - name: messaging
    service: messaging
    exports:
    - name: quarkus-config
      kind: configmap

Address

Addresses are used for sending and receiving messages. Each address has a type that defines its semantics, as well as a plan that specifies the amount of reserved resources. An address can be defined, for example, like this:

apiVersion: enmasse.io/v1beta1
kind: Address
metadata:
  name: quarkus-example.orders
spec:
  address: orders
  type: queue
  plan: brokered-queue

Messaging System User

To ensure that only trusted applications can send and receive messages to your addresses, a user must be created in the messaging system. For applications running on the cluster, clients can be authenticated using an OpenShift service account. A user "serviceaccount" can be defined, for example, like this:

apiVersion: user.enmasse.io/v1beta1
kind: MessagingUser
metadata:
  name: quarkus-example.app
spec:
  username: system:serviceaccount:myapp:default
  authentication:
    type: serviceaccount
  authorization:
  - operations: ["send", "recv"]
    addresses: ["orders"]

Permissions for Application Configuration

For AMQ Online to create the configmap we used to inject AMQP endpoint information, a role and role binding (Role and RoleBinding) must be specified:

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: quarkus-config
spec:
  rules:
  - apiGroups: [ "" ]
    resources: [ "configmaps" ]
    verbs: [ "create" ]
  - apiGroups: [ "" ]
    resources: [ "configmaps" ]
    resourceNames: [ "quarkus-config" ]
    verbs: [ "get", "update", "patch" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: quarkus-config
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: quarkus-config
subjects:
- kind: ServiceAccount
  name: address-space-controller
  namespace: amq-online-infra

How to Apply Configurations

You can apply the messaging system configuration like this:

cd enmasse-example-clients/quarkus-example-client
oc project myapp
oc apply -f src/main/resources/k8s/addressspace
oc apply -f src/main/resources/k8s/address

Application Verification

To ensure that the application has started, let's first check if the relevant addresses have been created and are active:

until [[ `oc get address quarkus-example.prices -o jsonpath='{.status.phase}'` == "Active" ]]; do echo "Not yet ready"; sleep 5; done

Then, let's check the application's route URL (simply open this address in the browser):

echo "http://$(oc get route quarkus-example-client -o jsonpath='{.spec.host}')/prices.html"

The browser should show that the tickets are being updated periodically as messages are sent and received by AMQ Online.

Summing up

So, we wrote a Quarkus application that uses AMQP for messaging, configured this application to run on the Red Hat OpenShift platform, and implemented its configuration based on the AMQ Online setup. Next, we created the manifests necessary to initialize the messaging system for our application.

This concludes our series on Quarkus, but there's much more exciting content ahead, so stay tuned!

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster