Skip to content

Quicklab for demonstrating how to write and run Java functions on Apache Openwhisk on IBM Cloud

License

Notifications You must be signed in to change notification settings

IBMDeveloperBNL/java-serverless-quicklab

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

76 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Java Serverless QuickLab

Serverless functions enables you to decompose business operations into very fine grain chunks of code. Functions provide advantages to an organization by increasing agility and cost savings by allowing "scale to zero" when a function is no longer actively being used.

In this quicklab we will look at how to write Serverless Functions in Java and run them on Apache Openwhisk hosted on IBM Cloud.

0. Setup

  1. Go to https://cloud.ibm.com and click Log in on the top right of the page to log on with your IBM ID.

  2. Once logged in, open an IBM Cloud Shell by clicking (1)

    and wait until the session is ready to use.

  3. The default region for the shell is us-south. Europe based Trial Account users most likely have eu-gb as their region. If necessary, switch region by typing:

    ibmcloud target -r <your_region>

    where <your_region> is the value of the region your need to switch to. Contact the workshop organiser if you're not sure what your region is.

  4. Next, select the default resource group by typing:

     ibmcloud target -g default
  5. Then, create a Cloud Functions namespace:

    ibmcloud fn namespace create <YOUR_NAMESPACE_NAME>
  6. And complete the IBM Cloud CLI configuration by typing:

    ibmcloud fn namespace target <YOUR_NAMESPACE_NAME>
  7. Make sure you select the IBM Cloud Shell tab in your browser. On the command line clone the repo by typing:

    git clone https://github.com/IBMDeveloperBNL/java-serverless-quicklab
  8. Change directory to the cloned repo:

    cd java-serverless-quicklab

1. Executing a Serverless Function with the IBM Cloud CLI

  1. Run the following command to invoke a test function from the command-line:

    ibmcloud fn action invoke whisk.system/utils/echo -p message hello --result

    You should get back a result that looks like this:

    {
        "message": "hello"
    }

    This command verifies that IBM Cloud CLI is configured correctly . If this does not work, please contact the workshop organiser to provide assistance!

2. Build and Deploy your First Java Serverless Function

Let's build and deploy our own Java serverless function.

  1. Build and jar the Java application:

    ./mvnw package
  2. Deploy the function to IBM Cloud:

    ibmcloud fn action create helloJava target/hello-world-java.jar --main com.example.FunctionApp
  3. Execute the function:

    ibmcloud fn action invoke --result helloJava --param name World

    You should see:

    {
        "greetings": "Hello World"
    }

    --result means wait for the call to complete and show the results. Adding --result to the call implicitly makes it a blocking call as well. Next, omit that and see what you get back :)

3. Getting Familiar with OpenWhisk Commands

Let's take a deeper look at some of the common commands you will be using when running functions on OpenWhisk.

Executing Functions Asynchronously

So far we have been executing functions synchronously with the --result tag. Let's take a look at executing functions asynchronously.

  1. To execute a function in asynchronous mode simply omit --result when invoking the function:

    ibmcloud fn action invoke helloJava --param name World

    You should get a response that includes an id you can use to look up the result of the function later:

    ok: invoked /_/helloJava with id c51e11cf3bad42a39e11cf3badb2a3a3
    
  2. Use the below command to retrieve the result of the function invocation:

    ibmcloud fn activation result [id]

    You should get a response that looks something like this:

    {
        "greetings": "Hello World"
    }

    Note: Functions execute in asynchronous mode by default, you can also use the tag --blocking to explicitly invoke a function and then wait for it to complete.

Viewing Function Invocation Information

When invoking a function OpenWhisk is generating diagnostic information that can be used for tracking system usage, debugging, and other purposes.

  1. You can view the invocation information of the function we executed earlier with this command:

    ibmcloud fn activation get [id]

    You should get a response back that looks something like this:

    {
    	"namespace": "e7417dfb-0d1f-4a7e-b74a-c4c2d49c2b39",
    	"name": "helloJava",
    	"version": "0.0.1",
    	"subject": "edward_ciggaar@nl.ibm.com",
    	"activationId": "eaf1c26406874a2fb1c26406877a2f02",
    	"start": 1670338759140,
    	"end": 1670338759143,
    	"duration": 3,
    	"statusCode": 0,
    	"response": {
        	"status": "success",
        	"statusCode": 0,
        	"success": true,
        	"result": {
            	"greetings": "Hello World"
        	}
    	},
    	"logs": [
        	"2022-12-06T14:59:19.143076Z    stderr: Dec 06, 2022 2:59:19 PM com.example.FunctionApp main",
        	"2022-12-06T14:59:19.143104Z    stderr: INFO: invoked with params:"
    	],
    	"annotations": [
        	{
            	"key": "path",
            	"value": "e7417dfb-0d1f-4a7e-b74a-c4c2d49c2b39/helloJava"
        	},
        	{
            	"key": "waitTime",
            	"value": 320
        	},
        	{
            	"key": "transId",
            	"value": "10c98e55b43d67cbe963ed76e70d1c7b"
        	},
        	{
            	"key": "kind",
            	"value": "java:8"
        	},
        	{
            	"key": "timeout",
            	"value": false
        	},
        	{
            	"key": "limits",
            	"value": {
                	"concurrency": 1,
                	"logs": 10,
                	"memory": 256,
                	"timeout": 60000
            	}
        	}
    	],
    	"publish": false
    }	

Viewing Function Invocation Logs

ibmcloud fn activation get returns the logs from an invocation, but you can also just view the logs from invocation to make debugging a bit easier.

  1. To view the logs from an invocation run the following:

    ibmcloud fn activation logs [id]

    You should get a return thaty looks like this:

    2022-12-06T14:59:19.143076Z    stderr: Dec 06, 2022 2:59:19 PM com.example.FunctionApp main
    2022-12-06T14:59:19.143104Z    stderr: INFO: invoked with params:
    
  2. For longer running functions, you can tail the logs a function is producing with the following command:

    ibmcloud fn activation poll [id]

Retrieve Most Recent Function Execution

For shorthand purposes you can use the tag --last in-lieu of an id to retrieve information about an activation.

ibmcloud fn activation [get|result|logs] --last

Show Recent Function Invocations

You can view recent function invocations; id, function executed with the following:

ibmcloud fn activation list

Show Available Functions

You can view a list of all functions available in the current namespace with the following:

ibmcloud fn list

4. Creating a Web Action

Functions can be setup so they can be called directly over http as well. Let's take a look at how to do this.

  1. To allow a function to be executed over http run the following command:

    ibmcloud fn action update helloJava --web true
  2. To find the url to execute the function run the following:

    ibmcloud fn action get helloJava --url

    This command will return with the url to call you function:

    https://[region].functions.cloud.ibm.com/api/v1/web/SAMPLE_URL/default/helloJava
    
  3. Because this command returns JSON, we will need to append the end of the url with .json when calling it:

    curl -i https://[region].functions.cloud.ibm.com/api/v1/web/SAMPLE_URL/default/helloJava.json
  4. You might have noticed the result was different this time. Previous we have been passing the param name to the function when invoking it through the command line --param name World. We can accomplish this same behavior by passing a value as a query param (e.g. ?name=World):

    curl -i https://[region].functions.cloud.ibm.com/api/v1/web/SAMPLE_URL/default/helloJava.json?name=World

5. Using Functions to Return HTML

So far we have been just return JSON from our function, but functions are more flexible than that! Let's setup a function to return HTML:

  1. Change the current directory we are in to the root package of our Java app:

    cd src/main/java/com/example
  2. Create and open a new Java file WebHello.java with this command:

    vi WebHello.java
  3. Copy in the body of the Java file:

    package com.example;
    
    import com.google.gson.JsonObject;
    import com.google.gson.JsonPrimitive;
    
    import java.util.logging.Logger;
    
    /**
     * Hello FunctionApp
     */
    public class WebHello {
      protected static final Logger logger = Logger.getLogger("basic");
    
      public static JsonObject main(JsonObject args) {
    
        JsonObject response = new JsonObject();
        JsonPrimitive nameArg = args.getAsJsonPrimitive("name");
    
        String result;
        if (nameArg == null) {
          result = "Welcome to OpenWhisk";
        } else {
          result = "Hello " + nameArg.getAsString();
        }
        response.addProperty("body", "<html><body><h3>" + result + "</h3></body></html>");
    
    
        logger.info("invoked with params:");
        return response;
      }
    }
  4. Save and exit from vi by typing :wq

  5. Return to the root of the repo:

    cd ../../../../..
  6. Rebuild the Java .jar:

    ./mvnw package
  7. Functions can be updated if you want to change their behavior. Run the following command to add the new webHello action:

    ibmcloud fn action create webHello target/hello-world-java.jar --main com.example.WebHello --web true
  8. Get the url for the function with the following command like earlier:

    ibmcloud fn action get webHello --url
  9. Invoke the above URL directly from the your web browser.

  10. Like earlier, you can change the name query parameter to change the value being returned.

6. Viewing the Functions Dashboard

IBM Cloud provides a convenient dashboard for viewing your functions. You can access this dashboard here: https://cloud.ibm.com/functions/actions. It should list the following functions:

These functions have been created via the CLI at the start of this lab. Optionally, you could also define an API that can be further explored in the API section of the dashboard. Check out our deep-dive repo -- listed at the bottom of this lab -- if you want to know more about this topic.

  1. The serverless functions helloJava and webHello are both written in Java. Hence, the code cannot be viewed and changed via the dashboard. They can be invoked though.

    Invoke the function helloJava by clicking the function. Next, click Invoke.

    As you can see the result is similar to when the function is invoked via the command line.

  2. Next, change the Input by clicking Change Input and change the input to:

    {
       "name": "your name here.."
    }

    Change the value of name to your own name, or something you like, and click Apply. Click Invoke to invoke this function with the changed input. The result should be:

    {
       "greetings": "Hello your name here..."
    }

    Finally, return to the functions dashboard.

7. Speed up your Java Serverless Function with Quarkus

Use the command below to obtain a list of the most recent activations of your serverless functions.

ibmcloud fn activation list

The result should look similar to:

2022-12-06 15:18:15 9a072b3e597a48f5872b3e597a08f559 java:8    warm  4ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:18:13 cb78e963d78a44d6b8e963d78a64d6e7 java:8    warm  4ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:18:12 7c74cdc63a364fcdb4cdc63a364fcda1 java:8    warm  3ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:18:02 59f5aed9244b4679b5aed9244b56795e java:8    cold  361ms      success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:17:29 9ed06fe9ec50416e906fe9ec50e16e6a java:8    cold  841ms      success e7417dfb-0...49c2b39/helloJava:0.0.2
2022-12-06 15:14:45 ebe6953905454b1da695390545bb1d3f java:8    cold  793ms      success e7417dfb-0...49c2b39/webHello:0.0.1
2022-12-06 14:59:19 eaf1c26406874a2fb1c26406877a2f02 java:8    warm  3ms        success e7417dfb-0...49c2b39/helloJava:0.0.1
2022-12-06 14:59:10 055c80db6be547e19c80db6be5c7e1a3 java:8    cold  681ms      success e7417dfb-0...49c2b39/helloJava:0.0.1
2022-12-06 14:58:39 305b334700dc43309b334700dc9330ec nodejs:12 cold  32ms       success e7417dfb-0...49c2b39/echo:0.0.515

Check out the cold starts of your Java functions in this list. As you can see, they can take a relatively long time to complete.

Quarkus to the rescue

The following paragraph is taken from the https://quarkus.io website and explains very well in a nutshell what Quarkus is all about.

What is Quarkus

Traditional Java stacks were engineered for monolithic applications with long startup times and large memory requirements in a world where the cloud, containers, and Kubernetes did not exist. Java frameworks needed to evolve to meet the needs of this new world.

Quarkus was created to enable Java developers to create applications for a modern, cloud-native world. Quarkus is a Kubernetes-native Java framework tailored for GraalVM and HotSpot, crafted from best-of-breed Java libraries and standards. The goal is to make Java the leading platform in Kubernetes and serverless environments while offering developers a framework to address a wider range of distributed application architectures.

So, let's see how we can benefit by using Quarkus for our sample serverless function.

To run a Java function on OpenWhisk that is built with Quarkus, we need to create a so-called custom runtime image. This image needs to implement the Action interface. See Creating and invoking Docker actions for more info on the how to .

  1. For this lab, the image has already been prepared for you. So let's create a new function that uses our custom Quarkus runtime image.

    ibmcloud fn action create helloQuarkus --docker eciggaar/action-quarkus:v2.14.2 -m 128

    Note that the action is created with only 128M as maximum memory limit!!

  2. Next, invoke the action a couple of times to generate some activity. You might wanna replace the value of the name parameter with your own...

    ibmcloud fn action invoke helloQuarkus --result --param name Quarkus
  3. Now do the same for the regular Java action.

    ibmcloud fn action invoke helloJava --result --param name Quarkus
  4. Finally, retrieve the list of activations again to see the results.

    ibmcloud fn activation list

    This should result in output similar to:

    2022-12-06 15:20:53 c2889f3878054840889f387805a84080 java:8    warm  7ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
    2022-12-06 15:20:51 d2e865bc0ff74c9ea865bc0ff72c9e5d java:8    warm  5ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
    2022-12-06 15:20:44 cc5f892e2bd74f449f892e2bd74f4484 blackbox  warm  4ms        success e7417dfb-0...49c2b39/helloQuarkus:0.0.1
    2022-12-06 15:20:43 a226111e9e104aaea6111e9e10caae2e blackbox  warm  2ms        success e7417dfb-0...49c2b39/helloQuarkus:0.0.1
    2022-12-06 15:20:41 f8d37463225c4f1f937463225c6f1fc3 blackbox  cold  55ms       success e7417dfb-0...49c2b39/helloQuarkus:0.0.1
    2022-12-06 15:20:30 760935f57ac545bf8935f57ac545bfcb blackbox  cold  61ms       success e7417dfb-0...49c2b39/helloQuarkus:0.0.1
    2022-12-06 15:18:15 9a072b3e597a48f5872b3e597a08f559 java:8    warm  4ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
    2022-12-06 15:18:13 cb78e963d78a44d6b8e963d78a64d6e7 java:8    warm  4ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
    2022-12-06 15:18:12 7c74cdc63a364fcdb4cdc63a364fcda1 java:8    warm  3ms        success e7417dfb-0...49c2b39/helloJava:0.0.2
    2022-12-06 15:18:02 59f5aed9244b4679b5aed9244b56795e java:8    cold  361ms      success e7417dfb-0...49c2b39/helloJava:0.0.2
    2022-12-06 15:17:29 9ed06fe9ec50416e906fe9ec50e16e6a java:8    cold  841ms      success e7417dfb-0...49c2b39/helloJava:0.0.2
    2022-12-06 15:14:45 ebe6953905454b1da695390545bb1d3f java:8    cold  793ms      success e7417dfb-0...49c2b39/webHello:0.0.1
    2022-12-06 14:59:19 eaf1c26406874a2fb1c26406877a2f02 java:8    warm  3ms        success e7417dfb-0...49c2b39/helloJava:0.0.1
    2022-12-06 14:59:10 055c80db6be547e19c80db6be5c7e1a3 java:8    cold  681ms      success e7417dfb-0...49c2b39/helloJava:0.0.1
    2022-12-06 14:58:39 305b334700dc43309b334700dc9330ec nodejs:12 cold  32ms       success e7417dfb-0...49c2b39/echo:0.0.515	
    

    Take a look at the startup times for the cold starts of both the helloQuarkus and helloJava action -- you might have to scroll a bit to the right -- and notice the difference...


TIP: If you want to experiment yourself with developing Java functions using Quarkus, then check out this excellent blog on Serverless Java Functions with Quarkus and OpenWhisk by Niklas Heidloff.


CONGRATULATIONS!! πŸ˜ƒ πŸ‘

You successfully completed the lab!! If you want, you can continue with the optional steps below.

[Optional] Create a new function via the Cloud Functions dashboard

To explore the possibilities when creating cloud functions via the UI, click the Create button. On the next page, you can either create new triggers and/or sequences, but also new functions via quick templates or from scratch. Select the Quickstart Templates to continue and choose Hello World. You should see a screen similar to:

Now select a favourite language using the dropdown (1). We've chosen for NodeJS 10 in the screenshot above. Click Deploy (2) to create the new function written in NodeJS. It outputs practically the same as our helloJava function. When no input is given, clicking Invoke returns:

{
   "greeting": "Hello stranger!"
}

When there is input, the result is the same as for the helloJava function. Please see for yourself by invoking the hello-world function with some input as well.

Continue Learning

We only scratched the surface of all that is possible with serverless functions. Want to learn how to chain the execution of functions together? Or how to configure a trigger to have a function executed?

Then check out the following repo: https://github.com/eciggaar/go-serverless-with-java

Collaborator: Pratik Patel Github Twitter

About

Quicklab for demonstrating how to write and run Java functions on Apache Openwhisk on IBM Cloud

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%