Merge branch 'geni-v3' of ssh://git.onelab.eu/git/sfa into geni-v3
[sfa.git] / sfa / iotlab / iotlabdriver.py
1 """
2 Implements what a driver should provide for SFA to work.
3 """
4 from datetime import datetime
5 from sfa.util.faults import SliverDoesNotExist, Forbidden
6 from sfa.util.sfalogging import logger
7
8 from sfa.storage.model import RegRecord, RegUser, RegSlice, RegKey
9 from sfa.util.sfatime import utcparse, datetime_to_string
10 from sfa.trust.certificate import Keypair, convert_public_key
11
12 from sfa.trust.hierarchy import Hierarchy
13 from sfa.trust.gid import create_uuid
14
15 from sfa.managers.driver import Driver
16 from sfa.rspecs.version_manager import VersionManager
17 from sfa.rspecs.rspec import RSpec
18
19 from sfa.iotlab.iotlabxrn import IotlabXrn, xrn_object, xrn_to_hostname
20 from sfa.util.xrn import Xrn, hrn_to_urn, get_authority, urn_to_hrn
21 from sfa.iotlab.iotlabaggregate import IotlabAggregate
22
23 from sfa.iotlab.iotlabslices import IotlabSlices
24
25 from sfa.trust.credential import Credential
26 from sfa.storage.model import SliverAllocation
27
28 from sfa.iotlab.iotlabshell import IotlabShell
29 from sqlalchemy.orm import joinedload
30 from sfa.iotlab.iotlabpostgres import LeaseTableXP
31
32 class IotlabDriver(Driver):
33     """ Iotlab Driver class inherited from Driver generic class.
34
35     Contains methods compliant with the SFA standard and the testbed
36         infrastructure (calls to LDAP and OAR).
37
38     .. seealso::: Driver class
39
40     """
41     def __init__(self, api):
42         """
43
44         Sets the iotlab SFA config parameters,
45             instanciates the testbed api .
46
47         :param api: SfaApi configuration object. Holds reference to the
48             database.
49         :type api: SfaApi object
50
51         """
52         Driver.__init__(self, api)
53         self.api = api
54         config = api.config
55         self.testbed_shell = IotlabShell(config)
56         self.cache = None
57
58     def GetPeers(self, peer_filter=None ):
59         """ Gathers registered authorities in SFA DB and looks for specific peer
60         if peer_filter is specified.
61         :param peer_filter: name of the site authority looked for.
62         :type peer_filter: string
63         :returns: list of records.
64
65         """
66
67         existing_records = {}
68         existing_hrns_by_types = {}
69         logger.debug("IOTLAB_API \tGetPeers peer_filter %s " % (peer_filter))
70         query = self.api.dbsession().query(RegRecord)
71         all_records = query.filter(RegRecord.type.like('%authority%')).all()
72
73         for record in all_records:
74             existing_records[(record.hrn, record.type)] = record
75             if record.type not in existing_hrns_by_types:
76                 existing_hrns_by_types[record.type] = [record.hrn]
77             else:
78                 existing_hrns_by_types[record.type].append(record.hrn)
79
80         logger.debug("IOTLAB_API \tGetPeer\texisting_hrns_by_types %s "
81                      % (existing_hrns_by_types))
82         records_list = []
83
84         try:
85             if peer_filter:
86                 records_list.append(existing_records[(peer_filter,
87                                                      'authority')])
88             else:
89                 for hrn in existing_hrns_by_types['authority']:
90                     records_list.append(existing_records[(hrn, 'authority')])
91
92             logger.debug("IOTLAB_API \tGetPeer \trecords_list  %s "
93                          % (records_list))
94
95         except KeyError:
96             pass
97
98         return_records = records_list
99         logger.debug("IOTLAB_API \tGetPeer return_records %s "
100                      % (return_records))
101         return return_records
102
103     def GetKeys(self, key_filter=None):
104         """Returns a dict of dict based on the key string. Each dict entry
105         contains the key id, the ssh key, the user's email and the
106         user's hrn.
107         If key_filter is specified and is an array of key identifiers,
108         only keys matching the filter will be returned.
109
110         Admin may query all keys. Non-admins may only query their own keys.
111         FROM PLC API DOC
112
113         :returns: dict with ssh key as key and dicts as value.
114         :rtype: dict
115         """
116         query = self.api.dbsession().query(RegKey)
117         if key_filter is None:
118             keys = query.options(joinedload('reg_user')).all()
119         else:
120             constraint = RegKey.key.in_(key_filter)
121             keys = query.options(joinedload('reg_user')).filter(constraint).all()
122
123         key_dict = {}
124         for key in keys:
125             key_dict[key.key] = {'key_id': key.key_id, 'key': key.key,
126                                  'email': key.reg_user.email,
127                                  'hrn': key.reg_user.hrn}
128
129         #ldap_rslt = self.ldap.LdapSearch({'enabled']=True})
130         #user_by_email = dict((user[1]['mail'][0], user[1]['sshPublicKey']) \
131                                         #for user in ldap_rslt)
132
133         logger.debug("IOTLAB_API  GetKeys  -key_dict %s \r\n " % (key_dict))
134         return key_dict
135
136
137
138     def AddPerson(self, record, add_to_ldap = True):
139         """
140
141         Adds a new account. Any fields specified in records are used,
142             otherwise defaults are used. Creates an appropriate login by calling
143             LdapAddUser.
144
145         :param record: dictionary with the sfa user's properties.
146         :returns: a dicitonary with the status. If successful, the dictionary
147             boolean is set to True and there is a 'uid' key with the new login
148             added to LDAP, otherwise the bool is set to False and a key
149             'message' is in the dictionary, with the error message.
150         :rtype: dict
151
152         """
153         if not add_to_ldap:
154             ret = self.__add_person_to_db(record)
155             return ret
156
157         ret = self.testbed_shell.ldap.LdapAddUser(record)
158
159         if ret['bool'] is True:
160             record['hrn'] = self.testbed_shell.root_auth + '.' + ret['uid']
161             logger.debug("IOTLAB_API AddPerson return code %s record %s  "
162                          % (ret, record))
163             self.__add_person_to_db(record)
164         return ret
165
166     def __add_person_to_db(self, user_dict):
167         """
168         Add a federated user straight to db when the user issues a lease
169         request with iotlab nodes and that he has not registered with iotlab
170         yet (that is he does not have a LDAP entry yet).
171         Uses parts of the routines in IotlabImport when importing user from
172         LDAP. Called by AddPerson, right after LdapAddUser.
173         :param user_dict: Must contain email, hrn and pkey to get a GID
174         and be added to the SFA db.
175         :type user_dict: dict
176
177         """
178         query = self.api.dbsession().query(RegUser)
179         check_if_exists = query.filter_by(email = user_dict['email']).first()
180         logger.debug("LOIC __add_person_to_db %s" % check_if_exists)
181         #user doesn't exists
182         if not check_if_exists:
183             logger.debug("__add_person_to_db \t Adding %s \r\n \r\n \
184                                             " %(user_dict))
185             hrn = user_dict['hrn']
186             person_urn = hrn_to_urn(hrn, 'user')
187             try:
188                 pubkey = user_dict['pkey']
189                 pkey = convert_public_key(pubkey)
190             except TypeError:
191                 #key not good. create another pkey
192                 logger.warn('__add_person_to_db: no public key or unable to convert public \
193                                     key for %s' %(hrn ))
194                 pkey = Keypair(create=True)
195
196
197             if pubkey is not None and pkey is not None :
198                 hierarchy = Hierarchy()
199                 person_gid = hierarchy.create_gid(person_urn, create_uuid(), \
200                                 pkey)
201                 if user_dict['email']:
202                     logger.debug("__add_person_to_db \r\n \r\n \
203                         IOTLAB IMPORTER PERSON EMAIL OK email %s "\
204                         %(user_dict['email']))
205                     person_gid.set_email(user_dict['email'])
206
207             user_record = RegUser(hrn=hrn , pointer= '-1', \
208                                     authority=get_authority(hrn), \
209                                     email=user_dict['email'], gid = person_gid)
210             #user_record.reg_keys = [RegKey(user_dict['pkey'])]
211             user_record.just_created()
212             self.api.dbsession().add (user_record)
213             self.api.dbsession().commit()
214         return
215
216
217
218     def _sql_get_slice_info(self, slice_filter):
219         """
220         Get the slice record based on the slice hrn. Fetch the record of the
221         user associated with the slice by using joinedload based on the
222         reg_researchers relationship.
223
224         :param slice_filter: the slice hrn we are looking for
225         :type slice_filter: string
226         :returns: the slice record enhanced with the user's information if the
227             slice was found, None it wasn't.
228
229         :rtype: dict or None.
230         """
231         #DO NOT USE RegSlice - reg_researchers to get the hrn
232         #of the user otherwise will mess up the RegRecord in
233         #Resolve, don't know why - SA 08/08/2012
234
235         #Only one entry for one user  = one slice in testbed_xp table
236         #slicerec = dbsession.query(RegRecord).filter_by(hrn = slice_filter).first()
237
238         raw_slicerec = self.api.dbsession().query(RegSlice).options(joinedload('reg_researchers')).filter_by(hrn=slice_filter).first()
239         #raw_slicerec = self.api.dbsession().query(RegRecord).filter_by(hrn = slice_filter).first()
240         if raw_slicerec:
241             #load_reg_researchers
242             #raw_slicerec.reg_researchers
243             raw_slicerec = raw_slicerec.__dict__
244             logger.debug(" IOTLAB_API \t  _sql_get_slice_info slice_filter %s  \
245                             raw_slicerec %s" % (slice_filter, raw_slicerec))
246             slicerec = raw_slicerec
247             #only one researcher per slice so take the first one
248             #slicerec['reg_researchers'] = raw_slicerec['reg_researchers']
249             #del slicerec['reg_researchers']['_sa_instance_state']
250             return slicerec
251
252         else:
253             return None
254
255     def _sql_get_slice_info_from_user(self, slice_filter):
256         """
257         Get the slice record based on the user recordid by using a joinedload
258         on the relationship reg_slices_as_researcher. Format the sql record
259         into a dict with the mandatory fields for user and slice.
260         :returns: dict with slice record and user record if the record was found
261         based on the user's id, None if not..
262         :rtype:dict or None..
263         """
264         #slicerec = dbsession.query(RegRecord).filter_by(record_id = slice_filter).first()
265         raw_slicerec = self.api.dbsession().query(RegUser).options(joinedload('reg_slices_as_researcher')).filter_by(record_id=slice_filter).first()
266         #raw_slicerec = self.api.dbsession().query(RegRecord).filter_by(record_id = slice_filter).first()
267         #Put it in correct order
268         user_needed_fields = ['peer_authority', 'hrn', 'last_updated',
269                               'classtype', 'authority', 'gid', 'record_id',
270                               'date_created', 'type', 'email', 'pointer']
271         slice_needed_fields = ['peer_authority', 'hrn', 'last_updated',
272                                'classtype', 'authority', 'gid', 'record_id',
273                                'date_created', 'type', 'pointer']
274         if raw_slicerec:
275             #raw_slicerec.reg_slices_as_researcher
276             raw_slicerec = raw_slicerec.__dict__
277             slicerec = {}
278             slicerec = \
279                 dict([(k, raw_slicerec[
280                     'reg_slices_as_researcher'][0].__dict__[k])
281                     for k in slice_needed_fields])
282             slicerec['reg_researchers'] = dict([(k, raw_slicerec[k])
283                                                 for k in user_needed_fields])
284              #TODO Handle multiple slices for one user SA 10/12/12
285                         #for now only take the first slice record associated to the rec user
286                         ##slicerec  = raw_slicerec['reg_slices_as_researcher'][0].__dict__
287                         #del raw_slicerec['reg_slices_as_researcher']
288                         #slicerec['reg_researchers'] = raw_slicerec
289                         ##del slicerec['_sa_instance_state']
290
291             return slicerec
292
293         else:
294             return None
295
296
297
298     def _get_slice_records(self, slice_filter=None,
299                            slice_filter_type=None):
300         """
301         Get the slice record depending on the slice filter and its type.
302         :param slice_filter: Can be either the slice hrn or the user's record
303         id.
304         :type slice_filter: string
305         :param slice_filter_type: describes the slice filter type used, can be
306         slice_hrn or record_id_user
307         :type: string
308         :returns: the slice record
309         :rtype:dict
310         .. seealso::_sql_get_slice_info_from_user
311         .. seealso:: _sql_get_slice_info
312         """
313         logger.debug("JORDAN get_slice_records slice_filter=%r slice_filter_type=%r" % (slice_filter, slice_filter_type))
314
315         #Get list of slices based on the slice hrn
316         if slice_filter_type == 'slice_hrn':
317
318             #if get_authority(slice_filter) == self.root_auth:
319                 #login = slice_filter.split(".")[1].split("_")[0]
320
321             slicerec = self._sql_get_slice_info(slice_filter)
322
323             if slicerec is None:
324                 return None
325                 #return login, None
326
327         #Get slice based on user id
328         if slice_filter_type == 'record_id_user':
329
330             slicerec = self._sql_get_slice_info_from_user(slice_filter)
331
332         if slicerec:
333             fixed_slicerec_dict = slicerec
334             #At this point if there is no login it means
335             #record_id_user filter has been used for filtering
336             #if login is None :
337                 ##If theslice record is from iotlab
338                 #if fixed_slicerec_dict['peer_authority'] is None:
339                     #login = fixed_slicerec_dict['hrn'].split(".")[1].split("_")[0]
340             #return login, fixed_slicerec_dict
341             return fixed_slicerec_dict
342         else:
343             return None
344
345
346
347     def GetSlices(self, slice_filter=None, slice_filter_type=None,
348                   login=None):
349         """Get the slice records from the iotlab db and add lease information
350             if any.
351
352         :param slice_filter: can be the slice hrn or slice record id in the db
353             depending on the slice_filter_type.
354         :param slice_filter_type: defines the type of the filtering used, Can be
355             either 'slice_hrn' or "record_id'.
356         :type slice_filter: string
357         :type slice_filter_type: string
358         :returns: a slice dict if slice_filter  and slice_filter_type
359             are specified and a matching entry is found in the db. The result
360             is put into a list.Or a list of slice dictionnaries if no filters
361             arespecified.
362
363         :rtype: list
364
365         """
366         #login = None
367         authorized_filter_types_list = ['slice_hrn', 'record_id_user']
368         return_slicerec_dictlist = []
369
370         #First try to get information on the slice based on the filter provided
371         if slice_filter_type in authorized_filter_types_list:
372             logger.debug("JORDAN GET SLICES 1")
373             fixed_slicerec_dict = self._get_slice_records(slice_filter,
374                                                     slice_filter_type)
375             logger.debug("JORDAN GET SLICE RECORDS %r" % fixed_slicerec_dict)
376             # if the slice was not found in the sfa db
377             if fixed_slicerec_dict is None:
378                 return return_slicerec_dictlist
379
380             slice_hrn = fixed_slicerec_dict['hrn']
381
382             logger.debug(" IOTLAB_API \tGetSlices login %s \
383                             slice record %s slice_filter %s \
384                             slice_filter_type %s " % (login,
385                             fixed_slicerec_dict, slice_filter,
386                             slice_filter_type))
387
388
389             #Now we have the slice record fixed_slicerec_dict, get the
390             #jobs associated to this slice
391             leases_list = []
392
393             leases_list = self.GetLeases(login=login)
394             #If no job is running or no job scheduled
395             #return only the slice record
396             if leases_list == [] and fixed_slicerec_dict:
397                 logger.debug("JORDAN CASE 1")
398                 return_slicerec_dictlist.append(fixed_slicerec_dict)
399
400             # if the jobs running don't belong to the user/slice we are looking
401             # for
402             leases_hrn = [lease['slice_hrn'] for lease in leases_list]
403             if slice_hrn not in leases_hrn:
404                 logger.debug("JORDAN CASE 2")
405                 return_slicerec_dictlist.append(fixed_slicerec_dict)
406             #If several jobs for one slice , put the slice record into
407             # each lease information dict
408             for lease in leases_list:
409                 slicerec_dict = {}
410                 logger.debug("IOTLAB_API.PY  \tGetSlices slice_filter %s   \
411                         \t lease['slice_hrn'] %s"
412                              % (slice_filter, lease['slice_hrn']))
413                 if lease['slice_hrn'] == slice_hrn:
414                     slicerec_dict['oar_job_id'] = lease['lease_id']
415                     #Update lease dict with the slice record
416                     if fixed_slicerec_dict:
417                         fixed_slicerec_dict['oar_job_id'] = []
418                         fixed_slicerec_dict['oar_job_id'].append(
419                             slicerec_dict['oar_job_id'])
420                         slicerec_dict.update(fixed_slicerec_dict)
421                         #slicerec_dict.update({'hrn':\
422                                         #str(fixed_slicerec_dict['slice_hrn'])})
423                     slicerec_dict['slice_hrn'] = lease['slice_hrn']
424                     slicerec_dict['hrn'] = lease['slice_hrn']
425                     slicerec_dict['user'] = lease['user']
426                     slicerec_dict.update(
427                         {'list_node_ids':
428                         {'hostname': lease['reserved_nodes']}})
429                     slicerec_dict.update({'node_ids': lease['reserved_nodes']})
430
431
432
433                     return_slicerec_dictlist.append(slicerec_dict)
434
435                 logger.debug("IOTLAB_API.PY  \tGetSlices  \
436                         slicerec_dict %s return_slicerec_dictlist %s \
437                         lease['reserved_nodes'] \
438                         %s" % (slicerec_dict, return_slicerec_dictlist,
439                                lease['reserved_nodes']))
440
441             logger.debug("IOTLAB_API.PY  \tGetSlices  RETURN \
442                         return_slicerec_dictlist  %s"
443                           % (return_slicerec_dictlist))
444
445             return return_slicerec_dictlist
446
447
448         else:
449             logger.debug("JORDAN GET SLICES 2")
450             #Get all slices from the iotlab sfa database ,
451             #put them in dict format
452             #query_slice_list = dbsession.query(RegRecord).all()
453             query_slice_list = \
454                 self.api.dbsession().query(RegSlice).options(joinedload('reg_researchers')).all()
455
456             for record in query_slice_list:
457                 tmp = record.__dict__
458                 tmp['reg_researchers'] = tmp['reg_researchers'][0].__dict__
459                 #del tmp['reg_researchers']['_sa_instance_state']
460                 return_slicerec_dictlist.append(tmp)
461                 #return_slicerec_dictlist.append(record.__dict__)
462
463             #Get all the jobs reserved nodes
464             leases_list = self.testbed_shell.GetReservedNodes()
465
466             for fixed_slicerec_dict in return_slicerec_dictlist:
467                 slicerec_dict = {}
468                 #Check if the slice belongs to a iotlab user
469                 if fixed_slicerec_dict['peer_authority'] is None:
470                     owner = fixed_slicerec_dict['hrn'].split(
471                         ".")[1].split("_")[0]
472                 else:
473                     owner = None
474                 for lease in leases_list:
475                     if owner == lease['user']:
476                         slicerec_dict['oar_job_id'] = lease['lease_id']
477
478                         #for reserved_node in lease['reserved_nodes']:
479                         logger.debug("IOTLAB_API.PY  \tGetSlices lease %s "
480                                      % (lease))
481                         slicerec_dict.update(fixed_slicerec_dict)
482                         slicerec_dict.update({'node_ids':
483                                               lease['reserved_nodes']})
484                         slicerec_dict.update({'list_node_ids':
485                                              {'hostname':
486                                              lease['reserved_nodes']}})
487
488                         #slicerec_dict.update({'hrn':\
489                                     #str(fixed_slicerec_dict['slice_hrn'])})
490                         #return_slicerec_dictlist.append(slicerec_dict)
491                         fixed_slicerec_dict.update(slicerec_dict)
492
493             logger.debug("IOTLAB_API.PY  \tGetSlices RETURN \
494                         return_slicerec_dictlist %s \t slice_filter %s " \
495                         %(return_slicerec_dictlist, slice_filter))
496
497         return return_slicerec_dictlist
498
499     def AddLeases(self, hostname_list, slice_record,
500                   lease_start_time, lease_duration):
501
502         """Creates a job in OAR corresponding to the information provided
503         as parameters. Adds the job id and the slice hrn in the iotlab
504         database so that we are able to know which slice has which nodes.
505
506         :param hostname_list: list of nodes' OAR hostnames.
507         :param slice_record: sfa slice record, must contain login and hrn.
508         :param lease_start_time: starting time , unix timestamp format
509         :param lease_duration: duration in minutes
510
511         :type hostname_list: list
512         :type slice_record: dict
513         :type lease_start_time: integer
514         :type lease_duration: integer
515         :returns: job_id, can be None if the job request failed.
516
517         """
518         logger.debug("IOTLAB_API \r\n \r\n \t AddLeases hostname_list %s  \
519                 slice_record %s lease_start_time %s lease_duration %s  "\
520                  %( hostname_list, slice_record , lease_start_time, \
521                  lease_duration))
522
523         #tmp = slice_record['reg-researchers'][0].split(".")
524         username = slice_record['login']
525         #username = tmp[(len(tmp)-1)]
526         job_id = self.testbed_shell.LaunchExperimentOnOAR(hostname_list, \
527                                     slice_record['hrn'], \
528                                     lease_start_time, lease_duration, \
529                                     username)
530         if job_id is not None:
531             start_time = \
532                     datetime.fromtimestamp(int(lease_start_time)).\
533                     strftime(self.testbed_shell.time_format)
534             end_time = lease_start_time + lease_duration
535
536
537             logger.debug("IOTLAB_API \r\n \r\n \t AddLeases TURN ON LOGGING SQL \
538                         %s %s %s "%(slice_record['hrn'], job_id, end_time))
539
540
541             logger.debug("IOTLAB_API \r\n \r\n \t AddLeases %s %s %s " \
542                     %(type(slice_record['hrn']), type(job_id), type(end_time)))
543
544             iotlab_ex_row = LeaseTableXP(slice_hrn = slice_record['hrn'],
545                                                     experiment_id=job_id,
546                                                     end_time= end_time)
547
548             logger.debug("IOTLAB_API \r\n \r\n \t AddLeases iotlab_ex_row %s" \
549                     %(iotlab_ex_row))
550             self.api.dbsession().add(iotlab_ex_row)
551             self.api.dbsession().commit()
552
553             logger.debug("IOTLAB_API \t AddLeases hostname_list start_time %s "
554                         %(start_time))
555
556         return job_id
557
558     def GetLeases(self, lease_filter_dict=None, login=None):
559         """
560
561         Get the list of leases from OAR with complete information
562             about which slice owns which jobs and nodes.
563             Two purposes:
564             -Fetch all the jobs from OAR (running, waiting..)
565             complete the reservation information with slice hrn
566             found in lease_table . If not available in the table,
567             assume it is a iotlab slice.
568             -Updates the iotlab table, deleting jobs when necessary.
569
570         :returns: reservation_list, list of dictionaries with 'lease_id',
571             'reserved_nodes','slice_id', 'state', 'user', 'component_id_list',
572             'slice_hrn', 'resource_ids', 't_from', 't_until'
573         :rtype: list
574
575         """
576
577         unfiltered_reservation_list = self.testbed_shell.GetReservedNodes(login)
578
579         reservation_list = []
580         #Find the slice associated with this user iotlab ldap uid
581         logger.debug(" IOTLAB_API.PY \tGetLeases login %s\
582                         unfiltered_reservation_list %s "
583                      % (login, unfiltered_reservation_list))
584         #Create user dict first to avoid looking several times for
585         #the same user in LDAP SA 27/07/12
586         job_oar_list = []
587         jobs_psql_query = self.api.dbsession().query(LeaseTableXP).all()
588         jobs_psql_dict = dict([(row.experiment_id, row.__dict__)
589                                for row in jobs_psql_query])
590         #jobs_psql_dict = jobs_psql_dict)
591         logger.debug("IOTLAB_API \tGetLeases jobs_psql_dict %s"
592                      % (jobs_psql_dict))
593         jobs_psql_id_list = [row.experiment_id for row in jobs_psql_query]
594
595         for resa in unfiltered_reservation_list:
596             logger.debug("IOTLAB_API \tGetLeases USER %s"
597                          % (resa['user']))
598             #Construct list of jobs (runing, waiting..) in oar
599             job_oar_list.append(resa['lease_id'])
600             #If there is information on the job in IOTLAB DB ]
601             #(slice used and job id)
602             if resa['lease_id'] in jobs_psql_dict:
603                 job_info = jobs_psql_dict[resa['lease_id']]
604                 logger.debug("IOTLAB_API \tGetLeases job_info %s"
605                           % (job_info))
606                 resa['slice_hrn'] = job_info['slice_hrn']
607                 resa['slice_id'] = hrn_to_urn(resa['slice_hrn'], 'slice')
608
609             #otherwise, assume it is a iotlab slice:
610             else:
611                 resa['slice_id'] = hrn_to_urn(self.testbed_shell.root_auth \
612                                             + '.' + resa['user'] + "_slice",
613                                             'slice')
614                 resa['slice_hrn'] = Xrn(resa['slice_id']).get_hrn()
615
616             resa['component_id_list'] = []
617             #Transform the hostnames into urns (component ids)
618             for node in resa['reserved_nodes']:
619
620                 iotlab_xrn = xrn_object(self.testbed_shell.root_auth, node)
621                 resa['component_id_list'].append(iotlab_xrn.urn)
622
623         if lease_filter_dict:
624             logger.debug("IOTLAB_API \tGetLeases  \
625                     \r\n leasefilter %s" % ( lease_filter_dict))
626
627             # filter_dict_functions = {
628             # 'slice_hrn' : IotlabShell.filter_lease_name,
629             # 't_from' : IotlabShell.filter_lease_start_time
630             # }
631             reservation_list = list(unfiltered_reservation_list)
632             for filter_type in lease_filter_dict:
633                 logger.debug("IOTLAB_API \tGetLeases reservation_list %s" \
634                     % (reservation_list))
635                 reservation_list = self.testbed_shell.filter_lease(
636                         reservation_list,filter_type,
637                         lease_filter_dict[filter_type] )
638
639                 # Filter the reservation list with a maximum timespan so that the
640                 # leases and jobs running after this timestamp do not appear
641                 # in the result leases.
642                 # if 'start_time' in :
643                 #     if resa['start_time'] < lease_filter_dict['start_time']:
644                 #        reservation_list.append(resa)
645
646
647                 # if 'name' in lease_filter_dict and \
648                 #     lease_filter_dict['name'] == resa['slice_hrn']:
649                 #     reservation_list.append(resa)
650
651
652         if lease_filter_dict is None:
653             reservation_list = unfiltered_reservation_list
654
655         self.update_experiments_in_lease_table(job_oar_list, jobs_psql_id_list)
656
657         logger.debug(" IOTLAB_API.PY \tGetLeases reservation_list %s"
658                      % (reservation_list))
659         return reservation_list
660
661
662
663     def update_experiments_in_lease_table(self,
664         experiment_list_from_testbed, experiment_list_in_db):
665         """ Cleans the lease_table by deleting expired and cancelled jobs.
666
667         Compares the list of experiment ids given by the testbed with the
668         experiment ids that are already in the database, deletes the
669         experiments that are no longer in the testbed experiment id list.
670
671         :param  experiment_list_from_testbed: list of experiment ids coming
672             from testbed
673         :type experiment_list_from_testbed: list
674         :param experiment_list_in_db: list of experiment ids from the sfa
675             additionnal database.
676         :type experiment_list_in_db: list
677
678         :returns: None
679         """
680         #Turn the list into a set
681         set_experiment_list_in_db = set(experiment_list_in_db)
682
683         kept_experiments = set(experiment_list_from_testbed).intersection(set_experiment_list_in_db)
684         logger.debug("\r\n \t update_experiments_in_lease_table \
685                         experiment_list_in_db %s \r\n \
686                         experiment_list_from_testbed %s \
687                         kept_experiments %s "
688                      % (set_experiment_list_in_db,
689                       experiment_list_from_testbed, kept_experiments))
690         deleted_experiments = set_experiment_list_in_db.difference(
691             kept_experiments)
692         deleted_experiments = list(deleted_experiments)
693         if len(deleted_experiments) > 0:
694             request = self.api.dbsession().query(LeaseTableXP)
695             request.filter(LeaseTableXP.experiment_id.in_(deleted_experiments)).delete(synchronize_session='fetch')
696             self.api.dbsession().commit()
697         return
698
699
700     def AddSlice(self, slice_record, user_record):
701         """
702
703         Add slice to the local iotlab sfa tables if the slice comes
704             from a federated site and is not yet in the iotlab sfa DB,
705             although the user has already a LDAP login.
706             Called by verify_slice during lease/sliver creation.
707
708         :param slice_record: record of slice, must contain hrn, gid, slice_id
709             and authority of the slice.
710         :type slice_record: dictionary
711         :param user_record: record of the user
712         :type user_record: RegUser
713
714         """
715
716         sfa_record = RegSlice(hrn=slice_record['hrn'],
717                               gid=slice_record['gid'],
718                               #pointer=slice_record['slice_id'],
719                               authority=slice_record['authority'])
720         logger.debug("IOTLAB_API.PY AddSlice  sfa_record %s user_record %s"
721                      % (sfa_record, user_record))
722         sfa_record.just_created()
723         self.api.dbsession().add(sfa_record)
724         self.api.dbsession().commit()
725         #Update the reg-researchers dependency table
726         if user_record is not None:
727             sfa_record.reg_researchers = [user_record]
728         else:
729             sfa_record.reg_researchers = slice_record['reg-researchers']
730         self.api.dbsession().commit()
731
732         return
733
734     def augment_records_with_testbed_info(self, record_list):
735         """
736
737         Adds specific testbed info to the records.
738
739         :param record_list: list of sfa dictionaries records
740         :type record_list: list
741         :returns: list of records with extended information in each record
742         :rtype: list
743
744         """
745         return self.fill_record_info(record_list)
746
747     def fill_record_info(self, record_list):
748         """
749
750         For each SFA record, fill in the iotlab specific and SFA specific
751             fields in the record.
752
753         :param record_list: list of sfa dictionaries records
754         :type record_list: list
755         :returns: list of records with extended information in each record
756         :rtype: list
757
758         .. warning:: Should not be modifying record_list directly because modi
759             fication are kept outside the method's scope. Howerver, there is no
760             other way to do it given the way it's called in registry manager.
761
762         """
763
764         logger.debug("IOTLABDRIVER \tfill_record_info records %s "
765                      % (record_list))
766         if not isinstance(record_list, list):
767             record_list = [record_list]
768
769         try:
770             for record in record_list:
771
772                 if str(record['type']) == 'node':
773                     # look for node info using GetNodes
774                     # the record is about one node only
775                     filter_dict = {'hrn': [record['hrn']]}
776                     node_info = self.testbed_shell.GetNodes(filter_dict)
777                     # the node_info is about one node only, but it is formatted
778                     # as a list
779                     record.update(node_info[0])
780                     logger.debug("IOTLABDRIVER.PY \t \
781                                   fill_record_info NODE" % (record))
782
783                 #If the record is a SFA slice record, then add information
784                 #about the user of this slice. This kind of
785                 #information is in the Iotlab's DB.
786                 if str(record['type']) == 'slice':
787                     if 'reg_researchers' in record and isinstance(record
788                                                             ['reg_researchers'],
789                                                             list):
790                         record['reg_researchers'] = \
791                             record['reg_researchers'][0].__dict__
792                         record.update(
793                             {'PI': [record['reg_researchers']['hrn']],
794                              'researcher': [record['reg_researchers']['hrn']],
795                              'name': record['hrn'],
796                              'oar_job_id': [],
797                              'node_ids': [],
798                              'person_ids': [record['reg_researchers']
799                                             ['record_id']],
800                                 # For client_helper.py compatibility
801                              'geni_urn': '',
802                                 # For client_helper.py compatibility
803                              'keys': '',
804                                 # For client_helper.py compatibility
805                              'key_ids': ''})
806
807                     #Get iotlab slice record and oar job id if any.
808                     recslice_list = self.GetSlices(
809                         slice_filter=str(record['hrn']),
810                         slice_filter_type='slice_hrn')
811
812                     logger.debug("IOTLABDRIVER \tfill_record_info \
813                         TYPE SLICE RECUSER record['hrn'] %s record['oar_job_id']\
814                          %s " % (record['hrn'], record['oar_job_id']))
815                     del record['reg_researchers']
816                     try:
817                         for rec in recslice_list:
818                             logger.debug("IOTLABDRIVER\r\n  \t  \
819                             fill_record_info oar_job_id %s "
820                                          % (rec['oar_job_id']))
821
822                             record['node_ids'] = [self.testbed_shell.root_auth +
823                                                   '.' + hostname for hostname
824                                                   in rec['node_ids']]
825                     except KeyError:
826                         pass
827
828                     logger.debug("IOTLABDRIVER.PY \t fill_record_info SLICE \
829                                     recslice_list  %s \r\n \t RECORD %s \r\n \
830                                     \r\n" % (recslice_list, record))
831
832                 if str(record['type']) == 'user':
833                     #The record is a SFA user record.
834                     #Get the information about his slice from Iotlab's DB
835                     #and add it to the user record.
836                     recslice_list = self.GetSlices(
837                                     slice_filter=record['record_id'],
838                                     slice_filter_type='record_id_user')
839
840                     logger.debug("IOTLABDRIVER.PY \t fill_record_info \
841                         TYPE USER recslice_list %s \r\n \t RECORD %s \r\n"
842                                  % (recslice_list, record))
843                     #Append slice record in records list,
844                     #therefore fetches user and slice info again(one more loop)
845                     #Will update PIs and researcher for the slice
846
847                     recuser = recslice_list[0]['reg_researchers']
848                     logger.debug("IOTLABDRIVER.PY \t fill_record_info USER  \
849                                             recuser %s \r\n \r\n" % (recuser))
850                     recslice = {}
851                     recslice = recslice_list[0]
852                     recslice.update(
853                         {'PI': [recuser['hrn']],
854                          'researcher': [recuser['hrn']],
855                          'name': recuser['hrn'],
856                          'node_ids': [],
857                          'oar_job_id': [],
858                          'person_ids': [recuser['record_id']]})
859                     try:
860                         for rec in recslice_list:
861                             recslice['oar_job_id'].append(rec['oar_job_id'])
862                     except KeyError:
863                         pass
864
865                     recslice.update({'type': 'slice',
866                                      'hrn': recslice_list[0]['hrn']})
867
868                     #GetPersons takes [] as filters
869                     user_iotlab = self.testbed_shell.GetPersons([record])
870
871                     record.update(user_iotlab[0])
872                     #For client_helper.py compatibility
873                     record.update(
874                         {'geni_urn': '',
875                          'keys': '',
876                          'key_ids': ''})
877                     record_list.append(recslice)
878
879                     logger.debug("IOTLABDRIVER.PY \t \
880                         fill_record_info ADDING SLICE\
881                         INFO TO USER records %s" % (record_list))
882
883         except TypeError, error:
884             logger.log_exc("IOTLABDRIVER \t fill_record_info  EXCEPTION %s"
885                            % (error))
886
887         return record_list
888
889     def sliver_status(self, slice_urn, slice_hrn):
890         """
891         Receive a status request for slice named urn/hrn
892             urn:publicid:IDN+iotlab+nturro_slice hrn iotlab.nturro_slice
893             shall return a structure as described in
894             http://groups.geni.net/geni/wiki/GAPI_AM_API_V2#SliverStatus
895             NT : not sure if we should implement this or not, but used by sface.
896
897         :param slice_urn: slice urn
898         :type slice_urn: string
899         :param slice_hrn: slice hrn
900         :type slice_hrn: string
901
902         """
903
904         #First get the slice with the slice hrn
905         slice_list = self.GetSlices(slice_filter=slice_hrn,
906                                     slice_filter_type='slice_hrn')
907
908         if len(slice_list) == 0:
909             raise SliverDoesNotExist("%s  slice_hrn" % (slice_hrn))
910
911         #Used for fetching the user info witch comes along the slice info
912         one_slice = slice_list[0]
913
914         #Make a list of all the nodes hostnames  in use for this slice
915         slice_nodes_list = []
916         slice_nodes_list = one_slice['node_ids']
917         #Get all the corresponding nodes details
918         nodes_all = self.testbed_shell.GetNodes(
919             {'hostname': slice_nodes_list},
920             ['node_id', 'hostname', 'site', 'boot_state'])
921         nodeall_byhostname = dict([(one_node['hostname'], one_node)
922                                   for one_node in nodes_all])
923
924         for single_slice in slice_list:
925               #For compatibility
926             top_level_status = 'empty'
927             result = {}
928             result.fromkeys(
929                 ['geni_urn', 'geni_error', 'iotlab_login', 'geni_status',
930                  'geni_resources'], None)
931             # result.fromkeys(\
932             #     ['geni_urn','geni_error', 'pl_login','geni_status',
933             # 'geni_resources'], None)
934             # result['pl_login'] = one_slice['reg_researchers'][0].hrn
935             result['iotlab_login'] = one_slice['user']
936             logger.debug("Slabdriver - sliver_status Sliver status \
937                             urn %s hrn %s single_slice  %s \r\n "
938                          % (slice_urn, slice_hrn, single_slice))
939
940             if 'node_ids' not in single_slice:
941                 #No job in the slice
942                 result['geni_status'] = top_level_status
943                 result['geni_resources'] = []
944                 return result
945
946             top_level_status = 'ready'
947
948             #A job is running on Iotlab for this slice
949             # report about the local nodes that are in the slice only
950
951             result['geni_urn'] = slice_urn
952
953             resources = []
954             for node_hostname in single_slice['node_ids']:
955                 res = {}
956                 res['iotlab_hostname'] = node_hostname
957                 res['iotlab_boot_state'] = \
958                     nodeall_byhostname[node_hostname]['boot_state']
959
960                 #res['pl_hostname'] = node['hostname']
961                 #res['pl_boot_state'] = \
962                             #nodeall_byhostname[node['hostname']]['boot_state']
963                 #res['pl_last_contact'] = strftime(self.time_format, \
964                                                     #gmtime(float(timestamp)))
965                 sliver_id = Xrn(
966                     slice_urn, type='slice',
967                     id=nodeall_byhostname[node_hostname]['node_id']).urn
968
969                 res['geni_urn'] = sliver_id
970                 #node_name  = node['hostname']
971                 if nodeall_byhostname[node_hostname]['boot_state'] == 'Alive':
972
973                     res['geni_status'] = 'ready'
974                 else:
975                     res['geni_status'] = 'failed'
976                     top_level_status = 'failed'
977
978                 res['geni_error'] = ''
979
980                 resources.append(res)
981
982             result['geni_status'] = top_level_status
983             result['geni_resources'] = resources
984             logger.debug("IOTLABDRIVER \tsliver_statusresources %s res %s "
985                          % (resources, res))
986             return result
987
988     def get_user_record(self, hrn):
989         """
990
991         Returns the user record based on the hrn from the SFA DB .
992
993         :param hrn: user's hrn
994         :type hrn: string
995         :returns: user record from SFA database
996         :rtype: RegUser
997
998         """
999         return self.api.dbsession().query(RegRecord).filter_by(hrn=hrn).first()
1000
1001     def testbed_name(self):
1002         """
1003
1004         Returns testbed's name.
1005         :returns: testbed authority name.
1006         :rtype: string
1007
1008         """
1009         return self.hrn
1010
1011
1012     def _get_requested_leases_list(self, rspec):
1013         """
1014         Process leases in rspec depending on the rspec version (format)
1015             type. Find the lease requests in the rspec and creates
1016             a lease request list with the mandatory information ( nodes,
1017             start time and duration) of the valid leases (duration above or
1018             equal to the iotlab experiment minimum duration).
1019
1020         :param rspec: rspec request received.
1021         :type rspec: RSpec
1022         :returns: list of lease requests found in the rspec
1023         :rtype: list
1024         """
1025         requested_lease_list = []
1026         for lease in rspec.version.get_leases():
1027             single_requested_lease = {}
1028             logger.debug("IOTLABDRIVER.PY \t \
1029                 _get_requested_leases_list lease %s " % (lease))
1030
1031             if not lease.get('lease_id'):
1032                 if get_authority(lease['component_id']) == \
1033                         self.testbed_shell.root_auth:
1034                     single_requested_lease['hostname'] = \
1035                         xrn_to_hostname(\
1036                             lease.get('component_id').strip())
1037                     single_requested_lease['start_time'] = \
1038                         lease.get('start_time')
1039                     single_requested_lease['duration'] = lease.get('duration')
1040                     #Check the experiment's duration is valid before adding
1041                     #the lease to the requested leases list
1042                     duration_in_seconds = \
1043                         int(single_requested_lease['duration'])
1044                     if duration_in_seconds >= self.testbed_shell.GetMinExperimentDurationInGranularity():
1045                         requested_lease_list.append(single_requested_lease)
1046
1047         return requested_lease_list
1048
1049     @staticmethod
1050     def _group_leases_by_start_time(requested_lease_list):
1051         """
1052         Create dict of leases by start_time, regrouping nodes reserved
1053             at the same time, for the same amount of time so as to
1054             define one job on OAR.
1055
1056         :param requested_lease_list: list of leases
1057         :type requested_lease_list: list
1058         :returns: Dictionary with key = start time, value = list of leases
1059             with the same start time.
1060         :rtype: dictionary
1061
1062         """
1063
1064         requested_xp_dict = {}
1065         for lease in requested_lease_list:
1066
1067             #In case it is an asap experiment start_time is empty
1068             if lease['start_time'] == '':
1069                 lease['start_time'] = '0'
1070
1071             if lease['start_time'] not in requested_xp_dict:
1072                 if isinstance(lease['hostname'], str):
1073                     lease['hostname'] = [lease['hostname']]
1074
1075                 requested_xp_dict[lease['start_time']] = lease
1076
1077             else:
1078                 job_lease = requested_xp_dict[lease['start_time']]
1079                 if lease['duration'] == job_lease['duration']:
1080                     job_lease['hostname'].append(lease['hostname'])
1081
1082         return requested_xp_dict
1083
1084     def _process_requested_xp_dict(self, rspec):
1085         """
1086         Turns the requested leases and information into a dictionary
1087             of requested jobs, grouped by starting time.
1088
1089         :param rspec: RSpec received
1090         :type rspec : RSpec
1091         :rtype: dictionary
1092
1093         """
1094         requested_lease_list = self._get_requested_leases_list(rspec)
1095         logger.debug("IOTLABDRIVER _process_requested_xp_dict \
1096             requested_lease_list  %s" % (requested_lease_list))
1097         xp_dict = self._group_leases_by_start_time(requested_lease_list)
1098         logger.debug("IOTLABDRIVER _process_requested_xp_dict  xp_dict\
1099         %s" % (xp_dict))
1100
1101         return xp_dict
1102
1103
1104
1105     def delete(self, slice_urns, options=None):
1106         """
1107         Deletes the lease associated with the slice hrn and the credentials
1108             if the slice belongs to iotlab. Answer to DeleteSliver.
1109
1110         :param slice_urn: urn of the slice
1111         :type slice_urn: string
1112
1113
1114         :returns: 1 if the slice to delete was not found on iotlab,
1115             True if the deletion was successful, False otherwise otherwise.
1116
1117         .. note:: Should really be named delete_leases because iotlab does
1118             not have any slivers, but only deals with leases. However,
1119             SFA api only have delete_sliver define so far. SA 13/05/2013
1120         .. note:: creds are unused, and are not used either in the dummy driver
1121              delete_sliver .
1122         """
1123         if options is None: options={}
1124         # collect sliver ids so we can update sliver allocation states after
1125         # we remove the slivers.
1126         aggregate = IotlabAggregate(self)
1127         slivers = aggregate.get_slivers(slice_urns)
1128         if slivers:
1129             # slice_id = slivers[0]['slice_id']
1130             node_ids = []
1131             sliver_ids = []
1132             sliver_jobs_dict = {}
1133             for sliver in slivers:
1134                 node_ids.append(sliver['node_id'])
1135                 sliver_ids.append(sliver['sliver_id'])
1136                 job_id = sliver['sliver_id'].split('+')[-1].split('-')[0]
1137                 sliver_jobs_dict[job_id] = sliver['sliver_id']
1138         logger.debug("IOTLABDRIVER.PY delete_sliver slivers %s slice_urns %s"
1139             % (slivers, slice_urns))
1140         slice_hrn = urn_to_hrn(slice_urns[0])[0]
1141
1142         sfa_slice_list = self.GetSlices(slice_filter=slice_hrn,
1143                                         slice_filter_type='slice_hrn')
1144
1145         if not sfa_slice_list:
1146             return 1
1147
1148         #Delete all leases in the slice
1149         for sfa_slice in sfa_slice_list:
1150             logger.debug("IOTLABDRIVER.PY delete_sliver slice %s" % (sfa_slice))
1151             slices = IotlabSlices(self)
1152             # determine if this is a peer slice
1153
1154             peer = slices.get_peer(slice_hrn)
1155
1156             logger.debug("IOTLABDRIVER.PY delete_sliver peer %s \
1157                 \r\n \t sfa_slice %s " % (peer, sfa_slice))
1158             oar_bool_ans = self.testbed_shell.DeleteSliceFromNodes(
1159                                                                     sfa_slice)
1160             for job_id in oar_bool_ans:
1161                 # if the job has not been successfully deleted
1162                 # don't delete the associated sliver
1163                 # remove it from the sliver list
1164                 if oar_bool_ans[job_id] is False:
1165                     sliver = sliver_jobs_dict[job_id]
1166                     sliver_ids.remove(sliver)
1167             try:
1168
1169                 dbsession = self.api.dbsession()
1170                 SliverAllocation.delete_allocations(sliver_ids, dbsession)
1171             except :
1172                 logger.log_exc("IOTLABDRIVER.PY delete error ")
1173
1174         # prepare return struct
1175         geni_slivers = []
1176         for sliver in slivers:
1177             geni_slivers.append(
1178                 {'geni_sliver_urn': sliver['sliver_id'],
1179                  'geni_allocation_status': 'geni_unallocated',
1180                  'geni_expires': datetime_to_string(utcparse(sliver['expires']))})
1181         return geni_slivers
1182
1183
1184
1185
1186     def list_slices(self, creds, options):
1187         """Answer to ListSlices.
1188
1189         List slices belonging to iotlab, returns slice urns list.
1190             No caching used. Options unused but are defined in the SFA method
1191             api prototype.
1192
1193         :returns: slice urns list
1194         :rtype: list
1195
1196         .. note:: creds and options are unused - SA 12/12/13
1197         """
1198         # look in cache first
1199         #if self.cache:
1200             #slices = self.cache.get('slices')
1201             #if slices:
1202                 #logger.debug("PlDriver.list_slices returns from cache")
1203                 #return slices
1204
1205         # get data from db
1206
1207         slices = self.GetSlices()
1208         logger.debug("IOTLABDRIVER.PY \tlist_slices hrn %s \r\n \r\n"
1209                      % (slices))
1210         slice_hrns = [iotlab_slice['hrn'] for iotlab_slice in slices]
1211
1212         slice_urns = [hrn_to_urn(slice_hrn, 'slice')
1213                       for slice_hrn in slice_hrns]
1214
1215         # cache the result
1216         #if self.cache:
1217             #logger.debug ("IotlabDriver.list_slices stores value in cache")
1218             #self.cache.add('slices', slice_urns)
1219
1220         return slice_urns
1221
1222
1223     def register(self, sfa_record, hrn, pub_key):
1224         """
1225         Adding new user, slice, node or site should not be handled
1226             by SFA.
1227
1228         ..warnings:: should not be used. Different components are in charge of
1229             doing this task. Adding nodes = OAR
1230             Adding users = LDAP Iotlab
1231             Adding slice = Import from LDAP users
1232             Adding site = OAR
1233
1234         :param sfa_record: record provided by the client of the
1235             Register API call.
1236         :type sfa_record: dict
1237         :param pub_key: public key of the user
1238         :type pub_key: string
1239
1240         .. note:: DOES NOTHING. Returns -1.
1241
1242         """
1243         return -1
1244
1245
1246     def update(self, old_sfa_record, new_sfa_record, hrn, new_key):
1247         """
1248         No site or node record update allowed in Iotlab. The only modifications
1249         authorized here are key deletion/addition on an existing user and
1250         password change. On an existing user, CAN NOT BE MODIFIED: 'first_name',
1251         'last_name', 'email'. DOES NOT EXIST IN SENSLAB: 'phone', 'url', 'bio',
1252         'title', 'accepted_aup'. A slice is bound to its user, so modifying the
1253         user's ssh key should nmodify the slice's GID after an import procedure.
1254
1255         :param old_sfa_record: what is in the db for this hrn
1256         :param new_sfa_record: what was passed to the update call
1257         :param new_key: the new user's public key
1258         :param hrn: the user's sfa hrn
1259         :type old_sfa_record: dict
1260         :type new_sfa_record: dict
1261         :type new_key: string
1262         :type hrn: string
1263
1264         TODO: needs review
1265         .. warning:: SA 12/12/13 - Removed. should be done in iotlabimporter
1266         since users, keys and slice are managed by the LDAP.
1267
1268         """
1269         # pointer = old_sfa_record['pointer']
1270         # old_sfa_record_type = old_sfa_record['type']
1271
1272         # # new_key implemented for users only
1273         # if new_key and old_sfa_record_type not in ['user']:
1274         #     raise UnknownSfaType(old_sfa_record_type)
1275
1276         # if old_sfa_record_type == "user":
1277         #     update_fields = {}
1278         #     all_fields = new_sfa_record
1279         #     for key in all_fields.keys():
1280         #         if key in ['key', 'password']:
1281         #             update_fields[key] = all_fields[key]
1282
1283         #     if new_key:
1284         #         # must check this key against the previous one if it exists
1285         #         persons = self.testbed_shell.GetPersons([old_sfa_record])
1286         #         person = persons[0]
1287         #         keys = [person['pkey']]
1288         #         #Get all the person's keys
1289         #         keys_dict = self.GetKeys(keys)
1290
1291         #         # Delete all stale keys, meaning the user has only one key
1292         #         #at a time
1293         #         #TODO: do we really want to delete all the other keys?
1294         #         #Is this a problem with the GID generation to have multiple
1295         #         #keys? SA 30/05/13
1296         #         key_exists = False
1297         #         if key in keys_dict:
1298         #             key_exists = True
1299         #         else:
1300         #             #remove all the other keys
1301         #             for key in keys_dict:
1302         #                 self.testbed_shell.DeleteKey(person, key)
1303         #             self.testbed_shell.AddPersonKey(
1304         #                 person, {'sshPublicKey': person['pkey']},
1305         #                 {'sshPublicKey': new_key})
1306         logger.warning ("UNDEFINED - Update should be done by the \
1307             iotlabimporter")
1308         return True
1309
1310     def remove(self, sfa_record):
1311         """
1312
1313         Removes users only. Mark the user as disabled in LDAP. The user and his
1314         slice are then deleted from the db by running an import on the registry.
1315
1316         :param sfa_record: record is the existing sfa record in the db
1317         :type sfa_record: dict
1318
1319         ..warning::As fas as the slice is concerned, here only the leases are
1320             removed from the slice. The slice is record itself is not removed
1321             from the db.
1322
1323         TODO: needs review
1324
1325         TODO : REMOVE SLICE FROM THE DB AS WELL? SA 14/05/2013,
1326
1327         TODO: return boolean for the slice part
1328         """
1329         sfa_record_type = sfa_record['type']
1330         hrn = sfa_record['hrn']
1331         if sfa_record_type == 'user':
1332
1333             #get user from iotlab ldap
1334             person = self.testbed_shell.GetPersons(sfa_record)
1335             #No registering at a given site in Iotlab.
1336             #Once registered to the LDAP, all iotlab sites are
1337             #accesible.
1338             if person:
1339                 #Mark account as disabled in ldap
1340                 return self.testbed_shell.DeletePerson(sfa_record)
1341
1342         elif sfa_record_type == 'slice':
1343             if self.GetSlices(slice_filter=hrn,
1344                                 slice_filter_type='slice_hrn'):
1345                 ret = self.testbed_shell.DeleteSlice(sfa_record)
1346             return True
1347
1348     def check_sliver_credentials(self, creds, urns):
1349         """Check that the sliver urns belongs to the slice specified in the
1350         credentials.
1351
1352         :param urns: list of sliver urns.
1353         :type urns: list.
1354         :param creds: slice credentials.
1355         :type creds: Credential object.
1356
1357
1358         """
1359         # build list of cred object hrns
1360         slice_cred_names = []
1361         for cred in creds:
1362             slice_cred_hrn = Credential(cred=cred).get_gid_object().get_hrn()
1363             slicename = IotlabXrn(xrn=slice_cred_hrn).iotlab_slicename()
1364             slice_cred_names.append(slicename)
1365
1366         # look up slice name of slivers listed in urns arg
1367
1368         slice_ids = []
1369         for urn in urns:
1370             sliver_id_parts = Xrn(xrn=urn).get_sliver_id_parts()
1371             try:
1372                 slice_ids.append(int(sliver_id_parts[0]))
1373             except ValueError:
1374                 pass
1375
1376         if not slice_ids:
1377             raise Forbidden("sliver urn not provided")
1378
1379         slices = self.GetSlices(slice_ids)
1380         sliver_names = [single_slice['name'] for single_slice in slices]
1381
1382         # make sure we have a credential for every specified sliver
1383         for sliver_name in sliver_names:
1384             if sliver_name not in slice_cred_names:
1385                 msg = "Valid credential not found for target: %s" % sliver_name
1386                 raise Forbidden(msg)
1387
1388     ########################################
1389     ########## aggregate oriented
1390     ########################################
1391
1392     # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
1393     def aggregate_version(self):
1394         """
1395
1396         Returns the testbed's supported rspec advertisement and request
1397         versions.
1398         :returns: rspec versions supported ad a dictionary.
1399         :rtype: dict
1400
1401         """
1402         version_manager = VersionManager()
1403         ad_rspec_versions = []
1404         request_rspec_versions = []
1405         for rspec_version in version_manager.versions:
1406             if rspec_version.content_type in ['*', 'ad']:
1407                 ad_rspec_versions.append(rspec_version.to_dict())
1408             if rspec_version.content_type in ['*', 'request']:
1409                 request_rspec_versions.append(rspec_version.to_dict())
1410         return {
1411             'testbed': self.testbed_name(),
1412             'geni_request_rspec_versions': request_rspec_versions,
1413             'geni_ad_rspec_versions': ad_rspec_versions}
1414
1415     # first 2 args are None in case of resource discovery
1416     def list_resources (self, version=None, options=None):
1417         if options is None: options={}
1418         aggregate = IotlabAggregate(self)
1419         rspec =  aggregate.list_resources(version=version, options=options)
1420         return rspec
1421
1422     def describe(self, urns, version, options=None):
1423         if options is None: options={}
1424         aggregate = IotlabAggregate(self)
1425         return aggregate.describe(urns, version=version, options=options)
1426
1427     def status (self, urns, options=None):
1428         if options is None: options={}
1429         aggregate = IotlabAggregate(self)
1430         desc =  aggregate.describe(urns, version='GENI 3')
1431         status = {'geni_urn': desc['geni_urn'],
1432                   'geni_slivers': desc['geni_slivers']}
1433         return status
1434
1435
1436     def allocate (self, urn, rspec_string, expiration, options=None):
1437         if options is None: options={}
1438         xrn = Xrn(urn)
1439         aggregate = IotlabAggregate(self)
1440
1441         slices = IotlabSlices(self)
1442         peer = slices.get_peer(xrn.get_hrn())
1443         sfa_peer = slices.get_sfa_peer(xrn.get_hrn())
1444
1445         caller_hrn = options.get('actual_caller_hrn', [])
1446         caller_xrn = Xrn(caller_hrn)
1447         caller_urn = caller_xrn.get_urn()
1448
1449         logger.debug("IOTLABDRIVER.PY :: Allocate caller = %s" % (caller_urn))
1450
1451         slice_record = {}
1452         users = options.get('geni_users', [])
1453         sfa_users = options.get('sfa_users', [])
1454         
1455         if sfa_users:
1456             user = None
1457             # Looking for the user who actually called the Allocate function in the list of users of the slice
1458             for u in sfa_users:
1459                 if 'urn' in u and u['urn'] == caller_urn:
1460                     user = u
1461                     logger.debug("user = %s" % u)
1462             # If we find the user in the list we use it, else we take the 1st in the list as before
1463             if user:
1464                 user_hrn = caller_hrn
1465             else:
1466                 user = sfa_users[0]
1467                 # XXX Always empty ??? no slice_record in the Allocate call
1468                 #slice_record = sfa_users[0].get('slice_record', [])
1469                 user_xrn = Xrn(sfa_users[0]['urn'])
1470                 user_hrn = user_xrn.get_hrn()
1471
1472             slice_record = user.get('slice_record', {})
1473             slice_record['user'] = {'keys': user['keys'],
1474                                     'email': user['email'],
1475                                     'hrn': user_hrn}
1476             slice_record['authority'] = xrn.get_authority_hrn() 
1477
1478         logger.debug("IOTLABDRIVER.PY \t urn %s allocate options  %s "
1479                      % (urn, options))
1480
1481         # parse rspec
1482         rspec = RSpec(rspec_string)
1483         # requested_attributes = rspec.version.get_slice_attributes()
1484
1485         # ensure site record exists
1486
1487         # ensure person records exists
1488
1489         # oui c'est degueulasse, le slice_record se retrouve modifie
1490         # dans la methode avec les infos du user, els infos sont propagees
1491         # dans verify_slice_leases
1492         logger.debug("IOTLABDRIVER.PY  BEFORE slices.verify_persons")
1493         # XXX JORDAN XXX slice_record devrait recevoir le caller_xrn...
1494         logger.debug("LOIC users = %r" % users)
1495         # XXX LOIC XXX Trying here to bypass the verify_persons function
1496         # But it doesn't work, It seems we have to add users in LDAP anyway...
1497         for user in users:
1498             # XXX LOIC using hrn is a workaround because the function 
1499             # Xrn.get_urn returns 'urn:publicid:IDN+onelab:upmc+timur_friedman'
1500             # Instead of this     'urn:publicid:IDN+onelab:upmc+user+timur_friedman'
1501             user['hrn'] = urn_to_hrn(user['urn'])[0]
1502             # XXX LOIC adding the users of the slice to reg-researchers
1503             # reg-researchers is used in iotlabslices.py verify_slice in order to add the slice
1504             if 'reg-researchers' not in slice_record:
1505                 slice_record['reg-researchers'] = list()
1506             slice_record['reg-researchers'].append(user['hrn'])
1507             if caller_hrn == user['hrn']:
1508                 #hierarchical_user = user['hrn'].split(".")
1509                 #user['login'] = hierarchical_user[-1]            
1510                 #slice_record['login'] = user['login']
1511                 slice_record['user']=user
1512
1513         # XXX LOIC XXX Need to re-activate this function and understand exactly what is required
1514
1515         persons = slices.verify_persons(xrn.hrn, slice_record, [slice_record['user']], options=options)
1516         logger.debug("IOTLABDRIVER.PY  AFTER slices.verify_persons")
1517         logger.debug("LOIC - IOTLABDRIVER.PY - AFTER slices.verify_persons slice_record = %r" % slice_record)
1518         # ensure slice attributes exists
1519         # XXX LOIC XXX This method doesn't exist !!!
1520         #slices.verify_slice_attributes(slice, requested_attributes, options=options)
1521
1522         # XXX LOIC !!! Essayons d'inverser : d'abord verify_persons ensuite verify_slice
1523         
1524         # ensure slice record exists
1525         # XXX LOIC !!! verify_slice uniquement pour iotlab - les slices externes ne doivent pas être vérifiés = Ils ne sont pas dans le registry IOTLAB
1526         current_slice = None
1527         #current_slice = slices.verify_slice(xrn.hrn, slice_record, sfa_peer)
1528         #logger.debug("IOTLABDRIVER.PY \t ===============allocate \t\
1529         #                    \r\n \r\n  current_slice %s" % (current_slice))
1530
1531         # add/remove slice from nodes
1532         # XXX JORDAN ensure requested_xp_dict returns a dict with all new leases
1533         requested_xp_dict = self._process_requested_xp_dict(rspec)
1534
1535         logger.debug("IOTLABDRIVER.PY \tallocate  requested_xp_dict %s "
1536                      % (requested_xp_dict))
1537         request_nodes = rspec.version.get_nodes_with_slivers()
1538
1539         # JORDAN: nodes_list will contain a list of newly allocated nodes
1540         nodes_list = []
1541         for start_time in requested_xp_dict:
1542             lease = requested_xp_dict[start_time]
1543             for hostname in lease['hostname']:
1544                 nodes_list.append(hostname)
1545
1546         # nodes = slices.verify_slice_nodes(slice_record,request_nodes, peer)
1547         logger.debug("IOTLABDRIVER.PY \tallocate  nodes_list %s slice_record %s"
1548                      % (nodes_list, slice_record))
1549
1550         # add/remove leases
1551         rspec_requested_leases = rspec.version.get_leases()
1552         leases = slices.verify_slice_leases(slice_record,
1553                                                 requested_xp_dict, peer)
1554         # JORDAN: 
1555         #   leases = already in slice
1556         #   rspec_requested_leases = newly requested
1557         logger.debug("IOTLABDRIVER.PY \tallocate leases  %s \
1558                         rspec_requested_leases %s" % (leases,
1559                         rspec_requested_leases))
1560
1561         # XXX LOIC !!! What is in the slice_record?
1562         # Where is affected reg_researchers value???
1563         logger.debug("LOIC - IOTLABDRIVER.PY - After verify_slice_leases slice_record = %r" %slice_record)
1564         # update sliver allocations
1565         # JORDAN Here we loop over newly allocated nodes
1566         for hostname in nodes_list:
1567             logger.debug("FORLOOP JORDAN hostname=%r" % hostname)
1568             client_id = hostname
1569             node_urn = xrn_object(self.testbed_shell.root_auth, hostname).urn
1570             component_id = node_urn
1571             if current_slice is not None:
1572                 if 'reg-urn' in current_slice:
1573                     slice_urn = current_slice['reg-urn']
1574                 else:
1575                     slice_urn = current_slice['urn']
1576             else:
1577                 slice_urn = slice_record['urn']
1578             # JORDAN: We loop over leases previously in the slice
1579             for lease in leases: # rspec_requested_leases ?????? XXX
1580                 logger.debug("FOR LEASE LOOP JORDAN lease=%r" % lease)
1581                 logger.debug("JORDAN hostname=%r lease['reserved_nodes']=%r, bool=%r" % (hostname, lease['reserved_nodes'], hostname in lease['reserved_nodes']))
1582                 if hostname in lease['reserved_nodes']:
1583                     logger.debug("JORDAN IF OK")
1584                     index = lease['reserved_nodes'].index(hostname)
1585                     logger.debug("JORDAN index=%r" % index)
1586                     sliver_hrn = '%s.%s-%s' % (self.hrn, lease['lease_id'],
1587                                    lease['resource_ids'][index] )
1588
1589                     logger.debug("LOIC sliver_hrn=%r" % sliver_hrn)
1590                     sliver_id = Xrn(sliver_hrn, type='sliver').urn
1591
1592                     logger.debug("LOIC sliver_id=%r" % sliver_id)
1593                     record = SliverAllocation(sliver_id=sliver_id, client_id=client_id,
1594                                               component_id=component_id,
1595                                               slice_urn = slice_urn,
1596                                               allocation_state='geni_allocated')
1597                     record.sync(self.api.dbsession())
1598
1599         # JORDAN : added describe_options which was not specified at all
1600         describe_options = {
1601             'geni_slice_urn': urn,
1602             'list_leases': 'all',
1603         }
1604         return aggregate.describe([xrn.get_urn()], version=rspec.version, options=describe_options)
1605
1606     def provision(self, urns, options=None):
1607         if options is None: options={}
1608         # update users
1609         slices = IotlabSlices(self)
1610         aggregate = IotlabAggregate(self)
1611         slivers = aggregate.get_slivers(urns)
1612         current_slice = slivers[0]
1613         logger.debug("Provision current slice: %r" % (current_slice,))
1614         peer = slices.get_peer(current_slice['hrn'])
1615         sfa_peer = slices.get_sfa_peer(current_slice['hrn'])
1616         users = options.get('geni_users', [])
1617         # persons = slices.verify_persons(current_slice['hrn'],
1618             # current_slice, users, peer, sfa_peer, options=options)
1619         # slices.handle_peer(None, None, persons, peer)
1620         # update sliver allocation states and set them to geni_provisioned
1621         sliver_ids = [sliver['sliver_id'] for sliver in slivers]
1622         dbsession = self.api.dbsession()
1623         SliverAllocation.set_allocations(sliver_ids, 'geni_provisioned',
1624                                                                 dbsession)
1625         version_manager = VersionManager()
1626         rspec_version = version_manager.get_version(options[
1627                                                         'geni_rspec_version'])
1628         # JORDAN : added describe_options instead of options
1629         # urns at the begining ???
1630         describe_options = {
1631             'geni_slice_urn': current_slice['urn'],
1632             'list_leases': 'all',
1633         }
1634         return self.describe(urns, rspec_version, options=describe_options)