8. Introduction to Apache OpenWhisk – Serverless Architectures with Kubernetes

8. Introduction to Apache OpenWhisk

Learning Objectives

By the end of this chapter, you will be able to:

  • Run OpenWhisk with IBM Cloud Functions
  • Create, list, invoke, update, and delete OpenWhisk actions
  • Utilize and invoke OpenWhisk web actions and sequences
  • Automate OpenWhisk action invocation with feeds, triggers, and rules

This chapter covers Apache OpenWhisk and how to work with its actions, triggers, and packages.

Introduction to OpenWhisk

Until now in this book, we have learned about the Kubeless framework, which is an open source Kubernetes-native serverless framework. We discussed the Kubeless architecture, and created and worked with the Kubeless functions and triggers. In this chapter, we shall be learning about OpenWhisk, which is another open source serverless framework that can be deployed on top of Kubernetes.

OpenWhisk is an open source serverless framework that is part of the Apache Software Foundation. This was originally developed at IBM with the project code name of Whisk, and later branded as OpenWhisk once the source code was open sourced. Apache OpenWhisk supports many programming languages, including Ballerina, Go, Java, JavaScript, PHP, Python, Ruby, Swift, and .NET Core. It allows us to invoke functions written in these programming languages in response to events. OpenWhisk supports many deployment options, such as on-premises and cloud infrastructure.

There are four core components of OpenWhisk:

  • Actions: These contain application logic written in one of the supported languages that will be executed in response to events.
  • Sequences: These link multiple actions together to create more complex processing pipelines.
  • Triggers and rules: These automate the invocation of actions by binding them to external event sources.
  • Packages: These combine related actions together for distribution.

The following diagram illustrates how these components interact with each other:

Figure 8.1: OpenWhisk core components

In the next section, we will learn how to run Apache OpenWhisk with IBM Cloud Functions.

Running OpenWhisk with IBM Cloud Functions

OpenWhisk is a framework that can be deployed on-premises or in a cloud infrastructure. However, OpenWhisk is also available as a managed service from IBM, the creator of the OpenWhisk project. IBM Cloud Functions is the name for the managed OpenWhisk implementation on the IBM Cloud infrastructure. This book will use this service to deploy our serverless functions because IBM Cloud Functions is the easiest way to start working with OpenWhisk. We will first begin by setting up an IBM Cloud account.

Exercise 24: Setting Up an IBM Cloud Account

In this exercise, we are going to set up an account on IBM Cloud.

Note

A credit card is not required to register with IBM Cloud.

The following steps will help you complete the exercise:

  1. First, we need to register on IBM Cloud at https://cloud.ibm.com/registration. Then, fill in the required data and submit the form. It should look similar to the following screenshot:
    Figure 8.2: IBM Cloud registration page

    Once the registration is complete, you should see the following:

    Figure 8.3: IBM Cloud registration completion page
  2. At this point, we will receive an email with an activation link. Click on the Confirm account button to activate your account, as shown in the following figure:
    Figure 8.4: IBM Cloud Activation Email
  3. When you click on the Confirm account button in the email, we will be taken to the IBM Cloud welcome screen. Click on the Log in button to log in with the credentials used to register with IBM Cloud, as shown in the following figure:
    Figure 8.5: IBM Cloud welcome page
  4. Acknowledge the privacy data by clicking on the Proceed button, as shown in the following figure:
    Figure 8.6: IBM Cloud privacy policy
  5. You can skip the introduction video and proceed to the home page. Now you can click the hamburger icon () in the top-left corner of the screen and select Functions from the menu, as shown in the following figure:
    Figure 8.7: IBM Cloud home page
  6. This will take you to the IBM Cloud functions page (https://cloud.ibm.com/functions/), as shown in the following figure:
Figure 8.8: IBM Cloud Functions page

OpenWhisk offers a CLI named wsk to create and manage OpenWhisk entities. Next, we will install the OpenWhisk CLI, which will be used to interact with the OpenWhisk platform.

Exercise 25: Installing the IBM Cloud CLI

In this exercise, we are going to install the IBM Cloud CLI with the Cloud Functions plugin, which supports OpenWhisk:

  1. First, we need to download the compressed IBM Cloud CLI file. Use the curl command with the -Lo flag to download the CLI, as follows:

    $ curl -Lo ibm-cli.tar.gz  https://clis.cloud.ibm.com/download/bluemix-cli/0.18.0/linux64

    The output should be as follows:

    Figure 8.9: Downloading the IBM Cloud CLI
  2. Next, we will extract the tar.gz file using the tar command as follows:

    $ tar zxvf ibm-cli.tar.gz

    The output should be as follows:

    Figure 8.10: Extracting the IBM Cloud CLI
  3. Then move the ibmcloud executable file to the /usr/local/bin/ path, as shown in the following command:

    $ sudo mv Bluemix_CLI/bin/ibmcloud /usr/local/bin/ibmcloud

    The output should be as follows:

    Figure 8.11: Moving ibmcloud to /usr/local/bin
  4. Now we will log in to IBM Cloud using the IBM Cloud CLI. Execute the following command, replacing <YOUR_EMAIL> with the email address used when registering to IBM Cloud. Provide the email and password used during the registration phase when prompted and set the region number as 5 (us-south), as you can see in the following command:

    $ ibmcloud login -a cloud.ibm.com -o "<YOUR_EMAIL>" -s "dev"

    The output should be as follows:

    Figure 8.12: Logging in to IBM Cloud
  5. Now we will install the Cloud Functions plugin using the ibmcloud CLI, as shown in the following command. This plugin will be used when we work with OpenWhisk entities:

    $ ibmcloud plugin install cloud-functions

    The output should be as follows:

    Figure 8.13: Installing Cloud Functions
  6. Next, we will provide the target organization (the organization name is your email address) and the space (which defaults to dev) using the following command:

    $ ibmcloud target -o <YOUR_EMAIL> -s dev

    The output should be as follows:

    Figure 8.14: Setting the target organization and space
  7. Now the configurations are done. We can use ibmcloud wsk to interact with OpenWhisk entities, as shown in the following command:

    $ ibmcloud wsk action list

    The output should be as follows:

    Figure 8.15: Listing OpenWhisk actions

    Note

    In this book, we will be using the wsk command to manage OpenWhisk entities instead of the ibmcloud wsk command provided by IBM Cloud Functions. Both of them provide the same functionality. The only difference is that wsk is the standard CLI for OpenWhisk and ibmcloud fn is from the IBM Cloud Functions plugin.

  8. Let's create a Linux alias, wsk="ibmcloud wsk". First, open the ~/.bashrc file with your favorite text editor. In the following command, we will be using the vim text editor to open the file:

    vim ~/.bashrc

    Add the following line at the end of the file:

    alias wsk="ibmcloud wsk"

  9. Source the ~/.bashrc file to apply the changes, as shown in the following command:

    $ source ~/.bashrc

    The output should be as follows:

    Figure 8.16: Sourcing the bashrc file
  10. Now we should be able to invoke OpenWhisk with the wsk command. Execute the following command to verify the installation:

    $ wsk --help

    This will print the help page of the wsk command, as shown in the following figure:

Figure 8.17: Output for wsk command

Now, let's proceed to the next section on OpenWhisk actions.

OpenWhisk Actions

In OpenWhisk, actions are code snippets written by developers that will be executed in response to events. These actions can be written in any programming language supported by OpenWhisk:

  • Ballerina
  • Go
  • Java
  • JavaScript
  • PHP
  • Python
  • Ruby
  • Swift
  • .NET Core

Also, we can use a custom Docker image if our preferred language runtime is not supported by OpenWhisk yet. These actions will receive a JSON object as input, then perform the necessary processing within the action, and finally return a JSON object with the processed results. In the following sections, we will focus on how to write, create, list, invoke, update, and delete OpenWhisk actions using the wsk CLI.

Writing Actions for OpenWhisk

When writing OpenWhisk actions with your preferred language, there are few standards that you must follow. They are as follows:

  • Each action should have a function named main, which is the entry point of the action. The source code can have additional functions, but the main function will be executed once the action is triggered.
  • The function must return a JSON object as the response.

    Note

    In this chapter, we will be mainly using JavaScript to create the function code.

Let's look at an example in which we create a JavaScript code (random-number.js) that conforms to the rules we've just mentioned. This is a simple function that generates a random number between 0 to 1 and returns the generated number as the function's response:

function main() {

    var randomNumber = Math.random();

    return { number: randomNumber };

}

Here is a PHP function that conforms to the rules:

<?php

function main()

{

    $randomNumber = rand();

    return ["number" => $randomNumber];

}

Creating Actions on the OpenWhisk Framework

Now it's time to create an action on the OpenWhisk framework by using the action code written in the previous section. We will be using the wsk action create command, which has the following format:

$ wsk action create <action-name> <action-file-name>

<action-name> is the identifier of the action. It should be unique to prevent naming conflicts. <action-file-name> is the file that contains the source code of the action. Let's execute the following command to create an OpenWhisk action named randomNumber using the action source code in the random-number.js file:

$ wsk action create randomNumber random-number.js

The output we receive from this command looks like this:

Figure 8.18: Creating a randomNumber action

As we can see in the output, whenever an action is successfully created, the CLI prompt appropriately informs the reader of the status of the action.

The OpenWhisk framework will determine the runtime to execute the action based on the extension of the source code file. In the preceding scenario, the Node.js 10 runtime will be selected for the provided .js file. You can use the --kind flag with the wsk action create command if you want to override the default runtime selected by the OpenWhisk framework:

$ wsk action create secondRandomNumber random-number.js --kind nodejs:8

The output should be as follows:

Figure 8.19: Creating a randomNumber action with the nodejs:8 runtime

The preceding output indicates that secondRandomNumber was created successfully. At the end of this section, we have deployed two OpenWhisk actions.

Having learned how to create actions on the OpenWhisk framework, next we shall work on listing OpenWhisk actions.

Listing OpenWhisk Actions

In this section, we are going to list the OpenWhisk actions in our environment with the wsk CLI using the following command:

$ wsk action list

The output should be as follows:

Figure 8.20: Listing all actions

From the preceding output, we can see the two actions we created earlier with the names randomNumber and secondRandomNumber. The wsk action list command lists the actions and the runtime of these actions, such as nodejs:8 or nodejs:10. By default, the action list will be sorted based on the last update time, so the most recently updated action will be at the top of the list. If we want the list to be sorted alphabetically, we can use the --name-sort (or -n) flag, as shown in the following command:

$ wsk action list --name-sort

The output should be as follows:

Figure 8.21: Listing all actions sorted by name in ascending order

Invoking OpenWhisk Actions

Now our actions are ready to be invoked. OpenWhisk actions can be invoked in two ways using the wsk CLI:

  • Request-response
  • Fire-and-forget

The request-response method is synchronous; the action invocation will wait until the results are available. On the other hand, the fire-and-forget method is asynchronous. This will return an ID called the activation ID, which can be used later to get the results.

Here is the standard format of the wsk command to invoke the action:

$ wsk action invoke <action-name>

Request-Response Invocation Method

In the request-response method, the wsk action invoke command is used with the --blocking (or -b) flag, which asks the wsk CLI to wait for the invocation results:

$ wsk action invoke randomNumber --blocking

The preceding command will return the following output in the terminal, which contains the result returned from the method with other metadata about the method invocation:

ok: invoked /_/randomNumber with id 002738b1acee4abba738b1aceedabb60

{

    "activationId": "002738b1acee4abba738b1aceedabb60",

    "annotations": [

        {

            "key": "path",

            "value": "your_email_address_dev/randomNumber"

        },

        {

            "key": "waitTime",

            "value": 79

        },

        {

            "key": "kind",

            "value": "nodejs:10"

        },

        {

            "key": "timeout",

            "value": false

        },

        {

            "key": "limits",

            "value": {

                "concurrency": 1,

                "logs": 10,

                "memory": 256,

                "timeout": 60000

            }

        },

        {

            "key": "initTime",

            "value": 39

        }

    ],

    "duration": 46,

    "end": 1564829766237,

    "logs": [],

    "name": "randomNumber",

    "namespace": "your_email_address_dev",

    "publish": false,

    "response": {

        "result": {

            "number": 0.6488215545330562

        },

        "status": "success",

        "success": true

    },

    "start": 1564829766191,

    "subject": "your_email_address",

    "version": "0.0.1"

}

We can see the output ("number": 0.6488215545330562) returned by the main function within the response section of the returned JSON object. This is the random number generated by the JavaScript function that we wrote previously. The returned JSON object contains an activation ID ("activationId": "002738b1acee4abba738b1aceedabb60"), which we can use to get the results later. This output includes other important values, such as the action invocation status ("status": "success"), the start time ("start": 156482976619), the end time ("end": 1564829766237), and the execution duration ("duration": 46) of this action.

Note

We will discuss how to get the activation results using activationId in the Fire-and-Forget Invocation Method section.

We can use the --result (or -r) flag if we need to get the result of the action without the other metadata, as shown in the following code:

$ wsk action invoke randomNumber --result

The output should be as follows:

Figure 8.22: Invoking the randomNumber action using the request-and-response method

Fire-and-Forget Invocation Method

Action invocations with the fire-and-forget method do not wait for the result of the action. Instead, they return an activation ID that we can use to get the results of the action. This invocation method uses a similar command to the request-response method but without the --blocking (or -b) flag:

$ wsk action invoke randomNumber

The output should be as follows:

Figure 8.23: Invoking the randomNumber action using the fire-and-forget method

In the preceding result, we can see the returned activation ID of 2b90ade473e443bc90ade473e4b3bcff (please note that your activation ID will be different).

Now we can use the wsk activation get command to get the results for a given activation ID:

$ wsk activation get "<activation_id>"

You need to replace <activation_id> with the value returned when you invoked the function using the wsk action invoke command:

$ wsk activation get 2b90ade473e443bc90ade473e4b3bcff

ok: got activation 2b90ade473e443bc90ade473e4b3bcff

{

    "namespace": "sathsara89@gmail.com_dev",

    "name": "randomNumber",

    "version": "0.0.2",

    "subject": "sathsara89@gmail.com",

    "activationId": "2b90ade473e443bc90ade473e4b3bcff",

    "start": 1564832684116,

    "end": 1564832684171,

    "duration": 55,

    "statusCode": 0,

    "response": {

        "status": "success",

        "statusCode": 0,

        "success": true,

        "result": {

            "number": 0.05105974715780626

        }

    },

    "logs": [],

    "annotations": [

        {

            "key": "path",

            "value": "sathsara89@gmail.com_dev/randomNumber"

        },

        {

            "key": "waitTime",

            "value": 126

        },

        {

            "key": "kind",

            "value": "nodejs:10"

        },

        {

            "key": "timeout",

            "value": false

        },

        {

            "key": "limits",

            "value": {

                "concurrency": 1,

                "logs": 10,

                "memory": 256,

                "timeout": 60000

            }

        },

        {

            "key": "initTime",

            "value": 41

        }

    ],

    "publish": false

}

If you would prefer to retrieve only a summary of the activation, the --summary (or -s) flag should be provided with the wsk activation get command:

$ wsk activation get <activation-id> --summary

The output from the preceding command will print a summary of the activation details, as shown in the following screenshot:

Figure 8.24: The activation summary

The wsk activation result command returns only the results of the action, omitting any metadata:

$ wsk activation result <activation-id>

The output should be as follows:

Figure 8.25: The activation result

The wsk activation list command can be used to list all the activations:

$ wsk activation list

The output should be as follows:

Figure 8.26: Listing activations

The preceding command returns a list of activations sorted by the datetime of the activation's invocation. The following table describes the information provided by each column:

Figure 8.27: Column description

Updating OpenWhisk Actions

In this section, we will learn how to update the source code of an action once it has been created on the OpenWhisk platform. We might want to update the action for several reasons. There could be a bug in the code, or we may simply want to enhance the code. The wsk action update command can be used to update an OpenWhisk action using the wsk CLI:

$ wsk action update <action-name> <action-file-name>

We already have an action that prints a random number, which is defined in the random-number.js function. This function prints a value between 0 and 1, but what if we want to print a random number between 1 and 100? This can now be done using the following code:

function main() {

    var randomNumber = Math.floor((Math.random() * 100) + 1);

    return { number: randomNumber };

}

Then, we can execute the wsk action update command to update the randomNumber action:

$ wsk action update randomNumber random-number.js

The output should be as follows:

Figure 8.28: Updating the randomNumber action

Now we can verify the result of the updated action by executing the following command:

$ wsk action invoke randomNumber --result

Figure 8.29: Invoking the randomNumber action

As we can see, the randomNumber action has returned a number between 1 to 100. We can invoke the randomNumber function number multiple times to verify that it returns an output number between 1 and 100.

Deleting OpenWhisk Actions

In this section, we will discuss how to delete an OpenWhisk action. The wsk action delete command is used to delete OpenWhisk actions:

$ wsk action delete <action-name>

Let's execute the wsk action delete command to delete the randomNumber and secondRandomNumber actions we created in the preceding sections:

$ wsk action delete randomNumber

$ wsk action delete secondRandomNumber

The output should be as follows:

Figure 8.30: Deleting the randomNumber and secondRandomNumber actions

Now we have learned how to write, create, list, invoke, update, and delete OpenWhisk actions. Let's move on to an exercise in which you will create your first OpenWhisk action.

Exercise 26: Creating Your First OpenWhisk Action

In this exercise, we will first create a JavaScript function that receives exam marks as input and returns the exam results using the following criteria:

  • Return Pass if marks are equal to or above 60.
  • Return Fail if marks are below 60.

Next, we will create an action named examResults in the OpenWhisk framework with the previously mentioned JavaScript function code. Then, we will invoke the action to verify that it returns the results as expected. Once the action response is verified, we will update the action to return the exam grade with the results based on the following criteria:

  • Return Pass with grade A if marks are equal to or above 80.
  • Return Pass with grade B if marks are equal to or above 70.
  • Return Pass with grade C if marks are equal to or above 60.
  • Return Fail if marks are below 60.

Again, we will invoke the action to verify the results and finally delete the action.

Note

The code files for this exercise can be found at https://github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson08/Exercise26.

Perform the following steps to complete the exercise:

  1. First, let's create a JavaScript function in the exam-result.js file that will return the exam results based on the provided exam marks:

    function main(params) {

        var examResult = '';

        if (params.examMarks < 0 || params.examMarks > 100) {

            examResult = 'ERROR: invalid exam mark';

        } else if (params.examMarks >= 60) {

            examResult = 'Pass';

        } else {

          examResult = 'Fail';

        }

        

        return { result: examResult };

    }

  2. Now, let's create the OpenWhisk action named examResult from the exam-result.js file created in step 1:

    $ wsk action create examResult exam-result.js

    The output should be as follows:

    Figure 8.31: Creating the examResult action
  3. Once the action creation is successful, we can invoke the examResult action by sending a value between 0 to 100 to the examMarks parameter:

    $ wsk action invoke examResult --param examMarks 72 –result

    The output should be as follows:

    Figure 8.32: Invoking the examResult action
  4. At this step, we are going to create a new JavaScript function in exam-result-02.js to return the exam results with the grade parameter:

    function main(params) {

        var examResult = '';

        if (params.examMarks < 0 || params.examMarks > 100) {

            examResult = 'ERROR: invalid exam mark';

        } else if (params.examMarks > 80) {

            examResult = 'Pass with grade A';

        } else if (params.examMarks > 70) {

            examResult = 'Pass with grade B';

        } else if (params.examMarks > 60) {

            examResult = 'Pass with grade C';

        } else {

            examResult = 'Fail';

        }

        

        return { result: examResult };

    }

  5. Now, let's update the OpenWhisk action with the previously updated exam-result-02.js file:

    $ wsk action update examResult exam-result-02.js

    The output should be as follows:

    Figure 8.33: Updating the examResult action
  6. Once the action is updated, we can invoke the action multiple times with different exam marks as parameters to verify the functionality:

    $ wsk action invoke examResult --param examMarks 150 --result

    $ wsk action invoke examResult --param examMarks 75 --result

    $ wsk action invoke examResult --param examMarks 42 –result

    The output should be as follows:

    Figure 8.34: Invoking the examResult action with different parameter values
  7. Finally, we will delete the examResult action:

$ wsk action delete examResult

The output should be as follows:

Figure 8.35: Deleting the examResult action

In this exercise, we learned how to create a JavaScript function that follows the standards for OpenWhisk actions. Then we created the action and invoked it with the wsk CLI. After that, we changed the logic of the function code and updated the action with the latest function code. Finally, we performed a cleanup by deleting the action.

OpenWhisk Sequences

In OpenWhisk, and in general with programming, functions (known as actions in OpenWhisk) are expected to perform a single focused task. This will help to reduce code duplication by reusing the same function code. But creating complex applications requires connecting multiple actions together to achieve the desired result. OpenWhisk sequences are used to chain multiple OpenWhisk actions (which can be in different programming language runtimes) together and create more complex processing pipelines.

The following diagram illustrates how a sequence can be constructed by chaining multiple actions:

Figure 8.36: OpenWhisk sequence

We can pass parameters (if any) to the sequence, which will be used as the input for the first action. Then, the output of each action will be the input for the next action, and the final action of the sequence will return its result as the output of the sequence. Actions written in different programming languages can also be chained together with sequences.

Sequences can be created using the wsk action create command with the --sequence flag to provide a comma-separated list of actions to invoke:

$ wsk action create <sequence-name> --sequence <action-01>,<action-02>

In order to demonstrate the concept of OpenWhisk sequences, we will be creating a sequence named login in the following section, which consists of two actions, named authentication and authorization. The login action will be invoked when a user tries to log in to the application. If the user provides correct credentials at login, they can view all the content on the system. But if the user fails to provide the correct login credentials, they can only view the public content of the system.

Note

Authentication is verifying the user's identity, and authorization is granting the required level of access to the system.

First, let's create the authentication.js function. This function will receive two parameters, named username and password. If the username and password match the hardcoded values of admin (for the username parameter) and openwhisk (for the password parameter), the function will return authenticationResult as true. Otherwise, authenticationResult will be false:

function main(params) {

    var authenticationResult = '';

    if (params.username == 'admin' && params.password == 'openwhisk') {

        authenticationResult = 'true';

    } else {

        authenticationResult = 'false';

    }

    return { authenticationSuccess: authenticationResult };

}

The next function is authorization.js, which takes the authenticationSuccess value as input and displays appropriate content to the users. If the user is successfully authenticated (authenticationSuccess = true), the 'Authentication Success! You can view all content' message will be displayed. If authentication failed (authenticationSuccess != true), the 'Authentication Failed! You can view only public content' message will be displayed:

function main(params) {

    var contentMessage = '';

    if (params.authenticationSuccess == "true") {

        contentMessage = 'Authentication Success! You can view all content';

    } else {

        contentMessage = 'Authentication Failed! You can view only public content';

    }

    return { content: contentMessage };

}

Now, let's deploy both actions with the wsk action create command:

$ wsk action create authentication authentication.js

$ wsk action create authorization authorization.js

The output should be as follows:

Figure 8.37: Creating authentication and authorization actions

Now both authentication and authorization actions are ready. Let's create a sequence named login by combining authentication and authorization actions:

$ wsk action create login --sequence authentication,authorization

The output should be as follows:

Figure 8.38: Creating a login sequence

Now it's time to test the login sequence. First, we will invoke the login sequence by sending the correct credentials (username = admin and password = openwhisk):

$ wsk action invoke login --param username admin --param password openwhisk –result

The output should be as follows:

Figure 8.39: Invoking the login sequence with valid credentials

The expected result for a successful login is shown in the preceding screenshot. Now, let's invoke the login sequence by sending incorrect credentials (username = hacker and password = hacker). This time we expect to receive an authentication failure message:

$ wsk action invoke login --param username hacker --param password hacker –result

The output should be as follows:

Figure 8.40: Invoking the login sequence with invalid credentials

In this section, we learned about OpenWhisk sequences. We created multiple actions, linked them together using a sequence, and invoked the sequence by sending the required parameters.

Exercise 27: Creating OpenWhisk Sequences

In this exercise, we will create a sequence with two actions written in different languages. The first action, written in Python, receives the marks for two exams and returns the average marks. The second action, written in JavaScript, receives the average marks and returns either pass or fail.

Note

The code files for this exercise can be found at https://github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson08/Exercise27.

The following steps will help you complete the exercise:

  1. Write the first function (calculate-average.py), which calculates the average marks. This function will receive the marks for two exams as the input:

    def main(params):

        examOneMarks = params.get("examOneMarks")

        examTwoMarks = params.get("examTwoMarks")

        

        fullMarks = examOneMarks + examTwoMarks

        averageMarks =  fullMarks / 2

            

        return {"averageMarks": averageMarks}

  2. Create an OpenWhisk action named calculateAverage from calculate-average.py:

    $ wsk action create calculateAverage calculate-average.py

    The output should be as follows:

    Figure 8.41: Creating the calculateAverage action
  3. Check that the calculateAverage action is working as expected by invoking it:

    $ wsk action invoke calculateAverage --param examOneMarks 82 --param examTwoMarks 68 –result

  4. The output should be as follows:
    Figure 8.42: Invoking the calculateAverage action
  5. Create the second function (show-result.js), which returns the exam result (Pass or Fail) based on the average marks. The exam results will be based on the logic as marks less than 0 or greater than 100 will return an Error; marks greater than or equal to 60 will return Pass; else it will return Fail.

    The code would be as follows:

    function main(params) {

        var examResult = '';

        if (params.averageMarks < 0 || params.averageMarks > 100) {

            examResult = 'ERROR: invalid average exam mark';

        } else if (params.averageMarks >= 60) {

            examResult = 'Pass';

        } else {

            examResult = 'Fail';

        }

        

        return { result: examResult };

    }

  6. Create an OpenWhisk action named showResult from show-result.js:

    $ wsk action create showResult show-result.js

    The output should be as follows:

    Figure 8.43: Creating the showResult action
  7. Check that the showResult action is working as expected by invoking it:

    $ wsk action invoke showResult --param averageMarks 75 –result

    The output should be as follows:

    Figure 8.44: Invoking the showResult action
  8. Create the getExamResults sequence with the calculateAverage and showResult actions:

    $ wsk action create getExamResults --sequence calculateAverage,showResult

    The output should be as follows:

    Figure 8.45: Creating the getExamResults sequence
  9. Invoke the getExamResults sequence and verify the result:

    $ wsk action invoke getExamResults --param examOneMarks 82 --param examTwoMarks 68 –result

    The output should be as follows:

Figure 8.46: Invoking the getExamResults sequence

OpenWhisk Web Actions

So far, we have invoked our OpenWhisk actions through the wsk CLI with the wsk action invoke command. Even though this invocation method is very simple and suits us well during the development stage, the wsk CLI cannot be used by external parties, such as external applications or users, to invoke our actions. As a solution, we can use OpenWhisk web actions, which will allow actions to be invoked through HTTP requests with a publicly available URL.

OpenWhisk standard actions require authentication when invoking the action (this is handled internally by the wsk CLI) and must return a JSON payload as the response. In contrast, web actions can be invoked without authentication and can return additional information, such as HTTP headers and non-JSON payloads such as HTML and binary data.

An OpenWhisk standard action can be converted to a web action by sending the --web true (or --web yes) flag when creating (wsk action create) or updating (wsk action update) actions with the wsk CLI.

Let's create a JavaScript function (web-action.js) to be invoked as a web action. This function will return Hello, Stranger if we did not pass a value for the name parameter and returns Hello with the name when we pass a value for the name parameter with the web action URL:

function main(params) {

    var helloMessage = ''

    if (params.name) {

        helloMessage = 'Hello, ' + params.name;

    } else {

        helloMessage = 'Hello, Stranger';

    }

    

    return { result: helloMessage };

}

Now we can create a web action by sending the --web true flag with the wsk action create command:

$ wsk action create myWebAction web-action.js --web true

The output should be as follows:

Figure 8.47: Creating myWebAction as a web action

Then, we can invoke the created web action using the web action URL. The general format of a web action URL is as follows:

https://{APIHOST}/api/v1/web/{QUALIFIED_ACTION_NAME}.{EXT}

Let's discuss each component of this URL:

  • APIHOST: The APIHOST value for IBM Cloud Functions is openwhisk.ng.bluemix.net.
  • QUALIFIED_ACTION_NAME: The fully qualified name of the web action in <namespace>/<package-name>/<action-name> format. If the action is not in a named package, use default as the value of <package-name>.
  • EXT: The extension that represents the expected response type of the web action.

We can use the --url flag with the wsk action get command to retrieve the URL of a web action:

$ wsk action get myWebAction –url

The output should be as follows:

Figure 8.48: Retrieving the public URL of myWebAction

We need to append .json as an extension to the preceding URL since our web action is responding with a JSON payload. Now we can either open this URL in a web browser or use the curl command to retrieve the output.

Let's invoke in the preceding URL using a web browser:

Figure 8.49: Invoking myWebAction from a web browser without the name parameter

Hello, Stranger is the expected response because we did not pass a value for the name parameter in the query.

Now, let's invoke the same URL by appending ?name=OpenWhisk at the end of the URL:

https://us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.json?name=OpenWhisk

The output should be as follows:

Figure 8.50: Invoking myWebAction from a web browser with the name parameter

We can invoke the same URL as a curl request with the following command:

$ curl https://us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.json?name=OpenWhisk

The output should be as follows:

Figure 8.51: Invoking myWebAction as a curl command with the name parameter

This command will produce the same output as we saw in the web browser.

As we discussed previously, OpenWhisk web actions can be configured to return additional information including HTTP headers, HTTP status codes, and body content of different types using one or more of the following fields in the JSON response:

  • headers: This field is used to send HTTP headers in the response. An example would be to send Content-Type as text/html.
  • statusCode: This will send a valid HTTP response code. The status code of 200 OK will be sent unless specified explicitly.
  • body: This contains the response content, which is either plain text, a JSON object or array, or a base64-encoded string for binary data.

Now we will update the web-action.js function to send the response in the format we discussed earlier:

function main(params) {

    var helloMessage = ''

    

    if (params.name) {

        username = params.name;

        httpResponseCode = 200;

    } else {

        username = 'Stranger';

        httpResponseCode = 400;

    }

    

    var htmlMessage = '<html><body><h3>' + 'Hello, ' + username + '</h3></body></html>';

    

    return {

        headers: {

            'Set-Cookie': 'Username=' + username + '; Max-Age=3600',

            'Content-Type': 'text/html'

        },

        statusCode: httpResponseCode,

        body: htmlMessage

    };

}

Then, we will update the myWebAction action with the latest function code:

$ wsk action update myWebAction web-action.js

The output should be as follows:

Figure 8.52: Updating myWebAction

Let's invoke the updated action with the following curl command. We will provide name=OpenWhisk as a query parameter in the URL. Also, the -v option is used to print verbose output, which will help us to verify the fields we added to the response:

$ curl https://us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http?name=OpenWhisk -v

Here is the response we received after the preceding curl command:

     *   Trying 104.17.9.194...

* Connected to us-south.functions.cloud.ibm.com (104.17.9.194) port 443 (#0)

* * *

* * *

> GET /api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http?name=OpenWhisk HTTP/1.1

> Host: us-south.functions.cloud.ibm.com

> User-Agent: curl/7.47.0

> Accept: */*

>

< HTTP/1.1 200 OK

< Date: Sun, 04 Aug 2019 16:32:56 GMT

< Content-Type: text/html; charset=UTF-8

< Transfer-Encoding: chunked

< Connection: keep-alive

< Set-Cookie: __cfduid=d1cb4dec494fb11bd8b60a225c218b3101564936375; expires=Mon, 03-Aug-20 16:32:55 GMT; path=/; domain=.functions.cloud.ibm.com; HttpOnly

< X-Request-ID: 7dbce6e92b0a90e313d47e0c2afe203b

< Access-Control-Allow-Origin: *

< Access-Control-Allow-Methods: OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH

< Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, User-Agent

< x-openwhisk-activation-id: f86aad67a9674aa1aaad67a9674aa12b

< Set-Cookie: Username=OpenWhisk; Max-Age=3600

< IBM_Cloud_Functions: OpenWhisk

< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"

< Server: cloudflare

< CF-RAY: 5011ee17db5d7f2f-CMB

<

* Connection #0 to host us-south.functions.cloud.ibm.com left intact

<html><body><h3>Hello, OpenWhisk</h3></body></html>

As expected, we have received HTTP/1.1 200 OK as the HTTP response code, Content-Type: text/html as a header, a cookie, and <html><body><h3>Hello, OpenWhisk</h3></body></html> as the body of the response.

Now, let's invoke the same curl request without the name=OpenWhisk query parameter. This time, the expected response code is HTTP/1.1 400 Bad Request because we did not pass a value for the query parameter. Also, the curl command will respond with <html><body><h3>Hello, Stranger</h3></body></html> as the HTTP response body code:

$ curl https://us-south.functions.cloud.ibm.com/api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http -v

Here is the response from the preceding curl command:

*   Trying 104.17.9.194...

* Connected to us-south.functions.cloud.ibm.com (104.17.9.194) port 443 (#0)

* * *

* * *

* ALPN, server accepted to use http/1.1

> GET /api/v1/web/sathsara89%40gmail.com_dev/default/myWebAction.http HTTP/1.1

> Host: us-south.functions.cloud.ibm.com

> User-Agent: curl/7.47.0

> Accept: */*

>

< HTTP/1.1 400 Bad Request

< Date: Sun, 04 Aug 2019 16:35:09 GMT

< Content-Type: text/html; charset=UTF-8

< Transfer-Encoding: chunked

< Connection: keep-alive

< Set-Cookie: __cfduid=dedba31160ddcdb6791a04ff4359764611564936508; expires=Mon, 03-Aug-20 16:35:08 GMT; path=/; domain=.functions.cloud.ibm.com; HttpOnly

< X-Request-ID: 8c2091fae68ab4b678d835a000a21cc2

< Access-Control-Allow-Origin: *

< Access-Control-Allow-Methods: OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH

< Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, User-Agent

< x-openwhisk-activation-id: 700916ace1d843e78916ace1d813e7c3

< Set-Cookie: Username=Stranger; Max-Age=3600

< IBM_Cloud_Functions: OpenWhisk

< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"

< Server: cloudflare

< CF-RAY: 5011f1577b7a7f35-CMB

<

* Connection #0 to host us-south.functions.cloud.ibm.com left intact

<html><body><h3>Hello, Stranger</h3></body></html>

In this section, we introduced OpenWhisk web actions and discussed the differences between standard actions and web actions. Then, we created a web action using the wsk CLI. Next, we learned about the format of the URL exposed by web actions. We invoked the web action with both web browser and curl commands. Then, we discussed the additional information that can be returned with web actions. Finally, we updated our web action to include headers, statusCode, and the body in the response and invoked the web action using the curl command to verify the response.

OpenWhisk Feeds, Triggers, and Rules

In the previous sections, we learned how to invoke actions either with the wsk CLI or with HTTP requests using web actions. In this section, we are going to learn how to automate action invocation with OpenWhisk feeds, triggers, and rules. The following diagram illustrates how actions are invoked with events from external event sources using feeds, triggers, and rules:

Figure 8.53: OpenWhisk Feeds, triggers, and rules

Triggers are different types of events sent from event sources. These triggers can be fired either manually with the wsk CLI or automatically from events occurring in external event sources. Some examples of an event source are a Git repository, an email account, or a Slack channel. As illustrated in the preceding diagram, feeds are used to connect the triggers to external event sources. Examples for feeds are as follows:

  • A commit is made to a Git repository.
  • Incoming email messages to a particular account.
  • Message received by a Slack channel.

As illustrated, the rule is the component that connects triggers with actions. A rule will connect one trigger with one action. Once this link is created, every invocation of the trigger will execute the associated action. The following scenarios are also possible by creating an appropriate set of rules:

  • A single trigger to execute multiple actions
  • A single action to be executed in response to multiple triggers

Let's start by creating a simple action to be invoked with triggers and rules. Create a file named triggers-rules.js and add the following JavaScript function:

function main(params) {

    var helloMessage = 'Invoked with triggers and rules';

    return { result: helloMessage };

}

Then we will create the action:

$ wsk action create triggersAndRules triggers-rules.js

Now it's time to create our first trigger. We will use the wsk trigger create command to create the trigger using the wsk CLI:

$ wsk trigger create <trigger-name>

Let's create a trigger called myTrigger:

$ wsk trigger create myTrigger

The output should be as follows:

Figure 8.54: Creating myTrigger

We can list the available triggers to make sure that myTrigger has been created successfully:

$ wsk trigger list

The output should be as follows:

Figure 8.55: Listing all triggers

Triggers are useless until we connect them with actions through a rule. Now we will be creating an OpenWhisk rule with the wsk rule create command, which has the following format:

$ wsk rule create <rule-name> <trigger-name> <action-name>

Let's create a rule named myRule to connect the myTrigger and triggerAndRules actions together:

$ wsk rule create myRule myTrigger triggersAndRules

The output should be as follows:

Figure 8.56: Creating myRule to connect myTrigger with the triggersAndRules action

We can get the details about myRule, which shows the trigger and action associated with the rule:

$ wsk rule get myRule

This command will print detailed output about myRule as shown in the following screenshot, which includes the namespace, version, status, and associated triggers and actions of rule.

The output should be as follows:

Figure 8.57: Getting the details of myRule

It's time to see triggers in action once the action, trigger, and rule are ready. Let's fire the trigger using the wsk trigger fire command:

$ wsk trigger fire myTrigger

The output should be as follows:

Figure 8.58: Firing myTrigger

This will print the ID of the activation for the trigger.

Let's execute the following command to list the last two activations:

$ wsk activation list --limit 2

The output should be as follows:

Figure 8.59: Listing the last two activations

In the preceding screenshot, we can see that the myTrigger trigger activation is recorded, followed by the triggersAndRules action activation.

We can print the result of the triggersAndRules action activation to make sure that the action was invoked properly by the trigger:

$ wsk activation get 85d9d7e50891468299d7e50891d68224 –summary

The output should be as follows:

Figure 8.60: Printing the result of the activation

In this section, we discussed how to automate action invocation with feeds, triggers, and rules. We created an action, a trigger, and then a rule to connect them. Finally, we invoked the action by firing the trigger.

OpenWhisk CronJob Triggers

In the preceding section, we discussed how to fire a trigger with the wsk trigger fire command. However, there are situations in which we need to automate the firing of triggers. An example would be performing a periodic task, such as running system backups, log archiving, or database purging. OpenWhisk provides cron-based triggers for invoking serverless functions at fixed intervals. The /whisk.system/alarms package provided by OpenWhisk can be used to fire triggers at scheduled intervals.

This package includes the following feeds:

Figure 8.61: Feeds available in alarms package

In the following exercise, let's learn how to create a cron job-based trigger.

Exercise 28: Creating CronJob Triggers

In this exercise, we are going to create an OpenWhisk action that will be invoked every minute using feeds, triggers, and rules. The function code will print the current date and time as the output so we can verify that the cron job trigger has correctly invoked the action.

Note

The code files for this exercise can be found at https://github.com/TrainingByPackt/Serverless-Architectures-with-Kubernetes/tree/master/Lesson08/Exercise28.

The following steps will help you to complete the exercise:

  1. Let's start by creating the function code. This function will return the current date and time. Create a date-time.js file with the following code and create an action called dateTimeAction:

    function main() {

        var currentDateTime = new Date();

        return { currentDateTime: currentDateTime };

    }

    $ wsk action create dateTimeAction date-time.js

    The output should be as follows:

    Figure 8.62: Creating dateTimeAction
  2. The next step is to create a trigger with the /whisk.system/alarms/alarm feed. The cron value is provided as "* * * * *", which aims to trigger this action every minute:

    $ wsk trigger create dateTimeCronTrigger \

                                --feed /whisk.system/alarms/alarm \

                                --param cron "* * * * *"

    Here is the response for the wsk trigger create command. Make sure there is ok: created trigger dateTimeCronTrigger at the end of the output, which indicates the successful creation of dateTimeCronTrigger:

    ok: invoked /whisk.system/alarms/alarm with id 06f8535f9d364882b8535f9d368882cd

    {

        "activationId": "06f8535f9d364882b8535f9d368882cd",

        "annotations": [

            {

                "key": "path",

                "value": "whisk.system/alarms/alarm"

            },

            {

                "key": "waitTime",

                "value": 85

            },

            {

                "key": "kind",

                "value": "nodejs:10"

            },

            {

                "key": "timeout",

                "value": false

            },

            {

                "key": "limits",

                "value": {

                    "concurrency": 1,

                    "logs": 10,

                    "memory": 256,

                    "timeout": 60000

                }

            },

            {

                "key": "initTime",

                "value": 338

            }

        ],

        "duration": 594,

        "end": 1565083299218,

        "logs": [],

        "name": "alarm",

        "namespace": "sathsara89@gmail.com_dev",

        "publish": false,

        "response": {

            "result": {

                "status": "success"

            },

            "status": "success",

            "success": true

        },

        "start": 1565083298624,

        "subject": "sathsara89@gmail.com",

        "version": "0.0.152"

    }

    ok: created trigger dateTimeCronTrigger

  3. Create the rule (dateTimeRule) to connect the action (dateTimeAction) with the trigger (dateTimeCronTrigger):

    $ wsk rule create dateTimeRule dateTimeCronTrigger dateTimeAction

    The output should be as follows:

    Figure 8.63: Creating dateTimeRule to connect dateTimeCronTrigger with dateTimeAction
  4. This action will now be triggered every minute. Allow the cron job trigger to run for around 5 minutes. We can list the last 6 activations with the following command:

    $ wsk activation list --limit 6

    The output should be as follows:

    Figure 8.64: Listing the last six activations
  5. List the summary of the activations of dateTimeAction to make sure it has printed the current datetime every minute:

    $ wsk activation get 04012f4f3e6044ed812f4f3e6054edc4 --summary

    $ wsk activation get c4758e5fa4464d0cb58e5fa446cd0cf7 --summary

    $ wsk activation get cf78acfd78d044e8b8acfd78d044e89c –summary

    The output should be as follows:

Figure 8.65: Printing the summary of dateTimeAction activations

Check the value of the currentDateTime field, printed for each invocation to verify that this action was invoked every minute as scheduled. In the preceding screenshot, we can see that the action was invoked at 09:37:02, then again at 09:38:03, and finally at 09:39:03.

In this activity, we created a simple function that prints the current date and time. Then, we created a cron job trigger to invoke this action every minute.

OpenWhisk Packages

OpenWhisk packages allow us to organize our actions by bundling the related actions together. As an example, consider that we have multiple actions, such as createOrder, processOrder, dispatchOrder, and refundOrder. These actions will perform the relevant application logic when an application user creates an order, processes an order, dispatches an order, and refunds an order respectively. In this case, we can create a package named order to group all order-related actions together.

As we learned previously, action names should be unique. Packages help to prevent naming conflicts because we can create multiple actions with the same name by placing them in different packages. As an example, the retrieveInfo action from the order package may retrieve information about an order, but the retrieveInfo action from the customer package can retrieve information about a customer.

So far, we have created many actions without bothering about packages. How was this possible? This is because OpenWhisk places actions into default packages if we do not mention any specific package during action creation.

There are two types of packages in OpenWhisk:

  • Built-in packages (packages come with OpenWhisk)
  • User-defined packages (other packages created by users)

All the packages available in a namespace can be retrieved with the wsk package list <namespace> command.

The output should be as follows:

Figure 8.66: Listing the packages in the /whisk.system namespace

Packages can be created with the wsk package create command:

$ wsk package create <package-name>

In this section, we introduced the concept of packages and discussed the built-in packages and user-defined packages of OpenWhisk. In the next exercise, we will create a package and add an action to the newly created package.

Exercise 29: Creating OpenWhisk Packages

In this exercise, we will create a package named arithmetic that contains all arithmetic-related actions, such as add, subtract, multiply, and divide. We will create a function that receives two numbers as input and returns the result by adding the numbers. Then, we will create this action within the arithmetic package:

  1. Let's start by creating a package named arithmetic:

    $ wsk package create arithmetic

    The output should be as follows:

    Figure 8.67: Creating the arithmetic package
  2. Now we are going to create an action that will be added to our arithmetic package. Create a file named add.js with the following content:

    function main(params) {

        var result = params.firstNumber + params.secondNumber;

        return { result: result };

    }

  3. We can create the action and add it to the arithmetic package simultaneously with the wsk action create command. This will only require us to prefix the action name with the package name. Execute the following command:

    $ wsk action create arithmetic/add add.js

    In the output, we can see that the action has been successfully created in the arithmetic package.

    The output should be as follows:

    Figure 8.68: Adding an add action to the arithmetic package
  4. Now we can verify that our add action has been placed in the arithmetic package using the wsk action list command.

    $ wsk action list --limit 2

    The output should be as follows:

    Figure 8.69: Listing the actions
  5. The wsk package get command will return JSON output that describes the package:

    $ wsk package get arithmetic

    The output should be as follows:

    Figure 8.70: Getting a detailed description of the arithmetic package
  6. We can use the --summary flag if we want to see a summary of the package description, which lists the actions within the package:

    $ wsk package get arithmetic –summary

    The output should be as follows:

Figure 8.71: Getting the summary description of the arithmetic package

Activity 8: Receive Daily Weather Updates via Email

Imagine that you are working for a disaster management center and need to be updated with weather information. You have decided to create an application that can send you weather updates via email at specified intervals. To achieve this, you have decided to deploy an application that can retrieve the current weather in a specific city and send a daily email at 8.00 AM with the current weather information to a specified email address. In this activity, we will be using external services to retrieve weather information (OpenWeather) and send emails (SendGrid).

We need to have the following before we start this activity:

  • An OpenWeather account (to retrieve current weather information)
  • A SendGrid account (to send emails)
  • npm installed
  • zip installed

Execute the following steps to create an OpenWeather account and a SendGrid account:

  1. Create an OpenWeather (https://openweathermap.org/) account to retrieve current weather information and save the API key. Create an OpenWeather account at https://home.openweathermap.org/users/sign_up.

    Go to the API keys tab (https://home.openweathermap.org/api_keys) and save the API key as this API key is required to fetch the data from the OpenWeather API.

    Test the OpenWeather API using https://api.openweathermap.org/data/2.5/weather?q=London&appid=<YOUR-API-KEY> in a web browser. Please note that you need to replace <YOUR-API-KEY> with your API key from step 1.

  2. Create a SendGrid (https://sendgrid.com) account and save the API key. This is used to send emails. Create a SendGrid account at https://signup.sendgrid.com/.

    Go to Settings > API Keys and click on the Create API Key button.

    Provide a name in the API Key Name field, select the Full Access radio button, and click on the Create & View button to create an API key with full access.

    Once the key is generated, copy the API key and save it somewhere safe as you will see this key only once.

    Note

    Detailed steps on creating an OpenWeather account and a SendGrid account are available in the Appendix section on page 432.

    Now we are ready to start the activity. Execute the following steps to complete this activity:

  3. Create a function in any language that you are familiar with (and supported by the OpenWhisk framework) that will take the city name as a parameter and return a JSON object with weather information retrieved from the OpenWeather API.

    Note

    For this solution, we will be using functions written in JavaScript. However, you can use any language that you are familiar with to write the functions.

    Here is an example function written in JavaScript:

    const request = require('request');

    function main(params) {

        const cityName = params.cityName

        const openWeatherApiKey = '<OPEN_WEATHER_API_KEY>';

        const openWeatherUrl = 'https://api.openweathermap.org/data/2.5/weather?q=' + cityName + '&mode=json&units=metric&appid=' + openWeatherApiKey ;

        return new Promise(function(resolve, reject) {

            request(openWeatherUrl, function(error, response, body) {   

                if (error) {

                    reject('Requesting weather data from provider failed '

                            + 'with status code '

                            + response.statusCode + '.\n'

                            + 'Please check the provided cityName argument.');

                } else {

                    try {

                        var weatherData = JSON.parse(body);

                        resolve({weatherData:weatherData});

                    } catch (ex) {

                        reject('Error occurred while parsing weather data.');

                    }

                }

            });

        });

    }

  4. Create a second function (in any language that you are familiar with and is supported by the OpenWhisk framework) that will take a message as input and send the input message to a specified email address using the SendGrid service.

    Here is an example function written in JavaScript:

    const sendGridMailer = require('@sendgrid/mail');

    function main(params) {

        const sendGridApiKey = '<SEND_GRID_API_KEY>';

        const toMail = '<TO_EMAIL>';

        const fromMail = '<FROM_EMAIL>';

        const mailSubject = 'Weather Information for Today';

        const mailContent = params.message;

        return new Promise(function(resolve, reject) {

            sendGridMailer.setApiKey(sendGridApiKey);

            const msg = {

                to: toMail,

                from: fromMail,

                subject: mailSubject,

                text: mailContent,

            };

            sendGridMailer.send(msg, (error, result) => {

                if (error) {

                    reject({msg: "Message sending failed."});

                } else {

                    resolve({msg: "Message sent!"});

                }

            });

        });

    }

    exports.main = main;

  5. Create a third function (in any language that you are familiar with and is supported by the OpenWhisk framework) that will take the JSON object with the weather data and format it as a string message to be sent as the email body.

    Here is an example function written in JavaScript:

         function main(params) {

        return new Promise(function(resolve, reject) {

            if (!params.weatherData) {

                reject("Weather data not provided");

            }

            const weatherData = params.weatherData;

            const cityName = weatherData.name;

            const currentTemperature = weatherData.main.temp;

            weatherMessage = "It's " + currentTemperature

                                     + " degrees celsius in " + cityName;

            resolve({message: weatherMessage});

       });

    }

  6. Next, create a sequence connecting all three actions.
  7. Finally, create the trigger and rule to invoke the sequence daily at 8.00 AM.

    Note

    The solution to the activity can be found on page 432.

Summary

In this chapter, we first learned about the history and the core concepts of Apache OpenWhisk. Then, we learned how to set up IBM Cloud Functions with CLI to run our serverless functions. After that, OpenWhisk actions were introduced, which are the code snippets written in one of the languages supported by OpenWhisk. We discussed how to write, create, list, invoke, update, and delete OpenWhisk actions using the wsk CLI. Next, we went over OpenWhisk sequences, which are used to combine multiple actions together to create a more complex processing pipeline. Going forward, we learned how to expose actions publicly using a URL with web actions. We discussed how web actions allow us to return additional information from the action, such as HTTP headers and non-JSON payloads, including HTML and binary data. The next section was on feeds, triggers, and rules that automate action invocation using events from external event sources. Finally, OpenWhisk packages were discussed, which are used to organize related actions by bundling them together.

In the next and final chapter, we shall learn about OpenFaaS and work with an OpenFaaS function.