Moving methods using the SFA db and api object from IotlabShell to IotlabDriver.
[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 TestbedAdditionalSfaDB, 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     def AddLeases(self, hostname_list, slice_record,
632                   lease_start_time, lease_duration):
633
634         """Creates a job in OAR corresponding to the information provided
635         as parameters. Adds the job id and the slice hrn in the iotlab
636         database so that we are able to know which slice has which nodes.
637
638         :param hostname_list: list of nodes' OAR hostnames.
639         :param slice_record: sfa slice record, must contain login and hrn.
640         :param lease_start_time: starting time , unix timestamp format
641         :param lease_duration: duration in minutes
642
643         :type hostname_list: list
644         :type slice_record: dict
645         :type lease_start_time: integer
646         :type lease_duration: integer
647         :returns: job_id, can be None if the job request failed.
648
649         """
650         logger.debug("IOTLAB_API \r\n \r\n \t AddLeases hostname_list %s  \
651                 slice_record %s lease_start_time %s lease_duration %s  "\
652                  %( hostname_list, slice_record , lease_start_time, \
653                  lease_duration))
654
655         #tmp = slice_record['reg-researchers'][0].split(".")
656         username = slice_record['login']
657         #username = tmp[(len(tmp)-1)]
658         job_id = self.LaunchExperimentOnOAR(hostname_list, \
659                                     slice_record['hrn'], \
660                                     lease_start_time, lease_duration, \
661                                     username)
662         if job_id is not None:
663             start_time = \
664                     datetime.fromtimestamp(int(lease_start_time)).\
665                     strftime(self.time_format)
666             end_time = lease_start_time + lease_duration
667
668
669             logger.debug("IOTLAB_API \r\n \r\n \t AddLeases TURN ON LOGGING SQL \
670                         %s %s %s "%(slice_record['hrn'], job_id, end_time))
671
672
673             logger.debug("IOTLAB_API \r\n \r\n \t AddLeases %s %s %s " \
674                     %(type(slice_record['hrn']), type(job_id), type(end_time)))
675
676             iotlab_ex_row = LeaseTableXP(slice_hrn = slice_record['hrn'],
677                                                     experiment_id=job_id,
678                                                     end_time= end_time)
679
680             logger.debug("IOTLAB_API \r\n \r\n \t AddLeases iotlab_ex_row %s" \
681                     %(iotlab_ex_row))
682             self.leases_db.testbed_session.add(iotlab_ex_row)
683             self.leases_db.testbed_session.commit()
684
685             logger.debug("IOTLAB_API \t AddLeases hostname_list start_time %s "
686                         %(start_time))
687
688         return job_id
689
690
691     #Delete the jobs from job_iotlab table
692     def DeleteSliceFromNodes(self, slice_record):
693         """
694
695         Deletes all the running or scheduled jobs of a given slice
696             given its record.
697
698         :param slice_record: record of the slice, must contain oar_job_id, user
699         :type slice_record: dict
700
701         :returns: dict of the jobs'deletion status. Success= True, Failure=
702             False, for each job id.
703         :rtype: dict
704
705         """
706         logger.debug("IOTLAB_API \t  DeleteSliceFromNodes %s "
707                      % (slice_record))
708
709         if isinstance(slice_record['oar_job_id'], list):
710             oar_bool_answer = {}
711             for job_id in slice_record['oar_job_id']:
712                 ret = self.DeleteJobs(job_id, slice_record['user'])
713
714                 oar_bool_answer.update(ret)
715
716         else:
717             oar_bool_answer = self.DeleteJobs(slice_record['oar_job_id'],
718                                                slice_record['user'])
719
720         return oar_bool_answer
721
722
723
724     def GetLeaseGranularity(self):
725         """ Returns the granularity of an experiment in the Iotlab testbed.
726         OAR uses seconds for experiments duration , the granulaity is also
727         defined in seconds.
728         Experiments which last less than 10 min (600 sec) are invalid"""
729         return self.grain
730
731
732
733     @staticmethod
734     def filter_lease_name(reservation_list, filter_value):
735         filtered_reservation_list = list(reservation_list)
736         logger.debug("IOTLAB_API \t filter_lease_name reservation_list %s" \
737                         % (reservation_list))
738         for reservation in reservation_list:
739             if 'slice_hrn' in reservation and \
740                 reservation['slice_hrn'] != filter_value:
741                 filtered_reservation_list.remove(reservation)
742
743         logger.debug("IOTLAB_API \t filter_lease_name filtered_reservation_list\
744                      %s" % (filtered_reservation_list))
745         return filtered_reservation_list
746
747     @staticmethod
748     def filter_lease_start_time(reservation_list, filter_value):
749         filtered_reservation_list = list(reservation_list)
750
751         for reservation in reservation_list:
752             if 't_from' in reservation and \
753                 reservation['t_from'] > filter_value:
754                 filtered_reservation_list.remove(reservation)
755
756         return filtered_reservation_list
757
758
759     def GetLeases(self, lease_filter_dict=None, login=None):
760         """
761
762         Get the list of leases from OAR with complete information
763             about which slice owns which jobs and nodes.
764             Two purposes:
765             -Fetch all the jobs from OAR (running, waiting..)
766             complete the reservation information with slice hrn
767             found in testbed_xp table. If not available in the table,
768             assume it is a iotlab slice.
769             -Updates the iotlab table, deleting jobs when necessary.
770
771         :returns: reservation_list, list of dictionaries with 'lease_id',
772             'reserved_nodes','slice_id', 'state', 'user', 'component_id_list',
773             'slice_hrn', 'resource_ids', 't_from', 't_until'
774         :rtype: list
775
776         """
777
778         unfiltered_reservation_list = self.GetReservedNodes(login)
779
780         reservation_list = []
781         #Find the slice associated with this user iotlab ldap uid
782         logger.debug(" IOTLAB_API.PY \tGetLeases login %s\
783                         unfiltered_reservation_list %s "
784                      % (login, unfiltered_reservation_list))
785         #Create user dict first to avoid looking several times for
786         #the same user in LDAP SA 27/07/12
787         job_oar_list = []
788         jobs_psql_query = self.leases_db.testbed_session.query(LeaseTableXP).all()
789         jobs_psql_dict = dict([(row.experiment_id, row.__dict__)
790                                for row in jobs_psql_query])
791         #jobs_psql_dict = jobs_psql_dict)
792         logger.debug("IOTLAB_API \tGetLeases jobs_psql_dict %s"
793                      % (jobs_psql_dict))
794         jobs_psql_id_list = [row.experiment_id for row in jobs_psql_query]
795
796         for resa in unfiltered_reservation_list:
797             logger.debug("IOTLAB_API \tGetLeases USER %s"
798                          % (resa['user']))
799             #Construct list of jobs (runing, waiting..) in oar
800             job_oar_list.append(resa['lease_id'])
801             #If there is information on the job in IOTLAB DB ]
802             #(slice used and job id)
803             if resa['lease_id'] in jobs_psql_dict:
804                 job_info = jobs_psql_dict[resa['lease_id']]
805                 logger.debug("IOTLAB_API \tGetLeases job_info %s"
806                           % (job_info))
807                 resa['slice_hrn'] = job_info['slice_hrn']
808                 resa['slice_id'] = hrn_to_urn(resa['slice_hrn'], 'slice')
809
810             #otherwise, assume it is a iotlab slice:
811             else:
812                 resa['slice_id'] = hrn_to_urn(self.root_auth + '.' +
813                                               resa['user'] + "_slice", 'slice')
814                 resa['slice_hrn'] = Xrn(resa['slice_id']).get_hrn()
815
816             resa['component_id_list'] = []
817             #Transform the hostnames into urns (component ids)
818             for node in resa['reserved_nodes']:
819
820                 iotlab_xrn = xrn_object(self.root_auth, node)
821                 resa['component_id_list'].append(iotlab_xrn.urn)
822
823         if lease_filter_dict:
824             logger.debug("IOTLAB_API \tGetLeases  \
825                     \r\n leasefilter %s" % ( lease_filter_dict))
826
827             filter_dict_functions = {
828             'slice_hrn' : IotlabShell.filter_lease_name,
829             't_from' : IotlabShell.filter_lease_start_time
830             }
831             reservation_list = list(unfiltered_reservation_list)
832             for filter_type in lease_filter_dict:
833                 logger.debug("IOTLAB_API \tGetLeases reservation_list %s" \
834                     % (reservation_list))
835                 reservation_list = filter_dict_functions[filter_type](\
836                     reservation_list,lease_filter_dict[filter_type] )
837
838                 # Filter the reservation list with a maximum timespan so that the
839                 # leases and jobs running after this timestamp do not appear
840                 # in the result leases.
841                 # if 'start_time' in :
842                 #     if resa['start_time'] < lease_filter_dict['start_time']:
843                 #        reservation_list.append(resa)
844
845
846                 # if 'name' in lease_filter_dict and \
847                 #     lease_filter_dict['name'] == resa['slice_hrn']:
848                 #     reservation_list.append(resa)
849
850
851         if lease_filter_dict is None:
852             reservation_list = unfiltered_reservation_list
853
854         self.leases_db.update_experiments_in_additional_sfa_db(job_oar_list, jobs_psql_id_list)
855
856         logger.debug(" IOTLAB_API.PY \tGetLeases reservation_list %s"
857                      % (reservation_list))
858         return reservation_list
859
860
861
862
863 #TODO FUNCTIONS SECTION 04/07/2012 SA
864
865     ##TODO : Is UnBindObjectFromPeer still necessary ? Currently does nothing
866     ##04/07/2012 SA
867     #@staticmethod
868     #def UnBindObjectFromPeer( auth, object_type, object_id, shortname):
869         #""" This method is a hopefully temporary hack to let the sfa correctly
870         #detach the objects it creates from a remote peer object. This is
871         #needed so that the sfa federation link can work in parallel with
872         #RefreshPeer, as RefreshPeer depends on remote objects being correctly
873         #marked.
874         #Parameters:
875         #auth : struct, API authentication structure
876             #AuthMethod : string, Authentication method to use
877         #object_type : string, Object type, among 'site','person','slice',
878         #'node','key'
879         #object_id : int, object_id
880         #shortname : string, peer shortname
881         #FROM PLC DOC
882
883         #"""
884         #logger.warning("IOTLAB_API \tUnBindObjectFromPeer EMPTY-\
885                         #DO NOTHING \r\n ")
886         #return
887
888     ##TODO Is BindObjectToPeer still necessary ? Currently does nothing
889     ##04/07/2012 SA
890     #|| Commented out 28/05/13 SA
891     #def BindObjectToPeer(self, auth, object_type, object_id, shortname=None, \
892                                                     #remote_object_id=None):
893         #"""This method is a hopefully temporary hack to let the sfa correctly
894         #attach the objects it creates to a remote peer object. This is needed
895         #so that the sfa federation link can work in parallel with RefreshPeer,
896         #as RefreshPeer depends on remote objects being correctly marked.
897         #Parameters:
898         #shortname : string, peer shortname
899         #remote_object_id : int, remote object_id, set to 0 if unknown
900         #FROM PLC API DOC
901
902         #"""
903         #logger.warning("IOTLAB_API \tBindObjectToPeer EMPTY - DO NOTHING \r\n ")
904         #return
905
906     ##TODO UpdateSlice 04/07/2012 SA || Commented out 28/05/13 SA
907     ##Funciton should delete and create another job since oin iotlab slice=job
908     #def UpdateSlice(self, auth, slice_id_or_name, slice_fields=None):
909         #"""Updates the parameters of an existing slice with the values in
910         #slice_fields.
911         #Users may only update slices of which they are members.
912         #PIs may update any of the slices at their sites, or any slices of
913         #which they are members. Admins may update any slice.
914         #Only PIs and admins may update max_nodes. Slices cannot be renewed
915         #(by updating the expires parameter) more than 8 weeks into the future.
916          #Returns 1 if successful, faults otherwise.
917         #FROM PLC API DOC
918
919         #"""
920         #logger.warning("IOTLAB_API UpdateSlice EMPTY - DO NOTHING \r\n ")
921         #return
922
923     #Unused SA 30/05/13, we only update the user's key or we delete it.
924     ##TODO UpdatePerson 04/07/2012 SA
925     #def UpdatePerson(self, iotlab_hrn, federated_hrn, person_fields=None):
926         #"""Updates a person. Only the fields specified in person_fields
927         #are updated, all other fields are left untouched.
928         #Users and techs can only update themselves. PIs can only update
929         #themselves and other non-PIs at their sites.
930         #Returns 1 if successful, faults otherwise.
931         #FROM PLC API DOC
932
933         #"""
934         ##new_row = FederatedToIotlab(iotlab_hrn, federated_hrn)
935         ##self.leases_db.testbed_session.add(new_row)
936         ##self.leases_db.testbed_session.commit()
937
938         #logger.debug("IOTLAB_API UpdatePerson EMPTY - DO NOTHING \r\n ")
939         #return
940
941
942
943
944     #TODO : test
945     def DeleteKey(self, user_record, key_string):
946         """Deletes a key in the LDAP entry of the specified user.
947
948         Removes the key_string from the user's key list and updates the LDAP
949             user's entry with the new key attributes.
950
951         :param key_string: The ssh key to remove
952         :param user_record: User's record
953         :type key_string: string
954         :type user_record: dict
955         :returns: True if sucessful, False if not.
956         :rtype: Boolean
957
958         """
959
960         all_user_keys = user_record['keys']
961         all_user_keys.remove(key_string)
962         new_attributes = {'sshPublicKey':all_user_keys}
963         ret = self.ldap.LdapModifyUser(user_record, new_attributes)
964         logger.debug("IOTLAB_API  DeleteKey  %s- " % (ret))
965         return ret['bool']
966
967
968
969
970
971
972
973
974     #Update slice unused, therefore  sfa_fields_to_iotlab_fields unused
975     #SA 30/05/13
976     #@staticmethod
977     #def sfa_fields_to_iotlab_fields(sfa_type, hrn, record):
978         #"""
979         #"""
980
981         #iotlab_record = {}
982         ##for field in record:
983         ##    iotlab_record[field] = record[field]
984
985         #if sfa_type == "slice":
986             ##instantion used in get_slivers ?
987             #if not "instantiation" in iotlab_record:
988                 #iotlab_record["instantiation"] = "iotlab-instantiated"
989             ##iotlab_record["hrn"] = hrn_to_pl_slicename(hrn)
990             ##Unused hrn_to_pl_slicename because Iotlab's hrn already
991             ##in the appropriate form SA 23/07/12
992             #iotlab_record["hrn"] = hrn
993             #logger.debug("IOTLAB_API.PY sfa_fields_to_iotlab_fields \
994                         #iotlab_record %s  " %(iotlab_record['hrn']))
995             #if "url" in record:
996                 #iotlab_record["url"] = record["url"]
997             #if "description" in record:
998                 #iotlab_record["description"] = record["description"]
999             #if "expires" in record:
1000                 #iotlab_record["expires"] = int(record["expires"])
1001
1002         ##nodes added by OAR only and then imported to SFA
1003         ##elif type == "node":
1004             ##if not "hostname" in iotlab_record:
1005                 ##if not "hostname" in record:
1006                     ##raise MissingSfaInfo("hostname")
1007                 ##iotlab_record["hostname"] = record["hostname"]
1008             ##if not "model" in iotlab_record:
1009                 ##iotlab_record["model"] = "geni"
1010
1011         ##One authority only
1012         ##elif type == "authority":
1013             ##iotlab_record["login_base"] = hrn_to_iotlab_login_base(hrn)
1014
1015             ##if not "name" in iotlab_record:
1016                 ##iotlab_record["name"] = hrn
1017
1018             ##if not "abbreviated_name" in iotlab_record:
1019                 ##iotlab_record["abbreviated_name"] = hrn
1020
1021             ##if not "enabled" in iotlab_record:
1022                 ##iotlab_record["enabled"] = True
1023
1024             ##if not "is_public" in iotlab_record:
1025                 ##iotlab_record["is_public"] = True
1026
1027         #return iotlab_record
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037