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