c80f762d427ea8766d6cddb0b274f57210d2f6ed
[sfa.git] / sfa / senslab / slabdriver.py
1 from sfa.util.faults import SliverDoesNotExist, UnknownSfaType
2 from sfa.util.sfalogging import logger
3 from sfa.storage.alchemy import dbsession
4 from sfa.storage.model import RegRecord
5
6
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
15 from sfa.senslab.slabpostgres import SlabDB
16                                                      
17                                                                 
18 from sfa.senslab.slabaggregate import SlabAggregate, slab_xrn_to_hostname
19                                                             
20 from sfa.senslab.slabslices import SlabSlices
21
22
23 from sfa.senslab.slabapi import SlabTestbedAPI
24
25
26      
27 class SlabDriver(Driver):
28     """ Senslab Driver class inherited from Driver generic class.
29     
30     Contains methods compliant with the SFA standard and the testbed
31     infrastructure (calls to LDAP and OAR).
32     
33    .. seealso:: Driver class
34     
35     """
36     def __init__(self, config):
37         """ 
38         
39         Sets the senslab SFA config parameters ,
40         instanciates the testbed api and the senslab database.
41         
42         :param config: senslab SFA configuration object
43         :type config: Config object
44         """
45         Driver.__init__ (self, config)
46         self.config = config
47
48         self.db = SlabDB(config, debug = False)
49         self.slab_api = SlabTestbedAPI(config)
50         self.cache = None
51         
52     def augment_records_with_testbed_info (self, record_list ):
53         """ 
54         
55         Adds specific testbed info to the records. 
56         
57         :param record_list: list of sfa dictionaries records
58         :type record_list: list
59         :return: list of records with extended information in each record
60         :rtype: list
61         """
62         return self.fill_record_info (record_list)
63     
64     def fill_record_info(self, record_list):
65         """
66         For each SFA record, fill in the senslab specific and SFA specific
67         fields in the record. 
68         
69         :param record_list: list of sfa dictionaries records
70         :type record_list: list
71         :return: list of records with extended information in each record
72         :rtype: list
73         
74         .. warnings:: Should not be modifying record_list directly because modi
75         fication are kept outside the method's scope. Howerver, there is no 
76         other way to do it given the way it's called in registry manager.
77         """
78                     
79         logger.debug("SLABDRIVER \tfill_record_info records %s " %(record_list))
80         if not isinstance(record_list, list):
81             record_list = [record_list]
82
83             
84         try:
85             for record in record_list:
86                 #If the record is a SFA slice record, then add information 
87                 #about the user of this slice. This kind of 
88                 #information is in the Senslab's DB.
89                 if str(record['type']) == 'slice':
90                     if 'reg_researchers' in record and \
91                     isinstance(record['reg_researchers'], list) :
92                         record['reg_researchers'] = \
93                             record['reg_researchers'][0].__dict__
94                         record.update({'PI':[record['reg_researchers']['hrn']],
95                             'researcher': [record['reg_researchers']['hrn']],
96                             'name':record['hrn'], 
97                             'oar_job_id':[],
98                             'node_ids': [],
99                             'person_ids':[record['reg_researchers']['record_id']],
100                             'geni_urn':'',  #For client_helper.py compatibility
101                             'keys':'',  #For client_helper.py compatibility
102                             'key_ids':''})  #For client_helper.py compatibility
103                         
104                         
105                     #Get slab slice record and oar job id if any.
106                     recslice_list = self.slab_api.GetSlices(slice_filter = \
107                                                 str(record['hrn']),\
108                                                 slice_filter_type = 'slice_hrn')
109                     
110                    
111                     logger.debug("SLABDRIVER \tfill_record_info \
112                         TYPE SLICE RECUSER record['hrn'] %s ecord['oar_job_id']\
113                          %s " %(record['hrn'], record['oar_job_id']))
114                     del record['reg_researchers']
115                     try:
116                         for rec in recslice_list: 
117                             logger.debug("SLABDRIVER\r\n  \t  \
118                             fill_record_info oar_job_id %s " \
119                             %(rec['oar_job_id']))
120                             
121                             record['node_ids'] = [ self.slab_api.root_auth + \
122                                     hostname for hostname in rec['node_ids']]
123                     except KeyError:
124                         pass
125                         
126                     
127                     logger.debug( "SLABDRIVER.PY \t fill_record_info SLICE \
128                                     recslice_list  %s \r\n \t RECORD %s \r\n \
129                                     \r\n" %(recslice_list, record)) 
130                                     
131                 if str(record['type']) == 'user':
132                     #The record is a SFA user record.
133                     #Get the information about his slice from Senslab's DB
134                     #and add it to the user record.
135                     recslice_list = self.slab_api.GetSlices(\
136                             slice_filter = record['record_id'],\
137                             slice_filter_type = 'record_id_user')
138                                             
139                     logger.debug( "SLABDRIVER.PY \t fill_record_info TYPE USER \
140                                 recslice_list %s \r\n \t RECORD %s \r\n" \
141                                 %(recslice_list , record)) 
142                     #Append slice record in records list, 
143                     #therefore fetches user and slice info again(one more loop)
144                     #Will update PIs and researcher for the slice
145                    
146                     recuser = recslice_list[0]['reg_researchers']
147                     logger.debug( "SLABDRIVER.PY \t fill_record_info USER  \
148                                             recuser %s \r\n \r\n" %(recuser)) 
149                     recslice = {}
150                     recslice = recslice_list[0]
151                     recslice.update({'PI':[recuser['hrn']],
152                         'researcher': [recuser['hrn']],
153                         'name':record['hrn'], 
154                         'node_ids': [],
155                         'oar_job_id': [],
156                         'person_ids':[recuser['record_id']]}) 
157                     try:
158                         for rec in recslice_list:
159                             recslice['oar_job_id'].append(rec['oar_job_id'])
160                     except KeyError:
161                         pass
162                             
163                     recslice.update({'type':'slice', \
164                                                 'hrn':recslice_list[0]['hrn']})
165
166
167                     #GetPersons takes [] as filters 
168                     user_slab = self.slab_api.GetPersons([record])
169     
170                     
171                     record.update(user_slab[0])
172                     #For client_helper.py compatibility
173                     record.update( { 'geni_urn':'',
174                     'keys':'',
175                     'key_ids':'' })                
176                     record_list.append(recslice)
177                     
178                     logger.debug("SLABDRIVER.PY \tfill_record_info ADDING SLICE\
179                                 INFO TO USER records %s" %(record_list)) 
180                   
181
182         except TypeError, error:
183             logger.log_exc("SLABDRIVER \t fill_record_info  EXCEPTION %s"\
184                                                                      %(error))
185                               
186         return record_list
187                     
188                     
189     def sliver_status(self, slice_urn, slice_hrn):
190         """
191         Receive a status request for slice named urn/hrn 
192         urn:publicid:IDN+senslab+nturro_slice hrn senslab.nturro_slice
193         shall return a structure as described in
194         http://groups.geni.net/geni/wiki/GAPI_AM_API_V2#SliverStatus
195         NT : not sure if we should implement this or not, but used by sface.
196         
197         :param slice_urn: slice urn
198         :type slice_urn: string
199         :param slice_hrn: slice hrn
200         :type slice_hrn: string
201
202         """
203         
204         
205         #First get the slice with the slice hrn
206         slice_list =  self.slab_api.GetSlices(slice_filter = slice_hrn, \
207                                     slice_filter_type = 'slice_hrn')
208         
209         if len(slice_list) is 0:
210             raise SliverDoesNotExist("%s  slice_hrn" % (slice_hrn))
211         
212         #Used for fetching the user info witch comes along the slice info 
213         one_slice = slice_list[0] 
214
215         
216         #Make a list of all the nodes hostnames  in use for this slice
217         slice_nodes_list = []
218         #for single_slice in slice_list:
219             #for node in single_slice['node_ids']:
220                 #slice_nodes_list.append(node['hostname'])
221         #for node in one_slice:
222             #slice_nodes_list.append(node['hostname'])
223         slice_nodes_list = one_slice['node_ids']
224         #Get all the corresponding nodes details    
225         nodes_all = self.slab_api.GetNodes({'hostname':slice_nodes_list},
226                                 ['node_id', 'hostname','site','boot_state'])
227         nodeall_byhostname = dict([(one_node['hostname'], one_node) \
228                                             for one_node in nodes_all])  
229           
230           
231           
232         for single_slice in slice_list:
233
234               #For compatibility
235             top_level_status = 'empty' 
236             result = {}
237             result.fromkeys(\
238                 ['geni_urn','geni_error', 'pl_login','geni_status','geni_resources'], None)
239             result['pl_login'] = one_slice['reg_researchers'][0].hrn
240             logger.debug("Slabdriver - sliver_status Sliver status \
241                                         urn %s hrn %s single_slice  %s \r\n " \
242                                         %(slice_urn, slice_hrn, single_slice))
243                                         
244             if 'node_ids' not in single_slice:
245                 #No job in the slice
246                 result['geni_status'] = top_level_status
247                 result['geni_resources'] = [] 
248                 return result
249            
250             top_level_status = 'ready' 
251
252             #A job is running on Senslab for this slice
253             # report about the local nodes that are in the slice only
254          
255             result['geni_urn'] = slice_urn
256
257             resources = []
258             for node_hostname in single_slice['node_ids']:
259                 res = {}
260                 res['slab_hostname'] = node_hostname
261                 res['slab_boot_state'] = nodeall_byhostname[node_hostname]['boot_state']
262                 
263                 #res['pl_hostname'] = node['hostname']
264                 #res['pl_boot_state'] = \
265                             #nodeall_byhostname[node['hostname']]['boot_state']
266                 #res['pl_last_contact'] = strftime(self.time_format, \
267                                                     #gmtime(float(timestamp)))
268                 sliver_id =  Xrn(slice_urn, type='slice', \
269                         id=nodeall_byhostname[node_hostname]['node_id'], \
270                         authority=self.hrn).urn
271     
272                 res['geni_urn'] = sliver_id 
273                 #node_name  = node['hostname']
274                 if nodeall_byhostname[node_hostname]['boot_state'] == 'Alive':
275
276                     res['geni_status'] = 'ready'
277                 else:
278                     res['geni_status'] = 'failed'
279                     top_level_status = 'failed' 
280                     
281                 res['geni_error'] = ''
282         
283                 resources.append(res)
284                 
285             result['geni_status'] = top_level_status
286             result['geni_resources'] = resources 
287             logger.debug("SLABDRIVER \tsliver_statusresources %s res %s "\
288                                                     %(resources,res))
289             return result  
290                 
291     @staticmethod                
292     def get_user_record(hrn):        
293         """ 
294         Returns the user record based on the hrn from the SFA DB .
295         
296         :param hrn: user's hrn
297         :type hrn: string
298         :return : user record from SFA database
299         :rtype: RegUser
300         
301         """
302         return dbsession.query(RegRecord).filter_by(hrn = hrn).first() 
303          
304      
305     def testbed_name (self): 
306         """ 
307         Returns testbed's name. 
308         
309         :rtype: string
310         """
311         return self.hrn
312          
313     # 'geni_request_rspec_versions' and 'geni_ad_rspec_versions' are mandatory
314     def aggregate_version (self):
315         """
316         
317         Returns the testbed's supported rspec advertisement and 
318         request versions.
319         
320         :rtype: dict
321         """
322         version_manager = VersionManager()
323         ad_rspec_versions = []
324         request_rspec_versions = []
325         for rspec_version in version_manager.versions:
326             if rspec_version.content_type in ['*', 'ad']:
327                 ad_rspec_versions.append(rspec_version.to_dict())
328             if rspec_version.content_type in ['*', 'request']:
329                 request_rspec_versions.append(rspec_version.to_dict()) 
330         return {
331             'testbed':self.testbed_name(),
332             'geni_request_rspec_versions': request_rspec_versions,
333             'geni_ad_rspec_versions': ad_rspec_versions,
334             }  
335             
336    
337          
338     def _get_requested_leases_list(self, rspec):
339         """
340         Process leases in rspec depending on the rspec version (format)
341         type. Find the lease requests in the rspec and creates
342         a lease request list with the mandatory information ( nodes,
343         start time and duration) of the valid leases (duration above or equal
344         to the senslab experiment minimum duration).
345         
346         :param rspec: rspec request received. 
347         :type rspec: RSpec
348         :return: list of lease requests found in the rspec
349         :rtype: list
350         """
351         requested_lease_list = []
352         for lease in rspec.version.get_leases():
353             single_requested_lease = {}
354             logger.debug("SLABDRIVER.PY \tcreate_sliver lease %s " %(lease))
355             
356             if not lease.get('lease_id'):
357                 if get_authority(lease['component_id']) == \
358                                             self.slab_api.root_auth:
359                     single_requested_lease['hostname'] = \
360                                         slab_xrn_to_hostname(\
361                                         lease.get('component_id').strip())
362                     single_requested_lease['start_time'] = \
363                                                         lease.get('start_time')
364                     single_requested_lease['duration'] = lease.get('duration')
365                     #Check the experiment's duration is valid before adding
366                     #the lease to the requested leases list
367                     duration_in_seconds = \
368                             int(single_requested_lease['duration'])
369                     if duration_in_seconds > self.slab_api.GetMinExperimentDurationInSec() :
370                         requested_lease_list.append(single_requested_lease)
371                         
372         return requested_lease_list
373                         
374     @staticmethod                    
375     def _group_leases_by_start_time(requested_lease_list):
376         """
377         Create dict of leases by start_time, regrouping nodes reserved
378         at the same time, for the same amount of time so as to 
379         define one job on OAR.
380         
381         :param requested_lease_list: list of leases
382         :type requested_lease_list: list
383         :return: Dictionary with key = start time, value = list of leases
384         with the same start time. 
385         :rtype: dictionary
386         """
387
388         requested_job_dict = {}
389         for lease in requested_lease_list:
390             
391             #In case it is an asap experiment start_time is empty
392             if lease['start_time'] == '':
393                 lease['start_time'] = '0' 
394                 
395             if lease['start_time'] not in requested_job_dict:
396                 if isinstance(lease['hostname'], str):
397                     lease['hostname'] = [lease['hostname']]
398
399                     
400                 requested_job_dict[lease['start_time']] = lease
401                 
402             else :
403                 job_lease = requested_job_dict[lease['start_time']]
404                 if lease['duration'] == job_lease['duration'] :
405                     job_lease['hostname'].append(lease['hostname'])  
406                                  
407         return requested_job_dict
408                 
409     def _process_requested_jobs(self, rspec):
410         """
411         Turns the requested leases and information into a dictionary 
412         of requested jobs, grouped by starting time.
413         
414         :param rspec: RSpec received
415         :type rspec : RSpec
416         :rtype: dictionary 
417         """
418         requested_lease_list = self._get_requested_leases_list(rspec)
419         logger.debug("SLABDRIVER _process_requested_jobs requested_lease_list \
420         %s"%(requested_lease_list))
421         job_dict =  self._group_leases_by_start_time(requested_lease_list) 
422         logger.debug("SLABDRIVER _process_requested_jobs  job_dict\
423         %s"%(job_dict))
424         
425         return job_dict
426                 
427     def create_sliver (self, slice_urn, slice_hrn, creds, rspec_string, \
428                                                              users, options):
429         """ 
430         Answer to CreateSliver.
431         Creates the leases and slivers for the users from the information 
432         found in the rspec string.
433         Launch experiment on OAR if the requested leases is valid. Delete
434         no longer requested leases.
435         
436
437         :param creds: user's credentials 
438         :type creds: string
439         :param users: user record list
440         :type users: list
441         :param options:
442         :type options:
443         
444         :return: a valid Rspec for the slice which has just been 
445         modified.
446         :rtype: RSpec
447         
448         
449         """
450         aggregate = SlabAggregate(self)
451         
452         slices = SlabSlices(self)
453         peer = slices.get_peer(slice_hrn)
454         sfa_peer = slices.get_sfa_peer(slice_hrn)
455         slice_record = None 
456  
457         if not isinstance(creds, list):
458             creds = [creds]
459     
460         if users:
461             slice_record = users[0].get('slice_record', {}) 
462             logger.debug("SLABDRIVER.PY \t ===============create_sliver \t\
463                                         creds %s \r\n \r\n users %s" \
464                                         %(creds, users))
465             slice_record['user'] = {'keys':users[0]['keys'], \
466                                     'email':users[0]['email'], \
467                                     'hrn':slice_record['reg-researchers'][0]}
468         # parse rspec
469         rspec = RSpec(rspec_string)
470         logger.debug("SLABDRIVER.PY \t create_sliver \trspec.version \
471                                         %s slice_record %s users %s" \
472                                         %(rspec.version,slice_record, users))
473                                             
474
475         # ensure site record exists?
476         # ensure slice record exists
477         #Removed options to verify_slice SA 14/08/12
478         sfa_slice = slices.verify_slice(slice_hrn, slice_record, peer, \
479                                                     sfa_peer)
480                                                     
481         # ensure person records exists
482         #verify_persons returns added persons but since the return value
483         #is not used 
484         slices.verify_persons(slice_hrn, sfa_slice, users, peer, \
485                                                     sfa_peer, options=options)                                           
486         #requested_attributes returned by rspec.version.get_slice_attributes() 
487         #unused, removed SA 13/08/12
488         #rspec.version.get_slice_attributes()
489
490         logger.debug("SLABDRIVER.PY create_sliver slice %s " %(sfa_slice))
491
492         # add/remove slice from nodes 
493        
494         #requested_slivers = [node.get('component_id') \
495                             #for node in rspec.version.get_nodes_with_slivers()\
496                             #if node.get('authority_id') is self.slab_api.root_auth]
497         #l = [ node for node in rspec.version.get_nodes_with_slivers() ]
498         #logger.debug("SLADRIVER \tcreate_sliver requested_slivers \
499                                     #requested_slivers %s  listnodes %s" \
500                                     #%(requested_slivers,l))
501         #verify_slice_nodes returns nodes, but unused here. Removed SA 13/08/12.
502         #slices.verify_slice_nodes(sfa_slice, requested_slivers, peer) 
503         
504
505         requested_job_dict = self._process_requested_jobs(rspec)
506                    
507              
508         logger.debug("SLABDRIVER.PY \tcreate_sliver  requested_job_dict %s "\
509                                                      %(requested_job_dict))    
510         #verify_slice_leases returns the leases , but the return value is unused
511         #here. Removed SA 13/08/12           
512         slices.verify_slice_leases(sfa_slice, \
513                                     requested_job_dict, peer)
514         
515         return aggregate.get_rspec(slice_xrn=slice_urn, \
516                 login=sfa_slice['login'], version=rspec.version)
517         
518         
519     def delete_sliver (self, slice_urn, slice_hrn, creds, options):
520         """
521         Deletes the lease associated with the slice hrn and the credentials
522         if the slice belongs to senslab. Answer to DeleteSliver.
523         
524         :return: 1 if the slice to delete was not found on senslab, 
525         True if the deletion was successful, False otherwise otherwise. 
526         
527         .. note:: Should really be named delete_leases because senslab does 
528         not have any slivers, but only deals with leases. However, SFA api only
529         have delete_sliver define so far. SA 13.05/2013
530         """
531         
532         sfa_slice_list  = self.slab_api.GetSlices(slice_filter = slice_hrn, \
533                                             slice_filter_type = 'slice_hrn')
534         
535         if not sfa_slice_list:
536             return 1
537         
538         #Delete all leases in the slice
539         for sfa_slice in sfa_slice_list:
540
541         
542             logger.debug("SLABDRIVER.PY delete_sliver slice %s" %(sfa_slice))
543             slices = SlabSlices(self)
544             # determine if this is a peer slice
545         
546             peer = slices.get_peer(slice_hrn) 
547
548             logger.debug("SLABDRIVER.PY delete_sliver peer %s \
549             \r\n \t sfa_slice %s " %(peer, sfa_slice))
550             try:
551               
552                 self.slab_api.DeleteSliceFromNodes(sfa_slice)
553                 return True
554             except :
555                 return False
556            
557     
558     def list_resources (self, slice_urn, slice_hrn, creds, options):
559         """
560         List resources from the senslab aggregate and returns a Rspec 
561         advertisement with resources found when slice_urn and slice_hrn are None 
562         (in case of resource discovery).
563         If a slice hrn and urn are provided, list experiment's slice
564         nodes in a rspec format. Answer to ListResources.
565         Caching unused. 
566         :param options: options used when listing resources (list_leases, info,
567         geni_available) 
568         :return: rspec string in xml
569         :rtype: string 
570         """
571         
572         #cached_requested = options.get('cached', True) 
573     
574         version_manager = VersionManager()
575         # get the rspec's return format from options
576         rspec_version = \
577                 version_manager.get_version(options.get('geni_rspec_version'))
578         version_string = "rspec_%s" % (rspec_version)
579     
580         #panos adding the info option to the caching key (can be improved)
581         if options.get('info'):
582             version_string = version_string + "_" + \
583                                         options.get('info', 'default')
584                                         
585         # Adding the list_leases option to the caching key
586         if options.get('list_leases'):
587             version_string = version_string + "_" + \
588             options.get('list_leases', 'default')
589             
590         # Adding geni_available to caching key
591         if options.get('geni_available'):
592             version_string = version_string + "_" + \
593                 str(options.get('geni_available'))
594     
595         # look in cache first
596         #if cached_requested and self.cache and not slice_hrn:
597             #rspec = self.cache.get(version_string)
598             #if rspec:
599                 #logger.debug("SlabDriver.ListResources: \
600                                     #returning cached advertisement")
601                 #return rspec 
602     
603         #panos: passing user-defined options
604         aggregate = SlabAggregate(self)
605        
606         rspec =  aggregate.get_rspec(slice_xrn=slice_urn, \
607                                         version=rspec_version, options=options)
608        
609         # cache the result
610         #if self.cache and not slice_hrn:
611             #logger.debug("Slab.ListResources: stores advertisement in cache")
612             #self.cache.add(version_string, rspec)
613     
614         return rspec
615         
616         
617     def list_slices (self, creds, options):
618         """
619         Answer to ListSlices.
620         List slices belonging to senslab, returns slice urns list. 
621         No caching used. Options unused but are defined in the SFA method
622         api prototype. 
623         
624         :return: slice urns list
625         :rtype: list
626         
627         """
628         # look in cache first
629         #if self.cache:
630             #slices = self.cache.get('slices')
631             #if slices:
632                 #logger.debug("PlDriver.list_slices returns from cache")
633                 #return slices
634     
635         # get data from db 
636
637         slices = self.slab_api.GetSlices()        
638         logger.debug("SLABDRIVER.PY \tlist_slices hrn %s \r\n \r\n" %(slices))        
639         slice_hrns = [slab_slice['hrn'] for slab_slice in slices]
640
641         slice_urns = [hrn_to_urn(slice_hrn, 'slice') \
642                                                 for slice_hrn in slice_hrns]
643
644         # cache the result
645         #if self.cache:
646             #logger.debug ("SlabDriver.list_slices stores value in cache")
647             #self.cache.add('slices', slice_urns) 
648     
649         return slice_urns
650     
651    
652     def register (self, sfa_record, hrn, pub_key):
653         """ 
654         Adding new user, slice, node or site should not be handled
655         by SFA.
656         
657         ..warnings:: should not be used. Different components are in charge of 
658         doing this task. Adding nodes = OAR
659         Adding users = LDAP Senslab
660         Adding slice = Import from LDAP users
661         Adding site = OAR
662         
663         :param sfa_record: record provided by the client of the 
664         Register API call. 
665         :type sfa_record: dict
666         """
667         return -1
668             
669       
670     def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
671         """No site or node record update allowed in Senslab.
672         The only modifications authorized here are key deletion/addition 
673         on an existing user and password change.
674         On an existing user, CAN NOT BE MODIFIED:
675         'first_name', 'last_name', 'email'
676          DOES NOT EXIST IN SENSLAB:
677          'phone', 'url', 'bio','title', 'accepted_aup',
678         A slice is bound to its user, so modifying the user's ssh key should
679         modify the slice's GID after an import procedure. 
680         
681         :param old_sfa_record: what is in the db for this hrn
682         :param new_sfa_record: what was passed to the Update call
683         
684         ..seealso:: update in driver.py. 
685         """
686         
687         pointer = old_sfa_record['pointer']
688         old_sfa_record_type = old_sfa_record['type']
689
690         # new_key implemented for users only
691         if new_key and old_sfa_record_type not in [ 'user' ]:
692             raise UnknownSfaType(old_sfa_record_type)
693         
694     
695         if old_sfa_record_type == "user":
696             update_fields = {}
697             all_fields = new_sfa_record
698             for key in all_fields.keys():
699                 if key in ['key', 'password']:
700                     update_fields[key] = all_fields[key]
701            
702     
703             if new_key:
704                 # must check this key against the previous one if it exists
705                 persons = self.slab_api.GetPersons([old_sfa_record])
706                 person = persons[0]
707                 keys = [person['pkey']]
708                 #Get all the person's keys
709                 keys_dict = self.slab_api.GetKeys(keys)
710                 
711                 # Delete all stale keys, meaning the user has only one key
712                 #at a time
713                 #TODO: do we really want to delete all the other keys?
714                 #Is this a problem with the GID generation to have multiple 
715                 #keys? SA 30/05/13
716                 key_exists = False
717                 if key in keys_dict:
718                     key_exists = True
719                 else:
720                     #remove all the other keys
721                     for key in keys_dict:
722                         self.slab_api.DeleteKey(person, key)
723                     self.slab_api.AddPersonKey(person, \
724                     {'sshPublicKey': person['pkey']},{'sshPublicKey': new_key} )
725                     #self.slab_api.AddPersonKey(person, {'key_type': 'ssh', \
726                                                     #'key': new_key})
727         return True
728         
729
730     def remove (self, sfa_record):
731         """
732         Removes users only. Mark the user as disabled in
733         LDAP. The user and his slice are then deleted from the db by running an 
734         import on the registry.
735         
736        
737         
738         :param sfa_record: record is the existing sfa record in the db 
739         :type sfa_record: dict
740         
741         ..warning::As fas as the slice is concerned, here only the leases are
742         removed from the slice. The slice is record itself is not removed from 
743         the db. 
744         TODO : REMOVE SLICE FROM THE DB AS WELL? SA 14/05/2013, 
745         
746         TODO: return boolean for the slice part 
747         """
748         sfa_record_type = sfa_record['type']
749         hrn = sfa_record['hrn']
750         if sfa_record_type == 'user':
751
752             #get user from senslab ldap  
753             person = self.slab_api.GetPersons(sfa_record)
754             #No registering at a given site in Senslab.
755             #Once registered to the LDAP, all senslab sites are
756             #accesible.
757             if person :
758                 #Mark account as disabled in ldap
759                 return self.slab_api.DeletePerson(sfa_record)
760
761         elif sfa_record_type == 'slice':
762             if self.slab_api.GetSlices(slice_filter = hrn, \
763                                     slice_filter_type = 'slice_hrn'):
764                 ret = self.slab_api.DeleteSlice(sfa_record)
765
766
767
768             return True
769             
770