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 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 TestbedAdditionalSfaDB, LeaseTableXP
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
27 """ Class enabled to use LDAP and OAR api calls. """
29 _MINIMUM_DURATION = 10 # 10 units of granularity 60 s, 10 mins
31 def __init__(self, api):
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
42 self.leases_db = TestbedAdditionalSfaDB(config)
43 self.oar = OARrestapi()
45 self.time_format = "%Y-%m-%d %H:%M:%S"
46 self.root_auth = config.SFA_REGISTRY_ROOT_AUTH
47 self.grain = 60 # 10 mins lease minimum, 60 sec granularity
48 #import logging, logging.handlers
49 #from sfa.util.sfalogging import _SfaLogger
50 #sql_logger = _SfaLogger(loggername = 'sqlalchemy.engine', \
55 def GetMinExperimentDurationInGranularity():
56 """ Returns the minimum allowed duration for an experiment on the
60 return IotlabShell._MINIMUM_DURATION
63 def GetPeers(self, peer_filter=None ):
64 """ Gathers registered authorities in SFA DB and looks for specific peer
65 if peer_filter is specified.
66 :param peer_filter: name of the site authority looked for.
67 :type peer_filter: string
68 :returns: list of records.
73 existing_hrns_by_types = {}
74 logger.debug("IOTLAB_API \tGetPeers peer_filter %s " % (peer_filter))
75 all_records = dbsession.query(RegRecord).filter(RegRecord.type.like('%authority%')).all()
77 for record in all_records:
78 existing_records[(record.hrn, record.type)] = record
79 if record.type not in existing_hrns_by_types:
80 existing_hrns_by_types[record.type] = [record.hrn]
82 existing_hrns_by_types[record.type].append(record.hrn)
84 logger.debug("IOTLAB_API \tGetPeer\texisting_hrns_by_types %s "
85 % (existing_hrns_by_types))
90 records_list.append(existing_records[(peer_filter,
93 for hrn in existing_hrns_by_types['authority']:
94 records_list.append(existing_records[(hrn, 'authority')])
96 logger.debug("IOTLAB_API \tGetPeer \trecords_list %s "
102 return_records = records_list
103 logger.debug("IOTLAB_API \tGetPeer return_records %s "
105 return return_records
107 #TODO : Handling OR request in make_ldap_filters_from_records
108 #instead of the for loop
109 #over the records' list
110 def GetPersons(self, person_filter=None):
112 Get the enabled users and their properties from Iotlab LDAP.
113 If a filter is specified, looks for the user whose properties match
114 the filter, otherwise returns the whole enabled users'list.
116 :param person_filter: Must be a list of dictionnaries with users
117 properties when not set to None.
118 :type person_filter: list of dict
120 :returns: Returns a list of users whose accounts are enabled
122 :rtype: list of dicts
125 logger.debug("IOTLAB_API \tGetPersons person_filter %s"
128 if person_filter and isinstance(person_filter, list):
129 #If we are looking for a list of users (list of dict records)
130 #Usually the list contains only one user record
131 for searched_attributes in person_filter:
133 #Get only enabled user accounts in iotlab LDAP :
134 #add a filter for make_ldap_filters_from_record
135 person = self.ldap.LdapFindUser(searched_attributes,
136 is_user_enabled=True)
137 #If a person was found, append it to the list
139 person_list.append(person)
141 #If the list is empty, return None
142 if len(person_list) is 0:
146 #Get only enabled user accounts in iotlab LDAP :
147 #add a filter for make_ldap_filters_from_record
148 person_list = self.ldap.LdapFindUser(is_user_enabled=True)
153 #def GetTimezone(self):
154 #""" Returns the OAR server time and timezone.
155 #Unused SA 30/05/13"""
156 #server_timestamp, server_tz = self.oar.parser.\
157 #SendRequest("GET_timezone")
158 #return server_timestamp, server_tz
160 def DeleteJobs(self, job_id, username):
163 Deletes the job with the specified job_id and username on OAR by
164 posting a delete request to OAR.
166 :param job_id: job id in OAR.
167 :param username: user's iotlab login in LDAP.
168 :type job_id: integer
169 :type username: string
171 :returns: dictionary with the job id and if delete has been successful
176 logger.debug("IOTLAB_API \tDeleteJobs jobid %s username %s "
177 % (job_id, username))
178 if not job_id or job_id is -1:
182 reqdict['method'] = "delete"
183 reqdict['strval'] = str(job_id)
185 answer = self.oar.POSTRequestToOARRestAPI('DELETE_jobs_id',
187 if answer['status'] == 'Delete request registered':
190 ret = {job_id: False}
191 logger.debug("IOTLAB_API \tDeleteJobs jobid %s \r\n answer %s \
192 username %s" % (job_id, answer, username))
197 ##TODO : Unused GetJobsId ? SA 05/07/12
198 #def GetJobsId(self, job_id, username = None ):
200 #Details about a specific job.
201 #Includes details about submission time, jot type, state, events,
202 #owner, assigned ressources, walltime etc...
206 #node_list_k = 'assigned_network_address'
207 ##Get job info from OAR
208 #job_info = self.oar.parser.SendRequest(req, job_id, username)
210 #logger.debug("IOTLAB_API \t GetJobsId %s " %(job_info))
212 #if job_info['state'] == 'Terminated':
213 #logger.debug("IOTLAB_API \t GetJobsId job %s TERMINATED"\
216 #if job_info['state'] == 'Error':
217 #logger.debug("IOTLAB_API \t GetJobsId ERROR message %s "\
222 #logger.error("IOTLAB_API \tGetJobsId KeyError")
225 #parsed_job_info = self.get_info_on_reserved_nodes(job_info, \
227 ##Replaces the previous entry
228 ##"assigned_network_address" / "reserved_resources"
230 #job_info.update({'node_ids':parsed_job_info[node_list_k]})
231 #del job_info[node_list_k]
232 #logger.debug(" \r\nIOTLAB_API \t GetJobsId job_info %s " %(job_info))
236 def GetJobsResources(self, job_id, username = None):
237 """ Gets the list of nodes associated with the job_id and username
239 Transforms the iotlab hostnames to the corresponding
241 Rertuns dict key :'node_ids' , value : hostnames list
242 :param username: user's LDAP login
243 :paran job_id: job's OAR identifier.
244 :type username: string
245 :type job_id: integer
247 :returns: dicionary with nodes' hostnames belonging to the job.
249 .. warning: Unused. SA 16/10/13
252 req = "GET_jobs_id_resources"
255 #Get job resources list from OAR
256 node_id_list = self.oar.parser.SendRequest(req, job_id, username)
257 logger.debug("IOTLAB_API \t GetJobsResources %s " %(node_id_list))
260 self.__get_hostnames_from_oar_node_ids(node_id_list)
263 #Replaces the previous entry "assigned_network_address" /
264 #"reserved_resources" with "node_ids"
265 job_info = {'node_ids': hostname_list}
270 #def get_info_on_reserved_nodes(self, job_info, node_list_name):
272 #..warning:unused SA 23/05/13
274 ##Get the list of the testbed nodes records and make a
275 ##dictionnary keyed on the hostname out of it
276 #node_list_dict = self.GetNodes()
277 ##node_hostname_list = []
278 #node_hostname_list = [node['hostname'] for node in node_list_dict]
279 ##for node in node_list_dict:
280 ##node_hostname_list.append(node['hostname'])
281 #node_dict = dict(zip(node_hostname_list, node_list_dict))
283 #reserved_node_hostname_list = []
284 #for index in range(len(job_info[node_list_name])):
285 ##job_info[node_list_name][k] =
286 #reserved_node_hostname_list[index] = \
287 #node_dict[job_info[node_list_name][index]]['hostname']
289 #logger.debug("IOTLAB_API \t get_info_on_reserved_nodes \
290 #reserved_node_hostname_list %s" \
291 #%(reserved_node_hostname_list))
293 #logger.error("IOTLAB_API \t get_info_on_reserved_nodes KEYERROR " )
295 #return reserved_node_hostname_list
297 def GetNodesCurrentlyInUse(self):
298 """Returns a list of all the nodes already involved in an oar running
300 :rtype: list of nodes hostnames.
302 return self.oar.parser.SendRequest("GET_running_jobs")
304 def __get_hostnames_from_oar_node_ids(self, oar_id_node_dict,
306 """Get the hostnames of the nodes from their OAR identifiers.
307 Get the list of nodes dict using GetNodes and find the hostname
308 associated with the identifier.
309 :param oar_id_node_dict: full node dictionary list keyed by oar node id
310 :param resource_id_list: list of nodes identifiers
311 :returns: list of node hostnames.
315 for resource_id in resource_id_list:
316 #Because jobs requested "asap" do not have defined resources
317 if resource_id is not "Undefined":
318 hostname_list.append(\
319 oar_id_node_dict[resource_id]['hostname'])
321 #hostname_list.append(oar_id_node_dict[resource_id]['hostname'])
324 def GetReservedNodes(self, username=None):
325 """ Get list of leases. Get the leases for the username if specified,
326 otherwise get all the leases. Finds the nodes hostnames for each
328 :param username: user's LDAP login
329 :type username: string
330 :returns: list of reservations dict
334 #Get the nodes in use and the reserved nodes
335 reservation_dict_list = \
336 self.oar.parser.SendRequest("GET_reserved_nodes", \
339 # Get the full node dict list once for all
340 # so that we can get the hostnames given their oar node id afterwards
341 # when the reservations are checked.
342 full_nodes_dict_list = self.GetNodes()
343 #Put the full node list into a dictionary keyed by oar node id
344 oar_id_node_dict = {}
345 for node in full_nodes_dict_list:
346 oar_id_node_dict[node['oar_id']] = node
348 for resa in reservation_dict_list:
349 logger.debug ("GetReservedNodes resa %s"%(resa))
350 #dict list of hostnames and their site
351 resa['reserved_nodes'] = \
352 self.__get_hostnames_from_oar_node_ids(oar_id_node_dict,
353 resa['resource_ids'])
355 #del resa['resource_ids']
356 return reservation_dict_list
358 def GetNodes(self, node_filter_dict=None, return_fields_list=None):
361 Make a list of iotlab nodes and their properties from information
362 given by OAR. Search for specific nodes if some filters are
363 specified. Nodes properties returned if no return_fields_list given:
364 'hrn','archi','mobile','hostname','site','boot_state','node_id',
365 'radio','posx','posy','oar_id','posz'.
367 :param node_filter_dict: dictionnary of lists with node properties. For
368 instance, if you want to look for a specific node with its hrn,
369 the node_filter_dict should be {'hrn': [hrn_of_the_node]}
370 :type node_filter_dict: dict
371 :param return_fields_list: list of specific fields the user wants to be
373 :type return_fields_list: list
374 :returns: list of dictionaries with node properties
378 node_dict_by_id = self.oar.parser.SendRequest("GET_resources_full")
379 node_dict_list = node_dict_by_id.values()
380 logger.debug (" IOTLAB_API GetNodes node_filter_dict %s \
381 return_fields_list %s " % (node_filter_dict, return_fields_list))
382 #No filtering needed return the list directly
383 if not (node_filter_dict or return_fields_list):
384 return node_dict_list
386 return_node_list = []
388 for filter_key in node_filter_dict:
390 #Filter the node_dict_list by each value contained in the
391 #list node_filter_dict[filter_key]
392 for value in node_filter_dict[filter_key]:
393 for node in node_dict_list:
394 if node[filter_key] == value:
395 if return_fields_list:
397 for k in return_fields_list:
399 return_node_list.append(tmp)
401 return_node_list.append(node)
403 logger.log_exc("GetNodes KeyError")
407 return return_node_list
410 def AddSlice(self, slice_record, user_record):
413 Add slice to the local iotlab sfa tables if the slice comes
414 from a federated site and is not yet in the iotlab sfa DB,
415 although the user has already a LDAP login.
416 Called by verify_slice during lease/sliver creation.
418 :param slice_record: record of slice, must contain hrn, gid, slice_id
419 and authority of the slice.
420 :type slice_record: dictionary
421 :param user_record: record of the user
422 :type user_record: RegUser
426 sfa_record = RegSlice(hrn=slice_record['hrn'],
427 gid=slice_record['gid'],
428 pointer=slice_record['slice_id'],
429 authority=slice_record['authority'])
430 logger.debug("IOTLAB_API.PY AddSlice sfa_record %s user_record %s"
431 % (sfa_record, user_record))
432 sfa_record.just_created()
433 dbsession.add(sfa_record)
435 #Update the reg-researcher dependance table
436 sfa_record.reg_researchers = [user_record]
442 def GetSites(self, site_filter_name_list=None, return_fields_list=None):
443 """Returns the list of Iotlab's sites with the associated nodes and
444 the sites' properties as dictionaries.
447 ['address_ids', 'slice_ids', 'name', 'node_ids', 'url', 'person_ids',
448 'site_tag_ids', 'enabled', 'site', 'longitude', 'pcu_ids',
449 'max_slivers', 'max_slices', 'ext_consortium_id', 'date_created',
450 'latitude', 'is_public', 'peer_site_id', 'peer_id', 'abbreviated_name']
451 Uses the OAR request GET_sites to find the Iotlab's sites.
453 :param site_filter_name_list: used to specify specific sites
454 :param return_fields_list: field that has to be returned
455 :type site_filter_name_list: list
456 :type return_fields_list: list
460 site_dict = self.oar.parser.SendRequest("GET_sites")
461 #site_dict : dict where the key is the sit ename
462 return_site_list = []
463 if not (site_filter_name_list or return_fields_list):
464 return_site_list = site_dict.values()
465 return return_site_list
467 for site_filter_name in site_filter_name_list:
468 if site_filter_name in site_dict:
469 if return_fields_list:
470 for field in return_fields_list:
473 tmp[field] = site_dict[site_filter_name][field]
475 logger.error("GetSites KeyError %s " % (field))
477 return_site_list.append(tmp)
479 return_site_list.append(site_dict[site_filter_name])
481 return return_site_list
484 #TODO : Check rights to delete person
485 def DeletePerson(self, person_record):
486 """Disable an existing account in iotlab LDAP.
488 Users and techs can only delete themselves. PIs can only
489 delete themselves and other non-PIs at their sites.
490 ins can delete anyone.
492 :param person_record: user's record
493 :type person_record: dict
494 :returns: True if successful, False otherwise.
497 .. todo:: CHECK THAT ONLY THE USER OR ADMIN CAN DEL HIMSELF.
499 #Disable user account in iotlab LDAP
500 ret = self.ldap.LdapMarkUserAsDeleted(person_record)
501 logger.warning("IOTLAB_API DeletePerson %s " % (person_record))
504 def DeleteSlice(self, slice_record):
505 """Deletes the specified slice and kills the jobs associated with
506 the slice if any, using DeleteSliceFromNodes.
508 :param slice_record: record of the slice, must contain oar_job_id, user
509 :type slice_record: dict
510 :returns: True if all the jobs in the slice have been deleted,
511 or the list of jobs that could not be deleted otherwise.
512 :rtype: list or boolean
514 .. seealso:: DeleteSliceFromNodes
517 ret = self.DeleteSliceFromNodes(slice_record)
520 if False in ret[job_id]:
521 if delete_failed is None:
523 delete_failed.append(job_id)
525 logger.info("IOTLAB_API DeleteSlice %s answer %s"%(slice_record, \
527 return delete_failed or True
530 def __add_person_to_db(self, user_dict):
532 Add a federated user straight to db when the user issues a lease
533 request with iotlab nodes and that he has not registered with iotlab
534 yet (that is he does not have a LDAP entry yet).
535 Uses parts of the routines in IotlabImport when importing user from LDAP.
536 Called by AddPerson, right after LdapAddUser.
537 :param user_dict: Must contain email, hrn and pkey to get a GID
538 and be added to the SFA db.
539 :type user_dict: dict
543 dbsession.query(RegUser).filter_by(email = user_dict['email']).first()
545 if not check_if_exists:
546 logger.debug("__add_person_to_db \t Adding %s \r\n \r\n \
548 hrn = user_dict['hrn']
549 person_urn = hrn_to_urn(hrn, 'user')
550 pubkey = user_dict['pkey']
552 pkey = convert_public_key(pubkey)
554 #key not good. create another pkey
555 logger.warn('__add_person_to_db: unable to convert public \
557 pkey = Keypair(create=True)
560 if pubkey is not None and pkey is not None :
561 hierarchy = Hierarchy()
562 person_gid = hierarchy.create_gid(person_urn, create_uuid(), \
564 if user_dict['email']:
565 logger.debug("__add_person_to_db \r\n \r\n \
566 IOTLAB IMPORTER PERSON EMAIL OK email %s "\
567 %(user_dict['email']))
568 person_gid.set_email(user_dict['email'])
570 user_record = RegUser(hrn=hrn , pointer= '-1', \
571 authority=get_authority(hrn), \
572 email=user_dict['email'], gid = person_gid)
573 user_record.reg_keys = [RegKey(user_dict['pkey'])]
574 user_record.just_created()
575 dbsession.add (user_record)
580 def AddPerson(self, record):
583 Adds a new account. Any fields specified in records are used,
584 otherwise defaults are used. Creates an appropriate login by calling
587 :param record: dictionary with the sfa user's properties.
588 :returns: a dicitonary with the status. If successful, the dictionary
589 boolean is set to True and there is a 'uid' key with the new login
590 added to LDAP, otherwise the bool is set to False and a key
591 'message' is in the dictionary, with the error message.
595 ret = self.ldap.LdapAddUser(record)
597 if ret['bool'] is True:
598 record['hrn'] = self.root_auth + '.' + ret['uid']
599 logger.debug("IOTLAB_API AddPerson return code %s record %s "
601 self.__add_person_to_db(record)
608 #TODO AddPersonKey 04/07/2012 SA
609 def AddPersonKey(self, person_uid, old_attributes_dict, new_key_dict):
610 """Adds a new key to the specified account. Adds the key to the
611 iotlab ldap, provided that the person_uid is valid.
613 Non-admins can only modify their own keys.
615 :param person_uid: user's iotlab login in LDAP
616 :param old_attributes_dict: dict with the user's old sshPublicKey
617 :param new_key_dict: dict with the user's new sshPublicKey
618 :type person_uid: string
622 :returns: True if the key has been modified, False otherwise.
625 ret = self.ldap.LdapModify(person_uid, old_attributes_dict, \
627 logger.warning("IOTLAB_API AddPersonKey EMPTY - DO NOTHING \r\n ")
630 def DeleteLeases(self, leases_id_list, slice_hrn):
633 Deletes several leases, based on their job ids and the slice
634 they are associated with. Uses DeleteJobs to delete the jobs
635 on OAR. Note that one slice can contain multiple jobs, and in this
636 case all the jobs in the leases_id_list MUST belong to ONE slice,
637 since there is only one slice hrn provided here.
639 :param leases_id_list: list of job ids that belong to the slice whose
640 slice hrn is provided.
641 :param slice_hrn: the slice hrn.
642 :type slice_hrn: string
644 .. warning:: Does not have a return value since there was no easy
645 way to handle failure when dealing with multiple job delete. Plus,
646 there was no easy way to report it to the user.
649 logger.debug("IOTLAB_API DeleteLeases leases_id_list %s slice_hrn %s \
650 \r\n " %(leases_id_list, slice_hrn))
651 for job_id in leases_id_list:
652 self.DeleteJobs(job_id, slice_hrn)
657 def _process_walltime(duration):
658 """ Calculates the walltime in seconds from the duration in H:M:S
659 specified in the RSpec.
663 # Fixing the walltime by adding a few delays.
664 # First put the walltime in seconds oarAdditionalDelay = 20;
665 # additional delay for /bin/sleep command to
666 # take in account prologue and epilogue scripts execution
667 # int walltimeAdditionalDelay = 240; additional delay
668 #for prologue/epilogue execution = $SERVER_PROLOGUE_EPILOGUE_TIMEOUT
670 # Put the duration in seconds first
671 #desired_walltime = duration * 60
672 desired_walltime = duration
673 total_walltime = desired_walltime + 240 #+4 min Update SA 23/10/12
674 sleep_walltime = desired_walltime # 0 sec added Update SA 23/10/12
676 #Put the walltime back in str form
678 walltime.append(str(total_walltime / 3600))
679 total_walltime = total_walltime - 3600 * int(walltime[0])
680 #Get the remaining minutes
681 walltime.append(str(total_walltime / 60))
682 total_walltime = total_walltime - 60 * int(walltime[1])
684 walltime.append(str(total_walltime))
687 logger.log_exc(" __process_walltime duration null")
689 return walltime, sleep_walltime
692 def _create_job_structure_request_for_OAR(lease_dict):
693 """ Creates the structure needed for a correct POST on OAR.
694 Makes the timestamp transformation into the appropriate format.
695 Sends the POST request to create the job with the resources in
704 reqdict['workdir'] = '/tmp'
705 reqdict['resource'] = "{network_address in ("
707 for node in lease_dict['added_nodes']:
708 logger.debug("\r\n \r\n OARrestapi \t \
709 __create_job_structure_request_for_OAR node %s" %(node))
711 # Get the ID of the node
713 reqdict['resource'] += "'" + nodeid + "', "
714 nodeid_list.append(nodeid)
716 custom_length = len(reqdict['resource'])- 2
717 reqdict['resource'] = reqdict['resource'][0:custom_length] + \
718 ")}/nodes=" + str(len(nodeid_list))
721 walltime, sleep_walltime = \
722 IotlabShell._process_walltime(\
723 int(lease_dict['lease_duration']))
726 reqdict['resource'] += ",walltime=" + str(walltime[0]) + \
727 ":" + str(walltime[1]) + ":" + str(walltime[2])
728 reqdict['script_path'] = "/bin/sleep " + str(sleep_walltime)
730 #In case of a scheduled experiment (not immediate)
731 #To run an XP immediately, don't specify date and time in RSpec
732 #They will be set to None.
733 if lease_dict['lease_start_time'] is not '0':
734 #Readable time accepted by OAR
735 start_time = datetime.fromtimestamp( \
736 int(lease_dict['lease_start_time'])).\
737 strftime(lease_dict['time_format'])
738 reqdict['reservation'] = start_time
739 #If there is not start time, Immediate XP. No need to add special
743 reqdict['type'] = "deploy"
744 reqdict['directory'] = ""
745 reqdict['name'] = "SFA_" + lease_dict['slice_user']
750 def LaunchExperimentOnOAR(self, added_nodes, slice_name, \
751 lease_start_time, lease_duration, slice_user=None):
754 Create a job request structure based on the information provided
755 and post the job on OAR.
756 :param added_nodes: list of nodes that belong to the described lease.
757 :param slice_name: the slice hrn associated to the lease.
758 :param lease_start_time: timestamp of the lease startting time.
759 :param lease_duration: lease durationin minutes
763 lease_dict['lease_start_time'] = lease_start_time
764 lease_dict['lease_duration'] = lease_duration
765 lease_dict['added_nodes'] = added_nodes
766 lease_dict['slice_name'] = slice_name
767 lease_dict['slice_user'] = slice_user
768 lease_dict['grain'] = self.GetLeaseGranularity()
769 lease_dict['time_format'] = self.time_format
772 logger.debug("IOTLAB_API.PY \tLaunchExperimentOnOAR slice_user %s\
773 \r\n " %(slice_user))
774 #Create the request for OAR
775 reqdict = self._create_job_structure_request_for_OAR(lease_dict)
776 # first step : start the OAR job and update the job
777 logger.debug("IOTLAB_API.PY \tLaunchExperimentOnOAR reqdict %s\
780 answer = self.oar.POSTRequestToOARRestAPI('POST_job', \
782 logger.debug("IOTLAB_API \tLaunchExperimentOnOAR jobid %s " %(answer))
786 logger.log_exc("IOTLAB_API \tLaunchExperimentOnOAR \
787 Impossible to create job %s " %(answer))
794 logger.debug("IOTLAB_API \tLaunchExperimentOnOAR jobid %s \
795 added_nodes %s slice_user %s" %(jobid, added_nodes, \
802 def AddLeases(self, hostname_list, slice_record,
803 lease_start_time, lease_duration):
805 """Creates a job in OAR corresponding to the information provided
806 as parameters. Adds the job id and the slice hrn in the iotlab
807 database so that we are able to know which slice has which nodes.
809 :param hostname_list: list of nodes' OAR hostnames.
810 :param slice_record: sfa slice record, must contain login and hrn.
811 :param lease_start_time: starting time , unix timestamp format
812 :param lease_duration: duration in minutes
814 :type hostname_list: list
815 :type slice_record: dict
816 :type lease_start_time: integer
817 :type lease_duration: integer
820 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases hostname_list %s \
821 slice_record %s lease_start_time %s lease_duration %s "\
822 %( hostname_list, slice_record , lease_start_time, \
825 #tmp = slice_record['reg-researchers'][0].split(".")
826 username = slice_record['login']
827 #username = tmp[(len(tmp)-1)]
828 job_id = self.LaunchExperimentOnOAR(hostname_list, \
829 slice_record['hrn'], \
830 lease_start_time, lease_duration, \
833 datetime.fromtimestamp(int(lease_start_time)).\
834 strftime(self.time_format)
835 end_time = lease_start_time + lease_duration
838 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases TURN ON LOGGING SQL \
839 %s %s %s "%(slice_record['hrn'], job_id, end_time))
842 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases %s %s %s " \
843 %(type(slice_record['hrn']), type(job_id), type(end_time)))
845 iotlab_ex_row = LeaseTableXP(slice_hrn = slice_record['hrn'], experiment_id=job_id,
848 logger.debug("IOTLAB_API \r\n \r\n \t AddLeases iotlab_ex_row %s" \
850 self.leases_db.testbed_session.add(iotlab_ex_row)
851 self.leases_db.testbed_session.commit()
853 logger.debug("IOTLAB_API \t AddLeases hostname_list start_time %s " \
859 #Delete the jobs from job_iotlab table
860 def DeleteSliceFromNodes(self, slice_record):
863 Deletes all the running or scheduled jobs of a given slice
866 :param slice_record: record of the slice, must contain oar_job_id, user
867 :type slice_record: dict
869 :returns: dict of the jobs'deletion status. Success= True, Failure=
870 False, for each job id.
874 logger.debug("IOTLAB_API \t DeleteSliceFromNodes %s "
877 if isinstance(slice_record['oar_job_id'], list):
879 for job_id in slice_record['oar_job_id']:
880 ret = self.DeleteJobs(job_id, slice_record['user'])
882 oar_bool_answer.update(ret)
885 oar_bool_answer = [self.DeleteJobs(slice_record['oar_job_id'],
886 slice_record['user'])]
888 return oar_bool_answer
892 def GetLeaseGranularity(self):
893 """ Returns the granularity of an experiment in the Iotlab testbed.
894 OAR uses seconds for experiments duration , the granulaity is also
896 Experiments which last less than 10 min (600 sec) are invalid"""
902 def filter_lease_name(reservation_list, filter_value):
903 filtered_reservation_list = list(reservation_list)
904 logger.debug("IOTLAB_API \t filter_lease_name reservation_list %s" \
905 % (reservation_list))
906 for reservation in reservation_list:
907 if 'slice_hrn' in reservation and \
908 reservation['slice_hrn'] != filter_value:
909 filtered_reservation_list.remove(reservation)
911 logger.debug("IOTLAB_API \t filter_lease_name filtered_reservation_list %s" \
912 % (filtered_reservation_list))
913 return filtered_reservation_list
916 def filter_lease_start_time(reservation_list, filter_value):
917 filtered_reservation_list = list(reservation_list)
919 for reservation in reservation_list:
920 if 't_from' in reservation and \
921 reservation['t_from'] > filter_value:
922 filtered_reservation_list.remove(reservation)
924 return filtered_reservation_list
927 def GetLeases(self, lease_filter_dict=None, login=None):
930 Get the list of leases from OAR with complete information
931 about which slice owns which jobs and nodes.
933 -Fetch all the jobs from OAR (running, waiting..)
934 complete the reservation information with slice hrn
935 found in testbed_xp table. If not available in the table,
936 assume it is a iotlab slice.
937 -Updates the iotlab table, deleting jobs when necessary.
939 :returns: reservation_list, list of dictionaries with 'lease_id',
940 'reserved_nodes','slice_id', 'state', 'user', 'component_id_list',
941 'slice_hrn', 'resource_ids', 't_from', 't_until'
946 unfiltered_reservation_list = self.GetReservedNodes(login)
948 reservation_list = []
949 #Find the slice associated with this user iotlab ldap uid
950 logger.debug(" IOTLAB_API.PY \tGetLeases login %s\
951 unfiltered_reservation_list %s "
952 % (login, unfiltered_reservation_list))
953 #Create user dict first to avoid looking several times for
954 #the same user in LDAP SA 27/07/12
957 jobs_psql_query = self.leases_db.testbed_session.query(LeaseTableXP).all()
958 jobs_psql_dict = dict([(row.experiment_id, row.__dict__)
959 for row in jobs_psql_query])
960 #jobs_psql_dict = jobs_psql_dict)
961 logger.debug("IOTLAB_API \tGetLeases jobs_psql_dict %s"
963 jobs_psql_id_list = [row.experiment_id for row in jobs_psql_query]
965 for resa in unfiltered_reservation_list:
966 logger.debug("IOTLAB_API \tGetLeases USER %s"
968 #Construct list of jobs (runing, waiting..) in oar
969 job_oar_list.append(resa['lease_id'])
970 #If there is information on the job in IOTLAB DB ]
971 #(slice used and job id)
972 if resa['lease_id'] in jobs_psql_dict:
973 job_info = jobs_psql_dict[resa['lease_id']]
974 logger.debug("IOTLAB_API \tGetLeases job_info %s"
976 resa['slice_hrn'] = job_info['slice_hrn']
977 resa['slice_id'] = hrn_to_urn(resa['slice_hrn'], 'slice')
979 #otherwise, assume it is a iotlab slice:
981 resa['slice_id'] = hrn_to_urn(self.root_auth + '.' +
982 resa['user'] + "_slice", 'slice')
983 resa['slice_hrn'] = Xrn(resa['slice_id']).get_hrn()
985 resa['component_id_list'] = []
986 #Transform the hostnames into urns (component ids)
987 for node in resa['reserved_nodes']:
989 iotlab_xrn = iotlab_xrn_object(self.root_auth, node)
990 resa['component_id_list'].append(iotlab_xrn.urn)
992 if lease_filter_dict:
993 logger.debug("IOTLAB_API \tGetLeases \
994 \r\n leasefilter %s" % ( lease_filter_dict))
996 filter_dict_functions = {
997 'slice_hrn' : IotlabShell.filter_lease_name,
998 't_from' : IotlabShell.filter_lease_start_time
1000 reservation_list = list(unfiltered_reservation_list)
1001 for filter_type in lease_filter_dict:
1002 logger.debug("IOTLAB_API \tGetLeases reservation_list %s" \
1003 % (reservation_list))
1004 reservation_list = filter_dict_functions[filter_type](\
1005 reservation_list,lease_filter_dict[filter_type] )
1007 # Filter the reservation list with a maximum timespan so that the
1008 # leases and jobs running after this timestamp do not appear
1009 # in the result leases.
1010 # if 'start_time' in :
1011 # if resa['start_time'] < lease_filter_dict['start_time']:
1012 # reservation_list.append(resa)
1015 # if 'name' in lease_filter_dict and \
1016 # lease_filter_dict['name'] == resa['slice_hrn']:
1017 # reservation_list.append(resa)
1020 if lease_filter_dict is None:
1021 reservation_list = unfiltered_reservation_list
1023 self.leases_db.update_experiments_in_additional_sfa_db(job_oar_list, jobs_psql_id_list)
1025 logger.debug(" IOTLAB_API.PY \tGetLeases reservation_list %s"
1026 % (reservation_list))
1027 return reservation_list
1032 #TODO FUNCTIONS SECTION 04/07/2012 SA
1034 ##TODO : Is UnBindObjectFromPeer still necessary ? Currently does nothing
1037 #def UnBindObjectFromPeer( auth, object_type, object_id, shortname):
1038 #""" This method is a hopefully temporary hack to let the sfa correctly
1039 #detach the objects it creates from a remote peer object. This is
1040 #needed so that the sfa federation link can work in parallel with
1041 #RefreshPeer, as RefreshPeer depends on remote objects being correctly
1044 #auth : struct, API authentication structure
1045 #AuthMethod : string, Authentication method to use
1046 #object_type : string, Object type, among 'site','person','slice',
1048 #object_id : int, object_id
1049 #shortname : string, peer shortname
1053 #logger.warning("IOTLAB_API \tUnBindObjectFromPeer EMPTY-\
1057 ##TODO Is BindObjectToPeer still necessary ? Currently does nothing
1059 #|| Commented out 28/05/13 SA
1060 #def BindObjectToPeer(self, auth, object_type, object_id, shortname=None, \
1061 #remote_object_id=None):
1062 #"""This method is a hopefully temporary hack to let the sfa correctly
1063 #attach the objects it creates to a remote peer object. This is needed
1064 #so that the sfa federation link can work in parallel with RefreshPeer,
1065 #as RefreshPeer depends on remote objects being correctly marked.
1067 #shortname : string, peer shortname
1068 #remote_object_id : int, remote object_id, set to 0 if unknown
1072 #logger.warning("IOTLAB_API \tBindObjectToPeer EMPTY - DO NOTHING \r\n ")
1075 ##TODO UpdateSlice 04/07/2012 SA || Commented out 28/05/13 SA
1076 ##Funciton should delete and create another job since oin iotlab slice=job
1077 #def UpdateSlice(self, auth, slice_id_or_name, slice_fields=None):
1078 #"""Updates the parameters of an existing slice with the values in
1080 #Users may only update slices of which they are members.
1081 #PIs may update any of the slices at their sites, or any slices of
1082 #which they are members. Admins may update any slice.
1083 #Only PIs and admins may update max_nodes. Slices cannot be renewed
1084 #(by updating the expires parameter) more than 8 weeks into the future.
1085 #Returns 1 if successful, faults otherwise.
1089 #logger.warning("IOTLAB_API UpdateSlice EMPTY - DO NOTHING \r\n ")
1092 #Unused SA 30/05/13, we only update the user's key or we delete it.
1093 ##TODO UpdatePerson 04/07/2012 SA
1094 #def UpdatePerson(self, iotlab_hrn, federated_hrn, person_fields=None):
1095 #"""Updates a person. Only the fields specified in person_fields
1096 #are updated, all other fields are left untouched.
1097 #Users and techs can only update themselves. PIs can only update
1098 #themselves and other non-PIs at their sites.
1099 #Returns 1 if successful, faults otherwise.
1103 ##new_row = FederatedToIotlab(iotlab_hrn, federated_hrn)
1104 ##self.leases_db.testbed_session.add(new_row)
1105 ##self.leases_db.testbed_session.commit()
1107 #logger.debug("IOTLAB_API UpdatePerson EMPTY - DO NOTHING \r\n ")
1111 def GetKeys(self, key_filter=None):
1112 """Returns a dict of dict based on the key string. Each dict entry
1113 contains the key id, the ssh key, the user's email and the
1115 If key_filter is specified and is an array of key identifiers,
1116 only keys matching the filter will be returned.
1118 Admin may query all keys. Non-admins may only query their own keys.
1121 :returns: dict with ssh key as key and dicts as value.
1124 if key_filter is None:
1125 keys = dbsession.query(RegKey).options(joinedload('reg_user')).all()
1127 keys = dbsession.query(RegKey).options(joinedload('reg_user')).filter(RegKey.key.in_(key_filter)).all()
1131 key_dict[key.key] = {'key_id': key.key_id, 'key': key.key,
1132 'email': key.reg_user.email,
1133 'hrn': key.reg_user.hrn}
1135 #ldap_rslt = self.ldap.LdapSearch({'enabled']=True})
1136 #user_by_email = dict((user[1]['mail'][0], user[1]['sshPublicKey']) \
1137 #for user in ldap_rslt)
1139 logger.debug("IOTLAB_API GetKeys -key_dict %s \r\n " % (key_dict))
1143 def DeleteKey(self, user_record, key_string):
1144 """Deletes a key in the LDAP entry of the specified user.
1146 Removes the key_string from the user's key list and updates the LDAP
1147 user's entry with the new key attributes.
1149 :param key_string: The ssh key to remove
1150 :param user_record: User's record
1151 :type key_string: string
1152 :type user_record: dict
1153 :returns: True if sucessful, False if not.
1157 all_user_keys = user_record['keys']
1158 all_user_keys.remove(key_string)
1159 new_attributes = {'sshPublicKey':all_user_keys}
1160 ret = self.ldap.LdapModifyUser(user_record, new_attributes)
1161 logger.debug("IOTLAB_API DeleteKey %s- " % (ret))
1165 def _sql_get_slice_info(self, slice_filter):
1167 Get the slice record based on the slice hrn. Fetch the record of the
1168 user associated with the slice by using joinedload based on the
1169 reg_researcher relationship.
1171 :param slice_filter: the slice hrn we are looking for
1172 :type slice_filter: string
1173 :returns: the slice record enhanced with the user's information if the
1174 slice was found, None it wasn't.
1176 :rtype: dict or None.
1178 #DO NOT USE RegSlice - reg_researchers to get the hrn
1179 #of the user otherwise will mess up the RegRecord in
1180 #Resolve, don't know why - SA 08/08/2012
1182 #Only one entry for one user = one slice in testbed_xp table
1183 #slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
1184 raw_slicerec = dbsession.query(RegSlice).options(joinedload('reg_researchers')).filter_by(hrn=slice_filter).first()
1185 #raw_slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
1187 #load_reg_researcher
1188 #raw_slicerec.reg_researchers
1189 raw_slicerec = raw_slicerec.__dict__
1190 logger.debug(" IOTLAB_API \t _sql_get_slice_info slice_filter %s \
1191 raw_slicerec %s" % (slice_filter, raw_slicerec))
1192 slicerec = raw_slicerec
1193 #only one researcher per slice so take the first one
1194 #slicerec['reg_researchers'] = raw_slicerec['reg_researchers']
1195 #del slicerec['reg_researchers']['_sa_instance_state']
1201 def _sql_get_slice_info_from_user(self, slice_filter):
1203 Get the slice record based on the user recordid by using a joinedload
1204 on the relationship reg_slices_as_researcher. Format the sql record
1205 into a dict with the mandatory fields for user and slice.
1206 :returns: dict with slice record and user record if the record was found
1207 based on the user's id, None if not..
1208 :rtype:dict or None..
1210 #slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
1211 raw_slicerec = dbsession.query(RegUser).options(joinedload('reg_slices_as_researcher')).filter_by(record_id=slice_filter).first()
1212 #raw_slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
1213 #Put it in correct order
1214 user_needed_fields = ['peer_authority', 'hrn', 'last_updated',
1215 'classtype', 'authority', 'gid', 'record_id',
1216 'date_created', 'type', 'email', 'pointer']
1217 slice_needed_fields = ['peer_authority', 'hrn', 'last_updated',
1218 'classtype', 'authority', 'gid', 'record_id',
1219 'date_created', 'type', 'pointer']
1221 #raw_slicerec.reg_slices_as_researcher
1222 raw_slicerec = raw_slicerec.__dict__
1225 dict([(k, raw_slicerec[
1226 'reg_slices_as_researcher'][0].__dict__[k])
1227 for k in slice_needed_fields])
1228 slicerec['reg_researchers'] = dict([(k, raw_slicerec[k])
1229 for k in user_needed_fields])
1230 #TODO Handle multiple slices for one user SA 10/12/12
1231 #for now only take the first slice record associated to the rec user
1232 ##slicerec = raw_slicerec['reg_slices_as_researcher'][0].__dict__
1233 #del raw_slicerec['reg_slices_as_researcher']
1234 #slicerec['reg_researchers'] = raw_slicerec
1235 ##del slicerec['_sa_instance_state']
1242 def _get_slice_records(self, slice_filter=None,
1243 slice_filter_type=None):
1245 Get the slice record depending on the slice filter and its type.
1246 :param slice_filter: Can be either the slice hrn or the user's record
1248 :type slice_filter: string
1249 :param slice_filter_type: describes the slice filter type used, can be
1250 slice_hrn or record_id_user
1252 :returns: the slice record
1254 .. seealso::_sql_get_slice_info_from_user
1255 .. seealso:: _sql_get_slice_info
1258 #Get list of slices based on the slice hrn
1259 if slice_filter_type == 'slice_hrn':
1261 #if get_authority(slice_filter) == self.root_auth:
1262 #login = slice_filter.split(".")[1].split("_")[0]
1264 slicerec = self._sql_get_slice_info(slice_filter)
1266 if slicerec is None:
1270 #Get slice based on user id
1271 if slice_filter_type == 'record_id_user':
1273 slicerec = self._sql_get_slice_info_from_user(slice_filter)
1276 fixed_slicerec_dict = slicerec
1277 #At this point if there is no login it means
1278 #record_id_user filter has been used for filtering
1280 ##If theslice record is from iotlab
1281 #if fixed_slicerec_dict['peer_authority'] is None:
1282 #login = fixed_slicerec_dict['hrn'].split(".")[1].split("_")[0]
1283 #return login, fixed_slicerec_dict
1284 return fixed_slicerec_dict
1289 def GetSlices(self, slice_filter=None, slice_filter_type=None,
1291 """Get the slice records from the iotlab db and add lease information
1294 :param slice_filter: can be the slice hrn or slice record id in the db
1295 depending on the slice_filter_type.
1296 :param slice_filter_type: defines the type of the filtering used, Can be
1297 either 'slice_hrn' or "record_id'.
1298 :type slice_filter: string
1299 :type slice_filter_type: string
1300 :returns: a slice dict if slice_filter and slice_filter_type
1301 are specified and a matching entry is found in the db. The result
1302 is put into a list.Or a list of slice dictionnaries if no filters
1309 authorized_filter_types_list = ['slice_hrn', 'record_id_user']
1310 return_slicerec_dictlist = []
1312 #First try to get information on the slice based on the filter provided
1313 if slice_filter_type in authorized_filter_types_list:
1314 fixed_slicerec_dict = self._get_slice_records(slice_filter,
1316 # if the slice was not found in the sfa db
1317 if fixed_slicerec_dict is None:
1318 return return_slicerec_dictlist
1320 slice_hrn = fixed_slicerec_dict['hrn']
1322 logger.debug(" IOTLAB_API \tGetSlices login %s \
1323 slice record %s slice_filter %s \
1324 slice_filter_type %s " % (login,
1325 fixed_slicerec_dict, slice_filter,
1329 #Now we have the slice record fixed_slicerec_dict, get the
1330 #jobs associated to this slice
1333 leases_list = self.GetLeases(login=login)
1334 #If no job is running or no job scheduled
1335 #return only the slice record
1336 if leases_list == [] and fixed_slicerec_dict:
1337 return_slicerec_dictlist.append(fixed_slicerec_dict)
1339 # if the jobs running don't belong to the user/slice we are looking
1341 leases_hrn = [lease['slice_hrn'] for lease in leases_list]
1342 if slice_hrn not in leases_hrn:
1343 return_slicerec_dictlist.append(fixed_slicerec_dict)
1344 #If several jobs for one slice , put the slice record into
1345 # each lease information dict
1346 for lease in leases_list:
1348 logger.debug("IOTLAB_API.PY \tGetSlices slice_filter %s \
1349 \t lease['slice_hrn'] %s"
1350 % (slice_filter, lease['slice_hrn']))
1351 if lease['slice_hrn'] == slice_hrn:
1352 slicerec_dict['oar_job_id'] = lease['lease_id']
1353 #Update lease dict with the slice record
1354 if fixed_slicerec_dict:
1355 fixed_slicerec_dict['oar_job_id'] = []
1356 fixed_slicerec_dict['oar_job_id'].append(
1357 slicerec_dict['oar_job_id'])
1358 slicerec_dict.update(fixed_slicerec_dict)
1359 #slicerec_dict.update({'hrn':\
1360 #str(fixed_slicerec_dict['slice_hrn'])})
1361 slicerec_dict['slice_hrn'] = lease['slice_hrn']
1362 slicerec_dict['hrn'] = lease['slice_hrn']
1363 slicerec_dict['user'] = lease['user']
1364 slicerec_dict.update(
1366 {'hostname': lease['reserved_nodes']}})
1367 slicerec_dict.update({'node_ids': lease['reserved_nodes']})
1371 return_slicerec_dictlist.append(slicerec_dict)
1372 logger.debug("IOTLAB_API.PY \tGetSlices \
1373 OHOHOHOH %s" %(return_slicerec_dictlist))
1375 logger.debug("IOTLAB_API.PY \tGetSlices \
1376 slicerec_dict %s return_slicerec_dictlist %s \
1377 lease['reserved_nodes'] \
1378 %s" % (slicerec_dict, return_slicerec_dictlist,
1379 lease['reserved_nodes']))
1381 logger.debug("IOTLAB_API.PY \tGetSlices RETURN \
1382 return_slicerec_dictlist %s"
1383 % (return_slicerec_dictlist))
1385 return return_slicerec_dictlist
1389 #Get all slices from the iotlab sfa database ,
1390 #put them in dict format
1391 #query_slice_list = dbsession.query(RegRecord).all()
1392 query_slice_list = \
1393 dbsession.query(RegSlice).options(joinedload('reg_researchers')).all()
1395 for record in query_slice_list:
1396 tmp = record.__dict__
1397 tmp['reg_researchers'] = tmp['reg_researchers'][0].__dict__
1398 #del tmp['reg_researchers']['_sa_instance_state']
1399 return_slicerec_dictlist.append(tmp)
1400 #return_slicerec_dictlist.append(record.__dict__)
1402 #Get all the jobs reserved nodes
1403 leases_list = self.GetReservedNodes()
1405 for fixed_slicerec_dict in return_slicerec_dictlist:
1407 #Check if the slice belongs to a iotlab user
1408 if fixed_slicerec_dict['peer_authority'] is None:
1409 owner = fixed_slicerec_dict['hrn'].split(
1410 ".")[1].split("_")[0]
1413 for lease in leases_list:
1414 if owner == lease['user']:
1415 slicerec_dict['oar_job_id'] = lease['lease_id']
1417 #for reserved_node in lease['reserved_nodes']:
1418 logger.debug("IOTLAB_API.PY \tGetSlices lease %s "
1420 slicerec_dict.update(fixed_slicerec_dict)
1421 slicerec_dict.update({'node_ids':
1422 lease['reserved_nodes']})
1423 slicerec_dict.update({'list_node_ids':
1425 lease['reserved_nodes']}})
1427 #slicerec_dict.update({'hrn':\
1428 #str(fixed_slicerec_dict['slice_hrn'])})
1429 #return_slicerec_dictlist.append(slicerec_dict)
1430 fixed_slicerec_dict.update(slicerec_dict)
1432 logger.debug("IOTLAB_API.PY \tGetSlices RETURN \
1433 return_slicerec_dictlist %s \slice_filter %s " \
1434 %(return_slicerec_dictlist, slice_filter))
1436 return return_slicerec_dictlist
1440 #Update slice unused, therefore sfa_fields_to_iotlab_fields unused
1443 #def sfa_fields_to_iotlab_fields(sfa_type, hrn, record):
1448 ##for field in record:
1449 ## iotlab_record[field] = record[field]
1451 #if sfa_type == "slice":
1452 ##instantion used in get_slivers ?
1453 #if not "instantiation" in iotlab_record:
1454 #iotlab_record["instantiation"] = "iotlab-instantiated"
1455 ##iotlab_record["hrn"] = hrn_to_pl_slicename(hrn)
1456 ##Unused hrn_to_pl_slicename because Iotlab's hrn already
1457 ##in the appropriate form SA 23/07/12
1458 #iotlab_record["hrn"] = hrn
1459 #logger.debug("IOTLAB_API.PY sfa_fields_to_iotlab_fields \
1460 #iotlab_record %s " %(iotlab_record['hrn']))
1461 #if "url" in record:
1462 #iotlab_record["url"] = record["url"]
1463 #if "description" in record:
1464 #iotlab_record["description"] = record["description"]
1465 #if "expires" in record:
1466 #iotlab_record["expires"] = int(record["expires"])
1468 ##nodes added by OAR only and then imported to SFA
1469 ##elif type == "node":
1470 ##if not "hostname" in iotlab_record:
1471 ##if not "hostname" in record:
1472 ##raise MissingSfaInfo("hostname")
1473 ##iotlab_record["hostname"] = record["hostname"]
1474 ##if not "model" in iotlab_record:
1475 ##iotlab_record["model"] = "geni"
1477 ##One authority only
1478 ##elif type == "authority":
1479 ##iotlab_record["login_base"] = hrn_to_iotlab_login_base(hrn)
1481 ##if not "name" in iotlab_record:
1482 ##iotlab_record["name"] = hrn
1484 ##if not "abbreviated_name" in iotlab_record:
1485 ##iotlab_record["abbreviated_name"] = hrn
1487 ##if not "enabled" in iotlab_record:
1488 ##iotlab_record["enabled"] = True
1490 ##if not "is_public" in iotlab_record:
1491 ##iotlab_record["is_public"] = True
1493 #return iotlab_record