Skip to Content
Technical Articles
Author's profile photo Carlos Roggan

Using Job Scheduler in SAP BTP [7]: Multitenancy (0): Intro

This series of blogs intends to show in an easy way how to use the Job Scheduling Service in SAP Business Technology Platform (aka SAP Cloud Platform).

In the present blog post we want to understand how Job Scheduler can help with recurring tasks in a multitenant application.
I found it very confusing, so I’ve tried to separate the considerations in different aspects:
Project architecture, job creation, JWT tokens, XSUAA.

Note:
This post is not a guide on how to design multitenant applications!

Overview

1) Multitenant project architecture with Job Scheduler

How does Job Scheduler fit into a multitenant scenario?
How can we model the project architecture involving Job Scheduler?
We can distinguish the following 3 use cases:

* use case 1: customer-agnostic
* use case 2: customer-specific + visible
* use case 3: customer-specific + hidden

The following sections explain what I mean.

Note:
We’ll be looking at the aspects from a most minimalistic project architecture.
We don’t consider frontend (ui5), app router, or CAP, etc.
I think, all the stuff below is long enough and adding the frontend wouldn’t change the considerations.

Use Case 0

Before we start discussing the first multitenant use case, let’s first remember the normal (singletenant) use case:
We have a normal singletenant app, we add a REST endpoint and Job Scheduler will call it.
The implementation of the REST endpoint then does anything useful.

Example:
Our app has a list of products and every night we update the prices.
Our app URL: https://app.cf.apps.com
The endpoint to be called by Job Scheduler: https://app.cf.apps.com/action

The diagram shows a normal app that is bound to Job Scheduler.
The app has a REST endpoint (called “/action”) that is invoked by a Job defined in the Job Scheduler dashboard.
On the left side we see a user who accesses the app to see the list of products

Use Case 1

In case of multitenancy, our (provider) app is bound to Job Scheduler instance as well.
This is done in the provider subaccount, where the provider application is deployed.
The first use case for a Job is agnostic of customers (subscribers).
We just want to do anything relevant for our app, on a regular basis.

Example:
Our multitenant app displays a list of products and every night we update the prices.
For this update of prices, we don’t need any knowledge specific to our customers (subscribers)

As such, we create a job which calls an endpoint on our provider app itself:
Our app URL: https://app.cf.apps.com
The endpoint to be called by Job Scheduler: https://app.cf.apps.com/action

The diagram shows the multitenant app deployed in provider subaccount.
Job Scheduling Service instance is created in the provider subaccount and bound to the MT app.
The provider app exposes the “action” endpoint which is invoked by Job Scheduler.

There are multiple customer subaccounts (tenants), subscribed to the MT app.
Each subscription has a different URL.
The end user accesses specific app-URL, where he can see the list of products.

As we can see, in this first use case it makes (almost) no difference if multitenant or singletenant mode.
Job Scheduler calls an action endpoint which does anything that has nothing to do with customer subaccounts.

Sample code for this use case can be found in this tutorial.

Use Case 2

Now we want to consider that in multitenant scenario we have customer-specific data.
I mean, a customer subaccount creates a subscription to our app and since we’re multitenant-aware, we store the info about this subscription in a customer-specific space.

Example:
Our app still displays products and prices. But each customer has specific status and gets specific discount.

Now we want to create a job for each customer. The job updates the discount specific to each customer.
As such, we create a job which calls a customer-specific endpoint.

The customer-specific app URL: https://customer1-app.cf.apps.com
The customer-specific endpoint for Job Scheduler:  https://customer1-app.cf.apps.com/action

The diagram shows that each subscription-app has a specific action endpoint URL, according to specific app-URL.
The endpoint does something specific to customer.
Job Scheduler calls that specific action endpoint.
Don’t be confused: the Implementation of the endpoint is always in the provider app. There is no difference in the setup. The difference is only in the implementation logic (distinguish tenants) and in the target action endpoint that is defined in the Job.

In this tutorial, we’re creating a sample application according to use case 2.

Use Case 3

Here we have similar logic as in use case 2: we need to do customer-specific stuff in the action endpoint.
The difference to previous case is that we want to do it under the hood.
Means, the customer shouldn’t know about it.

Example:
In the previous use case, we offered e.g. a button on the app, where the user could choose to e.g. “run/configure discount update”.
Now we think that it doesn’t make sense that the user knows about discount update.
As such, it should be us, the developers of the provider app, who decide on the frequency of discount-update.
Hence, we don’t need to call a customer-specific endpoint, instead we have an endpoint only on our provider app.
However, the logic of the implementation is still customer-specific, we need customer-info for updating the correct customer-specific discount.
These are the differences between use case 3 and the previous use cases.

How to achieve it?
I mean, we don’t have customer-specific URL, so we can’t read the customer-subdomain from the request. But we need the customer-info.
Answer:
It is possible only in case of OAuth protected scenario:
When the end user does logon into his app (subscription), then a JWT token is issued and that token contains the info which we need to assign the job to the concrete customer (tenant).

Our app URL: https://app.cf.apps.com
The endpoint to be called by Job Scheduler: https://app.cf.apps.com/action

In the diagram we can see that the Job Scheduler calls an action endpoint which doesn’t have customer-specific URL.
Job Scheduler sends a JWT token and the action implementation can extract the customer-specific info from it.
This scenario is only possible if user-login is enforced, as we can see on the left side.

Note

Of course, these diagrams and use cases are simplified and reduced to a bare minimum, just to clarify how the Job Scheduler fits into a multitenant scenario.
In case of professional MT applications with frontend and custom domains etc it would need to be adapted, but the basics remain the same:
Job Scheduler will always call a REST endpoint and for creating a Job we need a customer-specific token.

2) Multitenancy Support by Job Scheduler

What does it mean: Jobscheduler is multitenancy aware?
Or: how does Jobscheduler support multitenancy?

We can see the assignment of Jobs to tenants (customer subaccounts) and subdomains in the dashboard job overview.
See below screenshot as example:

The screenshot shows 3 jobs that were generated by one sample MT app for 3 different subscribed subaccounts (use case 2 and 3).
The last job was created manually for the provider MT app (use case 1).

Note:
One thing that is NOT supported by Job Scheduler:
From the Dashboard you can only create Job with the tenant of the provider application. Jobs for the subscribed tenants (SaaS tenants) can only be created (programmatically) via the REST API of Job Scheduler.
Why?
Because the tenant-ID of the subscribed customer-subaccount is not entered manually in the dashboard. Job Scheduler extracts it from the JWT token.

This leads us to another important note:

Note:
Job Scheduler is multitenancy-aware only in context of JWT tokens which it receives.
The REST API of Job Scheduler is OAuth protected, which means that we have to send a JWT token. The point is: to fetch this JWT token, we call the authorization server of the tenant.
Example for customer-specific authorization server:
https://customer1.authentication.sap.com/oauth/token
The token we get from it, contains the needed info about the customer
Note that this is only possible if dependency to Job Scheduler has been maintained in SaaS registry callback

What else?
When configuring our application for multitenancy, the Job Scheduler instance is added as dependency.
This means that Job Scheduler gets involved whenever a costumer subscribes
What for?
Whenever a customer deletes a subscription, then Job Scheduler removes all jobs created for this tenant.

Security:
We have to consider that app endpoints are OAuth-protected.
So Job Scheduler has to send a JWT token which is accepted by customer-specific security-validation.
In case of multitenancy, our MT app is bound to an instance of XSUAA which is configured with “shared” tenant mode.
That means that each subscribing customer gets an own OAuth client, which has same clientid/secret, but own authorization server URL, corresponding to his subdomain.

Example for customer-specific authorization server:
https://customer1.authentication.sap.com/oauth/token

As such, when Job Scheduler calls that endpoint it has to send a JWT token which was issued by that customer-specific authorization server. To do so, it needs the customer-specific OAuth client (that is the cloned xsuaa-instance).
And the good news:
Job Scheduler can do it.👍

YEAH !

3) Creation of Jo Jo Jobs

As mentioned above, the key of multitenancy-support of Job Scheduler lies in the way how jobs are generated.

Job Creation in Use Case 1

In a scenario, where the action endpoint does some work which is not related to the consumers, we can create jobs in the Dashboard. Such jobs are not tenant-aware.

Note:
In the wizard, we can enter a customer-specific URL as action-endpoint. Job Scheduler will call that URL. But this doesn’t mean that the job is tenant-aware. When the job is executed, a JWT token is always sent to the action-endpoint. But that token would not be customer-specific.

In the diagram we can see the Job Scheduler Dashboard in the provider account.
Jobs are created manually by the developer (or admin or any responsible for the provider application).
Information about the consumer is not required.

Jo Jo Creation in Use Case 2

In the multitenant scenario, job creation needs to be done programmatically.
Fortunately, Job Scheduling Service offers a REST API which allows to manage jobs remotely.
Means, in our MT application, we need some logic that calls the REST API of Job Scheduler in order to create or modify jobs.

Example:

POST https://jobscheduler-rest.cfapps.eu10.hana.ondemand.com/scheduler/jobs
Body:
   name: `MuteteJob`,
   action: `https://${subdomain}-mutete.cfapps.sap.hana.ondemand.com/action`,
   schedules: [{
      time: 'now'
   ...

The REST API is protected with OAuth.
Before we can fire requests to the REST API, we need to fetch a JWT token. The credentials can be found in the binding of our app.
There we can find clientId/secret and the token URL of our provider subaccount.
However, the point is:
we must call the token URL of the consumer subaccount, not provider subaccount.
Reason:
if we do so, the obtained JWT token will contain the information about the consumer.
And the job will be created with the notion of the consumer.
With other words: tenant-aware.

The above described procedure is the OAuth flow called client-credentials.
In addition, in some scenarios it makes sense to obtain the JWT token, which is required by Job Scheduler, via token exchange.
This would be the case in some user-centric scenario:
The multitenant application would require user-login.
After login, the user-JWT-token would be available in the app.
But this token cannot be sent to Job Scheduler for creating jobs.
It would be rejected.
Reason:
Job Scheduler has its own validation, different authorization server.
But this user-token can be sent to the authorization server in order to exchange it for a JWT-token which would be accepted by Job Scheduler.
This is called JWT Bearer Token Grant.

The setup for use case 2:
The app has a customer-specific endpoint for creating jobs.
The Job Scheduler REST API is called with customer-specific JWT token.

Above diagram shows that the job creation functionality (here it is just an endpoint) is visible to the customer. When executing the customer-specific createJob request, a customer-specific JWT token is sent.

Jo Jo Jo Creation in Use Case 3

The setup for use case 3:
The customer doesn’t know anything about creating jobs, so the job creation is done under the hood by MT provider application. Nevertheless, a JWT token is fetched from customers XSUAA and sent to Job Scheduler REST API for creating jobs.

Above diagram shows that the creation of job is hidden and customer doesn’t see it.
Nevertheless, a customer-specific token has to be fetched (exchanged) and sent to the REST API for job creation.

4) Tok Tok Tokens

After talking about JWT tokens there might be some confusion about which and where and why and for what we need token. So the time might have come for some clarification.

In our scenario, we can distinguish 3 different JWT tokens:

  1. User login
  2. REST API
  3. action endpoint

Below diagram gives and overview for which request they are required:

Tok 1

Typically, enterprise applications require user login.
Here the user enters credentials in order to access the application.
The associated XSUAA server issues a JWT token.
This is the user-token which contains e.g. the roles assigned to the user in SAP BTP.
In our app, we can use this user-token to exchange it for a new JWT token which can then be sent to Job Scheduler for job creation.

Above diagram shows the end user logging in to our application. Our app gets hold of the JWT token and can validate it further or use it for token exchange.

Tok 2

Another request where we need to send a JWT token is the job creation.
The REST API of the Job Scheduling Service is protected with OAuth, so we need to fetch a token.
In multitenancy, this token becomes more relevant, because it contains the information which is required to make Job Scheduler multitenancy-aware.
No matter if the JWT token is obtained via client-credentials or token exchange: it would be tenant-specific in both cases, thus useful for Job Scheduler.

Above diagram shows a scenario with a customer.specific endpoint. The implementation of this endpoint calls the Job Scheduler REST API and sends a customer-specific JWT token.
As such, the token contains the necessary customer-specific info.

However, as discussed earlier (use case 3), the job creation can happen also hidden from consumer, and the token can still be consumer-specific (because the consumer-xsuaa was used for fetching)

Tok 3

The last request in our scenario is executed by the job which triggers the “action”-endpoint.
Here it depends on our application if a token is required at all, but usually all endpoints of applications will be protected. Job Scheduler can handle the OAuth flow and is able to trigger OAuth-protected endpoints (see corresponding blog post )

In our application we provide a REST endpoint that should be triggered by Job Scheduler. We would typically protect this endpoint with OAuth, such that it cannot be invoked by unauthorized person.
To call that protected endpoint, Job Scheduler needs to handle the OAuth flow, means it must fetch a valid JWT token.

Note:
In case of multitenancy, it depends on the project setup if the action endpoint has tenant-specific URL (use case 2) or if it is hidden from user (use case 3).

Above diagram shows how a job – that was previously generated and configured – triggers an endpoint on our MT app. This endpoint is protected with OAuth, so Job Scheduler must send a valid token.
Sample for sending JWT token to tenant-specific endpoint can be found here.

Note:
Again, it depends on our project setup, if the action endpoint is customer-specific. Anyways, a token has to be sent.

5) XSUAA A A

We’ve mentioned several times that we need to fetch a JWT token from authorization server.
Now we have a detailed look at this aspect.
Which authorization server? And where? And how?

Let’s repeat the 3 tok toks, this time with focus on xsuaa.

XSUAA A 1

The first request where a JWT token was involved, was the user login.
Here we assume that an app router is in place.
The user logs in to the customer-specific subscription with customer-specific URL.
The user is redirected to the customer-specific clone of XSUAA.

Remember:
We create our MT app and bind it to an instance of XSUAA in our provider account.
When a customer subscribes, a kind of clone of the XSUAA instance is created.
This clone has own authorization server URL (according to customer subaccount), but same clientid/clientsecret (according to provider-xsuaa-instance).
So when the customer accesses his subscription, he is redirected to the customer-specific clone of XSUAA and the JWT which is issued, will contain customer-specific info.

Above diagram shows the user login and how the JWT token is issued by the tenant-specific clone of XSUAA.
The diagram also shows the multitenant application is bound to the (master) instance of XSUAA in provider account.

XSUAA AA 2

As mentioned several times, the createJob-request has to fetch a tenant-specific JWT token, to enable multitenancy support by Job Scheduler.
To obtain such tenant-specific token, the createJob implementation talks to the tenant-specific clone of XSUAA:

Above diagram shows how the createJob implementation fetches a token from tenant-specific xsuaa, before actually creating a job.

XSUAA AAA 3

When the job triggers the “/action” endpoint, it always sends JWT token (given that the Job Scheduling Service instance was created with param “enable-xsuaa-support”: true).
Now the question is: is that JWT token tenant-specific?
And the answer is: yes.
If the job was generated with REST API and with a tenant-specific token (see previous section), then Job Scheduler fetches a tenant-specific JWT token when it calls the action endpoint.

If we introspect the JWT token which we receive in the “/action” endpoint, then we can see the following values:

issuer https://customer1.authentication.sap.hana.ondemand.com/oauth/token
ext_attr.zdn (subdomain) customer1
zid (zoneId) 8482aac2-abcd-123456
aud (audience) sb-2233aa-1234!b17916|sap-jobscheduler!b21,uaa,mutetexsapp!t17916

They all are specific for the customer subaccount.
The audience contains our OAuth client mutetexsapp!t17916. As such, the token validation will be successful, although the token was issued from “foreign” subdomain.

Above diagram shows Job Scheduler fetching a token from tenant-specific xsuaa, when calling the action endpoint.

Small recap

In this chapter, we’ve had a detailed look into how the JWT tokens are fetched for each of the requests that require a token (as described in tok chapter)

We can distinguish 2 XSUAA authorization servers:

  • located in provider subdomain
    URL: https://provider.authentication.sap.hana.ondemand.com/oauth/token
  • located in subscriber subdomain
    URL: https://customer1.authentication.sap.hana.ondemand.com/oauth/token

We have 3 OAuth clients in the picture:

  • xsuaa instance created in provider account and bound to our MT app
    Example: clientid: sb-mutetexsapp!t17916
  • clone of this xsuaa instance, created when customer1 subscribes to our MT app
    It has same Clientid and secret like above
  • xsuaa instance which corresponds to Job Scheduler.
    Credentials are required for calling the REST API
    Example: clientid: sb-9a1234cb-abcd-1234!b17916|sap-jobscheduler!b21

Given that our goal was to support customer-specific jobs, we’ve seen:
-> When our app generates a job: we fetch JWT token from customer-specific xsuaa
-> When Job Scheduler calls the action endpoint, it fetches JWT token from customer-specific xsuaa

XSUAA AAAA 4 (optional)

To make this chapter complete, let’s as well have a quick look at 2 more XSUAAs that have been in the diagrams but we haven’t talked about:
It is the customer-agnostic use case.
We’re talking about 2 requests that weren’t described in the tok-chapter:
Here we generate a job with REST API (instead of dashboard).
For job creation, we fetch a JWT token from the jobscheduler-xsuaa.
We find the required clientid/secret in the binding of our app, in the
jobscheduler-credentials-uaa node.
For calling the action endpoint, Job Scheduler will fetch the JWT token from the xsuaa instance which we created along with our app and bound to our app.

This would be the flow in a single-tenant application, or in a multitenant application where we don’t need any information about the subscribed customer (use case 1)

Above diagram shows how the job creation is done with token from the JobScheduler-xsuaa, and how the action endpoint is triggered using a token from the app-xsuaa

Summary

If we have a multitenant application, we can bind Job Scheduler and create + run jobs.
Jobs can do tenant-specific stuff.
Job Scheduler can handle tenant-specific JWT tokens (extract tenant-info from token)
Job Scheduler removes tenant-specific jobs when subscription is deleted

In a multitenant scenario, we have several options of getting things done (project-architecture).

Relevant:
* Jobs must be created programmatically with token
* When triggering the action endpoint, Job Scheduler must send another token

Tokens:
The required JWT tokens are fetched from customer’s xsuaa, to make the token customer-specific

At the end of the day, it is all about the JWT token which is used to generate a Job via REST API:
If this token is customer-specific, then the Job is tenant-aware.
No matter how we obtain the token and where the job creation is done: the token must be fetched on behalf of the customer.

Multitenancy-aware:
Job Scheduler instance with “enable-xsuaa-support”: true
XSUAA instance with tenant-mode:shared
MT app registers the Job Scheduler in “getDependencies” callback

Next Steps

Next step is to get hands dirty with a very simple MT app that is bound to Job Scheduler (use case 1)
Then we add job generation (use case 2)
Afterwards we’ll be adding Security
An example for Token Exchange (coming later)
A scenario including Application Router (but no UI5 frontend) (probably coming)
Who knows what else would be interesting?

Links

There are multitenancy related links in the overview blog post.
This blog post for beginners introduces you to the usage of the Cloud Foundry command line client.

SAP Help Portal about multitenancy and composing the subscriber-app-URL

Job Scheduler docu about REST API, to create and manage jobs remotely

Cloud Foundry docs about JWT Bearer Token Grant

Assigned tags

      8 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Yogananda Muthaiah
      Yogananda Muthaiah

      Hi Carlos Roggan

      very good and comprehensive information about Job-scheduler in your article...

      If you can include some of the sample working example for each of the steps (Step 1 to Step 5) that would get benefit for all the readers who can try out your real example as playground.

      that said, your making us to learn and think beyond!  Keep sharing lot of out of the box innovative examples 🙂

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi, Yogananda Muthaiah , thanks so much for this helpful comment !! 😉
      Yes, I've planned to add very simple samples for each of the described use cases. But it will take some time, because I'm leaving for vacation soon 😉
      Please give me some more time 😉

      Cheers, Carlos

      Author's profile photo Yogananda Muthaiah
      Yogananda Muthaiah

      enjoy your summer break Carlos Roggan

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Thank you ! 😉

      Author's profile photo Yevgen Trukhin
      Yevgen Trukhin

      Awesome, thanks for great blog!

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Thanks so much, Yevgen Trukhin !

      Author's profile photo Ming Hao Xie
      Ming Hao Xie

      I like your style and it's also very informative. One question: for the action to be executed by the job-scheduler, it should be programmed to accept scope like "jobcallback"?

      Thanks!

      Author's profile photo Carlos Roggan
      Carlos Roggan
      Blog Post Author

      Hi Ming Hao Xie ,
      sorry for the late reply, due to my vacation. This is a good point and I guess the answer is "yes".
      I assume, in most cases the jobscheduler would be used to trigger actions like cleanup database, replicate data from onprem to cloud, update stuff from third-party, etc
      I mean, this would be actions that should be reserved for jobscheduler, such that end-user don't trigger it by mistake. As such, require a specific role for that endpoint would be the correct way.

      I'm not discussing scopes here, because I try to keep things as simple as possible.

      Kind Regards,

      Carlos