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