Skip to Content

Since the introduction of SAP HANA extended application services, advanced model (XSA), SAP has supported many new approaches to accessing the platform APIs.  XSA is based upon Cloud Foundry which provides its core functionality via a command line client and a REST enabled web API.  XSA has this same functionality, but most of the focus on platform admin and interaction tends to focus on the CLI interface or the browser-based administrative UI which SAP deliveries. However behind that web UI are essentially calls to the controller APIs. These same APIs can be called from and used in your own applications.  In this blog I’d like to share some experiences trying to use these APIs.  There are some common challenges anyone using them will face and perhaps this blog can be helpful in that regard.

Documentation

One of the first questions about these APIs is probably going to be where to go to get documentation.  Your first inclination might be to go to the SAP HANA Platform documentation.  There you will find all the concepts of the XSA Controller described and you will find the command-line interface reference:
https://help.sap.com/viewer/4505d0bdaf4948449b7f7379d24d0f0d/2.0.03/en-US/addd59069e6f444ca6ccc064d131feec.html

However you won’t find much in the way of details about the XSA controller REST APIs. This is where falling back to underlying Cloud Foundry concepts is necessary. The XSA controller APIs are essentially the same as their standard Cloud Foundry counterparts. Of course this is very helpful if writing an application that will also run on SAP Cloud Platform where standard Cloud Foundry is used as well.  Therefore much of the documentation you might want about these APIs can be found on the Cloud Foundry site:

http://apidocs.cloudfoundry.org/1.27.0/

Let’s take an example. You might want to list all the users in XSA. There is a Cloud Foundry API for this (/v2/users):

https://apidocs.cloudfoundry.org/1.27.0/users/list_all_users.html

If we come over to XSA and write a Node.js application to call this same API on the XSA Controller, we get the same kind of results:

		let request = require("request");
		var options = {
			method: "GET",
			json: true,
			url: global.__controller + "/v2/users",
			auth: {}
		};

Which incidentally would be the same kind of results you could get by calling the xs users or cf users CLI command.

Controller URL

If you notice in the above example, I’m building a URL that points to the controller API plus the API REST path. But the next challenge we face is; how to get this URL. Of course we could hard code the URL, but that’s not very elegant as we would have to change the code for each system we deploy to. We could setup a user provided service to configure the URL. This would be a good solution, but there is something easier.

The XS/CF deployer has certain built-in variables which are filled automatically. These can be accessed in the mta.yaml file using the ${<variable name>} approach. If you’ve done much XSA or CF based development, you’ve probably used the most common of these variables – ${default-url}.

This common example of default-url is how we can take the generated URL of one micro service and pass it dynamically to another.  Its key to how the application router/web module can wrap inner Node.js and Java services:

 - name: node
   type: nodejs
   path: node
   provides:
    - name: node_api
      properties:
         url: ${default-url}
   requires:
    - name: controller-api-ex-uaa
    - name: controller-config
      group: destinations
      properties:
        name: controller-config
        url: ~{url}
        forwardAuthToken: true   

But default-url is hardly the only variable we have available. Now this is where things get interesting.  We have variables controller-url and authorization-url that can give us access to the controller and UAA APIs.  So you can setup a resource with this property getting filled via the controller-url variable:

resources:
 - name: controller-api-ex-uaa
   type: com.sap.xs.uaa
   parameters:
     config-path: ./xs-security.json   
 - name: controller-config
   properties:
     url: ${controller-url}

The only problem: this doesn’t work when running the application from the SAP Web IDE.  The run command from the Web IDE uses a special approach (in order to provide debugging and delta run support). However this means its doesn’t have all the functionality of the full deployer.  So this does mean that for debugging and development in the Web IDE, you need to temporarily hard-code the controller url and only use this controller-url variable for “real” deployments.

Now that we got the messiness out of the way, we can see that we’ve added the controller URL to a resource and we’ve bound that resource to a Node.js module. This can then be easily accessed from coding because bound resources are detailed in the environment of the bound application.  From Node.js the environment is read from process.env variable. So at the bootstrap of the Node.js application, I just tuck away this value in a global variable:

global.__controller = JSON.parse(process.env.destinations)[0].url;

If you were ever curious about this environment information, it contains all kinds of good stuff. For example it can be used to retrieve the XSA/CF Organization at runtime

let VCAP = JSON.parse(process.env.VCAP_APPLICATION);
res.type("application/json").status(200).send(JSON.stringify(VCAP.organization_name));	

Or the same for the XSA/CF Space:

let VCAP = JSON.parse(process.env.VCAP_APPLICATION);
res.type("application/json").status(200).send(JSON.stringify(VCAP.space_name));

This VCAP_APPLICATION and VCAP_SERVICES sections are particularly chalk full of good information.

/v2/info

Before we go further, let’s talk about a special controller api: /v2/info

This is the only one of the Controller APIs which doesn’t require authentication. Therefore once you have access to the base controller URL, this is often the first API you want to call. It returns lots of runtime information about the system including URLs to other important APIs and applications. This is a great entry point into the rest of the system.

https://apidocs.cloudfoundry.org/1.27.0/info/get_info.html

	app.get("/info", (req, res) => {
		let request = require("request");
		let options = {
			url: global.__controller + "/v2/info"
		};
		request.get(
			options,
			function(error, response, body) {
				if (error) {
					console.log(error.toString());
					res.type("text/html").status(200).send(error.toString());
					return;
				}
				res.type("application/json").status(200).send(body);
			}
		);
	});

Authentication

Now that we have access to the controller API URL and we’ve called the un-authenticated /v2/info API, the final challenge we face is how to make authenticated requests to the remainder of the APIs. In all likelihood we probably want to use principal propagation – meaning whatever user is already authentication to our application services is the same one that we want to forward onto the controller APIs. This means the authenticated user for our application probably needs controller admin or developer rights in order to call most of these APIs.

This means that our application which wants to call the controller APIs needs to have a typical setup for UAA and Application Router.  There is nothing especially specific about this setup for the controller APIs, so I won’t repeat all that setup here. Its been described in other tutorials and documents already. However at the end you should have your Node.js and Web/App Router module and the redirect with xsuaa authentication between them:

Where this gets interesting is in the Node.js module and how we re-use this authentication token for the outbound requests to the controller APIs.  We start by wiring the UAA service into Express via passport.  This will allow Express via middelware to do all the heavy lifting of the processing the authentication token:

//Initialize Express App for XS UAA and HDBEXT Middleware
var app = express();

passport.use("JWT", new xssec.JWTStrategy(xsenv.getServices({
	uaa: {
		tag: "xsuaa"
	}
}).uaa));
app.use(logging.expressMiddleware(appContext));
app.use(passport.initialize());
app.use(
	passport.authenticate("JWT", {
		session: false
	})
);

The beauty of this approach is that Express does the processing and then places the authorization information into the req object which it passes into all route handlers.  For the controller APIs we need to extract the Bearer authorization details out of this information:

module.exports = {
	getAccessToken: function(req) {
		var accessToken = null;
		if (req.headers.authorization && req.headers.authorization.split(" ")[0] === "Bearer") {
		   accessToken =  req.headers.authorization.split(" ")[1];
		}
		return accessToken;
		
	}
};

Now whenever we want to call any of the controller APIs with authentication, extract this bearer information from the incoming request object and then insert it into the outbound one as shown below:

	app.get("/getOrgs", (req, res) => {
		let request = require("request");
		var options = {
			method: "GET",
			json: true,
			url: global.__controller + "/v2/organizations",				
			auth: {}
		};
		options.auth.bearer = require(global.__base + "utils/auth").getAccessToken(req);
		request.get(
			options,
			function(error, response, body) {
				if (error) {
					console.log(error.toString());
					res.type("text/html").status(200).send(error.toString());
					return;
				}
				res.type("application/json").status(200).send(body);
			}
		);
	});

 

Closing

We’ve walked through a lot of different aspects of using the controller API in both SAP HANA XSA and Cloud Foundry. If you are wanting to see the entire process put together in a sample application, I’ve posted my test application to Github here: https://github.com/jungsap/controllerAPI

To report this post you need to login first.

6 Comments

You must be Logged on to comment or reply to a post.

  1. Christopher Solomon

    Very cool!….and very timely!…..just wrapping up the Q4/2017 “Software Development on SAP HANA” series you and Rich did and preparing for the certification (went through your *new* course on that this week!). All of your blog actually makes sense to me now. hahaha Thanks!!!

    (0) 
  2. Sergio Guerrero

    thank you for your share Thomas… I am using HANA 2 SP3…. i am getting an error Failed to communicate with the XS controller. connection refused. any ideas why? this is a new installation of the HXE on more than enough memory (24GB allocated to the VM) thank you in advance

    (0) 
    1. Thomas Jung
      Post author

      Where are you getting this error? When you call the API or just when you try and connect via XS CLI? If API, did you create your own application or are you cloning mine? If you cloned mine, did you adjust that very last line in the mta.yaml file?

      (0) 
      1. Sergio Guerrero

        I did a brand new installation after downloading the HANA XE from the SAP site and following the pdf instructions. The error comes after changing the default password … when trying to log in xs-admin-login.. using the new password.. then i get the error

        (0) 
        1. Thomas Jung
          Post author

          I’d really suggest as this is a general installation and usage question that you post it as its own question in the Q&A section and not as a blog comment.  There is no obvious connection to the specific topic of this blog.

          (0) 

Leave a Reply