|
|
|
|
|
|
|
import subprocess
|
|
|
|
import shutil
|
|
|
|
import threading
|
|
|
|
import json
|
|
|
|
import os
|
|
|
|
from flask import Flask, request,jsonify
|
|
|
|
import pymongo
|
|
|
|
import orchestrator
|
|
|
|
import validate_trigger
|
|
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
action_url_mappings = {} #Store action->url mappings
|
|
|
|
action_properties_mapping = {} #Stores the action name and its corresponding properties
|
|
|
|
responses = []
|
|
|
|
list_of_func_ids = []
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/")
|
|
|
|
def home():
|
|
|
|
data = {"message": "Hello, Welcome to DAGit","author":"Anubhav Jana"}
|
|
|
|
return jsonify(data)
|
|
|
|
|
|
|
|
@app.route('/view/functions', methods=['GET'])
|
|
|
|
def list_actions():
|
|
|
|
list_of_actions = []
|
|
|
|
stream = os.popen(' wsk -i action list')
|
|
|
|
actions = stream.read().strip().split(' ')
|
|
|
|
try:
|
|
|
|
for action in actions:
|
|
|
|
if action=='' or action=='private' or action=='blackbox':
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
list_of_actions.append(action.split('/')[2])
|
|
|
|
data = {"status": 200,"DAGit functions":list_of_actions}
|
|
|
|
return data
|
|
|
|
except Exception as e:
|
|
|
|
data = {"status": 404, "failure reason": e}
|
|
|
|
|
|
|
|
@app.route('/register/trigger/',methods=['POST'])
|
|
|
|
def register_trigger():
|
|
|
|
trigger_json = request.json
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["trigger_store"]
|
|
|
|
mycol = mydb["triggers"]
|
|
|
|
try:
|
|
|
|
cursor = mycol.insert_one(trigger_json)
|
|
|
|
print("OBJECT ID GENERATED",cursor.inserted_id)
|
|
|
|
if(trigger_json["type"]=="dag"):
|
|
|
|
targets = trigger_json["dags"]
|
|
|
|
elif(trigger_json["type"]=="function"):
|
|
|
|
targets = trigger_json["functions"]
|
|
|
|
data = {"status":"success","trigger_name":trigger_json["trigger_name"],"trigger_type":trigger_json["type"],"trigger_target":targets}
|
|
|
|
return json.dumps(data)
|
|
|
|
except Exception as e:
|
|
|
|
data = {"status":"fail","reason":e}
|
|
|
|
return json.dumps(data)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/register/function/<function_name>',methods=['POST'])
|
|
|
|
def register_function(function_name):
|
|
|
|
list_of_file_keys = []
|
|
|
|
document = {}
|
|
|
|
function_dir = '/home/ubuntu/function_modules' # Library of functions
|
|
|
|
new_dir = function_name
|
|
|
|
destination = os.path.join(function_dir, new_dir)
|
|
|
|
# Create the directory
|
|
|
|
os.makedirs(destination, exist_ok=True)
|
|
|
|
files = request.files
|
|
|
|
for filekey in files:
|
|
|
|
if filekey!='description':
|
|
|
|
list_of_file_keys.append(filekey)
|
|
|
|
for key in list_of_file_keys:
|
|
|
|
file = request.files[key]
|
|
|
|
filename = file.filename
|
|
|
|
# Save, copy, remove
|
|
|
|
file.save(file.filename)
|
|
|
|
shutil.copy(filename, destination)
|
|
|
|
os.remove(filename)
|
|
|
|
image_build_script = '/home/ubuntu/DAGit/controlplane/buildAndPush.sh'
|
|
|
|
shutil.copy(image_build_script, destination)
|
|
|
|
|
|
|
|
# Prepare data
|
|
|
|
document["function_name"] = function_name
|
|
|
|
document["image_build_script"] = '/home/ubuntu/DAGit/controlplane/buildAndPush.sh'
|
|
|
|
document["python_script"] = (request.files[list_of_file_keys[0]]).filename
|
|
|
|
document["dockerfile"] = (request.files[list_of_file_keys[1]]).filename
|
|
|
|
document["requirements.txt"] =(request.files[list_of_file_keys[2]]).filename
|
|
|
|
|
|
|
|
docker_image_name = "127.0.0.1:5000/"+function_name+"-image"
|
|
|
|
|
|
|
|
api_name = "/"+function_name+"-api"
|
|
|
|
path_name = "/"+function_name+"-path"
|
|
|
|
password = 'Nijinlong1.'
|
|
|
|
docker_registry_host = "localhost:5000"
|
|
|
|
# build docker image
|
|
|
|
cmd = ["sudo", "-S", "/home/ubuntu/function_modules/test/buildAndPush.sh",function_name+"-image",docker_registry_host,destination]
|
|
|
|
# open subprocess with Popen
|
|
|
|
process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
|
|
|
|
|
|
|
|
# pass password to standard input
|
|
|
|
process.stdin.write(password + "\n")
|
|
|
|
process.stdin.flush()
|
|
|
|
|
|
|
|
# wait for process to complete and get output
|
|
|
|
output, errors = process.communicate()
|
|
|
|
print("OUTPUT---------",output)
|
|
|
|
print("ERRORS---------",errors)
|
|
|
|
# if(errors):
|
|
|
|
# print("There is error building docker file")
|
|
|
|
# data = {"message":"fail","reason":"docker build failed"}
|
|
|
|
# return json.dumps(data)
|
|
|
|
# else:
|
|
|
|
|
|
|
|
# create action, register action with api, populate its mapping
|
|
|
|
subprocess.call(['./create_action.sh',destination,docker_image_name,function_name])
|
|
|
|
subprocess.call(['./register.sh',api_name,path_name,function_name])
|
|
|
|
subprocess.call(['bash', './actions.sh'])
|
|
|
|
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["function_store"]
|
|
|
|
mycol = mydb["functions"]
|
|
|
|
try:
|
|
|
|
cursor = mycol.insert_one(document)
|
|
|
|
print("OBJECT ID GENERATED",cursor.inserted_id)
|
|
|
|
data = {"message":"success"}
|
|
|
|
return json.dumps(data)
|
|
|
|
except Exception as e:
|
|
|
|
print("Error--->",e)
|
|
|
|
data = {"message":"fail","reason":e}
|
|
|
|
return json.dumps(data)
|
|
|
|
|
|
|
|
# data = {"message":"success"}
|
|
|
|
# return json.dumps(data)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/register/dag/',methods=['POST'])
|
|
|
|
def register_dag():
|
|
|
|
dag_json = request.json
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["dag_store"]
|
|
|
|
mycol = mydb["dags"]
|
|
|
|
try:
|
|
|
|
cursor = mycol.insert_one(dag_json)
|
|
|
|
print("OBJECT ID GENERATED",cursor.inserted_id)
|
|
|
|
data = {"message":"success"}
|
|
|
|
return json.dumps(data)
|
|
|
|
except Exception as e:
|
|
|
|
print("Error--->",e)
|
|
|
|
data = {"message":"fail","reason":e}
|
|
|
|
return json.dumps(data)
|
|
|
|
|
|
|
|
@app.route('/view/dag/<dag_name>',methods=['GET'])
|
|
|
|
def view_dag(dag_name):
|
|
|
|
dag_info_map = {}
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["dag_store"]
|
|
|
|
mycol = mydb["dags"]
|
|
|
|
document = mycol.find({"name":dag_name})
|
|
|
|
data = list(document)
|
|
|
|
dag_info_list = []
|
|
|
|
for items in data:
|
|
|
|
dag_info_list = items["dag"]
|
|
|
|
dag_info_map["dag_name"] = items["name"]
|
|
|
|
|
|
|
|
dag_info_map["number_of_nodes"] = len(dag_info_list)
|
|
|
|
dag_info_map["starting_node"] = dag_info_list[0]["node_id"]
|
|
|
|
|
|
|
|
for dag_items in dag_info_list:
|
|
|
|
node_info_map = {}
|
|
|
|
if(len(dag_items["properties"]["outputs_from"])==0):
|
|
|
|
node_info_map["get_outputs_from"] = "Starting action - >No outputs consumed"
|
|
|
|
else:
|
|
|
|
node_info_map["get_outputs_from"] = dag_items["properties"]["outputs_from"]
|
|
|
|
node_info_map["primitive"] = dag_items["properties"]["primitive"]
|
|
|
|
if(dag_items["properties"]["primitive"]=="condition"):
|
|
|
|
node_info_map["next_node_id_if_condition_true"] = dag_items["properties"]["branch_1"]
|
|
|
|
node_info_map["next_node_id_if_condition_false"] = dag_items["properties"]["branch_2"]
|
|
|
|
else:
|
|
|
|
if(dag_items["properties"]["next"]!=""):
|
|
|
|
node_info_map["next_function"] = dag_items["properties"]["next"]
|
|
|
|
else:
|
|
|
|
node_info_map["next_function"] = "Ending node_id of a path"
|
|
|
|
dag_info_map[dag_items["node_id"]] = node_info_map
|
|
|
|
response = {"dag_data":dag_info_map}
|
|
|
|
# formatted_json = json.dumps(response, indent=20)
|
|
|
|
return response
|
|
|
|
|
|
|
|
@app.route('/view/dags',methods=['GET'])
|
|
|
|
def view_dags():
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["dag_store"]
|
|
|
|
mycol = mydb["dags"]
|
|
|
|
document = mycol.find()
|
|
|
|
data = list(document)
|
|
|
|
# Serialize the data to JSON
|
|
|
|
json_data = json.dumps(data, default=str)
|
|
|
|
json_string ='{"dag":'+str(json_data)+'}'
|
|
|
|
data = json.loads(json_string)
|
|
|
|
# # Format the JSON string with indentation
|
|
|
|
# formatted_json = json.dumps(data, indent=4)
|
|
|
|
return data
|
|
|
|
|
|
|
|
@app.route('/view/triggers',methods=['GET'])
|
|
|
|
def view_triggers():
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["trigger_store"]
|
|
|
|
mycol = mydb["triggers"]
|
|
|
|
document = mycol.find()
|
|
|
|
data = list(document)
|
|
|
|
# Serialize the data to JSON
|
|
|
|
json_data = json.dumps(data, default=str)
|
|
|
|
json_string ='{"trigger":'+str(json_data)+'}'
|
|
|
|
data = json.loads(json_string)
|
|
|
|
# Format the JSON string with indentation
|
|
|
|
# formatted_json = json.dumps(data, indent=4)
|
|
|
|
return data
|
|
|
|
|
|
|
|
@app.route('/view/trigger/<trigger_name>',methods=['GET'])
|
|
|
|
def view_trigger(trigger_name):
|
|
|
|
print(request.url)
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["trigger_store"]
|
|
|
|
mycol = mydb["triggers"]
|
|
|
|
query = {"trigger_name":trigger_name}
|
|
|
|
projection = {"_id": 0,"trigger_name":1,"type":1,"trigger":1,"dags":1,"functions":1}
|
|
|
|
document = mycol.find(query,projection)
|
|
|
|
data = list(document)
|
|
|
|
# print(data)
|
|
|
|
json_data = json.dumps(data, default=str)
|
|
|
|
json_string ='{"trigger":'+str(json_data)+'}'
|
|
|
|
data = json.loads(json_string)
|
|
|
|
formatted_json = json.dumps(data, indent=4)
|
|
|
|
return formatted_json
|
|
|
|
|
|
|
|
# EXAMPLE URL: http://127.0.0.1:5001/view/activation/8d7df93e8f2940b8bdf93e8f2910b80f
|
|
|
|
@app.route('/view/activation/<activation_id>', methods=['GET', 'POST'])
|
|
|
|
def list_activations(activation_id):
|
|
|
|
# activation_id = '74a7b6c707d14973a7b6c707d1a97392'
|
|
|
|
cmd = ['wsk', '-i', 'activation', 'get', activation_id]
|
|
|
|
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
json_res = result.stdout.decode().split('\n')[1:] # Ignore first line of output
|
|
|
|
res = json.loads('\n'.join(json_res))
|
|
|
|
d={}
|
|
|
|
d["action_name"] = res["name"]
|
|
|
|
d["duration"] = res["duration"]
|
|
|
|
d["status"] = res["response"]["status"]
|
|
|
|
d["result"] = res["response"]["result"]
|
|
|
|
return({"action_name":res["name"],
|
|
|
|
"duration": res["duration"],
|
|
|
|
"status": res["response"]["status"],
|
|
|
|
"result":res["response"]["result"]
|
|
|
|
})
|
|
|
|
|
|
|
|
# EXAMPLE URL: http://127.0.0.1:5001/view/dag/76cc8a53-0a63-47bb-a5b5-9e6744f67c61
|
|
|
|
@app.route('/view/<dag_id>',methods=['GET'])
|
|
|
|
def view_dag_metadata(dag_id):
|
|
|
|
myclient = pymongo.MongoClient("mongodb://127.0.0.1/27017")
|
|
|
|
mydb = myclient["dag_store"]
|
|
|
|
mycol = mydb["dag_metadata"]
|
|
|
|
query = {"dag_id":dag_id}
|
|
|
|
projection = {"_id": 0,"dag_id":1,"dag_name":1,"function_activation_ids":1}
|
|
|
|
document = mycol.find(query, projection)
|
|
|
|
data = list(document)
|
|
|
|
response = {"dag_metadata":data}
|
|
|
|
return json.dumps(response)
|
|
|
|
|
|
|
|
# EXAMPLE URL: http://127.0.0.1:5001/run/action/odd-even-action
|
|
|
|
# http://127.0.0.1:5001/run/action/decode-function
|
|
|
|
|
|
|
|
def execute_action(action_name):
|
|
|
|
try:
|
|
|
|
res = orchestrator.execute_action(action_name)
|
|
|
|
data = {"status": 200,"dag_output":res}
|
|
|
|
return data
|
|
|
|
except Exception as e:
|
|
|
|
data = {"status": 404 ,"failure_reason":e}
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
# EXAMPLE URL: http://127.0.0.1:5001/run/mydagtrigger
|
|
|
|
@app.route('/run/<trigger_name>', methods=['GET', 'POST'])
|
|
|
|
def orchestrate_dag(trigger_name):
|
|
|
|
|
|
|
|
|
|
|
|
orchestrator.dag_responses = []
|
|
|
|
try:
|
|
|
|
triggers = validate_trigger.get_trigger_json(trigger_name)
|
|
|
|
if len(triggers) == 0:
|
|
|
|
return {"response": "the given trigger is not registered in DAGit trigger store"}
|
|
|
|
else:
|
|
|
|
thread_list = []
|
|
|
|
if triggers[0]['type'] == 'dag':
|
|
|
|
# dags = triggers[0]['dags']
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
# If only 1 dag execute it directly without thread.
|
|
|
|
|
|
|
|
no_of_dags = len(triggers[0]['dags'])
|
|
|
|
|
|
|
|
if no_of_dags==1:
|
|
|
|
# print('Inside libne 341')
|
|
|
|
|
|
|
|
|
|
|
|
orchestrator.execute_dag(triggers[0]['dags'][0], request.json)
|
|
|
|
# print(orchestrator.dag_responses)
|
|
|
|
# orchestrator.execute_dag(triggers[0]['dags'][0],request.json)
|
|
|
|
if(len(orchestrator.dag_responses)!=0):
|
|
|
|
response = orchestrator.dag_responses
|
|
|
|
orchestrator.dag_responses = []
|
|
|
|
return {"response": response, "status": 200}
|
|
|
|
else:
|
|
|
|
return{"response":"Workflow did not execute completely", "status": 400}
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
for dag in triggers[0]['dags']:
|
|
|
|
thread_list.append(threading.Thread(target=orchestrator.execute_dag, args=[dag, request.json]))
|
|
|
|
for thread in thread_list:
|
|
|
|
thread.start()
|
|
|
|
for thread in thread_list:
|
|
|
|
thread.join()
|
|
|
|
|
|
|
|
if(len(orchestrator.dag_responses)!=0):
|
|
|
|
return {"response": orchestrator.dag_responses, "status": 200}
|
|
|
|
else:
|
|
|
|
return{"response":"Workflow did not execute completely", "status": 400}
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
print("Error------->",e)
|
|
|
|
return {"response": "failed", "status": 400}
|
|
|
|
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
functions = triggers[0]['functions']
|
|
|
|
arguments = request.json
|
|
|
|
# with lock:
|
|
|
|
for function in functions:
|
|
|
|
thread_list.append(threading.Thread(target=orchestrator.execute_action, args=[function, arguments]))
|
|
|
|
for thread in thread_list:
|
|
|
|
thread.start()
|
|
|
|
for thread in thread_list:
|
|
|
|
thread.join()
|
|
|
|
|
|
|
|
return {"response": orchestrator.function_responses, "status": 200}
|
|
|
|
except Exception as e:
|
|
|
|
# print("Error------->",e)
|
|
|
|
return {"response": "failed", "status": 400}
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
# print("Error------->",e)(e)
|
|
|
|
data = {"status": 404, "message": "failed"}
|
|
|
|
return data
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
######### Updates the list of action->url mapping ###################
|
|
|
|
script_file = './actions.sh'
|
|
|
|
subprocess.call(['bash', script_file])
|
|
|
|
#####################################################################
|
|
|
|
orchestrator.preprocess("action_url.txt")
|
|
|
|
app.run(host='0.0.0.0', port=5001,threaded=True)
|
|
|
|
# app.run()
|