Allocate, Describe, Provision now working for iotlab.
[sfa.git] / sfa / iotlab / iotlabdriver.py
1 """
2 Implements what a driver should provide for SFA to work.
3 """
4 from sfa.util.faults import SliverDoesNotExist, UnknownSfaType
5 from sfa.util.sfalogging import logger
6 from sfa.storage.model import RegRecord
7 from sfa.util.sfatime import utcparse, datetime_to_string
8
9 from sfa.managers.driver import Driver
10 from sfa.rspecs.version_manager import VersionManager
11 from sfa.rspecs.rspec import RSpec
12
13 from sfa.iotlab.iotlabxrn import xrn_object
14 from sfa.util.xrn import Xrn, hrn_to_urn, get_authority, urn_to_hrn
15
16 from sfa.iotlab.iotlabaggregate import IotlabAggregate
17 from sfa.iotlab.iotlabxrn import xrn_to_hostname
18 from sfa.iotlab.iotlabslices import IotlabSlices
19
20 from sfa.storage.model import SliverAllocation
21 from sfa.iotlab.iotlabshell import IotlabShell
22
23
24 class IotlabDriver(Driver):
25     """ Iotlab Driver class inherited from Driver generic class.
26
27     Contains methods compliant with the SFA standard and the testbed
28         infrastructure (calls to LDAP and OAR).
29
30     .. seealso::: Driver class
31
32     """
33     def __init__(self, api):
34         """
35
36         Sets the iotlab SFA config parameters,
37             instanciates the testbed api and the iotlab database.
38
39         :param config: iotlab SFA configuration object
40         :type config: Config object
41
42         """
43         Driver.__init__(self, api)
44         self.api = api
45         config = api.config
46         self.testbed_shell = IotlabShell(api)
47         self.cache = None
48
49     def augment_records_with_testbed_info(self, record_list):
50         """
51
52         Adds specific testbed info to the records.
53
54         :param record_list: list of sfa dictionaries records
55         :type record_list: list
56         :returns: list of records with extended information in each record
57         :rtype: list
58
59         """
60         return self.fill_record_info(record_list)
61
62     def fill_record_info(self, record_list):
63         """
64
65         For each SFA record, fill in the iotlab specific and SFA specific
66             fields in the record.
67
68         :param record_list: list of sfa dictionaries records
69         :type record_list: list
70         :returns: list of records with extended information in each record
71         :rtype: list
72
73         .. warning:: Should not be modifying record_list directly because modi
74             fication are kept outside the method's scope. Howerver, there is no
75             other way to do it given the way it's called in registry manager.
76
77         """
78
79         logger.debug("IOTLABDRIVER \tfill_record_info records %s "
80                      % (record_list))
81         if not isinstance(record_list, list):
82             record_list = [record_list]
83
84         try:
85             for record in record_list:
86
87                 if str(record['type']) == 'node':
88                     # look for node info using GetNodes
89                     # the record is about one node only
90                     filter_dict = {'hrn': [record['hrn']]}
91                     node_info = self.testbed_shell.GetNodes(filter_dict)
92                     # the node_info is about one node only, but it is formatted
93                     # as a list
94                     record.update(node_info[0])
95                     logger.debug("IOTLABDRIVER.PY \t \
96                                   fill_record_info NODE" % (record))
97
98                 #If the record is a SFA slice record, then add information
99                 #about the user of this slice. This kind of
100                 #information is in the Iotlab's DB.
101                 if str(record['type']) == 'slice':
102                     if 'reg_researchers' in record and isinstance(record
103                                                             ['reg_researchers'],
104                                                             list):
105                         record['reg_researchers'] = \
106                             record['reg_researchers'][0].__dict__
107                         record.update(
108                             {'PI': [record['reg_researchers']['hrn']],
109                              'researcher': [record['reg_researchers']['hrn']],
110                              'name': record['hrn'],
111                              'oar_job_id': [],
112                              'node_ids': [],
113                              'person_ids': [record['reg_researchers']
114                                             ['record_id']],
115                                 # For client_helper.py compatibility
116                              'geni_urn': '',
117                                 # For client_helper.py compatibility
118                              'keys': '',
119                                 # For client_helper.py compatibility
120                              'key_ids': ''})
121
122                     #Get iotlab slice record and oar job id if any.
123                     recslice_list = self.testbed_shell.GetSlices(
124                         slice_filter=str(record['hrn']),
125                         slice_filter_type='slice_hrn')
126
127                     logger.debug("IOTLABDRIVER \tfill_record_info \
128                         TYPE SLICE RECUSER record['hrn'] %s record['oar_job_id']\
129                          %s " % (record['hrn'], record['oar_job_id']))
130                     del record['reg_researchers']
131                     try:
132                         for rec in recslice_list:
133                             logger.debug("IOTLABDRIVER\r\n  \t  \
134                             fill_record_info oar_job_id %s "
135                                          % (rec['oar_job_id']))
136
137                             record['node_ids'] = [self.testbed_shell.root_auth +
138                                                   '.' + hostname for hostname
139                                                   in rec['node_ids']]
140                     except KeyError:
141                         pass
142
143                     logger.debug("IOTLABDRIVER.PY \t fill_record_info SLICE \
144                                     recslice_list  %s \r\n \t RECORD %s \r\n \
145                                     \r\n" % (recslice_list, record))
146
147                 if str(record['type']) == 'user':
148                     #The record is a SFA user record.
149                     #Get the information about his slice from Iotlab's DB
150                     #and add it to the user record.
151                     recslice_list = self.testbed_shell.GetSlices(
152                         slice_filter=record['record_id'],
153                         slice_filter_type='record_id_user')
154
155                     logger.debug("IOTLABDRIVER.PY \t fill_record_info \
156                         TYPE USER recslice_list %s \r\n \t RECORD %s \r\n"
157                                  % (recslice_list, record))
158                     #Append slice record in records list,
159                     #therefore fetches user and slice info again(one more loop)
160                     #Will update PIs and researcher for the slice
161
162                     recuser = recslice_list[0]['reg_researchers']
163                     logger.debug("IOTLABDRIVER.PY \t fill_record_info USER  \
164                                             recuser %s \r\n \r\n" % (recuser))
165                     recslice = {}
166                     recslice = recslice_list[0]
167                     recslice.update(
168                         {'PI': [recuser['hrn']],
169                          'researcher': [recuser['hrn']],
170                          'name': record['hrn'],
171                          'node_ids': [],
172                          'oar_job_id': [],
173                          'person_ids': [recuser['record_id']]})
174                     try:
175                         for rec in recslice_list:
176                             recslice['oar_job_id'].append(rec['oar_job_id'])
177                     except KeyError:
178                         pass
179
180                     recslice.update({'type': 'slice',
181                                      'hrn': recslice_list[0]['hrn']})
182
183                     #GetPersons takes [] as filters
184                     user_iotlab = self.testbed_shell.GetPersons([record])
185
186                     record.update(user_iotlab[0])
187                     #For client_helper.py compatibility
188                     record.update(
189                         {'geni_urn': '',
190                          'keys': '',
191                          'key_ids': ''})
192                     record_list.append(recslice)
193
194                     logger.debug("IOTLABDRIVER.PY \t \
195                         fill_record_info ADDING SLICE\
196                         INFO TO USER records %s" % (record_list))
197
198         except TypeError, error:
199             logger.log_exc("IOTLABDRIVER \t fill_record_info  EXCEPTION %s"
200                            % (error))
201
202         return record_list
203
204     def sliver_status(self, slice_urn, slice_hrn):
205         """
206         Receive a status request for slice named urn/hrn
207             urn:publicid:IDN+iotlab+nturro_slice hrn iotlab.nturro_slice
208             shall return a structure as described in
209             http://groups.geni.net/geni/wiki/GAPI_AM_API_V2#SliverStatus
210             NT : not sure if we should implement this or not, but used by sface.
211
212         :param slice_urn: slice urn
213         :type slice_urn: string
214         :param slice_hrn: slice hrn
215         :type slice_hrn: string
216
217         """
218
219         #First get the slice with the slice hrn
220         slice_list = self.testbed_shell.GetSlices(slice_filter=slice_hrn,
221                                                slice_filter_type='slice_hrn')
222
223         if len(slice_list) == 0:
224             raise SliverDoesNotExist("%s  slice_hrn" % (slice_hrn))
225
226         #Used for fetching the user info witch comes along the slice info
227         one_slice = slice_list[0]
228
229         #Make a list of all the nodes hostnames  in use for this slice
230         slice_nodes_list = []
231         slice_nodes_list = one_slice['node_ids']
232         #Get all the corresponding nodes details
233         nodes_all = self.testbed_shell.GetNodes(
234             {'hostname': slice_nodes_list},
235             ['node_id', 'hostname', 'site', 'boot_state'])
236         nodeall_byhostname = dict([(one_node['hostname'], one_node)
237                                   for one_node in nodes_all])
238
239         for single_slice in slice_list:
240               #For compatibility
241             top_level_status = 'empty'
242             result = {}
243             result.fromkeys(
244                 ['geni_urn', 'geni_error', 'iotlab_login', 'geni_status',
245                  'geni_resources'], None)
246             # result.fromkeys(\
247             #     ['geni_urn','geni_error', 'pl_login','geni_status',
248             # 'geni_resources'], None)
249             # result['pl_login'] = one_slice['reg_researchers'][0].hrn
250             result['iotlab_login'] = one_slice['user']
251             logger.debug("Slabdriver - sliver_status Sliver status \
252                             urn %s hrn %s single_slice  %s \r\n "
253                          % (slice_urn, slice_hrn, single_slice))
254
255             if 'node_ids' not in single_slice:
256                 #No job in the slice
257                 result['geni_status'] = top_level_status
258                 result['geni_resources'] = []
259                 return result
260
261             top_level_status = 'ready'
262
263             #A job is running on Iotlab for this slice
264             # report about the local nodes that are in the slice only
265
266             result['geni_urn'] = slice_urn
267
268             resources = []
269             for node_hostname in single_slice['node_ids']:
270                 res = {}
271                 res['iotlab_hostname'] = node_hostname
272                 res['iotlab_boot_state'] = \
273                     nodeall_byhostname[node_hostname]['boot_state']
274
275                 #res['pl_hostname'] = node['hostname']
276                 #res['pl_boot_state'] = \
277                             #nodeall_byhostname[node['hostname']]['boot_state']
278                 #res['pl_last_contact'] = strftime(self.time_format, \
279                                                     #gmtime(float(timestamp)))
280                 sliver_id = Xrn(
281                     slice_urn, type='slice',
282                     id=nodeall_byhostname[node_hostname]['node_id']).urn
283
284                 res['geni_urn'] = sliver_id
285                 #node_name  = node['hostname']
286                 if nodeall_byhostname[node_hostname]['boot_state'] == 'Alive':
287
288                     res['geni_status'] = 'ready'
289                 else:
290                     res['geni_status'] = 'failed'
291                     top_level_status = 'failed'
292
293                 res['geni_error'] = ''
294
295                 resources.append(res)
296
297             result['geni_status'] = top_level_status
298             result['geni_resources'] = resources
299             logger.debug("IOTLABDRIVER \tsliver_statusresources %s res %s "
300                          % (resources, res))
301             return result
302
303     def get_user_record(self, hrn):
304         """
305
306         Returns the user record based on the hrn from the SFA DB .
307
308         :param hrn: user's hrn
309         :type hrn: string
310         :returns: user record from SFA database
311         :rtype: RegUser
312
313         """
314         return self.api.dbsession().query(RegRecord).filter_by(hrn=hrn).first()
315
316     def testbed_name(self):
317         """
318
319         Returns testbed's name.
320         :returns: testbed authority name.
321         :rtype: string
322
323         """
324         return self.hrn
325
326     # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
327     def aggregate_version(self):
328         """
329
330         Returns the testbed's supported rspec advertisement and request
331         versions.
332         :returns: rspec versions supported ad a dictionary.
333         :rtype: dict
334
335         """
336         version_manager = VersionManager()
337         ad_rspec_versions = []
338         request_rspec_versions = []
339         for rspec_version in version_manager.versions:
340             if rspec_version.content_type in ['*', 'ad']:
341                 ad_rspec_versions.append(rspec_version.to_dict())
342             if rspec_version.content_type in ['*', 'request']:
343                 request_rspec_versions.append(rspec_version.to_dict())
344         return {
345             'testbed': self.testbed_name(),
346             'geni_request_rspec_versions': request_rspec_versions,
347             'geni_ad_rspec_versions': ad_rspec_versions}
348
349     def _get_requested_leases_list(self, rspec):
350         """
351         Process leases in rspec depending on the rspec version (format)
352             type. Find the lease requests in the rspec and creates
353             a lease request list with the mandatory information ( nodes,
354             start time and duration) of the valid leases (duration above or
355             equal to the iotlab experiment minimum duration).
356
357         :param rspec: rspec request received.
358         :type rspec: RSpec
359         :returns: list of lease requests found in the rspec
360         :rtype: list
361         """
362         requested_lease_list = []
363         for lease in rspec.version.get_leases():
364             single_requested_lease = {}
365             logger.debug("IOTLABDRIVER.PY \t \
366                 _get_requested_leases_list lease %s " % (lease))
367
368             if not lease.get('lease_id'):
369                 if get_authority(lease['component_id']) == \
370                         self.testbed_shell.root_auth:
371                     single_requested_lease['hostname'] = \
372                         xrn_to_hostname(\
373                             lease.get('component_id').strip())
374                     single_requested_lease['start_time'] = \
375                         lease.get('start_time')
376                     single_requested_lease['duration'] = lease.get('duration')
377                     #Check the experiment's duration is valid before adding
378                     #the lease to the requested leases list
379                     duration_in_seconds = \
380                         int(single_requested_lease['duration'])
381                     if duration_in_seconds >= self.testbed_shell.GetMinExperimentDurationInGranularity():
382                         requested_lease_list.append(single_requested_lease)
383
384         return requested_lease_list
385
386     @staticmethod
387     def _group_leases_by_start_time(requested_lease_list):
388         """
389         Create dict of leases by start_time, regrouping nodes reserved
390             at the same time, for the same amount of time so as to
391             define one job on OAR.
392
393         :param requested_lease_list: list of leases
394         :type requested_lease_list: list
395         :returns: Dictionary with key = start time, value = list of leases
396             with the same start time.
397         :rtype: dictionary
398
399         """
400
401         requested_xp_dict = {}
402         for lease in requested_lease_list:
403
404             #In case it is an asap experiment start_time is empty
405             if lease['start_time'] == '':
406                 lease['start_time'] = '0'
407
408             if lease['start_time'] not in requested_xp_dict:
409                 if isinstance(lease['hostname'], str):
410                     lease['hostname'] = [lease['hostname']]
411
412                 requested_xp_dict[lease['start_time']] = lease
413
414             else:
415                 job_lease = requested_xp_dict[lease['start_time']]
416                 if lease['duration'] == job_lease['duration']:
417                     job_lease['hostname'].append(lease['hostname'])
418
419         return requested_xp_dict
420
421     def _process_requested_xp_dict(self, rspec):
422         """
423         Turns the requested leases and information into a dictionary
424             of requested jobs, grouped by starting time.
425
426         :param rspec: RSpec received
427         :type rspec : RSpec
428         :rtype: dictionary
429
430         """
431         requested_lease_list = self._get_requested_leases_list(rspec)
432         logger.debug("IOTLABDRIVER _process_requested_xp_dict \
433             requested_lease_list  %s" % (requested_lease_list))
434         xp_dict = self._group_leases_by_start_time(requested_lease_list)
435         logger.debug("IOTLABDRIVER _process_requested_xp_dict  xp_dict\
436         %s" % (xp_dict))
437
438         return xp_dict
439
440
441     def create_sliver(self, slice_urn, slice_hrn, creds, rspec_string,
442                       users, options):
443         """Answer to CreateSliver.
444
445         Creates the leases and slivers for the users from the information
446             found in the rspec string.
447             Launch experiment on OAR if the requested leases is valid. Delete
448             no longer requested leases.
449
450
451         :param creds: user's credentials
452         :type creds: string
453         :param users: user record list
454         :type users: list
455         :param options:
456         :type options:
457
458         :returns: a valid Rspec for the slice which has just been
459             modified.
460         :rtype: RSpec
461
462
463         """
464         aggregate = IotlabAggregate(self)
465
466         slices = IotlabSlices(self)
467         peer = slices.get_peer(slice_hrn)
468         sfa_peer = slices.get_sfa_peer(slice_hrn)
469         slice_record = None
470
471         if not isinstance(creds, list):
472             creds = [creds]
473
474         if users:
475             slice_record = users[0].get('slice_record', {})
476             logger.debug("IOTLABDRIVER.PY \t ===============create_sliver \t\
477                             creds %s \r\n \r\n users %s"
478                          % (creds, users))
479             slice_record['user'] = {'keys': users[0]['keys'],
480                                     'email': users[0]['email'],
481                                     'hrn': slice_record['reg-researchers'][0]}
482         # parse rspec
483         rspec = RSpec(rspec_string)
484         logger.debug("IOTLABDRIVER.PY \t create_sliver \trspec.version \
485                      %s slice_record %s users %s"
486                      % (rspec.version, slice_record, users))
487
488         # ensure site record exists?
489         # ensure slice record exists
490         #Removed options in verify_slice SA 14/08/12
491         #Removed peer record in  verify_slice SA 18/07/13
492         sfa_slice = slices.verify_slice(slice_hrn, slice_record, sfa_peer)
493
494         # ensure person records exists
495         #verify_persons returns added persons but the return value
496         #is not used
497         #Removed peer record and sfa_peer in  verify_persons SA 18/07/13
498         slices.verify_persons(slice_hrn, sfa_slice, users, options=options)
499         #requested_attributes returned by rspec.version.get_slice_attributes()
500         #unused, removed SA 13/08/12
501         #rspec.version.get_slice_attributes()
502
503         logger.debug("IOTLABDRIVER.PY create_sliver slice %s " % (sfa_slice))
504
505         # add/remove slice from nodes
506
507         #requested_slivers = [node.get('component_id') \
508                     #for node in rspec.version.get_nodes_with_slivers()\
509                     #if node.get('authority_id') is self.testbed_shell.root_auth]
510         #l = [ node for node in rspec.version.get_nodes_with_slivers() ]
511         #logger.debug("SLADRIVER \tcreate_sliver requested_slivers \
512                                     #requested_slivers %s  listnodes %s" \
513                                     #%(requested_slivers,l))
514         #verify_slice_nodes returns nodes, but unused here. Removed SA 13/08/12.
515         #slices.verify_slice_nodes(sfa_slice, requested_slivers, peer)
516
517         requested_xp_dict = self._process_requested_xp_dict(rspec)
518
519         logger.debug("IOTLABDRIVER.PY \tcreate_sliver  requested_xp_dict %s "
520                      % (requested_xp_dict))
521         #verify_slice_leases returns the leases , but the return value is unused
522         #here. Removed SA 13/08/12
523         slices.verify_slice_leases(sfa_slice,
524                                    requested_xp_dict, peer)
525
526         return aggregate.get_rspec(slice_xrn=slice_urn,
527                                    login=sfa_slice['login'],
528                                    version=rspec.version)
529
530     def delete(self, slice_urns, options):
531         """
532         Deletes the lease associated with the slice hrn and the credentials
533             if the slice belongs to iotlab. Answer to DeleteSliver.
534
535         :param slice_urn: urn of the slice
536         :type slice_urn: string
537
538
539         :returns: 1 if the slice to delete was not found on iotlab,
540             True if the deletion was successful, False otherwise otherwise.
541
542         .. note:: Should really be named delete_leases because iotlab does
543             not have any slivers, but only deals with leases. However,
544             SFA api only have delete_sliver define so far. SA 13/05/2013
545         .. note:: creds are unused, and are not used either in the dummy driver
546              delete_sliver .
547         """
548         # collect sliver ids so we can update sliver allocation states after
549         # we remove the slivers.
550         aggregate = IotlabAggregate(self)
551         slivers = aggregate.get_slivers(slice_urns)
552         if slivers:
553             slice_id = slivers[0]['slice_id']
554             node_ids = []
555             sliver_ids = []
556             for sliver in slivers:
557                 node_ids.append(sliver['node_id'])
558                 sliver_ids.append(sliver['sliver_id'])
559         logger.debug("IOTLABDRIVER.PY delete_sliver slivers %s slice_urns %s"
560             % (slivers, slice_urns))
561         slice_hrn = urn_to_hrn(slice_urns[0])[0]
562
563         sfa_slice_list = self.testbed_shell.GetSlices(
564             slice_filter=slice_hrn,
565             slice_filter_type='slice_hrn')
566
567         if not sfa_slice_list:
568             return 1
569
570         #Delete all leases in the slice
571         for sfa_slice in sfa_slice_list:
572             logger.debug("IOTLABDRIVER.PY delete_sliver slice %s" % (sfa_slice))
573             slices = IotlabSlices(self)
574             # determine if this is a peer slice
575
576             peer = slices.get_peer(slice_hrn)
577
578             logger.debug("IOTLABDRIVER.PY delete_sliver peer %s \
579                 \r\n \t sfa_slice %s " % (peer, sfa_slice))
580             try:
581                 self.testbed_shell.DeleteSliceFromNodes(sfa_slice)
582                 dbsession = self.api.dbsession()
583                 SliverAllocation.delete_allocations(sliver_ids,dbsession)
584             except:
585                 logger.log_exc("IOTLABDRIVER.PY delete error ")
586
587         # prepare return struct
588         geni_slivers = []
589         for sliver in slivers:
590             geni_slivers.append(
591                 {'geni_sliver_urn': sliver['sliver_id'],
592                  'geni_allocation_status': 'geni_unallocated',
593                  'geni_expires': datetime_to_string(utcparse(sliver['expires']))})
594         return geni_slivers
595
596     def list_resources (self, slice_urn, slice_hrn, creds, options):
597         """
598
599         List resources from the iotlab aggregate and returns a Rspec
600             advertisement with resources found when slice_urn and slice_hrn are
601             None (in case of resource discovery).
602             If a slice hrn and urn are provided, list experiment's slice
603             nodes in a rspec format. Answer to ListResources.
604             Caching unused.
605
606         :param slice_urn: urn of the slice
607         :param slice_hrn: name of the slice
608         :param creds: slice credenials
609         :type slice_urn: string
610         :type slice_hrn: string
611         :type creds: ? unused
612         :param options: options used when listing resources (list_leases, info,
613             geni_available)
614         :returns: rspec string in xml
615         :rtype: string
616
617         .. note:: creds are unused
618         """
619
620         #cached_requested = options.get('cached', True)
621
622         version_manager = VersionManager()
623         # get the rspec's return format from options
624         rspec_version = \
625             version_manager.get_version(options.get('geni_rspec_version'))
626         version_string = "rspec_%s" % (rspec_version)
627
628         #panos adding the info option to the caching key (can be improved)
629         if options.get('info'):
630             version_string = version_string + "_" + \
631                 options.get('info', 'default')
632
633         # Adding the list_leases option to the caching key
634         if options.get('list_leases'):
635             version_string = version_string + "_" + \
636             options.get('list_leases', 'default')
637
638         # Adding geni_available to caching key
639         if options.get('geni_available'):
640             version_string = version_string + "_" + \
641                 str(options.get('geni_available'))
642
643         # look in cache first
644         #if cached_requested and self.cache and not slice_hrn:
645             #rspec = self.cache.get(version_string)
646             #if rspec:
647                 #logger.debug("IotlabDriver.ListResources: \
648                                     #returning cached advertisement")
649                 #return rspec
650
651         #panos: passing user-defined options
652         aggregate = IotlabAggregate(self)
653
654         rspec = aggregate.get_rspec(slice_xrn=slice_urn,
655                                     version=rspec_version, options=options)
656
657         # cache the result
658         #if self.cache and not slice_hrn:
659             #logger.debug("Iotlab.ListResources: stores advertisement in cache")
660             #self.cache.add(version_string, rspec)
661
662         return rspec
663
664
665     def list_slices(self, creds, options):
666         """Answer to ListSlices.
667
668         List slices belonging to iotlab, returns slice urns list.
669             No caching used. Options unused but are defined in the SFA method
670             api prototype.
671
672         :returns: slice urns list
673         :rtype: list
674
675         .. note:: creds are unused
676         """
677         # look in cache first
678         #if self.cache:
679             #slices = self.cache.get('slices')
680             #if slices:
681                 #logger.debug("PlDriver.list_slices returns from cache")
682                 #return slices
683
684         # get data from db
685
686         slices = self.testbed_shell.GetSlices()
687         logger.debug("IOTLABDRIVER.PY \tlist_slices hrn %s \r\n \r\n"
688                      % (slices))
689         slice_hrns = [iotlab_slice['hrn'] for iotlab_slice in slices]
690
691         slice_urns = [hrn_to_urn(slice_hrn, 'slice')
692                       for slice_hrn in slice_hrns]
693
694         # cache the result
695         #if self.cache:
696             #logger.debug ("IotlabDriver.list_slices stores value in cache")
697             #self.cache.add('slices', slice_urns)
698
699         return slice_urns
700
701
702     def register(self, sfa_record, hrn, pub_key):
703         """
704         Adding new user, slice, node or site should not be handled
705             by SFA.
706
707         ..warnings:: should not be used. Different components are in charge of
708             doing this task. Adding nodes = OAR
709             Adding users = LDAP Iotlab
710             Adding slice = Import from LDAP users
711             Adding site = OAR
712
713         :param sfa_record: record provided by the client of the
714             Register API call.
715         :type sfa_record: dict
716         :param pub_key: public key of the user
717         :type pub_key: string
718
719         .. note:: DOES NOTHING. Returns -1.
720
721         """
722         return -1
723
724
725     def update(self, old_sfa_record, new_sfa_record, hrn, new_key):
726         """
727         No site or node record update allowed in Iotlab. The only modifications
728         authorized here are key deletion/addition on an existing user and
729         password change. On an existing user, CAN NOT BE MODIFIED: 'first_name',
730         'last_name', 'email'. DOES NOT EXIST IN SENSLAB: 'phone', 'url', 'bio',
731         'title', 'accepted_aup'. A slice is bound to its user, so modifying the
732         user's ssh key should nmodify the slice's GID after an import procedure.
733
734         :param old_sfa_record: what is in the db for this hrn
735         :param new_sfa_record: what was passed to the update call
736         :param new_key: the new user's public key
737         :param hrn: the user's sfa hrn
738         :type old_sfa_record: dict
739         :type new_sfa_record: dict
740         :type new_key: string
741         :type hrn: string
742
743         TODO: needs review
744         .. seealso:: update in driver.py.
745
746         """
747         pointer = old_sfa_record['pointer']
748         old_sfa_record_type = old_sfa_record['type']
749
750         # new_key implemented for users only
751         if new_key and old_sfa_record_type not in ['user']:
752             raise UnknownSfaType(old_sfa_record_type)
753
754         if old_sfa_record_type == "user":
755             update_fields = {}
756             all_fields = new_sfa_record
757             for key in all_fields.keys():
758                 if key in ['key', 'password']:
759                     update_fields[key] = all_fields[key]
760
761             if new_key:
762                 # must check this key against the previous one if it exists
763                 persons = self.testbed_shell.GetPersons([old_sfa_record])
764                 person = persons[0]
765                 keys = [person['pkey']]
766                 #Get all the person's keys
767                 keys_dict = self.testbed_shell.GetKeys(keys)
768
769                 # Delete all stale keys, meaning the user has only one key
770                 #at a time
771                 #TODO: do we really want to delete all the other keys?
772                 #Is this a problem with the GID generation to have multiple
773                 #keys? SA 30/05/13
774                 key_exists = False
775                 if key in keys_dict:
776                     key_exists = True
777                 else:
778                     #remove all the other keys
779                     for key in keys_dict:
780                         self.testbed_shell.DeleteKey(person, key)
781                     self.testbed_shell.AddPersonKey(
782                         person, {'sshPublicKey': person['pkey']},
783                         {'sshPublicKey': new_key})
784         return True
785
786     def remove(self, sfa_record):
787         """
788
789         Removes users only. Mark the user as disabled in LDAP. The user and his
790         slice are then deleted from the db by running an import on the registry.
791
792         :param sfa_record: record is the existing sfa record in the db
793         :type sfa_record: dict
794
795         ..warning::As fas as the slice is concerned, here only the leases are
796             removed from the slice. The slice is record itself is not removed
797             from the db.
798
799         TODO: needs review
800
801         TODO : REMOVE SLICE FROM THE DB AS WELL? SA 14/05/2013,
802
803         TODO: return boolean for the slice part
804         """
805         sfa_record_type = sfa_record['type']
806         hrn = sfa_record['hrn']
807         if sfa_record_type == 'user':
808
809             #get user from iotlab ldap
810             person = self.testbed_shell.GetPersons(sfa_record)
811             #No registering at a given site in Iotlab.
812             #Once registered to the LDAP, all iotlab sites are
813             #accesible.
814             if person:
815                 #Mark account as disabled in ldap
816                 return self.testbed_shell.DeletePerson(sfa_record)
817
818         elif sfa_record_type == 'slice':
819             if self.testbed_shell.GetSlices(slice_filter=hrn,
820                                          slice_filter_type='slice_hrn'):
821                 ret = self.testbed_shell.DeleteSlice(sfa_record)
822             return True
823
824     def check_sliver_credentials(self, creds, urns):
825         # build list of cred object hrns
826         slice_cred_names = []
827         for cred in creds:
828             slice_cred_hrn = Credential(cred=cred).get_gid_object().get_hrn()
829             slicename = Xrn(xrn=slice_cred_hrn).iotlab_slicename()
830             logger.debug("IOTLABDRIVER.PY \t check_sliver_credentials slicename %s \r\n \r\n"
831                      % (slicename))
832             slice_cred_names.append(slicename)
833
834         # look up slice name of slivers listed in urns arg
835
836         slice_ids = []
837         for urn in urns:
838             sliver_id_parts = Xrn(xrn=urn).get_sliver_id_parts()
839             try:
840                 slice_ids.append(int(sliver_id_parts[0]))
841             except ValueError:
842                 pass
843
844         if not slice_ids:
845              raise Forbidden("sliver urn not provided")
846
847         slices = self.testbed_shell.GetSlices(slice_ids)
848         sliver_names = [single_slice['name'] for single_slice in slices]
849
850         # make sure we have a credential for every specified sliver ierd
851         for sliver_name in sliver_names:
852             if sliver_name not in slice_cred_names:
853                 msg = "Valid credential not found for target: %s" % sliver_name
854                 raise Forbidden(msg)
855
856     ########################################
857     ########## aggregate oriented
858     ########################################
859
860
861     def testbed_name (self): return "iotlab"
862
863     def aggregate_version (self):
864         return {}
865
866     # first 2 args are None in case of resource discovery
867     def list_resources (self, version=None, options={}):
868         aggregate = IotlabAggregate(self)
869         rspec =  aggregate.list_resources(version=version, options=options)
870         return rspec
871
872     def describe(self, urns, version, options={}):
873         aggregate = IotlabAggregate(self)
874         return aggregate.describe(urns, version=version, options=options)
875
876     def status (self, urns, options={}):
877         aggregate = IotlabAggregate(self)
878         desc =  aggregate.describe(urns, version='GENI 3')
879         status = {'geni_urn': desc['geni_urn'],
880                   'geni_slivers': desc['geni_slivers']}
881         return status
882
883
884     def allocate (self, urn, rspec_string, expiration, options={}):
885         xrn = Xrn(urn)
886         aggregate = IotlabAggregate(self)
887
888         slices = IotlabSlices(self)
889         peer = slices.get_peer(xrn.get_hrn())
890         sfa_peer = slices.get_sfa_peer(xrn.get_hrn())
891
892
893         slice_record = None
894         users = options.get('geni_users', [])
895
896         sfa_users = options.get('sfa_users', [])
897         if sfa_users:
898             slice_record = sfa_users[0].get('slice_record', [])
899         logger.debug("IOTLABDRIVER.PY \t ===============allocate \t\
900                             \r\n \r\n options %s slice_record %s" % (options,slice_record))
901         # parse rspec
902         rspec = RSpec(rspec_string)
903         # requested_attributes = rspec.version.get_slice_attributes()
904
905         # ensure site record exists
906         # site = slices.verify_site(xrn.hrn, slice_record, peer, sfa_peer, options=options)
907         # ensure slice record exists
908
909         current_slice = slices.verify_slice(xrn.hrn, slice_record, sfa_peer)
910         logger.debug("IOTLABDRIVER.PY \t ===============allocate \t\
911                             \r\n \r\n  current_slice %s" % (current_slice))
912         # ensure person records exists
913
914         # oui c'est degueulasse, le slice_record se retrouve modifie
915         # dans la methode avec les infos du user, els infos sont propagees
916         # dans verify_slice_leases
917         persons = slices.verify_persons(xrn.hrn, slice_record, users, options=options)
918         # ensure slice attributes exists
919         # slices.verify_slice_attributes(slice, requested_attributes, options=options)
920
921         # add/remove slice from nodes
922         requested_xp_dict = self._process_requested_xp_dict(rspec)
923
924         logger.debug("IOTLABDRIVER.PY \tallocate  requested_xp_dict %s "
925                      % (requested_xp_dict))
926         request_nodes = rspec.version.get_nodes_with_slivers()
927         nodes_list = []
928         for start_time in requested_xp_dict:
929             lease = requested_xp_dict[start_time]
930             for hostname in lease['hostname']:
931                 nodes_list.append(hostname)
932
933         # nodes = slices.verify_slice_nodes(slice_record,request_nodes, peer)
934         logger.debug("IOTLABDRIVER.PY \tallocate  nodes_list %s slice_record %s"
935                      % (nodes_list, slice_record))
936
937         # add/remove leases
938         rspec_requested_leases = rspec.version.get_leases()
939         leases = slices.verify_slice_leases(slice_record, requested_xp_dict, peer)
940         logger.debug("IOTLABDRIVER.PY \tallocate leases  %s rspec_requested_leases %s"
941                      % (leases,rspec_requested_leases))
942          # update sliver allocations
943         for hostname in nodes_list:
944             client_id = hostname
945             node_urn = xrn_object(self.testbed_shell.root_auth, hostname).urn
946             component_id = node_urn
947             slice_urn = current_slice['reg-urn']
948             for lease in leases:
949                 if hostname in lease['reserved_nodes']:
950                     index = lease['reserved_nodes'].index(hostname)
951                     sliver_hrn = '%s.%s-%s' % (self.hrn, lease['lease_id'],
952                                    lease['resource_ids'][index] )
953             sliver_id = Xrn(sliver_hrn, type='sliver').urn
954             record = SliverAllocation(sliver_id=sliver_id, client_id=client_id,
955                                       component_id=component_id,
956                                       slice_urn = slice_urn,
957                                       allocation_state='geni_allocated')
958
959             logger.debug("\r\n \
960                 ===============================IOTLABDRIVER.PY \tallocate  sliver_id %s slice_urn %s \r\n"
961                      % (sliver_id,slice_urn))
962             record.sync(self.api.dbsession())
963         # add/remove links links
964         # slices.verify_slice_links(slice, rspec.version.get_link_requests(), nodes)
965
966
967
968         return aggregate.describe([xrn.get_urn()], version=rspec.version)
969
970     def provision(self, urns, options={}):
971         # update users
972         slices = IotlabSlices(self)
973         aggregate = IotlabAggregate(self)
974         slivers = aggregate.get_slivers(urns)
975         current_slice = slivers[0]
976         peer = slices.get_peer(current_slice['hrn'])
977         sfa_peer = slices.get_sfa_peer(current_slice['hrn'])
978         users = options.get('geni_users', [])
979         # persons = slices.verify_persons(current_slice['hrn'],
980             # current_slice, users, peer, sfa_peer, options=options)
981         # slices.handle_peer(None, None, persons, peer)
982         # update sliver allocation states and set them to geni_provisioned
983         sliver_ids = [sliver['sliver_id'] for sliver in slivers]
984         dbsession =self.api.dbsession()
985         SliverAllocation.set_allocations(sliver_ids, 'geni_provisioned',dbsession)
986         version_manager = VersionManager()
987         rspec_version = version_manager.get_version(options['geni_rspec_version'])
988         return self.describe(urns, rspec_version, options=options)