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