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