Technical Articles
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.
Hello,
Will these steps work if instead of XSJS service, I have a netweaver gateway odata service?
Thanks!
Hi Jini Tidalgo ,
Yes it will work with Odata as well.
Regards
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
Hi 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:
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 :
any solution? Is it useful for my intention ?
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
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.
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
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>.
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.
Hi Boudhayan,
I appreciate your work. It worked like a charm. Thank you very much.