Trying to fix iotlab driver, need to be tested
[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={}):
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         # collect sliver ids so we can update sliver allocation states after
1124         # we remove the slivers.
1125         aggregate = IotlabAggregate(self)
1126         slivers = aggregate.get_slivers(slice_urns)
1127         if slivers:
1128             # slice_id = slivers[0]['slice_id']
1129             node_ids = []
1130             sliver_ids = []
1131             sliver_jobs_dict = {}
1132             for sliver in slivers:
1133                 node_ids.append(sliver['node_id'])
1134                 sliver_ids.append(sliver['sliver_id'])
1135                 job_id = sliver['sliver_id'].split('+')[-1].split('-')[0]
1136                 sliver_jobs_dict[job_id] = sliver['sliver_id']
1137         logger.debug("IOTLABDRIVER.PY delete_sliver slivers %s slice_urns %s"
1138             % (slivers, slice_urns))
1139         slice_hrn = urn_to_hrn(slice_urns[0])[0]
1140
1141         sfa_slice_list = self.GetSlices(slice_filter=slice_hrn,
1142                                         slice_filter_type='slice_hrn')
1143
1144         if not sfa_slice_list:
1145             return 1
1146
1147         #Delete all leases in the slice
1148         for sfa_slice in sfa_slice_list:
1149             logger.debug("IOTLABDRIVER.PY delete_sliver slice %s" % (sfa_slice))
1150             slices = IotlabSlices(self)
1151             # determine if this is a peer slice
1152
1153             peer = slices.get_peer(slice_hrn)
1154
1155             logger.debug("IOTLABDRIVER.PY delete_sliver peer %s \
1156                 \r\n \t sfa_slice %s " % (peer, sfa_slice))
1157             oar_bool_ans = self.testbed_shell.DeleteSliceFromNodes(
1158                                                                     sfa_slice)
1159             for job_id in oar_bool_ans:
1160                 # if the job has not been successfully deleted
1161                 # don't delete the associated sliver
1162                 # remove it from the sliver list
1163                 if oar_bool_ans[job_id] is False:
1164                     sliver = sliver_jobs_dict[job_id]
1165                     sliver_ids.remove(sliver)
1166             try:
1167
1168                 dbsession = self.api.dbsession()
1169                 SliverAllocation.delete_allocations(sliver_ids, dbsession)
1170             except :
1171                 logger.log_exc("IOTLABDRIVER.PY delete error ")
1172
1173         # prepare return struct
1174         geni_slivers = []
1175         for sliver in slivers:
1176             geni_slivers.append(
1177                 {'geni_sliver_urn': sliver['sliver_id'],
1178                  'geni_allocation_status': 'geni_unallocated',
1179                  'geni_expires': datetime_to_string(utcparse(sliver['expires']))})
1180         return geni_slivers
1181
1182
1183
1184
1185     def list_slices(self, creds, options):
1186         """Answer to ListSlices.
1187
1188         List slices belonging to iotlab, returns slice urns list.
1189             No caching used. Options unused but are defined in the SFA method
1190             api prototype.
1191
1192         :returns: slice urns list
1193         :rtype: list
1194
1195         .. note:: creds and options are unused - SA 12/12/13
1196         """
1197         # look in cache first
1198         #if self.cache:
1199             #slices = self.cache.get('slices')
1200             #if slices:
1201                 #logger.debug("PlDriver.list_slices returns from cache")
1202                 #return slices
1203
1204         # get data from db
1205
1206         slices = self.GetSlices()
1207         logger.debug("IOTLABDRIVER.PY \tlist_slices hrn %s \r\n \r\n"
1208                      % (slices))
1209         slice_hrns = [iotlab_slice['hrn'] for iotlab_slice in slices]
1210
1211         slice_urns = [hrn_to_urn(slice_hrn, 'slice')
1212                       for slice_hrn in slice_hrns]
1213
1214         # cache the result
1215         #if self.cache:
1216             #logger.debug ("IotlabDriver.list_slices stores value in cache")
1217             #self.cache.add('slices', slice_urns)
1218
1219         return slice_urns
1220
1221
1222     def register(self, sfa_record, hrn, pub_key):
1223         """
1224         Adding new user, slice, node or site should not be handled
1225             by SFA.
1226
1227         ..warnings:: should not be used. Different components are in charge of
1228             doing this task. Adding nodes = OAR
1229             Adding users = LDAP Iotlab
1230             Adding slice = Import from LDAP users
1231             Adding site = OAR
1232
1233         :param sfa_record: record provided by the client of the
1234             Register API call.
1235         :type sfa_record: dict
1236         :param pub_key: public key of the user
1237         :type pub_key: string
1238
1239         .. note:: DOES NOTHING. Returns -1.
1240
1241         """
1242         return -1
1243
1244
1245     def update(self, old_sfa_record, new_sfa_record, hrn, new_key):
1246         """
1247         No site or node record update allowed in Iotlab. The only modifications
1248         authorized here are key deletion/addition on an existing user and
1249         password change. On an existing user, CAN NOT BE MODIFIED: 'first_name',
1250         'last_name', 'email'. DOES NOT EXIST IN SENSLAB: 'phone', 'url', 'bio',
1251         'title', 'accepted_aup'. A slice is bound to its user, so modifying the
1252         user's ssh key should nmodify the slice's GID after an import procedure.
1253
1254         :param old_sfa_record: what is in the db for this hrn
1255         :param new_sfa_record: what was passed to the update call
1256         :param new_key: the new user's public key
1257         :param hrn: the user's sfa hrn
1258         :type old_sfa_record: dict
1259         :type new_sfa_record: dict
1260         :type new_key: string
1261         :type hrn: string
1262
1263         TODO: needs review
1264         .. warning:: SA 12/12/13 - Removed. should be done in iotlabimporter
1265         since users, keys and slice are managed by the LDAP.
1266
1267         """
1268         # pointer = old_sfa_record['pointer']
1269         # old_sfa_record_type = old_sfa_record['type']
1270
1271         # # new_key implemented for users only
1272         # if new_key and old_sfa_record_type not in ['user']:
1273         #     raise UnknownSfaType(old_sfa_record_type)
1274
1275         # if old_sfa_record_type == "user":
1276         #     update_fields = {}
1277         #     all_fields = new_sfa_record
1278         #     for key in all_fields.keys():
1279         #         if key in ['key', 'password']:
1280         #             update_fields[key] = all_fields[key]
1281
1282         #     if new_key:
1283         #         # must check this key against the previous one if it exists
1284         #         persons = self.testbed_shell.GetPersons([old_sfa_record])
1285         #         person = persons[0]
1286         #         keys = [person['pkey']]
1287         #         #Get all the person's keys
1288         #         keys_dict = self.GetKeys(keys)
1289
1290         #         # Delete all stale keys, meaning the user has only one key
1291         #         #at a time
1292         #         #TODO: do we really want to delete all the other keys?
1293         #         #Is this a problem with the GID generation to have multiple
1294         #         #keys? SA 30/05/13
1295         #         key_exists = False
1296         #         if key in keys_dict:
1297         #             key_exists = True
1298         #         else:
1299         #             #remove all the other keys
1300         #             for key in keys_dict:
1301         #                 self.testbed_shell.DeleteKey(person, key)
1302         #             self.testbed_shell.AddPersonKey(
1303         #                 person, {'sshPublicKey': person['pkey']},
1304         #                 {'sshPublicKey': new_key})
1305         logger.warning ("UNDEFINED - Update should be done by the \
1306             iotlabimporter")
1307         return True
1308
1309     def remove(self, sfa_record):
1310         """
1311
1312         Removes users only. Mark the user as disabled in LDAP. The user and his
1313         slice are then deleted from the db by running an import on the registry.
1314
1315         :param sfa_record: record is the existing sfa record in the db
1316         :type sfa_record: dict
1317
1318         ..warning::As fas as the slice is concerned, here only the leases are
1319             removed from the slice. The slice is record itself is not removed
1320             from the db.
1321
1322         TODO: needs review
1323
1324         TODO : REMOVE SLICE FROM THE DB AS WELL? SA 14/05/2013,
1325
1326         TODO: return boolean for the slice part
1327         """
1328         sfa_record_type = sfa_record['type']
1329         hrn = sfa_record['hrn']
1330         if sfa_record_type == 'user':
1331
1332             #get user from iotlab ldap
1333             person = self.testbed_shell.GetPersons(sfa_record)
1334             #No registering at a given site in Iotlab.
1335             #Once registered to the LDAP, all iotlab sites are
1336             #accesible.
1337             if person:
1338                 #Mark account as disabled in ldap
1339                 return self.testbed_shell.DeletePerson(sfa_record)
1340
1341         elif sfa_record_type == 'slice':
1342             if self.GetSlices(slice_filter=hrn,
1343                                 slice_filter_type='slice_hrn'):
1344                 ret = self.testbed_shell.DeleteSlice(sfa_record)
1345             return True
1346
1347     def check_sliver_credentials(self, creds, urns):
1348         """Check that the sliver urns belongs to the slice specified in the
1349         credentials.
1350
1351         :param urns: list of sliver urns.
1352         :type urns: list.
1353         :param creds: slice credentials.
1354         :type creds: Credential object.
1355
1356
1357         """
1358         # build list of cred object hrns
1359         slice_cred_names = []
1360         for cred in creds:
1361             slice_cred_hrn = Credential(cred=cred).get_gid_object().get_hrn()
1362             slicename = IotlabXrn(xrn=slice_cred_hrn).iotlab_slicename()
1363             slice_cred_names.append(slicename)
1364
1365         # look up slice name of slivers listed in urns arg
1366
1367         slice_ids = []
1368         for urn in urns:
1369             sliver_id_parts = Xrn(xrn=urn).get_sliver_id_parts()
1370             try:
1371                 slice_ids.append(int(sliver_id_parts[0]))
1372             except ValueError:
1373                 pass
1374
1375         if not slice_ids:
1376             raise Forbidden("sliver urn not provided")
1377
1378         slices = self.GetSlices(slice_ids)
1379         sliver_names = [single_slice['name'] for single_slice in slices]
1380
1381         # make sure we have a credential for every specified sliver
1382         for sliver_name in sliver_names:
1383             if sliver_name not in slice_cred_names:
1384                 msg = "Valid credential not found for target: %s" % sliver_name
1385                 raise Forbidden(msg)
1386
1387     ########################################
1388     ########## aggregate oriented
1389     ########################################
1390
1391     # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
1392     def aggregate_version(self):
1393         """
1394
1395         Returns the testbed's supported rspec advertisement and request
1396         versions.
1397         :returns: rspec versions supported ad a dictionary.
1398         :rtype: dict
1399
1400         """
1401         version_manager = VersionManager()
1402         ad_rspec_versions = []
1403         request_rspec_versions = []
1404         for rspec_version in version_manager.versions:
1405             if rspec_version.content_type in ['*', 'ad']:
1406                 ad_rspec_versions.append(rspec_version.to_dict())
1407             if rspec_version.content_type in ['*', 'request']:
1408                 request_rspec_versions.append(rspec_version.to_dict())
1409         return {
1410             'testbed': self.testbed_name(),
1411             'geni_request_rspec_versions': request_rspec_versions,
1412             'geni_ad_rspec_versions': ad_rspec_versions}
1413
1414     # first 2 args are None in case of resource discovery
1415     def list_resources (self, version=None, options={}):
1416         aggregate = IotlabAggregate(self)
1417         rspec =  aggregate.list_resources(version=version, options=options)
1418         return rspec
1419
1420     def describe(self, urns, version, options={}):
1421         aggregate = IotlabAggregate(self)
1422         return aggregate.describe(urns, version=version, options=options)
1423
1424     def status (self, urns, options={}):
1425         aggregate = IotlabAggregate(self)
1426         desc =  aggregate.describe(urns, version='GENI 3')
1427         status = {'geni_urn': desc['geni_urn'],
1428                   'geni_slivers': desc['geni_slivers']}
1429         return status
1430
1431
1432     def allocate (self, urn, rspec_string, expiration, options={}):
1433         xrn = Xrn(urn)
1434         aggregate = IotlabAggregate(self)
1435
1436         slices = IotlabSlices(self)
1437         peer = slices.get_peer(xrn.get_hrn())
1438         sfa_peer = slices.get_sfa_peer(xrn.get_hrn())
1439
1440         caller_hrn = options.get('actual_caller_hrn', [])
1441         caller_xrn = Xrn(caller_hrn)
1442         caller_urn = caller_xrn.get_urn()
1443
1444         logger.debug("IOTLABDRIVER.PY :: Allocate caller = %s" % (caller_urn))
1445
1446         slice_record = {}
1447         users = options.get('geni_users', [])
1448         sfa_users = options.get('sfa_users', [])
1449         
1450         if sfa_users:
1451             user = None
1452             # Looking for the user who actually called the Allocate function in the list of users of the slice
1453             for u in sfa_users:
1454                 if 'urn' in u and u['urn'] == caller_urn:
1455                     user = u
1456                     logger.debug("user = %s" % u)
1457             # If we find the user in the list we use it, else we take the 1st in the list as before
1458             if user:
1459                 user_hrn = caller_hrn
1460             else:
1461                 user = sfa_users[0]
1462                 # XXX Always empty ??? no slice_record in the Allocate call
1463                 #slice_record = sfa_users[0].get('slice_record', [])
1464                 user_xrn = Xrn(sfa_users[0]['urn'])
1465                 user_hrn = user_xrn.get_hrn()
1466
1467             slice_record = user.get('slice_record', {})
1468             slice_record['user'] = {'keys': user['keys'],
1469                                     'email': user['email'],
1470                                     'hrn': user_hrn}
1471             slice_record['authority'] = xrn.get_authority_hrn() 
1472
1473         logger.debug("IOTLABDRIVER.PY \t urn %s allocate options  %s "
1474                      % (urn, options))
1475
1476         # parse rspec
1477         rspec = RSpec(rspec_string)
1478         # requested_attributes = rspec.version.get_slice_attributes()
1479
1480         # ensure site record exists
1481
1482         # ensure person records exists
1483
1484         # oui c'est degueulasse, le slice_record se retrouve modifie
1485         # dans la methode avec les infos du user, els infos sont propagees
1486         # dans verify_slice_leases
1487         logger.debug("IOTLABDRIVER.PY  BEFORE slices.verify_persons")
1488         # XXX JORDAN XXX slice_record devrait recevoir le caller_xrn...
1489         logger.debug("LOIC users = %r" % users)
1490         # XXX LOIC XXX Trying here to bypass the verify_persons function
1491         # But it doesn't work, It seems we have to add users in LDAP anyway...
1492         for user in users:
1493             # XXX LOIC using hrn is a workaround because the function 
1494             # Xrn.get_urn returns 'urn:publicid:IDN+onelab:upmc+timur_friedman'
1495             # Instead of this     'urn:publicid:IDN+onelab:upmc+user+timur_friedman'
1496             user['hrn'] = urn_to_hrn(user['urn'])[0]
1497             # XXX LOIC adding the users of the slice to reg-researchers
1498             # reg-researchers is used in iotlabslices.py verify_slice in order to add the slice
1499             if 'reg-researchers' not in slice_record:
1500                 slice_record['reg-researchers'] = list()
1501             slice_record['reg-researchers'].append(user['hrn'])
1502             if caller_hrn == user['hrn']:
1503                 #hierarchical_user = user['hrn'].split(".")
1504                 #user['login'] = hierarchical_user[-1]            
1505                 #slice_record['login'] = user['login']
1506                 slice_record['user']=user
1507
1508         # XXX LOIC XXX Need to re-activate this function and understand exactly what is required
1509
1510         persons = slices.verify_persons(xrn.hrn, slice_record, [slice_record['user']], options=options)
1511         logger.debug("IOTLABDRIVER.PY  AFTER slices.verify_persons")
1512         logger.debug("LOIC - IOTLABDRIVER.PY - AFTER slices.verify_persons slice_record = %r" % slice_record)
1513         # ensure slice attributes exists
1514         # XXX LOIC XXX This method doesn't exist !!!
1515         #slices.verify_slice_attributes(slice, requested_attributes, options=options)
1516
1517         # XXX LOIC !!! Essayons d'inverser : d'abord verify_persons ensuite verify_slice
1518         
1519         # ensure slice record exists
1520         # 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
1521         current_slice = None
1522         #current_slice = slices.verify_slice(xrn.hrn, slice_record, sfa_peer)
1523         #logger.debug("IOTLABDRIVER.PY \t ===============allocate \t\
1524         #                    \r\n \r\n  current_slice %s" % (current_slice))
1525
1526         # add/remove slice from nodes
1527         # XXX JORDAN ensure requested_xp_dict returns a dict with all new leases
1528         requested_xp_dict = self._process_requested_xp_dict(rspec)
1529
1530         logger.debug("IOTLABDRIVER.PY \tallocate  requested_xp_dict %s "
1531                      % (requested_xp_dict))
1532         request_nodes = rspec.version.get_nodes_with_slivers()
1533
1534         # JORDAN: nodes_list will contain a list of newly allocated nodes
1535         nodes_list = []
1536         for start_time in requested_xp_dict:
1537             lease = requested_xp_dict[start_time]
1538             for hostname in lease['hostname']:
1539                 nodes_list.append(hostname)
1540
1541         # nodes = slices.verify_slice_nodes(slice_record,request_nodes, peer)
1542         logger.debug("IOTLABDRIVER.PY \tallocate  nodes_list %s slice_record %s"
1543                      % (nodes_list, slice_record))
1544
1545         # add/remove leases
1546         rspec_requested_leases = rspec.version.get_leases()
1547         leases = slices.verify_slice_leases(slice_record,
1548                                                 requested_xp_dict, peer)
1549         # JORDAN: 
1550         #   leases = already in slice
1551         #   rspec_requested_leases = newly requested
1552         logger.debug("IOTLABDRIVER.PY \tallocate leases  %s \
1553                         rspec_requested_leases %s" % (leases,
1554                         rspec_requested_leases))
1555
1556         # XXX LOIC !!! What is in the slice_record?
1557         # Where is affected reg_researchers value???
1558         logger.debug("LOIC - IOTLABDRIVER.PY - After verify_slice_leases slice_record = %r" %slice_record)
1559         # update sliver allocations
1560         # JORDAN Here we loop over newly allocated nodes
1561         for hostname in nodes_list:
1562             logger.debug("FORLOOP JORDAN hostname=%r" % hostname)
1563             client_id = hostname
1564             node_urn = xrn_object(self.testbed_shell.root_auth, hostname).urn
1565             component_id = node_urn
1566             if current_slice is not None:
1567                 if 'reg-urn' in current_slice:
1568                     slice_urn = current_slice['reg-urn']
1569                 else:
1570                     slice_urn = current_slice['urn']
1571             else:
1572                 slice_urn = slice_record['urn']
1573             # JORDAN: We loop over leases previously in the slice
1574             for lease in leases: # rspec_requested_leases ?????? XXX
1575                 logger.debug("FOR LEASE LOOP JORDAN lease=%r" % lease)
1576                 logger.debug("JORDAN hostname=%r lease['reserved_nodes']=%r, bool=%r" % (hostname, lease['reserved_nodes'], hostname in lease['reserved_nodes']))
1577                 if hostname in lease['reserved_nodes']:
1578                     logger.debug("JORDAN IF OK")
1579                     index = lease['reserved_nodes'].index(hostname)
1580                     logger.debug("JORDAN index=%r" % index)
1581                     sliver_hrn = '%s.%s-%s' % (self.hrn, lease['lease_id'],
1582                                    lease['resource_ids'][index] )
1583
1584                     logger.debug("LOIC sliver_hrn=%r" % sliver_hrn)
1585                     sliver_id = Xrn(sliver_hrn, type='sliver').urn
1586
1587                     logger.debug("LOIC sliver_id=%r" % sliver_id)
1588                     record = SliverAllocation(sliver_id=sliver_id, client_id=client_id,
1589                                               component_id=component_id,
1590                                               slice_urn = slice_urn,
1591                                               allocation_state='geni_allocated')
1592                     record.sync(self.api.dbsession())
1593
1594         # JORDAN : added describe_options which was not specified at all
1595         describe_options = {
1596             'geni_slice_urn': urn,
1597             'list_leases': 'all',
1598         }
1599         return aggregate.describe([xrn.get_urn()], version=rspec.version, options=describe_options)
1600
1601     def provision(self, urns, options={}):
1602         # update users
1603         slices = IotlabSlices(self)
1604         aggregate = IotlabAggregate(self)
1605         slivers = aggregate.get_slivers(urns)
1606         current_slice = slivers[0]
1607         logger.debug("Provision current slice: %r" % (current_slice,))
1608         peer = slices.get_peer(current_slice['hrn'])
1609         sfa_peer = slices.get_sfa_peer(current_slice['hrn'])
1610         users = options.get('geni_users', [])
1611         # persons = slices.verify_persons(current_slice['hrn'],
1612             # current_slice, users, peer, sfa_peer, options=options)
1613         # slices.handle_peer(None, None, persons, peer)
1614         # update sliver allocation states and set them to geni_provisioned
1615         sliver_ids = [sliver['sliver_id'] for sliver in slivers]
1616         dbsession = self.api.dbsession()
1617         SliverAllocation.set_allocations(sliver_ids, 'geni_provisioned',
1618                                                                 dbsession)
1619         version_manager = VersionManager()
1620         rspec_version = version_manager.get_version(options[
1621                                                         'geni_rspec_version'])
1622         # JORDAN : added describe_options instead of options
1623         # urns at the begining ???
1624         describe_options = {
1625             'geni_slice_urn': current_slice['urn'],
1626             'list_leases': 'all',
1627         }
1628         return self.describe(urns, rspec_version, options=describe_options)