diff --git a/docker/pseudo_dtn/Dockerfile b/docker/pseudo_dtn/Dockerfile new file mode 100644 index 00000000..8d52d42e --- /dev/null +++ b/docker/pseudo_dtn/Dockerfile @@ -0,0 +1,7 @@ +FROM metabrainz/docker-anon-ftp + +RUN apt-get update && apt-get install -y ftp + +COPY server.py /root/ + +CMD python3 /root/server.py \ No newline at end of file diff --git a/docker/pseudo_dtn/server.py b/docker/pseudo_dtn/server.py new file mode 100644 index 00000000..be98fa46 --- /dev/null +++ b/docker/pseudo_dtn/server.py @@ -0,0 +1,197 @@ +# Copyright 2019 - Sean Donovan +# AtlanticWave/SDX Project + +# This is a REST interface to an anonymous FTP client Uses the ftplib +# Borrowing heavily from the following tutorials: +# https://pythonprogramming.net/ftp-transfers-python-ftplib/ +# https://stackoverflow.com/questions/111954/using-pythons-ftplib-to-get-a-directory-listing-portably + +from flask import Flask, jsonify, abort, make_response, request +from flask_restful import Api, Resource, reqparse, fields, marshal +from threading import Thread +from ftplib import FTP +from time import sleep +from datetime import datetime +import sys + + + +errors = { + 'NotFound': { + 'message': "A resource with that ID does not exist.", + 'status': 404, + 'extra': "No extra information", + }, +} + + +# HTTP status codes: +HTTP_GOOD = 200 +HTTP_CREATED = 201 +HTTP_ACCEPTED = 202 +HTTP_NO_CONTENT = 204 +HTTP_NOT_MODIFIED = 304 +HTTP_BAD_REQUEST = 400 +HTTP_FORBIDDEN = 403 # RETURNED BY FLASK-RESTFUL ONLY +HTTP_NOT_FOUND = 404 +HTTP_NOT_ACCEPTABLE = 406 +HTTP_CONFLICT = 409 +HTTP_SERVER_ERROR = 500 # RETURNED BY FLASK-RESTFUL ONLY + +# In progress status +IP_NO_TRANSFER = "No Transfer" +IP_STARTED_TRANSFER = "Started Transfer" +IP_TRANSFER_COMPLETE = "Transfer Complete" +IP_TRANSFER_FAILED = "Transfer Failed" + +global in_progress +global total_time +global transfer_thread + +in_progress = IP_NO_TRANSFER +total_time = None +api_process = None +transfer_thread = None + +class DTNRequest(Resource): + #global in_progress + #def __init__(self, urlbase): + # self.urlbase = urlbase + + def post(self, remote, filename): + global in_progress + global total_time + global transfer_thread + print("DTNRequest for %s:%s" % (remote,filename)) + self.remote = remote + self.filename = filename + + # Initialize IP_NO_TRANSFER and total_time + in_progress = IP_NO_TRANSFER + total_time = None + + # Kick off transfer thread + transfer_thread = Thread(target=run_transfer_thread, + args=(self.remote, self.filename)) + transfer_thread.daemon = True + transfer_thread.start() + + # Watch the in_progress for changes + while(in_progress == IP_NO_TRANSFER): + print("in_progress hasn't changed yet. %s" % in_progress) + sleep(.1) + + # Once we have a changed status, return corresponding change + if in_progress == IP_STARTED_TRANSFER: + # good! + retval = {'state': 'Started transfer of %s' % filename, + 'remote': remote, + 'filename': filename}, HTTP_GOOD + else: + retval = {'state': 'FAILURE: %s' % in_progress}, HTTP_BAD_REQUEST + + return retval + + +def run_transfer_thread(remote, filename): + global in_progress + global total_time + # Get start time + start_time = datetime.now() + + # Get connection with FTP + ftp = FTP(remote) + ftp.login(user='anonymous', passwd='') + + # Verify file exists and set in_progress to correct result + files = [] + try: + files = ftp.nlst() + except ftplib.error_perm, resp: + if str(resp) == "550 No files found": + print("No files in this directory") + in_progress = IP_TRANSFER_FAILED + return + else: + print("Some other error occurred: %s" % str(resp)) + in_progress = IP_TRANSFER_FAILED + return + if filename not in files: + print("File is not available %s" % files) + in_progress = IP_TRANSFER_FAILED + return + + in_progress = IP_STARTED_TRANSFER + + # Get file from remote connection + localfile = open(filename, 'wb') + ftp.retrbinary('RETR ' + filename, localfile.write, 1024) + print("File %s has been transferred" % filename) + + + # Set total_time to correct result + end_time = datetime.now() + total_time = str(end_time - start_time) + in_progress = IP_TRANSFER_COMPLETE + return + + +class Dir(Resource): + def get(self, remote): + # Get connection with FTP + ftp = FTP(remote) + ftp.login(user='anonymous', passwd='') + + # Get the list of files + files = [] + try: + files = ftp.nlst() + except ftplib.error_perm, resp: + if str(resp) == "550 No files found": + print("No files in this directory") + return("No files in this directory") + else: + print("Some other error occurred: %s" % str(resp)) + return("Some other error occurred: %s" % str(resp)) + return files + +class Status(Resource): + #def __init__(self, urlbase): + # self.urlbase = urlbase + + def get(self): + global in_progress + global total_time + return ("%s-%s" % (in_progress, total_time)) + + + + +if len(sys.argv) < 3: + print("Need two arguments:") + print(" hostip - IP of current host") + print(" port - port on host to listen to") + exit() +host = sys.argv[1] +port = sys.argv[2] +print("HOST: %s" % host) +print("PORT: %s" % port) + +app = Flask(__name__) +api = Api(app, errors=errors) +api.add_resource(DTNRequest, + '/dtn/transfer//', + endpoint='transfer',) + #resource_class_kwargs={'urlbase':urlbase}) +api.add_resource(Dir, + '/dtn/transfer/', + endpoint='dir',) + #resource_class_kwargs={'urlbase':urlbase}) +api.add_resource(Status, + '/dtn/status', + endpoint='status',) + #resource_class_kwargs={'urlbase':self.urlbase}) + +app.run(host=host, port=port) + + diff --git a/docker/sci_gw_demo/Dockerfile b/docker/sci_gw_demo/Dockerfile new file mode 100644 index 00000000..f186e44f --- /dev/null +++ b/docker/sci_gw_demo/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:18.04 + +RUN apt update && apt install -y iputils-ping iperf net-tools + +COPY science_gateway.py /root/ + +CMD python3 /root/science_gateway.py \ No newline at end of file diff --git a/docker/sci_gw_demo/science_gateway.py b/docker/sci_gw_demo/science_gateway.py new file mode 100644 index 00000000..75798fd8 --- /dev/null +++ b/docker/sci_gw_demo/science_gateway.py @@ -0,0 +1,213 @@ +# Copyright 2019 - Sean Donovan +# AtlanticWave/SDX Project + +# This is an example Science Gateway-like application that creates paths and +# initiates transfers between our pseudo-DTN applications + + +import sys +import subprocess +from datetime import datetime, timedelta +from time import sleep + + +# HTTP status codes: +HTTP_GOOD = 200 +HTTP_CREATED = 201 +HTTP_ACCEPTED = 202 +HTTP_NO_CONTENT = 204 +HTTP_NOT_MODIFIED = 304 +HTTP_BAD_REQUEST = 400 +HTTP_FORBIDDEN = 403 # RETURNED BY FLASK-RESTFUL ONLY +HTTP_NOT_FOUND = 404 +HTTP_NOT_ACCEPTABLE = 406 +HTTP_CONFLICT = 409 +HTTP_SERVER_ERROR = 500 # RETURNED BY FLASK-RESTFUL ONLY + +port = 9999 + +# List of endpoints +endpoints = {'atl-dtn':{'ip':'1.2.3.4','port':9999, + 'switch':'atl-switch', 'switchport':3, 'vlan':123}, + 'mia-dtn':{'ip':'2.3.4.5','port':9999, + 'switch':'mia-switch', 'switchport':2, 'vlan':123}} + +global sdx_ip, sdx_port, sdx_user, sdx_pw, login_cookie, tunnel_policy +sdx_ip = '1.2.3.4' +sdx_port = 5555 +sdx_user = 'sdonovan' +sdx_pw = '1234' +login_cookie = None +tunnel_policy = None + + +def create_tunnel(srcswitch, dstswitch, srcport, dstport, + srcvlan, dstvlan, time): + global sdx_ip, sdx_port, login_cookie, tunnel_policy + + rfc3339format = "%Y-%m-%dT%H:%M:%S" + + + if login_cookie == None: + login_to_sdx_controller() + + # Calculate start and end times + starttime = datetime.now() + endtime = starttime + time + + # Issue POST command + endpoint = "http://%s:%s/api/v1/policies/type/l2tunnel" % (sdx_ip, sdx_port) + l2tunnel = '{"L2Tunnel":{"starttime":"%s","endtime":"%s","srcswitch":"%s","dstswitch":"%s","srcport":%s,"dstport":%s,"srcvlan":%s,"dstvlan":%s,"bandwidth":10000000}}' % ( + starttime.strftime(rfc3339format), endtime.strftime(rfc3339format), + srcswitch, dstswitch, srcport, dstport, srcvlan, dstvlan) + + output = subprocess.check_call(['curl', '-X', 'POST', + '-H', + 'Content-type: application/json', + '-H', "Accept: application/json", + endpoint, + '-d', l2tunnel, + '-b', login_cookie]) + + # Get policy number + output = json.loads(output) + installed_policynum = int(output['policy']['href'].split('/')[-1]) + + # Set tunnel_policy + tunnel_policy = installed_policynum + +def delete_tunnel(): + global sdx_ip, sdx_port, tunnel_policy, login_cookie + + if login_cookie == None: + login_to_sdx_controller() + + # Issue DELETE command + endpoint = "http://%s:%s/api/vl/policies/number/%s" % ( + sdx_ip, sdx_port, tunnel_policy) + + output = subprocess.check_call(['curl', '-X', 'DELETE', + '-H', 'Accept: application/json', + endpoint, + '-b' login_cookie]) + + tunnel_policy = None + +def login_to_sdx_controller(): + global sdx_ip, sdx_port, sdx_user, sdx_pw, login_cookie + + # if there's a saved login, return + if login_cookie != None: + return + + # Login to SDX Controller + cookie_filename = 'sdx.cookie' + endpoint = 'http://%s:%s/api/v1/login' % (sdx_ip, sdx_port) + output = subprocess.check_output(['curl', '-X', 'POST', + '-F', 'username=%s'%sdx_user, + '-F', 'password=%s'%sdx_pw, + endpoint, + '-c', cookie_filename]) + + # set login_cookie to the filename + login_cookie = cookie_filename + print("Logged into SDX Controller %s:%s" % (sdx_ip, sdx_port)) + + +def print_endpoint(endpoints): + a = 0 + for ep in endpoints: + print("%s - %s" % (a, ep)) + a += 1 + +def get_ep_dict_from_num(endpoints, num): + a = 0 + for ep in endpoints: + if a == num: + return endpoinep + +def get_dir(srcip, srcport): + endpoint = 'http://%s:%s/dtn/transfer/%s' % (srcip, srcport, srcip) + output = subprocess.check_output(['curl', '-X', 'GET', endpoint]) + + + +def parse_files(filestr): + # Split the filestr into a list of files + # Based on https://stackoverflow.com/questions/1894269/convert-string-representation-of-list-to-list + ls = filestr.strip('[]').replace('"', '').replace(' ', '').split(',') + return ls + + +def print_files(files): + a = 0 + for f in files: + print("%s - %s" % (a, f)) + a += 1 + + +def transfer_file(srcip, srcport, dstip, dstport, filename): + timeout = 1000 + + # Execute file transfer + endpoint = 'http://%s:%s/dtn/transfer/%s/%s' % ( + dstip, dstport, srcip, filename) + output = subprocess.check_output(['curl', '-X', 'POST', endpoint]) + print("Transferring file: %s" % output) + + # loop here, checking on status + endpoint = 'http://%s:%s/dtn/status' % (dstip, dstport) + + output ='' + count = 0 + while('Started Transfer' in output): + output = subprocess.check_output(['curl', '-X', 'GET', endpoint]) + sleep(1) + count += 1 + if count > timeout: break + + print("Status of transfer: %s" % output) + if count > timeout: + print("Transfer timed out!") + + # Once we have a complete or a failure, return status + return output + +# Main loop! +while(True): + # Select src and destination + print_endpoint(endpoints) + src = input("Source: ") + dst = input("Destination: ") + + # Establish a path between src and dst for 1 sec + srcdict = get_ep_dict_from_num(endpoints, src) + dstdict = get_ep_dict_from_num(endpoints, dst) + + create_tunnel(srcdict['switch'], dstdict['switch'], + srcdict['switchport'], dstdict['switchport'], + srcdict['vlan'], dstdict['vlan'], + timedelta(0,1)) + + # Get and display files available on src + rawfiles = get_dir(srcdict['ip'], srcdict['port']) + fileslist = parse_files(rawfiles) + print_files(fileslist) + delete_tunnel() + + # Let user choose file to transfer + filenumber = input("Choose a file: ") + filename = fileslist[filenumber] + + # Reestablish path between src and dest + create_tunnel(srcdict['switch'], dstdict['switch'], + srcdict['switchport'], dstdict['switchport'], + srcdict['vlan'], dstdict['vlan'], + timedelta(1,0)) # 1 day, excessive, but we'll delete it, don't worry + + # Make transfer call + transfer_file(srcdict['ip'], srcdict['port'], + dstdict['ip'], dstdict['port'], filename) + + # Clean up + delete_tunnel()