Cleaner version of cortexlab.
[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.cortexlab.cortexlabaggregate import CortexlabAggregate, \
16             cortexlab_xrn_to_hostname
17
18 from sfa.iotlab.iotlabslices import CortexlabSlices
19
20 from sfa.cortexlab.cortexlabapi import CortexlabTestbedAPI
21
22
23 class CortexlabDriver(Driver):
24     """ Cortexlab Driver class inherited from Driver generic class.
25
26     Contains methods compliant with the SFA standard and the testbed
27         infrastructure (calls to LDAP and scheduler to book the nodes).
28
29     .. seealso::: Driver class
30
31     """
32     def __init__(self, config):
33         """
34
35         Sets the iotlab SFA config parameters,
36             instanciates the testbed api and the iotlab database.
37
38         :param config: iotlab SFA configuration object
39         :type config: Config object
40
41         """
42         Driver.__init__(self, config)
43         self.config = config
44         self.cortexlab_api = CortexlabTestbedAPI(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("CORTEXLABDRIVER \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.cortexlab_api.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("CORTEXLABDRIVER.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                              'experiment_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.cortexlab_api.GetSlices(
122                         slice_filter=str(record['hrn']),
123                         slice_filter_type='slice_hrn')
124
125                     logger.debug("CORTEXLABDRIVER \tfill_record_info \
126                         TYPE SLICE RECUSER record['hrn'] %s record['experiment_id']\
127                          %s " % (record['hrn'], record['experiment_id']))
128                     del record['reg_researchers']
129                     try:
130                         for rec in recslice_list:
131                             logger.debug("CORTEXLABDRIVER\r\n  \t  \
132                             fill_record_info experiment_id %s "
133                                          % (rec['experiment_id']))
134
135                             record['node_ids'] = [self.cortexlab_api.root_auth +
136                                                   '.' + hostname for hostname
137                                                   in rec['node_ids']]
138                     except KeyError:
139                         pass
140
141                     logger.debug("CORTEXLABDRIVER.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.cortexlab_api.GetSlices(
150                         slice_filter=record['record_id'],
151                         slice_filter_type='record_id_user')
152
153                     logger.debug("CORTEXLABDRIVER.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("CORTEXLABDRIVER.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                          'experiment_id': [],
171                          'person_ids': [recuser['record_id']]})
172                     try:
173                         for rec in recslice_list:
174                             recslice['experiment_id'].append(rec['experiment_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_cortexlab = self.cortexlab_api.GetPersons([record])
183
184                     record.update(user_cortexlab[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("CORTEXLABDRIVER.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("CORTEXLABDRIVER \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.cortexlab_api.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.cortexlab_api.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', 'cortexlab_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['cortexlab_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['cortexlab_hostname'] = node_hostname
270                 res['cortexlab_boot_state'] = \
271                     nodeall_byhostname[node_hostname]['boot_state']
272
273                 sliver_id = Xrn(
274                     slice_urn, type='slice',
275                     id=nodeall_byhostname[node_hostname]['node_id']).urn
276
277                 res['geni_urn'] = sliver_id
278                 #node_name  = node['hostname']
279                 if nodeall_byhostname[node_hostname]['boot_state'] == 'Alive':
280
281                     res['geni_status'] = 'ready'
282                 else:
283                     res['geni_status'] = 'failed'
284                     top_level_status = 'failed'
285
286                 res['geni_error'] = ''
287
288                 resources.append(res)
289
290             result['geni_status'] = top_level_status
291             result['geni_resources'] = resources
292             logger.debug("CORTEXLABDRIVER \tsliver_statusresources %s res %s "
293                          % (resources, res))
294             return result
295
296     @staticmethod
297     def get_user_record(hrn):
298         """
299
300         Returns the user record based on the hrn from the SFA DB .
301
302         :param hrn: user's hrn
303         :type hrn: string
304         :returns: user record from SFA database
305         :rtype: RegUser
306
307         """
308         return dbsession.query(RegRecord).filter_by(hrn=hrn).first()
309
310     def testbed_name(self):
311         """
312
313         Returns testbed's name.
314         :returns: testbed authority name.
315         :rtype: string
316
317         """
318         return self.hrn
319
320     # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
321     def aggregate_version(self):
322         """
323
324         Returns the testbed's supported rspec advertisement and request
325         versions.
326         :returns: rspec versions supported ad a dictionary.
327         :rtype: dict
328
329         """
330         version_manager = VersionManager()
331         ad_rspec_versions = []
332         request_rspec_versions = []
333         for rspec_version in version_manager.versions:
334             if rspec_version.content_type in ['*', 'ad']:
335                 ad_rspec_versions.append(rspec_version.to_dict())
336             if rspec_version.content_type in ['*', 'request']:
337                 request_rspec_versions.append(rspec_version.to_dict())
338         return {
339             'testbed': self.testbed_name(),
340             'geni_request_rspec_versions': request_rspec_versions,
341             'geni_ad_rspec_versions': ad_rspec_versions}
342
343     def _get_requested_leases_list(self, rspec):
344         """
345         Process leases in rspec depending on the rspec version (format)
346             type. Find the lease requests in the rspec and creates
347             a lease request list with the mandatory information ( nodes,
348             start time and duration) of the valid leases (duration above or
349             equal to the iotlab experiment minimum duration).
350
351         :param rspec: rspec request received.
352         :type rspec: RSpec
353         :returns: list of lease requests found in the rspec
354         :rtype: list
355         """
356         requested_lease_list = []
357         for lease in rspec.version.get_leases():
358             single_requested_lease = {}
359             logger.debug("CORTEXLABDRIVER.PY \t \
360                 _get_requested_leases_list lease %s " % (lease))
361
362             if not lease.get('lease_id'):
363                 if get_authority(lease['component_id']) == \
364                         self.cortexlab_api.root_auth:
365                     single_requested_lease['hostname'] = \
366                         cortexlab_xrn_to_hostname(\
367                             lease.get('component_id').strip())
368                     single_requested_lease['start_time'] = \
369                         lease.get('start_time')
370                     single_requested_lease['duration'] = lease.get('duration')
371                     #Check the experiment's duration is valid before adding
372                     #the lease to the requested leases list
373                     duration_in_seconds = \
374                         int(single_requested_lease['duration'])
375                     if duration_in_seconds >= self.cortexlab_api.GetMinExperimentDurationInGranularity():
376                         requested_lease_list.append(single_requested_lease)
377
378         return requested_lease_list
379
380     @staticmethod
381     def _group_leases_by_start_time(requested_lease_list):
382         """
383         Create dict of leases by start_time, regrouping nodes reserved
384             at the same time, for the same amount of time so as to
385             define one job on OAR.
386
387         :param requested_lease_list: list of leases
388         :type requested_lease_list: list
389         :returns: Dictionary with key = start time, value = list of leases
390             with the same start time.
391         :rtype: dictionary
392
393         """
394
395         requested_xp_dict = {}
396         for lease in requested_lease_list:
397
398             #In case it is an asap experiment start_time is empty
399             if lease['start_time'] == '':
400                 lease['start_time'] = '0'
401
402             if lease['start_time'] not in requested_xp_dict:
403                 if isinstance(lease['hostname'], str):
404                     lease['hostname'] = [lease['hostname']]
405
406                 requested_xp_dict[lease['start_time']] = lease
407
408             else:
409                 job_lease = requested_xp_dict[lease['start_time']]
410                 if lease['duration'] == job_lease['duration']:
411                     job_lease['hostname'].append(lease['hostname'])
412
413         return requested_xp_dict
414
415     def _process_requested_xp_dict(self, rspec):
416         """
417         Turns the requested leases and information into a dictionary
418             of requested jobs, grouped by starting time.
419
420         :param rspec: RSpec received
421         :type rspec : RSpec
422         :rtype: dictionary
423
424         """
425         requested_lease_list = self._get_requested_leases_list(rspec)
426         logger.debug("CORTEXLABDRIVER _process_requested_xp_dict \
427             requested_lease_list  %s" % (requested_lease_list))
428         xp_dict = self._group_leases_by_start_time(requested_lease_list)
429         logger.debug("CORTEXLABDRIVER _process_requested_xp_dict  xp_dict\
430         %s" % (xp_dict))
431
432         return xp_dict
433
434     def create_sliver(self, slice_urn, slice_hrn, creds, rspec_string,
435                       users, options):
436         """Answer to CreateSliver.
437
438         Creates the leases and slivers for the users from the information
439             found in the rspec string.
440             Launch experiment on OAR if the requested leases is valid. Delete
441             no longer requested leases.
442
443
444         :param creds: user's credentials
445         :type creds: string
446         :param users: user record list
447         :type users: list
448         :param options:
449         :type options:
450
451         :returns: a valid Rspec for the slice which has just been
452             modified.
453         :rtype: RSpec
454
455
456         """
457         aggregate = CortexlabAggregate(self)
458
459         slices = CortexlabSlices(self)
460         peer = slices.get_peer(slice_hrn)
461         sfa_peer = slices.get_sfa_peer(slice_hrn)
462         slice_record = None
463
464         if not isinstance(creds, list):
465             creds = [creds]
466
467         if users:
468             slice_record = users[0].get('slice_record', {})
469             logger.debug("CORTEXLABDRIVER.PY \t ===============create_sliver \t\
470                             creds %s \r\n \r\n users %s"
471                          % (creds, users))
472             slice_record['user'] = {'keys': users[0]['keys'],
473                                     'email': users[0]['email'],
474                                     'hrn': slice_record['reg-researchers'][0]}
475         # parse rspec
476         rspec = RSpec(rspec_string)
477         logger.debug("CORTEXLABDRIVER.PY \t create_sliver \trspec.version \
478                      %s slice_record %s users %s"
479                      % (rspec.version, slice_record, users))
480
481         # ensure site record exists?
482         # ensure slice record exists
483         #Removed options in verify_slice SA 14/08/12
484         #Removed peer record in  verify_slice SA 18/07/13
485         sfa_slice = slices.verify_slice(slice_hrn, slice_record, sfa_peer)
486
487         # ensure person records exists
488         #verify_persons returns added persons but the return value
489         #is not used
490         #Removed peer record and sfa_peer in  verify_persons SA 18/07/13
491         slices.verify_persons(slice_hrn, sfa_slice, users, options=options)
492         #requested_attributes returned by rspec.version.get_slice_attributes()
493         #unused, removed SA 13/08/12
494         #rspec.version.get_slice_attributes()
495
496         logger.debug("CORTEXLABDRIVER.PY create_sliver slice %s " % (sfa_slice))
497
498         # add/remove slice from nodes
499
500         #requested_slivers = [node.get('component_id') \
501                     #for node in rspec.version.get_nodes_with_slivers()\
502                     #if node.get('authority_id') is self.cortexlab_api.root_auth]
503         #l = [ node for node in rspec.version.get_nodes_with_slivers() ]
504         #logger.debug("SLADRIVER \tcreate_sliver requested_slivers \
505                                     #requested_slivers %s  listnodes %s" \
506                                     #%(requested_slivers,l))
507         #verify_slice_nodes returns nodes, but unused here. Removed SA 13/08/12.
508         #slices.verify_slice_nodes(sfa_slice, requested_slivers, peer)
509
510         requested_xp_dict = self._process_requested_xp_dict(rspec)
511
512         logger.debug("CORTEXLABDRIVER.PY \tcreate_sliver  requested_xp_dict %s "
513                      % (requested_xp_dict))
514         #verify_slice_leases returns the leases , but the return value is unused
515         #here. Removed SA 13/08/12
516         slices.verify_slice_leases(sfa_slice,
517                                    requested_xp_dict, peer)
518
519         return aggregate.get_rspec(slice_xrn=slice_urn,
520                                    login=sfa_slice['login'],
521                                    version=rspec.version)
522
523     def delete_sliver(self, slice_urn, slice_hrn, creds, options):
524         """
525         Deletes the lease associated with the slice hrn and the credentials
526             if the slice belongs to iotlab. Answer to DeleteSliver.
527
528         :param slice_urn: urn of the slice
529         :param slice_hrn: name of the slice
530         :param creds: slice credenials
531         :type slice_urn: string
532         :type slice_hrn: string
533         :type creds: ? unused
534
535         :returns: 1 if the slice to delete was not found on iotlab,
536             True if the deletion was successful, False otherwise otherwise.
537
538         .. note:: Should really be named delete_leases because iotlab does
539             not have any slivers, but only deals with leases. However,
540             SFA api only have delete_sliver define so far. SA 13/05/2013
541         .. note:: creds are unused, and are not used either in the dummy driver
542              delete_sliver .
543         """
544
545         sfa_slice_list = self.cortexlab_api.GetSlices(
546             slice_filter=slice_hrn,
547             slice_filter_type='slice_hrn')
548
549         if not sfa_slice_list:
550             return 1
551
552         #Delete all leases in the slice
553         for sfa_slice in sfa_slice_list:
554             logger.debug("CORTEXLABDRIVER.PY delete_sliver slice %s" % (sfa_slice))
555             slices = CortexlabSlices(self)
556             # determine if this is a peer slice
557
558             peer = slices.get_peer(slice_hrn)
559
560             logger.debug("CORTEXLABDRIVER.PY delete_sliver peer %s \
561                 \r\n \t sfa_slice %s " % (peer, sfa_slice))
562             try:
563                 self.cortexlab_api.DeleteSliceFromNodes(sfa_slice)
564                 return True
565             except:
566                 return False
567
568     def list_resources (self, slice_urn, slice_hrn, creds, options):
569         """
570
571         List resources from the iotlab aggregate and returns a Rspec
572             advertisement with resources found when slice_urn and slice_hrn are
573             None (in case of resource discovery).
574             If a slice hrn and urn are provided, list experiment's slice
575             nodes in a rspec format. Answer to ListResources.
576             Caching unused.
577
578         :param slice_urn: urn of the slice
579         :param slice_hrn: name of the slice
580         :param creds: slice credenials
581         :type slice_urn: string
582         :type slice_hrn: string
583         :type creds: ? unused
584         :param options: options used when listing resources (list_leases, info,
585             geni_available)
586         :returns: rspec string in xml
587         :rtype: string
588
589         .. note:: creds are unused
590         """
591
592         #cached_requested = options.get('cached', True)
593
594         version_manager = VersionManager()
595         # get the rspec's return format from options
596         rspec_version = \
597             version_manager.get_version(options.get('geni_rspec_version'))
598         version_string = "rspec_%s" % (rspec_version)
599
600         #panos adding the info option to the caching key (can be improved)
601         if options.get('info'):
602             version_string = version_string + "_" + \
603                 options.get('info', 'default')
604
605         # Adding the list_leases option to the caching key
606         if options.get('list_leases'):
607             version_string = version_string + "_" + \
608             options.get('list_leases', 'default')
609
610         # Adding geni_available to caching key
611         if options.get('geni_available'):
612             version_string = version_string + "_" + \
613                 str(options.get('geni_available'))
614
615         # look in cache first
616         #if cached_requested and self.cache and not slice_hrn:
617             #rspec = self.cache.get(version_string)
618             #if rspec:
619                 #logger.debug("IotlabDriver.ListResources: \
620                                     #returning cached advertisement")
621                 #return rspec
622
623         #panos: passing user-defined options
624         aggregate = CortexlabAggregate(self)
625
626         rspec = aggregate.get_rspec(slice_xrn=slice_urn,
627                                     version=rspec_version, options=options)
628
629         # cache the result
630         #if self.cache and not slice_hrn:
631             #logger.debug("Iotlab.ListResources: stores advertisement in cache")
632             #self.cache.add(version_string, rspec)
633
634         return rspec
635
636
637     def list_slices(self, creds, options):
638         """Answer to ListSlices.
639
640         List slices belonging to iotlab, returns slice urns list.
641             No caching used. Options unused but are defined in the SFA method
642             api prototype.
643
644         :returns: slice urns list
645         :rtype: list
646
647         .. note:: creds are unused
648         """
649         # look in cache first
650         #if self.cache:
651             #slices = self.cache.get('slices')
652             #if slices:
653                 #logger.debug("PlDriver.list_slices returns from cache")
654                 #return slices
655
656         # get data from db
657
658         slices = self.cortexlab_api.GetSlices()
659         logger.debug("CORTEXLABDRIVER.PY \tlist_slices hrn %s \r\n \r\n"
660                      % (slices))
661         slice_hrns = [iotlab_slice['hrn'] for iotlab_slice in slices]
662
663         slice_urns = [hrn_to_urn(slice_hrn, 'slice')
664                       for slice_hrn in slice_hrns]
665
666         # cache the result
667         #if self.cache:
668             #logger.debug ("IotlabDriver.list_slices stores value in cache")
669             #self.cache.add('slices', slice_urns)
670
671         return slice_urns
672
673
674     def register(self, sfa_record, hrn, pub_key):
675         """
676         Adding new user, slice, node or site should not be handled
677             by SFA.
678
679         ..warnings:: should not be used. Different components are in charge of
680             doing this task. Adding nodes = OAR
681             Adding users = LDAP Iotlab
682             Adding slice = Import from LDAP users
683             Adding site = OAR
684
685         :param sfa_record: record provided by the client of the
686             Register API call.
687         :type sfa_record: dict
688         :param pub_key: public key of the user
689         :type pub_key: string
690
691         .. note:: DOES NOTHING. Returns -1.
692
693         """
694         return -1
695
696
697     def update(self, old_sfa_record, new_sfa_record, hrn, new_key):
698         """
699         No site or node record update allowed in Cortexlab. The only modifications
700         authorized here are key deletion/addition on an existing user and
701         password change. On an existing user, CAN NOT BE MODIFIED: 'first_name',
702         'last_name', 'email'. DOES NOT EXIST IN LDAP: 'phone', 'url', 'bio',
703         'title', 'accepted_aup'. A slice is bound to its user, so modifying the
704         user's ssh key should nmodify the slice's GID after an import procedure.
705
706         :param old_sfa_record: what is in the db for this hrn
707         :param new_sfa_record: what was passed to the update call
708         :param new_key: the new user's public key
709         :param hrn: the user's sfa hrn
710         :type old_sfa_record: dict
711         :type new_sfa_record: dict
712         :type new_key: string
713         :type hrn: string
714
715         TODO: needs review
716         .. seealso:: update in driver.py.
717
718         """
719         pointer = old_sfa_record['pointer']
720         old_sfa_record_type = old_sfa_record['type']
721
722         # new_key implemented for users only
723         if new_key and old_sfa_record_type not in ['user']:
724             raise UnknownSfaType(old_sfa_record_type)
725
726         if old_sfa_record_type == "user":
727             update_fields = {}
728             all_fields = new_sfa_record
729             for key in all_fields.keys():
730                 if key in ['key', 'password']:
731                     update_fields[key] = all_fields[key]
732
733             if new_key:
734                 # must check this key against the previous one if it exists
735                 persons = self.cortexlab_api.GetPersons([old_sfa_record])
736                 person = persons[0]
737                 keys = [person['pkey']]
738                 #Get all the person's keys
739                 keys_dict = self.cortexlab_api.GetKeys(keys)
740
741                 # Delete all stale keys, meaning the user has only one key
742                 #at a time
743                 #TODO: do we really want to delete all the other keys?
744                 #Is this a problem with the GID generation to have multiple
745                 #keys? SA 30/05/13
746                 key_exists = False
747                 if key in keys_dict:
748                     key_exists = True
749                 else:
750                     #remove all the other keys
751                     for key in keys_dict:
752                         self.cortexlab_api.DeleteKey(person, key)
753                     self.cortexlab_api.AddPersonKey(
754                         person, {'sshPublicKey': person['pkey']},
755                         {'sshPublicKey': new_key})
756         return True
757
758     def remove(self, sfa_record):
759         """
760
761         Removes users only. Mark the user as disabled in LDAP. The user and his
762         slice are then deleted from the db by running an import on the registry.
763
764         :param sfa_record: record is the existing sfa record in the db
765         :type sfa_record: dict
766
767         ..warning::As fas as the slice is concerned, here only the leases are
768             removed from the slice. The slice is record itself is not removed
769             from the db.
770
771         TODO: needs review
772
773         TODO : REMOVE SLICE FROM THE DB AS WELL? SA 14/05/2013,
774
775         TODO: return boolean for the slice part
776         """
777         sfa_record_type = sfa_record['type']
778         hrn = sfa_record['hrn']
779         if sfa_record_type == 'user':
780
781             #get user from iotlab ldap
782             person = self.cortexlab_api.GetPersons(sfa_record)
783             #No registering at a given site in Iotlab.
784             #Once registered to the LDAP, all iotlab sites are
785             #accesible.
786             if person:
787                 #Mark account as disabled in ldap
788                 return self.cortexlab_api.DeletePerson(sfa_record)
789
790         elif sfa_record_type == 'slice':
791             if self.cortexlab_api.GetSlices(slice_filter=hrn,
792                                          slice_filter_type='slice_hrn'):
793                 ret = self.cortexlab_api.DeleteSlice(sfa_record)
794             return True