2 File containing the IotlabTestbedAPI, used to interact with nodes, users,
3 slices, leases and keys, as well as the dedicated iotlab database and table,
4 holding information about which slice is running which job.
7 from datetime import datetime
9 from sfa.util.sfalogging import logger
11 from sfa.storage.alchemy import dbsession
12 from sqlalchemy.orm import joinedload
13 from sfa.storage.model import RegRecord, RegUser, RegSlice, RegKey
14 from sfa.iotlab.iotlabpostgres import IotlabDB, IotlabXP
15 from sfa.iotlab.OARrestapi import OARrestapi
16 from sfa.iotlab.LDAPapi import LDAPapi
18 from sfa.util.xrn import Xrn, hrn_to_urn, get_authority
20 from sfa.trust.certificate import Keypair, convert_public_key
21 from sfa.trust.gid import create_uuid
22 from sfa.trust.hierarchy import Hierarchy
24 from sfa.iotlab.iotlabaggregate import iotlab_xrn_object
26 class IotlabTestbedAPI():
27 """ Class enabled to use LDAP and OAR api calls. """
29 _MINIMUM_DURATION = 600
31 def __init__(self, config):
32 """Creates an instance of OARrestapi and LDAPapi which will be used to
33 issue calls to OAR or LDAP methods.
34 Set the time format and the testbed granularity used for OAR
35 reservation and leases.
37 :param config: configuration object from sfa.util.config
38 :type config: Config object
40 self.iotlab_db = IotlabDB(config)
41 self.oar = OARrestapi()
43 self.time_format = "%Y-%m-%d %H:%M:%S"
44 self.root_auth = config.SFA_REGISTRY_ROOT_AUTH
45 self.grain = 1 # 10 mins lease minimum, 1 sec granularity
46 #import logging, logging.handlers
47 #from sfa.util.sfalogging import _SfaLogger
48 #sql_logger = _SfaLogger(loggername = 'sqlalchemy.engine', \
53 def GetMinExperimentDurationInSec():
54 """ Returns the minimum allowed duration for an experiment on the
58 return IotlabTestbedAPI._MINIMUM_DURATION
61 def GetPeers (peer_filter=None ):
62 """ Gathers registered authorities in SFA DB and looks for specific peer
63 if peer_filter is specified.
64 :param peer_filter: name of the site authority looked for.
65 :type peer_filter: string
66 :returns: list of records.
71 existing_hrns_by_types = {}
72 logger.debug("IOTLABDRIVER \tGetPeers peer_filter %s, \
74 all_records = dbsession.query(RegRecord).filter(RegRecord.type.like('%authority%')).all()
76 for record in all_records:
77 existing_records[(record.hrn, record.type)] = record
78 if record.type not in existing_hrns_by_types:
79 existing_hrns_by_types[record.type] = [record.hrn]
81 existing_hrns_by_types[record.type].append(record.hrn)
84 logger.debug("IOTLABDRIVER \tGetPeer\texisting_hrns_by_types %s "\
85 %( existing_hrns_by_types))
90 records_list.append(existing_records[(peer_filter,'authority')])
92 for hrn in existing_hrns_by_types['authority']:
93 records_list.append(existing_records[(hrn,'authority')])
95 logger.debug("IOTLABDRIVER \tGetPeer \trecords_list %s " \
101 return_records = records_list
102 logger.debug("IOTLABDRIVER \tGetPeer return_records %s " \
104 return return_records
108 #TODO : Handling OR request in make_ldap_filters_from_records
109 #instead of the for loop
110 #over the records' list
111 def GetPersons(self, person_filter=None):
113 Get the enabled users and their properties from Iotlab LDAP.
114 If a filter is specified, looks for the user whose properties match
115 the filter, otherwise returns the whole enabled users'list.
116 :param person_filter: Must be a list of dictionnaries
117 with users properties when not set to None.
118 :param person_filter: list of dict
119 :returns:Returns a list of users whose accounts are enabled
121 :rtype: list of dicts
124 logger.debug("IOTLABDRIVER \tGetPersons person_filter %s" \
127 if person_filter and isinstance(person_filter, list):
128 #If we are looking for a list of users (list of dict records)
129 #Usually the list contains only one user record
130 for searched_attributes in person_filter:
132 #Get only enabled user accounts in iotlab LDAP :
133 #add a filter for make_ldap_filters_from_record
134 person = self.ldap.LdapFindUser(searched_attributes, \
135 is_user_enabled=True)
136 #If a person was found, append it to the list
138 person_list.append(person)
140 #If the list is empty, return None
141 if len(person_list) is 0:
145 #Get only enabled user accounts in iotlab LDAP :
146 #add a filter for make_ldap_filters_from_record
147 person_list = self.ldap.LdapFindUser(is_user_enabled=True)
152 #def GetTimezone(self):
153 #""" Returns the OAR server time and timezone.
154 #Unused SA 30/05/13"""
155 #server_timestamp, server_tz = self.oar.parser.\
156 #SendRequest("GET_timezone")
157 #return server_timestamp, server_tz
159 def DeleteJobs(self, job_id, username):
162 Deletes the job with the specified job_id and username on OAR by
163 posting a delete request to OAR.
165 :param job_id: job id in OAR.
166 :param username: user's iotlab login in LDAP.
167 :type job_id: integer
168 :type username: string
170 :returns: dictionary with the job id and if delete has been successful
175 logger.debug("IOTLABDRIVER \tDeleteJobs jobid %s username %s "
176 % (job_id, username))
177 if not job_id or job_id is -1:
181 reqdict['method'] = "delete"
182 reqdict['strval'] = str(job_id)
184 answer = self.oar.POSTRequestToOARRestAPI('DELETE_jobs_id',
186 if answer['status'] == 'Delete request registered':
189 ret = {job_id: False}
190 logger.debug("IOTLABDRIVER \tDeleteJobs jobid %s \r\n answer %s \
191 username %s" % (job_id, answer, username))
196 ##TODO : Unused GetJobsId ? SA 05/07/12
197 #def GetJobsId(self, job_id, username = None ):
199 #Details about a specific job.
200 #Includes details about submission time, jot type, state, events,
201 #owner, assigned ressources, walltime etc...
205 #node_list_k = 'assigned_network_address'
206 ##Get job info from OAR
207 #job_info = self.oar.parser.SendRequest(req, job_id, username)
209 #logger.debug("IOTLABDRIVER \t GetJobsId %s " %(job_info))
211 #if job_info['state'] == 'Terminated':
212 #logger.debug("IOTLABDRIVER \t GetJobsId job %s TERMINATED"\
215 #if job_info['state'] == 'Error':
216 #logger.debug("IOTLABDRIVER \t GetJobsId ERROR message %s "\
221 #logger.error("IOTLABDRIVER \tGetJobsId KeyError")
224 #parsed_job_info = self.get_info_on_reserved_nodes(job_info, \
226 ##Replaces the previous entry
227 ##"assigned_network_address" / "reserved_resources"
229 #job_info.update({'node_ids':parsed_job_info[node_list_k]})
230 #del job_info[node_list_k]
231 #logger.debug(" \r\nIOTLABDRIVER \t GetJobsId job_info %s " %(job_info))
235 def GetJobsResources(self, job_id, username = None):
236 """ Gets the list of nodes associated with the job_id and username
238 Transforms the iotlab hostnames to the corresponding
240 Rertuns dict key :'node_ids' , value : hostnames list
241 :param username: user's LDAP login
242 :paran job_id: job's OAR identifier.
243 :type username: string
244 :type job_id: integer
246 :returns: dicionary with nodes' hostnames belonging to the job.
250 req = "GET_jobs_id_resources"
253 #Get job resources list from OAR
254 node_id_list = self.oar.parser.SendRequest(req, job_id, username)
255 logger.debug("IOTLABDRIVER \t GetJobsResources %s " %(node_id_list))
258 self.__get_hostnames_from_oar_node_ids(node_id_list)
261 #Replaces the previous entry "assigned_network_address" /
262 #"reserved_resources" with "node_ids"
263 job_info = {'node_ids': hostname_list}
268 #def get_info_on_reserved_nodes(self, job_info, node_list_name):
270 #..warning:unused SA 23/05/13
272 ##Get the list of the testbed nodes records and make a
273 ##dictionnary keyed on the hostname out of it
274 #node_list_dict = self.GetNodes()
275 ##node_hostname_list = []
276 #node_hostname_list = [node['hostname'] for node in node_list_dict]
277 ##for node in node_list_dict:
278 ##node_hostname_list.append(node['hostname'])
279 #node_dict = dict(zip(node_hostname_list, node_list_dict))
281 #reserved_node_hostname_list = []
282 #for index in range(len(job_info[node_list_name])):
283 ##job_info[node_list_name][k] =
284 #reserved_node_hostname_list[index] = \
285 #node_dict[job_info[node_list_name][index]]['hostname']
287 #logger.debug("IOTLABDRIVER \t get_info_on_reserved_nodes \
288 #reserved_node_hostname_list %s" \
289 #%(reserved_node_hostname_list))
291 #logger.error("IOTLABDRIVER \t get_info_on_reserved_nodes KEYERROR " )
293 #return reserved_node_hostname_list
295 def GetNodesCurrentlyInUse(self):
296 """Returns a list of all the nodes already involved in an oar running
298 :rtype: list of nodes hostnames.
300 return self.oar.parser.SendRequest("GET_running_jobs")
302 def __get_hostnames_from_oar_node_ids(self, resource_id_list ):
303 """Get the hostnames of the nodes from their OAR identifiers.
304 Get the list of nodes dict using GetNodes and find the hostname
305 associated with the identifier.
306 :param resource_id_list: list of nodes identifiers
307 :returns: list of node hostnames.
309 full_nodes_dict_list = self.GetNodes()
310 #Put the full node list into a dictionary keyed by oar node id
311 oar_id_node_dict = {}
312 for node in full_nodes_dict_list:
313 oar_id_node_dict[node['oar_id']] = node
316 for resource_id in resource_id_list:
317 #Because jobs requested "asap" do not have defined resources
318 if resource_id is not "Undefined":
319 hostname_list.append(\
320 oar_id_node_dict[resource_id]['hostname'])
322 #hostname_list.append(oar_id_node_dict[resource_id]['hostname'])
325 def GetReservedNodes(self, username = None):
326 """ Get list of leases. Get the leases for the username if specified,
327 otherwise get all the leases. Finds the nodes hostnames for each
329 :param username: user's LDAP login
330 :type username: string
331 :returns: list of reservations dict
335 #Get the nodes in use and the reserved nodes
336 reservation_dict_list = \
337 self.oar.parser.SendRequest("GET_reserved_nodes", \
341 for resa in reservation_dict_list:
342 logger.debug ("GetReservedNodes resa %s"%(resa))
343 #dict list of hostnames and their site
344 resa['reserved_nodes'] = \
345 self.__get_hostnames_from_oar_node_ids(resa['resource_ids'])
347 #del resa['resource_ids']
348 return reservation_dict_list
350 def GetNodes(self, node_filter_dict=None, return_fields_list=None):
353 Make a list of iotlab nodes and their properties from information
354 given by OAR. Search for specific nodes if some filters are
355 specified. Nodes properties returned if no return_fields_list given:
356 'hrn','archi','mobile','hostname','site','boot_state','node_id',
357 'radio','posx','posy','oar_id','posz'.
359 :param node_filter_dict: dictionnary of lists with node properties. For
360 instance, if you want to look for a specific node with its hrn,
361 the node_filter_dict should be {'hrn': [hrn_of_the_node]}
362 :type node_filter_dict: dict
363 :param return_fields_list: list of specific fields the user wants to be
365 :type return_fields_list: list
366 :returns: list of dictionaries with node properties
370 node_dict_by_id = self.oar.parser.SendRequest("GET_resources_full")
371 node_dict_list = node_dict_by_id.values()
372 logger.debug (" IOTLABDRIVER GetNodes node_filter_dict %s \
373 return_fields_list %s " % (node_filter_dict, return_fields_list))
374 #No filtering needed return the list directly
375 if not (node_filter_dict or return_fields_list):
376 return node_dict_list
378 return_node_list = []
380 for filter_key in node_filter_dict:
382 #Filter the node_dict_list by each value contained in the
383 #list node_filter_dict[filter_key]
384 for value in node_filter_dict[filter_key]:
385 for node in node_dict_list:
386 if node[filter_key] == value:
387 if return_fields_list:
389 for k in return_fields_list:
391 return_node_list.append(tmp)
393 return_node_list.append(node)
395 logger.log_exc("GetNodes KeyError")
399 return return_node_list
404 def AddSlice(slice_record, user_record):
407 Add slice to the local iotlab sfa tables if the slice comes
408 from a federated site and is not yet in the iotlab sfa DB,
409 although the user has already a LDAP login.
410 Called by verify_slice during lease/sliver creation.
412 :param slice_record: record of slice, must contain hrn, gid, slice_id
413 and authority of the slice.
414 :type slice_record: dictionary
415 :param user_record: record of the user
416 :type user_record: RegUser
420 sfa_record = RegSlice(hrn=slice_record['hrn'],
421 gid=slice_record['gid'],
422 pointer=slice_record['slice_id'],
423 authority=slice_record['authority'])
424 logger.debug("IOTLABDRIVER.PY AddSlice sfa_record %s user_record %s"
425 % (sfa_record, user_record))
426 sfa_record.just_created()
427 dbsession.add(sfa_record)
429 #Update the reg-researcher dependance table
430 sfa_record.reg_researchers = [user_record]
436 def GetSites(self, site_filter_name_list=None, return_fields_list=None):
437 """Returns the list of Iotlab's sites with the associated nodes and
438 their properties as dictionaries.
440 Uses the OAR request GET_sites to find the Iotlab's sites.
442 :param site_filter_name_list: used to specify specific sites
443 :param return_fields_list: field that has to be returned
444 :type site_filter_name_list: list
445 :type return_fields_list: list
449 site_dict = self.oar.parser.SendRequest("GET_sites")
450 #site_dict : dict where the key is the sit ename
451 return_site_list = []
452 if not (site_filter_name_list or return_fields_list):
453 return_site_list = site_dict.values()
454 return return_site_list
456 for site_filter_name in site_filter_name_list:
457 if site_filter_name in site_dict:
458 if return_fields_list:
459 for field in return_fields_list:
462 tmp[field] = site_dict[site_filter_name][field]
464 logger.error("GetSites KeyError %s " % (field))
466 return_site_list.append(tmp)
468 return_site_list.append(site_dict[site_filter_name])
470 return return_site_list
473 #TODO : Check rights to delete person
474 def DeletePerson(self, person_record):
475 """Disable an existing account in iotlab LDAP.
477 Users and techs can only delete themselves. PIs can only
478 delete themselves and other non-PIs at their sites.
479 ins can delete anyone.
481 :param person_record: user's record
482 :type person_record: dict
483 :returns: True if successful, False otherwise.
486 .. todo:: CHECK THAT ONLY THE USER OR ADMIN CAN DEL HIMSELF.
488 #Disable user account in iotlab LDAP
489 ret = self.ldap.LdapMarkUserAsDeleted(person_record)
490 logger.warning("IOTLABDRIVER DeletePerson %s " % (person_record))
493 def DeleteSlice(self, slice_record):
494 """Deletes the specified slice and kills the jobs associated with
495 the slice if any, using DeleteSliceFromNodes.
497 :param slice_record: record of the slice, must contain oar_job_id, user
498 :type slice_record: dict
499 :returns: True if all the jobs in the slice have been deleted,
500 or the list of jobs that could not be deleted otherwise.
501 :rtype: list or boolean
503 .. seealso:: DeleteSliceFromNodes
506 ret = self.DeleteSliceFromNodes(slice_record)
509 if False in ret[job_id]:
510 if delete_failed is None:
512 delete_failed.append(job_id)
514 logger.info("IOTLABDRIVER DeleteSlice %s answer %s"%(slice_record, \
516 return delete_failed or True
519 def __add_person_to_db(user_dict):
521 Add a federated user straight to db when the user issues a lease
522 request with iotlab nodes and that he has not registered with iotlab
523 yet (that is he does not have a LDAP entry yet).
524 Uses parts of the routines in SlabImport when importing user from LDAP.
525 Called by AddPerson, right after LdapAddUser.
526 :param user_dict: Must contain email, hrn and pkey to get a GID
527 and be added to the SFA db.
528 :type user_dict: dict
532 dbsession.query(RegUser).filter_by(email = user_dict['email']).first()
534 if not check_if_exists:
535 logger.debug("__add_person_to_db \t Adding %s \r\n \r\n \
537 hrn = user_dict['hrn']
538 person_urn = hrn_to_urn(hrn, 'user')
539 pubkey = user_dict['pkey']
541 pkey = convert_public_key(pubkey)
543 #key not good. create another pkey
544 logger.warn('__add_person_to_db: unable to convert public \
546 pkey = Keypair(create=True)
549 if pubkey is not None and pkey is not None :
550 hierarchy = Hierarchy()
551 person_gid = hierarchy.create_gid(person_urn, create_uuid(), \
553 if user_dict['email']:
554 logger.debug("__add_person_to_db \r\n \r\n \
555 IOTLAB IMPORTER PERSON EMAIL OK email %s "\
556 %(user_dict['email']))
557 person_gid.set_email(user_dict['email'])
559 user_record = RegUser(hrn=hrn , pointer= '-1', \
560 authority=get_authority(hrn), \
561 email=user_dict['email'], gid = person_gid)
562 user_record.reg_keys = [RegKey(user_dict['pkey'])]
563 user_record.just_created()
564 dbsession.add (user_record)
569 def AddPerson(self, record):
572 Adds a new account. Any fields specified in records are used,
573 otherwise defaults are used. Creates an appropriate login by calling
576 :param record: dictionary with the sfa user's properties.
577 :returns: The uid of the added person if sucessful, otherwise returns
578 the error message from LDAP.
579 :rtype: interger or string
582 ret = self.ldap.LdapAddUser(record)
584 if ret['bool'] is True:
585 record['hrn'] = self.root_auth + '.' + ret['uid']
586 logger.debug("IOTLABDRIVER AddPerson return code %s record %s \r\n "\
588 self.__add_person_to_db(record)
591 return ret['message']
595 #TODO AddPersonKey 04/07/2012 SA
596 def AddPersonKey(self, person_uid, old_attributes_dict, new_key_dict):
597 """Adds a new key to the specified account. Adds the key to the
598 iotlab ldap, provided that the person_uid is valid.
600 Non-admins can only modify their own keys.
602 :param person_uid: user's iotlab login in LDAP
603 :param old_attributes_dict: dict with the user's old sshPublicKey
604 :param new_key_dict: dict with the user's new sshPublicKey
605 :type person_uid: string
609 :returns: True if the key has been modified, False otherwise.
612 ret = self.ldap.LdapModify(person_uid, old_attributes_dict, \
614 logger.warning("IOTLABDRIVER AddPersonKey EMPTY - DO NOTHING \r\n ")
617 def DeleteLeases(self, leases_id_list, slice_hrn):
620 Deletes several leases, based on their job ids and the slice
621 they are associated with. Uses DeleteJobs to delete the jobs
622 on OAR. Note that one slice can contain multiple jobs, and in this
623 case all the jobs in the leases_id_list MUST belong to ONE slice,
624 since there is only one slice hrn provided here.
626 :param leases_id_list: list of job ids that belong to the slice whose
627 slice hrn is provided.
628 :param slice_hrn: the slice hrn.
629 :type slice_hrn: string
631 .. warning:: Does not have a return value since there was no easy
632 way to handle failure when dealing with multiple job delete. Plus,
633 there was no easy way to report it to the user.
636 logger.debug("IOTLABDRIVER DeleteLeases leases_id_list %s slice_hrn %s \
637 \r\n " %(leases_id_list, slice_hrn))
638 for job_id in leases_id_list:
639 self.DeleteJobs(job_id, slice_hrn)
644 def _process_walltime(duration):
645 """ Calculates the walltime in seconds from the duration in H:M:S
646 specified in the RSpec.
650 # Fixing the walltime by adding a few delays.
651 # First put the walltime in seconds oarAdditionalDelay = 20;
652 # additional delay for /bin/sleep command to
653 # take in account prologue and epilogue scripts execution
654 # int walltimeAdditionalDelay = 240; additional delay
655 #for prologue/epilogue execution = $SERVER_PROLOGUE_EPILOGUE_TIMEOUT
657 # Put the duration in seconds first
658 #desired_walltime = duration * 60
659 desired_walltime = duration
660 total_walltime = desired_walltime + 240 #+4 min Update SA 23/10/12
661 sleep_walltime = desired_walltime # 0 sec added Update SA 23/10/12
663 #Put the walltime back in str form
665 walltime.append(str(total_walltime / 3600))
666 total_walltime = total_walltime - 3600 * int(walltime[0])
667 #Get the remaining minutes
668 walltime.append(str(total_walltime / 60))
669 total_walltime = total_walltime - 60 * int(walltime[1])
671 walltime.append(str(total_walltime))
674 logger.log_exc(" __process_walltime duration null")
676 return walltime, sleep_walltime
679 def _create_job_structure_request_for_OAR(lease_dict):
680 """ Creates the structure needed for a correct POST on OAR.
681 Makes the timestamp transformation into the appropriate format.
682 Sends the POST request to create the job with the resources in
691 reqdict['workdir'] = '/tmp'
692 reqdict['resource'] = "{network_address in ("
694 for node in lease_dict['added_nodes']:
695 logger.debug("\r\n \r\n OARrestapi \t \
696 __create_job_structure_request_for_OAR node %s" %(node))
698 # Get the ID of the node
700 reqdict['resource'] += "'" + nodeid + "', "
701 nodeid_list.append(nodeid)
703 custom_length = len(reqdict['resource'])- 2
704 reqdict['resource'] = reqdict['resource'][0:custom_length] + \
705 ")}/nodes=" + str(len(nodeid_list))
708 walltime, sleep_walltime = \
709 IotlabTestbedAPI._process_walltime(\
710 int(lease_dict['lease_duration']))
713 reqdict['resource'] += ",walltime=" + str(walltime[0]) + \
714 ":" + str(walltime[1]) + ":" + str(walltime[2])
715 reqdict['script_path'] = "/bin/sleep " + str(sleep_walltime)
717 #In case of a scheduled experiment (not immediate)
718 #To run an XP immediately, don't specify date and time in RSpec
719 #They will be set to None.
720 if lease_dict['lease_start_time'] is not '0':
721 #Readable time accepted by OAR
722 start_time = datetime.fromtimestamp( \
723 int(lease_dict['lease_start_time'])).\
724 strftime(lease_dict['time_format'])
725 reqdict['reservation'] = start_time
726 #If there is not start time, Immediate XP. No need to add special
730 reqdict['type'] = "deploy"
731 reqdict['directory'] = ""
732 reqdict['name'] = "SFA_" + lease_dict['slice_user']
737 def LaunchExperimentOnOAR(self, added_nodes, slice_name, \
738 lease_start_time, lease_duration, slice_user=None):
741 Create a job request structure based on the information provided
742 and post the job on OAR.
743 :param added_nodes: list of nodes that belong to the described lease.
744 :param slice_name: the slice hrn associated to the lease.
745 :param lease_start_time: timestamp of the lease startting time.
746 :param lease_duration: lease durationin minutes
750 lease_dict['lease_start_time'] = lease_start_time
751 lease_dict['lease_duration'] = lease_duration
752 lease_dict['added_nodes'] = added_nodes
753 lease_dict['slice_name'] = slice_name
754 lease_dict['slice_user'] = slice_user
755 lease_dict['grain'] = self.GetLeaseGranularity()
756 lease_dict['time_format'] = self.time_format
759 logger.debug("IOTLABDRIVER.PY \tLaunchExperimentOnOAR slice_user %s\
760 \r\n " %(slice_user))
761 #Create the request for OAR
762 reqdict = self._create_job_structure_request_for_OAR(lease_dict)
763 # first step : start the OAR job and update the job
764 logger.debug("IOTLABDRIVER.PY \tLaunchExperimentOnOAR reqdict %s\
767 answer = self.oar.POSTRequestToOARRestAPI('POST_job', \
769 logger.debug("IOTLABDRIVER \tLaunchExperimentOnOAR jobid %s " %(answer))
773 logger.log_exc("IOTLABDRIVER \tLaunchExperimentOnOAR \
774 Impossible to create job %s " %(answer))
781 logger.debug("IOTLABDRIVER \tLaunchExperimentOnOAR jobid %s \
782 added_nodes %s slice_user %s" %(jobid, added_nodes, \
789 def AddLeases(self, hostname_list, slice_record, \
790 lease_start_time, lease_duration):
792 """Creates a job in OAR corresponding to the information provided
793 as parameters. Adds the job id and the slice hrn in the iotlab
794 database so that we are able to know which slice has which nodes.
796 :param hostname_list: list of nodes' OAR hostnames.
797 :param slice_record: sfa slice record, must contain login and hrn.
798 :param lease_start_time: starting time , unix timestamp format
799 :param lease_duration: duration in minutes
801 :type hostname_list: list
802 :type slice_record: dict
803 :type lease_start_time: integer
804 :type lease_duration: integer
807 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases hostname_list %s \
808 slice_record %s lease_start_time %s lease_duration %s "\
809 %( hostname_list, slice_record , lease_start_time, \
812 #tmp = slice_record['reg-researchers'][0].split(".")
813 username = slice_record['login']
814 #username = tmp[(len(tmp)-1)]
815 job_id = self.LaunchExperimentOnOAR(hostname_list, \
816 slice_record['hrn'], \
817 lease_start_time, lease_duration, \
820 datetime.fromtimestamp(int(lease_start_time)).\
821 strftime(self.time_format)
822 end_time = lease_start_time + lease_duration
825 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases TURN ON LOGGING SQL \
826 %s %s %s "%(slice_record['hrn'], job_id, end_time))
829 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases %s %s %s " \
830 %(type(slice_record['hrn']), type(job_id), type(end_time)))
832 iotlab_ex_row = IotlabXP(slice_hrn = slice_record['hrn'], \
833 job_id = job_id, end_time= end_time)
835 logger.debug("IOTLABDRIVER \r\n \r\n \t AddLeases iotlab_ex_row %s" \
837 self.iotlab_db.iotlab_session.add(iotlab_ex_row)
838 self.iotlab_db.iotlab_session.commit()
840 logger.debug("IOTLABDRIVER \t AddLeases hostname_list start_time %s " \
846 #Delete the jobs from job_iotlab table
847 def DeleteSliceFromNodes(self, slice_record):
850 Deletes all the running or scheduled jobs of a given slice
853 :param slice_record: record of the slice, must contain oar_job_id, user
854 :type slice_record: dict
856 :returns: dict of the jobs'deletion status. Success= True, Failure=
857 False, for each job id.
861 logger.debug("IOTLABDRIVER \t DeleteSliceFromNodes %s "
864 if isinstance(slice_record['oar_job_id'], list):
866 for job_id in slice_record['oar_job_id']:
867 ret = self.DeleteJobs(job_id, slice_record['user'])
869 oar_bool_answer.update(ret)
872 oar_bool_answer = [self.DeleteJobs(slice_record['oar_job_id'],
873 slice_record['user'])]
875 return oar_bool_answer
879 def GetLeaseGranularity(self):
880 """ Returns the granularity of an experiment in the Iotlab testbed.
881 OAR uses seconds for experiments duration , the granulaity is also
883 Experiments which last less than 10 min (600 sec) are invalid"""
888 # def update_jobs_in_iotlabdb( job_oar_list, jobs_psql):
889 # """ Cleans the iotlab db by deleting expired and cancelled jobs.
890 # Compares the list of job ids given by OAR with the job ids that
891 # are already in the database, deletes the jobs that are no longer in
892 # the OAR job id list.
893 # :param job_oar_list: list of job ids coming from OAR
894 # :type job_oar_list: list
895 # :param job_psql: list of job ids cfrom the database.
896 # type job_psql: list
898 # #Turn the list into a set
899 # set_jobs_psql = set(jobs_psql)
901 # kept_jobs = set(job_oar_list).intersection(set_jobs_psql)
902 # logger.debug ( "\r\n \t\ update_jobs_in_iotlabdb jobs_psql %s \r\n \t \
903 # job_oar_list %s kept_jobs %s "%(set_jobs_psql, job_oar_list, kept_jobs))
904 # deleted_jobs = set_jobs_psql.difference(kept_jobs)
905 # deleted_jobs = list(deleted_jobs)
906 # if len(deleted_jobs) > 0:
907 # self.iotlab_db.iotlab_session.query(IotlabXP).filter(IotlabXP.job_id.in_(deleted_jobs)).delete(synchronize_session='fetch')
908 # self.iotlab_db.iotlab_session.commit()
913 def GetLeases(self, lease_filter_dict=None, login=None):
916 Get the list of leases from OAR with complete information
917 about which slice owns which jobs and nodes.
919 -Fetch all the jobs from OAR (running, waiting..)
920 complete the reservation information with slice hrn
921 found in iotlab_xp table. If not available in the table,
922 assume it is a iotlab slice.
923 -Updates the iotlab table, deleting jobs when necessary.
925 :returns: reservation_list, list of dictionaries with 'lease_id',
926 'reserved_nodes','slice_id', 'state', 'user', 'component_id_list',
927 'slice_hrn', 'resource_ids', 't_from', 't_until'
932 unfiltered_reservation_list = self.GetReservedNodes(login)
934 reservation_list = []
935 #Find the slice associated with this user iotlab ldap uid
936 logger.debug(" IOTLABDRIVER.PY \tGetLeases login %s\
937 unfiltered_reservation_list %s "
938 % (login, unfiltered_reservation_list))
939 #Create user dict first to avoid looking several times for
940 #the same user in LDAP SA 27/07/12
943 jobs_psql_query = self.iotlab_db.iotlab_session.query(IotlabXP).all()
944 jobs_psql_dict = dict([(row.job_id, row.__dict__)
945 for row in jobs_psql_query])
946 #jobs_psql_dict = jobs_psql_dict)
947 logger.debug("IOTLABDRIVER \tGetLeases jobs_psql_dict %s"
949 jobs_psql_id_list = [row.job_id for row in jobs_psql_query]
951 for resa in unfiltered_reservation_list:
952 logger.debug("IOTLABDRIVER \tGetLeases USER %s"
954 #Construct list of jobs (runing, waiting..) in oar
955 job_oar_list.append(resa['lease_id'])
956 #If there is information on the job in IOTLAB DB ]
957 #(slice used and job id)
958 if resa['lease_id'] in jobs_psql_dict:
959 job_info = jobs_psql_dict[resa['lease_id']]
960 logger.debug("IOTLABDRIVER \tGetLeases job_info %s"
962 resa['slice_hrn'] = job_info['slice_hrn']
963 resa['slice_id'] = hrn_to_urn(resa['slice_hrn'], 'slice')
965 #otherwise, assume it is a iotlab slice:
967 resa['slice_id'] = hrn_to_urn(self.root_auth + '.' +
968 resa['user'] + "_slice", 'slice')
969 resa['slice_hrn'] = Xrn(resa['slice_id']).get_hrn()
971 resa['component_id_list'] = []
972 #Transform the hostnames into urns (component ids)
973 for node in resa['reserved_nodes']:
975 iotlab_xrn = iotlab_xrn_object(self.root_auth, node)
976 resa['component_id_list'].append(iotlab_xrn.urn)
978 if lease_filter_dict:
979 logger.debug("IOTLABDRIVER \tGetLeases resa_ %s \
980 \r\n leasefilter %s" % (resa, lease_filter_dict))
982 if lease_filter_dict['name'] == resa['slice_hrn']:
983 reservation_list.append(resa)
985 if lease_filter_dict is None:
986 reservation_list = unfiltered_reservation_list
988 self.iotlab_db.update_jobs_in_iotlabdb(job_oar_list, jobs_psql_id_list)
990 logger.debug(" IOTLABDRIVER.PY \tGetLeases reservation_list %s"
991 % (reservation_list))
992 return reservation_list
997 #TODO FUNCTIONS SECTION 04/07/2012 SA
999 ##TODO : Is UnBindObjectFromPeer still necessary ? Currently does nothing
1002 #def UnBindObjectFromPeer( auth, object_type, object_id, shortname):
1003 #""" This method is a hopefully temporary hack to let the sfa correctly
1004 #detach the objects it creates from a remote peer object. This is
1005 #needed so that the sfa federation link can work in parallel with
1006 #RefreshPeer, as RefreshPeer depends on remote objects being correctly
1009 #auth : struct, API authentication structure
1010 #AuthMethod : string, Authentication method to use
1011 #object_type : string, Object type, among 'site','person','slice',
1013 #object_id : int, object_id
1014 #shortname : string, peer shortname
1018 #logger.warning("IOTLABDRIVER \tUnBindObjectFromPeer EMPTY-\
1022 ##TODO Is BindObjectToPeer still necessary ? Currently does nothing
1024 #|| Commented out 28/05/13 SA
1025 #def BindObjectToPeer(self, auth, object_type, object_id, shortname=None, \
1026 #remote_object_id=None):
1027 #"""This method is a hopefully temporary hack to let the sfa correctly
1028 #attach the objects it creates to a remote peer object. This is needed
1029 #so that the sfa federation link can work in parallel with RefreshPeer,
1030 #as RefreshPeer depends on remote objects being correctly marked.
1032 #shortname : string, peer shortname
1033 #remote_object_id : int, remote object_id, set to 0 if unknown
1037 #logger.warning("IOTLABDRIVER \tBindObjectToPeer EMPTY - DO NOTHING \r\n ")
1040 ##TODO UpdateSlice 04/07/2012 SA || Commented out 28/05/13 SA
1041 ##Funciton should delete and create another job since oin iotlab slice=job
1042 #def UpdateSlice(self, auth, slice_id_or_name, slice_fields=None):
1043 #"""Updates the parameters of an existing slice with the values in
1045 #Users may only update slices of which they are members.
1046 #PIs may update any of the slices at their sites, or any slices of
1047 #which they are members. Admins may update any slice.
1048 #Only PIs and admins may update max_nodes. Slices cannot be renewed
1049 #(by updating the expires parameter) more than 8 weeks into the future.
1050 #Returns 1 if successful, faults otherwise.
1054 #logger.warning("IOTLABDRIVER UpdateSlice EMPTY - DO NOTHING \r\n ")
1057 #Unused SA 30/05/13, we only update the user's key or we delete it.
1058 ##TODO UpdatePerson 04/07/2012 SA
1059 #def UpdatePerson(self, iotlab_hrn, federated_hrn, person_fields=None):
1060 #"""Updates a person. Only the fields specified in person_fields
1061 #are updated, all other fields are left untouched.
1062 #Users and techs can only update themselves. PIs can only update
1063 #themselves and other non-PIs at their sites.
1064 #Returns 1 if successful, faults otherwise.
1068 ##new_row = FederatedToIotlab(iotlab_hrn, federated_hrn)
1069 ##self.iotlab_db.iotlab_session.add(new_row)
1070 ##self.iotlab_db.iotlab_session.commit()
1072 #logger.debug("IOTLABDRIVER UpdatePerson EMPTY - DO NOTHING \r\n ")
1076 def GetKeys(key_filter=None):
1077 """Returns a dict of dict based on the key string. Each dict entry
1078 contains the key id, the ssh key, the user's email and the
1080 If key_filter is specified and is an array of key identifiers,
1081 only keys matching the filter will be returned.
1083 Admin may query all keys. Non-admins may only query their own keys.
1086 :returns: dict with ssh key as key and dicts as value.
1089 if key_filter is None:
1090 keys = dbsession.query(RegKey).options(joinedload('reg_user')).all()
1092 keys = dbsession.query(RegKey).options(joinedload('reg_user')).filter(RegKey.key.in_(key_filter)).all()
1096 key_dict[key.key] = {'key_id': key.key_id, 'key': key.key,
1097 'email': key.reg_user.email,
1098 'hrn': key.reg_user.hrn}
1100 #ldap_rslt = self.ldap.LdapSearch({'enabled']=True})
1101 #user_by_email = dict((user[1]['mail'][0], user[1]['sshPublicKey']) \
1102 #for user in ldap_rslt)
1104 logger.debug("IOTLABDRIVER GetKeys -key_dict %s \r\n " % (key_dict))
1108 def DeleteKey(self, user_record, key_string):
1109 """Deletes a key in the LDAP entry of the specified user.
1111 Removes the key_string from the user's key list and updates the LDAP
1112 user's entry with the new key attributes.
1114 :param key_string: The ssh key to remove
1115 :param user_record: User's record
1116 :type key_string: string
1117 :type user_record: dict
1118 :returns: True if sucessful, False if not.
1122 all_user_keys = user_record['keys']
1123 all_user_keys.remove(key_string)
1124 new_attributes = {'sshPublicKey':all_user_keys}
1125 ret = self.ldap.LdapModifyUser(user_record, new_attributes)
1126 logger.debug("IOTLABDRIVER DeleteKey %s- " % (ret))
1133 def _sql_get_slice_info( slice_filter ):
1135 Get the slice record based on the slice hrn. Fetch the record of the
1136 user associated with the slice by using joinedload based on the
1137 reg_researcher relationship.
1139 :param slice_filter: the slice hrn we are looking for
1140 :type slice_filter: string
1141 :returns: the slice record enhanced with the user's information if the
1142 slice was found, None it wasn't.
1144 :rtype: dict or None.
1146 #DO NOT USE RegSlice - reg_researchers to get the hrn
1147 #of the user otherwise will mess up the RegRecord in
1148 #Resolve, don't know why - SA 08/08/2012
1150 #Only one entry for one user = one slice in iotlab_xp table
1151 #slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
1152 raw_slicerec = dbsession.query(RegSlice).options(joinedload('reg_researchers')).filter_by(hrn=slice_filter).first()
1153 #raw_slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
1155 #load_reg_researcher
1156 #raw_slicerec.reg_researchers
1157 raw_slicerec = raw_slicerec.__dict__
1158 logger.debug(" IOTLABDRIVER \t get_slice_info slice_filter %s \
1159 raw_slicerec %s" % (slice_filter, raw_slicerec))
1160 slicerec = raw_slicerec
1161 #only one researcher per slice so take the first one
1162 #slicerec['reg_researchers'] = raw_slicerec['reg_researchers']
1163 #del slicerec['reg_researchers']['_sa_instance_state']
1170 def _sql_get_slice_info_from_user(slice_filter):
1172 Get the slice record based on the user recordid by using a joinedload
1173 on the relationship reg_slices_as_researcher. Format the sql record
1174 into a dict with the mandatory fields for user and slice.
1175 :returns: dict with slice record and user record if the record was found
1176 based on the user's id, None if not..
1177 :rtype:dict or None..
1179 #slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
1180 raw_slicerec = dbsession.query(RegUser).options(joinedload('reg_slices_as_researcher')).filter_by(record_id=slice_filter).first()
1181 #raw_slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
1182 #Put it in correct order
1183 user_needed_fields = ['peer_authority', 'hrn', 'last_updated',
1184 'classtype', 'authority', 'gid', 'record_id',
1185 'date_created', 'type', 'email', 'pointer']
1186 slice_needed_fields = ['peer_authority', 'hrn', 'last_updated',
1187 'classtype', 'authority', 'gid', 'record_id',
1188 'date_created', 'type', 'pointer']
1190 #raw_slicerec.reg_slices_as_researcher
1191 raw_slicerec = raw_slicerec.__dict__
1194 dict([(k, raw_slicerec[
1195 'reg_slices_as_researcher'][0].__dict__[k])
1196 for k in slice_needed_fields])
1197 slicerec['reg_researchers'] = dict([(k, raw_slicerec[k])
1198 for k in user_needed_fields])
1199 #TODO Handle multiple slices for one user SA 10/12/12
1200 #for now only take the first slice record associated to the rec user
1201 ##slicerec = raw_slicerec['reg_slices_as_researcher'][0].__dict__
1202 #del raw_slicerec['reg_slices_as_researcher']
1203 #slicerec['reg_researchers'] = raw_slicerec
1204 ##del slicerec['_sa_instance_state']
1211 def _get_slice_records(self, slice_filter=None,
1212 slice_filter_type=None):
1214 Get the slice record depending on the slice filter and its type.
1215 :param slice_filter: Can be either the slice hrn or the user's record
1217 :type slice_filter: string
1218 :param slice_filter_type: describes the slice filter type used, can be
1219 slice_hrn or record_id_user
1221 :returns: the slice record
1223 .. seealso::_sql_get_slice_info_from_user
1224 .. seealso:: _sql_get_slice_info
1227 #Get list of slices based on the slice hrn
1228 if slice_filter_type == 'slice_hrn':
1230 #if get_authority(slice_filter) == self.root_auth:
1231 #login = slice_filter.split(".")[1].split("_")[0]
1233 slicerec = self._sql_get_slice_info(slice_filter)
1235 if slicerec is None:
1239 #Get slice based on user id
1240 if slice_filter_type == 'record_id_user':
1242 slicerec = self._sql_get_slice_info_from_user(slice_filter)
1245 fixed_slicerec_dict = slicerec
1246 #At this point if there is no login it means
1247 #record_id_user filter has been used for filtering
1249 ##If theslice record is from iotlab
1250 #if fixed_slicerec_dict['peer_authority'] is None:
1251 #login = fixed_slicerec_dict['hrn'].split(".")[1].split("_")[0]
1252 #return login, fixed_slicerec_dict
1253 return fixed_slicerec_dict
1256 def GetSlices(self, slice_filter=None, slice_filter_type=None,
1258 """Get the slice records from the iotlab db and add lease information
1261 :param slice_filter: can be the slice hrn or slice record id in the db
1262 depending on the slice_filter_type.
1263 :param slice_filter_type: defines the type of the filtering used, Can be
1264 either 'slice_hrn' or "record_id'.
1265 :type slice_filter: string
1266 :type slice_filter_type: string
1267 :returns: a slice dict if slice_filter and slice_filter_type
1268 are specified and a matching entry is found in the db. The result
1269 is put into a list.Or a list of slice dictionnaries if no filters
1276 authorized_filter_types_list = ['slice_hrn', 'record_id_user']
1277 return_slicerec_dictlist = []
1279 #First try to get information on the slice based on the filter provided
1280 if slice_filter_type in authorized_filter_types_list:
1281 fixed_slicerec_dict = self._get_slice_records(slice_filter,
1283 slice_hrn = fixed_slicerec_dict['hrn']
1285 logger.debug(" IOTLABDRIVER \tGetSlices login %s \
1286 slice record %s slice_filter %s \
1287 slice_filter_type %s " % (login,
1288 fixed_slicerec_dict, slice_filter,
1292 #Now we have the slice record fixed_slicerec_dict, get the
1293 #jobs associated to this slice
1296 leases_list = self.GetLeases(login=login)
1297 #If no job is running or no job scheduled
1298 #return only the slice record
1299 if leases_list == [] and fixed_slicerec_dict:
1300 return_slicerec_dictlist.append(fixed_slicerec_dict)
1302 #If several jobs for one slice , put the slice record into
1303 # each lease information dict
1305 for lease in leases_list:
1307 logger.debug("IOTLABDRIVER.PY \tGetSlices slice_filter %s \
1308 \t lease['slice_hrn'] %s"
1309 % (slice_filter, lease['slice_hrn']))
1310 if lease['slice_hrn'] == slice_hrn:
1311 slicerec_dict['oar_job_id'] = lease['lease_id']
1312 #Update lease dict with the slice record
1313 if fixed_slicerec_dict:
1314 fixed_slicerec_dict['oar_job_id'] = []
1315 fixed_slicerec_dict['oar_job_id'].append(
1316 slicerec_dict['oar_job_id'])
1317 slicerec_dict.update(fixed_slicerec_dict)
1318 #slicerec_dict.update({'hrn':\
1319 #str(fixed_slicerec_dict['slice_hrn'])})
1320 slicerec_dict['slice_hrn'] = lease['slice_hrn']
1321 slicerec_dict['hrn'] = lease['slice_hrn']
1322 slicerec_dict['user'] = lease['user']
1323 slicerec_dict.update(
1325 {'hostname': lease['reserved_nodes']}})
1326 slicerec_dict.update({'node_ids': lease['reserved_nodes']})
1330 return_slicerec_dictlist.append(slicerec_dict)
1331 logger.debug("IOTLABDRIVER.PY \tGetSlices \
1332 OHOHOHOH %s" %(return_slicerec_dictlist))
1334 logger.debug("IOTLABDRIVER.PY \tGetSlices \
1335 slicerec_dict %s return_slicerec_dictlist %s \
1336 lease['reserved_nodes'] \
1337 %s" % (slicerec_dict, return_slicerec_dictlist,
1338 lease['reserved_nodes']))
1340 logger.debug("IOTLABDRIVER.PY \tGetSlices RETURN \
1341 return_slicerec_dictlist %s"
1342 % (return_slicerec_dictlist))
1344 return return_slicerec_dictlist
1348 #Get all slices from the iotlab sfa database ,
1349 #put them in dict format
1350 #query_slice_list = dbsession.query(RegRecord).all()
1351 query_slice_list = \
1352 dbsession.query(RegSlice).options(joinedload('reg_researchers')).all()
1354 for record in query_slice_list:
1355 tmp = record.__dict__
1356 tmp['reg_researchers'] = tmp['reg_researchers'][0].__dict__
1357 #del tmp['reg_researchers']['_sa_instance_state']
1358 return_slicerec_dictlist.append(tmp)
1359 #return_slicerec_dictlist.append(record.__dict__)
1361 #Get all the jobs reserved nodes
1362 leases_list = self.GetReservedNodes()
1364 for fixed_slicerec_dict in return_slicerec_dictlist:
1366 #Check if the slice belongs to a iotlab user
1367 if fixed_slicerec_dict['peer_authority'] is None:
1368 owner = fixed_slicerec_dict['hrn'].split(
1369 ".")[1].split("_")[0]
1372 for lease in leases_list:
1373 if owner == lease['user']:
1374 slicerec_dict['oar_job_id'] = lease['lease_id']
1376 #for reserved_node in lease['reserved_nodes']:
1377 logger.debug("IOTLABDRIVER.PY \tGetSlices lease %s "
1379 slicerec_dict.update(fixed_slicerec_dict)
1380 slicerec_dict.update({'node_ids':
1381 lease['reserved_nodes']})
1382 slicerec_dict.update({'list_node_ids':
1384 lease['reserved_nodes']}})
1386 #slicerec_dict.update({'hrn':\
1387 #str(fixed_slicerec_dict['slice_hrn'])})
1388 #return_slicerec_dictlist.append(slicerec_dict)
1389 fixed_slicerec_dict.update(slicerec_dict)
1391 logger.debug("IOTLABDRIVER.PY \tGetSlices RETURN \
1392 return_slicerec_dictlist %s \slice_filter %s " \
1393 %(return_slicerec_dictlist, slice_filter))
1395 return return_slicerec_dictlist
1399 #Update slice unused, therefore sfa_fields_to_iotlab_fields unused
1402 #def sfa_fields_to_iotlab_fields(sfa_type, hrn, record):
1407 ##for field in record:
1408 ## iotlab_record[field] = record[field]
1410 #if sfa_type == "slice":
1411 ##instantion used in get_slivers ?
1412 #if not "instantiation" in iotlab_record:
1413 #iotlab_record["instantiation"] = "iotlab-instantiated"
1414 ##iotlab_record["hrn"] = hrn_to_pl_slicename(hrn)
1415 ##Unused hrn_to_pl_slicename because Iotlab's hrn already
1416 ##in the appropriate form SA 23/07/12
1417 #iotlab_record["hrn"] = hrn
1418 #logger.debug("IOTLABDRIVER.PY sfa_fields_to_iotlab_fields \
1419 #iotlab_record %s " %(iotlab_record['hrn']))
1420 #if "url" in record:
1421 #iotlab_record["url"] = record["url"]
1422 #if "description" in record:
1423 #iotlab_record["description"] = record["description"]
1424 #if "expires" in record:
1425 #iotlab_record["expires"] = int(record["expires"])
1427 ##nodes added by OAR only and then imported to SFA
1428 ##elif type == "node":
1429 ##if not "hostname" in iotlab_record:
1430 ##if not "hostname" in record:
1431 ##raise MissingSfaInfo("hostname")
1432 ##iotlab_record["hostname"] = record["hostname"]
1433 ##if not "model" in iotlab_record:
1434 ##iotlab_record["model"] = "geni"
1436 ##One authority only
1437 ##elif type == "authority":
1438 ##iotlab_record["login_base"] = hrn_to_iotlab_login_base(hrn)
1440 ##if not "name" in iotlab_record:
1441 ##iotlab_record["name"] = hrn
1443 ##if not "abbreviated_name" in iotlab_record:
1444 ##iotlab_record["abbreviated_name"] = hrn
1446 ##if not "enabled" in iotlab_record:
1447 ##iotlab_record["enabled"] = True
1449 ##if not "is_public" in iotlab_record:
1450 ##iotlab_record["is_public"] = True
1452 #return iotlab_record