· 5 years ago · Nov 18, 2020, 06:52 AM
1#!/usr/local/bin/python
2# Created by Chunwang, Desmond, James, Te Ye, Tran Ly Vu
3# 15 June 2016
4# 22 Nov 2016
5"""
6Python Web Client to invoke the following functions from a Python Web Service:
70. Create users via apply project script?
81. Create users via join project script
92. Apply for new project after login
103. Join existing project after login
114. Approve Join Request
125. Create experiment
136. Start experiment
14
15Later TODO:
161. Remove users and projects
17"""
18from ConfigParser import SafeConfigParser
19import MySQLdb
20import generateDeterUserId
21import json
22import web
23import time
24import sys
25import os
26import subprocess
27import logging
28import base64
29import datetime
30import re
31import urllib
32import string
33import random
34
35ADMIN = 'ncl'
36EXPERIMENT_NOT_FOUND = 'no experiment found'
37CONFIG_FILE = "config.ini"
38CONFIG_DETER_URI_TITLE = "DETER_URI"
39# need time delay to allow subprocess to finish processing
40# in seconds
41TIME_DELAY = 2
42EXP_STATUS_DELAY = 30
43
44# to encode characters not specify in urllib.quote
45SPECIAL_CHARMAP_URLLIB = {'_': '%5F', '.': '%2E', '-': '%2D', '/': '%2F'}
46
47urls = (
48 '/login', 'login_users',
49 '/joinProjectNewUsers', 'join_project_new_users',
50 '/applyProjectNewUsers', 'apply_project_new_users',
51 '/joinProject', 'join_project_existing_user',
52 '/applyProject', 'apply_project_existing_user',
53 '/changePassword', 'change_password',
54 '/createExperiment', 'create_experiment',
55 '/getRunningExperimentsCount', 'get_running_experiments_count',
56 '/getSavedImages', 'get_saved_images',
57 '/getCveImages', 'get_cve_images',
58 '/getLoggedInUsersCount', 'get_logged_in_users',
59 '/modifyNSFile', 'modify_ns_file',
60 '/modifyExperimentSettings', 'modify_experiment_settings',
61 '/saveImage', 'save_image',
62 '/deleteImage', 'delete_image',
63 '/startExperiment', 'start_experiment',
64 '/stopExperiment', 'stop_experiment',
65 '/deleteExperiment', 'delete_experiment',
66 '/approveJoinRequest', 'approve_join_request',
67 '/rejectJoinRequest', 'reject_join_request',
68 '/approveProject', 'approve_project',
69 '/rejectProject', 'reject_project',
70 '/getExpDetails', 'get_exp_details',
71 '/getExpStatus', 'get_exp_status2',
72 '/getFreeNodes', 'get_free_nodes',
73 '/getNodesStatus', 'get_nodes_status',
74 '/getTotalNodes', 'get_total_nodes',
75 '/getTopoThumbnail', 'get_topo_thumbnail',
76 # '/getExpReport', 'get_exp_report_with_nodes',
77 '/resetPassword', 'reset_password',
78 # '/getUsageStatistics', 'get_usage_statistics',
79 '/removeUserFromTeam', 'remove_user_from_team',
80 '/getExperimentsByTeam', 'get_experiments_by_team',
81 '/getExperiment', 'get_experiment',
82 '/getPublicKeys', 'get_pub_keys',
83 '/addPublicKey', 'add_pub_key',
84 '/deletePublicKey', 'delete_pub_key',
85 '/getReservation', 'get_reservation',
86 '/releaseNodes', 'release_nodes',
87 '/reserveNodes', 'reserve_nodes',
88 '/getTeamExptStats', 'show_stats_by_project',
89 '/addStudentsByEmail', 'add_students_by_email',
90 '/changePasswordStudent', 'change_password_student',
91 '/getProjectsWithReservation', 'show_projects_with_reservation',
92 '/getNodeList', 'get_node_list'
93)
94
95app = web.application(urls, globals())
96
97
98def escapeSpecialChars(str):
99 global SPECIAL_CHARMAP_URLLIB
100 str_list = [SPECIAL_CHARMAP_URLLIB.get(c, c) for c in str]
101 return ''.join(str_list)
102
103
104# escape certain bash special characters with backslash
105# used for curl -F parameters
106# Reference:
107# [1] 'Quoting' https://www.freebsd.org/cgi/man.cgi?query=bash&apropos=0&sektion=1&manpath=FreeBSD+9.0-RELEASE+and+Ports&arch=default&format=html
108# [2] https://stackoverflow.com/questions/4202538/python-escape-regex-special-characters
109def escapeBashArguments(str):
110 return re.sub(r'([\$\"\`\\])', r'\\\1', str)
111
112
113# escape arguments in single quotes and adds a backslash to single quotes for used in command line arguments
114# e.g. apple' = 'apple\''
115# http://www.php2python.com/wiki/function.escapeshellarg/
116def escapeShellArguments(str):
117 return "\\'".join("'" + p + "'" for p in str.split("'"))
118
119
120# encode strings that will be used for arguments in curl commands
121# special characters are converted to their hex representations with a percentage-sign
122# e.g. apple@ = apple"%"40
123# used for curl -d parameters
124def escapeUrl(str):
125 # percent-encode the url
126 urlencoded = urllib.quote(str)
127
128 # urlencode does not encode '_.-/'
129 urlencoded = escapeSpecialChars(urlencoded)
130
131 return urlencoded.replace("%", "\"%\"")
132
133
134###########################################################################################
135################################### RESET PASSWORD #######################################
136###########################################################################################
137class reset_password:
138 def POST(self):
139 data = web.data()
140 parsed_json = json.loads(data)
141 uid = parsed_json['uid']
142 password = escapeShellArguments(parsed_json['password'])
143
144 logging.info('Resetting password for user %s', uid)
145
146 # use openssl to generate hash string for the password
147 cmd1 = "openssl passwd -1 " + password
148 pswd_hash = subprocess.check_output(cmd1, shell=True)
149 if pswd_hash.startswith('$1$') == 'False':
150 logging.warning('Failed to generate password hash: %s', pswd_hash)
151 return '{"msg": "failed to generate password hash"}'
152
153 # user deter cmd 'tbacct' to change password
154 cmd2 = '/usr/testbed/sbin/tbacct passwd ' + uid + ' \'' + pswd_hash.rstrip() + '\''
155 ret_val = subprocess.call(cmd2, shell=True)
156 if ret_val != 0:
157 logging.warning("Failed to exec cmd: %s", cmd2)
158 return '{"msg": "failed to execute tbacct command"}'
159
160 # update tbdb.pswd_expires to 1 year later
161 pswd_expires = datetime.datetime.strftime(datetime.datetime.now() + datetime.timedelta(365), '%Y-%m-%d')
162 cmd3 = 'mysql -e \"update tbdb.users set pswd_expires=\'' + pswd_expires + '\' where uid=\'' + uid + '\'\"'
163 db_ret = subprocess.call(cmd3, shell=True)
164 if db_ret != 0:
165 logging.warning('Failed to update db: %s', cmd3)
166 return '{"msg": "failed to update db"}'
167
168 logging.info("Password was reset for user %s", uid)
169 return '{"msg": "password was reset"}'
170
171
172###########################################################################################
173################################### USAGE STATISTICS #######################################
174###########################################################################################
175# class get_usage_statistics:
176# def POST(self):
177# data = web.data()
178# parsed_json = json.loads(data)
179# pid = parsed_json['pid']
180# start = parsed_json['start']
181# end = parsed_json['end']
182#
183# logging.info('Getting usage statistics for project %s, start %s, end %s', pid, start, end)
184#
185# # deter_uri = getDeterURI()
186# # cmd = 'curl --silent -k -d \"' + 'range=' + start + '-' + end + '&rtype=' + pid + '&Get=Get\" '+ deter_uri + 'measure_usage.php'
187# #
188# # out = subprocess.check_output(cmd, shell=True)
189# #
190# # if 'failed' in out:
191# # logging.warning('Failed to get usage statistics for %s, start %s, end %s; return \'?\'', pid, start, end)
192# # return '?'
193#
194# begin = int(time.mktime(time.strptime(start + " 00:00:00", "%m/%d/%y %H:%M:%S")))
195# end = int(time.mktime(time.strptime(end + " 23:59:59", "%m/%d/%y %H:%M:%S")))
196# out = subprocess.check_output(["perl", "calcusage.pl", str(begin), str(end), pid])
197#
198# usage = extract_usage_info(out, pid)
199# logging.info('Return %s', usage)
200# return usage
201
202#
203# def extract_usage_info(str, pid):
204# pattern = '<TR><TD>Project ' + pid.lower() + ' </TD><TD>(\d+)</TD></TR>'
205# m = re.search(pattern, str)
206# if m:
207# return m.group(1)
208# return '?'
209
210
211###########################################################################################
212################################### CHANGE PASSWORD #######################################
213###########################################################################################
214class change_password:
215 def POST(self):
216 data = web.data()
217 parsed_json = json.loads(data)
218 credential_dict = create_credential_dict(parsed_json)
219 uid = credential_dict['uid']
220 password = credential_dict['password1']
221 deter_uri = getDeterURI()
222 logging.info('Changing password for %s', uid)
223 cookie_file = uid + '.txt'
224
225 password_safe = urllib.quote_plus(password)
226
227 cmd_line = "curl -L -s -k -b " + cookie_file + " -d \"formfields[password1]=" + password_safe + \
228 "&formfields[password2]=" + password_safe + \
229 "&submit=Submit" + "\" " + \
230 deter_uri + "moduserinfo.php?user=" + uid + " -v"
231
232 out = subprocess.check_output(cmd_line, shell=True)
233
234 if 'Done!' in out:
235 logging.info('Password changed for %s', uid)
236 login(uid, password)
237 return '{"msg":"password change success"}'
238 else:
239 logging.warning('Change password for %s failed: %s', uid, out)
240 return '{"msg":"password change fail"}'
241
242
243#
244# def get_pwd(uid):
245# # get the password hash from the database
246# cmd = "mysql -N -e \"select usr_pswd from tbdb.users where uid='" + uid + "'\""
247#
248# # print cmd
249# q = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
250# out, err = q.communicate()
251#
252# output_list = out.split('\n')
253#
254# # mysql statement returns two parameters with a newline
255# if len(output_list) < 1:
256# return 'fail'
257#
258# return output_list[0]
259
260
261class login_users:
262 def POST(self):
263 data = web.data()
264 parsed_json = json.loads(data)
265 return login(parsed_json['uid'], parsed_json['password'])
266
267
268class join_project_existing_user:
269 def POST(self):
270 data = web.data()
271 parsed_json = json.loads(data)
272
273 uid = parsed_json['uid']
274 pid = parsed_json['pid']
275
276 deter_uri = getDeterURI()
277
278 logging.info('Join project existing user: uid %s, pid %s', uid, pid)
279
280 # calling the deterlab script to join project as existing users
281 # use curl -L to follow redirects
282 cookie_file = uid + '.txt'
283 cmd = "curl -L --silent -k -b " + cookie_file + " -d \"submit=Submit" + \
284 "&formfields[pid]=" + pid + "\" " \
285 + deter_uri + "joinproject.php -v"
286
287 out = subprocess.check_output(cmd, shell=True)
288
289 if "The project leader has been notified of your application" in out:
290 logging.info('Join project existing user: ok')
291 return '{"msg":"join project existing user ok"}'
292 elif "Invalid Project Name" in out:
293 logging.warning('Join project existing user: error, project %s does not exist', pid)
294 return '{"msg":"Team was not found on DeterLab"}'
295 elif "You are already a member" in out:
296 logging.warning(
297 'Join project existing user: error, user %s is already a member of project %s', uid, pid)
298 return '{"msg":"You are already a member of the team on DeterLab"}'
299 elif "The project you as seeking to join is a class." in out:
300 logging.warning('Join project existing user: error, project %s is a class', pid)
301 return '{"msg":"Team is a teaching class, please contact the team leader for an account"}'
302 else:
303 logging.warning('Join project existing user: unknown error %s', out)
304 return '{"msg":"Unknown error, please contact NCL Support"}'
305
306
307class apply_project_new_users:
308 def POST(self):
309 data = web.data()
310 parsed_json = json.loads(data)
311
312 # json key must match that defined under RegistrationService.java
313 logging.info("<apply_project_new_users> Parsed JSON is: {}".format(parsed_json))
314 user_info_dict = create_user_info_dict(parsed_json)
315 logging.info("<apply_project_new_users> User info dictionary is: {}".format(user_info_dict))
316 proj_w_user_info_dict = add_proj_info_dict(parsed_json, user_info_dict)
317 logging.info("<apply_project_new_users> proj_w_user_info_dict is : {}".format(proj_w_user_info_dict))
318
319 # call a python module to generate a unique deterlab userid
320 uid = generateDeterUserId.generate(proj_w_user_info_dict['first_name'], proj_w_user_info_dict['last_name'])
321
322 # print uid
323 logging.info('apply new project - newly generated deter uid: %s', uid)
324
325 # apply project (create user account in tbdb)
326 result = create_project(uid, proj_w_user_info_dict)
327 if result == 'good':
328 return verify_user(uid)
329 elif result == 'email address in use':
330 return '{"msg":"Email address already exists, please use a different one"}'
331 elif result == 'invalid password':
332 return '{"msg":"Invalid password, please use stronger password"}'
333 elif result == 'team name is already in use':
334 return '{"msg":"Team name already exists, please use a different one"}'
335 else:
336 return '{"msg":"Unknown error, please contact NCL Support"}'
337
338
339class join_project_new_users:
340 def POST(self):
341 # web.header('Content-Type', 'application/json')
342 data = web.data()
343 parsed_json = json.loads(data)
344
345 # json key must match that defined under RegistrationService.java
346 user_info_dict = create_user_info_dict(parsed_json)
347
348 # call a python module to generate a unique deterlab userid
349 uid = generateDeterUserId.generate(user_info_dict['first_name'], user_info_dict['last_name'])
350
351 # print uid
352 logging.info('join existing project - newly generated deter uid: %s', uid)
353
354 # join project (create user account in tbdb)
355 result = join_project(uid, user_info_dict)
356 if result == 'good':
357 return verify_user(uid)
358 elif result == 'email address in use':
359 return '{"msg":"Email address already exists, please use a different one"}'
360 elif result == 'invalid password':
361 return '{"msg":"Invalid password, please use stronger password"}'
362 elif result == 'project not found':
363 return '{"msg":"Team was not found on DeterLab"}'
364 elif result == "team is a class":
365 return '{"msg":"Team is a teaching class, please contact the team leader for an account"}'
366 else:
367 return '{"msg":"Unknown error, please contact NCL Support"}'
368
369
370def add_proj_info_dict(parsed_json, user_info_dict):
371 try:
372 user_info_dict['proj_name'] = parsed_json['projName']
373 user_info_dict['proj_goals'] = parsed_json['projGoals']
374 user_info_dict['pid'] = parsed_json['pid']
375 user_info_dict['proj_web'] = parsed_json['projWeb']
376 user_info_dict['proj_org'] = parsed_json['projOrg']
377 user_info_dict['proj_research_type'] = parsed_json['projResearchType']
378
379 if parsed_json['projPublic'] == 'PUBLIC':
380 user_info_dict['proj_public'] = 'Yes'
381 else:
382 user_info_dict['proj_public'] = 'No'
383 except Exception as e:
384 logging.info("<add_proj_info_dict> Exception happened inside <add_proj_info_dict> method: {}".format(e))
385
386 return user_info_dict
387
388
389# def join_project_existing_user( uid, pid):
390# deter_uri = getDeterURI()
391#
392# # calling the deterlab script to join project as existing users
393# # use curl -L to follow redirects
394# cookie_file = uid + '.txt'
395# cmd = "curl -L --silent -k -b " + cookie_file + " -d \"submit=Submit" + \
396# "&formfields[pid]=" + pid + "\" " \
397# + deter_uri + "joinproject.php -v"
398#
399# p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
400# out, err = p.communicate()
401#
402# logging.info('join project current users: %s', out)
403#
404# if "The project leader has been notified of your application" in out :
405# return '{"msg":"join project request for existing user is successful"}'
406# elif "Invalid Project Name" in out :
407# logging.info( 'Join project existing user: error, project %s does not exist', pid)
408# return '{"msg":"project was not found on DeterLab"}'
409# else :
410# return '{"msg":"unknown reason in join project request for existing user"}'
411
412def join_project(uid, my_dict):
413 pid = my_dict['pid']
414
415 logging.info('Joining project %s, new user %s ', pid, uid)
416
417 deter_uri = getDeterURI()
418 # password cannot contain double-quote '"'
419 password = my_dict['password']
420
421 # need to parse in the form fields
422 # uid, email address must be unique
423 cmd = "curl -L -s -k -X POST" + \
424 " -F \"formfields[pid]=" + pid + "\"" + \
425 " -F \"formfields[gid]=\"" + \
426 " -F \"formfields[usr_name]=" + my_dict['last_name'] + " " + my_dict['first_name'] + "\"" + \
427 " -F \"formfields[usr_email]=" + my_dict['email'] + "\"" + \
428 " -F \"formfields[usr_phone]=" + my_dict['phone'] + "\"" + \
429 " -F \"formfields[usr_title]=" + my_dict['job_title'] + "\"" + \
430 " -F \"formfields[usr_affil]=" + my_dict['institution'] + "\"" + \
431 " -F \"formfields[usr_affil_abbrev]=" + my_dict['institution_abbrev'] + "\"" + \
432 " -F \"formfields[usr_addr]=" + my_dict['address1'] + "\"" + \
433 " -F \"formfields[usr_addr2]=" + my_dict['address2'] + "\"" + \
434 " -F \"formfields[usr_URL]=http://www.aaa.com\"" + \
435 " -F \"formfields[usr_city]=" + my_dict['city'] + "\"" + \
436 " -F \"formfields[usr_state]=" + my_dict['region'] + "\"" + \
437 " -F \"formfields[usr_zip]=" + my_dict['zip_code'] + "\"" + \
438 " -F \"formfields[usr_country]=" + my_dict['country'] + "\"" + \
439 " -F \"formfields[uid]=" + uid + "\"" + \
440 " -F \"formfields[password1]=" + password + "\"" + \
441 " -F \"formfields[password2]=" + password + "\"" + \
442 " -F \"submit=Submit\"" + \
443 " '" + deter_uri + "joinproject.php -v'"
444
445 out = subprocess.check_output(cmd, shell=True)
446
447 if "As a pending user of the Testbed you will receive a key via email" in out:
448 logging.info('Join project new user: OK, user %s created in project %s', uid, pid)
449 return 'good'
450 elif "Already in use. <b>Did you forget to login?" in out:
451 logging.info('Join project new user: error, email address already in use')
452 return 'email address in use'
453 elif "Invalid Password" in out or "Invalid DETER Password" in out:
454 logging.info('Join project new user: error, invalid password')
455 return 'invalid password'
456 elif "Invalid Project Name" in out:
457 logging.info('Join project new user: error, invalid project name')
458 return 'project not found'
459 elif "The project you as seeking to join is a class." in out:
460 logging.warning('Join project new user: error, project %s is a class', pid)
461 return "team is a class"
462 else:
463 logging.info('Join project new user: unknown error %s', out)
464 return "unknown error"
465
466
467def create_project(uid, proj_w_user_info_dict):
468 # a new user registers and applies to create a new project
469 logging.info("<create_project> uid: {} proj_w_user_info_dict: {}".format(uid, proj_w_user_info_dict))
470 pid = proj_w_user_info_dict['pid']
471
472 logging.info('<create_project>Creating new project %s and user %s on Deter', pid, uid)
473
474 deter_uri = getDeterURI()
475 logging.info("<create_project>Deter_URI obtained is : {}".format(deter_uri))
476
477 # password cannot contain double-quote '"'
478 password = proj_w_user_info_dict['password']
479 logging.info("<create_project> Password is: {}".format(password))
480 logging.info("<create_project>Before forming command the proj_w_user_info_dict is: {}".format(proj_w_user_info_dict))
481 try:
482 cmd = "curl -L -s -k -X POST" + \
483 " -F \"formfields[usr_name]=" + proj_w_user_info_dict['last_name'] + " " + proj_w_user_info_dict[
484 'first_name'] + "\"" + \
485 " -F \"formfields[usr_email]=" + proj_w_user_info_dict['email'] + "\"" + \
486 " -F \"formfields[usr_phone]=" + proj_w_user_info_dict['phone'] + "\"" + \
487 " -F \"formfields[usr_title]=" + proj_w_user_info_dict['job_title'] + "\"" + \
488 " -F \"formfields[usr_affil]=" + proj_w_user_info_dict['institution'] + "\"" + \
489 " -F \"formfields[usr_affil_abbrev]=" + proj_w_user_info_dict['institution_abbrev'] + "\"" + \
490 " -F \"formfields[usr_addr]=" + proj_w_user_info_dict['address1'] + "\"" + \
491 " -F \"formfields[usr_addr2]=" + proj_w_user_info_dict['address2'] + "\"" + \
492 " -F \"formfields[usr_URL]=http://www.aaa.com\"" + \
493 " -F \"formfields[usr_city]=" + proj_w_user_info_dict['city'] + "\"" + \
494 " -F \"formfields[usr_state]=" + proj_w_user_info_dict['region'] + "\"" + \
495 " -F \"formfields[usr_zip]=" + proj_w_user_info_dict['zip_code'] + "\"" + \
496 " -F \"formfields[usr_country]=" + proj_w_user_info_dict['country'] + "\"" + \
497 " -F \"formfields[uid]=" + uid + "\"" + \
498 " -F \"formfields[pid]=" + pid + "\"" + \
499 " -F \"formfields[gid]=\"" + \
500 " -F \"formfields[proj_name]=" + proj_w_user_info_dict['proj_name'] + "\"" + \
501 " -F \"formfields[proj_why]=" + proj_w_user_info_dict['proj_goals'] + "\"" + \
502 " -F \"formfields[proj_URL]=" + proj_w_user_info_dict['proj_web'] + "\"" + \
503 " -F \"formfields[proj_org]=" + proj_w_user_info_dict['proj_org'] + "\"" + \
504 " -F \"formfields[proj_research_type]=" + proj_w_user_info_dict['proj_research_type'] + "\"" + \
505 " -F \"formfields[proj_funders]=Other\"" + \
506 " -F \"formfields[proj_public]=" + proj_w_user_info_dict['proj_public'] + "\"" + \
507 " -F \"formfields[password1]=" + password + "\"" + \
508 " -F \"formfields[password2]=" + password + "\"" + \
509 " -F \"submit=Submit\"" + \
510 " '" + deter_uri + "newproject.php -v'"
511 except Exception as e:
512 logging.error("<create_project> Something wrong happened while constructing the command: {}".format(e))
513 logging.info("<create_project> Command formed is: {}".format(cmd))
514 out = subprocess.check_output(cmd, shell=True)
515
516 if "Your project request has been successfully queued" in out:
517 logging.info('Create new project new user: project %s and user %s created', pid, uid)
518 return "good"
519 elif "Already in use. <b>Did you forget to login?" in out:
520 logging.warning('Create new project new user: error, email address already in use')
521 return "email address in use"
522 elif "Invalid Password" in out or "Invalid DETER Password" in out:
523 logging.warning('Create new project new user: error, invalid password')
524 return "invalid password"
525 elif "Already in use. Select another" in out:
526 logging.warning('Create new project new user: error, project name %s already in use', pid)
527 return "team name is already in use"
528 else:
529 logging.warning('Create new project new user: unknown error %s', out)
530 return "unknown error"
531
532
533def login(uid, password):
534 # create a cookie file unique to each users
535 deter_uri = getDeterURI()
536
537 password_safe = urllib.quote_plus(password)
538 cookie_file = uid + '.txt'
539
540 login_url = "curl -s -k -c " + cookie_file + " -d " + \
541 "\"uid=" + uid + \
542 "&password=" + password_safe + \
543 "&login=" + uid + "\"" + \
544 " '" + deter_uri + "login.php'"
545
546 out = subprocess.check_output(login_url, shell=True)
547
548 if "Login attempt failed" in out:
549 logging.warning('User %s login failed: %s', uid, out)
550 return '{"msg":"user is logged in error"}'
551 elif "Your account has been frozen" in out:
552 logging.warning('User %s login failed: Account frozen due to earlier login attempt failures', uid)
553 return '{"msg":"user account is frozen"}'
554 else:
555 logging.info('User %s logged in, cookie file %s created', uid, cookie_file)
556 return '{"msg":"user is logged in"}'
557
558
559# automatically verify a new user in DeterLab
560def verify_user(uid):
561 logging.info('Trying to verify user %s', uid)
562
563 user_status_before = get_user_status(uid)
564
565 if user_status_before == 'newuser':
566 # implies DeterLab has created the user account
567
568 # change status from newuser to unapproved
569 cmd = "mysql -e \"update tbdb.users set status='unapproved' where uid='" + uid + "'\""
570 subprocess.call(cmd, shell=True)
571
572 user_status_after = get_user_status(uid)
573
574 if user_status_after == 'unapproved':
575 logging.info('User %s was verified', uid)
576 return '{"msg": "user is created", "uid":"' + uid + '"}'
577 else:
578 logging.warning('Verify user %s failed: please check the adapter log', uid)
579 return '{"msg": "user cannot be verified on DeterLab"}'
580 else:
581 logging.warning('Verify user %s failed: user not found in table tbdb.users', uid)
582 return '{"msg": "user was not created on DeterLab"}'
583
584
585def get_user_status(uid):
586 cmd = "mysql -N -e \"select status from tbdb.users where uid='" + uid + "'\""
587 out = subprocess.check_output(cmd, shell=True)
588
589 result_list = out.split('\n')
590
591 if result_list[0] == '':
592 return "no such user"
593 else:
594 return result_list[0]
595
596
597def create_user_info_dict(parsed_json):
598 my_dict = {}
599 try:
600 my_dict['first_name'] = parsed_json['firstName']
601 my_dict['last_name'] = parsed_json['lastName']
602 my_dict['job_title'] = parsed_json['jobTitle']
603 my_dict['password'] = parsed_json['password']
604 my_dict['email'] = parsed_json['email']
605 my_dict['phone'] = parsed_json['phone']
606 my_dict['institution'] = parsed_json['institution']
607 my_dict['institution_abbrev'] = parsed_json['institutionAbbreviation']
608 my_dict['institution_web'] = parsed_json['institutionWeb']
609 my_dict['address1'] = parsed_json['address1']
610 my_dict['address2'] = parsed_json['address2']
611 my_dict['country'] = parsed_json['country']
612 my_dict['region'] = parsed_json['region']
613 my_dict['city'] = parsed_json['city']
614 my_dict['zip_code'] = parsed_json['zipCode']
615 my_dict['pid'] = parsed_json['pid']
616 except Exception as e:
617 logging.info("<create_user_info_dict>Exception happened inside create_user_info_dict method {}".format(e))
618 return my_dict
619
620
621def create_user_info_dict2(parsed_json):
622 # for logged on users
623 my_dict = {}
624 my_dict['uid'] = parsed_json['uid']
625 my_dict['pid'] = parsed_json['pid']
626 return my_dict
627
628
629def create_credential_dict(parsed_json):
630 # for modifying credentials
631 my_dict = {}
632 my_dict['uid'] = parsed_json['uid']
633 my_dict['password1'] = parsed_json['password1']
634 my_dict['password2'] = parsed_json['password2']
635 return my_dict
636
637
638def getDeterURI():
639 parser = SafeConfigParser()
640 parser.read(CONFIG_FILE)
641 return parser.get('DETER_URI', 'uri')
642
643
644def getDBURI():
645 parser = SafeConfigParser()
646 parser.read(CONFIG_FILE)
647 return parser.get('DB_URI', 'db_uri')
648
649
650def getDBName():
651 parser = SafeConfigParser()
652 parser.read(CONFIG_FILE)
653 return parser.get('DB_NAME', 'db_name')
654
655
656def getDomain():
657 parser = SafeConfigParser()
658 parser.read(CONFIG_FILE)
659 return parser.get('DOMAIN', 'domain')
660
661
662###########################################################################################
663# create experiment
664
665class create_experiment:
666 def POST(self):
667 data = web.data()
668 parsed_json = json.loads(data)
669 experiment_dict = create_experiment_dict(parsed_json)
670 pid = experiment_dict['team_name']
671 eid = experiment_dict['exp_name']
672 logging.info('Creating experiment %s/%s', pid, eid)
673
674 deter_uri = getDeterURI()
675 cookie_file = experiment_dict['deter_login'] + ".txt"
676
677 autoswap_timeout = experiment_dict['max_duration']
678
679 if experiment_dict['max_duration'] != "0":
680 # experiment set to swap out after some hours
681 autoswap_enable = "1"
682 else:
683 # experiment will never auto swapout
684 # still have to set the timeout value for the webpage to process without errors
685 autoswap_enable = "0"
686 autoswap_timeout = "16"
687
688 cmd_line = "curl --silent -b " + cookie_file + " -k -d \"formfields[exp_pid]=" + experiment_dict['team_name'] + \
689 "&formfields[exp_id]=" + experiment_dict['exp_name'] + \
690 "&formfields[exp_description]=" + experiment_dict['description'] + \
691 "&formfields[exp_nsfile_contents]=" + urllib.quote(experiment_dict['ns_file_content'], safe='') + \
692 "&formfields[exp_noswap_reason]=reason" + \
693 "&formfields[exp_noidleswap_reason]=reason" + \
694 "&formfields[exp_idleswap_timeout]=4" + \
695 "&formfields[exp_autoswap_timeout]=" + autoswap_timeout + \
696 "&formfields[exp_autoswap]=" + autoswap_enable + \
697 "&formfields[exp_swappable]=1" + \
698 "&formfields[exp_swapin]=0" + \
699 "&beginexp=Submit" + "\" " + \
700 deter_uri + "beginexp_html.php -v"
701
702 out = subprocess.check_output(cmd_line, shell=True)
703
704 if "Working ..." in out:
705 # DeterLab is prerunning the new experiment
706 counter = 0
707 while counter <= 100:
708 time.sleep(2)
709 exp_status = get_exp_status(eid, pid)
710 if exp_status == 'swapped':
711 logging.info('Create experiment %s/%s succeeded', pid, eid)
712 return '{"msg":"experiment create success"}'
713 elif exp_status == EXPERIMENT_NOT_FOUND:
714 # possible to reach here if ns file still has error which result in no experiment being created
715 logging.warning('Create experiment %s/%s failed: pre-run failed', pid, eid)
716 return '{"msg":"experiment create fail ns file error"}'
717 counter += 1
718 # few minutes have passed and experiment is still not created on deter?
719 logging.warning('Create experiment %s/%s failed: timed out', pid, eid)
720 return '{"msg":"experiment create fail"}'
721 elif "parse-ns: Parsing failed" in out:
722 logging.warning('Create experiment %s/%s failed: Parsing failed', pid, eid)
723 return '{"msg":"experiment create fail ns file error"}'
724 elif "Already in use" in out:
725 logging.warning('Create experiment %s/%s failed: exp name already exists', pid, eid)
726 return '{"msg":"experiment create fail exp name already in use"}'
727 elif "Your account has not been approved yet" in out:
728 logging.warning('Create experiment %s/%s failed: user not approved yet', pid, eid)
729 return '{"msg":"experiment create fail"}'
730 else:
731 logging.warning('Create experiment %s/%s failed: unknown error %s', pid, eid, out)
732 return '{"msg":"experiment create fail"}'
733
734
735def create_experiment_dict(parsed_json):
736 my_dict = {}
737 my_dict['id'] = parsed_json['id']
738 my_dict['user_id'] = parsed_json['userId']
739 my_dict['team_id'] = parsed_json['teamId']
740 my_dict['team_name'] = parsed_json['teamName']
741 my_dict['exp_name'] = parsed_json['name']
742 my_dict['description'] = parsed_json['description']
743 my_dict['ns_file'] = parsed_json['nsFile']
744 my_dict['ns_file_content'] = parsed_json['nsFileContent']
745 my_dict['idle_swap'] = parsed_json['idleSwap']
746 my_dict['max_duration'] = parsed_json['maxDuration']
747 my_dict['deter_login'] = parsed_json['deterLogin']
748 my_dict['user_server_uri'] = parsed_json['userServerUri']
749 return my_dict
750
751
752# def create_ns_file(file_path, file_content):
753# file = open(file_path, 'w')
754# file.write(file_content)
755# file.close
756
757
758###########################################################################################
759# start experiment
760
761class start_experiment:
762 def POST(self):
763 data = web.data()
764 parsed_json = json.loads(data)
765 http_command = parsed_json['httpCommand']
766 cookie_file = parsed_json['deterLogin'] + ".txt"
767 pid = parsed_json['pid']
768 eid = parsed_json['eid']
769 logging.info('Starting experiment %s/%s', pid, eid)
770
771 cmd_line = craft_http_command(http_command, cookie_file)
772
773 #process = subprocess.call(cmd_line, shell=True, stdout=subprocess.PIPE)
774 out = subprocess.check_output(cmd_line, shell=True)
775 result = {'msg': 'experiment start fail', 'status': 'swapped', 'report': ''}
776 if 'Your experiment has started its swapin' in out:
777 logging.info('Experiment %s/%s has started its swapin', pid, eid)
778 result['msg'] = 'experiment start success'
779 result['status'] = 'activating'
780 elif 'Experiment swapping is disabled' in out:
781 logging.warning('Start experiment %s/%s failed: swapping is disabled', pid, eid)
782 elif 'You do not have permission for' in out:
783 logging.warning('Start experiment %s/%s failed: permission denied', pid, eid)
784 elif 'Cannot proceed' in out:
785 logging.warning('Start experiment %s/%s failed: the experiment is locked down', pid, eid)
786 else:
787 logging.warning('Start experiment %s/%s failed: %s', pid, eid, out)
788
789 return result
790
791
792###########################################################################################
793# stop experiment
794
795class stop_experiment:
796 def POST(self):
797 data = web.data()
798 parsed_json = json.loads(data)
799
800 http_command = parsed_json['httpCommand']
801 cookie_file = parsed_json['deterLogin'] + ".txt"
802 pid = parsed_json['pid']
803 eid = parsed_json['eid']
804 logging.info('Stopping experiment %s/%s', pid, eid)
805
806 cmd_line = craft_http_command(http_command, cookie_file)
807
808 # process = subprocess.call(cmd_line, shell=True, stdout=subprocess.PIPE)
809 #
810 # logging.info('stop experiment: %s', process)
811 #
812 # # to fix service ObjectOptimisticLockingFailureException
813 # # treat stop experiment as always success since the status is updated upon page refresh
814 #
815 # result = '{ "msg": "experiment stop success", "status" : "swapped", "report" : "" }'
816 # logging.info('result of stop experiment: %s', result)
817 # return result
818 out = subprocess.check_output(cmd_line, shell=True)
819 result = {'msg': 'experiment stop fail', 'status': 'active', 'report': ''}
820 if 'Your experiment has started its swapout' in out:
821 logging.info('Experiment %s/%s has started its swapout', pid, eid)
822 result['msg'] = 'experiment stop success'
823 result['status'] = 'swapped'
824 elif 'Experiment swapping is disabled' in out:
825 logging.warning('Stop experiment %s/%s failed: swapping is disabled', pid, eid)
826 elif 'You do not have permission for' in out:
827 logging.warning('Stop experiment %s/%s failed: permission denied', pid, eid)
828 elif 'Cannot proceed' in out:
829 logging.warning('Stop experiment %s/%s failed: the experiment is locked down', pid, eid)
830 else:
831 logging.warning('Stop experiment %s/%s failed: %s', pid, eid, out)
832
833 return result
834
835
836# get experiment activity log for experiment profile page
837# returns a dictionary in the format
838# { 'msg' : 'success/fail', 'activity_log' : 'activity_log_contents/No activity log found.' }
839def get_activity_log(pid, eid):
840 # get swapexp log
841 result = get_experiment_log_file(pid, eid, 'swapexp.log', 'activity log')
842
843 if result['msg'] == 'get activity log fail':
844 # no swapexp log
845 # experiment may have just been created
846 # find startexp log
847 result = get_experiment_log_file(pid, eid, 'startexp.log', 'activity log')
848
849 return result
850
851
852# get experiment log files under the directory from /usr/testbed/expwork/ for experiment profile page
853# filename: nsfile.ns, swapexp.log, startexp.log
854# log_type: 'ns file', 'activity log'
855# returns a dictionary in the format
856# { 'msg' : 'success/fail', 'log_type' : 'file_contents/No file found.' }
857def get_experiment_log_file(pid, eid, filename, log_type):
858 logging.info('Retrieving %s for %s/%s', log_type, pid, eid)
859
860 filepath = '/usr/testbed/expwork/' + pid + '/' + eid + '/' + filename
861
862 file_not_found = 'No ' + log_type + ' found.'
863 log_msg_success = 'Retrieve ' + log_type + ' success'
864 msg_success = 'get ' + log_type + ' success'
865 msg_fail = 'get ' + log_type + ' fail'
866
867 result = {}
868 # prepare the key for the result dictionary, e.g. ns<space>file becomes ns_file
869 result_key = log_type.replace(" ", "_")
870
871 if os.path.exists(filepath):
872 try:
873 with open(filepath) as myFile:
874 logging.info(log_msg_success)
875 result['msg'] = msg_success
876 result[result_key] = myFile.read()
877 myFile.close()
878 except IOError as e:
879 result['msg'] = msg_fail
880 result[result_key] = file_not_found
881 else:
882 result['msg'] = msg_fail
883 result[result_key] = file_not_found
884
885 return result
886
887
888# get realization details for experiment profile page
889# returns a dictionary in the format
890# { 'msg' : 'success/fail', 'realization_details' : 'realization_details_contents/No experiment details found.' }
891def get_realization_details(pid, eid):
892 logging.info('Retrieving realization details for %s/%s', pid, eid)
893
894 result = {}
895
896 # invokes tbreport -b
897 # don't need the event details which is tbreport -v
898 exp_report = get_exp_report(pid, eid)
899
900 if exp_report != '':
901 logging.info('Retrieve realization details success')
902 result['msg'] = 'get realization details success'
903 result['realization_details'] = exp_report
904 else:
905 result['msg'] = 'get realization details fail'
906 result['realization_details'] = 'No experiment details found.'
907
908 return result
909
910
911###########################################################################################
912'''
913- retrieve experiment nsfile, realization details and nsfile
914- returns a JSON dump of a dictionary in the format:
915{
916 'ns_file' :
917 {
918 'msg' : 'success/fail',
919 'ns_file' : 'ns_file_contents'
920 },
921 'realization_details' :
922 {
923 'msg' : 'success/fail',
924 'realization_details' : 'realization_details_contents'
925 },
926 'activity_log' :
927 {
928 'msg' : 'success/fail',
929 'activity_log' : 'activity_log_contents'
930 }
931}
932- not to be confused with get_exp_report_with_nodes
933- this endpoint is for the experiment_profile page
934'''
935
936
937class get_exp_details:
938 def POST(self):
939 data = web.data()
940 parsed_json = json.loads(data)
941 pid = parsed_json['pid']
942 eid = parsed_json['eid']
943 logging.info('Retrieving experiment details for %s/%s', pid, eid)
944
945 result = {}
946 result['ns_file'] = get_experiment_log_file(pid, eid, 'nsfile.ns', 'ns file')
947 result['realization_details'] = get_realization_details(pid, eid)
948 result['activity_log'] = get_activity_log(pid, eid)
949
950 return json.dumps(result)
951
952
953###########################################################################################
954# get experiment status by eid, pid
955
956class get_exp_status2:
957 def POST(self):
958 data = web.data()
959 parsed_json = json.loads(data)
960
961 eid = parsed_json['eid']
962 pid = parsed_json['pid']
963
964 status = get_exp_status(eid, pid)
965
966 if status == 'active':
967 # exp_report = get_exp_report(pid, eid)
968 # exp_report = sanitize(exp_report)
969 exp_report = get_exp_nodes_info(pid, eid)
970 # logging.info('experiemnt status: %s', exp_report)
971 return "{\"status\" : \"" + status + "\", \"report\":" + exp_report + "}"
972
973 return "{ \"status\" : \"" + status + "\" }"
974
975
976'''
977returns a string
9781. no experiment found
9792. swapped / activating / active /swapping etc
980'''
981
982
983def get_exp_status(eid, pid):
984
985 logging.info('Getting experiment status for %s/%s', pid, eid)
986
987 cmd = "mysql -N -e \"select state from tbdb.experiments where eid='" + eid + "'" + " AND pid='" + pid + "'\""
988
989 out = subprocess.check_output(cmd, shell=True)
990
991 output_list = out.split('\n')
992
993 # if sql query returns empty set: the output_list is ['']
994 if output_list[0] == '':
995 return EXPERIMENT_NOT_FOUND
996
997 return output_list[0]
998
999
1000def get_exp_report(pid, eid):
1001 # returns a string
1002 # get the idx from the deter database
1003 cmd = "/usr/testbed/bin/tbreport -b " + pid + " " + eid
1004 out = subprocess.check_output(cmd, shell=True)
1005 return out
1006
1007
1008def sanitize(exp_report):
1009 rv = ''
1010 # truncate 'Experiment: xxxx' and 'State:....' and newline
1011 if "Experiment:" and "State:" in exp_report:
1012 exp_report_list = exp_report.split('\n')
1013 result_list = []
1014 # used to truncate lines after Virtual Lan/Link Info...
1015 # only keep the simple Qualified Name section
1016 flag = False
1017 for report_line in exp_report_list[2:]:
1018 if ("Virtual Lan" in report_line) or ("Physical Node Mapping" in report_line):
1019 flag = True
1020 break
1021 if flag is False:
1022 result_list.append(report_line)
1023 rv = '@'.join(result_list)
1024 return rv
1025
1026
1027###############################
1028# craft http command for start and stop experiment
1029
1030def craft_http_command(http_command, cookie_file):
1031 deter_uri = getDeterURI()
1032
1033 cmd_line = "curl --silent -X POST \"" + deter_uri + "swapexp.php" + http_command + "\"" \
1034 " -b " + cookie_file + " -k -d \"confirmed=Confirm\" -v"
1035
1036 return cmd_line
1037
1038
1039# TODO: use this when using deter wrapper
1040def get_swapexp_cmd(experiment_dict, swap_type):
1041 # TODO: get deter-user URL/IP
1042 # TODO: test this script
1043 # swap_type = IN/OUT
1044
1045 # script_wrapper.py --server=deter_user --login=logged_in_personnel swapexp pid eid in/out
1046 cmd_line = "python script_wrapper.py" + \
1047 " --server=" + experiment_dict['user_server_uri'] + \
1048 " --login=" + experiment_dict['deter_login'] + \
1049 " swapexp" + \
1050 " " + experiment_dict['team_name'] + \
1051 " " + experiment_dict['exp_name'] + \
1052 " " + swap_type
1053
1054 return cmd_line
1055
1056
1057###########################################################################################
1058# delete experiment
1059
1060class delete_experiment:
1061 def POST(self):
1062 data = web.data()
1063 parsed_json = json.loads(data)
1064 eid = parsed_json['experimentName']
1065 pid = parsed_json['teamName']
1066 cookie_file = parsed_json['deterLogin'] + ".txt"
1067 logging.info('Terminating experiment %s/%s', pid, eid)
1068
1069 # get the idx from the deter database
1070 cmd = "mysql -N -e \"select idx from tbdb.experiments where eid='" + eid + "'" + " AND pid ='" + pid + "'\""
1071 out = subprocess.check_output(cmd, shell=True)
1072 output_list = out.split('\n')
1073 # mysql statement returns two parameters with a newline
1074 if len(output_list) < 1:
1075 logging.warning('Experiment %s/%s not found, it has been terminated already?', pid, eid)
1076 result = {'status': 'no experiment found'}
1077 return result
1078
1079 idx = output_list[0]
1080 deter_uri = getDeterURI()
1081 cmd_line = "curl -s -X POST \"" + deter_uri + "endexp.php?experiment=" + idx + "\"" \
1082 " -b " + cookie_file + " -k -d \"confirmed=Confirm\" -v"
1083
1084 out = subprocess.check_output(cmd_line, shell=True)
1085 if 'Termination has started' in out:
1086 logging.info('Termination has started')
1087 # start to check until the experiment is removed from database
1088 counter = 0
1089 while counter < 100:
1090 exp_status = get_exp_status(eid, pid)
1091 if exp_status == EXPERIMENT_NOT_FOUND:
1092 logging.info('Experiment %s/%s terminated', pid, eid)
1093 result = {'status': 'no experiment found'}
1094 return result
1095 counter += 1
1096 time.sleep(2)
1097 # experiment is still not removed from database after the while loop
1098 logging.warning('Termination failed: timed out')
1099 result = {'status': 'termination timed out'}
1100 return result
1101 elif 'Experiment swapping is disabled' in out:
1102 logging.warning('Termination failed: swapping is disabled')
1103 result = {'status': 'swapping is disabled'}
1104 return result
1105 elif 'You do not have permission to end experiment' in out:
1106 logging.warning('Termination failed: permission denied')
1107 result = {'status': 'permission denied'}
1108 return result
1109 elif 'the experiment is locked down' in out:
1110 logging.warning('Termination failed: experiment is locked down')
1111 result = {'status': 'experiment is locked down'}
1112 return result
1113 else:
1114 logging.warning('Termination failed: %s', out)
1115 result = {'status': 'could not proceed'}
1116 return result
1117
1118
1119###########################################################################################
1120# approve join request
1121
1122class approve_join_request:
1123 def POST(self):
1124 data = web.data()
1125 parsed_json = json.loads(data)
1126 join_request_dict = create_join_request_dict(parsed_json)
1127 approver = join_request_dict['approver_uid']
1128 uid = join_request_dict['uid']
1129 pid = join_request_dict['pid']
1130 gid = join_request_dict['gid']
1131 logging.info('Approving %s request to join project %s/%s, approver %s', uid, pid, gid, approver)
1132
1133 deter_uri = getDeterURI()
1134 cookie_file = approver + '.txt'
1135
1136 # check if user is in group first
1137 member_status_before = get_group_membership_status(pid, uid)
1138
1139 if member_status_before == 'not found':
1140 logging.info('Join request for project %s for user %s NOT found', pid, uid)
1141 return '{"msg":"no join request found"}'
1142 elif member_status_before == 'local_root' or member_status_before == 'group_root' or member_status_before == 'project_root':
1143 logging.info('User %s is already a member in project %s', uid, pid)
1144 return '{"msg":"user is already an approved member in the project"}'
1145
1146 logging.info('Member status before: %s', member_status_before)
1147
1148 # only process join request for users not already a member
1149 # format
1150 # actions: U[uid]%24%24approval-[pid]%2Fgid=approve postpone, deny, nuke
1151 # trust level: U[uid]%24%24trust-[pid]%2Fgid=local_root grouproot, user
1152 cmd_line = "curl -L -s -k -b " + cookie_file + " -d \"U" + uid + "%24%24approval-" + \
1153 pid + "%2F" + gid + "=approve" + \
1154 "&U" + uid + "%24%24trust-" + pid + "%2F" + \
1155 gid + "=local_root" + \
1156 "&OK=Submit" + "\" " + \
1157 deter_uri + "approveuser.php -v"
1158
1159 out = subprocess.check_output(cmd_line, shell=True)
1160
1161 member_status_after = get_group_membership_status(pid, uid)
1162
1163 logging.info('Member status after: %s', member_status_after)
1164
1165 if member_status_after == 'local_root':
1166 logging.info('Join request for project %s for user %s approved', pid, uid)
1167 return '{"msg":"process join request OK"}'
1168 else:
1169 logging.warning('Approve join request for project %s for user %s failed: %s', pid, uid, out)
1170 return '{"msg":"process join request FAIL"}'
1171
1172
1173class reject_join_request:
1174 def POST(self):
1175 data = web.data()
1176 parsed_json = json.loads(data)
1177 join_request_dict = create_join_request_dict(parsed_json)
1178 approver = join_request_dict['approver_uid']
1179 uid = join_request_dict['uid']
1180 pid = join_request_dict['pid']
1181 gid = join_request_dict['gid']
1182 logging.info('Rejecting %s request to join project %s/%s, approver %s', uid, pid, gid, approver)
1183
1184 deter_uri = getDeterURI()
1185 cookie_file = approver + '.txt'
1186
1187 # check if user is in group first
1188 member_status_before = get_group_membership_status(pid, uid)
1189
1190 if member_status_before == 'not found':
1191 logging.info('Join request for project %s for user %s NOT found', pid, uid)
1192 return '{"msg":"no join request found"}'
1193 elif member_status_before == 'local_root' or member_status_before == 'group_root' or member_status_before == 'project_root':
1194 # useless to reject a member who is already approved
1195 logging.info('User %s is already a member in project %s', uid, pid)
1196 return '{"msg":"user is already an approved member in the project"}'
1197
1198 # format
1199 # actions: U[uid]%24%24approval-[pid]%2Fgid=approve postpone, deny, nuke
1200 # trust level: U[uid]%24%24trust-[pid]%2Fgid=local_root grouproot, user
1201 cmd_line = "curl -L -s -k -b " + cookie_file + " -d \"U" + uid + "%24%24approval-" + \
1202 pid + "%2F" + gid + "=nuke" + \
1203 "&U" + uid + "%24%24trust-" + pid + "%2F" + \
1204 gid + "=local_root" + \
1205 "&OK=Submit" + "\" " + \
1206 deter_uri + "approveuser.php > -v"
1207
1208 out = subprocess.check_output(cmd_line, shell=True)
1209
1210 member_status_after = get_group_membership_status(join_request_dict['pid'], join_request_dict['uid'])
1211 if member_status_after == 'not found':
1212 logging.info('Join request for project %s for user %s rejected', pid, uid)
1213 return '{"msg":"process join request OK"}'
1214 else:
1215 logging.warning('Reject join request for project %s for user %s failed: %s', pid, uid, out)
1216 return '{"msg":"process join request FAIL"}'
1217
1218
1219def create_join_request_dict(parsed_json):
1220 my_dict = {}
1221 my_dict['approver_uid'] = parsed_json['approverUid']
1222 my_dict['uid'] = parsed_json['uid']
1223 my_dict['pid'] = parsed_json['pid']
1224 my_dict['gid'] = parsed_json['gid']
1225 return my_dict
1226
1227
1228###########################################################################################
1229
1230###########################################################################################
1231# apply project for existing users
1232
1233class apply_project_existing_user:
1234 def POST(self):
1235 data = web.data()
1236 parsed_json = json.loads(data)
1237 logging.info("<apply_project_existing_user> Parsed json is : {}".format(parsed_json))
1238 new_project_dict = create_new_project_dict(parsed_json)
1239 logging.info("<apply_project_existing_user> New project dictionary is: {}".format(new_project_dict))
1240 deter_uri = getDeterURI()
1241 logging.info("<apply_project_existing_user> Deter uri is: {}".format(deter_uri))
1242 pid = new_project_dict['pid']
1243 uid = new_project_dict['uid']
1244
1245 logging.info('<apply_project_existing_user> Applying project for existing user: pid %s, uid %s', pid, uid)
1246
1247 cookie_file = uid + '.txt'
1248 logging.info("<apply_project_existing_user> Cookie file is: {} and before trying to construct the command".format(cookie_file))
1249 cmd = ''
1250 try:
1251 cmd = "curl -L -s -k -b " + cookie_file + " -X POST" + \
1252 " -F \"formfields[proj_name]=" + new_project_dict['proj_name'] + "\"" + \
1253 " -F \"formfields[proj_why]=" + new_project_dict['proj_goals'] + "\"" + \
1254 " -F \"formfields[pid]=" + pid + "\"" + \
1255 " -F \"formfields[proj_URL]=" + new_project_dict['proj_web'] + "\"" + \
1256 " -F \"formfields[proj_org]=" + new_project_dict['proj_org'] + "\"" + \
1257 " -F \"formfields[proj_research_type]=" + new_project_dict['proj_research_type'] + "\"" + \
1258 " -F \"formfields[proj_funders]=Other\"" + \
1259 " -F \"formfields[proj_public]=" + new_project_dict['proj_public'] + "\"" + \
1260 " -F \"submit=Submit\"" + \
1261 " '" + deter_uri + "newproject.php -v'"
1262 except Exception as e:
1263 logging.error("<apply_project_existing_user> Something fatal has happened while constructing the command:Check this{}".format(e))
1264 logging.info("<apply_project_existing_user> Command constructed is: {}".format(cmd))
1265 out = subprocess.check_output(cmd, shell=True)
1266
1267 if "Your project request has been successfully queued" in out:
1268 logging.info('Apply project %s for existing user %s ok', pid, uid)
1269 return '{"msg":"apply project existing users ok"}'
1270 elif 'Already in use. Select another' in out:
1271 logging.warning('Apply project existing user: error, project name already in use')
1272 return '{"msg":"team name already exists, please use a different one"}'
1273 elif "Illegal characters" in out:
1274 logging.warning('Apply project existing user: error, illegal characters in pid')
1275 return '{"msg":"team name contains illegal characters"}'
1276 else:
1277 logging.warning('Apply project existing user: unknown error %s', out)
1278 return '{"msg":"unknown error"}'
1279
1280
1281def create_new_project_dict(parsed_json):
1282 my_dict = {}
1283 try:
1284 my_dict['uid'] = parsed_json['uid']
1285 my_dict['proj_name'] = parsed_json['projName']
1286 my_dict['proj_goals'] = parsed_json['projGoals']
1287 my_dict['pid'] = parsed_json['pid']
1288 my_dict['proj_web'] = parsed_json['projWeb']
1289 my_dict['proj_org'] = parsed_json['projOrg']
1290 my_dict['proj_research_type'] = parsed_json['projResearchType']
1291
1292 if parsed_json['projPublic'] == 'PUBLIC':
1293 my_dict['proj_public'] = 'Yes'
1294 else:
1295 my_dict['proj_public'] = 'No'
1296 except Exception as e:
1297 logging.error("<create_new_project_dict> Something fatal has happened: {}".format(e))
1298
1299 return my_dict
1300
1301
1302###########################################################################################
1303
1304###########################################################################################
1305# approve project
1306
1307class approve_project:
1308 def POST(self):
1309 data = web.data()
1310 parsed_json = json.loads(data)
1311 pid = parsed_json['pid']
1312 uid = parsed_json['uid']
1313 is_class = parsed_json['isClass']
1314
1315 deter_uri = getDeterURI()
1316 cookie_file = 'ncl.txt'
1317
1318 logging.info('Approving project %s, head uid %s, is_class %s', pid, uid, is_class)
1319
1320 activateAdminFlag()
1321
1322 # check if project exists first on deterlab
1323 proj_status_before = get_proj_status(pid)
1324
1325 #check member status
1326 member_status_before = get_group_membership_status(pid, uid)
1327 logging.info('Before approve project pid %s, uid %s, project_status %s, member_status %s', pid, uid, proj_status_before, member_status_before)
1328
1329
1330 if proj_status_before == 'no project found':
1331 logging.warning('Approve project %s: NOT found', pid)
1332 return '{"msg":"no project found"}'
1333 elif proj_status_before == 'approved':
1334 logging.info('Approve project %s: already approved', pid)
1335 return '{"msg":"approve project OK"}'
1336 elif proj_status_before == 'archived':
1337 logging.warning('Approve project %s: already archived', pid)
1338 return '{"msg":"project has been archived"}'
1339 if not is_class:
1340 cmd = "curl -L --silent -k -b " + cookie_file + " -d \"approval=approve&prprefix=&silent=Yep&head_uid=" + \
1341 uid + "&user_interface=emulab&message=&OK=Submit\" " + "\"" + deter_uri + "approveproject.php?pid=" + pid + "\"" + " -v"
1342 else:
1343 random_string = random_6_char()
1344 # TODO: check if randomstring has been used
1345 cmd = "curl -L --silent -k -b " + cookie_file + \
1346 " -d \"approval=approve&prprefix=" + random_string + "&silent=Yep&head_uid=" + uid \
1347 + "&user_interface=emulab&message=&OK=Submit\" " \
1348 + "\"" + deter_uri + "approveproject.php?pid=" + pid + "\"" + " -v"
1349
1350
1351 logging.info('Message to approveproject.php: %s', cmd)
1352
1353 out = subprocess.check_output(cmd, shell=True)
1354
1355 logging.info('Message from approveproject.php: %s', out)
1356
1357 proj_status = get_proj_status(pid)
1358 member_status_after = get_group_membership_status(pid, uid)
1359 logging.info('After approve project pid %s, uid %s, project_status %s, member_status %s', pid, uid, proj_status, member_status_after)
1360
1361
1362 if proj_status == 'approved':
1363 logging.info('Approve project %s OK', pid)
1364 return '{"msg":"approve project OK"}'
1365 else:
1366 logging.warning('Approve project %s failed: %s', pid, out)
1367 return '{"msg":"approve project FAIL"}'
1368
1369
1370def random_6_char():
1371 string_list = string.ascii_lowercase
1372 random_string = ''
1373 for i in range(6):
1374 random_string += random.choice(string_list)
1375 return random_string
1376
1377###########################################################################################
1378# reject project
1379class reject_project:
1380 def POST(self):
1381 data = web.data()
1382 parsed_json = json.loads(data)
1383 pid = parsed_json['pid']
1384 logging.info('Rejecting project %s', pid)
1385 deter_uri = getDeterURI()
1386 cookie_file = 'ncl.txt'
1387
1388 activateAdminFlag()
1389
1390 # check if project exists first on deterlab
1391 proj_status_before = get_proj_status(pid)
1392
1393 if proj_status_before == 'no project found':
1394 # return ok because we want to remove entry from our db
1395 logging.info('Reject project %s: NOT found', pid)
1396 return '{"msg":"reject project OK"}'
1397 elif proj_status_before == 'approved':
1398 logging.warning('Reject project %s: has been approved', pid)
1399 return '{"msg":"project has been approved"}'
1400 elif proj_status_before == 'archived':
1401 logging.warning('Reject project %s: has been archived', pid)
1402 return '{"msg":"project has been archived"}'
1403
1404 cmd = "curl -L --silent -k -b " + cookie_file + " -d \"approval=destroy&prprefix=&silent=Yep&head_uid=&user_interface=emulab&message=&OK=Submit\" " + "\"" + deter_uri + "approveproject.php?pid=" + pid + "\"" + " -v"
1405
1406 out = subprocess.check_output(cmd, shell=True)
1407
1408 proj_status_after = get_proj_status(pid)
1409 if proj_status_after == 'no project found':
1410 logging.info('Reject project %s OK', pid)
1411 return '{"msg":"reject project OK"}'
1412 else:
1413 logging.warning('Reject project %s failed: %s', pid, out)
1414 return '{"msg":"reject project FAIL"}'
1415
1416
1417def activateAdminFlag():
1418 deter_uri = getDeterURI()
1419 cookie_file = 'ncl.txt'
1420 cmd = "curl --silent -k -b " + cookie_file + " " + "\"" + deter_uri + "toggle.php?user=ncl&type=adminon&value=1\"" + " -v"
1421 out = subprocess.check_output(cmd, shell=True)
1422 logging.info('Activated admin flag')
1423
1424
1425###########################################################################################
1426# get status of project
1427def get_proj_status(pid):
1428 cmd = "mysql -N -e \"select status from tbdb.projects where pid='" + pid + "'\""
1429 out = subprocess.check_output(cmd, shell=True)
1430 output_list = out.split('\n')
1431 # if sql query returns empty set: the output_list is ['']
1432 if output_list[0] == '':
1433 return "no project found"
1434
1435 status = output_list[0]
1436 return status
1437
1438
1439###########################################################################################
1440# get status of group membership
1441def get_group_membership_status(pid, uid):
1442 cmd = "mysql -N -e \"select trust from tbdb.group_membership where pid='" + pid + "' AND uid ='" + uid + "'\""
1443 out = subprocess.check_output(cmd, shell=True)
1444 output_list = out.split('\n')
1445 # if sql query returns empty set: the output_list is ['']
1446 if output_list[0] == '':
1447 return "not found"
1448
1449 status = output_list[0]
1450 return status
1451
1452
1453###########################################################################################
1454# get number of free nodes
1455class get_free_nodes:
1456 def GET(self):
1457 logging.info('Retrieving number of free nodes...')
1458
1459 cmd = "mysql -N -e \"select count(*) from tbdb.nodes as n left join tbdb.node_types as nt on n.type=nt.type left join tbdb.reserved as r on r.node_id=n.node_id where (role='testnode') and class='pc' and (n.eventstate='POWEROFF' or n.eventstate='ISUP' or n.eventstate='PXEWAIT' or n.eventstate='ALWAYSUP') and r.pid is null and n.reserved_pid is null;\""
1460
1461 out = subprocess.check_output(cmd, shell=True)
1462
1463 output_list = out.split('\n')
1464 logging.info('Number of free nodes returned: %s', output_list)
1465
1466 # if sql query returns empty set: the output_list is ['']
1467 if output_list[0] == '':
1468 return 0
1469
1470 return output_list[0]
1471
1472
1473###########################################################################################
1474# get number of total nodes
1475class get_total_nodes:
1476 def GET(self):
1477 logging.info('Retrieving number of total nodes...')
1478
1479 cmd = "mysql -N -e \"select n.type,count(*) from tbdb.nodes as n left join tbdb.node_types as nt on n.type=nt.type left join tbdb.node_type_attributes as na on n.type=na.type where (role='testnode') and na.attrkey ='special_hw' group BY n.type, na.attrvalue;\""
1480
1481 out = subprocess.check_output(cmd, shell=True)
1482
1483 # type | count
1484 output_list = out.split('\n')
1485 logging.info('Number of total nodes returned: %s', output_list)
1486
1487 # if sql query returns empty set: the output_list is ['']
1488 if output_list[0] == '':
1489 return 0
1490
1491 total_node_count = 0
1492
1493 for output_block in output_list:
1494 # split (type count) by whitespaces
1495 # sum up the node count by type
1496 output_list2 = output_block.split()
1497
1498 # ['system type', 'count', '']
1499 if output_list2:
1500 total_node_count += int(output_list2[1])
1501
1502 return total_node_count
1503
1504
1505###########################################################################################
1506# get all the nodes status
1507# include nodes type, physical node number
1508# output is a json dump of a dictionary in the following format
1509# { node_type: [{ <status for node 1> }, { <status for node 2> }...{} ] }
1510# <status for node 1> is a internal dictionary like: { id : <node_id>, status : <status>, pid: <pid>, eid: <eid> }
1511# returns an empty dictionary if fail
1512class get_nodes_status:
1513 def GET(self):
1514 logging.info('Retrieving node status...')
1515
1516 output = {}
1517 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
1518 cursor = db.cursor()
1519
1520 try:
1521 # fetch all the nodes' types
1522 # change the attribute from special_hw to others to detect more types
1523 cursor.execute(
1524 "select distinct n.type from nodes as n left join node_types as nt on n.type=nt.type left join node_type_attributes as na on n.type=na.type where na.attrkey='special_hw'")
1525 results = cursor.fetchall()
1526
1527 # list of lists
1528 for row in results:
1529 logging.info('Retrieving current type... %s', row[0])
1530
1531 # convert to uppercase for ease of making it to a enum in Java
1532 node_type = row[0].upper()
1533 # get all the nodes info for each node type
1534 nodes_lists = []
1535
1536 # returns node id, event state, pid, reserved pid
1537 # add eid (modified by Vu)
1538 cursor.execute("select distinct n.node_id, n.eventstate, ifnull(r.pid,rp.pid) as pid, \
1539 n.reserved_pid as reserved_pid, \
1540 ifnull(r.eid,rp.eid) as eid \
1541 from nodes as n \
1542 left join node_types as nt on n.type=nt.type \
1543 left join reserved as r on n.node_id=r.node_id \
1544 left join reserved as rp on n.phys_nodeid=rp.node_id \
1545 where nt.type ='" + node_type + "' \
1546 and (role='testnode' or role='virtnode') ORDER BY priority")
1547
1548 results2 = cursor.fetchall()
1549
1550 # column value is fetch by row2 sql statement in the order of the select statement, e.g. node_id = row2[0]
1551 for row2 in results2:
1552 node_info = {}
1553 node_info['id'] = row2[0]
1554 node_info['status'] = convertToNodesStatus(row2[1], row2[2], row2[3])
1555 if (row2[2] is None) and row2[3]:
1556 node_info['pid'] = row2[3]
1557 else:
1558 node_info['pid'] = row2[2]
1559 node_info['eid'] = row2[4]
1560 nodes_lists.append(node_info)
1561
1562 # sort nodes alphabetically by status then by its id
1563 output[node_type] = sorted(nodes_lists, key=lambda k: (k['status'], k['id']))
1564
1565 except:
1566 logging.warning('Retrieving node status failed')
1567
1568 db.close()
1569 return json.dumps(output)
1570
1571
1572# covnerts deterlab state to simple free, in_use, reload
1573def convertToNodesStatus(eventState, pid, reserved_pid):
1574 free_state = "free"
1575 in_use_state = "in_use"
1576 reload_state = "reload" # may indicate dead machine as well
1577 reserved_state = "reserved"
1578
1579 if pid:
1580 # project is allocated a node
1581 return in_use_state
1582 else:
1583 if (eventState == 'ISUP') or (eventState == 'ALWAYSUP') or \
1584 (eventState == 'POWEROFF') or (eventState == 'PXEWAIT'):
1585 if reserved_pid is None:
1586 return free_state
1587 else:
1588 # reserved pid implies nodes are pre-reserved for a project
1589 return reserved_state
1590 else:
1591 return reload_state
1592
1593
1594###########################################################################################
1595# get the network topology thumbnail of an experiment
1596class get_topo_thumbnail:
1597 def POST(self):
1598 data = web.data()
1599 parsed_json = json.loads(data)
1600 pid = parsed_json['pid']
1601 eid = parsed_json['eid']
1602 logging.info('Retrieving topology thumbnail for %s/%s', pid, eid)
1603
1604 cmd = "mysql --raw -N -e \"select thumbnail from tbdb.experiment_resources where idx = (select rsrcidx from tbdb.experiment_stats where eid_uuid = (select eid_uuid from tbdb.experiments where pid='" + pid + "' and eid='" + eid + "'));\""
1605
1606 out = subprocess.check_output(cmd, shell=True)
1607
1608 logging.info('Done')
1609 return base64.encodestring(out)
1610
1611
1612###########################################################################################
1613# # get exp report with nodes
1614# def get_exp_report_with_nodes(pid, eid):
1615# logging.info('Consolidating experiment information')
1616#
1617# # get node info
1618# # cmd = "/usr/testbed/bin/tbreport -n " + pid + " " + eid
1619# cmd = "/usr/testbed/bin/tbreport -n " + pid + " " + eid
1620#
1621# # print cmd
1622# q = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
1623# node_info, err = q.communicate()
1624#
1625# # get physical node mapping
1626# # cmd2 = "/usr/testbed/bin/tbreport -m " + pid + " " + eid
1627# cmd2 = "/usr/testbed/bin/tbreport -m " + pid + " " + eid
1628#
1629# q2 = subprocess.Popen(cmd2, shell=True, stdout=subprocess.PIPE)
1630# node_mapping, err = q2.communicate()
1631#
1632# node_info_split = node_info.split('\n')
1633# node_mapping_split = node_mapping.split('\n')
1634#
1635# # print node_info
1636# # print node_mapping
1637# # print node_info_split[6:-2]
1638# # print node_mapping_split[6:-2]
1639#
1640# result = {}
1641# # start from index 6 because we want to skip all the headers info
1642# # end at -2 because last two entries is always empty string
1643# '''
1644# information now is in this format
1645# ['nodeIDA typeA osA qualifiedNameA', 'nodeNameB...'] tbreport -n result
1646# ['nodeIDA typeA osA physicalA'] tbreport -m result
1647# '''
1648# for node_info_str in node_info_split[6:-2]:
1649# # information now is this format ['nodeIDA','typeA','osA','qualifiedNameA']
1650# # ID, TYPE (aka CLASS pc), OS, QUALIFIED_NAME
1651#
1652# node_info_str_list = node_info_str.split()
1653# node_info_dict = {}
1654#
1655# # there is a chance OS will be empty if the user did not use tb-set-node-os
1656# if len(node_info_str_list) >= 3:
1657# # OS field exists
1658# node_info_dict['qualifiedName'] = node_info_str_list[-1]
1659# else:
1660# # some other errors occurred:
1661# node_info_dict['qualifiedName'] = 'None'
1662#
1663# # result[nodeId] = dictionary
1664# # do not add sdn controller nodes
1665# machine_type = node_info_str_list[1]
1666# if machine_type != 'ovxctl' and machine_type != 'ofedge' and machine_type != 'ofcore':
1667# result[node_info_str_list[0]] = node_info_dict
1668#
1669# for node_mapping_str in node_mapping_split[6:-2]:
1670# # ID (node name e.g. n0), TYPE, OS, PHYSICAL
1671# # add os information here is more reliable
1672# # add node id information
1673# # add another key-pair to the result[nodeId] existing dictionary
1674# node_mapping_str_list = node_mapping_str.split()
1675# node_name = node_mapping_str_list[0]
1676#
1677# if node_name in result:
1678# # only add more details if result already contains the node name from previous for loop
1679# # because node_mapping_str_list may contain key value such as tbdelay0 which is not in result
1680# # several types of splitted node_mapping_str may occur:
1681# # 1. ['node_name', 'TYPE', 'OS', 'physical node id']
1682# # 2. ['node_name', 'TYPE', 'physical node id', 'physical node id']
1683# if len(node_mapping_str_list) > 3:
1684# if 'ofport' in node_mapping_str_list[2] and 'ofport' in node_mapping_str_list[3]:
1685# # for sdn experiments, the switch has no OS and may be in this form after split: ['switch1p1','ofedge','ofport11', '(ofportpc1)']
1686# # combine [2] and [3] together
1687# result[node_mapping_str_list[0]]['os'] = 'N.A.'
1688# result[node_mapping_str_list[0]]['nodeId'] = node_mapping_str_list[2] + ' ' + node_mapping_str_list[3]
1689# else:
1690# result[node_mapping_str_list[0]]['os'] = node_mapping_str_list[2]
1691# result[node_mapping_str_list[0]]['nodeId'] = node_mapping_str_list[3]
1692# else:
1693# # node_mapping_str_list is length of 3
1694# # e.g. ['switch1p1', 'ofedge', 'ofport11']
1695# result[node_mapping_str_list[0]]['os'] = 'N.A.'
1696# result[node_mapping_str_list[0]]['nodeId'] = node_mapping_str_list[2]
1697#
1698# logging.info('exp nodes info: %s', result)
1699# return json.dumps(result)
1700
1701###########################################################################################
1702# save image
1703class save_image:
1704 def POST(self):
1705 data = web.data()
1706 parsed_json = json.loads(data)
1707 image_name = parsed_json['imageName']
1708 pid = parsed_json['pid']
1709 uid = parsed_json['uid']
1710 node_id = parsed_json['nodeId']
1711 logging.info('Saving image %s from node %s, project %s, user %s', image_name, node_id, pid, uid)
1712 deter_uri = getDeterURI()
1713 cookie_file = uid + '.txt'
1714
1715 # check if the node is up; can only create an image from a running machine
1716 mysql_cmd = " mysql -N -e \"select status from tbdb.node_status where node_id = '" + node_id + "'\""
1717 # logging.debug("SQL to query node status: " + mysql_cmd)
1718 status = subprocess.check_output(mysql_cmd, shell=True)
1719 logging.debug(node_id + ": " + status)
1720 if status.strip() != 'up':
1721 logging.warning('Saving image %s failed: can only create image from a running machine', image_name)
1722 return '{"msg": "Node is not up. You can only create an image from a running node."}'
1723
1724 # Tran: check if uid is the user who swapped the experiment
1725 swapper_query = "mysql -N -e \"select u.uid from tbdb.users u, tbdb.experiments e, tbdb.reserved r " + \
1726 "where e.swapper_idx = u.uid_idx and e.idx = r.exptidx and r.node_id = '" + node_id + "'\""
1727 # logging.debug("SQL to query experiment swapper: %s", swapper_query)
1728 swapper = subprocess.check_output(swapper_query, shell=True)
1729 logging.debug("Swapper: %s", swapper)
1730 if swapper.strip() != uid:
1731 logging.warning("Saving image %s failed: swapper not found from DB, or user is not the swapper")
1732 return '{"msg": "Permission denied. Only the user who started the experiment can save image."}'
1733
1734 # retrieve parameters from the current OS on the node
1735 mysql_cmd = "mysql -N -e \" select o.osname, o.OS, o.version, o.op_mode, o.osfeatures, o.reboot_waittime, i.loadpart, i.imageid " + \
1736 " from tbdb.os_info o, tbdb.images i, tbdb.osidtoimageid oi, tbdb.nodes n " + \
1737 " where o.osid = n.osid and i.imageid = oi.imageid and oi.osid = n.osid and n.node_id = '" + node_id + "' " + \
1738 " group by oi.imageid\""
1739 # logging.debug("SQL to load OS parameters: %s", mysql_cmd)
1740 para_row = subprocess.check_output(mysql_cmd, shell=True)
1741 logging.debug("OS parameters on the node: %s", para_row)
1742 para_row_list = para_row.split()
1743 if para_row_list[0] == '':
1744 logging.warning("Save image %s failed: cannot load parameters from OS on node", image_name)
1745 return '{"msg":"Failed to load parameters from the current OS on the node."}'
1746
1747 # retrieve node types from DB
1748 mysql_cmd = "mysql -N -e \"select distinct n.type from tbdb.nodes as n left join tbdb.node_types as nt on n.type=nt.type left join tbdb.node_type_attributes as a on a.type=n.type where a.attrkey='imageable' and a.attrvalue!='0' and n.role='testnode'\""
1749 # logging.debug("SQL to retrieve node types: %s", mysql_cmd)
1750 node_type_row = subprocess.check_output(mysql_cmd, shell=True)
1751 logging.debug(node_type_row)
1752 node_type_list = node_type_row.split()
1753
1754 if node_type_list[0] == '':
1755 logging.warning("Save image %s failed: cannot load node types from DB", image_name)
1756 return '{"msg":"Failed to retrieve node types from database."}'
1757
1758 cmd = "curl -L -s -k -b " + cookie_file + " -X POST" + \
1759 " -F \"formfields[simple]=0\"" + \
1760 " -F \"formfields[node_id]=" + node_id + "\"" + \
1761 " -F \"formfields[pid]=" + pid + "\"" + \
1762 " -F \"formfields[imagename]=" + image_name + "\"" + \
1763 " -F \"formfields[description]=default\"" + \
1764 " -F \"formfields[loadpart]=1\"" + \
1765 " -F \"formfields[OS]=" + para_row_list[1] + "\"" + \
1766 " -F \"formfields[version]=" + para_row_list[2] + "\"" + \
1767 " -F \"formfields[path]=/proj/" + pid + "/images/" + image_name + ".ndz\"" + \
1768 " -F \"formfields[current_OS]=" + para_row_list[0] + "\"" + \
1769 " -F \"formfields[os_feature_ping]=checked\"" + \
1770 " -F \"formfields[os_feature_ssh]=checked\"" + \
1771 " -F \"formfields[os_feature_ipod]=checked\"" + \
1772 " -F \"formfields[os_feature_isup]=checked\"" + \
1773 " -F \"formfields[op_mode]=" + para_row_list[3] + "\""
1774
1775 # checked all node type, e.g. Intel, SystemX
1776 # formfields[mtype_all] is not able to select all the node types
1777 for node_type in node_type_list:
1778 cmd += " -F \"formfields[mtype_" + node_type + "]=Yep\""
1779
1780 cmd += " -F \"formfields[wholedisk]=Yep\"" + \
1781 " -F \"formfields[max_concurrent]=\"" + \
1782 " -F \"formfields[reboot_waittime]=\"" + \
1783 " -F \"submit=Submit\"" + \
1784 " '" + deter_uri + "newimageid_ez.php'"
1785
1786 # logging.debug("Curl command to save image: %s", cmd)
1787 out = subprocess.check_output(cmd, shell=True)
1788 # logging.debug(out)
1789
1790 if 'Taking a snapshot of node' in out:
1791 logging.info('Saving image %s has started', image_name)
1792 return '{"msg":"save image OK"}'
1793 elif 'Illegal Characters' in out:
1794 logging.warning('Saving image %s failed: Invalid image name', image_name)
1795 return '{"msg":"Image name contains invalid characters."}'
1796 elif 'already exists' in out:
1797 logging.warning('Saving image %s failed: Image name already exists', image_name)
1798 return '{"msg":"Image name already exists."}'
1799 else:
1800 logging.warning('Saving image %s failed: %s', image_name, out)
1801 return '{"msg":"There is an error in your request."}'
1802
1803
1804###########################################################################################
1805#########################DELETE IMAGE #####################################################
1806###########################################################################################
1807# delete image
1808class delete_image:
1809 def DELETE(self):
1810 data = web.data()
1811 parsed_json = json.loads(data)
1812 image_name = parsed_json['imageName']
1813 pid = parsed_json['pid']
1814 uid = parsed_json['uid']
1815 isImageCreator = parsed_json['isImageCreator']
1816
1817 image_filepath = "/proj/" + pid + "/images/" + image_name + ".ndz"
1818 image_sig_filepath = "/proj/" + pid + "/images/sigs/" + image_name + ".ndz.sig"
1819
1820 result = {}
1821
1822 if isImageCreator == "yes":
1823 logging.info('Retrieving creator from imagename %s', image_name)
1824 # mysql options:
1825 # -N Do not write column names in result(s).
1826 # -e statement
1827 mysql_cmd = "mysql -N -e \"select creator from tbdb.images where imagename=\'" + image_name + "\' and pid=\'" + pid + "\'\""
1828
1829 creator_query = subprocess.Popen(mysql_cmd, shell=True, stdout=subprocess.PIPE)
1830 creator_query_out, creator_query_err = creator_query.communicate()
1831
1832 if creator_query.returncode != 0:
1833 logging.warning(
1834 'Deleting image %s from user %s of project %s: error in DeterLab when retrieving creator of image',
1835 image_name, uid, pid)
1836 return '{"msg": "unknown error in quering database to retrieving creator of image"}'
1837
1838 creator_query_list = creator_query_out.split('\n')
1839 if creator_query_list[0] == '':
1840 return '{"msg": "no creator found when querying database"}'
1841 else:
1842 creator = creator_query_list[0]
1843 if (creator != uid):
1844 return '{"msg": "not the creator"}'
1845
1846 logging.info('Retrieving imageid from imagename %s', image_name)
1847 # mysql options:
1848 # -N Do not write column names in result(s).
1849 # -e statement
1850 mysql_cmd = "mysql -N -e \"select imageid from tbdb.images where imagename=\'" + image_name + "\' and pid=\'" + pid + "\'\""
1851 query = subprocess.Popen(mysql_cmd, shell=True, stdout=subprocess.PIPE)
1852 query_out, query_err = query.communicate()
1853
1854 if query.returncode != 0:
1855 logging.warning('Deleting image %s from user %s of project %s: error in DeterLab when retrieving imageid',
1856 image_name, uid, pid)
1857 return '{"msg": "unknown error in querying database to retrieve imageid"}'
1858
1859 output_list = query_out.split('\n')
1860 if output_list[0] == '':
1861 return '{"msg": "required image id is not found when querying database"}'
1862 else:
1863 image_id = output_list[0]
1864
1865 # check if image is in use
1866 check_inuse_cmd = "mysql -N -e \"select distinct v.eid from tbdb.virt_nodes as v left join tbdb.experiments as e on v.pid=e.pid and v.eid=e.eid where v.pid=\'" + pid + "\' and v.osname=\'" + image_name + "\'\""
1867 check_inuse_query = subprocess.check_output(check_inuse_cmd, shell=True)
1868
1869 experiments_list = check_inuse_query.rstrip().split('\n')
1870
1871 if experiments_list[0] != '':
1872 logging.warning('Deleting image %s from user %s of project %s: experiments {} still using image',
1873 image_name, uid, pid, experiments_list)
1874
1875 result['msg'] = 'image still in use';
1876 result['experiments'] = experiments_list;
1877 return json.dumps(result)
1878 else:
1879 # statement receive when executing Curl command
1880 succesful_statement = "Image " + "\'" + image_id \
1881 + "\'" + " in project " + pid \
1882 + " has been deleted!"
1883 no_permission_statement = "You do not have permission to destroy ImageID"
1884 no_imageid_statement = "Could not map page arguments to 'image'"
1885 image_in_use_statement = "Image " + image_id + " is still in use or busy!"
1886
1887 logging.info('Deleting imageid %s from web: executing curl command', image_id)
1888 deter_uri = getDeterURI()
1889 cookie_file = uid + '.txt'
1890 # Curl options:
1891 # -s silent mode
1892 # -L redo the request on the new place if the requested page has moved to a different location
1893 # -k This option allows curl to proceed and operate even for server connections otherwise considered insecure.
1894 # -b Pass the data to the HTTP server in the Cookie header
1895 # -X Specifies a custom request method to use when communicating with the HTTP server.
1896 # -F this lets curl emulate a filled-in form
1897 # -d data
1898 curl_cmd = "curl -s -L -k -b " + cookie_file + " -X POST" + \
1899 " -d \"imageid=" + image_id + \
1900 "&confirmed=Confirm\"" + \
1901 " '" + deter_uri + "deleteimageid.php'"
1902 curl = subprocess.Popen(curl_cmd, shell=True, stdout=subprocess.PIPE)
1903 curl_out, curl_err = curl.communicate()
1904
1905 if curl.returncode != 0:
1906 logging.warning(
1907 'Deleting image %s from user %s of project %s: error in DeterLab when using Curl command',
1908 image_name, uid, pid)
1909 return '{"msg": "unknown error when executing Curl command"}'
1910
1911 elif no_imageid_statement in curl_out:
1912 logging.warning(
1913 'Deleting image %s from user %s of project %s: imageid in Curl command could not be found',
1914 image_name, uid, pid)
1915 return '{"msg": "imageid could not be found when executing Curl command"}'
1916
1917 elif no_permission_statement in curl_out:
1918 logging.warning('Deleting image %s from user %s of project %s: no permission to delete the imageid',
1919 image_name, uid, pid)
1920 return '{"msg": "no permission to delete the imageid when executing Curl command"}'
1921
1922 elif image_in_use_statement in curl_out:
1923 logging.warning('Deleting image %s from user %s of project %s: image still in use', image_name, uid,
1924 pid)
1925 return '{"msg": "image still in use"}'
1926
1927 # if Curl is succesfull, we start to remove physical image and image signature in proj directory
1928 # note: image file will exist but signature file may or may not exist
1929 elif succesful_statement in curl_out:
1930
1931 logging.info(
1932 'Succesfully delete image from web , starting to deleting physical image and image signature in project %s directory',
1933 pid)
1934
1935 rm_cmd = "sudo rm " + image_filepath + " && sudo rm " + image_sig_filepath
1936 rm = subprocess.Popen(rm_cmd, shell=True, stdout=subprocess.PIPE)
1937 rm_out, rm_err = rm.communicate()
1938
1939 # if no file in directory, the output is in rm_error
1940 if '' in rm_out or 'No such file or directory' in rm_err:
1941 return '{"msg":"delete image OK from both web and project directory"}'
1942
1943 else:
1944 logging.warning(
1945 'Deleting image %s from user %s of project %s: error in DeterLab when using rm command',
1946 image_name, uid, pid)
1947 return '{"msg":"delete image OK from web but there is unknown error when deleting physical image"}'
1948
1949 # if some other statement is captured then return unknown error
1950 return '{"msg":"unknown error in deleting image from web"}'
1951
1952
1953###########################################################################################
1954# get saved images by team
1955class get_saved_images:
1956 def POST(self):
1957 data = web.data()
1958 parsed_json = json.loads(data)
1959 pid = parsed_json['pid']
1960 logging.info("Getting saved images for team %s", pid)
1961
1962 cmd = "mysql -N -e \"select osname from tbdb.os_info where pid=\'" + pid + "\'\""
1963 out = subprocess.check_output(cmd, shell=True)
1964 image_list = out.split()
1965
1966 result = {}
1967
1968 if image_list:
1969 for osname in image_list:
1970 # check if the .ndz file exists or not
1971 ndz_file = "/proj/" + pid + "/images/" + osname + ".ndz"
1972 cmd = "sudo test -e \"" + ndz_file + "\"; echo $?"
1973 out = subprocess.check_output(cmd, shell=True)
1974 # the ndz file does not exist
1975 if out.strip() == '1':
1976 logging.warning("Image file %s does not exist.", ndz_file)
1977 result[osname] = 'notfound'
1978 # the ndz file exists
1979 else:
1980 cmd = "sudo stat -f %z " + ndz_file
1981 out = subprocess.check_output(cmd, shell=True)
1982 if out.strip() == '0':
1983 # .ndz file exists but size 0
1984 # if the file size remains 0 for a certain amount of time, meaning that the saving failed
1985 cmd = 'sudo stat -f %m ' + ndz_file
1986 mtime = subprocess.check_output(cmd, shell=True)
1987 if (time.time() - float(mtime.strip())) > 3600 * 24:
1988 logging.warning("Size of %s remains 0 for more than 24 hours, saving failed", ndz_file)
1989 result[osname] = 'failed'
1990 else:
1991 result[osname] = 'saving'
1992 else:
1993 # get size of signature file to check if image has been created successfully
1994 # signature file size should be more than zero bytes
1995 sig_file = "/proj/" + pid + "/images/sigs/" + osname + ".ndz.sig"
1996 cmd = "sudo test -e \"" + sig_file + "\"; echo $?"
1997 out = subprocess.check_output(cmd, shell=True)
1998 if out.strip() == '1':
1999 result[osname] = 'saving'
2000 else:
2001 cmd = "sudo stat -f %z " + sig_file
2002 out = subprocess.check_output(cmd, shell=True)
2003 if out.strip() == '0':
2004 result[osname] = 'saving'
2005 else:
2006 result[osname] = 'created'
2007
2008 logging.debug("Saved images for team %s: %s", pid, result)
2009 # result dictionary looks like this:
2010 # { 'imageA' : 'notfound', 'imageB' : 'saving', 'imageC' : 'created' }
2011 return json.dumps(result)
2012
2013
2014###########################################################################################
2015# get cve images that are globally shared
2016# returns a dump of the resulting dictionary
2017class get_cve_images:
2018 def GET(self):
2019 supported = "4"
2020 cmd = "mysql -N -e \"select a.imagename, b.OS, a.description from tbdb.images a left join tbdb.os_info b on a.imagename=b.osname where b.supported = " + supported + " order by a.imagename;\""
2021 p = subprocess.check_output(cmd, shell=True)
2022
2023 result = {}
2024
2025 if p:
2026 logging.info('Retrieving list of global images')
2027 # ['imageA \t osname \t description \n imageB \t osname \t description']
2028 image_list = p.split('|')
2029
2030 # ['imageA \t osname \t description', 'imageB \t osname \t description']
2031 image_list = image_list[0].split('\n')
2032
2033 for images in image_list:
2034 # images may be empty
2035 if images:
2036 # now get individual columns
2037 # images_details[0] = image
2038 # images_details[1] = osname
2039 # images_details[2] = description
2040 images_details = images.split('\t')
2041 images_details_dict = {}
2042
2043 images_details_dict['osname'] = images_details[1]
2044 images_details_dict['description'] = images_details[2]
2045
2046 result[images_details[0]] = images_details_dict
2047
2048 # result dictionary looks like this:
2049 # if empty : {}
2050 # if non-empty: {'imageA' : {'os' : 'Linux', 'description' : 'Ubuntu 12.04 LTS 64 bit'}, 'imageB' : {...}}
2051 return json.dumps(result)
2052
2053
2054###########################################################################################
2055# remove user from team
2056class remove_user_from_team:
2057 def POST(self):
2058 data = web.data()
2059 parsed_json = json.loads(data)
2060 pid = parsed_json['pid']
2061 uid_to_be_removed = parsed_json['uidToBeRemoved']
2062 owner_uid = parsed_json['ownerUid']
2063
2064 logging.info('Removing user %s from team %s, team owner %s', uid_to_be_removed, pid, owner_uid)
2065
2066 membership = get_group_membership_status(pid, uid_to_be_removed)
2067 if membership == "not found":
2068 logging.info('Remove user %s from team %s: no membership record found', uid_to_be_removed, pid)
2069 return '{"msg":"remove user from team ok"}'
2070
2071 deter_uri = getDeterURI()
2072 cookie_file = owner_uid + '.txt'
2073
2074 cmd = "curl -L -s -k -b " + cookie_file + " -d \"confirmed=Confirm&confirmed_twice=Confirm\"" + \
2075 " '" + deter_uri + "deleteuser.php?user=" + uid_to_be_removed + "&pid=" + pid + "'"
2076
2077 out = subprocess.check_output(cmd, shell=True)
2078
2079 membership = get_group_membership_status(pid, uid_to_be_removed)
2080
2081 if membership == "not found" and 'Done' in out:
2082 logging.info('Remove user %s from team %s: ok', uid_to_be_removed, pid)
2083 return '{"msg":"remove user from team ok"}'
2084 elif "They must be terminated before you can remove the user!" in out:
2085 logging.warning('Remove user %s from team %s: failed - user has experiments', uid_to_be_removed, pid)
2086 return '{"msg":"user has experiments"}'
2087 else:
2088 logging.warning('Remove user %s from team %s: failed, member status is %s', uid_to_be_removed, pid,
2089 membership)
2090 logging.debug('Remove user %s from team %s: Deter Output -- %s', uid_to_be_removed, pid, out)
2091 return '{"msg":"remove user from team failed"}'
2092
2093
2094###########################################################################################
2095# get number of logged in users
2096class get_logged_in_users:
2097 def GET(self):
2098 logging.info('Retrieving number of logged in users...')
2099
2100 cmd = "mysql -N -e \"select count(distinct uid) from tbdb.login where timeout > UNIX_TIMESTAMP(now());\""
2101
2102 out = subprocess.check_output(cmd, shell=True)
2103
2104 output_list = out.split('\n')
2105 logging.info('Number of logged in users returned: %s', output_list)
2106
2107 # if sql query returns empty set: the output_list is ['']
2108 if output_list[0] == '':
2109 return 0
2110
2111 return output_list[0]
2112
2113
2114###########################################################################################
2115# get number of running experiments
2116class get_running_experiments_count:
2117 def GET(self):
2118 logging.info('Retrieving number of running experiments...')
2119
2120 cmd = "mysql -N -e \"select count(*) from tbdb.experiments where state = 'active' and pid != 'emulab-ops' and pid != 'testbed';\""
2121
2122 out = subprocess.check_output(cmd, shell=True)
2123
2124 output_list = out.split('\n')
2125 logging.info('Number of running experiments returned: %s', output_list)
2126
2127 # if sql query returns empty set: the output_list is ['']
2128 if output_list[0] == '':
2129 return 0
2130
2131 return output_list[0]
2132
2133
2134###########################################################################################
2135# modify experiment
2136# check ns file syntax before modifying experiment
2137# waits for the modification to complete
2138class modify_ns_file:
2139 def POST(self):
2140 data = web.data()
2141 parsed_json = json.loads(data)
2142 pid = parsed_json['pid']
2143 eid = parsed_json['eid']
2144 uid = parsed_json['uid']
2145 nsfile = parsed_json['nsfile']
2146 logging.info('Modifying experiment %s/%s ns file by %s', pid, eid, uid)
2147
2148 deter_uri = getDeterURI()
2149 cookie_file = uid + '.txt'
2150 result = {}
2151
2152 # get the idx from the deter database
2153 cmd = "mysql -N -e \"select idx from tbdb.experiments where eid='" + eid + "'" + " AND pid ='" + pid + "'\""
2154
2155 exp_idx = subprocess.check_output(cmd, shell=True)
2156
2157 if exp_idx:
2158 cmd_line = "curl -L --silent -b " + cookie_file + " -k -d \"MAX_FILE_SIZE=512000" + \
2159 "&exp_nsfile=" + "\"\"" + \
2160 "&exp_localnsfile=" + "\"\"" + \
2161 "&nsdata=" + urllib.quote(nsfile) + \
2162 "&go=Modify" + "\" " + \
2163 deter_uri + "modifyexp.php?experiment=" + exp_idx
2164
2165 out = subprocess.check_output(cmd_line, shell=True)
2166
2167 if "Modified NS file contains syntax errors" in out or "Experiment modify could not proceed" in out:
2168 # get the error output
2169 error_output = re.search('<pre>((.|\n)*)<pre>', out).group(1)
2170 logging.warning('Modify experiment %s/%s ns file failed: %s', pid, eid, error_output)
2171 result['msg'] = 'modify experiment fail ns parse error'
2172 result['modify_experiment'] = error_output
2173 else:
2174 # experiment is modifying
2175 # inspect the log file
2176 logfile_cmd = "mysql -N -e \"select logfile from tbdb.experiments where eid='" + eid + "'" + " AND pid ='" + pid + "'\""
2177 logfile_id = subprocess.check_output(logfile_cmd, shell=True)
2178
2179 inspect_cmd = "curl -L -s -k -b " + cookie_file + " " + deter_uri + "/spewlogfile.php?logfile=" + logfile_id
2180 logfile_output = subprocess.check_output(inspect_cmd, shell=True)
2181
2182 if "Swap Success" in logfile_output:
2183 logging.info('Modify experiment %s/%s ns file succeeded', pid, eid)
2184 result['msg'] = 'modify experiment success'
2185 result['modify_experiment'] = logfile_output.lstrip()
2186 else:
2187 logging.warning('Modify experiment %s/%s ns file failed: %s', pid, eid, logfile_output.lstrip())
2188 result['msg'] = 'modify experiment fail'
2189 result['modify_experiment'] = logfile_output.lstrip()
2190 else:
2191 logging.warning('Experiment %s/%s not found in database', pid, eid)
2192 result['msg'] = 'modify experiment fail'
2193 result['modify_experiment'] = 'Experiment not found in database'
2194
2195 return json.dumps(result)
2196
2197
2198###########################################################################################
2199# modify experiment settings
2200# different PHP call from modify_ns_file
2201# max duration, swap. etc
2202class modify_experiment_settings:
2203 def POST(self):
2204 data = web.data()
2205 parsed_json = json.loads(data)
2206 pid = parsed_json['pid']
2207 eid = parsed_json['eid']
2208 uid = parsed_json['uid']
2209 logging.info('Modifying experiment %s/%s settings by %s', pid, eid, uid)
2210
2211 autoswap_timeout = str(parsed_json['maxDuration'])
2212 autoswap_enable = "0"
2213 if autoswap_timeout != "0":
2214 # experiment set to swap out after some hours
2215 autoswap_enable = "1"
2216 else:
2217 # experiment will never auto swapout
2218 # set the default value to something else other than 0 as the webpage checks for this parameter
2219 autoswap_timeout = "16"
2220
2221 deter_uri = getDeterURI()
2222 cookie_file = uid + '.txt'
2223 result = {}
2224
2225 # get the idx from the deter database
2226 cmd = "mysql -N -e \"select idx from tbdb.experiments where eid='" + eid + "'" + " AND pid ='" + pid + "'\""
2227
2228 exp_idx = subprocess.check_output(cmd, shell=True)
2229
2230 if exp_idx:
2231 cmd_line = "curl --silent -b " + cookie_file + " -k -d \"formfields[autoswap_timeout]=" + autoswap_timeout + \
2232 "&formfields[autoswap]=" + autoswap_enable + \
2233 "&submit=Submit" + "\" " + \
2234 deter_uri + "editexp.php?experiment=" + exp_idx
2235
2236 out = subprocess.check_output(cmd_line, shell=True)
2237
2238 if "please fix the following errors" in out:
2239 logging.warning('Modify experiment %s/%s settings failed: %s', pid, eid, out)
2240 result['msg'] = 'modify experiment settings: max_duration fail'
2241 else:
2242 logging.info('Modify experiment %s/%s settings succeeded', pid, eid)
2243 result['msg'] = 'modify experiment settings: max_duration success'
2244
2245 return json.dumps(result)
2246
2247
2248##############################################################
2249# get the list of experiments of a project
2250# If an experiment is active, also returns the realization details
2251class get_experiments_by_team:
2252 def POST(self):
2253 data = web.data()
2254 parsed_json = json.loads(data)
2255 pid = parsed_json['pid']
2256 logging.info('Getting list of experiments for %s', pid)
2257
2258 cmd = "mysql -N -e \"select e.pid, e.eid, e.state, count(r.node_id) as nodes, round(e.minimum_nodes+.1,0) as min_nodes " + \
2259 " from tbdb.experiments as e " + \
2260 " left join tbdb.reserved as r on e.pid=r.pid and e.eid=r.eid " + \
2261 " where e.pid='" + pid + "' " + \
2262 " group by e.pid,e.eid order by e.state,e.eid\""
2263
2264 out = subprocess.check_output(cmd, shell=True)
2265 #
2266 # +------------+-----------+---------+-------+-----------+
2267 # | nclproject | demo | active | 2 | 2 |
2268 # | nclproject | containe2 | swapped | 0 | 2 |
2269 # | nclproject | container | swapped | 0 | 1 |
2270 # +------------+-----------+---------+-------+-----------+
2271
2272 out_list = out.split('\n')
2273 result = {}
2274
2275 for expt in out_list:
2276 if expt:
2277 expt_details = expt.split()
2278 eid = expt_details[1]
2279 state = expt_details[2]
2280 tmp = {}
2281 tmp['state'] = state
2282 tmp['nodes'] = expt_details[3]
2283 tmp['min_nodes'] = expt_details[4]
2284 tmp['idle_hours'] = '0' # will implement the idle-hours later
2285
2286 if state == 'active':
2287 tmp['details'] = get_exp_nodes_info(pid, eid)
2288 else:
2289 tmp['details'] = ''
2290
2291 result[eid] = tmp
2292
2293 # {
2294 # "eid1": {"state": "active", "min_nodes": "2", "nodes": "2", "idle_hours": "0", "details": "..."},
2295 # "eid2": {"state": "swapped", "min_nodes": "1", "nodes": "0", "idle_hours": "0", "details": ""},
2296 # "eid3": {"state": "active", "min_nodes": "2", "nodes": "2", "idle_hours": "0", "details": "..."}
2297 # }
2298 return json.dumps(result)
2299
2300
2301##############################################################
2302# get the status of an experiment of a project
2303# If the experiment is active, also returns the realization details
2304class get_experiment:
2305 def POST(self):
2306 data = web.data()
2307 parsed_json = json.loads(data)
2308 pid = parsed_json['pid']
2309 eid = parsed_json['eid']
2310 logging.info('Getting detailed status for %s/%s', pid, eid)
2311
2312 cmd = "mysql -N -e \"select e.pid, e.eid, e.state, count(r.node_id) as nodes, round(e.minimum_nodes+.1,0) as min_nodes " + \
2313 " from tbdb.experiments as e " + \
2314 " left join tbdb.reserved as r on e.pid=r.pid and e.eid=r.eid " + \
2315 " where e.pid='" + pid + "' and e.eid='" + eid + "' group by e.pid,e.eid\""
2316
2317 out = subprocess.check_output(cmd, shell=True)
2318
2319 # +------------+-----------+---------+-------+-----------+
2320 # | nclproject | demo | active | 2 | 2 |
2321 # +------------+-----------+---------+-------+-----------+
2322
2323 out_list = out.split('\n')
2324 result = {}
2325
2326 if len(out_list) >= 1 and out_list[0]:
2327 expt = out_list[0]
2328 expt_details = expt.split()
2329 eid = expt_details[1]
2330 state = expt_details[2]
2331 tmp = {}
2332 tmp['state'] = state
2333 tmp['nodes'] = expt_details[3]
2334 tmp['min_nodes'] = expt_details[4]
2335 tmp['idle_hours'] = '0' # will implement the idle-hours later
2336
2337 if state == 'active':
2338 tmp['details'] = get_exp_nodes_info(pid, eid)
2339 else:
2340 tmp['details'] = ''
2341
2342 result[eid] = tmp
2343
2344 # {
2345 # "eid1": {"state": "active", "min_nodes": "2", "nodes": "2", "idle_hours": "0", "details": "..."}
2346 # }
2347 return json.dumps(result)
2348
2349
2350####################################################################
2351# get the virtual to physical nodes mapping for an active experiment
2352def get_exp_nodes_info(pid, eid):
2353 logging.info('Getting nodes allocation info for %s/%s', pid, eid)
2354
2355 sql_cmd = "mysql -N -e \"select r.vname,r.node_id,n.type,n.phys_nodeid,o.osname " + \
2356 " from tbdb.reserved as r " + \
2357 " left join tbdb.nodes as n on r.node_id=n.node_id " + \
2358 " left join tbdb.os_info as o on o.osid=n.def_boot_osid " + \
2359 " where r.pid='" + pid + "' and r.eid='" + eid + "' order by vname\""
2360
2361 out = subprocess.check_output(sql_cmd, shell=True)
2362 # logging.debug(out)
2363 # +-------+---------+---------+-------------+---------------------+
2364 # | vname | node_id | type | phys_nodeid | osname |
2365 # +-------+---------+---------+-------------+---------------------+
2366 # | n0 | pc5 | ServerA | pc5 | Ubuntu16.04.3-64-pr |
2367 # | n1 | pc2 | ServerA | pc2 | Ubuntu16.04.3-64-pr |
2368 # +-------+---------+---------+-------------+---------------------+
2369
2370 out_list = out.split('\n')
2371 result = {}
2372 for line in out_list:
2373 if line:
2374 tmp_list = line.split()
2375 type = tmp_list[2]
2376 if type != 'ofedge' and type != 'ofcore' and type != 'ovxctl':
2377 tmp = {}
2378 tmp['nodeId'] = tmp_list[1]
2379 tmp['type'] = tmp_list[2]
2380 tmp['os'] = tmp_list[4]
2381 tmp['qualifiedName'] = tmp_list[0] + '.' + eid + '.' + pid + '.' + getDomain()
2382
2383 result[tmp_list[0]] = tmp
2384
2385 # {'n0': {'qualifiedName': 'n0.akamai-data-test.minsukgroup.ncl.sg', 'os': 'Ubuntu1404-64-STD', 'nodeId': 'pc10b', 'type': 'SystemX'},
2386 # 'n1': {'qualifiedName': 'n1.akamai-data-test.minsukgroup.ncl.sg', 'os': 'Ubuntu1404-64-STD', 'nodeId': 'pc11b', 'type': 'SystemX'}}
2387 # logging.debug(result)
2388 return json.dumps(result)
2389
2390
2391####################################################################
2392# get/add/delete ssh public key
2393class get_pub_keys:
2394 def POST(self):
2395 data = web.data()
2396 parsed_json = json.loads(data)
2397 uid = parsed_json['uid']
2398 logging.info('Getting public keys for %s', uid)
2399
2400 cmd = "mysql -N -e \"select distinct comment, pubkey, stamp, idx from tbdb.users a, tbdb.user_pubkeys b " \
2401 "where a.uid='" + uid + "' and a.uid_idx=b.uid_idx and b.external=1;\""
2402 p = subprocess.check_output(cmd, shell=True)
2403 r = {}
2404
2405 if p:
2406 key_list = p.split('|')
2407 key_list = key_list[0].split('\n')
2408
2409 for keys in key_list:
2410 if keys:
2411 keys_details = keys.split('\t')
2412 keys_details_dict = {}
2413
2414 keys_details_dict['comment'] = keys_details[0]
2415 keys_details_dict['pubkey'] = keys_details[1]
2416 keys_details_dict['stamp'] = keys_details[2]
2417
2418 r[keys_details[3]] = keys_details_dict
2419
2420 return json.dumps(r)
2421
2422
2423class add_pub_key:
2424 def POST(self):
2425 data = web.data()
2426 parsed_json = json.loads(data)
2427
2428 uid = parsed_json['uid']
2429 pubkey = parsed_json['pubkey']
2430 passwd = parsed_json['passwd']
2431 logging.info('Adding public key for %s', uid)
2432
2433 filename = "/tmp/" + str(time.time()) + ".pub"
2434 file = open(filename, "w")
2435 file.write(pubkey)
2436 file.close()
2437
2438 deter_uri = getDeterURI()
2439 cookie_file = uid + '.txt'
2440
2441 cmd = "curl -L --silent -k -b " + cookie_file + \
2442 " -F \"MAX_FILE_SIZE=4096" + "\"" + \
2443 " -F \"usr_keyfile=@" + filename + "\"" + \
2444 " -F \"formfields[password]=" + passwd + "\"" + \
2445 " -F \"submit=Submit\" \"" + deter_uri + "showpubkeys.php?user=" + uid + "\" -v"
2446
2447 out = subprocess.check_output(cmd, shell=True)
2448 err = {}
2449
2450 if out.find("No such file") > -1:
2451 err['PubKey File'] = "No such file"
2452 elif out.find("Invalid characters") > -1:
2453 err['PubKey File'] = "Invalid characters"
2454
2455 if out.find("Must supply a verification password") > -1:
2456 err['Password'] = "Must supply a verification password"
2457 elif out.find("Incorrect password") > -1:
2458 err['Password'] = "Incorrect password"
2459
2460 if out.find("Please supply keyfile") > -1:
2461 err['Missing Args'] = "Please supply keyfile"
2462
2463 if out.find("Could not be parsed. Is it a public key?") > -1:
2464 err['Pubkey Format'] = "Could not be parsed. Is it a public key?"
2465
2466 if err:
2467 logging.warning('Add public key for %s failed: %s', uid, out)
2468 else:
2469 logging.info('Add public key for %s succeeded', uid)
2470
2471 return json.dumps(err)
2472
2473
2474class delete_pub_key:
2475 def DELETE(self):
2476 data = web.data()
2477 parsed_json = json.loads(data)
2478
2479 uid = parsed_json['uid']
2480 kid = parsed_json['kid']
2481 logging.info('Deleting public key %s for %s', kid, uid)
2482
2483 deter_uri = getDeterURI()
2484 cookie_file = uid + '.txt'
2485
2486 cmd = "curl -L --silent -k -b " + cookie_file + \
2487 " -F \"confirmed=Confirm\" \"" + deter_uri + "deletepubkey?user=" + uid + "&key=" + kid + "\" -v"
2488
2489 out = subprocess.check_output(cmd, shell=True)
2490
2491 return out
2492
2493
2494###########################################################################################
2495################################### GET RESERVATION #######################################
2496###########################################################################################
2497"""
2498output is a json dump of a dictionary in the following format
2499{"status": "OK", "reserved": ["pc1", "pc4", "pc2"], "in_use": [["pc4", "ncltest01", "vnctest"], ["pc2", "testbed-ncl", "thales-poc"], ["pc1", "testbed-ncl", "test"]]}
2500"""
2501
2502
2503class get_reservation:
2504 def POST(self):
2505 data = web.data()
2506 parsed_json = json.loads(data)
2507 pid = parsed_json['pid']
2508
2509 output = {}
2510
2511 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2512 cursor = db.cursor()
2513
2514 # get reserved nodes from tbdb.nodes
2515 # get in_use nodes from tbdb.reserved
2516
2517 try:
2518 cursor.execute("select node_id from tbdb.nodes where reserved_pid='" + pid + "'")
2519 results = cursor.fetchall()
2520 reserved_list = [row[0] for row in results]
2521
2522 in_use_list = []
2523
2524 if reserved_list:
2525 # convert reserved list into a string for sql statement
2526 # e.g. ['A','B'] -> ('A','B')
2527 reserved_list_str = "('" + "','".join(reserved_list) + "')"
2528 query = 'SELECT node_id, pid, eid FROM tbdb.reserved WHERE node_id IN ' + reserved_list_str
2529 cursor.execute(query)
2530 in_use_list = cursor.fetchall()
2531
2532 output['status'] = 'OK'
2533 output['reserved'] = reserved_list
2534 output['in_use'] = in_use_list
2535 logging.info("Get reservation for %s succeeded", pid)
2536 except:
2537 output['status'] = 'FAIL'
2538 output['reserved'] = []
2539 output['in_use'] = []
2540 logging.warning("Get reservation for %s failed", pid)
2541
2542 db.close()
2543
2544 return json.dumps(output)
2545
2546
2547###########################################################################################
2548################################### RELEASE NODES #########################################
2549###########################################################################################
2550"""
2551release a number of nodes from project
2552pid: project name
2553numNodes: number of nodes to release, -1 means releasing all the reserved nodes
2554return all node_ids that are released
2555output is a json dump of a dictionary in the following format
2556{ status : 'OK/FAIL', 'released' : [node_id_list] }
2557"""
2558
2559
2560class release_nodes:
2561 def POST(self):
2562 data = web.data()
2563 parsed_json = json.loads(data)
2564 pid = parsed_json['pid']
2565 num_of_nodes_to_release = parsed_json['numNodes']
2566
2567 output = {}
2568 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2569 cursor = db.cursor()
2570
2571 try:
2572 # get all reserved nodes currently
2573 cursor.execute("select node_id from tbdb.nodes where reserved_pid='" + pid + "'")
2574 results = cursor.fetchall()
2575 reserved_list = [row[0] for row in results]
2576
2577 # get all nodes that are currently used by this team
2578 cursor.execute("select node_id from tbdb.reserved where pid='" + pid + "'")
2579 results = cursor.fetchall()
2580 in_use_list = [row[0] for row in results]
2581
2582 # compute actual number of nodes to release
2583 num_of_nodes_reserved = len(reserved_list)
2584 if int(num_of_nodes_to_release) < 0 or int(num_of_nodes_to_release) > num_of_nodes_reserved:
2585 actual_num_of_nodes_to_release = num_of_nodes_reserved
2586 else:
2587 actual_num_of_nodes_to_release = num_of_nodes_to_release
2588
2589 reserved_not_in_use_list = [node for node in reserved_list if node not in in_use_list]
2590 reserved_in_use_list = [node for node in reserved_list if node in in_use_list]
2591 reserved_list = reserved_not_in_use_list + reserved_in_use_list
2592 release_list = reserved_list[0:actual_num_of_nodes_to_release]
2593
2594 if release_list:
2595 # convert release list into a string for sql statement
2596 # e.g. ['A','B'] -> ('A','B')
2597 release_list_str = "('" + "','".join(release_list) + "')"
2598
2599 cursor.execute(
2600 "update tbdb.nodes set reserved_pid = NULL where node_id IN " + release_list_str)
2601
2602 output['status'] = 'OK'
2603 output['released'] = release_list
2604 logging.info("Release nodes for %s succeeded", pid)
2605 except:
2606 output['status'] = 'FAIL'
2607 output['released'] = []
2608 logging.warning("Release nodes for %s failed", pid)
2609
2610 db.close()
2611
2612 return json.dumps(output)
2613
2614
2615###########################################################################################
2616################################### RESERVE NODES #########################################
2617###########################################################################################
2618"""
2619reserve nodes for a project
2620pid: project name
2621numNodes: number of nodes to reserve
2622machineType: particular type of machine to reserve
2623
2624output is a json dump of a dictionary in the following format
2625{ status : 'OK/FAIL', 'message' : 'XXXX', 'reserved' : [node_id_list] }
2626"""
2627
2628
2629class reserve_nodes:
2630 def POST(self):
2631 data = web.data()
2632 parsed_json = json.loads(data)
2633 pid = parsed_json['pid']
2634 num_of_nodes = parsed_json['numNodes']
2635 machine_type = parsed_json['machineType']
2636
2637 output = {}
2638
2639 # sanitization
2640 if not num_of_nodes or int(num_of_nodes) <= 0:
2641 output['status'] = 'FAIL'
2642 output['message'] = 'Number of nodes not specified'
2643 output['reserved'] = []
2644 logging.warning("Reserve %s nodes for %s failed: no of nodes not specified", num_of_nodes, pid)
2645 return json.dumps(output)
2646
2647 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2648 cursor = db.cursor()
2649
2650 try:
2651 # retrieve list of nodes that are currently used by this project
2652 # need to filter out node_id like 'ofport10d' which are virtual nodes for SDN
2653 if machine_type:
2654 sql_stmt1 = "select r.node_id from tbdb.reserved as r left join tbdb.nodes as n on r.node_id=n.node_id " + \
2655 "where r.pid = '" + pid + "' and n.type = '" + machine_type + "' and n.node_id like 'pc%'"
2656 else:
2657 sql_stmt1 = "select node_id from tbdb.reserved where pid ='" + pid + "' and node_id like 'pc%'"
2658
2659 cursor.execute(sql_stmt1)
2660 in_use_list = [row[0] for row in cursor.fetchall()]
2661
2662 # compute the list of nodes that are in use but have not been reserved for any project
2663 in_use_not_reserved_list = []
2664
2665 if in_use_list:
2666 in_use_list_str = "('" + "','".join(in_use_list) + "')"
2667 sql_stmt2 = "select node_id from tbdb.nodes where reserved_pid is NOT NULL and node_id in " + in_use_list_str
2668 cursor.execute(sql_stmt2)
2669 in_use_reserved_list = [row[0] for row in cursor.fetchall()]
2670 in_use_not_reserved_list = [node for node in in_use_list if node not in in_use_reserved_list]
2671
2672 # retrieve the list of free nodes
2673 sql_stmt3 = "select n.node_id from tbdb.nodes as n left join tbdb.reserved as r on r.node_id=n.node_id " + \
2674 "where role='testnode' " + \
2675 "and n.node_id like 'pc%' " + \
2676 "and (n.eventstate='POWEROFF' or n.eventstate='ISUP' or n.eventstate='PXEWAIT' or n.eventstate='ALWAYSUP') " + \
2677 "and r.pid is NULL and n.reserved_pid is NULL"
2678
2679 if machine_type:
2680 sql_stmt3 += " and n.type = '" + machine_type + "'"
2681
2682 cursor.execute(sql_stmt3)
2683 free_nodes_list = [row[0] for row in cursor.fetchall()]
2684
2685 # this is the list of nodes that can be reserved for this project
2686 reservable_list = in_use_not_reserved_list + free_nodes_list
2687
2688 if int(num_of_nodes) > len(reservable_list):
2689 output['status'] = 'FAIL'
2690 output['message'] = 'Insufficient number of nodes: only ' + str(
2691 len(reservable_list)) + ' nodes available'
2692 output['reserved'] = []
2693 logging.warning("Reserve %s nodes for %s failed: insufficient no. of nodes", num_of_nodes, pid)
2694 else:
2695 to_reserve_list = reservable_list[0:num_of_nodes]
2696 to_reserve_list_str = "('" + "','".join(to_reserve_list) + "')"
2697 cursor.execute(
2698 "update tbdb.nodes set reserved_pid = '" + pid + "' where node_id in " + to_reserve_list_str)
2699 num_of_rows_changed = cursor.rowcount
2700
2701 if num_of_rows_changed != len(to_reserve_list):
2702 output['status'] = 'FAIL'
2703 output['message'] = 'Only ' + str(num_of_rows_changed) + '/' + str(
2704 len(to_reserve_list)) + ' nodes were successfully reserved'
2705 output['reserved'] = []
2706 logging.warning("Reserve %s nodes for %s partially failed: only %s nodes reserved", num_of_nodes,
2707 pid, num_of_rows_changed)
2708 else:
2709 output['status'] = 'OK'
2710 output['message'] = ''
2711 output['reserved'] = to_reserve_list
2712 logging.info("Reserve %s nodes for %s succeeded", num_of_nodes, pid)
2713
2714 except:
2715 output['status'] = 'FAIL'
2716 output['message'] = 'Failed to reserve nodes for ' + pid
2717 output['reserved'] = []
2718 logging.warning("Reserve %s nodes for %s failed: error in SQL execution", num_of_nodes, pid)
2719
2720 db.close()
2721
2722 return json.dumps(output)
2723
2724########################################################
2725## get all the experiment statistics for a project
2726########################################################
2727class show_stats_by_project:
2728 def POST(self):
2729 data = web.data()
2730 parsed_json = json.loads(data)
2731 pid = parsed_json['pid']
2732 r = {}
2733
2734 logging.info('Getting experiment stats for %s', pid)
2735
2736 cmd = "select t.exptidx, t.start_time, t.end_time, t.action, r.pnodes " + \
2737 "from tbdb.testbed_stats as t " + \
2738 "left join tbdb.experiment_stats as s on s.exptidx=t.exptidx " + \
2739 "left join tbdb.experiment_resources as r on r.idx=t.rsrcidx " + \
2740 "where s.pid='" + pid + "' and t.exitcode=0 " + \
2741 "order by t.start_time asc"
2742
2743 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2744 cur = db.cursor()
2745 cur.execute(cmd)
2746
2747 for counter, stats_details in enumerate(cur.fetchall()):
2748 stats_details_dict = {}
2749 stats_details_dict['exptidx'],\
2750 stats_details_dict['start_time'],\
2751 stats_details_dict['end_time'],\
2752 stats_details_dict['action'],\
2753 stats_details_dict['pnodes'] = stats_details
2754 r[counter] = stats_details_dict
2755
2756 db.close()
2757
2758 return json.dumps(r, default=str)
2759
2760
2761###################################################
2762## check if a class project has been set up or not;
2763## if not, set it up as needed
2764###################################################
2765"""
2766pid: the project to set up
2767uid: uid of the project leader
2768"""
2769def set_up_class_if_needed(pid, uid):
2770 logging.info('Checking class setup for %s by user %s', pid, uid)
2771 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2772 cursor = db.cursor()
2773
2774 try:
2775 cursor.execute("SELECT * FROM class_schedule WHERE pid='" + pid + "'")
2776 rows = cursor.fetchall()
2777 if not rows: # no schedule found, need to set up the class
2778 logging.info('Class %s has not been set up. Setting it up now...', pid)
2779 cookie_file = uid + '.txt'
2780 deter_uri = getDeterURI()
2781
2782 # hardcoded, class end date = class start/created date + 1 year
2783 today = datetime.datetime.strftime(datetime.datetime.now(),'%Y-%m-%d')
2784 class_end = datetime.datetime.strftime(datetime.datetime.now()+datetime.timedelta(365),'%Y-%m-%d')
2785
2786 cmd = "curl -L -s -k -b " + cookie_file + \
2787 " -d \"semesterends=" + class_end + \
2788 "&students=100" + \
2789 "&start[1]=" + today + \
2790 "&due[1]=" + class_end + \
2791 "&perstudent[1]=1&limit[1]=100\""+ \
2792 " \'" + deter_uri + "manage_class.php?pid=" + pid + "&action=finishsetup" + "\'"
2793
2794 out = subprocess.check_output(cmd, shell=True)
2795
2796 if ('manage_class.php?pid=' + pid + '&action=manage') in out: # Deter redirect to manage students page
2797 logging.info('Done')
2798 result = 'ok'
2799 else:
2800 logging.warning('There is an error in setting up class %s:\n %s', pid, out)
2801 result = 'fail'
2802 else:
2803 logging.info('Class %s has already been set up', pid)
2804 result = 'ok'
2805 except:
2806 logging.warning('There is an error in checking class schedule for %s', pid)
2807 result = 'fail'
2808
2809 db.close()
2810
2811 return result
2812
2813
2814###########################################################################################
2815## add class members by emails
2816###########################################################################################
2817
2818"""
2819add class members by emails
2820pid: project name
2821uid: project leader
2822emails: a string in the format 'hello01@gmail.com\r\nhello02@gmail.com\r\nhello03@gmail.com'
2823
2824output is a json dump of a dictionary in the following format
2825{'email1': 'uid1', 'email2': 'uid2'} or
2826{'error': 'error message'}
2827
2828"""
2829class add_students_by_email:
2830 def POST(self):
2831 data = web.data()
2832 parsed_json = json.loads(data)
2833 pid = parsed_json['pid']
2834 uid = parsed_json['uid']
2835 emails = parsed_json['emails']
2836
2837 deter_uri = getDeterURI()
2838 cookie_file = uid + '.txt'
2839 result = {}
2840
2841 logging.debug('Adding students %s to project %s by user %s', emails, pid, uid)
2842
2843 # first check if class schedule has been set up
2844 if set_up_class_if_needed(pid, uid) == 'fail':
2845 result['error'] = 'Class schedule is not found or cannot be set up'
2846 return json.dumps(result)
2847
2848 # ask Deter to add new class members
2849 cmd = "curl -L -s -k -b " + cookie_file + \
2850 " -d \"students=" + \
2851 urllib.quote_plus(emails) + "&trust=localroot\"" + \
2852 " \'" + deter_uri + "manage_class.php?pid=" + \
2853 pid + "&action=assign" + "\'"
2854
2855 out = subprocess.check_output(cmd, shell=True)
2856
2857 if 'Your accounts are being created' in out:
2858 # update chpasswd_expires for the new users
2859 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2860 cursor = db.cursor()
2861 try:
2862 email_list = emails.split('\r\n')
2863 for email in email_list:
2864 if email:
2865 cursor.execute("SELECT uid FROM email_aliases WHERE usr_email='" + email + "'")
2866 results = cursor.fetchall()
2867 if len(results) < 1:
2868 logging.warning('Error in adding members to project %s by user %s: Table email_aliases is not populated', pid, uid)
2869 result['error'] = "Table email_aliases is not populated"
2870 break
2871 else:
2872 generated_uid = results[0][0]
2873 # change chpasswd_expires to max int(10)
2874 cursor.execute("UPDATE users SET chpasswd_expires=4294967295 WHERE uid='" + generated_uid +"'")
2875 if cursor.rowcount != 1:
2876 logging.warning('Error in adding members to project %s by user %s: Failed to update users.chpasswd_expires', pid, uid)
2877 result['error'] = "Failed to update table users"
2878 break
2879 else:
2880 logging.info("Added member %s to project %s", email, pid)
2881 result[email] = generated_uid
2882 except:
2883 logging.warning('Error in adding members to project %s by user %s: SQL execution failed', pid, uid)
2884 result['error'] = "Failed to execute SQL"
2885
2886 db.close()
2887
2888 elif "Invalid address" in out:
2889 logging.warning('Error in adding members to project %s by user %s: Invalid address', pid, uid)
2890 result['error'] = "Invalid email address"
2891 else:
2892 logging.warning('Error in adding members to project %s by user %s: %s', pid, uid, out)
2893 result['error'] = "Unknown error. Please contact NCL Support."
2894
2895 return json.dumps(result)
2896
2897###########################################################################################
2898## reset password for new student member
2899class change_password_student:
2900 def POST(self):
2901 data = web.data()
2902 parsed_json = json.loads(data)
2903 uid = parsed_json['uid']
2904 first_name = parsed_json['firstName']
2905 last_name = parsed_json['lastName']
2906 phone = parsed_json['phone']
2907 password = parsed_json['password']
2908 logging.info('Resetting password for new class member %s', uid)
2909
2910 deter_uri = getDeterURI()
2911 cookie_file = uid + '.txt'
2912 result = {}
2913
2914 # get key and uid_idx
2915 sql_cmd = "mysql -N -e \"select uid_idx, chpasswd_key from tbdb.users where uid='" + uid + "'\""
2916 sql_cmd_out = subprocess.check_output(sql_cmd, shell=True)
2917 out_list = sql_cmd_out.split('\n')
2918
2919 if len(out_list) == 0:
2920 logging.warning('Key and uid_idx not found in table users')
2921 result['error'] = "Key and uid_idx not found in table users"
2922 else:
2923 columns = out_list[0].split()
2924 uid_idx = columns[0]
2925 key = columns[1]
2926
2927 escaped_passwd = urllib.quote_plus(password)
2928
2929 cmd = "curl -L -s -k -b " + cookie_file + \
2930 " --data \"usr_name=" + first_name + "+" + last_name +\
2931 "&usr_phone=" + phone + \
2932 "&password1=" + escaped_passwd + \
2933 "&password2=" + escaped_passwd + \
2934 "&reset=Reset+Password\"" + \
2935 " \'" + deter_uri + "chpasswdstudent.php?user=" + \
2936 uid_idx + "&key=" + key + "&simple=0" + "\'"
2937
2938 out = subprocess.check_output(cmd, shell=True)
2939
2940 if "Your password has been changed" in out:
2941 logging.info('Done')
2942 elif "You must enter a valid first and last name" in out:
2943 logging.warning('First name or last name is not valid')
2944 result['error'] = "First name or last name is not valid"
2945 elif "You must supply a password" in out:
2946 logging.warning('Password is not supplied')
2947 result['error'] = "Password is not supplied"
2948 elif "Invalid Password" in out:
2949 logging.warning('Password is not valid')
2950 result['error'] = "Password is not valid"
2951 else:
2952 logging.warning('Error:\n%s', out)
2953 result['error'] = "Unknown error. Please contact NCL Support."
2954
2955 return json.dumps(result)
2956
2957
2958########################################################################
2959## show all the projects that have reserved nodes
2960########################################################################
2961class show_projects_with_reservation:
2962 def GET(self):
2963 logging.info('Getting list of projects with reservation')
2964 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2965 cursor = db.cursor()
2966 cursor.execute("select distinct(reserved_pid) from nodes where reserved_pid is not null and node_id like 'pc%'")
2967 result = cursor.fetchall()
2968 db.close()
2969 return json.dumps(result)
2970
2971
2972########################################################################
2973## get nodes that are currently used by an experiment
2974########################################################################
2975class get_node_list:
2976 def POST(self):
2977 data = web.data()
2978 parsed_json = json.loads(data)
2979 pid = parsed_json['pid']
2980 eid = parsed_json['eid']
2981 logging.info('Getting list of nodes for %s/%s', pid, eid)
2982 db = MySQLdb.connect(host=getDBURI(), db=getDBName())
2983 cursor = db.cursor()
2984 cursor.execute("select node_id from reserved where pid='" + pid + "' and eid='" + eid + "' and node_id like 'pc%'")
2985 nodes = cursor.fetchall()
2986 db.close()
2987
2988 result = ()
2989 if len(nodes) <= 0:
2990 logging.warning('There is no node allocated for %s/%s', pid, eid)
2991 else:
2992 for node in nodes:
2993 if node:
2994 result += node
2995 logging.info("Nodes allocated to %s/%s: %s", pid, eid, result)
2996
2997 return result
2998
2999
3000if __name__ == "__main__":
3001 logging.basicConfig(filename='log_adapter.txt', level=logging.DEBUG,
3002 format='%(asctime)s - %(levelname)s - %(message)s')
3003 app.run()
3004