2 File containing the IotlabShell, 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 sqlalchemy.orm import joinedload
12 from sfa.storage.model import RegRecord, RegUser, RegSlice, RegKey
13 from sfa.iotlab.iotlabpostgres import TestbedAdditionalSfaDB, LeaseTableXP
14 from sfa.iotlab.OARrestapi import OARrestapi
15 from sfa.iotlab.LDAPapi import LDAPapi
17 from sfa.util.xrn import Xrn, hrn_to_urn, get_authority
19 from sfa.trust.certificate import Keypair, convert_public_key
20 from sfa.trust.gid import create_uuid
21 from sfa.trust.hierarchy import Hierarchy
23 from sfa.iotlab.iotlabxrn import xrn_object
26 """ Class enabled to use LDAP and OAR api calls. """
28 _MINIMUM_DURATION = 10 # 10 units of granularity 60 s, 10 mins
30 def __init__(self, api):
31 """Creates an instance of OARrestapi and LDAPapi which will be used to
32 issue calls to OAR or LDAP methods.
33 Set the time format and the testbed granularity used for OAR
34 reservation and leases.
36 :param config: configuration object from sfa.util.config
37 :type config: Config object
41 self.leases_db = TestbedAdditionalSfaDB(config)
42 self.oar = OARrestapi()
44 self.time_format = "%Y-%m-%d %H:%M:%S"
45 self.root_auth = config.SFA_REGISTRY_ROOT_AUTH
46 self.grain = 60 # 10 mins lease minimum, 60 sec granularity
47 #import logging, logging.handlers
48 #from sfa.util.sfalogging import _SfaLogger
49 #sql_logger = _SfaLogger(loggername = 'sqlalchemy.engine', \
54 def GetMinExperimentDurationInGranularity():
55 """ Returns the minimum allowed duration for an experiment on the
59 return IotlabShell._MINIMUM_DURATION
62 def GetPeers(self, peer_filter=None ):
63 """ Gathers registered authorities in SFA DB and looks for specific peer
64 if peer_filter is specified.
65 :param peer_filter: name of the site authority looked for.
66 :type peer_filter: string
67 :returns: list of records.
72 existing_hrns_by_types = {}
73 logger.debug("IOTLAB_API \tGetPeers peer_filter %s " % (peer_filter))
74 all_records = self.api.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)
83 logger.debug("IOTLAB_API \tGetPeer\texisting_hrns_by_types %s "
84 % (existing_hrns_by_types))
89 records_list.append(existing_records[(peer_filter,
92 for hrn in existing_hrns_by_types['authority']:
93 records_list.append(existing_records[(hrn, 'authority')])
95 logger.debug("IOTLAB_API \tGetPeer \trecords_list %s "
101 return_records = records_list
102 logger.debug("IOTLAB_API \tGetPeer return_records %s "
104 return return_records
106 #TODO : Handling OR request in make_ldap_filters_from_records
107 #instead of the for loop
108 #over the records' list
109 def GetPersons(self, person_filter=None):
111 Get the enabled users and their properties from Iotlab LDAP.
112 If a filter is specified, looks for the user whose properties match
113 the filter, otherwise returns the whole enabled users'list.
115 :param person_filter: Must be a list of dictionnaries with users
116 properties when not set to None.
117 :type person_filter: list of dict
119 :returns: Returns a list of users whose accounts are enabled
121 :rtype: list of dicts
124 logger.debug("IOTLAB_API \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("IOTLAB_API \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("IOTLAB_API \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("IOTLAB_API \t GetJobsId %s " %(job_info))
211 #if job_info['state'] == 'Terminated':
212 #logger.debug("IOTLAB_API \t GetJobsId job %s TERMINATED"\
215 #if job_info['state'] == 'Error':
216 #logger.debug("IOTLAB_API \t GetJobsId ERROR message %s "\
221 #logger.error("IOTLAB_API \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\nIOTLAB_API \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.
248 .. warning: Unused. SA 16/10/13
251 req = "GET_jobs_id_resources"
254 #Get job resources list from OAR
255 node_id_list = self.oar.parser.SendRequest(req, job_id, username)
256 logger.debug("IOTLAB_API \t GetJobsResources %s " %(node_id_list))
259 self.__get_hostnames_from_oar_node_ids(node_id_list)
262 #Replaces the previous entry "assigned_network_address" /
263 #"reserved_resources" with "node_ids"
264 job_info = {'node_ids': hostname_list}
269 #def get_info_on_reserved_nodes(self, job_info, node_list_name):
271 #..warning:unused SA 23/05/13
273 ##Get the list of the testbed nodes records and make a
274 ##dictionnary keyed on the hostname out of it
275 #node_list_dict = self.GetNodes()
276 ##node_hostname_list = []
277 #node_hostname_list = [node['hostname'] for node in node_list_dict]
278 ##for node in node_list_dict:
279 ##node_hostname_list.append(node['hostname'])
280 #node_dict = dict(zip(node_hostname_list, node_list_dict))
282 #reserved_node_hostname_list = []
283 #for index in range(len(job_info[node_list_name])):
284 ##job_info[node_list_name][k] =
285 #reserved_node_hostname_list[index] = \
286 #node_dict[job_info[node_list_name][index]]['hostname']
288 #logger.debug("IOTLAB_API \t get_info_on_reserved_nodes \
289 #reserved_node_hostname_list %s" \
290 #%(reserved_node_hostname_list))
292 #logger.error("IOTLAB_API \t get_info_on_reserved_nodes KEYERROR " )
294 #return reserved_node_hostname_list
296 def GetNodesCurrentlyInUse(self):
297 """Returns a list of all the nodes already involved in an oar running
299 :rtype: list of nodes hostnames.
301 return self.oar.parser.SendRequest("GET_running_jobs")
303 def __get_hostnames_from_oar_node_ids(self, oar_id_node_dict,
305 """Get the hostnames of the nodes from their OAR identifiers.
306 Get the list of nodes dict using GetNodes and find the hostname
307 associated with the identifier.
308 :param oar_id_node_dict: full node dictionary list keyed by oar node id
309 :param resource_id_list: list of nodes identifiers
310 :returns: list of node hostnames.
314 for resource_id in resource_id_list:
315 #Because jobs requested "asap" do not have defined resources
316 if resource_id is not "Undefined":
317 hostname_list.append(\
318 oar_id_node_dict[resource_id]['hostname'])
320 #hostname_list.append(oar_id_node_dict[resource_id]['hostname'])
323 def GetReservedNodes(self, username=None):
324 """ Get list of leases. Get the leases for the username if specified,
325 otherwise get all the leases. Finds the nodes hostnames for each
327 :param username: user's LDAP login
328 :type username: string
329 :returns: list of reservations dict
333 #Get the nodes in use and the reserved nodes
334 reservation_dict_list = \
335 self.oar.parser.SendRequest("GET_reserved_nodes", \
338 # Get the full node dict list once for all
339 # so that we can get the hostnames given their oar node id afterwards
340 # when the reservations are checked.
341 full_nodes_dict_list = self.GetNodes()
342 #Put the full node list into a dictionary keyed by oar node id
343 oar_id_node_dict = {}
344 for node in full_nodes_dict_list:
345 oar_id_node_dict[node['oar_id']] = node
347 for resa in reservation_dict_list:
348 logger.debug ("GetReservedNodes resa %s"%(resa))
349 #dict list of hostnames and their site
350 resa['reserved_nodes'] = \
351 self.__get_hostnames_from_oar_node_ids(oar_id_node_dict,
352 resa['resource_ids'])
354 #del resa['resource_ids']
355 return reservation_dict_list
357 def GetNodes(self, node_filter_dict=None, return_fields_list=None):
360 Make a list of iotlab nodes and their properties from information
361 given by OAR. Search for specific nodes if some filters are
362 specified. Nodes properties returned if no return_fields_list given:
363 'hrn','archi','mobile','hostname','site','boot_state','node_id',
364 'radio','posx','posy','oar_id','posz'.
366 :param node_filter_dict: dictionnary of lists with node properties. For
367 instance, if you want to look for a specific node with its hrn,
368 the node_filter_dict should be {'hrn': [hrn_of_the_node]}
369 :type node_filter_dict: dict
370 :param return_fields_list: list of specific fields the user wants to be
372 :type return_fields_list: list
373 :returns: list of dictionaries with node properties
377 node_dict_by_id = self.oar.parser.SendRequest("GET_resources_full")
378 node_dict_list = node_dict_by_id.values()
379 logger.debug (" IOTLAB_API GetNodes node_filter_dict %s \
380 return_fields_list %s " % (node_filter_dict, return_fields_list))
381 #No filtering needed return the list directly
382 if not (node_filter_dict or return_fields_list):
383 return node_dict_list
385 return_node_list = []
387 for filter_key in node_filter_dict:
389 #Filter the node_dict_list by each value contained in the
390 #list node_filter_dict[filter_key]
391 for value in node_filter_dict[filter_key]:
392 for node in node_dict_list:
393 if node[filter_key] == value:
394 if return_fields_list:
396 for k in return_fields_list:
398 return_node_list.append(tmp)
400 return_node_list.append(node)
402 logger.log_exc("GetNodes KeyError")
406 return return_node_list
409 def AddSlice(self, slice_record, user_record):
412 Add slice to the local iotlab sfa tables if the slice comes
413 from a federated site and is not yet in the iotlab sfa DB,
414 although the user has already a LDAP login.
415 Called by verify_slice during lease/sliver creation.
417 :param slice_record: record of slice, must contain hrn, gid, slice_id
418 and authority of the slice.
419 :type slice_record: dictionary
420 :param user_record: record of the user
421 :type user_record: RegUser
425 sfa_record = RegSlice(hrn=slice_record['hrn'],
426 gid=slice_record['gid'],
427 pointer=slice_record['slice_id'],
428 authority=slice_record['authority'])
429 logger.debug("IOTLAB_API.PY AddSlice sfa_record %s user_record %s"
430 % (sfa_record, user_record))
431 sfa_record.just_created()
432 self.api.dbsession().add(sfa_record)
433 self.api.dbsession().commit()
434 #Update the reg-researcher dependance table
435 sfa_record.reg_researchers = [user_record]
436 self.api.dbsession().commit()
441 def GetSites(self, site_filter_name_list=None, return_fields_list=None):
442 """Returns the list of Iotlab's sites with the associated nodes and
443 the sites' properties as dictionaries.
446 ['address_ids', 'slice_ids', 'name', 'node_ids', 'url', 'person_ids',
447 'site_tag_ids', 'enabled', 'site', 'longitude', 'pcu_ids',
448 'max_slivers', 'max_slices', 'ext_consortium_id', 'date_created',
449 'latitude', 'is_public', 'peer_site_id', 'peer_id', 'abbreviated_name']
450 Uses the OAR request GET_sites to find the Iotlab's sites.
452 :param site_filter_name_list: used to specify specific sites
453 :param return_fields_list: field that has to be returned
454 :type site_filter_name_list: list
455 :type return_fields_list: list
459 site_dict = self.oar.parser.SendRequest("GET_sites")
460 #site_dict : dict where the key is the sit ename
461 return_site_list = []
462 if not (site_filter_name_list or return_fields_list):
463 return_site_list = site_dict.values()
464 return return_site_list
466 for site_filter_name in site_filter_name_list:
467 if site_filter_name in site_dict:
468 if return_fields_list:
469 for field in return_fields_list:
472 tmp[field] = site_dict[site_filter_name][field]
474 logger.error("GetSites KeyError %s " % (field))
476 return_site_list.append(tmp)
478 return_site_list.append(site_dict[site_filter_name])
480 return return_site_list
483 #TODO : Check rights to delete person
484 def DeletePerson(self, person_record):
485 """Disable an existing account in iotlab LDAP.
487 Users and techs can only delete themselves. PIs can only
488 delete themselves and other non-PIs at their sites.
489 ins can delete anyone.
491 :param person_record: user's record
492 :type person_record: dict
493 :returns: True if successful, False otherwise.
496 .. todo:: CHECK THAT ONLY THE USER OR ADMIN CAN DEL HIMSELF.
498 #Disable user account in iotlab LDAP
499 ret = self.ldap.LdapMarkUserAsDeleted(person_record)
500 logger.warning("IOTLAB_API DeletePerson %s " % (person_record))
503 def DeleteSlice(self, slice_record):
504 """Deletes the specified slice and kills the jobs associated with
505 the slice if any, using DeleteSliceFromNodes.
507 :param slice_record: record of the slice, must contain oar_job_id, user
508 :type slice_record: dict
509 :returns: True if all the jobs in the slice have been deleted,
510 or the list of jobs that could not be deleted otherwise.
511 :rtype: list or boolean
513 .. seealso:: DeleteSliceFromNodes
516 ret = self.DeleteSliceFromNodes(slice_record)
519 if False in ret[job_id]:
520 if delete_failed is None:
522 delete_failed.append(job_id)
524 logger.info("IOTLAB_API DeleteSlice %s answer %s"%(slice_record, \
526 return delete_failed or True
529 def __add_person_to_db(self, user_dict):
531 Add a federated user straight to db when the user issues a lease
532 request with iotlab nodes and that he has not registered with iotlab
533 yet (that is he does not have a LDAP entry yet).
534 Uses parts of the routines in IotlabImport when importing user from LDAP.
535 Called by AddPerson, right after LdapAddUser.
536 :param user_dict: Must contain email, hrn and pkey to get a GID
537 and be added to the SFA db.
538 :type user_dict: dict
542 self.api.dbsession().query(RegUser).filter_by(email = user_dict['email']).first()
544 if not check_if_exists:
545 logger.debug("__add_person_to_db \t Adding %s \r\n \r\n \
547 hrn = user_dict['hrn']
548 person_urn = hrn_to_urn(hrn, 'user')
549 pubkey = user_dict['pkey']
551 pkey = convert_public_key(pubkey)
553 #key not good. create another pkey
554 logger.warn('__add_person_to_db: unable to convert public \
556 pkey = Keypair(create=True)
559 if pubkey is not None and pkey is not None :
560 hierarchy = Hierarchy()
561 person_gid = hierarchy.create_gid(person_urn, create_uuid(), \
563 if user_dict['email']:
564 logger.debug("__add_person_to_db \r\n \r\n \
565 IOTLAB IMPORTER PERSON EMAIL OK email %s "\
566 %(user_dict['email']))
567 person_gid.set_email(user_dict['email'])
569 user_record = RegUser(hrn=hrn , pointer= '-1', \
570 authority=get_authority(hrn), \
571 email=user_dict['email'], gid = person_gid)
572 user_record.reg_keys = [RegKey(user_dict['pkey'])]
573 user_record.just_created()
574 self.api.dbsession().add (user_record)
575 self.api.dbsession().commit()
579 def AddPerson(self, record):
582 Adds a new account. Any fields specified in records are used,
583 otherwise defaults are used. Creates an appropriate login by calling
586 :param record: dictionary with the sfa user's properties.
587 :returns: a dicitonary with the status. If successful, the dictionary
588 boolean is set to True and there is a 'uid' key with the new login
589 added to LDAP, otherwise the bool is set to False and a key
590 'message' is in the dictionary, with the error message.
594 ret = self.ldap.LdapAddUser(record)
596 if ret['bool'] is True:
597 record['hrn'] = self.root_auth + '.' + ret['uid']
598 logger.debug("IOTLAB_API AddPerson return code %s record %s "
600 self.__add_person_to_db(record)
607 #TODO AddPersonKey 04/07/2012 SA
608 def AddPersonKey(self, person_uid, old_attributes_dict, new_key_dict):
609 """Adds a new key to the specified account. Adds the key to the
610 iotlab ldap, provided that the person_uid is valid.
612 Non-admins can only modify their own keys.
614 :param person_uid: user's iotlab login in LDAP
615 :param old_attributes_dict: dict with the user's old sshPublicKey
616 :param new_key_dict: dict with the user's new sshPublicKey
617 :type person_uid: string
621 :returns: True if the key has been modified, False otherwise.
624 ret = self.ldap.LdapModify(person_uid, old_attributes_dict, \
626 logger.warning("IOTLAB_API AddPersonKey EMPTY - DO NOTHING \r\n ")
629 def DeleteLeases(self, leases_id_list, slice_hrn):
632 Deletes several leases, based on their job ids and the slice
633 they are associated with. Uses DeleteJobs to delete the jobs
634 on OAR. Note that one slice can contain multiple jobs, and in this
635 case all the jobs in the leases_id_list MUST belong to ONE slice,
636 since there is only one slice hrn provided here.
638 :param leases_id_list: list of job ids that belong to the slice whose
639 slice hrn is provided.
640 :param slice_hrn: the slice hrn.
641 :type slice_hrn: string
643 .. warning:: Does not have a return value since there was no easy
644 way to handle failure when dealing with multiple job delete. Plus,
645 there was no easy way to report it to the user.
648 logger.debug("IOTLAB_API DeleteLeases leases_id_list %s slice_hrn %s \
649 \r\n " %(leases_id_list, slice_hrn))
650 for job_id in leases_id_list:
651 self.DeleteJobs(job_id, slice_hrn)
656 def _process_walltime(duration):
657 """ Calculates the walltime in seconds from the duration in H:M:S
658 specified in the RSpec.
662 # Fixing the walltime by adding a few delays.
663 # First put the walltime in seconds oarAdditionalDelay = 20;
664 # additional delay for /bin/sleep command to
665 # take in account prologue and epilogue scripts execution
666 # int walltimeAdditionalDelay = 240; additional delay
667 #for prologue/epilogue execution = $SERVER_PROLOGUE_EPILOGUE_TIMEOUT
669 # Put the duration in seconds first
670 #desired_walltime = duration * 60
671 desired_walltime = duration
672 total_walltime = desired_walltime + 240 #+4 min Update SA 23/10/12
673 sleep_walltime = desired_walltime # 0 sec added Update SA 23/10/12
675 #Put the walltime back in str form
677 walltime.append(str(total_walltime / 3600))
678 total_walltime = total_walltime - 3600 * int(walltime[0])
679 #Get the remaining minutes
680 walltime.append(str(total_walltime / 60))
681 total_walltime = total_walltime - 60 * int(walltime[1])
683 walltime.append(str(total_walltime))
686 logger.log_exc(" __process_walltime duration null")
688 return walltime, sleep_walltime
691 def _create_job_structure_request_for_OAR(lease_dict):
692 """ Creates the structure needed for a correct POST on OAR.
693 Makes the timestamp transformation into the appropriate format.
694 Sends the POST request to create the job with the resources in
703 reqdict['workdir'] = '/tmp'
704 reqdict['resource'] = "{network_address in ("
706 for node in lease_dict['added_nodes']:
707 logger.debug("\r\n \r\n OARrestapi \t \
708 __create_job_structure_request_for_OAR node %s" %(node))
710 # Get the ID of the node
712 reqdict['resource'] += "'" + nodeid + "', "
713 nodeid_list.append(nodeid)
715 custom_length = len(reqdict['resource'])- 2
716 reqdict['resource'] = reqdict['resource'][0:custom_length] + \
717 ")}/nodes=" + str(len(nodeid_list))
720 walltime, sleep_walltime = \
721 IotlabShell._process_walltime(\
722 int(lease_dict['lease_duration']))
725 reqdict['resource'] += ",walltime=" + str(walltime[0]) + \
726 ":" + str(walltime[1]) + ":" + str(walltime[2])
727 reqdict['script_path'] = "/bin/sleep " + str(sleep_walltime)
729 #In case of a scheduled experiment (not immediate)
730 #To run an XP immediately, don't specify date and time in RSpec
731 #They will be set to None.
732 if lease_dict['lease_start_time'] is not '0':
733 #Readable time accepted by OAR
734 start_time = datetime.fromtimestamp( \
735 int(lease_dict['lease_start_time'])).\
736 strftime(lease_dict['time_format'])
737 reqdict['reservation'] = start_time
738 #If there is not start time, Immediate XP. No need to add special
742 reqdict['type'] = "deploy"
743 reqdict['directory'] = ""
744 reqdict['name'] = "SFA_" + lease_dict['slice_user']
749 def LaunchExperimentOnOAR(self, added_nodes, slice_name, \
750 lease_start_time, lease_duration, slice_user=None):
753 Create a job request structure based on the information provided
754 and post the job on OAR.
755 :param added_nodes: list of nodes that belong to the described lease.
756 :param slice_name: the slice hrn associated to the lease.
757 :param lease_start_time: timestamp of the lease startting time.
758 :param lease_duration: lease durationin minutes
762 lease_dict['lease_start_time'] = lease_start_time
763 lease_dict['lease_duration'] = lease_duration
764 lease_dict['added_nodes'] = added_nodes
765 lease_dict['slice_name'] = slice_name
766 lease_dict['slice_user'] = slice_user
767 lease_dict['grain'] = self.GetLeaseGranularity()
768 lease_dict['time_format'] = self.time_format
771 logger.debug("IOTLAB_API.PY \tLaunchExperimentOnOAR slice_user %s\
772 \r\n " %(slice_user))
773 #Create the request for OAR
774 reqdict = self._create_job_structure_request_for_OAR(lease_dict)
775 # first step : start the OAR job and update the job
776 logger.debug("IOTLAB_API.PY \tLaunchExperimentOnOAR reqdict %s\
779 answer = self.oar.POSTRequestToOARRestAPI('POST_job', \
781 logger.debug("IOTLAB_API \tLaunchExperimentOnOAR jobid %s " %(answer))
785 logger.log_exc("IOTLAB_API \tLaunchExperimentOnOAR \
786 Impossible to create job %s " %(answer))
793 logger.debug("IOTLAB_API \tLaunchExperimentOnOAR jobid %s \
794 added_nodes %s slice_user %s" %(jobid, added_nodes, \
801 def AddLeases(self, hostname_list, slice_record,
802 lease_start_time, lease_duration):
804 """Creates a job in OAR corresponding to the information provided
805 as parameters. Adds the job id and the slice hrn in the iotlab
806 database so that we are able to know which slice has which nodes.
808 :param hostname_list: list of nodes' OAR hostnames.
809 :param slice_record: sfa slice record, must contain login and hrn.
810 :param lease_start_time: starting time , unix timestamp format
811 :param lease_duration: duration in minutes
813 :type hostname_list: list
814 :type slice_record: dict
815 :type lease_start_time: integer
816 :type lease_duration: integer
819 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases hostname_list %s \
820 slice_record %s lease_start_time %s lease_duration %s "\
821 %( hostname_list, slice_record , lease_start_time, \
824 #tmp = slice_record['reg-researchers'][0].split(".")
825 username = slice_record['login']
826 #username = tmp[(len(tmp)-1)]
827 job_id = self.LaunchExperimentOnOAR(hostname_list, \
828 slice_record['hrn'], \
829 lease_start_time, lease_duration, \
832 datetime.fromtimestamp(int(lease_start_time)).\
833 strftime(self.time_format)
834 end_time = lease_start_time + lease_duration
837 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases TURN ON LOGGING SQL \
838 %s %s %s "%(slice_record['hrn'], job_id, end_time))
841 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases %s %s %s " \
842 %(type(slice_record['hrn']), type(job_id), type(end_time)))
844 iotlab_ex_row = LeaseTableXP(slice_hrn = slice_record['hrn'], experiment_id=job_id,
847 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases iotlab_ex_row %s" \
849 self.leases_db.testbed_session.add(iotlab_ex_row)
850 self.leases_db.testbed_session.commit()
852 logger.debug("IOTLAB_API \t AddLeases hostname_list start_time %s " \
858 #Delete the jobs from job_iotlab table
859 def DeleteSliceFromNodes(self, slice_record):
862 Deletes all the running or scheduled jobs of a given slice
865 :param slice_record: record of the slice, must contain oar_job_id, user
866 :type slice_record: dict
868 :returns: dict of the jobs'deletion status. Success= True, Failure=
869 False, for each job id.
873 logger.debug("IOTLAB_API \t DeleteSliceFromNodes %s "
876 if isinstance(slice_record['oar_job_id'], list):
878 for job_id in slice_record['oar_job_id']:
879 ret = self.DeleteJobs(job_id, slice_record['user'])
881 oar_bool_answer.update(ret)
884 oar_bool_answer = [self.DeleteJobs(slice_record['oar_job_id'],
885 slice_record['user'])]
887 return oar_bool_answer
891 def GetLeaseGranularity(self):
892 """ Returns the granularity of an experiment in the Iotlab testbed.
893 OAR uses seconds for experiments duration , the granulaity is also
895 Experiments which last less than 10 min (600 sec) are invalid"""
901 def filter_lease_name(reservation_list, filter_value):
902 filtered_reservation_list = list(reservation_list)
903 logger.debug("IOTLAB_API \t filter_lease_name reservation_list %s" \
904 % (reservation_list))
905 for reservation in reservation_list:
906 if 'slice_hrn' in reservation and \
907 reservation['slice_hrn'] != filter_value:
908 filtered_reservation_list.remove(reservation)
910 logger.debug("IOTLAB_API \t filter_lease_name filtered_reservation_list %s" \
911 % (filtered_reservation_list))
912 return filtered_reservation_list
915 def filter_lease_start_time(reservation_list, filter_value):
916 filtered_reservation_list = list(reservation_list)
918 for reservation in reservation_list:
919 if 't_from' in reservation and \
920 reservation['t_from'] > filter_value:
921 filtered_reservation_list.remove(reservation)
923 return filtered_reservation_list
926 def GetLeases(self, lease_filter_dict=None, login=None):
929 Get the list of leases from OAR with complete information
930 about which slice owns which jobs and nodes.
932 -Fetch all the jobs from OAR (running, waiting..)
933 complete the reservation information with slice hrn
934 found in testbed_xp table. If not available in the table,
935 assume it is a iotlab slice.
936 -Updates the iotlab table, deleting jobs when necessary.
938 :returns: reservation_list, list of dictionaries with 'lease_id',
939 'reserved_nodes','slice_id', 'state', 'user', 'component_id_list',
940 'slice_hrn', 'resource_ids', 't_from', 't_until'
945 unfiltered_reservation_list = self.GetReservedNodes(login)
947 reservation_list = []
948 #Find the slice associated with this user iotlab ldap uid
949 logger.debug(" IOTLAB_API.PY \tGetLeases login %s\
950 unfiltered_reservation_list %s "
951 % (login, unfiltered_reservation_list))
952 #Create user dict first to avoid looking several times for
953 #the same user in LDAP SA 27/07/12
956 jobs_psql_query = self.leases_db.testbed_session.query(LeaseTableXP).all()
957 jobs_psql_dict = dict([(row.experiment_id, row.__dict__)
958 for row in jobs_psql_query])
959 #jobs_psql_dict = jobs_psql_dict)
960 logger.debug("IOTLAB_API \tGetLeases jobs_psql_dict %s"
962 jobs_psql_id_list = [row.experiment_id for row in jobs_psql_query]
964 for resa in unfiltered_reservation_list:
965 logger.debug("IOTLAB_API \tGetLeases USER %s"
967 #Construct list of jobs (runing, waiting..) in oar
968 job_oar_list.append(resa['lease_id'])
969 #If there is information on the job in IOTLAB DB ]
970 #(slice used and job id)
971 if resa['lease_id'] in jobs_psql_dict:
972 job_info = jobs_psql_dict[resa['lease_id']]
973 logger.debug("IOTLAB_API \tGetLeases job_info %s"
975 resa['slice_hrn'] = job_info['slice_hrn']
976 resa['slice_id'] = hrn_to_urn(resa['slice_hrn'], 'slice')
978 #otherwise, assume it is a iotlab slice:
980 resa['slice_id'] = hrn_to_urn(self.root_auth + '.' +
981 resa['user'] + "_slice", 'slice')
982 resa['slice_hrn'] = Xrn(resa['slice_id']).get_hrn()
984 resa['component_id_list'] = []
985 #Transform the hostnames into urns (component ids)
986 for node in resa['reserved_nodes']:
988 iotlab_xrn = xrn_object(self.root_auth, node)
989 resa['component_id_list'].append(iotlab_xrn.urn)
991 if lease_filter_dict:
992 logger.debug("IOTLAB_API \tGetLeases \
993 \r\n leasefilter %s" % ( lease_filter_dict))
995 filter_dict_functions = {
996 'slice_hrn' : IotlabShell.filter_lease_name,
997 't_from' : IotlabShell.filter_lease_start_time
999 reservation_list = list(unfiltered_reservation_list)
1000 for filter_type in lease_filter_dict:
1001 logger.debug("IOTLAB_API \tGetLeases reservation_list %s" \
1002 % (reservation_list))
1003 reservation_list = filter_dict_functions[filter_type](\
1004 reservation_list,lease_filter_dict[filter_type] )
1006 # Filter the reservation list with a maximum timespan so that the
1007 # leases and jobs running after this timestamp do not appear
1008 # in the result leases.
1009 # if 'start_time' in :
1010 # if resa['start_time'] < lease_filter_dict['start_time']:
1011 # reservation_list.append(resa)
1014 # if 'name' in lease_filter_dict and \
1015 # lease_filter_dict['name'] == resa['slice_hrn']:
1016 # reservation_list.append(resa)
1019 if lease_filter_dict is None:
1020 reservation_list = unfiltered_reservation_list
1022 self.leases_db.update_experiments_in_additional_sfa_db(job_oar_list, jobs_psql_id_list)
1024 logger.debug(" IOTLAB_API.PY \tGetLeases reservation_list %s"
1025 % (reservation_list))
1026 return reservation_list
1031 #TODO FUNCTIONS SECTION 04/07/2012 SA
1033 ##TODO : Is UnBindObjectFromPeer still necessary ? Currently does nothing
1036 #def UnBindObjectFromPeer( auth, object_type, object_id, shortname):
1037 #""" This method is a hopefully temporary hack to let the sfa correctly
1038 #detach the objects it creates from a remote peer object. This is
1039 #needed so that the sfa federation link can work in parallel with
1040 #RefreshPeer, as RefreshPeer depends on remote objects being correctly
1043 #auth : struct, API authentication structure
1044 #AuthMethod : string, Authentication method to use
1045 #object_type : string, Object type, among 'site','person','slice',
1047 #object_id : int, object_id
1048 #shortname : string, peer shortname
1052 #logger.warning("IOTLAB_API \tUnBindObjectFromPeer EMPTY-\
1056 ##TODO Is BindObjectToPeer still necessary ? Currently does nothing
1058 #|| Commented out 28/05/13 SA
1059 #def BindObjectToPeer(self, auth, object_type, object_id, shortname=None, \
1060 #remote_object_id=None):
1061 #"""This method is a hopefully temporary hack to let the sfa correctly
1062 #attach the objects it creates to a remote peer object. This is needed
1063 #so that the sfa federation link can work in parallel with RefreshPeer,
1064 #as RefreshPeer depends on remote objects being correctly marked.
1066 #shortname : string, peer shortname
1067 #remote_object_id : int, remote object_id, set to 0 if unknown
1071 #logger.warning("IOTLAB_API \tBindObjectToPeer EMPTY - DO NOTHING \r\n ")
1074 ##TODO UpdateSlice 04/07/2012 SA || Commented out 28/05/13 SA
1075 ##Funciton should delete and create another job since oin iotlab slice=job
1076 #def UpdateSlice(self, auth, slice_id_or_name, slice_fields=None):
1077 #"""Updates the parameters of an existing slice with the values in
1079 #Users may only update slices of which they are members.
1080 #PIs may update any of the slices at their sites, or any slices of
1081 #which they are members. Admins may update any slice.
1082 #Only PIs and admins may update max_nodes. Slices cannot be renewed
1083 #(by updating the expires parameter) more than 8 weeks into the future.
1084 #Returns 1 if successful, faults otherwise.
1088 #logger.warning("IOTLAB_API UpdateSlice EMPTY - DO NOTHING \r\n ")
1091 #Unused SA 30/05/13, we only update the user's key or we delete it.
1092 ##TODO UpdatePerson 04/07/2012 SA
1093 #def UpdatePerson(self, iotlab_hrn, federated_hrn, person_fields=None):
1094 #"""Updates a person. Only the fields specified in person_fields
1095 #are updated, all other fields are left untouched.
1096 #Users and techs can only update themselves. PIs can only update
1097 #themselves and other non-PIs at their sites.
1098 #Returns 1 if successful, faults otherwise.
1102 ##new_row = FederatedToIotlab(iotlab_hrn, federated_hrn)
1103 ##self.leases_db.testbed_session.add(new_row)
1104 ##self.leases_db.testbed_session.commit()
1106 #logger.debug("IOTLAB_API UpdatePerson EMPTY - DO NOTHING \r\n ")
1110 def GetKeys(self, key_filter=None):
1111 """Returns a dict of dict based on the key string. Each dict entry
1112 contains the key id, the ssh key, the user's email and the
1114 If key_filter is specified and is an array of key identifiers,
1115 only keys matching the filter will be returned.
1117 Admin may query all keys. Non-admins may only query their own keys.
1120 :returns: dict with ssh key as key and dicts as value.
1123 if key_filter is None:
1124 keys = self.api.dbsession().query(RegKey).options(joinedload('reg_user')).all()
1126 keys = self.api.dbsession().query(RegKey).options(joinedload('reg_user')).filter(RegKey.key.in_(key_filter)).all()
1130 key_dict[key.key] = {'key_id': key.key_id, 'key': key.key,
1131 'email': key.reg_user.email,
1132 'hrn': key.reg_user.hrn}
1134 #ldap_rslt = self.ldap.LdapSearch({'enabled']=True})
1135 #user_by_email = dict((user[1]['mail'][0], user[1]['sshPublicKey']) \
1136 #for user in ldap_rslt)
1138 logger.debug("IOTLAB_API GetKeys -key_dict %s \r\n " % (key_dict))
1142 def DeleteKey(self, user_record, key_string):
1143 """Deletes a key in the LDAP entry of the specified user.
1145 Removes the key_string from the user's key list and updates the LDAP
1146 user's entry with the new key attributes.
1148 :param key_string: The ssh key to remove
1149 :param user_record: User's record
1150 :type key_string: string
1151 :type user_record: dict
1152 :returns: True if sucessful, False if not.
1156 all_user_keys = user_record['keys']
1157 all_user_keys.remove(key_string)
1158 new_attributes = {'sshPublicKey':all_user_keys}
1159 ret = self.ldap.LdapModifyUser(user_record, new_attributes)
1160 logger.debug("IOTLAB_API DeleteKey %s- " % (ret))
1164 def _sql_get_slice_info(self, slice_filter):
1166 Get the slice record based on the slice hrn. Fetch the record of the
1167 user associated with the slice by using joinedload based on the
1168 reg_researcher relationship.
1170 :param slice_filter: the slice hrn we are looking for
1171 :type slice_filter: string
1172 :returns: the slice record enhanced with the user's information if the
1173 slice was found, None it wasn't.
1175 :rtype: dict or None.
1177 #DO NOT USE RegSlice - reg_researchers to get the hrn
1178 #of the user otherwise will mess up the RegRecord in
1179 #Resolve, don't know why - SA 08/08/2012
1181 #Only one entry for one user = one slice in testbed_xp table
1182 #slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
1183 raw_slicerec = self.api.dbsession().query(RegSlice).options(joinedload('reg_researchers')).filter_by(hrn=slice_filter).first()
1184 #raw_slicerec = self.api.dbsession().query(RegRecord).filter_by(hrn = slice_filter).first()
1186 #load_reg_researcher
1187 #raw_slicerec.reg_researchers
1188 raw_slicerec = raw_slicerec.__dict__
1189 logger.debug(" IOTLAB_API \t _sql_get_slice_info slice_filter %s \
1190 raw_slicerec %s" % (slice_filter, raw_slicerec))
1191 slicerec = raw_slicerec
1192 #only one researcher per slice so take the first one
1193 #slicerec['reg_researchers'] = raw_slicerec['reg_researchers']
1194 #del slicerec['reg_researchers']['_sa_instance_state']
1200 def _sql_get_slice_info_from_user(self, slice_filter):
1202 Get the slice record based on the user recordid by using a joinedload
1203 on the relationship reg_slices_as_researcher. Format the sql record
1204 into a dict with the mandatory fields for user and slice.
1205 :returns: dict with slice record and user record if the record was found
1206 based on the user's id, None if not..
1207 :rtype:dict or None..
1209 #slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
1210 raw_slicerec = self.api.dbsession().query(RegUser).options(joinedload('reg_slices_as_researcher')).filter_by(record_id=slice_filter).first()
1211 #raw_slicerec = self.api.dbsession().query(RegRecord).filter_by(record_id = slice_filter).first()
1212 #Put it in correct order
1213 user_needed_fields = ['peer_authority', 'hrn', 'last_updated',
1214 'classtype', 'authority', 'gid', 'record_id',
1215 'date_created', 'type', 'email', 'pointer']
1216 slice_needed_fields = ['peer_authority', 'hrn', 'last_updated',
1217 'classtype', 'authority', 'gid', 'record_id',
1218 'date_created', 'type', 'pointer']
1220 #raw_slicerec.reg_slices_as_researcher
1221 raw_slicerec = raw_slicerec.__dict__
1224 dict([(k, raw_slicerec[
1225 'reg_slices_as_researcher'][0].__dict__[k])
1226 for k in slice_needed_fields])
1227 slicerec['reg_researchers'] = dict([(k, raw_slicerec[k])
1228 for k in user_needed_fields])
1229 #TODO Handle multiple slices for one user SA 10/12/12
1230 #for now only take the first slice record associated to the rec user
1231 ##slicerec = raw_slicerec['reg_slices_as_researcher'][0].__dict__
1232 #del raw_slicerec['reg_slices_as_researcher']
1233 #slicerec['reg_researchers'] = raw_slicerec
1234 ##del slicerec['_sa_instance_state']
1241 def _get_slice_records(self, slice_filter=None,
1242 slice_filter_type=None):
1244 Get the slice record depending on the slice filter and its type.
1245 :param slice_filter: Can be either the slice hrn or the user's record
1247 :type slice_filter: string
1248 :param slice_filter_type: describes the slice filter type used, can be
1249 slice_hrn or record_id_user
1251 :returns: the slice record
1253 .. seealso::_sql_get_slice_info_from_user
1254 .. seealso:: _sql_get_slice_info
1257 #Get list of slices based on the slice hrn
1258 if slice_filter_type == 'slice_hrn':
1260 #if get_authority(slice_filter) == self.root_auth:
1261 #login = slice_filter.split(".")[1].split("_")[0]
1263 slicerec = self._sql_get_slice_info(slice_filter)
1265 if slicerec is None:
1269 #Get slice based on user id
1270 if slice_filter_type == 'record_id_user':
1272 slicerec = self._sql_get_slice_info_from_user(slice_filter)
1275 fixed_slicerec_dict = slicerec
1276 #At this point if there is no login it means
1277 #record_id_user filter has been used for filtering
1279 ##If theslice record is from iotlab
1280 #if fixed_slicerec_dict['peer_authority'] is None:
1281 #login = fixed_slicerec_dict['hrn'].split(".")[1].split("_")[0]
1282 #return login, fixed_slicerec_dict
1283 return fixed_slicerec_dict
1288 def GetSlices(self, slice_filter=None, slice_filter_type=None,
1290 """Get the slice records from the iotlab db and add lease information
1293 :param slice_filter: can be the slice hrn or slice record id in the db
1294 depending on the slice_filter_type.
1295 :param slice_filter_type: defines the type of the filtering used, Can be
1296 either 'slice_hrn' or "record_id'.
1297 :type slice_filter: string
1298 :type slice_filter_type: string
1299 :returns: a slice dict if slice_filter and slice_filter_type
1300 are specified and a matching entry is found in the db. The result
1301 is put into a list.Or a list of slice dictionnaries if no filters
1308 authorized_filter_types_list = ['slice_hrn', 'record_id_user']
1309 return_slicerec_dictlist = []
1311 #First try to get information on the slice based on the filter provided
1312 if slice_filter_type in authorized_filter_types_list:
1313 fixed_slicerec_dict = self._get_slice_records(slice_filter,
1315 # if the slice was not found in the sfa db
1316 if fixed_slicerec_dict is None:
1317 return return_slicerec_dictlist
1319 slice_hrn = fixed_slicerec_dict['hrn']
1321 logger.debug(" IOTLAB_API \tGetSlices login %s \
1322 slice record %s slice_filter %s \
1323 slice_filter_type %s " % (login,
1324 fixed_slicerec_dict, slice_filter,
1328 #Now we have the slice record fixed_slicerec_dict, get the
1329 #jobs associated to this slice
1332 leases_list = self.GetLeases(login=login)
1333 #If no job is running or no job scheduled
1334 #return only the slice record
1335 if leases_list == [] and fixed_slicerec_dict:
1336 return_slicerec_dictlist.append(fixed_slicerec_dict)
1338 # if the jobs running don't belong to the user/slice we are looking
1340 leases_hrn = [lease['slice_hrn'] for lease in leases_list]
1341 if slice_hrn not in leases_hrn:
1342 return_slicerec_dictlist.append(fixed_slicerec_dict)
1343 #If several jobs for one slice , put the slice record into
1344 # each lease information dict
1345 for lease in leases_list:
1347 logger.debug("IOTLAB_API.PY \tGetSlices slice_filter %s \
1348 \t lease['slice_hrn'] %s"
1349 % (slice_filter, lease['slice_hrn']))
1350 if lease['slice_hrn'] == slice_hrn:
1351 slicerec_dict['oar_job_id'] = lease['lease_id']
1352 #Update lease dict with the slice record
1353 if fixed_slicerec_dict:
1354 fixed_slicerec_dict['oar_job_id'] = []
1355 fixed_slicerec_dict['oar_job_id'].append(
1356 slicerec_dict['oar_job_id'])
1357 slicerec_dict.update(fixed_slicerec_dict)
1358 #slicerec_dict.update({'hrn':\
1359 #str(fixed_slicerec_dict['slice_hrn'])})
1360 slicerec_dict['slice_hrn'] = lease['slice_hrn']
1361 slicerec_dict['hrn'] = lease['slice_hrn']
1362 slicerec_dict['user'] = lease['user']
1363 slicerec_dict.update(
1365 {'hostname': lease['reserved_nodes']}})
1366 slicerec_dict.update({'node_ids': lease['reserved_nodes']})
1370 return_slicerec_dictlist.append(slicerec_dict)
1371 logger.debug("IOTLAB_API.PY \tGetSlices \
1372 OHOHOHOH %s" %(return_slicerec_dictlist))
1374 logger.debug("IOTLAB_API.PY \tGetSlices \
1375 slicerec_dict %s return_slicerec_dictlist %s \
1376 lease['reserved_nodes'] \
1377 %s" % (slicerec_dict, return_slicerec_dictlist,
1378 lease['reserved_nodes']))
1380 logger.debug("IOTLAB_API.PY \tGetSlices RETURN \
1381 return_slicerec_dictlist %s"
1382 % (return_slicerec_dictlist))
1384 return return_slicerec_dictlist
1388 #Get all slices from the iotlab sfa database ,
1389 #put them in dict format
1390 #query_slice_list = dbsession.query(RegRecord).all()
1391 query_slice_list = \
1392 self.api.dbsession().query(RegSlice).options(joinedload('reg_researchers')).all()
1394 for record in query_slice_list:
1395 tmp = record.__dict__
1396 tmp['reg_researchers'] = tmp['reg_researchers'][0].__dict__
1397 #del tmp['reg_researchers']['_sa_instance_state']
1398 return_slicerec_dictlist.append(tmp)
1399 #return_slicerec_dictlist.append(record.__dict__)
1401 #Get all the jobs reserved nodes
1402 leases_list = self.GetReservedNodes()
1404 for fixed_slicerec_dict in return_slicerec_dictlist:
1406 #Check if the slice belongs to a iotlab user
1407 if fixed_slicerec_dict['peer_authority'] is None:
1408 owner = fixed_slicerec_dict['hrn'].split(
1409 ".")[1].split("_")[0]
1412 for lease in leases_list:
1413 if owner == lease['user']:
1414 slicerec_dict['oar_job_id'] = lease['lease_id']
1416 #for reserved_node in lease['reserved_nodes']:
1417 logger.debug("IOTLAB_API.PY \tGetSlices lease %s "
1419 slicerec_dict.update(fixed_slicerec_dict)
1420 slicerec_dict.update({'node_ids':
1421 lease['reserved_nodes']})
1422 slicerec_dict.update({'list_node_ids':
1424 lease['reserved_nodes']}})
1426 #slicerec_dict.update({'hrn':\
1427 #str(fixed_slicerec_dict['slice_hrn'])})
1428 #return_slicerec_dictlist.append(slicerec_dict)
1429 fixed_slicerec_dict.update(slicerec_dict)
1431 logger.debug("IOTLAB_API.PY \tGetSlices RETURN \
1432 return_slicerec_dictlist %s \slice_filter %s " \
1433 %(return_slicerec_dictlist, slice_filter))
1435 return return_slicerec_dictlist
1439 #Update slice unused, therefore sfa_fields_to_iotlab_fields unused
1442 #def sfa_fields_to_iotlab_fields(sfa_type, hrn, record):
1447 ##for field in record:
1448 ## iotlab_record[field] = record[field]
1450 #if sfa_type == "slice":
1451 ##instantion used in get_slivers ?
1452 #if not "instantiation" in iotlab_record:
1453 #iotlab_record["instantiation"] = "iotlab-instantiated"
1454 ##iotlab_record["hrn"] = hrn_to_pl_slicename(hrn)
1455 ##Unused hrn_to_pl_slicename because Iotlab's hrn already
1456 ##in the appropriate form SA 23/07/12
1457 #iotlab_record["hrn"] = hrn
1458 #logger.debug("IOTLAB_API.PY sfa_fields_to_iotlab_fields \
1459 #iotlab_record %s " %(iotlab_record['hrn']))
1460 #if "url" in record:
1461 #iotlab_record["url"] = record["url"]
1462 #if "description" in record:
1463 #iotlab_record["description"] = record["description"]
1464 #if "expires" in record:
1465 #iotlab_record["expires"] = int(record["expires"])
1467 ##nodes added by OAR only and then imported to SFA
1468 ##elif type == "node":
1469 ##if not "hostname" in iotlab_record:
1470 ##if not "hostname" in record:
1471 ##raise MissingSfaInfo("hostname")
1472 ##iotlab_record["hostname"] = record["hostname"]
1473 ##if not "model" in iotlab_record:
1474 ##iotlab_record["model"] = "geni"
1476 ##One authority only
1477 ##elif type == "authority":
1478 ##iotlab_record["login_base"] = hrn_to_iotlab_login_base(hrn)
1480 ##if not "name" in iotlab_record:
1481 ##iotlab_record["name"] = hrn
1483 ##if not "abbreviated_name" in iotlab_record:
1484 ##iotlab_record["abbreviated_name"] = hrn
1486 ##if not "enabled" in iotlab_record:
1487 ##iotlab_record["enabled"] = True
1489 ##if not "is_public" in iotlab_record:
1490 ##iotlab_record["is_public"] = True
1492 #return iotlab_record