Lab 4.2 - Updating the HelloWorld iControl LX Extension

Now that we have an initial version of our extension up and running, let’s enhance it by adding the following capabilities:

  • Handle POST requests
  • Add logging information (It’s always useful to have detailed logs, especially when troubleshooting)
  • Send a REST API call to a 3rd system

Task 1 - Update our iControl LX Extension - Handle POST Requests

Here we will add code to our extension to handle POST requests, as well as add some troubleshooting statements.

The first thing we must do is enable our f5-logger. This will log messages to /var/log/restnoded/restnoded.log.

Perform the following steps to complete this task:

  1. Launch your preferred text editor once again to edit the file /var/config/rest/iapps/HelloWorld/nodejs/hello_world.js.

  2. Below the first comment,

    /**
     * A simple iControl LX Extension that handles only HTTP GET
     */
    

    add the following code: (i, Paste, Enter if you use vi)

    var logger = require('f5-logger').getInstance();
    var DEBUG = true;
    

    Hint

    You can Paste by right-clicking in the text editor window.

  3. Save the changes (CTRL-X, Y if you use nano, ESC ESC :wq if you use vi).

  4. Now we will be able to use the logger statement to print information to the /var/log/restnoded/restnoded.log log file. We can also enable/disable all logging by changing the value of the DEBUG variable to false.

    Note

    Here is an EXAMPLE of a logger statement (DO NOT PUT THIS INTO YOUR CODE)

    if (DEBUG === true) {
       logger.info("DEBUG: HelloWorld - onGet request");
    }
    
  5. We may also want to create a VARIABLE for our default JSON response of Hello World!. This way if we ever need to change it, we’ll only need to change it in a single location.

  6. Under var DEBUG = true; add:

    var DEFAULT_MSG = {"value": "Hello World!"};
    

    It should look like this:

    var logger = require('f5-logger').getInstance();
    var DEBUG = true;
    var DEFAULT_MSG = {"value": "Hello World!"};
    
  7. Next, let’s update your onGET prototype. Right now you should have this:

    HelloWorld.prototype.onGet = function(restOperation) {
      restOperation.setBody(JSON.stringify( { value: "Hello World!" } ));
      this.completeRestOperation(restOperation);
    };
    
  8. Replace this code with the following:

    HelloWorld.prototype.onGet = function(restOperation) {
      if (DEBUG === true) {
        logger.info("DEBUG: HelloWorld - onGet request");
      }
      restOperation.setBody(JSON.stringify(DEFAULT_MSG));
      this.completeRestOperation(restOperation);
    };
    

    Here’s what we’ve done:

    • Add a logger statement so that we can track when our extension is called with a GET request
    • Replace our static response { value: "Hello World!" } with our variable
  9. Below our onGet prototype, we will now add an OnPost prototype to handle POST requests with our extension.

    Add the following code below the onGet prototype:

    /**
    *handle onPost HTTP request
    */
    HelloWorld.prototype.onPost = function(restOperation) {
    
      //we retrieve the payload sent with the POST request
      var newState = restOperation.getBody();
    
      if (DEBUG === true) {
        logger.info("DEBUG: HelloWorld - onPost received Body is: " + JSON.stringify(newState,' ','\t'));
      }
      //we extract the variable name from the payload
      var name = newState.name;
    
      //if it's empty, we just print Hello World, otherwise Hello <name>
      if (name) {
        if (DEBUG === true) {
          logger.info("DEBUG: HelloWorld - onPost request, the extracted name is : " + name);
        }
        restOperation.setBody(JSON.stringify({ "value": "Hello " + name + "!"}));
      } else {
        if (DEBUG === true) {
          logger.info("DEBUG: HelloWorld - onPost request, no name parameter provided... using default value");
        }
        restOperation.setBody(JSON.stringify(DEFAULT_MSG));
      }
      this.completeRestOperation(restOperation);
    };
    
  10. Let’s review the code we have now, it should look like this:

    /**
    * A simple iControl LX Extension that handles only HTTP GET
    */
    
    var logger = require('f5-logger').getInstance();
    var DEBUG = true;
    var DEFAULT_MSG = {"value": "Hello World!"};
    
    function HelloWorld() {}
    
    HelloWorld.prototype.WORKER_URI_PATH = "ilxe_lab/hello_world";
    HelloWorld.prototype.isPublic = true;
    
    /**
    * handle onGet HTTP request
    */
    HelloWorld.prototype.onGet = function(restOperation) {
      if (DEBUG === true) {
        logger.info("DEBUG: HelloWorld - onGet request");
      }
      restOperation.setBody(JSON.stringify(DEFAULT_MSG));
      this.completeRestOperation(restOperation);
    };
    
    /**
    *handle onPost HTTP request
    */
    HelloWorld.prototype.onPost = function(restOperation) {
      //we retrieve the payload sent with the POST request
     var newState = restOperation.getBody();
    
     if (DEBUG === true) {
        logger.info("DEBUG: HelloWorld - onPost received Body is: " + JSON.stringify(newState,' ','\t'));
      }
      //we extract the variable name from the payload
      var name = newState.name;
    
      //if it's empty, we just print Hello World, otherwise Hello <name>
      if (name) {
        if (DEBUG === true) {
          logger.info("DEBUG: HelloWorld - onPost request, the extracted name is : " + name);
        }
        restOperation.setBody(JSON.stringify({ "value": "Hello " + name + "!"}));
      } else {
        if (DEBUG === true) {
          logger.info("DEBUG: HelloWorld - onPost request, no name parameter provided... using default value");
        }
        restOperation.setBody(JSON.stringify(DEFAULT_MSG));
      }
      this.completeRestOperation(restOperation);
    };
    
    /**
    * handle /example HTTP request
    */
    HelloWorld.prototype.getExampleState = function () {
      return {
        "value": "your_string"
      };
    };
    
    module.exports = HelloWorld;
    
    • The lines starting with // are comments. It’s always good to comment your code to help people read/understand your code… the more code there is, the more important it is to provide properly commented code.
    • var newState = restOperation.getBody(); With this statement we retrieve the PAYLOAD that was sent in the POST request and we show this payload in the following logger command.
    • var name = newState.name; With this statement we assign the name parameter’s value (sent with the POST request) to the name variable.
    • The following if/else statement determines whether the variable name is empty or not (if the POST payload didn’t contain a name parameter) and depending on this will do the following:
      • If the variable name is not empty: reply to the POST request with Hello and the name of the user
      • If the variable name is empty: reply to the POST request with Hello World!
  11. Make sure you save your updated file.

  12. Time to test our code! Open another SSH session to the BIG-IP and run the following command:

    bigstart restart restnoded ; tail -f /var/log/restnoded/restnoded.log

  13. Review the logs to make sure there aren’t any errors or issues with your updated file.

    Note

    Keep this SSH session open to monitor your logs. It’s easier to have one window for logs and a separate one for issuing commands.

  14. You should have something like this:

    Tue, 17 Oct 2017 13:11:19 GMT - finest: [LoaderWorker] triggered at path:  /var/config/rest/iapps/HelloWorld
    Tue, 17 Oct 2017 13:11:19 GMT - finest: [LoaderWorker] triggered at path:  /var/config/rest/iapps/HelloWorld/nodejs
    Tue, 17 Oct 2017 13:11:19 GMT - finest: [LoaderWorker] triggered at path:  /var/config/rest/iapps/HelloWorld/nodejs/hello_world.js
    Tue, 17 Oct 2017 13:11:19 GMT - config: [RestWorker] /ilxe_lab/hello_world has started. Name:HelloWorld
    
  15. You can now test your updated extension with the following commands:

    curl -k -u admin:admin https://10.1.1.245/mgmt/ilxe_lab/hello_world

    The console output should look like this:

    {"value":"Hello World!"}

  16. The /var/log/restnoded/restnoded.log output should look like this:

    Tue, 17 Oct 2017 13:33:45 GMT - info: DEBUG: HelloWorld - onGet request

  17. Run this command:

    curl -H "Content-Type: application/json" -k -u admin:admin -X POST -d '{"name":"iControl LX Lab"}' https://10.1.1.245/mgmt/ilxe_lab/hello_world

  18. The console output should look like this:

    {"value":"Hello iControl LX Lab!"}

  19. The /var/log/restnoded/restnoded.log output should look like this:

    Tue, 17 Oct 2017 13:36:46 GMT - info: DEBUG: HelloWorld - onPost received Body is: {
    "name": "iControl LX Lab"
    }
    Tue, 17 Oct 2017 13:36:46 GMT - info: DEBUG: HelloWorld - onPost request, the extracted name is : iControl LX Lab
    
  20. Run this command:

    curl -H "Content-Type: application/json" -k -u admin:admin -X POST -d '{"other":"iControl LX Lab"}' https://10.1.1.245/mgmt/ilxe_lab/hello_world

  21. The console output should look like this (the name parameter wasn’t found in the POST payload):

    {"value":"Hello World!"}

  22. The /var/log/restnoded/restnoded.log output should look like this:

    Tue, 17 Oct 2017 13:38:24 GMT - info: DEBUG: HelloWorld - onPost received Body is: {
    "other": "iControl LX Lab"
    }
    Tue, 17 Oct 2017 13:38:24 GMT - info: DEBUG: HelloWorld - onPost request, no name parameter provided... using default value
    

We now have an iControl LX Extension that is able to handle GET and POST requests as well as provide debugging information.

Task 2 - Update our iControl LX Extension - Perform a REST API Call

Right now, our iControl LX Extension provides a default message that is set at the beginning of our code. If this “content” is owned by someone else, it may be inefficient to have it directly in the code. Let’s see how we could leverage an HTTP request to retrieve our default message.

For this task, we will do 3 things:

  • Add the http module to our extension
  • Add a new prototype onStart to our code
  • Perform an HTTP request to GitHub to retrieve our default message

Perform the following steps to complete this task:

  1. To add the http module to our extension, we’ll need to add a bit of code. If your text editor has been closed, please reopen it. Below the line var DEFAULT_MSG = {"value": "Hello World!"};

    add the following code:

    var http = require('http');
    
  2. The prototype onStart is something you can leverage to do some processing when your iControl LX Extension is loaded in restnoded. It is triggered only once, when your extension is loaded. It’s a good prototype to leverage to retrieve our default message.

  3. Under the line HelloWorld.prototype.isPublic = true; add the following code:

    /**
     * Perform worker start functions
     */
    
    HelloWorld.prototype.onStart = function(success, error) {
    
    if (DEBUG === true) {
      logger.info("DEBUG: HelloWorld - onStart request");
    }
    
    var options = {
      "method": "GET",
      "hostname": "s3-eu-west-1.amazonaws.com",
      "port": 80,
      "path": "/nicolas-labs/helloworld_resp.json",
      "headers": {
        "cache-control": "no-cache"
      }
    };
    
    var req = http.request(options, function (res) {
    
      var chunks = [];
    
      res.on("data", function (chunk) {
        chunks.push(chunk);
      });
    
      res.on("end", function () {
        var body = Buffer.concat(chunks);
        if (DEBUG === true) {
          logger.info("DEBUG: HelloWorld - onStart - the default message body is: " + body);
        }
        DEFAULT_MSG = JSON.parse(body);
      });
    });
    
    req.end();
    
    if (DEBUG === true) {
      logger.info("DEBUG: HelloWorld - onStart - the default message is: " + this.state);
    }
    success();
    };
    
  4. The purpose of this code is to retrieve the file helloworld_resp. This file will give us the default payload we should return when we receive a request.

  5. Make sure you save your updated code. Then run the following command:

    bigstart restart restnoded ; tail -f /var/log/restnoded/restnoded.log

  6. Review the logs to make sure there aren’t any errors or issues with your updated file.

    You should have something like this:

    Wed, 18 Oct 2017 09:30:08 GMT - finest: [LoaderWorker] triggered at path:  /var/config/rest/iapps/HelloWorld/nodejs
    Wed, 18 Oct 2017 09:30:08 GMT - finest: [LoaderWorker] triggered at path:  /var/config/rest/iapps/HelloWorld/nodejs/hello_world.js
    Wed, 18 Oct 2017 09:30:08 GMT - info: DEBUG: HelloWorld - onStart request
    Wed, 18 Oct 2017 09:30:08 GMT - config: [RestWorker] /ilxe_lab/hello_world has started. Name:HelloWorld
    Wed, 18 Oct 2017 09:30:08 GMT - info: DEBUG: HelloWorld - onStart - the default message body is: { "value": "Congratulations on your lab!" }
    
  7. You can now test your updated extension with the following command:

    curl -k -u admin:admin https://10.1.1.245/mgmt/ilxe_lab/hello_world

  8. The console output should look like this:

    {"value":"Congratulations on your lab!"}

  9. The /var/log/restnoded/restnoded.log output should look like this:

    Tue, 17 Oct 2017 13:33:45 GMT - info: DEBUG: HelloWorld - onGet request

  10. Run this command:

    curl -H "Content-Type: application/json" -k -u admin:admin -X POST -d '{"name":"iControl LX Lab"}' https://10.1.1.245/mgmt/ilxe_lab/hello_world

  11. The console output should look like this:

    {"value":"Hello iControl LX Lab!"}

  12. The /var/log/restnoded/restnoded.log output should look like this:

    Wed, 18 Oct 2017 09:32:40 GMT - info: DEBUG: HelloWorld - onPost received Body is: {
    "name": "iControl LX Lab"
    }
    Wed, 18 Oct 2017 09:32:40 GMT - info: DEBUG: HelloWorld - onPost request, the extracted name is : iControl LX Lab
    
  13. Run this command:

    curl -H "Content-Type: application/json" -k -u admin:admin -X POST -d '{"other":"iControl LX Lab"}' https://10.1.1.245/mgmt/ilxe_lab/hello_world

  14. The console output should look like this (the name parameter wasn’t found in the POST payload):

    {"value":"Congratulations on your lab!"}

  15. The /var/log/restnoded/restnoded.log output should look like this:

    Wed, 18 Oct 2017 09:33:38 GMT - info: DEBUG: HelloWorld - onPost received Body is: {
    "other": "iControl LX Lab"
    }
    Wed, 18 Oct 2017 09:33:38 GMT - info: DEBUG: HelloWorld - onPost request, no name parameter provided... using default value
    

Task 3 - Take a 5 minute break!

Congratulations!!!! You’ve just modified the behavior of the F5 iControl LX Extension. Now take a moment to think about what workflows you could implement to make life easier.