substantial cleanup of the renew method and client
Thierry Parmentelat [Wed, 28 May 2014 14:37:46 +0000 (16:37 +0200)]
* sfi -l aka --as-long-as-possible is supported in sfi
* sfi renew <> +2d / +3w / +4m is now working as well as the other time formats (int, rfc3339...)
* most importantly the final expiration time is trimmed to the min of credential expiration and max_slice_renewal, but goes on with these values instead of whining

sfa/client/sfi.py
sfa/managers/aggregate_manager.py
sfa/methods/Renew.py
sfa/util/sfatime.py

index fc4e609..4cceb51 100644 (file)
@@ -448,6 +448,9 @@ class Sfi:
         if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
                               help="show credential(s) used in human-readable form")
+        if canonical in ("renew"):
+            parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
+                              help="renew as long as possible")
         # registy filter option
         if canonical in ("list", "show", "remove"):
             parser.add_option("-t", "--type", dest="type", type="choice",
@@ -1396,7 +1399,12 @@ use this if you mean an authority instead""")
             print value
         return value
 
-    @declare_command("slice_hrn [<sliver_urn>...] time","sfi renew onelab.ple.heartbeat 2015-04-31")
+    @declare_command("slice_hrn [<sliver_urn>...] time",
+                     "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
+                                "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
+                                "sfi renew onelab.ple.heartbeat +5d",
+                                "sfi renew onelab.ple.heartbeat +3w",
+                                "sfi renew onelab.ple.heartbeat +2m",]))
     def renew(self, options, args):
         """
         renew slice (Renew)
@@ -1423,6 +1431,8 @@ use this if you mean an authority instead""")
         # options and call_id when supported
         api_options = {}
         api_options['call_id']=unique_call_id()
+        if options.alap:
+            api_options['geni_extend_alap']=True
         if options.show_credential:
             show_credentials(creds)
         result =  server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
index 2864567..1521103 100644 (file)
@@ -147,14 +147,6 @@ class AggregateManager:
         call_id = options.get('call_id')
         if Callids().already_handled(call_id): return True
 
-        # extend as long as possible
-        if options.get('geni_extend_alap'):
-            now = datetime.datetime.now()
-            requested = utcparse(expiration_time)
-            max = adjust_datetime(now, days=int(api.config.SFA_MAX_SLICE_RENEW))
-            if requested > max:
-                expiration_time = max
-
         return api.driver.renew(xrns, expiration_time, options)
 
     def PerformOperationalAction(self, api, xrns, creds, action, options={}):
index c75c781..0a7ac1a 100644 (file)
@@ -3,7 +3,7 @@ import datetime
 from sfa.util.faults import InsufficientRights
 from sfa.util.xrn import urn_to_hrn
 from sfa.util.method import Method
-from sfa.util.sfatime import utcparse
+from sfa.util.sfatime import utcparse, add_datetime
 
 from sfa.trust.credential import Credential
 
@@ -14,7 +14,7 @@ class Renew(Method):
     Renews the resources in the specified slice or slivers by 
     extending the lifetime.
     
-    @param surn ([string]) List of URNs of to renew
+    @param urns ([string]) List of URNs of to renew
     @param credentials ([string]) of credentials
     @param expiration_time (string) requested time of expiration
     @param options (dict) options
@@ -37,16 +37,32 @@ class Renew(Method):
                                                               options=options)
         the_credential = Credential(cred=valid_creds[0])
         actual_caller_hrn = the_credential.actual_caller_hrn()
-        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-urns: %s\texp:%s\tmethod-name: %s"%\
+        self.api.logger.info("interface: %s\tcaller-hrn: %s\ttarget-urns: %s\texpiration:%s\tmethod-name: %s"%\
                              (self.api.interface, actual_caller_hrn, urns, expiration_time,self.name))
 
 
+        # extend as long as possible : take the min of requested and now+SFA_MAX_SLICE_RENEW
+        if options.get('geni_extend_alap'):
+            # ignore requested time and set to max
+            expiration_time = add_datetime(datetime.datetime.utcnow(), days=int(self.api.config.SFA_MAX_SLICE_RENEW))
+
         # Validate that the time does not go beyond the credential's expiration time
-        requested_time = utcparse(expiration_time)
+        requested_expire = utcparse(expiration_time)
+        self.api.logger.info("requested_expire = %s"%requested_expire)
+        credential_expire = the_credential.get_expiration()
+        self.api.logger.info("credential_expire = %s"%credential_expire)
         max_renew_days = int(self.api.config.SFA_MAX_SLICE_RENEW)
-        if requested_time > Credential(cred=valid_creds[0]).get_expiration():
-            raise InsufficientRights('Renewsliver: Credential expires before requested expiration time')
-        if requested_time > datetime.datetime.utcnow() + datetime.timedelta(days=max_renew_days):
-            raise Exception('Cannot renew > %s days from now' % max_renew_days)
-        return self.api.manager.Renew(self.api, urns, creds, expiration_time, options)
+        max_expire = datetime.datetime.utcnow() + datetime.timedelta (days=max_renew_days)
+        if requested_expire > credential_expire:
+            # used to throw an InsufficientRights exception here, this was not right
+            self.api.logger.warning("Requested expiration %s, after credential expiration (%s) -> trimming to the latter/sooner"%\
+                                    (requested_expire, credential_expire))
+            requested_expire = credential_expire
+        if requested_expire > max_expire:
+            # likewise
+            self.api.logger.warning("Requested expiration %s, after maximal expiration %s days (%s) -> trimming to the latter/sooner"%\
+                                    (requested_expire, self.api.config.SFA_MAX_SLICE_RENEW,max_expire))
+            requested_expire = max_expire
+
+        return self.api.manager.Renew(self.api, urns, creds, requested_expire, options)
     
index d14f44b..27418b2 100644 (file)
@@ -24,6 +24,7 @@ from types import StringTypes
 import dateutil.parser
 import datetime
 import time
+import re
 
 from sfa.util.sfalogging import logger
 
@@ -35,16 +36,36 @@ the timezone, so that it's compatible with normal datetime.datetime objects.
 
 For safety this can also handle inputs that are either timestamps, or datetimes
 """
+
+    def handle_shorthands (input):
+        """recognize string like +5d or +3w or +2m as 
+        2 days, 3 weeks or 2 months from now"""
+        if input.startswith('+'):
+            match=re.match (r"([0-9]+)([dwm])",input[1:])
+            if match:
+                how_many=int(match.group(1))
+                what=match.group(2)
+                if what == 'd':         d=datetime.timedelta(days=how_many)
+                elif what == 'w':       d=datetime.timedelta(weeks=how_many)
+                elif what == 'm':       d=datetime.timedelta(weeks=4*how_many)
+                return datetime.datetime.utcnow()+d
+
     # prepare the input for the checks below by
     # casting strings ('1327098335') to ints
     if isinstance(input, StringTypes):
         try:
             input = int(input)
         except ValueError:
-            pass
+            try:
+                new_input=handle_shorthands(input)
+                if new_input is not None: input=new_input
+            except:
+                import traceback
+                traceback.print_exc()
 
+    #################### here we go
     if isinstance (input, datetime.datetime):
-        logger.warn ("argument to utcparse already a datetime - doing nothing")
+        #logger.info ("argument to utcparse already a datetime - doing nothing")
         return input
     elif isinstance (input, StringTypes):
         t = dateutil.parser.parse(input)
@@ -65,9 +86,23 @@ def datetime_to_utc(input):
 def datetime_to_epoch(input):
     return int(time.mktime(input.timetuple()))
 
-def adjust_datetime(input, days=0, hours=0, minutes=0, seconds=0):
+def add_datetime(input, days=0, hours=0, minutes=0, seconds=0):
     """
     Adjust the input date by the specified delta (in seconds).
     """
     dt = utcparse(input)
     return dt + datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)
+
+if __name__ == '__main__':
+    for input in [
+            '+2d',
+            '+3w',
+            '+2m',
+            1401282977.575632,
+            1401282977,
+            '1401282977',
+            '2014-05-28',
+            '2014-05-28T15:18',
+            '2014-05-28T15:18:30',
+    ]:
+        print "input=%20s -> parsed %s"%(input,datetime_to_string(utcparse(input)))