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