Homelab, Linux, JS & ABAP (~˘▾˘)~
 

[CAP] Multitenant Job Scheduler – Request timeout after 15 seconds

For Jobs running longer than 15 seconds, you have to manually inform the Job Scheduler if your operation succeeded or not. Else, your job will only stay in status COMPLETED/UNKNOWN due to the timeout.

Informing the Job Scheduler about your succeeded operation can be done vie REST API Endpoint Update Job Run Log. You can read more about Long-Running (Async) Jobs here. I therefore wrote a function named updateJobStatus, which I call at the end of every long-running endpoint. It checks if the endpoint is called manually or via Job Scheduler service and updates the Job Run Log using the @sap/jobs-client if required.

const cds = require('@sap/cds')
const LOG = cds.log('JobService')
const xsenv = require("@sap/xsenv")
const JobSchedulerClient = require("@sap/jobs-client")

async function fetchAccessToken(url, creds) {
    const response = await fetch(`${url}/oauth/token`, {
        method: 'POST',
        body: 'grant_type=client_credentials&client_id=' + creds.uaa.clientid + '&client_secret=' + creds.uaa.clientsecret,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    })
    return await response.json()
}

async function getJobscheduler(req) {
    xsenv.loadEnv()
    const services = xsenv.getServices({
        jobscheduler: { tags: "jobscheduler" }
    })
    if (!services.jobscheduler) req.reject("no jobscheduler service instance found")

    const subdomain = (process.env.NODE_ENV === 'production') ? req.http.req.authInfo.getSubdomain() : 'customer1' // workaround for local testing
    const domain = `https://${subdomain}.${services.jobscheduler.uaa.uaadomain}`
    const token = await fetchAccessToken(domain, services.jobscheduler)

    const options = {
        baseURL: services.jobscheduler.url,
        token: token.access_token
    }
    return new JobSchedulerClient.Scheduler(options)
}

async function updateJobStatus(req) {
    const jobId = req.headers['x-sap-job-id']
    const scheduleId = req.headers['x-sap-job-schedule-id']
    const runId = req.headers['x-sap-job-run-id']

    if (!jobId || !scheduleId || !runId) return
    LOG.info('Endpoint is called via Job Scheduler')

    const scheduler = await getJobscheduler(req)

    const payload = {
        jobId: jobId,
        scheduleId: scheduleId,
        runId: runId,
        data: { success: true, message: 'The endpoint has successfully executed the long-running job' }
    }

    scheduler.updateJobRunLog(payload, function (err, result) {
        if (err) return LOG.error('Error updating run log: %s', err)
        //Run log updated successfully
        LOG.info('Run log updated successfully')
    })
}

module.exports = {
  updateJobStatus
}

[CAP] Get subdomain in a multitenant scenario

Until recently, I was always decoded the JWT to get the subdomain of a subscribed tenant like this:

const jwt = retrieveJwt(req)
const subdomain = decodeJwt(jwt).ext_attr.zdn

I now noticed that you can also use the original request object and the getSubdomain function of the authInfo object. It’s provided by @sap/xssec and is only available when using XSUAA, means not with mocked authentication. This way you can get the subdomain in a single line:

const subdomain = req.http.req.authInfo.getSubdomain()

And there are some more helpful functions, that are documented here: