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