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