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