2 File containing the CortexlabShell, 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
10 from sfa.util.sfatime import SFATIME_FORMAT
12 from sfa.iotlab.iotlabpostgres import LeaseTableXP
13 from sfa.cortexlab.LDAPapi import LDAPapi
17 from sfa.iotlab.iotlabxrn import xrn_object
18 from sfa.cortexlab.cortexlabnodes import CortexlabQueryNodes
20 class CortexlabShell():
21 """ Class enabled to use LDAP and OAR api calls. """
23 _MINIMUM_DURATION = 10 # 10 units of granularity 60 s, 10 mins
25 def __init__(self, config):
26 """Creates an instance of OARrestapi and LDAPapi which will be used to
27 issue calls to OAR or LDAP methods.
28 Set the time format and the testbed granularity used for OAR
29 reservation and leases.
31 :param config: configuration object from sfa.util.config
32 :type config: Config object
35 self.query_sites = CortexlabQueryNodes()
37 self.time_format = SFATIME_FORMAT
38 self.root_auth = config.SFA_REGISTRY_ROOT_AUTH
39 self.grain = 60 # 10 mins lease minimum, 60 sec granularity
40 #import logging, logging.handlers
41 #from sfa.util.sfalogging import _SfaLogger
42 #sql_logger = _SfaLogger(loggername = 'sqlalchemy.engine', \
47 def GetMinExperimentDurationInGranularity():
48 """ Returns the minimum allowed duration for an experiment on the
52 return CortexlabShell._MINIMUM_DURATION
54 #TODO : Handling OR request in make_ldap_filters_from_records
55 #instead of the for loop
56 #over the records' list
57 def GetPersons(self, person_filter=None):
59 Get the enabled users and their properties from Cortexlab LDAP.
60 If a filter is specified, looks for the user whose properties match
61 the filter, otherwise returns the whole enabled users'list.
63 :param person_filter: Must be a list of dictionnaries with users
64 properties when not set to None.
65 :type person_filter: list of dict
67 :returns: Returns a list of users whose accounts are enabled
72 logger.debug("CORTEXLAB_API \tGetPersons person_filter %s"
75 if person_filter and isinstance(person_filter, list):
76 #If we are looking for a list of users (list of dict records)
77 #Usually the list contains only one user record
78 for searched_attributes in person_filter:
80 #Get only enabled user accounts in iotlab LDAP :
81 #add a filter for make_ldap_filters_from_record
82 person = self.ldap.LdapFindUser(searched_attributes,
84 #If a person was found, append it to the list
86 person_list.append(person)
88 #If the list is empty, return None
89 if len(person_list) is 0:
93 #Get only enabled user accounts in iotlab LDAP :
94 #add a filter for make_ldap_filters_from_record
95 person_list = self.ldap.LdapFindUser(is_user_enabled=True)
101 def DeleteOneLease(self, lease_id, username):
104 Deletes the lease with the specified lease_id and username on OAR by
105 posting a delete request to OAR.
107 :param lease_id: Reservation identifier.
108 :param username: user's iotlab login in LDAP.
109 :type lease_id: Depends on what tou are using, could be integer or
111 :type username: string
113 :returns: dictionary with the lease id and if delete has been successful
119 # Here delete the lease specified
120 answer = self.query_sites.delete_experiment(lease_id, username)
122 # If the username is not necessary to delete the lease, then you can
123 # remove it from the parameters, given that you propagate the changes
124 # Return delete status so that you know if the delete has been
128 if answer['status'] is True:
129 ret = {lease_id: True}
131 ret = {lease_id: False}
132 logger.debug("CORTEXLAB_API \DeleteOneLease lease_id %s \r\n answer %s \
133 username %s" % (lease_id, answer, username))
138 def GetNodesCurrentlyInUse(self):
139 """Returns a list of all the nodes involved in a currently running
140 experiment (and only the one not available at the moment the call to
141 this method is issued)
142 :rtype: list of nodes hostnames.
144 node_hostnames_list = []
145 return node_hostnames_list
148 def GetReservedNodes(self, username=None):
149 """ Get list of leases. Get the leases for the username if specified,
150 otherwise get all the leases. Finds the nodes hostnames for each
152 :param username: user's LDAP login
153 :type username: string
154 :returns: list of reservations dict
158 #Get the nodes in use and the reserved nodes
159 mandatory_sfa_keys = ['reserved_nodes','lease_id']
160 reservation_dict_list = \
161 self.query_sites.get_reserved_nodes(username = username)
163 if len(reservation_dict_list) == 0:
167 # Ensure mandatory keys are in the dict
168 if not self.ensure_format_is_valid(reservation_dict_list,
170 raise KeyError, "GetReservedNodes : Missing SFA mandatory keys"
173 return reservation_dict_list
176 def ensure_format_is_valid(list_dictionary_to_check, mandatory_keys_list):
177 for entry in list_dictionary_to_check:
178 if not all (key in entry for key in mandatory_keys_list):
182 def GetNodes(self, node_filter_dict=None, return_fields_list=None):
185 Make a list of cortexlab nodes and their properties from information
186 given by ?. Search for specific nodes if some filters are
187 specified. Nodes properties returned if no return_fields_list given:
188 'hrn','archi','mobile','hostname','site','boot_state','node_id',
189 'radio','posx','posy,'posz'.
191 :param node_filter_dict: dictionnary of lists with node properties. For
192 instance, if you want to look for a specific node with its hrn,
193 the node_filter_dict should be {'hrn': [hrn_of_the_node]}
194 :type node_filter_dict: dict
195 :param return_fields_list: list of specific fields the user wants to be
197 :type return_fields_list: list
198 :returns: list of dictionaries with node properties. Mandatory
199 properties hrn, site, hostname. Complete list (iotlab) ['hrn',
200 'archi', 'mobile', 'hostname', 'site', 'mobility_type',
201 'boot_state', 'node_id','radio', 'posx', 'posy', 'oar_id', 'posz']
202 Radio, archi, mobile and position are useful to help users choose
203 the appropriate nodes.
206 :TODO: FILL IN THE BLANKS
209 # Here get full dict of nodes with all their properties.
210 mandatory_sfa_keys = ['hrn', 'site', 'hostname']
211 node_list_dict = self.query_sites.get_all_nodes(node_filter_dict,
214 if len(node_list_dict) == 0:
215 return_node_list = []
218 # Ensure mandatory keys are in the dict
219 if not self.ensure_format_is_valid(node_list_dict,
221 raise KeyError, "GetNodes : Missing SFA mandatory keys"
224 return_node_list = node_list_dict
225 return return_node_list
230 def GetSites(self, site_filter_name_list=None, return_fields_list=None):
231 """Returns the list of Cortexlab's sites with the associated nodes and
232 the sites' properties as dictionaries. Used in import.
235 ['address_ids', 'slice_ids', 'name', 'node_ids', 'url', 'person_ids',
236 'site_tag_ids', 'enabled', 'site', 'longitude', 'pcu_ids',
237 'max_slivers', 'max_slices', 'ext_consortium_id', 'date_created',
238 'latitude', 'is_public', 'peer_site_id', 'peer_id', 'abbreviated_name']
239 can be empty ( []): address_ids, slice_ids, pcu_ids, person_ids,
242 :param site_filter_name_list: used to specify specific sites
243 :param return_fields_list: field that has to be returned
244 :type site_filter_name_list: list
245 :type return_fields_list: list
246 :rtype: list of dicts
249 site_list_dict = self.query_sites.get_sites(site_filter_name_list,
252 mandatory_sfa_keys = ['name', 'node_ids', 'longitude','site' ]
254 if len(site_list_dict) == 0:
255 return_site_list = []
258 # Ensure mandatory keys are in the dict
259 if not self.ensure_format_is_valid(site_list_dict,
261 raise KeyError, "GetSites : Missing sfa mandatory keys"
263 return_site_list = site_list_dict
264 return return_site_list
267 #TODO : Check rights to delete person
268 def DeletePerson(self, person_record):
269 """Disable an existing account in cortexlab LDAP.
271 Users and techs can only delete themselves. PIs can only
272 delete themselves and other non-PIs at their sites.
273 ins can delete anyone.
275 :param person_record: user's record
276 :type person_record: dict
277 :returns: True if successful, False otherwise.
280 .. todo:: CHECK THAT ONLY THE USER OR ADMIN CAN DEL HIMSELF.
282 #Disable user account in iotlab LDAP
283 ret = self.ldap.LdapMarkUserAsDeleted(person_record)
284 logger.warning("CORTEXLAB_API DeletePerson %s " % (person_record))
287 def DeleteSlice(self, slice_record):
288 """Deletes the specified slice and kills the jobs associated with
289 the slice if any, using DeleteSliceFromNodes.
291 :param slice_record: record of the slice, must contain experiment_id, user
292 :type slice_record: dict
293 :returns: True if all the jobs in the slice have been deleted,
294 or the list of jobs that could not be deleted otherwise.
295 :rtype: list or boolean
297 .. seealso:: DeleteSliceFromNodes
300 ret = self.DeleteSliceFromNodes(slice_record)
302 for experiment_id in ret:
303 if False in ret[experiment_id]:
304 if delete_failed is None:
306 delete_failed.append(experiment_id)
308 logger.info("CORTEXLAB_API DeleteSlice %s answer %s"%(slice_record, \
310 return delete_failed or True
313 #TODO AddPersonKey 04/07/2012 SA
314 def AddPersonKey(self, person_uid, old_attributes_dict, new_key_dict):
315 """Adds a new key to the specified account. Adds the key to the
316 iotlab ldap, provided that the person_uid is valid.
318 Non-admins can only modify their own keys.
320 :param person_uid: user's iotlab login in LDAP
321 :param old_attributes_dict: dict with the user's old sshPublicKey
322 :param new_key_dict: dict with the user's new sshPublicKey
323 :type person_uid: string
327 :returns: True if the key has been modified, False otherwise.
330 ret = self.ldap.LdapModify(person_uid, old_attributes_dict, \
332 logger.warning("CORTEXLAB_API AddPersonKey EMPTY - DO NOTHING \r\n ")
335 def DeleteLeases(self, leases_id_list, slice_hrn):
338 Deletes several leases, based on their experiment ids and the slice
339 they are associated with. Uses DeleteOneLease to delete the
340 experiment on the testbed. Note that one slice can contain multiple
341 experiments, and in this
342 case all the experiments in the leases_id_list MUST belong to this
343 same slice, since there is only one slice hrn provided here.
345 :param leases_id_list: list of job ids that belong to the slice whose
346 slice hrn is provided.
347 :param slice_hrn: the slice hrn.
348 :type slice_hrn: string
350 .. warning:: Does not have a return value since there was no easy
351 way to handle failure when dealing with multiple job delete. Plus,
352 there was no easy way to report it to the user.
355 logger.debug("CORTEXLAB_API DeleteLeases leases_id_list %s slice_hrn %s \
356 \r\n " %(leases_id_list, slice_hrn))
357 for experiment_id in leases_id_list:
358 self.DeleteOneLease(experiment_id, slice_hrn)
364 def _process_walltime(duration):
365 """ Calculates the walltime in seconds from the duration in H:M:S
366 specified in the RSpec.
370 # Fixing the walltime by adding a few delays.
371 # First put the walltime in seconds oarAdditionalDelay = 20;
372 # additional delay for /bin/sleep command to
373 # take in account prologue and epilogue scripts execution
374 # int walltimeAdditionalDelay = 240; additional delay
375 #for prologue/epilogue execution = $SERVER_PROLOGUE_EPILOGUE_TIMEOUT
377 # Put the duration in seconds first
378 #desired_walltime = duration * 60
379 desired_walltime = duration
380 total_walltime = desired_walltime + 240 #+4 min Update SA 23/10/12
381 sleep_walltime = desired_walltime # 0 sec added Update SA 23/10/12
383 #Put the walltime back in str form
385 walltime.append(str(total_walltime / 3600))
386 total_walltime = total_walltime - 3600 * int(walltime[0])
387 #Get the remaining minutes
388 walltime.append(str(total_walltime / 60))
389 total_walltime = total_walltime - 60 * int(walltime[1])
391 walltime.append(str(total_walltime))
394 logger.log_exc(" __process_walltime duration null")
396 return walltime, sleep_walltime
399 def _create_job_structure_request_for_OAR(lease_dict):
400 """ Creates the structure needed for a correct POST on OAR.
401 Makes the timestamp transformation into the appropriate format.
402 Sends the POST request to create the job with the resources in
411 reqdict['workdir'] = '/tmp'
412 reqdict['resource'] = "{network_address in ("
414 for node in lease_dict['added_nodes']:
415 logger.debug("\r\n \r\n OARrestapi \t \
416 __create_job_structure_request_for_OAR node %s" %(node))
418 # Get the ID of the node
420 reqdict['resource'] += "'" + nodeid + "', "
421 nodeid_list.append(nodeid)
423 custom_length = len(reqdict['resource'])- 2
424 reqdict['resource'] = reqdict['resource'][0:custom_length] + \
425 ")}/nodes=" + str(len(nodeid_list))
428 walltime, sleep_walltime = \
429 CortexlabShell._process_walltime(\
430 int(lease_dict['lease_duration']))
433 reqdict['resource'] += ",walltime=" + str(walltime[0]) + \
434 ":" + str(walltime[1]) + ":" + str(walltime[2])
435 reqdict['script_path'] = "/bin/sleep " + str(sleep_walltime)
437 #In case of a scheduled experiment (not immediate)
438 #To run an XP immediately, don't specify date and time in RSpec
439 #They will be set to None.
440 if lease_dict['lease_start_time'] is not '0':
441 #Readable time accepted by OAR
442 start_time = datetime.fromtimestamp( \
443 int(lease_dict['lease_start_time'])).\
444 strftime(lease_dict['time_format'])
445 reqdict['reservation'] = start_time
446 #If there is not start time, Immediate XP. No need to add special
450 reqdict['type'] = "deploy"
451 reqdict['directory'] = ""
452 reqdict['name'] = "SFA_" + lease_dict['slice_user']
457 def LaunchExperimentOnTestbed(self, added_nodes, slice_name, \
458 lease_start_time, lease_duration, slice_user=None):
461 Create an experiment request structure based on the information provided
462 and schedule/run the experiment on the testbed by reserving the nodes.
463 :param added_nodes: list of nodes that belong to the described lease.
464 :param slice_name: the slice hrn associated to the lease.
465 :param lease_start_time: timestamp of the lease startting time.
466 :param lease_duration: lease duration in minutes
470 # Add in the dict whatever is necessary to create the experiment on
472 lease_dict['lease_start_time'] = lease_start_time
473 lease_dict['lease_duration'] = lease_duration
474 lease_dict['added_nodes'] = added_nodes
475 lease_dict['slice_name'] = slice_name
476 lease_dict['slice_user'] = slice_user
477 lease_dict['grain'] = self.GetLeaseGranularity()
481 answer = self.query_sites.schedule_experiment(lease_dict)
483 experiment_id = answer['id']
485 logger.log_exc("CORTEXLAB_API \tLaunchExperimentOnTestbed \
486 Impossible to create xp %s " %(answer))
490 logger.debug("CORTEXLAB_API \tLaunchExperimentOnTestbed \
491 experiment_id %s added_nodes %s slice_user %s"
492 %(experiment_id, added_nodes, slice_user))
500 #Delete the jobs from job_iotlab table
501 def DeleteSliceFromNodes(self, slice_record):
503 Deletes all the running or scheduled jobs of a given slice
506 :param slice_record: record of the slice, must contain experiment_id,
508 :type slice_record: dict
509 :returns: dict of the jobs'deletion status. Success= True, Failure=
510 False, for each job id.
513 .. note: used in driver delete_sliver
516 logger.debug("CORTEXLAB_API \t DeleteSliceFromNodes %s "
519 if isinstance(slice_record['experiment_id'], list):
520 experiment_bool_answer = {}
521 for experiment_id in slice_record['experiment_id']:
522 ret = self.DeleteOneLease(experiment_id, slice_record['user'])
524 experiment_bool_answer.update(ret)
527 experiment_bool_answer = [self.DeleteOneLease(
528 slice_record['experiment_id'],
529 slice_record['user'])]
531 return experiment_bool_answer
535 def GetLeaseGranularity(self):
536 """ Returns the granularity of an experiment in the Iotlab testbed.
537 OAR uses seconds for experiments duration , the granulaity is also
539 Experiments which last less than 10 min (600 sec) are invalid"""
543 def filter_lease(reservation_list, filter_type, filter_value ):
544 """Filters the lease reservation list by removing each lease whose
545 filter_type is not equal to the filter_value provided. Returns the list
546 of leases in one slice, defined by the slice_hrn if filter_type
547 is 'slice_hrn'. Otherwise, returns all leases scheduled starting from
548 the filter_value if filter_type is 't_from'.
550 :param reservation_list: leases list
551 :type reservation_list: list of dictionary
552 :param filter_type: can be either 't_from' or 'slice hrn'
553 :type filter_type: string
554 :param filter_value: depending on the filter_type, can be the slice_hrn
555 or can be defining a timespan.
556 :type filter_value: if filter_type is 't_from', filter_value is int.
557 if filter_type is 'slice_hrn', filter_value is a string.
560 :returns: filtered_reservation_list, contains only leases running or
561 scheduled in the given slice (wanted_slice).Dict keys are
562 'lease_id','reserved_nodes','slice_id', 'state', 'user',
563 'component_id_list','slice_hrn', 'resource_ids', 't_from', 't_until'
567 filtered_reservation_list = list(reservation_list)
568 logger.debug("IOTLAB_API \t filter_lease_name reservation_list %s" \
569 % (reservation_list))
571 for reservation in reservation_list:
573 (filter_type is 'slice_hrn' and \
574 reservation['slice_hrn'] != filter_value) or \
575 (filter_type is 't_from' and \
576 reservation['t_from'] > filter_value):
577 filtered_reservation_list.remove(reservation)
579 logger.log_exc("Iotlabshell filter_lease : filter_type %s \
580 filter_value %s not in lease" %(filter_type,
583 return filtered_reservation_list
586 # def filter_lease_name(reservation_list, filter_value):
587 # filtered_reservation_list = list(reservation_list)
588 # logger.debug("CORTEXLAB_API \t filter_lease_name reservation_list %s" \
589 # % (reservation_list))
590 # for reservation in reservation_list:
591 # if 'slice_hrn' in reservation and \
592 # reservation['slice_hrn'] != filter_value:
593 # filtered_reservation_list.remove(reservation)
595 # logger.debug("CORTEXLAB_API \t filter_lease_name filtered_reservation_list %s" \
596 # % (filtered_reservation_list))
597 # return filtered_reservation_list
600 # def filter_lease_start_time(reservation_list, filter_value):
601 # filtered_reservation_list = list(reservation_list)
603 # for reservation in reservation_list:
604 # if 't_from' in reservation and \
605 # reservation['t_from'] > filter_value:
606 # filtered_reservation_list.remove(reservation)
608 # return filtered_reservation_list
610 def complete_leases_info(self, unfiltered_reservation_list, db_xp_dict):
612 """Check that the leases list of dictionaries contains the appropriate
613 fields and piece of information here
614 :param unfiltered_reservation_list: list of leases to be completed.
615 :param db_xp_dict: leases information in the lease_sfa table
616 :returns local_unfiltered_reservation_list: list of leases completed.
617 list of dictionaries describing the leases, with all the needed
618 information (sfa,ldap,nodes)to identify one particular lease.
619 :returns testbed_xp_list: list of experiments'ids running or scheduled
621 :rtype local_unfiltered_reservation_list: list of dict
622 :rtype testbed_xp_list: list
626 local_unfiltered_reservation_list = list(unfiltered_reservation_list)
627 # slice_hrn and lease_id are in the lease_table,
628 # so they are in the db_xp_dict.
629 # component_id_list : list of nodes xrns
630 # reserved_nodes : list of nodes' hostnames
631 # slice_id : slice urn, can be made from the slice hrn using hrn_to_urn
632 for resa in local_unfiltered_reservation_list:
634 #Construct list of scheduled experiments (runing, waiting..)
635 testbed_xp_list.append(resa['lease_id'])
636 #If there is information on the experiment in the lease table
637 #(slice used and experiment id), meaning the experiment was created
639 if resa['lease_id'] in db_xp_dict:
640 xp_info = db_xp_dict[resa['lease_id']]
641 logger.debug("CORTEXLAB_API \tGetLeases xp_info %s"
643 resa['slice_hrn'] = xp_info['slice_hrn']
644 resa['slice_id'] = hrn_to_urn(resa['slice_hrn'], 'slice')
646 #otherwise, assume it is a cortexlab slice, created via the
649 resa['slice_id'] = hrn_to_urn(self.root_auth + '.' +
650 resa['user'] + "_slice", 'slice')
651 resa['slice_hrn'] = Xrn(resa['slice_id']).get_hrn()
653 resa['component_id_list'] = []
654 #Transform the hostnames into urns (component ids)
655 for node in resa['reserved_nodes']:
657 iotlab_xrn = xrn_object(self.root_auth, node)
658 resa['component_id_list'].append(iotlab_xrn.urn)
660 return local_unfiltered_reservation_list, testbed_xp_list
663 #TODO FUNCTIONS SECTION 04/07/2012 SA
665 ##TODO : Is UnBindObjectFromPeer still necessary ? Currently does nothing
668 #def UnBindObjectFromPeer( auth, object_type, object_id, shortname):
669 #""" This method is a hopefully temporary hack to let the sfa correctly
670 #detach the objects it creates from a remote peer object. This is
671 #needed so that the sfa federation link can work in parallel with
672 #RefreshPeer, as RefreshPeer depends on remote objects being correctly
675 #auth : struct, API authentication structure
676 #AuthMethod : string, Authentication method to use
677 #object_type : string, Object type, among 'site','person','slice',
679 #object_id : int, object_id
680 #shortname : string, peer shortname
684 #logger.warning("CORTEXLAB_API \tUnBindObjectFromPeer EMPTY-\
688 ##TODO Is BindObjectToPeer still necessary ? Currently does nothing
690 #|| Commented out 28/05/13 SA
691 #def BindObjectToPeer(self, auth, object_type, object_id, shortname=None, \
692 #remote_object_id=None):
693 #"""This method is a hopefully temporary hack to let the sfa correctly
694 #attach the objects it creates to a remote peer object. This is needed
695 #so that the sfa federation link can work in parallel with RefreshPeer,
696 #as RefreshPeer depends on remote objects being correctly marked.
698 #shortname : string, peer shortname
699 #remote_object_id : int, remote object_id, set to 0 if unknown
703 #logger.warning("CORTEXLAB_API \tBindObjectToPeer EMPTY - DO NOTHING \r\n ")
706 ##TODO UpdateSlice 04/07/2012 SA || Commented out 28/05/13 SA
707 ##Funciton should delete and create another job since oin iotlab slice=job
708 #def UpdateSlice(self, auth, slice_id_or_name, slice_fields=None):
709 #"""Updates the parameters of an existing slice with the values in
711 #Users may only update slices of which they are members.
712 #PIs may update any of the slices at their sites, or any slices of
713 #which they are members. Admins may update any slice.
714 #Only PIs and admins may update max_nodes. Slices cannot be renewed
715 #(by updating the expires parameter) more than 8 weeks into the future.
716 #Returns 1 if successful, faults otherwise.
720 #logger.warning("CORTEXLAB_API UpdateSlice EMPTY - DO NOTHING \r\n ")
723 #Unused SA 30/05/13, we only update the user's key or we delete it.
724 ##TODO UpdatePerson 04/07/2012 SA
725 #def UpdatePerson(self, iotlab_hrn, federated_hrn, person_fields=None):
726 #"""Updates a person. Only the fields specified in person_fields
727 #are updated, all other fields are left untouched.
728 #Users and techs can only update themselves. PIs can only update
729 #themselves and other non-PIs at their sites.
730 #Returns 1 if successful, faults otherwise.
734 ##new_row = FederatedToIotlab(iotlab_hrn, federated_hrn)
735 ##self.leases_db.testbed_session.add(new_row)
736 ##self.leases_db.testbed_session.commit()
738 #logger.debug("CORTEXLAB_API UpdatePerson EMPTY - DO NOTHING \r\n ")
745 def DeleteKey(self, user_record, key_string):
746 """Deletes a key in the LDAP entry of the specified user.
748 Removes the key_string from the user's key list and updates the LDAP
749 user's entry with the new key attributes.
751 :param key_string: The ssh key to remove
752 :param user_record: User's record
753 :type key_string: string
754 :type user_record: dict
755 :returns: True if sucessful, False if not.
759 all_user_keys = user_record['keys']
760 all_user_keys.remove(key_string)
761 new_attributes = {'sshPublicKey':all_user_keys}
762 ret = self.ldap.LdapModifyUser(user_record, new_attributes)
763 logger.debug("CORTEXLAB_API DeleteKey %s- " % (ret))
772 #Update slice unused, therefore sfa_fields_to_iotlab_fields unused
775 #def sfa_fields_to_iotlab_fields(sfa_type, hrn, record):
780 ##for field in record:
781 ## iotlab_record[field] = record[field]
783 #if sfa_type == "slice":
784 ##instantion used in get_slivers ?
785 #if not "instantiation" in iotlab_record:
786 #iotlab_record["instantiation"] = "iotlab-instantiated"
787 ##iotlab_record["hrn"] = hrn_to_pl_slicename(hrn)
788 ##Unused hrn_to_pl_slicename because Iotlab's hrn already
789 ##in the appropriate form SA 23/07/12
790 #iotlab_record["hrn"] = hrn
791 #logger.debug("CORTEXLAB_API.PY sfa_fields_to_iotlab_fields \
792 #iotlab_record %s " %(iotlab_record['hrn']))
794 #iotlab_record["url"] = record["url"]
795 #if "description" in record:
796 #iotlab_record["description"] = record["description"]
797 #if "expires" in record:
798 #iotlab_record["expires"] = int(record["expires"])
800 ##nodes added by OAR only and then imported to SFA
801 ##elif type == "node":
802 ##if not "hostname" in iotlab_record:
803 ##if not "hostname" in record:
804 ##raise MissingSfaInfo("hostname")
805 ##iotlab_record["hostname"] = record["hostname"]
806 ##if not "model" in iotlab_record:
807 ##iotlab_record["model"] = "geni"
810 ##elif type == "authority":
811 ##iotlab_record["login_base"] = hrn_to_iotlab_login_base(hrn)
813 ##if not "name" in iotlab_record:
814 ##iotlab_record["name"] = hrn
816 ##if not "abbreviated_name" in iotlab_record:
817 ##iotlab_record["abbreviated_name"] = hrn
819 ##if not "enabled" in iotlab_record:
820 ##iotlab_record["enabled"] = True
822 ##if not "is_public" in iotlab_record:
823 ##iotlab_record["is_public"] = True
825 #return iotlab_record