Newer
Older
"""
All the API endpoints for controling processes and tunnels via SSH
"""
import json
from flask import session, redirect, request, Response, make_response
from flask_restful import Resource
import flask_restful
from . import api, islocal
from .localssh import Ssh, SshAgentException
from .localtunnelstat import SSHSession
# from .localtunnelstat import Tunnelstat
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class GetCert(Resource):
"""
This class is necessary because I'm not reconfiguring
SSHAuthZ to support CORS, but the TES does support CORS
"""
def post(self):
"""
takes a public key, returns to the SPA a certificate
"""
print("in GetCert.post")
data = request.get_json()
print(data)
return {'cert':GetCert.get_cert(data['token'], data['pubkey'], data['signing_url'])}
@staticmethod
def get_cert(access_token, pub_key, url):
"""
Sign a pub key into a cert
"""
import requests
print("accss_token {}".format(access_token))
print("pub_key {}".format(pub_key))
print("url {}".format(url))
sess = requests.Session()
headers = {"Authorization":"Bearer %s"%access_token}
data = {"public_key":pub_key}
resp = sess.post(url, json=data, headers=headers, verify=False)
print("get_cert returned from its external call")
data = resp.json()
return data['certificate']
class TestAuth(Resource):
"""
Tests whether the backend can login to the selected compute Resource
"""
def get(self):
"""
tell the SPA if the TES is logged in
"""
return 'token' in session
class StartAgent(Resource):
def get(self):
from .localtunnelstat import SSHSession
sshsess = SSHSession.get_sshsession()
sshsess.start_agent()
sshsess.refresh()
return "{}".format(sshsess.socket)
class AddKey(Resource):
def post(self):
from .localtunnelstat import SSHSession
sshsess = SSHSession.get_sshsession()
data = request.get_json()
print(data)
sshsess.add_keycert(key=data['key'],cert=data['cert'])
sshsess.refresh()
return "OK"
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def get_conn_params():
"""
Return parameters relating to the backend compute service
Retrieve them from the session (ideally)
"""
return get_m3_params()
def get_m3_params():
"""
Hard code the parameters for M3. This will be removed latter
factored into a site config file for each compute backend
parsed and set by the frontend.
"""
params = {}
params['host'] = 'm3.massive.org.au'
params['user'] = 'chines'
params['cancelcmd'] = 'scancel {jobid}'
params['statcmd'] = '/home/chines/jsonstat.py'
params['submitcmd'] = 'sbatch --partition=m3f'
params['internalfirewall'] = False
return params
def get_app_params():
"""
Return the parameters for the application retrieved from the Session
"""
keys = ['startscript', 'paramscmd', 'client','localbind']
returnvalue = {}
for k in keys:
try:
returnvalue[k] = session.get(k)
except: # This should be a key exception, i.e. if the key doesn't exist on the session
pass
print("got app params",returnvalue)
return returnvalue
class TunnelstatEP(Resource):
"""
Endpoints used by the WS proxy
"""
def put(self, authtok):
"""
update the last used time on a tunnel
"""
Tunnelstat.update(authtok)
def get(self, authtok):
"""
given an authtoken, return the port of the tunnels
"""
from . import sshsessions
for sshsess in sshsessions.values():
if sshsess.authtok == authtok:
return sshsess.port
return None
class Stat(Resource):
"""
endpoints to return info on jobs on the backend compute Resource
"""
def get(self):
"""
get info on the job from the backend
"""
params = get_conn_params()
sshsess = SSHSession.get_sshsession()
try:
res = Ssh.execute(sshsess, host=params['host'], user=params['user'], cmd=params['statcmd'])
except SshAgentException as e:
return flask_restful.abort(404,message="{}".format(e))
if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
print(res['stderr'])
flask_restful.abort(400, message=res['stderr'].decode())
return json.loads(res['stdout'].decode())
class JobCancel(Resource):
"""
Terminate a job on the compute backend
"""
def delete(self, jobid):
"""
Terminate a job on the backend
"""
print("in jobcancle jobid is {}".format(jobid))
params = get_conn_params()
sshsess = SSHSession.get_sshsession()
res = Ssh.execute(sshsess, host=params['host'], user=params['user'],
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
cmd=params['cancelcmd'].format(jobid=jobid))
if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
flask_restful.abort(400, message=res['stderr'].decode())
return res['stdout'].decode()
class AppSetup(Resource):
"""
configure the session for the app the user wants
"""
def post(self):
"""
post details of the app to be run or connected to
"""
data = request.get_json()
keys = ['startscript', 'paramscmd', 'client','localbind']
for key in keys:
session[key] = data[key]
print(data[key])
class JobSubmit(Resource):
"""
Class dealing the starting a new job on the compute backend
"""
def post(self):
"""starting a job is a post, since it changes the state of the backend"""
params = get_conn_params()
appparams = get_app_params()
sshsess = SSHSession.get_sshsession()
res = Ssh.execute(sshsess, host=params['host'], user=params['user'],
cmd=params['submitcmd'], stdin=appparams['startscript'])
if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
print(res['stderr'])
flask_restful.abort(400, message=res['stderr'].decode())
return res['stdout'].decode()
def gen_authtok():
"""
generate a random string suitable for an auth token stored in a cookie
"""
import random
import string
return ''.join(random.SystemRandom().choice(string.ascii_uppercase +
string.digits) for _ in range(16))
class JobConnect(Resource):
"""
endpoints for connecting to an existing JobCancel
"""
def create_tunnel(self, params, appparams, batchhost, data):
connectparams = {}
sshsess = SSHSession.get_sshsession()
if 'paramscmd' in appparams and appparams['paramscmd'] is not None:
connectparams['batchhost'] = batchhost
paramcmd = 'ssh {batchhost} '.format(batchhost=batchhost) + appparams['paramscmd']
res = Ssh.execute(sshsess, host=params['host'], user=params['user'], cmd=paramcmd.format(data))
try:
connectparams.update(json.loads(res['stdout']))
except json.decoder.JSONDecodeError as e:
print(res['stdout'])
print(res['stderr'])
if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
flask_restful.abort(400, message=res['stderr'].decode())
if 'port' in connectparams and 'batchhost' in connectparams:
tunnelport, pids = Ssh.tunnel(sshsess, port=connectparams['port'],
batchhost=connectparams['batchhost'],
user=params['user'], host=params['host'],
internalfirewall=params['internalfirewall'],
localbind=appparams['localbind'])
authtok = gen_authtok()
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
connectparams['localtunnelport'] = tunnelport
connectparams['authtok'] = authtok
return connectparams
def get(self, jobid, batchhost):
"""
Connecting to a job is a get operation (i.e. it does not make modifications)
"""
params = get_conn_params()
appparams = get_app_params()
data = request.get_json()
connectparams = self.create_tunnel(params, appparams, batchhost, data)
return self.connect(appparams, connectparams)
def connect(self, appparams, connectparams):
"""
perform the connection either by forking a local client or returning a redirect
"""
import subprocess
if 'cmd' in appparams['client'] and appparams['client']['cmd'] is not None:
# We need for fork a local process such as vncviewer or a terminal
# We may need a wrapper for local processes to find the correct
# process on all OS
cmdlist = []
for cmdarg in appparams['client']['cmd']:
cmdlist.append(cmdarg.format(**connectparams))
app_process = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# stdout, stderr = app_process.communicate()
# if stderr is not "":
# return "connected with cmd {} but got error {}".format(cmdlist,stderr)
elif 'redir' in appparams['client'] and appparams['client']['redir'] is not None:
template_response = Response()
template_response.set_cookie(key='twsproxyauth', value=connectparams['authtok'])
twsproxy = 'http://localhost:4000/'
response = make_response(redirect(twsproxy+appparams['client']['redir'].
format(**connectparams)))
response.set_cookie('twsproxyauth', connectparams['authtok'])
return response
return "Connecting with cmd {}".format(cmdlist)
api.add_resource(TunnelstatEP, '/tunnelstat/<string:authtok>')
api.add_resource(GetCert, '/getcert')
api.add_resource(Stat, '/stat')
api.add_resource(JobCancel, '/cancel/<int:jobid>')
api.add_resource(JobSubmit, '/submit')
api.add_resource(JobConnect, '/connect/<int:jobid>/<string:batchhost>')
api.add_resource(AppSetup, '/appsetup')
# api.add_resource(SessionTest,'/sesstest')
api.add_resource(StartAgent,'/startagent')
api.add_resource(AddKey,'/addkey')