Create new cortexlab forlder, for the Cortex-lab testbed,
[sfa.git] / sfa / cortexlab / cortexlabdriver.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.alchemy import dbsession
7 from sfa.storage.model import RegRecord
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.util.xrn import Xrn, hrn_to_urn, get_authority
14
15 from sfa.iotlab.iotlabaggregate import IotlabAggregate, iotlab_xrn_to_hostname
16 from sfa.iotlab.iotlabslices import IotlabSlices
17
18
19 from sfa.iotlab.iotlabapi import IotlabTestbedAPI
20
21
22 class CortexlabDriver(Driver):
23     """ Cortexlab 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 scheduler to book the nodes).
27
28     .. seealso::: Driver class
29
30     """
31     def __init__(self, config):
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, config)
42         self.config = config
43         self.iotlab_api = IotlabTestbedAPI(config)
44         self.cache = None
45
46     def augment_records_with_testbed_info(self, record_list):
47         """
48
49         Adds specific testbed info to the records.
50
51         :param record_list: list of sfa dictionaries records
52         :type record_list: list
53         :returns: list of records with extended information in each record
54         :rtype: list
55
56         """
57         return self.fill_record_info(record_list)
58
59     def fill_record_info(self, record_list):
60         """
61
62         For each SFA record, fill in the iotlab specific and SFA specific
63             fields in the record.
64
65         :param record_list: list of sfa dictionaries records
66         :type record_list: list
67         :returns: list of records with extended information in each record
68         :rtype: list
69
70         .. warning:: Should not be modifying record_list directly because modi
71             fication are kept outside the method's scope. Howerver, there is no
72             other way to do it given the way it's called in registry manager.
73
74         """
75
76         logger.debug("IOTLABDRIVER \tfill_record_info records %s "
77                      % (record_list))
78         if not isinstance(record_list, list):
79             record_list = [record_list]
80
81         try:
82             for record in record_list:
83
84                 if str(record['type']) == 'node':
85                     # look for node info using GetNodes
86                     # the record is about one node only
87                     filter_dict = {'hrn': [record['hrn']]}
88                     node_info = self.iotlab_api.GetNodes(filter_dict)
89                     # the node_info is about one node only, but it is formatted
90                     # as a list
91                     record.update(node_info[0])
92                     logger.debug("IOTLABDRIVER.PY \t \
93                                   fill_record_info NODE" % (record))
94
95                 #If the record is a SFA slice record, then add information
96                 #about the user of this slice. This kind of
97                 #information is in the Iotlab's DB.
98                 if str(record['type']) == 'slice':
99                     if 'reg_researchers' in record and isinstance(record
100                                                             ['reg_researchers'],
101                                                             list):
102                         record['reg_researchers'] = \
103                             record['reg_researchers'][0].__dict__
104                         record.update(
105                             {'PI': [record['reg_researchers']['hrn']],
106                              'researcher': [record['reg_researchers']['hrn']],
107                              'name': record['hrn'],
108                              'oar_job_id': [],
109                              'node_ids': [],
110                              'person_ids': [record['reg_researchers']
111                                             ['record_id']],
112                                 # For client_helper.py compatibility
113                              'geni_urn': '',
114                                 # For client_helper.py compatibility
115                              'keys': '',
116                                 # For client_helper.py compatibility
117                              'key_ids': ''})
118
119                     #Get iotlab slice record and oar job id if any.
120                     recslice_list = self.iotlab_api.GetSlices(
121                         slice_filter=str(record['hrn']),
122                         slice_filter_type='slice_hrn')
123
124                     logger.debug("IOTLABDRIVER \tfill_record_info \
125                         TYPE SLICE RECUSER record['hrn'] %s record['oar_job_id']\
126                          %s " % (record['hrn'], record['oar_job_id']))
127                     del record['reg_researchers']
128                     try:
129                         for rec in recslice_list:
130                             logger.debug("IOTLABDRIVER\r\n  \t  \
131                             fill_record_info oar_job_id %s "
132                                          % (rec['oar_job_id']))
133
134                             record['node_ids'] = [self.iotlab_api.root_auth +
135                                                   '.' + hostname for hostname
136                                                   in rec['node_ids']]
137                     except KeyError:
138                         pass
139
140                     logger.debug("IOTLABDRIVER.PY \t fill_record_info SLICE \
141                                     recslice_list  %s \r\n \t RECORD %s \r\n \
142                                     \r\n" % (recslice_list, record))
143
144                 if str(record['type']) == 'user':
145                     #The record is a SFA user record.
146                     #Get the information about his slice from Iotlab's DB
147                     #and add it to the user record.
148                     recslice_list = self.iotlab_api.GetSlices(
149                         slice_filter=record['record_id'],
150                         slice_filter_type='record_id_user')
151
152                     logger.debug("IOTLABDRIVER.PY \t fill_record_info \
153                         TYPE USER recslice_list %s \r\n \t RECORD %s \r\n"
154                                  % (recslice_list, record))
155                     #Append slice record in records list,
156                     #therefore fetches user and slice info again(one more loop)
157                     #Will update PIs and researcher for the slice
158
159                     recuser = recslice_list[0]['reg_researchers']
160                     logger.debug("IOTLABDRIVER.PY \t fill_record_info USER  \
161                                             recuser %s \r\n \r\n" % (recuser))
162                     recslice = {}
163                     recslice = recslice_list[0]
164                     recslice.update(
165                         {'PI': [recuser['hrn']],
166                          'researcher': [recuser['hrn']],
167                          'name': record['hrn'],
168                          'node_ids': [],
169                          'oar_job_id': [],
170                          'person_ids': [recuser['record_id']]})
171                     try:
172                         for rec in recslice_list:
173                             recslice['oar_job_id'].append(rec['oar_job_id'])
174                     except KeyError:
175                         pass
176
177                     recslice.update({'type': 'slice',
178                                      'hrn': recslice_list[0]['hrn']})
179
180                     #GetPersons takes [] as filters
181                     user_iotlab = self.iotlab_api.GetPersons([record])
182
183                     record.update(user_iotlab[0])
184                     #For client_helper.py compatibility
185                     record.update(
186                         {'geni_urn': '',
187                          'keys': '',
188                          'key_ids': ''})
189                     record_list.append(recslice)
190
191                     logger.debug("IOTLABDRIVER.PY \t \
192                         fill_record_info ADDING SLICE\
193                         INFO TO USER records %s" % (record_list))
194
195         except TypeError, error:
196             logger.log_exc("IOTLABDRIVER \t fill_record_info  EXCEPTION %s"
197                            % (error))
198
199         return record_list
200
201     def sliver_status(self, slice_urn, slice_hrn):
202         """
203         Receive a status request for slice named urn/hrn
204             urn:publicid:IDN+iotlab+nturro_slice hrn iotlab.nturro_slice
205             shall return a structure as described in
206             http://groups.geni.net/geni/wiki/GAPI_AM_API_V2#SliverStatus
207             NT : not sure if we should implement this or not, but used by sface.
208
209         :param slice_urn: slice urn
210         :type slice_urn: string
211         :param slice_hrn: slice hrn
212         :type slice_hrn: string
213
214         """
215
216         #First get the slice with the slice hrn
217         slice_list = self.iotlab_api.GetSlices(slice_filter=slice_hrn,
218                                                slice_filter_type='slice_hrn')
219
220         if len(slice_list) == 0:
221             raise SliverDoesNotExist("%s  slice_hrn" % (slice_hrn))
222
223         #Used for fetching the user info witch comes along the slice info
224         one_slice = slice_list[0]
225
226         #Make a list of all the nodes hostnames  in use for this slice
227         slice_nodes_list = []
228         slice_nodes_list = one_slice['node_ids']
229         #Get all the corresponding nodes details
230         nodes_all = self.iotlab_api.GetNodes(
231             {'hostname': slice_nodes_list},
232             ['node_id', 'hostname', 'site', 'boot_state'])
233         nodeall_byhostname = dict([(one_node['hostname'], one_node)
234                                   for one_node in nodes_all])
235
236         for single_slice in slice_list:
237               #For compatibility
238             top_level_status = 'empty'
239             result = {}
240             result.fromkeys(
241                 ['geni_urn', 'geni_error', 'iotlab_login', 'geni_status',
242                  'geni_resources'], None)
243             # result.fromkeys(\
244             #     ['geni_urn','geni_error', 'pl_login','geni_status',
245             # 'geni_resources'], None)
246             # result['pl_login'] = one_slice['reg_researchers'][0].hrn
247             result['iotlab_login'] = one_slice['user']
248             logger.debug("Slabdriver - sliver_status Sliver status \
249                             urn %s hrn %s single_slice  %s \r\n "
250                          % (slice_urn, slice_hrn, single_slice))
251
252             if 'node_ids' not in single_slice:
253                 #No job in the slice
254                 result['geni_status'] = top_level_status
255                 result['geni_resources'] = []
256                 return result
257
258             top_level_status = 'ready'
259
260             #A job is running on Iotlab for this slice
261             # report about the local nodes that are in the slice only
262
263             result['geni_urn'] = slice_urn
264
265             resources = []
266             for node_hostname in single_slice['node_ids']:
267                 res = {}
268                 res['iotlab_hostname'] = node_hostname
269                 res['iotlab_boot_state'] = \
270                     nodeall_byhostname[node_hostname]['boot_state']
271
272                 #res['pl_hostname'] = node['hostname']
273                 #res['pl_boot_state'] = \
274                             #nodeall_byhostname[node['hostname']]['boot_state']
275                 #res['pl_last_contact'] = strftime(self.time_format, \
276                                                     #gmtime(float(timestamp)))
277                 sliver_id = Xrn(
278                     slice_urn, type='slice',
279                     id=nodeall_byhostname[node_hostname]['node_id']).urn
280
281                 res['geni_urn'] = sliver_id
282                 #node_name  = node['hostname']
283                 if nodeall_byhostname[node_hostname]['boot_state'] == 'Alive':
284
285                     res['geni_status'] = 'ready'
286                 else:
287                     res['geni_status'] = 'failed'
288                     top_level_status = 'failed'
289
290                 res['geni_error'] = ''
291
292                 resources.append(res)
293
294             result['geni_status'] = top_level_status
295             result['geni_resources'] = resources
296             logger.debug("IOTLABDRIVER \tsliver_statusresources %s res %s "
297                          % (resources, res))
298             return result
299
300     @staticmethod
301     def get_user_record(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 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.iotlab_api.root_auth:
369                     single_requested_lease['hostname'] = \
370                         iotlab_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.iotlab_api.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_job_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_job_dict:
407                 if isinstance(lease['hostname'], str):
408                     lease['hostname'] = [lease['hostname']]
409
410                 requested_job_dict[lease['start_time']] = lease
411
412             else:
413                 job_lease = requested_job_dict[lease['start_time']]
414                 if lease['duration'] == job_lease['duration']:
415                     job_lease['hostname'].append(lease['hostname'])
416
417         return requested_job_dict
418
419     def _process_requested_jobs(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_jobs \
431             requested_lease_list  %s" % (requested_lease_list))
432         job_dict = self._group_leases_by_start_time(requested_lease_list)
433         logger.debug("IOTLABDRIVER _process_requested_jobs  job_dict\
434         %s" % (job_dict))
435
436         return job_dict
437
438     def create_sliver(self, slice_urn, slice_hrn, creds, rspec_string,
439                       users, options):
440         """Answer to CreateSliver.
441
442         Creates the leases and slivers for the users from the information
443             found in the rspec string.
444             Launch experiment on OAR if the requested leases is valid. Delete
445             no longer requested leases.
446
447
448         :param creds: user's credentials
449         :type creds: string
450         :param users: user record list
451         :type users: list
452         :param options:
453         :type options:
454
455         :returns: a valid Rspec for the slice which has just been
456             modified.
457         :rtype: RSpec
458
459
460         """
461         aggregate = IotlabAggregate(self)
462
463         slices = IotlabSlices(self)
464         peer = slices.get_peer(slice_hrn)
465         sfa_peer = slices.get_sfa_peer(slice_hrn)
466         slice_record = None
467
468         if not isinstance(creds, list):
469             creds = [creds]
470
471         if users:
472             slice_record = users[0].get('slice_record', {})
473             logger.debug("IOTLABDRIVER.PY \t ===============create_sliver \t\
474                             creds %s \r\n \r\n users %s"
475                          % (creds, users))
476             slice_record['user'] = {'keys': users[0]['keys'],
477                                     'email': users[0]['email'],
478                                     'hrn': slice_record['reg-researchers'][0]}
479         # parse rspec
480         rspec = RSpec(rspec_string)
481         logger.debug("IOTLABDRIVER.PY \t create_sliver \trspec.version \
482                      %s slice_record %s users %s"
483                      % (rspec.version, slice_record, users))
484
485         # ensure site record exists?
486         # ensure slice record exists
487         #Removed options in verify_slice SA 14/08/12
488         #Removed peer record in  verify_slice SA 18/07/13
489         sfa_slice = slices.verify_slice(slice_hrn, slice_record, sfa_peer)
490
491         # ensure person records exists
492         #verify_persons returns added persons but the return value
493         #is not used
494         #Removed peer record and sfa_peer in  verify_persons SA 18/07/13
495         slices.verify_persons(slice_hrn, sfa_slice, users, options=options)
496         #requested_attributes returned by rspec.version.get_slice_attributes()
497         #unused, removed SA 13/08/12
498         #rspec.version.get_slice_attributes()
499
500         logger.debug("IOTLABDRIVER.PY create_sliver slice %s " % (sfa_slice))
501
502         # add/remove slice from nodes
503
504         #requested_slivers = [node.get('component_id') \
505                     #for node in rspec.version.get_nodes_with_slivers()\
506                     #if node.get('authority_id') is self.iotlab_api.root_auth]
507         #l = [ node for node in rspec.version.get_nodes_with_slivers() ]
508         #logger.debug("SLADRIVER \tcreate_sliver requested_slivers \
509                                     #requested_slivers %s  listnodes %s" \
510                                     #%(requested_slivers,l))
511         #verify_slice_nodes returns nodes, but unused here. Removed SA 13/08/12.
512         #slices.verify_slice_nodes(sfa_slice, requested_slivers, peer)
513
514         requested_job_dict = self._process_requested_jobs(rspec)
515
516         logger.debug("IOTLABDRIVER.PY \tcreate_sliver  requested_job_dict %s "
517                      % (requested_job_dict))
518         #verify_slice_leases returns the leases , but the return value is unused
519         #here. Removed SA 13/08/12
520         slices.verify_slice_leases(sfa_slice,
521                                    requested_job_dict, peer)
522
523         return aggregate.get_rspec(slice_xrn=slice_urn,
524                                    login=sfa_slice['login'],
525                                    version=rspec.version)
526
527     def delete_sliver(self, slice_urn, slice_hrn, creds, options):
528         """
529         Deletes the lease associated with the slice hrn and the credentials
530             if the slice belongs to iotlab. Answer to DeleteSliver.
531
532         :param slice_urn: urn of the slice
533         :param slice_hrn: name of the slice
534         :param creds: slice credenials
535         :type slice_urn: string
536         :type slice_hrn: string
537         :type creds: ? unused
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
549         sfa_slice_list = self.iotlab_api.GetSlices(
550             slice_filter=slice_hrn,
551             slice_filter_type='slice_hrn')
552
553         if not sfa_slice_list:
554             return 1
555
556         #Delete all leases in the slice
557         for sfa_slice in sfa_slice_list:
558             logger.debug("IOTLABDRIVER.PY delete_sliver slice %s" % (sfa_slice))
559             slices = IotlabSlices(self)
560             # determine if this is a peer slice
561
562             peer = slices.get_peer(slice_hrn)
563
564             logger.debug("IOTLABDRIVER.PY delete_sliver peer %s \
565                 \r\n \t sfa_slice %s " % (peer, sfa_slice))
566             try:
567                 self.iotlab_api.DeleteSliceFromNodes(sfa_slice)
568                 return True
569             except:
570                 return False
571
572     def list_resources (self, slice_urn, slice_hrn, creds, options):
573         """
574
575         List resources from the iotlab aggregate and returns a Rspec
576             advertisement with resources found when slice_urn and slice_hrn are
577             None (in case of resource discovery).
578             If a slice hrn and urn are provided, list experiment's slice
579             nodes in a rspec format. Answer to ListResources.
580             Caching unused.
581
582         :param slice_urn: urn of the slice
583         :param slice_hrn: name of the slice
584         :param creds: slice credenials
585         :type slice_urn: string
586         :type slice_hrn: string
587         :type creds: ? unused
588         :param options: options used when listing resources (list_leases, info,
589             geni_available)
590         :returns: rspec string in xml
591         :rtype: string
592
593         .. note:: creds are unused
594         """
595
596         #cached_requested = options.get('cached', True)
597
598         version_manager = VersionManager()
599         # get the rspec's return format from options
600         rspec_version = \
601             version_manager.get_version(options.get('geni_rspec_version'))
602         version_string = "rspec_%s" % (rspec_version)
603
604         #panos adding the info option to the caching key (can be improved)
605         if options.get('info'):
606             version_string = version_string + "_" + \
607                 options.get('info', 'default')
608
609         # Adding the list_leases option to the caching key
610         if options.get('list_leases'):
611             version_string = version_string + "_" + \
612             options.get('list_leases', 'default')
613
614         # Adding geni_available to caching key
615         if options.get('geni_available'):
616             version_string = version_string + "_" + \
617                 str(options.get('geni_available'))
618
619         # look in cache first
620         #if cached_requested and self.cache and not slice_hrn:
621             #rspec = self.cache.get(version_string)
622             #if rspec:
623                 #logger.debug("IotlabDriver.ListResources: \
624                                     #returning cached advertisement")
625                 #return rspec
626
627         #panos: passing user-defined options
628         aggregate = IotlabAggregate(self)
629
630         rspec = aggregate.get_rspec(slice_xrn=slice_urn,
631                                     version=rspec_version, options=options)
632
633         # cache the result
634         #if self.cache and not slice_hrn:
635             #logger.debug("Iotlab.ListResources: stores advertisement in cache")
636             #self.cache.add(version_string, rspec)
637
638         return rspec
639
640
641     def list_slices(self, creds, options):
642         """Answer to ListSlices.
643
644         List slices belonging to iotlab, returns slice urns list.
645             No caching used. Options unused but are defined in the SFA method
646             api prototype.
647
648         :returns: slice urns list
649         :rtype: list
650
651         .. note:: creds are unused
652         """
653         # look in cache first
654         #if self.cache:
655             #slices = self.cache.get('slices')
656             #if slices:
657                 #logger.debug("PlDriver.list_slices returns from cache")
658                 #return slices
659
660         # get data from db
661
662         slices = self.iotlab_api.GetSlices()
663         logger.debug("IOTLABDRIVER.PY \tlist_slices hrn %s \r\n \r\n"
664                      % (slices))
665         slice_hrns = [iotlab_slice['hrn'] for iotlab_slice in slices]
666
667         slice_urns = [hrn_to_urn(slice_hrn, 'slice')
668                       for slice_hrn in slice_hrns]
669
670         # cache the result
671         #if self.cache:
672             #logger.debug ("IotlabDriver.list_slices stores value in cache")
673             #self.cache.add('slices', slice_urns)
674
675         return slice_urns
676
677
678     def register(self, sfa_record, hrn, pub_key):
679         """
680         Adding new user, slice, node or site should not be handled
681             by SFA.
682
683         ..warnings:: should not be used. Different components are in charge of
684             doing this task. Adding nodes = OAR
685             Adding users = LDAP Iotlab
686             Adding slice = Import from LDAP users
687             Adding site = OAR
688
689         :param sfa_record: record provided by the client of the
690             Register API call.
691         :type sfa_record: dict
692         :param pub_key: public key of the user
693         :type pub_key: string
694
695         .. note:: DOES NOTHING. Returns -1.
696
697         """
698         return -1
699
700
701     def update(self, old_sfa_record, new_sfa_record, hrn, new_key):
702         """
703         No site or node record update allowed in Iotlab. The only modifications
704         authorized here are key deletion/addition on an existing user and
705         password change. On an existing user, CAN NOT BE MODIFIED: 'first_name',
706         'last_name', 'email'. DOES NOT EXIST IN SENSLAB: 'phone', 'url', 'bio',
707         'title', 'accepted_aup'. A slice is bound to its user, so modifying the
708         user's ssh key should nmodify the slice's GID after an import procedure.
709
710         :param old_sfa_record: what is in the db for this hrn
711         :param new_sfa_record: what was passed to the update call
712         :param new_key: the new user's public key
713         :param hrn: the user's sfa hrn
714         :type old_sfa_record: dict
715         :type new_sfa_record: dict
716         :type new_key: string
717         :type hrn: string
718
719         TODO: needs review
720         .. seealso:: update in driver.py.
721
722         """
723         pointer = old_sfa_record['pointer']
724         old_sfa_record_type = old_sfa_record['type']
725
726         # new_key implemented for users only
727         if new_key and old_sfa_record_type not in ['user']:
728             raise UnknownSfaType(old_sfa_record_type)
729
730         if old_sfa_record_type == "user":
731             update_fields = {}
732             all_fields = new_sfa_record
733             for key in all_fields.keys():
734                 if key in ['key', 'password']:
735                     update_fields[key] = all_fields[key]
736
737             if new_key:
738                 # must check this key against the previous one if it exists
739                 persons = self.iotlab_api.GetPersons([old_sfa_record])
740                 person = persons[0]
741                 keys = [person['pkey']]
742                 #Get all the person's keys
743                 keys_dict = self.iotlab_api.GetKeys(keys)
744
745                 # Delete all stale keys, meaning the user has only one key
746                 #at a time
747                 #TODO: do we really want to delete all the other keys?
748                 #Is this a problem with the GID generation to have multiple
749                 #keys? SA 30/05/13
750                 key_exists = False
751                 if key in keys_dict:
752                     key_exists = True
753                 else:
754                     #remove all the other keys
755                     for key in keys_dict:
756                         self.iotlab_api.DeleteKey(person, key)
757                     self.iotlab_api.AddPersonKey(
758                         person, {'sshPublicKey': person['pkey']},
759                         {'sshPublicKey': new_key})
760         return True
761
762     def remove(self, sfa_record):
763         """
764
765         Removes users only. Mark the user as disabled in LDAP. The user and his
766         slice are then deleted from the db by running an import on the registry.
767
768         :param sfa_record: record is the existing sfa record in the db
769         :type sfa_record: dict
770
771         ..warning::As fas as the slice is concerned, here only the leases are
772             removed from the slice. The slice is record itself is not removed
773             from the db.
774
775         TODO: needs review
776
777         TODO : REMOVE SLICE FROM THE DB AS WELL? SA 14/05/2013,
778
779         TODO: return boolean for the slice part
780         """
781         sfa_record_type = sfa_record['type']
782         hrn = sfa_record['hrn']
783         if sfa_record_type == 'user':
784
785             #get user from iotlab ldap
786             person = self.iotlab_api.GetPersons(sfa_record)
787             #No registering at a given site in Iotlab.
788             #Once registered to the LDAP, all iotlab sites are
789             #accesible.
790             if person:
791                 #Mark account as disabled in ldap
792                 return self.iotlab_api.DeletePerson(sfa_record)
793
794         elif sfa_record_type == 'slice':
795             if self.iotlab_api.GetSlices(slice_filter=hrn,
796                                          slice_filter_type='slice_hrn'):
797                 ret = self.iotlab_api.DeleteSlice(sfa_record)
798             return True