Skip to Content
Technical Articles
Author's profile photo Boudhayan Dev

How to connect On-Premise system to SAP Cloud Foundry Environment using SAP Cloud Connector

The best way to learn is by doing. The only way to build a strong work ethic is getting your hands dirty.

                                                                                                                – Alex Spanos

In this blog I’ll demonstrate how to connect services that are deployed on an on-premise system to a Flask application that is deployed on Cloud Foundry. For this exercise, we will make use of the SAP Cloud Connector.

While working on a project that required me to connect CF and On-Premise systems, I came across two very wonderful blogs written by –  Matthieu Pelatan and  Marius Obert . While Matthieu’s blog shows end to end connection between the two systems, he relies on JAVA to demonstrate his example. Since I am not very familiar with JAVA, I decided to follow – Marius article. Marius shows how to call destinations endpoints from Flask application, which is great but it did not solve my cross-system connectivity issue. So, I decided to write this blog to show how we can call services from different systems (on-prem) via Flask (Python) application in CF environment. I thank Marius and Matthieu for providing such wonderful resources.

1. On-Premise

For the purpose of this blog, we will have a service (XSJS) deployed in an on-premise system as follows –

process_query.xsjs

// Function that return current balance ( hard coded )
function returnBalance(){
    try{
        var output = { 
                        status:200,
                        replies:[{
                          'type': 'text',
                          'content': 'Hi, current balance is: $ 23,900'
                        }]
                    };
    
        var body = JSON.stringify(output);
        $.response.contentType = 'application/json';
        $.response.setBody(body);
        $.response.status = $.net.http.OK;
    }
     
    catch (e) {
        var output2 = {
                        status:500,
                        replies:[{
                          'type': 'text',
                          'content': e.message
                        }]

                    };
        
        $.response.contentType = 'application/json';
        var body2 = JSON.stringify(output2);
        $.response.setBody(body2);
        $.response.status = $.net.http.OK;
        
    }
    
}

returnBalance();

 

Service status – 

 

The service is available now as seen above. Now we shall expose this service via SAP Cloud connector.

 

2. SAP Cloud Connector

 

Our SAP CF account looks as follows –

 

Now, let’s make the Connection between our CF system and On-premise system using Cloud Connector –

 

2.1. Create connection to CF

2.2. Add the On-Premise system

 

2.3. Add the service endpoint.

 

3. Cloud Foundry

Now that we have set up connection between the Cloud Foundry environment and the On-premise system, let’s deploy our Flask application.

3.1 Create Destination

Let’s add a new Destination entry which will point to the <VirtualHost>:<VirtualPort> corresponding to the <ActualHost>:<ActualPort> that we have mapped in SAP Cloud connector.

 

3.2 Add Destination, XSUAA and Connectivity service.

Lets’s add the Destination , XSUAA and Connectivity service to out CF Sub-account.The purpose of these services are as follows –

  • Destination – Provides a uniform way to add service endpoints in an application.
  • XSUAA – Provides authentication service.
  • Connectivity – This will invoke the Cloud Connector service which in turn will call our services deployed in On-premise system.
3.2.1 Service

 

For the services – Just provide the plan and the name of the service. DO NOT provide anything in the Specify Parameters/ Assign Application section.

After adding all the required services, Navigate to service instance tab –

3.3 Flask Application

 

Let’s add our Flask Application now

app.py –

from flask import Flask, request, jsonify
import requests
import json
import base64
from cfenv import AppEnv


app = Flask(__name__)
env = AppEnv()

# Creating instances of the Destiantion, XSUAA and Connectivity service
UAA_SERVICE = env.get_service(name='uaa_service')
DESTINATION_SERVICE = env.get_service(name='destination_service')
CONNECTIVITY_SERVICE = env.get_service(name='connectivity_service')
# Destination name
DESTINATION = 'demoService'
#connectivity proxy
CONNECTIVITY_PROXY = CONNECTIVITY_SERVICE.credentials["onpremise_proxy_host"]+":"
                    + CONNECTIVITY_SERVICE.credentials["onpremise_proxy_port"]

# Connectivity and Destination Authentication tokens
CONNECTIVITY_SECRET = CONNECTIVITY_SERVICE.credentials["clientid"] + \
    ':' + CONNECTIVITY_SERVICE.credentials["clientsecret"]

DESTINATION_SECRET = DESTINATION_SERVICE.credentials["clientid"] + \
    ':' + DESTINATION_SERVICE.credentials["clientsecret"]

CONNECTIVITY_CREDENTIALS = base64.b64encode(
    CONNECTIVITY_SECRET.encode()).decode('ascii')

DESTINATION_CREDENTIALS = base64.b64encode(
    DESTINATION_SECRET.encode()).decode('ascii')


def getAccessToken(credentials, serviceName):
    #Getting access token for Connectivity service.
    headers = {'Authorization': 'Basic ' + credentials,
               'content-type': 'application/x-www-form-urlencoded'}

    form = [('client_id', serviceName.credentials["clientid"]),
            ('grant_type', 'client_credentials')]

    r = requests.post(
        UAA_SERVICE.credentials["url"] + '/oauth/token', data=form, headers=headers)

    token = r.json()['access_token']
    return token


# Helper that Returns the URL of the destination.
def _getDestinationURL(token):
    headers = {'Authorization': 'Bearer ' + token}

    r = requests.get(DESTINATION_SERVICE.credentials["uri"] +
                     '/destination-configuration/v1/destinations/' + DESTINATION, headers=headers)

    destination = r.json()
    return destination["destinationConfiguration"]["URL"]


def getURL():
    # Fetch URL of the Destination
    destination_token = getAccessToken(
        DESTINATION_CREDENTIALS, DESTINATION_SERVICE)
    url = _getDestinationURL(destination_token)
    return url


def getProxy():
    data = {}
    connectivity_token = getAccessToken(
        CONNECTIVITY_CREDENTIALS, CONNECTIVITY_SERVICE)

    # Setting proxies and header for the Destination that needs to be called.
    headers = {
        'Proxy-Authorization': 'Bearer ' + connectivity_token}
    # connection['headers'] = str(headers)
    # proxy
    proxies = {
        "http": CONNECTIVITY_PROXY
    }
    # connection['proxies'] = str(proxies)
    data['headers'] = headers
    data['proxies'] = proxies
    return data

def makeRequest(request,endpoint):
    # Get destination URL
    url = getURL()
    #Get proxy parameters
    connection = getProxy()

    # Call the on-prem process_query service.
    r = requests.post(url+endpoint,
                      proxies=connection['proxies'], headers=connection['headers'],
                      verify=False, timeout=10)
    return json.loads(r.text)


# Routes
@app.route('/process_query', methods=['POST', 'GET'])
def process_query():
    responseText = makeRequest(request,'/process_query.xsjs')
    return jsonify(responseText)



if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug= True)
	

 

Let’s break down the code and see what’s going on –

  • We have created an endpoint /process_query which shall call the process_query.xsjs service in On-Premise system.
  • getAccessToken() – This function provides a JWT token from the XSUAA service. For the connectivity and destination service, we generate two different JWT Token.
  • getURL() – This function invokes the destination service and fetches the Endpoint defined under the demoService destination.
  • getProxy() –  This function provides a proxy for the connectivity service to call the SAP Cloud Connector and in turn invoke the on-premise service.

 

We shall bind all the 3 services – Destination, Connectivity and XSUAA service in the manifest.yml  file –

---
applications:
- memory: 128MB
  name: demo_app
  disk_quota: 256MB
  random-route: true
  services:
  - destination_service
  - uaa_service
  - connectivity_service

 

We shall freeze the Flask application’s dependencies in a requirements.txt  file –

Flask
requests
cfenv

 

We shall also add a Procfle  – 

web: python app.py

Finally, we will add the command needed to start the application in a  runtime.txt  as follows –

python-3.6.6

Our application directory will be as follows –

Now that we are ready with our application, let’s deploy it-

cf push demo_app -u none

Deployment logs –

 

 

4. Test

 

Let’s test our application and see what we get –

Hit the URL- demoapp-silly-buffalo.cfapps.sap.hana.ondemand.com/process_query

SUCCESS !!!

 

Now, our application deployed in Cloud Foundry is able to communicate with XSJS services deployed in an On-Premise system.

I hope this helps anyone who wants to connect systems using SAP Cloud Connector.

 

Regards,

Boudhayan Dev

 

 

EDIT –

The sample On-Premise service shown above does not require any authentication. However, it is very common to have some sort of authentication for your backend services. If that is the case, please use the following modified code –

Calling On-premise service with authentication

 

Do not forget to update the credentials in destination tab before this.

Assigned Tags

      11 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jini Tidalgo
      Jini Tidalgo

      Hello,

       

      Will these steps work if instead of XSJS service, I have a netweaver gateway odata service?

      Thanks!

      Author's profile photo Boudhayan Dev
      Boudhayan Dev
      Blog Post Author

      Hi Jini Tidalgo ,

       

      Yes it will work with Odata as well.

       

      Regards

      Author's profile photo Sebastiano Marchesini
      Sebastiano Marchesini

      Hi Boudhayan Dev,

      thanks for the blog.

      Two question from a neo developer in SCP Cloud Foundry : is this connection possible with trial account ? When I try to create a new destination instance with the service there is this error :

      Are Destination, XSUAA and Connectivity service necessary for create a simple python application connected with on-premise CDS?

      Thank you again,

      Sebastiano

      Author's profile photo Boudhayan Dev
      Boudhayan Dev
      Blog Post Author

      Hi Sebastiano Marchesini ,

       

      1. Yes, it works in trial as well. The error in the above image is due to insufficient quota for connectivity service. Increase in by going into the subaccount level and then the quota plans section.
      2. Yes, XSUAA, Destination and Connectivity service is required if you want to communicate with your on-premise services. These services are available in CF.

       

      Author's profile photo Sebastiano Marchesini
      Sebastiano Marchesini

      Thank you so much Boudhayan Dev, !!! ?

      Now I’ve the destination, uaa and connection service and the application has been deployed.

       

      But I can’t use the backend system for an error in the request for token. I copied your code in my app:

      def getAccessToken(credentials, serviceName):
          #Getting access token for Connectivity service.
          headers = {'Authorization': 'Basic ' + credentials,
                     'content-type': 'application/x-www-form-urlencoded'}
      
          form = [('client_id', serviceName.credentials["clientid"]),
                  ('grant_type', 'client_credentials')]
      
          r = requests.post(UAA_SERVICE.credentials["url"] + '/oauth/token', data=form, headers=headers)
      
          token = r.json()['access_token']
          return token

       

      The system tell me it’s a {“error”:”unauthorized”,”error_description”:”Bad credentials”} . But I set all the instance and key of uaa_service, connectivity_service, and destination_service. Where am I wrong ?

      Thanks again ?

      PS: I’m following your blog and the Pranav Blog, but in my case the system expose an oData service from simply CDS.

      ----- EDIT ----

      Ok, I never followed this blog, maybe is for that the error :

      https://blogs.sap.com/2019/01/25/sap-cloud-platform-backend-service-tutorial-0.1-preparation/#enablebeta

       

      but when I'm initializing the service SAP Cloud Platform Backend Service I've got this error :

       

      Could not subscribe to SAP Cloud Platform Backend service [BETA]. Please try again. If the problem persists, open a ticket using the BCP component OPU-GW-OD-EBS and include the job ID 32ed3584-5c0b-4c9f-ba6f-7af9615d11d1 and the correlation ID 32ed3584-5c0b-4c9f-ba6f-7af9615d11d1

      Like of this question:

      https://answers.sap.com/questions/12716212/sap-cloud-platform-backend-service-beta-version-on.html

      any solution? Is it useful for my intention ? 

      Author's profile photo Ramesh Vodela
      Ramesh Vodela

      Hello

      When I tried to create a destination in the cloud foundry as mentioned I got an error

      Failure reason: Could not check at the moment please try later.

      Has anybody tried recently and to check on Prem Destination on Cloud Foundry.

      Ramesh

      Author's profile photo Kevin Zhang
      Kevin Zhang

      I got the same error. I think the service of "check connection" is unavailable for trail account.

      however it works well for my paid account with the same destination configuration.

      Author's profile photo Kevin Zhang
      Kevin Zhang

      Hi Boudhayan Dev,

      Thanks for the blogs.

      I followed your blog and consumed an odata service from on premise.

      I got an error: urllib3.exceptions.ProxySchemeUnknown

      urllib3.exceptions.ProxySchemeUnknown: Not supported proxy scheme None

      The runtime version that I used is 3.8.1.

      Any ideas?

      Thanks,

      Kevin

      Author's profile photo Kevin Zhang
      Kevin Zhang

      Fixed this issue by adding "http://" for CONNECTIVITY_PROXY.  

      After 3.7.X, the parser of url is changed.  Only IP:Port is not enough. it should be http://<ip>:<port>.

      Author's profile photo Ankur Gokhale
      Ankur Gokhale

      Hello Boudhayan,

      Great article, code snippets worked well at my end. However one problem I faced was about Authentication in Destination type HTTP. Basic Authentication method did not work.

      Author's profile photo Shireen Sheikh
      Shireen Sheikh

      Hi Boudhayan,

      I appreciate your work. It worked like a charm. Thank you very much.