1 from datetime import datetime
3 from sfa.util.sfalogging import logger
5 from sfa.storage.alchemy import dbsession
6 from sqlalchemy.orm import joinedload
7 from sfa.storage.model import RegRecord, RegUser, RegSlice, RegKey
8 # from sfa.iotlab.iotlabpostgres import iotlab_dbsession, IotlabXP
9 from sfa.iotlab.iotlabpostgres import IotlabDB, IotlabXP
10 from sfa.iotlab.OARrestapi import OARrestapi
11 from sfa.iotlab.LDAPapi import LDAPapi
13 from sfa.util.xrn import Xrn, hrn_to_urn, get_authority
15 from sfa.trust.certificate import Keypair, convert_public_key
16 from sfa.trust.gid import create_uuid
17 from sfa.trust.hierarchy import Hierarchy
19 from sfa.iotlab.iotlabaggregate import iotlab_xrn_object
21 class IotlabTestbedAPI():
22 """ Class enabled to use LDAP and OAR api calls. """
24 def __init__(self, config):
25 """Creates an instance of OARrestapi and LDAPapi which will be used to
26 issue calls to OAR or LDAP methods.
27 Set the time format and the testbed granularity used for OAR
28 reservation and leases.
30 :param config: configuration object from sfa.util.config
31 :type config: Config object
33 self.iotlab_db = IotlabDB(config)
34 self.oar = OARrestapi()
36 self.time_format = "%Y-%m-%d %H:%M:%S"
37 self.root_auth = config.SFA_REGISTRY_ROOT_AUTH
38 self.grain = 1 # 10 mins lease minimum, 1 sec granularity
39 #import logging, logging.handlers
40 #from sfa.util.sfalogging import _SfaLogger
41 #sql_logger = _SfaLogger(loggername = 'sqlalchemy.engine', \
47 def GetMinExperimentDurationInSec():
51 def GetPeers (peer_filter=None ):
52 """ Gathers registered authorities in SFA DB and looks for specific peer
53 if peer_filter is specified.
54 :param peer_filter: name of the site authority looked for.
55 :type peer_filter: string
56 :returns: list of records.
61 existing_hrns_by_types = {}
62 logger.debug("IOTLABDRIVER \tGetPeers peer_filter %s, \
64 all_records = dbsession.query(RegRecord).filter(RegRecord.type.like('%authority%')).all()
66 for record in all_records:
67 existing_records[(record.hrn, record.type)] = record
68 if record.type not in existing_hrns_by_types:
69 existing_hrns_by_types[record.type] = [record.hrn]
71 existing_hrns_by_types[record.type].append(record.hrn)
74 logger.debug("IOTLABDRIVER \tGetPeer\texisting_hrns_by_types %s "\
75 %( existing_hrns_by_types))
80 records_list.append(existing_records[(peer_filter,'authority')])
82 for hrn in existing_hrns_by_types['authority']:
83 records_list.append(existing_records[(hrn,'authority')])
85 logger.debug("IOTLABDRIVER \tGetPeer \trecords_list %s " \
91 return_records = records_list
92 logger.debug("IOTLABDRIVER \tGetPeer return_records %s " \
98 #TODO : Handling OR request in make_ldap_filters_from_records
99 #instead of the for loop
100 #over the records' list
101 def GetPersons(self, person_filter=None):
103 Get the enabled users and their properties from Iotlab LDAP.
104 If a filter is specified, looks for the user whose properties match
105 the filter, otherwise returns the whole enabled users'list.
106 :param person_filter: Must be a list of dictionnaries
107 with users properties when not set to None.
108 :param person_filter: list of dict
109 :returns:Returns a list of users whose accounts are enabled
111 :rtype: list of dicts
114 logger.debug("IOTLABDRIVER \tGetPersons person_filter %s" \
117 if person_filter and isinstance(person_filter, list):
118 #If we are looking for a list of users (list of dict records)
119 #Usually the list contains only one user record
120 for searched_attributes in person_filter:
122 #Get only enabled user accounts in iotlab LDAP :
123 #add a filter for make_ldap_filters_from_record
124 person = self.ldap.LdapFindUser(searched_attributes, \
125 is_user_enabled=True)
126 #If a person was found, append it to the list
128 person_list.append(person)
130 #If the list is empty, return None
131 if len(person_list) is 0:
135 #Get only enabled user accounts in iotlab LDAP :
136 #add a filter for make_ldap_filters_from_record
137 person_list = self.ldap.LdapFindUser(is_user_enabled=True)
142 #def GetTimezone(self):
143 #""" Returns the OAR server time and timezone.
144 #Unused SA 30/05/13"""
145 #server_timestamp, server_tz = self.oar.parser.\
146 #SendRequest("GET_timezone")
147 #return server_timestamp, server_tz
149 def DeleteJobs(self, job_id, username):
152 Deletes the job with the specified job_id and username on OAR by
153 posting a delete request to OAR.
155 :param job_id: job id in OAR.
156 :param username: user's iotlab login in LDAP.
157 :type job_id: integer
158 :type username: string
160 :returns: dictionary with the job id and if delete has been successful
165 logger.debug("IOTLABDRIVER \tDeleteJobs jobid %s username %s "\
167 if not job_id or job_id is -1:
171 reqdict['method'] = "delete"
172 reqdict['strval'] = str(job_id)
175 answer = self.oar.POSTRequestToOARRestAPI('DELETE_jobs_id', \
177 if answer['status'] == 'Delete request registered':
178 ret = {job_id : True }
180 ret = {job_id :False }
181 logger.debug("IOTLABDRIVER \tDeleteJobs jobid %s \r\n answer %s \
182 username %s" %(job_id, answer, username))
187 ##TODO : Unused GetJobsId ? SA 05/07/12
188 #def GetJobsId(self, job_id, username = None ):
190 #Details about a specific job.
191 #Includes details about submission time, jot type, state, events,
192 #owner, assigned ressources, walltime etc...
196 #node_list_k = 'assigned_network_address'
197 ##Get job info from OAR
198 #job_info = self.oar.parser.SendRequest(req, job_id, username)
200 #logger.debug("IOTLABDRIVER \t GetJobsId %s " %(job_info))
202 #if job_info['state'] == 'Terminated':
203 #logger.debug("IOTLABDRIVER \t GetJobsId job %s TERMINATED"\
206 #if job_info['state'] == 'Error':
207 #logger.debug("IOTLABDRIVER \t GetJobsId ERROR message %s "\
212 #logger.error("IOTLABDRIVER \tGetJobsId KeyError")
215 #parsed_job_info = self.get_info_on_reserved_nodes(job_info, \
217 ##Replaces the previous entry
218 ##"assigned_network_address" / "reserved_resources"
220 #job_info.update({'node_ids':parsed_job_info[node_list_k]})
221 #del job_info[node_list_k]
222 #logger.debug(" \r\nIOTLABDRIVER \t GetJobsId job_info %s " %(job_info))
226 def GetJobsResources(self, job_id, username = None):
227 """ Gets the list of nodes associated with the job_id and username
229 Transforms the iotlab hostnames to the corresponding
231 Rertuns dict key :'node_ids' , value : hostnames list
232 :param username: user's LDAP login
233 :paran job_id: job's OAR identifier.
234 :type username: string
235 :type job_id: integer
237 :returns: dicionary with nodes' hostnames belonging to the job.
241 req = "GET_jobs_id_resources"
244 #Get job resources list from OAR
245 node_id_list = self.oar.parser.SendRequest(req, job_id, username)
246 logger.debug("IOTLABDRIVER \t GetJobsResources %s " %(node_id_list))
249 self.__get_hostnames_from_oar_node_ids(node_id_list)
252 #Replaces the previous entry "assigned_network_address" /
253 #"reserved_resources" with "node_ids"
254 job_info = {'node_ids': hostname_list}
259 #def get_info_on_reserved_nodes(self, job_info, node_list_name):
261 #..warning:unused SA 23/05/13
263 ##Get the list of the testbed nodes records and make a
264 ##dictionnary keyed on the hostname out of it
265 #node_list_dict = self.GetNodes()
266 ##node_hostname_list = []
267 #node_hostname_list = [node['hostname'] for node in node_list_dict]
268 ##for node in node_list_dict:
269 ##node_hostname_list.append(node['hostname'])
270 #node_dict = dict(zip(node_hostname_list, node_list_dict))
272 #reserved_node_hostname_list = []
273 #for index in range(len(job_info[node_list_name])):
274 ##job_info[node_list_name][k] =
275 #reserved_node_hostname_list[index] = \
276 #node_dict[job_info[node_list_name][index]]['hostname']
278 #logger.debug("IOTLABDRIVER \t get_info_on_reserved_nodes \
279 #reserved_node_hostname_list %s" \
280 #%(reserved_node_hostname_list))
282 #logger.error("IOTLABDRIVER \t get_info_on_reserved_nodes KEYERROR " )
284 #return reserved_node_hostname_list
286 def GetNodesCurrentlyInUse(self):
287 """Returns a list of all the nodes already involved in an oar running
289 :rtype: list of nodes hostnames.
291 return self.oar.parser.SendRequest("GET_running_jobs")
293 def __get_hostnames_from_oar_node_ids(self, resource_id_list ):
294 """Get the hostnames of the nodes from their OAR identifiers.
295 Get the list of nodes dict using GetNodes and find the hostname
296 associated with the identifier.
297 :param resource_id_list: list of nodes identifiers
298 :returns: list of node hostnames.
300 full_nodes_dict_list = self.GetNodes()
301 #Put the full node list into a dictionary keyed by oar node id
302 oar_id_node_dict = {}
303 for node in full_nodes_dict_list:
304 oar_id_node_dict[node['oar_id']] = node
307 for resource_id in resource_id_list:
308 #Because jobs requested "asap" do not have defined resources
309 if resource_id is not "Undefined":
310 hostname_list.append(\
311 oar_id_node_dict[resource_id]['hostname'])
313 #hostname_list.append(oar_id_node_dict[resource_id]['hostname'])
316 def GetReservedNodes(self, username = None):
317 """ Get list of leases. Get the leases for the username if specified,
318 otherwise get all the leases. Finds the nodes hostnames for each
320 :param username: user's LDAP login
321 :type username: string
322 :returns: list of reservations dict
326 #Get the nodes in use and the reserved nodes
327 reservation_dict_list = \
328 self.oar.parser.SendRequest("GET_reserved_nodes", \
332 for resa in reservation_dict_list:
333 logger.debug ("GetReservedNodes resa %s"%(resa))
334 #dict list of hostnames and their site
335 resa['reserved_nodes'] = \
336 self.__get_hostnames_from_oar_node_ids(resa['resource_ids'])
338 #del resa['resource_ids']
339 return reservation_dict_list
341 def GetNodes(self, node_filter_dict = None, return_fields_list = None):
344 Make a list of iotlab nodes and their properties from information
345 given by OAR. Search for specific nodes if some filters are
346 specified. Nodes properties returned if no return_fields_list given:
347 'hrn','archi','mobile','hostname','site','boot_state','node_id',
348 'radio','posx','posy','oar_id','posz'.
350 :param node_filter_dict: dictionnary of lists with node properties
351 :type node_filter_dict: dict
352 :param return_fields_list: list of specific fields the user wants to be
354 :type return_fields_list: list
355 :returns: list of dictionaries with node properties
359 node_dict_by_id = self.oar.parser.SendRequest("GET_resources_full")
360 node_dict_list = node_dict_by_id.values()
361 logger.debug (" IOTLABDRIVER GetNodes node_filter_dict %s \
362 return_fields_list %s "%(node_filter_dict, return_fields_list))
363 #No filtering needed return the list directly
364 if not (node_filter_dict or return_fields_list):
365 return node_dict_list
367 return_node_list = []
369 for filter_key in node_filter_dict:
371 #Filter the node_dict_list by each value contained in the
372 #list node_filter_dict[filter_key]
373 for value in node_filter_dict[filter_key]:
374 for node in node_dict_list:
375 if node[filter_key] == value:
376 if return_fields_list :
378 for k in return_fields_list:
380 return_node_list.append(tmp)
382 return_node_list.append(node)
384 logger.log_exc("GetNodes KeyError")
388 return return_node_list
393 def AddSlice(slice_record, user_record):
396 Add slice to the local iotlab sfa tables if the slice comes
397 from a federated site and is not yet in the iotlab sfa DB,
398 although the user has already a LDAP login.
399 Called by verify_slice during lease/sliver creation.
401 :param slice_record: record of slice, must contain hrn, gid, slice_id
402 and authority of the slice.
403 :type slice_record: dictionary
404 :param user_record: record of the user
405 :type user_record: RegUser
409 sfa_record = RegSlice(hrn=slice_record['hrn'],
410 gid=slice_record['gid'],
411 pointer=slice_record['slice_id'],
412 authority=slice_record['authority'])
413 logger.debug("IOTLABDRIVER.PY AddSlice sfa_record %s user_record %s"
414 % (sfa_record, user_record))
415 sfa_record.just_created()
416 dbsession.add(sfa_record)
418 #Update the reg-researcher dependance table
419 sfa_record.reg_researchers = [user_record]
425 def GetSites(self, site_filter_name_list = None, return_fields_list = None):
426 site_dict = self.oar.parser.SendRequest("GET_sites")
427 #site_dict : dict where the key is the sit ename
428 return_site_list = []
429 if not ( site_filter_name_list or return_fields_list):
430 return_site_list = site_dict.values()
431 return return_site_list
433 for site_filter_name in site_filter_name_list:
434 if site_filter_name in site_dict:
435 if return_fields_list:
436 for field in return_fields_list:
439 tmp[field] = site_dict[site_filter_name][field]
441 logger.error("GetSites KeyError %s "%(field))
443 return_site_list.append(tmp)
445 return_site_list.append( site_dict[site_filter_name])
448 return return_site_list
454 #TODO : Check rights to delete person
455 def DeletePerson(self, person_record):
456 """Disable an existing account in iotlab LDAP.
458 Users and techs can only delete themselves. PIs can only
459 delete themselves and other non-PIs at their sites.
460 ins can delete anyone.
462 :param person_record: user's record
463 :type person_record: dict
464 :returns: True if successful, False otherwise.
467 .. todo:: CHECK THAT ONLY THE USER OR ADMIN CAN DEL HIMSELF.
469 #Disable user account in iotlab LDAP
470 ret = self.ldap.LdapMarkUserAsDeleted(person_record)
471 logger.warning("IOTLABDRIVER DeletePerson %s " % (person_record))
474 def DeleteSlice(self, slice_record):
475 """Deletes the specified slice and kills the jobs associated with
476 the slice if any, using DeleteSliceFromNodes.
478 :param slice_record: record of the slice, must contain oar_job_id, user
479 :type slice_record: dict
480 :returns: True if all the jobs in the slice have been deleted,
481 or the list of jobs that could not be deleted otherwise.
482 :rtype: list or boolean
484 .. seealso:: DeleteSliceFromNodes
487 ret = self.DeleteSliceFromNodes(slice_record)
490 if False in ret[job_id]:
491 if delete_failed is None:
493 delete_failed.append(job_id)
495 logger.info("IOTLABDRIVER DeleteSlice %s answer %s"%(slice_record, \
497 return delete_failed or True
500 def __add_person_to_db(user_dict):
502 Add a federated user straight to db when the user issues a lease
503 request with iotlab nodes and that he has not registered with iotlab
504 yet (that is he does not have a LDAP entry yet).
505 Uses parts of the routines in SlabImport when importing user from LDAP.
506 Called by AddPerson, right after LdapAddUser.
507 :param user_dict: Must contain email, hrn and pkey to get a GID
508 and be added to the SFA db.
509 :type user_dict: dict
513 dbsession.query(RegUser).filter_by(email = user_dict['email']).first()
515 if not check_if_exists:
516 logger.debug("__add_person_to_db \t Adding %s \r\n \r\n \
518 hrn = user_dict['hrn']
519 person_urn = hrn_to_urn(hrn, 'user')
520 pubkey = user_dict['pkey']
522 pkey = convert_public_key(pubkey)
524 #key not good. create another pkey
525 logger.warn('__add_person_to_db: unable to convert public \
527 pkey = Keypair(create=True)
530 if pubkey is not None and pkey is not None :
531 hierarchy = Hierarchy()
532 person_gid = hierarchy.create_gid(person_urn, create_uuid(), \
534 if user_dict['email']:
535 logger.debug("__add_person_to_db \r\n \r\n \
536 IOTLAB IMPORTER PERSON EMAIL OK email %s "\
537 %(user_dict['email']))
538 person_gid.set_email(user_dict['email'])
540 user_record = RegUser(hrn=hrn , pointer= '-1', \
541 authority=get_authority(hrn), \
542 email=user_dict['email'], gid = person_gid)
543 user_record.reg_keys = [RegKey(user_dict['pkey'])]
544 user_record.just_created()
545 dbsession.add (user_record)
550 def AddPerson(self, record):
553 Adds a new account. Any fields specified in records are used,
554 otherwise defaults are used. Creates an appropriate login by calling
557 :param record: dictionary with the sfa user's properties.
558 :returns: The uid of the added person if sucessful, otherwise returns
559 the error message from LDAP.
560 :rtype: interger or string
563 ret = self.ldap.LdapAddUser(record)
565 if ret['bool'] is True:
566 record['hrn'] = self.root_auth + '.' + ret['uid']
567 logger.debug("IOTLABDRIVER AddPerson return code %s record %s \r\n "\
569 self.__add_person_to_db(record)
572 return ret['message']
576 #TODO AddPersonKey 04/07/2012 SA
577 def AddPersonKey(self, person_uid, old_attributes_dict, new_key_dict):
578 """Adds a new key to the specified account. Adds the key to the
579 iotlab ldap, provided that the person_uid is valid.
581 Non-admins can only modify their own keys.
583 :param person_uid: user's iotlab login in LDAP
584 :param old_attributes_dict: dict with the user's old sshPublicKey
585 :param new_key_dict: dict with the user's new sshPublicKey
586 :type person_uid: string
590 :returns: True if the key has been modified, False otherwise.
593 ret = self.ldap.LdapModify(person_uid, old_attributes_dict, \
595 logger.warning("IOTLABDRIVER AddPersonKey EMPTY - DO NOTHING \r\n ")
598 def DeleteLeases(self, leases_id_list, slice_hrn):
601 Deletes several leases, based on their job ids and the slice
602 they are associated with. Uses DeleteJobs to delete the jobs
603 on OAR. Note that one slice can contain multiple jobs, and in this
604 case all the jobs in the leases_id_list MUST belong to ONE slice,
605 since there is only one slice hrn provided here.
607 :param leases_id_list: list of job ids that belong to the slice whose
608 slice hrn is provided.
609 :param slice_hrn: the slice hrn.
610 :type slice_hrn: string
612 .. warning:: Does not have a return value since there was no easy
613 way to handle failure when dealing with multiple job delete. Plus,
614 there was no easy way to report it to the user.
617 logger.debug("IOTLABDRIVER DeleteLeases leases_id_list %s slice_hrn %s \
618 \r\n " %(leases_id_list, slice_hrn))
619 for job_id in leases_id_list:
620 self.DeleteJobs(job_id, slice_hrn)
625 def _process_walltime(duration):
626 """ Calculates the walltime in seconds from the duration in H:M:S
627 specified in the RSpec.
631 # Fixing the walltime by adding a few delays.
632 # First put the walltime in seconds oarAdditionalDelay = 20;
633 # additional delay for /bin/sleep command to
634 # take in account prologue and epilogue scripts execution
635 # int walltimeAdditionalDelay = 240; additional delay
636 #for prologue/epilogue execution = $SERVER_PROLOGUE_EPILOGUE_TIMEOUT
638 # Put the duration in seconds first
639 #desired_walltime = duration * 60
640 desired_walltime = duration
641 total_walltime = desired_walltime + 240 #+4 min Update SA 23/10/12
642 sleep_walltime = desired_walltime # 0 sec added Update SA 23/10/12
644 #Put the walltime back in str form
646 walltime.append(str(total_walltime / 3600))
647 total_walltime = total_walltime - 3600 * int(walltime[0])
648 #Get the remaining minutes
649 walltime.append(str(total_walltime / 60))
650 total_walltime = total_walltime - 60 * int(walltime[1])
652 walltime.append(str(total_walltime))
655 logger.log_exc(" __process_walltime duration null")
657 return walltime, sleep_walltime
660 def _create_job_structure_request_for_OAR(lease_dict):
661 """ Creates the structure needed for a correct POST on OAR.
662 Makes the timestamp transformation into the appropriate format.
663 Sends the POST request to create the job with the resources in
672 reqdict['workdir'] = '/tmp'
673 reqdict['resource'] = "{network_address in ("
675 for node in lease_dict['added_nodes']:
676 logger.debug("\r\n \r\n OARrestapi \t \
677 __create_job_structure_request_for_OAR node %s" %(node))
679 # Get the ID of the node
681 reqdict['resource'] += "'" + nodeid + "', "
682 nodeid_list.append(nodeid)
684 custom_length = len(reqdict['resource'])- 2
685 reqdict['resource'] = reqdict['resource'][0:custom_length] + \
686 ")}/nodes=" + str(len(nodeid_list))
689 walltime, sleep_walltime = \
690 IotlabTestbedAPI._process_walltime(\
691 int(lease_dict['lease_duration']))
694 reqdict['resource'] += ",walltime=" + str(walltime[0]) + \
695 ":" + str(walltime[1]) + ":" + str(walltime[2])
696 reqdict['script_path'] = "/bin/sleep " + str(sleep_walltime)
698 #In case of a scheduled experiment (not immediate)
699 #To run an XP immediately, don't specify date and time in RSpec
700 #They will be set to None.
701 if lease_dict['lease_start_time'] is not '0':
702 #Readable time accepted by OAR
703 start_time = datetime.fromtimestamp( \
704 int(lease_dict['lease_start_time'])).\
705 strftime(lease_dict['time_format'])
706 reqdict['reservation'] = start_time
707 #If there is not start time, Immediate XP. No need to add special
711 reqdict['type'] = "deploy"
712 reqdict['directory'] = ""
713 reqdict['name'] = "SFA_" + lease_dict['slice_user']
718 def LaunchExperimentOnOAR(self, added_nodes, slice_name, \
719 lease_start_time, lease_duration, slice_user=None):
722 Create a job request structure based on the information provided
723 and post the job on OAR.
724 :param added_nodes: list of nodes that belong to the described lease.
725 :param slice_name: the slice hrn associated to the lease.
726 :param lease_start_time: timestamp of the lease startting time.
727 :param lease_duration: lease durationin minutes
731 lease_dict['lease_start_time'] = lease_start_time
732 lease_dict['lease_duration'] = lease_duration
733 lease_dict['added_nodes'] = added_nodes
734 lease_dict['slice_name'] = slice_name
735 lease_dict['slice_user'] = slice_user
736 lease_dict['grain'] = self.GetLeaseGranularity()
737 lease_dict['time_format'] = self.time_format
740 logger.debug("IOTLABDRIVER.PY \tLaunchExperimentOnOAR slice_user %s\
741 \r\n " %(slice_user))
742 #Create the request for OAR
743 reqdict = self._create_job_structure_request_for_OAR(lease_dict)
744 # first step : start the OAR job and update the job
745 logger.debug("IOTLABDRIVER.PY \tLaunchExperimentOnOAR reqdict %s\
748 answer = self.oar.POSTRequestToOARRestAPI('POST_job', \
750 logger.debug("IOTLABDRIVER \tLaunchExperimentOnOAR jobid %s " %(answer))
754 logger.log_exc("IOTLABDRIVER \tLaunchExperimentOnOAR \
755 Impossible to create job %s " %(answer))
762 logger.debug("IOTLABDRIVER \tLaunchExperimentOnOAR jobid %s \
763 added_nodes %s slice_user %s" %(jobid, added_nodes, \
770 def AddLeases(self, hostname_list, slice_record, \
771 lease_start_time, lease_duration):
773 """Creates a job in OAR corresponding to the information provided
774 as parameters. Adds the job id and the slice hrn in the iotlab
775 database so that we are able to know which slice has which nodes.
777 :param hostname_list: list of nodes' OAR hostnames.
778 :param slice_record: sfa slice record, must contain login and hrn.
779 :param lease_start_time: starting time , unix timestamp format
780 :param lease_duration: duration in minutes
782 :type hostname_list: list
783 :type slice_record: dict
784 :type lease_start_time: integer
785 :type lease_duration: integer
788 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases hostname_list %s \
789 slice_record %s lease_start_time %s lease_duration %s "\
790 %( hostname_list, slice_record , lease_start_time, \
793 #tmp = slice_record['reg-researchers'][0].split(".")
794 username = slice_record['login']
795 #username = tmp[(len(tmp)-1)]
796 job_id = self.LaunchExperimentOnOAR(hostname_list, \
797 slice_record['hrn'], \
798 lease_start_time, lease_duration, \
801 datetime.fromtimestamp(int(lease_start_time)).\
802 strftime(self.time_format)
803 end_time = lease_start_time + lease_duration
806 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases TURN ON LOGGING SQL \
807 %s %s %s "%(slice_record['hrn'], job_id, end_time))
810 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases %s %s %s " \
811 %(type(slice_record['hrn']), type(job_id), type(end_time)))
813 iotlab_ex_row = IotlabXP(slice_hrn = slice_record['hrn'], \
814 job_id = job_id, end_time= end_time)
816 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases iotlab_ex_row %s" \
818 self.iotlab_db.iotlab_session.add(iotlab_ex_row)
819 self.iotlab_db.iotlab_session.commit()
821 logger.debug("IOTLABDRIVER \t AddLeases hostname_list start_time %s " \
827 #Delete the jobs from job_iotlab table
828 def DeleteSliceFromNodes(self, slice_record):
831 Deletes all the running or scheduled jobs of a given slice
834 :param slice_record: record of the slice, must contain oar_job_id, user
835 :type slice_record: dict
837 :returns: dict of the jobs'deletion status. Success= True, Failure=
838 False, for each job id.
842 logger.debug("IOTLABDRIVER \t DeleteSliceFromNodese %s " %(slice_record))
844 if isinstance(slice_record['oar_job_id'], list):
846 for job_id in slice_record['oar_job_id']:
847 ret = self.DeleteJobs(job_id, slice_record['user'])
849 oar_bool_answer.update(ret)
852 oar_bool_answer = [self.DeleteJobs(slice_record['oar_job_id'], \
853 slice_record['user'])]
855 return oar_bool_answer
859 def GetLeaseGranularity(self):
860 """ Returns the granularity of an experiment in the Iotlab testbed.
861 OAR uses seconds for experiments duration , the granulaity is also
863 Experiments which last less than 10 min (600 sec) are invalid"""
868 # def update_jobs_in_iotlabdb( job_oar_list, jobs_psql):
869 # """ Cleans the iotlab db by deleting expired and cancelled jobs.
870 # Compares the list of job ids given by OAR with the job ids that
871 # are already in the database, deletes the jobs that are no longer in
872 # the OAR job id list.
873 # :param job_oar_list: list of job ids coming from OAR
874 # :type job_oar_list: list
875 # :param job_psql: list of job ids cfrom the database.
876 # type job_psql: list
878 # #Turn the list into a set
879 # set_jobs_psql = set(jobs_psql)
881 # kept_jobs = set(job_oar_list).intersection(set_jobs_psql)
882 # logger.debug ( "\r\n \t\ update_jobs_in_iotlabdb jobs_psql %s \r\n \t \
883 # job_oar_list %s kept_jobs %s "%(set_jobs_psql, job_oar_list, kept_jobs))
884 # deleted_jobs = set_jobs_psql.difference(kept_jobs)
885 # deleted_jobs = list(deleted_jobs)
886 # if len(deleted_jobs) > 0:
887 # self.iotlab_db.iotlab_session.query(IotlabXP).filter(IotlabXP.job_id.in_(deleted_jobs)).delete(synchronize_session='fetch')
888 # self.iotlab_db.iotlab_session.commit()
894 def GetLeases(self, lease_filter_dict=None, login=None):
897 Get the list of leases from OAR with complete information
898 about which slice owns which jobs and nodes.
900 -Fetch all the jobs from OAR (running, waiting..)
901 complete the reservation information with slice hrn
902 found in iotlab_xp table. If not available in the table,
903 assume it is a iotlab slice.
904 -Updates the iotlab table, deleting jobs when necessary.
906 :returns: reservation_list, list of dictionaries with 'lease_id',
907 'reserved_nodes','slice_id', 'state', 'user', 'component_id_list',
908 'slice_hrn', 'resource_ids', 't_from', 't_until'
913 unfiltered_reservation_list = self.GetReservedNodes(login)
915 reservation_list = []
916 #Find the slice associated with this user iotlab ldap uid
917 logger.debug(" IOTLABDRIVER.PY \tGetLeases login %s\
918 unfiltered_reservation_list %s " %(login, unfiltered_reservation_list))
919 #Create user dict first to avoid looking several times for
920 #the same user in LDAP SA 27/07/12
923 jobs_psql_query = self.iotlab_db.iotlab_session.query(IotlabXP).all()
924 jobs_psql_dict = dict([(row.job_id, row.__dict__ ) for row in jobs_psql_query ])
925 #jobs_psql_dict = jobs_psql_dict)
926 logger.debug("IOTLABDRIVER \tGetLeases jobs_psql_dict %s"\
928 jobs_psql_id_list = [ row.job_id for row in jobs_psql_query ]
932 for resa in unfiltered_reservation_list:
933 logger.debug("IOTLABDRIVER \tGetLeases USER %s"\
935 #Construct list of jobs (runing, waiting..) in oar
936 job_oar_list.append(resa['lease_id'])
937 #If there is information on the job in IOTLAB DB ]
938 #(slice used and job id)
939 if resa['lease_id'] in jobs_psql_dict:
940 job_info = jobs_psql_dict[resa['lease_id']]
941 logger.debug("IOTLABDRIVER \tGetLeases job_info %s"\
943 resa['slice_hrn'] = job_info['slice_hrn']
944 resa['slice_id'] = hrn_to_urn(resa['slice_hrn'], 'slice')
946 #otherwise, assume it is a iotlab slice:
948 resa['slice_id'] = hrn_to_urn(self.root_auth+'.'+ \
949 resa['user'] +"_slice" , 'slice')
950 resa['slice_hrn'] = Xrn(resa['slice_id']).get_hrn()
952 resa['component_id_list'] = []
953 #Transform the hostnames into urns (component ids)
954 for node in resa['reserved_nodes']:
956 iotlab_xrn = iotlab_xrn_object(self.root_auth, node)
957 resa['component_id_list'].append(iotlab_xrn.urn)
959 if lease_filter_dict:
960 logger.debug("IOTLABDRIVER \tGetLeases resa_ %s \
961 \r\n leasefilter %s" %(resa, lease_filter_dict))
963 if lease_filter_dict['name'] == resa['slice_hrn']:
964 reservation_list.append(resa)
966 if lease_filter_dict is None:
967 reservation_list = unfiltered_reservation_list
970 self.iotlab_db.update_jobs_in_iotlabdb(job_oar_list, jobs_psql_id_list)
972 logger.debug(" IOTLABDRIVER.PY \tGetLeases reservation_list %s"\
974 return reservation_list
979 #TODO FUNCTIONS SECTION 04/07/2012 SA
981 ##TODO : Is UnBindObjectFromPeer still necessary ? Currently does nothing
984 #def UnBindObjectFromPeer( auth, object_type, object_id, shortname):
985 #""" This method is a hopefully temporary hack to let the sfa correctly
986 #detach the objects it creates from a remote peer object. This is
987 #needed so that the sfa federation link can work in parallel with
988 #RefreshPeer, as RefreshPeer depends on remote objects being correctly
991 #auth : struct, API authentication structure
992 #AuthMethod : string, Authentication method to use
993 #object_type : string, Object type, among 'site','person','slice',
995 #object_id : int, object_id
996 #shortname : string, peer shortname
1000 #logger.warning("IOTLABDRIVER \tUnBindObjectFromPeer EMPTY-\
1004 ##TODO Is BindObjectToPeer still necessary ? Currently does nothing
1006 #|| Commented out 28/05/13 SA
1007 #def BindObjectToPeer(self, auth, object_type, object_id, shortname=None, \
1008 #remote_object_id=None):
1009 #"""This method is a hopefully temporary hack to let the sfa correctly
1010 #attach the objects it creates to a remote peer object. This is needed
1011 #so that the sfa federation link can work in parallel with RefreshPeer,
1012 #as RefreshPeer depends on remote objects being correctly marked.
1014 #shortname : string, peer shortname
1015 #remote_object_id : int, remote object_id, set to 0 if unknown
1019 #logger.warning("IOTLABDRIVER \tBindObjectToPeer EMPTY - DO NOTHING \r\n ")
1022 ##TODO UpdateSlice 04/07/2012 SA || Commented out 28/05/13 SA
1023 ##Funciton should delete and create another job since oin iotlab slice=job
1024 #def UpdateSlice(self, auth, slice_id_or_name, slice_fields=None):
1025 #"""Updates the parameters of an existing slice with the values in
1027 #Users may only update slices of which they are members.
1028 #PIs may update any of the slices at their sites, or any slices of
1029 #which they are members. Admins may update any slice.
1030 #Only PIs and admins may update max_nodes. Slices cannot be renewed
1031 #(by updating the expires parameter) more than 8 weeks into the future.
1032 #Returns 1 if successful, faults otherwise.
1036 #logger.warning("IOTLABDRIVER UpdateSlice EMPTY - DO NOTHING \r\n ")
1039 #Unused SA 30/05/13, we only update the user's key or we delete it.
1040 ##TODO UpdatePerson 04/07/2012 SA
1041 #def UpdatePerson(self, iotlab_hrn, federated_hrn, person_fields=None):
1042 #"""Updates a person. Only the fields specified in person_fields
1043 #are updated, all other fields are left untouched.
1044 #Users and techs can only update themselves. PIs can only update
1045 #themselves and other non-PIs at their sites.
1046 #Returns 1 if successful, faults otherwise.
1050 ##new_row = FederatedToIotlab(iotlab_hrn, federated_hrn)
1051 ##self.iotlab_db.iotlab_session.add(new_row)
1052 ##self.iotlab_db.iotlab_session.commit()
1054 #logger.debug("IOTLABDRIVER UpdatePerson EMPTY - DO NOTHING \r\n ")
1058 def GetKeys(key_filter=None):
1059 """Returns a dict of dict based on the key string. Each dict entry
1060 contains the key id, the ssh key, the user's email and the
1062 If key_filter is specified and is an array of key identifiers,
1063 only keys matching the filter will be returned.
1065 Admin may query all keys. Non-admins may only query their own keys.
1068 :returns: dict with ssh key as key and dicts as value.
1071 if key_filter is None:
1072 keys = dbsession.query(RegKey).options(joinedload('reg_user')).all()
1074 keys = dbsession.query(RegKey).options(joinedload('reg_user')).filter(RegKey.key.in_(key_filter)).all()
1078 key_dict[key.key] = {'key_id': key.key_id, 'key': key.key, \
1079 'email': key.reg_user.email, 'hrn':key.reg_user.hrn}
1081 #ldap_rslt = self.ldap.LdapSearch({'enabled']=True})
1082 #user_by_email = dict((user[1]['mail'][0], user[1]['sshPublicKey']) \
1083 #for user in ldap_rslt)
1085 logger.debug("IOTLABDRIVER GetKeys -key_dict %s \r\n " %(key_dict))
1089 def DeleteKey(self, user_record, key_string):
1090 """Deletes a key in the LDAP entry of the specified user.
1092 Removes the key_string from the user's key list and updates the LDAP
1093 user's entry with the new key attributes.
1095 :param key_string: The ssh key to remove
1096 :param user_record: User's record
1097 :type key_string: string
1098 :type user_record: dict
1099 :returns: True if sucessful, False if not.
1103 all_user_keys = user_record['keys']
1104 all_user_keys.remove(key_string)
1105 new_attributes = {'sshPublicKey':all_user_keys}
1106 ret = self.ldap.LdapModifyUser(user_record, new_attributes)
1107 logger.debug("IOTLABDRIVER DeleteKey %s- "%(ret))
1114 def _sql_get_slice_info( slice_filter ):
1116 Get the slice record based on the slice hrn. Fetch the record of the
1117 user associated with the slice by using joinedload based on the
1118 reg_researcher relationship.
1120 :param slice_filter: the slice hrn we are looking for
1121 :type slice_filter: string
1122 :returns: the slice record enhanced with the user's information if the
1123 slice was found, None it wasn't.
1125 :rtype: dict or None.
1127 #DO NOT USE RegSlice - reg_researchers to get the hrn
1128 #of the user otherwise will mess up the RegRecord in
1129 #Resolve, don't know why - SA 08/08/2012
1131 #Only one entry for one user = one slice in iotlab_xp table
1132 #slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
1133 raw_slicerec = dbsession.query(RegSlice).options(joinedload('reg_researchers')).filter_by(hrn = slice_filter).first()
1134 #raw_slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
1136 #load_reg_researcher
1137 #raw_slicerec.reg_researchers
1138 raw_slicerec = raw_slicerec.__dict__
1139 logger.debug(" IOTLABDRIVER \t get_slice_info slice_filter %s \
1140 raw_slicerec %s"%(slice_filter, raw_slicerec))
1141 slicerec = raw_slicerec
1142 #only one researcher per slice so take the first one
1143 #slicerec['reg_researchers'] = raw_slicerec['reg_researchers']
1144 #del slicerec['reg_researchers']['_sa_instance_state']
1151 def _sql_get_slice_info_from_user(slice_filter ):
1153 Get the slice record based on the user recordid by using a joinedload
1154 on the relationship reg_slices_as_researcher. Format the sql record
1155 into a dict with the mandatory fields for user and slice.
1156 :returns: dict with slice record and user record if the record was found
1157 based on the user's id, None if not..
1158 :rtype:dict or None..
1160 #slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
1161 raw_slicerec = dbsession.query(RegUser).options(joinedload('reg_slices_as_researcher')).filter_by(record_id = slice_filter).first()
1162 #raw_slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
1163 #Put it in correct order
1164 user_needed_fields = ['peer_authority', 'hrn', 'last_updated', 'classtype', 'authority', 'gid', 'record_id', 'date_created', 'type', 'email', 'pointer']
1165 slice_needed_fields = ['peer_authority', 'hrn', 'last_updated', 'classtype', 'authority', 'gid', 'record_id', 'date_created', 'type', 'pointer']
1167 #raw_slicerec.reg_slices_as_researcher
1168 raw_slicerec = raw_slicerec.__dict__
1171 dict([(k, raw_slicerec['reg_slices_as_researcher'][0].__dict__[k]) \
1172 for k in slice_needed_fields])
1173 slicerec['reg_researchers'] = dict([(k, raw_slicerec[k]) \
1174 for k in user_needed_fields])
1175 #TODO Handle multiple slices for one user SA 10/12/12
1176 #for now only take the first slice record associated to the rec user
1177 ##slicerec = raw_slicerec['reg_slices_as_researcher'][0].__dict__
1178 #del raw_slicerec['reg_slices_as_researcher']
1179 #slicerec['reg_researchers'] = raw_slicerec
1180 ##del slicerec['_sa_instance_state']
1187 def _get_slice_records(self, slice_filter = None, \
1188 slice_filter_type = None):
1190 Get the slice record depending on the slice filter and its type.
1191 :param slice_filter: Can be either the slice hrn or the user's record
1193 :type slice_filter: string
1194 :param slice_filter_type: describes the slice filter type used, can be
1195 slice_hrn or record_id_user
1197 :returns: the slice record
1199 .. seealso::_sql_get_slice_info_from_user
1200 .. seealso:: _sql_get_slice_info
1203 #Get list of slices based on the slice hrn
1204 if slice_filter_type == 'slice_hrn':
1206 #if get_authority(slice_filter) == self.root_auth:
1207 #login = slice_filter.split(".")[1].split("_")[0]
1209 slicerec = self._sql_get_slice_info(slice_filter)
1211 if slicerec is None:
1215 #Get slice based on user id
1216 if slice_filter_type == 'record_id_user':
1218 slicerec = self._sql_get_slice_info_from_user(slice_filter)
1221 fixed_slicerec_dict = slicerec
1222 #At this point if there is no login it means
1223 #record_id_user filter has been used for filtering
1225 ##If theslice record is from iotlab
1226 #if fixed_slicerec_dict['peer_authority'] is None:
1227 #login = fixed_slicerec_dict['hrn'].split(".")[1].split("_")[0]
1228 #return login, fixed_slicerec_dict
1229 return fixed_slicerec_dict
1232 def GetSlices(self, slice_filter=None, slice_filter_type=None,
1234 """Get the slice records from the iotlab db and add lease information
1237 :param slice_filter: can be the slice hrn or slice record id in the db
1238 depending on the slice_filter_type.
1239 :param slice_filter_type: defines the type of the filtering used, Can be
1240 either 'slice_hrn' or "record_id'.
1241 :type slice_filter: string
1242 :type slice_filter_type: string
1243 :returns: a slice dict if slice_filter and slice_filter_type
1244 are specified and a matching entry is found in the db. The result
1245 is put into a list.Or a list of slice dictionnaries if no filters
1252 authorized_filter_types_list = ['slice_hrn', 'record_id_user']
1253 return_slicerec_dictlist = []
1255 #First try to get information on the slice based on the filter provided
1256 if slice_filter_type in authorized_filter_types_list:
1257 fixed_slicerec_dict = self._get_slice_records(slice_filter,
1259 slice_hrn = fixed_slicerec_dict['hrn']
1261 logger.debug(" IOTLABDRIVER \tGetSlices login %s \
1262 slice record %s slice_filter %s \
1263 slice_filter_type %s " % (login,
1264 fixed_slicerec_dict, slice_filter,
1268 #Now we have the slice record fixed_slicerec_dict, get the
1269 #jobs associated to this slice
1272 leases_list = self.GetLeases(login=login)
1273 #If no job is running or no job scheduled
1274 #return only the slice record
1275 if leases_list == [] and fixed_slicerec_dict:
1276 return_slicerec_dictlist.append(fixed_slicerec_dict)
1278 #If several jobs for one slice , put the slice record into
1279 # each lease information dict
1281 for lease in leases_list :
1283 logger.debug("IOTLABDRIVER.PY \tGetSlices slice_filter %s \
1284 \ lease['slice_hrn'] %s"
1285 % (slice_filter, lease['slice_hrn']))
1286 if lease['slice_hrn'] == slice_hrn:
1287 slicerec_dict['slice_hrn'] = lease['slice_hrn']
1288 slicerec_dict['hrn'] = lease['slice_hrn']
1289 slicerec_dict['user'] = lease['user']
1290 slicerec_dict['oar_job_id'] = lease['lease_id']
1291 slicerec_dict.update(
1293 {'hostname': lease['reserved_nodes']}
1295 slicerec_dict.update({'node_ids': lease['reserved_nodes']})
1297 #Update lease dict with the slice record
1298 if fixed_slicerec_dict:
1299 fixed_slicerec_dict['oar_job_id'] = []
1300 fixed_slicerec_dict['oar_job_id'].append(
1301 slicerec_dict['oar_job_id'])
1302 slicerec_dict.update(fixed_slicerec_dict)
1303 #slicerec_dict.update({'hrn':\
1304 #str(fixed_slicerec_dict['slice_hrn'])})
1306 return_slicerec_dictlist.append(slicerec_dict)
1307 logger.debug("IOTLABDRIVER.PY \tGetSlices \
1308 OHOHOHOH %s" %(return_slicerec_dictlist ))
1310 logger.debug("IOTLABDRIVER.PY \tGetSlices \
1311 slicerec_dict %s return_slicerec_dictlist %s \
1312 lease['reserved_nodes'] \
1313 %s" %(slicerec_dict, return_slicerec_dictlist, \
1314 lease['reserved_nodes'] ))
1316 logger.debug("IOTLABDRIVER.PY \tGetSlices RETURN \
1317 return_slicerec_dictlist %s" \
1318 %(return_slicerec_dictlist))
1320 return return_slicerec_dictlist
1324 #Get all slices from the iotlab sfa database ,
1325 #put them in dict format
1326 #query_slice_list = dbsession.query(RegRecord).all()
1327 query_slice_list = dbsession.query(RegSlice).options(joinedload('reg_researchers')).all()
1329 for record in query_slice_list:
1330 tmp = record.__dict__
1331 tmp['reg_researchers'] = tmp['reg_researchers'][0].__dict__
1332 #del tmp['reg_researchers']['_sa_instance_state']
1333 return_slicerec_dictlist.append(tmp)
1334 #return_slicerec_dictlist.append(record.__dict__)
1336 #Get all the jobs reserved nodes
1337 leases_list = self.GetReservedNodes()
1340 for fixed_slicerec_dict in return_slicerec_dictlist:
1342 #Check if the slice belongs to a iotlab user
1343 if fixed_slicerec_dict['peer_authority'] is None:
1344 owner = fixed_slicerec_dict['hrn'].split(".")[1].split("_")[0]
1347 for lease in leases_list:
1348 if owner == lease['user']:
1349 slicerec_dict['oar_job_id'] = lease['lease_id']
1351 #for reserved_node in lease['reserved_nodes']:
1352 logger.debug("IOTLABDRIVER.PY \tGetSlices lease %s "\
1355 slicerec_dict.update({'node_ids':lease['reserved_nodes']})
1356 slicerec_dict.update({'list_node_ids':{'hostname':lease['reserved_nodes']}})
1357 slicerec_dict.update(fixed_slicerec_dict)
1358 #slicerec_dict.update({'hrn':\
1359 #str(fixed_slicerec_dict['slice_hrn'])})
1360 #return_slicerec_dictlist.append(slicerec_dict)
1361 fixed_slicerec_dict.update(slicerec_dict)
1363 logger.debug("IOTLABDRIVER.PY \tGetSlices RETURN \
1364 return_slicerec_dictlist %s \slice_filter %s " \
1365 %(return_slicerec_dictlist, slice_filter))
1367 return return_slicerec_dictlist
1371 #Update slice unused, therefore sfa_fields_to_iotlab_fields unused
1374 #def sfa_fields_to_iotlab_fields(sfa_type, hrn, record):
1379 ##for field in record:
1380 ## iotlab_record[field] = record[field]
1382 #if sfa_type == "slice":
1383 ##instantion used in get_slivers ?
1384 #if not "instantiation" in iotlab_record:
1385 #iotlab_record["instantiation"] = "iotlab-instantiated"
1386 ##iotlab_record["hrn"] = hrn_to_pl_slicename(hrn)
1387 ##Unused hrn_to_pl_slicename because Iotlab's hrn already
1388 ##in the appropriate form SA 23/07/12
1389 #iotlab_record["hrn"] = hrn
1390 #logger.debug("IOTLABDRIVER.PY sfa_fields_to_iotlab_fields \
1391 #iotlab_record %s " %(iotlab_record['hrn']))
1392 #if "url" in record:
1393 #iotlab_record["url"] = record["url"]
1394 #if "description" in record:
1395 #iotlab_record["description"] = record["description"]
1396 #if "expires" in record:
1397 #iotlab_record["expires"] = int(record["expires"])
1399 ##nodes added by OAR only and then imported to SFA
1400 ##elif type == "node":
1401 ##if not "hostname" in iotlab_record:
1402 ##if not "hostname" in record:
1403 ##raise MissingSfaInfo("hostname")
1404 ##iotlab_record["hostname"] = record["hostname"]
1405 ##if not "model" in iotlab_record:
1406 ##iotlab_record["model"] = "geni"
1408 ##One authority only
1409 ##elif type == "authority":
1410 ##iotlab_record["login_base"] = hrn_to_iotlab_login_base(hrn)
1412 ##if not "name" in iotlab_record:
1413 ##iotlab_record["name"] = hrn
1415 ##if not "abbreviated_name" in iotlab_record:
1416 ##iotlab_record["abbreviated_name"] = hrn
1418 ##if not "enabled" in iotlab_record:
1419 ##iotlab_record["enabled"] = True
1421 ##if not "is_public" in iotlab_record:
1422 ##iotlab_record["is_public"] = True
1424 #return iotlab_record