Merge branch 'geni-v3' of ssh://git.onelab.eu/git/sfa into geni-v3
[sfa.git] / sfa / iotlab / iotlabshell.py
1 """
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.
5 TODO: Remove interactons with the SFA DB and put it in the driver iotlabdriver
6 instead.
7
8 """
9 from datetime import datetime
10
11 from sfa.util.sfalogging import logger
12
13
14 from sfa.iotlab.iotlabpostgres import LeaseTableXP
15 from sfa.iotlab.OARrestapi import OARrestapi
16 from sfa.iotlab.LDAPapi import LDAPapi
17
18 from sfa.util.xrn import Xrn, hrn_to_urn, get_authority
19
20 from sfa.iotlab.iotlabxrn import xrn_object
21
22 class IotlabShell():
23     """ Class enabled to use LDAP and OAR api calls. """
24
25     _MINIMUM_DURATION = 10  # 10 units of granularity 60 s, 10 mins
26
27     def __init__(self, config):
28         """Creates an instance of OARrestapi and LDAPapi which will be used to
29         issue calls to OAR or LDAP methods.
30         Set the time format  and the testbed granularity used for OAR
31         reservation and leases.
32
33         :param config: configuration object from sfa.util.config
34         :type config: Config object
35         """
36
37         # self.leases_db = TestbedAdditionalSfaDB(config)
38         self.oar = OARrestapi()
39         self.ldap = LDAPapi()
40         self.time_format = "%Y-%m-%d %H:%M:%S"
41         self.root_auth = config.SFA_REGISTRY_ROOT_AUTH
42         self.grain = 60  # 10 mins lease minimum, 60 sec granularity
43         #import logging, logging.handlers
44         #from sfa.util.sfalogging import _SfaLogger
45         #sql_logger = _SfaLogger(loggername = 'sqlalchemy.engine', \
46                                                     #level=logging.DEBUG)
47         return
48
49     @staticmethod
50     def GetMinExperimentDurationInGranularity():
51         """ Returns the minimum allowed duration for an experiment on the
52         testbed. In seconds.
53
54         """
55         return IotlabShell._MINIMUM_DURATION
56
57
58
59
60     #TODO  : Handling OR request in make_ldap_filters_from_records
61     #instead of the for loop
62     #over the records' list
63     def GetPersons(self, person_filter=None):
64         """
65         Get the enabled users and their properties from Iotlab LDAP.
66         If a filter is specified, looks for the user whose properties match
67         the filter, otherwise returns the whole enabled users'list.
68
69         :param person_filter: Must be a list of dictionnaries with users
70             properties when not set to None.
71         :type person_filter: list of dict
72
73         :returns: Returns a list of users whose accounts are enabled
74             found in ldap.
75         :rtype: list of dicts
76
77         """
78         logger.debug("IOTLAB_API \tGetPersons person_filter %s"
79                      % (person_filter))
80         person_list = []
81         if person_filter and isinstance(person_filter, list):
82         #If we are looking for a list of users (list of dict records)
83         #Usually the list contains only one user record
84             for searched_attributes in person_filter:
85
86                 #Get only enabled user accounts in iotlab LDAP :
87                 #add a filter for make_ldap_filters_from_record
88                 person = self.ldap.LdapFindUser(searched_attributes,
89                                                 is_user_enabled=True)
90                 #If a person was found, append it to the list
91                 if person:
92                     person_list.append(person)
93
94             #If the list is empty, return None
95             if len(person_list) is 0:
96                 person_list = None
97
98         else:
99             #Get only enabled user accounts in iotlab LDAP :
100             #add a filter for make_ldap_filters_from_record
101             person_list  = self.ldap.LdapFindUser(is_user_enabled=True)
102
103         return person_list
104
105
106     #def GetTimezone(self):
107         #""" Returns the OAR server time and timezone.
108         #Unused SA 30/05/13"""
109         #server_timestamp, server_tz = self.oar.parser.\
110                                             #SendRequest("GET_timezone")
111         #return server_timestamp, server_tz
112
113     def DeleteJobs(self, job_id, username):
114         """
115
116         Deletes the job with the specified job_id and username on OAR by
117             posting a delete request to OAR.
118
119         :param job_id: job id in OAR.
120         :param username: user's iotlab login in LDAP.
121         :type job_id: integer
122         :type username: string
123
124         :returns: dictionary with the job id and if delete has been successful
125             (True) or no (False)
126         :rtype: dict
127
128         """
129         logger.debug("IOTLAB_API \tDeleteJobs jobid  %s username %s "
130                      % (job_id, username))
131         if not job_id or job_id is -1:
132             return
133
134         reqdict = {}
135         reqdict['method'] = "delete"
136         reqdict['strval'] = str(job_id)
137
138         answer = self.oar.POSTRequestToOARRestAPI('DELETE_jobs_id',
139                                                   reqdict, username)
140         if answer['status'] == 'Delete request registered':
141             ret = {job_id: True}
142         else:
143             ret = {job_id: False}
144         logger.debug("IOTLAB_API \tDeleteJobs jobid  %s \r\n answer %s \
145                                 username %s" % (job_id, answer, username))
146         return ret
147
148
149
150         ##TODO : Unused GetJobsId ? SA 05/07/12
151     #def GetJobsId(self, job_id, username = None ):
152         #"""
153         #Details about a specific job.
154         #Includes details about submission time, jot type, state, events,
155         #owner, assigned ressources, walltime etc...
156
157         #"""
158         #req = "GET_jobs_id"
159         #node_list_k = 'assigned_network_address'
160         ##Get job info from OAR
161         #job_info = self.oar.parser.SendRequest(req, job_id, username)
162
163         #logger.debug("IOTLAB_API \t GetJobsId  %s " %(job_info))
164         #try:
165             #if job_info['state'] == 'Terminated':
166                 #logger.debug("IOTLAB_API \t GetJobsId job %s TERMINATED"\
167                                                             #%(job_id))
168                 #return None
169             #if job_info['state'] == 'Error':
170                 #logger.debug("IOTLAB_API \t GetJobsId ERROR message %s "\
171                                                             #%(job_info))
172                 #return None
173
174         #except KeyError:
175             #logger.error("IOTLAB_API \tGetJobsId KeyError")
176             #return None
177
178         #parsed_job_info  = self.get_info_on_reserved_nodes(job_info, \
179                                                             #node_list_k)
180         ##Replaces the previous entry
181         ##"assigned_network_address" / "reserved_resources"
182         ##with "node_ids"
183         #job_info.update({'node_ids':parsed_job_info[node_list_k]})
184         #del job_info[node_list_k]
185         #logger.debug(" \r\nIOTLAB_API \t GetJobsId job_info %s " %(job_info))
186         #return job_info
187
188
189     def GetJobsResources(self, job_id, username = None):
190         """ Gets the list of nodes associated with the job_id and username
191         if provided.
192
193         Transforms the iotlab hostnames to the corresponding SFA nodes hrns.
194         Returns dict key :'node_ids' , value : hostnames list.
195
196         :param username: user's LDAP login
197         :paran job_id: job's OAR identifier.
198         :type username: string
199         :type job_id: integer
200
201         :returns: dicionary with nodes' hostnames belonging to the job.
202         :rtype: dict
203
204         .. warning:: Unused. SA 16/10/13
205         """
206
207         req = "GET_jobs_id_resources"
208
209
210         #Get job resources list from OAR
211         node_id_list = self.oar.parser.SendRequest(req, job_id, username)
212         logger.debug("IOTLAB_API \t GetJobsResources  %s " %(node_id_list))
213
214         hostname_list = \
215             self.__get_hostnames_from_oar_node_ids(node_id_list)
216
217
218         #Replaces the previous entry "assigned_network_address" /
219         #"reserved_resources" with "node_ids"
220         job_info = {'node_ids': hostname_list}
221
222         return job_info
223
224
225     def GetNodesCurrentlyInUse(self):
226         """Returns a list of all the nodes already involved in an oar running
227         job.
228         :rtype: list of nodes hostnames.
229         """
230         return self.oar.parser.SendRequest("GET_running_jobs")
231
232     def __get_hostnames_from_oar_node_ids(self, oar_id_node_dict,
233             resource_id_list ):
234         """Get the hostnames of the nodes from their OAR identifiers.
235         Get the list of nodes dict using GetNodes and find the hostname
236         associated with the identifier.
237         :param oar_id_node_dict: full node dictionary list keyed by oar node id
238         :param resource_id_list: list of nodes identifiers
239         :returns: list of node hostnames.
240         """
241
242         hostname_list = []
243         for resource_id in resource_id_list:
244             #Because jobs requested "asap" do not have defined resources
245             if resource_id is not "Undefined":
246                 hostname_list.append(\
247                         oar_id_node_dict[resource_id]['hostname'])
248
249         return hostname_list
250
251     def GetReservedNodes(self, username=None):
252         """ Get list of leases. Get the leases for the username if specified,
253         otherwise get all the leases. Finds the nodes hostnames for each
254         OAR node identifier.
255         :param username: user's LDAP login
256         :type username: string
257         :returns: list of reservations dict
258         :rtype: dict list
259         """
260
261         #Get the nodes in use and the reserved nodes
262         reservation_dict_list = \
263                         self.oar.parser.SendRequest("GET_reserved_nodes", \
264                         username = username)
265
266         # Get the full node dict list once for all
267         # so that we can get the hostnames given their oar node id afterwards
268         # when the reservations are checked.
269         full_nodes_dict_list = self.GetNodes()
270         #Put the full node list into a dictionary keyed by oar node id
271         oar_id_node_dict = {}
272         for node in full_nodes_dict_list:
273             oar_id_node_dict[node['oar_id']] = node
274
275         for resa in reservation_dict_list:
276             logger.debug ("GetReservedNodes resa %s"%(resa))
277             #dict list of hostnames and their site
278             resa['reserved_nodes'] = \
279                 self.__get_hostnames_from_oar_node_ids(oar_id_node_dict,
280                     resa['resource_ids'])
281
282         #del resa['resource_ids']
283         return reservation_dict_list
284
285     def GetNodes(self, node_filter_dict=None, return_fields_list=None):
286         """
287
288         Make a list of iotlab nodes and their properties from information
289             given by OAR. Search for specific nodes if some filters are
290             specified. Nodes properties returned if no return_fields_list given:
291             'hrn','archi','mobile','hostname','site','boot_state','node_id',
292             'radio','posx','posy','oar_id','posz'.
293
294         :param node_filter_dict: dictionnary of lists with node properties. For
295             instance, if you want to look for a specific node with its hrn,
296             the node_filter_dict should be {'hrn': [hrn_of_the_node]}
297         :type node_filter_dict: dict
298         :param return_fields_list: list of specific fields the user wants to be
299             returned.
300         :type return_fields_list: list
301         :returns: list of dictionaries with node properties
302         :rtype: list
303
304         """
305         node_dict_by_id = self.oar.parser.SendRequest("GET_resources_full")
306         node_dict_list = node_dict_by_id.values()
307         logger.debug (" IOTLAB_API GetNodes  node_filter_dict %s \
308             return_fields_list %s " % (node_filter_dict, return_fields_list))
309         #No  filtering needed return the list directly
310         if not (node_filter_dict or return_fields_list):
311             return node_dict_list
312
313         return_node_list = []
314         if node_filter_dict:
315             for filter_key in node_filter_dict:
316                 try:
317                     #Filter the node_dict_list by each value contained in the
318                     #list node_filter_dict[filter_key]
319                     for value in node_filter_dict[filter_key]:
320                         for node in node_dict_list:
321                             if node[filter_key] == value:
322                                 if return_fields_list:
323                                     tmp = {}
324                                     for k in return_fields_list:
325                                         tmp[k] = node[k]
326                                     return_node_list.append(tmp)
327                                 else:
328                                     return_node_list.append(node)
329                 except KeyError:
330                     logger.log_exc("GetNodes KeyError")
331                     return
332
333
334         return return_node_list
335
336
337
338
339
340     def GetSites(self, site_filter_name_list=None, return_fields_list=None):
341         """Returns the list of Iotlab's sites with the associated nodes and
342         the sites' properties as dictionaries.
343
344         Site properties:
345         ['address_ids', 'slice_ids', 'name', 'node_ids', 'url', 'person_ids',
346         'site_tag_ids', 'enabled', 'site', 'longitude', 'pcu_ids',
347         'max_slivers', 'max_slices', 'ext_consortium_id', 'date_created',
348         'latitude', 'is_public', 'peer_site_id', 'peer_id', 'abbreviated_name']
349         Uses the OAR request GET_sites to find the Iotlab's sites.
350
351         :param site_filter_name_list: used to specify specific sites
352         :param return_fields_list: field that has to be returned
353         :type site_filter_name_list: list
354         :type return_fields_list: list
355
356
357         """
358         site_dict = self.oar.parser.SendRequest("GET_sites")
359         #site_dict : dict where the key is the sit ename
360         return_site_list = []
361         if not (site_filter_name_list or return_fields_list):
362             return_site_list = site_dict.values()
363             return return_site_list
364
365         for site_filter_name in site_filter_name_list:
366             if site_filter_name in site_dict:
367                 if return_fields_list:
368                     for field in return_fields_list:
369                         tmp = {}
370                         try:
371                             tmp[field] = site_dict[site_filter_name][field]
372                         except KeyError:
373                             logger.error("GetSites KeyError %s " % (field))
374                             return None
375                     return_site_list.append(tmp)
376                 else:
377                     return_site_list.append(site_dict[site_filter_name])
378
379         return return_site_list
380
381
382     #TODO : Check rights to delete person
383     def DeletePerson(self, person_record):
384         """Disable an existing account in iotlab LDAP.
385
386         Users and techs can only delete themselves. PIs can only
387             delete themselves and other non-PIs at their sites.
388             ins can delete anyone.
389
390         :param person_record: user's record
391         :type person_record: dict
392         :returns:  True if successful, False otherwise.
393         :rtype: boolean
394
395         .. todo:: CHECK THAT ONLY THE USER OR ADMIN CAN DEL HIMSELF.
396         """
397         #Disable user account in iotlab LDAP
398         ret = self.ldap.LdapMarkUserAsDeleted(person_record)
399         logger.warning("IOTLAB_API DeletePerson %s " % (person_record))
400         return ret['bool']
401
402     def DeleteSlice(self, slice_record):
403         """Deletes the specified slice and kills the jobs associated with
404             the slice if any,  using DeleteSliceFromNodes.
405
406         :param slice_record: record of the slice, must contain oar_job_id, user
407         :type slice_record: dict
408         :returns: True if all the jobs in the slice have been deleted,
409             or the list of jobs that could not be deleted otherwise.
410         :rtype: list or boolean
411
412          .. seealso:: DeleteSliceFromNodes
413
414         """
415         ret = self.DeleteSliceFromNodes(slice_record)
416         delete_failed = None
417         for job_id in ret:
418             if False in ret[job_id]:
419                 if delete_failed is None:
420                     delete_failed = []
421                 delete_failed.append(job_id)
422
423         logger.info("IOTLAB_API DeleteSlice %s  answer %s"%(slice_record, \
424                     delete_failed))
425         return delete_failed or True
426
427
428
429
430
431
432
433
434
435
436
437     #TODO AddPersonKey 04/07/2012 SA
438     def AddPersonKey(self, person_uid, old_attributes_dict, new_key_dict):
439         """Adds a new key to the specified account. Adds the key to the
440             iotlab ldap, provided that the person_uid is valid.
441
442         Non-admins can only modify their own keys.
443
444         :param person_uid: user's iotlab login in LDAP
445         :param old_attributes_dict: dict with the user's old sshPublicKey
446         :param new_key_dict: dict with the user's new sshPublicKey
447         :type person_uid: string
448
449
450         :rtype: Boolean
451         :returns: True if the key has been modified, False otherwise.
452
453         """
454         ret = self.ldap.LdapModify(person_uid, old_attributes_dict, \
455                                                                 new_key_dict)
456         logger.warning("IOTLAB_API AddPersonKey EMPTY - DO NOTHING \r\n ")
457         return ret['bool']
458
459     def DeleteLeases(self, leases_id_list, slice_hrn):
460         """
461
462         Deletes several leases, based on their job ids and the slice
463             they are associated with. Uses DeleteJobs to delete the jobs
464             on OAR. Note that one slice can contain multiple jobs, and in this
465             case all the jobs in the leases_id_list MUST belong to ONE slice,
466             since there is only one slice hrn provided here.
467
468         :param leases_id_list: list of job ids that belong to the slice whose
469             slice hrn is provided.
470         :param slice_hrn: the slice hrn.
471         :type slice_hrn: string
472
473         .. warning:: Does not have a return value since there was no easy
474             way to handle failure when dealing with multiple job delete. Plus,
475             there was no easy way to report it to the user.
476
477         """
478         logger.debug("IOTLAB_API DeleteLeases leases_id_list %s slice_hrn %s \
479                 \r\n " %(leases_id_list, slice_hrn))
480         for job_id in leases_id_list:
481             self.DeleteJobs(job_id, slice_hrn)
482
483         return
484
485     @staticmethod
486     def _process_walltime(duration):
487         """ Calculates the walltime in seconds from the duration in H:M:S
488             specified in the RSpec.
489
490         """
491         if duration:
492             # Fixing the walltime by adding a few delays.
493             # First put the walltime in seconds oarAdditionalDelay = 20;
494             #  additional delay for /bin/sleep command to
495             # take in account  prologue and epilogue scripts execution
496             # int walltimeAdditionalDelay = 240;  additional delay
497             #for prologue/epilogue execution = $SERVER_PROLOGUE_EPILOGUE_TIMEOUT
498             #in oar.conf
499             # Put the duration in seconds first
500             #desired_walltime = duration * 60
501             desired_walltime = duration
502             total_walltime = desired_walltime + 240 #+4 min Update SA 23/10/12
503             sleep_walltime = desired_walltime  # 0 sec added Update SA 23/10/12
504             walltime = []
505             #Put the walltime back in str form
506             #First get the hours
507             walltime.append(str(total_walltime / 3600))
508             total_walltime = total_walltime - 3600 * int(walltime[0])
509             #Get the remaining minutes
510             walltime.append(str(total_walltime / 60))
511             total_walltime = total_walltime - 60 * int(walltime[1])
512             #Get the seconds
513             walltime.append(str(total_walltime))
514
515         else:
516             logger.log_exc(" __process_walltime duration null")
517
518         return walltime, sleep_walltime
519
520     @staticmethod
521     def _create_job_structure_request_for_OAR(lease_dict):
522         """ Creates the structure needed for a correct POST on OAR.
523         Makes the timestamp transformation into the appropriate format.
524         Sends the POST request to create the job with the resources in
525         added_nodes.
526
527         """
528
529         nodeid_list = []
530         reqdict = {}
531
532
533         reqdict['workdir'] = '/tmp'
534         reqdict['resource'] = "{network_address in ("
535
536         for node in lease_dict['added_nodes']:
537             logger.debug("\r\n \r\n OARrestapi \t \
538             __create_job_structure_request_for_OAR node %s" %(node))
539
540             # Get the ID of the node
541             nodeid = node
542             reqdict['resource'] += "'" + nodeid + "', "
543             nodeid_list.append(nodeid)
544
545         custom_length = len(reqdict['resource'])- 2
546         reqdict['resource'] = reqdict['resource'][0:custom_length] + \
547                                             ")}/nodes=" + str(len(nodeid_list))
548
549
550         walltime, sleep_walltime = \
551                     IotlabShell._process_walltime(\
552                                      int(lease_dict['lease_duration']))
553
554
555         reqdict['resource'] += ",walltime=" + str(walltime[0]) + \
556                             ":" + str(walltime[1]) + ":" + str(walltime[2])
557         reqdict['script_path'] = "/bin/sleep " + str(sleep_walltime)
558
559         #In case of a scheduled experiment (not immediate)
560         #To run an XP immediately, don't specify date and time in RSpec
561         #They will be set to None.
562         if lease_dict['lease_start_time'] is not '0':
563             #Readable time accepted by OAR
564             start_time = datetime.fromtimestamp( \
565                 int(lease_dict['lease_start_time'])).\
566                 strftime(lease_dict['time_format'])
567             reqdict['reservation'] = start_time
568         #If there is not start time, Immediate XP. No need to add special
569         # OAR parameters
570
571
572         reqdict['type'] = "deploy"
573         reqdict['directory'] = ""
574         reqdict['name'] = "SFA_" + lease_dict['slice_user']
575
576         return reqdict
577
578
579     def LaunchExperimentOnOAR(self, added_nodes, slice_name, \
580                         lease_start_time, lease_duration, slice_user=None):
581
582         """
583         Create a job request structure based on the information provided
584         and post the job on OAR.
585         :param added_nodes: list of nodes that belong to the described lease.
586         :param slice_name: the slice hrn associated to the lease.
587         :param lease_start_time: timestamp of the lease startting time.
588         :param lease_duration: lease durationin minutes
589
590         """
591         lease_dict = {}
592         lease_dict['lease_start_time'] = lease_start_time
593         lease_dict['lease_duration'] = lease_duration
594         lease_dict['added_nodes'] = added_nodes
595         lease_dict['slice_name'] = slice_name
596         lease_dict['slice_user'] = slice_user
597         lease_dict['grain'] = self.GetLeaseGranularity()
598         lease_dict['time_format'] = self.time_format
599
600
601         logger.debug("IOTLAB_API.PY \tLaunchExperimentOnOAR slice_user %s\
602                              \r\n "  %(slice_user))
603         #Create the request for OAR
604         reqdict = self._create_job_structure_request_for_OAR(lease_dict)
605          # first step : start the OAR job and update the job
606         logger.debug("IOTLAB_API.PY \tLaunchExperimentOnOAR reqdict %s\
607                              \r\n "  %(reqdict))
608
609         answer = self.oar.POSTRequestToOARRestAPI('POST_job', \
610                                                 reqdict, slice_user)
611         logger.debug("IOTLAB_API \tLaunchExperimentOnOAR jobid  %s " %(answer))
612         try:
613             jobid = answer['id']
614         except KeyError:
615             logger.log_exc("IOTLAB_API \tLaunchExperimentOnOAR \
616                                 Impossible to create job  %s "  %(answer))
617             return None
618
619
620
621
622         if jobid :
623             logger.debug("IOTLAB_API \tLaunchExperimentOnOAR jobid %s \
624                     added_nodes %s slice_user %s" %(jobid, added_nodes, \
625                                                             slice_user))
626
627
628         return jobid
629
630
631
632
633
634     #Delete the jobs from job_iotlab table
635     def DeleteSliceFromNodes(self, slice_record):
636         """
637
638         Deletes all the running or scheduled jobs of a given slice
639             given its record.
640
641         :param slice_record: record of the slice, must contain oar_job_id, user
642         :type slice_record: dict
643
644         :returns: dict of the jobs'deletion status. Success= True, Failure=
645             False, for each job id.
646         :rtype: dict
647
648         """
649         logger.debug("IOTLAB_API \t  DeleteSliceFromNodes %s "
650                      % (slice_record))
651
652         if isinstance(slice_record['oar_job_id'], list):
653             oar_bool_answer = {}
654             for job_id in slice_record['oar_job_id']:
655                 ret = self.DeleteJobs(job_id, slice_record['user'])
656
657                 oar_bool_answer.update(ret)
658
659         else:
660             oar_bool_answer = self.DeleteJobs(slice_record['oar_job_id'],
661                                                slice_record['user'])
662
663         return oar_bool_answer
664
665
666
667     def GetLeaseGranularity(self):
668         """ Returns the granularity of an experiment in the Iotlab testbed.
669         OAR uses seconds for experiments duration , the granulaity is also
670         defined in seconds.
671         Experiments which last less than 10 min (600 sec) are invalid"""
672         return self.grain
673
674
675
676     @staticmethod
677     def filter_lease_name(reservation_list, filter_value):
678         filtered_reservation_list = list(reservation_list)
679         logger.debug("IOTLAB_API \t filter_lease_name reservation_list %s" \
680                         % (reservation_list))
681         for reservation in reservation_list:
682             if 'slice_hrn' in reservation and \
683                 reservation['slice_hrn'] != filter_value:
684                 filtered_reservation_list.remove(reservation)
685
686         logger.debug("IOTLAB_API \t filter_lease_name filtered_reservation_list\
687                      %s" % (filtered_reservation_list))
688         return filtered_reservation_list
689
690     @staticmethod
691     def filter_lease_start_time(reservation_list, filter_value):
692         filtered_reservation_list = list(reservation_list)
693
694         for reservation in reservation_list:
695             if 't_from' in reservation and \
696                 reservation['t_from'] > filter_value:
697                 filtered_reservation_list.remove(reservation)
698
699         return filtered_reservation_list
700
701
702
703
704
705
706 #TODO FUNCTIONS SECTION 04/07/2012 SA
707
708     ##TODO : Is UnBindObjectFromPeer still necessary ? Currently does nothing
709     ##04/07/2012 SA
710     #@staticmethod
711     #def UnBindObjectFromPeer( auth, object_type, object_id, shortname):
712         #""" This method is a hopefully temporary hack to let the sfa correctly
713         #detach the objects it creates from a remote peer object. This is
714         #needed so that the sfa federation link can work in parallel with
715         #RefreshPeer, as RefreshPeer depends on remote objects being correctly
716         #marked.
717         #Parameters:
718         #auth : struct, API authentication structure
719             #AuthMethod : string, Authentication method to use
720         #object_type : string, Object type, among 'site','person','slice',
721         #'node','key'
722         #object_id : int, object_id
723         #shortname : string, peer shortname
724         #FROM PLC DOC
725
726         #"""
727         #logger.warning("IOTLAB_API \tUnBindObjectFromPeer EMPTY-\
728                         #DO NOTHING \r\n ")
729         #return
730
731     ##TODO Is BindObjectToPeer still necessary ? Currently does nothing
732     ##04/07/2012 SA
733     #|| Commented out 28/05/13 SA
734     #def BindObjectToPeer(self, auth, object_type, object_id, shortname=None, \
735                                                     #remote_object_id=None):
736         #"""This method is a hopefully temporary hack to let the sfa correctly
737         #attach the objects it creates to a remote peer object. This is needed
738         #so that the sfa federation link can work in parallel with RefreshPeer,
739         #as RefreshPeer depends on remote objects being correctly marked.
740         #Parameters:
741         #shortname : string, peer shortname
742         #remote_object_id : int, remote object_id, set to 0 if unknown
743         #FROM PLC API DOC
744
745         #"""
746         #logger.warning("IOTLAB_API \tBindObjectToPeer EMPTY - DO NOTHING \r\n ")
747         #return
748
749     ##TODO UpdateSlice 04/07/2012 SA || Commented out 28/05/13 SA
750     ##Funciton should delete and create another job since oin iotlab slice=job
751     #def UpdateSlice(self, auth, slice_id_or_name, slice_fields=None):
752         #"""Updates the parameters of an existing slice with the values in
753         #slice_fields.
754         #Users may only update slices of which they are members.
755         #PIs may update any of the slices at their sites, or any slices of
756         #which they are members. Admins may update any slice.
757         #Only PIs and admins may update max_nodes. Slices cannot be renewed
758         #(by updating the expires parameter) more than 8 weeks into the future.
759          #Returns 1 if successful, faults otherwise.
760         #FROM PLC API DOC
761
762         #"""
763         #logger.warning("IOTLAB_API UpdateSlice EMPTY - DO NOTHING \r\n ")
764         #return
765
766     #Unused SA 30/05/13, we only update the user's key or we delete it.
767     ##TODO UpdatePerson 04/07/2012 SA
768     #def UpdatePerson(self, iotlab_hrn, federated_hrn, person_fields=None):
769         #"""Updates a person. Only the fields specified in person_fields
770         #are updated, all other fields are left untouched.
771         #Users and techs can only update themselves. PIs can only update
772         #themselves and other non-PIs at their sites.
773         #Returns 1 if successful, faults otherwise.
774         #FROM PLC API DOC
775
776         #"""
777         ##new_row = FederatedToIotlab(iotlab_hrn, federated_hrn)
778         ##self.leases_db.testbed_session.add(new_row)
779         ##self.leases_db.testbed_session.commit()
780
781         #logger.debug("IOTLAB_API UpdatePerson EMPTY - DO NOTHING \r\n ")
782         #return
783
784
785
786
787     #TODO : test
788     def DeleteKey(self, user_record, key_string):
789         """Deletes a key in the LDAP entry of the specified user.
790
791         Removes the key_string from the user's key list and updates the LDAP
792             user's entry with the new key attributes.
793
794         :param key_string: The ssh key to remove
795         :param user_record: User's record
796         :type key_string: string
797         :type user_record: dict
798         :returns: True if sucessful, False if not.
799         :rtype: Boolean
800
801         """
802
803         all_user_keys = user_record['keys']
804         all_user_keys.remove(key_string)
805         new_attributes = {'sshPublicKey':all_user_keys}
806         ret = self.ldap.LdapModifyUser(user_record, new_attributes)
807         logger.debug("IOTLAB_API  DeleteKey  %s- " % (ret))
808         return ret['bool']
809
810
811
812
813
814
815
816
817     #Update slice unused, therefore  sfa_fields_to_iotlab_fields unused
818     #SA 30/05/13
819     #@staticmethod
820     #def sfa_fields_to_iotlab_fields(sfa_type, hrn, record):
821         #"""
822         #"""
823
824         #iotlab_record = {}
825         ##for field in record:
826         ##    iotlab_record[field] = record[field]
827
828         #if sfa_type == "slice":
829             ##instantion used in get_slivers ?
830             #if not "instantiation" in iotlab_record:
831                 #iotlab_record["instantiation"] = "iotlab-instantiated"
832             ##iotlab_record["hrn"] = hrn_to_pl_slicename(hrn)
833             ##Unused hrn_to_pl_slicename because Iotlab's hrn already
834             ##in the appropriate form SA 23/07/12
835             #iotlab_record["hrn"] = hrn
836             #logger.debug("IOTLAB_API.PY sfa_fields_to_iotlab_fields \
837                         #iotlab_record %s  " %(iotlab_record['hrn']))
838             #if "url" in record:
839                 #iotlab_record["url"] = record["url"]
840             #if "description" in record:
841                 #iotlab_record["description"] = record["description"]
842             #if "expires" in record:
843                 #iotlab_record["expires"] = int(record["expires"])
844
845         ##nodes added by OAR only and then imported to SFA
846         ##elif type == "node":
847             ##if not "hostname" in iotlab_record:
848                 ##if not "hostname" in record:
849                     ##raise MissingSfaInfo("hostname")
850                 ##iotlab_record["hostname"] = record["hostname"]
851             ##if not "model" in iotlab_record:
852                 ##iotlab_record["model"] = "geni"
853
854         ##One authority only
855         ##elif type == "authority":
856             ##iotlab_record["login_base"] = hrn_to_iotlab_login_base(hrn)
857
858             ##if not "name" in iotlab_record:
859                 ##iotlab_record["name"] = hrn
860
861             ##if not "abbreviated_name" in iotlab_record:
862                 ##iotlab_record["abbreviated_name"] = hrn
863
864             ##if not "enabled" in iotlab_record:
865                 ##iotlab_record["enabled"] = True
866
867             ##if not "is_public" in iotlab_record:
868                 ##iotlab_record["is_public"] = True
869
870         #return iotlab_record
871
872
873
874
875
876
877
878
879
880