Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions fri/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM centos/python-36-centos7
COPY requirements.txt /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt
WORKDIR /fri
COPY server /fri
USER root
RUN useradd fri && chown -R fri /fri
USER fri
CMD ["gunicorn", "--timeout=180", "--workers=4", "--bind=0.0.0.0:8080", "--access-logfile=-", "main:app"]
133 changes: 133 additions & 0 deletions fri/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# The Control-Core FRI for Closed-Loop Neuromodulation Control Systems

The Control-Core File Receiving Interface (FRI) is built with is Python-3.10. It is the core component that makes the distributed executions a reality in the Control-Core framework.


# Building FRI Container

Connect to the Server VM, assuming x.x.x.x to be the IP address of your server.
````
$ ssh -i "controlcore.pem" ubuntu@x.x.x.x
````
Perform Git clone if this is the first time you are configuring the Server
````
$ git clone git@github.com/ControlCore-Project/concore.git
````

First build the Docker Container of the FRI.
````
$ git pull

$ sudo docker build -t fri .
````

# Running Control-Core FRI with Kong as containers

If you are already running FRI, make sure to stop and clear existing FRI container as it is likely conflict with the port. If there is Kong gateway running in default ports, stop and clear it too.
````
$ docker stop fri
$ docker rm fri
$ docker stop kong
$ docker rm kong
````

Start and configure Cassandra container for Kong API.
````
$ docker run -d --name kong-database \
-p 9042:9042 \
cassandra:3


$ docker run --rm \
--link kong-database:kong-database \
-e "KONG_DATABASE=cassandra" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_USER=kong" \
-e "KONG_PG_PASSWORD=kong" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
kong kong migrations bootstrap
````

Start Kong
````
$ docker run -d --name kong \
--link kong-database:kong-database \
-e "KONG_DATABASE=cassandra" \
-e "KONG_PG_HOST=kong-database" \
-e "KONG_PG_PASSWORD=kong" \
-e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
-p 80:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 8444:8444 \
kong
````

Start FRI container
````
$ nohup sudo docker run --name fri -p 8090:8081 fri > controlcore.out &
````

Delete if there is a previously configured Kong service. If not, skip this step. First you need to find the ID-VALUE for the route with a GET command before deleting the route and service.
````
$ curl -X GET "http://localhost:8001/services/fri/routes"
````
Use the ID output from above to issue the delete command as below (issue this only if you have a previous conflicting service definiton in kong. Otherwise, skip this step):
````
$ curl -X DELETE "http://localhost:8001/services/fri/routes/ID-VALUE"

$ curl -X DELETE "http://localhost:8001/services/fri/"
````

Define Kong Service and Route.

First Configure a Kong service, replacing the variable "private-ip" with the private IP address of your server below.
````
$ curl -i -X POST --url http://localhost:8001/services/ --data 'name=fri' --data 'url=http://private-ip:8090'
````
Then configure route to the service
````
$ curl -i -X POST --url http://localhost:8001/services/fri/routes --data 'paths=/'
````

Now, controlcore.org is routed through the Kong APIs.


# Troubleshooting the FRI

Connect to the Server VM
````
$ ssh -i "controlcore.pem" ubuntu@x.x.x.x
````
Check the Server logs.
````
$ tail -f controlcore.out
````
or
````
$ sudo docker logs fri -f
````
Find the FRI docker container
````
$ sudo docker ps
````
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dfdd3b3d3308 fri "python main.py" 38 minutes ago Up 38 minutes 0.0.0.0:80->80/tcp fri

Access the container
````
$ sudo docker exec -it dfdd /bin/bash
````



# Citing the CONTROL-CORE FRI

If you use the CONTROL-CORE FRI in your research, please cite the below paper:

* Kathiravelu, P., Arnold, M., Fleischer, J., Yao, Y., Awasthi, S., Goel, A. K., Branen, A., Sarikhani, P., Kumar, G., Kothare, M. V., and Mahmoudi, B. **CONTROL-CORE: A Framework for Simulation and Design of Closed-Loop Peripheral Neuromodulation Control Systems**. In IEEE Access. March 2022. https://doi.org/10.1109/ACCESS.2022.3161471
1 change: 1 addition & 0 deletions fri/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
print("Concore/fri")
2 changes: 2 additions & 0 deletions fri/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask
gunicorn==20.1.0
164 changes: 164 additions & 0 deletions fri/server/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from flask import Flask, request, jsonify, send_file, send_from_directory
from werkzeug.utils import secure_filename
import os
from subprocess import call
from pathlib import Path


app = Flask(__name__)
app.secret_key = "secret key"

# To upload multiple file. For example, /upload/test?apikey=xyz
@app.route('/upload/<dir>', methods=['POST'])
def upload(dir):
apikey = request.args.get('apikey')
dirname = dir + "_" + apikey

if 'files[]' not in request.files:
resp = jsonify({'message': 'No file in the request'})
resp.status_code = 400
return resp

files = request.files.getlist('files[]')

errors = {}
success = False

if not os.path.exists(secure_filename(dirname)):
os.makedirs(secure_filename(dirname))

for file in files:
if file:
filename = secure_filename(file.filename)
file.save(secure_filename(dirname)+"/"+filename)
success = True

if success and errors:
errors['message'] = 'File(s) successfully uploaded'
resp = jsonify(errors)
resp.status_code = 500
return resp
if success:
resp = jsonify({'message': 'Files successfully uploaded'})
resp.status_code = 201
return resp
else:
resp = jsonify(errors)
resp.status_code = 500
return resp

# To execute any python file. For example, /execute/test?apikey=xyz
@app.route('/execute/<dir>', methods=['POST'])
def execute(dir):
apikey = request.args.get('apikey')
dirname = dir + "_" + apikey

if 'file' not in request.files:
resp = jsonify({'message': 'No file in the request'})
resp.status_code = 400
return resp

file = request.files['file']

if file.filename == '':
resp = jsonify({'message': 'No file selected for Executing'})
resp.status_code = 400
return resp

errors = {}
success = False

if not os.path.exists(secure_filename(dirname)):
os.makedirs(secure_filename(dirname))

if file:
filename = secure_filename(file.filename)
file.save(secure_filename(dirname)+"/"+filename)
output_filename = filename + ".out"
file_path = secure_filename(dirname) + "/"+filename
outputfile_path = secure_filename(dirname)+"/"+output_filename
f = open(outputfile_path, "w")
call(["nohup", "python3", file_path], stdout=f)
success = True

if success:
resp = jsonify({'message': 'Files successfully executed'})
resp.status_code = 201
return resp
else:
resp = jsonify(errors)
resp.status_code = 500
return resp

# to download /build/<dir>?fetch=<graphml>. For example, /build/test?fetch=sample1
@app.route('/build/<dir>', methods=['POST'])
def build(dir):
graphml_file = request.args.get('fetch')
makestudy_dir = dir+ "/" + graphml_file #for makestudy
cur_path = os.getcwd()
concore_path = os.path.abspath(os.path.join(cur_path, '../../'))
dir_path = os.path.abspath(os.path.join(concore_path, graphml_file)) #path for ./build
if not os.path.exists(secure_filename(dir_path)):
p1 = call(["./makestudy", makestudy_dir], cwd=concore_path)
if(p1 == 0):
resp = jsonify({'message': 'Directory successfully created'})
resp.status_code = 201
else:
resp = jsonify({'message': 'There is an Error'})
resp.status_code = 500
call(["./build"], cwd=dir_path)
return resp


@app.route('/debug/<dir>', methods=['POST'])
def debug(dir):
cur_path = os.getcwd()
concore_path = os.path.abspath(os.path.join(cur_path, '../../'))
dir_path = os.path.abspath(os.path.join(concore_path, dir))
p1 = call(["./debug"], cwd=dir_path)
if(p1 == 0):
resp = jsonify({'message': 'Close the pop window after obtaing result'})
resp.status_code = 201
return resp
else:
resp = jsonify({'message': 'There is an Error'})
resp.status_code = 500
return resp



# to download /download/<dir>?fetch=<downloadfile>. For example, /download/test?fetch=example.py.out&apikey=xyz
@app.route('/download/<dir>', methods=['POST', 'GET'])
def download(dir):
download_file = request.args.get('fetch')
apikey = request.args.get('apikey')
dirname = dir + "_" + apikey

if not os.path.exists(secure_filename(dirname)):
resp = jsonify({'message': 'Directory not found'})
resp.status_code = 400
return resp

try:
return send_from_directory(secure_filename(dirname), download_file, as_attachment=True)
except:
resp = jsonify({'message': 'file not found'})
resp.status_code = 400
return resp

@app.route('/destroy/<dir>', methods=['POST'])
def destroy(dir):
cur_path = os.getcwd()
concore_path = os.path.abspath(os.path.join(cur_path, '../../'))
p1 = call(["./destroy", dir], cwd=concore_path)
if(p1 == 0):
resp = jsonify({'message': 'Successfuly deleted Dirctory'})
resp.status_code = 201
return resp
else:
resp = jsonify({'message': 'There is an Error'})
resp.status_code = 500
return resp

if __name__ == "__main__":
app.run(host="0.0.0.0")
22 changes: 22 additions & 0 deletions fri/service_config.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash

SERVICES=('upload' 'execute' 'download')
for service in "${SERVICES[@]}"
do
name="name="$service
path="paths=/"$service
service_path="url=http://private-ip:8090/"$service
route_path="http://localhost:8001/services/"$service"/routes"
plugin_path="http://localhost:8001/services/"$service"/plugins"
echo "Name is, $name"
echo "Path is, $path"
echo "Service path is, $service_path"
echo "Route path is, $route_path"
echo "Plugin path is, $plugin_path"
echo "Creating Service, $service..."
curl -i -X POST --url http://localhost:8001/services/ --data $name --data $service_path
echo "Creating route for $service..."
curl -i -X POST --url $route_path --data $path
echo "Creating apikey-based authentication for $service..."
curl -X POST $plugin_path --data "name=key-auth" --data "config.key_names=apikey"
done
48 changes: 48 additions & 0 deletions fri/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import requests
import os
import urllib.request

# function to test upload() method.
def upload():
url = "http://127.0.0.1:5000/upload/test?apikey=xyz"

path = os.path.abspath("example.py")

payload={}
files=[
('files[]',('example.py',open(path,'rb'),'application/octet-stream'))
]
headers = {}

response = requests.request("POST", url, headers=headers, data=payload, files=files)

print(response.text)


# # *******

# function to test execute() method.
def execute():
url = "http://127.0.0.1:5000/execute/test?apikey=xyz"

path = os.path.abspath("example.py")

payload={}
files=[
('file',('example.py',open(path,'rb'),'application/octet-stream'))
]
headers = {}

response = requests.request("POST", url, headers=headers, data=payload, files=files)

print(response.text)

# function to test download() method.
def download():
url = "http://127.0.0.1:5000/download/test?fetch=f1.txt&apikey=xyz"
urllib.request.urlretrieve(url, "f1.txt")

upload()
execute()
download()

Loading