Fixing the delete slice and lease feature.
[sfa.git] / sfa / senslab / slabdriver.py
index 051fd12..8ab31d7 100644 (file)
@@ -78,7 +78,7 @@ class SlabDriver(Driver):
                     
         logger.debug("SLABDRIVER \tfill_record_info records %s " %(record_list))
         if not isinstance(record_list, list):
-            #record_list = [record_list]
+            record_list = [record_list]
 
             
         try:
@@ -188,7 +188,6 @@ class SlabDriver(Driver):
                     
     def sliver_status(self, slice_urn, slice_hrn):
         """
-        
         Receive a status request for slice named urn/hrn 
         urn:publicid:IDN+senslab+nturro_slice hrn senslab.nturro_slice
         shall return a structure as described in
@@ -199,9 +198,7 @@ class SlabDriver(Driver):
         :type slice_urn: string
         :param slice_hrn: slice hrn
         :type slice_hrn: string
-        
-        .. note:: UNUSED. sface deprecated. SA May 7th 2013
-        
+
         """
         
         
@@ -294,7 +291,6 @@ class SlabDriver(Driver):
     @staticmethod                
     def get_user_record(hrn):        
         """ 
-        
         Returns the user record based on the hrn from the SFA DB .
         
         :param hrn: user's hrn
@@ -308,7 +304,6 @@ class SlabDriver(Driver):
      
     def testbed_name (self): 
         """ 
-        
         Returns testbed's name. 
         
         :rtype: string
@@ -337,13 +332,118 @@ class SlabDriver(Driver):
             'geni_request_rspec_versions': request_rspec_versions,
             'geni_ad_rspec_versions': ad_rspec_versions,
             }  
-               
-               
+            
+   
+         
+    def _get_requested_leases_list(self, rspec):
+        """
+        Process leases in rspec depending on the rspec version (format)
+        type. Find the lease requests in the rspec and creates
+        a lease request list with the mandatory information ( nodes,
+        start time and duration) of the valid leases (duration above or equal
+        to the senslab experiment minimum duration).
+        
+        :param rspec: rspec request received. 
+        :type rspec: RSpec
+        :return: list of lease requests found in the rspec
+        :rtype: list
+        """
+        requested_lease_list = []
+        for lease in rspec.version.get_leases():
+            single_requested_lease = {}
+            logger.debug("SLABDRIVER.PY \tcreate_sliver lease %s " %(lease))
+            
+            if not lease.get('lease_id'):
+                if get_authority(lease['component_id']) == \
+                                            self.slab_api.root_auth:
+                    single_requested_lease['hostname'] = \
+                                        slab_xrn_to_hostname(\
+                                        lease.get('component_id').strip())
+                    single_requested_lease['start_time'] = \
+                                                        lease.get('start_time')
+                    single_requested_lease['duration'] = lease.get('duration')
+                    #Check the experiment's duration is valid before adding
+                    #the lease to the requested leases list
+                    duration_in_seconds = \
+                            int(single_requested_lease['duration'])*60
+                    if duration_in_seconds > self.slab_api.GetMinExperimentDurationInSec() :
+                        requested_lease_list.append(single_requested_lease)
+                        
+        return requested_lease_list
+                        
+    @staticmethod                    
+    def _group_leases_by_start_time(requested_lease_list):
+        """
+        Create dict of leases by start_time, regrouping nodes reserved
+        at the same time, for the same amount of time so as to 
+        define one job on OAR.
+        
+        :param requested_lease_list: list of leases
+        :type requested_lease_list: list
+        :return: Dictionary with key = start time, value = list of leases
+        with the same start time. 
+        :rtype: dictionary
+        """
+
+        requested_job_dict = {}
+        for lease in requested_lease_list:
+            
+            #In case it is an asap experiment start_time is empty
+            if lease['start_time'] == '':
+                lease['start_time'] = '0' 
+                
+            if lease['start_time'] not in requested_job_dict:
+                if isinstance(lease['hostname'], str):
+                    lease['hostname'] = [lease['hostname']]
+
+                    
+                requested_job_dict[lease['start_time']] = lease
+                
+            else :
+                job_lease = requested_job_dict[lease['start_time']]
+                if lease['duration'] == job_lease['duration'] :
+                    job_lease['hostname'].append(lease['hostname'])  
+                                 
+        return requested_job_dict
+                
+    def _process_requested_jobs(self, rspec):
+        """
+        Turns the requested leases and information into a dictionary 
+        of requested jobs, grouped by starting time.
+        
+        :param rspec: RSpec received
+        :type rspec : RSpec
+        :rtype: dictionary 
+        """
+        requested_lease_list = self._get_requested_leases_list(rspec)
+        logger.debug("SLABDRIVER _process_requested_jobs requested_lease_list \
+        %s"%(requested_lease_list))
+        job_dict =  self._group_leases_by_start_time(requested_lease_list) 
+        logger.debug("SLABDRIVER _process_requested_jobs  job_dict\
+        %s"%(job_dict))
+        
+        return job_dict
+                
     def create_sliver (self, slice_urn, slice_hrn, creds, rspec_string, \
                                                              users, options):
         """ 
+        Answer to CreateSliver.
+        Creates the leases and slivers for the users from the information 
+        found in the rspec string.
+        Launch experiment on OAR if the requested leases is valid. Delete
+        no longer requested leases.
+        
+
+        :param creds: user's credentials 
+        :type creds: string
+        :param users: user record list
+        :type users: list
+        :param options:
+        :type options:
         
-        Creates the leases and slivers.
+        :return: a valid Rspec for the slice which has just been 
+        modified.
+        :rtype: RSpec
         
         
         """
@@ -385,70 +485,26 @@ class SlabDriver(Driver):
                                                     sfa_peer, options=options)                                           
         #requested_attributes returned by rspec.version.get_slice_attributes() 
         #unused, removed SA 13/08/12
-        rspec.version.get_slice_attributes()
+        #rspec.version.get_slice_attributes()
 
         logger.debug("SLABDRIVER.PY create_sliver slice %s " %(sfa_slice))
 
         # add/remove slice from nodes 
        
-        requested_slivers = [node.get('component_id') \
-                            for node in rspec.version.get_nodes_with_slivers()\
-                            if node.get('authority_id') is self.slab_api.root_auth]
-        l = [ node for node in rspec.version.get_nodes_with_slivers() ]
-        logger.debug("SLADRIVER \tcreate_sliver requested_slivers \
-                                    requested_slivers %s  listnodes %s" \
-                                    %(requested_slivers,l))
+        #requested_slivers = [node.get('component_id') \
+                            #for node in rspec.version.get_nodes_with_slivers()\
+                            #if node.get('authority_id') is self.slab_api.root_auth]
+        #l = [ node for node in rspec.version.get_nodes_with_slivers() ]
+        #logger.debug("SLADRIVER \tcreate_sliver requested_slivers \
+                                    #requested_slivers %s  listnodes %s" \
+                                    #%(requested_slivers,l))
         #verify_slice_nodes returns nodes, but unused here. Removed SA 13/08/12.
         #slices.verify_slice_nodes(sfa_slice, requested_slivers, peer) 
         
-        # add/remove leases
-        requested_lease_list = []
 
-
-
-        for lease in rspec.version.get_leases():
-            single_requested_lease = {}
-            logger.debug("SLABDRIVER.PY \tcreate_sliver lease %s " %(lease))
-            
-            if not lease.get('lease_id'):
-                if get_authority(lease['component_id']) == self.slab_api.root_auth:
-                    single_requested_lease['hostname'] = \
-                                        slab_xrn_to_hostname(\
-                                        lease.get('component_id').strip())
-                    single_requested_lease['start_time'] = \
-                                                        lease.get('start_time')
-                    single_requested_lease['duration'] = lease.get('duration')
-                    #Check the experiment's duration is valid before adding
-                    #the lease to the requested leases list
-                    duration_in_seconds = \
-                            int(single_requested_lease['duration'])*60
-                    if duration_in_seconds > self.slab_api.GetLeaseGranularity():
-                        requested_lease_list.append(single_requested_lease)
-                     
-        #Create dict of leases by start_time, regrouping nodes reserved
-        #at the same
-        #time, for the same amount of time = one job on OAR
-        requested_job_dict = {}
-        for lease in requested_lease_list:
-            
-            #In case it is an asap experiment start_time is empty
-            if lease['start_time'] == '':
-                lease['start_time'] = '0' 
-                
-            if lease['start_time'] not in requested_job_dict:
-                if isinstance(lease['hostname'], str):
-                    lease['hostname'] =  [lease['hostname']]
-                    
-                requested_job_dict[lease['start_time']] = lease
-                
-            else :
-                job_lease = requested_job_dict[lease['start_time']]
-                if lease['duration'] == job_lease['duration'] :
-                    job_lease['hostname'].append(lease['hostname'])
-                    
-          
-                
-                        
+        requested_job_dict = self._process_requested_jobs(rspec)
+                   
+             
         logger.debug("SLABDRIVER.PY \tcreate_sliver  requested_job_dict %s "\
                                                      %(requested_job_dict))    
         #verify_slice_leases returns the leases , but the return value is unused
@@ -461,6 +517,17 @@ class SlabDriver(Driver):
         
         
     def delete_sliver (self, slice_urn, slice_hrn, creds, options):
+        """
+        Deletes the lease associated with the slice hrn and the credentials
+        if the slice belongs to senslab. Answer to DeleteSliver.
+        
+        :return: 1 if the slice to delete was not found on senslab, 
+        True if the deletion was successful, False otherwise otherwise. 
+        
+        .. note:: Should really be named delete_leases because senslab does 
+        not have any slivers, but only deals with leases. However, SFA api only
+        have delete_sliver define so far. SA 13.05/2013
+        """
         
         sfa_slice_list  = self.slab_api.GetSlices(slice_filter = slice_hrn, \
                                             slice_filter_type = 'slice_hrn')
@@ -468,7 +535,7 @@ class SlabDriver(Driver):
         if not sfa_slice_list:
             return 1
         
-        #Delete all in the slice
+        #Delete all leases in the slice
         for sfa_slice in sfa_slice_list:
 
         
@@ -477,30 +544,31 @@ class SlabDriver(Driver):
             # determine if this is a peer slice
         
             peer = slices.get_peer(slice_hrn) 
-            #TODO delete_sliver SA : UnBindObjectFromPeer should be 
-            #used when there is another 
-            #senslab testbed, which is not the case 14/08/12 . 
-            
-            logger.debug("SLABDRIVER.PY delete_sliver peer %s \r\n \t sfa_slice %s " %(peer, sfa_slice))
+
+            logger.debug("SLABDRIVER.PY delete_sliver peer %s \
+            \r\n \t sfa_slice %s " %(peer, sfa_slice))
             try:
-                #if peer:
-                    #self.slab_api.UnBindObjectFromPeer('slice', \
-                                            #sfa_slice['record_id_slice'], \
-                                            #peer, None)
+              
                 self.slab_api.DeleteSliceFromNodes(sfa_slice)
                 return True
             except :
                 return False
-            #finally:
-                #if peer:
-                    #self.slab_api.BindObjectToPeer('slice', \
-                                            #sfa_slice['record_id_slice'], \
-                                            #peer, sfa_slice['peer_slice_id'])
-            #return 1
-                
+           
     
-    # first 2 args are None in case of resource discovery
     def list_resources (self, slice_urn, slice_hrn, creds, options):
+        """
+        List resources from the senslab aggregate and returns a Rspec 
+        advertisement with resources found when slice_urn and slice_hrn are None 
+        (in case of resource discovery).
+        If a slice hrn and urn are provided, list experiment's slice
+        nodes in a rspec format. Answer to ListResources.
+        Caching unused. 
+        :param options: options used when listing resources (list_leases, info,
+        geni_available) 
+        :return: rspec string in xml
+        :rtype: string 
+        """
+        
         #cached_requested = options.get('cached', True) 
     
         version_manager = VersionManager()
@@ -516,11 +584,13 @@ class SlabDriver(Driver):
                                         
         # Adding the list_leases option to the caching key
         if options.get('list_leases'):
-            version_string = version_string + "_"+options.get('list_leases', 'default')
+            version_string = version_string + "_" + \
+            options.get('list_leases', 'default')
             
         # Adding geni_available to caching key
         if options.get('geni_available'):
-            version_string = version_string + "_" + str(options.get('geni_available'))
+            version_string = version_string + "_" + \
+                str(options.get('geni_available'))
     
         # look in cache first
         #if cached_requested and self.cache and not slice_hrn:
@@ -532,8 +602,7 @@ class SlabDriver(Driver):
     
         #panos: passing user-defined options
         aggregate = SlabAggregate(self)
-        #origin_hrn = Credential(string=creds[0]).get_gid_caller().get_hrn()
-        #options.update({'origin_hrn':origin_hrn})
+       
         rspec =  aggregate.get_rspec(slice_xrn=slice_urn, \
                                         version=rspec_version, options=options)
        
@@ -546,6 +615,16 @@ class SlabDriver(Driver):
         
         
     def list_slices (self, creds, options):
+        """
+        Answer to ListSlices.
+        List slices belonging to senslab, returns slice urns list. 
+        No caching used. Options unused but are defined in the SFA method
+        api prototype. 
+        
+        :return: slice urns list
+        :rtype: list
+        
+        """
         # look in cache first
         #if self.cache:
             #slices = self.cache.get('slices')
@@ -575,16 +654,35 @@ class SlabDriver(Driver):
         Adding new user, slice, node or site should not be handled
         by SFA.
         
-        Adding nodes = OAR
+        ..warnings:: should not be used. Different components are in charge of 
+        doing this task. Adding nodes = OAR
         Adding users = LDAP Senslab
         Adding slice = Import from LDAP users
         Adding site = OAR
+        
+        :param sfa_record: record provided by the client of the 
+        Register API call. 
+        :type sfa_record: dict
         """
         return -1
             
       
     def update (self, old_sfa_record, new_sfa_record, hrn, new_key):
-        """No site or node record update allowed in Senslab."""
+        """No site or node record update allowed in Senslab.
+        The only modifications authorized here are key deletion/addition 
+        on an existing user and password change.
+        On an existing user, CAN NOT BE MODIFIED:
+        'first_name', 'last_name', 'email'
+         DOES NOT EXIST IN SENSLAB:
+         'phone', 'url', 'bio','title', 'accepted_aup',
+        A slice is bound to its user, so modifying the user's ssh key should
+        modify the slice's GID after an import procedure. 
+        
+        :param old_sfa_record: what is in the db for this hrn
+        :param new_sfa_record: what was passed to the Update call
+        
+        ..seealso:: update in driver.py. 
+        """
         
         pointer = old_sfa_record['pointer']
         old_sfa_record_type = old_sfa_record['type']
@@ -593,53 +691,60 @@ class SlabDriver(Driver):
         if new_key and old_sfa_record_type not in [ 'user' ]:
             raise UnknownSfaType(old_sfa_record_type)
         
-        #if (type == "authority"):
-            #self.shell.UpdateSite(pointer, new_sfa_record)
-    
-        if old_sfa_record_type == "slice":
-            slab_record = self.slab_api.sfa_fields_to_slab_fields(old_sfa_record_type, \
-                                                hrn, new_sfa_record)
-            if 'name' in slab_record:
-                slab_record.pop('name')
-                #Prototype should be UpdateSlice(self,
-                #auth, slice_id_or_name, slice_fields)
-                #Senslab cannot update slice since slice = job
-                #so we must delete and create another job
-                self.slab_api.UpdateSlice(pointer, slab_record)
     
-        elif old_sfa_record_type == "user":
+        if old_sfa_record_type == "user":
             update_fields = {}
             all_fields = new_sfa_record
             for key in all_fields.keys():
-                if key in ['first_name', 'last_name', 'title', 'email',
-                           'password', 'phone', 'url', 'bio', 'accepted_aup',
-                           'enabled']:
+                if key in ['key', 'password']:
                     update_fields[key] = all_fields[key]
-            self.slab_api.UpdatePerson(pointer, update_fields)
+           
     
             if new_key:
                 # must check this key against the previous one if it exists
-                persons = self.slab_api.GetPersons(['key_ids'])
+                persons = self.slab_api.GetPersons([old_sfa_record])
                 person = persons[0]
-                keys = person['key_ids']
-                keys = self.slab_api.GetKeys(person['key_ids'])
+                keys = [person['pkey']]
+                #Get all the person's keys
+                keys_dict = self.slab_api.GetKeys(keys)
                 
-                # Delete all stale keys
+                # Delete all stale keys, meaning the user has only one key
+                #at a time
+                #TODO: do we really want to delete all the other keys?
+                #Is this a problem with the GID generation to have multiple 
+                #keys? SA 30/05/13
                 key_exists = False
-                for key in keys:
-                    if new_key != key['key']:
-                        self.slab_api.DeleteKey(key['key_id'])
-                    else:
-                        key_exists = True
-                if not key_exists:
-                    self.slab_api.AddPersonKey(pointer, {'key_type': 'ssh', \
-                                                    'key': new_key})
-
-
+                if key in keys_dict:
+                    key_exists = True
+                else:
+                    #remove all the other keys
+                    for key in keys_dict:
+                        self.slab_api.DeleteKey(person, key)
+                    self.slab_api.AddPersonKey(person, \
+                    {'sshPublicKey': person['pkey']},{'sshPublicKey': new_key} )
+                    #self.slab_api.AddPersonKey(person, {'key_type': 'ssh', \
+                                                    #'key': new_key})
         return True
         
 
     def remove (self, sfa_record):
+        """
+        Removes users only. Mark the user as disabled in
+        LDAP. The user and his slice are then deleted from the db by running an 
+        import on the registry.
+        
+       
+        
+        :param sfa_record: record is the existing sfa record in the db 
+        :type sfa_record: dict
+        
+        ..warning::As fas as the slice is concerned, here only the leases are
+        removed from the slice. The slice is record itself is not removed from 
+        the db. 
+        TODO : REMOVE SLICE FROM THE DB AS WELL? SA 14/05/2013, 
+        
+        TODO: return boolean for the slice part 
+        """
         sfa_record_type = sfa_record['type']
         hrn = sfa_record['hrn']
         if sfa_record_type == 'user':
@@ -651,16 +756,15 @@ class SlabDriver(Driver):
             #accesible.
             if person :
                 #Mark account as disabled in ldap
-                self.slab_api.DeletePerson(sfa_record)
+                return self.slab_api.DeletePerson(sfa_record)
+
         elif sfa_record_type == 'slice':
             if self.slab_api.GetSlices(slice_filter = hrn, \
                                     slice_filter_type = 'slice_hrn'):
-                self.slab_api.DeleteSlice(sfa_record)
+                ret = self.slab_api.DeleteSlice(sfa_record)
 
-        #elif type == 'authority':
-            #if self.GetSites(pointer):
-                #self.DeleteSite(pointer)
 
-        return True
+
+            return True