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